summaryrefslogtreecommitdiffstats
path: root/uClinux-2.4.31-uc0/net
diff options
context:
space:
mode:
authorOliver Schinagl <oliver@schinagl.nl>2011-04-27 13:13:05 (GMT)
committerOliver Schinagl <oliver@schinagl.nl>2011-04-27 13:13:05 (GMT)
commitcb589e64ddfbc502e8b1189ec7253c43b42cd183 (patch)
treea45aa4df23db84c279f39bd2c894ecf6bada0289 /uClinux-2.4.31-uc0/net
parentd53ae4b2067e5e7c4f5a0b9a234a89e0582c2e84 (diff)
downloadopenipcam-cb589e64ddfbc502e8b1189ec7253c43b42cd183.zip
openipcam-cb589e64ddfbc502e8b1189ec7253c43b42cd183.tar.gz
openipcam-cb589e64ddfbc502e8b1189ec7253c43b42cd183.tar.bz2
linux-2.4.31 with uCLinux uc0 pre-patched
Diffstat (limited to 'uClinux-2.4.31-uc0/net')
-rw-r--r--uClinux-2.4.31-uc0/net/802/Makefile39
-rw-r--r--uClinux-2.4.31-uc0/net/802/TODO29
-rw-r--r--uClinux-2.4.31-uc0/net/802/cl2llc.c615
-rw-r--r--uClinux-2.4.31-uc0/net/802/cl2llc.pre615
-rw-r--r--uClinux-2.4.31-uc0/net/802/fc.c135
-rw-r--r--uClinux-2.4.31-uc0/net/802/fddi.c166
-rw-r--r--uClinux-2.4.31-uc0/net/802/hippi.c154
-rw-r--r--uClinux-2.4.31-uc0/net/802/llc_macinit.c214
-rw-r--r--uClinux-2.4.31-uc0/net/802/llc_sendpdu.c357
-rw-r--r--uClinux-2.4.31-uc0/net/802/llc_utility.c253
-rw-r--r--uClinux-2.4.31-uc0/net/802/p8022.c153
-rw-r--r--uClinux-2.4.31-uc0/net/802/p8023.c62
-rw-r--r--uClinux-2.4.31-uc0/net/802/pseudo/Makefile13
-rw-r--r--uClinux-2.4.31-uc0/net/802/pseudo/actionnm.awk27
-rw-r--r--uClinux-2.4.31-uc0/net/802/pseudo/actionnm.h51
-rw-r--r--uClinux-2.4.31-uc0/net/802/pseudo/compile.awk57
-rw-r--r--uClinux-2.4.31-uc0/net/802/pseudo/opcd2num.sed72
-rw-r--r--uClinux-2.4.31-uc0/net/802/pseudo/opcodes72
-rw-r--r--uClinux-2.4.31-uc0/net/802/pseudo/opcodesnm.h23
-rw-r--r--uClinux-2.4.31-uc0/net/802/pseudo/pseudocode780
-rw-r--r--uClinux-2.4.31-uc0/net/802/pseudo/pseudocode.h287
-rw-r--r--uClinux-2.4.31-uc0/net/802/psnap.c164
-rw-r--r--uClinux-2.4.31-uc0/net/802/sysctl_net_802.c28
-rw-r--r--uClinux-2.4.31-uc0/net/802/tr.c558
-rw-r--r--uClinux-2.4.31-uc0/net/802/transit/Makefile13
-rw-r--r--uClinux-2.4.31-uc0/net/802/transit/compile.awk81
-rw-r--r--uClinux-2.4.31-uc0/net/802/transit/pdutr.h309
-rw-r--r--uClinux-2.4.31-uc0/net/802/transit/pdutr.pre1121
-rw-r--r--uClinux-2.4.31-uc0/net/802/transit/timertr.h157
-rw-r--r--uClinux-2.4.31-uc0/net/802/transit/timertr.pre527
-rw-r--r--uClinux-2.4.31-uc0/net/8021q/Makefile15
-rw-r--r--uClinux-2.4.31-uc0/net/8021q/vlan.c785
-rw-r--r--uClinux-2.4.31-uc0/net/8021q/vlan.h79
-rw-r--r--uClinux-2.4.31-uc0/net/8021q/vlan_dev.c904
-rw-r--r--uClinux-2.4.31-uc0/net/8021q/vlanproc.c470
-rw-r--r--uClinux-2.4.31-uc0/net/8021q/vlanproc.h12
-rw-r--r--uClinux-2.4.31-uc0/net/Config.in116
-rw-r--r--uClinux-2.4.31-uc0/net/Makefile72
-rw-r--r--uClinux-2.4.31-uc0/net/TUNABLE50
-rw-r--r--uClinux-2.4.31-uc0/net/appletalk/Makefile20
-rw-r--r--uClinux-2.4.31-uc0/net/appletalk/aarp.c986
-rw-r--r--uClinux-2.4.31-uc0/net/appletalk/ddp.c2024
-rw-r--r--uClinux-2.4.31-uc0/net/appletalk/sysctl_net_atalk.c61
-rw-r--r--uClinux-2.4.31-uc0/net/atm/Makefile58
-rw-r--r--uClinux-2.4.31-uc0/net/atm/addr.c141
-rw-r--r--uClinux-2.4.31-uc0/net/atm/addr.h18
-rw-r--r--uClinux-2.4.31-uc0/net/atm/atm_misc.c173
-rw-r--r--uClinux-2.4.31-uc0/net/atm/br2684.c813
-rw-r--r--uClinux-2.4.31-uc0/net/atm/clip.c798
-rw-r--r--uClinux-2.4.31-uc0/net/atm/common.c1161
-rw-r--r--uClinux-2.4.31-uc0/net/atm/common.h48
-rw-r--r--uClinux-2.4.31-uc0/net/atm/ipcommon.c71
-rw-r--r--uClinux-2.4.31-uc0/net/atm/ipcommon.h25
-rw-r--r--uClinux-2.4.31-uc0/net/atm/ipv6atm.c419
-rw-r--r--uClinux-2.4.31-uc0/net/atm/lec.c2194
-rw-r--r--uClinux-2.4.31-uc0/net/atm/lec.h165
-rw-r--r--uClinux-2.4.31-uc0/net/atm/lec_arpc.h116
-rw-r--r--uClinux-2.4.31-uc0/net/atm/mpc.c1478
-rw-r--r--uClinux-2.4.31-uc0/net/atm/mpc.h67
-rw-r--r--uClinux-2.4.31-uc0/net/atm/mpoa_caches.c576
-rw-r--r--uClinux-2.4.31-uc0/net/atm/mpoa_caches.h96
-rw-r--r--uClinux-2.4.31-uc0/net/atm/mpoa_proc.c348
-rw-r--r--uClinux-2.4.31-uc0/net/atm/pppoatm.c365
-rw-r--r--uClinux-2.4.31-uc0/net/atm/proc.c796
-rw-r--r--uClinux-2.4.31-uc0/net/atm/protocols.h16
-rw-r--r--uClinux-2.4.31-uc0/net/atm/pvc.c157
-rw-r--r--uClinux-2.4.31-uc0/net/atm/raw.c93
-rw-r--r--uClinux-2.4.31-uc0/net/atm/resources.c405
-rw-r--r--uClinux-2.4.31-uc0/net/atm/resources.h29
-rw-r--r--uClinux-2.4.31-uc0/net/atm/signaling.c260
-rw-r--r--uClinux-2.4.31-uc0/net/atm/signaling.h33
-rw-r--r--uClinux-2.4.31-uc0/net/atm/svc.c574
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/Config.in36
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/Makefile25
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/af_ax25.c1884
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_addr.c303
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_dev.c213
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_ds_in.c312
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_ds_subr.c217
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_ds_timer.c224
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_iface.c268
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_in.c488
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_ip.c222
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_out.c409
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_route.c452
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_std_in.c467
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_std_subr.c102
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_std_timer.c170
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_subr.c315
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_timer.c256
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/ax25_uid.c178
-rw-r--r--uClinux-2.4.31-uc0/net/ax25/sysctl_net_ax25.c162
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/Config.in22
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/Makefile31
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/af_bluetooth.c336
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/bnep/Config.in11
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/bnep/Makefile10
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/bnep/Makefile.lib1
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/bnep/bnep.h185
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/bnep/core.c716
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/bnep/netdev.c254
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/bnep/sock.c210
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/cmtp/Config.in7
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/cmtp/Makefile10
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/cmtp/capi.c707
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/cmtp/cmtp.h138
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/cmtp/core.c515
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/cmtp/sock.c208
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/hci_conn.c435
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/hci_core.c1410
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/hci_event.c910
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/hci_sock.c648
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/l2cap.c2222
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/lib.c175
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/rfcomm/Config.in10
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/rfcomm/Makefile11
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/rfcomm/core.c1940
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/rfcomm/crc.c71
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/rfcomm/sock.c847
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/rfcomm/tty.c957
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/sco.c1018
-rw-r--r--uClinux-2.4.31-uc0/net/bluetooth/syms.c81
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/Makefile23
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br.c89
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_device.c137
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_fdb.c331
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_forward.c155
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_if.c291
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_input.c182
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_ioctl.c248
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_netfilter.c720
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_notify.c75
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_private.h209
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_private_stp.h53
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_private_timer.h54
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_stp.c480
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_stp_bpdu.c236
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_stp_if.c228
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/br_stp_timer.c184
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/Config.in22
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/Makefile33
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_arp.c149
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_dnat.c65
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_ip.c121
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_log.c152
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_mark.c66
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_mark_m.c61
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_redirect.c71
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_snat.c64
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_vlan.c259
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/ebtable_broute.c79
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/ebtable_filter.c90
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/ebtable_nat.c96
-rw-r--r--uClinux-2.4.31-uc0/net/bridge/netfilter/ebtables.c1490
-rw-r--r--uClinux-2.4.31-uc0/net/core/Makefile35
-rw-r--r--uClinux-2.4.31-uc0/net/core/datagram.c448
-rw-r--r--uClinux-2.4.31-uc0/net/core/dev.c2942
-rw-r--r--uClinux-2.4.31-uc0/net/core/dev_mcast.c275
-rw-r--r--uClinux-2.4.31-uc0/net/core/dst.c224
-rw-r--r--uClinux-2.4.31-uc0/net/core/dv.c559
-rw-r--r--uClinux-2.4.31-uc0/net/core/ethtool.c797
-rw-r--r--uClinux-2.4.31-uc0/net/core/filter.c497
-rw-r--r--uClinux-2.4.31-uc0/net/core/iovec.c279
-rw-r--r--uClinux-2.4.31-uc0/net/core/neighbour.c2280
-rw-r--r--uClinux-2.4.31-uc0/net/core/netfilter.c679
-rw-r--r--uClinux-2.4.31-uc0/net/core/pktgen.c1405
-rw-r--r--uClinux-2.4.31-uc0/net/core/profile.c293
-rw-r--r--uClinux-2.4.31-uc0/net/core/rtnetlink.c530
-rw-r--r--uClinux-2.4.31-uc0/net/core/scm.c271
-rw-r--r--uClinux-2.4.31-uc0/net/core/skbuff.c1274
-rw-r--r--uClinux-2.4.31-uc0/net/core/sock.c1232
-rw-r--r--uClinux-2.4.31-uc0/net/core/sysctl_net_core.c98
-rw-r--r--uClinux-2.4.31-uc0/net/core/utils.c73
-rw-r--r--uClinux-2.4.31-uc0/net/core/wireless.c1282
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/Config.in12
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/Makefile13
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/README8
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/TODO57
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/af_decnet.c2325
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/dn_dev.c1257
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/dn_fib.c661
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/dn_neigh.c606
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/dn_nsp_in.c910
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/dn_nsp_out.c730
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/dn_route.c1306
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/dn_rules.c371
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/dn_table.c901
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/dn_timer.c155
-rw-r--r--uClinux-2.4.31-uc0/net/decnet/sysctl_net_decnet.c389
-rw-r--r--uClinux-2.4.31-uc0/net/econet/Makefile16
-rw-r--r--uClinux-2.4.31-uc0/net/econet/af_econet.c1137
-rw-r--r--uClinux-2.4.31-uc0/net/ethernet/Makefile28
-rw-r--r--uClinux-2.4.31-uc0/net/ethernet/eth.c257
-rw-r--r--uClinux-2.4.31-uc0/net/ethernet/pe2.c38
-rw-r--r--uClinux-2.4.31-uc0/net/ethernet/sysctl_net_ether.c13
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/Config.in62
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/Makefile29
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/af_inet.c1229
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/arp.c1435
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/devinet.c1278
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/fib_frontend.c660
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/fib_hash.c942
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/fib_rules.c467
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/fib_semantics.c1047
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/icmp.c1007
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/igmp.c2236
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/inetpeer.c453
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ip_forward.c166
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ip_fragment.c688
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ip_gre.c1431
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ip_input.c444
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ip_nat_dumb.c166
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ip_options.c629
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ip_output.c1038
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ip_sockglue.c1077
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipconfig.c1443
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipip.c1068
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipmr.c1757
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/Config.in26
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/Makefile43
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_app.c502
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_conn.c1569
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_core.c1284
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_ctl.c2174
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_dh.c261
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_est.c200
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_ftp.c408
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_lblc.c620
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_lblcr.c880
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_lc.c138
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_nq.c173
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_rr.c116
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sched.c257
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sed.c163
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sh.c258
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sync.c864
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_wlc.c153
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_wrr.c238
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/Config.in181
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/Makefile163
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/arp_tables.c1316
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/arpt_mangle.c101
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/arptable_filter.c174
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_amanda.c144
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_core.c1517
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_ftp.c439
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_h323.c313
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_irc.c313
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_pptp.c637
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_pptp_priv.h24
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_generic.c61
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_gre.c345
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_icmp.c116
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_tcp.c258
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_udp.c75
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_standalone.c636
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_tftp.c136
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_fw_compat.c308
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_fw_compat_masq.c339
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_fw_compat_redir.c307
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_amanda.c148
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_core.c1070
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_ftp.c325
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_h323.c419
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_helper.c575
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_irc.c265
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_pptp.c478
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_gre.c225
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_icmp.c97
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_tcp.c149
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_udp.c142
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_unknown.c61
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_rule.c298
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_snmp_basic.c1363
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_standalone.c368
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_tftp.c195
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_queue.c707
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_tables.c1894
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipchains_core.c1771
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipfwadm_core.c1368
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_CONNLOG.c148
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_CONNMARK.c87
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_DSCP.c108
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ECN.c186
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_IMQ.c78
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_LOG.c400
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_MARK.c67
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_MASQUERADE.c213
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_MIRROR.c209
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_NETMAP.c109
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_REDIRECT.c115
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_REJECT.c426
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_TCPMSS.c269
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_TOS.c97
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ULOG.c371
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ah.c109
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_connmark.c55
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_conntrack.c122
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_dscp.c58
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ecn.c118
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_esp.c106
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_helper.c112
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_iprange.c101
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_length.c55
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_limit.c147
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_mac.c65
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_mark.c51
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_multiport.c104
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_owner.c201
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_physdev.c127
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_pkttype.c59
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_recent.c1005
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_state.c59
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_string.c216
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_tcpmss.c109
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_time.c189
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_tos.c52
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ttl.c75
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_unclean.c595
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/iptable_filter.c182
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/netfilter/iptable_mangle.c243
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/proc.c222
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/protocol.c181
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/raw.c703
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/route.c2643
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/syncookies.c192
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/sysctl_net_ipv4.c277
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/tcp.c2652
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/tcp_diag.c630
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/tcp_input.c4800
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/tcp_ipv4.c2520
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/tcp_minisocks.c1023
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/tcp_output.c1469
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/tcp_timer.c655
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/udp.c1339
-rw-r--r--uClinux-2.4.31-uc0/net/ipv4/utils.c74
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/Config.in92
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/Makefile31
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/README8
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/addrconf.c4086
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/af_inet6.c826
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/anycast.c466
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/datagram.c456
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/exthdrs.c858
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/icmp.c1759
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/ip6_fib.c1243
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/ip6_flowlabel.c629
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/ip6_fw.c390
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/ip6_input.c300
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/ip6_output.c945
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/ipv6_sockglue.c825
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/ipv6_syms.c48
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/ipv6_tunnel.c1898
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/mcast.c2459
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/ndisc.c1917
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/Config.in82
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/Makefile37
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6_queue.c710
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6_tables.c1935
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_IMQ.c78
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_LOG.c472
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_MARK.c68
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_REJECT.c301
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_agr.c91
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_ah.c207
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_dst.c278
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_esp.c176
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_eui64.c89
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_frag.c219
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_hbh.c278
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_hl.c74
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_ipv6header.c164
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_length.c51
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_limit.c136
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_mac.c66
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_mark.c51
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_multiport.c104
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_owner.c165
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_rt.c294
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6table_filter.c184
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6table_mangle.c244
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/proc.c228
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/protocol.c118
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/raw.c966
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/reassembly.c792
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/route.c2606
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/sit.c881
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/sysctl_net_ipv6.c73
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/tcp_ipv6.c2372
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/udp.c1191
-rw-r--r--uClinux-2.4.31-uc0/net/ipv6/utils.c50
-rw-r--r--uClinux-2.4.31-uc0/net/ipx/Config.in8
-rw-r--r--uClinux-2.4.31-uc0/net/ipx/Makefile25
-rw-r--r--uClinux-2.4.31-uc0/net/ipx/af_ipx.c2641
-rw-r--r--uClinux-2.4.31-uc0/net/ipx/af_spx.c937
-rw-r--r--uClinux-2.4.31-uc0/net/ipx/sysctl_net_ipx.c47
-rw-r--r--uClinux-2.4.31-uc0/net/irda/Config.in28
-rw-r--r--uClinux-2.4.31-uc0/net/irda/Makefile43
-rw-r--r--uClinux-2.4.31-uc0/net/irda/af_irda.c2613
-rw-r--r--uClinux-2.4.31-uc0/net/irda/crc.c65
-rw-r--r--uClinux-2.4.31-uc0/net/irda/discovery.c368
-rw-r--r--uClinux-2.4.31-uc0/net/irda/ircomm/Config.in3
-rw-r--r--uClinux-2.4.31-uc0/net/irda/ircomm/Makefile25
-rw-r--r--uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_core.c546
-rw-r--r--uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_event.c257
-rw-r--r--uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_lmp.c340
-rw-r--r--uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_param.c524
-rw-r--r--uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_ttp.c304
-rw-r--r--uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_tty.c1409
-rw-r--r--uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_tty_attach.c958
-rw-r--r--uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_tty_ioctl.c456
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irda_device.c629
-rw-r--r--uClinux-2.4.31-uc0/net/irda/iriap.c1036
-rw-r--r--uClinux-2.4.31-uc0/net/irda/iriap_event.c514
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irias_object.c539
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlan/Config.in1
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlan/Makefile15
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlan/irlan_client.c564
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlan/irlan_client_event.c532
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlan/irlan_common.c1232
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlan/irlan_eth.c411
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlan/irlan_event.c60
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlan/irlan_filter.c241
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlan/irlan_provider.c413
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlan/irlan_provider_event.c241
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlap.c1183
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlap_event.c2203
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlap_frame.c1374
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlmp.c1806
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlmp_event.c898
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irlmp_frame.c477
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irnet/Config.in4
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irnet/Makefile16
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irnet/irnet.h521
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irnet/irnet_irda.c1867
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irnet/irnet_irda.h185
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irnet/irnet_ppp.c1100
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irnet/irnet_ppp.h125
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irproc.c97
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irqueue.c783
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irsyms.c243
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irsysctl.c187
-rw-r--r--uClinux-2.4.31-uc0/net/irda/irttp.c1778
-rw-r--r--uClinux-2.4.31-uc0/net/irda/parameters.c587
-rw-r--r--uClinux-2.4.31-uc0/net/irda/qos.c774
-rw-r--r--uClinux-2.4.31-uc0/net/irda/timer.c239
-rw-r--r--uClinux-2.4.31-uc0/net/irda/wrapper.c370
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/Config.in10
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/Makefile25
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/README247
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/accept.c127
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/datasending.c241
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/logging.c95
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/main.c362
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/make_times_h.c122
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/misc.c242
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/prototypes.h120
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/rfc.c374
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/rfc_time.c227
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/security.c254
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/security.h12
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/sockets.c117
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/structure.h68
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/sysctl.c341
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/sysctl.h20
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/userspace.c243
-rw-r--r--uClinux-2.4.31-uc0/net/khttpd/waitheaders.c302
-rw-r--r--uClinux-2.4.31-uc0/net/lapb/Makefile19
-rw-r--r--uClinux-2.4.31-uc0/net/lapb/lapb_iface.c412
-rw-r--r--uClinux-2.4.31-uc0/net/lapb/lapb_in.c641
-rw-r--r--uClinux-2.4.31-uc0/net/lapb/lapb_out.c223
-rw-r--r--uClinux-2.4.31-uc0/net/lapb/lapb_subr.c276
-rw-r--r--uClinux-2.4.31-uc0/net/lapb/lapb_timer.c189
-rw-r--r--uClinux-2.4.31-uc0/net/netlink/Makefile20
-rw-r--r--uClinux-2.4.31-uc0/net/netlink/af_netlink.c1306
-rw-r--r--uClinux-2.4.31-uc0/net/netlink/netlink_dev.c223
-rw-r--r--uClinux-2.4.31-uc0/net/netrom/Makefile19
-rw-r--r--uClinux-2.4.31-uc0/net/netrom/af_netrom.c1384
-rw-r--r--uClinux-2.4.31-uc0/net/netrom/nr_dev.c237
-rw-r--r--uClinux-2.4.31-uc0/net/netrom/nr_in.c304
-rw-r--r--uClinux-2.4.31-uc0/net/netrom/nr_loopback.c100
-rw-r--r--uClinux-2.4.31-uc0/net/netrom/nr_out.c272
-rw-r--r--uClinux-2.4.31-uc0/net/netrom/nr_route.c908
-rw-r--r--uClinux-2.4.31-uc0/net/netrom/nr_subr.c288
-rw-r--r--uClinux-2.4.31-uc0/net/netrom/nr_timer.c245
-rw-r--r--uClinux-2.4.31-uc0/net/netrom/sysctl_net_netrom.c90
-rw-r--r--uClinux-2.4.31-uc0/net/netsyms.c635
-rw-r--r--uClinux-2.4.31-uc0/net/packet/Makefile14
-rw-r--r--uClinux-2.4.31-uc0/net/packet/af_packet.c1922
-rw-r--r--uClinux-2.4.31-uc0/net/rose/Makefile19
-rw-r--r--uClinux-2.4.31-uc0/net/rose/af_rose.c1531
-rw-r--r--uClinux-2.4.31-uc0/net/rose/rose_dev.c204
-rw-r--r--uClinux-2.4.31-uc0/net/rose/rose_in.c303
-rw-r--r--uClinux-2.4.31-uc0/net/rose/rose_link.c343
-rw-r--r--uClinux-2.4.31-uc0/net/rose/rose_loopback.c119
-rw-r--r--uClinux-2.4.31-uc0/net/rose/rose_out.c130
-rw-r--r--uClinux-2.4.31-uc0/net/rose/rose_route.c1156
-rw-r--r--uClinux-2.4.31-uc0/net/rose/rose_subr.c544
-rw-r--r--uClinux-2.4.31-uc0/net/rose/rose_timer.c207
-rw-r--r--uClinux-2.4.31-uc0/net/rose/sysctl_net_rose.c78
-rw-r--r--uClinux-2.4.31-uc0/net/sched/Config.in43
-rw-r--r--uClinux-2.4.31-uc0/net/sched/Makefile37
-rw-r--r--uClinux-2.4.31-uc0/net/sched/cls_api.c468
-rw-r--r--uClinux-2.4.31-uc0/net/sched/cls_fw.c379
-rw-r--r--uClinux-2.4.31-uc0/net/sched/cls_route.c635
-rw-r--r--uClinux-2.4.31-uc0/net/sched/cls_rsvp.c42
-rw-r--r--uClinux-2.4.31-uc0/net/sched/cls_rsvp.h698
-rw-r--r--uClinux-2.4.31-uc0/net/sched/cls_rsvp6.c43
-rw-r--r--uClinux-2.4.31-uc0/net/sched/cls_tcindex.c516
-rw-r--r--uClinux-2.4.31-uc0/net/sched/cls_u32.c722
-rw-r--r--uClinux-2.4.31-uc0/net/sched/estimator.c193
-rw-r--r--uClinux-2.4.31-uc0/net/sched/police.c251
-rw-r--r--uClinux-2.4.31-uc0/net/sched/proxydict.c153
-rw-r--r--uClinux-2.4.31-uc0/net/sched/proxydict.h32
-rw-r--r--uClinux-2.4.31-uc0/net/sched/proxyremap.h33
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_api.c1269
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_atm.c717
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_cbq.c2121
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_csz.c1069
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_dsmark.c488
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_fifo.c211
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_generic.c537
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_gred.c637
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_hfsc.c1817
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_htb.c1739
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_ingress.c399
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_netem.c600
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_prio.c424
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_red.c481
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_sfq.c502
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_tbf.c554
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_teql.c496
-rw-r--r--uClinux-2.4.31-uc0/net/sched/sch_wrr.c1364
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/Config.in43
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/Makefile22
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/adler32.c171
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/associola.c1220
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/bind_addr.c417
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/chunk.c309
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/command.c81
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/crc32c.c220
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/debug.c191
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/endpointola.c389
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/hashdriver.c128
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/input.c885
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/inqueue.c208
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/ipv6.c1008
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/objcnt.c138
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/output.c646
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/outqueue.c1744
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/primitive.c219
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/proc.c288
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/protocol.c1207
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/sla1.c281
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/sm_make_chunk.c2750
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/sm_sideeffect.c1395
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/sm_statefuns.c5240
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/sm_statetable.c1004
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/socket.c4766
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/ssnmap.c131
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/sysctl.c251
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/transport.c514
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/tsnmap.c417
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/ulpevent.c942
-rw-r--r--uClinux-2.4.31-uc0/net/sctp/ulpqueue.c865
-rw-r--r--uClinux-2.4.31-uc0/net/socket.c1759
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/Makefile24
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/auth.c376
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/auth_null.c140
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/auth_unix.c252
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/clnt.c962
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/pmap_clnt.c278
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/sched.c1158
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/stats.c206
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/sunrpc_syms.c109
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/svc.c427
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/svcauth.c166
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/svcauth_des.c215
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/svcsock.c1343
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/sysctl.c137
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/timer.c74
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/xdr.c554
-rw-r--r--uClinux-2.4.31-uc0/net/sunrpc/xprt.c1608
-rw-r--r--uClinux-2.4.31-uc0/net/sysctl_net.c53
-rw-r--r--uClinux-2.4.31-uc0/net/unix/Makefile18
-rw-r--r--uClinux-2.4.31-uc0/net/unix/af_unix.c1892
-rw-r--r--uClinux-2.4.31-uc0/net/unix/garbage.c310
-rw-r--r--uClinux-2.4.31-uc0/net/unix/sysctl_net_unix.c45
-rw-r--r--uClinux-2.4.31-uc0/net/wanrouter/Makefile20
-rw-r--r--uClinux-2.4.31-uc0/net/wanrouter/af_wanpipe.c2766
-rw-r--r--uClinux-2.4.31-uc0/net/wanrouter/patchlevel1
-rw-r--r--uClinux-2.4.31-uc0/net/wanrouter/wanmain.c1083
-rw-r--r--uClinux-2.4.31-uc0/net/wanrouter/wanproc.c1088
-rw-r--r--uClinux-2.4.31-uc0/net/x25/Makefile19
-rw-r--r--uClinux-2.4.31-uc0/net/x25/af_x25.c1382
-rw-r--r--uClinux-2.4.31-uc0/net/x25/sysctl_net_x25.c58
-rw-r--r--uClinux-2.4.31-uc0/net/x25/x25_dev.c250
-rw-r--r--uClinux-2.4.31-uc0/net/x25/x25_facilities.c232
-rw-r--r--uClinux-2.4.31-uc0/net/x25/x25_in.c369
-rw-r--r--uClinux-2.4.31-uc0/net/x25/x25_link.c436
-rw-r--r--uClinux-2.4.31-uc0/net/x25/x25_out.c229
-rw-r--r--uClinux-2.4.31-uc0/net/x25/x25_route.c271
-rw-r--r--uClinux-2.4.31-uc0/net/x25/x25_subr.c356
-rw-r--r--uClinux-2.4.31-uc0/net/x25/x25_timer.c192
604 files changed, 312109 insertions, 0 deletions
diff --git a/uClinux-2.4.31-uc0/net/802/Makefile b/uClinux-2.4.31-uc0/net/802/Makefile
new file mode 100644
index 0000000..2bbecb7
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/Makefile
@@ -0,0 +1,39 @@
+#
+# Makefile for the Linux 802.x protocol layers.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := 802.o
+
+export-objs = llc_macinit.o p8022.o psnap.o fc.o
+
+obj-y := p8023.o
+
+obj-$(CONFIG_SYSCTL) += sysctl_net_802.o
+
+subdir-$(CONFIG_LLC) += transit
+obj-$(CONFIG_LLC) += llc_sendpdu.o llc_utility.o cl2llc.o llc_macinit.o
+obj-$(CONFIG_LLC) += p8022.o psnap.o
+
+obj-$(CONFIG_TR) += p8022.o psnap.o tr.o
+
+obj-$(CONFIG_NET_FC) += fc.o
+
+obj-$(CONFIG_FDDI) += fddi.o
+
+obj-$(CONFIG_HIPPI) += hippi.o
+
+obj-$(CONFIG_IPX) += p8022.o psnap.o
+
+obj-$(CONFIG_ATALK) += p8022.o psnap.o
+
+
+include $(TOPDIR)/Rules.make
+
+cl2llc.c: cl2llc.pre
+ @rm -f $@
+ sed -f ./pseudo/opcd2num.sed cl2llc.pre >cl2llc.c
diff --git a/uClinux-2.4.31-uc0/net/802/TODO b/uClinux-2.4.31-uc0/net/802/TODO
new file mode 100644
index 0000000..888c639
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/TODO
@@ -0,0 +1,29 @@
+Remaining Problems:
+
+1. Serialization of access to variables in the llc structure
+by mac_data_indicate(), timer expired functions, and data_request() .
+There is not serialization of any kind right now.
+While testing, I have not seen any problems that stem from this lack of
+serialization, but it wories me...
+
+2. The code is currently able to handle one connection only,
+there is more work in register_cl2llc_client() to make a chain
+of llc structures and in mac_data_indicate() to find back
+the llc structure addressed by an incoming frame.
+According to IEEE, connections are identified by (remote mac + local mac
++ dsap + ssap). dsap and ssap do not seem important: existing applications
+always use the same dsap/ssap. Its probably sufficient to index on
+the remote mac only.
+
+3. There is no test to see if the transmit window is full in data_request()
+as described in the doc p73, "7.5.1 Sending I PDUs" 3th alinea.
+The pdus presented to data_request() could probably go on the
+awaiting-transmit-queue (atq). The real difficulty is coding a test
+to see if the transmit window is used up and to send the queue
+when space in the window becomes available.
+As I have no network layer that can generate a continous flow of pdus it is
+difficult to simulate a remote busy condition and hence to test the code
+to handle it.
+
+4. A simple flow control algorithm, steering the size of the transmit
+window would be nice to have.
diff --git a/uClinux-2.4.31-uc0/net/802/cl2llc.c b/uClinux-2.4.31-uc0/net/802/cl2llc.c
new file mode 100644
index 0000000..a1e3248
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/cl2llc.c
@@ -0,0 +1,615 @@
+/*
+ * NET An implementation of the IEEE 802.2 LLC protocol for the
+ * LINUX operating system. LLC is implemented as a set of
+ * state machines and callbacks for higher networking layers.
+ *
+ * Class 2 llc algorithm.
+ * Pseudocode interpreter, transition table lookup,
+ * data_request & indicate primitives...
+ *
+ * Code for initialization, termination, registration and
+ * MAC layer glue.
+ *
+ * Copyright Tim Alpaerts,
+ * <Tim_Alpaerts@toyota-motor-europe.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes
+ * Alan Cox : Chainsawed into Linux format
+ * Modified to use llc_ names
+ * Changed callbacks
+ *
+ * This file must be processed by sed before it can be compiled.
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/p8022.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <asm/byteorder.h>
+
+#include "pseudo/pseudocode.h"
+#include "transit/pdutr.h"
+#include "transit/timertr.h"
+#include <net/llc_frame.h>
+#include <net/llc.h>
+
+/*
+ * Data_request() is called by the client to present a data unit
+ * to the llc for transmission.
+ * In the future this function should also check if the transmit window
+ * allows the sending of another pdu, and if not put the skb on the atq
+ * for deferred sending.
+ */
+
+int llc_data_request(llcptr lp, struct sk_buff *skb)
+{
+ if (skb_headroom(skb) < (lp->dev->hard_header_len +4)){
+ printk("cl2llc: data_request() not enough headroom in skb\n");
+ return -1;
+ };
+
+ skb_push(skb, 4);
+
+ if ((lp->state != NORMAL) && (lp->state != BUSY) && (lp->state != REJECT))
+ {
+ printk("cl2llc: data_request() while no llc connection\n");
+ return -1;
+ }
+
+ if (lp->remote_busy)
+ { /* if the remote llc is BUSY, */
+ ADD_TO_ATQ(skb); /* save skb in the await transmit queue */
+ return 0;
+ }
+ else
+ {
+ /*
+ * Else proceed with xmit
+ */
+
+ switch(lp->state)
+ {
+ case NORMAL:
+ if(lp->p_flag)
+ llc_interpret_pseudo_code(lp, NORMAL2, skb, NO_FRAME);
+ else
+ llc_interpret_pseudo_code(lp, NORMAL1, skb, NO_FRAME);
+ break;
+ case BUSY:
+ if (lp->p_flag)
+ llc_interpret_pseudo_code(lp, BUSY2, skb, NO_FRAME);
+ else
+ llc_interpret_pseudo_code(lp, BUSY1, skb, NO_FRAME);
+ break;
+ case REJECT:
+ if (lp->p_flag)
+ llc_interpret_pseudo_code(lp, REJECT2, skb, NO_FRAME);
+ else
+ llc_interpret_pseudo_code(lp, REJECT1, skb, NO_FRAME);
+ break;
+ default:;
+ }
+ if(lp->llc_callbacks)
+ {
+ lp->llc_event(lp);
+ lp->llc_callbacks=0;
+ }
+ return 0;
+ }
+}
+
+
+
+/*
+ * Disconnect_request() requests that the llc to terminate a connection
+ */
+
+void disconnect_request(llcptr lp)
+{
+ if ((lp->state == NORMAL) ||
+ (lp->state == BUSY) ||
+ (lp->state == REJECT) ||
+ (lp->state == AWAIT) ||
+ (lp->state == AWAIT_BUSY) ||
+ (lp->state == AWAIT_REJECT))
+ {
+ lp->state = D_CONN;
+ llc_interpret_pseudo_code(lp, SH1, NULL, NO_FRAME);
+ if(lp->llc_callbacks)
+ {
+ lp->llc_event(lp);
+ lp->llc_callbacks=0;
+ }
+ /*
+ * lp may be invalid after the callback
+ */
+ }
+}
+
+
+/*
+ * Connect_request() requests that the llc to start a connection
+ */
+
+void connect_request(llcptr lp)
+{
+ if (lp->state == ADM)
+ {
+ lp->state = SETUP;
+ llc_interpret_pseudo_code(lp, ADM1, NULL, NO_FRAME);
+ if(lp->llc_callbacks)
+ {
+ lp->llc_event(lp);
+ lp->llc_callbacks=0;
+ }
+ /*
+ * lp may be invalid after the callback
+ */
+ }
+}
+
+
+/*
+ * Interpret_pseudo_code() executes the actions in the connection component
+ * state transition table. Table 4 in document on p88.
+ *
+ * If this function is called to handle an incoming pdu, skb will point
+ * to the buffer with the pdu and type will contain the decoded pdu type.
+ *
+ * If called by data_request skb points to an skb that was skb_alloc-ed by
+ * the llc client to hold the information unit to be transmitted, there is
+ * no valid type in this case.
+ *
+ * If called because a timer expired no skb is passed, and there is no
+ * type.
+ */
+
+void llc_interpret_pseudo_code(llcptr lp, int pc_label, struct sk_buff *skb,
+ char type)
+{
+ short int pc; /* program counter in pseudo code array */
+ char p_flag_received;
+ frameptr fr;
+ int resend_count; /* number of pdus resend by llc_resend_ipdu() */
+ int ack_count; /* number of pdus acknowledged */
+ struct sk_buff *skb2;
+
+ if (skb != NULL)
+ {
+ fr = (frameptr) skb->data;
+ }
+ else
+ fr = NULL;
+
+ pc = pseudo_code_idx[pc_label];
+ while(pseudo_code[pc])
+ {
+ switch(pseudo_code[pc])
+ {
+ case 9:
+ if ((type != I_CMD) || (fr->i_hdr.i_pflag == 0))
+ break;
+ case 1:
+ lp->remote_busy = 0;
+ llc_stop_timer(lp, BUSY_TIMER);
+ if ((lp->state == NORMAL) ||
+ (lp->state == REJECT) ||
+ (lp->state == BUSY))
+ {
+ skb2 = llc_pull_from_atq(lp);
+ if (skb2 != NULL)
+ llc_start_timer(lp, ACK_TIMER);
+ while (skb2 != NULL)
+ {
+ llc_sendipdu( lp, I_CMD, 0, skb2);
+ skb2 = llc_pull_from_atq(lp);
+ }
+ }
+ break;
+ case 2:
+ lp->state = NORMAL; /* needed to eliminate connect_response() */
+ lp->llc_mode = MODE_ABM;
+ lp->llc_callbacks|=LLC_CONN_INDICATION;
+ break;
+ case 3:
+ lp->llc_mode = MODE_ABM;
+ lp->llc_callbacks|=LLC_CONN_CONFIRM;
+ break;
+ case 4:
+ skb_pull(skb, 4);
+ lp->inc_skb=skb;
+ lp->llc_callbacks|=LLC_DATA_INDIC;
+ break;
+ case 5:
+ lp->llc_mode = MODE_ADM;
+ lp->llc_callbacks|=LLC_DISC_INDICATION;
+ break;
+ case 70:
+ lp->llc_callbacks|=LLC_RESET_INDIC_LOC;
+ break;
+ case 71:
+ lp->llc_callbacks|=LLC_RESET_INDIC_REM;
+ break;
+ case 7:
+ lp->llc_callbacks|=LLC_RST_CONFIRM;
+ break;
+ case 66:
+ lp->llc_callbacks|=LLC_FRMR_RECV;
+ break;
+ case 67:
+ lp->llc_callbacks|=LLC_FRMR_SENT;
+ break;
+ case 68:
+ lp->llc_callbacks|=LLC_REMOTE_BUSY;
+ break;
+ case 69:
+ lp->llc_callbacks|=LLC_REMOTE_NOTBUSY;
+ break;
+ case 11:
+ llc_sendpdu(lp, DISC_CMD, lp->f_flag, 0, NULL);
+ break;
+ case 12:
+ llc_sendpdu(lp, DM_RSP, 0, 0, NULL);
+ break;
+ case 13:
+ lp->frmr_info_fld.cntl1 = fr->pdu_cntl.byte1;
+ lp->frmr_info_fld.cntl2 = fr->pdu_cntl.byte2;
+ lp->frmr_info_fld.vs = lp->vs;
+ lp->frmr_info_fld.vr_cr = lp->vr;
+ llc_sendpdu(lp, FRMR_RSP, 0, 5, (char *) &lp->frmr_info_fld);
+ break;
+ case 14:
+ llc_sendpdu(lp, FRMR_RSP, 0, 5, (char *) &lp->frmr_info_fld);
+ break;
+ case 15:
+ llc_sendpdu(lp, FRMR_RSP, lp->p_flag,
+ 5, (char *) &lp->frmr_info_fld);
+ break;
+ case 16:
+ llc_sendipdu(lp, I_CMD, 1, skb);
+ break;
+ case 17:
+ resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_CMD, 1);
+ break;
+ case 18:
+ resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_CMD, 1);
+ if (resend_count == 0)
+ {
+ llc_sendpdu(lp, RR_CMD, 1, 0, NULL);
+ }
+ break;
+ case 19:
+ llc_sendipdu(lp, I_CMD, 0, skb);
+ break;
+ case 20:
+ resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_CMD, 0);
+ break;
+ case 21:
+ resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_CMD, 0);
+ if (resend_count == 0)
+ {
+ llc_sendpdu(lp, RR_CMD, 0, 0, NULL);
+ }
+ break;
+ case 22:
+ resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_RSP, 1);
+ break;
+ case 23:
+ llc_sendpdu(lp, REJ_CMD, 1, 0, NULL);
+ break;
+ case 24:
+ llc_sendpdu(lp, REJ_RSP, 1, 0, NULL);
+ break;
+ case 25:
+ if (IS_RSP(fr))
+ llc_sendpdu(lp, REJ_CMD, 0, 0, NULL);
+ else
+ llc_sendpdu(lp, REJ_RSP, 0, 0, NULL);
+ break;
+ case 26:
+ llc_sendpdu(lp, RNR_CMD, 1, 0, NULL);
+ break;
+ case 27:
+ llc_sendpdu(lp, RNR_RSP, 1, 0, NULL);
+ break;
+ case 28:
+ if (IS_RSP(fr))
+ llc_sendpdu(lp, RNR_CMD, 0, 0, NULL);
+ else
+ llc_sendpdu(lp, RNR_RSP, 0, 0, NULL);
+ break;
+ case 29:
+ if (lp->remote_busy == 0)
+ {
+ lp->remote_busy = 1;
+ llc_start_timer(lp, BUSY_TIMER);
+ lp->llc_callbacks|=LLC_REMOTE_BUSY;
+ }
+ else if (lp->timer_state[BUSY_TIMER] == TIMER_IDLE)
+ {
+ llc_start_timer(lp, BUSY_TIMER);
+ }
+ break;
+ case 30:
+ if (IS_RSP(fr))
+ llc_sendpdu(lp, RNR_CMD, 0, 0, NULL);
+ else
+ llc_sendpdu(lp, RNR_RSP, 0, 0, NULL);
+ break;
+ case 31:
+ llc_sendpdu(lp, RR_CMD, 1, 0, NULL);
+ break;
+ case 32:
+ llc_sendpdu(lp, RR_CMD, 1, 0, NULL);
+ break;
+ case 33:
+ llc_sendpdu(lp, RR_RSP, 1, 0, NULL);
+ break;
+ case 34:
+ llc_sendpdu(lp, RR_RSP, 1, 0, NULL);
+ break;
+ case 35:
+ llc_sendpdu(lp, RR_RSP, 0, 0, NULL);
+ break;
+ case 36:
+ if (IS_RSP(fr))
+ llc_sendpdu(lp, RR_CMD, 0, 0, NULL);
+ else
+ llc_sendpdu(lp, RR_RSP, 0, 0, NULL);
+ break;
+ case 37:
+ llc_sendpdu(lp, SABME_CMD, 0, 0, NULL);
+ lp->f_flag = 0;
+ break;
+ case 38:
+ llc_sendpdu(lp, UA_RSP, lp->f_flag, 0, NULL);
+ break;
+ case 39:
+ lp->s_flag = 0;
+ break;
+ case 40:
+ lp->s_flag = 1;
+ break;
+ case 41:
+ if(lp->timer_state[P_TIMER] == TIMER_RUNNING)
+ llc_stop_timer(lp, P_TIMER);
+ llc_start_timer(lp, P_TIMER);
+ if (lp->p_flag == 0)
+ {
+ lp->retry_count = 0;
+ lp->p_flag = 1;
+ }
+ break;
+ case 44:
+ if (lp->timer_state[ACK_TIMER] == TIMER_IDLE)
+ llc_start_timer(lp, ACK_TIMER);
+ break;
+ case 42:
+ llc_start_timer(lp, ACK_TIMER);
+ break;
+ case 43:
+ llc_start_timer(lp, REJ_TIMER);
+ break;
+ case 45:
+ llc_stop_timer(lp, ACK_TIMER);
+ break;
+ case 46:
+ llc_stop_timer(lp, ACK_TIMER);
+ lp->p_flag = 0;
+ break;
+ case 10:
+ if (lp->data_flag == 2)
+ llc_stop_timer(lp, REJ_TIMER);
+ break;
+ case 47:
+ llc_stop_timer(lp, REJ_TIMER);
+ break;
+ case 48:
+ llc_stop_timer(lp, ACK_TIMER);
+ llc_stop_timer(lp, P_TIMER);
+ llc_stop_timer(lp, REJ_TIMER);
+ llc_stop_timer(lp, BUSY_TIMER);
+ break;
+ case 49:
+ llc_stop_timer(lp, P_TIMER);
+ llc_stop_timer(lp, REJ_TIMER);
+ llc_stop_timer(lp, BUSY_TIMER);
+ break;
+ case 50:
+ ack_count = llc_free_acknowledged_skbs(lp,
+ (unsigned char) fr->s_hdr.nr);
+ if (ack_count > 0)
+ {
+ lp->retry_count = 0;
+ llc_stop_timer(lp, ACK_TIMER);
+ if (skb_peek(&lp->rtq) != NULL)
+ {
+ /*
+ * Re-transmit queue not empty
+ */
+ llc_start_timer(lp, ACK_TIMER);
+ }
+ }
+ break;
+ case 51:
+ if (IS_UFRAME(fr))
+ p_flag_received = fr->u_hdr.u_pflag;
+ else
+ p_flag_received = fr->i_hdr.i_pflag;
+ if ((fr->pdu_hdr.ssap & 0x01) && (p_flag_received))
+ {
+ lp->p_flag = 0;
+ llc_stop_timer(lp, P_TIMER);
+ }
+ break;
+ case 52:
+ lp->data_flag = 2;
+ break;
+ case 53:
+ lp->data_flag = 0;
+ break;
+ case 54:
+ lp->data_flag = 1;
+ break;
+ case 55:
+ if (lp->data_flag == 0)
+ lp->data_flag = 1;
+ break;
+ case 56:
+ lp->p_flag = 0;
+ break;
+ case 57:
+ lp->p_flag = lp->f_flag;
+ break;
+ case 58:
+ lp->remote_busy = 0;
+ break;
+ case 59:
+ lp->retry_count = 0;
+ break;
+ case 60:
+ lp->retry_count++;
+ break;
+ case 61:
+ lp->vr = 0;
+ break;
+ case 62:
+ lp->vr++;
+ break;
+ case 63:
+ lp->vs = 0;
+ break;
+ case 64:
+ lp->vs = fr->i_hdr.nr;
+ break;
+ case 65:
+ if (IS_UFRAME(fr))
+ lp->f_flag = fr->u_hdr.u_pflag;
+ else
+ lp->f_flag = fr->i_hdr.i_pflag;
+ break;
+ default:;
+ }
+ pc++;
+ }
+}
+
+
+/*
+ * Process_otype2_frame will handle incoming frames
+ * for 802.2 Type 2 Procedure.
+ */
+
+void llc_process_otype2_frame(llcptr lp, struct sk_buff *skb, char type)
+{
+ int idx; /* index in transition table */
+ int pc_label; /* action to perform, from tr tbl */
+ int validation; /* result of validate_seq_nos */
+ int p_flag_received; /* p_flag in received frame */
+ frameptr fr;
+
+ fr = (frameptr) skb->data;
+
+ if (IS_UFRAME(fr))
+ p_flag_received = fr->u_hdr.u_pflag;
+ else
+ p_flag_received = fr->i_hdr.i_pflag;
+
+ switch(lp->state)
+ {
+ /* Compute index in transition table: */
+ case ADM:
+ idx = type;
+ idx = (idx << 1) + p_flag_received;
+ break;
+ case CONN:
+ case RESET_WAIT:
+ case RESET_CHECK:
+ case ERROR:
+ idx = type;
+ break;
+ case SETUP:
+ case RESET:
+ case D_CONN:
+ idx = type;
+ idx = (idx << 1) + lp->p_flag;
+ break;
+ case NORMAL:
+ case BUSY:
+ case REJECT:
+ case AWAIT:
+ case AWAIT_BUSY:
+ case AWAIT_REJECT:
+ validation = llc_validate_seq_nos(lp, fr);
+ if (validation > 3)
+ type = BAD_FRAME;
+ idx = type;
+ idx = (idx << 1);
+ if (validation & 1)
+ idx = idx +1;
+ idx = (idx << 1) + p_flag_received;
+ idx = (idx << 1) + lp->p_flag;
+ default:
+ printk("llc_proc: bad state\n");
+ return;
+ }
+ idx = (idx << 1) + pdutr_offset[lp->state];
+ lp->state = pdutr_entry[idx +1];
+ pc_label = pdutr_entry[idx];
+ if (pc_label != 0)
+ {
+ llc_interpret_pseudo_code(lp, pc_label, skb, type);
+ if(lp->llc_callbacks)
+ {
+ lp->llc_event(lp);
+ lp->llc_callbacks=0;
+ }
+ /*
+ * lp may no longer be valid after this point. Be
+ * careful what is added!
+ */
+ }
+}
+
+
+void llc_timer_expired(llcptr lp, int t)
+{
+ int idx; /* index in transition table */
+ int pc_label; /* action to perform, from tr tbl */
+
+ lp->timer_state[t] = TIMER_EXPIRED;
+ idx = lp->state; /* Compute index in transition table: */
+ idx = (idx << 2) + t;
+ idx = idx << 1;
+ if (lp->retry_count >= lp->n2)
+ idx = idx + 1;
+ idx = (idx << 1) + lp->s_flag;
+ idx = (idx << 1) + lp->p_flag;
+ idx = idx << 1; /* 2 bytes per entry: action & newstate */
+
+ pc_label = timertr_entry[idx];
+ if (pc_label != 0)
+ {
+ llc_interpret_pseudo_code(lp, pc_label, NULL, NO_FRAME);
+ lp->state = timertr_entry[idx +1];
+ }
+ lp->timer_state[t] = TIMER_IDLE;
+ if(lp->llc_callbacks)
+ {
+ lp->llc_event(lp);
+ lp->llc_callbacks=0;
+ }
+ /*
+ * And lp may have vanished in the event callback
+ */
+}
+
diff --git a/uClinux-2.4.31-uc0/net/802/cl2llc.pre b/uClinux-2.4.31-uc0/net/802/cl2llc.pre
new file mode 100644
index 0000000..c38ce62
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/cl2llc.pre
@@ -0,0 +1,615 @@
+/*
+ * NET An implementation of the IEEE 802.2 LLC protocol for the
+ * LINUX operating system. LLC is implemented as a set of
+ * state machines and callbacks for higher networking layers.
+ *
+ * Class 2 llc algorithm.
+ * Pseudocode interpreter, transition table lookup,
+ * data_request & indicate primitives...
+ *
+ * Code for initialization, termination, registration and
+ * MAC layer glue.
+ *
+ * Copyright Tim Alpaerts,
+ * <Tim_Alpaerts@toyota-motor-europe.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes
+ * Alan Cox : Chainsawed into Linux format
+ * Modified to use llc_ names
+ * Changed callbacks
+ *
+ * This file must be processed by sed before it can be compiled.
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/p8022.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <asm/byteorder.h>
+
+#include "pseudo/pseudocode.h"
+#include "transit/pdutr.h"
+#include "transit/timertr.h"
+#include <net/llc_frame.h>
+#include <net/llc.h>
+
+/*
+ * Data_request() is called by the client to present a data unit
+ * to the llc for transmission.
+ * In the future this function should also check if the transmit window
+ * allows the sending of another pdu, and if not put the skb on the atq
+ * for deferred sending.
+ */
+
+int llc_data_request(llcptr lp, struct sk_buff *skb)
+{
+ if (skb_headroom(skb) < (lp->dev->hard_header_len +4)){
+ printk("cl2llc: data_request() not enough headroom in skb\n");
+ return -1;
+ };
+
+ skb_push(skb, 4);
+
+ if ((lp->state != NORMAL) && (lp->state != BUSY) && (lp->state != REJECT))
+ {
+ printk("cl2llc: data_request() while no llc connection\n");
+ return -1;
+ }
+
+ if (lp->remote_busy)
+ { /* if the remote llc is BUSY, */
+ ADD_TO_ATQ(skb); /* save skb in the await transmit queue */
+ return 0;
+ }
+ else
+ {
+ /*
+ * Else proceed with xmit
+ */
+
+ switch(lp->state)
+ {
+ case NORMAL:
+ if(lp->p_flag)
+ llc_interpret_pseudo_code(lp, NORMAL2, skb, NO_FRAME);
+ else
+ llc_interpret_pseudo_code(lp, NORMAL1, skb, NO_FRAME);
+ break;
+ case BUSY:
+ if (lp->p_flag)
+ llc_interpret_pseudo_code(lp, BUSY2, skb, NO_FRAME);
+ else
+ llc_interpret_pseudo_code(lp, BUSY1, skb, NO_FRAME);
+ break;
+ case REJECT:
+ if (lp->p_flag)
+ llc_interpret_pseudo_code(lp, REJECT2, skb, NO_FRAME);
+ else
+ llc_interpret_pseudo_code(lp, REJECT1, skb, NO_FRAME);
+ break;
+ default:;
+ }
+ if(lp->llc_callbacks)
+ {
+ lp->llc_event(lp);
+ lp->llc_callbacks=0;
+ }
+ return 0;
+ }
+}
+
+
+
+/*
+ * Disconnect_request() requests that the llc to terminate a connection
+ */
+
+void disconnect_request(llcptr lp)
+{
+ if ((lp->state == NORMAL) ||
+ (lp->state == BUSY) ||
+ (lp->state == REJECT) ||
+ (lp->state == AWAIT) ||
+ (lp->state == AWAIT_BUSY) ||
+ (lp->state == AWAIT_REJECT))
+ {
+ lp->state = D_CONN;
+ llc_interpret_pseudo_code(lp, SH1, NULL, NO_FRAME);
+ if(lp->llc_callbacks)
+ {
+ lp->llc_event(lp);
+ lp->llc_callbacks=0;
+ }
+ /*
+ * lp may be invalid after the callback
+ */
+ }
+}
+
+
+/*
+ * Connect_request() requests that the llc to start a connection
+ */
+
+void connect_request(llcptr lp)
+{
+ if (lp->state == ADM)
+ {
+ lp->state = SETUP;
+ llc_interpret_pseudo_code(lp, ADM1, NULL, NO_FRAME);
+ if(lp->llc_callbacks)
+ {
+ lp->llc_event(lp);
+ lp->llc_callbacks=0;
+ }
+ /*
+ * lp may be invalid after the callback
+ */
+ }
+}
+
+
+/*
+ * Interpret_pseudo_code() executes the actions in the connection component
+ * state transition table. Table 4 in document on p88.
+ *
+ * If this function is called to handle an incoming pdu, skb will point
+ * to the buffer with the pdu and type will contain the decoded pdu type.
+ *
+ * If called by data_request skb points to an skb that was skb_alloc-ed by
+ * the llc client to hold the information unit to be transmitted, there is
+ * no valid type in this case.
+ *
+ * If called because a timer expired no skb is passed, and there is no
+ * type.
+ */
+
+void llc_interpret_pseudo_code(llcptr lp, int pc_label, struct sk_buff *skb,
+ char type)
+{
+ short int pc; /* program counter in pseudo code array */
+ char p_flag_received;
+ frameptr fr;
+ int resend_count; /* number of pdus resend by llc_resend_ipdu() */
+ int ack_count; /* number of pdus acknowledged */
+ struct sk_buff *skb2;
+
+ if (skb != NULL)
+ {
+ fr = (frameptr) skb->data;
+ }
+ else
+ fr = NULL;
+
+ pc = pseudo_code_idx[pc_label];
+ while(pseudo_code[pc])
+ {
+ switch(pseudo_code[pc])
+ {
+ case IF_F=1_CLEAR_REMOTE_BUSY:
+ if ((type != I_CMD) || (fr->i_hdr.i_pflag == 0))
+ break;
+ case CLEAR_REMOTE_BUSY:
+ lp->remote_busy = 0;
+ llc_stop_timer(lp, BUSY_TIMER);
+ if ((lp->state == NORMAL) ||
+ (lp->state == REJECT) ||
+ (lp->state == BUSY))
+ {
+ skb2 = llc_pull_from_atq(lp);
+ if (skb2 != NULL)
+ llc_start_timer(lp, ACK_TIMER);
+ while (skb2 != NULL)
+ {
+ llc_sendipdu( lp, I_CMD, 0, skb2);
+ skb2 = llc_pull_from_atq(lp);
+ }
+ }
+ break;
+ case CONNECT_INDICATION:
+ lp->state = NORMAL; /* needed to eliminate connect_response() */
+ lp->llc_mode = MODE_ABM;
+ lp->llc_callbacks|=LLC_CONN_INDICATION;
+ break;
+ case CONNECT_CONFIRM:
+ lp->llc_mode = MODE_ABM;
+ lp->llc_callbacks|=LLC_CONN_CONFIRM;
+ break;
+ case DATA_INDICATION:
+ skb_pull(skb, 4);
+ lp->inc_skb=skb;
+ lp->llc_callbacks|=LLC_DATA_INDIC;
+ break;
+ case DISCONNECT_INDICATION:
+ lp->llc_mode = MODE_ADM;
+ lp->llc_callbacks|=LLC_DISC_INDICATION;
+ break;
+ case RESET_INDICATION(LOCAL):
+ lp->llc_callbacks|=LLC_RESET_INDIC_LOC;
+ break;
+ case RESET_INDICATION(REMOTE):
+ lp->llc_callbacks|=LLC_RESET_INDIC_REM;
+ break;
+ case RESET_CONFIRM:
+ lp->llc_callbacks|=LLC_RST_CONFIRM;
+ break;
+ case REPORT_STATUS(FRMR_RECEIVED):
+ lp->llc_callbacks|=LLC_FRMR_RECV;
+ break;
+ case REPORT_STATUS(FRMR_SENT):
+ lp->llc_callbacks|=LLC_FRMR_SENT;
+ break;
+ case REPORT_STATUS(REMOTE_BUSY):
+ lp->llc_callbacks|=LLC_REMOTE_BUSY;
+ break;
+ case REPORT_STATUS(REMOTE_NOT_BUSY):
+ lp->llc_callbacks|=LLC_REMOTE_NOTBUSY;
+ break;
+ case SEND_DISC_CMD(P=X):
+ llc_sendpdu(lp, DISC_CMD, lp->f_flag, 0, NULL);
+ break;
+ case SEND_DM_RSP(F=X):
+ llc_sendpdu(lp, DM_RSP, 0, 0, NULL);
+ break;
+ case SEND_FRMR_RSP(F=X):
+ lp->frmr_info_fld.cntl1 = fr->pdu_cntl.byte1;
+ lp->frmr_info_fld.cntl2 = fr->pdu_cntl.byte2;
+ lp->frmr_info_fld.vs = lp->vs;
+ lp->frmr_info_fld.vr_cr = lp->vr;
+ llc_sendpdu(lp, FRMR_RSP, 0, 5, (char *) &lp->frmr_info_fld);
+ break;
+ case RE-SEND_FRMR_RSP(F=0):
+ llc_sendpdu(lp, FRMR_RSP, 0, 5, (char *) &lp->frmr_info_fld);
+ break;
+ case RE-SEND_FRMR_RSP(F=P):
+ llc_sendpdu(lp, FRMR_RSP, lp->p_flag,
+ 5, (char *) &lp->frmr_info_fld);
+ break;
+ case SEND_I_CMD(P=1):
+ llc_sendipdu(lp, I_CMD, 1, skb);
+ break;
+ case RE-SEND_I_CMD(P=1):
+ resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_CMD, 1);
+ break;
+ case RE-SEND_I_CMD(P=1)_OR_SEND_RR:
+ resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_CMD, 1);
+ if (resend_count == 0)
+ {
+ llc_sendpdu(lp, RR_CMD, 1, 0, NULL);
+ }
+ break;
+ case SEND_I_XXX(X=0):
+ llc_sendipdu(lp, I_CMD, 0, skb);
+ break;
+ case RE-SEND_I_XXX(X=0):
+ resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_CMD, 0);
+ break;
+ case RE-SEND_I_XXX(X=0)_OR_SEND_RR:
+ resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_CMD, 0);
+ if (resend_count == 0)
+ {
+ llc_sendpdu(lp, RR_CMD, 0, 0, NULL);
+ }
+ break;
+ case RE-SEND_I_RSP(F=1):
+ resend_count = llc_resend_ipdu(lp, fr->i_hdr.nr, I_RSP, 1);
+ break;
+ case SEND_REJ_CMD(P=1):
+ llc_sendpdu(lp, REJ_CMD, 1, 0, NULL);
+ break;
+ case SEND_REJ_RSP(F=1):
+ llc_sendpdu(lp, REJ_RSP, 1, 0, NULL);
+ break;
+ case SEND_REJ_XXX(X=0):
+ if (IS_RSP(fr))
+ llc_sendpdu(lp, REJ_CMD, 0, 0, NULL);
+ else
+ llc_sendpdu(lp, REJ_RSP, 0, 0, NULL);
+ break;
+ case SEND_RNR_CMD(F=1):
+ llc_sendpdu(lp, RNR_CMD, 1, 0, NULL);
+ break;
+ case SEND_RNR_RSP(F=1):
+ llc_sendpdu(lp, RNR_RSP, 1, 0, NULL);
+ break;
+ case SEND_RNR_XXX(X=0):
+ if (IS_RSP(fr))
+ llc_sendpdu(lp, RNR_CMD, 0, 0, NULL);
+ else
+ llc_sendpdu(lp, RNR_RSP, 0, 0, NULL);
+ break;
+ case SET_REMOTE_BUSY:
+ if (lp->remote_busy == 0)
+ {
+ lp->remote_busy = 1;
+ llc_start_timer(lp, BUSY_TIMER);
+ lp->llc_callbacks|=LLC_REMOTE_BUSY;
+ }
+ else if (lp->timer_state[BUSY_TIMER] == TIMER_IDLE)
+ {
+ llc_start_timer(lp, BUSY_TIMER);
+ }
+ break;
+ case OPTIONAL_SEND_RNR_XXX(X=0):
+ if (IS_RSP(fr))
+ llc_sendpdu(lp, RNR_CMD, 0, 0, NULL);
+ else
+ llc_sendpdu(lp, RNR_RSP, 0, 0, NULL);
+ break;
+ case SEND_RR_CMD(P=1):
+ llc_sendpdu(lp, RR_CMD, 1, 0, NULL);
+ break;
+ case SEND_ACKNOWLEDGE_CMD(P=1):
+ llc_sendpdu(lp, RR_CMD, 1, 0, NULL);
+ break;
+ case SEND_RR_RSP(F=1):
+ llc_sendpdu(lp, RR_RSP, 1, 0, NULL);
+ break;
+ case SEND_ACKNOWLEDGE_RSP(F=1):
+ llc_sendpdu(lp, RR_RSP, 1, 0, NULL);
+ break;
+ case SEND_RR_XXX(X=0):
+ llc_sendpdu(lp, RR_RSP, 0, 0, NULL);
+ break;
+ case SEND_ACKNOWLEDGE_XXX(X=0):
+ if (IS_RSP(fr))
+ llc_sendpdu(lp, RR_CMD, 0, 0, NULL);
+ else
+ llc_sendpdu(lp, RR_RSP, 0, 0, NULL);
+ break;
+ case SEND_SABME_CMD(P=X):
+ llc_sendpdu(lp, SABME_CMD, 0, 0, NULL);
+ lp->f_flag = 0;
+ break;
+ case SEND_UA_RSP(F=X):
+ llc_sendpdu(lp, UA_RSP, lp->f_flag, 0, NULL);
+ break;
+ case S_FLAG:=0:
+ lp->s_flag = 0;
+ break;
+ case S_FLAG:=1:
+ lp->s_flag = 1;
+ break;
+ case START_P_TIMER:
+ if(lp->timer_state[P_TIMER] == TIMER_RUNNING)
+ llc_stop_timer(lp, P_TIMER);
+ llc_start_timer(lp, P_TIMER);
+ if (lp->p_flag == 0)
+ {
+ lp->retry_count = 0;
+ lp->p_flag = 1;
+ }
+ break;
+ case START_ACK_TIMER_IF_NOT_RUNNING:
+ if (lp->timer_state[ACK_TIMER] == TIMER_IDLE)
+ llc_start_timer(lp, ACK_TIMER);
+ break;
+ case START_ACK_TIMER:
+ llc_start_timer(lp, ACK_TIMER);
+ break;
+ case START_REJ_TIMER:
+ llc_start_timer(lp, REJ_TIMER);
+ break;
+ case STOP_ACK_TIMER:
+ llc_stop_timer(lp, ACK_TIMER);
+ break;
+ case STOP_P_TIMER:
+ llc_stop_timer(lp, ACK_TIMER);
+ lp->p_flag = 0;
+ break;
+ case IF_DATA_FLAG=2_STOP_REJ_TIMER:
+ if (lp->data_flag == 2)
+ llc_stop_timer(lp, REJ_TIMER);
+ break;
+ case STOP_REJ_TIMER:
+ llc_stop_timer(lp, REJ_TIMER);
+ break;
+ case STOP_ALL_TIMERS:
+ llc_stop_timer(lp, ACK_TIMER);
+ llc_stop_timer(lp, P_TIMER);
+ llc_stop_timer(lp, REJ_TIMER);
+ llc_stop_timer(lp, BUSY_TIMER);
+ break;
+ case STOP_OTHER_TIMERS:
+ llc_stop_timer(lp, P_TIMER);
+ llc_stop_timer(lp, REJ_TIMER);
+ llc_stop_timer(lp, BUSY_TIMER);
+ break;
+ case UPDATE_N(R)_RECEIVED:
+ ack_count = llc_free_acknowledged_skbs(lp,
+ (unsigned char) fr->s_hdr.nr);
+ if (ack_count > 0)
+ {
+ lp->retry_count = 0;
+ llc_stop_timer(lp, ACK_TIMER);
+ if (skb_peek(&lp->rtq) != NULL)
+ {
+ /*
+ * Re-transmit queue not empty
+ */
+ llc_start_timer(lp, ACK_TIMER);
+ }
+ }
+ break;
+ case UPDATE_P_FLAG:
+ if (IS_UFRAME(fr))
+ p_flag_received = fr->u_hdr.u_pflag;
+ else
+ p_flag_received = fr->i_hdr.i_pflag;
+ if ((fr->pdu_hdr.ssap & 0x01) && (p_flag_received))
+ {
+ lp->p_flag = 0;
+ llc_stop_timer(lp, P_TIMER);
+ }
+ break;
+ case DATA_FLAG:=2:
+ lp->data_flag = 2;
+ break;
+ case DATA_FLAG:=0:
+ lp->data_flag = 0;
+ break;
+ case DATA_FLAG:=1:
+ lp->data_flag = 1;
+ break;
+ case IF_DATA_FLAG_=0_THEN_DATA_FLAG:=1:
+ if (lp->data_flag == 0)
+ lp->data_flag = 1;
+ break;
+ case P_FLAG:=0:
+ lp->p_flag = 0;
+ break;
+ case P_FLAG:=P:
+ lp->p_flag = lp->f_flag;
+ break;
+ case REMOTE_BUSY:=0:
+ lp->remote_busy = 0;
+ break;
+ case RETRY_COUNT:=0:
+ lp->retry_count = 0;
+ break;
+ case RETRY_COUNT:=RETRY_COUNT+1:
+ lp->retry_count++;
+ break;
+ case V(R):=0:
+ lp->vr = 0;
+ break;
+ case V(R):=V(R)+1:
+ lp->vr++;
+ break;
+ case V(S):=0:
+ lp->vs = 0;
+ break;
+ case V(S):=N(R):
+ lp->vs = fr->i_hdr.nr;
+ break;
+ case F_FLAG:=P:
+ if (IS_UFRAME(fr))
+ lp->f_flag = fr->u_hdr.u_pflag;
+ else
+ lp->f_flag = fr->i_hdr.i_pflag;
+ break;
+ default:;
+ }
+ pc++;
+ }
+}
+
+
+/*
+ * Process_otype2_frame will handle incoming frames
+ * for 802.2 Type 2 Procedure.
+ */
+
+void llc_process_otype2_frame(llcptr lp, struct sk_buff *skb, char type)
+{
+ int idx; /* index in transition table */
+ int pc_label; /* action to perform, from tr tbl */
+ int validation; /* result of validate_seq_nos */
+ int p_flag_received; /* p_flag in received frame */
+ frameptr fr;
+
+ fr = (frameptr) skb->data;
+
+ if (IS_UFRAME(fr))
+ p_flag_received = fr->u_hdr.u_pflag;
+ else
+ p_flag_received = fr->i_hdr.i_pflag;
+
+ switch(lp->state)
+ {
+ /* Compute index in transition table: */
+ case ADM:
+ idx = type;
+ idx = (idx << 1) + p_flag_received;
+ break;
+ case CONN:
+ case RESET_WAIT:
+ case RESET_CHECK:
+ case ERROR:
+ idx = type;
+ break;
+ case SETUP:
+ case RESET:
+ case D_CONN:
+ idx = type;
+ idx = (idx << 1) + lp->p_flag;
+ break;
+ case NORMAL:
+ case BUSY:
+ case REJECT:
+ case AWAIT:
+ case AWAIT_BUSY:
+ case AWAIT_REJECT:
+ validation = llc_validate_seq_nos(lp, fr);
+ if (validation > 3)
+ type = BAD_FRAME;
+ idx = type;
+ idx = (idx << 1);
+ if (validation & 1)
+ idx = idx +1;
+ idx = (idx << 1) + p_flag_received;
+ idx = (idx << 1) + lp->p_flag;
+ default:
+ printk("llc_proc: bad state\n");
+ return;
+ }
+ idx = (idx << 1) + pdutr_offset[lp->state];
+ lp->state = pdutr_entry[idx +1];
+ pc_label = pdutr_entry[idx];
+ if (pc_label != NOP)
+ {
+ llc_interpret_pseudo_code(lp, pc_label, skb, type);
+ if(lp->llc_callbacks)
+ {
+ lp->llc_event(lp);
+ lp->llc_callbacks=0;
+ }
+ /*
+ * lp may no longer be valid after this point. Be
+ * careful what is added!
+ */
+ }
+}
+
+
+void llc_timer_expired(llcptr lp, int t)
+{
+ int idx; /* index in transition table */
+ int pc_label; /* action to perform, from tr tbl */
+
+ lp->timer_state[t] = TIMER_EXPIRED;
+ idx = lp->state; /* Compute index in transition table: */
+ idx = (idx << 2) + t;
+ idx = idx << 1;
+ if (lp->retry_count >= lp->n2)
+ idx = idx + 1;
+ idx = (idx << 1) + lp->s_flag;
+ idx = (idx << 1) + lp->p_flag;
+ idx = idx << 1; /* 2 bytes per entry: action & newstate */
+
+ pc_label = timertr_entry[idx];
+ if (pc_label != NOP)
+ {
+ llc_interpret_pseudo_code(lp, pc_label, NULL, NO_FRAME);
+ lp->state = timertr_entry[idx +1];
+ }
+ lp->timer_state[t] = TIMER_IDLE;
+ if(lp->llc_callbacks)
+ {
+ lp->llc_event(lp);
+ lp->llc_callbacks=0;
+ }
+ /*
+ * And lp may have vanished in the event callback
+ */
+}
+
diff --git a/uClinux-2.4.31-uc0/net/802/fc.c b/uClinux-2.4.31-uc0/net/802/fc.c
new file mode 100644
index 0000000..8205520
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/fc.c
@@ -0,0 +1,135 @@
+/*
+ * NET3: Fibre Channel device handling subroutines
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Vineet Abraham <vma@iol.unh.edu>
+ * v 1.0 03/22/99
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/fcdevice.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/timer.h>
+#include <linux/net.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <net/arp.h>
+
+/*
+ * Put the headers on a Fibre Channel packet.
+ */
+
+int fc_header(struct sk_buff *skb, struct net_device *dev, unsigned short type,
+ void *daddr, void *saddr, unsigned len)
+{
+ struct fch_hdr *fch;
+ int hdr_len;
+
+ /*
+ * Add the 802.2 SNAP header if IP as the IPv4 code calls
+ * dev->hard_header directly.
+ */
+ if (type == ETH_P_IP || type == ETH_P_ARP)
+ {
+ struct fcllc *fcllc=(struct fcllc *)(fch+1);
+
+ hdr_len = sizeof(struct fch_hdr) + sizeof(struct fcllc);
+ fch = (struct fch_hdr *)skb_push(skb, hdr_len);
+ fcllc = (struct fcllc *)(fch+1);
+ fcllc->dsap = fcllc->ssap = EXTENDED_SAP;
+ fcllc->llc = UI_CMD;
+ fcllc->protid[0] = fcllc->protid[1] = fcllc->protid[2] = 0x00;
+ fcllc->ethertype = htons(type);
+ }
+ else
+ {
+ hdr_len = sizeof(struct fch_hdr);
+ fch = (struct fch_hdr *)skb_push(skb, hdr_len);
+ }
+
+ if(saddr)
+ memcpy(fch->saddr,saddr,dev->addr_len);
+ else
+ memcpy(fch->saddr,dev->dev_addr,dev->addr_len);
+
+ if(daddr)
+ {
+ memcpy(fch->daddr,daddr,dev->addr_len);
+ return(hdr_len);
+ }
+ return -hdr_len;
+}
+
+/*
+ * A neighbour discovery of some species (eg arp) has completed. We
+ * can now send the packet.
+ */
+
+int fc_rebuild_header(struct sk_buff *skb)
+{
+ struct fch_hdr *fch=(struct fch_hdr *)skb->data;
+ struct fcllc *fcllc=(struct fcllc *)(skb->data+sizeof(struct fch_hdr));
+ if(fcllc->ethertype != htons(ETH_P_IP)) {
+ printk("fc_rebuild_header: Don't know how to resolve type %04X addresses ?\n",(unsigned int)htons(fcllc->ethertype));
+ return 0;
+ }
+#ifdef CONFIG_INET
+ return arp_find(fch->daddr, skb);
+#else
+ return 0;
+#endif
+}
+
+EXPORT_SYMBOL(fc_type_trans);
+
+unsigned short
+fc_type_trans(struct sk_buff *skb, struct net_device *dev)
+{
+ struct fch_hdr *fch = (struct fch_hdr *)skb->data;
+ struct fcllc *fcllc;
+
+ skb->mac.raw = skb->data;
+ fcllc = (struct fcllc *)(skb->data + sizeof (struct fch_hdr) + 2);
+ skb_pull(skb, sizeof (struct fch_hdr) + 2);
+
+ if (*fch->daddr & 1) {
+ if (!memcmp(fch->daddr, dev->broadcast, FC_ALEN))
+ skb->pkt_type = PACKET_BROADCAST;
+ else
+ skb->pkt_type = PACKET_MULTICAST;
+ } else if (dev->flags & IFF_PROMISC) {
+ if (memcmp(fch->daddr, dev->dev_addr, FC_ALEN))
+ skb->pkt_type = PACKET_OTHERHOST;
+ }
+
+ /*
+ * Strip the SNAP header from ARP packets since we don't pass
+ * them through to the 802.2/SNAP layers.
+ */
+ if (fcllc->dsap == EXTENDED_SAP &&
+ (fcllc->ethertype == ntohs(ETH_P_IP) ||
+ fcllc->ethertype == ntohs(ETH_P_ARP))) {
+ skb_pull(skb, sizeof (struct fcllc));
+ return fcllc->ethertype;
+ }
+
+ return ntohs(ETH_P_802_2);
+}
diff --git a/uClinux-2.4.31-uc0/net/802/fddi.c b/uClinux-2.4.31-uc0/net/802/fddi.c
new file mode 100644
index 0000000..7edee2b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/fddi.c
@@ -0,0 +1,166 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * FDDI-type device handling.
+ *
+ * Version: @(#)fddi.c 1.0.0 08/12/96
+ *
+ * Authors: Lawrence V. Stefani, <stefani@lkg.dec.com>
+ *
+ * fddi.c is based on previous eth.c and tr.c work by
+ * Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Mark Evans, <evansmp@uhura.aston.ac.uk>
+ * Florian La Roche, <rzsfl@rz.uni-sb.de>
+ * Alan Cox, <gw4pts@gw4pts.ampr.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes
+ * Alan Cox : New arp/rebuild header
+ * Maciej W. Rozycki : IPv6 support
+ */
+
+#include <linux/config.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/fddidevice.h>
+#include <linux/if_ether.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <net/arp.h>
+#include <net/sock.h>
+
+/*
+ * Create the FDDI MAC header for an arbitrary protocol layer
+ *
+ * saddr=NULL means use device source address
+ * daddr=NULL means leave destination address (eg unresolved arp)
+ */
+
+int fddi_header(struct sk_buff *skb, struct net_device *dev, unsigned short type,
+ void *daddr, void *saddr, unsigned len)
+{
+ int hl = FDDI_K_SNAP_HLEN;
+ struct fddihdr *fddi;
+
+ if(type != ETH_P_IP && type != ETH_P_IPV6 && type != ETH_P_ARP)
+ hl=FDDI_K_8022_HLEN-3;
+ fddi = (struct fddihdr *)skb_push(skb, hl);
+ fddi->fc = FDDI_FC_K_ASYNC_LLC_DEF;
+ if(type == ETH_P_IP || type == ETH_P_IPV6 || type == ETH_P_ARP)
+ {
+ fddi->hdr.llc_snap.dsap = FDDI_EXTENDED_SAP;
+ fddi->hdr.llc_snap.ssap = FDDI_EXTENDED_SAP;
+ fddi->hdr.llc_snap.ctrl = FDDI_UI_CMD;
+ fddi->hdr.llc_snap.oui[0] = 0x00;
+ fddi->hdr.llc_snap.oui[1] = 0x00;
+ fddi->hdr.llc_snap.oui[2] = 0x00;
+ fddi->hdr.llc_snap.ethertype = htons(type);
+ }
+
+ /* Set the source and destination hardware addresses */
+
+ if (saddr != NULL)
+ memcpy(fddi->saddr, saddr, dev->addr_len);
+ else
+ memcpy(fddi->saddr, dev->dev_addr, dev->addr_len);
+
+ if (daddr != NULL)
+ {
+ memcpy(fddi->daddr, daddr, dev->addr_len);
+ return(hl);
+ }
+
+ return(-hl);
+}
+
+
+/*
+ * Rebuild the FDDI MAC header. This is called after an ARP
+ * (or in future other address resolution) has completed on
+ * this sk_buff. We now let ARP fill in the other fields.
+ */
+
+int fddi_rebuild_header(struct sk_buff *skb)
+{
+ struct fddihdr *fddi = (struct fddihdr *)skb->data;
+
+#ifdef CONFIG_INET
+ if (fddi->hdr.llc_snap.ethertype == __constant_htons(ETH_P_IP))
+ /* Try to get ARP to resolve the header and fill destination address */
+ return arp_find(fddi->daddr, skb);
+ else
+#endif
+ {
+ printk("%s: Don't know how to resolve type %02X addresses.\n",
+ skb->dev->name, htons(fddi->hdr.llc_snap.ethertype));
+ return(0);
+ }
+}
+
+
+/*
+ * Determine the packet's protocol ID and fill in skb fields.
+ * This routine is called before an incoming packet is passed
+ * up. It's used to fill in specific skb fields and to set
+ * the proper pointer to the start of packet data (skb->data).
+ */
+
+unsigned short fddi_type_trans(struct sk_buff *skb, struct net_device *dev)
+{
+ struct fddihdr *fddi = (struct fddihdr *)skb->data;
+ unsigned short type;
+
+ /*
+ * Set mac.raw field to point to FC byte, set data field to point
+ * to start of packet data. Assume 802.2 SNAP frames for now.
+ */
+
+ skb->mac.raw = skb->data; /* point to frame control (FC) */
+
+ if(fddi->hdr.llc_8022_1.dsap==0xe0)
+ {
+ skb_pull(skb, FDDI_K_8022_HLEN-3);
+ type = __constant_htons(ETH_P_802_2);
+ }
+ else
+ {
+ skb_pull(skb, FDDI_K_SNAP_HLEN); /* adjust for 21 byte header */
+ type=fddi->hdr.llc_snap.ethertype;
+ }
+
+ /* Set packet type based on destination address and flag settings */
+
+ if (*fddi->daddr & 0x01)
+ {
+ if (memcmp(fddi->daddr, dev->broadcast, FDDI_K_ALEN) == 0)
+ skb->pkt_type = PACKET_BROADCAST;
+ else
+ skb->pkt_type = PACKET_MULTICAST;
+ }
+
+ else if (dev->flags & IFF_PROMISC)
+ {
+ if (memcmp(fddi->daddr, dev->dev_addr, FDDI_K_ALEN))
+ skb->pkt_type = PACKET_OTHERHOST;
+ }
+
+ /* Assume 802.2 SNAP frames, for now */
+
+ return(type);
+}
diff --git a/uClinux-2.4.31-uc0/net/802/hippi.c b/uClinux-2.4.31-uc0/net/802/hippi.c
new file mode 100644
index 0000000..2e160f2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/hippi.c
@@ -0,0 +1,154 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * HIPPI-type device handling.
+ *
+ * Version: @(#)hippi.c 1.0.0 05/29/97
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Mark Evans, <evansmp@uhura.aston.ac.uk>
+ * Florian La Roche, <rzsfl@rz.uni-sb.de>
+ * Alan Cox, <gw4pts@gw4pts.ampr.org>
+ * Jes Sorensen, <Jes.Sorensen@cern.ch>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/hippidevice.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <net/arp.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/checksum.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+
+/*
+ * hippi_net_init()
+ *
+ * Do nothing, this is just to pursuade the stupid linker to behave.
+ */
+
+void hippi_net_init(void)
+{
+ return;
+}
+
+/*
+ * Create the HIPPI MAC header for an arbitrary protocol layer
+ *
+ * saddr=NULL means use device source address
+ * daddr=NULL means leave destination address (eg unresolved arp)
+ */
+
+int hippi_header(struct sk_buff *skb, struct net_device *dev,
+ unsigned short type, void *daddr, void *saddr,
+ unsigned len)
+{
+ struct hippi_hdr *hip = (struct hippi_hdr *)skb_push(skb, HIPPI_HLEN);
+
+ if (!len){
+ len = skb->len - HIPPI_HLEN;
+ printk("hippi_header(): length not supplied\n");
+ }
+
+ /*
+ * Due to the stupidity of the little endian byte-order we
+ * have to set the fp field this way.
+ */
+ hip->fp.fixed = __constant_htonl(0x04800018);
+ hip->fp.d2_size = htonl(len + 8);
+ hip->le.fc = 0;
+ hip->le.double_wide = 0; /* only HIPPI 800 for the time being */
+ hip->le.message_type = 0; /* Data PDU */
+
+ hip->le.dest_addr_type = 2; /* 12 bit SC address */
+ hip->le.src_addr_type = 2; /* 12 bit SC address */
+
+ memcpy(hip->le.src_switch_addr, dev->dev_addr + 3, 3);
+ memset(&hip->le.reserved, 0, 16);
+
+ hip->snap.dsap = HIPPI_EXTENDED_SAP;
+ hip->snap.ssap = HIPPI_EXTENDED_SAP;
+ hip->snap.ctrl = HIPPI_UI_CMD;
+ hip->snap.oui[0] = 0x00;
+ hip->snap.oui[1] = 0x00;
+ hip->snap.oui[2] = 0x00;
+ hip->snap.ethertype = htons(type);
+
+ if (daddr)
+ {
+ memcpy(hip->le.dest_switch_addr, daddr + 3, 3);
+ memcpy(&skb->private.ifield, daddr + 2, 4);
+ return HIPPI_HLEN;
+ }
+ return -((int)HIPPI_HLEN);
+}
+
+
+/*
+ * Rebuild the HIPPI MAC header. This is called after an ARP has
+ * completed on this sk_buff. We now let ARP fill in the other fields.
+ */
+
+int hippi_rebuild_header(struct sk_buff *skb)
+{
+ struct hippi_hdr *hip = (struct hippi_hdr *)skb->data;
+
+ /*
+ * Only IP is currently supported
+ */
+
+ if(hip->snap.ethertype != __constant_htons(ETH_P_IP))
+ {
+ printk(KERN_DEBUG "%s: unable to resolve type %X addresses.\n",skb->dev->name,ntohs(hip->snap.ethertype));
+ return 0;
+ }
+
+ /*
+ * We don't support dynamic ARP on HIPPI, but we use the ARP
+ * static ARP tables to hold the I-FIELDs.
+ */
+ return arp_find(hip->le.daddr, skb);
+}
+
+
+/*
+ * Determine the packet's protocol ID.
+ */
+
+unsigned short hippi_type_trans(struct sk_buff *skb, struct net_device *dev)
+{
+ struct hippi_hdr *hip;
+
+ hip = (struct hippi_hdr *) skb->data;
+
+ /*
+ * This is actually wrong ... question is if we really should
+ * set the raw address here.
+ */
+ skb->mac.raw = skb->data;
+ skb_pull(skb, HIPPI_HLEN);
+
+ /*
+ * No fancy promisc stuff here now.
+ */
+
+ return hip->snap.ethertype;
+}
diff --git a/uClinux-2.4.31-uc0/net/802/llc_macinit.c b/uClinux-2.4.31-uc0/net/802/llc_macinit.c
new file mode 100644
index 0000000..3b6dc34
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/llc_macinit.c
@@ -0,0 +1,214 @@
+/*
+ * NET An implementation of the IEEE 802.2 LLC protocol for the
+ * LINUX operating system. LLC is implemented as a set of
+ * state machines and callbacks for higher networking layers.
+ *
+ * Code for initialization, termination, registration and
+ * MAC layer glue.
+ *
+ * Written by Tim Alpaerts, Tim_Alpaerts@toyota-motor-europe.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes
+ * Alan Cox : Chainsawed to Linux format
+ * Added llc_ to names
+ * Started restructuring handlers
+ *
+ * Horst von Brand : Add #include <linux/string.h>
+ */
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/unistd.h>
+#include <linux/string.h>
+#include <linux/netdevice.h>
+#include <linux/init.h>
+#include <net/p8022.h>
+
+#include <asm/byteorder.h>
+
+#include <net/llc_frame.h>
+#include <net/llc.h>
+
+/*
+ * All incoming frames pass thru mac_data_indicate().
+ * On entry the llc structure related to the frame is passed as parameter.
+ * The received sk_buffs with pdus other than I_CMD and I_RSP
+ * are freed by mac_data_indicate() after processing,
+ * the I pdu buffers are freed by the cl2llc client when it no longer needs
+ * the skb.
+*/
+
+int llc_mac_data_indicate(llcptr lp, struct sk_buff *skb)
+{
+ int ll; /* logical length == 802.3 length field */
+ unsigned char p_flag;
+ unsigned char type;
+ frameptr fr;
+ int free=1;
+
+ lp->inc_skb=NULL;
+
+ /*
+ * Truncate buffer to true 802.3 length
+ * [FIXME: move to 802.2 demux]
+ */
+
+ ll = *(skb->data -2) * 256 + *(skb->data -1);
+ skb_trim( skb, ll );
+
+ fr = (frameptr) skb->data;
+ type = llc_decode_frametype( fr );
+
+
+ if (type <= FRMR_RSP)
+ {
+ /*
+ * PDU is of the type 2 set
+ */
+ if ((lp->llc_mode == MODE_ABM)||(type == SABME_CMD))
+ llc_process_otype2_frame(lp, skb, type);
+
+ }
+ else
+ {
+ /*
+ * PDU belongs to type 1 set
+ */
+ p_flag = fr->u_hdr.u_pflag;
+ switch(type)
+ {
+ case TEST_CMD:
+ llc_sendpdu(lp, TEST_RSP, 0,ll -3,
+ fr->u_hdr.u_info);
+ break;
+ case TEST_RSP:
+ lp->llc_callbacks|=LLC_TEST_INDICATION;
+ lp->inc_skb=skb;
+ free=0;
+ break;
+ case XID_CMD:
+ /*
+ * Basic format XID is handled by LLC itself
+ * Doc 5.4.1.1.2 p 48/49
+ */
+
+ if ((ll == 6)&&(fr->u_hdr.u_info[0] == 0x81))
+ {
+ lp->k = fr->u_hdr.u_info[2];
+ llc_sendpdu(lp, XID_RSP,
+ fr->u_hdr.u_pflag, ll -3,
+ fr->u_hdr.u_info);
+ }
+ break;
+
+ case XID_RSP:
+ if( ll == 6 && fr->u_hdr.u_info[0] == 0x81 )
+ {
+ lp->k = fr->u_hdr.u_info[2];
+ }
+ lp->llc_callbacks|=LLC_XID_INDICATION;
+ lp->inc_skb=skb;
+ free=0;
+ break;
+
+ case UI_CMD:
+ lp->llc_callbacks|=LLC_UI_DATA;
+ skb_pull(skb,3);
+ lp->inc_skb=skb;
+ free=0;
+ break;
+
+ default:;
+ /*
+ * All other type 1 pdus ignored for now
+ */
+ }
+ }
+
+ if (free&&(!(IS_IFRAME(fr))))
+ {
+ /*
+ * No auto free for I pdus
+ */
+ skb->sk = NULL;
+ kfree_skb(skb);
+ }
+
+ if(lp->llc_callbacks)
+ {
+ if ( lp->llc_event != NULL ) lp->llc_event(lp);
+ lp->llc_callbacks=0;
+ }
+ return 0;
+}
+
+
+/*
+ * Create an LLC client. As it is the job of the caller to clean up
+ * LLC's on device down, the device list must be locked before this call.
+ */
+
+int register_cl2llc_client(llcptr lp, const char *device, void (*event)(llcptr), u8 *rmac, u8 ssap, u8 dsap)
+{
+ char eye_init[] = "LLC\0";
+
+ memset(lp, 0, sizeof(*lp));
+ lp->dev = __dev_get_by_name(device);
+ if(lp->dev == NULL)
+ return -ENODEV;
+ memcpy(lp->eye, eye_init, sizeof(lp->eye));
+ lp->rw = 1;
+ lp->k = 127;
+ lp->n1 = 1490;
+ lp->n2 = 10;
+ lp->timer_interval[P_TIMER] = HZ; /* 1 sec */
+ lp->timer_interval[REJ_TIMER] = HZ/8;
+ lp->timer_interval[ACK_TIMER] = HZ/8;
+ lp->timer_interval[BUSY_TIMER] = HZ*2;
+ lp->local_sap = ssap;
+ lp->llc_event = event;
+ memcpy(lp->remote_mac, rmac, sizeof(lp->remote_mac));
+ lp->state = 0;
+ lp->llc_mode = MODE_ADM;
+ lp->remote_sap = dsap;
+ skb_queue_head_init(&lp->atq);
+ skb_queue_head_init(&lp->rtq);
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+
+void unregister_cl2llc_client(llcptr lp)
+{
+ llc_cancel_timers(lp);
+ MOD_DEC_USE_COUNT;
+ kfree(lp);
+}
+
+
+EXPORT_SYMBOL(register_cl2llc_client);
+EXPORT_SYMBOL(unregister_cl2llc_client);
+EXPORT_SYMBOL(llc_data_request);
+EXPORT_SYMBOL(llc_unit_data_request);
+EXPORT_SYMBOL(llc_test_request);
+EXPORT_SYMBOL(llc_xid_request);
+EXPORT_SYMBOL(llc_mac_data_indicate);
+EXPORT_SYMBOL(llc_cancel_timers);
+
+#define ALL_TYPES_8022 0
+
+static int __init llc_init(void)
+{
+ printk(KERN_NOTICE "IEEE 802.2 LLC for Linux 2.1 (c) 1996 Tim Alpaerts\n");
+ return 0;
+}
+
+
+module_init(llc_init);
diff --git a/uClinux-2.4.31-uc0/net/802/llc_sendpdu.c b/uClinux-2.4.31-uc0/net/802/llc_sendpdu.c
new file mode 100644
index 0000000..c436c36
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/llc_sendpdu.c
@@ -0,0 +1,357 @@
+/*
+ * NET An implementation of the IEEE 802.2 LLC protocol for the
+ * LINUX operating system. LLC is implemented as a set of
+ * state machines and callbacks for higher networking layers.
+ *
+ * llc_sendpdu(), llc_sendipdu(), resend() + queue handling code
+ *
+ * Written by Tim Alpaerts, Tim_Alpaerts@toyota-motor-europe.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes
+ * Alan Cox : Chainsawed into Linux format, style
+ * Added llc_ to function names
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/p8022.h>
+#include <linux/stat.h>
+#include <asm/byteorder.h>
+#include <net/llc_frame.h>
+#include <net/llc.h>
+
+static unsigned char cntl_byte_encode[] =
+{
+ 0x00, /* I_CMD */
+ 0x01, /* RR_CMD */
+ 0x05, /* RNR_CMD */
+ 0x09, /* REJ_CMD */
+ 0x43, /* DISC_CMD */
+ 0x7F, /* SABME_CMD */
+ 0x00, /* I_RSP */
+ 0x01, /* RR_RSP */
+ 0x05, /* RNR_RSP */
+ 0x09, /* REJ_RSP */
+ 0x63, /* UA_RSP */
+ 0x0F, /* DM_RSP */
+ 0x87, /* FRMR_RSP */
+ 0xFF, /* BAD_FRAME */
+ 0x03, /* UI_CMD */
+ 0xBF, /* XID_CMD */
+ 0xE3, /* TEST_CMD */
+ 0xBF, /* XID_RSP */
+ 0xE3 /* TEST_RSP */
+};
+
+static unsigned char fr_length_encode[] =
+{
+ 0x04, /* I_CMD */
+ 0x04, /* RR_CMD */
+ 0x04, /* RNR_CMD */
+ 0x04, /* REJ_CMD */
+ 0x03, /* DISC_CMD */
+ 0x03, /* SABME_CMD */
+ 0x04, /* I_RSP */
+ 0x04, /* RR_RSP */
+ 0x04, /* RNR_RSP */
+ 0x04, /* REJ_RSP */
+ 0x03, /* UA_RSP */
+ 0x03, /* DM_RSP */
+ 0x03, /* FRMR_RSP */
+ 0x00, /* BAD_FRAME */
+ 0x03, /* UI_CMD */
+ 0x03, /* XID_CMD */
+ 0x03, /* TEST_CMD */
+ 0x03, /* XID_RSP */
+ 0x03 /* TEST_RSP */
+};
+
+static unsigned char cr_bit_encode[] = {
+ 0x00, /* I_CMD */
+ 0x00, /* RR_CMD */
+ 0x00, /* RNR_CMD */
+ 0x00, /* REJ_CMD */
+ 0x00, /* DISC_CMD */
+ 0x00, /* SABME_CMD */
+ 0x01, /* I_RSP */
+ 0x01, /* RR_RSP */
+ 0x01, /* RNR_RSP */
+ 0x01, /* REJ_RSP */
+ 0x01, /* UA_RSP */
+ 0x01, /* DM_RSP */
+ 0x01, /* FRMR_RSP */
+ 0x00, /* BAD_FRAME */
+ 0x00, /* UI_CMD */
+ 0x00, /* XID_CMD */
+ 0x00, /* TEST_CMD */
+ 0x01, /* XID_RSP */
+ 0x01 /* TEST_RSP */
+};
+
+/*
+ * Sendpdu() constructs an output frame in a new skb and
+ * gives it to the MAC layer for transmission.
+ * This function is not used to send I pdus.
+ * No queues are updated here, nothing is saved for retransmission.
+ *
+ * Parameter pf controls both the poll/final bit and dsap
+ * fields in the output pdu.
+ * The dsap trick was needed to implement XID_CMD send with
+ * zero dsap field as described in doc 6.6 item 1 of enum.
+ */
+
+void llc_sendpdu(llcptr lp, char type, char pf, int data_len, char *pdu_data)
+{
+ frameptr fr; /* ptr to output pdu buffer */
+ unsigned short int fl; /* frame length == 802.3 "length" value */
+ struct sk_buff *skb;
+
+ fl = data_len + fr_length_encode[(int)type];
+ skb = alloc_skb(16 + fl, GFP_ATOMIC);
+ if (skb != NULL)
+ {
+ skb->dev = lp->dev;
+ skb_reserve(skb, 16);
+ fr = (frameptr) skb_put(skb, fl);
+ memset(fr, 0, fl);
+ /*
+ * Construct 802.2 header
+ */
+ if (pf & 0x02)
+ fr->pdu_hdr.dsap = 0;
+ else
+ fr->pdu_hdr.dsap = lp->remote_sap;
+ fr->pdu_hdr.ssap = lp->local_sap + cr_bit_encode[(int)type];
+ fr->pdu_cntl.byte1 = cntl_byte_encode[(int)type];
+ /*
+ * Fill in pflag and seq nbrs:
+ */
+ if (IS_SFRAME(fr))
+ {
+ /* case S-frames */
+ if (pf & 0x01)
+ fr->i_hdr.i_pflag = 1;
+ fr->i_hdr.nr = lp->vr;
+ }
+ else
+ {
+ /* case U frames */
+ if (pf & 0x01)
+ fr->u_hdr.u_pflag = 1;
+ }
+
+ if (data_len > 0)
+ { /* append data if any */
+ if (IS_UFRAME(fr))
+ {
+ memcpy(fr->u_hdr.u_info, pdu_data, data_len);
+ }
+ else
+ {
+ memcpy(fr->i_hdr.is_info, pdu_data, data_len);
+ }
+ }
+ lp->dev->hard_header(skb, lp->dev, ETH_P_802_3,
+ lp->remote_mac, NULL, fl);
+ skb->dev=lp->dev;
+ dev_queue_xmit(skb);
+ }
+ else
+ printk(KERN_DEBUG "cl2llc: skb_alloc() in llc_sendpdu() failed\n");
+}
+
+void llc_xid_request(llcptr lp, char opt, int ll, char * data)
+{
+ llc_sendpdu(lp, XID_CMD, opt, ll, data);
+}
+
+void llc_test_request(llcptr lp, int ll, char * data)
+{
+ llc_sendpdu(lp, TEST_CMD, 0, ll, data);
+}
+
+void llc_unit_data_request(llcptr lp, int ll, char * data)
+{
+ llc_sendpdu(lp, UI_CMD, 0, ll, data);
+}
+
+
+/*
+ * llc_sendipdu() Completes an I pdu in an existing skb and gives it
+ * to the MAC layer for transmission.
+ * Parameter "type" must be either I_CMD or I_RSP.
+ * The skb is not freed after xmit, it is kept in case a retransmission
+ * is requested. If needed it can be picked up again from the rtq.
+ */
+
+void llc_sendipdu(llcptr lp, char type, char pf, struct sk_buff *skb)
+{
+ frameptr fr; /* ptr to output pdu buffer */
+ struct sk_buff *tmp;
+
+ fr = (frameptr) skb->data;
+
+ fr->pdu_hdr.dsap = lp->remote_sap;
+ fr->pdu_hdr.ssap = lp->local_sap + cr_bit_encode[(int)type];
+ fr->pdu_cntl.byte1 = cntl_byte_encode[(int)type];
+
+ if (pf)
+ fr->i_hdr.i_pflag = 1; /* p/f and seq numbers */
+ fr->i_hdr.nr = lp->vr;
+ fr->i_hdr.ns = lp->vs;
+ lp->vs++;
+ if (lp->vs > 127)
+ lp->vs = 0;
+ lp->dev->hard_header(skb, lp->dev, ETH_P_802_3,
+ lp->remote_mac, NULL, skb->len);
+ ADD_TO_RTQ(skb); /* add skb to the retransmit queue */
+ tmp=skb_clone(skb, GFP_ATOMIC);
+ if(tmp!=NULL)
+ {
+ tmp->dev=lp->dev;
+ dev_queue_xmit(tmp);
+ }
+}
+
+
+/*
+ * Resend_ipdu() will resend the pdus in the retransmit queue (rtq)
+ * the return value is the number of pdus resend.
+ * ack_nr is N(R) of 1st pdu to resent.
+ * Type is I_CMD or I_RSP for 1st pdu resent.
+ * p is p/f flag 0 or 1 for 1st pdu resent.
+ * All subsequent pdus will be sent as I_CMDs with p/f set to 0
+ */
+
+int llc_resend_ipdu(llcptr lp, unsigned char ack_nr, unsigned char type, char p)
+{
+ struct sk_buff *skb,*tmp;
+ int resend_count;
+ frameptr fr;
+ unsigned long flags;
+
+
+ resend_count = 0;
+
+ save_flags(flags);
+ cli();
+
+ skb = skb_peek(&lp->rtq);
+
+ while(skb && skb != (struct sk_buff *)&lp->rtq)
+ {
+ fr = (frameptr) (skb->data + lp->dev->hard_header_len);
+ if (resend_count == 0)
+ {
+ /*
+ * Resending 1st pdu:
+ */
+
+ if (p)
+ fr->i_hdr.i_pflag = 1;
+ else
+ fr->i_hdr.i_pflag = 0;
+
+ if (type == I_CMD)
+ fr->pdu_hdr.ssap = fr->pdu_hdr.ssap & 0xfe;
+ else
+ fr->pdu_hdr.ssap = fr->pdu_hdr.ssap | 0x01;
+ }
+ else
+ {
+ /*
+ * Resending pdu 2...n
+ */
+
+ fr->pdu_hdr.ssap = fr->pdu_hdr.ssap & 0xfe;
+ fr->i_hdr.i_pflag = 0;
+ }
+ fr->i_hdr.nr = lp->vr;
+ fr->i_hdr.ns = lp->vs;
+ lp->vs++;
+ if (lp->vs > 127)
+ lp->vs = 0;
+ tmp=skb_clone(skb, GFP_ATOMIC);
+ if(tmp!=NULL)
+ {
+ tmp->dev = lp->dev;
+ dev_queue_xmit(tmp);
+ }
+ resend_count++;
+ skb = skb->next;
+ }
+ restore_flags(flags);
+ return resend_count;
+}
+
+/* ************** internal queue management code ****************** */
+
+
+/*
+ * Remove one skb from the front of the awaiting transmit queue
+ * (this is the skb longest on the queue) and return a pointer to
+ * that skb.
+ */
+
+struct sk_buff *llc_pull_from_atq(llcptr lp)
+{
+ return skb_dequeue(&lp->atq);
+}
+
+/*
+ * Free_acknowledged_skbs(), remove from retransmit queue (rtq)
+ * and free all skbs with an N(S) chronologicaly before 'pdu_ack'.
+ * The return value is the number of pdus acknowledged.
+ */
+
+int llc_free_acknowledged_skbs(llcptr lp, unsigned char pdu_ack)
+{
+ struct sk_buff *pp;
+ frameptr fr;
+ int ack_count;
+ unsigned char ack; /* N(S) of most recently ack'ed pdu */
+ unsigned char ns_save;
+ unsigned long flags;
+
+ if (pdu_ack > 0)
+ ack = pdu_ack -1;
+ else
+ ack = 127;
+
+ ack_count = 0;
+
+ save_flags(flags);
+ cli();
+
+ pp = skb_dequeue(&lp->rtq);
+ while (pp != NULL)
+ {
+ /*
+ * Locate skb with N(S) == ack
+ */
+
+ /*
+ * BUG: FIXME - use skb->h.*
+ */
+ fr = (frameptr) (pp->data + lp->dev->hard_header_len);
+ ns_save = fr->i_hdr.ns;
+
+ kfree_skb(pp);
+ ack_count++;
+
+ if (ns_save == ack)
+ break;
+ pp = skb_dequeue(&lp->rtq);
+ }
+ restore_flags(flags);
+ return ack_count;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/802/llc_utility.c b/uClinux-2.4.31-uc0/net/802/llc_utility.c
new file mode 100644
index 0000000..d0a5801
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/llc_utility.c
@@ -0,0 +1,253 @@
+/*
+ * NET An implementation of the IEEE 802.2 LLC protocol for the
+ * LINUX operating system. LLC is implemented as a set of
+ * state machines and callbacks for higher networking layers.
+ *
+ * Small utilities, Linux timer handling.
+ *
+ * Written by Tim Alpaerts, Tim_Alpaerts@toyota-motor-europe.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes
+ * Alan Cox : Chainsawed into Linux form.
+ * Added llc_ function name prefixes.
+ * Fixed bug in stop/start timer.
+ * Added llc_cancel_timers for closing
+ * down an llc
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <net/llc_frame.h>
+#include <net/llc.h>
+
+int llc_decode_frametype(frameptr fr)
+{
+ if (IS_UFRAME(fr))
+ { /* unnumbered cmd/rsp */
+ switch(fr->u_mm.mm & 0x3B)
+ {
+ case 0x1B:
+ return(SABME_CMD);
+ break;
+ case 0x10:
+ return(DISC_CMD);
+ break;
+ case 0x18:
+ return(UA_RSP);
+ break;
+ case 0x03:
+ return(DM_RSP);
+ break;
+ case 0x21:
+ return(FRMR_RSP);
+ break;
+ case 0x00:
+ return(UI_CMD);
+ break;
+ case 0x2B:
+ if (IS_RSP(fr))
+ return(XID_RSP);
+ else
+ return(XID_CMD);
+ break;
+ case 0x38:
+ if (IS_RSP(fr))
+ return(TEST_RSP);
+ else
+ return(TEST_CMD);
+ break;
+ default:
+ return(BAD_FRAME);
+ }
+ }
+ else if (IS_SFRAME(fr))
+ { /* supervisory cmd/rsp */
+ switch(fr->s_hdr.ss)
+ {
+ case 0x00:
+ if (IS_RSP(fr))
+ return(RR_RSP);
+ else
+ return(RR_CMD);
+ break;
+ case 0x02:
+ if (IS_RSP(fr))
+ return(REJ_RSP);
+ else
+ return(REJ_CMD);
+ break;
+ case 0x01:
+ if (IS_RSP(fr))
+ return(RNR_RSP);
+ else
+ return(RNR_CMD);
+ break;
+ default:
+ return(BAD_FRAME);
+ }
+ }
+ else
+ { /* information xfer */
+ if (IS_RSP(fr))
+ return(I_RSP);
+ else
+ return(I_CMD);
+ }
+}
+
+
+/*
+ * Validate_seq_nos will check N(S) and N(R) to see if they are
+ * invalid or unexpected.
+ * "unexpected" is explained on p44 Send State Variable.
+ * The return value is:
+ * 4 * invalid N(R) +
+ * 2 * invalid N(S) +
+ * 1 * unexpected N(S)
+ */
+
+int llc_validate_seq_nos(llcptr lp, frameptr fr)
+{
+ int res;
+
+ /*
+ * A U-frame is always good
+ */
+
+ if (IS_UFRAME(fr))
+ return(0);
+
+ /*
+ * For S- and I-frames check N(R):
+ */
+
+ if (fr->i_hdr.nr == lp->vs)
+ { /* if N(R) = V(S) */
+ res = 0; /* N(R) is good */
+ }
+ else
+ { /* lp->k = transmit window size */
+ if (lp->vs >= lp->k)
+ { /* if window not wrapped around 127 */
+ if ((fr->i_hdr.nr < lp->vs) &&
+ (fr->i_hdr.nr > (lp->vs - lp->k)))
+ res = 0;
+ else
+ res = 4; /* N(R) invalid */
+ }
+ else
+ { /* window wraps around 127 */
+ if ((fr->i_hdr.nr < lp->vs) ||
+ (fr->i_hdr.nr > (128 + lp->vs - lp->k)))
+ res = 0;
+ else
+ res = 4; /* N(R) invalid */
+ }
+ }
+
+ /*
+ * For an I-frame, must check N(S) also:
+ */
+
+ if (IS_IFRAME(fr))
+ {
+ if (fr->i_hdr.ns == lp->vr)
+ return res; /* N(S) good */
+ if (lp->vr >= lp->rw)
+ {
+ /* if receive window not wrapped */
+
+ if ((fr->i_hdr.ns < lp->vr) &&
+ (fr->i_hdr.ns > (lp->vr - lp->k)))
+ res = res +1; /* N(S) unexpected */
+ else
+ res = res +2; /* N(S) invalid */
+ }
+ else
+ {
+ /* Window wraps around 127 */
+
+ if ((fr->i_hdr.ns < lp->vr) ||
+ (fr->i_hdr.ns > (128 + lp->vr - lp->k)))
+ res = res +1; /* N(S) unexpected */
+ else
+ res = res +2; /* N(S) invalid */
+ }
+ }
+ return(res);
+}
+
+/* **************** timer management routines ********************* */
+
+static void llc_p_timer_expired(unsigned long ulp)
+{
+ llc_timer_expired((llcptr) ulp, P_TIMER);
+}
+
+static void llc_rej_timer_expired(unsigned long ulp)
+{
+ llc_timer_expired((llcptr) ulp, REJ_TIMER);
+}
+
+static void llc_ack_timer_expired(unsigned long ulp)
+{
+ llc_timer_expired((llcptr) ulp, ACK_TIMER);
+}
+
+static void llc_busy_timer_expired(unsigned long ulp)
+{
+ llc_timer_expired((llcptr) ulp, BUSY_TIMER);
+}
+
+/* exp_fcn is an array holding the 4 entry points of the
+ timer expiry routines above.
+ It is required to keep start_timer() generic.
+ Thank you cdecl.
+ */
+
+static void (* exp_fcn[])(unsigned long) =
+{
+ llc_p_timer_expired,
+ llc_rej_timer_expired,
+ llc_ack_timer_expired,
+ llc_busy_timer_expired
+};
+
+void llc_start_timer(llcptr lp, int t)
+{
+ if (lp->timer_state[t] == TIMER_IDLE)
+ {
+ lp->tl[t].expires = jiffies + lp->timer_interval[t];
+ lp->tl[t].data = (unsigned long) lp;
+ lp->tl[t].function = exp_fcn[t];
+ add_timer(&lp->tl[t]);
+ lp->timer_state[t] = TIMER_RUNNING;
+ }
+}
+
+void llc_stop_timer(llcptr lp, int t)
+{
+ if (lp->timer_state[t] == TIMER_RUNNING)
+ {
+ del_timer(&lp->tl[t]);
+ lp->timer_state[t] = TIMER_IDLE;
+ }
+}
+
+void llc_cancel_timers(llcptr lp)
+{
+ llc_stop_timer(lp, P_TIMER);
+ llc_stop_timer(lp, REJ_TIMER);
+ llc_stop_timer(lp, ACK_TIMER);
+ llc_stop_timer(lp, BUSY_TIMER);
+}
+
diff --git a/uClinux-2.4.31-uc0/net/802/p8022.c b/uClinux-2.4.31-uc0/net/802/p8022.c
new file mode 100644
index 0000000..320deb3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/p8022.c
@@ -0,0 +1,153 @@
+/*
+ * NET3: Support for 802.2 demultiplexing off Ethernet (Token ring
+ * is kept separate see p8022tr.c)
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Demultiplex 802.2 encoded protocols. We match the entry by the
+ * SSAP/DSAP pair and then deliver to the registered datalink that
+ * matches. The control byte is ignored and handling of such items
+ * is up to the routine passed the frame.
+ *
+ * Unlike the 802.3 datalink we have a list of 802.2 entries as there
+ * are multiple protocols to demux. The list is currently short (3 or
+ * 4 entries at most). The current demux assumes this.
+ */
+
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/datalink.h>
+#include <linux/mm.h>
+#include <linux/in.h>
+#include <linux/init.h>
+#include <net/p8022.h>
+
+static struct datalink_proto *p8022_list = NULL;
+
+/*
+ * We don't handle the loopback SAP stuff, the extended
+ * 802.2 command set, multicast SAP identifiers and non UI
+ * frames. We have the absolute minimum needed for IPX,
+ * IP and Appletalk phase 2. See the llc_* routines for
+ * support libraries if your protocol needs these.
+ */
+
+static struct datalink_proto *find_8022_client(unsigned char type)
+{
+ struct datalink_proto *proto;
+
+ for (proto = p8022_list;
+ ((proto != NULL) && (*(proto->type) != type));
+ proto = proto->next)
+ ;
+
+ return proto;
+}
+
+int p8022_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
+{
+ struct datalink_proto *proto;
+
+ proto = find_8022_client(*(skb->h.raw));
+ if (proto != NULL)
+ {
+ skb->h.raw += 3;
+ skb->nh.raw += 3;
+ skb_pull(skb,3);
+ return proto->rcvfunc(skb, dev, pt);
+ }
+
+ skb->sk = NULL;
+ kfree_skb(skb);
+ return 0;
+}
+
+static void p8022_datalink_header(struct datalink_proto *dl,
+ struct sk_buff *skb, unsigned char *dest_node)
+{
+ struct net_device *dev = skb->dev;
+ unsigned char *rawp;
+
+ rawp = skb_push(skb,3);
+ *rawp++ = dl->type[0];
+ *rawp++ = dl->type[0];
+ *rawp = 0x03; /* UI */
+ dev->hard_header(skb, dev, ETH_P_802_3, dest_node, NULL, skb->len);
+}
+
+static struct packet_type p8022_packet_type =
+{
+ 0, /* MUTTER ntohs(ETH_P_8022),*/
+ NULL, /* All devices */
+ p8022_rcv,
+ NULL,
+ NULL,
+};
+
+EXPORT_SYMBOL(register_8022_client);
+EXPORT_SYMBOL(unregister_8022_client);
+
+static int __init p8022_init(void)
+{
+ p8022_packet_type.type=htons(ETH_P_802_2);
+ dev_add_pack(&p8022_packet_type);
+ return 0;
+}
+
+static void __exit p8022_exit(void)
+{
+ dev_remove_pack(&p8022_packet_type);
+ return;
+}
+
+module_init(p8022_init);
+module_exit(p8022_exit);
+
+struct datalink_proto *register_8022_client(unsigned char type, int (*rcvfunc)(struct sk_buff *, struct net_device *, struct packet_type *))
+{
+ struct datalink_proto *proto;
+
+ if (find_8022_client(type) != NULL)
+ return NULL;
+
+ proto = (struct datalink_proto *) kmalloc(sizeof(*proto), GFP_ATOMIC);
+ if (proto != NULL) {
+ proto->type[0] = type;
+ proto->type_len = 1;
+ proto->rcvfunc = rcvfunc;
+ proto->header_length = 3;
+ proto->datalink_header = p8022_datalink_header;
+ proto->string_name = "802.2";
+ proto->next = p8022_list;
+ p8022_list = proto;
+ }
+
+ return proto;
+}
+
+void unregister_8022_client(unsigned char type)
+{
+ struct datalink_proto *tmp, **clients = &p8022_list;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ while ((tmp = *clients) != NULL)
+ {
+ if (tmp->type[0] == type) {
+ *clients = tmp->next;
+ kfree(tmp);
+ break;
+ } else {
+ clients = &tmp->next;
+ }
+ }
+
+ restore_flags(flags);
+}
+
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/802/p8023.c b/uClinux-2.4.31-uc0/net/802/p8023.c
new file mode 100644
index 0000000..57649c2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/p8023.c
@@ -0,0 +1,62 @@
+/*
+ * NET3: 802.3 data link hooks used for IPX 802.3
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * 802.3 isn't really a protocol data link layer. Some old IPX stuff
+ * uses it however. Note that there is only one 802.3 protocol layer
+ * in the system. We don't currently support different protocols
+ * running raw 802.3 on different devices. Thankfully nobody else
+ * has done anything like the old IPX.
+ */
+
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/datalink.h>
+#include <linux/mm.h>
+#include <linux/in.h>
+
+/*
+ * Place an 802.3 header on a packet. The driver will do the mac
+ * addresses, we just need to give it the buffer length.
+ */
+
+static void p8023_datalink_header(struct datalink_proto *dl,
+ struct sk_buff *skb, unsigned char *dest_node)
+{
+ struct net_device *dev = skb->dev;
+ dev->hard_header(skb, dev, ETH_P_802_3, dest_node, NULL, skb->len);
+}
+
+/*
+ * Create an 802.3 client. Note there can be only one 802.3 client
+ */
+
+struct datalink_proto *make_8023_client(void)
+{
+ struct datalink_proto *proto;
+
+ proto = (struct datalink_proto *) kmalloc(sizeof(*proto), GFP_ATOMIC);
+ if (proto != NULL)
+ {
+ proto->type_len = 0;
+ proto->header_length = 0;
+ proto->datalink_header = p8023_datalink_header;
+ proto->string_name = "802.3";
+ }
+ return proto;
+}
+
+/*
+ * Destroy the 802.3 client.
+ */
+
+void destroy_8023_client(struct datalink_proto *dl)
+{
+ if (dl)
+ kfree(dl);
+}
+
diff --git a/uClinux-2.4.31-uc0/net/802/pseudo/Makefile b/uClinux-2.4.31-uc0/net/802/pseudo/Makefile
new file mode 100644
index 0000000..d9f4160
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/pseudo/Makefile
@@ -0,0 +1,13 @@
+all: pseudocode.h actionnm.h
+
+clean:
+ touch pseudocode.h actionnm.h
+ rm pseudocode.h actionnm.h
+
+pseudocode.h: pseudocode opcd2num.sed compile.awk
+ sed -f opcd2num.sed pseudocode | awk -f compile.awk >pseudocode.h
+
+actionnm.h: pseudocode.h actionnm.awk
+ awk -f actionnm.awk pseudocode.h>actionnm.h
+
+
diff --git a/uClinux-2.4.31-uc0/net/802/pseudo/actionnm.awk b/uClinux-2.4.31-uc0/net/802/pseudo/actionnm.awk
new file mode 100644
index 0000000..b5ca782
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/pseudo/actionnm.awk
@@ -0,0 +1,27 @@
+# usage: awk -f actionnm.awk pseudocode.h
+#
+BEGIN { "date" | getline
+ today = $0
+ printf("\n/* this file generated on %s */\n", today )
+ printf("\nstatic char *action_names[] = { \n " )
+ opl = 0
+}
+
+/^#define/ {
+ if ( opl > 3 ) {
+ printf("\n ")
+ opl = 0
+ }
+ opl = opl +1
+ t = sprintf("\"%s\"", $2 )
+ printf("%-15s ,", t )
+# printf("%-10s", $2 )
+}
+
+END {
+ if ( opl > 3 ) {
+ printf("\n ")
+ }
+ printf("\t 0\n};\n\n")
+}
+
diff --git a/uClinux-2.4.31-uc0/net/802/pseudo/actionnm.h b/uClinux-2.4.31-uc0/net/802/pseudo/actionnm.h
new file mode 100644
index 0000000..924cf99
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/pseudo/actionnm.h
@@ -0,0 +1,51 @@
+
+/* this file generated on Thu Oct 24 11:42:37 GMT 1996 */
+
+static char *action_names[] = {
+ "NOP" ,"ADM1" ,"ADM2" ,"ADM3" ,
+ "ADM4" ,"ADM5" ,"CONN2" ,"CONN3" ,
+ "CONN4" ,"CONN5" ,"RESWAIT1" ,"RESWAIT2" ,
+ "RESWAIT3" ,"RESWAIT4" ,"RESWAIT5" ,"RESWAIT6" ,
+ "RESWAIT7" ,"RESWAIT8" ,"RESCHK1" ,"RESCHK2" ,
+ "RESCHK3" ,"RESCHK4" ,"RESCHK5" ,"RESCHK6" ,
+ "SETUP1" ,"SETUP2" ,"SETUP3" ,"SETUP4" ,
+ "SETUP5" ,"SETUP6" ,"SETUP7" ,"SETUP8" ,
+ "RESET1" ,"RESET2" ,"RESET3" ,"RESET4" ,
+ "RESET5" ,"RESET6" ,"RESET7" ,"RESET8" ,
+ "D_CONN1" ,"D_CONN2" ,"D_CONN3" ,"D_CONN4" ,
+ "D_CONN5" ,"D_CONN6" ,"D_CONN7" ,"ERR1" ,
+ "ERR2" ,"ERR3" ,"ERR4" ,"ERR5" ,
+ "ERR6" ,"ERR7" ,"ERR8" ,"SH1" ,
+ "SH2" ,"SH3" ,"SH4" ,"SH5" ,
+ "SH6" ,"SH7" ,"SH8" ,"SH9" ,
+ "SH10" ,"SH11" ,"NORMAL1" ,"NORMAL2" ,
+ "NORMAL3" ,"NORMAL4" ,"NORMAL5" ,"NORMAL6" ,
+ "NORMAL7" ,"NORMAL8A" ,"NORMAL8B" ,"NORMAL9" ,
+ "NORMAL10" ,"NORMAL11" ,"NORMAL12" ,"NORMAL13" ,
+ "NORMAL14" ,"NORMAL15" ,"NORMAL16" ,"NORMAL17" ,
+ "NORMAL18" ,"NORMAL19" ,"NORMAL20" ,"BUSY1" ,
+ "BUSY2" ,"BUSY3" ,"BUSY4" ,"BUSY5" ,
+ "BUSY6" ,"BUSY7" ,"BUSY8" ,"BUSY9" ,
+ "BUSY10" ,"BUSY11" ,"BUSY12" ,"BUSY13" ,
+ "BUSY14" ,"BUSY15" ,"BUSY16" ,"BUSY17" ,
+ "BUSY18" ,"BUSY19" ,"BUSY20" ,"BUSY21" ,
+ "BUSY22" ,"BUSY23" ,"BUSY24" ,"BUSY25" ,
+ "BUSY26" ,"REJECT1" ,"REJECT2" ,"REJECT3" ,
+ "REJECT4" ,"REJECT5" ,"REJECT6" ,"REJECT7" ,
+ "REJECT8" ,"REJECT9" ,"REJECT10" ,"REJECT11" ,
+ "REJECT12" ,"REJECT13" ,"REJECT14" ,"REJECT15" ,
+ "REJECT16" ,"REJECT17" ,"REJECT18" ,"REJECT19" ,
+ "REJECT20" ,"AWAIT1" ,"AWAIT2" ,"AWAIT3" ,
+ "AWAIT4" ,"AWAIT5" ,"AWAIT6" ,"AWAIT7" ,
+ "AWAIT8" ,"AWAIT9" ,"AWAIT10" ,"AWAIT11" ,
+ "AWAIT12" ,"AWAIT13" ,"AWAIT14" ,"AWAIT_BUSY1" ,
+ "AWAIT_BUSY2" ,"AWAIT_BUSY3" ,"AWAIT_BUSY4" ,"AWAIT_BUSY5" ,
+ "AWAIT_BUSY6" ,"AWAIT_BUSY7" ,"AWAIT_BUSY8" ,"AWAIT_BUSY9" ,
+ "AWAIT_BUSY10" ,"AWAIT_BUSY11" ,"AWAIT_BUSY12" ,"AWAIT_BUSY13" ,
+ "AWAIT_BUSY14" ,"AWAIT_BUSY15" ,"AWAIT_BUSY16" ,"AWAIT_REJECT1" ,
+ "AWAIT_REJECT2" ,"AWAIT_REJECT3" ,"AWAIT_REJECT4" ,"AWAIT_REJECT5" ,
+ "AWAIT_REJECT6" ,"AWAIT_REJECT7" ,"AWAIT_REJECT8" ,"AWAIT_REJECT9" ,
+ "AWAIT_REJECT10" ,"AWAIT_REJECT11" ,"AWAIT_REJECT12" ,"AWAIT_REJECT13" ,
+ 0
+};
+
diff --git a/uClinux-2.4.31-uc0/net/802/pseudo/compile.awk b/uClinux-2.4.31-uc0/net/802/pseudo/compile.awk
new file mode 100644
index 0000000..ca901fa
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/pseudo/compile.awk
@@ -0,0 +1,57 @@
+# usage: cat pseudocode | sed -f act2num | awk -f compile.awk
+#
+#
+BEGIN { "date" | getline
+ today = $0
+ printf("\n/* this file generated on %s */\n", today )
+ printf("\nstatic char pseudo_code [ ] = { \n" )
+ opl = 0 # op codes on the current line
+
+ opc = 0 # opcode counter
+ fpi = 0 # fill pointer for idx array
+}
+
+/^;/ { } # line starting with semicolon is comment
+
+/^[A-Z]/ { # start of a new action
+ emit( 0 )
+ idx[ ++fpi ] = opc
+ name[ fpi ] = $1
+ emit( $2 )
+}
+
+/^[\t ]/ {
+ emit( $1 )
+}
+
+END {
+ if ( opl > 8 ) {
+ printf("\n")
+ }
+ printf("\t 0\n};\n\n")
+ printf("static short int pseudo_code_idx [ ] ={\n")
+ opl = 0
+ emit( 0 )
+ for( ii = 1; ii <= fpi; ii++ )
+ emit( idx[ ii ] )
+ if ( opl > 8 ) {
+ printf("\n")
+ }
+ printf("\t 0\n};\n\n")
+
+ printf("#define %-10s \t %3d \n", "NOP", 0 )
+ for( ii = 1; ii <= fpi; ii++ )
+ printf("#define %-10s \t %3d \n", name[ ii ], ii )
+ printf("\n")
+}
+
+function emit( opcode ){ # Niclaus Wirth
+ if ( opl > 8 ) {
+ printf("\n")
+ opl = 0
+ }
+ opl = opl +1
+ printf("\t%4d,", opcode )
+ opc++
+}
+
diff --git a/uClinux-2.4.31-uc0/net/802/pseudo/opcd2num.sed b/uClinux-2.4.31-uc0/net/802/pseudo/opcd2num.sed
new file mode 100644
index 0000000..c66f85a
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/pseudo/opcd2num.sed
@@ -0,0 +1,72 @@
+s/NOP/0/
+s/DUMMY_6/6/
+s/DUMMY_8/8/
+s/IF_F=1_CLEAR_REMOTE_BUSY/9/
+s/CLEAR_REMOTE_BUSY/1/
+s/CONNECT_CONFIRM/3/
+s/DISCONNECT_INDICATION/5/
+s/CONNECT_INDICATION/2/
+s/IF_DATA_FLAG_=0_THEN_DATA_FLAG:=1/55/
+s/DATA_FLAG:=0/53/
+s/DATA_FLAG:=1/54/
+s/DATA_FLAG:=2/52/
+s/DATA_INDICATION/4/
+s/F_FLAG:=P/65/
+s/IF_DATA_FLAG=2_STOP_REJ_TIMER/10/
+s/OPTIONAL_SEND_RNR_XXX(X=0)/30/
+s/P_FLAG:=0/56/
+s/P_FLAG:=P/57/
+s/RE-SEND_FRMR_RSP(F=0)/14/
+s/RE-SEND_FRMR_RSP(F=P)/15/
+s/RE-SEND_I_CMD(P=1)_OR_SEND_RR/18/
+s/RE-SEND_I_CMD(P=1)/17/
+s/RE-SEND_I_RSP(F=1)/22/
+s/RE-SEND_I_XXX(X=0)_OR_SEND_RR/21/
+s/RE-SEND_I_XXX(X=0)/20/
+s/REMOTE_BUSY:=0/58/
+s/REPORT_STATUS(FRMR_RECEIVED)/66/
+s/REPORT_STATUS(FRMR_SENT)/67/
+s/REPORT_STATUS(REMOTE_BUSY)/68/
+s/REPORT_STATUS(REMOTE_NOT_BUSY)/69/
+s/RESET_CONFIRM/7/
+s/RESET_INDICATION(LOCAL)/70/
+s/RESET_INDICATION(REMOTE)/71/
+s/RETRY_COUNT:=RETRY_COUNT+1/60/
+s/RETRY_COUNT:=0/59/
+s/SEND_ACKNOWLEDGE_CMD(P=1)/32/
+s/SEND_ACKNOWLEDGE_RSP(F=1)/34/
+s/SEND_ACKNOWLEDGE_XXX(X=0)/36/
+s/SEND_DISC_CMD(P=X)/11/
+s/SEND_DM_RSP(F=X)/12/
+s/SEND_FRMR_RSP(F=X)/13/
+s/SEND_I_CMD(P=1)/16/
+s/SEND_I_XXX(X=0)/19/
+s/SEND_REJ_CMD(P=1)/23/
+s/SEND_REJ_RSP(F=1)/24/
+s/SEND_REJ_XXX(X=0)/25/
+s/SEND_RNR_CMD(F=1)/26/
+s/SEND_RNR_RSP(F=1)/27/
+s/SEND_RNR_XXX(X=0)/28/
+s/SEND_RR_CMD(P=1)/31/
+s/SEND_RR_RSP(F=1)/33/
+s/SEND_RR_XXX(X=0)/35/
+s/SEND_SABME_CMD(P=X)/37/
+s/SEND_UA_RSP(F=X)/38/
+s/SET_REMOTE_BUSY/29/
+s/START_ACK_TIMER_IF_NOT_RUNNING/44/
+s/START_ACK_TIMER/42/
+s/START_P_TIMER/41/
+s/START_REJ_TIMER/43/
+s/STOP_ACK_TIMER/45/
+s/STOP_ALL_TIMERS/48/
+s/STOP_OTHER_TIMERS/49/
+s/STOP_P_TIMER/46/
+s/STOP_REJ_TIMER/47/
+s/S_FLAG:=0/39/
+s/S_FLAG:=1/40/
+s/UPDATE_N(R)_RECEIVED/50/
+s/UPDATE_P_FLAG/51/
+s/V(R):=0/61/
+s/V(R):=V(R)+1/62/
+s/V(S):=0/63/
+s/V(S):=N(R)/64/
diff --git a/uClinux-2.4.31-uc0/net/802/pseudo/opcodes b/uClinux-2.4.31-uc0/net/802/pseudo/opcodes
new file mode 100644
index 0000000..1bd7e72
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/pseudo/opcodes
@@ -0,0 +1,72 @@
+ 0 NOP
+ 1 CLEAR_REMOTE_BUSY
+ 2 CONNECT_INDICATION
+ 3 CONNECT_CONFIRM
+ 4 DATA_INDICATION
+ 5 DISCONNECT_INDICATION
+ 6 DUMMY_6
+ 7 RESET_CONFIRM
+ 8 DUMMY_8
+ 9 IF_F=1_CLEAR_REMOTE_BUSY
+ 10 IF_DATA_FLAG=2_STOP_REJ_TIMER
+ 11 SEND_DISC_CMD(P=X)
+ 12 SEND_DM_RSP(F=X)
+ 13 SEND_FRMR_RSP(F=X)
+ 14 RE-SEND_FRMR_RSP(F=0)
+ 15 RE-SEND_FRMR_RSP(F=P)
+ 16 SEND_I_CMD(P=1)
+ 17 RE-SEND_I_CMD(P=1)
+ 18 RE-SEND_I_CMD(P=1)_OR_SEND_RR
+ 19 SEND_I_XXX(X=0)
+ 20 RE-SEND_I_XXX(X=0)
+ 21 RE-SEND_I_XXX(X=0)_OR_SEND_RR
+ 22 RE-SEND_I_RSP(F=1)
+ 23 SEND_REJ_CMD(P=1)
+ 24 SEND_REJ_RSP(F=1)
+ 25 SEND_REJ_XXX(X=0)
+ 26 SEND_RNR_CMD(F=1)
+ 27 SEND_RNR_RSP(F=1)
+ 28 SEND_RNR_XXX(X=0)
+ 29 SET_REMOTE_BUSY
+ 30 OPTIONAL_SEND_RNR_XXX(X=0)
+ 31 SEND_RR_CMD(P=1)
+ 32 SEND_ACKNOWLEDGE_CMD(P=1)
+ 33 SEND_RR_RSP(F=1)
+ 34 SEND_ACKNOWLEDGE_RSP(F=1)
+ 35 SEND_RR_XXX(X=0)
+ 36 SEND_ACKNOWLEDGE_XXX(X=0)
+ 37 SEND_SABME_CMD(P=X)
+ 38 SEND_UA_RSP(F=X)
+ 39 S_FLAG:=0
+ 40 S_FLAG:=1
+ 41 START_P_TIMER
+ 42 START_ACK_TIMER
+ 43 START_REJ_TIMER
+ 44 START_ACK_TIMER_IF_NOT_RUNNING
+ 45 STOP_ACK_TIMER
+ 46 STOP_P_TIMER
+ 47 STOP_REJ_TIMER
+ 48 STOP_ALL_TIMERS
+ 49 STOP_OTHER_TIMERS
+ 50 UPDATE_N(R)_RECEIVED
+ 51 UPDATE_P_FLAG
+ 52 DATA_FLAG:=2
+ 53 DATA_FLAG:=0
+ 54 DATA_FLAG:=1
+ 55 IF_DATA_FLAG_=0_THEN_DATA_FLAG:=1
+ 56 P_FLAG:=0
+ 57 P_FLAG:=P
+ 58 REMOTE_BUSY:=0
+ 59 RETRY_COUNT:=0
+ 60 RETRY_COUNT:=RETRY_COUNT+1
+ 61 V(R):=0
+ 62 V(R):=V(R)+1
+ 63 V(S):=0
+ 64 V(S):=N(R)
+ 65 F_FLAG:=P
+ 66 REPORT_STATUS(FRMR_RECEIVED)
+ 67 REPORT_STATUS(FRMR_SENT)
+ 68 REPORT_STATUS(REMOTE_BUSY)
+ 69 REPORT_STATUS(REMOTE_NOT_BUSY)
+ 70 RESET_INDICATION(LOCAL)
+ 71 RESET_INDICATION(REMOTE)
diff --git a/uClinux-2.4.31-uc0/net/802/pseudo/opcodesnm.h b/uClinux-2.4.31-uc0/net/802/pseudo/opcodesnm.h
new file mode 100644
index 0000000..d24f2c3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/pseudo/opcodesnm.h
@@ -0,0 +1,23 @@
+static char *opcode_names[] = {
+"NOP", "CLEAR_REMOTE_BUSY", "CONNECT_INDICATION", "CONNECT_CONFIRM", "DATA_INDICATION",
+"DISCONNECT_INDICATION", "DUMMY_6", "RESET_CONFIRM", "DUMMY_8",
+"IF_F=1_CLEAR_REMOTE_BUSY", "IF_DATA_FLAG=2_STOP_REJ_TIMER", "SEND_DISC_CMD(P=X)",
+"SEND_DM_RSP(F=X)", "SEND_FRMR_RSP(F=X)", "RE-SEND_FRMR_RSP(F=0)",
+"RE-SEND_FRMR_RSP(F=P)", "SEND_I_CMD(P=1)", "RE-SEND_I_CMD(P=1)",
+"RE-SEND_I_CMD(P=1)_OR_SEND_RR", "SEND_I_XXX(X=0)", "RE-SEND_I_XXX(X=0)",
+"RE-SEND_I_XXX(X=0)_OR_SEND_RR", "RE-SEND_I_RSP(F=1)", "SEND_REJ_CMD(P=1)",
+"SEND_REJ_RSP(F=1)", "SEND_REJ_XXX(X=0)", "SEND_RNR_CMD(F=1)", "SEND_RNR_RSP(F=1)",
+"SEND_RNR_XXX(X=0)", "SET_REMOTE_BUSY", "OPTIONAL_SEND_RNR_XXX(X=0)",
+"SEND_RR_CMD(P=1)", "SEND_ACKNOWLEDGE_CMD(P=1)", "SEND_RR_RSP(F=1)",
+"SEND_ACKNOWLEDGE_RSP(F=1)", "SEND_RR_XXX(X=0)", "SEND_ACKNOWLEDGE_XXX(X=0)",
+"SEND_SABME_CMD(P=X)", "SEND_UA_RSP(F=X)", "S_FLAG:=0", "S_FLAG:=1", "START_P_TIMER",
+"START_ACK_TIMER", "START_REJ_TIMER", "START_ACK_TIMER_IF_NOT_RUNNING",
+"STOP_ACK_TIMER", "STOP_P_TIMER", "STOP_REJ_TIMER", "STOP_ALL_TIMERS",
+"STOP_OTHER_TIMERS", "UPDATE_N(R)_RECEIVED", "UPDATE_P_FLAG", "DATA_FLAG:=2",
+"DATA_FLAG:=0", "DATA_FLAG:=1", "IF_DATA_FLAG_=0_THEN_DATA_FLAG:=1", "P_FLAG:=0",
+"P_FLAG:=P", "REMOTE_BUSY:=0", "RETRY_COUNT:=0", "RETRY_COUNT:=RETRY_COUNT+1",
+"V(R):=0", "V(R):=V(R)+1", "V(S):=0", "V(S):=N(R)", "F_FLAG:=P",
+"REPORT_STATUS(FRMR_RECEIVED)", "REPORT_STATUS(FRMR_SENT)",
+"REPORT_STATUS(REMOTE_BUSY)", "REPORT_STATUS(REMOTE_NOT_BUSY)",
+"RESET_INDICATION(LOCAL)", "RESET_INDICATION(REMOTE)"
+};
diff --git a/uClinux-2.4.31-uc0/net/802/pseudo/pseudocode b/uClinux-2.4.31-uc0/net/802/pseudo/pseudocode
new file mode 100644
index 0000000..25cc46a
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/pseudo/pseudocode
@@ -0,0 +1,780 @@
+;============================================================================
+;
+; translate this with
+; cat pseudocode | sed -f act2num | awk -f compile.awk >pseudocode.h
+;
+; actionname pseudocode
+;
+;============================================================================
+ADM1 SEND_SABME_CMD(P=X)
+ P_FLAG:=P
+ START_ACK_TIMER
+ RETRY_COUNT:=0
+ S_FLAG:=0
+;
+; instructions in ADM2 have been changed:
+; 1. P_FLAG:=P is probably wrong in doc...
+; I think it should be F_FLAG:=P the way it is in CONN3
+; 2. CONNECT_RESPONSE has been wired in here,
+; CONN1 is no longer referenced
+;
+ADM2 F_FLAG:=P
+ SEND_UA_RSP(F=X)
+ V(S):=0
+ V(R):=0
+ RETRY_COUNT:=0
+ P_FLAG:=0
+ REMOTE_BUSY:=0
+ CONNECT_INDICATION
+ADM3 SEND_DM_RSP(F=X)
+ADM4 SEND_DM_RSP(F=X)
+ADM5 NOP
+;============================================================================
+;CONN1 SEND_UA_RSP(F=X)
+; V(S):=0
+; V(R):=0
+; RETRY_COUNT:=0
+; P_FLAG:=0
+; REMOTE_BUSY:=0
+CONN2 SEND_DM_RSP(F=X)
+CONN3 F_FLAG:=P
+CONN4 DISCONNECT_INDICATION
+CONN5 NOP
+;============================================================================
+RESWAIT1 SEND_SABME_CMD(P=X)
+ P_FLAG:=P
+ START_ACK_TIMER
+ RETRY_COUNT:=0
+RESWAIT2 SEND_UA_RSP(F=X)
+ V(S):=0
+ V(R):=0
+ RETRY_COUNT:=0
+ P_FLAG:=0
+ REMOTE_BUSY:=0
+ RESET_CONFIRM
+RESWAIT3 SEND_DISC_CMD(P=X)
+ P_FLAG:=P
+ START_ACK_TIMER
+ RETRY_COUNT:=0
+RESWAIT4 SEND_DM_RSP(F=X)
+RESWAIT5 DISCONNECT_INDICATION
+RESWAIT6 S_FLAG:=1
+ F_FLAG:=P
+RESWAIT7 SEND_DM_RSP(F=X)
+ DISCONNECT_INDICATION
+RESWAIT8 NOP
+;============================================================================
+RESCHK1 SEND_UA_RSP(F=X)
+ V(S):=0
+ V(R):=0
+ RETRY_COUNT:=0
+ P_FLAG:=0
+ REMOTE_BUSY:=0
+RESCHK2 SEND_DM_RSP(F=X)
+RESCHK3 DISCONNECT_INDICATION
+RESCHK4 F_FLAG:=P
+RESCHK5 SEND_DM_RSP(F=X)
+ DISCONNECT_INDICATION
+RESCHK6 NOP
+;============================================================================
+SETUP1 SEND_UA_RSP(F=X)
+ V(S):=0
+ V(R):=0
+ RETRY_COUNT:=0
+ S_FLAG:=1
+SETUP2 STOP_ACK_TIMER
+ V(S):=0
+ V(R):=0
+ RETRY_COUNT:=0
+ UPDATE_P_FLAG
+ CONNECT_CONFIRM
+ REMOTE_BUSY:=0
+SETUP3 P_FLAG:=0
+ CONNECT_CONFIRM
+ REMOTE_BUSY:=0
+SETUP4 SEND_DM_RSP(F=X)
+ DISCONNECT_INDICATION
+ STOP_ACK_TIMER
+SETUP5 DISCONNECT_INDICATION
+ STOP_ACK_TIMER
+SETUP6 NOP
+SETUP7 SEND_SABME_CMD(P=X)
+ P_FLAG:=P
+ START_ACK_TIMER
+ RETRY_COUNT:=RETRY_COUNT+1
+SETUP8 DISCONNECT_INDICATION
+;============================================================================
+RESET1 SEND_UA_RSP(F=X)
+ V(S):=0
+ V(R):=0
+ RETRY_COUNT:=0
+ S_FLAG:=1
+RESET2 STOP_ACK_TIMER
+ V(S):=0
+ V(R):=0
+ RETRY_COUNT:=0
+ UPDATE_P_FLAG
+ RESET_CONFIRM
+ REMOTE_BUSY:=0
+RESET3 P_FLAG:=0
+ RESET_CONFIRM
+ REMOTE_BUSY:=0
+RESET4 SEND_DM_RSP(F=X)
+ DISCONNECT_INDICATION
+ STOP_ACK_TIMER
+RESET5 DISCONNECT_INDICATION
+ STOP_ACK_TIMER
+RESET6 NOP
+RESET7 SEND_SABME_CMD(P=X)
+ P_FLAG:=P
+ START_ACK_TIMER
+ RETRY_COUNT:=RETRY_COUNT+1
+RESET8 DISCONNECT_INDICATION
+;============================================================================
+D_CONN1 SEND_DM_RSP(F=X)
+ STOP_ACK_TIMER
+D_CONN2 STOP_ACK_TIMER
+D_CONN3 SEND_UA_RSP(F=X)
+D_CONN4 STOP_ACK_TIMER
+D_CONN5 NOP
+D_CONN6 SEND_DISC_CMD(P=X)
+ P_FLAG:=P
+ START_ACK_TIMER
+ RETRY_COUNT:=RETRY_COUNT+1
+D_CONN7 NOP
+;============================================================================
+ERR1 RESET_INDICATION(REMOTE)
+ STOP_ACK_TIMER
+ F_FLAG:=P
+ERR2 SEND_UA_RSP(F=X)
+ DISCONNECT_INDICATION
+ STOP_ACK_TIMER
+ERR3 DISCONNECT_INDICATION
+ STOP_ACK_TIMER
+ERR4 RESET_INDICATION(LOCAL)
+ STOP_ACK_TIMER
+ REPORT_STATUS(FRMR_RECEIVED)
+ S_FLAG:=0
+ERR5 RE-SEND_FRMR_RSP(F=P)
+ START_ACK_TIMER
+ERR6 NOP
+ERR7 RE-SEND_FRMR_RSP(F=0)
+ START_ACK_TIMER
+ RETRY_COUNT:=RETRY_COUNT+1
+ERR8 S_FLAG:=0
+ RESET_INDICATION(LOCAL)
+;============================================================================
+; the shared actions are common to states NORMAL, BUSY, REJECT,
+; AWAIT, AWAIT_BUSY and AWAIT_REJECT.
+;============================================================================
+SH1 SEND_DISC_CMD(P=X)
+ P_FLAG:=P
+ START_ACK_TIMER
+ STOP_OTHER_TIMERS
+ RETRY_COUNT:=0
+SH2 SEND_SABME_CMD(P=X)
+ P_FLAG:=P
+ START_ACK_TIMER
+ STOP_OTHER_TIMERS
+ RETRY_COUNT:=0
+ S_FLAG:=0
+SH3 RESET_INDICATION(REMOTE)
+ F_FLAG:=P
+ STOP_ALL_TIMERS
+SH4 SEND_UA_RSP(F=X)
+ DISCONNECT_INDICATION
+ STOP_ALL_TIMERS
+SH5 STOP_ALL_TIMERS
+ RESET_INDICATION(LOCAL)
+ REPORT_STATUS(FRMR_RECEIVED)
+ S_FLAG:=0
+SH6 DISCONNECT_INDICATION
+ STOP_ALL_TIMERS
+SH7 SEND_FRMR_RSP(F=X)
+ REPORT_STATUS(FRMR_SENT)
+ START_ACK_TIMER
+ STOP_OTHER_TIMERS
+ RETRY_COUNT:=0
+SH8 SEND_FRMR_RSP(F=0)
+ REPORT_STATUS(FRMR_SENT)
+ START_ACK_TIMER
+ STOP_OTHER_TIMERS
+ RETRY_COUNT:=0
+SH9 SEND_FRMR_RSP(F=0)
+ REPORT_STATUS(FRMR_SENT)
+ START_ACK_TIMER
+ STOP_OTHER_TIMERS
+ RETRY_COUNT:=0
+SH10 SEND_FRMR_RSP(F=X)
+ REPORT_STATUS(FRMR_SENT)
+ START_ACK_TIMER
+ STOP_OTHER_TIMERS
+ RETRY_COUNT:=0
+SH11 STOP_ALL_TIMERS
+ RESET_INDICATION(LOCAL)
+ S_FLAG:=0
+;============================================================================
+NORMAL1 SEND_I_CMD(P=1)
+ START_P_TIMER
+ START_ACK_TIMER_IF_NOT_RUNNING
+; SEND_I_XXX(X=0)
+; START_ACK_TIMER_IF_NOT_RUNNING
+NORMAL2 SEND_I_XXX(X=0)
+ START_ACK_TIMER_IF_NOT_RUNNING
+NORMAL3 SEND_RNR_CMD(F=1)
+ START_P_TIMER
+ DATA_FLAG:=0
+; SEND_RNR_XXX(X=0)
+; DATA_FLAG:=0
+NORMAL4 SEND_RNR_XXX(X=0)
+ DATA_FLAG:=0
+NORMAL5 SEND_REJ_XXX(X=0)
+ UPDATE_N(R)_RECEIVED
+ UPDATE_P_FLAG
+ START_REJ_TIMER
+ IF_F=1_CLEAR_REMOTE_BUSY
+; SEND_REJ_CMD(P=1)
+; UPDATE_N(R)_RECEIVED
+; START_P_TIMER
+; START_REJ_TIMER
+; IF_F=1_CLEAR_REMOTE_BUSY
+NORMAL6 SEND_REJ_XXX(X=0)
+ UPDATE_N(R)_RECEIVED
+ START_REJ_TIMER
+NORMAL7 SEND_REJ_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ START_REJ_TIMER
+;
+; the order of opcodes in NORMAL8 is changed.
+; the transition table will execute NORMAL8A for incoming pdus
+; with p/f 1, pdus with pf 0 are treated in NORMAL8B.
+;
+NORMAL8A V(R):=V(R)+1
+ SEND_ACKNOWLEDGE_CMD(P=1)
+ START_P_TIMER
+ UPDATE_N(R)_RECEIVED
+ IF_F=1_CLEAR_REMOTE_BUSY
+ DATA_INDICATION
+;
+NORMAL8B V(R):=V(R)+1
+ UPDATE_P_FLAG
+ SEND_ACKNOWLEDGE_XXX(X=0)
+ UPDATE_N(R)_RECEIVED
+ IF_F=1_CLEAR_REMOTE_BUSY
+ DATA_INDICATION
+;
+; the order of opcodes in NORMAL9 is changed
+NORMAL9 V(R):=V(R)+1
+ SEND_ACKNOWLEDGE_XXX(X=0)
+ UPDATE_N(R)_RECEIVED
+ DATA_INDICATION
+;
+; the order of opcodes in NORMAL10 is changed
+NORMAL10 V(R):=V(R)+1
+ SEND_ACKNOWLEDGE_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ DATA_INDICATION
+NORMAL11 UPDATE_P_FLAG
+ UPDATE_N(R)_RECEIVED
+ CLEAR_REMOTE_BUSY
+NORMAL12 SEND_ACKNOWLEDGE_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ CLEAR_REMOTE_BUSY
+NORMAL13 UPDATE_P_FLAG
+ UPDATE_N(R)_RECEIVED
+ SET_REMOTE_BUSY
+NORMAL14 SEND_RR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ SET_REMOTE_BUSY
+NORMAL15 V(S):=N(R)
+ UPDATE_N(R)_RECEIVED
+ UPDATE_P_FLAG
+ RE-SEND_I_XXX(X=0)
+ CLEAR_REMOTE_BUSY
+; V(S):=N(R)
+; UPDATE_N(R)_RECEIVED
+; START_P_TIMER
+; RE-SEND_I_CMD(P=1)
+; CLEAR_REMOTE_BUSY
+NORMAL16 V(S):=N(R)
+ UPDATE_N(R)_RECEIVED
+ RE-SEND_I_XXX(X=0)
+ CLEAR_REMOTE_BUSY
+NORMAL17 V(S):=N(R)
+ UPDATE_N(R)_RECEIVED
+ RE-SEND_I_RSP(F=1)
+ CLEAR_REMOTE_BUSY
+NORMAL18 SEND_RR_CMD(P=1)
+ START_P_TIMER
+NORMAL19 P_FLAG:=0
+; SEND_RR_CMD(P=1)
+; START_P_TIMER
+; RETRY_COUNT:=RETRY_COUNT+1
+NORMAL20 SEND_RR_CMD(P=1)
+ START_P_TIMER
+ RETRY_COUNT:=RETRY_COUNT+1
+;============================================================================
+BUSY1 SEND_I_CMD(P=1)
+ START_P_TIMER
+ START_ACK_TIMER_IF_NOT_RUNNING
+; SEND_I_XXX(X=0)
+; START_ACK_TIMER_IF_NOT_RUNNING
+BUSY2 SEND_I_XXX(X=0)
+ START_ACK_TIMER_IF_NOT_RUNNING
+BUSY3 SEND_REJ_CMD(P=1)
+ START_REJ_TIMER
+ START_P_TIMER
+; SEND_REJ_XXX(X=0)
+; START_REJ_TIMER
+BUSY4 SEND_REJ_XXX(X=0)
+ START_REJ_TIMER
+BUSY5 SEND_RR_CMD(P=1)
+ START_P_TIMER
+ SEND_RR_XXX(X=0)
+BUSY6 SEND_RR_XXX(X=0)
+BUSY7 SEND_RR_CMD(P=1)
+ START_P_TIMER
+ SEND_RR_XXX(X=0)
+BUSY8 SEND_RR_XXX(X=0)
+BUSY9 OPTIONAL_SEND_RNR_XXX(X=0)
+ UPDATE_P_FLAG
+ UPDATE_N(R)_RECEIVED
+ IF_DATA_FLAG_=0_THEN_DATA_FLAG:=1
+ IF_F=1_CLEAR_REMOTE_BUSY
+; SEND_RNR_CMD(P=1)
+; START_P_TIMER
+; UPDATE_N(R)_RECEIVED
+; IF_DATA_FLAG_=0_THEN_DATA_FLAG:=1
+; IF_F=1_CLEAR_REMOTE_BUSY
+BUSY10 OPTIONAL_SEND_RNR_XXX(X=0)
+ UPDATE_N(R)_RECEIVED
+ IF_DATA_FLAG_=0_THEN_DATA_FLAG:=1
+BUSY11 SEND_RNR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ IF_DATA_FLAG_=0_THEN_DATA_FLAG:=1
+BUSY12 SEND_RNR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ IF_DATA_FLAG=2_STOP_REJ_TIMER
+ DATA_FLAG:=1
+; V(R):=V(R)+1
+; DATA_INDICATION
+; SEND_RNR_RSP(F=1)
+; UPDATE_N(R)_RECEIVED
+; IF_DATA_FLAG=2_STOP_REJ_TIMER
+; DATA_FLAG:=0
+BUSY13 OPTIONAL_SEND_RNR_XXX(X=0)
+ UPDATE_P_FLAG
+ UPDATE_N(R)_RECEIVED
+ IF_DATA_FLAG=2_STOP_REJ_TIMER
+ DATA_FLAG:=1
+ IF_F=1_CLEAR_REMOTE_BUSY
+; SEND_RNR_CMD(F=1)
+; START_P_TIMER
+; UPDATE_N(R)_RECEIVED
+; IF_DATA_FLAG=2_STOP_REJ_TIMER
+; DATA_FLAG:=1
+; IF_F=1_CLEAR_REMOTE_BUSY
+; V(R):=V(R)+1
+; DATA_INDICATION
+; SEND_RNR_CMD(F=1)
+; START_P_TIMER
+; UPDATE_N(R)_RECEIVED
+; IF_DATA_FLAG=2_STOP_REJ_TIMER
+; DATA_FLAG:=0
+; IF_F=1_CLEAR_REMOTE_BUSY
+; V(R):=V(R)+1
+; DATA_INDICATION
+; UPDATE_P_FLAG
+; OPTIONAL_SEND_RNR_XXX(X=0)
+; UPDATE_N(R)_RECEIVED
+; IF_DATA_FLAG=2_STOP_REJ_TIMER
+; DATA_FLAG:=0
+; IF_F=1_CLEAR_REMOTE_BUSY
+BUSY14 OPTIONAL_SEND_RNR_XXX(X=0)
+ UPDATE_N(R)_RECEIVED
+ IF_DATA_FLAG=2_STOP_REJ_TIMER
+ DATA_FLAG:=1
+; V(R):=V(R)+1
+; DATA_INDICATION
+; OPTIONAL_SEND_RNR_XXX(X=0)
+; UPDATE_N(R)_RECEIVED
+; IF_DATA_FLAG=2_STOP_REJ_TIMER
+; DATA_FLAG:=0
+BUSY15 UPDATE_P_FLAG
+ UPDATE_N(R)_RECEIVED
+ CLEAR_REMOTE_BUSY
+BUSY16 SEND_RNR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ CLEAR_REMOTE_BUSY
+BUSY17 UPDATE_P_FLAG
+ UPDATE_N(R)_RECEIVED
+ SET_REMOTE_BUSY
+BUSY18 SEND_RNR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ SET_REMOTE_BUSY
+BUSY19 V(S):=N(R)
+ UPDATE_N(R)_RECEIVED
+ UPDATE_P_FLAG
+ RE-SEND_I_XXX(X=0)
+ CLEAR_REMOTE_BUSY
+; V(S):=N(R)
+; UPDATE_N(R)_RECEIVED
+; RE-SEND_I_CMD(P=1)
+; CLEAR_REMOTE_BUSY
+BUSY20 V(S):=N(R)
+ UPDATE_N(R)_RECEIVED
+ RE-SEND_I_XXX(X=0)
+ CLEAR_REMOTE_BUSY
+BUSY21 V(S):=N(R)
+ UPDATE_N(R)_RECEIVED
+ SEND_RNR_RSP(F=1)
+ RE-SEND_I_XXX(X=0)
+ CLEAR_REMOTE_BUSY
+BUSY22 SEND_RNR_CMD(F=1)
+ START_P_TIMER
+BUSY23 P_FLAG:=0
+; SEND_RNR_CMD(F=1)
+; START_P_TIMER
+; RETRY_COUNT:=RETRY_COUNT+1
+BUSY24 SEND_RNR_CMD(F=1)
+ START_P_TIMER
+ RETRY_COUNT:=RETRY_COUNT+1
+BUSY25 DATA_FLAG:=1
+; SEND_RNR_CMD(F=1)
+; START_P_TIMER
+; RETRY_COUNT:=RETRY_COUNT+1
+; DATA_FLAG:=1
+BUSY26 DATA_FLAG:=1
+;============================================================================
+REJECT1 SEND_I_CMD(P=1)
+ START_P_TIMER
+ START_ACK_TIMER_IF_NOT_RUNNING
+; SEND_I_XXX(X=0)
+; START_ACK_TIMER_IF_NOT_RUNNING
+REJECT2 SEND_I_XXX(X=0)
+ START_ACK_TIMER_IF_NOT_RUNNING
+REJECT3 SEND_RNR_CMD(F=1)
+ START_P_TIMER
+ DATA_FLAG:=2
+; SEND_RNR_XXX(X=0)
+; DATA_FLAG:=2
+REJECT4 SEND_RNR_XXX(X=0)
+ DATA_FLAG:=2
+REJECT5 UPDATE_N(R)_RECEIVED
+ UPDATE_P_FLAG
+ IF_F=1_CLEAR_REMOTE_BUSY
+REJECT6 SEND_RR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+;
+; order of opcodes in REJECT7 is changed
+REJECT7 V(R):=V(R)+1
+ SEND_ACKNOWLEDGE_CMD(P=1)
+ START_P_TIMER
+ UPDATE_N(R)_RECEIVED
+ IF_F=1_CLEAR_REMOTE_BUSY
+ STOP_REJ_TIMER
+ DATA_INDICATION
+; V(R):=V(R)+1
+; DATA_INDICATION
+; UPDATE_P_FLAG
+; SEND_ACKNOWLEDGE_XXX(X=0)
+; UPDATE_N(R)_RECEIVED
+; IF_F=1_CLEAR_REMOTE_BUSY
+; STOP_REJ_TIMER
+;
+; order of opcodes in REJECT8 is changed
+REJECT8 V(R):=V(R)+1
+ SEND_ACKNOWLEDGE_XXX(X=0)
+ UPDATE_N(R)_RECEIVED
+ STOP_REJ_TIMER
+ DATA_INDICATION
+;
+; order of opcodes in REJECT9 is changed
+REJECT9 V(R):=V(R)+1
+ SEND_ACKNOWLEDGE_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ STOP_REJ_TIMER
+ DATA_INDICATION
+REJECT10 UPDATE_P_FLAG
+ UPDATE_N(R)_RECEIVED
+ CLEAR_REMOTE_BUSY
+REJECT11 SEND_ACKNOWLEDGE_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ CLEAR_REMOTE_BUSY
+REJECT12 UPDATE_P_FLAG
+ UPDATE_N(R)_RECEIVED
+ SET_REMOTE_BUSY
+REJECT13 SEND_RR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ SET_REMOTE_BUSY
+REJECT14 V(S):=N(R)
+ UPDATE_N(R)_RECEIVED
+ UPDATE_P_FLAG
+ RE-SEND_I_XXX(X=0)
+ CLEAR_REMOTE_BUSY
+; V(S):=N(R)
+; UPDATE_N(R)_RECEIVED
+; RE-SEND_I_CMD(P=1)
+; START_P_TIMER
+; CLEAR_REMOTE_BUSY
+REJECT15 V(S):=N(R)
+ UPDATE_N(R)_RECEIVED
+ RE-SEND_I_XXX(X=0)
+ CLEAR_REMOTE_BUSY
+REJECT16 V(S):=N(R)
+ UPDATE_N(R)_RECEIVED
+ RE-SEND_I_RSP(F=1)
+ CLEAR_REMOTE_BUSY
+REJECT17 SEND_RR_CMD(P=1)
+ START_P_TIMER
+REJECT18 SEND_REJ_CMD(P=1)
+ START_P_TIMER
+ START_REJ_TIMER
+ RETRY_COUNT:=RETRY_COUNT+1
+REJECT19 P_FLAG:=0
+; SEND_RR_CMD(P=1)
+; START_P_TIMER
+; START_REJ_TIMER
+; RETRY_COUNT:=RETRY_COUNT+1
+REJECT20 SEND_RR_CMD(P=1)
+ START_P_TIMER
+ START_REJ_TIMER
+ RETRY_COUNT:=RETRY_COUNT+1
+;============================================================================
+AWAIT1 SEND_RNR_XXX(X=0)
+ DATA_FLAG:=0
+AWAIT2 SEND_REJ_XXX(X=0)
+ UPDATE_N(R)_RECEIVED
+ V(S):=N(R)
+ STOP_P_TIMER
+ RE-SEND_I_XXX(X=0)
+ START_REJ_TIMER
+ CLEAR_REMOTE_BUSY
+; SEND_REJ_CMD(P=1)
+; UPDATE_N(R)_RECEIVED
+; V(S):=N(R)
+; RE-SEND_I_XXX(X=0)
+; START_P_TIMER
+; START_REJ_TIMER
+; CLEAR_REMOTE_BUSY
+AWAIT3 SEND_REJ_XXX(X=0)
+ UPDATE_N(R)_RECEIVED
+ START_REJ_TIMER
+AWAIT4 SEND_REJ_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ START_REJ_TIMER
+;
+; order of opcode in AWAIT5 changed
+AWAIT5 V(R):=V(R)+1
+ UPDATE_N(R)_RECEIVED
+ V(S):=N(R)
+ RE-SEND_I_CMD(P=1)_OR_SEND_RR
+ START_P_TIMER
+ CLEAR_REMOTE_BUSY
+ DATA_INDICATION
+; V(R):=V(R)+1
+; DATA_INDICATION
+; STOP_P_TIMER
+; UPDATE_N(R)_RECEIVED
+; V(S):=N(R)
+; RE-SEND_I_XXX(X=0)_OR_SEND_RR
+; CLEAR_REMOTE_BUSY
+;
+; order of opcode in AWAIT6 changed
+AWAIT6 V(R):=V(R)+1
+ SEND_RR_XXX(X=0)
+ UPDATE_N(R)_RECEIVED
+ DATA_INDICATION
+;
+; order of opcode in AWAIT7 changed
+AWAIT7 V(R):=V(R)+1
+ SEND_RR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ DATA_INDICATION
+AWAIT8 UPDATE_N(R)_RECEIVED
+ V(S):=N(R)
+ STOP_P_TIMER
+ RE-SEND_I_XXX(X=0)
+ CLEAR_REMOTE_BUSY
+; UPDATE_N(R)_RECEIVED
+; V(S):=N(R)
+; RE-SEND_I_CMD(P=1)
+; START_P_TIMER
+; CLEAR_REMOTE_BUSY
+AWAIT9 UPDATE_N(R)_RECEIVED
+ CLEAR_REMOTE_BUSY
+AWAIT10 SEND_RR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ CLEAR_REMOTE_BUSY
+AWAIT11 UPDATE_N(R)_RECEIVED
+ V(S):=N(R)
+ STOP_P_TIMER
+ SET_REMOTE_BUSY
+AWAIT12 UPDATE_N(R)_RECEIVED
+ SET_REMOTE_BUSY
+AWAIT13 SEND_RR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ SET_REMOTE_BUSY
+AWAIT14 SEND_RR_CMD(P=1)
+ START_P_TIMER
+ RETRY_COUNT:=RETRY_COUNT+1
+;============================================================================
+AWAIT_BUSY1 SEND_REJ_XXX(X=0)
+ START_REJ_TIMER
+AWAIT_BUSY2 SEND_RR_XXX(X=0)
+AWAIT_BUSY3 SEND_RR_XXX(X=0)
+AWAIT_BUSY4 OPTIONAL_SEND_RNR_XXX(X=0)
+ UPDATE_N(R)_RECEIVED
+ V(S):=N(R)
+ STOP_P_TIMER
+ DATA_FLAG:=1
+ CLEAR_REMOTE_BUSY
+ RE-SEND_I_XXX(X=0)
+; SEND_RNR_CMD(F=1)
+; UPDATE_N(R)_RECEIVED
+; V(S):=N(R)
+; START_P_TIMER
+; DATA_FLAG:=1
+; CLEAR_REMOTE_BUSY
+; RE-SEND_I_XXX(X=0)
+AWAIT_BUSY5 OPTIONAL_SEND_RNR_XXX(X=0)
+ UPDATE_N(R)_RECEIVED
+ DATA_FLAG:=1
+AWAIT_BUSY6 SEND_RNR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ DATA_FLAG:=1
+AWAIT_BUSY7 OPTIONAL_SEND_RNR_XXX(X=0)
+ UPDATE_N(R)_RECEIVED
+ V(S):=N(R)
+ DATA_FLAG:=1
+ STOP_P_TIMER
+ CLEAR_REMOTE_BUSY
+ RE-SEND_I_XXX(X=0)
+; SEND_RNR_CMD(F=1)
+; V(R):=V(R)+1
+; DATA_INDICATION
+; START_P_TIMER
+; UPDATE_N(R)_RECEIVED
+; V(S):=N(R)
+; DATA_FLAG:=0
+; CLEAR_REMOTE_BUSY
+; RE-SEND_I_XXX(X=0)
+; OPTIONAL_SEND_RNR_XXX(X=0)
+; V(R):=V(R)+1
+; DATA_INDICATION
+; STOP_P_TIMER
+; UPDATE_N(R)_RECEIVED
+; V(S):=N(R)
+; DATA_FLAG:=0
+; CLEAR_REMOTE_BUSY
+; RE-SEND_I_XXX(X=0)
+AWAIT_BUSY8 OPTIONAL_SEND_RNR_XXX(X=0)
+ UPDATE_N(R)_RECEIVED
+ DATA_FLAG:=1
+; OPTIONAL_SEND_RNR_XXX(X=0)
+; V(R):=V(R)+1
+; DATA_INDICATION
+; UPDATE_N(R)_RECEIVED
+; DATA_FLAG:=0
+AWAIT_BUSY9 SEND_RNR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ DATA_FLAG:=1
+; SEND_RNR_RSP(F=1)
+; V(R):=V(R)+1
+; DATA_INDICATION
+; UPDATE_N(R)_RECEIVED
+; DATA_FLAG:=0
+AWAIT_BUSY10 UPDATE_N(R)_RECEIVED
+ V(S):=N(R)
+ STOP_P_TIMER
+ RE-SEND_I_XXX(X=0)
+ CLEAR_REMOTE_BUSY
+; UPDATE_N(R)_RECEIVED
+; V(S):=N(R)
+; RE-SEND_I_CMD(P=1)
+; START_P_TIMER
+; CLEAR_REMOTE_BUSY
+AWAIT_BUSY11 UPDATE_N(R)_RECEIVED
+ CLEAR_REMOTE_BUSY
+AWAIT_BUSY12 SEND_RNR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ CLEAR_REMOTE_BUSY
+AWAIT_BUSY13 UPDATE_N(R)_RECEIVED
+ V(S):=N(R)
+ STOP_P_TIMER
+ SET_REMOTE_BUSY
+AWAIT_BUSY14 UPDATE_N(R)_RECEIVED
+ SET_REMOTE_BUSY
+AWAIT_BUSY15 SEND_RNR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ SET_REMOTE_BUSY
+AWAIT_BUSY16 SEND_RNR_CMD(F=1)
+ START_P_TIMER
+ RETRY_COUNT:=RETRY_COUNT+1
+;============================================================================
+AWAIT_REJECT1 SEND_RNR_XXX(X=0)
+ DATA_FLAG:=2
+AWAIT_REJECT2 UPDATE_N(R)_RECEIVED
+AWAIT_REJECT3 SEND_RR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+;
+; order of opcodes in AWAIT_REJECT4 changed
+AWAIT_REJECT4 V(R):=V(R)+1
+ UPDATE_N(R)_RECEIVED
+ V(S):=N(R)
+ RE-SEND_I_CMD(P=1)_OR_SEND_RR
+ START_P_TIMER
+ STOP_REJ_TIMER
+ CLEAR_REMOTE_BUSY
+ DATA_INDICATION
+; V(R):=V(R)+1
+; DATA_INDICATION
+; STOP_P_TIMER
+; STOP_REJ_TIMER
+; UPDATE_N(R)_RECEIVED
+; V(S):=N(R)
+; RE-SEND_I_CMD(P=1)_OR_SEND_RR
+; CLEAR_REMOTE_BUSY
+;
+; order of opcodes in AWAIT_REJECT5 changed
+AWAIT_REJECT5 V(R):=V(R)+1
+ SEND_RR_XXX(X=0)
+ STOP_REJ_TIMER
+ UPDATE_N(R)_RECEIVED
+ DATA_INDICATION
+;
+; order of opcodes in AWAIT_REJECT6 changed
+AWAIT_REJECT6 V(R):=V(R)+1
+ SEND_RR_RSP(F=1)
+ STOP_REJ_TIMER
+ UPDATE_N(R)_RECEIVED
+ DATA_INDICATION
+AWAIT_REJECT7 UPDATE_N(R)_RECEIVED
+ V(S):=N(R)
+ STOP_P_TIMER
+ RE-SEND_I_XXX(X=0)
+ CLEAR_REMOTE_BUSY
+; UPDATE_N(R)_RECEIVED
+; V(S):=N(R)
+; RE-SEND_I_CMD(P=1)
+; START_P_TIMER
+; CLEAR_REMOTE_BUSY
+AWAIT_REJECT8 UPDATE_N(R)_RECEIVED
+ CLEAR_REMOTE_BUSY
+AWAIT_REJECT9 SEND_RR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ CLEAR_REMOTE_BUSY
+AWAIT_REJECT10 UPDATE_N(R)_RECEIVED
+ V(S):=N(R)
+ STOP_P_TIMER
+ SET_REMOTE_BUSY
+AWAIT_REJECT11 UPDATE_N(R)_RECEIVED
+ SET_REMOTE_BUSY
+AWAIT_REJECT12 SEND_RR_RSP(F=1)
+ UPDATE_N(R)_RECEIVED
+ SET_REMOTE_BUSY
+AWAIT_REJECT13 SEND_REJ_CMD(P=1)
+ START_P_TIMER
+ RETRY_COUNT:=RETRY_COUNT+1
+;============================================================================
+
diff --git a/uClinux-2.4.31-uc0/net/802/pseudo/pseudocode.h b/uClinux-2.4.31-uc0/net/802/pseudo/pseudocode.h
new file mode 100644
index 0000000..f32bfeb
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/pseudo/pseudocode.h
@@ -0,0 +1,287 @@
+
+/* this file generated on Thu Oct 24 11:42:35 GMT 1996 */
+
+static char pseudo_code [ ] = {
+ 0, 37, 57, 42, 59, 39, 0, 65, 38,
+ 63, 61, 59, 56, 58, 2, 0, 12, 0,
+ 12, 0, 0, 0, 12, 0, 65, 0, 5,
+ 0, 0, 0, 37, 57, 42, 59, 0, 38,
+ 63, 61, 59, 56, 58, 7, 0, 11, 57,
+ 42, 59, 0, 12, 0, 5, 0, 40, 65,
+ 0, 12, 5, 0, 0, 0, 38, 63, 61,
+ 59, 56, 58, 0, 12, 0, 5, 0, 65,
+ 0, 12, 5, 0, 0, 0, 38, 63, 61,
+ 59, 40, 0, 45, 63, 61, 59, 51, 3,
+ 58, 0, 56, 3, 58, 0, 12, 5, 45,
+ 0, 5, 45, 0, 0, 0, 37, 57, 42,
+ 60, 0, 5, 0, 38, 63, 61, 59, 40,
+ 0, 45, 63, 61, 59, 51, 7, 58, 0,
+ 56, 7, 58, 0, 12, 5, 45, 0, 5,
+ 45, 0, 0, 0, 37, 57, 42, 60, 0,
+ 5, 0, 12, 45, 0, 45, 0, 38, 0,
+ 45, 0, 0, 0, 11, 57, 42, 60, 0,
+ 0, 0, 71, 45, 65, 0, 38, 5, 45,
+ 0, 5, 45, 0, 70, 45, 66, 39, 0,
+ 15, 42, 0, 0, 0, 14, 42, 60, 0,
+ 39, 70, 0, 11, 57, 42, 49, 59, 0,
+ 37, 57, 42, 49, 59, 39, 0, 71, 65,
+ 48, 0, 38, 5, 48, 0, 48, 70, 66,
+ 39, 0, 5, 48, 0, 13, 67, 42, 49,
+ 59, 0, 0, 67, 42, 49, 59, 0, 0,
+ 67, 42, 49, 59, 0, 13, 67, 42, 49,
+ 59, 0, 48, 70, 39, 0, 16, 41, 44,
+ 0, 19, 44, 0, 26, 41, 53, 0, 28,
+ 53, 0, 25, 50, 51, 43, 9, 0, 25,
+ 50, 43, 0, 24, 50, 43, 0, 62, 32,
+ 41, 50, 9, 4, 0, 62, 51, 36, 50,
+ 9, 4, 0, 62, 36, 50, 4, 0, 62,
+ 34, 50, 4, 0, 51, 50, 1, 0, 34,
+ 50, 1, 0, 51, 50, 29, 0, 33, 50,
+ 29, 0, 64, 50, 51, 20, 1, 0, 64,
+ 50, 20, 1, 0, 64, 50, 22, 1, 0,
+ 31, 41, 0, 56, 0, 31, 41, 60, 0,
+ 16, 41, 44, 0, 19, 44, 0, 23, 43,
+ 41, 0, 25, 43, 0, 31, 41, 35, 0,
+ 35, 0, 31, 41, 35, 0, 35, 0, 30,
+ 51, 50, 55, 9, 0, 30, 50, 55, 0,
+ 27, 50, 55, 0, 27, 50, 10, 54, 0,
+ 30, 51, 50, 10, 54, 9, 0, 30, 50,
+ 10, 54, 0, 51, 50, 1, 0, 27, 50,
+ 1, 0, 51, 50, 29, 0, 27, 50, 29,
+ 0, 64, 50, 51, 20, 1, 0, 64, 50,
+ 20, 1, 0, 64, 50, 27, 20, 1, 0,
+ 26, 41, 0, 56, 0, 26, 41, 60, 0,
+ 54, 0, 54, 0, 16, 41, 44, 0, 19,
+ 44, 0, 26, 41, 52, 0, 28, 52, 0,
+ 50, 51, 9, 0, 33, 50, 0, 62, 32,
+ 41, 50, 9, 47, 4, 0, 62, 36, 50,
+ 47, 4, 0, 62, 34, 50, 47, 4, 0,
+ 51, 50, 1, 0, 34, 50, 1, 0, 51,
+ 50, 29, 0, 33, 50, 29, 0, 64, 50,
+ 51, 20, 1, 0, 64, 50, 20, 1, 0,
+ 64, 50, 22, 1, 0, 31, 41, 0, 23,
+ 41, 43, 60, 0, 56, 0, 31, 41, 43,
+ 60, 0, 28, 53, 0, 25, 50, 64, 46,
+ 20, 43, 1, 0, 25, 50, 43, 0, 24,
+ 50, 43, 0, 62, 50, 64, 18, 41, 1,
+ 4, 0, 62, 35, 50, 4, 0, 62, 33,
+ 50, 4, 0, 50, 64, 46, 20, 1, 0,
+ 50, 1, 0, 33, 50, 1, 0, 50, 64,
+ 46, 29, 0, 50, 29, 0, 33, 50, 29,
+ 0, 31, 41, 60, 0, 25, 43, 0, 35,
+ 0, 35, 0, 30, 50, 64, 46, 54, 1,
+ 20, 0, 30, 50, 54, 0, 27, 50, 54,
+ 0, 30, 50, 64, 54, 46, 1, 20, 0,
+ 30, 50, 54, 0, 27, 50, 54, 0, 50,
+ 64, 46, 20, 1, 0, 50, 1, 0, 27,
+ 50, 1, 0, 50, 64, 46, 29, 0, 50,
+ 29, 0, 27, 50, 29, 0, 26, 41, 60,
+ 0, 28, 52, 0, 50, 0, 33, 50, 0,
+ 62, 50, 64, 18, 41, 47, 1, 4, 0,
+ 62, 35, 47, 50, 4, 0, 62, 33, 47,
+ 50, 4, 0, 50, 64, 46, 20, 1, 0,
+ 50, 1, 0, 33, 50, 1, 0, 50, 64,
+ 46, 29, 0, 50, 29, 0, 33, 50, 29,
+ 0, 23, 41, 60, 0
+};
+
+static short int pseudo_code_idx [ ] ={
+ 0, 1, 7, 16, 18, 20, 22, 24, 26,
+ 28, 30, 35, 43, 48, 50, 52, 55, 58,
+ 60, 67, 69, 71, 73, 76, 78, 84, 92,
+ 96, 100, 103, 105, 110, 112, 118, 126, 130,
+ 134, 137, 139, 144, 146, 149, 151, 153, 155,
+ 157, 162, 164, 168, 172, 175, 180, 183, 185,
+ 189, 192, 198, 205, 209, 213, 218, 221, 227,
+ 233, 239, 245, 249, 253, 256, 260, 263, 269,
+ 273, 277, 284, 291, 296, 301, 305, 309, 313,
+ 317, 323, 328, 333, 336, 338, 342, 346, 349,
+ 353, 356, 360, 362, 366, 368, 374, 378, 382,
+ 387, 394, 399, 403, 407, 411, 415, 421, 426,
+ 432, 435, 437, 441, 443, 445, 449, 452, 456,
+ 459, 463, 466, 474, 480, 486, 490, 494, 498,
+ 502, 508, 513, 518, 521, 526, 528, 533, 536,
+ 544, 548, 552, 560, 565, 570, 576, 579, 583,
+ 588, 591, 595, 599, 602, 604, 606, 614, 618,
+ 622, 630, 634, 638, 644, 647, 651, 656, 659,
+ 663, 667, 670, 672, 675, 684, 690, 696, 702,
+ 705, 709, 714, 717, 721, 0
+};
+
+#define NOP 0
+#define ADM1 1
+#define ADM2 2
+#define ADM3 3
+#define ADM4 4
+#define ADM5 5
+#define CONN2 6
+#define CONN3 7
+#define CONN4 8
+#define CONN5 9
+#define RESWAIT1 10
+#define RESWAIT2 11
+#define RESWAIT3 12
+#define RESWAIT4 13
+#define RESWAIT5 14
+#define RESWAIT6 15
+#define RESWAIT7 16
+#define RESWAIT8 17
+#define RESCHK1 18
+#define RESCHK2 19
+#define RESCHK3 20
+#define RESCHK4 21
+#define RESCHK5 22
+#define RESCHK6 23
+#define SETUP1 24
+#define SETUP2 25
+#define SETUP3 26
+#define SETUP4 27
+#define SETUP5 28
+#define SETUP6 29
+#define SETUP7 30
+#define SETUP8 31
+#define RESET1 32
+#define RESET2 33
+#define RESET3 34
+#define RESET4 35
+#define RESET5 36
+#define RESET6 37
+#define RESET7 38
+#define RESET8 39
+#define D_CONN1 40
+#define D_CONN2 41
+#define D_CONN3 42
+#define D_CONN4 43
+#define D_CONN5 44
+#define D_CONN6 45
+#define D_CONN7 46
+#define ERR1 47
+#define ERR2 48
+#define ERR3 49
+#define ERR4 50
+#define ERR5 51
+#define ERR6 52
+#define ERR7 53
+#define ERR8 54
+#define SH1 55
+#define SH2 56
+#define SH3 57
+#define SH4 58
+#define SH5 59
+#define SH6 60
+#define SH7 61
+#define SH8 62
+#define SH9 63
+#define SH10 64
+#define SH11 65
+#define NORMAL1 66
+#define NORMAL2 67
+#define NORMAL3 68
+#define NORMAL4 69
+#define NORMAL5 70
+#define NORMAL6 71
+#define NORMAL7 72
+#define NORMAL8A 73
+#define NORMAL8B 74
+#define NORMAL9 75
+#define NORMAL10 76
+#define NORMAL11 77
+#define NORMAL12 78
+#define NORMAL13 79
+#define NORMAL14 80
+#define NORMAL15 81
+#define NORMAL16 82
+#define NORMAL17 83
+#define NORMAL18 84
+#define NORMAL19 85
+#define NORMAL20 86
+#define BUSY1 87
+#define BUSY2 88
+#define BUSY3 89
+#define BUSY4 90
+#define BUSY5 91
+#define BUSY6 92
+#define BUSY7 93
+#define BUSY8 94
+#define BUSY9 95
+#define BUSY10 96
+#define BUSY11 97
+#define BUSY12 98
+#define BUSY13 99
+#define BUSY14 100
+#define BUSY15 101
+#define BUSY16 102
+#define BUSY17 103
+#define BUSY18 104
+#define BUSY19 105
+#define BUSY20 106
+#define BUSY21 107
+#define BUSY22 108
+#define BUSY23 109
+#define BUSY24 110
+#define BUSY25 111
+#define BUSY26 112
+#define REJECT1 113
+#define REJECT2 114
+#define REJECT3 115
+#define REJECT4 116
+#define REJECT5 117
+#define REJECT6 118
+#define REJECT7 119
+#define REJECT8 120
+#define REJECT9 121
+#define REJECT10 122
+#define REJECT11 123
+#define REJECT12 124
+#define REJECT13 125
+#define REJECT14 126
+#define REJECT15 127
+#define REJECT16 128
+#define REJECT17 129
+#define REJECT18 130
+#define REJECT19 131
+#define REJECT20 132
+#define AWAIT1 133
+#define AWAIT2 134
+#define AWAIT3 135
+#define AWAIT4 136
+#define AWAIT5 137
+#define AWAIT6 138
+#define AWAIT7 139
+#define AWAIT8 140
+#define AWAIT9 141
+#define AWAIT10 142
+#define AWAIT11 143
+#define AWAIT12 144
+#define AWAIT13 145
+#define AWAIT14 146
+#define AWAIT_BUSY1 147
+#define AWAIT_BUSY2 148
+#define AWAIT_BUSY3 149
+#define AWAIT_BUSY4 150
+#define AWAIT_BUSY5 151
+#define AWAIT_BUSY6 152
+#define AWAIT_BUSY7 153
+#define AWAIT_BUSY8 154
+#define AWAIT_BUSY9 155
+#define AWAIT_BUSY10 156
+#define AWAIT_BUSY11 157
+#define AWAIT_BUSY12 158
+#define AWAIT_BUSY13 159
+#define AWAIT_BUSY14 160
+#define AWAIT_BUSY15 161
+#define AWAIT_BUSY16 162
+#define AWAIT_REJECT1 163
+#define AWAIT_REJECT2 164
+#define AWAIT_REJECT3 165
+#define AWAIT_REJECT4 166
+#define AWAIT_REJECT5 167
+#define AWAIT_REJECT6 168
+#define AWAIT_REJECT7 169
+#define AWAIT_REJECT8 170
+#define AWAIT_REJECT9 171
+#define AWAIT_REJECT10 172
+#define AWAIT_REJECT11 173
+#define AWAIT_REJECT12 174
+#define AWAIT_REJECT13 175
+
diff --git a/uClinux-2.4.31-uc0/net/802/psnap.c b/uClinux-2.4.31-uc0/net/802/psnap.c
new file mode 100644
index 0000000..c40036e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/psnap.c
@@ -0,0 +1,164 @@
+/*
+ * SNAP data link layer. Derived from 802.2
+ *
+ * Alan Cox <Alan.Cox@linux.org>, from the 802.2 layer by Greg Page.
+ * Merged in additions from Greg Page's psnap.c.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/datalink.h>
+#include <net/p8022.h>
+#include <net/psnap.h>
+#include <linux/mm.h>
+#include <linux/in.h>
+#include <linux/init.h>
+
+static struct datalink_proto *snap_list = NULL;
+static struct datalink_proto *snap_dl = NULL; /* 802.2 DL for SNAP */
+
+/*
+ * Find a snap client by matching the 5 bytes.
+ */
+
+static struct datalink_proto *find_snap_client(unsigned char *desc)
+{
+ struct datalink_proto *proto;
+
+ for (proto = snap_list; proto != NULL && memcmp(proto->type, desc, 5) ; proto = proto->next);
+ return proto;
+}
+
+/*
+ * A SNAP packet has arrived
+ */
+
+int snap_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
+{
+ static struct packet_type psnap_packet_type =
+ {
+ 0,
+ NULL, /* All Devices */
+ snap_rcv,
+ NULL,
+ NULL,
+ };
+
+ struct datalink_proto *proto;
+
+ proto = find_snap_client(skb->h.raw);
+ if (proto != NULL)
+ {
+ /*
+ * Pass the frame on.
+ */
+
+ skb->h.raw += 5;
+ skb->nh.raw += 5;
+ skb_pull(skb,5);
+ if (psnap_packet_type.type == 0)
+ psnap_packet_type.type=htons(ETH_P_SNAP);
+
+ return proto->rcvfunc(skb, dev, &psnap_packet_type);
+ }
+ skb->sk = NULL;
+ kfree_skb(skb);
+ return 0;
+}
+
+/*
+ * Put a SNAP header on a frame and pass to 802.2
+ */
+
+static void snap_datalink_header(struct datalink_proto *dl, struct sk_buff *skb, unsigned char *dest_node)
+{
+ memcpy(skb_push(skb,5),dl->type,5);
+ snap_dl->datalink_header(snap_dl, skb, dest_node);
+}
+
+/*
+ * Set up the SNAP layer
+ */
+
+EXPORT_SYMBOL(register_snap_client);
+EXPORT_SYMBOL(unregister_snap_client);
+
+static int __init snap_init(void)
+{
+ snap_dl=register_8022_client(0xAA, snap_rcv);
+ if(snap_dl==NULL)
+ printk("SNAP - unable to register with 802.2\n");
+ return 0;
+}
+
+static void __exit snap_exit(void)
+{
+ unregister_8022_client(0xAA);
+}
+
+module_init(snap_init);
+module_exit(snap_exit);
+
+
+/*
+ * Register SNAP clients. We don't yet use this for IP.
+ */
+
+struct datalink_proto *register_snap_client(unsigned char *desc, int (*rcvfunc)(struct sk_buff *, struct net_device *, struct packet_type *))
+{
+ struct datalink_proto *proto;
+
+ if (find_snap_client(desc) != NULL)
+ return NULL;
+
+ proto = (struct datalink_proto *) kmalloc(sizeof(*proto), GFP_ATOMIC);
+ if (proto != NULL)
+ {
+ memcpy(proto->type, desc,5);
+ proto->type_len = 5;
+ proto->rcvfunc = rcvfunc;
+ proto->header_length = 5+snap_dl->header_length;
+ proto->datalink_header = snap_datalink_header;
+ proto->string_name = "SNAP";
+ proto->next = snap_list;
+ snap_list = proto;
+ }
+
+ return proto;
+}
+
+/*
+ * Unregister SNAP clients. Protocols no longer want to play with us ...
+ */
+
+void unregister_snap_client(unsigned char *desc)
+{
+ struct datalink_proto **clients = &snap_list;
+ struct datalink_proto *tmp;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ while ((tmp = *clients) != NULL)
+ {
+ if (memcmp(tmp->type,desc,5) == 0)
+ {
+ *clients = tmp->next;
+ kfree(tmp);
+ break;
+ }
+ else
+ clients = &tmp->next;
+ }
+
+ restore_flags(flags);
+}
+
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/802/sysctl_net_802.c b/uClinux-2.4.31-uc0/net/802/sysctl_net_802.c
new file mode 100644
index 0000000..19cd47a
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/sysctl_net_802.c
@@ -0,0 +1,28 @@
+/* -*- linux-c -*-
+ * sysctl_net_802.c: sysctl interface to net 802 subsystem.
+ *
+ * Begun April 1, 1996, Mike Shaver.
+ * Added /proc/sys/net/802 directory entry (empty =) ). [MS]
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+#include <linux/config.h>
+
+ctl_table e802_table[] = {
+ {0}
+};
+
+#ifdef CONFIG_TR
+extern int sysctl_tr_rif_timeout;
+ctl_table tr_table[] = {
+ {NET_TR_RIF_TIMEOUT, "rif_timeout", &sysctl_tr_rif_timeout, sizeof(int),
+ 0644, NULL, &proc_dointvec},
+ {0}
+};
+#endif
diff --git a/uClinux-2.4.31-uc0/net/802/tr.c b/uClinux-2.4.31-uc0/net/802/tr.c
new file mode 100644
index 0000000..88fbab8
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/tr.c
@@ -0,0 +1,558 @@
+/*
+ * NET3: Token ring device handling subroutines
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Fixes: 3 Feb 97 Paul Norton <pnorton@cts.com> Minor routing fixes.
+ * Added rif table to /proc/net/tr_rif and rif timeout to
+ * /proc/sys/net/token-ring/rif_timeout.
+ * 22 Jun 98 Paul Norton <p.norton@computer.org> Rearranged
+ * tr_header and tr_type_trans to handle passing IPX SNAP and
+ * 802.2 through the correct layers. Eliminated tr_reformat.
+ *
+ */
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/trdevice.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/timer.h>
+#include <linux/net.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <net/arp.h>
+
+void tr_source_route(struct sk_buff *skb, struct trh_hdr *trh,
+ struct net_device *dev);
+static void tr_add_rif_info(struct trh_hdr *trh, struct net_device *dev);
+static void rif_check_expire(unsigned long dummy);
+
+#define TR_SR_DEBUG 0
+
+typedef struct rif_cache_s *rif_cache;
+
+/*
+ * Each RIF entry we learn is kept this way
+ */
+
+struct rif_cache_s {
+ unsigned char addr[TR_ALEN];
+ int iface;
+ __u16 rcf;
+ __u16 rseg[8];
+ rif_cache next;
+ unsigned long last_used;
+ unsigned char local_ring;
+};
+
+#define RIF_TABLE_SIZE 32
+
+/*
+ * We hash the RIF cache 32 ways. We do after all have to look it
+ * up a lot.
+ */
+
+static rif_cache rif_table[RIF_TABLE_SIZE];
+static spinlock_t rif_lock = SPIN_LOCK_UNLOCKED;
+
+#define RIF_TIMEOUT 60*10*HZ
+#define RIF_CHECK_INTERVAL 60*HZ
+
+/*
+ * Garbage disposal timer.
+ */
+
+static struct timer_list rif_timer;
+
+int sysctl_tr_rif_timeout = RIF_TIMEOUT;
+
+/*
+ * Put the headers on a token ring packet. Token ring source routing
+ * makes this a little more exciting than on ethernet.
+ */
+
+int tr_header(struct sk_buff *skb, struct net_device *dev, unsigned short type,
+ void *daddr, void *saddr, unsigned len)
+{
+ struct trh_hdr *trh;
+ int hdr_len;
+
+ /*
+ * Add the 802.2 SNAP header if IP as the IPv4/IPv6 code calls
+ * dev->hard_header directly.
+ */
+ if (type == ETH_P_IP || type == ETH_P_IPV6 || type == ETH_P_ARP)
+ {
+ struct trllc *trllc=(struct trllc *)(trh+1);
+
+ hdr_len = sizeof(struct trh_hdr) + sizeof(struct trllc);
+ trh = (struct trh_hdr *)skb_push(skb, hdr_len);
+ trllc = (struct trllc *)(trh+1);
+ trllc->dsap = trllc->ssap = EXTENDED_SAP;
+ trllc->llc = UI_CMD;
+ trllc->protid[0] = trllc->protid[1] = trllc->protid[2] = 0x00;
+ trllc->ethertype = htons(type);
+ }
+ else
+ {
+ hdr_len = sizeof(struct trh_hdr);
+ trh = (struct trh_hdr *)skb_push(skb, hdr_len);
+ }
+
+ trh->ac=AC;
+ trh->fc=LLC_FRAME;
+
+ if(saddr)
+ memcpy(trh->saddr,saddr,dev->addr_len);
+ else
+ memcpy(trh->saddr,dev->dev_addr,dev->addr_len);
+
+ /*
+ * Build the destination and then source route the frame
+ */
+
+ if(daddr)
+ {
+ memcpy(trh->daddr,daddr,dev->addr_len);
+ tr_source_route(skb,trh,dev);
+ return(hdr_len);
+ }
+
+ return -hdr_len;
+}
+
+/*
+ * A neighbour discovery of some species (eg arp) has completed. We
+ * can now send the packet.
+ */
+
+int tr_rebuild_header(struct sk_buff *skb)
+{
+ struct trh_hdr *trh=(struct trh_hdr *)skb->data;
+ struct trllc *trllc=(struct trllc *)(skb->data+sizeof(struct trh_hdr));
+ struct net_device *dev = skb->dev;
+
+ /*
+ * FIXME: We don't yet support IPv6 over token rings
+ */
+
+ if(trllc->ethertype != htons(ETH_P_IP)) {
+ printk("tr_rebuild_header: Don't know how to resolve type %04X addresses ?\n",(unsigned int)htons(trllc->ethertype));
+ return 0;
+ }
+
+#ifdef CONFIG_INET
+ if(arp_find(trh->daddr, skb)) {
+ return 1;
+ }
+ else
+#endif
+ {
+ tr_source_route(skb,trh,dev);
+ return 0;
+ }
+}
+
+/*
+ * Some of this is a bit hackish. We intercept RIF information
+ * used for source routing. We also grab IP directly and don't feed
+ * it via SNAP.
+ */
+
+unsigned short tr_type_trans(struct sk_buff *skb, struct net_device *dev)
+{
+
+ struct trh_hdr *trh=(struct trh_hdr *)skb->data;
+ struct trllc *trllc;
+ unsigned riflen=0;
+
+ skb->mac.raw = skb->data;
+
+ if(trh->saddr[0] & TR_RII)
+ riflen = (ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8;
+
+ trllc = (struct trllc *)(skb->data+sizeof(struct trh_hdr)-TR_MAXRIFLEN+riflen);
+
+ skb_pull(skb,sizeof(struct trh_hdr)-TR_MAXRIFLEN+riflen);
+
+ if(*trh->daddr & 0x80)
+ {
+ if(!memcmp(trh->daddr,dev->broadcast,TR_ALEN))
+ skb->pkt_type=PACKET_BROADCAST;
+ else
+ skb->pkt_type=PACKET_MULTICAST;
+ }
+ else if ( (trh->daddr[0] & 0x01) && (trh->daddr[1] & 0x00) && (trh->daddr[2] & 0x5E))
+ {
+ skb->pkt_type=PACKET_MULTICAST;
+ }
+ else if(dev->flags & IFF_PROMISC)
+ {
+ if(memcmp(trh->daddr, dev->dev_addr, TR_ALEN))
+ skb->pkt_type=PACKET_OTHERHOST;
+ }
+
+ if ((skb->pkt_type != PACKET_BROADCAST) &&
+ (skb->pkt_type != PACKET_MULTICAST))
+ tr_add_rif_info(trh,dev) ;
+
+ /*
+ * Strip the SNAP header from ARP packets since we don't
+ * pass them through to the 802.2/SNAP layers.
+ */
+
+ if (trllc->dsap == EXTENDED_SAP &&
+ (trllc->ethertype == ntohs(ETH_P_IP) ||
+ trllc->ethertype == ntohs(ETH_P_IPV6) ||
+ trllc->ethertype == ntohs(ETH_P_ARP)))
+ {
+ skb_pull(skb, sizeof(struct trllc));
+ return trllc->ethertype;
+ }
+
+ return ntohs(ETH_P_802_2);
+}
+
+/*
+ * We try to do source routing...
+ */
+
+void tr_source_route(struct sk_buff *skb, struct trh_hdr *trh,
+ struct net_device *dev)
+{
+ int i, slack;
+ unsigned int hash;
+ rif_cache entry;
+ unsigned char *olddata;
+ unsigned char mcast_func_addr[] = {0xC0,0x00,0x00,0x04,0x00,0x00};
+ unsigned long flags ;
+
+ spin_lock_irqsave(&rif_lock,flags);
+
+ /*
+ * Broadcasts are single route as stated in RFC 1042
+ */
+ if( (!memcmp(&(trh->daddr[0]),&(dev->broadcast[0]),TR_ALEN)) ||
+ (!memcmp(&(trh->daddr[0]),&(mcast_func_addr[0]), TR_ALEN)) )
+ {
+ trh->rcf=htons((((sizeof(trh->rcf)) << 8) & TR_RCF_LEN_MASK)
+ | TR_RCF_FRAME2K | TR_RCF_LIMITED_BROADCAST);
+ trh->saddr[0]|=TR_RII;
+ }
+ else
+ {
+ for(i=0,hash=0;i<TR_ALEN;hash+=trh->daddr[i++]);
+ hash&=RIF_TABLE_SIZE-1;
+ /*
+ * Walk the hash table and look for an entry
+ */
+ for(entry=rif_table[hash];entry && memcmp(&(entry->addr[0]),&(trh->daddr[0]),TR_ALEN);entry=entry->next);
+
+ /*
+ * If we found an entry we can route the frame.
+ */
+ if(entry)
+ {
+#if TR_SR_DEBUG
+printk("source routing for %02X:%02X:%02X:%02X:%02X:%02X\n",trh->daddr[0],
+ trh->daddr[1],trh->daddr[2],trh->daddr[3],trh->daddr[4],trh->daddr[5]);
+#endif
+ if(!entry->local_ring && (ntohs(entry->rcf) & TR_RCF_LEN_MASK) >> 8)
+ {
+ trh->rcf=entry->rcf;
+ memcpy(&trh->rseg[0],&entry->rseg[0],8*sizeof(unsigned short));
+ trh->rcf^=htons(TR_RCF_DIR_BIT);
+ trh->rcf&=htons(0x1fff); /* Issam Chehab <ichehab@madge1.demon.co.uk> */
+
+ trh->saddr[0]|=TR_RII;
+#if TR_SR_DEBUG
+ printk("entry found with rcf %04x\n", entry->rcf);
+ }
+ else
+ {
+ printk("entry found but without rcf length, local=%02x\n", entry->local_ring);
+#endif
+ }
+ entry->last_used=jiffies;
+ }
+ else
+ {
+ /*
+ * Without the information we simply have to shout
+ * on the wire. The replies should rapidly clean this
+ * situation up.
+ */
+ trh->rcf=htons((((sizeof(trh->rcf)) << 8) & TR_RCF_LEN_MASK)
+ | TR_RCF_FRAME2K | TR_RCF_LIMITED_BROADCAST);
+ trh->saddr[0]|=TR_RII;
+#if TR_SR_DEBUG
+ printk("no entry in rif table found - broadcasting frame\n");
+#endif
+ }
+ }
+
+ /* Compress the RIF here so we don't have to do it in the driver(s) */
+ if (!(trh->saddr[0] & 0x80))
+ slack = 18;
+ else
+ slack = 18 - ((ntohs(trh->rcf) & TR_RCF_LEN_MASK)>>8);
+ olddata = skb->data;
+ spin_unlock_irqrestore(&rif_lock,flags);
+
+ skb_pull(skb, slack);
+ memmove(skb->data, olddata, sizeof(struct trh_hdr) - slack);
+}
+
+/*
+ * We have learned some new RIF information for our source
+ * routing.
+ */
+
+static void tr_add_rif_info(struct trh_hdr *trh, struct net_device *dev)
+{
+ int i;
+ unsigned int hash, rii_p = 0;
+ rif_cache entry;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rif_lock, flags);
+
+ /*
+ * Firstly see if the entry exists
+ */
+
+ if(trh->saddr[0] & TR_RII)
+ {
+ trh->saddr[0]&=0x7f;
+ if (((ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8) > 2)
+ {
+ rii_p = 1;
+ }
+ }
+
+ for(i=0,hash=0;i<TR_ALEN;hash+=trh->saddr[i++]);
+ hash&=RIF_TABLE_SIZE-1;
+ for(entry=rif_table[hash];entry && memcmp(&(entry->addr[0]),&(trh->saddr[0]),TR_ALEN);entry=entry->next);
+
+ if(entry==NULL)
+ {
+#if TR_SR_DEBUG
+printk("adding rif_entry: addr:%02X:%02X:%02X:%02X:%02X:%02X rcf:%04X\n",
+ trh->saddr[0],trh->saddr[1],trh->saddr[2],
+ trh->saddr[3],trh->saddr[4],trh->saddr[5],
+ ntohs(trh->rcf));
+#endif
+ /*
+ * Allocate our new entry. A failure to allocate loses
+ * use the information. This is harmless.
+ *
+ * FIXME: We ought to keep some kind of cache size
+ * limiting and adjust the timers to suit.
+ */
+ entry=kmalloc(sizeof(struct rif_cache_s),GFP_ATOMIC);
+
+ if(!entry)
+ {
+ printk(KERN_DEBUG "tr.c: Couldn't malloc rif cache entry !\n");
+ spin_unlock_irqrestore(&rif_lock,flags);
+ return;
+ }
+
+ memcpy(&(entry->addr[0]),&(trh->saddr[0]),TR_ALEN);
+ entry->iface = dev->ifindex;
+ entry->next=rif_table[hash];
+ entry->last_used=jiffies;
+ rif_table[hash]=entry;
+
+ if (rii_p)
+ {
+ entry->rcf = trh->rcf & htons((unsigned short)~TR_RCF_BROADCAST_MASK);
+ memcpy(&(entry->rseg[0]),&(trh->rseg[0]),8*sizeof(unsigned short));
+ entry->local_ring = 0;
+ trh->saddr[0]|=TR_RII; /* put the routing indicator back for tcpdump */
+ }
+ else
+ {
+ entry->local_ring = 1;
+ }
+ }
+ else /* Y. Tahara added */
+ {
+ /*
+ * Update existing entries
+ */
+ if (!entry->local_ring)
+ if (entry->rcf != (trh->rcf & htons((unsigned short)~TR_RCF_BROADCAST_MASK)) &&
+ !(trh->rcf & htons(TR_RCF_BROADCAST_MASK)))
+ {
+#if TR_SR_DEBUG
+printk("updating rif_entry: addr:%02X:%02X:%02X:%02X:%02X:%02X rcf:%04X\n",
+ trh->saddr[0],trh->saddr[1],trh->saddr[2],
+ trh->saddr[3],trh->saddr[4],trh->saddr[5],
+ ntohs(trh->rcf));
+#endif
+ entry->rcf = trh->rcf & htons((unsigned short)~TR_RCF_BROADCAST_MASK);
+ memcpy(&(entry->rseg[0]),&(trh->rseg[0]),8*sizeof(unsigned short));
+ }
+ entry->last_used=jiffies;
+ }
+ spin_unlock_irqrestore(&rif_lock,flags);
+}
+
+/*
+ * Scan the cache with a timer and see what we need to throw out.
+ */
+
+static void rif_check_expire(unsigned long dummy)
+{
+ int i;
+ unsigned long now=jiffies;
+ unsigned long flags ;
+
+ spin_lock_irqsave(&rif_lock,flags);
+
+ for(i=0; i < RIF_TABLE_SIZE;i++)
+ {
+ rif_cache entry, *pentry=rif_table+i;
+ while((entry=*pentry))
+ {
+ /*
+ * Out it goes
+ */
+ if((now-entry->last_used) > sysctl_tr_rif_timeout)
+ {
+ *pentry=entry->next;
+ kfree(entry);
+ }
+ else
+ pentry=&entry->next;
+ }
+ }
+
+ spin_unlock_irqrestore(&rif_lock,flags);
+
+ /*
+ * Reset the timer
+ */
+
+ mod_timer(&rif_timer, jiffies+sysctl_tr_rif_timeout);
+
+}
+
+/*
+ * Generate the /proc/net information for the token ring RIF
+ * routing.
+ */
+
+#ifndef CONFIG_PROC_FS
+static int rif_get_info(char *buffer,char **start, off_t offset, int length) { return 0;}
+#else
+static int rif_get_info(char *buffer,char **start, off_t offset, int length)
+{
+ int len=0;
+ off_t begin=0;
+ off_t pos=0;
+ int size,i,j,rcf_len,segment,brdgnmb;
+ unsigned long now=jiffies;
+ unsigned long flags;
+
+ rif_cache entry;
+
+ size=sprintf(buffer,
+ "if TR address TTL rcf routing segments\n");
+ pos+=size;
+ len+=size;
+
+ spin_lock_irqsave(&rif_lock, flags);
+ for(i=0;i < RIF_TABLE_SIZE;i++)
+ {
+ for(entry=rif_table[i];entry;entry=entry->next) {
+ struct net_device *dev = __dev_get_by_index(entry->iface);
+
+ size=sprintf(buffer+len,"%s %02X:%02X:%02X:%02X:%02X:%02X %7li ",
+ dev?dev->name:"?",entry->addr[0],entry->addr[1],entry->addr[2],entry->addr[3],entry->addr[4],entry->addr[5],
+ sysctl_tr_rif_timeout-(now-entry->last_used));
+ len+=size;
+ pos=begin+len;
+ if (entry->local_ring)
+ size=sprintf(buffer+len,"local\n");
+ else {
+ size=sprintf(buffer+len,"%04X", ntohs(entry->rcf));
+ rcf_len = ((ntohs(entry->rcf) & TR_RCF_LEN_MASK)>>8)-2;
+ if (rcf_len)
+ rcf_len >>= 1;
+ for(j = 1; j < rcf_len; j++) {
+ if(j==1) {
+ segment=ntohs(entry->rseg[j-1])>>4;
+ len+=size;
+ pos=begin+len;
+ size=sprintf(buffer+len," %03X",segment);
+ };
+ segment=ntohs(entry->rseg[j])>>4;
+ brdgnmb=ntohs(entry->rseg[j-1])&0x00f;
+ len+=size;
+ pos=begin+len;
+ size=sprintf(buffer+len,"-%01X-%03X",brdgnmb,segment);
+ }
+ len+=size;
+ pos=begin+len;
+ size=sprintf(buffer+len,"\n");
+ }
+ len+=size;
+ pos=begin+len;
+
+ if(pos<offset)
+ {
+ len=0;
+ begin=pos;
+ }
+ if(pos>offset+length)
+ break;
+ }
+ if(pos>offset+length)
+ break;
+ }
+ spin_unlock_irqrestore(&rif_lock,flags);
+
+ *start=buffer+(offset-begin); /* Start of wanted data */
+ len-=(offset-begin); /* Start slop */
+ if(len>length)
+ len=length; /* Ending slop */
+ if (len<0)
+ len=0;
+ return len;
+}
+#endif
+
+/*
+ * Called during bootup. We don't actually have to initialise
+ * too much for this.
+ */
+
+static int __init rif_init(void)
+{
+ rif_timer.expires = RIF_TIMEOUT;
+ rif_timer.data = 0L;
+ rif_timer.function = rif_check_expire;
+ init_timer(&rif_timer);
+ add_timer(&rif_timer);
+
+ proc_net_create("tr_rif",0,rif_get_info);
+ return 0;
+}
+
+module_init(rif_init);
diff --git a/uClinux-2.4.31-uc0/net/802/transit/Makefile b/uClinux-2.4.31-uc0/net/802/transit/Makefile
new file mode 100644
index 0000000..2493697
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/transit/Makefile
@@ -0,0 +1,13 @@
+include $(TOPDIR)/Rules.make
+
+all: pdutr.h timertr.h
+
+pdutr.h: pdutr.pre compile.awk
+ awk -f ./compile.awk pdutr.pre > pdutr.h
+
+timertr.h: timertr.pre compile.awk
+ awk -f ./compile.awk timertr.pre > timertr.h
+
+clean:
+ touch pdutr.h timertr.h
+ rm pdutr.h timertr.h
diff --git a/uClinux-2.4.31-uc0/net/802/transit/compile.awk b/uClinux-2.4.31-uc0/net/802/transit/compile.awk
new file mode 100644
index 0000000..1b3b56c
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/transit/compile.awk
@@ -0,0 +1,81 @@
+# to run: awk -f transit.awk transit.p0
+#
+BEGIN { "date" | getline
+ enable_index = 1
+ today = $0
+ printf("\n/* this file was generated on %s */\n", today )
+ not_firstone = 0 # flag to avoid empty entry in 1st table
+ fpe = 0 # entry tbl array fill pointer
+ fpeo = 0 # entry tbl offset list fill pointer
+ fpdef = 0 # define list fill pointer
+}
+
+### /^;/ { } # line starting with a semicolon is comment
+
+/^[A-Z]/ { # table name
+ if ( $1 == "TABLE" ) {
+ tbl = $2 # get table name
+ newtbl( tbl )
+ }
+ else if ( $1 == "COMPILE" ) {
+ array_name = $2
+ if ( $3 == "NOINDEX" ) { enable_index = 0 }
+ }
+ else { # table entry
+ ec = ec +1
+ n = split( $0, fld, " " )
+ action = fld[ n-1 ]
+ newstate = fld[ n ]
+ store( action, newstate )
+ ecct = ecct +1
+ }
+}
+
+END { store( action, newstate )
+
+ if ( enable_index ) {
+ printf( "\n/* index name #defines: */\n\n",
+ ec, ecct )
+
+ for( ii = 1; ii <= fpeo; ii++ ){
+ printf( "#define %-12s %3d\n", define[ ii ], ii -1 )
+ }
+ }
+
+ printf( "\n\n/* size of transition table is %d bytes */\n",
+ fpe )
+
+ if ( enable_index ) {
+ printf( "\nstatic short int %s_offset [ ] ={", array_name )
+ for( ii = 1; ii <= fpeo; ii++ ){
+ if ( (ii % 10) == 1 ) printf("\n ")
+ printf( " %4d", entry_offset[ ii ] )
+ if ( ii < fpeo ) printf( "," )
+ }
+ printf(" };\n")
+ }
+
+ printf( "\nstatic char %s_entry [ ] = {", array_name )
+ for( ii = 1; ii <= fpe; ii++ ){
+ if ( (ii % 6) == 1 ) printf("\n ")
+ printf( " %-14s", entry[ ii ] )
+ if ( ii < fpe ) printf( "," )
+ }
+ printf(" };\n")
+
+}
+
+function store( act, ns ){
+# printf( "%s %s\n", act, ns )
+ entry[ ++fpe ] = act
+ entry[ ++fpe ] = ns
+}
+
+function newtbl( tbl ){
+ if ( not_firstone ) {
+ store( action, newstate )
+ }
+ not_firstone = 1
+ entry_offset[ ++fpeo ] = fpe # entry tbl offset list
+ define[ ++fpdef ] = tbl # state name to define
+}
diff --git a/uClinux-2.4.31-uc0/net/802/transit/pdutr.h b/uClinux-2.4.31-uc0/net/802/transit/pdutr.h
new file mode 100644
index 0000000..900dc74
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/transit/pdutr.h
@@ -0,0 +1,309 @@
+
+/* this file was generated on Thu Jan 8 00:21:19 GMT 1998 */
+
+/* index name #defines: */
+
+#define ADM 0
+#define CONN 1
+#define RESET_WAIT 2
+#define RESET_CHECK 3
+#define SETUP 4
+#define RESET 5
+#define D_CONN 6
+#define ERROR 7
+#define NORMAL 8
+#define BUSY 9
+#define REJECT 10
+#define AWAIT 11
+#define AWAIT_BUSY 12
+#define AWAIT_REJECT 13
+
+
+/* size of transition table is 1684 bytes */
+
+static short int pdutr_offset [ ] ={
+ 0, 54, 82, 110, 138, 192, 246, 300, 328, 554,
+ 780, 1006, 1232, 1458 };
+
+static char pdutr_entry [ ] = {
+ ADM5 , ADM , ADM4 , ADM , ADM5 , ADM ,
+ ADM4 , ADM , ADM5 , ADM , ADM4 , ADM ,
+ ADM5 , ADM , ADM4 , ADM , ADM3 , ADM ,
+ ADM3 , ADM , ADM2 , CONN , ADM2 , CONN ,
+ ADM5 , ADM , ADM5 , ADM , ADM5 , ADM ,
+ ADM5 , ADM , ADM5 , ADM , ADM5 , ADM ,
+ ADM5 , ADM , ADM5 , ADM , ADM5 , ADM ,
+ ADM5 , ADM , ADM5 , ADM , ADM5 , ADM ,
+ ADM5 , ADM , ADM5 , ADM , ADM5 , ADM ,
+ CONN5 , CONN , CONN5 , CONN , CONN5 , CONN ,
+ CONN5 , CONN , CONN5 , CONN , CONN3 , CONN ,
+ CONN5 , CONN , CONN5 , CONN , CONN5 , CONN ,
+ CONN5 , CONN , CONN5 , CONN , CONN4 , ADM ,
+ CONN5 , CONN , CONN5 , CONN , RESWAIT8 , RESET_WAIT ,
+ RESWAIT8 , RESET_WAIT , RESWAIT8 , RESET_WAIT , RESWAIT8 , RESET_WAIT ,
+ RESWAIT7 , RESET_WAIT , RESWAIT6 , RESET_WAIT , RESWAIT8 , RESET_WAIT ,
+ RESWAIT8 , RESET_WAIT , RESWAIT8 , RESET_WAIT , RESWAIT8 , RESET_WAIT ,
+ RESWAIT8 , RESET_WAIT , RESWAIT5 , ADM , RESWAIT8 , RESET_WAIT ,
+ RESWAIT8 , RESET_WAIT , RESCHK6 , RESET_CHECK , RESCHK6 , RESET_CHECK ,
+ RESCHK6 , RESET_CHECK , RESCHK6 , RESET_CHECK , RESCHK5 , ADM ,
+ RESCHK4 , RESET_CHECK , RESCHK6 , RESET_CHECK , RESCHK6 , RESET_CHECK ,
+ RESCHK6 , RESET_CHECK , RESCHK6 , RESET_CHECK , RESCHK6 , RESET_CHECK ,
+ RESCHK3 , ADM , RESCHK6 , RESET_CHECK , RESCHK6 , RESET_CHECK ,
+ SETUP6 , SETUP , SETUP6 , SETUP , SETUP6 , SETUP ,
+ SETUP6 , SETUP , SETUP6 , SETUP , SETUP6 , SETUP ,
+ SETUP6 , SETUP , SETUP6 , SETUP , SETUP4 , ADM ,
+ SETUP4 , ADM , SETUP1 , SETUP , SETUP1 , SETUP ,
+ SETUP6 , SETUP , SETUP6 , SETUP , SETUP6 , SETUP ,
+ SETUP6 , SETUP , SETUP6 , SETUP , SETUP6 , SETUP ,
+ SETUP6 , SETUP , SETUP6 , SETUP , SETUP6 , SETUP ,
+ SETUP2 , NORMAL , SETUP5 , ADM , SETUP5 , ADM ,
+ SETUP6 , SETUP , SETUP6 , SETUP , SETUP6 , SETUP ,
+ RESET6 , RESET , RESET6 , RESET , RESET6 , RESET ,
+ RESET6 , RESET , RESET6 , RESET , RESET6 , RESET ,
+ RESET6 , RESET , RESET6 , RESET , RESET4 , ADM ,
+ RESET4 , ADM , RESET1 , RESET , RESET1 , RESET ,
+ RESET6 , RESET , RESET6 , RESET , RESET6 , RESET ,
+ RESET6 , RESET , RESET6 , RESET , RESET6 , RESET ,
+ RESET6 , RESET , RESET6 , RESET , RESET6 , RESET ,
+ RESET2 , NORMAL , RESET5 , ADM , RESET5 , ADM ,
+ RESET6 , RESET , RESET6 , RESET , RESET6 , RESET ,
+ D_CONN5 , D_CONN , D_CONN5 , D_CONN , D_CONN5 , D_CONN ,
+ D_CONN5 , D_CONN , D_CONN5 , D_CONN , D_CONN5 , D_CONN ,
+ D_CONN5 , D_CONN , D_CONN5 , D_CONN , D_CONN3 , D_CONN ,
+ D_CONN3 , D_CONN , D_CONN1 , ADM , D_CONN1 , ADM ,
+ D_CONN5 , D_CONN , D_CONN5 , D_CONN , D_CONN5 , D_CONN ,
+ D_CONN5 , D_CONN , D_CONN5 , D_CONN , D_CONN5 , D_CONN ,
+ D_CONN5 , D_CONN , D_CONN5 , D_CONN , D_CONN5 , D_CONN ,
+ D_CONN4 , ADM , D_CONN4 , ADM , D_CONN5 , ADM ,
+ D_CONN5 , D_CONN , D_CONN5 , D_CONN , D_CONN5 , D_CONN ,
+ ERR5 , ERROR , ERR5 , ERROR , ERR5 , ERROR ,
+ ERR5 , ERROR , ERR2 , ADM , ERR1 , RESET_CHECK ,
+ ERR6 , ERROR , ERR6 , ERROR , ERR6 , ERROR ,
+ ERR6 , ERROR , ERR6 , ERROR , ERR3 , ADM ,
+ ERR4 , RESET_WAIT , ERR4 , RESET_WAIT , NORMAL8B , NORMAL ,
+ NORMAL9 , NORMAL , NORMAL10 , NORMAL , NORMAL10 , NORMAL ,
+ NORMAL5 , REJECT , NORMAL6 , REJECT , NORMAL7 , REJECT ,
+ NORMAL7 , REJECT , NORMAL11 , NORMAL , NORMAL11 , NORMAL ,
+ NORMAL12 , NORMAL , NORMAL12 , NORMAL , NORMAL11 , NORMAL ,
+ NORMAL11 , NORMAL , NORMAL12 , NORMAL , NORMAL12 , NORMAL ,
+ NORMAL13 , NORMAL , NORMAL13 , NORMAL , NORMAL14 , NORMAL ,
+ NORMAL14 , NORMAL , NORMAL13 , NORMAL , NORMAL13 , NORMAL ,
+ NORMAL14 , NORMAL , NORMAL14 , NORMAL , NORMAL15 , NORMAL ,
+ NORMAL16 , NORMAL , NORMAL17 , NORMAL , NORMAL17 , NORMAL ,
+ NORMAL15 , NORMAL , NORMAL16 , NORMAL , NORMAL17 , NORMAL ,
+ NORMAL17 , NORMAL , SH4 , ADM , SH4 , ADM ,
+ SH4 , ADM , SH4 , ADM , SH4 , ADM ,
+ SH4 , ADM , SH4 , ADM , SH4 , ADM ,
+ SH3 , RESET_CHECK , SH3 , RESET_CHECK , SH3 , RESET_CHECK ,
+ SH3 , RESET_CHECK , SH3 , RESET_CHECK , SH3 , RESET_CHECK ,
+ SH3 , RESET_CHECK , SH3 , RESET_CHECK , NORMAL8B , NORMAL ,
+ NORMAL9 , NORMAL , SH10 , ERROR , NORMAL8A , NORMAL ,
+ NORMAL5 , REJECT , NORMAL6 , REJECT , SH10 , ERROR ,
+ NORMAL5 , REJECT , NORMAL11 , NORMAL , NORMAL11 , NORMAL ,
+ SH10 , ERROR , NORMAL11 , NORMAL , NORMAL11 , NORMAL ,
+ NORMAL11 , NORMAL , SH10 , ERROR , NORMAL11 , NORMAL ,
+ NORMAL13 , NORMAL , NORMAL13 , NORMAL , SH10 , ERROR ,
+ NORMAL13 , NORMAL , NORMAL13 , NORMAL , NORMAL13 , NORMAL ,
+ SH10 , ERROR , NORMAL13 , NORMAL , NORMAL15 , NORMAL ,
+ NORMAL16 , NORMAL , SH10 , ERROR , NORMAL15 , NORMAL ,
+ NORMAL15 , NORMAL , NORMAL16 , NORMAL , SH10 , ERROR ,
+ NORMAL15 , NORMAL , SH9 , ERROR , SH9 , ERROR ,
+ SH9 , ERROR , SH9 , ERROR , SH9 , ERROR ,
+ SH9 , ERROR , SH9 , ERROR , SH9 , ERROR ,
+ SH6 , ADM , SH6 , ADM , SH6 , ADM ,
+ SH6 , ADM , SH6 , ADM , SH6 , ADM ,
+ SH6 , ADM , SH6 , ADM , SH5 , RESET_WAIT ,
+ SH5 , RESET_WAIT , SH5 , RESET_WAIT , SH5 , RESET_WAIT ,
+ SH5 , RESET_WAIT , SH5 , RESET_WAIT , SH5 , RESET_WAIT ,
+ SH5 , RESET_WAIT , SH7 , ERROR , SH7 , ERROR ,
+ SH7 , ERROR , SH7 , ERROR , SH7 , ERROR ,
+ SH7 , ERROR , SH7 , ERROR , SH7 , ERROR ,
+ SH7 , ERROR , BUSY13 , BUSY , BUSY14 , BUSY ,
+ BUSY12 , BUSY , BUSY12 , BUSY , BUSY9 , BUSY ,
+ BUSY10 , BUSY , BUSY11 , BUSY , BUSY11 , BUSY ,
+ BUSY15 , BUSY , BUSY15 , BUSY , BUSY16 , BUSY ,
+ BUSY16 , BUSY , BUSY15 , BUSY , BUSY15 , BUSY ,
+ BUSY16 , BUSY , BUSY16 , BUSY , BUSY17 , BUSY ,
+ BUSY17 , BUSY , BUSY18 , BUSY , BUSY18 , BUSY ,
+ BUSY17 , BUSY , BUSY17 , BUSY , BUSY18 , BUSY ,
+ BUSY18 , BUSY , BUSY19 , BUSY , BUSY20 , BUSY ,
+ BUSY21 , BUSY , BUSY21 , BUSY , BUSY19 , BUSY ,
+ BUSY20 , BUSY , BUSY21 , BUSY , BUSY21 , BUSY ,
+ SH4 , ADM , SH4 , ADM , SH4 , ADM ,
+ SH4 , ADM , SH4 , ADM , SH4 , ADM ,
+ SH4 , ADM , SH4 , ADM , SH3 , RESET_CHECK ,
+ SH3 , RESET_CHECK , SH3 , RESET_CHECK , SH3 , RESET_CHECK ,
+ SH3 , RESET_CHECK , SH3 , RESET_CHECK , SH3 , RESET_CHECK ,
+ SH3 , RESET_CHECK , BUSY13 , BUSY , BUSY14 , BUSY ,
+ SH10 , ERROR , BUSY13 , BUSY , BUSY9 , BUSY ,
+ BUSY10 , BUSY , SH10 , ERROR , BUSY9 , BUSY ,
+ BUSY15 , BUSY , BUSY15 , BUSY , SH10 , ERROR ,
+ BUSY15 , BUSY , BUSY15 , BUSY , BUSY15 , BUSY ,
+ SH10 , ERROR , BUSY15 , BUSY , BUSY17 , BUSY ,
+ BUSY17 , BUSY , SH10 , ERROR , BUSY17 , BUSY ,
+ BUSY17 , BUSY , BUSY17 , BUSY , SH10 , ERROR ,
+ BUSY17 , BUSY , BUSY19 , BUSY , BUSY20 , BUSY ,
+ SH10 , ERROR , BUSY19 , BUSY , BUSY19 , BUSY ,
+ BUSY20 , BUSY , SH10 , ERROR , BUSY19 , BUSY ,
+ SH9 , ERROR , SH9 , ERROR , SH9 , ERROR ,
+ SH9 , ERROR , SH9 , ERROR , SH9 , ERROR ,
+ SH9 , ERROR , SH9 , ERROR , SH6 , ADM ,
+ SH6 , ADM , SH6 , ADM , SH6 , ADM ,
+ SH6 , ADM , SH6 , ADM , SH6 , ADM ,
+ SH6 , ADM , SH5 , RESET_WAIT , SH5 , RESET_WAIT ,
+ SH5 , RESET_WAIT , SH5 , RESET_WAIT , SH5 , RESET_WAIT ,
+ SH5 , RESET_WAIT , SH5 , RESET_WAIT , SH5 , RESET_WAIT ,
+ SH7 , ERROR , SH7 , ERROR , SH7 , ERROR ,
+ SH7 , ERROR , SH7 , ERROR , SH7 , ERROR ,
+ SH7 , ERROR , SH7 , ERROR , SH7 , ERROR ,
+ REJECT7 , REJECT , REJECT8 , REJECT , REJECT9 , REJECT ,
+ REJECT9 , REJECT , REJECT5 , REJECT , REJECT5 , REJECT ,
+ REJECT6 , REJECT , REJECT6 , REJECT , REJECT10 , REJECT ,
+ REJECT10 , REJECT , REJECT11 , REJECT , REJECT11 , REJECT ,
+ REJECT10 , REJECT , REJECT10 , REJECT , REJECT11 , REJECT ,
+ REJECT11 , REJECT , REJECT12 , REJECT , REJECT12 , REJECT ,
+ REJECT13 , REJECT , REJECT13 , REJECT , REJECT12 , REJECT ,
+ REJECT12 , REJECT , REJECT13 , REJECT , REJECT13 , REJECT ,
+ REJECT14 , REJECT , REJECT15 , REJECT , REJECT16 , REJECT ,
+ REJECT16 , REJECT , REJECT14 , REJECT , REJECT15 , REJECT ,
+ REJECT16 , REJECT , REJECT16 , REJECT , SH4 , ADM ,
+ SH4 , ADM , SH4 , ADM , SH4 , ADM ,
+ SH4 , ADM , SH4 , ADM , SH4 , ADM ,
+ SH4 , ADM , SH3 , RESET_CHECK , SH3 , RESET_CHECK ,
+ SH3 , RESET_CHECK , SH3 , RESET_CHECK , SH3 , RESET_CHECK ,
+ SH3 , RESET_CHECK , SH3 , RESET_CHECK , SH3 , RESET_CHECK ,
+ REJECT7 , REJECT , REJECT8 , REJECT , SH10 , ERROR ,
+ REJECT7 , REJECT , REJECT5 , REJECT , REJECT5 , REJECT ,
+ SH10 , ERROR , REJECT5 , REJECT , REJECT10 , REJECT ,
+ REJECT10 , REJECT , SH10 , ERROR , REJECT10 , REJECT ,
+ REJECT10 , REJECT , REJECT10 , REJECT , SH10 , ERROR ,
+ REJECT10 , REJECT , REJECT12 , REJECT , REJECT12 , REJECT ,
+ SH10 , ERROR , REJECT12 , REJECT , REJECT12 , REJECT ,
+ REJECT12 , REJECT , SH10 , ERROR , REJECT12 , REJECT ,
+ REJECT14 , REJECT , REJECT15 , REJECT , SH10 , ERROR ,
+ REJECT14 , REJECT , REJECT14 , REJECT , REJECT15 , REJECT ,
+ SH10 , ERROR , REJECT14 , REJECT , SH9 , ERROR ,
+ SH9 , ERROR , SH9 , ERROR , SH9 , ERROR ,
+ SH9 , ERROR , SH9 , ERROR , SH9 , ERROR ,
+ SH9 , ERROR , SH6 , ADM , SH6 , ADM ,
+ SH6 , ADM , SH6 , ADM , SH6 , ADM ,
+ SH6 , ADM , SH6 , ADM , SH6 , ADM ,
+ SH5 , RESET_WAIT , SH5 , RESET_WAIT , SH5 , RESET_WAIT ,
+ SH5 , RESET_WAIT , SH5 , RESET_WAIT , SH5 , RESET_WAIT ,
+ SH5 , RESET_WAIT , SH5 , RESET_WAIT , SH7 , ERROR ,
+ SH7 , ERROR , SH7 , ERROR , SH7 , ERROR ,
+ SH7 , ERROR , SH7 , ERROR , SH7 , ERROR ,
+ SH7 , ERROR , SH7 , ERROR , AWAIT6 , AWAIT ,
+ AWAIT6 , AWAIT , AWAIT7 , AWAIT , AWAIT7 , AWAIT ,
+ AWAIT3 , AWAIT_REJECT , AWAIT3 , AWAIT_REJECT , AWAIT4 , AWAIT_REJECT ,
+ AWAIT4 , AWAIT_REJECT , AWAIT9 , AWAIT , AWAIT9 , AWAIT ,
+ AWAIT10 , AWAIT , AWAIT10 , AWAIT , AWAIT9 , AWAIT ,
+ AWAIT9 , AWAIT , AWAIT10 , AWAIT , AWAIT10 , AWAIT ,
+ AWAIT12 , AWAIT , AWAIT12 , AWAIT , AWAIT13 , AWAIT ,
+ AWAIT13 , AWAIT , AWAIT12 , AWAIT , AWAIT12 , AWAIT ,
+ AWAIT13 , AWAIT , AWAIT13 , AWAIT , AWAIT9 , AWAIT ,
+ AWAIT9 , AWAIT , AWAIT10 , AWAIT , AWAIT10 , AWAIT ,
+ AWAIT9 , AWAIT , AWAIT9 , AWAIT , AWAIT10 , AWAIT ,
+ AWAIT10 , AWAIT , SH4 , ADM , SH4 , ADM ,
+ SH4 , ADM , SH4 , ADM , SH4 , ADM ,
+ SH4 , ADM , SH4 , ADM , SH4 , ADM ,
+ SH3 , RESET_CHECK , SH3 , RESET_CHECK , SH3 , RESET_CHECK ,
+ SH3 , RESET_CHECK , SH3 , RESET_CHECK , SH3 , RESET_CHECK ,
+ SH3 , RESET_CHECK , SH3 , RESET_CHECK , AWAIT6 , AWAIT ,
+ AWAIT6 , AWAIT , SH10 , ERROR , AWAIT5 , NORMAL ,
+ AWAIT3 , AWAIT_REJECT , AWAIT3 , AWAIT_REJECT , SH10 , ERROR ,
+ AWAIT2 , REJECT , AWAIT9 , AWAIT , AWAIT9 , AWAIT ,
+ SH10 , ERROR , AWAIT8 , AWAIT , AWAIT9 , AWAIT ,
+ AWAIT9 , AWAIT , SH10 , ERROR , AWAIT8 , AWAIT ,
+ AWAIT12 , AWAIT , AWAIT12 , AWAIT , SH10 , ERROR ,
+ AWAIT11 , AWAIT , AWAIT12 , AWAIT , AWAIT12 , AWAIT ,
+ SH10 , ERROR , AWAIT11 , AWAIT , AWAIT9 , AWAIT ,
+ AWAIT9 , AWAIT , SH10 , ERROR , AWAIT8 , AWAIT ,
+ AWAIT9 , AWAIT , AWAIT9 , AWAIT , SH10 , ERROR ,
+ AWAIT8 , AWAIT , SH9 , ERROR , SH9 , ERROR ,
+ SH9 , ERROR , SH9 , ERROR , SH9 , ERROR ,
+ SH9 , ERROR , SH9 , ERROR , SH9 , ERROR ,
+ SH6 , ADM , SH6 , ADM , SH6 , ADM ,
+ SH6 , ADM , SH6 , ADM , SH6 , ADM ,
+ SH6 , ADM , SH6 , ADM , SH5 , RESET_WAIT ,
+ SH5 , RESET_WAIT , SH5 , RESET_WAIT , SH5 , RESET_WAIT ,
+ SH5 , RESET_WAIT , SH5 , RESET_WAIT , SH5 , RESET_WAIT ,
+ SH5 , RESET_WAIT , SH7 , ERROR , SH7 , ERROR ,
+ SH7 , ERROR , SH7 , ERROR , SH7 , ERROR ,
+ SH7 , ERROR , SH7 , ERROR , SH7 , ERROR ,
+ SH7 , ERROR , AWAIT_BUSY8 , AWAIT_BUSY , AWAIT_BUSY8 , AWAIT_BUSY ,
+ AWAIT_BUSY9 , AWAIT_BUSY , AWAIT_BUSY9 , AWAIT_BUSY , AWAIT_BUSY5 , AWAIT_BUSY ,
+ AWAIT_BUSY5 , AWAIT_BUSY , AWAIT_BUSY6 , AWAIT_BUSY , AWAIT_BUSY6 , AWAIT_BUSY ,
+ AWAIT_BUSY11 , AWAIT_BUSY , AWAIT_BUSY11 , AWAIT_BUSY , AWAIT_BUSY12 , AWAIT_BUSY ,
+ AWAIT_BUSY12 , AWAIT_BUSY , AWAIT_BUSY11 , AWAIT_BUSY , AWAIT_BUSY11 , AWAIT_BUSY ,
+ AWAIT_BUSY12 , AWAIT_BUSY , AWAIT_BUSY12 , AWAIT_BUSY , AWAIT_BUSY14 , AWAIT_BUSY ,
+ AWAIT_BUSY14 , AWAIT_BUSY , AWAIT_BUSY15 , AWAIT_BUSY , AWAIT_BUSY15 , AWAIT_BUSY ,
+ AWAIT_BUSY14 , AWAIT_BUSY , AWAIT_BUSY14 , AWAIT_BUSY , AWAIT_BUSY15 , AWAIT_BUSY ,
+ AWAIT_BUSY15 , AWAIT_BUSY , AWAIT_BUSY11 , AWAIT_BUSY , AWAIT_BUSY11 , AWAIT_BUSY ,
+ AWAIT_BUSY12 , AWAIT_BUSY , AWAIT_BUSY12 , AWAIT_BUSY , AWAIT_BUSY11 , AWAIT_BUSY ,
+ AWAIT_BUSY11 , AWAIT_BUSY , AWAIT_BUSY12 , AWAIT_BUSY , AWAIT_BUSY12 , AWAIT_BUSY ,
+ SH4 , ADM , SH4 , ADM , SH4 , ADM ,
+ SH4 , ADM , SH4 , ADM , SH4 , ADM ,
+ SH4 , ADM , SH4 , ADM , SH3 , RESET_CHECK ,
+ SH3 , RESET_CHECK , SH3 , RESET_CHECK , SH3 , RESET_CHECK ,
+ SH3 , RESET_CHECK , SH3 , RESET_CHECK , SH3 , RESET_CHECK ,
+ SH3 , RESET_CHECK , AWAIT_BUSY8 , AWAIT_BUSY , AWAIT_BUSY8 , AWAIT_BUSY ,
+ SH10 , ERROR , AWAIT_BUSY7 , BUSY , AWAIT_BUSY5 , AWAIT_BUSY ,
+ AWAIT_BUSY5 , AWAIT_BUSY , SH10 , ERROR , AWAIT_BUSY4 , BUSY ,
+ AWAIT_BUSY11 , AWAIT_BUSY , AWAIT_BUSY11 , AWAIT_BUSY , SH10 , ERROR ,
+ AWAIT_BUSY10 , BUSY , AWAIT_BUSY11 , AWAIT_BUSY , AWAIT_BUSY11 , AWAIT_BUSY ,
+ SH10 , ERROR , AWAIT_BUSY10 , BUSY , AWAIT_BUSY14 , AWAIT_BUSY ,
+ AWAIT_BUSY14 , AWAIT_BUSY , SH10 , ERROR , AWAIT_BUSY13 , BUSY ,
+ AWAIT_BUSY14 , AWAIT_BUSY , AWAIT_BUSY14 , AWAIT_BUSY , SH10 , ERROR ,
+ AWAIT_BUSY13 , BUSY , AWAIT_BUSY11 , AWAIT_BUSY , AWAIT_BUSY11 , AWAIT_BUSY ,
+ SH10 , ERROR , AWAIT_BUSY10 , BUSY , AWAIT_BUSY11 , AWAIT_BUSY ,
+ AWAIT_BUSY11 , AWAIT_BUSY , SH10 , ERROR , AWAIT_BUSY10 , BUSY ,
+ SH9 , ERROR , SH9 , ERROR , SH9 , ERROR ,
+ SH9 , ERROR , SH9 , ERROR , SH9 , ERROR ,
+ SH9 , ERROR , SH9 , ERROR , SH6 , ADM ,
+ SH6 , ADM , SH6 , ADM , SH6 , ADM ,
+ SH6 , ADM , SH6 , ADM , SH6 , ADM ,
+ SH6 , ADM , SH5 , RESET_WAIT , SH5 , RESET_WAIT ,
+ SH5 , RESET_WAIT , SH5 , RESET_WAIT , SH5 , RESET_WAIT ,
+ SH5 , RESET_WAIT , SH5 , RESET_WAIT , SH5 , RESET_WAIT ,
+ SH7 , ERROR , SH7 , ERROR , SH7 , ERROR ,
+ SH7 , ERROR , SH7 , ERROR , SH7 , ERROR ,
+ SH7 , ERROR , SH7 , ERROR , SH7 , ERROR ,
+ AWAIT_REJECT5 , AWAIT , AWAIT_REJECT5 , AWAIT , AWAIT_REJECT6 , AWAIT ,
+ AWAIT_REJECT6 , AWAIT , AWAIT_REJECT2 , AWAIT_REJECT , AWAIT_REJECT2 , AWAIT_REJECT ,
+ AWAIT_REJECT3 , AWAIT_REJECT , AWAIT_REJECT3 , AWAIT_REJECT , AWAIT_REJECT8 , AWAIT_REJECT ,
+ AWAIT_REJECT8 , AWAIT_REJECT , AWAIT_REJECT9 , AWAIT_REJECT , AWAIT_REJECT9 , AWAIT_REJECT ,
+ AWAIT_REJECT8 , AWAIT_REJECT , AWAIT_REJECT8 , AWAIT_REJECT , AWAIT_REJECT9 , AWAIT_REJECT ,
+ AWAIT_REJECT9 , AWAIT_REJECT , AWAIT_REJECT11, AWAIT_REJECT , AWAIT_REJECT11, AWAIT_REJECT ,
+ AWAIT_REJECT12, AWAIT_REJECT , AWAIT_REJECT12, AWAIT_REJECT , AWAIT_REJECT11, AWAIT_REJECT ,
+ AWAIT_REJECT11, AWAIT_REJECT , AWAIT_REJECT12, AWAIT_REJECT , AWAIT_REJECT12, AWAIT_REJECT ,
+ AWAIT_REJECT8 , AWAIT_REJECT , AWAIT_REJECT8 , AWAIT_REJECT , AWAIT_REJECT9 , AWAIT_REJECT ,
+ AWAIT_REJECT9 , AWAIT_REJECT , AWAIT_REJECT8 , AWAIT_REJECT , AWAIT_REJECT8 , AWAIT_REJECT ,
+ AWAIT_REJECT9 , AWAIT_REJECT , AWAIT_REJECT9 , AWAIT_REJECT , SH4 , ADM ,
+ SH4 , ADM , SH4 , ADM , SH4 , ADM ,
+ SH4 , ADM , SH4 , ADM , SH4 , ADM ,
+ SH4 , ADM , SH3 , RESET_CHECK , SH3 , RESET_CHECK ,
+ SH3 , RESET_CHECK , SH3 , RESET_CHECK , SH3 , RESET_CHECK ,
+ SH3 , RESET_CHECK , SH3 , RESET_CHECK , SH3 , RESET_CHECK ,
+ AWAIT_REJECT5 , AWAIT , AWAIT_REJECT5 , AWAIT , SH10 , ERROR ,
+ AWAIT_REJECT4 , NORMAL , AWAIT_REJECT2 , AWAIT_REJECT , AWAIT_REJECT2 , AWAIT_REJECT ,
+ SH10 , ERROR , AWAIT_REJECT4 , NORMAL , AWAIT_REJECT8 , AWAIT_REJECT ,
+ AWAIT_REJECT8 , AWAIT_REJECT , SH10 , ERROR , AWAIT_REJECT7 , REJECT ,
+ AWAIT_REJECT8 , AWAIT_REJECT , AWAIT_REJECT8 , AWAIT_REJECT , SH10 , ERROR ,
+ AWAIT_REJECT7 , REJECT , AWAIT_REJECT11, AWAIT_REJECT , AWAIT_REJECT11, AWAIT_REJECT ,
+ SH10 , ERROR , AWAIT_REJECT10, REJECT , AWAIT_REJECT11, AWAIT_REJECT ,
+ AWAIT_REJECT11, AWAIT_REJECT , SH10 , ERROR , AWAIT_REJECT10, REJECT ,
+ AWAIT_REJECT8 , AWAIT_REJECT , AWAIT_REJECT8 , AWAIT_REJECT , SH10 , ERROR ,
+ AWAIT_REJECT7 , REJECT , AWAIT_REJECT8 , AWAIT_REJECT , AWAIT_REJECT8 , AWAIT_REJECT ,
+ SH10 , ERROR , AWAIT_REJECT7 , REJECT , SH9 , ERROR ,
+ SH9 , ERROR , SH9 , ERROR , SH9 , ERROR ,
+ SH9 , ERROR , SH9 , ERROR , SH9 , ERROR ,
+ SH9 , ERROR , SH6 , ADM , SH6 , ADM ,
+ SH6 , ADM , SH6 , ADM , SH6 , ADM ,
+ SH6 , ADM , SH6 , ADM , SH6 , ADM ,
+ SH5 , RESET_WAIT , SH5 , RESET_WAIT , SH5 , RESET_WAIT ,
+ SH5 , RESET_WAIT , SH5 , RESET_WAIT , SH5 , RESET_WAIT ,
+ SH5 , RESET_WAIT , SH5 , RESET_WAIT , SH7 , ERROR ,
+ SH7 , ERROR , SH7 , ERROR , SH7 , ERROR ,
+ SH7 , ERROR , SH7 , ERROR , SH7 , ERROR ,
+ SH7 , ERROR , SH7 , ERROR };
diff --git a/uClinux-2.4.31-uc0/net/802/transit/pdutr.pre b/uClinux-2.4.31-uc0/net/802/transit/pdutr.pre
new file mode 100644
index 0000000..3569239
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/transit/pdutr.pre
@@ -0,0 +1,1121 @@
+COMPILE pdutr INDEX
+;
+; Transition tables for incoming pdu events.
+; translate this thing into C with
+; awk -f ./compile.awk pdu.trans > pdutr.h
+;
+TABLE ADM
+;Transition table for the ADM state:
+;
+;frame type p bit action newstate
+;received in frame
+;
+I_CMD 0 ADM5 ADM
+I_CMD 1 ADM4 ADM
+RR_CMD 0 ADM5 ADM
+RR_CMD 1 ADM4 ADM
+RNR_CMD 0 ADM5 ADM
+RNR_CMD 1 ADM4 ADM
+REJ_CMD 0 ADM5 ADM
+REJ_CMD 1 ADM4 ADM
+DISC_CMD 0 ADM3 ADM
+DISC_CMD 1 ADM3 ADM
+SABME_CMD 0 ADM2 CONN
+SABME_CMD 1 ADM2 CONN
+I_RSP 0 ADM5 ADM
+I_RSP 1 ADM5 ADM
+RR_RSP 0 ADM5 ADM
+RR_RSP 1 ADM5 ADM
+RNR_RSP 0 ADM5 ADM
+RNR_RSP 1 ADM5 ADM
+REJ_RSP 0 ADM5 ADM
+REJ_RSP 1 ADM5 ADM
+UA_RSP 0 ADM5 ADM
+UA_RSP 1 ADM5 ADM
+DM_RSP 0 ADM5 ADM
+DM_RSP 1 ADM5 ADM
+FRMR_RSP 0 ADM5 ADM
+FRMR_RSP 1 ADM5 ADM
+;
+TABLE CONN
+;
+;Transition table for the CONN state:
+;
+;frame type action newstate
+;received
+;
+I_CMD CONN5 CONN
+RR_CMD CONN5 CONN
+RNR_CMD CONN5 CONN
+REJ_CMD CONN5 CONN
+DISC_CMD CONN5 CONN
+SABME_CMD CONN3 CONN
+I_RSP CONN5 CONN
+RR_RSP CONN5 CONN
+RNR_RSP CONN5 CONN
+REJ_RSP CONN5 CONN
+UA_RSP CONN5 CONN
+DM_RSP CONN4 ADM
+FRMR_RSP CONN5 CONN
+;
+TABLE RESET_WAIT
+;Transition table for the RESET_WAIT
+;
+;frame type action newstate
+;received
+;
+I_CMD RESWAIT8 RESET_WAIT
+RR_CMD RESWAIT8 RESET_WAIT
+RNR_CMD RESWAIT8 RESET_WAIT
+REJ_CMD RESWAIT8 RESET_WAIT
+DISC_CMD RESWAIT7 RESET_WAIT
+SABME_CMD RESWAIT6 RESET_WAIT
+I_RSP RESWAIT8 RESET_WAIT
+RR_RSP RESWAIT8 RESET_WAIT
+RNR_RSP RESWAIT8 RESET_WAIT
+REJ_RSP RESWAIT8 RESET_WAIT
+UA_RSP RESWAIT8 RESET_WAIT
+DM_RSP RESWAIT5 ADM
+FRMR_RSP RESWAIT8 RESET_WAIT
+;
+;
+TABLE RESET_CHECK
+;Transition table for the RESET_CHECK state
+;
+;frame type action newstate
+;received
+;
+I_CMD RESCHK6 RESET_CHECK
+RR_CMD RESCHK6 RESET_CHECK
+RNR_CMD RESCHK6 RESET_CHECK
+REJ_CMD RESCHK6 RESET_CHECK
+DISC_CMD RESCHK5 ADM
+SABME_CMD RESCHK4 RESET_CHECK
+I_RSP RESCHK6 RESET_CHECK
+RR_RSP RESCHK6 RESET_CHECK
+RNR_RSP RESCHK6 RESET_CHECK
+REJ_RSP RESCHK6 RESET_CHECK
+UA_RSP RESCHK6 RESET_CHECK
+DM_RSP RESCHK3 ADM
+FRMR_RSP RESCHK6 RESET_CHECK
+;
+;
+TABLE SETUP
+;Transition table for the SETUP state
+;
+;frame type p flag action newstate
+;received = f
+;
+I_CMD 0 SETUP6 SETUP
+I_CMD 1 SETUP6 SETUP
+RR_CMD 0 SETUP6 SETUP
+RR_CMD 1 SETUP6 SETUP
+RNR_CMD 0 SETUP6 SETUP
+RNR_CMD 1 SETUP6 SETUP
+REJ_CMD 0 SETUP6 SETUP
+REJ_CMD 1 SETUP6 SETUP
+DISC_CMD 0 SETUP4 ADM
+DISC_CMD 1 SETUP4 ADM
+SABME_CMD 0 SETUP1 SETUP
+SABME_CMD 1 SETUP1 SETUP
+I_RSP 0 SETUP6 SETUP
+I_RSP 1 SETUP6 SETUP
+RR_RSP 0 SETUP6 SETUP
+RR_RSP 1 SETUP6 SETUP
+RNR_RSP 0 SETUP6 SETUP
+RNR_RSP 1 SETUP6 SETUP
+REJ_RSP 0 SETUP6 SETUP
+REJ_RSP 1 SETUP6 SETUP
+UA_RSP 0 SETUP6 SETUP
+UA_RSP 1 SETUP2 NORMAL
+DM_RSP 0 SETUP5 ADM
+DM_RSP 1 SETUP5 ADM
+FRMR_RSP 0 SETUP6 SETUP
+FRMR_RSP 1 SETUP6 SETUP
+;
+;
+TABLE RESET
+;Transition table for the RESET state:
+;
+;frame type p flag action newstate
+;received = f
+;
+I_CMD 0 RESET6 RESET
+I_CMD 1 RESET6 RESET
+RR_CMD 0 RESET6 RESET
+RR_CMD 1 RESET6 RESET
+RNR_CMD 0 RESET6 RESET
+RNR_CMD 1 RESET6 RESET
+REJ_CMD 0 RESET6 RESET
+REJ_CMD 1 RESET6 RESET
+DISC_CMD 0 RESET4 ADM
+DISC_CMD 1 RESET4 ADM
+SABME_CMD 0 RESET1 RESET
+SABME_CMD 1 RESET1 RESET
+I_RSP 0 RESET6 RESET
+I_RSP 1 RESET6 RESET
+RR_RSP 0 RESET6 RESET
+RR_RSP 1 RESET6 RESET
+RNR_RSP 0 RESET6 RESET
+RNR_RSP 1 RESET6 RESET
+REJ_RSP 0 RESET6 RESET
+REJ_RSP 1 RESET6 RESET
+UA_RSP 0 RESET6 RESET
+UA_RSP 1 RESET2 NORMAL
+DM_RSP 0 RESET5 ADM
+DM_RSP 1 RESET5 ADM
+FRMR_RSP 0 RESET6 RESET
+FRMR_RSP 1 RESET6 RESET
+;
+;
+TABLE D_CONN
+;Transition table for the D_CONN state:
+;
+;frame type p bit action newstate
+;received in frame
+I_CMD 0 D_CONN5 D_CONN
+I_CMD 1 D_CONN5 D_CONN
+RR_CMD 0 D_CONN5 D_CONN
+RR_CMD 1 D_CONN5 D_CONN
+RNR_CMD 0 D_CONN5 D_CONN
+RNR_CMD 1 D_CONN5 D_CONN
+REJ_CMD 0 D_CONN5 D_CONN
+REJ_CMD 1 D_CONN5 D_CONN
+DISC_CMD 0 D_CONN3 D_CONN
+DISC_CMD 1 D_CONN3 D_CONN
+SABME_CMD 0 D_CONN1 ADM
+SABME_CMD 1 D_CONN1 ADM
+I_RSP 0 D_CONN5 D_CONN
+I_RSP 1 D_CONN5 D_CONN
+RR_RSP 0 D_CONN5 D_CONN
+RR_RSP 1 D_CONN5 D_CONN
+RNR_RSP 0 D_CONN5 D_CONN
+RNR_RSP 1 D_CONN5 D_CONN
+REJ_RSP 0 D_CONN5 D_CONN
+REJ_RSP 1 D_CONN5 D_CONN
+UA_RSP 0 D_CONN5 D_CONN
+UA_RSP 1 D_CONN4 ADM
+DM_RSP 0 D_CONN4 ADM
+DM_RSP 1 D_CONN5 ADM
+FRMR_RSP 0 D_CONN5 D_CONN
+FRMR_RSP 1 D_CONN5 D_CONN
+;
+;
+TABLE ERROR
+;Transition table for the ERROR state:
+;
+;frame type action newstate
+;received
+;
+I_CMD ERR5 ERROR
+RR_CMD ERR5 ERROR
+RNR_CMD ERR5 ERROR
+REJ_CMD ERR5 ERROR
+DISC_CMD ERR2 ADM
+SABME_CMD ERR1 RESET_CHECK
+I_RSP ERR6 ERROR
+RR_RSP ERR6 ERROR
+RNR_RSP ERR6 ERROR
+REJ_RSP ERR6 ERROR
+UA_RSP ERR6 ERROR
+DM_RSP ERR3 ADM
+FRMR_RSP ERR4 RESET_WAIT
+;
+TABLE NORMAL
+;Transition table for the NORMAL state:
+;
+;frame type uexpect p bit p_flag
+;received N(S) in frame
+;
+I_CMD 0 0 0 NORMAL8B NORMAL
+I_CMD 0 0 1 NORMAL9 NORMAL
+I_CMD 0 1 0 NORMAL10 NORMAL
+I_CMD 0 1 1 NORMAL10 NORMAL
+I_CMD 1 0 0 NORMAL5 REJECT
+I_CMD 1 0 1 NORMAL6 REJECT
+I_CMD 1 1 0 NORMAL7 REJECT
+I_CMD 1 1 1 NORMAL7 REJECT
+RR_CMD 0 0 0 NORMAL11 NORMAL
+RR_CMD 0 0 1 NORMAL11 NORMAL
+RR_CMD 0 1 0 NORMAL12 NORMAL
+RR_CMD 0 1 1 NORMAL12 NORMAL
+RR_CMD 1 0 0 NORMAL11 NORMAL
+RR_CMD 1 0 1 NORMAL11 NORMAL
+RR_CMD 1 1 0 NORMAL12 NORMAL
+RR_CMD 1 1 1 NORMAL12 NORMAL
+RNR_CMD 0 0 0 NORMAL13 NORMAL
+RNR_CMD 0 0 1 NORMAL13 NORMAL
+RNR_CMD 0 1 0 NORMAL14 NORMAL
+RNR_CMD 0 1 1 NORMAL14 NORMAL
+RNR_CMD 1 0 0 NORMAL13 NORMAL
+RNR_CMD 1 0 1 NORMAL13 NORMAL
+RNR_CMD 1 1 0 NORMAL14 NORMAL
+RNR_CMD 1 1 1 NORMAL14 NORMAL
+REJ_CMD 0 0 0 NORMAL15 NORMAL
+REJ_CMD 0 0 1 NORMAL16 NORMAL
+REJ_CMD 0 1 0 NORMAL17 NORMAL
+REJ_CMD 0 1 1 NORMAL17 NORMAL
+REJ_CMD 1 0 0 NORMAL15 NORMAL
+REJ_CMD 1 0 1 NORMAL16 NORMAL
+REJ_CMD 1 1 0 NORMAL17 NORMAL
+REJ_CMD 1 1 1 NORMAL17 NORMAL
+DISC_CMD 0 0 0 SH4 ADM
+DISC_CMD 0 0 1 SH4 ADM
+DISC_CMD 0 1 0 SH4 ADM
+DISC_CMD 0 1 1 SH4 ADM
+DISC_CMD 1 0 0 SH4 ADM
+DISC_CMD 1 0 1 SH4 ADM
+DISC_CMD 1 1 0 SH4 ADM
+DISC_CMD 1 1 1 SH4 ADM
+SABME_CMD 0 0 0 SH3 RESET_CHECK
+SABME_CMD 0 0 1 SH3 RESET_CHECK
+SABME_CMD 0 1 0 SH3 RESET_CHECK
+SABME_CMD 0 1 1 SH3 RESET_CHECK
+SABME_CMD 1 0 0 SH3 RESET_CHECK
+SABME_CMD 1 0 1 SH3 RESET_CHECK
+SABME_CMD 1 1 0 SH3 RESET_CHECK
+SABME_CMD 1 1 1 SH3 RESET_CHECK
+I_RSP 0 0 0 NORMAL8B NORMAL
+I_RSP 0 0 1 NORMAL9 NORMAL
+I_RSP 0 1 0 SH10 ERROR
+I_RSP 0 1 1 NORMAL8A NORMAL
+I_RSP 1 0 0 NORMAL5 REJECT
+I_RSP 1 0 1 NORMAL6 REJECT
+I_RSP 1 1 0 SH10 ERROR
+I_RSP 1 1 1 NORMAL5 REJECT
+RR_RSP 0 0 0 NORMAL11 NORMAL
+RR_RSP 0 0 1 NORMAL11 NORMAL
+RR_RSP 0 1 0 SH10 ERROR
+RR_RSP 0 1 1 NORMAL11 NORMAL
+RR_RSP 1 0 0 NORMAL11 NORMAL
+RR_RSP 1 0 1 NORMAL11 NORMAL
+RR_RSP 1 1 0 SH10 ERROR
+RR_RSP 1 1 1 NORMAL11 NORMAL
+RNR_RSP 0 0 0 NORMAL13 NORMAL
+RNR_RSP 0 0 1 NORMAL13 NORMAL
+RNR_RSP 0 1 0 SH10 ERROR
+RNR_RSP 0 1 1 NORMAL13 NORMAL
+RNR_RSP 1 0 0 NORMAL13 NORMAL
+RNR_RSP 1 0 1 NORMAL13 NORMAL
+RNR_RSP 1 1 0 SH10 ERROR
+RNR_RSP 1 1 1 NORMAL13 NORMAL
+REJ_RSP 0 0 0 NORMAL15 NORMAL
+REJ_RSP 0 0 1 NORMAL16 NORMAL
+REJ_RSP 0 1 0 SH10 ERROR
+REJ_RSP 0 1 1 NORMAL15 NORMAL
+REJ_RSP 1 0 0 NORMAL15 NORMAL
+REJ_RSP 1 0 1 NORMAL16 NORMAL
+REJ_RSP 1 1 0 SH10 ERROR
+REJ_RSP 1 1 1 NORMAL15 NORMAL
+UA_RSP 0 0 0 SH9 ERROR
+UA_RSP 0 0 1 SH9 ERROR
+UA_RSP 0 1 0 SH9 ERROR
+UA_RSP 0 1 1 SH9 ERROR
+UA_RSP 1 0 0 SH9 ERROR
+UA_RSP 1 0 1 SH9 ERROR
+UA_RSP 1 1 0 SH9 ERROR
+UA_RSP 1 1 1 SH9 ERROR
+DM_RSP 0 0 0 SH6 ADM
+DM_RSP 0 0 1 SH6 ADM
+DM_RSP 0 1 0 SH6 ADM
+DM_RSP 0 1 1 SH6 ADM
+DM_RSP 1 0 0 SH6 ADM
+DM_RSP 1 0 1 SH6 ADM
+DM_RSP 1 1 0 SH6 ADM
+DM_RSP 1 1 1 SH6 ADM
+FRMR_RSP 0 0 0 SH5 RESET_WAIT
+FRMR_RSP 0 0 1 SH5 RESET_WAIT
+FRMR_RSP 0 1 0 SH5 RESET_WAIT
+FRMR_RSP 0 1 1 SH5 RESET_WAIT
+FRMR_RSP 1 0 0 SH5 RESET_WAIT
+FRMR_RSP 1 0 1 SH5 RESET_WAIT
+FRMR_RSP 1 1 0 SH5 RESET_WAIT
+FRMR_RSP 1 1 1 SH5 RESET_WAIT
+BAD_FRAME 0 0 0 SH7 ERROR
+BAD_FRAME 0 0 1 SH7 ERROR
+BAD_FRAME 0 1 0 SH7 ERROR
+BAD_FRAME 0 1 1 SH7 ERROR
+BAD_FRAME 1 0 0 SH7 ERROR
+BAD_FRAME 1 0 1 SH7 ERROR
+BAD_FRAME 1 1 0 SH7 ERROR
+BAD_FRAME 1 1 1 SH7 ERROR
+;
+;112 entries in table, 8 modified by tredit4 SABME_CMD x x x SH3 RESET_CHECK
+;112 entries in table, 8 modified by tredit4 DISC_CMD x x x SH4 ADM
+;112 entries in table, 8 modified by tredit4 FRMR_RSP x x x SH5 RESET_WAIT
+;112 entries in table, 8 modified by tredit4 DM_RSP x x x SH6 ADM
+;112 entries in table, 8 modified by tredit4 BAD_FRAME x x x SH7 ERROR
+;112 entries in table, 8 modified by tredit4 UA_RSP x x x SH9 ERROR
+;112 entries in table, 8 modified by tredit4 anyrsp x 1 0 SH10 ERROR
+;112 entries in table, 1 modified by tredit4 I_CMD 1 0 0 NORMAL5 REJECT
+;112 entries in table, 1 modified by tredit4 I_RSP 1 0 0 NORMAL5 REJECT
+;112 entries in table, 1 modified by tredit4 I_RSP 1 1 1 NORMAL5 REJECT
+;112 entries in table, 1 modified by tredit4 I_CMD 1 0 1 NORMAL6 REJECT
+;112 entries in table, 1 modified by tredit4 I_RSP 1 0 1 NORMAL6 REJECT
+;112 entries in table, 2 modified by tredit4 I_CMD 1 1 x NORMAL7 REJECT
+;112 entries in table, 1 modified by tredit4 I_RSP x 1 1 NORMAL8A NORMAL
+;112 entries in table, 1 modified by tredit4 I_RSP x 0 0 NORMAL8B NORMAL
+;112 entries in table, 1 modified by tredit4 I_CMD x 0 0 NORMAL8B NORMAL
+;112 entries in table, 1 modified by tredit4 I_RSP x 0 1 NORMAL9 NORMAL
+;112 entries in table, 1 modified by tredit4 I_CMD x 0 1 NORMAL9 NORMAL
+;112 entries in table, 2 modified by tredit4 I_CMD x 1 x NORMAL10 NORMAL
+;112 entries in table, 4 modified by tredit4 RR_CMD x 0 x NORMAL11 NORMAL
+;112 entries in table, 4 modified by tredit4 RR_RSP x 0 x NORMAL11 NORMAL
+;112 entries in table, 2 modified by tredit4 RR_RSP x 1 1 NORMAL11 NORMAL
+;112 entries in table, 4 modified by tredit4 RR_CMD x 1 x NORMAL12 NORMAL
+;112 entries in table, 4 modified by tredit4 RNR_CMD x 0 x NORMAL13 NORMAL
+;112 entries in table, 4 modified by tredit4 RNR_RSP x 0 x NORMAL13 NORMAL
+;112 entries in table, 2 modified by tredit4 RNR_RSP x 1 1 NORMAL13 NORMAL
+;112 entries in table, 4 modified by tredit4 RNR_CMD x 1 x NORMAL14 NORMAL
+;112 entries in table, 2 modified by tredit4 REJ_CMD x 0 0 NORMAL15 NORMAL
+;112 entries in table, 2 modified by tredit4 REJ_RSP x 1 1 NORMAL15 NORMAL
+;112 entries in table, 2 modified by tredit4 REJ_RSP x 0 0 NORMAL15 NORMAL
+;112 entries in table, 2 modified by tredit4 REJ_CMD x 0 1 NORMAL16 NORMAL
+;112 entries in table, 2 modified by tredit4 REJ_RSP x 0 1 NORMAL16 NORMAL
+;112 entries in table, 4 modified by tredit4 REJ_CMD x 1 x NORMAL17 NORMAL
+;
+TABLE BUSY
+;Transition table for the BUSY state:
+;
+;frame type uexpect p bit p_flag
+;received N(S) in frame
+;
+I_CMD 0 0 0 BUSY13 BUSY
+I_CMD 0 0 1 BUSY14 BUSY
+I_CMD 0 1 0 BUSY12 BUSY
+I_CMD 0 1 1 BUSY12 BUSY
+I_CMD 1 0 0 BUSY9 BUSY
+I_CMD 1 0 1 BUSY10 BUSY
+I_CMD 1 1 0 BUSY11 BUSY
+I_CMD 1 1 1 BUSY11 BUSY
+RR_CMD 0 0 0 BUSY15 BUSY
+RR_CMD 0 0 1 BUSY15 BUSY
+RR_CMD 0 1 0 BUSY16 BUSY
+RR_CMD 0 1 1 BUSY16 BUSY
+RR_CMD 1 0 0 BUSY15 BUSY
+RR_CMD 1 0 1 BUSY15 BUSY
+RR_CMD 1 1 0 BUSY16 BUSY
+RR_CMD 1 1 1 BUSY16 BUSY
+RNR_CMD 0 0 0 BUSY17 BUSY
+RNR_CMD 0 0 1 BUSY17 BUSY
+RNR_CMD 0 1 0 BUSY18 BUSY
+RNR_CMD 0 1 1 BUSY18 BUSY
+RNR_CMD 1 0 0 BUSY17 BUSY
+RNR_CMD 1 0 1 BUSY17 BUSY
+RNR_CMD 1 1 0 BUSY18 BUSY
+RNR_CMD 1 1 1 BUSY18 BUSY
+REJ_CMD 0 0 0 BUSY19 BUSY
+REJ_CMD 0 0 1 BUSY20 BUSY
+REJ_CMD 0 1 0 BUSY21 BUSY
+REJ_CMD 0 1 1 BUSY21 BUSY
+REJ_CMD 1 0 0 BUSY19 BUSY
+REJ_CMD 1 0 1 BUSY20 BUSY
+REJ_CMD 1 1 0 BUSY21 BUSY
+REJ_CMD 1 1 1 BUSY21 BUSY
+DISC_CMD 0 0 0 SH4 ADM
+DISC_CMD 0 0 1 SH4 ADM
+DISC_CMD 0 1 0 SH4 ADM
+DISC_CMD 0 1 1 SH4 ADM
+DISC_CMD 1 0 0 SH4 ADM
+DISC_CMD 1 0 1 SH4 ADM
+DISC_CMD 1 1 0 SH4 ADM
+DISC_CMD 1 1 1 SH4 ADM
+SABME_CMD 0 0 0 SH3 RESET_CHECK
+SABME_CMD 0 0 1 SH3 RESET_CHECK
+SABME_CMD 0 1 0 SH3 RESET_CHECK
+SABME_CMD 0 1 1 SH3 RESET_CHECK
+SABME_CMD 1 0 0 SH3 RESET_CHECK
+SABME_CMD 1 0 1 SH3 RESET_CHECK
+SABME_CMD 1 1 0 SH3 RESET_CHECK
+SABME_CMD 1 1 1 SH3 RESET_CHECK
+I_RSP 0 0 0 BUSY13 BUSY
+I_RSP 0 0 1 BUSY14 BUSY
+I_RSP 0 1 0 SH10 ERROR
+I_RSP 0 1 1 BUSY13 BUSY
+I_RSP 1 0 0 BUSY9 BUSY
+I_RSP 1 0 1 BUSY10 BUSY
+I_RSP 1 1 0 SH10 ERROR
+I_RSP 1 1 1 BUSY9 BUSY
+RR_RSP 0 0 0 BUSY15 BUSY
+RR_RSP 0 0 1 BUSY15 BUSY
+RR_RSP 0 1 0 SH10 ERROR
+RR_RSP 0 1 1 BUSY15 BUSY
+RR_RSP 1 0 0 BUSY15 BUSY
+RR_RSP 1 0 1 BUSY15 BUSY
+RR_RSP 1 1 0 SH10 ERROR
+RR_RSP 1 1 1 BUSY15 BUSY
+RNR_RSP 0 0 0 BUSY17 BUSY
+RNR_RSP 0 0 1 BUSY17 BUSY
+RNR_RSP 0 1 0 SH10 ERROR
+RNR_RSP 0 1 1 BUSY17 BUSY
+RNR_RSP 1 0 0 BUSY17 BUSY
+RNR_RSP 1 0 1 BUSY17 BUSY
+RNR_RSP 1 1 0 SH10 ERROR
+RNR_RSP 1 1 1 BUSY17 BUSY
+REJ_RSP 0 0 0 BUSY19 BUSY
+REJ_RSP 0 0 1 BUSY20 BUSY
+REJ_RSP 0 1 0 SH10 ERROR
+REJ_RSP 0 1 1 BUSY19 BUSY
+REJ_RSP 1 0 0 BUSY19 BUSY
+REJ_RSP 1 0 1 BUSY20 BUSY
+REJ_RSP 1 1 0 SH10 ERROR
+REJ_RSP 1 1 1 BUSY19 BUSY
+UA_RSP 0 0 0 SH9 ERROR
+UA_RSP 0 0 1 SH9 ERROR
+UA_RSP 0 1 0 SH9 ERROR
+UA_RSP 0 1 1 SH9 ERROR
+UA_RSP 1 0 0 SH9 ERROR
+UA_RSP 1 0 1 SH9 ERROR
+UA_RSP 1 1 0 SH9 ERROR
+UA_RSP 1 1 1 SH9 ERROR
+DM_RSP 0 0 0 SH6 ADM
+DM_RSP 0 0 1 SH6 ADM
+DM_RSP 0 1 0 SH6 ADM
+DM_RSP 0 1 1 SH6 ADM
+DM_RSP 1 0 0 SH6 ADM
+DM_RSP 1 0 1 SH6 ADM
+DM_RSP 1 1 0 SH6 ADM
+DM_RSP 1 1 1 SH6 ADM
+FRMR_RSP 0 0 0 SH5 RESET_WAIT
+FRMR_RSP 0 0 1 SH5 RESET_WAIT
+FRMR_RSP 0 1 0 SH5 RESET_WAIT
+FRMR_RSP 0 1 1 SH5 RESET_WAIT
+FRMR_RSP 1 0 0 SH5 RESET_WAIT
+FRMR_RSP 1 0 1 SH5 RESET_WAIT
+FRMR_RSP 1 1 0 SH5 RESET_WAIT
+FRMR_RSP 1 1 1 SH5 RESET_WAIT
+BAD_FRAME 0 0 0 SH7 ERROR
+BAD_FRAME 0 0 1 SH7 ERROR
+BAD_FRAME 0 1 0 SH7 ERROR
+BAD_FRAME 0 1 1 SH7 ERROR
+BAD_FRAME 1 0 0 SH7 ERROR
+BAD_FRAME 1 0 1 SH7 ERROR
+BAD_FRAME 1 1 0 SH7 ERROR
+BAD_FRAME 1 1 1 SH7 ERROR
+;
+;112 entries in table, 8 modified by tredit4 SABME_CMD x x x SH3 RESET_CHECK
+;112 entries in table, 8 modified by tredit4 DISC_CMD x x x SH4 ADM
+;112 entries in table, 8 modified by tredit4 FRMR_RSP x x x SH5 RESET_WAIT
+;112 entries in table, 8 modified by tredit4 DM_RSP x x x SH6 ADM
+;112 entries in table, 8 modified by tredit4 BAD_FRAME x x x SH7 ERROR
+;112 entries in table, 8 modified by tredit4 UA_RSP x x x SH9 ERROR
+;112 entries in table, 8 modified by tredit4 anyrsp x 1 0 SH10 ERROR
+;112 entries in table, 1 modified by tredit4 I_RSP 1 0 0 BUSY9 BUSY
+;112 entries in table, 1 modified by tredit4 I_RSP 1 1 1 BUSY9 BUSY
+;112 entries in table, 1 modified by tredit4 I_CMD 1 0 0 BUSY9 BUSY
+;112 entries in table, 1 modified by tredit4 I_RSP 1 0 1 BUSY10 BUSY
+;112 entries in table, 1 modified by tredit4 I_CMD 1 0 1 BUSY10 BUSY
+;112 entries in table, 2 modified by tredit4 I_CMD 1 1 x BUSY11 BUSY
+;112 entries in table, 2 modified by tredit4 I_CMD x 1 x BUSY12 BUSY
+;112 entries in table, 1 modified by tredit4 I_RSP x 0 0 BUSY13 BUSY
+;112 entries in table, 1 modified by tredit4 I_RSP x 1 1 BUSY13 BUSY
+;112 entries in table, 1 modified by tredit4 I_CMD x 0 0 BUSY13 BUSY
+;112 entries in table, 1 modified by tredit4 I_RSP x 0 1 BUSY14 BUSY
+;112 entries in table, 1 modified by tredit4 I_CMD x 0 1 BUSY14 BUSY
+;112 entries in table, 4 modified by tredit4 RR_CMD x 0 x BUSY15 BUSY
+;112 entries in table, 4 modified by tredit4 RR_RSP x 0 x BUSY15 BUSY
+;112 entries in table, 2 modified by tredit4 RR_RSP x 1 1 BUSY15 BUSY
+;112 entries in table, 4 modified by tredit4 RR_CMD x 1 x BUSY16 BUSY
+;112 entries in table, 4 modified by tredit4 RNR_CMD x 0 x BUSY17 BUSY
+;112 entries in table, 4 modified by tredit4 RNR_RSP x 0 x BUSY17 BUSY
+;112 entries in table, 2 modified by tredit4 RNR_RSP x 1 1 BUSY17 BUSY
+;112 entries in table, 4 modified by tredit4 RNR_CMD x 1 x BUSY18 BUSY
+;112 entries in table, 2 modified by tredit4 REJ_CMD x 0 0 BUSY19 BUSY
+;112 entries in table, 2 modified by tredit4 REJ_RSP x 0 0 BUSY19 BUSY
+;112 entries in table, 2 modified by tredit4 REJ_RSP x 1 1 BUSY19 BUSY
+;112 entries in table, 2 modified by tredit4 REJ_CMD x 0 1 BUSY20 BUSY
+;112 entries in table, 2 modified by tredit4 REJ_RSP x 0 1 BUSY20 BUSY
+;112 entries in table, 4 modified by tredit4 REJ_CMD x 1 x BUSY21 BUSY
+;
+TABLE REJECT
+;Transition table for the REJECT state:
+;
+;frame type uexpect p bit p_flag
+;received N(S) in frame
+;
+I_CMD 0 0 0 REJECT7 REJECT
+I_CMD 0 0 1 REJECT8 REJECT
+I_CMD 0 1 0 REJECT9 REJECT
+I_CMD 0 1 1 REJECT9 REJECT
+I_CMD 1 0 0 REJECT5 REJECT
+I_CMD 1 0 1 REJECT5 REJECT
+I_CMD 1 1 0 REJECT6 REJECT
+I_CMD 1 1 1 REJECT6 REJECT
+RR_CMD 0 0 0 REJECT10 REJECT
+RR_CMD 0 0 1 REJECT10 REJECT
+RR_CMD 0 1 0 REJECT11 REJECT
+RR_CMD 0 1 1 REJECT11 REJECT
+RR_CMD 1 0 0 REJECT10 REJECT
+RR_CMD 1 0 1 REJECT10 REJECT
+RR_CMD 1 1 0 REJECT11 REJECT
+RR_CMD 1 1 1 REJECT11 REJECT
+RNR_CMD 0 0 0 REJECT12 REJECT
+RNR_CMD 0 0 1 REJECT12 REJECT
+RNR_CMD 0 1 0 REJECT13 REJECT
+RNR_CMD 0 1 1 REJECT13 REJECT
+RNR_CMD 1 0 0 REJECT12 REJECT
+RNR_CMD 1 0 1 REJECT12 REJECT
+RNR_CMD 1 1 0 REJECT13 REJECT
+RNR_CMD 1 1 1 REJECT13 REJECT
+REJ_CMD 0 0 0 REJECT14 REJECT
+REJ_CMD 0 0 1 REJECT15 REJECT
+REJ_CMD 0 1 0 REJECT16 REJECT
+REJ_CMD 0 1 1 REJECT16 REJECT
+REJ_CMD 1 0 0 REJECT14 REJECT
+REJ_CMD 1 0 1 REJECT15 REJECT
+REJ_CMD 1 1 0 REJECT16 REJECT
+REJ_CMD 1 1 1 REJECT16 REJECT
+DISC_CMD 0 0 0 SH4 ADM
+DISC_CMD 0 0 1 SH4 ADM
+DISC_CMD 0 1 0 SH4 ADM
+DISC_CMD 0 1 1 SH4 ADM
+DISC_CMD 1 0 0 SH4 ADM
+DISC_CMD 1 0 1 SH4 ADM
+DISC_CMD 1 1 0 SH4 ADM
+DISC_CMD 1 1 1 SH4 ADM
+SABME_CMD 0 0 0 SH3 RESET_CHECK
+SABME_CMD 0 0 1 SH3 RESET_CHECK
+SABME_CMD 0 1 0 SH3 RESET_CHECK
+SABME_CMD 0 1 1 SH3 RESET_CHECK
+SABME_CMD 1 0 0 SH3 RESET_CHECK
+SABME_CMD 1 0 1 SH3 RESET_CHECK
+SABME_CMD 1 1 0 SH3 RESET_CHECK
+SABME_CMD 1 1 1 SH3 RESET_CHECK
+I_RSP 0 0 0 REJECT7 REJECT
+I_RSP 0 0 1 REJECT8 REJECT
+I_RSP 0 1 0 SH10 ERROR
+I_RSP 0 1 1 REJECT7 REJECT
+I_RSP 1 0 0 REJECT5 REJECT
+I_RSP 1 0 1 REJECT5 REJECT
+I_RSP 1 1 0 SH10 ERROR
+I_RSP 1 1 1 REJECT5 REJECT
+RR_RSP 0 0 0 REJECT10 REJECT
+RR_RSP 0 0 1 REJECT10 REJECT
+RR_RSP 0 1 0 SH10 ERROR
+RR_RSP 0 1 1 REJECT10 REJECT
+RR_RSP 1 0 0 REJECT10 REJECT
+RR_RSP 1 0 1 REJECT10 REJECT
+RR_RSP 1 1 0 SH10 ERROR
+RR_RSP 1 1 1 REJECT10 REJECT
+RNR_RSP 0 0 0 REJECT12 REJECT
+RNR_RSP 0 0 1 REJECT12 REJECT
+RNR_RSP 0 1 0 SH10 ERROR
+RNR_RSP 0 1 1 REJECT12 REJECT
+RNR_RSP 1 0 0 REJECT12 REJECT
+RNR_RSP 1 0 1 REJECT12 REJECT
+RNR_RSP 1 1 0 SH10 ERROR
+RNR_RSP 1 1 1 REJECT12 REJECT
+REJ_RSP 0 0 0 REJECT14 REJECT
+REJ_RSP 0 0 1 REJECT15 REJECT
+REJ_RSP 0 1 0 SH10 ERROR
+REJ_RSP 0 1 1 REJECT14 REJECT
+REJ_RSP 1 0 0 REJECT14 REJECT
+REJ_RSP 1 0 1 REJECT15 REJECT
+REJ_RSP 1 1 0 SH10 ERROR
+REJ_RSP 1 1 1 REJECT14 REJECT
+UA_RSP 0 0 0 SH9 ERROR
+UA_RSP 0 0 1 SH9 ERROR
+UA_RSP 0 1 0 SH9 ERROR
+UA_RSP 0 1 1 SH9 ERROR
+UA_RSP 1 0 0 SH9 ERROR
+UA_RSP 1 0 1 SH9 ERROR
+UA_RSP 1 1 0 SH9 ERROR
+UA_RSP 1 1 1 SH9 ERROR
+DM_RSP 0 0 0 SH6 ADM
+DM_RSP 0 0 1 SH6 ADM
+DM_RSP 0 1 0 SH6 ADM
+DM_RSP 0 1 1 SH6 ADM
+DM_RSP 1 0 0 SH6 ADM
+DM_RSP 1 0 1 SH6 ADM
+DM_RSP 1 1 0 SH6 ADM
+DM_RSP 1 1 1 SH6 ADM
+FRMR_RSP 0 0 0 SH5 RESET_WAIT
+FRMR_RSP 0 0 1 SH5 RESET_WAIT
+FRMR_RSP 0 1 0 SH5 RESET_WAIT
+FRMR_RSP 0 1 1 SH5 RESET_WAIT
+FRMR_RSP 1 0 0 SH5 RESET_WAIT
+FRMR_RSP 1 0 1 SH5 RESET_WAIT
+FRMR_RSP 1 1 0 SH5 RESET_WAIT
+FRMR_RSP 1 1 1 SH5 RESET_WAIT
+BAD_FRAME 0 0 0 SH7 ERROR
+BAD_FRAME 0 0 1 SH7 ERROR
+BAD_FRAME 0 1 0 SH7 ERROR
+BAD_FRAME 0 1 1 SH7 ERROR
+BAD_FRAME 1 0 0 SH7 ERROR
+BAD_FRAME 1 0 1 SH7 ERROR
+BAD_FRAME 1 1 0 SH7 ERROR
+BAD_FRAME 1 1 1 SH7 ERROR
+;
+;112 entries in table, 8 modified by tredit4 SABME_CMD x x x SH3 RESET_CHECK
+;112 entries in table, 8 modified by tredit4 DISC_CMD x x x SH4 ADM
+;112 entries in table, 8 modified by tredit4 FRMR_RSP x x x SH5 RESET_WAIT
+;112 entries in table, 8 modified by tredit4 DM_RSP x x x SH6 ADM
+;112 entries in table, 8 modified by tredit4 BAD_FRAME x x x SH7 ERROR
+;112 entries in table, 8 modified by tredit4 UA_RSP x x x SH9 ERROR
+;112 entries in table, 8 modified by tredit4 anyrsp x 1 0 SH10 ERROR
+;112 entries in table, 2 modified by tredit4 I_CMD 1 0 x REJECT5 REJECT
+;112 entries in table, 2 modified by tredit4 I_RSP 1 0 x REJECT5 REJECT
+;112 entries in table, 1 modified by tredit4 I_RSP 1 1 1 REJECT5 REJECT
+;112 entries in table, 2 modified by tredit4 I_CMD 1 1 x REJECT6 REJECT
+;112 entries in table, 1 modified by tredit4 I_RSP x 1 1 REJECT7 REJECT
+;112 entries in table, 1 modified by tredit4 I_RSP x 0 0 REJECT7 REJECT
+;112 entries in table, 1 modified by tredit4 I_CMD x 0 0 REJECT7 REJECT
+;112 entries in table, 1 modified by tredit4 I_RSP x 0 1 REJECT8 REJECT
+;112 entries in table, 1 modified by tredit4 I_CMD x 0 1 REJECT8 REJECT
+;112 entries in table, 2 modified by tredit4 I_CMD x 1 x REJECT9 REJECT
+;112 entries in table, 4 modified by tredit4 RR_CMD x 0 x REJECT10 REJECT
+;112 entries in table, 4 modified by tredit4 RR_RSP x 0 x REJECT10 REJECT
+;112 entries in table, 2 modified by tredit4 RR_RSP x 1 1 REJECT10 REJECT
+;112 entries in table, 4 modified by tredit4 RR_CMD x 1 x REJECT11 REJECT
+;112 entries in table, 4 modified by tredit4 RNR_CMD x 0 x REJECT12 REJECT
+;112 entries in table, 4 modified by tredit4 RNR_RSP x 0 x REJECT12 REJECT
+;112 entries in table, 2 modified by tredit4 RNR_RSP x 1 1 REJECT12 REJECT
+;112 entries in table, 4 modified by tredit4 RNR_CMD x 1 x REJECT13 REJECT
+;112 entries in table, 2 modified by tredit4 REJ_CMD x 0 0 REJECT14 REJECT
+;112 entries in table, 2 modified by tredit4 REJ_RSP x 0 0 REJECT14 REJECT
+;112 entries in table, 2 modified by tredit4 REJ_RSP x 1 1 REJECT14 REJECT
+;112 entries in table, 2 modified by tredit4 REJ_CMD x 0 1 REJECT15 REJECT
+;112 entries in table, 2 modified by tredit4 REJ_RSP x 0 1 REJECT15 REJECT
+;112 entries in table, 4 modified by tredit4 REJ_CMD x 1 x REJECT16 REJECT
+;
+TABLE AWAIT
+;Transition table for the AWAIT state:
+;
+;frame type uexpect p bit p_flag
+;received N(S) in frame
+;
+I_CMD 0 0 0 AWAIT6 AWAIT
+I_CMD 0 0 1 AWAIT6 AWAIT
+I_CMD 0 1 0 AWAIT7 AWAIT
+I_CMD 0 1 1 AWAIT7 AWAIT
+I_CMD 1 0 0 AWAIT3 AWAIT_REJECT
+I_CMD 1 0 1 AWAIT3 AWAIT_REJECT
+I_CMD 1 1 0 AWAIT4 AWAIT_REJECT
+I_CMD 1 1 1 AWAIT4 AWAIT_REJECT
+RR_CMD 0 0 0 AWAIT9 AWAIT
+RR_CMD 0 0 1 AWAIT9 AWAIT
+RR_CMD 0 1 0 AWAIT10 AWAIT
+RR_CMD 0 1 1 AWAIT10 AWAIT
+RR_CMD 1 0 0 AWAIT9 AWAIT
+RR_CMD 1 0 1 AWAIT9 AWAIT
+RR_CMD 1 1 0 AWAIT10 AWAIT
+RR_CMD 1 1 1 AWAIT10 AWAIT
+RNR_CMD 0 0 0 AWAIT12 AWAIT
+RNR_CMD 0 0 1 AWAIT12 AWAIT
+RNR_CMD 0 1 0 AWAIT13 AWAIT
+RNR_CMD 0 1 1 AWAIT13 AWAIT
+RNR_CMD 1 0 0 AWAIT12 AWAIT
+RNR_CMD 1 0 1 AWAIT12 AWAIT
+RNR_CMD 1 1 0 AWAIT13 AWAIT
+RNR_CMD 1 1 1 AWAIT13 AWAIT
+REJ_CMD 0 0 0 AWAIT9 AWAIT
+REJ_CMD 0 0 1 AWAIT9 AWAIT
+REJ_CMD 0 1 0 AWAIT10 AWAIT
+REJ_CMD 0 1 1 AWAIT10 AWAIT
+REJ_CMD 1 0 0 AWAIT9 AWAIT
+REJ_CMD 1 0 1 AWAIT9 AWAIT
+REJ_CMD 1 1 0 AWAIT10 AWAIT
+REJ_CMD 1 1 1 AWAIT10 AWAIT
+DISC_CMD 0 0 0 SH4 ADM
+DISC_CMD 0 0 1 SH4 ADM
+DISC_CMD 0 1 0 SH4 ADM
+DISC_CMD 0 1 1 SH4 ADM
+DISC_CMD 1 0 0 SH4 ADM
+DISC_CMD 1 0 1 SH4 ADM
+DISC_CMD 1 1 0 SH4 ADM
+DISC_CMD 1 1 1 SH4 ADM
+SABME_CMD 0 0 0 SH3 RESET_CHECK
+SABME_CMD 0 0 1 SH3 RESET_CHECK
+SABME_CMD 0 1 0 SH3 RESET_CHECK
+SABME_CMD 0 1 1 SH3 RESET_CHECK
+SABME_CMD 1 0 0 SH3 RESET_CHECK
+SABME_CMD 1 0 1 SH3 RESET_CHECK
+SABME_CMD 1 1 0 SH3 RESET_CHECK
+SABME_CMD 1 1 1 SH3 RESET_CHECK
+I_RSP 0 0 0 AWAIT6 AWAIT
+I_RSP 0 0 1 AWAIT6 AWAIT
+I_RSP 0 1 0 SH10 ERROR
+I_RSP 0 1 1 AWAIT5 NORMAL
+I_RSP 1 0 0 AWAIT3 AWAIT_REJECT
+I_RSP 1 0 1 AWAIT3 AWAIT_REJECT
+I_RSP 1 1 0 SH10 ERROR
+I_RSP 1 1 1 AWAIT2 REJECT
+RR_RSP 0 0 0 AWAIT9 AWAIT
+RR_RSP 0 0 1 AWAIT9 AWAIT
+RR_RSP 0 1 0 SH10 ERROR
+RR_RSP 0 1 1 AWAIT8 AWAIT
+RR_RSP 1 0 0 AWAIT9 AWAIT
+RR_RSP 1 0 1 AWAIT9 AWAIT
+RR_RSP 1 1 0 SH10 ERROR
+RR_RSP 1 1 1 AWAIT8 AWAIT
+RNR_RSP 0 0 0 AWAIT12 AWAIT
+RNR_RSP 0 0 1 AWAIT12 AWAIT
+RNR_RSP 0 1 0 SH10 ERROR
+RNR_RSP 0 1 1 AWAIT11 AWAIT
+RNR_RSP 1 0 0 AWAIT12 AWAIT
+RNR_RSP 1 0 1 AWAIT12 AWAIT
+RNR_RSP 1 1 0 SH10 ERROR
+RNR_RSP 1 1 1 AWAIT11 AWAIT
+REJ_RSP 0 0 0 AWAIT9 AWAIT
+REJ_RSP 0 0 1 AWAIT9 AWAIT
+REJ_RSP 0 1 0 SH10 ERROR
+REJ_RSP 0 1 1 AWAIT8 AWAIT
+REJ_RSP 1 0 0 AWAIT9 AWAIT
+REJ_RSP 1 0 1 AWAIT9 AWAIT
+REJ_RSP 1 1 0 SH10 ERROR
+REJ_RSP 1 1 1 AWAIT8 AWAIT
+UA_RSP 0 0 0 SH9 ERROR
+UA_RSP 0 0 1 SH9 ERROR
+UA_RSP 0 1 0 SH9 ERROR
+UA_RSP 0 1 1 SH9 ERROR
+UA_RSP 1 0 0 SH9 ERROR
+UA_RSP 1 0 1 SH9 ERROR
+UA_RSP 1 1 0 SH9 ERROR
+UA_RSP 1 1 1 SH9 ERROR
+DM_RSP 0 0 0 SH6 ADM
+DM_RSP 0 0 1 SH6 ADM
+DM_RSP 0 1 0 SH6 ADM
+DM_RSP 0 1 1 SH6 ADM
+DM_RSP 1 0 0 SH6 ADM
+DM_RSP 1 0 1 SH6 ADM
+DM_RSP 1 1 0 SH6 ADM
+DM_RSP 1 1 1 SH6 ADM
+FRMR_RSP 0 0 0 SH5 RESET_WAIT
+FRMR_RSP 0 0 1 SH5 RESET_WAIT
+FRMR_RSP 0 1 0 SH5 RESET_WAIT
+FRMR_RSP 0 1 1 SH5 RESET_WAIT
+FRMR_RSP 1 0 0 SH5 RESET_WAIT
+FRMR_RSP 1 0 1 SH5 RESET_WAIT
+FRMR_RSP 1 1 0 SH5 RESET_WAIT
+FRMR_RSP 1 1 1 SH5 RESET_WAIT
+BAD_FRAME 0 0 0 SH7 ERROR
+BAD_FRAME 0 0 1 SH7 ERROR
+BAD_FRAME 0 1 0 SH7 ERROR
+BAD_FRAME 0 1 1 SH7 ERROR
+BAD_FRAME 1 0 0 SH7 ERROR
+BAD_FRAME 1 0 1 SH7 ERROR
+BAD_FRAME 1 1 0 SH7 ERROR
+BAD_FRAME 1 1 1 SH7 ERROR
+;
+;112 entries in table, 8 modified by tredit4 SABME_CMD x x x SH3 RESET_CHECK
+;112 entries in table, 8 modified by tredit4 DISC_CMD x x x SH4 ADM
+;112 entries in table, 8 modified by tredit4 FRMR_RSP x x x SH5 RESET_WAIT
+;112 entries in table, 8 modified by tredit4 DM_RSP x x x SH6 ADM
+;112 entries in table, 8 modified by tredit4 BAD_FRAME x x x SH7 ERROR
+;112 entries in table, 8 modified by tredit4 UA_RSP x x x SH9 ERROR
+;112 entries in table, 8 modified by tredit4 anyrsp x 1 0 SH10 ERROR
+;112 entries in table, 1 modified by tredit4 I_RSP 1 1 x AWAIT2 REJECT
+;112 entries in table, 2 modified by tredit4 I_CMD 1 0 x AWAIT3 AWAIT_REJECT
+;112 entries in table, 2 modified by tredit4 I_RSP 1 0 x AWAIT3 AWAIT_REJECT
+;112 entries in table, 2 modified by tredit4 I_CMD 1 1 x AWAIT4 AWAIT_REJECT
+;112 entries in table, 1 modified by tredit4 I_RSP x 1 x AWAIT5 NORMAL
+;112 entries in table, 2 modified by tredit4 I_RSP x 0 x AWAIT6 AWAIT
+;112 entries in table, 2 modified by tredit4 I_CMD x 0 x AWAIT6 AWAIT
+;112 entries in table, 2 modified by tredit4 I_CMD x 1 x AWAIT7 AWAIT
+;112 entries in table, 2 modified by tredit4 RR_RSP x 1 x AWAIT8 AWAIT
+;112 entries in table, 2 modified by tredit4 REJ_RSP x 1 x AWAIT8 AWAIT
+;112 entries in table, 4 modified by tredit4 RR_CMD x 0 x AWAIT9 AWAIT
+;112 entries in table, 4 modified by tredit4 RR_RSP x 0 x AWAIT9 AWAIT
+;112 entries in table, 4 modified by tredit4 REJ_CMD x 0 x AWAIT9 AWAIT
+;112 entries in table, 4 modified by tredit4 REJ_RSP x 0 x AWAIT9 AWAIT
+;112 entries in table, 4 modified by tredit4 RR_CMD x 1 x AWAIT10 AWAIT
+;112 entries in table, 4 modified by tredit4 REJ_CMD x 1 x AWAIT10 AWAIT
+;112 entries in table, 2 modified by tredit4 RNR_RSP x 1 x AWAIT11 AWAIT
+;112 entries in table, 4 modified by tredit4 RNR_CMD x 0 x AWAIT12 AWAIT
+;112 entries in table, 4 modified by tredit4 RNR_RSP x 0 x AWAIT12 AWAIT
+;112 entries in table, 4 modified by tredit4 RNR_CMD x 1 x AWAIT13 AWAIT
+;
+TABLE AWAIT_BUSY
+;Transition table for the AWAIT_BUSY state:
+;
+;frame type uexpect p bit p_flag
+;received N(S) in frame
+;
+I_CMD 0 0 0 AWAIT_BUSY8 AWAIT_BUSY
+I_CMD 0 0 1 AWAIT_BUSY8 AWAIT_BUSY
+I_CMD 0 1 0 AWAIT_BUSY9 AWAIT_BUSY
+I_CMD 0 1 1 AWAIT_BUSY9 AWAIT_BUSY
+I_CMD 1 0 0 AWAIT_BUSY5 AWAIT_BUSY
+I_CMD 1 0 1 AWAIT_BUSY5 AWAIT_BUSY
+I_CMD 1 1 0 AWAIT_BUSY6 AWAIT_BUSY
+I_CMD 1 1 1 AWAIT_BUSY6 AWAIT_BUSY
+RR_CMD 0 0 0 AWAIT_BUSY11 AWAIT_BUSY
+RR_CMD 0 0 1 AWAIT_BUSY11 AWAIT_BUSY
+RR_CMD 0 1 0 AWAIT_BUSY12 AWAIT_BUSY
+RR_CMD 0 1 1 AWAIT_BUSY12 AWAIT_BUSY
+RR_CMD 1 0 0 AWAIT_BUSY11 AWAIT_BUSY
+RR_CMD 1 0 1 AWAIT_BUSY11 AWAIT_BUSY
+RR_CMD 1 1 0 AWAIT_BUSY12 AWAIT_BUSY
+RR_CMD 1 1 1 AWAIT_BUSY12 AWAIT_BUSY
+RNR_CMD 0 0 0 AWAIT_BUSY14 AWAIT_BUSY
+RNR_CMD 0 0 1 AWAIT_BUSY14 AWAIT_BUSY
+RNR_CMD 0 1 0 AWAIT_BUSY15 AWAIT_BUSY
+RNR_CMD 0 1 1 AWAIT_BUSY15 AWAIT_BUSY
+RNR_CMD 1 0 0 AWAIT_BUSY14 AWAIT_BUSY
+RNR_CMD 1 0 1 AWAIT_BUSY14 AWAIT_BUSY
+RNR_CMD 1 1 0 AWAIT_BUSY15 AWAIT_BUSY
+RNR_CMD 1 1 1 AWAIT_BUSY15 AWAIT_BUSY
+REJ_CMD 0 0 0 AWAIT_BUSY11 AWAIT_BUSY
+REJ_CMD 0 0 1 AWAIT_BUSY11 AWAIT_BUSY
+REJ_CMD 0 1 0 AWAIT_BUSY12 AWAIT_BUSY
+REJ_CMD 0 1 1 AWAIT_BUSY12 AWAIT_BUSY
+REJ_CMD 1 0 0 AWAIT_BUSY11 AWAIT_BUSY
+REJ_CMD 1 0 1 AWAIT_BUSY11 AWAIT_BUSY
+REJ_CMD 1 1 0 AWAIT_BUSY12 AWAIT_BUSY
+REJ_CMD 1 1 1 AWAIT_BUSY12 AWAIT_BUSY
+DISC_CMD 0 0 0 SH4 ADM
+DISC_CMD 0 0 1 SH4 ADM
+DISC_CMD 0 1 0 SH4 ADM
+DISC_CMD 0 1 1 SH4 ADM
+DISC_CMD 1 0 0 SH4 ADM
+DISC_CMD 1 0 1 SH4 ADM
+DISC_CMD 1 1 0 SH4 ADM
+DISC_CMD 1 1 1 SH4 ADM
+SABME_CMD 0 0 0 SH3 RESET_CHECK
+SABME_CMD 0 0 1 SH3 RESET_CHECK
+SABME_CMD 0 1 0 SH3 RESET_CHECK
+SABME_CMD 0 1 1 SH3 RESET_CHECK
+SABME_CMD 1 0 0 SH3 RESET_CHECK
+SABME_CMD 1 0 1 SH3 RESET_CHECK
+SABME_CMD 1 1 0 SH3 RESET_CHECK
+SABME_CMD 1 1 1 SH3 RESET_CHECK
+I_RSP 0 0 0 AWAIT_BUSY8 AWAIT_BUSY
+I_RSP 0 0 1 AWAIT_BUSY8 AWAIT_BUSY
+I_RSP 0 1 0 SH10 ERROR
+I_RSP 0 1 1 AWAIT_BUSY7 BUSY
+I_RSP 1 0 0 AWAIT_BUSY5 AWAIT_BUSY
+I_RSP 1 0 1 AWAIT_BUSY5 AWAIT_BUSY
+I_RSP 1 1 0 SH10 ERROR
+I_RSP 1 1 1 AWAIT_BUSY4 BUSY
+RR_RSP 0 0 0 AWAIT_BUSY11 AWAIT_BUSY
+RR_RSP 0 0 1 AWAIT_BUSY11 AWAIT_BUSY
+RR_RSP 0 1 0 SH10 ERROR
+RR_RSP 0 1 1 AWAIT_BUSY10 BUSY
+RR_RSP 1 0 0 AWAIT_BUSY11 AWAIT_BUSY
+RR_RSP 1 0 1 AWAIT_BUSY11 AWAIT_BUSY
+RR_RSP 1 1 0 SH10 ERROR
+RR_RSP 1 1 1 AWAIT_BUSY10 BUSY
+RNR_RSP 0 0 0 AWAIT_BUSY14 AWAIT_BUSY
+RNR_RSP 0 0 1 AWAIT_BUSY14 AWAIT_BUSY
+RNR_RSP 0 1 0 SH10 ERROR
+RNR_RSP 0 1 1 AWAIT_BUSY13 BUSY
+RNR_RSP 1 0 0 AWAIT_BUSY14 AWAIT_BUSY
+RNR_RSP 1 0 1 AWAIT_BUSY14 AWAIT_BUSY
+RNR_RSP 1 1 0 SH10 ERROR
+RNR_RSP 1 1 1 AWAIT_BUSY13 BUSY
+REJ_RSP 0 0 0 AWAIT_BUSY11 AWAIT_BUSY
+REJ_RSP 0 0 1 AWAIT_BUSY11 AWAIT_BUSY
+REJ_RSP 0 1 0 SH10 ERROR
+REJ_RSP 0 1 1 AWAIT_BUSY10 BUSY
+REJ_RSP 1 0 0 AWAIT_BUSY11 AWAIT_BUSY
+REJ_RSP 1 0 1 AWAIT_BUSY11 AWAIT_BUSY
+REJ_RSP 1 1 0 SH10 ERROR
+REJ_RSP 1 1 1 AWAIT_BUSY10 BUSY
+UA_RSP 0 0 0 SH9 ERROR
+UA_RSP 0 0 1 SH9 ERROR
+UA_RSP 0 1 0 SH9 ERROR
+UA_RSP 0 1 1 SH9 ERROR
+UA_RSP 1 0 0 SH9 ERROR
+UA_RSP 1 0 1 SH9 ERROR
+UA_RSP 1 1 0 SH9 ERROR
+UA_RSP 1 1 1 SH9 ERROR
+DM_RSP 0 0 0 SH6 ADM
+DM_RSP 0 0 1 SH6 ADM
+DM_RSP 0 1 0 SH6 ADM
+DM_RSP 0 1 1 SH6 ADM
+DM_RSP 1 0 0 SH6 ADM
+DM_RSP 1 0 1 SH6 ADM
+DM_RSP 1 1 0 SH6 ADM
+DM_RSP 1 1 1 SH6 ADM
+FRMR_RSP 0 0 0 SH5 RESET_WAIT
+FRMR_RSP 0 0 1 SH5 RESET_WAIT
+FRMR_RSP 0 1 0 SH5 RESET_WAIT
+FRMR_RSP 0 1 1 SH5 RESET_WAIT
+FRMR_RSP 1 0 0 SH5 RESET_WAIT
+FRMR_RSP 1 0 1 SH5 RESET_WAIT
+FRMR_RSP 1 1 0 SH5 RESET_WAIT
+FRMR_RSP 1 1 1 SH5 RESET_WAIT
+BAD_FRAME 0 0 0 SH7 ERROR
+BAD_FRAME 0 0 1 SH7 ERROR
+BAD_FRAME 0 1 0 SH7 ERROR
+BAD_FRAME 0 1 1 SH7 ERROR
+BAD_FRAME 1 0 0 SH7 ERROR
+BAD_FRAME 1 0 1 SH7 ERROR
+BAD_FRAME 1 1 0 SH7 ERROR
+BAD_FRAME 1 1 1 SH7 ERROR
+;
+;112 entries in table, 8 modified by tredit4 SABME_CMD x x x SH3 RESET_CHECK
+;112 entries in table, 8 modified by tredit4 DISC_CMD x x x SH4 ADM
+;112 entries in table, 8 modified by tredit4 FRMR_RSP x x x SH5 RESET_WAIT
+;112 entries in table, 8 modified by tredit4 DM_RSP x x x SH6 ADM
+;112 entries in table, 8 modified by tredit4 BAD_FRAME x x x SH7 ERROR
+;112 entries in table, 8 modified by tredit4 UA_RSP x x x SH9 ERROR
+;112 entries in table, 8 modified by tredit4 anyrsp x 1 0 SH10 ERROR
+;112 entries in table, 1 modified by tredit4 I_RSP 1 1 x AWAIT_BUSY4 BUSY
+;112 entries in table, 2 modified by tredit4 I_CMD 1 0 x AWAIT_BUSY5 AWAIT_BUSY
+;112 entries in table, 2 modified by tredit4 I_RSP 1 0 x AWAIT_BUSY5 AWAIT_BUSY
+;112 entries in table, 2 modified by tredit4 I_CMD 1 1 x AWAIT_BUSY6 AWAIT_BUSY
+;112 entries in table, 1 modified by tredit4 I_RSP x 1 x AWAIT_BUSY7 BUSY
+;112 entries in table, 2 modified by tredit4 I_RSP x 0 x AWAIT_BUSY8 AWAIT_BUSY
+;112 entries in table, 2 modified by tredit4 I_CMD x 0 x AWAIT_BUSY8 AWAIT_BUSY
+;112 entries in table, 2 modified by tredit4 I_CMD x 1 x AWAIT_BUSY9 AWAIT_BUSY
+;112 entries in table, 2 modified by tredit4 RR_RSP x 1 x AWAIT_BUSY10 BUSY
+;112 entries in table, 2 modified by tredit4 REJ_RSP x 1 x AWAIT_BUSY10 BUSY
+;112 entries in table, 4 modified by tredit4 RR_CMD x 0 x AWAIT_BUSY11 AWAIT_BUSY
+;112 entries in table, 4 modified by tredit4 RR_RSP x 0 x AWAIT_BUSY11 AWAIT_BUSY
+;112 entries in table, 4 modified by tredit4 REJ_CMD x 0 x AWAIT_BUSY11 AWAIT_BUSY
+;112 entries in table, 4 modified by tredit4 REJ_RSP x 0 x AWAIT_BUSY11 AWAIT_BUSY
+;112 entries in table, 4 modified by tredit4 RR_CMD x 1 x AWAIT_BUSY12 AWAIT_BUSY
+;112 entries in table, 4 modified by tredit4 REJ_CMD x 1 x AWAIT_BUSY12 AWAIT_BUSY
+;112 entries in table, 2 modified by tredit4 RNR_RSP x 1 x AWAIT_BUSY13 BUSY
+;112 entries in table, 4 modified by tredit4 RNR_CMD x 0 x AWAIT_BUSY14 AWAIT_BUSY
+;112 entries in table, 4 modified by tredit4 RNR_RSP x 0 x AWAIT_BUSY14 AWAIT_BUSY
+;112 entries in table, 4 modified by tredit4 RNR_CMD x 1 x AWAIT_BUSY15 AWAIT_BUSY
+;
+TABLE AWAIT_REJECT
+;Transition table for the AWAIT_REJECT state:
+;
+;frame type uexpect p bit p_flag
+;received N(S) in frame
+;
+I_CMD 0 0 0 AWAIT_REJECT5 AWAIT
+I_CMD 0 0 1 AWAIT_REJECT5 AWAIT
+I_CMD 0 1 0 AWAIT_REJECT6 AWAIT
+I_CMD 0 1 1 AWAIT_REJECT6 AWAIT
+I_CMD 1 0 0 AWAIT_REJECT2 AWAIT_REJECT
+I_CMD 1 0 1 AWAIT_REJECT2 AWAIT_REJECT
+I_CMD 1 1 0 AWAIT_REJECT3 AWAIT_REJECT
+I_CMD 1 1 1 AWAIT_REJECT3 AWAIT_REJECT
+RR_CMD 0 0 0 AWAIT_REJECT8 AWAIT_REJECT
+RR_CMD 0 0 1 AWAIT_REJECT8 AWAIT_REJECT
+RR_CMD 0 1 0 AWAIT_REJECT9 AWAIT_REJECT
+RR_CMD 0 1 1 AWAIT_REJECT9 AWAIT_REJECT
+RR_CMD 1 0 0 AWAIT_REJECT8 AWAIT_REJECT
+RR_CMD 1 0 1 AWAIT_REJECT8 AWAIT_REJECT
+RR_CMD 1 1 0 AWAIT_REJECT9 AWAIT_REJECT
+RR_CMD 1 1 1 AWAIT_REJECT9 AWAIT_REJECT
+RNR_CMD 0 0 0 AWAIT_REJECT11 AWAIT_REJECT
+RNR_CMD 0 0 1 AWAIT_REJECT11 AWAIT_REJECT
+RNR_CMD 0 1 0 AWAIT_REJECT12 AWAIT_REJECT
+RNR_CMD 0 1 1 AWAIT_REJECT12 AWAIT_REJECT
+RNR_CMD 1 0 0 AWAIT_REJECT11 AWAIT_REJECT
+RNR_CMD 1 0 1 AWAIT_REJECT11 AWAIT_REJECT
+RNR_CMD 1 1 0 AWAIT_REJECT12 AWAIT_REJECT
+RNR_CMD 1 1 1 AWAIT_REJECT12 AWAIT_REJECT
+REJ_CMD 0 0 0 AWAIT_REJECT8 AWAIT_REJECT
+REJ_CMD 0 0 1 AWAIT_REJECT8 AWAIT_REJECT
+REJ_CMD 0 1 0 AWAIT_REJECT9 AWAIT_REJECT
+REJ_CMD 0 1 1 AWAIT_REJECT9 AWAIT_REJECT
+REJ_CMD 1 0 0 AWAIT_REJECT8 AWAIT_REJECT
+REJ_CMD 1 0 1 AWAIT_REJECT8 AWAIT_REJECT
+REJ_CMD 1 1 0 AWAIT_REJECT9 AWAIT_REJECT
+REJ_CMD 1 1 1 AWAIT_REJECT9 AWAIT_REJECT
+DISC_CMD 0 0 0 SH4 ADM
+DISC_CMD 0 0 1 SH4 ADM
+DISC_CMD 0 1 0 SH4 ADM
+DISC_CMD 0 1 1 SH4 ADM
+DISC_CMD 1 0 0 SH4 ADM
+DISC_CMD 1 0 1 SH4 ADM
+DISC_CMD 1 1 0 SH4 ADM
+DISC_CMD 1 1 1 SH4 ADM
+SABME_CMD 0 0 0 SH3 RESET_CHECK
+SABME_CMD 0 0 1 SH3 RESET_CHECK
+SABME_CMD 0 1 0 SH3 RESET_CHECK
+SABME_CMD 0 1 1 SH3 RESET_CHECK
+SABME_CMD 1 0 0 SH3 RESET_CHECK
+SABME_CMD 1 0 1 SH3 RESET_CHECK
+SABME_CMD 1 1 0 SH3 RESET_CHECK
+SABME_CMD 1 1 1 SH3 RESET_CHECK
+I_RSP 0 0 0 AWAIT_REJECT5 AWAIT
+I_RSP 0 0 1 AWAIT_REJECT5 AWAIT
+I_RSP 0 1 0 SH10 ERROR
+I_RSP 0 1 1 AWAIT_REJECT4 NORMAL
+I_RSP 1 0 0 AWAIT_REJECT2 AWAIT_REJECT
+I_RSP 1 0 1 AWAIT_REJECT2 AWAIT_REJECT
+I_RSP 1 1 0 SH10 ERROR
+I_RSP 1 1 1 AWAIT_REJECT4 NORMAL
+RR_RSP 0 0 0 AWAIT_REJECT8 AWAIT_REJECT
+RR_RSP 0 0 1 AWAIT_REJECT8 AWAIT_REJECT
+RR_RSP 0 1 0 SH10 ERROR
+RR_RSP 0 1 1 AWAIT_REJECT7 REJECT
+RR_RSP 1 0 0 AWAIT_REJECT8 AWAIT_REJECT
+RR_RSP 1 0 1 AWAIT_REJECT8 AWAIT_REJECT
+RR_RSP 1 1 0 SH10 ERROR
+RR_RSP 1 1 1 AWAIT_REJECT7 REJECT
+RNR_RSP 0 0 0 AWAIT_REJECT11 AWAIT_REJECT
+RNR_RSP 0 0 1 AWAIT_REJECT11 AWAIT_REJECT
+RNR_RSP 0 1 0 SH10 ERROR
+RNR_RSP 0 1 1 AWAIT_REJECT10 REJECT
+RNR_RSP 1 0 0 AWAIT_REJECT11 AWAIT_REJECT
+RNR_RSP 1 0 1 AWAIT_REJECT11 AWAIT_REJECT
+RNR_RSP 1 1 0 SH10 ERROR
+RNR_RSP 1 1 1 AWAIT_REJECT10 REJECT
+REJ_RSP 0 0 0 AWAIT_REJECT8 AWAIT_REJECT
+REJ_RSP 0 0 1 AWAIT_REJECT8 AWAIT_REJECT
+REJ_RSP 0 1 0 SH10 ERROR
+REJ_RSP 0 1 1 AWAIT_REJECT7 REJECT
+REJ_RSP 1 0 0 AWAIT_REJECT8 AWAIT_REJECT
+REJ_RSP 1 0 1 AWAIT_REJECT8 AWAIT_REJECT
+REJ_RSP 1 1 0 SH10 ERROR
+REJ_RSP 1 1 1 AWAIT_REJECT7 REJECT
+UA_RSP 0 0 0 SH9 ERROR
+UA_RSP 0 0 1 SH9 ERROR
+UA_RSP 0 1 0 SH9 ERROR
+UA_RSP 0 1 1 SH9 ERROR
+UA_RSP 1 0 0 SH9 ERROR
+UA_RSP 1 0 1 SH9 ERROR
+UA_RSP 1 1 0 SH9 ERROR
+UA_RSP 1 1 1 SH9 ERROR
+DM_RSP 0 0 0 SH6 ADM
+DM_RSP 0 0 1 SH6 ADM
+DM_RSP 0 1 0 SH6 ADM
+DM_RSP 0 1 1 SH6 ADM
+DM_RSP 1 0 0 SH6 ADM
+DM_RSP 1 0 1 SH6 ADM
+DM_RSP 1 1 0 SH6 ADM
+DM_RSP 1 1 1 SH6 ADM
+FRMR_RSP 0 0 0 SH5 RESET_WAIT
+FRMR_RSP 0 0 1 SH5 RESET_WAIT
+FRMR_RSP 0 1 0 SH5 RESET_WAIT
+FRMR_RSP 0 1 1 SH5 RESET_WAIT
+FRMR_RSP 1 0 0 SH5 RESET_WAIT
+FRMR_RSP 1 0 1 SH5 RESET_WAIT
+FRMR_RSP 1 1 0 SH5 RESET_WAIT
+FRMR_RSP 1 1 1 SH5 RESET_WAIT
+BAD_FRAME 0 0 0 SH7 ERROR
+BAD_FRAME 0 0 1 SH7 ERROR
+BAD_FRAME 0 1 0 SH7 ERROR
+BAD_FRAME 0 1 1 SH7 ERROR
+BAD_FRAME 1 0 0 SH7 ERROR
+BAD_FRAME 1 0 1 SH7 ERROR
+BAD_FRAME 1 1 0 SH7 ERROR
+BAD_FRAME 1 1 1 SH7 ERROR
+;
+;112 entries in table, 8 modified by tredit4 SABME_CMD x x x SH3 RESET_CHECK
+;112 entries in table, 8 modified by tredit4 DISC_CMD x x x SH4 ADM
+;112 entries in table, 8 modified by tredit4 FRMR_RSP x x x SH5 RESET_WAIT
+;112 entries in table, 8 modified by tredit4 DM_RSP x x x SH6 ADM
+;112 entries in table, 8 modified by tredit4 BAD_FRAME x x x SH7 ERROR
+;112 entries in table, 8 modified by tredit4 UA_RSP x x x SH9 ERROR
+;112 entries in table, 8 modified by tredit4 anyrsp x 1 0 SH10 ERROR
+;112 entries in table, 2 modified by tredit4 I_CMD 1 0 x AWAIT_REJECT2 AWAIT_REJECT
+;112 entries in table, 2 modified by tredit4 I_RSP 1 0 x AWAIT_REJECT2 AWAIT_REJECT
+;112 entries in table, 2 modified by tredit4 I_CMD 1 1 x AWAIT_REJECT3 AWAIT_REJECT
+;112 entries in table, 2 modified by tredit4 I_RSP x 1 x AWAIT_REJECT4 NORMAL
+;112 entries in table, 2 modified by tredit4 I_RSP x 0 x AWAIT_REJECT5 AWAIT
+;112 entries in table, 2 modified by tredit4 I_CMD x 0 x AWAIT_REJECT5 AWAIT
+;112 entries in table, 2 modified by tredit4 I_CMD x 1 x AWAIT_REJECT6 AWAIT
+;112 entries in table, 2 modified by tredit4 RR_RSP x 1 x AWAIT_REJECT7 REJECT
+;112 entries in table, 2 modified by tredit4 REJ_RSP x 1 x AWAIT_REJECT7 REJECT
+;112 entries in table, 0 modified by tredit4 I_RSP 1 1 x AWAIT_REJECT7 REJECT
+;112 entries in table, 4 modified by tredit4 RR_CMD x 0 x AWAIT_REJECT8 AWAIT_REJECT
+;112 entries in table, 4 modified by tredit4 RR_RSP x 0 x AWAIT_REJECT8 AWAIT_REJECT
+;112 entries in table, 4 modified by tredit4 REJ_CMD x 0 x AWAIT_REJECT8 AWAIT_REJECT
+;112 entries in table, 4 modified by tredit4 REJ_RSP x 0 x AWAIT_REJECT8 AWAIT_REJECT
+;112 entries in table, 4 modified by tredit4 RR_CMD x 1 x AWAIT_REJECT9 AWAIT_REJECT
+;112 entries in table, 4 modified by tredit4 REJ_CMD x 1 x AWAIT_REJECT9 AWAIT_REJECT
+;112 entries in table, 2 modified by tredit4 RNR_RSP x 1 x AWAIT_REJECT10 REJECT
+;112 entries in table, 4 modified by tredit4 RNR_CMD x 0 x AWAIT_REJECT11 AWAIT_REJECT
+;112 entries in table, 4 modified by tredit4 RNR_RSP x 0 x AWAIT_REJECT11 AWAIT_REJECT
+;112 entries in table, 4 modified by tredit4 RNR_CMD x 1 x AWAIT_REJECT12 AWAIT_REJECT
+;112 entries in table, 0 modified by tredit4 RNR_CMD x 1 x AWAIT_REJECT15 AWAIT_BUSY
diff --git a/uClinux-2.4.31-uc0/net/802/transit/timertr.h b/uClinux-2.4.31-uc0/net/802/transit/timertr.h
new file mode 100644
index 0000000..43237f1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/transit/timertr.h
@@ -0,0 +1,157 @@
+
+/* this file was generated on Thu Jan 8 00:21:21 GMT 1998 */
+
+
+/* size of transition table is 898 bytes */
+
+static char timertr_entry [ ] = {
+ NOP , ADM , NOP , ADM , NOP , ADM ,
+ NOP , ADM , NOP , ADM , NOP , ADM ,
+ NOP , ADM , NOP , ADM , NOP , ADM ,
+ NOP , ADM , NOP , ADM , NOP , ADM ,
+ NOP , ADM , NOP , ADM , NOP , ADM ,
+ NOP , ADM , NOP , ADM , NOP , ADM ,
+ NOP , ADM , NOP , ADM , NOP , ADM ,
+ NOP , ADM , NOP , ADM , NOP , ADM ,
+ NOP , ADM , NOP , ADM , NOP , ADM ,
+ NOP , ADM , NOP , ADM , NOP , ADM ,
+ NOP , ADM , NOP , ADM , NOP , CONN ,
+ NOP , CONN , NOP , CONN , NOP , CONN ,
+ NOP , CONN , NOP , CONN , NOP , CONN ,
+ NOP , CONN , NOP , CONN , NOP , CONN ,
+ NOP , CONN , NOP , CONN , NOP , CONN ,
+ NOP , CONN , NOP , CONN , NOP , CONN ,
+ NOP , CONN , NOP , CONN , NOP , CONN ,
+ NOP , CONN , NOP , CONN , NOP , CONN ,
+ NOP , CONN , NOP , CONN , NOP , CONN ,
+ NOP , CONN , NOP , CONN , NOP , CONN ,
+ NOP , CONN , NOP , CONN , NOP , CONN ,
+ NOP , CONN , NOP , RESET_WAIT , NOP , RESET_WAIT ,
+ NOP , RESET_WAIT , NOP , RESET_WAIT , NOP , RESET_WAIT ,
+ NOP , RESET_WAIT , NOP , RESET_WAIT , NOP , RESET_WAIT ,
+ NOP , RESET_WAIT , NOP , RESET_WAIT , NOP , RESET_WAIT ,
+ NOP , RESET_WAIT , NOP , RESET_WAIT , NOP , RESET_WAIT ,
+ NOP , RESET_WAIT , NOP , RESET_WAIT , NOP , RESET_WAIT ,
+ NOP , RESET_WAIT , NOP , RESET_WAIT , NOP , RESET_WAIT ,
+ NOP , RESET_WAIT , NOP , RESET_WAIT , NOP , RESET_WAIT ,
+ NOP , RESET_WAIT , NOP , RESET_WAIT , NOP , RESET_WAIT ,
+ NOP , RESET_WAIT , NOP , RESET_WAIT , NOP , RESET_WAIT ,
+ NOP , RESET_WAIT , NOP , RESET_WAIT , NOP , RESET_WAIT ,
+ NOP , RESET_CHECK , NOP , RESET_CHECK , NOP , RESET_CHECK ,
+ NOP , RESET_CHECK , NOP , RESET_CHECK , NOP , RESET_CHECK ,
+ NOP , RESET_CHECK , NOP , RESET_CHECK , NOP , RESET_CHECK ,
+ NOP , RESET_CHECK , NOP , RESET_CHECK , NOP , RESET_CHECK ,
+ NOP , RESET_CHECK , NOP , RESET_CHECK , NOP , RESET_CHECK ,
+ NOP , RESET_CHECK , NOP , RESET_CHECK , NOP , RESET_CHECK ,
+ NOP , RESET_CHECK , NOP , RESET_CHECK , NOP , RESET_CHECK ,
+ NOP , RESET_CHECK , NOP , RESET_CHECK , NOP , RESET_CHECK ,
+ NOP , RESET_CHECK , NOP , RESET_CHECK , NOP , RESET_CHECK ,
+ NOP , RESET_CHECK , NOP , RESET_CHECK , NOP , RESET_CHECK ,
+ NOP , RESET_CHECK , NOP , RESET_CHECK , SETUP7 , SETUP ,
+ SETUP7 , SETUP , SETUP3 , NORMAL , SETUP3 , NORMAL ,
+ SETUP8 , ADM , SETUP8 , ADM , SETUP3 , NORMAL ,
+ SETUP3 , NORMAL , NOP , SETUP , NOP , SETUP ,
+ NOP , SETUP , NOP , SETUP , NOP , SETUP ,
+ NOP , SETUP , NOP , SETUP , NOP , SETUP ,
+ NOP , SETUP , NOP , SETUP , NOP , SETUP ,
+ NOP , SETUP , NOP , SETUP , NOP , SETUP ,
+ NOP , SETUP , NOP , SETUP , NOP , SETUP ,
+ NOP , SETUP , NOP , SETUP , NOP , SETUP ,
+ NOP , SETUP , NOP , SETUP , NOP , SETUP ,
+ NOP , SETUP , RESET7 , RESET , RESET7 , RESET ,
+ RESET3 , NORMAL , RESET3 , NORMAL , RESET8 , ADM ,
+ RESET8 , ADM , RESET3 , NORMAL , RESET3 , NORMAL ,
+ NOP , RESET , NOP , RESET , NOP , RESET ,
+ NOP , RESET , NOP , RESET , NOP , RESET ,
+ NOP , RESET , NOP , RESET , NOP , RESET ,
+ NOP , RESET , NOP , RESET , NOP , RESET ,
+ NOP , RESET , NOP , RESET , NOP , RESET ,
+ NOP , RESET , NOP , RESET , NOP , RESET ,
+ NOP , RESET , NOP , RESET , NOP , RESET ,
+ NOP , RESET , NOP , RESET , NOP , RESET ,
+ D_CONN6 , D_CONN , D_CONN6 , D_CONN , D_CONN6 , D_CONN ,
+ D_CONN6 , D_CONN , D_CONN7 , ADM , D_CONN7 , ADM ,
+ D_CONN7 , ADM , D_CONN7 , ADM , NOP , D_CONN ,
+ NOP , D_CONN , NOP , D_CONN , NOP , D_CONN ,
+ NOP , D_CONN , NOP , D_CONN , NOP , D_CONN ,
+ NOP , D_CONN , NOP , D_CONN , NOP , D_CONN ,
+ NOP , D_CONN , NOP , D_CONN , NOP , D_CONN ,
+ NOP , D_CONN , NOP , D_CONN , NOP , D_CONN ,
+ NOP , D_CONN , NOP , D_CONN , NOP , D_CONN ,
+ NOP , D_CONN , NOP , D_CONN , NOP , D_CONN ,
+ NOP , D_CONN , NOP , D_CONN , ERR7 , ERROR ,
+ ERR7 , ERROR , ERR7 , ERROR , ERR7 , ERROR ,
+ ERR8 , RESET_WAIT , ERR8 , RESET_WAIT , ERR8 , RESET_WAIT ,
+ ERR8 , RESET_WAIT , NOP , ERROR , NOP , ERROR ,
+ NOP , ERROR , NOP , ERROR , NOP , ERROR ,
+ NOP , ERROR , NOP , ERROR , NOP , ERROR ,
+ NOP , ERROR , NOP , ERROR , NOP , ERROR ,
+ NOP , ERROR , NOP , ERROR , NOP , ERROR ,
+ NOP , ERROR , NOP , ERROR , NOP , ERROR ,
+ NOP , ERROR , NOP , ERROR , NOP , ERROR ,
+ NOP , ERROR , NOP , ERROR , NOP , ERROR ,
+ NOP , ERROR , NORMAL20 , AWAIT , NOP , NORMAL ,
+ NORMAL20 , AWAIT , NOP , NORMAL , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ NORMAL19 , NORMAL , NORMAL19 , NORMAL , NORMAL19 , NORMAL ,
+ NORMAL19 , NORMAL , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , NOP , NORMAL ,
+ NOP , NORMAL , NOP , NORMAL , NOP , NORMAL ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , NORMAL20 , AWAIT , NOP , NORMAL ,
+ NORMAL20 , AWAIT , NOP , NORMAL , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ BUSY24 , AWAIT_BUSY , NOP , BUSY , BUSY24 , AWAIT_BUSY ,
+ NOP , BUSY , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , BUSY23 , BUSY ,
+ BUSY23 , BUSY , BUSY23 , BUSY , BUSY23 , BUSY ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , BUSY25 , BUSY , BUSY26 , BUSY ,
+ BUSY25 , BUSY , BUSY26 , BUSY , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ NOP , BUSY , NOP , BUSY , NOP , BUSY ,
+ NOP , BUSY , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , NOP , REJECT ,
+ NOP , REJECT , NOP , REJECT , NOP , REJECT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , NOP , REJECT , NOP , REJECT ,
+ NOP , REJECT , NOP , REJECT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ NOP , REJECT , NOP , REJECT , NOP , REJECT ,
+ NOP , REJECT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , NOP , REJECT ,
+ NOP , REJECT , NOP , REJECT , NOP , REJECT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , NOP , AWAIT , NOP , AWAIT ,
+ NOP , AWAIT , NOP , AWAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ NOP , AWAIT , NOP , AWAIT , NOP , AWAIT ,
+ NOP , AWAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , NOP , AWAIT ,
+ NOP , AWAIT , NOP , AWAIT , NOP , AWAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , NOP , AWAIT , NOP , AWAIT ,
+ NOP , AWAIT , NOP , AWAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ NOP , AWAIT_BUSY , NOP , AWAIT_BUSY , NOP , AWAIT_BUSY ,
+ NOP , AWAIT_BUSY , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , NOP , AWAIT_BUSY ,
+ NOP , AWAIT_BUSY , NOP , AWAIT_BUSY , NOP , AWAIT_BUSY ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , NOP , AWAIT_BUSY , NOP , AWAIT_BUSY ,
+ NOP , AWAIT_BUSY , NOP , AWAIT_BUSY , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ NOP , AWAIT_BUSY , NOP , AWAIT_BUSY , NOP , AWAIT_BUSY ,
+ NOP , AWAIT_BUSY , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , NOP , AWAIT_REJECT ,
+ NOP , AWAIT_REJECT , NOP , AWAIT_REJECT , NOP , AWAIT_REJECT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , NOP , AWAIT_REJECT , NOP , AWAIT_REJECT ,
+ NOP , AWAIT_REJECT , NOP , AWAIT_REJECT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ NOP , AWAIT_REJECT , NOP , AWAIT_REJECT , NOP , AWAIT_REJECT ,
+ NOP , AWAIT_REJECT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , NOP , AWAIT_REJECT ,
+ NOP , AWAIT_REJECT , NOP , AWAIT_REJECT , NOP , AWAIT_REJECT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT , SH11 , RESET_WAIT ,
+ SH11 , RESET_WAIT , SH11 , RESET_WAIT };
diff --git a/uClinux-2.4.31-uc0/net/802/transit/timertr.pre b/uClinux-2.4.31-uc0/net/802/transit/timertr.pre
new file mode 100644
index 0000000..f082912
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/802/transit/timertr.pre
@@ -0,0 +1,527 @@
+COMPILE timertr NOINDEX
+TABLE XXX
+;
+;Transition table for expiring timers:
+;
+;llc state timer retry_c s_flag p_flag action newstate
+; expired >= N2
+;
+ADM ACK_TIMER 0 0 0 NOP ADM
+ADM ACK_TIMER 0 0 1 NOP ADM
+ADM ACK_TIMER 0 1 0 NOP ADM
+ADM ACK_TIMER 0 1 1 NOP ADM
+ADM ACK_TIMER 1 0 0 NOP ADM
+ADM ACK_TIMER 1 0 1 NOP ADM
+ADM ACK_TIMER 1 1 0 NOP ADM
+ADM ACK_TIMER 1 1 1 NOP ADM
+;;
+ADM P_TIMER 0 0 0 NOP ADM
+ADM P_TIMER 0 0 1 NOP ADM
+ADM P_TIMER 0 1 0 NOP ADM
+ADM P_TIMER 0 1 1 NOP ADM
+ADM P_TIMER 1 0 0 NOP ADM
+ADM P_TIMER 1 0 1 NOP ADM
+ADM P_TIMER 1 1 0 NOP ADM
+ADM P_TIMER 1 1 1 NOP ADM
+;;
+ADM REJ_TIMER 0 0 0 NOP ADM
+ADM REJ_TIMER 0 0 1 NOP ADM
+ADM REJ_TIMER 0 1 0 NOP ADM
+ADM REJ_TIMER 0 1 1 NOP ADM
+ADM REJ_TIMER 1 0 0 NOP ADM
+ADM REJ_TIMER 1 0 1 NOP ADM
+ADM REJ_TIMER 1 1 0 NOP ADM
+ADM REJ_TIMER 1 1 1 NOP ADM
+;;
+ADM BUSY_TIMER 0 0 0 NOP ADM
+ADM BUSY_TIMER 0 0 1 NOP ADM
+ADM BUSY_TIMER 0 1 0 NOP ADM
+ADM BUSY_TIMER 0 1 1 NOP ADM
+ADM BUSY_TIMER 1 0 0 NOP ADM
+ADM BUSY_TIMER 1 0 1 NOP ADM
+ADM BUSY_TIMER 1 1 0 NOP ADM
+ADM BUSY_TIMER 1 1 1 NOP ADM
+;;
+;;
+CONN ACK_TIMER 0 0 0 NOP CONN
+CONN ACK_TIMER 0 0 1 NOP CONN
+CONN ACK_TIMER 0 1 0 NOP CONN
+CONN ACK_TIMER 0 1 1 NOP CONN
+CONN ACK_TIMER 1 0 0 NOP CONN
+CONN ACK_TIMER 1 0 1 NOP CONN
+CONN ACK_TIMER 1 1 0 NOP CONN
+CONN ACK_TIMER 1 1 1 NOP CONN
+;;
+CONN P_TIMER 0 0 0 NOP CONN
+CONN P_TIMER 0 0 1 NOP CONN
+CONN P_TIMER 0 1 0 NOP CONN
+CONN P_TIMER 0 1 1 NOP CONN
+CONN P_TIMER 1 0 0 NOP CONN
+CONN P_TIMER 1 0 1 NOP CONN
+CONN P_TIMER 1 1 0 NOP CONN
+CONN P_TIMER 1 1 1 NOP CONN
+;;
+CONN REJ_TIMER 0 0 0 NOP CONN
+CONN REJ_TIMER 0 0 1 NOP CONN
+CONN REJ_TIMER 0 1 0 NOP CONN
+CONN REJ_TIMER 0 1 1 NOP CONN
+CONN REJ_TIMER 1 0 0 NOP CONN
+CONN REJ_TIMER 1 0 1 NOP CONN
+CONN REJ_TIMER 1 1 0 NOP CONN
+CONN REJ_TIMER 1 1 1 NOP CONN
+;;
+CONN BUSY_TIMER 0 0 0 NOP CONN
+CONN BUSY_TIMER 0 0 1 NOP CONN
+CONN BUSY_TIMER 0 1 0 NOP CONN
+CONN BUSY_TIMER 0 1 1 NOP CONN
+CONN BUSY_TIMER 1 0 0 NOP CONN
+CONN BUSY_TIMER 1 0 1 NOP CONN
+CONN BUSY_TIMER 1 1 0 NOP CONN
+CONN BUSY_TIMER 1 1 1 NOP CONN
+;;
+;;
+RESET_WAIT ACK_TIMER 0 0 0 NOP RESET_WAIT
+RESET_WAIT ACK_TIMER 0 0 1 NOP RESET_WAIT
+RESET_WAIT ACK_TIMER 0 1 0 NOP RESET_WAIT
+RESET_WAIT ACK_TIMER 0 1 1 NOP RESET_WAIT
+RESET_WAIT ACK_TIMER 1 0 0 NOP RESET_WAIT
+RESET_WAIT ACK_TIMER 1 0 1 NOP RESET_WAIT
+RESET_WAIT ACK_TIMER 1 1 0 NOP RESET_WAIT
+RESET_WAIT ACK_TIMER 1 1 1 NOP RESET_WAIT
+;;
+RESET_WAIT P_TIMER 0 0 0 NOP RESET_WAIT
+RESET_WAIT P_TIMER 0 0 1 NOP RESET_WAIT
+RESET_WAIT P_TIMER 0 1 0 NOP RESET_WAIT
+RESET_WAIT P_TIMER 0 1 1 NOP RESET_WAIT
+RESET_WAIT P_TIMER 1 0 0 NOP RESET_WAIT
+RESET_WAIT P_TIMER 1 0 1 NOP RESET_WAIT
+RESET_WAIT P_TIMER 1 1 0 NOP RESET_WAIT
+RESET_WAIT P_TIMER 1 1 1 NOP RESET_WAIT
+;;
+RESET_WAIT REJ_TIMER 0 0 0 NOP RESET_WAIT
+RESET_WAIT REJ_TIMER 0 0 1 NOP RESET_WAIT
+RESET_WAIT REJ_TIMER 0 1 0 NOP RESET_WAIT
+RESET_WAIT REJ_TIMER 0 1 1 NOP RESET_WAIT
+RESET_WAIT REJ_TIMER 1 0 0 NOP RESET_WAIT
+RESET_WAIT REJ_TIMER 1 0 1 NOP RESET_WAIT
+RESET_WAIT REJ_TIMER 1 1 0 NOP RESET_WAIT
+RESET_WAIT REJ_TIMER 1 1 1 NOP RESET_WAIT
+;;
+RESET_WAIT BUSY_TIMER 0 0 0 NOP RESET_WAIT
+RESET_WAIT BUSY_TIMER 0 0 1 NOP RESET_WAIT
+RESET_WAIT BUSY_TIMER 0 1 0 NOP RESET_WAIT
+RESET_WAIT BUSY_TIMER 0 1 1 NOP RESET_WAIT
+RESET_WAIT BUSY_TIMER 1 0 0 NOP RESET_WAIT
+RESET_WAIT BUSY_TIMER 1 0 1 NOP RESET_WAIT
+RESET_WAIT BUSY_TIMER 1 1 0 NOP RESET_WAIT
+RESET_WAIT BUSY_TIMER 1 1 1 NOP RESET_WAIT
+;;
+;;
+RESET_CHECK ACK_TIMER 0 0 0 NOP RESET_CHECK
+RESET_CHECK ACK_TIMER 0 0 1 NOP RESET_CHECK
+RESET_CHECK ACK_TIMER 0 1 0 NOP RESET_CHECK
+RESET_CHECK ACK_TIMER 0 1 1 NOP RESET_CHECK
+RESET_CHECK ACK_TIMER 1 0 0 NOP RESET_CHECK
+RESET_CHECK ACK_TIMER 1 0 1 NOP RESET_CHECK
+RESET_CHECK ACK_TIMER 1 1 0 NOP RESET_CHECK
+RESET_CHECK ACK_TIMER 1 1 1 NOP RESET_CHECK
+;;
+RESET_CHECK P_TIMER 0 0 0 NOP RESET_CHECK
+RESET_CHECK P_TIMER 0 0 1 NOP RESET_CHECK
+RESET_CHECK P_TIMER 0 1 0 NOP RESET_CHECK
+RESET_CHECK P_TIMER 0 1 1 NOP RESET_CHECK
+RESET_CHECK P_TIMER 1 0 0 NOP RESET_CHECK
+RESET_CHECK P_TIMER 1 0 1 NOP RESET_CHECK
+RESET_CHECK P_TIMER 1 1 0 NOP RESET_CHECK
+RESET_CHECK P_TIMER 1 1 1 NOP RESET_CHECK
+;;
+RESET_CHECK REJ_TIMER 0 0 0 NOP RESET_CHECK
+RESET_CHECK REJ_TIMER 0 0 1 NOP RESET_CHECK
+RESET_CHECK REJ_TIMER 0 1 0 NOP RESET_CHECK
+RESET_CHECK REJ_TIMER 0 1 1 NOP RESET_CHECK
+RESET_CHECK REJ_TIMER 1 0 0 NOP RESET_CHECK
+RESET_CHECK REJ_TIMER 1 0 1 NOP RESET_CHECK
+RESET_CHECK REJ_TIMER 1 1 0 NOP RESET_CHECK
+RESET_CHECK REJ_TIMER 1 1 1 NOP RESET_CHECK
+;;
+RESET_CHECK BUSY_TIMER 0 0 0 NOP RESET_CHECK
+RESET_CHECK BUSY_TIMER 0 0 1 NOP RESET_CHECK
+RESET_CHECK BUSY_TIMER 0 1 0 NOP RESET_CHECK
+RESET_CHECK BUSY_TIMER 0 1 1 NOP RESET_CHECK
+RESET_CHECK BUSY_TIMER 1 0 0 NOP RESET_CHECK
+RESET_CHECK BUSY_TIMER 1 0 1 NOP RESET_CHECK
+RESET_CHECK BUSY_TIMER 1 1 0 NOP RESET_CHECK
+RESET_CHECK BUSY_TIMER 1 1 1 NOP RESET_CHECK
+;;
+;;
+;;
+SETUP ACK_TIMER 0 0 0 SETUP7 SETUP
+SETUP ACK_TIMER 0 0 1 SETUP7 SETUP
+SETUP ACK_TIMER 0 1 0 SETUP3 NORMAL
+SETUP ACK_TIMER 0 1 1 SETUP3 NORMAL
+SETUP ACK_TIMER 1 0 0 SETUP8 ADM
+SETUP ACK_TIMER 1 0 1 SETUP8 ADM
+SETUP ACK_TIMER 1 1 0 SETUP3 NORMAL
+SETUP ACK_TIMER 1 1 1 SETUP3 NORMAL
+;;
+SETUP P_TIMER 0 0 0 NOP SETUP
+SETUP P_TIMER 0 0 1 NOP SETUP
+SETUP P_TIMER 0 1 0 NOP SETUP
+SETUP P_TIMER 0 1 1 NOP SETUP
+SETUP P_TIMER 1 0 0 NOP SETUP
+SETUP P_TIMER 1 0 1 NOP SETUP
+SETUP P_TIMER 1 1 0 NOP SETUP
+SETUP P_TIMER 1 1 1 NOP SETUP
+;;
+SETUP REJ_TIMER 0 0 0 NOP SETUP
+SETUP REJ_TIMER 0 0 1 NOP SETUP
+SETUP REJ_TIMER 0 1 0 NOP SETUP
+SETUP REJ_TIMER 0 1 1 NOP SETUP
+SETUP REJ_TIMER 1 0 0 NOP SETUP
+SETUP REJ_TIMER 1 0 1 NOP SETUP
+SETUP REJ_TIMER 1 1 0 NOP SETUP
+SETUP REJ_TIMER 1 1 1 NOP SETUP
+;;
+SETUP BUSY_TIMER 0 0 0 NOP SETUP
+SETUP BUSY_TIMER 0 0 1 NOP SETUP
+SETUP BUSY_TIMER 0 1 0 NOP SETUP
+SETUP BUSY_TIMER 0 1 1 NOP SETUP
+SETUP BUSY_TIMER 1 0 0 NOP SETUP
+SETUP BUSY_TIMER 1 0 1 NOP SETUP
+SETUP BUSY_TIMER 1 1 0 NOP SETUP
+SETUP BUSY_TIMER 1 1 1 NOP SETUP
+;;
+;;
+;;
+RESET ACK_TIMER 0 0 0 RESET7 RESET
+RESET ACK_TIMER 0 0 1 RESET7 RESET
+RESET ACK_TIMER 0 1 0 RESET3 NORMAL
+RESET ACK_TIMER 0 1 1 RESET3 NORMAL
+RESET ACK_TIMER 1 0 0 RESET8 ADM
+RESET ACK_TIMER 1 0 1 RESET8 ADM
+RESET ACK_TIMER 1 1 0 RESET3 NORMAL
+RESET ACK_TIMER 1 1 1 RESET3 NORMAL
+;;
+RESET P_TIMER 0 0 0 NOP RESET
+RESET P_TIMER 0 0 1 NOP RESET
+RESET P_TIMER 0 1 0 NOP RESET
+RESET P_TIMER 0 1 1 NOP RESET
+RESET P_TIMER 1 0 0 NOP RESET
+RESET P_TIMER 1 0 1 NOP RESET
+RESET P_TIMER 1 1 0 NOP RESET
+RESET P_TIMER 1 1 1 NOP RESET
+;;
+RESET REJ_TIMER 0 0 0 NOP RESET
+RESET REJ_TIMER 0 0 1 NOP RESET
+RESET REJ_TIMER 0 1 0 NOP RESET
+RESET REJ_TIMER 0 1 1 NOP RESET
+RESET REJ_TIMER 1 0 0 NOP RESET
+RESET REJ_TIMER 1 0 1 NOP RESET
+RESET REJ_TIMER 1 1 0 NOP RESET
+RESET REJ_TIMER 1 1 1 NOP RESET
+;;
+RESET BUSY_TIMER 0 0 0 NOP RESET
+RESET BUSY_TIMER 0 0 1 NOP RESET
+RESET BUSY_TIMER 0 1 0 NOP RESET
+RESET BUSY_TIMER 0 1 1 NOP RESET
+RESET BUSY_TIMER 1 0 0 NOP RESET
+RESET BUSY_TIMER 1 0 1 NOP RESET
+RESET BUSY_TIMER 1 1 0 NOP RESET
+RESET BUSY_TIMER 1 1 1 NOP RESET
+;;
+;;
+D_CONN ACK_TIMER 0 0 0 D_CONN6 D_CONN
+D_CONN ACK_TIMER 0 0 1 D_CONN6 D_CONN
+D_CONN ACK_TIMER 0 1 0 D_CONN6 D_CONN
+D_CONN ACK_TIMER 0 1 1 D_CONN6 D_CONN
+D_CONN ACK_TIMER 1 0 0 D_CONN7 ADM
+D_CONN ACK_TIMER 1 0 1 D_CONN7 ADM
+D_CONN ACK_TIMER 1 1 0 D_CONN7 ADM
+D_CONN ACK_TIMER 1 1 1 D_CONN7 ADM
+;;
+D_CONN P_TIMER 0 0 0 NOP D_CONN
+D_CONN P_TIMER 0 0 1 NOP D_CONN
+D_CONN P_TIMER 0 1 0 NOP D_CONN
+D_CONN P_TIMER 0 1 1 NOP D_CONN
+D_CONN P_TIMER 1 0 0 NOP D_CONN
+D_CONN P_TIMER 1 0 1 NOP D_CONN
+D_CONN P_TIMER 1 1 0 NOP D_CONN
+D_CONN P_TIMER 1 1 1 NOP D_CONN
+;;
+D_CONN REJ_TIMER 0 0 0 NOP D_CONN
+D_CONN REJ_TIMER 0 0 1 NOP D_CONN
+D_CONN REJ_TIMER 0 1 0 NOP D_CONN
+D_CONN REJ_TIMER 0 1 1 NOP D_CONN
+D_CONN REJ_TIMER 1 0 0 NOP D_CONN
+D_CONN REJ_TIMER 1 0 1 NOP D_CONN
+D_CONN REJ_TIMER 1 1 0 NOP D_CONN
+D_CONN REJ_TIMER 1 1 1 NOP D_CONN
+;;
+D_CONN BUSY_TIMER 0 0 0 NOP D_CONN
+D_CONN BUSY_TIMER 0 0 1 NOP D_CONN
+D_CONN BUSY_TIMER 0 1 0 NOP D_CONN
+D_CONN BUSY_TIMER 0 1 1 NOP D_CONN
+D_CONN BUSY_TIMER 1 0 0 NOP D_CONN
+D_CONN BUSY_TIMER 1 0 1 NOP D_CONN
+D_CONN BUSY_TIMER 1 1 0 NOP D_CONN
+D_CONN BUSY_TIMER 1 1 1 NOP D_CONN
+;;
+;;
+ERROR ACK_TIMER 0 0 0 ERR7 ERROR
+ERROR ACK_TIMER 0 0 1 ERR7 ERROR
+ERROR ACK_TIMER 0 1 0 ERR7 ERROR
+ERROR ACK_TIMER 0 1 1 ERR7 ERROR
+ERROR ACK_TIMER 1 0 0 ERR8 RESET_WAIT
+ERROR ACK_TIMER 1 0 1 ERR8 RESET_WAIT
+ERROR ACK_TIMER 1 1 0 ERR8 RESET_WAIT
+ERROR ACK_TIMER 1 1 1 ERR8 RESET_WAIT
+;;
+ERROR P_TIMER 0 0 0 NOP ERROR
+ERROR P_TIMER 0 0 1 NOP ERROR
+ERROR P_TIMER 0 1 0 NOP ERROR
+ERROR P_TIMER 0 1 1 NOP ERROR
+ERROR P_TIMER 1 0 0 NOP ERROR
+ERROR P_TIMER 1 0 1 NOP ERROR
+ERROR P_TIMER 1 1 0 NOP ERROR
+ERROR P_TIMER 1 1 1 NOP ERROR
+;;
+ERROR REJ_TIMER 0 0 0 NOP ERROR
+ERROR REJ_TIMER 0 0 1 NOP ERROR
+ERROR REJ_TIMER 0 1 0 NOP ERROR
+ERROR REJ_TIMER 0 1 1 NOP ERROR
+ERROR REJ_TIMER 1 0 0 NOP ERROR
+ERROR REJ_TIMER 1 0 1 NOP ERROR
+ERROR REJ_TIMER 1 1 0 NOP ERROR
+ERROR REJ_TIMER 1 1 1 NOP ERROR
+;;
+ERROR BUSY_TIMER 0 0 0 NOP ERROR
+ERROR BUSY_TIMER 0 0 1 NOP ERROR
+ERROR BUSY_TIMER 0 1 0 NOP ERROR
+ERROR BUSY_TIMER 0 1 1 NOP ERROR
+ERROR BUSY_TIMER 1 0 0 NOP ERROR
+ERROR BUSY_TIMER 1 0 1 NOP ERROR
+ERROR BUSY_TIMER 1 1 0 NOP ERROR
+ERROR BUSY_TIMER 1 1 1 NOP ERROR
+;;
+;;
+NORMAL ACK_TIMER 0 0 0 NORMAL20 AWAIT
+NORMAL ACK_TIMER 0 0 1 NOP NORMAL
+NORMAL ACK_TIMER 0 1 0 NORMAL20 AWAIT
+NORMAL ACK_TIMER 0 1 1 NOP NORMAL
+NORMAL ACK_TIMER 1 0 0 SH11 RESET_WAIT
+NORMAL ACK_TIMER 1 0 1 SH11 RESET_WAIT
+NORMAL ACK_TIMER 1 1 0 SH11 RESET_WAIT
+NORMAL ACK_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+NORMAL P_TIMER 0 0 0 NORMAL19 NORMAL
+NORMAL P_TIMER 0 0 1 NORMAL19 NORMAL
+NORMAL P_TIMER 0 1 0 NORMAL19 NORMAL
+NORMAL P_TIMER 0 1 1 NORMAL19 NORMAL
+NORMAL P_TIMER 1 0 0 SH11 RESET_WAIT
+NORMAL P_TIMER 1 0 1 SH11 RESET_WAIT
+NORMAL P_TIMER 1 1 0 SH11 RESET_WAIT
+NORMAL P_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+NORMAL REJ_TIMER 0 0 0 NOP NORMAL
+NORMAL REJ_TIMER 0 0 1 NOP NORMAL
+NORMAL REJ_TIMER 0 1 0 NOP NORMAL
+NORMAL REJ_TIMER 0 1 1 NOP NORMAL
+NORMAL REJ_TIMER 1 0 0 SH11 RESET_WAIT
+NORMAL REJ_TIMER 1 0 1 SH11 RESET_WAIT
+NORMAL REJ_TIMER 1 1 0 SH11 RESET_WAIT
+NORMAL REJ_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+NORMAL BUSY_TIMER 0 0 0 NORMAL20 AWAIT
+NORMAL BUSY_TIMER 0 0 1 NOP NORMAL
+NORMAL BUSY_TIMER 0 1 0 NORMAL20 AWAIT
+NORMAL BUSY_TIMER 0 1 1 NOP NORMAL
+NORMAL BUSY_TIMER 1 0 0 SH11 RESET_WAIT
+NORMAL BUSY_TIMER 1 0 1 SH11 RESET_WAIT
+NORMAL BUSY_TIMER 1 1 0 SH11 RESET_WAIT
+NORMAL BUSY_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+;;
+BUSY ACK_TIMER 0 0 0 BUSY24 AWAIT_BUSY
+BUSY ACK_TIMER 0 0 1 NOP BUSY
+BUSY ACK_TIMER 0 1 0 BUSY24 AWAIT_BUSY
+BUSY ACK_TIMER 0 1 1 NOP BUSY
+BUSY ACK_TIMER 1 0 0 SH11 RESET_WAIT
+BUSY ACK_TIMER 1 0 1 SH11 RESET_WAIT
+BUSY ACK_TIMER 1 1 0 SH11 RESET_WAIT
+BUSY ACK_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+BUSY P_TIMER 0 0 0 BUSY23 BUSY
+BUSY P_TIMER 0 0 1 BUSY23 BUSY
+BUSY P_TIMER 0 1 0 BUSY23 BUSY
+BUSY P_TIMER 0 1 1 BUSY23 BUSY
+BUSY P_TIMER 1 0 0 SH11 RESET_WAIT
+BUSY P_TIMER 1 0 1 SH11 RESET_WAIT
+BUSY P_TIMER 1 1 0 SH11 RESET_WAIT
+BUSY P_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+BUSY REJ_TIMER 0 0 0 BUSY25 BUSY
+BUSY REJ_TIMER 0 0 1 BUSY26 BUSY
+BUSY REJ_TIMER 0 1 0 BUSY25 BUSY
+BUSY REJ_TIMER 0 1 1 BUSY26 BUSY
+BUSY REJ_TIMER 1 0 0 SH11 RESET_WAIT
+BUSY REJ_TIMER 1 0 1 SH11 RESET_WAIT
+BUSY REJ_TIMER 1 1 0 SH11 RESET_WAIT
+BUSY REJ_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+BUSY BUSY_TIMER 0 0 0 NOP BUSY
+BUSY BUSY_TIMER 0 0 1 NOP BUSY
+BUSY BUSY_TIMER 0 1 0 NOP BUSY
+BUSY BUSY_TIMER 0 1 1 NOP BUSY
+BUSY BUSY_TIMER 1 0 0 SH11 RESET_WAIT
+BUSY BUSY_TIMER 1 0 1 SH11 RESET_WAIT
+BUSY BUSY_TIMER 1 1 0 SH11 RESET_WAIT
+BUSY BUSY_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+;;
+REJECT ACK_TIMER 0 0 0 NOP REJECT
+REJECT ACK_TIMER 0 0 1 NOP REJECT
+REJECT ACK_TIMER 0 1 0 NOP REJECT
+REJECT ACK_TIMER 0 1 1 NOP REJECT
+REJECT ACK_TIMER 1 0 0 SH11 RESET_WAIT
+REJECT ACK_TIMER 1 0 1 SH11 RESET_WAIT
+REJECT ACK_TIMER 1 1 0 SH11 RESET_WAIT
+REJECT ACK_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+REJECT P_TIMER 0 0 0 NOP REJECT
+REJECT P_TIMER 0 0 1 NOP REJECT
+REJECT P_TIMER 0 1 0 NOP REJECT
+REJECT P_TIMER 0 1 1 NOP REJECT
+REJECT P_TIMER 1 0 0 SH11 RESET_WAIT
+REJECT P_TIMER 1 0 1 SH11 RESET_WAIT
+REJECT P_TIMER 1 1 0 SH11 RESET_WAIT
+REJECT P_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+REJECT REJ_TIMER 0 0 0 NOP REJECT
+REJECT REJ_TIMER 0 0 1 NOP REJECT
+REJECT REJ_TIMER 0 1 0 NOP REJECT
+REJECT REJ_TIMER 0 1 1 NOP REJECT
+REJECT REJ_TIMER 1 0 0 SH11 RESET_WAIT
+REJECT REJ_TIMER 1 0 1 SH11 RESET_WAIT
+REJECT REJ_TIMER 1 1 0 SH11 RESET_WAIT
+REJECT REJ_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+REJECT BUSY_TIMER 0 0 0 NOP REJECT
+REJECT BUSY_TIMER 0 0 1 NOP REJECT
+REJECT BUSY_TIMER 0 1 0 NOP REJECT
+REJECT BUSY_TIMER 0 1 1 NOP REJECT
+REJECT BUSY_TIMER 1 0 0 SH11 RESET_WAIT
+REJECT BUSY_TIMER 1 0 1 SH11 RESET_WAIT
+REJECT BUSY_TIMER 1 1 0 SH11 RESET_WAIT
+REJECT BUSY_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+;;
+AWAIT ACK_TIMER 0 0 0 NOP AWAIT
+AWAIT ACK_TIMER 0 0 1 NOP AWAIT
+AWAIT ACK_TIMER 0 1 0 NOP AWAIT
+AWAIT ACK_TIMER 0 1 1 NOP AWAIT
+AWAIT ACK_TIMER 1 0 0 SH11 RESET_WAIT
+AWAIT ACK_TIMER 1 0 1 SH11 RESET_WAIT
+AWAIT ACK_TIMER 1 1 0 SH11 RESET_WAIT
+AWAIT ACK_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+AWAIT P_TIMER 0 0 0 NOP AWAIT
+AWAIT P_TIMER 0 0 1 NOP AWAIT
+AWAIT P_TIMER 0 1 0 NOP AWAIT
+AWAIT P_TIMER 0 1 1 NOP AWAIT
+AWAIT P_TIMER 1 0 0 SH11 RESET_WAIT
+AWAIT P_TIMER 1 0 1 SH11 RESET_WAIT
+AWAIT P_TIMER 1 1 0 SH11 RESET_WAIT
+AWAIT P_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+AWAIT REJ_TIMER 0 0 0 NOP AWAIT
+AWAIT REJ_TIMER 0 0 1 NOP AWAIT
+AWAIT REJ_TIMER 0 1 0 NOP AWAIT
+AWAIT REJ_TIMER 0 1 1 NOP AWAIT
+AWAIT REJ_TIMER 1 0 0 SH11 RESET_WAIT
+AWAIT REJ_TIMER 1 0 1 SH11 RESET_WAIT
+AWAIT REJ_TIMER 1 1 0 SH11 RESET_WAIT
+AWAIT REJ_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+AWAIT BUSY_TIMER 0 0 0 NOP AWAIT
+AWAIT BUSY_TIMER 0 0 1 NOP AWAIT
+AWAIT BUSY_TIMER 0 1 0 NOP AWAIT
+AWAIT BUSY_TIMER 0 1 1 NOP AWAIT
+AWAIT BUSY_TIMER 1 0 0 SH11 RESET_WAIT
+AWAIT BUSY_TIMER 1 0 1 SH11 RESET_WAIT
+AWAIT BUSY_TIMER 1 1 0 SH11 RESET_WAIT
+AWAIT BUSY_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+;;
+AWAIT_BUSY ACK_TIMER 0 0 0 NOP AWAIT_BUSY
+AWAIT_BUSY ACK_TIMER 0 0 1 NOP AWAIT_BUSY
+AWAIT_BUSY ACK_TIMER 0 1 0 NOP AWAIT_BUSY
+AWAIT_BUSY ACK_TIMER 0 1 1 NOP AWAIT_BUSY
+AWAIT_BUSY ACK_TIMER 1 0 0 SH11 RESET_WAIT
+AWAIT_BUSY ACK_TIMER 1 0 1 SH11 RESET_WAIT
+AWAIT_BUSY ACK_TIMER 1 1 0 SH11 RESET_WAIT
+AWAIT_BUSY ACK_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+AWAIT_BUSY P_TIMER 0 0 0 NOP AWAIT_BUSY
+AWAIT_BUSY P_TIMER 0 0 1 NOP AWAIT_BUSY
+AWAIT_BUSY P_TIMER 0 1 0 NOP AWAIT_BUSY
+AWAIT_BUSY P_TIMER 0 1 1 NOP AWAIT_BUSY
+AWAIT_BUSY P_TIMER 1 0 0 SH11 RESET_WAIT
+AWAIT_BUSY P_TIMER 1 0 1 SH11 RESET_WAIT
+AWAIT_BUSY P_TIMER 1 1 0 SH11 RESET_WAIT
+AWAIT_BUSY P_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+AWAIT_BUSY REJ_TIMER 0 0 0 NOP AWAIT_BUSY
+AWAIT_BUSY REJ_TIMER 0 0 1 NOP AWAIT_BUSY
+AWAIT_BUSY REJ_TIMER 0 1 0 NOP AWAIT_BUSY
+AWAIT_BUSY REJ_TIMER 0 1 1 NOP AWAIT_BUSY
+AWAIT_BUSY REJ_TIMER 1 0 0 SH11 RESET_WAIT
+AWAIT_BUSY REJ_TIMER 1 0 1 SH11 RESET_WAIT
+AWAIT_BUSY REJ_TIMER 1 1 0 SH11 RESET_WAIT
+AWAIT_BUSY REJ_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+AWAIT_BUSY BUSY_TIMER 0 0 0 NOP AWAIT_BUSY
+AWAIT_BUSY BUSY_TIMER 0 0 1 NOP AWAIT_BUSY
+AWAIT_BUSY BUSY_TIMER 0 1 0 NOP AWAIT_BUSY
+AWAIT_BUSY BUSY_TIMER 0 1 1 NOP AWAIT_BUSY
+AWAIT_BUSY BUSY_TIMER 1 0 0 SH11 RESET_WAIT
+AWAIT_BUSY BUSY_TIMER 1 0 1 SH11 RESET_WAIT
+AWAIT_BUSY BUSY_TIMER 1 1 0 SH11 RESET_WAIT
+AWAIT_BUSY BUSY_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+;;
+AWAIT_REJECT ACK_TIMER 0 0 0 NOP AWAIT_REJECT
+AWAIT_REJECT ACK_TIMER 0 0 1 NOP AWAIT_REJECT
+AWAIT_REJECT ACK_TIMER 0 1 0 NOP AWAIT_REJECT
+AWAIT_REJECT ACK_TIMER 0 1 1 NOP AWAIT_REJECT
+AWAIT_REJECT ACK_TIMER 1 0 0 SH11 RESET_WAIT
+AWAIT_REJECT ACK_TIMER 1 0 1 SH11 RESET_WAIT
+AWAIT_REJECT ACK_TIMER 1 1 0 SH11 RESET_WAIT
+AWAIT_REJECT ACK_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+AWAIT_REJECT P_TIMER 0 0 0 NOP AWAIT_REJECT
+AWAIT_REJECT P_TIMER 0 0 1 NOP AWAIT_REJECT
+AWAIT_REJECT P_TIMER 0 1 0 NOP AWAIT_REJECT
+AWAIT_REJECT P_TIMER 0 1 1 NOP AWAIT_REJECT
+AWAIT_REJECT P_TIMER 1 0 0 SH11 RESET_WAIT
+AWAIT_REJECT P_TIMER 1 0 1 SH11 RESET_WAIT
+AWAIT_REJECT P_TIMER 1 1 0 SH11 RESET_WAIT
+AWAIT_REJECT P_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+AWAIT_REJECT REJ_TIMER 0 0 0 NOP AWAIT_REJECT
+AWAIT_REJECT REJ_TIMER 0 0 1 NOP AWAIT_REJECT
+AWAIT_REJECT REJ_TIMER 0 1 0 NOP AWAIT_REJECT
+AWAIT_REJECT REJ_TIMER 0 1 1 NOP AWAIT_REJECT
+AWAIT_REJECT REJ_TIMER 1 0 0 SH11 RESET_WAIT
+AWAIT_REJECT REJ_TIMER 1 0 1 SH11 RESET_WAIT
+AWAIT_REJECT REJ_TIMER 1 1 0 SH11 RESET_WAIT
+AWAIT_REJECT REJ_TIMER 1 1 1 SH11 RESET_WAIT
+;;
+AWAIT_REJECT BUSY_TIMER 0 0 0 NOP AWAIT_REJECT
+AWAIT_REJECT BUSY_TIMER 0 0 1 NOP AWAIT_REJECT
+AWAIT_REJECT BUSY_TIMER 0 1 0 NOP AWAIT_REJECT
+AWAIT_REJECT BUSY_TIMER 0 1 1 NOP AWAIT_REJECT
+AWAIT_REJECT BUSY_TIMER 1 0 0 SH11 RESET_WAIT
+AWAIT_REJECT BUSY_TIMER 1 0 1 SH11 RESET_WAIT
+AWAIT_REJECT BUSY_TIMER 1 1 0 SH11 RESET_WAIT
+AWAIT_REJECT BUSY_TIMER 1 1 1 SH11 RESET_WAIT
+;;
diff --git a/uClinux-2.4.31-uc0/net/8021q/Makefile b/uClinux-2.4.31-uc0/net/8021q/Makefile
new file mode 100644
index 0000000..e7ab840
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/8021q/Makefile
@@ -0,0 +1,15 @@
+#
+# Makefile for the Linux VLAN layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := 8021q.o
+
+obj-y := vlan.o vlanproc.o vlan_dev.o
+obj-m := $(O_TARGET)
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/8021q/vlan.c b/uClinux-2.4.31-uc0/net/8021q/vlan.c
new file mode 100644
index 0000000..12257b2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/8021q/vlan.c
@@ -0,0 +1,785 @@
+/* -*- linux-c -*-
+ * INET 802.1Q VLAN
+ * Ethernet-type device handling.
+ *
+ * Authors: Ben Greear <greearb@candelatech.com>
+ * Please send support related email to: vlan@scry.wanfear.com
+ * VLAN Home Page: http://www.candelatech.com/~greear/vlan.html
+ *
+ * Fixes:
+ * Fix for packet capture - Nick Eggleston <nick@dccinc.com>;
+ * Add HW acceleration hooks - David S. Miller <davem@redhat.com>;
+ * Correct all the locking - David S. Miller <davem@redhat.com>;
+ * Use hash table for VLAN groups - David S. Miller <davem@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <asm/uaccess.h> /* for copy_from_user */
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/datalink.h>
+#include <linux/mm.h>
+#include <linux/in.h>
+#include <linux/init.h>
+#include <net/p8022.h>
+#include <net/arp.h>
+#include <linux/rtnetlink.h>
+#include <linux/brlock.h>
+#include <linux/notifier.h>
+
+#include <linux/if_vlan.h>
+#include "vlan.h"
+#include "vlanproc.h"
+
+/* Global VLAN variables */
+
+/* Our listing of VLAN group(s) */
+struct vlan_group *vlan_group_hash[VLAN_GRP_HASH_SIZE];
+spinlock_t vlan_group_lock = SPIN_LOCK_UNLOCKED;
+#define vlan_grp_hashfn(IDX) ((((IDX) >> VLAN_GRP_HASH_SHIFT) ^ (IDX)) & VLAN_GRP_HASH_MASK)
+
+static char vlan_fullname[] = "802.1Q VLAN Support";
+static unsigned int vlan_version = 1;
+static unsigned int vlan_release = 8;
+static char vlan_copyright[] = "Ben Greear <greearb@candelatech.com>";
+static char vlan_buggyright[] = "David S. Miller <davem@redhat.com>";
+
+static int vlan_device_event(struct notifier_block *, unsigned long, void *);
+
+struct notifier_block vlan_notifier_block = {
+ notifier_call: vlan_device_event,
+};
+
+/* These may be changed at run-time through IOCTLs */
+
+/* Determines interface naming scheme. */
+unsigned short vlan_name_type = VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD;
+
+/* DO reorder the header by default */
+unsigned short vlan_default_dev_flags = 1;
+
+static struct packet_type vlan_packet_type = {
+ type: __constant_htons(ETH_P_8021Q),
+ dev: NULL,
+ func: vlan_skb_recv, /* VLAN receive method */
+ data: (void *)(-1), /* Set here '(void *)1' when this code can SHARE SKBs */
+ next: NULL
+};
+
+/* End of global variables definitions. */
+
+/*
+ * Function vlan_proto_init (pro)
+ *
+ * Initialize VLAN protocol layer,
+ *
+ */
+static int __init vlan_proto_init(void)
+{
+ int err;
+
+ printk(VLAN_INF "%s v%u.%u %s\n",
+ vlan_fullname, vlan_version, vlan_release, vlan_copyright);
+ printk(VLAN_INF "Other stuff added by %s\n",
+ vlan_buggyright);
+
+ /* proc file system initialization */
+ err = vlan_proc_init();
+ if (err < 0) {
+ printk(KERN_ERR
+ "%s %s: can't create entry in proc filesystem!\n",
+ __FUNCTION__, VLAN_NAME);
+ return 1;
+ }
+
+ dev_add_pack(&vlan_packet_type);
+
+ /* Register us to receive netdevice events */
+ register_netdevice_notifier(&vlan_notifier_block);
+
+ vlan_ioctl_hook = vlan_ioctl_handler;
+
+ return 0;
+}
+
+/*
+ * Module 'remove' entry point.
+ * o delete /proc/net/router directory and static entries.
+ */
+static void __exit vlan_cleanup_module(void)
+{
+ int i;
+
+ /* This table must be empty if there are no module
+ * references left.
+ */
+ for (i = 0; i < VLAN_GRP_HASH_SIZE; i++) {
+ if (vlan_group_hash[i] != NULL)
+ BUG();
+ }
+
+ /* Un-register us from receiving netdevice events */
+ unregister_netdevice_notifier(&vlan_notifier_block);
+
+ dev_remove_pack(&vlan_packet_type);
+ vlan_proc_cleanup();
+ vlan_ioctl_hook = NULL;
+}
+
+module_init(vlan_proto_init);
+module_exit(vlan_cleanup_module);
+
+/* Must be invoked with vlan_group_lock held. */
+static struct vlan_group *__vlan_find_group(int real_dev_ifindex)
+{
+ struct vlan_group *grp;
+
+ for (grp = vlan_group_hash[vlan_grp_hashfn(real_dev_ifindex)];
+ grp != NULL;
+ grp = grp->next) {
+ if (grp->real_dev_ifindex == real_dev_ifindex)
+ break;
+ }
+
+ return grp;
+}
+
+/* Must hold vlan_group_lock. */
+static void __grp_hash(struct vlan_group *grp)
+{
+ struct vlan_group **head;
+
+ head = &vlan_group_hash[vlan_grp_hashfn(grp->real_dev_ifindex)];
+ grp->next = *head;
+ *head = grp;
+}
+
+/* Must hold vlan_group_lock. */
+static void __grp_unhash(struct vlan_group *grp)
+{
+ struct vlan_group *next, **pprev;
+
+ pprev = &vlan_group_hash[vlan_grp_hashfn(grp->real_dev_ifindex)];
+ next = *pprev;
+ while (next != grp) {
+ pprev = &next->next;
+ next = *pprev;
+ }
+ *pprev = grp->next;
+}
+
+/* Find the protocol handler. Assumes VID < VLAN_VID_MASK.
+ *
+ * Must be invoked with vlan_group_lock held.
+ */
+struct net_device *__find_vlan_dev(struct net_device *real_dev,
+ unsigned short VID)
+{
+ struct vlan_group *grp = __vlan_find_group(real_dev->ifindex);
+
+ if (grp)
+ return grp->vlan_devices[VID];
+
+ return NULL;
+}
+
+/* This returns 0 if everything went fine.
+ * It will return 1 if the group was killed as a result.
+ * A negative return indicates failure.
+ *
+ * The RTNL lock must be held.
+ */
+static int unregister_vlan_dev(struct net_device *real_dev,
+ unsigned short vlan_id)
+{
+ struct net_device *dev = NULL;
+ int real_dev_ifindex = real_dev->ifindex;
+ struct vlan_group *grp;
+ int i, ret;
+
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "%s: VID: %i\n", __FUNCTION__, vlan_id);
+#endif
+
+ /* sanity check */
+ if (vlan_id >= VLAN_VID_MASK)
+ return -EINVAL;
+
+ spin_lock_bh(&vlan_group_lock);
+ grp = __vlan_find_group(real_dev_ifindex);
+ spin_unlock_bh(&vlan_group_lock);
+
+ ret = 0;
+
+ if (grp) {
+ dev = grp->vlan_devices[vlan_id];
+ if (dev) {
+ /* Remove proc entry */
+ vlan_proc_rem_dev(dev);
+
+ /* Take it out of our own structures, but be sure to
+ * interlock with HW accelerating devices or SW vlan
+ * input packet processing.
+ */
+ if (real_dev->features &
+ (NETIF_F_HW_VLAN_RX | NETIF_F_HW_VLAN_FILTER)) {
+ real_dev->vlan_rx_kill_vid(real_dev, vlan_id);
+ }
+
+ br_write_lock(BR_NETPROTO_LOCK);
+ grp->vlan_devices[vlan_id] = NULL;
+ br_write_unlock(BR_NETPROTO_LOCK);
+
+
+ /* Caller unregisters (and if necessary, puts)
+ * VLAN device, but we get rid of the reference to
+ * real_dev here.
+ */
+ dev_put(real_dev);
+
+ /* If the group is now empty, kill off the
+ * group.
+ */
+ for (i = 0; i < VLAN_VID_MASK; i++)
+ if (grp->vlan_devices[i])
+ break;
+
+ if (i == VLAN_VID_MASK) {
+ if (real_dev->features & NETIF_F_HW_VLAN_RX)
+ real_dev->vlan_rx_register(real_dev, NULL);
+
+ spin_lock_bh(&vlan_group_lock);
+ __grp_unhash(grp);
+ spin_unlock_bh(&vlan_group_lock);
+
+ /* Free the group, after we have removed it
+ * from the hash.
+ */
+ kfree(grp);
+ grp = NULL;
+
+ ret = 1;
+ }
+
+ MOD_DEC_USE_COUNT;
+ }
+ }
+
+ return ret;
+}
+
+static int unregister_vlan_device(const char *vlan_IF_name)
+{
+ struct net_device *dev = NULL;
+ int ret;
+
+
+ dev = dev_get_by_name(vlan_IF_name);
+ ret = -EINVAL;
+ if (dev) {
+ if (dev->priv_flags & IFF_802_1Q_VLAN) {
+ rtnl_lock();
+
+ ret = unregister_vlan_dev(VLAN_DEV_INFO(dev)->real_dev,
+ VLAN_DEV_INFO(dev)->vlan_id);
+
+ dev_put(dev);
+ unregister_netdevice(dev);
+
+ rtnl_unlock();
+
+ if (ret == 1)
+ ret = 0;
+ } else {
+ printk(VLAN_ERR
+ "%s: ERROR: Tried to remove a non-vlan device "
+ "with VLAN code, name: %s priv_flags: %hX\n",
+ __FUNCTION__, dev->name, dev->priv_flags);
+ dev_put(dev);
+ ret = -EPERM;
+ }
+ } else {
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "%s: WARNING: Could not find dev.\n", __FUNCTION__);
+#endif
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/* Attach a VLAN device to a mac address (ie Ethernet Card).
+ * Returns the device that was created, or NULL if there was
+ * an error of some kind.
+ */
+static struct net_device *register_vlan_device(const char *eth_IF_name,
+ unsigned short VLAN_ID)
+{
+ struct vlan_group *grp;
+ struct net_device *new_dev;
+ struct net_device *real_dev; /* the ethernet device */
+ int malloc_size = 0;
+ int r;
+
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "%s: if_name -:%s:- vid: %i\n",
+ __FUNCTION__, eth_IF_name, VLAN_ID);
+#endif
+
+ if (VLAN_ID >= VLAN_VID_MASK)
+ goto out_ret_null;
+
+ /* find the device relating to eth_IF_name. */
+ real_dev = dev_get_by_name(eth_IF_name);
+ if (!real_dev)
+ goto out_ret_null;
+
+ if (real_dev->features & NETIF_F_VLAN_CHALLENGED) {
+ printk(VLAN_DBG "%s: VLANs not supported on %s.\n",
+ __FUNCTION__, real_dev->name);
+ goto out_put_dev;
+ }
+
+ if ((real_dev->features & NETIF_F_HW_VLAN_RX) &&
+ (real_dev->vlan_rx_register == NULL ||
+ real_dev->vlan_rx_kill_vid == NULL)) {
+ printk(VLAN_DBG "%s: Device %s has buggy VLAN hw accel.\n",
+ __FUNCTION__, real_dev->name);
+ goto out_put_dev;
+ }
+
+ if ((real_dev->features & NETIF_F_HW_VLAN_FILTER) &&
+ (real_dev->vlan_rx_add_vid == NULL ||
+ real_dev->vlan_rx_kill_vid == NULL)) {
+ printk(VLAN_DBG "%s: Device %s has buggy VLAN hw accel.\n",
+ __FUNCTION__, real_dev->name);
+ goto out_put_dev;
+ }
+
+ /* From this point on, all the data structures must remain
+ * consistent.
+ */
+ rtnl_lock();
+
+ /* The real device must be up and operating in order to
+ * assosciate a VLAN device with it.
+ */
+ if (!(real_dev->flags & IFF_UP))
+ goto out_unlock;
+
+ spin_lock_bh(&vlan_group_lock);
+ r = (__find_vlan_dev(real_dev, VLAN_ID) != NULL);
+ spin_unlock_bh(&vlan_group_lock);
+
+ if (r) {
+ /* was already registered. */
+ printk(VLAN_DBG "%s: ALREADY had VLAN registered\n", __FUNCTION__);
+ goto out_unlock;
+ }
+
+ malloc_size = (sizeof(struct net_device));
+ new_dev = (struct net_device *) kmalloc(malloc_size, GFP_KERNEL);
+ VLAN_MEM_DBG("net_device malloc, addr: %p size: %i\n",
+ new_dev, malloc_size);
+
+ if (new_dev == NULL)
+ goto out_unlock;
+
+ memset(new_dev, 0, malloc_size);
+
+ /* Set us up to have no queue, as the underlying Hardware device
+ * can do all the queueing we could want.
+ */
+ new_dev->tx_queue_len = 0;
+
+ /* Gotta set up the fields for the device. */
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "About to allocate name, vlan_name_type: %i\n",
+ vlan_name_type);
+#endif
+ switch (vlan_name_type) {
+ case VLAN_NAME_TYPE_RAW_PLUS_VID:
+ /* name will look like: eth1.0005 */
+ sprintf(new_dev->name, "%s.%.4i", real_dev->name, VLAN_ID);
+ break;
+ case VLAN_NAME_TYPE_PLUS_VID_NO_PAD:
+ /* Put our vlan.VID in the name.
+ * Name will look like: vlan5
+ */
+ sprintf(new_dev->name, "vlan%i", VLAN_ID);
+ break;
+ case VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD:
+ /* Put our vlan.VID in the name.
+ * Name will look like: eth0.5
+ */
+ sprintf(new_dev->name, "%s.%i", real_dev->name, VLAN_ID);
+ break;
+ case VLAN_NAME_TYPE_PLUS_VID:
+ /* Put our vlan.VID in the name.
+ * Name will look like: vlan0005
+ */
+ default:
+ sprintf(new_dev->name, "vlan%.4i", VLAN_ID);
+ };
+
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "Allocated new name -:%s:-\n", new_dev->name);
+#endif
+ /* set up method calls */
+ new_dev->init = vlan_dev_init;
+ new_dev->destructor = vlan_dev_destruct;
+ new_dev->features |= NETIF_F_DYNALLOC ;
+
+ /* new_dev->ifindex = 0; it will be set when added to
+ * the global list.
+ * iflink is set as well.
+ */
+ new_dev->get_stats = vlan_dev_get_stats;
+
+ /* IFF_BROADCAST|IFF_MULTICAST; ??? */
+ new_dev->flags = real_dev->flags;
+ new_dev->flags &= ~IFF_UP;
+
+ /* Make this thing known as a VLAN device */
+ new_dev->priv_flags |= IFF_802_1Q_VLAN;
+
+ /* need 4 bytes for extra VLAN header info,
+ * hope the underlying device can handle it.
+ */
+ new_dev->mtu = real_dev->mtu;
+ new_dev->change_mtu = vlan_dev_change_mtu;
+
+ /* TODO: maybe just assign it to be ETHERNET? */
+ new_dev->type = real_dev->type;
+
+ new_dev->hard_header_len = real_dev->hard_header_len;
+ if (!(real_dev->features & NETIF_F_HW_VLAN_TX)) {
+ /* Regular ethernet + 4 bytes (18 total). */
+ new_dev->hard_header_len += VLAN_HLEN;
+ }
+
+ new_dev->priv = kmalloc(sizeof(struct vlan_dev_info),
+ GFP_KERNEL);
+ VLAN_MEM_DBG("new_dev->priv malloc, addr: %p size: %i\n",
+ new_dev->priv,
+ sizeof(struct vlan_dev_info));
+
+ if (new_dev->priv == NULL)
+ goto out_free_newdev;
+
+ memset(new_dev->priv, 0, sizeof(struct vlan_dev_info));
+
+ memcpy(new_dev->broadcast, real_dev->broadcast, real_dev->addr_len);
+ memcpy(new_dev->dev_addr, real_dev->dev_addr, real_dev->addr_len);
+ new_dev->addr_len = real_dev->addr_len;
+
+ new_dev->open = vlan_dev_open;
+ new_dev->stop = vlan_dev_stop;
+
+ if (real_dev->features & NETIF_F_HW_VLAN_TX) {
+ new_dev->hard_header = real_dev->hard_header;
+ new_dev->hard_start_xmit = vlan_dev_hwaccel_hard_start_xmit;
+ new_dev->rebuild_header = real_dev->rebuild_header;
+ } else {
+ new_dev->hard_header = vlan_dev_hard_header;
+ new_dev->hard_start_xmit = vlan_dev_hard_start_xmit;
+ new_dev->rebuild_header = vlan_dev_rebuild_header;
+ }
+ new_dev->hard_header_parse = real_dev->hard_header_parse;
+ new_dev->set_mac_address = vlan_dev_set_mac_address;
+ new_dev->set_multicast_list = vlan_dev_set_multicast_list;
+
+ VLAN_DEV_INFO(new_dev)->vlan_id = VLAN_ID; /* 1 through VLAN_VID_MASK */
+ VLAN_DEV_INFO(new_dev)->real_dev = real_dev;
+ VLAN_DEV_INFO(new_dev)->dent = NULL;
+ VLAN_DEV_INFO(new_dev)->flags = vlan_default_dev_flags;
+
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "About to go find the group for idx: %i\n",
+ real_dev->ifindex);
+#endif
+
+ /* So, got the sucker initialized, now lets place
+ * it into our local structure.
+ */
+ spin_lock_bh(&vlan_group_lock);
+ grp = __vlan_find_group(real_dev->ifindex);
+ spin_unlock_bh(&vlan_group_lock);
+
+ /* Note, we are running under the RTNL semaphore
+ * so it cannot "appear" on us.
+ */
+ if (!grp) { /* need to add a new group */
+ grp = kmalloc(sizeof(struct vlan_group), GFP_KERNEL);
+ if (!grp)
+ goto out_free_newdev_priv;
+
+ /* printk(KERN_ALERT "VLAN REGISTER: Allocated new group.\n"); */
+ memset(grp, 0, sizeof(struct vlan_group));
+ grp->real_dev_ifindex = real_dev->ifindex;
+
+ spin_lock_bh(&vlan_group_lock);
+ __grp_hash(grp);
+ spin_unlock_bh(&vlan_group_lock);
+
+ if (real_dev->features & NETIF_F_HW_VLAN_RX)
+ real_dev->vlan_rx_register(real_dev, grp);
+ }
+
+ grp->vlan_devices[VLAN_ID] = new_dev;
+
+ if (vlan_proc_add_dev(new_dev)<0)/* create it's proc entry */
+ printk(KERN_WARNING "VLAN: failed to add proc entry for %s\n",
+ new_dev->name);
+
+ if (real_dev->features & NETIF_F_HW_VLAN_FILTER)
+ real_dev->vlan_rx_add_vid(real_dev, VLAN_ID);
+
+ register_netdevice(new_dev);
+
+ rtnl_unlock();
+
+ /* NOTE: We have a reference to the real device,
+ * so hold on to the reference.
+ */
+ MOD_INC_USE_COUNT; /* Add was a success!! */
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "Allocated new device successfully, returning.\n");
+#endif
+ return new_dev;
+
+out_free_newdev_priv:
+ kfree(new_dev->priv);
+
+out_free_newdev:
+ kfree(new_dev);
+
+out_unlock:
+ rtnl_unlock();
+
+out_put_dev:
+ dev_put(real_dev);
+
+out_ret_null:
+ return NULL;
+}
+
+static int vlan_device_event(struct notifier_block *unused, unsigned long event, void *ptr)
+{
+ struct net_device *dev = (struct net_device *)(ptr);
+ struct vlan_group *grp = NULL;
+ int i, flgs;
+ struct net_device *vlandev = NULL;
+
+ spin_lock_bh(&vlan_group_lock);
+ grp = __vlan_find_group(dev->ifindex);
+ spin_unlock_bh(&vlan_group_lock);
+
+ if (!grp)
+ goto out;
+
+ /* It is OK that we do not hold the group lock right now,
+ * as we run under the RTNL lock.
+ */
+
+ switch (event) {
+ case NETDEV_CHANGEADDR:
+ case NETDEV_GOING_DOWN:
+ /* Ignore for now */
+ break;
+
+ case NETDEV_DOWN:
+ /* Put all VLANs for this dev in the down state too. */
+ for (i = 0; i < VLAN_GROUP_ARRAY_LEN; i++) {
+ vlandev = grp->vlan_devices[i];
+ if (!vlandev)
+ continue;
+
+ flgs = vlandev->flags;
+ if (!(flgs & IFF_UP))
+ continue;
+
+ dev_change_flags(vlandev, flgs & ~IFF_UP);
+ }
+ break;
+
+ case NETDEV_UP:
+ /* Put all VLANs for this dev in the up state too. */
+ for (i = 0; i < VLAN_GROUP_ARRAY_LEN; i++) {
+ vlandev = grp->vlan_devices[i];
+ if (!vlandev)
+ continue;
+
+ flgs = vlandev->flags;
+ if (flgs & IFF_UP)
+ continue;
+
+ dev_change_flags(vlandev, flgs | IFF_UP);
+ }
+ break;
+
+ case NETDEV_UNREGISTER:
+ /* Delete all VLANs for this dev. */
+ for (i = 0; i < VLAN_GROUP_ARRAY_LEN; i++) {
+ int ret;
+
+ vlandev = grp->vlan_devices[i];
+ if (!vlandev)
+ continue;
+
+ ret = unregister_vlan_dev(dev,
+ VLAN_DEV_INFO(vlandev)->vlan_id);
+
+ dev_put(vlandev);
+ unregister_netdevice(vlandev);
+
+ /* Group was destroyed? */
+ if (ret == 1)
+ break;
+ }
+ break;
+ };
+
+out:
+ return NOTIFY_DONE;
+}
+
+/*
+ * VLAN IOCTL handler.
+ * o execute requested action or pass command to the device driver
+ * arg is really a void* to a vlan_ioctl_args structure.
+ */
+int vlan_ioctl_handler(unsigned long arg)
+{
+ int err = 0;
+ unsigned short vid = 0;
+ struct vlan_ioctl_args args;
+
+ if (copy_from_user(&args, (void*)arg,
+ sizeof(struct vlan_ioctl_args)))
+ return -EFAULT;
+
+ /* Null terminate this sucker, just in case. */
+ args.device1[23] = 0;
+ args.u.device2[23] = 0;
+
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "%s: args.cmd: %x\n", __FUNCTION__, args.cmd);
+#endif
+
+ switch (args.cmd) {
+ case SET_VLAN_INGRESS_PRIORITY_CMD:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ err = vlan_dev_set_ingress_priority(args.device1,
+ args.u.skb_priority,
+ args.vlan_qos);
+ break;
+
+ case SET_VLAN_EGRESS_PRIORITY_CMD:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ err = vlan_dev_set_egress_priority(args.device1,
+ args.u.skb_priority,
+ args.vlan_qos);
+ break;
+
+ case SET_VLAN_FLAG_CMD:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ err = vlan_dev_set_vlan_flag(args.device1,
+ args.u.flag,
+ args.vlan_qos);
+ break;
+
+ case SET_VLAN_NAME_TYPE_CMD:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if ((args.u.name_type >= 0) &&
+ (args.u.name_type < VLAN_NAME_TYPE_HIGHEST)) {
+ vlan_name_type = args.u.name_type;
+ err = 0;
+ } else {
+ err = -EINVAL;
+ }
+ break;
+
+ case ADD_VLAN_CMD:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ /* we have been given the name of the Ethernet Device we want to
+ * talk to: args.dev1 We also have the
+ * VLAN ID: args.u.VID
+ */
+ if (register_vlan_device(args.device1, args.u.VID)) {
+ err = 0;
+ } else {
+ err = -EINVAL;
+ }
+ break;
+
+ case DEL_VLAN_CMD:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ /* Here, the args.dev1 is the actual VLAN we want
+ * to get rid of.
+ */
+ err = unregister_vlan_device(args.device1);
+ break;
+
+ case GET_VLAN_INGRESS_PRIORITY_CMD:
+ /* TODO: Implement
+ err = vlan_dev_get_ingress_priority(args);
+ if (copy_to_user((void*)arg, &args,
+ sizeof(struct vlan_ioctl_args))) {
+ err = -EFAULT;
+ }
+ */
+ err = -EINVAL;
+ break;
+
+ case GET_VLAN_EGRESS_PRIORITY_CMD:
+ /* TODO: Implement
+ err = vlan_dev_get_egress_priority(args.device1, &(args.args);
+ if (copy_to_user((void*)arg, &args,
+ sizeof(struct vlan_ioctl_args))) {
+ err = -EFAULT;
+ }
+ */
+ err = -EINVAL;
+ break;
+
+ case GET_VLAN_REALDEV_NAME_CMD:
+ err = vlan_dev_get_realdev_name(args.device1, args.u.device2);
+ if (copy_to_user((void*)arg, &args,
+ sizeof(struct vlan_ioctl_args))) {
+ err = -EFAULT;
+ }
+ break;
+
+ case GET_VLAN_VID_CMD:
+ err = vlan_dev_get_vid(args.device1, &vid);
+ args.u.VID = vid;
+ if (copy_to_user((void*)arg, &args,
+ sizeof(struct vlan_ioctl_args))) {
+ err = -EFAULT;
+ }
+ break;
+
+ default:
+ /* pass on to underlying device instead?? */
+ printk(VLAN_DBG "%s: Unknown VLAN CMD: %x \n",
+ __FUNCTION__, args.cmd);
+ return -EINVAL;
+ };
+
+ return err;
+}
+
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/8021q/vlan.h b/uClinux-2.4.31-uc0/net/8021q/vlan.h
new file mode 100644
index 0000000..c24e424
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/8021q/vlan.h
@@ -0,0 +1,79 @@
+#ifndef __BEN_VLAN_802_1Q_INC__
+#define __BEN_VLAN_802_1Q_INC__
+
+#include <linux/if_vlan.h>
+
+/* Uncomment this if you want debug traces to be shown. */
+/* #define VLAN_DEBUG */
+
+#define VLAN_ERR KERN_ERR
+#define VLAN_INF KERN_INFO
+#define VLAN_DBG KERN_DEBUG /* change these... to debug, having a hard time
+ * changing the log level at run-time..for some reason.
+ */
+
+/*
+
+These I use for memory debugging. I feared a leak at one time, but
+I never found it..and the problem seems to have dissappeared. Still,
+I'll bet they might prove useful again... --Ben
+
+
+#define VLAN_MEM_DBG(x, y, z) printk(VLAN_DBG __FUNCTION__ ": " x, y, z);
+#define VLAN_FMEM_DBG(x, y) printk(VLAN_DBG __FUNCTION__ ": " x, y);
+*/
+
+/* This way they don't do anything! */
+#define VLAN_MEM_DBG(x, y, z)
+#define VLAN_FMEM_DBG(x, y)
+
+
+extern unsigned short vlan_name_type;
+
+int vlan_ioctl_handler(unsigned long arg);
+
+#define VLAN_GRP_HASH_SHIFT 5
+#define VLAN_GRP_HASH_SIZE (1 << VLAN_GRP_HASH_SHIFT)
+#define VLAN_GRP_HASH_MASK (VLAN_GRP_HASH_SIZE - 1)
+extern struct vlan_group *vlan_group_hash[VLAN_GRP_HASH_SIZE];
+extern spinlock_t vlan_group_lock;
+
+/* Find a VLAN device by the MAC address of it's Ethernet device, and
+ * it's VLAN ID. The default configuration is to have VLAN's scope
+ * to be box-wide, so the MAC will be ignored. The mac will only be
+ * looked at if we are configured to have a seperate set of VLANs per
+ * each MAC addressable interface. Note that this latter option does
+ * NOT follow the spec for VLANs, but may be useful for doing very
+ * large quantities of VLAN MUX/DEMUX onto FrameRelay or ATM PVCs.
+ *
+ * Must be invoked with vlan_group_lock held and that lock MUST NOT
+ * be dropped until a reference is obtained on the returned device.
+ * You may drop the lock earlier if you are running under the RTNL
+ * semaphore, however.
+ */
+struct net_device *__find_vlan_dev(struct net_device* real_dev,
+ unsigned short VID); /* vlan.c */
+
+/* found in vlan_dev.c */
+int vlan_dev_rebuild_header(struct sk_buff *skb);
+int vlan_skb_recv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type* ptype);
+int vlan_dev_hard_header(struct sk_buff *skb, struct net_device *dev,
+ unsigned short type, void *daddr, void *saddr,
+ unsigned len);
+int vlan_dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev);
+int vlan_dev_hwaccel_hard_start_xmit(struct sk_buff *skb, struct net_device *dev);
+int vlan_dev_change_mtu(struct net_device *dev, int new_mtu);
+int vlan_dev_set_mac_address(struct net_device *dev, void* addr);
+int vlan_dev_open(struct net_device* dev);
+int vlan_dev_stop(struct net_device* dev);
+int vlan_dev_init(struct net_device* dev);
+void vlan_dev_destruct(struct net_device* dev);
+int vlan_dev_set_ingress_priority(char* dev_name, __u32 skb_prio, short vlan_prio);
+int vlan_dev_set_egress_priority(char* dev_name, __u32 skb_prio, short vlan_prio);
+int vlan_dev_set_vlan_flag(char* dev_name, __u32 flag, short flag_val);
+void vlan_dev_set_multicast_list(struct net_device *vlan_dev);
+int vlan_dev_get_realdev_name(const char *dev_name, char* result);
+int vlan_dev_get_vid(const char *dev_name, unsigned short* result);
+
+#endif /* !(__BEN_VLAN_802_1Q_INC__) */
diff --git a/uClinux-2.4.31-uc0/net/8021q/vlan_dev.c b/uClinux-2.4.31-uc0/net/8021q/vlan_dev.c
new file mode 100644
index 0000000..2f0100d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/8021q/vlan_dev.c
@@ -0,0 +1,904 @@
+/* -*- linux-c -*-
+ * INET 802.1Q VLAN
+ * Ethernet-type device handling.
+ *
+ * Authors: Ben Greear <greearb@candelatech.com>
+ * Please send support related email to: vlan@scry.wanfear.com
+ * VLAN Home Page: http://www.candelatech.com/~greear/vlan.html
+ *
+ * Fixes: Mar 22 2001: Martin Bokaemper <mbokaemper@unispherenetworks.com>
+ * - reset skb->pkt_type on incoming packets when MAC was changed
+ * - see that changed MAC is saddr for outgoing packets
+ * Oct 20, 2001: Ard van Breeman:
+ * - Fix MC-list, finally.
+ * - Flush MC-list on VLAN destroy.
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/in.h>
+#include <linux/init.h>
+#include <asm/uaccess.h> /* for copy_from_user */
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/datalink.h>
+#include <net/p8022.h>
+#include <net/arp.h>
+#include <linux/brlock.h>
+
+#include "vlan.h"
+#include "vlanproc.h"
+#include <linux/if_vlan.h>
+#include <net/ip.h>
+
+/*
+ * Rebuild the Ethernet MAC header. This is called after an ARP
+ * (or in future other address resolution) has completed on this
+ * sk_buff. We now let ARP fill in the other fields.
+ *
+ * This routine CANNOT use cached dst->neigh!
+ * Really, it is used only when dst->neigh is wrong.
+ *
+ * TODO: This needs a checkup, I'm ignorant here. --BLG
+ */
+int vlan_dev_rebuild_header(struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+ struct vlan_ethhdr *veth = (struct vlan_ethhdr *)(skb->data);
+
+ switch (veth->h_vlan_encapsulated_proto) {
+#ifdef CONFIG_INET
+ case __constant_htons(ETH_P_IP):
+
+ /* TODO: Confirm this will work with VLAN headers... */
+ return arp_find(veth->h_dest, skb);
+#endif
+ default:
+ printk(VLAN_DBG
+ "%s: unable to resolve type %X addresses.\n",
+ dev->name, (int)veth->h_vlan_encapsulated_proto);
+
+ memcpy(veth->h_source, dev->dev_addr, ETH_ALEN);
+ break;
+ };
+
+ return 0;
+}
+
+static inline struct sk_buff *vlan_check_reorder_header(struct sk_buff *skb)
+{
+ if (VLAN_DEV_INFO(skb->dev)->flags & 1) {
+ if (skb_shared(skb) || skb_cloned(skb)) {
+ struct sk_buff *nskb = skb_copy(skb, GFP_ATOMIC);
+ kfree_skb(skb);
+ skb = nskb;
+ }
+ if (skb) {
+ /* Lifted from Gleb's VLAN code... */
+ memmove(skb->data - ETH_HLEN,
+ skb->data - VLAN_ETH_HLEN, 12);
+ skb->mac.raw += VLAN_HLEN;
+ }
+ }
+
+ return skb;
+}
+
+/*
+ * Determine the packet's protocol ID. The rule here is that we
+ * assume 802.3 if the type field is short enough to be a length.
+ * This is normal practice and works for any 'now in use' protocol.
+ *
+ * Also, at this point we assume that we ARE dealing exclusively with
+ * VLAN packets, or packets that should be made into VLAN packets based
+ * on a default VLAN ID.
+ *
+ * NOTE: Should be similar to ethernet/eth.c.
+ *
+ * SANITY NOTE: This method is called when a packet is moving up the stack
+ * towards userland. To get here, it would have already passed
+ * through the ethernet/eth.c eth_type_trans() method.
+ * SANITY NOTE 2: We are referencing to the VLAN_HDR frields, which MAY be
+ * stored UNALIGNED in the memory. RISC systems don't like
+ * such cases very much...
+ * SANITY NOTE 2a: According to Dave Miller & Alexey, it will always be aligned,
+ * so there doesn't need to be any of the unaligned stuff. It has
+ * been commented out now... --Ben
+ *
+ */
+int vlan_skb_recv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type* ptype)
+{
+ unsigned char *rawp = NULL;
+ struct vlan_hdr *vhdr = (struct vlan_hdr *)(skb->data);
+ unsigned short vid;
+ struct net_device_stats *stats;
+ unsigned short vlan_TCI;
+ unsigned short proto;
+
+ /* vlan_TCI = ntohs(get_unaligned(&vhdr->h_vlan_TCI)); */
+ vlan_TCI = ntohs(vhdr->h_vlan_TCI);
+
+ vid = (vlan_TCI & VLAN_VID_MASK);
+
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "%s: skb: %p vlan_id: %hx\n",
+ __FUNCTION__, skb, vid);
+#endif
+
+ /* Ok, we will find the correct VLAN device, strip the header,
+ * and then go on as usual.
+ */
+
+ /* We have 12 bits of vlan ID.
+ *
+ * We must not drop the vlan_group_lock until we hold a
+ * reference to the device (netif_rx does that) or we
+ * fail.
+ */
+
+ spin_lock_bh(&vlan_group_lock);
+ skb->dev = __find_vlan_dev(dev, vid);
+ if (!skb->dev) {
+ spin_unlock_bh(&vlan_group_lock);
+
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "%s: ERROR: No net_device for VID: %i on dev: %s [%i]\n",
+ __FUNCTION__, (unsigned int)(vid), dev->name, dev->ifindex);
+#endif
+ kfree_skb(skb);
+ return -1;
+ }
+
+ skb->dev->last_rx = jiffies;
+
+ /* Bump the rx counters for the VLAN device. */
+ stats = vlan_dev_get_stats(skb->dev);
+ stats->rx_packets++;
+ stats->rx_bytes += skb->len;
+
+ skb_pull(skb, VLAN_HLEN); /* take off the VLAN header (4 bytes currently) */
+
+ /* Ok, lets check to make sure the device (dev) we
+ * came in on is what this VLAN is attached to.
+ */
+
+ if (dev != VLAN_DEV_INFO(skb->dev)->real_dev) {
+ spin_unlock_bh(&vlan_group_lock);
+
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "%s: dropping skb: %p because came in on wrong device, dev: %s real_dev: %s, skb_dev: %s\n",
+ __FUNCTION__, skb, dev->name,
+ VLAN_DEV_INFO(skb->dev)->real_dev->name,
+ skb->dev->name);
+#endif
+ kfree_skb(skb);
+ stats->rx_errors++;
+ return -1;
+ }
+
+ /*
+ * Deal with ingress priority mapping.
+ */
+ skb->priority = vlan_get_ingress_priority(skb->dev, ntohs(vhdr->h_vlan_TCI));
+
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "%s: priority: %lu for TCI: %hu (hbo)\n",
+ __FUNCTION__, (unsigned long)(skb->priority),
+ ntohs(vhdr->h_vlan_TCI));
+#endif
+
+ /* The ethernet driver already did the pkt_type calculations
+ * for us...
+ */
+ switch (skb->pkt_type) {
+ case PACKET_BROADCAST: /* Yeah, stats collect these together.. */
+ // stats->broadcast ++; // no such counter :-(
+ break;
+
+ case PACKET_MULTICAST:
+ stats->multicast++;
+ break;
+
+ case PACKET_OTHERHOST:
+ /* Our lower layer thinks this is not local, let's make sure.
+ * This allows the VLAN to have a different MAC than the underlying
+ * device, and still route correctly.
+ */
+ if (memcmp(skb->mac.ethernet->h_dest, skb->dev->dev_addr, ETH_ALEN) == 0) {
+ /* It is for our (changed) MAC-address! */
+ skb->pkt_type = PACKET_HOST;
+ }
+ break;
+ default:
+ break;
+ };
+
+ /* Was a VLAN packet, grab the encapsulated protocol, which the layer
+ * three protocols care about.
+ */
+ /* proto = get_unaligned(&vhdr->h_vlan_encapsulated_proto); */
+ proto = vhdr->h_vlan_encapsulated_proto;
+
+ skb->protocol = proto;
+ if (ntohs(proto) >= 1536) {
+ /* place it back on the queue to be handled by
+ * true layer 3 protocols.
+ */
+
+ /* See if we are configured to re-write the VLAN header
+ * to make it look like ethernet...
+ */
+ skb = vlan_check_reorder_header(skb);
+
+ /* Can be null if skb-clone fails when re-ordering */
+ if (skb) {
+ netif_rx(skb);
+ } else {
+ /* TODO: Add a more specific counter here. */
+ stats->rx_errors++;
+ }
+ spin_unlock_bh(&vlan_group_lock);
+ return 0;
+ }
+
+ rawp = skb->data;
+
+ /*
+ * This is a magic hack to spot IPX packets. Older Novell breaks
+ * the protocol design and runs IPX over 802.3 without an 802.2 LLC
+ * layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This
+ * won't work for fault tolerant netware but does for the rest.
+ */
+ if (*(unsigned short *)rawp == 0xFFFF) {
+ skb->protocol = __constant_htons(ETH_P_802_3);
+ /* place it back on the queue to be handled by true layer 3 protocols.
+ */
+
+ /* See if we are configured to re-write the VLAN header
+ * to make it look like ethernet...
+ */
+ skb = vlan_check_reorder_header(skb);
+
+ /* Can be null if skb-clone fails when re-ordering */
+ if (skb) {
+ netif_rx(skb);
+ } else {
+ /* TODO: Add a more specific counter here. */
+ stats->rx_errors++;
+ }
+ spin_unlock_bh(&vlan_group_lock);
+ return 0;
+ }
+
+ /*
+ * Real 802.2 LLC
+ */
+ skb->protocol = __constant_htons(ETH_P_802_2);
+ /* place it back on the queue to be handled by upper layer protocols.
+ */
+
+ /* See if we are configured to re-write the VLAN header
+ * to make it look like ethernet...
+ */
+ skb = vlan_check_reorder_header(skb);
+
+ /* Can be null if skb-clone fails when re-ordering */
+ if (skb) {
+ netif_rx(skb);
+ } else {
+ /* TODO: Add a more specific counter here. */
+ stats->rx_errors++;
+ }
+ spin_unlock_bh(&vlan_group_lock);
+ return 0;
+}
+
+static inline unsigned short vlan_dev_get_egress_qos_mask(struct net_device* dev,
+ struct sk_buff* skb)
+{
+ struct vlan_priority_tci_mapping *mp =
+ VLAN_DEV_INFO(dev)->egress_priority_map[(skb->priority & 0xF)];
+
+ while (mp) {
+ if (mp->priority == skb->priority) {
+ return mp->vlan_qos; /* This should already be shifted to mask
+ * correctly with the VLAN's TCI
+ */
+ }
+ mp = mp->next;
+ }
+ return 0;
+}
+
+/*
+ * Create the VLAN header for an arbitrary protocol layer
+ *
+ * saddr=NULL means use device source address
+ * daddr=NULL means leave destination address (eg unresolved arp)
+ *
+ * This is called when the SKB is moving down the stack towards the
+ * physical devices.
+ */
+int vlan_dev_hard_header(struct sk_buff *skb, struct net_device *dev,
+ unsigned short type, void *daddr, void *saddr,
+ unsigned len)
+{
+ struct vlan_hdr *vhdr;
+ unsigned short veth_TCI = 0;
+ int rc = 0;
+ int build_vlan_header = 0;
+ struct net_device *vdev = dev; /* save this for the bottom of the method */
+
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "%s: skb: %p type: %hx len: %x vlan_id: %hx, daddr: %p\n",
+ __FUNCTION__, skb, type, len, VLAN_DEV_INFO(dev)->vlan_id, daddr);
+#endif
+
+ /* build vlan header only if re_order_header flag is NOT set. This
+ * fixes some programs that get confused when they see a VLAN device
+ * sending a frame that is VLAN encoded (the consensus is that the VLAN
+ * device should look completely like an Ethernet device when the
+ * REORDER_HEADER flag is set) The drawback to this is some extra
+ * header shuffling in the hard_start_xmit. Users can turn off this
+ * REORDER behaviour with the vconfig tool.
+ */
+ build_vlan_header = ((VLAN_DEV_INFO(dev)->flags & 1) == 0);
+
+ if (build_vlan_header) {
+ vhdr = (struct vlan_hdr *) skb_push(skb, VLAN_HLEN);
+
+ /* build the four bytes that make this a VLAN header. */
+
+ /* Now, construct the second two bytes. This field looks something
+ * like:
+ * usr_priority: 3 bits (high bits)
+ * CFI 1 bit
+ * VLAN ID 12 bits (low bits)
+ *
+ */
+ veth_TCI = VLAN_DEV_INFO(dev)->vlan_id;
+ veth_TCI |= vlan_dev_get_egress_qos_mask(dev, skb);
+
+ vhdr->h_vlan_TCI = htons(veth_TCI);
+
+ /*
+ * Set the protocol type.
+ * For a packet of type ETH_P_802_3 we put the length in here instead.
+ * It is up to the 802.2 layer to carry protocol information.
+ */
+
+ if (type != ETH_P_802_3) {
+ vhdr->h_vlan_encapsulated_proto = htons(type);
+ } else {
+ vhdr->h_vlan_encapsulated_proto = htons(len);
+ }
+ }
+
+ /* Before delegating work to the lower layer, enter our MAC-address */
+ if (saddr == NULL)
+ saddr = dev->dev_addr;
+
+ dev = VLAN_DEV_INFO(dev)->real_dev;
+
+ /* MPLS can send us skbuffs w/out enough space. This check will grow the
+ * skb if it doesn't have enough headroom. Not a beautiful solution, so
+ * I'll tick a counter so that users can know it's happening... If they
+ * care...
+ */
+
+ /* NOTE: This may still break if the underlying device is not the final
+ * device (and thus there are more headers to add...) It should work for
+ * good-ole-ethernet though.
+ */
+ if (skb_headroom(skb) < dev->hard_header_len) {
+ struct sk_buff *sk_tmp = skb;
+ skb = skb_realloc_headroom(sk_tmp, dev->hard_header_len);
+ kfree_skb(sk_tmp);
+ if (skb == NULL) {
+ struct net_device_stats *stats = vlan_dev_get_stats(vdev);
+ stats->tx_dropped++;
+ return -ENOMEM;
+ }
+ VLAN_DEV_INFO(vdev)->cnt_inc_headroom_on_tx++;
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "%s: %s: had to grow skb.\n", __FUNCTION__, vdev->name);
+#endif
+ }
+
+ if (build_vlan_header) {
+ /* Now make the underlying real hard header */
+ rc = dev->hard_header(skb, dev, ETH_P_8021Q, daddr, saddr, len + VLAN_HLEN);
+
+ if (rc > 0) {
+ rc += VLAN_HLEN;
+ } else if (rc < 0) {
+ rc -= VLAN_HLEN;
+ }
+ } else {
+ /* If here, then we'll just make a normal looking ethernet frame,
+ * but, the hard_start_xmit method will insert the tag (it has to
+ * be able to do this for bridged and other skbs that don't come
+ * down the protocol stack in an orderly manner.
+ */
+ rc = dev->hard_header(skb, dev, type, daddr, saddr, len);
+ }
+
+ return rc;
+}
+
+int vlan_dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_device_stats *stats = vlan_dev_get_stats(dev);
+ struct vlan_ethhdr *veth = (struct vlan_ethhdr *)(skb->data);
+
+ /* Handle non-VLAN frames if they are sent to us, for example by DHCP.
+ *
+ * NOTE: THIS ASSUMES DIX ETHERNET, SPECIFICALLY NOT SUPPORTING
+ * OTHER THINGS LIKE FDDI/TokenRing/802.3 SNAPs...
+ */
+
+ if (veth->h_vlan_proto != __constant_htons(ETH_P_8021Q)) {
+ int orig_headroom = skb_headroom(skb);
+ unsigned short veth_TCI;
+
+ /* This is not a VLAN frame...but we can fix that! */
+ VLAN_DEV_INFO(dev)->cnt_encap_on_xmit++;
+
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "%s: proto to encap: 0x%hx (hbo)\n",
+ __FUNCTION__, htons(veth->h_vlan_proto));
+#endif
+ /* Construct the second two bytes. This field looks something
+ * like:
+ * usr_priority: 3 bits (high bits)
+ * CFI 1 bit
+ * VLAN ID 12 bits (low bits)
+ */
+ veth_TCI = VLAN_DEV_INFO(dev)->vlan_id;
+ veth_TCI |= vlan_dev_get_egress_qos_mask(dev, skb);
+
+ skb = __vlan_put_tag(skb, veth_TCI);
+ if (!skb) {
+ stats->tx_dropped++;
+ return 0;
+ }
+
+ if (orig_headroom < VLAN_HLEN) {
+ VLAN_DEV_INFO(dev)->cnt_inc_headroom_on_tx++;
+ }
+ }
+
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG "%s: about to send skb: %p to dev: %s\n",
+ __FUNCTION__, skb, skb->dev->name);
+ printk(VLAN_DBG " %2hx.%2hx.%2hx.%2xh.%2hx.%2hx %2hx.%2hx.%2hx.%2hx.%2hx.%2hx %4hx %4hx %4hx\n",
+ veth->h_dest[0], veth->h_dest[1], veth->h_dest[2], veth->h_dest[3], veth->h_dest[4], veth->h_dest[5],
+ veth->h_source[0], veth->h_source[1], veth->h_source[2], veth->h_source[3], veth->h_source[4], veth->h_source[5],
+ veth->h_vlan_proto, veth->h_vlan_TCI, veth->h_vlan_encapsulated_proto);
+#endif
+
+ stats->tx_packets++; /* for statics only */
+ stats->tx_bytes += skb->len;
+
+ skb->protocol = __constant_htons(ETH_P_8021Q);
+ skb->mac.raw -= VLAN_HLEN;
+ skb->nh.raw -= VLAN_HLEN;
+
+ skb->dev = VLAN_DEV_INFO(dev)->real_dev;
+ dev_queue_xmit(skb);
+
+ return 0;
+}
+
+int vlan_dev_hwaccel_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_device_stats *stats = vlan_dev_get_stats(dev);
+ unsigned short veth_TCI;
+
+ /* Construct the second two bytes. This field looks something
+ * like:
+ * usr_priority: 3 bits (high bits)
+ * CFI 1 bit
+ * VLAN ID 12 bits (low bits)
+ */
+ veth_TCI = VLAN_DEV_INFO(dev)->vlan_id;
+ veth_TCI |= vlan_dev_get_egress_qos_mask(dev, skb);
+ skb = __vlan_hwaccel_put_tag(skb, veth_TCI);
+
+ stats->tx_packets++;
+ stats->tx_bytes += skb->len;
+
+ skb->dev = VLAN_DEV_INFO(dev)->real_dev;
+ dev_queue_xmit(skb);
+
+ return 0;
+}
+
+int vlan_dev_change_mtu(struct net_device *dev, int new_mtu)
+{
+ /* TODO: gotta make sure the underlying layer can handle it,
+ * maybe an IFF_VLAN_CAPABLE flag for devices?
+ */
+ if (VLAN_DEV_INFO(dev)->real_dev->mtu < new_mtu)
+ return -ERANGE;
+
+ dev->mtu = new_mtu;
+
+ return 0;
+}
+
+int vlan_dev_set_ingress_priority(char *dev_name, __u32 skb_prio, short vlan_prio)
+{
+ struct net_device *dev = dev_get_by_name(dev_name);
+
+ if (dev) {
+ if (dev->priv_flags & IFF_802_1Q_VLAN) {
+ /* see if a priority mapping exists.. */
+ VLAN_DEV_INFO(dev)->ingress_priority_map[vlan_prio & 0x7] = skb_prio;
+ dev_put(dev);
+ return 0;
+ }
+
+ dev_put(dev);
+ }
+ return -EINVAL;
+}
+
+int vlan_dev_set_egress_priority(char *dev_name, __u32 skb_prio, short vlan_prio)
+{
+ struct net_device *dev = dev_get_by_name(dev_name);
+ struct vlan_priority_tci_mapping *mp = NULL;
+ struct vlan_priority_tci_mapping *np;
+
+ if (dev) {
+ if (dev->priv_flags & IFF_802_1Q_VLAN) {
+ /* See if a priority mapping exists.. */
+ mp = VLAN_DEV_INFO(dev)->egress_priority_map[skb_prio & 0xF];
+ while (mp) {
+ if (mp->priority == skb_prio) {
+ mp->vlan_qos = ((vlan_prio << 13) & 0xE000);
+ dev_put(dev);
+ return 0;
+ }
+ mp = mp->next;
+ }
+
+ /* Create a new mapping then. */
+ mp = VLAN_DEV_INFO(dev)->egress_priority_map[skb_prio & 0xF];
+ np = kmalloc(sizeof(struct vlan_priority_tci_mapping), GFP_KERNEL);
+ if (np) {
+ np->next = mp;
+ np->priority = skb_prio;
+ np->vlan_qos = ((vlan_prio << 13) & 0xE000);
+ VLAN_DEV_INFO(dev)->egress_priority_map[skb_prio & 0xF] = np;
+ dev_put(dev);
+ return 0;
+ } else {
+ dev_put(dev);
+ return -ENOBUFS;
+ }
+ }
+ dev_put(dev);
+ }
+ return -EINVAL;
+}
+
+/* Flags are defined in the vlan_dev_info class in include/linux/if_vlan.h file. */
+int vlan_dev_set_vlan_flag(char *dev_name, __u32 flag, short flag_val)
+{
+ struct net_device *dev = dev_get_by_name(dev_name);
+
+ if (dev) {
+ if (dev->priv_flags & IFF_802_1Q_VLAN) {
+ /* verify flag is supported */
+ if (flag == 1) {
+ if (flag_val) {
+ VLAN_DEV_INFO(dev)->flags |= 1;
+ } else {
+ VLAN_DEV_INFO(dev)->flags &= ~1;
+ }
+ dev_put(dev);
+ return 0;
+ } else {
+ printk(KERN_ERR "%s: flag %i is not valid.\n",
+ __FUNCTION__, (int)(flag));
+ dev_put(dev);
+ return -EINVAL;
+ }
+ } else {
+ printk(KERN_ERR
+ "%s: %s is not a vlan device, priv_flags: %hX.\n",
+ __FUNCTION__, dev->name, dev->priv_flags);
+ dev_put(dev);
+ }
+ } else {
+ printk(KERN_ERR "%s: Could not find device: %s\n",
+ __FUNCTION__, dev_name);
+ }
+
+ return -EINVAL;
+}
+
+
+int vlan_dev_get_realdev_name(const char *dev_name, char* result)
+{
+ struct net_device *dev = dev_get_by_name(dev_name);
+ int rv = 0;
+
+ if (dev) {
+ if (dev->priv_flags & IFF_802_1Q_VLAN) {
+ strncpy(result, VLAN_DEV_INFO(dev)->real_dev->name, 23);
+ dev_put(dev);
+ rv = 0;
+ } else {
+ /*printk(KERN_ERR
+ "%s: %s is not a vlan device, priv_flags: %hX.\n",
+ __FUNCTION__, dev->name, dev->priv_flags);*/
+ dev_put(dev);
+ rv = -EINVAL;
+ }
+ } else {
+ /* printk(KERN_ERR "%s: Could not find device: %s\n",
+ __FUNCTION__, dev_name); */
+ rv = -ENODEV;
+ }
+
+ return rv;
+}
+
+int vlan_dev_get_vid(const char *dev_name, unsigned short* result)
+{
+ struct net_device *dev = dev_get_by_name(dev_name);
+ int rv = 0;
+
+ if (dev) {
+ if (dev->priv_flags & IFF_802_1Q_VLAN) {
+ *result = VLAN_DEV_INFO(dev)->vlan_id;
+ dev_put(dev);
+ rv = 0;
+ } else {
+ /*printk(KERN_ERR
+ "%s: %s is not a vlan device, priv_flags: %hX.\n",
+ __FUNCTION__, dev->name, dev->priv_flags);*/
+ dev_put(dev);
+ rv = -EINVAL;
+ }
+ } else {
+ /* printk(KERN_ERR "%s: Could not find device: %s\n",
+ __FUNCTION__, dev_name);*/
+ rv = -ENODEV;
+ }
+
+ return rv;
+}
+
+
+int vlan_dev_set_mac_address(struct net_device *dev, void *addr_struct_p)
+{
+ struct sockaddr *addr = (struct sockaddr *)(addr_struct_p);
+ int i;
+
+ if (netif_running(dev))
+ return -EBUSY;
+
+ memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
+
+ printk("%s: Setting MAC address to ", dev->name);
+ for (i = 0; i < 6; i++)
+ printk(" %2.2x", dev->dev_addr[i]);
+ printk(".\n");
+
+ if (memcmp(VLAN_DEV_INFO(dev)->real_dev->dev_addr,
+ dev->dev_addr,
+ dev->addr_len) != 0) {
+ if (!(VLAN_DEV_INFO(dev)->real_dev->flags & IFF_PROMISC)) {
+ int flgs = VLAN_DEV_INFO(dev)->real_dev->flags;
+
+ /* Increment our in-use promiscuity counter */
+ dev_set_promiscuity(VLAN_DEV_INFO(dev)->real_dev, 1);
+
+ /* Make PROMISC visible to the user. */
+ flgs |= IFF_PROMISC;
+ printk("VLAN (%s): Setting underlying device (%s) to promiscious mode.\n",
+ dev->name, VLAN_DEV_INFO(dev)->real_dev->name);
+ dev_change_flags(VLAN_DEV_INFO(dev)->real_dev, flgs);
+ }
+ } else {
+ printk("VLAN (%s): Underlying device (%s) has same MAC, not checking promiscious mode.\n",
+ dev->name, VLAN_DEV_INFO(dev)->real_dev->name);
+ }
+
+ return 0;
+}
+
+static inline int vlan_dmi_equals(struct dev_mc_list *dmi1,
+ struct dev_mc_list *dmi2)
+{
+ return ((dmi1->dmi_addrlen == dmi2->dmi_addrlen) &&
+ (memcmp(dmi1->dmi_addr, dmi2->dmi_addr, dmi1->dmi_addrlen) == 0));
+}
+
+/** dmi is a single entry into a dev_mc_list, a single node. mc_list is
+ * an entire list, and we'll iterate through it.
+ */
+static int vlan_should_add_mc(struct dev_mc_list *dmi, struct dev_mc_list *mc_list)
+{
+ struct dev_mc_list *idmi;
+
+ for (idmi = mc_list; idmi != NULL; ) {
+ if (vlan_dmi_equals(dmi, idmi)) {
+ if (dmi->dmi_users > idmi->dmi_users)
+ return 1;
+ else
+ return 0;
+ } else {
+ idmi = idmi->next;
+ }
+ }
+
+ return 1;
+}
+
+static inline void vlan_destroy_mc_list(struct dev_mc_list *mc_list)
+{
+ struct dev_mc_list *dmi = mc_list;
+ struct dev_mc_list *next;
+
+ while(dmi) {
+ next = dmi->next;
+ kfree(dmi);
+ dmi = next;
+ }
+}
+
+static void vlan_copy_mc_list(struct dev_mc_list *mc_list, struct vlan_dev_info *vlan_info)
+{
+ struct dev_mc_list *dmi, *new_dmi;
+
+ vlan_destroy_mc_list(vlan_info->old_mc_list);
+ vlan_info->old_mc_list = NULL;
+
+ for (dmi = mc_list; dmi != NULL; dmi = dmi->next) {
+ new_dmi = kmalloc(sizeof(*new_dmi), GFP_ATOMIC);
+ if (new_dmi == NULL) {
+ printk(KERN_ERR "vlan: cannot allocate memory. "
+ "Multicast may not work properly from now.\n");
+ return;
+ }
+
+ /* Copy whole structure, then make new 'next' pointer */
+ *new_dmi = *dmi;
+ new_dmi->next = vlan_info->old_mc_list;
+ vlan_info->old_mc_list = new_dmi;
+ }
+}
+
+static void vlan_flush_mc_list(struct net_device *dev)
+{
+ struct dev_mc_list *dmi = dev->mc_list;
+
+ while (dmi) {
+ printk(KERN_DEBUG "%s: del %.2x:%.2x:%.2x:%.2x:%.2x:%.2x mcast address from vlan interface\n",
+ dev->name,
+ dmi->dmi_addr[0],
+ dmi->dmi_addr[1],
+ dmi->dmi_addr[2],
+ dmi->dmi_addr[3],
+ dmi->dmi_addr[4],
+ dmi->dmi_addr[5]);
+ dev_mc_delete(dev, dmi->dmi_addr, dmi->dmi_addrlen, 0);
+ dmi = dev->mc_list;
+ }
+
+ /* dev->mc_list is NULL by the time we get here. */
+ vlan_destroy_mc_list(VLAN_DEV_INFO(dev)->old_mc_list);
+ VLAN_DEV_INFO(dev)->old_mc_list = NULL;
+}
+
+int vlan_dev_open(struct net_device *dev)
+{
+ if (!(VLAN_DEV_INFO(dev)->real_dev->flags & IFF_UP))
+ return -ENETDOWN;
+
+ return 0;
+}
+
+int vlan_dev_stop(struct net_device *dev)
+{
+ vlan_flush_mc_list(dev);
+ return 0;
+}
+
+int vlan_dev_init(struct net_device *dev)
+{
+ /* TODO: figure this out, maybe do nothing?? */
+ return 0;
+}
+
+void vlan_dev_destruct(struct net_device *dev)
+{
+ if (dev) {
+ vlan_flush_mc_list(dev);
+ if (dev->priv) {
+ if (VLAN_DEV_INFO(dev)->dent)
+ BUG();
+
+ kfree(dev->priv);
+ dev->priv = NULL;
+ }
+ }
+}
+
+/** Taken from Gleb + Lennert's VLAN code, and modified... */
+void vlan_dev_set_multicast_list(struct net_device *vlan_dev)
+{
+ struct dev_mc_list *dmi;
+ struct net_device *real_dev;
+ int inc;
+
+ if (vlan_dev && (vlan_dev->priv_flags & IFF_802_1Q_VLAN)) {
+ /* Then it's a real vlan device, as far as we can tell.. */
+ real_dev = VLAN_DEV_INFO(vlan_dev)->real_dev;
+
+ /* compare the current promiscuity to the last promisc we had.. */
+ inc = vlan_dev->promiscuity - VLAN_DEV_INFO(vlan_dev)->old_promiscuity;
+ if (inc) {
+ printk(KERN_INFO "%s: dev_set_promiscuity(master, %d)\n",
+ vlan_dev->name, inc);
+ dev_set_promiscuity(real_dev, inc); /* found in dev.c */
+ VLAN_DEV_INFO(vlan_dev)->old_promiscuity = vlan_dev->promiscuity;
+ }
+
+ inc = vlan_dev->allmulti - VLAN_DEV_INFO(vlan_dev)->old_allmulti;
+ if (inc) {
+ printk(KERN_INFO "%s: dev_set_allmulti(master, %d)\n",
+ vlan_dev->name, inc);
+ dev_set_allmulti(real_dev, inc); /* dev.c */
+ VLAN_DEV_INFO(vlan_dev)->old_allmulti = vlan_dev->allmulti;
+ }
+
+ /* looking for addresses to add to master's list */
+ for (dmi = vlan_dev->mc_list; dmi != NULL; dmi = dmi->next) {
+ if (vlan_should_add_mc(dmi, VLAN_DEV_INFO(vlan_dev)->old_mc_list)) {
+ dev_mc_add(real_dev, dmi->dmi_addr, dmi->dmi_addrlen, 0);
+ printk(KERN_DEBUG "%s: add %.2x:%.2x:%.2x:%.2x:%.2x:%.2x mcast address to master interface\n",
+ vlan_dev->name,
+ dmi->dmi_addr[0],
+ dmi->dmi_addr[1],
+ dmi->dmi_addr[2],
+ dmi->dmi_addr[3],
+ dmi->dmi_addr[4],
+ dmi->dmi_addr[5]);
+ }
+ }
+
+ /* looking for addresses to delete from master's list */
+ for (dmi = VLAN_DEV_INFO(vlan_dev)->old_mc_list; dmi != NULL; dmi = dmi->next) {
+ if (vlan_should_add_mc(dmi, vlan_dev->mc_list)) {
+ /* if we think we should add it to the new list, then we should really
+ * delete it from the real list on the underlying device.
+ */
+ dev_mc_delete(real_dev, dmi->dmi_addr, dmi->dmi_addrlen, 0);
+ printk(KERN_DEBUG "%s: del %.2x:%.2x:%.2x:%.2x:%.2x:%.2x mcast address from master interface\n",
+ vlan_dev->name,
+ dmi->dmi_addr[0],
+ dmi->dmi_addr[1],
+ dmi->dmi_addr[2],
+ dmi->dmi_addr[3],
+ dmi->dmi_addr[4],
+ dmi->dmi_addr[5]);
+ }
+ }
+
+ /* save multicast list */
+ vlan_copy_mc_list(vlan_dev->mc_list, VLAN_DEV_INFO(vlan_dev));
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/8021q/vlanproc.c b/uClinux-2.4.31-uc0/net/8021q/vlanproc.c
new file mode 100644
index 0000000..5246c46
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/8021q/vlanproc.c
@@ -0,0 +1,470 @@
+/******************************************************************************
+ * vlanproc.c VLAN Module. /proc filesystem interface.
+ *
+ * This module is completely hardware-independent and provides
+ * access to the router using Linux /proc filesystem.
+ *
+ * Author: Ben Greear, <greearb@candelatech.com> coppied from wanproc.c
+ * by: Gene Kozin <genek@compuserve.com>
+ *
+ * Copyright: (c) 1998 Ben Greear
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ * ============================================================================
+ * Jan 20, 1998 Ben Greear Initial Version
+ *****************************************************************************/
+
+#include <linux/config.h>
+#include <linux/stddef.h> /* offsetof(), etc. */
+#include <linux/errno.h> /* return codes */
+#include <linux/kernel.h>
+#include <linux/slab.h> /* kmalloc(), kfree() */
+#include <linux/mm.h> /* verify_area(), etc. */
+#include <linux/string.h> /* inline mem*, str* functions */
+#include <linux/init.h> /* __initfunc et al. */
+#include <asm/segment.h> /* kernel <-> user copy */
+#include <asm/byteorder.h> /* htons(), etc. */
+#include <asm/uaccess.h> /* copy_to_user */
+#include <asm/io.h>
+#include <linux/proc_fs.h>
+#include <linux/fs.h>
+#include <linux/netdevice.h>
+#include <linux/if_vlan.h>
+#include "vlanproc.h"
+#include "vlan.h"
+
+/****** Function Prototypes *************************************************/
+
+#ifdef CONFIG_PROC_FS
+
+/* Proc filesystem interface */
+static ssize_t vlan_proc_read(struct file *file, char *buf, size_t count,
+ loff_t *ppos);
+
+/* Methods for preparing data for reading proc entries */
+
+static int vlan_config_get_info(char *buf, char **start, off_t offs, int len);
+static int vlandev_get_info(char *buf, char **start, off_t offs, int len);
+
+/* Miscellaneous */
+
+/*
+ * Global Data
+ */
+
+/*
+ * Names of the proc directory entries
+ */
+
+static char name_root[] = "vlan";
+static char name_conf[] = "config";
+static char term_msg[] = "***KERNEL: Out of buffer space!***\n";
+
+/*
+ * Structures for interfacing with the /proc filesystem.
+ * VLAN creates its own directory /proc/net/vlan with the folowing
+ * entries:
+ * config device status/configuration
+ * <device> entry for each device
+ */
+
+/*
+ * Generic /proc/net/vlan/<file> file and inode operations
+ */
+
+static struct file_operations vlan_fops = {
+ read: vlan_proc_read,
+ ioctl: NULL, /* vlan_proc_ioctl */
+};
+
+/*
+ * /proc/net/vlan/<device> file and inode operations
+ */
+
+static struct file_operations vlandev_fops = {
+ read: vlan_proc_read,
+ ioctl: NULL, /* vlan_proc_ioctl */
+};
+
+/*
+ * Proc filesystem derectory entries.
+ */
+
+/*
+ * /proc/net/vlan
+ */
+
+static struct proc_dir_entry *proc_vlan_dir;
+
+/*
+ * /proc/net/vlan/config
+ */
+
+static struct proc_dir_entry *proc_vlan_conf;
+
+/* Strings */
+static char conf_hdr[] = "VLAN Dev name | VLAN ID\n";
+
+/*
+ * Interface functions
+ */
+
+/*
+ * Clean up /proc/net/vlan entries
+ */
+
+void vlan_proc_cleanup(void)
+{
+ if (proc_vlan_conf)
+ remove_proc_entry(name_conf, proc_vlan_dir);
+
+ if (proc_vlan_dir)
+ proc_net_remove(name_root);
+
+ /* Dynamically added entries should be cleaned up as their vlan_device
+ * is removed, so we should not have to take care of it here...
+ */
+}
+
+/*
+ * Create /proc/net/vlan entries
+ */
+
+int __init vlan_proc_init(void)
+{
+ proc_vlan_dir = proc_mkdir(name_root, proc_net);
+ if (proc_vlan_dir) {
+ proc_vlan_conf = create_proc_entry(name_conf,
+ S_IFREG|S_IRUSR|S_IWUSR,
+ proc_vlan_dir);
+ if (proc_vlan_conf) {
+ proc_vlan_conf->proc_fops = &vlan_fops;
+ proc_vlan_conf->get_info = vlan_config_get_info;
+ return 0;
+ }
+ }
+ vlan_proc_cleanup();
+ return -ENOBUFS;
+}
+
+/*
+ * Add directory entry for VLAN device.
+ */
+
+int vlan_proc_add_dev (struct net_device *vlandev)
+{
+ struct vlan_dev_info *dev_info = VLAN_DEV_INFO(vlandev);
+
+ if (!(vlandev->priv_flags & IFF_802_1Q_VLAN)) {
+ printk(KERN_ERR
+ "ERROR: vlan_proc_add, device -:%s:- is NOT a VLAN\n",
+ vlandev->name);
+ return -EINVAL;
+ }
+
+ dev_info->dent = create_proc_entry(vlandev->name,
+ S_IFREG|S_IRUSR|S_IWUSR,
+ proc_vlan_dir);
+ if (!dev_info->dent)
+ return -ENOBUFS;
+
+ dev_info->dent->proc_fops = &vlandev_fops;
+ dev_info->dent->get_info = &vlandev_get_info;
+ dev_info->dent->data = vlandev;
+
+#ifdef VLAN_DEBUG
+ printk(KERN_ERR "vlan_proc_add, device -:%s:- being added.\n",
+ vlandev->name);
+#endif
+ return 0;
+}
+
+/*
+ * Delete directory entry for VLAN device.
+ */
+int vlan_proc_rem_dev(struct net_device *vlandev)
+{
+ if (!vlandev) {
+ printk(VLAN_ERR "%s: invalid argument: %p\n",
+ __FUNCTION__, vlandev);
+ return -EINVAL;
+ }
+
+ if (!(vlandev->priv_flags & IFF_802_1Q_VLAN)) {
+ printk(VLAN_DBG "%s: invalid argument, device: %s is not a VLAN device, priv_flags: 0x%4hX.\n",
+ __FUNCTION__, vlandev->name, vlandev->priv_flags);
+ return -EINVAL;
+ }
+
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG __FUNCTION__ ": dev: %p\n", vlandev);
+#endif
+
+ /** NOTE: This will consume the memory pointed to by dent, it seems. */
+ if (VLAN_DEV_INFO(vlandev)->dent) {
+ remove_proc_entry(VLAN_DEV_INFO(vlandev)->dent->name, proc_vlan_dir);
+ VLAN_DEV_INFO(vlandev)->dent = NULL;
+ }
+
+ return 0;
+}
+
+/****** Proc filesystem entry points ****************************************/
+
+/*
+ * Read VLAN proc directory entry.
+ * This is universal routine for reading all entries in /proc/net/vlan
+ * directory. Each directory entry contains a pointer to the 'method' for
+ * preparing data for that entry.
+ * o verify arguments
+ * o allocate kernel buffer
+ * o call get_info() to prepare data
+ * o copy data to user space
+ * o release kernel buffer
+ *
+ * Return: number of bytes copied to user space (0, if no data)
+ * <0 error
+ */
+static ssize_t vlan_proc_read(struct file *file, char *buf,
+ size_t count, loff_t *ppos)
+{
+ struct inode *inode = file->f_dentry->d_inode;
+ struct proc_dir_entry *dent;
+ char *page;
+ int pos, len;
+ loff_t n = *ppos;
+ unsigned offs = n;
+
+ if (count <= 0)
+ return 0;
+
+ dent = inode->u.generic_ip;
+ if ((dent == NULL) || (dent->get_info == NULL))
+ return 0;
+
+ page = kmalloc(VLAN_PROC_BUFSZ, GFP_KERNEL);
+ VLAN_MEM_DBG("page malloc, addr: %p size: %i\n",
+ page, VLAN_PROC_BUFSZ);
+
+ if (page == NULL)
+ return -ENOBUFS;
+
+ pos = dent->get_info(page, dent->data, 0, 0);
+ if (offs == n && offs < pos) {
+ len = min_t(int, pos - offs, count);
+ if (copy_to_user(buf, (page + offs), len)) {
+ kfree(page);
+ return -EFAULT;
+ }
+
+ *ppos = offs + len;
+ } else {
+ len = 0;
+ }
+
+ kfree(page);
+ VLAN_FMEM_DBG("page free, addr: %p\n", page);
+ return len;
+}
+
+/*
+ * The following few functions build the content of /proc/net/vlan/config
+ */
+
+static int vlan_proc_get_vlan_info(char* buf, unsigned int cnt)
+{
+ struct net_device *vlandev = NULL;
+ struct vlan_group *grp = NULL;
+ int h, i;
+ char *nm_type = NULL;
+ struct vlan_dev_info *dev_info = NULL;
+
+#ifdef VLAN_DEBUG
+ printk(VLAN_DBG __FUNCTION__ ": cnt == %i\n", cnt);
+#endif
+
+ if (vlan_name_type == VLAN_NAME_TYPE_RAW_PLUS_VID) {
+ nm_type = "VLAN_NAME_TYPE_RAW_PLUS_VID";
+ } else if (vlan_name_type == VLAN_NAME_TYPE_PLUS_VID_NO_PAD) {
+ nm_type = "VLAN_NAME_TYPE_PLUS_VID_NO_PAD";
+ } else if (vlan_name_type == VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD) {
+ nm_type = "VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD";
+ } else if (vlan_name_type == VLAN_NAME_TYPE_PLUS_VID) {
+ nm_type = "VLAN_NAME_TYPE_PLUS_VID";
+ } else {
+ nm_type = "UNKNOWN";
+ }
+
+ cnt += sprintf(buf + cnt, "Name-Type: %s\n", nm_type);
+
+ spin_lock_bh(&vlan_group_lock);
+ for (h = 0; h < VLAN_GRP_HASH_SIZE; h++) {
+ for (grp = vlan_group_hash[h]; grp != NULL; grp = grp->next) {
+ for (i = 0; i < VLAN_GROUP_ARRAY_LEN; i++) {
+ vlandev = grp->vlan_devices[i];
+ if (!vlandev)
+ continue;
+
+ if ((cnt + 100) > VLAN_PROC_BUFSZ) {
+ if ((cnt+strlen(term_msg)) < VLAN_PROC_BUFSZ)
+ cnt += sprintf(buf+cnt, "%s", term_msg);
+
+ goto out;
+ }
+
+ dev_info = VLAN_DEV_INFO(vlandev);
+ cnt += sprintf(buf + cnt, "%-15s| %d | %s\n",
+ vlandev->name,
+ dev_info->vlan_id,
+ dev_info->real_dev->name);
+ }
+ }
+ }
+out:
+ spin_unlock_bh(&vlan_group_lock);
+
+ return cnt;
+}
+
+/*
+ * Prepare data for reading 'Config' entry.
+ * Return length of data.
+ */
+
+static int vlan_config_get_info(char *buf, char **start,
+ off_t offs, int len)
+{
+ strcpy(buf, conf_hdr);
+ return vlan_proc_get_vlan_info(buf, (unsigned int)(strlen(conf_hdr)));
+}
+
+/*
+ * Prepare data for reading <device> entry.
+ * Return length of data.
+ *
+ * On entry, the 'start' argument will contain a pointer to VLAN device
+ * data space.
+ */
+
+static int vlandev_get_info(char *buf, char **start,
+ off_t offs, int len)
+{
+ struct net_device *vlandev = (void *) start;
+ struct net_device_stats *stats = NULL;
+ struct vlan_dev_info *dev_info = NULL;
+ struct vlan_priority_tci_mapping *mp;
+ int cnt = 0;
+ int i;
+
+ if ((vlandev == NULL) || (!(vlandev->priv_flags & IFF_802_1Q_VLAN)))
+ return 0;
+
+ dev_info = VLAN_DEV_INFO(vlandev);
+
+ cnt += sprintf(buf + cnt, "%s VID: %d REORDER_HDR: %i dev->priv_flags: %hx\n",
+ vlandev->name, dev_info->vlan_id,
+ (int)(dev_info->flags & 1), vlandev->priv_flags);
+
+ stats = vlan_dev_get_stats(vlandev);
+
+ cnt += sprintf(buf + cnt, "%30s: %12lu\n",
+ "total frames received", stats->rx_packets);
+
+ cnt += sprintf(buf + cnt, "%30s: %12lu\n",
+ "total bytes received", stats->rx_bytes);
+
+ cnt += sprintf(buf + cnt, "%30s: %12lu\n",
+ "Broadcast/Multicast Rcvd", stats->multicast);
+
+ cnt += sprintf(buf + cnt, "\n%30s: %12lu\n",
+ "total frames transmitted", stats->tx_packets);
+
+ cnt += sprintf(buf + cnt, "%30s: %12lu\n",
+ "total bytes transmitted", stats->tx_bytes);
+
+ cnt += sprintf(buf + cnt, "%30s: %12lu\n",
+ "total headroom inc", dev_info->cnt_inc_headroom_on_tx);
+
+ cnt += sprintf(buf + cnt, "%30s: %12lu\n",
+ "total encap on xmit", dev_info->cnt_encap_on_xmit);
+
+ cnt += sprintf(buf + cnt, "Device: %s", dev_info->real_dev->name);
+
+ /* now show all PRIORITY mappings relating to this VLAN */
+ cnt += sprintf(buf + cnt, "\nINGRESS priority mappings: 0:%lu 1:%lu 2:%lu 3:%lu 4:%lu 5:%lu 6:%lu 7:%lu\n",
+ dev_info->ingress_priority_map[0],
+ dev_info->ingress_priority_map[1],
+ dev_info->ingress_priority_map[2],
+ dev_info->ingress_priority_map[3],
+ dev_info->ingress_priority_map[4],
+ dev_info->ingress_priority_map[5],
+ dev_info->ingress_priority_map[6],
+ dev_info->ingress_priority_map[7]);
+
+ if ((cnt + 100) > VLAN_PROC_BUFSZ) {
+ if ((cnt + strlen(term_msg)) >= VLAN_PROC_BUFSZ) {
+ /* should never get here */
+ return cnt;
+ } else {
+ cnt += sprintf(buf + cnt, "%s", term_msg);
+ return cnt;
+ }
+ }
+
+ cnt += sprintf(buf + cnt, "EGRESSS priority Mappings: ");
+
+ for (i = 0; i < 16; i++) {
+ mp = dev_info->egress_priority_map[i];
+ while (mp) {
+ cnt += sprintf(buf + cnt, "%lu:%hu ",
+ mp->priority, ((mp->vlan_qos >> 13) & 0x7));
+
+ if ((cnt + 100) > VLAN_PROC_BUFSZ) {
+ if ((cnt + strlen(term_msg)) >= VLAN_PROC_BUFSZ) {
+ /* should never get here */
+ return cnt;
+ } else {
+ cnt += sprintf(buf + cnt, "%s", term_msg);
+ return cnt;
+ }
+ }
+ mp = mp->next;
+ }
+ }
+
+ cnt += sprintf(buf + cnt, "\n");
+
+ return cnt;
+}
+
+#else /* No CONFIG_PROC_FS */
+
+/*
+ * No /proc - output stubs
+ */
+
+int __init vlan_proc_init (void)
+{
+ return 0;
+}
+
+void vlan_proc_cleanup(void)
+{
+ return;
+}
+
+
+int vlan_proc_add_dev(struct net_device *vlandev)
+{
+ return 0;
+}
+
+int vlan_proc_rem_dev(struct net_device *vlandev)
+{
+ return 0;
+}
+
+#endif /* No CONFIG_PROC_FS */
diff --git a/uClinux-2.4.31-uc0/net/8021q/vlanproc.h b/uClinux-2.4.31-uc0/net/8021q/vlanproc.h
new file mode 100644
index 0000000..ac1d67e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/8021q/vlanproc.h
@@ -0,0 +1,12 @@
+#ifndef __BEN_VLAN_PROC_INC__
+#define __BEN_VLAN_PROC_INC__
+
+int vlan_proc_init(void);
+
+int vlan_proc_rem_dev(struct net_device *vlandev);
+int vlan_proc_add_dev (struct net_device *vlandev);
+void vlan_proc_cleanup (void);
+
+#define VLAN_PROC_BUFSZ (4096) /* buffer size for printing proc info */
+
+#endif /* !(__BEN_VLAN_PROC_INC__) */
diff --git a/uClinux-2.4.31-uc0/net/Config.in b/uClinux-2.4.31-uc0/net/Config.in
new file mode 100644
index 0000000..42aea59
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/Config.in
@@ -0,0 +1,116 @@
+# $USAGI: Config.in,v 1.26 2003/11/12 05:11:58 yoshfuji Exp $
+#
+# Network configuration
+#
+mainmenu_option next_comment
+comment 'Networking options'
+tristate 'Packet socket' CONFIG_PACKET
+if [ "$CONFIG_PACKET" != "n" ]; then
+ bool ' Packet socket: mmapped IO' CONFIG_PACKET_MMAP
+fi
+
+tristate 'Netlink device emulation' CONFIG_NETLINK_DEV
+
+bool 'Network packet filtering (replaces ipchains)' CONFIG_NETFILTER
+if [ "$CONFIG_NETFILTER" = "y" ]; then
+ bool ' Network packet filtering debugging' CONFIG_NETFILTER_DEBUG
+fi
+bool 'Socket Filtering' CONFIG_FILTER
+bool 'Address Resolution / Neighbour Discovery Debugging' CONFIG_NET_NEIGH_DEBUG
+bool 'Restrict SO_REUSEADDR only for same user' CONFIG_NET_RESTRICTED_REUSE
+tristate 'Unix domain sockets' CONFIG_UNIX
+bool 'TCP/IP networking' CONFIG_INET
+if [ "$CONFIG_INET" = "y" ]; then
+ source net/ipv4/Config.in
+ if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+# IPv6 as module will cause a CRASH if you try to unload it
+ tristate ' The IPv6 protocol (EXPERIMENTAL)' CONFIG_IPV6
+ if [ "$CONFIG_IPV6" != "n" ]; then
+ source net/ipv6/Config.in
+ fi
+ fi
+ if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ source net/khttpd/Config.in
+ fi
+ if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ source net/sctp/Config.in
+ fi
+fi
+if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ tristate 'Asynchronous Transfer Mode (ATM) (EXPERIMENTAL)' CONFIG_ATM
+ if [ "$CONFIG_ATM" = "y" -o "$CONFIG_ATM" = "m" ]; then
+ if [ "$CONFIG_INET" = "y" ]; then
+ dep_tristate ' Classical IP over ATM' CONFIG_ATM_CLIP $CONFIG_ATM
+ if [ "$CONFIG_ATM_CLIP" != "n" ]; then
+ bool ' Do NOT send ICMP if no neighbour' CONFIG_ATM_CLIP_NO_ICMP
+ fi
+ if [ "$CONFIG_IPV6" != "n" ]; then
+ bool ' IPv6 Enhance for ATM' CONFIG_ATM_IPV6
+ fi
+ fi
+ dep_tristate ' LAN Emulation (LANE) support' CONFIG_ATM_LANE $CONFIG_ATM
+ if [ "$CONFIG_INET" = "y" -a "$CONFIG_ATM_LANE" != "n" ]; then
+ tristate ' Multi-Protocol Over ATM (MPOA) support' CONFIG_ATM_MPOA
+ fi
+ dep_tristate ' RFC1483/2684 Bridged protocols' CONFIG_ATM_BR2684 $CONFIG_ATM
+ if [ "$CONFIG_ATM_BR2684" != "n" ]; then
+ bool ' Per-VC IP filter kludge' CONFIG_ATM_BR2684_IPFILTER
+ fi
+ fi
+fi
+tristate '802.1Q VLAN Support' CONFIG_VLAN_8021Q
+
+comment ' '
+tristate 'The IPX protocol' CONFIG_IPX
+if [ "$CONFIG_IPX" != "n" ]; then
+ source net/ipx/Config.in
+fi
+
+tristate 'Appletalk protocol support' CONFIG_ATALK
+if [ "$CONFIG_ATALK" != "n" ]; then
+ source drivers/net/appletalk/Config.in
+fi
+
+tristate 'DECnet Support' CONFIG_DECNET
+if [ "$CONFIG_DECNET" != "n" ]; then
+ source net/decnet/Config.in
+fi
+dep_tristate '802.1d Ethernet Bridging' CONFIG_BRIDGE $CONFIG_INET
+if [ "$CONFIG_BRIDGE" != "n" -a "$CONFIG_NETFILTER" != "n" ]; then
+ source net/bridge/netfilter/Config.in
+fi
+if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ tristate 'CCITT X.25 Packet Layer (EXPERIMENTAL)' CONFIG_X25
+ tristate 'LAPB Data Link Driver (EXPERIMENTAL)' CONFIG_LAPB
+ bool '802.2 LLC (EXPERIMENTAL)' CONFIG_LLC
+ bool 'Frame Diverter (EXPERIMENTAL)' CONFIG_NET_DIVERT
+# if [ "$CONFIG_LLC" = "y" ]; then
+# bool ' Netbeui (EXPERIMENTAL)' CONFIG_NETBEUI
+# fi
+ if [ "$CONFIG_INET" = "y" ]; then
+ tristate 'Acorn Econet/AUN protocols (EXPERIMENTAL)' CONFIG_ECONET
+ if [ "$CONFIG_ECONET" != "n" ]; then
+ bool ' AUN over UDP' CONFIG_ECONET_AUNUDP
+ bool ' Native Econet' CONFIG_ECONET_NATIVE
+ fi
+ fi
+ tristate 'WAN router' CONFIG_WAN_ROUTER
+ bool 'Fast switching (read help!)' CONFIG_NET_FASTROUTE
+ bool 'Forwarding between high speed interfaces' CONFIG_NET_HW_FLOWCONTROL
+fi
+
+mainmenu_option next_comment
+comment 'QoS and/or fair queueing'
+bool 'QoS and/or fair queueing' CONFIG_NET_SCHED
+if [ "$CONFIG_NET_SCHED" = "y" ]; then
+ source net/sched/Config.in
+fi
+#bool 'Network code profiler' CONFIG_NET_PROFILE
+endmenu
+
+mainmenu_option next_comment
+comment 'Network testing'
+dep_tristate 'Packet Generator (USE WITH CAUTION)' CONFIG_NET_PKTGEN $CONFIG_PROC_FS
+endmenu
+
+endmenu
diff --git a/uClinux-2.4.31-uc0/net/Makefile b/uClinux-2.4.31-uc0/net/Makefile
new file mode 100644
index 0000000..0f49735
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/Makefile
@@ -0,0 +1,72 @@
+# $USAGI: Makefile,v 1.15 2003/11/12 05:11:58 yoshfuji Exp $
+#
+# Makefile for the linux networking.
+#
+# 2 Sep 2000, Christoph Hellwig <hch@infradead.org>
+# Rewritten to use lists instead of if-statements.
+#
+
+O_TARGET := network.o
+
+mod-subdirs := ipv4/netfilter ipv6/netfilter bridge/netfilter ipx irda bluetooth atm netlink sched core sctp 802
+export-objs := netsyms.o
+
+subdir-y := core ethernet
+subdir-m := ipv4 # hum?
+
+
+subdir-$(CONFIG_NET) += 802 sched netlink
+subdir-$(CONFIG_IPV6) += ipv6
+subdir-$(CONFIG_INET) += ipv4
+subdir-$(CONFIG_NETFILTER) += ipv4/netfilter
+subdir-$(CONFIG_UNIX) += unix
+subdir-$(CONFIG_IP_SCTP) += sctp
+
+ifneq ($(CONFIG_IPV6),n)
+ifneq ($(CONFIG_IPV6),)
+subdir-$(CONFIG_NETFILTER) += ipv6/netfilter
+endif
+endif
+
+ifeq ($(CONFIG_IPV6),y)
+subdir-$(CONFIG_IPV6_IPV6_TUNNEL) += ipv6
+endif
+
+ifneq ($(CONFIG_BRIDGE),n)
+ifneq ($(CONFIG_BRIDGE),)
+subdir-$(CONFIG_BRIDGE) += bridge/netfilter
+endif
+endif
+
+subdir-$(CONFIG_KHTTPD) += khttpd
+subdir-$(CONFIG_PACKET) += packet
+subdir-$(CONFIG_NET_SCHED) += sched
+subdir-$(CONFIG_BRIDGE) += bridge
+subdir-$(CONFIG_IPX) += ipx
+subdir-$(CONFIG_ATALK) += appletalk
+subdir-$(CONFIG_WAN_ROUTER) += wanrouter
+subdir-$(CONFIG_X25) += x25
+subdir-$(CONFIG_LAPB) += lapb
+subdir-$(CONFIG_NETROM) += netrom
+subdir-$(CONFIG_ROSE) += rose
+subdir-$(CONFIG_AX25) += ax25
+subdir-$(CONFIG_IRDA) += irda
+subdir-$(CONFIG_BLUEZ) += bluetooth
+subdir-$(CONFIG_SUNRPC) += sunrpc
+subdir-$(CONFIG_ATM) += atm
+subdir-$(CONFIG_DECNET) += decnet
+subdir-$(CONFIG_ECONET) += econet
+subdir-$(CONFIG_VLAN_8021Q) += 8021q
+
+ifeq ($(CONFIG_NETFILTER),y)
+ mod-subdirs += ipv4/ipvs
+ subdir-$(CONFIG_IP_VS) += ipv4/ipvs
+endif
+
+obj-y := socket.o $(join $(subdir-y), $(patsubst %,/%.o,$(notdir $(subdir-y))))
+ifeq ($(CONFIG_NET),y)
+obj-$(CONFIG_MODULES) += netsyms.o
+obj-$(CONFIG_SYSCTL) += sysctl_net.o
+endif
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/TUNABLE b/uClinux-2.4.31-uc0/net/TUNABLE
new file mode 100644
index 0000000..9913211
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/TUNABLE
@@ -0,0 +1,50 @@
+The following parameters should be tunable at compile time. Some of them
+exist as sysctls too.
+
+This is far from complete
+
+Item Description
+----------------------------------------------------------------------------
+MAX_LINKS Maximum number of netlink minor devices. (1-32)
+RIF_TABLE_SIZE Token ring RIF cache size (tunable)
+AARP_HASH_SIZE Size of Appletalk hash table (tunable)
+AX25_DEF_T1 AX.25 parameters. These are all tunable via
+AX25_DEF_T2 SIOCAX25SETPARMS
+AX25_DEF_T3 T1-T3,N2 have the meanings in the specification
+AX25_DEF_N2
+AX25_DEF_AXDEFMODE 8 = normal 128 is PE1CHL extended
+AX25_DEF_IPDEFMODE 'D' - datagram 'V' - virtual connection
+AX25_DEF_BACKOFF 'E'xponential 'L'inear
+AX25_DEF_NETROM Allow netrom 1=Y
+AX25_DF_TEXT Allow PID=Text 1=Y
+AX25_DEF_WINDOW Window for normal mode
+AX25_DEF_EWINDOW Window for PE1CHL mode
+AX25_DEF_DIGI 1 for inband 2 for cross band 3 for both
+AX25_DEF_CONMODE Allow connected modes 1=Yes
+AX25_ROUTE_MAX AX.25 route cache size - no currently tunable
+Unnamed (16) Number of protocol hash slots (tunable)
+DEV_NUMBUFFS Number of priority levels (not easily tunable)
+Unnamed (300) Maximum packet backlog queue (tunable)
+MAX_IOVEC Maximum number of iovecs in a message (tunable)
+MIN_WINDOW Offered minimum window (tunable)
+MAX_WINDOW Offered maximum window (tunable)
+MAX_HEADER Largest physical header (tunable)
+MAX_ADDR_LEN Largest physical address (tunable)
+SOCK_ARRAY_SIZE IP socket array hash size (tunable)
+IP_MAX_MEMBERSHIPS Largest number of groups per socket (BSD style) (tunable)
+16 Hard coded constant for amount of room allowed for
+ cache align and faster forwarding (tunable)
+IP_FRAG_TIME Time we hold a fragment for. (tunable)
+PORT_MASQ_BEGIN First port reserved for masquerade (tunable)
+PORT_MASQ_END Last port used for masquerade (tunable)
+MASQUERADE_EXPIRE_TCP_FIN Time we keep a masquerade for after a FIN
+MASQUERADE_EXPIRE_UDP Time we keep a UDP masquerade for (tunable)
+MAXVIFS Maximum mrouted vifs (1-32)
+MFC_LINES Lines in the multicast router cache (tunable)
+
+NetROM parameters are tunable via an ioctl passing a struct
+
+4000 Size a Unix domain socket malloc falls back to
+ (tunable) should be 8K - a bit for 8K machines like
+ the ALPHA
+
diff --git a/uClinux-2.4.31-uc0/net/appletalk/Makefile b/uClinux-2.4.31-uc0/net/appletalk/Makefile
new file mode 100644
index 0000000..e8e2c8f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/appletalk/Makefile
@@ -0,0 +1,20 @@
+#
+# Makefile for the Linux AppleTalk layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := appletalk.o
+
+export-objs = ddp.o
+
+obj-y := aarp.o ddp.o
+obj-m := $(O_TARGET)
+
+obj-$(CONFIG_SYSCTL) += sysctl_net_atalk.o
+
+include $(TOPDIR)/Rules.make
+
diff --git a/uClinux-2.4.31-uc0/net/appletalk/aarp.c b/uClinux-2.4.31-uc0/net/appletalk/aarp.c
new file mode 100644
index 0000000..f5048d1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/appletalk/aarp.c
@@ -0,0 +1,986 @@
+/*
+ * AARP: An implementation of the AppleTalk AARP protocol for
+ * Ethernet 'ELAP'.
+ *
+ * Alan Cox <Alan.Cox@linux.org>
+ *
+ * This doesn't fit cleanly with the IP arp. Potentially we can use
+ * the generic neighbour discovery code to clean this up.
+ *
+ * FIXME:
+ * We ought to handle the retransmits with a single list and a
+ * separate fast timer for when it is needed.
+ * Use neighbour discovery code.
+ * Token Ring Support.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ *
+ * References:
+ * Inside AppleTalk (2nd Ed).
+ * Fixes:
+ * Jaume Grau - flush caches on AARP_PROBE
+ * Rob Newberry - Added proxy AARP and AARP proc fs,
+ * moved probing from DDP module.
+ * Arnaldo C. Melo - don't mangle rx packets
+ *
+ */
+
+#include <linux/config.h>
+#if defined(CONFIG_ATALK) || defined(CONFIG_ATALK_MODULE)
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/notifier.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <net/sock.h>
+#include <net/datalink.h>
+#include <net/psnap.h>
+#include <linux/atalk.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/module.h>
+
+int sysctl_aarp_expiry_time = AARP_EXPIRY_TIME;
+int sysctl_aarp_tick_time = AARP_TICK_TIME;
+int sysctl_aarp_retransmit_limit = AARP_RETRANSMIT_LIMIT;
+int sysctl_aarp_resolve_time = AARP_RESOLVE_TIME;
+
+/* Lists of aarp entries */
+struct aarp_entry {
+ /* These first two are only used for unresolved entries */
+ unsigned long last_sent; /* Last time we xmitted the aarp request */
+ struct sk_buff_head packet_queue; /* Queue of frames wait for resolution */
+ int status; /* Used for proxy AARP */
+ unsigned long expires_at; /* Entry expiry time */
+ struct at_addr target_addr; /* DDP Address */
+ struct net_device *dev; /* Device to use */
+ char hwaddr[6]; /* Physical i/f address of target/router */
+ unsigned short xmit_count; /* When this hits 10 we give up */
+ struct aarp_entry *next; /* Next entry in chain */
+};
+
+/* Hashed list of resolved, unresolved and proxy entries */
+static struct aarp_entry *resolved[AARP_HASH_SIZE];
+static struct aarp_entry *unresolved[AARP_HASH_SIZE];
+static struct aarp_entry *proxies[AARP_HASH_SIZE];
+static int unresolved_count;
+
+/* One lock protects it all. */
+static spinlock_t aarp_lock = SPIN_LOCK_UNLOCKED;
+
+/* Used to walk the list and purge/kick entries. */
+static struct timer_list aarp_timer;
+
+/*
+ * Delete an aarp queue
+ *
+ * Must run under aarp_lock.
+ */
+static void __aarp_expire(struct aarp_entry *a)
+{
+ skb_queue_purge(&a->packet_queue);
+ kfree(a);
+}
+
+/*
+ * Send an aarp queue entry request
+ *
+ * Must run under aarp_lock.
+ */
+
+static void __aarp_send_query(struct aarp_entry *a)
+{
+ static char aarp_eth_multicast[ETH_ALEN] =
+ { 0x09, 0x00, 0x07, 0xFF, 0xFF, 0xFF };
+ struct net_device *dev = a->dev;
+ int len = dev->hard_header_len + sizeof(struct elapaarp) +
+ aarp_dl->header_length;
+ struct sk_buff *skb = alloc_skb(len, GFP_ATOMIC);
+ struct at_addr *sat = atalk_find_dev_addr(dev);
+ struct elapaarp *eah;
+
+ if (!skb)
+ return;
+
+ if (!sat) {
+ kfree_skb(skb);
+ return;
+ }
+
+ /* Set up the buffer */
+ skb_reserve(skb, dev->hard_header_len + aarp_dl->header_length);
+ eah = (struct elapaarp *)skb_put(skb,
+ sizeof(struct elapaarp));
+ skb->protocol = htons(ETH_P_ATALK);
+ skb->nh.raw = skb->h.raw = (void *) eah;
+ skb->dev = dev;
+
+ /* Set up the ARP */
+ eah->hw_type = htons(AARP_HW_TYPE_ETHERNET);
+ eah->pa_type = htons(ETH_P_ATALK);
+ eah->hw_len = ETH_ALEN;
+ eah->pa_len = AARP_PA_ALEN;
+ eah->function = htons(AARP_REQUEST);
+
+ memcpy(eah->hw_src, dev->dev_addr, ETH_ALEN);
+
+ eah->pa_src_zero= 0;
+ eah->pa_src_net = sat->s_net;
+ eah->pa_src_node= sat->s_node;
+
+ memset(eah->hw_dst, '\0', ETH_ALEN);
+
+ eah->pa_dst_zero= 0;
+ eah->pa_dst_net = a->target_addr.s_net;
+ eah->pa_dst_node= a->target_addr.s_node;
+
+ /* Add ELAP headers and set target to the AARP multicast */
+ aarp_dl->datalink_header(aarp_dl, skb, aarp_eth_multicast);
+
+ /* Send it */
+ dev_queue_xmit(skb);
+ /* Update the sending count */
+ a->xmit_count++;
+}
+
+/* This runs under aarp_lock and in softint context, so only atomic memory
+ * allocations can be used. */
+static void aarp_send_reply(struct net_device *dev, struct at_addr *us,
+ struct at_addr *them, unsigned char *sha)
+{
+ int len = dev->hard_header_len + sizeof(struct elapaarp) +
+ aarp_dl->header_length;
+ struct sk_buff *skb = alloc_skb(len, GFP_ATOMIC);
+ struct elapaarp *eah;
+
+ if (!skb)
+ return;
+
+ /* Set up the buffer */
+ skb_reserve(skb, dev->hard_header_len + aarp_dl->header_length);
+ eah = (struct elapaarp *)skb_put(skb,
+ sizeof(struct elapaarp));
+ skb->protocol = htons(ETH_P_ATALK);
+ skb->nh.raw = skb->h.raw = (void *) eah;
+ skb->dev = dev;
+
+ /* Set up the ARP */
+ eah->hw_type = htons(AARP_HW_TYPE_ETHERNET);
+ eah->pa_type = htons(ETH_P_ATALK);
+ eah->hw_len = ETH_ALEN;
+ eah->pa_len = AARP_PA_ALEN;
+ eah->function = htons(AARP_REPLY);
+
+ memcpy(eah->hw_src, dev->dev_addr, ETH_ALEN);
+
+ eah->pa_src_zero= 0;
+ eah->pa_src_net = us->s_net;
+ eah->pa_src_node= us->s_node;
+
+ if (!sha)
+ memset(eah->hw_dst, '\0', ETH_ALEN);
+ else
+ memcpy(eah->hw_dst, sha, ETH_ALEN);
+
+ eah->pa_dst_zero= 0;
+ eah->pa_dst_net = them->s_net;
+ eah->pa_dst_node= them->s_node;
+
+ /* Add ELAP headers and set target to the AARP multicast */
+ aarp_dl->datalink_header(aarp_dl, skb, sha);
+ /* Send it */
+ dev_queue_xmit(skb);
+}
+
+/*
+ * Send probe frames. Called from aarp_probe_network and
+ * aarp_proxy_probe_network.
+ */
+
+void aarp_send_probe(struct net_device *dev, struct at_addr *us)
+{
+ int len = dev->hard_header_len + sizeof(struct elapaarp) +
+ aarp_dl->header_length;
+ struct sk_buff *skb = alloc_skb(len, GFP_ATOMIC);
+ static char aarp_eth_multicast[ETH_ALEN] =
+ { 0x09, 0x00, 0x07, 0xFF, 0xFF, 0xFF };
+ struct elapaarp *eah;
+
+ if (!skb)
+ return;
+
+ /* Set up the buffer */
+ skb_reserve(skb, dev->hard_header_len + aarp_dl->header_length);
+ eah = (struct elapaarp *)skb_put(skb,
+ sizeof(struct elapaarp));
+ skb->protocol = htons(ETH_P_ATALK);
+ skb->nh.raw = skb->h.raw = (void *) eah;
+ skb->dev = dev;
+
+ /* Set up the ARP */
+ eah->hw_type = htons(AARP_HW_TYPE_ETHERNET);
+ eah->pa_type = htons(ETH_P_ATALK);
+ eah->hw_len = ETH_ALEN;
+ eah->pa_len = AARP_PA_ALEN;
+ eah->function = htons(AARP_PROBE);
+
+ memcpy(eah->hw_src, dev->dev_addr, ETH_ALEN);
+
+ eah->pa_src_zero= 0;
+ eah->pa_src_net = us->s_net;
+ eah->pa_src_node= us->s_node;
+
+ memset(eah->hw_dst, '\0', ETH_ALEN);
+
+ eah->pa_dst_zero= 0;
+ eah->pa_dst_net = us->s_net;
+ eah->pa_dst_node= us->s_node;
+
+ /* Add ELAP headers and set target to the AARP multicast */
+ aarp_dl->datalink_header(aarp_dl, skb, aarp_eth_multicast);
+ /* Send it */
+ dev_queue_xmit(skb);
+}
+
+/*
+ * Handle an aarp timer expire
+ *
+ * Must run under the aarp_lock.
+ */
+
+static void __aarp_expire_timer(struct aarp_entry **n)
+{
+ struct aarp_entry *t;
+
+ while (*n)
+ /* Expired ? */
+ if (time_after(jiffies, (*n)->expires_at)) {
+ t = *n;
+ *n = (*n)->next;
+ __aarp_expire(t);
+ } else
+ n = &((*n)->next);
+}
+
+/*
+ * Kick all pending requests 5 times a second.
+ *
+ * Must run under the aarp_lock.
+ */
+
+static void __aarp_kick(struct aarp_entry **n)
+{
+ struct aarp_entry *t;
+
+ while (*n)
+ /* Expired: if this will be the 11th tx, we delete instead. */
+ if ((*n)->xmit_count >= sysctl_aarp_retransmit_limit) {
+ t = *n;
+ *n = (*n)->next;
+ __aarp_expire(t);
+ } else {
+ __aarp_send_query(*n);
+ n = &((*n)->next);
+ }
+}
+
+/*
+ * A device has gone down. Take all entries referring to the device
+ * and remove them.
+ *
+ * Must run under the aarp_lock.
+ */
+
+static void __aarp_expire_device(struct aarp_entry **n, struct net_device *dev)
+{
+ struct aarp_entry *t;
+
+ while (*n)
+ if ((*n)->dev == dev) {
+ t = *n;
+ *n = (*n)->next;
+ __aarp_expire(t);
+ } else
+ n = &((*n)->next);
+}
+
+/* Handle the timer event */
+static void aarp_expire_timeout(unsigned long unused)
+{
+ int ct;
+
+ spin_lock_bh(&aarp_lock);
+
+ for (ct = 0; ct < AARP_HASH_SIZE; ct++) {
+ __aarp_expire_timer(&resolved[ct]);
+ __aarp_kick(&unresolved[ct]);
+ __aarp_expire_timer(&unresolved[ct]);
+ __aarp_expire_timer(&proxies[ct]);
+ }
+
+ spin_unlock_bh(&aarp_lock);
+ mod_timer(&aarp_timer, jiffies +
+ (unresolved_count ? sysctl_aarp_tick_time :
+ sysctl_aarp_expiry_time));
+}
+
+/* Network device notifier chain handler. */
+static int aarp_device_event(struct notifier_block *this, unsigned long event,
+ void *ptr)
+{
+ int ct;
+
+ if (event == NETDEV_DOWN) {
+ spin_lock_bh(&aarp_lock);
+
+ for (ct = 0; ct < AARP_HASH_SIZE; ct++) {
+ __aarp_expire_device(&resolved[ct], ptr);
+ __aarp_expire_device(&unresolved[ct], ptr);
+ __aarp_expire_device(&proxies[ct], ptr);
+ }
+
+ spin_unlock_bh(&aarp_lock);
+ }
+ return NOTIFY_DONE;
+}
+
+/*
+ * Create a new aarp entry. This must use GFP_ATOMIC because it
+ * runs while holding spinlocks.
+ */
+
+static struct aarp_entry *aarp_alloc(void)
+{
+ struct aarp_entry *a = kmalloc(sizeof(struct aarp_entry), GFP_ATOMIC);
+
+ if (a)
+ skb_queue_head_init(&a->packet_queue);
+ return a;
+}
+
+/*
+ * Find an entry. We might return an expired but not yet purged entry. We
+ * don't care as it will do no harm.
+ *
+ * This must run under the aarp_lock.
+ */
+static struct aarp_entry *__aarp_find_entry(struct aarp_entry *list,
+ struct net_device *dev,
+ struct at_addr *sat)
+{
+ while (list) {
+ if (list->target_addr.s_net == sat->s_net &&
+ list->target_addr.s_node == sat->s_node &&
+ list->dev == dev)
+ break;
+ list = list->next;
+ }
+
+ return list;
+}
+
+/* Called from the DDP code, and thus must be exported. */
+void aarp_proxy_remove(struct net_device *dev, struct at_addr *sa)
+{
+ int hash = sa->s_node % (AARP_HASH_SIZE - 1);
+ struct aarp_entry *a;
+
+ spin_lock_bh(&aarp_lock);
+
+ a = __aarp_find_entry(proxies[hash], dev, sa);
+ if (a)
+ a->expires_at = jiffies - 1;
+
+ spin_unlock_bh(&aarp_lock);
+}
+
+/* This must run under aarp_lock. */
+static struct at_addr *__aarp_proxy_find(struct net_device *dev,
+ struct at_addr *sa)
+{
+ int hash = sa->s_node % (AARP_HASH_SIZE - 1);
+ struct aarp_entry *a = __aarp_find_entry(proxies[hash], dev, sa);
+
+ return a ? sa : NULL;
+}
+
+/*
+ * Probe a Phase 1 device or a device that requires its Net:Node to
+ * be set via an ioctl.
+ */
+void aarp_send_probe_phase1(struct atalk_iface *iface)
+{
+ struct ifreq atreq;
+ struct sockaddr_at *sa = (struct sockaddr_at *)&atreq.ifr_addr;
+
+ sa->sat_addr.s_node = iface->address.s_node;
+ sa->sat_addr.s_net = ntohs(iface->address.s_net);
+
+ /* We pass the Net:Node to the drivers/cards by a Device ioctl. */
+ if (!(iface->dev->do_ioctl(iface->dev, &atreq, SIOCSIFADDR))) {
+ (void)iface->dev->do_ioctl(iface->dev, &atreq, SIOCGIFADDR);
+ if (iface->address.s_net != htons(sa->sat_addr.s_net) ||
+ iface->address.s_node != sa->sat_addr.s_node)
+ iface->status |= ATIF_PROBE_FAIL;
+
+ iface->address.s_net = htons(sa->sat_addr.s_net);
+ iface->address.s_node = sa->sat_addr.s_node;
+ }
+}
+
+
+void aarp_probe_network(struct atalk_iface *atif)
+{
+ if (atif->dev->type == ARPHRD_LOCALTLK ||
+ atif->dev->type == ARPHRD_PPP)
+ aarp_send_probe_phase1(atif);
+ else {
+ unsigned int count;
+
+ for (count = 0; count < AARP_RETRANSMIT_LIMIT; count++) {
+ aarp_send_probe(atif->dev, &atif->address);
+
+ /* Defer 1/10th */
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/10);
+
+ if (atif->status & ATIF_PROBE_FAIL)
+ break;
+ }
+ }
+}
+
+int aarp_proxy_probe_network(struct atalk_iface *atif, struct at_addr *sa)
+{
+ int hash, retval = 1;
+ struct aarp_entry *entry;
+ unsigned int count;
+
+ /*
+ * we don't currently support LocalTalk or PPP for proxy AARP;
+ * if someone wants to try and add it, have fun
+ */
+ if (atif->dev->type == ARPHRD_LOCALTLK)
+ return -EPROTONOSUPPORT;
+
+ if (atif->dev->type == ARPHRD_PPP)
+ return -EPROTONOSUPPORT;
+
+ /*
+ * create a new AARP entry with the flags set to be published --
+ * we need this one to hang around even if it's in use
+ */
+ entry = aarp_alloc();
+ if (!entry)
+ return -ENOMEM;
+
+ entry->expires_at = -1;
+ entry->status = ATIF_PROBE;
+ entry->target_addr.s_node = sa->s_node;
+ entry->target_addr.s_net = sa->s_net;
+ entry->dev = atif->dev;
+
+ spin_lock_bh(&aarp_lock);
+
+ hash = sa->s_node % (AARP_HASH_SIZE - 1);
+ entry->next = proxies[hash];
+ proxies[hash] = entry;
+
+ for (count = 0; count < AARP_RETRANSMIT_LIMIT; count++) {
+ aarp_send_probe(atif->dev, sa);
+
+ /* Defer 1/10th */
+ current->state = TASK_INTERRUPTIBLE;
+ spin_unlock_bh(&aarp_lock);
+ schedule_timeout(HZ/10);
+ spin_lock_bh(&aarp_lock);
+
+ if (entry->status & ATIF_PROBE_FAIL)
+ break;
+ }
+
+ if (entry->status & ATIF_PROBE_FAIL) {
+ entry->expires_at = jiffies - 1; /* free the entry */
+ retval = -EADDRINUSE; /* return network full */
+ } else /* clear the probing flag */
+ entry->status &= ~ATIF_PROBE;
+
+ spin_unlock_bh(&aarp_lock);
+ return retval;
+}
+
+/* Send a DDP frame */
+int aarp_send_ddp(struct net_device *dev,struct sk_buff *skb,
+ struct at_addr *sa, void *hwaddr)
+{
+ static char ddp_eth_multicast[ETH_ALEN] =
+ { 0x09, 0x00, 0x07, 0xFF, 0xFF, 0xFF };
+ int hash;
+ struct aarp_entry *a;
+
+ skb->nh.raw = skb->data;
+
+ /* Check for LocalTalk first */
+ if (dev->type == ARPHRD_LOCALTLK) {
+ struct at_addr *at = atalk_find_dev_addr(dev);
+ struct ddpehdr *ddp = (struct ddpehdr *)skb->data;
+ int ft = 2;
+
+ /*
+ * Compressible ?
+ *
+ * IFF: src_net==dest_net==device_net
+ * (zero matches anything)
+ */
+
+ if ((!ddp->deh_snet || at->s_net == ddp->deh_snet) &&
+ (!ddp->deh_dnet || at->s_net == ddp->deh_dnet)) {
+ skb_pull(skb, sizeof(struct ddpehdr) - 4);
+
+ /*
+ * The upper two remaining bytes are the port
+ * numbers we just happen to need. Now put the
+ * length in the lower two.
+ */
+ *((__u16 *)skb->data) = htons(skb->len);
+ ft = 1;
+ }
+ /*
+ * Nice and easy. No AARP type protocols occur here
+ * so we can just shovel it out with a 3 byte LLAP header
+ */
+
+ skb_push(skb, 3);
+ skb->data[0] = sa->s_node;
+ skb->data[1] = at->s_node;
+ skb->data[2] = ft;
+ skb->dev = dev;
+ goto sendit;
+ }
+
+ /* On a PPP link we neither compress nor aarp. */
+ if (dev->type == ARPHRD_PPP) {
+ skb->protocol = htons(ETH_P_PPPTALK);
+ skb->dev = dev;
+ goto sendit;
+ }
+
+ /* Non ELAP we cannot do. */
+ if (dev->type != ARPHRD_ETHER)
+ return -1;
+
+ skb->dev = dev;
+ skb->protocol = htons(ETH_P_ATALK);
+ hash = sa->s_node % (AARP_HASH_SIZE - 1);
+
+ /* Do we have a resolved entry? */
+ if (sa->s_node == ATADDR_BCAST) {
+ ddp_dl->datalink_header(ddp_dl, skb, ddp_eth_multicast);
+ goto sendit;
+ }
+
+ spin_lock_bh(&aarp_lock);
+ a = __aarp_find_entry(resolved[hash], dev, sa);
+
+ if (a) { /* Return 1 and fill in the address */
+ a->expires_at = jiffies + (sysctl_aarp_expiry_time * 10);
+ ddp_dl->datalink_header(ddp_dl, skb, a->hwaddr);
+ spin_unlock_bh(&aarp_lock);
+ goto sendit;
+ }
+
+ /* Do we have an unresolved entry: This is the less common path */
+ a = __aarp_find_entry(unresolved[hash], dev, sa);
+ if (a) { /* Queue onto the unresolved queue */
+ skb_queue_tail(&a->packet_queue, skb);
+ spin_unlock_bh(&aarp_lock);
+ return 0;
+ }
+
+ /* Allocate a new entry */
+ a = aarp_alloc();
+ if (!a) {
+ /* Whoops slipped... good job it's an unreliable protocol 8) */
+ spin_unlock_bh(&aarp_lock);
+ return -1;
+ }
+
+ /* Set up the queue */
+ skb_queue_tail(&a->packet_queue, skb);
+ a->expires_at = jiffies + sysctl_aarp_resolve_time;
+ a->dev = dev;
+ a->next = unresolved[hash];
+ a->target_addr = *sa;
+ a->xmit_count = 0;
+ unresolved[hash] = a;
+ unresolved_count++;
+
+ /* Send an initial request for the address */
+ __aarp_send_query(a);
+
+ /*
+ * Switch to fast timer if needed (That is if this is the
+ * first unresolved entry to get added)
+ */
+
+ if (unresolved_count == 1)
+ mod_timer(&aarp_timer, jiffies + sysctl_aarp_tick_time);
+
+ /* Now finally, it is safe to drop the lock. */
+ spin_unlock_bh(&aarp_lock);
+
+ /* Tell the ddp layer we have taken over for this frame. */
+ return 0;
+
+sendit: if (skb->sk)
+ skb->priority = skb->sk->priority;
+ dev_queue_xmit(skb);
+ return 1;
+}
+
+/*
+ * An entry in the aarp unresolved queue has become resolved. Send
+ * all the frames queued under it.
+ *
+ * Must run under aarp_lock.
+ */
+static void __aarp_resolved(struct aarp_entry **list, struct aarp_entry *a,
+ int hash)
+{
+ struct sk_buff *skb;
+
+ while (*list)
+ if (*list == a) {
+ unresolved_count--;
+ *list = a->next;
+
+ /* Move into the resolved list */
+ a->next = resolved[hash];
+ resolved[hash] = a;
+
+ /* Kick frames off */
+ while ((skb = skb_dequeue(&a->packet_queue)) != NULL) {
+ a->expires_at = jiffies +
+ sysctl_aarp_expiry_time * 10;
+ ddp_dl->datalink_header(ddp_dl, skb, a->hwaddr);
+ if (skb->sk)
+ skb->priority = skb->sk->priority;
+ dev_queue_xmit(skb);
+ }
+ } else
+ list = &((*list)->next);
+}
+
+/*
+ * This is called by the SNAP driver whenever we see an AARP SNAP
+ * frame. We currently only support Ethernet.
+ */
+static int aarp_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt)
+{
+ struct elapaarp *ea = (struct elapaarp *)skb->h.raw;
+ int hash, ret = 0;
+ __u16 function;
+ struct aarp_entry *a;
+ struct at_addr sa, *ma, da;
+ struct atalk_iface *ifa;
+
+ /* We only do Ethernet SNAP AARP. */
+ if (dev->type != ARPHRD_ETHER)
+ goto out0;
+
+ /* Frame size ok? */
+ if (!skb_pull(skb, sizeof(*ea)))
+ goto out0;
+
+ function = ntohs(ea->function);
+
+ /* Sanity check fields. */
+ if (function < AARP_REQUEST || function > AARP_PROBE ||
+ ea->hw_len != ETH_ALEN || ea->pa_len != AARP_PA_ALEN ||
+ ea->pa_src_zero || ea->pa_dst_zero)
+ goto out0;
+
+ /* Looks good. */
+ hash = ea->pa_src_node % (AARP_HASH_SIZE - 1);
+
+ /* Build an address. */
+ sa.s_node = ea->pa_src_node;
+ sa.s_net = ea->pa_src_net;
+
+ /* Process the packet. Check for replies of me. */
+ ifa = atalk_find_dev(dev);
+ if (!ifa)
+ goto out1;
+
+ if (ifa->status & ATIF_PROBE &&
+ ifa->address.s_node == ea->pa_dst_node &&
+ ifa->address.s_net == ea->pa_dst_net) {
+ ifa->status |= ATIF_PROBE_FAIL; /* Fail the probe (in use) */
+ goto out1;
+ }
+
+ /* Check for replies of proxy AARP entries */
+ da.s_node = ea->pa_dst_node;
+ da.s_net = ea->pa_dst_net;
+
+ spin_lock_bh(&aarp_lock);
+ a = __aarp_find_entry(proxies[hash], dev, &da);
+
+ if (a && a->status & ATIF_PROBE) {
+ a->status |= ATIF_PROBE_FAIL;
+ /*
+ * we do not respond to probe or request packets for
+ * this address while we are probing this address
+ */
+ goto unlock;
+ }
+
+ switch (function) {
+ case AARP_REPLY:
+ if (!unresolved_count) /* Speed up */
+ break;
+
+ /* Find the entry. */
+ a = __aarp_find_entry(unresolved[hash],dev,&sa);
+ if (!a || dev != a->dev)
+ break;
+
+ /* We can fill one in - this is good. */
+ memcpy(a->hwaddr,ea->hw_src,ETH_ALEN);
+ __aarp_resolved(&unresolved[hash],a,hash);
+ if (!unresolved_count)
+ mod_timer(&aarp_timer,
+ jiffies + sysctl_aarp_expiry_time);
+ break;
+
+ case AARP_REQUEST:
+ case AARP_PROBE:
+ /*
+ * If it is my address set ma to my address and
+ * reply. We can treat probe and request the
+ * same. Probe simply means we shouldn't cache
+ * the querying host, as in a probe they are
+ * proposing an address not using one.
+ *
+ * Support for proxy-AARP added. We check if the
+ * address is one of our proxies before we toss
+ * the packet out.
+ */
+
+ sa.s_node = ea->pa_dst_node;
+ sa.s_net = ea->pa_dst_net;
+
+ /* See if we have a matching proxy. */
+ ma = __aarp_proxy_find(dev, &sa);
+ if (!ma)
+ ma = &ifa->address;
+ else { /* We need to make a copy of the entry. */
+ da.s_node = sa.s_node;
+ da.s_net = da.s_net;
+ ma = &da;
+ }
+
+ if (function == AARP_PROBE) {
+ /* A probe implies someone trying to get an
+ * address. So as a precaution flush any
+ * entries we have for this address. */
+ struct aarp_entry *a = __aarp_find_entry(
+ resolved[sa.s_node%(AARP_HASH_SIZE-1)],
+ skb->dev, &sa);
+ /* Make it expire next tick - that avoids us
+ * getting into a probe/flush/learn/probe/
+ * flush/learn cycle during probing of a slow
+ * to respond host addr. */
+ if (a) {
+ a->expires_at = jiffies - 1;
+ mod_timer(&aarp_timer, jiffies +
+ sysctl_aarp_tick_time);
+ }
+ }
+
+ if (sa.s_node != ma->s_node)
+ break;
+
+ if (sa.s_net && ma->s_net && sa.s_net != ma->s_net)
+ break;
+
+ sa.s_node = ea->pa_src_node;
+ sa.s_net = ea->pa_src_net;
+
+ /* aarp_my_address has found the address to use for us.
+ */
+ aarp_send_reply(dev, ma, &sa, ea->hw_src);
+ break;
+ }
+
+unlock: spin_unlock_bh(&aarp_lock);
+out1: ret = 1;
+out0: kfree_skb(skb);
+ return ret;
+}
+
+static struct notifier_block aarp_notifier = {
+ notifier_call: aarp_device_event,
+};
+
+static char aarp_snap_id[] = { 0x00, 0x00, 0x00, 0x80, 0xF3 };
+
+void __init aarp_proto_init(void)
+{
+ aarp_dl = register_snap_client(aarp_snap_id, aarp_rcv);
+ if (!aarp_dl)
+ printk(KERN_CRIT "Unable to register AARP with SNAP.\n");
+ init_timer(&aarp_timer);
+ aarp_timer.function = aarp_expire_timeout;
+ aarp_timer.data = 0;
+ aarp_timer.expires = jiffies + sysctl_aarp_expiry_time;
+ add_timer(&aarp_timer);
+ register_netdevice_notifier(&aarp_notifier);
+}
+
+/* Remove the AARP entries associated with a device. */
+void aarp_device_down(struct net_device *dev)
+{
+ int ct;
+
+ spin_lock_bh(&aarp_lock);
+
+ for (ct = 0; ct < AARP_HASH_SIZE; ct++) {
+ __aarp_expire_device(&resolved[ct], dev);
+ __aarp_expire_device(&unresolved[ct], dev);
+ __aarp_expire_device(&proxies[ct], dev);
+ }
+
+ spin_unlock_bh(&aarp_lock);
+}
+
+/* Called from proc fs */
+static int aarp_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ /* we should dump all our AARP entries */
+ struct aarp_entry *entry;
+ int len, ct;
+
+ len = sprintf(buffer,
+ "%-10.10s %-10.10s%-18.18s%12.12s%12.12s xmit_count status\n",
+ "address", "device", "hw addr", "last_sent", "expires");
+
+ spin_lock_bh(&aarp_lock);
+
+ for (ct = 0; ct < AARP_HASH_SIZE; ct++) {
+ for (entry = resolved[ct]; entry; entry = entry->next) {
+ len+= sprintf(buffer+len,"%6u:%-3u ",
+ (unsigned int)ntohs(entry->target_addr.s_net),
+ (unsigned int)(entry->target_addr.s_node));
+ len+= sprintf(buffer+len,"%-10.10s",
+ entry->dev->name);
+ len+= sprintf(buffer+len,"%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
+ (int)(entry->hwaddr[0] & 0x000000FF),
+ (int)(entry->hwaddr[1] & 0x000000FF),
+ (int)(entry->hwaddr[2] & 0x000000FF),
+ (int)(entry->hwaddr[3] & 0x000000FF),
+ (int)(entry->hwaddr[4] & 0x000000FF),
+ (int)(entry->hwaddr[5] & 0x000000FF));
+ len+= sprintf(buffer+len,"%12lu ""%12lu ",
+ (unsigned long)entry->last_sent,
+ (unsigned long)entry->expires_at);
+ len+=sprintf(buffer+len,"%10u",
+ (unsigned int)entry->xmit_count);
+
+ len+=sprintf(buffer+len," resolved\n");
+ }
+ }
+
+ for (ct = 0; ct < AARP_HASH_SIZE; ct++) {
+ for (entry = unresolved[ct]; entry; entry = entry->next) {
+ len+= sprintf(buffer+len,"%6u:%-3u ",
+ (unsigned int)ntohs(entry->target_addr.s_net),
+ (unsigned int)(entry->target_addr.s_node));
+ len+= sprintf(buffer+len,"%-10.10s",
+ entry->dev->name);
+ len+= sprintf(buffer+len,"%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
+ (int)(entry->hwaddr[0] & 0x000000FF),
+ (int)(entry->hwaddr[1] & 0x000000FF),
+ (int)(entry->hwaddr[2] & 0x000000FF),
+ (int)(entry->hwaddr[3] & 0x000000FF),
+ (int)(entry->hwaddr[4] & 0x000000FF),
+ (int)(entry->hwaddr[5] & 0x000000FF));
+ len+= sprintf(buffer+len,"%12lu ""%12lu ",
+ (unsigned long)entry->last_sent,
+ (unsigned long)entry->expires_at);
+ len+=sprintf(buffer+len,"%10u",
+ (unsigned int)entry->xmit_count);
+ len+=sprintf(buffer+len," unresolved\n");
+ }
+ }
+
+ for (ct = 0; ct < AARP_HASH_SIZE; ct++) {
+ for (entry = proxies[ct]; entry; entry = entry->next) {
+ len+= sprintf(buffer+len,"%6u:%-3u ",
+ (unsigned int)ntohs(entry->target_addr.s_net),
+ (unsigned int)(entry->target_addr.s_node));
+ len+= sprintf(buffer+len,"%-10.10s",
+ entry->dev->name);
+ len+= sprintf(buffer+len,"%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
+ (int)(entry->hwaddr[0] & 0x000000FF),
+ (int)(entry->hwaddr[1] & 0x000000FF),
+ (int)(entry->hwaddr[2] & 0x000000FF),
+ (int)(entry->hwaddr[3] & 0x000000FF),
+ (int)(entry->hwaddr[4] & 0x000000FF),
+ (int)(entry->hwaddr[5] & 0x000000FF));
+ len+= sprintf(buffer+len,"%12lu ""%12lu ",
+ (unsigned long)entry->last_sent,
+ (unsigned long)entry->expires_at);
+ len+=sprintf(buffer+len,"%10u",
+ (unsigned int)entry->xmit_count);
+ len+=sprintf(buffer+len," proxy\n");
+ }
+ }
+
+ spin_unlock_bh(&aarp_lock);
+ return len;
+}
+
+#ifdef MODULE
+/* General module cleanup. Called from cleanup_module() in ddp.c. */
+void aarp_cleanup_module(void)
+{
+ del_timer(&aarp_timer);
+ unregister_netdevice_notifier(&aarp_notifier);
+ unregister_snap_client(aarp_snap_id);
+}
+#endif /* MODULE */
+#ifdef CONFIG_PROC_FS
+void aarp_register_proc_fs(void)
+{
+ proc_net_create("aarp", 0, aarp_get_info);
+}
+
+void aarp_unregister_proc_fs(void)
+{
+ proc_net_remove("aarp");
+}
+#endif
+#endif /* CONFIG_ATALK || CONFIG_ATALK_MODULE */
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/appletalk/ddp.c b/uClinux-2.4.31-uc0/net/appletalk/ddp.c
new file mode 100644
index 0000000..417d7c3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/appletalk/ddp.c
@@ -0,0 +1,2024 @@
+/*
+ * DDP: An implementation of the AppleTalk DDP protocol for
+ * Ethernet 'ELAP'.
+ *
+ * Alan Cox <Alan.Cox@linux.org>
+ *
+ * With more than a little assistance from
+ *
+ * Wesley Craig <netatalk@umich.edu>
+ *
+ * Fixes:
+ * Michael Callahan : Made routing work
+ * Wesley Craig : Fix probing to listen to a
+ * passed node id.
+ * Alan Cox : Added send/recvmsg support
+ * Alan Cox : Moved at. to protinfo in
+ * socket.
+ * Alan Cox : Added firewall hooks.
+ * Alan Cox : Supports new ARPHRD_LOOPBACK
+ * Christer Weinigel : Routing and /proc fixes.
+ * Bradford Johnson : LocalTalk.
+ * Tom Dyas : Module support.
+ * Alan Cox : Hooks for PPP (based on the
+ * LocalTalk hook).
+ * Alan Cox : Posix bits
+ * Alan Cox/Mike Freeman : Possible fix to NBP problems
+ * Bradford Johnson : IP-over-DDP (experimental)
+ * Jay Schulist : Moved IP-over-DDP to its own
+ * driver file. (ipddp.c & ipddp.h)
+ * Jay Schulist : Made work as module with
+ * AppleTalk drivers, cleaned it.
+ * Rob Newberry : Added proxy AARP and AARP
+ * procfs, moved probing to AARP
+ * module.
+ * Adrian Sun/
+ * Michael Zuelsdorff : fix for net.0 packets. don't
+ * allow illegal ether/tokentalk
+ * port assignment. we lose a
+ * valid localtalk port as a
+ * result.
+ * Arnaldo C. de Melo : Cleanup, in preparation for
+ * shared skb support 8)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/config.h>
+#if defined(CONFIG_ATALK) || defined(CONFIG_ATALK_MODULE)
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/notifier.h>
+#include <linux/netdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/route.h>
+#include <linux/inet.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/termios.h> /* For TIOCOUTQ/INQ */
+#include <net/datalink.h>
+#include <net/p8022.h>
+#include <net/psnap.h>
+#include <net/sock.h>
+#include <linux/ip.h>
+#include <net/route.h>
+#include <linux/atalk.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+
+#ifdef CONFIG_PROC_FS
+extern void aarp_register_proc_fs(void);
+extern void aarp_unregister_proc_fs(void);
+#endif
+
+extern void aarp_cleanup_module(void);
+
+extern void aarp_probe_network(struct atalk_iface *atif);
+extern int aarp_proxy_probe_network(struct atalk_iface *atif,
+ struct at_addr *sa);
+extern void aarp_proxy_remove(struct net_device *dev, struct at_addr *sa);
+
+#undef APPLETALK_DEBUG
+#ifdef APPLETALK_DEBUG
+#define DPRINT(x) print(x)
+#else
+#define DPRINT(x)
+#endif /* APPLETALK_DEBUG */
+
+#ifdef CONFIG_SYSCTL
+extern void atalk_register_sysctl(void);
+extern void atalk_unregister_sysctl(void);
+#endif /* CONFIG_SYSCTL */
+
+struct datalink_proto *ddp_dl, *aarp_dl;
+static struct proto_ops atalk_dgram_ops;
+
+/**************************************************************************\
+* *
+* Handlers for the socket list. *
+* *
+\**************************************************************************/
+
+static struct sock *atalk_sockets;
+static spinlock_t atalk_sockets_lock = SPIN_LOCK_UNLOCKED;
+
+extern inline void atalk_insert_socket(struct sock *sk)
+{
+ spin_lock_bh(&atalk_sockets_lock);
+ sk->next = atalk_sockets;
+ if (sk->next)
+ atalk_sockets->pprev = &sk->next;
+ atalk_sockets = sk;
+ sk->pprev = &atalk_sockets;
+ spin_unlock_bh(&atalk_sockets_lock);
+}
+
+extern inline void atalk_remove_socket(struct sock *sk)
+{
+ spin_lock_bh(&atalk_sockets_lock);
+ if (sk->pprev) {
+ if (sk->next)
+ sk->next->pprev = sk->pprev;
+ *sk->pprev = sk->next;
+ sk->pprev = NULL;
+ }
+ spin_unlock_bh(&atalk_sockets_lock);
+}
+
+static struct sock *atalk_search_socket(struct sockaddr_at *to,
+ struct atalk_iface *atif)
+{
+ struct sock *s;
+
+ spin_lock_bh(&atalk_sockets_lock);
+ for (s = atalk_sockets; s; s = s->next) {
+ if (to->sat_port != s->protinfo.af_at.src_port)
+ continue;
+
+ if (to->sat_addr.s_net == ATADDR_ANYNET &&
+ to->sat_addr.s_node == ATADDR_BCAST &&
+ s->protinfo.af_at.src_net == atif->address.s_net)
+ break;
+
+ if (to->sat_addr.s_net == s->protinfo.af_at.src_net &&
+ (to->sat_addr.s_node == s->protinfo.af_at.src_node ||
+ to->sat_addr.s_node == ATADDR_BCAST ||
+ to->sat_addr.s_node == ATADDR_ANYNODE))
+ break;
+
+ /* XXXX.0 -- we got a request for this router. make sure
+ * that the node is appropriately set. */
+ if (to->sat_addr.s_node == ATADDR_ANYNODE &&
+ to->sat_addr.s_net != ATADDR_ANYNET &&
+ atif->address.s_node == s->protinfo.af_at.src_node) {
+ to->sat_addr.s_node = atif->address.s_node;
+ break;
+ }
+ }
+ spin_unlock_bh(&atalk_sockets_lock);
+ return s;
+}
+
+/*
+ * Try to find a socket matching ADDR in the socket list,
+ * if found then return it. If not, insert SK into the
+ * socket list.
+ *
+ * This entire operation must execute atomically.
+ */
+static struct sock *atalk_find_or_insert_socket(struct sock *sk,
+ struct sockaddr_at *sat)
+{
+ struct sock *s;
+
+ spin_lock_bh(&atalk_sockets_lock);
+ for (s = atalk_sockets; s; s = s->next)
+ if (s->protinfo.af_at.src_net == sat->sat_addr.s_net &&
+ s->protinfo.af_at.src_node == sat->sat_addr.s_node &&
+ s->protinfo.af_at.src_port == sat->sat_port)
+ break;
+
+ if (!s) {
+ /* Wheee, it's free, assign and insert. */
+ sk->next = atalk_sockets;
+ if (sk->next)
+ atalk_sockets->pprev = &sk->next;
+ atalk_sockets = sk;
+ sk->pprev = &atalk_sockets;
+ }
+
+ spin_unlock_bh(&atalk_sockets_lock);
+ return s;
+}
+
+static void atalk_destroy_timer(unsigned long data)
+{
+ struct sock *sk = (struct sock *) data;
+
+ if (!atomic_read(&sk->wmem_alloc) &&
+ !atomic_read(&sk->rmem_alloc) && sk->dead) {
+ sock_put(sk);
+ MOD_DEC_USE_COUNT;
+ } else {
+ sk->timer.expires = jiffies + SOCK_DESTROY_TIME;
+ add_timer(&sk->timer);
+ }
+}
+
+extern inline void atalk_destroy_socket(struct sock *sk)
+{
+ atalk_remove_socket(sk);
+ skb_queue_purge(&sk->receive_queue);
+
+ if (!atomic_read(&sk->wmem_alloc) &&
+ !atomic_read(&sk->rmem_alloc) && sk->dead) {
+ sock_put(sk);
+ MOD_DEC_USE_COUNT;
+ } else {
+ init_timer(&sk->timer);
+ sk->timer.expires = jiffies + SOCK_DESTROY_TIME;
+ sk->timer.function = atalk_destroy_timer;
+ sk->timer.data = (unsigned long) sk;
+ add_timer(&sk->timer);
+ }
+}
+
+/* Called from proc fs */
+static int atalk_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ off_t pos = 0;
+ off_t begin = 0;
+ int len = sprintf(buffer, "Type local_addr remote_addr tx_queue "
+ "rx_queue st uid\n");
+ struct sock *s;
+ /* Output the AppleTalk data for the /proc filesystem */
+
+ spin_lock_bh(&atalk_sockets_lock);
+ for (s = atalk_sockets; s; s = s->next) {
+ len += sprintf(buffer + len,"%02X ", s->type);
+ len += sprintf(buffer + len,"%04X:%02X:%02X ",
+ ntohs(s->protinfo.af_at.src_net),
+ s->protinfo.af_at.src_node,
+ s->protinfo.af_at.src_port);
+ len += sprintf(buffer + len,"%04X:%02X:%02X ",
+ ntohs(s->protinfo.af_at.dest_net),
+ s->protinfo.af_at.dest_node,
+ s->protinfo.af_at.dest_port);
+ len += sprintf(buffer + len,"%08X:%08X ",
+ atomic_read(&s->wmem_alloc),
+ atomic_read(&s->rmem_alloc));
+ len += sprintf(buffer + len,"%02X %d\n", s->state,
+ SOCK_INODE(s->socket)->i_uid);
+
+ /* Are we still dumping unwanted data then discard the record */
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0; /* Keep dumping into the buffer start */
+ begin = pos;
+ }
+ if (pos > offset + length) /* We have dumped enough */
+ break;
+ }
+ spin_unlock_bh(&atalk_sockets_lock);
+
+ /* The data in question runs from begin to begin+len */
+ *start = buffer + offset - begin; /* Start of wanted data */
+ len -= offset - begin; /* Remove unwanted header data from length */
+ if (len > length)
+ len = length; /* Remove unwanted tail data from length */
+
+ return len;
+}
+
+/**************************************************************************\
+* *
+* Routing tables for the AppleTalk socket layer. *
+* *
+\**************************************************************************/
+
+/* Anti-deadlock ordering is router_lock --> iface_lock -DaveM */
+static struct atalk_route *atalk_router_list;
+static rwlock_t atalk_router_lock = RW_LOCK_UNLOCKED;
+
+static struct atalk_iface *atalk_iface_list;
+static spinlock_t atalk_iface_lock = SPIN_LOCK_UNLOCKED;
+
+/* For probing devices or in a routerless network */
+static struct atalk_route atrtr_default;
+
+/* AppleTalk interface control */
+/*
+ * Drop a device. Doesn't drop any of its routes - that is the caller's
+ * problem. Called when we down the interface or delete the address.
+ */
+static void atif_drop_device(struct net_device *dev)
+{
+ struct atalk_iface **iface = &atalk_iface_list;
+ struct atalk_iface *tmp;
+
+ spin_lock_bh(&atalk_iface_lock);
+ while ((tmp = *iface) != NULL) {
+ if (tmp->dev == dev) {
+ *iface = tmp->next;
+ kfree(tmp);
+ dev->atalk_ptr = NULL;
+ MOD_DEC_USE_COUNT;
+ } else
+ iface = &tmp->next;
+ }
+ spin_unlock_bh(&atalk_iface_lock);
+}
+
+static struct atalk_iface *atif_add_device(struct net_device *dev,
+ struct at_addr *sa)
+{
+ struct atalk_iface *iface = kmalloc(sizeof(*iface), GFP_KERNEL);
+
+ if (!iface)
+ return NULL;
+
+ iface->dev = dev;
+ dev->atalk_ptr = iface;
+ iface->address = *sa;
+ iface->status = 0;
+
+ spin_lock_bh(&atalk_iface_lock);
+ iface->next = atalk_iface_list;
+ atalk_iface_list = iface;
+ spin_unlock_bh(&atalk_iface_lock);
+
+ MOD_INC_USE_COUNT;
+ return iface;
+}
+
+/* Perform phase 2 AARP probing on our tentative address */
+static int atif_probe_device(struct atalk_iface *atif)
+{
+ int netrange = ntohs(atif->nets.nr_lastnet) -
+ ntohs(atif->nets.nr_firstnet) + 1;
+ int probe_net = ntohs(atif->address.s_net);
+ int probe_node = atif->address.s_node;
+ int netct, nodect;
+
+ /* Offset the network we start probing with */
+ if (probe_net == ATADDR_ANYNET) {
+ probe_net = ntohs(atif->nets.nr_firstnet);
+ if (netrange)
+ probe_net += jiffies % netrange;
+ }
+ if (probe_node == ATADDR_ANYNODE)
+ probe_node = jiffies & 0xFF;
+
+ /* Scan the networks */
+ atif->status |= ATIF_PROBE;
+ for (netct = 0; netct <= netrange; netct++) {
+ /* Sweep the available nodes from a given start */
+ atif->address.s_net = htons(probe_net);
+ for (nodect = 0; nodect < 256; nodect++) {
+ atif->address.s_node = ((nodect+probe_node) & 0xFF);
+ if (atif->address.s_node > 0 &&
+ atif->address.s_node < 254) {
+ /* Probe a proposed address */
+ aarp_probe_network(atif);
+
+ if (!(atif->status & ATIF_PROBE_FAIL)) {
+ atif->status &= ~ATIF_PROBE;
+ return 0;
+ }
+ }
+ atif->status &= ~ATIF_PROBE_FAIL;
+ }
+ probe_net++;
+ if (probe_net > ntohs(atif->nets.nr_lastnet))
+ probe_net = ntohs(atif->nets.nr_firstnet);
+ }
+ atif->status &= ~ATIF_PROBE;
+
+ return -EADDRINUSE; /* Network is full... */
+}
+
+
+/* Perform AARP probing for a proxy address */
+static int atif_proxy_probe_device(struct atalk_iface *atif,
+ struct at_addr* proxy_addr)
+{
+ int netrange = ntohs(atif->nets.nr_lastnet) -
+ ntohs(atif->nets.nr_firstnet) + 1;
+ /* we probe the interface's network */
+ int probe_net = ntohs(atif->address.s_net);
+ int probe_node = ATADDR_ANYNODE; /* we'll take anything */
+ int netct, nodect;
+
+ /* Offset the network we start probing with */
+ if (probe_net == ATADDR_ANYNET) {
+ probe_net = ntohs(atif->nets.nr_firstnet);
+ if (netrange)
+ probe_net += jiffies % netrange;
+ }
+
+ if (probe_node == ATADDR_ANYNODE)
+ probe_node = jiffies & 0xFF;
+
+ /* Scan the networks */
+ for (netct = 0; netct <= netrange; netct++) {
+ /* Sweep the available nodes from a given start */
+ proxy_addr->s_net = htons(probe_net);
+ for (nodect = 0; nodect < 256; nodect++) {
+ proxy_addr->s_node = ((nodect + probe_node) & 0xFF);
+ if (proxy_addr->s_node > 0 &&
+ proxy_addr->s_node < 254) {
+ /* Tell AARP to probe a proposed address */
+ int ret = aarp_proxy_probe_network(atif,
+ proxy_addr);
+
+ if (ret != -EADDRINUSE)
+ return ret;
+ }
+ }
+ probe_net++;
+ if (probe_net > ntohs(atif->nets.nr_lastnet))
+ probe_net = ntohs(atif->nets.nr_firstnet);
+ }
+
+ return -EADDRINUSE; /* Network is full... */
+}
+
+
+struct at_addr *atalk_find_dev_addr(struct net_device *dev)
+{
+ struct atalk_iface *iface = dev->atalk_ptr;
+ return iface ? &iface->address : NULL;
+}
+
+static struct at_addr *atalk_find_primary(void)
+{
+ struct atalk_iface *fiface = NULL;
+ struct at_addr *retval;
+ struct atalk_iface *iface;
+
+ /*
+ * Return a point-to-point interface only if
+ * there is no non-ptp interface available.
+ */
+ spin_lock_bh(&atalk_iface_lock);
+ for (iface = atalk_iface_list; iface; iface = iface->next) {
+ if (!fiface && !(iface->dev->flags & IFF_LOOPBACK))
+ fiface = iface;
+ if (!(iface->dev->flags & (IFF_LOOPBACK | IFF_POINTOPOINT))) {
+ retval = &iface->address;
+ goto out;
+ }
+ }
+
+ if (fiface)
+ retval = &fiface->address;
+ else if (atalk_iface_list)
+ retval = &atalk_iface_list->address;
+ else
+ retval = NULL;
+out: spin_unlock_bh(&atalk_iface_lock);
+ return retval;
+}
+
+/*
+ * Find a match for 'any network' - ie any of our interfaces with that
+ * node number will do just nicely.
+ */
+static struct atalk_iface *atalk_find_anynet(int node, struct net_device *dev)
+{
+ struct atalk_iface *iface = dev->atalk_ptr;
+
+ if (!iface || iface->status & ATIF_PROBE)
+ return NULL;
+
+ if (node == ATADDR_BCAST ||
+ iface->address.s_node == node ||
+ node == ATADDR_ANYNODE)
+ return iface;
+
+ return NULL;
+}
+
+/* Find a match for a specific network:node pair */
+static struct atalk_iface *atalk_find_interface(int net, int node)
+{
+ struct atalk_iface *iface;
+
+ spin_lock_bh(&atalk_iface_lock);
+ for (iface = atalk_iface_list; iface; iface = iface->next) {
+ if ((node == ATADDR_BCAST ||
+ node == ATADDR_ANYNODE ||
+ iface->address.s_node == node) &&
+ iface->address.s_net == net &&
+ !(iface->status & ATIF_PROBE))
+ break;
+
+ /* XXXX.0 -- net.0 returns the iface associated with net */
+ if (node == ATADDR_ANYNODE && net != ATADDR_ANYNET &&
+ ntohs(iface->nets.nr_firstnet) <= ntohs(net) &&
+ ntohs(net) <= ntohs(iface->nets.nr_lastnet))
+ break;
+ }
+ spin_unlock_bh(&atalk_iface_lock);
+ return iface;
+}
+
+
+/*
+ * Find a route for an AppleTalk packet. This ought to get cached in
+ * the socket (later on...). We know about host routes and the fact
+ * that a route must be direct to broadcast.
+ */
+static struct atalk_route *atrtr_find(struct at_addr *target)
+{
+ /*
+ * we must search through all routes unless we find a
+ * host route, because some host routes might overlap
+ * network routes
+ */
+ struct atalk_route *net_route = NULL;
+ struct atalk_route *r;
+
+ read_lock_bh(&atalk_router_lock);
+ for (r = atalk_router_list; r; r = r->next) {
+ if (!(r->flags & RTF_UP))
+ continue;
+
+ if (r->target.s_net == target->s_net) {
+ if (r->flags & RTF_HOST) {
+ /*
+ * if this host route is for the target,
+ * the we're done
+ */
+ if (r->target.s_node == target->s_node)
+ goto out;
+ } else
+ /*
+ * this route will work if there isn't a
+ * direct host route, so cache it
+ */
+ net_route = r;
+ }
+ }
+
+ /*
+ * if we found a network route but not a direct host
+ * route, then return it
+ */
+ if (net_route)
+ r = net_route;
+ else if (atrtr_default.dev)
+ r = &atrtr_default;
+ else /* No route can be found */
+ r = NULL;
+out: read_unlock_bh(&atalk_router_lock);
+ return r;
+}
+
+
+/*
+ * Given an AppleTalk network, find the device to use. This can be
+ * a simple lookup.
+ */
+struct net_device *atrtr_get_dev(struct at_addr *sa)
+{
+ struct atalk_route *atr = atrtr_find(sa);
+ return atr ? atr->dev : NULL;
+}
+
+/* Set up a default router */
+static void atrtr_set_default(struct net_device *dev)
+{
+ atrtr_default.dev = dev;
+ atrtr_default.flags = RTF_UP;
+ atrtr_default.gateway.s_net = htons(0);
+ atrtr_default.gateway.s_node = 0;
+}
+
+/*
+ * Add a router. Basically make sure it looks valid and stuff the
+ * entry in the list. While it uses netranges we always set them to one
+ * entry to work like netatalk.
+ */
+static int atrtr_create(struct rtentry *r, struct net_device *devhint)
+{
+ struct sockaddr_at *ta = (struct sockaddr_at *)&r->rt_dst;
+ struct sockaddr_at *ga = (struct sockaddr_at *)&r->rt_gateway;
+ struct atalk_route *rt;
+ struct atalk_iface *iface, *riface;
+ int retval;
+
+ /*
+ * Fixme: Raise/Lower a routing change semaphore for these
+ * operations.
+ */
+
+ /* Validate the request */
+ if (ta->sat_family != AF_APPLETALK)
+ return -EINVAL;
+
+ if (!devhint && ga->sat_family != AF_APPLETALK)
+ return -EINVAL;
+
+ /* Now walk the routing table and make our decisions */
+ write_lock_bh(&atalk_router_lock);
+ for (rt = atalk_router_list; rt; rt = rt->next) {
+ if (r->rt_flags != rt->flags)
+ continue;
+
+ if (ta->sat_addr.s_net == rt->target.s_net) {
+ if (!(rt->flags & RTF_HOST))
+ break;
+ if (ta->sat_addr.s_node == rt->target.s_node)
+ break;
+ }
+ }
+
+ if (!devhint) {
+ riface = NULL;
+
+ spin_lock_bh(&atalk_iface_lock);
+ for (iface = atalk_iface_list; iface; iface = iface->next) {
+ if (!riface &&
+ ntohs(ga->sat_addr.s_net) >=
+ ntohs(iface->nets.nr_firstnet) &&
+ ntohs(ga->sat_addr.s_net) <=
+ ntohs(iface->nets.nr_lastnet))
+ riface = iface;
+
+ if (ga->sat_addr.s_net == iface->address.s_net &&
+ ga->sat_addr.s_node == iface->address.s_node)
+ riface = iface;
+ }
+ spin_unlock_bh(&atalk_iface_lock);
+
+ retval = -ENETUNREACH;
+ if (!riface)
+ goto out;
+
+ devhint = riface->dev;
+ }
+
+ if (!rt) {
+ rt = kmalloc(sizeof(struct atalk_route), GFP_ATOMIC);
+
+ retval = -ENOBUFS;
+ if (!rt)
+ goto out;
+
+ rt->next = atalk_router_list;
+ atalk_router_list = rt;
+ }
+
+ /* Fill in the routing entry */
+ rt->target = ta->sat_addr;
+ rt->dev = devhint;
+ rt->flags = r->rt_flags;
+ rt->gateway = ga->sat_addr;
+
+ retval = 0;
+out: write_unlock_bh(&atalk_router_lock);
+ return retval;
+}
+
+/* Delete a route. Find it and discard it */
+static int atrtr_delete(struct at_addr * addr)
+{
+ struct atalk_route **r = &atalk_router_list;
+ int retval = 0;
+ struct atalk_route *tmp;
+
+ write_lock_bh(&atalk_router_lock);
+ while ((tmp = *r) != NULL) {
+ if (tmp->target.s_net == addr->s_net &&
+ (!(tmp->flags&RTF_GATEWAY) ||
+ tmp->target.s_node == addr->s_node)) {
+ *r = tmp->next;
+ kfree(tmp);
+ goto out;
+ }
+ r = &tmp->next;
+ }
+ retval = -ENOENT;
+out: write_unlock_bh(&atalk_router_lock);
+ return retval;
+}
+
+/*
+ * Called when a device is downed. Just throw away any routes
+ * via it.
+ */
+void atrtr_device_down(struct net_device *dev)
+{
+ struct atalk_route **r = &atalk_router_list;
+ struct atalk_route *tmp;
+
+ write_lock_bh(&atalk_router_lock);
+ while ((tmp = *r) != NULL) {
+ if (tmp->dev == dev) {
+ *r = tmp->next;
+ kfree(tmp);
+ } else
+ r = &tmp->next;
+ }
+ write_unlock_bh(&atalk_router_lock);
+
+ if (atrtr_default.dev == dev)
+ atrtr_set_default(NULL);
+}
+
+/* Actually down the interface */
+static inline void atalk_dev_down(struct net_device *dev)
+{
+ atrtr_device_down(dev); /* Remove all routes for the device */
+ aarp_device_down(dev); /* Remove AARP entries for the device */
+ atif_drop_device(dev); /* Remove the device */
+}
+
+/*
+ * A device event has occurred. Watch for devices going down and
+ * delete our use of them (iface and route).
+ */
+static int ddp_device_event(struct notifier_block *this, unsigned long event,
+ void *ptr)
+{
+ if (event == NETDEV_DOWN)
+ /* Discard any use of this */
+ atalk_dev_down((struct net_device *) ptr);
+
+ return NOTIFY_DONE;
+}
+
+/* ioctl calls. Shouldn't even need touching */
+/* Device configuration ioctl calls */
+static int atif_ioctl(int cmd, void *arg)
+{
+ static char aarp_mcast[6] = {0x09, 0x00, 0x00, 0xFF, 0xFF, 0xFF};
+ struct ifreq atreq;
+ struct netrange *nr;
+ struct sockaddr_at *sa;
+ struct net_device *dev;
+ struct atalk_iface *atif;
+ int ct;
+ int limit;
+ struct rtentry rtdef;
+ int add_route;
+
+ if (copy_from_user(&atreq, arg, sizeof(atreq)))
+ return -EFAULT;
+
+ dev = __dev_get_by_name(atreq.ifr_name);
+ if (!dev)
+ return -ENODEV;
+
+ sa = (struct sockaddr_at*) &atreq.ifr_addr;
+ atif = atalk_find_dev(dev);
+
+ switch (cmd) {
+ case SIOCSIFADDR:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (sa->sat_family != AF_APPLETALK)
+ return -EINVAL;
+ if (dev->type != ARPHRD_ETHER &&
+ dev->type != ARPHRD_LOOPBACK &&
+ dev->type != ARPHRD_LOCALTLK &&
+ dev->type != ARPHRD_PPP)
+ return -EPROTONOSUPPORT;
+
+ nr = (struct netrange *) &sa->sat_zero[0];
+ add_route = 1;
+
+ /*
+ * if this is a point-to-point iface, and we already
+ * have an iface for this AppleTalk address, then we
+ * should not add a route
+ */
+ if ((dev->flags & IFF_POINTOPOINT) &&
+ atalk_find_interface(sa->sat_addr.s_net,
+ sa->sat_addr.s_node)) {
+ printk(KERN_DEBUG "AppleTalk: point-to-point "
+ "interface added with "
+ "existing address\n");
+ add_route = 0;
+ }
+
+ /*
+ * Phase 1 is fine on LocalTalk but we don't do
+ * EtherTalk phase 1. Anyone wanting to add it go ahead.
+ */
+ if (dev->type == ARPHRD_ETHER && nr->nr_phase != 2)
+ return -EPROTONOSUPPORT;
+ if (sa->sat_addr.s_node == ATADDR_BCAST ||
+ sa->sat_addr.s_node == 254)
+ return -EINVAL;
+ if (atif) {
+ /* Already setting address */
+ if (atif->status & ATIF_PROBE)
+ return -EBUSY;
+
+ atif->address.s_net = sa->sat_addr.s_net;
+ atif->address.s_node = sa->sat_addr.s_node;
+ atrtr_device_down(dev); /* Flush old routes */
+ } else {
+ atif = atif_add_device(dev, &sa->sat_addr);
+ if (!atif)
+ return -ENOMEM;
+ }
+ atif->nets = *nr;
+
+ /*
+ * Check if the chosen address is used. If so we
+ * error and atalkd will try another.
+ */
+
+ if (!(dev->flags & IFF_LOOPBACK) &&
+ !(dev->flags & IFF_POINTOPOINT) &&
+ atif_probe_device(atif) < 0) {
+ atif_drop_device(dev);
+ return -EADDRINUSE;
+ }
+
+ /* Hey it worked - add the direct routes */
+ sa = (struct sockaddr_at *) &rtdef.rt_gateway;
+ sa->sat_family = AF_APPLETALK;
+ sa->sat_addr.s_net = atif->address.s_net;
+ sa->sat_addr.s_node = atif->address.s_node;
+ sa = (struct sockaddr_at *) &rtdef.rt_dst;
+ rtdef.rt_flags = RTF_UP;
+ sa->sat_family = AF_APPLETALK;
+ sa->sat_addr.s_node = ATADDR_ANYNODE;
+ if (dev->flags & IFF_LOOPBACK ||
+ dev->flags & IFF_POINTOPOINT)
+ rtdef.rt_flags |= RTF_HOST;
+
+ /* Routerless initial state */
+ if (nr->nr_firstnet == htons(0) &&
+ nr->nr_lastnet == htons(0xFFFE)) {
+ sa->sat_addr.s_net = atif->address.s_net;
+ atrtr_create(&rtdef, dev);
+ atrtr_set_default(dev);
+ } else {
+ limit = ntohs(nr->nr_lastnet);
+ if (limit - ntohs(nr->nr_firstnet) > 4096) {
+ printk(KERN_WARNING "Too many routes/"
+ "iface.\n");
+ return -EINVAL;
+ }
+ if (add_route)
+ for (ct = ntohs(nr->nr_firstnet);
+ ct <= limit; ct++) {
+ sa->sat_addr.s_net = htons(ct);
+ atrtr_create(&rtdef, dev);
+ }
+ }
+ dev_mc_add(dev, aarp_mcast, 6, 1);
+ return 0;
+
+ case SIOCGIFADDR:
+ if (!atif)
+ return -EADDRNOTAVAIL;
+
+ sa->sat_family = AF_APPLETALK;
+ sa->sat_addr = atif->address;
+ break;
+
+ case SIOCGIFBRDADDR:
+ if (!atif)
+ return -EADDRNOTAVAIL;
+
+ sa->sat_family = AF_APPLETALK;
+ sa->sat_addr.s_net = atif->address.s_net;
+ sa->sat_addr.s_node = ATADDR_BCAST;
+ break;
+
+ case SIOCATALKDIFADDR:
+ case SIOCDIFADDR:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (sa->sat_family != AF_APPLETALK)
+ return -EINVAL;
+ atalk_dev_down(dev);
+ break;
+
+ case SIOCSARP:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (sa->sat_family != AF_APPLETALK)
+ return -EINVAL;
+ if (!atif)
+ return -EADDRNOTAVAIL;
+
+ /*
+ * for now, we only support proxy AARP on ELAP;
+ * we should be able to do it for LocalTalk, too.
+ */
+ if (dev->type != ARPHRD_ETHER)
+ return -EPROTONOSUPPORT;
+
+ /*
+ * atif points to the current interface on this network;
+ * we aren't concerned about its current status (at
+ * least for now), but it has all the settings about
+ * the network we're going to probe. Consequently, it
+ * must exist.
+ */
+ if (!atif)
+ return -EADDRNOTAVAIL;
+
+ nr = (struct netrange *) &(atif->nets);
+ /*
+ * Phase 1 is fine on Localtalk but we don't do
+ * Ethertalk phase 1. Anyone wanting to add it go ahead.
+ */
+ if (dev->type == ARPHRD_ETHER && nr->nr_phase != 2)
+ return -EPROTONOSUPPORT;
+
+ if (sa->sat_addr.s_node == ATADDR_BCAST ||
+ sa->sat_addr.s_node == 254)
+ return -EINVAL;
+
+ /*
+ * Check if the chosen address is used. If so we
+ * error and ATCP will try another.
+ */
+ if (atif_proxy_probe_device(atif, &(sa->sat_addr)) < 0)
+ return -EADDRINUSE;
+
+ /*
+ * We now have an address on the local network, and
+ * the AARP code will defend it for us until we take it
+ * down. We don't set up any routes right now, because
+ * ATCP will install them manually via SIOCADDRT.
+ */
+ break;
+
+ case SIOCDARP:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (sa->sat_family != AF_APPLETALK)
+ return -EINVAL;
+ if (!atif)
+ return -EADDRNOTAVAIL;
+
+ /* give to aarp module to remove proxy entry */
+ aarp_proxy_remove(atif->dev, &(sa->sat_addr));
+ return 0;
+ }
+
+ return copy_to_user(arg, &atreq, sizeof(atreq)) ? -EFAULT : 0;
+}
+
+/* Routing ioctl() calls */
+static int atrtr_ioctl(unsigned int cmd, void *arg)
+{
+ struct net_device *dev = NULL;
+ struct rtentry rt;
+
+ if (copy_from_user(&rt, arg, sizeof(rt)))
+ return -EFAULT;
+
+ switch (cmd) {
+ case SIOCDELRT:
+ if (rt.rt_dst.sa_family != AF_APPLETALK)
+ return -EINVAL;
+ return atrtr_delete(&((struct sockaddr_at *)
+ &rt.rt_dst)->sat_addr);
+
+ case SIOCADDRT:
+ /* FIXME: the name of the device is still in user
+ * space, isn't it? */
+ if (rt.rt_dev) {
+ dev = __dev_get_by_name(rt.rt_dev);
+ if (!dev)
+ return -ENODEV;
+ }
+ return atrtr_create(&rt, dev);
+ }
+ return -EINVAL;
+}
+
+/* Called from proc fs - just make it print the ifaces neatly */
+static int atalk_if_get_info(char *buffer, char **start, off_t offset,
+ int length)
+{
+ off_t pos = 0;
+ off_t begin = 0;
+ struct atalk_iface *iface;
+ int len = sprintf(buffer, "Interface Address "
+ "Networks Status\n");
+
+ spin_lock_bh(&atalk_iface_lock);
+ for (iface = atalk_iface_list; iface; iface = iface->next) {
+ len += sprintf(buffer+len,"%-16s %04X:%02X %04X-%04X %d\n",
+ iface->dev->name, ntohs(iface->address.s_net),
+ iface->address.s_node,
+ ntohs(iface->nets.nr_firstnet),
+ ntohs(iface->nets.nr_lastnet), iface->status);
+ pos = begin + len;
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (pos > offset + length)
+ break;
+ }
+ spin_unlock_bh(&atalk_iface_lock);
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+ if (len > length)
+ len = length;
+ return len;
+}
+
+/* Called from proc fs - just make it print the routes neatly */
+static int atalk_rt_get_info(char *buffer, char **start, off_t offset,
+ int length)
+{
+ off_t pos = 0;
+ off_t begin = 0;
+ int len = sprintf(buffer, "Target Router Flags Dev\n");
+ struct atalk_route *rt;
+
+ if (atrtr_default.dev) {
+ rt = &atrtr_default;
+ len += sprintf(buffer + len,"Default %04X:%02X %-4d %s\n",
+ ntohs(rt->gateway.s_net), rt->gateway.s_node,
+ rt->flags, rt->dev->name);
+ }
+
+ read_lock_bh(&atalk_router_lock);
+ for (rt = atalk_router_list; rt; rt = rt->next) {
+ len += sprintf(buffer + len,
+ "%04X:%02X %04X:%02X %-4d %s\n",
+ ntohs(rt->target.s_net), rt->target.s_node,
+ ntohs(rt->gateway.s_net), rt->gateway.s_node,
+ rt->flags, rt->dev->name);
+ pos = begin + len;
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (pos > offset + length)
+ break;
+ }
+ read_unlock_bh(&atalk_router_lock);
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+ if (len > length)
+ len = length;
+ return len;
+}
+
+/**************************************************************************\
+* *
+* Handling for system calls applied via the various interfaces to an *
+* AppleTalk socket object. *
+* *
+\**************************************************************************/
+
+/*
+ * Checksum: This is 'optional'. It's quite likely also a good
+ * candidate for assembler hackery 8)
+ */
+unsigned short atalk_checksum(struct ddpehdr *ddp, int len)
+{
+ unsigned long sum = 0; /* Assume unsigned long is >16 bits */
+ unsigned char *data = (unsigned char *) ddp;
+
+ len -= 4; /* skip header 4 bytes */
+ data += 4;
+
+ /* This ought to be unwrapped neatly. I'll trust gcc for now */
+ while (len--) {
+ sum += *data;
+ sum <<= 1;
+ if (sum & 0x10000) {
+ sum++;
+ sum &= 0xFFFF;
+ }
+ data++;
+ }
+ /* Use 0xFFFF for 0. 0 itself means none */
+ return sum ? htons((unsigned short) sum) : 0xFFFF;
+}
+
+/*
+ * Create a socket. Initialise the socket, blank the addresses
+ * set the state.
+ */
+static int atalk_create(struct socket *sock, int protocol)
+{
+ struct sock *sk = sk_alloc(PF_APPLETALK, GFP_KERNEL, 1);
+
+ if (!sk)
+ return -ENOMEM;
+
+ switch (sock->type) {
+ /*
+ * We permit SOCK_DGRAM and RAW is an extension. It is
+ * trivial to do and gives you the full ELAP frame.
+ * Should be handy for CAP 8)
+ */
+ case SOCK_RAW:
+ case SOCK_DGRAM:
+ sock->ops = &atalk_dgram_ops;
+ break;
+
+ case SOCK_STREAM:
+ /*
+ * TODO: if you want to implement ADSP, here's the
+ * place to start
+ */
+ /*
+ sock->ops = &atalk_stream_ops;
+ break;
+ */
+ default:
+ sk_free(sk);
+ return -ESOCKTNOSUPPORT;
+ }
+
+ MOD_INC_USE_COUNT;
+ sock_init_data(sock, sk);
+ sk->destruct = NULL;
+ /* Checksums on by default */
+ sk->zapped = 1;
+ return 0;
+}
+
+/* Free a socket. No work needed */
+static int atalk_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ if (!sk)
+ return 0;
+
+ if (!sk->dead)
+ sk->state_change(sk);
+
+ sk->dead = 1;
+ sock->sk = NULL;
+ atalk_destroy_socket(sk);
+ return 0;
+}
+
+/*
+ * Pick a source port when one is not given. If we can
+ * find a suitable free one, we insert the socket into
+ * the tables using it.
+ *
+ * This whole operation must be atomic.
+ */
+static int atalk_pick_and_bind_port(struct sock *sk, struct sockaddr_at *sat)
+{
+ struct sock *s;
+ int retval;
+
+ spin_lock_bh(&atalk_sockets_lock);
+
+ for (sat->sat_port = ATPORT_RESERVED;
+ sat->sat_port < ATPORT_LAST;
+ sat->sat_port++) {
+ for (s = atalk_sockets; s; s = s->next) {
+ if (s->protinfo.af_at.src_net == sat->sat_addr.s_net &&
+ s->protinfo.af_at.src_node ==
+ sat->sat_addr.s_node &&
+ s->protinfo.af_at.src_port == sat->sat_port)
+ goto try_next_port;
+ }
+
+ /* Wheee, it's free, assign and insert. */
+ sk->next = atalk_sockets;
+ if (sk->next)
+ atalk_sockets->pprev = &sk->next;
+ atalk_sockets = sk;
+ sk->pprev = &atalk_sockets;
+ sk->protinfo.af_at.src_port = sat->sat_port;
+ retval = 0;
+ goto out;
+
+ try_next_port:
+ ;
+ }
+
+ retval = -EBUSY;
+out: spin_unlock_bh(&atalk_sockets_lock);
+ return retval;
+}
+
+static int atalk_autobind(struct sock *sk)
+{
+ struct sockaddr_at sat;
+ int n;
+ struct at_addr *ap = atalk_find_primary();
+
+ if (!ap || ap->s_net == htons(ATADDR_ANYNET))
+ return -EADDRNOTAVAIL;
+
+ sk->protinfo.af_at.src_net = sat.sat_addr.s_net = ap->s_net;
+ sk->protinfo.af_at.src_node = sat.sat_addr.s_node = ap->s_node;
+
+ n = atalk_pick_and_bind_port(sk, &sat);
+ if (n < 0)
+ return n;
+
+ sk->zapped = 0;
+ return 0;
+}
+
+/* Set the address 'our end' of the connection */
+static int atalk_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sockaddr_at *addr = (struct sockaddr_at *)uaddr;
+ struct sock *sk = sock->sk;
+
+ if (!sk->zapped || addr_len != sizeof(struct sockaddr_at))
+ return -EINVAL;
+
+ if (addr->sat_family != AF_APPLETALK)
+ return -EAFNOSUPPORT;
+
+ if (addr->sat_addr.s_net == htons(ATADDR_ANYNET)) {
+ struct at_addr *ap = atalk_find_primary();
+
+ if (!ap)
+ return -EADDRNOTAVAIL;
+
+ sk->protinfo.af_at.src_net = addr->sat_addr.s_net = ap->s_net;
+ sk->protinfo.af_at.src_node = addr->sat_addr.s_node= ap->s_node;
+ } else {
+ if (!atalk_find_interface(addr->sat_addr.s_net,
+ addr->sat_addr.s_node))
+ return -EADDRNOTAVAIL;
+
+ sk->protinfo.af_at.src_net = addr->sat_addr.s_net;
+ sk->protinfo.af_at.src_node = addr->sat_addr.s_node;
+ }
+
+ if (addr->sat_port == ATADDR_ANYPORT) {
+ int n = atalk_pick_and_bind_port(sk, addr);
+
+ if (n < 0)
+ return n;
+ } else {
+ sk->protinfo.af_at.src_port = addr->sat_port;
+
+ if (atalk_find_or_insert_socket(sk, addr))
+ return -EADDRINUSE;
+ }
+
+ sk->zapped = 0;
+ return 0;
+}
+
+/* Set the address we talk to */
+static int atalk_connect(struct socket *sock, struct sockaddr *uaddr,
+ int addr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_at *addr;
+
+ sk->state = TCP_CLOSE;
+ sock->state = SS_UNCONNECTED;
+
+ if (addr_len != sizeof(*addr))
+ return -EINVAL;
+
+ addr = (struct sockaddr_at *)uaddr;
+
+ if (addr->sat_family != AF_APPLETALK)
+ return -EAFNOSUPPORT;
+
+ if (addr->sat_addr.s_node == ATADDR_BCAST && !sk->broadcast) {
+#if 1
+ printk(KERN_WARNING "%s is broken and did not set "
+ "SO_BROADCAST. It will break when 2.2 is "
+ "released.\n",
+ current->comm);
+#else
+ return -EACCES;
+#endif
+ }
+
+ if (sk->zapped)
+ if (atalk_autobind(sk) < 0)
+ return -EBUSY;
+
+ if (!atrtr_get_dev(&addr->sat_addr))
+ return -ENETUNREACH;
+
+ sk->protinfo.af_at.dest_port = addr->sat_port;
+ sk->protinfo.af_at.dest_net = addr->sat_addr.s_net;
+ sk->protinfo.af_at.dest_node = addr->sat_addr.s_node;
+
+ sock->state = SS_CONNECTED;
+ sk->state = TCP_ESTABLISHED;
+ return 0;
+}
+
+
+/*
+ * Find the name of an AppleTalk socket. Just copy the right
+ * fields into the sockaddr.
+ */
+static int atalk_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *uaddr_len, int peer)
+{
+ struct sockaddr_at sat;
+ struct sock *sk = sock->sk;
+
+ if (sk->zapped)
+ if (atalk_autobind(sk) < 0)
+ return -ENOBUFS;
+
+ *uaddr_len = sizeof(struct sockaddr_at);
+
+ if (peer) {
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+
+ sat.sat_addr.s_net = sk->protinfo.af_at.dest_net;
+ sat.sat_addr.s_node = sk->protinfo.af_at.dest_node;
+ sat.sat_port = sk->protinfo.af_at.dest_port;
+ } else {
+ sat.sat_addr.s_net = sk->protinfo.af_at.src_net;
+ sat.sat_addr.s_node = sk->protinfo.af_at.src_node;
+ sat.sat_port = sk->protinfo.af_at.src_port;
+ }
+
+ sat.sat_family = AF_APPLETALK;
+ memcpy(uaddr, &sat, sizeof(sat));
+ return 0;
+}
+
+/*
+ * Receive a packet (in skb) from device dev. This has come from the SNAP
+ * decoder, and on entry skb->h.raw is the DDP header, skb->len is the DDP
+ * header, skb->len is the DDP length. The physical headers have been
+ * extracted. PPP should probably pass frames marked as for this layer.
+ * [ie ARPHRD_ETHERTALK]
+ */
+static int atalk_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt)
+{
+ struct ddpehdr *ddp = (void *) skb->h.raw;
+ struct sock *sock;
+ struct atalk_iface *atif;
+ struct sockaddr_at tosat;
+ int origlen;
+ struct ddpebits ddphv;
+
+ /* Size check */
+ if (skb->len < sizeof(*ddp))
+ goto freeit;
+
+ /*
+ * Fix up the length field [Ok this is horrible but otherwise
+ * I end up with unions of bit fields and messy bit field order
+ * compiler/endian dependencies..]
+ *
+ * FIXME: This is a write to a shared object. Granted it
+ * happens to be safe BUT.. (Its safe as user space will not
+ * run until we put it back)
+ */
+ *((__u16 *)&ddphv) = ntohs(*((__u16 *)ddp));
+
+ /* Trim buffer in case of stray trailing data */
+ origlen = skb->len;
+ skb_trim(skb, min_t(unsigned int, skb->len, ddphv.deh_len));
+
+ /*
+ * Size check to see if ddp->deh_len was crap
+ * (Otherwise we'll detonate most spectacularly
+ * in the middle of recvmsg()).
+ */
+ if (skb->len < sizeof(*ddp))
+ goto freeit;
+
+ /*
+ * Any checksums. Note we don't do htons() on this == is assumed to be
+ * valid for net byte orders all over the networking code...
+ */
+ if (ddp->deh_sum &&
+ atalk_checksum(ddp, ddphv.deh_len) != ddp->deh_sum)
+ /* Not a valid AppleTalk frame - dustbin time */
+ goto freeit;
+
+ /* Check the packet is aimed at us */
+ if (!ddp->deh_dnet) /* Net 0 is 'this network' */
+ atif = atalk_find_anynet(ddp->deh_dnode, dev);
+ else
+ atif = atalk_find_interface(ddp->deh_dnet, ddp->deh_dnode);
+
+ /* Not ours, so we route the packet via the correct AppleTalk iface */
+ if (!atif) {
+ struct atalk_route *rt;
+ struct at_addr ta;
+
+ /*
+ * Don't route multicast, etc., packets, or packets
+ * sent to "this network"
+ */
+ if (skb->pkt_type != PACKET_HOST || !ddp->deh_dnet) {
+ /* FIXME:
+ * Can it ever happen that a packet is from a PPP
+ * iface and needs to be broadcast onto the default
+ * network? */
+ if (dev->type == ARPHRD_PPP)
+ printk(KERN_DEBUG "AppleTalk: didn't forward "
+ "broadcast packet received "
+ "from PPP iface\n");
+ goto freeit;
+ }
+
+ ta.s_net = ddp->deh_dnet;
+ ta.s_node = ddp->deh_dnode;
+
+ /* Route the packet */
+ rt = atrtr_find(&ta);
+ if (!rt || ddphv.deh_hops == DDP_MAXHOPS)
+ goto freeit;
+ ddphv.deh_hops++;
+
+ /*
+ * Route goes through another gateway, so
+ * set the target to the gateway instead.
+ */
+ if (rt->flags & RTF_GATEWAY) {
+ ta.s_net = rt->gateway.s_net;
+ ta.s_node = rt->gateway.s_node;
+ }
+
+ /* Fix up skb->len field */
+ skb_trim(skb, min_t(unsigned int, origlen, rt->dev->hard_header_len +
+ ddp_dl->header_length + ddphv.deh_len));
+
+ /* Mend the byte order */
+ *((__u16 *)ddp) = ntohs(*((__u16 *)&ddphv));
+
+ /*
+ * Send the buffer onwards
+ *
+ * Now we must always be careful. If it's come from
+ * LocalTalk to EtherTalk it might not fit
+ *
+ * Order matters here: If a packet has to be copied
+ * to make a new headroom (rare hopefully) then it
+ * won't need unsharing.
+ *
+ * Note. ddp-> becomes invalid at the realloc.
+ */
+ if (skb_headroom(skb) < 22) {
+ /* 22 bytes - 12 ether, 2 len, 3 802.2 5 snap */
+ struct sk_buff *nskb = skb_realloc_headroom(skb, 32);
+ kfree_skb(skb);
+ if (!nskb)
+ goto out;
+ skb = nskb;
+ } else
+ skb = skb_unshare(skb, GFP_ATOMIC);
+
+ /*
+ * If the buffer didn't vanish into the lack of
+ * space bitbucket we can send it.
+ */
+ if (skb && aarp_send_ddp(rt->dev, skb, &ta, NULL) == -1)
+ goto freeit;
+ goto out;
+ }
+
+#if defined(CONFIG_IPDDP) || defined(CONFIG_IPDDP_MODULE)
+ /* Check if IP-over-DDP */
+ if (skb->data[12] == 22) {
+ struct net_device *dev = __dev_get_by_name("ipddp0");
+ struct net_device_stats *stats;
+
+ /* This needs to be able to handle ipddp"N" devices */
+ if (!dev)
+ return -ENODEV;
+
+ skb->protocol = htons(ETH_P_IP);
+ skb_pull(skb, 13);
+ skb->dev = dev;
+ skb->h.raw = skb->data;
+
+ stats = dev->priv;
+ stats->rx_packets++;
+ stats->rx_bytes += skb->len + 13;
+ netif_rx(skb); /* Send the SKB up to a higher place. */
+ goto out;
+ }
+#endif
+ /*
+ * Which socket - atalk_search_socket() looks for a *full match*
+ * of the <net,node,port> tuple.
+ */
+ tosat.sat_addr.s_net = ddp->deh_dnet;
+ tosat.sat_addr.s_node = ddp->deh_dnode;
+ tosat.sat_port = ddp->deh_dport;
+
+ sock = atalk_search_socket(&tosat, atif);
+ if (!sock) /* But not one of our sockets */
+ goto freeit;
+
+ /* Queue packet (standard) */
+ skb->sk = sock;
+
+ if (sock_queue_rcv_skb(sock, skb) < 0)
+ goto freeit;
+ goto out;
+freeit: kfree_skb(skb);
+out: return 0;
+}
+
+/*
+ * Receive a LocalTalk frame. We make some demands on the caller here.
+ * Caller must provide enough headroom on the packet to pull the short
+ * header and append a long one.
+ */
+static int ltalk_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *pt)
+{
+ struct ddpehdr *ddp;
+ struct at_addr *ap;
+
+ /* Expand any short form frames */
+ if (skb->mac.raw[2] == 1) {
+ /* Find our address */
+
+ ap = atalk_find_dev_addr(dev);
+ if (!ap || skb->len < sizeof(struct ddpshdr)) {
+ kfree_skb(skb);
+ return 0;
+ }
+
+ /*
+ * The push leaves us with a ddephdr not an shdr, and
+ * handily the port bytes in the right place preset.
+ */
+
+ skb_push(skb, sizeof(*ddp) - 4);
+ ddp = (struct ddpehdr *)skb->data;
+
+ /* Now fill in the long header */
+
+ /*
+ * These two first. The mac overlays the new source/dest
+ * network information so we MUST copy these before
+ * we write the network numbers !
+ */
+
+ ddp->deh_dnode = skb->mac.raw[0]; /* From physical header */
+ ddp->deh_snode = skb->mac.raw[1]; /* From physical header */
+
+ ddp->deh_dnet = ap->s_net; /* Network number */
+ ddp->deh_snet = ap->s_net;
+ ddp->deh_sum = 0; /* No checksum */
+ /*
+ * Not sure about this bit...
+ */
+ ddp->deh_len = skb->len;
+ ddp->deh_hops = DDP_MAXHOPS; /* Non routable, so force a drop
+ if we slip up later */
+ /* Mend the byte order */
+ *((__u16 *)ddp) = htons(*((__u16 *)ddp));
+ }
+ skb->h.raw = skb->data;
+
+ return atalk_rcv(skb, dev, pt);
+}
+
+static int atalk_sendmsg(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_at *usat = (struct sockaddr_at *)msg->msg_name;
+ int flags = msg->msg_flags;
+ int loopback = 0;
+ struct sockaddr_at local_satalk, gsat;
+ struct sk_buff *skb;
+ struct net_device *dev;
+ struct ddpehdr *ddp;
+ int size;
+ struct atalk_route *rt;
+ int err;
+
+ if (flags & ~MSG_DONTWAIT)
+ return -EINVAL;
+
+ if (len > DDP_MAXSZ)
+ return -EMSGSIZE;
+
+ if (usat) {
+ if (sk->zapped)
+ if (atalk_autobind(sk) < 0)
+ return -EBUSY;
+
+ if (msg->msg_namelen < sizeof(*usat) ||
+ usat->sat_family != AF_APPLETALK)
+ return -EINVAL;
+
+ /* netatalk doesn't implement this check */
+ if (usat->sat_addr.s_node == ATADDR_BCAST && !sk->broadcast) {
+ printk(KERN_INFO "SO_BROADCAST: Fix your netatalk as "
+ "it will break before 2.2\n");
+#if 0
+ return -EPERM;
+#endif
+ }
+ } else {
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+ usat = &local_satalk;
+ usat->sat_family = AF_APPLETALK;
+ usat->sat_port = sk->protinfo.af_at.dest_port;
+ usat->sat_addr.s_node = sk->protinfo.af_at.dest_node;
+ usat->sat_addr.s_net = sk->protinfo.af_at.dest_net;
+ }
+
+ /* Build a packet */
+ SOCK_DEBUG(sk, "SK %p: Got address.\n", sk);
+
+ /* For headers */
+ size = sizeof(struct ddpehdr) + len + ddp_dl->header_length;
+
+ if (usat->sat_addr.s_net || usat->sat_addr.s_node == ATADDR_ANYNODE) {
+ rt = atrtr_find(&usat->sat_addr);
+ if (!rt)
+ return -ENETUNREACH;
+
+ dev = rt->dev;
+ } else {
+ struct at_addr at_hint;
+
+ at_hint.s_node = 0;
+ at_hint.s_net = sk->protinfo.af_at.src_net;
+
+ rt = atrtr_find(&at_hint);
+ if (!rt)
+ return -ENETUNREACH;
+
+ dev = rt->dev;
+ }
+
+ SOCK_DEBUG(sk, "SK %p: Size needed %d, device %s\n",
+ sk, size, dev->name);
+
+ size += dev->hard_header_len;
+ skb = sock_alloc_send_skb(sk, size, (flags & MSG_DONTWAIT), &err);
+ if (!skb)
+ return err;
+
+ skb->sk = sk;
+ skb_reserve(skb, ddp_dl->header_length);
+ skb_reserve(skb, dev->hard_header_len);
+ skb->dev = dev;
+
+ SOCK_DEBUG(sk, "SK %p: Begin build.\n", sk);
+
+ ddp = (struct ddpehdr *)skb_put(skb, sizeof(struct ddpehdr));
+ ddp->deh_pad = 0;
+ ddp->deh_hops = 0;
+ ddp->deh_len = len + sizeof(*ddp);
+ /*
+ * Fix up the length field [Ok this is horrible but otherwise
+ * I end up with unions of bit fields and messy bit field order
+ * compiler/endian dependencies..
+ */
+ *((__u16 *)ddp) = ntohs(*((__u16 *)ddp));
+
+ ddp->deh_dnet = usat->sat_addr.s_net;
+ ddp->deh_snet = sk->protinfo.af_at.src_net;
+ ddp->deh_dnode = usat->sat_addr.s_node;
+ ddp->deh_snode = sk->protinfo.af_at.src_node;
+ ddp->deh_dport = usat->sat_port;
+ ddp->deh_sport = sk->protinfo.af_at.src_port;
+
+ SOCK_DEBUG(sk, "SK %p: Copy user data (%d bytes).\n", sk, len);
+
+ err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
+ if (err) {
+ kfree_skb(skb);
+ return -EFAULT;
+ }
+
+ if (sk->no_check == 1)
+ ddp->deh_sum = 0;
+ else
+ ddp->deh_sum = atalk_checksum(ddp, len + sizeof(*ddp));
+
+ /*
+ * Loopback broadcast packets to non gateway targets (ie routes
+ * to group we are in)
+ */
+ if (ddp->deh_dnode == ATADDR_BCAST &&
+ !(rt->flags & RTF_GATEWAY) && !(dev->flags & IFF_LOOPBACK)) {
+ struct sk_buff *skb2 = skb_copy(skb, GFP_KERNEL);
+
+ if (skb2) {
+ loopback = 1;
+ SOCK_DEBUG(sk, "SK %p: send out(copy).\n", sk);
+ if (aarp_send_ddp(dev, skb2,
+ &usat->sat_addr, NULL) == -1)
+ kfree_skb(skb2);
+ /* else queued/sent above in the aarp queue */
+ }
+ }
+
+ if (dev->flags & IFF_LOOPBACK || loopback) {
+ SOCK_DEBUG(sk, "SK %p: Loop back.\n", sk);
+ /* loop back */
+ skb_orphan(skb);
+ ddp_dl->datalink_header(ddp_dl, skb, dev->dev_addr);
+ skb->mac.raw = skb->data;
+ skb->h.raw = skb->data + ddp_dl->header_length +
+ dev->hard_header_len;
+ skb_pull(skb, dev->hard_header_len);
+ skb_pull(skb, ddp_dl->header_length);
+ atalk_rcv(skb, dev, NULL);
+ } else {
+ SOCK_DEBUG(sk, "SK %p: send out.\n", sk);
+ if (rt->flags & RTF_GATEWAY) {
+ gsat.sat_addr = rt->gateway;
+ usat = &gsat;
+ }
+
+ if (aarp_send_ddp(dev, skb, &usat->sat_addr, NULL) == -1)
+ kfree_skb(skb);
+ /* else queued/sent above in the aarp queue */
+ }
+ SOCK_DEBUG(sk, "SK %p: Done write (%d).\n", sk, len);
+
+ return len;
+}
+
+static int atalk_recvmsg(struct socket *sock, struct msghdr *msg, int size,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_at *sat = (struct sockaddr_at *)msg->msg_name;
+ struct ddpehdr *ddp = NULL;
+ int copied = 0;
+ int err = 0;
+ struct ddpebits ddphv;
+ struct sk_buff *skb;
+
+ skb = skb_recv_datagram(sk, flags & ~MSG_DONTWAIT,
+ flags & MSG_DONTWAIT, &err);
+ if (!skb)
+ return err;
+
+ ddp = (struct ddpehdr *)(skb->h.raw);
+ *((__u16 *)&ddphv) = ntohs(*((__u16 *)ddp));
+
+ if (sk->type == SOCK_RAW) {
+ copied = ddphv.deh_len;
+ if (copied > size) {
+ copied = size;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+
+ err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+ } else {
+ copied = ddphv.deh_len - sizeof(*ddp);
+ if (copied > size) {
+ copied = size;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+ err = skb_copy_datagram_iovec(skb, sizeof(*ddp),
+ msg->msg_iov, copied);
+ }
+
+ if (!err) {
+ if (sat) {
+ sat->sat_family = AF_APPLETALK;
+ sat->sat_port = ddp->deh_sport;
+ sat->sat_addr.s_node = ddp->deh_snode;
+ sat->sat_addr.s_net = ddp->deh_snet;
+ }
+ msg->msg_namelen = sizeof(*sat);
+ }
+
+ skb_free_datagram(sk, skb); /* Free the datagram. */
+ return err ? err : copied;
+}
+
+
+/*
+ * AppleTalk ioctl calls.
+ */
+static int atalk_ioctl(struct socket *sock,unsigned int cmd, unsigned long arg)
+{
+ long amount = 0;
+ struct sock *sk = sock->sk;
+
+ switch (cmd) {
+ /* Protocol layer */
+ case TIOCOUTQ:
+ amount = sk->sndbuf - atomic_read(&sk->wmem_alloc);
+ if (amount < 0)
+ amount = 0;
+ break;
+ case TIOCINQ:
+ {
+ /* These two are safe on a single CPU system as only
+ * user tasks fiddle here */
+ struct sk_buff *skb = skb_peek(&sk->receive_queue);
+
+ if (skb)
+ amount = skb->len-sizeof(struct ddpehdr);
+ break;
+ }
+ case SIOCGSTAMP:
+ if (!sk)
+ return -EINVAL;
+ if (!sk->stamp.tv_sec)
+ return -ENOENT;
+ return copy_to_user((void *)arg, &sk->stamp,
+ sizeof(struct timeval)) ? -EFAULT : 0;
+ /* Routing */
+ case SIOCADDRT:
+ case SIOCDELRT:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ return atrtr_ioctl(cmd, (void *)arg);
+ /* Interface */
+ case SIOCGIFADDR:
+ case SIOCSIFADDR:
+ case SIOCGIFBRDADDR:
+ case SIOCATALKDIFADDR:
+ case SIOCDIFADDR:
+ case SIOCSARP: /* proxy AARP */
+ case SIOCDARP: /* proxy AARP */
+ {
+ int ret;
+
+ rtnl_lock();
+ ret = atif_ioctl(cmd, (void *)arg);
+ rtnl_unlock();
+
+ return ret;
+ }
+ /* Physical layer ioctl calls */
+ case SIOCSIFLINK:
+ case SIOCGIFHWADDR:
+ case SIOCSIFHWADDR:
+ case SIOCGIFFLAGS:
+ case SIOCSIFFLAGS:
+ case SIOCGIFMTU:
+ case SIOCGIFCONF:
+ case SIOCADDMULTI:
+ case SIOCDELMULTI:
+ case SIOCGIFCOUNT:
+ case SIOCGIFINDEX:
+ case SIOCGIFNAME:
+ return dev_ioctl(cmd,(void *) arg);
+ case SIOCSIFMETRIC:
+ case SIOCSIFBRDADDR:
+ case SIOCGIFNETMASK:
+ case SIOCSIFNETMASK:
+ case SIOCGIFMEM:
+ case SIOCSIFMEM:
+ case SIOCGIFDSTADDR:
+ case SIOCSIFDSTADDR:
+ return -EINVAL;
+ default:
+ return -EINVAL;
+ }
+
+ return put_user(amount, (int *)arg);
+}
+
+static struct net_proto_family atalk_family_ops =
+{
+ PF_APPLETALK,
+ atalk_create
+};
+
+static struct proto_ops SOCKOPS_WRAPPED(atalk_dgram_ops)=
+{
+ family: PF_APPLETALK,
+
+ release: atalk_release,
+ bind: atalk_bind,
+ connect: atalk_connect,
+ socketpair: sock_no_socketpair,
+ accept: sock_no_accept,
+ getname: atalk_getname,
+ poll: datagram_poll,
+ ioctl: atalk_ioctl,
+ listen: sock_no_listen,
+ shutdown: sock_no_shutdown,
+ setsockopt: sock_no_setsockopt,
+ getsockopt: sock_no_getsockopt,
+ sendmsg: atalk_sendmsg,
+ recvmsg: atalk_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+#include <linux/smp_lock.h>
+SOCKOPS_WRAP(atalk_dgram, PF_APPLETALK);
+
+static struct notifier_block ddp_notifier=
+{
+ ddp_device_event,
+ NULL,
+ 0
+};
+
+struct packet_type ltalk_packet_type=
+{
+ 0,
+ NULL,
+ ltalk_rcv,
+ NULL,
+ NULL
+};
+
+struct packet_type ppptalk_packet_type=
+{
+ 0,
+ NULL,
+ atalk_rcv,
+ NULL,
+ NULL
+};
+
+static char ddp_snap_id[] = {0x08, 0x00, 0x07, 0x80, 0x9B};
+
+/* Export symbols for use by drivers when AppleTalk is a module */
+EXPORT_SYMBOL(aarp_send_ddp);
+EXPORT_SYMBOL(atrtr_get_dev);
+EXPORT_SYMBOL(atalk_find_dev_addr);
+
+/* Called by proto.c on kernel start up */
+static int __init atalk_init(void)
+{
+ (void) sock_register(&atalk_family_ops);
+ ddp_dl = register_snap_client(ddp_snap_id, atalk_rcv);
+ if (!ddp_dl)
+ printk(KERN_CRIT "Unable to register DDP with SNAP.\n");
+
+ ltalk_packet_type.type = htons(ETH_P_LOCALTALK);
+ dev_add_pack(&ltalk_packet_type);
+
+ ppptalk_packet_type.type = htons(ETH_P_PPPTALK);
+ dev_add_pack(&ppptalk_packet_type);
+
+ register_netdevice_notifier(&ddp_notifier);
+ aarp_proto_init();
+
+ proc_net_create("appletalk", 0, atalk_get_info);
+ proc_net_create("atalk_route", 0, atalk_rt_get_info);
+ proc_net_create("atalk_iface", 0, atalk_if_get_info);
+#ifdef CONFIG_PROC_FS
+ aarp_register_proc_fs();
+#endif /* CONFIG_PROC_FS */
+#ifdef CONFIG_SYSCTL
+ atalk_register_sysctl();
+#endif /* CONFIG_SYSCTL */
+ printk(KERN_INFO "NET4: AppleTalk 0.18a for Linux NET4.0\n");
+ return 0;
+}
+module_init(atalk_init);
+
+#ifdef MODULE
+/*
+ * Note on MOD_{INC,DEC}_USE_COUNT:
+ *
+ * Use counts are incremented/decremented when
+ * sockets are created/deleted.
+ *
+ * AppleTalk interfaces are not incremented until atalkd is run
+ * and are only decremented when they are downed.
+ *
+ * Ergo, before the AppleTalk module can be removed, all AppleTalk
+ * sockets be closed from user space.
+ */
+static void __exit atalk_exit(void)
+{
+#ifdef CONFIG_SYSCTL
+ atalk_unregister_sysctl();
+#endif /* CONFIG_SYSCTL */
+ proc_net_remove("appletalk");
+ proc_net_remove("atalk_route");
+ proc_net_remove("atalk_iface");
+#ifdef CONFIG_PROC_FS
+ aarp_unregister_proc_fs();
+#endif /* CONFIG_PROC_FS */
+ aarp_cleanup_module(); /* General aarp clean-up. */
+ unregister_netdevice_notifier(&ddp_notifier);
+ dev_remove_pack(&ltalk_packet_type);
+ dev_remove_pack(&ppptalk_packet_type);
+ unregister_snap_client(ddp_snap_id);
+ sock_unregister(PF_APPLETALK);
+}
+module_exit(atalk_exit);
+#endif /* MODULE */
+#endif /* CONFIG_ATALK || CONFIG_ATALK_MODULE */
diff --git a/uClinux-2.4.31-uc0/net/appletalk/sysctl_net_atalk.c b/uClinux-2.4.31-uc0/net/appletalk/sysctl_net_atalk.c
new file mode 100644
index 0000000..8d9fe23
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/appletalk/sysctl_net_atalk.c
@@ -0,0 +1,61 @@
+/* -*- linux-c -*-
+ * sysctl_net_atalk.c: sysctl interface to net AppleTalk subsystem.
+ *
+ * Begun April 1, 1996, Mike Shaver.
+ * Added /proc/sys/net/atalk directory entry (empty =) ). [MS]
+ * Dynamic registration, added aarp entries. (5/30/97 Chris Horn)
+ */
+
+#include <linux/config.h>
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+
+extern int sysctl_aarp_expiry_time;
+extern int sysctl_aarp_tick_time;
+extern int sysctl_aarp_retransmit_limit;
+extern int sysctl_aarp_resolve_time;
+
+#ifdef CONFIG_SYSCTL
+static ctl_table atalk_table[] = {
+ {NET_ATALK_AARP_EXPIRY_TIME, "aarp-expiry-time",
+ &sysctl_aarp_expiry_time, sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {NET_ATALK_AARP_TICK_TIME, "aarp-tick-time",
+ &sysctl_aarp_tick_time, sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {NET_ATALK_AARP_RETRANSMIT_LIMIT, "aarp-retransmit-limit",
+ &sysctl_aarp_retransmit_limit, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_ATALK_AARP_RESOLVE_TIME, "aarp-resolve-time",
+ &sysctl_aarp_resolve_time, sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {0}
+};
+
+static ctl_table atalk_dir_table[] = {
+ {NET_ATALK, "appletalk", NULL, 0, 0555, atalk_table},
+ {0}
+};
+
+static ctl_table atalk_root_table[] = {
+ {CTL_NET, "net", NULL, 0, 0555, atalk_dir_table},
+ {0}
+};
+
+static struct ctl_table_header *atalk_table_header;
+
+void atalk_register_sysctl(void)
+{
+ atalk_table_header = register_sysctl_table(atalk_root_table, 1);
+}
+
+void atalk_unregister_sysctl(void)
+{
+ unregister_sysctl_table(atalk_table_header);
+}
+
+#else
+void atalk_register_sysctl(void)
+{
+}
+
+void atalk_unregister_sysctl(void)
+{
+}
+#endif
diff --git a/uClinux-2.4.31-uc0/net/atm/Makefile b/uClinux-2.4.31-uc0/net/atm/Makefile
new file mode 100644
index 0000000..d0170a6
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/Makefile
@@ -0,0 +1,58 @@
+#
+# Makefile for the ATM Protocol Families.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := atm.o
+
+export-objs := common.o atm_misc.o raw.o resources.o ipcommon.o proc.o
+
+list-multi := mpoa.o
+mpoa-objs := mpc.o mpoa_caches.o mpoa_proc.o
+
+obj-y := addr.o pvc.o signaling.o svc.o common.o atm_misc.o raw.o resources.o
+ifeq ($(CONFIG_ATM),m)
+ obj-m += $(O_TARGET)
+endif
+
+ifeq ($(CONFIG_ATM_IPV6),y)
+obj-y += ipv6atm.o
+NEED_IPCOM = ipcommon.o
+endif
+
+ifneq ($(CONFIG_ATM_CLIP),n)
+ NEED_IPCOM = ipcommon.o
+endif
+obj-$(CONFIG_ATM_CLIP) += clip.o
+
+ifeq ($(CONFIG_ATM_BR2684),y)
+ NEED_IPCOM = ipcommon.o
+else
+ ifeq ($(CONFIG_ATM_BR2684),m)
+ NEED_IPCOM = ipcommon.o
+ endif
+endif
+obj-$(CONFIG_ATM_BR2684) += br2684.o
+
+ifeq ($(CONFIG_NET_SCH_ATM),y)
+ NEED_IPCOM = ipcommon.o
+endif
+
+obj-y += $(NEED_IPCOM)
+
+ifeq ($(CONFIG_PROC_FS),y)
+ obj-y += proc.o
+endif
+
+obj-$(CONFIG_ATM_LANE) += lec.o
+obj-$(CONFIG_ATM_MPOA) += mpoa.o
+obj-$(CONFIG_PPPOATM) += pppoatm.o
+
+include $(TOPDIR)/Rules.make
+
+mpoa.o: $(mpoa-objs)
+ $(LD) -r -o mpoa.o $(mpoa-objs)
diff --git a/uClinux-2.4.31-uc0/net/atm/addr.c b/uClinux-2.4.31-uc0/net/atm/addr.c
new file mode 100644
index 0000000..47cff5f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/addr.c
@@ -0,0 +1,141 @@
+/* net/atm/addr.c - Local ATM address registry */
+
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/sched.h>
+#include <asm/uaccess.h>
+
+#include "signaling.h"
+#include "addr.h"
+
+
+static int check_addr(struct sockaddr_atmsvc *addr)
+{
+ int i;
+
+ if (addr->sas_family != AF_ATMSVC) return -EAFNOSUPPORT;
+ if (!*addr->sas_addr.pub)
+ return *addr->sas_addr.prv ? 0 : -EINVAL;
+ for (i = 1; i < ATM_E164_LEN+1; i++) /* make sure it's \0-terminated */
+ if (!addr->sas_addr.pub[i]) return 0;
+ return -EINVAL;
+}
+
+
+static int identical(struct sockaddr_atmsvc *a,struct sockaddr_atmsvc *b)
+{
+ if (*a->sas_addr.prv)
+ if (memcmp(a->sas_addr.prv,b->sas_addr.prv,ATM_ESA_LEN))
+ return 0;
+ if (!*a->sas_addr.pub) return !*b->sas_addr.pub;
+ if (!*b->sas_addr.pub) return 0;
+ return !strcmp(a->sas_addr.pub,b->sas_addr.pub);
+}
+
+
+static void notify_sigd(struct atm_dev *dev)
+{
+ struct sockaddr_atmpvc pvc;
+
+ pvc.sap_addr.itf = dev->number;
+ sigd_enq(NULL,as_itf_notify,NULL,&pvc,NULL);
+}
+
+
+void atm_reset_addr(struct atm_dev *dev)
+{
+ unsigned long flags;
+ struct atm_dev_addr *this;
+
+ spin_lock_irqsave(&dev->lock, flags);
+ while (dev->local) {
+ this = dev->local;
+ dev->local = this->next;
+ kfree(this);
+ }
+ spin_unlock_irqrestore(&dev->lock, flags);
+ notify_sigd(dev);
+}
+
+
+int atm_add_addr(struct atm_dev *dev,struct sockaddr_atmsvc *addr)
+{
+ unsigned long flags;
+ struct atm_dev_addr **walk;
+ int error;
+
+ error = check_addr(addr);
+ if (error)
+ return error;
+ spin_lock_irqsave(&dev->lock, flags);
+ for (walk = &dev->local; *walk; walk = &(*walk)->next)
+ if (identical(&(*walk)->addr,addr)) {
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return -EEXIST;
+ }
+ *walk = kmalloc(sizeof(struct atm_dev_addr), GFP_ATOMIC);
+ if (!*walk) {
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return -ENOMEM;
+ }
+ (*walk)->addr = *addr;
+ (*walk)->next = NULL;
+ spin_unlock_irqrestore(&dev->lock, flags);
+ notify_sigd(dev);
+ return 0;
+}
+
+
+int atm_del_addr(struct atm_dev *dev,struct sockaddr_atmsvc *addr)
+{
+ unsigned long flags;
+ struct atm_dev_addr **walk,*this;
+ int error;
+
+ error = check_addr(addr);
+ if (error)
+ return error;
+ spin_lock_irqsave(&dev->lock, flags);
+ for (walk = &dev->local; *walk; walk = &(*walk)->next)
+ if (identical(&(*walk)->addr,addr)) break;
+ if (!*walk) {
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return -ENOENT;
+ }
+ this = *walk;
+ *walk = this->next;
+ kfree(this);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ notify_sigd(dev);
+ return 0;
+}
+
+
+int atm_get_addr(struct atm_dev *dev,struct sockaddr_atmsvc *u_buf,size_t size)
+{
+ unsigned long flags;
+ struct atm_dev_addr *walk;
+ int total = 0, error;
+ struct sockaddr_atmsvc *tmp_buf, *tmp_bufp;
+
+
+ spin_lock_irqsave(&dev->lock, flags);
+ for (walk = dev->local; walk; walk = walk->next)
+ total += sizeof(struct sockaddr_atmsvc);
+ tmp_buf = tmp_bufp = kmalloc(total, GFP_ATOMIC);
+ if (!tmp_buf) {
+ spin_unlock_irqrestore(&dev->lock, flags);
+ return -ENOMEM;
+ }
+ for (walk = dev->local; walk; walk = walk->next)
+ memcpy(tmp_bufp++, &walk->addr, sizeof(struct sockaddr_atmsvc));
+ spin_unlock_irqrestore(&dev->lock, flags);
+ error = total > size ? -E2BIG : total;
+ if (copy_to_user(u_buf, tmp_buf, total < size ? total : size))
+ error = -EFAULT;
+ kfree(tmp_buf);
+ return error;
+}
diff --git a/uClinux-2.4.31-uc0/net/atm/addr.h b/uClinux-2.4.31-uc0/net/atm/addr.h
new file mode 100644
index 0000000..9e6634d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/addr.h
@@ -0,0 +1,18 @@
+/* net/atm/addr.h - Local ATM address registry */
+
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#ifndef NET_ATM_ADDR_H
+#define NET_ATM_ADDR_H
+
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+
+
+void atm_reset_addr(struct atm_dev *dev);
+int atm_add_addr(struct atm_dev *dev,struct sockaddr_atmsvc *addr);
+int atm_del_addr(struct atm_dev *dev,struct sockaddr_atmsvc *addr);
+int atm_get_addr(struct atm_dev *dev,struct sockaddr_atmsvc *u_buf,size_t size);
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/atm/atm_misc.c b/uClinux-2.4.31-uc0/net/atm/atm_misc.c
new file mode 100644
index 0000000..5b880f4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/atm_misc.c
@@ -0,0 +1,173 @@
+/* net/atm/atm_misc.c - Various functions for use by ATM drivers */
+
+/* Written 1995-2000 by Werner Almesberger, EPFL ICA */
+
+
+#include <linux/module.h>
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/skbuff.h>
+#include <linux/sonet.h>
+#include <linux/bitops.h>
+#include <asm/atomic.h>
+#include <asm/errno.h>
+
+
+int atm_charge(struct atm_vcc *vcc,int truesize)
+{
+ atm_force_charge(vcc,truesize);
+ if (atomic_read(&vcc->sk->rmem_alloc) <= vcc->sk->rcvbuf) return 1;
+ atm_return(vcc,truesize);
+ atomic_inc(&vcc->stats->rx_drop);
+ return 0;
+}
+
+
+struct sk_buff *atm_alloc_charge(struct atm_vcc *vcc,int pdu_size,
+ int gfp_flags)
+{
+ int guess = atm_guess_pdu2truesize(pdu_size);
+
+ atm_force_charge(vcc,guess);
+ if (atomic_read(&vcc->sk->rmem_alloc) <= vcc->sk->rcvbuf) {
+ struct sk_buff *skb = alloc_skb(pdu_size,gfp_flags);
+
+ if (skb) {
+ atomic_add(skb->truesize-guess,&vcc->sk->rmem_alloc);
+ return skb;
+ }
+ }
+ atm_return(vcc,guess);
+ atomic_inc(&vcc->stats->rx_drop);
+ return NULL;
+}
+
+
+static int check_ci(struct atm_vcc *vcc,short vpi,int vci)
+{
+ struct sock *s;
+ struct atm_vcc *walk;
+
+ for (s = vcc_sklist; s; s = s->next) {
+ walk = s->protinfo.af_atm;
+ if (walk->dev != vcc->dev)
+ continue;
+ if (test_bit(ATM_VF_ADDR,&walk->flags) && walk->vpi == vpi &&
+ walk->vci == vci && ((walk->qos.txtp.traffic_class !=
+ ATM_NONE && vcc->qos.txtp.traffic_class != ATM_NONE) ||
+ (walk->qos.rxtp.traffic_class != ATM_NONE &&
+ vcc->qos.rxtp.traffic_class != ATM_NONE)))
+ return -EADDRINUSE;
+ }
+ /* allow VCCs with same VPI/VCI iff they don't collide on
+ TX/RX (but we may refuse such sharing for other reasons,
+ e.g. if protocol requires to have both channels) */
+ return 0;
+}
+
+
+int atm_find_ci(struct atm_vcc *vcc,short *vpi,int *vci)
+{
+ static short p = 0; /* poor man's per-device cache */
+ static int c = 0;
+ short old_p;
+ int old_c;
+ int err;
+
+ read_lock(&vcc_sklist_lock);
+ if (*vpi != ATM_VPI_ANY && *vci != ATM_VCI_ANY) {
+ err = check_ci(vcc,*vpi,*vci);
+ read_unlock(&vcc_sklist_lock);
+ return err;
+ }
+ /* last scan may have left values out of bounds for current device */
+ if (*vpi != ATM_VPI_ANY) p = *vpi;
+ else if (p >= 1 << vcc->dev->ci_range.vpi_bits) p = 0;
+ if (*vci != ATM_VCI_ANY) c = *vci;
+ else if (c < ATM_NOT_RSV_VCI || c >= 1 << vcc->dev->ci_range.vci_bits)
+ c = ATM_NOT_RSV_VCI;
+ old_p = p;
+ old_c = c;
+ do {
+ if (!check_ci(vcc,p,c)) {
+ *vpi = p;
+ *vci = c;
+ read_unlock(&vcc_sklist_lock);
+ return 0;
+ }
+ if (*vci == ATM_VCI_ANY) {
+ c++;
+ if (c >= 1 << vcc->dev->ci_range.vci_bits)
+ c = ATM_NOT_RSV_VCI;
+ }
+ if ((c == ATM_NOT_RSV_VCI || *vci != ATM_VCI_ANY) &&
+ *vpi == ATM_VPI_ANY) {
+ p++;
+ if (p >= 1 << vcc->dev->ci_range.vpi_bits) p = 0;
+ }
+ }
+ while (old_p != p || old_c != c);
+ read_unlock(&vcc_sklist_lock);
+ return -EADDRINUSE;
+}
+
+
+/*
+ * atm_pcr_goal returns the positive PCR if it should be rounded up, the
+ * negative PCR if it should be rounded down, and zero if the maximum available
+ * bandwidth should be used.
+ *
+ * The rules are as follows (* = maximum, - = absent (0), x = value "x",
+ * (x+ = x or next value above x, x- = x or next value below):
+ *
+ * min max pcr result min max pcr result
+ * - - - * (UBR only) x - - x+
+ * - - * * x - * *
+ * - - z z- x - z z-
+ * - * - * x * - x+
+ * - * * * x * * *
+ * - * z z- x * z z-
+ * - y - y- x y - x+
+ * - y * y- x y * y-
+ * - y z z- x y z z-
+ *
+ * All non-error cases can be converted with the following simple set of rules:
+ *
+ * if pcr == z then z-
+ * else if min == x && pcr == - then x+
+ * else if max == y then y-
+ * else *
+ */
+
+
+int atm_pcr_goal(struct atm_trafprm *tp)
+{
+ if (tp->pcr && tp->pcr != ATM_MAX_PCR) return -tp->pcr;
+ if (tp->min_pcr && !tp->pcr) return tp->min_pcr;
+ if (tp->max_pcr != ATM_MAX_PCR) return -tp->max_pcr;
+ return 0;
+}
+
+
+void sonet_copy_stats(struct k_sonet_stats *from,struct sonet_stats *to)
+{
+#define __HANDLE_ITEM(i) to->i = atomic_read(&from->i)
+ __SONET_ITEMS
+#undef __HANDLE_ITEM
+}
+
+
+void sonet_subtract_stats(struct k_sonet_stats *from,struct sonet_stats *to)
+{
+#define __HANDLE_ITEM(i) atomic_sub(to->i,&from->i)
+ __SONET_ITEMS
+#undef __HANDLE_ITEM
+}
+
+
+EXPORT_SYMBOL(atm_charge);
+EXPORT_SYMBOL(atm_alloc_charge);
+EXPORT_SYMBOL(atm_find_ci);
+EXPORT_SYMBOL(atm_pcr_goal);
+EXPORT_SYMBOL(sonet_copy_stats);
+EXPORT_SYMBOL(sonet_subtract_stats);
diff --git a/uClinux-2.4.31-uc0/net/atm/br2684.c b/uClinux-2.4.31-uc0/net/atm/br2684.c
new file mode 100644
index 0000000..d57e7a8
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/br2684.c
@@ -0,0 +1,813 @@
+/*
+Experimental ethernet netdevice using ATM AAL5 as underlying carrier
+(RFC1483 obsoleted by RFC2684) for Linux 2.4
+Author: Marcell GAL, 2000, XDSL Ltd, Hungary
+*/
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <asm/uaccess.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/etherdevice.h>
+#include <net/arp.h>
+#include <linux/rtnetlink.h>
+#include <linux/ip.h>
+#include <asm/uaccess.h>
+#include <net/arp.h>
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+
+#include <linux/atmbr2684.h>
+
+#include "common.h"
+#include "ipcommon.h"
+
+/*
+ * Define this to use a version of the code which interacts with the higher
+ * layers in a more intellegent way, by always reserving enough space for
+ * our header at the begining of the packet. However, there may still be
+ * some problems with programs like tcpdump. In 2.5 we'll sort out what
+ * we need to do to get this perfect. For now we just will copy the packet
+ * if we need space for the header
+ */
+/* #define FASTER_VERSION */
+
+#ifdef DEBUG
+#define DPRINTK(format, args...) printk(KERN_DEBUG "br2684: " format, ##args)
+#else
+#define DPRINTK(format, args...)
+#endif
+
+#ifdef SKB_DEBUG
+static void skb_debug(const struct sk_buff *skb)
+{
+#define NUM2PRINT 50
+ char buf[NUM2PRINT * 3 + 1]; /* 3 chars per byte */
+ int i = 0;
+ for (i = 0; i < skb->len && i < NUM2PRINT; i++) {
+ sprintf(buf + i * 3, "%2.2x ", 0xff & skb->data[i]);
+ }
+ printk(KERN_DEBUG "br2684: skb: %s\n", buf);
+}
+#else
+#define skb_debug(skb) do {} while (0)
+#endif
+
+static unsigned char llc_oui_pid_pad[] =
+ { 0xAA, 0xAA, 0x03, 0x00, 0x80, 0xC2, 0x00, 0x07, 0x00, 0x00 };
+#define PADLEN (2)
+
+enum br2684_encaps {
+ e_vc = BR2684_ENCAPS_VC,
+ e_llc = BR2684_ENCAPS_LLC,
+};
+
+struct br2684_vcc {
+ struct atm_vcc *atmvcc;
+ struct br2684_dev *brdev;
+ /* keep old push,pop functions for chaining */
+ void (*old_push)(struct atm_vcc *vcc,struct sk_buff *skb);
+ /* void (*old_pop)(struct atm_vcc *vcc,struct sk_buff *skb); */
+ enum br2684_encaps encaps;
+ struct list_head brvccs;
+#ifdef CONFIG_ATM_BR2684_IPFILTER
+ struct br2684_filter filter;
+#endif /* CONFIG_ATM_BR2684_IPFILTER */
+#ifndef FASTER_VERSION
+ unsigned copies_needed, copies_failed;
+#endif /* FASTER_VERSION */
+};
+
+struct br2684_dev {
+ struct net_device net_dev;
+ struct list_head br2684_devs;
+ int number;
+ struct list_head brvccs; /* one device <=> one vcc (before xmas) */
+ struct net_device_stats stats;
+ int mac_was_set;
+};
+
+/*
+ * This lock should be held for writing any time the list of devices or
+ * their attached vcc's could be altered. It should be held for reading
+ * any time these are being queried. Note that we sometimes need to
+ * do read-locking under interrupt context, so write locking must block
+ * the current CPU's interrupts
+ */
+static rwlock_t devs_lock = RW_LOCK_UNLOCKED;
+
+static LIST_HEAD(br2684_devs);
+
+static inline struct br2684_dev *BRPRIV(const struct net_device *net_dev)
+{
+ return (struct br2684_dev *) ((char *) (net_dev) -
+ (unsigned long) (&((struct br2684_dev *) 0)->net_dev));
+}
+
+static inline struct br2684_dev *list_entry_brdev(const struct list_head *le)
+{
+ return list_entry(le, struct br2684_dev, br2684_devs);
+}
+
+static inline struct br2684_vcc *BR2684_VCC(const struct atm_vcc *atmvcc)
+{
+ return (struct br2684_vcc *) (atmvcc->user_back);
+}
+
+static inline struct br2684_vcc *list_entry_brvcc(const struct list_head *le)
+{
+ return list_entry(le, struct br2684_vcc, brvccs);
+}
+
+/* Caller should hold read_lock(&devs_lock) */
+static struct br2684_dev *br2684_find_dev(const struct br2684_if_spec *s)
+{
+ struct list_head *lh;
+ struct br2684_dev *brdev;
+ switch (s->method) {
+ case BR2684_FIND_BYNUM:
+ list_for_each(lh, &br2684_devs) {
+ brdev = list_entry_brdev(lh);
+ if (brdev->number == s->spec.devnum)
+ return brdev;
+ }
+ break;
+ case BR2684_FIND_BYIFNAME:
+ list_for_each(lh, &br2684_devs) {
+ brdev = list_entry_brdev(lh);
+ if (!strncmp(brdev->net_dev.name, s->spec.ifname,
+ sizeof brdev->net_dev.name))
+ return brdev;
+ }
+ break;
+ }
+ return NULL;
+}
+
+/*
+ * Send a packet out a particular vcc. Not to useful right now, but paves
+ * the way for multiple vcc's per itf. Returns true if we can send,
+ * otherwise false
+ */
+static int br2684_xmit_vcc(struct sk_buff *skb, struct br2684_dev *brdev,
+ struct br2684_vcc *brvcc)
+{
+ struct atm_vcc *atmvcc;
+#ifdef FASTER_VERSION
+ if (brvcc->encaps == e_llc)
+ memcpy(skb_push(skb, 8), llc_oui_pid_pad, 8);
+ /* last 2 bytes of llc_oui_pid_pad are managed by header routines;
+ yes, you got it: 8 + 2 = sizeof(llc_oui_pid_pad)
+ */
+#else
+ int minheadroom = (brvcc->encaps == e_llc) ? 10 : 2;
+ if (skb_headroom(skb) < minheadroom) {
+ struct sk_buff *skb2 = skb_realloc_headroom(skb, minheadroom);
+ brvcc->copies_needed++;
+ dev_kfree_skb(skb);
+ if (skb2 == NULL) {
+ brvcc->copies_failed++;
+ return 0;
+ }
+ skb = skb2;
+ }
+ skb_push(skb, minheadroom);
+ if (brvcc->encaps == e_llc)
+ memcpy(skb->data, llc_oui_pid_pad, 10);
+ else
+ memset(skb->data, 0, 2);
+#endif /* FASTER_VERSION */
+ skb_debug(skb);
+
+ ATM_SKB(skb)->vcc = atmvcc = brvcc->atmvcc;
+ DPRINTK("atm_skb(%p)->vcc(%p)->dev(%p)\n", skb, atmvcc, atmvcc->dev);
+ if (!atm_may_send(atmvcc, skb->truesize)) {
+ /* we free this here for now, because we cannot know in a higher
+ layer whether the skb point it supplied wasn't freed yet.
+ now, it always is.
+ */
+ dev_kfree_skb(skb);
+ return 0;
+ }
+ atomic_add(skb->truesize, &atmvcc->sk->wmem_alloc);
+ ATM_SKB(skb)->atm_options = atmvcc->atm_options;
+ brdev->stats.tx_packets++;
+ brdev->stats.tx_bytes += skb->len;
+ atmvcc->send(atmvcc, skb);
+ return 1;
+}
+
+static inline struct br2684_vcc *pick_outgoing_vcc(struct sk_buff *skb,
+ struct br2684_dev *brdev)
+{
+ return list_empty(&brdev->brvccs) ? NULL :
+ list_entry_brvcc(brdev->brvccs.next); /* 1 vcc/dev right now */
+}
+
+static int br2684_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct br2684_dev *brdev = BRPRIV(dev);
+ struct br2684_vcc *brvcc;
+
+ DPRINTK("br2684_start_xmit, skb->dst=%p\n", skb->dst);
+ read_lock(&devs_lock);
+ brvcc = pick_outgoing_vcc(skb, brdev);
+ if (brvcc == NULL) {
+ DPRINTK("no vcc attached to dev %s\n", dev->name);
+ brdev->stats.tx_errors++;
+ brdev->stats.tx_carrier_errors++;
+ /* netif_stop_queue(dev); */
+ dev_kfree_skb(skb);
+ read_unlock(&devs_lock);
+ return -EUNATCH;
+ }
+ if (!br2684_xmit_vcc(skb, brdev, brvcc)) {
+ /*
+ * We should probably use netif_*_queue() here, but that
+ * involves added complication. We need to walk before
+ * we can run
+ */
+ /* don't free here! this pointer might be no longer valid!
+ dev_kfree_skb(skb);
+ */
+ brdev->stats.tx_errors++;
+ brdev->stats.tx_fifo_errors++;
+ }
+ read_unlock(&devs_lock);
+ return 0;
+}
+
+static struct net_device_stats *br2684_get_stats(struct net_device *dev)
+{
+ DPRINTK("br2684_get_stats\n");
+ return &BRPRIV(dev)->stats;
+}
+
+#ifdef FASTER_VERSION
+/*
+ * These mirror eth_header and eth_header_cache. They are not usually
+ * exported for use in modules, so we grab them from net_device
+ * after ether_setup() is done with it. Bit of a hack.
+ */
+static int (*my_eth_header)(struct sk_buff *, struct net_device *,
+ unsigned short, void *, void *, unsigned);
+static int (*my_eth_header_cache)(struct neighbour *, struct hh_cache *);
+
+static int
+br2684_header(struct sk_buff *skb, struct net_device *dev,
+ unsigned short type, void *daddr, void *saddr, unsigned len)
+{
+ u16 *pad_before_eth;
+ int t = my_eth_header(skb, dev, type, daddr, saddr, len);
+ if (t > 0) {
+ pad_before_eth = (u16 *) skb_push(skb, 2);
+ *pad_before_eth = 0;
+ return dev->hard_header_len; /* or return 16; ? */
+ } else
+ return t;
+}
+
+static int
+br2684_header_cache(struct neighbour *neigh, struct hh_cache *hh)
+{
+/* hh_data is 16 bytes long. if encaps is ether-llc we need 24, so
+xmit will add the additional header part in that case */
+ u16 *pad_before_eth = (u16 *)(hh->hh_data);
+ int t = my_eth_header_cache(neigh, hh);
+ DPRINTK("br2684_header_cache, neigh=%p, hh_cache=%p\n", neigh, hh);
+ if (t < 0)
+ return t;
+ else {
+ *pad_before_eth = 0;
+ hh->hh_len = PADLEN + ETH_HLEN;
+ }
+ return 0;
+}
+
+/*
+ * This is similar to eth_type_trans, which cannot be used because of
+ * our dev->hard_header_len
+ */
+static inline unsigned short br_type_trans(struct sk_buff *skb,
+ struct net_device *dev)
+{
+ struct ethhdr *eth;
+ unsigned char *rawp;
+ eth = skb->mac.ethernet;
+
+ if (*eth->h_dest & 1) {
+ if (memcmp(eth->h_dest, dev->broadcast, ETH_ALEN) == 0)
+ skb->pkt_type = PACKET_BROADCAST;
+ else
+ skb->pkt_type = PACKET_MULTICAST;
+ }
+
+ else if (memcmp(eth->h_dest, dev->dev_addr, ETH_ALEN))
+ skb->pkt_type = PACKET_OTHERHOST;
+
+ if (ntohs(eth->h_proto) >= 1536)
+ return eth->h_proto;
+
+ rawp = skb->data;
+
+ /*
+ * This is a magic hack to spot IPX packets. Older Novell breaks
+ * the protocol design and runs IPX over 802.3 without an 802.2 LLC
+ * layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This
+ * won't work for fault tolerant netware but does for the rest.
+ */
+ if (*(unsigned short *) rawp == 0xFFFF)
+ return htons(ETH_P_802_3);
+
+ /*
+ * Real 802.2 LLC
+ */
+ return htons(ETH_P_802_2);
+}
+#endif /* FASTER_VERSION */
+
+/*
+ * We remember when the MAC gets set, so we don't override it later with
+ * the ESI of the ATM card of the first VC
+ */
+static int (*my_eth_mac_addr)(struct net_device *, void *);
+static int br2684_mac_addr(struct net_device *dev, void *p)
+{
+ int err = my_eth_mac_addr(dev, p);
+ if (!err)
+ BRPRIV(dev)->mac_was_set = 1;
+ return err;
+}
+
+#ifdef CONFIG_ATM_BR2684_IPFILTER
+/* this IOCTL is experimental. */
+static int br2684_setfilt(struct atm_vcc *atmvcc, unsigned long arg)
+{
+ struct br2684_vcc *brvcc;
+ struct br2684_filter_set fs;
+
+ if (copy_from_user(&fs, (void *) arg, sizeof fs))
+ return -EFAULT;
+ if (fs.ifspec.method != BR2684_FIND_BYNOTHING) {
+ /*
+ * This is really a per-vcc thing, but we can also search
+ * by device
+ */
+ struct br2684_dev *brdev;
+ read_lock(&devs_lock);
+ brdev = br2684_find_dev(&fs.ifspec);
+ if (brdev == NULL || list_empty(&brdev->brvccs) ||
+ brdev->brvccs.next != brdev->brvccs.prev) /* >1 VCC */
+ brvcc = NULL;
+ else
+ brvcc = list_entry_brvcc(brdev->brvccs.next);
+ read_unlock(&devs_lock);
+ if (brvcc == NULL)
+ return -ESRCH;
+ } else
+ brvcc = BR2684_VCC(atmvcc);
+ memcpy(&brvcc->filter, &fs.filter, sizeof(brvcc->filter));
+ return 0;
+}
+
+/* Returns 1 if packet should be dropped */
+static inline int
+packet_fails_filter(u16 type, struct br2684_vcc *brvcc, struct sk_buff *skb)
+{
+ if (brvcc->filter.netmask == 0)
+ return 0; /* no filter in place */
+ if (type == __constant_htons(ETH_P_IP) &&
+ (((struct iphdr *) (skb->data))->daddr & brvcc->filter.
+ netmask) == brvcc->filter.prefix)
+ return 0;
+ if (type == __constant_htons(ETH_P_ARP))
+ return 0;
+ /* TODO: we should probably filter ARPs too.. don't want to have
+ * them returning values that don't make sense, or is that ok?
+ */
+ return 1; /* drop */
+}
+#endif /* CONFIG_ATM_BR2684_IPFILTER */
+
+static void br2684_close_vcc(struct br2684_vcc *brvcc)
+{
+ DPRINTK("removing VCC %p from dev %p\n", brvcc, brvcc->brdev);
+ write_lock_irq(&devs_lock);
+ list_del(&brvcc->brvccs);
+ write_unlock_irq(&devs_lock);
+ brvcc->atmvcc->user_back = NULL; /* what about vcc->recvq ??? */
+ brvcc->old_push(brvcc->atmvcc, NULL); /* pass on the bad news */
+ kfree(brvcc);
+ MOD_DEC_USE_COUNT;
+}
+
+/* when AAL5 PDU comes in: */
+static void br2684_push(struct atm_vcc *atmvcc, struct sk_buff *skb)
+{
+ struct br2684_vcc *brvcc = BR2684_VCC(atmvcc);
+ struct br2684_dev *brdev = brvcc->brdev;
+ int plen = sizeof(llc_oui_pid_pad) + ETH_HLEN;
+
+ DPRINTK("br2684_push\n");
+
+ if (skb == NULL) { /* skb==NULL means VCC is being destroyed */
+ br2684_close_vcc(brvcc);
+ return;
+ }
+
+ skb_debug(skb);
+ atm_return(atmvcc, skb->truesize);
+ DPRINTK("skb from brdev %p\n", brdev);
+ if (brvcc->encaps == e_llc) {
+ /* let us waste some time for checking the encapsulation.
+ Note, that only 7 char is checked so frames with a valid FCS
+ are also accepted (but FCS is not checked of course) */
+ if (memcmp(skb->data, llc_oui_pid_pad, 7)) {
+ brdev->stats.rx_errors++;
+ dev_kfree_skb(skb);
+ return;
+ }
+
+ /* Strip FCS if present */
+ if (skb->len > 7 && skb->data[7] == 0x01)
+ __skb_trim(skb, skb->len - 4);
+ } else {
+ plen = PADLEN + ETH_HLEN; /* pad, dstmac,srcmac, ethtype */
+ /* first 2 chars should be 0 */
+ if (*((u16 *) (skb->data)) != 0) {
+ brdev->stats.rx_errors++;
+ dev_kfree_skb(skb);
+ return;
+ }
+ }
+ if (skb->len < plen) {
+ brdev->stats.rx_errors++;
+ dev_kfree_skb(skb); /* dev_ not needed? */
+ return;
+ }
+
+#ifdef FASTER_VERSION
+ /* FIXME: tcpdump shows that pointer to mac header is 2 bytes earlier,
+ than should be. What else should I set? */
+ skb_pull(skb, plen);
+ skb->mac.raw = ((char *) (skb->data)) - ETH_HLEN;
+ skb->pkt_type = PACKET_HOST;
+#ifdef CONFIG_BR2684_FAST_TRANS
+ skb->protocol = ((u16 *) skb->data)[-1];
+#else /* some protocols might require this: */
+ skb->protocol = br_type_trans(skb, &brdev->net_dev);
+#endif /* CONFIG_BR2684_FAST_TRANS */
+#else
+ skb_pull(skb, plen - ETH_HLEN);
+ skb->protocol = eth_type_trans(skb, &brdev->net_dev);
+#endif /* FASTER_VERSION */
+#ifdef CONFIG_ATM_BR2684_IPFILTER
+ if (packet_fails_filter(skb->protocol, brvcc, skb)) {
+ brdev->stats.rx_dropped++;
+ dev_kfree_skb(skb);
+ return;
+ }
+#endif /* CONFIG_ATM_BR2684_IPFILTER */
+ skb->dev = &brdev->net_dev;
+ ATM_SKB(skb)->vcc = atmvcc; /* needed ? */
+ DPRINTK("received packet's protocol: %x\n", ntohs(skb->protocol));
+ skb_debug(skb);
+ if (!(brdev->net_dev.flags & IFF_UP)) { /* sigh, interface is down */
+ brdev->stats.rx_dropped++;
+ dev_kfree_skb(skb);
+ return;
+ }
+ brdev->stats.rx_packets++;
+ brdev->stats.rx_bytes += skb->len;
+ memset(ATM_SKB(skb), 0, sizeof(struct atm_skb_data));
+ netif_rx(skb);
+}
+
+static int br2684_regvcc(struct atm_vcc *atmvcc, unsigned long arg)
+{
+/* assign a vcc to a dev
+Note: we do not have explicit unassign, but look at _push()
+*/
+ int err;
+ struct br2684_vcc *brvcc;
+ struct sk_buff_head copy;
+ struct sk_buff *skb;
+ struct br2684_dev *brdev;
+ struct atm_backend_br2684 be;
+
+ MOD_INC_USE_COUNT;
+ if (copy_from_user(&be, (void *) arg, sizeof be)) {
+ MOD_DEC_USE_COUNT;
+ return -EFAULT;
+ }
+ write_lock_irq(&devs_lock);
+ brdev = br2684_find_dev(&be.ifspec);
+ if (brdev == NULL) {
+ printk(KERN_ERR
+ "br2684: tried to attach to non-existant device\n");
+ err = -ENXIO;
+ goto error;
+ }
+ if (atmvcc->push == NULL) {
+ err = -EBADFD;
+ goto error;
+ }
+ if (!list_empty(&brdev->brvccs)) { /* Only 1 VCC/dev right now */
+ err = -EEXIST;
+ goto error;
+ }
+ if (be.fcs_in != BR2684_FCSIN_NO || be.fcs_out != BR2684_FCSOUT_NO ||
+ be.fcs_auto || be.has_vpiid || be.send_padding || (be.encaps !=
+ BR2684_ENCAPS_VC && be.encaps != BR2684_ENCAPS_LLC) ||
+ be.min_size != 0) {
+ err = -EINVAL;
+ goto error;
+ }
+ brvcc = kmalloc(sizeof(struct br2684_vcc), GFP_KERNEL);
+ if (!brvcc) {
+ err = -ENOMEM;
+ goto error;
+ }
+ memset(brvcc, 0, sizeof(struct br2684_vcc));
+ DPRINTK("br2684_regvcc vcc=%p, encaps=%d, brvcc=%p\n", atmvcc, be.encaps,
+ brvcc);
+ if (list_empty(&brdev->brvccs) && !brdev->mac_was_set) {
+ unsigned char *esi = atmvcc->dev->esi;
+ if (esi[0] | esi[1] | esi[2] | esi[3] | esi[4] | esi[5])
+ memcpy(brdev->net_dev.dev_addr, esi,
+ brdev->net_dev.addr_len);
+ else
+ brdev->net_dev.dev_addr[2] = 1;
+ }
+ list_add(&brvcc->brvccs, &brdev->brvccs);
+ write_unlock_irq(&devs_lock);
+ brvcc->brdev = brdev;
+ brvcc->atmvcc = atmvcc;
+ atmvcc->user_back = brvcc;
+ brvcc->encaps = (enum br2684_encaps) be.encaps;
+ brvcc->old_push = atmvcc->push;
+ barrier();
+ atmvcc->push = br2684_push;
+ skb_queue_head_init(&copy);
+ skb_migrate(&atmvcc->sk->receive_queue, &copy);
+ while ((skb = skb_dequeue(&copy))) {
+ BRPRIV(skb->dev)->stats.rx_bytes -= skb->len;
+ BRPRIV(skb->dev)->stats.rx_packets--;
+ br2684_push(atmvcc, skb);
+ }
+ return 0;
+ error:
+ write_unlock_irq(&devs_lock);
+ MOD_DEC_USE_COUNT;
+ return err;
+}
+
+static int br2684_create(unsigned long arg)
+{
+ int err;
+ struct br2684_dev *brdev;
+ struct atm_newif_br2684 ni;
+
+ DPRINTK("br2684_create\n");
+ /*
+ * We track module use by vcc's NOT the devices they're on. We're
+ * protected here against module death by the kernel_lock, but if
+ * we need to sleep we should make sure that the module doesn't
+ * disappear under us.
+ */
+ MOD_INC_USE_COUNT;
+ if (copy_from_user(&ni, (void *) arg, sizeof ni)) {
+ MOD_DEC_USE_COUNT;
+ return -EFAULT;
+ }
+ if (ni.media != BR2684_MEDIA_ETHERNET || ni.mtu != 1500) {
+ MOD_DEC_USE_COUNT;
+ return -EINVAL;
+ }
+ if ((brdev = kmalloc(sizeof(struct br2684_dev), GFP_KERNEL)) == NULL) {
+ MOD_DEC_USE_COUNT;
+ return -ENOMEM;
+ }
+ memset(brdev, 0, sizeof(struct br2684_dev));
+ INIT_LIST_HEAD(&brdev->brvccs);
+
+ write_lock_irq(&devs_lock);
+ brdev->number = list_empty(&br2684_devs) ? 1 :
+ list_entry_brdev(br2684_devs.prev)->number + 1;
+ list_add_tail(&brdev->br2684_devs, &br2684_devs);
+ write_unlock_irq(&devs_lock);
+
+ if (ni.ifname[0] != '\0') {
+ memcpy(brdev->net_dev.name, ni.ifname,
+ sizeof(brdev->net_dev.name));
+ brdev->net_dev.name[sizeof(brdev->net_dev.name) - 1] = '\0';
+ } else
+ sprintf(brdev->net_dev.name, "nas%d", brdev->number);
+ DPRINTK("registered netdev %s\n", brdev->net_dev.name);
+ ether_setup(&brdev->net_dev);
+ brdev->mac_was_set = 0;
+#ifdef FASTER_VERSION
+ my_eth_header = brdev->net_dev.hard_header;
+ brdev->net_dev.hard_header = br2684_header;
+ my_eth_header_cache = brdev->net_dev.hard_header_cache;
+ brdev->net_dev.hard_header_cache = br2684_header_cache;
+ brdev->net_dev.hard_header_len = sizeof(llc_oui_pid_pad) + ETH_HLEN; /* 10 + 14 */
+#endif
+ my_eth_mac_addr = brdev->net_dev.set_mac_address;
+ brdev->net_dev.set_mac_address = br2684_mac_addr;
+ brdev->net_dev.hard_start_xmit = br2684_start_xmit;
+ brdev->net_dev.get_stats = br2684_get_stats;
+
+ /* open, stop, do_ioctl ? */
+ err = register_netdev(&brdev->net_dev);
+ MOD_DEC_USE_COUNT;
+ if (err < 0) {
+ printk(KERN_ERR "br2684_create: register_netdev failed\n");
+ write_lock_irq(&devs_lock);
+ list_del(&brdev->br2684_devs);
+ write_unlock_irq(&devs_lock);
+ kfree(brdev);
+ return err;
+ }
+ return 0;
+}
+
+/*
+ * This handles ioctls actually performed on our vcc - we must return
+ * -ENOIOCTLCMD for any unrecognized ioctl
+ */
+static int br2684_ioctl(struct atm_vcc *atmvcc, unsigned int cmd,
+ unsigned long arg)
+{
+ int err;
+ switch(cmd) {
+ case ATM_SETBACKEND:
+ case ATM_NEWBACKENDIF: {
+ atm_backend_t b;
+ MOD_INC_USE_COUNT;
+ err = get_user(b, (atm_backend_t *) arg);
+ MOD_DEC_USE_COUNT;
+ if (err)
+ return -EFAULT;
+ if (b != ATM_BACKEND_BR2684)
+ return -ENOIOCTLCMD;
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (cmd == ATM_SETBACKEND)
+ return br2684_regvcc(atmvcc, arg);
+ else
+ return br2684_create(arg);
+ }
+#ifdef CONFIG_ATM_BR2684_IPFILTER
+ case BR2684_SETFILT:
+ if (atmvcc->push != br2684_push)
+ return -ENOIOCTLCMD;
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ MOD_INC_USE_COUNT;
+ err = br2684_setfilt(atmvcc, arg);
+ MOD_DEC_USE_COUNT;
+ return err;
+#endif /* CONFIG_ATM_BR2684_IPFILTER */
+ }
+ return -ENOIOCTLCMD;
+}
+
+#ifdef CONFIG_PROC_FS
+/* Never put more than 256 bytes in at once */
+static int br2684_proc_engine(loff_t pos, char *buf)
+{
+ struct list_head *lhd, *lhc;
+ struct br2684_dev *brdev;
+ struct br2684_vcc *brvcc;
+ list_for_each(lhd, &br2684_devs) {
+ brdev = list_entry_brdev(lhd);
+ if (pos-- == 0)
+ return sprintf(buf, "dev %.16s: num=%d, mac=%02X:%02X:"
+ "%02X:%02X:%02X:%02X (%s)\n", brdev->net_dev.name,
+ brdev->number,
+ brdev->net_dev.dev_addr[0],
+ brdev->net_dev.dev_addr[1],
+ brdev->net_dev.dev_addr[2],
+ brdev->net_dev.dev_addr[3],
+ brdev->net_dev.dev_addr[4],
+ brdev->net_dev.dev_addr[5],
+ brdev->mac_was_set ? "set" : "auto");
+ list_for_each(lhc, &brdev->brvccs) {
+ brvcc = list_entry_brvcc(lhc);
+ if (pos-- == 0)
+ return sprintf(buf, " vcc %d.%d.%d: encaps=%s"
+#ifndef FASTER_VERSION
+ ", failed copies %u/%u"
+#endif /* FASTER_VERSION */
+ "\n", brvcc->atmvcc->dev->number,
+ brvcc->atmvcc->vpi, brvcc->atmvcc->vci,
+ (brvcc->encaps == e_llc) ? "LLC" : "VC"
+#ifndef FASTER_VERSION
+ , brvcc->copies_failed
+ , brvcc->copies_needed
+#endif /* FASTER_VERSION */
+ );
+#ifdef CONFIG_ATM_BR2684_IPFILTER
+#define b1(var, byte) ((u8 *) &brvcc->filter.var)[byte]
+#define bs(var) b1(var, 0), b1(var, 1), b1(var, 2), b1(var, 3)
+ if (brvcc->filter.netmask != 0 && pos-- == 0)
+ return sprintf(buf, " filter=%d.%d.%d.%d/"
+ "%d.%d.%d.%d\n", bs(prefix), bs(netmask));
+#undef bs
+#undef b1
+#endif /* CONFIG_ATM_BR2684_IPFILTER */
+ }
+ }
+ return 0;
+}
+
+static ssize_t br2684_proc_read(struct file *file, char *buf, size_t count,
+ loff_t *pos)
+{
+ unsigned long page;
+ int len = 0, x, left;
+ loff_t n = *pos;
+
+ page = get_free_page(GFP_KERNEL);
+ if (!page)
+ return -ENOMEM;
+ left = PAGE_SIZE - 256;
+ if (count < left)
+ left = count;
+ read_lock(&devs_lock);
+ for (;;) {
+ x = br2684_proc_engine(n, &((char *) page)[len]);
+ if (x == 0)
+ break;
+ if (x > left)
+ /*
+ * This should only happen if the user passed in
+ * a "count" too small for even one line
+ */
+ x = -EINVAL;
+ if (x < 0) {
+ len = x;
+ break;
+ }
+ len += x;
+ left -= x;
+ n++;
+ if (left < 256)
+ break;
+ }
+ read_unlock(&devs_lock);
+ *pos = n;
+ if (len > 0 && copy_to_user(buf, (char *) page, len))
+ len = -EFAULT;
+ free_page(page);
+ return len;
+}
+
+static struct file_operations br2684_proc_operations = {
+ read: br2684_proc_read,
+};
+
+extern struct proc_dir_entry *atm_proc_root; /* from proc.c */
+#endif /* CONFIG_PROC_FS */
+
+/* the following avoids some spurious warnings from the compiler */
+#define UNUSED __attribute__((unused))
+
+static int __init UNUSED br2684_init(void)
+{
+#ifdef CONFIG_PROC_FS
+ struct proc_dir_entry *p;
+ if ((p = create_proc_entry("br2684", 0, atm_proc_root)) == NULL)
+ return -ENOMEM;
+ p->proc_fops = &br2684_proc_operations;
+#endif /* CONFIG_PROC_FS */
+ br2684_ioctl_set(br2684_ioctl);
+ return 0;
+}
+
+static void __exit UNUSED br2684_exit(void)
+{
+ struct br2684_dev *brdev;
+ br2684_ioctl_set(NULL);
+#ifdef CONFIG_PROC_FS
+ remove_proc_entry("br2684", atm_proc_root);
+#endif /* CONFIG_PROC_FS */
+ while (!list_empty(&br2684_devs)) {
+ brdev = list_entry_brdev(br2684_devs.next);
+ unregister_netdev(&brdev->net_dev);
+ list_del(&brdev->br2684_devs);
+ kfree(brdev);
+ }
+}
+
+module_init(br2684_init);
+module_exit(br2684_exit);
+
+MODULE_AUTHOR("Marcell GAL");
+MODULE_DESCRIPTION("RFC2684 bridged protocols over ATM/AAL5");
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/atm/clip.c b/uClinux-2.4.31-uc0/net/atm/clip.c
new file mode 100644
index 0000000..6cf5670
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/clip.c
@@ -0,0 +1,798 @@
+/* net/atm/clip.c - RFC1577 Classical IP over ATM */
+
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA
+ *
+ * Changes:
+ * Harald Welte <laforge@gnumonks.org>:
+ * - backport DaveM's generalized neighbour cache from 2.6.9-rcX */
+
+
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/kernel.h> /* for UINT_MAX */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/wait.h>
+#include <linux/timer.h>
+#include <linux/if_arp.h> /* for some manifest constants */
+#include <linux/notifier.h>
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/atmclip.h>
+#include <linux/atmarp.h>
+#include <linux/ip.h> /* for net/route.h */
+#include <linux/in.h> /* for struct sockaddr_in */
+#include <linux/if.h> /* for IFF_UP */
+#include <linux/inetdevice.h>
+#include <linux/bitops.h>
+#include <linux/jhash.h>
+#include <net/route.h> /* for struct rtable and routing */
+#include <net/icmp.h> /* icmp_send */
+#include <asm/param.h> /* for HZ */
+#include <asm/byteorder.h> /* for htons etc. */
+#include <asm/system.h> /* save/restore_flags */
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+
+#include "common.h"
+#include "resources.h"
+#include "ipcommon.h"
+#include <net/atmclip.h>
+
+
+#if 0
+#define DPRINTK(format,args...) printk(format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+
+struct net_device *clip_devs = NULL;
+struct atm_vcc *atmarpd = NULL;
+static struct neigh_table clip_tbl;
+static struct timer_list idle_timer;
+static int start_timer = 1;
+
+
+static int to_atmarpd(enum atmarp_ctrl_type type,int itf,unsigned long ip)
+{
+ struct atmarp_ctrl *ctrl;
+ struct sk_buff *skb;
+
+ DPRINTK("to_atmarpd(%d)\n",type);
+ if (!atmarpd) return -EUNATCH;
+ skb = alloc_skb(sizeof(struct atmarp_ctrl),GFP_ATOMIC);
+ if (!skb) return -ENOMEM;
+ ctrl = (struct atmarp_ctrl *) skb_put(skb,sizeof(struct atmarp_ctrl));
+ ctrl->type = type;
+ ctrl->itf_num = itf;
+ ctrl->ip = ip;
+ atm_force_charge(atmarpd,skb->truesize);
+ skb_queue_tail(&atmarpd->sk->receive_queue,skb);
+ wake_up(&atmarpd->sleep);
+ return 0;
+}
+
+
+static void link_vcc(struct clip_vcc *clip_vcc,struct atmarp_entry *entry)
+{
+ DPRINTK("link_vcc %p to entry %p (neigh %p)\n",clip_vcc,entry,
+ entry->neigh);
+ clip_vcc->entry = entry;
+ clip_vcc->xoff = 0; /* @@@ may overrun buffer by one packet */
+ clip_vcc->next = entry->vccs;
+ entry->vccs = clip_vcc;
+ entry->neigh->used = jiffies;
+}
+
+
+static void unlink_clip_vcc(struct clip_vcc *clip_vcc)
+{
+ struct atmarp_entry *entry = clip_vcc->entry;
+ struct clip_vcc **walk;
+
+ if (!entry) {
+ printk(KERN_CRIT "!clip_vcc->entry (clip_vcc %p)\n",clip_vcc);
+ return;
+ }
+ spin_lock_bh(&entry->neigh->dev->xmit_lock); /* block clip_start_xmit() */
+ entry->neigh->used = jiffies;
+ for (walk = &entry->vccs; *walk; walk = &(*walk)->next)
+ if (*walk == clip_vcc) {
+ int error;
+
+ *walk = clip_vcc->next; /* atomic */
+ clip_vcc->entry = NULL;
+ if (clip_vcc->xoff)
+ netif_wake_queue(entry->neigh->dev);
+ if (entry->vccs)
+ goto out;
+ entry->expires = jiffies-1;
+ /* force resolution or expiration */
+ error = neigh_update(entry->neigh,NULL,NUD_NONE,0,0);
+ if (error)
+ printk(KERN_CRIT "unlink_clip_vcc: "
+ "neigh_update failed with %d\n",error);
+ goto out;
+ }
+ printk(KERN_CRIT "ATMARP: unlink_clip_vcc failed (entry %p, vcc "
+ "0x%p)\n",entry,clip_vcc);
+out:
+ spin_unlock_bh(&entry->neigh->dev->xmit_lock);
+}
+
+/* The neighbour entry n->lock is held. */
+static int neigh_check_cb(struct neighbour *n)
+{
+ struct atmarp_entry *entry = NEIGH2ENTRY(n);
+ struct clip_vcc *cv;
+
+ for (cv = entry->vccs; cv; cv = cv->next) {
+ unsigned long exp = cv->last_use + cv->idle_timeout;
+
+ if (cv->idle_timeout && time_after(jiffies, exp)) {
+ DPRINTK("releasing vcc %p->%p of entry %p\n",
+ cv, cv->vcc, entry);
+ vcc_release_async(cv->vcc, -ETIMEDOUT);
+ }
+ }
+
+ if (entry->vccs || time_before(jiffies, entry->expires))
+ return 0;
+
+ if (atomic_read(&n->refcnt) > 1) {
+ struct sk_buff *skb;
+
+ DPRINTK("destruction postponed with ref %d\n",
+ atomic_read(&n->refcnt));
+
+ while ((skb = skb_dequeue(&n->arp_queue)) != NULL)
+ dev_kfree_skb(skb);
+
+ return 0;
+ }
+
+ DPRINTK("expired neigh %p\n",n);
+ return 1;
+}
+
+static void idle_timer_check(unsigned long dummy)
+{
+ write_lock(&clip_tbl.lock);
+ __neigh_for_each_release(&clip_tbl, neigh_check_cb);
+ mod_timer(&idle_timer, jiffies+CLIP_CHECK_INTERVAL*HZ);
+ write_unlock(&clip_tbl.lock);
+}
+
+static int clip_arp_rcv(struct sk_buff *skb)
+{
+ struct atm_vcc *vcc;
+
+ DPRINTK("clip_arp_rcv\n");
+ vcc = ATM_SKB(skb)->vcc;
+ if (!vcc || !atm_charge(vcc,skb->truesize)) {
+ dev_kfree_skb_any(skb);
+ return 0;
+ }
+ DPRINTK("pushing to %p\n",vcc);
+ DPRINTK("using %p\n",CLIP_VCC(vcc)->old_push);
+ CLIP_VCC(vcc)->old_push(vcc,skb);
+ return 0;
+}
+
+
+static void clip_push(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+ struct clip_vcc *clip_vcc = CLIP_VCC(vcc);
+
+ DPRINTK("clip push\n");
+ if (!skb) {
+ DPRINTK("removing VCC %p\n",clip_vcc);
+ if (clip_vcc->entry) unlink_clip_vcc(clip_vcc);
+ clip_vcc->old_push(vcc,NULL); /* pass on the bad news */
+ kfree(clip_vcc);
+ return;
+ }
+ atm_return(vcc,skb->truesize);
+ skb->dev = clip_vcc->entry ? clip_vcc->entry->neigh->dev : clip_devs;
+ /* clip_vcc->entry == NULL if we don't have an IP address yet */
+ if (!skb->dev) {
+ dev_kfree_skb_any(skb);
+ return;
+ }
+ ATM_SKB(skb)->vcc = vcc;
+ skb->mac.raw = skb->data;
+ if (!clip_vcc->encap || skb->len < RFC1483LLC_LEN || memcmp(skb->data,
+ llc_oui,sizeof(llc_oui))) skb->protocol = htons(ETH_P_IP);
+ else {
+ skb->protocol = ((u16 *) skb->data)[3];
+ skb_pull(skb,RFC1483LLC_LEN);
+ if (skb->protocol == htons(ETH_P_ARP)) {
+ PRIV(skb->dev)->stats.rx_packets++;
+ PRIV(skb->dev)->stats.rx_bytes += skb->len;
+ clip_arp_rcv(skb);
+ return;
+ }
+ }
+ clip_vcc->last_use = jiffies;
+ PRIV(skb->dev)->stats.rx_packets++;
+ PRIV(skb->dev)->stats.rx_bytes += skb->len;
+ memset(ATM_SKB(skb), 0, sizeof(struct atm_skb_data));
+ netif_rx(skb);
+}
+
+
+/*
+ * Note: these spinlocks _must_not_ block on non-SMP. The only goal is that
+ * clip_pop is atomic with respect to the critical section in clip_start_xmit.
+ */
+
+
+static void clip_pop(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+ struct clip_vcc *clip_vcc = CLIP_VCC(vcc);
+ struct net_device *dev = skb->dev;
+ int old;
+ unsigned long flags;
+
+ DPRINTK("clip_pop(vcc %p)\n",vcc);
+ clip_vcc->old_pop(vcc,skb);
+ /* skb->dev == NULL in outbound ARP packets */
+ if (!dev) return;
+ spin_lock_irqsave(&PRIV(dev)->xoff_lock,flags);
+ if (atm_may_send(vcc,0)) {
+ old = xchg(&clip_vcc->xoff,0);
+ if (old) netif_wake_queue(dev);
+ }
+ spin_unlock_irqrestore(&PRIV(dev)->xoff_lock,flags);
+}
+
+
+static void clip_neigh_destroy(struct neighbour *neigh)
+{
+ DPRINTK("clip_neigh_destroy (neigh %p)\n",neigh);
+ if (NEIGH2ENTRY(neigh)->vccs)
+ printk(KERN_CRIT "clip_neigh_destroy: vccs != NULL !!!\n");
+ NEIGH2ENTRY(neigh)->vccs = (void *) 0xdeadbeef;
+}
+
+
+static void clip_neigh_solicit(struct neighbour *neigh,struct sk_buff *skb)
+{
+ DPRINTK("clip_neigh_solicit (neigh %p, skb %p)\n",neigh,skb);
+ to_atmarpd(act_need,PRIV(neigh->dev)->number,NEIGH2ENTRY(neigh)->ip);
+}
+
+
+static void clip_neigh_error(struct neighbour *neigh,struct sk_buff *skb)
+{
+#ifndef CONFIG_ATM_CLIP_NO_ICMP
+ icmp_send(skb,ICMP_DEST_UNREACH,ICMP_HOST_UNREACH,0);
+#endif
+ kfree_skb(skb);
+}
+
+
+static struct neigh_ops clip_neigh_ops = {
+ family: AF_INET,
+ destructor: clip_neigh_destroy,
+ solicit: clip_neigh_solicit,
+ error_report: clip_neigh_error,
+ output: dev_queue_xmit,
+ connected_output: dev_queue_xmit,
+ hh_output: dev_queue_xmit,
+ queue_xmit: dev_queue_xmit,
+};
+
+
+static int clip_constructor(struct neighbour *neigh)
+{
+ struct atmarp_entry *entry = NEIGH2ENTRY(neigh);
+ struct net_device *dev = neigh->dev;
+ struct in_device *in_dev = dev->ip_ptr;
+
+ DPRINTK("clip_constructor (neigh %p, entry %p)\n",neigh,entry);
+ if (!in_dev) return -EINVAL;
+ neigh->type = inet_addr_type(entry->ip);
+ if (neigh->type != RTN_UNICAST) return -EINVAL;
+ if (in_dev->arp_parms) neigh->parms = in_dev->arp_parms;
+ neigh->ops = &clip_neigh_ops;
+ neigh->output = neigh->nud_state & NUD_VALID ?
+ neigh->ops->connected_output : neigh->ops->output;
+ entry->neigh = neigh;
+ entry->vccs = NULL;
+ entry->expires = jiffies-1;
+ return 0;
+}
+
+static u32 clip_hash(const void *pkey, const struct net_device *dev)
+{
+ return jhash_2words(*(u32 *)pkey, dev->ifindex, clip_tbl.hash_rnd);
+}
+
+
+static struct neigh_table clip_tbl = {
+ NULL, /* next */
+ AF_INET, /* family */
+ sizeof(struct neighbour)+sizeof(struct atmarp_entry), /* entry_size */
+ 4, /* key_len */
+ clip_hash,
+ clip_constructor, /* constructor */
+ NULL, /* pconstructor */
+ NULL, /* pdestructor */
+ NULL, /* proxy_redo */
+ "clip_arp_cache",
+ { /* neigh_parms */
+ NULL, /* next */
+ NULL, /* neigh_setup */
+ &clip_tbl, /* tbl */
+ 0, /* entries */
+ NULL, /* priv */
+ NULL, /* sysctl_table */
+ 30*HZ, /* base_reachable_time */
+ 1*HZ, /* retrans_time */
+ 60*HZ, /* gc_staletime */
+ 30*HZ, /* reachable_time */
+ 5*HZ, /* delay_probe_time */
+ 3, /* queue_len */
+ 3, /* ucast_probes */
+ 0, /* app_probes */
+ 3, /* mcast_probes */
+ 1*HZ, /* anycast_delay */
+ (8*HZ)/10, /* proxy_delay */
+ 1*HZ, /* proxy_qlen */
+ 64 /* locktime */
+ },
+ 30*HZ,128,512,1024 /* copied from ARP ... */
+};
+
+
+/* @@@ copy bh locking from arp.c -- need to bh-enable atm code before */
+
+/*
+ * We play with the resolve flag: 0 and 1 have the usual meaning, but -1 means
+ * to allocate the neighbour entry but not to ask atmarpd for resolution. Also,
+ * don't increment the usage count. This is used to create entries in
+ * clip_setentry.
+ */
+
+
+static int clip_encap(struct atm_vcc *vcc,int mode)
+{
+ CLIP_VCC(vcc)->encap = mode;
+ return 0;
+}
+
+
+static int clip_start_xmit(struct sk_buff *skb,struct net_device *dev)
+{
+ struct clip_priv *clip_priv = PRIV(dev);
+ struct atmarp_entry *entry;
+ struct atm_vcc *vcc;
+ struct clip_vcc *vccs;
+ int old;
+ unsigned long flags;
+
+ DPRINTK("clip_start_xmit (skb %p)\n",skb);
+ if (!skb->dst) {
+ printk(KERN_ERR "clip_start_xmit: skb->dst == NULL\n");
+ dev_kfree_skb(skb);
+ clip_priv->stats.tx_dropped++;
+ return 0;
+ }
+ if (!skb->dst->neighbour) {
+#if 0
+ skb->dst->neighbour = clip_find_neighbour(skb->dst,1);
+ if (!skb->dst->neighbour) {
+ dev_kfree_skb(skb); /* lost that one */
+ clip_priv->stats.tx_dropped++;
+ return 0;
+ }
+#endif
+ printk(KERN_ERR "clip_start_xmit: NO NEIGHBOUR !\n");
+ dev_kfree_skb(skb);
+ clip_priv->stats.tx_dropped++;
+ return 0;
+ }
+ entry = NEIGH2ENTRY(skb->dst->neighbour);
+ vccs = entry->vccs;
+ if (!vccs) {
+ if (time_after(jiffies, entry->expires)) {
+ /* should be resolved */
+ entry->expires = jiffies+ATMARP_RETRY_DELAY*HZ;
+ to_atmarpd(act_need,PRIV(dev)->number,entry->ip);
+ }
+ if (entry->neigh->arp_queue.qlen < ATMARP_MAX_UNRES_PACKETS)
+ skb_queue_tail(&entry->neigh->arp_queue,skb);
+ else {
+ dev_kfree_skb(skb);
+ clip_priv->stats.tx_dropped++;
+ }
+ return 0;
+ }
+ DPRINTK("neigh %p, vccs %p\n",entry,vccs);
+ ATM_SKB(skb)->vcc = vcc = vccs->vcc;
+ DPRINTK("using neighbour %p, vcc %p\n",skb->dst->neighbour,vcc);
+ if (vccs->encap) {
+ void *here;
+
+ here = skb_push(skb,RFC1483LLC_LEN);
+ memcpy(here,llc_oui,sizeof(llc_oui));
+ ((u16 *) here)[3] = skb->protocol;
+ }
+ atomic_add(skb->truesize,&vcc->sk->wmem_alloc);
+ ATM_SKB(skb)->atm_options = vcc->atm_options;
+ vccs->last_use = jiffies;
+ DPRINTK("atm_skb(%p)->vcc(%p)->dev(%p)\n",skb,vcc,vcc->dev);
+ old = xchg(&vccs->xoff,1); /* assume XOFF ... */
+ if (old) {
+ printk(KERN_WARNING "clip_start_xmit: XOFF->XOFF transition\n");
+ return 0;
+ }
+ clip_priv->stats.tx_packets++;
+ clip_priv->stats.tx_bytes += skb->len;
+ (void) vcc->send(vcc,skb);
+ if (atm_may_send(vcc,0)) {
+ vccs->xoff = 0;
+ return 0;
+ }
+ spin_lock_irqsave(&clip_priv->xoff_lock,flags);
+ netif_stop_queue(dev); /* XOFF -> throttle immediately */
+ barrier();
+ if (!vccs->xoff)
+ netif_start_queue(dev);
+ /* Oh, we just raced with clip_pop. netif_start_queue should be
+ good enough, because nothing should really be asleep because
+ of the brief netif_stop_queue. If this isn't true or if it
+ changes, use netif_wake_queue instead. */
+ spin_unlock_irqrestore(&clip_priv->xoff_lock,flags);
+ return 0;
+}
+
+
+static struct net_device_stats *clip_get_stats(struct net_device *dev)
+{
+ return &PRIV(dev)->stats;
+}
+
+
+static int clip_mkip(struct atm_vcc *vcc,int timeout)
+{
+ struct clip_vcc *clip_vcc;
+ struct sk_buff_head copy;
+ struct sk_buff *skb;
+
+ if (!vcc->push) return -EBADFD;
+ clip_vcc = kmalloc(sizeof(struct clip_vcc),GFP_KERNEL);
+ if (!clip_vcc) return -ENOMEM;
+ DPRINTK("mkip clip_vcc %p vcc %p\n",clip_vcc,vcc);
+ clip_vcc->vcc = vcc;
+ vcc->user_back = clip_vcc;
+ clip_vcc->entry = NULL;
+ clip_vcc->xoff = 0;
+ clip_vcc->encap = 1;
+ clip_vcc->last_use = jiffies;
+ clip_vcc->idle_timeout = timeout*HZ;
+ clip_vcc->old_push = vcc->push;
+ clip_vcc->old_pop = vcc->pop;
+ vcc->push = clip_push;
+ vcc->pop = clip_pop;
+ skb_queue_head_init(&copy);
+ skb_migrate(&vcc->sk->receive_queue,&copy);
+ /* re-process everything received between connection setup and MKIP */
+ while ((skb = skb_dequeue(&copy)))
+ if (!clip_devs) {
+ atm_return(vcc,skb->truesize);
+ kfree_skb(skb);
+ }
+ else {
+ unsigned int len = skb->len;
+
+ clip_push(vcc,skb);
+ PRIV(skb->dev)->stats.rx_packets--;
+ PRIV(skb->dev)->stats.rx_bytes -= len;
+ }
+ return 0;
+}
+
+
+static int clip_setentry(struct atm_vcc *vcc,u32 ip)
+{
+ struct neighbour *neigh;
+ struct atmarp_entry *entry;
+ int error;
+ struct clip_vcc *clip_vcc;
+ struct rtable *rt;
+
+ if (vcc->push != clip_push) {
+ printk(KERN_WARNING "clip_setentry: non-CLIP VCC\n");
+ return -EBADF;
+ }
+ clip_vcc = CLIP_VCC(vcc);
+ if (!ip) {
+ if (!clip_vcc->entry) {
+ printk(KERN_ERR "hiding hidden ATMARP entry\n");
+ return 0;
+ }
+ DPRINTK("setentry: remove\n");
+ unlink_clip_vcc(clip_vcc);
+ return 0;
+ }
+ error = ip_route_output(&rt,ip,0,1,0);
+ if (error) return error;
+ neigh = __neigh_lookup(&clip_tbl,&ip,rt->u.dst.dev,1);
+ ip_rt_put(rt);
+ if (!neigh)
+ return -ENOMEM;
+ entry = NEIGH2ENTRY(neigh);
+ if (entry != clip_vcc->entry) {
+ if (!clip_vcc->entry) DPRINTK("setentry: add\n");
+ else {
+ DPRINTK("setentry: update\n");
+ unlink_clip_vcc(clip_vcc);
+ }
+ link_vcc(clip_vcc,entry);
+ }
+ error = neigh_update(neigh,llc_oui,NUD_PERMANENT,1,0);
+ neigh_release(neigh);
+ return error;
+}
+
+
+static int clip_init(struct net_device *dev)
+{
+ DPRINTK("clip_init %s\n",dev->name);
+ dev->hard_start_xmit = clip_start_xmit;
+ /* sg_xmit ... */
+ dev->hard_header = NULL;
+ dev->rebuild_header = NULL;
+ dev->set_mac_address = NULL;
+ dev->hard_header_parse = NULL;
+ dev->hard_header_cache = NULL;
+ dev->header_cache_update = NULL;
+ dev->change_mtu = NULL;
+ dev->do_ioctl = NULL;
+ dev->get_stats = clip_get_stats;
+ dev->type = ARPHRD_ATM;
+ dev->hard_header_len = RFC1483LLC_LEN;
+ dev->mtu = RFC1626_MTU;
+ dev->addr_len = 0;
+ dev->tx_queue_len = 100; /* "normal" queue (packets) */
+ /* When using a "real" qdisc, the qdisc determines the queue */
+ /* length. tx_queue_len is only used for the default case, */
+ /* without any more elaborate queuing. 100 is a reasonable */
+ /* compromise between decent burst-tolerance and protection */
+ /* against memory hogs. */
+ dev->flags = 0;
+ return 0;
+}
+
+
+static int clip_create(int number)
+{
+ struct net_device *dev;
+ struct clip_priv *clip_priv;
+ int error;
+
+ if (number != -1) {
+ for (dev = clip_devs; dev; dev = PRIV(dev)->next)
+ if (PRIV(dev)->number == number) return -EEXIST;
+ }
+ else {
+ number = 0;
+ for (dev = clip_devs; dev; dev = PRIV(dev)->next)
+ if (PRIV(dev)->number >= number)
+ number = PRIV(dev)->number+1;
+ }
+ dev = kmalloc(sizeof(struct net_device)+sizeof(struct clip_priv),
+ GFP_KERNEL);
+ if (!dev) return -ENOMEM;
+ memset(dev,0,sizeof(struct net_device)+sizeof(struct clip_priv));
+ clip_priv = PRIV(dev);
+ sprintf(dev->name,"atm%d",number);
+ dev->init = clip_init;
+ spin_lock_init(&clip_priv->xoff_lock);
+ clip_priv->number = number;
+ error = register_netdev(dev);
+ if (error) {
+ kfree(dev);
+ return error;
+ }
+ clip_priv->next = clip_devs;
+ clip_devs = dev;
+ DPRINTK("registered (net:%s)\n",dev->name);
+ return number;
+}
+
+
+static int clip_device_event(struct notifier_block *this,unsigned long event,
+ void *dev)
+{
+ /* ignore non-CLIP devices */
+ if (((struct net_device *) dev)->type != ARPHRD_ATM ||
+ ((struct net_device *) dev)->init != clip_init)
+ return NOTIFY_DONE;
+ switch (event) {
+ case NETDEV_UP:
+ DPRINTK("clip_device_event NETDEV_UP\n");
+ (void) to_atmarpd(act_up,PRIV(dev)->number,0);
+ break;
+ case NETDEV_GOING_DOWN:
+ DPRINTK("clip_device_event NETDEV_DOWN\n");
+ (void) to_atmarpd(act_down,PRIV(dev)->number,0);
+ break;
+ case NETDEV_CHANGE:
+ case NETDEV_CHANGEMTU:
+ DPRINTK("clip_device_event NETDEV_CHANGE*\n");
+ (void) to_atmarpd(act_change,PRIV(dev)->number,0);
+ break;
+ case NETDEV_REBOOT:
+ case NETDEV_REGISTER:
+ case NETDEV_DOWN:
+ DPRINTK("clip_device_event %ld\n",event);
+ /* ignore */
+ break;
+ default:
+ printk(KERN_WARNING "clip_device_event: unknown event "
+ "%ld\n",event);
+ break;
+ }
+ return NOTIFY_DONE;
+}
+
+
+static int clip_inet_event(struct notifier_block *this,unsigned long event,
+ void *ifa)
+{
+ struct in_device *in_dev;
+
+ in_dev = ((struct in_ifaddr *) ifa)->ifa_dev;
+ if (!in_dev || !in_dev->dev) {
+ printk(KERN_WARNING "clip_inet_event: no device\n");
+ return NOTIFY_DONE;
+ }
+ /*
+ * Transitions are of the down-change-up type, so it's sufficient to
+ * handle the change on up.
+ */
+ if (event != NETDEV_UP) return NOTIFY_DONE;
+ return clip_device_event(this,NETDEV_CHANGE,in_dev->dev);
+}
+
+
+static struct notifier_block clip_dev_notifier = {
+ clip_device_event,
+ NULL,
+ 0
+};
+
+
+
+static struct notifier_block clip_inet_notifier = {
+ clip_inet_event,
+ NULL,
+ 0
+};
+
+
+
+static void atmarpd_close(struct atm_vcc *vcc)
+{
+ DPRINTK("atmarpd_close\n");
+ atmarpd = NULL; /* assumed to be atomic */
+ barrier();
+ unregister_inetaddr_notifier(&clip_inet_notifier);
+ unregister_netdevice_notifier(&clip_dev_notifier);
+ if (skb_peek(&vcc->sk->receive_queue))
+ printk(KERN_ERR "atmarpd_close: closing with requests "
+ "pending\n");
+ skb_queue_purge(&vcc->sk->receive_queue);
+ DPRINTK("(done)\n");
+ MOD_DEC_USE_COUNT;
+}
+
+
+static struct atmdev_ops atmarpd_dev_ops = {
+ .close = atmarpd_close,
+};
+
+
+static struct atm_dev atmarpd_dev = {
+ .ops = &atmarpd_dev_ops,
+ .type = "arpd",
+ .number = 999,
+ .lock = SPIN_LOCK_UNLOCKED
+};
+
+
+static int atm_init_atmarp(struct atm_vcc *vcc)
+{
+ struct net_device *dev;
+
+ if (atmarpd) return -EADDRINUSE;
+ if (start_timer) {
+ start_timer = 0;
+ init_timer(&idle_timer);
+ idle_timer.expires = jiffies+CLIP_CHECK_INTERVAL*HZ;
+ idle_timer.function = idle_timer_check;
+ add_timer(&idle_timer);
+ }
+ atmarpd = vcc;
+ set_bit(ATM_VF_META,&vcc->flags);
+ set_bit(ATM_VF_READY,&vcc->flags);
+ /* allow replies and avoid getting closed if signaling dies */
+ vcc->dev = &atmarpd_dev;
+ vcc_insert_socket(vcc->sk);
+ vcc->push = NULL;
+ vcc->pop = NULL; /* crash */
+ vcc->push_oam = NULL; /* crash */
+ if (register_netdevice_notifier(&clip_dev_notifier))
+ printk(KERN_ERR "register_netdevice_notifier failed\n");
+ if (register_inetaddr_notifier(&clip_inet_notifier))
+ printk(KERN_ERR "register_inetaddr_notifier failed\n");
+ for (dev = clip_devs; dev; dev = PRIV(dev)->next)
+ if (dev->flags & IFF_UP)
+ (void) to_atmarpd(act_up,PRIV(dev)->number,0);
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static struct atm_clip_ops __atm_clip_ops = {
+ .clip_create = clip_create,
+ .clip_mkip = clip_mkip,
+ .clip_setentry = clip_setentry,
+ .clip_encap = clip_encap,
+ .clip_push = clip_push,
+ .atm_init_atmarp = atm_init_atmarp,
+ .owner = THIS_MODULE
+};
+
+static int __init atm_clip_init(void)
+{
+ neigh_table_init(&clip_tbl);
+
+ clip_tbl_hook = &clip_tbl;
+ atm_clip_ops_set(&__atm_clip_ops);
+
+ return 0;
+}
+
+static void __exit atm_clip_exit(void)
+{
+ struct net_device *dev, *next;
+
+ atm_clip_ops_set(NULL);
+
+ /* First, stop the idle timer, so it stops banging
+ * on the table.
+ */
+ if (start_timer == 0)
+ del_timer(&idle_timer);
+
+ /* Next, purge the table, so that the device
+ * unregister loop below does not hang due to
+ * device references remaining in the table.
+ */
+ neigh_ifdown(&clip_tbl, NULL);
+
+ dev = clip_devs;
+ while (dev) {
+ next = PRIV(dev)->next;
+ unregister_netdev(dev);
+ kfree(dev);
+ dev = next;
+ }
+
+ /* Now it is safe to fully shutdown whole table. */
+ neigh_table_clear(&clip_tbl);
+
+ clip_tbl_hook = NULL;
+}
+
+module_init(atm_clip_init);
+module_exit(atm_clip_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/atm/common.c b/uClinux-2.4.31-uc0/net/atm/common.c
new file mode 100644
index 0000000..ffc7264
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/common.c
@@ -0,0 +1,1161 @@
+/* $USAGI: common.c,v 1.9 2003/11/12 22:50:57 yoshfuji Exp $ */
+/* net/atm/common.c - ATM sockets (common part for PVC and SVC) */
+
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kmod.h>
+#include <linux/net.h> /* struct socket, struct net_proto, struct
+ proto_ops */
+#include <linux/atm.h> /* ATM stuff */
+#include <linux/atmdev.h>
+#include <linux/atmclip.h> /* CLIP_*ENCAP */
+#ifdef CONFIG_ATM_IPV6
+#include <linux/atmipv6.h> /* IPv6 over ATM */
+#include <net/atmipv6.h> /* IPv6 over ATM */
+#endif /* ifdef CONFIG_ATM_IPV6 */
+#include <linux/atmarp.h> /* manifest constants */
+#include <linux/sonet.h> /* for ioctls */
+#include <linux/socket.h> /* SOL_SOCKET */
+#include <linux/errno.h> /* error codes */
+#include <linux/capability.h>
+#include <linux/mm.h> /* verify_area */
+#include <linux/sched.h>
+#include <linux/time.h> /* struct timeval */
+#include <linux/skbuff.h>
+#include <linux/bitops.h>
+#include <linux/init.h>
+#include <net/sock.h> /* struct sock */
+
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+#include <asm/poll.h>
+#include <asm/ioctls.h>
+
+#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)
+#include <linux/atmlec.h>
+#include "lec.h"
+#include "lec_arpc.h"
+struct atm_lane_ops *atm_lane_ops;
+static DECLARE_MUTEX(atm_lane_ops_mutex);
+
+void atm_lane_ops_set(struct atm_lane_ops *hook)
+{
+ down(&atm_lane_ops_mutex);
+ atm_lane_ops = hook;
+ up(&atm_lane_ops_mutex);
+}
+
+int try_atm_lane_ops(void)
+{
+ down(&atm_lane_ops_mutex);
+ if (atm_lane_ops && try_inc_mod_count(atm_lane_ops->owner)) {
+ up(&atm_lane_ops_mutex);
+ return 1;
+ }
+ up(&atm_lane_ops_mutex);
+ return 0;
+}
+
+#if defined(CONFIG_ATM_LANE_MODULE) || defined(CONFIG_ATM_MPOA_MODULE)
+EXPORT_SYMBOL(atm_lane_ops);
+EXPORT_SYMBOL(try_atm_lane_ops);
+EXPORT_SYMBOL(atm_lane_ops_set);
+#endif
+#endif
+
+#if defined(CONFIG_ATM_MPOA) || defined(CONFIG_ATM_MPOA_MODULE)
+#include <linux/atmmpc.h>
+#include "mpc.h"
+struct atm_mpoa_ops *atm_mpoa_ops;
+static DECLARE_MUTEX(atm_mpoa_ops_mutex);
+
+void atm_mpoa_ops_set(struct atm_mpoa_ops *hook)
+{
+ down(&atm_mpoa_ops_mutex);
+ atm_mpoa_ops = hook;
+ up(&atm_mpoa_ops_mutex);
+}
+
+int try_atm_mpoa_ops(void)
+{
+ down(&atm_mpoa_ops_mutex);
+ if (atm_mpoa_ops && try_inc_mod_count(atm_mpoa_ops->owner)) {
+ up(&atm_mpoa_ops_mutex);
+ return 1;
+ }
+ up(&atm_mpoa_ops_mutex);
+ return 0;
+}
+#ifdef CONFIG_ATM_MPOA_MODULE
+EXPORT_SYMBOL(atm_mpoa_ops);
+EXPORT_SYMBOL(try_atm_mpoa_ops);
+EXPORT_SYMBOL(atm_mpoa_ops_set);
+#endif
+#endif
+
+#if defined(CONFIG_ATM_TCP) || defined(CONFIG_ATM_TCP_MODULE)
+#include <linux/atm_tcp.h>
+#ifdef CONFIG_ATM_TCP_MODULE
+struct atm_tcp_ops atm_tcp_ops;
+EXPORT_SYMBOL(atm_tcp_ops);
+#endif
+#endif
+
+#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
+#include <net/atmclip.h>
+struct atm_clip_ops *atm_clip_ops;
+static DECLARE_MUTEX(atm_clip_ops_mutex);
+
+void atm_clip_ops_set(struct atm_clip_ops *hook)
+{
+ down(&atm_clip_ops_mutex);
+ atm_clip_ops = hook;
+ up(&atm_clip_ops_mutex);
+}
+
+int try_atm_clip_ops(void)
+{
+ down(&atm_clip_ops_mutex);
+ if (atm_clip_ops && try_inc_mod_count(atm_clip_ops->owner)) {
+ up(&atm_clip_ops_mutex);
+ return 1;
+ }
+ up(&atm_clip_ops_mutex);
+ return 0;
+}
+
+#ifdef CONFIG_ATM_CLIP_MODULE
+EXPORT_SYMBOL(atm_clip_ops);
+EXPORT_SYMBOL(try_atm_clip_ops);
+EXPORT_SYMBOL(atm_clip_ops_set);
+#endif
+#endif
+
+#if defined(CONFIG_PPPOATM) || defined(CONFIG_PPPOATM_MODULE)
+static DECLARE_MUTEX(pppoatm_ioctl_mutex);
+
+static int (*pppoatm_ioctl_hook)(struct atm_vcc *, unsigned int, unsigned long);
+
+void pppoatm_ioctl_set(int (*hook)(struct atm_vcc *, unsigned int, unsigned long))
+{
+ down(&pppoatm_ioctl_mutex);
+ pppoatm_ioctl_hook = hook;
+ up(&pppoatm_ioctl_mutex);
+}
+#ifdef CONFIG_PPPOATM_MODULE
+EXPORT_SYMBOL(pppoatm_ioctl_set);
+#endif
+#endif
+
+#if defined(CONFIG_ATM_BR2684) || defined(CONFIG_ATM_BR2684_MODULE)
+static DECLARE_MUTEX(br2684_ioctl_mutex);
+
+static int (*br2684_ioctl_hook)(struct atm_vcc *, unsigned int, unsigned long);
+
+void br2684_ioctl_set(int (*hook)(struct atm_vcc *, unsigned int, unsigned long))
+{
+ down(&br2684_ioctl_mutex);
+ br2684_ioctl_hook = hook;
+ up(&br2684_ioctl_mutex);
+}
+#ifdef CONFIG_ATM_BR2684_MODULE
+EXPORT_SYMBOL(br2684_ioctl_set);
+#endif
+#endif
+
+#include "resources.h" /* atm_find_dev */
+#include "common.h" /* prototypes */
+#include "protocols.h" /* atm_init_<transport> */
+#include "addr.h" /* address registry */
+#ifdef CONFIG_ATM_CLIP
+#include <net/atmclip.h> /* for clip_create */
+#endif
+#include "signaling.h" /* for WAITING and sigd_attach */
+
+
+#if 0
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+
+struct sock *vcc_sklist;
+rwlock_t vcc_sklist_lock = RW_LOCK_UNLOCKED;
+
+void __vcc_insert_socket(struct sock *sk)
+{
+ sk->next = vcc_sklist;
+ if (sk->next)
+ vcc_sklist->pprev = &sk->next;
+ vcc_sklist = sk;
+ sk->pprev = &vcc_sklist;
+}
+
+void vcc_insert_socket(struct sock *sk)
+{
+ write_lock_irq(&vcc_sklist_lock);
+ __vcc_insert_socket(sk);
+ write_unlock_irq(&vcc_sklist_lock);
+}
+
+void vcc_remove_socket(struct sock *sk)
+{
+ write_lock_irq(&vcc_sklist_lock);
+ if (sk->pprev) {
+ if (sk->next)
+ sk->next->pprev = sk->pprev;
+ *sk->pprev = sk->next;
+ sk->pprev = NULL;
+ }
+ write_unlock_irq(&vcc_sklist_lock);
+}
+
+
+static struct sk_buff *alloc_tx(struct atm_vcc *vcc,unsigned int size)
+{
+ struct sk_buff *skb;
+
+ if (atomic_read(&vcc->sk->wmem_alloc) && !atm_may_send(vcc,size)) {
+ DPRINTK("Sorry: wmem_alloc = %d, size = %d, sndbuf = %d\n",
+ atomic_read(&vcc->sk->wmem_alloc),size,vcc->sk->sndbuf);
+ return NULL;
+ }
+ while (!(skb = alloc_skb(size,GFP_KERNEL))) schedule();
+ DPRINTK("AlTx %d += %d\n",atomic_read(&vcc->sk->wmem_alloc),skb->truesize);
+ atomic_add(skb->truesize, &vcc->sk->wmem_alloc);
+ return skb;
+}
+
+
+EXPORT_SYMBOL(vcc_sklist);
+EXPORT_SYMBOL(vcc_sklist_lock);
+EXPORT_SYMBOL(vcc_insert_socket);
+EXPORT_SYMBOL(vcc_remove_socket);
+
+static void vcc_sock_destruct(struct sock *sk)
+{
+ struct atm_vcc *vcc = sk->protinfo.af_atm;
+
+ if (atomic_read(&vcc->sk->rmem_alloc))
+ printk(KERN_DEBUG "vcc_sock_destruct: rmem leakage (%d bytes) detected.\n", atomic_read(&sk->rmem_alloc));
+
+ if (atomic_read(&vcc->sk->wmem_alloc))
+ printk(KERN_DEBUG "vcc_sock_destruct: wmem leakage (%d bytes) detected.\n", atomic_read(&sk->wmem_alloc));
+
+ kfree(sk->protinfo.af_atm);
+
+ MOD_DEC_USE_COUNT;
+}
+
+int vcc_create(struct socket *sock, int protocol, int family)
+{
+ struct sock *sk;
+ struct atm_vcc *vcc;
+
+ sock->sk = NULL;
+ if (sock->type == SOCK_STREAM)
+ return -EINVAL;
+ sk = sk_alloc(family, GFP_KERNEL, 1);
+ if (!sk)
+ return -ENOMEM;
+ sock_init_data(NULL, sk);
+
+ vcc = sk->protinfo.af_atm = kmalloc(sizeof(*vcc), GFP_KERNEL);
+ if (!vcc) {
+ sk_free(sk);
+ return -ENOMEM;
+ }
+
+ memset(vcc, 0, sizeof(*vcc));
+ vcc->sk = sk;
+
+ vcc->dev = NULL;
+ vcc->callback = NULL;
+ memset(&vcc->local,0,sizeof(struct sockaddr_atmsvc));
+ memset(&vcc->remote,0,sizeof(struct sockaddr_atmsvc));
+ vcc->qos.txtp.max_sdu = 1 << 16; /* for meta VCs */
+ atomic_set(&vcc->sk->wmem_alloc,0);
+ atomic_set(&vcc->sk->rmem_alloc,0);
+ vcc->push = NULL;
+ vcc->pop = NULL;
+ vcc->push_oam = NULL;
+ vcc->vpi = vcc->vci = 0; /* no VCI/VPI yet */
+ vcc->atm_options = vcc->aal_options = 0;
+ init_waitqueue_head(&vcc->sleep);
+ sk->sleep = &vcc->sleep;
+ sk->destruct = vcc_sock_destruct;
+ sock->sk = sk;
+
+ MOD_INC_USE_COUNT;
+
+ return 0;
+}
+
+
+static void vcc_destroy_socket(struct sock *sk)
+{
+ struct atm_vcc *vcc;
+ struct sk_buff *skb;
+
+ vcc = sk->protinfo.af_atm;
+ DPRINTK("atm_release_vcc_sk(vcc:0x%08x)\n", (unsigned int)vcc);
+ clear_bit(ATM_VF_READY, &vcc->flags);
+ DPRINTK("atm_release_vcc_sk(dev:0x%08x)\n", (unsigned int)vcc->dev);
+ if (vcc->dev) {
+ if (vcc->dev->ops->close)
+ vcc->dev->ops->close(vcc);
+ if (vcc->push)
+ vcc->push(vcc, NULL); /* atmarpd has no push */
+
+ vcc_remove_socket(sk); /* no more receive */
+
+ while ((skb = skb_dequeue(&vcc->sk->receive_queue))) {
+ atm_return(vcc,skb->truesize);
+ kfree_skb(skb);
+ }
+
+ if (vcc->dev->ops->owner)
+ __MOD_DEC_USE_COUNT(vcc->dev->ops->owner);
+ atm_dev_put(vcc->dev);
+ }
+}
+
+
+int vcc_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk) {
+ lock_sock(sk);
+ vcc_destroy_socket(sock->sk);
+ release_sock(sk);
+ sock_put(sk);
+ }
+
+ return 0;
+}
+
+
+void vcc_release_async(struct atm_vcc *vcc, int reply)
+{
+ set_bit(ATM_VF_CLOSE, &vcc->flags);
+ vcc->reply = reply;
+ vcc->sk->err = -reply;
+ wake_up(&vcc->sleep);
+}
+
+
+EXPORT_SYMBOL(vcc_release_async);
+
+
+static int adjust_tp(struct atm_trafprm *tp,unsigned char aal)
+{
+ int max_sdu;
+
+ if (!tp->traffic_class) return 0;
+ switch (aal) {
+ case ATM_AAL0:
+ max_sdu = ATM_CELL_SIZE-1;
+ break;
+ case ATM_AAL34:
+ max_sdu = ATM_MAX_AAL34_PDU;
+ break;
+ default:
+ printk(KERN_WARNING "ATM: AAL problems ... "
+ "(%d)\n",aal);
+ /* fall through */
+ case ATM_AAL5:
+ max_sdu = ATM_MAX_AAL5_PDU;
+ }
+ if (!tp->max_sdu) tp->max_sdu = max_sdu;
+ else if (tp->max_sdu > max_sdu) return -EINVAL;
+ if (!tp->max_cdv) tp->max_cdv = ATM_MAX_CDV;
+ return 0;
+}
+
+
+static int __vcc_connect(struct atm_vcc *vcc, struct atm_dev *dev, int vpi,
+ int vci)
+{
+ int error;
+
+ if ((vpi != ATM_VPI_UNSPEC && vpi != ATM_VPI_ANY &&
+ vpi >> dev->ci_range.vpi_bits) || (vci != ATM_VCI_UNSPEC &&
+ vci != ATM_VCI_ANY && vci >> dev->ci_range.vci_bits))
+ return -EINVAL;
+ if (vci > 0 && vci < ATM_NOT_RSV_VCI && !capable(CAP_NET_BIND_SERVICE))
+ return -EPERM;
+ error = 0;
+ if (!try_inc_mod_count(dev->ops->owner))
+ return -ENODEV;
+ vcc->dev = dev;
+ vcc_insert_socket(vcc->sk);
+ switch (vcc->qos.aal) {
+ case ATM_AAL0:
+ error = atm_init_aal0(vcc);
+ vcc->stats = &dev->stats.aal0;
+ break;
+ case ATM_AAL34:
+ error = atm_init_aal34(vcc);
+ vcc->stats = &dev->stats.aal34;
+ break;
+ case ATM_NO_AAL:
+ /* ATM_AAL5 is also used in the "0 for default" case */
+ vcc->qos.aal = ATM_AAL5;
+ /* fall through */
+ case ATM_AAL5:
+ error = atm_init_aal5(vcc);
+ vcc->stats = &dev->stats.aal5;
+ break;
+ default:
+ error = -EPROTOTYPE;
+ }
+ if (!error) error = adjust_tp(&vcc->qos.txtp,vcc->qos.aal);
+ if (!error) error = adjust_tp(&vcc->qos.rxtp,vcc->qos.aal);
+ if (error)
+ goto fail;
+ DPRINTK("VCC %d.%d, AAL %d\n",vpi,vci,vcc->qos.aal);
+ DPRINTK(" TX: %d, PCR %d..%d, SDU %d\n",vcc->qos.txtp.traffic_class,
+ vcc->qos.txtp.min_pcr,vcc->qos.txtp.max_pcr,vcc->qos.txtp.max_sdu);
+ DPRINTK(" RX: %d, PCR %d..%d, SDU %d\n",vcc->qos.rxtp.traffic_class,
+ vcc->qos.rxtp.min_pcr,vcc->qos.rxtp.max_pcr,vcc->qos.rxtp.max_sdu);
+ if (dev->ops->open) {
+ if ((error = dev->ops->open(vcc,vpi,vci)))
+ goto fail;
+ }
+ return 0;
+
+fail:
+ vcc_remove_socket(vcc->sk);
+ if (dev->ops->owner)
+ __MOD_DEC_USE_COUNT(dev->ops->owner);
+ /* ensure we get dev module ref count correct */
+ vcc->dev = NULL;
+ return error;
+
+}
+
+
+int vcc_connect(struct socket *sock, int itf, short vpi, int vci)
+{
+ struct atm_dev *dev;
+ struct atm_vcc *vcc = ATM_SD(sock);
+ int error;
+
+ DPRINTK("vcc_connect (vpi %d, vci %d)\n",vpi,vci);
+ if (sock->state == SS_CONNECTED)
+ return -EISCONN;
+ if (sock->state != SS_UNCONNECTED)
+ return -EINVAL;
+ if (!(vpi || vci))
+ return -EINVAL;
+
+ if (vpi != ATM_VPI_UNSPEC && vci != ATM_VCI_UNSPEC)
+ clear_bit(ATM_VF_PARTIAL,&vcc->flags);
+ else
+ if (test_bit(ATM_VF_PARTIAL,&vcc->flags))
+ return -EINVAL;
+ DPRINTK("vcc_connect (TX: cl %d,bw %d-%d,sdu %d; "
+ "RX: cl %d,bw %d-%d,sdu %d,AAL %s%d)\n",
+ vcc->qos.txtp.traffic_class,vcc->qos.txtp.min_pcr,
+ vcc->qos.txtp.max_pcr,vcc->qos.txtp.max_sdu,
+ vcc->qos.rxtp.traffic_class,vcc->qos.rxtp.min_pcr,
+ vcc->qos.rxtp.max_pcr,vcc->qos.rxtp.max_sdu,
+ vcc->qos.aal == ATM_AAL5 ? "" : vcc->qos.aal == ATM_AAL0 ? "" :
+ " ??? code ",vcc->qos.aal == ATM_AAL0 ? 0 : vcc->qos.aal);
+ if (!test_bit(ATM_VF_HASQOS, &vcc->flags))
+ return -EBADFD;
+ if (vcc->qos.txtp.traffic_class == ATM_ANYCLASS ||
+ vcc->qos.rxtp.traffic_class == ATM_ANYCLASS)
+ return -EINVAL;
+ if (itf != ATM_ITF_ANY) {
+ dev = atm_dev_lookup(itf);
+ if (!dev)
+ return -ENODEV;
+ error = __vcc_connect(vcc, dev, vpi, vci);
+ if (error) {
+ atm_dev_put(dev);
+ return error;
+ }
+ } else {
+ struct list_head *p, *next;
+
+ dev = NULL;
+ spin_lock(&atm_dev_lock);
+ list_for_each_safe(p, next, &atm_devs) {
+ dev = list_entry(p, struct atm_dev, dev_list);
+ atm_dev_hold(dev);
+ spin_unlock(&atm_dev_lock);
+ if (!__vcc_connect(vcc, dev, vpi, vci))
+ break;
+ atm_dev_put(dev);
+ dev = NULL;
+ spin_lock(&atm_dev_lock);
+ }
+ spin_unlock(&atm_dev_lock);
+ if (!dev)
+ return -ENODEV;
+ }
+ if (vpi == ATM_VPI_UNSPEC || vci == ATM_VCI_UNSPEC)
+ set_bit(ATM_VF_PARTIAL,&vcc->flags);
+ if (test_bit(ATM_VF_READY,&ATM_SD(sock)->flags))
+ sock->state = SS_CONNECTED;
+ return 0;
+}
+
+
+int vcc_recvmsg(struct socket *sock, struct msghdr *msg,
+ int size, int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct atm_vcc *vcc;
+ struct sk_buff *skb;
+ int copied, error = -EINVAL;
+
+ if (sock->state != SS_CONNECTED)
+ return -ENOTCONN;
+ if (flags & ~MSG_DONTWAIT) /* only handle MSG_DONTWAIT */
+ return -EOPNOTSUPP;
+ vcc = ATM_SD(sock);
+ if (test_bit(ATM_VF_RELEASED,&vcc->flags) ||
+ test_bit(ATM_VF_CLOSE, &vcc->flags) ||
+ !test_bit(ATM_VF_READY, &vcc->flags))
+ return 0;
+
+ skb = skb_recv_datagram(sk, flags, flags & MSG_DONTWAIT, &error);
+ if (!skb)
+ return error;
+
+ copied = skb->len;
+ if (copied > size) {
+ copied = size;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+
+ error = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+ if (error)
+ return error;
+ sock_recv_timestamp(msg, sk, skb);
+ if (vcc->dev->ops->feedback)
+ vcc->dev->ops->feedback(vcc, skb, (unsigned long) skb->data,
+ (unsigned long) msg->msg_iov->iov_base, copied);
+ DPRINTK("RcvM %d -= %d\n", atomic_read(&vcc->sk->rmem_alloc), skb->truesize);
+ atm_return(vcc, skb->truesize);
+ skb_free_datagram(sk, skb);
+ return copied;
+}
+
+
+
+int vcc_sendmsg(struct socket *sock, struct msghdr *m, int total_len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ DECLARE_WAITQUEUE(wait,current);
+ struct atm_vcc *vcc;
+ struct sk_buff *skb;
+ int eff,error;
+ const void *buff;
+ int size;
+
+ lock_sock(sk);
+ if (sock->state != SS_CONNECTED) {
+ error = -ENOTCONN;
+ goto out;
+ }
+ if (m->msg_name) {
+ error = -EISCONN;
+ goto out;
+ }
+ if (m->msg_iovlen != 1) {
+ error = -ENOSYS; /* fix this later @@@ */
+ goto out;
+ }
+ buff = m->msg_iov->iov_base;
+ size = m->msg_iov->iov_len;
+ vcc = ATM_SD(sock);
+ if (test_bit(ATM_VF_RELEASED, &vcc->flags) ||
+ test_bit(ATM_VF_CLOSE, &vcc->flags) ||
+ !test_bit(ATM_VF_READY, &vcc->flags)) {
+ error = -EPIPE;
+ send_sig(SIGPIPE, current, 0);
+ goto out;
+ }
+ if (!size) {
+ error = 0;
+ goto out;
+ }
+ if (size < 0 || size > vcc->qos.txtp.max_sdu) {
+ error = -EMSGSIZE;
+ goto out;
+ }
+ /* verify_area is done by net/socket.c */
+ eff = (size+3) & ~3; /* align to word boundary */
+ add_wait_queue(&vcc->sleep,&wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ error = 0;
+ while (!(skb = alloc_tx(vcc,eff))) {
+ if (m->msg_flags & MSG_DONTWAIT) {
+ error = -EAGAIN;
+ break;
+ }
+ schedule();
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (signal_pending(current)) {
+ error = -ERESTARTSYS;
+ break;
+ }
+ if (test_bit(ATM_VF_RELEASED,&vcc->flags) ||
+ test_bit(ATM_VF_CLOSE, &vcc->flags) ||
+ !test_bit(ATM_VF_READY, &vcc->flags)) {
+ error = -EPIPE;
+ send_sig(SIGPIPE, current, 0);
+ break;
+ }
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&vcc->sleep,&wait);
+ if (error)
+ goto out;
+ skb->dev = NULL; /* for paths shared with net_device interfaces */
+ ATM_SKB(skb)->atm_options = vcc->atm_options;
+ if (copy_from_user(skb_put(skb,size),buff,size)) {
+ kfree_skb(skb);
+ error = -EFAULT;
+ goto out;
+ }
+ if (eff != size) memset(skb->data+size,0,eff-size);
+ error = vcc->dev->ops->send(vcc,skb);
+ error = error ? error : size;
+out:
+ release_sock(sk);
+ return error;
+}
+
+
+unsigned int atm_poll(struct file *file,struct socket *sock,poll_table *wait)
+{
+ struct atm_vcc *vcc;
+ unsigned int mask;
+
+ vcc = ATM_SD(sock);
+ poll_wait(file,&vcc->sleep,wait);
+ mask = 0;
+ if (skb_peek(&vcc->sk->receive_queue))
+ mask |= POLLIN | POLLRDNORM;
+ if (test_bit(ATM_VF_RELEASED,&vcc->flags) ||
+ test_bit(ATM_VF_CLOSE,&vcc->flags))
+ mask |= POLLHUP;
+ if (sock->state != SS_CONNECTING) {
+ if (vcc->qos.txtp.traffic_class != ATM_NONE &&
+ vcc->qos.txtp.max_sdu+atomic_read(&vcc->sk->wmem_alloc) <= vcc->sk->sndbuf)
+ mask |= POLLOUT | POLLWRNORM;
+ }
+ else if (vcc->reply != WAITING) {
+ mask |= POLLOUT | POLLWRNORM;
+ if (vcc->reply) mask |= POLLERR;
+ }
+ return mask;
+}
+
+
+int vcc_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct atm_vcc *vcc;
+ int error;
+
+ vcc = ATM_SD(sock);
+ switch (cmd) {
+ case SIOCOUTQ:
+ if (sock->state != SS_CONNECTED ||
+ !test_bit(ATM_VF_READY, &vcc->flags)) {
+ error = -EINVAL;
+ goto done;
+ }
+ error = put_user(vcc->sk->sndbuf-
+ atomic_read(&vcc->sk->wmem_alloc),
+ (int *) arg) ? -EFAULT : 0;
+ goto done;
+ case SIOCINQ:
+ {
+ struct sk_buff *skb;
+
+ if (sock->state != SS_CONNECTED) {
+ error = -EINVAL;
+ goto done;
+ }
+ skb = skb_peek(&vcc->sk->receive_queue);
+ error = put_user(skb ? skb->len : 0,
+ (int *) arg) ? -EFAULT : 0;
+ goto done;
+ }
+ case SIOCGSTAMP: /* borrowed from IP */
+ if (!vcc->sk->stamp.tv_sec) {
+ error = -ENOENT;
+ goto done;
+ }
+ error = copy_to_user((void *) arg, &vcc->sk->stamp,
+ sizeof(struct timeval)) ? -EFAULT : 0;
+ goto done;
+ case ATM_SETSC:
+ printk(KERN_WARNING "ATM_SETSC is obsolete\n");
+ error = 0;
+ goto done;
+ case ATMSIGD_CTRL:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ /*
+ * The user/kernel protocol for exchanging signalling
+ * info uses kernel pointers as opaque references,
+ * so the holder of the file descriptor can scribble
+ * on the kernel... so we should make sure that we
+ * have the same privledges that /proc/kcore needs
+ */
+ if (!capable(CAP_SYS_RAWIO)) {
+ error = -EPERM;
+ goto done;
+ }
+ error = sigd_attach(vcc);
+ if (!error)
+ sock->state = SS_CONNECTED;
+ goto done;
+#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
+ case SIOCMKCLIP:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ if (try_atm_clip_ops()) {
+ error = atm_clip_ops->clip_create(arg);
+ if (atm_clip_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_clip_ops->owner);
+ } else
+ error = -ENOSYS;
+ goto done;
+ case ATMARPD_CTRL:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+#if defined(CONFIG_ATM_CLIP_MODULE)
+ if (!atm_clip_ops)
+ request_module("clip");
+#endif
+ if (try_atm_clip_ops()) {
+ error = atm_clip_ops->atm_init_atmarp(vcc);
+ if (atm_clip_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_clip_ops->owner);
+ if (!error)
+ sock->state = SS_CONNECTED;
+ } else
+ error = -ENOSYS;
+ goto done;
+ case ATMARP_MKIP:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ if (try_atm_clip_ops()) {
+ error = atm_clip_ops->clip_mkip(vcc, arg);
+ if (atm_clip_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_clip_ops->owner);
+ } else
+ error = -ENOSYS;
+ goto done;
+ case ATMARP_SETENTRY:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ if (try_atm_clip_ops()) {
+ error = atm_clip_ops->clip_setentry(vcc, arg);
+ if (atm_clip_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_clip_ops->owner);
+ } else
+ error = -ENOSYS;
+ goto done;
+ case ATMARP_ENCAP:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ if (try_atm_clip_ops()) {
+ error = atm_clip_ops->clip_encap(vcc, arg);
+ if (atm_clip_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_clip_ops->owner);
+ } else
+ error = -ENOSYS;
+ goto done;
+#endif
+#ifdef CONFIG_ATM_IPV6
+ case SIOCMKP2PPVC:
+ if (!capable(CAP_NET_ADMIN))
+ error = -EPERM;
+ else
+ error = clip6_create(arg);
+ goto done;
+ case ATMIPV6_MKIPV6:
+ if (!capable(CAP_NET_ADMIN))
+ error = -EPERM;
+ else
+ error = clip6_mkip(vcc,arg);
+ goto done;
+ case ATMIPV6_SETP2P:
+ if (!capable(CAP_NET_ADMIN))
+ error = -EPERM;
+ else
+ error = clip6_set_vcc_netif(sock,arg);
+ goto done;
+ case ATMIPV6_ENCAP:
+ if (!capable(CAP_NET_ADMIN))
+ error = -EPERM;
+ else
+ error = clip6_encap(vcc,arg);
+ goto done;
+#endif /* ifdef CONFIG_ATM_IPV6 */
+#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)
+ case ATMLEC_CTRL:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+#if defined(CONFIG_ATM_LANE_MODULE)
+ if (!atm_lane_ops)
+ request_module("lec");
+#endif
+ if (try_atm_lane_ops()) {
+ error = atm_lane_ops->lecd_attach(vcc, (int) arg);
+ if (atm_lane_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_lane_ops->owner);
+ if (error >= 0)
+ sock->state = SS_CONNECTED;
+ } else
+ error = -ENOSYS;
+ goto done;
+ case ATMLEC_MCAST:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ if (try_atm_lane_ops()) {
+ error = atm_lane_ops->mcast_attach(vcc, (int) arg);
+ if (atm_lane_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_lane_ops->owner);
+ } else
+ error = -ENOSYS;
+ goto done;
+ case ATMLEC_DATA:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ if (try_atm_lane_ops()) {
+ error = atm_lane_ops->vcc_attach(vcc, (void *) arg);
+ if (atm_lane_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_lane_ops->owner);
+ } else
+ error = -ENOSYS;
+ goto done;
+#endif
+#if defined(CONFIG_ATM_MPOA) || defined(CONFIG_ATM_MPOA_MODULE)
+ case ATMMPC_CTRL:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+#if defined(CONFIG_ATM_MPOA_MODULE)
+ if (!atm_mpoa_ops)
+ request_module("mpoa");
+#endif
+ if (try_atm_mpoa_ops()) {
+ error = atm_mpoa_ops->mpoad_attach(vcc, (int) arg);
+ if (atm_mpoa_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_mpoa_ops->owner);
+ if (error >= 0)
+ sock->state = SS_CONNECTED;
+ } else
+ error = -ENOSYS;
+ goto done;
+ case ATMMPC_DATA:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ if (try_atm_mpoa_ops()) {
+ error = atm_mpoa_ops->vcc_attach(vcc, arg);
+ if (atm_mpoa_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_mpoa_ops->owner);
+ } else
+ error = -ENOSYS;
+ goto done;
+#endif
+#if defined(CONFIG_ATM_TCP) || defined(CONFIG_ATM_TCP_MODULE)
+ case SIOCSIFATMTCP:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ if (!atm_tcp_ops.attach) {
+ error = -ENOPKG;
+ goto done;
+ }
+ fops_get(&atm_tcp_ops);
+ error = atm_tcp_ops.attach(vcc, (int) arg);
+ if (error >= 0)
+ sock->state = SS_CONNECTED;
+ else
+ fops_put(&atm_tcp_ops);
+ goto done;
+ case ATMTCP_CREATE:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ if (!atm_tcp_ops.create_persistent) {
+ error = -ENOPKG;
+ goto done;
+ }
+ error = atm_tcp_ops.create_persistent((int) arg);
+ if (error < 0)
+ fops_put(&atm_tcp_ops);
+ goto done;
+ case ATMTCP_REMOVE:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ if (!atm_tcp_ops.remove_persistent) {
+ error = -ENOPKG;
+ goto done;
+ }
+ error = atm_tcp_ops.remove_persistent((int) arg);
+ fops_put(&atm_tcp_ops);
+ goto done;
+#endif
+ default:
+ break;
+ }
+ error = -ENOIOCTLCMD;
+#if defined(CONFIG_PPPOATM) || defined(CONFIG_PPPOATM_MODULE)
+ down(&pppoatm_ioctl_mutex);
+ if (pppoatm_ioctl_hook)
+ error = pppoatm_ioctl_hook(vcc, cmd, arg);
+ up(&pppoatm_ioctl_mutex);
+ if (error != -ENOIOCTLCMD)
+ goto done;
+#endif
+#if defined(CONFIG_ATM_BR2684) || defined(CONFIG_ATM_BR2684_MODULE)
+ down(&br2684_ioctl_mutex);
+ if (br2684_ioctl_hook)
+ error = br2684_ioctl_hook(vcc, cmd, arg);
+ up(&br2684_ioctl_mutex);
+ if (error != -ENOIOCTLCMD)
+ goto done;
+#endif
+
+ error = atm_dev_ioctl(cmd, arg);
+
+done:
+ return error;
+}
+
+
+static int atm_change_qos(struct atm_vcc *vcc,struct atm_qos *qos)
+{
+ int error;
+
+ /*
+ * Don't let the QoS change the already connected AAL type nor the
+ * traffic class.
+ */
+ if (qos->aal != vcc->qos.aal ||
+ qos->rxtp.traffic_class != vcc->qos.rxtp.traffic_class ||
+ qos->txtp.traffic_class != vcc->qos.txtp.traffic_class)
+ return -EINVAL;
+ error = adjust_tp(&qos->txtp,qos->aal);
+ if (!error) error = adjust_tp(&qos->rxtp,qos->aal);
+ if (error) return error;
+ if (!vcc->dev->ops->change_qos) return -EOPNOTSUPP;
+ if (vcc->sk->family == AF_ATMPVC)
+ return vcc->dev->ops->change_qos(vcc,qos,ATM_MF_SET);
+ return svc_change_qos(vcc,qos);
+}
+
+
+static int check_tp(struct atm_trafprm *tp)
+{
+ /* @@@ Should be merged with adjust_tp */
+ if (!tp->traffic_class || tp->traffic_class == ATM_ANYCLASS) return 0;
+ if (tp->traffic_class != ATM_UBR && !tp->min_pcr && !tp->pcr &&
+ !tp->max_pcr) return -EINVAL;
+ if (tp->min_pcr == ATM_MAX_PCR) return -EINVAL;
+ if (tp->min_pcr && tp->max_pcr && tp->max_pcr != ATM_MAX_PCR &&
+ tp->min_pcr > tp->max_pcr) return -EINVAL;
+ /*
+ * We allow pcr to be outside [min_pcr,max_pcr], because later
+ * adjustment may still push it in the valid range.
+ */
+ return 0;
+}
+
+
+static int check_qos(struct atm_qos *qos)
+{
+ int error;
+
+ if (!qos->txtp.traffic_class && !qos->rxtp.traffic_class)
+ return -EINVAL;
+ if (qos->txtp.traffic_class != qos->rxtp.traffic_class &&
+ qos->txtp.traffic_class && qos->rxtp.traffic_class &&
+ qos->txtp.traffic_class != ATM_ANYCLASS &&
+ qos->rxtp.traffic_class != ATM_ANYCLASS) return -EINVAL;
+ error = check_tp(&qos->txtp);
+ if (error) return error;
+ return check_tp(&qos->rxtp);
+}
+
+int vcc_setsockopt(struct socket *sock, int level, int optname,
+ char *optval, int optlen)
+{
+ struct atm_vcc *vcc;
+ unsigned long value;
+ int error;
+
+ if (__SO_LEVEL_MATCH(optname, level) && optlen != __SO_SIZE(optname))
+ return -EINVAL;
+
+ vcc = ATM_SD(sock);
+ switch (optname) {
+ case SO_ATMQOS:
+ {
+ struct atm_qos qos;
+
+ if (copy_from_user(&qos,optval,sizeof(qos)))
+ return -EFAULT;
+ error = check_qos(&qos);
+ if (error) return error;
+ if (sock->state == SS_CONNECTED)
+ return atm_change_qos(vcc,&qos);
+ if (sock->state != SS_UNCONNECTED)
+ return -EBADFD;
+ vcc->qos = qos;
+ set_bit(ATM_VF_HASQOS,&vcc->flags);
+ return 0;
+ }
+ case SO_SETCLP:
+ if (get_user(value,(unsigned long *) optval))
+ return -EFAULT;
+ if (value) vcc->atm_options |= ATM_ATMOPT_CLP;
+ else vcc->atm_options &= ~ATM_ATMOPT_CLP;
+ return 0;
+ default:
+ if (level == SOL_SOCKET) return -EINVAL;
+ break;
+ }
+ if (!vcc->dev || !vcc->dev->ops->setsockopt) return -EINVAL;
+ return vcc->dev->ops->setsockopt(vcc,level,optname,optval,optlen);
+}
+
+
+int vcc_getsockopt(struct socket *sock, int level, int optname,
+ char *optval, int *optlen)
+{
+ struct atm_vcc *vcc;
+ int len;
+
+ if (get_user(len, optlen))
+ return -EFAULT;
+ if (__SO_LEVEL_MATCH(optname, level) && len != __SO_SIZE(optname))
+ return -EINVAL;
+
+ vcc = ATM_SD(sock);
+ switch (optname) {
+ case SO_ATMQOS:
+ if (!test_bit(ATM_VF_HASQOS,&vcc->flags))
+ return -EINVAL;
+ return copy_to_user(optval,&vcc->qos,sizeof(vcc->qos)) ?
+ -EFAULT : 0;
+ case SO_SETCLP:
+ return put_user(vcc->atm_options & ATM_ATMOPT_CLP ? 1 :
+ 0,(unsigned long *) optval) ? -EFAULT : 0;
+ case SO_ATMPVC:
+ {
+ struct sockaddr_atmpvc pvc;
+
+ if (!vcc->dev ||
+ !test_bit(ATM_VF_ADDR,&vcc->flags))
+ return -ENOTCONN;
+ pvc.sap_family = AF_ATMPVC;
+ pvc.sap_addr.itf = vcc->dev->number;
+ pvc.sap_addr.vpi = vcc->vpi;
+ pvc.sap_addr.vci = vcc->vci;
+ return copy_to_user(optval,&pvc,sizeof(pvc)) ?
+ -EFAULT : 0;
+ }
+ default:
+ if (level == SOL_SOCKET) return -EINVAL;
+ break;
+ }
+ if (!vcc->dev || !vcc->dev->ops->getsockopt) return -EINVAL;
+ return vcc->dev->ops->getsockopt(vcc, level, optname, optval, len);
+}
+
+
+#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+struct net_bridge_fdb_entry *(*br_fdb_get_hook)(struct net_bridge *br,
+ unsigned char *addr) = NULL;
+void (*br_fdb_put_hook)(struct net_bridge_fdb_entry *ent) = NULL;
+#if defined(CONFIG_ATM_LANE_MODULE) || defined(CONFIG_BRIDGE_MODULE)
+EXPORT_SYMBOL(br_fdb_get_hook);
+EXPORT_SYMBOL(br_fdb_put_hook);
+#endif /* defined(CONFIG_ATM_LANE_MODULE) || defined(CONFIG_BRIDGE_MODULE) */
+#endif /* defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) */
+#endif /* defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE) */
+
+
+static int __init atm_init(void)
+{
+ int error;
+
+ if ((error = atmpvc_init()) < 0) {
+ printk(KERN_ERR "atmpvc_init() failed with %d\n", error);
+ goto failure;
+ }
+ if ((error = atmsvc_init()) < 0) {
+ printk(KERN_ERR "atmsvc_init() failed with %d\n", error);
+ goto failure;
+ }
+#ifdef CONFIG_PROC_FS
+ if ((error = atm_proc_init()) < 0) {
+ printk(KERN_ERR "atm_proc_init() failed with %d\n",error);
+ goto failure;
+ }
+#endif
+ return 0;
+
+failure:
+ atmsvc_exit();
+ atmpvc_exit();
+ return error;
+}
+
+static void __exit atm_exit(void)
+{
+#ifdef CONFIG_PROC_FS
+ atm_proc_exit();
+#endif
+ atmsvc_exit();
+ atmpvc_exit();
+}
+
+module_init(atm_init);
+module_exit(atm_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/atm/common.h b/uClinux-2.4.31-uc0/net/atm/common.h
new file mode 100644
index 0000000..ac609ac
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/common.h
@@ -0,0 +1,48 @@
+/* net/atm/common.h - ATM sockets (common part for PVC and SVC) */
+
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#ifndef NET_ATM_COMMON_H
+#define NET_ATM_COMMON_H
+
+#include <linux/net.h>
+#include <linux/poll.h> /* for poll_table */
+
+
+int vcc_create(struct socket *sock, int protocol, int family);
+int vcc_release(struct socket *sock);
+int vcc_connect(struct socket *sock, int itf, short vpi, int vci);
+int vcc_recvmsg(struct socket *sock, struct msghdr *msg,
+ int size, int flags, struct scm_cookie *scm);
+int vcc_sendmsg(struct socket *sock, struct msghdr *m, int total_len,
+ struct scm_cookie *scm);
+unsigned int atm_poll(struct file *file,struct socket *sock,poll_table *wait);
+int vcc_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg);
+int vcc_setsockopt(struct socket *sock, int level, int optname, char *optval,
+ int optlen);
+int vcc_getsockopt(struct socket *sock, int level, int optname, char *optval,
+ int *optlen);
+
+void atm_shutdown_dev(struct atm_dev *dev);
+
+void pppoatm_ioctl_set(int (*hook)(struct atm_vcc *, unsigned int, unsigned long));
+void br2684_ioctl_set(int (*hook)(struct atm_vcc *, unsigned int, unsigned long));
+
+int atmpvc_init(void);
+void atmpvc_exit(void);
+int atmsvc_init(void);
+void atmsvc_exit(void);
+int atm_proc_init(void);
+void atm_proc_exit(void);
+
+/* SVC */
+
+void svc_callback(struct atm_vcc *vcc);
+int svc_change_qos(struct atm_vcc *vcc,struct atm_qos *qos);
+
+/* p2mp */
+
+int create_leaf(struct socket *leaf,struct socket *session);
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/atm/ipcommon.c b/uClinux-2.4.31-uc0/net/atm/ipcommon.c
new file mode 100644
index 0000000..f939736
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/ipcommon.c
@@ -0,0 +1,71 @@
+/* net/atm/ipcommon.c - Common items for all ways of doing IP over ATM */
+
+/* Written 1996-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/in.h>
+#include <linux/atmdev.h>
+#include <linux/atmclip.h>
+
+#include "common.h"
+#include "ipcommon.h"
+
+
+#if 0
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+
+const unsigned char llc_oui[] = {
+ 0xaa, /* DSAP: non-ISO */
+ 0xaa, /* SSAP: non-ISO */
+ 0x03, /* Ctrl: Unnumbered Information Command PDU */
+ 0x00, /* OUI: EtherType */
+ 0x00,
+ 0x00 };
+
+
+/*
+ * skb_migrate appends the list at "from" to "to", emptying "from" in the
+ * process. skb_migrate is atomic with respect to all other skb operations on
+ * "from" and "to". Note that it locks both lists at the same time, so beware
+ * of potential deadlocks.
+ *
+ * This function should live in skbuff.c or skbuff.h.
+ */
+
+
+void skb_migrate(struct sk_buff_head *from,struct sk_buff_head *to)
+{
+ struct sk_buff *skb;
+ unsigned long flags;
+ struct sk_buff *skb_from = (struct sk_buff *) from;
+ struct sk_buff *skb_to = (struct sk_buff *) to;
+ struct sk_buff *prev;
+
+ spin_lock_irqsave(&from->lock,flags);
+ spin_lock(&to->lock);
+ prev = from->prev;
+ from->next->prev = to->prev;
+ prev->next = skb_to;
+ to->prev->next = from->next;
+ to->prev = from->prev;
+ for (skb = from->next; skb != skb_to; skb = skb->next)
+ skb->list = to;
+ to->qlen += from->qlen;
+ spin_unlock(&to->lock);
+ from->prev = skb_from;
+ from->next = skb_from;
+ from->qlen = 0;
+ spin_unlock_irqrestore(&from->lock,flags);
+}
+
+
+EXPORT_SYMBOL(llc_oui);
+EXPORT_SYMBOL(skb_migrate);
diff --git a/uClinux-2.4.31-uc0/net/atm/ipcommon.h b/uClinux-2.4.31-uc0/net/atm/ipcommon.h
new file mode 100644
index 0000000..bc1675e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/ipcommon.h
@@ -0,0 +1,25 @@
+/* net/atm/ipcommon.h - Common items for all ways of doing IP over ATM */
+
+/* Written 1996-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#ifndef NET_ATM_IPCOMMON_H
+#define NET_ATM_IPCOMMON_H
+
+
+#include <linux/string.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/atmdev.h>
+
+
+extern struct net_device *clip_devs;
+
+/*
+ * Appends all skbs from "from" to "to". The operation is atomic with respect
+ * to all other skb operations on "from" or "to".
+ */
+
+void skb_migrate(struct sk_buff_head *from,struct sk_buff_head *to);
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/atm/ipv6atm.c b/uClinux-2.4.31-uc0/net/atm/ipv6atm.c
new file mode 100644
index 0000000..3d0c98e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/ipv6atm.c
@@ -0,0 +1,419 @@
+/* $USAGI: ipv6atm.c,v 1.11 2003/12/01 08:01:21 sekiya Exp $
+ *
+ * RFC2492 IPv6 over ATM
+ *
+ * Copyright (C)2001 USAGI/WIDE Project
+ *
+ * Written by Kazuyoshi SERIZAWA, based on
+ * net/atm/clip.c - RFC1577 Classical IP over ATM
+ * Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA
+ */
+
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/kernel.h> /* for UINT_MAX */
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/wait.h>
+#include <linux/timer.h>
+#include <linux/if_arp.h> /* for some manifest constants */
+#include <linux/notifier.h>
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/atmipv6.h> /* IPv6 over ATM */
+#include <linux/ip.h> /* for net/route.h */
+#include <linux/in.h> /* for struct sockaddr_in */
+#include <linux/if.h> /* for IFF_UP */
+#include <linux/inetdevice.h>
+#include <linux/bitops.h>
+#include <net/route.h> /* for struct rtable and routing */
+#include <net/icmp.h> /* icmp_send */
+#include <asm/param.h> /* for HZ */
+#include <asm/byteorder.h> /* for htons etc. */
+#include <asm/system.h> /* save/restore_flags */
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+
+#include "common.h"
+#include "resources.h"
+#include "ipcommon.h"
+#include <net/atmipv6.h>
+
+#if 0
+#define DPRINTK(format,args...) printk(format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+
+struct net_device *clip6_devs = NULL;
+
+
+void clip6_push(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+ struct clip6_vcc *clip6_vcc = CLIP6_VCC(vcc);
+
+ DPRINTK("clip6 push\n");
+ if (!skb) {
+ DPRINTK("removing VCC %p\n",clip6_vcc);
+ clip6_vcc->old_push(vcc,NULL); /* pass on the bad news */
+ kfree(clip6_vcc);
+ return;
+ }
+ atm_return(vcc,skb->truesize);
+ skb->dev = clip6_vcc->dev;
+ /* clip6_vcc->entry == NULL if we don't have an IP address yet */
+ if (!skb->dev) {
+ dev_kfree_skb_any(skb);
+ return;
+ }
+ ATM_SKB(skb)->vcc = vcc;
+ skb->mac.raw = skb->data;
+ if (!clip6_vcc->encap || skb->len < RFC1483LLC_LEN || memcmp(skb->data,
+ llc_oui,sizeof(llc_oui))) {
+ skb->protocol = ((u16 *) skb->data)[3];
+ /* ??? Should I validate skb->protocol is
+ either ETH_P_IPV6 or ETH_P_IP? */
+ }
+ else {
+ skb->protocol = ((u16 *) skb->data)[3];
+ skb_pull(skb,RFC1483LLC_LEN);
+ /* TODO: check */
+#if 0
+ if (skb->protocol == htons(ETH_P_ARP)) {
+ PRIV(skb->dev)->stats.rx_packets++;
+ PRIV(skb->dev)->stats.rx_bytes += skb->len;
+ clip6_arp_rcv(skb);
+ return;
+ }
+#endif /* if 0 */
+ }
+ clip6_vcc->last_use = jiffies;
+ PRIV(skb->dev)->stats.rx_packets++;
+ PRIV(skb->dev)->stats.rx_bytes += skb->len;
+ netif_rx(skb);
+}
+
+
+/*
+ * Note: these spinlocks _must_not_ block on non-SMP. The only goal is that
+ * clip6_pop is atomic with respect to the critical section in clip6_start_xmit.
+ */
+
+
+static void clip6_pop(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+ struct clip6_vcc *clip6_vcc = CLIP6_VCC(vcc);
+ struct net_device *dev = skb->dev;
+ int old;
+ unsigned long flags;
+
+ DPRINTK("clip6_pop(vcc %p)\n",vcc);
+ clip6_vcc->old_pop(vcc,skb);
+ /* skb->dev == NULL in outbound ARP packets */
+ if (!dev) return;
+ spin_lock_irqsave(&PRIV(dev)->xoff_lock,flags);
+ if (atm_may_send(vcc,0)) {
+ old = xchg(&clip6_vcc->xoff,0);
+ if (old) netif_wake_queue(dev);
+ }
+ spin_unlock_irqrestore(&PRIV(dev)->xoff_lock,flags);
+}
+
+
+/* @@@ copy bh locking from arp.c -- need to bh-enable atm code before */
+
+/*
+ * We play with the resolve flag: 0 and 1 have the usual meaning, but -1 means
+ * to allocate the neighbour entry but not to ask atmarpd for resolution. Also,
+ * don't increment the usage count. This is used to create entries in
+ * clip6_setentry.
+ */
+
+
+int clip6_encap(struct atm_vcc *vcc,int mode)
+{
+ if (mode != 1) return -EINVAL; /* Only LLC/SNAP is supported */
+ CLIP6_VCC(vcc)->encap = mode;
+ return 0;
+}
+
+
+static int clip6_start_xmit(struct sk_buff *skb,struct net_device *dev)
+{
+ struct clip6_priv *clip6_priv = PRIV(dev);
+ struct atm_vcc *vcc;
+ struct clip6_vcc *vccs;
+ int old;
+ unsigned long flags;
+
+ DPRINTK("clip6_start_xmit (skb %p)\n",skb);
+
+ vccs = clip6_priv->vccs;
+ if (!vccs) {
+ /* SVC is not supported yet. */
+ printk(KERN_ERR "clip6_start_xmit: vccs == NULL\n");
+ dev_kfree_skb(skb);
+ clip6_priv->stats.tx_dropped++;
+ return 0;
+ }
+ DPRINTK("vccs %p\n", vccs);
+ ATM_SKB(skb)->vcc = vcc = vccs->vcc;
+ DPRINTK("using p2p vcc %p\n",vcc);
+ if (vccs->encap) {
+ void *here;
+
+ here = skb_push(skb,RFC1483LLC_LEN);
+ memcpy(here,llc_oui,sizeof(llc_oui));
+ ((u16 *) here)[3] = skb->protocol;
+ }
+ atomic_add(skb->truesize, &vcc->sk->wmem_alloc);
+ ATM_SKB(skb)->atm_options = vcc->atm_options;
+ /* TODO: check */
+ vccs->last_use = jiffies;
+ DPRINTK("atm_skb(%p)->vcc(%p)->dev(%p)\n",skb,vcc,vcc->dev);
+ old = xchg(&vccs->xoff,1); /* assume XOFF ... */
+ if (old) {
+ printk(KERN_WARNING "clip6_start_xmit: XOFF->XOFF transition\n");
+ return 0;
+ }
+ clip6_priv->stats.tx_packets++;
+ clip6_priv->stats.tx_bytes += skb->len;
+ (void) vcc->send(vcc,skb);
+ if (atm_may_send(vcc,0)) {
+ vccs->xoff = 0;
+ return 0;
+ }
+ spin_lock_irqsave(&clip6_priv->xoff_lock,flags);
+ netif_stop_queue(dev); /* XOFF -> throttle immediately */
+ barrier();
+ if (!vccs->xoff)
+ netif_start_queue(dev);
+ /* Oh, we just raced with clip6_pop. netif_start_queue should be
+ good enough, because nothing should really be asleep because
+ of the brief netif_stop_queue. If this isn't true or if it
+ changes, use netif_wake_queue instead. */
+ spin_unlock_irqrestore(&clip6_priv->xoff_lock,flags);
+ return 0;
+}
+
+
+static struct net_device_stats *clip6_get_stats(struct net_device *dev)
+{
+ return &PRIV(dev)->stats;
+}
+
+
+int clip6_mkip(struct atm_vcc *vcc,int timeout)
+{
+ struct clip6_vcc *clip6_vcc;
+ struct sk_buff_head copy;
+ struct sk_buff *skb;
+
+ if (!vcc->push) return -EBADFD;
+ clip6_vcc = kmalloc(sizeof(struct clip6_vcc),GFP_KERNEL);
+ if (!clip6_vcc) return -ENOMEM;
+ DPRINTK("mkip6 clip6_vcc %p vcc %p\n",clip6_vcc,vcc);
+ clip6_vcc->vcc = vcc;
+ vcc->user_back = clip6_vcc;
+ clip6_vcc->entry = NULL;
+ clip6_vcc->dev = NULL;
+ clip6_vcc->xoff = 0;
+ clip6_vcc->encap = 1;
+ clip6_vcc->last_use = jiffies;
+ clip6_vcc->idle_timeout = timeout*HZ;
+ clip6_vcc->old_push = vcc->push;
+ clip6_vcc->old_pop = vcc->pop;
+ vcc->push = clip6_push;
+ vcc->pop = clip6_pop;
+ skb_queue_head_init(&copy);
+ skb_migrate(&vcc->sk->receive_queue,&copy);
+ /* re-process everything received between connection setup and MKIP */
+ while ((skb = skb_dequeue(&copy)))
+ if (!clip6_devs) {
+ atm_return(vcc,skb->truesize);
+ kfree_skb(skb);
+ }
+ else {
+ unsigned int len = skb->len;
+
+ clip6_push(vcc,skb);
+ PRIV(skb->dev)->stats.rx_packets--;
+ PRIV(skb->dev)->stats.rx_bytes -= len;
+ }
+ return 0;
+}
+
+
+int clip6_set_vcc_netif(struct socket *sock,int number)
+{
+ struct clip6_vcc *clip6_vcc;
+ struct sock *sk = NULL;
+ struct net_device *dev;
+ struct atm_vcc *vcc = ATM_SD(sock);
+
+ DPRINTK("clip6_set_vcc_netif 0x%08x\n", (unsigned int)vcc);
+ if (vcc->push != clip6_push) {
+ printk(KERN_WARNING "clip6_set_vcc_netif: non-CLIP VCC\n");
+ return -EBADF;
+ }
+
+ /* allocate a scapegoat sk and vcc */
+ if (sock->type == SOCK_STREAM)
+ return -EINVAL;
+ sk = sk_alloc(sock->sk->family, GFP_KERNEL, 1);
+ if (!sk)
+ return -ENOMEM;
+ sock_init_data(NULL, sk);
+
+ vcc = sk->protinfo.af_atm = kmalloc(sizeof(*vcc), GFP_KERNEL);
+ if (!vcc) {
+ sk_free(sk);
+ return -ENOMEM;
+ }
+
+ memset(vcc, 0, sizeof(*vcc));
+ vcc->sk = sk;
+
+ clip6_vcc = CLIP6_VCC(vcc);
+
+ for (dev = clip6_devs; dev; dev = PRIV(dev)->next) {
+ if (PRIV(dev)->number == number) {
+ PRIV(dev)->vccs = clip6_vcc;
+ clip6_vcc->dev = dev;
+ if (vcc->dev) {
+ /* copy MAC address */
+ /* TODO: This will cause address duplication
+ in case loop back.
+ To avoid this, dev_addr should include
+ the number of interface, or such. */
+ dev->addr_len = ESI_LEN;
+ memcpy(dev->dev_addr, vcc->dev->esi, dev->addr_len);
+ }
+ /* detach vcc from a soket */
+ sk->rcvbuf = vcc->sk->rcvbuf;
+ sk->sndbuf = vcc->sk->sndbuf;
+
+ PRIV(dev)->vcc = vcc;
+ PRIV(dev)->vcc->sk = sk;
+
+ *(&ATM_SD(sock)) = sk->protinfo.af_atm;
+ sk->protinfo.af_atm->sk = sock->sk;
+
+ /* TODO: ininialize lists, vcc->prev,next, nodev_vccs */
+ return 0;
+ }
+ }
+ return -ENODEV;
+}
+
+
+static int clip6_init(struct net_device *dev)
+{
+ DPRINTK("clip6_init %s\n",dev->name);
+ dev->hard_start_xmit = clip6_start_xmit;
+ /* sg_xmit ... */
+ dev->hard_header = NULL;
+ dev->rebuild_header = NULL;
+ dev->set_mac_address = NULL;
+ dev->hard_header_parse = NULL;
+ dev->hard_header_cache = NULL;
+ dev->header_cache_update = NULL;
+ dev->change_mtu = NULL;
+ dev->do_ioctl = NULL;
+ dev->get_stats = clip6_get_stats;
+ dev->type = ARPHRD_ATM;
+ dev->hard_header_len = RFC1483LLC_LEN;
+ /* TODO: check */
+ dev->mtu = RFC1626_MTU;
+ dev->addr_len = 0;
+ dev->tx_queue_len = 100; /* "normal" queue (packets) */
+ /* When using a "real" qdisc, the qdisc determines the queue */
+ /* length. tx_queue_len is only used for the default case, */
+ /* without any more elaborate queuing. 100 is a reasonable */
+ /* compromise between decent burst-tolerance and protection */
+ /* against memory hogs. */
+ dev->flags = 0;
+ return 0;
+}
+
+
+int clip6_create(int number)
+{
+ struct net_device *dev;
+ struct clip6_priv *clip6_priv;
+ int error;
+
+ DPRINTK("clip6_create\n");
+ if (number != -1) {
+ for (dev = clip6_devs; dev; dev = PRIV(dev)->next)
+ if (PRIV(dev)->number == number) return -EEXIST;
+ }
+ else {
+ number = 0;
+ for (dev = clip6_devs; dev; dev = PRIV(dev)->next)
+ if (PRIV(dev)->number >= number)
+ number = PRIV(dev)->number+1;
+ DPRINTK("clip6_create number %d\n", number);
+ }
+ dev = kmalloc(sizeof(struct net_device)+sizeof(struct clip6_priv),
+ GFP_KERNEL);
+ DPRINTK("clip6_create mem\n");
+ if (!dev) return -ENOMEM;
+ DPRINTK("clip6_create mem ok\n");
+ memset(dev,0,sizeof(struct net_device)+sizeof(struct clip6_priv));
+ clip6_priv = PRIV(dev);
+ sprintf(dev->name,"atm%d",number);
+ dev->init = clip6_init;
+ spin_lock_init(&clip6_priv->xoff_lock);
+ clip6_priv->number = number;
+ error = register_netdev(dev);
+ if (error) {
+ kfree(dev);
+ DPRINTK("clip6_create error %d\n",error);
+ return error;
+ }
+ clip6_priv->next = clip6_devs;
+ clip6_devs = dev;
+ clip6_priv->vccs = NULL;
+ clip6_priv->vcc = NULL;
+ DPRINTK("registered (net:%s)\n",dev->name);
+ return number;
+}
+
+
+static int clip6_device_event(struct notifier_block *this,unsigned long event,
+ void *dev)
+{
+ /* ignore non-CLIP devices */
+ if (((struct net_device *) dev)->type != ARPHRD_ATM ||
+ ((struct net_device *) dev)->init != clip6_init)
+ return NOTIFY_DONE;
+ switch (event) {
+ case NETDEV_UP:
+ DPRINTK("clip6_device_event NETDEV_UP\n");
+ /* ignore */
+ break;
+ case NETDEV_GOING_DOWN:
+ DPRINTK("clip6_device_event NETDEV_DOWN\n");
+ /* ignore */
+ break;
+ case NETDEV_CHANGE:
+ case NETDEV_CHANGEMTU:
+ DPRINTK("clip6_device_event NETDEV_CHANGE*\n");
+ /* ignore */
+ break;
+ case NETDEV_REBOOT:
+ case NETDEV_REGISTER:
+ case NETDEV_DOWN:
+ DPRINTK("clip6_device_event %ld\n",event);
+ /* ignore */
+ break;
+ default:
+ printk(KERN_WARNING "clip6_device_event: unknown event "
+ "%ld\n",event);
+ break;
+ }
+ return NOTIFY_DONE;
+}
diff --git a/uClinux-2.4.31-uc0/net/atm/lec.c b/uClinux-2.4.31-uc0/net/atm/lec.c
new file mode 100644
index 0000000..fd58bd7
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/lec.c
@@ -0,0 +1,2194 @@
+/*
+ * lec.c: Lan Emulation driver
+ * Marko Kiiskila mkiiskila@yahoo.com
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+
+/* We are ethernet device */
+#include <linux/if_ether.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/etherdevice.h>
+#include <net/sock.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <asm/byteorder.h>
+#include <asm/uaccess.h>
+#include <net/arp.h>
+#include <net/dst.h>
+#include <linux/proc_fs.h>
+#include <linux/spinlock.h>
+
+/* TokenRing if needed */
+#ifdef CONFIG_TR
+#include <linux/trdevice.h>
+#endif
+
+/* And atm device */
+#include <linux/atmdev.h>
+#include <linux/atmlec.h>
+
+/* Proxy LEC knows about bridging */
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+#include <linux/if_bridge.h>
+#include "../bridge/br_private.h"
+static unsigned char bridge_ula_lec[] = {0x01, 0x80, 0xc2, 0x00, 0x00};
+#endif
+
+/* Modular too */
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include "lec.h"
+#include "lec_arpc.h"
+#include "resources.h"
+
+#if 0
+#define DPRINTK printk
+#else
+#define DPRINTK(format,args...)
+#endif
+
+struct net_bridge;
+extern struct net_bridge_fdb_entry *(*br_fdb_get_hook)(struct net_bridge *br,
+ unsigned char *addr);
+extern void (*br_fdb_put_hook)(struct net_bridge_fdb_entry *ent);
+
+#define DUMP_PACKETS 0 /* 0 = None,
+ * 1 = 30 first bytes
+ * 2 = Whole packet
+ */
+
+#define LEC_UNRES_QUE_LEN 8 /* number of tx packets to queue for a
+ single destination while waiting for SVC */
+
+static int lec_open(struct net_device *dev);
+static int lec_send_packet(struct sk_buff *skb, struct net_device *dev);
+static int lec_close(struct net_device *dev);
+static struct net_device_stats *lec_get_stats(struct net_device *dev);
+static void lec_init(struct net_device *dev);
+static struct lec_arp_table* lec_arp_find(struct lec_priv *priv,
+ unsigned char *mac_addr);
+static int lec_arp_remove(struct lec_priv *priv,
+ struct lec_arp_table *to_remove);
+/* LANE2 functions */
+static void lane2_associate_ind (struct net_device *dev, u8 *mac_address,
+ u8 *tlvs, u32 sizeoftlvs);
+static int lane2_resolve(struct net_device *dev, u8 *dst_mac, int force,
+ u8 **tlvs, u32 *sizeoftlvs);
+static int lane2_associate_req (struct net_device *dev, u8 *lan_dst,
+ u8 *tlvs, u32 sizeoftlvs);
+
+static struct lane2_ops lane2_ops = {
+ lane2_resolve, /* resolve, spec 3.1.3 */
+ lane2_associate_req, /* associate_req, spec 3.1.4 */
+ NULL /* associate indicator, spec 3.1.5 */
+};
+
+static unsigned char bus_mac[ETH_ALEN] = {0xff,0xff,0xff,0xff,0xff,0xff};
+
+/* Device structures */
+static struct net_device *dev_lec[MAX_LEC_ITF];
+
+/* This will be called from proc.c via function pointer */
+struct net_device *get_dev_lec(int itf)
+{
+ struct net_device *dev;
+
+ if (itf >= MAX_LEC_ITF)
+ return NULL;
+ rtnl_lock();
+ dev = dev_lec[itf];
+ if (dev)
+ dev_hold(dev);
+ rtnl_unlock();
+ return dev;
+}
+
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+static void lec_handle_bridge(struct sk_buff *skb, struct net_device *dev)
+{
+ struct ethhdr *eth;
+ char *buff;
+ struct lec_priv *priv;
+
+ /* Check if this is a BPDU. If so, ask zeppelin to send
+ * LE_TOPOLOGY_REQUEST with the same value of Topology Change bit
+ * as the Config BPDU has */
+ eth = (struct ethhdr *)skb->data;
+ buff = skb->data + skb->dev->hard_header_len;
+ if (*buff++ == 0x42 && *buff++ == 0x42 && *buff++ == 0x03) {
+ struct sk_buff *skb2;
+ struct atmlec_msg *mesg;
+
+ skb2 = alloc_skb(sizeof(struct atmlec_msg), GFP_ATOMIC);
+ if (skb2 == NULL) return;
+ skb2->len = sizeof(struct atmlec_msg);
+ mesg = (struct atmlec_msg *)skb2->data;
+ mesg->type = l_topology_change;
+ buff += 4;
+ mesg->content.normal.flag = *buff & 0x01; /* 0x01 is topology change */
+
+ priv = (struct lec_priv *)dev->priv;
+ atm_force_charge(priv->lecd, skb2->truesize);
+ skb_queue_tail(&priv->lecd->sk->receive_queue, skb2);
+ wake_up(&priv->lecd->sleep);
+ }
+
+ return;
+}
+#endif /* defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) */
+
+/*
+ * Modelled after tr_type_trans
+ * All multicast and ARE or STE frames go to BUS.
+ * Non source routed frames go by destination address.
+ * Last hop source routed frames go by destination address.
+ * Not last hop source routed frames go by _next_ route descriptor.
+ * Returns pointer to destination MAC address or fills in rdesc
+ * and returns NULL.
+ */
+#ifdef CONFIG_TR
+unsigned char *get_tr_dst(unsigned char *packet, unsigned char *rdesc)
+{
+ struct trh_hdr *trh;
+ int riflen, num_rdsc;
+
+ trh = (struct trh_hdr *)packet;
+ if (trh->daddr[0] & (uint8_t)0x80)
+ return bus_mac; /* multicast */
+
+ if (trh->saddr[0] & TR_RII) {
+ riflen = (ntohs(trh->rcf) & TR_RCF_LEN_MASK) >> 8;
+ if ((ntohs(trh->rcf) >> 13) != 0)
+ return bus_mac; /* ARE or STE */
+ }
+ else
+ return trh->daddr; /* not source routed */
+
+ if (riflen < 6)
+ return trh->daddr; /* last hop, source routed */
+
+ /* riflen is 6 or more, packet has more than one route descriptor */
+ num_rdsc = (riflen/2) - 1;
+ memset(rdesc, 0, ETH_ALEN);
+ /* offset 4 comes from LAN destination field in LE control frames */
+ if (trh->rcf & htons((uint16_t)TR_RCF_DIR_BIT))
+ memcpy(&rdesc[4], &trh->rseg[num_rdsc-2], sizeof(uint16_t));
+ else {
+ memcpy(&rdesc[4], &trh->rseg[1], sizeof(uint16_t));
+ rdesc[5] = ((ntohs(trh->rseg[0]) & 0x000f) | (rdesc[5] & 0xf0));
+ }
+
+ return NULL;
+}
+#endif /* CONFIG_TR */
+
+/*
+ * Open/initialize the netdevice. This is called (in the current kernel)
+ * sometime after booting when the 'ifconfig' program is run.
+ *
+ * This routine should set everything up anew at each open, even
+ * registers that "should" only need to be set once at boot, so that
+ * there is non-reboot way to recover if something goes wrong.
+ */
+
+static int
+lec_open(struct net_device *dev)
+{
+ struct lec_priv *priv = (struct lec_priv *)dev->priv;
+
+ netif_start_queue(dev);
+ memset(&priv->stats,0,sizeof(struct net_device_stats));
+
+ return 0;
+}
+
+static __inline__ void
+lec_send(struct atm_vcc *vcc, struct sk_buff *skb, struct lec_priv *priv)
+{
+ if (atm_may_send(vcc, skb->len)) {
+ atomic_add(skb->truesize, &vcc->sk->wmem_alloc);
+ ATM_SKB(skb)->vcc = vcc;
+ ATM_SKB(skb)->atm_options = vcc->atm_options;
+ priv->stats.tx_packets++;
+ priv->stats.tx_bytes += skb->len;
+ vcc->send(vcc, skb);
+ } else {
+ priv->stats.tx_dropped++;
+ dev_kfree_skb(skb);
+ }
+}
+
+static int
+lec_send_packet(struct sk_buff *skb, struct net_device *dev)
+{
+ struct sk_buff *skb2;
+ struct lec_priv *priv = (struct lec_priv *)dev->priv;
+ struct lecdatahdr_8023 *lec_h;
+ struct atm_vcc *send_vcc;
+ struct lec_arp_table *entry;
+ unsigned char *dst;
+ int min_frame_size;
+#ifdef CONFIG_TR
+ unsigned char rdesc[ETH_ALEN]; /* Token Ring route descriptor */
+#endif
+ int is_rdesc;
+#if DUMP_PACKETS > 0
+ char buf[300];
+ int i=0;
+#endif /* DUMP_PACKETS >0 */
+
+ DPRINTK("Lec_send_packet called\n");
+ if (!priv->lecd) {
+ printk("%s:No lecd attached\n",dev->name);
+ priv->stats.tx_errors++;
+ netif_stop_queue(dev);
+ return -EUNATCH;
+ }
+
+ DPRINTK("skbuff head:%lx data:%lx tail:%lx end:%lx\n",
+ (long)skb->head, (long)skb->data, (long)skb->tail,
+ (long)skb->end);
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ if (memcmp(skb->data, bridge_ula_lec, sizeof(bridge_ula_lec)) == 0)
+ lec_handle_bridge(skb, dev);
+#endif
+
+ /* Make sure we have room for lec_id */
+ if (skb_headroom(skb) < 2) {
+
+ DPRINTK("lec_send_packet: reallocating skb\n");
+ skb2 = skb_realloc_headroom(skb, LEC_HEADER_LEN);
+ kfree_skb(skb);
+ if (skb2 == NULL) return 0;
+ skb = skb2;
+ }
+ skb_push(skb, 2);
+
+ /* Put le header to place, works for TokenRing too */
+ lec_h = (struct lecdatahdr_8023*)skb->data;
+ lec_h->le_header = htons(priv->lecid);
+
+#ifdef CONFIG_TR
+ /* Ugly. Use this to realign Token Ring packets for
+ * e.g. PCA-200E driver. */
+ if (priv->is_trdev) {
+ skb2 = skb_realloc_headroom(skb, LEC_HEADER_LEN);
+ kfree_skb(skb);
+ if (skb2 == NULL) return 0;
+ skb = skb2;
+ }
+#endif
+
+#if DUMP_PACKETS > 0
+ printk("%s: send datalen:%ld lecid:%4.4x\n", dev->name,
+ skb->len, priv->lecid);
+#if DUMP_PACKETS >= 2
+ for(i=0;i<skb->len && i <99;i++) {
+ sprintf(buf+i*3,"%2.2x ",0xff&skb->data[i]);
+ }
+#elif DUMP_PACKETS >= 1
+ for(i=0;i<skb->len && i < 30;i++) {
+ sprintf(buf+i*3,"%2.2x ", 0xff&skb->data[i]);
+ }
+#endif /* DUMP_PACKETS >= 1 */
+ if (i==skb->len)
+ printk("%s\n",buf);
+ else
+ printk("%s...\n",buf);
+#endif /* DUMP_PACKETS > 0 */
+
+ /* Minimum ethernet-frame size */
+#ifdef CONFIG_TR
+ if (priv->is_trdev)
+ min_frame_size = LEC_MINIMUM_8025_SIZE;
+ else
+#endif
+ min_frame_size = LEC_MINIMUM_8023_SIZE;
+ if (skb->len < min_frame_size) {
+ if ((skb->len + skb_tailroom(skb)) < min_frame_size) {
+ skb2 = skb_copy_expand(skb, 0,
+ min_frame_size - skb->truesize, GFP_ATOMIC);
+ dev_kfree_skb(skb);
+ if (skb2 == NULL) {
+ priv->stats.tx_dropped++;
+ return 0;
+ }
+ skb = skb2;
+ }
+ skb_put(skb, min_frame_size - skb->len);
+ }
+
+ /* Send to right vcc */
+ is_rdesc = 0;
+ dst = lec_h->h_dest;
+#ifdef CONFIG_TR
+ if (priv->is_trdev) {
+ dst = get_tr_dst(skb->data+2, rdesc);
+ if (dst == NULL) {
+ dst = rdesc;
+ is_rdesc = 1;
+ }
+ }
+#endif
+ entry = NULL;
+ send_vcc = lec_arp_resolve(priv, dst, is_rdesc, &entry);
+ DPRINTK("%s:send_vcc:%p vcc_flags:%x, entry:%p\n", dev->name,
+ send_vcc, send_vcc?send_vcc->flags:0, entry);
+ if (!send_vcc || !test_bit(ATM_VF_READY,&send_vcc->flags)) {
+ if (entry && (entry->tx_wait.qlen < LEC_UNRES_QUE_LEN)) {
+ DPRINTK("%s:lec_send_packet: queuing packet, ", dev->name);
+ DPRINTK("MAC address 0x%02x:%02x:%02x:%02x:%02x:%02x\n",
+ lec_h->h_dest[0], lec_h->h_dest[1], lec_h->h_dest[2],
+ lec_h->h_dest[3], lec_h->h_dest[4], lec_h->h_dest[5]);
+ skb_queue_tail(&entry->tx_wait, skb);
+ } else {
+ DPRINTK("%s:lec_send_packet: tx queue full or no arp entry, dropping, ", dev->name);
+ DPRINTK("MAC address 0x%02x:%02x:%02x:%02x:%02x:%02x\n",
+ lec_h->h_dest[0], lec_h->h_dest[1], lec_h->h_dest[2],
+ lec_h->h_dest[3], lec_h->h_dest[4], lec_h->h_dest[5]);
+ priv->stats.tx_dropped++;
+ dev_kfree_skb(skb);
+ }
+ return 0;
+ }
+
+#if DUMP_PACKETS > 0
+ printk("%s:sending to vpi:%d vci:%d\n", dev->name,
+ send_vcc->vpi, send_vcc->vci);
+#endif /* DUMP_PACKETS > 0 */
+
+ while (entry && (skb2 = skb_dequeue(&entry->tx_wait))) {
+ DPRINTK("lec.c: emptying tx queue, ");
+ DPRINTK("MAC address 0x%02x:%02x:%02x:%02x:%02x:%02x\n",
+ lec_h->h_dest[0], lec_h->h_dest[1], lec_h->h_dest[2],
+ lec_h->h_dest[3], lec_h->h_dest[4], lec_h->h_dest[5]);
+ lec_send(send_vcc, skb2, priv);
+ }
+
+ lec_send(send_vcc, skb, priv);
+#if 0
+ /* Should we wait for card's device driver to notify us? */
+ dev->tbusy=0;
+#endif
+ return 0;
+}
+
+/* The inverse routine to net_open(). */
+static int
+lec_close(struct net_device *dev)
+{
+ netif_stop_queue(dev);
+ return 0;
+}
+
+/*
+ * Get the current statistics.
+ * This may be called with the card open or closed.
+ */
+static struct net_device_stats *
+lec_get_stats(struct net_device *dev)
+{
+ return &((struct lec_priv *)dev->priv)->stats;
+}
+
+static int
+lec_atm_send(struct atm_vcc *vcc, struct sk_buff *skb)
+{
+ struct net_device *dev = (struct net_device*)vcc->proto_data;
+ struct lec_priv *priv = (struct lec_priv*)dev->priv;
+ struct atmlec_msg *mesg;
+ struct lec_arp_table *entry;
+ int i;
+ char *tmp; /* FIXME */
+
+ atomic_sub(skb->truesize, &vcc->sk->wmem_alloc);
+ mesg = (struct atmlec_msg *)skb->data;
+ tmp = skb->data;
+ tmp += sizeof(struct atmlec_msg);
+ DPRINTK("%s: msg from zeppelin:%d\n", dev->name, mesg->type);
+ switch(mesg->type) {
+ case l_set_mac_addr:
+ for (i=0;i<6;i++) {
+ dev->dev_addr[i] = mesg->content.normal.mac_addr[i];
+ }
+ break;
+ case l_del_mac_addr:
+ for(i=0;i<6;i++) {
+ dev->dev_addr[i] = 0;
+ }
+ break;
+ case l_addr_delete:
+ lec_addr_delete(priv, mesg->content.normal.atm_addr,
+ mesg->content.normal.flag);
+ break;
+ case l_topology_change:
+ priv->topology_change = mesg->content.normal.flag;
+ break;
+ case l_flush_complete:
+ lec_flush_complete(priv, mesg->content.normal.flag);
+ break;
+ case l_narp_req: /* LANE2: see 7.1.35 in the lane2 spec */
+ entry = lec_arp_find(priv, mesg->content.normal.mac_addr);
+ lec_arp_remove(priv, entry);
+
+ if (mesg->content.normal.no_source_le_narp)
+ break;
+ /* FALL THROUGH */
+ case l_arp_update:
+ lec_arp_update(priv, mesg->content.normal.mac_addr,
+ mesg->content.normal.atm_addr,
+ mesg->content.normal.flag,
+ mesg->content.normal.targetless_le_arp);
+ DPRINTK("lec: in l_arp_update\n");
+ if (mesg->sizeoftlvs != 0) { /* LANE2 3.1.5 */
+ DPRINTK("lec: LANE2 3.1.5, got tlvs, size %d\n", mesg->sizeoftlvs);
+ lane2_associate_ind(dev,
+ mesg->content.normal.mac_addr,
+ tmp, mesg->sizeoftlvs);
+ }
+ break;
+ case l_config:
+ priv->maximum_unknown_frame_count =
+ mesg->content.config.maximum_unknown_frame_count;
+ priv->max_unknown_frame_time =
+ (mesg->content.config.max_unknown_frame_time*HZ);
+ priv->max_retry_count =
+ mesg->content.config.max_retry_count;
+ priv->aging_time = (mesg->content.config.aging_time*HZ);
+ priv->forward_delay_time =
+ (mesg->content.config.forward_delay_time*HZ);
+ priv->arp_response_time =
+ (mesg->content.config.arp_response_time*HZ);
+ priv->flush_timeout = (mesg->content.config.flush_timeout*HZ);
+ priv->path_switching_delay =
+ (mesg->content.config.path_switching_delay*HZ);
+ priv->lane_version = mesg->content.config.lane_version; /* LANE2 */
+ priv->lane2_ops = NULL;
+ if (priv->lane_version > 1)
+ priv->lane2_ops = &lane2_ops;
+ if (dev->change_mtu(dev, mesg->content.config.mtu))
+ printk("%s: change_mtu to %d failed\n", dev->name,
+ mesg->content.config.mtu);
+ priv->is_proxy = mesg->content.config.is_proxy;
+ break;
+ case l_flush_tran_id:
+ lec_set_flush_tran_id(priv, mesg->content.normal.atm_addr,
+ mesg->content.normal.flag);
+ break;
+ case l_set_lecid:
+ priv->lecid=(unsigned short)(0xffff&mesg->content.normal.flag);
+ break;
+ case l_should_bridge: {
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ struct net_bridge_fdb_entry *f;
+
+ DPRINTK("%s: bridge zeppelin asks about 0x%02x:%02x:%02x:%02x:%02x:%02x\n",
+ dev->name,
+ mesg->content.proxy.mac_addr[0], mesg->content.proxy.mac_addr[1],
+ mesg->content.proxy.mac_addr[2], mesg->content.proxy.mac_addr[3],
+ mesg->content.proxy.mac_addr[4], mesg->content.proxy.mac_addr[5]);
+
+ if (br_fdb_get_hook == NULL || dev->br_port == NULL)
+ break;
+
+ f = br_fdb_get_hook(dev->br_port->br, mesg->content.proxy.mac_addr);
+ if (f != NULL &&
+ f->dst->dev != dev &&
+ f->dst->state == BR_STATE_FORWARDING) {
+ /* hit from bridge table, send LE_ARP_RESPONSE */
+ struct sk_buff *skb2;
+
+ DPRINTK("%s: entry found, responding to zeppelin\n", dev->name);
+ skb2 = alloc_skb(sizeof(struct atmlec_msg), GFP_ATOMIC);
+ if (skb2 == NULL) {
+ br_fdb_put_hook(f);
+ break;
+ }
+ skb2->len = sizeof(struct atmlec_msg);
+ memcpy(skb2->data, mesg, sizeof(struct atmlec_msg));
+ atm_force_charge(priv->lecd, skb2->truesize);
+ skb_queue_tail(&priv->lecd->sk->receive_queue, skb2);
+ wake_up(&priv->lecd->sleep);
+ }
+ if (f != NULL) br_fdb_put_hook(f);
+#endif /* defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) */
+ }
+ break;
+ default:
+ printk("%s: Unknown message type %d\n", dev->name, mesg->type);
+ dev_kfree_skb(skb);
+ return -EINVAL;
+ }
+ dev_kfree_skb(skb);
+ return 0;
+}
+
+static void
+lec_atm_close(struct atm_vcc *vcc)
+{
+ struct sk_buff *skb;
+ struct net_device *dev = (struct net_device *)vcc->proto_data;
+ struct lec_priv *priv = (struct lec_priv *)dev->priv;
+
+ priv->lecd = NULL;
+ /* Do something needful? */
+
+ netif_stop_queue(dev);
+ lec_arp_destroy(priv);
+
+ if (skb_peek(&vcc->sk->receive_queue))
+ printk("%s lec_atm_close: closing with messages pending\n",
+ dev->name);
+ while ((skb = skb_dequeue(&vcc->sk->receive_queue))) {
+ atm_return(vcc, skb->truesize);
+ dev_kfree_skb(skb);
+ }
+
+ printk("%s: Shut down!\n", dev->name);
+ MOD_DEC_USE_COUNT;
+}
+
+static struct atmdev_ops lecdev_ops = {
+ .close = lec_atm_close,
+ .send = lec_atm_send
+};
+
+static struct atm_dev lecatm_dev = {
+ .ops = &lecdev_ops,
+ .type = "lec",
+ .number = 999,
+ .lock = SPIN_LOCK_UNLOCKED
+};
+
+/*
+ * LANE2: new argument struct sk_buff *data contains
+ * the LE_ARP based TLVs introduced in the LANE2 spec
+ */
+int
+send_to_lecd(struct lec_priv *priv, atmlec_msg_type type,
+ unsigned char *mac_addr, unsigned char *atm_addr,
+ struct sk_buff *data)
+{
+ struct sk_buff *skb;
+ struct atmlec_msg *mesg;
+
+ if (!priv || !priv->lecd) {
+ return -1;
+ }
+ skb = alloc_skb(sizeof(struct atmlec_msg), GFP_ATOMIC);
+ if (!skb)
+ return -1;
+ skb->len = sizeof(struct atmlec_msg);
+ mesg = (struct atmlec_msg *)skb->data;
+ memset(mesg, 0, sizeof(struct atmlec_msg));
+ mesg->type = type;
+ if (data != NULL)
+ mesg->sizeoftlvs = data->len;
+ if (mac_addr)
+ memcpy(&mesg->content.normal.mac_addr, mac_addr, ETH_ALEN);
+ else
+ mesg->content.normal.targetless_le_arp = 1;
+ if (atm_addr)
+ memcpy(&mesg->content.normal.atm_addr, atm_addr, ATM_ESA_LEN);
+
+ atm_force_charge(priv->lecd, skb->truesize);
+ skb_queue_tail(&priv->lecd->sk->receive_queue, skb);
+ wake_up(&priv->lecd->sleep);
+
+ if (data != NULL) {
+ DPRINTK("lec: about to send %d bytes of data\n", data->len);
+ atm_force_charge(priv->lecd, data->truesize);
+ skb_queue_tail(&priv->lecd->sk->receive_queue, data);
+ wake_up(&priv->lecd->sleep);
+ }
+
+ return 0;
+}
+
+/* shamelessly stolen from drivers/net/net_init.c */
+static int lec_change_mtu(struct net_device *dev, int new_mtu)
+{
+ if ((new_mtu < 68) || (new_mtu > 18190))
+ return -EINVAL;
+ dev->mtu = new_mtu;
+ return 0;
+}
+
+static void lec_set_multicast_list(struct net_device *dev)
+{
+ /* by default, all multicast frames arrive over the bus.
+ * eventually support selective multicast service
+ */
+ return;
+}
+
+static void
+lec_init(struct net_device *dev)
+{
+ dev->change_mtu = lec_change_mtu;
+ dev->open = lec_open;
+ dev->stop = lec_close;
+ dev->hard_start_xmit = lec_send_packet;
+
+ dev->get_stats = lec_get_stats;
+ dev->set_multicast_list = lec_set_multicast_list;
+ dev->do_ioctl = NULL;
+ printk("%s: Initialized!\n",dev->name);
+ return;
+}
+
+static unsigned char lec_ctrl_magic[] = {
+ 0xff,
+ 0x00,
+ 0x01,
+ 0x01 };
+
+void
+lec_push(struct atm_vcc *vcc, struct sk_buff *skb)
+{
+ struct net_device *dev = (struct net_device *)vcc->proto_data;
+ struct lec_priv *priv = (struct lec_priv *)dev->priv;
+
+#if DUMP_PACKETS >0
+ int i=0;
+ char buf[300];
+
+ printk("%s: lec_push vcc vpi:%d vci:%d\n", dev->name,
+ vcc->vpi, vcc->vci);
+#endif
+ if (!skb) {
+ DPRINTK("%s: null skb\n",dev->name);
+ lec_vcc_close(priv, vcc);
+ return;
+ }
+#if DUMP_PACKETS > 0
+ printk("%s: rcv datalen:%ld lecid:%4.4x\n", dev->name,
+ skb->len, priv->lecid);
+#if DUMP_PACKETS >= 2
+ for(i=0;i<skb->len && i <99;i++) {
+ sprintf(buf+i*3,"%2.2x ",0xff&skb->data[i]);
+ }
+#elif DUMP_PACKETS >= 1
+ for(i=0;i<skb->len && i < 30;i++) {
+ sprintf(buf+i*3,"%2.2x ", 0xff&skb->data[i]);
+ }
+#endif /* DUMP_PACKETS >= 1 */
+ if (i==skb->len)
+ printk("%s\n",buf);
+ else
+ printk("%s...\n",buf);
+#endif /* DUMP_PACKETS > 0 */
+ if (memcmp(skb->data, lec_ctrl_magic, 4) ==0) { /* Control frame, to daemon*/
+ DPRINTK("%s: To daemon\n",dev->name);
+ skb_queue_tail(&vcc->sk->receive_queue, skb);
+ wake_up(&vcc->sleep);
+ } else { /* Data frame, queue to protocol handlers */
+ unsigned char *dst;
+
+ atm_return(vcc,skb->truesize);
+ if (*(uint16_t *)skb->data == htons(priv->lecid) ||
+ !priv->lecd) {
+ /* Probably looping back, or if lecd is missing,
+ lecd has gone down */
+ DPRINTK("Ignoring loopback frame...\n");
+ dev_kfree_skb(skb);
+ return;
+ }
+#ifdef CONFIG_TR
+ if (priv->is_trdev) dst = ((struct lecdatahdr_8025 *)skb->data)->h_dest;
+ else
+#endif
+ dst = ((struct lecdatahdr_8023 *)skb->data)->h_dest;
+
+ if (!(dst[0]&0x01) && /* Never filter Multi/Broadcast */
+ !priv->is_proxy && /* Proxy wants all the packets */
+ memcmp(dst, dev->dev_addr, dev->addr_len)) {
+ dev_kfree_skb(skb);
+ return;
+ }
+ if (priv->lec_arp_empty_ones) {
+ lec_arp_check_empties(priv, vcc, skb);
+ }
+ skb->dev = dev;
+ skb_pull(skb, 2); /* skip lec_id */
+#ifdef CONFIG_TR
+ if (priv->is_trdev) skb->protocol = tr_type_trans(skb, dev);
+ else
+#endif
+ skb->protocol = eth_type_trans(skb, dev);
+ priv->stats.rx_packets++;
+ priv->stats.rx_bytes += skb->len;
+ memset(ATM_SKB(skb), 0, sizeof(struct atm_skb_data));
+ netif_rx(skb);
+ }
+}
+
+int
+lec_vcc_attach(struct atm_vcc *vcc, void *arg)
+{
+ int bytes_left;
+ struct atmlec_ioc ioc_data;
+
+ /* Lecd must be up in this case */
+ bytes_left = copy_from_user(&ioc_data, arg, sizeof(struct atmlec_ioc));
+ if (bytes_left != 0) {
+ printk("lec: lec_vcc_attach, copy from user failed for %d bytes\n",
+ bytes_left);
+ }
+ if (ioc_data.dev_num < 0 || ioc_data.dev_num >= MAX_LEC_ITF ||
+ !dev_lec[ioc_data.dev_num])
+ return -EINVAL;
+ lec_vcc_added(dev_lec[ioc_data.dev_num]->priv,
+ &ioc_data, vcc, vcc->push);
+ vcc->proto_data = dev_lec[ioc_data.dev_num];
+ vcc->push = lec_push;
+ return 0;
+}
+
+int
+lec_mcast_attach(struct atm_vcc *vcc, int arg)
+{
+ if (arg <0 || arg >= MAX_LEC_ITF || !dev_lec[arg])
+ return -EINVAL;
+ vcc->proto_data = dev_lec[arg];
+ return (lec_mcast_make((struct lec_priv*)dev_lec[arg]->priv, vcc));
+}
+
+/* Initialize device. */
+int
+lecd_attach(struct atm_vcc *vcc, int arg)
+{
+ int i;
+ struct lec_priv *priv;
+
+ if (arg<0)
+ i = 0;
+ else
+ i = arg;
+#ifdef CONFIG_TR
+ if (arg >= MAX_LEC_ITF)
+ return -EINVAL;
+#else /* Reserve the top NUM_TR_DEVS for TR */
+ if (arg >= (MAX_LEC_ITF-NUM_TR_DEVS))
+ return -EINVAL;
+#endif
+ if (!dev_lec[i]) {
+ int is_trdev, size;
+
+ is_trdev = 0;
+ if (i >= (MAX_LEC_ITF - NUM_TR_DEVS))
+ is_trdev = 1;
+
+ size = sizeof(struct lec_priv);
+#ifdef CONFIG_TR
+ if (is_trdev)
+ dev_lec[i] = alloc_trdev(size);
+ else
+#endif
+ dev_lec[i] = alloc_etherdev(size);
+ if (!dev_lec[i])
+ return -ENOMEM;
+ snprintf(dev_lec[i]->name, IFNAMSIZ, "lec%d", i);
+ if (register_netdev(dev_lec[i])) {
+ kfree(dev_lec[i]);
+ return -EINVAL;
+ }
+
+ priv = dev_lec[i]->priv;
+ priv->is_trdev = is_trdev;
+ lec_init(dev_lec[i]);
+ } else {
+ priv = dev_lec[i]->priv;
+ if (priv->lecd)
+ return -EADDRINUSE;
+ }
+ lec_arp_init(priv);
+ priv->itfnum = i; /* LANE2 addition */
+ priv->lecd = vcc;
+ vcc->dev = &lecatm_dev;
+ vcc_insert_socket(vcc->sk);
+
+ vcc->proto_data = dev_lec[i];
+ set_bit(ATM_VF_META,&vcc->flags);
+ set_bit(ATM_VF_READY,&vcc->flags);
+
+ /* Set default values to these variables */
+ priv->maximum_unknown_frame_count = 1;
+ priv->max_unknown_frame_time = (1*HZ);
+ priv->vcc_timeout_period = (1200*HZ);
+ priv->max_retry_count = 1;
+ priv->aging_time = (300*HZ);
+ priv->forward_delay_time = (15*HZ);
+ priv->topology_change = 0;
+ priv->arp_response_time = (1*HZ);
+ priv->flush_timeout = (4*HZ);
+ priv->path_switching_delay = (6*HZ);
+
+ if (dev_lec[i]->flags & IFF_UP) {
+ netif_start_queue(dev_lec[i]);
+ }
+ MOD_INC_USE_COUNT;
+ return i;
+}
+
+static struct atm_lane_ops __atm_lane_ops =
+{
+ .lecd_attach = lecd_attach,
+ .mcast_attach = lec_mcast_attach,
+ .vcc_attach = lec_vcc_attach,
+ .get_lec = get_dev_lec,
+ .owner = THIS_MODULE
+};
+
+static int __init lane_module_init(void)
+{
+ atm_lane_ops_set(&__atm_lane_ops);
+ printk("lec.c: " __DATE__ " " __TIME__ " initialized\n");
+ return 0;
+}
+
+static void __exit lane_module_cleanup(void)
+{
+ int i;
+ struct lec_priv *priv;
+
+ atm_lane_ops_set(NULL);
+
+ for (i = 0; i < MAX_LEC_ITF; i++) {
+ if (dev_lec[i] != NULL) {
+ priv = (struct lec_priv *)dev_lec[i]->priv;
+#if defined(CONFIG_TR)
+ if (priv->is_trdev)
+ unregister_trdev(dev_lec[i]);
+ else
+#endif
+ unregister_netdev(dev_lec[i]);
+ kfree(dev_lec[i]);
+ dev_lec[i] = NULL;
+ }
+ }
+
+ return;
+}
+
+module_init(lane_module_init);
+module_exit(lane_module_cleanup);
+
+/*
+ * LANE2: 3.1.3, LE_RESOLVE.request
+ * Non force allocates memory and fills in *tlvs, fills in *sizeoftlvs.
+ * If sizeoftlvs == NULL the default TLVs associated with with this
+ * lec will be used.
+ * If dst_mac == NULL, targetless LE_ARP will be sent
+ */
+static int lane2_resolve(struct net_device *dev, u8 *dst_mac, int force,
+ u8 **tlvs, u32 *sizeoftlvs)
+{
+ struct lec_priv *priv = (struct lec_priv *)dev->priv;
+ struct lec_arp_table *table;
+ struct sk_buff *skb;
+ int retval;
+
+ if (force == 0) {
+ table = lec_arp_find(priv, dst_mac);
+ if(table == NULL)
+ return -1;
+
+ *tlvs = kmalloc(table->sizeoftlvs, GFP_KERNEL);
+ if (*tlvs == NULL)
+ return -1;
+
+ memcpy(*tlvs, table->tlvs, table->sizeoftlvs);
+ *sizeoftlvs = table->sizeoftlvs;
+
+ return 0;
+ }
+
+ if (sizeoftlvs == NULL)
+ retval = send_to_lecd(priv, l_arp_xmt, dst_mac, NULL, NULL);
+
+ else {
+ skb = alloc_skb(*sizeoftlvs, GFP_ATOMIC);
+ if (skb == NULL)
+ return -1;
+ skb->len = *sizeoftlvs;
+ memcpy(skb->data, *tlvs, *sizeoftlvs);
+ retval = send_to_lecd(priv, l_arp_xmt, dst_mac, NULL, skb);
+ }
+ return retval;
+}
+
+
+/*
+ * LANE2: 3.1.4, LE_ASSOCIATE.request
+ * Associate the *tlvs with the *lan_dst address.
+ * Will overwrite any previous association
+ * Returns 1 for success, 0 for failure (out of memory)
+ *
+ */
+static int lane2_associate_req (struct net_device *dev, u8 *lan_dst,
+ u8 *tlvs, u32 sizeoftlvs)
+{
+ int retval;
+ struct sk_buff *skb;
+ struct lec_priv *priv = (struct lec_priv*)dev->priv;
+
+ if ( memcmp(lan_dst, dev->dev_addr, ETH_ALEN) != 0 )
+ return (0); /* not our mac address */
+
+ kfree(priv->tlvs); /* NULL if there was no previous association */
+
+ priv->tlvs = kmalloc(sizeoftlvs, GFP_KERNEL);
+ if (priv->tlvs == NULL)
+ return (0);
+ priv->sizeoftlvs = sizeoftlvs;
+ memcpy(priv->tlvs, tlvs, sizeoftlvs);
+
+ skb = alloc_skb(sizeoftlvs, GFP_ATOMIC);
+ if (skb == NULL)
+ return 0;
+ skb->len = sizeoftlvs;
+ memcpy(skb->data, tlvs, sizeoftlvs);
+ retval = send_to_lecd(priv, l_associate_req, NULL, NULL, skb);
+ if (retval != 0)
+ printk("lec.c: lane2_associate_req() failed\n");
+ /* If the previous association has changed we must
+ * somehow notify other LANE entities about the change
+ */
+ return (1);
+}
+
+/*
+ * LANE2: 3.1.5, LE_ASSOCIATE.indication
+ *
+ */
+static void lane2_associate_ind (struct net_device *dev, u8 *mac_addr,
+ u8 *tlvs, u32 sizeoftlvs)
+{
+#if 0
+ int i = 0;
+#endif
+ struct lec_priv *priv = (struct lec_priv *)dev->priv;
+#if 0 /* Why have the TLVs in LE_ARP entries since we do not use them? When you
+ uncomment this code, make sure the TLVs get freed when entry is killed */
+ struct lec_arp_table *entry = lec_arp_find(priv, mac_addr);
+
+ if (entry == NULL)
+ return; /* should not happen */
+
+ kfree(entry->tlvs);
+
+ entry->tlvs = kmalloc(sizeoftlvs, GFP_KERNEL);
+ if (entry->tlvs == NULL)
+ return;
+
+ entry->sizeoftlvs = sizeoftlvs;
+ memcpy(entry->tlvs, tlvs, sizeoftlvs);
+#endif
+#if 0
+ printk("lec.c: lane2_associate_ind()\n");
+ printk("dump of tlvs, sizeoftlvs=%d\n", sizeoftlvs);
+ while (i < sizeoftlvs)
+ printk("%02x ", tlvs[i++]);
+
+ printk("\n");
+#endif
+
+ /* tell MPOA about the TLVs we saw */
+ if (priv->lane2_ops && priv->lane2_ops->associate_indicator) {
+ priv->lane2_ops->associate_indicator(dev, mac_addr,
+ tlvs, sizeoftlvs);
+ }
+ return;
+}
+
+/*
+ * Here starts what used to lec_arpc.c
+ *
+ * lec_arpc.c was added here when making
+ * lane client modular. October 1997
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <asm/param.h>
+#include <asm/atomic.h>
+#include <linux/inetdevice.h>
+#include <net/route.h>
+
+
+#if 0
+#define DPRINTK(format,args...)
+/*
+#define DPRINTK printk
+*/
+#endif
+#define DEBUG_ARP_TABLE 0
+
+#define LEC_ARP_REFRESH_INTERVAL (3*HZ)
+
+static void lec_arp_check_expire(unsigned long data);
+static void lec_arp_expire_arp(unsigned long data);
+void dump_arp_table(struct lec_priv *priv);
+
+/*
+ * Arp table funcs
+ */
+
+#define HASH(ch) (ch & (LEC_ARP_TABLE_SIZE -1))
+
+static __inline__ void
+lec_arp_get(struct lec_priv *priv)
+{
+ atomic_inc(&priv->lec_arp_users);
+}
+
+static __inline__ void
+lec_arp_put(struct lec_priv *priv)
+{
+ atomic_dec(&priv->lec_arp_users);
+}
+
+/*
+ * Initialization of arp-cache
+ */
+void
+lec_arp_init(struct lec_priv *priv)
+{
+ unsigned short i;
+
+ for (i=0;i<LEC_ARP_TABLE_SIZE;i++) {
+ priv->lec_arp_tables[i] = NULL;
+ }
+ spin_lock_init(&priv->lec_arp_lock);
+ init_timer(&priv->lec_arp_timer);
+ priv->lec_arp_timer.expires = jiffies+LEC_ARP_REFRESH_INTERVAL;
+ priv->lec_arp_timer.data = (unsigned long)priv;
+ priv->lec_arp_timer.function = lec_arp_check_expire;
+ add_timer(&priv->lec_arp_timer);
+}
+
+void
+lec_arp_clear_vccs(struct lec_arp_table *entry)
+{
+ if (entry->vcc) {
+ entry->vcc->push = entry->old_push;
+#if 0 /* August 6, 1998 */
+ set_bit(ATM_VF_RELEASED,&entry->vcc->flags);
+ clear_bit(ATM_VF_READY,&entry->vcc->flags);
+ entry->vcc->push(entry->vcc, NULL);
+#endif
+ vcc_release_async(entry->vcc, -EPIPE);
+ entry->vcc = NULL;
+ }
+ if (entry->recv_vcc) {
+ entry->recv_vcc->push = entry->old_recv_push;
+#if 0
+ set_bit(ATM_VF_RELEASED,&entry->recv_vcc->flags);
+ clear_bit(ATM_VF_READY,&entry->recv_vcc->flags);
+ entry->recv_vcc->push(entry->recv_vcc, NULL);
+#endif
+ vcc_release_async(entry->recv_vcc, -EPIPE);
+ entry->recv_vcc = NULL;
+ }
+}
+
+/*
+ * Insert entry to lec_arp_table
+ * LANE2: Add to the end of the list to satisfy 8.1.13
+ */
+static inline void
+lec_arp_add(struct lec_priv *priv, struct lec_arp_table *to_add)
+{
+ unsigned long flags;
+ unsigned short place;
+ struct lec_arp_table *tmp;
+
+ spin_lock_irqsave(&priv->lec_arp_lock, flags);
+
+ place = HASH(to_add->mac_addr[ETH_ALEN-1]);
+ tmp = priv->lec_arp_tables[place];
+ to_add->next = NULL;
+ if (tmp == NULL)
+ priv->lec_arp_tables[place] = to_add;
+
+ else { /* add to the end */
+ while (tmp->next)
+ tmp = tmp->next;
+ tmp->next = to_add;
+ }
+
+ spin_unlock_irqrestore(&priv->lec_arp_lock, flags);
+
+ DPRINTK("LEC_ARP: Added entry:%2.2x %2.2x %2.2x %2.2x %2.2x %2.2x\n",
+ 0xff&to_add->mac_addr[0], 0xff&to_add->mac_addr[1],
+ 0xff&to_add->mac_addr[2], 0xff&to_add->mac_addr[3],
+ 0xff&to_add->mac_addr[4], 0xff&to_add->mac_addr[5]);
+}
+
+/*
+ * Remove entry from lec_arp_table
+ */
+static int
+lec_arp_remove(struct lec_priv *priv,
+ struct lec_arp_table *to_remove)
+{
+ unsigned long flags;
+ unsigned short place;
+ struct lec_arp_table *tmp;
+ int remove_vcc=1;
+
+ spin_lock_irqsave(&priv->lec_arp_lock, flags);
+
+ if (!to_remove) {
+ spin_unlock_irqrestore(&priv->lec_arp_lock, flags);
+ return -1;
+ }
+ place = HASH(to_remove->mac_addr[ETH_ALEN-1]);
+ tmp = priv->lec_arp_tables[place];
+ if (tmp == to_remove) {
+ priv->lec_arp_tables[place] = tmp->next;
+ } else {
+ while(tmp && tmp->next != to_remove) {
+ tmp = tmp->next;
+ }
+ if (!tmp) {/* Entry was not found */
+ spin_unlock_irqrestore(&priv->lec_arp_lock, flags);
+ return -1;
+ }
+ }
+ tmp->next = to_remove->next;
+ del_timer(&to_remove->timer);
+
+ /* If this is the only MAC connected to this VCC, also tear down
+ the VCC */
+ if (to_remove->status >= ESI_FLUSH_PENDING) {
+ /*
+ * ESI_FLUSH_PENDING, ESI_FORWARD_DIRECT
+ */
+ for(place=0;place<LEC_ARP_TABLE_SIZE;place++) {
+ for(tmp = priv->lec_arp_tables[place]; tmp != NULL; tmp = tmp->next) {
+ if (memcmp(tmp->atm_addr, to_remove->atm_addr,
+ ATM_ESA_LEN)==0) {
+ remove_vcc=0;
+ break;
+ }
+ }
+ }
+ if (remove_vcc)
+ lec_arp_clear_vccs(to_remove);
+ }
+ skb_queue_purge(&to_remove->tx_wait); /* FIXME: good place for this? */
+
+ spin_unlock_irqrestore(&priv->lec_arp_lock, flags);
+
+ DPRINTK("LEC_ARP: Removed entry:%2.2x %2.2x %2.2x %2.2x %2.2x %2.2x\n",
+ 0xff&to_remove->mac_addr[0], 0xff&to_remove->mac_addr[1],
+ 0xff&to_remove->mac_addr[2], 0xff&to_remove->mac_addr[3],
+ 0xff&to_remove->mac_addr[4], 0xff&to_remove->mac_addr[5]);
+ return 0;
+}
+
+#if DEBUG_ARP_TABLE
+static char*
+get_status_string(unsigned char st)
+{
+ switch(st) {
+ case ESI_UNKNOWN:
+ return "ESI_UNKNOWN";
+ case ESI_ARP_PENDING:
+ return "ESI_ARP_PENDING";
+ case ESI_VC_PENDING:
+ return "ESI_VC_PENDING";
+ case ESI_FLUSH_PENDING:
+ return "ESI_FLUSH_PENDING";
+ case ESI_FORWARD_DIRECT:
+ return "ESI_FORWARD_DIRECT";
+ default:
+ return "<UNKNOWN>";
+ }
+}
+#endif
+
+void
+dump_arp_table(struct lec_priv *priv)
+{
+#if DEBUG_ARP_TABLE
+ int i,j, offset;
+ struct lec_arp_table *rulla;
+ char buf[1024];
+ struct lec_arp_table **lec_arp_tables =
+ (struct lec_arp_table **)priv->lec_arp_tables;
+ struct lec_arp_table *lec_arp_empty_ones =
+ (struct lec_arp_table *)priv->lec_arp_empty_ones;
+ struct lec_arp_table *lec_no_forward =
+ (struct lec_arp_table *)priv->lec_no_forward;
+ struct lec_arp_table *mcast_fwds = priv->mcast_fwds;
+
+
+ printk("Dump %p:\n",priv);
+ for (i=0;i<LEC_ARP_TABLE_SIZE;i++) {
+ rulla = lec_arp_tables[i];
+ offset = 0;
+ offset += sprintf(buf,"%d: %p\n",i, rulla);
+ while (rulla) {
+ offset += sprintf(buf+offset,"Mac:");
+ for(j=0;j<ETH_ALEN;j++) {
+ offset+=sprintf(buf+offset,
+ "%2.2x ",
+ rulla->mac_addr[j]&0xff);
+ }
+ offset +=sprintf(buf+offset,"Atm:");
+ for(j=0;j<ATM_ESA_LEN;j++) {
+ offset+=sprintf(buf+offset,
+ "%2.2x ",
+ rulla->atm_addr[j]&0xff);
+ }
+ offset+=sprintf(buf+offset,
+ "Vcc vpi:%d vci:%d, Recv_vcc vpi:%d vci:%d Last_used:%lx, Timestamp:%lx, No_tries:%d ",
+ rulla->vcc?rulla->vcc->vpi:0,
+ rulla->vcc?rulla->vcc->vci:0,
+ rulla->recv_vcc?rulla->recv_vcc->vpi:0,
+ rulla->recv_vcc?rulla->recv_vcc->vci:0,
+ rulla->last_used,
+ rulla->timestamp, rulla->no_tries);
+ offset+=sprintf(buf+offset,
+ "Flags:%x, Packets_flooded:%x, Status: %s ",
+ rulla->flags, rulla->packets_flooded,
+ get_status_string(rulla->status));
+ offset+=sprintf(buf+offset,"->%p\n",rulla->next);
+ rulla = rulla->next;
+ }
+ printk("%s",buf);
+ }
+ rulla = lec_no_forward;
+ if (rulla)
+ printk("No forward\n");
+ while(rulla) {
+ offset=0;
+ offset += sprintf(buf+offset,"Mac:");
+ for(j=0;j<ETH_ALEN;j++) {
+ offset+=sprintf(buf+offset,"%2.2x ",
+ rulla->mac_addr[j]&0xff);
+ }
+ offset +=sprintf(buf+offset,"Atm:");
+ for(j=0;j<ATM_ESA_LEN;j++) {
+ offset+=sprintf(buf+offset,"%2.2x ",
+ rulla->atm_addr[j]&0xff);
+ }
+ offset+=sprintf(buf+offset,
+ "Vcc vpi:%d vci:%d, Recv_vcc vpi:%d vci:%d Last_used:%lx, Timestamp:%lx, No_tries:%d ",
+ rulla->vcc?rulla->vcc->vpi:0,
+ rulla->vcc?rulla->vcc->vci:0,
+ rulla->recv_vcc?rulla->recv_vcc->vpi:0,
+ rulla->recv_vcc?rulla->recv_vcc->vci:0,
+ rulla->last_used,
+ rulla->timestamp, rulla->no_tries);
+ offset+=sprintf(buf+offset,
+ "Flags:%x, Packets_flooded:%x, Status: %s ",
+ rulla->flags, rulla->packets_flooded,
+ get_status_string(rulla->status));
+ offset+=sprintf(buf+offset,"->%lx\n",(long)rulla->next);
+ rulla = rulla->next;
+ printk("%s",buf);
+ }
+ rulla = lec_arp_empty_ones;
+ if (rulla)
+ printk("Empty ones\n");
+ while(rulla) {
+ offset=0;
+ offset += sprintf(buf+offset,"Mac:");
+ for(j=0;j<ETH_ALEN;j++) {
+ offset+=sprintf(buf+offset,"%2.2x ",
+ rulla->mac_addr[j]&0xff);
+ }
+ offset +=sprintf(buf+offset,"Atm:");
+ for(j=0;j<ATM_ESA_LEN;j++) {
+ offset+=sprintf(buf+offset,"%2.2x ",
+ rulla->atm_addr[j]&0xff);
+ }
+ offset+=sprintf(buf+offset,
+ "Vcc vpi:%d vci:%d, Recv_vcc vpi:%d vci:%d Last_used:%lx, Timestamp:%lx, No_tries:%d ",
+ rulla->vcc?rulla->vcc->vpi:0,
+ rulla->vcc?rulla->vcc->vci:0,
+ rulla->recv_vcc?rulla->recv_vcc->vpi:0,
+ rulla->recv_vcc?rulla->recv_vcc->vci:0,
+ rulla->last_used,
+ rulla->timestamp, rulla->no_tries);
+ offset+=sprintf(buf+offset,
+ "Flags:%x, Packets_flooded:%x, Status: %s ",
+ rulla->flags, rulla->packets_flooded,
+ get_status_string(rulla->status));
+ offset+=sprintf(buf+offset,"->%lx\n",(long)rulla->next);
+ rulla = rulla->next;
+ printk("%s",buf);
+ }
+
+ rulla = mcast_fwds;
+ if (rulla)
+ printk("Multicast Forward VCCs\n");
+ while(rulla) {
+ offset=0;
+ offset += sprintf(buf+offset,"Mac:");
+ for(j=0;j<ETH_ALEN;j++) {
+ offset+=sprintf(buf+offset,"%2.2x ",
+ rulla->mac_addr[j]&0xff);
+ }
+ offset +=sprintf(buf+offset,"Atm:");
+ for(j=0;j<ATM_ESA_LEN;j++) {
+ offset+=sprintf(buf+offset,"%2.2x ",
+ rulla->atm_addr[j]&0xff);
+ }
+ offset+=sprintf(buf+offset,
+ "Vcc vpi:%d vci:%d, Recv_vcc vpi:%d vci:%d Last_used:%lx, Timestamp:%lx, No_tries:%d ",
+ rulla->vcc?rulla->vcc->vpi:0,
+ rulla->vcc?rulla->vcc->vci:0,
+ rulla->recv_vcc?rulla->recv_vcc->vpi:0,
+ rulla->recv_vcc?rulla->recv_vcc->vci:0,
+ rulla->last_used,
+ rulla->timestamp, rulla->no_tries);
+ offset+=sprintf(buf+offset,
+ "Flags:%x, Packets_flooded:%x, Status: %s ",
+ rulla->flags, rulla->packets_flooded,
+ get_status_string(rulla->status));
+ offset+=sprintf(buf+offset,"->%lx\n",(long)rulla->next);
+ rulla = rulla->next;
+ printk("%s",buf);
+ }
+
+#endif
+}
+
+/*
+ * Destruction of arp-cache
+ */
+void
+lec_arp_destroy(struct lec_priv *priv)
+{
+ struct lec_arp_table *entry, *next;
+ int i;
+
+ del_timer_sync(&priv->lec_arp_timer);
+
+ /*
+ * Remove all entries
+ */
+ for (i=0;i<LEC_ARP_TABLE_SIZE;i++) {
+ for(entry =priv->lec_arp_tables[i];entry != NULL; entry=next) {
+ next = entry->next;
+ lec_arp_remove(priv, entry);
+ kfree(entry);
+ }
+ }
+ entry = priv->lec_arp_empty_ones;
+ while(entry) {
+ next = entry->next;
+ del_timer_sync(&entry->timer);
+ lec_arp_clear_vccs(entry);
+ kfree(entry);
+ entry = next;
+ }
+ priv->lec_arp_empty_ones = NULL;
+ entry = priv->lec_no_forward;
+ while(entry) {
+ next = entry->next;
+ del_timer_sync(&entry->timer);
+ lec_arp_clear_vccs(entry);
+ kfree(entry);
+ entry = next;
+ }
+ priv->lec_no_forward = NULL;
+ entry = priv->mcast_fwds;
+ while(entry) {
+ next = entry->next;
+ /* No timer, LANEv2 7.1.20 and 2.3.5.3 */
+ lec_arp_clear_vccs(entry);
+ kfree(entry);
+ entry = next;
+ }
+ priv->mcast_fwds = NULL;
+ priv->mcast_vcc = NULL;
+ memset(priv->lec_arp_tables, 0,
+ sizeof(struct lec_arp_table*)*LEC_ARP_TABLE_SIZE);
+}
+
+
+/*
+ * Find entry by mac_address
+ */
+static struct lec_arp_table*
+lec_arp_find(struct lec_priv *priv,
+ unsigned char *mac_addr)
+{
+ unsigned short place;
+ struct lec_arp_table *to_return;
+
+ DPRINTK("LEC_ARP: lec_arp_find :%2.2x %2.2x %2.2x %2.2x %2.2x %2.2x\n",
+ mac_addr[0]&0xff, mac_addr[1]&0xff, mac_addr[2]&0xff,
+ mac_addr[3]&0xff, mac_addr[4]&0xff, mac_addr[5]&0xff);
+ lec_arp_get(priv);
+ place = HASH(mac_addr[ETH_ALEN-1]);
+
+ to_return = priv->lec_arp_tables[place];
+ while(to_return) {
+ if (memcmp(mac_addr, to_return->mac_addr, ETH_ALEN) == 0) {
+ lec_arp_put(priv);
+ return to_return;
+ }
+ to_return = to_return->next;
+ }
+ lec_arp_put(priv);
+ return NULL;
+}
+
+static struct lec_arp_table*
+make_entry(struct lec_priv *priv, unsigned char *mac_addr)
+{
+ struct lec_arp_table *to_return;
+
+ to_return=(struct lec_arp_table *)kmalloc(sizeof(struct lec_arp_table),
+ GFP_ATOMIC);
+ if (!to_return) {
+ printk("LEC: Arp entry kmalloc failed\n");
+ return NULL;
+ }
+ memset(to_return,0,sizeof(struct lec_arp_table));
+ memcpy(to_return->mac_addr, mac_addr, ETH_ALEN);
+ init_timer(&to_return->timer);
+ to_return->timer.function = lec_arp_expire_arp;
+ to_return->timer.data = (unsigned long)to_return;
+ to_return->last_used = jiffies;
+ to_return->priv = priv;
+ skb_queue_head_init(&to_return->tx_wait);
+ return to_return;
+}
+
+/*
+ *
+ * Arp sent timer expired
+ *
+ */
+static void
+lec_arp_expire_arp(unsigned long data)
+{
+ struct lec_arp_table *entry;
+
+ entry = (struct lec_arp_table *)data;
+
+ DPRINTK("lec_arp_expire_arp\n");
+ if (entry->status == ESI_ARP_PENDING) {
+ if (entry->no_tries <= entry->priv->max_retry_count) {
+ if (entry->is_rdesc)
+ send_to_lecd(entry->priv, l_rdesc_arp_xmt, entry->mac_addr, NULL, NULL);
+ else
+ send_to_lecd(entry->priv, l_arp_xmt, entry->mac_addr, NULL, NULL);
+ entry->no_tries++;
+ }
+ mod_timer(&entry->timer, jiffies + (1*HZ));
+ }
+}
+
+/*
+ *
+ * Unknown/unused vcc expire, remove associated entry
+ *
+ */
+static void
+lec_arp_expire_vcc(unsigned long data)
+{
+ struct lec_arp_table *to_remove = (struct lec_arp_table*)data;
+ struct lec_priv *priv = (struct lec_priv *)to_remove->priv;
+ struct lec_arp_table *entry = NULL;
+
+ del_timer(&to_remove->timer);
+
+ DPRINTK("LEC_ARP %p %p: lec_arp_expire_vcc vpi:%d vci:%d\n",
+ to_remove, priv,
+ to_remove->vcc?to_remove->recv_vcc->vpi:0,
+ to_remove->vcc?to_remove->recv_vcc->vci:0);
+ DPRINTK("eo:%p nf:%p\n",priv->lec_arp_empty_ones,priv->lec_no_forward);
+ if (to_remove == priv->lec_arp_empty_ones)
+ priv->lec_arp_empty_ones = to_remove->next;
+ else {
+ entry = priv->lec_arp_empty_ones;
+ while (entry && entry->next != to_remove)
+ entry = entry->next;
+ if (entry)
+ entry->next = to_remove->next;
+ }
+ if (!entry) {
+ if (to_remove == priv->lec_no_forward) {
+ priv->lec_no_forward = to_remove->next;
+ } else {
+ entry = priv->lec_no_forward;
+ while (entry && entry->next != to_remove)
+ entry = entry->next;
+ if (entry)
+ entry->next = to_remove->next;
+ }
+ }
+ lec_arp_clear_vccs(to_remove);
+ kfree(to_remove);
+}
+
+/*
+ * Expire entries.
+ * 1. Re-set timer
+ * 2. For each entry, delete entries that have aged past the age limit.
+ * 3. For each entry, depending on the status of the entry, perform
+ * the following maintenance.
+ * a. If status is ESI_VC_PENDING or ESI_ARP_PENDING then if the
+ * tick_count is above the max_unknown_frame_time, clear
+ * the tick_count to zero and clear the packets_flooded counter
+ * to zero. This supports the packet rate limit per address
+ * while flooding unknowns.
+ * b. If the status is ESI_FLUSH_PENDING and the tick_count is greater
+ * than or equal to the path_switching_delay, change the status
+ * to ESI_FORWARD_DIRECT. This causes the flush period to end
+ * regardless of the progress of the flush protocol.
+ */
+static void
+lec_arp_check_expire(unsigned long data)
+{
+ struct lec_priv *priv = (struct lec_priv *)data;
+ struct lec_arp_table *entry, *next;
+ unsigned long now;
+ unsigned long time_to_check;
+ int i;
+
+ DPRINTK("lec_arp_check_expire %p,%d\n",priv,
+ atomic_read(&priv->lec_arp_users));
+ DPRINTK("expire: eo:%p nf:%p\n",priv->lec_arp_empty_ones,
+ priv->lec_no_forward);
+ if (!atomic_read(&priv->lec_arp_users)) {
+ lec_arp_get(priv);
+ now = jiffies;
+ for(i=0;i<LEC_ARP_TABLE_SIZE;i++) {
+ for(entry = priv->lec_arp_tables[i]; entry != NULL; ) {
+ if ((entry->flags) & LEC_REMOTE_FLAG &&
+ priv->topology_change)
+ time_to_check=priv->forward_delay_time;
+ else
+ time_to_check = priv->aging_time;
+
+ DPRINTK("About to expire: %lx - %lx > %lx\n",
+ now,entry->last_used, time_to_check);
+ if( time_after(now, entry->last_used+
+ time_to_check) &&
+ !(entry->flags & LEC_PERMANENT_FLAG) &&
+ !(entry->mac_addr[0] & 0x01) ) { /* LANE2: 7.1.20 */
+ /* Remove entry */
+ DPRINTK("LEC:Entry timed out\n");
+ next = entry->next;
+ lec_arp_remove(priv, entry);
+ kfree(entry);
+ entry = next;
+ } else {
+ /* Something else */
+ if ((entry->status == ESI_VC_PENDING ||
+ entry->status == ESI_ARP_PENDING)
+ && time_after_eq(now,
+ entry->timestamp +
+ priv->max_unknown_frame_time)) {
+ entry->timestamp = jiffies;
+ entry->packets_flooded = 0;
+ if (entry->status == ESI_VC_PENDING)
+ send_to_lecd(priv, l_svc_setup, entry->mac_addr, entry->atm_addr, NULL);
+ }
+ if (entry->status == ESI_FLUSH_PENDING
+ &&
+ time_after_eq(now, entry->timestamp+
+ priv->path_switching_delay)) {
+ struct sk_buff *skb;
+
+ while ((skb = skb_dequeue(&entry->tx_wait)))
+ lec_send(entry->vcc, skb, entry->priv);
+ entry->last_used = jiffies;
+ entry->status =
+ ESI_FORWARD_DIRECT;
+ }
+ entry = entry->next;
+ }
+ }
+ }
+ lec_arp_put(priv);
+ }
+
+ mod_timer(&priv->lec_arp_timer, jiffies + LEC_ARP_REFRESH_INTERVAL);
+}
+/*
+ * Try to find vcc where mac_address is attached.
+ *
+ */
+struct atm_vcc*
+lec_arp_resolve(struct lec_priv *priv, unsigned char *mac_to_find, int is_rdesc,
+ struct lec_arp_table **ret_entry)
+{
+ struct lec_arp_table *entry;
+
+ if (mac_to_find[0]&0x01) {
+ switch (priv->lane_version) {
+ case 1:
+ return priv->mcast_vcc;
+ break;
+ case 2: /* LANE2 wants arp for multicast addresses */
+ if ( memcmp(mac_to_find, bus_mac, ETH_ALEN) == 0)
+ return priv->mcast_vcc;
+ break;
+ default:
+ break;
+ }
+ }
+
+ entry = lec_arp_find(priv, mac_to_find);
+
+ if (entry) {
+ if (entry->status == ESI_FORWARD_DIRECT) {
+ /* Connection Ok */
+ entry->last_used = jiffies;
+ *ret_entry = entry;
+ return entry->vcc;
+ }
+ /* Data direct VC not yet set up, check to see if the unknown
+ frame count is greater than the limit. If the limit has
+ not been reached, allow the caller to send packet to
+ BUS. */
+ if (entry->status != ESI_FLUSH_PENDING &&
+ entry->packets_flooded<priv->maximum_unknown_frame_count) {
+ entry->packets_flooded++;
+ DPRINTK("LEC_ARP: Flooding..\n");
+ return priv->mcast_vcc;
+ }
+ /* We got here because entry->status == ESI_FLUSH_PENDING
+ * or BUS flood limit was reached for an entry which is
+ * in ESI_ARP_PENDING or ESI_VC_PENDING state.
+ */
+ *ret_entry = entry;
+ DPRINTK("lec: entry->status %d entry->vcc %p\n", entry->status, entry->vcc);
+ return NULL;
+ } else {
+ /* No matching entry was found */
+ entry = make_entry(priv, mac_to_find);
+ DPRINTK("LEC_ARP: Making entry\n");
+ if (!entry) {
+ return priv->mcast_vcc;
+ }
+ lec_arp_add(priv, entry);
+ /* We want arp-request(s) to be sent */
+ entry->packets_flooded =1;
+ entry->status = ESI_ARP_PENDING;
+ entry->no_tries = 1;
+ entry->last_used = entry->timestamp = jiffies;
+ entry->is_rdesc = is_rdesc;
+ if (entry->is_rdesc)
+ send_to_lecd(priv, l_rdesc_arp_xmt, mac_to_find, NULL, NULL);
+ else
+ send_to_lecd(priv, l_arp_xmt, mac_to_find, NULL, NULL);
+ entry->timer.expires = jiffies + (1*HZ);
+ entry->timer.function = lec_arp_expire_arp;
+ add_timer(&entry->timer);
+ return priv->mcast_vcc;
+ }
+}
+
+int
+lec_addr_delete(struct lec_priv *priv, unsigned char *atm_addr,
+ unsigned long permanent)
+{
+ struct lec_arp_table *entry, *next;
+ int i;
+
+ lec_arp_get(priv);
+ DPRINTK("lec_addr_delete\n");
+ for(i=0;i<LEC_ARP_TABLE_SIZE;i++) {
+ for(entry=priv->lec_arp_tables[i];entry != NULL; entry=next) {
+ next = entry->next;
+ if (!memcmp(atm_addr, entry->atm_addr, ATM_ESA_LEN)
+ && (permanent ||
+ !(entry->flags & LEC_PERMANENT_FLAG))) {
+ lec_arp_remove(priv, entry);
+ kfree(entry);
+ }
+ lec_arp_put(priv);
+ return 0;
+ }
+ }
+ lec_arp_put(priv);
+ return -1;
+}
+
+/*
+ * Notifies: Response to arp_request (atm_addr != NULL)
+ */
+void
+lec_arp_update(struct lec_priv *priv, unsigned char *mac_addr,
+ unsigned char *atm_addr, unsigned long remoteflag,
+ unsigned int targetless_le_arp)
+{
+ struct lec_arp_table *entry, *tmp;
+ int i;
+
+ DPRINTK("lec:%s", (targetless_le_arp) ? "targetless ": " ");
+ DPRINTK("lec_arp_update mac:%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x\n",
+ mac_addr[0],mac_addr[1],mac_addr[2],mac_addr[3],
+ mac_addr[4],mac_addr[5]);
+
+ entry = lec_arp_find(priv, mac_addr);
+ if (entry == NULL && targetless_le_arp)
+ return; /* LANE2: ignore targetless LE_ARPs for which
+ * we have no entry in the cache. 7.1.30
+ */
+ lec_arp_get(priv);
+ if (priv->lec_arp_empty_ones) {
+ entry = priv->lec_arp_empty_ones;
+ if (!memcmp(entry->atm_addr, atm_addr, ATM_ESA_LEN)) {
+ priv->lec_arp_empty_ones = entry->next;
+ } else {
+ while(entry->next && memcmp(entry->next->atm_addr,
+ atm_addr, ATM_ESA_LEN))
+ entry = entry->next;
+ if (entry->next) {
+ tmp = entry;
+ entry = entry->next;
+ tmp->next = entry->next;
+ } else
+ entry = NULL;
+
+ }
+ if (entry) {
+ del_timer(&entry->timer);
+ tmp = lec_arp_find(priv, mac_addr);
+ if (tmp) {
+ del_timer(&tmp->timer);
+ tmp->status = ESI_FORWARD_DIRECT;
+ memcpy(tmp->atm_addr, atm_addr, ATM_ESA_LEN);
+ tmp->vcc = entry->vcc;
+ tmp->old_push = entry->old_push;
+ tmp->last_used = jiffies;
+ del_timer(&entry->timer);
+ kfree(entry);
+ entry=tmp;
+ } else {
+ entry->status = ESI_FORWARD_DIRECT;
+ memcpy(entry->mac_addr, mac_addr, ETH_ALEN);
+ entry->last_used = jiffies;
+ lec_arp_add(priv, entry);
+ }
+ if (remoteflag)
+ entry->flags|=LEC_REMOTE_FLAG;
+ else
+ entry->flags&=~LEC_REMOTE_FLAG;
+ lec_arp_put(priv);
+ DPRINTK("After update\n");
+ dump_arp_table(priv);
+ return;
+ }
+ }
+ entry = lec_arp_find(priv, mac_addr);
+ if (!entry) {
+ entry = make_entry(priv, mac_addr);
+ if (!entry) {
+ lec_arp_put(priv);
+ return;
+ }
+ entry->status = ESI_UNKNOWN;
+ lec_arp_add(priv, entry);
+ /* Temporary, changes before end of function */
+ }
+ memcpy(entry->atm_addr, atm_addr, ATM_ESA_LEN);
+ del_timer(&entry->timer);
+ for(i=0;i<LEC_ARP_TABLE_SIZE;i++) {
+ for(tmp=priv->lec_arp_tables[i];tmp;tmp=tmp->next) {
+ if (entry != tmp &&
+ !memcmp(tmp->atm_addr, atm_addr,
+ ATM_ESA_LEN)) {
+ /* Vcc to this host exists */
+ if (tmp->status > ESI_VC_PENDING) {
+ /*
+ * ESI_FLUSH_PENDING,
+ * ESI_FORWARD_DIRECT
+ */
+ entry->vcc = tmp->vcc;
+ entry->old_push=tmp->old_push;
+ }
+ entry->status=tmp->status;
+ break;
+ }
+ }
+ }
+ if (remoteflag)
+ entry->flags|=LEC_REMOTE_FLAG;
+ else
+ entry->flags&=~LEC_REMOTE_FLAG;
+ if (entry->status == ESI_ARP_PENDING ||
+ entry->status == ESI_UNKNOWN) {
+ entry->status = ESI_VC_PENDING;
+ send_to_lecd(priv, l_svc_setup, entry->mac_addr, atm_addr, NULL);
+ }
+ DPRINTK("After update2\n");
+ dump_arp_table(priv);
+ lec_arp_put(priv);
+}
+
+/*
+ * Notifies: Vcc setup ready
+ */
+void
+lec_vcc_added(struct lec_priv *priv, struct atmlec_ioc *ioc_data,
+ struct atm_vcc *vcc,
+ void (*old_push)(struct atm_vcc *vcc, struct sk_buff *skb))
+{
+ struct lec_arp_table *entry;
+ int i, found_entry=0;
+
+ lec_arp_get(priv);
+ if (ioc_data->receive == 2) {
+ /* Vcc for Multicast Forward. No timer, LANEv2 7.1.20 and 2.3.5.3 */
+
+ DPRINTK("LEC_ARP: Attaching mcast forward\n");
+#if 0
+ entry = lec_arp_find(priv, bus_mac);
+ if (!entry) {
+ printk("LEC_ARP: Multicast entry not found!\n");
+ lec_arp_put(priv);
+ return;
+ }
+ memcpy(entry->atm_addr, ioc_data->atm_addr, ATM_ESA_LEN);
+ entry->recv_vcc = vcc;
+ entry->old_recv_push = old_push;
+#endif
+ entry = make_entry(priv, bus_mac);
+ if (entry == NULL) {
+ lec_arp_put(priv);
+ return;
+ }
+ del_timer(&entry->timer);
+ memcpy(entry->atm_addr, ioc_data->atm_addr, ATM_ESA_LEN);
+ entry->recv_vcc = vcc;
+ entry->old_recv_push = old_push;
+ entry->next = priv->mcast_fwds;
+ priv->mcast_fwds = entry;
+ lec_arp_put(priv);
+ return;
+ } else if (ioc_data->receive == 1) {
+ /* Vcc which we don't want to make default vcc, attach it
+ anyway. */
+ DPRINTK("LEC_ARP:Attaching data direct, not default :%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x\n",
+ ioc_data->atm_addr[0],ioc_data->atm_addr[1],
+ ioc_data->atm_addr[2],ioc_data->atm_addr[3],
+ ioc_data->atm_addr[4],ioc_data->atm_addr[5],
+ ioc_data->atm_addr[6],ioc_data->atm_addr[7],
+ ioc_data->atm_addr[8],ioc_data->atm_addr[9],
+ ioc_data->atm_addr[10],ioc_data->atm_addr[11],
+ ioc_data->atm_addr[12],ioc_data->atm_addr[13],
+ ioc_data->atm_addr[14],ioc_data->atm_addr[15],
+ ioc_data->atm_addr[16],ioc_data->atm_addr[17],
+ ioc_data->atm_addr[18],ioc_data->atm_addr[19]);
+ entry = make_entry(priv, bus_mac);
+ if (entry == NULL) {
+ lec_arp_put(priv);
+ return;
+ }
+ memcpy(entry->atm_addr, ioc_data->atm_addr, ATM_ESA_LEN);
+ memset(entry->mac_addr, 0, ETH_ALEN);
+ entry->recv_vcc = vcc;
+ entry->old_recv_push = old_push;
+ entry->status = ESI_UNKNOWN;
+ entry->timer.expires = jiffies + priv->vcc_timeout_period;
+ entry->timer.function = lec_arp_expire_vcc;
+ add_timer(&entry->timer);
+ entry->next = priv->lec_no_forward;
+ priv->lec_no_forward = entry;
+ lec_arp_put(priv);
+ dump_arp_table(priv);
+ return;
+ }
+ DPRINTK("LEC_ARP:Attaching data direct, default:%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x%2.2x\n",
+ ioc_data->atm_addr[0],ioc_data->atm_addr[1],
+ ioc_data->atm_addr[2],ioc_data->atm_addr[3],
+ ioc_data->atm_addr[4],ioc_data->atm_addr[5],
+ ioc_data->atm_addr[6],ioc_data->atm_addr[7],
+ ioc_data->atm_addr[8],ioc_data->atm_addr[9],
+ ioc_data->atm_addr[10],ioc_data->atm_addr[11],
+ ioc_data->atm_addr[12],ioc_data->atm_addr[13],
+ ioc_data->atm_addr[14],ioc_data->atm_addr[15],
+ ioc_data->atm_addr[16],ioc_data->atm_addr[17],
+ ioc_data->atm_addr[18],ioc_data->atm_addr[19]);
+ for (i=0;i<LEC_ARP_TABLE_SIZE;i++) {
+ for (entry = priv->lec_arp_tables[i];entry;entry=entry->next) {
+ if (memcmp(ioc_data->atm_addr, entry->atm_addr,
+ ATM_ESA_LEN)==0) {
+ DPRINTK("LEC_ARP: Attaching data direct\n");
+ DPRINTK("Currently -> Vcc: %d, Rvcc:%d\n",
+ entry->vcc?entry->vcc->vci:0,
+ entry->recv_vcc?entry->recv_vcc->vci:0);
+ found_entry=1;
+ del_timer(&entry->timer);
+ entry->vcc = vcc;
+ entry->old_push = old_push;
+ if (entry->status == ESI_VC_PENDING) {
+ if(priv->maximum_unknown_frame_count
+ ==0)
+ entry->status =
+ ESI_FORWARD_DIRECT;
+ else {
+ entry->timestamp = jiffies;
+ entry->status =
+ ESI_FLUSH_PENDING;
+#if 0
+ send_to_lecd(priv,l_flush_xmt,
+ NULL,
+ entry->atm_addr,
+ NULL);
+#endif
+ }
+ } else {
+ /* They were forming a connection
+ to us, and we to them. Our
+ ATM address is numerically lower
+ than theirs, so we make connection
+ we formed into default VCC (8.1.11).
+ Connection they made gets torn
+ down. This might confuse some
+ clients. Can be changed if
+ someone reports trouble... */
+ ;
+ }
+ }
+ }
+ }
+ if (found_entry) {
+ lec_arp_put(priv);
+ DPRINTK("After vcc was added\n");
+ dump_arp_table(priv);
+ return;
+ }
+ /* Not found, snatch address from first data packet that arrives from
+ this vcc */
+ entry = make_entry(priv, bus_mac);
+ if (!entry) {
+ lec_arp_put(priv);
+ return;
+ }
+ entry->vcc = vcc;
+ entry->old_push = old_push;
+ memcpy(entry->atm_addr, ioc_data->atm_addr, ATM_ESA_LEN);
+ memset(entry->mac_addr, 0, ETH_ALEN);
+ entry->status = ESI_UNKNOWN;
+ entry->next = priv->lec_arp_empty_ones;
+ priv->lec_arp_empty_ones = entry;
+ entry->timer.expires = jiffies + priv->vcc_timeout_period;
+ entry->timer.function = lec_arp_expire_vcc;
+ add_timer(&entry->timer);
+ lec_arp_put(priv);
+ DPRINTK("After vcc was added\n");
+ dump_arp_table(priv);
+}
+
+void
+lec_flush_complete(struct lec_priv *priv, unsigned long tran_id)
+{
+ struct lec_arp_table *entry;
+ int i;
+
+ DPRINTK("LEC:lec_flush_complete %lx\n",tran_id);
+ for (i=0;i<LEC_ARP_TABLE_SIZE;i++) {
+ for (entry=priv->lec_arp_tables[i];entry;entry=entry->next) {
+ if (entry->flush_tran_id == tran_id &&
+ entry->status == ESI_FLUSH_PENDING) {
+ struct sk_buff *skb;
+
+ while ((skb = skb_dequeue(&entry->tx_wait)))
+ lec_send(entry->vcc, skb, entry->priv);
+ entry->status = ESI_FORWARD_DIRECT;
+ DPRINTK("LEC_ARP: Flushed\n");
+ }
+ }
+ }
+ dump_arp_table(priv);
+}
+
+void
+lec_set_flush_tran_id(struct lec_priv *priv,
+ unsigned char *atm_addr, unsigned long tran_id)
+{
+ struct lec_arp_table *entry;
+ int i;
+
+ for (i=0;i<LEC_ARP_TABLE_SIZE;i++)
+ for(entry=priv->lec_arp_tables[i];entry;entry=entry->next)
+ if (!memcmp(atm_addr, entry->atm_addr, ATM_ESA_LEN)) {
+ entry->flush_tran_id = tran_id;
+ DPRINTK("Set flush transaction id to %lx for %p\n",tran_id,entry);
+ }
+}
+
+int
+lec_mcast_make(struct lec_priv *priv, struct atm_vcc *vcc)
+{
+ unsigned char mac_addr[] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+ struct lec_arp_table *to_add;
+
+ lec_arp_get(priv);
+ to_add = make_entry(priv, mac_addr);
+ if (!to_add) {
+ lec_arp_put(priv);
+ return -ENOMEM;
+ }
+ memcpy(to_add->atm_addr, vcc->remote.sas_addr.prv, ATM_ESA_LEN);
+ to_add->status = ESI_FORWARD_DIRECT;
+ to_add->flags |= LEC_PERMANENT_FLAG;
+ to_add->vcc = vcc;
+ to_add->old_push = vcc->push;
+ vcc->push = lec_push;
+ priv->mcast_vcc = vcc;
+ lec_arp_add(priv, to_add);
+ lec_arp_put(priv);
+ return 0;
+}
+
+void
+lec_vcc_close(struct lec_priv *priv, struct atm_vcc *vcc)
+{
+ struct lec_arp_table *entry, *next;
+ int i;
+
+ DPRINTK("LEC_ARP: lec_vcc_close vpi:%d vci:%d\n",vcc->vpi,vcc->vci);
+ dump_arp_table(priv);
+ lec_arp_get(priv);
+ for(i=0;i<LEC_ARP_TABLE_SIZE;i++) {
+ for(entry = priv->lec_arp_tables[i];entry; entry=next) {
+ next = entry->next;
+ if (vcc == entry->vcc) {
+ lec_arp_remove(priv, entry);
+ kfree(entry);
+ if (priv->mcast_vcc == vcc) {
+ priv->mcast_vcc = NULL;
+ }
+ }
+ }
+ }
+
+ entry = priv->lec_arp_empty_ones;
+ priv->lec_arp_empty_ones = NULL;
+ while (entry != NULL) {
+ next = entry->next;
+ if (entry->vcc == vcc) { /* leave it out from the list */
+ lec_arp_clear_vccs(entry);
+ del_timer(&entry->timer);
+ kfree(entry);
+ }
+ else { /* put it back to the list */
+ entry->next = priv->lec_arp_empty_ones;
+ priv->lec_arp_empty_ones = entry;
+ }
+ entry = next;
+ }
+
+ entry = priv->lec_no_forward;
+ priv->lec_no_forward = NULL;
+ while (entry != NULL) {
+ next = entry->next;
+ if (entry->recv_vcc == vcc) {
+ lec_arp_clear_vccs(entry);
+ del_timer(&entry->timer);
+ kfree(entry);
+ }
+ else {
+ entry->next = priv->lec_no_forward;
+ priv->lec_no_forward = entry;
+ }
+ entry = next;
+ }
+
+ entry = priv->mcast_fwds;
+ priv->mcast_fwds = NULL;
+ while (entry != NULL) {
+ next = entry->next;
+ if (entry->recv_vcc == vcc) {
+ lec_arp_clear_vccs(entry);
+ /* No timer, LANEv2 7.1.20 and 2.3.5.3 */
+ kfree(entry);
+ }
+ else {
+ entry->next = priv->mcast_fwds;
+ priv->mcast_fwds = entry;
+ }
+ entry = next;
+ }
+
+ lec_arp_put(priv);
+ dump_arp_table(priv);
+}
+
+void
+lec_arp_check_empties(struct lec_priv *priv,
+ struct atm_vcc *vcc, struct sk_buff *skb)
+{
+ unsigned long flags;
+ struct lec_arp_table *entry, *prev;
+ struct lecdatahdr_8023 *hdr = (struct lecdatahdr_8023 *)skb->data;
+ unsigned char *src;
+#ifdef CONFIG_TR
+ struct lecdatahdr_8025 *tr_hdr = (struct lecdatahdr_8025 *)skb->data;
+
+ if (priv->is_trdev) src = tr_hdr->h_source;
+ else
+#endif
+ src = hdr->h_source;
+
+ lec_arp_get(priv);
+ entry = priv->lec_arp_empty_ones;
+ if (vcc == entry->vcc) {
+ spin_lock_irqsave(&priv->lec_arp_lock, flags);
+ del_timer(&entry->timer);
+ memcpy(entry->mac_addr, src, ETH_ALEN);
+ entry->status = ESI_FORWARD_DIRECT;
+ entry->last_used = jiffies;
+ priv->lec_arp_empty_ones = entry->next;
+ spin_unlock_irqrestore(&priv->lec_arp_lock, flags);
+ /* We might have got an entry */
+ if ((prev=lec_arp_find(priv,src))) {
+ lec_arp_remove(priv, prev);
+ kfree(prev);
+ }
+ lec_arp_add(priv, entry);
+ lec_arp_put(priv);
+ return;
+ }
+ spin_lock_irqsave(&priv->lec_arp_lock, flags);
+ prev = entry;
+ entry = entry->next;
+ while (entry && entry->vcc != vcc) {
+ prev= entry;
+ entry = entry->next;
+ }
+ if (!entry) {
+ DPRINTK("LEC_ARP: Arp_check_empties: entry not found!\n");
+ lec_arp_put(priv);
+ spin_unlock_irqrestore(&priv->lec_arp_lock, flags);
+ return;
+ }
+ del_timer(&entry->timer);
+ memcpy(entry->mac_addr, src, ETH_ALEN);
+ entry->status = ESI_FORWARD_DIRECT;
+ entry->last_used = jiffies;
+ prev->next = entry->next;
+ spin_unlock_irqrestore(&priv->lec_arp_lock, flags);
+ if ((prev = lec_arp_find(priv, src))) {
+ lec_arp_remove(priv, prev);
+ kfree(prev);
+ }
+ lec_arp_add(priv, entry);
+ lec_arp_put(priv);
+}
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/atm/lec.h b/uClinux-2.4.31-uc0/net/atm/lec.h
new file mode 100644
index 0000000..e59fed1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/lec.h
@@ -0,0 +1,165 @@
+/*
+ *
+ * Lan Emulation client header file
+ *
+ * Marko Kiiskila mkiiskila@yahoo.com
+ *
+ */
+
+#ifndef _LEC_H_
+#define _LEC_H_
+
+#include <linux/config.h>
+#include <linux/atmdev.h>
+#include <linux/netdevice.h>
+#include <linux/atmlec.h>
+
+#if defined (CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+#include <linux/if_bridge.h>
+extern struct net_bridge_fdb_entry *(*br_fdb_get_hook)(struct net_bridge *br,
+ unsigned char *addr);
+extern void (*br_fdb_put_hook)(struct net_bridge_fdb_entry *ent);
+#endif /* defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) */
+
+#define LEC_HEADER_LEN 16
+
+struct lecdatahdr_8023 {
+ unsigned short le_header;
+ unsigned char h_dest[ETH_ALEN];
+ unsigned char h_source[ETH_ALEN];
+ unsigned short h_type;
+};
+
+struct lecdatahdr_8025 {
+ unsigned short le_header;
+ unsigned char ac_pad;
+ unsigned char fc;
+ unsigned char h_dest[ETH_ALEN];
+ unsigned char h_source[ETH_ALEN];
+};
+
+#define LEC_MINIMUM_8023_SIZE 62
+#define LEC_MINIMUM_8025_SIZE 16
+
+/*
+ * Operations that LANE2 capable device can do. Two first functions
+ * are used to make the device do things. See spec 3.1.3 and 3.1.4.
+ *
+ * The third function is intented for the MPOA component sitting on
+ * top of the LANE device. The MPOA component assigns it's own function
+ * to (*associate_indicator)() and the LANE device will use that
+ * function to tell about TLVs it sees floating through.
+ *
+ */
+struct lane2_ops {
+ int (*resolve)(struct net_device *dev, u8 *dst_mac, int force,
+ u8 **tlvs, u32 *sizeoftlvs);
+ int (*associate_req)(struct net_device *dev, u8 *lan_dst,
+ u8 *tlvs, u32 sizeoftlvs);
+ void (*associate_indicator)(struct net_device *dev, u8 *mac_addr,
+ u8 *tlvs, u32 sizeoftlvs);
+};
+
+struct atm_lane_ops {
+ int (*lecd_attach)(struct atm_vcc *vcc, int arg);
+ int (*mcast_attach)(struct atm_vcc *vcc, int arg);
+ int (*vcc_attach)(struct atm_vcc *vcc, void *arg);
+ struct net_device * (*get_lec)(int itf);
+ struct module *owner;
+};
+
+/*
+ * ATM LAN Emulation supports both LLC & Dix Ethernet EtherType
+ * frames.
+ * 1. Dix Ethernet EtherType frames encoded by placing EtherType
+ * field in h_type field. Data follows immediatelly after header.
+ * 2. LLC Data frames whose total length, including LLC field and data,
+ * but not padding required to meet the minimum data frame length,
+ * is less than 1536(0x0600) MUST be encoded by placing that length
+ * in the h_type field. The LLC field follows header immediatelly.
+ * 3. LLC data frames longer than this maximum MUST be encoded by placing
+ * the value 0 in the h_type field.
+ *
+ */
+
+/* Hash table size */
+#define LEC_ARP_TABLE_SIZE 16
+
+struct lec_priv {
+ struct net_device_stats stats;
+ unsigned short lecid; /* Lecid of this client */
+ struct lec_arp_table *lec_arp_empty_ones;
+ /* Used for storing VCC's that don't have a MAC address attached yet */
+ struct lec_arp_table *lec_arp_tables[LEC_ARP_TABLE_SIZE];
+ /* Actual LE ARP table */
+ struct lec_arp_table *lec_no_forward;
+ /* Used for storing VCC's (and forward packets from) which are to
+ age out by not using them to forward packets.
+ This is because to some LE clients there will be 2 VCCs. Only
+ one of them gets used. */
+ struct lec_arp_table *mcast_fwds;
+ /* With LANEv2 it is possible that BUS (or a special multicast server)
+ establishes multiple Multicast Forward VCCs to us. This list
+ collects all those VCCs. LANEv1 client has only one item in this
+ list. These entries are not aged out. */
+ atomic_t lec_arp_users;
+ spinlock_t lec_arp_lock;
+ struct atm_vcc *mcast_vcc; /* Default Multicast Send VCC */
+ struct atm_vcc *lecd;
+ struct timer_list lec_arp_timer;
+ /* C10 */
+ unsigned int maximum_unknown_frame_count;
+/* Within the period of time defined by this variable, the client will send
+ no more than C10 frames to BUS for a given unicast destination. (C11) */
+ unsigned long max_unknown_frame_time;
+/* If no traffic has been sent in this vcc for this period of time,
+ vcc will be torn down (C12)*/
+ unsigned long vcc_timeout_period;
+/* An LE Client MUST not retry an LE_ARP_REQUEST for a
+ given frame's LAN Destination more than maximum retry count times,
+ after the first LEC_ARP_REQUEST (C13)*/
+ unsigned short max_retry_count;
+/* Max time the client will maintain an entry in its arp cache in
+ absence of a verification of that relationship (C17)*/
+ unsigned long aging_time;
+/* Max time the client will maintain an entry in cache when
+ topology change flag is true (C18) */
+ unsigned long forward_delay_time;
+/* Topology change flag (C19)*/
+ int topology_change;
+/* Max time the client expects an LE_ARP_REQUEST/LE_ARP_RESPONSE
+ cycle to take (C20)*/
+ unsigned long arp_response_time;
+/* Time limit ot wait to receive an LE_FLUSH_RESPONSE after the
+ LE_FLUSH_REQUEST has been sent before taking recover action. (C21)*/
+ unsigned long flush_timeout;
+/* The time since sending a frame to the bus after which the
+ LE Client may assume that the frame has been either discarded or
+ delivered to the recipient (C22) */
+ unsigned long path_switching_delay;
+
+ u8 *tlvs; /* LANE2: TLVs are new */
+ u32 sizeoftlvs; /* The size of the tlv array in bytes */
+ int lane_version; /* LANE2 */
+ int itfnum; /* e.g. 2 for lec2, 5 for lec5 */
+ struct lane2_ops *lane2_ops; /* can be NULL for LANE v1 */
+ int is_proxy; /* bridge between ATM and Ethernet */
+ int is_trdev; /* Device type, 0 = Ethernet, 1 = TokenRing */
+};
+
+int lecd_attach(struct atm_vcc *vcc, int arg);
+int lec_vcc_attach(struct atm_vcc *vcc, void *arg);
+int lec_mcast_attach(struct atm_vcc *vcc, int arg);
+struct net_device *get_dev_lec(int itf);
+int make_lec(struct atm_vcc *vcc);
+int send_to_lecd(struct lec_priv *priv,
+ atmlec_msg_type type, unsigned char *mac_addr,
+ unsigned char *atm_addr, struct sk_buff *data);
+void lec_push(struct atm_vcc *vcc, struct sk_buff *skb);
+
+extern struct atm_lane_ops *atm_lane_ops;
+void atm_lane_ops_set(struct atm_lane_ops *hook);
+int try_atm_lane_ops(void);
+
+#endif /* _LEC_H_ */
+
diff --git a/uClinux-2.4.31-uc0/net/atm/lec_arpc.h b/uClinux-2.4.31-uc0/net/atm/lec_arpc.h
new file mode 100644
index 0000000..d07fc6a
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/lec_arpc.h
@@ -0,0 +1,116 @@
+/*
+ * Lec arp cache
+ * Marko Kiiskila mkiiskila@yahoo.com
+ *
+ */
+#ifndef _LEC_ARP_H
+#define _LEC_ARP_H
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/if_ether.h>
+#include <linux/atmlec.h>
+
+struct lec_arp_table {
+ struct lec_arp_table *next; /* Linked entry list */
+ unsigned char atm_addr[ATM_ESA_LEN]; /* Atm address */
+ unsigned char mac_addr[ETH_ALEN]; /* Mac address */
+ int is_rdesc; /* Mac address is a route descriptor */
+ struct atm_vcc *vcc; /* Vcc this entry is attached */
+ struct atm_vcc *recv_vcc; /* Vcc we receive data from */
+ void (*old_push)(struct atm_vcc *vcc,struct sk_buff *skb);
+ /* Push that leads to daemon */
+ void (*old_recv_push)(struct atm_vcc *vcc, struct sk_buff *skb);
+ /* Push that leads to daemon */
+ void (*old_close)(struct atm_vcc *vcc);
+ /* We want to see when this
+ * vcc gets closed */
+ unsigned long last_used; /* For expiry */
+ unsigned long timestamp; /* Used for various timestamping
+ * things:
+ * 1. FLUSH started
+ * (status=ESI_FLUSH_PENDING)
+ * 2. Counting to
+ * max_unknown_frame_time
+ * (status=ESI_ARP_PENDING||
+ * status=ESI_VC_PENDING)
+ */
+ unsigned char no_tries; /* No of times arp retry has been
+ tried */
+ unsigned char status; /* Status of this entry */
+ unsigned short flags; /* Flags for this entry */
+ unsigned short packets_flooded; /* Data packets flooded */
+ unsigned long flush_tran_id; /* Transaction id in flush protocol */
+ struct timer_list timer; /* Arping timer */
+ struct lec_priv *priv; /* Pointer back */
+
+ u8 *tlvs; /* LANE2: Each MAC address can have TLVs */
+ u32 sizeoftlvs; /* associated with it. sizeoftlvs tells the */
+ /* the length of the tlvs array */
+ struct sk_buff_head tx_wait; /* wait queue for outgoing packets */
+};
+
+struct tlv { /* LANE2: Template tlv struct for accessing */
+ /* the tlvs in the lec_arp_table->tlvs array*/
+ u32 type;
+ u8 length;
+ u8 value[255];
+};
+
+/* Status fields */
+#define ESI_UNKNOWN 0 /*
+ * Next packet sent to this mac address
+ * causes ARP-request to be sent
+ */
+#define ESI_ARP_PENDING 1 /*
+ * There is no ATM address associated with this
+ * 48-bit address. The LE-ARP protocol is in
+ * progress.
+ */
+#define ESI_VC_PENDING 2 /*
+ * There is a valid ATM address associated with
+ * this 48-bit address but there is no VC set
+ * up to that ATM address. The signaling
+ * protocol is in process.
+ */
+#define ESI_FLUSH_PENDING 4 /*
+ * The LEC has been notified of the FLUSH_START
+ * status and it is assumed that the flush
+ * protocol is in process.
+ */
+#define ESI_FORWARD_DIRECT 5 /*
+ * Either the Path Switching Delay (C22) has
+ * elapsed or the LEC has notified the Mapping
+ * that the flush protocol has completed. In
+ * either case, it is safe to forward packets
+ * to this address via the data direct VC.
+ */
+
+/* Flag values */
+#define LEC_REMOTE_FLAG 0x0001
+#define LEC_PERMANENT_FLAG 0x0002
+
+/* Protos */
+void lec_arp_init(struct lec_priv *priv);
+int lec_mcast_make(struct lec_priv *priv, struct atm_vcc *vcc);
+void lec_arp_destroy(struct lec_priv *priv);
+void lec_vcc_close(struct lec_priv *priv, struct atm_vcc *vcc);
+
+struct atm_vcc *lec_arp_resolve(struct lec_priv *priv,
+ unsigned char *mac_to_addr,
+ int is_rdesc,
+ struct lec_arp_table **ret_entry);
+void lec_vcc_added(struct lec_priv *dev,
+ struct atmlec_ioc *ioc_data, struct atm_vcc *vcc,
+ void (*old_push)(struct atm_vcc *vcc, struct sk_buff *skb));
+void lec_arp_check_empties(struct lec_priv *priv,
+ struct atm_vcc *vcc, struct sk_buff *skb);
+int lec_addr_delete(struct lec_priv *priv,
+ unsigned char *mac_addr, unsigned long permanent);
+void lec_flush_complete(struct lec_priv *priv, unsigned long tran_id);
+void lec_arp_update(struct lec_priv *priv,
+ unsigned char *mac_addr, unsigned char *atm_addr,
+ unsigned long remoteflag, unsigned int targetless_le_arp);
+void lec_set_flush_tran_id(struct lec_priv *priv,
+ unsigned char *mac_addr, unsigned long tran_id);
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/atm/mpc.c b/uClinux-2.4.31-uc0/net/atm/mpc.c
new file mode 100644
index 0000000..94382e0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/mpc.c
@@ -0,0 +1,1478 @@
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <linux/bitops.h>
+
+/* We are an ethernet device */
+#include <linux/if_ether.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <net/sock.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <asm/byteorder.h>
+#include <asm/uaccess.h>
+#include <asm/checksum.h> /* for ip_fast_csum() */
+#include <net/arp.h>
+#include <net/dst.h>
+#include <linux/proc_fs.h>
+
+/* And atm device */
+#include <linux/atmdev.h>
+#include <linux/atmlec.h>
+#include <linux/atmmpc.h>
+/* Modular too */
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include "lec.h"
+#include "mpc.h"
+#include "resources.h"
+
+/*
+ * mpc.c: Implementation of MPOA client kernel part
+ */
+
+#if 0
+#define dprintk printk /* debug */
+#else
+#define dprintk(format,args...)
+#endif
+
+#if 0
+#define ddprintk printk /* more debug */
+#else
+#define ddprintk(format,args...)
+#endif
+
+
+
+#define MPOA_TAG_LEN 4
+
+/* mpc_daemon -> kernel */
+static void MPOA_trigger_rcvd (struct k_message *msg, struct mpoa_client *mpc);
+static void MPOA_res_reply_rcvd(struct k_message *msg, struct mpoa_client *mpc);
+static void ingress_purge_rcvd(struct k_message *msg, struct mpoa_client *mpc);
+static void egress_purge_rcvd(struct k_message *msg, struct mpoa_client *mpc);
+static void mps_death(struct k_message *msg, struct mpoa_client *mpc);
+static void clean_up(struct k_message *msg, struct mpoa_client *mpc, int action);
+static void MPOA_cache_impos_rcvd(struct k_message *msg, struct mpoa_client *mpc);
+static void set_mpc_ctrl_addr_rcvd(struct k_message *mesg, struct mpoa_client *mpc);
+static void set_mps_mac_addr_rcvd(struct k_message *mesg, struct mpoa_client *mpc);
+
+static uint8_t *copy_macs(struct mpoa_client *mpc, uint8_t *router_mac,
+ uint8_t *tlvs, uint8_t mps_macs, uint8_t device_type);
+static void purge_egress_shortcut(struct atm_vcc *vcc, eg_cache_entry *entry);
+
+static void send_set_mps_ctrl_addr(char *addr, struct mpoa_client *mpc);
+static void mpoad_close(struct atm_vcc *vcc);
+static int msg_from_mpoad(struct atm_vcc *vcc, struct sk_buff *skb);
+
+static void mpc_push(struct atm_vcc *vcc, struct sk_buff *skb);
+static int mpc_send_packet(struct sk_buff *skb, struct net_device *dev);
+static int mpoa_event_listener(struct notifier_block *mpoa_notifier, unsigned long event, void *dev);
+static void mpc_timer_refresh(void);
+static void mpc_cache_check( unsigned long checking_time );
+
+static struct llc_snap_hdr llc_snap_mpoa_ctrl = {
+ 0xaa, 0xaa, 0x03,
+ {0x00, 0x00, 0x5e},
+ {0x00, 0x03} /* For MPOA control PDUs */
+};
+static struct llc_snap_hdr llc_snap_mpoa_data = {
+ 0xaa, 0xaa, 0x03,
+ {0x00, 0x00, 0x00},
+ {0x08, 0x00} /* This is for IP PDUs only */
+};
+static struct llc_snap_hdr llc_snap_mpoa_data_tagged = {
+ 0xaa, 0xaa, 0x03,
+ {0x00, 0x00, 0x00},
+ {0x88, 0x4c} /* This is for tagged data PDUs */
+};
+
+static struct notifier_block mpoa_notifier = {
+ mpoa_event_listener,
+ NULL,
+ 0
+};
+
+#ifdef CONFIG_PROC_FS
+extern int mpc_proc_init(void);
+extern void mpc_proc_clean(void);
+#endif
+
+struct mpoa_client *mpcs = NULL; /* FIXME */
+static struct atm_mpoa_qos *qos_head = NULL;
+static struct timer_list mpc_timer;
+
+
+static struct mpoa_client *find_mpc_by_itfnum(int itf)
+{
+ struct mpoa_client *mpc;
+
+ mpc = mpcs; /* our global linked list */
+ while (mpc != NULL) {
+ if (mpc->dev_num == itf)
+ return mpc;
+ mpc = mpc->next;
+ }
+
+ return NULL; /* not found */
+}
+
+static struct mpoa_client *find_mpc_by_vcc(struct atm_vcc *vcc)
+{
+ struct mpoa_client *mpc;
+
+ mpc = mpcs; /* our global linked list */
+ while (mpc != NULL) {
+ if (mpc->mpoad_vcc == vcc)
+ return mpc;
+ mpc = mpc->next;
+ }
+
+ return NULL; /* not found */
+}
+
+static struct mpoa_client *find_mpc_by_lec(struct net_device *dev)
+{
+ struct mpoa_client *mpc;
+
+ mpc = mpcs; /* our global linked list */
+ while (mpc != NULL) {
+ if (mpc->dev == dev)
+ return mpc;
+ mpc = mpc->next;
+ }
+
+ return NULL; /* not found */
+}
+
+/*
+ * Functions for managing QoS list
+ */
+
+/*
+ * Overwrites the old entry or makes a new one.
+ */
+struct atm_mpoa_qos *atm_mpoa_add_qos(uint32_t dst_ip, struct atm_qos *qos)
+{
+ struct atm_mpoa_qos *entry;
+
+ entry = atm_mpoa_search_qos(dst_ip);
+ if (entry != NULL) {
+ entry->qos = *qos;
+ return entry;
+ }
+
+ entry = kmalloc(sizeof(struct atm_mpoa_qos), GFP_KERNEL);
+ if (entry == NULL) {
+ printk("mpoa: atm_mpoa_add_qos: out of memory\n");
+ return entry;
+ }
+
+ entry->ipaddr = dst_ip;
+ entry->qos = *qos;
+
+ entry->next = qos_head;
+ qos_head = entry;
+
+ return entry;
+}
+
+struct atm_mpoa_qos *atm_mpoa_search_qos(uint32_t dst_ip)
+{
+ struct atm_mpoa_qos *qos;
+
+ qos = qos_head;
+ while( qos != NULL ){
+ if(qos->ipaddr == dst_ip) {
+ break;
+ }
+ qos = qos->next;
+ }
+
+ return qos;
+}
+
+/*
+ * Returns 0 for failure
+ */
+int atm_mpoa_delete_qos(struct atm_mpoa_qos *entry)
+{
+
+ struct atm_mpoa_qos *curr;
+
+ if (entry == NULL) return 0;
+ if (entry == qos_head) {
+ qos_head = qos_head->next;
+ kfree(entry);
+ return 1;
+ }
+
+ curr = qos_head;
+ while (curr != NULL) {
+ if (curr->next == entry) {
+ curr->next = entry->next;
+ kfree(entry);
+ return 1;
+ }
+ curr = curr->next;
+ }
+
+ return 0;
+}
+
+void atm_mpoa_disp_qos(char *page, int *len)
+{
+
+ unsigned char *ip;
+ char ipaddr[16];
+ struct atm_mpoa_qos *qos;
+
+ qos = qos_head;
+ *len += sprintf(page + *len, "QoS entries for shortcuts:\n");
+ *len += sprintf(page + *len, "IP address\n TX:max_pcr pcr min_pcr max_cdv max_sdu\n RX:max_pcr pcr min_pcr max_cdv max_sdu\n");
+
+ ipaddr[sizeof(ipaddr)-1] = '\0';
+ while (qos != NULL) {
+ ip = (unsigned char *)&qos->ipaddr;
+ sprintf(ipaddr, "%u.%u.%u.%u", NIPQUAD(ip));
+ *len += sprintf(page + *len, "%u.%u.%u.%u\n %-7d %-7d %-7d %-7d %-7d\n %-7d %-7d %-7d %-7d %-7d\n",
+ NIPQUAD(ipaddr),
+ qos->qos.txtp.max_pcr, qos->qos.txtp.pcr, qos->qos.txtp.min_pcr, qos->qos.txtp.max_cdv, qos->qos.txtp.max_sdu,
+ qos->qos.rxtp.max_pcr, qos->qos.rxtp.pcr, qos->qos.rxtp.min_pcr, qos->qos.rxtp.max_cdv, qos->qos.rxtp.max_sdu);
+ qos = qos->next;
+ }
+
+ return;
+}
+
+static struct net_device *find_lec_by_itfnum(int itf)
+{
+ struct net_device *dev;
+ if (!try_atm_lane_ops())
+ return NULL;
+
+ dev = atm_lane_ops->get_lec(itf);
+ if (atm_lane_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_lane_ops->owner);
+ return dev;
+}
+
+static struct mpoa_client *alloc_mpc(void)
+{
+ struct mpoa_client *mpc;
+
+ mpc = kmalloc(sizeof (struct mpoa_client), GFP_KERNEL);
+ if (mpc == NULL)
+ return NULL;
+ memset(mpc, 0, sizeof(struct mpoa_client));
+ mpc->ingress_lock = RW_LOCK_UNLOCKED;
+ mpc->egress_lock = RW_LOCK_UNLOCKED;
+ mpc->next = mpcs;
+ atm_mpoa_init_cache(mpc);
+
+ mpc->parameters.mpc_p1 = MPC_P1;
+ mpc->parameters.mpc_p2 = MPC_P2;
+ memset(mpc->parameters.mpc_p3,0,sizeof(mpc->parameters.mpc_p3));
+ mpc->parameters.mpc_p4 = MPC_P4;
+ mpc->parameters.mpc_p5 = MPC_P5;
+ mpc->parameters.mpc_p6 = MPC_P6;
+
+ mpcs = mpc;
+
+ return mpc;
+}
+
+/*
+ *
+ * start_mpc() puts the MPC on line. All the packets destined
+ * to the lec underneath us are now being monitored and
+ * shortcuts will be established.
+ *
+ */
+static void start_mpc(struct mpoa_client *mpc, struct net_device *dev)
+{
+
+ dprintk("mpoa: (%s) start_mpc:\n", mpc->dev->name);
+ if (dev->hard_start_xmit == NULL) {
+ printk("mpoa: (%s) start_mpc: dev->hard_start_xmit == NULL, not starting\n",
+ dev->name);
+ return;
+ }
+ mpc->old_hard_start_xmit = dev->hard_start_xmit;
+ dev->hard_start_xmit = mpc_send_packet;
+
+ return;
+}
+
+static void stop_mpc(struct mpoa_client *mpc)
+{
+
+ dprintk("mpoa: (%s) stop_mpc:", mpc->dev->name);
+
+ /* Lets not nullify lec device's dev->hard_start_xmit */
+ if (mpc->dev->hard_start_xmit != mpc_send_packet) {
+ dprintk(" mpc already stopped, not fatal\n");
+ return;
+ }
+ dprintk("\n");
+ mpc->dev->hard_start_xmit = mpc->old_hard_start_xmit;
+ mpc->old_hard_start_xmit = NULL;
+ /* close_shortcuts(mpc); ??? FIXME */
+
+ return;
+}
+
+static const char * __attribute__ ((unused)) mpoa_device_type_string(char type)
+{
+ switch(type) {
+ case NON_MPOA:
+ return "non-MPOA device";
+ break;
+ case MPS:
+ return "MPS";
+ break;
+ case MPC:
+ return "MPC";
+ break;
+ case MPS_AND_MPC:
+ return "both MPS and MPC";
+ break;
+ default:
+ return "unspecified (non-MPOA) device";
+ break;
+ }
+
+ return ""; /* not reached */
+}
+
+/*
+ * lec device calls this via its dev->priv->lane2_ops->associate_indicator()
+ * when it sees a TLV in LE_ARP packet.
+ * We fill in the pointer above when we see a LANE2 lec initializing
+ * See LANE2 spec 3.1.5
+ *
+ * Quite a big and ugly function but when you look at it
+ * all it does is to try to locate and parse MPOA Device
+ * Type TLV.
+ * We give our lec a pointer to this function and when the
+ * lec sees a TLV it uses the pointer to call this function.
+ *
+ */
+static void lane2_assoc_ind(struct net_device *dev, uint8_t *mac_addr,
+ uint8_t *tlvs, uint32_t sizeoftlvs)
+{
+ uint32_t type;
+ uint8_t length, mpoa_device_type, number_of_mps_macs;
+ uint8_t *end_of_tlvs;
+ struct mpoa_client *mpc;
+
+ mpoa_device_type = number_of_mps_macs = 0; /* silence gcc */
+ dprintk("mpoa: (%s) lane2_assoc_ind: received TLV(s), ", dev->name);
+ dprintk("total length of all TLVs %d\n", sizeoftlvs);
+ mpc = find_mpc_by_lec(dev); /* Sampo-Fix: moved here from below */
+ if (mpc == NULL) {
+ printk("mpoa: (%s) lane2_assoc_ind: no mpc\n", dev->name);
+ return;
+ }
+ end_of_tlvs = tlvs + sizeoftlvs;
+ while (end_of_tlvs - tlvs >= 5) {
+ type = (tlvs[0] << 24) | (tlvs[1] << 16) | (tlvs[2] << 8) | tlvs[3];
+ length = tlvs[4];
+ tlvs += 5;
+ dprintk(" type 0x%x length %02x\n", type, length);
+ if (tlvs + length > end_of_tlvs) {
+ printk("TLV value extends past its buffer, aborting parse\n");
+ return;
+ }
+
+ if (type == 0) {
+ printk("mpoa: (%s) lane2_assoc_ind: TLV type was 0, returning\n", dev->name);
+ return;
+ }
+
+ if (type != TLV_MPOA_DEVICE_TYPE) {
+ tlvs += length;
+ continue; /* skip other TLVs */
+ }
+ mpoa_device_type = *tlvs++;
+ number_of_mps_macs = *tlvs++;
+ dprintk("mpoa: (%s) MPOA device type '%s', ", dev->name, mpoa_device_type_string(mpoa_device_type));
+ if (mpoa_device_type == MPS_AND_MPC &&
+ length < (42 + number_of_mps_macs*ETH_ALEN)) { /* :) */
+ printk("\nmpoa: (%s) lane2_assoc_ind: short MPOA Device Type TLV\n",
+ dev->name);
+ continue;
+ }
+ if ((mpoa_device_type == MPS || mpoa_device_type == MPC)
+ && length < 22 + number_of_mps_macs*ETH_ALEN) {
+ printk("\nmpoa: (%s) lane2_assoc_ind: short MPOA Device Type TLV\n",
+ dev->name);
+ continue;
+ }
+ if (mpoa_device_type != MPS && mpoa_device_type != MPS_AND_MPC) {
+ dprintk("ignoring non-MPS device\n");
+ if (mpoa_device_type == MPC) tlvs += 20;
+ continue; /* we are only interested in MPSs */
+ }
+ if (number_of_mps_macs == 0 && mpoa_device_type == MPS_AND_MPC) {
+ printk("\nmpoa: (%s) lane2_assoc_ind: MPS_AND_MPC has zero MACs\n", dev->name);
+ continue; /* someone should read the spec */
+ }
+ dprintk("this MPS has %d MAC addresses\n", number_of_mps_macs);
+
+ /* ok, now we can go and tell our daemon the control address of MPS */
+ send_set_mps_ctrl_addr(tlvs, mpc);
+
+ tlvs = copy_macs(mpc, mac_addr, tlvs, number_of_mps_macs, mpoa_device_type);
+ if (tlvs == NULL) return;
+ }
+ if (end_of_tlvs - tlvs != 0)
+ printk("mpoa: (%s) lane2_assoc_ind: ignoring %d bytes of trailing TLV carbage\n",
+ dev->name, end_of_tlvs - tlvs);
+ return;
+}
+
+/*
+ * Store at least advertizing router's MAC address
+ * plus the possible MAC address(es) to mpc->mps_macs.
+ * For a freshly allocated MPOA client mpc->mps_macs == 0.
+ */
+static uint8_t *copy_macs(struct mpoa_client *mpc, uint8_t *router_mac,
+ uint8_t *tlvs, uint8_t mps_macs, uint8_t device_type)
+{
+ int num_macs;
+ num_macs = (mps_macs > 1) ? mps_macs : 1;
+
+ if (mpc->number_of_mps_macs != num_macs) { /* need to reallocate? */
+ if (mpc->number_of_mps_macs != 0) kfree(mpc->mps_macs);
+ mpc->number_of_mps_macs = 0;
+ mpc->mps_macs = kmalloc(num_macs*ETH_ALEN, GFP_KERNEL);
+ if (mpc->mps_macs == NULL) {
+ printk("mpoa: (%s) copy_macs: out of mem\n", mpc->dev->name);
+ return NULL;
+ }
+ }
+ memcpy(mpc->mps_macs, router_mac, ETH_ALEN);
+ tlvs += 20; if (device_type == MPS_AND_MPC) tlvs += 20;
+ if (mps_macs > 0)
+ memcpy(mpc->mps_macs, tlvs, mps_macs*ETH_ALEN);
+ tlvs += mps_macs*ETH_ALEN;
+ mpc->number_of_mps_macs = num_macs;
+
+ return tlvs;
+}
+
+static int send_via_shortcut(struct sk_buff *skb, struct mpoa_client *mpc)
+{
+ in_cache_entry *entry;
+ struct iphdr *iph;
+ char *buff;
+ uint32_t ipaddr = 0;
+
+ static struct {
+ struct llc_snap_hdr hdr;
+ uint32_t tag;
+ } tagged_llc_snap_hdr = {
+ {0xaa, 0xaa, 0x03, {0x00, 0x00, 0x00}, {0x88, 0x4c}},
+ 0
+ };
+
+ buff = skb->data + mpc->dev->hard_header_len;
+ iph = (struct iphdr *)buff;
+ ipaddr = iph->daddr;
+
+ ddprintk("mpoa: (%s) send_via_shortcut: ipaddr 0x%x\n", mpc->dev->name, ipaddr);
+
+ entry = mpc->in_ops->get(ipaddr, mpc);
+ if (entry == NULL) {
+ entry = mpc->in_ops->add_entry(ipaddr, mpc);
+ if (entry != NULL) mpc->in_ops->put(entry);
+ return 1;
+ }
+ if (mpc->in_ops->cache_hit(entry, mpc) != OPEN){ /* threshold not exceeded or VCC not ready */
+ ddprintk("mpoa: (%s) send_via_shortcut: cache_hit: returns != OPEN\n", mpc->dev->name);
+ mpc->in_ops->put(entry);
+ return 1;
+ }
+
+ ddprintk("mpoa: (%s) send_via_shortcut: using shortcut\n", mpc->dev->name);
+ /* MPOA spec A.1.4, MPOA client must decrement IP ttl at least by one */
+ if (iph->ttl <= 1) {
+ ddprintk("mpoa: (%s) send_via_shortcut: IP ttl = %u, using LANE\n", mpc->dev->name, iph->ttl);
+ mpc->in_ops->put(entry);
+ return 1;
+ }
+ iph->ttl--;
+ iph->check = 0;
+ iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
+
+ if (entry->ctrl_info.tag != 0) {
+ ddprintk("mpoa: (%s) send_via_shortcut: adding tag 0x%x\n", mpc->dev->name, entry->ctrl_info.tag);
+ tagged_llc_snap_hdr.tag = entry->ctrl_info.tag;
+ skb_pull(skb, ETH_HLEN); /* get rid of Eth header */
+ skb_push(skb, sizeof(tagged_llc_snap_hdr)); /* add LLC/SNAP header */
+ memcpy(skb->data, &tagged_llc_snap_hdr, sizeof(tagged_llc_snap_hdr));
+ } else {
+ skb_pull(skb, ETH_HLEN); /* get rid of Eth header */
+ skb_push(skb, sizeof(struct llc_snap_hdr)); /* add LLC/SNAP header + tag */
+ memcpy(skb->data, &llc_snap_mpoa_data, sizeof(struct llc_snap_hdr));
+ }
+
+ atomic_add(skb->truesize, &entry->shortcut->sk->wmem_alloc);
+ ATM_SKB(skb)->atm_options = entry->shortcut->atm_options;
+ entry->shortcut->send(entry->shortcut, skb);
+ entry->packets_fwded++;
+ mpc->in_ops->put(entry);
+
+ return 0;
+}
+
+/*
+ * Probably needs some error checks and locking, not sure...
+ */
+static int mpc_send_packet(struct sk_buff *skb, struct net_device *dev)
+{
+ int retval;
+ struct mpoa_client *mpc;
+ struct ethhdr *eth;
+ int i = 0;
+
+ mpc = find_mpc_by_lec(dev); /* this should NEVER fail */
+ if(mpc == NULL) {
+ printk("mpoa: (%s) mpc_send_packet: no MPC found\n", dev->name);
+ goto non_ip;
+ }
+
+ eth = (struct ethhdr *)skb->data;
+ if (eth->h_proto != htons(ETH_P_IP))
+ goto non_ip; /* Multi-Protocol Over ATM :-) */
+
+ while (i < mpc->number_of_mps_macs) {
+ if (memcmp(eth->h_dest, (mpc->mps_macs + i*ETH_ALEN), ETH_ALEN) == 0)
+ if ( send_via_shortcut(skb, mpc) == 0 ) /* try shortcut */
+ return 0; /* success! */
+ i++;
+ }
+
+ non_ip:
+ retval = mpc->old_hard_start_xmit(skb,dev);
+
+ return retval;
+}
+
+int atm_mpoa_vcc_attach(struct atm_vcc *vcc, long arg)
+{
+ int bytes_left;
+ struct mpoa_client *mpc;
+ struct atmmpc_ioc ioc_data;
+ in_cache_entry *in_entry;
+ uint32_t ipaddr;
+ unsigned char *ip;
+
+ bytes_left = copy_from_user(&ioc_data, (void *)arg, sizeof(struct atmmpc_ioc));
+ if (bytes_left != 0) {
+ printk("mpoa: mpc_vcc_attach: Short read (missed %d bytes) from userland\n", bytes_left);
+ return -EFAULT;
+ }
+ ipaddr = ioc_data.ipaddr;
+ if (ioc_data.dev_num < 0 || ioc_data.dev_num >= MAX_LEC_ITF)
+ return -EINVAL;
+
+ mpc = find_mpc_by_itfnum(ioc_data.dev_num);
+ if (mpc == NULL)
+ return -EINVAL;
+
+ if (ioc_data.type == MPC_SOCKET_INGRESS) {
+ in_entry = mpc->in_ops->get(ipaddr, mpc);
+ if (in_entry == NULL || in_entry->entry_state < INGRESS_RESOLVED) {
+ printk("mpoa: (%s) mpc_vcc_attach: did not find RESOLVED entry from ingress cache\n",
+ mpc->dev->name);
+ if (in_entry != NULL) mpc->in_ops->put(in_entry);
+ return -EINVAL;
+ }
+ ip = (unsigned char*)&in_entry->ctrl_info.in_dst_ip;
+ printk("mpoa: (%s) mpc_vcc_attach: attaching ingress SVC, entry = %u.%u.%u.%u\n",
+ mpc->dev->name, ip[0], ip[1], ip[2], ip[3]);
+ in_entry->shortcut = vcc;
+ mpc->in_ops->put(in_entry);
+ } else {
+ printk("mpoa: (%s) mpc_vcc_attach: attaching egress SVC\n", mpc->dev->name);
+ }
+
+ vcc->proto_data = mpc->dev;
+ vcc->push = mpc_push;
+
+ return 0;
+}
+
+/*
+ *
+ */
+static void mpc_vcc_close(struct atm_vcc *vcc, struct net_device *dev)
+{
+ struct mpoa_client *mpc;
+ in_cache_entry *in_entry;
+ eg_cache_entry *eg_entry;
+
+ mpc = find_mpc_by_lec(dev);
+ if (mpc == NULL) {
+ printk("mpoa: (%s) mpc_vcc_close: close for unknown MPC\n", dev->name);
+ return;
+ }
+
+ dprintk("mpoa: (%s) mpc_vcc_close:\n", dev->name);
+ in_entry = mpc->in_ops->get_by_vcc(vcc, mpc);
+ if (in_entry) {
+ unsigned char *ip __attribute__ ((unused)) =
+ (unsigned char *)&in_entry->ctrl_info.in_dst_ip;
+ dprintk("mpoa: (%s) mpc_vcc_close: ingress SVC closed ip = %u.%u.%u.%u\n",
+ mpc->dev->name, ip[0], ip[1], ip[2], ip[3]);
+ in_entry->shortcut = NULL;
+ mpc->in_ops->put(in_entry);
+ }
+ eg_entry = mpc->eg_ops->get_by_vcc(vcc, mpc);
+ if (eg_entry) {
+ dprintk("mpoa: (%s) mpc_vcc_close: egress SVC closed\n", mpc->dev->name);
+ eg_entry->shortcut = NULL;
+ mpc->eg_ops->put(eg_entry);
+ }
+
+ if (in_entry == NULL && eg_entry == NULL)
+ dprintk("mpoa: (%s) mpc_vcc_close: unused vcc closed\n", dev->name);
+
+ return;
+}
+
+static void mpc_push(struct atm_vcc *vcc, struct sk_buff *skb)
+{
+ struct net_device *dev = (struct net_device *)vcc->proto_data;
+ struct sk_buff *new_skb;
+ eg_cache_entry *eg;
+ struct mpoa_client *mpc;
+ uint32_t tag;
+ char *tmp;
+
+ ddprintk("mpoa: (%s) mpc_push:\n", dev->name);
+ if (skb == NULL) {
+ dprintk("mpoa: (%s) mpc_push: null skb, closing VCC\n", dev->name);
+ mpc_vcc_close(vcc, dev);
+ return;
+ }
+
+ skb->dev = dev;
+ if (memcmp(skb->data, &llc_snap_mpoa_ctrl, sizeof(struct llc_snap_hdr)) == 0) {
+ dprintk("mpoa: (%s) mpc_push: control packet arrived\n", dev->name);
+ skb_queue_tail(&vcc->sk->receive_queue, skb); /* Pass control packets to daemon */
+ wake_up(&vcc->sleep);
+ return;
+ }
+
+ /* data coming over the shortcut */
+ atm_return(vcc, skb->truesize);
+
+ mpc = find_mpc_by_lec(dev);
+ if (mpc == NULL) {
+ printk("mpoa: (%s) mpc_push: unknown MPC\n", dev->name);
+ return;
+ }
+
+ if (memcmp(skb->data, &llc_snap_mpoa_data_tagged, sizeof(struct llc_snap_hdr)) == 0) { /* MPOA tagged data */
+ ddprintk("mpoa: (%s) mpc_push: tagged data packet arrived\n", dev->name);
+
+ } else if (memcmp(skb->data, &llc_snap_mpoa_data, sizeof(struct llc_snap_hdr)) == 0) { /* MPOA data */
+ printk("mpoa: (%s) mpc_push: non-tagged data packet arrived\n", dev->name);
+ printk(" mpc_push: non-tagged data unsupported, purging\n");
+ dev_kfree_skb_any(skb);
+ return;
+ } else {
+ printk("mpoa: (%s) mpc_push: garbage arrived, purging\n", dev->name);
+ dev_kfree_skb_any(skb);
+ return;
+ }
+
+ tmp = skb->data + sizeof(struct llc_snap_hdr);
+ tag = *(uint32_t *)tmp;
+
+ eg = mpc->eg_ops->get_by_tag(tag, mpc);
+ if (eg == NULL) {
+ printk("mpoa: (%s) mpc_push: Didn't find egress cache entry, tag = %u\n",
+ dev->name,tag);
+ purge_egress_shortcut(vcc, NULL);
+ dev_kfree_skb_any(skb);
+ return;
+ }
+
+ /*
+ * See if ingress MPC is using shortcut we opened as a return channel.
+ * This means we have a bi-directional vcc opened by us.
+ */
+ if (eg->shortcut == NULL) {
+ eg->shortcut = vcc;
+ printk("mpoa: (%s) mpc_push: egress SVC in use\n", dev->name);
+ }
+
+ skb_pull(skb, sizeof(struct llc_snap_hdr) + sizeof(tag)); /* get rid of LLC/SNAP header */
+ new_skb = skb_realloc_headroom(skb, eg->ctrl_info.DH_length); /* LLC/SNAP is shorter than MAC header :( */
+ dev_kfree_skb_any(skb);
+ if (new_skb == NULL){
+ mpc->eg_ops->put(eg);
+ return;
+ }
+ skb_push(new_skb, eg->ctrl_info.DH_length); /* add MAC header */
+ memcpy(new_skb->data, eg->ctrl_info.DLL_header, eg->ctrl_info.DH_length);
+ new_skb->protocol = eth_type_trans(new_skb, dev);
+ new_skb->nh.raw = new_skb->data;
+
+ eg->latest_ip_addr = new_skb->nh.iph->saddr;
+ eg->packets_rcvd++;
+ mpc->eg_ops->put(eg);
+
+ memset(ATM_SKB(new_skb), 0, sizeof(struct atm_skb_data));
+ netif_rx(new_skb);
+
+ return;
+}
+
+static struct atmdev_ops mpc_ops = { /* only send is required */
+ .close = mpoad_close,
+ .send = msg_from_mpoad
+};
+
+static struct atm_dev mpc_dev = {
+ .ops = &mpc_ops,
+ .type = "mpc",
+ .number = 42,
+ .lock = SPIN_LOCK_UNLOCKED
+ /* rest of the members will be 0 */
+};
+
+int atm_mpoa_mpoad_attach (struct atm_vcc *vcc, int arg)
+{
+ struct mpoa_client *mpc;
+ struct lec_priv *priv;
+
+ if (mpcs == NULL) {
+ init_timer(&mpc_timer);
+ mpc_timer_refresh();
+
+ /* This lets us now how our LECs are doing */
+ register_netdevice_notifier(&mpoa_notifier);
+ }
+
+ mpc = find_mpc_by_itfnum(arg);
+ if (mpc == NULL) {
+ dprintk("mpoa: mpoad_attach: allocating new mpc for itf %d\n", arg);
+ mpc = alloc_mpc();
+ if (mpc == NULL)
+ return -ENOMEM;
+ mpc->dev_num = arg;
+ mpc->dev = find_lec_by_itfnum(arg); /* NULL if there was no lec */
+ }
+ if (mpc->mpoad_vcc) {
+ printk("mpoa: mpoad_attach: mpoad is already present for itf %d\n", arg);
+ return -EADDRINUSE;
+ }
+
+ if (mpc->dev) { /* check if the lec is LANE2 capable */
+ priv = (struct lec_priv *)mpc->dev->priv;
+ if (priv->lane_version < 2) {
+ dev_put(mpc->dev);
+ mpc->dev = NULL;
+ } else
+ priv->lane2_ops->associate_indicator = lane2_assoc_ind;
+ }
+
+ mpc->mpoad_vcc = vcc;
+ vcc->dev = &mpc_dev;
+ vcc_insert_socket(vcc->sk);
+ set_bit(ATM_VF_META,&vcc->flags);
+ set_bit(ATM_VF_READY,&vcc->flags);
+
+ if (mpc->dev) {
+ char empty[ATM_ESA_LEN];
+ memset(empty, 0, ATM_ESA_LEN);
+
+ start_mpc(mpc, mpc->dev);
+ /* set address if mpcd e.g. gets killed and restarted.
+ * If we do not do it now we have to wait for the next LE_ARP
+ */
+ if ( memcmp(mpc->mps_ctrl_addr, empty, ATM_ESA_LEN) != 0 )
+ send_set_mps_ctrl_addr(mpc->mps_ctrl_addr, mpc);
+ }
+
+ MOD_INC_USE_COUNT;
+ return arg;
+}
+
+static void send_set_mps_ctrl_addr(char *addr, struct mpoa_client *mpc)
+{
+ struct k_message mesg;
+
+ memcpy (mpc->mps_ctrl_addr, addr, ATM_ESA_LEN);
+
+ mesg.type = SET_MPS_CTRL_ADDR;
+ memcpy(mesg.MPS_ctrl, addr, ATM_ESA_LEN);
+ msg_to_mpoad(&mesg, mpc);
+
+ return;
+}
+
+static void mpoad_close(struct atm_vcc *vcc)
+{
+ struct mpoa_client *mpc;
+ struct sk_buff *skb;
+
+ mpc = find_mpc_by_vcc(vcc);
+ if (mpc == NULL) {
+ printk("mpoa: mpoad_close: did not find MPC\n");
+ return;
+ }
+ if (!mpc->mpoad_vcc) {
+ printk("mpoa: mpoad_close: close for non-present mpoad\n");
+ return;
+ }
+
+ mpc->mpoad_vcc = NULL;
+ if (mpc->dev) {
+ struct lec_priv *priv = (struct lec_priv *)mpc->dev->priv;
+ priv->lane2_ops->associate_indicator = NULL;
+ stop_mpc(mpc);
+ dev_put(mpc->dev);
+ }
+
+ mpc->in_ops->destroy_cache(mpc);
+ mpc->eg_ops->destroy_cache(mpc);
+
+ while ( (skb = skb_dequeue(&vcc->sk->receive_queue)) ){
+ atm_return(vcc, skb->truesize);
+ kfree_skb(skb);
+ }
+
+ printk("mpoa: (%s) going down\n",
+ (mpc->dev) ? mpc->dev->name : "<unknown>");
+ MOD_DEC_USE_COUNT;
+
+ return;
+}
+
+/*
+ *
+ */
+static int msg_from_mpoad(struct atm_vcc *vcc, struct sk_buff *skb)
+{
+
+ struct mpoa_client *mpc = find_mpc_by_vcc(vcc);
+ struct k_message *mesg = (struct k_message*)skb->data;
+ atomic_sub(skb->truesize, &vcc->sk->wmem_alloc);
+
+ if (mpc == NULL) {
+ printk("mpoa: msg_from_mpoad: no mpc found\n");
+ return 0;
+ }
+ dprintk("mpoa: (%s) msg_from_mpoad:", (mpc->dev) ? mpc->dev->name : "<unknown>");
+ switch(mesg->type) {
+ case MPOA_RES_REPLY_RCVD:
+ dprintk(" mpoa_res_reply_rcvd\n");
+ MPOA_res_reply_rcvd(mesg, mpc);
+ break;
+ case MPOA_TRIGGER_RCVD:
+ dprintk(" mpoa_trigger_rcvd\n");
+ MPOA_trigger_rcvd(mesg, mpc);
+ break;
+ case INGRESS_PURGE_RCVD:
+ dprintk(" nhrp_purge_rcvd\n");
+ ingress_purge_rcvd(mesg, mpc);
+ break;
+ case EGRESS_PURGE_RCVD:
+ dprintk(" egress_purge_reply_rcvd\n");
+ egress_purge_rcvd(mesg, mpc);
+ break;
+ case MPS_DEATH:
+ dprintk(" mps_death\n");
+ mps_death(mesg, mpc);
+ break;
+ case CACHE_IMPOS_RCVD:
+ dprintk(" cache_impos_rcvd\n");
+ MPOA_cache_impos_rcvd(mesg, mpc);
+ break;
+ case SET_MPC_CTRL_ADDR:
+ dprintk(" set_mpc_ctrl_addr\n");
+ set_mpc_ctrl_addr_rcvd(mesg, mpc);
+ break;
+ case SET_MPS_MAC_ADDR:
+ dprintk(" set_mps_mac_addr\n");
+ set_mps_mac_addr_rcvd(mesg, mpc);
+ break;
+ case CLEAN_UP_AND_EXIT:
+ dprintk(" clean_up_and_exit\n");
+ clean_up(mesg, mpc, DIE);
+ break;
+ case RELOAD:
+ dprintk(" reload\n");
+ clean_up(mesg, mpc, RELOAD);
+ break;
+ case SET_MPC_PARAMS:
+ dprintk(" set_mpc_params\n");
+ mpc->parameters = mesg->content.params;
+ break;
+ default:
+ dprintk(" unknown message %d\n", mesg->type);
+ break;
+ }
+ kfree_skb(skb);
+
+ return 0;
+}
+
+/* Remember that this function may not do things that sleep */
+int msg_to_mpoad(struct k_message *mesg, struct mpoa_client *mpc)
+{
+ struct sk_buff *skb;
+
+ if (mpc == NULL || !mpc->mpoad_vcc) {
+ printk("mpoa: msg_to_mpoad: mesg %d to a non-existent mpoad\n", mesg->type);
+ return -ENXIO;
+ }
+
+ skb = alloc_skb(sizeof(struct k_message), GFP_ATOMIC);
+ if (skb == NULL)
+ return -ENOMEM;
+ skb_put(skb, sizeof(struct k_message));
+ memcpy(skb->data, mesg, sizeof(struct k_message));
+ atm_force_charge(mpc->mpoad_vcc, skb->truesize);
+ skb_queue_tail(&mpc->mpoad_vcc->sk->receive_queue, skb);
+ wake_up(&mpc->mpoad_vcc->sleep);
+
+ return 0;
+}
+
+static int mpoa_event_listener(struct notifier_block *mpoa_notifier, unsigned long event, void *dev_ptr)
+{
+ struct net_device *dev;
+ struct mpoa_client *mpc;
+ struct lec_priv *priv;
+
+ dev = (struct net_device *)dev_ptr;
+ if (dev->name == NULL || strncmp(dev->name, "lec", 3))
+ return NOTIFY_DONE; /* we are only interested in lec:s */
+
+ switch (event) {
+ case NETDEV_REGISTER: /* a new lec device was allocated */
+ priv = (struct lec_priv *)dev->priv;
+ if (priv->lane_version < 2)
+ break;
+ priv->lane2_ops->associate_indicator = lane2_assoc_ind;
+ mpc = find_mpc_by_itfnum(priv->itfnum);
+ if (mpc == NULL) {
+ dprintk("mpoa: mpoa_event_listener: allocating new mpc for %s\n",
+ dev->name);
+ mpc = alloc_mpc();
+ if (mpc == NULL) {
+ printk("mpoa: mpoa_event_listener: no new mpc");
+ break;
+ }
+ }
+ mpc->dev_num = priv->itfnum;
+ mpc->dev = dev;
+ dev_hold(dev);
+ dprintk("mpoa: (%s) was initialized\n", dev->name);
+ break;
+ case NETDEV_UNREGISTER:
+ /* the lec device was deallocated */
+ mpc = find_mpc_by_lec(dev);
+ if (mpc == NULL)
+ break;
+ dprintk("mpoa: device (%s) was deallocated\n", dev->name);
+ stop_mpc(mpc);
+ dev_put(mpc->dev);
+ mpc->dev = NULL;
+ break;
+ case NETDEV_UP:
+ /* the dev was ifconfig'ed up */
+ mpc = find_mpc_by_lec(dev);
+ if (mpc == NULL)
+ break;
+ if (mpc->mpoad_vcc != NULL) {
+ start_mpc(mpc, dev);
+ }
+ break;
+ case NETDEV_DOWN:
+ /* the dev was ifconfig'ed down */
+ /* this means that the flow of packets from the
+ * upper layer stops
+ */
+ mpc = find_mpc_by_lec(dev);
+ if (mpc == NULL)
+ break;
+ if (mpc->mpoad_vcc != NULL) {
+ stop_mpc(mpc);
+ }
+ break;
+ case NETDEV_REBOOT:
+ case NETDEV_CHANGE:
+ case NETDEV_CHANGEMTU:
+ case NETDEV_CHANGEADDR:
+ case NETDEV_GOING_DOWN:
+ break;
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Functions which are called after a message is received from mpcd.
+ * Msg is reused on purpose.
+ */
+
+
+static void MPOA_trigger_rcvd(struct k_message *msg, struct mpoa_client *mpc)
+{
+ uint32_t dst_ip = msg->content.in_info.in_dst_ip;
+ in_cache_entry *entry;
+
+ entry = mpc->in_ops->get(dst_ip, mpc);
+ if(entry == NULL){
+ entry = mpc->in_ops->add_entry(dst_ip, mpc);
+ entry->entry_state = INGRESS_RESOLVING;
+ msg->type = SND_MPOA_RES_RQST;
+ msg->content.in_info = entry->ctrl_info;
+ msg_to_mpoad(msg, mpc);
+ do_gettimeofday(&(entry->reply_wait));
+ mpc->in_ops->put(entry);
+ return;
+ }
+
+ if(entry->entry_state == INGRESS_INVALID){
+ entry->entry_state = INGRESS_RESOLVING;
+ msg->type = SND_MPOA_RES_RQST;
+ msg->content.in_info = entry->ctrl_info;
+ msg_to_mpoad(msg, mpc);
+ do_gettimeofday(&(entry->reply_wait));
+ mpc->in_ops->put(entry);
+ return;
+ }
+
+ printk("mpoa: (%s) MPOA_trigger_rcvd: entry already in resolving state\n",
+ (mpc->dev) ? mpc->dev->name : "<unknown>");
+ mpc->in_ops->put(entry);
+ return;
+}
+
+/*
+ * Things get complicated because we have to check if there's an egress
+ * shortcut with suitable traffic parameters we could use.
+ */
+static void check_qos_and_open_shortcut(struct k_message *msg, struct mpoa_client *client, in_cache_entry *entry)
+{
+ uint32_t dst_ip = msg->content.in_info.in_dst_ip;
+ unsigned char *ip __attribute__ ((unused)) = (unsigned char *)&dst_ip;
+ struct atm_mpoa_qos *qos = atm_mpoa_search_qos(dst_ip);
+ eg_cache_entry *eg_entry = client->eg_ops->get_by_src_ip(dst_ip, client);
+
+ if(eg_entry && eg_entry->shortcut){
+ if(eg_entry->shortcut->qos.txtp.traffic_class &
+ msg->qos.txtp.traffic_class &
+ (qos ? qos->qos.txtp.traffic_class : ATM_UBR | ATM_CBR)){
+ if(eg_entry->shortcut->qos.txtp.traffic_class == ATM_UBR)
+ entry->shortcut = eg_entry->shortcut;
+ else if(eg_entry->shortcut->qos.txtp.max_pcr > 0)
+ entry->shortcut = eg_entry->shortcut;
+ }
+ if(entry->shortcut){
+ dprintk("mpoa: (%s) using egress SVC to reach %u.%u.%u.%u\n",client->dev->name, NIPQUAD(ip));
+ client->eg_ops->put(eg_entry);
+ return;
+ }
+ }
+ if (eg_entry != NULL)
+ client->eg_ops->put(eg_entry);
+
+ /* No luck in the egress cache we must open an ingress SVC */
+ msg->type = OPEN_INGRESS_SVC;
+ if (qos && (qos->qos.txtp.traffic_class == msg->qos.txtp.traffic_class))
+ {
+ msg->qos = qos->qos;
+ printk("mpoa: (%s) trying to get a CBR shortcut\n",client->dev->name);
+ }
+ else memset(&msg->qos,0,sizeof(struct atm_qos));
+ msg_to_mpoad(msg, client);
+ return;
+}
+
+static void MPOA_res_reply_rcvd(struct k_message *msg, struct mpoa_client *mpc)
+{
+ unsigned char *ip;
+
+ uint32_t dst_ip = msg->content.in_info.in_dst_ip;
+ in_cache_entry *entry = mpc->in_ops->get(dst_ip, mpc);
+ ip = (unsigned char *)&dst_ip;
+ dprintk("mpoa: (%s) MPOA_res_reply_rcvd: ip %u.%u.%u.%u\n", mpc->dev->name, NIPQUAD(ip));
+ ddprintk("mpoa: (%s) MPOA_res_reply_rcvd() entry = %p", mpc->dev->name, entry);
+ if(entry == NULL){
+ printk("\nmpoa: (%s) ARGH, received res. reply for an entry that doesn't exist.\n", mpc->dev->name);
+ return;
+ }
+ ddprintk(" entry_state = %d ", entry->entry_state);
+
+ if (entry->entry_state == INGRESS_RESOLVED) {
+ printk("\nmpoa: (%s) MPOA_res_reply_rcvd for RESOLVED entry!\n", mpc->dev->name);
+ mpc->in_ops->put(entry);
+ return;
+ }
+
+ entry->ctrl_info = msg->content.in_info;
+ do_gettimeofday(&(entry->tv));
+ do_gettimeofday(&(entry->reply_wait)); /* Used in refreshing func from now on */
+ entry->refresh_time = 0;
+ ddprintk("entry->shortcut = %p\n", entry->shortcut);
+
+ if(entry->entry_state == INGRESS_RESOLVING && entry->shortcut != NULL){
+ entry->entry_state = INGRESS_RESOLVED;
+ mpc->in_ops->put(entry);
+ return; /* Shortcut already open... */
+ }
+
+ if (entry->shortcut != NULL) {
+ printk("mpoa: (%s) MPOA_res_reply_rcvd: entry->shortcut != NULL, impossible!\n",
+ mpc->dev->name);
+ mpc->in_ops->put(entry);
+ return;
+ }
+
+ check_qos_and_open_shortcut(msg, mpc, entry);
+ entry->entry_state = INGRESS_RESOLVED;
+ mpc->in_ops->put(entry);
+
+ return;
+
+}
+
+static void ingress_purge_rcvd(struct k_message *msg, struct mpoa_client *mpc)
+{
+ uint32_t dst_ip = msg->content.in_info.in_dst_ip;
+ uint32_t mask = msg->ip_mask;
+ unsigned char *ip = (unsigned char *)&dst_ip;
+ in_cache_entry *entry = mpc->in_ops->get_with_mask(dst_ip, mpc, mask);
+
+ if(entry == NULL){
+ printk("mpoa: (%s) ingress_purge_rcvd: purge for a non-existing entry, ", mpc->dev->name);
+ printk("ip = %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]);
+ return;
+ }
+
+ do {
+ dprintk("mpoa: (%s) ingress_purge_rcvd: removing an ingress entry, ip = %u.%u.%u.%u\n" ,
+ mpc->dev->name, ip[0], ip[1], ip[2], ip[3]);
+ write_lock_bh(&mpc->ingress_lock);
+ mpc->in_ops->remove_entry(entry, mpc);
+ write_unlock_bh(&mpc->ingress_lock);
+ mpc->in_ops->put(entry);
+ entry = mpc->in_ops->get_with_mask(dst_ip, mpc, mask);
+ } while (entry != NULL);
+
+ return;
+}
+
+static void egress_purge_rcvd(struct k_message *msg, struct mpoa_client *mpc)
+{
+ uint32_t cache_id = msg->content.eg_info.cache_id;
+ eg_cache_entry *entry = mpc->eg_ops->get_by_cache_id(cache_id, mpc);
+
+ if (entry == NULL) {
+ dprintk("mpoa: (%s) egress_purge_rcvd: purge for a non-existing entry\n", mpc->dev->name);
+ return;
+ }
+
+ write_lock_irq(&mpc->egress_lock);
+ mpc->eg_ops->remove_entry(entry, mpc);
+ write_unlock_irq(&mpc->egress_lock);
+
+ mpc->eg_ops->put(entry);
+
+ return;
+}
+
+static void purge_egress_shortcut(struct atm_vcc *vcc, eg_cache_entry *entry)
+{
+ struct k_message *purge_msg;
+ struct sk_buff *skb;
+
+ dprintk("mpoa: purge_egress_shortcut: entering\n");
+ if (vcc == NULL) {
+ printk("mpoa: purge_egress_shortcut: vcc == NULL\n");
+ return;
+ }
+
+ skb = alloc_skb(sizeof(struct k_message), GFP_ATOMIC);
+ if (skb == NULL) {
+ printk("mpoa: purge_egress_shortcut: out of memory\n");
+ return;
+ }
+
+ skb_put(skb, sizeof(struct k_message));
+ memset(skb->data, 0, sizeof(struct k_message));
+ purge_msg = (struct k_message *)skb->data;
+ purge_msg->type = DATA_PLANE_PURGE;
+ if (entry != NULL)
+ purge_msg->content.eg_info = entry->ctrl_info;
+
+ atm_force_charge(vcc, skb->truesize);
+ skb_queue_tail(&vcc->sk->receive_queue, skb);
+ wake_up(&vcc->sleep);
+ dprintk("mpoa: purge_egress_shortcut: exiting:\n");
+
+ return;
+}
+
+/*
+ * Our MPS died. Tell our daemon to send NHRP data plane purge to each
+ * of the egress shortcuts we have.
+ */
+static void mps_death( struct k_message * msg, struct mpoa_client * mpc )
+{
+ eg_cache_entry *entry;
+
+ dprintk("mpoa: (%s) mps_death:\n", mpc->dev->name);
+
+ if(memcmp(msg->MPS_ctrl, mpc->mps_ctrl_addr, ATM_ESA_LEN)){
+ printk("mpoa: (%s) mps_death: wrong MPS\n", mpc->dev->name);
+ return;
+ }
+
+ /* FIXME: This knows too much of the cache structure */
+ read_lock_irq(&mpc->egress_lock);
+ entry = mpc->eg_cache;
+ while (entry != NULL) {
+ purge_egress_shortcut(entry->shortcut, entry);
+ entry = entry->next;
+ }
+ read_unlock_irq(&mpc->egress_lock);
+
+ mpc->in_ops->destroy_cache(mpc);
+ mpc->eg_ops->destroy_cache(mpc);
+
+ return;
+}
+
+static void MPOA_cache_impos_rcvd( struct k_message * msg, struct mpoa_client * mpc)
+{
+ uint16_t holding_time;
+ eg_cache_entry *entry = mpc->eg_ops->get_by_cache_id(msg->content.eg_info.cache_id, mpc);
+
+ holding_time = msg->content.eg_info.holding_time;
+ dprintk("mpoa: (%s) MPOA_cache_impos_rcvd: entry = %p, holding_time = %u\n",
+ mpc->dev->name, entry, holding_time);
+ if(entry == NULL && holding_time) {
+ entry = mpc->eg_ops->add_entry(msg, mpc);
+ mpc->eg_ops->put(entry);
+ return;
+ }
+ if(holding_time){
+ mpc->eg_ops->update(entry, holding_time);
+ return;
+ }
+
+ write_lock_irq(&mpc->egress_lock);
+ mpc->eg_ops->remove_entry(entry, mpc);
+ write_unlock_irq(&mpc->egress_lock);
+
+ mpc->eg_ops->put(entry);
+
+ return;
+}
+
+static void set_mpc_ctrl_addr_rcvd(struct k_message *mesg, struct mpoa_client *mpc)
+{
+ struct lec_priv *priv;
+ int i, retval ;
+
+ uint8_t tlv[4 + 1 + 1 + 1 + ATM_ESA_LEN];
+
+ tlv[0] = 00; tlv[1] = 0xa0; tlv[2] = 0x3e; tlv[3] = 0x2a; /* type */
+ tlv[4] = 1 + 1 + ATM_ESA_LEN; /* length */
+ tlv[5] = 0x02; /* MPOA client */
+ tlv[6] = 0x00; /* number of MPS MAC addresses */
+
+ memcpy(&tlv[7], mesg->MPS_ctrl, ATM_ESA_LEN); /* MPC ctrl ATM addr */
+ memcpy(mpc->our_ctrl_addr, mesg->MPS_ctrl, ATM_ESA_LEN);
+
+ dprintk("mpoa: (%s) setting MPC ctrl ATM address to ",
+ (mpc->dev) ? mpc->dev->name : "<unknown>");
+ for (i = 7; i < sizeof(tlv); i++)
+ dprintk("%02x ", tlv[i]);
+ dprintk("\n");
+
+ if (mpc->dev) {
+ priv = (struct lec_priv *)mpc->dev->priv;
+ retval = priv->lane2_ops->associate_req(mpc->dev, mpc->dev->dev_addr, tlv, sizeof(tlv));
+ if (retval == 0)
+ printk("mpoa: (%s) MPOA device type TLV association failed\n", mpc->dev->name);
+ retval = priv->lane2_ops->resolve(mpc->dev, NULL, 1, NULL, NULL);
+ if (retval < 0)
+ printk("mpoa: (%s) targetless LE_ARP request failed\n", mpc->dev->name);
+ }
+
+ return;
+}
+
+static void set_mps_mac_addr_rcvd(struct k_message *msg, struct mpoa_client *client)
+{
+
+ if(client->number_of_mps_macs)
+ kfree(client->mps_macs);
+ client->number_of_mps_macs = 0;
+ client->mps_macs = kmalloc(ETH_ALEN,GFP_KERNEL);
+ if (client->mps_macs == NULL) {
+ printk("mpoa: set_mps_mac_addr_rcvd: out of memory\n");
+ return;
+ }
+ client->number_of_mps_macs = 1;
+ memcpy(client->mps_macs, msg->MPS_ctrl, ETH_ALEN);
+
+ return;
+}
+
+/*
+ * purge egress cache and tell daemon to 'action' (DIE, RELOAD)
+ */
+static void clean_up(struct k_message *msg, struct mpoa_client *mpc, int action)
+{
+
+ eg_cache_entry *entry;
+ msg->type = SND_EGRESS_PURGE;
+
+
+ /* FIXME: This knows too much of the cache structure */
+ read_lock_irq(&mpc->egress_lock);
+ entry = mpc->eg_cache;
+ while (entry != NULL){
+ msg->content.eg_info = entry->ctrl_info;
+ dprintk("mpoa: cache_id %u\n", entry->ctrl_info.cache_id);
+ msg_to_mpoad(msg, mpc);
+ entry = entry->next;
+ }
+ read_unlock_irq(&mpc->egress_lock);
+
+ msg->type = action;
+ msg_to_mpoad(msg, mpc);
+ return;
+}
+
+static void mpc_timer_refresh()
+{
+ mpc_timer.expires = jiffies + (MPC_P2 * HZ);
+ mpc_timer.data = mpc_timer.expires;
+ mpc_timer.function = mpc_cache_check;
+ add_timer(&mpc_timer);
+
+ return;
+}
+
+static void mpc_cache_check( unsigned long checking_time )
+{
+ struct mpoa_client *mpc = mpcs;
+ static unsigned long previous_resolving_check_time = 0;
+ static unsigned long previous_refresh_time = 0;
+
+ while( mpc != NULL ){
+ mpc->in_ops->clear_count(mpc);
+ mpc->eg_ops->clear_expired(mpc);
+ if(checking_time - previous_resolving_check_time > mpc->parameters.mpc_p4 * HZ ){
+ mpc->in_ops->check_resolving(mpc);
+ previous_resolving_check_time = checking_time;
+ }
+ if(checking_time - previous_refresh_time > mpc->parameters.mpc_p5 * HZ ){
+ mpc->in_ops->refresh(mpc);
+ previous_refresh_time = checking_time;
+ }
+ mpc = mpc->next;
+ }
+ mpc_timer_refresh();
+
+ return;
+}
+
+static struct atm_mpoa_ops __atm_mpoa_ops = {
+ .mpoad_attach = atm_mpoa_mpoad_attach,
+ .vcc_attach = atm_mpoa_vcc_attach,
+ .owner = THIS_MODULE
+};
+
+static __init int atm_mpoa_init(void)
+{
+ atm_mpoa_ops_set(&__atm_mpoa_ops);
+
+#ifdef CONFIG_PROC_FS
+ if (mpc_proc_init() != 0)
+ printk(KERN_INFO "mpoa: failed to initialize /proc/mpoa\n");
+ else
+ printk(KERN_INFO "mpoa: /proc/mpoa initialized\n");
+#endif
+
+ printk("mpc.c: " __DATE__ " " __TIME__ " initialized\n");
+
+ return 0;
+}
+
+void __exit atm_mpoa_cleanup(void)
+{
+ struct mpoa_client *mpc, *tmp;
+ struct atm_mpoa_qos *qos, *nextqos;
+ struct lec_priv *priv;
+
+ if (MOD_IN_USE) {
+ printk("mpc.c: module in use\n");
+ return;
+ }
+#ifdef CONFIG_PROC_FS
+ mpc_proc_clean();
+#endif
+
+ del_timer(&mpc_timer);
+ unregister_netdevice_notifier(&mpoa_notifier);
+ atm_mpoa_ops_set(NULL);
+
+ mpc = mpcs;
+ mpcs = NULL;
+ while (mpc != NULL) {
+ tmp = mpc->next;
+ if (mpc->dev != NULL) {
+ stop_mpc(mpc);
+ priv = (struct lec_priv *)mpc->dev->priv;
+ if (priv->lane2_ops != NULL)
+ priv->lane2_ops->associate_indicator = NULL;
+ }
+ ddprintk("mpoa: cleanup_module: about to clear caches\n");
+ mpc->in_ops->destroy_cache(mpc);
+ mpc->eg_ops->destroy_cache(mpc);
+ ddprintk("mpoa: cleanup_module: caches cleared\n");
+ kfree(mpc->mps_macs);
+ memset(mpc, 0, sizeof(struct mpoa_client));
+ ddprintk("mpoa: cleanup_module: about to kfree %p\n", mpc);
+ kfree(mpc);
+ ddprintk("mpoa: cleanup_module: next mpc is at %p\n", tmp);
+ mpc = tmp;
+ }
+
+ qos = qos_head;
+ qos_head = NULL;
+ while (qos != NULL) {
+ nextqos = qos->next;
+ dprintk("mpoa: cleanup_module: freeing qos entry %p\n", qos);
+ kfree(qos);
+ qos = nextqos;
+ }
+
+ return;
+}
+
+module_init(atm_mpoa_init);
+module_exit(atm_mpoa_cleanup);
+
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/atm/mpc.h b/uClinux-2.4.31-uc0/net/atm/mpc.h
new file mode 100644
index 0000000..f0b33db
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/mpc.h
@@ -0,0 +1,67 @@
+#ifndef _MPC_H_
+#define _MPC_H_
+
+#include <linux/atm.h>
+#include <linux/atmmpc.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include "mpoa_caches.h"
+
+/* kernel -> mpc-daemon */
+int msg_to_mpoad(struct k_message *msg, struct mpoa_client *mpc);
+
+/* Functions for ioctl(ATMMPC_*) operations */
+int atm_mpoa_mpoad_attach(struct atm_vcc *vcc, int arg);
+int atm_mpoa_vcc_attach(struct atm_vcc *vcc, long arg);
+
+struct mpoa_client {
+ struct mpoa_client *next;
+ struct net_device *dev; /* lec in question */
+ int dev_num; /* e.g. 2 for lec2 */
+ int (*old_hard_start_xmit)(struct sk_buff *skb, struct net_device *dev);
+ struct atm_vcc *mpoad_vcc; /* control channel to mpoad */
+ uint8_t mps_ctrl_addr[ATM_ESA_LEN]; /* MPS control ATM address */
+ uint8_t our_ctrl_addr[ATM_ESA_LEN]; /* MPC's control ATM address */
+
+ rwlock_t ingress_lock;
+ struct in_cache_ops *in_ops; /* ingress cache operations */
+ in_cache_entry *in_cache; /* the ingress cache of this MPC */
+
+ rwlock_t egress_lock;
+ struct eg_cache_ops *eg_ops; /* egress cache operations */
+ eg_cache_entry *eg_cache; /* the egress cache of this MPC */
+
+ uint8_t *mps_macs; /* array of MPS MAC addresses, >=1 */
+ int number_of_mps_macs; /* number of the above MAC addresses */
+ struct mpc_parameters parameters; /* parameters for this client */
+};
+
+
+struct atm_mpoa_qos {
+ struct atm_mpoa_qos *next;
+ uint32_t ipaddr;
+ struct atm_qos qos;
+};
+
+
+/* Functions to call during ioctl(ATMMPC, ) */
+struct atm_mpoa_ops {
+ int (*mpoad_attach)(struct atm_vcc *vcc, int arg); /* attach mpoa daemon */
+ int (*vcc_attach)(struct atm_vcc *vcc, long arg); /* attach shortcut vcc */
+ struct module *owner;
+};
+
+/* Boot/module initialization function */
+extern struct atm_mpoa_ops *atm_mpoa_ops;
+int try_atm_mpoa_ops(void);
+void atm_mpoa_ops_set(struct atm_mpoa_ops *hook);
+
+/* MPOA QoS operations */
+struct atm_mpoa_qos *atm_mpoa_add_qos(uint32_t dst_ip, struct atm_qos *qos);
+struct atm_mpoa_qos *atm_mpoa_search_qos(uint32_t dst_ip);
+int atm_mpoa_delete_qos(struct atm_mpoa_qos *qos);
+
+/* Display QoS entries. This is for the procfs */
+void atm_mpoa_disp_qos(char *page, int *len);
+
+#endif /* _MPC_H_ */
diff --git a/uClinux-2.4.31-uc0/net/atm/mpoa_caches.c b/uClinux-2.4.31-uc0/net/atm/mpoa_caches.c
new file mode 100644
index 0000000..64ddebb
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/mpoa_caches.c
@@ -0,0 +1,576 @@
+#include <linux/types.h>
+#include <linux/atmmpc.h>
+#include <linux/time.h>
+
+#include "mpoa_caches.h"
+#include "mpc.h"
+
+/*
+ * mpoa_caches.c: Implementation of ingress and egress cache
+ * handling functions
+ */
+
+#if 0
+#define dprintk printk /* debug */
+#else
+#define dprintk(format,args...)
+#endif
+
+#if 0
+#define ddprintk printk /* more debug */
+#else
+#define ddprintk(format,args...)
+#endif
+
+static in_cache_entry *in_cache_get(uint32_t dst_ip,
+ struct mpoa_client *client)
+{
+ in_cache_entry *entry;
+
+ read_lock_bh(&client->ingress_lock);
+ entry = client->in_cache;
+ while(entry != NULL){
+ if( entry->ctrl_info.in_dst_ip == dst_ip ){
+ atomic_inc(&entry->use);
+ read_unlock_bh(&client->ingress_lock);
+ return entry;
+ }
+ entry = entry->next;
+ }
+ read_unlock_bh(&client->ingress_lock);
+
+ return NULL;
+}
+
+static in_cache_entry *in_cache_get_with_mask(uint32_t dst_ip,
+ struct mpoa_client *client,
+ uint32_t mask)
+{
+ in_cache_entry *entry;
+
+ read_lock_bh(&client->ingress_lock);
+ entry = client->in_cache;
+ while(entry != NULL){
+ if((entry->ctrl_info.in_dst_ip & mask) == (dst_ip & mask )){
+ atomic_inc(&entry->use);
+ read_unlock_bh(&client->ingress_lock);
+ return entry;
+ }
+ entry = entry->next;
+ }
+ read_unlock_bh(&client->ingress_lock);
+
+ return NULL;
+
+}
+
+static in_cache_entry *in_cache_get_by_vcc(struct atm_vcc *vcc,
+ struct mpoa_client *client )
+{
+ in_cache_entry *entry;
+
+ read_lock_bh(&client->ingress_lock);
+ entry = client->in_cache;
+ while(entry != NULL){
+ if(entry->shortcut == vcc) {
+ atomic_inc(&entry->use);
+ read_unlock_bh(&client->ingress_lock);
+ return entry;
+ }
+ entry = entry->next;
+ }
+ read_unlock_bh(&client->ingress_lock);
+
+ return NULL;
+}
+
+static in_cache_entry *in_cache_add_entry(uint32_t dst_ip,
+ struct mpoa_client *client)
+{
+ unsigned char *ip __attribute__ ((unused)) = (unsigned char *)&dst_ip;
+ in_cache_entry* entry = kmalloc(sizeof(in_cache_entry), GFP_KERNEL);
+
+ if (entry == NULL) {
+ printk("mpoa: mpoa_caches.c: new_in_cache_entry: out of memory\n");
+ return NULL;
+ }
+
+ dprintk("mpoa: mpoa_caches.c: adding an ingress entry, ip = %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]);
+ memset(entry,0,sizeof(in_cache_entry));
+
+ atomic_set(&entry->use, 1);
+ dprintk("mpoa: mpoa_caches.c: new_in_cache_entry: about to lock\n");
+ write_lock_bh(&client->ingress_lock);
+ entry->next = client->in_cache;
+ entry->prev = NULL;
+ if (client->in_cache != NULL)
+ client->in_cache->prev = entry;
+ client->in_cache = entry;
+
+ memcpy(entry->MPS_ctrl_ATM_addr, client->mps_ctrl_addr, ATM_ESA_LEN);
+ entry->ctrl_info.in_dst_ip = dst_ip;
+ do_gettimeofday(&(entry->tv));
+ entry->retry_time = client->parameters.mpc_p4;
+ entry->count = 1;
+ entry->entry_state = INGRESS_INVALID;
+ entry->ctrl_info.holding_time = HOLDING_TIME_DEFAULT;
+ atomic_inc(&entry->use);
+
+ write_unlock_bh(&client->ingress_lock);
+ dprintk("mpoa: mpoa_caches.c: new_in_cache_entry: unlocked\n");
+
+ return entry;
+}
+
+static int cache_hit(in_cache_entry *entry, struct mpoa_client *mpc)
+{
+ struct atm_mpoa_qos *qos;
+ struct k_message msg;
+
+ entry->count++;
+ if(entry->entry_state == INGRESS_RESOLVED && entry->shortcut != NULL)
+ return OPEN;
+
+ if(entry->entry_state == INGRESS_REFRESHING){
+ if(entry->count > mpc->parameters.mpc_p1){
+ msg.type = SND_MPOA_RES_RQST;
+ msg.content.in_info = entry->ctrl_info;
+ memcpy(msg.MPS_ctrl, mpc->mps_ctrl_addr, ATM_ESA_LEN);
+ qos = atm_mpoa_search_qos(entry->ctrl_info.in_dst_ip);
+ if (qos != NULL) msg.qos = qos->qos;
+ msg_to_mpoad(&msg, mpc);
+ do_gettimeofday(&(entry->reply_wait));
+ entry->entry_state = INGRESS_RESOLVING;
+ }
+ if(entry->shortcut != NULL)
+ return OPEN;
+ return CLOSED;
+ }
+
+ if(entry->entry_state == INGRESS_RESOLVING && entry->shortcut != NULL)
+ return OPEN;
+
+ if( entry->count > mpc->parameters.mpc_p1 &&
+ entry->entry_state == INGRESS_INVALID){
+ unsigned char *ip __attribute__ ((unused)) =
+ (unsigned char *)&entry->ctrl_info.in_dst_ip;
+
+ dprintk("mpoa: (%s) mpoa_caches.c: threshold exceeded for ip %u.%u.%u.%u, sending MPOA res req\n", mpc->dev->name, ip[0], ip[1], ip[2], ip[3]);
+ entry->entry_state = INGRESS_RESOLVING;
+ msg.type = SND_MPOA_RES_RQST;
+ memcpy(msg.MPS_ctrl, mpc->mps_ctrl_addr, ATM_ESA_LEN );
+ msg.content.in_info = entry->ctrl_info;
+ qos = atm_mpoa_search_qos(entry->ctrl_info.in_dst_ip);
+ if (qos != NULL) msg.qos = qos->qos;
+ msg_to_mpoad( &msg, mpc);
+ do_gettimeofday(&(entry->reply_wait));
+ }
+
+ return CLOSED;
+}
+
+static void in_cache_put(in_cache_entry *entry)
+{
+ if (atomic_dec_and_test(&entry->use)) {
+ memset(entry, 0, sizeof(in_cache_entry));
+ kfree(entry);
+ }
+
+ return;
+}
+
+/*
+ * This should be called with write lock on
+ */
+static void in_cache_remove_entry(in_cache_entry *entry,
+ struct mpoa_client *client)
+{
+ struct atm_vcc *vcc;
+ struct k_message msg;
+ unsigned char *ip;
+
+ vcc = entry->shortcut;
+ ip = (unsigned char *)&entry->ctrl_info.in_dst_ip;
+ dprintk("mpoa: mpoa_caches.c: removing an ingress entry, ip = %u.%u.%u.%u\n",ip[0], ip[1], ip[2], ip[3]);
+
+ if (entry->prev != NULL)
+ entry->prev->next = entry->next;
+ else
+ client->in_cache = entry->next;
+ if (entry->next != NULL)
+ entry->next->prev = entry->prev;
+ client->in_ops->put(entry);
+ if(client->in_cache == NULL && client->eg_cache == NULL){
+ msg.type = STOP_KEEP_ALIVE_SM;
+ msg_to_mpoad(&msg,client);
+ }
+
+ /* Check if the egress side still uses this VCC */
+ if (vcc != NULL) {
+ eg_cache_entry *eg_entry = client->eg_ops->get_by_vcc(vcc, client);
+ if (eg_entry != NULL) {
+ client->eg_ops->put(eg_entry);
+ return;
+ }
+ vcc_release_async(vcc, -EPIPE);
+ }
+
+ return;
+}
+
+
+/* Call this every MPC-p2 seconds... Not exactly correct solution,
+ but an easy one... */
+static void clear_count_and_expired(struct mpoa_client *client)
+{
+ unsigned char *ip;
+ in_cache_entry *entry, *next_entry;
+ struct timeval now;
+
+ do_gettimeofday(&now);
+
+ write_lock_bh(&client->ingress_lock);
+ entry = client->in_cache;
+ while(entry != NULL){
+ entry->count=0;
+ next_entry = entry->next;
+ if((now.tv_sec - entry->tv.tv_sec)
+ > entry->ctrl_info.holding_time){
+ ip = (unsigned char*)&entry->ctrl_info.in_dst_ip;
+ dprintk("mpoa: mpoa_caches.c: holding time expired, ip = %u.%u.%u.%u\n", NIPQUAD(ip));
+ client->in_ops->remove_entry(entry, client);
+ }
+ entry = next_entry;
+ }
+ write_unlock_bh(&client->ingress_lock);
+
+ return;
+}
+
+/* Call this every MPC-p4 seconds. */
+static void check_resolving_entries(struct mpoa_client *client)
+{
+
+ struct atm_mpoa_qos *qos;
+ in_cache_entry *entry;
+ struct timeval now;
+ struct k_message msg;
+
+ do_gettimeofday( &now );
+
+ read_lock_bh(&client->ingress_lock);
+ entry = client->in_cache;
+ while( entry != NULL ){
+ if(entry->entry_state == INGRESS_RESOLVING){
+ if(now.tv_sec - entry->hold_down.tv_sec < client->parameters.mpc_p6){
+ entry = entry->next; /* Entry in hold down */
+ continue;
+ }
+ if( (now.tv_sec - entry->reply_wait.tv_sec) >
+ entry->retry_time ){
+ entry->retry_time = MPC_C1*( entry->retry_time );
+ if(entry->retry_time > client->parameters.mpc_p5){
+ /* Retry time maximum exceeded, put entry in hold down. */
+ do_gettimeofday(&(entry->hold_down));
+ entry->retry_time = client->parameters.mpc_p4;
+ entry = entry->next;
+ continue;
+ }
+ /* Ask daemon to send a resolution request. */
+ memset(&(entry->hold_down),0,sizeof(struct timeval));
+ msg.type = SND_MPOA_RES_RTRY;
+ memcpy(msg.MPS_ctrl, client->mps_ctrl_addr, ATM_ESA_LEN);
+ msg.content.in_info = entry->ctrl_info;
+ qos = atm_mpoa_search_qos(entry->ctrl_info.in_dst_ip);
+ if (qos != NULL) msg.qos = qos->qos;
+ msg_to_mpoad(&msg, client);
+ do_gettimeofday(&(entry->reply_wait));
+ }
+ }
+ entry = entry->next;
+ }
+ read_unlock_bh(&client->ingress_lock);
+}
+
+/* Call this every MPC-p5 seconds. */
+static void refresh_entries(struct mpoa_client *client)
+{
+ struct timeval now;
+ struct in_cache_entry *entry = client->in_cache;
+
+ ddprintk("mpoa: mpoa_caches.c: refresh_entries\n");
+ do_gettimeofday(&now);
+
+ read_lock_bh(&client->ingress_lock);
+ while( entry != NULL ){
+ if( entry->entry_state == INGRESS_RESOLVED ){
+ if(!(entry->refresh_time))
+ entry->refresh_time = (2*(entry->ctrl_info.holding_time))/3;
+ if( (now.tv_sec - entry->reply_wait.tv_sec) > entry->refresh_time ){
+ dprintk("mpoa: mpoa_caches.c: refreshing an entry.\n");
+ entry->entry_state = INGRESS_REFRESHING;
+
+ }
+ }
+ entry = entry->next;
+ }
+ read_unlock_bh(&client->ingress_lock);
+}
+
+static void in_destroy_cache(struct mpoa_client *mpc)
+{
+ write_lock_irq(&mpc->ingress_lock);
+ while(mpc->in_cache != NULL)
+ mpc->in_ops->remove_entry(mpc->in_cache, mpc);
+ write_unlock_irq(&mpc->ingress_lock);
+
+ return;
+}
+
+static eg_cache_entry *eg_cache_get_by_cache_id(uint32_t cache_id, struct mpoa_client *mpc)
+{
+ eg_cache_entry *entry;
+
+ read_lock_irq(&mpc->egress_lock);
+ entry = mpc->eg_cache;
+ while(entry != NULL){
+ if(entry->ctrl_info.cache_id == cache_id){
+ atomic_inc(&entry->use);
+ read_unlock_irq(&mpc->egress_lock);
+ return entry;
+ }
+ entry = entry->next;
+ }
+ read_unlock_irq(&mpc->egress_lock);
+
+ return NULL;
+}
+
+/* This can be called from any context since it saves CPU flags */
+static eg_cache_entry *eg_cache_get_by_tag(uint32_t tag, struct mpoa_client *mpc)
+{
+ unsigned long flags;
+ eg_cache_entry *entry;
+
+ read_lock_irqsave(&mpc->egress_lock, flags);
+ entry = mpc->eg_cache;
+ while (entry != NULL){
+ if (entry->ctrl_info.tag == tag) {
+ atomic_inc(&entry->use);
+ read_unlock_irqrestore(&mpc->egress_lock, flags);
+ return entry;
+ }
+ entry = entry->next;
+ }
+ read_unlock_irqrestore(&mpc->egress_lock, flags);
+
+ return NULL;
+}
+
+/* This can be called from any context since it saves CPU flags */
+static eg_cache_entry *eg_cache_get_by_vcc(struct atm_vcc *vcc, struct mpoa_client *mpc)
+{
+ unsigned long flags;
+ eg_cache_entry *entry;
+
+ read_lock_irqsave(&mpc->egress_lock, flags);
+ entry = mpc->eg_cache;
+ while (entry != NULL){
+ if (entry->shortcut == vcc) {
+ atomic_inc(&entry->use);
+ read_unlock_irqrestore(&mpc->egress_lock, flags);
+ return entry;
+ }
+ entry = entry->next;
+ }
+ read_unlock_irqrestore(&mpc->egress_lock, flags);
+
+ return NULL;
+}
+
+static eg_cache_entry *eg_cache_get_by_src_ip(uint32_t ipaddr, struct mpoa_client *mpc)
+{
+ eg_cache_entry *entry;
+
+ read_lock_irq(&mpc->egress_lock);
+ entry = mpc->eg_cache;
+ while(entry != NULL){
+ if(entry->latest_ip_addr == ipaddr) {
+ atomic_inc(&entry->use);
+ read_unlock_irq(&mpc->egress_lock);
+ return entry;
+ }
+ entry = entry->next;
+ }
+ read_unlock_irq(&mpc->egress_lock);
+
+ return NULL;
+}
+
+static void eg_cache_put(eg_cache_entry *entry)
+{
+ if (atomic_dec_and_test(&entry->use)) {
+ memset(entry, 0, sizeof(eg_cache_entry));
+ kfree(entry);
+ }
+
+ return;
+}
+
+/*
+ * This should be called with write lock on
+ */
+static void eg_cache_remove_entry(eg_cache_entry *entry,
+ struct mpoa_client *client)
+{
+ struct atm_vcc *vcc;
+ struct k_message msg;
+
+ vcc = entry->shortcut;
+ dprintk("mpoa: mpoa_caches.c: removing an egress entry.\n");
+ if (entry->prev != NULL)
+ entry->prev->next = entry->next;
+ else
+ client->eg_cache = entry->next;
+ if (entry->next != NULL)
+ entry->next->prev = entry->prev;
+ client->eg_ops->put(entry);
+ if(client->in_cache == NULL && client->eg_cache == NULL){
+ msg.type = STOP_KEEP_ALIVE_SM;
+ msg_to_mpoad(&msg,client);
+ }
+
+ /* Check if the ingress side still uses this VCC */
+ if (vcc != NULL) {
+ in_cache_entry *in_entry = client->in_ops->get_by_vcc(vcc, client);
+ if (in_entry != NULL) {
+ client->in_ops->put(in_entry);
+ return;
+ }
+ vcc_release_async(vcc, -EPIPE);
+ }
+
+ return;
+}
+
+static eg_cache_entry *eg_cache_add_entry(struct k_message *msg, struct mpoa_client *client)
+{
+ unsigned char *ip;
+ eg_cache_entry *entry = kmalloc(sizeof(eg_cache_entry), GFP_KERNEL);
+
+ if (entry == NULL) {
+ printk("mpoa: mpoa_caches.c: new_eg_cache_entry: out of memory\n");
+ return NULL;
+ }
+
+ ip = (unsigned char *)&msg->content.eg_info.eg_dst_ip;
+ dprintk("mpoa: mpoa_caches.c: adding an egress entry, ip = %u.%u.%u.%u, this should be our IP\n", NIPQUAD(ip));
+ memset(entry, 0, sizeof(eg_cache_entry));
+
+ atomic_set(&entry->use, 1);
+ dprintk("mpoa: mpoa_caches.c: new_eg_cache_entry: about to lock\n");
+ write_lock_irq(&client->egress_lock);
+ entry->next = client->eg_cache;
+ entry->prev = NULL;
+ if (client->eg_cache != NULL)
+ client->eg_cache->prev = entry;
+ client->eg_cache = entry;
+
+ memcpy(entry->MPS_ctrl_ATM_addr, client->mps_ctrl_addr, ATM_ESA_LEN);
+ entry->ctrl_info = msg->content.eg_info;
+ do_gettimeofday(&(entry->tv));
+ entry->entry_state = EGRESS_RESOLVED;
+ dprintk("mpoa: mpoa_caches.c: new_eg_cache_entry cache_id %lu\n", ntohl(entry->ctrl_info.cache_id));
+ ip = (unsigned char *)&entry->ctrl_info.mps_ip;
+ dprintk("mpoa: mpoa_caches.c: mps_ip = %u.%u.%u.%u\n", NIPQUAD(ip));
+ atomic_inc(&entry->use);
+
+ write_unlock_irq(&client->egress_lock);
+ dprintk("mpoa: mpoa_caches.c: new_eg_cache_entry: unlocked\n");
+
+ return entry;
+}
+
+static void update_eg_cache_entry(eg_cache_entry * entry, uint16_t holding_time)
+{
+ do_gettimeofday(&(entry->tv));
+ entry->entry_state = EGRESS_RESOLVED;
+ entry->ctrl_info.holding_time = holding_time;
+
+ return;
+}
+
+static void clear_expired(struct mpoa_client *client)
+{
+ eg_cache_entry *entry, *next_entry;
+ struct timeval now;
+ struct k_message msg;
+
+ do_gettimeofday(&now);
+
+ write_lock_irq(&client->egress_lock);
+ entry = client->eg_cache;
+ while(entry != NULL){
+ next_entry = entry->next;
+ if((now.tv_sec - entry->tv.tv_sec)
+ > entry->ctrl_info.holding_time){
+ msg.type = SND_EGRESS_PURGE;
+ msg.content.eg_info = entry->ctrl_info;
+ dprintk("mpoa: mpoa_caches.c: egress_cache: holding time expired, cache_id = %lu.\n",ntohl(entry->ctrl_info.cache_id));
+ msg_to_mpoad(&msg, client);
+ client->eg_ops->remove_entry(entry, client);
+ }
+ entry = next_entry;
+ }
+ write_unlock_irq(&client->egress_lock);
+
+ return;
+}
+
+static void eg_destroy_cache(struct mpoa_client *mpc)
+{
+ write_lock_irq(&mpc->egress_lock);
+ while(mpc->eg_cache != NULL)
+ mpc->eg_ops->remove_entry(mpc->eg_cache, mpc);
+ write_unlock_irq(&mpc->egress_lock);
+
+ return;
+}
+
+
+
+static struct in_cache_ops ingress_ops = {
+ in_cache_add_entry, /* add_entry */
+ in_cache_get, /* get */
+ in_cache_get_with_mask, /* get_with_mask */
+ in_cache_get_by_vcc, /* get_by_vcc */
+ in_cache_put, /* put */
+ in_cache_remove_entry, /* remove_entry */
+ cache_hit, /* cache_hit */
+ clear_count_and_expired, /* clear_count */
+ check_resolving_entries, /* check_resolving */
+ refresh_entries, /* refresh */
+ in_destroy_cache /* destroy_cache */
+};
+
+static struct eg_cache_ops egress_ops = {
+ eg_cache_add_entry, /* add_entry */
+ eg_cache_get_by_cache_id, /* get_by_cache_id */
+ eg_cache_get_by_tag, /* get_by_tag */
+ eg_cache_get_by_vcc, /* get_by_vcc */
+ eg_cache_get_by_src_ip, /* get_by_src_ip */
+ eg_cache_put, /* put */
+ eg_cache_remove_entry, /* remove_entry */
+ update_eg_cache_entry, /* update */
+ clear_expired, /* clear_expired */
+ eg_destroy_cache /* destroy_cache */
+};
+
+
+void atm_mpoa_init_cache(struct mpoa_client *mpc)
+{
+ mpc->in_ops = &ingress_ops;
+ mpc->eg_ops = &egress_ops;
+
+ return;
+}
diff --git a/uClinux-2.4.31-uc0/net/atm/mpoa_caches.h b/uClinux-2.4.31-uc0/net/atm/mpoa_caches.h
new file mode 100644
index 0000000..6c9886a
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/mpoa_caches.h
@@ -0,0 +1,96 @@
+#ifndef MPOA_CACHES_H
+#define MPOA_CACHES_H
+
+#include <linux/netdevice.h>
+#include <linux/types.h>
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/atmmpc.h>
+
+struct mpoa_client;
+
+void atm_mpoa_init_cache(struct mpoa_client *mpc);
+
+typedef struct in_cache_entry {
+ struct in_cache_entry *next;
+ struct in_cache_entry *prev;
+ struct timeval tv;
+ struct timeval reply_wait;
+ struct timeval hold_down;
+ uint32_t packets_fwded;
+ uint16_t entry_state;
+ uint32_t retry_time;
+ uint32_t refresh_time;
+ uint32_t count;
+ struct atm_vcc *shortcut;
+ uint8_t MPS_ctrl_ATM_addr[ATM_ESA_LEN];
+ struct in_ctrl_info ctrl_info;
+ atomic_t use;
+} in_cache_entry;
+
+struct in_cache_ops{
+ in_cache_entry *(*add_entry)(uint32_t dst_ip,
+ struct mpoa_client *client);
+ in_cache_entry *(*get)(uint32_t dst_ip, struct mpoa_client *client);
+ in_cache_entry *(*get_with_mask)(uint32_t dst_ip,
+ struct mpoa_client *client,
+ uint32_t mask);
+ in_cache_entry *(*get_by_vcc)(struct atm_vcc *vcc,
+ struct mpoa_client *client);
+ void (*put)(in_cache_entry *entry);
+ void (*remove_entry)(in_cache_entry *delEntry,
+ struct mpoa_client *client );
+ int (*cache_hit)(in_cache_entry *entry,
+ struct mpoa_client *client);
+ void (*clear_count)(struct mpoa_client *client);
+ void (*check_resolving)(struct mpoa_client *client);
+ void (*refresh)(struct mpoa_client *client);
+ void (*destroy_cache)(struct mpoa_client *mpc);
+};
+
+typedef struct eg_cache_entry{
+ struct eg_cache_entry *next;
+ struct eg_cache_entry *prev;
+ struct timeval tv;
+ uint8_t MPS_ctrl_ATM_addr[ATM_ESA_LEN];
+ struct atm_vcc *shortcut;
+ uint32_t packets_rcvd;
+ uint16_t entry_state;
+ uint32_t latest_ip_addr; /* The src IP address of the last packet */
+ struct eg_ctrl_info ctrl_info;
+ atomic_t use;
+} eg_cache_entry;
+
+struct eg_cache_ops{
+ eg_cache_entry *(*add_entry)(struct k_message *msg, struct mpoa_client *client);
+ eg_cache_entry *(*get_by_cache_id)(uint32_t cache_id, struct mpoa_client *client);
+ eg_cache_entry *(*get_by_tag)(uint32_t cache_id, struct mpoa_client *client);
+ eg_cache_entry *(*get_by_vcc)(struct atm_vcc *vcc, struct mpoa_client *client);
+ eg_cache_entry *(*get_by_src_ip)(uint32_t ipaddr, struct mpoa_client *client);
+ void (*put)(eg_cache_entry *entry);
+ void (*remove_entry)(eg_cache_entry *entry, struct mpoa_client *client);
+ void (*update)(eg_cache_entry *entry, uint16_t holding_time);
+ void (*clear_expired)(struct mpoa_client *client);
+ void (*destroy_cache)(struct mpoa_client *mpc);
+};
+
+
+/* Ingress cache entry states */
+
+#define INGRESS_REFRESHING 3
+#define INGRESS_RESOLVED 2
+#define INGRESS_RESOLVING 1
+#define INGRESS_INVALID 0
+
+/* VCC states */
+
+#define OPEN 1
+#define CLOSED 0
+
+/* Egress cache entry states */
+
+#define EGRESS_RESOLVED 2
+#define EGRESS_PURGE 1
+#define EGRESS_INVALID 0
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/atm/mpoa_proc.c b/uClinux-2.4.31-uc0/net/atm/mpoa_proc.c
new file mode 100644
index 0000000..8d84f52
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/mpoa_proc.c
@@ -0,0 +1,348 @@
+#include <linux/config.h>
+
+#ifdef CONFIG_PROC_FS
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/time.h>
+#include <asm/uaccess.h>
+#include <linux/atmmpc.h>
+#include <linux/atm.h>
+#include "mpc.h"
+#include "mpoa_caches.h"
+
+/*
+ * mpoa_proc.c: Implementation MPOA client's proc
+ * file system statistics
+ */
+
+#if 1
+#define dprintk printk /* debug */
+#else
+#define dprintk(format,args...)
+#endif
+
+#define STAT_FILE_NAME "mpc" /* Our statistic file's name */
+
+extern struct mpoa_client *mpcs;
+extern struct proc_dir_entry *atm_proc_root; /* from proc.c. */
+
+static ssize_t proc_mpc_read(struct file *file, char *buff,
+ size_t count, loff_t *pos);
+
+static ssize_t proc_mpc_write(struct file *file, const char *buff,
+ size_t nbytes, loff_t *ppos);
+
+static int parse_qos(const char *buff, int len);
+
+/*
+ * Define allowed FILE OPERATIONS
+ */
+static struct file_operations mpc_file_operations = {
+ read: proc_mpc_read,
+ write: proc_mpc_write,
+};
+
+static int print_header(char *buff,struct mpoa_client *mpc){
+ if(mpc != NULL){
+ return sprintf(buff,"\nInterface %d:\n\n",mpc->dev_num);
+
+ }
+ return 0;
+}
+
+/*
+ * Returns the state of an ingress cache entry as a string
+ */
+static const char *ingress_state_string(int state){
+ switch(state) {
+ case INGRESS_RESOLVING:
+ return "resolving ";
+ break;
+ case INGRESS_RESOLVED:
+ return "resolved ";
+ break;
+ case INGRESS_INVALID:
+ return "invalid ";
+ break;
+ case INGRESS_REFRESHING:
+ return "refreshing ";
+ break;
+ default:
+ return "";
+ }
+}
+
+/*
+ * Returns the state of an egress cache entry as a string
+ */
+static const char *egress_state_string(int state){
+ switch(state) {
+ case EGRESS_RESOLVED:
+ return "resolved ";
+ break;
+ case EGRESS_PURGE:
+ return "purge ";
+ break;
+ case EGRESS_INVALID:
+ return "invalid ";
+ break;
+ default:
+ return "";
+ }
+}
+
+/*
+ * READING function - called when the /proc/atm/mpoa file is read from.
+ */
+static ssize_t proc_mpc_read(struct file *file, char *buff,
+ size_t count, loff_t *pos){
+ unsigned long page = 0;
+ unsigned char *temp;
+ int length = 0;
+ int i = 0;
+ struct mpoa_client *mpc = mpcs;
+ in_cache_entry *in_entry;
+ eg_cache_entry *eg_entry;
+ struct timeval now;
+ unsigned char ip_string[16];
+ loff_t n = *pos;
+ if(count == 0)
+ return 0;
+ page = get_free_page(GFP_KERNEL);
+ if(!page)
+ return -ENOMEM;
+ atm_mpoa_disp_qos((char *)page, &length);
+ while(mpc != NULL){
+ length += print_header((char *)page + length, mpc);
+ length += sprintf((char *)page + length,"Ingress Entries:\nIP address State Holding time Packets fwded VPI VCI\n");
+ in_entry = mpc->in_cache;
+ do_gettimeofday(&now);
+ while(in_entry != NULL){
+ temp = (unsigned char *)&in_entry->ctrl_info.in_dst_ip; sprintf(ip_string,"%d.%d.%d.%d", temp[0], temp[1], temp[2], temp[3]);
+ length += sprintf((char *)page + length,"%-16s%s%-14lu%-12u", ip_string, ingress_state_string(in_entry->entry_state), (in_entry->ctrl_info.holding_time-(now.tv_sec-in_entry->tv.tv_sec)), in_entry->packets_fwded);
+ if(in_entry->shortcut)
+ length += sprintf((char *)page + length," %-3d %-3d",in_entry->shortcut->vpi,in_entry->shortcut->vci);
+ length += sprintf((char *)page + length,"\n");
+ in_entry = in_entry->next;
+ }
+ length += sprintf((char *)page + length,"\n");
+ eg_entry = mpc->eg_cache;
+ length += sprintf((char *)page + length,"Egress Entries:\nIngress MPC ATM addr\nCache-id State Holding time Packets recvd Latest IP addr VPI VCI\n");
+ while(eg_entry != NULL){
+ for(i=0;i<ATM_ESA_LEN;i++){
+ length += sprintf((char *)page + length,"%02x",eg_entry->ctrl_info.in_MPC_data_ATM_addr[i]);}
+ length += sprintf((char *)page + length,"\n%-16lu%s%-14lu%-15u",(unsigned long) ntohl(eg_entry->ctrl_info.cache_id), egress_state_string(eg_entry->entry_state), (eg_entry->ctrl_info.holding_time-(now.tv_sec-eg_entry->tv.tv_sec)), eg_entry->packets_rcvd);
+
+ /* latest IP address */
+ temp = (unsigned char *)&eg_entry->latest_ip_addr;
+ sprintf(ip_string, "%d.%d.%d.%d", temp[0], temp[1], temp[2], temp[3]);
+ length += sprintf((char *)page + length, "%-16s", ip_string);
+
+ if(eg_entry->shortcut)
+ length += sprintf((char *)page + length," %-3d %-3d",eg_entry->shortcut->vpi,eg_entry->shortcut->vci);
+ length += sprintf((char *)page + length,"\n");
+ eg_entry = eg_entry->next;
+ }
+ length += sprintf((char *)page + length,"\n");
+ mpc = mpc->next;
+ }
+
+ if (n != (unsigned)n || n >= length) length = 0;
+ else {
+ if (count > length - n) count = length - n;
+ if (copy_to_user(buff, (char *)page , count)) {
+ free_page(page);
+ return -EFAULT;
+ }
+ *pos = n + count;
+ }
+
+ free_page(page);
+ return length;
+}
+
+static ssize_t proc_mpc_write(struct file *file, const char *buff,
+ size_t nbytes, loff_t *ppos)
+{
+ int incoming, error, retval;
+ char *page, c;
+ const char *tmp;
+
+ if (nbytes == 0) return 0;
+ if (nbytes >= PAGE_SIZE) nbytes = PAGE_SIZE-1;
+
+ error = verify_area(VERIFY_READ, buff, nbytes);
+ if (error) return error;
+
+ page = (char *)__get_free_page(GFP_KERNEL);
+ if (page == NULL) return -ENOMEM;
+
+ incoming = 0;
+ tmp = buff;
+ while(incoming < nbytes){
+ if (get_user(c, tmp++)) return -EFAULT;
+ incoming++;
+ if (c == '\0' || c == '\n')
+ break;
+ }
+
+ retval = copy_from_user(page, buff, incoming);
+ if (retval != 0) {
+ printk("mpoa: proc_mpc_write: copy_from_user() failed\n");
+ return -EFAULT;
+ }
+
+ *ppos += incoming;
+
+ page[incoming] = '\0';
+ retval = parse_qos(page, incoming);
+ if (retval == 0)
+ printk("mpoa: proc_mpc_write: could not parse '%s'\n", page);
+
+ free_page((unsigned long)page);
+
+ return nbytes;
+}
+
+static int parse_qos(const char *buff, int len)
+{
+ /* possible lines look like this
+ * add 130.230.54.142 tx=max_pcr,max_sdu rx=max_pcr,max_sdu
+ */
+
+ int pos, i;
+ uint32_t ipaddr;
+ unsigned char ip[4];
+ char cmd[4], temp[256];
+ const char *tmp, *prev;
+ struct atm_qos qos;
+ int value[5];
+
+ memset(&qos, 0, sizeof(struct atm_qos));
+ strncpy(cmd, buff, 3);
+ if( strncmp(cmd,"add", 3) && strncmp(cmd,"del", 3))
+ return 0; /* not add or del */
+
+ pos = 4;
+ /* next parse ip */
+ prev = buff + pos;
+ for (i = 0; i < 3; i++) {
+ tmp = strchr(prev, '.');
+ if (tmp == NULL) return 0;
+ memset(temp, '\0', 256);
+ memcpy(temp, prev, tmp-prev);
+ ip[i] = (char)simple_strtoul(temp, NULL, 0);
+ tmp ++;
+ prev = tmp;
+ }
+ tmp = strchr(prev, ' ');
+ if (tmp == NULL) return 0;
+ memset(temp, '\0', 256);
+ memcpy(temp, prev, tmp-prev);
+ ip[i] = (char)simple_strtoul(temp, NULL, 0);
+ ipaddr = *(uint32_t *)ip;
+
+ if(!strncmp(cmd, "del", 3))
+ return atm_mpoa_delete_qos(atm_mpoa_search_qos(ipaddr));
+
+ /* next transmit values */
+ tmp = strstr(buff, "tx=");
+ if(tmp == NULL) return 0;
+ tmp += 3;
+ prev = tmp;
+ for( i = 0; i < 1; i++){
+ tmp = strchr(prev, ',');
+ if (tmp == NULL) return 0;
+ memset(temp, '\0', 256);
+ memcpy(temp, prev, tmp-prev);
+ value[i] = (int)simple_strtoul(temp, NULL, 0);
+ tmp ++;
+ prev = tmp;
+ }
+ tmp = strchr(prev, ' ');
+ if (tmp == NULL) return 0;
+ memset(temp, '\0', 256);
+ memcpy(temp, prev, tmp-prev);
+ value[i] = (int)simple_strtoul(temp, NULL, 0);
+ qos.txtp.traffic_class = ATM_CBR;
+ qos.txtp.max_pcr = value[0];
+ qos.txtp.max_sdu = value[1];
+
+ /* next receive values */
+ tmp = strstr(buff, "rx=");
+ if(tmp == NULL) return 0;
+ if (strstr(buff, "rx=tx")) { /* rx == tx */
+ qos.rxtp.traffic_class = qos.txtp.traffic_class;
+ qos.rxtp.max_pcr = qos.txtp.max_pcr;
+ qos.rxtp.max_cdv = qos.txtp.max_cdv;
+ qos.rxtp.max_sdu = qos.txtp.max_sdu;
+ } else {
+ tmp += 3;
+ prev = tmp;
+ for( i = 0; i < 1; i++){
+ tmp = strchr(prev, ',');
+ if (tmp == NULL) return 0;
+ memset(temp, '\0', 256);
+ memcpy(temp, prev, tmp-prev);
+ value[i] = (int)simple_strtoul(temp, NULL, 0);
+ tmp ++;
+ prev = tmp;
+ }
+ tmp = strchr(prev, '\0');
+ if (tmp == NULL) return 0;
+ memset(temp, '\0', 256);
+ memcpy(temp, prev, tmp-prev);
+ value[i] = (int)simple_strtoul(temp, NULL, 0);
+ qos.rxtp.traffic_class = ATM_CBR;
+ qos.rxtp.max_pcr = value[0];
+ qos.rxtp.max_sdu = value[1];
+ }
+ qos.aal = ATM_AAL5;
+ dprintk("mpoa: mpoa_proc.c: parse_qos(): setting qos paramameters to tx=%d,%d rx=%d,%d\n",
+ qos.txtp.max_pcr,
+ qos.txtp.max_sdu,
+ qos.rxtp.max_pcr,
+ qos.rxtp.max_sdu
+ );
+
+ atm_mpoa_add_qos(ipaddr, &qos);
+ return 1;
+}
+
+/*
+ * INITIALIZATION function - called when module is initialized/loaded.
+ */
+int mpc_proc_init(void)
+{
+ struct proc_dir_entry *p;
+
+ p = create_proc_entry(STAT_FILE_NAME, 0, atm_proc_root);
+ if (!p) {
+ printk(KERN_ERR "Unable to initialize /proc/atm/%s\n", STAT_FILE_NAME);
+ return -ENOMEM;
+ }
+ p->proc_fops = &mpc_file_operations;
+ p->owner = THIS_MODULE;
+ return 0;
+}
+
+/*
+ * DELETING function - called when module is removed.
+ */
+void mpc_proc_clean(void)
+{
+ remove_proc_entry(STAT_FILE_NAME,atm_proc_root);
+}
+
+
+#endif /* CONFIG_PROC_FS */
+
+
+
+
+
+
diff --git a/uClinux-2.4.31-uc0/net/atm/pppoatm.c b/uClinux-2.4.31-uc0/net/atm/pppoatm.c
new file mode 100644
index 0000000..2a431d2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/pppoatm.c
@@ -0,0 +1,365 @@
+/* net/atm/pppoatm.c - RFC2364 PPP over ATM/AAL5 */
+
+/* Copyright 1999-2000 by Mitchell Blank Jr */
+/* Based on clip.c; 1995-1999 by Werner Almesberger, EPFL LRC/ICA */
+/* And on ppp_async.c; Copyright 1999 Paul Mackerras */
+/* And help from Jens Axboe */
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * This driver provides the encapsulation and framing for sending
+ * and receiving PPP frames in ATM AAL5 PDUs.
+ */
+
+/*
+ * One shortcoming of this driver is that it does not comply with
+ * section 8 of RFC2364 - we are supposed to detect a change
+ * in encapsulation and immediately abort the connection (in order
+ * to avoid a black-hole being created if our peer loses state
+ * and changes encapsulation unilaterally. However, since the
+ * ppp_generic layer actually does the decapsulation, we need
+ * a way of notifying it when we _think_ there might be a problem)
+ * There's two cases:
+ * 1. LLC-encapsulation was missing when it was enabled. In
+ * this case, we should tell the upper layer "tear down
+ * this session if this skb looks ok to you"
+ * 2. LLC-encapsulation was present when it was disabled. Then
+ * we need to tell the upper layer "this packet may be
+ * ok, but if its in error tear down the session"
+ * These hooks are not yet available in ppp_generic
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/ppp_defs.h>
+#include <linux/if_ppp.h>
+#include <linux/ppp_channel.h>
+#include <linux/atmppp.h>
+
+#include "common.h"
+
+#if 0
+#define DPRINTK(format, args...) \
+ printk(KERN_DEBUG "pppoatm: " format, ##args)
+#else
+#define DPRINTK(format, args...)
+#endif
+
+enum pppoatm_encaps {
+ e_autodetect = PPPOATM_ENCAPS_AUTODETECT,
+ e_vc = PPPOATM_ENCAPS_VC,
+ e_llc = PPPOATM_ENCAPS_LLC,
+};
+
+struct pppoatm_vcc {
+ struct atm_vcc *atmvcc; /* VCC descriptor */
+ void (*old_push)(struct atm_vcc *, struct sk_buff *);
+ void (*old_pop)(struct atm_vcc *, struct sk_buff *);
+ /* keep old push/pop for detaching */
+ enum pppoatm_encaps encaps;
+ int flags; /* SC_COMP_PROT - compress protocol */
+ struct ppp_channel chan; /* interface to generic ppp layer */
+ struct tasklet_struct wakeup_tasklet;
+};
+
+/*
+ * Header used for LLC Encapsulated PPP (4 bytes) followed by the LCP protocol
+ * ID (0xC021) used in autodetection
+ */
+static const unsigned char pppllc[6] = { 0xFE, 0xFE, 0x03, 0xCF, 0xC0, 0x21 };
+#define LLC_LEN (4)
+
+static inline struct pppoatm_vcc *atmvcc_to_pvcc(const struct atm_vcc *atmvcc)
+{
+ return (struct pppoatm_vcc *) (atmvcc->user_back);
+}
+
+static inline struct pppoatm_vcc *chan_to_pvcc(const struct ppp_channel *chan)
+{
+ return (struct pppoatm_vcc *) (chan->private);
+}
+
+/*
+ * We can't do this directly from our _pop handler, since the ppp code
+ * doesn't want to be called in interrupt context, so we do it from
+ * a tasklet
+ */
+static void pppoatm_wakeup_sender(unsigned long arg)
+{
+ ppp_output_wakeup((struct ppp_channel *) arg);
+}
+
+/*
+ * This gets called every time the ATM card has finished sending our
+ * skb. The ->old_pop will take care up normal atm flow control,
+ * but we also need to wake up the device if we blocked it
+ */
+static void pppoatm_pop(struct atm_vcc *atmvcc, struct sk_buff *skb)
+{
+ struct pppoatm_vcc *pvcc = atmvcc_to_pvcc(atmvcc);
+ pvcc->old_pop(atmvcc, skb);
+ /*
+ * We don't really always want to do this since it's
+ * really inefficient - it would be much better if we could
+ * test if we had actually throttled the generic layer.
+ * Unfortunately then there would be a nasty SMP race where
+ * we could clear that flag just as we refuse another packet.
+ * For now we do the safe thing.
+ */
+ tasklet_schedule(&pvcc->wakeup_tasklet);
+}
+
+/*
+ * Unbind from PPP - currently we only do this when closing the socket,
+ * but we could put this into an ioctl if need be
+ */
+static void pppoatm_unassign_vcc(struct atm_vcc *atmvcc)
+{
+ struct pppoatm_vcc *pvcc;
+ pvcc = atmvcc_to_pvcc(atmvcc);
+ atmvcc->push = pvcc->old_push;
+ atmvcc->pop = pvcc->old_pop;
+ tasklet_kill(&pvcc->wakeup_tasklet);
+ ppp_unregister_channel(&pvcc->chan);
+ atmvcc->user_back = NULL;
+ kfree(pvcc);
+ /* Gee, I hope we have the big kernel lock here... */
+ MOD_DEC_USE_COUNT;
+}
+
+/* Called when an AAL5 PDU comes in */
+static void pppoatm_push(struct atm_vcc *atmvcc, struct sk_buff *skb)
+{
+ struct pppoatm_vcc *pvcc = atmvcc_to_pvcc(atmvcc);
+ DPRINTK("pppoatm push\n");
+ if (skb == NULL) { /* VCC was closed */
+ DPRINTK("removing ATMPPP VCC %p\n", pvcc);
+ pppoatm_unassign_vcc(atmvcc);
+ atmvcc->push(atmvcc, NULL); /* Pass along bad news */
+ return;
+ }
+ atm_return(atmvcc, skb->truesize);
+ switch (pvcc->encaps) {
+ case e_llc:
+ if (skb->len < LLC_LEN ||
+ memcmp(skb->data, pppllc, LLC_LEN))
+ goto error;
+ skb_pull(skb, LLC_LEN);
+ break;
+ case e_autodetect:
+ if (pvcc->chan.ppp == NULL) { /* Not bound yet! */
+ kfree_skb(skb);
+ return;
+ }
+ if (skb->len >= sizeof(pppllc) &&
+ !memcmp(skb->data, pppllc, sizeof(pppllc))) {
+ pvcc->encaps = e_llc;
+ skb_pull(skb, LLC_LEN);
+ break;
+ }
+ if (skb->len >= (sizeof(pppllc) - LLC_LEN) &&
+ !memcmp(skb->data, &pppllc[LLC_LEN],
+ sizeof(pppllc) - LLC_LEN)) {
+ pvcc->encaps = e_vc;
+ pvcc->chan.mtu += LLC_LEN;
+ break;
+ }
+ DPRINTK("(unit %d): Couldn't autodetect yet "
+ "(skb: %02X %02X %02X %02X %02X %02X)\n",
+ pvcc->chan.unit,
+ skb->data[0], skb->data[1], skb->data[2],
+ skb->data[3], skb->data[4], skb->data[5]);
+ goto error;
+ case e_vc:
+ break;
+ }
+ ppp_input(&pvcc->chan, skb);
+ return;
+ error:
+ kfree_skb(skb);
+ ppp_input_error(&pvcc->chan, 0);
+}
+
+/*
+ * Called by the ppp_generic.c to send a packet - returns true if packet
+ * was accepted. If we return false, then it's our job to call
+ * ppp_output_wakeup(chan) when we're feeling more up to it.
+ * Note that in the ENOMEM case (as opposed to the !atm_may_send case)
+ * we should really drop the packet, but the generic layer doesn't
+ * support this yet. We just return 'DROP_PACKET' which we actually define
+ * as success, just to be clear what we're really doing.
+ */
+#define DROP_PACKET 1
+static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb)
+{
+ struct pppoatm_vcc *pvcc = chan_to_pvcc(chan);
+ ATM_SKB(skb)->vcc = pvcc->atmvcc;
+ DPRINTK("(unit %d): pppoatm_send (skb=0x%p, vcc=0x%p)\n",
+ pvcc->chan.unit, skb, pvcc->atmvcc);
+ if (skb->data[0] == '\0' && (pvcc->flags & SC_COMP_PROT))
+ (void) skb_pull(skb, 1);
+ switch (pvcc->encaps) { /* LLC encapsulation needed */
+ case e_llc:
+ if (skb_headroom(skb) < LLC_LEN) {
+ struct sk_buff *n;
+ n = skb_realloc_headroom(skb, LLC_LEN);
+ if (n != NULL &&
+ !atm_may_send(pvcc->atmvcc, n->truesize)) {
+ kfree_skb(n);
+ goto nospace;
+ }
+ kfree_skb(skb);
+ if ((skb = n) == NULL)
+ return DROP_PACKET;
+ } else if (!atm_may_send(pvcc->atmvcc, skb->truesize))
+ goto nospace;
+ memcpy(skb_push(skb, LLC_LEN), pppllc, LLC_LEN);
+ break;
+ case e_vc:
+ if (!atm_may_send(pvcc->atmvcc, skb->truesize))
+ goto nospace;
+ break;
+ case e_autodetect:
+ DPRINTK("(unit %d): Trying to send without setting encaps!\n",
+ pvcc->chan.unit);
+ kfree_skb(skb);
+ return 1;
+ }
+ atomic_add(skb->truesize, &ATM_SKB(skb)->vcc->sk->wmem_alloc);
+ ATM_SKB(skb)->atm_options = ATM_SKB(skb)->vcc->atm_options;
+ DPRINTK("(unit %d): atm_skb(%p)->vcc(%p)->dev(%p)\n",
+ pvcc->chan.unit, skb, ATM_SKB(skb)->vcc,
+ ATM_SKB(skb)->vcc->dev);
+ return ATM_SKB(skb)->vcc->send(ATM_SKB(skb)->vcc, skb)
+ ? DROP_PACKET : 1;
+ nospace:
+ /*
+ * We don't have space to send this SKB now, but we might have
+ * already applied SC_COMP_PROT compression, so may need to undo
+ */
+ if ((pvcc->flags & SC_COMP_PROT) && skb_headroom(skb) > 0 &&
+ skb->data[-1] == '\0')
+ (void) skb_push(skb, 1);
+ return 0;
+}
+
+/* This handles ioctls sent to the /dev/ppp interface */
+static int pppoatm_devppp_ioctl(struct ppp_channel *chan, unsigned int cmd,
+ unsigned long arg)
+{
+ switch (cmd) {
+ case PPPIOCGFLAGS:
+ return put_user(chan_to_pvcc(chan)->flags, (int *) arg)
+ ? -EFAULT : 0;
+ case PPPIOCSFLAGS:
+ return get_user(chan_to_pvcc(chan)->flags, (int *) arg)
+ ? -EFAULT : 0;
+ }
+ return -ENOTTY;
+}
+
+static /*const*/ struct ppp_channel_ops pppoatm_ops = {
+ start_xmit: pppoatm_send,
+ ioctl: pppoatm_devppp_ioctl,
+};
+
+static int pppoatm_assign_vcc(struct atm_vcc *atmvcc, unsigned long arg)
+{
+ struct atm_backend_ppp be;
+ struct pppoatm_vcc *pvcc;
+ int err;
+ /*
+ * Each PPPoATM instance has its own tasklet - this is just a
+ * prototypical one used to initialize them
+ */
+ static const DECLARE_TASKLET(tasklet_proto, pppoatm_wakeup_sender, 0);
+ if (copy_from_user(&be, (void *) arg, sizeof be))
+ return -EFAULT;
+ if (be.encaps != PPPOATM_ENCAPS_AUTODETECT &&
+ be.encaps != PPPOATM_ENCAPS_VC && be.encaps != PPPOATM_ENCAPS_LLC)
+ return -EINVAL;
+ MOD_INC_USE_COUNT;
+ pvcc = kmalloc(sizeof(*pvcc), GFP_KERNEL);
+ if (pvcc == NULL) {
+ MOD_DEC_USE_COUNT;
+ return -ENOMEM;
+ }
+ memset(pvcc, 0, sizeof(*pvcc));
+ pvcc->atmvcc = atmvcc;
+ pvcc->old_push = atmvcc->push;
+ pvcc->old_pop = atmvcc->pop;
+ pvcc->encaps = (enum pppoatm_encaps) be.encaps;
+ pvcc->chan.private = pvcc;
+ pvcc->chan.ops = &pppoatm_ops;
+ pvcc->chan.mtu = atmvcc->qos.txtp.max_sdu - PPP_HDRLEN -
+ (be.encaps == e_vc ? 0 : LLC_LEN);
+ pvcc->wakeup_tasklet = tasklet_proto;
+ pvcc->wakeup_tasklet.data = (unsigned long) &pvcc->chan;
+ if ((err = ppp_register_channel(&pvcc->chan)) != 0) {
+ kfree(pvcc);
+ return err;
+ }
+ atmvcc->user_back = pvcc;
+ atmvcc->push = pppoatm_push;
+ atmvcc->pop = pppoatm_pop;
+ return 0;
+}
+
+/*
+ * This handles ioctls actually performed on our vcc - we must return
+ * -ENOIOCTLCMD for any unrecognized ioctl
+ */
+static int pppoatm_ioctl(struct atm_vcc *atmvcc, unsigned int cmd,
+ unsigned long arg)
+{
+ if (cmd != ATM_SETBACKEND && atmvcc->push != pppoatm_push)
+ return -ENOIOCTLCMD;
+ switch (cmd) {
+ case ATM_SETBACKEND: {
+ atm_backend_t b;
+ if (get_user(b, (atm_backend_t *) arg))
+ return -EFAULT;
+ if (b != ATM_BACKEND_PPP)
+ return -ENOIOCTLCMD;
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ return pppoatm_assign_vcc(atmvcc, arg);
+ }
+ case PPPIOCGCHAN:
+ return put_user(ppp_channel_index(&atmvcc_to_pvcc(atmvcc)->
+ chan), (int *) arg) ? -EFAULT : 0;
+ case PPPIOCGUNIT:
+ return put_user(ppp_unit_number(&atmvcc_to_pvcc(atmvcc)->
+ chan), (int *) arg) ? -EFAULT : 0;
+ }
+ return -ENOIOCTLCMD;
+}
+
+/* the following avoids some spurious warnings from the compiler */
+#define UNUSED __attribute__((unused))
+
+static int __init UNUSED pppoatm_init(void)
+{
+ pppoatm_ioctl_set(pppoatm_ioctl);
+ return 0;
+}
+
+static void __exit UNUSED pppoatm_exit(void)
+{
+ pppoatm_ioctl_set(NULL);
+}
+
+module_init(pppoatm_init);
+module_exit(pppoatm_exit);
+
+MODULE_AUTHOR("Mitchell Blank Jr <mitch@sfgoth.com>");
+MODULE_DESCRIPTION("RFC2364 PPP over ATM/AAL5");
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/atm/proc.c b/uClinux-2.4.31-uc0/net/atm/proc.c
new file mode 100644
index 0000000..25141a2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/proc.c
@@ -0,0 +1,796 @@
+/* $USAGI: proc.c,v 1.8 2003/11/12 05:11:59 yoshfuji Exp $ */
+/* net/atm/proc.c - ATM /proc interface */
+
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+/*
+ * The mechanism used here isn't designed for speed but rather for convenience
+ * of implementation. We only return one entry per read system call, so we can
+ * be reasonably sure not to overrun the page and race conditions may lead to
+ * the addition or omission of some lines but never to any corruption of a
+ * line's internal structure.
+ *
+ * Making the whole thing slightly more efficient is left as an exercise to the
+ * reader. (Suggestions: wrapper which loops to get several entries per system
+ * call; or make --left slightly more clever to avoid O(n^2) characteristics.)
+ * I find it fast enough on my unloaded 266 MHz Pentium 2 :-)
+ */
+
+
+#include <linux/config.h>
+#include <linux/module.h> /* for EXPORT_SYMBOL */
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/fs.h>
+#include <linux/stat.h>
+#include <linux/proc_fs.h>
+#include <linux/errno.h>
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/netdevice.h>
+#include <linux/atmclip.h>
+#include <linux/atmarp.h>
+#include <linux/if_arp.h>
+#include <linux/init.h> /* for __init */
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+#include <asm/param.h> /* for HZ */
+#include "resources.h"
+#include "common.h" /* atm_proc_init prototype */
+#include "signaling.h" /* to get sigd - ugly too */
+
+#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
+#include <net/atmclip.h>
+#include "ipcommon.h"
+#endif
+
+#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)
+#include "lec.h"
+#include "lec_arpc.h"
+#endif
+
+static ssize_t proc_dev_atm_read(struct file *file,char *buf,size_t count,
+ loff_t *pos);
+static ssize_t proc_spec_atm_read(struct file *file,char *buf,size_t count,
+ loff_t *pos);
+
+static struct file_operations proc_dev_atm_operations = {
+ read: proc_dev_atm_read,
+};
+
+static struct file_operations proc_spec_atm_operations = {
+ read: proc_spec_atm_read,
+};
+
+static void add_stats(char *buf,const char *aal,
+ const struct k_atm_aal_stats *stats)
+{
+ sprintf(strchr(buf,0),"%s ( %d %d %d %d %d )",aal,
+ atomic_read(&stats->tx),atomic_read(&stats->tx_err),
+ atomic_read(&stats->rx),atomic_read(&stats->rx_err),
+ atomic_read(&stats->rx_drop));
+}
+
+
+static void dev_info(const struct atm_dev *dev,char *buf)
+{
+ int off,i;
+
+ off = sprintf(buf,"%3d %-8s",dev->number,dev->type);
+ for (i = 0; i < ESI_LEN; i++)
+ off += sprintf(buf+off,"%02x",dev->esi[i]);
+ strcat(buf," ");
+ add_stats(buf,"0",&dev->stats.aal0);
+ strcat(buf," ");
+ add_stats(buf,"5",&dev->stats.aal5);
+ sprintf(strchr(buf,0), "\t[%d]", atomic_read(&dev->refcnt));
+ strcat(buf,"\n");
+}
+
+
+#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
+
+#define SEQ_NO_VCC_TOKEN ((void *) 2)
+
+static void svc_addr(struct seq_file *seq, struct sockaddr_atmsvc *addr)
+{
+ static int code[] = { 1,2,10,6,1,0 };
+ static int e164[] = { 1,8,4,6,1,0 };
+
+ if (*addr->sas_addr.pub) {
+ seq_printf(seq, "%s", addr->sas_addr.pub);
+ if (*addr->sas_addr.prv)
+ seq_putc(seq, '+');
+ } else if (!*addr->sas_addr.prv) {
+ seq_printf(seq, "%s", "(none)");
+ return;
+ }
+ if (*addr->sas_addr.prv) {
+ unsigned char *prv = addr->sas_addr.prv;
+ int *fields;
+ int i, j;
+
+ fields = *prv == ATM_AFI_E164 ? e164 : code;
+ for (i = 0; fields[i]; i++) {
+ for (j = fields[i]; j; j--)
+ seq_printf(seq, "%02X", *prv++);
+ if (fields[i+1])
+ seq_putc(seq, '.');
+ }
+ }
+}
+
+
+static void atmarp_info(struct seq_file *seq, struct net_device *dev,struct
+ atmarp_entry *entry, struct clip_vcc *clip_vcc) {
+ unsigned long exp;
+ char buf[17];
+ int svc, llc, off;
+
+ svc = ((clip_vcc == SEQ_NO_VCC_TOKEN) ||
+ (clip_vcc->vcc->sk->family == AF_ATMSVC));
+
+ llc = ((clip_vcc == SEQ_NO_VCC_TOKEN) ||
+ (clip_vcc->encap));
+
+ if (clip_vcc == SEQ_NO_VCC_TOKEN)
+ exp = entry->neigh->used;
+ else
+ exp = clip_vcc->last_use;
+
+ exp = (jiffies - exp) / HZ;
+
+ seq_printf(seq, "%-6s%-4s%-4s%5ld ",
+ dev->name,
+ svc ? "SVC" : "PVC",
+ llc ? "LLC" : "NULL",
+ exp);
+
+ off = snprintf(buf, sizeof(buf)-1, "%d.%d.%d.%d", NIPQUAD(entry->ip));
+ while (off < 16)
+ buf[off++] = ' ';
+ buf[off] = '\0';
+ seq_printf(seq, "%s", buf);
+
+ if (clip_vcc == SEQ_NO_VCC_TOKEN) {
+ if (time_before(jiffies, entry->expires))
+ seq_printf(seq, "(resolving)\n");
+ else
+ seq_printf(seq, "(expired, ref %d)\n",
+ atomic_read(&entry->neigh->refcnt));
+ } else if (!svc) {
+ seq_printf(seq, "%d.%d.%d\n",
+ clip_vcc->vcc->dev->number,
+ clip_vcc->vcc->vpi,
+ clip_vcc->vcc->vci);
+ } else {
+ svc_addr(seq, &clip_vcc->vcc->remote);
+ seq_putc(seq, '\n');
+ }
+}
+
+struct clip_seq_state {
+ /* This member must be first. */
+ struct neigh_seq_state ns;
+
+ /* Local to clip specific iteration. */
+ struct clip_vcc *vcc;
+};
+
+static struct clip_vcc *clip_seq_next_vcc(struct atmarp_entry *e,
+ struct clip_vcc *curr)
+{
+ if (!curr) {
+ curr = e->vccs;
+ if (!curr)
+ return SEQ_NO_VCC_TOKEN;
+ return curr;
+ }
+
+ if (curr == SEQ_NO_VCC_TOKEN)
+ return NULL;
+
+ curr = curr->next;
+
+ return curr;
+}
+
+static void *clip_seq_vcc_walk(struct clip_seq_state *state,
+ struct atmarp_entry *e, loff_t *pos)
+{
+ struct clip_vcc *vcc = state->vcc;
+
+ vcc = clip_seq_next_vcc(e, vcc);
+ if (vcc && pos != NULL) {
+ while (*pos) {
+ vcc = clip_seq_next_vcc(e, vcc);
+ if (!vcc)
+ break;
+ --(*pos);
+ }
+ }
+ state->vcc = vcc;
+
+ return vcc;
+}
+
+static void *clip_seq_sub_iter(struct neigh_seq_state *_state,
+ struct neighbour *n, loff_t *pos)
+{
+ struct clip_seq_state *state = (struct clip_seq_state *) _state;
+
+ return clip_seq_vcc_walk(state, NEIGH2ENTRY(n), pos);
+}
+
+static void *clip_seq_start(struct seq_file *seq, loff_t *pos)
+{
+ return neigh_seq_start(seq, pos, clip_tbl_hook, NEIGH_SEQ_NEIGH_ONLY);
+}
+
+static int clip_seq_show(struct seq_file *seq, void *v)
+{
+ static char atm_arp_banner[] =
+ "IPitf TypeEncp Idle IP address ATM address\n";
+
+ if (v == SEQ_START_TOKEN) {
+ seq_puts(seq, atm_arp_banner);
+ } else {
+ struct clip_seq_state *state = seq->private;
+ struct neighbour *n = v;
+ struct clip_vcc *vcc = state->vcc;
+
+ atmarp_info(seq, n->dev, NEIGH2ENTRY(n), vcc);
+ }
+ return 0;
+}
+
+static struct seq_operations arp_seq_ops = {
+ .start = clip_seq_start,
+ .next = neigh_seq_next,
+ .stop = neigh_seq_stop,
+ .show = clip_seq_show,
+};
+
+static int arp_seq_open(struct inode *inode, struct file *file)
+{
+ struct clip_seq_state *state;
+ struct seq_file *seq;
+ int rc = -EAGAIN;
+
+ state = kmalloc(sizeof(*state), GFP_KERNEL);
+ if (!state) {
+ rc = -ENOMEM;
+ goto out_kfree;
+ }
+ memset(state, 0, sizeof(*state));
+ state->ns.neigh_sub_iter = clip_seq_sub_iter;
+
+ rc = seq_open(file, &arp_seq_ops);
+ if (rc)
+ goto out_kfree;
+
+ seq = file->private_data;
+ seq->private = state;
+out:
+ return rc;
+
+out_kfree:
+ kfree(state);
+ goto out;
+}
+
+static struct file_operations arp_seq_fops = {
+ .open = arp_seq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release_private,
+ .owner = THIS_MODULE,
+};
+#endif
+
+#ifdef CONFIG_ATM_IPV6
+ /* TODO: solve conflicts. */
+#include <net/atmipv6.h>
+#endif /* ifdef CONFIG_ATM_IPV6 */
+
+static void pvc_info(struct atm_vcc *vcc, char *buf, int clip_info)
+{
+ static const char *class_name[] = { "off","UBR","CBR","VBR","ABR" };
+ static const char *aal_name[] = {
+ "---", "1", "2", "3/4", /* 0- 3 */
+ "???", "5", "???", "???", /* 4- 7 */
+ "???", "???", "???", "???", /* 8-11 */
+ "???", "0", "???", "???"}; /* 12-15 */
+ int off;
+
+ off = sprintf(buf,"%3d %3d %5d %-3s %7d %-5s %7d %-6s",
+ vcc->dev->number,vcc->vpi,vcc->vci,
+ vcc->qos.aal >= sizeof(aal_name)/sizeof(aal_name[0]) ? "err" :
+ aal_name[vcc->qos.aal],vcc->qos.rxtp.min_pcr,
+ class_name[vcc->qos.rxtp.traffic_class],vcc->qos.txtp.min_pcr,
+ class_name[vcc->qos.txtp.traffic_class]);
+#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
+ if (clip_info && (vcc->push == atm_clip_ops->clip_push)) {
+ struct clip_vcc *clip_vcc = CLIP_VCC(vcc);
+#ifdef CONFIG_ATM_IPV6
+ struct clip6_vcc *clip6_vcc = CLIP6_VCC(vcc);
+#endif
+ struct net_device *dev;
+
+#ifdef CONFIG_ATM_IPV6
+ dev = clip_vcc->entry ? clip_vcc->entry->neigh->dev :
+ (clip6_vcc->dev ? clip6_vcc->dev : NULL);
+#else /* i.e., !ifdef CONFIG_ATM_IPV6 */
+ dev = clip_vcc->entry ? clip_vcc->entry->neigh->dev : NULL;
+#endif /* ifdef CONFIG_ATM_IPV6 */
+ off += sprintf(buf+off,"CLIP, Itf:%s, Encap:",
+ dev ? dev->name : "none?");
+ if (clip_vcc->encap)
+ off += sprintf(buf+off,"LLC/SNAP");
+ else
+ off += sprintf(buf+off,"None");
+ }
+#endif
+#ifdef CONFIG_ATM_IPV6
+ if (vcc->push == clip6_push) {
+ struct clip6_vcc *clip6_vcc = CLIP6_VCC(vcc);
+ struct net_device *dev;
+
+ dev = clip6_vcc->dev ? clip6_vcc->dev : NULL;
+ off += sprintf(buf+off,"CLIP6, Itf:%s, Encap:",
+ dev ? dev->name : "none?");
+ if (clip6_vcc->encap) off += sprintf(buf+off,"LLC/SNAP");
+ else off += sprintf(buf+off,"None");
+ }
+#endif /* ifdef CONFIG_ATM_IPV6 */
+ strcpy(buf+off,"\n");
+}
+
+
+static const char *vcc_state(struct atm_vcc *vcc)
+{
+ static const char *map[] = { ATM_VS2TXT_MAP };
+
+ return map[ATM_VF2VS(vcc->flags)];
+}
+
+
+static void vc_info(struct atm_vcc *vcc,char *buf)
+{
+ char *here;
+
+ here = buf+sprintf(buf,"%p ",vcc);
+ if (!vcc->dev) here += sprintf(here,"Unassigned ");
+ else here += sprintf(here,"%3d %3d %5d ",vcc->dev->number,vcc->vpi,
+ vcc->vci);
+ switch (vcc->sk->family) {
+ case AF_ATMPVC:
+ here += sprintf(here,"PVC");
+ break;
+ case AF_ATMSVC:
+ here += sprintf(here,"SVC");
+ break;
+ default:
+ here += sprintf(here,"%3d",vcc->sk->family);
+ }
+ here += sprintf(here," %04lx %5d %7d/%7d %7d/%7d\n",vcc->flags.bits,
+ vcc->reply,
+ atomic_read(&vcc->sk->wmem_alloc),vcc->sk->sndbuf,
+ atomic_read(&vcc->sk->rmem_alloc),vcc->sk->rcvbuf);
+}
+
+
+static void svc_info(struct atm_vcc *vcc,char *buf)
+{
+ char *here;
+ int i;
+
+ if (!vcc->dev)
+ sprintf(buf,sizeof(void *) == 4 ? "N/A@%p%10s" : "N/A@%p%2s",
+ vcc,"");
+ else sprintf(buf,"%3d %3d %5d ",vcc->dev->number,vcc->vpi,
+ vcc->vci);
+ here = strchr(buf,0);
+ here += sprintf(here,"%-10s ",vcc_state(vcc));
+ here += sprintf(here,"%s%s",vcc->remote.sas_addr.pub,
+ *vcc->remote.sas_addr.pub && *vcc->remote.sas_addr.prv ? "+" : "");
+ if (*vcc->remote.sas_addr.prv)
+ for (i = 0; i < ATM_ESA_LEN; i++)
+ here += sprintf(here,"%02x",
+ vcc->remote.sas_addr.prv[i]);
+ strcat(here,"\n");
+}
+
+
+#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)
+
+static char*
+lec_arp_get_status_string(unsigned char status)
+{
+ switch(status) {
+ case ESI_UNKNOWN:
+ return "ESI_UNKNOWN ";
+ case ESI_ARP_PENDING:
+ return "ESI_ARP_PENDING ";
+ case ESI_VC_PENDING:
+ return "ESI_VC_PENDING ";
+ case ESI_FLUSH_PENDING:
+ return "ESI_FLUSH_PENDING ";
+ case ESI_FORWARD_DIRECT:
+ return "ESI_FORWARD_DIRECT";
+ default:
+ return "<Unknown> ";
+ }
+}
+
+static void
+lec_info(struct lec_arp_table *entry, char *buf)
+{
+ int j, offset=0;
+
+ for(j=0;j<ETH_ALEN;j++) {
+ offset+=sprintf(buf+offset,"%2.2x",0xff&entry->mac_addr[j]);
+ }
+ offset+=sprintf(buf+offset, " ");
+ for(j=0;j<ATM_ESA_LEN;j++) {
+ offset+=sprintf(buf+offset,"%2.2x",0xff&entry->atm_addr[j]);
+ }
+ offset+=sprintf(buf+offset, " %s %4.4x",
+ lec_arp_get_status_string(entry->status),
+ entry->flags&0xffff);
+ if (entry->vcc) {
+ offset+=sprintf(buf+offset, "%3d %3d ", entry->vcc->vpi,
+ entry->vcc->vci);
+ } else
+ offset+=sprintf(buf+offset, " ");
+ if (entry->recv_vcc) {
+ offset+=sprintf(buf+offset, " %3d %3d",
+ entry->recv_vcc->vpi, entry->recv_vcc->vci);
+ }
+
+ sprintf(buf+offset,"\n");
+}
+
+#endif
+
+static int atm_devices_info(loff_t pos,char *buf)
+{
+ struct atm_dev *dev;
+ struct list_head *p;
+ int left;
+
+ if (!pos) {
+ return sprintf(buf,"Itf Type ESI/\"MAC\"addr "
+ "AAL(TX,err,RX,err,drop) ... [refcnt]\n");
+ }
+ left = pos-1;
+ spin_lock(&atm_dev_lock);
+ list_for_each(p, &atm_devs) {
+ dev = list_entry(p, struct atm_dev, dev_list);
+ if (left-- == 0) {
+ dev_info(dev,buf);
+ spin_unlock(&atm_dev_lock);
+ return strlen(buf);
+ }
+ }
+ spin_unlock(&atm_dev_lock);
+ return 0;
+}
+
+/*
+ * FIXME: it isn't safe to walk the VCC list without turning off interrupts.
+ * What is really needed is some lock on the devices. Ditto for ATMARP.
+ */
+
+static int atm_pvc_info(loff_t pos,char *buf)
+{
+ struct sock *s;
+ struct atm_vcc *vcc;
+ int left, clip_info = 0;
+
+ if (!pos) {
+ return sprintf(buf,"Itf VPI VCI AAL RX(PCR,Class) "
+ "TX(PCR,Class)\n");
+ }
+ left = pos-1;
+#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
+ if (try_atm_clip_ops())
+ clip_info = 1;
+#endif
+ read_lock(&vcc_sklist_lock);
+ for(s = vcc_sklist; s; s = s->next) {
+ vcc = s->protinfo.af_atm;
+ if (vcc->sk->family == PF_ATMPVC && vcc->dev && !left--) {
+ pvc_info(vcc,buf,clip_info);
+ read_unlock(&vcc_sklist_lock);
+#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
+ if (clip_info && atm_clip_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_clip_ops->owner);
+#endif
+ return strlen(buf);
+ }
+ }
+ read_unlock(&vcc_sklist_lock);
+#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
+ if (clip_info && atm_clip_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_clip_ops->owner);
+#endif
+ return 0;
+}
+
+
+static int atm_vc_info(loff_t pos,char *buf)
+{
+ struct atm_vcc *vcc;
+ struct sock *s;
+ int left;
+
+ if (!pos)
+ return sprintf(buf,sizeof(void *) == 4 ? "%-8s%s" : "%-16s%s",
+ "Address"," Itf VPI VCI Fam Flags Reply Send buffer"
+ " Recv buffer\n");
+ left = pos-1;
+ read_lock(&vcc_sklist_lock);
+ for(s = vcc_sklist; s; s = s->next) {
+ vcc = s->protinfo.af_atm;
+ if (!left--) {
+ vc_info(vcc,buf);
+ read_unlock(&vcc_sklist_lock);
+ return strlen(buf);
+ }
+ }
+ read_unlock(&vcc_sklist_lock);
+
+ return 0;
+}
+
+
+static int atm_svc_info(loff_t pos,char *buf)
+{
+ struct sock *s;
+ struct atm_vcc *vcc;
+ int left;
+
+ if (!pos)
+ return sprintf(buf,"Itf VPI VCI State Remote\n");
+ left = pos-1;
+ read_lock(&vcc_sklist_lock);
+ for(s = vcc_sklist; s; s = s->next) {
+ vcc = s->protinfo.af_atm;
+ if (vcc->sk->family == PF_ATMSVC && !left--) {
+ svc_info(vcc,buf);
+ read_unlock(&vcc_sklist_lock);
+ return strlen(buf);
+ }
+ }
+ read_unlock(&vcc_sklist_lock);
+
+ return 0;
+}
+
+#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)
+static int atm_lec_info(loff_t pos,char *buf)
+{
+ unsigned long flags;
+ struct lec_priv *priv;
+ struct lec_arp_table *entry;
+ int i, count, d, e;
+ struct net_device *dev;
+
+ if (!pos) {
+ return sprintf(buf,"Itf MAC ATM destination"
+ " Status Flags "
+ "VPI/VCI Recv VPI/VCI\n");
+ }
+ if (!try_atm_lane_ops())
+ return 0; /* the lane module is not there yet */
+
+ count = pos;
+ for(d = 0; d < MAX_LEC_ITF; d++) {
+ dev = atm_lane_ops->get_lec(d);
+ if (!dev || !(priv = (struct lec_priv *) dev->priv))
+ continue;
+ spin_lock_irqsave(&priv->lec_arp_lock, flags);
+ for(i = 0; i < LEC_ARP_TABLE_SIZE; i++) {
+ for(entry = priv->lec_arp_tables[i]; entry; entry = entry->next) {
+ if (--count)
+ continue;
+ e = sprintf(buf,"%s ", dev->name);
+ lec_info(entry, buf+e);
+ spin_unlock_irqrestore(&priv->lec_arp_lock, flags);
+ dev_put(dev);
+ if (atm_lane_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_lane_ops->owner);
+ return strlen(buf);
+ }
+ }
+ for(entry = priv->lec_arp_empty_ones; entry; entry = entry->next) {
+ if (--count)
+ continue;
+ e = sprintf(buf,"%s ", dev->name);
+ lec_info(entry, buf+e);
+ spin_unlock_irqrestore(&priv->lec_arp_lock, flags);
+ dev_put(dev);
+ if (atm_lane_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_lane_ops->owner);
+ return strlen(buf);
+ }
+ for(entry = priv->lec_no_forward; entry; entry=entry->next) {
+ if (--count)
+ continue;
+ e = sprintf(buf,"%s ", dev->name);
+ lec_info(entry, buf+e);
+ spin_unlock_irqrestore(&priv->lec_arp_lock, flags);
+ dev_put(dev);
+ if (atm_lane_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_lane_ops->owner);
+ return strlen(buf);
+ }
+ for(entry = priv->mcast_fwds; entry; entry = entry->next) {
+ if (--count)
+ continue;
+ e = sprintf(buf,"%s ", dev->name);
+ lec_info(entry, buf+e);
+ spin_unlock_irqrestore(&priv->lec_arp_lock, flags);
+ dev_put(dev);
+ if (atm_lane_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_lane_ops->owner);
+ return strlen(buf);
+ }
+ spin_unlock_irqrestore(&priv->lec_arp_lock, flags);
+ dev_put(dev);
+ }
+ if (atm_lane_ops->owner)
+ __MOD_DEC_USE_COUNT(atm_lane_ops->owner);
+ return 0;
+}
+#endif
+
+
+static ssize_t proc_dev_atm_read(struct file *file,char *buf,size_t count,
+ loff_t *pos)
+{
+ struct atm_dev *dev;
+ unsigned long page;
+ int length;
+
+ if (count == 0) return 0;
+ page = get_free_page(GFP_KERNEL);
+ if (!page) return -ENOMEM;
+ dev = ((struct proc_dir_entry *) file->f_dentry->d_inode->u.generic_ip)
+ ->data;
+ if (!dev->ops->proc_read)
+ length = -EINVAL;
+ else {
+ length = dev->ops->proc_read(dev,pos,(char *) page);
+ if (length > count) length = -EINVAL;
+ }
+ if (length >= 0) {
+ if (copy_to_user(buf,(char *) page,length)) length = -EFAULT;
+ (*pos)++;
+ }
+ free_page(page);
+ return length;
+}
+
+
+static ssize_t proc_spec_atm_read(struct file *file,char *buf,size_t count,
+ loff_t *pos)
+{
+ unsigned long page;
+ int length;
+ int (*info)(loff_t,char *);
+ info = ((struct proc_dir_entry *) file->f_dentry->d_inode->u.generic_ip)
+ ->data;
+
+ if (count == 0) return 0;
+ page = get_free_page(GFP_KERNEL);
+ if (!page) return -ENOMEM;
+ length = (*info)(*pos,(char *) page);
+ if (length > count) length = -EINVAL;
+ if (length >= 0) {
+ if (copy_to_user(buf,(char *) page,length)) length = -EFAULT;
+ (*pos)++;
+ }
+ free_page(page);
+ return length;
+}
+
+
+struct proc_dir_entry *atm_proc_root;
+EXPORT_SYMBOL(atm_proc_root);
+
+
+int atm_proc_dev_register(struct atm_dev *dev)
+{
+ int digits,num;
+ int error;
+
+ error = -ENOMEM;
+ digits = 0;
+ for (num = dev->number; num; num /= 10) digits++;
+ if (!digits) digits++;
+
+ dev->proc_name = kmalloc(strlen(dev->type) + digits + 2, GFP_ATOMIC);
+ if (!dev->proc_name)
+ goto fail1;
+ sprintf(dev->proc_name,"%s:%d",dev->type, dev->number);
+
+ dev->proc_entry = create_proc_entry(dev->proc_name, 0, atm_proc_root);
+ if (!dev->proc_entry)
+ goto fail0;
+ dev->proc_entry->data = dev;
+ dev->proc_entry->proc_fops = &proc_dev_atm_operations;
+ dev->proc_entry->owner = THIS_MODULE;
+ return 0;
+fail0:
+ kfree(dev->proc_name);
+fail1:
+ return error;
+}
+
+
+void atm_proc_dev_deregister(struct atm_dev *dev)
+{
+ remove_proc_entry(dev->proc_name, atm_proc_root);
+ kfree(dev->proc_name);
+}
+
+
+#define CREATE_ENTRY(name) \
+ name = create_proc_entry(#name,0,atm_proc_root); \
+ if (!name) goto cleanup; \
+ name->data = atm_##name##_info; \
+ name->proc_fops = &proc_spec_atm_operations; \
+ name->owner = THIS_MODULE
+
+static struct proc_dir_entry *devices = NULL, *pvc = NULL,
+ *svc = NULL, *arp = NULL, *lec = NULL, *vc = NULL;
+
+static void atm_proc_cleanup(void)
+{
+ if (devices)
+ remove_proc_entry("devices",atm_proc_root);
+ if (pvc)
+ remove_proc_entry("pvc",atm_proc_root);
+ if (svc)
+ remove_proc_entry("svc",atm_proc_root);
+ if (arp)
+ remove_proc_entry("arp",atm_proc_root);
+ if (lec)
+ remove_proc_entry("lec",atm_proc_root);
+ if (vc)
+ remove_proc_entry("vc",atm_proc_root);
+ remove_proc_entry("net/atm",NULL);
+}
+
+int atm_proc_init(void)
+{
+ atm_proc_root = proc_mkdir("net/atm",NULL);
+ if (!atm_proc_root)
+ return -ENOMEM;
+ CREATE_ENTRY(devices);
+ CREATE_ENTRY(pvc);
+ CREATE_ENTRY(svc);
+ CREATE_ENTRY(vc);
+#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
+ arp = create_proc_entry("arp", S_IRUGO, atm_proc_root);
+ if (!arp)
+ goto cleanup;
+ arp->proc_fops = &arp_seq_fops;
+#endif
+#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)
+ CREATE_ENTRY(lec);
+#endif
+ return 0;
+
+cleanup:
+ atm_proc_cleanup();
+ return -ENOMEM;
+}
+
+void atm_proc_exit(void)
+{
+ atm_proc_cleanup();
+}
diff --git a/uClinux-2.4.31-uc0/net/atm/protocols.h b/uClinux-2.4.31-uc0/net/atm/protocols.h
new file mode 100644
index 0000000..6a65b34
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/protocols.h
@@ -0,0 +1,16 @@
+/* net/atm/protocols.h - ATM protocol handler entry points */
+
+/* Written 1995-1997 by Werner Almesberger, EPFL LRC */
+
+
+#ifndef NET_ATM_PROTOCOLS_H
+#define NET_ATM_PROTOCOLS_H
+
+void atm_push_raw(struct atm_vcc *vcc,struct sk_buff *skb);
+
+int atm_init_aal0(struct atm_vcc *vcc); /* "raw" AAL0 */
+int atm_init_aal34(struct atm_vcc *vcc);/* "raw" AAL3/4 transport */
+int atm_init_aal5(struct atm_vcc *vcc); /* "raw" AAL5 transport */
+int atm_init_atmarp(struct atm_vcc *vcc);/* ATM ARP */
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/atm/pvc.c b/uClinux-2.4.31-uc0/net/atm/pvc.c
new file mode 100644
index 0000000..cadaa75
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/pvc.c
@@ -0,0 +1,157 @@
+/* net/atm/pvc.c - ATM PVC sockets */
+
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#include <linux/config.h>
+#include <linux/net.h> /* struct socket, struct net_proto,
+ struct proto_ops */
+#include <linux/atm.h> /* ATM stuff */
+#include <linux/atmdev.h> /* ATM devices */
+#include <linux/errno.h> /* error codes */
+#include <linux/kernel.h> /* printk */
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/bitops.h>
+#include <net/sock.h> /* for sock_no_* */
+
+#include "resources.h" /* devs and vccs */
+#include "common.h" /* common for PVCs and SVCs */
+
+
+static int pvc_shutdown(struct socket *sock,int how)
+{
+ return 0;
+}
+
+
+static int pvc_bind(struct socket *sock,struct sockaddr *sockaddr,
+ int sockaddr_len)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_atmpvc *addr;
+ struct atm_vcc *vcc;
+ int error;
+
+ if (sockaddr_len != sizeof(struct sockaddr_atmpvc)) return -EINVAL;
+ addr = (struct sockaddr_atmpvc *) sockaddr;
+ if (addr->sap_family != AF_ATMPVC) return -EAFNOSUPPORT;
+ lock_sock(sk);
+ vcc = ATM_SD(sock);
+ if (!test_bit(ATM_VF_HASQOS, &vcc->flags)) {
+ error = -EBADFD;
+ goto out;
+ }
+ if (test_bit(ATM_VF_PARTIAL,&vcc->flags)) {
+ if (vcc->vpi != ATM_VPI_UNSPEC) addr->sap_addr.vpi = vcc->vpi;
+ if (vcc->vci != ATM_VCI_UNSPEC) addr->sap_addr.vci = vcc->vci;
+ }
+ error = vcc_connect(sock, addr->sap_addr.itf, addr->sap_addr.vpi,
+ addr->sap_addr.vci);
+out:
+ release_sock(sk);
+ return error;
+}
+
+
+static int pvc_connect(struct socket *sock,struct sockaddr *sockaddr,
+ int sockaddr_len,int flags)
+{
+ return pvc_bind(sock,sockaddr,sockaddr_len);
+}
+
+static int pvc_setsockopt(struct socket *sock, int level, int optname,
+ char *optval, int optlen)
+{
+ struct sock *sk = sock->sk;
+ int error;
+
+ lock_sock(sk);
+ error = vcc_setsockopt(sock, level, optname, optval, optlen);
+ release_sock(sk);
+ return error;
+}
+
+
+static int pvc_getsockopt(struct socket *sock, int level, int optname,
+ char *optval, int *optlen)
+{
+ struct sock *sk = sock->sk;
+ int error;
+
+ lock_sock(sk);
+ error = vcc_getsockopt(sock, level, optname, optval, optlen);
+ release_sock(sk);
+ return error;
+}
+
+
+static int pvc_getname(struct socket *sock,struct sockaddr *sockaddr,
+ int *sockaddr_len,int peer)
+{
+ struct sockaddr_atmpvc *addr;
+ struct atm_vcc *vcc = ATM_SD(sock);
+
+ if (!vcc->dev || !test_bit(ATM_VF_ADDR,&vcc->flags)) return -ENOTCONN;
+ *sockaddr_len = sizeof(struct sockaddr_atmpvc);
+ addr = (struct sockaddr_atmpvc *) sockaddr;
+ addr->sap_family = AF_ATMPVC;
+ addr->sap_addr.itf = vcc->dev->number;
+ addr->sap_addr.vpi = vcc->vpi;
+ addr->sap_addr.vci = vcc->vci;
+ return 0;
+}
+
+
+static struct proto_ops pvc_proto_ops = {
+ .family = PF_ATMPVC,
+
+ .release = vcc_release,
+ .bind = pvc_bind,
+ .connect = pvc_connect,
+ .socketpair = sock_no_socketpair,
+ .accept = sock_no_accept,
+ .getname = pvc_getname,
+ .poll = atm_poll,
+ .ioctl = vcc_ioctl,
+ .listen = sock_no_listen,
+ .shutdown = pvc_shutdown,
+ .setsockopt = pvc_setsockopt,
+ .getsockopt = pvc_getsockopt,
+ .sendmsg = vcc_sendmsg,
+ .recvmsg = vcc_recvmsg,
+ .mmap = sock_no_mmap,
+ .sendpage = sock_no_sendpage,
+};
+
+
+static int pvc_create(struct socket *sock,int protocol)
+{
+ sock->ops = &pvc_proto_ops;
+ return vcc_create(sock, protocol, PF_ATMPVC);
+}
+
+
+static struct net_proto_family pvc_family_ops = {
+ PF_ATMPVC,
+ pvc_create,
+ 0, /* no authentication */
+ 0, /* no encryption */
+ 0 /* no encrypt_net */
+};
+
+
+/*
+ * Initialize the ATM PVC protocol family
+ */
+
+
+int atmpvc_init(void)
+{
+ return sock_register(&pvc_family_ops);
+}
+
+void atmpvc_exit(void)
+{
+ sock_unregister(PF_ATMPVC);
+}
diff --git a/uClinux-2.4.31-uc0/net/atm/raw.c b/uClinux-2.4.31-uc0/net/atm/raw.c
new file mode 100644
index 0000000..b32e992
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/raw.c
@@ -0,0 +1,93 @@
+/* net/atm/raw.c - Raw AAL0 and AAL5 transports */
+
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/atmdev.h>
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+#include <linux/mm.h>
+
+#include "common.h"
+#include "protocols.h"
+
+
+#if 0
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+
+/*
+ * SKB == NULL indicates that the link is being closed
+ */
+
+void atm_push_raw(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+ if (skb) {
+ skb_queue_tail(&vcc->sk->receive_queue,skb);
+ wake_up(&vcc->sleep);
+ }
+}
+
+
+static void atm_pop_raw(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+ DPRINTK("APopR (%d) %d -= %d\n",vcc->vci,vcc->sk->wmem_alloc,skb->truesize);
+ atomic_sub(skb->truesize, &vcc->sk->wmem_alloc);
+ dev_kfree_skb_any(skb);
+ wake_up(&vcc->sleep);
+}
+
+
+static int atm_send_aal0(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+ /*
+ * Note that if vpi/vci are _ANY or _UNSPEC the below will
+ * still work
+ */
+ if (!capable(CAP_NET_ADMIN) &&
+ (((u32 *) skb->data)[0] & (ATM_HDR_VPI_MASK | ATM_HDR_VCI_MASK)) !=
+ ((vcc->vpi << ATM_HDR_VPI_SHIFT) | (vcc->vci << ATM_HDR_VCI_SHIFT)))
+ {
+ kfree_skb(skb);
+ return -EADDRNOTAVAIL;
+ }
+ return vcc->dev->ops->send(vcc,skb);
+}
+
+
+int atm_init_aal0(struct atm_vcc *vcc)
+{
+ vcc->push = atm_push_raw;
+ vcc->pop = atm_pop_raw;
+ vcc->push_oam = NULL;
+ vcc->send = atm_send_aal0;
+ return 0;
+}
+
+
+int atm_init_aal34(struct atm_vcc *vcc)
+{
+ vcc->push = atm_push_raw;
+ vcc->pop = atm_pop_raw;
+ vcc->push_oam = NULL;
+ vcc->send = vcc->dev->ops->send;
+ return 0;
+}
+
+
+int atm_init_aal5(struct atm_vcc *vcc)
+{
+ vcc->push = atm_push_raw;
+ vcc->pop = atm_pop_raw;
+ vcc->push_oam = NULL;
+ vcc->send = vcc->dev->ops->send;
+ return 0;
+}
+
+
+EXPORT_SYMBOL(atm_init_aal5);
diff --git a/uClinux-2.4.31-uc0/net/atm/resources.c b/uClinux-2.4.31-uc0/net/atm/resources.c
new file mode 100644
index 0000000..d600ffd
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/resources.c
@@ -0,0 +1,405 @@
+/* net/atm/resources.c - Staticly allocated resources */
+
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#include <linux/config.h>
+#include <linux/ctype.h>
+#include <linux/string.h>
+#include <linux/atmdev.h>
+#include <linux/sonet.h>
+#include <linux/kernel.h> /* for barrier */
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <net/sock.h> /* for struct sock */
+#include <asm/segment.h> /* for get_fs_long and put_fs_long */
+
+#include "common.h"
+#include "resources.h"
+#include "addr.h"
+
+
+LIST_HEAD(atm_devs);
+spinlock_t atm_dev_lock = SPIN_LOCK_UNLOCKED;
+
+
+static struct atm_dev *__alloc_atm_dev(const char *type)
+{
+ struct atm_dev *dev;
+
+ dev = kmalloc(sizeof(*dev), GFP_ATOMIC);
+ if (!dev)
+ return NULL;
+ memset(dev, 0, sizeof(*dev));
+ dev->type = type;
+ dev->signal = ATM_PHY_SIG_UNKNOWN;
+ dev->link_rate = ATM_OC3_PCR;
+ spin_lock_init(&dev->lock);
+
+ return dev;
+}
+
+
+static void __free_atm_dev(struct atm_dev *dev)
+{
+ kfree(dev);
+}
+
+static struct atm_dev *__atm_dev_lookup(int number)
+{
+ struct atm_dev *dev;
+ struct list_head *p;
+
+ list_for_each(p, &atm_devs) {
+ dev = list_entry(p, struct atm_dev, dev_list);
+ if ((dev->ops) && (dev->number == number)) {
+ atm_dev_hold(dev);
+ return dev;
+ }
+ }
+ return NULL;
+}
+
+struct atm_dev *atm_dev_lookup(int number)
+{
+ struct atm_dev *dev;
+
+ spin_lock(&atm_dev_lock);
+ dev = __atm_dev_lookup(number);
+ spin_unlock(&atm_dev_lock);
+ return dev;
+}
+
+struct atm_dev *atm_dev_register(const char *type, const struct atmdev_ops *ops,
+ int number, atm_dev_flags_t *flags)
+{
+ struct atm_dev *dev, *inuse;
+
+
+ dev = __alloc_atm_dev(type);
+ if (!dev) {
+ printk(KERN_ERR "atm_dev_register: no space for dev %s\n",
+ type);
+ return NULL;
+ }
+ spin_lock(&atm_dev_lock);
+ if (number != -1) {
+ if ((inuse = __atm_dev_lookup(number))) {
+ atm_dev_put(inuse);
+ spin_unlock(&atm_dev_lock);
+ __free_atm_dev(dev);
+ return NULL;
+ }
+ dev->number = number;
+ } else {
+ dev->number = 0;
+ while ((inuse = __atm_dev_lookup(dev->number))) {
+ atm_dev_put(inuse);
+ dev->number++;
+ }
+ }
+
+ dev->ops = ops;
+ if (flags)
+ dev->flags = *flags;
+ else
+ memset(&dev->flags, 0, sizeof(dev->flags));
+ memset(&dev->stats, 0, sizeof(dev->stats));
+ atomic_set(&dev->refcnt, 1);
+ list_add_tail(&dev->dev_list, &atm_devs);
+ spin_unlock(&atm_dev_lock);
+
+#ifdef CONFIG_PROC_FS
+ if (ops->proc_read) {
+ if (atm_proc_dev_register(dev) < 0) {
+ printk(KERN_ERR "atm_dev_register: "
+ "atm_proc_dev_register failed for dev %s\n",
+ type);
+ spin_lock(&atm_dev_lock);
+ list_del(&dev->dev_list);
+ spin_unlock(&atm_dev_lock);
+ __free_atm_dev(dev);
+ return NULL;
+ }
+ }
+#endif
+
+ return dev;
+}
+
+
+void atm_dev_deregister(struct atm_dev *dev)
+{
+ unsigned long warning_time;
+
+#ifdef CONFIG_PROC_FS
+ if (dev->ops->proc_read)
+ atm_proc_dev_deregister(dev);
+#endif
+ spin_lock(&atm_dev_lock);
+ list_del(&dev->dev_list);
+ spin_unlock(&atm_dev_lock);
+
+ warning_time = jiffies;
+ while (atomic_read(&dev->refcnt) != 1) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ / 4);
+ current->state = TASK_RUNNING;
+ if ((jiffies - warning_time) > 10 * HZ) {
+ printk(KERN_EMERG "atm_dev_deregister: waiting for "
+ "dev %d to become free. Usage count = %d\n",
+ dev->number, atomic_read(&dev->refcnt));
+ warning_time = jiffies;
+ }
+ }
+
+ __free_atm_dev(dev);
+}
+
+void shutdown_atm_dev(struct atm_dev *dev)
+{
+ if (atomic_read(&dev->refcnt) > 1) {
+ set_bit(ATM_DF_CLOSE, &dev->flags);
+ return;
+ }
+ if (dev->ops->dev_close)
+ dev->ops->dev_close(dev);
+ atm_dev_deregister(dev);
+}
+
+
+static void copy_aal_stats(struct k_atm_aal_stats *from,
+ struct atm_aal_stats *to)
+{
+#define __HANDLE_ITEM(i) to->i = atomic_read(&from->i)
+ __AAL_STAT_ITEMS
+#undef __HANDLE_ITEM
+}
+
+
+static void subtract_aal_stats(struct k_atm_aal_stats *from,
+ struct atm_aal_stats *to)
+{
+#define __HANDLE_ITEM(i) atomic_sub(to->i, &from->i)
+ __AAL_STAT_ITEMS
+#undef __HANDLE_ITEM
+}
+
+
+static int fetch_stats(struct atm_dev *dev, struct atm_dev_stats *arg, int zero)
+{
+ struct atm_dev_stats tmp;
+ int error = 0;
+
+ copy_aal_stats(&dev->stats.aal0, &tmp.aal0);
+ copy_aal_stats(&dev->stats.aal34, &tmp.aal34);
+ copy_aal_stats(&dev->stats.aal5, &tmp.aal5);
+ if (arg)
+ error = copy_to_user(arg, &tmp, sizeof(tmp));
+ if (zero && !error) {
+ subtract_aal_stats(&dev->stats.aal0, &tmp.aal0);
+ subtract_aal_stats(&dev->stats.aal34, &tmp.aal34);
+ subtract_aal_stats(&dev->stats.aal5, &tmp.aal5);
+ }
+ return error ? -EFAULT : 0;
+}
+
+
+int atm_dev_ioctl(unsigned int cmd, unsigned long arg)
+{
+ void *buf;
+ int error = 0, len, number, size = 0;
+ struct atm_dev *dev;
+
+ if (cmd == ATM_GETNAMES) {
+ int *tmp_buf, *tmp_bufp;
+ struct list_head *p;
+ /*
+ * ATM_GETNAMES is a special case: it doesn't require a
+ * device number argument
+ */
+ if (get_user(buf, &((struct atm_iobuf *) arg)->buffer))
+ return -EFAULT;
+ if (get_user(len, &((struct atm_iobuf *) arg)->length))
+ return -EFAULT;
+ spin_lock(&atm_dev_lock);
+ list_for_each(p, &atm_devs)
+ size += sizeof(int);
+ if (size > len) {
+ spin_unlock(&atm_dev_lock);
+ return -E2BIG;
+ }
+ tmp_buf = tmp_bufp = kmalloc(size, GFP_ATOMIC);
+ if (!tmp_buf) {
+ spin_unlock(&atm_dev_lock);
+ return -ENOMEM;
+ }
+ list_for_each(p, &atm_devs) {
+ dev = list_entry(p, struct atm_dev, dev_list);
+ *tmp_bufp++ = dev->number;
+ }
+ spin_unlock(&atm_dev_lock);
+ error = (copy_to_user(buf, tmp_buf, size) ||
+ put_user(size, &((struct atm_iobuf *) arg)->length))
+ ? -EFAULT : 0;
+ kfree(tmp_buf);
+ return error;
+ }
+
+ if (get_user(buf, &((struct atmif_sioc *) arg)->arg))
+ return -EFAULT;
+ if (get_user(len, &((struct atmif_sioc *) arg)->length))
+ return -EFAULT;
+ if (get_user(number, &((struct atmif_sioc *) arg)->number))
+ return -EFAULT;
+
+ if (!(dev = atm_dev_lookup(number)))
+ return -ENODEV;
+
+ switch (cmd) {
+ case ATM_GETTYPE:
+ size = strlen(dev->type) + 1;
+ if (copy_to_user(buf, dev->type, size)) {
+ error = -EFAULT;
+ goto done;
+ }
+ break;
+ case ATM_GETESI:
+ size = ESI_LEN;
+ if (copy_to_user(buf, dev->esi, size)) {
+ error = -EFAULT;
+ goto done;
+ }
+ break;
+ case ATM_SETESI:
+ {
+ int i;
+
+ for (i = 0; i < ESI_LEN; i++)
+ if (dev->esi[i]) {
+ error = -EEXIST;
+ goto done;
+ }
+ }
+ /* fall through */
+ case ATM_SETESIF:
+ {
+ unsigned char esi[ESI_LEN];
+
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ if (copy_from_user(esi, buf, ESI_LEN)) {
+ error = -EFAULT;
+ goto done;
+ }
+ memcpy(dev->esi, esi, ESI_LEN);
+ error = ESI_LEN;
+ goto done;
+ }
+ case ATM_GETSTATZ:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ /* fall through */
+ case ATM_GETSTAT:
+ size = sizeof(struct atm_dev_stats);
+ error = fetch_stats(dev, buf, cmd == ATM_GETSTATZ);
+ if (error)
+ goto done;
+ break;
+ case ATM_GETCIRANGE:
+ size = sizeof(struct atm_cirange);
+ if (copy_to_user(buf, &dev->ci_range, size)) {
+ error = -EFAULT;
+ goto done;
+ }
+ break;
+ case ATM_GETLINKRATE:
+ size = sizeof(int);
+ if (copy_to_user(buf, &dev->link_rate, size)) {
+ error = -EFAULT;
+ goto done;
+ }
+ break;
+ case ATM_RSTADDR:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ atm_reset_addr(dev);
+ break;
+ case ATM_ADDADDR:
+ case ATM_DELADDR:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ {
+ struct sockaddr_atmsvc addr;
+
+ if (copy_from_user(&addr, buf, sizeof(addr))) {
+ error = -EFAULT;
+ goto done;
+ }
+ if (cmd == ATM_ADDADDR)
+ error = atm_add_addr(dev, &addr);
+ else
+ error = atm_del_addr(dev, &addr);
+ goto done;
+ }
+ case ATM_GETADDR:
+ error = atm_get_addr(dev, buf, len);
+ if (error < 0)
+ goto done;
+ size = error;
+ /* write back size even if it's zero */
+ goto write_size;
+ case ATM_SETLOOP:
+ if (__ATM_LM_XTRMT((int) (long) buf) &&
+ __ATM_LM_XTLOC((int) (long) buf) >
+ __ATM_LM_XTRMT((int) (long) buf)) {
+ error = -EINVAL;
+ goto done;
+ }
+ /* fall through */
+ case ATM_SETCIRANGE:
+ case SONET_GETSTATZ:
+ case SONET_SETDIAG:
+ case SONET_CLRDIAG:
+ case SONET_SETFRAMING:
+ if (!capable(CAP_NET_ADMIN)) {
+ error = -EPERM;
+ goto done;
+ }
+ /* fall through */
+ default:
+ if (!dev->ops->ioctl) {
+ error = -EINVAL;
+ goto done;
+ }
+ size = dev->ops->ioctl(dev, cmd, buf);
+ if (size < 0) {
+ error = (size == -ENOIOCTLCMD ? -EINVAL : size);
+ goto done;
+ }
+ }
+
+ if (size) {
+write_size:
+ error = put_user(size,
+ &((struct atmif_sioc *) arg)->length)
+ ? -EFAULT : 0;
+ }
+done:
+ atm_dev_put(dev);
+ return error;
+}
+
+
+EXPORT_SYMBOL(atm_dev_register);
+EXPORT_SYMBOL(atm_dev_deregister);
+EXPORT_SYMBOL(atm_dev_lookup);
+EXPORT_SYMBOL(shutdown_atm_dev);
diff --git a/uClinux-2.4.31-uc0/net/atm/resources.h b/uClinux-2.4.31-uc0/net/atm/resources.h
new file mode 100644
index 0000000..c4ecc1b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/resources.h
@@ -0,0 +1,29 @@
+/* net/atm/resources.h - ATM-related resources */
+
+/* Written 1995-1998 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#ifndef NET_ATM_RESOURCES_H
+#define NET_ATM_RESOURCES_H
+
+#include <linux/config.h>
+#include <linux/atmdev.h>
+
+
+extern struct list_head atm_devs;
+extern spinlock_t atm_dev_lock;
+
+
+int atm_dev_ioctl(unsigned int cmd, unsigned long arg);
+
+
+#ifdef CONFIG_PROC_FS
+
+#include <linux/proc_fs.h>
+
+int atm_proc_dev_register(struct atm_dev *dev);
+void atm_proc_dev_deregister(struct atm_dev *dev);
+
+#endif
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/atm/signaling.c b/uClinux-2.4.31-uc0/net/atm/signaling.c
new file mode 100644
index 0000000..b16313f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/signaling.c
@@ -0,0 +1,260 @@
+/* net/atm/signaling.c - ATM signaling */
+
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#include <linux/errno.h> /* error codes */
+#include <linux/kernel.h> /* printk */
+#include <linux/skbuff.h>
+#include <linux/wait.h>
+#include <linux/sched.h> /* jiffies and HZ */
+#include <linux/atm.h> /* ATM stuff */
+#include <linux/atmsap.h>
+#include <linux/atmsvc.h>
+#include <linux/atmdev.h>
+#include <linux/bitops.h>
+
+#include "resources.h"
+#include "signaling.h"
+
+
+#undef WAIT_FOR_DEMON /* #define this if system calls on SVC sockets
+ should block until the demon runs.
+ Danger: may cause nasty hangs if the demon
+ crashes. */
+
+#if 0
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+
+struct atm_vcc *sigd = NULL;
+static DECLARE_WAIT_QUEUE_HEAD(sigd_sleep);
+
+
+static void sigd_put_skb(struct sk_buff *skb)
+{
+#ifdef WAIT_FOR_DEMON
+ static unsigned long silence = 0;
+ DECLARE_WAITQUEUE(wait,current);
+
+ add_wait_queue(&sigd_sleep,&wait);
+ while (!sigd) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ if (time_after(jiffies, silence) || silence == 0) {
+ printk(KERN_INFO "atmsvc: waiting for signaling demon "
+ "...\n");
+ silence = (jiffies+30*HZ)|1;
+ }
+ schedule();
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&sigd_sleep,&wait);
+#else
+ if (!sigd) {
+ printk(KERN_WARNING "atmsvc: no signaling demon\n");
+ kfree_skb(skb);
+ return;
+ }
+#endif
+ atm_force_charge(sigd,skb->truesize);
+ skb_queue_tail(&sigd->sk->receive_queue,skb);
+ wake_up(&sigd->sleep);
+}
+
+
+static void modify_qos(struct atm_vcc *vcc,struct atmsvc_msg *msg)
+{
+ struct sk_buff *skb;
+
+ if (test_bit(ATM_VF_RELEASED,&vcc->flags) ||
+ !test_bit(ATM_VF_READY,&vcc->flags))
+ return;
+ msg->type = as_error;
+ if (!vcc->dev->ops->change_qos) msg->reply = -EOPNOTSUPP;
+ else {
+ /* should lock VCC */
+ msg->reply = vcc->dev->ops->change_qos(vcc,&msg->qos,
+ msg->reply);
+ if (!msg->reply) msg->type = as_okay;
+ }
+ /*
+ * Should probably just turn around the old skb. But the, the buffer
+ * space accounting needs to follow the change too. Maybe later.
+ */
+ while (!(skb = alloc_skb(sizeof(struct atmsvc_msg),GFP_KERNEL)))
+ schedule();
+ *(struct atmsvc_msg *) skb_put(skb,sizeof(struct atmsvc_msg)) = *msg;
+ sigd_put_skb(skb);
+}
+
+
+static int sigd_send(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+ struct atmsvc_msg *msg;
+ struct atm_vcc *session_vcc;
+
+ msg = (struct atmsvc_msg *) skb->data;
+ atomic_sub(skb->truesize, &vcc->sk->wmem_alloc);
+ DPRINTK("sigd_send %d (0x%lx)\n",(int) msg->type,
+ (unsigned long) msg->vcc);
+ vcc = *(struct atm_vcc **) &msg->vcc;
+ switch (msg->type) {
+ case as_okay:
+ vcc->reply = msg->reply;
+ if (!*vcc->local.sas_addr.prv &&
+ !*vcc->local.sas_addr.pub) {
+ vcc->local.sas_family = AF_ATMSVC;
+ memcpy(vcc->local.sas_addr.prv,
+ msg->local.sas_addr.prv,ATM_ESA_LEN);
+ memcpy(vcc->local.sas_addr.pub,
+ msg->local.sas_addr.pub,ATM_E164_LEN+1);
+ }
+ session_vcc = vcc->session ? vcc->session : vcc;
+ if (session_vcc->vpi || session_vcc->vci) break;
+ session_vcc->itf = msg->pvc.sap_addr.itf;
+ session_vcc->vpi = msg->pvc.sap_addr.vpi;
+ session_vcc->vci = msg->pvc.sap_addr.vci;
+ if (session_vcc->vpi || session_vcc->vci)
+ session_vcc->qos = msg->qos;
+ break;
+ case as_error:
+ clear_bit(ATM_VF_REGIS,&vcc->flags);
+ clear_bit(ATM_VF_READY,&vcc->flags);
+ vcc->reply = msg->reply;
+ vcc->sk->err = -msg->reply;
+ break;
+ case as_indicate:
+ vcc = *(struct atm_vcc **) &msg->listen_vcc;
+ DPRINTK("as_indicate!!!\n");
+ lock_sock(vcc->sk);
+ if (vcc->sk->ack_backlog == vcc->sk->max_ack_backlog) {
+ sigd_enq(0,as_reject,vcc,NULL,NULL);
+ goto as_indicate_complete;
+ }
+ vcc->sk->ack_backlog++;
+ skb_queue_tail(&vcc->sk->receive_queue,skb);
+ if (vcc->callback) {
+ DPRINTK("waking vcc->sleep 0x%p\n",
+ &vcc->sleep);
+ vcc->callback(vcc);
+ }
+as_indicate_complete:
+ release_sock(vcc->sk);
+ return 0;
+ case as_close:
+ set_bit(ATM_VF_RELEASED,&vcc->flags);
+ clear_bit(ATM_VF_READY,&vcc->flags);
+ vcc->reply = msg->reply;
+ vcc->sk->err = -msg->reply;
+ break;
+ case as_modify:
+ modify_qos(vcc,msg);
+ break;
+ default:
+ printk(KERN_ALERT "sigd_send: bad message type %d\n",
+ (int) msg->type);
+ return -EINVAL;
+ }
+ if (vcc->callback) vcc->callback(vcc);
+ dev_kfree_skb(skb);
+ return 0;
+}
+
+
+void sigd_enq2(struct atm_vcc *vcc,enum atmsvc_msg_type type,
+ struct atm_vcc *listen_vcc,const struct sockaddr_atmpvc *pvc,
+ const struct sockaddr_atmsvc *svc,const struct atm_qos *qos,int reply)
+{
+ struct sk_buff *skb;
+ struct atmsvc_msg *msg;
+
+ DPRINTK("sigd_enq %d (0x%p)\n",(int) type,vcc);
+ while (!(skb = alloc_skb(sizeof(struct atmsvc_msg),GFP_KERNEL)))
+ schedule();
+ msg = (struct atmsvc_msg *) skb_put(skb,sizeof(struct atmsvc_msg));
+ memset(msg,0,sizeof(*msg));
+ msg->type = type;
+ *(struct atm_vcc **) &msg->vcc = vcc;
+ *(struct atm_vcc **) &msg->listen_vcc = listen_vcc;
+ msg->reply = reply;
+ if (qos) msg->qos = *qos;
+ if (vcc) msg->sap = vcc->sap;
+ if (svc) msg->svc = *svc;
+ if (vcc) msg->local = vcc->local;
+ if (pvc) msg->pvc = *pvc;
+ sigd_put_skb(skb);
+ if (vcc) set_bit(ATM_VF_REGIS,&vcc->flags);
+}
+
+
+void sigd_enq(struct atm_vcc *vcc,enum atmsvc_msg_type type,
+ struct atm_vcc *listen_vcc,const struct sockaddr_atmpvc *pvc,
+ const struct sockaddr_atmsvc *svc)
+{
+ sigd_enq2(vcc,type,listen_vcc,pvc,svc,vcc ? &vcc->qos : NULL,0);
+ /* other ISP applications may use "reply" */
+}
+
+
+static void purge_vcc(struct atm_vcc *vcc)
+{
+ if (vcc->sk->family == PF_ATMSVC &&
+ !test_bit(ATM_VF_META, &vcc->flags)) {
+ set_bit(ATM_VF_RELEASED, &vcc->flags);
+ vcc->reply = -EUNATCH;
+ vcc->sk->err = EUNATCH;
+ wake_up(&vcc->sleep);
+ }
+}
+
+
+static void sigd_close(struct atm_vcc *vcc)
+{
+ struct sock *s;
+
+ DPRINTK("sigd_close\n");
+ sigd = NULL;
+ if (skb_peek(&vcc->sk->receive_queue))
+ printk(KERN_ERR "sigd_close: closing with requests pending\n");
+ skb_queue_purge(&vcc->sk->receive_queue);
+
+ read_lock(&vcc_sklist_lock);
+ for(s = vcc_sklist; s; s = s->next) {
+ struct atm_vcc *vcc = s->protinfo.af_atm;
+
+ if (vcc->dev)
+ purge_vcc(vcc);
+ }
+ read_unlock(&vcc_sklist_lock);
+}
+
+
+static struct atmdev_ops sigd_dev_ops = {
+ .close = sigd_close,
+ .send = sigd_send
+};
+
+
+static struct atm_dev sigd_dev = {
+ .ops = &sigd_dev_ops,
+ .type = "sig",
+ .number = 999,
+ .lock = SPIN_LOCK_UNLOCKED
+};
+
+
+int sigd_attach(struct atm_vcc *vcc)
+{
+ if (sigd) return -EADDRINUSE;
+ DPRINTK("sigd_attach\n");
+ sigd = vcc;
+ vcc->dev = &sigd_dev;
+ vcc_insert_socket(vcc->sk);
+ set_bit(ATM_VF_META,&vcc->flags);
+ set_bit(ATM_VF_READY,&vcc->flags);
+ wake_up(&sigd_sleep);
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/atm/signaling.h b/uClinux-2.4.31-uc0/net/atm/signaling.h
new file mode 100644
index 0000000..3b933dd
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/signaling.h
@@ -0,0 +1,33 @@
+/* net/atm/signaling.h - ATM signaling */
+
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#ifndef NET_ATM_SIGNALING_H
+#define NET_ATM_SIGNALING_H
+
+#include <linux/atm.h>
+#include <linux/atmdev.h>
+#include <linux/atmsvc.h>
+
+
+#define WAITING 1 /* for reply: 0: no error, < 0: error, ... */
+
+
+extern struct atm_vcc *sigd; /* needed in svc_release */
+
+
+/*
+ * sigd_enq is a wrapper for sigd_enq2, covering the more common cases, and
+ * avoiding huge lists of null values.
+ */
+
+void sigd_enq2(struct atm_vcc *vcc,enum atmsvc_msg_type type,
+ struct atm_vcc *listen_vcc,const struct sockaddr_atmpvc *pvc,
+ const struct sockaddr_atmsvc *svc,const struct atm_qos *qos,int reply);
+void sigd_enq(struct atm_vcc *vcc,enum atmsvc_msg_type type,
+ struct atm_vcc *listen_vcc,const struct sockaddr_atmpvc *pvc,
+ const struct sockaddr_atmsvc *svc);
+int sigd_attach(struct atm_vcc *vcc);
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/atm/svc.c b/uClinux-2.4.31-uc0/net/atm/svc.c
new file mode 100644
index 0000000..9709bf5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/atm/svc.c
@@ -0,0 +1,574 @@
+/* net/atm/svc.c - ATM SVC sockets */
+
+/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */
+
+
+#include <linux/string.h>
+#include <linux/net.h> /* struct socket, struct net_proto,
+ struct proto_ops */
+#include <linux/errno.h> /* error codes */
+#include <linux/kernel.h> /* printk */
+#include <linux/skbuff.h>
+#include <linux/wait.h>
+#include <linux/sched.h> /* jiffies and HZ */
+#include <linux/fcntl.h> /* O_NONBLOCK */
+#include <linux/init.h>
+#include <linux/atm.h> /* ATM stuff */
+#include <linux/atmsap.h>
+#include <linux/atmsvc.h>
+#include <linux/atmdev.h>
+#include <linux/bitops.h>
+#include <net/sock.h> /* for sock_no_* */
+#include <asm/uaccess.h>
+
+#include "resources.h"
+#include "common.h" /* common for PVCs and SVCs */
+#include "signaling.h"
+#include "addr.h"
+
+
+#if 0
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+
+static int svc_create(struct socket *sock,int protocol);
+
+
+/*
+ * Note: since all this is still nicely synchronized with the signaling demon,
+ * there's no need to protect sleep loops with clis. If signaling is
+ * moved into the kernel, that would change.
+ */
+
+
+void svc_callback(struct atm_vcc *vcc)
+{
+ wake_up(&vcc->sleep);
+}
+
+
+
+
+static int svc_shutdown(struct socket *sock,int how)
+{
+ return 0;
+}
+
+
+static void svc_disconnect(struct atm_vcc *vcc)
+{
+ DECLARE_WAITQUEUE(wait,current);
+ struct sk_buff *skb;
+
+ DPRINTK("svc_disconnect %p\n",vcc);
+ if (test_bit(ATM_VF_REGIS,&vcc->flags)) {
+ add_wait_queue(&vcc->sleep,&wait);
+ sigd_enq(vcc,as_close,NULL,NULL,NULL);
+ while (!test_bit(ATM_VF_RELEASED,&vcc->flags) && sigd) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule();
+ }
+ remove_wait_queue(&vcc->sleep,&wait);
+ }
+ /* beware - socket is still in use by atmsigd until the last
+ as_indicate has been answered */
+ while ((skb = skb_dequeue(&vcc->sk->receive_queue))) {
+ DPRINTK("LISTEN REL\n");
+ sigd_enq2(NULL,as_reject,vcc,NULL,NULL,&vcc->qos,0);
+ dev_kfree_skb(skb);
+ }
+ clear_bit(ATM_VF_REGIS,&vcc->flags);
+ clear_bit(ATM_VF_RELEASED,&vcc->flags);
+ clear_bit(ATM_VF_CLOSE,&vcc->flags);
+ /* ... may retry later */
+}
+
+
+static int svc_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct atm_vcc *vcc;
+
+ if (sk) {
+ vcc = ATM_SD(sock);
+ DPRINTK("svc_release %p\n", vcc);
+ clear_bit(ATM_VF_READY, &vcc->flags);
+ /* VCC pointer is used as a reference, so we must not free it
+ (thereby subjecting it to re-use) before all pending connections
+ are closed */
+ sock_hold(sk);
+ vcc_release(sock);
+ svc_disconnect(vcc);
+ sock_put(sk);
+ }
+ return 0;
+}
+
+
+static int svc_bind(struct socket *sock,struct sockaddr *sockaddr,
+ int sockaddr_len)
+{
+ DECLARE_WAITQUEUE(wait,current);
+ struct sock *sk = sock->sk;
+ struct sockaddr_atmsvc *addr;
+ struct atm_vcc *vcc;
+ int error;
+
+ if (sockaddr_len != sizeof(struct sockaddr_atmsvc))
+ return -EINVAL;
+ lock_sock(sk);
+ if (sock->state == SS_CONNECTED) {
+ error = -EISCONN;
+ goto out;
+ }
+ if (sock->state != SS_UNCONNECTED) {
+ error = -EINVAL;
+ goto out;
+ }
+ vcc = ATM_SD(sock);
+ if (test_bit(ATM_VF_SESSION, &vcc->flags)) {
+ error = -EINVAL;
+ goto out;
+ }
+ addr = (struct sockaddr_atmsvc *) sockaddr;
+ if (addr->sas_family != AF_ATMSVC) {
+ error = -EAFNOSUPPORT;
+ goto out;
+ }
+ clear_bit(ATM_VF_BOUND,&vcc->flags);
+ /* failing rebind will kill old binding */
+ /* @@@ check memory (de)allocation on rebind */
+ if (!test_bit(ATM_VF_HASQOS,&vcc->flags)) {
+ error = -EBADFD;
+ goto out;
+ }
+ vcc->local = *addr;
+ vcc->reply = WAITING;
+ add_wait_queue(&vcc->sleep,&wait);
+ sigd_enq(vcc,as_bind,NULL,NULL,&vcc->local);
+ while (vcc->reply == WAITING && sigd) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule();
+ }
+ remove_wait_queue(&vcc->sleep,&wait);
+ clear_bit(ATM_VF_REGIS,&vcc->flags); /* doesn't count */
+ if (!sigd) {
+ error = -EUNATCH;
+ goto out;
+ }
+ if (!vcc->reply)
+ set_bit(ATM_VF_BOUND,&vcc->flags);
+ error = vcc->reply;
+out:
+ release_sock(sk);
+ return error;
+}
+
+
+static int svc_connect(struct socket *sock,struct sockaddr *sockaddr,
+ int sockaddr_len,int flags)
+{
+ DECLARE_WAITQUEUE(wait,current);
+ struct sock *sk = sock->sk;
+ struct sockaddr_atmsvc *addr;
+ struct atm_vcc *vcc = ATM_SD(sock);
+ int error;
+
+ DPRINTK("svc_connect %p\n",vcc);
+ lock_sock(sk);
+ if (sockaddr_len != sizeof(struct sockaddr_atmsvc)) {
+ error = -EINVAL;
+ goto out;
+ }
+
+ switch (sock->state) {
+ default:
+ error = -EINVAL;
+ goto out;
+ case SS_CONNECTED:
+ error = -EISCONN;
+ goto out;
+ case SS_CONNECTING:
+ if (vcc->reply == WAITING) {
+ error = -EALREADY;
+ goto out;
+ }
+ sock->state = SS_UNCONNECTED;
+ if (vcc->reply) {
+ error = vcc->reply;
+ goto out;
+ }
+ break;
+ case SS_UNCONNECTED:
+ if (test_bit(ATM_VF_SESSION, &vcc->flags)) {
+ error = -EINVAL;
+ goto out;
+ }
+ addr = (struct sockaddr_atmsvc *) sockaddr;
+ if (addr->sas_family != AF_ATMSVC) {
+ error = -EAFNOSUPPORT;
+ goto out;
+ }
+ if (!test_bit(ATM_VF_HASQOS, &vcc->flags)) {
+ error = -EBADFD;
+ goto out;
+ }
+ if (vcc->qos.txtp.traffic_class == ATM_ANYCLASS ||
+ vcc->qos.rxtp.traffic_class == ATM_ANYCLASS) {
+ error = -EINVAL;
+ goto out;
+ }
+ if (!vcc->qos.txtp.traffic_class &&
+ !vcc->qos.rxtp.traffic_class) {
+ error = -EINVAL;
+ goto out;
+ }
+ vcc->remote = *addr;
+ vcc->reply = WAITING;
+ add_wait_queue(&vcc->sleep,&wait);
+ sigd_enq(vcc,as_connect,NULL,NULL,&vcc->remote);
+ if (flags & O_NONBLOCK) {
+ remove_wait_queue(&vcc->sleep,&wait);
+ sock->state = SS_CONNECTING;
+ error = -EINPROGRESS;
+ goto out;
+ }
+ error = 0;
+ while (vcc->reply == WAITING && sigd) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule();
+ if (!signal_pending(current)) continue;
+ DPRINTK("*ABORT*\n");
+ /*
+ * This is tricky:
+ * Kernel ---close--> Demon
+ * Kernel <--close--- Demon
+ * or
+ * Kernel ---close--> Demon
+ * Kernel <--error--- Demon
+ * or
+ * Kernel ---close--> Demon
+ * Kernel <--okay---- Demon
+ * Kernel <--close--- Demon
+ */
+ sigd_enq(vcc,as_close,NULL,NULL,NULL);
+ while (vcc->reply == WAITING && sigd) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule();
+ }
+ if (!vcc->reply)
+ while (!test_bit(ATM_VF_RELEASED,&vcc->flags)
+ && sigd) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule();
+ }
+ clear_bit(ATM_VF_REGIS,&vcc->flags);
+ clear_bit(ATM_VF_RELEASED,&vcc->flags);
+ clear_bit(ATM_VF_CLOSE,&vcc->flags);
+ /* we're gone now but may connect later */
+ error = -EINTR;
+ break;
+ }
+ remove_wait_queue(&vcc->sleep,&wait);
+ if (error)
+ goto out;
+ if (!sigd) {
+ error = -EUNATCH;
+ goto out;
+ }
+ if (vcc->reply) {
+ error = vcc->reply;
+ goto out;
+ }
+ }
+/*
+ * Not supported yet
+ *
+ * #ifndef CONFIG_SINGLE_SIGITF
+ */
+ vcc->qos.txtp.max_pcr = SELECT_TOP_PCR(vcc->qos.txtp);
+ vcc->qos.txtp.pcr = 0;
+ vcc->qos.txtp.min_pcr = 0;
+/*
+ * #endif
+ */
+ if (!(error = vcc_connect(sock, vcc->itf, vcc->vpi, vcc->vci)))
+ sock->state = SS_CONNECTED;
+ else (void) svc_disconnect(vcc);
+out:
+ release_sock(sk);
+ return error;
+}
+
+
+static int svc_listen(struct socket *sock,int backlog)
+{
+ DECLARE_WAITQUEUE(wait,current);
+ struct sock *sk = sock->sk;
+ struct atm_vcc *vcc = ATM_SD(sock);
+ int error;
+
+ DPRINTK("svc_listen %p\n",vcc);
+ lock_sock(sk);
+ /* let server handle listen on unbound sockets */
+ if (test_bit(ATM_VF_SESSION,&vcc->flags)) {
+ error = -EINVAL;
+ goto out;
+ }
+ vcc->reply = WAITING;
+ add_wait_queue(&vcc->sleep,&wait);
+ sigd_enq(vcc,as_listen,NULL,NULL,&vcc->local);
+ while (vcc->reply == WAITING && sigd) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule();
+ }
+ remove_wait_queue(&vcc->sleep,&wait);
+ if (!sigd) {
+ error = -EUNATCH;
+ goto out;
+ }
+ set_bit(ATM_VF_LISTEN,&vcc->flags);
+ vcc->sk->max_ack_backlog = backlog > 0 ? backlog : ATM_BACKLOG_DEFAULT;
+ error = vcc->reply;
+out:
+ release_sock(sk);
+ return error;
+}
+
+
+static int svc_accept(struct socket *sock,struct socket *newsock,int flags)
+{
+ struct sock *sk = sock->sk;
+ struct sk_buff *skb;
+ struct atmsvc_msg *msg;
+ struct atm_vcc *old_vcc = ATM_SD(sock);
+ struct atm_vcc *new_vcc;
+ int error;
+
+ lock_sock(sk);
+
+ error = svc_create(newsock,0);
+ if (error)
+ goto out;
+
+ new_vcc = ATM_SD(newsock);
+
+ DPRINTK("svc_accept %p -> %p\n",old_vcc,new_vcc);
+ while (1) {
+ DECLARE_WAITQUEUE(wait,current);
+
+ add_wait_queue(&old_vcc->sleep,&wait);
+ while (!(skb = skb_dequeue(&old_vcc->sk->receive_queue)) && sigd) {
+ if (test_bit(ATM_VF_RELEASED,&old_vcc->flags)) break;
+ if (test_bit(ATM_VF_CLOSE,&old_vcc->flags)) {
+ error = old_vcc->reply;
+ break;
+ }
+ if (flags & O_NONBLOCK) {
+ error = -EAGAIN;
+ break;
+ }
+ release_sock(sk);
+ schedule();
+ lock_sock(sk);
+ if (signal_pending(current)) {
+ error = -ERESTARTSYS;
+ break;
+ }
+ }
+ remove_wait_queue(&old_vcc->sleep,&wait);
+ if (error)
+ goto out;
+ if (!skb) {
+ error = -EUNATCH;
+ goto out;
+ }
+ msg = (struct atmsvc_msg *) skb->data;
+ new_vcc->qos = msg->qos;
+ set_bit(ATM_VF_HASQOS,&new_vcc->flags);
+ new_vcc->remote = msg->svc;
+ new_vcc->local = msg->local;
+ new_vcc->sap = msg->sap;
+ error = vcc_connect(newsock, msg->pvc.sap_addr.itf,
+ msg->pvc.sap_addr.vpi, msg->pvc.sap_addr.vci);
+ dev_kfree_skb(skb);
+ old_vcc->sk->ack_backlog--;
+ if (error) {
+ sigd_enq2(NULL,as_reject,old_vcc,NULL,NULL,
+ &old_vcc->qos,error);
+ error = error == -EAGAIN ? -EBUSY : error;
+ goto out;
+ }
+ /* wait should be short, so we ignore the non-blocking flag */
+ new_vcc->reply = WAITING;
+ add_wait_queue(&new_vcc->sleep,&wait);
+ sigd_enq(new_vcc,as_accept,old_vcc,NULL,NULL);
+ while (new_vcc->reply == WAITING && sigd) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ release_sock(sk);
+ schedule();
+ lock_sock(sk);
+ }
+ remove_wait_queue(&new_vcc->sleep,&wait);
+ if (!sigd) {
+ error = -EUNATCH;
+ goto out;
+ }
+ if (!new_vcc->reply) break;
+ if (new_vcc->reply != -ERESTARTSYS) {
+ error = new_vcc->reply;
+ goto out;
+ }
+ }
+ newsock->state = SS_CONNECTED;
+out:
+ release_sock(sk);
+ return error;
+}
+
+
+static int svc_getname(struct socket *sock,struct sockaddr *sockaddr,
+ int *sockaddr_len,int peer)
+{
+ struct sockaddr_atmsvc *addr;
+
+ *sockaddr_len = sizeof(struct sockaddr_atmsvc);
+ addr = (struct sockaddr_atmsvc *) sockaddr;
+ memcpy(addr,peer ? &ATM_SD(sock)->remote : &ATM_SD(sock)->local,
+ sizeof(struct sockaddr_atmsvc));
+ return 0;
+}
+
+
+int svc_change_qos(struct atm_vcc *vcc,struct atm_qos *qos)
+{
+ DECLARE_WAITQUEUE(wait,current);
+
+ vcc->reply = WAITING;
+ add_wait_queue(&vcc->sleep,&wait);
+ sigd_enq2(vcc,as_modify,NULL,NULL,&vcc->local,qos,0);
+ while (vcc->reply == WAITING && !test_bit(ATM_VF_RELEASED,&vcc->flags)
+ && sigd) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule();
+ }
+ remove_wait_queue(&vcc->sleep,&wait);
+ if (!sigd) return -EUNATCH;
+ return vcc->reply;
+}
+
+
+static int svc_setsockopt(struct socket *sock,int level,int optname,
+ char *optval,int optlen)
+{
+ struct sock *sk = sock->sk;
+ struct atm_vcc *vcc;
+ int error = 0;
+
+ if (!__SO_LEVEL_MATCH(optname, level) || optname != SO_ATMSAP ||
+ optlen != sizeof(struct atm_sap)) {
+ error = vcc_setsockopt(sock, level, optname, optval, optlen);
+ goto out;
+ }
+ vcc = ATM_SD(sock);
+ if (copy_from_user(&vcc->sap, optval, optlen)) {
+ error = -EFAULT;
+ goto out;
+ }
+ set_bit(ATM_VF_HASSAP, &vcc->flags);
+out:
+ release_sock(sk);
+ return error;
+}
+
+
+static int svc_getsockopt(struct socket *sock,int level,int optname,
+ char *optval,int *optlen)
+{
+ struct sock *sk = sock->sk;
+ int error = 0, len;
+
+ lock_sock(sk);
+ if (!__SO_LEVEL_MATCH(optname, level) || optname != SO_ATMSAP) {
+ error = vcc_getsockopt(sock, level, optname, optval, optlen);
+ goto out;
+ }
+ if (get_user(len, optlen)) {
+ error = -EFAULT;
+ goto out;
+ }
+ if (len != sizeof(struct atm_sap)) {
+ error = -EINVAL;
+ goto out;
+ }
+ if (copy_to_user(optval, &ATM_SD(sock)->sap, sizeof(struct atm_sap))) {
+ error = -EFAULT;
+ goto out;
+ }
+out:
+ release_sock(sk);
+ return error;
+}
+
+
+static struct proto_ops svc_proto_ops = {
+ .family = PF_ATMSVC,
+
+ .release = svc_release,
+ .bind = svc_bind,
+ .connect = svc_connect,
+ .socketpair = sock_no_socketpair,
+ .accept = svc_accept,
+ .getname = svc_getname,
+ .poll = atm_poll,
+ .ioctl = vcc_ioctl,
+ .listen = svc_listen,
+ .shutdown = svc_shutdown,
+ .setsockopt = svc_setsockopt,
+ .getsockopt = svc_getsockopt,
+ .sendmsg = vcc_sendmsg,
+ .recvmsg = vcc_recvmsg,
+ .mmap = sock_no_mmap,
+ .sendpage = sock_no_sendpage,
+};
+
+
+static int svc_create(struct socket *sock,int protocol)
+{
+ int error;
+
+ sock->ops = &svc_proto_ops;
+ error = vcc_create(sock, protocol, AF_ATMSVC);
+ if (error) return error;
+ ATM_SD(sock)->callback = svc_callback;
+ ATM_SD(sock)->local.sas_family = AF_ATMSVC;
+ ATM_SD(sock)->remote.sas_family = AF_ATMSVC;
+ return 0;
+}
+
+
+static struct net_proto_family svc_family_ops = {
+ PF_ATMSVC,
+ svc_create,
+ 0, /* no authentication */
+ 0, /* no encryption */
+ 0 /* no encrypt_net */
+};
+
+
+/*
+ * Initialize the ATM SVC protocol family
+ */
+
+int atmsvc_init(void)
+{
+ return sock_register(&svc_family_ops);
+}
+
+void atmsvc_exit(void)
+{
+ sock_unregister(PF_ATMSVC);
+}
diff --git a/uClinux-2.4.31-uc0/net/ax25/Config.in b/uClinux-2.4.31-uc0/net/ax25/Config.in
new file mode 100644
index 0000000..b8e5d73
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/Config.in
@@ -0,0 +1,36 @@
+#
+# Amateur Radio protocols and AX.25 device configuration
+#
+# 19971130 Now in an own category to make correct compilation of the
+# AX.25 stuff easier...
+# Joerg Reuter DL1BKE <jreuter@yaina.de>
+# 19980129 Moved to net/ax25/Config.in, sourcing device drivers.
+
+mainmenu_option next_comment
+comment 'Amateur Radio support'
+bool 'Amateur Radio support' CONFIG_HAMRADIO
+
+if [ "$CONFIG_HAMRADIO" != "n" ]; then
+ if [ "$CONFIG_NET" != "n" ]; then
+ comment 'Packet Radio protocols'
+ tristate ' Amateur Radio AX.25 Level 2 protocol' CONFIG_AX25
+ if [ "$CONFIG_AX25" != "n" ]; then
+ bool ' AX.25 DAMA Slave support' CONFIG_AX25_DAMA_SLAVE
+# bool ' AX.25 DAMA Master support' CONFIG_AX25_DAMA_MASTER
+ dep_tristate ' Amateur Radio NET/ROM protocol' CONFIG_NETROM $CONFIG_AX25
+ dep_tristate ' Amateur Radio X.25 PLP (Rose)' CONFIG_ROSE $CONFIG_AX25
+ fi
+
+ if [ "$CONFIG_AX25" != "n" ]; then
+ mainmenu_option next_comment
+ comment 'AX.25 network device drivers'
+
+ source drivers/net/hamradio/Config.in
+
+ endmenu
+ fi
+ fi
+
+fi
+
+endmenu
diff --git a/uClinux-2.4.31-uc0/net/ax25/Makefile b/uClinux-2.4.31-uc0/net/ax25/Makefile
new file mode 100644
index 0000000..5974031
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/Makefile
@@ -0,0 +1,25 @@
+#
+# Makefile for the Linux AX.25 layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+
+O_TARGET := ax25.o
+
+export-objs := af_ax25.o
+
+obj-y := ax25_addr.o ax25_dev.o ax25_iface.o ax25_in.o ax25_ip.o ax25_out.o \
+ ax25_route.o ax25_std_in.o ax25_std_subr.o ax25_std_timer.o \
+ ax25_subr.o ax25_timer.o ax25_uid.o af_ax25.o
+
+obj-m := $(O_TARGET)
+
+obj-$(CONFIG_AX25_DAMA_SLAVE) += ax25_ds_in.o ax25_ds_subr.o ax25_ds_timer.o
+obj-$(CONFIG_SYSCTL) += sysctl_net_ax25.o
+
+include $(TOPDIR)/Rules.make
+
diff --git a/uClinux-2.4.31-uc0/net/ax25/af_ax25.c b/uClinux-2.4.31-uc0/net/ax25/af_ax25.c
new file mode 100644
index 0000000..1304a34
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/af_ax25.c
@@ -0,0 +1,1884 @@
+/*
+ * AX.25 release 038
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * AX.25 006 Alan(GW4PTS) Nearly died of shock - it's working 8-)
+ * AX.25 007 Alan(GW4PTS) Removed the silliest bugs
+ * AX.25 008 Alan(GW4PTS) Cleaned up, fixed a few state machine problems, added callbacks
+ * AX.25 009 Alan(GW4PTS) Emergency patch kit to fix memory corruption
+ * AX.25 010 Alan(GW4PTS) Added RAW sockets/Digipeat.
+ * AX.25 011 Alan(GW4PTS) RAW socket and datagram fixes (thanks) - Raw sendto now gets PID right
+ * datagram sendto uses correct target address.
+ * AX.25 012 Alan(GW4PTS) Correct incoming connection handling, send DM to failed connects.
+ * Use skb->data not skb+1. Support sk->priority correctly.
+ * Correct receive on SOCK_DGRAM.
+ * AX.25 013 Alan(GW4PTS) Send DM to all unknown frames, missing initialiser fixed
+ * Leave spare SSID bits set (DAMA etc) - thanks for bug report,
+ * removed device registration (it's not used or needed). Clean up for
+ * gcc 2.5.8. PID to AX25_P_
+ * AX.25 014 Alan(GW4PTS) Cleanup and NET3 merge
+ * AX.25 015 Alan(GW4PTS) Internal test version.
+ * AX.25 016 Alan(GW4PTS) Semi Internal version for PI card
+ * work.
+ * AX.25 017 Alan(GW4PTS) Fixed some small bugs reported by
+ * G4KLX
+ * AX.25 018 Alan(GW4PTS) Fixed a small error in SOCK_DGRAM
+ * AX.25 019 Alan(GW4PTS) Clean ups for the non INET kernel and device ioctls in AX.25
+ * AX.25 020 Jonathan(G4KLX) /proc support and other changes.
+ * AX.25 021 Alan(GW4PTS) Added AX25_T1, AX25_N2, AX25_T3 as requested.
+ * AX.25 022 Jonathan(G4KLX) More work on the ax25 auto router and /proc improved (again)!
+ * Alan(GW4PTS) Added TIOCINQ/OUTQ
+ * AX.25 023 Alan(GW4PTS) Fixed shutdown bug
+ * AX.25 023 Alan(GW4PTS) Linus changed timers
+ * AX.25 024 Alan(GW4PTS) Small bug fixes
+ * AX.25 025 Alan(GW4PTS) More fixes, Linux 1.1.51 compatibility stuff, timers again!
+ * AX.25 026 Alan(GW4PTS) Small state fix.
+ * AX.25 027 Alan(GW4PTS) Socket close crash fixes.
+ * AX.25 028 Alan(GW4PTS) Callsign control including settings per uid.
+ * Small bug fixes.
+ * Protocol set by sockets only.
+ * Small changes to allow for start of NET/ROM layer.
+ * AX.25 028a Jonathan(G4KLX) Changes to state machine.
+ * AX.25 028b Jonathan(G4KLX) Extracted ax25 control block
+ * from sock structure.
+ * AX.25 029 Alan(GW4PTS) Combined 028b and some KA9Q code
+ * Jonathan(G4KLX) and removed all the old Berkeley, added IP mode registration.
+ * Darryl(G7LED) stuff. Cross-port digipeating. Minor fixes and enhancements.
+ * Alan(GW4PTS) Missed suser() on axassociate checks
+ * AX.25 030 Alan(GW4PTS) Added variable length headers.
+ * Jonathan(G4KLX) Added BPQ Ethernet interface.
+ * Steven(GW7RRM) Added digi-peating control ioctl.
+ * Added extended AX.25 support.
+ * Added AX.25 frame segmentation.
+ * Darryl(G7LED) Changed connect(), recvfrom(), sendto() sockaddr/addrlen to
+ * fall inline with bind() and new policy.
+ * Moved digipeating ctl to new ax25_dev structs.
+ * Fixed ax25_release(), set TCP_CLOSE, wakeup app
+ * context, THEN make the sock dead.
+ * Alan(GW4PTS) Cleaned up for single recvmsg methods.
+ * Alan(GW4PTS) Fixed not clearing error on connect failure.
+ * AX.25 031 Jonathan(G4KLX) Added binding to any device.
+ * Joerg(DL1BKE) Added DAMA support, fixed (?) digipeating, fixed buffer locking
+ * for "virtual connect" mode... Result: Probably the
+ * "Most Buggiest Code You've Ever Seen" (TM)
+ * HaJo(DD8NE) Implementation of a T5 (idle) timer
+ * Joerg(DL1BKE) Renamed T5 to IDLE and changed behaviour:
+ * the timer gets reloaded on every received or transmitted
+ * I frame for IP or NETROM. The idle timer is not active
+ * on "vanilla AX.25" connections. Furthermore added PACLEN
+ * to provide AX.25-layer based fragmentation (like WAMPES)
+ * AX.25 032 Joerg(DL1BKE) Fixed DAMA timeout error.
+ * ax25_send_frame() limits the number of enqueued
+ * datagrams per socket.
+ * AX.25 033 Jonathan(G4KLX) Removed auto-router.
+ * Hans(PE1AYX) Converted to Module.
+ * Joerg(DL1BKE) Moved BPQ Ethernet to separate driver.
+ * AX.25 034 Jonathan(G4KLX) 2.1 changes
+ * Alan(GW4PTS) Small POSIXisations
+ * AX.25 035 Alan(GW4PTS) Started fixing to the new
+ * format.
+ * Hans(PE1AYX) Fixed interface to IP layer.
+ * Alan(GW4PTS) Added asynchronous support.
+ * Frederic(F1OAT) Support for pseudo-digipeating.
+ * Jonathan(G4KLX) Support for packet forwarding.
+ * AX.25 036 Jonathan(G4KLX) Major restructuring.
+ * Joerg(DL1BKE) Fixed DAMA Slave.
+ * Jonathan(G4KLX) Fix wildcard listen parameter setting.
+ * AX.25 037 Jonathan(G4KLX) New timer architecture.
+ * AX.25 038 Matthias(DG2FEF) Small fixes to the syscall interface to make kernel
+ * independent of AX25_MAX_DIGIS used by applications.
+ * Tomi(OH2BNS) Fixed ax25_getname().
+ * Joerg(DL1BKE) Starting to phase out the support for full_sockaddr_ax25
+ * with only 6 digipeaters and sockaddr_ax25 in ax25_bind(),
+ * ax25_connect() and ax25_sendmsg()
+ * Joerg(DL1BKE) Added support for SO_BINDTODEVICE
+ * Arnaldo C. Melo s/suser/capable(CAP_NET_ADMIN)/, some more cleanups
+ * Michal Ostrowski Module initialization cleanup.
+ * Jeroen(PE1RXQ) Use sock_orphan() on release.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/termios.h> /* For TIOCINQ/OUTQ */
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/netfilter.h>
+#include <linux/sysctl.h>
+#include <linux/init.h>
+#include <net/ip.h>
+#include <net/arp.h>
+
+
+
+ax25_cb *volatile ax25_list;
+
+static struct proto_ops ax25_proto_ops;
+
+/*
+ * Free an allocated ax25 control block. This is done to centralise
+ * the MOD count code.
+ */
+void ax25_free_cb(ax25_cb *ax25)
+{
+ if (ax25->digipeat != NULL) {
+ kfree(ax25->digipeat);
+ ax25->digipeat = NULL;
+ }
+
+ kfree(ax25);
+
+ MOD_DEC_USE_COUNT;
+}
+
+static void ax25_free_sock(struct sock *sk)
+{
+ ax25_free_cb(sk->protinfo.ax25);
+}
+
+/*
+ * Socket removal during an interrupt is now safe.
+ */
+static void ax25_remove_socket(ax25_cb *ax25)
+{
+ ax25_cb *s;
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+ if ((s = ax25_list) == ax25) {
+ ax25_list = s->next;
+ restore_flags(flags);
+ return;
+ }
+
+ while (s != NULL && s->next != NULL) {
+ if (s->next == ax25) {
+ s->next = ax25->next;
+ restore_flags(flags);
+ return;
+ }
+
+ s = s->next;
+ }
+
+ restore_flags(flags);
+}
+
+/*
+ * Kill all bound sockets on a dropped device.
+ */
+static void ax25_kill_by_device(struct net_device *dev)
+{
+ ax25_dev *ax25_dev;
+ ax25_cb *s;
+
+ if ((ax25_dev = ax25_dev_ax25dev(dev)) == NULL)
+ return;
+
+ for (s = ax25_list; s != NULL; s = s->next) {
+ if (s->ax25_dev == ax25_dev) {
+ s->ax25_dev = NULL;
+ ax25_disconnect(s, ENETUNREACH);
+ }
+ }
+}
+
+/*
+ * Handle device status changes.
+ */
+static int ax25_device_event(struct notifier_block *this,unsigned long event, void *ptr)
+{
+ struct net_device *dev = (struct net_device *)ptr;
+
+ /* Reject non AX.25 devices */
+ if (dev->type != ARPHRD_AX25)
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case NETDEV_UP:
+ ax25_dev_device_up(dev);
+ break;
+ case NETDEV_DOWN:
+ ax25_kill_by_device(dev);
+ ax25_rt_device_down(dev);
+ ax25_dev_device_down(dev);
+ break;
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Add a socket to the bound sockets list.
+ */
+void ax25_insert_socket(ax25_cb *ax25)
+{
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ ax25->next = ax25_list;
+ ax25_list = ax25;
+
+ restore_flags(flags);
+}
+
+/*
+ * Find a socket that wants to accept the SABM we have just
+ * received.
+ */
+struct sock *ax25_find_listener(ax25_address *addr, int digi, struct net_device *dev, int type)
+{
+ unsigned long flags;
+ ax25_cb *s;
+
+ save_flags(flags);
+ cli();
+
+ for (s = ax25_list; s != NULL; s = s->next) {
+ if ((s->iamdigi && !digi) || (!s->iamdigi && digi))
+ continue;
+ if (s->sk != NULL && ax25cmp(&s->source_addr, addr) == 0 && s->sk->type == type && s->sk->state == TCP_LISTEN) {
+ /* If device is null we match any device */
+ if (s->ax25_dev == NULL || s->ax25_dev->dev == dev) {
+ restore_flags(flags);
+ return s->sk;
+ }
+ }
+ }
+
+ restore_flags(flags);
+ return NULL;
+}
+
+/*
+ * Find an AX.25 socket given both ends.
+ */
+struct sock *ax25_find_socket(ax25_address *my_addr, ax25_address *dest_addr, int type)
+{
+ ax25_cb *s;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ for (s = ax25_list; s != NULL; s = s->next) {
+ if (s->sk != NULL && ax25cmp(&s->source_addr, my_addr) == 0 && ax25cmp(&s->dest_addr, dest_addr) == 0 && s->sk->type == type) {
+ restore_flags(flags);
+ return s->sk;
+ }
+ }
+
+ restore_flags(flags);
+
+ return NULL;
+}
+
+/*
+ * Find an AX.25 control block given both ends. It will only pick up
+ * floating AX.25 control blocks or non Raw socket bound control blocks.
+ */
+ax25_cb *ax25_find_cb(ax25_address *src_addr, ax25_address *dest_addr, ax25_digi *digi, struct net_device *dev)
+{
+ ax25_cb *s;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ for (s = ax25_list; s != NULL; s = s->next) {
+ if (s->sk != NULL && s->sk->type != SOCK_SEQPACKET)
+ continue;
+ if (s->ax25_dev == NULL)
+ continue;
+ if (ax25cmp(&s->source_addr, src_addr) == 0 && ax25cmp(&s->dest_addr, dest_addr) == 0 && s->ax25_dev->dev == dev) {
+ if (digi != NULL && digi->ndigi != 0) {
+ if (s->digipeat == NULL)
+ continue;
+ if (ax25digicmp(s->digipeat, digi) != 0)
+ continue;
+ } else {
+ if (s->digipeat != NULL && s->digipeat->ndigi != 0)
+ continue;
+ }
+ restore_flags(flags);
+ return s;
+ }
+ }
+
+ restore_flags(flags);
+
+ return NULL;
+}
+
+/*
+ * Look for any matching address - RAW sockets can bind to arbitrary names
+ */
+struct sock *ax25_addr_match(ax25_address *addr)
+{
+ unsigned long flags;
+ ax25_cb *s;
+
+ save_flags(flags);
+ cli();
+
+ for (s = ax25_list; s != NULL; s = s->next) {
+ if (s->sk != NULL && ax25cmp(&s->source_addr, addr) == 0 && s->sk->type == SOCK_RAW) {
+ restore_flags(flags);
+ return s->sk;
+ }
+ }
+
+ restore_flags(flags);
+
+ return NULL;
+}
+
+void ax25_send_to_raw(struct sock *sk, struct sk_buff *skb, int proto)
+{
+ struct sk_buff *copy;
+
+ while (sk != NULL) {
+ if (sk->type == SOCK_RAW &&
+ sk->protocol == proto &&
+ atomic_read(&sk->rmem_alloc) <= sk->rcvbuf) {
+ if ((copy = skb_clone(skb, GFP_ATOMIC)) == NULL)
+ return;
+
+ if (sock_queue_rcv_skb(sk, copy) != 0)
+ kfree_skb(copy);
+ }
+
+ sk = sk->next;
+ }
+}
+
+/*
+ * Deferred destroy.
+ */
+void ax25_destroy_socket(ax25_cb *);
+
+/*
+ * Handler for deferred kills.
+ */
+static void ax25_destroy_timer(unsigned long data)
+{
+ ax25_destroy_socket((ax25_cb *)data);
+}
+
+/*
+ * This is called from user mode and the timers. Thus it protects itself against
+ * interrupt users but doesn't worry about being called during work.
+ * Once it is removed from the queue no interrupt or bottom half will
+ * touch it and we are (fairly 8-) ) safe.
+ */
+void ax25_destroy_socket(ax25_cb *ax25) /* Not static as it's used by the timer */
+{
+ struct sk_buff *skb;
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+ ax25_stop_heartbeat(ax25);
+ ax25_stop_t1timer(ax25);
+ ax25_stop_t2timer(ax25);
+ ax25_stop_t3timer(ax25);
+ ax25_stop_idletimer(ax25);
+
+ ax25_remove_socket(ax25);
+ ax25_clear_queues(ax25); /* Flush the queues */
+
+ if (ax25->sk != NULL) {
+ while ((skb = skb_dequeue(&ax25->sk->receive_queue)) != NULL) {
+ if (skb->sk != ax25->sk) { /* A pending connection */
+ skb->sk->dead = 1; /* Queue the unaccepted socket for death */
+ ax25_start_heartbeat(skb->sk->protinfo.ax25);
+ skb->sk->protinfo.ax25->state = AX25_STATE_0;
+ }
+
+ kfree_skb(skb);
+ }
+ }
+
+ if (ax25->sk != NULL) {
+ if (atomic_read(&ax25->sk->wmem_alloc) != 0 ||
+ atomic_read(&ax25->sk->rmem_alloc) != 0) {
+ /* Defer: outstanding buffers */
+ init_timer(&ax25->timer);
+ ax25->timer.expires = jiffies + 10 * HZ;
+ ax25->timer.function = ax25_destroy_timer;
+ ax25->timer.data = (unsigned long)ax25;
+ add_timer(&ax25->timer);
+ } else {
+ sk_free(ax25->sk);
+ }
+ } else {
+ ax25_free_cb(ax25);
+ }
+
+ restore_flags(flags);
+}
+
+/*
+ * dl1bke 960311: set parameters for existing AX.25 connections,
+ * includes a KILL command to abort any connection.
+ * VERY useful for debugging ;-)
+ */
+static int ax25_ctl_ioctl(const unsigned int cmd, void *arg)
+{
+ struct ax25_ctl_struct ax25_ctl;
+ ax25_digi digi;
+ ax25_dev *ax25_dev;
+ ax25_cb *ax25;
+ unsigned int k;
+
+ if (copy_from_user(&ax25_ctl, arg, sizeof(ax25_ctl)))
+ return -EFAULT;
+
+ if ((ax25_dev = ax25_addr_ax25dev(&ax25_ctl.port_addr)) == NULL)
+ return -ENODEV;
+
+ if (ax25_ctl.digi_count > AX25_MAX_DIGIS)
+ return -EINVAL;
+
+ digi.ndigi = ax25_ctl.digi_count;
+ for (k = 0; k < digi.ndigi; k++)
+ digi.calls[k] = ax25_ctl.digi_addr[k];
+
+ if ((ax25 = ax25_find_cb(&ax25_ctl.source_addr, &ax25_ctl.dest_addr, &digi, ax25_dev->dev)) == NULL)
+ return -ENOTCONN;
+
+ switch (ax25_ctl.cmd) {
+ case AX25_KILL:
+ ax25_send_control(ax25, AX25_DISC, AX25_POLLON, AX25_COMMAND);
+#ifdef CONFIG_AX25_DAMA_SLAVE
+ if (ax25_dev->dama.slave && ax25->ax25_dev->values[AX25_VALUES_PROTOCOL] == AX25_PROTO_DAMA_SLAVE)
+ ax25_dama_off(ax25);
+#endif
+ ax25_disconnect(ax25, ENETRESET);
+ break;
+
+ case AX25_WINDOW:
+ if (ax25->modulus == AX25_MODULUS) {
+ if (ax25_ctl.arg < 1 || ax25_ctl.arg > 7)
+ return -EINVAL;
+ } else {
+ if (ax25_ctl.arg < 1 || ax25_ctl.arg > 63)
+ return -EINVAL;
+ }
+ ax25->window = ax25_ctl.arg;
+ break;
+
+ case AX25_T1:
+ if (ax25_ctl.arg < 1)
+ return -EINVAL;
+ ax25->rtt = (ax25_ctl.arg * HZ) / 2;
+ ax25->t1 = ax25_ctl.arg * HZ;
+ break;
+
+ case AX25_T2:
+ if (ax25_ctl.arg < 1)
+ return -EINVAL;
+ ax25->t2 = ax25_ctl.arg * HZ;
+ break;
+
+ case AX25_N2:
+ if (ax25_ctl.arg < 1 || ax25_ctl.arg > 31)
+ return -EINVAL;
+ ax25->n2count = 0;
+ ax25->n2 = ax25_ctl.arg;
+ break;
+
+ case AX25_T3:
+ if (ax25_ctl.arg < 0)
+ return -EINVAL;
+ ax25->t3 = ax25_ctl.arg * HZ;
+ break;
+
+ case AX25_IDLE:
+ if (ax25_ctl.arg < 0)
+ return -EINVAL;
+ ax25->idle = ax25_ctl.arg * 60 * HZ;
+ break;
+
+ case AX25_PACLEN:
+ if (ax25_ctl.arg < 16 || ax25_ctl.arg > 65535)
+ return -EINVAL;
+ ax25->paclen = ax25_ctl.arg;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Fill in a created AX.25 created control block with the default
+ * values for a particular device.
+ */
+void ax25_fillin_cb(ax25_cb *ax25, ax25_dev *ax25_dev)
+{
+ ax25->ax25_dev = ax25_dev;
+
+ if (ax25->ax25_dev != NULL) {
+ ax25->rtt = ax25_dev->values[AX25_VALUES_T1] / 2;
+ ax25->t1 = ax25_dev->values[AX25_VALUES_T1];
+ ax25->t2 = ax25_dev->values[AX25_VALUES_T2];
+ ax25->t3 = ax25_dev->values[AX25_VALUES_T3];
+ ax25->n2 = ax25_dev->values[AX25_VALUES_N2];
+ ax25->paclen = ax25_dev->values[AX25_VALUES_PACLEN];
+ ax25->idle = ax25_dev->values[AX25_VALUES_IDLE];
+ ax25->backoff = ax25_dev->values[AX25_VALUES_BACKOFF];
+
+ if (ax25_dev->values[AX25_VALUES_AXDEFMODE]) {
+ ax25->modulus = AX25_EMODULUS;
+ ax25->window = ax25_dev->values[AX25_VALUES_EWINDOW];
+ } else {
+ ax25->modulus = AX25_MODULUS;
+ ax25->window = ax25_dev->values[AX25_VALUES_WINDOW];
+ }
+ } else {
+ ax25->rtt = AX25_DEF_T1 / 2;
+ ax25->t1 = AX25_DEF_T1;
+ ax25->t2 = AX25_DEF_T2;
+ ax25->t3 = AX25_DEF_T3;
+ ax25->n2 = AX25_DEF_N2;
+ ax25->paclen = AX25_DEF_PACLEN;
+ ax25->idle = AX25_DEF_IDLE;
+ ax25->backoff = AX25_DEF_BACKOFF;
+
+ if (AX25_DEF_AXDEFMODE) {
+ ax25->modulus = AX25_EMODULUS;
+ ax25->window = AX25_DEF_EWINDOW;
+ } else {
+ ax25->modulus = AX25_MODULUS;
+ ax25->window = AX25_DEF_WINDOW;
+ }
+ }
+}
+
+/*
+ * Create an empty AX.25 control block.
+ */
+ax25_cb *ax25_create_cb(void)
+{
+ ax25_cb *ax25;
+
+ if ((ax25 = kmalloc(sizeof(*ax25), GFP_ATOMIC)) == NULL)
+ return NULL;
+
+ MOD_INC_USE_COUNT;
+
+ memset(ax25, 0x00, sizeof(*ax25));
+
+ skb_queue_head_init(&ax25->write_queue);
+ skb_queue_head_init(&ax25->frag_queue);
+ skb_queue_head_init(&ax25->ack_queue);
+ skb_queue_head_init(&ax25->reseq_queue);
+
+ init_timer(&ax25->timer);
+ init_timer(&ax25->t1timer);
+ init_timer(&ax25->t2timer);
+ init_timer(&ax25->t3timer);
+ init_timer(&ax25->idletimer);
+
+ ax25_fillin_cb(ax25, NULL);
+
+ ax25->state = AX25_STATE_0;
+
+ return ax25;
+}
+
+/*
+ * Handling for system calls applied via the various interfaces to an
+ * AX25 socket object
+ */
+
+static int ax25_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen)
+{
+ struct sock *sk = sock->sk;
+ struct net_device *dev;
+ char devname[IFNAMSIZ];
+ int opt;
+
+ if (level != SOL_AX25)
+ return -ENOPROTOOPT;
+
+ if (optlen < sizeof(int))
+ return -EINVAL;
+
+ if (get_user(opt, (int *)optval))
+ return -EFAULT;
+
+ switch (optname) {
+ case AX25_WINDOW:
+ if (sk->protinfo.ax25->modulus == AX25_MODULUS) {
+ if (opt < 1 || opt > 7)
+ return -EINVAL;
+ } else {
+ if (opt < 1 || opt > 63)
+ return -EINVAL;
+ }
+ sk->protinfo.ax25->window = opt;
+ return 0;
+
+ case AX25_T1:
+ if (opt < 1)
+ return -EINVAL;
+ sk->protinfo.ax25->rtt = (opt * HZ) / 2;
+ sk->protinfo.ax25->t1 = opt * HZ;
+ return 0;
+
+ case AX25_T2:
+ if (opt < 1)
+ return -EINVAL;
+ sk->protinfo.ax25->t2 = opt * HZ;
+ return 0;
+
+ case AX25_N2:
+ if (opt < 1 || opt > 31)
+ return -EINVAL;
+ sk->protinfo.ax25->n2 = opt;
+ return 0;
+
+ case AX25_T3:
+ if (opt < 1)
+ return -EINVAL;
+ sk->protinfo.ax25->t3 = opt * HZ;
+ return 0;
+
+ case AX25_IDLE:
+ if (opt < 0)
+ return -EINVAL;
+ sk->protinfo.ax25->idle = opt * 60 * HZ;
+ return 0;
+
+ case AX25_BACKOFF:
+ if (opt < 0 || opt > 2)
+ return -EINVAL;
+ sk->protinfo.ax25->backoff = opt;
+ return 0;
+
+ case AX25_EXTSEQ:
+ sk->protinfo.ax25->modulus = opt ? AX25_EMODULUS : AX25_MODULUS;
+ return 0;
+
+ case AX25_PIDINCL:
+ sk->protinfo.ax25->pidincl = opt ? 1 : 0;
+ return 0;
+
+ case AX25_IAMDIGI:
+ sk->protinfo.ax25->iamdigi = opt ? 1 : 0;
+ return 0;
+
+ case AX25_PACLEN:
+ if (opt < 16 || opt > 65535)
+ return -EINVAL;
+ sk->protinfo.ax25->paclen = opt;
+ return 0;
+
+ case SO_BINDTODEVICE:
+ if (optlen > IFNAMSIZ) optlen=IFNAMSIZ;
+ if (copy_from_user(devname, optval, optlen))
+ return -EFAULT;
+
+ dev = dev_get_by_name(devname);
+ if (dev == NULL) return -ENODEV;
+
+ if (sk->type == SOCK_SEQPACKET &&
+ (sock->state != SS_UNCONNECTED || sk->state == TCP_LISTEN))
+ return -EADDRNOTAVAIL;
+
+ sk->protinfo.ax25->ax25_dev = ax25_dev_ax25dev(dev);
+ ax25_fillin_cb(sk->protinfo.ax25, sk->protinfo.ax25->ax25_dev);
+ return 0;
+
+ default:
+ return -ENOPROTOOPT;
+ }
+}
+
+static int ax25_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen)
+{
+ struct sock *sk = sock->sk;
+ struct ax25_dev *ax25_dev;
+ char devname[IFNAMSIZ];
+ void *valptr;
+ int val = 0;
+ int maxlen, length;
+
+ if (level != SOL_AX25)
+ return -ENOPROTOOPT;
+
+ if (get_user(maxlen, optlen))
+ return -EFAULT;
+
+ if (maxlen < 1)
+ return -EFAULT;
+
+ valptr = (void *) &val;
+ length = min_t(unsigned int, maxlen, sizeof(int));
+
+ switch (optname) {
+ case AX25_WINDOW:
+ val = sk->protinfo.ax25->window;
+ break;
+
+ case AX25_T1:
+ val = sk->protinfo.ax25->t1 / HZ;
+ break;
+
+ case AX25_T2:
+ val = sk->protinfo.ax25->t2 / HZ;
+ break;
+
+ case AX25_N2:
+ val = sk->protinfo.ax25->n2;
+ break;
+
+ case AX25_T3:
+ val = sk->protinfo.ax25->t3 / HZ;
+ break;
+
+ case AX25_IDLE:
+ val = sk->protinfo.ax25->idle / (60 * HZ);
+ break;
+
+ case AX25_BACKOFF:
+ val = sk->protinfo.ax25->backoff;
+ break;
+
+ case AX25_EXTSEQ:
+ val = (sk->protinfo.ax25->modulus == AX25_EMODULUS);
+ break;
+
+ case AX25_PIDINCL:
+ val = sk->protinfo.ax25->pidincl;
+ break;
+
+ case AX25_IAMDIGI:
+ val = sk->protinfo.ax25->iamdigi;
+ break;
+
+ case AX25_PACLEN:
+ val = sk->protinfo.ax25->paclen;
+ break;
+
+ case SO_BINDTODEVICE:
+ ax25_dev = sk->protinfo.ax25->ax25_dev;
+
+ if (ax25_dev != NULL && ax25_dev->dev != NULL) {
+ strncpy(devname, ax25_dev->dev->name, IFNAMSIZ);
+ length = min_t(unsigned int, strlen(ax25_dev->dev->name)+1, maxlen);
+ devname[length-1] = '\0';
+ } else {
+ *devname = '\0';
+ length = 1;
+ }
+
+ valptr = (void *) devname;
+ break;
+
+ default:
+ return -ENOPROTOOPT;
+ }
+
+ if (put_user(length, optlen))
+ return -EFAULT;
+
+ return copy_to_user(optval, valptr, length) ? -EFAULT : 0;
+}
+
+static int ax25_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk->type == SOCK_SEQPACKET && sk->state != TCP_LISTEN) {
+ sk->max_ack_backlog = backlog;
+ sk->state = TCP_LISTEN;
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+int ax25_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+ ax25_cb *ax25;
+
+ switch (sock->type) {
+ case SOCK_DGRAM:
+ if (protocol == 0 || protocol == PF_AX25)
+ protocol = AX25_P_TEXT;
+ break;
+ case SOCK_SEQPACKET:
+ switch (protocol) {
+ case 0:
+ case PF_AX25: /* For CLX */
+ protocol = AX25_P_TEXT;
+ break;
+ case AX25_P_SEGMENT:
+#ifdef CONFIG_INET
+ case AX25_P_ARP:
+ case AX25_P_IP:
+#endif
+#ifdef CONFIG_NETROM
+ case AX25_P_NETROM:
+#endif
+#ifdef CONFIG_ROSE
+ case AX25_P_ROSE:
+#endif
+ return -ESOCKTNOSUPPORT;
+#ifdef CONFIG_NETROM_MODULE
+ case AX25_P_NETROM:
+ if (ax25_protocol_is_registered(AX25_P_NETROM))
+ return -ESOCKTNOSUPPORT;
+#endif
+#ifdef CONFIG_ROSE_MODULE
+ case AX25_P_ROSE:
+ if (ax25_protocol_is_registered(AX25_P_ROSE))
+ return -ESOCKTNOSUPPORT;
+#endif
+ default:
+ break;
+ }
+ break;
+ case SOCK_RAW:
+ break;
+ default:
+ return -ESOCKTNOSUPPORT;
+ }
+
+ if ((sk = sk_alloc(PF_AX25, GFP_ATOMIC, 1)) == NULL)
+ return -ENOMEM;
+
+ if ((ax25 = ax25_create_cb()) == NULL) {
+ sk_free(sk);
+ return -ENOMEM;
+ }
+
+ sock_init_data(sock, sk);
+
+ sk->destruct = ax25_free_sock;
+ sock->ops = &ax25_proto_ops;
+ sk->protocol = protocol;
+
+ ax25->sk = sk;
+ sk->protinfo.ax25 = ax25;
+
+ return 0;
+}
+
+struct sock *ax25_make_new(struct sock *osk, struct ax25_dev *ax25_dev)
+{
+ struct sock *sk;
+ ax25_cb *ax25;
+
+ if ((sk = sk_alloc(PF_AX25, GFP_ATOMIC, 1)) == NULL)
+ return NULL;
+
+ if ((ax25 = ax25_create_cb()) == NULL) {
+ sk_free(sk);
+ return NULL;
+ }
+
+ switch (osk->type) {
+ case SOCK_DGRAM:
+ break;
+ case SOCK_SEQPACKET:
+ break;
+ default:
+ sk_free(sk);
+ ax25_free_cb(ax25);
+ return NULL;
+ }
+
+ sock_init_data(NULL, sk);
+
+ sk->destruct = ax25_free_sock;
+ sk->type = osk->type;
+ sk->socket = osk->socket;
+ sk->priority = osk->priority;
+ sk->protocol = osk->protocol;
+ sk->rcvbuf = osk->rcvbuf;
+ sk->sndbuf = osk->sndbuf;
+ sk->debug = osk->debug;
+ sk->state = TCP_ESTABLISHED;
+ sk->sleep = osk->sleep;
+ sk->zapped = osk->zapped;
+
+ ax25->modulus = osk->protinfo.ax25->modulus;
+ ax25->backoff = osk->protinfo.ax25->backoff;
+ ax25->pidincl = osk->protinfo.ax25->pidincl;
+ ax25->iamdigi = osk->protinfo.ax25->iamdigi;
+ ax25->rtt = osk->protinfo.ax25->rtt;
+ ax25->t1 = osk->protinfo.ax25->t1;
+ ax25->t2 = osk->protinfo.ax25->t2;
+ ax25->t3 = osk->protinfo.ax25->t3;
+ ax25->n2 = osk->protinfo.ax25->n2;
+ ax25->idle = osk->protinfo.ax25->idle;
+ ax25->paclen = osk->protinfo.ax25->paclen;
+ ax25->window = osk->protinfo.ax25->window;
+
+ ax25->ax25_dev = ax25_dev;
+ ax25->source_addr = osk->protinfo.ax25->source_addr;
+
+ if (osk->protinfo.ax25->digipeat != NULL) {
+ if ((ax25->digipeat = kmalloc(sizeof(ax25_digi), GFP_ATOMIC)) == NULL) {
+ sk_free(sk);
+ return NULL;
+ }
+
+ memcpy(ax25->digipeat, osk->protinfo.ax25->digipeat, sizeof(ax25_digi));
+ }
+
+ sk->protinfo.ax25 = ax25;
+ ax25->sk = sk;
+
+ return sk;
+}
+
+static int ax25_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk == NULL) return 0;
+
+ if (sk->type == SOCK_SEQPACKET) {
+ switch (sk->protinfo.ax25->state) {
+ case AX25_STATE_0:
+ ax25_disconnect(sk->protinfo.ax25, 0);
+ ax25_destroy_socket(sk->protinfo.ax25);
+ break;
+
+ case AX25_STATE_1:
+ case AX25_STATE_2:
+ ax25_send_control(sk->protinfo.ax25, AX25_DISC, AX25_POLLON, AX25_COMMAND);
+ ax25_disconnect(sk->protinfo.ax25, 0);
+ ax25_destroy_socket(sk->protinfo.ax25);
+ break;
+
+ case AX25_STATE_3:
+ case AX25_STATE_4:
+ ax25_clear_queues(sk->protinfo.ax25);
+ sk->protinfo.ax25->n2count = 0;
+ switch (sk->protinfo.ax25->ax25_dev->values[AX25_VALUES_PROTOCOL]) {
+ case AX25_PROTO_STD_SIMPLEX:
+ case AX25_PROTO_STD_DUPLEX:
+ ax25_send_control(sk->protinfo.ax25, AX25_DISC, AX25_POLLON, AX25_COMMAND);
+ ax25_stop_t2timer(sk->protinfo.ax25);
+ ax25_stop_t3timer(sk->protinfo.ax25);
+ ax25_stop_idletimer(sk->protinfo.ax25);
+ break;
+#ifdef CONFIG_AX25_DAMA_SLAVE
+ case AX25_PROTO_DAMA_SLAVE:
+ ax25_stop_t3timer(sk->protinfo.ax25);
+ ax25_stop_idletimer(sk->protinfo.ax25);
+ break;
+#endif
+ }
+ ax25_calculate_t1(sk->protinfo.ax25);
+ ax25_start_t1timer(sk->protinfo.ax25);
+ sk->protinfo.ax25->state = AX25_STATE_2;
+ sk->state = TCP_CLOSE;
+ sk->shutdown |= SEND_SHUTDOWN;
+ sk->state_change(sk);
+ sock_orphan(sk);
+ sk->destroy = 1;
+ break;
+
+ default:
+ break;
+ }
+ } else {
+ sk->state = TCP_CLOSE;
+ sk->shutdown |= SEND_SHUTDOWN;
+ sk->state_change(sk);
+ sock_orphan(sk);
+ ax25_destroy_socket(sk->protinfo.ax25);
+ }
+
+ sock->sk = NULL;
+ sk->socket = NULL; /* Not used, but we should do this */
+
+ return 0;
+}
+
+/*
+ * We support a funny extension here so you can (as root) give any callsign
+ * digipeated via a local address as source. This hack is obsolete now
+ * that we've implemented support for SO_BINDTODEVICE. It is however small
+ * and trivially backward compatible.
+ */
+static int ax25_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sock *sk = sock->sk;
+ struct full_sockaddr_ax25 *addr = (struct full_sockaddr_ax25 *)uaddr;
+ ax25_address *call;
+ ax25_dev *ax25_dev = NULL;
+
+ if (sk->zapped == 0)
+ return -EINVAL;
+
+ if (addr_len != sizeof(struct sockaddr_ax25) &&
+ addr_len != sizeof(struct full_sockaddr_ax25)) {
+ /* support for old structure may go away some time */
+ if ((addr_len < sizeof(struct sockaddr_ax25) + sizeof(ax25_address) * 6) ||
+ (addr_len > sizeof(struct full_sockaddr_ax25)))
+ return -EINVAL;
+
+ printk(KERN_WARNING "ax25_bind(): %s uses old (6 digipeater) socket structure.\n",
+ current->comm);
+ }
+
+ if (addr->fsa_ax25.sax25_family != AF_AX25)
+ return -EINVAL;
+
+ call = ax25_findbyuid(current->euid);
+ if (call == NULL && ax25_uid_policy && !capable(CAP_NET_ADMIN))
+ return -EACCES;
+
+ if (call == NULL)
+ sk->protinfo.ax25->source_addr = addr->fsa_ax25.sax25_call;
+ else
+ sk->protinfo.ax25->source_addr = *call;
+
+ /*
+ * User already set interface with SO_BINDTODEVICE
+ */
+
+ if (sk->protinfo.ax25->ax25_dev != NULL)
+ goto done;
+
+ if (addr_len > sizeof(struct sockaddr_ax25) && addr->fsa_ax25.sax25_ndigis == 1) {
+ if (ax25cmp(&addr->fsa_digipeater[0], &null_ax25_address) != 0 &&
+ (ax25_dev = ax25_addr_ax25dev(&addr->fsa_digipeater[0])) == NULL)
+ return -EADDRNOTAVAIL;
+ } else {
+ if ((ax25_dev = ax25_addr_ax25dev(&addr->fsa_ax25.sax25_call)) == NULL)
+ return -EADDRNOTAVAIL;
+ }
+
+ if (ax25_dev != NULL)
+ ax25_fillin_cb(sk->protinfo.ax25, ax25_dev);
+
+done:
+ ax25_insert_socket(sk->protinfo.ax25);
+ sk->zapped = 0;
+ return 0;
+}
+
+/*
+ * FIXME: nonblock behaviour looks like it may have a bug.
+ */
+static int ax25_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct full_sockaddr_ax25 *fsa = (struct full_sockaddr_ax25 *)uaddr;
+ ax25_digi *digi = NULL;
+ int ct = 0, err;
+
+ /* deal with restarts */
+ if (sock->state == SS_CONNECTING) {
+ switch (sk->state) {
+ case TCP_SYN_SENT: /* still trying */
+ return -EINPROGRESS;
+
+ case TCP_ESTABLISHED: /* connection established */
+ sock->state = SS_CONNECTED;
+ return 0;
+
+ case TCP_CLOSE: /* connection refused */
+ sock->state = SS_UNCONNECTED;
+ return -ECONNREFUSED;
+ }
+ }
+
+ if (sk->state == TCP_ESTABLISHED && sk->type == SOCK_SEQPACKET)
+ return -EISCONN; /* No reconnect on a seqpacket socket */
+
+ sk->state = TCP_CLOSE;
+ sock->state = SS_UNCONNECTED;
+
+ /*
+ * some sanity checks. code further down depends on this
+ */
+
+ if (addr_len == sizeof(struct sockaddr_ax25)) {
+ /* support for this will go away in early 2.5.x */
+ printk(KERN_WARNING "ax25_connect(): %s uses obsolete socket structure\n",
+ current->comm);
+ }
+ else if (addr_len != sizeof(struct full_sockaddr_ax25)) {
+ /* support for old structure may go away some time */
+ if ((addr_len < sizeof(struct sockaddr_ax25) + sizeof(ax25_address) * 6) ||
+ (addr_len > sizeof(struct full_sockaddr_ax25)))
+ return -EINVAL;
+
+ printk(KERN_WARNING "ax25_connect(): %s uses old (6 digipeater) socket structure.\n",
+ current->comm);
+ }
+
+ if (fsa->fsa_ax25.sax25_family != AF_AX25)
+ return -EINVAL;
+
+ if (sk->protinfo.ax25->digipeat != NULL) {
+ kfree(sk->protinfo.ax25->digipeat);
+ sk->protinfo.ax25->digipeat = NULL;
+ }
+
+ /*
+ * Handle digi-peaters to be used.
+ */
+ if (addr_len > sizeof(struct sockaddr_ax25) && fsa->fsa_ax25.sax25_ndigis != 0) {
+ /* Valid number of digipeaters ? */
+ if (fsa->fsa_ax25.sax25_ndigis < 1 || fsa->fsa_ax25.sax25_ndigis > AX25_MAX_DIGIS)
+ return -EINVAL;
+
+ if ((digi = kmalloc(sizeof(ax25_digi), GFP_KERNEL)) == NULL)
+ return -ENOBUFS;
+
+ digi->ndigi = fsa->fsa_ax25.sax25_ndigis;
+ digi->lastrepeat = -1;
+
+ while (ct < fsa->fsa_ax25.sax25_ndigis) {
+ if ((fsa->fsa_digipeater[ct].ax25_call[6] & AX25_HBIT) && sk->protinfo.ax25->iamdigi) {
+ digi->repeated[ct] = 1;
+ digi->lastrepeat = ct;
+ } else {
+ digi->repeated[ct] = 0;
+ }
+ digi->calls[ct] = fsa->fsa_digipeater[ct];
+ ct++;
+ }
+ }
+
+ /*
+ * Must bind first - autobinding in this may or may not work. If
+ * the socket is already bound, check to see if the device has
+ * been filled in, error if it hasn't.
+ */
+ if (sk->zapped) {
+ /* check if we can remove this feature. It is broken. */
+ printk(KERN_WARNING "ax25_connect(): %s uses autobind, please contact jreuter@yaina.de\n",
+ current->comm);
+ if ((err = ax25_rt_autobind(sk->protinfo.ax25, &fsa->fsa_ax25.sax25_call)) < 0)
+ return err;
+ ax25_fillin_cb(sk->protinfo.ax25, sk->protinfo.ax25->ax25_dev);
+ ax25_insert_socket(sk->protinfo.ax25);
+ } else {
+ if (sk->protinfo.ax25->ax25_dev == NULL)
+ return -EHOSTUNREACH;
+ }
+
+ if (sk->type == SOCK_SEQPACKET && ax25_find_cb(&sk->protinfo.ax25->source_addr, &fsa->fsa_ax25.sax25_call, digi, sk->protinfo.ax25->ax25_dev->dev) != NULL) {
+ if (digi != NULL) kfree(digi);
+ return -EADDRINUSE; /* Already such a connection */
+ }
+
+ sk->protinfo.ax25->dest_addr = fsa->fsa_ax25.sax25_call;
+ sk->protinfo.ax25->digipeat = digi;
+
+ /* First the easy one */
+ if (sk->type != SOCK_SEQPACKET) {
+ sock->state = SS_CONNECTED;
+ sk->state = TCP_ESTABLISHED;
+ return 0;
+ }
+
+ /* Move to connecting socket, ax.25 lapb WAIT_UA.. */
+ sock->state = SS_CONNECTING;
+ sk->state = TCP_SYN_SENT;
+
+ switch (sk->protinfo.ax25->ax25_dev->values[AX25_VALUES_PROTOCOL]) {
+ case AX25_PROTO_STD_SIMPLEX:
+ case AX25_PROTO_STD_DUPLEX:
+ ax25_std_establish_data_link(sk->protinfo.ax25);
+ break;
+
+#ifdef CONFIG_AX25_DAMA_SLAVE
+ case AX25_PROTO_DAMA_SLAVE:
+ sk->protinfo.ax25->modulus = AX25_MODULUS;
+ sk->protinfo.ax25->window = sk->protinfo.ax25->ax25_dev->values[AX25_VALUES_WINDOW];
+ if (sk->protinfo.ax25->ax25_dev->dama.slave)
+ ax25_ds_establish_data_link(sk->protinfo.ax25);
+ else
+ ax25_std_establish_data_link(sk->protinfo.ax25);
+ break;
+#endif
+ }
+
+ sk->protinfo.ax25->state = AX25_STATE_1;
+
+ ax25_start_heartbeat(sk->protinfo.ax25);
+
+ /* Now the loop */
+ if (sk->state != TCP_ESTABLISHED && (flags & O_NONBLOCK))
+ return -EINPROGRESS;
+
+ cli(); /* To avoid races on the sleep */
+
+ /* A DM or timeout will go to closed, a UA will go to ABM */
+ while (sk->state == TCP_SYN_SENT) {
+ interruptible_sleep_on(sk->sleep);
+ if (signal_pending(current)) {
+ sti();
+ return -ERESTARTSYS;
+ }
+ }
+
+ if (sk->state != TCP_ESTABLISHED) {
+ /* Not in ABM, not in WAIT_UA -> failed */
+ sti();
+ sock->state = SS_UNCONNECTED;
+ return sock_error(sk); /* Always set at this point */
+ }
+
+ sock->state = SS_CONNECTED;
+
+ sti();
+
+ return 0;
+}
+
+
+static int ax25_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+ struct sock *sk;
+ struct sock *newsk;
+ struct sk_buff *skb;
+
+ if (sock->state != SS_UNCONNECTED)
+ return -EINVAL;
+
+ if ((sk = sock->sk) == NULL)
+ return -EINVAL;
+
+ if (sk->type != SOCK_SEQPACKET)
+ return -EOPNOTSUPP;
+
+ if (sk->state != TCP_LISTEN)
+ return -EINVAL;
+
+ /*
+ * The read queue this time is holding sockets ready to use
+ * hooked into the SABM we saved
+ */
+ do {
+ if ((skb = skb_dequeue(&sk->receive_queue)) == NULL) {
+ if (flags & O_NONBLOCK)
+ return -EWOULDBLOCK;
+
+ interruptible_sleep_on(sk->sleep);
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ }
+ } while (skb == NULL);
+
+ newsk = skb->sk;
+ newsk->pair = NULL;
+ newsk->socket = newsock;
+ newsk->sleep = &newsock->wait;
+
+ /* Now attach up the new socket */
+ kfree_skb(skb);
+ sk->ack_backlog--;
+ newsock->sk = newsk;
+ newsock->state = SS_CONNECTED;
+
+ return 0;
+}
+
+static int ax25_getname(struct socket *sock, struct sockaddr *uaddr, int *uaddr_len, int peer)
+{
+ struct sock *sk = sock->sk;
+ struct full_sockaddr_ax25 *fsa = (struct full_sockaddr_ax25 *)uaddr;
+ unsigned char ndigi, i;
+
+ if (peer != 0) {
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+
+ fsa->fsa_ax25.sax25_family = AF_AX25;
+ fsa->fsa_ax25.sax25_call = sk->protinfo.ax25->dest_addr;
+ fsa->fsa_ax25.sax25_ndigis = 0;
+
+ if (sk->protinfo.ax25->digipeat != NULL) {
+ ndigi = sk->protinfo.ax25->digipeat->ndigi;
+ fsa->fsa_ax25.sax25_ndigis = ndigi;
+ for (i = 0; i < ndigi; i++)
+ fsa->fsa_digipeater[i] = sk->protinfo.ax25->digipeat->calls[i];
+ }
+ } else {
+ fsa->fsa_ax25.sax25_family = AF_AX25;
+ fsa->fsa_ax25.sax25_call = sk->protinfo.ax25->source_addr;
+ fsa->fsa_ax25.sax25_ndigis = 1;
+ if (sk->protinfo.ax25->ax25_dev != NULL) {
+ memcpy(&fsa->fsa_digipeater[0], sk->protinfo.ax25->ax25_dev->dev->dev_addr, AX25_ADDR_LEN);
+ } else {
+ fsa->fsa_digipeater[0] = null_ax25_address;
+ }
+ }
+ *uaddr_len = sizeof (struct full_sockaddr_ax25);
+ return 0;
+}
+
+static int ax25_sendmsg(struct socket *sock, struct msghdr *msg, int len, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_ax25 *usax = (struct sockaddr_ax25 *)msg->msg_name;
+ int err;
+ struct sockaddr_ax25 sax;
+ struct sk_buff *skb;
+ unsigned char *asmptr;
+ int size;
+ ax25_digi *dp;
+ ax25_digi dtmp;
+ int lv;
+ int addr_len = msg->msg_namelen;
+
+ if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_EOR))
+ return -EINVAL;
+
+ if (sk->zapped)
+ return -EADDRNOTAVAIL;
+
+ if (sk->shutdown & SEND_SHUTDOWN) {
+ send_sig(SIGPIPE, current, 0);
+ return -EPIPE;
+ }
+
+ if (sk->protinfo.ax25->ax25_dev == NULL)
+ return -ENETUNREACH;
+
+ if (usax != NULL) {
+ if (usax->sax25_family != AF_AX25)
+ return -EINVAL;
+
+ if (addr_len == sizeof(struct sockaddr_ax25)) {
+ printk(KERN_WARNING "ax25_sendmsg(): %s uses obsolete socket structure\n",
+ current->comm);
+ }
+ else if (addr_len != sizeof(struct full_sockaddr_ax25)) {
+ /* support for old structure may go away some time */
+ if ((addr_len < sizeof(struct sockaddr_ax25) + sizeof(ax25_address) * 6) ||
+ (addr_len > sizeof(struct full_sockaddr_ax25)))
+ return -EINVAL;
+
+ printk(KERN_WARNING "ax25_sendmsg(): %s uses old (6 digipeater) socket structure.\n",
+ current->comm);
+ }
+
+ if (addr_len > sizeof(struct sockaddr_ax25) && usax->sax25_ndigis != 0) {
+ int ct = 0;
+ struct full_sockaddr_ax25 *fsa = (struct full_sockaddr_ax25 *)usax;
+
+ /* Valid number of digipeaters ? */
+ if (usax->sax25_ndigis < 1 || usax->sax25_ndigis > AX25_MAX_DIGIS)
+ return -EINVAL;
+
+ dtmp.ndigi = usax->sax25_ndigis;
+
+ while (ct < usax->sax25_ndigis) {
+ dtmp.repeated[ct] = 0;
+ dtmp.calls[ct] = fsa->fsa_digipeater[ct];
+ ct++;
+ }
+
+ dtmp.lastrepeat = 0;
+ }
+
+ sax = *usax;
+ if (sk->type == SOCK_SEQPACKET && ax25cmp(&sk->protinfo.ax25->dest_addr, &sax.sax25_call) != 0)
+ return -EISCONN;
+ if (usax->sax25_ndigis == 0)
+ dp = NULL;
+ else
+ dp = &dtmp;
+ } else {
+ /*
+ * FIXME: 1003.1g - if the socket is like this because
+ * it has become closed (not started closed) and is VC
+ * we ought to SIGPIPE, EPIPE
+ */
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+ sax.sax25_family = AF_AX25;
+ sax.sax25_call = sk->protinfo.ax25->dest_addr;
+ dp = sk->protinfo.ax25->digipeat;
+ }
+
+ SOCK_DEBUG(sk, "AX.25: sendto: Addresses built.\n");
+
+ /* Build a packet */
+ SOCK_DEBUG(sk, "AX.25: sendto: building packet.\n");
+
+ /* Assume the worst case */
+ size = len + 3 + ax25_addr_size(dp) + AX25_BPQ_HEADER_LEN;
+
+ if ((skb = sock_alloc_send_skb(sk, size, msg->msg_flags & MSG_DONTWAIT, &err)) == NULL)
+ return err;
+
+ skb_reserve(skb, size - len);
+
+ SOCK_DEBUG(sk, "AX.25: Appending user data\n");
+
+ /* User data follows immediately after the AX.25 data */
+ memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
+ skb->nh.raw = skb->data;
+
+ /* Add the PID if one is not supplied by the user in the skb */
+ if (!sk->protinfo.ax25->pidincl) {
+ asmptr = skb_push(skb, 1);
+ *asmptr = sk->protocol;
+ }
+
+ SOCK_DEBUG(sk, "AX.25: Transmitting buffer\n");
+
+ if (sk->type == SOCK_SEQPACKET) {
+ /* Connected mode sockets go via the LAPB machine */
+ if (sk->state != TCP_ESTABLISHED) {
+ kfree_skb(skb);
+ return -ENOTCONN;
+ }
+
+ ax25_output(sk->protinfo.ax25, sk->protinfo.ax25->paclen, skb); /* Shove it onto the queue and kick */
+
+ return len;
+ } else {
+ asmptr = skb_push(skb, 1 + ax25_addr_size(dp));
+
+ SOCK_DEBUG(sk, "Building AX.25 Header (dp=%p).\n", dp);
+
+ if (dp != NULL)
+ SOCK_DEBUG(sk, "Num digipeaters=%d\n", dp->ndigi);
+
+ /* Build an AX.25 header */
+ asmptr += (lv = ax25_addr_build(asmptr, &sk->protinfo.ax25->source_addr, &sax.sax25_call, dp, AX25_COMMAND, AX25_MODULUS));
+
+ SOCK_DEBUG(sk, "Built header (%d bytes)\n",lv);
+
+ skb->h.raw = asmptr;
+
+ SOCK_DEBUG(sk, "base=%p pos=%p\n", skb->data, asmptr);
+
+ *asmptr = AX25_UI;
+
+ /* Datagram frames go straight out of the door as UI */
+ skb->dev = sk->protinfo.ax25->ax25_dev->dev;
+
+ ax25_queue_xmit(skb);
+
+ return len;
+ }
+}
+
+static int ax25_recvmsg(struct socket *sock, struct msghdr *msg, int size, int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ int copied;
+ struct sk_buff *skb;
+ int er;
+
+ /*
+ * This works for seqpacket too. The receiver has ordered the
+ * queue for us! We do one quick check first though
+ */
+ if (sk->type == SOCK_SEQPACKET && sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+
+ /* Now we can treat all alike */
+ if ((skb = skb_recv_datagram(sk, flags & ~MSG_DONTWAIT, flags & MSG_DONTWAIT, &er)) == NULL)
+ return er;
+
+ if (!sk->protinfo.ax25->pidincl)
+ skb_pull(skb, 1); /* Remove PID */
+
+ skb->h.raw = skb->data;
+ copied = skb->len;
+
+ if (copied > size) {
+ copied = size;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+
+ skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+
+ if (msg->msg_namelen != 0) {
+ struct sockaddr_ax25 *sax = (struct sockaddr_ax25 *)msg->msg_name;
+ ax25_digi digi;
+ ax25_address dest;
+
+ ax25_addr_parse(skb->mac.raw+1, skb->data-skb->mac.raw-1, NULL, &dest, &digi, NULL, NULL);
+
+ sax->sax25_family = AF_AX25;
+ /* We set this correctly, even though we may not let the
+ application know the digi calls further down (because it
+ did NOT ask to know them). This could get political... **/
+ sax->sax25_ndigis = digi.ndigi;
+ sax->sax25_call = dest;
+
+ if (sax->sax25_ndigis != 0) {
+ int ct;
+ struct full_sockaddr_ax25 *fsa = (struct full_sockaddr_ax25 *)sax;
+
+ for (ct = 0; ct < digi.ndigi; ct++)
+ fsa->fsa_digipeater[ct] = digi.calls[ct];
+ }
+ msg->msg_namelen = sizeof(struct full_sockaddr_ax25);
+ }
+
+ skb_free_datagram(sk, skb);
+
+ return copied;
+}
+
+static int ax25_shutdown(struct socket *sk, int how)
+{
+ /* FIXME - generate DM and RNR states */
+ return -EOPNOTSUPP;
+}
+
+static int ax25_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+
+ switch (cmd) {
+ case TIOCOUTQ: {
+ long amount;
+ amount = sk->sndbuf - atomic_read(&sk->wmem_alloc);
+ if (amount < 0)
+ amount = 0;
+ return put_user(amount, (int *)arg);
+ }
+
+ case TIOCINQ: {
+ struct sk_buff *skb;
+ long amount = 0L;
+ /* These two are safe on a single CPU system as only user tasks fiddle here */
+ if ((skb = skb_peek(&sk->receive_queue)) != NULL)
+ amount = skb->len;
+ return put_user(amount, (int *)arg);
+ }
+
+ case SIOCGSTAMP:
+ if (sk != NULL) {
+ if (sk->stamp.tv_sec == 0)
+ return -ENOENT;
+ return copy_to_user((void *)arg, &sk->stamp, sizeof(struct timeval)) ? -EFAULT : 0;
+ }
+ return -EINVAL;
+
+ case SIOCAX25ADDUID: /* Add a uid to the uid/call map table */
+ case SIOCAX25DELUID: /* Delete a uid from the uid/call map table */
+ case SIOCAX25GETUID: {
+ struct sockaddr_ax25 sax25;
+ if (copy_from_user(&sax25, (void *)arg, sizeof(sax25)))
+ return -EFAULT;
+ return ax25_uid_ioctl(cmd, &sax25);
+ }
+
+ case SIOCAX25NOUID: { /* Set the default policy (default/bar) */
+ long amount;
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (get_user(amount, (long *)arg))
+ return -EFAULT;
+ if (amount > AX25_NOUID_BLOCK)
+ return -EINVAL;
+ ax25_uid_policy = amount;
+ return 0;
+ }
+
+ case SIOCADDRT:
+ case SIOCDELRT:
+ case SIOCAX25OPTRT:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ return ax25_rt_ioctl(cmd, (void *)arg);
+
+ case SIOCAX25CTLCON:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ return ax25_ctl_ioctl(cmd, (void *)arg);
+
+ case SIOCAX25GETINFO:
+ case SIOCAX25GETINFOOLD: {
+ struct ax25_info_struct ax25_info;
+
+ ax25_info.t1 = sk->protinfo.ax25->t1 / HZ;
+ ax25_info.t2 = sk->protinfo.ax25->t2 / HZ;
+ ax25_info.t3 = sk->protinfo.ax25->t3 / HZ;
+ ax25_info.idle = sk->protinfo.ax25->idle / (60 * HZ);
+ ax25_info.n2 = sk->protinfo.ax25->n2;
+ ax25_info.t1timer = ax25_display_timer(&sk->protinfo.ax25->t1timer) / HZ;
+ ax25_info.t2timer = ax25_display_timer(&sk->protinfo.ax25->t2timer) / HZ;
+ ax25_info.t3timer = ax25_display_timer(&sk->protinfo.ax25->t3timer) / HZ;
+ ax25_info.idletimer = ax25_display_timer(&sk->protinfo.ax25->idletimer) / (60 * HZ);
+ ax25_info.n2count = sk->protinfo.ax25->n2count;
+ ax25_info.state = sk->protinfo.ax25->state;
+ ax25_info.rcv_q = atomic_read(&sk->rmem_alloc);
+ ax25_info.snd_q = atomic_read(&sk->wmem_alloc);
+ ax25_info.vs = sk->protinfo.ax25->vs;
+ ax25_info.vr = sk->protinfo.ax25->vr;
+ ax25_info.va = sk->protinfo.ax25->va;
+ ax25_info.vs_max = sk->protinfo.ax25->vs; /* reserved */
+ ax25_info.paclen = sk->protinfo.ax25->paclen;
+ ax25_info.window = sk->protinfo.ax25->window;
+
+ /* old structure? */
+ if (cmd == SIOCAX25GETINFOOLD) {
+ static int warned = 0;
+ if (!warned) {
+ printk(KERN_INFO "%s uses old SIOCAX25GETINFO\n",
+ current->comm);
+ warned=1;
+ }
+
+ if (copy_to_user((void *)arg, &ax25_info, sizeof(struct ax25_info_struct_depreciated)))
+ return -EFAULT;
+ } else {
+ if (copy_to_user((void *)arg, &ax25_info, sizeof(struct ax25_info_struct)))
+ return -EINVAL;
+ }
+ return 0;
+ }
+
+ case SIOCAX25ADDFWD:
+ case SIOCAX25DELFWD: {
+ struct ax25_fwd_struct ax25_fwd;
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (copy_from_user(&ax25_fwd, (void *)arg, sizeof(ax25_fwd)))
+ return -EFAULT;
+ return ax25_fwd_ioctl(cmd, &ax25_fwd);
+ }
+
+ case SIOCGIFADDR:
+ case SIOCSIFADDR:
+ case SIOCGIFDSTADDR:
+ case SIOCSIFDSTADDR:
+ case SIOCGIFBRDADDR:
+ case SIOCSIFBRDADDR:
+ case SIOCGIFNETMASK:
+ case SIOCSIFNETMASK:
+ case SIOCGIFMETRIC:
+ case SIOCSIFMETRIC:
+ return -EINVAL;
+
+ default:
+ return dev_ioctl(cmd, (void *)arg);
+ }
+
+ /*NOTREACHED*/
+ return 0;
+}
+
+static int ax25_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ ax25_cb *ax25;
+ int k;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+
+ cli();
+
+ /*
+ * New format:
+ * magic dev src_addr dest_addr,digi1,digi2,.. st vs vr va t1 t1 t2 t2 t3 t3 idle idle n2 n2 rtt window paclen Snd-Q Rcv-Q inode
+ */
+
+ for (ax25 = ax25_list; ax25 != NULL; ax25 = ax25->next) {
+ len += sprintf(buffer+len, "%8.8lx %s %s%s ",
+ (long) ax25,
+ ax25->ax25_dev == NULL? "???" : ax25->ax25_dev->dev->name,
+ ax2asc(&ax25->source_addr),
+ ax25->iamdigi? "*":"");
+
+ len += sprintf(buffer+len, "%s", ax2asc(&ax25->dest_addr));
+
+ for (k=0; (ax25->digipeat != NULL) && (k < ax25->digipeat->ndigi); k++) {
+ len += sprintf(buffer+len, ",%s%s",
+ ax2asc(&ax25->digipeat->calls[k]),
+ ax25->digipeat->repeated[k]? "*":"");
+ }
+
+ len += sprintf(buffer+len, " %d %d %d %d %lu %lu %lu %lu %lu %lu %lu %lu %d %d %lu %d %d",
+ ax25->state,
+ ax25->vs, ax25->vr, ax25->va,
+ ax25_display_timer(&ax25->t1timer) / HZ, ax25->t1 / HZ,
+ ax25_display_timer(&ax25->t2timer) / HZ, ax25->t2 / HZ,
+ ax25_display_timer(&ax25->t3timer) / HZ, ax25->t3 / HZ,
+ ax25_display_timer(&ax25->idletimer) / (60 * HZ),
+ ax25->idle / (60 * HZ),
+ ax25->n2count, ax25->n2,
+ ax25->rtt / HZ,
+ ax25->window,
+ ax25->paclen);
+
+ if (ax25->sk != NULL) {
+ len += sprintf(buffer + len, " %d %d %ld\n",
+ atomic_read(&ax25->sk->wmem_alloc),
+ atomic_read(&ax25->sk->rmem_alloc),
+ ax25->sk->socket != NULL ? ax25->sk->socket->inode->i_ino : 0L);
+ } else {
+ len += sprintf(buffer + len, " * * *\n");
+ }
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ break;
+ }
+
+ sti();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return(len);
+}
+
+static struct net_proto_family ax25_family_ops = {
+ family: PF_AX25,
+ create: ax25_create,
+};
+
+static struct proto_ops SOCKOPS_WRAPPED(ax25_proto_ops) = {
+ family: PF_AX25,
+
+ release: ax25_release,
+ bind: ax25_bind,
+ connect: ax25_connect,
+ socketpair: sock_no_socketpair,
+ accept: ax25_accept,
+ getname: ax25_getname,
+ poll: datagram_poll,
+ ioctl: ax25_ioctl,
+ listen: ax25_listen,
+ shutdown: ax25_shutdown,
+ setsockopt: ax25_setsockopt,
+ getsockopt: ax25_getsockopt,
+ sendmsg: ax25_sendmsg,
+ recvmsg: ax25_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+#include <linux/smp_lock.h>
+SOCKOPS_WRAP(ax25_proto, PF_AX25);
+
+/*
+ * Called by socket.c on kernel start up
+ */
+static struct packet_type ax25_packet_type = {
+ type: __constant_htons(ETH_P_AX25),
+ func: ax25_kiss_rcv,
+};
+
+static struct notifier_block ax25_dev_notifier = {
+ notifier_call: ax25_device_event,
+};
+
+EXPORT_SYMBOL(ax25_encapsulate);
+EXPORT_SYMBOL(ax25_rebuild_header);
+EXPORT_SYMBOL(ax25_findbyuid);
+EXPORT_SYMBOL(ax25_find_cb);
+EXPORT_SYMBOL(ax25_linkfail_register);
+EXPORT_SYMBOL(ax25_linkfail_release);
+EXPORT_SYMBOL(ax25_listen_register);
+EXPORT_SYMBOL(ax25_listen_release);
+EXPORT_SYMBOL(ax25_protocol_register);
+EXPORT_SYMBOL(ax25_protocol_release);
+EXPORT_SYMBOL(ax25_send_frame);
+EXPORT_SYMBOL(ax25_uid_policy);
+EXPORT_SYMBOL(ax25cmp);
+EXPORT_SYMBOL(ax2asc);
+EXPORT_SYMBOL(asc2ax);
+EXPORT_SYMBOL(null_ax25_address);
+EXPORT_SYMBOL(ax25_display_timer);
+
+static char banner[] __initdata = KERN_INFO "NET4: G4KLX/GW4PTS AX.25 for Linux. Version 0.37 for Linux NET4.0\n";
+
+static int __init ax25_init(void)
+{
+ sock_register(&ax25_family_ops);
+ dev_add_pack(&ax25_packet_type);
+ register_netdevice_notifier(&ax25_dev_notifier);
+ ax25_register_sysctl();
+
+ proc_net_create("ax25_route", 0, ax25_rt_get_info);
+ proc_net_create("ax25", 0, ax25_get_info);
+ proc_net_create("ax25_calls", 0, ax25_uid_get_info);
+
+ printk(banner);
+ return 0;
+}
+module_init(ax25_init);
+
+
+MODULE_AUTHOR("Jonathan Naylor G4KLX <g4klx@g4klx.demon.co.uk>");
+MODULE_DESCRIPTION("The amateur radio AX.25 link layer protocol");
+MODULE_LICENSE("GPL");
+
+static void __exit ax25_exit(void)
+{
+ proc_net_remove("ax25_route");
+ proc_net_remove("ax25");
+ proc_net_remove("ax25_calls");
+ ax25_rt_free();
+ ax25_uid_free();
+ ax25_dev_free();
+
+ ax25_unregister_sysctl();
+ unregister_netdevice_notifier(&ax25_dev_notifier);
+
+ dev_remove_pack(&ax25_packet_type);
+
+ sock_unregister(PF_AX25);
+}
+module_exit(ax25_exit);
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_addr.c b/uClinux-2.4.31-uc0/net/ax25/ax25_addr.c
new file mode 100644
index 0000000..f96c655
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_addr.c
@@ -0,0 +1,303 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Most of this code is based on the SDL diagrams published in the 7th
+ * ARRL Computer Networking Conference papers. The diagrams have mistakes
+ * in them, but are mostly correct. Before you modify the code could you
+ * read the SDL diagrams as the code is not obvious and probably very
+ * easy to break;
+ *
+ * History
+ * AX.25 036 Jonathan(G4KLX) Split from ax25_subr.c.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+
+/*
+ * The null address is defined as a callsign of all spaces with an
+ * SSID of zero.
+ */
+ax25_address null_ax25_address = {{0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00}};
+
+/*
+ * ax25 -> ascii conversion
+ */
+char *ax2asc(ax25_address *a)
+{
+ static char buf[11];
+ char c, *s;
+ int n;
+
+ for (n = 0, s = buf; n < 6; n++) {
+ c = (a->ax25_call[n] >> 1) & 0x7F;
+
+ if (c != ' ') *s++ = c;
+ }
+
+ *s++ = '-';
+
+ if ((n = ((a->ax25_call[6] >> 1) & 0x0F)) > 9) {
+ *s++ = '1';
+ n -= 10;
+ }
+
+ *s++ = n + '0';
+ *s++ = '\0';
+
+ if (*buf == '\0' || *buf == '-')
+ return "*";
+
+ return buf;
+
+}
+
+/*
+ * ascii -> ax25 conversion
+ */
+ax25_address *asc2ax(char *callsign)
+{
+ static ax25_address addr;
+ char *s;
+ int n;
+
+ for (s = callsign, n = 0; n < 6; n++) {
+ if (*s != '\0' && *s != '-')
+ addr.ax25_call[n] = *s++;
+ else
+ addr.ax25_call[n] = ' ';
+ addr.ax25_call[n] <<= 1;
+ addr.ax25_call[n] &= 0xFE;
+ }
+
+ if (*s++ == '\0') {
+ addr.ax25_call[6] = 0x00;
+ return &addr;
+ }
+
+ addr.ax25_call[6] = *s++ - '0';
+
+ if (*s != '\0') {
+ addr.ax25_call[6] *= 10;
+ addr.ax25_call[6] += *s++ - '0';
+ }
+
+ addr.ax25_call[6] <<= 1;
+ addr.ax25_call[6] &= 0x1E;
+
+ return &addr;
+}
+
+/*
+ * Compare two ax.25 addresses
+ */
+int ax25cmp(ax25_address *a, ax25_address *b)
+{
+ int ct = 0;
+
+ while (ct < 6) {
+ if ((a->ax25_call[ct] & 0xFE) != (b->ax25_call[ct] & 0xFE)) /* Clean off repeater bits */
+ return 1;
+ ct++;
+ }
+
+ if ((a->ax25_call[ct] & 0x1E) == (b->ax25_call[ct] & 0x1E)) /* SSID without control bit */
+ return 0;
+
+ return 2; /* Partial match */
+}
+
+/*
+ * Compare two AX.25 digipeater paths.
+ */
+int ax25digicmp(ax25_digi *digi1, ax25_digi *digi2)
+{
+ int i;
+
+ if (digi1->ndigi != digi2->ndigi)
+ return 1;
+
+ if (digi1->lastrepeat != digi2->lastrepeat)
+ return 1;
+
+ for (i = 0; i < digi1->ndigi; i++)
+ if (ax25cmp(&digi1->calls[i], &digi2->calls[i]) != 0)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Given an AX.25 address pull of to, from, digi list, command/response and the start of data
+ *
+ */
+unsigned char *ax25_addr_parse(unsigned char *buf, int len, ax25_address *src, ax25_address *dest, ax25_digi *digi, int *flags, int *dama)
+{
+ int d = 0;
+
+ if (len < 14) return NULL;
+
+ if (flags != NULL) {
+ *flags = 0;
+
+ if (buf[6] & AX25_CBIT)
+ *flags = AX25_COMMAND;
+ if (buf[13] & AX25_CBIT)
+ *flags = AX25_RESPONSE;
+ }
+
+ if (dama != NULL)
+ *dama = ~buf[13] & AX25_DAMA_FLAG;
+
+ /* Copy to, from */
+ if (dest != NULL)
+ memcpy(dest, buf + 0, AX25_ADDR_LEN);
+ if (src != NULL)
+ memcpy(src, buf + 7, AX25_ADDR_LEN);
+
+ buf += 2 * AX25_ADDR_LEN;
+ len -= 2 * AX25_ADDR_LEN;
+
+ digi->lastrepeat = -1;
+ digi->ndigi = 0;
+
+ while (!(buf[-1] & AX25_EBIT)) {
+ if (d >= AX25_MAX_DIGIS) return NULL; /* Max of 6 digis */
+ if (len < 7) return NULL; /* Short packet */
+
+ memcpy(&digi->calls[d], buf, AX25_ADDR_LEN);
+ digi->ndigi = d + 1;
+
+ if (buf[6] & AX25_HBIT) {
+ digi->repeated[d] = 1;
+ digi->lastrepeat = d;
+ } else {
+ digi->repeated[d] = 0;
+ }
+
+ buf += AX25_ADDR_LEN;
+ len -= AX25_ADDR_LEN;
+ d++;
+ }
+
+ return buf;
+}
+
+/*
+ * Assemble an AX.25 header from the bits
+ */
+int ax25_addr_build(unsigned char *buf, ax25_address *src, ax25_address *dest, ax25_digi *d, int flag, int modulus)
+{
+ int len = 0;
+ int ct = 0;
+
+ memcpy(buf, dest, AX25_ADDR_LEN);
+ buf[6] &= ~(AX25_EBIT | AX25_CBIT);
+ buf[6] |= AX25_SSSID_SPARE;
+
+ if (flag == AX25_COMMAND) buf[6] |= AX25_CBIT;
+
+ buf += AX25_ADDR_LEN;
+ len += AX25_ADDR_LEN;
+
+ memcpy(buf, src, AX25_ADDR_LEN);
+ buf[6] &= ~(AX25_EBIT | AX25_CBIT);
+ buf[6] &= ~AX25_SSSID_SPARE;
+
+ if (modulus == AX25_MODULUS)
+ buf[6] |= AX25_SSSID_SPARE;
+ else
+ buf[6] |= AX25_ESSID_SPARE;
+
+ if (flag == AX25_RESPONSE) buf[6] |= AX25_CBIT;
+
+ /*
+ * Fast path the normal digiless path
+ */
+ if (d == NULL || d->ndigi == 0) {
+ buf[6] |= AX25_EBIT;
+ return 2 * AX25_ADDR_LEN;
+ }
+
+ buf += AX25_ADDR_LEN;
+ len += AX25_ADDR_LEN;
+
+ while (ct < d->ndigi) {
+ memcpy(buf, &d->calls[ct], AX25_ADDR_LEN);
+
+ if (d->repeated[ct])
+ buf[6] |= AX25_HBIT;
+ else
+ buf[6] &= ~AX25_HBIT;
+
+ buf[6] &= ~AX25_EBIT;
+ buf[6] |= AX25_SSSID_SPARE;
+
+ buf += AX25_ADDR_LEN;
+ len += AX25_ADDR_LEN;
+ ct++;
+ }
+
+ buf[-1] |= AX25_EBIT;
+
+ return len;
+}
+
+int ax25_addr_size(ax25_digi *dp)
+{
+ if (dp == NULL)
+ return 2 * AX25_ADDR_LEN;
+
+ return AX25_ADDR_LEN * (2 + dp->ndigi);
+}
+
+/*
+ * Reverse Digipeat List. May not pass both parameters as same struct
+ */
+void ax25_digi_invert(ax25_digi *in, ax25_digi *out)
+{
+ int ct;
+
+ out->ndigi = in->ndigi;
+ out->lastrepeat = in->ndigi - in->lastrepeat - 2;
+
+ /* Invert the digipeaters */
+ for (ct = 0; ct < in->ndigi; ct++) {
+ out->calls[ct] = in->calls[in->ndigi - ct - 1];
+
+ if (ct <= out->lastrepeat) {
+ out->calls[ct].ax25_call[6] |= AX25_HBIT;
+ out->repeated[ct] = 1;
+ } else {
+ out->calls[ct].ax25_call[6] &= ~AX25_HBIT;
+ out->repeated[ct] = 0;
+ }
+ }
+}
+
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_dev.c b/uClinux-2.4.31-uc0/net/ax25/ax25_dev.c
new file mode 100644
index 0000000..efeec64
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_dev.c
@@ -0,0 +1,213 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Other kernels modules in this kit are generally BSD derived. See the copyright headers.
+ *
+ *
+ * History
+ * AX.25 036 Jonathan(G4KLX) Split from ax25_route.c.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+
+ax25_dev *ax25_dev_list;
+
+ax25_dev *ax25_dev_ax25dev(struct net_device *dev)
+{
+ ax25_dev *ax25_dev;
+
+ for (ax25_dev = ax25_dev_list; ax25_dev != NULL; ax25_dev = ax25_dev->next)
+ if (ax25_dev->dev == dev)
+ return ax25_dev;
+
+ return NULL;
+}
+
+ax25_dev *ax25_addr_ax25dev(ax25_address *addr)
+{
+ ax25_dev *ax25_dev;
+
+ for (ax25_dev = ax25_dev_list; ax25_dev != NULL; ax25_dev = ax25_dev->next)
+ if (ax25cmp(addr, (ax25_address *)ax25_dev->dev->dev_addr) == 0)
+ return ax25_dev;
+
+ return NULL;
+}
+
+/*
+ * This is called when an interface is brought up. These are
+ * reasonable defaults.
+ */
+void ax25_dev_device_up(struct net_device *dev)
+{
+ ax25_dev *ax25_dev;
+ unsigned long flags;
+
+ if ((ax25_dev = kmalloc(sizeof(*ax25_dev), GFP_ATOMIC)) == NULL) {
+ printk(KERN_ERR "AX.25: ax25_dev_device_up - out of memory\n");
+ return;
+ }
+
+ ax25_unregister_sysctl();
+
+ memset(ax25_dev, 0x00, sizeof(*ax25_dev));
+
+ ax25_dev->dev = dev;
+ ax25_dev->forward = NULL;
+
+ ax25_dev->values[AX25_VALUES_IPDEFMODE] = AX25_DEF_IPDEFMODE;
+ ax25_dev->values[AX25_VALUES_AXDEFMODE] = AX25_DEF_AXDEFMODE;
+ ax25_dev->values[AX25_VALUES_BACKOFF] = AX25_DEF_BACKOFF;
+ ax25_dev->values[AX25_VALUES_CONMODE] = AX25_DEF_CONMODE;
+ ax25_dev->values[AX25_VALUES_WINDOW] = AX25_DEF_WINDOW;
+ ax25_dev->values[AX25_VALUES_EWINDOW] = AX25_DEF_EWINDOW;
+ ax25_dev->values[AX25_VALUES_T1] = AX25_DEF_T1;
+ ax25_dev->values[AX25_VALUES_T2] = AX25_DEF_T2;
+ ax25_dev->values[AX25_VALUES_T3] = AX25_DEF_T3;
+ ax25_dev->values[AX25_VALUES_IDLE] = AX25_DEF_IDLE;
+ ax25_dev->values[AX25_VALUES_N2] = AX25_DEF_N2;
+ ax25_dev->values[AX25_VALUES_PACLEN] = AX25_DEF_PACLEN;
+ ax25_dev->values[AX25_VALUES_PROTOCOL] = AX25_DEF_PROTOCOL;
+ ax25_dev->values[AX25_VALUES_DS_TIMEOUT]= AX25_DEF_DS_TIMEOUT;
+
+ save_flags(flags); cli();
+ ax25_dev->next = ax25_dev_list;
+ ax25_dev_list = ax25_dev;
+ restore_flags(flags);
+
+ ax25_register_sysctl();
+}
+
+void ax25_dev_device_down(struct net_device *dev)
+{
+ ax25_dev *s, *ax25_dev;
+ unsigned long flags;
+
+ if ((ax25_dev = ax25_dev_ax25dev(dev)) == NULL)
+ return;
+
+ ax25_unregister_sysctl();
+
+ save_flags(flags); cli();
+
+#ifdef CONFIG_AX25_DAMA_SLAVE
+ ax25_ds_del_timer(ax25_dev);
+#endif
+
+ /*
+ * Remove any packet forwarding that points to this device.
+ */
+ for (s = ax25_dev_list; s != NULL; s = s->next)
+ if (s->forward == dev)
+ s->forward = NULL;
+
+ if ((s = ax25_dev_list) == ax25_dev) {
+ ax25_dev_list = s->next;
+ restore_flags(flags);
+ kfree(ax25_dev);
+ ax25_register_sysctl();
+ return;
+ }
+
+ while (s != NULL && s->next != NULL) {
+ if (s->next == ax25_dev) {
+ s->next = ax25_dev->next;
+ restore_flags(flags);
+ kfree(ax25_dev);
+ ax25_register_sysctl();
+ return;
+ }
+
+ s = s->next;
+ }
+
+ restore_flags(flags);
+ ax25_register_sysctl();
+}
+
+int ax25_fwd_ioctl(unsigned int cmd, struct ax25_fwd_struct *fwd)
+{
+ ax25_dev *ax25_dev, *fwd_dev;
+
+ if ((ax25_dev = ax25_addr_ax25dev(&fwd->port_from)) == NULL)
+ return -EINVAL;
+
+ switch (cmd) {
+ case SIOCAX25ADDFWD:
+ if ((fwd_dev = ax25_addr_ax25dev(&fwd->port_to)) == NULL)
+ return -EINVAL;
+ if (ax25_dev->forward != NULL)
+ return -EINVAL;
+ ax25_dev->forward = fwd_dev->dev;
+ break;
+
+ case SIOCAX25DELFWD:
+ if (ax25_dev->forward == NULL)
+ return -EINVAL;
+ ax25_dev->forward = NULL;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+struct net_device *ax25_fwd_dev(struct net_device *dev)
+{
+ ax25_dev *ax25_dev;
+
+ if ((ax25_dev = ax25_dev_ax25dev(dev)) == NULL)
+ return dev;
+
+ if (ax25_dev->forward == NULL)
+ return dev;
+
+ return ax25_dev->forward;
+}
+
+/*
+ * Free all memory associated with device structures.
+ */
+void __exit ax25_dev_free(void)
+{
+ ax25_dev *s, *ax25_dev = ax25_dev_list;
+
+ while (ax25_dev != NULL) {
+ s = ax25_dev;
+ ax25_dev = ax25_dev->next;
+
+ kfree(s);
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_ds_in.c b/uClinux-2.4.31-uc0/net/ax25/ax25_ds_in.c
new file mode 100644
index 0000000..6c65bae
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_ds_in.c
@@ -0,0 +1,312 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Most of this code is based on the SDL diagrams published in the 7th
+ * ARRL Computer Networking Conference papers. The diagrams have mistakes
+ * in them, but are mostly correct. Before you modify the code could you
+ * read the SDL diagrams as the code is not obvious and probably very
+ * easy to break;
+ *
+ * History
+ * AX.25 036 Jonathan(G4KLX) Cloned from ax25_in.c
+ * Joerg(DL1BKE) Fixed it.
+ * AX.25 037 Jonathan(G4KLX) New timer architecture.
+ * Joerg(DL1BKE) ax25->n2count never got reset
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/ip.h> /* For ip_rcv */
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+
+/*
+ * State machine for state 1, Awaiting Connection State.
+ * The handling of the timer(s) is in file ax25_ds_timer.c.
+ * Handling of state 0 and connection release is in ax25.c.
+ */
+static int ax25_ds_state1_machine(ax25_cb *ax25, struct sk_buff *skb, int frametype, int pf, int type)
+{
+ switch (frametype) {
+ case AX25_SABM:
+ ax25->modulus = AX25_MODULUS;
+ ax25->window = ax25->ax25_dev->values[AX25_VALUES_WINDOW];
+ ax25_send_control(ax25, AX25_UA, pf, AX25_RESPONSE);
+ break;
+
+ case AX25_SABME:
+ ax25->modulus = AX25_EMODULUS;
+ ax25->window = ax25->ax25_dev->values[AX25_VALUES_EWINDOW];
+ ax25_send_control(ax25, AX25_UA, pf, AX25_RESPONSE);
+ break;
+
+ case AX25_DISC:
+ ax25_send_control(ax25, AX25_DM, pf, AX25_RESPONSE);
+ break;
+
+ case AX25_UA:
+ ax25_calculate_rtt(ax25);
+ ax25_stop_t1timer(ax25);
+ ax25_start_t3timer(ax25);
+ ax25_start_idletimer(ax25);
+ ax25->vs = 0;
+ ax25->va = 0;
+ ax25->vr = 0;
+ ax25->state = AX25_STATE_3;
+ ax25->n2count = 0;
+ if (ax25->sk != NULL) {
+ ax25->sk->state = TCP_ESTABLISHED;
+ /* For WAIT_SABM connections we will produce an accept ready socket here */
+ if (!ax25->sk->dead)
+ ax25->sk->state_change(ax25->sk);
+ }
+ ax25_dama_on(ax25);
+
+ /* according to DK4EG´s spec we are required to
+ * send a RR RESPONSE FINAL NR=0.
+ */
+
+ ax25_std_enquiry_response(ax25);
+ break;
+
+ case AX25_DM:
+ if (pf) ax25_disconnect(ax25, ECONNREFUSED);
+ break;
+
+ default:
+ if (pf) ax25_send_control(ax25, AX25_SABM, AX25_POLLON, AX25_COMMAND);
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * State machine for state 2, Awaiting Release State.
+ * The handling of the timer(s) is in file ax25_ds_timer.c
+ * Handling of state 0 and connection release is in ax25.c.
+ */
+static int ax25_ds_state2_machine(ax25_cb *ax25, struct sk_buff *skb, int frametype, int pf, int type)
+{
+ switch (frametype) {
+ case AX25_SABM:
+ case AX25_SABME:
+ ax25_send_control(ax25, AX25_DISC, AX25_POLLON, AX25_COMMAND);
+ ax25_dama_off(ax25);
+ break;
+
+ case AX25_DISC:
+ ax25_send_control(ax25, AX25_UA, pf, AX25_RESPONSE);
+ ax25_dama_off(ax25);
+ ax25_disconnect(ax25, 0);
+ break;
+
+ case AX25_DM:
+ case AX25_UA:
+ if (pf) {
+ ax25_dama_off(ax25);
+ ax25_disconnect(ax25, 0);
+ }
+ break;
+
+ case AX25_I:
+ case AX25_REJ:
+ case AX25_RNR:
+ case AX25_RR:
+ if (pf) {
+ ax25_send_control(ax25, AX25_DISC, AX25_POLLON, AX25_COMMAND);
+ ax25_dama_off(ax25);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * State machine for state 3, Connected State.
+ * The handling of the timer(s) is in file ax25_timer.c
+ * Handling of state 0 and connection release is in ax25.c.
+ */
+static int ax25_ds_state3_machine(ax25_cb *ax25, struct sk_buff *skb, int frametype, int ns, int nr, int pf, int type)
+{
+ int queued = 0;
+
+ switch (frametype) {
+ case AX25_SABM:
+ case AX25_SABME:
+ if (frametype == AX25_SABM) {
+ ax25->modulus = AX25_MODULUS;
+ ax25->window = ax25->ax25_dev->values[AX25_VALUES_WINDOW];
+ } else {
+ ax25->modulus = AX25_EMODULUS;
+ ax25->window = ax25->ax25_dev->values[AX25_VALUES_EWINDOW];
+ }
+ ax25_send_control(ax25, AX25_UA, pf, AX25_RESPONSE);
+ ax25_stop_t1timer(ax25);
+ ax25_start_t3timer(ax25);
+ ax25_start_idletimer(ax25);
+ ax25->condition = 0x00;
+ ax25->vs = 0;
+ ax25->va = 0;
+ ax25->vr = 0;
+ ax25_requeue_frames(ax25);
+ ax25_dama_on(ax25);
+ break;
+
+ case AX25_DISC:
+ ax25_send_control(ax25, AX25_UA, pf, AX25_RESPONSE);
+ ax25_dama_off(ax25);
+ ax25_disconnect(ax25, 0);
+ break;
+
+ case AX25_DM:
+ ax25_dama_off(ax25);
+ ax25_disconnect(ax25, ECONNRESET);
+ break;
+
+ case AX25_RR:
+ case AX25_RNR:
+ if (frametype == AX25_RR)
+ ax25->condition &= ~AX25_COND_PEER_RX_BUSY;
+ else
+ ax25->condition |= AX25_COND_PEER_RX_BUSY;
+
+ if (ax25_validate_nr(ax25, nr)) {
+ if (ax25_check_iframes_acked(ax25, nr))
+ ax25->n2count=0;
+ if (type == AX25_COMMAND && pf)
+ ax25_ds_enquiry_response(ax25);
+ } else {
+ ax25_ds_nr_error_recovery(ax25);
+ ax25->state = AX25_STATE_1;
+ }
+ break;
+
+ case AX25_REJ:
+ ax25->condition &= ~AX25_COND_PEER_RX_BUSY;
+
+ if (ax25_validate_nr(ax25, nr)) {
+ if (ax25->va != nr)
+ ax25->n2count=0;
+
+ ax25_frames_acked(ax25, nr);
+ ax25_calculate_rtt(ax25);
+ ax25_stop_t1timer(ax25);
+ ax25_start_t3timer(ax25);
+ ax25_requeue_frames(ax25);
+
+ if (type == AX25_COMMAND && pf)
+ ax25_ds_enquiry_response(ax25);
+ } else {
+ ax25_ds_nr_error_recovery(ax25);
+ ax25->state = AX25_STATE_1;
+ }
+ break;
+
+ case AX25_I:
+ if (!ax25_validate_nr(ax25, nr)) {
+ ax25_ds_nr_error_recovery(ax25);
+ ax25->state = AX25_STATE_1;
+ break;
+ }
+ if (ax25->condition & AX25_COND_PEER_RX_BUSY) {
+ ax25_frames_acked(ax25, nr);
+ ax25->n2count = 0;
+ } else {
+ if (ax25_check_iframes_acked(ax25, nr))
+ ax25->n2count = 0;
+ }
+ if (ax25->condition & AX25_COND_OWN_RX_BUSY) {
+ if (pf) ax25_ds_enquiry_response(ax25);
+ break;
+ }
+ if (ns == ax25->vr) {
+ ax25->vr = (ax25->vr + 1) % ax25->modulus;
+ queued = ax25_rx_iframe(ax25, skb);
+ if (ax25->condition & AX25_COND_OWN_RX_BUSY)
+ ax25->vr = ns; /* ax25->vr - 1 */
+ ax25->condition &= ~AX25_COND_REJECT;
+ if (pf) {
+ ax25_ds_enquiry_response(ax25);
+ } else {
+ if (!(ax25->condition & AX25_COND_ACK_PENDING)) {
+ ax25->condition |= AX25_COND_ACK_PENDING;
+ ax25_start_t2timer(ax25);
+ }
+ }
+ } else {
+ if (ax25->condition & AX25_COND_REJECT) {
+ if (pf) ax25_ds_enquiry_response(ax25);
+ } else {
+ ax25->condition |= AX25_COND_REJECT;
+ ax25_ds_enquiry_response(ax25);
+ ax25->condition &= ~AX25_COND_ACK_PENDING;
+ }
+ }
+ break;
+
+ case AX25_FRMR:
+ case AX25_ILLEGAL:
+ ax25_ds_establish_data_link(ax25);
+ ax25->state = AX25_STATE_1;
+ break;
+
+ default:
+ break;
+ }
+
+ return queued;
+}
+
+/*
+ * Higher level upcall for a LAPB frame
+ */
+int ax25_ds_frame_in(ax25_cb *ax25, struct sk_buff *skb, int type)
+{
+ int queued = 0, frametype, ns, nr, pf;
+
+ frametype = ax25_decode(ax25, skb, &ns, &nr, &pf);
+
+ switch (ax25->state) {
+ case AX25_STATE_1:
+ queued = ax25_ds_state1_machine(ax25, skb, frametype, pf, type);
+ break;
+ case AX25_STATE_2:
+ queued = ax25_ds_state2_machine(ax25, skb, frametype, pf, type);
+ break;
+ case AX25_STATE_3:
+ queued = ax25_ds_state3_machine(ax25, skb, frametype, ns, nr, pf, type);
+ break;
+ }
+
+ return queued;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_ds_subr.c b/uClinux-2.4.31-uc0/net/ax25/ax25_ds_subr.c
new file mode 100644
index 0000000..e3e88d7
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_ds_subr.c
@@ -0,0 +1,217 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Most of this code is based on the SDL diagrams published in the 7th
+ * ARRL Computer Networking Conference papers. The diagrams have mistakes
+ * in them, but are mostly correct. Before you modify the code could you
+ * read the SDL diagrams as the code is not obvious and probably very
+ * easy to break;
+ *
+ * History
+ * AX.25 036 Jonathan(G4KLX) Cloned from ax25_out.c and ax25_subr.c.
+ * Joerg(DL1BKE) Changed ax25_ds_enquiry_response(),
+ * fixed ax25_dama_on() and ax25_dama_off().
+ * AX.25 037 Jonathan(G4KLX) New timer architecture.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+
+void ax25_ds_nr_error_recovery(ax25_cb *ax25)
+{
+ ax25_ds_establish_data_link(ax25);
+}
+
+/*
+ * dl1bke 960114: transmit I frames on DAMA poll
+ */
+void ax25_ds_enquiry_response(ax25_cb *ax25)
+{
+ ax25_cb *ax25o;
+
+ /* Please note that neither DK4EG´s nor DG2FEF´s
+ * DAMA spec mention the following behaviour as seen
+ * with TheFirmware:
+ *
+ * DB0ACH->DL1BKE <RR C P R0> [DAMA]
+ * DL1BKE->DB0ACH <I NR=0 NS=0>
+ * DL1BKE-7->DB0PRA-6 DB0ACH <I C S3 R5>
+ * DL1BKE->DB0ACH <RR R F R0>
+ *
+ * The Flexnet DAMA Master implementation apparently
+ * insists on the "proper" AX.25 behaviour:
+ *
+ * DB0ACH->DL1BKE <RR C P R0> [DAMA]
+ * DL1BKE->DB0ACH <RR R F R0>
+ * DL1BKE->DB0ACH <I NR=0 NS=0>
+ * DL1BKE-7->DB0PRA-6 DB0ACH <I C S3 R5>
+ *
+ * Flexnet refuses to send us *any* I frame if we send
+ * a REJ in case AX25_COND_REJECT is set. It is superfluous in
+ * this mode anyway (a RR or RNR invokes the retransmission).
+ * Is this a Flexnet bug?
+ */
+
+ ax25_std_enquiry_response(ax25);
+
+ if (!(ax25->condition & AX25_COND_PEER_RX_BUSY)) {
+ ax25_requeue_frames(ax25);
+ ax25_kick(ax25);
+ }
+
+ if (ax25->state == AX25_STATE_1 || ax25->state == AX25_STATE_2 || skb_peek(&ax25->ack_queue) != NULL)
+ ax25_ds_t1_timeout(ax25);
+ else
+ ax25->n2count = 0;
+
+ ax25_start_t3timer(ax25);
+ ax25_ds_set_timer(ax25->ax25_dev);
+
+ for (ax25o = ax25_list; ax25o != NULL; ax25o = ax25o->next) {
+ if (ax25o == ax25)
+ continue;
+
+ if (ax25o->ax25_dev != ax25->ax25_dev)
+ continue;
+
+ if (ax25o->state == AX25_STATE_1 || ax25o->state == AX25_STATE_2) {
+ ax25_ds_t1_timeout(ax25o);
+ continue;
+ }
+
+ if (!(ax25o->condition & AX25_COND_PEER_RX_BUSY) && ax25o->state == AX25_STATE_3) {
+ ax25_requeue_frames(ax25o);
+ ax25_kick(ax25o);
+ }
+
+ if (ax25o->state == AX25_STATE_1 || ax25o->state == AX25_STATE_2 || skb_peek(&ax25o->ack_queue) != NULL)
+ ax25_ds_t1_timeout(ax25o);
+
+ /* do not start T3 for listening sockets (tnx DD8NE) */
+
+ if (ax25o->state != AX25_STATE_0)
+ ax25_start_t3timer(ax25o);
+ }
+}
+
+void ax25_ds_establish_data_link(ax25_cb *ax25)
+{
+ ax25->condition &= AX25_COND_DAMA_MODE;
+ ax25->n2count = 0;
+ ax25_calculate_t1(ax25);
+ ax25_start_t1timer(ax25);
+ ax25_stop_t2timer(ax25);
+ ax25_start_t3timer(ax25);
+}
+
+/*
+ * :::FIXME:::
+ * This is a kludge. Not all drivers recognize kiss commands.
+ * We need a driver level request to switch duplex mode, that does
+ * either SCC changing, PI config or KISS as required. Currently
+ * this request isn't reliable.
+ */
+static void ax25_kiss_cmd(ax25_dev *ax25_dev, unsigned char cmd, unsigned char param)
+{
+ struct sk_buff *skb;
+ unsigned char *p;
+
+ if (ax25_dev->dev == NULL)
+ return;
+
+ if ((skb = alloc_skb(2, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb->nh.raw = skb->data;
+ p = skb_put(skb, 2);
+
+ *p++ = cmd;
+ *p++ = param;
+
+ skb->dev = ax25_dev->dev;
+ skb->protocol = htons(ETH_P_AX25);
+
+ dev_queue_xmit(skb);
+}
+
+/*
+ * A nasty problem arises if we count the number of DAMA connections
+ * wrong, especially when connections on the device already existed
+ * and our network node (or the sysop) decides to turn on DAMA Master
+ * mode. We thus flag the 'real' slave connections with
+ * ax25->dama_slave=1 and look on every disconnect if still slave
+ * connections exist.
+ */
+static int ax25_check_dama_slave(ax25_dev *ax25_dev)
+{
+ ax25_cb *ax25;
+
+ for (ax25 = ax25_list; ax25 != NULL ; ax25 = ax25->next)
+ if (ax25->ax25_dev == ax25_dev && (ax25->condition & AX25_COND_DAMA_MODE) && ax25->state > AX25_STATE_1)
+ return 1;
+
+ return 0;
+}
+
+void ax25_dev_dama_on(ax25_dev *ax25_dev)
+{
+ if (ax25_dev == NULL)
+ return;
+
+ if (ax25_dev->dama.slave == 0)
+ ax25_kiss_cmd(ax25_dev, 5, 1);
+
+ ax25_dev->dama.slave = 1;
+ ax25_ds_set_timer(ax25_dev);
+}
+
+void ax25_dev_dama_off(ax25_dev *ax25_dev)
+{
+ if (ax25_dev == NULL)
+ return;
+
+ if (ax25_dev->dama.slave && !ax25_check_dama_slave(ax25_dev)) {
+ ax25_kiss_cmd(ax25_dev, 5, 0);
+ ax25_dev->dama.slave = 0;
+ ax25_ds_del_timer(ax25_dev);
+ }
+}
+
+void ax25_dama_on(ax25_cb *ax25)
+{
+ ax25_dev_dama_on(ax25->ax25_dev);
+ ax25->condition |= AX25_COND_DAMA_MODE;
+}
+
+void ax25_dama_off(ax25_cb *ax25)
+{
+ ax25->condition &= ~AX25_COND_DAMA_MODE;
+ ax25_dev_dama_off(ax25->ax25_dev);
+}
+
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_ds_timer.c b/uClinux-2.4.31-uc0/net/ax25/ax25_ds_timer.c
new file mode 100644
index 0000000..3c5b2ea
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_ds_timer.c
@@ -0,0 +1,224 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * AX.25 036 Jonathan(G4KLX) Cloned from ax25_timer.c.
+ * Joerg(DL1BKE) Added DAMA Slave Timeout timer
+ * AX.25 037 Jonathan(G4KLX) New timer architecture.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+
+static void ax25_ds_timeout(unsigned long);
+
+/*
+ * Add DAMA slave timeout timer to timer list.
+ * Unlike the connection based timers the timeout function gets
+ * triggered every second. Please note that NET_AX25_DAMA_SLAVE_TIMEOUT
+ * (aka /proc/sys/net/ax25/{dev}/dama_slave_timeout) is still in
+ * 1/10th of a second.
+ */
+
+static void ax25_ds_add_timer(ax25_dev *ax25_dev)
+{
+ struct timer_list *t = &ax25_dev->dama.slave_timer;
+ t->data = (unsigned long) ax25_dev;
+ t->function = &ax25_ds_timeout;
+ t->expires = jiffies + HZ;
+ add_timer(t);
+}
+
+void ax25_ds_del_timer(ax25_dev *ax25_dev)
+{
+ if (ax25_dev) del_timer(&ax25_dev->dama.slave_timer);
+}
+
+void ax25_ds_set_timer(ax25_dev *ax25_dev)
+{
+ if (ax25_dev == NULL) /* paranoia */
+ return;
+
+ del_timer(&ax25_dev->dama.slave_timer);
+ ax25_dev->dama.slave_timeout = ax25_dev->values[AX25_VALUES_DS_TIMEOUT] / 10;
+ ax25_ds_add_timer(ax25_dev);
+}
+
+/*
+ * DAMA Slave Timeout
+ * Silently discard all (slave) connections in case our master forgot us...
+ */
+
+static void ax25_ds_timeout(unsigned long arg)
+{
+ ax25_dev *ax25_dev = (struct ax25_dev *) arg;
+ ax25_cb *ax25;
+
+ if (ax25_dev == NULL || !ax25_dev->dama.slave)
+ return; /* Yikes! */
+
+ if (!ax25_dev->dama.slave_timeout || --ax25_dev->dama.slave_timeout) {
+ ax25_ds_set_timer(ax25_dev);
+ return;
+ }
+
+ for (ax25=ax25_list; ax25 != NULL; ax25 = ax25->next) {
+ if (ax25->ax25_dev != ax25_dev || !(ax25->condition & AX25_COND_DAMA_MODE))
+ continue;
+
+ ax25_send_control(ax25, AX25_DISC, AX25_POLLON, AX25_COMMAND);
+ ax25_disconnect(ax25, ETIMEDOUT);
+ }
+
+ ax25_dev_dama_off(ax25_dev);
+}
+
+void ax25_ds_heartbeat_expiry(ax25_cb *ax25)
+{
+ switch (ax25->state) {
+
+ case AX25_STATE_0:
+ /* Magic here: If we listen() and a new link dies before it
+ is accepted() it isn't 'dead' so doesn't get removed. */
+ if (ax25->sk == NULL || ax25->sk->destroy || (ax25->sk->state == TCP_LISTEN && ax25->sk->dead)) {
+ ax25_destroy_socket(ax25);
+ return;
+ }
+ break;
+
+ case AX25_STATE_3:
+ /*
+ * Check the state of the receive buffer.
+ */
+ if (ax25->sk != NULL) {
+ if (atomic_read(&ax25->sk->rmem_alloc) < (ax25->sk->rcvbuf / 2) &&
+ (ax25->condition & AX25_COND_OWN_RX_BUSY)) {
+ ax25->condition &= ~AX25_COND_OWN_RX_BUSY;
+ ax25->condition &= ~AX25_COND_ACK_PENDING;
+ break;
+ }
+ }
+ break;
+ }
+
+ ax25_start_heartbeat(ax25);
+}
+
+/* dl1bke 960114: T3 works much like the IDLE timeout, but
+ * gets reloaded with every frame for this
+ * connection.
+ */
+void ax25_ds_t3timer_expiry(ax25_cb *ax25)
+{
+ ax25_send_control(ax25, AX25_DISC, AX25_POLLON, AX25_COMMAND);
+ ax25_dama_off(ax25);
+ ax25_disconnect(ax25, ETIMEDOUT);
+}
+
+/* dl1bke 960228: close the connection when IDLE expires.
+ * unlike T3 this timer gets reloaded only on
+ * I frames.
+ */
+void ax25_ds_idletimer_expiry(ax25_cb *ax25)
+{
+ ax25_clear_queues(ax25);
+
+ ax25->n2count = 0;
+ ax25->state = AX25_STATE_2;
+
+ ax25_calculate_t1(ax25);
+ ax25_start_t1timer(ax25);
+ ax25_stop_t3timer(ax25);
+
+ if (ax25->sk != NULL) {
+ ax25->sk->state = TCP_CLOSE;
+ ax25->sk->err = 0;
+ ax25->sk->shutdown |= SEND_SHUTDOWN;
+ if (!ax25->sk->dead)
+ ax25->sk->state_change(ax25->sk);
+ ax25->sk->dead = 1;
+ }
+}
+
+/* dl1bke 960114: The DAMA protocol requires to send data and SABM/DISC
+ * within the poll of any connected channel. Remember
+ * that we are not allowed to send anything unless we
+ * get polled by the Master.
+ *
+ * Thus we'll have to do parts of our T1 handling in
+ * ax25_enquiry_response().
+ */
+void ax25_ds_t1_timeout(ax25_cb *ax25)
+{
+ switch (ax25->state) {
+
+ case AX25_STATE_1:
+ if (ax25->n2count == ax25->n2) {
+ if (ax25->modulus == AX25_MODULUS) {
+ ax25_disconnect(ax25, ETIMEDOUT);
+ return;
+ } else {
+ ax25->modulus = AX25_MODULUS;
+ ax25->window = ax25->ax25_dev->values[AX25_VALUES_WINDOW];
+ ax25->n2count = 0;
+ ax25_send_control(ax25, AX25_SABM, AX25_POLLOFF, AX25_COMMAND);
+ }
+ } else {
+ ax25->n2count++;
+ if (ax25->modulus == AX25_MODULUS)
+ ax25_send_control(ax25, AX25_SABM, AX25_POLLOFF, AX25_COMMAND);
+ else
+ ax25_send_control(ax25, AX25_SABME, AX25_POLLOFF, AX25_COMMAND);
+ }
+ break;
+
+ case AX25_STATE_2:
+ if (ax25->n2count == ax25->n2) {
+ ax25_send_control(ax25, AX25_DISC, AX25_POLLON, AX25_COMMAND);
+ ax25_disconnect(ax25, ETIMEDOUT);
+ return;
+ } else {
+ ax25->n2count++;
+ }
+ break;
+
+ case AX25_STATE_3:
+ if (ax25->n2count == ax25->n2) {
+ ax25_send_control(ax25, AX25_DM, AX25_POLLON, AX25_RESPONSE);
+ ax25_disconnect(ax25, ETIMEDOUT);
+ return;
+ } else {
+ ax25->n2count++;
+ }
+ break;
+ }
+
+ ax25_calculate_t1(ax25);
+ ax25_start_t1timer(ax25);
+}
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_iface.c b/uClinux-2.4.31-uc0/net/ax25/ax25_iface.c
new file mode 100644
index 0000000..15ca9c4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_iface.c
@@ -0,0 +1,268 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * AX.25 036 Jonathan(G4KLX) Split from ax25_timer.c.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+
+static struct protocol_struct {
+ struct protocol_struct *next;
+ unsigned int pid;
+ int (*func)(struct sk_buff *, ax25_cb *);
+} *protocol_list;
+
+static struct linkfail_struct {
+ struct linkfail_struct *next;
+ void (*func)(ax25_cb *, int);
+} *linkfail_list;
+
+static struct listen_struct {
+ struct listen_struct *next;
+ ax25_address callsign;
+ struct net_device *dev;
+} *listen_list;
+
+int ax25_protocol_register(unsigned int pid, int (*func)(struct sk_buff *, ax25_cb *))
+{
+ struct protocol_struct *protocol;
+ unsigned long flags;
+
+ if (pid == AX25_P_TEXT || pid == AX25_P_SEGMENT)
+ return 0;
+#ifdef CONFIG_INET
+ if (pid == AX25_P_IP || pid == AX25_P_ARP)
+ return 0;
+#endif
+ if ((protocol = kmalloc(sizeof(*protocol), GFP_ATOMIC)) == NULL)
+ return 0;
+
+ protocol->pid = pid;
+ protocol->func = func;
+
+ save_flags(flags);
+ cli();
+
+ protocol->next = protocol_list;
+ protocol_list = protocol;
+
+ restore_flags(flags);
+
+ return 1;
+}
+
+void ax25_protocol_release(unsigned int pid)
+{
+ struct protocol_struct *s, *protocol = protocol_list;
+ unsigned long flags;
+
+ if (protocol == NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+
+ if (protocol->pid == pid) {
+ protocol_list = protocol->next;
+ restore_flags(flags);
+ kfree(protocol);
+ return;
+ }
+
+ while (protocol != NULL && protocol->next != NULL) {
+ if (protocol->next->pid == pid) {
+ s = protocol->next;
+ protocol->next = protocol->next->next;
+ restore_flags(flags);
+ kfree(s);
+ return;
+ }
+
+ protocol = protocol->next;
+ }
+
+ restore_flags(flags);
+}
+
+int ax25_linkfail_register(void (*func)(ax25_cb *, int))
+{
+ struct linkfail_struct *linkfail;
+ unsigned long flags;
+
+ if ((linkfail = kmalloc(sizeof(*linkfail), GFP_ATOMIC)) == NULL)
+ return 0;
+
+ linkfail->func = func;
+
+ save_flags(flags);
+ cli();
+
+ linkfail->next = linkfail_list;
+ linkfail_list = linkfail;
+
+ restore_flags(flags);
+
+ return 1;
+}
+
+void ax25_linkfail_release(void (*func)(ax25_cb *, int))
+{
+ struct linkfail_struct *s, *linkfail = linkfail_list;
+ unsigned long flags;
+
+ if (linkfail == NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+
+ if (linkfail->func == func) {
+ linkfail_list = linkfail->next;
+ restore_flags(flags);
+ kfree(linkfail);
+ return;
+ }
+
+ while (linkfail != NULL && linkfail->next != NULL) {
+ if (linkfail->next->func == func) {
+ s = linkfail->next;
+ linkfail->next = linkfail->next->next;
+ restore_flags(flags);
+ kfree(s);
+ return;
+ }
+
+ linkfail = linkfail->next;
+ }
+
+ restore_flags(flags);
+}
+
+int ax25_listen_register(ax25_address *callsign, struct net_device *dev)
+{
+ struct listen_struct *listen;
+ unsigned long flags;
+
+ if (ax25_listen_mine(callsign, dev))
+ return 0;
+
+ if ((listen = kmalloc(sizeof(*listen), GFP_ATOMIC)) == NULL)
+ return 0;
+
+ listen->callsign = *callsign;
+ listen->dev = dev;
+
+ save_flags(flags);
+ cli();
+
+ listen->next = listen_list;
+ listen_list = listen;
+
+ restore_flags(flags);
+
+ return 1;
+}
+
+void ax25_listen_release(ax25_address *callsign, struct net_device *dev)
+{
+ struct listen_struct *s, *listen = listen_list;
+ unsigned long flags;
+
+ if (listen == NULL)
+ return;
+
+ save_flags(flags);
+ cli();
+
+ if (ax25cmp(&listen->callsign, callsign) == 0 && listen->dev == dev) {
+ listen_list = listen->next;
+ restore_flags(flags);
+ kfree(listen);
+ return;
+ }
+
+ while (listen != NULL && listen->next != NULL) {
+ if (ax25cmp(&listen->next->callsign, callsign) == 0 && listen->next->dev == dev) {
+ s = listen->next;
+ listen->next = listen->next->next;
+ restore_flags(flags);
+ kfree(s);
+ return;
+ }
+
+ listen = listen->next;
+ }
+
+ restore_flags(flags);
+}
+
+int (*ax25_protocol_function(unsigned int pid))(struct sk_buff *, ax25_cb *)
+{
+ struct protocol_struct *protocol;
+
+ for (protocol = protocol_list; protocol != NULL; protocol = protocol->next)
+ if (protocol->pid == pid)
+ return protocol->func;
+
+ return NULL;
+}
+
+int ax25_listen_mine(ax25_address *callsign, struct net_device *dev)
+{
+ struct listen_struct *listen;
+
+ for (listen = listen_list; listen != NULL; listen = listen->next)
+ if (ax25cmp(&listen->callsign, callsign) == 0 && (listen->dev == dev || listen->dev == NULL))
+ return 1;
+
+ return 0;
+}
+
+void ax25_link_failed(ax25_cb *ax25, int reason)
+{
+ struct linkfail_struct *linkfail;
+
+ for (linkfail = linkfail_list; linkfail != NULL; linkfail = linkfail->next)
+ (linkfail->func)(ax25, reason);
+}
+
+int ax25_protocol_is_registered(unsigned int pid)
+{
+ struct protocol_struct *protocol;
+
+ for (protocol = protocol_list; protocol != NULL; protocol = protocol->next)
+ if (protocol->pid == pid)
+ return 1;
+
+ return 0;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_in.c b/uClinux-2.4.31-uc0/net/ax25/ax25_in.c
new file mode 100644
index 0000000..22a83a2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_in.c
@@ -0,0 +1,488 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Most of this code is based on the SDL diagrams published in the 7th
+ * ARRL Computer Networking Conference papers. The diagrams have mistakes
+ * in them, but are mostly correct. Before you modify the code could you
+ * read the SDL diagrams as the code is not obvious and probably very
+ * easy to break;
+ *
+ * History
+ * AX.25 028a Jonathan(G4KLX) New state machine based on SDL diagrams.
+ * AX.25 028b Jonathan(G4KLX) Extracted AX25 control block from
+ * the sock structure.
+ * AX.25 029 Alan(GW4PTS) Switched to KA9Q constant names.
+ * Jonathan(G4KLX) Added IP mode registration.
+ * AX.25 030 Jonathan(G4KLX) Added AX.25 fragment reception.
+ * Upgraded state machine for SABME.
+ * Added arbitrary protocol id support.
+ * AX.25 031 Joerg(DL1BKE) Added DAMA support
+ * HaJo(DD8NE) Added Idle Disc Timer T5
+ * Joerg(DL1BKE) Renamed it to "IDLE" with a slightly
+ * different behaviour. Fixed defrag
+ * routine (I hope)
+ * AX.25 032 Darryl(G7LED) AX.25 segmentation fixed.
+ * AX.25 033 Jonathan(G4KLX) Remove auto-router.
+ * Modularisation changes.
+ * AX.25 035 Hans(PE1AYX) Fixed interface to IP layer.
+ * AX.25 036 Jonathan(G4KLX) Move DAMA code into own file.
+ * Joerg(DL1BKE) Fixed DAMA Slave.
+ * AX.25 037 Jonathan(G4KLX) New timer architecture.
+ * Thomas(DL9SAU) Fixed missing initialization of skb->protocol.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter.h>
+#include <net/sock.h>
+#include <net/ip.h> /* For ip_rcv */
+#include <net/arp.h> /* For arp_rcv */
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+
+/*
+ * Given a fragment, queue it on the fragment queue and if the fragment
+ * is complete, send it back to ax25_rx_iframe.
+ */
+static int ax25_rx_fragment(ax25_cb *ax25, struct sk_buff *skb)
+{
+ struct sk_buff *skbn, *skbo;
+
+ if (ax25->fragno != 0) {
+ if (!(*skb->data & AX25_SEG_FIRST)) {
+ if ((ax25->fragno - 1) == (*skb->data & AX25_SEG_REM)) {
+ /* Enqueue fragment */
+ ax25->fragno = *skb->data & AX25_SEG_REM;
+ skb_pull(skb, 1); /* skip fragno */
+ ax25->fraglen += skb->len;
+ skb_queue_tail(&ax25->frag_queue, skb);
+
+ /* Last fragment received ? */
+ if (ax25->fragno == 0) {
+ skbn = alloc_skb(AX25_MAX_HEADER_LEN +
+ ax25->fraglen,
+ GFP_ATOMIC);
+ if (!skbn) {
+ skb_queue_purge(&ax25->frag_queue);
+ return 1;
+ }
+
+ skb_reserve(skbn, AX25_MAX_HEADER_LEN);
+
+ skbn->dev = ax25->ax25_dev->dev;
+ skbn->h.raw = skbn->data;
+ skbn->nh.raw = skbn->data;
+
+ /* Copy data from the fragments */
+ while ((skbo = skb_dequeue(&ax25->frag_queue)) != NULL) {
+ memcpy(skb_put(skbn, skbo->len), skbo->data, skbo->len);
+ kfree_skb(skbo);
+ }
+
+ ax25->fraglen = 0;
+
+ if (ax25_rx_iframe(ax25, skbn) == 0)
+ kfree_skb(skbn);
+ }
+
+ return 1;
+ }
+ }
+ } else {
+ /* First fragment received */
+ if (*skb->data & AX25_SEG_FIRST) {
+ skb_queue_purge(&ax25->frag_queue);
+ ax25->fragno = *skb->data & AX25_SEG_REM;
+ skb_pull(skb, 1); /* skip fragno */
+ ax25->fraglen = skb->len;
+ skb_queue_tail(&ax25->frag_queue, skb);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * This is where all valid I frames are sent to, to be dispatched to
+ * whichever protocol requires them.
+ */
+int ax25_rx_iframe(ax25_cb *ax25, struct sk_buff *skb)
+{
+ int (*func)(struct sk_buff *, ax25_cb *);
+ volatile int queued = 0;
+ unsigned char pid;
+
+ if (skb == NULL) return 0;
+
+ ax25_start_idletimer(ax25);
+
+ pid = *skb->data;
+
+#ifdef CONFIG_INET
+ if (pid == AX25_P_IP) {
+ /* working around a TCP bug to keep additional listeners
+ * happy. TCP re-uses the buffer and destroys the original
+ * content.
+ */
+ struct sk_buff *skbn = skb_copy(skb, GFP_ATOMIC);
+ if (skbn != NULL) {
+ kfree_skb(skb);
+ skb = skbn;
+ }
+
+ skb_pull(skb, 1); /* Remove PID */
+ skb->h.raw = skb->data;
+ skb->nh.raw = skb->data;
+ skb->dev = ax25->ax25_dev->dev;
+ skb->pkt_type = PACKET_HOST;
+ skb->protocol = htons(ETH_P_IP);
+ ip_rcv(skb, skb->dev, NULL); /* Wrong ptype */
+ return 1;
+ }
+#endif
+ if (pid == AX25_P_SEGMENT) {
+ skb_pull(skb, 1); /* Remove PID */
+ return ax25_rx_fragment(ax25, skb);
+ }
+
+ if ((func = ax25_protocol_function(pid)) != NULL) {
+ skb_pull(skb, 1); /* Remove PID */
+ return (*func)(skb, ax25);
+ }
+
+ if (ax25->sk != NULL && ax25->ax25_dev->values[AX25_VALUES_CONMODE] == 2) {
+ if ((!ax25->pidincl && ax25->sk->protocol == pid) || ax25->pidincl) {
+ if (sock_queue_rcv_skb(ax25->sk, skb) == 0)
+ queued = 1;
+ else
+ ax25->condition |= AX25_COND_OWN_RX_BUSY;
+ }
+ }
+
+ return queued;
+}
+
+/*
+ * Higher level upcall for a LAPB frame
+ */
+static int ax25_process_rx_frame(ax25_cb *ax25, struct sk_buff *skb, int type, int dama)
+{
+ int queued = 0;
+
+ if (ax25->state == AX25_STATE_0)
+ return 0;
+
+ switch (ax25->ax25_dev->values[AX25_VALUES_PROTOCOL]) {
+ case AX25_PROTO_STD_SIMPLEX:
+ case AX25_PROTO_STD_DUPLEX:
+ queued = ax25_std_frame_in(ax25, skb, type);
+ break;
+
+#ifdef CONFIG_AX25_DAMA_SLAVE
+ case AX25_PROTO_DAMA_SLAVE:
+ if (dama || ax25->ax25_dev->dama.slave)
+ queued = ax25_ds_frame_in(ax25, skb, type);
+ else
+ queued = ax25_std_frame_in(ax25, skb, type);
+ break;
+#endif
+ }
+
+ return queued;
+}
+
+static int ax25_rcv(struct sk_buff *skb, struct net_device *dev, ax25_address *dev_addr, struct packet_type *ptype)
+{
+ struct sock *make;
+ struct sock *sk;
+ int type = 0;
+ ax25_digi dp, reverse_dp;
+ ax25_cb *ax25;
+ ax25_address src, dest;
+ ax25_address *next_digi = NULL;
+ ax25_dev *ax25_dev;
+ struct sock *raw;
+ int mine = 0;
+ int dama;
+
+ /*
+ * Process the AX.25/LAPB frame.
+ */
+
+ skb->h.raw = skb->data;
+
+ if ((ax25_dev = ax25_dev_ax25dev(dev)) == NULL) {
+ kfree_skb(skb);
+ return 0;
+ }
+
+ /*
+ * Parse the address header.
+ */
+
+ if (ax25_addr_parse(skb->data, skb->len, &src, &dest, &dp, &type, &dama) == NULL) {
+ kfree_skb(skb);
+ return 0;
+ }
+
+ /*
+ * Ours perhaps ?
+ */
+ if (dp.lastrepeat + 1 < dp.ndigi) /* Not yet digipeated completely */
+ next_digi = &dp.calls[dp.lastrepeat + 1];
+
+ /*
+ * Pull of the AX.25 headers leaving the CTRL/PID bytes
+ */
+ skb_pull(skb, ax25_addr_size(&dp));
+
+ /* For our port addresses ? */
+ if (ax25cmp(&dest, dev_addr) == 0 && dp.lastrepeat + 1 == dp.ndigi)
+ mine = 1;
+
+ /* Also match on any registered callsign from L3/4 */
+ if (!mine && ax25_listen_mine(&dest, dev) && dp.lastrepeat + 1 == dp.ndigi)
+ mine = 1;
+
+ /* UI frame - bypass LAPB processing */
+ if ((*skb->data & ~0x10) == AX25_UI && dp.lastrepeat + 1 == dp.ndigi) {
+ skb->h.raw = skb->data + 2; /* skip control and pid */
+
+ if ((raw = ax25_addr_match(&dest)) != NULL)
+ ax25_send_to_raw(raw, skb, skb->data[1]);
+
+ if (!mine && ax25cmp(&dest, (ax25_address *)dev->broadcast) != 0) {
+ kfree_skb(skb);
+ return 0;
+ }
+
+ /* Now we are pointing at the pid byte */
+ switch (skb->data[1]) {
+#ifdef CONFIG_INET
+ case AX25_P_IP:
+ skb_pull(skb,2); /* drop PID/CTRL */
+ skb->h.raw = skb->data;
+ skb->nh.raw = skb->data;
+ skb->dev = dev;
+ skb->pkt_type = PACKET_HOST;
+ skb->protocol = htons(ETH_P_IP);
+ ip_rcv(skb, dev, ptype); /* Note ptype here is the wrong one, fix me later */
+ break;
+
+ case AX25_P_ARP:
+ skb_pull(skb,2);
+ skb->h.raw = skb->data;
+ skb->nh.raw = skb->data;
+ skb->dev = dev;
+ skb->pkt_type = PACKET_HOST;
+ skb->protocol = htons(ETH_P_ARP);
+ arp_rcv(skb, dev, ptype); /* Note ptype here is wrong... */
+ break;
+#endif
+ case AX25_P_TEXT:
+ /* Now find a suitable dgram socket */
+ if ((sk = ax25_find_socket(&dest, &src, SOCK_DGRAM)) != NULL) {
+ if (atomic_read(&sk->rmem_alloc) >= sk->rcvbuf) {
+ kfree_skb(skb);
+ } else {
+ /*
+ * Remove the control and PID.
+ */
+ skb_pull(skb, 2);
+ if (sock_queue_rcv_skb(sk, skb) != 0)
+ kfree_skb(skb);
+ }
+ } else {
+ kfree_skb(skb);
+ }
+ break;
+
+ default:
+ kfree_skb(skb); /* Will scan SOCK_AX25 RAW sockets */
+ break;
+ }
+
+ return 0;
+ }
+
+ /*
+ * Is connected mode supported on this device ?
+ * If not, should we DM the incoming frame (except DMs) or
+ * silently ignore them. For now we stay quiet.
+ */
+ if (ax25_dev->values[AX25_VALUES_CONMODE] == 0) {
+ kfree_skb(skb);
+ return 0;
+ }
+
+ /* LAPB */
+
+ /* AX.25 state 1-4 */
+
+ ax25_digi_invert(&dp, &reverse_dp);
+
+ if ((ax25 = ax25_find_cb(&dest, &src, &reverse_dp, dev)) != NULL) {
+ /*
+ * Process the frame. If it is queued up internally it returns one otherwise we
+ * free it immediately. This routine itself wakes the user context layers so we
+ * do no further work
+ */
+ if (ax25_process_rx_frame(ax25, skb, type, dama) == 0)
+ kfree_skb(skb);
+
+ return 0;
+ }
+
+ /* AX.25 state 0 (disconnected) */
+
+ /* a) received not a SABM(E) */
+
+ if ((*skb->data & ~AX25_PF) != AX25_SABM && (*skb->data & ~AX25_PF) != AX25_SABME) {
+ /*
+ * Never reply to a DM. Also ignore any connects for
+ * addresses that are not our interfaces and not a socket.
+ */
+ if ((*skb->data & ~AX25_PF) != AX25_DM && mine)
+ ax25_return_dm(dev, &src, &dest, &dp);
+
+ kfree_skb(skb);
+ return 0;
+ }
+
+ /* b) received SABM(E) */
+
+ if (dp.lastrepeat + 1 == dp.ndigi)
+ sk = ax25_find_listener(&dest, 0, dev, SOCK_SEQPACKET);
+ else
+ sk = ax25_find_listener(next_digi, 1, dev, SOCK_SEQPACKET);
+
+ if (sk != NULL) {
+ if (sk->ack_backlog == sk->max_ack_backlog || (make = ax25_make_new(sk, ax25_dev)) == NULL) {
+ if (mine) ax25_return_dm(dev, &src, &dest, &dp);
+ kfree_skb(skb);
+ return 0;
+ }
+
+ ax25 = make->protinfo.ax25;
+ skb_set_owner_r(skb, make);
+ skb_queue_head(&sk->receive_queue, skb);
+
+ make->state = TCP_ESTABLISHED;
+ make->pair = sk;
+
+ sk->ack_backlog++;
+ } else {
+ if (!mine) {
+ kfree_skb(skb);
+ return 0;
+ }
+
+ if ((ax25 = ax25_create_cb()) == NULL) {
+ ax25_return_dm(dev, &src, &dest, &dp);
+ kfree_skb(skb);
+ return 0;
+ }
+
+ ax25_fillin_cb(ax25, ax25_dev);
+ }
+
+ ax25->source_addr = dest;
+ ax25->dest_addr = src;
+
+ /*
+ * Sort out any digipeated paths.
+ */
+ if (dp.ndigi && !ax25->digipeat &&
+ (ax25->digipeat = kmalloc(sizeof(ax25_digi), GFP_ATOMIC)) == NULL) {
+ kfree_skb(skb);
+ ax25_destroy_socket(ax25);
+ return 0;
+ }
+
+ if (dp.ndigi == 0) {
+ if (ax25->digipeat != NULL) {
+ kfree(ax25->digipeat);
+ ax25->digipeat = NULL;
+ }
+ } else {
+ /* Reverse the source SABM's path */
+ memcpy(ax25->digipeat, &reverse_dp, sizeof(ax25_digi));
+ }
+
+ if ((*skb->data & ~AX25_PF) == AX25_SABME) {
+ ax25->modulus = AX25_EMODULUS;
+ ax25->window = ax25_dev->values[AX25_VALUES_EWINDOW];
+ } else {
+ ax25->modulus = AX25_MODULUS;
+ ax25->window = ax25_dev->values[AX25_VALUES_WINDOW];
+ }
+
+ ax25_send_control(ax25, AX25_UA, AX25_POLLON, AX25_RESPONSE);
+
+#ifdef CONFIG_AX25_DAMA_SLAVE
+ if (dama && ax25->ax25_dev->values[AX25_VALUES_PROTOCOL] == AX25_PROTO_DAMA_SLAVE)
+ ax25_dama_on(ax25);
+#endif
+
+ ax25->state = AX25_STATE_3;
+
+ ax25_insert_socket(ax25);
+
+ ax25_start_heartbeat(ax25);
+ ax25_start_t3timer(ax25);
+ ax25_start_idletimer(ax25);
+
+ if (sk != NULL) {
+ if (!sk->dead)
+ sk->data_ready(sk, skb->len);
+ } else {
+ kfree_skb(skb);
+ }
+
+ return 0;
+}
+
+/*
+ * Receive an AX.25 frame via a SLIP interface.
+ */
+int ax25_kiss_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *ptype)
+{
+ skb->sk = NULL; /* Initially we don't know who it's for */
+ skb->destructor = NULL; /* Who initializes this, dammit?! */
+
+ if ((*skb->data & 0x0F) != 0) {
+ kfree_skb(skb); /* Not a KISS data frame */
+ return 0;
+ }
+
+ skb_pull(skb, AX25_KISS_HEADER_LEN); /* Remove the KISS byte */
+
+ return ax25_rcv(skb, dev, (ax25_address *)dev->dev_addr, ptype);
+}
+
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_ip.c b/uClinux-2.4.31-uc0/net/ax25/ax25_ip.c
new file mode 100644
index 0000000..6717302
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_ip.c
@@ -0,0 +1,222 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * AX.25 036 Jonathan(G4KLX) Split from af_ax25.c.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/termios.h> /* For TIOCINQ/OUTQ */
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/netfilter.h>
+#include <linux/sysctl.h>
+#include <net/ip.h>
+#include <net/arp.h>
+
+/*
+ * IP over AX.25 encapsulation.
+ */
+
+/*
+ * Shove an AX.25 UI header on an IP packet and handle ARP
+ */
+
+#ifdef CONFIG_INET
+
+int ax25_encapsulate(struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned len)
+{
+ unsigned char *buff;
+
+ /* they sometimes come back to us... */
+ if (type == ETH_P_AX25)
+ return 0;
+
+ /* header is an AX.25 UI frame from us to them */
+ buff = skb_push(skb, AX25_HEADER_LEN);
+ *buff++ = 0x00; /* KISS DATA */
+
+ if (daddr != NULL)
+ memcpy(buff, daddr, dev->addr_len); /* Address specified */
+
+ buff[6] &= ~AX25_CBIT;
+ buff[6] &= ~AX25_EBIT;
+ buff[6] |= AX25_SSSID_SPARE;
+ buff += AX25_ADDR_LEN;
+
+ if (saddr != NULL)
+ memcpy(buff, saddr, dev->addr_len);
+ else
+ memcpy(buff, dev->dev_addr, dev->addr_len);
+
+ buff[6] &= ~AX25_CBIT;
+ buff[6] |= AX25_EBIT;
+ buff[6] |= AX25_SSSID_SPARE;
+ buff += AX25_ADDR_LEN;
+
+ *buff++ = AX25_UI; /* UI */
+
+ /* Append a suitable AX.25 PID */
+ switch (type) {
+ case ETH_P_IP:
+ *buff++ = AX25_P_IP;
+ break;
+ case ETH_P_ARP:
+ *buff++ = AX25_P_ARP;
+ break;
+ default:
+ printk(KERN_ERR "AX.25: ax25_encapsulate - wrong protocol type 0x%2.2x\n", type);
+ *buff++ = 0;
+ break;
+ }
+
+ if (daddr != NULL)
+ return AX25_HEADER_LEN;
+
+ return -AX25_HEADER_LEN; /* Unfinished header */
+}
+
+int ax25_rebuild_header(struct sk_buff *skb)
+{
+ struct sk_buff *ourskb;
+ unsigned char *bp = skb->data;
+ struct net_device *dev;
+ ax25_address *src, *dst;
+ ax25_route *route;
+ ax25_dev *ax25_dev;
+
+ dst = (ax25_address *)(bp + 1);
+ src = (ax25_address *)(bp + 8);
+
+ if (arp_find(bp + 1, skb))
+ return 1;
+
+ route = ax25_rt_find_route(dst, NULL);
+ dev = route->dev;
+
+ if (dev == NULL)
+ dev = skb->dev;
+
+ if ((ax25_dev = ax25_dev_ax25dev(dev)) == NULL)
+ return 1;
+
+ if (bp[16] == AX25_P_IP) {
+ if (route->ip_mode == 'V' || (route->ip_mode == ' ' && ax25_dev->values[AX25_VALUES_IPDEFMODE])) {
+ /*
+ * We copy the buffer and release the original thereby
+ * keeping it straight
+ *
+ * Note: we report 1 back so the caller will
+ * not feed the frame direct to the physical device
+ * We don't want that to happen. (It won't be upset
+ * as we have pulled the frame from the queue by
+ * freeing it).
+ *
+ * NB: TCP modifies buffers that are still
+ * on a device queue, thus we use skb_copy()
+ * instead of using skb_clone() unless this
+ * gets fixed.
+ */
+
+ ax25_address src_c;
+ ax25_address dst_c;
+
+ if ((ourskb = skb_copy(skb, GFP_ATOMIC)) == NULL) {
+ kfree_skb(skb);
+ return 1;
+ }
+
+ if (skb->sk != NULL)
+ skb_set_owner_w(ourskb, skb->sk);
+
+ kfree_skb(skb);
+ /* dl9sau: bugfix
+ * after kfree_skb(), dst and src which were pointer
+ * to bp which is part of skb->data would not be valid
+ * anymore hope that after skb_pull(ourskb, ..) our
+ * dsc_c and src_c will not become invalid
+ */
+ bp = ourskb->data;
+ dst_c = *(ax25_address *)(bp + 1);
+ src_c = *(ax25_address *)(bp + 8);
+
+ skb_pull(ourskb, AX25_HEADER_LEN - 1); /* Keep PID */
+ ourskb->nh.raw = ourskb->data;
+
+ ax25_send_frame(ourskb, ax25_dev->values[AX25_VALUES_PACLEN], &src_c,
+&dst_c, route->digipeat, dev);
+
+ return 1;
+ }
+ }
+
+ bp[7] &= ~AX25_CBIT;
+ bp[7] &= ~AX25_EBIT;
+ bp[7] |= AX25_SSSID_SPARE;
+
+ bp[14] &= ~AX25_CBIT;
+ bp[14] |= AX25_EBIT;
+ bp[14] |= AX25_SSSID_SPARE;
+
+ skb_pull(skb, AX25_KISS_HEADER_LEN);
+
+ if (route->digipeat != NULL) {
+ if ((ourskb = ax25_rt_build_path(skb, src, dst, route->digipeat)) == NULL) {
+ kfree_skb(skb);
+ return 1;
+ }
+
+ skb = ourskb;
+ }
+
+ skb->dev = dev;
+
+ ax25_queue_xmit(skb);
+
+ return 1;
+}
+
+#else /* INET */
+
+int ax25_encapsulate(struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned len)
+{
+ return -AX25_HEADER_LEN;
+}
+
+int ax25_rebuild_header(struct sk_buff *skb)
+{
+ return 1;
+}
+
+#endif
+
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_out.c b/uClinux-2.4.31-uc0/net/ax25/ax25_out.c
new file mode 100644
index 0000000..0fefb1b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_out.c
@@ -0,0 +1,409 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Most of this code is based on the SDL diagrams published in the 7th
+ * ARRL Computer Networking Conference papers. The diagrams have mistakes
+ * in them, but are mostly correct. Before you modify the code could you
+ * read the SDL diagrams as the code is not obvious and probably very
+ * easy to break;
+ *
+ * History
+ * AX.25 028a Jonathan(G4KLX) New state machine based on SDL diagrams.
+ * AX.25 029 Alan(GW4PTS) Switched to KA9Q constant names.
+ * Jonathan(G4KLX) Only poll when window is full.
+ * AX.25 030 Jonathan(G4KLX) Added fragmentation to ax25_output.
+ * Added support for extended AX.25.
+ * AX.25 031 Joerg(DL1BKE) Added DAMA support
+ * Joerg(DL1BKE) Modified fragmenter to fragment vanilla
+ * AX.25 I-Frames. Added PACLEN parameter.
+ * Joerg(DL1BKE) Fixed a problem with buffer allocation
+ * for fragments.
+ * AX.25 037 Jonathan(G4KLX) New timer architecture.
+ * Joerg(DL1BKE) Fixed DAMA Slave mode: will work
+ * on non-DAMA interfaces like AX25L2V2
+ * again (this behaviour is _required_).
+ * Joerg(DL1BKE) ax25_check_iframes_acked() returns a
+ * value now (for DAMA n2count handling)
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+
+ax25_cb *ax25_send_frame(struct sk_buff *skb, int paclen, ax25_address *src, ax25_address *dest, ax25_digi *digi, struct net_device *dev)
+{
+ ax25_dev *ax25_dev;
+ ax25_cb *ax25;
+
+ /*
+ * Take the default packet length for the device if zero is
+ * specified.
+ */
+ if (paclen == 0) {
+ if ((ax25_dev = ax25_dev_ax25dev(dev)) == NULL)
+ return NULL;
+
+ paclen = ax25_dev->values[AX25_VALUES_PACLEN];
+ }
+
+ /*
+ * Look for an existing connection.
+ */
+ if ((ax25 = ax25_find_cb(src, dest, digi, dev)) != NULL) {
+ ax25_output(ax25, paclen, skb);
+ return ax25; /* It already existed */
+ }
+
+ if ((ax25_dev = ax25_dev_ax25dev(dev)) == NULL)
+ return NULL;
+
+ if ((ax25 = ax25_create_cb()) == NULL)
+ return NULL;
+
+ ax25_fillin_cb(ax25, ax25_dev);
+
+ ax25->source_addr = *src;
+ ax25->dest_addr = *dest;
+
+ if (digi != NULL) {
+ if ((ax25->digipeat = kmalloc(sizeof(ax25_digi), GFP_ATOMIC)) == NULL) {
+ ax25_free_cb(ax25);
+ return NULL;
+ }
+ memcpy(ax25->digipeat, digi, sizeof(ax25_digi));
+ }
+
+ switch (ax25->ax25_dev->values[AX25_VALUES_PROTOCOL]) {
+ case AX25_PROTO_STD_SIMPLEX:
+ case AX25_PROTO_STD_DUPLEX:
+ ax25_std_establish_data_link(ax25);
+ break;
+
+#ifdef CONFIG_AX25_DAMA_SLAVE
+ case AX25_PROTO_DAMA_SLAVE:
+ if (ax25_dev->dama.slave)
+ ax25_ds_establish_data_link(ax25);
+ else
+ ax25_std_establish_data_link(ax25);
+ break;
+#endif
+ }
+
+ ax25_insert_socket(ax25);
+
+ ax25->state = AX25_STATE_1;
+
+ ax25_start_heartbeat(ax25);
+
+ ax25_output(ax25, paclen, skb);
+
+ return ax25; /* We had to create it */
+}
+
+/*
+ * All outgoing AX.25 I frames pass via this routine. Therefore this is
+ * where the fragmentation of frames takes place. If fragment is set to
+ * zero then we are not allowed to do fragmentation, even if the frame
+ * is too large.
+ */
+void ax25_output(ax25_cb *ax25, int paclen, struct sk_buff *skb)
+{
+ struct sk_buff *skbn;
+ unsigned char *p;
+ int frontlen, len, fragno, ka9qfrag, first = 1;
+ unsigned long flags;
+
+ if ((skb->len - 1) > paclen) {
+ if (*skb->data == AX25_P_TEXT) {
+ skb_pull(skb, 1); /* skip PID */
+ ka9qfrag = 0;
+ } else {
+ paclen -= 2; /* Allow for fragment control info */
+ ka9qfrag = 1;
+ }
+
+ fragno = skb->len / paclen;
+ if (skb->len % paclen == 0) fragno--;
+
+ frontlen = skb_headroom(skb); /* Address space + CTRL */
+
+ while (skb->len > 0) {
+ save_flags(flags);
+ cli();
+
+ if ((skbn = alloc_skb(paclen + 2 + frontlen, GFP_ATOMIC)) == NULL) {
+ restore_flags(flags);
+ printk(KERN_CRIT "AX.25: ax25_output - out of memory\n");
+ return;
+ }
+
+ if (skb->sk != NULL)
+ skb_set_owner_w(skbn, skb->sk);
+
+ restore_flags(flags);
+
+ len = (paclen > skb->len) ? skb->len : paclen;
+
+ if (ka9qfrag == 1) {
+ skb_reserve(skbn, frontlen + 2);
+ skbn->nh.raw = skbn->data + (skb->nh.raw - skb->data);
+ memcpy(skb_put(skbn, len), skb->data, len);
+ p = skb_push(skbn, 2);
+
+ *p++ = AX25_P_SEGMENT;
+
+ *p = fragno--;
+ if (first) {
+ *p |= AX25_SEG_FIRST;
+ first = 0;
+ }
+ } else {
+ skb_reserve(skbn, frontlen + 1);
+ skbn->nh.raw = skbn->data + (skb->nh.raw - skb->data);
+ memcpy(skb_put(skbn, len), skb->data, len);
+ p = skb_push(skbn, 1);
+ *p = AX25_P_TEXT;
+ }
+
+ skb_pull(skb, len);
+ skb_queue_tail(&ax25->write_queue, skbn); /* Throw it on the queue */
+ }
+
+ kfree_skb(skb);
+ } else {
+ skb_queue_tail(&ax25->write_queue, skb); /* Throw it on the queue */
+ }
+
+ switch (ax25->ax25_dev->values[AX25_VALUES_PROTOCOL]) {
+ case AX25_PROTO_STD_SIMPLEX:
+ case AX25_PROTO_STD_DUPLEX:
+ ax25_kick(ax25);
+ break;
+
+#ifdef CONFIG_AX25_DAMA_SLAVE
+ /*
+ * A DAMA slave is _required_ to work as normal AX.25L2V2
+ * if no DAMA master is available.
+ */
+ case AX25_PROTO_DAMA_SLAVE:
+ if (!ax25->ax25_dev->dama.slave) ax25_kick(ax25);
+ break;
+#endif
+ }
+}
+
+/*
+ * This procedure is passed a buffer descriptor for an iframe. It builds
+ * the rest of the control part of the frame and then writes it out.
+ */
+static void ax25_send_iframe(ax25_cb *ax25, struct sk_buff *skb, int poll_bit)
+{
+ unsigned char *frame;
+
+ if (skb == NULL)
+ return;
+
+ skb->nh.raw = skb->data;
+
+ if (ax25->modulus == AX25_MODULUS) {
+ frame = skb_push(skb, 1);
+
+ *frame = AX25_I;
+ *frame |= (poll_bit) ? AX25_PF : 0;
+ *frame |= (ax25->vr << 5);
+ *frame |= (ax25->vs << 1);
+ } else {
+ frame = skb_push(skb, 2);
+
+ frame[0] = AX25_I;
+ frame[0] |= (ax25->vs << 1);
+ frame[1] = (poll_bit) ? AX25_EPF : 0;
+ frame[1] |= (ax25->vr << 1);
+ }
+
+ ax25_start_idletimer(ax25);
+
+ ax25_transmit_buffer(ax25, skb, AX25_COMMAND);
+}
+
+void ax25_kick(ax25_cb *ax25)
+{
+ struct sk_buff *skb, *skbn;
+ int last = 1;
+ unsigned short start, end, next;
+
+ if (ax25->state != AX25_STATE_3 && ax25->state != AX25_STATE_4)
+ return;
+
+ if (ax25->condition & AX25_COND_PEER_RX_BUSY)
+ return;
+
+ if (skb_peek(&ax25->write_queue) == NULL)
+ return;
+
+ start = (skb_peek(&ax25->ack_queue) == NULL) ? ax25->va : ax25->vs;
+ end = (ax25->va + ax25->window) % ax25->modulus;
+
+ if (start == end)
+ return;
+
+ ax25->vs = start;
+
+ /*
+ * Transmit data until either we're out of data to send or
+ * the window is full. Send a poll on the final I frame if
+ * the window is filled.
+ */
+
+ /*
+ * Dequeue the frame and copy it.
+ */
+ skb = skb_dequeue(&ax25->write_queue);
+
+ do {
+ if ((skbn = skb_clone(skb, GFP_ATOMIC)) == NULL) {
+ skb_queue_head(&ax25->write_queue, skb);
+ break;
+ }
+
+ if (skb->sk != NULL)
+ skb_set_owner_w(skbn, skb->sk);
+
+ next = (ax25->vs + 1) % ax25->modulus;
+ last = (next == end);
+
+ /*
+ * Transmit the frame copy.
+ * bke 960114: do not set the Poll bit on the last frame
+ * in DAMA mode.
+ */
+ switch (ax25->ax25_dev->values[AX25_VALUES_PROTOCOL]) {
+ case AX25_PROTO_STD_SIMPLEX:
+ case AX25_PROTO_STD_DUPLEX:
+ ax25_send_iframe(ax25, skbn, (last) ? AX25_POLLON : AX25_POLLOFF);
+ break;
+
+#ifdef CONFIG_AX25_DAMA_SLAVE
+ case AX25_PROTO_DAMA_SLAVE:
+ ax25_send_iframe(ax25, skbn, AX25_POLLOFF);
+ break;
+#endif
+ }
+
+ ax25->vs = next;
+
+ /*
+ * Requeue the original data frame.
+ */
+ skb_queue_tail(&ax25->ack_queue, skb);
+
+ } while (!last && (skb = skb_dequeue(&ax25->write_queue)) != NULL);
+
+ ax25->condition &= ~AX25_COND_ACK_PENDING;
+
+ if (!ax25_t1timer_running(ax25)) {
+ ax25_stop_t3timer(ax25);
+ ax25_calculate_t1(ax25);
+ ax25_start_t1timer(ax25);
+ }
+}
+
+void ax25_transmit_buffer(ax25_cb *ax25, struct sk_buff *skb, int type)
+{
+ struct sk_buff *skbn;
+ unsigned char *ptr;
+ int headroom;
+
+ if (ax25->ax25_dev == NULL) {
+ ax25_disconnect(ax25, ENETUNREACH);
+ return;
+ }
+
+ headroom = ax25_addr_size(ax25->digipeat);
+
+ if (skb_headroom(skb) < headroom) {
+ if ((skbn = skb_realloc_headroom(skb, headroom)) == NULL) {
+ printk(KERN_CRIT "AX.25: ax25_transmit_buffer - out of memory\n");
+ kfree_skb(skb);
+ return;
+ }
+
+ if (skb->sk != NULL)
+ skb_set_owner_w(skbn, skb->sk);
+
+ kfree_skb(skb);
+ skb = skbn;
+ }
+
+ ptr = skb_push(skb, headroom);
+
+ ax25_addr_build(ptr, &ax25->source_addr, &ax25->dest_addr, ax25->digipeat, type, ax25->modulus);
+
+ skb->dev = ax25->ax25_dev->dev;
+
+ ax25_queue_xmit(skb);
+}
+
+/*
+ * A small shim to dev_queue_xmit to add the KISS control byte, and do
+ * any packet forwarding in operation.
+ */
+void ax25_queue_xmit(struct sk_buff *skb)
+{
+ unsigned char *ptr;
+
+ skb->protocol = htons(ETH_P_AX25);
+ skb->dev = ax25_fwd_dev(skb->dev);
+
+ ptr = skb_push(skb, 1);
+ *ptr = 0x00; /* KISS */
+
+ dev_queue_xmit(skb);
+}
+
+int ax25_check_iframes_acked(ax25_cb *ax25, unsigned short nr)
+{
+ if (ax25->vs == nr) {
+ ax25_frames_acked(ax25, nr);
+ ax25_calculate_rtt(ax25);
+ ax25_stop_t1timer(ax25);
+ ax25_start_t3timer(ax25);
+ return 1;
+ } else {
+ if (ax25->va != nr) {
+ ax25_frames_acked(ax25, nr);
+ ax25_calculate_t1(ax25);
+ ax25_start_t1timer(ax25);
+ return 1;
+ }
+ }
+ return 0;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_route.c b/uClinux-2.4.31-uc0/net/ax25/ax25_route.c
new file mode 100644
index 0000000..254ff36
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_route.c
@@ -0,0 +1,452 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Other kernels modules in this kit are generally BSD derived. See the copyright headers.
+ *
+ *
+ * History
+ * AX.25 020 Jonathan(G4KLX) First go.
+ * AX.25 022 Jonathan(G4KLX) Added the actual meat to this - we now have a nice heard list.
+ * AX.25 025 Alan(GW4PTS) First cut at autobinding by route scan.
+ * AX.25 028b Jonathan(G4KLX) Extracted AX25 control block from the
+ * sock structure. Device removal now
+ * removes the heard structure.
+ * AX.25 029 Steven(GW7RRM) Added /proc information for uid/callsign mapping.
+ * Jonathan(G4KLX) Handling of IP mode in the routing list and /proc entry.
+ * AX.25 030 Jonathan(G4KLX) Added digi-peaters to routing table, and
+ * ioctls to manipulate them. Added port
+ * configuration.
+ * AX.25 031 Jonathan(G4KLX) Added concept of default route.
+ * Joerg(DL1BKE) ax25_rt_build_path() find digipeater list and device by
+ * destination call. Needed for IP routing via digipeater
+ * Jonathan(G4KLX) Added routing for IP datagram packets.
+ * Joerg(DL1BKE) Changed routing for IP datagram and VC to use a default
+ * route if available. Does not overwrite default routes
+ * on route-table overflow anymore.
+ * Joerg(DL1BKE) Fixed AX.25 routing of IP datagram and VC, new ioctl()
+ * "SIOCAX25OPTRT" to set IP mode and a 'permanent' flag
+ * on routes.
+ * AX.25 033 Jonathan(G4KLX) Remove auto-router.
+ * Joerg(DL1BKE) Moved BPQ Ethernet driver to separate device.
+ * AX.25 035 Frederic(F1OAT) Support for pseudo-digipeating.
+ * Jonathan(G4KLX) Support for packet forwarding.
+ * Arnaldo C. Melo s/suser/capable/
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+
+static ax25_route *ax25_route_list;
+
+static ax25_route *ax25_find_route(ax25_address *, struct net_device *);
+
+/*
+ * small macro to drop non-digipeated digipeaters and reverse path
+ */
+static inline void ax25_route_invert(ax25_digi *in, ax25_digi *out)
+{
+ int k;
+
+ for (k = 0; k < in->ndigi; k++)
+ if (!in->repeated[k])
+ break;
+
+ in->ndigi = k;
+
+ ax25_digi_invert(in, out);
+}
+
+void ax25_rt_device_down(struct net_device *dev)
+{
+ ax25_route *s, *t, *ax25_rt = ax25_route_list;
+
+ while (ax25_rt != NULL) {
+ s = ax25_rt;
+ ax25_rt = ax25_rt->next;
+
+ if (s->dev == dev) {
+ if (ax25_route_list == s) {
+ ax25_route_list = s->next;
+ if (s->digipeat != NULL)
+ kfree(s->digipeat);
+ kfree(s);
+ } else {
+ for (t = ax25_route_list; t != NULL; t = t->next) {
+ if (t->next == s) {
+ t->next = s->next;
+ if (s->digipeat != NULL)
+ kfree(s->digipeat);
+ kfree(s);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+int ax25_rt_ioctl(unsigned int cmd, void *arg)
+{
+ unsigned long flags;
+ ax25_route *s, *t, *ax25_rt;
+ struct ax25_routes_struct route;
+ struct ax25_route_opt_struct rt_option;
+ ax25_dev *ax25_dev;
+ int i;
+
+ switch (cmd) {
+ case SIOCADDRT:
+ if (copy_from_user(&route, arg, sizeof(route)))
+ return -EFAULT;
+ if ((ax25_dev = ax25_addr_ax25dev(&route.port_addr)) == NULL)
+ return -EINVAL;
+ if (route.digi_count > AX25_MAX_DIGIS)
+ return -EINVAL;
+ for (ax25_rt = ax25_route_list; ax25_rt != NULL; ax25_rt = ax25_rt->next) {
+ if (ax25cmp(&ax25_rt->callsign, &route.dest_addr) == 0 && ax25_rt->dev == ax25_dev->dev) {
+ if (ax25_rt->digipeat != NULL) {
+ kfree(ax25_rt->digipeat);
+ ax25_rt->digipeat = NULL;
+ }
+ if (route.digi_count != 0) {
+ if ((ax25_rt->digipeat = kmalloc(sizeof(ax25_digi), GFP_ATOMIC)) == NULL)
+ return -ENOMEM;
+ ax25_rt->digipeat->lastrepeat = -1;
+ ax25_rt->digipeat->ndigi = route.digi_count;
+ for (i = 0; i < route.digi_count; i++) {
+ ax25_rt->digipeat->repeated[i] = 0;
+ ax25_rt->digipeat->calls[i] = route.digi_addr[i];
+ }
+ }
+ return 0;
+ }
+ }
+ if ((ax25_rt = kmalloc(sizeof(ax25_route), GFP_ATOMIC)) == NULL)
+ return -ENOMEM;
+ ax25_rt->callsign = route.dest_addr;
+ ax25_rt->dev = ax25_dev->dev;
+ ax25_rt->digipeat = NULL;
+ ax25_rt->ip_mode = ' ';
+ if (route.digi_count != 0) {
+ if ((ax25_rt->digipeat = kmalloc(sizeof(ax25_digi), GFP_ATOMIC)) == NULL) {
+ kfree(ax25_rt);
+ return -ENOMEM;
+ }
+ ax25_rt->digipeat->lastrepeat = -1;
+ ax25_rt->digipeat->ndigi = route.digi_count;
+ for (i = 0; i < route.digi_count; i++) {
+ ax25_rt->digipeat->repeated[i] = 0;
+ ax25_rt->digipeat->calls[i] = route.digi_addr[i];
+ }
+ }
+ save_flags(flags); cli();
+ ax25_rt->next = ax25_route_list;
+ ax25_route_list = ax25_rt;
+ restore_flags(flags);
+ break;
+
+ case SIOCDELRT:
+ if (copy_from_user(&route, arg, sizeof(route)))
+ return -EFAULT;
+ if ((ax25_dev = ax25_addr_ax25dev(&route.port_addr)) == NULL)
+ return -EINVAL;
+ ax25_rt = ax25_route_list;
+ while (ax25_rt != NULL) {
+ s = ax25_rt;
+ ax25_rt = ax25_rt->next;
+ if (s->dev == ax25_dev->dev && ax25cmp(&route.dest_addr, &s->callsign) == 0) {
+ if (ax25_route_list == s) {
+ ax25_route_list = s->next;
+ if (s->digipeat != NULL)
+ kfree(s->digipeat);
+ kfree(s);
+ } else {
+ for (t = ax25_route_list; t != NULL; t = t->next) {
+ if (t->next == s) {
+ t->next = s->next;
+ if (s->digipeat != NULL)
+ kfree(s->digipeat);
+ kfree(s);
+ break;
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case SIOCAX25OPTRT:
+ if (copy_from_user(&rt_option, arg, sizeof(rt_option)))
+ return -EFAULT;
+ if ((ax25_dev = ax25_addr_ax25dev(&rt_option.port_addr)) == NULL)
+ return -EINVAL;
+ for (ax25_rt = ax25_route_list; ax25_rt != NULL; ax25_rt = ax25_rt->next) {
+ if (ax25_rt->dev == ax25_dev->dev && ax25cmp(&rt_option.dest_addr, &ax25_rt->callsign) == 0) {
+ switch (rt_option.cmd) {
+ case AX25_SET_RT_IPMODE:
+ switch (rt_option.arg) {
+ case ' ':
+ case 'D':
+ case 'V':
+ ax25_rt->ip_mode = rt_option.arg;
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+ }
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int ax25_rt_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ ax25_route *ax25_rt;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+ char *callsign;
+ int i;
+
+ cli();
+
+ len += sprintf(buffer, "callsign dev mode digipeaters\n");
+
+ for (ax25_rt = ax25_route_list; ax25_rt != NULL; ax25_rt = ax25_rt->next) {
+ if (ax25cmp(&ax25_rt->callsign, &null_ax25_address) == 0)
+ callsign = "default";
+ else
+ callsign = ax2asc(&ax25_rt->callsign);
+ len += sprintf(buffer + len, "%-9s %-4s",
+ callsign,
+ ax25_rt->dev ? ax25_rt->dev->name : "???");
+
+ switch (ax25_rt->ip_mode) {
+ case 'V':
+ len += sprintf(buffer + len, " vc");
+ break;
+ case 'D':
+ len += sprintf(buffer + len, " dg");
+ break;
+ default:
+ len += sprintf(buffer + len, " *");
+ break;
+ }
+
+ if (ax25_rt->digipeat != NULL)
+ for (i = 0; i < ax25_rt->digipeat->ndigi; i++)
+ len += sprintf(buffer + len, " %s", ax2asc(&ax25_rt->digipeat->calls[i]));
+
+ len += sprintf(buffer + len, "\n");
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ break;
+ }
+
+ sti();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return len;
+}
+
+/*
+ * Find AX.25 route
+ */
+static ax25_route *ax25_find_route(ax25_address *addr, struct net_device *dev)
+{
+ ax25_route *ax25_spe_rt = NULL;
+ ax25_route *ax25_def_rt = NULL;
+ ax25_route *ax25_rt;
+
+ /*
+ * Bind to the physical interface we heard them on, or the default
+ * route if none is found;
+ */
+ for (ax25_rt = ax25_route_list; ax25_rt != NULL; ax25_rt = ax25_rt->next) {
+ if (dev == NULL) {
+ if (ax25cmp(&ax25_rt->callsign, addr) == 0 && ax25_rt->dev != NULL)
+ ax25_spe_rt = ax25_rt;
+ if (ax25cmp(&ax25_rt->callsign, &null_ax25_address) == 0 && ax25_rt->dev != NULL)
+ ax25_def_rt = ax25_rt;
+ } else {
+ if (ax25cmp(&ax25_rt->callsign, addr) == 0 && ax25_rt->dev == dev)
+ ax25_spe_rt = ax25_rt;
+ if (ax25cmp(&ax25_rt->callsign, &null_ax25_address) == 0 && ax25_rt->dev == dev)
+ ax25_def_rt = ax25_rt;
+ }
+ }
+
+ if (ax25_spe_rt != NULL)
+ return ax25_spe_rt;
+
+ return ax25_def_rt;
+}
+
+/*
+ * Adjust path: If you specify a default route and want to connect
+ * a target on the digipeater path but w/o having a special route
+ * set before, the path has to be truncated from your target on.
+ */
+static inline void ax25_adjust_path(ax25_address *addr, ax25_digi *digipeat)
+{
+ int k;
+
+ for (k = 0; k < digipeat->ndigi; k++) {
+ if (ax25cmp(addr, &digipeat->calls[k]) == 0)
+ break;
+ }
+
+ digipeat->ndigi = k;
+}
+
+
+/*
+ * Find which interface to use.
+ */
+int ax25_rt_autobind(ax25_cb *ax25, ax25_address *addr)
+{
+ ax25_route *ax25_rt;
+ ax25_address *call;
+
+ if ((ax25_rt = ax25_find_route(addr, NULL)) == NULL)
+ return -EHOSTUNREACH;
+
+ if ((ax25->ax25_dev = ax25_dev_ax25dev(ax25_rt->dev)) == NULL)
+ return -EHOSTUNREACH;
+
+ if ((call = ax25_findbyuid(current->euid)) == NULL) {
+ if (ax25_uid_policy && !capable(CAP_NET_BIND_SERVICE))
+ return -EPERM;
+ call = (ax25_address *)ax25->ax25_dev->dev->dev_addr;
+ }
+
+ ax25->source_addr = *call;
+
+ if (ax25_rt->digipeat != NULL) {
+ if ((ax25->digipeat = kmalloc(sizeof(ax25_digi), GFP_ATOMIC)) == NULL)
+ return -ENOMEM;
+ memcpy(ax25->digipeat, ax25_rt->digipeat, sizeof(ax25_digi));
+ ax25_adjust_path(addr, ax25->digipeat);
+ }
+
+ if (ax25->sk != NULL)
+ ax25->sk->zapped = 0;
+
+ return 0;
+}
+
+/*
+ * dl1bke 960117: build digipeater path
+ * dl1bke 960301: use the default route if it exists
+ */
+ax25_route *ax25_rt_find_route(ax25_address *addr, struct net_device *dev)
+{
+ static ax25_route route;
+ ax25_route *ax25_rt;
+
+ if ((ax25_rt = ax25_find_route(addr, dev)) == NULL) {
+ route.next = NULL;
+ route.callsign = *addr;
+ route.dev = dev;
+ route.digipeat = NULL;
+ route.ip_mode = ' ';
+ return &route;
+ }
+
+ return ax25_rt;
+}
+
+struct sk_buff *ax25_rt_build_path(struct sk_buff *skb, ax25_address *src, ax25_address *dest, ax25_digi *digi)
+{
+ struct sk_buff *skbn;
+ unsigned char *bp;
+ int len;
+
+ len = digi->ndigi * AX25_ADDR_LEN;
+
+ if (skb_headroom(skb) < len) {
+ if ((skbn = skb_realloc_headroom(skb, len)) == NULL) {
+ printk(KERN_CRIT "AX.25: ax25_dg_build_path - out of memory\n");
+ return NULL;
+ }
+
+ if (skb->sk != NULL)
+ skb_set_owner_w(skbn, skb->sk);
+
+ kfree_skb(skb);
+
+ skb = skbn;
+ }
+
+ bp = skb_push(skb, len);
+
+ ax25_addr_build(bp, src, dest, digi, AX25_COMMAND, AX25_MODULUS);
+
+ return skb;
+}
+
+/*
+ * Free all memory associated with routing structures.
+ */
+void __exit ax25_rt_free(void)
+{
+ ax25_route *s, *ax25_rt = ax25_route_list;
+
+ while (ax25_rt != NULL) {
+ s = ax25_rt;
+ ax25_rt = ax25_rt->next;
+
+ if (s->digipeat != NULL)
+ kfree(s->digipeat);
+
+ kfree(s);
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_std_in.c b/uClinux-2.4.31-uc0/net/ax25/ax25_std_in.c
new file mode 100644
index 0000000..d1cfc3f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_std_in.c
@@ -0,0 +1,467 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Most of this code is based on the SDL diagrams published in the 7th
+ * ARRL Computer Networking Conference papers. The diagrams have mistakes
+ * in them, but are mostly correct. Before you modify the code could you
+ * read the SDL diagrams as the code is not obvious and probably very
+ * easy to break;
+ *
+ * History
+ * AX.25 028a Jonathan(G4KLX) New state machine based on SDL diagrams.
+ * AX.25 028b Jonathan(G4KLX) Extracted AX25 control block from
+ * the sock structure.
+ * AX.25 029 Alan(GW4PTS) Switched to KA9Q constant names.
+ * Jonathan(G4KLX) Added IP mode registration.
+ * AX.25 030 Jonathan(G4KLX) Added AX.25 fragment reception.
+ * Upgraded state machine for SABME.
+ * Added arbitrary protocol id support.
+ * AX.25 031 Joerg(DL1BKE) Added DAMA support
+ * HaJo(DD8NE) Added Idle Disc Timer T5
+ * Joerg(DL1BKE) Renamed it to "IDLE" with a slightly
+ * different behaviour. Fixed defrag
+ * routine (I hope)
+ * AX.25 032 Darryl(G7LED) AX.25 segmentation fixed.
+ * AX.25 033 Jonathan(G4KLX) Remove auto-router.
+ * Modularisation changes.
+ * AX.25 035 Hans(PE1AYX) Fixed interface to IP layer.
+ * AX.25 036 Jonathan(G4KLX) Cloned from ax25_in.c.
+ * AX.25 037 Jonathan(G4KLX) New timer architecture.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/ip.h> /* For ip_rcv */
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+
+/*
+ * State machine for state 1, Awaiting Connection State.
+ * The handling of the timer(s) is in file ax25_std_timer.c.
+ * Handling of state 0 and connection release is in ax25.c.
+ */
+static int ax25_std_state1_machine(ax25_cb *ax25, struct sk_buff *skb, int frametype, int pf, int type)
+{
+ switch (frametype) {
+ case AX25_SABM:
+ ax25->modulus = AX25_MODULUS;
+ ax25->window = ax25->ax25_dev->values[AX25_VALUES_WINDOW];
+ ax25_send_control(ax25, AX25_UA, pf, AX25_RESPONSE);
+ break;
+
+ case AX25_SABME:
+ ax25->modulus = AX25_EMODULUS;
+ ax25->window = ax25->ax25_dev->values[AX25_VALUES_EWINDOW];
+ ax25_send_control(ax25, AX25_UA, pf, AX25_RESPONSE);
+ break;
+
+ case AX25_DISC:
+ ax25_send_control(ax25, AX25_DM, pf, AX25_RESPONSE);
+ break;
+
+ case AX25_UA:
+ if (pf) {
+ ax25_calculate_rtt(ax25);
+ ax25_stop_t1timer(ax25);
+ ax25_start_t3timer(ax25);
+ ax25_start_idletimer(ax25);
+ ax25->vs = 0;
+ ax25->va = 0;
+ ax25->vr = 0;
+ ax25->state = AX25_STATE_3;
+ ax25->n2count = 0;
+ if (ax25->sk != NULL) {
+ ax25->sk->state = TCP_ESTABLISHED;
+ /* For WAIT_SABM connections we will produce an accept ready socket here */
+ if (!ax25->sk->dead)
+ ax25->sk->state_change(ax25->sk);
+ }
+ }
+ break;
+
+ case AX25_DM:
+ if (pf) {
+ if (ax25->modulus == AX25_MODULUS) {
+ ax25_disconnect(ax25, ECONNREFUSED);
+ } else {
+ ax25->modulus = AX25_MODULUS;
+ ax25->window = ax25->ax25_dev->values[AX25_VALUES_WINDOW];
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * State machine for state 2, Awaiting Release State.
+ * The handling of the timer(s) is in file ax25_std_timer.c
+ * Handling of state 0 and connection release is in ax25.c.
+ */
+static int ax25_std_state2_machine(ax25_cb *ax25, struct sk_buff *skb, int frametype, int pf, int type)
+{
+ switch (frametype) {
+ case AX25_SABM:
+ case AX25_SABME:
+ ax25_send_control(ax25, AX25_DM, pf, AX25_RESPONSE);
+ break;
+
+ case AX25_DISC:
+ ax25_send_control(ax25, AX25_UA, pf, AX25_RESPONSE);
+ ax25_disconnect(ax25, 0);
+ break;
+
+ case AX25_DM:
+ case AX25_UA:
+ if (pf) ax25_disconnect(ax25, 0);
+ break;
+
+ case AX25_I:
+ case AX25_REJ:
+ case AX25_RNR:
+ case AX25_RR:
+ if (pf) ax25_send_control(ax25, AX25_DM, AX25_POLLON, AX25_RESPONSE);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * State machine for state 3, Connected State.
+ * The handling of the timer(s) is in file ax25_std_timer.c
+ * Handling of state 0 and connection release is in ax25.c.
+ */
+static int ax25_std_state3_machine(ax25_cb *ax25, struct sk_buff *skb, int frametype, int ns, int nr, int pf, int type)
+{
+ int queued = 0;
+
+ switch (frametype) {
+ case AX25_SABM:
+ case AX25_SABME:
+ if (frametype == AX25_SABM) {
+ ax25->modulus = AX25_MODULUS;
+ ax25->window = ax25->ax25_dev->values[AX25_VALUES_WINDOW];
+ } else {
+ ax25->modulus = AX25_EMODULUS;
+ ax25->window = ax25->ax25_dev->values[AX25_VALUES_EWINDOW];
+ }
+ ax25_send_control(ax25, AX25_UA, pf, AX25_RESPONSE);
+ ax25_stop_t1timer(ax25);
+ ax25_stop_t2timer(ax25);
+ ax25_start_t3timer(ax25);
+ ax25_start_idletimer(ax25);
+ ax25->condition = 0x00;
+ ax25->vs = 0;
+ ax25->va = 0;
+ ax25->vr = 0;
+ ax25_requeue_frames(ax25);
+ break;
+
+ case AX25_DISC:
+ ax25_send_control(ax25, AX25_UA, pf, AX25_RESPONSE);
+ ax25_disconnect(ax25, 0);
+ break;
+
+ case AX25_DM:
+ ax25_disconnect(ax25, ECONNRESET);
+ break;
+
+ case AX25_RR:
+ case AX25_RNR:
+ if (frametype == AX25_RR)
+ ax25->condition &= ~AX25_COND_PEER_RX_BUSY;
+ else
+ ax25->condition |= AX25_COND_PEER_RX_BUSY;
+ if (type == AX25_COMMAND && pf)
+ ax25_std_enquiry_response(ax25);
+ if (ax25_validate_nr(ax25, nr)) {
+ ax25_check_iframes_acked(ax25, nr);
+ } else {
+ ax25_std_nr_error_recovery(ax25);
+ ax25->state = AX25_STATE_1;
+ }
+ break;
+
+ case AX25_REJ:
+ ax25->condition &= ~AX25_COND_PEER_RX_BUSY;
+ if (type == AX25_COMMAND && pf)
+ ax25_std_enquiry_response(ax25);
+ if (ax25_validate_nr(ax25, nr)) {
+ ax25_frames_acked(ax25, nr);
+ ax25_calculate_rtt(ax25);
+ ax25_stop_t1timer(ax25);
+ ax25_start_t3timer(ax25);
+ ax25_requeue_frames(ax25);
+ } else {
+ ax25_std_nr_error_recovery(ax25);
+ ax25->state = AX25_STATE_1;
+ }
+ break;
+
+ case AX25_I:
+ if (!ax25_validate_nr(ax25, nr)) {
+ ax25_std_nr_error_recovery(ax25);
+ ax25->state = AX25_STATE_1;
+ break;
+ }
+ if (ax25->condition & AX25_COND_PEER_RX_BUSY) {
+ ax25_frames_acked(ax25, nr);
+ } else {
+ ax25_check_iframes_acked(ax25, nr);
+ }
+ if (ax25->condition & AX25_COND_OWN_RX_BUSY) {
+ if (pf) ax25_std_enquiry_response(ax25);
+ break;
+ }
+ if (ns == ax25->vr) {
+ ax25->vr = (ax25->vr + 1) % ax25->modulus;
+ queued = ax25_rx_iframe(ax25, skb);
+ if (ax25->condition & AX25_COND_OWN_RX_BUSY)
+ ax25->vr = ns; /* ax25->vr - 1 */
+ ax25->condition &= ~AX25_COND_REJECT;
+ if (pf) {
+ ax25_std_enquiry_response(ax25);
+ } else {
+ if (!(ax25->condition & AX25_COND_ACK_PENDING)) {
+ ax25->condition |= AX25_COND_ACK_PENDING;
+ ax25_start_t2timer(ax25);
+ }
+ }
+ } else {
+ if (ax25->condition & AX25_COND_REJECT) {
+ if (pf) ax25_std_enquiry_response(ax25);
+ } else {
+ ax25->condition |= AX25_COND_REJECT;
+ ax25_send_control(ax25, AX25_REJ, pf, AX25_RESPONSE);
+ ax25->condition &= ~AX25_COND_ACK_PENDING;
+ }
+ }
+ break;
+
+ case AX25_FRMR:
+ case AX25_ILLEGAL:
+ ax25_std_establish_data_link(ax25);
+ ax25->state = AX25_STATE_1;
+ break;
+
+ default:
+ break;
+ }
+
+ return queued;
+}
+
+/*
+ * State machine for state 4, Timer Recovery State.
+ * The handling of the timer(s) is in file ax25_std_timer.c
+ * Handling of state 0 and connection release is in ax25.c.
+ */
+static int ax25_std_state4_machine(ax25_cb *ax25, struct sk_buff *skb, int frametype, int ns, int nr, int pf, int type)
+{
+ int queued = 0;
+
+ switch (frametype) {
+ case AX25_SABM:
+ case AX25_SABME:
+ if (frametype == AX25_SABM) {
+ ax25->modulus = AX25_MODULUS;
+ ax25->window = ax25->ax25_dev->values[AX25_VALUES_WINDOW];
+ } else {
+ ax25->modulus = AX25_EMODULUS;
+ ax25->window = ax25->ax25_dev->values[AX25_VALUES_EWINDOW];
+ }
+ ax25_send_control(ax25, AX25_UA, pf, AX25_RESPONSE);
+ ax25_stop_t1timer(ax25);
+ ax25_stop_t2timer(ax25);
+ ax25_start_t3timer(ax25);
+ ax25_start_idletimer(ax25);
+ ax25->condition = 0x00;
+ ax25->vs = 0;
+ ax25->va = 0;
+ ax25->vr = 0;
+ ax25->state = AX25_STATE_3;
+ ax25->n2count = 0;
+ ax25_requeue_frames(ax25);
+ break;
+
+ case AX25_DISC:
+ ax25_send_control(ax25, AX25_UA, pf, AX25_RESPONSE);
+ ax25_disconnect(ax25, 0);
+ break;
+
+ case AX25_DM:
+ ax25_disconnect(ax25, ECONNRESET);
+ break;
+
+ case AX25_RR:
+ case AX25_RNR:
+ if (frametype == AX25_RR)
+ ax25->condition &= ~AX25_COND_PEER_RX_BUSY;
+ else
+ ax25->condition |= AX25_COND_PEER_RX_BUSY;
+ if (type == AX25_RESPONSE && pf) {
+ ax25_stop_t1timer(ax25);
+ ax25->n2count = 0;
+ if (ax25_validate_nr(ax25, nr)) {
+ ax25_frames_acked(ax25, nr);
+ if (ax25->vs == ax25->va) {
+ ax25_start_t3timer(ax25);
+ ax25->state = AX25_STATE_3;
+ } else {
+ ax25_requeue_frames(ax25);
+ }
+ } else {
+ ax25_std_nr_error_recovery(ax25);
+ ax25->state = AX25_STATE_1;
+ }
+ break;
+ }
+ if (type == AX25_COMMAND && pf)
+ ax25_std_enquiry_response(ax25);
+ if (ax25_validate_nr(ax25, nr)) {
+ ax25_frames_acked(ax25, nr);
+ } else {
+ ax25_std_nr_error_recovery(ax25);
+ ax25->state = AX25_STATE_1;
+ }
+ break;
+
+ case AX25_REJ:
+ ax25->condition &= ~AX25_COND_PEER_RX_BUSY;
+ if (pf && type == AX25_RESPONSE) {
+ ax25_stop_t1timer(ax25);
+ ax25->n2count = 0;
+ if (ax25_validate_nr(ax25, nr)) {
+ ax25_frames_acked(ax25, nr);
+ if (ax25->vs == ax25->va) {
+ ax25_start_t3timer(ax25);
+ ax25->state = AX25_STATE_3;
+ } else {
+ ax25_requeue_frames(ax25);
+ }
+ } else {
+ ax25_std_nr_error_recovery(ax25);
+ ax25->state = AX25_STATE_1;
+ }
+ break;
+ }
+ if (type == AX25_COMMAND && pf)
+ ax25_std_enquiry_response(ax25);
+ if (ax25_validate_nr(ax25, nr)) {
+ ax25_frames_acked(ax25, nr);
+ ax25_requeue_frames(ax25);
+ } else {
+ ax25_std_nr_error_recovery(ax25);
+ ax25->state = AX25_STATE_1;
+ }
+ break;
+
+ case AX25_I:
+ if (!ax25_validate_nr(ax25, nr)) {
+ ax25_std_nr_error_recovery(ax25);
+ ax25->state = AX25_STATE_1;
+ break;
+ }
+ ax25_frames_acked(ax25, nr);
+ if (ax25->condition & AX25_COND_OWN_RX_BUSY) {
+ if (pf) ax25_std_enquiry_response(ax25);
+ break;
+ }
+ if (ns == ax25->vr) {
+ ax25->vr = (ax25->vr + 1) % ax25->modulus;
+ queued = ax25_rx_iframe(ax25, skb);
+ if (ax25->condition & AX25_COND_OWN_RX_BUSY)
+ ax25->vr = ns; /* ax25->vr - 1 */
+ ax25->condition &= ~AX25_COND_REJECT;
+ if (pf) {
+ ax25_std_enquiry_response(ax25);
+ } else {
+ if (!(ax25->condition & AX25_COND_ACK_PENDING)) {
+ ax25->condition |= AX25_COND_ACK_PENDING;
+ ax25_start_t2timer(ax25);
+ }
+ }
+ } else {
+ if (ax25->condition & AX25_COND_REJECT) {
+ if (pf) ax25_std_enquiry_response(ax25);
+ } else {
+ ax25->condition |= AX25_COND_REJECT;
+ ax25_send_control(ax25, AX25_REJ, pf, AX25_RESPONSE);
+ ax25->condition &= ~AX25_COND_ACK_PENDING;
+ }
+ }
+ break;
+
+ case AX25_FRMR:
+ case AX25_ILLEGAL:
+ ax25_std_establish_data_link(ax25);
+ ax25->state = AX25_STATE_1;
+ break;
+
+ default:
+ break;
+ }
+
+ return queued;
+}
+
+/*
+ * Higher level upcall for a LAPB frame
+ */
+int ax25_std_frame_in(ax25_cb *ax25, struct sk_buff *skb, int type)
+{
+ int queued = 0, frametype, ns, nr, pf;
+
+ frametype = ax25_decode(ax25, skb, &ns, &nr, &pf);
+
+ switch (ax25->state) {
+ case AX25_STATE_1:
+ queued = ax25_std_state1_machine(ax25, skb, frametype, pf, type);
+ break;
+ case AX25_STATE_2:
+ queued = ax25_std_state2_machine(ax25, skb, frametype, pf, type);
+ break;
+ case AX25_STATE_3:
+ queued = ax25_std_state3_machine(ax25, skb, frametype, ns, nr, pf, type);
+ break;
+ case AX25_STATE_4:
+ queued = ax25_std_state4_machine(ax25, skb, frametype, ns, nr, pf, type);
+ break;
+ }
+
+ ax25_kick(ax25);
+
+ return queued;
+}
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_std_subr.c b/uClinux-2.4.31-uc0/net/ax25/ax25_std_subr.c
new file mode 100644
index 0000000..c868e05
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_std_subr.c
@@ -0,0 +1,102 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Most of this code is based on the SDL diagrams published in the 7th
+ * ARRL Computer Networking Conference papers. The diagrams have mistakes
+ * in them, but are mostly correct. Before you modify the code could you
+ * read the SDL diagrams as the code is not obvious and probably very
+ * easy to break;
+ *
+ * History
+ * AX.25 036 Jonathan(G4KLX) Split from ax25_out.c.
+ * AX.25 037 Jonathan(G4KLX) New timer architecture.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+
+/*
+ * The following routines are taken from page 170 of the 7th ARRL Computer
+ * Networking Conference paper, as is the whole state machine.
+ */
+
+void ax25_std_nr_error_recovery(ax25_cb *ax25)
+{
+ ax25_std_establish_data_link(ax25);
+}
+
+void ax25_std_establish_data_link(ax25_cb *ax25)
+{
+ ax25->condition = 0x00;
+ ax25->n2count = 0;
+
+ if (ax25->modulus == AX25_MODULUS)
+ ax25_send_control(ax25, AX25_SABM, AX25_POLLON, AX25_COMMAND);
+ else
+ ax25_send_control(ax25, AX25_SABME, AX25_POLLON, AX25_COMMAND);
+
+ ax25_calculate_t1(ax25);
+ ax25_stop_idletimer(ax25);
+ ax25_stop_t3timer(ax25);
+ ax25_stop_t2timer(ax25);
+ ax25_start_t1timer(ax25);
+}
+
+void ax25_std_transmit_enquiry(ax25_cb *ax25)
+{
+ if (ax25->condition & AX25_COND_OWN_RX_BUSY)
+ ax25_send_control(ax25, AX25_RNR, AX25_POLLON, AX25_COMMAND);
+ else
+ ax25_send_control(ax25, AX25_RR, AX25_POLLON, AX25_COMMAND);
+
+ ax25->condition &= ~AX25_COND_ACK_PENDING;
+
+ ax25_calculate_t1(ax25);
+ ax25_start_t1timer(ax25);
+}
+
+void ax25_std_enquiry_response(ax25_cb *ax25)
+{
+ if (ax25->condition & AX25_COND_OWN_RX_BUSY)
+ ax25_send_control(ax25, AX25_RNR, AX25_POLLON, AX25_RESPONSE);
+ else
+ ax25_send_control(ax25, AX25_RR, AX25_POLLON, AX25_RESPONSE);
+
+ ax25->condition &= ~AX25_COND_ACK_PENDING;
+}
+
+void ax25_std_timeout_response(ax25_cb *ax25)
+{
+ if (ax25->condition & AX25_COND_OWN_RX_BUSY)
+ ax25_send_control(ax25, AX25_RNR, AX25_POLLOFF, AX25_RESPONSE);
+ else
+ ax25_send_control(ax25, AX25_RR, AX25_POLLOFF, AX25_RESPONSE);
+
+ ax25->condition &= ~AX25_COND_ACK_PENDING;
+}
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_std_timer.c b/uClinux-2.4.31-uc0/net/ax25/ax25_std_timer.c
new file mode 100644
index 0000000..5a2d877
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_std_timer.c
@@ -0,0 +1,170 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * AX.25 028a Jonathan(G4KLX) New state machine based on SDL diagrams.
+ * AX.25 028b Jonathan(G4KLX) Extracted AX25 control block from the
+ * sock structure.
+ * AX.25 029 Alan(GW4PTS) Switched to KA9Q constant names.
+ * AX.25 031 Joerg(DL1BKE) Added DAMA support
+ * AX.25 032 Joerg(DL1BKE) Fixed DAMA timeout bug
+ * AX.25 033 Jonathan(G4KLX) Modularisation functions.
+ * AX.25 035 Frederic(F1OAT) Support for pseudo-digipeating.
+ * AX.25 036 Jonathan(G4KLX) Split from ax25_timer.c.
+ * AX.25 037 Jonathan(G4KLX) New timer architecture.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+
+void ax25_std_heartbeat_expiry(ax25_cb *ax25)
+{
+ switch (ax25->state) {
+
+ case AX25_STATE_0:
+ /* Magic here: If we listen() and a new link dies before it
+ is accepted() it isn't 'dead' so doesn't get removed. */
+ if (ax25->sk == NULL || ax25->sk->destroy || (ax25->sk->state == TCP_LISTEN && ax25->sk->dead)) {
+ ax25_destroy_socket(ax25);
+ return;
+ }
+ break;
+
+ case AX25_STATE_3:
+ case AX25_STATE_4:
+ /*
+ * Check the state of the receive buffer.
+ */
+ if (ax25->sk != NULL) {
+ if (atomic_read(&ax25->sk->rmem_alloc) < (ax25->sk->rcvbuf / 2) &&
+ (ax25->condition & AX25_COND_OWN_RX_BUSY)) {
+ ax25->condition &= ~AX25_COND_OWN_RX_BUSY;
+ ax25->condition &= ~AX25_COND_ACK_PENDING;
+ ax25_send_control(ax25, AX25_RR, AX25_POLLOFF, AX25_RESPONSE);
+ break;
+ }
+ }
+ }
+
+ ax25_start_heartbeat(ax25);
+}
+
+void ax25_std_t2timer_expiry(ax25_cb *ax25)
+{
+ if (ax25->condition & AX25_COND_ACK_PENDING) {
+ ax25->condition &= ~AX25_COND_ACK_PENDING;
+ ax25_std_timeout_response(ax25);
+ }
+}
+
+void ax25_std_t3timer_expiry(ax25_cb *ax25)
+{
+ ax25->n2count = 0;
+ ax25_std_transmit_enquiry(ax25);
+ ax25->state = AX25_STATE_4;
+}
+
+void ax25_std_idletimer_expiry(ax25_cb *ax25)
+{
+ ax25_clear_queues(ax25);
+
+ ax25->n2count = 0;
+ ax25_send_control(ax25, AX25_DISC, AX25_POLLON, AX25_COMMAND);
+ ax25->state = AX25_STATE_2;
+
+ ax25_calculate_t1(ax25);
+ ax25_start_t1timer(ax25);
+ ax25_stop_t2timer(ax25);
+ ax25_stop_t3timer(ax25);
+
+ if (ax25->sk != NULL) {
+ ax25->sk->state = TCP_CLOSE;
+ ax25->sk->err = 0;
+ ax25->sk->shutdown |= SEND_SHUTDOWN;
+ if (!ax25->sk->dead)
+ ax25->sk->state_change(ax25->sk);
+ ax25->sk->dead = 1;
+ }
+}
+
+void ax25_std_t1timer_expiry(ax25_cb *ax25)
+{
+ switch (ax25->state) {
+ case AX25_STATE_1:
+ if (ax25->n2count == ax25->n2) {
+ if (ax25->modulus == AX25_MODULUS) {
+ ax25_disconnect(ax25, ETIMEDOUT);
+ return;
+ } else {
+ ax25->modulus = AX25_MODULUS;
+ ax25->window = ax25->ax25_dev->values[AX25_VALUES_WINDOW];
+ ax25->n2count = 0;
+ ax25_send_control(ax25, AX25_SABM, AX25_POLLON, AX25_COMMAND);
+ }
+ } else {
+ ax25->n2count++;
+ if (ax25->modulus == AX25_MODULUS)
+ ax25_send_control(ax25, AX25_SABM, AX25_POLLON, AX25_COMMAND);
+ else
+ ax25_send_control(ax25, AX25_SABME, AX25_POLLON, AX25_COMMAND);
+ }
+ break;
+
+ case AX25_STATE_2:
+ if (ax25->n2count == ax25->n2) {
+ ax25_send_control(ax25, AX25_DISC, AX25_POLLON, AX25_COMMAND);
+ ax25_disconnect(ax25, ETIMEDOUT);
+ return;
+ } else {
+ ax25->n2count++;
+ ax25_send_control(ax25, AX25_DISC, AX25_POLLON, AX25_COMMAND);
+ }
+ break;
+
+ case AX25_STATE_3:
+ ax25->n2count = 1;
+ ax25_std_transmit_enquiry(ax25);
+ ax25->state = AX25_STATE_4;
+ break;
+
+ case AX25_STATE_4:
+ if (ax25->n2count == ax25->n2) {
+ ax25_send_control(ax25, AX25_DM, AX25_POLLON, AX25_RESPONSE);
+ ax25_disconnect(ax25, ETIMEDOUT);
+ return;
+ } else {
+ ax25->n2count++;
+ ax25_std_transmit_enquiry(ax25);
+ }
+ break;
+ }
+
+ ax25_calculate_t1(ax25);
+ ax25_start_t1timer(ax25);
+}
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_subr.c b/uClinux-2.4.31-uc0/net/ax25/ax25_subr.c
new file mode 100644
index 0000000..3ac22ea
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_subr.c
@@ -0,0 +1,315 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Most of this code is based on the SDL diagrams published in the 7th
+ * ARRL Computer Networking Conference papers. The diagrams have mistakes
+ * in them, but are mostly correct. Before you modify the code could you
+ * read the SDL diagrams as the code is not obvious and probably very
+ * easy to break;
+ *
+ * History
+ * AX.25 029 Alan(GW4PTS) Switched to KA9Q constant names. Removed
+ * old BSD code.
+ * AX.25 030 Jonathan(G4KLX) Added support for extended AX.25.
+ * Added fragmentation support.
+ * Darryl(G7LED) Added function ax25_requeue_frames() to split
+ * it up from ax25_frames_acked().
+ * AX.25 031 Joerg(DL1BKE) DAMA needs KISS Fullduplex ON/OFF.
+ * Thus we have ax25_kiss_cmd() now... ;-)
+ * Dave Brown(N2RJT)
+ * Killed a silly bug in the DAMA code.
+ * Joerg(DL1BKE) Found the real bug in ax25.h, sri.
+ * AX.25 032 Joerg(DL1BKE) Added ax25_queue_length to count the number of
+ * enqueued buffers of a socket..
+ * AX.25 035 Frederic(F1OAT) Support for pseudo-digipeating.
+ * AX.25 037 Jonathan(G4KLX) New timer architecture.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+
+/*
+ * This routine purges all the queues of frames.
+ */
+void ax25_clear_queues(ax25_cb *ax25)
+{
+ skb_queue_purge(&ax25->write_queue);
+ skb_queue_purge(&ax25->ack_queue);
+ skb_queue_purge(&ax25->reseq_queue);
+ skb_queue_purge(&ax25->frag_queue);
+}
+
+/*
+ * This routine purges the input queue of those frames that have been
+ * acknowledged. This replaces the boxes labelled "V(a) <- N(r)" on the
+ * SDL diagram.
+ */
+void ax25_frames_acked(ax25_cb *ax25, unsigned short nr)
+{
+ struct sk_buff *skb;
+
+ /*
+ * Remove all the ack-ed frames from the ack queue.
+ */
+ if (ax25->va != nr) {
+ while (skb_peek(&ax25->ack_queue) != NULL && ax25->va != nr) {
+ skb = skb_dequeue(&ax25->ack_queue);
+ kfree_skb(skb);
+ ax25->va = (ax25->va + 1) % ax25->modulus;
+ }
+ }
+}
+
+void ax25_requeue_frames(ax25_cb *ax25)
+{
+ struct sk_buff *skb, *skb_prev = NULL;
+
+ /*
+ * Requeue all the un-ack-ed frames on the output queue to be picked
+ * up by ax25_kick called from the timer. This arrangement handles the
+ * possibility of an empty output queue.
+ */
+ while ((skb = skb_dequeue(&ax25->ack_queue)) != NULL) {
+ if (skb_prev == NULL)
+ skb_queue_head(&ax25->write_queue, skb);
+ else
+ skb_append(skb_prev, skb);
+ skb_prev = skb;
+ }
+}
+
+/*
+ * Validate that the value of nr is between va and vs. Return true or
+ * false for testing.
+ */
+int ax25_validate_nr(ax25_cb *ax25, unsigned short nr)
+{
+ unsigned short vc = ax25->va;
+
+ while (vc != ax25->vs) {
+ if (nr == vc) return 1;
+ vc = (vc + 1) % ax25->modulus;
+ }
+
+ if (nr == ax25->vs) return 1;
+
+ return 0;
+}
+
+/*
+ * This routine is the centralised routine for parsing the control
+ * information for the different frame formats.
+ */
+int ax25_decode(ax25_cb *ax25, struct sk_buff *skb, int *ns, int *nr, int *pf)
+{
+ unsigned char *frame;
+ int frametype = AX25_ILLEGAL;
+
+ frame = skb->data;
+ *ns = *nr = *pf = 0;
+
+ if (ax25->modulus == AX25_MODULUS) {
+ if ((frame[0] & AX25_S) == 0) {
+ frametype = AX25_I; /* I frame - carries NR/NS/PF */
+ *ns = (frame[0] >> 1) & 0x07;
+ *nr = (frame[0] >> 5) & 0x07;
+ *pf = frame[0] & AX25_PF;
+ } else if ((frame[0] & AX25_U) == 1) { /* S frame - take out PF/NR */
+ frametype = frame[0] & 0x0F;
+ *nr = (frame[0] >> 5) & 0x07;
+ *pf = frame[0] & AX25_PF;
+ } else if ((frame[0] & AX25_U) == 3) { /* U frame - take out PF */
+ frametype = frame[0] & ~AX25_PF;
+ *pf = frame[0] & AX25_PF;
+ }
+ skb_pull(skb, 1);
+ } else {
+ if ((frame[0] & AX25_S) == 0) {
+ frametype = AX25_I; /* I frame - carries NR/NS/PF */
+ *ns = (frame[0] >> 1) & 0x7F;
+ *nr = (frame[1] >> 1) & 0x7F;
+ *pf = frame[1] & AX25_EPF;
+ skb_pull(skb, 2);
+ } else if ((frame[0] & AX25_U) == 1) { /* S frame - take out PF/NR */
+ frametype = frame[0] & 0x0F;
+ *nr = (frame[1] >> 1) & 0x7F;
+ *pf = frame[1] & AX25_EPF;
+ skb_pull(skb, 2);
+ } else if ((frame[0] & AX25_U) == 3) { /* U frame - take out PF */
+ frametype = frame[0] & ~AX25_PF;
+ *pf = frame[0] & AX25_PF;
+ skb_pull(skb, 1);
+ }
+ }
+
+ return frametype;
+}
+
+/*
+ * This routine is called when the HDLC layer internally generates a
+ * command or response for the remote machine ( eg. RR, UA etc. ).
+ * Only supervisory or unnumbered frames are processed.
+ */
+void ax25_send_control(ax25_cb *ax25, int frametype, int poll_bit, int type)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+
+ if ((skb = alloc_skb(AX25_BPQ_HEADER_LEN + ax25_addr_size(ax25->digipeat) + 2, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skb, AX25_BPQ_HEADER_LEN + ax25_addr_size(ax25->digipeat));
+
+ skb->nh.raw = skb->data;
+
+ /* Assume a response - address structure for DTE */
+ if (ax25->modulus == AX25_MODULUS) {
+ dptr = skb_put(skb, 1);
+ *dptr = frametype;
+ *dptr |= (poll_bit) ? AX25_PF : 0;
+ if ((frametype & AX25_U) == AX25_S) /* S frames carry NR */
+ *dptr |= (ax25->vr << 5);
+ } else {
+ if ((frametype & AX25_U) == AX25_U) {
+ dptr = skb_put(skb, 1);
+ *dptr = frametype;
+ *dptr |= (poll_bit) ? AX25_PF : 0;
+ } else {
+ dptr = skb_put(skb, 2);
+ dptr[0] = frametype;
+ dptr[1] = (ax25->vr << 1);
+ dptr[1] |= (poll_bit) ? AX25_EPF : 0;
+ }
+ }
+
+ ax25_transmit_buffer(ax25, skb, type);
+}
+
+/*
+ * Send a 'DM' to an unknown connection attempt, or an invalid caller.
+ *
+ * Note: src here is the sender, thus it's the target of the DM
+ */
+void ax25_return_dm(struct net_device *dev, ax25_address *src, ax25_address *dest, ax25_digi *digi)
+{
+ struct sk_buff *skb;
+ char *dptr;
+ ax25_digi retdigi;
+
+ if (dev == NULL)
+ return;
+
+ if ((skb = alloc_skb(AX25_BPQ_HEADER_LEN + ax25_addr_size(digi) + 1, GFP_ATOMIC)) == NULL)
+ return; /* Next SABM will get DM'd */
+
+ skb_reserve(skb, AX25_BPQ_HEADER_LEN + ax25_addr_size(digi));
+ skb->nh.raw = skb->data;
+
+ ax25_digi_invert(digi, &retdigi);
+
+ dptr = skb_put(skb, 1);
+
+ *dptr = AX25_DM | AX25_PF;
+
+ /*
+ * Do the address ourselves
+ */
+ dptr = skb_push(skb, ax25_addr_size(digi));
+ dptr += ax25_addr_build(dptr, dest, src, &retdigi, AX25_RESPONSE, AX25_MODULUS);
+
+ skb->dev = dev;
+
+ ax25_queue_xmit(skb);
+}
+
+/*
+ * Exponential backoff for AX.25
+ */
+void ax25_calculate_t1(ax25_cb *ax25)
+{
+ int n, t = 2;
+
+ switch (ax25->backoff) {
+ case 0:
+ break;
+
+ case 1:
+ t += 2 * ax25->n2count;
+ break;
+
+ case 2:
+ for (n = 0; n < ax25->n2count; n++)
+ t *= 2;
+ if (t > 8) t = 8;
+ break;
+ }
+
+ ax25->t1 = t * ax25->rtt;
+}
+
+/*
+ * Calculate the Round Trip Time
+ */
+void ax25_calculate_rtt(ax25_cb *ax25)
+{
+ if (ax25->backoff == 0)
+ return;
+
+ if (ax25_t1timer_running(ax25) && ax25->n2count == 0)
+ ax25->rtt = (9 * ax25->rtt + ax25->t1 - ax25_display_timer(&ax25->t1timer)) / 10;
+
+ if (ax25->rtt < AX25_T1CLAMPLO)
+ ax25->rtt = AX25_T1CLAMPLO;
+
+ if (ax25->rtt > AX25_T1CLAMPHI)
+ ax25->rtt = AX25_T1CLAMPHI;
+}
+
+void ax25_disconnect(ax25_cb *ax25, int reason)
+{
+ ax25_clear_queues(ax25);
+
+ ax25_stop_t1timer(ax25);
+ ax25_stop_t2timer(ax25);
+ ax25_stop_t3timer(ax25);
+ ax25_stop_idletimer(ax25);
+
+ ax25->state = AX25_STATE_0;
+
+ ax25_link_failed(ax25, reason);
+
+ if (ax25->sk != NULL) {
+ ax25->sk->state = TCP_CLOSE;
+ ax25->sk->err = reason;
+ ax25->sk->shutdown |= SEND_SHUTDOWN;
+ if (!ax25->sk->dead)
+ ax25->sk->state_change(ax25->sk);
+ ax25->sk->dead = 1;
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_timer.c b/uClinux-2.4.31-uc0/net/ax25/ax25_timer.c
new file mode 100644
index 0000000..01e7596
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_timer.c
@@ -0,0 +1,256 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * AX.25 028a Jonathan(G4KLX) New state machine based on SDL diagrams.
+ * AX.25 028b Jonathan(G4KLX) Extracted AX25 control block from the
+ * sock structure.
+ * AX.25 029 Alan(GW4PTS) Switched to KA9Q constant names.
+ * AX.25 031 Joerg(DL1BKE) Added DAMA support
+ * AX.25 032 Joerg(DL1BKE) Fixed DAMA timeout bug
+ * AX.25 033 Jonathan(G4KLX) Modularisation functions.
+ * AX.25 035 Frederic(F1OAT) Support for pseudo-digipeating.
+ * AX.25 036 Jonathan(G4KLX) Split Standard and DAMA code into separate files.
+ * Joerg(DL1BKE) Fixed DAMA Slave. We are *required* to start with
+ * standard AX.25 mode.
+ * AX.25 037 Jonathan(G4KLX) New timer architecture.
+ * Tomi(OH2BNS) Fixed heartbeat expiry (check ax25_dev).
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+
+static void ax25_heartbeat_expiry(unsigned long);
+static void ax25_t1timer_expiry(unsigned long);
+static void ax25_t2timer_expiry(unsigned long);
+static void ax25_t3timer_expiry(unsigned long);
+static void ax25_idletimer_expiry(unsigned long);
+
+void ax25_start_heartbeat(ax25_cb *ax25)
+{
+ del_timer(&ax25->timer);
+
+ ax25->timer.data = (unsigned long)ax25;
+ ax25->timer.function = &ax25_heartbeat_expiry;
+ ax25->timer.expires = jiffies + 5 * HZ;
+
+ add_timer(&ax25->timer);
+}
+
+void ax25_start_t1timer(ax25_cb *ax25)
+{
+ del_timer(&ax25->t1timer);
+
+ ax25->t1timer.data = (unsigned long)ax25;
+ ax25->t1timer.function = &ax25_t1timer_expiry;
+ ax25->t1timer.expires = jiffies + ax25->t1;
+
+ add_timer(&ax25->t1timer);
+}
+
+void ax25_start_t2timer(ax25_cb *ax25)
+{
+ del_timer(&ax25->t2timer);
+
+ ax25->t2timer.data = (unsigned long)ax25;
+ ax25->t2timer.function = &ax25_t2timer_expiry;
+ ax25->t2timer.expires = jiffies + ax25->t2;
+
+ add_timer(&ax25->t2timer);
+}
+
+void ax25_start_t3timer(ax25_cb *ax25)
+{
+ del_timer(&ax25->t3timer);
+
+ if (ax25->t3 > 0) {
+ ax25->t3timer.data = (unsigned long)ax25;
+ ax25->t3timer.function = &ax25_t3timer_expiry;
+ ax25->t3timer.expires = jiffies + ax25->t3;
+
+ add_timer(&ax25->t3timer);
+ }
+}
+
+void ax25_start_idletimer(ax25_cb *ax25)
+{
+ del_timer(&ax25->idletimer);
+
+ if (ax25->idle > 0) {
+ ax25->idletimer.data = (unsigned long)ax25;
+ ax25->idletimer.function = &ax25_idletimer_expiry;
+ ax25->idletimer.expires = jiffies + ax25->idle;
+
+ add_timer(&ax25->idletimer);
+ }
+}
+
+void ax25_stop_heartbeat(ax25_cb *ax25)
+{
+ del_timer(&ax25->timer);
+}
+
+void ax25_stop_t1timer(ax25_cb *ax25)
+{
+ del_timer(&ax25->t1timer);
+}
+
+void ax25_stop_t2timer(ax25_cb *ax25)
+{
+ del_timer(&ax25->t2timer);
+}
+
+void ax25_stop_t3timer(ax25_cb *ax25)
+{
+ del_timer(&ax25->t3timer);
+}
+
+void ax25_stop_idletimer(ax25_cb *ax25)
+{
+ del_timer(&ax25->idletimer);
+}
+
+int ax25_t1timer_running(ax25_cb *ax25)
+{
+ return timer_pending(&ax25->t1timer);
+}
+
+unsigned long ax25_display_timer(struct timer_list *timer)
+{
+ if (!timer_pending(timer))
+ return 0;
+
+ return timer->expires - jiffies;
+}
+
+static void ax25_heartbeat_expiry(unsigned long param)
+{
+ ax25_cb *ax25 = (ax25_cb *)param;
+ int proto = AX25_PROTO_STD_SIMPLEX;
+
+ if (ax25->ax25_dev)
+ proto = ax25->ax25_dev->values[AX25_VALUES_PROTOCOL];
+
+ switch (proto) {
+ case AX25_PROTO_STD_SIMPLEX:
+ case AX25_PROTO_STD_DUPLEX:
+ ax25_std_heartbeat_expiry(ax25);
+ break;
+
+#ifdef CONFIG_AX25_DAMA_SLAVE
+ case AX25_PROTO_DAMA_SLAVE:
+ if (ax25->ax25_dev->dama.slave)
+ ax25_ds_heartbeat_expiry(ax25);
+ else
+ ax25_std_heartbeat_expiry(ax25);
+ break;
+#endif
+ }
+}
+
+static void ax25_t1timer_expiry(unsigned long param)
+{
+ ax25_cb *ax25 = (ax25_cb *)param;
+
+ switch (ax25->ax25_dev->values[AX25_VALUES_PROTOCOL]) {
+ case AX25_PROTO_STD_SIMPLEX:
+ case AX25_PROTO_STD_DUPLEX:
+ ax25_std_t1timer_expiry(ax25);
+ break;
+
+#ifdef CONFIG_AX25_DAMA_SLAVE
+ case AX25_PROTO_DAMA_SLAVE:
+ if (!ax25->ax25_dev->dama.slave)
+ ax25_std_t1timer_expiry(ax25);
+ break;
+#endif
+ }
+}
+
+static void ax25_t2timer_expiry(unsigned long param)
+{
+ ax25_cb *ax25 = (ax25_cb *)param;
+
+ switch (ax25->ax25_dev->values[AX25_VALUES_PROTOCOL]) {
+ case AX25_PROTO_STD_SIMPLEX:
+ case AX25_PROTO_STD_DUPLEX:
+ ax25_std_t2timer_expiry(ax25);
+ break;
+
+#ifdef CONFIG_AX25_DAMA_SLAVE
+ case AX25_PROTO_DAMA_SLAVE:
+ if (!ax25->ax25_dev->dama.slave)
+ ax25_std_t2timer_expiry(ax25);
+ break;
+#endif
+ }
+}
+
+static void ax25_t3timer_expiry(unsigned long param)
+{
+ ax25_cb *ax25 = (ax25_cb *)param;
+
+ switch (ax25->ax25_dev->values[AX25_VALUES_PROTOCOL]) {
+ case AX25_PROTO_STD_SIMPLEX:
+ case AX25_PROTO_STD_DUPLEX:
+ ax25_std_t3timer_expiry(ax25);
+ break;
+
+#ifdef CONFIG_AX25_DAMA_SLAVE
+ case AX25_PROTO_DAMA_SLAVE:
+ if (ax25->ax25_dev->dama.slave)
+ ax25_ds_t3timer_expiry(ax25);
+ else
+ ax25_std_t3timer_expiry(ax25);
+ break;
+#endif
+ }
+}
+
+static void ax25_idletimer_expiry(unsigned long param)
+{
+ ax25_cb *ax25 = (ax25_cb *)param;
+
+ switch (ax25->ax25_dev->values[AX25_VALUES_PROTOCOL]) {
+ case AX25_PROTO_STD_SIMPLEX:
+ case AX25_PROTO_STD_DUPLEX:
+ ax25_std_idletimer_expiry(ax25);
+ break;
+
+#ifdef CONFIG_AX25_DAMA_SLAVE
+ case AX25_PROTO_DAMA_SLAVE:
+ if (ax25->ax25_dev->dama.slave)
+ ax25_ds_idletimer_expiry(ax25);
+ else
+ ax25_std_idletimer_expiry(ax25);
+ break;
+#endif
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/ax25/ax25_uid.c b/uClinux-2.4.31-uc0/net/ax25/ax25_uid.c
new file mode 100644
index 0000000..603d8b8
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/ax25_uid.c
@@ -0,0 +1,178 @@
+/*
+ * AX.25 release 037
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * AX.25 036 Jonathan(G4KLX) Split from af_ax25.c.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/netfilter.h>
+#include <linux/sysctl.h>
+#include <net/ip.h>
+#include <net/arp.h>
+
+/*
+ * Callsign/UID mapper. This is in kernel space for security on multi-amateur machines.
+ */
+
+static ax25_uid_assoc *ax25_uid_list;
+
+int ax25_uid_policy = 0;
+
+ax25_address *ax25_findbyuid(uid_t uid)
+{
+ ax25_uid_assoc *ax25_uid;
+
+ for (ax25_uid = ax25_uid_list; ax25_uid != NULL; ax25_uid = ax25_uid->next) {
+ if (ax25_uid->uid == uid)
+ return &ax25_uid->call;
+ }
+
+ return NULL;
+}
+
+int ax25_uid_ioctl(int cmd, struct sockaddr_ax25 *sax)
+{
+ ax25_uid_assoc *s, *ax25_uid;
+ unsigned long flags;
+
+ switch (cmd) {
+ case SIOCAX25GETUID:
+ for (ax25_uid = ax25_uid_list; ax25_uid != NULL; ax25_uid = ax25_uid->next) {
+ if (ax25cmp(&sax->sax25_call, &ax25_uid->call) == 0)
+ return ax25_uid->uid;
+ }
+ return -ENOENT;
+
+ case SIOCAX25ADDUID:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (ax25_findbyuid(sax->sax25_uid))
+ return -EEXIST;
+ if (sax->sax25_uid == 0)
+ return -EINVAL;
+ if ((ax25_uid = kmalloc(sizeof(*ax25_uid), GFP_KERNEL)) == NULL)
+ return -ENOMEM;
+ ax25_uid->uid = sax->sax25_uid;
+ ax25_uid->call = sax->sax25_call;
+ save_flags(flags); cli();
+ ax25_uid->next = ax25_uid_list;
+ ax25_uid_list = ax25_uid;
+ restore_flags(flags);
+ return 0;
+
+ case SIOCAX25DELUID:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ for (ax25_uid = ax25_uid_list; ax25_uid != NULL; ax25_uid = ax25_uid->next) {
+ if (ax25cmp(&sax->sax25_call, &ax25_uid->call) == 0)
+ break;
+ }
+ if (ax25_uid == NULL)
+ return -ENOENT;
+ save_flags(flags); cli();
+ if ((s = ax25_uid_list) == ax25_uid) {
+ ax25_uid_list = s->next;
+ restore_flags(flags);
+ kfree(ax25_uid);
+ return 0;
+ }
+ while (s != NULL && s->next != NULL) {
+ if (s->next == ax25_uid) {
+ s->next = ax25_uid->next;
+ restore_flags(flags);
+ kfree(ax25_uid);
+ return 0;
+ }
+ s = s->next;
+ }
+ restore_flags(flags);
+ return -ENOENT;
+
+ default:
+ return -EINVAL;
+ }
+
+ return -EINVAL; /*NOTREACHED */
+}
+
+int ax25_uid_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ ax25_uid_assoc *pt;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+
+ cli();
+
+ len += sprintf(buffer, "Policy: %d\n", ax25_uid_policy);
+
+ for (pt = ax25_uid_list; pt != NULL; pt = pt->next) {
+ len += sprintf(buffer + len, "%6d %s\n", pt->uid, ax2asc(&pt->call));
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ break;
+ }
+
+ sti();
+
+ *start = buffer + (offset - begin);
+ len -= offset - begin;
+
+ if (len > length) len = length;
+
+ return len;
+}
+
+/*
+ * Free all memory associated with UID/Callsign structures.
+ */
+void __exit ax25_uid_free(void)
+{
+ ax25_uid_assoc *s, *ax25_uid = ax25_uid_list;
+
+ while (ax25_uid != NULL) {
+ s = ax25_uid;
+ ax25_uid = ax25_uid->next;
+
+ kfree(s);
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/ax25/sysctl_net_ax25.c b/uClinux-2.4.31-uc0/net/ax25/sysctl_net_ax25.c
new file mode 100644
index 0000000..e6016f9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ax25/sysctl_net_ax25.c
@@ -0,0 +1,162 @@
+/* -*- linux-c -*-
+ * sysctl_net_ax25.c: sysctl interface to net AX.25 subsystem.
+ *
+ * Begun April 1, 1996, Mike Shaver.
+ * Added /proc/sys/net/ax25 directory entry (empty =) ). [MS]
+ */
+
+#include <linux/config.h>
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+#include <net/ax25.h>
+
+static int min_ipdefmode[] = {0}, max_ipdefmode[] = {1};
+static int min_axdefmode[] = {0}, max_axdefmode[] = {1};
+static int min_backoff[] = {0}, max_backoff[] = {2};
+static int min_conmode[] = {0}, max_conmode[] = {2};
+static int min_window[] = {1}, max_window[] = {7};
+static int min_ewindow[] = {1}, max_ewindow[] = {63};
+static int min_t1[] = {1}, max_t1[] = {30 * HZ};
+static int min_t2[] = {1}, max_t2[] = {20 * HZ};
+static int min_t3[] = {0}, max_t3[] = {3600 * HZ};
+static int min_idle[] = {0}, max_idle[] = {65535 * HZ};
+static int min_n2[] = {1}, max_n2[] = {31};
+static int min_paclen[] = {1}, max_paclen[] = {512};
+static int min_proto[] = {0}, max_proto[] = {3};
+static int min_ds_timeout[] = {0}, max_ds_timeout[] = {65535 * HZ};
+
+static struct ctl_table_header *ax25_table_header;
+
+static ctl_table *ax25_table;
+static int ax25_table_size;
+
+static ctl_table ax25_dir_table[] = {
+ {NET_AX25, "ax25", NULL, 0, 0555, NULL},
+ {0}
+};
+
+static ctl_table ax25_root_table[] = {
+ {CTL_NET, "net", NULL, 0, 0555, ax25_dir_table},
+ {0}
+};
+
+static const ctl_table ax25_param_table[] = {
+ {NET_AX25_IP_DEFAULT_MODE, "ip_default_mode",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_ipdefmode, &max_ipdefmode},
+ {NET_AX25_DEFAULT_MODE, "ax25_default_mode",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_axdefmode, &max_axdefmode},
+ {NET_AX25_BACKOFF_TYPE, "backoff_type",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_backoff, &max_backoff},
+ {NET_AX25_CONNECT_MODE, "connect_mode",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_conmode, &max_conmode},
+ {NET_AX25_STANDARD_WINDOW, "standard_window_size",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_window, &max_window},
+ {NET_AX25_EXTENDED_WINDOW, "extended_window_size",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_ewindow, &max_ewindow},
+ {NET_AX25_T1_TIMEOUT, "t1_timeout",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_t1, &max_t1},
+ {NET_AX25_T2_TIMEOUT, "t2_timeout",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_t2, &max_t2},
+ {NET_AX25_T3_TIMEOUT, "t3_timeout",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_t3, &max_t3},
+ {NET_AX25_IDLE_TIMEOUT, "idle_timeout",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_idle, &max_idle},
+ {NET_AX25_N2, "maximum_retry_count",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_n2, &max_n2},
+ {NET_AX25_PACLEN, "maximum_packet_length",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_paclen, &max_paclen},
+ {NET_AX25_PROTOCOL, "protocol",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_proto, &max_proto},
+ {NET_AX25_DAMA_SLAVE_TIMEOUT, "dama_slave_timeout",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_ds_timeout, &max_ds_timeout},
+ {0} /* that's all, folks! */
+};
+
+void ax25_register_sysctl(void)
+{
+ ax25_dev *ax25_dev;
+ int n, k;
+
+ for (ax25_table_size = sizeof(ctl_table), ax25_dev = ax25_dev_list; ax25_dev != NULL; ax25_dev = ax25_dev->next)
+ ax25_table_size += sizeof(ctl_table);
+
+ if ((ax25_table = kmalloc(ax25_table_size, GFP_ATOMIC)) == NULL)
+ return;
+
+ memset(ax25_table, 0x00, ax25_table_size);
+
+ for (n = 0, ax25_dev = ax25_dev_list; ax25_dev != NULL; ax25_dev = ax25_dev->next) {
+ ctl_table *child = kmalloc(sizeof(ax25_param_table), GFP_ATOMIC);
+ if (!child) {
+ while (n--)
+ kfree(ax25_table[n].child);
+ kfree(ax25_table);
+ return;
+ }
+ memcpy(child, ax25_param_table, sizeof(ax25_param_table));
+ ax25_table[n].child = ax25_dev->systable = child;
+ ax25_table[n].ctl_name = n + 1;
+ ax25_table[n].procname = ax25_dev->dev->name;
+ ax25_table[n].mode = 0555;
+
+#ifndef CONFIG_AX25_DAMA_SLAVE
+ /*
+ * We do not wish to have a representation of this parameter
+ * in /proc/sys/ when configured *not* to include the
+ * AX.25 DAMA slave code, do we?
+ */
+
+ child[AX25_VALUES_DS_TIMEOUT].procname = NULL;
+#endif
+
+ child[AX25_MAX_VALUES].ctl_name = 0; /* just in case... */
+
+ for (k = 0; k < AX25_MAX_VALUES; k++)
+ child[k].data = &ax25_dev->values[k];
+
+ n++;
+ }
+
+ ax25_dir_table[0].child = ax25_table;
+
+ ax25_table_header = register_sysctl_table(ax25_root_table, 1);
+}
+
+void ax25_unregister_sysctl(void)
+{
+ ctl_table *p;
+ unregister_sysctl_table(ax25_table_header);
+
+ ax25_dir_table[0].child = NULL;
+ for (p = ax25_table; p->ctl_name; p++)
+ kfree(p->child);
+ kfree(ax25_table);
+}
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/Config.in b/uClinux-2.4.31-uc0/net/bluetooth/Config.in
new file mode 100644
index 0000000..c7cda2e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/Config.in
@@ -0,0 +1,22 @@
+#
+# Bluetooth subsystem configuration
+#
+
+if [ "$CONFIG_NET" != "n" ]; then
+
+ mainmenu_option next_comment
+ comment 'Bluetooth support'
+ dep_tristate 'Bluetooth subsystem support' CONFIG_BLUEZ $CONFIG_NET
+
+ if [ "$CONFIG_BLUEZ" != "n" ]; then
+ dep_tristate 'L2CAP protocol support' CONFIG_BLUEZ_L2CAP $CONFIG_BLUEZ
+ dep_tristate 'SCO links support' CONFIG_BLUEZ_SCO $CONFIG_BLUEZ
+ source net/bluetooth/rfcomm/Config.in
+ source net/bluetooth/bnep/Config.in
+ source net/bluetooth/cmtp/Config.in
+ source drivers/bluetooth/Config.in
+ fi
+
+ endmenu
+fi
+
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/Makefile b/uClinux-2.4.31-uc0/net/bluetooth/Makefile
new file mode 100644
index 0000000..07ab420
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/Makefile
@@ -0,0 +1,31 @@
+#
+# Makefile for the Linux Bluetooth subsystem
+#
+
+O_TARGET := bluetooth.o
+
+list-multi := bluez.o
+export-objs := syms.o l2cap.o
+
+bluez-objs := af_bluetooth.o hci_core.o hci_conn.o hci_event.o hci_sock.o lib.o syms.o
+
+obj-$(CONFIG_BLUEZ) += bluez.o
+obj-$(CONFIG_BLUEZ_L2CAP) += l2cap.o
+obj-$(CONFIG_BLUEZ_SCO) += sco.o
+
+subdir-$(CONFIG_BLUEZ_RFCOMM) += rfcomm
+subdir-$(CONFIG_BLUEZ_BNEP) += bnep
+subdir-$(CONFIG_BLUEZ_CMTP) += cmtp
+
+ifeq ($(CONFIG_BLUEZ_RFCOMM),y)
+obj-y += rfcomm/rfcomm.o
+endif
+
+ifeq ($(CONFIG_BLUEZ_BNEP),y)
+obj-y += bnep/bnep.o
+endif
+
+include $(TOPDIR)/Rules.make
+
+bluez.o: $(bluez-objs)
+ $(LD) -r -o $@ $(bluez-objs)
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/af_bluetooth.c b/uClinux-2.4.31-uc0/net/bluetooth/af_bluetooth.c
new file mode 100644
index 0000000..3b773fb
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/af_bluetooth.c
@@ -0,0 +1,336 @@
+/*
+ BlueZ - Bluetooth protocol stack for Linux
+ Copyright (C) 2000-2001 Qualcomm Incorporated
+
+ Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * BlueZ Bluetooth address family and sockets.
+ *
+ * $Id: af_bluetooth.c,v 1.8 2002/07/22 20:32:54 maxk Exp $
+ */
+#define VERSION "2.4"
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <net/sock.h>
+
+#if defined(CONFIG_KMOD)
+#include <linux/kmod.h>
+#endif
+
+#include <net/bluetooth/bluetooth.h>
+
+#ifndef AF_BLUETOOTH_DEBUG
+#undef BT_DBG
+#define BT_DBG( A... )
+#endif
+
+/* Bluetooth sockets */
+#define BLUEZ_MAX_PROTO 7
+static struct net_proto_family *bluez_proto[BLUEZ_MAX_PROTO];
+
+int bluez_sock_register(int proto, struct net_proto_family *ops)
+{
+ if (proto < 0 || proto >= BLUEZ_MAX_PROTO)
+ return -EINVAL;
+
+ if (bluez_proto[proto])
+ return -EEXIST;
+
+ bluez_proto[proto] = ops;
+ return 0;
+}
+
+int bluez_sock_unregister(int proto)
+{
+ if (proto < 0 || proto >= BLUEZ_MAX_PROTO)
+ return -EINVAL;
+
+ if (!bluez_proto[proto])
+ return -ENOENT;
+
+ bluez_proto[proto] = NULL;
+ return 0;
+}
+
+static int bluez_sock_create(struct socket *sock, int proto)
+{
+ if (proto < 0 || proto >= BLUEZ_MAX_PROTO)
+ return -EINVAL;
+
+#if defined(CONFIG_KMOD)
+ if (!bluez_proto[proto]) {
+ char module_name[30];
+ sprintf(module_name, "bt-proto-%d", proto);
+ request_module(module_name);
+ }
+#endif
+
+ if (!bluez_proto[proto])
+ return -ENOENT;
+
+ return bluez_proto[proto]->create(sock, proto);
+}
+
+void bluez_sock_init(struct socket *sock, struct sock *sk)
+{
+ sock_init_data(sock, sk);
+ INIT_LIST_HEAD(&bluez_pi(sk)->accept_q);
+}
+
+void bluez_sock_link(struct bluez_sock_list *l, struct sock *sk)
+{
+ write_lock_bh(&l->lock);
+ sk->next = l->head;
+ l->head = sk;
+ sock_hold(sk);
+ write_unlock_bh(&l->lock);
+}
+
+void bluez_sock_unlink(struct bluez_sock_list *l, struct sock *sk)
+{
+ struct sock **skp;
+
+ write_lock_bh(&l->lock);
+ for (skp = &l->head; *skp; skp = &((*skp)->next)) {
+ if (*skp == sk) {
+ *skp = sk->next;
+ __sock_put(sk);
+ break;
+ }
+ }
+ write_unlock_bh(&l->lock);
+}
+
+void bluez_accept_enqueue(struct sock *parent, struct sock *sk)
+{
+ BT_DBG("parent %p, sk %p", parent, sk);
+
+ sock_hold(sk);
+ list_add_tail(&bluez_pi(sk)->accept_q, &bluez_pi(parent)->accept_q);
+ bluez_pi(sk)->parent = parent;
+ parent->ack_backlog++;
+}
+
+static void bluez_accept_unlink(struct sock *sk)
+{
+ BT_DBG("sk %p state %d", sk, sk->state);
+
+ list_del_init(&bluez_pi(sk)->accept_q);
+ bluez_pi(sk)->parent->ack_backlog--;
+ bluez_pi(sk)->parent = NULL;
+ sock_put(sk);
+}
+
+struct sock *bluez_accept_dequeue(struct sock *parent, struct socket *newsock)
+{
+ struct list_head *p, *n;
+ struct bluez_pinfo *pi;
+ struct sock *sk;
+
+ BT_DBG("parent %p", parent);
+
+ list_for_each_safe(p, n, &bluez_pi(parent)->accept_q) {
+ pi = list_entry(p, struct bluez_pinfo, accept_q);
+ sk = bluez_sk(pi);
+
+ lock_sock(sk);
+ if (sk->state == BT_CLOSED) {
+ release_sock(sk);
+ bluez_accept_unlink(sk);
+ continue;
+ }
+
+ if (sk->state == BT_CONNECTED || !newsock) {
+ bluez_accept_unlink(sk);
+ if (newsock)
+ sock_graft(sk, newsock);
+ release_sock(sk);
+ return sk;
+ }
+ release_sock(sk);
+ }
+ return NULL;
+}
+
+int bluez_sock_recvmsg(struct socket *sock, struct msghdr *msg, int len, int flags, struct scm_cookie *scm)
+{
+ int noblock = flags & MSG_DONTWAIT;
+ struct sock *sk = sock->sk;
+ struct sk_buff *skb;
+ int copied, err;
+
+ BT_DBG("sock %p sk %p len %d", sock, sk, len);
+
+ if (flags & (MSG_OOB))
+ return -EOPNOTSUPP;
+
+ if (!(skb = skb_recv_datagram(sk, flags, noblock, &err))) {
+ if (sk->shutdown & RCV_SHUTDOWN)
+ return 0;
+ return err;
+ }
+
+ msg->msg_namelen = 0;
+
+ copied = skb->len;
+ if (len < copied) {
+ msg->msg_flags |= MSG_TRUNC;
+ copied = len;
+ }
+
+ skb->h.raw = skb->data;
+ err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+
+ skb_free_datagram(sk, skb);
+
+ return err ? : copied;
+}
+
+unsigned int bluez_sock_poll(struct file * file, struct socket *sock, poll_table *wait)
+{
+ struct sock *sk = sock->sk;
+ unsigned int mask = 0;
+
+ BT_DBG("sock %p, sk %p", sock, sk);
+
+ poll_wait(file, sk->sleep, wait);
+
+ if (sk->err || !skb_queue_empty(&sk->error_queue))
+ mask |= POLLERR;
+
+ if (sk->shutdown == SHUTDOWN_MASK)
+ mask |= POLLHUP;
+
+ if (!skb_queue_empty(&sk->receive_queue) ||
+ !list_empty(&bluez_pi(sk)->accept_q) ||
+ (sk->shutdown & RCV_SHUTDOWN))
+ mask |= POLLIN | POLLRDNORM;
+
+ if (sk->state == BT_CLOSED)
+ mask |= POLLHUP;
+
+ if (sk->state == BT_CONNECT ||
+ sk->state == BT_CONNECT2 ||
+ sk->state == BT_CONFIG)
+ return mask;
+
+ if (sock_writeable(sk))
+ mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
+ else
+ set_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
+
+ return mask;
+}
+
+int bluez_sock_wait_state(struct sock *sk, int state, unsigned long timeo)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ int err = 0;
+
+ BT_DBG("sk %p", sk);
+
+ add_wait_queue(sk->sleep, &wait);
+ while (sk->state != state) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (!timeo) {
+ err = -EAGAIN;
+ break;
+ }
+
+ if (signal_pending(current)) {
+ err = sock_intr_errno(timeo);
+ break;
+ }
+
+ release_sock(sk);
+ timeo = schedule_timeout(timeo);
+ lock_sock(sk);
+
+ if (sk->err) {
+ err = sock_error(sk);
+ break;
+ }
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(sk->sleep, &wait);
+ return err;
+}
+
+struct net_proto_family bluez_sock_family_ops =
+{
+ PF_BLUETOOTH, bluez_sock_create
+};
+
+int bluez_init(void)
+{
+ BT_INFO("BlueZ Core ver %s Copyright (C) 2000,2001 Qualcomm Inc",
+ VERSION);
+ BT_INFO("Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>");
+
+ proc_mkdir("bluetooth", NULL);
+
+ sock_register(&bluez_sock_family_ops);
+
+ /* Init HCI Core */
+ hci_core_init();
+
+ /* Init sockets */
+ hci_sock_init();
+
+ return 0;
+}
+
+void bluez_cleanup(void)
+{
+ /* Release socket */
+ hci_sock_cleanup();
+
+ /* Release core */
+ hci_core_cleanup();
+
+ sock_unregister(PF_BLUETOOTH);
+
+ remove_proc_entry("bluetooth", NULL);
+}
+
+#ifdef MODULE
+module_init(bluez_init);
+module_exit(bluez_cleanup);
+
+MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>");
+MODULE_DESCRIPTION("BlueZ Core ver " VERSION);
+MODULE_LICENSE("GPL");
+#endif
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/bnep/Config.in b/uClinux-2.4.31-uc0/net/bluetooth/bnep/Config.in
new file mode 100644
index 0000000..eea8417
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/bnep/Config.in
@@ -0,0 +1,11 @@
+#
+# Bluetooth BNEP layer configuration
+#
+
+dep_tristate 'BNEP protocol support' CONFIG_BLUEZ_BNEP $CONFIG_BLUEZ_L2CAP
+
+if [ "$CONFIG_BLUEZ_BNEP" != "n" ]; then
+ bool ' Multicast filter support' CONFIG_BLUEZ_BNEP_MC_FILTER
+ bool ' Protocol filter support' CONFIG_BLUEZ_BNEP_PROTO_FILTER
+fi
+
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/bnep/Makefile b/uClinux-2.4.31-uc0/net/bluetooth/bnep/Makefile
new file mode 100644
index 0000000..a6c2fb4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/bnep/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for the Linux Bluetooth BNEP layer
+#
+
+O_TARGET := bnep.o
+
+obj-y := core.o sock.o netdev.o
+obj-m += $(O_TARGET)
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/bnep/Makefile.lib b/uClinux-2.4.31-uc0/net/bluetooth/bnep/Makefile.lib
new file mode 100644
index 0000000..ef04b57
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/bnep/Makefile.lib
@@ -0,0 +1 @@
+obj-$(CONFIG_BLUEZ_BNEP) += crc32.o
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/bnep/bnep.h b/uClinux-2.4.31-uc0/net/bluetooth/bnep/bnep.h
new file mode 100644
index 0000000..b1ad197
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/bnep/bnep.h
@@ -0,0 +1,185 @@
+/*
+ BNEP protocol definition for Linux Bluetooth stack (BlueZ).
+ Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License, version 2, as
+ published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+/*
+ * $Id: bnep2.h,v 1.9 2002/07/14 07:09:19 maxk Exp $
+ */
+
+#ifndef _BNEP_H
+#define _BNEP_H
+
+#include <linux/types.h>
+#include <net/bluetooth/bluetooth.h>
+
+#include <linux/crc32.h>
+
+// Limits
+#define BNEP_MAX_PROTO_FILTERS 5
+#define BNEP_MAX_MULTICAST_FILTERS 20
+
+// UUIDs
+#define BNEP_BASE_UUID 0x0000000000001000800000805F9B34FB
+#define BNEP_UUID16 0x02
+#define BNEP_UUID32 0x04
+#define BNEP_UUID128 0x16
+
+#define BNEP_SVC_PANU 0x1115
+#define BNEP_SVC_NAP 0x1116
+#define BNEP_SVC_GN 0x1117
+
+// Packet types
+#define BNEP_GENERAL 0x00
+#define BNEP_CONTROL 0x01
+#define BNEP_COMPRESSED 0x02
+#define BNEP_COMPRESSED_SRC_ONLY 0x03
+#define BNEP_COMPRESSED_DST_ONLY 0x04
+
+// Control types
+#define BNEP_CMD_NOT_UNDERSTOOD 0x00
+#define BNEP_SETUP_CONN_REQ 0x01
+#define BNEP_SETUP_CONN_RSP 0x02
+#define BNEP_FILTER_NET_TYPE_SET 0x03
+#define BNEP_FILTER_NET_TYPE_RSP 0x04
+#define BNEP_FILTER_MULTI_ADDR_SET 0x05
+#define BNEP_FILTER_MULTI_ADDR_RSP 0x06
+
+// Extension types
+#define BNEP_EXT_CONTROL 0x00
+
+// Response messages
+#define BNEP_SUCCESS 0x00
+
+#define BNEP_CONN_INVALID_DST 0x01
+#define BNEP_CONN_INVALID_SRC 0x02
+#define BNEP_CONN_INVALID_SVC 0x03
+#define BNEP_CONN_NOT_ALLOWED 0x04
+
+#define BNEP_FILTER_UNSUPPORTED_REQ 0x01
+#define BNEP_FILTER_INVALID_RANGE 0x02
+#define BNEP_FILTER_INVALID_MCADDR 0x02
+#define BNEP_FILTER_LIMIT_REACHED 0x03
+#define BNEP_FILTER_DENIED_SECURITY 0x04
+
+// L2CAP settings
+#define BNEP_MTU 1691
+#define BNEP_PSM 0x0f
+#define BNEP_FLUSH_TO 0xffff
+#define BNEP_CONNECT_TO 15
+#define BNEP_FILTER_TO 15
+
+// Headers
+#define BNEP_TYPE_MASK 0x7f
+#define BNEP_EXT_HEADER 0x80
+
+struct bnep_setup_conn_req {
+ __u8 type;
+ __u8 ctrl;
+ __u8 uuid_size;
+ __u8 service[0];
+} __attribute__((packed));
+
+struct bnep_set_filter_req {
+ __u8 type;
+ __u8 ctrl;
+ __u16 len;
+ __u8 list[0];
+} __attribute__((packed));
+
+struct bnep_control_rsp {
+ __u8 type;
+ __u8 ctrl;
+ __u16 resp;
+} __attribute__((packed));
+
+struct bnep_ext_hdr {
+ __u8 type;
+ __u8 len;
+ __u8 data[0];
+} __attribute__((packed));
+
+/* BNEP ioctl defines */
+#define BNEPCONNADD _IOW('B', 200, int)
+#define BNEPCONNDEL _IOW('B', 201, int)
+#define BNEPGETCONNLIST _IOR('B', 210, int)
+#define BNEPGETCONNINFO _IOR('B', 211, int)
+
+struct bnep_connadd_req {
+ int sock; // Connected socket
+ __u32 flags;
+ __u16 role;
+ char device[16]; // Name of the Ethernet device
+};
+
+struct bnep_conndel_req {
+ __u32 flags;
+ __u8 dst[ETH_ALEN];
+};
+
+struct bnep_conninfo {
+ __u32 flags;
+ __u16 role;
+ __u16 state;
+ __u8 dst[ETH_ALEN];
+ char device[16];
+};
+
+struct bnep_connlist_req {
+ __u32 cnum;
+ struct bnep_conninfo *ci;
+};
+
+struct bnep_proto_filter {
+ __u16 start;
+ __u16 end;
+};
+
+int bnep_add_connection(struct bnep_connadd_req *req, struct socket *sock);
+int bnep_del_connection(struct bnep_conndel_req *req);
+int bnep_get_connlist(struct bnep_connlist_req *req);
+int bnep_get_conninfo(struct bnep_conninfo *ci);
+
+// BNEP sessions
+struct bnep_session {
+ struct list_head list;
+
+ unsigned int role;
+ unsigned long state;
+ unsigned long flags;
+ atomic_t killed;
+
+ struct ethhdr eh;
+ struct msghdr msg;
+
+ struct bnep_proto_filter proto_filter[BNEP_MAX_PROTO_FILTERS];
+ u64 mc_filter;
+
+ struct socket *sock;
+ struct net_device dev;
+ struct net_device_stats stats;
+};
+
+int bnep_net_init(struct net_device *dev);
+int bnep_sock_init(void);
+int bnep_sock_cleanup(void);
+
+static inline int bnep_mc_hash(__u8 *addr)
+{
+ return (crc32_be(~0, addr, ETH_ALEN) >> 26);
+}
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/bnep/core.c b/uClinux-2.4.31-uc0/net/bluetooth/bnep/core.c
new file mode 100644
index 0000000..a397e04
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/bnep/core.c
@@ -0,0 +1,716 @@
+/*
+ BNEP implementation for Linux Bluetooth stack (BlueZ).
+ Copyright (C) 2001-2002 Inventel Systemes
+ Written 2001-2002 by
+ Clément Moreau <clement.moreau@inventel.fr>
+ David Libault <david.libault@inventel.fr>
+
+ Copyright (C) 2002 Maxim Krasnyanskiy <maxk@qualcomm.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * $Id: core.c,v 1.18 2002/07/14 07:09:19 maxk Exp $
+ */
+
+#define __KERNEL_SYSCALLS__
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/signal.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/errno.h>
+#include <linux/smp_lock.h>
+#include <linux/net.h>
+#include <net/sock.h>
+
+#include <linux/socket.h>
+#include <linux/file.h>
+
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+
+#include <asm/unaligned.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/l2cap.h>
+
+#include "bnep.h"
+
+#ifndef CONFIG_BLUEZ_BNEP_DEBUG
+#undef BT_DBG
+#define BT_DBG(D...)
+#endif
+
+#define VERSION "1.2"
+
+static LIST_HEAD(bnep_session_list);
+static DECLARE_RWSEM(bnep_session_sem);
+
+static struct bnep_session *__bnep_get_session(u8 *dst)
+{
+ struct bnep_session *s;
+ struct list_head *p;
+
+ BT_DBG("");
+
+ list_for_each(p, &bnep_session_list) {
+ s = list_entry(p, struct bnep_session, list);
+ if (!memcmp(dst, s->eh.h_source, ETH_ALEN))
+ return s;
+ }
+ return NULL;
+}
+
+static void __bnep_link_session(struct bnep_session *s)
+{
+ MOD_INC_USE_COUNT;
+ list_add(&s->list, &bnep_session_list);
+}
+
+static void __bnep_unlink_session(struct bnep_session *s)
+{
+ list_del(&s->list);
+ MOD_DEC_USE_COUNT;
+}
+
+static int bnep_send(struct bnep_session *s, void *data, size_t len)
+{
+ struct socket *sock = s->sock;
+ struct iovec iv = { data, len };
+ s->msg.msg_iov = &iv;
+ s->msg.msg_iovlen = 1;
+ return sock->ops->sendmsg(sock, &s->msg, len, NULL);
+}
+
+static int bnep_send_rsp(struct bnep_session *s, u8 ctrl, u16 resp)
+{
+ struct bnep_control_rsp rsp;
+ rsp.type = BNEP_CONTROL;
+ rsp.ctrl = ctrl;
+ rsp.resp = htons(resp);
+ return bnep_send(s, &rsp, sizeof(rsp));
+}
+
+#ifdef CONFIG_BLUEZ_BNEP_PROTO_FILTER
+static inline void bnep_set_default_proto_filter(struct bnep_session *s)
+{
+ /* (IPv4, ARP) */
+ s->proto_filter[0].start = htons(0x0800);
+ s->proto_filter[0].end = htons(0x0806);
+ /* (RARP, AppleTalk) */
+ s->proto_filter[1].start = htons(0x8035);
+ s->proto_filter[1].end = htons(0x80F3);
+ /* (IPX, IPv6) */
+ s->proto_filter[2].start = htons(0x8137);
+ s->proto_filter[2].end = htons(0x86DD);
+}
+#endif
+
+static int bnep_ctrl_set_netfilter(struct bnep_session *s, u16 *data, int len)
+{
+ int n;
+
+ if (len < 2)
+ return -EILSEQ;
+
+ n = ntohs(get_unaligned(data));
+ data++; len -= 2;
+
+ if (len < n)
+ return -EILSEQ;
+
+ BT_DBG("filter len %d", n);
+
+#ifdef CONFIG_BLUEZ_BNEP_PROTO_FILTER
+ n /= 4;
+ if (n <= BNEP_MAX_PROTO_FILTERS) {
+ struct bnep_proto_filter *f = s->proto_filter;
+ int i;
+
+ for (i = 0; i < n; i++) {
+ f[i].start = get_unaligned(data++);
+ f[i].end = get_unaligned(data++);
+
+ BT_DBG("proto filter start %d end %d",
+ f[i].start, f[i].end);
+ }
+
+ if (i < BNEP_MAX_PROTO_FILTERS)
+ memset(f + i, 0, sizeof(*f));
+
+ if (n == 0)
+ bnep_set_default_proto_filter(s);
+
+ bnep_send_rsp(s, BNEP_FILTER_NET_TYPE_RSP, BNEP_SUCCESS);
+ } else {
+ bnep_send_rsp(s, BNEP_FILTER_NET_TYPE_RSP, BNEP_FILTER_LIMIT_REACHED);
+ }
+#else
+ bnep_send_rsp(s, BNEP_FILTER_NET_TYPE_RSP, BNEP_FILTER_UNSUPPORTED_REQ);
+#endif
+ return 0;
+}
+
+static int bnep_ctrl_set_mcfilter(struct bnep_session *s, u8 *data, int len)
+{
+ int n;
+
+ if (len < 2)
+ return -EILSEQ;
+
+ n = ntohs(get_unaligned((u16 *) data));
+ data += 2; len -= 2;
+
+ if (len < n)
+ return -EILSEQ;
+
+ BT_DBG("filter len %d", n);
+
+#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER
+ n /= (ETH_ALEN * 2);
+
+ if (n > 0) {
+ s->mc_filter = 0;
+
+ /* Always send broadcast */
+ set_bit(bnep_mc_hash(s->dev.broadcast), &s->mc_filter);
+
+ /* Add address ranges to the multicast hash */
+ for (; n > 0; n--) {
+ u8 a1[6], *a2;
+
+ memcpy(a1, data, ETH_ALEN); data += ETH_ALEN;
+ a2 = data; data += ETH_ALEN;
+
+ BT_DBG("mc filter %s -> %s",
+ batostr((void *) a1), batostr((void *) a2));
+
+ #define INCA(a) { int i = 5; while (i >=0 && ++a[i--] == 0); }
+
+ /* Iterate from a1 to a2 */
+ set_bit(bnep_mc_hash(a1), &s->mc_filter);
+ while (memcmp(a1, a2, 6) < 0 && s->mc_filter != ~0LL) {
+ INCA(a1);
+ set_bit(bnep_mc_hash(a1), &s->mc_filter);
+ }
+ }
+ }
+
+ BT_DBG("mc filter hash 0x%llx", s->mc_filter);
+
+ bnep_send_rsp(s, BNEP_FILTER_MULTI_ADDR_RSP, BNEP_SUCCESS);
+#else
+ bnep_send_rsp(s, BNEP_FILTER_MULTI_ADDR_RSP, BNEP_FILTER_UNSUPPORTED_REQ);
+#endif
+ return 0;
+}
+
+static int bnep_rx_control(struct bnep_session *s, void *data, int len)
+{
+ u8 cmd = *(u8 *)data;
+ int err = 0;
+
+ data++; len--;
+
+ switch (cmd) {
+ case BNEP_CMD_NOT_UNDERSTOOD:
+ case BNEP_SETUP_CONN_REQ:
+ case BNEP_SETUP_CONN_RSP:
+ case BNEP_FILTER_NET_TYPE_RSP:
+ case BNEP_FILTER_MULTI_ADDR_RSP:
+ /* Ignore these for now */
+ break;
+
+ case BNEP_FILTER_NET_TYPE_SET:
+ err = bnep_ctrl_set_netfilter(s, data, len);
+ break;
+
+ case BNEP_FILTER_MULTI_ADDR_SET:
+ err = bnep_ctrl_set_mcfilter(s, data, len);
+ break;
+
+ default: {
+ u8 pkt[3];
+ pkt[0] = BNEP_CONTROL;
+ pkt[1] = BNEP_CMD_NOT_UNDERSTOOD;
+ pkt[2] = cmd;
+ bnep_send(s, pkt, sizeof(pkt));
+ }
+ break;
+ }
+
+ return err;
+}
+
+static int bnep_rx_extension(struct bnep_session *s, struct sk_buff *skb)
+{
+ struct bnep_ext_hdr *h;
+ int err = 0;
+
+ do {
+ h = (void *) skb->data;
+ if (!skb_pull(skb, sizeof(*h))) {
+ err = -EILSEQ;
+ break;
+ }
+
+ BT_DBG("type 0x%x len %d", h->type, h->len);
+
+ switch (h->type & BNEP_TYPE_MASK) {
+ case BNEP_EXT_CONTROL:
+ bnep_rx_control(s, skb->data, skb->len);
+ break;
+
+ default:
+ /* Unknown extension, skip it. */
+ break;
+ }
+
+ if (!skb_pull(skb, h->len)) {
+ err = -EILSEQ;
+ break;
+ }
+ } while (!err && (h->type & BNEP_EXT_HEADER));
+
+ return err;
+}
+
+static u8 __bnep_rx_hlen[] = {
+ ETH_HLEN, /* BNEP_GENERAL */
+ 0, /* BNEP_CONTROL */
+ 2, /* BNEP_COMPRESSED */
+ ETH_ALEN + 2, /* BNEP_COMPRESSED_SRC_ONLY */
+ ETH_ALEN + 2 /* BNEP_COMPRESSED_DST_ONLY */
+};
+#define BNEP_RX_TYPES (sizeof(__bnep_rx_hlen) - 1)
+
+static inline int bnep_rx_frame(struct bnep_session *s, struct sk_buff *skb)
+{
+ struct net_device *dev = &s->dev;
+ struct sk_buff *nskb;
+ u8 type;
+
+ dev->last_rx = jiffies;
+ s->stats.rx_bytes += skb->len;
+
+ type = *(u8 *) skb->data; skb_pull(skb, 1);
+
+ if ((type & BNEP_TYPE_MASK) > BNEP_RX_TYPES)
+ goto badframe;
+
+ if ((type & BNEP_TYPE_MASK) == BNEP_CONTROL) {
+ bnep_rx_control(s, skb->data, skb->len);
+ kfree_skb(skb);
+ return 0;
+ }
+
+ skb->mac.raw = skb->data;
+
+ /* Verify and pull out header */
+ if (!skb_pull(skb, __bnep_rx_hlen[type & BNEP_TYPE_MASK]))
+ goto badframe;
+
+ s->eh.h_proto = get_unaligned((u16 *) (skb->data - 2));
+
+ if (type & BNEP_EXT_HEADER) {
+ if (bnep_rx_extension(s, skb) < 0)
+ goto badframe;
+ }
+
+ /* Strip 802.1p header */
+ if (ntohs(s->eh.h_proto) == 0x8100) {
+ if (!skb_pull(skb, 4))
+ goto badframe;
+ s->eh.h_proto = get_unaligned((u16 *) (skb->data - 2));
+ }
+
+ /* We have to alloc new skb and copy data here :(. Because original skb
+ * may not be modified and because of the alignment requirements. */
+ nskb = alloc_skb(2 + ETH_HLEN + skb->len, GFP_KERNEL);
+ if (!nskb) {
+ s->stats.rx_dropped++;
+ kfree_skb(skb);
+ return -ENOMEM;
+ }
+ skb_reserve(nskb, 2);
+
+ /* Decompress header and construct ether frame */
+ switch (type & BNEP_TYPE_MASK) {
+ case BNEP_COMPRESSED:
+ memcpy(__skb_put(nskb, ETH_HLEN), &s->eh, ETH_HLEN);
+ break;
+
+ case BNEP_COMPRESSED_SRC_ONLY:
+ memcpy(__skb_put(nskb, ETH_ALEN), s->eh.h_dest, ETH_ALEN);
+ memcpy(__skb_put(nskb, ETH_ALEN), skb->mac.raw, ETH_ALEN);
+ put_unaligned(s->eh.h_proto, (u16 *) __skb_put(nskb, 2));
+ break;
+
+ case BNEP_COMPRESSED_DST_ONLY:
+ memcpy(__skb_put(nskb, ETH_ALEN), skb->mac.raw, ETH_ALEN);
+ memcpy(__skb_put(nskb, ETH_ALEN + 2), s->eh.h_source, ETH_ALEN + 2);
+ break;
+
+ case BNEP_GENERAL:
+ memcpy(__skb_put(nskb, ETH_ALEN * 2), skb->mac.raw, ETH_ALEN * 2);
+ put_unaligned(s->eh.h_proto, (u16 *) __skb_put(nskb, 2));
+ break;
+ }
+
+ memcpy(__skb_put(nskb, skb->len), skb->data, skb->len);
+ kfree_skb(skb);
+
+ s->stats.rx_packets++;
+ nskb->dev = dev;
+ nskb->ip_summed = CHECKSUM_UNNECESSARY;
+ nskb->protocol = eth_type_trans(nskb, dev);
+ netif_rx_ni(nskb);
+ return 0;
+
+badframe:
+ s->stats.rx_errors++;
+ kfree_skb(skb);
+ return 0;
+}
+
+static u8 __bnep_tx_types[] = {
+ BNEP_GENERAL,
+ BNEP_COMPRESSED_SRC_ONLY,
+ BNEP_COMPRESSED_DST_ONLY,
+ BNEP_COMPRESSED
+};
+
+static inline int bnep_tx_frame(struct bnep_session *s, struct sk_buff *skb)
+{
+ struct ethhdr *eh = (void *) skb->data;
+ struct socket *sock = s->sock;
+ struct iovec iv[3];
+ int len = 0, il = 0;
+ u8 type = 0;
+
+ BT_DBG("skb %p dev %p type %d", skb, skb->dev, skb->pkt_type);
+
+ if (!skb->dev) {
+ /* Control frame sent by us */
+ goto send;
+ }
+
+ iv[il++] = (struct iovec) { &type, 1 };
+ len++;
+
+ if (!memcmp(eh->h_dest, s->eh.h_source, ETH_ALEN))
+ type |= 0x01;
+
+ if (!memcmp(eh->h_source, s->eh.h_dest, ETH_ALEN))
+ type |= 0x02;
+
+ if (type)
+ skb_pull(skb, ETH_ALEN * 2);
+
+ type = __bnep_tx_types[type];
+ switch (type) {
+ case BNEP_COMPRESSED_SRC_ONLY:
+ iv[il++] = (struct iovec) { eh->h_source, ETH_ALEN };
+ len += ETH_ALEN;
+ break;
+
+ case BNEP_COMPRESSED_DST_ONLY:
+ iv[il++] = (struct iovec) { eh->h_dest, ETH_ALEN };
+ len += ETH_ALEN;
+ break;
+ }
+
+send:
+ iv[il++] = (struct iovec) { skb->data, skb->len };
+ len += skb->len;
+
+ /* FIXME: linearize skb */
+
+ s->msg.msg_iov = iv;
+ s->msg.msg_iovlen = il;
+ len = sock->ops->sendmsg(sock, &s->msg, len, NULL);
+ kfree_skb(skb);
+
+ if (len > 0) {
+ s->stats.tx_bytes += len;
+ s->stats.tx_packets++;
+ return 0;
+ }
+
+ return len;
+}
+
+static int bnep_session(void *arg)
+{
+ struct bnep_session *s = arg;
+ struct net_device *dev = &s->dev;
+ struct sock *sk = s->sock->sk;
+ struct sk_buff *skb;
+ wait_queue_t wait;
+
+ BT_DBG("");
+
+ daemonize(); reparent_to_init();
+
+ sprintf(current->comm, "kbnepd %s", dev->name);
+
+ sigfillset(&current->blocked);
+ flush_signals(current);
+
+ current->nice = -15;
+
+ set_fs(KERNEL_DS);
+
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(sk->sleep, &wait);
+ while (!atomic_read(&s->killed)) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ // RX
+ while ((skb = skb_dequeue(&sk->receive_queue))) {
+ skb_orphan(skb);
+ bnep_rx_frame(s, skb);
+ }
+
+ if (sk->state != BT_CONNECTED)
+ break;
+
+ // TX
+ while ((skb = skb_dequeue(&sk->write_queue)))
+ if (bnep_tx_frame(s, skb))
+ break;
+ netif_wake_queue(dev);
+
+ schedule();
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(sk->sleep, &wait);
+
+ /* Cleanup session */
+ down_write(&bnep_session_sem);
+
+ /* Delete network device */
+ unregister_netdev(dev);
+
+ /* Release the socket */
+ fput(s->sock->file);
+
+ __bnep_unlink_session(s);
+
+ up_write(&bnep_session_sem);
+ kfree(s);
+ return 0;
+}
+
+int bnep_add_connection(struct bnep_connadd_req *req, struct socket *sock)
+{
+ struct net_device *dev;
+ struct bnep_session *s, *ss;
+ u8 dst[ETH_ALEN], src[ETH_ALEN];
+ int err;
+
+ BT_DBG("");
+
+ baswap((void *) dst, &bluez_pi(sock->sk)->dst);
+ baswap((void *) src, &bluez_pi(sock->sk)->src);
+
+ s = kmalloc(sizeof(struct bnep_session), GFP_KERNEL);
+ if (!s)
+ return -ENOMEM;
+ memset(s, 0, sizeof(struct bnep_session));
+
+ down_write(&bnep_session_sem);
+
+ ss = __bnep_get_session(dst);
+ if (ss && ss->state == BT_CONNECTED) {
+ err = -EEXIST;
+ goto failed;
+ }
+
+ dev = &s->dev;
+
+ if (*req->device)
+ strcpy(dev->name, req->device);
+ else
+ strcpy(dev->name, "bnep%d");
+
+ memset(dev->broadcast, 0xff, ETH_ALEN);
+
+ /* This is rx header therefor addresses are swaped.
+ * ie eh.h_dest is our local address. */
+ memcpy(s->eh.h_dest, &src, ETH_ALEN);
+ memcpy(s->eh.h_source, &dst, ETH_ALEN);
+
+ s->sock = sock;
+ s->role = req->role;
+ s->state = BT_CONNECTED;
+
+ s->msg.msg_flags = MSG_NOSIGNAL;
+
+#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER
+ /* Set default mc filter */
+ set_bit(bnep_mc_hash(dev->broadcast), &s->mc_filter);
+#endif
+
+#ifdef CONFIG_BLUEZ_BNEP_PROTO_FILTER
+ /* Set default protocol filter */
+ bnep_set_default_proto_filter(s);
+#endif
+
+ dev->init = bnep_net_init;
+ dev->priv = s;
+ err = register_netdev(dev);
+ if (err) {
+ goto failed;
+ }
+
+ __bnep_link_session(s);
+
+ err = kernel_thread(bnep_session, s, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
+ if (err < 0) {
+ /* Session thread start failed, gotta cleanup. */
+ unregister_netdev(dev);
+ __bnep_unlink_session(s);
+ goto failed;
+ }
+
+ up_write(&bnep_session_sem);
+ strcpy(req->device, dev->name);
+ return 0;
+
+failed:
+ up_write(&bnep_session_sem);
+ kfree(s);
+ return err;
+}
+
+int bnep_del_connection(struct bnep_conndel_req *req)
+{
+ struct bnep_session *s;
+ int err = 0;
+
+ BT_DBG("");
+
+ down_read(&bnep_session_sem);
+
+ s = __bnep_get_session(req->dst);
+ if (s) {
+ /* Wakeup user-space which is polling for socket errors.
+ * This is temporary hack untill we have shutdown in L2CAP */
+ s->sock->sk->err = EUNATCH;
+
+ /* Kill session thread */
+ atomic_inc(&s->killed);
+ wake_up_interruptible(s->sock->sk->sleep);
+ } else
+ err = -ENOENT;
+
+ up_read(&bnep_session_sem);
+ return err;
+}
+
+static void __bnep_copy_ci(struct bnep_conninfo *ci, struct bnep_session *s)
+{
+ memcpy(ci->dst, s->eh.h_source, ETH_ALEN);
+ strcpy(ci->device, s->dev.name);
+ ci->flags = s->flags;
+ ci->state = s->state;
+ ci->role = s->role;
+}
+
+int bnep_get_connlist(struct bnep_connlist_req *req)
+{
+ struct list_head *p;
+ int err = 0, n = 0;
+
+ down_read(&bnep_session_sem);
+
+ list_for_each(p, &bnep_session_list) {
+ struct bnep_session *s;
+ struct bnep_conninfo ci;
+
+ s = list_entry(p, struct bnep_session, list);
+
+ __bnep_copy_ci(&ci, s);
+
+ if (copy_to_user(req->ci, &ci, sizeof(ci))) {
+ err = -EFAULT;
+ break;
+ }
+
+ if (++n >= req->cnum)
+ break;
+
+ req->ci++;
+ }
+ req->cnum = n;
+
+ up_read(&bnep_session_sem);
+ return err;
+}
+
+int bnep_get_conninfo(struct bnep_conninfo *ci)
+{
+ struct bnep_session *s;
+ int err = 0;
+
+ down_read(&bnep_session_sem);
+
+ s = __bnep_get_session(ci->dst);
+ if (s)
+ __bnep_copy_ci(ci, s);
+ else
+ err = -ENOENT;
+
+ up_read(&bnep_session_sem);
+ return err;
+}
+
+static int __init bnep_init_module(void)
+{
+ l2cap_load();
+
+ bnep_sock_init();
+
+ BT_INFO("BlueZ BNEP ver %s", VERSION);
+ BT_INFO("Copyright (C) 2001,2002 Inventel Systemes");
+ BT_INFO("Written 2001,2002 by Clement Moreau <clement.moreau@inventel.fr>");
+ BT_INFO("Written 2001,2002 by David Libault <david.libault@inventel.fr>");
+ BT_INFO("Copyright (C) 2002 Maxim Krasnyanskiy <maxk@qualcomm.com>");
+
+ return 0;
+}
+
+static void __exit bnep_cleanup_module(void)
+{
+ bnep_sock_cleanup();
+}
+
+module_init(bnep_init_module);
+module_exit(bnep_cleanup_module);
+
+MODULE_DESCRIPTION("BlueZ BNEP ver " VERSION);
+MODULE_AUTHOR("David Libault <david.libault@inventel.fr>, Maxim Krasnyanskiy <maxk@qualcomm.com>");
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/bnep/netdev.c b/uClinux-2.4.31-uc0/net/bluetooth/bnep/netdev.c
new file mode 100644
index 0000000..6ac7742
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/bnep/netdev.c
@@ -0,0 +1,254 @@
+/*
+ BNEP implementation for Linux Bluetooth stack (BlueZ).
+ Copyright (C) 2001-2002 Inventel Systemes
+ Written 2001-2002 by
+ Clément Moreau <clement.moreau@inventel.fr>
+ David Libault <david.libault@inventel.fr>
+
+ Copyright (C) 2002 Maxim Krasnyanskiy <maxk@qualcomm.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * $Id: netdev.c,v 1.7 2002/07/14 05:39:26 maxk Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/socket.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/wait.h>
+
+#include <asm/unaligned.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+#include <net/bluetooth/l2cap.h>
+
+#include "bnep.h"
+
+#ifndef CONFIG_BLUEZ_BNEP_DEBUG
+#undef BT_DBG
+#define BT_DBG( A... )
+#endif
+
+#define BNEP_TX_QUEUE_LEN 20
+
+static int bnep_net_open(struct net_device *dev)
+{
+ netif_start_queue(dev);
+ return 0;
+}
+
+static int bnep_net_close(struct net_device *dev)
+{
+ netif_stop_queue(dev);
+ return 0;
+}
+
+static struct net_device_stats *bnep_net_get_stats(struct net_device *dev)
+{
+ struct bnep_session *s = dev->priv;
+ return &s->stats;
+}
+
+static void bnep_net_set_mc_list(struct net_device *dev)
+{
+#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER
+ struct bnep_session *s = dev->priv;
+ struct sock *sk = s->sock->sk;
+ struct bnep_set_filter_req *r;
+ struct sk_buff *skb;
+ int size;
+
+ BT_DBG("%s mc_count %d", dev->name, dev->mc_count);
+
+ size = sizeof(*r) + (BNEP_MAX_MULTICAST_FILTERS + 1) * ETH_ALEN * 2;
+ skb = alloc_skb(size, GFP_ATOMIC);
+ if (!skb) {
+ BT_ERR("%s Multicast list allocation failed", dev->name);
+ return;
+ }
+
+ r = (void *) skb->data;
+ __skb_put(skb, sizeof(*r));
+
+ r->type = BNEP_CONTROL;
+ r->ctrl = BNEP_FILTER_MULTI_ADDR_SET;
+
+ if (dev->flags & (IFF_PROMISC | IFF_ALLMULTI)) {
+ u8 start[ETH_ALEN] = { 0x01 };
+
+ /* Request all addresses */
+ memcpy(__skb_put(skb, ETH_ALEN), start, ETH_ALEN);
+ memcpy(__skb_put(skb, ETH_ALEN), dev->broadcast, ETH_ALEN);
+ r->len = htons(ETH_ALEN * 2);
+ } else {
+ struct dev_mc_list *dmi = dev->mc_list;
+ int i, len = skb->len;
+
+ if (dev->flags & IFF_BROADCAST) {
+ memcpy(__skb_put(skb, ETH_ALEN), dev->broadcast, ETH_ALEN);
+ memcpy(__skb_put(skb, ETH_ALEN), dev->broadcast, ETH_ALEN);
+ }
+
+ /* FIXME: We should group addresses here. */
+
+ for (i = 0; i < dev->mc_count && i < BNEP_MAX_MULTICAST_FILTERS; i++) {
+ memcpy(__skb_put(skb, ETH_ALEN), dmi->dmi_addr, ETH_ALEN);
+ memcpy(__skb_put(skb, ETH_ALEN), dmi->dmi_addr, ETH_ALEN);
+ dmi = dmi->next;
+ }
+ r->len = htons(skb->len - len);
+ }
+
+ skb_queue_tail(&sk->write_queue, skb);
+ wake_up_interruptible(sk->sleep);
+#endif
+}
+
+static int bnep_net_set_mac_addr(struct net_device *dev, void *arg)
+{
+ BT_DBG("%s", dev->name);
+ return 0;
+}
+
+static void bnep_net_timeout(struct net_device *dev)
+{
+ BT_DBG("net_timeout");
+ netif_wake_queue(dev);
+}
+
+static int bnep_net_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ return -EINVAL;
+}
+
+#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER
+static inline int bnep_net_mc_filter(struct sk_buff *skb, struct bnep_session *s)
+{
+ struct ethhdr *eh = (void *) skb->data;
+
+ if ((eh->h_dest[0] & 1) && !test_bit(bnep_mc_hash(eh->h_dest), &s->mc_filter)) {
+ BT_DBG("BNEP: filtered skb %p, dst %.2x:%.2x:%.2x:%.2x:%.2x:%.2x", skb,
+ eh->h_dest[0], eh->h_dest[1], eh->h_dest[2],
+ eh->h_dest[3], eh->h_dest[4], eh->h_dest[5]);
+ return 1;
+ }
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_BLUEZ_BNEP_PROTO_FILTER
+/* Determine ether protocol. Based on eth_type_trans. */
+static inline u16 bnep_net_eth_proto(struct sk_buff *skb)
+{
+ struct ethhdr *eh = (void *) skb->data;
+
+ if (ntohs(eh->h_proto) >= 1536)
+ return eh->h_proto;
+
+ if (get_unaligned((u16 *) skb->data) == 0xFFFF)
+ return htons(ETH_P_802_3);
+
+ return htons(ETH_P_802_2);
+}
+
+static inline int bnep_net_proto_filter(struct sk_buff *skb, struct bnep_session *s)
+{
+ u16 proto = bnep_net_eth_proto(skb);
+ struct bnep_proto_filter *f = s->proto_filter;
+ int i;
+
+ for (i = 0; i < BNEP_MAX_PROTO_FILTERS && f[i].end; i++) {
+ if (proto >= f[i].start && proto <= f[i].end)
+ return 0;
+ }
+
+ BT_DBG("BNEP: filtered skb %p, proto 0x%.4x", skb, proto);
+ return 1;
+}
+#endif
+
+static int bnep_net_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct bnep_session *s = dev->priv;
+ struct sock *sk = s->sock->sk;
+
+ BT_DBG("skb %p, dev %p", skb, dev);
+
+#ifdef CONFIG_BLUEZ_BNEP_MC_FILTER
+ if (bnep_net_mc_filter(skb, s)) {
+ kfree_skb(skb);
+ return 0;
+ }
+#endif
+
+#ifdef CONFIG_BLUEZ_BNEP_PROTO_FILTER
+ if (bnep_net_proto_filter(skb, s)) {
+ kfree_skb(skb);
+ return 0;
+ }
+#endif
+
+ /*
+ * We cannot send L2CAP packets from here as we are potentially in a bh.
+ * So we have to queue them and wake up session thread which is sleeping
+ * on the sk->sleep.
+ */
+ dev->trans_start = jiffies;
+ skb_queue_tail(&sk->write_queue, skb);
+ wake_up_interruptible(sk->sleep);
+
+ if (skb_queue_len(&sk->write_queue) >= BNEP_TX_QUEUE_LEN) {
+ BT_DBG("tx queue is full");
+
+ /* Stop queuing.
+ * Session thread will do netif_wake_queue() */
+ netif_stop_queue(dev);
+ }
+
+ return 0;
+}
+
+int bnep_net_init(struct net_device *dev)
+{
+ struct bnep_session *s = dev->priv;
+
+ memcpy(dev->dev_addr, s->eh.h_dest, ETH_ALEN);
+ dev->addr_len = ETH_ALEN;
+
+ ether_setup(dev);
+
+ dev->open = bnep_net_open;
+ dev->stop = bnep_net_close;
+ dev->hard_start_xmit = bnep_net_xmit;
+ dev->get_stats = bnep_net_get_stats;
+ dev->do_ioctl = bnep_net_ioctl;
+ dev->set_mac_address = bnep_net_set_mac_addr;
+ dev->set_multicast_list = bnep_net_set_mc_list;
+
+ dev->watchdog_timeo = HZ * 2;
+ dev->tx_timeout = bnep_net_timeout;
+
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/bnep/sock.c b/uClinux-2.4.31-uc0/net/bluetooth/bnep/sock.c
new file mode 100644
index 0000000..ee1ce59
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/bnep/sock.c
@@ -0,0 +1,210 @@
+/*
+ BNEP implementation for Linux Bluetooth stack (BlueZ).
+ Copyright (C) 2001-2002 Inventel Systemes
+ Written 2001-2002 by
+ David Libault <david.libault@inventel.fr>
+
+ Copyright (C) 2002 Maxim Krasnyanskiy <maxk@qualcomm.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * $Id: sock.c,v 1.3 2002/07/10 22:59:52 maxk Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/fcntl.h>
+#include <linux/skbuff.h>
+#include <linux/socket.h>
+#include <linux/ioctl.h>
+#include <linux/file.h>
+#include <net/sock.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+#include "bnep.h"
+
+#ifndef CONFIG_BLUEZ_BNEP_DEBUG
+#undef BT_DBG
+#define BT_DBG( A... )
+#endif
+
+static int bnep_sock_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ BT_DBG("sock %p sk %p", sock, sk);
+
+ if (!sk)
+ return 0;
+
+ sock_orphan(sk);
+ sock_put(sk);
+
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int bnep_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct bnep_connlist_req cl;
+ struct bnep_connadd_req ca;
+ struct bnep_conndel_req cd;
+ struct bnep_conninfo ci;
+ struct socket *nsock;
+ int err;
+
+ BT_DBG("cmd %x arg %lx", cmd, arg);
+
+ switch (cmd) {
+ case BNEPCONNADD:
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+
+ if (copy_from_user(&ca, (void *) arg, sizeof(ca)))
+ return -EFAULT;
+
+ nsock = sockfd_lookup(ca.sock, &err);
+ if (!nsock)
+ return err;
+
+ if (nsock->sk->state != BT_CONNECTED) {
+ fput(nsock->file);
+ return -EBADFD;
+ }
+
+ err = bnep_add_connection(&ca, nsock);
+ if (!err) {
+ if (copy_to_user((void *) arg, &ca, sizeof(ca)))
+ err = -EFAULT;
+ } else
+ fput(nsock->file);
+
+ return err;
+
+ case BNEPCONNDEL:
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+
+ if (copy_from_user(&cd, (void *) arg, sizeof(cd)))
+ return -EFAULT;
+
+ return bnep_del_connection(&cd);
+
+ case BNEPGETCONNLIST:
+ if (copy_from_user(&cl, (void *) arg, sizeof(cl)))
+ return -EFAULT;
+
+ if (cl.cnum <= 0)
+ return -EINVAL;
+
+ err = bnep_get_connlist(&cl);
+ if (!err && copy_to_user((void *) arg, &cl, sizeof(cl)))
+ return -EFAULT;
+
+ return err;
+
+ case BNEPGETCONNINFO:
+ if (copy_from_user(&ci, (void *) arg, sizeof(ci)))
+ return -EFAULT;
+
+ err = bnep_get_conninfo(&ci);
+ if (!err && copy_to_user((void *) arg, &ci, sizeof(ci)))
+ return -EFAULT;
+
+ return err;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct proto_ops bnep_sock_ops = {
+ family: PF_BLUETOOTH,
+ release: bnep_sock_release,
+ ioctl: bnep_sock_ioctl,
+ bind: sock_no_bind,
+ getname: sock_no_getname,
+ sendmsg: sock_no_sendmsg,
+ recvmsg: sock_no_recvmsg,
+ poll: sock_no_poll,
+ listen: sock_no_listen,
+ shutdown: sock_no_shutdown,
+ setsockopt: sock_no_setsockopt,
+ getsockopt: sock_no_getsockopt,
+ connect: sock_no_connect,
+ socketpair: sock_no_socketpair,
+ accept: sock_no_accept,
+ mmap: sock_no_mmap
+};
+
+static int bnep_sock_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+
+ BT_DBG("sock %p", sock);
+
+ if (sock->type != SOCK_RAW)
+ return -ESOCKTNOSUPPORT;
+
+ sock->ops = &bnep_sock_ops;
+
+ if (!(sk = sk_alloc(PF_BLUETOOTH, GFP_KERNEL, 1)))
+ return -ENOMEM;
+
+ MOD_INC_USE_COUNT;
+
+ sock->state = SS_UNCONNECTED;
+ sock_init_data(sock, sk);
+
+ sk->destruct = NULL;
+ sk->protocol = protocol;
+
+ return 0;
+}
+
+static struct net_proto_family bnep_sock_family_ops = {
+ family: PF_BLUETOOTH,
+ create: bnep_sock_create
+};
+
+int bnep_sock_init(void)
+{
+ bluez_sock_register(BTPROTO_BNEP, &bnep_sock_family_ops);
+ return 0;
+}
+
+int bnep_sock_cleanup(void)
+{
+ if (bluez_sock_unregister(BTPROTO_BNEP))
+ BT_ERR("Can't unregister BNEP socket");
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/cmtp/Config.in b/uClinux-2.4.31-uc0/net/bluetooth/cmtp/Config.in
new file mode 100644
index 0000000..ad29842
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/cmtp/Config.in
@@ -0,0 +1,7 @@
+#
+# Bluetooth CMTP layer configuration
+#
+
+if [ "$CONFIG_ISDN" = "y" -o "$CONFIG_ISDN" = "m" ]; then
+ dep_tristate 'CMTP protocol support' CONFIG_BLUEZ_CMTP $CONFIG_ISDN_CAPI $CONFIG_BLUEZ_L2CAP
+fi
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/cmtp/Makefile b/uClinux-2.4.31-uc0/net/bluetooth/cmtp/Makefile
new file mode 100644
index 0000000..a486e61
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/cmtp/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for the Linux Bluetooth CMTP layer
+#
+
+O_TARGET := cmtp.o
+
+obj-y := core.o sock.o capi.o
+obj-m += $(O_TARGET)
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/cmtp/capi.c b/uClinux-2.4.31-uc0/net/bluetooth/cmtp/capi.c
new file mode 100644
index 0000000..cc91b75
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/cmtp/capi.c
@@ -0,0 +1,707 @@
+/*
+ CMTP implementation for Linux Bluetooth stack (BlueZ).
+ Copyright (C) 2002-2003 Marcel Holtmann <marcel@holtmann.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/fcntl.h>
+#include <linux/skbuff.h>
+#include <linux/socket.h>
+#include <linux/ioctl.h>
+#include <linux/file.h>
+#include <net/sock.h>
+
+#include <linux/capi.h>
+
+#include "../drivers/isdn/avmb1/capilli.h"
+#include "../drivers/isdn/avmb1/capicmd.h"
+#include "../drivers/isdn/avmb1/capiutil.h"
+
+#include "cmtp.h"
+
+#ifndef CONFIG_BLUEZ_CMTP_DEBUG
+#undef BT_DBG
+#define BT_DBG(D...)
+#endif
+
+#define REVISION "1.0"
+
+#define CAPI_INTEROPERABILITY 0x20
+
+#define CAPI_INTEROPERABILITY_REQ CAPICMD(CAPI_INTEROPERABILITY, CAPI_REQ)
+#define CAPI_INTEROPERABILITY_CONF CAPICMD(CAPI_INTEROPERABILITY, CAPI_CONF)
+#define CAPI_INTEROPERABILITY_IND CAPICMD(CAPI_INTEROPERABILITY, CAPI_IND)
+#define CAPI_INTEROPERABILITY_RESP CAPICMD(CAPI_INTEROPERABILITY, CAPI_RESP)
+
+#define CAPI_INTEROPERABILITY_REQ_LEN (CAPI_MSG_BASELEN + 2)
+#define CAPI_INTEROPERABILITY_CONF_LEN (CAPI_MSG_BASELEN + 4)
+#define CAPI_INTEROPERABILITY_IND_LEN (CAPI_MSG_BASELEN + 2)
+#define CAPI_INTEROPERABILITY_RESP_LEN (CAPI_MSG_BASELEN + 2)
+
+#define CAPI_FUNCTION_REGISTER 0
+#define CAPI_FUNCTION_RELEASE 1
+#define CAPI_FUNCTION_GET_PROFILE 2
+#define CAPI_FUNCTION_GET_MANUFACTURER 3
+#define CAPI_FUNCTION_GET_VERSION 4
+#define CAPI_FUNCTION_GET_SERIAL_NUMBER 5
+#define CAPI_FUNCTION_MANUFACTURER 6
+#define CAPI_FUNCTION_LOOPBACK 7
+
+static struct capi_driver_interface *di;
+
+
+#define CMTP_MSGNUM 1
+#define CMTP_APPLID 2
+#define CMTP_MAPPING 3
+
+static struct cmtp_application *cmtp_application_add(struct cmtp_session *session, __u16 appl)
+{
+ struct cmtp_application *app = kmalloc(sizeof(*app), GFP_KERNEL);
+
+ BT_DBG("session %p application %p appl %d", session, app, appl);
+
+ if (!app)
+ return NULL;
+
+ memset(app, 0, sizeof(*app));
+
+ app->state = BT_OPEN;
+ app->appl = appl;
+
+ list_add_tail(&app->list, &session->applications);
+
+ return app;
+}
+
+static void cmtp_application_del(struct cmtp_session *session, struct cmtp_application *app)
+{
+ BT_DBG("session %p application %p", session, app);
+
+ if (app) {
+ list_del(&app->list);
+ kfree(app);
+ }
+}
+
+static struct cmtp_application *cmtp_application_get(struct cmtp_session *session, int pattern, __u16 value)
+{
+ struct cmtp_application *app;
+ struct list_head *p, *n;
+
+ list_for_each_safe(p, n, &session->applications) {
+ app = list_entry(p, struct cmtp_application, list);
+ switch (pattern) {
+ case CMTP_MSGNUM:
+ if (app->msgnum == value)
+ return app;
+ break;
+ case CMTP_APPLID:
+ if (app->appl == value)
+ return app;
+ break;
+ case CMTP_MAPPING:
+ if (app->mapping == value)
+ return app;
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+static int cmtp_msgnum_get(struct cmtp_session *session)
+{
+ session->msgnum++;
+
+ if ((session->msgnum & 0xff) > 200)
+ session->msgnum = CMTP_INITIAL_MSGNUM + 1;
+
+ return session->msgnum;
+}
+
+
+static void cmtp_send_interopmsg(struct cmtp_session *session,
+ __u8 subcmd, __u16 appl, __u16 msgnum,
+ __u16 function, unsigned char *buf, int len)
+{
+ struct sk_buff *skb;
+ unsigned char *s;
+
+ BT_DBG("session %p subcmd 0x%02x appl %d msgnum %d", session, subcmd, appl, msgnum);
+
+ if (!(skb = alloc_skb(CAPI_MSG_BASELEN + 6 + len, GFP_ATOMIC))) {
+ BT_ERR("Can't allocate memory for interoperability packet");
+ return;
+ }
+
+ s = skb_put(skb, CAPI_MSG_BASELEN + 6 + len);
+
+ capimsg_setu16(s, 0, CAPI_MSG_BASELEN + 6 + len);
+ capimsg_setu16(s, 2, appl);
+ capimsg_setu8 (s, 4, CAPI_INTEROPERABILITY);
+ capimsg_setu8 (s, 5, subcmd);
+ capimsg_setu16(s, 6, msgnum);
+
+ /* Interoperability selector (Bluetooth Device Management) */
+ capimsg_setu16(s, 8, 0x0001);
+
+ capimsg_setu8 (s, 10, 3 + len);
+ capimsg_setu16(s, 11, function);
+ capimsg_setu8 (s, 13, len);
+
+ if (len > 0)
+ memcpy(s + 14, buf, len);
+
+ cmtp_send_capimsg(session, skb);
+}
+
+static void cmtp_recv_interopmsg(struct cmtp_session *session, struct sk_buff *skb)
+{
+ struct capi_ctr *ctrl = session->ctrl;
+ struct cmtp_application *application;
+ __u16 appl, msgnum, func, info;
+ __u32 controller;
+
+ BT_DBG("session %p skb %p len %d", session, skb, skb->len);
+
+ switch (CAPIMSG_SUBCOMMAND(skb->data)) {
+ case CAPI_CONF:
+ func = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 5);
+ info = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 8);
+
+ switch (func) {
+ case CAPI_FUNCTION_REGISTER:
+ msgnum = CAPIMSG_MSGID(skb->data);
+
+ application = cmtp_application_get(session, CMTP_MSGNUM, msgnum);
+ if (application) {
+ application->state = BT_CONNECTED;
+ application->msgnum = 0;
+ application->mapping = CAPIMSG_APPID(skb->data);
+ wake_up_interruptible(&session->wait);
+ }
+
+ break;
+
+ case CAPI_FUNCTION_RELEASE:
+ appl = CAPIMSG_APPID(skb->data);
+
+ application = cmtp_application_get(session, CMTP_MAPPING, appl);
+ if (application) {
+ application->state = BT_CLOSED;
+ application->msgnum = 0;
+ wake_up_interruptible(&session->wait);
+ }
+
+ break;
+
+ case CAPI_FUNCTION_GET_PROFILE:
+ controller = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 11);
+ msgnum = CAPIMSG_MSGID(skb->data);
+
+ if (!info && (msgnum == CMTP_INITIAL_MSGNUM)) {
+ session->ncontroller = controller;
+ wake_up_interruptible(&session->wait);
+ break;
+ }
+
+ if (!info && ctrl) {
+ memcpy(&ctrl->profile,
+ skb->data + CAPI_MSG_BASELEN + 11,
+ sizeof(capi_profile));
+ session->state = BT_CONNECTED;
+ ctrl->ready(ctrl);
+ }
+
+ break;
+
+ case CAPI_FUNCTION_GET_MANUFACTURER:
+ controller = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 10);
+
+ if (!info && ctrl) {
+ strncpy(ctrl->manu,
+ skb->data + CAPI_MSG_BASELEN + 15,
+ skb->data[CAPI_MSG_BASELEN + 14]);
+ }
+
+ break;
+
+ case CAPI_FUNCTION_GET_VERSION:
+ controller = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 12);
+
+ if (!info && ctrl) {
+ ctrl->version.majorversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 16);
+ ctrl->version.minorversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 20);
+ ctrl->version.majormanuversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 24);
+ ctrl->version.minormanuversion = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 28);
+ }
+
+ break;
+
+ case CAPI_FUNCTION_GET_SERIAL_NUMBER:
+ controller = CAPIMSG_U32(skb->data, CAPI_MSG_BASELEN + 12);
+
+ if (!info && ctrl) {
+ memset(ctrl->serial, 0, CAPI_SERIAL_LEN);
+ strncpy(ctrl->serial,
+ skb->data + CAPI_MSG_BASELEN + 17,
+ skb->data[CAPI_MSG_BASELEN + 16]);
+ }
+
+ break;
+ }
+
+ break;
+
+ case CAPI_IND:
+ func = CAPIMSG_U16(skb->data, CAPI_MSG_BASELEN + 3);
+
+ if (func == CAPI_FUNCTION_LOOPBACK) {
+ appl = CAPIMSG_APPID(skb->data);
+ msgnum = CAPIMSG_MSGID(skb->data);
+ cmtp_send_interopmsg(session, CAPI_RESP, appl, msgnum, func,
+ skb->data + CAPI_MSG_BASELEN + 6,
+ skb->data[CAPI_MSG_BASELEN + 5]);
+ }
+
+ break;
+ }
+
+ kfree_skb(skb);
+}
+
+void cmtp_recv_capimsg(struct cmtp_session *session, struct sk_buff *skb)
+{
+ struct capi_ctr *ctrl = session->ctrl;
+ struct cmtp_application *application;
+ __u16 cmd, appl, info;
+ __u32 ncci, contr;
+
+ BT_DBG("session %p skb %p len %d", session, skb, skb->len);
+
+ if (CAPIMSG_COMMAND(skb->data) == CAPI_INTEROPERABILITY) {
+ cmtp_recv_interopmsg(session, skb);
+ return;
+ }
+
+ if (session->flags & (1 << CMTP_LOOPBACK)) {
+ kfree_skb(skb);
+ return;
+ }
+
+ cmd = CAPICMD(CAPIMSG_COMMAND(skb->data), CAPIMSG_SUBCOMMAND(skb->data));
+ appl = CAPIMSG_APPID(skb->data);
+ contr = CAPIMSG_CONTROL(skb->data);
+
+ application = cmtp_application_get(session, CMTP_MAPPING, appl);
+ if (application) {
+ appl = application->appl;
+ CAPIMSG_SETAPPID(skb->data, appl);
+ } else {
+ BT_ERR("Can't find application with id %d", appl);
+ kfree_skb(skb);
+ return;
+ }
+
+ if ((contr & 0x7f) == 0x01) {
+ contr = (contr & 0xffffff80) | session->num;
+ CAPIMSG_SETCONTROL(skb->data, contr);
+ }
+
+ if (!ctrl) {
+ BT_ERR("Can't find controller %d for message", session->num);
+ kfree_skb(skb);
+ return;
+ }
+
+ switch (cmd) {
+ case CAPI_CONNECT_B3_CONF:
+ ncci = CAPIMSG_NCCI(skb->data);
+ info = CAPIMSG_U16(skb->data, 12);
+
+ BT_DBG("CONNECT_B3_CONF ncci 0x%02x info 0x%02x", ncci, info);
+
+ if (info == 0)
+ ctrl->new_ncci(ctrl, appl, ncci, 8);
+
+ ctrl->handle_capimsg(ctrl, appl, skb);
+ break;
+
+ case CAPI_CONNECT_B3_IND:
+ ncci = CAPIMSG_NCCI(skb->data);
+
+ BT_DBG("CONNECT_B3_IND ncci 0x%02x", ncci);
+
+ ctrl->new_ncci(ctrl, appl, ncci, 8);
+ ctrl->handle_capimsg(ctrl, appl, skb);
+ break;
+
+ case CAPI_DISCONNECT_B3_IND:
+ ncci = CAPIMSG_NCCI(skb->data);
+
+ BT_DBG("DISCONNECT_B3_IND ncci 0x%02x", ncci);
+
+ if (ncci == 0xffffffff)
+ BT_ERR("DISCONNECT_B3_IND with ncci 0xffffffff");
+
+ ctrl->handle_capimsg(ctrl, appl, skb);
+ ctrl->free_ncci(ctrl, appl, ncci);
+ break;
+
+ default:
+ ctrl->handle_capimsg(ctrl, appl, skb);
+ break;
+ }
+}
+
+void cmtp_send_capimsg(struct cmtp_session *session, struct sk_buff *skb)
+{
+ struct cmtp_scb *scb = (void *) skb->cb;
+
+ BT_DBG("session %p skb %p len %d", session, skb, skb->len);
+
+ scb->id = -1;
+ scb->data = (CAPIMSG_COMMAND(skb->data) == CAPI_DATA_B3);
+
+ skb_queue_tail(&session->transmit, skb);
+
+ cmtp_schedule(session);
+}
+
+
+static int cmtp_load_firmware(struct capi_ctr *ctrl, capiloaddata *data)
+{
+ BT_DBG("ctrl %p data %p", ctrl, data);
+
+ return -EIO;
+}
+
+static void cmtp_reset_ctr(struct capi_ctr *ctrl)
+{
+ BT_DBG("ctrl %p", ctrl);
+
+ ctrl->reseted(ctrl);
+}
+
+static void cmtp_remove_ctr(struct capi_ctr *ctrl)
+{
+ struct cmtp_session *session = ctrl->driverdata;
+
+ BT_DBG("ctrl %p", ctrl);
+
+ ctrl->suspend_output(ctrl);
+
+ atomic_inc(&session->terminate);
+ cmtp_schedule(session);
+}
+
+static void cmtp_register_appl(struct capi_ctr *ctrl, __u16 appl, capi_register_params *rp)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct cmtp_session *session = ctrl->driverdata;
+ struct cmtp_application *application;
+ unsigned long timeo = CMTP_INTEROP_TIMEOUT;
+ unsigned char buf[8];
+ int err = 0, nconn, want = rp->level3cnt;
+
+ BT_DBG("ctrl %p appl %d level3cnt %d datablkcnt %d datablklen %d",
+ ctrl, appl, rp->level3cnt, rp->datablkcnt, rp->datablklen);
+
+ application = cmtp_application_add(session, appl);
+ if (!application) {
+ BT_ERR("Can't allocate memory for new application");
+ ctrl->appl_released(ctrl, appl);
+ return;
+ }
+
+ if (want < 0)
+ nconn = ctrl->profile.nbchannel * -want;
+ else
+ nconn = want;
+
+ if (nconn == 0)
+ nconn = ctrl->profile.nbchannel;
+
+ capimsg_setu16(buf, 0, nconn);
+ capimsg_setu16(buf, 2, rp->datablkcnt);
+ capimsg_setu16(buf, 4, rp->datablklen);
+
+ application->state = BT_CONFIG;
+ application->msgnum = cmtp_msgnum_get(session);
+
+ cmtp_send_interopmsg(session, CAPI_REQ, 0x0000, application->msgnum,
+ CAPI_FUNCTION_REGISTER, buf, 6);
+
+ add_wait_queue(&session->wait, &wait);
+ while (1) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (!timeo) {
+ err = -EAGAIN;
+ break;
+ }
+
+ if (application->state == BT_CLOSED) {
+ err = -application->err;
+ break;
+ }
+
+ if (application->state == BT_CONNECTED)
+ break;
+
+ if (signal_pending(current)) {
+ err = -EINTR;
+ break;
+ }
+
+ timeo = schedule_timeout(timeo);
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&session->wait, &wait);
+
+ if (err) {
+ ctrl->appl_released(ctrl, appl);
+ cmtp_application_del(session, application);
+ return;
+ }
+
+ ctrl->appl_registered(ctrl, appl);
+}
+
+static void cmtp_release_appl(struct capi_ctr *ctrl, __u16 appl)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct cmtp_session *session = ctrl->driverdata;
+ struct cmtp_application *application;
+ unsigned long timeo = CMTP_INTEROP_TIMEOUT;
+
+ BT_DBG("ctrl %p appl %d", ctrl, appl);
+
+ application = cmtp_application_get(session, CMTP_APPLID, appl);
+ if (!application) {
+ BT_ERR("Can't find application");
+ return;
+ }
+
+ application->msgnum = cmtp_msgnum_get(session);
+
+ cmtp_send_interopmsg(session, CAPI_REQ, application->mapping, application->msgnum,
+ CAPI_FUNCTION_RELEASE, NULL, 0);
+
+ add_wait_queue(&session->wait, &wait);
+ while (timeo) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (application->state == BT_CLOSED)
+ break;
+
+ if (signal_pending(current))
+ break;
+
+ timeo = schedule_timeout(timeo);
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&session->wait, &wait);
+
+ cmtp_application_del(session, application);
+ ctrl->appl_released(ctrl, appl);
+}
+
+static void cmtp_send_message(struct capi_ctr *ctrl, struct sk_buff *skb)
+{
+ struct cmtp_session *session = ctrl->driverdata;
+ struct cmtp_application *application;
+ __u16 appl;
+ __u32 contr;
+
+ BT_DBG("ctrl %p skb %p", ctrl, skb);
+
+ appl = CAPIMSG_APPID(skb->data);
+ contr = CAPIMSG_CONTROL(skb->data);
+
+ application = cmtp_application_get(session, CMTP_APPLID, appl);
+ if ((!application) || (application->state != BT_CONNECTED)) {
+ BT_ERR("Can't find application with id %d", appl);
+ kfree_skb(skb);
+ return;
+ }
+
+ CAPIMSG_SETAPPID(skb->data, application->mapping);
+
+ if ((contr & 0x7f) == session->num) {
+ contr = (contr & 0xffffff80) | 0x01;
+ CAPIMSG_SETCONTROL(skb->data, contr);
+ }
+
+ cmtp_send_capimsg(session, skb);
+}
+
+static char *cmtp_procinfo(struct capi_ctr *ctrl)
+{
+ return "CAPI Message Transport Protocol";
+}
+
+static int cmtp_ctr_read_proc(char *page, char **start, off_t off, int count, int *eof, struct capi_ctr *ctrl)
+{
+ struct cmtp_session *session = ctrl->driverdata;
+ struct cmtp_application *app;
+ struct list_head *p, *n;
+ int len = 0;
+
+ len += sprintf(page + len, "%s (Revision %s)\n\n", cmtp_procinfo(ctrl), REVISION);
+ len += sprintf(page + len, "addr %s\n", session->name);
+ len += sprintf(page + len, "ctrl %d\n", session->num);
+
+ list_for_each_safe(p, n, &session->applications) {
+ app = list_entry(p, struct cmtp_application, list);
+ len += sprintf(page + len, "appl %d -> %d\n", app->appl, app->mapping);
+ }
+
+ if (off + count >= len)
+ *eof = 1;
+
+ if (len < off)
+ return 0;
+
+ *start = page + off;
+
+ return ((count < len - off) ? count : len - off);
+}
+
+static struct capi_driver cmtp_driver = {
+ name: "cmtp",
+ revision: REVISION,
+ load_firmware: cmtp_load_firmware,
+ reset_ctr: cmtp_reset_ctr,
+ remove_ctr: cmtp_remove_ctr,
+ register_appl: cmtp_register_appl,
+ release_appl: cmtp_release_appl,
+ send_message: cmtp_send_message,
+ procinfo: cmtp_procinfo,
+ ctr_read_proc: cmtp_ctr_read_proc,
+
+ driver_read_proc: 0,
+ add_card: 0,
+};
+
+
+int cmtp_attach_device(struct cmtp_session *session)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long timeo = CMTP_INTEROP_TIMEOUT;
+ unsigned char buf[4];
+
+ BT_DBG("session %p", session);
+
+ capimsg_setu32(buf, 0, 0);
+
+ cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, CMTP_INITIAL_MSGNUM,
+ CAPI_FUNCTION_GET_PROFILE, buf, 4);
+
+ add_wait_queue(&session->wait, &wait);
+ while (timeo) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (session->ncontroller)
+ break;
+
+ if (signal_pending(current))
+ break;
+
+ timeo = schedule_timeout(timeo);
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&session->wait, &wait);
+
+ BT_INFO("Found %d CAPI controller(s) on device %s", session->ncontroller, session->name);
+
+ if (!timeo)
+ return -ETIMEDOUT;
+
+ if (!session->ncontroller)
+ return -ENODEV;
+
+
+ if (session->ncontroller > 1)
+ BT_INFO("Setting up only CAPI controller 1");
+
+ if (!(session->ctrl = di->attach_ctr(&cmtp_driver, session->name, session))) {
+ BT_ERR("Can't attach new controller");
+ return -EBUSY;
+ }
+
+ session->num = session->ctrl->cnr;
+
+ BT_DBG("session %p ctrl %p num %d", session, session->ctrl, session->num);
+
+ capimsg_setu32(buf, 0, 1);
+
+ cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session),
+ CAPI_FUNCTION_GET_MANUFACTURER, buf, 4);
+
+ cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session),
+ CAPI_FUNCTION_GET_VERSION, buf, 4);
+
+ cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session),
+ CAPI_FUNCTION_GET_SERIAL_NUMBER, buf, 4);
+
+ cmtp_send_interopmsg(session, CAPI_REQ, 0xffff, cmtp_msgnum_get(session),
+ CAPI_FUNCTION_GET_PROFILE, buf, 4);
+
+ return 0;
+}
+
+void cmtp_detach_device(struct cmtp_session *session)
+{
+ struct capi_ctr *ctrl = session->ctrl;
+
+ BT_DBG("session %p ctrl %p", session, ctrl);
+
+ if (!ctrl)
+ return;
+
+ ctrl->reseted(ctrl);
+
+ di->detach_ctr(ctrl);
+}
+
+int cmtp_init_capi(void)
+{
+ if (!(di = attach_capi_driver(&cmtp_driver))) {
+ BT_ERR("Can't attach CAPI driver");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+void cmtp_cleanup_capi(void)
+{
+ detach_capi_driver(&cmtp_driver);
+}
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/cmtp/cmtp.h b/uClinux-2.4.31-uc0/net/bluetooth/cmtp/cmtp.h
new file mode 100644
index 0000000..7393819
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/cmtp/cmtp.h
@@ -0,0 +1,138 @@
+/*
+ CMTP implementation for Linux Bluetooth stack (BlueZ).
+ Copyright (C) 2002-2003 Marcel Holtmann <marcel@holtmann.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+#ifndef __CMTP_H
+#define __CMTP_H
+
+#include <linux/types.h>
+#include <net/bluetooth/bluetooth.h>
+
+#define BTNAMSIZ 18
+
+/* CMTP ioctl defines */
+#define CMTPCONNADD _IOW('C', 200, int)
+#define CMTPCONNDEL _IOW('C', 201, int)
+#define CMTPGETCONNLIST _IOR('C', 210, int)
+#define CMTPGETCONNINFO _IOR('C', 211, int)
+
+#define CMTP_LOOPBACK 0
+
+struct cmtp_connadd_req {
+ int sock; // Connected socket
+ __u32 flags;
+};
+
+struct cmtp_conndel_req {
+ bdaddr_t bdaddr;
+ __u32 flags;
+};
+
+struct cmtp_conninfo {
+ bdaddr_t bdaddr;
+ __u32 flags;
+ __u16 state;
+ int num;
+};
+
+struct cmtp_connlist_req {
+ __u32 cnum;
+ struct cmtp_conninfo *ci;
+};
+
+int cmtp_add_connection(struct cmtp_connadd_req *req, struct socket *sock);
+int cmtp_del_connection(struct cmtp_conndel_req *req);
+int cmtp_get_connlist(struct cmtp_connlist_req *req);
+int cmtp_get_conninfo(struct cmtp_conninfo *ci);
+
+/* CMTP session defines */
+#define CMTP_INTEROP_TIMEOUT (HZ * 5)
+#define CMTP_INITIAL_MSGNUM 0xff00
+
+struct cmtp_session {
+ struct list_head list;
+
+ struct socket *sock;
+
+ bdaddr_t bdaddr;
+
+ unsigned long state;
+ unsigned long flags;
+
+ uint mtu;
+
+ char name[BTNAMSIZ];
+
+ atomic_t terminate;
+
+ wait_queue_head_t wait;
+
+ int ncontroller;
+ int num;
+ struct capi_ctr *ctrl;
+
+ struct list_head applications;
+
+ unsigned long blockids;
+ int msgnum;
+
+ struct sk_buff_head transmit;
+
+ struct sk_buff *reassembly[16];
+};
+
+struct cmtp_application {
+ struct list_head list;
+
+ unsigned long state;
+ int err;
+
+ __u16 appl;
+ __u16 mapping;
+
+ __u16 msgnum;
+};
+
+struct cmtp_scb {
+ int id;
+ int data;
+};
+
+int cmtp_attach_device(struct cmtp_session *session);
+void cmtp_detach_device(struct cmtp_session *session);
+
+void cmtp_recv_capimsg(struct cmtp_session *session, struct sk_buff *skb);
+void cmtp_send_capimsg(struct cmtp_session *session, struct sk_buff *skb);
+
+static inline void cmtp_schedule(struct cmtp_session *session)
+{
+ struct sock *sk = session->sock->sk;
+
+ wake_up_interruptible(sk->sleep);
+}
+
+/* CMTP init defines */
+int cmtp_init_capi(void);
+int cmtp_init_sockets(void);
+void cmtp_cleanup_capi(void);
+void cmtp_cleanup_sockets(void);
+
+#endif /* __CMTP_H */
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/cmtp/core.c b/uClinux-2.4.31-uc0/net/bluetooth/cmtp/core.c
new file mode 100644
index 0000000..d47aef0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/cmtp/core.c
@@ -0,0 +1,515 @@
+/*
+ CMTP implementation for Linux Bluetooth stack (BlueZ).
+ Copyright (C) 2002-2003 Marcel Holtmann <marcel@holtmann.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/fcntl.h>
+#include <linux/skbuff.h>
+#include <linux/socket.h>
+#include <linux/ioctl.h>
+#include <linux/file.h>
+#include <linux/init.h>
+#include <net/sock.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/l2cap.h>
+
+#include "cmtp.h"
+
+#ifndef CONFIG_BLUEZ_CMTP_DEBUG
+#undef BT_DBG
+#define BT_DBG(D...)
+#endif
+
+#define VERSION "1.0"
+
+static DECLARE_RWSEM(cmtp_session_sem);
+static LIST_HEAD(cmtp_session_list);
+
+static struct cmtp_session *__cmtp_get_session(bdaddr_t *bdaddr)
+{
+ struct cmtp_session *session;
+ struct list_head *p;
+
+ BT_DBG("");
+
+ list_for_each(p, &cmtp_session_list) {
+ session = list_entry(p, struct cmtp_session, list);
+ if (!bacmp(bdaddr, &session->bdaddr))
+ return session;
+ }
+ return NULL;
+}
+
+static void __cmtp_link_session(struct cmtp_session *session)
+{
+ MOD_INC_USE_COUNT;
+ list_add(&session->list, &cmtp_session_list);
+}
+
+static void __cmtp_unlink_session(struct cmtp_session *session)
+{
+ list_del(&session->list);
+ MOD_DEC_USE_COUNT;
+}
+
+static void __cmtp_copy_session(struct cmtp_session *session, struct cmtp_conninfo *ci)
+{
+ bacpy(&ci->bdaddr, &session->bdaddr);
+
+ ci->flags = session->flags;
+ ci->state = session->state;
+
+ ci->num = session->num;
+}
+
+
+static inline int cmtp_alloc_block_id(struct cmtp_session *session)
+{
+ int i, id = -1;
+
+ for (i = 0; i < 16; i++)
+ if (!test_and_set_bit(i, &session->blockids)) {
+ id = i;
+ break;
+ }
+
+ return id;
+}
+
+static inline void cmtp_free_block_id(struct cmtp_session *session, int id)
+{
+ clear_bit(id, &session->blockids);
+}
+
+static inline void cmtp_add_msgpart(struct cmtp_session *session, int id, const unsigned char *buf, int count)
+{
+ struct sk_buff *skb = session->reassembly[id], *nskb;
+ int size;
+
+ BT_DBG("session %p buf %p count %d", session, buf, count);
+
+ size = (skb) ? skb->len + count : count;
+
+ if (!(nskb = alloc_skb(size, GFP_ATOMIC))) {
+ BT_ERR("Can't allocate memory for CAPI message");
+ return;
+ }
+
+ if (skb && (skb->len > 0))
+ memcpy(skb_put(nskb, skb->len), skb->data, skb->len);
+
+ memcpy(skb_put(nskb, count), buf, count);
+
+ session->reassembly[id] = nskb;
+
+ if (skb)
+ kfree_skb(skb);
+}
+
+static inline int cmtp_recv_frame(struct cmtp_session *session, struct sk_buff *skb)
+{
+ __u8 hdr, hdrlen, id;
+ __u16 len;
+
+ BT_DBG("session %p skb %p len %d", session, skb, skb->len);
+
+ while (skb->len > 0) {
+ hdr = skb->data[0];
+
+ switch (hdr & 0xc0) {
+ case 0x40:
+ hdrlen = 2;
+ len = skb->data[1];
+ break;
+ case 0x80:
+ hdrlen = 3;
+ len = skb->data[1] | (skb->data[2] << 8);
+ break;
+ default:
+ hdrlen = 1;
+ len = 0;
+ break;
+ }
+
+ id = (hdr & 0x3c) >> 2;
+
+ BT_DBG("hdr 0x%02x hdrlen %d len %d id %d", hdr, hdrlen, len, id);
+
+ if (hdrlen + len > skb->len) {
+ BT_ERR("Wrong size or header information in CMTP frame");
+ break;
+ }
+
+ if (len == 0) {
+ skb_pull(skb, hdrlen);
+ continue;
+ }
+
+ switch (hdr & 0x03) {
+ case 0x00:
+ cmtp_add_msgpart(session, id, skb->data + hdrlen, len);
+ cmtp_recv_capimsg(session, session->reassembly[id]);
+ session->reassembly[id] = NULL;
+ break;
+ case 0x01:
+ cmtp_add_msgpart(session, id, skb->data + hdrlen, len);
+ break;
+ default:
+ if (session->reassembly[id] != NULL)
+ kfree_skb(session->reassembly[id]);
+ session->reassembly[id] = NULL;
+ break;
+ }
+
+ skb_pull(skb, hdrlen + len);
+ }
+
+ kfree_skb(skb);
+ return 0;
+}
+
+static int cmtp_send_frame(struct cmtp_session *session, unsigned char *data, int len)
+{
+ struct socket *sock = session->sock;
+ struct iovec iv = { data, len };
+ struct msghdr msg;
+ int err;
+
+ BT_DBG("session %p data %p len %d", session, data, len);
+
+ if (!len)
+ return 0;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iovlen = 1;
+ msg.msg_iov = &iv;
+
+ err = sock->ops->sendmsg(sock, &msg, len, 0);
+ return err;
+}
+
+static int cmtp_process_transmit(struct cmtp_session *session)
+{
+ struct sk_buff *skb, *nskb;
+ unsigned char *hdr;
+ unsigned int size, tail;
+
+ BT_DBG("session %p", session);
+
+ if (!(nskb = alloc_skb(session->mtu, GFP_ATOMIC))) {
+ BT_ERR("Can't allocate memory for new frame");
+ return -ENOMEM;
+ }
+
+ while ((skb = skb_dequeue(&session->transmit))) {
+ struct cmtp_scb *scb = (void *) skb->cb;
+
+ if ((tail = (session->mtu - nskb->len)) < 5) {
+ cmtp_send_frame(session, nskb->data, nskb->len);
+ skb_trim(nskb, 0);
+ tail = session->mtu;
+ }
+
+ size = min_t(uint, ((tail < 258) ? (tail - 2) : (tail - 3)), skb->len);
+
+ if ((scb->id < 0) && ((scb->id = cmtp_alloc_block_id(session)) < 0)) {
+ skb_queue_head(&session->transmit, skb);
+ break;
+ }
+
+ if (size < 256) {
+ hdr = skb_put(nskb, 2);
+ hdr[0] = 0x40
+ | ((scb->id << 2) & 0x3c)
+ | ((skb->len == size) ? 0x00 : 0x01);
+ hdr[1] = size;
+ } else {
+ hdr = skb_put(nskb, 3);
+ hdr[0] = 0x80
+ | ((scb->id << 2) & 0x3c)
+ | ((skb->len == size) ? 0x00 : 0x01);
+ hdr[1] = size & 0xff;
+ hdr[2] = size >> 8;
+ }
+
+ memcpy(skb_put(nskb, size), skb->data, size);
+ skb_pull(skb, size);
+
+ if (skb->len > 0) {
+ skb_queue_head(&session->transmit, skb);
+ } else {
+ cmtp_free_block_id(session, scb->id);
+ if (scb->data) {
+ cmtp_send_frame(session, nskb->data, nskb->len);
+ skb_trim(nskb, 0);
+ }
+ kfree_skb(skb);
+ }
+ }
+
+ cmtp_send_frame(session, nskb->data, nskb->len);
+
+ kfree_skb(nskb);
+
+ return skb_queue_len(&session->transmit);
+}
+
+static int cmtp_session(void *arg)
+{
+ struct cmtp_session *session = arg;
+ struct sock *sk = session->sock->sk;
+ struct sk_buff *skb;
+ wait_queue_t wait;
+
+ BT_DBG("session %p", session);
+
+ daemonize(); reparent_to_init();
+
+ sprintf(current->comm, "kcmtpd_ctr_%d", session->num);
+
+ sigfillset(&current->blocked);
+ flush_signals(current);
+
+ current->nice = -15;
+
+ set_fs(KERNEL_DS);
+
+ init_waitqueue_entry(&wait, current);
+ add_wait_queue(sk->sleep, &wait);
+ while (!atomic_read(&session->terminate)) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (sk->state != BT_CONNECTED)
+ break;
+
+ while ((skb = skb_dequeue(&sk->receive_queue))) {
+ skb_orphan(skb);
+ cmtp_recv_frame(session, skb);
+ }
+
+ cmtp_process_transmit(session);
+
+ schedule();
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(sk->sleep, &wait);
+
+ down_write(&cmtp_session_sem);
+
+ if (!(session->flags & (1 << CMTP_LOOPBACK)))
+ cmtp_detach_device(session);
+
+ fput(session->sock->file);
+
+ __cmtp_unlink_session(session);
+
+ up_write(&cmtp_session_sem);
+
+ kfree(session);
+ return 0;
+}
+
+int cmtp_add_connection(struct cmtp_connadd_req *req, struct socket *sock)
+{
+ struct cmtp_session *session, *s;
+ bdaddr_t src, dst;
+ int i, err;
+
+ BT_DBG("");
+
+ baswap(&src, &bluez_pi(sock->sk)->src);
+ baswap(&dst, &bluez_pi(sock->sk)->dst);
+
+ session = kmalloc(sizeof(struct cmtp_session), GFP_KERNEL);
+ if (!session)
+ return -ENOMEM;
+ memset(session, 0, sizeof(struct cmtp_session));
+
+ down_write(&cmtp_session_sem);
+
+ s = __cmtp_get_session(&bluez_pi(sock->sk)->dst);
+ if (s && s->state == BT_CONNECTED) {
+ err = -EEXIST;
+ goto failed;
+ }
+
+ bacpy(&session->bdaddr, &bluez_pi(sock->sk)->dst);
+
+ session->mtu = min_t(uint, l2cap_pi(sock->sk)->omtu, l2cap_pi(sock->sk)->imtu);
+
+ BT_DBG("mtu %d", session->mtu);
+
+ sprintf(session->name, "%s", batostr(&dst));
+
+ session->sock = sock;
+ session->state = BT_CONFIG;
+
+ init_waitqueue_head(&session->wait);
+
+ session->ctrl = NULL;
+ session->msgnum = CMTP_INITIAL_MSGNUM;
+
+ INIT_LIST_HEAD(&session->applications);
+
+ skb_queue_head_init(&session->transmit);
+
+ for (i = 0; i < 16; i++)
+ session->reassembly[i] = NULL;
+
+ session->flags = req->flags;
+
+ __cmtp_link_session(session);
+
+ err = kernel_thread(cmtp_session, session, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
+ if (err < 0)
+ goto unlink;
+
+ if (!(session->flags & (1 << CMTP_LOOPBACK))) {
+ err = cmtp_attach_device(session);
+ if (err < 0)
+ goto detach;
+ }
+
+ up_write(&cmtp_session_sem);
+ return 0;
+
+detach:
+ cmtp_detach_device(session);
+
+unlink:
+ __cmtp_unlink_session(session);
+
+failed:
+ up_write(&cmtp_session_sem);
+ kfree(session);
+ return err;
+}
+
+int cmtp_del_connection(struct cmtp_conndel_req *req)
+{
+ struct cmtp_session *session;
+ int err = 0;
+
+ BT_DBG("");
+
+ down_read(&cmtp_session_sem);
+
+ session = __cmtp_get_session(&req->bdaddr);
+ if (session) {
+ /* Flush the transmit queue */
+ skb_queue_purge(&session->transmit);
+
+ /* Kill session thread */
+ atomic_inc(&session->terminate);
+ cmtp_schedule(session);
+ } else
+ err = -ENOENT;
+
+ up_read(&cmtp_session_sem);
+ return err;
+}
+
+int cmtp_get_connlist(struct cmtp_connlist_req *req)
+{
+ struct list_head *p;
+ int err = 0, n = 0;
+
+ BT_DBG("");
+
+ down_read(&cmtp_session_sem);
+
+ list_for_each(p, &cmtp_session_list) {
+ struct cmtp_session *session;
+ struct cmtp_conninfo ci;
+
+ session = list_entry(p, struct cmtp_session, list);
+
+ __cmtp_copy_session(session, &ci);
+
+ if (copy_to_user(req->ci, &ci, sizeof(ci))) {
+ err = -EFAULT;
+ break;
+ }
+
+ if (++n >= req->cnum)
+ break;
+
+ req->ci++;
+ }
+ req->cnum = n;
+
+ up_read(&cmtp_session_sem);
+ return err;
+}
+
+int cmtp_get_conninfo(struct cmtp_conninfo *ci)
+{
+ struct cmtp_session *session;
+ int err = 0;
+
+ down_read(&cmtp_session_sem);
+
+ session = __cmtp_get_session(&ci->bdaddr);
+ if (session)
+ __cmtp_copy_session(session, ci);
+ else
+ err = -ENOENT;
+
+ up_read(&cmtp_session_sem);
+ return err;
+}
+
+
+int __init init_cmtp(void)
+{
+ l2cap_load();
+
+ cmtp_init_capi();
+ cmtp_init_sockets();
+
+ BT_INFO("BlueZ CMTP ver %s", VERSION);
+ BT_INFO("Copyright (C) 2002-2003 Marcel Holtmann <marcel@holtmann.org>");
+
+ return 0;
+}
+
+void __exit exit_cmtp(void)
+{
+ cmtp_cleanup_sockets();
+ cmtp_cleanup_capi();
+}
+
+module_init(init_cmtp);
+module_exit(exit_cmtp);
+
+MODULE_AUTHOR("Marcel Holtmann <marcel@holtmann.org>");
+MODULE_DESCRIPTION("BlueZ CMTP ver " VERSION);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/cmtp/sock.c b/uClinux-2.4.31-uc0/net/bluetooth/cmtp/sock.c
new file mode 100644
index 0000000..37c6ac1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/cmtp/sock.c
@@ -0,0 +1,208 @@
+/*
+ CMTP implementation for Linux Bluetooth stack (BlueZ).
+ Copyright (C) 2002-2003 Marcel Holtmann <marcel@holtmann.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/fcntl.h>
+#include <linux/skbuff.h>
+#include <linux/socket.h>
+#include <linux/ioctl.h>
+#include <linux/file.h>
+#include <net/sock.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+#include "cmtp.h"
+
+#ifndef CONFIG_BLUEZ_CMTP_DEBUG
+#undef BT_DBG
+#define BT_DBG(D...)
+#endif
+
+static int cmtp_sock_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ BT_DBG("sock %p sk %p", sock, sk);
+
+ if (!sk)
+ return 0;
+
+ sock_orphan(sk);
+ sock_put(sk);
+
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int cmtp_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct cmtp_connadd_req ca;
+ struct cmtp_conndel_req cd;
+ struct cmtp_connlist_req cl;
+ struct cmtp_conninfo ci;
+ struct socket *nsock;
+ int err;
+
+ BT_DBG("cmd %x arg %lx", cmd, arg);
+
+ switch (cmd) {
+ case CMTPCONNADD:
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+
+ if (copy_from_user(&ca, (void *) arg, sizeof(ca)))
+ return -EFAULT;
+
+ nsock = sockfd_lookup(ca.sock, &err);
+ if (!nsock)
+ return err;
+
+ if (nsock->sk->state != BT_CONNECTED) {
+ fput(nsock->file);
+ return -EBADFD;
+ }
+
+ err = cmtp_add_connection(&ca, nsock);
+ if (!err) {
+ if (copy_to_user((void *) arg, &ca, sizeof(ca)))
+ err = -EFAULT;
+ } else
+ fput(nsock->file);
+
+ return err;
+
+ case CMTPCONNDEL:
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+
+ if (copy_from_user(&cd, (void *) arg, sizeof(cd)))
+ return -EFAULT;
+
+ return cmtp_del_connection(&cd);
+
+ case CMTPGETCONNLIST:
+ if (copy_from_user(&cl, (void *) arg, sizeof(cl)))
+ return -EFAULT;
+
+ if (cl.cnum <= 0)
+ return -EINVAL;
+
+ err = cmtp_get_connlist(&cl);
+ if (!err && copy_to_user((void *) arg, &cl, sizeof(cl)))
+ return -EFAULT;
+
+ return err;
+
+ case CMTPGETCONNINFO:
+ if (copy_from_user(&ci, (void *) arg, sizeof(ci)))
+ return -EFAULT;
+
+ err = cmtp_get_conninfo(&ci);
+ if (!err && copy_to_user((void *) arg, &ci, sizeof(ci)))
+ return -EFAULT;
+
+ return err;
+ }
+
+ return -EINVAL;
+}
+
+static struct proto_ops cmtp_sock_ops = {
+ family: PF_BLUETOOTH,
+ release: cmtp_sock_release,
+ ioctl: cmtp_sock_ioctl,
+ bind: sock_no_bind,
+ getname: sock_no_getname,
+ sendmsg: sock_no_sendmsg,
+ recvmsg: sock_no_recvmsg,
+ poll: sock_no_poll,
+ listen: sock_no_listen,
+ shutdown: sock_no_shutdown,
+ setsockopt: sock_no_setsockopt,
+ getsockopt: sock_no_getsockopt,
+ connect: sock_no_connect,
+ socketpair: sock_no_socketpair,
+ accept: sock_no_accept,
+ mmap: sock_no_mmap
+};
+
+static int cmtp_sock_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+
+ BT_DBG("sock %p", sock);
+
+ if (sock->type != SOCK_RAW)
+ return -ESOCKTNOSUPPORT;
+
+ sock->ops = &cmtp_sock_ops;
+
+ if (!(sk = sk_alloc(PF_BLUETOOTH, GFP_KERNEL, 1)))
+ return -ENOMEM;
+
+ MOD_INC_USE_COUNT;
+
+ sock->state = SS_UNCONNECTED;
+ sock_init_data(sock, sk);
+
+ sk->destruct = NULL;
+ sk->protocol = protocol;
+
+ return 0;
+}
+
+static struct net_proto_family cmtp_sock_family_ops = {
+ family: PF_BLUETOOTH,
+ create: cmtp_sock_create
+};
+
+int cmtp_init_sockets(void)
+{
+ int err;
+
+ if ((err = bluez_sock_register(BTPROTO_CMTP, &cmtp_sock_family_ops))) {
+ BT_ERR("Can't register CMTP socket layer (%d)", err);
+ return err;
+ }
+
+ return 0;
+}
+
+void cmtp_cleanup_sockets(void)
+{
+ int err;
+
+ if ((err = bluez_sock_unregister(BTPROTO_CMTP)))
+ BT_ERR("Can't unregister CMTP socket layer (%d)", err);
+
+ return;
+}
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/hci_conn.c b/uClinux-2.4.31-uc0/net/bluetooth/hci_conn.c
new file mode 100644
index 0000000..95d01a9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/hci_conn.c
@@ -0,0 +1,435 @@
+/*
+ BlueZ - Bluetooth protocol stack for Linux
+ Copyright (C) 2000-2001 Qualcomm Incorporated
+
+ Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * HCI Connection handling.
+ *
+ * $Id: hci_conn.c,v 1.5 2002/07/17 18:46:25 maxk Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <net/sock.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/unaligned.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#ifndef HCI_CORE_DEBUG
+#undef BT_DBG
+#define BT_DBG( A... )
+#endif
+
+void hci_acl_connect(struct hci_conn *conn)
+{
+ struct hci_dev *hdev = conn->hdev;
+ struct inquiry_entry *ie;
+ create_conn_cp cp;
+
+ BT_DBG("%p", conn);
+
+ conn->state = BT_CONNECT;
+ conn->out = 1;
+ conn->link_mode = HCI_LM_MASTER;
+
+ memset(&cp, 0, sizeof(cp));
+ bacpy(&cp.bdaddr, &conn->dst);
+ cp.pscan_rep_mode = 0x02;
+
+ if ((ie = inquiry_cache_lookup(hdev, &conn->dst)) &&
+ inquiry_entry_age(ie) <= INQUIRY_ENTRY_AGE_MAX) {
+ cp.pscan_rep_mode = ie->info.pscan_rep_mode;
+ cp.pscan_mode = ie->info.pscan_mode;
+ cp.clock_offset = ie->info.clock_offset | __cpu_to_le16(0x8000);
+ }
+
+ cp.pkt_type = __cpu_to_le16(hdev->pkt_type & ACL_PTYPE_MASK);
+ if (lmp_rswitch_capable(hdev) && !(hdev->link_mode & HCI_LM_MASTER))
+ cp.role_switch = 0x01;
+ else
+ cp.role_switch = 0x00;
+
+ hci_send_cmd(hdev, OGF_LINK_CTL, OCF_CREATE_CONN,
+ CREATE_CONN_CP_SIZE, &cp);
+}
+
+void hci_acl_disconn(struct hci_conn *conn, __u8 reason)
+{
+ disconnect_cp cp;
+
+ BT_DBG("%p", conn);
+
+ conn->state = BT_DISCONN;
+
+ cp.handle = __cpu_to_le16(conn->handle);
+ cp.reason = reason;
+ hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_DISCONNECT,
+ DISCONNECT_CP_SIZE, &cp);
+}
+
+void hci_add_sco(struct hci_conn *conn, __u16 handle)
+{
+ struct hci_dev *hdev = conn->hdev;
+ add_sco_cp cp;
+
+ BT_DBG("%p", conn);
+
+ conn->state = BT_CONNECT;
+ conn->out = 1;
+
+ cp.pkt_type = __cpu_to_le16(hdev->pkt_type & SCO_PTYPE_MASK);
+ cp.handle = __cpu_to_le16(handle);
+
+ hci_send_cmd(hdev, OGF_LINK_CTL, OCF_ADD_SCO, ADD_SCO_CP_SIZE, &cp);
+}
+
+static void hci_conn_timeout(unsigned long arg)
+{
+ struct hci_conn *conn = (void *)arg;
+ struct hci_dev *hdev = conn->hdev;
+
+ BT_DBG("conn %p state %d", conn, conn->state);
+
+ if (atomic_read(&conn->refcnt))
+ return;
+
+ hci_dev_lock(hdev);
+ if (conn->state == BT_CONNECTED)
+ hci_acl_disconn(conn, 0x13);
+ else
+ conn->state = BT_CLOSED;
+ hci_dev_unlock(hdev);
+ return;
+}
+
+static void hci_conn_init_timer(struct hci_conn *conn)
+{
+ init_timer(&conn->timer);
+ conn->timer.function = hci_conn_timeout;
+ conn->timer.data = (unsigned long)conn;
+}
+
+struct hci_conn *hci_conn_add(struct hci_dev *hdev, int type, bdaddr_t *dst)
+{
+ struct hci_conn *conn;
+
+ BT_DBG("%s dst %s", hdev->name, batostr(dst));
+
+ if (!(conn = kmalloc(sizeof(struct hci_conn), GFP_ATOMIC)))
+ return NULL;
+ memset(conn, 0, sizeof(struct hci_conn));
+
+ bacpy(&conn->dst, dst);
+ conn->type = type;
+ conn->hdev = hdev;
+ conn->state = BT_OPEN;
+
+ skb_queue_head_init(&conn->data_q);
+ hci_conn_init_timer(conn);
+
+ atomic_set(&conn->refcnt, 0);
+
+ hci_dev_hold(hdev);
+
+ tasklet_disable(&hdev->tx_task);
+ conn_hash_add(hdev, conn);
+ tasklet_enable(&hdev->tx_task);
+
+ return conn;
+}
+
+int hci_conn_del(struct hci_conn *conn)
+{
+ struct hci_dev *hdev = conn->hdev;
+
+ BT_DBG("%s conn %p handle %d", hdev->name, conn, conn->handle);
+
+ hci_conn_del_timer(conn);
+
+ if (conn->type == SCO_LINK) {
+ struct hci_conn *acl = conn->link;
+ if (acl) {
+ acl->link = NULL;
+ hci_conn_put(acl);
+ }
+ } else {
+ struct hci_conn *sco = conn->link;
+ if (sco)
+ sco->link = NULL;
+
+ /* Unacked frames */
+ hdev->acl_cnt += conn->sent;
+ }
+
+ tasklet_disable(&hdev->tx_task);
+ conn_hash_del(hdev, conn);
+ tasklet_enable(&hdev->tx_task);
+
+ skb_queue_purge(&conn->data_q);
+
+ hci_dev_put(hdev);
+
+ kfree(conn);
+ return 0;
+}
+
+struct hci_dev *hci_get_route(bdaddr_t *dst, bdaddr_t *src)
+{
+ int use_src = bacmp(src, BDADDR_ANY);
+ struct hci_dev *hdev = NULL;
+ struct list_head *p;
+
+ BT_DBG("%s -> %s", batostr(src), batostr(dst));
+
+ read_lock_bh(&hdev_list_lock);
+
+ list_for_each(p, &hdev_list) {
+ struct hci_dev *d;
+ d = list_entry(p, struct hci_dev, list);
+
+ if (!test_bit(HCI_UP, &d->flags))
+ continue;
+
+ /* Simple routing:
+ * No source address - find interface with bdaddr != dst
+ * Source address - find interface with bdaddr == src
+ */
+
+ if (use_src) {
+ if (!bacmp(&d->bdaddr, src)) {
+ hdev = d; break;
+ }
+ } else {
+ if (bacmp(&d->bdaddr, dst)) {
+ hdev = d; break;
+ }
+ }
+ }
+
+ if (hdev)
+ hci_dev_hold(hdev);
+
+ read_unlock_bh(&hdev_list_lock);
+ return hdev;
+}
+
+/* Create SCO or ACL connection.
+ * Device _must_ be locked */
+struct hci_conn * hci_connect(struct hci_dev *hdev, int type, bdaddr_t *dst)
+{
+ struct hci_conn *acl;
+
+ BT_DBG("%s dst %s", hdev->name, batostr(dst));
+
+ if (!(acl = conn_hash_lookup_ba(hdev, ACL_LINK, dst))) {
+ if (!(acl = hci_conn_add(hdev, ACL_LINK, dst)))
+ return NULL;
+ }
+
+ hci_conn_hold(acl);
+
+ if (acl->state == BT_OPEN || acl->state == BT_CLOSED)
+ hci_acl_connect(acl);
+
+ if (type == SCO_LINK) {
+ struct hci_conn *sco;
+
+ if (!(sco = conn_hash_lookup_ba(hdev, SCO_LINK, dst))) {
+ if (!(sco = hci_conn_add(hdev, SCO_LINK, dst))) {
+ hci_conn_put(acl);
+ return NULL;
+ }
+ }
+ acl->link = sco;
+ sco->link = acl;
+
+ hci_conn_hold(sco);
+
+ if (acl->state == BT_CONNECTED &&
+ (sco->state == BT_OPEN || sco->state == BT_CLOSED))
+ hci_add_sco(sco, acl->handle);
+
+ return sco;
+ } else {
+ return acl;
+ }
+}
+
+/* Authenticate remote device */
+int hci_conn_auth(struct hci_conn *conn)
+{
+ BT_DBG("conn %p", conn);
+
+ if (conn->link_mode & HCI_LM_AUTH)
+ return 1;
+
+ if (!test_and_set_bit(HCI_CONN_AUTH_PEND, &conn->pend)) {
+ auth_requested_cp ar;
+ ar.handle = __cpu_to_le16(conn->handle);
+ hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_AUTH_REQUESTED,
+ AUTH_REQUESTED_CP_SIZE, &ar);
+ }
+ return 0;
+}
+
+/* Enable encryption */
+int hci_conn_encrypt(struct hci_conn *conn)
+{
+ BT_DBG("conn %p", conn);
+
+ if (conn->link_mode & HCI_LM_ENCRYPT)
+ return 1;
+
+ if (test_and_set_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend))
+ return 0;
+
+ if (hci_conn_auth(conn)) {
+ set_conn_encrypt_cp ce;
+ ce.handle = __cpu_to_le16(conn->handle);
+ ce.encrypt = 1;
+ hci_send_cmd(conn->hdev, OGF_LINK_CTL, OCF_SET_CONN_ENCRYPT,
+ SET_CONN_ENCRYPT_CP_SIZE, &ce);
+ }
+ return 0;
+}
+
+/* Drop all connection on the device */
+void hci_conn_hash_flush(struct hci_dev *hdev)
+{
+ struct conn_hash *h = &hdev->conn_hash;
+ struct list_head *p;
+
+ BT_DBG("hdev %s", hdev->name);
+
+ p = h->list.next;
+ while (p != &h->list) {
+ struct hci_conn *c;
+
+ c = list_entry(p, struct hci_conn, list);
+ p = p->next;
+
+ c->state = BT_CLOSED;
+
+ hci_proto_disconn_ind(c, 0x16);
+ hci_conn_del(c);
+ }
+}
+
+int hci_get_conn_list(unsigned long arg)
+{
+ struct hci_conn_list_req req, *cl;
+ struct hci_conn_info *ci;
+ struct hci_dev *hdev;
+ struct list_head *p;
+ int n = 0, size, err;
+
+ if (copy_from_user(&req, (void *) arg, sizeof(req)))
+ return -EFAULT;
+
+ if (!req.conn_num || req.conn_num > (PAGE_SIZE * 2) / sizeof(*ci))
+ return -EINVAL;
+
+ size = sizeof(req) + req.conn_num * sizeof(*ci);
+
+ if (!(cl = (void *) kmalloc(size, GFP_KERNEL)))
+ return -ENOMEM;
+
+ if (!(hdev = hci_dev_get(req.dev_id))) {
+ kfree(cl);
+ return -ENODEV;
+ }
+
+ ci = cl->conn_info;
+
+ hci_dev_lock_bh(hdev);
+ list_for_each(p, &hdev->conn_hash.list) {
+ register struct hci_conn *c;
+ c = list_entry(p, struct hci_conn, list);
+
+ bacpy(&(ci + n)->bdaddr, &c->dst);
+ (ci + n)->handle = c->handle;
+ (ci + n)->type = c->type;
+ (ci + n)->out = c->out;
+ (ci + n)->state = c->state;
+ (ci + n)->link_mode = c->link_mode;
+ if (++n >= req.conn_num)
+ break;
+ }
+ hci_dev_unlock_bh(hdev);
+
+ cl->dev_id = hdev->id;
+ cl->conn_num = n;
+ size = sizeof(req) + n * sizeof(*ci);
+
+ hci_dev_put(hdev);
+
+ err = copy_to_user((void *) arg, cl, size);
+ kfree(cl);
+
+ return err ? -EFAULT : 0;
+}
+
+int hci_get_conn_info(struct hci_dev *hdev, unsigned long arg)
+{
+ struct hci_conn_info_req req;
+ struct hci_conn_info ci;
+ struct hci_conn *conn;
+ char *ptr = (void *) arg + sizeof(req);
+
+ if (copy_from_user(&req, (void *) arg, sizeof(req)))
+ return -EFAULT;
+
+ hci_dev_lock_bh(hdev);
+ conn = conn_hash_lookup_ba(hdev, req.type, &req.bdaddr);
+ if (conn) {
+ bacpy(&ci.bdaddr, &conn->dst);
+ ci.handle = conn->handle;
+ ci.type = conn->type;
+ ci.out = conn->out;
+ ci.state = conn->state;
+ ci.link_mode = conn->link_mode;
+ }
+ hci_dev_unlock_bh(hdev);
+
+ if (!conn)
+ return -ENOENT;
+
+ return copy_to_user(ptr, &ci, sizeof(ci)) ? -EFAULT : 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/hci_core.c b/uClinux-2.4.31-uc0/net/bluetooth/hci_core.c
new file mode 100644
index 0000000..66564af
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/hci_core.c
@@ -0,0 +1,1410 @@
+/*
+ BlueZ - Bluetooth protocol stack for Linux
+ Copyright (C) 2000-2001 Qualcomm Incorporated
+
+ Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * BlueZ HCI Core.
+ *
+ * $Id: hci_core.c,v 1.14 2002/08/26 16:57:57 maxk Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kmod.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <net/sock.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/unaligned.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#ifndef HCI_CORE_DEBUG
+#undef BT_DBG
+#define BT_DBG( A... )
+#endif
+
+static void hci_cmd_task(unsigned long arg);
+static void hci_rx_task(unsigned long arg);
+static void hci_tx_task(unsigned long arg);
+static void hci_notify(struct hci_dev *hdev, int event);
+
+rwlock_t hci_task_lock = RW_LOCK_UNLOCKED;
+
+/* HCI device list */
+LIST_HEAD(hdev_list);
+rwlock_t hdev_list_lock = RW_LOCK_UNLOCKED;
+
+/* HCI protocols */
+#define HCI_MAX_PROTO 2
+struct hci_proto *hci_proto[HCI_MAX_PROTO];
+
+/* HCI notifiers list */
+static struct notifier_block *hci_notifier;
+
+
+/* ---- HCI notifications ---- */
+
+int hci_register_notifier(struct notifier_block *nb)
+{
+ return notifier_chain_register(&hci_notifier, nb);
+}
+
+int hci_unregister_notifier(struct notifier_block *nb)
+{
+ return notifier_chain_unregister(&hci_notifier, nb);
+}
+
+void hci_notify(struct hci_dev *hdev, int event)
+{
+ notifier_call_chain(&hci_notifier, event, hdev);
+}
+
+/* ---- HCI hotplug support ---- */
+
+#ifdef CONFIG_HOTPLUG
+
+static int hci_run_hotplug(char *dev, char *action)
+{
+ char *argv[3], *envp[5], dstr[20], astr[32];
+
+ sprintf(dstr, "DEVICE=%s", dev);
+ sprintf(astr, "ACTION=%s", action);
+
+ argv[0] = hotplug_path;
+ argv[1] = "bluetooth";
+ argv[2] = NULL;
+
+ envp[0] = "HOME=/";
+ envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
+ envp[2] = dstr;
+ envp[3] = astr;
+ envp[4] = NULL;
+
+ return call_usermodehelper(argv[0], argv, envp);
+}
+#else
+#define hci_run_hotplug(A...)
+#endif
+
+/* ---- HCI requests ---- */
+
+void hci_req_complete(struct hci_dev *hdev, int result)
+{
+ BT_DBG("%s result 0x%2.2x", hdev->name, result);
+
+ if (hdev->req_status == HCI_REQ_PEND) {
+ hdev->req_result = result;
+ hdev->req_status = HCI_REQ_DONE;
+ wake_up_interruptible(&hdev->req_wait_q);
+ }
+}
+
+void hci_req_cancel(struct hci_dev *hdev, int err)
+{
+ BT_DBG("%s err 0x%2.2x", hdev->name, err);
+
+ if (hdev->req_status == HCI_REQ_PEND) {
+ hdev->req_result = err;
+ hdev->req_status = HCI_REQ_CANCELED;
+ wake_up_interruptible(&hdev->req_wait_q);
+ }
+}
+
+/* Execute request and wait for completion. */
+static int __hci_request(struct hci_dev *hdev, void (*req)(struct hci_dev *hdev, unsigned long opt), unsigned long opt, __u32 timeout)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ int err = 0;
+
+ BT_DBG("%s start", hdev->name);
+
+ hdev->req_status = HCI_REQ_PEND;
+
+ add_wait_queue(&hdev->req_wait_q, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ req(hdev, opt);
+ schedule_timeout(timeout);
+
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&hdev->req_wait_q, &wait);
+
+ if (signal_pending(current))
+ return -EINTR;
+
+ switch (hdev->req_status) {
+ case HCI_REQ_DONE:
+ err = -bterr(hdev->req_result);
+ break;
+
+ case HCI_REQ_CANCELED:
+ err = -hdev->req_result;
+ break;
+
+ default:
+ err = -ETIMEDOUT;
+ break;
+ };
+
+ hdev->req_status = hdev->req_result = 0;
+
+ BT_DBG("%s end: err %d", hdev->name, err);
+
+ return err;
+}
+
+static inline int hci_request(struct hci_dev *hdev, void (*req)(struct hci_dev *hdev, unsigned long opt),
+ unsigned long opt, __u32 timeout)
+{
+ int ret;
+
+ /* Serialize all requests */
+ hci_req_lock(hdev);
+ ret = __hci_request(hdev, req, opt, timeout);
+ hci_req_unlock(hdev);
+
+ return ret;
+}
+
+static void hci_reset_req(struct hci_dev *hdev, unsigned long opt)
+{
+ BT_DBG("%s %ld", hdev->name, opt);
+
+ /* Reset device */
+ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_RESET, 0, NULL);
+}
+
+static void hci_init_req(struct hci_dev *hdev, unsigned long opt)
+{
+ set_event_flt_cp ef;
+ __u16 param;
+
+ BT_DBG("%s %ld", hdev->name, opt);
+
+ /* Mandatory initialization */
+
+ /* Reset */
+ if (test_bit(HCI_QUIRK_RESET_ON_INIT, &hdev->quirks))
+ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_RESET, 0, NULL);
+
+ /* Read Local Supported Features */
+ hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_LOCAL_FEATURES, 0, NULL);
+
+ /* Read Buffer Size (ACL mtu, max pkt, etc.) */
+ hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_BUFFER_SIZE, 0, NULL);
+
+#if 0
+ /* Host buffer size */
+ {
+ host_buffer_size_cp bs;
+ bs.acl_mtu = __cpu_to_le16(HCI_MAX_ACL_SIZE);
+ bs.sco_mtu = HCI_MAX_SCO_SIZE;
+ bs.acl_max_pkt = __cpu_to_le16(0xffff);
+ bs.sco_max_pkt = __cpu_to_le16(0xffff);
+ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_HOST_BUFFER_SIZE,
+ HOST_BUFFER_SIZE_CP_SIZE, &bs);
+ }
+#endif
+
+ /* Read BD Address */
+ hci_send_cmd(hdev, OGF_INFO_PARAM, OCF_READ_BD_ADDR, 0, NULL);
+
+ /* Optional initialization */
+
+ /* Clear Event Filters */
+ ef.flt_type = FLT_CLEAR_ALL;
+ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_SET_EVENT_FLT, 1, &ef);
+
+ /* Page timeout ~20 secs */
+ param = __cpu_to_le16(0x8000);
+ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_PG_TIMEOUT, 2, &param);
+
+ /* Connection accept timeout ~20 secs */
+ param = __cpu_to_le16(0x7d00);
+ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_CA_TIMEOUT, 2, &param);
+}
+
+static void hci_scan_req(struct hci_dev *hdev, unsigned long opt)
+{
+ __u8 scan = opt;
+
+ BT_DBG("%s %x", hdev->name, scan);
+
+ /* Inquiry and Page scans */
+ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE, 1, &scan);
+}
+
+static void hci_auth_req(struct hci_dev *hdev, unsigned long opt)
+{
+ __u8 auth = opt;
+
+ BT_DBG("%s %x", hdev->name, auth);
+
+ /* Authentication */
+ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_AUTH_ENABLE, 1, &auth);
+}
+
+static void hci_encrypt_req(struct hci_dev *hdev, unsigned long opt)
+{
+ __u8 encrypt = opt;
+
+ BT_DBG("%s %x", hdev->name, encrypt);
+
+ /* Authentication */
+ hci_send_cmd(hdev, OGF_HOST_CTL, OCF_WRITE_ENCRYPT_MODE, 1, &encrypt);
+}
+
+/* Get HCI device by index.
+ * Device is locked on return. */
+struct hci_dev *hci_dev_get(int index)
+{
+ struct hci_dev *hdev;
+ struct list_head *p;
+
+ BT_DBG("%d", index);
+
+ if (index < 0)
+ return NULL;
+
+ read_lock(&hdev_list_lock);
+ list_for_each(p, &hdev_list) {
+ hdev = list_entry(p, struct hci_dev, list);
+ if (hdev->id == index) {
+ hci_dev_hold(hdev);
+ goto done;
+ }
+ }
+ hdev = NULL;
+done:
+ read_unlock(&hdev_list_lock);
+ return hdev;
+}
+
+/* ---- Inquiry support ---- */
+void inquiry_cache_flush(struct hci_dev *hdev)
+{
+ struct inquiry_cache *cache = &hdev->inq_cache;
+ struct inquiry_entry *next = cache->list, *e;
+
+ BT_DBG("cache %p", cache);
+
+ cache->list = NULL;
+ while ((e = next)) {
+ next = e->next;
+ kfree(e);
+ }
+}
+
+struct inquiry_entry *inquiry_cache_lookup(struct hci_dev *hdev, bdaddr_t *bdaddr)
+{
+ struct inquiry_cache *cache = &hdev->inq_cache;
+ struct inquiry_entry *e;
+
+ BT_DBG("cache %p, %s", cache, batostr(bdaddr));
+
+ for (e = cache->list; e; e = e->next)
+ if (!bacmp(&e->info.bdaddr, bdaddr))
+ break;
+ return e;
+}
+
+void inquiry_cache_update(struct hci_dev *hdev, inquiry_info *info)
+{
+ struct inquiry_cache *cache = &hdev->inq_cache;
+ struct inquiry_entry *e;
+
+ BT_DBG("cache %p, %s", cache, batostr(&info->bdaddr));
+
+ if (!(e = inquiry_cache_lookup(hdev, &info->bdaddr))) {
+ /* Entry not in the cache. Add new one. */
+ if (!(e = kmalloc(sizeof(struct inquiry_entry), GFP_ATOMIC)))
+ return;
+ memset(e, 0, sizeof(struct inquiry_entry));
+ e->next = cache->list;
+ cache->list = e;
+ }
+
+ memcpy(&e->info, info, sizeof(inquiry_info));
+ e->timestamp = jiffies;
+ cache->timestamp = jiffies;
+}
+
+int inquiry_cache_dump(struct hci_dev *hdev, int num, __u8 *buf)
+{
+ struct inquiry_cache *cache = &hdev->inq_cache;
+ inquiry_info *info = (inquiry_info *) buf;
+ struct inquiry_entry *e;
+ int copied = 0;
+
+ for (e = cache->list; e && copied < num; e = e->next, copied++)
+ memcpy(info++, &e->info, sizeof(inquiry_info));
+
+ BT_DBG("cache %p, copied %d", cache, copied);
+ return copied;
+}
+
+static void hci_inq_req(struct hci_dev *hdev, unsigned long opt)
+{
+ struct hci_inquiry_req *ir = (struct hci_inquiry_req *) opt;
+ inquiry_cp ic;
+
+ BT_DBG("%s", hdev->name);
+
+ if (test_bit(HCI_INQUIRY, &hdev->flags))
+ return;
+
+ /* Start Inquiry */
+ memcpy(&ic.lap, &ir->lap, 3);
+ ic.length = ir->length;
+ ic.num_rsp = ir->num_rsp;
+ hci_send_cmd(hdev, OGF_LINK_CTL, OCF_INQUIRY, INQUIRY_CP_SIZE, &ic);
+}
+
+int hci_inquiry(unsigned long arg)
+{
+ struct hci_inquiry_req ir;
+ struct hci_dev *hdev;
+ int err = 0, do_inquiry = 0, max_rsp;
+ long timeo;
+ __u8 *buf, *ptr;
+
+ ptr = (void *) arg;
+ if (copy_from_user(&ir, ptr, sizeof(ir)))
+ return -EFAULT;
+
+ if (!(hdev = hci_dev_get(ir.dev_id)))
+ return -ENODEV;
+
+ hci_dev_lock_bh(hdev);
+ if (inquiry_cache_age(hdev) > INQUIRY_CACHE_AGE_MAX ||
+ inquiry_cache_empty(hdev) ||
+ ir.flags & IREQ_CACHE_FLUSH) {
+ inquiry_cache_flush(hdev);
+ do_inquiry = 1;
+ }
+ hci_dev_unlock_bh(hdev);
+
+ timeo = ir.length * 2 * HZ;
+ if (do_inquiry && (err = hci_request(hdev, hci_inq_req, (unsigned long)&ir, timeo)) < 0)
+ goto done;
+
+ /* for unlimited number of responses we will use buffer with 255 entries */
+ max_rsp = (ir.num_rsp == 0) ? 255 : ir.num_rsp;
+
+ /* cache_dump can't sleep. Therefore we allocate temp buffer and then
+ * copy it to the user space.
+ */
+ if (!(buf = kmalloc(sizeof(inquiry_info) * max_rsp, GFP_KERNEL))) {
+ err = -ENOMEM;
+ goto done;
+ }
+
+ hci_dev_lock_bh(hdev);
+ ir.num_rsp = inquiry_cache_dump(hdev, max_rsp, buf);
+ hci_dev_unlock_bh(hdev);
+
+ BT_DBG("num_rsp %d", ir.num_rsp);
+
+ if (!verify_area(VERIFY_WRITE, ptr, sizeof(ir) +
+ (sizeof(inquiry_info) * ir.num_rsp))) {
+ copy_to_user(ptr, &ir, sizeof(ir));
+ ptr += sizeof(ir);
+ copy_to_user(ptr, buf, sizeof(inquiry_info) * ir.num_rsp);
+ } else
+ err = -EFAULT;
+
+ kfree(buf);
+
+done:
+ hci_dev_put(hdev);
+ return err;
+}
+
+/* ---- HCI ioctl helpers ---- */
+
+int hci_dev_open(__u16 dev)
+{
+ struct hci_dev *hdev;
+ int ret = 0;
+
+ if (!(hdev = hci_dev_get(dev)))
+ return -ENODEV;
+
+ BT_DBG("%s %p", hdev->name, hdev);
+
+ hci_req_lock(hdev);
+
+ if (test_bit(HCI_UP, &hdev->flags)) {
+ ret = -EALREADY;
+ goto done;
+ }
+
+ if (hdev->open(hdev)) {
+ ret = -EIO;
+ goto done;
+ }
+
+ if (!test_bit(HCI_RAW, &hdev->flags)) {
+ atomic_set(&hdev->cmd_cnt, 1);
+ set_bit(HCI_INIT, &hdev->flags);
+
+ //__hci_request(hdev, hci_reset_req, 0, HZ);
+ ret = __hci_request(hdev, hci_init_req, 0, HCI_INIT_TIMEOUT);
+
+ clear_bit(HCI_INIT, &hdev->flags);
+ }
+
+ if (!ret) {
+ set_bit(HCI_UP, &hdev->flags);
+ hci_notify(hdev, HCI_DEV_UP);
+ } else {
+ /* Init failed, cleanup */
+ tasklet_kill(&hdev->rx_task);
+ tasklet_kill(&hdev->tx_task);
+ tasklet_kill(&hdev->cmd_task);
+
+ skb_queue_purge(&hdev->cmd_q);
+ skb_queue_purge(&hdev->rx_q);
+
+ if (hdev->flush)
+ hdev->flush(hdev);
+
+ if (hdev->sent_cmd) {
+ kfree_skb(hdev->sent_cmd);
+ hdev->sent_cmd = NULL;
+ }
+
+ hdev->close(hdev);
+ hdev->flags = 0;
+ }
+
+done:
+ hci_req_unlock(hdev);
+ hci_dev_put(hdev);
+ return ret;
+}
+
+static int hci_dev_do_close(struct hci_dev *hdev)
+{
+ BT_DBG("%s %p", hdev->name, hdev);
+
+ hci_req_cancel(hdev, ENODEV);
+ hci_req_lock(hdev);
+
+ if (!test_and_clear_bit(HCI_UP, &hdev->flags)) {
+ hci_req_unlock(hdev);
+ return 0;
+ }
+
+ /* Kill RX and TX tasks */
+ tasklet_kill(&hdev->rx_task);
+ tasklet_kill(&hdev->tx_task);
+
+ hci_dev_lock_bh(hdev);
+ inquiry_cache_flush(hdev);
+ hci_conn_hash_flush(hdev);
+ hci_dev_unlock_bh(hdev);
+
+ hci_notify(hdev, HCI_DEV_DOWN);
+
+ if (hdev->flush)
+ hdev->flush(hdev);
+
+ /* Reset device */
+ skb_queue_purge(&hdev->cmd_q);
+ atomic_set(&hdev->cmd_cnt, 1);
+ set_bit(HCI_INIT, &hdev->flags);
+ __hci_request(hdev, hci_reset_req, 0, HZ/4);
+ clear_bit(HCI_INIT, &hdev->flags);
+
+ /* Kill cmd task */
+ tasklet_kill(&hdev->cmd_task);
+
+ /* Drop queues */
+ skb_queue_purge(&hdev->rx_q);
+ skb_queue_purge(&hdev->cmd_q);
+ skb_queue_purge(&hdev->raw_q);
+
+ /* Drop last sent command */
+ if (hdev->sent_cmd) {
+ kfree_skb(hdev->sent_cmd);
+ hdev->sent_cmd = NULL;
+ }
+
+ /* After this point our queues are empty
+ * and no tasks are scheduled. */
+ hdev->close(hdev);
+
+ /* Clear flags */
+ hdev->flags = 0;
+
+ hci_req_unlock(hdev);
+ return 0;
+}
+
+int hci_dev_close(__u16 dev)
+{
+ struct hci_dev *hdev;
+ int err;
+
+ if (!(hdev = hci_dev_get(dev)))
+ return -ENODEV;
+ err = hci_dev_do_close(hdev);
+ hci_dev_put(hdev);
+ return err;
+}
+
+int hci_dev_reset(__u16 dev)
+{
+ struct hci_dev *hdev;
+ int ret = 0;
+
+ if (!(hdev = hci_dev_get(dev)))
+ return -ENODEV;
+
+ hci_req_lock(hdev);
+ tasklet_disable(&hdev->tx_task);
+
+ if (!test_bit(HCI_UP, &hdev->flags))
+ goto done;
+
+ /* Drop queues */
+ skb_queue_purge(&hdev->rx_q);
+ skb_queue_purge(&hdev->cmd_q);
+
+ hci_dev_lock_bh(hdev);
+ inquiry_cache_flush(hdev);
+ hci_conn_hash_flush(hdev);
+ hci_dev_unlock_bh(hdev);
+
+ if (hdev->flush)
+ hdev->flush(hdev);
+
+ atomic_set(&hdev->cmd_cnt, 1);
+ hdev->acl_cnt = 0; hdev->sco_cnt = 0;
+
+ ret = __hci_request(hdev, hci_reset_req, 0, HCI_INIT_TIMEOUT);
+
+done:
+ tasklet_enable(&hdev->tx_task);
+ hci_req_unlock(hdev);
+ hci_dev_put(hdev);
+ return ret;
+}
+
+int hci_dev_reset_stat(__u16 dev)
+{
+ struct hci_dev *hdev;
+ int ret = 0;
+
+ if (!(hdev = hci_dev_get(dev)))
+ return -ENODEV;
+
+ memset(&hdev->stat, 0, sizeof(struct hci_dev_stats));
+
+ hci_dev_put(hdev);
+
+ return ret;
+}
+
+int hci_dev_cmd(unsigned int cmd, unsigned long arg)
+{
+ struct hci_dev *hdev;
+ struct hci_dev_req dr;
+ int err = 0;
+
+ if (copy_from_user(&dr, (void *) arg, sizeof(dr)))
+ return -EFAULT;
+
+ if (!(hdev = hci_dev_get(dr.dev_id)))
+ return -ENODEV;
+
+ switch (cmd) {
+ case HCISETAUTH:
+ err = hci_request(hdev, hci_auth_req, dr.dev_opt, HCI_INIT_TIMEOUT);
+ break;
+
+ case HCISETENCRYPT:
+ if (!lmp_encrypt_capable(hdev)) {
+ err = -EOPNOTSUPP;
+ break;
+ }
+
+ if (!test_bit(HCI_AUTH, &hdev->flags)) {
+ /* Auth must be enabled first */
+ err = hci_request(hdev, hci_auth_req,
+ dr.dev_opt, HCI_INIT_TIMEOUT);
+ if (err)
+ break;
+ }
+
+ err = hci_request(hdev, hci_encrypt_req,
+ dr.dev_opt, HCI_INIT_TIMEOUT);
+ break;
+
+ case HCISETSCAN:
+ err = hci_request(hdev, hci_scan_req, dr.dev_opt, HCI_INIT_TIMEOUT);
+ break;
+
+ case HCISETPTYPE:
+ hdev->pkt_type = (__u16) dr.dev_opt;
+ break;
+
+ case HCISETLINKPOL:
+ hdev->link_policy = (__u16) dr.dev_opt;
+ break;
+
+ case HCISETLINKMODE:
+ hdev->link_mode = ((__u16) dr.dev_opt) & (HCI_LM_MASTER | HCI_LM_ACCEPT);
+ break;
+
+ case HCISETACLMTU:
+ hdev->acl_mtu = *((__u16 *)&dr.dev_opt + 1);
+ hdev->acl_pkts = *((__u16 *)&dr.dev_opt + 0);
+ break;
+
+ case HCISETSCOMTU:
+ hdev->sco_mtu = *((__u16 *)&dr.dev_opt + 1);
+ hdev->sco_pkts = *((__u16 *)&dr.dev_opt + 0);
+ break;
+
+ default:
+ err = -EINVAL;
+ break;
+ }
+ hci_dev_put(hdev);
+ return err;
+}
+
+int hci_get_dev_list(unsigned long arg)
+{
+ struct hci_dev_list_req *dl;
+ struct hci_dev_req *dr;
+ struct list_head *p;
+ int n = 0, size, err;
+ __u16 dev_num;
+
+ if (get_user(dev_num, (__u16 *) arg))
+ return -EFAULT;
+
+ if (!dev_num || dev_num > (PAGE_SIZE * 2) / sizeof(*dr))
+ return -EINVAL;
+
+ size = sizeof(*dl) + dev_num * sizeof(*dr);
+
+ if (!(dl = kmalloc(size, GFP_KERNEL)))
+ return -ENOMEM;
+
+ dr = dl->dev_req;
+
+ read_lock_bh(&hdev_list_lock);
+ list_for_each(p, &hdev_list) {
+ struct hci_dev *hdev;
+ hdev = list_entry(p, struct hci_dev, list);
+ (dr + n)->dev_id = hdev->id;
+ (dr + n)->dev_opt = hdev->flags;
+ if (++n >= dev_num)
+ break;
+ }
+ read_unlock_bh(&hdev_list_lock);
+
+ dl->dev_num = n;
+ size = sizeof(*dl) + n * sizeof(*dr);
+
+ err = copy_to_user((void *) arg, dl, size);
+ kfree(dl);
+
+ return err ? -EFAULT : 0;
+}
+
+int hci_get_dev_info(unsigned long arg)
+{
+ struct hci_dev *hdev;
+ struct hci_dev_info di;
+ int err = 0;
+
+ if (copy_from_user(&di, (void *) arg, sizeof(di)))
+ return -EFAULT;
+
+ if (!(hdev = hci_dev_get(di.dev_id)))
+ return -ENODEV;
+
+ strcpy(di.name, hdev->name);
+ di.bdaddr = hdev->bdaddr;
+ di.type = hdev->type;
+ di.flags = hdev->flags;
+ di.pkt_type = hdev->pkt_type;
+ di.acl_mtu = hdev->acl_mtu;
+ di.acl_pkts = hdev->acl_pkts;
+ di.sco_mtu = hdev->sco_mtu;
+ di.sco_pkts = hdev->sco_pkts;
+ di.link_policy = hdev->link_policy;
+ di.link_mode = hdev->link_mode;
+
+ memcpy(&di.stat, &hdev->stat, sizeof(di.stat));
+ memcpy(&di.features, &hdev->features, sizeof(di.features));
+
+ if (copy_to_user((void *) arg, &di, sizeof(di)))
+ err = -EFAULT;
+
+ hci_dev_put(hdev);
+
+ return err;
+}
+
+
+/* ---- Interface to HCI drivers ---- */
+
+/* Register HCI device */
+int hci_register_dev(struct hci_dev *hdev)
+{
+ struct list_head *head = &hdev_list, *p;
+ int id = 0;
+
+ BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type);
+
+ if (!hdev->open || !hdev->close || !hdev->destruct)
+ return -EINVAL;
+
+ write_lock_bh(&hdev_list_lock);
+
+ /* Find first available device id */
+ list_for_each(p, &hdev_list) {
+ if (list_entry(p, struct hci_dev, list)->id != id)
+ break;
+ head = p; id++;
+ }
+
+ sprintf(hdev->name, "hci%d", id);
+ hdev->id = id;
+ list_add(&hdev->list, head);
+
+ atomic_set(&hdev->refcnt, 1);
+ spin_lock_init(&hdev->lock);
+
+ hdev->flags = 0;
+ hdev->pkt_type = (HCI_DM1 | HCI_DH1 | HCI_HV1);
+ hdev->link_mode = (HCI_LM_ACCEPT);
+
+ tasklet_init(&hdev->cmd_task, hci_cmd_task,(unsigned long) hdev);
+ tasklet_init(&hdev->rx_task, hci_rx_task, (unsigned long) hdev);
+ tasklet_init(&hdev->tx_task, hci_tx_task, (unsigned long) hdev);
+
+ skb_queue_head_init(&hdev->rx_q);
+ skb_queue_head_init(&hdev->cmd_q);
+ skb_queue_head_init(&hdev->raw_q);
+
+ init_waitqueue_head(&hdev->req_wait_q);
+ init_MUTEX(&hdev->req_lock);
+
+ inquiry_cache_init(hdev);
+
+ conn_hash_init(hdev);
+
+ memset(&hdev->stat, 0, sizeof(struct hci_dev_stats));
+
+ atomic_set(&hdev->promisc, 0);
+
+ MOD_INC_USE_COUNT;
+
+ write_unlock_bh(&hdev_list_lock);
+
+ hci_notify(hdev, HCI_DEV_REG);
+ hci_run_hotplug(hdev->name, "register");
+
+ return id;
+}
+
+/* Unregister HCI device */
+int hci_unregister_dev(struct hci_dev *hdev)
+{
+ BT_DBG("%p name %s type %d", hdev, hdev->name, hdev->type);
+
+ write_lock_bh(&hdev_list_lock);
+ list_del(&hdev->list);
+ write_unlock_bh(&hdev_list_lock);
+
+ hci_dev_do_close(hdev);
+
+ hci_notify(hdev, HCI_DEV_UNREG);
+ hci_run_hotplug(hdev->name, "unregister");
+
+ hci_dev_put(hdev);
+
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+/* Suspend HCI device */
+int hci_suspend_dev(struct hci_dev *hdev)
+{
+ hci_notify(hdev, HCI_DEV_SUSPEND);
+ hci_run_hotplug(hdev->name, "suspend");
+ return 0;
+}
+
+/* Resume HCI device */
+int hci_resume_dev(struct hci_dev *hdev)
+{
+ hci_notify(hdev, HCI_DEV_RESUME);
+ hci_run_hotplug(hdev->name, "resume");
+ return 0;
+}
+
+/* Receive frame from HCI drivers */
+int hci_recv_frame(struct sk_buff *skb)
+{
+ struct hci_dev *hdev = (struct hci_dev *) skb->dev;
+
+ if (!hdev || (!test_bit(HCI_UP, &hdev->flags) &&
+ !test_bit(HCI_INIT, &hdev->flags)) ) {
+ kfree_skb(skb);
+ return -1;
+ }
+
+ BT_DBG("%s type %d len %d", hdev->name, skb->pkt_type, skb->len);
+
+ /* Incomming skb */
+ bluez_cb(skb)->incomming = 1;
+
+ /* Time stamp */
+ do_gettimeofday(&skb->stamp);
+
+ /* Queue frame for rx task */
+ skb_queue_tail(&hdev->rx_q, skb);
+ hci_sched_rx(hdev);
+ return 0;
+}
+
+/* ---- Interface to upper protocols ---- */
+
+/* Register/Unregister protocols.
+ * hci_task_lock is used to ensure that no tasks are running. */
+int hci_register_proto(struct hci_proto *hp)
+{
+ int err = 0;
+
+ BT_DBG("%p name %s id %d", hp, hp->name, hp->id);
+
+ if (hp->id >= HCI_MAX_PROTO)
+ return -EINVAL;
+
+ write_lock_bh(&hci_task_lock);
+
+ if (!hci_proto[hp->id])
+ hci_proto[hp->id] = hp;
+ else
+ err = -EEXIST;
+
+ write_unlock_bh(&hci_task_lock);
+
+ return err;
+}
+
+int hci_unregister_proto(struct hci_proto *hp)
+{
+ int err = 0;
+
+ BT_DBG("%p name %s id %d", hp, hp->name, hp->id);
+
+ if (hp->id >= HCI_MAX_PROTO)
+ return -EINVAL;
+
+ write_lock_bh(&hci_task_lock);
+
+ if (hci_proto[hp->id])
+ hci_proto[hp->id] = NULL;
+ else
+ err = -ENOENT;
+
+ write_unlock_bh(&hci_task_lock);
+
+ return err;
+}
+
+static int hci_send_frame(struct sk_buff *skb)
+{
+ struct hci_dev *hdev = (struct hci_dev *) skb->dev;
+
+ if (!hdev) {
+ kfree_skb(skb);
+ return -ENODEV;
+ }
+
+ BT_DBG("%s type %d len %d", hdev->name, skb->pkt_type, skb->len);
+
+ if (atomic_read(&hdev->promisc)) {
+ /* Time stamp */
+ do_gettimeofday(&skb->stamp);
+
+ hci_send_to_sock(hdev, skb);
+ }
+
+ /* Get rid of skb owner, prior to sending to the driver. */
+ skb_orphan(skb);
+
+ return hdev->send(skb);
+}
+
+/* Send HCI command */
+int hci_send_cmd(struct hci_dev *hdev, __u16 ogf, __u16 ocf, __u32 plen, void *param)
+{
+ int len = HCI_COMMAND_HDR_SIZE + plen;
+ hci_command_hdr *hc;
+ struct sk_buff *skb;
+
+ BT_DBG("%s ogf 0x%x ocf 0x%x plen %d", hdev->name, ogf, ocf, plen);
+
+ if (!(skb = bluez_skb_alloc(len, GFP_ATOMIC))) {
+ BT_ERR("%s Can't allocate memory for HCI command", hdev->name);
+ return -ENOMEM;
+ }
+
+ hc = (hci_command_hdr *) skb_put(skb, HCI_COMMAND_HDR_SIZE);
+ hc->opcode = __cpu_to_le16(cmd_opcode_pack(ogf, ocf));
+ hc->plen = plen;
+
+ if (plen)
+ memcpy(skb_put(skb, plen), param, plen);
+
+ BT_DBG("skb len %d", skb->len);
+
+ skb->pkt_type = HCI_COMMAND_PKT;
+ skb->dev = (void *) hdev;
+ skb_queue_tail(&hdev->cmd_q, skb);
+ hci_sched_cmd(hdev);
+
+ return 0;
+}
+
+/* Get data from the previously sent command */
+void *hci_sent_cmd_data(struct hci_dev *hdev, __u16 ogf, __u16 ocf)
+{
+ hci_command_hdr *hc;
+
+ if (!hdev->sent_cmd)
+ return NULL;
+
+ hc = (void *) hdev->sent_cmd->data;
+
+ if (hc->opcode != __cpu_to_le16(cmd_opcode_pack(ogf, ocf)))
+ return NULL;
+
+ BT_DBG("%s ogf 0x%x ocf 0x%x", hdev->name, ogf, ocf);
+
+ return hdev->sent_cmd->data + HCI_COMMAND_HDR_SIZE;
+}
+
+/* Send ACL data */
+static void hci_add_acl_hdr(struct sk_buff *skb, __u16 handle, __u16 flags)
+{
+ int len = skb->len;
+ hci_acl_hdr *ah;
+
+ ah = (hci_acl_hdr *) skb_push(skb, HCI_ACL_HDR_SIZE);
+ ah->handle = __cpu_to_le16(acl_handle_pack(handle, flags));
+ ah->dlen = __cpu_to_le16(len);
+
+ skb->h.raw = (void *) ah;
+}
+
+int hci_send_acl(struct hci_conn *conn, struct sk_buff *skb, __u16 flags)
+{
+ struct hci_dev *hdev = conn->hdev;
+ struct sk_buff *list;
+
+ BT_DBG("%s conn %p flags 0x%x", hdev->name, conn, flags);
+
+ skb->dev = (void *) hdev;
+ skb->pkt_type = HCI_ACLDATA_PKT;
+ hci_add_acl_hdr(skb, conn->handle, flags | ACL_START);
+
+ if (!(list = skb_shinfo(skb)->frag_list)) {
+ /* Non fragmented */
+ BT_DBG("%s nonfrag skb %p len %d", hdev->name, skb, skb->len);
+
+ skb_queue_tail(&conn->data_q, skb);
+ } else {
+ /* Fragmented */
+ BT_DBG("%s frag %p len %d", hdev->name, skb, skb->len);
+
+ skb_shinfo(skb)->frag_list = NULL;
+
+ /* Queue all fragments atomically */
+ spin_lock_bh(&conn->data_q.lock);
+
+ __skb_queue_tail(&conn->data_q, skb);
+ do {
+ skb = list; list = list->next;
+
+ skb->dev = (void *) hdev;
+ skb->pkt_type = HCI_ACLDATA_PKT;
+ hci_add_acl_hdr(skb, conn->handle, flags | ACL_CONT);
+
+ BT_DBG("%s frag %p len %d", hdev->name, skb, skb->len);
+
+ __skb_queue_tail(&conn->data_q, skb);
+ } while (list);
+
+ spin_unlock_bh(&conn->data_q.lock);
+ }
+
+ hci_sched_tx(hdev);
+ return 0;
+}
+
+/* Send SCO data */
+int hci_send_sco(struct hci_conn *conn, struct sk_buff *skb)
+{
+ struct hci_dev *hdev = conn->hdev;
+ hci_sco_hdr hs;
+
+ BT_DBG("%s len %d", hdev->name, skb->len);
+
+ if (skb->len > hdev->sco_mtu) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ hs.handle = __cpu_to_le16(conn->handle);
+ hs.dlen = skb->len;
+
+ skb->h.raw = skb_push(skb, HCI_SCO_HDR_SIZE);
+ memcpy(skb->h.raw, &hs, HCI_SCO_HDR_SIZE);
+
+ skb->dev = (void *) hdev;
+ skb->pkt_type = HCI_SCODATA_PKT;
+ skb_queue_tail(&conn->data_q, skb);
+ hci_sched_tx(hdev);
+ return 0;
+}
+
+/* ---- HCI TX task (outgoing data) ---- */
+
+/* HCI Connection scheduler */
+static inline struct hci_conn *hci_low_sent(struct hci_dev *hdev, __u8 type, int *quote)
+{
+ struct conn_hash *h = &hdev->conn_hash;
+ struct hci_conn *conn = NULL;
+ int num = 0, min = ~0;
+ struct list_head *p;
+
+ /* We don't have to lock device here. Connections are always
+ * added and removed with TX task disabled. */
+ list_for_each(p, &h->list) {
+ struct hci_conn *c;
+ c = list_entry(p, struct hci_conn, list);
+
+ if (c->type != type || c->state != BT_CONNECTED
+ || skb_queue_empty(&c->data_q))
+ continue;
+ num++;
+
+ if (c->sent < min) {
+ min = c->sent;
+ conn = c;
+ }
+ }
+
+ if (conn) {
+ int cnt = (type == ACL_LINK ? hdev->acl_cnt : hdev->sco_cnt);
+ int q = cnt / num;
+ *quote = q ? q : 1;
+ } else
+ *quote = 0;
+
+ BT_DBG("conn %p quote %d", conn, *quote);
+ return conn;
+}
+
+static inline void hci_acl_tx_to(struct hci_dev *hdev)
+{
+ struct conn_hash *h = &hdev->conn_hash;
+ struct list_head *p;
+ struct hci_conn *c;
+
+ BT_ERR("%s ACL tx timeout", hdev->name);
+
+ /* Kill stalled connections */
+ list_for_each(p, &h->list) {
+ c = list_entry(p, struct hci_conn, list);
+ if (c->type == ACL_LINK && c->sent) {
+ BT_ERR("%s killing stalled ACL connection %s",
+ hdev->name, batostr(&c->dst));
+ hci_acl_disconn(c, 0x13);
+ }
+ }
+}
+
+static inline void hci_sched_acl(struct hci_dev *hdev)
+{
+ struct hci_conn *conn;
+ struct sk_buff *skb;
+ int quote;
+
+ BT_DBG("%s", hdev->name);
+
+ /* ACL tx timeout must be longer than maximum
+ * link supervision timeout (40.9 seconds) */
+ if (!hdev->acl_cnt && (jiffies - hdev->acl_last_tx) > (HZ * 45))
+ hci_acl_tx_to(hdev);
+
+ while (hdev->acl_cnt && (conn = hci_low_sent(hdev, ACL_LINK, &quote))) {
+ while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
+ BT_DBG("skb %p len %d", skb, skb->len);
+ hci_send_frame(skb);
+ hdev->acl_last_tx = jiffies;
+
+ hdev->acl_cnt--;
+ conn->sent++;
+ }
+ }
+}
+
+/* Schedule SCO */
+static inline void hci_sched_sco(struct hci_dev *hdev)
+{
+ struct hci_conn *conn;
+ struct sk_buff *skb;
+ int quote;
+
+ BT_DBG("%s", hdev->name);
+
+ while (hdev->sco_cnt && (conn = hci_low_sent(hdev, SCO_LINK, &quote))) {
+ while (quote-- && (skb = skb_dequeue(&conn->data_q))) {
+ BT_DBG("skb %p len %d", skb, skb->len);
+ hci_send_frame(skb);
+
+ conn->sent++;
+ if (conn->sent == ~0)
+ conn->sent = 0;
+ }
+ }
+}
+
+static void hci_tx_task(unsigned long arg)
+{
+ struct hci_dev *hdev = (struct hci_dev *) arg;
+ struct sk_buff *skb;
+
+ read_lock(&hci_task_lock);
+
+ BT_DBG("%s acl %d sco %d", hdev->name, hdev->acl_cnt, hdev->sco_cnt);
+
+ /* Schedule queues and send stuff to HCI driver */
+
+ hci_sched_acl(hdev);
+
+ hci_sched_sco(hdev);
+
+ /* Send next queued raw (unknown type) packet */
+ while ((skb = skb_dequeue(&hdev->raw_q)))
+ hci_send_frame(skb);
+
+ read_unlock(&hci_task_lock);
+}
+
+
+/* ----- HCI RX task (incomming data proccessing) ----- */
+
+/* ACL data packet */
+static inline void hci_acldata_packet(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ hci_acl_hdr *ah = (void *) skb->data;
+ struct hci_conn *conn;
+ __u16 handle, flags;
+
+ skb_pull(skb, HCI_ACL_HDR_SIZE);
+
+ handle = __le16_to_cpu(ah->handle);
+ flags = acl_flags(handle);
+ handle = acl_handle(handle);
+
+ BT_DBG("%s len %d handle 0x%x flags 0x%x", hdev->name, skb->len, handle, flags);
+
+ hdev->stat.acl_rx++;
+
+ hci_dev_lock(hdev);
+ conn = conn_hash_lookup_handle(hdev, handle);
+ hci_dev_unlock(hdev);
+
+ if (conn) {
+ register struct hci_proto *hp;
+
+ /* Send to upper protocol */
+ if ((hp = hci_proto[HCI_PROTO_L2CAP]) && hp->recv_acldata) {
+ hp->recv_acldata(conn, skb, flags);
+ return;
+ }
+ } else {
+ BT_ERR("%s ACL packet for unknown connection handle %d",
+ hdev->name, handle);
+ }
+
+ kfree_skb(skb);
+}
+
+/* SCO data packet */
+static inline void hci_scodata_packet(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ hci_sco_hdr *sh = (void *) skb->data;
+ struct hci_conn *conn;
+ __u16 handle;
+
+ skb_pull(skb, HCI_SCO_HDR_SIZE);
+
+ handle = __le16_to_cpu(sh->handle);
+
+ BT_DBG("%s len %d handle 0x%x", hdev->name, skb->len, handle);
+
+ hdev->stat.sco_rx++;
+
+ hci_dev_lock(hdev);
+ conn = conn_hash_lookup_handle(hdev, handle);
+ hci_dev_unlock(hdev);
+
+ if (conn) {
+ register struct hci_proto *hp;
+
+ /* Send to upper protocol */
+ if ((hp = hci_proto[HCI_PROTO_SCO]) && hp->recv_scodata) {
+ hp->recv_scodata(conn, skb);
+ return;
+ }
+ } else {
+ BT_ERR("%s SCO packet for unknown connection handle %d",
+ hdev->name, handle);
+ }
+
+ kfree_skb(skb);
+}
+
+void hci_rx_task(unsigned long arg)
+{
+ struct hci_dev *hdev = (struct hci_dev *) arg;
+ struct sk_buff *skb;
+
+ BT_DBG("%s", hdev->name);
+
+ read_lock(&hci_task_lock);
+
+ while ((skb = skb_dequeue(&hdev->rx_q))) {
+ if (atomic_read(&hdev->promisc)) {
+ /* Send copy to the sockets */
+ hci_send_to_sock(hdev, skb);
+ }
+
+ if (test_bit(HCI_RAW, &hdev->flags)) {
+ kfree_skb(skb);
+ continue;
+ }
+
+ if (test_bit(HCI_INIT, &hdev->flags)) {
+ /* Don't process data packets in this states. */
+ switch (skb->pkt_type) {
+ case HCI_ACLDATA_PKT:
+ case HCI_SCODATA_PKT:
+ kfree_skb(skb);
+ continue;
+ };
+ }
+
+ /* Process frame */
+ switch (skb->pkt_type) {
+ case HCI_EVENT_PKT:
+ hci_event_packet(hdev, skb);
+ break;
+
+ case HCI_ACLDATA_PKT:
+ BT_DBG("%s ACL data packet", hdev->name);
+ hci_acldata_packet(hdev, skb);
+ break;
+
+ case HCI_SCODATA_PKT:
+ BT_DBG("%s SCO data packet", hdev->name);
+ hci_scodata_packet(hdev, skb);
+ break;
+
+ default:
+ kfree_skb(skb);
+ break;
+ }
+ }
+
+ read_unlock(&hci_task_lock);
+}
+
+static void hci_cmd_task(unsigned long arg)
+{
+ struct hci_dev *hdev = (struct hci_dev *) arg;
+ struct sk_buff *skb;
+
+ BT_DBG("%s cmd %d", hdev->name, atomic_read(&hdev->cmd_cnt));
+
+ if (!atomic_read(&hdev->cmd_cnt) && (jiffies - hdev->cmd_last_tx) > HZ) {
+ BT_ERR("%s command tx timeout", hdev->name);
+ atomic_set(&hdev->cmd_cnt, 1);
+ }
+
+ /* Send queued commands */
+ if (atomic_read(&hdev->cmd_cnt) && (skb = skb_dequeue(&hdev->cmd_q))) {
+ if (hdev->sent_cmd)
+ kfree_skb(hdev->sent_cmd);
+
+ if ((hdev->sent_cmd = skb_clone(skb, GFP_ATOMIC))) {
+ atomic_dec(&hdev->cmd_cnt);
+ hci_send_frame(skb);
+ hdev->cmd_last_tx = jiffies;
+ } else {
+ skb_queue_head(&hdev->cmd_q, skb);
+ hci_sched_cmd(hdev);
+ }
+ }
+}
+
+/* ---- Initialization ---- */
+
+int hci_core_init(void)
+{
+ return 0;
+}
+
+int hci_core_cleanup(void)
+{
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/hci_event.c b/uClinux-2.4.31-uc0/net/bluetooth/hci_event.c
new file mode 100644
index 0000000..70a7912
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/hci_event.c
@@ -0,0 +1,910 @@
+/*
+ BlueZ - Bluetooth protocol stack for Linux
+ Copyright (C) 2000-2001 Qualcomm Incorporated
+
+ Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * HCI Events.
+ *
+ * $Id: hci_event.c,v 1.4 2002/07/27 18:14:38 maxk Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <net/sock.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/unaligned.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#ifndef HCI_CORE_DEBUG
+#undef BT_DBG
+#define BT_DBG( A... )
+#endif
+
+/* Handle HCI Event packets */
+
+/* Command Complete OGF LINK_CTL */
+static void hci_cc_link_ctl(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb)
+{
+ __u8 status;
+
+ BT_DBG("%s ocf 0x%x", hdev->name, ocf);
+
+ switch (ocf) {
+ case OCF_INQUIRY_CANCEL:
+ status = *((__u8 *) skb->data);
+
+ if (status) {
+ BT_DBG("%s Inquiry cancel error: status 0x%x", hdev->name, status);
+ } else {
+ clear_bit(HCI_INQUIRY, &hdev->flags);
+ hci_req_complete(hdev, status);
+ }
+ break;
+
+ default:
+ BT_DBG("%s Command complete: ogf LINK_CTL ocf %x", hdev->name, ocf);
+ break;
+ };
+}
+
+/* Command Complete OGF LINK_POLICY */
+static void hci_cc_link_policy(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb)
+{
+ struct hci_conn *conn;
+ role_discovery_rp *rd;
+
+ BT_DBG("%s ocf 0x%x", hdev->name, ocf);
+
+ switch (ocf) {
+ case OCF_ROLE_DISCOVERY:
+ rd = (void *) skb->data;
+
+ if (rd->status)
+ break;
+
+ hci_dev_lock(hdev);
+
+ conn = conn_hash_lookup_handle(hdev, __le16_to_cpu(rd->handle));
+ if (conn) {
+ if (rd->role)
+ conn->link_mode &= ~HCI_LM_MASTER;
+ else
+ conn->link_mode |= HCI_LM_MASTER;
+ }
+
+ hci_dev_unlock(hdev);
+ break;
+
+ default:
+ BT_DBG("%s: Command complete: ogf LINK_POLICY ocf %x",
+ hdev->name, ocf);
+ break;
+ };
+}
+
+/* Command Complete OGF HOST_CTL */
+static void hci_cc_host_ctl(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb)
+{
+ __u8 status, param;
+ void *sent;
+
+ BT_DBG("%s ocf 0x%x", hdev->name, ocf);
+
+ switch (ocf) {
+ case OCF_RESET:
+ status = *((__u8 *) skb->data);
+ hci_req_complete(hdev, status);
+ break;
+
+ case OCF_SET_EVENT_FLT:
+ status = *((__u8 *) skb->data);
+ if (status) {
+ BT_DBG("%s SET_EVENT_FLT failed %d", hdev->name, status);
+ } else {
+ BT_DBG("%s SET_EVENT_FLT succeseful", hdev->name);
+ }
+ break;
+
+ case OCF_WRITE_AUTH_ENABLE:
+ sent = hci_sent_cmd_data(hdev, OGF_HOST_CTL, OCF_WRITE_AUTH_ENABLE);
+ if (!sent)
+ break;
+
+ status = *((__u8 *) skb->data);
+ param = *((__u8 *) sent);
+
+ if (!status) {
+ if (param == AUTH_ENABLED)
+ set_bit(HCI_AUTH, &hdev->flags);
+ else
+ clear_bit(HCI_AUTH, &hdev->flags);
+ }
+ hci_req_complete(hdev, status);
+ break;
+
+ case OCF_WRITE_ENCRYPT_MODE:
+ sent = hci_sent_cmd_data(hdev, OGF_HOST_CTL, OCF_WRITE_ENCRYPT_MODE);
+ if (!sent)
+ break;
+
+ status = *((__u8 *) skb->data);
+ param = *((__u8 *) sent);
+
+ if (!status) {
+ if (param)
+ set_bit(HCI_ENCRYPT, &hdev->flags);
+ else
+ clear_bit(HCI_ENCRYPT, &hdev->flags);
+ }
+ hci_req_complete(hdev, status);
+ break;
+
+ case OCF_WRITE_CA_TIMEOUT:
+ status = *((__u8 *) skb->data);
+ if (status) {
+ BT_DBG("%s OCF_WRITE_CA_TIMEOUT failed %d", hdev->name, status);
+ } else {
+ BT_DBG("%s OCF_WRITE_CA_TIMEOUT succeseful", hdev->name);
+ }
+ break;
+
+ case OCF_WRITE_PG_TIMEOUT:
+ status = *((__u8 *) skb->data);
+ if (status) {
+ BT_DBG("%s OCF_WRITE_PG_TIMEOUT failed %d", hdev->name, status);
+ } else {
+ BT_DBG("%s: OCF_WRITE_PG_TIMEOUT succeseful", hdev->name);
+ }
+ break;
+
+ case OCF_WRITE_SCAN_ENABLE:
+ sent = hci_sent_cmd_data(hdev, OGF_HOST_CTL, OCF_WRITE_SCAN_ENABLE);
+ if (!sent)
+ break;
+ status = *((__u8 *) skb->data);
+ param = *((__u8 *) sent);
+
+ BT_DBG("param 0x%x", param);
+
+ if (!status) {
+ clear_bit(HCI_PSCAN, &hdev->flags);
+ clear_bit(HCI_ISCAN, &hdev->flags);
+ if (param & SCAN_INQUIRY)
+ set_bit(HCI_ISCAN, &hdev->flags);
+
+ if (param & SCAN_PAGE)
+ set_bit(HCI_PSCAN, &hdev->flags);
+ }
+ hci_req_complete(hdev, status);
+ break;
+
+ case OCF_HOST_BUFFER_SIZE:
+ status = *((__u8 *) skb->data);
+ if (status) {
+ BT_DBG("%s OCF_BUFFER_SIZE failed %d", hdev->name, status);
+ hci_req_complete(hdev, status);
+ }
+ break;
+
+ default:
+ BT_DBG("%s Command complete: ogf HOST_CTL ocf %x", hdev->name, ocf);
+ break;
+ };
+}
+
+/* Command Complete OGF INFO_PARAM */
+static void hci_cc_info_param(struct hci_dev *hdev, __u16 ocf, struct sk_buff *skb)
+{
+ read_local_features_rp *lf;
+ read_buffer_size_rp *bs;
+ read_bd_addr_rp *ba;
+
+ BT_DBG("%s ocf 0x%x", hdev->name, ocf);
+
+ switch (ocf) {
+ case OCF_READ_LOCAL_FEATURES:
+ lf = (read_local_features_rp *) skb->data;
+
+ if (lf->status) {
+ BT_DBG("%s READ_LOCAL_FEATURES failed %d", hdev->name, lf->status);
+ break;
+ }
+
+ memcpy(hdev->features, lf->features, sizeof(hdev->features));
+
+ /* Adjust default settings according to features
+ * supported by device. */
+ if (hdev->features[0] & LMP_3SLOT)
+ hdev->pkt_type |= (HCI_DM3 | HCI_DH3);
+
+ if (hdev->features[0] & LMP_5SLOT)
+ hdev->pkt_type |= (HCI_DM5 | HCI_DH5);
+
+ if (hdev->features[1] & LMP_HV2)
+ hdev->pkt_type |= (HCI_HV2);
+
+ if (hdev->features[1] & LMP_HV3)
+ hdev->pkt_type |= (HCI_HV3);
+
+ BT_DBG("%s: features 0x%x 0x%x 0x%x", hdev->name, lf->features[0], lf->features[1], lf->features[2]);
+
+ break;
+
+ case OCF_READ_BUFFER_SIZE:
+ bs = (read_buffer_size_rp *) skb->data;
+
+ if (bs->status) {
+ BT_DBG("%s READ_BUFFER_SIZE failed %d", hdev->name, bs->status);
+ hci_req_complete(hdev, bs->status);
+ break;
+ }
+
+ hdev->acl_mtu = __le16_to_cpu(bs->acl_mtu);
+ hdev->sco_mtu = bs->sco_mtu ? bs->sco_mtu : 64;
+ hdev->acl_pkts = hdev->acl_cnt = __le16_to_cpu(bs->acl_max_pkt);
+ hdev->sco_pkts = hdev->sco_cnt = __le16_to_cpu(bs->sco_max_pkt);
+
+ BT_DBG("%s mtu: acl %d, sco %d max_pkt: acl %d, sco %d", hdev->name,
+ hdev->acl_mtu, hdev->sco_mtu, hdev->acl_pkts, hdev->sco_pkts);
+ break;
+
+ case OCF_READ_BD_ADDR:
+ ba = (read_bd_addr_rp *) skb->data;
+
+ if (!ba->status) {
+ bacpy(&hdev->bdaddr, &ba->bdaddr);
+ } else {
+ BT_DBG("%s: READ_BD_ADDR failed %d", hdev->name, ba->status);
+ }
+
+ hci_req_complete(hdev, ba->status);
+ break;
+
+ default:
+ BT_DBG("%s Command complete: ogf INFO_PARAM ocf %x", hdev->name, ocf);
+ break;
+ };
+}
+
+/* Command Status OGF LINK_CTL */
+static inline void hci_cs_create_conn(struct hci_dev *hdev, __u8 status)
+{
+ struct hci_conn *conn;
+ create_conn_cp *cc = hci_sent_cmd_data(hdev, OGF_LINK_CTL, OCF_CREATE_CONN);
+
+ if (!cc)
+ return;
+
+ hci_dev_lock(hdev);
+
+ conn = conn_hash_lookup_ba(hdev, ACL_LINK, &cc->bdaddr);
+
+ BT_DBG("%s status 0x%x bdaddr %s conn %p", hdev->name,
+ status, batostr(&cc->bdaddr), conn);
+
+ if (status) {
+ if (conn && conn->state == BT_CONNECT) {
+ conn->state = BT_CLOSED;
+ hci_proto_connect_cfm(conn, status);
+ hci_conn_del(conn);
+ }
+ } else {
+ if (!conn) {
+ conn = hci_conn_add(hdev, ACL_LINK, &cc->bdaddr);
+ if (conn) {
+ conn->out = 1;
+ conn->link_mode |= HCI_LM_MASTER;
+ } else
+ BT_ERR("No memmory for new connection");
+ }
+ }
+
+ hci_dev_unlock(hdev);
+}
+
+static void hci_cs_link_ctl(struct hci_dev *hdev, __u16 ocf, __u8 status)
+{
+ BT_DBG("%s ocf 0x%x", hdev->name, ocf);
+
+ switch (ocf) {
+ case OCF_CREATE_CONN:
+ hci_cs_create_conn(hdev, status);
+ break;
+
+ case OCF_ADD_SCO:
+ if (status) {
+ struct hci_conn *acl, *sco;
+ add_sco_cp *cp = hci_sent_cmd_data(hdev,
+ OGF_LINK_CTL, OCF_ADD_SCO);
+ __u16 handle;
+
+ if (!cp)
+ break;
+
+ handle = __le16_to_cpu(cp->handle);
+
+ BT_DBG("%s Add SCO error: handle %d status 0x%x", hdev->name, handle, status);
+
+ hci_dev_lock(hdev);
+
+ acl = conn_hash_lookup_handle(hdev, handle);
+ if (acl && (sco = acl->link)) {
+ sco->state = BT_CLOSED;
+ hci_proto_connect_cfm(sco, status);
+ hci_conn_del(sco);
+ }
+
+ hci_dev_unlock(hdev);
+ }
+ break;
+
+ case OCF_INQUIRY:
+ if (status) {
+ BT_DBG("%s Inquiry error: status 0x%x", hdev->name, status);
+ hci_req_complete(hdev, status);
+ } else {
+ set_bit(HCI_INQUIRY, &hdev->flags);
+ }
+ break;
+
+ default:
+ BT_DBG("%s Command status: ogf LINK_CTL ocf %x status %d",
+ hdev->name, ocf, status);
+ break;
+ };
+}
+
+/* Command Status OGF LINK_POLICY */
+static void hci_cs_link_policy(struct hci_dev *hdev, __u16 ocf, __u8 status)
+{
+ BT_DBG("%s ocf 0x%x", hdev->name, ocf);
+
+ switch (ocf) {
+ default:
+ BT_DBG("%s Command status: ogf HOST_POLICY ocf %x", hdev->name, ocf);
+ break;
+ };
+}
+
+/* Command Status OGF HOST_CTL */
+static void hci_cs_host_ctl(struct hci_dev *hdev, __u16 ocf, __u8 status)
+{
+ BT_DBG("%s ocf 0x%x", hdev->name, ocf);
+
+ switch (ocf) {
+ default:
+ BT_DBG("%s Command status: ogf HOST_CTL ocf %x", hdev->name, ocf);
+ break;
+ };
+}
+
+/* Command Status OGF INFO_PARAM */
+static void hci_cs_info_param(struct hci_dev *hdev, __u16 ocf, __u8 status)
+{
+ BT_DBG("%s: hci_cs_info_param: ocf 0x%x", hdev->name, ocf);
+
+ switch (ocf) {
+ default:
+ BT_DBG("%s Command status: ogf INFO_PARAM ocf %x", hdev->name, ocf);
+ break;
+ };
+}
+
+/* Inquiry Complete */
+static inline void hci_inquiry_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ __u8 status = *((__u8 *) skb->data);
+
+ BT_DBG("%s status %d", hdev->name, status);
+
+ clear_bit(HCI_INQUIRY, &hdev->flags);
+ hci_req_complete(hdev, status);
+}
+
+/* Inquiry Result */
+static inline void hci_inquiry_result_evt(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ inquiry_info *info = (inquiry_info *) (skb->data + 1);
+ int num_rsp = *((__u8 *) skb->data);
+
+ BT_DBG("%s num_rsp %d", hdev->name, num_rsp);
+
+ hci_dev_lock(hdev);
+ for (; num_rsp; num_rsp--)
+ inquiry_cache_update(hdev, info++);
+ hci_dev_unlock(hdev);
+}
+
+/* Inquiry Result With RSSI */
+static inline void hci_inquiry_result_with_rssi_evt(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ inquiry_info_with_rssi *info = (inquiry_info_with_rssi *) (skb->data + 1);
+ int num_rsp = *((__u8 *) skb->data);
+
+ BT_DBG("%s num_rsp %d", hdev->name, num_rsp);
+
+ hci_dev_lock(hdev);
+ for (; num_rsp; num_rsp--) {
+ inquiry_info tmp;
+ bacpy(&tmp.bdaddr, &info->bdaddr);
+ tmp.pscan_rep_mode = info->pscan_rep_mode;
+ tmp.pscan_period_mode = info->pscan_period_mode;
+ tmp.pscan_mode = 0x00;
+ memcpy(tmp.dev_class, &info->dev_class, 3);
+ tmp.clock_offset = info->clock_offset;
+ info++;
+ inquiry_cache_update(hdev, &tmp);
+ }
+ hci_dev_unlock(hdev);
+}
+
+/* Connect Request */
+static inline void hci_conn_request_evt(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ evt_conn_request *cr = (evt_conn_request *) skb->data;
+ int mask = hdev->link_mode;
+
+ BT_DBG("%s Connection request: %s type 0x%x", hdev->name,
+ batostr(&cr->bdaddr), cr->link_type);
+
+ mask |= hci_proto_connect_ind(hdev, &cr->bdaddr, cr->link_type);
+
+ if (mask & HCI_LM_ACCEPT) {
+ /* Connection accepted */
+ struct hci_conn *conn;
+ accept_conn_req_cp ac;
+
+ hci_dev_lock(hdev);
+ conn = conn_hash_lookup_ba(hdev, cr->link_type, &cr->bdaddr);
+ if (!conn) {
+ if (!(conn = hci_conn_add(hdev, cr->link_type, &cr->bdaddr))) {
+ BT_ERR("No memmory for new connection");
+ hci_dev_unlock(hdev);
+ return;
+ }
+ }
+ conn->state = BT_CONNECT;
+ hci_dev_unlock(hdev);
+
+ bacpy(&ac.bdaddr, &cr->bdaddr);
+
+ if (lmp_rswitch_capable(hdev) && (mask & HCI_LM_MASTER))
+ ac.role = 0x00; /* Become master */
+ else
+ ac.role = 0x01; /* Remain slave */
+
+ hci_send_cmd(hdev, OGF_LINK_CTL, OCF_ACCEPT_CONN_REQ,
+ ACCEPT_CONN_REQ_CP_SIZE, &ac);
+ } else {
+ /* Connection rejected */
+ reject_conn_req_cp rc;
+
+ bacpy(&rc.bdaddr, &cr->bdaddr);
+ rc.reason = 0x0f;
+ hci_send_cmd(hdev, OGF_LINK_CTL, OCF_REJECT_CONN_REQ,
+ REJECT_CONN_REQ_CP_SIZE, &rc);
+ }
+}
+
+/* Connect Complete */
+static inline void hci_conn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ evt_conn_complete *cc = (evt_conn_complete *) skb->data;
+ struct hci_conn *conn = NULL;
+
+ BT_DBG("%s", hdev->name);
+
+ hci_dev_lock(hdev);
+
+ conn = conn_hash_lookup_ba(hdev, cc->link_type, &cc->bdaddr);
+ if (!conn) {
+ hci_dev_unlock(hdev);
+ return;
+ }
+
+ if (!cc->status) {
+ conn->handle = __le16_to_cpu(cc->handle);
+ conn->state = BT_CONNECTED;
+
+ if (test_bit(HCI_AUTH, &hdev->flags))
+ conn->link_mode |= HCI_LM_AUTH;
+
+ if (test_bit(HCI_ENCRYPT, &hdev->flags))
+ conn->link_mode |= HCI_LM_ENCRYPT;
+
+
+ /* Set link policy */
+ if (conn->type == ACL_LINK && hdev->link_policy) {
+ write_link_policy_cp lp;
+ lp.handle = cc->handle;
+ lp.policy = __cpu_to_le16(hdev->link_policy);
+ hci_send_cmd(hdev, OGF_LINK_POLICY, OCF_WRITE_LINK_POLICY,
+ WRITE_LINK_POLICY_CP_SIZE, &lp);
+ }
+
+ /* Set packet type for incomming connection */
+ if (!conn->out) {
+ change_conn_ptype_cp cp;
+ cp.handle = cc->handle;
+ cp.pkt_type = (conn->type == ACL_LINK) ?
+ __cpu_to_le16(hdev->pkt_type & ACL_PTYPE_MASK):
+ __cpu_to_le16(hdev->pkt_type & SCO_PTYPE_MASK);
+
+ hci_send_cmd(hdev, OGF_LINK_CTL, OCF_CHANGE_CONN_PTYPE,
+ CHANGE_CONN_PTYPE_CP_SIZE, &cp);
+ }
+ } else
+ conn->state = BT_CLOSED;
+
+ if (conn->type == ACL_LINK) {
+ struct hci_conn *sco = conn->link;
+ if (sco) {
+ if (!cc->status)
+ hci_add_sco(sco, conn->handle);
+ else {
+ hci_proto_connect_cfm(sco, cc->status);
+ hci_conn_del(sco);
+ }
+ }
+ }
+
+ hci_proto_connect_cfm(conn, cc->status);
+ if (cc->status)
+ hci_conn_del(conn);
+
+ hci_dev_unlock(hdev);
+}
+
+/* Disconnect Complete */
+static inline void hci_disconn_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ evt_disconn_complete *dc = (evt_disconn_complete *) skb->data;
+ struct hci_conn *conn = NULL;
+ __u16 handle = __le16_to_cpu(dc->handle);
+
+ BT_DBG("%s status %d", hdev->name, dc->status);
+
+ if (dc->status)
+ return;
+
+ hci_dev_lock(hdev);
+
+ conn = conn_hash_lookup_handle(hdev, handle);
+ if (conn) {
+ conn->state = BT_CLOSED;
+ hci_proto_disconn_ind(conn, dc->reason);
+ hci_conn_del(conn);
+ }
+
+ hci_dev_unlock(hdev);
+}
+
+/* Number of completed packets */
+static inline void hci_num_comp_pkts_evt(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ evt_num_comp_pkts *nc = (evt_num_comp_pkts *) skb->data;
+ __u16 *ptr;
+ int i;
+
+ skb_pull(skb, EVT_NUM_COMP_PKTS_SIZE);
+
+ BT_DBG("%s num_hndl %d", hdev->name, nc->num_hndl);
+
+ if (skb->len < nc->num_hndl * 4) {
+ BT_DBG("%s bad parameters", hdev->name);
+ return;
+ }
+
+ tasklet_disable(&hdev->tx_task);
+
+ for (i = 0, ptr = (__u16 *) skb->data; i < nc->num_hndl; i++) {
+ struct hci_conn *conn;
+ __u16 handle, count;
+
+ handle = __le16_to_cpu(get_unaligned(ptr++));
+ count = __le16_to_cpu(get_unaligned(ptr++));
+
+ conn = conn_hash_lookup_handle(hdev, handle);
+ if (conn) {
+ conn->sent -= count;
+
+ if (conn->type == SCO_LINK) {
+ if ((hdev->sco_cnt += count) > hdev->sco_pkts)
+ hdev->sco_cnt = hdev->sco_pkts;
+ } else {
+ if ((hdev->acl_cnt += count) > hdev->acl_pkts)
+ hdev->acl_cnt = hdev->acl_pkts;
+ }
+ }
+ }
+ hci_sched_tx(hdev);
+
+ tasklet_enable(&hdev->tx_task);
+}
+
+/* Role Change */
+static inline void hci_role_change_evt(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ evt_role_change *rc = (evt_role_change *) skb->data;
+ struct hci_conn *conn = NULL;
+
+ BT_DBG("%s status %d", hdev->name, rc->status);
+
+ if (rc->status)
+ return;
+
+ hci_dev_lock(hdev);
+
+ conn = conn_hash_lookup_ba(hdev, ACL_LINK, &rc->bdaddr);
+ if (conn) {
+ if (rc->role)
+ conn->link_mode &= ~HCI_LM_MASTER;
+ else
+ conn->link_mode |= HCI_LM_MASTER;
+ }
+
+ hci_dev_unlock(hdev);
+}
+
+/* Authentication Complete */
+static inline void hci_auth_complete_evt(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ evt_auth_complete *ac = (evt_auth_complete *) skb->data;
+ struct hci_conn *conn = NULL;
+ __u16 handle = __le16_to_cpu(ac->handle);
+
+ BT_DBG("%s status %d", hdev->name, ac->status);
+
+ hci_dev_lock(hdev);
+
+ conn = conn_hash_lookup_handle(hdev, handle);
+ if (conn) {
+ if (!ac->status)
+ conn->link_mode |= HCI_LM_AUTH;
+ clear_bit(HCI_CONN_AUTH_PEND, &conn->pend);
+
+ hci_proto_auth_cfm(conn, ac->status);
+
+ if (test_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend)) {
+ if (!ac->status) {
+ set_conn_encrypt_cp ce;
+ ce.handle = __cpu_to_le16(conn->handle);
+ ce.encrypt = 1;
+ hci_send_cmd(conn->hdev, OGF_LINK_CTL,
+ OCF_SET_CONN_ENCRYPT,
+ SET_CONN_ENCRYPT_CP_SIZE, &ce);
+ } else {
+ clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend);
+ hci_proto_encrypt_cfm(conn, ac->status);
+ }
+ }
+ }
+
+ hci_dev_unlock(hdev);
+}
+
+/* Encryption Change */
+static inline void hci_encrypt_change_evt(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ evt_encrypt_change *ec = (evt_encrypt_change *) skb->data;
+ struct hci_conn *conn = NULL;
+ __u16 handle = __le16_to_cpu(ec->handle);
+
+ BT_DBG("%s status %d", hdev->name, ec->status);
+
+ hci_dev_lock(hdev);
+
+ conn = conn_hash_lookup_handle(hdev, handle);
+ if (conn) {
+ if (!ec->status) {
+ if (ec->encrypt)
+ conn->link_mode |= HCI_LM_ENCRYPT;
+ else
+ conn->link_mode &= ~HCI_LM_ENCRYPT;
+ }
+ clear_bit(HCI_CONN_ENCRYPT_PEND, &conn->pend);
+
+ hci_proto_encrypt_cfm(conn, ec->status);
+ }
+
+ hci_dev_unlock(hdev);
+}
+
+void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ hci_event_hdr *he = (hci_event_hdr *) skb->data;
+ evt_cmd_status *cs;
+ evt_cmd_complete *ec;
+ __u16 opcode, ocf, ogf;
+
+ skb_pull(skb, HCI_EVENT_HDR_SIZE);
+
+ BT_DBG("%s evt 0x%x", hdev->name, he->evt);
+
+ switch (he->evt) {
+ case EVT_NUM_COMP_PKTS:
+ hci_num_comp_pkts_evt(hdev, skb);
+ break;
+
+ case EVT_INQUIRY_COMPLETE:
+ hci_inquiry_complete_evt(hdev, skb);
+ break;
+
+ case EVT_INQUIRY_RESULT:
+ hci_inquiry_result_evt(hdev, skb);
+ break;
+
+ case EVT_INQUIRY_RESULT_WITH_RSSI:
+ hci_inquiry_result_with_rssi_evt(hdev, skb);
+ break;
+
+ case EVT_CONN_REQUEST:
+ hci_conn_request_evt(hdev, skb);
+ break;
+
+ case EVT_CONN_COMPLETE:
+ hci_conn_complete_evt(hdev, skb);
+ break;
+
+ case EVT_DISCONN_COMPLETE:
+ hci_disconn_complete_evt(hdev, skb);
+ break;
+
+ case EVT_ROLE_CHANGE:
+ hci_role_change_evt(hdev, skb);
+ break;
+
+ case EVT_AUTH_COMPLETE:
+ hci_auth_complete_evt(hdev, skb);
+ break;
+
+ case EVT_ENCRYPT_CHANGE:
+ hci_encrypt_change_evt(hdev, skb);
+ break;
+
+ case EVT_CMD_STATUS:
+ cs = (evt_cmd_status *) skb->data;
+ skb_pull(skb, EVT_CMD_STATUS_SIZE);
+
+ opcode = __le16_to_cpu(cs->opcode);
+ ogf = cmd_opcode_ogf(opcode);
+ ocf = cmd_opcode_ocf(opcode);
+
+ switch (ogf) {
+ case OGF_INFO_PARAM:
+ hci_cs_info_param(hdev, ocf, cs->status);
+ break;
+
+ case OGF_HOST_CTL:
+ hci_cs_host_ctl(hdev, ocf, cs->status);
+ break;
+
+ case OGF_LINK_CTL:
+ hci_cs_link_ctl(hdev, ocf, cs->status);
+ break;
+
+ case OGF_LINK_POLICY:
+ hci_cs_link_policy(hdev, ocf, cs->status);
+ break;
+
+ default:
+ BT_DBG("%s Command Status OGF %x", hdev->name, ogf);
+ break;
+ };
+
+ if (cs->ncmd) {
+ atomic_set(&hdev->cmd_cnt, 1);
+ if (!skb_queue_empty(&hdev->cmd_q))
+ hci_sched_cmd(hdev);
+ }
+ break;
+
+ case EVT_CMD_COMPLETE:
+ ec = (evt_cmd_complete *) skb->data;
+ skb_pull(skb, EVT_CMD_COMPLETE_SIZE);
+
+ opcode = __le16_to_cpu(ec->opcode);
+ ogf = cmd_opcode_ogf(opcode);
+ ocf = cmd_opcode_ocf(opcode);
+
+ switch (ogf) {
+ case OGF_INFO_PARAM:
+ hci_cc_info_param(hdev, ocf, skb);
+ break;
+
+ case OGF_HOST_CTL:
+ hci_cc_host_ctl(hdev, ocf, skb);
+ break;
+
+ case OGF_LINK_CTL:
+ hci_cc_link_ctl(hdev, ocf, skb);
+ break;
+
+ case OGF_LINK_POLICY:
+ hci_cc_link_policy(hdev, ocf, skb);
+ break;
+
+ default:
+ BT_DBG("%s Command Completed OGF %x", hdev->name, ogf);
+ break;
+ };
+
+ if (ec->ncmd) {
+ atomic_set(&hdev->cmd_cnt, 1);
+ if (!skb_queue_empty(&hdev->cmd_q))
+ hci_sched_cmd(hdev);
+ }
+ break;
+ };
+
+ kfree_skb(skb);
+ hdev->stat.evt_rx++;
+}
+
+/* General internal stack event */
+void hci_si_event(struct hci_dev *hdev, int type, int dlen, void *data)
+{
+ hci_event_hdr *eh;
+ evt_stack_internal *si;
+ struct sk_buff *skb;
+ int size;
+ void *ptr;
+
+ size = HCI_EVENT_HDR_SIZE + EVT_STACK_INTERNAL_SIZE + dlen;
+ skb = bluez_skb_alloc(size, GFP_ATOMIC);
+ if (!skb)
+ return;
+
+ ptr = skb_put(skb, size);
+
+ eh = ptr;
+ eh->evt = EVT_STACK_INTERNAL;
+ eh->plen = EVT_STACK_INTERNAL_SIZE + dlen;
+ ptr += HCI_EVENT_HDR_SIZE;
+
+ si = ptr;
+ si->type = type;
+ memcpy(si->data, data, dlen);
+
+ skb->pkt_type = HCI_EVENT_PKT;
+ skb->dev = (void *) hdev;
+ hci_send_to_sock(hdev, skb);
+ kfree_skb(skb);
+}
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/hci_sock.c b/uClinux-2.4.31-uc0/net/bluetooth/hci_sock.c
new file mode 100644
index 0000000..0d3aed8
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/hci_sock.c
@@ -0,0 +1,648 @@
+/*
+ BlueZ - Bluetooth protocol stack for Linux
+ Copyright (C) 2000-2001 Qualcomm Incorporated
+
+ Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * BlueZ HCI socket layer.
+ *
+ * $Id: hci_sock.c,v 1.5 2002/07/22 20:32:54 maxk Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/tqueue.h>
+#include <linux/interrupt.h>
+#include <linux/socket.h>
+#include <linux/ioctl.h>
+#include <net/sock.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/unaligned.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#ifndef HCI_SOCK_DEBUG
+#undef BT_DBG
+#define BT_DBG( A... )
+#endif
+
+/* ----- HCI socket interface ----- */
+
+/* Security filter */
+static struct hci_sec_filter hci_sec_filter = {
+ /* Packet types */
+ 0x10,
+ /* Events */
+ { 0x1000d9fe, 0x0000300c },
+ /* Commands */
+ {
+ { 0x0 },
+ /* OGF_LINK_CTL */
+ { 0xbe000006, 0x00000001, 0x0000, 0x00 },
+ /* OGF_LINK_POLICY */
+ { 0x00005200, 0x00000000, 0x0000, 0x00 },
+ /* OGF_HOST_CTL */
+ { 0xaab00200, 0x2b402aaa, 0x0154, 0x00 },
+ /* OGF_INFO_PARAM */
+ { 0x000002be, 0x00000000, 0x0000, 0x00 },
+ /* OGF_STATUS_PARAM */
+ { 0x000000ea, 0x00000000, 0x0000, 0x00 }
+ }
+};
+
+static struct bluez_sock_list hci_sk_list = {
+ lock: RW_LOCK_UNLOCKED
+};
+
+/* Send frame to RAW socket */
+void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct sock * sk;
+
+ BT_DBG("hdev %p len %d", hdev, skb->len);
+
+ read_lock(&hci_sk_list.lock);
+ for (sk = hci_sk_list.head; sk; sk = sk->next) {
+ struct hci_filter *flt;
+ struct sk_buff *nskb;
+
+ if (sk->state != BT_BOUND || hci_pi(sk)->hdev != hdev)
+ continue;
+
+ /* Don't send frame to the socket it came from */
+ if (skb->sk == sk)
+ continue;
+
+ /* Apply filter */
+ flt = &hci_pi(sk)->filter;
+
+ if (!hci_test_bit((skb->pkt_type & HCI_FLT_TYPE_BITS), &flt->type_mask))
+ continue;
+
+ if (skb->pkt_type == HCI_EVENT_PKT) {
+ register int evt = (*(__u8 *)skb->data & HCI_FLT_EVENT_BITS);
+
+ if (!hci_test_bit(evt, &flt->event_mask))
+ continue;
+
+ if (flt->opcode && ((evt == EVT_CMD_COMPLETE &&
+ flt->opcode != *(__u16 *)(skb->data + 3)) ||
+ (evt == EVT_CMD_STATUS &&
+ flt->opcode != *(__u16 *)(skb->data + 4))))
+ continue;
+ }
+
+ if (!(nskb = skb_clone(skb, GFP_ATOMIC)))
+ continue;
+
+ /* Put type byte before the data */
+ memcpy(skb_push(nskb, 1), &nskb->pkt_type, 1);
+
+ if (sock_queue_rcv_skb(sk, nskb))
+ kfree_skb(nskb);
+ }
+ read_unlock(&hci_sk_list.lock);
+}
+
+static int hci_sock_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct hci_dev *hdev = hci_pi(sk)->hdev;
+
+ BT_DBG("sock %p sk %p", sock, sk);
+
+ if (!sk)
+ return 0;
+
+ bluez_sock_unlink(&hci_sk_list, sk);
+
+ if (hdev) {
+ atomic_dec(&hdev->promisc);
+ hci_dev_put(hdev);
+ }
+
+ sock_orphan(sk);
+
+ skb_queue_purge(&sk->receive_queue);
+ skb_queue_purge(&sk->write_queue);
+
+ sock_put(sk);
+
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+/* Ioctls that require bound socket */
+static inline int hci_sock_bound_ioctl(struct sock *sk, unsigned int cmd, unsigned long arg)
+{
+ struct hci_dev *hdev = hci_pi(sk)->hdev;
+
+ if (!hdev)
+ return -EBADFD;
+
+ switch (cmd) {
+ case HCISETRAW:
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+
+ if (arg)
+ set_bit(HCI_RAW, &hdev->flags);
+ else
+ clear_bit(HCI_RAW, &hdev->flags);
+
+ return 0;
+
+ case HCIGETCONNINFO:
+ return hci_get_conn_info(hdev, arg);
+
+ default:
+ if (hdev->ioctl)
+ return hdev->ioctl(hdev, cmd, arg);
+ return -EINVAL;
+ }
+}
+
+static int hci_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+ int err;
+
+ BT_DBG("cmd %x arg %lx", cmd, arg);
+
+ switch (cmd) {
+ case HCIGETDEVLIST:
+ return hci_get_dev_list(arg);
+
+ case HCIGETDEVINFO:
+ return hci_get_dev_info(arg);
+
+ case HCIGETCONNLIST:
+ return hci_get_conn_list(arg);
+
+ case HCIDEVUP:
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+ return hci_dev_open(arg);
+
+ case HCIDEVDOWN:
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+ return hci_dev_close(arg);
+
+ case HCIDEVRESET:
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+ return hci_dev_reset(arg);
+
+ case HCIDEVRESTAT:
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+ return hci_dev_reset_stat(arg);
+
+ case HCISETSCAN:
+ case HCISETAUTH:
+ case HCISETENCRYPT:
+ case HCISETPTYPE:
+ case HCISETLINKPOL:
+ case HCISETLINKMODE:
+ case HCISETACLMTU:
+ case HCISETSCOMTU:
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+ return hci_dev_cmd(cmd, arg);
+
+ case HCIINQUIRY:
+ return hci_inquiry(arg);
+
+ default:
+ lock_sock(sk);
+ err = hci_sock_bound_ioctl(sk, cmd, arg);
+ release_sock(sk);
+ return err;
+ };
+}
+
+static int hci_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
+{
+ struct sockaddr_hci *haddr = (struct sockaddr_hci *) addr;
+ struct sock *sk = sock->sk;
+ struct hci_dev *hdev = NULL;
+ int err = 0;
+
+ BT_DBG("sock %p sk %p", sock, sk);
+
+ if (!haddr || haddr->hci_family != AF_BLUETOOTH)
+ return -EINVAL;
+
+ lock_sock(sk);
+
+ if (hci_pi(sk)->hdev) {
+ err = -EALREADY;
+ goto done;
+ }
+
+ if (haddr->hci_dev != HCI_DEV_NONE) {
+ if (!(hdev = hci_dev_get(haddr->hci_dev))) {
+ err = -ENODEV;
+ goto done;
+ }
+
+ atomic_inc(&hdev->promisc);
+ }
+
+ hci_pi(sk)->hdev = hdev;
+ sk->state = BT_BOUND;
+
+done:
+ release_sock(sk);
+ return err;
+}
+
+static int hci_sock_getname(struct socket *sock, struct sockaddr *addr, int *addr_len, int peer)
+{
+ struct sockaddr_hci *haddr = (struct sockaddr_hci *) addr;
+ struct sock *sk = sock->sk;
+
+ BT_DBG("sock %p sk %p", sock, sk);
+
+ lock_sock(sk);
+
+ *addr_len = sizeof(*haddr);
+ haddr->hci_family = AF_BLUETOOTH;
+ haddr->hci_dev = hci_pi(sk)->hdev->id;
+
+ release_sock(sk);
+ return 0;
+}
+
+static inline void hci_sock_cmsg(struct sock *sk, struct msghdr *msg, struct sk_buff *skb)
+{
+ __u32 mask = hci_pi(sk)->cmsg_mask;
+
+ if (mask & HCI_CMSG_DIR)
+ put_cmsg(msg, SOL_HCI, HCI_CMSG_DIR, sizeof(int), &bluez_cb(skb)->incomming);
+
+ if (mask & HCI_CMSG_TSTAMP)
+ put_cmsg(msg, SOL_HCI, HCI_CMSG_TSTAMP, sizeof(skb->stamp), &skb->stamp);
+}
+
+static int hci_sock_recvmsg(struct socket *sock, struct msghdr *msg, int len, int flags, struct scm_cookie *scm)
+{
+ int noblock = flags & MSG_DONTWAIT;
+ struct sock *sk = sock->sk;
+ struct sk_buff *skb;
+ int copied, err;
+
+ BT_DBG("sock %p, sk %p", sock, sk);
+
+ if (flags & (MSG_OOB))
+ return -EOPNOTSUPP;
+
+ if (sk->state == BT_CLOSED)
+ return 0;
+
+ if (!(skb = skb_recv_datagram(sk, flags, noblock, &err)))
+ return err;
+
+ msg->msg_namelen = 0;
+
+ copied = skb->len;
+ if (len < copied) {
+ msg->msg_flags |= MSG_TRUNC;
+ copied = len;
+ }
+
+ skb->h.raw = skb->data;
+ err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+
+ hci_sock_cmsg(sk, msg, skb);
+
+ skb_free_datagram(sk, skb);
+
+ return err ? : copied;
+}
+
+static int hci_sock_sendmsg(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct hci_dev *hdev;
+ struct sk_buff *skb;
+ int err;
+
+ BT_DBG("sock %p sk %p", sock, sk);
+
+ if (msg->msg_flags & MSG_OOB)
+ return -EOPNOTSUPP;
+
+ if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_NOSIGNAL|MSG_ERRQUEUE))
+ return -EINVAL;
+
+ if (len < 4)
+ return -EINVAL;
+
+ lock_sock(sk);
+
+ if (!(hdev = hci_pi(sk)->hdev)) {
+ err = -EBADFD;
+ goto done;
+ }
+
+ if (!(skb = bluez_skb_send_alloc(sk, len, msg->msg_flags & MSG_DONTWAIT, &err)))
+ goto done;
+
+ if (memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)) {
+ err = -EFAULT;
+ goto drop;
+ }
+
+ skb->pkt_type = *((unsigned char *) skb->data);
+ skb_pull(skb, 1);
+ skb->dev = (void *) hdev;
+
+ if (skb->pkt_type == HCI_COMMAND_PKT) {
+ u16 opcode = __le16_to_cpu(get_unaligned((u16 *)skb->data));
+ u16 ogf = cmd_opcode_ogf(opcode);
+ u16 ocf = cmd_opcode_ocf(opcode);
+
+ if (((ogf > HCI_SFLT_MAX_OGF) ||
+ !hci_test_bit(ocf & HCI_FLT_OCF_BITS, &hci_sec_filter.ocf_mask[ogf])) &&
+ !capable(CAP_NET_RAW)) {
+ err = -EPERM;
+ goto drop;
+ }
+
+ if (test_bit(HCI_RAW, &hdev->flags) || (ogf == OGF_VENDOR_CMD)) {
+ skb_queue_tail(&hdev->raw_q, skb);
+ hci_sched_tx(hdev);
+ } else {
+ skb_queue_tail(&hdev->cmd_q, skb);
+ hci_sched_cmd(hdev);
+ }
+ } else {
+ if (!capable(CAP_NET_RAW)) {
+ err = -EPERM;
+ goto drop;
+ }
+
+ skb_queue_tail(&hdev->raw_q, skb);
+ hci_sched_tx(hdev);
+ }
+
+ err = len;
+
+done:
+ release_sock(sk);
+ return err;
+
+drop:
+ kfree_skb(skb);
+ goto done;
+}
+
+int hci_sock_setsockopt(struct socket *sock, int level, int optname, char *optval, int len)
+{
+ struct sock *sk = sock->sk;
+ struct hci_filter flt = { opcode: 0 };
+ int err = 0, opt = 0;
+
+ BT_DBG("sk %p, opt %d", sk, optname);
+
+ lock_sock(sk);
+
+ switch (optname) {
+ case HCI_DATA_DIR:
+ if (get_user(opt, (int *)optval)) {
+ err = -EFAULT;
+ break;
+ }
+
+ if (opt)
+ hci_pi(sk)->cmsg_mask |= HCI_CMSG_DIR;
+ else
+ hci_pi(sk)->cmsg_mask &= ~HCI_CMSG_DIR;
+ break;
+
+ case HCI_TIME_STAMP:
+ if (get_user(opt, (int *)optval)) {
+ err = -EFAULT;
+ break;
+ }
+
+ if (opt)
+ hci_pi(sk)->cmsg_mask |= HCI_CMSG_TSTAMP;
+ else
+ hci_pi(sk)->cmsg_mask &= ~HCI_CMSG_TSTAMP;
+ break;
+
+ case HCI_FILTER:
+ len = MIN(len, sizeof(struct hci_filter));
+ if (copy_from_user(&flt, optval, len)) {
+ err = -EFAULT;
+ break;
+ }
+
+ if (!capable(CAP_NET_RAW)) {
+ flt.type_mask &= hci_sec_filter.type_mask;
+ flt.event_mask[0] &= hci_sec_filter.event_mask[0];
+ flt.event_mask[1] &= hci_sec_filter.event_mask[1];
+ }
+
+ memcpy(&hci_pi(sk)->filter, &flt, len);
+ break;
+
+ default:
+ err = -ENOPROTOOPT;
+ break;
+ };
+
+ release_sock(sk);
+ return err;
+}
+
+int hci_sock_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen)
+{
+ struct sock *sk = sock->sk;
+ int len, opt;
+
+ if (get_user(len, optlen))
+ return -EFAULT;
+
+ switch (optname) {
+ case HCI_DATA_DIR:
+ if (hci_pi(sk)->cmsg_mask & HCI_CMSG_DIR)
+ opt = 1;
+ else
+ opt = 0;
+
+ if (put_user(opt, optval))
+ return -EFAULT;
+ break;
+
+ case HCI_TIME_STAMP:
+ if (hci_pi(sk)->cmsg_mask & HCI_CMSG_TSTAMP)
+ opt = 1;
+ else
+ opt = 0;
+
+ if (put_user(opt, optval))
+ return -EFAULT;
+ break;
+
+ case HCI_FILTER:
+ len = MIN(len, sizeof(struct hci_filter));
+ if (copy_to_user(optval, &hci_pi(sk)->filter, len))
+ return -EFAULT;
+ break;
+
+ default:
+ return -ENOPROTOOPT;
+ break;
+ };
+
+ return 0;
+}
+
+struct proto_ops hci_sock_ops = {
+ family: PF_BLUETOOTH,
+ release: hci_sock_release,
+ bind: hci_sock_bind,
+ getname: hci_sock_getname,
+ sendmsg: hci_sock_sendmsg,
+ recvmsg: hci_sock_recvmsg,
+ ioctl: hci_sock_ioctl,
+ poll: datagram_poll,
+ listen: sock_no_listen,
+ shutdown: sock_no_shutdown,
+ setsockopt: hci_sock_setsockopt,
+ getsockopt: hci_sock_getsockopt,
+ connect: sock_no_connect,
+ socketpair: sock_no_socketpair,
+ accept: sock_no_accept,
+ mmap: sock_no_mmap
+};
+
+static int hci_sock_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+
+ BT_DBG("sock %p", sock);
+
+ if (sock->type != SOCK_RAW)
+ return -ESOCKTNOSUPPORT;
+
+ sock->ops = &hci_sock_ops;
+
+ if (!(sk = sk_alloc(PF_BLUETOOTH, GFP_KERNEL, 1)))
+ return -ENOMEM;
+
+ sock->state = SS_UNCONNECTED;
+ sock_init_data(sock, sk);
+
+ memset(&sk->protinfo, 0, sizeof(struct hci_pinfo));
+ sk->destruct = NULL;
+ sk->protocol = protocol;
+ sk->state = BT_OPEN;
+
+ bluez_sock_link(&hci_sk_list, sk);
+
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int hci_sock_dev_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct hci_dev *hdev = (struct hci_dev *) ptr;
+ evt_si_device sd;
+
+ BT_DBG("hdev %s event %ld", hdev->name, event);
+
+ /* Send event to sockets */
+ sd.event = event;
+ sd.dev_id = hdev->id;
+ hci_si_event(NULL, EVT_SI_DEVICE, EVT_SI_DEVICE_SIZE, &sd);
+
+ if (event == HCI_DEV_UNREG) {
+ struct sock *sk;
+
+ /* Detach sockets from device */
+ read_lock(&hci_sk_list.lock);
+ for (sk = hci_sk_list.head; sk; sk = sk->next) {
+ bh_lock_sock(sk);
+ if (hci_pi(sk)->hdev == hdev) {
+ hci_pi(sk)->hdev = NULL;
+ sk->err = EPIPE;
+ sk->state = BT_OPEN;
+ sk->state_change(sk);
+
+ hci_dev_put(hdev);
+ }
+ bh_unlock_sock(sk);
+ }
+ read_unlock(&hci_sk_list.lock);
+ }
+
+ return NOTIFY_DONE;
+}
+
+struct net_proto_family hci_sock_family_ops = {
+ family: PF_BLUETOOTH,
+ create: hci_sock_create
+};
+
+struct notifier_block hci_sock_nblock = {
+ notifier_call: hci_sock_dev_event
+};
+
+int hci_sock_init(void)
+{
+ if (bluez_sock_register(BTPROTO_HCI, &hci_sock_family_ops)) {
+ BT_ERR("Can't register HCI socket");
+ return -EPROTO;
+ }
+
+ hci_register_notifier(&hci_sock_nblock);
+ return 0;
+}
+
+int hci_sock_cleanup(void)
+{
+ if (bluez_sock_unregister(BTPROTO_HCI))
+ BT_ERR("Can't unregister HCI socket");
+
+ hci_unregister_notifier(&hci_sock_nblock);
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/l2cap.c b/uClinux-2.4.31-uc0/net/bluetooth/l2cap.c
new file mode 100644
index 0000000..8a54327
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/l2cap.c
@@ -0,0 +1,2222 @@
+/*
+ BlueZ - Bluetooth protocol stack for Linux
+ Copyright (C) 2000-2001 Qualcomm Incorporated
+
+ Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * BlueZ L2CAP core and sockets.
+ *
+ * $Id: l2cap.c,v 1.15 2002/09/09 01:14:52 maxk Exp $
+ */
+#define VERSION "2.3"
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/interrupt.h>
+#include <linux/socket.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/list.h>
+#include <net/sock.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/unaligned.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+#include <net/bluetooth/l2cap.h>
+
+#ifndef L2CAP_DEBUG
+#undef BT_DBG
+#define BT_DBG( A... )
+#endif
+
+static struct proto_ops l2cap_sock_ops;
+
+struct bluez_sock_list l2cap_sk_list = {
+ lock: RW_LOCK_UNLOCKED
+};
+
+static int l2cap_conn_del(struct hci_conn *conn, int err);
+
+static void __l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct sock *parent);
+static void l2cap_chan_del(struct sock *sk, int err);
+static int l2cap_chan_send(struct sock *sk, struct msghdr *msg, int len);
+
+static void __l2cap_sock_close(struct sock *sk, int reason);
+static void l2cap_sock_close(struct sock *sk);
+static void l2cap_sock_kill(struct sock *sk);
+
+static int l2cap_send_req(struct l2cap_conn *conn, __u8 code, __u16 len, void *data);
+static int l2cap_send_rsp(struct l2cap_conn *conn, __u8 ident, __u8 code, __u16 len, void *data);
+
+/* ----- L2CAP timers ------ */
+static void l2cap_sock_timeout(unsigned long arg)
+{
+ struct sock *sk = (struct sock *) arg;
+
+ BT_DBG("sock %p state %d", sk, sk->state);
+
+ bh_lock_sock(sk);
+ __l2cap_sock_close(sk, ETIMEDOUT);
+ bh_unlock_sock(sk);
+
+ l2cap_sock_kill(sk);
+ sock_put(sk);
+}
+
+static void l2cap_sock_set_timer(struct sock *sk, long timeout)
+{
+ BT_DBG("sk %p state %d timeout %ld", sk, sk->state, timeout);
+
+ if (!mod_timer(&sk->timer, jiffies + timeout))
+ sock_hold(sk);
+}
+
+static void l2cap_sock_clear_timer(struct sock *sk)
+{
+ BT_DBG("sock %p state %d", sk, sk->state);
+
+ if (timer_pending(&sk->timer) && del_timer(&sk->timer))
+ __sock_put(sk);
+}
+
+static void l2cap_sock_init_timer(struct sock *sk)
+{
+ init_timer(&sk->timer);
+ sk->timer.function = l2cap_sock_timeout;
+ sk->timer.data = (unsigned long)sk;
+}
+
+/* -------- L2CAP connections --------- */
+static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon, __u8 status)
+{
+ struct l2cap_conn *conn;
+
+ if ((conn = hcon->l2cap_data))
+ return conn;
+
+ if (status)
+ return conn;
+
+ if (!(conn = kmalloc(sizeof(struct l2cap_conn), GFP_ATOMIC)))
+ return NULL;
+ memset(conn, 0, sizeof(struct l2cap_conn));
+
+ hcon->l2cap_data = conn;
+ conn->hcon = hcon;
+
+ conn->mtu = hcon->hdev->acl_mtu;
+ conn->src = &hcon->hdev->bdaddr;
+ conn->dst = &hcon->dst;
+
+ spin_lock_init(&conn->lock);
+ conn->chan_list.lock = RW_LOCK_UNLOCKED;
+
+ BT_DBG("hcon %p conn %p", hcon, conn);
+
+ MOD_INC_USE_COUNT;
+ return conn;
+}
+
+static int l2cap_conn_del(struct hci_conn *hcon, int err)
+{
+ struct l2cap_conn *conn;
+ struct sock *sk;
+
+ if (!(conn = hcon->l2cap_data))
+ return 0;
+
+ BT_DBG("hcon %p conn %p, err %d", hcon, conn, err);
+
+ if (conn->rx_skb)
+ kfree_skb(conn->rx_skb);
+
+ /* Kill channels */
+ while ((sk = conn->chan_list.head)) {
+ bh_lock_sock(sk);
+ l2cap_chan_del(sk, err);
+ bh_unlock_sock(sk);
+ l2cap_sock_kill(sk);
+ }
+
+ hcon->l2cap_data = NULL;
+ kfree(conn);
+
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+/* -------- Socket interface ---------- */
+static struct sock *__l2cap_get_sock_by_addr(__u16 psm, bdaddr_t *src)
+{
+ struct sock *sk;
+ for (sk = l2cap_sk_list.head; sk; sk = sk->next) {
+ if (sk->sport == psm && !bacmp(&bluez_pi(sk)->src, src))
+ break;
+ }
+ return sk;
+}
+
+/* Find socket with psm and source bdaddr.
+ * Returns closest match.
+ */
+static struct sock *__l2cap_get_sock_by_psm(int state, __u16 psm, bdaddr_t *src)
+{
+ struct sock *sk, *sk1 = NULL;
+
+ for (sk = l2cap_sk_list.head; sk; sk = sk->next) {
+ if (state && sk->state != state)
+ continue;
+
+ if (l2cap_pi(sk)->psm == psm) {
+ /* Exact match. */
+ if (!bacmp(&bluez_pi(sk)->src, src))
+ break;
+
+ /* Closest match */
+ if (!bacmp(&bluez_pi(sk)->src, BDADDR_ANY))
+ sk1 = sk;
+ }
+ }
+ return sk ? sk : sk1;
+}
+
+/* Find socket with given address (psm, src).
+ * Returns locked socket */
+static inline struct sock *l2cap_get_sock_by_psm(int state, __u16 psm, bdaddr_t *src)
+{
+ struct sock *s;
+ read_lock(&l2cap_sk_list.lock);
+ s = __l2cap_get_sock_by_psm(state, psm, src);
+ if (s) bh_lock_sock(s);
+ read_unlock(&l2cap_sk_list.lock);
+ return s;
+}
+
+static void l2cap_sock_destruct(struct sock *sk)
+{
+ BT_DBG("sk %p", sk);
+
+ skb_queue_purge(&sk->receive_queue);
+ skb_queue_purge(&sk->write_queue);
+
+ MOD_DEC_USE_COUNT;
+}
+
+static void l2cap_sock_cleanup_listen(struct sock *parent)
+{
+ struct sock *sk;
+
+ BT_DBG("parent %p", parent);
+
+ /* Close not yet accepted channels */
+ while ((sk = bluez_accept_dequeue(parent, NULL)))
+ l2cap_sock_close(sk);
+
+ parent->state = BT_CLOSED;
+ parent->zapped = 1;
+}
+
+/* Kill socket (only if zapped and orphan)
+ * Must be called on unlocked socket.
+ */
+static void l2cap_sock_kill(struct sock *sk)
+{
+ if (!sk->zapped || sk->socket)
+ return;
+
+ BT_DBG("sk %p state %d", sk, sk->state);
+
+ /* Kill poor orphan */
+ bluez_sock_unlink(&l2cap_sk_list, sk);
+ sk->dead = 1;
+ sock_put(sk);
+}
+
+/* Close socket.
+ */
+static void __l2cap_sock_close(struct sock *sk, int reason)
+{
+ BT_DBG("sk %p state %d socket %p", sk, sk->state, sk->socket);
+
+ switch (sk->state) {
+ case BT_LISTEN:
+ l2cap_sock_cleanup_listen(sk);
+ break;
+
+ case BT_CONNECTED:
+ case BT_CONFIG:
+ case BT_CONNECT2:
+ if (sk->type == SOCK_SEQPACKET) {
+ struct l2cap_conn *conn = l2cap_pi(sk)->conn;
+ l2cap_disconn_req req;
+
+ sk->state = BT_DISCONN;
+ l2cap_sock_set_timer(sk, sk->sndtimeo);
+
+ req.dcid = __cpu_to_le16(l2cap_pi(sk)->dcid);
+ req.scid = __cpu_to_le16(l2cap_pi(sk)->scid);
+ l2cap_send_req(conn, L2CAP_DISCONN_REQ, L2CAP_DISCONN_REQ_SIZE, &req);
+ } else {
+ l2cap_chan_del(sk, reason);
+ }
+ break;
+
+ case BT_CONNECT:
+ case BT_DISCONN:
+ l2cap_chan_del(sk, reason);
+ break;
+
+ default:
+ sk->zapped = 1;
+ break;
+ };
+}
+
+/* Must be called on unlocked socket. */
+static void l2cap_sock_close(struct sock *sk)
+{
+ l2cap_sock_clear_timer(sk);
+ lock_sock(sk);
+ __l2cap_sock_close(sk, ECONNRESET);
+ release_sock(sk);
+ l2cap_sock_kill(sk);
+}
+
+static void l2cap_sock_init(struct sock *sk, struct sock *parent)
+{
+ struct l2cap_pinfo *pi = l2cap_pi(sk);
+
+ BT_DBG("sk %p", sk);
+
+ if (parent) {
+ sk->type = parent->type;
+ pi->imtu = l2cap_pi(parent)->imtu;
+ pi->omtu = l2cap_pi(parent)->omtu;
+ pi->link_mode = l2cap_pi(parent)->link_mode;
+ } else {
+ pi->imtu = L2CAP_DEFAULT_MTU;
+ pi->omtu = 0;
+ pi->link_mode = 0;
+ }
+
+ /* Default config options */
+ pi->conf_mtu = L2CAP_DEFAULT_MTU;
+ pi->flush_to = L2CAP_DEFAULT_FLUSH_TO;
+}
+
+static struct sock *l2cap_sock_alloc(struct socket *sock, int proto, int prio)
+{
+ struct sock *sk;
+
+ if (!(sk = sk_alloc(PF_BLUETOOTH, prio, 1)))
+ return NULL;
+
+ bluez_sock_init(sock, sk);
+
+ sk->zapped = 0;
+
+ sk->destruct = l2cap_sock_destruct;
+ sk->sndtimeo = L2CAP_CONN_TIMEOUT;
+
+ sk->protocol = proto;
+ sk->state = BT_OPEN;
+
+ l2cap_sock_init_timer(sk);
+
+ bluez_sock_link(&l2cap_sk_list, sk);
+
+ MOD_INC_USE_COUNT;
+ return sk;
+}
+
+static int l2cap_sock_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+
+ BT_DBG("sock %p", sock);
+
+ sock->state = SS_UNCONNECTED;
+
+ if (sock->type != SOCK_SEQPACKET && sock->type != SOCK_DGRAM && sock->type != SOCK_RAW)
+ return -ESOCKTNOSUPPORT;
+
+ if (sock->type == SOCK_RAW && !capable(CAP_NET_RAW))
+ return -EPERM;
+
+ sock->ops = &l2cap_sock_ops;
+
+ if (!(sk = l2cap_sock_alloc(sock, protocol, GFP_KERNEL)))
+ return -ENOMEM;
+
+ l2cap_sock_init(sk, NULL);
+ return 0;
+}
+
+static int l2cap_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
+{
+ struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr;
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ BT_DBG("sk %p, %s %d", sk, batostr(&la->l2_bdaddr), la->l2_psm);
+
+ if (!addr || addr->sa_family != AF_BLUETOOTH)
+ return -EINVAL;
+
+ lock_sock(sk);
+
+ if (sk->state != BT_OPEN) {
+ err = -EBADFD;
+ goto done;
+ }
+
+ write_lock_bh(&l2cap_sk_list.lock);
+ if (la->l2_psm && __l2cap_get_sock_by_addr(la->l2_psm, &la->l2_bdaddr)) {
+ err = -EADDRINUSE;
+ } else {
+ /* Save source address */
+ bacpy(&bluez_pi(sk)->src, &la->l2_bdaddr);
+ l2cap_pi(sk)->psm = la->l2_psm;
+ sk->sport = la->l2_psm;
+ sk->state = BT_BOUND;
+ }
+ write_unlock_bh(&l2cap_sk_list.lock);
+
+done:
+ release_sock(sk);
+ return err;
+}
+
+static inline void l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct sock *parent)
+{
+ struct l2cap_chan_list *l = &conn->chan_list;
+ write_lock(&l->lock);
+ __l2cap_chan_add(conn, sk, parent);
+ write_unlock(&l->lock);
+}
+
+static int l2cap_do_connect(struct sock *sk)
+{
+ bdaddr_t *src = &bluez_pi(sk)->src;
+ bdaddr_t *dst = &bluez_pi(sk)->dst;
+ struct l2cap_conn *conn;
+ struct hci_conn *hcon;
+ struct hci_dev *hdev;
+ int err = 0;
+
+ BT_DBG("%s -> %s psm 0x%2.2x", batostr(src), batostr(dst), l2cap_pi(sk)->psm);
+
+ if (!(hdev = hci_get_route(dst, src)))
+ return -EHOSTUNREACH;
+
+ hci_dev_lock_bh(hdev);
+
+ err = -ENOMEM;
+
+ hcon = hci_connect(hdev, ACL_LINK, dst);
+ if (!hcon)
+ goto done;
+
+ conn = l2cap_conn_add(hcon, 0);
+ if (!conn) {
+ hci_conn_put(hcon);
+ goto done;
+ }
+
+ err = 0;
+
+ /* Update source addr of the socket */
+ bacpy(src, conn->src);
+
+ l2cap_chan_add(conn, sk, NULL);
+
+ sk->state = BT_CONNECT;
+ l2cap_sock_set_timer(sk, sk->sndtimeo);
+
+ if (hcon->state == BT_CONNECTED) {
+ if (sk->type == SOCK_SEQPACKET) {
+ l2cap_conn_req req;
+ req.scid = __cpu_to_le16(l2cap_pi(sk)->scid);
+ req.psm = l2cap_pi(sk)->psm;
+ l2cap_send_req(conn, L2CAP_CONN_REQ, L2CAP_CONN_REQ_SIZE, &req);
+ } else {
+ l2cap_sock_clear_timer(sk);
+ sk->state = BT_CONNECTED;
+ }
+ }
+
+done:
+ hci_dev_unlock_bh(hdev);
+ hci_dev_put(hdev);
+ return err;
+}
+
+static int l2cap_sock_connect(struct socket *sock, struct sockaddr *addr, int alen, int flags)
+{
+ struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr;
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ lock_sock(sk);
+
+ BT_DBG("sk %p", sk);
+
+ if (addr->sa_family != AF_BLUETOOTH || alen < sizeof(struct sockaddr_l2)) {
+ err = -EINVAL;
+ goto done;
+ }
+
+ if (sk->type == SOCK_SEQPACKET && !la->l2_psm) {
+ err = -EINVAL;
+ goto done;
+ }
+
+ switch(sk->state) {
+ case BT_CONNECT:
+ case BT_CONNECT2:
+ case BT_CONFIG:
+ /* Already connecting */
+ goto wait;
+
+ case BT_CONNECTED:
+ /* Already connected */
+ goto done;
+
+ case BT_OPEN:
+ case BT_BOUND:
+ /* Can connect */
+ break;
+
+ default:
+ err = -EBADFD;
+ goto done;
+ }
+
+ /* Set destination address and psm */
+ bacpy(&bluez_pi(sk)->dst, &la->l2_bdaddr);
+ l2cap_pi(sk)->psm = la->l2_psm;
+
+ if ((err = l2cap_do_connect(sk)))
+ goto done;
+
+wait:
+ err = bluez_sock_wait_state(sk, BT_CONNECTED,
+ sock_sndtimeo(sk, flags & O_NONBLOCK));
+
+done:
+ release_sock(sk);
+ return err;
+}
+
+int l2cap_sock_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ BT_DBG("sk %p backlog %d", sk, backlog);
+
+ lock_sock(sk);
+
+ if (sk->state != BT_BOUND || sock->type != SOCK_SEQPACKET) {
+ err = -EBADFD;
+ goto done;
+ }
+
+ if (!l2cap_pi(sk)->psm) {
+ err = -EINVAL;
+ goto done;
+ }
+
+ sk->max_ack_backlog = backlog;
+ sk->ack_backlog = 0;
+ sk->state = BT_LISTEN;
+
+done:
+ release_sock(sk);
+ return err;
+}
+
+int l2cap_sock_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct sock *sk = sock->sk, *nsk;
+ long timeo;
+ int err = 0;
+
+ lock_sock(sk);
+
+ if (sk->state != BT_LISTEN) {
+ err = -EBADFD;
+ goto done;
+ }
+
+ timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
+
+ BT_DBG("sk %p timeo %ld", sk, timeo);
+
+ /* Wait for an incoming connection. (wake-one). */
+ add_wait_queue_exclusive(sk->sleep, &wait);
+ while (!(nsk = bluez_accept_dequeue(sk, newsock))) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (!timeo) {
+ err = -EAGAIN;
+ break;
+ }
+
+ release_sock(sk);
+ timeo = schedule_timeout(timeo);
+ lock_sock(sk);
+
+ if (sk->state != BT_LISTEN) {
+ err = -EBADFD;
+ break;
+ }
+
+ if (signal_pending(current)) {
+ err = sock_intr_errno(timeo);
+ break;
+ }
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(sk->sleep, &wait);
+
+ if (err)
+ goto done;
+
+ newsock->state = SS_CONNECTED;
+
+ BT_DBG("new socket %p", nsk);
+
+done:
+ release_sock(sk);
+ return err;
+}
+
+static int l2cap_sock_getname(struct socket *sock, struct sockaddr *addr, int *len, int peer)
+{
+ struct sockaddr_l2 *la = (struct sockaddr_l2 *) addr;
+ struct sock *sk = sock->sk;
+
+ BT_DBG("sock %p, sk %p", sock, sk);
+
+ addr->sa_family = AF_BLUETOOTH;
+ *len = sizeof(struct sockaddr_l2);
+
+ if (peer)
+ bacpy(&la->l2_bdaddr, &bluez_pi(sk)->dst);
+ else
+ bacpy(&la->l2_bdaddr, &bluez_pi(sk)->src);
+
+ la->l2_psm = l2cap_pi(sk)->psm;
+ return 0;
+}
+
+static int l2cap_sock_sendmsg(struct socket *sock, struct msghdr *msg, int len, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ BT_DBG("sock %p, sk %p", sock, sk);
+
+ if (sk->err)
+ return sock_error(sk);
+
+ if (msg->msg_flags & MSG_OOB)
+ return -EOPNOTSUPP;
+
+ /* Check outgoing MTU */
+ if (len > l2cap_pi(sk)->omtu)
+ return -EINVAL;
+
+ lock_sock(sk);
+
+ if (sk->state == BT_CONNECTED)
+ err = l2cap_chan_send(sk, msg, len);
+ else
+ err = -ENOTCONN;
+
+ release_sock(sk);
+ return err;
+}
+
+static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen)
+{
+ struct sock *sk = sock->sk;
+ struct l2cap_options opts;
+ int err = 0, len;
+ __u32 opt;
+
+ BT_DBG("sk %p", sk);
+
+ lock_sock(sk);
+
+ switch (optname) {
+ case L2CAP_OPTIONS:
+ len = MIN(sizeof(opts), optlen);
+ if (copy_from_user((char *)&opts, optval, len)) {
+ err = -EFAULT;
+ break;
+ }
+ l2cap_pi(sk)->imtu = opts.imtu;
+ l2cap_pi(sk)->omtu = opts.omtu;
+ break;
+
+ case L2CAP_LM:
+ if (get_user(opt, (__u32 *)optval)) {
+ err = -EFAULT;
+ break;
+ }
+
+ l2cap_pi(sk)->link_mode = opt;
+ break;
+
+ default:
+ err = -ENOPROTOOPT;
+ break;
+ }
+
+ release_sock(sk);
+ return err;
+}
+
+static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen)
+{
+ struct sock *sk = sock->sk;
+ struct l2cap_options opts;
+ struct l2cap_conninfo cinfo;
+ int len, err = 0;
+
+ if (get_user(len, optlen))
+ return -EFAULT;
+
+ lock_sock(sk);
+
+ switch (optname) {
+ case L2CAP_OPTIONS:
+ opts.imtu = l2cap_pi(sk)->imtu;
+ opts.omtu = l2cap_pi(sk)->omtu;
+ opts.flush_to = l2cap_pi(sk)->flush_to;
+
+ len = MIN(len, sizeof(opts));
+ if (copy_to_user(optval, (char *)&opts, len))
+ err = -EFAULT;
+
+ break;
+
+ case L2CAP_LM:
+ if (put_user(l2cap_pi(sk)->link_mode, (__u32 *)optval))
+ err = -EFAULT;
+ break;
+
+ case L2CAP_CONNINFO:
+ if (sk->state != BT_CONNECTED) {
+ err = -ENOTCONN;
+ break;
+ }
+
+ cinfo.hci_handle = l2cap_pi(sk)->conn->hcon->handle;
+
+ len = MIN(len, sizeof(cinfo));
+ if (copy_to_user(optval, (char *)&cinfo, len))
+ err = -EFAULT;
+
+ break;
+
+ default:
+ err = -ENOPROTOOPT;
+ break;
+ }
+
+ release_sock(sk);
+ return err;
+}
+
+static int l2cap_sock_shutdown(struct socket *sock, int how)
+{
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ BT_DBG("sock %p, sk %p", sock, sk);
+
+ if (!sk) return 0;
+
+ lock_sock(sk);
+ if (!sk->shutdown) {
+ sk->shutdown = SHUTDOWN_MASK;
+ l2cap_sock_clear_timer(sk);
+ __l2cap_sock_close(sk, 0);
+
+ if (sk->linger)
+ err = bluez_sock_wait_state(sk, BT_CLOSED, sk->lingertime);
+ }
+ release_sock(sk);
+ return err;
+}
+
+static int l2cap_sock_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ int err;
+
+ BT_DBG("sock %p, sk %p", sock, sk);
+
+ if (!sk) return 0;
+
+ err = l2cap_sock_shutdown(sock, 2);
+
+ sock_orphan(sk);
+ l2cap_sock_kill(sk);
+ return err;
+}
+
+/* --------- L2CAP channels --------- */
+static struct sock * __l2cap_get_chan_by_dcid(struct l2cap_chan_list *l, __u16 cid)
+{
+ struct sock *s;
+ for (s = l->head; s; s = l2cap_pi(s)->next_c) {
+ if (l2cap_pi(s)->dcid == cid)
+ break;
+ }
+ return s;
+}
+
+static struct sock *__l2cap_get_chan_by_scid(struct l2cap_chan_list *l, __u16 cid)
+{
+ struct sock *s;
+ for (s = l->head; s; s = l2cap_pi(s)->next_c) {
+ if (l2cap_pi(s)->scid == cid)
+ break;
+ }
+ return s;
+}
+
+/* Find channel with given SCID.
+ * Returns locked socket */
+static inline struct sock *l2cap_get_chan_by_scid(struct l2cap_chan_list *l, __u16 cid)
+{
+ struct sock *s;
+ read_lock(&l->lock);
+ s = __l2cap_get_chan_by_scid(l, cid);
+ if (s) bh_lock_sock(s);
+ read_unlock(&l->lock);
+ return s;
+}
+
+static __u16 l2cap_alloc_cid(struct l2cap_chan_list *l)
+{
+ __u16 cid = 0x0040;
+
+ for (; cid < 0xffff; cid++) {
+ if(!__l2cap_get_chan_by_scid(l, cid))
+ return cid;
+ }
+
+ return 0;
+}
+
+static inline void __l2cap_chan_link(struct l2cap_chan_list *l, struct sock *sk)
+{
+ sock_hold(sk);
+
+ if (l->head)
+ l2cap_pi(l->head)->prev_c = sk;
+
+ l2cap_pi(sk)->next_c = l->head;
+ l2cap_pi(sk)->prev_c = NULL;
+ l->head = sk;
+}
+
+static inline void l2cap_chan_unlink(struct l2cap_chan_list *l, struct sock *sk)
+{
+ struct sock *next = l2cap_pi(sk)->next_c, *prev = l2cap_pi(sk)->prev_c;
+
+ write_lock(&l->lock);
+ if (sk == l->head)
+ l->head = next;
+
+ if (next)
+ l2cap_pi(next)->prev_c = prev;
+ if (prev)
+ l2cap_pi(prev)->next_c = next;
+ write_unlock(&l->lock);
+
+ __sock_put(sk);
+}
+
+static void __l2cap_chan_add(struct l2cap_conn *conn, struct sock *sk, struct sock *parent)
+{
+ struct l2cap_chan_list *l = &conn->chan_list;
+
+ BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn, l2cap_pi(sk)->psm, l2cap_pi(sk)->dcid);
+
+ l2cap_pi(sk)->conn = conn;
+
+ if (sk->type == SOCK_SEQPACKET) {
+ /* Alloc CID for connection-oriented socket */
+ l2cap_pi(sk)->scid = l2cap_alloc_cid(l);
+ } else if (sk->type == SOCK_DGRAM) {
+ /* Connectionless socket */
+ l2cap_pi(sk)->scid = 0x0002;
+ l2cap_pi(sk)->dcid = 0x0002;
+ l2cap_pi(sk)->omtu = L2CAP_DEFAULT_MTU;
+ } else {
+ /* Raw socket can send/recv signalling messages only */
+ l2cap_pi(sk)->scid = 0x0001;
+ l2cap_pi(sk)->dcid = 0x0001;
+ l2cap_pi(sk)->omtu = L2CAP_DEFAULT_MTU;
+ }
+
+ __l2cap_chan_link(l, sk);
+
+ if (parent)
+ bluez_accept_enqueue(parent, sk);
+}
+
+/* Delete channel.
+ * Must be called on the locked socket. */
+static void l2cap_chan_del(struct sock *sk, int err)
+{
+ struct l2cap_conn *conn = l2cap_pi(sk)->conn;
+ struct sock *parent = bluez_pi(sk)->parent;
+
+ l2cap_sock_clear_timer(sk);
+
+ BT_DBG("sk %p, conn %p, err %d", sk, conn, err);
+
+ if (conn) {
+ /* Unlink from channel list */
+ l2cap_chan_unlink(&conn->chan_list, sk);
+ l2cap_pi(sk)->conn = NULL;
+ hci_conn_put(conn->hcon);
+ }
+
+ sk->state = BT_CLOSED;
+ sk->zapped = 1;
+
+ if (err)
+ sk->err = err;
+
+ if (parent)
+ parent->data_ready(parent, 0);
+ else
+ sk->state_change(sk);
+}
+
+static void l2cap_conn_ready(struct l2cap_conn *conn)
+{
+ struct l2cap_chan_list *l = &conn->chan_list;
+ struct sock *sk;
+
+ BT_DBG("conn %p", conn);
+
+ read_lock(&l->lock);
+
+ for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) {
+ bh_lock_sock(sk);
+
+ if (sk->type != SOCK_SEQPACKET) {
+ l2cap_sock_clear_timer(sk);
+ sk->state = BT_CONNECTED;
+ sk->state_change(sk);
+ } else if (sk->state == BT_CONNECT) {
+ l2cap_conn_req req;
+ req.scid = __cpu_to_le16(l2cap_pi(sk)->scid);
+ req.psm = l2cap_pi(sk)->psm;
+ l2cap_send_req(conn, L2CAP_CONN_REQ, L2CAP_CONN_REQ_SIZE, &req);
+ }
+
+ bh_unlock_sock(sk);
+ }
+
+ read_unlock(&l->lock);
+}
+
+/* Notify sockets that we cannot guaranty reliability anymore */
+static void l2cap_conn_unreliable(struct l2cap_conn *conn, int err)
+{
+ struct l2cap_chan_list *l = &conn->chan_list;
+ struct sock *sk;
+
+ BT_DBG("conn %p", conn);
+
+ read_lock(&l->lock);
+ for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) {
+ if (l2cap_pi(sk)->link_mode & L2CAP_LM_RELIABLE)
+ sk->err = err;
+ }
+ read_unlock(&l->lock);
+}
+
+static void l2cap_chan_ready(struct sock *sk)
+{
+ struct sock *parent = bluez_pi(sk)->parent;
+
+ BT_DBG("sk %p, parent %p", sk, parent);
+
+ l2cap_pi(sk)->conf_state = 0;
+ l2cap_sock_clear_timer(sk);
+
+ if (!parent) {
+ /* Outgoing channel.
+ * Wake up socket sleeping on connect.
+ */
+ sk->state = BT_CONNECTED;
+ sk->state_change(sk);
+ } else {
+ /* Incomming channel.
+ * Wake up socket sleeping on accept.
+ */
+ parent->data_ready(parent, 0);
+ }
+}
+
+/* Copy frame to all raw sockets on that connection */
+void l2cap_raw_recv(struct l2cap_conn *conn, struct sk_buff *skb)
+{
+ struct l2cap_chan_list *l = &conn->chan_list;
+ struct sk_buff *nskb;
+ struct sock * sk;
+
+ BT_DBG("conn %p", conn);
+
+ read_lock(&l->lock);
+ for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) {
+ if (sk->type != SOCK_RAW)
+ continue;
+
+ /* Don't send frame to the socket it came from */
+ if (skb->sk == sk)
+ continue;
+
+ if (!(nskb = skb_clone(skb, GFP_ATOMIC)))
+ continue;
+
+ if (sock_queue_rcv_skb(sk, nskb))
+ kfree_skb(nskb);
+ }
+ read_unlock(&l->lock);
+}
+
+static int l2cap_chan_send(struct sock *sk, struct msghdr *msg, int len)
+{
+ struct l2cap_conn *conn = l2cap_pi(sk)->conn;
+ struct sk_buff *skb, **frag;
+ int err, hlen, count, sent=0;
+ l2cap_hdr *lh;
+
+ BT_DBG("sk %p len %d", sk, len);
+
+ /* First fragment (with L2CAP header) */
+ if (sk->type == SOCK_DGRAM)
+ hlen = L2CAP_HDR_SIZE + 2;
+ else
+ hlen = L2CAP_HDR_SIZE;
+
+ count = MIN(conn->mtu - hlen, len);
+
+ skb = bluez_skb_send_alloc(sk, hlen + count,
+ msg->msg_flags & MSG_DONTWAIT, &err);
+ if (!skb)
+ return err;
+
+ /* Create L2CAP header */
+ lh = (l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE);
+ lh->cid = __cpu_to_le16(l2cap_pi(sk)->dcid);
+ lh->len = __cpu_to_le16(len + (hlen - L2CAP_HDR_SIZE));
+
+ if (sk->type == SOCK_DGRAM)
+ put_unaligned(l2cap_pi(sk)->psm, (__u16 *) skb_put(skb, 2));
+
+ if (memcpy_fromiovec(skb_put(skb, count), msg->msg_iov, count)) {
+ err = -EFAULT;
+ goto fail;
+ }
+
+ sent += count;
+ len -= count;
+
+ /* Continuation fragments (no L2CAP header) */
+ frag = &skb_shinfo(skb)->frag_list;
+ while (len) {
+ count = MIN(conn->mtu, len);
+
+ *frag = bluez_skb_send_alloc(sk, count, msg->msg_flags & MSG_DONTWAIT, &err);
+ if (!*frag)
+ goto fail;
+
+ if (memcpy_fromiovec(skb_put(*frag, count), msg->msg_iov, count)) {
+ err = -EFAULT;
+ goto fail;
+ }
+
+ sent += count;
+ len -= count;
+
+ frag = &(*frag)->next;
+ }
+
+ if ((err = hci_send_acl(conn->hcon, skb, 0)) < 0)
+ goto fail;
+
+ return sent;
+
+fail:
+ kfree_skb(skb);
+ return err;
+}
+
+/* --------- L2CAP signalling commands --------- */
+static inline __u8 l2cap_get_ident(struct l2cap_conn *conn)
+{
+ __u8 id;
+
+ /* Get next available identificator.
+ * 1 - 199 are used by kernel.
+ * 200 - 254 are used by utilities like l2ping, etc
+ */
+
+ spin_lock(&conn->lock);
+
+ if (++conn->tx_ident > 199)
+ conn->tx_ident = 1;
+
+ id = conn->tx_ident;
+
+ spin_unlock(&conn->lock);
+
+ return id;
+}
+
+static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn,
+ __u8 code, __u8 ident, __u16 dlen, void *data)
+{
+ struct sk_buff *skb, **frag;
+ l2cap_cmd_hdr *cmd;
+ l2cap_hdr *lh;
+ int len, count;
+
+ BT_DBG("conn %p, code 0x%2.2x, ident 0x%2.2x, len %d", conn, code, ident, dlen);
+
+ len = L2CAP_HDR_SIZE + L2CAP_CMD_HDR_SIZE + dlen;
+ count = MIN(conn->mtu, len);
+
+ skb = bluez_skb_alloc(count, GFP_ATOMIC);
+ if (!skb)
+ return NULL;
+
+ lh = (l2cap_hdr *) skb_put(skb, L2CAP_HDR_SIZE);
+ lh->len = __cpu_to_le16(L2CAP_CMD_HDR_SIZE + dlen);
+ lh->cid = __cpu_to_le16(0x0001);
+
+ cmd = (l2cap_cmd_hdr *) skb_put(skb, L2CAP_CMD_HDR_SIZE);
+ cmd->code = code;
+ cmd->ident = ident;
+ cmd->len = __cpu_to_le16(dlen);
+
+ if (dlen) {
+ count -= L2CAP_HDR_SIZE + L2CAP_CMD_HDR_SIZE;
+ memcpy(skb_put(skb, count), data, count);
+ data += count;
+ }
+
+ len -= skb->len;
+
+ /* Continuation fragments (no L2CAP header) */
+ frag = &skb_shinfo(skb)->frag_list;
+ while (len) {
+ count = MIN(conn->mtu, len);
+
+ *frag = bluez_skb_alloc(count, GFP_ATOMIC);
+ if (!*frag)
+ goto fail;
+
+ memcpy(skb_put(*frag, count), data, count);
+
+ len -= count;
+ data += count;
+
+ frag = &(*frag)->next;
+ }
+
+ return skb;
+
+fail:
+ kfree_skb(skb);
+ return NULL;
+}
+
+static int l2cap_send_req(struct l2cap_conn *conn, __u8 code, __u16 len, void *data)
+{
+ __u8 ident = l2cap_get_ident(conn);
+ struct sk_buff *skb = l2cap_build_cmd(conn, code, ident, len, data);
+
+ BT_DBG("code 0x%2.2x", code);
+
+ if (!skb)
+ return -ENOMEM;
+ return hci_send_acl(conn->hcon, skb, 0);
+}
+
+static int l2cap_send_rsp(struct l2cap_conn *conn, __u8 ident, __u8 code, __u16 len, void *data)
+{
+ struct sk_buff *skb = l2cap_build_cmd(conn, code, ident, len, data);
+
+ BT_DBG("code 0x%2.2x", code);
+
+ if (!skb)
+ return -ENOMEM;
+ return hci_send_acl(conn->hcon, skb, 0);
+}
+
+static inline int l2cap_get_conf_opt(void **ptr, int *type, int *olen, unsigned long *val)
+{
+ l2cap_conf_opt *opt = *ptr;
+ int len;
+
+ len = L2CAP_CONF_OPT_SIZE + opt->len;
+ *ptr += len;
+
+ *type = opt->type;
+ *olen = opt->len;
+
+ switch (opt->len) {
+ case 1:
+ *val = *((__u8 *) opt->val);
+ break;
+
+ case 2:
+ *val = __le16_to_cpu(*((__u16 *)opt->val));
+ break;
+
+ case 4:
+ *val = __le32_to_cpu(*((__u32 *)opt->val));
+ break;
+
+ default:
+ *val = (unsigned long) opt->val;
+ break;
+ };
+
+ BT_DBG("type 0x%2.2x len %d val 0x%lx", *type, opt->len, *val);
+ return len;
+}
+
+static inline void l2cap_parse_conf_req(struct sock *sk, void *data, int len)
+{
+ int type, hint, olen;
+ unsigned long val;
+ void *ptr = data;
+
+ BT_DBG("sk %p len %d", sk, len);
+
+ while (len >= L2CAP_CONF_OPT_SIZE) {
+ len -= l2cap_get_conf_opt(&ptr, &type, &olen, &val);
+
+ hint = type & 0x80;
+ type &= 0x7f;
+
+ switch (type) {
+ case L2CAP_CONF_MTU:
+ l2cap_pi(sk)->conf_mtu = val;
+ break;
+
+ case L2CAP_CONF_FLUSH_TO:
+ l2cap_pi(sk)->flush_to = val;
+ break;
+
+ case L2CAP_CONF_QOS:
+ break;
+
+ default:
+ if (hint)
+ break;
+
+ /* FIXME: Reject unknown option */
+ break;
+ };
+ }
+}
+
+static void l2cap_add_conf_opt(void **ptr, __u8 type, __u8 len, unsigned long val)
+{
+ register l2cap_conf_opt *opt = *ptr;
+
+ BT_DBG("type 0x%2.2x len %d val 0x%lx", type, len, val);
+
+ opt->type = type;
+ opt->len = len;
+
+ switch (len) {
+ case 1:
+ *((__u8 *) opt->val) = val;
+ break;
+
+ case 2:
+ *((__u16 *) opt->val) = __cpu_to_le16(val);
+ break;
+
+ case 4:
+ *((__u32 *) opt->val) = __cpu_to_le32(val);
+ break;
+
+ default:
+ memcpy(opt->val, (void *) val, len);
+ break;
+ };
+
+ *ptr += L2CAP_CONF_OPT_SIZE + len;
+}
+
+static int l2cap_build_conf_req(struct sock *sk, void *data)
+{
+ struct l2cap_pinfo *pi = l2cap_pi(sk);
+ l2cap_conf_req *req = (l2cap_conf_req *) data;
+ void *ptr = req->data;
+
+ BT_DBG("sk %p", sk);
+
+ if (pi->imtu != L2CAP_DEFAULT_MTU)
+ l2cap_add_conf_opt(&ptr, L2CAP_CONF_MTU, 2, pi->imtu);
+
+ /* FIXME. Need actual value of the flush timeout */
+ //if (flush_to != L2CAP_DEFAULT_FLUSH_TO)
+ // l2cap_add_conf_opt(&ptr, L2CAP_CONF_FLUSH_TO, 2, pi->flush_to);
+
+ req->dcid = __cpu_to_le16(pi->dcid);
+ req->flags = __cpu_to_le16(0);
+
+ return ptr - data;
+}
+
+static inline int l2cap_conf_output(struct sock *sk, void **ptr)
+{
+ struct l2cap_pinfo *pi = l2cap_pi(sk);
+ int result = 0;
+
+ /* Configure output options and let the other side know
+ * which ones we don't like.
+ */
+ if (pi->conf_mtu < pi->omtu) {
+ l2cap_add_conf_opt(ptr, L2CAP_CONF_MTU, 2, pi->omtu);
+ result = L2CAP_CONF_UNACCEPT;
+ } else {
+ pi->omtu = pi->conf_mtu;
+ }
+
+ BT_DBG("sk %p result %d", sk, result);
+ return result;
+}
+
+static int l2cap_build_conf_rsp(struct sock *sk, void *data, int *result)
+{
+ l2cap_conf_rsp *rsp = (l2cap_conf_rsp *) data;
+ void *ptr = rsp->data;
+ u16 flags = 0;
+
+ BT_DBG("sk %p complete %d", sk, result ? 1 : 0);
+
+ if (result)
+ *result = l2cap_conf_output(sk, &ptr);
+ else
+ flags |= 0x0001;
+
+ rsp->scid = __cpu_to_le16(l2cap_pi(sk)->dcid);
+ rsp->result = __cpu_to_le16(result ? *result : 0);
+ rsp->flags = __cpu_to_le16(flags);
+
+ return ptr - data;
+}
+
+static inline int l2cap_connect_req(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data)
+{
+ struct l2cap_chan_list *list = &conn->chan_list;
+ l2cap_conn_req *req = (l2cap_conn_req *) data;
+ l2cap_conn_rsp rsp;
+ struct sock *sk, *parent;
+ int result = 0, status = 0;
+
+ __u16 dcid = 0, scid = __le16_to_cpu(req->scid);
+ __u16 psm = req->psm;
+
+ BT_DBG("psm 0x%2.2x scid 0x%4.4x", psm, scid);
+
+ /* Check if we have socket listening on psm */
+ parent = l2cap_get_sock_by_psm(BT_LISTEN, psm, conn->src);
+ if (!parent) {
+ result = L2CAP_CR_BAD_PSM;
+ goto sendresp;
+ }
+
+ result = L2CAP_CR_NO_MEM;
+
+ /* Check for backlog size */
+ if (parent->ack_backlog > parent->max_ack_backlog) {
+ BT_DBG("backlog full %d", parent->ack_backlog);
+ goto response;
+ }
+
+ sk = l2cap_sock_alloc(NULL, BTPROTO_L2CAP, GFP_ATOMIC);
+ if (!sk)
+ goto response;
+
+ write_lock(&list->lock);
+
+ /* Check if we already have channel with that dcid */
+ if (__l2cap_get_chan_by_dcid(list, scid)) {
+ write_unlock(&list->lock);
+ sk->zapped = 1;
+ l2cap_sock_kill(sk);
+ goto response;
+ }
+
+ hci_conn_hold(conn->hcon);
+
+ l2cap_sock_init(sk, parent);
+ bacpy(&bluez_pi(sk)->src, conn->src);
+ bacpy(&bluez_pi(sk)->dst, conn->dst);
+ l2cap_pi(sk)->psm = psm;
+ l2cap_pi(sk)->dcid = scid;
+
+ __l2cap_chan_add(conn, sk, parent);
+ dcid = l2cap_pi(sk)->scid;
+
+ l2cap_sock_set_timer(sk, sk->sndtimeo);
+
+ /* Service level security */
+ result = L2CAP_CR_PEND;
+ status = L2CAP_CS_AUTHEN_PEND;
+ sk->state = BT_CONNECT2;
+ l2cap_pi(sk)->ident = cmd->ident;
+
+ if (l2cap_pi(sk)->link_mode & L2CAP_LM_ENCRYPT) {
+ if (!hci_conn_encrypt(conn->hcon))
+ goto done;
+ } else if (l2cap_pi(sk)->link_mode & L2CAP_LM_AUTH) {
+ if (!hci_conn_auth(conn->hcon))
+ goto done;
+ }
+
+ sk->state = BT_CONFIG;
+ result = status = 0;
+
+done:
+ write_unlock(&list->lock);
+
+response:
+ bh_unlock_sock(parent);
+
+sendresp:
+ rsp.scid = __cpu_to_le16(scid);
+ rsp.dcid = __cpu_to_le16(dcid);
+ rsp.result = __cpu_to_le16(result);
+ rsp.status = __cpu_to_le16(status);
+ l2cap_send_rsp(conn, cmd->ident, L2CAP_CONN_RSP, L2CAP_CONN_RSP_SIZE, &rsp);
+ return 0;
+}
+
+static inline int l2cap_connect_rsp(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data)
+{
+ l2cap_conn_rsp *rsp = (l2cap_conn_rsp *) data;
+ __u16 scid, dcid, result, status;
+ struct sock *sk;
+ char req[128];
+
+ scid = __le16_to_cpu(rsp->scid);
+ dcid = __le16_to_cpu(rsp->dcid);
+ result = __le16_to_cpu(rsp->result);
+ status = __le16_to_cpu(rsp->status);
+
+ BT_DBG("dcid 0x%4.4x scid 0x%4.4x result 0x%2.2x status 0x%2.2x", dcid, scid, result, status);
+
+ if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, scid)))
+ return -ENOENT;
+
+ switch (result) {
+ case L2CAP_CR_SUCCESS:
+ sk->state = BT_CONFIG;
+ l2cap_pi(sk)->dcid = dcid;
+ l2cap_pi(sk)->conf_state |= L2CAP_CONF_REQ_SENT;
+
+ l2cap_send_req(conn, L2CAP_CONF_REQ, l2cap_build_conf_req(sk, req), req);
+ break;
+
+ case L2CAP_CR_PEND:
+ break;
+
+ default:
+ l2cap_chan_del(sk, ECONNREFUSED);
+ break;
+ }
+
+ bh_unlock_sock(sk);
+ return 0;
+}
+
+static inline int l2cap_config_req(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data)
+{
+ l2cap_conf_req * req = (l2cap_conf_req *) data;
+ __u16 dcid, flags;
+ __u8 rsp[64];
+ struct sock *sk;
+ int result;
+
+ dcid = __le16_to_cpu(req->dcid);
+ flags = __le16_to_cpu(req->flags);
+
+ BT_DBG("dcid 0x%4.4x flags 0x%2.2x", dcid, flags);
+
+ if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, dcid)))
+ return -ENOENT;
+
+ l2cap_parse_conf_req(sk, req->data, cmd->len - L2CAP_CONF_REQ_SIZE);
+
+ if (flags & 0x0001) {
+ /* Incomplete config. Send empty response. */
+ l2cap_send_rsp(conn, cmd->ident, L2CAP_CONF_RSP, l2cap_build_conf_rsp(sk, rsp, NULL), rsp);
+ goto unlock;
+ }
+
+ /* Complete config. */
+ l2cap_send_rsp(conn, cmd->ident, L2CAP_CONF_RSP, l2cap_build_conf_rsp(sk, rsp, &result), rsp);
+
+ if (result)
+ goto unlock;
+
+ /* Output config done */
+ l2cap_pi(sk)->conf_state |= L2CAP_CONF_OUTPUT_DONE;
+
+ if (l2cap_pi(sk)->conf_state & L2CAP_CONF_INPUT_DONE) {
+ sk->state = BT_CONNECTED;
+ l2cap_chan_ready(sk);
+ } else if (!(l2cap_pi(sk)->conf_state & L2CAP_CONF_REQ_SENT)) {
+ char req[64];
+ l2cap_send_req(conn, L2CAP_CONF_REQ, l2cap_build_conf_req(sk, req), req);
+ }
+
+unlock:
+ bh_unlock_sock(sk);
+ return 0;
+}
+
+static inline int l2cap_config_rsp(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data)
+{
+ l2cap_conf_rsp *rsp = (l2cap_conf_rsp *)data;
+ __u16 scid, flags, result;
+ struct sock *sk;
+ int err = 0;
+
+ scid = __le16_to_cpu(rsp->scid);
+ flags = __le16_to_cpu(rsp->flags);
+ result = __le16_to_cpu(rsp->result);
+
+ BT_DBG("scid 0x%4.4x flags 0x%2.2x result 0x%2.2x", scid, flags, result);
+
+ if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, scid)))
+ return -ENOENT;
+
+ switch (result) {
+ case L2CAP_CONF_SUCCESS:
+ break;
+
+ case L2CAP_CONF_UNACCEPT:
+ if (++l2cap_pi(sk)->conf_retry < L2CAP_CONF_MAX_RETRIES) {
+ char req[128];
+ /*
+ It does not make sense to adjust L2CAP parameters
+ that are currently defined in the spec. We simply
+ resend config request that we sent earlier. It is
+ stupid :) but it helps qualification testing
+ which expects at least some response from us.
+ */
+ l2cap_send_req(conn, L2CAP_CONF_REQ,
+ l2cap_build_conf_req(sk, req), req);
+ goto done;
+ }
+ default:
+ sk->state = BT_DISCONN;
+ sk->err = ECONNRESET;
+ l2cap_sock_set_timer(sk, HZ * 5);
+ {
+ l2cap_disconn_req req;
+ req.dcid = __cpu_to_le16(l2cap_pi(sk)->dcid);
+ req.scid = __cpu_to_le16(l2cap_pi(sk)->scid);
+ l2cap_send_req(conn, L2CAP_DISCONN_REQ, L2CAP_DISCONN_REQ_SIZE, &req);
+ }
+ goto done;
+ }
+
+ if (flags & 0x01)
+ goto done;
+
+ /* Input config done */
+ l2cap_pi(sk)->conf_state |= L2CAP_CONF_INPUT_DONE;
+
+ if (l2cap_pi(sk)->conf_state & L2CAP_CONF_OUTPUT_DONE) {
+ sk->state = BT_CONNECTED;
+ l2cap_chan_ready(sk);
+ }
+
+done:
+ bh_unlock_sock(sk);
+ return err;
+}
+
+static inline int l2cap_disconnect_req(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data)
+{
+ l2cap_disconn_req *req = (l2cap_disconn_req *) data;
+ l2cap_disconn_rsp rsp;
+ __u16 dcid, scid;
+ struct sock *sk;
+
+ scid = __le16_to_cpu(req->scid);
+ dcid = __le16_to_cpu(req->dcid);
+
+ BT_DBG("scid 0x%4.4x dcid 0x%4.4x", scid, dcid);
+
+ if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, dcid)))
+ return 0;
+
+ rsp.dcid = __cpu_to_le16(l2cap_pi(sk)->scid);
+ rsp.scid = __cpu_to_le16(l2cap_pi(sk)->dcid);
+ l2cap_send_rsp(conn, cmd->ident, L2CAP_DISCONN_RSP, L2CAP_DISCONN_RSP_SIZE, &rsp);
+
+ sk->shutdown = SHUTDOWN_MASK;
+
+ l2cap_chan_del(sk, ECONNRESET);
+ bh_unlock_sock(sk);
+
+ l2cap_sock_kill(sk);
+ return 0;
+}
+
+static inline int l2cap_disconnect_rsp(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, __u8 *data)
+{
+ l2cap_disconn_rsp *rsp = (l2cap_disconn_rsp *) data;
+ __u16 dcid, scid;
+ struct sock *sk;
+
+ scid = __le16_to_cpu(rsp->scid);
+ dcid = __le16_to_cpu(rsp->dcid);
+
+ BT_DBG("dcid 0x%4.4x scid 0x%4.4x", dcid, scid);
+
+ if (!(sk = l2cap_get_chan_by_scid(&conn->chan_list, scid)))
+ return 0;
+ l2cap_chan_del(sk, 0);
+ bh_unlock_sock(sk);
+
+ l2cap_sock_kill(sk);
+ return 0;
+}
+
+static inline int l2cap_information_req(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, u8 *data)
+{
+ l2cap_info_req *req = (l2cap_info_req *) data;
+ l2cap_info_rsp rsp;
+ u16 type;
+
+ type = __le16_to_cpu(req->type);
+
+ BT_DBG("type 0x%4.4x", type);
+
+ rsp.type = __cpu_to_le16(type);
+ rsp.result = __cpu_to_le16(L2CAP_IR_NOTSUPP);
+ l2cap_send_rsp(conn, cmd->ident, L2CAP_INFO_RSP, sizeof(rsp), &rsp);
+ return 0;
+}
+
+static inline int l2cap_information_rsp(struct l2cap_conn *conn, l2cap_cmd_hdr *cmd, u8 *data)
+{
+ l2cap_info_rsp *rsp = (l2cap_info_rsp *) data;
+ u16 type, result;
+
+ type = __le16_to_cpu(rsp->type);
+ result = __le16_to_cpu(rsp->result);
+
+ BT_DBG("type 0x%4.4x result 0x%2.2x", type, result);
+
+ return 0;
+}
+
+static inline void l2cap_sig_channel(struct l2cap_conn *conn, struct sk_buff *skb)
+{
+ __u8 *data = skb->data;
+ int len = skb->len;
+ l2cap_cmd_hdr cmd;
+ int err = 0;
+
+ l2cap_raw_recv(conn, skb);
+
+ while (len >= L2CAP_CMD_HDR_SIZE) {
+ memcpy(&cmd, data, L2CAP_CMD_HDR_SIZE);
+ data += L2CAP_CMD_HDR_SIZE;
+ len -= L2CAP_CMD_HDR_SIZE;
+
+ cmd.len = __le16_to_cpu(cmd.len);
+
+ BT_DBG("code 0x%2.2x len %d id 0x%2.2x", cmd.code, cmd.len, cmd.ident);
+
+ if (cmd.len > len || !cmd.ident) {
+ BT_DBG("corrupted command");
+ break;
+ }
+
+ switch (cmd.code) {
+ case L2CAP_COMMAND_REJ:
+ /* FIXME: We should process this */
+ break;
+
+ case L2CAP_CONN_REQ:
+ err = l2cap_connect_req(conn, &cmd, data);
+ break;
+
+ case L2CAP_CONN_RSP:
+ err = l2cap_connect_rsp(conn, &cmd, data);
+ break;
+
+ case L2CAP_CONF_REQ:
+ err = l2cap_config_req(conn, &cmd, data);
+ break;
+
+ case L2CAP_CONF_RSP:
+ err = l2cap_config_rsp(conn, &cmd, data);
+ break;
+
+ case L2CAP_DISCONN_REQ:
+ err = l2cap_disconnect_req(conn, &cmd, data);
+ break;
+
+ case L2CAP_DISCONN_RSP:
+ err = l2cap_disconnect_rsp(conn, &cmd, data);
+ break;
+
+ case L2CAP_ECHO_REQ:
+ l2cap_send_rsp(conn, cmd.ident, L2CAP_ECHO_RSP, cmd.len, data);
+ break;
+
+ case L2CAP_ECHO_RSP:
+ break;
+
+ case L2CAP_INFO_REQ:
+ err = l2cap_information_req(conn, &cmd, data);
+ break;
+
+ case L2CAP_INFO_RSP:
+ err = l2cap_information_rsp(conn, &cmd, data);
+ break;
+
+ default:
+ BT_ERR("Unknown signaling command 0x%2.2x", cmd.code);
+ err = -EINVAL;
+ break;
+ };
+
+ if (err) {
+ l2cap_cmd_rej rej;
+ BT_DBG("error %d", err);
+
+ /* FIXME: Map err to a valid reason */
+ rej.reason = __cpu_to_le16(0);
+ l2cap_send_rsp(conn, cmd.ident, L2CAP_COMMAND_REJ, L2CAP_CMD_REJ_SIZE, &rej);
+ }
+
+ data += cmd.len;
+ len -= cmd.len;
+ }
+
+ kfree_skb(skb);
+}
+
+static inline int l2cap_data_channel(struct l2cap_conn *conn, __u16 cid, struct sk_buff *skb)
+{
+ struct sock *sk;
+
+ sk = l2cap_get_chan_by_scid(&conn->chan_list, cid);
+ if (!sk) {
+ BT_DBG("unknown cid 0x%4.4x", cid);
+ goto drop;
+ }
+
+ BT_DBG("sk %p, len %d", sk, skb->len);
+
+ if (sk->state != BT_CONNECTED)
+ goto drop;
+
+ if (l2cap_pi(sk)->imtu < skb->len)
+ goto drop;
+
+ /* If socket recv buffers overflows we drop data here
+ * which is *bad* because L2CAP has to be reliable.
+ * But we don't have any other choice. L2CAP doesn't
+ * provide flow control mechanism */
+
+ if (!sock_queue_rcv_skb(sk, skb))
+ goto done;
+
+drop:
+ kfree_skb(skb);
+
+done:
+ if (sk) bh_unlock_sock(sk);
+ return 0;
+}
+
+static inline int l2cap_conless_channel(struct l2cap_conn *conn, __u16 psm, struct sk_buff *skb)
+{
+ struct sock *sk;
+
+ sk = l2cap_get_sock_by_psm(0, psm, conn->src);
+ if (!sk)
+ goto drop;
+
+ BT_DBG("sk %p, len %d", sk, skb->len);
+
+ if (sk->state != BT_BOUND && sk->state != BT_CONNECTED)
+ goto drop;
+
+ if (l2cap_pi(sk)->imtu < skb->len)
+ goto drop;
+
+ if (!sock_queue_rcv_skb(sk, skb))
+ goto done;
+
+drop:
+ kfree_skb(skb);
+
+done:
+ if (sk) bh_unlock_sock(sk);
+ return 0;
+}
+
+static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
+{
+ l2cap_hdr *lh = (l2cap_hdr *) skb->data;
+ __u16 cid, psm, len;
+
+ skb_pull(skb, L2CAP_HDR_SIZE);
+ cid = __le16_to_cpu(lh->cid);
+ len = __le16_to_cpu(lh->len);
+
+ BT_DBG("len %d, cid 0x%4.4x", len, cid);
+
+ switch (cid) {
+ case 0x0001:
+ l2cap_sig_channel(conn, skb);
+ break;
+
+ case 0x0002:
+ psm = get_unaligned((__u16 *) skb->data);
+ skb_pull(skb, 2);
+ l2cap_conless_channel(conn, psm, skb);
+ break;
+
+ default:
+ l2cap_data_channel(conn, cid, skb);
+ break;
+ }
+}
+
+/* ------------ L2CAP interface with lower layer (HCI) ------------- */
+
+static int l2cap_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type)
+{
+ int exact = 0, lm1 = 0, lm2 = 0;
+ register struct sock *sk;
+
+ if (type != ACL_LINK)
+ return 0;
+
+ BT_DBG("hdev %s, bdaddr %s", hdev->name, batostr(bdaddr));
+
+ /* Find listening sockets and check their link_mode */
+ read_lock(&l2cap_sk_list.lock);
+ for (sk = l2cap_sk_list.head; sk; sk = sk->next) {
+ if (sk->state != BT_LISTEN)
+ continue;
+
+ if (!bacmp(&bluez_pi(sk)->src, &hdev->bdaddr)) {
+ lm1 |= (HCI_LM_ACCEPT | l2cap_pi(sk)->link_mode);
+ exact++;
+ } else if (!bacmp(&bluez_pi(sk)->src, BDADDR_ANY))
+ lm2 |= (HCI_LM_ACCEPT | l2cap_pi(sk)->link_mode);
+ }
+ read_unlock(&l2cap_sk_list.lock);
+
+ return exact ? lm1 : lm2;
+}
+
+static int l2cap_connect_cfm(struct hci_conn *hcon, __u8 status)
+{
+ BT_DBG("hcon %p bdaddr %s status %d", hcon, batostr(&hcon->dst), status);
+
+ if (hcon->type != ACL_LINK)
+ return 0;
+
+ if (!status) {
+ struct l2cap_conn *conn;
+
+ conn = l2cap_conn_add(hcon, status);
+ if (conn)
+ l2cap_conn_ready(conn);
+ } else
+ l2cap_conn_del(hcon, bterr(status));
+
+ return 0;
+}
+
+static int l2cap_disconn_ind(struct hci_conn *hcon, __u8 reason)
+{
+ BT_DBG("hcon %p reason %d", hcon, reason);
+
+ if (hcon->type != ACL_LINK)
+ return 0;
+
+ l2cap_conn_del(hcon, bterr(reason));
+ return 0;
+}
+
+static int l2cap_auth_cfm(struct hci_conn *hcon, __u8 status)
+{
+ struct l2cap_chan_list *l;
+ struct l2cap_conn *conn;
+ l2cap_conn_rsp rsp;
+ struct sock *sk;
+ int result;
+
+ if (!(conn = hcon->l2cap_data))
+ return 0;
+ l = &conn->chan_list;
+
+ BT_DBG("conn %p", conn);
+
+ read_lock(&l->lock);
+
+ for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) {
+ bh_lock_sock(sk);
+
+ if (sk->state != BT_CONNECT2 ||
+ (l2cap_pi(sk)->link_mode & L2CAP_LM_ENCRYPT)) {
+ bh_unlock_sock(sk);
+ continue;
+ }
+
+ if (!status) {
+ sk->state = BT_CONFIG;
+ result = 0;
+ } else {
+ sk->state = BT_DISCONN;
+ l2cap_sock_set_timer(sk, HZ/10);
+ result = L2CAP_CR_SEC_BLOCK;
+ }
+
+ rsp.scid = __cpu_to_le16(l2cap_pi(sk)->dcid);
+ rsp.dcid = __cpu_to_le16(l2cap_pi(sk)->scid);
+ rsp.result = __cpu_to_le16(result);
+ rsp.status = __cpu_to_le16(0);
+ l2cap_send_rsp(conn, l2cap_pi(sk)->ident, L2CAP_CONN_RSP,
+ L2CAP_CONN_RSP_SIZE, &rsp);
+
+ bh_unlock_sock(sk);
+ }
+
+ read_unlock(&l->lock);
+ return 0;
+}
+
+static int l2cap_encrypt_cfm(struct hci_conn *hcon, __u8 status)
+{
+ struct l2cap_chan_list *l;
+ struct l2cap_conn *conn;
+ l2cap_conn_rsp rsp;
+ struct sock *sk;
+ int result;
+
+ if (!(conn = hcon->l2cap_data))
+ return 0;
+ l = &conn->chan_list;
+
+ BT_DBG("conn %p", conn);
+
+ read_lock(&l->lock);
+
+ for (sk = l->head; sk; sk = l2cap_pi(sk)->next_c) {
+ bh_lock_sock(sk);
+
+ if (sk->state != BT_CONNECT2) {
+ bh_unlock_sock(sk);
+ continue;
+ }
+
+ if (!status) {
+ sk->state = BT_CONFIG;
+ result = 0;
+ } else {
+ sk->state = BT_DISCONN;
+ l2cap_sock_set_timer(sk, HZ/10);
+ result = L2CAP_CR_SEC_BLOCK;
+ }
+
+ rsp.scid = __cpu_to_le16(l2cap_pi(sk)->dcid);
+ rsp.dcid = __cpu_to_le16(l2cap_pi(sk)->scid);
+ rsp.result = __cpu_to_le16(result);
+ rsp.status = __cpu_to_le16(0);
+ l2cap_send_rsp(conn, l2cap_pi(sk)->ident, L2CAP_CONN_RSP,
+ L2CAP_CONN_RSP_SIZE, &rsp);
+
+ bh_unlock_sock(sk);
+ }
+
+ read_unlock(&l->lock);
+ return 0;
+}
+
+static int l2cap_recv_acldata(struct hci_conn *hcon, struct sk_buff *skb, __u16 flags)
+{
+ struct l2cap_conn *conn = hcon->l2cap_data;
+
+ if (!conn && !(conn = l2cap_conn_add(hcon, 0)))
+ goto drop;
+
+ BT_DBG("conn %p len %d flags 0x%x", conn, skb->len, flags);
+
+ if (flags & ACL_START) {
+ l2cap_hdr *hdr;
+ int len;
+
+ if (conn->rx_len) {
+ BT_ERR("Unexpected start frame (len %d)", skb->len);
+ kfree_skb(conn->rx_skb);
+ conn->rx_skb = NULL;
+ conn->rx_len = 0;
+ l2cap_conn_unreliable(conn, ECOMM);
+ }
+
+ if (skb->len < 2) {
+ BT_ERR("Frame is too short (len %d)", skb->len);
+ l2cap_conn_unreliable(conn, ECOMM);
+ goto drop;
+ }
+
+ hdr = (l2cap_hdr *) skb->data;
+ len = __le16_to_cpu(hdr->len) + L2CAP_HDR_SIZE;
+
+ if (len == skb->len) {
+ /* Complete frame received */
+ l2cap_recv_frame(conn, skb);
+ return 0;
+ }
+
+ BT_DBG("Start: total len %d, frag len %d", len, skb->len);
+
+ if (skb->len > len) {
+ BT_ERR("Frame is too long (len %d, expected len %d)",
+ skb->len, len);
+ l2cap_conn_unreliable(conn, ECOMM);
+ goto drop;
+ }
+
+ /* Allocate skb for the complete frame including header */
+ conn->rx_skb = bluez_skb_alloc(len, GFP_ATOMIC);
+ if (!conn->rx_skb)
+ goto drop;
+
+ memcpy(skb_put(conn->rx_skb, skb->len), skb->data, skb->len);
+ conn->rx_len = len - skb->len;
+ } else {
+ BT_DBG("Cont: frag len %d (expecting %d)", skb->len, conn->rx_len);
+
+ if (!conn->rx_len) {
+ BT_ERR("Unexpected continuation frame (len %d)", skb->len);
+ l2cap_conn_unreliable(conn, ECOMM);
+ goto drop;
+ }
+
+ if (skb->len > conn->rx_len) {
+ BT_ERR("Fragment is too long (len %d, expected %d)",
+ skb->len, conn->rx_len);
+ kfree_skb(conn->rx_skb);
+ conn->rx_skb = NULL;
+ conn->rx_len = 0;
+ l2cap_conn_unreliable(conn, ECOMM);
+ goto drop;
+ }
+
+ memcpy(skb_put(conn->rx_skb, skb->len), skb->data, skb->len);
+ conn->rx_len -= skb->len;
+
+ if (!conn->rx_len) {
+ /* Complete frame received */
+ l2cap_recv_frame(conn, conn->rx_skb);
+ conn->rx_skb = NULL;
+ }
+ }
+
+drop:
+ kfree_skb(skb);
+ return 0;
+}
+
+/* ----- Proc fs support ------ */
+static int l2cap_sock_dump(char *buf, struct bluez_sock_list *list)
+{
+ struct l2cap_pinfo *pi;
+ struct sock *sk;
+ char *ptr = buf;
+
+ read_lock_bh(&list->lock);
+
+ for (sk = list->head; sk; sk = sk->next) {
+ pi = l2cap_pi(sk);
+ ptr += sprintf(ptr, "%s %s %d %d 0x%4.4x 0x%4.4x %d %d 0x%x\n",
+ batostr(&bluez_pi(sk)->src), batostr(&bluez_pi(sk)->dst),
+ sk->state, pi->psm, pi->scid, pi->dcid, pi->imtu, pi->omtu,
+ pi->link_mode);
+ }
+
+ read_unlock_bh(&list->lock);
+
+ ptr += sprintf(ptr, "\n");
+ return ptr - buf;
+}
+
+static int l2cap_read_proc(char *buf, char **start, off_t offset, int count, int *eof, void *priv)
+{
+ char *ptr = buf;
+ int len;
+
+ BT_DBG("count %d, offset %ld", count, offset);
+
+ ptr += l2cap_sock_dump(ptr, &l2cap_sk_list);
+ len = ptr - buf;
+
+ if (len <= count + offset)
+ *eof = 1;
+
+ *start = buf + offset;
+ len -= offset;
+
+ if (len > count)
+ len = count;
+ if (len < 0)
+ len = 0;
+
+ return len;
+}
+
+static struct proto_ops l2cap_sock_ops = {
+ family: PF_BLUETOOTH,
+ release: l2cap_sock_release,
+ bind: l2cap_sock_bind,
+ connect: l2cap_sock_connect,
+ listen: l2cap_sock_listen,
+ accept: l2cap_sock_accept,
+ getname: l2cap_sock_getname,
+ sendmsg: l2cap_sock_sendmsg,
+ recvmsg: bluez_sock_recvmsg,
+ poll: bluez_sock_poll,
+ socketpair: sock_no_socketpair,
+ ioctl: sock_no_ioctl,
+ shutdown: l2cap_sock_shutdown,
+ setsockopt: l2cap_sock_setsockopt,
+ getsockopt: l2cap_sock_getsockopt,
+ mmap: sock_no_mmap
+};
+
+static struct net_proto_family l2cap_sock_family_ops = {
+ family: PF_BLUETOOTH,
+ create: l2cap_sock_create
+};
+
+static struct hci_proto l2cap_hci_proto = {
+ name: "L2CAP",
+ id: HCI_PROTO_L2CAP,
+ connect_ind: l2cap_connect_ind,
+ connect_cfm: l2cap_connect_cfm,
+ disconn_ind: l2cap_disconn_ind,
+ recv_acldata: l2cap_recv_acldata,
+ auth_cfm: l2cap_auth_cfm,
+ encrypt_cfm: l2cap_encrypt_cfm
+};
+
+int __init l2cap_init(void)
+{
+ int err;
+
+ if ((err = bluez_sock_register(BTPROTO_L2CAP, &l2cap_sock_family_ops))) {
+ BT_ERR("Can't register L2CAP socket");
+ return err;
+ }
+
+ if ((err = hci_register_proto(&l2cap_hci_proto))) {
+ BT_ERR("Can't register L2CAP protocol");
+ return err;
+ }
+
+ create_proc_read_entry("bluetooth/l2cap", 0, 0, l2cap_read_proc, NULL);
+
+ BT_INFO("BlueZ L2CAP ver %s Copyright (C) 2000,2001 Qualcomm Inc", VERSION);
+ BT_INFO("Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>");
+ return 0;
+}
+
+void l2cap_cleanup(void)
+{
+ remove_proc_entry("bluetooth/l2cap", NULL);
+
+ /* Unregister socket and protocol */
+ if (bluez_sock_unregister(BTPROTO_L2CAP))
+ BT_ERR("Can't unregister L2CAP socket");
+
+ if (hci_unregister_proto(&l2cap_hci_proto))
+ BT_ERR("Can't unregister L2CAP protocol");
+}
+
+void l2cap_load(void)
+{
+ /* Dummy function to trigger automatic L2CAP module loading by
+ other modules that use L2CAP sockets but do not use any other
+ symbols from it. */
+ return;
+}
+
+EXPORT_SYMBOL(l2cap_load);
+
+module_init(l2cap_init);
+module_exit(l2cap_cleanup);
+
+MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>");
+MODULE_DESCRIPTION("BlueZ L2CAP ver " VERSION);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/lib.c b/uClinux-2.4.31-uc0/net/bluetooth/lib.c
new file mode 100644
index 0000000..cb34b07
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/lib.c
@@ -0,0 +1,175 @@
+/*
+ BlueZ - Bluetooth protocol stack for Linux
+ Copyright (C) 2000-2001 Qualcomm Incorporated
+
+ Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * BlueZ kernel library.
+ *
+ * $Id: lib.c,v 1.2 2002/06/20 19:55:08 maxk Exp $
+ */
+
+#include <linux/kernel.h>
+#include <linux/stddef.h>
+#include <linux/string.h>
+#include <asm/errno.h>
+
+#include <net/bluetooth/bluetooth.h>
+
+void bluez_dump(char *pref, __u8 *buf, int count)
+{
+ char *ptr;
+ char line[100];
+ int i;
+
+ printk(KERN_INFO "%s: dump, len %d\n", pref, count);
+
+ ptr = line;
+ *ptr = 0;
+ for (i = 0; i<count; i++) {
+ ptr += sprintf(ptr, " %2.2X", buf[i]);
+
+ if (i && !((i + 1) % 20)) {
+ printk(KERN_INFO "%s:%s\n", pref, line);
+ ptr = line;
+ *ptr = 0;
+ }
+ }
+
+ if (line[0])
+ printk(KERN_INFO "%s:%s\n", pref, line);
+}
+
+void baswap(bdaddr_t *dst, bdaddr_t *src)
+{
+ unsigned char *d = (unsigned char *) dst;
+ unsigned char *s = (unsigned char *) src;
+ int i;
+
+ for (i = 0; i < 6; i++)
+ d[i] = s[5 - i];
+}
+
+char *batostr(bdaddr_t *ba)
+{
+ static char str[2][18];
+ static int i = 1;
+
+ i ^= 1;
+ sprintf(str[i], "%2.2X:%2.2X:%2.2X:%2.2X:%2.2X:%2.2X",
+ ba->b[0], ba->b[1], ba->b[2],
+ ba->b[3], ba->b[4], ba->b[5]);
+
+ return str[i];
+}
+
+/* Bluetooth error codes to Unix errno mapping */
+int bterr(__u16 code)
+{
+ switch (code) {
+ case 0:
+ return 0;
+
+ case 0x01:
+ return EBADRQC;
+
+ case 0x02:
+ return ENOTCONN;
+
+ case 0x03:
+ return EIO;
+
+ case 0x04:
+ return EHOSTDOWN;
+
+ case 0x05:
+ return EACCES;
+
+ case 0x06:
+ return EBADE;
+
+ case 0x07:
+ return ENOMEM;
+
+ case 0x08:
+ return ETIMEDOUT;
+
+ case 0x09:
+ return EMLINK;
+
+ case 0x0a:
+ return EMLINK;
+
+ case 0x0b:
+ return EALREADY;
+
+ case 0x0c:
+ return EBUSY;
+
+ case 0x0d:
+ case 0x0e:
+ case 0x0f:
+ return ECONNREFUSED;
+
+ case 0x10:
+ return ETIMEDOUT;
+
+ case 0x11:
+ case 0x27:
+ case 0x29:
+ case 0x20:
+ return EOPNOTSUPP;
+
+ case 0x12:
+ return EINVAL;
+
+ case 0x13:
+ case 0x14:
+ case 0x15:
+ return ECONNRESET;
+
+ case 0x16:
+ return ECONNABORTED;
+
+ case 0x17:
+ return ELOOP;
+
+ case 0x18:
+ return EACCES;
+
+ case 0x1a:
+ return EPROTONOSUPPORT;
+
+ case 0x1b:
+ return ECONNREFUSED;
+
+ case 0x19:
+ case 0x1e:
+ case 0x23:
+ case 0x24:
+ case 0x25:
+ return EPROTO;
+
+ default:
+ return ENOSYS;
+ };
+}
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/Config.in b/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/Config.in
new file mode 100644
index 0000000..158fef6
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/Config.in
@@ -0,0 +1,10 @@
+#
+# Bluetooth RFCOMM layer configuration
+#
+
+dep_tristate 'RFCOMM protocol support' CONFIG_BLUEZ_RFCOMM $CONFIG_BLUEZ_L2CAP
+
+if [ "$CONFIG_BLUEZ_RFCOMM" != "n" ]; then
+ bool ' RFCOMM TTY support' CONFIG_BLUEZ_RFCOMM_TTY
+fi
+
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/Makefile b/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/Makefile
new file mode 100644
index 0000000..949c881
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/Makefile
@@ -0,0 +1,11 @@
+#
+# Makefile for the Linux Bluetooth RFCOMM layer
+#
+
+O_TARGET := rfcomm.o
+
+obj-y := core.o sock.o crc.o
+obj-$(CONFIG_BLUEZ_RFCOMM_TTY) += tty.o
+obj-m += $(O_TARGET)
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/core.c b/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/core.c
new file mode 100644
index 0000000..9de0103
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/core.c
@@ -0,0 +1,1940 @@
+/*
+ RFCOMM implementation for Linux Bluetooth stack (BlueZ).
+ Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com>
+ Copyright (C) 2002 Marcel Holtmann <marcel@holtmann.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ RPN support - Dirk Husemann <hud@zurich.ibm.com>
+*/
+
+/*
+ * RFCOMM core.
+ *
+ * $Id: core.c,v 1.46 2002/10/18 20:12:12 maxk Exp $
+ */
+
+#define __KERNEL_SYSCALLS__
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/signal.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/net.h>
+#include <linux/proc_fs.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/unaligned.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/l2cap.h>
+#include <net/bluetooth/rfcomm.h>
+
+#define VERSION "1.1"
+
+#ifndef CONFIG_BLUEZ_RFCOMM_DEBUG
+#undef BT_DBG
+#define BT_DBG(D...)
+#endif
+
+struct task_struct *rfcomm_thread;
+DECLARE_MUTEX(rfcomm_sem);
+unsigned long rfcomm_event;
+
+static LIST_HEAD(session_list);
+static atomic_t terminate, running;
+
+static int rfcomm_send_frame(struct rfcomm_session *s, u8 *data, int len);
+static int rfcomm_send_sabm(struct rfcomm_session *s, u8 dlci);
+static int rfcomm_send_disc(struct rfcomm_session *s, u8 dlci);
+static int rfcomm_queue_disc(struct rfcomm_dlc *d);
+static int rfcomm_send_nsc(struct rfcomm_session *s, int cr, u8 type);
+static int rfcomm_send_pn(struct rfcomm_session *s, int cr, struct rfcomm_dlc *d);
+static int rfcomm_send_msc(struct rfcomm_session *s, int cr, u8 dlci, u8 v24_sig);
+static int rfcomm_send_test(struct rfcomm_session *s, int cr, u8 *pattern, int len);
+static int rfcomm_send_credits(struct rfcomm_session *s, u8 addr, u8 credits);
+static void rfcomm_make_uih(struct sk_buff *skb, u8 addr);
+
+static void rfcomm_process_connect(struct rfcomm_session *s);
+
+/* ---- RFCOMM frame parsing macros ---- */
+#define __get_dlci(b) ((b & 0xfc) >> 2)
+#define __get_channel(b) ((b & 0xf8) >> 3)
+#define __get_dir(b) ((b & 0x04) >> 2)
+#define __get_type(b) ((b & 0xef))
+
+#define __test_ea(b) ((b & 0x01))
+#define __test_cr(b) ((b & 0x02))
+#define __test_pf(b) ((b & 0x10))
+
+#define __addr(cr, dlci) (((dlci & 0x3f) << 2) | (cr << 1) | 0x01)
+#define __ctrl(type, pf) (((type & 0xef) | (pf << 4)))
+#define __dlci(dir, chn) (((chn & 0x1f) << 1) | dir)
+#define __srv_channel(dlci) (dlci >> 1)
+#define __dir(dlci) (dlci & 0x01)
+
+#define __len8(len) (((len) << 1) | 1)
+#define __len16(len) ((len) << 1)
+
+/* MCC macros */
+#define __mcc_type(cr, type) (((type << 2) | (cr << 1) | 0x01))
+#define __get_mcc_type(b) ((b & 0xfc) >> 2)
+#define __get_mcc_len(b) ((b & 0xfe) >> 1)
+
+/* RPN macros */
+#define __rpn_line_settings(data, stop, parity) ((data & 0x3) | ((stop & 0x1) << 2) | ((parity & 0x3) << 3))
+#define __get_rpn_data_bits(line) ((line) & 0x3)
+#define __get_rpn_stop_bits(line) (((line) >> 2) & 0x1)
+#define __get_rpn_parity(line) (((line) >> 3) & 0x3)
+
+/* ---- RFCOMM FCS computation ---- */
+
+/* CRC on 2 bytes */
+#define __crc(data) (rfcomm_crc_table[rfcomm_crc_table[0xff ^ data[0]] ^ data[1]])
+
+/* FCS on 2 bytes */
+static inline u8 __fcs(u8 *data)
+{
+ return (0xff - __crc(data));
+}
+
+/* FCS on 3 bytes */
+static inline u8 __fcs2(u8 *data)
+{
+ return (0xff - rfcomm_crc_table[__crc(data) ^ data[2]]);
+}
+
+/* Check FCS */
+static inline int __check_fcs(u8 *data, int type, u8 fcs)
+{
+ u8 f = __crc(data);
+
+ if (type != RFCOMM_UIH)
+ f = rfcomm_crc_table[f ^ data[2]];
+
+ return rfcomm_crc_table[f ^ fcs] != 0xcf;
+}
+
+/* ---- L2CAP callbacks ---- */
+static void rfcomm_l2state_change(struct sock *sk)
+{
+ BT_DBG("%p state %d", sk, sk->state);
+ rfcomm_schedule(RFCOMM_SCHED_STATE);
+}
+
+static void rfcomm_l2data_ready(struct sock *sk, int bytes)
+{
+ BT_DBG("%p bytes %d", sk, bytes);
+ rfcomm_schedule(RFCOMM_SCHED_RX);
+}
+
+static int rfcomm_l2sock_create(struct socket **sock)
+{
+ int err;
+
+ BT_DBG("");
+
+ err = sock_create(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP, sock);
+ if (!err) {
+ struct sock *sk = (*sock)->sk;
+ sk->data_ready = rfcomm_l2data_ready;
+ sk->state_change = rfcomm_l2state_change;
+ }
+ return err;
+}
+
+/* ---- RFCOMM DLCs ---- */
+static void rfcomm_dlc_timeout(unsigned long arg)
+{
+ struct rfcomm_dlc *d = (void *) arg;
+
+ BT_DBG("dlc %p state %ld", d, d->state);
+
+ set_bit(RFCOMM_TIMED_OUT, &d->flags);
+ rfcomm_dlc_put(d);
+ rfcomm_schedule(RFCOMM_SCHED_TIMEO);
+}
+
+static void rfcomm_dlc_set_timer(struct rfcomm_dlc *d, long timeout)
+{
+ BT_DBG("dlc %p state %ld timeout %ld", d, d->state, timeout);
+
+ if (!mod_timer(&d->timer, jiffies + timeout))
+ rfcomm_dlc_hold(d);
+}
+
+static void rfcomm_dlc_clear_timer(struct rfcomm_dlc *d)
+{
+ BT_DBG("dlc %p state %ld", d, d->state);
+
+ if (timer_pending(&d->timer) && del_timer(&d->timer))
+ rfcomm_dlc_put(d);
+}
+
+static void rfcomm_dlc_clear_state(struct rfcomm_dlc *d)
+{
+ BT_DBG("%p", d);
+
+ d->state = BT_OPEN;
+ d->flags = 0;
+ d->mscex = 0;
+ d->mtu = RFCOMM_DEFAULT_MTU;
+ d->v24_sig = RFCOMM_V24_RTC | RFCOMM_V24_RTR | RFCOMM_V24_DV;
+
+ d->cfc = RFCOMM_CFC_DISABLED;
+ d->rx_credits = RFCOMM_DEFAULT_CREDITS;
+}
+
+struct rfcomm_dlc *rfcomm_dlc_alloc(int prio)
+{
+ struct rfcomm_dlc *d = kmalloc(sizeof(*d), prio);
+ if (!d)
+ return NULL;
+ memset(d, 0, sizeof(*d));
+
+ init_timer(&d->timer);
+ d->timer.function = rfcomm_dlc_timeout;
+ d->timer.data = (unsigned long) d;
+
+ skb_queue_head_init(&d->tx_queue);
+ spin_lock_init(&d->lock);
+ atomic_set(&d->refcnt, 1);
+
+ rfcomm_dlc_clear_state(d);
+
+ BT_DBG("%p", d);
+ return d;
+}
+
+void rfcomm_dlc_free(struct rfcomm_dlc *d)
+{
+ BT_DBG("%p", d);
+
+ skb_queue_purge(&d->tx_queue);
+ kfree(d);
+}
+
+static void rfcomm_dlc_link(struct rfcomm_session *s, struct rfcomm_dlc *d)
+{
+ BT_DBG("dlc %p session %p", d, s);
+
+ rfcomm_session_hold(s);
+
+ rfcomm_dlc_hold(d);
+ list_add(&d->list, &s->dlcs);
+ d->session = s;
+}
+
+static void rfcomm_dlc_unlink(struct rfcomm_dlc *d)
+{
+ struct rfcomm_session *s = d->session;
+
+ BT_DBG("dlc %p refcnt %d session %p", d, atomic_read(&d->refcnt), s);
+
+ list_del(&d->list);
+ d->session = NULL;
+ rfcomm_dlc_put(d);
+
+ rfcomm_session_put(s);
+}
+
+static struct rfcomm_dlc *rfcomm_dlc_get(struct rfcomm_session *s, u8 dlci)
+{
+ struct rfcomm_dlc *d;
+ struct list_head *p;
+
+ list_for_each(p, &s->dlcs) {
+ d = list_entry(p, struct rfcomm_dlc, list);
+ if (d->dlci == dlci)
+ return d;
+ }
+ return NULL;
+}
+
+static int __rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel)
+{
+ struct rfcomm_session *s;
+ int err = 0;
+ u8 dlci;
+
+ BT_DBG("dlc %p state %ld %s %s channel %d",
+ d, d->state, batostr(src), batostr(dst), channel);
+
+ if (channel < 1 || channel > 30)
+ return -EINVAL;
+
+ if (d->state != BT_OPEN && d->state != BT_CLOSED)
+ return 0;
+
+ s = rfcomm_session_get(src, dst);
+ if (!s) {
+ s = rfcomm_session_create(src, dst, &err);
+ if (!s)
+ return err;
+ }
+
+ dlci = __dlci(!s->initiator, channel);
+
+ /* Check if DLCI already exists */
+ if (rfcomm_dlc_get(s, dlci))
+ return -EBUSY;
+
+ rfcomm_dlc_clear_state(d);
+
+ d->dlci = dlci;
+ d->addr = __addr(s->initiator, dlci);
+ d->priority = 7;
+
+ d->state = BT_CONFIG;
+ rfcomm_dlc_link(s, d);
+
+ d->mtu = s->mtu;
+ d->cfc = (s->cfc == RFCOMM_CFC_UNKNOWN) ? 0 : s->cfc;
+
+ if (s->state == BT_CONNECTED)
+ rfcomm_send_pn(s, 1, d);
+ rfcomm_dlc_set_timer(d, RFCOMM_CONN_TIMEOUT);
+ return 0;
+}
+
+int rfcomm_dlc_open(struct rfcomm_dlc *d, bdaddr_t *src, bdaddr_t *dst, u8 channel)
+{
+ mm_segment_t fs;
+ int r;
+
+ rfcomm_lock();
+
+ fs = get_fs(); set_fs(KERNEL_DS);
+ r = __rfcomm_dlc_open(d, src, dst, channel);
+ set_fs(fs);
+
+ rfcomm_unlock();
+ return r;
+}
+
+static int __rfcomm_dlc_close(struct rfcomm_dlc *d, int err)
+{
+ struct rfcomm_session *s = d->session;
+ if (!s)
+ return 0;
+
+ BT_DBG("dlc %p state %ld dlci %d err %d session %p",
+ d, d->state, d->dlci, err, s);
+
+ switch (d->state) {
+ case BT_CONNECTED:
+ case BT_CONFIG:
+ case BT_CONNECT:
+ d->state = BT_DISCONN;
+ if (skb_queue_empty(&d->tx_queue)) {
+ rfcomm_send_disc(s, d->dlci);
+ rfcomm_dlc_set_timer(d, RFCOMM_DISC_TIMEOUT);
+ } else {
+ rfcomm_queue_disc(d);
+ rfcomm_dlc_set_timer(d, RFCOMM_DISC_TIMEOUT * 2);
+ }
+ break;
+
+ default:
+ rfcomm_dlc_clear_timer(d);
+
+ rfcomm_dlc_lock(d);
+ d->state = BT_CLOSED;
+ d->state_change(d, err);
+ rfcomm_dlc_unlock(d);
+
+ skb_queue_purge(&d->tx_queue);
+ rfcomm_dlc_unlink(d);
+ }
+
+ return 0;
+}
+
+int rfcomm_dlc_close(struct rfcomm_dlc *d, int err)
+{
+ mm_segment_t fs;
+ int r;
+
+ rfcomm_lock();
+
+ fs = get_fs(); set_fs(KERNEL_DS);
+ r = __rfcomm_dlc_close(d, err);
+ set_fs(fs);
+
+ rfcomm_unlock();
+ return r;
+}
+
+int rfcomm_dlc_send(struct rfcomm_dlc *d, struct sk_buff *skb)
+{
+ int len = skb->len;
+
+ if (d->state != BT_CONNECTED)
+ return -ENOTCONN;
+
+ BT_DBG("dlc %p mtu %d len %d", d, d->mtu, len);
+
+ if (len > d->mtu)
+ return -EINVAL;
+
+ rfcomm_make_uih(skb, d->addr);
+ skb_queue_tail(&d->tx_queue, skb);
+
+ if (!test_bit(RFCOMM_TX_THROTTLED, &d->flags))
+ rfcomm_schedule(RFCOMM_SCHED_TX);
+ return len;
+}
+
+void fastcall __rfcomm_dlc_throttle(struct rfcomm_dlc *d)
+{
+ BT_DBG("dlc %p state %ld", d, d->state);
+
+ if (!d->cfc) {
+ d->v24_sig |= RFCOMM_V24_FC;
+ set_bit(RFCOMM_MSC_PENDING, &d->flags);
+ }
+ rfcomm_schedule(RFCOMM_SCHED_TX);
+}
+
+void fastcall __rfcomm_dlc_unthrottle(struct rfcomm_dlc *d)
+{
+ BT_DBG("dlc %p state %ld", d, d->state);
+
+ if (!d->cfc) {
+ d->v24_sig &= ~RFCOMM_V24_FC;
+ set_bit(RFCOMM_MSC_PENDING, &d->flags);
+ }
+ rfcomm_schedule(RFCOMM_SCHED_TX);
+}
+
+/*
+ Set/get modem status functions use _local_ status i.e. what we report
+ to the other side.
+ Remote status is provided by dlc->modem_status() callback.
+ */
+int rfcomm_dlc_set_modem_status(struct rfcomm_dlc *d, u8 v24_sig)
+{
+ BT_DBG("dlc %p state %ld v24_sig 0x%x",
+ d, d->state, v24_sig);
+
+ if (test_bit(RFCOMM_RX_THROTTLED, &d->flags))
+ v24_sig |= RFCOMM_V24_FC;
+ else
+ v24_sig &= ~RFCOMM_V24_FC;
+
+ d->v24_sig = v24_sig;
+
+ if (!test_and_set_bit(RFCOMM_MSC_PENDING, &d->flags))
+ rfcomm_schedule(RFCOMM_SCHED_TX);
+
+ return 0;
+}
+
+int rfcomm_dlc_get_modem_status(struct rfcomm_dlc *d, u8 *v24_sig)
+{
+ BT_DBG("dlc %p state %ld v24_sig 0x%x",
+ d, d->state, d->v24_sig);
+
+ *v24_sig = d->v24_sig;
+ return 0;
+}
+
+/* ---- RFCOMM sessions ---- */
+struct rfcomm_session *rfcomm_session_add(struct socket *sock, int state)
+{
+ struct rfcomm_session *s = kmalloc(sizeof(*s), GFP_KERNEL);
+ if (!s)
+ return NULL;
+ memset(s, 0, sizeof(*s));
+
+ BT_DBG("session %p sock %p", s, sock);
+
+ INIT_LIST_HEAD(&s->dlcs);
+ s->state = state;
+ s->sock = sock;
+
+ s->mtu = RFCOMM_DEFAULT_MTU;
+ s->cfc = RFCOMM_CFC_UNKNOWN;
+
+ list_add(&s->list, &session_list);
+
+ /* Do not increment module usage count for listeting sessions.
+ * Otherwise we won't be able to unload the module. */
+ if (state != BT_LISTEN)
+ MOD_INC_USE_COUNT;
+ return s;
+}
+
+void rfcomm_session_del(struct rfcomm_session *s)
+{
+ int state = s->state;
+
+ BT_DBG("session %p state %ld", s, s->state);
+
+ list_del(&s->list);
+
+ if (state == BT_CONNECTED)
+ rfcomm_send_disc(s, 0);
+
+ sock_release(s->sock);
+ kfree(s);
+
+ if (state != BT_LISTEN)
+ MOD_DEC_USE_COUNT;
+}
+
+struct rfcomm_session *rfcomm_session_get(bdaddr_t *src, bdaddr_t *dst)
+{
+ struct rfcomm_session *s;
+ struct list_head *p, *n;
+ struct bluez_pinfo *pi;
+ list_for_each_safe(p, n, &session_list) {
+ s = list_entry(p, struct rfcomm_session, list);
+ pi = bluez_pi(s->sock->sk);
+
+ if ((!bacmp(src, BDADDR_ANY) || !bacmp(&pi->src, src)) &&
+ !bacmp(&pi->dst, dst))
+ return s;
+ }
+ return NULL;
+}
+
+void rfcomm_session_close(struct rfcomm_session *s, int err)
+{
+ struct rfcomm_dlc *d;
+ struct list_head *p, *n;
+
+ BT_DBG("session %p state %ld err %d", s, s->state, err);
+
+ rfcomm_session_hold(s);
+
+ s->state = BT_CLOSED;
+
+ /* Close all dlcs */
+ list_for_each_safe(p, n, &s->dlcs) {
+ d = list_entry(p, struct rfcomm_dlc, list);
+ d->state = BT_CLOSED;
+ __rfcomm_dlc_close(d, err);
+ }
+
+ rfcomm_session_put(s);
+}
+
+struct rfcomm_session *rfcomm_session_create(bdaddr_t *src, bdaddr_t *dst, int *err)
+{
+ struct rfcomm_session *s = NULL;
+ struct sockaddr_l2 addr;
+ struct l2cap_options opts;
+ struct socket *sock;
+ int size;
+
+ BT_DBG("%s %s", batostr(src), batostr(dst));
+
+ *err = rfcomm_l2sock_create(&sock);
+ if (*err < 0)
+ return NULL;
+
+ bacpy(&addr.l2_bdaddr, src);
+ addr.l2_family = AF_BLUETOOTH;
+ addr.l2_psm = 0;
+ *err = sock->ops->bind(sock, (struct sockaddr *) &addr, sizeof(addr));
+ if (*err < 0)
+ goto failed;
+
+ /* Set L2CAP options */
+ size = sizeof(opts);
+ sock->ops->getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, (void *)&opts, &size);
+
+ opts.imtu = RFCOMM_MAX_L2CAP_MTU;
+ sock->ops->setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, (void *)&opts, size);
+
+ s = rfcomm_session_add(sock, BT_BOUND);
+ if (!s) {
+ *err = -ENOMEM;
+ goto failed;
+ }
+
+ s->initiator = 1;
+
+ bacpy(&addr.l2_bdaddr, dst);
+ addr.l2_family = AF_BLUETOOTH;
+ addr.l2_psm = htobs(RFCOMM_PSM);
+ *err = sock->ops->connect(sock, (struct sockaddr *) &addr, sizeof(addr), O_NONBLOCK);
+ if (*err == 0 || *err == -EAGAIN)
+ return s;
+
+ rfcomm_session_del(s);
+ return NULL;
+
+failed:
+ sock_release(sock);
+ return NULL;
+}
+
+void rfcomm_session_getaddr(struct rfcomm_session *s, bdaddr_t *src, bdaddr_t *dst)
+{
+ struct sock *sk = s->sock->sk;
+ if (src)
+ bacpy(src, &bluez_pi(sk)->src);
+ if (dst)
+ bacpy(dst, &bluez_pi(sk)->dst);
+}
+
+/* ---- RFCOMM frame sending ---- */
+static int rfcomm_send_frame(struct rfcomm_session *s, u8 *data, int len)
+{
+ struct socket *sock = s->sock;
+ struct iovec iv = { data, len };
+ struct msghdr msg;
+ int err;
+
+ BT_DBG("session %p len %d", s, len);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iovlen = 1;
+ msg.msg_iov = &iv;
+
+ err = sock->ops->sendmsg(sock, &msg, len, 0);
+ return err;
+}
+
+static int rfcomm_send_sabm(struct rfcomm_session *s, u8 dlci)
+{
+ struct rfcomm_cmd cmd;
+
+ BT_DBG("%p dlci %d", s, dlci);
+
+ cmd.addr = __addr(s->initiator, dlci);
+ cmd.ctrl = __ctrl(RFCOMM_SABM, 1);
+ cmd.len = __len8(0);
+ cmd.fcs = __fcs2((u8 *) &cmd);
+
+ return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd));
+}
+
+static int rfcomm_send_ua(struct rfcomm_session *s, u8 dlci)
+{
+ struct rfcomm_cmd cmd;
+
+ BT_DBG("%p dlci %d", s, dlci);
+
+ cmd.addr = __addr(!s->initiator, dlci);
+ cmd.ctrl = __ctrl(RFCOMM_UA, 1);
+ cmd.len = __len8(0);
+ cmd.fcs = __fcs2((u8 *) &cmd);
+
+ return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd));
+}
+
+static int rfcomm_send_disc(struct rfcomm_session *s, u8 dlci)
+{
+ struct rfcomm_cmd cmd;
+
+ BT_DBG("%p dlci %d", s, dlci);
+
+ cmd.addr = __addr(s->initiator, dlci);
+ cmd.ctrl = __ctrl(RFCOMM_DISC, 1);
+ cmd.len = __len8(0);
+ cmd.fcs = __fcs2((u8 *) &cmd);
+
+ return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd));
+}
+
+static int rfcomm_queue_disc(struct rfcomm_dlc *d)
+{
+ struct rfcomm_cmd *cmd;
+ struct sk_buff *skb;
+
+ BT_DBG("dlc %p dlci %d", d, d->dlci);
+
+ skb = alloc_skb(sizeof(*cmd), GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ cmd = (void *) __skb_put(skb, sizeof(*cmd));
+ cmd->addr = d->addr;
+ cmd->ctrl = __ctrl(RFCOMM_DISC, 1);
+ cmd->len = __len8(0);
+ cmd->fcs = __fcs2((u8 *) cmd);
+
+ skb_queue_tail(&d->tx_queue, skb);
+ rfcomm_schedule(RFCOMM_SCHED_TX);
+ return 0;
+}
+
+static int rfcomm_send_dm(struct rfcomm_session *s, u8 dlci)
+{
+ struct rfcomm_cmd cmd;
+
+ BT_DBG("%p dlci %d", s, dlci);
+
+ cmd.addr = __addr(!s->initiator, dlci);
+ cmd.ctrl = __ctrl(RFCOMM_DM, 1);
+ cmd.len = __len8(0);
+ cmd.fcs = __fcs2((u8 *) &cmd);
+
+ return rfcomm_send_frame(s, (void *) &cmd, sizeof(cmd));
+}
+
+static int rfcomm_send_nsc(struct rfcomm_session *s, int cr, u8 type)
+{
+ struct rfcomm_hdr *hdr;
+ struct rfcomm_mcc *mcc;
+ u8 buf[16], *ptr = buf;
+
+ BT_DBG("%p cr %d type %d", s, cr, type);
+
+ hdr = (void *) ptr; ptr += sizeof(*hdr);
+ hdr->addr = __addr(s->initiator, 0);
+ hdr->ctrl = __ctrl(RFCOMM_UIH, 0);
+ hdr->len = __len8(sizeof(*mcc) + 1);
+
+ mcc = (void *) ptr; ptr += sizeof(*mcc);
+ mcc->type = __mcc_type(cr, RFCOMM_NSC);
+ mcc->len = __len8(1);
+
+ /* Type that we didn't like */
+ *ptr = __mcc_type(cr, type); ptr++;
+
+ *ptr = __fcs(buf); ptr++;
+
+ return rfcomm_send_frame(s, buf, ptr - buf);
+}
+
+static int rfcomm_send_pn(struct rfcomm_session *s, int cr, struct rfcomm_dlc *d)
+{
+ struct rfcomm_hdr *hdr;
+ struct rfcomm_mcc *mcc;
+ struct rfcomm_pn *pn;
+ u8 buf[16], *ptr = buf;
+
+ BT_DBG("%p cr %d dlci %d mtu %d", s, cr, d->dlci, d->mtu);
+
+ hdr = (void *) ptr; ptr += sizeof(*hdr);
+ hdr->addr = __addr(s->initiator, 0);
+ hdr->ctrl = __ctrl(RFCOMM_UIH, 0);
+ hdr->len = __len8(sizeof(*mcc) + sizeof(*pn));
+
+ mcc = (void *) ptr; ptr += sizeof(*mcc);
+ mcc->type = __mcc_type(cr, RFCOMM_PN);
+ mcc->len = __len8(sizeof(*pn));
+
+ pn = (void *) ptr; ptr += sizeof(*pn);
+ pn->dlci = d->dlci;
+ pn->priority = d->priority;
+ pn->ack_timer = 0;
+ pn->max_retrans = 0;
+
+ if (s->cfc) {
+ pn->flow_ctrl = cr ? 0xf0 : 0xe0;
+ pn->credits = RFCOMM_DEFAULT_CREDITS;
+ } else {
+ pn->flow_ctrl = 0;
+ pn->credits = 0;
+ }
+
+ pn->mtu = htobs(d->mtu);
+
+ *ptr = __fcs(buf); ptr++;
+
+ return rfcomm_send_frame(s, buf, ptr - buf);
+}
+
+static int rfcomm_send_rpn(struct rfcomm_session *s, int cr, u8 dlci,
+ u8 bit_rate, u8 data_bits, u8 stop_bits,
+ u8 parity, u8 flow_ctrl_settings,
+ u8 xon_char, u8 xoff_char, u16 param_mask)
+{
+ struct rfcomm_hdr *hdr;
+ struct rfcomm_mcc *mcc;
+ struct rfcomm_rpn *rpn;
+ u8 buf[16], *ptr = buf;
+
+ BT_DBG("%p cr %d dlci %d bit_r 0x%x data_b 0x%x stop_b 0x%x parity 0x%x"
+ "flwc_s 0x%x xon_c 0x%x xoff_c 0x%x p_mask 0x%x",
+ s, cr, dlci, bit_rate, data_bits, stop_bits, parity,
+ flow_ctrl_settings, xon_char, xoff_char, param_mask);
+
+ hdr = (void *) ptr; ptr += sizeof(*hdr);
+ hdr->addr = __addr(s->initiator, 0);
+ hdr->ctrl = __ctrl(RFCOMM_UIH, 0);
+ hdr->len = __len8(sizeof(*mcc) + sizeof(*rpn));
+
+ mcc = (void *) ptr; ptr += sizeof(*mcc);
+ mcc->type = __mcc_type(cr, RFCOMM_RPN);
+ mcc->len = __len8(sizeof(*rpn));
+
+ rpn = (void *) ptr; ptr += sizeof(*rpn);
+ rpn->dlci = __addr(1, dlci);
+ rpn->bit_rate = bit_rate;
+ rpn->line_settings = __rpn_line_settings(data_bits, stop_bits, parity);
+ rpn->flow_ctrl = flow_ctrl_settings;
+ rpn->xon_char = xon_char;
+ rpn->xoff_char = xoff_char;
+ rpn->param_mask = param_mask;
+
+ *ptr = __fcs(buf); ptr++;
+
+ return rfcomm_send_frame(s, buf, ptr - buf);
+}
+
+static int rfcomm_send_rls(struct rfcomm_session *s, int cr, u8 dlci, u8 status)
+{
+ struct rfcomm_hdr *hdr;
+ struct rfcomm_mcc *mcc;
+ struct rfcomm_rls *rls;
+ u8 buf[16], *ptr = buf;
+
+ BT_DBG("%p cr %d status 0x%x", s, cr, status);
+
+ hdr = (void *) ptr; ptr += sizeof(*hdr);
+ hdr->addr = __addr(s->initiator, 0);
+ hdr->ctrl = __ctrl(RFCOMM_UIH, 0);
+ hdr->len = __len8(sizeof(*mcc) + sizeof(*rls));
+
+ mcc = (void *) ptr; ptr += sizeof(*mcc);
+ mcc->type = __mcc_type(cr, RFCOMM_RLS);
+ mcc->len = __len8(sizeof(*rls));
+
+ rls = (void *) ptr; ptr += sizeof(*rls);
+ rls->dlci = __addr(1, dlci);
+ rls->status = status;
+
+ *ptr = __fcs(buf); ptr++;
+
+ return rfcomm_send_frame(s, buf, ptr - buf);
+}
+
+static int rfcomm_send_msc(struct rfcomm_session *s, int cr, u8 dlci, u8 v24_sig)
+{
+ struct rfcomm_hdr *hdr;
+ struct rfcomm_mcc *mcc;
+ struct rfcomm_msc *msc;
+ u8 buf[16], *ptr = buf;
+
+ BT_DBG("%p cr %d v24 0x%x", s, cr, v24_sig);
+
+ hdr = (void *) ptr; ptr += sizeof(*hdr);
+ hdr->addr = __addr(s->initiator, 0);
+ hdr->ctrl = __ctrl(RFCOMM_UIH, 0);
+ hdr->len = __len8(sizeof(*mcc) + sizeof(*msc));
+
+ mcc = (void *) ptr; ptr += sizeof(*mcc);
+ mcc->type = __mcc_type(cr, RFCOMM_MSC);
+ mcc->len = __len8(sizeof(*msc));
+
+ msc = (void *) ptr; ptr += sizeof(*msc);
+ msc->dlci = __addr(1, dlci);
+ msc->v24_sig = v24_sig | 0x01;
+
+ *ptr = __fcs(buf); ptr++;
+
+ return rfcomm_send_frame(s, buf, ptr - buf);
+}
+
+static int rfcomm_send_fcoff(struct rfcomm_session *s, int cr)
+{
+ struct rfcomm_hdr *hdr;
+ struct rfcomm_mcc *mcc;
+ u8 buf[16], *ptr = buf;
+
+ BT_DBG("%p cr %d", s, cr);
+
+ hdr = (void *) ptr; ptr += sizeof(*hdr);
+ hdr->addr = __addr(s->initiator, 0);
+ hdr->ctrl = __ctrl(RFCOMM_UIH, 0);
+ hdr->len = __len8(sizeof(*mcc));
+
+ mcc = (void *) ptr; ptr += sizeof(*mcc);
+ mcc->type = __mcc_type(cr, RFCOMM_FCOFF);
+ mcc->len = __len8(0);
+
+ *ptr = __fcs(buf); ptr++;
+
+ return rfcomm_send_frame(s, buf, ptr - buf);
+}
+
+static int rfcomm_send_fcon(struct rfcomm_session *s, int cr)
+{
+ struct rfcomm_hdr *hdr;
+ struct rfcomm_mcc *mcc;
+ u8 buf[16], *ptr = buf;
+
+ BT_DBG("%p cr %d", s, cr);
+
+ hdr = (void *) ptr; ptr += sizeof(*hdr);
+ hdr->addr = __addr(s->initiator, 0);
+ hdr->ctrl = __ctrl(RFCOMM_UIH, 0);
+ hdr->len = __len8(sizeof(*mcc));
+
+ mcc = (void *) ptr; ptr += sizeof(*mcc);
+ mcc->type = __mcc_type(cr, RFCOMM_FCON);
+ mcc->len = __len8(0);
+
+ *ptr = __fcs(buf); ptr++;
+
+ return rfcomm_send_frame(s, buf, ptr - buf);
+}
+
+static int rfcomm_send_test(struct rfcomm_session *s, int cr, u8 *pattern, int len)
+{
+ struct socket *sock = s->sock;
+ struct iovec iv[3];
+ struct msghdr msg;
+ unsigned char hdr[5], crc[1];
+
+ if (len > 125)
+ return -EINVAL;
+
+ BT_DBG("%p cr %d", s, cr);
+
+ hdr[0] = __addr(s->initiator, 0);
+ hdr[1] = __ctrl(RFCOMM_UIH, 0);
+ hdr[2] = 0x01 | ((len + 2) << 1);
+ hdr[3] = 0x01 | ((cr & 0x01) << 1) | (RFCOMM_TEST << 2);
+ hdr[4] = 0x01 | (len << 1);
+
+ crc[0] = __fcs(hdr);
+
+ iv[0].iov_base = hdr;
+ iv[0].iov_len = 5;
+ iv[1].iov_base = pattern;
+ iv[1].iov_len = len;
+ iv[2].iov_base = crc;
+ iv[2].iov_len = 1;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iovlen = 3;
+ msg.msg_iov = iv;
+ return sock->ops->sendmsg(sock, &msg, 6 + len, 0);
+}
+
+static int rfcomm_send_credits(struct rfcomm_session *s, u8 addr, u8 credits)
+{
+ struct rfcomm_hdr *hdr;
+ u8 buf[16], *ptr = buf;
+
+ BT_DBG("%p addr %d credits %d", s, addr, credits);
+
+ hdr = (void *) ptr; ptr += sizeof(*hdr);
+ hdr->addr = addr;
+ hdr->ctrl = __ctrl(RFCOMM_UIH, 1);
+ hdr->len = __len8(0);
+
+ *ptr = credits; ptr++;
+
+ *ptr = __fcs(buf); ptr++;
+
+ return rfcomm_send_frame(s, buf, ptr - buf);
+}
+
+static void rfcomm_make_uih(struct sk_buff *skb, u8 addr)
+{
+ struct rfcomm_hdr *hdr;
+ int len = skb->len;
+ u8 *crc;
+
+ if (len > 127) {
+ hdr = (void *) skb_push(skb, 4);
+ put_unaligned(htobs(__len16(len)), (u16 *) &hdr->len);
+ } else {
+ hdr = (void *) skb_push(skb, 3);
+ hdr->len = __len8(len);
+ }
+ hdr->addr = addr;
+ hdr->ctrl = __ctrl(RFCOMM_UIH, 0);
+
+ crc = skb_put(skb, 1);
+ *crc = __fcs((void *) hdr);
+}
+
+/* ---- RFCOMM frame reception ---- */
+static int rfcomm_recv_ua(struct rfcomm_session *s, u8 dlci)
+{
+ BT_DBG("session %p state %ld dlci %d", s, s->state, dlci);
+
+ if (dlci) {
+ /* Data channel */
+ struct rfcomm_dlc *d = rfcomm_dlc_get(s, dlci);
+ if (!d) {
+ rfcomm_send_dm(s, dlci);
+ return 0;
+ }
+
+ switch (d->state) {
+ case BT_CONNECT:
+ rfcomm_dlc_clear_timer(d);
+
+ rfcomm_dlc_lock(d);
+ d->state = BT_CONNECTED;
+ d->state_change(d, 0);
+ rfcomm_dlc_unlock(d);
+
+ rfcomm_send_msc(s, 1, dlci, d->v24_sig);
+ break;
+
+ case BT_DISCONN:
+ d->state = BT_CLOSED;
+ __rfcomm_dlc_close(d, 0);
+ break;
+ }
+ } else {
+ /* Control channel */
+ switch (s->state) {
+ case BT_CONNECT:
+ s->state = BT_CONNECTED;
+ rfcomm_process_connect(s);
+ break;
+ }
+ }
+ return 0;
+}
+
+static int rfcomm_recv_dm(struct rfcomm_session *s, u8 dlci)
+{
+ int err = 0;
+
+ BT_DBG("session %p state %ld dlci %d", s, s->state, dlci);
+
+ if (dlci) {
+ /* Data DLC */
+ struct rfcomm_dlc *d = rfcomm_dlc_get(s, dlci);
+ if (d) {
+ if (d->state == BT_CONNECT || d->state == BT_CONFIG)
+ err = ECONNREFUSED;
+ else
+ err = ECONNRESET;
+
+ d->state = BT_CLOSED;
+ __rfcomm_dlc_close(d, err);
+ }
+ } else {
+ if (s->state == BT_CONNECT)
+ err = ECONNREFUSED;
+ else
+ err = ECONNRESET;
+
+ s->state = BT_CLOSED;
+ rfcomm_session_close(s, err);
+ }
+ return 0;
+}
+
+static int rfcomm_recv_disc(struct rfcomm_session *s, u8 dlci)
+{
+ int err = 0;
+
+ BT_DBG("session %p state %ld dlci %d", s, s->state, dlci);
+
+ if (dlci) {
+ struct rfcomm_dlc *d = rfcomm_dlc_get(s, dlci);
+ if (d) {
+ rfcomm_send_ua(s, dlci);
+
+ if (d->state == BT_CONNECT || d->state == BT_CONFIG)
+ err = ECONNREFUSED;
+ else
+ err = ECONNRESET;
+
+ d->state = BT_CLOSED;
+ __rfcomm_dlc_close(d, err);
+ } else
+ rfcomm_send_dm(s, dlci);
+
+ } else {
+ rfcomm_send_ua(s, 0);
+
+ if (s->state == BT_CONNECT)
+ err = ECONNREFUSED;
+ else
+ err = ECONNRESET;
+
+ s->state = BT_CLOSED;
+ rfcomm_session_close(s, err);
+ }
+
+ return 0;
+}
+
+static int rfcomm_recv_sabm(struct rfcomm_session *s, u8 dlci)
+{
+ struct rfcomm_dlc *d;
+ u8 channel;
+
+ BT_DBG("session %p state %ld dlci %d", s, s->state, dlci);
+
+ if (!dlci) {
+ rfcomm_send_ua(s, 0);
+
+ if (s->state == BT_OPEN) {
+ s->state = BT_CONNECTED;
+ rfcomm_process_connect(s);
+ }
+ return 0;
+ }
+
+ /* Check if DLC exists */
+ d = rfcomm_dlc_get(s, dlci);
+ if (d) {
+ if (d->state == BT_OPEN) {
+ /* DLC was previously opened by PN request */
+ rfcomm_send_ua(s, dlci);
+
+ rfcomm_dlc_lock(d);
+ d->state = BT_CONNECTED;
+ d->state_change(d, 0);
+ rfcomm_dlc_unlock(d);
+
+ rfcomm_send_msc(s, 1, dlci, d->v24_sig);
+ }
+ return 0;
+ }
+
+ /* Notify socket layer about incomming connection */
+ channel = __srv_channel(dlci);
+ if (rfcomm_connect_ind(s, channel, &d)) {
+ d->dlci = dlci;
+ d->addr = __addr(s->initiator, dlci);
+ rfcomm_dlc_link(s, d);
+
+ rfcomm_send_ua(s, dlci);
+
+ rfcomm_dlc_lock(d);
+ d->state = BT_CONNECTED;
+ d->state_change(d, 0);
+ rfcomm_dlc_unlock(d);
+
+ rfcomm_send_msc(s, 1, dlci, d->v24_sig);
+ } else {
+ rfcomm_send_dm(s, dlci);
+ }
+
+ return 0;
+}
+
+static int rfcomm_apply_pn(struct rfcomm_dlc *d, int cr, struct rfcomm_pn *pn)
+{
+ struct rfcomm_session *s = d->session;
+
+ BT_DBG("dlc %p state %ld dlci %d mtu %d fc 0x%x credits %d",
+ d, d->state, d->dlci, pn->mtu, pn->flow_ctrl, pn->credits);
+
+ if (pn->flow_ctrl == 0xf0 || pn->flow_ctrl == 0xe0) {
+ d->cfc = s->cfc = RFCOMM_CFC_ENABLED;
+ d->tx_credits = pn->credits;
+ } else {
+ d->cfc = s->cfc = RFCOMM_CFC_DISABLED;
+ set_bit(RFCOMM_TX_THROTTLED, &d->flags);
+ }
+
+ d->priority = pn->priority;
+
+ d->mtu = s->mtu = btohs(pn->mtu);
+
+ return 0;
+}
+
+static int rfcomm_recv_pn(struct rfcomm_session *s, int cr, struct sk_buff *skb)
+{
+ struct rfcomm_pn *pn = (void *) skb->data;
+ struct rfcomm_dlc *d;
+ u8 dlci = pn->dlci;
+
+ BT_DBG("session %p state %ld dlci %d", s, s->state, dlci);
+
+ if (!dlci)
+ return 0;
+
+ d = rfcomm_dlc_get(s, dlci);
+ if (d) {
+ if (cr) {
+ /* PN request */
+ rfcomm_apply_pn(d, cr, pn);
+ rfcomm_send_pn(s, 0, d);
+ } else {
+ /* PN response */
+ switch (d->state) {
+ case BT_CONFIG:
+ rfcomm_apply_pn(d, cr, pn);
+
+ d->state = BT_CONNECT;
+ rfcomm_send_sabm(s, d->dlci);
+ break;
+ }
+ }
+ } else {
+ u8 channel = __srv_channel(dlci);
+
+ if (!cr)
+ return 0;
+
+ /* PN request for non existing DLC.
+ * Assume incomming connection. */
+ if (rfcomm_connect_ind(s, channel, &d)) {
+ d->dlci = dlci;
+ d->addr = __addr(s->initiator, dlci);
+ rfcomm_dlc_link(s, d);
+
+ rfcomm_apply_pn(d, cr, pn);
+
+ d->state = BT_OPEN;
+ rfcomm_send_pn(s, 0, d);
+ } else {
+ rfcomm_send_dm(s, dlci);
+ }
+ }
+ return 0;
+}
+
+static int rfcomm_recv_rpn(struct rfcomm_session *s, int cr, int len, struct sk_buff *skb)
+{
+ struct rfcomm_rpn *rpn = (void *) skb->data;
+ u8 dlci = __get_dlci(rpn->dlci);
+
+ u8 bit_rate = 0;
+ u8 data_bits = 0;
+ u8 stop_bits = 0;
+ u8 parity = 0;
+ u8 flow_ctrl = 0;
+ u8 xon_char = 0;
+ u8 xoff_char = 0;
+ u16 rpn_mask = RFCOMM_RPN_PM_ALL;
+
+ BT_DBG("dlci %d cr %d len 0x%x bitr 0x%x line 0x%x flow 0x%x xonc 0x%x xoffc 0x%x pm 0x%x",
+ dlci, cr, len, rpn->bit_rate, rpn->line_settings, rpn->flow_ctrl,
+ rpn->xon_char, rpn->xoff_char, rpn->param_mask);
+
+ if (!cr)
+ return 0;
+
+ if (len == 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_ctrl = RFCOMM_RPN_FLOW_NONE;
+ xon_char = RFCOMM_RPN_XON_CHAR;
+ xoff_char = RFCOMM_RPN_XOFF_CHAR;
+
+ goto rpn_out;
+ }
+ /* check for sane values: ignore/accept bit_rate, 8 bits, 1 stop bit, no parity,
+ no flow control lines, normal XON/XOFF chars */
+ if (rpn->param_mask & RFCOMM_RPN_PM_BITRATE) {
+ bit_rate = rpn->bit_rate;
+ if (bit_rate != RFCOMM_RPN_BR_115200) {
+ BT_DBG("RPN bit rate mismatch 0x%x", bit_rate);
+ bit_rate = RFCOMM_RPN_BR_115200;
+ rpn_mask ^= RFCOMM_RPN_PM_BITRATE;
+ }
+ }
+ if (rpn->param_mask & RFCOMM_RPN_PM_DATA) {
+ data_bits = __get_rpn_data_bits(rpn->line_settings);
+ if (data_bits != RFCOMM_RPN_DATA_8) {
+ BT_DBG("RPN data bits mismatch 0x%x", data_bits);
+ data_bits = RFCOMM_RPN_DATA_8;
+ rpn_mask ^= RFCOMM_RPN_PM_DATA;
+ }
+ }
+ if (rpn->param_mask & RFCOMM_RPN_PM_STOP) {
+ stop_bits = __get_rpn_stop_bits(rpn->line_settings);
+ if (stop_bits != RFCOMM_RPN_STOP_1) {
+ BT_DBG("RPN stop bits mismatch 0x%x", stop_bits);
+ stop_bits = RFCOMM_RPN_STOP_1;
+ rpn_mask ^= RFCOMM_RPN_PM_STOP;
+ }
+ }
+ if (rpn->param_mask & RFCOMM_RPN_PM_PARITY) {
+ parity = __get_rpn_parity(rpn->line_settings);
+ if (parity != RFCOMM_RPN_PARITY_NONE) {
+ BT_DBG("RPN parity mismatch 0x%x", parity);
+ parity = RFCOMM_RPN_PARITY_NONE;
+ rpn_mask ^= RFCOMM_RPN_PM_PARITY;
+ }
+ }
+ if (rpn->param_mask & RFCOMM_RPN_PM_FLOW) {
+ flow_ctrl = rpn->flow_ctrl;
+ if (flow_ctrl != RFCOMM_RPN_FLOW_NONE) {
+ BT_DBG("RPN flow ctrl mismatch 0x%x", flow_ctrl);
+ flow_ctrl = RFCOMM_RPN_FLOW_NONE;
+ rpn_mask ^= RFCOMM_RPN_PM_FLOW;
+ }
+ }
+ if (rpn->param_mask & RFCOMM_RPN_PM_XON) {
+ xon_char = rpn->xon_char;
+ if (xon_char != RFCOMM_RPN_XON_CHAR) {
+ BT_DBG("RPN XON char mismatch 0x%x", xon_char);
+ xon_char = RFCOMM_RPN_XON_CHAR;
+ rpn_mask ^= RFCOMM_RPN_PM_XON;
+ }
+ }
+ if (rpn->param_mask & RFCOMM_RPN_PM_XOFF) {
+ xoff_char = rpn->xoff_char;
+ if (xoff_char != RFCOMM_RPN_XOFF_CHAR) {
+ BT_DBG("RPN XOFF char mismatch 0x%x", xoff_char);
+ xoff_char = RFCOMM_RPN_XOFF_CHAR;
+ rpn_mask ^= RFCOMM_RPN_PM_XOFF;
+ }
+ }
+
+rpn_out:
+ rfcomm_send_rpn(s, 0, dlci,
+ bit_rate, data_bits, stop_bits, parity, flow_ctrl,
+ xon_char, xoff_char, rpn_mask);
+
+ return 0;
+}
+
+static int rfcomm_recv_rls(struct rfcomm_session *s, int cr, struct sk_buff *skb)
+{
+ struct rfcomm_rls *rls = (void *) skb->data;
+ u8 dlci = __get_dlci(rls->dlci);
+
+ BT_DBG("dlci %d cr %d status 0x%x", dlci, cr, rls->status);
+
+ if (!cr)
+ return 0;
+
+ /* FIXME: We should probably do something with this
+ information here. But for now it's sufficient just
+ to reply -- Bluetooth 1.1 says it's mandatory to
+ recognise and respond to RLS */
+
+ rfcomm_send_rls(s, 0, dlci, rls->status);
+
+ return 0;
+}
+
+static int rfcomm_recv_msc(struct rfcomm_session *s, int cr, struct sk_buff *skb)
+{
+ struct rfcomm_msc *msc = (void *) skb->data;
+ struct rfcomm_dlc *d;
+ u8 dlci = __get_dlci(msc->dlci);
+
+ BT_DBG("dlci %d cr %d v24 0x%x", dlci, cr, msc->v24_sig);
+
+ d = rfcomm_dlc_get(s, dlci);
+ if (!d)
+ return 0;
+
+ if (cr) {
+ if (msc->v24_sig & RFCOMM_V24_FC && !d->cfc)
+ set_bit(RFCOMM_TX_THROTTLED, &d->flags);
+ else
+ clear_bit(RFCOMM_TX_THROTTLED, &d->flags);
+
+ rfcomm_dlc_lock(d);
+ if (d->modem_status)
+ d->modem_status(d, msc->v24_sig);
+ rfcomm_dlc_unlock(d);
+
+ rfcomm_send_msc(s, 0, dlci, msc->v24_sig);
+
+ d->mscex |= RFCOMM_MSCEX_RX;
+ } else
+ d->mscex |= RFCOMM_MSCEX_TX;
+
+ return 0;
+}
+
+static int rfcomm_recv_mcc(struct rfcomm_session *s, struct sk_buff *skb)
+{
+ struct rfcomm_mcc *mcc = (void *) skb->data;
+ u8 type, cr, len;
+
+ cr = __test_cr(mcc->type);
+ type = __get_mcc_type(mcc->type);
+ len = __get_mcc_len(mcc->len);
+
+ BT_DBG("%p type 0x%x cr %d", s, type, cr);
+
+ skb_pull(skb, 2);
+
+ switch (type) {
+ case RFCOMM_PN:
+ rfcomm_recv_pn(s, cr, skb);
+ break;
+
+ case RFCOMM_RPN:
+ rfcomm_recv_rpn(s, cr, len, skb);
+ break;
+
+ case RFCOMM_RLS:
+ rfcomm_recv_rls(s, cr, skb);
+ break;
+
+ case RFCOMM_MSC:
+ rfcomm_recv_msc(s, cr, skb);
+ break;
+
+ case RFCOMM_FCOFF:
+ if (cr) {
+ set_bit(RFCOMM_TX_THROTTLED, &s->flags);
+ rfcomm_send_fcoff(s, 0);
+ }
+ break;
+
+ case RFCOMM_FCON:
+ if (cr) {
+ clear_bit(RFCOMM_TX_THROTTLED, &s->flags);
+ rfcomm_send_fcon(s, 0);
+ }
+ break;
+
+ case RFCOMM_TEST:
+ if (cr)
+ rfcomm_send_test(s, 0, skb->data, skb->len);
+ break;
+
+ case RFCOMM_NSC:
+ break;
+
+ default:
+ BT_ERR("Unknown control type 0x%02x", type);
+ rfcomm_send_nsc(s, cr, type);
+ break;
+ }
+ return 0;
+}
+
+static int rfcomm_recv_data(struct rfcomm_session *s, u8 dlci, int pf, struct sk_buff *skb)
+{
+ struct rfcomm_dlc *d;
+
+ BT_DBG("session %p state %ld dlci %d pf %d", s, s->state, dlci, pf);
+
+ d = rfcomm_dlc_get(s, dlci);
+ if (!d) {
+ rfcomm_send_dm(s, dlci);
+ goto drop;
+ }
+
+ if (pf && d->cfc) {
+ u8 credits = *(u8 *) skb->data; skb_pull(skb, 1);
+
+ d->tx_credits += credits;
+ if (d->tx_credits)
+ clear_bit(RFCOMM_TX_THROTTLED, &d->flags);
+ }
+
+ if (skb->len && d->state == BT_CONNECTED) {
+ rfcomm_dlc_lock(d);
+ d->rx_credits--;
+ d->data_ready(d, skb);
+ rfcomm_dlc_unlock(d);
+ return 0;
+ }
+
+drop:
+ kfree_skb(skb);
+ return 0;
+}
+
+static int rfcomm_recv_frame(struct rfcomm_session *s, struct sk_buff *skb)
+{
+ struct rfcomm_hdr *hdr = (void *) skb->data;
+ u8 type, dlci, fcs;
+
+ dlci = __get_dlci(hdr->addr);
+ type = __get_type(hdr->ctrl);
+
+ /* Trim FCS */
+ skb->len--; skb->tail--;
+ fcs = *(u8 *) skb->tail;
+
+ if (__check_fcs(skb->data, type, fcs)) {
+ BT_ERR("bad checksum in packet");
+ kfree_skb(skb);
+ return -EILSEQ;
+ }
+
+ if (__test_ea(hdr->len))
+ skb_pull(skb, 3);
+ else
+ skb_pull(skb, 4);
+
+ switch (type) {
+ case RFCOMM_SABM:
+ if (__test_pf(hdr->ctrl))
+ rfcomm_recv_sabm(s, dlci);
+ break;
+
+ case RFCOMM_DISC:
+ if (__test_pf(hdr->ctrl))
+ rfcomm_recv_disc(s, dlci);
+ break;
+
+ case RFCOMM_UA:
+ if (__test_pf(hdr->ctrl))
+ rfcomm_recv_ua(s, dlci);
+ break;
+
+ case RFCOMM_DM:
+ rfcomm_recv_dm(s, dlci);
+ break;
+
+ case RFCOMM_UIH:
+ if (dlci)
+ return rfcomm_recv_data(s, dlci, __test_pf(hdr->ctrl), skb);
+
+ rfcomm_recv_mcc(s, skb);
+ break;
+
+ default:
+ BT_ERR("Unknown packet type 0x%02x\n", type);
+ break;
+ }
+ kfree_skb(skb);
+ return 0;
+}
+
+/* ---- Connection and data processing ---- */
+
+static void rfcomm_process_connect(struct rfcomm_session *s)
+{
+ struct rfcomm_dlc *d;
+ struct list_head *p, *n;
+
+ BT_DBG("session %p state %ld", s, s->state);
+
+ list_for_each_safe(p, n, &s->dlcs) {
+ d = list_entry(p, struct rfcomm_dlc, list);
+ if (d->state == BT_CONFIG) {
+ d->mtu = s->mtu;
+ rfcomm_send_pn(s, 1, d);
+ }
+ }
+}
+
+/* Send data queued for the DLC.
+ * Return number of frames left in the queue.
+ */
+static inline int rfcomm_process_tx(struct rfcomm_dlc *d)
+{
+ struct sk_buff *skb;
+ int err;
+
+ BT_DBG("dlc %p state %ld cfc %d rx_credits %d tx_credits %d",
+ d, d->state, d->cfc, d->rx_credits, d->tx_credits);
+
+ /* Send pending MSC */
+ if (test_and_clear_bit(RFCOMM_MSC_PENDING, &d->flags))
+ rfcomm_send_msc(d->session, 1, d->dlci, d->v24_sig);
+
+ if (d->cfc) {
+ /* CFC enabled.
+ * Give them some credits */
+ if (!test_bit(RFCOMM_RX_THROTTLED, &d->flags) &&
+ d->rx_credits <= (d->cfc >> 2)) {
+ rfcomm_send_credits(d->session, d->addr, d->cfc - d->rx_credits);
+ d->rx_credits = d->cfc;
+ }
+ } else {
+ /* CFC disabled.
+ * Give ourselves some credits */
+ d->tx_credits = 5;
+ }
+
+ if (test_bit(RFCOMM_TX_THROTTLED, &d->flags))
+ return skb_queue_len(&d->tx_queue);
+
+ while (d->tx_credits && (skb = skb_dequeue(&d->tx_queue))) {
+ err = rfcomm_send_frame(d->session, skb->data, skb->len);
+ if (err < 0) {
+ skb_queue_head(&d->tx_queue, skb);
+ break;
+ }
+ kfree_skb(skb);
+ d->tx_credits--;
+ }
+
+ if (d->cfc && !d->tx_credits) {
+ /* We're out of TX credits.
+ * Set TX_THROTTLED flag to avoid unnesary wakeups by dlc_send. */
+ set_bit(RFCOMM_TX_THROTTLED, &d->flags);
+ }
+
+ return skb_queue_len(&d->tx_queue);
+}
+
+static inline void rfcomm_process_dlcs(struct rfcomm_session *s)
+{
+ struct rfcomm_dlc *d;
+ struct list_head *p, *n;
+
+ BT_DBG("session %p state %ld", s, s->state);
+
+ list_for_each_safe(p, n, &s->dlcs) {
+ d = list_entry(p, struct rfcomm_dlc, list);
+ if (test_bit(RFCOMM_TIMED_OUT, &d->flags)) {
+ __rfcomm_dlc_close(d, ETIMEDOUT);
+ continue;
+ }
+
+ if (test_bit(RFCOMM_TX_THROTTLED, &s->flags))
+ continue;
+
+ if ((d->state == BT_CONNECTED || d->state == BT_DISCONN) &&
+ d->mscex == RFCOMM_MSCEX_OK)
+ rfcomm_process_tx(d);
+ }
+}
+
+static inline void rfcomm_process_rx(struct rfcomm_session *s)
+{
+ struct socket *sock = s->sock;
+ struct sock *sk = sock->sk;
+ struct sk_buff *skb;
+
+ BT_DBG("session %p state %ld qlen %d", s, s->state, skb_queue_len(&sk->receive_queue));
+
+ /* Get data directly from socket receive queue without copying it. */
+ while ((skb = skb_dequeue(&sk->receive_queue))) {
+ skb_orphan(skb);
+ rfcomm_recv_frame(s, skb);
+ }
+
+ if (sk->state == BT_CLOSED) {
+ if (!s->initiator)
+ rfcomm_session_put(s);
+
+ rfcomm_session_close(s, sk->err);
+ }
+}
+
+static inline void rfcomm_accept_connection(struct rfcomm_session *s)
+{
+ struct socket *sock = s->sock, *nsock;
+ int err;
+
+ /* Fast check for a new connection.
+ * Avoids unnesesary socket allocations. */
+ if (list_empty(&bluez_pi(sock->sk)->accept_q))
+ return;
+
+ BT_DBG("session %p", s);
+
+ nsock = sock_alloc();
+ if (!nsock)
+ return;
+
+ nsock->type = sock->type;
+ nsock->ops = sock->ops;
+
+ err = sock->ops->accept(sock, nsock, O_NONBLOCK);
+ if (err < 0) {
+ sock_release(nsock);
+ return;
+ }
+
+ /* Set our callbacks */
+ nsock->sk->data_ready = rfcomm_l2data_ready;
+ nsock->sk->state_change = rfcomm_l2state_change;
+
+ s = rfcomm_session_add(nsock, BT_OPEN);
+ if (s) {
+ rfcomm_session_hold(s);
+ rfcomm_schedule(RFCOMM_SCHED_RX);
+ } else
+ sock_release(nsock);
+}
+
+static inline void rfcomm_check_connection(struct rfcomm_session *s)
+{
+ struct sock *sk = s->sock->sk;
+
+ BT_DBG("%p state %ld", s, s->state);
+
+ switch(sk->state) {
+ case BT_CONNECTED:
+ s->state = BT_CONNECT;
+
+ /* We can adjust MTU on outgoing sessions.
+ * L2CAP MTU minus UIH header and FCS. */
+ s->mtu = min(l2cap_pi(sk)->omtu, l2cap_pi(sk)->imtu) - 5;
+
+ rfcomm_send_sabm(s, 0);
+ break;
+
+ case BT_CLOSED:
+ s->state = BT_CLOSED;
+ rfcomm_session_close(s, sk->err);
+ break;
+ }
+}
+
+static inline void rfcomm_process_sessions(void)
+{
+ struct list_head *p, *n;
+
+ rfcomm_lock();
+
+ list_for_each_safe(p, n, &session_list) {
+ struct rfcomm_session *s;
+ s = list_entry(p, struct rfcomm_session, list);
+
+ if (s->state == BT_LISTEN) {
+ rfcomm_accept_connection(s);
+ continue;
+ }
+
+ rfcomm_session_hold(s);
+
+ switch (s->state) {
+ case BT_BOUND:
+ rfcomm_check_connection(s);
+ break;
+
+ default:
+ rfcomm_process_rx(s);
+ break;
+ }
+
+ rfcomm_process_dlcs(s);
+
+ rfcomm_session_put(s);
+ }
+
+ rfcomm_unlock();
+}
+
+static void rfcomm_worker(void)
+{
+ BT_DBG("");
+
+ daemonize(); reparent_to_init();
+ set_fs(KERNEL_DS);
+
+ while (!atomic_read(&terminate)) {
+ BT_DBG("worker loop event 0x%lx", rfcomm_event);
+
+ if (!test_bit(RFCOMM_SCHED_WAKEUP, &rfcomm_event)) {
+ /* No pending events. Let's sleep.
+ * Incomming connections and data will wake us up. */
+ set_current_state(TASK_INTERRUPTIBLE);
+ schedule();
+ }
+
+ /* Process stuff */
+ clear_bit(RFCOMM_SCHED_WAKEUP, &rfcomm_event);
+ rfcomm_process_sessions();
+ }
+ set_current_state(TASK_RUNNING);
+ return;
+}
+
+static int rfcomm_add_listener(bdaddr_t *ba)
+{
+ struct sockaddr_l2 addr;
+ struct l2cap_options opts;
+ struct socket *sock;
+ struct rfcomm_session *s;
+ int size, err = 0;
+
+ /* Create socket */
+ err = rfcomm_l2sock_create(&sock);
+ if (err < 0) {
+ BT_ERR("Create socket failed %d", err);
+ return err;
+ }
+
+ /* Bind socket */
+ bacpy(&addr.l2_bdaddr, ba);
+ addr.l2_family = AF_BLUETOOTH;
+ addr.l2_psm = htobs(RFCOMM_PSM);
+ err = sock->ops->bind(sock, (struct sockaddr *) &addr, sizeof(addr));
+ if (err < 0) {
+ BT_ERR("Bind failed %d", err);
+ goto failed;
+ }
+
+ /* Set L2CAP options */
+ size = sizeof(opts);
+ sock->ops->getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, (void *)&opts, &size);
+
+ opts.imtu = RFCOMM_MAX_L2CAP_MTU;
+ sock->ops->setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, (void *)&opts, size);
+
+ /* Start listening on the socket */
+ err = sock->ops->listen(sock, 10);
+ if (err) {
+ BT_ERR("Listen failed %d", err);
+ goto failed;
+ }
+
+ /* Add listening session */
+ s = rfcomm_session_add(sock, BT_LISTEN);
+ if (!s)
+ goto failed;
+
+ rfcomm_session_hold(s);
+ return 0;
+failed:
+ sock_release(sock);
+ return err;
+}
+
+static void rfcomm_kill_listener(void)
+{
+ struct rfcomm_session *s;
+ struct list_head *p, *n;
+
+ BT_DBG("");
+
+ list_for_each_safe(p, n, &session_list) {
+ s = list_entry(p, struct rfcomm_session, list);
+ rfcomm_session_del(s);
+ }
+}
+
+static int rfcomm_run(void *unused)
+{
+ rfcomm_thread = current;
+
+ atomic_inc(&running);
+
+ daemonize(); reparent_to_init();
+
+ sigfillset(&current->blocked);
+ set_fs(KERNEL_DS);
+
+ sprintf(current->comm, "krfcommd");
+
+ BT_DBG("");
+
+ rfcomm_add_listener(BDADDR_ANY);
+
+ rfcomm_worker();
+
+ rfcomm_kill_listener();
+
+ atomic_dec(&running);
+ return 0;
+}
+
+/* ---- Proc fs support ---- */
+static int rfcomm_dlc_dump(char *buf)
+{
+ struct rfcomm_session *s;
+ struct sock *sk;
+ struct list_head *p, *pp;
+ char *ptr = buf;
+
+ rfcomm_lock();
+
+ list_for_each(p, &session_list) {
+ s = list_entry(p, struct rfcomm_session, list);
+ sk = s->sock->sk;
+
+ list_for_each(pp, &s->dlcs) {
+ struct rfcomm_dlc *d;
+ d = list_entry(pp, struct rfcomm_dlc, list);
+
+ ptr += sprintf(ptr, "dlc %s %s %ld %d %d %d %d\n",
+ batostr(&bluez_pi(sk)->src), batostr(&bluez_pi(sk)->dst),
+ d->state, d->dlci, d->mtu, d->rx_credits, d->tx_credits);
+ }
+ }
+
+ rfcomm_unlock();
+
+ return ptr - buf;
+}
+
+extern int rfcomm_sock_dump(char *buf);
+
+static int rfcomm_read_proc(char *buf, char **start, off_t offset, int count, int *eof, void *priv)
+{
+ char *ptr = buf;
+ int len;
+
+ BT_DBG("count %d, offset %ld", count, offset);
+
+ ptr += rfcomm_dlc_dump(ptr);
+ ptr += rfcomm_sock_dump(ptr);
+ len = ptr - buf;
+
+ if (len <= count + offset)
+ *eof = 1;
+
+ *start = buf + offset;
+ len -= offset;
+
+ if (len > count)
+ len = count;
+ if (len < 0)
+ len = 0;
+
+ return len;
+}
+
+/* ---- Initialization ---- */
+int __init rfcomm_init(void)
+{
+ l2cap_load();
+
+ kernel_thread(rfcomm_run, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
+
+ rfcomm_init_sockets();
+
+#ifdef CONFIG_BLUEZ_RFCOMM_TTY
+ rfcomm_init_ttys();
+#endif
+
+ create_proc_read_entry("bluetooth/rfcomm", 0, 0, rfcomm_read_proc, NULL);
+
+ BT_INFO("BlueZ RFCOMM ver %s", VERSION);
+ BT_INFO("Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com>");
+ BT_INFO("Copyright (C) 2002 Marcel Holtmann <marcel@holtmann.org>");
+ return 0;
+}
+
+void rfcomm_cleanup(void)
+{
+ /* Terminate working thread.
+ * ie. Set terminate flag and wake it up */
+ atomic_inc(&terminate);
+ rfcomm_schedule(RFCOMM_SCHED_STATE);
+
+ /* Wait until thread is running */
+ while (atomic_read(&running))
+ schedule();
+
+ remove_proc_entry("bluetooth/rfcomm", NULL);
+
+#ifdef CONFIG_BLUEZ_RFCOMM_TTY
+ rfcomm_cleanup_ttys();
+#endif
+
+ rfcomm_cleanup_sockets();
+ return;
+}
+
+module_init(rfcomm_init);
+module_exit(rfcomm_cleanup);
+
+MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>, Marcel Holtmann <marcel@holtmann.org>");
+MODULE_DESCRIPTION("BlueZ RFCOMM ver " VERSION);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/crc.c b/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/crc.c
new file mode 100644
index 0000000..1011bc4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/crc.c
@@ -0,0 +1,71 @@
+/*
+ RFCOMM implementation for Linux Bluetooth stack (BlueZ).
+ Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com>
+ Copyright (C) 2002 Marcel Holtmann <marcel@holtmann.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * RFCOMM FCS calculation.
+ *
+ * $Id: crc.c,v 1.2 2002/09/21 09:54:32 holtmann Exp $
+ */
+
+/* reversed, 8-bit, poly=0x07 */
+unsigned char 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
+};
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/sock.c b/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/sock.c
new file mode 100644
index 0000000..d011d3f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/sock.c
@@ -0,0 +1,847 @@
+/*
+ RFCOMM implementation for Linux Bluetooth stack (BlueZ).
+ Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com>
+ Copyright (C) 2002 Marcel Holtmann <marcel@holtmann.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * RFCOMM sockets.
+ *
+ * $Id: sock.c,v 1.30 2002/10/18 20:12:12 maxk Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/interrupt.h>
+#include <linux/socket.h>
+#include <linux/skbuff.h>
+#include <linux/list.h>
+#include <net/sock.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/rfcomm.h>
+
+#ifndef CONFIG_BLUEZ_RFCOMM_DEBUG
+#undef BT_DBG
+#define BT_DBG(D...)
+#endif
+
+static struct proto_ops rfcomm_sock_ops;
+
+static struct bluez_sock_list rfcomm_sk_list = {
+ lock: RW_LOCK_UNLOCKED
+};
+
+static void rfcomm_sock_close(struct sock *sk);
+static void rfcomm_sock_kill(struct sock *sk);
+
+/* ---- DLC callbacks ----
+ *
+ * called under rfcomm_dlc_lock()
+ */
+static void rfcomm_sk_data_ready(struct rfcomm_dlc *d, struct sk_buff *skb)
+{
+ struct sock *sk = d->owner;
+ if (!sk)
+ return;
+
+ atomic_add(skb->len, &sk->rmem_alloc);
+ skb_queue_tail(&sk->receive_queue, skb);
+ sk->data_ready(sk, skb->len);
+
+ if (atomic_read(&sk->rmem_alloc) >= sk->rcvbuf)
+ rfcomm_dlc_throttle(d);
+}
+
+static void rfcomm_sk_state_change(struct rfcomm_dlc *d, int err)
+{
+ struct sock *sk = d->owner, *parent;
+ if (!sk)
+ return;
+
+ BT_DBG("dlc %p state %ld err %d", d, d->state, err);
+
+ bh_lock_sock(sk);
+
+ if (err)
+ sk->err = err;
+ sk->state = d->state;
+
+ parent = bluez_pi(sk)->parent;
+ if (!parent) {
+ if (d->state == BT_CONNECTED)
+ rfcomm_session_getaddr(d->session, &bluez_pi(sk)->src, NULL);
+ sk->state_change(sk);
+ } else
+ parent->data_ready(parent, 0);
+
+ bh_unlock_sock(sk);
+}
+
+/* ---- Socket functions ---- */
+static struct sock *__rfcomm_get_sock_by_addr(u8 channel, bdaddr_t *src)
+{
+ struct sock *sk;
+
+ for (sk = rfcomm_sk_list.head; sk; sk = sk->next) {
+ if (rfcomm_pi(sk)->channel == channel &&
+ !bacmp(&bluez_pi(sk)->src, src))
+ break;
+ }
+
+ return sk;
+}
+
+/* Find socket with channel and source bdaddr.
+ * Returns closest match.
+ */
+static struct sock *__rfcomm_get_sock_by_channel(int state, u8 channel, bdaddr_t *src)
+{
+ struct sock *sk, *sk1 = NULL;
+
+ for (sk = rfcomm_sk_list.head; sk; sk = sk->next) {
+ if (state && sk->state != state)
+ continue;
+
+ if (rfcomm_pi(sk)->channel == channel) {
+ /* Exact match. */
+ if (!bacmp(&bluez_pi(sk)->src, src))
+ break;
+
+ /* Closest match */
+ if (!bacmp(&bluez_pi(sk)->src, BDADDR_ANY))
+ sk1 = sk;
+ }
+ }
+ return sk ? sk : sk1;
+}
+
+/* Find socket with given address (channel, src).
+ * Returns locked socket */
+static inline struct sock *rfcomm_get_sock_by_channel(int state, u8 channel, bdaddr_t *src)
+{
+ struct sock *s;
+ read_lock(&rfcomm_sk_list.lock);
+ s = __rfcomm_get_sock_by_channel(state, channel, src);
+ if (s) bh_lock_sock(s);
+ read_unlock(&rfcomm_sk_list.lock);
+ return s;
+}
+
+static void rfcomm_sock_destruct(struct sock *sk)
+{
+ struct rfcomm_dlc *d = rfcomm_pi(sk)->dlc;
+
+ BT_DBG("sk %p dlc %p", sk, d);
+
+ skb_queue_purge(&sk->receive_queue);
+ skb_queue_purge(&sk->write_queue);
+
+ rfcomm_dlc_lock(d);
+ rfcomm_pi(sk)->dlc = NULL;
+
+ /* Detach DLC if it's owned by this socket */
+ if (d->owner == sk)
+ d->owner = NULL;
+ rfcomm_dlc_unlock(d);
+
+ rfcomm_dlc_put(d);
+
+ MOD_DEC_USE_COUNT;
+}
+
+static void rfcomm_sock_cleanup_listen(struct sock *parent)
+{
+ struct sock *sk;
+
+ BT_DBG("parent %p", parent);
+
+ /* Close not yet accepted dlcs */
+ while ((sk = bluez_accept_dequeue(parent, NULL))) {
+ rfcomm_sock_close(sk);
+ rfcomm_sock_kill(sk);
+ }
+
+ parent->state = BT_CLOSED;
+ parent->zapped = 1;
+}
+
+/* Kill socket (only if zapped and orphan)
+ * Must be called on unlocked socket.
+ */
+static void rfcomm_sock_kill(struct sock *sk)
+{
+ if (!sk->zapped || sk->socket)
+ return;
+
+ BT_DBG("sk %p state %d refcnt %d", sk, sk->state, atomic_read(&sk->refcnt));
+
+ /* Kill poor orphan */
+ bluez_sock_unlink(&rfcomm_sk_list, sk);
+ sk->dead = 1;
+ sock_put(sk);
+}
+
+static void __rfcomm_sock_close(struct sock *sk)
+{
+ struct rfcomm_dlc *d = rfcomm_pi(sk)->dlc;
+
+ BT_DBG("sk %p state %d socket %p", sk, sk->state, sk->socket);
+
+ switch (sk->state) {
+ case BT_LISTEN:
+ rfcomm_sock_cleanup_listen(sk);
+ break;
+
+ case BT_CONNECT:
+ case BT_CONNECT2:
+ case BT_CONFIG:
+ case BT_CONNECTED:
+ rfcomm_dlc_close(d, 0);
+
+ default:
+ sk->zapped = 1;
+ break;
+ }
+}
+
+/* Close socket.
+ * Must be called on unlocked socket.
+ */
+static void rfcomm_sock_close(struct sock *sk)
+{
+ lock_sock(sk);
+ __rfcomm_sock_close(sk);
+ release_sock(sk);
+}
+
+static void rfcomm_sock_init(struct sock *sk, struct sock *parent)
+{
+ BT_DBG("sk %p", sk);
+
+ if (parent)
+ sk->type = parent->type;
+}
+
+static struct sock *rfcomm_sock_alloc(struct socket *sock, int proto, int prio)
+{
+ struct rfcomm_dlc *d;
+ struct sock *sk;
+
+ sk = sk_alloc(PF_BLUETOOTH, prio, 1);
+ if (!sk)
+ return NULL;
+
+ d = rfcomm_dlc_alloc(prio);
+ if (!d) {
+ sk_free(sk);
+ return NULL;
+ }
+ d->data_ready = rfcomm_sk_data_ready;
+ d->state_change = rfcomm_sk_state_change;
+
+ rfcomm_pi(sk)->dlc = d;
+ d->owner = sk;
+
+ bluez_sock_init(sock, sk);
+
+ sk->zapped = 0;
+
+ sk->destruct = rfcomm_sock_destruct;
+ sk->sndtimeo = RFCOMM_CONN_TIMEOUT;
+
+ sk->sndbuf = RFCOMM_MAX_CREDITS * RFCOMM_DEFAULT_MTU * 10;
+ sk->rcvbuf = RFCOMM_MAX_CREDITS * RFCOMM_DEFAULT_MTU * 10;
+
+ sk->protocol = proto;
+ sk->state = BT_OPEN;
+
+ bluez_sock_link(&rfcomm_sk_list, sk);
+
+ BT_DBG("sk %p", sk);
+
+ MOD_INC_USE_COUNT;
+ return sk;
+}
+
+static int rfcomm_sock_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+
+ BT_DBG("sock %p", sock);
+
+ sock->state = SS_UNCONNECTED;
+
+ if (sock->type != SOCK_STREAM && sock->type != SOCK_RAW)
+ return -ESOCKTNOSUPPORT;
+
+ sock->ops = &rfcomm_sock_ops;
+
+ if (!(sk = rfcomm_sock_alloc(sock, protocol, GFP_KERNEL)))
+ return -ENOMEM;
+
+ rfcomm_sock_init(sk, NULL);
+ return 0;
+}
+
+static int rfcomm_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
+{
+ struct sockaddr_rc *sa = (struct sockaddr_rc *) addr;
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ BT_DBG("sk %p %s", sk, batostr(&sa->rc_bdaddr));
+
+ if (!addr || addr->sa_family != AF_BLUETOOTH)
+ return -EINVAL;
+
+ lock_sock(sk);
+
+ if (sk->state != BT_OPEN) {
+ err = -EBADFD;
+ goto done;
+ }
+
+ write_lock_bh(&rfcomm_sk_list.lock);
+
+ if (sa->rc_channel && __rfcomm_get_sock_by_addr(sa->rc_channel, &sa->rc_bdaddr)) {
+ err = -EADDRINUSE;
+ } else {
+ /* Save source address */
+ bacpy(&bluez_pi(sk)->src, &sa->rc_bdaddr);
+ rfcomm_pi(sk)->channel = sa->rc_channel;
+ sk->state = BT_BOUND;
+ }
+
+ write_unlock_bh(&rfcomm_sk_list.lock);
+
+done:
+ release_sock(sk);
+ return err;
+}
+
+static int rfcomm_sock_connect(struct socket *sock, struct sockaddr *addr, int alen, int flags)
+{
+ struct sockaddr_rc *sa = (struct sockaddr_rc *) addr;
+ struct sock *sk = sock->sk;
+ struct rfcomm_dlc *d = rfcomm_pi(sk)->dlc;
+ int err = 0;
+
+ BT_DBG("sk %p", sk);
+
+ if (addr->sa_family != AF_BLUETOOTH || alen < sizeof(struct sockaddr_rc))
+ return -EINVAL;
+
+ if (sk->state != BT_OPEN && sk->state != BT_BOUND)
+ return -EBADFD;
+
+ if (sk->type != SOCK_STREAM)
+ return -EINVAL;
+
+ lock_sock(sk);
+
+ sk->state = BT_CONNECT;
+ bacpy(&bluez_pi(sk)->dst, &sa->rc_bdaddr);
+ rfcomm_pi(sk)->channel = sa->rc_channel;
+
+ err = rfcomm_dlc_open(d, &bluez_pi(sk)->src, &sa->rc_bdaddr, sa->rc_channel);
+ if (!err)
+ err = bluez_sock_wait_state(sk, BT_CONNECTED,
+ sock_sndtimeo(sk, flags & O_NONBLOCK));
+
+ release_sock(sk);
+ return err;
+}
+
+int rfcomm_sock_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ BT_DBG("sk %p backlog %d", sk, backlog);
+
+ lock_sock(sk);
+
+ if (sk->state != BT_BOUND) {
+ err = -EBADFD;
+ goto done;
+ }
+
+ sk->max_ack_backlog = backlog;
+ sk->ack_backlog = 0;
+ sk->state = BT_LISTEN;
+
+done:
+ release_sock(sk);
+ return err;
+}
+
+int rfcomm_sock_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct sock *sk = sock->sk, *nsk;
+ long timeo;
+ int err = 0;
+
+ lock_sock(sk);
+
+ if (sk->state != BT_LISTEN) {
+ err = -EBADFD;
+ goto done;
+ }
+
+ timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
+
+ BT_DBG("sk %p timeo %ld", sk, timeo);
+
+ /* Wait for an incoming connection. (wake-one). */
+ add_wait_queue_exclusive(sk->sleep, &wait);
+ while (!(nsk = bluez_accept_dequeue(sk, newsock))) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (!timeo) {
+ err = -EAGAIN;
+ break;
+ }
+
+ release_sock(sk);
+ timeo = schedule_timeout(timeo);
+ lock_sock(sk);
+
+ if (sk->state != BT_LISTEN) {
+ err = -EBADFD;
+ break;
+ }
+
+ if (signal_pending(current)) {
+ err = sock_intr_errno(timeo);
+ break;
+ }
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(sk->sleep, &wait);
+
+ if (err)
+ goto done;
+
+ newsock->state = SS_CONNECTED;
+
+ BT_DBG("new socket %p", nsk);
+
+done:
+ release_sock(sk);
+ return err;
+}
+
+static int rfcomm_sock_getname(struct socket *sock, struct sockaddr *addr, int *len, int peer)
+{
+ struct sockaddr_rc *sa = (struct sockaddr_rc *) addr;
+ struct sock *sk = sock->sk;
+
+ BT_DBG("sock %p, sk %p", sock, sk);
+
+ sa->rc_family = AF_BLUETOOTH;
+ sa->rc_channel = rfcomm_pi(sk)->channel;
+ if (peer)
+ bacpy(&sa->rc_bdaddr, &bluez_pi(sk)->dst);
+ else
+ bacpy(&sa->rc_bdaddr, &bluez_pi(sk)->src);
+
+ *len = sizeof(struct sockaddr_rc);
+ return 0;
+}
+
+static int rfcomm_sock_sendmsg(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct rfcomm_dlc *d = rfcomm_pi(sk)->dlc;
+ struct sk_buff *skb;
+ int err, size;
+ int sent = 0;
+
+ if (msg->msg_flags & MSG_OOB)
+ return -EOPNOTSUPP;
+
+ if (sk->shutdown & SEND_SHUTDOWN)
+ return -EPIPE;
+
+ BT_DBG("sock %p, sk %p", sock, sk);
+
+ lock_sock(sk);
+
+ while (len) {
+ size = min_t(uint, len, d->mtu);
+
+ skb = sock_alloc_send_skb(sk, size + RFCOMM_SKB_RESERVE,
+ msg->msg_flags & MSG_DONTWAIT, &err);
+ if (!skb)
+ break;
+ skb_reserve(skb, RFCOMM_SKB_HEAD_RESERVE);
+
+ err = memcpy_fromiovec(skb_put(skb, size), msg->msg_iov, size);
+ if (err) {
+ kfree_skb(skb);
+ sent = err;
+ break;
+ }
+
+ err = rfcomm_dlc_send(d, skb);
+ if (err < 0) {
+ kfree_skb(skb);
+ break;
+ }
+
+ sent += size;
+ len -= size;
+ }
+
+ release_sock(sk);
+
+ return sent ? sent : err;
+}
+
+static long rfcomm_sock_data_wait(struct sock *sk, long timeo)
+{
+ DECLARE_WAITQUEUE(wait, current);
+
+ add_wait_queue(sk->sleep, &wait);
+ for (;;) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (skb_queue_len(&sk->receive_queue) || sk->err || (sk->shutdown & RCV_SHUTDOWN) ||
+ signal_pending(current) || !timeo)
+ break;
+
+ set_bit(SOCK_ASYNC_WAITDATA, &sk->socket->flags);
+ release_sock(sk);
+ timeo = schedule_timeout(timeo);
+ lock_sock(sk);
+ clear_bit(SOCK_ASYNC_WAITDATA, &sk->socket->flags);
+ }
+
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(sk->sleep, &wait);
+ return timeo;
+}
+
+static int rfcomm_sock_recvmsg(struct socket *sock, struct msghdr *msg, int size,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ int target, err = 0, copied = 0;
+ long timeo;
+
+ if (flags & MSG_OOB)
+ return -EOPNOTSUPP;
+
+ msg->msg_namelen = 0;
+
+ BT_DBG("sk %p size %d", sk, size);
+
+ lock_sock(sk);
+
+ target = sock_rcvlowat(sk, flags & MSG_WAITALL, size);
+ timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);
+
+ do {
+ struct sk_buff *skb;
+ int chunk;
+
+ skb = skb_dequeue(&sk->receive_queue);
+ if (!skb) {
+ if (copied >= target)
+ break;
+
+ if ((err = sock_error(sk)) != 0)
+ break;
+ if (sk->shutdown & RCV_SHUTDOWN)
+ break;
+
+ err = -EAGAIN;
+ if (!timeo)
+ break;
+
+ timeo = rfcomm_sock_data_wait(sk, timeo);
+
+ if (signal_pending(current)) {
+ err = sock_intr_errno(timeo);
+ goto out;
+ }
+ continue;
+ }
+
+ chunk = min_t(unsigned int, skb->len, size);
+ if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) {
+ skb_queue_head(&sk->receive_queue, skb);
+ if (!copied)
+ copied = -EFAULT;
+ break;
+ }
+ copied += chunk;
+ size -= chunk;
+
+ if (!(flags & MSG_PEEK)) {
+ atomic_sub(chunk, &sk->rmem_alloc);
+
+ skb_pull(skb, chunk);
+ if (skb->len) {
+ skb_queue_head(&sk->receive_queue, skb);
+ break;
+ }
+ kfree_skb(skb);
+
+ } else {
+ /* put message back and return */
+ skb_queue_head(&sk->receive_queue, skb);
+ break;
+ }
+ } while (size);
+
+out:
+ if (atomic_read(&sk->rmem_alloc) <= (sk->rcvbuf >> 2))
+ rfcomm_dlc_unthrottle(rfcomm_pi(sk)->dlc);
+
+ release_sock(sk);
+ return copied ? : err;
+}
+
+static int rfcomm_sock_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen)
+{
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ BT_DBG("sk %p", sk);
+
+ lock_sock(sk);
+
+ switch (optname) {
+ default:
+ err = -ENOPROTOOPT;
+ break;
+ };
+
+ release_sock(sk);
+ return err;
+}
+
+static int rfcomm_sock_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen)
+{
+ struct sock *sk = sock->sk;
+ int len, err = 0;
+
+ BT_DBG("sk %p", sk);
+
+ if (get_user(len, optlen))
+ return -EFAULT;
+
+ lock_sock(sk);
+
+ switch (optname) {
+ default:
+ err = -ENOPROTOOPT;
+ break;
+ };
+
+ release_sock(sk);
+ return err;
+}
+
+static int rfcomm_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+ int err;
+
+ lock_sock(sk);
+
+#ifdef CONFIG_BLUEZ_RFCOMM_TTY
+ err = rfcomm_dev_ioctl(sk, cmd, arg);
+#else
+ err = -EOPNOTSUPP;
+#endif
+
+ release_sock(sk);
+
+ return err;
+}
+
+static int rfcomm_sock_shutdown(struct socket *sock, int how)
+{
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ BT_DBG("sock %p, sk %p", sock, sk);
+
+ if (!sk) return 0;
+
+ lock_sock(sk);
+ if (!sk->shutdown) {
+ sk->shutdown = SHUTDOWN_MASK;
+ __rfcomm_sock_close(sk);
+
+ if (sk->linger)
+ err = bluez_sock_wait_state(sk, BT_CLOSED, sk->lingertime);
+ }
+ release_sock(sk);
+ return err;
+}
+
+static int rfcomm_sock_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ BT_DBG("sock %p, sk %p", sock, sk);
+
+ if (!sk)
+ return 0;
+
+ err = rfcomm_sock_shutdown(sock, 2);
+
+ sock_orphan(sk);
+ rfcomm_sock_kill(sk);
+ return err;
+}
+
+/* ---- RFCOMM core layer callbacks ----
+ *
+ * called under rfcomm_lock()
+ */
+int rfcomm_connect_ind(struct rfcomm_session *s, u8 channel, struct rfcomm_dlc **d)
+{
+ struct sock *sk, *parent;
+ bdaddr_t src, dst;
+ int result = 0;
+
+ BT_DBG("session %p channel %d", s, channel);
+
+ rfcomm_session_getaddr(s, &src, &dst);
+
+ /* Check if we have socket listening on this channel */
+ parent = rfcomm_get_sock_by_channel(BT_LISTEN, channel, &src);
+ if (!parent)
+ return 0;
+
+ /* Check for backlog size */
+ if (parent->ack_backlog > parent->max_ack_backlog) {
+ BT_DBG("backlog full %d", parent->ack_backlog);
+ goto done;
+ }
+
+ sk = rfcomm_sock_alloc(NULL, BTPROTO_RFCOMM, GFP_ATOMIC);
+ if (!sk)
+ goto done;
+
+ rfcomm_sock_init(sk, parent);
+ bacpy(&bluez_pi(sk)->src, &src);
+ bacpy(&bluez_pi(sk)->dst, &dst);
+ rfcomm_pi(sk)->channel = channel;
+
+ sk->state = BT_CONFIG;
+ bluez_accept_enqueue(parent, sk);
+
+ /* Accept connection and return socket DLC */
+ *d = rfcomm_pi(sk)->dlc;
+ result = 1;
+
+done:
+ bh_unlock_sock(parent);
+ return result;
+}
+
+/* ---- Proc fs support ---- */
+int rfcomm_sock_dump(char *buf)
+{
+ struct bluez_sock_list *list = &rfcomm_sk_list;
+ struct rfcomm_pinfo *pi;
+ struct sock *sk;
+ char *ptr = buf;
+
+ write_lock_bh(&list->lock);
+
+ for (sk = list->head; sk; sk = sk->next) {
+ pi = rfcomm_pi(sk);
+ ptr += sprintf(ptr, "sk %s %s %d %d\n",
+ batostr(&bluez_pi(sk)->src), batostr(&bluez_pi(sk)->dst),
+ sk->state, rfcomm_pi(sk)->channel);
+ }
+
+ write_unlock_bh(&list->lock);
+
+ return ptr - buf;
+}
+
+static struct proto_ops rfcomm_sock_ops = {
+ family: PF_BLUETOOTH,
+ release: rfcomm_sock_release,
+ bind: rfcomm_sock_bind,
+ connect: rfcomm_sock_connect,
+ listen: rfcomm_sock_listen,
+ accept: rfcomm_sock_accept,
+ getname: rfcomm_sock_getname,
+ sendmsg: rfcomm_sock_sendmsg,
+ recvmsg: rfcomm_sock_recvmsg,
+ shutdown: rfcomm_sock_shutdown,
+ setsockopt: rfcomm_sock_setsockopt,
+ getsockopt: rfcomm_sock_getsockopt,
+ ioctl: rfcomm_sock_ioctl,
+ poll: bluez_sock_poll,
+ socketpair: sock_no_socketpair,
+ mmap: sock_no_mmap
+};
+
+static struct net_proto_family rfcomm_sock_family_ops = {
+ family: PF_BLUETOOTH,
+ create: rfcomm_sock_create
+};
+
+int rfcomm_init_sockets(void)
+{
+ int err;
+
+ if ((err = bluez_sock_register(BTPROTO_RFCOMM, &rfcomm_sock_family_ops))) {
+ BT_ERR("Can't register RFCOMM socket layer");
+ return err;
+ }
+
+ return 0;
+}
+
+void rfcomm_cleanup_sockets(void)
+{
+ int err;
+
+ /* Unregister socket, protocol and notifier */
+ if ((err = bluez_sock_unregister(BTPROTO_RFCOMM)))
+ BT_ERR("Can't unregister RFCOMM socket layer %d", err);
+}
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/tty.c b/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/tty.c
new file mode 100644
index 0000000..02ee5e0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/rfcomm/tty.c
@@ -0,0 +1,957 @@
+/*
+ RFCOMM implementation for Linux Bluetooth stack (BlueZ).
+ Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com>
+ Copyright (C) 2002 Marcel Holtmann <marcel@holtmann.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * RFCOMM TTY.
+ *
+ * $Id: tty.c,v 1.26 2002/10/18 20:12:12 maxk Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/tty_flip.h>
+
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/rfcomm.h>
+
+#ifndef CONFIG_BLUEZ_RFCOMM_DEBUG
+#undef BT_DBG
+#define BT_DBG(D...)
+#endif
+
+#define RFCOMM_TTY_MAGIC 0x6d02 /* magic number for rfcomm struct */
+#define RFCOMM_TTY_PORTS RFCOMM_MAX_DEV /* whole lotta rfcomm devices */
+#define RFCOMM_TTY_MAJOR 216 /* device node major id of the usb/bluetooth.c driver */
+#define RFCOMM_TTY_MINOR 0
+
+struct rfcomm_dev {
+ struct list_head list;
+ atomic_t refcnt;
+
+ char name[12];
+ int id;
+ unsigned long flags;
+ int opened;
+ int err;
+
+ bdaddr_t src;
+ bdaddr_t dst;
+ u8 channel;
+
+ uint modem_status;
+
+ struct rfcomm_dlc *dlc;
+ struct tty_struct *tty;
+ wait_queue_head_t wait;
+ struct tasklet_struct wakeup_task;
+
+ atomic_t wmem_alloc;
+};
+
+static LIST_HEAD(rfcomm_dev_list);
+static rwlock_t rfcomm_dev_lock = RW_LOCK_UNLOCKED;
+
+static void rfcomm_dev_data_ready(struct rfcomm_dlc *dlc, struct sk_buff *skb);
+static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err);
+static void rfcomm_dev_modem_status(struct rfcomm_dlc *dlc, u8 v24_sig);
+
+static void rfcomm_tty_wakeup(unsigned long arg);
+
+/* ---- Device functions ---- */
+static void rfcomm_dev_destruct(struct rfcomm_dev *dev)
+{
+ struct rfcomm_dlc *dlc = dev->dlc;
+
+ BT_DBG("dev %p dlc %p", dev, dlc);
+
+ rfcomm_dlc_lock(dlc);
+ /* Detach DLC if it's owned by this dev */
+ if (dlc->owner == dev)
+ dlc->owner = NULL;
+ rfcomm_dlc_unlock(dlc);
+
+ rfcomm_dlc_put(dlc);
+ kfree(dev);
+
+ MOD_DEC_USE_COUNT;
+}
+
+static inline void rfcomm_dev_hold(struct rfcomm_dev *dev)
+{
+ atomic_inc(&dev->refcnt);
+}
+
+static inline void rfcomm_dev_put(struct rfcomm_dev *dev)
+{
+ /* The reason this isn't actually a race, as you no
+ doubt have a little voice screaming at you in your
+ head, is that the refcount should never actually
+ reach zero unless the device has already been taken
+ off the list, in rfcomm_dev_del(). And if that's not
+ true, we'll hit the BUG() in rfcomm_dev_destruct()
+ anyway. */
+ if (atomic_dec_and_test(&dev->refcnt))
+ rfcomm_dev_destruct(dev);
+}
+
+static struct rfcomm_dev *__rfcomm_dev_get(int id)
+{
+ struct rfcomm_dev *dev;
+ struct list_head *p;
+
+ list_for_each(p, &rfcomm_dev_list) {
+ dev = list_entry(p, struct rfcomm_dev, list);
+ if (dev->id == id)
+ return dev;
+ }
+
+ return NULL;
+}
+
+static inline struct rfcomm_dev *rfcomm_dev_get(int id)
+{
+ struct rfcomm_dev *dev;
+
+ read_lock(&rfcomm_dev_lock);
+
+ dev = __rfcomm_dev_get(id);
+ if (dev)
+ rfcomm_dev_hold(dev);
+
+ read_unlock(&rfcomm_dev_lock);
+
+ return dev;
+}
+
+static int rfcomm_dev_add(struct rfcomm_dev_req *req, struct rfcomm_dlc *dlc)
+{
+ struct rfcomm_dev *dev;
+ struct list_head *head = &rfcomm_dev_list, *p;
+ int err = 0;
+
+ BT_DBG("id %d channel %d", req->dev_id, req->channel);
+
+ dev = kmalloc(sizeof(struct rfcomm_dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+ memset(dev, 0, sizeof(struct rfcomm_dev));
+
+ write_lock_bh(&rfcomm_dev_lock);
+
+ if (req->dev_id < 0) {
+ dev->id = 0;
+
+ list_for_each(p, &rfcomm_dev_list) {
+ if (list_entry(p, struct rfcomm_dev, list)->id != dev->id)
+ break;
+
+ dev->id++;
+ head = p;
+ }
+ } else {
+ dev->id = req->dev_id;
+
+ list_for_each(p, &rfcomm_dev_list) {
+ struct rfcomm_dev *entry = list_entry(p, struct rfcomm_dev, list);
+
+ if (entry->id == dev->id) {
+ err = -EADDRINUSE;
+ goto out;
+ }
+
+ if (entry->id > dev->id - 1)
+ break;
+
+ head = p;
+ }
+ }
+
+ if ((dev->id < 0) || (dev->id > RFCOMM_MAX_DEV - 1)) {
+ err = -ENFILE;
+ goto out;
+ }
+
+ sprintf(dev->name, "rfcomm%d", dev->id);
+
+ list_add(&dev->list, head);
+ atomic_set(&dev->refcnt, 1);
+
+ bacpy(&dev->src, &req->src);
+ bacpy(&dev->dst, &req->dst);
+ dev->channel = req->channel;
+
+ dev->flags = req->flags &
+ ((1 << RFCOMM_RELEASE_ONHUP) | (1 << RFCOMM_REUSE_DLC));
+
+ init_waitqueue_head(&dev->wait);
+ tasklet_init(&dev->wakeup_task, rfcomm_tty_wakeup, (unsigned long) dev);
+
+ rfcomm_dlc_lock(dlc);
+ dlc->data_ready = rfcomm_dev_data_ready;
+ dlc->state_change = rfcomm_dev_state_change;
+ dlc->modem_status = rfcomm_dev_modem_status;
+
+ dlc->owner = dev;
+ dev->dlc = dlc;
+ rfcomm_dlc_unlock(dlc);
+
+ MOD_INC_USE_COUNT;
+
+out:
+ write_unlock_bh(&rfcomm_dev_lock);
+
+ if (err) {
+ kfree(dev);
+ return err;
+ } else
+ return dev->id;
+}
+
+static void rfcomm_dev_del(struct rfcomm_dev *dev)
+{
+ BT_DBG("dev %p", dev);
+
+ write_lock_bh(&rfcomm_dev_lock);
+ list_del_init(&dev->list);
+ write_unlock_bh(&rfcomm_dev_lock);
+
+ rfcomm_dev_put(dev);
+}
+
+/* ---- Send buffer ---- */
+
+static inline unsigned int rfcomm_room(struct rfcomm_dlc *dlc)
+{
+ /* We can't let it be zero, because we don't get a callback
+ when tx_credits becomes nonzero, hence we'd never wake up */
+ return dlc->mtu * (dlc->tx_credits?:1);
+}
+
+static void rfcomm_wfree(struct sk_buff *skb)
+{
+ struct rfcomm_dev *dev = (void *) skb->sk;
+ atomic_sub(skb->truesize, &dev->wmem_alloc);
+ if (test_bit(RFCOMM_TTY_ATTACHED, &dev->flags))
+ tasklet_schedule(&dev->wakeup_task);
+ rfcomm_dev_put(dev);
+}
+
+static inline void rfcomm_set_owner_w(struct sk_buff *skb, struct rfcomm_dev *dev)
+{
+ rfcomm_dev_hold(dev);
+ atomic_add(skb->truesize, &dev->wmem_alloc);
+ skb->sk = (void *) dev;
+ skb->destructor = rfcomm_wfree;
+}
+
+static struct sk_buff *rfcomm_wmalloc(struct rfcomm_dev *dev, unsigned long size, int force, int priority)
+{
+ if (force || atomic_read(&dev->wmem_alloc) < rfcomm_room(dev->dlc)) {
+ struct sk_buff *skb = alloc_skb(size, priority);
+ if (skb) {
+ rfcomm_set_owner_w(skb, dev);
+ return skb;
+ }
+ }
+ return NULL;
+}
+
+/* ---- Device IOCTLs ---- */
+
+#define NOCAP_FLAGS ((1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP))
+
+static int rfcomm_create_dev(struct sock *sk, unsigned long arg)
+{
+ struct rfcomm_dev_req req;
+ struct rfcomm_dlc *dlc;
+ int id;
+
+ if (copy_from_user(&req, (void *) arg, sizeof(req)))
+ return -EFAULT;
+
+ BT_DBG("sk %p dev_id %id flags 0x%x", sk, req.dev_id, req.flags);
+
+ if (req.flags != NOCAP_FLAGS && !capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (req.flags & (1 << RFCOMM_REUSE_DLC)) {
+ /* Socket must be connected */
+ if (sk->state != BT_CONNECTED)
+ return -EBADFD;
+
+ dlc = rfcomm_pi(sk)->dlc;
+ rfcomm_dlc_hold(dlc);
+ } else {
+ dlc = rfcomm_dlc_alloc(GFP_KERNEL);
+ if (!dlc)
+ return -ENOMEM;
+ }
+
+ id = rfcomm_dev_add(&req, dlc);
+ if (id < 0) {
+ rfcomm_dlc_put(dlc);
+ return id;
+ }
+
+ if (req.flags & (1 << RFCOMM_REUSE_DLC)) {
+ /* DLC is now used by device.
+ * Socket must be disconnected */
+ sk->state = BT_CLOSED;
+ }
+
+ return id;
+}
+
+static int rfcomm_release_dev(unsigned long arg)
+{
+ struct rfcomm_dev_req req;
+ struct rfcomm_dev *dev;
+
+ if (copy_from_user(&req, (void *) arg, sizeof(req)))
+ return -EFAULT;
+
+ BT_DBG("dev_id %id flags 0x%x", req.dev_id, req.flags);
+
+ if (!(dev = rfcomm_dev_get(req.dev_id)))
+ return -ENODEV;
+
+ if (dev->flags != NOCAP_FLAGS && !capable(CAP_NET_ADMIN)) {
+ rfcomm_dev_put(dev);
+ return -EPERM;
+ }
+
+ if (req.flags & (1 << RFCOMM_HANGUP_NOW))
+ rfcomm_dlc_close(dev->dlc, 0);
+
+ rfcomm_dev_del(dev);
+ rfcomm_dev_put(dev);
+ return 0;
+}
+
+static int rfcomm_get_dev_list(unsigned long arg)
+{
+ struct rfcomm_dev_list_req *dl;
+ struct rfcomm_dev_info *di;
+ struct list_head *p;
+ int n = 0, size, err;
+ u16 dev_num;
+
+ BT_DBG("");
+
+ if (get_user(dev_num, (u16 *) arg))
+ return -EFAULT;
+
+ if (!dev_num || dev_num > (PAGE_SIZE * 4) / sizeof(*di))
+ return -EINVAL;
+
+ size = sizeof(*dl) + dev_num * sizeof(*di);
+
+ if (!(dl = kmalloc(size, GFP_KERNEL)))
+ return -ENOMEM;
+
+ di = dl->dev_info;
+
+ read_lock_bh(&rfcomm_dev_lock);
+
+ list_for_each(p, &rfcomm_dev_list) {
+ struct rfcomm_dev *dev = list_entry(p, struct rfcomm_dev, list);
+ (di + n)->id = dev->id;
+ (di + n)->flags = dev->flags;
+ (di + n)->state = dev->dlc->state;
+ (di + n)->channel = dev->channel;
+ bacpy(&(di + n)->src, &dev->src);
+ bacpy(&(di + n)->dst, &dev->dst);
+ if (++n >= dev_num)
+ break;
+ }
+
+ read_unlock_bh(&rfcomm_dev_lock);
+
+ dl->dev_num = n;
+ size = sizeof(*dl) + n * sizeof(*di);
+
+ err = copy_to_user((void *) arg, dl, size);
+ kfree(dl);
+
+ return err ? -EFAULT : 0;
+}
+
+static int rfcomm_get_dev_info(unsigned long arg)
+{
+ struct rfcomm_dev *dev;
+ struct rfcomm_dev_info di;
+ int err = 0;
+
+ BT_DBG("");
+
+ if (copy_from_user(&di, (void *)arg, sizeof(di)))
+ return -EFAULT;
+
+ if (!(dev = rfcomm_dev_get(di.id)))
+ return -ENODEV;
+
+ di.flags = dev->flags;
+ di.channel = dev->channel;
+ di.state = dev->dlc->state;
+ bacpy(&di.src, &dev->src);
+ bacpy(&di.dst, &dev->dst);
+
+ if (copy_to_user((void *)arg, &di, sizeof(di)))
+ err = -EFAULT;
+
+ rfcomm_dev_put(dev);
+ return err;
+}
+
+int rfcomm_dev_ioctl(struct sock *sk, unsigned int cmd, unsigned long arg)
+{
+ BT_DBG("cmd %d arg %ld", cmd, arg);
+
+ switch (cmd) {
+ case RFCOMMCREATEDEV:
+ return rfcomm_create_dev(sk, arg);
+
+ case RFCOMMRELEASEDEV:
+ return rfcomm_release_dev(arg);
+
+ case RFCOMMGETDEVLIST:
+ return rfcomm_get_dev_list(arg);
+
+ case RFCOMMGETDEVINFO:
+ return rfcomm_get_dev_info(arg);
+ }
+
+ return -EINVAL;
+}
+
+/* ---- DLC callbacks ---- */
+static void rfcomm_dev_data_ready(struct rfcomm_dlc *dlc, struct sk_buff *skb)
+{
+ struct rfcomm_dev *dev = dlc->owner;
+ struct tty_struct *tty;
+
+ if (!dev || !(tty = dev->tty)) {
+ kfree_skb(skb);
+ return;
+ }
+
+ BT_DBG("dlc %p tty %p len %d", dlc, tty, skb->len);
+
+ if (test_bit(TTY_DONT_FLIP, &tty->flags)) {
+ register int i;
+ for (i = 0; i < skb->len; i++) {
+ if (tty->flip.count >= TTY_FLIPBUF_SIZE)
+ tty_flip_buffer_push(tty);
+
+ tty_insert_flip_char(tty, skb->data[i], 0);
+ }
+ tty_flip_buffer_push(tty);
+ } else
+ tty->ldisc.receive_buf(tty, skb->data, NULL, skb->len);
+
+ kfree_skb(skb);
+}
+
+static void rfcomm_dev_state_change(struct rfcomm_dlc *dlc, int err)
+{
+ struct rfcomm_dev *dev = dlc->owner;
+ if (!dev)
+ return;
+
+ BT_DBG("dlc %p dev %p err %d", dlc, dev, err);
+
+ dev->err = err;
+ wake_up_interruptible(&dev->wait);
+
+ if (dlc->state == BT_CLOSED) {
+ if (!dev->tty) {
+ if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags)) {
+ rfcomm_dev_hold(dev);
+ rfcomm_dev_del(dev);
+
+ /* We have to drop DLC lock here, otherwise
+ rfcomm_dev_put() will dead lock if it's
+ the last reference. */
+ rfcomm_dlc_unlock(dlc);
+ rfcomm_dev_put(dev);
+ rfcomm_dlc_lock(dlc);
+ }
+ } else
+ tty_hangup(dev->tty);
+ }
+}
+
+static void rfcomm_dev_modem_status(struct rfcomm_dlc *dlc, u8 v24_sig)
+{
+ struct rfcomm_dev *dev = dlc->owner;
+ if (!dev)
+ return;
+
+ BT_DBG("dlc %p dev %p v24_sig 0x%02x", dlc, dev, v24_sig);
+
+ dev->modem_status =
+ ((v24_sig & RFCOMM_V24_RTC) ? (TIOCM_DSR | TIOCM_DTR) : 0) |
+ ((v24_sig & RFCOMM_V24_RTR) ? (TIOCM_RTS | TIOCM_CTS) : 0) |
+ ((v24_sig & RFCOMM_V24_IC) ? TIOCM_RI : 0) |
+ ((v24_sig & RFCOMM_V24_DV) ? TIOCM_CD : 0);
+}
+
+/* ---- TTY functions ---- */
+static void rfcomm_tty_wakeup(unsigned long arg)
+{
+ struct rfcomm_dev *dev = (void *) arg;
+ struct tty_struct *tty = dev->tty;
+ if (!tty)
+ return;
+
+ BT_DBG("dev %p tty %p", dev, tty);
+
+ tty_wakeup(tty);
+
+#ifdef SERIAL_HAVE_POLL_WAIT
+ wake_up_interruptible(&tty->poll_wait);
+#endif
+}
+
+static int rfcomm_tty_open(struct tty_struct *tty, struct file *filp)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct rfcomm_dev *dev;
+ struct rfcomm_dlc *dlc;
+ int err, id;
+
+ id = MINOR(tty->device) - tty->driver.minor_start;
+
+ BT_DBG("tty %p id %d", tty, id);
+
+ /* We don't leak this refcount. For reasons which are not entirely
+ clear, the TTY layer will call our ->close() method even if the
+ open fails. We decrease the refcount there, and decreasing it
+ here too would cause breakage. */
+ dev = rfcomm_dev_get(id);
+ if (!dev)
+ return -ENODEV;
+
+ BT_DBG("dev %p dst %s channel %d opened %d", dev, batostr(&dev->dst), dev->channel, dev->opened);
+
+ if (dev->opened++ != 0)
+ return 0;
+
+ dlc = dev->dlc;
+
+ /* Attach TTY and open DLC */
+
+ rfcomm_dlc_lock(dlc);
+ tty->driver_data = dev;
+ dev->tty = tty;
+ rfcomm_dlc_unlock(dlc);
+ set_bit(RFCOMM_TTY_ATTACHED, &dev->flags);
+
+ err = rfcomm_dlc_open(dlc, &dev->src, &dev->dst, dev->channel);
+ if (err < 0)
+ return err;
+
+ /* Wait for DLC to connect */
+ add_wait_queue(&dev->wait, &wait);
+ while (1) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (dlc->state == BT_CLOSED) {
+ err = -dev->err;
+ break;
+ }
+
+ if (dlc->state == BT_CONNECTED)
+ break;
+
+ if (signal_pending(current)) {
+ err = -EINTR;
+ break;
+ }
+
+ schedule();
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&dev->wait, &wait);
+
+ return err;
+}
+
+static void rfcomm_tty_close(struct tty_struct *tty, struct file *filp)
+{
+ struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data;
+ if (!dev)
+ return;
+
+ BT_DBG("tty %p dev %p dlc %p opened %d", tty, dev, dev->dlc, dev->opened);
+
+ if (--dev->opened == 0) {
+ /* Close DLC and dettach TTY */
+ rfcomm_dlc_close(dev->dlc, 0);
+
+ clear_bit(RFCOMM_TTY_ATTACHED, &dev->flags);
+ tasklet_kill(&dev->wakeup_task);
+
+ rfcomm_dlc_lock(dev->dlc);
+ tty->driver_data = NULL;
+ dev->tty = NULL;
+ rfcomm_dlc_unlock(dev->dlc);
+ }
+
+ rfcomm_dev_put(dev);
+}
+
+static int rfcomm_tty_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count)
+{
+ struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data;
+ struct rfcomm_dlc *dlc = dev->dlc;
+ struct sk_buff *skb;
+ int err = 0, sent = 0, size;
+
+ BT_DBG("tty %p from_user %d count %d", tty, from_user, count);
+
+ while (count) {
+ size = min_t(uint, count, dlc->mtu);
+
+ if (from_user)
+ skb = rfcomm_wmalloc(dev, size + RFCOMM_SKB_RESERVE, 0, GFP_KERNEL);
+ else
+ skb = rfcomm_wmalloc(dev, size + RFCOMM_SKB_RESERVE, 0, GFP_ATOMIC);
+
+ if (!skb)
+ break;
+
+ skb_reserve(skb, RFCOMM_SKB_HEAD_RESERVE);
+
+ if (from_user)
+ copy_from_user(skb_put(skb, size), buf + sent, size);
+ else
+ memcpy(skb_put(skb, size), buf + sent, size);
+
+ if ((err = rfcomm_dlc_send(dlc, skb)) < 0) {
+ kfree_skb(skb);
+ break;
+ }
+
+ sent += size;
+ count -= size;
+ }
+
+ return sent ? sent : err;
+}
+
+static void rfcomm_tty_put_char(struct tty_struct *tty, unsigned char ch)
+{
+ struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data;
+ struct rfcomm_dlc *dlc = dev->dlc;
+ struct sk_buff *skb;
+
+ BT_DBG("tty %p char %x", tty, ch);
+
+ skb = rfcomm_wmalloc(dev, 1 + RFCOMM_SKB_RESERVE, 1, GFP_ATOMIC);
+
+ if (!skb)
+ return;
+
+ skb_reserve(skb, RFCOMM_SKB_HEAD_RESERVE);
+
+ *(char *)skb_put(skb, 1) = ch;
+
+ if ((rfcomm_dlc_send(dlc, skb)) < 0)
+ kfree_skb(skb);
+}
+
+static int rfcomm_tty_write_room(struct tty_struct *tty)
+{
+ struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data;
+ int room;
+
+ BT_DBG("tty %p", tty);
+
+ room = rfcomm_room(dev->dlc) - atomic_read(&dev->wmem_alloc);
+ if (room < 0)
+ room = 0;
+
+ return room;
+}
+
+static int rfcomm_tty_set_modem_status(uint cmd, struct rfcomm_dlc *dlc, uint status)
+{
+ u8 v24_sig, mask;
+
+ BT_DBG("dlc %p cmd 0x%02x", dlc, cmd);
+
+ if (cmd == TIOCMSET)
+ v24_sig = 0;
+ else
+ rfcomm_dlc_get_modem_status(dlc, &v24_sig);
+
+ mask = ((status & TIOCM_DSR) ? RFCOMM_V24_RTC : 0) |
+ ((status & TIOCM_DTR) ? RFCOMM_V24_RTC : 0) |
+ ((status & TIOCM_RTS) ? RFCOMM_V24_RTR : 0) |
+ ((status & TIOCM_CTS) ? RFCOMM_V24_RTR : 0) |
+ ((status & TIOCM_RI) ? RFCOMM_V24_IC : 0) |
+ ((status & TIOCM_CD) ? RFCOMM_V24_DV : 0);
+
+ if (cmd == TIOCMBIC)
+ v24_sig &= ~mask;
+ else
+ v24_sig |= mask;
+
+ rfcomm_dlc_set_modem_status(dlc, v24_sig);
+ return 0;
+}
+
+static int rfcomm_tty_ioctl(struct tty_struct *tty, struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data;
+ struct rfcomm_dlc *dlc = dev->dlc;
+ uint status;
+ int err;
+
+ BT_DBG("tty %p cmd 0x%02x", tty, cmd);
+
+ switch (cmd) {
+ case TCGETS:
+ BT_DBG("TCGETS is not supported");
+ return -ENOIOCTLCMD;
+
+ case TCSETS:
+ BT_DBG("TCSETS is not supported");
+ return -ENOIOCTLCMD;
+
+ case TIOCMGET:
+ BT_DBG("TIOCMGET");
+
+ return put_user(dev->modem_status, (unsigned int *)arg);
+
+ case TIOCMSET: /* Turns on and off the lines as specified by the mask */
+ case TIOCMBIS: /* Turns on the lines as specified by the mask */
+ case TIOCMBIC: /* Turns off the lines as specified by the mask */
+ if ((err = get_user(status, (unsigned int *)arg)))
+ return err;
+ return rfcomm_tty_set_modem_status(cmd, dlc, status);
+
+ case TIOCMIWAIT:
+ BT_DBG("TIOCMIWAIT");
+ break;
+
+ case TIOCGICOUNT:
+ BT_DBG("TIOCGICOUNT");
+ break;
+
+ case TIOCGSERIAL:
+ BT_ERR("TIOCGSERIAL is not supported");
+ return -ENOIOCTLCMD;
+
+ case TIOCSSERIAL:
+ BT_ERR("TIOCSSERIAL is not supported");
+ return -ENOIOCTLCMD;
+
+ case TIOCSERGSTRUCT:
+ BT_ERR("TIOCSERGSTRUCT is not supported");
+ return -ENOIOCTLCMD;
+
+ case TIOCSERGETLSR:
+ BT_ERR("TIOCSERGETLSR is not supported");
+ return -ENOIOCTLCMD;
+
+ case TIOCSERCONFIG:
+ BT_ERR("TIOCSERCONFIG is not supported");
+ return -ENOIOCTLCMD;
+
+ default:
+ return -ENOIOCTLCMD; /* ioctls which we must ignore */
+
+ }
+
+ return -ENOIOCTLCMD;
+}
+
+#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
+
+static void rfcomm_tty_set_termios(struct tty_struct *tty, struct termios *old)
+{
+ BT_DBG("tty %p", tty);
+
+ if ((tty->termios->c_cflag == old->c_cflag) &&
+ (RELEVANT_IFLAG(tty->termios->c_iflag) == RELEVANT_IFLAG(old->c_iflag)))
+ return;
+
+ /* handle turning off CRTSCTS */
+ if ((old->c_cflag & CRTSCTS) && !(tty->termios->c_cflag & CRTSCTS)) {
+ BT_DBG("turning off CRTSCTS");
+ }
+}
+
+static void rfcomm_tty_throttle(struct tty_struct *tty)
+{
+ struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data;
+
+ BT_DBG("tty %p dev %p", tty, dev);
+
+ rfcomm_dlc_throttle(dev->dlc);
+}
+
+static void rfcomm_tty_unthrottle(struct tty_struct *tty)
+{
+ struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data;
+
+ BT_DBG("tty %p dev %p", tty, dev);
+
+ rfcomm_dlc_unthrottle(dev->dlc);
+}
+
+static int rfcomm_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data;
+ struct rfcomm_dlc *dlc = dev->dlc;
+
+ BT_DBG("tty %p dev %p", tty, dev);
+
+ if (skb_queue_len(&dlc->tx_queue))
+ return dlc->mtu;
+
+ return 0;
+}
+
+static void rfcomm_tty_flush_buffer(struct tty_struct *tty)
+{
+ struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data;
+ if (!dev)
+ return;
+
+ BT_DBG("tty %p dev %p", tty, dev);
+
+ skb_queue_purge(&dev->dlc->tx_queue);
+
+ tty_wakeup(tty);
+}
+
+static void rfcomm_tty_send_xchar(struct tty_struct *tty, char ch)
+{
+ BT_DBG("tty %p ch %c", tty, ch);
+}
+
+static void rfcomm_tty_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ BT_DBG("tty %p timeout %d", tty, timeout);
+}
+
+static void rfcomm_tty_hangup(struct tty_struct *tty)
+{
+ struct rfcomm_dev *dev = (struct rfcomm_dev *) tty->driver_data;
+ if (!dev)
+ return;
+
+ BT_DBG("tty %p dev %p", tty, dev);
+
+ rfcomm_tty_flush_buffer(tty);
+
+ if (test_bit(RFCOMM_RELEASE_ONHUP, &dev->flags))
+ rfcomm_dev_del(dev);
+}
+
+static int rfcomm_tty_read_proc(char *buf, char **start, off_t offset, int len, int *eof, void *unused)
+{
+ return 0;
+}
+
+/* ---- TTY structure ---- */
+static int rfcomm_tty_refcount; /* If we manage several devices */
+
+static struct tty_struct *rfcomm_tty_table[RFCOMM_TTY_PORTS];
+static struct termios *rfcomm_tty_termios[RFCOMM_TTY_PORTS];
+static struct termios *rfcomm_tty_termios_locked[RFCOMM_TTY_PORTS];
+
+static struct tty_driver rfcomm_tty_driver = {
+ magic: TTY_DRIVER_MAGIC,
+ driver_name: "rfcomm",
+#ifdef CONFIG_DEVFS_FS
+ name: "bluetooth/rfcomm/%d",
+#else
+ name: "rfcomm",
+#endif
+ major: RFCOMM_TTY_MAJOR,
+ minor_start: RFCOMM_TTY_MINOR,
+ num: RFCOMM_TTY_PORTS,
+ type: TTY_DRIVER_TYPE_SERIAL,
+ subtype: SERIAL_TYPE_NORMAL,
+ flags: TTY_DRIVER_REAL_RAW,
+
+ refcount: &rfcomm_tty_refcount,
+ table: rfcomm_tty_table,
+ termios: rfcomm_tty_termios,
+ termios_locked: rfcomm_tty_termios_locked,
+
+ open: rfcomm_tty_open,
+ close: rfcomm_tty_close,
+ put_char: rfcomm_tty_put_char,
+ write: rfcomm_tty_write,
+ write_room: rfcomm_tty_write_room,
+ chars_in_buffer: rfcomm_tty_chars_in_buffer,
+ flush_buffer: rfcomm_tty_flush_buffer,
+ ioctl: rfcomm_tty_ioctl,
+ throttle: rfcomm_tty_throttle,
+ unthrottle: rfcomm_tty_unthrottle,
+ set_termios: rfcomm_tty_set_termios,
+ send_xchar: rfcomm_tty_send_xchar,
+ stop: NULL,
+ start: NULL,
+ hangup: rfcomm_tty_hangup,
+ wait_until_sent: rfcomm_tty_wait_until_sent,
+ read_proc: rfcomm_tty_read_proc,
+};
+
+int rfcomm_init_ttys(void)
+{
+ int i;
+
+ /* Initalize our global data */
+ for (i = 0; i < RFCOMM_TTY_PORTS; i++)
+ rfcomm_tty_table[i] = NULL;
+
+ /* Register the TTY driver */
+ rfcomm_tty_driver.init_termios = tty_std_termios;
+ rfcomm_tty_driver.init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ rfcomm_tty_driver.flags = TTY_DRIVER_REAL_RAW;
+
+ if (tty_register_driver(&rfcomm_tty_driver)) {
+ BT_ERR("Can't register RFCOMM TTY driver");
+ return -1;
+ }
+
+ return 0;
+}
+
+void rfcomm_cleanup_ttys(void)
+{
+ tty_unregister_driver(&rfcomm_tty_driver);
+ return;
+}
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/sco.c b/uClinux-2.4.31-uc0/net/bluetooth/sco.c
new file mode 100644
index 0000000..0acefcf
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/sco.c
@@ -0,0 +1,1018 @@
+/*
+ BlueZ - Bluetooth protocol stack for Linux
+ Copyright (C) 2000-2001 Qualcomm Incorporated
+
+ Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * BlueZ SCO sockets.
+ *
+ * $Id: sco.c,v 1.4 2002/07/22 20:32:54 maxk Exp $
+ */
+#define VERSION "0.3"
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/poll.h>
+#include <linux/fcntl.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/interrupt.h>
+#include <linux/socket.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/list.h>
+#include <net/sock.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+#include <net/bluetooth/sco.h>
+
+#ifndef SCO_DEBUG
+#undef BT_DBG
+#define BT_DBG( A... )
+#endif
+
+static struct proto_ops sco_sock_ops;
+
+static struct bluez_sock_list sco_sk_list = {
+ lock: RW_LOCK_UNLOCKED
+};
+
+static void __sco_chan_add(struct sco_conn *conn, struct sock *sk, struct sock *parent);
+static void sco_chan_del(struct sock *sk, int err);
+
+static int sco_conn_del(struct hci_conn *conn, int err);
+
+static void sco_sock_close(struct sock *sk);
+static void sco_sock_kill(struct sock *sk);
+
+/* ----- SCO timers ------ */
+static void sco_sock_timeout(unsigned long arg)
+{
+ struct sock *sk = (struct sock *) arg;
+
+ BT_DBG("sock %p state %d", sk, sk->state);
+
+ bh_lock_sock(sk);
+ sk->err = ETIMEDOUT;
+ sk->state_change(sk);
+ bh_unlock_sock(sk);
+
+ sco_sock_kill(sk);
+ sock_put(sk);
+}
+
+static void sco_sock_set_timer(struct sock *sk, long timeout)
+{
+ BT_DBG("sock %p state %d timeout %ld", sk, sk->state, timeout);
+
+ if (!mod_timer(&sk->timer, jiffies + timeout))
+ sock_hold(sk);
+}
+
+static void sco_sock_clear_timer(struct sock *sk)
+{
+ BT_DBG("sock %p state %d", sk, sk->state);
+
+ if (timer_pending(&sk->timer) && del_timer(&sk->timer))
+ __sock_put(sk);
+}
+
+static void sco_sock_init_timer(struct sock *sk)
+{
+ init_timer(&sk->timer);
+ sk->timer.function = sco_sock_timeout;
+ sk->timer.data = (unsigned long)sk;
+}
+
+/* -------- SCO connections --------- */
+static struct sco_conn *sco_conn_add(struct hci_conn *hcon, __u8 status)
+{
+ struct hci_dev *hdev = hcon->hdev;
+ struct sco_conn *conn;
+
+ if ((conn = hcon->sco_data))
+ return conn;
+
+ if (status)
+ return conn;
+
+ if (!(conn = kmalloc(sizeof(struct sco_conn), GFP_ATOMIC)))
+ return NULL;
+ memset(conn, 0, sizeof(struct sco_conn));
+
+ spin_lock_init(&conn->lock);
+
+ hcon->sco_data = conn;
+ conn->hcon = hcon;
+
+ conn->src = &hdev->bdaddr;
+ conn->dst = &hcon->dst;
+
+ if (hdev->sco_mtu > 0)
+ conn->mtu = hdev->sco_mtu;
+ else
+ conn->mtu = 60;
+
+ BT_DBG("hcon %p conn %p", hcon, conn);
+
+ MOD_INC_USE_COUNT;
+ return conn;
+}
+
+static inline struct sock * sco_chan_get(struct sco_conn *conn)
+{
+ struct sock *sk = NULL;
+ sco_conn_lock(conn);
+ sk = conn->sk;
+ sco_conn_unlock(conn);
+ return sk;
+}
+
+static int sco_conn_del(struct hci_conn *hcon, int err)
+{
+ struct sco_conn *conn;
+ struct sock *sk;
+
+ if (!(conn = hcon->sco_data))
+ return 0;
+
+ BT_DBG("hcon %p conn %p, err %d", hcon, conn, err);
+
+ /* Kill socket */
+ if ((sk = sco_chan_get(conn))) {
+ bh_lock_sock(sk);
+ sco_sock_clear_timer(sk);
+ sco_chan_del(sk, err);
+ bh_unlock_sock(sk);
+ sco_sock_kill(sk);
+ }
+
+ hcon->sco_data = NULL;
+ kfree(conn);
+
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static inline int sco_chan_add(struct sco_conn *conn, struct sock *sk, struct sock *parent)
+{
+ int err = 0;
+
+ sco_conn_lock(conn);
+ if (conn->sk) {
+ err = -EBUSY;
+ } else {
+ __sco_chan_add(conn, sk, parent);
+ }
+ sco_conn_unlock(conn);
+ return err;
+}
+
+int sco_connect(struct sock *sk)
+{
+ bdaddr_t *src = &bluez_pi(sk)->src;
+ bdaddr_t *dst = &bluez_pi(sk)->dst;
+ struct sco_conn *conn;
+ struct hci_conn *hcon;
+ struct hci_dev *hdev;
+ int err = 0;
+
+ BT_DBG("%s -> %s", batostr(src), batostr(dst));
+
+ if (!(hdev = hci_get_route(dst, src)))
+ return -EHOSTUNREACH;
+
+ hci_dev_lock_bh(hdev);
+
+ err = -ENOMEM;
+
+ hcon = hci_connect(hdev, SCO_LINK, dst);
+ if (!hcon)
+ goto done;
+
+ conn = sco_conn_add(hcon, 0);
+ if (!conn) {
+ hci_conn_put(hcon);
+ goto done;
+ }
+
+ /* Update source addr of the socket */
+ bacpy(src, conn->src);
+
+ err = sco_chan_add(conn, sk, NULL);
+ if (err)
+ goto done;
+
+ if (hcon->state == BT_CONNECTED) {
+ sco_sock_clear_timer(sk);
+ sk->state = BT_CONNECTED;
+ } else {
+ sk->state = BT_CONNECT;
+ sco_sock_set_timer(sk, sk->sndtimeo);
+ }
+done:
+ hci_dev_unlock_bh(hdev);
+ hci_dev_put(hdev);
+ return err;
+}
+
+static inline int sco_send_frame(struct sock *sk, struct msghdr *msg, int len)
+{
+ struct sco_conn *conn = sco_pi(sk)->conn;
+ struct sk_buff *skb;
+ int err, count;
+
+ /* Check outgoing MTU */
+ if (len > conn->mtu)
+ return -EINVAL;
+
+ BT_DBG("sk %p len %d", sk, len);
+
+ count = MIN(conn->mtu, len);
+ if (!(skb = bluez_skb_send_alloc(sk, count, msg->msg_flags & MSG_DONTWAIT, &err)))
+ return err;
+
+ if (memcpy_fromiovec(skb_put(skb, count), msg->msg_iov, count)) {
+ err = -EFAULT;
+ goto fail;
+ }
+
+ if ((err = hci_send_sco(conn->hcon, skb)) < 0)
+ goto fail;
+
+ return count;
+
+fail:
+ kfree_skb(skb);
+ return err;
+}
+
+static inline void sco_recv_frame(struct sco_conn *conn, struct sk_buff *skb)
+{
+ struct sock *sk = sco_chan_get(conn);
+
+ if (!sk)
+ goto drop;
+
+ BT_DBG("sk %p len %d", sk, skb->len);
+
+ if (sk->state != BT_CONNECTED)
+ goto drop;
+
+ if (!sock_queue_rcv_skb(sk, skb))
+ return;
+
+drop:
+ kfree_skb(skb);
+ return;
+}
+
+/* -------- Socket interface ---------- */
+static struct sock *__sco_get_sock_by_addr(bdaddr_t *ba)
+{
+ struct sock *sk;
+
+ for (sk = sco_sk_list.head; sk; sk = sk->next) {
+ if (!bacmp(&bluez_pi(sk)->src, ba))
+ break;
+ }
+
+ return sk;
+}
+
+/* Find socket listening on source bdaddr.
+ * Returns closest match.
+ */
+static struct sock *sco_get_sock_listen(bdaddr_t *src)
+{
+ struct sock *sk, *sk1 = NULL;
+
+ read_lock(&sco_sk_list.lock);
+
+ for (sk = sco_sk_list.head; sk; sk = sk->next) {
+ if (sk->state != BT_LISTEN)
+ continue;
+
+ /* Exact match. */
+ if (!bacmp(&bluez_pi(sk)->src, src))
+ break;
+
+ /* Closest match */
+ if (!bacmp(&bluez_pi(sk)->src, BDADDR_ANY))
+ sk1 = sk;
+ }
+
+ read_unlock(&sco_sk_list.lock);
+
+ return sk ? sk : sk1;
+}
+
+static void sco_sock_destruct(struct sock *sk)
+{
+ BT_DBG("sk %p", sk);
+
+ skb_queue_purge(&sk->receive_queue);
+ skb_queue_purge(&sk->write_queue);
+
+ MOD_DEC_USE_COUNT;
+}
+
+static void sco_sock_cleanup_listen(struct sock *parent)
+{
+ struct sock *sk;
+
+ BT_DBG("parent %p", parent);
+
+ /* Close not yet accepted channels */
+ while ((sk = bluez_accept_dequeue(parent, NULL))) {
+ sco_sock_close(sk);
+ sco_sock_kill(sk);
+ }
+
+ parent->state = BT_CLOSED;
+ parent->zapped = 1;
+}
+
+/* Kill socket (only if zapped and orphan)
+ * Must be called on unlocked socket.
+ */
+static void sco_sock_kill(struct sock *sk)
+{
+ if (!sk->zapped || sk->socket)
+ return;
+
+ BT_DBG("sk %p state %d", sk, sk->state);
+
+ /* Kill poor orphan */
+ bluez_sock_unlink(&sco_sk_list, sk);
+ sk->dead = 1;
+ sock_put(sk);
+}
+
+/* Close socket.
+ * Must be called on unlocked socket.
+ */
+static void sco_sock_close(struct sock *sk)
+{
+ struct sco_conn *conn;
+
+ sco_sock_clear_timer(sk);
+
+ lock_sock(sk);
+
+ conn = sco_pi(sk)->conn;
+
+ BT_DBG("sk %p state %d conn %p socket %p", sk, sk->state, conn, sk->socket);
+
+ switch (sk->state) {
+ case BT_LISTEN:
+ sco_sock_cleanup_listen(sk);
+ break;
+
+ case BT_CONNECTED:
+ case BT_CONFIG:
+ case BT_CONNECT:
+ case BT_DISCONN:
+ sco_chan_del(sk, ECONNRESET);
+ break;
+
+ default:
+ sk->zapped = 1;
+ break;
+ };
+
+ release_sock(sk);
+}
+
+static void sco_sock_init(struct sock *sk, struct sock *parent)
+{
+ BT_DBG("sk %p", sk);
+
+ if (parent)
+ sk->type = parent->type;
+}
+
+static struct sock *sco_sock_alloc(struct socket *sock, int proto, int prio)
+{
+ struct sock *sk;
+
+ if (!(sk = sk_alloc(PF_BLUETOOTH, prio, 1)))
+ return NULL;
+
+ bluez_sock_init(sock, sk);
+
+ sk->zapped = 0;
+
+ sk->destruct = sco_sock_destruct;
+ sk->sndtimeo = SCO_CONN_TIMEOUT;
+
+ sk->protocol = proto;
+ sk->state = BT_OPEN;
+
+ sco_sock_init_timer(sk);
+
+ bluez_sock_link(&sco_sk_list, sk);
+
+ MOD_INC_USE_COUNT;
+ return sk;
+}
+
+static int sco_sock_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+
+ BT_DBG("sock %p", sock);
+
+ sock->state = SS_UNCONNECTED;
+
+ if (sock->type != SOCK_SEQPACKET)
+ return -ESOCKTNOSUPPORT;
+
+ sock->ops = &sco_sock_ops;
+
+ if (!(sk = sco_sock_alloc(sock, protocol, GFP_KERNEL)))
+ return -ENOMEM;
+
+ sco_sock_init(sk, NULL);
+ return 0;
+}
+
+static int sco_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
+{
+ struct sockaddr_sco *sa = (struct sockaddr_sco *) addr;
+ struct sock *sk = sock->sk;
+ bdaddr_t *src = &sa->sco_bdaddr;
+ int err = 0;
+
+ BT_DBG("sk %p %s", sk, batostr(&sa->sco_bdaddr));
+
+ if (!addr || addr->sa_family != AF_BLUETOOTH)
+ return -EINVAL;
+
+ lock_sock(sk);
+
+ if (sk->state != BT_OPEN) {
+ err = -EBADFD;
+ goto done;
+ }
+
+ write_lock_bh(&sco_sk_list.lock);
+
+ if (bacmp(src, BDADDR_ANY) && __sco_get_sock_by_addr(src)) {
+ err = -EADDRINUSE;
+ } else {
+ /* Save source address */
+ bacpy(&bluez_pi(sk)->src, &sa->sco_bdaddr);
+ sk->state = BT_BOUND;
+ }
+
+ write_unlock_bh(&sco_sk_list.lock);
+
+done:
+ release_sock(sk);
+
+ return err;
+}
+
+static int sco_sock_connect(struct socket *sock, struct sockaddr *addr, int alen, int flags)
+{
+ struct sockaddr_sco *sa = (struct sockaddr_sco *) addr;
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+
+ BT_DBG("sk %p", sk);
+
+ if (addr->sa_family != AF_BLUETOOTH || alen < sizeof(struct sockaddr_sco))
+ return -EINVAL;
+
+ if (sk->state != BT_OPEN && sk->state != BT_BOUND)
+ return -EBADFD;
+
+ if (sk->type != SOCK_SEQPACKET)
+ return -EINVAL;
+
+ lock_sock(sk);
+
+ /* Set destination address and psm */
+ bacpy(&bluez_pi(sk)->dst, &sa->sco_bdaddr);
+
+ if ((err = sco_connect(sk)))
+ goto done;
+
+ err = bluez_sock_wait_state(sk, BT_CONNECTED,
+ sock_sndtimeo(sk, flags & O_NONBLOCK));
+
+done:
+ release_sock(sk);
+ return err;
+}
+
+int sco_sock_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ BT_DBG("sk %p backlog %d", sk, backlog);
+
+ lock_sock(sk);
+
+ if (sk->state != BT_BOUND || sock->type != SOCK_SEQPACKET) {
+ err = -EBADFD;
+ goto done;
+ }
+
+ sk->max_ack_backlog = backlog;
+ sk->ack_backlog = 0;
+ sk->state = BT_LISTEN;
+
+done:
+ release_sock(sk);
+ return err;
+}
+
+int sco_sock_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ struct sock *sk = sock->sk, *ch;
+ long timeo;
+ int err = 0;
+
+ lock_sock(sk);
+
+ if (sk->state != BT_LISTEN) {
+ err = -EBADFD;
+ goto done;
+ }
+
+ timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
+
+ BT_DBG("sk %p timeo %ld", sk, timeo);
+
+ /* Wait for an incoming connection. (wake-one). */
+ add_wait_queue_exclusive(sk->sleep, &wait);
+ while (!(ch = bluez_accept_dequeue(sk, newsock))) {
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (!timeo) {
+ err = -EAGAIN;
+ break;
+ }
+
+ release_sock(sk);
+ timeo = schedule_timeout(timeo);
+ lock_sock(sk);
+
+ if (sk->state != BT_LISTEN) {
+ err = -EBADFD;
+ break;
+ }
+
+ if (signal_pending(current)) {
+ err = sock_intr_errno(timeo);
+ break;
+ }
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(sk->sleep, &wait);
+
+ if (err)
+ goto done;
+
+ newsock->state = SS_CONNECTED;
+
+ BT_DBG("new socket %p", ch);
+
+done:
+ release_sock(sk);
+ return err;
+}
+
+static int sco_sock_getname(struct socket *sock, struct sockaddr *addr, int *len, int peer)
+{
+ struct sockaddr_sco *sa = (struct sockaddr_sco *) addr;
+ struct sock *sk = sock->sk;
+
+ BT_DBG("sock %p, sk %p", sock, sk);
+
+ addr->sa_family = AF_BLUETOOTH;
+ *len = sizeof(struct sockaddr_sco);
+
+ if (peer)
+ bacpy(&sa->sco_bdaddr, &bluez_pi(sk)->dst);
+ else
+ bacpy(&sa->sco_bdaddr, &bluez_pi(sk)->src);
+
+ return 0;
+}
+
+static int sco_sock_sendmsg(struct socket *sock, struct msghdr *msg, int len, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ BT_DBG("sock %p, sk %p", sock, sk);
+
+ if (sk->err)
+ return sock_error(sk);
+
+ if (msg->msg_flags & MSG_OOB)
+ return -EOPNOTSUPP;
+
+ lock_sock(sk);
+
+ if (sk->state == BT_CONNECTED)
+ err = sco_send_frame(sk, msg, len);
+ else
+ err = -ENOTCONN;
+
+ release_sock(sk);
+ return err;
+}
+
+int sco_sock_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen)
+{
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ BT_DBG("sk %p", sk);
+
+ lock_sock(sk);
+
+ switch (optname) {
+ default:
+ err = -ENOPROTOOPT;
+ break;
+ };
+
+ release_sock(sk);
+ return err;
+}
+
+int sco_sock_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen)
+{
+ struct sock *sk = sock->sk;
+ struct sco_options opts;
+ struct sco_conninfo cinfo;
+ int len, err = 0;
+
+ BT_DBG("sk %p", sk);
+
+ if (get_user(len, optlen))
+ return -EFAULT;
+
+ lock_sock(sk);
+
+ switch (optname) {
+ case SCO_OPTIONS:
+ if (sk->state != BT_CONNECTED) {
+ err = -ENOTCONN;
+ break;
+ }
+
+ opts.mtu = sco_pi(sk)->conn->mtu;
+
+ BT_DBG("mtu %d", opts.mtu);
+
+ len = MIN(len, sizeof(opts));
+ if (copy_to_user(optval, (char *)&opts, len))
+ err = -EFAULT;
+
+ break;
+
+ case SCO_CONNINFO:
+ if (sk->state != BT_CONNECTED) {
+ err = -ENOTCONN;
+ break;
+ }
+
+ cinfo.hci_handle = sco_pi(sk)->conn->hcon->handle;
+
+ len = MIN(len, sizeof(cinfo));
+ if (copy_to_user(optval, (char *)&cinfo, len))
+ err = -EFAULT;
+
+ break;
+
+ default:
+ err = -ENOPROTOOPT;
+ break;
+ };
+
+ release_sock(sk);
+ return err;
+}
+
+static int sco_sock_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ BT_DBG("sock %p, sk %p", sock, sk);
+
+ if (!sk)
+ return 0;
+
+ sco_sock_close(sk);
+ if (sk->linger) {
+ lock_sock(sk);
+ err = bluez_sock_wait_state(sk, BT_CLOSED, sk->lingertime);
+ release_sock(sk);
+ }
+
+ sock_orphan(sk);
+ sco_sock_kill(sk);
+ return err;
+}
+
+static void __sco_chan_add(struct sco_conn *conn, struct sock *sk, struct sock *parent)
+{
+ BT_DBG("conn %p", conn);
+
+ sco_pi(sk)->conn = conn;
+ conn->sk = sk;
+
+ if (parent)
+ bluez_accept_enqueue(parent, sk);
+}
+
+/* Delete channel.
+ * Must be called on the locked socket. */
+static void sco_chan_del(struct sock *sk, int err)
+{
+ struct sco_conn *conn;
+
+ conn = sco_pi(sk)->conn;
+
+ BT_DBG("sk %p, conn %p, err %d", sk, conn, err);
+
+ if (conn) {
+ sco_conn_lock(conn);
+ conn->sk = NULL;
+ sco_pi(sk)->conn = NULL;
+ sco_conn_unlock(conn);
+ hci_conn_put(conn->hcon);
+ }
+
+ sk->state = BT_CLOSED;
+ sk->err = err;
+ sk->state_change(sk);
+
+ sk->zapped = 1;
+}
+
+static void sco_conn_ready(struct sco_conn *conn)
+{
+ struct sock *parent, *sk;
+
+ BT_DBG("conn %p", conn);
+
+ sco_conn_lock(conn);
+
+ if ((sk = conn->sk)) {
+ sco_sock_clear_timer(sk);
+ bh_lock_sock(sk);
+ sk->state = BT_CONNECTED;
+ sk->state_change(sk);
+ bh_unlock_sock(sk);
+ } else {
+ parent = sco_get_sock_listen(conn->src);
+ if (!parent)
+ goto done;
+
+ bh_lock_sock(parent);
+
+ sk = sco_sock_alloc(NULL, BTPROTO_SCO, GFP_ATOMIC);
+ if (!sk) {
+ bh_unlock_sock(parent);
+ goto done;
+ }
+
+ sco_sock_init(sk, parent);
+
+ bacpy(&bluez_pi(sk)->src, conn->src);
+ bacpy(&bluez_pi(sk)->dst, conn->dst);
+
+ hci_conn_hold(conn->hcon);
+ __sco_chan_add(conn, sk, parent);
+
+ sk->state = BT_CONNECTED;
+
+ /* Wake up parent */
+ parent->data_ready(parent, 1);
+
+ bh_unlock_sock(parent);
+ }
+
+done:
+ sco_conn_unlock(conn);
+}
+
+/* ----- SCO interface with lower layer (HCI) ----- */
+int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 type)
+{
+ BT_DBG("hdev %s, bdaddr %s", hdev->name, batostr(bdaddr));
+
+ /* Always accept connection */
+ return HCI_LM_ACCEPT;
+}
+
+int sco_connect_cfm(struct hci_conn *hcon, __u8 status)
+{
+ BT_DBG("hcon %p bdaddr %s status %d", hcon, batostr(&hcon->dst), status);
+
+ if (hcon->type != SCO_LINK)
+ return 0;
+
+ if (!status) {
+ struct sco_conn *conn;
+
+ conn = sco_conn_add(hcon, status);
+ if (conn)
+ sco_conn_ready(conn);
+ } else
+ sco_conn_del(hcon, bterr(status));
+
+ return 0;
+}
+
+int sco_disconn_ind(struct hci_conn *hcon, __u8 reason)
+{
+ BT_DBG("hcon %p reason %d", hcon, reason);
+
+ if (hcon->type != SCO_LINK)
+ return 0;
+
+ sco_conn_del(hcon, bterr(reason));
+ return 0;
+}
+
+int sco_recv_scodata(struct hci_conn *hcon, struct sk_buff *skb)
+{
+ struct sco_conn *conn = hcon->sco_data;
+
+ if (!conn)
+ goto drop;
+
+ BT_DBG("conn %p len %d", conn, skb->len);
+
+ if (skb->len) {
+ sco_recv_frame(conn, skb);
+ return 0;
+ }
+
+drop:
+ kfree_skb(skb);
+ return 0;
+}
+
+/* ----- Proc fs support ------ */
+static int sco_sock_dump(char *buf, struct bluez_sock_list *list)
+{
+ struct sco_pinfo *pi;
+ struct sock *sk;
+ char *ptr = buf;
+
+ write_lock_bh(&list->lock);
+
+ for (sk = list->head; sk; sk = sk->next) {
+ pi = sco_pi(sk);
+ ptr += sprintf(ptr, "%s %s %d\n",
+ batostr(&bluez_pi(sk)->src), batostr(&bluez_pi(sk)->dst),
+ sk->state);
+ }
+
+ write_unlock_bh(&list->lock);
+
+ ptr += sprintf(ptr, "\n");
+
+ return ptr - buf;
+}
+
+static int sco_read_proc(char *buf, char **start, off_t offset, int count, int *eof, void *priv)
+{
+ char *ptr = buf;
+ int len;
+
+ BT_DBG("count %d, offset %ld", count, offset);
+
+ ptr += sco_sock_dump(ptr, &sco_sk_list);
+ len = ptr - buf;
+
+ if (len <= count + offset)
+ *eof = 1;
+
+ *start = buf + offset;
+ len -= offset;
+
+ if (len > count)
+ len = count;
+ if (len < 0)
+ len = 0;
+
+ return len;
+}
+
+static struct proto_ops sco_sock_ops = {
+ family: PF_BLUETOOTH,
+ release: sco_sock_release,
+ bind: sco_sock_bind,
+ connect: sco_sock_connect,
+ listen: sco_sock_listen,
+ accept: sco_sock_accept,
+ getname: sco_sock_getname,
+ sendmsg: sco_sock_sendmsg,
+ recvmsg: bluez_sock_recvmsg,
+ poll: bluez_sock_poll,
+ socketpair: sock_no_socketpair,
+ ioctl: sock_no_ioctl,
+ shutdown: sock_no_shutdown,
+ setsockopt: sco_sock_setsockopt,
+ getsockopt: sco_sock_getsockopt,
+ mmap: sock_no_mmap
+};
+
+static struct net_proto_family sco_sock_family_ops = {
+ family: PF_BLUETOOTH,
+ create: sco_sock_create
+};
+
+static struct hci_proto sco_hci_proto = {
+ name: "SCO",
+ id: HCI_PROTO_SCO,
+ connect_ind: sco_connect_ind,
+ connect_cfm: sco_connect_cfm,
+ disconn_ind: sco_disconn_ind,
+ recv_scodata: sco_recv_scodata,
+};
+
+int __init sco_init(void)
+{
+ int err;
+
+ if ((err = bluez_sock_register(BTPROTO_SCO, &sco_sock_family_ops))) {
+ BT_ERR("Can't register SCO socket layer");
+ return err;
+ }
+
+ if ((err = hci_register_proto(&sco_hci_proto))) {
+ BT_ERR("Can't register SCO protocol");
+ return err;
+ }
+
+ create_proc_read_entry("bluetooth/sco", 0, 0, sco_read_proc, NULL);
+
+ BT_INFO("BlueZ SCO ver %s Copyright (C) 2000,2001 Qualcomm Inc", VERSION);
+ BT_INFO("Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>");
+ return 0;
+}
+
+void sco_cleanup(void)
+{
+ int err;
+
+ remove_proc_entry("bluetooth/sco", NULL);
+
+ /* Unregister socket, protocol and notifier */
+ if ((err = bluez_sock_unregister(BTPROTO_SCO)))
+ BT_ERR("Can't unregister SCO socket layer %d", err);
+
+ if ((err = hci_unregister_proto(&sco_hci_proto)))
+ BT_ERR("Can't unregister SCO protocol %d", err);
+}
+
+module_init(sco_init);
+module_exit(sco_cleanup);
+
+MODULE_AUTHOR("Maxim Krasnyansky <maxk@qualcomm.com>");
+MODULE_DESCRIPTION("BlueZ SCO ver " VERSION);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bluetooth/syms.c b/uClinux-2.4.31-uc0/net/bluetooth/syms.c
new file mode 100644
index 0000000..9740d5f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bluetooth/syms.c
@@ -0,0 +1,81 @@
+/*
+ BlueZ - Bluetooth protocol stack for Linux
+ Copyright (C) 2000-2001 Qualcomm Incorporated
+
+ Written 2000,2001 by Maxim Krasnyansky <maxk@qualcomm.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY
+ CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
+ COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS
+ SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * BlueZ symbols.
+ *
+ * $Id: syms.c,v 1.1 2002/03/08 21:06:59 maxk Exp $
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/socket.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+/* HCI Core */
+EXPORT_SYMBOL(hci_register_dev);
+EXPORT_SYMBOL(hci_unregister_dev);
+EXPORT_SYMBOL(hci_suspend_dev);
+EXPORT_SYMBOL(hci_resume_dev);
+
+EXPORT_SYMBOL(hci_register_proto);
+EXPORT_SYMBOL(hci_unregister_proto);
+
+EXPORT_SYMBOL(hci_get_route);
+EXPORT_SYMBOL(hci_connect);
+EXPORT_SYMBOL(hci_dev_get);
+EXPORT_SYMBOL(hci_conn_auth);
+EXPORT_SYMBOL(hci_conn_encrypt);
+
+EXPORT_SYMBOL(hci_recv_frame);
+EXPORT_SYMBOL(hci_send_acl);
+EXPORT_SYMBOL(hci_send_sco);
+EXPORT_SYMBOL(hci_send_cmd);
+EXPORT_SYMBOL(hci_si_event);
+
+/* BlueZ lib */
+EXPORT_SYMBOL(bluez_dump);
+EXPORT_SYMBOL(baswap);
+EXPORT_SYMBOL(batostr);
+EXPORT_SYMBOL(bterr);
+
+/* BlueZ sockets */
+EXPORT_SYMBOL(bluez_sock_register);
+EXPORT_SYMBOL(bluez_sock_unregister);
+EXPORT_SYMBOL(bluez_sock_init);
+EXPORT_SYMBOL(bluez_sock_link);
+EXPORT_SYMBOL(bluez_sock_unlink);
+EXPORT_SYMBOL(bluez_sock_recvmsg);
+EXPORT_SYMBOL(bluez_sock_poll);
+EXPORT_SYMBOL(bluez_accept_enqueue);
+EXPORT_SYMBOL(bluez_accept_dequeue);
+EXPORT_SYMBOL(bluez_sock_wait_state);
diff --git a/uClinux-2.4.31-uc0/net/bridge/Makefile b/uClinux-2.4.31-uc0/net/bridge/Makefile
new file mode 100644
index 0000000..227a8ed
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/Makefile
@@ -0,0 +1,23 @@
+#
+# Makefile for the IEEE 802.1d ethernet bridging layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+export-objs := br.o
+
+O_TARGET := bridge.o
+obj-y := br.o br_device.o br_fdb.o br_forward.o br_if.o br_input.o \
+ br_ioctl.o br_notify.o br_stp.o br_stp_bpdu.o \
+ br_stp_if.o br_stp_timer.o
+
+ifeq ($(CONFIG_NETFILTER),y)
+obj-y += br_netfilter.o
+endif
+
+obj-m := $(O_TARGET)
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/bridge/br.c b/uClinux-2.4.31-uc0/net/bridge/br.c
new file mode 100644
index 0000000..0a9b3e8
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br.c
@@ -0,0 +1,89 @@
+/*
+ * Generic parts
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br.c,v 1.46.2.1 2001/12/24 00:56:13 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/init.h>
+#include <linux/if_bridge.h>
+#include <linux/brlock.h>
+#include <linux/rtnetlink.h>
+#include <asm/uaccess.h>
+#include "br_private.h"
+
+#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)
+#include "../atm/lec.h"
+#endif
+
+int (*br_should_route_hook) (struct sk_buff **pskb) = NULL;
+
+void br_dec_use_count()
+{
+ MOD_DEC_USE_COUNT;
+}
+
+void br_inc_use_count()
+{
+ MOD_INC_USE_COUNT;
+}
+
+static int __init br_init(void)
+{
+ printk(KERN_INFO "NET4: Ethernet Bridge 008 for NET4.0\n");
+
+#ifdef CONFIG_NETFILTER
+ if (br_netfilter_init())
+ return 1;
+#endif
+ br_handle_frame_hook = br_handle_frame;
+ br_ioctl_hook = br_ioctl_deviceless_stub;
+#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)
+ br_fdb_get_hook = br_fdb_get;
+ br_fdb_put_hook = br_fdb_put;
+#endif
+ register_netdevice_notifier(&br_device_notifier);
+
+ return 0;
+}
+
+static void __exit br_deinit(void)
+{
+#ifdef CONFIG_NETFILTER
+ br_netfilter_fini();
+#endif
+ unregister_netdevice_notifier(&br_device_notifier);
+
+ rtnl_lock();
+ br_ioctl_hook = NULL;
+ rtnl_unlock();
+
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ br_handle_frame_hook = NULL;
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+
+#if defined(CONFIG_ATM_LANE) || defined(CONFIG_ATM_LANE_MODULE)
+ br_fdb_get_hook = NULL;
+ br_fdb_put_hook = NULL;
+#endif
+}
+
+EXPORT_SYMBOL(br_should_route_hook);
+
+module_init(br_init)
+module_exit(br_deinit)
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_device.c b/uClinux-2.4.31-uc0/net/bridge/br_device.c
new file mode 100644
index 0000000..c05522b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_device.c
@@ -0,0 +1,137 @@
+/*
+ * Device handling code
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br_device.c,v 1.5.2.1 2001/12/24 00:59:27 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/if_bridge.h>
+#include <asm/uaccess.h>
+#include "br_private.h"
+
+static int br_dev_do_ioctl(struct net_device *dev, struct ifreq *rq, int cmd)
+{
+ unsigned long args[4];
+ unsigned long *data;
+
+ if (cmd != SIOCDEVPRIVATE)
+ return -EOPNOTSUPP;
+
+ data = (unsigned long *)rq->ifr_data;
+ if (copy_from_user(args, data, 4*sizeof(unsigned long)))
+ return -EFAULT;
+
+ return br_ioctl(dev->priv, args[0], args[1], args[2], args[3]);
+}
+
+static struct net_device_stats *br_dev_get_stats(struct net_device *dev)
+{
+ struct net_bridge *br;
+
+ br = dev->priv;
+
+ return &br->statistics;
+}
+
+static int __br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_bridge *br;
+ unsigned char *dest;
+ struct net_bridge_fdb_entry *dst;
+
+ br = dev->priv;
+ br->statistics.tx_packets++;
+ br->statistics.tx_bytes += skb->len;
+
+ dest = skb->mac.raw = skb->data;
+ skb_pull(skb, ETH_HLEN);
+
+ if (dest[0] & 1) {
+ br_flood_deliver(br, skb, 0);
+ return 0;
+ }
+
+ if ((dst = br_fdb_get(br, dest)) != NULL) {
+ br_deliver(dst->dst, skb);
+ br_fdb_put(dst);
+ return 0;
+ }
+
+ br_flood_deliver(br, skb, 0);
+ return 0;
+}
+
+int br_dev_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_bridge *br;
+ int ret;
+
+ br = dev->priv;
+ read_lock(&br->lock);
+ ret = __br_dev_xmit(skb, dev);
+ read_unlock(&br->lock);
+
+ return ret;
+}
+
+static int br_dev_open(struct net_device *dev)
+{
+ struct net_bridge *br;
+
+ netif_start_queue(dev);
+
+ br = dev->priv;
+ read_lock(&br->lock);
+ br_stp_enable_bridge(br);
+ read_unlock(&br->lock);
+
+ return 0;
+}
+
+static void br_dev_set_multicast_list(struct net_device *dev)
+{
+}
+
+static int br_dev_stop(struct net_device *dev)
+{
+ struct net_bridge *br;
+
+ br = dev->priv;
+ read_lock(&br->lock);
+ br_stp_disable_bridge(br);
+ read_unlock(&br->lock);
+
+ netif_stop_queue(dev);
+
+ return 0;
+}
+
+static int br_dev_accept_fastpath(struct net_device *dev, struct dst_entry *dst)
+{
+ return -1;
+}
+
+void br_dev_setup(struct net_device *dev)
+{
+ memset(dev->dev_addr, 0, ETH_ALEN);
+
+ dev->do_ioctl = br_dev_do_ioctl;
+ dev->get_stats = br_dev_get_stats;
+ dev->hard_start_xmit = br_dev_xmit;
+ dev->open = br_dev_open;
+ dev->set_multicast_list = br_dev_set_multicast_list;
+ dev->stop = br_dev_stop;
+ dev->accept_fastpath = br_dev_accept_fastpath;
+ dev->tx_queue_len = 0;
+ dev->set_mac_address = NULL;
+}
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_fdb.c b/uClinux-2.4.31-uc0/net/bridge/br_fdb.c
new file mode 100644
index 0000000..78b8223
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_fdb.c
@@ -0,0 +1,331 @@
+/*
+ * Forwarding database
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br_fdb.c,v 1.5.2.1 2002/01/17 00:59:01 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/if_bridge.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+#include "br_private.h"
+
+static __inline__ unsigned long __timeout(struct net_bridge *br)
+{
+ unsigned long timeout;
+
+ timeout = jiffies - br->ageing_time;
+ if (br->topology_change)
+ timeout = jiffies - br->forward_delay;
+
+ return timeout;
+}
+
+static __inline__ int has_expired(struct net_bridge *br,
+ struct net_bridge_fdb_entry *fdb)
+{
+ if (!fdb->is_static &&
+ time_before_eq(fdb->ageing_timer, __timeout(br)))
+ return 1;
+
+ return 0;
+}
+
+static __inline__ void copy_fdb(struct __fdb_entry *ent, struct net_bridge_fdb_entry *f)
+{
+ memset(ent, 0, sizeof(struct __fdb_entry));
+ memcpy(ent->mac_addr, f->addr.addr, ETH_ALEN);
+ ent->port_no = f->dst?f->dst->port_no:0;
+ ent->is_local = f->is_local;
+ ent->ageing_timer_value = 0;
+ if (!f->is_static)
+ ent->ageing_timer_value = jiffies - f->ageing_timer;
+}
+
+static __inline__ int br_mac_hash(unsigned char *mac)
+{
+ unsigned long x;
+
+ x = mac[0];
+ x = (x << 2) ^ mac[1];
+ x = (x << 2) ^ mac[2];
+ x = (x << 2) ^ mac[3];
+ x = (x << 2) ^ mac[4];
+ x = (x << 2) ^ mac[5];
+
+ x ^= x >> 8;
+
+ return x & (BR_HASH_SIZE - 1);
+}
+
+static __inline__ void __hash_link(struct net_bridge *br,
+ struct net_bridge_fdb_entry *ent,
+ int hash)
+{
+ ent->next_hash = br->hash[hash];
+ if (ent->next_hash != NULL)
+ ent->next_hash->pprev_hash = &ent->next_hash;
+ br->hash[hash] = ent;
+ ent->pprev_hash = &br->hash[hash];
+}
+
+static __inline__ void __hash_unlink(struct net_bridge_fdb_entry *ent)
+{
+ *(ent->pprev_hash) = ent->next_hash;
+ if (ent->next_hash != NULL)
+ ent->next_hash->pprev_hash = ent->pprev_hash;
+ ent->next_hash = NULL;
+ ent->pprev_hash = NULL;
+}
+
+
+
+void br_fdb_changeaddr(struct net_bridge_port *p, unsigned char *newaddr)
+{
+ struct net_bridge *br;
+ int i;
+
+ br = p->br;
+ write_lock_bh(&br->hash_lock);
+ for (i=0;i<BR_HASH_SIZE;i++) {
+ struct net_bridge_fdb_entry *f;
+
+ f = br->hash[i];
+ while (f != NULL) {
+ if (f->dst == p && f->is_local) {
+ __hash_unlink(f);
+ memcpy(f->addr.addr, newaddr, ETH_ALEN);
+ __hash_link(br, f, br_mac_hash(newaddr));
+ write_unlock_bh(&br->hash_lock);
+ return;
+ }
+ f = f->next_hash;
+ }
+ }
+ write_unlock_bh(&br->hash_lock);
+}
+
+void br_fdb_cleanup(struct net_bridge *br)
+{
+ int i;
+ unsigned long timeout;
+
+ timeout = __timeout(br);
+
+ write_lock_bh(&br->hash_lock);
+ for (i=0;i<BR_HASH_SIZE;i++) {
+ struct net_bridge_fdb_entry *f;
+
+ f = br->hash[i];
+ while (f != NULL) {
+ struct net_bridge_fdb_entry *g;
+
+ g = f->next_hash;
+ if (!f->is_static &&
+ time_before_eq(f->ageing_timer, timeout)) {
+ __hash_unlink(f);
+ br_fdb_put(f);
+ }
+ f = g;
+ }
+ }
+ write_unlock_bh(&br->hash_lock);
+}
+
+void br_fdb_delete_by_port(struct net_bridge *br, struct net_bridge_port *p)
+{
+ int i;
+
+ write_lock_bh(&br->hash_lock);
+ for (i=0;i<BR_HASH_SIZE;i++) {
+ struct net_bridge_fdb_entry *f;
+
+ f = br->hash[i];
+ while (f != NULL) {
+ struct net_bridge_fdb_entry *g;
+
+ g = f->next_hash;
+ if (f->dst == p) {
+ __hash_unlink(f);
+ br_fdb_put(f);
+ }
+ f = g;
+ }
+ }
+ write_unlock_bh(&br->hash_lock);
+}
+
+struct net_bridge_fdb_entry *br_fdb_get(struct net_bridge *br, unsigned char *addr)
+{
+ struct net_bridge_fdb_entry *fdb;
+
+ read_lock_bh(&br->hash_lock);
+ fdb = br->hash[br_mac_hash(addr)];
+ while (fdb != NULL) {
+ if (!memcmp(fdb->addr.addr, addr, ETH_ALEN)) {
+ if (!has_expired(br, fdb)) {
+ atomic_inc(&fdb->use_count);
+ read_unlock_bh(&br->hash_lock);
+ return fdb;
+ }
+
+ read_unlock_bh(&br->hash_lock);
+ return NULL;
+ }
+
+ fdb = fdb->next_hash;
+ }
+
+ read_unlock_bh(&br->hash_lock);
+ return NULL;
+}
+
+void br_fdb_put(struct net_bridge_fdb_entry *ent)
+{
+ if (atomic_dec_and_test(&ent->use_count))
+ kfree(ent);
+}
+
+int br_fdb_get_entries(struct net_bridge *br,
+ unsigned char *_buf,
+ int maxnum,
+ int offset)
+{
+ int i;
+ int num;
+ struct __fdb_entry *walk;
+
+ num = 0;
+ walk = (struct __fdb_entry *)_buf;
+
+ read_lock_bh(&br->hash_lock);
+ for (i=0;i<BR_HASH_SIZE;i++) {
+ struct net_bridge_fdb_entry *f;
+
+ f = br->hash[i];
+ while (f != NULL && num < maxnum) {
+ struct __fdb_entry ent;
+ int err;
+ struct net_bridge_fdb_entry *g;
+ struct net_bridge_fdb_entry **pp;
+
+ if (has_expired(br, f)) {
+ f = f->next_hash;
+ continue;
+ }
+
+ if (offset) {
+ offset--;
+ f = f->next_hash;
+ continue;
+ }
+
+ copy_fdb(&ent, f);
+
+ atomic_inc(&f->use_count);
+ read_unlock_bh(&br->hash_lock);
+ err = copy_to_user(walk, &ent, sizeof(struct __fdb_entry));
+ read_lock_bh(&br->hash_lock);
+
+ g = f->next_hash;
+ pp = f->pprev_hash;
+ br_fdb_put(f);
+
+ if (err)
+ goto out_fault;
+
+ if (g == NULL && pp == NULL)
+ goto out_disappeared;
+
+ num++;
+ walk++;
+
+ f = g;
+ }
+ }
+
+ out:
+ read_unlock_bh(&br->hash_lock);
+ return num;
+
+ out_disappeared:
+ num = -EAGAIN;
+ goto out;
+
+ out_fault:
+ num = -EFAULT;
+ goto out;
+}
+
+static __inline__ void __fdb_possibly_replace(struct net_bridge_fdb_entry *fdb,
+ struct net_bridge_port *source,
+ int is_local)
+{
+ if (!fdb->is_static || is_local) {
+ fdb->dst = source;
+ fdb->is_local = is_local;
+ fdb->is_static = is_local;
+ fdb->ageing_timer = jiffies;
+ }
+}
+
+void br_fdb_insert(struct net_bridge *br,
+ struct net_bridge_port *source,
+ unsigned char *addr,
+ int is_local)
+{
+ struct net_bridge_fdb_entry *fdb;
+ int hash;
+
+ hash = br_mac_hash(addr);
+
+ write_lock_bh(&br->hash_lock);
+ fdb = br->hash[hash];
+ while (fdb != NULL) {
+ if (!memcmp(fdb->addr.addr, addr, ETH_ALEN)) {
+ /* attempt to update an entry for a local interface */
+ if (fdb->is_local) {
+ if (is_local)
+ printk(KERN_INFO "%s: attempt to add"
+ " interface with same source address.\n",
+ source->dev->name);
+ else if (net_ratelimit())
+ printk(KERN_WARNING "%s: received packet with "
+ " own address as source address\n",
+ source->dev->name);
+ goto out;
+ }
+
+ __fdb_possibly_replace(fdb, source, is_local);
+ goto out;
+ }
+
+ fdb = fdb->next_hash;
+ }
+
+ fdb = kmalloc(sizeof(*fdb), GFP_ATOMIC);
+ if (fdb == NULL)
+ goto out;
+
+ memcpy(fdb->addr.addr, addr, ETH_ALEN);
+ atomic_set(&fdb->use_count, 1);
+ fdb->dst = source;
+ fdb->is_local = is_local;
+ fdb->is_static = is_local;
+ fdb->ageing_timer = jiffies;
+
+ __hash_link(br, fdb, hash);
+
+ out:
+ write_unlock_bh(&br->hash_lock);
+}
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_forward.c b/uClinux-2.4.31-uc0/net/bridge/br_forward.c
new file mode 100644
index 0000000..502a0fc
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_forward.c
@@ -0,0 +1,155 @@
+/*
+ * Forwarding decision
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br_forward.c,v 1.4 2001/08/14 22:05:57 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_bridge.h>
+#include <linux/netfilter_bridge.h>
+#include "br_private.h"
+
+static inline int should_deliver(struct net_bridge_port *p, struct sk_buff *skb)
+{
+ if (skb->dev == p->dev ||
+ p->state != BR_STATE_FORWARDING)
+ return 0;
+
+ return 1;
+}
+
+int br_dev_queue_push_xmit(struct sk_buff *skb)
+{
+#ifdef CONFIG_NETFILTER
+ nf_bridge_maybe_copy_header(skb);
+#endif
+ skb_push(skb, ETH_HLEN);
+ dev_queue_xmit(skb);
+
+ return 0;
+}
+
+int br_forward_finish(struct sk_buff *skb)
+{
+ NF_HOOK(PF_BRIDGE, NF_BR_POST_ROUTING, skb, NULL, skb->dev,
+ br_dev_queue_push_xmit);
+
+ return 0;
+}
+
+static void __br_deliver(struct net_bridge_port *to, struct sk_buff *skb)
+{
+ skb->dev = to->dev;
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug = 0;
+#endif
+ NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_OUT, skb, NULL, skb->dev,
+ br_forward_finish);
+}
+
+static void __br_forward(struct net_bridge_port *to, struct sk_buff *skb)
+{
+ struct net_device *indev;
+
+ indev = skb->dev;
+ skb->dev = to->dev;
+ skb->ip_summed = CHECKSUM_NONE;
+
+ NF_HOOK(PF_BRIDGE, NF_BR_FORWARD, skb, indev, skb->dev,
+ br_forward_finish);
+}
+
+/* called under bridge lock */
+void br_deliver(struct net_bridge_port *to, struct sk_buff *skb)
+{
+ if (should_deliver(to, skb)) {
+ __br_deliver(to, skb);
+ return;
+ }
+
+ kfree_skb(skb);
+}
+
+/* called under bridge lock */
+void br_forward(struct net_bridge_port *to, struct sk_buff *skb)
+{
+ if (should_deliver(to, skb)) {
+ __br_forward(to, skb);
+ return;
+ }
+
+ kfree_skb(skb);
+}
+
+/* called under bridge lock */
+static void br_flood(struct net_bridge *br, struct sk_buff *skb, int clone,
+ void (*__packet_hook)(struct net_bridge_port *p, struct sk_buff *skb))
+{
+ struct net_bridge_port *p;
+ struct net_bridge_port *prev;
+
+ if (clone) {
+ struct sk_buff *skb2;
+
+ if ((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL) {
+ br->statistics.tx_dropped++;
+ return;
+ }
+
+ skb = skb2;
+ }
+
+ prev = NULL;
+
+ p = br->port_list;
+ while (p != NULL) {
+ if (should_deliver(p, skb)) {
+ if (prev != NULL) {
+ struct sk_buff *skb2;
+
+ if ((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL) {
+ br->statistics.tx_dropped++;
+ kfree_skb(skb);
+ return;
+ }
+
+ __packet_hook(prev, skb2);
+ }
+
+ prev = p;
+ }
+
+ p = p->next;
+ }
+
+ if (prev != NULL) {
+ __packet_hook(prev, skb);
+ return;
+ }
+
+ kfree_skb(skb);
+}
+
+/* called under bridge lock */
+void br_flood_deliver(struct net_bridge *br, struct sk_buff *skb, int clone)
+{
+ br_flood(br, skb, clone, __br_deliver);
+}
+
+/* called under bridge lock */
+void br_flood_forward(struct net_bridge *br, struct sk_buff *skb, int clone)
+{
+ br_flood(br, skb, clone, __br_forward);
+}
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_if.c b/uClinux-2.4.31-uc0/net/bridge/br_if.c
new file mode 100644
index 0000000..3c5018c
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_if.c
@@ -0,0 +1,291 @@
+/*
+ * Userspace interface
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br_if.c,v 1.6.2.1 2001/12/24 00:59:27 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/if_arp.h>
+#include <linux/if_bridge.h>
+#include <linux/inetdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/brlock.h>
+#include <linux/etherdevice.h>
+#include <asm/uaccess.h>
+#include "br_private.h"
+
+static int br_initial_port_cost(struct net_device *dev)
+{
+ if (!strncmp(dev->name, "lec", 3))
+ return 7;
+
+ if (!strncmp(dev->name, "eth", 3))
+ return 100; /* FIXME handle 100Mbps */
+
+ if (!strncmp(dev->name, "plip", 4))
+ return 2500;
+
+ return 100;
+}
+
+/* called under BR_NETPROTO_LOCK and bridge lock */
+static int __br_del_if(struct net_bridge *br, struct net_device *dev)
+{
+ struct net_bridge_port *p;
+ struct net_bridge_port **pptr;
+
+ if ((p = dev->br_port) == NULL || p->br != br)
+ return -EINVAL;
+
+ br_stp_disable_port(p);
+
+ dev_set_promiscuity(dev, -1);
+ dev->br_port = NULL;
+
+ pptr = &br->port_list;
+ while (*pptr != NULL) {
+ if (*pptr == p) {
+ *pptr = p->next;
+ break;
+ }
+
+ pptr = &((*pptr)->next);
+ }
+
+ br_fdb_delete_by_port(br, p);
+ kfree(p);
+ dev_put(dev);
+
+ return 0;
+}
+
+static void del_ifs(struct net_bridge *br)
+{
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ write_lock(&br->lock);
+ while (br->port_list != NULL)
+ __br_del_if(br, br->port_list->dev);
+ write_unlock(&br->lock);
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+}
+
+static struct net_bridge *new_nb(char *name)
+{
+ struct net_bridge *br;
+ struct net_device *dev;
+
+ if ((br = kmalloc(sizeof(*br), GFP_KERNEL)) == NULL)
+ return NULL;
+
+ memset(br, 0, sizeof(*br));
+ dev = &br->dev;
+
+ strncpy(dev->name, name, IFNAMSIZ);
+ dev->priv = br;
+ ether_setup(dev);
+ br_dev_setup(dev);
+
+ br->lock = RW_LOCK_UNLOCKED;
+ br->hash_lock = RW_LOCK_UNLOCKED;
+
+ br->bridge_id.prio[0] = 0x80;
+ br->bridge_id.prio[1] = 0x00;
+ memset(br->bridge_id.addr, 0, ETH_ALEN);
+
+ br->stp_enabled = 0;
+ br->designated_root = br->bridge_id;
+ br->root_path_cost = 0;
+ br->root_port = 0;
+ br->bridge_max_age = br->max_age = 20 * HZ;
+ br->bridge_hello_time = br->hello_time = 2 * HZ;
+ br->bridge_forward_delay = br->forward_delay = 15 * HZ;
+ br->topology_change = 0;
+ br->topology_change_detected = 0;
+ br_timer_clear(&br->hello_timer);
+ br_timer_clear(&br->tcn_timer);
+ br_timer_clear(&br->topology_change_timer);
+
+ br->ageing_time = 300 * HZ;
+ br->gc_interval = 4 * HZ;
+
+ return br;
+}
+
+/* called under bridge lock */
+static struct net_bridge_port *new_nbp(struct net_bridge *br, struct net_device *dev)
+{
+ int i;
+ struct net_bridge_port *p;
+
+ p = kmalloc(sizeof(*p), GFP_ATOMIC);
+ if (p == NULL)
+ return p;
+
+ memset(p, 0, sizeof(*p));
+ p->br = br;
+ p->dev = dev;
+ p->path_cost = br_initial_port_cost(dev);
+ p->priority = 0x80;
+
+ for (i=1;i<255;i++)
+ if (br_get_port(br, i) == NULL)
+ break;
+
+ if (i == 255) {
+ kfree(p);
+ return NULL;
+ }
+
+ dev->br_port = p;
+
+ p->port_no = i;
+ br_init_port(p);
+ p->state = BR_STATE_DISABLED;
+
+ p->next = br->port_list;
+ br->port_list = p;
+
+ return p;
+}
+
+int br_add_bridge(char *name)
+{
+ struct net_bridge *br;
+ struct net_device *dev;
+ int err;
+
+ if ((br = new_nb(name)) == NULL)
+ return -ENOMEM;
+
+ dev = &br->dev;
+ if (strchr(dev->name, '%')) {
+ err = dev_alloc_name(dev, dev->name);
+ if (err < 0)
+ goto out;
+ }
+
+ err = register_netdevice(dev);
+ if (err == 0)
+ br_inc_use_count();
+
+ out:
+ return err;
+}
+
+int br_del_bridge(char *name)
+{
+ struct net_device *dev;
+ struct net_bridge *br;
+
+ dev = __dev_get_by_name(name);
+ if (!dev)
+ return -ENXIO;
+
+ if (dev->hard_start_xmit != br_dev_xmit)
+ return -EPERM;
+
+ if (dev->flags & IFF_UP)
+ return -EBUSY;
+
+ br = dev->priv;
+ BUG_ON(&br->dev != dev);
+
+ del_ifs(br);
+
+ unregister_netdevice(dev);
+ kfree(br);
+ br_dec_use_count();
+
+ return 0;
+}
+
+int br_add_if(struct net_bridge *br, struct net_device *dev)
+{
+ struct net_bridge_port *p;
+
+ if (dev->br_port != NULL)
+ return -EBUSY;
+
+#if 0
+ if (dev->flags & IFF_LOOPBACK || dev->type != ARPHRD_ETHER)
+ return -EINVAL;
+#endif
+
+ if (dev->hard_start_xmit == br_dev_xmit)
+ return -ELOOP;
+
+ if (!is_valid_ether_addr(dev->dev_addr))
+ return -EADDRNOTAVAIL;
+
+ dev_hold(dev);
+ write_lock_bh(&br->lock);
+ if ((p = new_nbp(br, dev)) == NULL) {
+ write_unlock_bh(&br->lock);
+ dev_put(dev);
+ return -EXFULL;
+ }
+
+ dev_set_promiscuity(dev, 1);
+
+ br_stp_recalculate_bridge_id(br);
+ br_fdb_insert(br, p, dev->dev_addr, 1);
+ if ((br->dev.flags & IFF_UP) && (dev->flags & IFF_UP))
+ br_stp_enable_port(p);
+ write_unlock_bh(&br->lock);
+
+ return 0;
+}
+
+int br_del_if(struct net_bridge *br, struct net_device *dev)
+{
+ int retval;
+
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ write_lock(&br->lock);
+ retval = __br_del_if(br, dev);
+ br_stp_recalculate_bridge_id(br);
+ write_unlock(&br->lock);
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+
+ return retval;
+}
+
+int br_get_bridge_ifindices(int *indices, int num, int user_buf)
+{
+ struct net_device *dev;
+ int i = 0;
+
+ for (dev = dev_base; i < num && dev != NULL; dev = dev->next) {
+ if (dev->hard_start_xmit == br_dev_xmit)
+ if (user_buf) {
+ if (copy_to_user(indices+i, &dev->ifindex, sizeof(dev->ifindex)))
+ return -EFAULT;
+ i++;
+ } else {
+ indices[i++] = dev->ifindex;
+ }
+ }
+
+ return i;
+}
+
+/* called under ioctl_lock */
+void br_get_port_ifindices(struct net_bridge *br, int *ifindices)
+{
+ struct net_bridge_port *p;
+
+ p = br->port_list;
+ while (p != NULL) {
+ ifindices[p->port_no] = p->dev->ifindex;
+ p = p->next;
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_input.c b/uClinux-2.4.31-uc0/net/bridge/br_input.c
new file mode 100644
index 0000000..c1c0b48
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_input.c
@@ -0,0 +1,182 @@
+/*
+ * Handle incoming frames
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br_input.c,v 1.9.2.1 2001/12/24 04:50:05 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/if_bridge.h>
+#include <linux/netfilter_bridge.h>
+#include "br_private.h"
+
+unsigned char bridge_ula[6] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 };
+
+static int br_pass_frame_up_finish(struct sk_buff *skb)
+{
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug = 0;
+#endif
+ netif_rx(skb);
+
+ return 0;
+}
+
+static void br_pass_frame_up(struct net_bridge *br, struct sk_buff *skb)
+{
+ struct net_device *indev;
+
+ br->statistics.rx_packets++;
+ br->statistics.rx_bytes += skb->len;
+
+ indev = skb->dev;
+ skb->dev = &br->dev;
+ skb->pkt_type = PACKET_HOST;
+ skb_push(skb, ETH_HLEN);
+ skb->protocol = eth_type_trans(skb, &br->dev);
+
+ NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_IN, skb, indev, NULL,
+ br_pass_frame_up_finish);
+}
+
+int br_handle_frame_finish(struct sk_buff *skb)
+{
+ struct net_bridge *br;
+ unsigned char *dest;
+ struct net_bridge_fdb_entry *dst;
+ struct net_bridge_port *p;
+ int passedup;
+
+ dest = skb->mac.ethernet->h_dest;
+
+ p = skb->dev->br_port;
+ if (p == NULL)
+ goto err_nolock;
+
+ br = p->br;
+ read_lock(&br->lock);
+ if (skb->dev->br_port == NULL)
+ goto err;
+
+ passedup = 0;
+ if (br->dev.flags & IFF_PROMISC) {
+ struct sk_buff *skb2;
+
+ skb2 = skb_clone(skb, GFP_ATOMIC);
+ if (skb2 != NULL) {
+ passedup = 1;
+ br_pass_frame_up(br, skb2);
+ }
+ }
+
+ if (dest[0] & 1) {
+ br_flood_forward(br, skb, !passedup);
+ if (!passedup)
+ br_pass_frame_up(br, skb);
+ goto out;
+ }
+
+ dst = br_fdb_get(br, dest);
+ if (dst != NULL && dst->is_local) {
+ if (!passedup)
+ br_pass_frame_up(br, skb);
+ else
+ kfree_skb(skb);
+ br_fdb_put(dst);
+ goto out;
+ }
+
+ if (dst != NULL) {
+ br_forward(dst->dst, skb);
+ br_fdb_put(dst);
+ goto out;
+ }
+
+ br_flood_forward(br, skb, 0);
+
+out:
+ read_unlock(&br->lock);
+ return 0;
+
+err:
+ read_unlock(&br->lock);
+err_nolock:
+ kfree_skb(skb);
+ return 0;
+}
+
+int br_handle_frame(struct sk_buff *skb)
+{
+ struct net_bridge *br;
+ unsigned char *dest;
+ struct net_bridge_port *p;
+
+ dest = skb->mac.ethernet->h_dest;
+
+ p = skb->dev->br_port;
+ if (p == NULL)
+ goto err_nolock;
+
+ br = p->br;
+ read_lock(&br->lock);
+ if (skb->dev->br_port == NULL)
+ goto err;
+
+ if (!(br->dev.flags & IFF_UP) ||
+ p->state == BR_STATE_DISABLED)
+ goto err;
+
+ if (skb->mac.ethernet->h_source[0] & 1)
+ goto err;
+
+ if (p->state == BR_STATE_LEARNING ||
+ p->state == BR_STATE_FORWARDING)
+ br_fdb_insert(br, p, skb->mac.ethernet->h_source, 0);
+
+ if (br->stp_enabled &&
+ !memcmp(dest, bridge_ula, 5) &&
+ !(dest[5] & 0xF0))
+ goto handle_special_frame;
+
+ if (p->state == BR_STATE_FORWARDING) {
+ if (br_should_route_hook && br_should_route_hook(&skb)) {
+ read_unlock(&br->lock);
+ return -1;
+ }
+
+ if (!memcmp(p->br->dev.dev_addr, dest, ETH_ALEN))
+ skb->pkt_type = PACKET_HOST;
+
+ NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
+ br_handle_frame_finish);
+ read_unlock(&br->lock);
+ return 0;
+ }
+
+err:
+ read_unlock(&br->lock);
+err_nolock:
+ kfree_skb(skb);
+ return 0;
+
+handle_special_frame:
+ if (!dest[5]) {
+ br_stp_handle_bpdu(skb);
+ read_unlock(&br->lock);
+ return 0;
+ }
+
+ read_unlock(&br->lock);
+ kfree_skb(skb);
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_ioctl.c b/uClinux-2.4.31-uc0/net/bridge/br_ioctl.c
new file mode 100644
index 0000000..e044173
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_ioctl.c
@@ -0,0 +1,248 @@
+/*
+ * Ioctl handler
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br_ioctl.c,v 1.4 2000/11/08 05:16:40 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/if_bridge.h>
+#include <linux/inetdevice.h>
+#include <linux/rtnetlink.h>
+#include <asm/uaccess.h>
+#include "br_private.h"
+
+static int br_ioctl_device(struct net_bridge *br,
+ unsigned int cmd,
+ unsigned long arg0,
+ unsigned long arg1,
+ unsigned long arg2)
+{
+ if (br == NULL)
+ return -EINVAL;
+
+ switch (cmd)
+ {
+ case BRCTL_ADD_IF:
+ case BRCTL_DEL_IF:
+ {
+ struct net_device *dev;
+ int ret;
+
+ dev = dev_get_by_index(arg0);
+ if (dev == NULL)
+ return -EINVAL;
+
+ if (cmd == BRCTL_ADD_IF)
+ ret = br_add_if(br, dev);
+ else
+ ret = br_del_if(br, dev);
+
+ dev_put(dev);
+ return ret;
+ }
+
+ case BRCTL_GET_BRIDGE_INFO:
+ {
+ struct __bridge_info b;
+
+ memset(&b, 0, sizeof(struct __bridge_info));
+ memcpy(&b.designated_root, &br->designated_root, 8);
+ memcpy(&b.bridge_id, &br->bridge_id, 8);
+ b.root_path_cost = br->root_path_cost;
+ b.max_age = br->max_age;
+ b.hello_time = br->hello_time;
+ b.forward_delay = br->forward_delay;
+ b.bridge_max_age = br->bridge_max_age;
+ b.bridge_hello_time = br->bridge_hello_time;
+ b.bridge_forward_delay = br->bridge_forward_delay;
+ b.topology_change = br->topology_change;
+ b.topology_change_detected = br->topology_change_detected;
+ b.root_port = br->root_port;
+ b.stp_enabled = br->stp_enabled;
+ b.ageing_time = br->ageing_time;
+ b.gc_interval = br->gc_interval;
+ b.hello_timer_value = br_timer_get_residue(&br->hello_timer);
+ b.tcn_timer_value = br_timer_get_residue(&br->tcn_timer);
+ b.topology_change_timer_value = br_timer_get_residue(&br->topology_change_timer);
+ b.gc_timer_value = br_timer_get_residue(&br->gc_timer);
+
+ if (copy_to_user((void *)arg0, &b, sizeof(b)))
+ return -EFAULT;
+
+ return 0;
+ }
+
+ case BRCTL_GET_PORT_LIST:
+ {
+ int i;
+ int indices[256];
+
+ for (i=0;i<256;i++)
+ indices[i] = 0;
+
+ br_get_port_ifindices(br, indices);
+ if (copy_to_user((void *)arg0, indices, 256*sizeof(int)))
+ return -EFAULT;
+
+ return 0;
+ }
+
+ case BRCTL_SET_BRIDGE_FORWARD_DELAY:
+ br->bridge_forward_delay = arg0;
+ if (br_is_root_bridge(br))
+ br->forward_delay = arg0;
+ return 0;
+
+ case BRCTL_SET_BRIDGE_HELLO_TIME:
+ br->bridge_hello_time = arg0;
+ if (br_is_root_bridge(br))
+ br->hello_time = arg0;
+ return 0;
+
+ case BRCTL_SET_BRIDGE_MAX_AGE:
+ br->bridge_max_age = arg0;
+ if (br_is_root_bridge(br))
+ br->max_age = arg0;
+ return 0;
+
+ case BRCTL_SET_AGEING_TIME:
+ br->ageing_time = arg0;
+ return 0;
+
+ case BRCTL_SET_GC_INTERVAL:
+ br->gc_interval = arg0;
+ return 0;
+
+ case BRCTL_GET_PORT_INFO:
+ {
+ struct __port_info p;
+ struct net_bridge_port *pt;
+
+ if ((pt = br_get_port(br, arg1)) == NULL)
+ return -EINVAL;
+
+ memset(&p, 0, sizeof(struct __port_info));
+ memcpy(&p.designated_root, &pt->designated_root, 8);
+ memcpy(&p.designated_bridge, &pt->designated_bridge, 8);
+ p.port_id = pt->port_id;
+ p.designated_port = pt->designated_port;
+ p.path_cost = pt->path_cost;
+ p.designated_cost = pt->designated_cost;
+ p.state = pt->state;
+ p.top_change_ack = pt->topology_change_ack;
+ p.config_pending = pt->config_pending;
+ p.message_age_timer_value = br_timer_get_residue(&pt->message_age_timer);
+ p.forward_delay_timer_value = br_timer_get_residue(&pt->forward_delay_timer);
+ p.hold_timer_value = br_timer_get_residue(&pt->hold_timer);
+
+ if (copy_to_user((void *)arg0, &p, sizeof(p)))
+ return -EFAULT;
+
+ return 0;
+ }
+
+ case BRCTL_SET_BRIDGE_STP_STATE:
+ br->stp_enabled = arg0?1:0;
+ return 0;
+
+ case BRCTL_SET_BRIDGE_PRIORITY:
+ br_stp_set_bridge_priority(br, arg0);
+ return 0;
+
+ case BRCTL_SET_PORT_PRIORITY:
+ {
+ struct net_bridge_port *p;
+
+ if ((p = br_get_port(br, arg0)) == NULL)
+ return -EINVAL;
+ br_stp_set_port_priority(p, arg1);
+ return 0;
+ }
+
+ case BRCTL_SET_PATH_COST:
+ {
+ struct net_bridge_port *p;
+
+ if ((p = br_get_port(br, arg0)) == NULL)
+ return -EINVAL;
+ br_stp_set_path_cost(p, arg1);
+ return 0;
+ }
+
+ case BRCTL_GET_FDB_ENTRIES:
+ return br_fdb_get_entries(br, (void *)arg0, arg1, arg2);
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static int br_ioctl_deviceless(unsigned int cmd,
+ unsigned long arg0,
+ unsigned long arg1)
+{
+ switch (cmd)
+ {
+ case BRCTL_GET_VERSION:
+ return BRCTL_VERSION;
+
+ case BRCTL_GET_BRIDGES:
+ {
+ arg1 = br_get_bridge_ifindices((void *)arg0, arg1, 1);
+
+ return arg1;
+ }
+
+ case BRCTL_ADD_BRIDGE:
+ case BRCTL_DEL_BRIDGE:
+ {
+ char buf[IFNAMSIZ];
+
+ if (copy_from_user(buf, (void *)arg0, IFNAMSIZ))
+ return -EFAULT;
+
+ buf[IFNAMSIZ-1] = 0;
+
+ if (cmd == BRCTL_ADD_BRIDGE)
+ return br_add_bridge(buf);
+
+ return br_del_bridge(buf);
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+int br_ioctl_deviceless_stub(unsigned long arg)
+{
+ unsigned long i[3];
+ int err;
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (copy_from_user(i, (void *)arg, 3*sizeof(unsigned long)))
+ return -EFAULT;
+
+ rtnl_lock();
+ err = br_ioctl_deviceless(i[0], i[1], i[2]);
+ rtnl_unlock();
+ return err;
+}
+
+int br_ioctl(struct net_bridge *br, unsigned int cmd, unsigned long arg0, unsigned long arg1, unsigned long arg2)
+{
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ ASSERT_RTNL();
+ return br_ioctl_device(br, cmd, arg0, arg1, arg2);
+}
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_netfilter.c b/uClinux-2.4.31-uc0/net/bridge/br_netfilter.c
new file mode 100644
index 0000000..7ca9ef9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_netfilter.c
@@ -0,0 +1,720 @@
+/*
+ * Handle firewalling
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ * Bart De Schuymer (maintainer) <bdschuym@pandora.be>
+ *
+ * Changes:
+ * Apr 29 2003: physdev module support (bdschuym)
+ * Jun 19 2003: let arptables see bridged ARP traffic (bdschuym)
+ * Oct 06 2003: filter encapsulated IP/ARP VLAN traffic on untagged bridge
+ * (bdschuym)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Lennert dedicates this file to Kerstin Wurdinger.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/ip.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/if_ether.h>
+#include <linux/if_vlan.h>
+#include <linux/netfilter_bridge.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/in_route.h>
+#include <net/ip.h>
+#include <asm/uaccess.h>
+#include <asm/checksum.h>
+#include "br_private.h"
+
+
+#define skb_origaddr(skb) (((struct bridge_skb_cb *) \
+ (skb->cb))->daddr.ipv4)
+#define store_orig_dstaddr(skb) (skb_origaddr(skb) = (skb)->nh.iph->daddr)
+#define dnat_took_place(skb) (skb_origaddr(skb) != (skb)->nh.iph->daddr)
+#define clear_cb(skb) (memset(&skb_origaddr(skb), 0, \
+ sizeof(struct bridge_skb_cb)))
+
+#define has_bridge_parent(device) ((device)->br_port != NULL)
+#define bridge_parent(device) (&((device)->br_port->br->dev))
+
+#define IS_VLAN_IP (skb->protocol == __constant_htons(ETH_P_8021Q) && \
+ hdr->h_vlan_encapsulated_proto == __constant_htons(ETH_P_IP))
+
+/* We need these fake structures to make netfilter happy --
+ * lots of places assume that skb->dst != NULL, which isn't
+ * all that unreasonable.
+ *
+ * Currently, we fill in the PMTU entry because netfilter
+ * refragmentation needs it, and the rt_flags entry because
+ * ipt_REJECT needs it. Future netfilter modules might
+ * require us to fill additional fields.
+ */
+static struct net_device __fake_net_device = {
+ .hard_header_len = ETH_HLEN
+};
+
+static struct rtable __fake_rtable = {
+ u: {
+ dst: {
+ __refcnt: ATOMIC_INIT(1),
+ dev: &__fake_net_device,
+ pmtu: 1500
+ }
+ },
+
+ rt_flags: 0
+};
+
+
+/* PF_BRIDGE/PRE_ROUTING *********************************************/
+static void __br_dnat_complain(void)
+{
+ static unsigned long last_complaint;
+
+ if (jiffies - last_complaint >= 5 * HZ) {
+ printk(KERN_WARNING "Performing cross-bridge DNAT requires IP "
+ "forwarding to be enabled\n");
+ last_complaint = jiffies;
+ }
+}
+
+
+/* This requires some explaining. If DNAT has taken place,
+ * we will need to fix up the destination Ethernet address,
+ * and this is a tricky process.
+ *
+ * There are two cases to consider:
+ * 1. The packet was DNAT'ed to a device in the same bridge
+ * port group as it was received on. We can still bridge
+ * the packet.
+ * 2. The packet was DNAT'ed to a different device, either
+ * a non-bridged device or another bridge port group.
+ * The packet will need to be routed.
+ *
+ * The correct way of distinguishing between these two cases is to
+ * call ip_route_input() and to look at skb->dst->dev, which is
+ * changed to the destination device if ip_route_input() succeeds.
+ *
+ * Let us first consider the case that ip_route_input() succeeds:
+ *
+ * If skb->dst->dev equals the logical bridge device the packet
+ * came in on, we can consider this bridging. We then call
+ * skb->dst->output() which will make the packet enter br_nf_local_out()
+ * not much later. In that function it is assured that the iptables
+ * FORWARD chain is traversed for the packet.
+ *
+ * Otherwise, the packet is considered to be routed and we just
+ * change the destination MAC address so that the packet will
+ * later be passed up to the IP stack to be routed.
+ *
+ * Let us now consider the case that ip_route_input() fails:
+ *
+ * After a "echo '0' > /proc/sys/net/ipv4/ip_forward" ip_route_input()
+ * will fail, while __ip_route_output_key() will return success. The source
+ * address for __ip_route_output_key() is set to zero, so __ip_route_output_key
+ * thinks we're handling a locally generated packet and won't care
+ * if IP forwarding is allowed. We send a warning message to the users's
+ * log telling her to put IP forwarding on.
+ *
+ * ip_route_input() will also fail if there is no route available.
+ * In that case we just drop the packet.
+ *
+ * --Lennert, 20020411
+ * --Bart, 20020416 (updated)
+ * --Bart, 20021007 (updated)
+ */
+
+static int br_nf_pre_routing_finish_bridge(struct sk_buff *skb)
+{
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug |= (1 << NF_BR_PRE_ROUTING) | (1 << NF_BR_FORWARD);
+#endif
+
+ if (skb->pkt_type == PACKET_OTHERHOST) {
+ skb->pkt_type = PACKET_HOST;
+ skb->nf_bridge->mask |= BRNF_PKT_TYPE;
+ }
+ skb->nf_bridge->mask ^= BRNF_NF_BRIDGE_PREROUTING;
+
+ skb->dev = bridge_parent(skb->dev);
+ if (skb->protocol == __constant_htons(ETH_P_8021Q)) {
+ skb_pull(skb, VLAN_HLEN);
+ skb->nh.raw += VLAN_HLEN;
+ }
+ skb->dst->output(skb);
+ return 0;
+}
+
+static int br_nf_pre_routing_finish(struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+ struct iphdr *iph = skb->nh.iph;
+ struct nf_bridge_info *nf_bridge = skb->nf_bridge;
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug ^= (1 << NF_BR_PRE_ROUTING);
+#endif
+
+ if (nf_bridge->mask & BRNF_PKT_TYPE) {
+ skb->pkt_type = PACKET_OTHERHOST;
+ nf_bridge->mask ^= BRNF_PKT_TYPE;
+ }
+ nf_bridge->mask ^= BRNF_NF_BRIDGE_PREROUTING;
+
+ if (dnat_took_place(skb)) {
+ if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,
+ dev)) {
+ struct rtable *rt;
+
+ if (!ip_route_output(&rt, iph->daddr, 0, iph->tos, 0)) {
+ /* Bridged-and-DNAT'ed traffic doesn't
+ * require ip_forwarding.
+ */
+ if (((struct dst_entry *)rt)->dev == dev) {
+ skb->dst = (struct dst_entry *)rt;
+ goto bridged_dnat;
+ }
+ __br_dnat_complain();
+ dst_release((struct dst_entry *)rt);
+ }
+ kfree_skb(skb);
+ return 0;
+ } else {
+ if (skb->dst->dev == dev) {
+bridged_dnat:
+ /* Tell br_nf_local_out this is a
+ * bridged frame
+ */
+ nf_bridge->mask |= BRNF_BRIDGED_DNAT;
+ skb->dev = nf_bridge->physindev;
+ clear_cb(skb);
+ if (skb->protocol ==
+ __constant_htons(ETH_P_8021Q)) {
+ skb_push(skb, VLAN_HLEN);
+ skb->nh.raw -= VLAN_HLEN;
+ }
+ NF_HOOK_THRESH(PF_BRIDGE, NF_BR_PRE_ROUTING,
+ skb, skb->dev, NULL,
+ br_nf_pre_routing_finish_bridge,
+ 1);
+ return 0;
+ }
+ memcpy(skb->mac.ethernet->h_dest, dev->dev_addr,
+ ETH_ALEN);
+ skb->pkt_type = PACKET_HOST;
+ }
+ } else {
+ skb->dst = (struct dst_entry *)&__fake_rtable;
+ dst_hold(skb->dst);
+ }
+
+ clear_cb(skb);
+ skb->dev = nf_bridge->physindev;
+ if (skb->protocol == __constant_htons(ETH_P_8021Q)) {
+ skb_push(skb, VLAN_HLEN);
+ skb->nh.raw -= VLAN_HLEN;
+ }
+ NF_HOOK_THRESH(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
+ br_handle_frame_finish, 1);
+
+ return 0;
+}
+
+/* Replicate the checks that IPv4 does on packet reception.
+ * Set skb->dev to the bridge device (i.e. parent of the
+ * receiving device) to make netfilter happy, the REDIRECT
+ * target in particular. Save the original destination IP
+ * address to be able to detect DNAT afterwards.
+ */
+static unsigned int br_nf_pre_routing(unsigned int hook, struct sk_buff **pskb,
+ const struct net_device *in, const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ struct iphdr *iph;
+ __u32 len;
+ struct sk_buff *skb = *pskb;
+ struct nf_bridge_info *nf_bridge;
+
+ if (skb->protocol != __constant_htons(ETH_P_IP)) {
+ struct vlan_ethhdr *hdr = (struct vlan_ethhdr *)
+ ((*pskb)->mac.ethernet);
+
+ if (!IS_VLAN_IP)
+ return NF_ACCEPT;
+ if ((skb = skb_share_check(*pskb, GFP_ATOMIC)) == NULL)
+ goto out;
+ skb_pull(*pskb, VLAN_HLEN);
+ (*pskb)->nh.raw += VLAN_HLEN;
+ } else if ((skb = skb_share_check(*pskb, GFP_ATOMIC)) == NULL)
+ goto out;
+
+ if (!pskb_may_pull(skb, sizeof(struct iphdr)))
+ goto inhdr_error;
+
+ iph = skb->nh.iph;
+ if (iph->ihl < 5 || iph->version != 4)
+ goto inhdr_error;
+
+ if (!pskb_may_pull(skb, 4*iph->ihl))
+ goto inhdr_error;
+
+ iph = skb->nh.iph;
+ if (ip_fast_csum((__u8 *)iph, iph->ihl) != 0)
+ goto inhdr_error;
+
+ len = ntohs(iph->tot_len);
+ if (skb->len < len || len < 4*iph->ihl)
+ goto inhdr_error;
+
+ if (skb->len > len) {
+ __pskb_trim(skb, len);
+ if (skb->ip_summed == CHECKSUM_HW)
+ skb->ip_summed = CHECKSUM_NONE;
+ }
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug ^= (1 << NF_IP_PRE_ROUTING);
+#endif
+ if ((nf_bridge = nf_bridge_alloc(skb)) == NULL)
+ return NF_DROP;
+
+ if (skb->pkt_type == PACKET_OTHERHOST) {
+ skb->pkt_type = PACKET_HOST;
+ nf_bridge->mask |= BRNF_PKT_TYPE;
+ }
+
+ nf_bridge->mask |= BRNF_NF_BRIDGE_PREROUTING;
+ nf_bridge->physindev = skb->dev;
+ skb->dev = bridge_parent(skb->dev);
+ store_orig_dstaddr(skb);
+
+ NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, skb->dev, NULL,
+ br_nf_pre_routing_finish);
+
+ return NF_STOLEN;
+
+inhdr_error:
+// IP_INC_STATS_BH(IpInHdrErrors);
+out:
+ return NF_DROP;
+}
+
+
+/* PF_BRIDGE/LOCAL_IN ************************************************/
+/* The packet is locally destined, which requires a real
+ * dst_entry, so detach the fake one. On the way up, the
+ * packet would pass through PRE_ROUTING again (which already
+ * took place when the packet entered the bridge), but we
+ * register an IPv4 PRE_ROUTING 'sabotage' hook that will
+ * prevent this from happening.
+ */
+static unsigned int br_nf_local_in(unsigned int hook, struct sk_buff **pskb,
+ const struct net_device *in, const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ struct sk_buff *skb = *pskb;
+
+ if (skb->dst == (struct dst_entry *)&__fake_rtable) {
+ dst_release(skb->dst);
+ skb->dst = NULL;
+ }
+
+ return NF_ACCEPT;
+}
+
+/* PF_BRIDGE/FORWARD *************************************************/
+static int br_nf_forward_finish(struct sk_buff *skb)
+{
+ struct nf_bridge_info *nf_bridge = skb->nf_bridge;
+ struct net_device *in;
+ struct vlan_ethhdr *hdr = (struct vlan_ethhdr *)(skb->mac.ethernet);
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug ^= (1 << NF_BR_FORWARD);
+#endif
+
+ if (skb->protocol == __constant_htons(ETH_P_IP) || IS_VLAN_IP) {
+ in = nf_bridge->physindev;
+ if (nf_bridge->mask & BRNF_PKT_TYPE) {
+ skb->pkt_type = PACKET_OTHERHOST;
+ nf_bridge->mask ^= BRNF_PKT_TYPE;
+ }
+ } else {
+ in = *((struct net_device **)(skb->cb));
+ }
+ if (skb->protocol == __constant_htons(ETH_P_8021Q)) {
+ skb_push(skb, VLAN_HLEN);
+ skb->nh.raw -= VLAN_HLEN;
+ }
+ NF_HOOK_THRESH(PF_BRIDGE, NF_BR_FORWARD, skb, in,
+ skb->dev, br_forward_finish, 1);
+ return 0;
+}
+
+/* This is the 'purely bridged' case. For IP, we pass the packet to
+ * netfilter with indev and outdev set to the bridge device,
+ * but we are still able to filter on the 'real' indev/outdev
+ * because of the ipt_physdev.c module.
+ */
+static unsigned int br_nf_forward(unsigned int hook, struct sk_buff **pskb,
+ const struct net_device *in, const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ struct sk_buff *skb = *pskb;
+ struct nf_bridge_info *nf_bridge;
+ struct vlan_ethhdr *hdr = (struct vlan_ethhdr *)(skb->mac.ethernet);
+
+ if (skb->protocol != __constant_htons(ETH_P_IP)) {
+ if (!IS_VLAN_IP)
+ return NF_ACCEPT;
+ skb_pull(*pskb, VLAN_HLEN);
+ (*pskb)->nh.raw += VLAN_HLEN;
+ }
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug ^= (1 << NF_BR_FORWARD);
+#endif
+ nf_bridge = skb->nf_bridge;
+ if (skb->pkt_type == PACKET_OTHERHOST) {
+ skb->pkt_type = PACKET_HOST;
+ nf_bridge->mask |= BRNF_PKT_TYPE;
+ }
+
+ /* The physdev module checks on this */
+ nf_bridge->mask |= BRNF_BRIDGED;
+ nf_bridge->physoutdev = skb->dev;
+
+ NF_HOOK(PF_INET, NF_IP_FORWARD, skb, bridge_parent(in),
+ bridge_parent(out), br_nf_forward_finish);
+
+ return NF_STOLEN;
+}
+
+
+/* PF_BRIDGE/LOCAL_OUT ***********************************************/
+static int br_nf_local_out_finish(struct sk_buff *skb)
+{
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug &= ~(1 << NF_BR_LOCAL_OUT);
+#endif
+ if (skb->protocol == __constant_htons(ETH_P_8021Q)) {
+ skb_push(skb, VLAN_HLEN);
+ skb->nh.raw -= VLAN_HLEN;
+ }
+
+ NF_HOOK_THRESH(PF_BRIDGE, NF_BR_LOCAL_OUT, skb, NULL, skb->dev,
+ br_forward_finish, NF_BR_PRI_FIRST + 1);
+
+ return 0;
+}
+
+
+/* This function sees both locally originated IP packets and forwarded
+ * IP packets (in both cases the destination device is a bridge
+ * device). It also sees bridged-and-DNAT'ed packets.
+ * To be able to filter on the physical bridge devices (with the ipt_physdev.c
+ * module), we steal packets destined to a bridge device away from the
+ * PF_INET/FORWARD and PF_INET/OUTPUT hook functions, and give them back later,
+ * when we have determined the real output device. This is done in here.
+ *
+ * If (nf_bridge->mask & BRNF_BRIDGED_DNAT) then the packet is bridged
+ * and we fake the PF_BRIDGE/FORWARD hook. The function br_nf_forward()
+ * will then fake the PF_INET/FORWARD hook. br_nf_local_out() has priority
+ * NF_BR_PRI_FIRST, so no relevant PF_BRIDGE/INPUT functions have been nor
+ * will be executed.
+ * Otherwise, if nf_bridge->physindev is NULL, the bridge-nf code never touched
+ * this packet before, and so the packet was locally originated. We fake
+ * the PF_INET/LOCAL_OUT hook.
+ * Finally, if nf_bridge->physindev isn't NULL, then the packet was IP routed,
+ * so we fake the PF_INET/FORWARD hook. ipv4_sabotage_out() makes sure
+ * even routed packets that didn't arrive on a bridge interface have their
+ * nf_bridge->physindev set.
+ */
+
+static unsigned int br_nf_local_out(unsigned int hook, struct sk_buff **pskb,
+ const struct net_device *in, const struct net_device *out,
+ int (*_okfn)(struct sk_buff *))
+{
+ int (*okfn)(struct sk_buff *skb);
+ struct net_device *realindev;
+ struct sk_buff *skb = *pskb;
+ struct nf_bridge_info *nf_bridge;
+ struct vlan_ethhdr *hdr = (struct vlan_ethhdr *)(skb->mac.ethernet);
+
+ if (skb->protocol != __constant_htons(ETH_P_IP) && !IS_VLAN_IP)
+ return NF_ACCEPT;
+
+ /* Sometimes we get packets with NULL ->dst here (for example,
+ * running a dhcp client daemon triggers this).
+ */
+ if (skb->dst == NULL)
+ return NF_ACCEPT;
+
+ nf_bridge = skb->nf_bridge;
+ nf_bridge->physoutdev = skb->dev;
+
+ realindev = nf_bridge->physindev;
+
+ /* Bridged, take PF_BRIDGE/FORWARD.
+ * (see big note in front of br_nf_pre_routing_finish)
+ */
+ if (nf_bridge->mask & BRNF_BRIDGED_DNAT) {
+ okfn = br_forward_finish;
+
+ if (nf_bridge->mask & BRNF_PKT_TYPE) {
+ skb->pkt_type = PACKET_OTHERHOST;
+ nf_bridge->mask ^= BRNF_PKT_TYPE;
+ }
+ if (skb->protocol == __constant_htons(ETH_P_8021Q)) {
+ skb_push(skb, VLAN_HLEN);
+ skb->nh.raw -= VLAN_HLEN;
+ }
+
+ NF_HOOK(PF_BRIDGE, NF_BR_FORWARD, skb, realindev,
+ skb->dev, okfn);
+ } else {
+ struct net_device *realoutdev = bridge_parent(skb->dev);
+
+#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
+ /* iptables should match -o br0.x */
+ if (nf_bridge->netoutdev)
+ realoutdev = nf_bridge->netoutdev;
+#endif
+ okfn = br_nf_local_out_finish;
+ if (skb->protocol == __constant_htons(ETH_P_8021Q)) {
+ skb_pull(skb, VLAN_HLEN);
+ (*pskb)->nh.raw += VLAN_HLEN;
+ }
+ /* IP forwarded traffic has a physindev, locally
+ * generated traffic hasn't.
+ */
+ if (realindev != NULL) {
+ if (((nf_bridge->mask & BRNF_DONT_TAKE_PARENT) == 0) &&
+ has_bridge_parent(realindev))
+ realindev = bridge_parent(realindev);
+ NF_HOOK_THRESH(PF_INET, NF_IP_FORWARD, skb, realindev,
+ realoutdev, okfn,
+ NF_IP_PRI_BRIDGE_SABOTAGE_FORWARD + 1);
+ } else {
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug ^= (1 << NF_IP_LOCAL_OUT);
+#endif
+
+ NF_HOOK_THRESH(PF_INET, NF_IP_LOCAL_OUT, skb, realindev,
+ realoutdev, okfn,
+ NF_IP_PRI_BRIDGE_SABOTAGE_LOCAL_OUT + 1);
+ }
+ }
+
+ return NF_STOLEN;
+}
+
+
+/* PF_BRIDGE/POST_ROUTING ********************************************/
+static unsigned int br_nf_post_routing(unsigned int hook, struct sk_buff **pskb,
+ const struct net_device *in, const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ struct sk_buff *skb = *pskb;
+ struct nf_bridge_info *nf_bridge = (*pskb)->nf_bridge;
+ struct vlan_ethhdr *hdr = (struct vlan_ethhdr *)(skb->mac.ethernet);
+ struct net_device *realoutdev = bridge_parent(skb->dev);
+
+ /* Be very paranoid. Must be a device driver bug. */
+ if (skb->mac.raw < skb->head || skb->mac.raw + ETH_HLEN > skb->data) {
+ printk(KERN_CRIT "br_netfilter: Argh!! br_nf_post_routing: "
+ "bad mac.raw pointer.");
+ if (skb->dev != NULL) {
+ printk("[%s]", skb->dev->name);
+ if (has_bridge_parent(skb->dev))
+ printk("[%s]", bridge_parent(skb->dev)->name);
+ }
+ printk(" head:%p, raw:%p\n", skb->head, skb->mac.raw);
+ return NF_ACCEPT;
+ }
+
+ if (skb->protocol != __constant_htons(ETH_P_IP) && !IS_VLAN_IP)
+ return NF_ACCEPT;
+
+ /* Sometimes we get packets with NULL ->dst here (for example,
+ * running a dhcp client daemon triggers this).
+ */
+ if (skb->dst == NULL)
+ return NF_ACCEPT;
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug ^= (1 << NF_IP_POST_ROUTING);
+#endif
+
+ /* We assume any code from br_dev_queue_push_xmit onwards doesn't care
+ * about the value of skb->pkt_type.
+ */
+ if (skb->pkt_type == PACKET_OTHERHOST) {
+ skb->pkt_type = PACKET_HOST;
+ nf_bridge->mask |= BRNF_PKT_TYPE;
+ }
+
+ if (skb->protocol == __constant_htons(ETH_P_8021Q)) {
+ skb_pull(skb, VLAN_HLEN);
+ skb->nh.raw += VLAN_HLEN;
+ }
+
+ nf_bridge_save_header(skb);
+
+#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
+ if (nf_bridge->netoutdev)
+ realoutdev = nf_bridge->netoutdev;
+#endif
+ NF_HOOK(PF_INET, NF_IP_POST_ROUTING, skb, NULL,
+ realoutdev, br_dev_queue_push_xmit);
+
+ return NF_STOLEN;
+}
+
+
+/* IPv4/SABOTAGE *****************************************************/
+
+/* Don't hand locally destined packets to PF_INET/PRE_ROUTING
+ * for the second time.
+ */
+static unsigned int ipv4_sabotage_in(unsigned int hook, struct sk_buff **pskb,
+ const struct net_device *in, const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ if ((*pskb)->nf_bridge &&
+ !((*pskb)->nf_bridge->mask & BRNF_NF_BRIDGE_PREROUTING)) {
+ okfn(*pskb);
+ return NF_STOLEN;
+ }
+
+ return NF_ACCEPT;
+}
+
+/* Postpone execution of PF_INET/FORWARD, PF_INET/LOCAL_OUT
+ * and PF_INET/POST_ROUTING until we have done the forwarding
+ * decision in the bridge code and have determined skb->physoutdev.
+ */
+static unsigned int ipv4_sabotage_out(unsigned int hook, struct sk_buff **pskb,
+ const struct net_device *in, const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ if ((out->hard_start_xmit == br_dev_xmit &&
+ okfn != br_nf_forward_finish &&
+ okfn != br_nf_local_out_finish &&
+ okfn != br_dev_queue_push_xmit)
+#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
+ || ((out->priv_flags & IFF_802_1Q_VLAN) &&
+ VLAN_DEV_INFO(out)->real_dev->hard_start_xmit == br_dev_xmit)
+#endif
+ ) {
+ struct sk_buff *skb = *pskb;
+ struct nf_bridge_info *nf_bridge;
+
+ if (!skb->nf_bridge && !nf_bridge_alloc(skb))
+ return NF_DROP;
+
+ nf_bridge = skb->nf_bridge;
+
+ /* This frame will arrive on PF_BRIDGE/LOCAL_OUT and we
+ * will need the indev then. For a brouter, the real indev
+ * can be a bridge port, so we make sure br_nf_local_out()
+ * doesn't use the bridge parent of the indev by using
+ * the BRNF_DONT_TAKE_PARENT mask.
+ */
+ if (hook == NF_IP_FORWARD && nf_bridge->physindev == NULL) {
+ nf_bridge->mask &= BRNF_DONT_TAKE_PARENT;
+ nf_bridge->physindev = (struct net_device *)in;
+ }
+#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
+ /* the iptables outdev is br0.x, not br0 */
+ if (out->priv_flags & IFF_802_1Q_VLAN)
+ nf_bridge->netoutdev = (struct net_device *)out;
+#endif
+ okfn(skb);
+ return NF_STOLEN;
+ }
+
+ return NF_ACCEPT;
+}
+
+/* For br_nf_local_out we need (prio = NF_BR_PRI_FIRST), to insure that innocent
+ * PF_BRIDGE/NF_BR_LOCAL_OUT functions don't get bridged traffic as input.
+ * For br_nf_post_routing, we need (prio = NF_BR_PRI_LAST), because
+ * ip_refrag() can return NF_STOLEN.
+ */
+static struct nf_hook_ops br_nf_ops[] = {
+ { .hook = br_nf_pre_routing,
+ .pf = PF_BRIDGE,
+ .hooknum = NF_BR_PRE_ROUTING,
+ .priority = NF_BR_PRI_BRNF, },
+ { .hook = br_nf_local_in,
+ .pf = PF_BRIDGE,
+ .hooknum = NF_BR_LOCAL_IN,
+ .priority = NF_BR_PRI_BRNF, },
+ { .hook = br_nf_forward,
+ .pf = PF_BRIDGE,
+ .hooknum = NF_BR_FORWARD,
+ .priority = NF_BR_PRI_BRNF, },
+ { .hook = br_nf_local_out,
+ .pf = PF_BRIDGE,
+ .hooknum = NF_BR_LOCAL_OUT,
+ .priority = NF_BR_PRI_FIRST, },
+ { .hook = br_nf_post_routing,
+ .pf = PF_BRIDGE,
+ .hooknum = NF_BR_POST_ROUTING,
+ .priority = NF_BR_PRI_LAST, },
+ { .hook = ipv4_sabotage_in,
+ .pf = PF_INET,
+ .hooknum = NF_IP_PRE_ROUTING,
+ .priority = NF_IP_PRI_FIRST, },
+ { .hook = ipv4_sabotage_out,
+ .pf = PF_INET,
+ .hooknum = NF_IP_FORWARD,
+ .priority = NF_IP_PRI_BRIDGE_SABOTAGE_FORWARD, },
+ { .hook = ipv4_sabotage_out,
+ .pf = PF_INET,
+ .hooknum = NF_IP_LOCAL_OUT,
+ .priority = NF_IP_PRI_BRIDGE_SABOTAGE_LOCAL_OUT, },
+ { .hook = ipv4_sabotage_out,
+ .pf = PF_INET,
+ .hooknum = NF_IP_POST_ROUTING,
+ .priority = NF_IP_PRI_FIRST, },
+};
+
+int br_netfilter_init(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(br_nf_ops); i++) {
+ int ret;
+
+ if ((ret = nf_register_hook(&br_nf_ops[i])) >= 0)
+ continue;
+
+ while (i--)
+ nf_unregister_hook(&br_nf_ops[i]);
+
+ return ret;
+ }
+
+ printk(KERN_NOTICE "Bridge firewalling registered\n");
+
+ return 0;
+}
+
+void br_netfilter_fini(void)
+{
+ int i;
+
+ for (i = ARRAY_SIZE(br_nf_ops) - 1; i >= 0; i--)
+ nf_unregister_hook(&br_nf_ops[i]);
+}
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_notify.c b/uClinux-2.4.31-uc0/net/bridge/br_notify.c
new file mode 100644
index 0000000..6ed309b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_notify.c
@@ -0,0 +1,75 @@
+/*
+ * Device event handling
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br_notify.c,v 1.2 2000/02/21 15:51:34 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/if_bridge.h>
+#include "br_private.h"
+
+static int br_device_event(struct notifier_block *unused, unsigned long event, void *ptr);
+
+struct notifier_block br_device_notifier =
+{
+ br_device_event,
+ NULL,
+ 0
+};
+
+static int br_device_event(struct notifier_block *unused, unsigned long event, void *ptr)
+{
+ struct net_device *dev;
+ struct net_bridge_port *p;
+
+ dev = ptr;
+ p = dev->br_port;
+
+ if (p == NULL)
+ return NOTIFY_DONE;
+
+ switch (event)
+ {
+ case NETDEV_CHANGEADDR:
+ read_lock(&p->br->lock);
+ br_fdb_changeaddr(p, dev->dev_addr);
+ br_stp_recalculate_bridge_id(p->br);
+ read_unlock(&p->br->lock);
+ break;
+
+ case NETDEV_GOING_DOWN:
+ /* extend the protocol to send some kind of notification? */
+ break;
+
+ case NETDEV_DOWN:
+ if (p->br->dev.flags & IFF_UP) {
+ read_lock(&p->br->lock);
+ br_stp_disable_port(dev->br_port);
+ read_unlock(&p->br->lock);
+ }
+ break;
+
+ case NETDEV_UP:
+ if (p->br->dev.flags & IFF_UP) {
+ read_lock(&p->br->lock);
+ br_stp_enable_port(dev->br_port);
+ read_unlock(&p->br->lock);
+ }
+ break;
+
+ case NETDEV_UNREGISTER:
+ br_del_if(dev->br_port->br, dev);
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_private.h b/uClinux-2.4.31-uc0/net/bridge/br_private.h
new file mode 100644
index 0000000..a57e442
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_private.h
@@ -0,0 +1,209 @@
+/*
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br_private.h,v 1.6.2.1 2001/12/24 00:59:27 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _BR_PRIVATE_H
+#define _BR_PRIVATE_H
+
+#include <linux/netdevice.h>
+#include <linux/miscdevice.h>
+#include <linux/if_bridge.h>
+#include "br_private_timer.h"
+
+#define BR_HASH_BITS 8
+#define BR_HASH_SIZE (1 << BR_HASH_BITS)
+
+#define BR_HOLD_TIME (1*HZ)
+
+typedef struct bridge_id bridge_id;
+typedef struct mac_addr mac_addr;
+typedef __u16 port_id;
+
+struct bridge_id
+{
+ unsigned char prio[2];
+ unsigned char addr[6];
+};
+
+struct mac_addr
+{
+ unsigned char addr[6];
+ unsigned char pad[2];
+};
+
+struct net_bridge_fdb_entry
+{
+ struct net_bridge_fdb_entry *next_hash;
+ struct net_bridge_fdb_entry **pprev_hash;
+ atomic_t use_count;
+ mac_addr addr;
+ struct net_bridge_port *dst;
+ unsigned long ageing_timer;
+ unsigned is_local:1;
+ unsigned is_static:1;
+};
+
+struct net_bridge_port
+{
+ struct net_bridge_port *next;
+ struct net_bridge *br;
+ struct net_device *dev;
+ int port_no;
+
+ /* STP */
+ port_id port_id;
+ int state;
+ int path_cost;
+ bridge_id designated_root;
+ int designated_cost;
+ bridge_id designated_bridge;
+ port_id designated_port;
+ unsigned topology_change_ack:1;
+ unsigned config_pending:1;
+ int priority;
+
+ struct br_timer forward_delay_timer;
+ struct br_timer hold_timer;
+ struct br_timer message_age_timer;
+};
+
+struct net_bridge
+{
+ rwlock_t lock;
+ struct net_bridge_port *port_list;
+ struct net_device dev;
+ struct net_device_stats statistics;
+ rwlock_t hash_lock;
+ struct net_bridge_fdb_entry *hash[BR_HASH_SIZE];
+ struct timer_list tick;
+
+ /* STP */
+ bridge_id designated_root;
+ int root_path_cost;
+ int root_port;
+ int max_age;
+ int hello_time;
+ int forward_delay;
+ bridge_id bridge_id;
+ int bridge_max_age;
+ int bridge_hello_time;
+ int bridge_forward_delay;
+ unsigned stp_enabled:1;
+ unsigned topology_change:1;
+ unsigned topology_change_detected:1;
+
+ struct br_timer hello_timer;
+ struct br_timer tcn_timer;
+ struct br_timer topology_change_timer;
+ struct br_timer gc_timer;
+
+ int ageing_time;
+ int gc_interval;
+};
+
+extern struct notifier_block br_device_notifier;
+extern unsigned char bridge_ula[6];
+
+/* br.c */
+extern void br_dec_use_count(void);
+extern void br_inc_use_count(void);
+
+/* br_device.c */
+extern void br_dev_setup(struct net_device *dev);
+extern int br_dev_xmit(struct sk_buff *skb, struct net_device *dev);
+
+/* br_fdb.c */
+extern void br_fdb_changeaddr(struct net_bridge_port *p,
+ unsigned char *newaddr);
+extern void br_fdb_cleanup(struct net_bridge *br);
+extern void br_fdb_delete_by_port(struct net_bridge *br,
+ struct net_bridge_port *p);
+extern struct net_bridge_fdb_entry *br_fdb_get(struct net_bridge *br,
+ unsigned char *addr);
+extern void br_fdb_put(struct net_bridge_fdb_entry *ent);
+extern int br_fdb_get_entries(struct net_bridge *br,
+ unsigned char *_buf,
+ int maxnum,
+ int offset);
+extern void br_fdb_insert(struct net_bridge *br,
+ struct net_bridge_port *source,
+ unsigned char *addr,
+ int is_local);
+
+/* br_forward.c */
+extern void br_deliver(struct net_bridge_port *to,
+ struct sk_buff *skb);
+extern int br_dev_queue_push_xmit(struct sk_buff *skb);
+extern void br_forward(struct net_bridge_port *to,
+ struct sk_buff *skb);
+extern int br_forward_finish(struct sk_buff *skb);
+extern void br_flood_deliver(struct net_bridge *br,
+ struct sk_buff *skb,
+ int clone);
+extern void br_flood_forward(struct net_bridge *br,
+ struct sk_buff *skb,
+ int clone);
+
+/* br_if.c */
+extern int br_add_bridge(char *name);
+extern int br_del_bridge(char *name);
+extern int br_add_if(struct net_bridge *br,
+ struct net_device *dev);
+extern int br_del_if(struct net_bridge *br,
+ struct net_device *dev);
+extern int br_get_bridge_ifindices(int *indices,
+ int num, int user_buf);
+extern void br_get_port_ifindices(struct net_bridge *br,
+ int *ifindices);
+
+/* br_input.c */
+extern int br_handle_frame_finish(struct sk_buff *skb);
+extern int br_handle_frame(struct sk_buff *skb);
+
+/* br_ioctl.c */
+extern int br_ioctl(struct net_bridge *br,
+ unsigned int cmd,
+ unsigned long arg0,
+ unsigned long arg1,
+ unsigned long arg2);
+extern int br_ioctl_deviceless_stub(unsigned long arg);
+
+/* br_netfilter.c */
+extern int br_netfilter_init(void);
+extern void br_netfilter_fini(void);
+
+/* br_stp.c */
+extern int br_is_root_bridge(struct net_bridge *br);
+extern struct net_bridge_port *br_get_port(struct net_bridge *br,
+ int port_no);
+extern void br_init_port(struct net_bridge_port *p);
+extern port_id br_make_port_id(struct net_bridge_port *p);
+extern void br_become_designated_port(struct net_bridge_port *p);
+
+/* br_stp_if.c */
+extern void br_stp_enable_bridge(struct net_bridge *br);
+extern void br_stp_disable_bridge(struct net_bridge *br);
+extern void br_stp_enable_port(struct net_bridge_port *p);
+extern void br_stp_disable_port(struct net_bridge_port *p);
+extern void br_stp_recalculate_bridge_id(struct net_bridge *br);
+extern void br_stp_set_bridge_priority(struct net_bridge *br,
+ int newprio);
+extern void br_stp_set_port_priority(struct net_bridge_port *p,
+ int newprio);
+extern void br_stp_set_path_cost(struct net_bridge_port *p,
+ int path_cost);
+
+/* br_stp_bpdu.c */
+extern int br_stp_handle_bpdu(struct sk_buff *skb);
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_private_stp.h b/uClinux-2.4.31-uc0/net/bridge/br_private_stp.h
new file mode 100644
index 0000000..7aa4af3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_private_stp.h
@@ -0,0 +1,53 @@
+/*
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br_private_stp.h,v 1.3 2001/02/05 06:03:47 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _BR_PRIVATE_STP_H
+#define _BR_PRIVATE_STP_H
+
+#define BPDU_TYPE_CONFIG 0
+#define BPDU_TYPE_TCN 0x80
+
+struct br_config_bpdu
+{
+ unsigned topology_change:1;
+ unsigned topology_change_ack:1;
+ bridge_id root;
+ int root_path_cost;
+ bridge_id bridge_id;
+ port_id port_id;
+ int message_age;
+ int max_age;
+ int hello_time;
+ int forward_delay;
+};
+
+/* br_stp.c */
+extern void br_become_root_bridge(struct net_bridge *br);
+extern void br_config_bpdu_generation(struct net_bridge *);
+extern void br_configuration_update(struct net_bridge *);
+extern int br_is_designated_port(struct net_bridge_port *p);
+extern int br_is_root_bridge(struct net_bridge *br);
+extern void br_port_state_selection(struct net_bridge *);
+extern void br_received_config_bpdu(struct net_bridge_port *p, struct br_config_bpdu *bpdu);
+extern void br_received_tcn_bpdu(struct net_bridge_port *p);
+extern void br_tick(unsigned long __data);
+extern void br_transmit_config(struct net_bridge_port *p);
+extern void br_transmit_tcn(struct net_bridge *br);
+extern void br_topology_change_detection(struct net_bridge *br);
+
+/* br_stp_bpdu.c */
+extern void br_send_config_bpdu(struct net_bridge_port *, struct br_config_bpdu *);
+extern void br_send_tcn_bpdu(struct net_bridge_port *);
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_private_timer.h b/uClinux-2.4.31-uc0/net/bridge/br_private_timer.h
new file mode 100644
index 0000000..6655ab9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_private_timer.h
@@ -0,0 +1,54 @@
+/*
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br_private_timer.h,v 1.1 2000/02/18 16:47:13 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#ifndef _BR_PRIVATE_TIMER_H
+#define _BR_PRIVATE_TIMER_H
+
+struct br_timer
+{
+ int running;
+ unsigned long expires;
+};
+
+extern __inline__ void br_timer_clear(struct br_timer *t)
+{
+ t->running = 0;
+}
+
+extern __inline__ unsigned long br_timer_get_residue(struct br_timer *t)
+{
+ if (t->running)
+ return jiffies - t->expires;
+
+ return 0;
+}
+
+extern __inline__ void br_timer_set(struct br_timer *t, unsigned long x)
+{
+ t->expires = x;
+ t->running = 1;
+}
+
+extern __inline__ int br_timer_is_running(struct br_timer *t)
+{
+ return t->running;
+}
+
+extern __inline__ int br_timer_has_expired(struct br_timer *t, unsigned long to)
+{
+ return t->running && time_after_eq(jiffies, t->expires + to);
+}
+
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_stp.c b/uClinux-2.4.31-uc0/net/bridge/br_stp.c
new file mode 100644
index 0000000..5d61475
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_stp.c
@@ -0,0 +1,480 @@
+/*
+ * Spanning tree protocol; generic parts
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br_stp.c,v 1.4 2000/06/19 10:13:35 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/if_bridge.h>
+#include <linux/smp_lock.h>
+#include <asm/uaccess.h>
+#include "br_private.h"
+#include "br_private_stp.h"
+
+/* since time values in bpdu are in jiffies and then scaled (1/256)
+ * before sending, make sure that is at least one.
+ */
+#define MESSAGE_AGE_INCR ((HZ < 256) ? 1 : (HZ/256))
+
+/* called under ioctl_lock or bridge lock */
+int br_is_root_bridge(struct net_bridge *br)
+{
+ return !memcmp(&br->bridge_id, &br->designated_root, 8);
+}
+
+/* called under bridge lock */
+int br_is_designated_port(struct net_bridge_port *p)
+{
+ return !memcmp(&p->designated_bridge, &p->br->bridge_id, 8) &&
+ (p->designated_port == p->port_id);
+}
+
+/* called under ioctl_lock or bridge lock */
+struct net_bridge_port *br_get_port(struct net_bridge *br, int port_no)
+{
+ struct net_bridge_port *p;
+
+ p = br->port_list;
+ while (p != NULL) {
+ if (p->port_no == port_no)
+ return p;
+
+ p = p->next;
+ }
+
+ return NULL;
+}
+
+/* called under bridge lock */
+static int br_should_become_root_port(struct net_bridge_port *p, int root_port)
+{
+ struct net_bridge *br;
+ struct net_bridge_port *rp;
+ int t;
+
+ br = p->br;
+ if (p->state == BR_STATE_DISABLED ||
+ br_is_designated_port(p))
+ return 0;
+
+ if (memcmp(&br->bridge_id, &p->designated_root, 8) <= 0)
+ return 0;
+
+ if (!root_port)
+ return 1;
+
+ rp = br_get_port(br, root_port);
+
+ t = memcmp(&p->designated_root, &rp->designated_root, 8);
+ if (t < 0)
+ return 1;
+ else if (t > 0)
+ return 0;
+
+ if (p->designated_cost + p->path_cost <
+ rp->designated_cost + rp->path_cost)
+ return 1;
+ else if (p->designated_cost + p->path_cost >
+ rp->designated_cost + rp->path_cost)
+ return 0;
+
+ t = memcmp(&p->designated_bridge, &rp->designated_bridge, 8);
+ if (t < 0)
+ return 1;
+ else if (t > 0)
+ return 0;
+
+ if (p->designated_port < rp->designated_port)
+ return 1;
+ else if (p->designated_port > rp->designated_port)
+ return 0;
+
+ if (p->port_id < rp->port_id)
+ return 1;
+
+ return 0;
+}
+
+/* called under bridge lock */
+static void br_root_selection(struct net_bridge *br)
+{
+ struct net_bridge_port *p;
+ int root_port;
+
+ root_port = 0;
+
+ p = br->port_list;
+ while (p != NULL) {
+ if (br_should_become_root_port(p, root_port))
+ root_port = p->port_no;
+
+ p = p->next;
+ }
+
+ br->root_port = root_port;
+
+ if (!root_port) {
+ br->designated_root = br->bridge_id;
+ br->root_path_cost = 0;
+ } else {
+ p = br_get_port(br, root_port);
+ br->designated_root = p->designated_root;
+ br->root_path_cost = p->designated_cost + p->path_cost;
+ }
+}
+
+/* called under bridge lock */
+void br_become_root_bridge(struct net_bridge *br)
+{
+ br->max_age = br->bridge_max_age;
+ br->hello_time = br->bridge_hello_time;
+ br->forward_delay = br->bridge_forward_delay;
+ br_topology_change_detection(br);
+ br_timer_clear(&br->tcn_timer);
+ br_config_bpdu_generation(br);
+ br_timer_set(&br->hello_timer, jiffies);
+}
+
+/* called under bridge lock */
+void br_transmit_config(struct net_bridge_port *p)
+{
+ struct br_config_bpdu bpdu;
+ struct net_bridge *br;
+
+ if (br_timer_is_running(&p->hold_timer)) {
+ p->config_pending = 1;
+ return;
+ }
+
+ br = p->br;
+
+ bpdu.topology_change = br->topology_change;
+ bpdu.topology_change_ack = p->topology_change_ack;
+ bpdu.root = br->designated_root;
+ bpdu.root_path_cost = br->root_path_cost;
+ bpdu.bridge_id = br->bridge_id;
+ bpdu.port_id = p->port_id;
+ if (br_is_root_bridge(br))
+ bpdu.message_age = 0;
+ else {
+ struct net_bridge_port *root;
+
+ root = br_get_port(br, br->root_port);
+ bpdu.message_age = br_timer_get_residue(&root->message_age_timer)
+ + MESSAGE_AGE_INCR;
+ }
+ bpdu.max_age = br->max_age;
+ bpdu.hello_time = br->hello_time;
+ bpdu.forward_delay = br->forward_delay;
+
+ if (bpdu.message_age < br->max_age) {
+ br_send_config_bpdu(p, &bpdu);
+
+ p->topology_change_ack = 0;
+ p->config_pending = 0;
+ br_timer_set(&p->hold_timer, jiffies);
+ }
+}
+
+/* called under bridge lock */
+static void br_record_config_information(struct net_bridge_port *p, struct br_config_bpdu *bpdu)
+{
+ p->designated_root = bpdu->root;
+ p->designated_cost = bpdu->root_path_cost;
+ p->designated_bridge = bpdu->bridge_id;
+ p->designated_port = bpdu->port_id;
+
+ br_timer_set(&p->message_age_timer, jiffies - bpdu->message_age);
+}
+
+/* called under bridge lock */
+static void br_record_config_timeout_values(struct net_bridge *br, struct br_config_bpdu *bpdu)
+{
+ br->max_age = bpdu->max_age;
+ br->hello_time = bpdu->hello_time;
+ br->forward_delay = bpdu->forward_delay;
+ br->topology_change = bpdu->topology_change;
+}
+
+/* called under bridge lock */
+void br_transmit_tcn(struct net_bridge *br)
+{
+ br_send_tcn_bpdu(br_get_port(br, br->root_port));
+}
+
+/* called under bridge lock */
+static int br_should_become_designated_port(struct net_bridge_port *p)
+{
+ struct net_bridge *br;
+ int t;
+
+ br = p->br;
+ if (br_is_designated_port(p))
+ return 1;
+
+ if (memcmp(&p->designated_root, &br->designated_root, 8))
+ return 1;
+
+ if (br->root_path_cost < p->designated_cost)
+ return 1;
+ else if (br->root_path_cost > p->designated_cost)
+ return 0;
+
+ t = memcmp(&br->bridge_id, &p->designated_bridge, 8);
+ if (t < 0)
+ return 1;
+ else if (t > 0)
+ return 0;
+
+ if (p->port_id < p->designated_port)
+ return 1;
+
+ return 0;
+}
+
+/* called under bridge lock */
+static void br_designated_port_selection(struct net_bridge *br)
+{
+ struct net_bridge_port *p;
+
+ p = br->port_list;
+ while (p != NULL) {
+ if (p->state != BR_STATE_DISABLED &&
+ br_should_become_designated_port(p))
+ br_become_designated_port(p);
+
+ p = p->next;
+ }
+}
+
+/* called under bridge lock */
+static int br_supersedes_port_info(struct net_bridge_port *p, struct br_config_bpdu *bpdu)
+{
+ int t;
+
+ t = memcmp(&bpdu->root, &p->designated_root, 8);
+ if (t < 0)
+ return 1;
+ else if (t > 0)
+ return 0;
+
+ if (bpdu->root_path_cost < p->designated_cost)
+ return 1;
+ else if (bpdu->root_path_cost > p->designated_cost)
+ return 0;
+
+ t = memcmp(&bpdu->bridge_id, &p->designated_bridge, 8);
+ if (t < 0)
+ return 1;
+ else if (t > 0)
+ return 0;
+
+ if (memcmp(&bpdu->bridge_id, &p->br->bridge_id, 8))
+ return 1;
+
+ if (bpdu->port_id <= p->designated_port)
+ return 1;
+
+ return 0;
+}
+
+/* called under bridge lock */
+static void br_topology_change_acknowledged(struct net_bridge *br)
+{
+ br->topology_change_detected = 0;
+ br_timer_clear(&br->tcn_timer);
+}
+
+/* called under bridge lock */
+void br_topology_change_detection(struct net_bridge *br)
+{
+ printk(KERN_INFO "%s: topology change detected", br->dev.name);
+
+ if (br_is_root_bridge(br)) {
+ printk(", propagating");
+ br->topology_change = 1;
+ br_timer_set(&br->topology_change_timer, jiffies);
+ } else if (!br->topology_change_detected) {
+ printk(", sending tcn bpdu");
+ br_transmit_tcn(br);
+ br_timer_set(&br->tcn_timer, jiffies);
+ }
+
+ printk("\n");
+ br->topology_change_detected = 1;
+}
+
+/* called under bridge lock */
+void br_config_bpdu_generation(struct net_bridge *br)
+{
+ struct net_bridge_port *p;
+
+ p = br->port_list;
+ while (p != NULL) {
+ if (p->state != BR_STATE_DISABLED &&
+ br_is_designated_port(p))
+ br_transmit_config(p);
+
+ p = p->next;
+ }
+}
+
+/* called under bridge lock */
+static void br_reply(struct net_bridge_port *p)
+{
+ br_transmit_config(p);
+}
+
+/* called under bridge lock */
+void br_configuration_update(struct net_bridge *br)
+{
+ br_root_selection(br);
+ br_designated_port_selection(br);
+}
+
+/* called under bridge lock */
+void br_become_designated_port(struct net_bridge_port *p)
+{
+ struct net_bridge *br;
+
+ br = p->br;
+ p->designated_root = br->designated_root;
+ p->designated_cost = br->root_path_cost;
+ p->designated_bridge = br->bridge_id;
+ p->designated_port = p->port_id;
+}
+
+/* called under bridge lock */
+static void br_make_blocking(struct net_bridge_port *p)
+{
+ if (p->state != BR_STATE_DISABLED &&
+ p->state != BR_STATE_BLOCKING) {
+ if (p->state == BR_STATE_FORWARDING ||
+ p->state == BR_STATE_LEARNING)
+ br_topology_change_detection(p->br);
+
+ printk(KERN_INFO "%s: port %i(%s) entering %s state\n",
+ p->br->dev.name, p->port_no, p->dev->name, "blocking");
+
+ p->state = BR_STATE_BLOCKING;
+ br_timer_clear(&p->forward_delay_timer);
+ }
+}
+
+/* called under bridge lock */
+static void br_make_forwarding(struct net_bridge_port *p)
+{
+ if (p->state == BR_STATE_BLOCKING) {
+ if (p->br->stp_enabled) {
+ printk(KERN_INFO "%s: port %i(%s) entering %s state\n",
+ p->br->dev.name, p->port_no, p->dev->name,
+ "listening");
+
+ p->state = BR_STATE_LISTENING;
+ } else {
+ printk(KERN_INFO "%s: port %i(%s) entering %s state\n",
+ p->br->dev.name, p->port_no, p->dev->name,
+ "learning");
+
+ p->state = BR_STATE_LEARNING;
+ }
+ br_timer_set(&p->forward_delay_timer, jiffies);
+ }
+}
+
+/* called under bridge lock */
+void br_port_state_selection(struct net_bridge *br)
+{
+ struct net_bridge_port *p;
+
+ p = br->port_list;
+ while (p != NULL) {
+ if (p->state != BR_STATE_DISABLED) {
+ if (p->port_no == br->root_port) {
+ p->config_pending = 0;
+ p->topology_change_ack = 0;
+ br_make_forwarding(p);
+ } else if (br_is_designated_port(p)) {
+ br_timer_clear(&p->message_age_timer);
+ br_make_forwarding(p);
+ } else {
+ p->config_pending = 0;
+ p->topology_change_ack = 0;
+ br_make_blocking(p);
+ }
+ }
+
+ p = p->next;
+ }
+}
+
+/* called under bridge lock */
+static void br_topology_change_acknowledge(struct net_bridge_port *p)
+{
+ p->topology_change_ack = 1;
+ br_transmit_config(p);
+}
+
+/* lock-safe */
+void br_received_config_bpdu(struct net_bridge_port *p, struct br_config_bpdu *bpdu)
+{
+ struct net_bridge *br;
+ int was_root;
+
+ if (p->state == BR_STATE_DISABLED)
+ return;
+
+ br = p->br;
+ read_lock(&br->lock);
+
+ was_root = br_is_root_bridge(br);
+ if (br_supersedes_port_info(p, bpdu)) {
+ br_record_config_information(p, bpdu);
+ br_configuration_update(br);
+ br_port_state_selection(br);
+
+ if (!br_is_root_bridge(br) && was_root) {
+ br_timer_clear(&br->hello_timer);
+ if (br->topology_change_detected) {
+ br_timer_clear(&br->topology_change_timer);
+ br_transmit_tcn(br);
+ br_timer_set(&br->tcn_timer, jiffies);
+ }
+ }
+
+ if (p->port_no == br->root_port) {
+ br_record_config_timeout_values(br, bpdu);
+ br_config_bpdu_generation(br);
+ if (bpdu->topology_change_ack)
+ br_topology_change_acknowledged(br);
+ }
+ } else if (br_is_designated_port(p)) {
+ br_reply(p);
+ }
+
+ read_unlock(&br->lock);
+}
+
+/* lock-safe */
+void br_received_tcn_bpdu(struct net_bridge_port *p)
+{
+ read_lock(&p->br->lock);
+ if (p->state != BR_STATE_DISABLED &&
+ br_is_designated_port(p)) {
+ printk(KERN_INFO "%s: received tcn bpdu on port %i(%s)\n",
+ p->br->dev.name, p->port_no, p->dev->name);
+
+ br_topology_change_detection(p->br);
+ br_topology_change_acknowledge(p);
+ }
+ read_unlock(&p->br->lock);
+}
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_stp_bpdu.c b/uClinux-2.4.31-uc0/net/bridge/br_stp_bpdu.c
new file mode 100644
index 0000000..8ba9b3d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_stp_bpdu.c
@@ -0,0 +1,236 @@
+/*
+ * Spanning tree protocol; BPDU handling
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br_stp_bpdu.c,v 1.3 2001/11/10 02:35:25 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/if_ether.h>
+#include <linux/if_bridge.h>
+#include <linux/netfilter_bridge.h>
+#include "br_private.h"
+#include "br_private_stp.h"
+
+#define JIFFIES_TO_TICKS(j) (((j) << 8) / HZ)
+#define TICKS_TO_JIFFIES(j) (((j) * HZ) >> 8)
+
+static void br_send_bpdu(struct net_bridge_port *p, unsigned char *data, int length)
+{
+ struct net_device *dev;
+ struct sk_buff *skb;
+ int size;
+
+ if (!p->br->stp_enabled)
+ return;
+
+ size = length + 2*ETH_ALEN + 2;
+ if (size < 60)
+ size = 60;
+
+ dev = p->dev;
+
+ if ((skb = dev_alloc_skb(size)) == NULL) {
+ printk(KERN_INFO "br: memory squeeze!\n");
+ return;
+ }
+
+ skb->dev = dev;
+ skb->protocol = htons(ETH_P_802_2);
+ skb->mac.raw = skb_put(skb, size);
+ memcpy(skb->mac.raw, bridge_ula, ETH_ALEN);
+ memcpy(skb->mac.raw+ETH_ALEN, dev->dev_addr, ETH_ALEN);
+ skb->mac.raw[2*ETH_ALEN] = 0;
+ skb->mac.raw[2*ETH_ALEN+1] = length;
+ skb->nh.raw = skb->mac.raw + 2*ETH_ALEN + 2;
+ memcpy(skb->nh.raw, data, length);
+ memset(skb->nh.raw + length, 0xa5, size - length - 2*ETH_ALEN - 2);
+
+ NF_HOOK(PF_BRIDGE, NF_BR_LOCAL_OUT, skb, NULL, skb->dev,
+ dev_queue_xmit);
+}
+
+static __inline__ void br_set_ticks(unsigned char *dest, int jiff)
+{
+ __u16 ticks;
+
+ ticks = JIFFIES_TO_TICKS(jiff);
+ dest[0] = (ticks >> 8) & 0xFF;
+ dest[1] = ticks & 0xFF;
+}
+
+static __inline__ int br_get_ticks(unsigned char *dest)
+{
+ return TICKS_TO_JIFFIES((dest[0] << 8) | dest[1]);
+}
+
+/* called under bridge lock */
+void br_send_config_bpdu(struct net_bridge_port *p, struct br_config_bpdu *bpdu)
+{
+ unsigned char buf[38];
+
+ buf[0] = 0x42;
+ buf[1] = 0x42;
+ buf[2] = 0x03;
+ buf[3] = 0;
+ buf[4] = 0;
+ buf[5] = 0;
+ buf[6] = BPDU_TYPE_CONFIG;
+ buf[7] = (bpdu->topology_change ? 0x01 : 0) |
+ (bpdu->topology_change_ack ? 0x80 : 0);
+ buf[8] = bpdu->root.prio[0];
+ buf[9] = bpdu->root.prio[1];
+ buf[10] = bpdu->root.addr[0];
+ buf[11] = bpdu->root.addr[1];
+ buf[12] = bpdu->root.addr[2];
+ buf[13] = bpdu->root.addr[3];
+ buf[14] = bpdu->root.addr[4];
+ buf[15] = bpdu->root.addr[5];
+ buf[16] = (bpdu->root_path_cost >> 24) & 0xFF;
+ buf[17] = (bpdu->root_path_cost >> 16) & 0xFF;
+ buf[18] = (bpdu->root_path_cost >> 8) & 0xFF;
+ buf[19] = bpdu->root_path_cost & 0xFF;
+ buf[20] = bpdu->bridge_id.prio[0];
+ buf[21] = bpdu->bridge_id.prio[1];
+ buf[22] = bpdu->bridge_id.addr[0];
+ buf[23] = bpdu->bridge_id.addr[1];
+ buf[24] = bpdu->bridge_id.addr[2];
+ buf[25] = bpdu->bridge_id.addr[3];
+ buf[26] = bpdu->bridge_id.addr[4];
+ buf[27] = bpdu->bridge_id.addr[5];
+ buf[28] = (bpdu->port_id >> 8) & 0xFF;
+ buf[29] = bpdu->port_id & 0xFF;
+
+ br_set_ticks(buf+30, bpdu->message_age);
+ br_set_ticks(buf+32, bpdu->max_age);
+ br_set_ticks(buf+34, bpdu->hello_time);
+ br_set_ticks(buf+36, bpdu->forward_delay);
+
+ br_send_bpdu(p, buf, 38);
+}
+
+/* called under bridge lock */
+void br_send_tcn_bpdu(struct net_bridge_port *p)
+{
+ unsigned char buf[7];
+
+ buf[0] = 0x42;
+ buf[1] = 0x42;
+ buf[2] = 0x03;
+ buf[3] = 0;
+ buf[4] = 0;
+ buf[5] = 0;
+ buf[6] = BPDU_TYPE_TCN;
+ br_send_bpdu(p, buf, 7);
+}
+
+static unsigned char header[6] = {0x42, 0x42, 0x03, 0x00, 0x00, 0x00};
+
+
+/* Must be called under bridge lock */
+void
+br_stp_do_bpdu(struct sk_buff *skb)
+{
+ unsigned char *buf;
+ struct net_bridge_port *p;
+
+ p = skb->dev->br_port;
+
+ buf = skb->data;
+
+ if (!p->br->stp_enabled || !buf)
+ return;
+
+ if (buf[0] == BPDU_TYPE_CONFIG) {
+ struct br_config_bpdu bpdu;
+
+ if (!pskb_may_pull(skb, 32))
+ return;
+
+ buf = skb->data;
+ bpdu.topology_change = (buf[1] & 0x01) ? 1 : 0;
+ bpdu.topology_change_ack = (buf[1] & 0x80) ? 1 : 0;
+
+ bpdu.root.prio[0] = buf[2];
+ bpdu.root.prio[1] = buf[3];
+ bpdu.root.addr[0] = buf[4];
+ bpdu.root.addr[1] = buf[5];
+ bpdu.root.addr[2] = buf[6];
+ bpdu.root.addr[3] = buf[7];
+ bpdu.root.addr[4] = buf[8];
+ bpdu.root.addr[5] = buf[9];
+ bpdu.root_path_cost =
+ (buf[10] << 24) |
+ (buf[11] << 16) |
+ (buf[12] << 8) |
+ buf[13];
+ bpdu.bridge_id.prio[0] = buf[14];
+ bpdu.bridge_id.prio[1] = buf[15];
+ bpdu.bridge_id.addr[0] = buf[16];
+ bpdu.bridge_id.addr[1] = buf[17];
+ bpdu.bridge_id.addr[2] = buf[18];
+ bpdu.bridge_id.addr[3] = buf[19];
+ bpdu.bridge_id.addr[4] = buf[20];
+ bpdu.bridge_id.addr[5] = buf[21];
+ bpdu.port_id = (buf[22] << 8) | buf[23];
+
+ bpdu.message_age = br_get_ticks(buf+24);
+ bpdu.max_age = br_get_ticks(buf+26);
+ bpdu.hello_time = br_get_ticks(buf+28);
+ bpdu.forward_delay = br_get_ticks(buf+30);
+
+ br_received_config_bpdu(p, &bpdu);
+ }
+
+ else if (buf[0] == BPDU_TYPE_TCN) {
+ br_received_tcn_bpdu(p);
+ }
+}
+
+/*
+ Any header should already be pulled off and the data must be pointing
+ to the start of the actual bpdu.
+ We start by ripping off the GRE protocol id and the version.
+*/
+void
+br_stp_do_bpdu_lock(struct sk_buff *skb)
+{
+ struct net_bridge_port *p;
+ struct net_bridge *br;
+
+ p = skb->dev->br_port;
+
+ if (!p || !(br = p->br) ||
+ !pskb_may_pull(skb, 4) || !skb_pull(skb, 4)) {
+ return;
+ }
+ read_lock(&br->lock);
+
+ br_stp_do_bpdu(skb);
+
+ read_unlock(&br->lock);
+}
+
+/* called under bridge lock */
+int br_stp_handle_bpdu(struct sk_buff *skb)
+{
+ if (!pskb_may_pull(skb, sizeof(header)+1) ||
+ memcmp(skb->data, header, sizeof(header)))
+ goto err;
+
+ skb_pull(skb, sizeof(header));
+
+ br_stp_do_bpdu(skb);
+
+ err:
+ kfree_skb(skb);
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_stp_if.c b/uClinux-2.4.31-uc0/net/bridge/br_stp_if.c
new file mode 100644
index 0000000..fdff4ba
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_stp_if.c
@@ -0,0 +1,228 @@
+/*
+ * Spanning tree protocol; interface code
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br_stp_if.c,v 1.4 2001/04/14 21:14:39 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/if_bridge.h>
+#include <linux/smp_lock.h>
+#include <asm/uaccess.h>
+#include "br_private.h"
+#include "br_private_stp.h"
+
+__u16 br_make_port_id(struct net_bridge_port *p)
+{
+ return (p->priority << 8) | p->port_no;
+}
+
+/* called under bridge lock */
+void br_init_port(struct net_bridge_port *p)
+{
+ p->port_id = br_make_port_id(p);
+ br_become_designated_port(p);
+ p->state = BR_STATE_BLOCKING;
+ p->topology_change_ack = 0;
+ p->config_pending = 0;
+ br_timer_clear(&p->message_age_timer);
+ br_timer_clear(&p->forward_delay_timer);
+ br_timer_clear(&p->hold_timer);
+}
+
+/* called under bridge lock */
+void br_stp_enable_bridge(struct net_bridge *br)
+{
+ struct net_bridge_port *p;
+ struct timer_list *timer = &br->tick;
+
+ init_timer(timer);
+ timer->data = (unsigned long) br;
+ timer->function = br_tick;
+ timer->expires = jiffies + 1;
+ add_timer(timer);
+
+ br_timer_set(&br->hello_timer, jiffies);
+ br_config_bpdu_generation(br);
+
+ p = br->port_list;
+ while (p != NULL) {
+ if (p->dev->flags & IFF_UP)
+ br_stp_enable_port(p);
+
+ p = p->next;
+ }
+
+ br_timer_set(&br->gc_timer, jiffies);
+}
+
+/* called under bridge lock */
+void br_stp_disable_bridge(struct net_bridge *br)
+{
+ struct net_bridge_port *p;
+
+ br->topology_change = 0;
+ br->topology_change_detected = 0;
+ br_timer_clear(&br->hello_timer);
+ br_timer_clear(&br->topology_change_timer);
+ br_timer_clear(&br->tcn_timer);
+ br_timer_clear(&br->gc_timer);
+ br_fdb_cleanup(br);
+
+ p = br->port_list;
+ while (p != NULL) {
+ if (p->state != BR_STATE_DISABLED)
+ br_stp_disable_port(p);
+
+ p = p->next;
+ }
+
+ del_timer(&br->tick);
+}
+
+/* called under bridge lock */
+void br_stp_enable_port(struct net_bridge_port *p)
+{
+ br_init_port(p);
+ br_port_state_selection(p->br);
+}
+
+/* called under bridge lock */
+void br_stp_disable_port(struct net_bridge_port *p)
+{
+ struct net_bridge *br;
+ int wasroot;
+
+ br = p->br;
+ printk(KERN_INFO "%s: port %i(%s) entering %s state\n",
+ br->dev.name, p->port_no, p->dev->name, "disabled");
+
+ wasroot = br_is_root_bridge(br);
+ br_become_designated_port(p);
+ p->state = BR_STATE_DISABLED;
+ p->topology_change_ack = 0;
+ p->config_pending = 0;
+ br_timer_clear(&p->message_age_timer);
+ br_timer_clear(&p->forward_delay_timer);
+ br_timer_clear(&p->hold_timer);
+ br_configuration_update(br);
+ br_port_state_selection(br);
+
+ if (br_is_root_bridge(br) && !wasroot)
+ br_become_root_bridge(br);
+}
+
+/* called under bridge lock */
+static void br_stp_change_bridge_id(struct net_bridge *br, unsigned char *addr)
+{
+ unsigned char oldaddr[6];
+ struct net_bridge_port *p;
+ int wasroot;
+
+ wasroot = br_is_root_bridge(br);
+
+ memcpy(oldaddr, br->bridge_id.addr, ETH_ALEN);
+ memcpy(br->bridge_id.addr, addr, ETH_ALEN);
+ memcpy(br->dev.dev_addr, addr, ETH_ALEN);
+
+ p = br->port_list;
+ while (p != NULL) {
+ if (!memcmp(p->designated_bridge.addr, oldaddr, ETH_ALEN))
+ memcpy(p->designated_bridge.addr, addr, ETH_ALEN);
+
+ if (!memcmp(p->designated_root.addr, oldaddr, ETH_ALEN))
+ memcpy(p->designated_root.addr, addr, ETH_ALEN);
+
+ p = p->next;
+ }
+
+ br_configuration_update(br);
+ br_port_state_selection(br);
+ if (br_is_root_bridge(br) && !wasroot)
+ br_become_root_bridge(br);
+}
+
+static unsigned char br_mac_zero[6] = {0,0,0,0,0,0};
+
+/* called under bridge lock */
+void br_stp_recalculate_bridge_id(struct net_bridge *br)
+{
+ unsigned char *addr;
+ struct net_bridge_port *p;
+
+ addr = br_mac_zero;
+
+ p = br->port_list;
+ while (p != NULL) {
+ if (addr == br_mac_zero ||
+ memcmp(p->dev->dev_addr, addr, ETH_ALEN) < 0)
+ addr = p->dev->dev_addr;
+
+ p = p->next;
+ }
+
+ if (memcmp(br->bridge_id.addr, addr, ETH_ALEN))
+ br_stp_change_bridge_id(br, addr);
+}
+
+/* called under bridge lock */
+void br_stp_set_bridge_priority(struct net_bridge *br, int newprio)
+{
+ struct net_bridge_port *p;
+ int wasroot;
+
+ wasroot = br_is_root_bridge(br);
+
+ p = br->port_list;
+ while (p != NULL) {
+ if (p->state != BR_STATE_DISABLED &&
+ br_is_designated_port(p)) {
+ p->designated_bridge.prio[0] = (newprio >> 8) & 0xFF;
+ p->designated_bridge.prio[1] = newprio & 0xFF;
+ }
+
+ p = p->next;
+ }
+
+ br->bridge_id.prio[0] = (newprio >> 8) & 0xFF;
+ br->bridge_id.prio[1] = newprio & 0xFF;
+ br_configuration_update(br);
+ br_port_state_selection(br);
+ if (br_is_root_bridge(br) && !wasroot)
+ br_become_root_bridge(br);
+}
+
+/* called under bridge lock */
+void br_stp_set_port_priority(struct net_bridge_port *p, int newprio)
+{
+ __u16 new_port_id;
+
+ p->priority = newprio & 0xFF;
+ new_port_id = br_make_port_id(p);
+
+ if (br_is_designated_port(p))
+ p->designated_port = new_port_id;
+
+ p->port_id = new_port_id;
+ if (!memcmp(&p->br->bridge_id, &p->designated_bridge, 8) &&
+ p->port_id < p->designated_port) {
+ br_become_designated_port(p);
+ br_port_state_selection(p->br);
+ }
+}
+
+/* called under bridge lock */
+void br_stp_set_path_cost(struct net_bridge_port *p, int path_cost)
+{
+ p->path_cost = path_cost;
+ br_configuration_update(p->br);
+ br_port_state_selection(p->br);
+}
diff --git a/uClinux-2.4.31-uc0/net/bridge/br_stp_timer.c b/uClinux-2.4.31-uc0/net/bridge/br_stp_timer.c
new file mode 100644
index 0000000..3402d49
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/br_stp_timer.c
@@ -0,0 +1,184 @@
+/*
+ * Spanning tree protocol; timer-related code
+ * Linux ethernet bridge
+ *
+ * Authors:
+ * Lennert Buytenhek <buytenh@gnu.org>
+ *
+ * $Id: br_stp_timer.c,v 1.3 2000/05/05 02:17:17 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/kernel.h>
+#include <linux/if_bridge.h>
+#include <linux/smp_lock.h>
+#include <asm/uaccess.h>
+#include "br_private.h"
+#include "br_private_stp.h"
+
+static void dump_bridge_id(bridge_id *id)
+{
+ printk("%.2x%.2x.%.2x:%.2x:%.2x:%.2x:%.2x:%.2x", id->prio[0],
+ id->prio[1], id->addr[0], id->addr[1], id->addr[2], id->addr[3],
+ id->addr[4], id->addr[5]);
+}
+
+/* called under bridge lock */
+static int br_is_designated_for_some_port(struct net_bridge *br)
+{
+ struct net_bridge_port *p;
+
+ p = br->port_list;
+ while (p != NULL) {
+ if (p->state != BR_STATE_DISABLED &&
+ !memcmp(&p->designated_bridge, &br->bridge_id, 8))
+ return 1;
+
+ p = p->next;
+ }
+
+ return 0;
+}
+
+/* called under bridge lock */
+static void br_hello_timer_expired(struct net_bridge *br)
+{
+ br_config_bpdu_generation(br);
+ br_timer_set(&br->hello_timer, jiffies);
+}
+
+/* called under bridge lock */
+static void br_message_age_timer_expired(struct net_bridge_port *p)
+{
+ struct net_bridge *br;
+ int was_root;
+
+ br = p->br;
+ printk(KERN_INFO "%s: ", br->dev.name);
+ printk("neighbour ");
+ dump_bridge_id(&p->designated_bridge);
+ printk(" lost on port %i(%s)\n", p->port_no, p->dev->name);
+
+ /*
+ * According to the spec, the message age timer cannot be
+ * running when we are the root bridge. So.. this was_root
+ * check is redundant. I'm leaving it in for now, though.
+ */
+ was_root = br_is_root_bridge(br);
+
+ br_become_designated_port(p);
+ br_configuration_update(br);
+ br_port_state_selection(br);
+ if (br_is_root_bridge(br) && !was_root)
+ br_become_root_bridge(br);
+}
+
+/* called under bridge lock */
+static void br_forward_delay_timer_expired(struct net_bridge_port *p)
+{
+ if (p->state == BR_STATE_LISTENING) {
+ printk(KERN_INFO "%s: port %i(%s) entering %s state\n",
+ p->br->dev.name, p->port_no, p->dev->name, "learning");
+
+ p->state = BR_STATE_LEARNING;
+ br_timer_set(&p->forward_delay_timer, jiffies);
+ } else if (p->state == BR_STATE_LEARNING) {
+ printk(KERN_INFO "%s: port %i(%s) entering %s state\n",
+ p->br->dev.name, p->port_no, p->dev->name, "forwarding");
+
+ p->state = BR_STATE_FORWARDING;
+ if (br_is_designated_for_some_port(p->br))
+ br_topology_change_detection(p->br);
+ }
+}
+
+/* called under bridge lock */
+static void br_tcn_timer_expired(struct net_bridge *br)
+{
+ printk(KERN_INFO "%s: retransmitting tcn bpdu\n", br->dev.name);
+ br_transmit_tcn(br);
+ br_timer_set(&br->tcn_timer, jiffies);
+}
+
+/* called under bridge lock */
+static void br_topology_change_timer_expired(struct net_bridge *br)
+{
+ br->topology_change_detected = 0;
+ br->topology_change = 0;
+}
+
+/* called under bridge lock */
+static void br_hold_timer_expired(struct net_bridge_port *p)
+{
+ if (p->config_pending)
+ br_transmit_config(p);
+}
+
+/* called under bridge lock */
+static void br_check_port_timers(struct net_bridge_port *p)
+{
+ if (br_timer_has_expired(&p->message_age_timer, p->br->max_age)) {
+ br_timer_clear(&p->message_age_timer);
+ br_message_age_timer_expired(p);
+ }
+
+ if (br_timer_has_expired(&p->forward_delay_timer, p->br->forward_delay)) {
+ br_timer_clear(&p->forward_delay_timer);
+ br_forward_delay_timer_expired(p);
+ }
+
+ if (br_timer_has_expired(&p->hold_timer, BR_HOLD_TIME)) {
+ br_timer_clear(&p->hold_timer);
+ br_hold_timer_expired(p);
+ }
+}
+
+/* called under bridge lock */
+static void br_check_timers(struct net_bridge *br)
+{
+ struct net_bridge_port *p;
+
+ if (br_timer_has_expired(&br->gc_timer, br->gc_interval)) {
+ br_timer_set(&br->gc_timer, jiffies);
+ br_fdb_cleanup(br);
+ }
+
+ if (br_timer_has_expired(&br->hello_timer, br->hello_time)) {
+ br_timer_clear(&br->hello_timer);
+ br_hello_timer_expired(br);
+ }
+
+ if (br_timer_has_expired(&br->tcn_timer, br->bridge_hello_time)) {
+ br_timer_clear(&br->tcn_timer);
+ br_tcn_timer_expired(br);
+ }
+
+ if (br_timer_has_expired(&br->topology_change_timer, br->bridge_forward_delay + br->bridge_max_age)) {
+ br_timer_clear(&br->topology_change_timer);
+ br_topology_change_timer_expired(br);
+ }
+
+ p = br->port_list;
+ while (p != NULL) {
+ if (p->state != BR_STATE_DISABLED)
+ br_check_port_timers(p);
+
+ p = p->next;
+ }
+}
+
+void br_tick(unsigned long __data)
+{
+ struct net_bridge *br = (struct net_bridge *)__data;
+
+ read_lock(&br->lock);
+ br_check_timers(br);
+ read_unlock(&br->lock);
+
+ br->tick.expires = jiffies + 1;
+ add_timer(&br->tick);
+}
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/Config.in b/uClinux-2.4.31-uc0/net/bridge/netfilter/Config.in
new file mode 100644
index 0000000..07cb3af
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/Config.in
@@ -0,0 +1,22 @@
+#
+# Bridge netfilter configuration
+#
+dep_tristate ' Bridge: ebtables' CONFIG_BRIDGE_NF_EBTABLES $CONFIG_BRIDGE
+dep_tristate ' ebt: filter table support' CONFIG_BRIDGE_EBT_T_FILTER $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: nat table support' CONFIG_BRIDGE_EBT_T_NAT $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: broute table support' CONFIG_BRIDGE_EBT_BROUTE $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: log support' CONFIG_BRIDGE_EBT_LOG $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: IP filter support' CONFIG_BRIDGE_EBT_IPF $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: ARP filter support' CONFIG_BRIDGE_EBT_ARPF $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: among filter support' CONFIG_BRIDGE_EBT_AMONG $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: limit filter support' CONFIG_BRIDGE_EBT_LIMIT $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: 802.1Q VLAN filter support' CONFIG_BRIDGE_EBT_VLANF $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: 802.3 filter support' CONFIG_BRIDGE_EBT_802_3 $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: packet type filter support' CONFIG_BRIDGE_EBT_PKTTYPE $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: STP filter support' CONFIG_BRIDGE_EBT_STP $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: mark filter support' CONFIG_BRIDGE_EBT_MARKF $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: arp reply target support' CONFIG_BRIDGE_EBT_ARPREPLY $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: snat target support' CONFIG_BRIDGE_EBT_SNAT $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: dnat target support' CONFIG_BRIDGE_EBT_DNAT $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: redirect target support' CONFIG_BRIDGE_EBT_REDIRECT $CONFIG_BRIDGE_NF_EBTABLES
+dep_tristate ' ebt: mark target support' CONFIG_BRIDGE_EBT_MARK_T $CONFIG_BRIDGE_NF_EBTABLES
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/Makefile b/uClinux-2.4.31-uc0/net/bridge/netfilter/Makefile
new file mode 100644
index 0000000..ef67365
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/Makefile
@@ -0,0 +1,33 @@
+#
+# Makefile for the netfilter modules on top of bridging.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := netfilter.o
+
+export-objs := ebtables.o
+
+obj-$(CONFIG_BRIDGE_NF_EBTABLES) += ebtables.o
+obj-$(CONFIG_BRIDGE_EBT_T_FILTER) += ebtable_filter.o
+obj-$(CONFIG_BRIDGE_EBT_T_NAT) += ebtable_nat.o
+obj-$(CONFIG_BRIDGE_EBT_BROUTE) += ebtable_broute.o
+obj-$(CONFIG_BRIDGE_EBT_802_3) += ebt_802_3.o
+obj-$(CONFIG_BRIDGE_EBT_ARPF) += ebt_arp.o
+obj-$(CONFIG_BRIDGE_EBT_AMONG) += ebt_among.o
+obj-$(CONFIG_BRIDGE_EBT_IPF) += ebt_ip.o
+obj-$(CONFIG_BRIDGE_EBT_LIMIT) += ebt_limit.o
+obj-$(CONFIG_BRIDGE_EBT_MARKF) += ebt_mark_m.o
+obj-$(CONFIG_BRIDGE_EBT_PKTTYPE) += ebt_pkttype.o
+obj-$(CONFIG_BRIDGE_EBT_STP) += ebt_stp.o
+obj-$(CONFIG_BRIDGE_EBT_VLANF) += ebt_vlan.o
+obj-$(CONFIG_BRIDGE_EBT_LOG) += ebt_log.o
+obj-$(CONFIG_BRIDGE_EBT_ARPREPLY) += ebt_arpreply.o
+obj-$(CONFIG_BRIDGE_EBT_DNAT) += ebt_dnat.o
+obj-$(CONFIG_BRIDGE_EBT_MARK_T) += ebt_mark.o
+obj-$(CONFIG_BRIDGE_EBT_REDIRECT) += ebt_redirect.o
+obj-$(CONFIG_BRIDGE_EBT_SNAT) += ebt_snat.o
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_arp.c b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_arp.c
new file mode 100644
index 0000000..4bb1281
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_arp.c
@@ -0,0 +1,149 @@
+/*
+ * ebt_arp
+ *
+ * Authors:
+ * Bart De Schuymer <bart.de.schuymer@pandora.be>
+ * Tim Gardner <timg@tpi.com>
+ *
+ * April, 2002
+ *
+ */
+
+#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/netfilter_bridge/ebt_arp.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/module.h>
+
+static int ebt_filter_arp(const struct sk_buff *skb, const struct net_device *in,
+ const struct net_device *out, const void *data, unsigned int datalen)
+{
+ struct ebt_arp_info *info = (struct ebt_arp_info *)data;
+
+ if (info->bitmask & EBT_ARP_OPCODE && FWINV(info->opcode !=
+ ((*skb).nh.arph)->ar_op, EBT_ARP_OPCODE))
+ return EBT_NOMATCH;
+ if (info->bitmask & EBT_ARP_HTYPE && FWINV(info->htype !=
+ ((*skb).nh.arph)->ar_hrd, EBT_ARP_HTYPE))
+ return EBT_NOMATCH;
+ if (info->bitmask & EBT_ARP_PTYPE && FWINV(info->ptype !=
+ ((*skb).nh.arph)->ar_pro, EBT_ARP_PTYPE))
+ return EBT_NOMATCH;
+
+ if (info->bitmask & (EBT_ARP_SRC_IP | EBT_ARP_DST_IP))
+ {
+ uint32_t arp_len = sizeof(struct arphdr) +
+ (2 * (((*skb).nh.arph)->ar_hln)) +
+ (2 * (((*skb).nh.arph)->ar_pln));
+ uint32_t dst;
+ uint32_t src;
+
+ // Make sure the packet is long enough.
+ if ((((*skb).nh.raw) + arp_len) > (*skb).tail)
+ return EBT_NOMATCH;
+ // IPv4 addresses are always 4 bytes.
+ if (((*skb).nh.arph)->ar_pln != sizeof(uint32_t))
+ return EBT_NOMATCH;
+
+ if (info->bitmask & EBT_ARP_SRC_IP) {
+ memcpy(&src, ((*skb).nh.raw) + sizeof(struct arphdr) +
+ ((*skb).nh.arph)->ar_hln, sizeof(uint32_t));
+ if (FWINV(info->saddr != (src & info->smsk),
+ EBT_ARP_SRC_IP))
+ return EBT_NOMATCH;
+ }
+
+ if (info->bitmask & EBT_ARP_DST_IP) {
+ memcpy(&dst, ((*skb).nh.raw)+sizeof(struct arphdr) +
+ (2*(((*skb).nh.arph)->ar_hln)) +
+ (((*skb).nh.arph)->ar_pln), sizeof(uint32_t));
+ if (FWINV(info->daddr != (dst & info->dmsk),
+ EBT_ARP_DST_IP))
+ return EBT_NOMATCH;
+ }
+ }
+
+ if (info->bitmask & (EBT_ARP_SRC_MAC | EBT_ARP_DST_MAC))
+ {
+ uint32_t arp_len = sizeof(struct arphdr) +
+ (2 * (((*skb).nh.arph)->ar_hln)) +
+ (2 * (((*skb).nh.arph)->ar_pln));
+ unsigned char dst[ETH_ALEN];
+ unsigned char src[ETH_ALEN];
+
+ // Make sure the packet is long enough.
+ if ((((*skb).nh.raw) + arp_len) > (*skb).tail)
+ return EBT_NOMATCH;
+ // MAC addresses are 6 bytes.
+ if (((*skb).nh.arph)->ar_hln != ETH_ALEN)
+ return EBT_NOMATCH;
+ if (info->bitmask & EBT_ARP_SRC_MAC) {
+ uint8_t verdict, i;
+
+ memcpy(&src, ((*skb).nh.raw) +
+ sizeof(struct arphdr),
+ ETH_ALEN);
+ verdict = 0;
+ for (i = 0; i < 6; i++)
+ verdict |= (src[i] ^ info->smaddr[i]) &
+ info->smmsk[i];
+ if (FWINV(verdict != 0, EBT_ARP_SRC_MAC))
+ return EBT_NOMATCH;
+ }
+
+ if (info->bitmask & EBT_ARP_DST_MAC) {
+ uint8_t verdict, i;
+
+ memcpy(&dst, ((*skb).nh.raw) +
+ sizeof(struct arphdr) +
+ (((*skb).nh.arph)->ar_hln) +
+ (((*skb).nh.arph)->ar_pln),
+ ETH_ALEN);
+ verdict = 0;
+ for (i = 0; i < 6; i++)
+ verdict |= (dst[i] ^ info->dmaddr[i]) &
+ info->dmmsk[i];
+ if (FWINV(verdict != 0, EBT_ARP_DST_MAC))
+ return EBT_NOMATCH;
+ }
+ }
+
+ return EBT_MATCH;
+}
+
+static int ebt_arp_check(const char *tablename, unsigned int hookmask,
+ const struct ebt_entry *e, void *data, unsigned int datalen)
+{
+ struct ebt_arp_info *info = (struct ebt_arp_info *)data;
+
+ if (datalen != EBT_ALIGN(sizeof(struct ebt_arp_info)))
+ return -EINVAL;
+ if ((e->ethproto != __constant_htons(ETH_P_ARP) &&
+ e->ethproto != __constant_htons(ETH_P_RARP)) ||
+ e->invflags & EBT_IPROTO)
+ return -EINVAL;
+ if (info->bitmask & ~EBT_ARP_MASK || info->invflags & ~EBT_ARP_MASK)
+ return -EINVAL;
+ return 0;
+}
+
+static struct ebt_match filter_arp =
+{
+ {NULL, NULL}, EBT_ARP_MATCH, ebt_filter_arp, ebt_arp_check, NULL,
+ THIS_MODULE
+};
+
+static int __init init(void)
+{
+ return ebt_register_match(&filter_arp);
+}
+
+static void __exit fini(void)
+{
+ ebt_unregister_match(&filter_arp);
+}
+
+module_init(init);
+module_exit(fini);
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_dnat.c b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_dnat.c
new file mode 100644
index 0000000..d3d1b4f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_dnat.c
@@ -0,0 +1,65 @@
+/*
+ * ebt_dnat
+ *
+ * Authors:
+ * Bart De Schuymer <bart.de.schuymer@pandora.be>
+ *
+ * June, 2002
+ *
+ */
+
+#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/netfilter_bridge/ebt_nat.h>
+#include <linux/module.h>
+#include <net/sock.h>
+
+static int ebt_target_dnat(struct sk_buff **pskb, unsigned int hooknr,
+ const struct net_device *in, const struct net_device *out,
+ const void *data, unsigned int datalen)
+{
+ struct ebt_nat_info *info = (struct ebt_nat_info *)data;
+
+ memcpy(((**pskb).mac.ethernet)->h_dest, info->mac,
+ ETH_ALEN * sizeof(unsigned char));
+ return info->target;
+}
+
+static int ebt_target_dnat_check(const char *tablename, unsigned int hookmask,
+ const struct ebt_entry *e, void *data, unsigned int datalen)
+{
+ struct ebt_nat_info *info = (struct ebt_nat_info *)data;
+
+ if (BASE_CHAIN && info->target == EBT_RETURN)
+ return -EINVAL;
+ CLEAR_BASE_CHAIN_BIT;
+ if ( (strcmp(tablename, "nat") ||
+ (hookmask & ~((1 << NF_BR_PRE_ROUTING) | (1 << NF_BR_LOCAL_OUT)))) &&
+ (strcmp(tablename, "broute") || hookmask & ~(1 << NF_BR_BROUTING)) )
+ return -EINVAL;
+ if (datalen != EBT_ALIGN(sizeof(struct ebt_nat_info)))
+ return -EINVAL;
+ if (INVALID_TARGET)
+ return -EINVAL;
+ return 0;
+}
+
+static struct ebt_target dnat =
+{
+ {NULL, NULL}, EBT_DNAT_TARGET, ebt_target_dnat, ebt_target_dnat_check,
+ NULL, THIS_MODULE
+};
+
+static int __init init(void)
+{
+ return ebt_register_target(&dnat);
+}
+
+static void __exit fini(void)
+{
+ ebt_unregister_target(&dnat);
+}
+
+module_init(init);
+module_exit(fini);
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_ip.c b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_ip.c
new file mode 100644
index 0000000..1631b33
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_ip.c
@@ -0,0 +1,121 @@
+/*
+ * ebt_ip
+ *
+ * Authors:
+ * Bart De Schuymer <bart.de.schuymer@pandora.be>
+ *
+ * April, 2002
+ *
+ * Changes:
+ * added ip-sport and ip-dport
+ * Innominate Security Technologies AG <mhopf@innominate.com>
+ * September, 2002
+ */
+
+#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/netfilter_bridge/ebt_ip.h>
+#include <linux/ip.h>
+#include <linux/in.h>
+#include <linux/module.h>
+
+struct tcpudphdr {
+ uint16_t src;
+ uint16_t dst;
+};
+
+union h_u {
+ unsigned char *raw;
+ struct tcpudphdr *tuh;
+};
+
+static int ebt_filter_ip(const struct sk_buff *skb, const struct net_device *in,
+ const struct net_device *out, const void *data,
+ unsigned int datalen)
+{
+ struct ebt_ip_info *info = (struct ebt_ip_info *)data;
+
+ if (info->bitmask & EBT_IP_TOS &&
+ FWINV(info->tos != ((*skb).nh.iph)->tos, EBT_IP_TOS))
+ return EBT_NOMATCH;
+ if (info->bitmask & EBT_IP_PROTO) {
+ if (FWINV(info->protocol != ((*skb).nh.iph)->protocol,
+ EBT_IP_PROTO))
+ return EBT_NOMATCH;
+ if ( info->protocol == IPPROTO_TCP ||
+ info->protocol == IPPROTO_UDP )
+ {
+ union h_u h;
+ h.raw = skb->data + skb->nh.iph->ihl*4;
+ if (info->bitmask & EBT_IP_DPORT) {
+ uint16_t port = ntohs(h.tuh->dst);
+ if (FWINV(port < info->dport[0] ||
+ port > info->dport[1],
+ EBT_IP_DPORT))
+ return EBT_NOMATCH;
+ }
+ if (info->bitmask & EBT_IP_SPORT) {
+ uint16_t port = ntohs(h.tuh->src);
+ if (FWINV(port < info->sport[0] ||
+ port > info->sport[1],
+ EBT_IP_SPORT))
+ return EBT_NOMATCH;
+ }
+ }
+ }
+ if (info->bitmask & EBT_IP_SOURCE &&
+ FWINV((((*skb).nh.iph)->saddr & info->smsk) !=
+ info->saddr, EBT_IP_SOURCE))
+ return EBT_NOMATCH;
+ if ((info->bitmask & EBT_IP_DEST) &&
+ FWINV((((*skb).nh.iph)->daddr & info->dmsk) !=
+ info->daddr, EBT_IP_DEST))
+ return EBT_NOMATCH;
+ return EBT_MATCH;
+}
+
+static int ebt_ip_check(const char *tablename, unsigned int hookmask,
+ const struct ebt_entry *e, void *data, unsigned int datalen)
+{
+ struct ebt_ip_info *info = (struct ebt_ip_info *)data;
+
+ if (datalen != EBT_ALIGN(sizeof(struct ebt_ip_info)))
+ return -EINVAL;
+ if (e->ethproto != __constant_htons(ETH_P_IP) ||
+ e->invflags & EBT_IPROTO)
+ return -EINVAL;
+ if (info->bitmask & ~EBT_IP_MASK || info->invflags & ~EBT_IP_MASK)
+ return -EINVAL;
+ if (info->bitmask & (EBT_IP_DPORT | EBT_IP_SPORT)) {
+ if (!info->bitmask & EBT_IPROTO)
+ return -EINVAL;
+ if (info->protocol != IPPROTO_TCP &&
+ info->protocol != IPPROTO_UDP)
+ return -EINVAL;
+ }
+ if (info->bitmask & EBT_IP_DPORT && info->dport[0] > info->dport[1])
+ return -EINVAL;
+ if (info->bitmask & EBT_IP_SPORT && info->sport[0] > info->sport[1])
+ return -EINVAL;
+ return 0;
+}
+
+static struct ebt_match filter_ip =
+{
+ {NULL, NULL}, EBT_IP_MATCH, ebt_filter_ip, ebt_ip_check, NULL,
+ THIS_MODULE
+};
+
+static int __init init(void)
+{
+ return ebt_register_match(&filter_ip);
+}
+
+static void __exit fini(void)
+{
+ ebt_unregister_match(&filter_ip);
+}
+
+module_init(init);
+module_exit(fini);
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_log.c b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_log.c
new file mode 100644
index 0000000..9b8aff1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_log.c
@@ -0,0 +1,152 @@
+/*
+ * ebt_log
+ *
+ * Authors:
+ * Bart De Schuymer <bart.de.schuymer@pandora.be>
+ *
+ * April, 2002
+ *
+ */
+
+#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/netfilter_bridge/ebt_log.h>
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/in.h>
+#include <linux/if_arp.h>
+#include <linux/spinlock.h>
+
+static spinlock_t ebt_log_lock = SPIN_LOCK_UNLOCKED;
+
+static int ebt_log_check(const char *tablename, unsigned int hookmask,
+ const struct ebt_entry *e, void *data, unsigned int datalen)
+{
+ struct ebt_log_info *info = (struct ebt_log_info *)data;
+
+ if (datalen != EBT_ALIGN(sizeof(struct ebt_log_info)))
+ return -EINVAL;
+ if (info->bitmask & ~EBT_LOG_MASK)
+ return -EINVAL;
+ if (info->loglevel >= 8)
+ return -EINVAL;
+ info->prefix[EBT_LOG_PREFIX_SIZE - 1] = '\0';
+ return 0;
+}
+
+struct tcpudphdr
+{
+ uint16_t src;
+ uint16_t dst;
+};
+
+struct arppayload
+{
+ unsigned char mac_src[ETH_ALEN];
+ unsigned char ip_src[4];
+ unsigned char mac_dst[ETH_ALEN];
+ unsigned char ip_dst[4];
+};
+
+static void print_MAC(unsigned char *p)
+{
+ int i;
+
+ for (i = 0; i < ETH_ALEN; i++, p++)
+ printk("%02x%c", *p, i == ETH_ALEN - 1 ? ' ':':');
+}
+
+#define myNIPQUAD(a) a[0], a[1], a[2], a[3]
+static void ebt_log(const struct sk_buff *skb, const struct net_device *in,
+ const struct net_device *out, const void *data, unsigned int datalen)
+{
+ struct ebt_log_info *info = (struct ebt_log_info *)data;
+ char level_string[4] = "< >";
+ level_string[1] = '0' + info->loglevel;
+
+ spin_lock_bh(&ebt_log_lock);
+ printk(level_string);
+ printk("%s IN=%s OUT=%s ", info->prefix, in ? in->name : "",
+ out ? out->name : "");
+
+ printk("MAC source = ");
+ print_MAC((skb->mac.ethernet)->h_source);
+ printk("MAC dest = ");
+ print_MAC((skb->mac.ethernet)->h_dest);
+
+ printk("proto = 0x%04x", ntohs(((*skb).mac.ethernet)->h_proto));
+
+ if ((info->bitmask & EBT_LOG_IP) && skb->mac.ethernet->h_proto ==
+ htons(ETH_P_IP)){
+ struct iphdr *iph = skb->nh.iph;
+ printk(" IP SRC=%u.%u.%u.%u IP DST=%u.%u.%u.%u,",
+ NIPQUAD(iph->saddr), NIPQUAD(iph->daddr));
+ printk(" IP tos=0x%02X, IP proto=%d", iph->tos, iph->protocol);
+ if (iph->protocol == IPPROTO_TCP ||
+ iph->protocol == IPPROTO_UDP) {
+ struct tcpudphdr *ports = (struct tcpudphdr *)(skb->data + iph->ihl*4);
+
+ if (skb->data + iph->ihl*4 > skb->tail) {
+ printk(" INCOMPLETE TCP/UDP header");
+ goto out;
+ }
+ printk(" SPT=%u DPT=%u", ntohs(ports->src),
+ ntohs(ports->dst));
+ }
+ goto out;
+ }
+
+ if ((info->bitmask & EBT_LOG_ARP) &&
+ ((skb->mac.ethernet->h_proto == __constant_htons(ETH_P_ARP)) ||
+ (skb->mac.ethernet->h_proto == __constant_htons(ETH_P_RARP)))) {
+ struct arphdr * arph = skb->nh.arph;
+ printk(" ARP HTYPE=%d, PTYPE=0x%04x, OPCODE=%d",
+ ntohs(arph->ar_hrd), ntohs(arph->ar_pro),
+ ntohs(arph->ar_op));
+ /* If it's for Ethernet and the lengths are OK,
+ * then log the ARP payload */
+ if (arph->ar_hrd == __constant_htons(1) &&
+ arph->ar_hln == ETH_ALEN &&
+ arph->ar_pln == sizeof(uint32_t)) {
+ struct arppayload *arpp = (struct arppayload *)(skb->data + sizeof(*arph));
+
+ if (skb->data + sizeof(*arph) > skb->tail) {
+ printk(" INCOMPLETE ARP header");
+ goto out;
+ }
+
+ printk(" ARP MAC SRC=");
+ print_MAC(arpp->mac_src);
+ printk(" ARP IP SRC=%u.%u.%u.%u",
+ myNIPQUAD(arpp->ip_src));
+ printk(" ARP MAC DST=");
+ print_MAC(arpp->mac_dst);
+ printk(" ARP IP DST=%u.%u.%u.%u",
+ myNIPQUAD(arpp->ip_dst));
+ }
+
+ }
+out:
+ printk("\n");
+ spin_unlock_bh(&ebt_log_lock);
+}
+
+static struct ebt_watcher log =
+{
+ {NULL, NULL}, EBT_LOG_WATCHER, ebt_log, ebt_log_check, NULL,
+ THIS_MODULE
+};
+
+static int __init init(void)
+{
+ return ebt_register_watcher(&log);
+}
+
+static void __exit fini(void)
+{
+ ebt_unregister_watcher(&log);
+}
+
+module_init(init);
+module_exit(fini);
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_mark.c b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_mark.c
new file mode 100644
index 0000000..af5c085
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_mark.c
@@ -0,0 +1,66 @@
+/*
+ * ebt_mark
+ *
+ * Authors:
+ * Bart De Schuymer <bart.de.schuymer@pandora.be>
+ *
+ * July, 2002
+ *
+ */
+
+// The mark target can be used in any chain
+// I believe adding a mangle table just for marking is total overkill
+// Marking a frame doesn't really change anything in the frame anyway
+
+#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/netfilter_bridge/ebt_mark_t.h>
+#include <linux/module.h>
+
+static int ebt_target_mark(struct sk_buff **pskb, unsigned int hooknr,
+ const struct net_device *in, const struct net_device *out,
+ const void *data, unsigned int datalen)
+{
+ struct ebt_mark_t_info *info = (struct ebt_mark_t_info *)data;
+
+ if ((*pskb)->nfmark != info->mark) {
+ (*pskb)->nfmark = info->mark;
+ (*pskb)->nfcache |= NFC_ALTERED;
+ }
+ return info->target;
+}
+
+static int ebt_target_mark_check(const char *tablename, unsigned int hookmask,
+ const struct ebt_entry *e, void *data, unsigned int datalen)
+{
+ struct ebt_mark_t_info *info = (struct ebt_mark_t_info *)data;
+
+ if (datalen != EBT_ALIGN(sizeof(struct ebt_mark_t_info)))
+ return -EINVAL;
+ if (BASE_CHAIN && info->target == EBT_RETURN)
+ return -EINVAL;
+ CLEAR_BASE_CHAIN_BIT;
+ if (INVALID_TARGET)
+ return -EINVAL;
+ return 0;
+}
+
+static struct ebt_target mark_target =
+{
+ {NULL, NULL}, EBT_MARK_TARGET, ebt_target_mark,
+ ebt_target_mark_check, NULL, THIS_MODULE
+};
+
+static int __init init(void)
+{
+ return ebt_register_target(&mark_target);
+}
+
+static void __exit fini(void)
+{
+ ebt_unregister_target(&mark_target);
+}
+
+module_init(init);
+module_exit(fini);
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_mark_m.c b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_mark_m.c
new file mode 100644
index 0000000..66916bb
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_mark_m.c
@@ -0,0 +1,61 @@
+/*
+ * ebt_mark_m
+ *
+ * Authors:
+ * Bart De Schuymer <bart.de.schuymer@pandora.be>
+ *
+ * July, 2002
+ *
+ */
+
+#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/netfilter_bridge/ebt_mark_m.h>
+#include <linux/module.h>
+
+static int ebt_filter_mark(const struct sk_buff *skb,
+ const struct net_device *in, const struct net_device *out, const void *data,
+ unsigned int datalen)
+{
+ struct ebt_mark_m_info *info = (struct ebt_mark_m_info *) data;
+
+ if (info->bitmask & EBT_MARK_OR)
+ return !(!!(skb->nfmark & info->mask) ^ info->invert);
+ return !(((skb->nfmark & info->mask) == info->mark) ^ info->invert);
+}
+
+static int ebt_mark_check(const char *tablename, unsigned int hookmask,
+ const struct ebt_entry *e, void *data, unsigned int datalen)
+{
+ struct ebt_mark_m_info *info = (struct ebt_mark_m_info *) data;
+
+ if (datalen != EBT_ALIGN(sizeof(struct ebt_mark_m_info)))
+ return -EINVAL;
+ if (info->bitmask & ~EBT_MARK_MASK)
+ return -EINVAL;
+ if ((info->bitmask & EBT_MARK_OR) && (info->bitmask & EBT_MARK_AND))
+ return -EINVAL;
+ if (!info->bitmask)
+ return -EINVAL;
+ return 0;
+}
+
+static struct ebt_match filter_mark =
+{
+ {NULL, NULL}, EBT_MARK_MATCH, ebt_filter_mark, ebt_mark_check, NULL,
+ THIS_MODULE
+};
+
+static int __init init(void)
+{
+ return ebt_register_match(&filter_mark);
+}
+
+static void __exit fini(void)
+{
+ ebt_unregister_match(&filter_mark);
+}
+
+module_init(init);
+module_exit(fini);
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_redirect.c b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_redirect.c
new file mode 100644
index 0000000..661165e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_redirect.c
@@ -0,0 +1,71 @@
+/*
+ * ebt_redirect
+ *
+ * Authors:
+ * Bart De Schuymer <bart.de.schuymer@pandora.be>
+ *
+ * April, 2002
+ *
+ */
+
+#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/netfilter_bridge/ebt_redirect.h>
+#include <linux/module.h>
+#include <net/sock.h>
+#include "../br_private.h"
+
+static int ebt_target_redirect(struct sk_buff **pskb, unsigned int hooknr,
+ const struct net_device *in, const struct net_device *out,
+ const void *data, unsigned int datalen)
+{
+ struct ebt_redirect_info *info = (struct ebt_redirect_info *)data;
+
+ if (hooknr != NF_BR_BROUTING)
+ memcpy((**pskb).mac.ethernet->h_dest,
+ in->br_port->br->dev.dev_addr, ETH_ALEN);
+ else {
+ memcpy((**pskb).mac.ethernet->h_dest,
+ in->dev_addr, ETH_ALEN);
+ (*pskb)->pkt_type = PACKET_HOST;
+ }
+ return info->target;
+}
+
+static int ebt_target_redirect_check(const char *tablename, unsigned int hookmask,
+ const struct ebt_entry *e, void *data, unsigned int datalen)
+{
+ struct ebt_redirect_info *info = (struct ebt_redirect_info *)data;
+
+ if (datalen != EBT_ALIGN(sizeof(struct ebt_redirect_info)))
+ return -EINVAL;
+ if (BASE_CHAIN && info->target == EBT_RETURN)
+ return -EINVAL;
+ CLEAR_BASE_CHAIN_BIT;
+ if ( (strcmp(tablename, "nat") || hookmask & ~(1 << NF_BR_PRE_ROUTING)) &&
+ (strcmp(tablename, "broute") || hookmask & ~(1 << NF_BR_BROUTING)) )
+ return -EINVAL;
+ if (INVALID_TARGET)
+ return -EINVAL;
+ return 0;
+}
+
+static struct ebt_target redirect_target =
+{
+ {NULL, NULL}, EBT_REDIRECT_TARGET, ebt_target_redirect,
+ ebt_target_redirect_check, NULL, THIS_MODULE
+};
+
+static int __init init(void)
+{
+ return ebt_register_target(&redirect_target);
+}
+
+static void __exit fini(void)
+{
+ ebt_unregister_target(&redirect_target);
+}
+
+module_init(init);
+module_exit(fini);
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_snat.c b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_snat.c
new file mode 100644
index 0000000..a327a1d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_snat.c
@@ -0,0 +1,64 @@
+/*
+ * ebt_snat
+ *
+ * Authors:
+ * Bart De Schuymer <bart.de.schuymer@pandora.be>
+ *
+ * June, 2002
+ *
+ */
+
+#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/netfilter_bridge/ebt_nat.h>
+#include <linux/module.h>
+
+static int ebt_target_snat(struct sk_buff **pskb, unsigned int hooknr,
+ const struct net_device *in, const struct net_device *out,
+ const void *data, unsigned int datalen)
+{
+ struct ebt_nat_info *info = (struct ebt_nat_info *) data;
+
+ memcpy(((**pskb).mac.ethernet)->h_source, info->mac,
+ ETH_ALEN * sizeof(unsigned char));
+ return info->target;
+}
+
+static int ebt_target_snat_check(const char *tablename, unsigned int hookmask,
+ const struct ebt_entry *e, void *data, unsigned int datalen)
+{
+ struct ebt_nat_info *info = (struct ebt_nat_info *) data;
+
+ if (datalen != EBT_ALIGN(sizeof(struct ebt_nat_info)))
+ return -EINVAL;
+ if (BASE_CHAIN && info->target == EBT_RETURN)
+ return -EINVAL;
+ CLEAR_BASE_CHAIN_BIT;
+ if (strcmp(tablename, "nat"))
+ return -EINVAL;
+ if (hookmask & ~(1 << NF_BR_POST_ROUTING))
+ return -EINVAL;
+ if (INVALID_TARGET)
+ return -EINVAL;
+ return 0;
+}
+
+static struct ebt_target snat =
+{
+ {NULL, NULL}, EBT_SNAT_TARGET, ebt_target_snat, ebt_target_snat_check,
+ NULL, THIS_MODULE
+};
+
+static int __init init(void)
+{
+ return ebt_register_target(&snat);
+}
+
+static void __exit fini(void)
+{
+ ebt_unregister_target(&snat);
+}
+
+module_init(init);
+module_exit(fini);
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_vlan.c b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_vlan.c
new file mode 100644
index 0000000..35abbcb
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebt_vlan.c
@@ -0,0 +1,259 @@
+/*
+ * Description: EBTables 802.1Q match extension kernelspace module.
+ * Authors: Nick Fedchik <nick@fedchik.org.ua>
+ * Bart De Schuymer <bart.de.schuymer@pandora.be>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/if_ether.h>
+#include <linux/if_vlan.h>
+#include <linux/module.h>
+#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/netfilter_bridge/ebt_vlan.h>
+
+static unsigned char debug;
+#define MODULE_VERSION "0.6"
+
+MODULE_PARM(debug, "0-1b");
+MODULE_PARM_DESC(debug, "debug=1 is turn on debug messages");
+MODULE_AUTHOR("Nick Fedchik <nick@fedchik.org.ua>");
+MODULE_DESCRIPTION("802.1Q match module (ebtables extension), v"
+ MODULE_VERSION);
+MODULE_LICENSE("GPL");
+
+
+#define DEBUG_MSG(args...) if (debug) printk (KERN_DEBUG "ebt_vlan: " args)
+#define INV_FLAG(_inv_flag_) (info->invflags & _inv_flag_) ? "!" : ""
+#define GET_BITMASK(_BIT_MASK_) info->bitmask & _BIT_MASK_
+#define SET_BITMASK(_BIT_MASK_) info->bitmask |= _BIT_MASK_
+#define EXIT_ON_MISMATCH(_MATCH_,_MASK_) if (!((info->_MATCH_ == _MATCH_)^!!(info->invflags & _MASK_))) return 1;
+
+/*
+ * Function description: ebt_filter_vlan() is main engine for
+ * checking passed 802.1Q frame according to
+ * the passed extension parameters (in the *data buffer)
+ * ebt_filter_vlan() is called after successfull check the rule params
+ * by ebt_check_vlan() function.
+ * Parameters:
+ * const struct sk_buff *skb - pointer to passed ethernet frame buffer
+ * const void *data - pointer to passed extension parameters
+ * unsigned int datalen - length of passed *data buffer
+ * const struct net_device *in -
+ * const struct net_device *out -
+ * const struct ebt_counter *c -
+ * Returned values:
+ * 0 - ok (all rule params matched)
+ * 1 - miss (rule params not acceptable to the parsed frame)
+ */
+static int
+ebt_filter_vlan(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *data, unsigned int datalen)
+{
+ struct ebt_vlan_info *info = (struct ebt_vlan_info *) data; /* userspace data */
+ struct vlan_ethhdr *frame = (struct vlan_ethhdr *) skb->mac.raw; /* Passed tagged frame */
+
+ unsigned short TCI; /* Whole TCI, given from parsed frame */
+ unsigned short id; /* VLAN ID, given from frame TCI */
+ unsigned char prio; /* user_priority, given from frame TCI */
+ unsigned short encap; /* VLAN encapsulated Type/Length field, given from orig frame */
+
+ /*
+ * Tag Control Information (TCI) consists of the following elements:
+ * - User_priority. The user_priority field is three bits in length,
+ * interpreted as a binary number.
+ * - Canonical Format Indicator (CFI). The Canonical Format Indicator
+ * (CFI) is a single bit flag value. Currently ignored.
+ * - VLAN Identifier (VID). The VID is encoded as
+ * an unsigned binary number.
+ */
+ TCI = ntohs(frame->h_vlan_TCI);
+ id = TCI & VLAN_VID_MASK;
+ prio = (TCI >> 13) & 0x7;
+ encap = frame->h_vlan_encapsulated_proto;
+
+ /*
+ * Checking VLAN Identifier (VID)
+ */
+ if (GET_BITMASK(EBT_VLAN_ID)) { /* Is VLAN ID parsed? */
+ EXIT_ON_MISMATCH(id, EBT_VLAN_ID);
+ }
+ /*
+ * Checking user_priority
+ */
+ if (GET_BITMASK(EBT_VLAN_PRIO)) { /* Is VLAN user_priority parsed? */
+ EXIT_ON_MISMATCH(prio, EBT_VLAN_PRIO);
+ }
+ /*
+ * Checking Encapsulated Proto (Length/Type) field
+ */
+ if (GET_BITMASK(EBT_VLAN_ENCAP)) { /* Is VLAN Encap parsed? */
+ EXIT_ON_MISMATCH(encap, EBT_VLAN_ENCAP);
+ }
+ /*
+ * All possible extension parameters was parsed.
+ * If rule never returned by missmatch, then all ok.
+ */
+ return 0;
+}
+
+/*
+ * Function description: ebt_vlan_check() is called when userspace
+ * delivers the table entry to the kernel,
+ * and to check that userspace doesn't give a bad table.
+ * Parameters:
+ * const char *tablename - table name string
+ * unsigned int hooknr - hook number
+ * const struct ebt_entry *e - ebtables entry basic set
+ * const void *data - pointer to passed extension parameters
+ * unsigned int datalen - length of passed *data buffer
+ * Returned values:
+ * 0 - ok (all delivered rule params are correct)
+ * 1 - miss (rule params is out of range, invalid, incompatible, etc.)
+ */
+static int
+ebt_check_vlan(const char *tablename,
+ unsigned int hooknr,
+ const struct ebt_entry *e, void *data, unsigned int datalen)
+{
+ struct ebt_vlan_info *info = (struct ebt_vlan_info *) data;
+
+ /*
+ * Parameters buffer overflow check
+ */
+ if (datalen != EBT_ALIGN(sizeof(struct ebt_vlan_info))) {
+ DEBUG_MSG
+ ("passed size %d is not eq to ebt_vlan_info (%d)\n",
+ datalen, sizeof(struct ebt_vlan_info));
+ return -EINVAL;
+ }
+
+ /*
+ * Is it 802.1Q frame checked?
+ */
+ if (e->ethproto != __constant_htons(ETH_P_8021Q)) {
+ DEBUG_MSG
+ ("passed entry proto %2.4X is not 802.1Q (8100)\n",
+ (unsigned short) ntohs(e->ethproto));
+ return -EINVAL;
+ }
+
+ /*
+ * Check for bitmask range
+ * True if even one bit is out of mask
+ */
+ if (info->bitmask & ~EBT_VLAN_MASK) {
+ DEBUG_MSG("bitmask %2X is out of mask (%2X)\n",
+ info->bitmask, EBT_VLAN_MASK);
+ return -EINVAL;
+ }
+
+ /*
+ * Check for inversion flags range
+ */
+ if (info->invflags & ~EBT_VLAN_MASK) {
+ DEBUG_MSG("inversion flags %2X is out of mask (%2X)\n",
+ info->invflags, EBT_VLAN_MASK);
+ return -EINVAL;
+ }
+
+ /*
+ * Reserved VLAN ID (VID) values
+ * -----------------------------
+ * 0 - The null VLAN ID.
+ * 1 - The default Port VID (PVID)
+ * 0x0FFF - Reserved for implementation use.
+ * if_vlan.h: VLAN_GROUP_ARRAY_LEN 4096.
+ */
+ if (GET_BITMASK(EBT_VLAN_ID)) { /* when vlan-id param was spec-ed */
+ if (!!info->id) { /* if id!=0 => check vid range */
+ if (info->id > VLAN_GROUP_ARRAY_LEN) {
+ DEBUG_MSG
+ ("id %d is out of range (1-4096)\n",
+ info->id);
+ return -EINVAL;
+ }
+ /*
+ * Note: This is valid VLAN-tagged frame point.
+ * Any value of user_priority are acceptable,
+ * but should be ignored according to 802.1Q Std.
+ * So we just drop the prio flag.
+ */
+ info->bitmask &= ~EBT_VLAN_PRIO;
+ }
+ /*
+ * Else, id=0 (null VLAN ID) => user_priority range (any?)
+ */
+ }
+
+ if (GET_BITMASK(EBT_VLAN_PRIO)) {
+ if ((unsigned char) info->prio > 7) {
+ DEBUG_MSG
+ ("prio %d is out of range (0-7)\n",
+ info->prio);
+ return -EINVAL;
+ }
+ }
+ /*
+ * Check for encapsulated proto range - it is possible to be
+ * any value for u_short range.
+ * if_ether.h: ETH_ZLEN 60 - Min. octets in frame sans FCS
+ */
+ if (GET_BITMASK(EBT_VLAN_ENCAP)) {
+ if ((unsigned short) ntohs(info->encap) < ETH_ZLEN) {
+ DEBUG_MSG
+ ("encap frame length %d is less than minimal\n",
+ ntohs(info->encap));
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static struct ebt_match filter_vlan = {
+ {NULL, NULL},
+ EBT_VLAN_MATCH,
+ ebt_filter_vlan,
+ ebt_check_vlan,
+ NULL,
+ THIS_MODULE
+};
+
+/*
+ * Module initialization function.
+ */
+static int __init init(void)
+{
+ DEBUG_MSG("ebtables 802.1Q extension module v"
+ MODULE_VERSION "\n");
+ DEBUG_MSG("module debug=%d\n", !!debug);
+ return ebt_register_match(&filter_vlan);
+}
+
+/*
+ * Module "finalization" function
+ */
+static void __exit fini(void)
+{
+ ebt_unregister_match(&filter_vlan);
+}
+
+module_init(init);
+module_exit(fini);
+
+EXPORT_NO_SYMBOLS;
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/ebtable_broute.c b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebtable_broute.c
new file mode 100644
index 0000000..ea443bb
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebtable_broute.c
@@ -0,0 +1,79 @@
+/*
+ * ebtable_broute
+ *
+ * Authors:
+ * Bart De Schuymer <bart.de.schuymer@pandora.be>
+ *
+ * April, 2002
+ *
+ * This table lets you choose between routing and bridging for frames
+ * entering on a bridge enslaved nic. This table is traversed before any
+ * other ebtables table. See net/bridge/br_input.c.
+ */
+
+#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/module.h>
+#include <linux/if_bridge.h>
+#include <linux/brlock.h>
+
+// EBT_ACCEPT means the frame will be bridged
+// EBT_DROP means the frame will be routed
+static struct ebt_entries initial_chain =
+ {0, "BROUTING", 0, EBT_ACCEPT, 0};
+
+static struct ebt_replace initial_table =
+{
+ "broute", 1 << NF_BR_BROUTING, 0, sizeof(struct ebt_entries),
+ { [NF_BR_BROUTING]&initial_chain}, 0, NULL, (char *)&initial_chain
+};
+
+static int check(const struct ebt_table_info *info, unsigned int valid_hooks)
+{
+ if (valid_hooks & ~(1 << NF_BR_BROUTING))
+ return -EINVAL;
+ return 0;
+}
+
+static struct ebt_table broute_table =
+{
+ {NULL, NULL}, "broute", &initial_table, 1 << NF_BR_BROUTING,
+ RW_LOCK_UNLOCKED, check, NULL
+};
+
+static int ebt_broute(struct sk_buff **pskb)
+{
+ int ret;
+
+ ret = ebt_do_table(NF_BR_BROUTING, pskb, (*pskb)->dev, NULL,
+ &broute_table);
+ if (ret == NF_DROP)
+ return 1; // route it
+ return 0; // bridge it
+}
+
+static int __init init(void)
+{
+ int ret;
+
+ ret = ebt_register_table(&broute_table);
+ if (ret < 0)
+ return ret;
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ // see br_input.c
+ br_should_route_hook = ebt_broute;
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ return ret;
+}
+
+static void __exit fini(void)
+{
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ br_should_route_hook = NULL;
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ ebt_unregister_table(&broute_table);
+}
+
+module_init(init);
+module_exit(fini);
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/ebtable_filter.c b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebtable_filter.c
new file mode 100644
index 0000000..9b7c0f9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebtable_filter.c
@@ -0,0 +1,90 @@
+/*
+ * ebtable_filter
+ *
+ * Authors:
+ * Bart De Schuymer <bart.de.schuymer@pandora.be>
+ *
+ * April, 2002
+ *
+ */
+
+#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/module.h>
+
+#define FILTER_VALID_HOOKS ((1 << NF_BR_LOCAL_IN) | (1 << NF_BR_FORWARD) | \
+ (1 << NF_BR_LOCAL_OUT))
+
+static struct ebt_entries initial_chains[] =
+{
+ {0, "INPUT", 0, EBT_ACCEPT, 0},
+ {0, "FORWARD", 0, EBT_ACCEPT, 0},
+ {0, "OUTPUT", 0, EBT_ACCEPT, 0}
+};
+
+static struct ebt_replace initial_table =
+{
+ "filter", FILTER_VALID_HOOKS, 0, 3 * sizeof(struct ebt_entries),
+ { [NF_BR_LOCAL_IN]&initial_chains[0], [NF_BR_FORWARD]&initial_chains[1],
+ [NF_BR_LOCAL_OUT]&initial_chains[2] }, 0, NULL, (char *)initial_chains
+};
+
+static int check(const struct ebt_table_info *info, unsigned int valid_hooks)
+{
+ if (valid_hooks & ~FILTER_VALID_HOOKS)
+ return -EINVAL;
+ return 0;
+}
+
+static struct ebt_table frame_filter =
+{
+ {NULL, NULL}, "filter", &initial_table, FILTER_VALID_HOOKS,
+ RW_LOCK_UNLOCKED, check, NULL
+};
+
+static unsigned int
+ebt_hook (unsigned int hook, struct sk_buff **pskb, const struct net_device *in,
+ const struct net_device *out, int (*okfn)(struct sk_buff *))
+{
+ return ebt_do_table(hook, pskb, in, out, &frame_filter);
+}
+
+static struct nf_hook_ops ebt_ops_filter[] = {
+ { { NULL, NULL }, ebt_hook, PF_BRIDGE, NF_BR_LOCAL_IN,
+ NF_BR_PRI_FILTER_BRIDGED},
+ { { NULL, NULL }, ebt_hook, PF_BRIDGE, NF_BR_FORWARD,
+ NF_BR_PRI_FILTER_BRIDGED},
+ { { NULL, NULL }, ebt_hook, PF_BRIDGE, NF_BR_LOCAL_OUT,
+ NF_BR_PRI_FILTER_OTHER}
+};
+
+static int __init init(void)
+{
+ int i, j, ret;
+
+ ret = ebt_register_table(&frame_filter);
+ if (ret < 0)
+ return ret;
+ for (i = 0; i < sizeof(ebt_ops_filter) / sizeof(ebt_ops_filter[0]); i++)
+ if ((ret = nf_register_hook(&ebt_ops_filter[i])) < 0)
+ goto cleanup;
+ return ret;
+cleanup:
+ for (j = 0; j < i; j++)
+ nf_unregister_hook(&ebt_ops_filter[j]);
+ ebt_unregister_table(&frame_filter);
+ return ret;
+}
+
+static void __exit fini(void)
+{
+ int i;
+
+ for (i = 0; i < sizeof(ebt_ops_filter) / sizeof(ebt_ops_filter[0]); i++)
+ nf_unregister_hook(&ebt_ops_filter[i]);
+ ebt_unregister_table(&frame_filter);
+}
+
+module_init(init);
+module_exit(fini);
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/ebtable_nat.c b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebtable_nat.c
new file mode 100644
index 0000000..f687c5a
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebtable_nat.c
@@ -0,0 +1,96 @@
+/*
+ * ebtable_nat
+ *
+ * Authors:
+ * Bart De Schuymer <bart.de.schuymer@pandora.be>
+ *
+ * April, 2002
+ *
+ */
+
+#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/module.h>
+#define NAT_VALID_HOOKS ((1 << NF_BR_PRE_ROUTING) | (1 << NF_BR_LOCAL_OUT) | \
+ (1 << NF_BR_POST_ROUTING))
+
+static struct ebt_entries initial_chains[] =
+{
+ {0, "PREROUTING", 0, EBT_ACCEPT, 0},
+ {0, "OUTPUT", 0, EBT_ACCEPT, 0},
+ {0, "POSTROUTING", 0, EBT_ACCEPT, 0}
+};
+
+static struct ebt_replace initial_table =
+{
+ "nat", NAT_VALID_HOOKS, 0, 3 * sizeof(struct ebt_entries),
+ { [NF_BR_PRE_ROUTING]&initial_chains[0], [NF_BR_LOCAL_OUT]&initial_chains[1],
+ [NF_BR_POST_ROUTING]&initial_chains[2] }, 0, NULL, (char *)initial_chains
+};
+
+static int check(const struct ebt_table_info *info, unsigned int valid_hooks)
+{
+ if (valid_hooks & ~NAT_VALID_HOOKS)
+ return -EINVAL;
+ return 0;
+}
+
+static struct ebt_table frame_nat =
+{
+ {NULL, NULL}, "nat", &initial_table, NAT_VALID_HOOKS,
+ RW_LOCK_UNLOCKED, check, NULL
+};
+
+static unsigned int
+ebt_nat_dst(unsigned int hook, struct sk_buff **pskb, const struct net_device *in
+ , const struct net_device *out, int (*okfn)(struct sk_buff *))
+{
+ return ebt_do_table(hook, pskb, in, out, &frame_nat);
+}
+
+static unsigned int
+ebt_nat_src(unsigned int hook, struct sk_buff **pskb, const struct net_device *in
+ , const struct net_device *out, int (*okfn)(struct sk_buff *))
+{
+ return ebt_do_table(hook, pskb, in, out, &frame_nat);
+}
+
+static struct nf_hook_ops ebt_ops_nat[] = {
+ { { NULL, NULL }, ebt_nat_dst, PF_BRIDGE, NF_BR_LOCAL_OUT,
+ NF_BR_PRI_NAT_DST_OTHER},
+ { { NULL, NULL }, ebt_nat_src, PF_BRIDGE, NF_BR_POST_ROUTING,
+ NF_BR_PRI_NAT_SRC},
+ { { NULL, NULL }, ebt_nat_dst, PF_BRIDGE, NF_BR_PRE_ROUTING,
+ NF_BR_PRI_NAT_DST_BRIDGED},
+};
+
+static int __init init(void)
+{
+ int i, ret, j;
+
+ ret = ebt_register_table(&frame_nat);
+ if (ret < 0)
+ return ret;
+ for (i = 0; i < sizeof(ebt_ops_nat) / sizeof(ebt_ops_nat[0]); i++)
+ if ((ret = nf_register_hook(&ebt_ops_nat[i])) < 0)
+ goto cleanup;
+ return ret;
+cleanup:
+ for (j = 0; j < i; j++)
+ nf_unregister_hook(&ebt_ops_nat[j]);
+ ebt_unregister_table(&frame_nat);
+ return ret;
+}
+
+static void __exit fini(void)
+{
+ int i;
+
+ for (i = 0; i < sizeof(ebt_ops_nat) / sizeof(ebt_ops_nat[0]); i++)
+ nf_unregister_hook(&ebt_ops_nat[i]);
+ ebt_unregister_table(&frame_nat);
+}
+
+module_init(init);
+module_exit(fini);
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/bridge/netfilter/ebtables.c b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebtables.c
new file mode 100644
index 0000000..9877321
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/bridge/netfilter/ebtables.c
@@ -0,0 +1,1490 @@
+/*
+ * ebtables
+ *
+ * Author:
+ * Bart De Schuymer <bart.de.schuymer@pandora.be>
+ *
+ * ebtables.c,v 2.0, July, 2002
+ *
+ * This code is stongly inspired on the iptables code which is
+ * Copyright (C) 1999 Paul `Rusty' Russell & Michael J. Neuling
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+// used for print_string
+#include <linux/sched.h>
+#include <linux/tty.h>
+
+#include <linux/kmod.h>
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+#include <linux/netfilter_bridge/ebtables.h>
+#include <linux/spinlock.h>
+#include <asm/uaccess.h>
+#include <linux/smp.h>
+#include <net/sock.h>
+// needed for logical [in,out]-dev filtering
+#include "../br_private.h"
+
+// list_named_find
+#define ASSERT_READ_LOCK(x)
+#define ASSERT_WRITE_LOCK(x)
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#if 0 // use this for remote debugging
+// Copyright (C) 1998 by Ori Pomerantz
+// Print the string to the appropriate tty, the one
+// the current task uses
+static void print_string(char *str)
+{
+ struct tty_struct *my_tty;
+
+ /* The tty for the current task */
+ my_tty = current->tty;
+ if (my_tty != NULL) {
+ (*(my_tty->driver).write)(my_tty, 0, str, strlen(str));
+ (*(my_tty->driver).write)(my_tty, 0, "\015\012", 2);
+ }
+}
+
+#define BUGPRINT(args) print_string(args);
+#else
+#define BUGPRINT(format, args...) printk("kernel msg: ebtables bug: please "\
+ "report to author: "format, ## args)
+// #define BUGPRINT(format, args...)
+#endif
+#define MEMPRINT(format, args...) printk("kernel msg: ebtables "\
+ ": out of memory: "format, ## args)
+// #define MEMPRINT(format, args...)
+
+
+
+// Each cpu has its own set of counters, so there is no need for write_lock in
+// the softirq
+// For reading or updating the counters, the user context needs to
+// get a write_lock
+
+// The size of each set of counters is altered to get cache alignment
+#define SMP_ALIGN(x) (((x) + SMP_CACHE_BYTES-1) & ~(SMP_CACHE_BYTES-1))
+#define COUNTER_OFFSET(n) (SMP_ALIGN(n * sizeof(struct ebt_counter)))
+#define COUNTER_BASE(c, n, cpu) ((struct ebt_counter *)(((char *)c) + \
+ COUNTER_OFFSET(n) * cpu))
+
+
+
+static DECLARE_MUTEX(ebt_mutex);
+static LIST_HEAD(ebt_tables);
+static LIST_HEAD(ebt_targets);
+static LIST_HEAD(ebt_matches);
+static LIST_HEAD(ebt_watchers);
+
+static struct ebt_target ebt_standard_target =
+{ {NULL, NULL}, EBT_STANDARD_TARGET, NULL, NULL, NULL, NULL};
+
+static inline int ebt_do_watcher (struct ebt_entry_watcher *w,
+ const struct sk_buff *skb, const struct net_device *in,
+ const struct net_device *out)
+{
+ w->u.watcher->watcher(skb, in, out, w->data,
+ w->watcher_size);
+ // watchers don't give a verdict
+ return 0;
+}
+
+static inline int ebt_do_match (struct ebt_entry_match *m,
+ const struct sk_buff *skb, const struct net_device *in,
+ const struct net_device *out)
+{
+ return m->u.match->match(skb, in, out, m->data,
+ m->match_size);
+}
+
+static inline int ebt_dev_check(char *entry, const struct net_device *device)
+{
+ if (*entry == '\0')
+ return 0;
+ if (!device)
+ return 1;
+ return !!strcmp(entry, device->name);
+}
+
+#define FWINV2(bool,invflg) ((bool) ^ !!(e->invflags & invflg))
+// process standard matches
+static inline int ebt_basic_match(struct ebt_entry *e, struct ethhdr *h,
+ const struct net_device *in, const struct net_device *out)
+{
+ int verdict, i;
+
+ if (e->bitmask & EBT_802_3) {
+ if (FWINV2(ntohs(h->h_proto) >= 1536, EBT_IPROTO))
+ return 1;
+ } else if (!(e->bitmask & EBT_NOPROTO) &&
+ FWINV2(e->ethproto != h->h_proto, EBT_IPROTO))
+ return 1;
+
+ if (FWINV2(ebt_dev_check(e->in, in), EBT_IIN))
+ return 1;
+ if (FWINV2(ebt_dev_check(e->out, out), EBT_IOUT))
+ return 1;
+ if ((!in || !in->br_port) ? 0 : FWINV2(ebt_dev_check(
+ e->logical_in, &in->br_port->br->dev), EBT_ILOGICALIN))
+ return 1;
+ if ((!out || !out->br_port) ? 0 : FWINV2(ebt_dev_check(
+ e->logical_out, &out->br_port->br->dev), EBT_ILOGICALOUT))
+ return 1;
+
+ if (e->bitmask & EBT_SOURCEMAC) {
+ verdict = 0;
+ for (i = 0; i < 6; i++)
+ verdict |= (h->h_source[i] ^ e->sourcemac[i]) &
+ e->sourcemsk[i];
+ if (FWINV2(verdict != 0, EBT_ISOURCE) )
+ return 1;
+ }
+ if (e->bitmask & EBT_DESTMAC) {
+ verdict = 0;
+ for (i = 0; i < 6; i++)
+ verdict |= (h->h_dest[i] ^ e->destmac[i]) &
+ e->destmsk[i];
+ if (FWINV2(verdict != 0, EBT_IDEST) )
+ return 1;
+ }
+ return 0;
+}
+
+// Do some firewalling
+unsigned int ebt_do_table (unsigned int hook, struct sk_buff **pskb,
+ const struct net_device *in, const struct net_device *out,
+ struct ebt_table *table)
+{
+ int i, nentries;
+ struct ebt_entry *point;
+ struct ebt_counter *counter_base, *cb_base;
+ struct ebt_entry_target *t;
+ int verdict, sp = 0;
+ struct ebt_chainstack *cs;
+ struct ebt_entries *chaininfo;
+ char *base;
+ struct ebt_table_info *private = table->private;
+
+ read_lock_bh(&table->lock);
+ cb_base = COUNTER_BASE(private->counters, private->nentries,
+ cpu_number_map(smp_processor_id()));
+ if (private->chainstack)
+ cs = private->chainstack[cpu_number_map(smp_processor_id())];
+ else
+ cs = NULL;
+ chaininfo = private->hook_entry[hook];
+ nentries = private->hook_entry[hook]->nentries;
+ point = (struct ebt_entry *)(private->hook_entry[hook]->data);
+ counter_base = cb_base + private->hook_entry[hook]->counter_offset;
+ // base for chain jumps
+ base = private->entries;
+ i = 0;
+ while (i < nentries) {
+ if (ebt_basic_match(point, (**pskb).mac.ethernet, in, out))
+ goto letscontinue;
+
+ if (EBT_MATCH_ITERATE(point, ebt_do_match, *pskb, in, out) != 0)
+ goto letscontinue;
+
+ // increase counter
+ (*(counter_base + i)).pcnt++;
+ (*(counter_base + i)).bcnt+=(**pskb).len;
+
+ // these should only watch: not modify, nor tell us
+ // what to do with the packet
+ EBT_WATCHER_ITERATE(point, ebt_do_watcher, *pskb, in,
+ out);
+
+ t = (struct ebt_entry_target *)
+ (((char *)point) + point->target_offset);
+ // standard target
+ if (!t->u.target->target)
+ verdict = ((struct ebt_standard_target *)t)->verdict;
+ else
+ verdict = t->u.target->target(pskb, hook,
+ in, out, t->data, t->target_size);
+ if (verdict == EBT_ACCEPT) {
+ read_unlock_bh(&table->lock);
+ return NF_ACCEPT;
+ }
+ if (verdict == EBT_DROP) {
+ read_unlock_bh(&table->lock);
+ return NF_DROP;
+ }
+ if (verdict == EBT_RETURN) {
+letsreturn:
+#ifdef CONFIG_NETFILTER_DEBUG
+ if (sp == 0) {
+ BUGPRINT("RETURN on base chain");
+ // act like this is EBT_CONTINUE
+ goto letscontinue;
+ }
+#endif
+ sp--;
+ // put all the local variables right
+ i = cs[sp].n;
+ chaininfo = cs[sp].chaininfo;
+ nentries = chaininfo->nentries;
+ point = cs[sp].e;
+ counter_base = cb_base +
+ chaininfo->counter_offset;
+ continue;
+ }
+ if (verdict == EBT_CONTINUE)
+ goto letscontinue;
+#ifdef CONFIG_NETFILTER_DEBUG
+ if (verdict < 0) {
+ BUGPRINT("bogus standard verdict\n");
+ read_unlock_bh(&table->lock);
+ return NF_DROP;
+ }
+#endif
+ // jump to a udc
+ cs[sp].n = i + 1;
+ cs[sp].chaininfo = chaininfo;
+ cs[sp].e = (struct ebt_entry *)
+ (((char *)point) + point->next_offset);
+ i = 0;
+ chaininfo = (struct ebt_entries *) (base + verdict);
+#ifdef CONFIG_NETFILTER_DEBUG
+ if (chaininfo->distinguisher) {
+ BUGPRINT("jump to non-chain\n");
+ read_unlock_bh(&table->lock);
+ return NF_DROP;
+ }
+#endif
+ nentries = chaininfo->nentries;
+ point = (struct ebt_entry *)chaininfo->data;
+ counter_base = cb_base + chaininfo->counter_offset;
+ sp++;
+ continue;
+letscontinue:
+ point = (struct ebt_entry *)
+ (((char *)point) + point->next_offset);
+ i++;
+ }
+
+ // I actually like this :)
+ if (chaininfo->policy == EBT_RETURN)
+ goto letsreturn;
+ if (chaininfo->policy == EBT_ACCEPT) {
+ read_unlock_bh(&table->lock);
+ return NF_ACCEPT;
+ }
+ read_unlock_bh(&table->lock);
+ return NF_DROP;
+}
+
+// If it succeeds, returns element and locks mutex
+static inline void *
+find_inlist_lock_noload(struct list_head *head, const char *name, int *error,
+ struct semaphore *mutex)
+{
+ void *ret;
+
+ *error = down_interruptible(mutex);
+ if (*error != 0)
+ return NULL;
+
+ ret = list_named_find(head, name);
+ if (!ret) {
+ *error = -ENOENT;
+ up(mutex);
+ }
+ return ret;
+}
+
+#ifndef CONFIG_KMOD
+#define find_inlist_lock(h,n,p,e,m) find_inlist_lock_noload((h),(n),(e),(m))
+#else
+static void *
+find_inlist_lock(struct list_head *head, const char *name, const char *prefix,
+ int *error, struct semaphore *mutex)
+{
+ void *ret;
+
+ ret = find_inlist_lock_noload(head, name, error, mutex);
+ if (!ret) {
+ char modulename[EBT_FUNCTION_MAXNAMELEN + strlen(prefix) + 1];
+ strcpy(modulename, prefix);
+ strcat(modulename, name);
+ request_module(modulename);
+ ret = find_inlist_lock_noload(head, name, error, mutex);
+ }
+ return ret;
+}
+#endif
+
+static inline struct ebt_table *
+find_table_lock(const char *name, int *error, struct semaphore *mutex)
+{
+ return find_inlist_lock(&ebt_tables, name, "ebtable_", error, mutex);
+}
+
+static inline struct ebt_match *
+find_match_lock(const char *name, int *error, struct semaphore *mutex)
+{
+ return find_inlist_lock(&ebt_matches, name, "ebt_", error, mutex);
+}
+
+static inline struct ebt_watcher *
+find_watcher_lock(const char *name, int *error, struct semaphore *mutex)
+{
+ return find_inlist_lock(&ebt_watchers, name, "ebt_", error, mutex);
+}
+
+static inline struct ebt_target *
+find_target_lock(const char *name, int *error, struct semaphore *mutex)
+{
+ return find_inlist_lock(&ebt_targets, name, "ebt_", error, mutex);
+}
+
+static inline int
+ebt_check_match(struct ebt_entry_match *m, struct ebt_entry *e,
+ const char *name, unsigned int hookmask, unsigned int *cnt)
+{
+ struct ebt_match *match;
+ int ret;
+
+ if (((char *)m) + m->match_size + sizeof(struct ebt_entry_match) >
+ ((char *)e) + e->watchers_offset)
+ return -EINVAL;
+ match = find_match_lock(m->u.name, &ret, &ebt_mutex);
+ if (!match)
+ return ret;
+ m->u.match = match;
+ if (match->me)
+ __MOD_INC_USE_COUNT(match->me);
+ up(&ebt_mutex);
+ if (match->check &&
+ match->check(name, hookmask, e, m->data, m->match_size) != 0) {
+ BUGPRINT("match->check failed\n");
+ if (match->me)
+ __MOD_DEC_USE_COUNT(match->me);
+ return -EINVAL;
+ }
+ (*cnt)++;
+ return 0;
+}
+
+static inline int
+ebt_check_watcher(struct ebt_entry_watcher *w, struct ebt_entry *e,
+ const char *name, unsigned int hookmask, unsigned int *cnt)
+{
+ struct ebt_watcher *watcher;
+ int ret;
+
+ if (((char *)w) + w->watcher_size + sizeof(struct ebt_entry_watcher) >
+ ((char *)e) + e->target_offset)
+ return -EINVAL;
+ watcher = find_watcher_lock(w->u.name, &ret, &ebt_mutex);
+ if (!watcher)
+ return ret;
+ w->u.watcher = watcher;
+ if (watcher->me)
+ __MOD_INC_USE_COUNT(watcher->me);
+ up(&ebt_mutex);
+ if (watcher->check &&
+ watcher->check(name, hookmask, e, w->data, w->watcher_size) != 0) {
+ BUGPRINT("watcher->check failed\n");
+ if (watcher->me)
+ __MOD_DEC_USE_COUNT(watcher->me);
+ return -EINVAL;
+ }
+ (*cnt)++;
+ return 0;
+}
+
+// this one is very careful, as it is the first function
+// to parse the userspace data
+static inline int
+ebt_check_entry_size_and_hooks(struct ebt_entry *e,
+ struct ebt_table_info *newinfo, char *base, char *limit,
+ struct ebt_entries **hook_entries, unsigned int *n, unsigned int *cnt,
+ unsigned int *totalcnt, unsigned int *udc_cnt, unsigned int valid_hooks)
+{
+ int i;
+
+ for (i = 0; i < NF_BR_NUMHOOKS; i++) {
+ if ((valid_hooks & (1 << i)) == 0)
+ continue;
+ if ( (char *)hook_entries[i] - base ==
+ (char *)e - newinfo->entries)
+ break;
+ }
+ // beginning of a new chain
+ // if i == NF_BR_NUMHOOKS it must be a user defined chain
+ if (i != NF_BR_NUMHOOKS || !(e->bitmask & EBT_ENTRY_OR_ENTRIES)) {
+ if ((e->bitmask & EBT_ENTRY_OR_ENTRIES) != 0) {
+ // we make userspace set this right,
+ // so there is no misunderstanding
+ BUGPRINT("EBT_ENTRY_OR_ENTRIES shouldn't be set "
+ "in distinguisher\n");
+ return -EINVAL;
+ }
+ // this checks if the previous chain has as many entries
+ // as it said it has
+ if (*n != *cnt) {
+ BUGPRINT("nentries does not equal the nr of entries "
+ "in the chain\n");
+ return -EINVAL;
+ }
+ // before we look at the struct, be sure it is not too big
+ if ((char *)hook_entries[i] + sizeof(struct ebt_entries)
+ > limit) {
+ BUGPRINT("entries_size too small\n");
+ return -EINVAL;
+ }
+ if (((struct ebt_entries *)e)->policy != EBT_DROP &&
+ ((struct ebt_entries *)e)->policy != EBT_ACCEPT) {
+ // only RETURN from udc
+ if (i != NF_BR_NUMHOOKS ||
+ ((struct ebt_entries *)e)->policy != EBT_RETURN) {
+ BUGPRINT("bad policy\n");
+ return -EINVAL;
+ }
+ }
+ if (i == NF_BR_NUMHOOKS) // it's a user defined chain
+ (*udc_cnt)++;
+ else
+ newinfo->hook_entry[i] = (struct ebt_entries *)e;
+ if (((struct ebt_entries *)e)->counter_offset != *totalcnt) {
+ BUGPRINT("counter_offset != totalcnt");
+ return -EINVAL;
+ }
+ *n = ((struct ebt_entries *)e)->nentries;
+ *cnt = 0;
+ return 0;
+ }
+ // a plain old entry, heh
+ if (sizeof(struct ebt_entry) > e->watchers_offset ||
+ e->watchers_offset > e->target_offset ||
+ e->target_offset >= e->next_offset) {
+ BUGPRINT("entry offsets not in right order\n");
+ return -EINVAL;
+ }
+ // this is not checked anywhere else
+ if (e->next_offset - e->target_offset < sizeof(struct ebt_entry_target)) {
+ BUGPRINT("target size too small\n");
+ return -EINVAL;
+ }
+
+ (*cnt)++;
+ (*totalcnt)++;
+ return 0;
+}
+
+struct ebt_cl_stack
+{
+ struct ebt_chainstack cs;
+ int from;
+ unsigned int hookmask;
+};
+
+// we need these positions to check that the jumps to a different part of the
+// entries is a jump to the beginning of a new chain.
+static inline int
+ebt_get_udc_positions(struct ebt_entry *e, struct ebt_table_info *newinfo,
+ struct ebt_entries **hook_entries, unsigned int *n, unsigned int valid_hooks,
+ struct ebt_cl_stack *udc)
+{
+ int i;
+
+ // we're only interested in chain starts
+ if (e->bitmask & EBT_ENTRY_OR_ENTRIES)
+ return 0;
+ for (i = 0; i < NF_BR_NUMHOOKS; i++) {
+ if ((valid_hooks & (1 << i)) == 0)
+ continue;
+ if (newinfo->hook_entry[i] == (struct ebt_entries *)e)
+ break;
+ }
+ // only care about udc
+ if (i != NF_BR_NUMHOOKS)
+ return 0;
+
+ udc[*n].cs.chaininfo = (struct ebt_entries *)e;
+ // these initialisations are depended on later in check_chainloops()
+ udc[*n].cs.n = 0;
+ udc[*n].hookmask = 0;
+
+ (*n)++;
+ return 0;
+}
+
+static inline int
+ebt_cleanup_match(struct ebt_entry_match *m, unsigned int *i)
+{
+ if (i && (*i)-- == 0)
+ return 1;
+ if (m->u.match->destroy)
+ m->u.match->destroy(m->data, m->match_size);
+ if (m->u.match->me)
+ __MOD_DEC_USE_COUNT(m->u.match->me);
+
+ return 0;
+}
+
+static inline int
+ebt_cleanup_watcher(struct ebt_entry_watcher *w, unsigned int *i)
+{
+ if (i && (*i)-- == 0)
+ return 1;
+ if (w->u.watcher->destroy)
+ w->u.watcher->destroy(w->data, w->watcher_size);
+ if (w->u.watcher->me)
+ __MOD_DEC_USE_COUNT(w->u.watcher->me);
+
+ return 0;
+}
+
+static inline int
+ebt_cleanup_entry(struct ebt_entry *e, unsigned int *cnt)
+{
+ struct ebt_entry_target *t;
+
+ if ((e->bitmask & EBT_ENTRY_OR_ENTRIES) == 0)
+ return 0;
+ // we're done
+ if (cnt && (*cnt)-- == 0)
+ return 1;
+ EBT_WATCHER_ITERATE(e, ebt_cleanup_watcher, NULL);
+ EBT_MATCH_ITERATE(e, ebt_cleanup_match, NULL);
+ t = (struct ebt_entry_target *)(((char *)e) + e->target_offset);
+ if (t->u.target->destroy)
+ t->u.target->destroy(t->data, t->target_size);
+ if (t->u.target->me)
+ __MOD_DEC_USE_COUNT(t->u.target->me);
+
+ return 0;
+}
+
+static inline int
+ebt_check_entry(struct ebt_entry *e, struct ebt_table_info *newinfo,
+ const char *name, unsigned int *cnt, unsigned int valid_hooks,
+ struct ebt_cl_stack *cl_s, unsigned int udc_cnt)
+{
+ struct ebt_entry_target *t;
+ struct ebt_target *target;
+ unsigned int i, j, hook = 0, hookmask = 0;
+ int ret;
+
+ // Don't mess with the struct ebt_entries
+ if ((e->bitmask & EBT_ENTRY_OR_ENTRIES) == 0)
+ return 0;
+
+ if (e->bitmask & ~EBT_F_MASK) {
+ BUGPRINT("Unknown flag for bitmask\n");
+ return -EINVAL;
+ }
+ if (e->invflags & ~EBT_INV_MASK) {
+ BUGPRINT("Unknown flag for inv bitmask\n");
+ return -EINVAL;
+ }
+ if ( (e->bitmask & EBT_NOPROTO) && (e->bitmask & EBT_802_3) ) {
+ BUGPRINT("NOPROTO & 802_3 not allowed\n");
+ return -EINVAL;
+ }
+ // what hook do we belong to?
+ for (i = 0; i < NF_BR_NUMHOOKS; i++) {
+ if ((valid_hooks & (1 << i)) == 0)
+ continue;
+ if ((char *)newinfo->hook_entry[i] < (char *)e)
+ hook = i;
+ else
+ break;
+ }
+ // (1 << NF_BR_NUMHOOKS) tells the check functions the rule is on
+ // a base chain
+ if (i < NF_BR_NUMHOOKS)
+ hookmask = (1 << hook) | (1 << NF_BR_NUMHOOKS);
+ else {
+ for (i = 0; i < udc_cnt; i++)
+ if ((char *)(cl_s[i].cs.chaininfo) > (char *)e)
+ break;
+ if (i == 0)
+ hookmask = (1 << hook) | (1 << NF_BR_NUMHOOKS);
+ else
+ hookmask = cl_s[i - 1].hookmask;
+ }
+ i = 0;
+ ret = EBT_MATCH_ITERATE(e, ebt_check_match, e, name, hookmask, &i);
+ if (ret != 0)
+ goto cleanup_matches;
+ j = 0;
+ ret = EBT_WATCHER_ITERATE(e, ebt_check_watcher, e, name, hookmask, &j);
+ if (ret != 0)
+ goto cleanup_watchers;
+ t = (struct ebt_entry_target *)(((char *)e) + e->target_offset);
+ target = find_target_lock(t->u.name, &ret, &ebt_mutex);
+ if (!target)
+ goto cleanup_watchers;
+ if (target->me)
+ __MOD_INC_USE_COUNT(target->me);
+ up(&ebt_mutex);
+
+ t->u.target = target;
+ if (t->u.target == &ebt_standard_target) {
+ if (e->target_offset + sizeof(struct ebt_standard_target) >
+ e->next_offset) {
+ BUGPRINT("Standard target size too big\n");
+ ret = -EFAULT;
+ goto cleanup_watchers;
+ }
+ if (((struct ebt_standard_target *)t)->verdict <
+ -NUM_STANDARD_TARGETS) {
+ BUGPRINT("Invalid standard target\n");
+ ret = -EFAULT;
+ goto cleanup_watchers;
+ }
+ } else if ((e->target_offset + t->target_size +
+ sizeof(struct ebt_entry_target) > e->next_offset) ||
+ (t->u.target->check &&
+ t->u.target->check(name, hookmask, e, t->data, t->target_size) != 0)){
+ if (t->u.target->me)
+ __MOD_DEC_USE_COUNT(t->u.target->me);
+ ret = -EFAULT;
+ goto cleanup_watchers;
+ }
+ (*cnt)++;
+ return 0;
+cleanup_watchers:
+ EBT_WATCHER_ITERATE(e, ebt_cleanup_watcher, &j);
+cleanup_matches:
+ EBT_MATCH_ITERATE(e, ebt_cleanup_match, &i);
+ return ret;
+}
+
+// checks for loops and sets the hook mask for udc
+// the hook mask for udc tells us from which base chains the udc can be
+// accessed. This mask is a parameter to the check() functions of the extensions
+static int check_chainloops(struct ebt_entries *chain,
+ struct ebt_cl_stack *cl_s, unsigned int udc_cnt,
+ unsigned int hooknr, char *base)
+{
+ int i, chain_nr = -1, pos = 0, nentries = chain->nentries, verdict;
+ struct ebt_entry *e = (struct ebt_entry *)chain->data;
+ struct ebt_entry_target *t;
+
+ while (pos < nentries || chain_nr != -1) {
+ // end of udc, go back one 'recursion' step
+ if (pos == nentries) {
+ // put back values of the time when this chain was called
+ e = cl_s[chain_nr].cs.e;
+ if (cl_s[chain_nr].from != -1)
+ nentries =
+ cl_s[cl_s[chain_nr].from].cs.chaininfo->nentries;
+ else
+ nentries = chain->nentries;
+ pos = cl_s[chain_nr].cs.n;
+ // make sure we won't see a loop that isn't one
+ cl_s[chain_nr].cs.n = 0;
+ chain_nr = cl_s[chain_nr].from;
+ if (pos == nentries)
+ continue;
+ }
+ t = (struct ebt_entry_target *)
+ (((char *)e) + e->target_offset);
+ if (strcmp(t->u.name, EBT_STANDARD_TARGET))
+ goto letscontinue;
+ if (e->target_offset + sizeof(struct ebt_standard_target) >
+ e->next_offset) {
+ BUGPRINT("Standard target size too big\n");
+ return -1;
+ }
+ verdict = ((struct ebt_standard_target *)t)->verdict;
+ if (verdict >= 0) { // jump to another chain
+ struct ebt_entries *hlp2 =
+ (struct ebt_entries *)(base + verdict);
+ for (i = 0; i < udc_cnt; i++)
+ if (hlp2 == cl_s[i].cs.chaininfo)
+ break;
+ // bad destination or loop
+ if (i == udc_cnt) {
+ BUGPRINT("bad destination\n");
+ return -1;
+ }
+ if (cl_s[i].cs.n) {
+ BUGPRINT("loop\n");
+ return -1;
+ }
+ // this can't be 0, so the above test is correct
+ cl_s[i].cs.n = pos + 1;
+ pos = 0;
+ cl_s[i].cs.e = ((void *)e + e->next_offset);
+ e = (struct ebt_entry *)(hlp2->data);
+ nentries = hlp2->nentries;
+ cl_s[i].from = chain_nr;
+ chain_nr = i;
+ // this udc is accessible from the base chain for hooknr
+ cl_s[i].hookmask |= (1 << hooknr);
+ continue;
+ }
+letscontinue:
+ e = (void *)e + e->next_offset;
+ pos++;
+ }
+ return 0;
+}
+
+// do the parsing of the table/chains/entries/matches/watchers/targets, heh
+static int translate_table(struct ebt_replace *repl,
+ struct ebt_table_info *newinfo)
+{
+ unsigned int i, j, k, udc_cnt;
+ int ret;
+ struct ebt_cl_stack *cl_s = NULL; // used in the checking for chain loops
+
+ i = 0;
+ while (i < NF_BR_NUMHOOKS && !(repl->valid_hooks & (1 << i)))
+ i++;
+ if (i == NF_BR_NUMHOOKS) {
+ BUGPRINT("No valid hooks specified\n");
+ return -EINVAL;
+ }
+ if (repl->hook_entry[i] != (struct ebt_entries *)repl->entries) {
+ BUGPRINT("Chains don't start at beginning\n");
+ return -EINVAL;
+ }
+ // make sure chains are ordered after each other in same order
+ // as their corresponding hooks
+ for (j = i + 1; j < NF_BR_NUMHOOKS; j++) {
+ if (!(repl->valid_hooks & (1 << j)))
+ continue;
+ if ( repl->hook_entry[j] <= repl->hook_entry[i] ) {
+ BUGPRINT("Hook order must be followed\n");
+ return -EINVAL;
+ }
+ i = j;
+ }
+
+ for (i = 0; i < NF_BR_NUMHOOKS; i++)
+ newinfo->hook_entry[i] = NULL;
+
+ newinfo->entries_size = repl->entries_size;
+ newinfo->nentries = repl->nentries;
+
+ // do some early checkings and initialize some things
+ i = 0; // holds the expected nr. of entries for the chain
+ j = 0; // holds the up to now counted entries for the chain
+ k = 0; // holds the total nr. of entries, should equal
+ // newinfo->nentries afterwards
+ udc_cnt = 0; // will hold the nr. of user defined chains (udc)
+ ret = EBT_ENTRY_ITERATE(newinfo->entries, newinfo->entries_size,
+ ebt_check_entry_size_and_hooks, newinfo, repl->entries,
+ repl->entries + repl->entries_size, repl->hook_entry, &i, &j, &k,
+ &udc_cnt, repl->valid_hooks);
+
+ if (ret != 0)
+ return ret;
+
+ if (i != j) {
+ BUGPRINT("nentries does not equal the nr of entries in the "
+ "(last) chain\n");
+ return -EINVAL;
+ }
+ if (k != newinfo->nentries) {
+ BUGPRINT("Total nentries is wrong\n");
+ return -EINVAL;
+ }
+
+ // check if all valid hooks have a chain
+ for (i = 0; i < NF_BR_NUMHOOKS; i++) {
+ if (newinfo->hook_entry[i] == NULL &&
+ (repl->valid_hooks & (1 << i))) {
+ BUGPRINT("Valid hook without chain\n");
+ return -EINVAL;
+ }
+ }
+
+ // Get the location of the udc, put them in an array
+ // While we're at it, allocate the chainstack
+ if (udc_cnt) {
+ // this will get free'd in do_replace()/ebt_register_table()
+ // if an error occurs
+ newinfo->chainstack = (struct ebt_chainstack **)
+ vmalloc(smp_num_cpus * sizeof(struct ebt_chainstack));
+ if (!newinfo->chainstack)
+ return -ENOMEM;
+ for (i = 0; i < smp_num_cpus; i++) {
+ newinfo->chainstack[i] =
+ vmalloc(udc_cnt * sizeof(struct ebt_chainstack));
+ if (!newinfo->chainstack[i]) {
+ while (i)
+ vfree(newinfo->chainstack[--i]);
+ vfree(newinfo->chainstack);
+ newinfo->chainstack = NULL;
+ return -ENOMEM;
+ }
+ }
+
+ cl_s = (struct ebt_cl_stack *)
+ vmalloc(udc_cnt * sizeof(struct ebt_cl_stack));
+ if (!cl_s)
+ return -ENOMEM;
+ i = 0; // the i'th udc
+ EBT_ENTRY_ITERATE(newinfo->entries, newinfo->entries_size,
+ ebt_get_udc_positions, newinfo, repl->hook_entry, &i,
+ repl->valid_hooks, cl_s);
+ // sanity check
+ if (i != udc_cnt) {
+ BUGPRINT("i != udc_cnt\n");
+ vfree(cl_s);
+ return -EFAULT;
+ }
+ }
+
+ // Check for loops
+ for (i = 0; i < NF_BR_NUMHOOKS; i++)
+ if (repl->valid_hooks & (1 << i))
+ if (check_chainloops(newinfo->hook_entry[i],
+ cl_s, udc_cnt, i, newinfo->entries)) {
+ if (cl_s)
+ vfree(cl_s);
+ return -EINVAL;
+ }
+
+ // we now know the following (along with E=mc²):
+ // - the nr of entries in each chain is right
+ // - the size of the allocated space is right
+ // - all valid hooks have a corresponding chain
+ // - there are no loops
+ // - wrong data can still be on the level of a single entry
+ // - could be there are jumps to places that are not the
+ // beginning of a chain. This can only occur in chains that
+ // are not accessible from any base chains, so we don't care.
+
+ // used to know what we need to clean up if something goes wrong
+ i = 0;
+ ret = EBT_ENTRY_ITERATE(newinfo->entries, newinfo->entries_size,
+ ebt_check_entry, newinfo, repl->name, &i, repl->valid_hooks,
+ cl_s, udc_cnt);
+ if (ret != 0) {
+ EBT_ENTRY_ITERATE(newinfo->entries, newinfo->entries_size,
+ ebt_cleanup_entry, &i);
+ }
+ if (cl_s)
+ vfree(cl_s);
+ return ret;
+}
+
+// called under write_lock
+static void get_counters(struct ebt_counter *oldcounters,
+ struct ebt_counter *counters, unsigned int nentries)
+{
+ int i, cpu;
+ struct ebt_counter *counter_base;
+
+ // counters of cpu 0
+ memcpy(counters, oldcounters,
+ sizeof(struct ebt_counter) * nentries);
+ // add other counters to those of cpu 0
+ for (cpu = 1; cpu < smp_num_cpus; cpu++) {
+ counter_base = COUNTER_BASE(oldcounters, nentries, cpu);
+ for (i = 0; i < nentries; i++) {
+ counters[i].pcnt += counter_base[i].pcnt;
+ counters[i].bcnt += counter_base[i].bcnt;
+ }
+ }
+}
+
+// replace the table
+static int do_replace(void *user, unsigned int len)
+{
+ int ret, i, countersize;
+ struct ebt_table_info *newinfo;
+ struct ebt_replace tmp;
+ struct ebt_table *t;
+ struct ebt_counter *counterstmp = NULL;
+ // used to be able to unlock earlier
+ struct ebt_table_info *table;
+
+ if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
+ return -EFAULT;
+
+ if (len != sizeof(tmp) + tmp.entries_size) {
+ BUGPRINT("Wrong len argument\n");
+ return -EINVAL;
+ }
+
+ if (tmp.entries_size == 0) {
+ BUGPRINT("Entries_size never zero\n");
+ return -EINVAL;
+ }
+ countersize = COUNTER_OFFSET(tmp.nentries) * smp_num_cpus;
+ newinfo = (struct ebt_table_info *)
+ vmalloc(sizeof(struct ebt_table_info) + countersize);
+ if (!newinfo)
+ return -ENOMEM;
+
+ if (countersize)
+ memset(newinfo->counters, 0, countersize);
+
+ newinfo->entries = (char *)vmalloc(tmp.entries_size);
+ if (!newinfo->entries) {
+ ret = -ENOMEM;
+ goto free_newinfo;
+ }
+ if (copy_from_user(
+ newinfo->entries, tmp.entries, tmp.entries_size) != 0) {
+ BUGPRINT("Couldn't copy entries from userspace\n");
+ ret = -EFAULT;
+ goto free_entries;
+ }
+
+ // the user wants counters back
+ // the check on the size is done later, when we have the lock
+ if (tmp.num_counters) {
+ counterstmp = (struct ebt_counter *)
+ vmalloc(tmp.num_counters * sizeof(struct ebt_counter));
+ if (!counterstmp) {
+ ret = -ENOMEM;
+ goto free_entries;
+ }
+ }
+ else
+ counterstmp = NULL;
+
+ // this can get initialized by translate_table()
+ newinfo->chainstack = NULL;
+ ret = translate_table(&tmp, newinfo);
+
+ if (ret != 0)
+ goto free_counterstmp;
+
+ t = find_table_lock(tmp.name, &ret, &ebt_mutex);
+ if (!t)
+ goto free_iterate;
+
+ // the table doesn't like it
+ if (t->check && (ret = t->check(newinfo, tmp.valid_hooks)))
+ goto free_unlock;
+
+ if (tmp.num_counters && tmp.num_counters != t->private->nentries) {
+ BUGPRINT("Wrong nr. of counters requested\n");
+ ret = -EINVAL;
+ goto free_unlock;
+ }
+
+ // we have the mutex lock, so no danger in reading this pointer
+ table = t->private;
+ // we need an atomic snapshot of the counters
+ write_lock_bh(&t->lock);
+ if (tmp.num_counters)
+ get_counters(t->private->counters, counterstmp,
+ t->private->nentries);
+
+ t->private = newinfo;
+ write_unlock_bh(&t->lock);
+ up(&ebt_mutex);
+ // So, a user can change the chains while having messed up her counter
+ // allocation. Only reason why this is done is because this way the lock
+ // is held only once, while this doesn't bring the kernel into a
+ // dangerous state.
+ if (tmp.num_counters &&
+ copy_to_user(tmp.counters, counterstmp,
+ tmp.num_counters * sizeof(struct ebt_counter))) {
+ BUGPRINT("Couldn't copy counters to userspace\n");
+ ret = -EFAULT;
+ }
+ else
+ ret = 0;
+
+ // decrease module count and free resources
+ EBT_ENTRY_ITERATE(table->entries, table->entries_size,
+ ebt_cleanup_entry, NULL);
+
+ vfree(table->entries);
+ if (table->chainstack) {
+ for (i = 0; i < smp_num_cpus; i++)
+ vfree(table->chainstack[i]);
+ vfree(table->chainstack);
+ }
+ vfree(table);
+
+ if (counterstmp)
+ vfree(counterstmp);
+ return ret;
+
+free_unlock:
+ up(&ebt_mutex);
+free_iterate:
+ EBT_ENTRY_ITERATE(newinfo->entries, newinfo->entries_size,
+ ebt_cleanup_entry, NULL);
+free_counterstmp:
+ if (counterstmp)
+ vfree(counterstmp);
+ // can be initialized in translate_table()
+ if (newinfo->chainstack) {
+ for (i = 0; i < smp_num_cpus; i++)
+ vfree(newinfo->chainstack[i]);
+ vfree(newinfo->chainstack);
+ }
+free_entries:
+ if (newinfo->entries)
+ vfree(newinfo->entries);
+free_newinfo:
+ if (newinfo)
+ vfree(newinfo);
+ return ret;
+}
+
+int ebt_register_target(struct ebt_target *target)
+{
+ int ret;
+
+ ret = down_interruptible(&ebt_mutex);
+ if (ret != 0)
+ return ret;
+ if (!list_named_insert(&ebt_targets, target)) {
+ up(&ebt_mutex);
+ return -EEXIST;
+ }
+ up(&ebt_mutex);
+ MOD_INC_USE_COUNT;
+
+ return 0;
+}
+
+void ebt_unregister_target(struct ebt_target *target)
+{
+ down(&ebt_mutex);
+ LIST_DELETE(&ebt_targets, target);
+ up(&ebt_mutex);
+ MOD_DEC_USE_COUNT;
+}
+
+int ebt_register_match(struct ebt_match *match)
+{
+ int ret;
+
+ ret = down_interruptible(&ebt_mutex);
+ if (ret != 0)
+ return ret;
+ if (!list_named_insert(&ebt_matches, match)) {
+ up(&ebt_mutex);
+ return -EEXIST;
+ }
+ up(&ebt_mutex);
+ MOD_INC_USE_COUNT;
+
+ return 0;
+}
+
+void ebt_unregister_match(struct ebt_match *match)
+{
+ down(&ebt_mutex);
+ LIST_DELETE(&ebt_matches, match);
+ up(&ebt_mutex);
+ MOD_DEC_USE_COUNT;
+}
+
+int ebt_register_watcher(struct ebt_watcher *watcher)
+{
+ int ret;
+
+ ret = down_interruptible(&ebt_mutex);
+ if (ret != 0)
+ return ret;
+ if (!list_named_insert(&ebt_watchers, watcher)) {
+ up(&ebt_mutex);
+ return -EEXIST;
+ }
+ up(&ebt_mutex);
+ MOD_INC_USE_COUNT;
+
+ return 0;
+}
+
+void ebt_unregister_watcher(struct ebt_watcher *watcher)
+{
+ down(&ebt_mutex);
+ LIST_DELETE(&ebt_watchers, watcher);
+ up(&ebt_mutex);
+ MOD_DEC_USE_COUNT;
+}
+
+int ebt_register_table(struct ebt_table *table)
+{
+ struct ebt_table_info *newinfo;
+ int ret, i, countersize;
+
+ if (!table || !table->table ||!table->table->entries ||
+ table->table->entries_size == 0 ||
+ table->table->counters || table->private) {
+ BUGPRINT("Bad table data for ebt_register_table!!!\n");
+ return -EINVAL;
+ }
+
+ countersize = COUNTER_OFFSET(table->table->nentries) * smp_num_cpus;
+ newinfo = (struct ebt_table_info *)
+ vmalloc(sizeof(struct ebt_table_info) + countersize);
+ ret = -ENOMEM;
+ if (!newinfo)
+ return -ENOMEM;
+
+ newinfo->entries = (char *)vmalloc(table->table->entries_size);
+ if (!(newinfo->entries))
+ goto free_newinfo;
+
+ memcpy(newinfo->entries, table->table->entries,
+ table->table->entries_size);
+
+ if (countersize)
+ memset(newinfo->counters, 0, countersize);
+
+ // fill in newinfo and parse the entries
+ newinfo->chainstack = NULL;
+ ret = translate_table(table->table, newinfo);
+ if (ret != 0) {
+ BUGPRINT("Translate_table failed\n");
+ goto free_chainstack;
+ }
+
+ if (table->check && table->check(newinfo, table->valid_hooks)) {
+ BUGPRINT("The table doesn't like its own initial data, lol\n");
+ return -EINVAL;
+ }
+
+ table->private = newinfo;
+ table->lock = RW_LOCK_UNLOCKED;
+ ret = down_interruptible(&ebt_mutex);
+ if (ret != 0)
+ goto free_chainstack;
+
+ if (list_named_find(&ebt_tables, table->name)) {
+ ret = -EEXIST;
+ BUGPRINT("Table name already exists\n");
+ goto free_unlock;
+ }
+
+ list_prepend(&ebt_tables, table);
+ up(&ebt_mutex);
+ MOD_INC_USE_COUNT;
+ return 0;
+free_unlock:
+ up(&ebt_mutex);
+free_chainstack:
+ if (newinfo->chainstack) {
+ for (i = 0; i < smp_num_cpus; i++)
+ vfree(newinfo->chainstack[i]);
+ vfree(newinfo->chainstack);
+ }
+ vfree(newinfo->entries);
+free_newinfo:
+ vfree(newinfo);
+ return ret;
+}
+
+void ebt_unregister_table(struct ebt_table *table)
+{
+ int i;
+
+ if (!table) {
+ BUGPRINT("Request to unregister NULL table!!!\n");
+ return;
+ }
+ down(&ebt_mutex);
+ LIST_DELETE(&ebt_tables, table);
+ up(&ebt_mutex);
+ EBT_ENTRY_ITERATE(table->private->entries,
+ table->private->entries_size, ebt_cleanup_entry, NULL);
+ if (table->private->entries)
+ vfree(table->private->entries);
+ if (table->private->chainstack) {
+ for (i = 0; i < smp_num_cpus; i++)
+ vfree(table->private->chainstack[i]);
+ vfree(table->private->chainstack);
+ }
+ vfree(table->private);
+ MOD_DEC_USE_COUNT;
+}
+
+// userspace just supplied us with counters
+static int update_counters(void *user, unsigned int len)
+{
+ int i, ret;
+ struct ebt_counter *tmp;
+ struct ebt_replace hlp;
+ struct ebt_table *t;
+
+ if (copy_from_user(&hlp, user, sizeof(hlp)))
+ return -EFAULT;
+
+ if (len != sizeof(hlp) + hlp.num_counters * sizeof(struct ebt_counter))
+ return -EINVAL;
+ if (hlp.num_counters == 0)
+ return -EINVAL;
+
+ if ( !(tmp = (struct ebt_counter *)
+ vmalloc(hlp.num_counters * sizeof(struct ebt_counter))) ){
+ MEMPRINT("Update_counters && nomemory\n");
+ return -ENOMEM;
+ }
+
+ t = find_table_lock(hlp.name, &ret, &ebt_mutex);
+ if (!t)
+ goto free_tmp;
+
+ if (hlp.num_counters != t->private->nentries) {
+ BUGPRINT("Wrong nr of counters\n");
+ ret = -EINVAL;
+ goto unlock_mutex;
+ }
+
+ if ( copy_from_user(tmp, hlp.counters,
+ hlp.num_counters * sizeof(struct ebt_counter)) ) {
+ BUGPRINT("Updata_counters && !cfu\n");
+ ret = -EFAULT;
+ goto unlock_mutex;
+ }
+
+ // we want an atomic add of the counters
+ write_lock_bh(&t->lock);
+
+ // we add to the counters of the first cpu
+ for (i = 0; i < hlp.num_counters; i++) {
+ t->private->counters[i].pcnt += tmp[i].pcnt;
+ t->private->counters[i].bcnt += tmp[i].bcnt;
+ }
+
+ write_unlock_bh(&t->lock);
+ ret = 0;
+unlock_mutex:
+ up(&ebt_mutex);
+free_tmp:
+ vfree(tmp);
+ return ret;
+}
+
+static inline int ebt_make_matchname(struct ebt_entry_match *m,
+ char *base, char *ubase)
+{
+ char *hlp = ubase - base + (char *)m;
+ if (copy_to_user(hlp, m->u.match->name, EBT_FUNCTION_MAXNAMELEN))
+ return -EFAULT;
+ return 0;
+}
+
+static inline int ebt_make_watchername(struct ebt_entry_watcher *w,
+ char *base, char *ubase)
+{
+ char *hlp = ubase - base + (char *)w;
+ if (copy_to_user(hlp , w->u.watcher->name, EBT_FUNCTION_MAXNAMELEN))
+ return -EFAULT;
+ return 0;
+}
+
+static inline int ebt_make_names(struct ebt_entry *e, char *base, char *ubase)
+{
+ int ret;
+ char *hlp;
+ struct ebt_entry_target *t;
+
+ if ((e->bitmask & EBT_ENTRY_OR_ENTRIES) == 0)
+ return 0;
+
+ hlp = ubase - base + (char *)e + e->target_offset;
+ t = (struct ebt_entry_target *)(((char *)e) + e->target_offset);
+
+ ret = EBT_MATCH_ITERATE(e, ebt_make_matchname, base, ubase);
+ if (ret != 0)
+ return ret;
+ ret = EBT_WATCHER_ITERATE(e, ebt_make_watchername, base, ubase);
+ if (ret != 0)
+ return ret;
+ if (copy_to_user(hlp, t->u.target->name, EBT_FUNCTION_MAXNAMELEN))
+ return -EFAULT;
+ return 0;
+}
+
+// called with ebt_mutex down
+static int copy_everything_to_user(struct ebt_table *t, void *user,
+ int *len, int cmd)
+{
+ struct ebt_replace tmp;
+ struct ebt_counter *counterstmp, *oldcounters;
+ unsigned int entries_size, nentries;
+ char *entries;
+
+ if (cmd == EBT_SO_GET_ENTRIES) {
+ entries_size = t->private->entries_size;
+ nentries = t->private->nentries;
+ entries = t->private->entries;
+ oldcounters = t->private->counters;
+ } else {
+ entries_size = t->table->entries_size;
+ nentries = t->table->nentries;
+ entries = t->table->entries;
+ oldcounters = t->table->counters;
+ }
+
+ if (copy_from_user(&tmp, user, sizeof(tmp))) {
+ BUGPRINT("Cfu didn't work\n");
+ return -EFAULT;
+ }
+
+ if (*len != sizeof(struct ebt_replace) + entries_size +
+ (tmp.num_counters? nentries * sizeof(struct ebt_counter): 0)) {
+ BUGPRINT("Wrong size\n");
+ return -EINVAL;
+ }
+
+ if (tmp.nentries != nentries) {
+ BUGPRINT("Nentries wrong\n");
+ return -EINVAL;
+ }
+
+ if (tmp.entries_size != entries_size) {
+ BUGPRINT("Wrong size\n");
+ return -EINVAL;
+ }
+
+ // userspace might not need the counters
+ if (tmp.num_counters) {
+ if (tmp.num_counters != nentries) {
+ BUGPRINT("Num_counters wrong\n");
+ return -EINVAL;
+ }
+ counterstmp = (struct ebt_counter *)
+ vmalloc(nentries * sizeof(struct ebt_counter));
+ if (!counterstmp) {
+ MEMPRINT("Couldn't copy counters, out of memory\n");
+ return -ENOMEM;
+ }
+ write_lock_bh(&t->lock);
+ get_counters(oldcounters, counterstmp, nentries);
+ write_unlock_bh(&t->lock);
+
+ if (copy_to_user(tmp.counters, counterstmp,
+ nentries * sizeof(struct ebt_counter))) {
+ BUGPRINT("Couldn't copy counters to userspace\n");
+ vfree(counterstmp);
+ return -EFAULT;
+ }
+ vfree(counterstmp);
+ }
+
+ if (copy_to_user(tmp.entries, entries, entries_size)) {
+ BUGPRINT("Couldn't copy entries to userspace\n");
+ return -EFAULT;
+ }
+ // set the match/watcher/target names right
+ return EBT_ENTRY_ITERATE(entries, entries_size,
+ ebt_make_names, entries, tmp.entries);
+}
+
+static int do_ebt_set_ctl(struct sock *sk,
+ int cmd, void *user, unsigned int len)
+{
+ int ret;
+
+ switch(cmd) {
+ case EBT_SO_SET_ENTRIES:
+ ret = do_replace(user, len);
+ break;
+ case EBT_SO_SET_COUNTERS:
+ ret = update_counters(user, len);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static int do_ebt_get_ctl(struct sock *sk, int cmd, void *user, int *len)
+{
+ int ret;
+ struct ebt_replace tmp;
+ struct ebt_table *t;
+
+ if (copy_from_user(&tmp, user, sizeof(tmp)))
+ return -EFAULT;
+
+ t = find_table_lock(tmp.name, &ret, &ebt_mutex);
+ if (!t)
+ return ret;
+
+ switch(cmd) {
+ case EBT_SO_GET_INFO:
+ case EBT_SO_GET_INIT_INFO:
+ if (*len != sizeof(struct ebt_replace)){
+ ret = -EINVAL;
+ up(&ebt_mutex);
+ break;
+ }
+ if (cmd == EBT_SO_GET_INFO) {
+ tmp.nentries = t->private->nentries;
+ tmp.entries_size = t->private->entries_size;
+ tmp.valid_hooks = t->valid_hooks;
+ } else {
+ tmp.nentries = t->table->nentries;
+ tmp.entries_size = t->table->entries_size;
+ tmp.valid_hooks = t->table->valid_hooks;
+ }
+ up(&ebt_mutex);
+ if (copy_to_user(user, &tmp, *len) != 0){
+ BUGPRINT("c2u Didn't work\n");
+ ret = -EFAULT;
+ break;
+ }
+ ret = 0;
+ break;
+
+ case EBT_SO_GET_ENTRIES:
+ case EBT_SO_GET_INIT_ENTRIES:
+ ret = copy_everything_to_user(t, user, len, cmd);
+ up(&ebt_mutex);
+ break;
+
+ default:
+ up(&ebt_mutex);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static struct nf_sockopt_ops ebt_sockopts =
+{ { NULL, NULL }, PF_INET, EBT_BASE_CTL, EBT_SO_SET_MAX + 1, do_ebt_set_ctl,
+ EBT_BASE_CTL, EBT_SO_GET_MAX + 1, do_ebt_get_ctl, 0, NULL
+};
+
+static int __init init(void)
+{
+ int ret;
+
+ down(&ebt_mutex);
+ list_named_insert(&ebt_targets, &ebt_standard_target);
+ up(&ebt_mutex);
+ if ((ret = nf_register_sockopt(&ebt_sockopts)) < 0)
+ return ret;
+
+ printk(KERN_NOTICE "Ebtables v2.0 registered\n");
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ nf_unregister_sockopt(&ebt_sockopts);
+ printk(KERN_NOTICE "Ebtables v2.0 unregistered\n");
+}
+
+EXPORT_SYMBOL(ebt_register_table);
+EXPORT_SYMBOL(ebt_unregister_table);
+EXPORT_SYMBOL(ebt_register_match);
+EXPORT_SYMBOL(ebt_unregister_match);
+EXPORT_SYMBOL(ebt_register_watcher);
+EXPORT_SYMBOL(ebt_unregister_watcher);
+EXPORT_SYMBOL(ebt_register_target);
+EXPORT_SYMBOL(ebt_unregister_target);
+EXPORT_SYMBOL(ebt_do_table);
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/core/Makefile b/uClinux-2.4.31-uc0/net/core/Makefile
new file mode 100644
index 0000000..26a2ed9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/Makefile
@@ -0,0 +1,35 @@
+#
+# Makefile for the Linux networking core.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := core.o
+
+export-objs := netfilter.o profile.o ethtool.o neighbour.o
+
+obj-y := sock.o skbuff.o iovec.o datagram.o scm.o
+
+ifeq ($(CONFIG_SYSCTL),y)
+ifeq ($(CONFIG_NET),y)
+obj-y += sysctl_net_core.o
+endif
+endif
+
+obj-$(CONFIG_FILTER) += filter.o
+
+obj-$(CONFIG_NET) += dev.o ethtool.o dev_mcast.o dst.o neighbour.o \
+ rtnetlink.o utils.o
+
+obj-$(CONFIG_NETFILTER) += netfilter.o
+obj-$(CONFIG_NET_DIVERT) += dv.o
+obj-$(CONFIG_NET_PROFILE) += profile.o
+obj-$(CONFIG_NET_PKTGEN) += pktgen.o
+obj-$(CONFIG_NET_RADIO) += wireless.o
+# Ugly. I wish all wireless drivers were moved in drivers/net/wireless
+obj-$(CONFIG_NET_PCMCIA_RADIO) += wireless.o
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/core/datagram.c b/uClinux-2.4.31-uc0/net/core/datagram.c
new file mode 100644
index 0000000..709166f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/datagram.c
@@ -0,0 +1,448 @@
+/*
+ * SUCS NET3:
+ *
+ * Generic datagram handling routines. These are generic for all protocols. Possibly a generic IP version on top
+ * of these would make sense. Not tonight however 8-).
+ * This is used because UDP, RAW, PACKET, DDP, IPX, AX.25 and NetROM layer all have identical poll code and mostly
+ * identical recvmsg() code. So we share it here. The poll was shared before but buried in udp.c so I moved it.
+ *
+ * Authors: Alan Cox <alan@redhat.com>. (datagram_poll() from old udp.c code)
+ *
+ * Fixes:
+ * Alan Cox : NULL return from skb_peek_copy() understood
+ * Alan Cox : Rewrote skb_read_datagram to avoid the skb_peek_copy stuff.
+ * Alan Cox : Added support for SOCK_SEQPACKET. IPX can no longer use the SO_TYPE hack but
+ * AX.25 now works right, and SPX is feasible.
+ * Alan Cox : Fixed write poll of non IP protocol crash.
+ * Florian La Roche: Changed for my new skbuff handling.
+ * Darryl Miles : Fixed non-blocking SOCK_SEQPACKET.
+ * Linus Torvalds : BSD semantic fixes.
+ * Alan Cox : Datagram iovec handling
+ * Darryl Miles : Fixed non-blocking SOCK_STREAM.
+ * Alan Cox : POSIXisms
+ * Pete Wyckoff : Unconnected accept() fix.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/poll.h>
+#include <linux/highmem.h>
+
+#include <net/protocol.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/checksum.h>
+
+
+/*
+ * Is a socket 'connection oriented' ?
+ */
+
+static inline int connection_based(struct sock *sk)
+{
+ return (sk->type==SOCK_SEQPACKET || sk->type==SOCK_STREAM);
+}
+
+
+/*
+ * Wait for a packet..
+ */
+
+static int wait_for_packet(struct sock * sk, int *err, long *timeo_p)
+{
+ int error;
+
+ DECLARE_WAITQUEUE(wait, current);
+
+ __set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue_exclusive(sk->sleep, &wait);
+
+ /* Socket errors? */
+ error = sock_error(sk);
+ if (error)
+ goto out_err;
+
+ if (!skb_queue_empty(&sk->receive_queue))
+ goto ready;
+
+ /* Socket shut down? */
+ if (sk->shutdown & RCV_SHUTDOWN)
+ goto out_noerr;
+
+ /* Sequenced packets can come disconnected. If so we report the problem */
+ error = -ENOTCONN;
+ if(connection_based(sk) && !(sk->state==TCP_ESTABLISHED || sk->state==TCP_LISTEN))
+ goto out_err;
+
+ /* handle signals */
+ if (signal_pending(current))
+ goto interrupted;
+
+ *timeo_p = schedule_timeout(*timeo_p);
+
+ready:
+ current->state = TASK_RUNNING;
+ remove_wait_queue(sk->sleep, &wait);
+ return 0;
+
+interrupted:
+ error = sock_intr_errno(*timeo_p);
+out_err:
+ *err = error;
+out:
+ current->state = TASK_RUNNING;
+ remove_wait_queue(sk->sleep, &wait);
+ return error;
+out_noerr:
+ *err = 0;
+ error = 1;
+ goto out;
+}
+
+/*
+ * Get a datagram skbuff, understands the peeking, nonblocking wakeups and possible
+ * races. This replaces identical code in packet,raw and udp, as well as the IPX
+ * AX.25 and Appletalk. It also finally fixes the long standing peek and read
+ * race for datagram sockets. If you alter this routine remember it must be
+ * re-entrant.
+ *
+ * This function will lock the socket if a skb is returned, so the caller
+ * needs to unlock the socket in that case (usually by calling skb_free_datagram)
+ *
+ * * It does not lock socket since today. This function is
+ * * free of race conditions. This measure should/can improve
+ * * significantly datagram socket latencies at high loads,
+ * * when data copying to user space takes lots of time.
+ * * (BTW I've just killed the last cli() in IP/IPv6/core/netlink/packet
+ * * 8) Great win.)
+ * * --ANK (980729)
+ *
+ * The order of the tests when we find no data waiting are specified
+ * quite explicitly by POSIX 1003.1g, don't change them without having
+ * the standard around please.
+ */
+
+struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags, int noblock, int *err)
+{
+ int error;
+ struct sk_buff *skb;
+ long timeo;
+
+ /* Caller is allowed not to check sk->err before skb_recv_datagram() */
+ error = sock_error(sk);
+ if (error)
+ goto no_packet;
+
+ timeo = sock_rcvtimeo(sk, noblock);
+
+ do {
+ /* Again only user level code calls this function, so nothing interrupt level
+ will suddenly eat the receive_queue.
+
+ Look at current nfs client by the way...
+ However, this function was corrent in any case. 8)
+ */
+ if (flags & MSG_PEEK)
+ {
+ unsigned long cpu_flags;
+
+ spin_lock_irqsave(&sk->receive_queue.lock, cpu_flags);
+ skb = skb_peek(&sk->receive_queue);
+ if(skb!=NULL)
+ atomic_inc(&skb->users);
+ spin_unlock_irqrestore(&sk->receive_queue.lock, cpu_flags);
+ } else
+ skb = skb_dequeue(&sk->receive_queue);
+
+ if (skb)
+ return skb;
+
+ /* User doesn't want to wait */
+ error = -EAGAIN;
+ if (!timeo)
+ goto no_packet;
+
+ } while (wait_for_packet(sk, err, &timeo) == 0);
+
+ return NULL;
+
+no_packet:
+ *err = error;
+ return NULL;
+}
+
+void skb_free_datagram(struct sock * sk, struct sk_buff *skb)
+{
+ kfree_skb(skb);
+}
+
+/*
+ * Copy a datagram to a linear buffer.
+ */
+
+int skb_copy_datagram(const struct sk_buff *skb, int offset, char *to, int size)
+{
+ struct iovec iov = { to, size };
+
+ return skb_copy_datagram_iovec(skb, offset, &iov, size);
+}
+
+/*
+ * Copy a datagram to an iovec.
+ * Note: the iovec is modified during the copy.
+ */
+int skb_copy_datagram_iovec(const struct sk_buff *skb, int offset, struct iovec *to,
+ int len)
+{
+ int i, copy;
+ int start = skb->len - skb->data_len;
+
+ /* Copy header. */
+ if ((copy = start-offset) > 0) {
+ if (copy > len)
+ copy = len;
+ if (memcpy_toiovec(to, skb->data + offset, copy))
+ goto fault;
+ if ((len -= copy) == 0)
+ return 0;
+ offset += copy;
+ }
+
+ /* Copy paged appendix. Hmm... why does this look so complicated? */
+ for (i=0; i<skb_shinfo(skb)->nr_frags; i++) {
+ int end;
+
+ BUG_TRAP(start <= offset+len);
+
+ end = start + skb_shinfo(skb)->frags[i].size;
+ if ((copy = end-offset) > 0) {
+ int err;
+ u8 *vaddr;
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+ struct page *page = frag->page;
+
+ if (copy > len)
+ copy = len;
+ vaddr = kmap(page);
+ err = memcpy_toiovec(to, vaddr + frag->page_offset +
+ offset-start, copy);
+ kunmap(page);
+ if (err)
+ goto fault;
+ if (!(len -= copy))
+ return 0;
+ offset += copy;
+ }
+ start = end;
+ }
+
+ if (skb_shinfo(skb)->frag_list) {
+ struct sk_buff *list;
+
+ for (list = skb_shinfo(skb)->frag_list; list; list=list->next) {
+ int end;
+
+ BUG_TRAP(start <= offset+len);
+
+ end = start + list->len;
+ if ((copy = end-offset) > 0) {
+ if (copy > len)
+ copy = len;
+ if (skb_copy_datagram_iovec(list, offset-start, to, copy))
+ goto fault;
+ if ((len -= copy) == 0)
+ return 0;
+ offset += copy;
+ }
+ start = end;
+ }
+ }
+ if (len == 0)
+ return 0;
+
+fault:
+ return -EFAULT;
+}
+
+int skb_copy_and_csum_datagram(const struct sk_buff *skb, int offset, u8 *to, int len, unsigned int *csump)
+{
+ int i, copy;
+ int start = skb->len - skb->data_len;
+ int pos = 0;
+
+ /* Copy header. */
+ if ((copy = start-offset) > 0) {
+ int err = 0;
+ if (copy > len)
+ copy = len;
+ *csump = csum_and_copy_to_user(skb->data+offset, to, copy, *csump, &err);
+ if (err)
+ goto fault;
+ if ((len -= copy) == 0)
+ return 0;
+ offset += copy;
+ to += copy;
+ pos = copy;
+ }
+
+ for (i=0; i<skb_shinfo(skb)->nr_frags; i++) {
+ int end;
+
+ BUG_TRAP(start <= offset+len);
+
+ end = start + skb_shinfo(skb)->frags[i].size;
+ if ((copy = end-offset) > 0) {
+ unsigned int csum2;
+ int err = 0;
+ u8 *vaddr;
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+ struct page *page = frag->page;
+
+ if (copy > len)
+ copy = len;
+ vaddr = kmap(page);
+ csum2 = csum_and_copy_to_user(vaddr + frag->page_offset +
+ offset-start, to, copy, 0, &err);
+ kunmap(page);
+ if (err)
+ goto fault;
+ *csump = csum_block_add(*csump, csum2, pos);
+ if (!(len -= copy))
+ return 0;
+ offset += copy;
+ to += copy;
+ pos += copy;
+ }
+ start = end;
+ }
+
+ if (skb_shinfo(skb)->frag_list) {
+ struct sk_buff *list;
+
+ for (list = skb_shinfo(skb)->frag_list; list; list=list->next) {
+ int end;
+
+ BUG_TRAP(start <= offset+len);
+
+ end = start + list->len;
+ if ((copy = end-offset) > 0) {
+ unsigned int csum2 = 0;
+ if (copy > len)
+ copy = len;
+ if (skb_copy_and_csum_datagram(list, offset-start, to, copy, &csum2))
+ goto fault;
+ *csump = csum_block_add(*csump, csum2, pos);
+ if ((len -= copy) == 0)
+ return 0;
+ offset += copy;
+ to += copy;
+ pos += copy;
+ }
+ start = end;
+ }
+ }
+ if (len == 0)
+ return 0;
+
+fault:
+ return -EFAULT;
+}
+
+/* Copy and checkum skb to user iovec. Caller _must_ check that
+ skb will fit to this iovec.
+
+ Returns: 0 - success.
+ -EINVAL - checksum failure.
+ -EFAULT - fault during copy. Beware, in this case iovec can be
+ modified!
+ */
+
+int skb_copy_and_csum_datagram_iovec(const struct sk_buff *skb, int hlen, struct iovec *iov)
+{
+ unsigned int csum;
+ int chunk = skb->len - hlen;
+
+ /* Skip filled elements. Pretty silly, look at memcpy_toiovec, though 8) */
+ while (iov->iov_len == 0)
+ iov++;
+
+ if (iov->iov_len < chunk) {
+ if ((unsigned short)csum_fold(skb_checksum(skb, 0, chunk+hlen, skb->csum)))
+ goto csum_error;
+ if (skb_copy_datagram_iovec(skb, hlen, iov, chunk))
+ goto fault;
+ } else {
+ csum = csum_partial(skb->data, hlen, skb->csum);
+ if (skb_copy_and_csum_datagram(skb, hlen, iov->iov_base, chunk, &csum))
+ goto fault;
+ if ((unsigned short)csum_fold(csum))
+ goto csum_error;
+ iov->iov_len -= chunk;
+ iov->iov_base += chunk;
+ }
+ return 0;
+
+csum_error:
+ return -EINVAL;
+
+fault:
+ return -EFAULT;
+}
+
+
+
+/*
+ * Datagram poll: Again totally generic. This also handles
+ * sequenced packet sockets providing the socket receive queue
+ * is only ever holding data ready to receive.
+ *
+ * Note: when you _don't_ use this routine for this protocol,
+ * and you use a different write policy from sock_writeable()
+ * then please supply your own write_space callback.
+ */
+
+unsigned int datagram_poll(struct file * file, struct socket *sock, poll_table *wait)
+{
+ struct sock *sk = sock->sk;
+ unsigned int mask;
+
+ poll_wait(file, sk->sleep, wait);
+ mask = 0;
+
+ /* exceptional events? */
+ if (sk->err || !skb_queue_empty(&sk->error_queue))
+ mask |= POLLERR;
+ if (sk->shutdown == SHUTDOWN_MASK)
+ mask |= POLLHUP;
+
+ /* readable? */
+ if (!skb_queue_empty(&sk->receive_queue) || (sk->shutdown&RCV_SHUTDOWN))
+ mask |= POLLIN | POLLRDNORM;
+
+ /* Connection-based need to check for termination and startup */
+ if (connection_based(sk)) {
+ if (sk->state==TCP_CLOSE)
+ mask |= POLLHUP;
+ /* connection hasn't started yet? */
+ if (sk->state == TCP_SYN_SENT)
+ return mask;
+ }
+
+ /* writable? */
+ if (sock_writeable(sk))
+ mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
+ else
+ set_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
+
+ return mask;
+}
diff --git a/uClinux-2.4.31-uc0/net/core/dev.c b/uClinux-2.4.31-uc0/net/core/dev.c
new file mode 100644
index 0000000..9862d93
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/dev.c
@@ -0,0 +1,2942 @@
+/* $USAGI: dev.c,v 1.24 2003/11/12 05:12:00 yoshfuji Exp $ */
+
+/*
+ * NET3 Protocol independent device support routines.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Derived from the non IP parts of dev.c 1.0.19
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Mark Evans, <evansmp@uhura.aston.ac.uk>
+ *
+ * Additional Authors:
+ * Florian la Roche <rzsfl@rz.uni-sb.de>
+ * Alan Cox <gw4pts@gw4pts.ampr.org>
+ * David Hinds <dahinds@users.sourceforge.net>
+ * Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
+ * Adam Sulmicki <adam@cfar.umd.edu>
+ * Pekka Riikonen <priikone@poesidon.pspt.fi>
+ *
+ * Changes:
+ * D.J. Barrow : Fixed bug where dev->refcnt gets set to 2
+ * if register_netdev gets called before
+ * net_dev_init & also removed a few lines
+ * of code in the process.
+ * Alan Cox : device private ioctl copies fields back.
+ * Alan Cox : Transmit queue code does relevant stunts to
+ * keep the queue safe.
+ * Alan Cox : Fixed double lock.
+ * Alan Cox : Fixed promisc NULL pointer trap
+ * ???????? : Support the full private ioctl range
+ * Alan Cox : Moved ioctl permission check into drivers
+ * Tim Kordas : SIOCADDMULTI/SIOCDELMULTI
+ * Alan Cox : 100 backlog just doesn't cut it when
+ * you start doing multicast video 8)
+ * Alan Cox : Rewrote net_bh and list manager.
+ * Alan Cox : Fix ETH_P_ALL echoback lengths.
+ * Alan Cox : Took out transmit every packet pass
+ * Saved a few bytes in the ioctl handler
+ * Alan Cox : Network driver sets packet type before calling netif_rx. Saves
+ * a function call a packet.
+ * Alan Cox : Hashed net_bh()
+ * Richard Kooijman: Timestamp fixes.
+ * Alan Cox : Wrong field in SIOCGIFDSTADDR
+ * Alan Cox : Device lock protection.
+ * Alan Cox : Fixed nasty side effect of device close changes.
+ * Rudi Cilibrasi : Pass the right thing to set_mac_address()
+ * Dave Miller : 32bit quantity for the device lock to make it work out
+ * on a Sparc.
+ * Bjorn Ekwall : Added KERNELD hack.
+ * Alan Cox : Cleaned up the backlog initialise.
+ * Craig Metz : SIOCGIFCONF fix if space for under
+ * 1 device.
+ * Thomas Bogendoerfer : Return ENODEV for dev_open, if there
+ * is no device open function.
+ * Andi Kleen : Fix error reporting for SIOCGIFCONF
+ * Michael Chastain : Fix signed/unsigned for SIOCGIFCONF
+ * Cyrus Durgin : Cleaned for KMOD
+ * Adam Sulmicki : Bug Fix : Network Device Unload
+ * A network device unload needs to purge
+ * the backlog queue.
+ * Paul Rusty Russell : SIOCSIFNAME
+ * Pekka Riikonen : Netdev boot-time settings code
+ * Andrew Morton : Make unregister_netdevice wait indefinitely on dev->refcnt
+ * J Hadi Salim : - Backlog queue sampling
+ * - netif_rx() feedback
+ */
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <linux/skbuff.h>
+#include <linux/brlock.h>
+#include <net/sock.h>
+#include <linux/rtnetlink.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/if_bridge.h>
+#include <linux/divert.h>
+#include <net/dst.h>
+#include <net/pkt_sched.h>
+#include <net/profile.h>
+#include <net/checksum.h>
+#include <linux/highmem.h>
+#include <linux/init.h>
+#include <linux/kmod.h>
+#include <linux/module.h>
+#if defined(CONFIG_NET_RADIO) || defined(CONFIG_NET_PCMCIA_RADIO)
+#include <linux/wireless.h> /* Note : will define WIRELESS_EXT */
+#include <net/iw_handler.h>
+#endif /* CONFIG_NET_RADIO || CONFIG_NET_PCMCIA_RADIO */
+#ifdef CONFIG_PLIP
+extern int plip_init(void);
+#endif
+
+
+/* This define, if set, will randomly drop a packet when congestion
+ * is more than moderate. It helps fairness in the multi-interface
+ * case when one of them is a hog, but it kills performance for the
+ * single interface case so it is off now by default.
+ */
+#undef RAND_LIE
+
+/* Setting this will sample the queue lengths and thus congestion
+ * via a timer instead of as each packet is received.
+ */
+#undef OFFLINE_SAMPLE
+
+NET_PROFILE_DEFINE(dev_queue_xmit)
+NET_PROFILE_DEFINE(softnet_process)
+
+const char *if_port_text[] = {
+ "unknown",
+ "BNC",
+ "10baseT",
+ "AUI",
+ "100baseT",
+ "100baseTX",
+ "100baseFX"
+};
+
+/*
+ * The list of packet types we will receive (as opposed to discard)
+ * and the routines to invoke.
+ *
+ * Why 16. Because with 16 the only overlap we get on a hash of the
+ * low nibble of the protocol value is RARP/SNAP/X.25.
+ *
+ * NOTE: That is no longer true with the addition of VLAN tags. Not
+ * sure which should go first, but I bet it won't make much
+ * difference if we are running VLANs. The good news is that
+ * this protocol won't be in the list unless compiled in, so
+ * the average user (w/out VLANs) will not be adversly affected.
+ * --BLG
+ *
+ * 0800 IP
+ * 8100 802.1Q VLAN
+ * 0001 802.3
+ * 0002 AX.25
+ * 0004 802.2
+ * 8035 RARP
+ * 0005 SNAP
+ * 0805 X.25
+ * 0806 ARP
+ * 8137 IPX
+ * 0009 Localtalk
+ * 86DD IPv6
+ */
+
+static struct packet_type *ptype_base[16]; /* 16 way hashed list */
+static struct packet_type *ptype_all = NULL; /* Taps */
+
+#ifdef OFFLINE_SAMPLE
+static void sample_queue(unsigned long dummy);
+static struct timer_list samp_timer = { function: sample_queue };
+#endif
+
+#ifdef CONFIG_HOTPLUG
+static int net_run_sbin_hotplug(struct net_device *dev, char *action);
+#else
+#define net_run_sbin_hotplug(dev, action) ({ 0; })
+#endif
+
+/*
+ * Our notifier list
+ */
+
+static struct notifier_block *netdev_chain=NULL;
+
+/*
+ * Device drivers call our routines to queue packets here. We empty the
+ * queue in the local softnet handler.
+ */
+struct softnet_data softnet_data[NR_CPUS] __cacheline_aligned;
+
+#ifdef CONFIG_NET_FASTROUTE
+int netdev_fastroute;
+int netdev_fastroute_obstacles;
+#endif
+
+
+/******************************************************************************************
+
+ Protocol management and registration routines
+
+*******************************************************************************************/
+
+/*
+ * For efficiency
+ */
+
+int netdev_nit=0;
+
+/*
+ * Add a protocol ID to the list. Now that the input handler is
+ * smarter we can dispense with all the messy stuff that used to be
+ * here.
+ *
+ * BEWARE!!! Protocol handlers, mangling input packets,
+ * MUST BE last in hash buckets and checking protocol handlers
+ * MUST start from promiscous ptype_all chain in net_bh.
+ * It is true now, do not change it.
+ * Explantion follows: if protocol handler, mangling packet, will
+ * be the first on list, it is not able to sense, that packet
+ * is cloned and should be copied-on-write, so that it will
+ * change it and subsequent readers will get broken packet.
+ * --ANK (980803)
+ */
+
+/**
+ * dev_add_pack - add packet handler
+ * @pt: packet type declaration
+ *
+ * Add a protocol handler to the networking stack. The passed &packet_type
+ * is linked into kernel lists and may not be freed until it has been
+ * removed from the kernel lists.
+ */
+
+void dev_add_pack(struct packet_type *pt)
+{
+ int hash;
+
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+
+#ifdef CONFIG_NET_FASTROUTE
+ /* Hack to detect packet socket */
+ if ((pt->data) && ((int)(pt->data)!=1)) {
+ netdev_fastroute_obstacles++;
+ dev_clear_fastroute(pt->dev);
+ }
+#endif
+ if (pt->type == htons(ETH_P_ALL)) {
+ netdev_nit++;
+ pt->next=ptype_all;
+ ptype_all=pt;
+ } else {
+ hash=ntohs(pt->type)&15;
+ pt->next = ptype_base[hash];
+ ptype_base[hash] = pt;
+ }
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+}
+
+
+/**
+ * dev_remove_pack - remove packet handler
+ * @pt: packet type declaration
+ *
+ * Remove a protocol handler that was previously added to the kernel
+ * protocol handlers by dev_add_pack(). The passed &packet_type is removed
+ * from the kernel lists and can be freed or reused once this function
+ * returns.
+ */
+
+void dev_remove_pack(struct packet_type *pt)
+{
+ struct packet_type **pt1;
+
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+
+ if (pt->type == htons(ETH_P_ALL)) {
+ netdev_nit--;
+ pt1=&ptype_all;
+ } else {
+ pt1=&ptype_base[ntohs(pt->type)&15];
+ }
+
+ for (; (*pt1) != NULL; pt1 = &((*pt1)->next)) {
+ if (pt == (*pt1)) {
+ *pt1 = pt->next;
+#ifdef CONFIG_NET_FASTROUTE
+ if (pt->data)
+ netdev_fastroute_obstacles--;
+#endif
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ return;
+ }
+ }
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ printk(KERN_WARNING "dev_remove_pack: %p not found.\n", pt);
+}
+
+/******************************************************************************
+
+ Device Boot-time Settings Routines
+
+*******************************************************************************/
+
+/* Boot time configuration table */
+static struct netdev_boot_setup dev_boot_setup[NETDEV_BOOT_SETUP_MAX];
+
+/**
+ * netdev_boot_setup_add - add new setup entry
+ * @name: name of the device
+ * @map: configured settings for the device
+ *
+ * Adds new setup entry to the dev_boot_setup list. The function
+ * returns 0 on error and 1 on success. This is a generic routine to
+ * all netdevices.
+ */
+int netdev_boot_setup_add(char *name, struct ifmap *map)
+{
+ struct netdev_boot_setup *s;
+ int i;
+
+ s = dev_boot_setup;
+ for (i = 0; i < NETDEV_BOOT_SETUP_MAX; i++) {
+ if (s[i].name[0] == '\0' || s[i].name[0] == ' ') {
+ memset(s[i].name, 0, sizeof(s[i].name));
+ strcpy(s[i].name, name);
+ memcpy(&s[i].map, map, sizeof(s[i].map));
+ break;
+ }
+ }
+
+ if (i >= NETDEV_BOOT_SETUP_MAX)
+ return 0;
+
+ return 1;
+}
+
+/**
+ * netdev_boot_setup_check - check boot time settings
+ * @dev: the netdevice
+ *
+ * Check boot time settings for the device.
+ * The found settings are set for the device to be used
+ * later in the device probing.
+ * Returns 0 if no settings found, 1 if they are.
+ */
+int netdev_boot_setup_check(struct net_device *dev)
+{
+ struct netdev_boot_setup *s;
+ int i;
+
+ s = dev_boot_setup;
+ for (i = 0; i < NETDEV_BOOT_SETUP_MAX; i++) {
+ if (s[i].name[0] != '\0' && s[i].name[0] != ' ' &&
+ !strncmp(dev->name, s[i].name, strlen(s[i].name))) {
+ dev->irq = s[i].map.irq;
+ dev->base_addr = s[i].map.base_addr;
+ dev->mem_start = s[i].map.mem_start;
+ dev->mem_end = s[i].map.mem_end;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+ * Saves at boot time configured settings for any netdevice.
+ */
+int __init netdev_boot_setup(char *str)
+{
+ int ints[5];
+ struct ifmap map;
+
+ str = get_options(str, ARRAY_SIZE(ints), ints);
+ if (!str || !*str)
+ return 0;
+
+ /* Save settings */
+ memset(&map, 0, sizeof(map));
+ if (ints[0] > 0)
+ map.irq = ints[1];
+ if (ints[0] > 1)
+ map.base_addr = ints[2];
+ if (ints[0] > 2)
+ map.mem_start = ints[3];
+ if (ints[0] > 3)
+ map.mem_end = ints[4];
+
+ /* Add new entry to the list */
+ return netdev_boot_setup_add(str, &map);
+}
+
+__setup("netdev=", netdev_boot_setup);
+
+/*****************************************************************************************
+
+ Device Interface Subroutines
+
+******************************************************************************************/
+
+/**
+ * __dev_get_by_name - find a device by its name
+ * @name: name to find
+ *
+ * Find an interface by name. Must be called under RTNL semaphore
+ * or @dev_base_lock. If the name is found a pointer to the device
+ * is returned. If the name is not found then %NULL is returned. The
+ * reference counters are not incremented so the caller must be
+ * careful with locks.
+ */
+
+
+struct net_device *__dev_get_by_name(const char *name)
+{
+ struct net_device *dev;
+
+ for (dev = dev_base; dev != NULL; dev = dev->next) {
+ if (strncmp(dev->name, name, IFNAMSIZ) == 0)
+ return dev;
+ }
+ return NULL;
+}
+
+/**
+ * dev_get_by_name - find a device by its name
+ * @name: name to find
+ *
+ * Find an interface by name. This can be called from any
+ * context and does its own locking. The returned handle has
+ * the usage count incremented and the caller must use dev_put() to
+ * release it when it is no longer needed. %NULL is returned if no
+ * matching device is found.
+ */
+
+struct net_device *dev_get_by_name(const char *name)
+{
+ struct net_device *dev;
+
+ read_lock(&dev_base_lock);
+ dev = __dev_get_by_name(name);
+ if (dev)
+ dev_hold(dev);
+ read_unlock(&dev_base_lock);
+ return dev;
+}
+
+/*
+ Return value is changed to int to prevent illegal usage in future.
+ It is still legal to use to check for device existence.
+
+ User should understand, that the result returned by this function
+ is meaningless, if it was not issued under rtnl semaphore.
+ */
+
+/**
+ * dev_get - test if a device exists
+ * @name: name to test for
+ *
+ * Test if a name exists. Returns true if the name is found. In order
+ * to be sure the name is not allocated or removed during the test the
+ * caller must hold the rtnl semaphore.
+ *
+ * This function primarily exists for back compatibility with older
+ * drivers.
+ */
+
+int dev_get(const char *name)
+{
+ struct net_device *dev;
+
+ read_lock(&dev_base_lock);
+ dev = __dev_get_by_name(name);
+ read_unlock(&dev_base_lock);
+ return dev != NULL;
+}
+
+/**
+ * __dev_get_by_index - find a device by its ifindex
+ * @ifindex: index of device
+ *
+ * Search for an interface by index. Returns %NULL if the device
+ * is not found or a pointer to the device. The device has not
+ * had its reference counter increased so the caller must be careful
+ * about locking. The caller must hold either the RTNL semaphore
+ * or @dev_base_lock.
+ */
+
+struct net_device * __dev_get_by_index(int ifindex)
+{
+ struct net_device *dev;
+
+ for (dev = dev_base; dev != NULL; dev = dev->next) {
+ if (dev->ifindex == ifindex)
+ return dev;
+ }
+ return NULL;
+}
+
+
+/**
+ * dev_get_by_index - find a device by its ifindex
+ * @ifindex: index of device
+ *
+ * Search for an interface by index. Returns NULL if the device
+ * is not found or a pointer to the device. The device returned has
+ * had a reference added and the pointer is safe until the user calls
+ * dev_put to indicate they have finished with it.
+ */
+
+struct net_device * dev_get_by_index(int ifindex)
+{
+ struct net_device *dev;
+
+ read_lock(&dev_base_lock);
+ dev = __dev_get_by_index(ifindex);
+ if (dev)
+ dev_hold(dev);
+ read_unlock(&dev_base_lock);
+ return dev;
+}
+
+/**
+ * dev_getbyhwaddr - find a device by its hardware address
+ * @type: media type of device
+ * @ha: hardware address
+ *
+ * Search for an interface by MAC address. Returns NULL if the device
+ * is not found or a pointer to the device. The caller must hold the
+ * rtnl semaphore. The returned device has not had its ref count increased
+ * and the caller must therefore be careful about locking
+ *
+ * BUGS:
+ * If the API was consistent this would be __dev_get_by_hwaddr
+ */
+
+struct net_device *dev_getbyhwaddr(unsigned short type, char *ha)
+{
+ struct net_device *dev;
+
+ ASSERT_RTNL();
+
+ for (dev = dev_base; dev != NULL; dev = dev->next) {
+ if (dev->type == type &&
+ memcmp(dev->dev_addr, ha, dev->addr_len) == 0)
+ return dev;
+ }
+ return NULL;
+}
+
+/**
+ * dev_get_by_flags - find any device with given flags
+ * @if_flags: IFF_* values
+ * @mask: bitmask of bits in if_flags to check
+ *
+ * Search for any interface with the given flags. Returns NULL if a device
+ * is not found or a pointer to the device. The device returned has
+ * had a reference added and the pointer is safe until the user calls
+ * dev_put to indicate they have finished with it.
+ */
+
+struct net_device * dev_get_by_flags(unsigned short if_flags, unsigned short mask)
+{
+ struct net_device *dev;
+
+ read_lock(&dev_base_lock);
+ dev = __dev_get_by_flags(if_flags, mask);
+ if (dev)
+ dev_hold(dev);
+ read_unlock(&dev_base_lock);
+ return dev;
+}
+
+/**
+ * __dev_get_by_flags - find any device with given flags
+ * @if_flags: IFF_* values
+ * @mask: bitmask of bits in if_flags to check
+ *
+ * Search for any interface with the given flags. Returns NULL if a device
+ * is not found or a pointer to the device. The caller must hold either
+ * the RTNL semaphore or @dev_base_lock.
+ */
+
+struct net_device *__dev_get_by_flags(unsigned short if_flags, unsigned short mask)
+{
+ struct net_device *dev;
+
+ for (dev = dev_base; dev != NULL; dev = dev->next) {
+ if (((dev->flags ^ if_flags) & mask) == 0)
+ return dev;
+ }
+ return NULL;
+}
+
+/**
+ * dev_alloc_name - allocate a name for a device
+ * @dev: device
+ * @name: name format string
+ *
+ * Passed a format string - eg "lt%d" it will try and find a suitable
+ * id. Not efficient for many devices, not called a lot. The caller
+ * must hold the dev_base or rtnl lock while allocating the name and
+ * adding the device in order to avoid duplicates. Returns the number
+ * of the unit assigned or a negative errno code.
+ */
+
+int dev_alloc_name(struct net_device *dev, const char *name)
+{
+ int i;
+ char buf[32];
+ char *p;
+
+ /*
+ * Verify the string as this thing may have come from
+ * the user. There must be either one "%d" and no other "%"
+ * characters, or no "%" characters at all.
+ */
+ p = strchr(name, '%');
+ if (p && (p[1] != 'd' || strchr(p+2, '%')))
+ return -EINVAL;
+
+ /*
+ * If you need over 100 please also fix the algorithm...
+ */
+ for (i = 0; i < 100; i++) {
+ snprintf(buf,sizeof(buf),name,i);
+ if (__dev_get_by_name(buf) == NULL) {
+ strcpy(dev->name, buf);
+ return i;
+ }
+ }
+ return -ENFILE; /* Over 100 of the things .. bail out! */
+}
+
+/**
+ * dev_alloc - allocate a network device and name
+ * @name: name format string
+ * @err: error return pointer
+ *
+ * Passed a format string, eg. "lt%d", it will allocate a network device
+ * and space for the name. %NULL is returned if no memory is available.
+ * If the allocation succeeds then the name is assigned and the
+ * device pointer returned. %NULL is returned if the name allocation
+ * failed. The cause of an error is returned as a negative errno code
+ * in the variable @err points to.
+ *
+ * The caller must hold the @dev_base or RTNL locks when doing this in
+ * order to avoid duplicate name allocations.
+ */
+
+struct net_device *dev_alloc(const char *name, int *err)
+{
+ struct net_device *dev=kmalloc(sizeof(struct net_device), GFP_KERNEL);
+ if (dev == NULL) {
+ *err = -ENOBUFS;
+ return NULL;
+ }
+ memset(dev, 0, sizeof(struct net_device));
+ *err = dev_alloc_name(dev, name);
+ if (*err < 0) {
+ kfree(dev);
+ return NULL;
+ }
+ return dev;
+}
+
+/**
+ * netdev_state_change - device changes state
+ * @dev: device to cause notification
+ *
+ * Called to indicate a device has changed state. This function calls
+ * the notifier chains for netdev_chain and sends a NEWLINK message
+ * to the routing socket.
+ */
+
+void netdev_state_change(struct net_device *dev)
+{
+ if (dev->flags&IFF_UP) {
+ notifier_call_chain(&netdev_chain, NETDEV_CHANGE, dev);
+ rtmsg_ifinfo(RTM_NEWLINK, dev, 0);
+ }
+}
+
+
+#ifdef CONFIG_KMOD
+
+/**
+ * dev_load - load a network module
+ * @name: name of interface
+ *
+ * If a network interface is not present and the process has suitable
+ * privileges this function loads the module. If module loading is not
+ * available in this kernel then it becomes a nop.
+ */
+
+void dev_load(const char *name)
+{
+ if (!dev_get(name) && capable(CAP_SYS_MODULE))
+ request_module(name);
+}
+
+#else
+
+extern inline void dev_load(const char *unused){;}
+
+#endif
+
+static int default_rebuild_header(struct sk_buff *skb)
+{
+ printk(KERN_DEBUG "%s: default_rebuild_header called -- BUG!\n", skb->dev ? skb->dev->name : "NULL!!!");
+ kfree_skb(skb);
+ return 1;
+}
+
+/**
+ * dev_open - prepare an interface for use.
+ * @dev: device to open
+ *
+ * Takes a device from down to up state. The device's private open
+ * function is invoked and then the multicast lists are loaded. Finally
+ * the device is moved into the up state and a %NETDEV_UP message is
+ * sent to the netdev notifier chain.
+ *
+ * Calling this function on an active interface is a nop. On a failure
+ * a negative errno code is returned.
+ */
+
+int dev_open(struct net_device *dev)
+{
+ int ret = 0;
+
+ /*
+ * Is it already up?
+ */
+
+ if (dev->flags&IFF_UP)
+ return 0;
+
+ /*
+ * Is it even present?
+ */
+ if (!netif_device_present(dev))
+ return -ENODEV;
+
+ /*
+ * Call device private open method
+ */
+ if (try_inc_mod_count(dev->owner)) {
+ set_bit(__LINK_STATE_START, &dev->state);
+ if (dev->open) {
+ ret = dev->open(dev);
+ if (ret != 0) {
+ clear_bit(__LINK_STATE_START, &dev->state);
+ if (dev->owner)
+ __MOD_DEC_USE_COUNT(dev->owner);
+ }
+ }
+ } else {
+ ret = -ENODEV;
+ }
+
+ /*
+ * If it went open OK then:
+ */
+
+ if (ret == 0)
+ {
+ /*
+ * Set the flags.
+ */
+ dev->flags |= IFF_UP;
+
+ /*
+ * Initialize multicasting status
+ */
+ dev_mc_upload(dev);
+
+ /*
+ * Wakeup transmit queue engine
+ */
+ dev_activate(dev);
+
+ /*
+ * ... and announce new interface.
+ */
+ notifier_call_chain(&netdev_chain, NETDEV_UP, dev);
+ }
+ return(ret);
+}
+
+#ifdef CONFIG_NET_FASTROUTE
+
+static void dev_do_clear_fastroute(struct net_device *dev)
+{
+ if (dev->accept_fastpath) {
+ int i;
+
+ for (i=0; i<=NETDEV_FASTROUTE_HMASK; i++) {
+ struct dst_entry *dst;
+
+ write_lock_irq(&dev->fastpath_lock);
+ dst = dev->fastpath[i];
+ dev->fastpath[i] = NULL;
+ write_unlock_irq(&dev->fastpath_lock);
+
+ dst_release(dst);
+ }
+ }
+}
+
+void dev_clear_fastroute(struct net_device *dev)
+{
+ if (dev) {
+ dev_do_clear_fastroute(dev);
+ } else {
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev; dev = dev->next)
+ dev_do_clear_fastroute(dev);
+ read_unlock(&dev_base_lock);
+ }
+}
+#endif
+
+/**
+ * dev_close - shutdown an interface.
+ * @dev: device to shutdown
+ *
+ * This function moves an active device into down state. A
+ * %NETDEV_GOING_DOWN is sent to the netdev notifier chain. The device
+ * is then deactivated and finally a %NETDEV_DOWN is sent to the notifier
+ * chain.
+ */
+
+int dev_close(struct net_device *dev)
+{
+ if (!(dev->flags&IFF_UP))
+ return 0;
+
+ /*
+ * Tell people we are going down, so that they can
+ * prepare to death, when device is still operating.
+ */
+ notifier_call_chain(&netdev_chain, NETDEV_GOING_DOWN, dev);
+
+ dev_deactivate(dev);
+
+ clear_bit(__LINK_STATE_START, &dev->state);
+
+ /* Synchronize to scheduled poll. We cannot touch poll list,
+ * it can be even on different cpu. So just clear netif_running(),
+ * and wait when poll really will happen. Actually, the best place
+ * for this is inside dev->stop() after device stopped its irq
+ * engine, but this requires more changes in devices. */
+
+ smp_mb__after_clear_bit(); /* Commit netif_running(). */
+ while (test_bit(__LINK_STATE_RX_SCHED, &dev->state)) {
+ /* No hurry. */
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(1);
+ }
+
+ /*
+ * Call the device specific close. This cannot fail.
+ * Only if device is UP
+ *
+ * We allow it to be called even after a DETACH hot-plug
+ * event.
+ */
+
+ if (dev->stop)
+ dev->stop(dev);
+
+ /*
+ * Device is now down.
+ */
+
+ dev->flags &= ~IFF_UP;
+#ifdef CONFIG_NET_FASTROUTE
+ dev_clear_fastroute(dev);
+#endif
+
+ /*
+ * Tell people we are down
+ */
+ notifier_call_chain(&netdev_chain, NETDEV_DOWN, dev);
+
+ /*
+ * Drop the module refcount
+ */
+ if (dev->owner)
+ __MOD_DEC_USE_COUNT(dev->owner);
+
+ return(0);
+}
+
+
+/*
+ * Device change register/unregister. These are not inline or static
+ * as we export them to the world.
+ */
+
+/**
+ * register_netdevice_notifier - register a network notifier block
+ * @nb: notifier
+ *
+ * Register a notifier to be called when network device events occur.
+ * The notifier passed is linked into the kernel structures and must
+ * not be reused until it has been unregistered. A negative errno code
+ * is returned on a failure.
+ */
+
+int register_netdevice_notifier(struct notifier_block *nb)
+{
+ return notifier_chain_register(&netdev_chain, nb);
+}
+
+/**
+ * unregister_netdevice_notifier - unregister a network notifier block
+ * @nb: notifier
+ *
+ * Unregister a notifier previously registered by
+ * register_netdevice_notifier(). The notifier is unlinked into the
+ * kernel structures and may then be reused. A negative errno code
+ * is returned on a failure.
+ */
+
+int unregister_netdevice_notifier(struct notifier_block *nb)
+{
+ return notifier_chain_unregister(&netdev_chain,nb);
+}
+
+/*
+ * Support routine. Sends outgoing frames to any network
+ * taps currently in use.
+ */
+
+void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct packet_type *ptype;
+ do_gettimeofday(&skb->stamp);
+
+ br_read_lock(BR_NETPROTO_LOCK);
+ for (ptype = ptype_all; ptype!=NULL; ptype = ptype->next)
+ {
+ /* Never send packets back to the socket
+ * they originated from - MvS (miquels@drinkel.ow.org)
+ */
+ if ((ptype->dev == dev || !ptype->dev) &&
+ ((struct sock *)ptype->data != skb->sk))
+ {
+ struct sk_buff *skb2;
+ if ((skb2 = skb_clone(skb, GFP_ATOMIC)) == NULL)
+ break;
+
+ /* skb->nh should be correctly
+ set by sender, so that the second statement is
+ just protection against buggy protocols.
+ */
+ skb2->mac.raw = skb2->data;
+
+ if (skb2->nh.raw < skb2->data || skb2->nh.raw > skb2->tail) {
+ if (net_ratelimit())
+ printk(KERN_CRIT "protocol %04x is buggy, dev %s\n", skb2->protocol, dev->name);
+ skb2->nh.raw = skb2->data;
+ }
+
+ skb2->h.raw = skb2->nh.raw;
+ skb2->pkt_type = PACKET_OUTGOING;
+ ptype->func(skb2, skb->dev, ptype);
+ }
+ }
+ br_read_unlock(BR_NETPROTO_LOCK);
+}
+
+/* Calculate csum in the case, when packet is misrouted.
+ * If it failed by some reason, ignore and send skb with wrong
+ * checksum.
+ */
+struct sk_buff * skb_checksum_help(struct sk_buff *skb)
+{
+ int offset;
+ unsigned int csum;
+
+ offset = skb->h.raw - skb->data;
+ if (offset > (int)skb->len)
+ BUG();
+ csum = skb_checksum(skb, offset, skb->len-offset, 0);
+
+ offset = skb->tail - skb->h.raw;
+ if (offset <= 0)
+ BUG();
+ if (skb->csum+2 > offset)
+ BUG();
+
+ *(u16*)(skb->h.raw + skb->csum) = csum_fold(csum);
+ skb->ip_summed = CHECKSUM_NONE;
+ return skb;
+}
+
+#ifdef CONFIG_HIGHMEM
+/* Actually, we should eliminate this check as soon as we know, that:
+ * 1. IOMMU is present and allows to map all the memory.
+ * 2. No high memory really exists on this machine.
+ */
+
+static inline int
+illegal_highdma(struct net_device *dev, struct sk_buff *skb)
+{
+ int i;
+
+ if (dev->features&NETIF_F_HIGHDMA)
+ return 0;
+
+ for (i=0; i<skb_shinfo(skb)->nr_frags; i++)
+ if (skb_shinfo(skb)->frags[i].page >= highmem_start_page)
+ return 1;
+
+ return 0;
+}
+#else
+#define illegal_highdma(dev, skb) (0)
+#endif
+
+/**
+ * dev_queue_xmit - transmit a buffer
+ * @skb: buffer to transmit
+ *
+ * Queue a buffer for transmission to a network device. The caller must
+ * have set the device and priority and built the buffer before calling this
+ * function. The function can be called from an interrupt.
+ *
+ * A negative errno code is returned on a failure. A success does not
+ * guarantee the frame will be transmitted as it may be dropped due
+ * to congestion or traffic shaping.
+ */
+
+int dev_queue_xmit(struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+ struct Qdisc *q;
+
+ if (skb_shinfo(skb)->frag_list &&
+ !(dev->features&NETIF_F_FRAGLIST) &&
+ skb_linearize(skb, GFP_ATOMIC) != 0) {
+ kfree_skb(skb);
+ return -ENOMEM;
+ }
+
+ /* Fragmented skb is linearized if device does not support SG,
+ * or if at least one of fragments is in highmem and device
+ * does not support DMA from it.
+ */
+ if (skb_shinfo(skb)->nr_frags &&
+ (!(dev->features&NETIF_F_SG) || illegal_highdma(dev, skb)) &&
+ skb_linearize(skb, GFP_ATOMIC) != 0) {
+ kfree_skb(skb);
+ return -ENOMEM;
+ }
+
+ /* If packet is not checksummed and device does not support
+ * checksumming for this protocol, complete checksumming here.
+ */
+ if (skb->ip_summed == CHECKSUM_HW &&
+ (!(dev->features&(NETIF_F_HW_CSUM|NETIF_F_NO_CSUM)) &&
+ (!(dev->features&NETIF_F_IP_CSUM) ||
+ skb->protocol != htons(ETH_P_IP)))) {
+ if ((skb = skb_checksum_help(skb)) == NULL)
+ return -ENOMEM;
+ }
+
+ /* Grab device queue */
+ spin_lock_bh(&dev->queue_lock);
+ q = dev->qdisc;
+ if (q->enqueue) {
+ int ret = q->enqueue(skb, q);
+
+ qdisc_run(dev);
+
+ spin_unlock_bh(&dev->queue_lock);
+ return ret == NET_XMIT_BYPASS ? NET_XMIT_SUCCESS : ret;
+ }
+
+ /* The device has no queue. Common case for software devices:
+ loopback, all the sorts of tunnels...
+
+ Really, it is unlikely that xmit_lock protection is necessary here.
+ (f.e. loopback and IP tunnels are clean ignoring statistics counters.)
+ However, it is possible, that they rely on protection
+ made by us here.
+
+ Check this and shot the lock. It is not prone from deadlocks.
+ Either shot noqueue qdisc, it is even simpler 8)
+ */
+ if (dev->flags&IFF_UP) {
+ int cpu = smp_processor_id();
+
+ if (dev->xmit_lock_owner != cpu) {
+ spin_unlock(&dev->queue_lock);
+ spin_lock(&dev->xmit_lock);
+ dev->xmit_lock_owner = cpu;
+
+ if (!netif_queue_stopped(dev)) {
+ if (netdev_nit)
+ dev_queue_xmit_nit(skb,dev);
+
+ if (dev->hard_start_xmit(skb, dev) == 0) {
+ dev->xmit_lock_owner = -1;
+ spin_unlock_bh(&dev->xmit_lock);
+ return 0;
+ }
+ }
+ dev->xmit_lock_owner = -1;
+ spin_unlock_bh(&dev->xmit_lock);
+ if (net_ratelimit())
+ printk(KERN_CRIT "Virtual device %s asks to queue packet!\n", dev->name);
+ kfree_skb(skb);
+ return -ENETDOWN;
+ } else {
+ /* Recursion is detected! It is possible, unfortunately */
+ if (net_ratelimit())
+ printk(KERN_CRIT "Dead loop on virtual device %s, fix it urgently!\n", dev->name);
+ }
+ }
+ spin_unlock_bh(&dev->queue_lock);
+
+ kfree_skb(skb);
+ return -ENETDOWN;
+}
+
+
+/*=======================================================================
+ Receiver routines
+ =======================================================================*/
+
+int netdev_max_backlog = 300;
+int weight_p = 64; /* old backlog weight */
+/* These numbers are selected based on intuition and some
+ * experimentatiom, if you have more scientific way of doing this
+ * please go ahead and fix things.
+ */
+int no_cong_thresh = 10;
+int no_cong = 20;
+int lo_cong = 100;
+int mod_cong = 290;
+
+struct netif_rx_stats netdev_rx_stat[NR_CPUS];
+
+
+#ifdef CONFIG_NET_HW_FLOWCONTROL
+atomic_t netdev_dropping = ATOMIC_INIT(0);
+static unsigned long netdev_fc_mask = 1;
+unsigned long netdev_fc_xoff = 0;
+spinlock_t netdev_fc_lock = SPIN_LOCK_UNLOCKED;
+
+static struct
+{
+ void (*stimul)(struct net_device *);
+ struct net_device *dev;
+} netdev_fc_slots[BITS_PER_LONG];
+
+int netdev_register_fc(struct net_device *dev, void (*stimul)(struct net_device *dev))
+{
+ int bit = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&netdev_fc_lock, flags);
+ if (netdev_fc_mask != ~0UL) {
+ bit = ffz(netdev_fc_mask);
+ netdev_fc_slots[bit].stimul = stimul;
+ netdev_fc_slots[bit].dev = dev;
+ set_bit(bit, &netdev_fc_mask);
+ clear_bit(bit, &netdev_fc_xoff);
+ }
+ spin_unlock_irqrestore(&netdev_fc_lock, flags);
+ return bit;
+}
+
+void netdev_unregister_fc(int bit)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&netdev_fc_lock, flags);
+ if (bit > 0) {
+ netdev_fc_slots[bit].stimul = NULL;
+ netdev_fc_slots[bit].dev = NULL;
+ clear_bit(bit, &netdev_fc_mask);
+ clear_bit(bit, &netdev_fc_xoff);
+ }
+ spin_unlock_irqrestore(&netdev_fc_lock, flags);
+}
+
+static void netdev_wakeup(void)
+{
+ unsigned long xoff;
+
+ spin_lock(&netdev_fc_lock);
+ xoff = netdev_fc_xoff;
+ netdev_fc_xoff = 0;
+ while (xoff) {
+ int i = ffz(~xoff);
+ xoff &= ~(1<<i);
+ netdev_fc_slots[i].stimul(netdev_fc_slots[i].dev);
+ }
+ spin_unlock(&netdev_fc_lock);
+}
+#endif
+
+static void get_sample_stats(int cpu)
+{
+#ifdef RAND_LIE
+ unsigned long rd;
+ int rq;
+#endif
+ int blog = softnet_data[cpu].input_pkt_queue.qlen;
+ int avg_blog = softnet_data[cpu].avg_blog;
+
+ avg_blog = (avg_blog >> 1)+ (blog >> 1);
+
+ if (avg_blog > mod_cong) {
+ /* Above moderate congestion levels. */
+ softnet_data[cpu].cng_level = NET_RX_CN_HIGH;
+#ifdef RAND_LIE
+ rd = net_random();
+ rq = rd % netdev_max_backlog;
+ if (rq < avg_blog) /* unlucky bastard */
+ softnet_data[cpu].cng_level = NET_RX_DROP;
+#endif
+ } else if (avg_blog > lo_cong) {
+ softnet_data[cpu].cng_level = NET_RX_CN_MOD;
+#ifdef RAND_LIE
+ rd = net_random();
+ rq = rd % netdev_max_backlog;
+ if (rq < avg_blog) /* unlucky bastard */
+ softnet_data[cpu].cng_level = NET_RX_CN_HIGH;
+#endif
+ } else if (avg_blog > no_cong)
+ softnet_data[cpu].cng_level = NET_RX_CN_LOW;
+ else /* no congestion */
+ softnet_data[cpu].cng_level = NET_RX_SUCCESS;
+
+ softnet_data[cpu].avg_blog = avg_blog;
+}
+
+#ifdef OFFLINE_SAMPLE
+static void sample_queue(unsigned long dummy)
+{
+/* 10 ms 0r 1ms -- i dont care -- JHS */
+ int next_tick = 1;
+ int cpu = smp_processor_id();
+
+ get_sample_stats(cpu);
+ next_tick += jiffies;
+ mod_timer(&samp_timer, next_tick);
+}
+#endif
+
+
+/**
+ * netif_rx - post buffer to the network code
+ * @skb: buffer to post
+ *
+ * This function receives a packet from a device driver and queues it for
+ * the upper (protocol) levels to process. It always succeeds. The buffer
+ * may be dropped during processing for congestion control or by the
+ * protocol layers.
+ *
+ * return values:
+ * NET_RX_SUCCESS (no congestion)
+ * NET_RX_CN_LOW (low congestion)
+ * NET_RX_CN_MOD (moderate congestion)
+ * NET_RX_CN_HIGH (high congestion)
+ * NET_RX_DROP (packet was dropped)
+ *
+ *
+ */
+
+int netif_rx(struct sk_buff *skb)
+{
+ int this_cpu = smp_processor_id();
+ struct softnet_data *queue;
+ unsigned long flags;
+
+ if (skb->stamp.tv_sec == 0)
+ do_gettimeofday(&skb->stamp);
+
+ /* The code is rearranged so that the path is the most
+ short when CPU is congested, but is still operating.
+ */
+ queue = &softnet_data[this_cpu];
+
+ local_irq_save(flags);
+
+ netdev_rx_stat[this_cpu].total++;
+ if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
+ if (queue->input_pkt_queue.qlen) {
+ if (queue->throttle)
+ goto drop;
+
+enqueue:
+ dev_hold(skb->dev);
+ __skb_queue_tail(&queue->input_pkt_queue,skb);
+ local_irq_restore(flags);
+#ifndef OFFLINE_SAMPLE
+ get_sample_stats(this_cpu);
+#endif
+ return queue->cng_level;
+ }
+
+ if (queue->throttle) {
+ queue->throttle = 0;
+#ifdef CONFIG_NET_HW_FLOWCONTROL
+ if (atomic_dec_and_test(&netdev_dropping))
+ netdev_wakeup();
+#endif
+ }
+
+ netif_rx_schedule(&queue->blog_dev);
+ goto enqueue;
+ }
+
+ if (queue->throttle == 0) {
+ queue->throttle = 1;
+ netdev_rx_stat[this_cpu].throttled++;
+#ifdef CONFIG_NET_HW_FLOWCONTROL
+ atomic_inc(&netdev_dropping);
+#endif
+ }
+
+drop:
+ netdev_rx_stat[this_cpu].dropped++;
+ local_irq_restore(flags);
+
+ kfree_skb(skb);
+ return NET_RX_DROP;
+}
+
+/* Deliver skb to an old protocol, which is not threaded well
+ or which do not understand shared skbs.
+ */
+static int deliver_to_old_ones(struct packet_type *pt, struct sk_buff *skb, int last)
+{
+ static spinlock_t net_bh_lock = SPIN_LOCK_UNLOCKED;
+ int ret = NET_RX_DROP;
+
+
+ if (!last) {
+ skb = skb_clone(skb, GFP_ATOMIC);
+ if (skb == NULL)
+ return ret;
+ }
+ if (skb_is_nonlinear(skb) && skb_linearize(skb, GFP_ATOMIC) != 0) {
+ kfree_skb(skb);
+ return ret;
+ }
+
+ /* The assumption (correct one) is that old protocols
+ did not depened on BHs different of NET_BH and TIMER_BH.
+ */
+
+ /* Emulate NET_BH with special spinlock */
+ spin_lock(&net_bh_lock);
+
+ /* Disable timers and wait for all timers completion */
+ tasklet_disable(bh_task_vec+TIMER_BH);
+
+ ret = pt->func(skb, skb->dev, pt);
+
+ tasklet_hi_enable(bh_task_vec+TIMER_BH);
+ spin_unlock(&net_bh_lock);
+ return ret;
+}
+
+static __inline__ void skb_bond(struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+
+ if (dev->master) {
+ skb->real_dev = skb->dev;
+ skb->dev = dev->master;
+ }
+}
+
+static void net_tx_action(struct softirq_action *h)
+{
+ int cpu = smp_processor_id();
+
+ if (softnet_data[cpu].completion_queue) {
+ struct sk_buff *clist;
+
+ local_irq_disable();
+ clist = softnet_data[cpu].completion_queue;
+ softnet_data[cpu].completion_queue = NULL;
+ local_irq_enable();
+
+ while (clist != NULL) {
+ struct sk_buff *skb = clist;
+ clist = clist->next;
+
+ BUG_TRAP(atomic_read(&skb->users) == 0);
+ __kfree_skb(skb);
+ }
+ }
+
+ if (softnet_data[cpu].output_queue) {
+ struct net_device *head;
+
+ local_irq_disable();
+ head = softnet_data[cpu].output_queue;
+ softnet_data[cpu].output_queue = NULL;
+ local_irq_enable();
+
+ while (head != NULL) {
+ struct net_device *dev = head;
+ head = head->next_sched;
+
+ smp_mb__before_clear_bit();
+ clear_bit(__LINK_STATE_SCHED, &dev->state);
+
+ if (spin_trylock(&dev->queue_lock)) {
+ qdisc_run(dev);
+ spin_unlock(&dev->queue_lock);
+ } else {
+ netif_schedule(dev);
+ }
+ }
+ }
+}
+
+
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+int (*br_handle_frame_hook)(struct sk_buff *skb) = NULL;
+#endif
+
+static __inline__ int handle_bridge(struct sk_buff *skb,
+ struct packet_type *pt_prev)
+{
+ int ret = NET_RX_DROP;
+
+ if (pt_prev) {
+ if (!pt_prev->data)
+ ret = deliver_to_old_ones(pt_prev, skb, 0);
+ else {
+ atomic_inc(&skb->users);
+ ret = pt_prev->func(skb, skb->dev, pt_prev);
+ }
+ }
+
+ return ret;
+}
+
+
+#ifdef CONFIG_NET_DIVERT
+static inline int handle_diverter(struct sk_buff *skb)
+{
+ /* if diversion is supported on device, then divert */
+ if (skb->dev->divert && skb->dev->divert->divert)
+ divert_frame(skb);
+ return 0;
+}
+#endif /* CONFIG_NET_DIVERT */
+
+int netif_receive_skb(struct sk_buff *skb)
+{
+ struct packet_type *ptype, *pt_prev;
+ int ret = NET_RX_DROP;
+ unsigned short type;
+
+ if (skb->stamp.tv_sec == 0)
+ do_gettimeofday(&skb->stamp);
+
+ skb_bond(skb);
+
+ netdev_rx_stat[smp_processor_id()].total++;
+
+#ifdef CONFIG_NET_FASTROUTE
+ if (skb->pkt_type == PACKET_FASTROUTE) {
+ netdev_rx_stat[smp_processor_id()].fastroute_deferred_out++;
+ return dev_queue_xmit(skb);
+ }
+#endif
+
+ skb->h.raw = skb->nh.raw = skb->data;
+
+ pt_prev = NULL;
+ for (ptype = ptype_all; ptype; ptype = ptype->next) {
+ if (!ptype->dev || ptype->dev == skb->dev) {
+ if (pt_prev) {
+ if (!pt_prev->data) {
+ ret = deliver_to_old_ones(pt_prev, skb, 0);
+ } else {
+ atomic_inc(&skb->users);
+ ret = pt_prev->func(skb, skb->dev, pt_prev);
+ }
+ }
+ pt_prev = ptype;
+ }
+ }
+
+#ifdef CONFIG_NET_DIVERT
+ if (skb->dev->divert && skb->dev->divert->divert)
+ ret = handle_diverter(skb);
+#endif /* CONFIG_NET_DIVERT */
+
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ if (skb->dev->br_port != NULL && br_handle_frame_hook != NULL &&
+ skb->pkt_type != PACKET_LOOPBACK) {
+ int ret;
+
+ ret = handle_bridge(skb, pt_prev);
+ if (br_handle_frame_hook(skb) == 0)
+ return ret;
+ pt_prev = NULL;
+ }
+#endif
+
+ type = skb->protocol;
+ for (ptype=ptype_base[ntohs(type)&15];ptype;ptype=ptype->next) {
+ if (ptype->type == type &&
+ (!ptype->dev || ptype->dev == skb->dev)) {
+ if (pt_prev) {
+ if (!pt_prev->data) {
+ ret = deliver_to_old_ones(pt_prev, skb, 0);
+ } else {
+ atomic_inc(&skb->users);
+ ret = pt_prev->func(skb, skb->dev, pt_prev);
+ }
+ }
+ pt_prev = ptype;
+ }
+ }
+
+ if (pt_prev) {
+ if (!pt_prev->data) {
+ ret = deliver_to_old_ones(pt_prev, skb, 1);
+ } else {
+ ret = pt_prev->func(skb, skb->dev, pt_prev);
+ }
+ } else {
+ kfree_skb(skb);
+ /* Jamal, now you will not able to escape explaining
+ * me how you were going to use this. :-)
+ */
+ ret = NET_RX_DROP;
+ }
+
+ return ret;
+}
+
+static int process_backlog(struct net_device *backlog_dev, int *budget)
+{
+ int work = 0;
+ int quota = min(backlog_dev->quota, *budget);
+ int this_cpu = smp_processor_id();
+ struct softnet_data *queue = &softnet_data[this_cpu];
+ unsigned long start_time = jiffies;
+
+ for (;;) {
+ struct sk_buff *skb;
+ struct net_device *dev;
+
+ local_irq_disable();
+ skb = __skb_dequeue(&queue->input_pkt_queue);
+ if (skb == NULL)
+ goto job_done;
+ local_irq_enable();
+
+ dev = skb->dev;
+
+ netif_receive_skb(skb);
+
+ dev_put(dev);
+
+ work++;
+
+ if (work >= quota || jiffies - start_time > 1)
+ break;
+
+#ifdef CONFIG_NET_HW_FLOWCONTROL
+ if (queue->throttle && queue->input_pkt_queue.qlen < no_cong_thresh ) {
+ queue->throttle = 0;
+ if (atomic_dec_and_test(&netdev_dropping)) {
+ netdev_wakeup();
+ break;
+ }
+ }
+#endif
+ }
+
+ backlog_dev->quota -= work;
+ *budget -= work;
+ return -1;
+
+job_done:
+ backlog_dev->quota -= work;
+ *budget -= work;
+
+ list_del(&backlog_dev->poll_list);
+ smp_mb__before_clear_bit();
+ netif_poll_enable(backlog_dev);
+
+ if (queue->throttle) {
+ queue->throttle = 0;
+#ifdef CONFIG_NET_HW_FLOWCONTROL
+ if (atomic_dec_and_test(&netdev_dropping))
+ netdev_wakeup();
+#endif
+ }
+ local_irq_enable();
+ return 0;
+}
+
+static void net_rx_action(struct softirq_action *h)
+{
+ int this_cpu = smp_processor_id();
+ struct softnet_data *queue = &softnet_data[this_cpu];
+ unsigned long start_time = jiffies;
+ int budget = netdev_max_backlog;
+
+ br_read_lock(BR_NETPROTO_LOCK);
+ local_irq_disable();
+
+ while (!list_empty(&queue->poll_list)) {
+ struct net_device *dev;
+
+ if (budget <= 0 || jiffies - start_time > 1)
+ goto softnet_break;
+
+ local_irq_enable();
+
+ dev = list_entry(queue->poll_list.next, struct net_device, poll_list);
+
+ if (dev->quota <= 0 || dev->poll(dev, &budget)) {
+ local_irq_disable();
+ list_del(&dev->poll_list);
+ list_add_tail(&dev->poll_list, &queue->poll_list);
+ if (dev->quota < 0)
+ dev->quota += dev->weight;
+ else
+ dev->quota = dev->weight;
+ } else {
+ dev_put(dev);
+ local_irq_disable();
+ }
+ }
+
+ local_irq_enable();
+ br_read_unlock(BR_NETPROTO_LOCK);
+ return;
+
+softnet_break:
+ netdev_rx_stat[this_cpu].time_squeeze++;
+ __cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ);
+
+ local_irq_enable();
+ br_read_unlock(BR_NETPROTO_LOCK);
+}
+
+static gifconf_func_t * gifconf_list [NPROTO];
+
+/**
+ * register_gifconf - register a SIOCGIF handler
+ * @family: Address family
+ * @gifconf: Function handler
+ *
+ * Register protocol dependent address dumping routines. The handler
+ * that is passed must not be freed or reused until it has been replaced
+ * by another handler.
+ */
+
+int register_gifconf(unsigned int family, gifconf_func_t * gifconf)
+{
+ if (family>=NPROTO)
+ return -EINVAL;
+ gifconf_list[family] = gifconf;
+ return 0;
+}
+
+
+/*
+ * Map an interface index to its name (SIOCGIFNAME)
+ */
+
+/*
+ * We need this ioctl for efficient implementation of the
+ * if_indextoname() function required by the IPv6 API. Without
+ * it, we would have to search all the interfaces to find a
+ * match. --pb
+ */
+
+static int dev_ifname(struct ifreq *arg)
+{
+ struct net_device *dev;
+ struct ifreq ifr;
+
+ /*
+ * Fetch the caller's info block.
+ */
+
+ if (copy_from_user(&ifr, arg, sizeof(struct ifreq)))
+ return -EFAULT;
+
+ read_lock(&dev_base_lock);
+ dev = __dev_get_by_index(ifr.ifr_ifindex);
+ if (!dev) {
+ read_unlock(&dev_base_lock);
+ return -ENODEV;
+ }
+
+ strcpy(ifr.ifr_name, dev->name);
+ read_unlock(&dev_base_lock);
+
+ if (copy_to_user(arg, &ifr, sizeof(struct ifreq)))
+ return -EFAULT;
+ return 0;
+}
+
+/*
+ * Perform a SIOCGIFCONF call. This structure will change
+ * size eventually, and there is nothing I can do about it.
+ * Thus we will need a 'compatibility mode'.
+ */
+
+static int dev_ifconf(char *arg)
+{
+ struct ifconf ifc;
+ struct net_device *dev;
+ char *pos;
+ int len;
+ int total;
+ int i;
+
+ /*
+ * Fetch the caller's info block.
+ */
+
+ if (copy_from_user(&ifc, arg, sizeof(struct ifconf)))
+ return -EFAULT;
+
+ pos = ifc.ifc_buf;
+ len = ifc.ifc_len;
+
+ /*
+ * Loop over the interfaces, and write an info block for each.
+ */
+
+ total = 0;
+ for (dev = dev_base; dev != NULL; dev = dev->next) {
+ for (i=0; i<NPROTO; i++) {
+ if (gifconf_list[i]) {
+ int done;
+ if (pos==NULL) {
+ done = gifconf_list[i](dev, NULL, 0);
+ } else {
+ done = gifconf_list[i](dev, pos+total, len-total);
+ }
+ if (done<0) {
+ return -EFAULT;
+ }
+ total += done;
+ }
+ }
+ }
+
+ /*
+ * All done. Write the updated control block back to the caller.
+ */
+ ifc.ifc_len = total;
+
+ if (copy_to_user(arg, &ifc, sizeof(struct ifconf)))
+ return -EFAULT;
+
+ /*
+ * Both BSD and Solaris return 0 here, so we do too.
+ */
+ return 0;
+}
+
+/*
+ * This is invoked by the /proc filesystem handler to display a device
+ * in detail.
+ */
+
+#ifdef CONFIG_PROC_FS
+
+static int sprintf_stats(char *buffer, struct net_device *dev)
+{
+ struct net_device_stats *stats = (dev->get_stats ? dev->get_stats(dev): NULL);
+ int size;
+
+ if (stats)
+ size = sprintf(buffer, "%6s:%8lu %7lu %4lu %4lu %4lu %5lu %10lu %9lu %8lu %7lu %4lu %4lu %4lu %5lu %7lu %10lu\n",
+ dev->name,
+ stats->rx_bytes,
+ stats->rx_packets, stats->rx_errors,
+ stats->rx_dropped + stats->rx_missed_errors,
+ stats->rx_fifo_errors,
+ stats->rx_length_errors + stats->rx_over_errors
+ + stats->rx_crc_errors + stats->rx_frame_errors,
+ stats->rx_compressed, stats->multicast,
+ stats->tx_bytes,
+ stats->tx_packets, stats->tx_errors, stats->tx_dropped,
+ stats->tx_fifo_errors, stats->collisions,
+ stats->tx_carrier_errors + stats->tx_aborted_errors
+ + stats->tx_window_errors + stats->tx_heartbeat_errors,
+ stats->tx_compressed);
+ else
+ size = sprintf(buffer, "%6s: No statistics available.\n", dev->name);
+
+ return size;
+}
+
+/*
+ * Called from the PROCfs module. This now uses the new arbitrary sized /proc/net interface
+ * to create /proc/net/dev
+ */
+
+static int dev_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int len = 0;
+ off_t begin = 0;
+ off_t pos = 0;
+ int size;
+ struct net_device *dev;
+
+
+ size = sprintf(buffer,
+ "Inter-| Receive | Transmit\n"
+ " face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed\n");
+
+ pos += size;
+ len += size;
+
+
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev != NULL; dev = dev->next) {
+ size = sprintf_stats(buffer+len, dev);
+ len += size;
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (pos > offset + length)
+ break;
+ }
+ read_unlock(&dev_base_lock);
+
+ *start = buffer + (offset - begin); /* Start of wanted data */
+ len -= (offset - begin); /* Start slop */
+ if (len > length)
+ len = length; /* Ending slop */
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+static int dev_proc_stats(char *buffer, char **start, off_t offset,
+ int length, int *eof, void *data)
+{
+ int i, lcpu;
+ int len=0;
+
+ for (lcpu=0; lcpu<smp_num_cpus; lcpu++) {
+ i = cpu_logical_map(lcpu);
+ len += sprintf(buffer+len, "%08x %08x %08x %08x %08x %08x %08x %08x %08x\n",
+ netdev_rx_stat[i].total,
+ netdev_rx_stat[i].dropped,
+ netdev_rx_stat[i].time_squeeze,
+ netdev_rx_stat[i].throttled,
+ netdev_rx_stat[i].fastroute_hit,
+ netdev_rx_stat[i].fastroute_success,
+ netdev_rx_stat[i].fastroute_defer,
+ netdev_rx_stat[i].fastroute_deferred_out,
+#if 0
+ netdev_rx_stat[i].fastroute_latency_reduction
+#else
+ netdev_rx_stat[i].cpu_collision
+#endif
+ );
+ }
+
+ len -= offset;
+
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+
+ *start = buffer + offset;
+ *eof = 1;
+
+ return len;
+}
+
+#endif /* CONFIG_PROC_FS */
+
+
+/**
+ * netdev_set_master - set up master/slave pair
+ * @slave: slave device
+ * @master: new master device
+ *
+ * Changes the master device of the slave. Pass %NULL to break the
+ * bonding. The caller must hold the RTNL semaphore. On a failure
+ * a negative errno code is returned. On success the reference counts
+ * are adjusted, %RTM_NEWLINK is sent to the routing socket and the
+ * function returns zero.
+ */
+
+int netdev_set_master(struct net_device *slave, struct net_device *master)
+{
+ struct net_device *old = slave->master;
+
+ ASSERT_RTNL();
+
+ if (master) {
+ if (old)
+ return -EBUSY;
+ dev_hold(master);
+ }
+
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ slave->master = master;
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+
+ if (old)
+ dev_put(old);
+
+ if (master)
+ slave->flags |= IFF_SLAVE;
+ else
+ slave->flags &= ~IFF_SLAVE;
+
+ rtmsg_ifinfo(RTM_NEWLINK, slave, IFF_SLAVE);
+ return 0;
+}
+
+/**
+ * dev_set_promiscuity - update promiscuity count on a device
+ * @dev: device
+ * @inc: modifier
+ *
+ * Add or remove promsicuity from a device. While the count in the device
+ * remains above zero the interface remains promiscuous. Once it hits zero
+ * the device reverts back to normal filtering operation. A negative inc
+ * value is used to drop promiscuity on the device.
+ */
+
+void dev_set_promiscuity(struct net_device *dev, int inc)
+{
+ unsigned short old_flags = dev->flags;
+
+ dev->flags |= IFF_PROMISC;
+ if ((dev->promiscuity += inc) == 0)
+ dev->flags &= ~IFF_PROMISC;
+ if (dev->flags^old_flags) {
+#ifdef CONFIG_NET_FASTROUTE
+ if (dev->flags&IFF_PROMISC) {
+ netdev_fastroute_obstacles++;
+ dev_clear_fastroute(dev);
+ } else
+ netdev_fastroute_obstacles--;
+#endif
+ dev_mc_upload(dev);
+ printk(KERN_INFO "device %s %s promiscuous mode\n",
+ dev->name, (dev->flags&IFF_PROMISC) ? "entered" : "left");
+ }
+}
+
+/**
+ * dev_set_allmulti - update allmulti count on a device
+ * @dev: device
+ * @inc: modifier
+ *
+ * Add or remove reception of all multicast frames to a device. While the
+ * count in the device remains above zero the interface remains listening
+ * to all interfaces. Once it hits zero the device reverts back to normal
+ * filtering operation. A negative @inc value is used to drop the counter
+ * when releasing a resource needing all multicasts.
+ */
+
+void dev_set_allmulti(struct net_device *dev, int inc)
+{
+ unsigned short old_flags = dev->flags;
+
+ dev->flags |= IFF_ALLMULTI;
+ if ((dev->allmulti += inc) == 0)
+ dev->flags &= ~IFF_ALLMULTI;
+ if (dev->flags^old_flags)
+ dev_mc_upload(dev);
+}
+
+int dev_change_flags(struct net_device *dev, unsigned flags)
+{
+ int ret;
+ int old_flags = dev->flags;
+
+ /*
+ * Set the flags on our device.
+ */
+
+ dev->flags = (flags & (IFF_DEBUG|IFF_NOTRAILERS|IFF_NOARP|IFF_DYNAMIC|
+ IFF_MULTICAST|IFF_PORTSEL|IFF_AUTOMEDIA)) |
+ (dev->flags & (IFF_UP|IFF_VOLATILE|IFF_PROMISC|IFF_ALLMULTI));
+
+ /*
+ * Load in the correct multicast list now the flags have changed.
+ */
+
+ dev_mc_upload(dev);
+
+ /*
+ * Have we downed the interface. We handle IFF_UP ourselves
+ * according to user attempts to set it, rather than blindly
+ * setting it.
+ */
+
+ ret = 0;
+ if ((old_flags^flags)&IFF_UP) /* Bit is different ? */
+ {
+ ret = ((old_flags & IFF_UP) ? dev_close : dev_open)(dev);
+
+ if (ret == 0)
+ dev_mc_upload(dev);
+ }
+
+ if (dev->flags&IFF_UP &&
+ ((old_flags^dev->flags)&~(IFF_UP|IFF_PROMISC|IFF_ALLMULTI|IFF_VOLATILE)))
+ notifier_call_chain(&netdev_chain, NETDEV_CHANGE, dev);
+
+ if ((flags^dev->gflags)&IFF_PROMISC) {
+ int inc = (flags&IFF_PROMISC) ? +1 : -1;
+ dev->gflags ^= IFF_PROMISC;
+ dev_set_promiscuity(dev, inc);
+ }
+
+ /* NOTE: order of synchronization of IFF_PROMISC and IFF_ALLMULTI
+ is important. Some (broken) drivers set IFF_PROMISC, when
+ IFF_ALLMULTI is requested not asking us and not reporting.
+ */
+ if ((flags^dev->gflags)&IFF_ALLMULTI) {
+ int inc = (flags&IFF_ALLMULTI) ? +1 : -1;
+ dev->gflags ^= IFF_ALLMULTI;
+ dev_set_allmulti(dev, inc);
+ }
+
+ if (old_flags^dev->flags)
+ rtmsg_ifinfo(RTM_NEWLINK, dev, old_flags^dev->flags);
+
+ return ret;
+}
+
+/*
+ * Perform the SIOCxIFxxx calls.
+ */
+
+static int dev_ifsioc(struct ifreq *ifr, unsigned int cmd)
+{
+ struct net_device *dev;
+ int err;
+
+ if ((dev = __dev_get_by_name(ifr->ifr_name)) == NULL)
+ return -ENODEV;
+
+ switch(cmd)
+ {
+ case SIOCGIFFLAGS: /* Get interface flags */
+ ifr->ifr_flags = (dev->flags&~(IFF_PROMISC|IFF_ALLMULTI|IFF_RUNNING))
+ |(dev->gflags&(IFF_PROMISC|IFF_ALLMULTI));
+ if (netif_running(dev) && netif_carrier_ok(dev))
+ ifr->ifr_flags |= IFF_RUNNING;
+ return 0;
+
+ case SIOCSIFFLAGS: /* Set interface flags */
+ return dev_change_flags(dev, ifr->ifr_flags);
+
+ case SIOCGIFMETRIC: /* Get the metric on the interface (currently unused) */
+ ifr->ifr_metric = 0;
+ return 0;
+
+ case SIOCSIFMETRIC: /* Set the metric on the interface (currently unused) */
+ return -EOPNOTSUPP;
+
+ case SIOCGIFMTU: /* Get the MTU of a device */
+ ifr->ifr_mtu = dev->mtu;
+ return 0;
+
+ case SIOCSIFMTU: /* Set the MTU of a device */
+ if (ifr->ifr_mtu == dev->mtu)
+ return 0;
+
+ /*
+ * MTU must be positive.
+ */
+
+ if (ifr->ifr_mtu<0)
+ return -EINVAL;
+
+ if (!netif_device_present(dev))
+ return -ENODEV;
+
+ if (dev->change_mtu)
+ err = dev->change_mtu(dev, ifr->ifr_mtu);
+ else {
+ dev->mtu = ifr->ifr_mtu;
+ err = 0;
+ }
+ if (!err && dev->flags&IFF_UP)
+ notifier_call_chain(&netdev_chain, NETDEV_CHANGEMTU, dev);
+ return err;
+
+ case SIOCGIFHWADDR:
+ memcpy(ifr->ifr_hwaddr.sa_data, dev->dev_addr,
+ min(sizeof ifr->ifr_hwaddr.sa_data, (size_t) dev->addr_len));
+ ifr->ifr_hwaddr.sa_family=dev->type;
+ return 0;
+
+ case SIOCSIFHWADDR:
+ if (dev->set_mac_address == NULL)
+ return -EOPNOTSUPP;
+ if (ifr->ifr_hwaddr.sa_family!=dev->type)
+ return -EINVAL;
+ if (!netif_device_present(dev))
+ return -ENODEV;
+ err = dev->set_mac_address(dev, &ifr->ifr_hwaddr);
+ if (!err)
+ notifier_call_chain(&netdev_chain, NETDEV_CHANGEADDR, dev);
+ return err;
+
+#ifdef SIOCGIFHWBROADCAST
+ case SIOCGIFHWBROADCAST:
+ memcpy(ifr->ifr_hwaddr.sa_data, dev->broadcast,
+ min(sizeof ifr->ifr_hwaddr.sa_data, (size_t) dev->addr_len));
+ ifr->ifr_hwaddr.sa_family=dev->type;
+ return 0;
+#endif
+
+ case SIOCSIFHWBROADCAST:
+ if (ifr->ifr_hwaddr.sa_family!=dev->type)
+ return -EINVAL;
+ memcpy(dev->broadcast, ifr->ifr_hwaddr.sa_data,
+ min(sizeof ifr->ifr_hwaddr.sa_data, (size_t) dev->addr_len));
+ notifier_call_chain(&netdev_chain, NETDEV_CHANGEADDR, dev);
+ return 0;
+
+ case SIOCGIFMAP:
+ ifr->ifr_map.mem_start=dev->mem_start;
+ ifr->ifr_map.mem_end=dev->mem_end;
+ ifr->ifr_map.base_addr=dev->base_addr;
+ ifr->ifr_map.irq=dev->irq;
+ ifr->ifr_map.dma=dev->dma;
+ ifr->ifr_map.port=dev->if_port;
+ return 0;
+
+ case SIOCSIFMAP:
+ if (dev->set_config) {
+ if (!netif_device_present(dev))
+ return -ENODEV;
+ return dev->set_config(dev,&ifr->ifr_map);
+ }
+ return -EOPNOTSUPP;
+
+ case SIOCADDMULTI:
+ if (dev->set_multicast_list == NULL ||
+ ifr->ifr_hwaddr.sa_family != AF_UNSPEC)
+ return -EINVAL;
+ if (!netif_device_present(dev))
+ return -ENODEV;
+ dev_mc_add(dev,ifr->ifr_hwaddr.sa_data, dev->addr_len, 1);
+ return 0;
+
+ case SIOCDELMULTI:
+ if (dev->set_multicast_list == NULL ||
+ ifr->ifr_hwaddr.sa_family!=AF_UNSPEC)
+ return -EINVAL;
+ if (!netif_device_present(dev))
+ return -ENODEV;
+ dev_mc_delete(dev,ifr->ifr_hwaddr.sa_data,dev->addr_len, 1);
+ return 0;
+
+ case SIOCGIFINDEX:
+ ifr->ifr_ifindex = dev->ifindex;
+ return 0;
+
+ case SIOCGIFTXQLEN:
+ ifr->ifr_qlen = dev->tx_queue_len;
+ return 0;
+
+ case SIOCSIFTXQLEN:
+ if (ifr->ifr_qlen<0)
+ return -EINVAL;
+ dev->tx_queue_len = ifr->ifr_qlen;
+ return 0;
+
+ case SIOCSIFNAME:
+ if (dev->flags&IFF_UP)
+ return -EBUSY;
+ /* Check if name contains a wildcard */
+ if (strchr(ifr->ifr_newname, '%')) {
+ char format[IFNAMSIZ + 1];
+ int ret;
+ memcpy(format, ifr->ifr_newname, IFNAMSIZ);
+ format[IFNAMSIZ-1] = 0;
+ /* Find a free name based on format.
+ * dev_alloc_name() replaces "%d" with at max
+ * 2 digits, so no name overflow. - Jean II */
+ ret = dev_alloc_name(dev, format);
+ if (ret < 0)
+ return ret;
+ /* Copy the new name back to caller. */
+ strncpy(ifr->ifr_newname, dev->name, IFNAMSIZ);
+ } else {
+ if (__dev_get_by_name(ifr->ifr_newname))
+ return -EEXIST;
+ memcpy(dev->name, ifr->ifr_newname, IFNAMSIZ);
+ dev->name[IFNAMSIZ-1] = 0;
+ }
+ notifier_call_chain(&netdev_chain, NETDEV_CHANGENAME, dev);
+ return 0;
+
+ /*
+ * Unknown or private ioctl
+ */
+
+ default:
+ if ((cmd >= SIOCDEVPRIVATE &&
+ cmd <= SIOCDEVPRIVATE + 15) ||
+ cmd == SIOCBONDENSLAVE ||
+ cmd == SIOCBONDRELEASE ||
+ cmd == SIOCBONDSETHWADDR ||
+ cmd == SIOCBONDSLAVEINFOQUERY ||
+ cmd == SIOCBONDINFOQUERY ||
+ cmd == SIOCBONDCHANGEACTIVE ||
+ cmd == SIOCGMIIPHY ||
+ cmd == SIOCGMIIREG ||
+ cmd == SIOCSMIIREG ||
+ cmd == SIOCWANDEV) {
+ if (dev->do_ioctl) {
+ if (!netif_device_present(dev))
+ return -ENODEV;
+ return dev->do_ioctl(dev, ifr, cmd);
+ }
+ return -EOPNOTSUPP;
+ }
+
+ }
+ return -EINVAL;
+}
+
+/*
+ * This function handles all "interface"-type I/O control requests. The actual
+ * 'doing' part of this is dev_ifsioc above.
+ */
+
+/**
+ * dev_ioctl - network device ioctl
+ * @cmd: command to issue
+ * @arg: pointer to a struct ifreq in user space
+ *
+ * Issue ioctl functions to devices. This is normally called by the
+ * user space syscall interfaces but can sometimes be useful for
+ * other purposes. The return value is the return from the syscall if
+ * positive or a negative errno code on error.
+ */
+
+int dev_ioctl(unsigned int cmd, void *arg)
+{
+ struct ifreq ifr;
+ int ret;
+ char *colon;
+
+ /* One special case: SIOCGIFCONF takes ifconf argument
+ and requires shared lock, because it sleeps writing
+ to user space.
+ */
+
+ if (cmd == SIOCGIFCONF) {
+ rtnl_shlock();
+ ret = dev_ifconf((char *) arg);
+ rtnl_shunlock();
+ return ret;
+ }
+ if (cmd == SIOCGIFNAME) {
+ return dev_ifname((struct ifreq *)arg);
+ }
+
+ if (copy_from_user(&ifr, arg, sizeof(struct ifreq)))
+ return -EFAULT;
+
+ ifr.ifr_name[IFNAMSIZ-1] = 0;
+
+ colon = strchr(ifr.ifr_name, ':');
+ if (colon)
+ *colon = 0;
+
+ /*
+ * See which interface the caller is talking about.
+ */
+
+ switch(cmd)
+ {
+ /*
+ * These ioctl calls:
+ * - can be done by all.
+ * - atomic and do not require locking.
+ * - return a value
+ */
+
+ case SIOCGIFFLAGS:
+ case SIOCGIFMETRIC:
+ case SIOCGIFMTU:
+ case SIOCGIFHWADDR:
+ case SIOCGIFSLAVE:
+ case SIOCGIFMAP:
+ case SIOCGIFINDEX:
+ case SIOCGIFTXQLEN:
+ dev_load(ifr.ifr_name);
+ read_lock(&dev_base_lock);
+ ret = dev_ifsioc(&ifr, cmd);
+ read_unlock(&dev_base_lock);
+ if (!ret) {
+ if (colon)
+ *colon = ':';
+ if (copy_to_user(arg, &ifr, sizeof(struct ifreq)))
+ return -EFAULT;
+ }
+ return ret;
+
+ case SIOCETHTOOL:
+ dev_load(ifr.ifr_name);
+ rtnl_lock();
+ ret = dev_ethtool(&ifr);
+ rtnl_unlock();
+ if (!ret) {
+ if (colon)
+ *colon = ':';
+ if (copy_to_user(arg, &ifr,
+ sizeof(struct ifreq)))
+ ret = -EFAULT;
+ }
+ return ret;
+
+ /*
+ * These ioctl calls:
+ * - require superuser power.
+ * - require strict serialization.
+ * - return a value
+ */
+
+ case SIOCSIFNAME:
+ case SIOCGMIIPHY:
+ case SIOCGMIIREG:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ dev_load(ifr.ifr_name);
+ dev_probe_lock();
+ rtnl_lock();
+ ret = dev_ifsioc(&ifr, cmd);
+ rtnl_unlock();
+ dev_probe_unlock();
+ if (!ret) {
+ if (colon)
+ *colon = ':';
+ if (copy_to_user(arg, &ifr, sizeof(struct ifreq)))
+ return -EFAULT;
+ }
+ return ret;
+
+ /*
+ * These ioctl calls:
+ * - require superuser power.
+ * - require strict serialization.
+ * - do not return a value
+ */
+
+ case SIOCSIFFLAGS:
+ case SIOCSIFMETRIC:
+ case SIOCSIFMTU:
+ case SIOCSIFMAP:
+ case SIOCSIFHWADDR:
+ case SIOCSIFSLAVE:
+ case SIOCADDMULTI:
+ case SIOCDELMULTI:
+ case SIOCSIFHWBROADCAST:
+ case SIOCSIFTXQLEN:
+ case SIOCSMIIREG:
+ case SIOCBONDENSLAVE:
+ case SIOCBONDRELEASE:
+ case SIOCBONDSETHWADDR:
+ case SIOCBONDSLAVEINFOQUERY:
+ case SIOCBONDINFOQUERY:
+ case SIOCBONDCHANGEACTIVE:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ dev_load(ifr.ifr_name);
+ dev_probe_lock();
+ rtnl_lock();
+ ret = dev_ifsioc(&ifr, cmd);
+ rtnl_unlock();
+ dev_probe_unlock();
+ return ret;
+
+ case SIOCGIFMEM:
+ /* Get the per device memory space. We can add this but currently
+ do not support it */
+ case SIOCSIFMEM:
+ /* Set the per device memory buffer space. Not applicable in our case */
+ case SIOCSIFLINK:
+ return -EINVAL;
+
+ /*
+ * Unknown or private ioctl.
+ */
+
+ default:
+ if (cmd == SIOCWANDEV ||
+ (cmd >= SIOCDEVPRIVATE &&
+ cmd <= SIOCDEVPRIVATE + 15)) {
+ dev_load(ifr.ifr_name);
+ dev_probe_lock();
+ rtnl_lock();
+ ret = dev_ifsioc(&ifr, cmd);
+ rtnl_unlock();
+ dev_probe_unlock();
+ if (!ret && copy_to_user(arg, &ifr, sizeof(struct ifreq)))
+ return -EFAULT;
+ return ret;
+ }
+#ifdef WIRELESS_EXT
+ /* Take care of Wireless Extensions */
+ if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) {
+ /* If command is `set a parameter', or
+ * `get the encoding parameters', check if
+ * the user has the right to do it */
+ if (IW_IS_SET(cmd) || (cmd == SIOCGIWENCODE)) {
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ }
+ dev_load(ifr.ifr_name);
+ rtnl_lock();
+ /* Follow me in net/core/wireless.c */
+ ret = wireless_process_ioctl(&ifr, cmd);
+ rtnl_unlock();
+ if (!ret && IW_IS_GET(cmd) &&
+ copy_to_user(arg, &ifr, sizeof(struct ifreq)))
+ return -EFAULT;
+ return ret;
+ }
+#endif /* WIRELESS_EXT */
+ return -EINVAL;
+ }
+}
+
+
+/**
+ * dev_new_index - allocate an ifindex
+ *
+ * Returns a suitable unique value for a new device interface
+ * number. The caller must hold the rtnl semaphore or the
+ * dev_base_lock to be sure it remains unique.
+ */
+
+int dev_new_index(void)
+{
+ static int ifindex;
+ for (;;) {
+ if (++ifindex <= 0)
+ ifindex=1;
+ if (__dev_get_by_index(ifindex) == NULL)
+ return ifindex;
+ }
+}
+
+static int dev_boot_phase = 1;
+
+/**
+ * register_netdevice - register a network device
+ * @dev: device to register
+ *
+ * Take a completed network device structure and add it to the kernel
+ * interfaces. A %NETDEV_REGISTER message is sent to the netdev notifier
+ * chain. 0 is returned on success. A negative errno code is returned
+ * on a failure to set up the device, or if the name is a duplicate.
+ *
+ * Callers must hold the rtnl semaphore. See the comment at the
+ * end of Space.c for details about the locking. You may want
+ * register_netdev() instead of this.
+ *
+ * BUGS:
+ * The locking appears insufficient to guarantee two parallel registers
+ * will not get the same name.
+ */
+
+int net_dev_init(void);
+
+int register_netdevice(struct net_device *dev)
+{
+ struct net_device *d, **dp;
+#ifdef CONFIG_NET_DIVERT
+ int ret;
+#endif
+
+ spin_lock_init(&dev->queue_lock);
+ spin_lock_init(&dev->xmit_lock);
+ dev->xmit_lock_owner = -1;
+#ifdef CONFIG_NET_FASTROUTE
+ dev->fastpath_lock=RW_LOCK_UNLOCKED;
+#endif
+
+ if (dev_boot_phase)
+ net_dev_init();
+
+#ifdef CONFIG_NET_DIVERT
+ ret = alloc_divert_blk(dev);
+ if (ret)
+ return ret;
+#endif /* CONFIG_NET_DIVERT */
+
+ dev->iflink = -1;
+
+ /* Init, if this function is available */
+ if (dev->init && dev->init(dev) != 0) {
+#ifdef CONFIG_NET_DIVERT
+ free_divert_blk(dev);
+#endif
+ return -EIO;
+ }
+
+ dev->ifindex = dev_new_index();
+ if (dev->iflink == -1)
+ dev->iflink = dev->ifindex;
+
+ /* Check for existence, and append to tail of chain */
+ for (dp=&dev_base; (d=*dp) != NULL; dp=&d->next) {
+ if (d == dev || strcmp(d->name, dev->name) == 0) {
+#ifdef CONFIG_NET_DIVERT
+ free_divert_blk(dev);
+#endif
+ return -EEXIST;
+ }
+ }
+
+ /* Fix illegal SG+CSUM combinations. */
+ if ((dev->features & NETIF_F_SG) &&
+ !(dev->features & (NETIF_F_IP_CSUM |
+ NETIF_F_NO_CSUM |
+ NETIF_F_HW_CSUM))) {
+ printk("%s: Dropping NETIF_F_SG since no checksum feature.\n",
+ dev->name);
+ dev->features &= ~NETIF_F_SG;
+ }
+
+ /*
+ * nil rebuild_header routine,
+ * that should be never called and used as just bug trap.
+ */
+
+ if (dev->rebuild_header == NULL)
+ dev->rebuild_header = default_rebuild_header;
+
+ /*
+ * Default initial state at registry is that the
+ * device is present.
+ */
+
+ set_bit(__LINK_STATE_PRESENT, &dev->state);
+
+ dev->next = NULL;
+ dev_init_scheduler(dev);
+ write_lock_bh(&dev_base_lock);
+ *dp = dev;
+ dev_hold(dev);
+ dev->deadbeaf = 0;
+ write_unlock_bh(&dev_base_lock);
+
+ /* Notify protocols, that a new device appeared. */
+ notifier_call_chain(&netdev_chain, NETDEV_REGISTER, dev);
+
+ net_run_sbin_hotplug(dev, "register");
+
+ return 0;
+}
+
+/**
+ * netdev_finish_unregister - complete unregistration
+ * @dev: device
+ *
+ * Destroy and free a dead device. A value of zero is returned on
+ * success.
+ */
+
+int netdev_finish_unregister(struct net_device *dev)
+{
+ BUG_TRAP(dev->ip_ptr==NULL);
+ BUG_TRAP(dev->ip6_ptr==NULL);
+ BUG_TRAP(dev->dn_ptr==NULL);
+
+ if (!dev->deadbeaf) {
+ printk(KERN_ERR "Freeing alive device %p, %s\n", dev, dev->name);
+ return 0;
+ }
+#ifdef NET_REFCNT_DEBUG
+ printk(KERN_DEBUG "netdev_finish_unregister: %s%s.\n", dev->name,
+ (dev->features & NETIF_F_DYNALLOC)?"":", old style");
+#endif
+ if (dev->destructor)
+ dev->destructor(dev);
+ if (dev->features & NETIF_F_DYNALLOC)
+ kfree(dev);
+ return 0;
+}
+
+/**
+ * unregister_netdevice - remove device from the kernel
+ * @dev: device
+ *
+ * This function shuts down a device interface and removes it
+ * from the kernel tables. On success 0 is returned, on a failure
+ * a negative errno code is returned.
+ *
+ * Callers must hold the rtnl semaphore. See the comment at the
+ * end of Space.c for details about the locking. You may want
+ * unregister_netdev() instead of this.
+ */
+
+int unregister_netdevice(struct net_device *dev)
+{
+ unsigned long now, warning_time;
+ struct net_device *d, **dp;
+
+ /* If device is running, close it first. */
+ if (dev->flags & IFF_UP)
+ dev_close(dev);
+
+ BUG_TRAP(dev->deadbeaf==0);
+ dev->deadbeaf = 1;
+
+ /* And unlink it from device chain. */
+ for (dp = &dev_base; (d=*dp) != NULL; dp=&d->next) {
+ if (d == dev) {
+ write_lock_bh(&dev_base_lock);
+ *dp = d->next;
+ write_unlock_bh(&dev_base_lock);
+ break;
+ }
+ }
+ if (d == NULL) {
+ printk(KERN_DEBUG "unregister_netdevice: device %s/%p never was registered\n", dev->name, dev);
+ return -ENODEV;
+ }
+
+ /* Synchronize to net_rx_action. */
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+
+ if (dev_boot_phase == 0) {
+#ifdef CONFIG_NET_FASTROUTE
+ dev_clear_fastroute(dev);
+#endif
+
+ /* Shutdown queueing discipline. */
+ dev_shutdown(dev);
+
+ net_run_sbin_hotplug(dev, "unregister");
+
+ /* Notify protocols, that we are about to destroy
+ this device. They should clean all the things.
+ */
+ notifier_call_chain(&netdev_chain, NETDEV_UNREGISTER, dev);
+
+ /*
+ * Flush the multicast chain
+ */
+ dev_mc_discard(dev);
+ }
+
+ if (dev->uninit)
+ dev->uninit(dev);
+
+ /* Notifier chain MUST detach us from master device. */
+ BUG_TRAP(dev->master==NULL);
+
+#ifdef CONFIG_NET_DIVERT
+ free_divert_blk(dev);
+#endif
+
+ if (dev->features & NETIF_F_DYNALLOC) {
+#ifdef NET_REFCNT_DEBUG
+ if (atomic_read(&dev->refcnt) != 1)
+ printk(KERN_DEBUG "unregister_netdevice: holding %s refcnt=%d\n", dev->name, atomic_read(&dev->refcnt)-1);
+#endif
+ dev_put(dev);
+ return 0;
+ }
+
+ /* Last reference is our one */
+ if (atomic_read(&dev->refcnt) == 1) {
+ dev_put(dev);
+ return 0;
+ }
+
+#ifdef NET_REFCNT_DEBUG
+ printk("unregister_netdevice: waiting %s refcnt=%d\n", dev->name, atomic_read(&dev->refcnt));
+#endif
+
+ /* EXPLANATION. If dev->refcnt is not now 1 (our own reference)
+ it means that someone in the kernel still has a reference
+ to this device and we cannot release it.
+
+ "New style" devices have destructors, hence we can return from this
+ function and destructor will do all the work later. As of kernel 2.4.0
+ there are very few "New Style" devices.
+
+ "Old style" devices expect that the device is free of any references
+ upon exit from this function.
+ We cannot return from this function until all such references have
+ fallen away. This is because the caller of this function will probably
+ immediately kfree(*dev) and then be unloaded via sys_delete_module.
+
+ So, we linger until all references fall away. The duration of the
+ linger is basically unbounded! It is driven by, for example, the
+ current setting of sysctl_ipfrag_time.
+
+ After 1 second, we start to rebroadcast unregister notifications
+ in hope that careless clients will release the device.
+
+ */
+
+ now = warning_time = jiffies;
+ while (atomic_read(&dev->refcnt) != 1) {
+ if ((jiffies - now) > 1*HZ) {
+ /* Rebroadcast unregister notification */
+ notifier_call_chain(&netdev_chain, NETDEV_UNREGISTER, dev);
+ }
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(HZ/4);
+ current->state = TASK_RUNNING;
+ if ((jiffies - warning_time) > 10*HZ) {
+ printk(KERN_EMERG "unregister_netdevice: waiting for %s to "
+ "become free. Usage count = %d\n",
+ dev->name, atomic_read(&dev->refcnt));
+ warning_time = jiffies;
+ }
+ }
+ dev_put(dev);
+ return 0;
+}
+
+
+/*
+ * Initialize the DEV module. At boot time this walks the device list and
+ * unhooks any devices that fail to initialise (normally hardware not
+ * present) and leaves us with a valid list of present and active devices.
+ *
+ */
+
+extern void net_device_init(void);
+extern void ip_auto_config(void);
+struct proc_dir_entry *proc_net_drivers;
+#ifdef CONFIG_NET_DIVERT
+extern void dv_init(void);
+#endif /* CONFIG_NET_DIVERT */
+
+
+/*
+ * Callers must hold the rtnl semaphore. See the comment at the
+ * end of Space.c for details about the locking.
+ */
+int __init net_dev_init(void)
+{
+ struct net_device *dev, **dp;
+ int i;
+
+ if (!dev_boot_phase)
+ return 0;
+
+
+#ifdef CONFIG_NET_DIVERT
+ dv_init();
+#endif /* CONFIG_NET_DIVERT */
+
+ /*
+ * Initialise the packet receive queues.
+ */
+
+ for (i = 0; i < NR_CPUS; i++) {
+ struct softnet_data *queue;
+
+ queue = &softnet_data[i];
+ skb_queue_head_init(&queue->input_pkt_queue);
+ queue->throttle = 0;
+ queue->cng_level = 0;
+ queue->avg_blog = 10; /* arbitrary non-zero */
+ queue->completion_queue = NULL;
+ INIT_LIST_HEAD(&queue->poll_list);
+ set_bit(__LINK_STATE_START, &queue->blog_dev.state);
+ queue->blog_dev.weight = weight_p;
+ queue->blog_dev.poll = process_backlog;
+ atomic_set(&queue->blog_dev.refcnt, 1);
+ }
+
+#ifdef CONFIG_NET_PROFILE
+ net_profile_init();
+ NET_PROFILE_REGISTER(dev_queue_xmit);
+ NET_PROFILE_REGISTER(softnet_process);
+#endif
+
+#ifdef OFFLINE_SAMPLE
+ samp_timer.expires = jiffies + (10 * HZ);
+ add_timer(&samp_timer);
+#endif
+
+ /*
+ * Add the devices.
+ * If the call to dev->init fails, the dev is removed
+ * from the chain disconnecting the device until the
+ * next reboot.
+ *
+ * NB At boot phase networking is dead. No locking is required.
+ * But we still preserve dev_base_lock for sanity.
+ */
+
+ dp = &dev_base;
+ while ((dev = *dp) != NULL) {
+ spin_lock_init(&dev->queue_lock);
+ spin_lock_init(&dev->xmit_lock);
+#ifdef CONFIG_NET_FASTROUTE
+ dev->fastpath_lock = RW_LOCK_UNLOCKED;
+#endif
+ dev->xmit_lock_owner = -1;
+ dev->iflink = -1;
+ dev_hold(dev);
+
+ /*
+ * Allocate name. If the init() fails
+ * the name will be reissued correctly.
+ */
+ if (strchr(dev->name, '%'))
+ dev_alloc_name(dev, dev->name);
+
+ /*
+ * Check boot time settings for the device.
+ */
+ netdev_boot_setup_check(dev);
+
+ if (dev->init && dev->init(dev)) {
+ /*
+ * It failed to come up. It will be unhooked later.
+ * dev_alloc_name can now advance to next suitable
+ * name that is checked next.
+ */
+ dev->deadbeaf = 1;
+ dp = &dev->next;
+ } else {
+ dp = &dev->next;
+ dev->ifindex = dev_new_index();
+ if (dev->iflink == -1)
+ dev->iflink = dev->ifindex;
+ if (dev->rebuild_header == NULL)
+ dev->rebuild_header = default_rebuild_header;
+ dev_init_scheduler(dev);
+ set_bit(__LINK_STATE_PRESENT, &dev->state);
+ }
+ }
+
+ /*
+ * Unhook devices that failed to come up
+ */
+ dp = &dev_base;
+ while ((dev = *dp) != NULL) {
+ if (dev->deadbeaf) {
+ write_lock_bh(&dev_base_lock);
+ *dp = dev->next;
+ write_unlock_bh(&dev_base_lock);
+ dev_put(dev);
+ } else {
+ dp = &dev->next;
+ }
+ }
+
+#ifdef CONFIG_PROC_FS
+ proc_net_create("dev", 0, dev_get_info);
+ create_proc_read_entry("net/softnet_stat", 0, 0, dev_proc_stats, NULL);
+ proc_net_drivers = proc_mkdir("net/drivers", 0);
+#ifdef WIRELESS_EXT
+ /* Available in net/core/wireless.c */
+ proc_net_create("wireless", 0, dev_get_wireless_info);
+#endif /* WIRELESS_EXT */
+#endif /* CONFIG_PROC_FS */
+
+ dev_boot_phase = 0;
+
+ open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
+ open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
+
+ dst_init();
+ dev_mcast_init();
+
+#ifdef CONFIG_NET_SCHED
+ pktsched_init();
+#endif
+ /*
+ * Initialise network devices
+ */
+
+ net_device_init();
+
+ return 0;
+}
+
+#ifdef CONFIG_HOTPLUG
+
+/* Notify userspace when a netdevice event occurs,
+ * by running '/sbin/hotplug net' with certain
+ * environment variables set.
+ */
+
+static int net_run_sbin_hotplug(struct net_device *dev, char *action)
+{
+ char *argv[3], *envp[5], ifname[12 + IFNAMSIZ], action_str[32];
+ int i;
+
+ sprintf(ifname, "INTERFACE=%s", dev->name);
+ sprintf(action_str, "ACTION=%s", action);
+
+ i = 0;
+ argv[i++] = hotplug_path;
+ argv[i++] = "net";
+ argv[i] = 0;
+
+ i = 0;
+ /* minimal command environment */
+ envp [i++] = "HOME=/";
+ envp [i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
+ envp [i++] = ifname;
+ envp [i++] = action_str;
+ envp [i] = 0;
+
+ return call_usermodehelper(argv [0], argv, envp);
+}
+#endif
diff --git a/uClinux-2.4.31-uc0/net/core/dev_mcast.c b/uClinux-2.4.31-uc0/net/core/dev_mcast.c
new file mode 100644
index 0000000..6b24a14
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/dev_mcast.c
@@ -0,0 +1,275 @@
+/*
+ * Linux NET3: Multicast List maintenance.
+ *
+ * Authors:
+ * Tim Kordas <tjk@nostromo.eeap.cwru.edu>
+ * Richard Underwood <richard@wuzz.demon.co.uk>
+ *
+ * Stir fried together from the IP multicast and CAP patches above
+ * Alan Cox <Alan.Cox@linux.org>
+ *
+ * Fixes:
+ * Alan Cox : Update the device on a real delete
+ * rather than any time but...
+ * Alan Cox : IFF_ALLMULTI support.
+ * Alan Cox : New format set_multicast_list() calls.
+ * Gleb Natapov : Remove dev_mc_lock.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/arp.h>
+
+
+/*
+ * Device multicast list maintenance.
+ *
+ * This is used both by IP and by the user level maintenance functions.
+ * Unlike BSD we maintain a usage count on a given multicast address so
+ * that a casual user application can add/delete multicasts used by
+ * protocols without doing damage to the protocols when it deletes the
+ * entries. It also helps IP as it tracks overlapping maps.
+ *
+ * Device mc lists are changed by bh at least if IPv6 is enabled,
+ * so that it must be bh protected.
+ *
+ * We block accesses to device mc filters with dev->xmit_lock.
+ */
+
+/*
+ * Update the multicast list into the physical NIC controller.
+ */
+
+static void __dev_mc_upload(struct net_device *dev)
+{
+ /* Don't do anything till we up the interface
+ * [dev_open will call this function so the list will
+ * stay sane]
+ */
+
+ if (!(dev->flags&IFF_UP))
+ return;
+
+ /*
+ * Devices with no set multicast or which have been
+ * detached don't get set.
+ */
+
+ if (dev->set_multicast_list == NULL ||
+ !netif_device_present(dev))
+ return;
+
+ dev->set_multicast_list(dev);
+}
+
+void dev_mc_upload(struct net_device *dev)
+{
+ spin_lock_bh(&dev->xmit_lock);
+ __dev_mc_upload(dev);
+ spin_unlock_bh(&dev->xmit_lock);
+}
+
+/*
+ * Delete a device level multicast
+ */
+
+int dev_mc_delete(struct net_device *dev, void *addr, int alen, int glbl)
+{
+ int err = 0;
+ struct dev_mc_list *dmi, **dmip;
+
+ spin_lock_bh(&dev->xmit_lock);
+
+ for (dmip = &dev->mc_list; (dmi = *dmip) != NULL; dmip = &dmi->next) {
+ /*
+ * Find the entry we want to delete. The device could
+ * have variable length entries so check these too.
+ */
+ if (memcmp(dmi->dmi_addr, addr, dmi->dmi_addrlen) == 0 &&
+ alen == dmi->dmi_addrlen) {
+ if (glbl) {
+ int old_glbl = dmi->dmi_gusers;
+ dmi->dmi_gusers = 0;
+ if (old_glbl == 0)
+ break;
+ }
+ if (--dmi->dmi_users)
+ goto done;
+
+ /*
+ * Last user. So delete the entry.
+ */
+ *dmip = dmi->next;
+ dev->mc_count--;
+
+ kfree(dmi);
+
+ /*
+ * We have altered the list, so the card
+ * loaded filter is now wrong. Fix it
+ */
+ __dev_mc_upload(dev);
+
+ spin_unlock_bh(&dev->xmit_lock);
+ return 0;
+ }
+ }
+ err = -ENOENT;
+done:
+ spin_unlock_bh(&dev->xmit_lock);
+ return err;
+}
+
+/*
+ * Add a device level multicast
+ */
+
+int dev_mc_add(struct net_device *dev, void *addr, int alen, int glbl)
+{
+ int err = 0;
+ struct dev_mc_list *dmi, *dmi1;
+
+ dmi1 = (struct dev_mc_list *)kmalloc(sizeof(*dmi), GFP_ATOMIC);
+
+ spin_lock_bh(&dev->xmit_lock);
+ for (dmi = dev->mc_list; dmi != NULL; dmi = dmi->next) {
+ if (memcmp(dmi->dmi_addr, addr, dmi->dmi_addrlen) == 0 &&
+ dmi->dmi_addrlen == alen) {
+ if (glbl) {
+ int old_glbl = dmi->dmi_gusers;
+ dmi->dmi_gusers = 1;
+ if (old_glbl)
+ goto done;
+ }
+ dmi->dmi_users++;
+ goto done;
+ }
+ }
+
+ if ((dmi = dmi1) == NULL) {
+ spin_unlock_bh(&dev->xmit_lock);
+ return -ENOMEM;
+ }
+ memcpy(dmi->dmi_addr, addr, alen);
+ dmi->dmi_addrlen = alen;
+ dmi->next = dev->mc_list;
+ dmi->dmi_users = 1;
+ dmi->dmi_gusers = glbl ? 1 : 0;
+ dev->mc_list = dmi;
+ dev->mc_count++;
+
+ __dev_mc_upload(dev);
+
+ spin_unlock_bh(&dev->xmit_lock);
+ return 0;
+
+done:
+ spin_unlock_bh(&dev->xmit_lock);
+ if (dmi1)
+ kfree(dmi1);
+ return err;
+}
+
+/*
+ * Discard multicast list when a device is downed
+ */
+
+void dev_mc_discard(struct net_device *dev)
+{
+ spin_lock_bh(&dev->xmit_lock);
+
+ while (dev->mc_list != NULL) {
+ struct dev_mc_list *tmp = dev->mc_list;
+ dev->mc_list = tmp->next;
+ if (tmp->dmi_users > tmp->dmi_gusers)
+ printk("dev_mc_discard: multicast leakage! dmi_users=%d\n", tmp->dmi_users);
+ kfree(tmp);
+ }
+ dev->mc_count = 0;
+
+ spin_unlock_bh(&dev->xmit_lock);
+}
+
+#ifdef CONFIG_PROC_FS
+static int dev_mc_read_proc(char *buffer, char **start, off_t offset,
+ int length, int *eof, void *data)
+{
+ off_t pos = 0, begin = 0;
+ struct dev_mc_list *m;
+ int len = 0;
+ struct net_device *dev;
+
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev; dev = dev->next) {
+ spin_lock_bh(&dev->xmit_lock);
+ for (m = dev->mc_list; m; m = m->next) {
+ int i;
+
+ len += sprintf(buffer+len,"%-4d %-15s %-5d %-5d ", dev->ifindex,
+ dev->name, m->dmi_users, m->dmi_gusers);
+
+ for (i = 0; i < m->dmi_addrlen; i++)
+ len += sprintf(buffer+len, "%02x", m->dmi_addr[i]);
+
+ len += sprintf(buffer+len, "\n");
+
+ pos = begin + len;
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (pos > offset + length) {
+ spin_unlock_bh(&dev->xmit_lock);
+ goto done;
+ }
+ }
+ spin_unlock_bh(&dev->xmit_lock);
+ }
+ *eof = 1;
+
+done:
+ read_unlock(&dev_base_lock);
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+#endif
+
+void __init dev_mcast_init(void)
+{
+#ifdef CONFIG_PROC_FS
+ create_proc_read_entry("net/dev_mcast", 0, 0, dev_mc_read_proc, NULL);
+#endif
+}
+
diff --git a/uClinux-2.4.31-uc0/net/core/dst.c b/uClinux-2.4.31-uc0/net/core/dst.c
new file mode 100644
index 0000000..209b175
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/dst.c
@@ -0,0 +1,224 @@
+/*
+ * net/dst.c Protocol independent destination cache.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <linux/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+
+#include <net/dst.h>
+
+/* Locking strategy:
+ * 1) Garbage collection state of dead destination cache
+ * entries is protected by dst_lock.
+ * 2) GC is run only from BH context, and is the only remover
+ * of entries.
+ * 3) Entries are added to the garbage list from both BH
+ * and non-BH context, so local BH disabling is needed.
+ * 4) All operations modify state, so a spinlock is used.
+ */
+static struct dst_entry *dst_garbage_list;
+#if RT_CACHE_DEBUG >= 2
+static atomic_t dst_total = ATOMIC_INIT(0);
+#endif
+static spinlock_t dst_lock = SPIN_LOCK_UNLOCKED;
+
+static unsigned long dst_gc_timer_expires;
+static unsigned long dst_gc_timer_inc = DST_GC_MAX;
+static void dst_run_gc(unsigned long);
+
+static struct timer_list dst_gc_timer =
+ { data: DST_GC_MIN, function: dst_run_gc };
+
+
+static void dst_run_gc(unsigned long dummy)
+{
+ int delayed = 0;
+ struct dst_entry * dst, **dstp;
+
+ if (!spin_trylock(&dst_lock)) {
+ mod_timer(&dst_gc_timer, jiffies + HZ/10);
+ return;
+ }
+
+
+ del_timer(&dst_gc_timer);
+ dstp = &dst_garbage_list;
+ while ((dst = *dstp) != NULL) {
+ if (atomic_read(&dst->__refcnt)) {
+ dstp = &dst->next;
+ delayed++;
+ continue;
+ }
+ *dstp = dst->next;
+ dst_destroy(dst);
+ }
+ if (!dst_garbage_list) {
+ dst_gc_timer_inc = DST_GC_MAX;
+ goto out;
+ }
+ if ((dst_gc_timer_expires += dst_gc_timer_inc) > DST_GC_MAX)
+ dst_gc_timer_expires = DST_GC_MAX;
+ dst_gc_timer_inc += DST_GC_INC;
+ dst_gc_timer.expires = jiffies + dst_gc_timer_expires;
+#if RT_CACHE_DEBUG >= 2
+ printk("dst_total: %d/%d %ld\n",
+ atomic_read(&dst_total), delayed, dst_gc_timer_expires);
+#endif
+ add_timer(&dst_gc_timer);
+
+out:
+ spin_unlock(&dst_lock);
+}
+
+static int dst_discard(struct sk_buff *skb)
+{
+ kfree_skb(skb);
+ return 0;
+}
+
+static int dst_blackhole(struct sk_buff *skb)
+{
+ kfree_skb(skb);
+ return 0;
+}
+
+void * dst_alloc(struct dst_ops * ops)
+{
+ struct dst_entry * dst;
+
+ if (ops->gc && atomic_read(&ops->entries) > ops->gc_thresh) {
+ if (ops->gc())
+ return NULL;
+ }
+ dst = kmem_cache_alloc(ops->kmem_cachep, SLAB_ATOMIC);
+ if (!dst)
+ return NULL;
+ memset(dst, 0, ops->entry_size);
+ atomic_set(&dst->__refcnt, 0);
+ dst->ops = ops;
+ dst->lastuse = jiffies;
+ dst->input = dst_discard;
+ dst->output = dst_blackhole;
+#if RT_CACHE_DEBUG >= 2
+ atomic_inc(&dst_total);
+#endif
+ atomic_inc(&ops->entries);
+ return dst;
+}
+
+void __dst_free(struct dst_entry * dst)
+{
+ spin_lock_bh(&dst_lock);
+
+ /* The first case (dev==NULL) is required, when
+ protocol module is unloaded.
+ */
+ if (dst->dev == NULL || !(dst->dev->flags&IFF_UP)) {
+ dst->input = dst_discard;
+ dst->output = dst_blackhole;
+ }
+ dst->obsolete = 2;
+ dst->next = dst_garbage_list;
+ dst_garbage_list = dst;
+ if (dst_gc_timer_inc > DST_GC_INC) {
+ dst_gc_timer_inc = DST_GC_INC;
+ dst_gc_timer_expires = DST_GC_MIN;
+ mod_timer(&dst_gc_timer, jiffies + dst_gc_timer_expires);
+ }
+
+ spin_unlock_bh(&dst_lock);
+}
+
+void dst_destroy(struct dst_entry * dst)
+{
+ struct neighbour *neigh;
+ struct hh_cache *hh;
+
+ smp_rmb();
+
+ neigh = dst->neighbour;
+ hh = dst->hh;
+
+ dst->hh = NULL;
+ if (hh && atomic_dec_and_test(&hh->hh_refcnt))
+ kfree(hh);
+
+ if (neigh) {
+ dst->neighbour = NULL;
+ neigh_release(neigh);
+ }
+
+ atomic_dec(&dst->ops->entries);
+
+ if (dst->ops->destroy)
+ dst->ops->destroy(dst);
+ if (dst->dev)
+ dev_put(dst->dev);
+#if RT_CACHE_DEBUG >= 2
+ atomic_dec(&dst_total);
+#endif
+ kmem_cache_free(dst->ops->kmem_cachep, dst);
+}
+
+static int dst_dev_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct net_device *dev = ptr;
+ struct dst_entry *dst;
+
+ switch (event) {
+ case NETDEV_UNREGISTER:
+ case NETDEV_DOWN:
+ spin_lock_bh(&dst_lock);
+ for (dst = dst_garbage_list; dst; dst = dst->next) {
+ if (dst->dev == dev) {
+ /* Dirty hack. We did it in 2.2 (in __dst_free),
+ we have _very_ good reasons not to repeat
+ this mistake in 2.3, but we have no choice
+ now. _It_ _is_ _explicit_ _deliberate_
+ _race_ _condition_.
+ */
+ if (event!=NETDEV_DOWN &&
+ !(dev->features & NETIF_F_DYNALLOC) &&
+ dst->output == dst_blackhole) {
+ dst->dev = &loopback_dev;
+ dev_put(dev);
+ dev_hold(&loopback_dev);
+ dst->output = dst_discard;
+ if (dst->neighbour && dst->neighbour->dev == dev) {
+ dst->neighbour->dev = &loopback_dev;
+ dev_put(dev);
+ dev_hold(&loopback_dev);
+ }
+ } else {
+ dst->input = dst_discard;
+ dst->output = dst_blackhole;
+ }
+ }
+ }
+ spin_unlock_bh(&dst_lock);
+ break;
+ }
+ return NOTIFY_DONE;
+}
+
+struct notifier_block dst_dev_notifier = {
+ dst_dev_event,
+ NULL,
+ 0
+};
+
+void __init dst_init(void)
+{
+ register_netdevice_notifier(&dst_dev_notifier);
+}
diff --git a/uClinux-2.4.31-uc0/net/core/dv.c b/uClinux-2.4.31-uc0/net/core/dv.c
new file mode 100644
index 0000000..6b152aa
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/dv.c
@@ -0,0 +1,559 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * Generic frame diversion
+ *
+ * Version: @(#)eth.c 0.41 09/09/2000
+ *
+ * Authors:
+ * Benoit LOCHER: initial integration within the kernel with support for ethernet
+ * Dave Miller: improvement on the code (correctness, performance and source files)
+ *
+ */
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <net/dst.h>
+#include <net/arp.h>
+#include <net/sock.h>
+#include <net/ipv6.h>
+#include <net/ip.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/checksum.h>
+#include <linux/divert.h>
+#include <linux/sockios.h>
+
+const char sysctl_divert_version[32]="0.46"; /* Current version */
+
+int __init dv_init(void)
+{
+ printk(KERN_INFO "NET4: Frame Diverter %s\n", sysctl_divert_version);
+ return 0;
+}
+
+/*
+ * Allocate a divert_blk for a device. This must be an ethernet nic.
+ */
+int alloc_divert_blk(struct net_device *dev)
+{
+ int alloc_size = (sizeof(struct divert_blk) + 3) & ~3;
+
+ if (dev->type == ARPHRD_ETHER) {
+ printk(KERN_DEBUG "divert: allocating divert_blk for %s\n",
+ dev->name);
+
+ dev->divert = (struct divert_blk *)
+ kmalloc(alloc_size, GFP_KERNEL);
+ if (dev->divert == NULL) {
+ printk(KERN_DEBUG "divert: unable to allocate divert_blk for %s\n",
+ dev->name);
+ return -ENOMEM;
+ } else {
+ memset(dev->divert, 0, sizeof(struct divert_blk));
+ }
+ dev_hold(dev);
+ } else {
+ printk(KERN_DEBUG "divert: not allocating divert_blk for non-ethernet device %s\n",
+ dev->name);
+
+ dev->divert = NULL;
+ }
+ return 0;
+}
+
+/*
+ * Free a divert_blk allocated by the above function, if it was
+ * allocated on that device.
+ */
+void free_divert_blk(struct net_device *dev)
+{
+ if (dev->divert) {
+ kfree(dev->divert);
+ dev->divert=NULL;
+ dev_put(dev);
+ printk(KERN_DEBUG "divert: freeing divert_blk for %s\n",
+ dev->name);
+ } else {
+ printk(KERN_DEBUG "divert: no divert_blk to free, %s not ethernet\n",
+ dev->name);
+ }
+}
+
+/*
+ * Adds a tcp/udp (source or dest) port to an array
+ */
+int add_port(u16 ports[], u16 port)
+{
+ int i;
+
+ if (port == 0)
+ return -EINVAL;
+
+ /* Storing directly in network format for performance,
+ * thanks Dave :)
+ */
+ port = htons(port);
+
+ for (i = 0; i < MAX_DIVERT_PORTS; i++) {
+ if (ports[i] == port)
+ return -EALREADY;
+ }
+
+ for (i = 0; i < MAX_DIVERT_PORTS; i++) {
+ if (ports[i] == 0) {
+ ports[i] = port;
+ return 0;
+ }
+ }
+
+ return -ENOBUFS;
+}
+
+/*
+ * Removes a port from an array tcp/udp (source or dest)
+ */
+int remove_port(u16 ports[], u16 port)
+{
+ int i;
+
+ if (port == 0)
+ return -EINVAL;
+
+ /* Storing directly in network format for performance,
+ * thanks Dave !
+ */
+ port = htons(port);
+
+ for (i = 0; i < MAX_DIVERT_PORTS; i++) {
+ if (ports[i] == port) {
+ ports[i] = 0;
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/* Some basic sanity checks on the arguments passed to divert_ioctl() */
+int check_args(struct divert_cf *div_cf, struct net_device **dev)
+{
+ char devname[32];
+ int ret;
+
+ if (dev == NULL)
+ return -EFAULT;
+
+ /* GETVERSION: all other args are unused */
+ if (div_cf->cmd == DIVCMD_GETVERSION)
+ return 0;
+
+ /* Network device index should reasonably be between 0 and 1000 :) */
+ if (div_cf->dev_index < 0 || div_cf->dev_index > 1000)
+ return -EINVAL;
+
+ /* Let's try to find the ifname */
+ sprintf(devname, "eth%d", div_cf->dev_index);
+ *dev = dev_get_by_name(devname);
+
+ /* dev should NOT be null */
+ if (*dev == NULL)
+ return -EINVAL;
+
+ ret = 0;
+
+ /* user issuing the ioctl must be a super one :) */
+ if (!capable(CAP_SYS_ADMIN)) {
+ ret = -EPERM;
+ goto out;
+ }
+
+ /* Device must have a divert_blk member NOT null */
+ if ((*dev)->divert == NULL)
+ ret = -EINVAL;
+out:
+ dev_put(*dev);
+ return ret;
+}
+
+/*
+ * control function of the diverter
+ */
+#define DVDBG(a) \
+ printk(KERN_DEBUG "divert_ioctl() line %d %s\n", __LINE__, (a))
+
+int divert_ioctl(unsigned int cmd, struct divert_cf *arg)
+{
+ struct divert_cf div_cf;
+ struct divert_blk *div_blk;
+ struct net_device *dev;
+ int ret;
+
+ switch (cmd) {
+ case SIOCGIFDIVERT:
+ DVDBG("SIOCGIFDIVERT, copy_from_user");
+ if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf)))
+ return -EFAULT;
+ DVDBG("before check_args");
+ ret = check_args(&div_cf, &dev);
+ if (ret)
+ return ret;
+ DVDBG("after checkargs");
+ div_blk = dev->divert;
+
+ DVDBG("befre switch()");
+ switch (div_cf.cmd) {
+ case DIVCMD_GETSTATUS:
+ /* Now, just give the user the raw divert block
+ * for him to play with :)
+ */
+ if (copy_to_user(div_cf.arg1.ptr, dev->divert,
+ sizeof(struct divert_blk)))
+ return -EFAULT;
+ break;
+
+ case DIVCMD_GETVERSION:
+ DVDBG("GETVERSION: checking ptr");
+ if (div_cf.arg1.ptr == NULL)
+ return -EINVAL;
+ DVDBG("GETVERSION: copying data to userland");
+ if (copy_to_user(div_cf.arg1.ptr,
+ sysctl_divert_version, 32))
+ return -EFAULT;
+ DVDBG("GETVERSION: data copied");
+ break;
+
+ default:
+ return -EINVAL;
+ };
+
+ break;
+
+ case SIOCSIFDIVERT:
+ if (copy_from_user(&div_cf, arg, sizeof(struct divert_cf)))
+ return -EFAULT;
+
+ ret = check_args(&div_cf, &dev);
+ if (ret)
+ return ret;
+
+ div_blk = dev->divert;
+
+ switch(div_cf.cmd) {
+ case DIVCMD_RESET:
+ div_blk->divert = 0;
+ div_blk->protos = DIVERT_PROTO_NONE;
+ memset(div_blk->tcp_dst, 0,
+ MAX_DIVERT_PORTS * sizeof(u16));
+ memset(div_blk->tcp_src, 0,
+ MAX_DIVERT_PORTS * sizeof(u16));
+ memset(div_blk->udp_dst, 0,
+ MAX_DIVERT_PORTS * sizeof(u16));
+ memset(div_blk->udp_src, 0,
+ MAX_DIVERT_PORTS * sizeof(u16));
+ return 0;
+
+ case DIVCMD_DIVERT:
+ switch(div_cf.arg1.int32) {
+ case DIVARG1_ENABLE:
+ if (div_blk->divert)
+ return -EALREADY;
+ div_blk->divert = 1;
+ break;
+
+ case DIVARG1_DISABLE:
+ if (!div_blk->divert)
+ return -EALREADY;
+ div_blk->divert = 0;
+ break;
+
+ default:
+ return -EINVAL;
+ };
+
+ break;
+
+ case DIVCMD_IP:
+ switch(div_cf.arg1.int32) {
+ case DIVARG1_ENABLE:
+ if (div_blk->protos & DIVERT_PROTO_IP)
+ return -EALREADY;
+ div_blk->protos |= DIVERT_PROTO_IP;
+ break;
+
+ case DIVARG1_DISABLE:
+ if (!(div_blk->protos & DIVERT_PROTO_IP))
+ return -EALREADY;
+ div_blk->protos &= ~DIVERT_PROTO_IP;
+ break;
+
+ default:
+ return -EINVAL;
+ };
+
+ break;
+
+ case DIVCMD_TCP:
+ switch(div_cf.arg1.int32) {
+ case DIVARG1_ENABLE:
+ if (div_blk->protos & DIVERT_PROTO_TCP)
+ return -EALREADY;
+ div_blk->protos |= DIVERT_PROTO_TCP;
+ break;
+
+ case DIVARG1_DISABLE:
+ if (!(div_blk->protos & DIVERT_PROTO_TCP))
+ return -EALREADY;
+ div_blk->protos &= ~DIVERT_PROTO_TCP;
+ break;
+
+ default:
+ return -EINVAL;
+ };
+
+ break;
+
+ case DIVCMD_TCPDST:
+ switch(div_cf.arg1.int32) {
+ case DIVARG1_ADD:
+ return add_port(div_blk->tcp_dst,
+ div_cf.arg2.uint16);
+
+ case DIVARG1_REMOVE:
+ return remove_port(div_blk->tcp_dst,
+ div_cf.arg2.uint16);
+
+ default:
+ return -EINVAL;
+ };
+
+ break;
+
+ case DIVCMD_TCPSRC:
+ switch(div_cf.arg1.int32) {
+ case DIVARG1_ADD:
+ return add_port(div_blk->tcp_src,
+ div_cf.arg2.uint16);
+
+ case DIVARG1_REMOVE:
+ return remove_port(div_blk->tcp_src,
+ div_cf.arg2.uint16);
+
+ default:
+ return -EINVAL;
+ };
+
+ break;
+
+ case DIVCMD_UDP:
+ switch(div_cf.arg1.int32) {
+ case DIVARG1_ENABLE:
+ if (div_blk->protos & DIVERT_PROTO_UDP)
+ return -EALREADY;
+ div_blk->protos |= DIVERT_PROTO_UDP;
+ break;
+
+ case DIVARG1_DISABLE:
+ if (!(div_blk->protos & DIVERT_PROTO_UDP))
+ return -EALREADY;
+ div_blk->protos &= ~DIVERT_PROTO_UDP;
+ break;
+
+ default:
+ return -EINVAL;
+ };
+
+ break;
+
+ case DIVCMD_UDPDST:
+ switch(div_cf.arg1.int32) {
+ case DIVARG1_ADD:
+ return add_port(div_blk->udp_dst,
+ div_cf.arg2.uint16);
+
+ case DIVARG1_REMOVE:
+ return remove_port(div_blk->udp_dst,
+ div_cf.arg2.uint16);
+
+ default:
+ return -EINVAL;
+ };
+
+ break;
+
+ case DIVCMD_UDPSRC:
+ switch(div_cf.arg1.int32) {
+ case DIVARG1_ADD:
+ return add_port(div_blk->udp_src,
+ div_cf.arg2.uint16);
+
+ case DIVARG1_REMOVE:
+ return remove_port(div_blk->udp_src,
+ div_cf.arg2.uint16);
+
+ default:
+ return -EINVAL;
+ };
+
+ break;
+
+ case DIVCMD_ICMP:
+ switch(div_cf.arg1.int32) {
+ case DIVARG1_ENABLE:
+ if (div_blk->protos & DIVERT_PROTO_ICMP)
+ return -EALREADY;
+ div_blk->protos |= DIVERT_PROTO_ICMP;
+ break;
+
+ case DIVARG1_DISABLE:
+ if (!(div_blk->protos & DIVERT_PROTO_ICMP))
+ return -EALREADY;
+ div_blk->protos &= ~DIVERT_PROTO_ICMP;
+ break;
+
+ default:
+ return -EINVAL;
+ };
+
+ break;
+
+ default:
+ return -EINVAL;
+ };
+
+ break;
+
+ default:
+ return -EINVAL;
+ };
+
+ return 0;
+}
+
+
+/*
+ * Check if packet should have its dest mac address set to the box itself
+ * for diversion
+ */
+
+#define ETH_DIVERT_FRAME(skb) \
+ memcpy(skb->mac.ethernet, skb->dev->dev_addr, ETH_ALEN); \
+ skb->pkt_type=PACKET_HOST
+
+void divert_frame(struct sk_buff *skb)
+{
+ struct ethhdr *eth = skb->mac.ethernet;
+ struct iphdr *iph;
+ struct tcphdr *tcph;
+ struct udphdr *udph;
+ struct divert_blk *divert = skb->dev->divert;
+ int i, src, dst;
+ unsigned char *skb_data_end = skb->data + skb->len;
+
+ /* Packet is already aimed at us, return */
+ if (!memcmp(eth, skb->dev->dev_addr, ETH_ALEN))
+ return;
+
+ /* proto is not IP, do nothing */
+ if (eth->h_proto != htons(ETH_P_IP))
+ return;
+
+ /* Divert all IP frames ? */
+ if (divert->protos & DIVERT_PROTO_IP) {
+ ETH_DIVERT_FRAME(skb);
+ return;
+ }
+
+ /* Check for possible (maliciously) malformed IP frame (thanks Dave) */
+ iph = (struct iphdr *) skb->data;
+ if (((iph->ihl<<2)+(unsigned char*)(iph)) >= skb_data_end) {
+ printk(KERN_INFO "divert: malformed IP packet !\n");
+ return;
+ }
+
+ switch (iph->protocol) {
+ /* Divert all ICMP frames ? */
+ case IPPROTO_ICMP:
+ if (divert->protos & DIVERT_PROTO_ICMP) {
+ ETH_DIVERT_FRAME(skb);
+ return;
+ }
+ break;
+
+ /* Divert all TCP frames ? */
+ case IPPROTO_TCP:
+ if (divert->protos & DIVERT_PROTO_TCP) {
+ ETH_DIVERT_FRAME(skb);
+ return;
+ }
+
+ /* Check for possible (maliciously) malformed IP
+ * frame (thanx Dave)
+ */
+ tcph = (struct tcphdr *)
+ (((unsigned char *)iph) + (iph->ihl<<2));
+ if (((unsigned char *)(tcph+1)) >= skb_data_end) {
+ printk(KERN_INFO "divert: malformed TCP packet !\n");
+ return;
+ }
+
+ /* Divert some tcp dst/src ports only ?*/
+ for (i = 0; i < MAX_DIVERT_PORTS; i++) {
+ dst = divert->tcp_dst[i];
+ src = divert->tcp_src[i];
+ if ((dst && dst == tcph->dest) ||
+ (src && src == tcph->source)) {
+ ETH_DIVERT_FRAME(skb);
+ return;
+ }
+ }
+ break;
+
+ /* Divert all UDP frames ? */
+ case IPPROTO_UDP:
+ if (divert->protos & DIVERT_PROTO_UDP) {
+ ETH_DIVERT_FRAME(skb);
+ return;
+ }
+
+ /* Check for possible (maliciously) malformed IP
+ * packet (thanks Dave)
+ */
+ udph = (struct udphdr *)
+ (((unsigned char *)iph) + (iph->ihl<<2));
+ if (((unsigned char *)(udph+1)) >= skb_data_end) {
+ printk(KERN_INFO
+ "divert: malformed UDP packet !\n");
+ return;
+ }
+
+ /* Divert some udp dst/src ports only ? */
+ for (i = 0; i < MAX_DIVERT_PORTS; i++) {
+ dst = divert->udp_dst[i];
+ src = divert->udp_src[i];
+ if ((dst && dst == udph->dest) ||
+ (src && src == udph->source)) {
+ ETH_DIVERT_FRAME(skb);
+ return;
+ }
+ }
+ break;
+ };
+
+ return;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/core/ethtool.c b/uClinux-2.4.31-uc0/net/core/ethtool.c
new file mode 100644
index 0000000..e140eb3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/ethtool.c
@@ -0,0 +1,797 @@
+/*
+ * net/core/ethtool.c - Ethtool ioctl handler
+ * Copyright (c) 2003 Matthew Wilcox <matthew@wil.cx>
+ *
+ * This file is where we call all the ethtool_ops commands to get
+ * the information ethtool needs. We fall back to calling do_ioctl()
+ * for drivers which haven't been converted to ethtool_ops yet.
+ *
+ * It's GPL, stupid.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/ethtool.h>
+#include <linux/netdevice.h>
+#include <asm/uaccess.h>
+
+/*
+ * Some useful ethtool_ops methods that're device independent.
+ * If we find that all drivers want to do the same thing here,
+ * we can turn these into dev_() function calls.
+ */
+
+u32 ethtool_op_get_link(struct net_device *dev)
+{
+ return netif_carrier_ok(dev) ? 1 : 0;
+}
+
+u32 ethtool_op_get_tx_csum(struct net_device *dev)
+{
+ return (dev->features & NETIF_F_IP_CSUM) != 0;
+}
+
+int ethtool_op_set_tx_csum(struct net_device *dev, u32 data)
+{
+ if (data)
+ dev->features |= NETIF_F_IP_CSUM;
+ else
+ dev->features &= ~NETIF_F_IP_CSUM;
+
+ return 0;
+}
+
+u32 ethtool_op_get_sg(struct net_device *dev)
+{
+ return (dev->features & NETIF_F_SG) != 0;
+}
+
+int ethtool_op_set_sg(struct net_device *dev, u32 data)
+{
+ if (data)
+ dev->features |= NETIF_F_SG;
+ else
+ dev->features &= ~NETIF_F_SG;
+
+ return 0;
+}
+
+#ifdef NETIF_F_TSO
+u32 ethtool_op_get_tso(struct net_device *dev)
+{
+ return (dev->features & NETIF_F_TSO) != 0;
+}
+
+int ethtool_op_set_tso(struct net_device *dev, u32 data)
+{
+ if (data)
+ dev->features |= NETIF_F_TSO;
+ else
+ dev->features &= ~NETIF_F_TSO;
+
+ return 0;
+}
+#endif /* NETIF_F_TSO */
+
+/* Handlers for each ethtool command */
+
+static int ethtool_get_settings(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_cmd cmd = { ETHTOOL_GSET };
+ int err;
+
+ if (!dev->ethtool_ops->get_settings)
+ return -EOPNOTSUPP;
+
+ err = dev->ethtool_ops->get_settings(dev, &cmd);
+ if (err < 0)
+ return err;
+
+ if (copy_to_user(useraddr, &cmd, sizeof(cmd)))
+ return -EFAULT;
+ return 0;
+}
+
+static int ethtool_set_settings(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_cmd cmd;
+
+ if (!dev->ethtool_ops->set_settings)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&cmd, useraddr, sizeof(cmd)))
+ return -EFAULT;
+
+ return dev->ethtool_ops->set_settings(dev, &cmd);
+}
+
+static int ethtool_get_drvinfo(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_drvinfo info;
+ struct ethtool_ops *ops = dev->ethtool_ops;
+
+ if (!ops->get_drvinfo)
+ return -EOPNOTSUPP;
+
+ memset(&info, 0, sizeof(info));
+ info.cmd = ETHTOOL_GDRVINFO;
+ ops->get_drvinfo(dev, &info);
+
+ if (ops->self_test_count)
+ info.testinfo_len = ops->self_test_count(dev);
+ if (ops->get_stats_count)
+ info.n_stats = ops->get_stats_count(dev);
+ if (ops->get_regs_len)
+ info.regdump_len = ops->get_regs_len(dev);
+ if (ops->get_eeprom_len)
+ info.eedump_len = ops->get_eeprom_len(dev);
+
+ if (copy_to_user(useraddr, &info, sizeof(info)))
+ return -EFAULT;
+ return 0;
+}
+
+static int ethtool_get_regs(struct net_device *dev, char __user *useraddr)
+{
+ struct ethtool_regs regs;
+ struct ethtool_ops *ops = dev->ethtool_ops;
+ void *regbuf;
+ int reglen, ret;
+
+ if (!ops->get_regs || !ops->get_regs_len)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&regs, useraddr, sizeof(regs)))
+ return -EFAULT;
+
+ reglen = ops->get_regs_len(dev);
+ if (regs.len > reglen)
+ regs.len = reglen;
+
+ regbuf = kmalloc(reglen, GFP_USER);
+ if (!regbuf)
+ return -ENOMEM;
+
+ ops->get_regs(dev, &regs, regbuf);
+
+ ret = -EFAULT;
+ if (copy_to_user(useraddr, &regs, sizeof(regs)))
+ goto out;
+ useraddr += offsetof(struct ethtool_regs, data);
+ if (copy_to_user(useraddr, regbuf, regs.len))
+ goto out;
+ ret = 0;
+
+ out:
+ kfree(regbuf);
+ return ret;
+}
+
+static int ethtool_get_wol(struct net_device *dev, char __user *useraddr)
+{
+ struct ethtool_wolinfo wol = { ETHTOOL_GWOL };
+
+ if (!dev->ethtool_ops->get_wol)
+ return -EOPNOTSUPP;
+
+ dev->ethtool_ops->get_wol(dev, &wol);
+
+ if (copy_to_user(useraddr, &wol, sizeof(wol)))
+ return -EFAULT;
+ return 0;
+}
+
+static int ethtool_set_wol(struct net_device *dev, char __user *useraddr)
+{
+ struct ethtool_wolinfo wol;
+
+ if (!dev->ethtool_ops->set_wol)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&wol, useraddr, sizeof(wol)))
+ return -EFAULT;
+
+ return dev->ethtool_ops->set_wol(dev, &wol);
+}
+
+static int ethtool_get_msglevel(struct net_device *dev, char __user *useraddr)
+{
+ struct ethtool_value edata = { ETHTOOL_GMSGLVL };
+
+ if (!dev->ethtool_ops->get_msglevel)
+ return -EOPNOTSUPP;
+
+ edata.data = dev->ethtool_ops->get_msglevel(dev);
+
+ if (copy_to_user(useraddr, &edata, sizeof(edata)))
+ return -EFAULT;
+ return 0;
+}
+
+static int ethtool_set_msglevel(struct net_device *dev, char __user *useraddr)
+{
+ struct ethtool_value edata;
+
+ if (!dev->ethtool_ops->set_msglevel)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&edata, useraddr, sizeof(edata)))
+ return -EFAULT;
+
+ dev->ethtool_ops->set_msglevel(dev, edata.data);
+ return 0;
+}
+
+static int ethtool_nway_reset(struct net_device *dev)
+{
+ if (!dev->ethtool_ops->nway_reset)
+ return -EOPNOTSUPP;
+
+ return dev->ethtool_ops->nway_reset(dev);
+}
+
+static int ethtool_get_link(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_value edata = { ETHTOOL_GLINK };
+
+ if (!dev->ethtool_ops->get_link)
+ return -EOPNOTSUPP;
+
+ edata.data = dev->ethtool_ops->get_link(dev);
+
+ if (copy_to_user(useraddr, &edata, sizeof(edata)))
+ return -EFAULT;
+ return 0;
+}
+
+static int ethtool_get_eeprom(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_eeprom eeprom;
+ struct ethtool_ops *ops = dev->ethtool_ops;
+ u8 *data;
+ int ret;
+
+ if (!ops->get_eeprom || !ops->get_eeprom_len)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&eeprom, useraddr, sizeof(eeprom)))
+ return -EFAULT;
+
+ /* Check for wrap and zero */
+ if (eeprom.offset + eeprom.len <= eeprom.offset)
+ return -EINVAL;
+
+ /* Check for exceeding total eeprom len */
+ if (eeprom.offset + eeprom.len > ops->get_eeprom_len(dev))
+ return -EINVAL;
+
+ data = kmalloc(eeprom.len, GFP_USER);
+ if (!data)
+ return -ENOMEM;
+
+ ret = -EFAULT;
+ if (copy_from_user(data, useraddr + sizeof(eeprom), eeprom.len))
+ goto out;
+
+ ret = ops->get_eeprom(dev, &eeprom, data);
+ if (ret)
+ goto out;
+
+ ret = -EFAULT;
+ if (copy_to_user(useraddr, &eeprom, sizeof(eeprom)))
+ goto out;
+ if (copy_to_user(useraddr + sizeof(eeprom), data, eeprom.len))
+ goto out;
+ ret = 0;
+
+ out:
+ kfree(data);
+ return ret;
+}
+
+static int ethtool_set_eeprom(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_eeprom eeprom;
+ struct ethtool_ops *ops = dev->ethtool_ops;
+ u8 *data;
+ int ret;
+
+ if (!ops->set_eeprom || !ops->get_eeprom_len)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&eeprom, useraddr, sizeof(eeprom)))
+ return -EFAULT;
+
+ /* Check for wrap and zero */
+ if (eeprom.offset + eeprom.len <= eeprom.offset)
+ return -EINVAL;
+
+ /* Check for exceeding total eeprom len */
+ if (eeprom.offset + eeprom.len > ops->get_eeprom_len(dev))
+ return -EINVAL;
+
+ data = kmalloc(eeprom.len, GFP_USER);
+ if (!data)
+ return -ENOMEM;
+
+ ret = -EFAULT;
+ if (copy_from_user(data, useraddr + sizeof(eeprom), eeprom.len))
+ goto out;
+
+ ret = ops->set_eeprom(dev, &eeprom, data);
+ if (ret)
+ goto out;
+
+ if (copy_to_user(useraddr + sizeof(eeprom), data, eeprom.len))
+ ret = -EFAULT;
+
+ out:
+ kfree(data);
+ return ret;
+}
+
+static int ethtool_get_coalesce(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_coalesce coalesce = { ETHTOOL_GCOALESCE };
+
+ if (!dev->ethtool_ops->get_coalesce)
+ return -EOPNOTSUPP;
+
+ dev->ethtool_ops->get_coalesce(dev, &coalesce);
+
+ if (copy_to_user(useraddr, &coalesce, sizeof(coalesce)))
+ return -EFAULT;
+ return 0;
+}
+
+static int ethtool_set_coalesce(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_coalesce coalesce;
+
+ if (!dev->ethtool_ops->get_coalesce)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&coalesce, useraddr, sizeof(coalesce)))
+ return -EFAULT;
+
+ return dev->ethtool_ops->set_coalesce(dev, &coalesce);
+}
+
+static int ethtool_get_ringparam(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_ringparam ringparam = { ETHTOOL_GRINGPARAM };
+
+ if (!dev->ethtool_ops->get_ringparam)
+ return -EOPNOTSUPP;
+
+ dev->ethtool_ops->get_ringparam(dev, &ringparam);
+
+ if (copy_to_user(useraddr, &ringparam, sizeof(ringparam)))
+ return -EFAULT;
+ return 0;
+}
+
+static int ethtool_set_ringparam(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_ringparam ringparam;
+
+ if (!dev->ethtool_ops->set_ringparam)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&ringparam, useraddr, sizeof(ringparam)))
+ return -EFAULT;
+
+ return dev->ethtool_ops->set_ringparam(dev, &ringparam);
+}
+
+static int ethtool_get_pauseparam(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_pauseparam pauseparam = { ETHTOOL_GPAUSEPARAM };
+
+ if (!dev->ethtool_ops->get_pauseparam)
+ return -EOPNOTSUPP;
+
+ dev->ethtool_ops->get_pauseparam(dev, &pauseparam);
+
+ if (copy_to_user(useraddr, &pauseparam, sizeof(pauseparam)))
+ return -EFAULT;
+ return 0;
+}
+
+static int ethtool_set_pauseparam(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_pauseparam pauseparam;
+
+ if (!dev->ethtool_ops->get_pauseparam)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&pauseparam, useraddr, sizeof(pauseparam)))
+ return -EFAULT;
+
+ return dev->ethtool_ops->set_pauseparam(dev, &pauseparam);
+}
+
+static int ethtool_get_rx_csum(struct net_device *dev, char __user *useraddr)
+{
+ struct ethtool_value edata = { ETHTOOL_GRXCSUM };
+
+ if (!dev->ethtool_ops->get_rx_csum)
+ return -EOPNOTSUPP;
+
+ edata.data = dev->ethtool_ops->get_rx_csum(dev);
+
+ if (copy_to_user(useraddr, &edata, sizeof(edata)))
+ return -EFAULT;
+ return 0;
+}
+
+static int ethtool_set_rx_csum(struct net_device *dev, char __user *useraddr)
+{
+ struct ethtool_value edata;
+
+ if (!dev->ethtool_ops->set_rx_csum)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&edata, useraddr, sizeof(edata)))
+ return -EFAULT;
+
+ dev->ethtool_ops->set_rx_csum(dev, edata.data);
+ return 0;
+}
+
+static int ethtool_get_tx_csum(struct net_device *dev, char __user *useraddr)
+{
+ struct ethtool_value edata = { ETHTOOL_GTXCSUM };
+
+ if (!dev->ethtool_ops->get_tx_csum)
+ return -EOPNOTSUPP;
+
+ edata.data = dev->ethtool_ops->get_tx_csum(dev);
+
+ if (copy_to_user(useraddr, &edata, sizeof(edata)))
+ return -EFAULT;
+ return 0;
+}
+
+static int ethtool_set_tx_csum(struct net_device *dev, char __user *useraddr)
+{
+ struct ethtool_value edata;
+
+ if (!dev->ethtool_ops->set_tx_csum)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&edata, useraddr, sizeof(edata)))
+ return -EFAULT;
+
+ return dev->ethtool_ops->set_tx_csum(dev, edata.data);
+}
+
+static int ethtool_get_sg(struct net_device *dev, char __user *useraddr)
+{
+ struct ethtool_value edata = { ETHTOOL_GSG };
+
+ if (!dev->ethtool_ops->get_sg)
+ return -EOPNOTSUPP;
+
+ edata.data = dev->ethtool_ops->get_sg(dev);
+
+ if (copy_to_user(useraddr, &edata, sizeof(edata)))
+ return -EFAULT;
+ return 0;
+}
+
+static int ethtool_set_sg(struct net_device *dev, char __user *useraddr)
+{
+ struct ethtool_value edata;
+
+ if (!dev->ethtool_ops->set_sg)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&edata, useraddr, sizeof(edata)))
+ return -EFAULT;
+
+ return dev->ethtool_ops->set_sg(dev, edata.data);
+}
+
+#ifdef NETIF_F_TSO
+static int ethtool_get_tso(struct net_device *dev, char __user *useraddr)
+{
+ struct ethtool_value edata = { ETHTOOL_GTSO };
+
+ if (!dev->ethtool_ops->get_tso)
+ return -EOPNOTSUPP;
+
+ edata.data = dev->ethtool_ops->get_tso(dev);
+
+ if (copy_to_user(useraddr, &edata, sizeof(edata)))
+ return -EFAULT;
+ return 0;
+}
+
+static int ethtool_set_tso(struct net_device *dev, char __user *useraddr)
+{
+ struct ethtool_value edata;
+
+ if (!dev->ethtool_ops->set_tso)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&edata, useraddr, sizeof(edata)))
+ return -EFAULT;
+
+ return dev->ethtool_ops->set_tso(dev, edata.data);
+}
+
+EXPORT_SYMBOL(ethtool_op_get_tso);
+EXPORT_SYMBOL(ethtool_op_set_tso);
+#endif /* NETIF_F_TSO */
+
+static int ethtool_self_test(struct net_device *dev, char __user *useraddr)
+{
+ struct ethtool_test test;
+ struct ethtool_ops *ops = dev->ethtool_ops;
+ u64 *data;
+ int ret;
+
+ if (!ops->self_test || !ops->self_test_count)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&test, useraddr, sizeof(test)))
+ return -EFAULT;
+
+ test.len = ops->self_test_count(dev);
+ data = kmalloc(test.len * sizeof(u64), GFP_USER);
+ if (!data)
+ return -ENOMEM;
+
+ ops->self_test(dev, &test, data);
+
+ ret = -EFAULT;
+ if (copy_to_user(useraddr, &test, sizeof(test)))
+ goto out;
+ useraddr += sizeof(test);
+ if (copy_to_user(useraddr, data, test.len * sizeof(u64)))
+ goto out;
+ ret = 0;
+
+ out:
+ kfree(data);
+ return ret;
+}
+
+static int ethtool_get_strings(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_gstrings gstrings;
+ struct ethtool_ops *ops = dev->ethtool_ops;
+ u8 *data;
+ int ret;
+
+ if (!ops->get_strings)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&gstrings, useraddr, sizeof(gstrings)))
+ return -EFAULT;
+
+ switch (gstrings.string_set) {
+ case ETH_SS_TEST:
+ if (!ops->self_test_count)
+ return -EOPNOTSUPP;
+ gstrings.len = ops->self_test_count(dev);
+ break;
+ case ETH_SS_STATS:
+ if (!ops->get_stats_count)
+ return -EOPNOTSUPP;
+ gstrings.len = ops->get_stats_count(dev);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ data = kmalloc(gstrings.len * ETH_GSTRING_LEN, GFP_USER);
+ if (!data)
+ return -ENOMEM;
+
+ ops->get_strings(dev, gstrings.string_set, data);
+
+ ret = -EFAULT;
+ if (copy_to_user(useraddr, &gstrings, sizeof(gstrings)))
+ goto out;
+ useraddr += sizeof(gstrings);
+ if (copy_to_user(useraddr, data, gstrings.len * ETH_GSTRING_LEN))
+ goto out;
+ ret = 0;
+
+ out:
+ kfree(data);
+ return ret;
+}
+
+static int ethtool_phys_id(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_value id;
+
+ if (!dev->ethtool_ops->phys_id)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&id, useraddr, sizeof(id)))
+ return -EFAULT;
+
+ return dev->ethtool_ops->phys_id(dev, id.data);
+}
+
+static int ethtool_get_stats(struct net_device *dev, void __user *useraddr)
+{
+ struct ethtool_stats stats;
+ struct ethtool_ops *ops = dev->ethtool_ops;
+ u64 *data;
+ int ret;
+
+ if (!ops->get_ethtool_stats || !ops->get_stats_count)
+ return -EOPNOTSUPP;
+
+ if (copy_from_user(&stats, useraddr, sizeof(stats)))
+ return -EFAULT;
+
+ stats.n_stats = ops->get_stats_count(dev);
+ data = kmalloc(stats.n_stats * sizeof(u64), GFP_USER);
+ if (!data)
+ return -ENOMEM;
+
+ ops->get_ethtool_stats(dev, &stats, data);
+
+ ret = -EFAULT;
+ if (copy_to_user(useraddr, &stats, sizeof(stats)))
+ goto out;
+ useraddr += sizeof(stats);
+ if (copy_to_user(useraddr, data, stats.n_stats * sizeof(u64)))
+ goto out;
+ ret = 0;
+
+ out:
+ kfree(data);
+ return ret;
+}
+
+/* The main entry point in this file. Called from net/core/dev.c */
+
+int dev_ethtool(struct ifreq *ifr)
+{
+ struct net_device *dev = __dev_get_by_name(ifr->ifr_name);
+ void __user *useraddr = ifr->ifr_data;
+ u32 ethcmd;
+ int rc;
+
+ /*
+ * XXX: This can be pushed down into the ethtool_* handlers that
+ * need it. Keep existing behaviour for the moment.
+ */
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (!dev || !netif_device_present(dev))
+ return -ENODEV;
+
+ if (!dev->ethtool_ops)
+ goto ioctl;
+
+ if (copy_from_user(&ethcmd, useraddr, sizeof (ethcmd)))
+ return -EFAULT;
+
+ if(dev->ethtool_ops->begin)
+ if ((rc = dev->ethtool_ops->begin(dev)) < 0)
+ return rc;
+
+ switch (ethcmd) {
+ case ETHTOOL_GSET:
+ rc = ethtool_get_settings(dev, useraddr);
+ break;
+ case ETHTOOL_SSET:
+ rc = ethtool_set_settings(dev, useraddr);
+ break;
+ case ETHTOOL_GDRVINFO:
+ rc = ethtool_get_drvinfo(dev, useraddr);
+
+ break;
+ case ETHTOOL_GREGS:
+ rc = ethtool_get_regs(dev, useraddr);
+ break;
+ case ETHTOOL_GWOL:
+ rc = ethtool_get_wol(dev, useraddr);
+ break;
+ case ETHTOOL_SWOL:
+ rc = ethtool_set_wol(dev, useraddr);
+ break;
+ case ETHTOOL_GMSGLVL:
+ rc = ethtool_get_msglevel(dev, useraddr);
+ break;
+ case ETHTOOL_SMSGLVL:
+ rc = ethtool_set_msglevel(dev, useraddr);
+ break;
+ case ETHTOOL_NWAY_RST:
+ rc = ethtool_nway_reset(dev);
+ break;
+ case ETHTOOL_GLINK:
+ rc = ethtool_get_link(dev, useraddr);
+ break;
+ case ETHTOOL_GEEPROM:
+ rc = ethtool_get_eeprom(dev, useraddr);
+ break;
+ case ETHTOOL_SEEPROM:
+ rc = ethtool_set_eeprom(dev, useraddr);
+ break;
+ case ETHTOOL_GCOALESCE:
+ rc = ethtool_get_coalesce(dev, useraddr);
+ break;
+ case ETHTOOL_SCOALESCE:
+ rc = ethtool_set_coalesce(dev, useraddr);
+ break;
+ case ETHTOOL_GRINGPARAM:
+ rc = ethtool_get_ringparam(dev, useraddr);
+ break;
+ case ETHTOOL_SRINGPARAM:
+ rc = ethtool_set_ringparam(dev, useraddr);
+ break;
+ case ETHTOOL_GPAUSEPARAM:
+ rc = ethtool_get_pauseparam(dev, useraddr);
+ break;
+ case ETHTOOL_SPAUSEPARAM:
+ rc = ethtool_set_pauseparam(dev, useraddr);
+ break;
+ case ETHTOOL_GRXCSUM:
+ rc = ethtool_get_rx_csum(dev, useraddr);
+ break;
+ case ETHTOOL_SRXCSUM:
+ rc = ethtool_set_rx_csum(dev, useraddr);
+ break;
+ case ETHTOOL_GTXCSUM:
+ rc = ethtool_get_tx_csum(dev, useraddr);
+ break;
+ case ETHTOOL_STXCSUM:
+ rc = ethtool_set_tx_csum(dev, useraddr);
+ break;
+ case ETHTOOL_GSG:
+ rc = ethtool_get_sg(dev, useraddr);
+ break;
+ case ETHTOOL_SSG:
+ rc = ethtool_set_sg(dev, useraddr);
+ break;
+#ifdef NETIF_F_TSO
+ case ETHTOOL_GTSO:
+ rc = ethtool_get_tso(dev, useraddr);
+ break;
+ case ETHTOOL_STSO:
+ rc = ethtool_set_tso(dev, useraddr);
+ break;
+#endif /* NETIF_F_TSO */
+ case ETHTOOL_TEST:
+ rc = ethtool_self_test(dev, useraddr);
+ break;
+ case ETHTOOL_GSTRINGS:
+ rc = ethtool_get_strings(dev, useraddr);
+ break;
+ case ETHTOOL_PHYS_ID:
+ rc = ethtool_phys_id(dev, useraddr);
+ break;
+ case ETHTOOL_GSTATS:
+ rc = ethtool_get_stats(dev, useraddr);
+ break;
+ default:
+ rc = -EOPNOTSUPP;
+ }
+
+ if(dev->ethtool_ops->complete)
+ dev->ethtool_ops->complete(dev);
+ return rc;
+
+ ioctl:
+ if (dev->do_ioctl)
+ return dev->do_ioctl(dev, ifr, SIOCETHTOOL);
+ return -EOPNOTSUPP;
+}
+
+EXPORT_SYMBOL(dev_ethtool);
+EXPORT_SYMBOL(ethtool_op_get_link);
+EXPORT_SYMBOL(ethtool_op_get_sg);
+EXPORT_SYMBOL(ethtool_op_get_tx_csum);
+EXPORT_SYMBOL(ethtool_op_set_sg);
+EXPORT_SYMBOL(ethtool_op_set_tx_csum);
diff --git a/uClinux-2.4.31-uc0/net/core/filter.c b/uClinux-2.4.31-uc0/net/core/filter.c
new file mode 100644
index 0000000..560e50b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/filter.c
@@ -0,0 +1,497 @@
+/*
+ * Linux Socket Filter - Kernel level socket filtering
+ *
+ * Author:
+ * Jay Schulist <jschlst@samba.org>
+ *
+ * Based on the design of:
+ * - The Berkeley Packet Filter
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Andi Kleen - Fix a few bad bugs and races.
+ */
+
+#include <linux/config.h>
+#if defined(CONFIG_FILTER)
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/fcntl.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_packet.h>
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <linux/errno.h>
+#include <linux/timer.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <linux/filter.h>
+
+/* No hurry in this branch */
+
+static u8 *load_pointer(struct sk_buff *skb, int k)
+{
+ u8 *ptr = NULL;
+
+ if (k>=SKF_NET_OFF)
+ ptr = skb->nh.raw + k - SKF_NET_OFF;
+ else if (k>=SKF_LL_OFF)
+ ptr = skb->mac.raw + k - SKF_LL_OFF;
+
+ if (ptr >= skb->head && ptr < skb->tail)
+ return ptr;
+ return NULL;
+}
+
+/**
+ * sk_run_filter - run a filter on a socket
+ * @skb: buffer to run the filter on
+ * @filter: filter to apply
+ * @flen: length of filter
+ *
+ * Decode and apply filter instructions to the skb->data.
+ * Return length to keep, 0 for none. skb is the data we are
+ * filtering, filter is the array of filter instructions, and
+ * len is the number of filter blocks in the array.
+ */
+
+int sk_run_filter(struct sk_buff *skb, struct sock_filter *filter, int flen)
+{
+ unsigned char *data = skb->data;
+ /* len is UNSIGNED. Byte wide insns relies only on implicit
+ type casts to prevent reading arbitrary memory locations.
+ */
+ unsigned int len = skb->len-skb->data_len;
+ struct sock_filter *fentry; /* We walk down these */
+ u32 A = 0; /* Accumulator */
+ u32 X = 0; /* Index Register */
+ u32 mem[BPF_MEMWORDS]; /* Scratch Memory Store */
+ int k;
+ int pc;
+
+ /*
+ * Process array of filter instructions.
+ */
+
+ for(pc = 0; pc < flen; pc++)
+ {
+ fentry = &filter[pc];
+
+ switch(fentry->code)
+ {
+ case BPF_ALU|BPF_ADD|BPF_X:
+ A += X;
+ continue;
+
+ case BPF_ALU|BPF_ADD|BPF_K:
+ A += fentry->k;
+ continue;
+
+ case BPF_ALU|BPF_SUB|BPF_X:
+ A -= X;
+ continue;
+
+ case BPF_ALU|BPF_SUB|BPF_K:
+ A -= fentry->k;
+ continue;
+
+ case BPF_ALU|BPF_MUL|BPF_X:
+ A *= X;
+ continue;
+
+ case BPF_ALU|BPF_MUL|BPF_K:
+ A *= fentry->k;
+ continue;
+
+ case BPF_ALU|BPF_DIV|BPF_X:
+ if(X == 0)
+ return (0);
+ A /= X;
+ continue;
+
+ case BPF_ALU|BPF_DIV|BPF_K:
+ if(fentry->k == 0)
+ return (0);
+ A /= fentry->k;
+ continue;
+
+ case BPF_ALU|BPF_AND|BPF_X:
+ A &= X;
+ continue;
+
+ case BPF_ALU|BPF_AND|BPF_K:
+ A &= fentry->k;
+ continue;
+
+ case BPF_ALU|BPF_OR|BPF_X:
+ A |= X;
+ continue;
+
+ case BPF_ALU|BPF_OR|BPF_K:
+ A |= fentry->k;
+ continue;
+
+ case BPF_ALU|BPF_LSH|BPF_X:
+ A <<= X;
+ continue;
+
+ case BPF_ALU|BPF_LSH|BPF_K:
+ A <<= fentry->k;
+ continue;
+
+ case BPF_ALU|BPF_RSH|BPF_X:
+ A >>= X;
+ continue;
+
+ case BPF_ALU|BPF_RSH|BPF_K:
+ A >>= fentry->k;
+ continue;
+
+ case BPF_ALU|BPF_NEG:
+ A = -A;
+ continue;
+
+ case BPF_JMP|BPF_JA:
+ pc += fentry->k;
+ continue;
+
+ case BPF_JMP|BPF_JGT|BPF_K:
+ pc += (A > fentry->k) ? fentry->jt : fentry->jf;
+ continue;
+
+ case BPF_JMP|BPF_JGE|BPF_K:
+ pc += (A >= fentry->k) ? fentry->jt : fentry->jf;
+ continue;
+
+ case BPF_JMP|BPF_JEQ|BPF_K:
+ pc += (A == fentry->k) ? fentry->jt : fentry->jf;
+ continue;
+
+ case BPF_JMP|BPF_JSET|BPF_K:
+ pc += (A & fentry->k) ? fentry->jt : fentry->jf;
+ continue;
+
+ case BPF_JMP|BPF_JGT|BPF_X:
+ pc += (A > X) ? fentry->jt : fentry->jf;
+ continue;
+
+ case BPF_JMP|BPF_JGE|BPF_X:
+ pc += (A >= X) ? fentry->jt : fentry->jf;
+ continue;
+
+ case BPF_JMP|BPF_JEQ|BPF_X:
+ pc += (A == X) ? fentry->jt : fentry->jf;
+ continue;
+
+ case BPF_JMP|BPF_JSET|BPF_X:
+ pc += (A & X) ? fentry->jt : fentry->jf;
+ continue;
+
+ case BPF_LD|BPF_W|BPF_ABS:
+ k = fentry->k;
+load_w:
+ if(k >= 0 && (unsigned int)(k+sizeof(u32)) <= len) {
+ A = ntohl(*(u32*)&data[k]);
+ continue;
+ }
+ if (k<0) {
+ u8 *ptr;
+
+ if (k>=SKF_AD_OFF)
+ break;
+ if ((ptr = load_pointer(skb, k)) != NULL) {
+ A = ntohl(*(u32*)ptr);
+ continue;
+ }
+ } else {
+ u32 tmp;
+ if (!skb_copy_bits(skb, k, &tmp, 4)) {
+ A = ntohl(tmp);
+ continue;
+ }
+ }
+ return 0;
+
+ case BPF_LD|BPF_H|BPF_ABS:
+ k = fentry->k;
+load_h:
+ if(k >= 0 && (unsigned int) (k + sizeof(u16)) <= len) {
+ A = ntohs(*(u16*)&data[k]);
+ continue;
+ }
+ if (k<0) {
+ u8 *ptr;
+
+ if (k>=SKF_AD_OFF)
+ break;
+ if ((ptr = load_pointer(skb, k)) != NULL) {
+ A = ntohs(*(u16*)ptr);
+ continue;
+ }
+ } else {
+ u16 tmp;
+ if (!skb_copy_bits(skb, k, &tmp, 2)) {
+ A = ntohs(tmp);
+ continue;
+ }
+ }
+ return 0;
+
+ case BPF_LD|BPF_B|BPF_ABS:
+ k = fentry->k;
+load_b:
+ if(k >= 0 && (unsigned int)k < len) {
+ A = data[k];
+ continue;
+ }
+ if (k<0) {
+ u8 *ptr;
+
+ if (k>=SKF_AD_OFF)
+ break;
+ if ((ptr = load_pointer(skb, k)) != NULL) {
+ A = *ptr;
+ continue;
+ }
+ } else {
+ u8 tmp;
+ if (!skb_copy_bits(skb, k, &tmp, 1)) {
+ A = tmp;
+ continue;
+ }
+ }
+ return 0;
+
+ case BPF_LD|BPF_W|BPF_LEN:
+ A = len;
+ continue;
+
+ case BPF_LDX|BPF_W|BPF_LEN:
+ X = len;
+ continue;
+
+ case BPF_LD|BPF_W|BPF_IND:
+ k = X + fentry->k;
+ goto load_w;
+
+ case BPF_LD|BPF_H|BPF_IND:
+ k = X + fentry->k;
+ goto load_h;
+
+ case BPF_LD|BPF_B|BPF_IND:
+ k = X + fentry->k;
+ goto load_b;
+
+ case BPF_LDX|BPF_B|BPF_MSH:
+ if(fentry->k >= len)
+ return (0);
+ X = (data[fentry->k] & 0xf) << 2;
+ continue;
+
+ case BPF_LD|BPF_IMM:
+ A = fentry->k;
+ continue;
+
+ case BPF_LDX|BPF_IMM:
+ X = fentry->k;
+ continue;
+
+ case BPF_LD|BPF_MEM:
+ A = mem[fentry->k];
+ continue;
+
+ case BPF_LDX|BPF_MEM:
+ X = mem[fentry->k];
+ continue;
+
+ case BPF_MISC|BPF_TAX:
+ X = A;
+ continue;
+
+ case BPF_MISC|BPF_TXA:
+ A = X;
+ continue;
+
+ case BPF_RET|BPF_K:
+ return ((unsigned int)fentry->k);
+
+ case BPF_RET|BPF_A:
+ return ((unsigned int)A);
+
+ case BPF_ST:
+ mem[fentry->k] = A;
+ continue;
+
+ case BPF_STX:
+ mem[fentry->k] = X;
+ continue;
+
+ default:
+ /* Invalid instruction counts as RET */
+ return (0);
+ }
+
+ /* Handle ancillary data, which are impossible
+ (or very difficult) to get parsing packet contents.
+ */
+ switch (k-SKF_AD_OFF) {
+ case SKF_AD_PROTOCOL:
+ A = htons(skb->protocol);
+ continue;
+ case SKF_AD_PKTTYPE:
+ A = skb->pkt_type;
+ continue;
+ case SKF_AD_IFINDEX:
+ A = skb->dev->ifindex;
+ continue;
+ default:
+ return 0;
+ }
+ }
+
+ return (0);
+}
+
+/**
+ * sk_chk_filter - verify socket filter code
+ * @filter: filter to verify
+ * @flen: length of filter
+ *
+ * Check the user's filter code. If we let some ugly
+ * filter code slip through kaboom! The filter must contain
+ * no references or jumps that are out of range, no illegal instructions
+ * and no backward jumps. It must end with a RET instruction
+ *
+ * Returns 0 if the rule set is legal or a negative errno code if not.
+ */
+
+int sk_chk_filter(struct sock_filter *filter, int flen)
+{
+ struct sock_filter *ftest;
+ int pc;
+
+ if ((unsigned int) flen >= (~0U / sizeof(struct sock_filter)))
+ return -EINVAL;
+
+ /*
+ * Check the filter code now.
+ */
+ for(pc = 0; pc < flen; pc++)
+ {
+ /*
+ * All jumps are forward as they are not signed
+ */
+
+ ftest = &filter[pc];
+ if(BPF_CLASS(ftest->code) == BPF_JMP)
+ {
+ /*
+ * But they mustn't jump off the end.
+ */
+ if(BPF_OP(ftest->code) == BPF_JA)
+ {
+ /* Note, the large ftest->k might cause
+ loops. Compare this with conditional
+ jumps below, where offsets are limited. --ANK (981016)
+ */
+ if (ftest->k >= (unsigned)(flen-pc-1))
+ return -EINVAL;
+ }
+ else
+ {
+ /*
+ * For conditionals both must be safe
+ */
+ if(pc + ftest->jt +1 >= flen || pc + ftest->jf +1 >= flen)
+ return -EINVAL;
+ }
+ }
+
+ /*
+ * Check that memory operations use valid addresses.
+ */
+
+ if (ftest->k >= BPF_MEMWORDS)
+ {
+ /*
+ * But it might not be a memory operation...
+ */
+ switch (ftest->code) {
+ case BPF_ST:
+ case BPF_STX:
+ case BPF_LD|BPF_MEM:
+ case BPF_LDX|BPF_MEM:
+ return -EINVAL;
+ }
+ }
+ }
+
+ /*
+ * The program must end with a return. We don't care where they
+ * jumped within the script (its always forwards) but in the
+ * end they _will_ hit this.
+ */
+
+ return (BPF_CLASS(filter[flen - 1].code) == BPF_RET)?0:-EINVAL;
+}
+
+/**
+ * sk_attach_filter - attach a socket filter
+ * @fprog: the filter program
+ * @sk: the socket to use
+ *
+ * Attach the user's filter code. We first run some sanity checks on
+ * it to make sure it does not explode on us later. If an error
+ * occurs or there is insufficient memory for the filter a negative
+ * errno code is returned. On success the return is zero.
+ */
+
+int sk_attach_filter(struct sock_fprog *fprog, struct sock *sk)
+{
+ struct sk_filter *fp;
+ unsigned int fsize = sizeof(struct sock_filter) * fprog->len;
+ int err;
+
+ /* Make sure new filter is there and in the right amounts. */
+ if (fprog->filter == NULL || fprog->len > BPF_MAXINSNS)
+ return (-EINVAL);
+
+ fp = (struct sk_filter *)sock_kmalloc(sk, fsize+sizeof(*fp), GFP_KERNEL);
+ if(fp == NULL)
+ return (-ENOMEM);
+
+ if (copy_from_user(fp->insns, fprog->filter, fsize)) {
+ sock_kfree_s(sk, fp, fsize+sizeof(*fp));
+ return -EFAULT;
+ }
+
+ atomic_set(&fp->refcnt, 1);
+ fp->len = fprog->len;
+
+ if ((err = sk_chk_filter(fp->insns, fp->len))==0) {
+ struct sk_filter *old_fp;
+
+ spin_lock_bh(&sk->lock.slock);
+ old_fp = sk->filter;
+ sk->filter = fp;
+ spin_unlock_bh(&sk->lock.slock);
+ fp = old_fp;
+ }
+
+ if (fp)
+ sk_filter_release(sk, fp);
+
+ return (err);
+}
+#endif /* CONFIG_FILTER */
diff --git a/uClinux-2.4.31-uc0/net/core/iovec.c b/uClinux-2.4.31-uc0/net/core/iovec.c
new file mode 100644
index 0000000..4c701b5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/iovec.c
@@ -0,0 +1,279 @@
+/*
+ * iovec manipulation routines.
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Fixes:
+ * Andrew Lunn : Errors in iovec copying.
+ * Pedro Roque : Added memcpy_fromiovecend and
+ * csum_..._fromiovecend.
+ * Andi Kleen : fixed error handling for 2.1
+ * Alexey Kuznetsov: 2.1 optimisations
+ * Andi Kleen : Fix csum*fromiovecend for IPv6.
+ */
+
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/net.h>
+#include <linux/in6.h>
+#include <asm/uaccess.h>
+#include <asm/byteorder.h>
+#include <net/checksum.h>
+#include <net/sock.h>
+
+/*
+ * Verify iovec. The caller must ensure that the iovec is big enough
+ * to hold the message iovec.
+ *
+ * Save time not doing verify_area. copy_*_user will make this work
+ * in any case.
+ */
+
+int verify_iovec(struct msghdr *m, struct iovec *iov, char *address, int mode)
+{
+ int size, err, ct;
+
+ if(m->msg_namelen)
+ {
+ if(mode==VERIFY_READ)
+ {
+ err=move_addr_to_kernel(m->msg_name, m->msg_namelen, address);
+ if(err<0)
+ goto out;
+ }
+
+ m->msg_name = address;
+ } else
+ m->msg_name = NULL;
+
+ err = -EFAULT;
+ size = m->msg_iovlen * sizeof(struct iovec);
+ if (copy_from_user(iov, m->msg_iov, size))
+ goto out;
+ m->msg_iov=iov;
+
+ for (err = 0, ct = 0; ct < m->msg_iovlen; ct++) {
+ err += iov[ct].iov_len;
+ /* Goal is not to verify user data, but to prevent returning
+ negative value, which is interpreted as errno.
+ Overflow is still possible, but it is harmless.
+ */
+ if (err < 0)
+ return -EMSGSIZE;
+ }
+out:
+ return err;
+}
+
+/*
+ * Copy kernel to iovec. Returns -EFAULT on error.
+ *
+ * Note: this modifies the original iovec.
+ */
+
+int memcpy_toiovec(struct iovec *iov, unsigned char *kdata, int len)
+{
+ int err = -EFAULT;
+
+ while(len>0)
+ {
+ if(iov->iov_len)
+ {
+ int copy = min_t(unsigned int, iov->iov_len, len);
+ if (copy_to_user(iov->iov_base, kdata, copy))
+ goto out;
+ kdata+=copy;
+ len-=copy;
+ iov->iov_len-=copy;
+ iov->iov_base+=copy;
+ }
+ iov++;
+ }
+ err = 0;
+out:
+ return err;
+}
+
+/*
+ * In kernel copy to iovec. Returns -EFAULT on error.
+ *
+ * Note: this modifies the original iovec.
+ */
+
+void memcpy_tokerneliovec(struct iovec *iov, unsigned char *kdata, int len)
+{
+ while(len>0)
+ {
+ if(iov->iov_len)
+ {
+ int copy = min_t(unsigned int, iov->iov_len, len);
+ memcpy(iov->iov_base, kdata, copy);
+ kdata+=copy;
+ len-=copy;
+ iov->iov_len-=copy;
+ iov->iov_base+=copy;
+ }
+ iov++;
+ }
+}
+
+
+/*
+ * Copy iovec to kernel. Returns -EFAULT on error.
+ *
+ * Note: this modifies the original iovec.
+ */
+
+int memcpy_fromiovec(unsigned char *kdata, struct iovec *iov, int len)
+{
+ int err = -EFAULT;
+
+ while(len>0)
+ {
+ if(iov->iov_len)
+ {
+ int copy = min_t(unsigned int, len, iov->iov_len);
+ if (copy_from_user(kdata, iov->iov_base, copy))
+ goto out;
+ len-=copy;
+ kdata+=copy;
+ iov->iov_base+=copy;
+ iov->iov_len-=copy;
+ }
+ iov++;
+ }
+ err = 0;
+out:
+ return err;
+}
+
+
+/*
+ * For use with ip_build_xmit
+ */
+
+int memcpy_fromiovecend(unsigned char *kdata, struct iovec *iov, int offset,
+ int len)
+{
+ int err = -EFAULT;
+
+ /* Skip over the finished iovecs */
+ while(offset >= iov->iov_len)
+ {
+ offset -= iov->iov_len;
+ iov++;
+ }
+
+ while (len > 0)
+ {
+ u8 *base = iov->iov_base + offset;
+ int copy = min_t(unsigned int, len, iov->iov_len - offset);
+
+ offset = 0;
+ if (copy_from_user(kdata, base, copy))
+ goto out;
+ len -= copy;
+ kdata += copy;
+ iov++;
+ }
+ err = 0;
+out:
+ return err;
+}
+
+/*
+ * And now for the all-in-one: copy and checksum from a user iovec
+ * directly to a datagram
+ * Calls to csum_partial but the last must be in 32 bit chunks
+ *
+ * ip_build_xmit must ensure that when fragmenting only the last
+ * call to this function will be unaligned also.
+ */
+
+int csum_partial_copy_fromiovecend(unsigned char *kdata, struct iovec *iov,
+ int offset, unsigned int len, int *csump)
+{
+ int csum = *csump;
+ int partial_cnt = 0, err = 0;
+
+ /* Skip over the finished iovecs */
+ while (offset >= iov->iov_len)
+ {
+ offset -= iov->iov_len;
+ iov++;
+ }
+
+ while (len > 0)
+ {
+ u8 *base = iov->iov_base + offset;
+ int copy = min_t(unsigned int, len, iov->iov_len - offset);
+
+ offset = 0;
+ /* There is a remnant from previous iov. */
+ if (partial_cnt)
+ {
+ int par_len = 4 - partial_cnt;
+
+ /* iov component is too short ... */
+ if (par_len > copy) {
+ if (copy_from_user(kdata, base, copy))
+ goto out_fault;
+ kdata += copy;
+ base += copy;
+ partial_cnt += copy;
+ len -= copy;
+ iov++;
+ if (len)
+ continue;
+ *csump = csum_partial(kdata - partial_cnt,
+ partial_cnt, csum);
+ goto out;
+ }
+ if (copy_from_user(kdata, base, par_len))
+ goto out_fault;
+ csum = csum_partial(kdata - partial_cnt, 4, csum);
+ kdata += par_len;
+ base += par_len;
+ copy -= par_len;
+ len -= par_len;
+ partial_cnt = 0;
+ }
+
+ if (len > copy)
+ {
+ partial_cnt = copy % 4;
+ if (partial_cnt)
+ {
+ copy -= partial_cnt;
+ if (copy_from_user(kdata + copy, base + copy,
+ partial_cnt))
+ goto out_fault;
+ }
+ }
+
+ if (copy) {
+ csum = csum_and_copy_from_user(base, kdata, copy,
+ csum, &err);
+ if (err)
+ goto out;
+ }
+ len -= copy + partial_cnt;
+ kdata += copy + partial_cnt;
+ iov++;
+ }
+ *csump = csum;
+out:
+ return err;
+
+out_fault:
+ err = -EFAULT;
+ goto out;
+}
diff --git a/uClinux-2.4.31-uc0/net/core/neighbour.c b/uClinux-2.4.31-uc0/net/core/neighbour.c
new file mode 100644
index 0000000..814314f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/neighbour.c
@@ -0,0 +1,2280 @@
+/* $USAGI: neighbour.c,v 1.40 2005/01/19 17:22:32 yoshfuji Exp $ */
+
+/*
+ * Generic address resolution entity
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ * Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Fixes:
+ * Vitaly E. Lavrov releasing NULL neighbor in neigh_add.
+ * Harald Welte Add neighbour cache statistics like rtstat
+ * Harald Welte port neighbour cache rework from 2.6.9-rcX
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/socket.h>
+#include <linux/sched.h>
+#include <linux/netdevice.h>
+#include <linux/proc_fs.h>
+#ifdef CONFIG_SYSCTL
+#include <linux/sysctl.h>
+#endif
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/sock.h>
+#include <linux/rtnetlink.h>
+#include <linux/random.h>
+#include <linux/module.h>
+
+#ifdef CONFIG_NET_NEIGH_DEBUG
+#define NEIGH_DEBUG 3
+#else
+#define NEIGH_DEBUG 1
+#endif
+
+#define NEIGH_PRINTK(x...) printk(x)
+#define NEIGH_NOPRINTK(x...) do { ; } while(0)
+#define NEIGH_PRINTK0 NEIGH_PRINTK
+#define NEIGH_PRINTK1 NEIGH_NOPRINTK
+#define NEIGH_PRINTK2 NEIGH_NOPRINTK
+#define NEIGH_PRINTK3 NEIGH_NOPRINTK
+
+#if NEIGH_DEBUG >= 1
+#undef NEIGH_PRINTK1
+#define NEIGH_PRINTK1 NEIGH_PRINTK
+#endif
+#if NEIGH_DEBUG >= 2
+#undef NEIGH_PRINTK2
+#define NEIGH_PRINTK2 NEIGH_PRINTK
+#endif
+#if NEIGH_DEBUG >= 3
+#undef NEIGH_PRINTK3
+#define NEIGH_PRINTK3 NEIGH_PRINTK
+#endif
+
+#define PNEIGH_HASHMASK 0xF
+
+static void neigh_timer_handler(unsigned long arg);
+static int pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev);
+void neigh_changeaddr(struct neigh_table *tbl, struct net_device *dev);
+
+static int neigh_glbl_allocs;
+static struct neigh_table *neigh_tables;
+static struct file_operations neigh_stat_seq_fops;
+
+/*
+ Neighbour hash table buckets are protected with rwlock tbl->lock.
+
+ - All the scans/updates to hash buckets MUST be made under this lock.
+ - NOTHING clever should be made under this lock: no callbacks
+ to protocol backends, no attempts to send something to network.
+ It will result in deadlocks, if backend/driver wants to use neighbour
+ cache.
+ - If the entry requires some non-trivial actions, increase
+ its reference count and release table lock.
+
+ Neighbour entries are protected:
+ - with reference count.
+ - with rwlock neigh->lock
+
+ Reference count prevents destruction.
+
+ neigh->lock mainly serializes ll address data and its validity state.
+ However, the same lock is used to protect another entry fields:
+ - timer
+ - resolution queue
+
+ Again, nothing clever shall be made under neigh->lock,
+ the most complicated procedure, which we allow is dev->hard_header.
+ It is supposed, that dev->hard_header is simplistic and does
+ not make callbacks to neighbour tables.
+
+ The last lock is neigh_tbl_lock. It is pure SMP lock, protecting
+ list of neighbour tables. This list is used only in process context,
+ */
+
+static rwlock_t neigh_tbl_lock = RW_LOCK_UNLOCKED;
+
+static int neigh_blackhole(struct sk_buff *skb)
+{
+ kfree_skb(skb);
+ return -ENETDOWN;
+}
+
+/*
+ * It is random distribution in the interval (1/2)*base...(3/2)*base.
+ * It corresponds to default IPv6 settings and is not overridable,
+ * because it is really reasonable choice.
+ */
+
+unsigned long neigh_rand_reach_time(unsigned long base)
+{
+ return (base ? (net_random() % base) + (base >> 1) : 0);
+}
+
+
+static int neigh_forced_gc(struct neigh_table *tbl)
+{
+ int shrunk = 0;
+ int i;
+
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_foced_gc(tbl=%p)\n", tbl);
+ NEIGH_CACHE_STAT_INC(tbl, forced_gc_runs);
+
+ write_lock_bh(&tbl->lock);
+ for (i = 0; i <= tbl->hash_mask; i++) {
+ struct neighbour *n, **np;
+
+ np = &tbl->hash_buckets[i];
+ while ((n = *np) != NULL) {
+ /* Neighbour record may be discarded if:
+ * - nobody refers to it.
+ * - it is not permanent
+ */
+ write_lock(&n->lock);
+ if (atomic_read(&n->refcnt) == 1 &&
+ !(n->nud_state&NUD_PERMANENT)) {
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: %s (dead, neigh_forced_gc)\n",
+ n, neigh_state(n->nud_state));
+ *np = n->next;
+ n->dead = 1;
+ shrunk = 1;
+ write_unlock(&n->lock);
+ neigh_release(n);
+ continue;
+ }
+ write_unlock(&n->lock);
+ np = &n->next;
+ }
+ }
+
+ tbl->last_flush = jiffies;
+
+ write_unlock_bh(&tbl->lock);
+
+ return shrunk;
+}
+
+static int neigh_del_timer(struct neighbour *n)
+{
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_del_timer(n=%p)\n", n);
+ if (n->nud_state & NUD_IN_TIMER) {
+ if (del_timer(&n->timer)) {
+ neigh_release(n);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void pneigh_queue_purge(struct sk_buff_head *list)
+{
+ struct sk_buff *skb;
+
+ NEIGH_PRINTK3(KERN_DEBUG "pneigh_queue_purge(list=%p)\n", list);
+
+ while ((skb = skb_dequeue(list)) != NULL) {
+ dev_put(skb->dev);
+ kfree_skb(skb);
+ }
+}
+
+void neigh_changeaddr(struct neigh_table *tbl, struct net_device *dev)
+{
+ int i;
+
+ write_lock_bh(&tbl->lock);
+
+ for (i=0; i <= tbl->hash_mask; i++) {
+ struct neighbour *n, **np;
+
+ np = &tbl->hash_buckets[i];
+ while ((n = *np) != NULL) {
+ if (dev && n->dev != dev) {
+ np = &n->next;
+ continue;
+ }
+ *np = n->next;
+ write_lock_bh(&n->lock);
+ n->dead = 1;
+ neigh_del_timer(n);
+ write_unlock_bh(&n->lock);
+ neigh_release(n);
+ }
+ }
+
+ write_unlock_bh(&tbl->lock);
+}
+
+int neigh_ifdown(struct neigh_table *tbl, struct net_device *dev)
+{
+ int i;
+
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_ifdown(tbl=%p, dev=%p(%s))\n",
+ tbl, dev, dev?dev->name:"<null>");
+
+ write_lock_bh(&tbl->lock);
+
+ for (i = 0; i <= tbl->hash_mask; i++) {
+ struct neighbour *n, **np;
+
+ np = &tbl->hash_buckets[i];
+ while ((n = *np) != NULL) {
+ if (dev && n->dev != dev) {
+ np = &n->next;
+ continue;
+ }
+ *np = n->next;
+ write_lock(&n->lock);
+ neigh_del_timer(n);
+ n->dead = 1;
+
+ if (atomic_read(&n->refcnt) != 1) {
+ /* The most unpleasant situation.
+ We must destroy neighbour entry,
+ but someone still uses it.
+
+ The destroy will be delayed until
+ the last user releases us, but
+ we must kill timers etc. and move
+ it to safe state.
+ */
+ n->parms = &tbl->parms;
+ skb_queue_purge(&n->arp_queue);
+ n->output = neigh_blackhole;
+ if (n->nud_state&NUD_VALID)
+ n->nud_state = NUD_NOARP;
+ else
+ n->nud_state = NUD_NONE;
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: ->%s (stray, neigh_ifdown).\n",
+ n, neigh_state(n->nud_state));
+ }
+ write_unlock(&n->lock);
+ neigh_release(n);
+ }
+ }
+
+ pneigh_ifdown(tbl, dev);
+ write_unlock_bh(&tbl->lock);
+
+ del_timer_sync(&tbl->proxy_timer);
+ pneigh_queue_purge(&tbl->proxy_queue);
+ return 0;
+}
+
+static struct neighbour *neigh_alloc(struct neigh_table *tbl)
+{
+ struct neighbour *n;
+ unsigned long now = jiffies;
+
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_alloc(tbl=%p)\n", tbl);
+
+ if (atomic_read(&tbl->entries) > tbl->gc_thresh3 ||
+ (atomic_read(&tbl->entries) > tbl->gc_thresh2 &&
+ now - tbl->last_flush > 5*HZ)) {
+ if (neigh_forced_gc(tbl) == 0 &&
+ atomic_read(&tbl->entries) > tbl->gc_thresh3) {
+ NEIGH_PRINTK1(KERN_WARNING "neigh_alloc(): neighbour table flood for neigh_table %p\n", tbl);
+ return NULL;
+ }
+ }
+
+ n = kmem_cache_alloc(tbl->kmem_cachep, SLAB_ATOMIC);
+ if (n == NULL) {
+ NEIGH_PRINTK1(KERN_WARNING "neigh_alloc(): kmem_cache_alloc() failed for neigh_table %p\n", tbl);
+ return NULL;
+ }
+
+ memset(n, 0, tbl->entry_size);
+
+ skb_queue_head_init(&n->arp_queue);
+ n->lock = RW_LOCK_UNLOCKED;
+ n->updated = n->used = now;
+ n->nud_state = NUD_NONE;
+ n->output = neigh_blackhole;
+ n->parms = &tbl->parms;
+ init_timer(&n->timer);
+ n->timer.function = neigh_timer_handler;
+ n->timer.data = (unsigned long)n;
+ NEIGH_CACHE_STAT_INC(tbl, allocs);
+ neigh_glbl_allocs++;
+ atomic_inc(&tbl->entries);
+ n->tbl = tbl;
+ atomic_set(&n->refcnt, 1);
+ n->dead = 1;
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: NUD_NONE (neigh_alloc).\n", n);
+ return n;
+}
+
+static struct neighbour **neigh_hash_alloc(unsigned int entries)
+{
+ unsigned long size = entries * sizeof(struct neighbour *);
+ struct neighbour **ret;
+
+ if (size <= PAGE_SIZE) {
+ ret = kmalloc(size, GFP_ATOMIC);
+ } else {
+ ret = (struct neighbour **)
+ __get_free_pages(GFP_ATOMIC, get_order(size));
+ }
+ if (ret)
+ memset(ret, 0, size);
+
+ return ret;
+}
+
+static void neigh_hash_free(struct neighbour **hash, unsigned int entries)
+{
+ unsigned long size = entries * sizeof(struct neighbour *);
+
+ if (size <= PAGE_SIZE)
+ kfree(hash);
+ else
+ free_pages((unsigned long)hash, get_order(size));
+}
+
+static void neigh_hash_grow(struct neigh_table *tbl, unsigned long new_entries)
+{
+ struct neighbour **new_hash, **old_hash;
+ unsigned int i, new_hash_mask, old_entries;
+
+ NEIGH_CACHE_STAT_INC(tbl, hash_grows);
+
+ BUG_ON(new_entries & (new_entries - 1));
+ new_hash = neigh_hash_alloc(new_entries);
+ if (!new_hash)
+ return;
+
+ old_entries = tbl->hash_mask + 1;
+ new_hash_mask = new_entries - 1;
+ old_hash = tbl->hash_buckets;
+
+ get_random_bytes(&tbl->hash_rnd, sizeof(tbl->hash_rnd));
+ for (i = 0; i < old_entries; i++) {
+ struct neighbour *n, *next;
+
+ for (n = old_hash[i]; n; n = next) {
+ unsigned int hash_val = tbl->hash(n->primary_key, n->dev);
+
+ hash_val &= new_hash_mask;
+ next = n->next;
+
+ n->next = new_hash[hash_val];
+ new_hash[hash_val] = n;
+ }
+ }
+ tbl->hash_buckets = new_hash;
+ tbl->hash_mask = new_hash_mask;
+
+ neigh_hash_free(old_hash, old_entries);
+}
+
+struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey,
+ struct net_device *dev)
+{
+ struct neighbour *n;
+ int key_len = tbl->key_len;
+ u32 hash_val = tbl->hash(pkey, dev) & tbl->hash_mask;
+
+ NEIGH_CACHE_STAT_INC(tbl, lookups);
+
+ read_lock_bh(&tbl->lock);
+ for (n = tbl->hash_buckets[hash_val]; n; n = n->next) {
+ if (dev == n->dev &&
+ memcmp(n->primary_key, pkey, key_len) == 0) {
+ neigh_hold(n);
+ NEIGH_CACHE_STAT_INC(tbl, hits);
+ break;
+ }
+ }
+ read_unlock_bh(&tbl->lock);
+ return n;
+}
+
+struct neighbour *neigh_lookup_nodev(struct neigh_table *tbl, const void *pkey)
+{
+ struct neighbour *n;
+ int key_len = tbl->key_len;
+ u32 hash_val = tbl->hash(pkey, NULL) & tbl->hash_mask;
+
+ NEIGH_CACHE_STAT_INC(tbl, lookups);
+
+ read_lock_bh(&tbl->lock);
+ for (n = tbl->hash_buckets[hash_val]; n; n = n->next) {
+ if (!memcmp(n->primary_key, pkey, key_len)) {
+ neigh_hold(n);
+ NEIGH_CACHE_STAT_INC(tbl, hits);
+ break;
+ }
+ }
+ read_unlock_bh(&tbl->lock);
+ return n;
+}
+
+struct neighbour * neigh_create(struct neigh_table *tbl, const void *pkey,
+ struct net_device *dev)
+{
+ struct neighbour *n, *n1;
+ u32 hash_val;
+ int key_len = tbl->key_len;
+ int error;
+
+ NEIGH_PRINTK3("neigh_create(tbl=%p, pkey=%p, dev=%p(%s))\n",
+ tbl, pkey, dev, dev?dev->name:"<null>");
+
+ n = neigh_alloc(tbl);
+ if (n == NULL)
+ return ERR_PTR(-ENOBUFS);
+
+ memcpy(n->primary_key, pkey, key_len);
+ n->dev = dev;
+ dev_hold(dev);
+
+ /* Protocol specific setup. */
+ if (tbl->constructor && (error = tbl->constructor(n)) < 0) {
+ neigh_release(n);
+ return ERR_PTR(error);
+ }
+
+ /* Device specific setup. */
+ if (n->parms->neigh_setup &&
+ (error = n->parms->neigh_setup(n)) < 0) {
+ neigh_release(n);
+ return ERR_PTR(error);
+ }
+
+ n->confirmed = jiffies - (n->parms->base_reachable_time<<1);
+
+ write_lock_bh(&tbl->lock);
+ if (atomic_read(&tbl->entries) > (tbl->hash_mask + 1))
+ neigh_hash_grow(tbl, (tbl->hash_mask + 1) << 1);
+
+ hash_val = tbl->hash(pkey, dev) & tbl->hash_mask;
+
+ for (n1 = tbl->hash_buckets[hash_val]; n1; n1 = n1->next) {
+ if (dev == n1->dev &&
+ memcmp(n1->primary_key, pkey, key_len) == 0) {
+ neigh_hold(n1);
+ write_unlock_bh(&tbl->lock);
+ neigh_release(n);
+ return n1;
+ }
+ }
+
+ n->next = tbl->hash_buckets[hash_val];
+ tbl->hash_buckets[hash_val] = n;
+ n->dead = 0;
+ neigh_hold(n);
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: %s (neigh_create).\n", n, neigh_state(n->nud_state));
+ write_unlock_bh(&tbl->lock);
+ return n;
+}
+
+struct pneigh_entry * pneigh_lookup(struct neigh_table *tbl, const void *pkey,
+ struct net_device *dev, int creat)
+{
+ struct pneigh_entry *n;
+ u32 hash_val;
+ int key_len = tbl->key_len;
+
+ hash_val = *(u32*)(pkey + key_len - 4);
+ hash_val ^= (hash_val>>16);
+ hash_val ^= hash_val>>8;
+ hash_val ^= hash_val>>4;
+ hash_val &= PNEIGH_HASHMASK;
+
+ read_lock_bh(&tbl->lock);
+
+ for (n = tbl->phash_buckets[hash_val]; n; n = n->next) {
+ if (memcmp(n->key, pkey, key_len) == 0 &&
+ (n->dev == dev || !n->dev)) {
+ read_unlock_bh(&tbl->lock);
+ return n;
+ }
+ }
+ read_unlock_bh(&tbl->lock);
+ if (!creat)
+ return NULL;
+
+#ifdef USE_IPV6_MOBILITY
+ n = kmalloc(sizeof(*n) + key_len, pneigh_alloc_flag());
+#else
+ n = kmalloc(sizeof(*n) + key_len,
+ (in_interrupt() ? GFP_ATOMIC : GFP_KERNEL));
+#endif
+
+
+ if (n == NULL)
+ return NULL;
+
+#ifdef USE_IPV6_MOBILITY
+ pneigh_refcnt_init(n);
+#endif
+
+ memcpy(n->key, pkey, key_len);
+ n->dev = dev;
+
+ if (tbl->pconstructor && tbl->pconstructor(n)) {
+ kfree(n);
+ return NULL;
+ }
+
+ write_lock_bh(&tbl->lock);
+ n->next = tbl->phash_buckets[hash_val];
+ tbl->phash_buckets[hash_val] = n;
+ write_unlock_bh(&tbl->lock);
+ return n;
+}
+
+
+int pneigh_delete(struct neigh_table *tbl, const void *pkey, struct net_device *dev)
+{
+ struct pneigh_entry *n, **np;
+ u32 hash_val;
+ int key_len = tbl->key_len;
+
+ NEIGH_PRINTK3(KERN_DEBUG "pneigh_delete(tbl=%p, pkey=%p, dev=%p(%s))\n",
+ tbl, pkey, dev, dev?dev->name:"<null>");
+
+ hash_val = *(u32*)(pkey + key_len - 4);
+ hash_val ^= (hash_val>>16);
+ hash_val ^= hash_val>>8;
+ hash_val ^= hash_val>>4;
+ hash_val &= PNEIGH_HASHMASK;
+
+ write_lock_bh(&tbl->lock);
+ for (np = &tbl->phash_buckets[hash_val]; (n=*np) != NULL; np = &n->next) {
+ if (memcmp(n->key, pkey, key_len) == 0 && n->dev == dev) {
+#ifdef USE_IPV6_MOBILITY
+ if (!pneigh_refcnt_dec_and_test(n)) {
+ write_unlock_bh(&tbl->lock);
+ return 0;
+ }
+#endif
+ *np = n->next;
+ write_unlock_bh(&tbl->lock);
+ if (tbl->pdestructor)
+ tbl->pdestructor(n);
+ kfree(n);
+ return 0;
+ }
+ }
+ write_unlock_bh(&tbl->lock);
+ return -ENOENT;
+}
+
+static int pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev)
+{
+ struct pneigh_entry *n, **np;
+ u32 h;
+
+ NEIGH_PRINTK3(KERN_DEBUG "pneigh_ifdown(tbl=%p, dev=%p(%s))\n",
+ tbl, dev, dev?dev->name:"<null>");
+
+ for (h=0; h<=PNEIGH_HASHMASK; h++) {
+ np = &tbl->phash_buckets[h];
+ while ((n=*np) != NULL) {
+ if (n->dev == dev || dev == NULL) {
+ *np = n->next;
+ if (tbl->pdestructor)
+ tbl->pdestructor(n);
+ kfree(n);
+ continue;
+ }
+ np = &n->next;
+ }
+ }
+ return -ENOENT;
+}
+
+
+/*
+ * neighbour must already be out of the table;
+ *
+ */
+void neigh_destroy(struct neighbour *neigh)
+{
+ struct hh_cache *hh;
+
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_destroy(neigh=%p)\n", neigh);
+ NEIGH_CACHE_STAT_INC(neigh->tbl, destroys);
+
+ if (!neigh->dead) {
+ NEIGH_PRINTK1(KERN_WARNING
+ "neigh_destroy(): destroying alive neighbour %p\n",
+ neigh);
+ dump_stack();
+ return;
+ }
+
+ if (neigh_del_timer(neigh))
+ NEIGH_PRINTK1(KERN_WARNING "neigh_destroy: impossible event; timer was alive.\n");
+
+ while ((hh = neigh->hh) != NULL) {
+ neigh->hh = hh->hh_next;
+ hh->hh_next = NULL;
+ write_lock_bh(&hh->hh_lock);
+ hh->hh_output = neigh_blackhole;
+ write_unlock_bh(&hh->hh_lock);
+ if (atomic_dec_and_test(&hh->hh_refcnt))
+ kfree(hh);
+ }
+
+ if (neigh->ops && neigh->ops->destructor)
+ (neigh->ops->destructor)(neigh);
+
+ skb_queue_purge(&neigh->arp_queue);
+
+ dev_put(neigh->dev);
+
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: (destroyed)\n", neigh);
+
+ neigh_glbl_allocs--;
+ atomic_dec(&neigh->tbl->entries);
+ kmem_cache_free(neigh->tbl->kmem_cachep, neigh);
+}
+
+/* Neighbour state is suspicious;
+ disable fast path.
+
+ Called with write_locked neigh.
+ */
+static void neigh_suspect(struct neighbour *neigh)
+{
+ struct hh_cache *hh;
+
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_suspect(neigh=%p)\n", neigh);
+
+ neigh->output = neigh->ops->output;
+
+ for (hh = neigh->hh; hh; hh = hh->hh_next)
+ hh->hh_output = neigh->ops->output;
+}
+
+/* Neighbour state is OK;
+ enable fast path.
+
+ Called with write_locked neigh.
+ */
+static void neigh_connect(struct neighbour *neigh)
+{
+ struct hh_cache *hh;
+
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_connect(neigh=%p)\n", neigh);
+
+ neigh->output = neigh->ops->connected_output;
+
+ for (hh = neigh->hh; hh; hh = hh->hh_next)
+ hh->hh_output = neigh->ops->hh_output;
+}
+
+static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg)
+{
+ struct neigh_table *tbl = (struct neigh_table*)arg;
+ struct neighbour *n, **np;
+ unsigned long expire, now = jiffies;
+
+#ifdef CONFIG_SMP
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_periodic_timer tasklet(tbl=%p)\n", tbl);
+#else
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_periodic_timer(tbl=%p)\n", tbl);
+#endif
+ NEIGH_CACHE_STAT_INC(tbl, periodic_gc_runs);
+
+ write_lock_bh(&tbl->lock);
+
+ /*
+ * periodicly recompute ReachableTime from random function
+ */
+
+ if (now - tbl->last_rand > 300*HZ) {
+ struct neigh_parms *p;
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_periodic_timer(): re-generating reachable_time\n");
+ tbl->last_rand = now;
+ for (p=&tbl->parms; p; p = p->next)
+ p->reachable_time = neigh_rand_reach_time(p->base_reachable_time);
+ }
+
+ np = &tbl->hash_buckets[tbl->hash_chain_gc];
+ tbl->hash_chain_gc = ((tbl->hash_chain_gc + 1) & tbl->hash_mask);
+
+ while ((n = *np) != NULL) {
+ unsigned int state;
+
+ write_lock_bh(&n->lock);
+
+ state = n->nud_state;
+ if (state == NUD_NONE ||
+ (state&(NUD_PERMANENT|NUD_IN_TIMER))) {
+ write_unlock_bh(&n->lock);
+ goto next_elt;
+ }
+
+ /* STALE,FAILED,NOARP */
+ if ((long)(n->used - n->confirmed) < 0)
+ n->used = n->confirmed;
+
+ if (atomic_read(&n->refcnt) == 1 &&
+ ((state&NUD_FAILED) ||
+ (now - n->used > n->parms->gc_staletime))) {
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: %s (dead, neigh_periodic_timer)\n",
+ n, neigh_state(state));
+ *np = n->next;
+ n->dead = 1;
+ write_unlock_bh(&n->lock);
+ neigh_release(n);
+ continue;
+ }
+ write_unlock_bh(&n->lock);
+
+next_elt:
+ np = &n->next;
+ }
+
+ /* Cycle through all hash buckets every base_reachable_time/2 ticks.
+ * ARP entry timeouts range from 1/2 base_reachable_time to 3/2
+ * base_reachable_time.
+ */
+ expire = tbl->parms.base_reachable_time >> 1;
+ expire /= (tbl->hash_mask + 1);
+ if (!expire)
+ expire = 1;
+
+ mod_timer(&tbl->gc_timer, now + expire);
+ write_unlock_bh(&tbl->lock);
+}
+
+#ifdef CONFIG_SMP
+static void neigh_periodic_timer(unsigned long arg)
+{
+ struct neigh_table *tbl = (struct neigh_table*)arg;
+
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_periodic_timer(tbl=%p)\n", tbl);
+ tasklet_schedule(&tbl->gc_task);
+}
+#endif
+
+static __inline__ int neigh_max_probes(struct neighbour *n)
+{
+ struct neigh_parms *p = n->parms;
+ return ((n->nud_state & NUD_PROBE) ?
+ p->ucast_probes :
+ p->ucast_probes + p->app_probes + p->mcast_probes);
+}
+
+
+/* Called when a timer expires for a neighbour entry. */
+
+static void neigh_timer_handler(unsigned long arg)
+{
+ unsigned long now, next;
+ struct neighbour *neigh = (struct neighbour*)arg;
+ unsigned state;
+
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_timer_handler(neigh=%p)\n", neigh);
+
+ write_lock_bh(&neigh->lock);
+ now = jiffies;
+ next = now + HZ;
+
+ state = neigh->nud_state;
+
+ if (!(state&NUD_IN_TIMER)) {
+ /* impossible */
+ write_unlock_bh(&neigh->lock);
+ NEIGH_PRINTK1(KERN_CRIT "neigh_timer_handler(): timer & !nud_in_timer\n");
+ return;
+ }
+
+ if (state&NUD_REACHABLE) {
+ if (now - neigh->confirmed < neigh->parms->reachable_time) {
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: REACHABLE (neigh_timer_handler).\n",
+ neigh);
+ next = neigh->confirmed + neigh->parms->reachable_time;
+ } else if (now - neigh->used <= neigh->parms->delay_probe_time) {
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: REACHABLE->DELAY (neigh_timer_handler).\n",
+ neigh);
+ neigh->nud_state = NUD_DELAY;
+ neigh_suspect(neigh);
+ next = now + neigh->parms->delay_probe_time;
+ } else {
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: REACHABLE->STALE (neigh_timer_handler).\n",
+ neigh);
+ neigh->nud_state = NUD_STALE;
+ neigh_suspect(neigh);
+ }
+ } else if (state&NUD_DELAY) {
+ if (now - neigh->confirmed <= neigh->parms->delay_probe_time) {
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: DELAY->REACHABLE (neigh_timer_handler).\n",
+ neigh);
+ neigh->nud_state = NUD_REACHABLE;
+ neigh_connect(neigh);
+ next = neigh->confirmed + neigh->parms->reachable_time;
+ } else {
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: DELAY->PROBE (neigh_timer_handler).\n",
+ neigh);
+ neigh->nud_state = NUD_PROBE;
+ atomic_set(&neigh->probes, 0);
+ next = now + neigh->parms->retrans_time;
+ }
+ } else {
+ /* PROBE,INCOMPLETE */
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: %s (neigh_timer_handler).\n",
+ neigh, neigh_state(state));
+ next = now + neigh->parms->retrans_time;
+ }
+
+ if (neigh->nud_state&(NUD_INCOMPLETE|NUD_PROBE)) {
+ if (atomic_read(&neigh->probes) >= neigh_max_probes(neigh)) {
+ struct sk_buff *skb;
+
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: %s->FAILED (neigh_timer_handler).\n",
+ neigh, neigh_state(neigh->nud_state));
+ neigh->updated = now;
+ neigh->nud_state = NUD_FAILED;
+
+ del_timer(&neigh->timer); /* release neigh later */
+
+ NEIGH_CACHE_STAT_INC(neigh->tbl, res_failed);
+
+ /* It is very thin place. report_unreachable is very complicated
+ routine. Particularly, it can hit the same neighbour entry!
+
+ So that, we try to be accurate and avoid dead loop. --ANK
+ */
+
+ /*
+ neigh_periodic_timer() should not release this entry
+ while processing. -- yoshfuji
+ */
+ neigh_hold(neigh);
+
+ while(neigh->nud_state==NUD_FAILED && (skb=__skb_dequeue(&neigh->arp_queue)) != NULL) {
+ write_unlock_bh(&neigh->lock);
+ neigh->ops->error_report(neigh, skb);
+ write_lock_bh(&neigh->lock);
+ }
+ skb_queue_purge(&neigh->arp_queue); /* XXX: !FAILED case? */
+#ifdef CONFIG_ARPD
+ if (neigh->parms->app_probes) {
+ write_unlock_bh(&neigh->lock);
+ neigh_app_notify(neigh);
+ } else {
+ write_unlock_bh(&neigh->lock);
+ }
+#else
+ write_unlock_bh(&neigh->lock);
+#endif
+ neigh_release(neigh);
+ neigh_release(neigh); /* timer */
+ return;
+ }
+ }
+
+ if (neigh->nud_state&NUD_IN_TIMER) {
+ neigh_hold(neigh); /* don't release while processing */
+ if (time_before(next, jiffies + HZ/2))
+ next = jiffies + HZ/2;
+ mod_timer(&neigh->timer, next);
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_timer_handler(neigh=%p): next=%lu (%lu sec)\n",
+ neigh, next, (next-jiffies)/HZ);
+ if (neigh->nud_state&(NUD_INCOMPLETE|NUD_PROBE)) {
+ struct sk_buff *skb = skb_peek(&neigh->arp_queue);
+ if (skb)
+ skb_get(skb);
+ write_unlock_bh(&neigh->lock);
+ neigh->ops->solicit(neigh, skb_peek(&neigh->arp_queue));
+ atomic_inc(&neigh->probes); /* XXX: race */
+ if (skb)
+ kfree_skb(skb);
+ } else {
+ write_unlock_bh(&neigh->lock);
+ }
+ } else {
+ del_timer(&neigh->timer);
+ write_unlock_bh(&neigh->lock);
+ }
+ neigh_release(neigh);
+}
+
+int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
+{
+ int ret = 0;
+ unsigned long now = jiffies;
+
+ NEIGH_PRINTK3(KERN_DEBUG "__neigh_event_send(neigh=%p, skb=%p)\n", neigh, skb);
+ if (neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE)) {
+ /* PERMANENT,NOARP,REACHABLE,DELAY,PROBE */
+ goto out;
+ }
+ /* INCOMPLETE,STALE,FAILED,NONE */
+ if (!(neigh->nud_state&(NUD_STALE|NUD_INCOMPLETE))) {
+ /* FAILED,NONE */
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: %s->INCOMPLETE (neigh_event_send).\n",
+ neigh, neigh_state(neigh->nud_state));
+ neigh_hold(neigh);
+ atomic_set(&neigh->probes, neigh->parms->ucast_probes);
+ neigh->nud_state = NUD_INCOMPLETE;
+ neigh->timer.expires = jiffies;
+ add_timer(&neigh->timer);
+ } else if (neigh->nud_state == NUD_STALE) {
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: STALE->DELAY (neigh_event_send).\n",
+ neigh);
+ neigh_hold(neigh);
+ neigh->nud_state = NUD_DELAY;
+ neigh->timer.expires = now + neigh->parms->delay_probe_time;
+ add_timer(&neigh->timer);
+ }
+ if (neigh->nud_state == NUD_INCOMPLETE) {
+ if (skb) {
+ if (skb_queue_len(&neigh->arp_queue) >= neigh->parms->queue_len) {
+ struct sk_buff *buff;
+ buff = neigh->arp_queue.next;
+ __skb_unlink(buff, &neigh->arp_queue);
+ kfree_skb(buff);
+ }
+ __skb_queue_tail(&neigh->arp_queue, skb);
+ }
+ ret = 1;
+ }
+out:
+ return ret;
+}
+
+static __inline__ void neigh_update_hhs(struct neighbour *neigh)
+{
+ struct hh_cache *hh;
+ void (*update)(struct hh_cache*, struct net_device*, unsigned char*) =
+ neigh->dev->header_cache_update;
+
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_update_hhs(neigh=%p)\n", neigh);
+
+ if (update) {
+ for (hh=neigh->hh; hh; hh=hh->hh_next) {
+ write_lock_bh(&hh->hh_lock);
+ update(hh, neigh->dev, neigh->ha);
+ write_unlock_bh(&hh->hh_lock);
+ }
+ }
+}
+
+
+/* Generic update routine.
+ -- lladdr is new lladdr or NULL, if it is not supplied.
+ -- new is new state.
+ -- flags specifies details of update
+
+ Caller MUST hold reference count on the entry.
+ __neigh_update() is called under write_lock_bh().
+
+ */
+
+int __neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, u32 flags)
+{
+ u8 old;
+ int err;
+ int notify = 0;
+ struct net_device *dev;
+ unsigned long now = jiffies;
+ int hold = 0;
+ int update_isrouter = 0;
+
+ if (!neigh) {
+ NEIGH_PRINTK1(KERN_WARNING "__neigh_update(): neigh==NULL\n");
+ return -EINVAL;
+ }
+
+ old = neigh->nud_state;
+
+ NEIGH_PRINTK3(KERN_DEBUG "__neigh_update(neigh=%p, lladdr=%p, new=%u(%s), flags=%08x): old=%u(%s)\n",
+ neigh, lladdr, new, neigh_state(new), flags,
+ old, neigh_state(old));
+
+ dev = neigh->dev;
+ if (!dev) {
+ NEIGH_PRINTK1(KERN_WARNING "__neigh_update(): neigh->dev==NULL\n");
+ return -EINVAL;
+ }
+
+ err = -EPERM;
+ if (!(flags&NEIGH_UPDATE_F_ADMIN) && (old&(NUD_NOARP|NUD_PERMANENT)))
+ goto out;
+
+ if (!(new&NUD_VALID)) {
+ /* NONE,INCOMPLETE,FAILED */
+ neigh_del_timer(neigh);
+ if (old&NUD_CONNECTED)
+ neigh_suspect(neigh);
+ neigh->nud_state = new;
+ err = 0;
+ notify = old&NUD_VALID;
+ goto out;
+ }
+
+ /* Compare new lladdr with cached one */
+ if (dev->addr_len == 0) {
+ /* First case: device needs no address. */
+ lladdr = neigh->ha;
+ } else if (lladdr) {
+ /* The second case: if something is already cached
+ and a new address is proposed:
+ - compare new & old
+ - if they are different, check override flag
+ */
+ if (old&NUD_VALID) {
+ if (memcmp(lladdr, neigh->ha, dev->addr_len) == 0)
+ lladdr = neigh->ha;
+ }
+ } else {
+ /* No address is supplied; if we know something,
+ use it, otherwise discard the request.
+ */
+ err = -EINVAL;
+ if (!(old&NUD_VALID))
+ goto out;
+ lladdr = neigh->ha;
+ }
+
+ /* If entry was valid and address is not changed,
+ do not change entry state, if new one is STALE.
+ */
+ err = 0;
+ if (old&NUD_VALID) {
+ if (lladdr != neigh->ha &&
+ !(flags&NEIGH_UPDATE_F_OVERRIDE)) {
+ if ((flags&NEIGH_UPDATE_F_SUSPECT_CONNECTED) &&
+ (old&NUD_CONNECTED)) {
+ new = NUD_STALE;
+ lladdr = neigh->ha;
+ } else {
+ goto out;
+ }
+ } else {
+ if ((flags&NEIGH_UPDATE_F_REUSEADDR) &&
+ new == old)
+ lladdr = neigh->ha;
+ else if (lladdr == neigh->ha && new == NUD_STALE) {
+ if ((flags&NEIGH_UPDATE_F_REUSESUSPECTSTATE) ||
+ (old&NUD_CONNECTED))
+ new = old;
+ }
+ update_isrouter = flags&NEIGH_UPDATE_F_OVERRIDE_VALID_ISROUTER;
+ }
+ } else {
+ /* INCOMPLETE */
+ update_isrouter = flags&NEIGH_UPDATE_F_OVERRIDE_VALID_ISROUTER;
+ }
+
+ if (new != old) {
+ if (new&NUD_IN_TIMER) {
+ unsigned long next = now;
+ switch(new) {
+ case NUD_REACHABLE:
+ next += neigh->parms->reachable_time;
+ break;
+ default:;
+ /*XXX*/
+ }
+ if (old&NUD_IN_TIMER) {
+ mod_timer(&neigh->timer, next);
+ } else {
+ neigh_hold(neigh);
+ neigh->timer.expires = next;
+ add_timer(&neigh->timer);
+ }
+ } else {
+ neigh_del_timer(neigh);
+ }
+ neigh->nud_state = new;
+ }
+ if ((new != old || lladdr != neigh->ha) &&
+ new&NUD_CONNECTED)
+ neigh->confirmed = now;
+ if (lladdr != neigh->ha) {
+ neigh->updated = now;
+ memcpy(&neigh->ha, lladdr, dev->addr_len);
+ neigh_update_hhs(neigh);
+ if (!(new&NUD_CONNECTED))
+ neigh->confirmed = now - (neigh->parms->base_reachable_time<<1);
+ notify = 1;
+ }
+ if (new == old)
+ goto out;
+ if (new&NUD_CONNECTED)
+ neigh_connect(neigh);
+ else
+ neigh_suspect(neigh);
+ if (!(old&NUD_VALID)) {
+ struct sk_buff *skb;
+
+ neigh_hold(neigh); /* don't release neigh while processing */
+ hold = 1;
+
+ if (new&NUD_VALID)
+ notify = 1;
+
+ /* Again: avoid dead loop if something went wrong */
+ while (neigh->nud_state&NUD_VALID &&
+ (skb=__skb_dequeue(&neigh->arp_queue)) != NULL) {
+ struct neighbour *n1 = neigh;
+ write_unlock_bh(&neigh->lock);
+ /* On shaper/eql skb->dst->neighbour != neigh :( */
+ if (skb->dst && skb->dst->neighbour)
+ n1 = skb->dst->neighbour;
+ n1->output(skb);
+ write_lock_bh(&neigh->lock);
+ }
+ skb_queue_purge(&neigh->arp_queue);
+ }
+out:
+ if (update_isrouter) {
+ neigh->flags = (flags&NEIGH_UPDATE_F_ISROUTER) ?
+ (neigh->flags|NTF_ROUTER) :
+ (neigh->flags&~NTF_ROUTER);
+ }
+ NEIGH_PRINTK3(KERN_DEBUG "neigh %p: %s->%s (neigh_update).\n",
+ neigh, neigh_state(old), neigh_state(neigh->nud_state));
+ if (hold)
+ neigh_release(neigh);
+ return err ? err : notify;
+}
+
+int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, int override, int arp)
+{
+ int update;
+ write_lock_bh(&neigh->lock);
+ neigh_hold(neigh);
+ update = __neigh_update(neigh, lladdr, new,
+ NEIGH_UPDATE_F_REUSEADDR|
+ (override ? NEIGH_UPDATE_F_OVERRIDE : 0) |
+ (arp ? 0 : NEIGH_UPDATE_F_ADMIN));
+#ifdef CONFIG_ARPD
+ if (update > 0 && neigh->parms->app_probes) {
+ write_unlock_bh(&neigh->lock);
+ neigh_app_notify(neigh);
+ } else {
+ write_unlock_bh(&neigh->lock);
+ }
+#else
+ write_unlock_bh(&neigh->lock);
+#endif
+ neigh_release(neigh); /*XXX: may invalidate neigh... */
+ return update >= 0 ? 0 : update;
+}
+
+struct neighbour * neigh_event_ns(struct neigh_table *tbl,
+ u8 *lladdr, void *saddr,
+ struct net_device *dev)
+{
+ struct neighbour *neigh;
+
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_event_ns(tbl=%p, lladdr=%p, saddr=%p, dev=%p(%s))\n",
+ tbl, lladdr, saddr, dev, dev?dev->name:"<null>");
+
+ /* neighbour status should be changed to NUD_STALE
+ if lladdr != neigh->ha
+ */
+ neigh = __neigh_lookup(tbl, saddr, dev, lladdr || !dev->addr_len);
+
+ if (neigh)
+ neigh_update(neigh, lladdr, NUD_STALE, 1, 1);
+ return neigh;
+}
+
+static void neigh_hh_init(struct neighbour *n, struct dst_entry *dst, u16 protocol)
+{
+ struct hh_cache *hh = NULL;
+ struct net_device *dev = dst->dev;
+
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_hh_init(n=%p, dst=%p, protocol=%u)\n",
+ n, dst, protocol);
+
+ for (hh=n->hh; hh; hh = hh->hh_next)
+ if (hh->hh_type == protocol)
+ break;
+
+ if (!hh && (hh = kmalloc(sizeof(*hh), GFP_ATOMIC)) != NULL) {
+ memset(hh, 0, sizeof(struct hh_cache));
+ hh->hh_lock = RW_LOCK_UNLOCKED;
+ hh->hh_type = protocol;
+ atomic_set(&hh->hh_refcnt, 0);
+ hh->hh_next = NULL;
+ if (dev->hard_header_cache(n, hh)) {
+ kfree(hh);
+ hh = NULL;
+ } else {
+ atomic_inc(&hh->hh_refcnt);
+ hh->hh_next = n->hh;
+ n->hh = hh;
+ if (n->nud_state&NUD_CONNECTED)
+ hh->hh_output = n->ops->hh_output;
+ else
+ hh->hh_output = n->ops->output;
+ }
+ }
+ if (hh) {
+ atomic_inc(&hh->hh_refcnt);
+ dst->hh = hh;
+ }
+}
+
+/* This function can be used in contexts, where only old dev_queue_xmit
+ worked, f.e. if you want to override normal output path (eql, shaper),
+ but resolution is not made yet.
+ */
+
+int neigh_compat_output(struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+
+ __skb_pull(skb, skb->nh.raw - skb->data);
+
+ if (dev->hard_header &&
+ dev->hard_header(skb, dev, ntohs(skb->protocol), NULL, NULL, skb->len) < 0 &&
+ dev->rebuild_header(skb))
+ return 0;
+
+ return dev_queue_xmit(skb);
+}
+
+/* Slow and careful. */
+
+int neigh_resolve_output(struct sk_buff *skb)
+{
+ struct dst_entry *dst = NULL;
+ struct neighbour *neigh = NULL;
+
+ if (!skb)
+ goto discard;
+ dst = skb->dst;
+ if (!dst || !(neigh = dst->neighbour))
+ goto discard;
+
+ __skb_pull(skb, skb->nh.raw - skb->data);
+
+ if (neigh_event_send(neigh, skb) == 0) {
+ int err;
+ struct net_device *dev = neigh->dev;
+ if (dev->hard_header_cache && dst->hh == NULL) {
+ write_lock_bh(&neigh->lock);
+ if (dst->hh == NULL)
+ neigh_hh_init(neigh, dst, dst->ops->protocol);
+ err = dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len);
+ write_unlock_bh(&neigh->lock);
+ } else {
+ read_lock_bh(&neigh->lock);
+ err = dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len);
+ read_unlock_bh(&neigh->lock);
+ }
+ if (err >= 0)
+ return neigh->ops->queue_xmit(skb);
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+ return 0;
+
+discard:
+ NEIGH_PRINTK1("neigh_resolve_output(skb=%p): dst=%p neigh=%p\n", skb, dst, neigh);
+ if (skb)
+ kfree_skb(skb);
+ return -EINVAL;
+}
+
+/* As fast as possible without hh cache */
+
+int neigh_connected_output(struct sk_buff *skb)
+{
+ int err;
+ struct dst_entry *dst = skb->dst;
+ struct neighbour *neigh = dst->neighbour;
+ struct net_device *dev = neigh->dev;
+
+ __skb_pull(skb, skb->nh.raw - skb->data);
+
+ read_lock_bh(&neigh->lock);
+ err = dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len);
+ read_unlock_bh(&neigh->lock);
+ if (err >= 0)
+ return neigh->ops->queue_xmit(skb);
+ kfree_skb(skb);
+ return -EINVAL;
+}
+
+static void neigh_proxy_process(unsigned long arg)
+{
+ struct neigh_table *tbl = (struct neigh_table *)arg;
+ long sched_next = 0;
+ unsigned long now = jiffies;
+ struct sk_buff *skb;
+
+ spin_lock(&tbl->proxy_queue.lock);
+
+ skb = tbl->proxy_queue.next;
+
+ while (skb != (struct sk_buff*)&tbl->proxy_queue) {
+ struct sk_buff *back = skb;
+ long tdif = back->stamp.tv_usec - now;
+
+ skb = skb->next;
+ if (tdif <= 0) {
+ struct net_device *dev = back->dev;
+ __skb_unlink(back, &tbl->proxy_queue);
+ if (tbl->proxy_redo && netif_running(dev))
+ tbl->proxy_redo(back);
+ else
+ kfree_skb(back);
+
+ dev_put(dev);
+ } else if (!sched_next || tdif < sched_next)
+ sched_next = tdif;
+ }
+ del_timer(&tbl->proxy_timer);
+ if (sched_next)
+ mod_timer(&tbl->proxy_timer, jiffies + sched_next);
+ spin_unlock(&tbl->proxy_queue.lock);
+}
+
+void pneigh_enqueue(struct neigh_table *tbl, struct neigh_parms *p,
+ struct sk_buff *skb)
+{
+ unsigned long now = jiffies;
+ long sched_next = net_random()%p->proxy_delay;
+
+ if (tbl->proxy_queue.qlen > p->proxy_qlen) {
+ kfree_skb(skb);
+ return;
+ }
+ skb->stamp.tv_sec = 0;
+ skb->stamp.tv_usec = now + sched_next;
+
+ spin_lock(&tbl->proxy_queue.lock);
+ if (del_timer(&tbl->proxy_timer)) {
+ long tval = tbl->proxy_timer.expires - now;
+ if (tval < sched_next)
+ sched_next = tval;
+ }
+ dst_release(skb->dst);
+ skb->dst = NULL;
+ dev_hold(skb->dev);
+ __skb_queue_tail(&tbl->proxy_queue, skb);
+ mod_timer(&tbl->proxy_timer, now + sched_next);
+ spin_unlock(&tbl->proxy_queue.lock);
+}
+
+
+struct neigh_parms *neigh_parms_alloc(struct net_device *dev, struct neigh_table *tbl)
+{
+ struct neigh_parms *p;
+ p = kmalloc(sizeof(*p), GFP_KERNEL);
+ if (p) {
+ memcpy(p, &tbl->parms, sizeof(*p));
+ p->tbl = tbl;
+ p->reachable_time = neigh_rand_reach_time(p->base_reachable_time);
+ if (dev && dev->neigh_setup) {
+ if (dev->neigh_setup(dev, p)) {
+ kfree(p);
+ return NULL;
+ }
+ }
+ p->sysctl_table = NULL;
+ write_lock_bh(&tbl->lock);
+ p->next = tbl->parms.next;
+ tbl->parms.next = p;
+ write_unlock_bh(&tbl->lock);
+ }
+ return p;
+}
+
+void neigh_parms_release(struct neigh_table *tbl, struct neigh_parms *parms)
+{
+ struct neigh_parms **p;
+
+ if (parms == NULL || parms == &tbl->parms)
+ return;
+ write_lock_bh(&tbl->lock);
+ for (p = &tbl->parms.next; *p; p = &(*p)->next) {
+ if (*p == parms) {
+ *p = parms->next;
+ write_unlock_bh(&tbl->lock);
+#ifdef CONFIG_SYSCTL
+ neigh_sysctl_unregister(parms);
+#endif
+ kfree(parms);
+ return;
+ }
+ }
+ write_unlock_bh(&tbl->lock);
+ NEIGH_PRINTK1("neigh_parms_release: not found\n");
+}
+
+
+void neigh_table_init(struct neigh_table *tbl)
+{
+ unsigned long now = jiffies;
+ unsigned long phsize;
+
+ tbl->parms.reachable_time = neigh_rand_reach_time(tbl->parms.base_reachable_time);
+
+ if (tbl->kmem_cachep == NULL)
+ tbl->kmem_cachep = kmem_cache_create(tbl->id,
+ (tbl->entry_size+15)&~15,
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+
+ if (!tbl->kmem_cachep)
+ panic("cannot create neighbour cache");
+
+#ifdef CONFIG_PROC_FS
+ tbl->pde = create_proc_entry(tbl->id, 0, proc_net_stat);
+ if (!tbl->pde)
+ panic("cannot create neighbour proc dir entry");
+ tbl->pde->proc_fops = &neigh_stat_seq_fops;
+ tbl->pde->data = tbl;
+#endif
+
+ tbl->hash_mask = 1;
+ tbl->hash_buckets = neigh_hash_alloc(tbl->hash_mask + 1);
+
+ phsize = (PNEIGH_HASHMASK + 1) * sizeof(struct pneigh_entry *);
+ tbl->phash_buckets = kmalloc(phsize, GFP_KERNEL);
+
+ if (!tbl->hash_buckets || !tbl->phash_buckets)
+ panic("cannot allocate neighbour cache hashes");
+
+ memset(tbl->phash_buckets, 0, phsize);
+
+ get_random_bytes(&tbl->hash_rnd, sizeof(tbl->hash_rnd));
+
+#ifdef CONFIG_SMP
+ tasklet_init(&tbl->gc_task, SMP_TIMER_NAME(neigh_periodic_timer), (unsigned long)tbl);
+#endif
+ init_timer(&tbl->gc_timer);
+ tbl->lock = RW_LOCK_UNLOCKED;
+ tbl->gc_timer.data = (unsigned long)tbl;
+ tbl->gc_timer.function = neigh_periodic_timer;
+ tbl->gc_timer.expires = now + 1;
+ add_timer(&tbl->gc_timer);
+
+ init_timer(&tbl->proxy_timer);
+ tbl->proxy_timer.data = (unsigned long)tbl;
+ tbl->proxy_timer.function = neigh_proxy_process;
+ skb_queue_head_init(&tbl->proxy_queue);
+
+ tbl->last_flush = now;
+ tbl->last_rand = now + tbl->parms.reachable_time*20;
+ write_lock(&neigh_tbl_lock);
+ tbl->next = neigh_tables;
+ neigh_tables = tbl;
+ write_unlock(&neigh_tbl_lock);
+}
+
+int neigh_table_clear(struct neigh_table *tbl)
+{
+ struct neigh_table **tp;
+
+ NEIGH_PRINTK3("neigh_table_clean(tbl=%p)\n", tbl);
+
+ /* It is not clean... Fix it to unload IPv6 module safely */
+ del_timer_sync(&tbl->gc_timer);
+ tasklet_kill(&tbl->gc_task);
+ del_timer_sync(&tbl->proxy_timer);
+ pneigh_queue_purge(&tbl->proxy_queue);
+ neigh_ifdown(tbl, NULL);
+ if (atomic_read(&tbl->entries))
+ NEIGH_PRINTK1(KERN_CRIT "neighbour leakage\n");
+ write_lock(&neigh_tbl_lock);
+ for (tp = &neigh_tables; *tp; tp = &(*tp)->next) {
+ if (*tp == tbl) {
+ *tp = tbl->next;
+ break;
+ }
+ }
+ write_unlock(&neigh_tbl_lock);
+
+ neigh_hash_free(tbl->hash_buckets, tbl->hash_mask + 1);
+ tbl->hash_buckets = NULL;
+
+ kfree(tbl->phash_buckets);
+ tbl->phash_buckets = NULL;
+
+ if (kmem_cache_destroy(tbl->kmem_cachep) == 0) {
+ NEIGH_PRINTK3("%s(tbl=%p): kmem_cache %p for %s destroyed.\n",
+ __FUNCTION__, tbl, tbl->kmem_cachep, tbl->id);
+ tbl->kmem_cachep = NULL;
+ } else {
+ NEIGH_PRINTK1(KERN_WARNING
+ "%s(tbl=%p): failed to destroy kmem_cache %p for %s.\n",
+ __FUNCTION__, tbl, tbl->kmem_cachep, tbl->id);
+ }
+
+#ifdef CONFIG_SYSCTL
+ neigh_sysctl_unregister(&tbl->parms);
+#endif
+ return 0;
+}
+
+int neigh_delete(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct ndmsg *ndm = NLMSG_DATA(nlh);
+ struct rtattr **nda = arg;
+ struct neigh_table *tbl;
+ struct net_device *dev = NULL;
+ int err = 0;
+
+ NEIGH_PRINTK3(KERN_DEBUG "neigh_delete(skb=%p, nlh=%p, nda=%p)\n",
+ skb, nlh, arg);
+
+ if (ndm->ndm_ifindex) {
+ if ((dev = dev_get_by_index(ndm->ndm_ifindex)) == NULL)
+ return -ENODEV;
+ }
+
+ read_lock(&neigh_tbl_lock);
+ for (tbl=neigh_tables; tbl; tbl = tbl->next) {
+ struct neighbour *n;
+
+ if (tbl->family != ndm->ndm_family)
+ continue;
+ read_unlock(&neigh_tbl_lock);
+
+ err = -EINVAL;
+ if (nda[NDA_DST-1] == NULL ||
+ nda[NDA_DST-1]->rta_len != RTA_LENGTH(tbl->key_len))
+ goto out;
+
+ if (ndm->ndm_flags&NTF_PROXY) {
+ err = pneigh_delete(tbl, RTA_DATA(nda[NDA_DST-1]), dev);
+ goto out;
+ }
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ n = neigh_lookup(tbl, RTA_DATA(nda[NDA_DST-1]), dev);
+ if (n) {
+ err = neigh_update(n, NULL, NUD_FAILED, 1, 0);
+ neigh_release(n);
+ }
+out:
+ if (dev)
+ dev_put(dev);
+ return err;
+ }
+ read_unlock(&neigh_tbl_lock);
+
+ if (dev)
+ dev_put(dev);
+
+ return -EADDRNOTAVAIL;
+}
+
+int neigh_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct ndmsg *ndm = NLMSG_DATA(nlh);
+ struct rtattr **nda = arg;
+ struct neigh_table *tbl;
+ struct net_device *dev = NULL;
+
+ if (ndm->ndm_ifindex) {
+ if ((dev = dev_get_by_index(ndm->ndm_ifindex)) == NULL)
+ return -ENODEV;
+ }
+
+ read_lock(&neigh_tbl_lock);
+ for (tbl=neigh_tables; tbl; tbl = tbl->next) {
+ int err = 0;
+ int override = 1;
+ struct neighbour *n;
+
+ if (tbl->family != ndm->ndm_family)
+ continue;
+ read_unlock(&neigh_tbl_lock);
+
+ err = -EINVAL;
+ if (nda[NDA_DST-1] == NULL ||
+ nda[NDA_DST-1]->rta_len != RTA_LENGTH(tbl->key_len))
+ goto out;
+ if (ndm->ndm_flags&NTF_PROXY) {
+ err = -ENOBUFS;
+ if (pneigh_lookup(tbl, RTA_DATA(nda[NDA_DST-1]), dev, 1))
+ err = 0;
+ goto out;
+ }
+ if (dev == NULL)
+ return -EINVAL;
+ err = -EINVAL;
+ if (nda[NDA_LLADDR-1] != NULL &&
+ nda[NDA_LLADDR-1]->rta_len != RTA_LENGTH(dev->addr_len))
+ goto out;
+ err = 0;
+ n = neigh_lookup(tbl, RTA_DATA(nda[NDA_DST-1]), dev);
+ if (n) {
+ if (nlh->nlmsg_flags&NLM_F_EXCL)
+ err = -EEXIST;
+ override = nlh->nlmsg_flags&NLM_F_REPLACE;
+ } else if (!(nlh->nlmsg_flags&NLM_F_CREATE))
+ err = -ENOENT;
+ else {
+ n = __neigh_lookup_errno(tbl, RTA_DATA(nda[NDA_DST-1]), dev);
+ if (IS_ERR(n)) {
+ err = PTR_ERR(n);
+ n = NULL;
+ }
+ }
+ if (err == 0) {
+ err = neigh_update(n, nda[NDA_LLADDR-1] ? RTA_DATA(nda[NDA_LLADDR-1]) : NULL,
+ ndm->ndm_state,
+ override, 0);
+ }
+ if (n)
+ neigh_release(n);
+out:
+ if (dev)
+ dev_put(dev);
+ return err;
+ }
+ read_unlock(&neigh_tbl_lock);
+
+ if (dev)
+ dev_put(dev);
+ return -EADDRNOTAVAIL;
+}
+
+
+static int neigh_fill_info(struct sk_buff *skb, struct neighbour *n,
+ u32 pid, u32 seq, int event)
+{
+ unsigned long now = jiffies;
+ struct ndmsg *ndm;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+ struct nda_cacheinfo ci;
+ int locked = 0;
+
+ nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*ndm));
+ ndm = NLMSG_DATA(nlh);
+ nlh->nlmsg_flags = pid ? NLM_F_MULTI : 0;
+ ndm->ndm_family = n->ops->family;
+ ndm->ndm_flags = n->flags;
+ ndm->ndm_type = n->type;
+ ndm->ndm_ifindex = n->dev->ifindex;
+ RTA_PUT(skb, NDA_DST, n->tbl->key_len, n->primary_key);
+ read_lock_bh(&n->lock);
+ locked=1;
+ ndm->ndm_state = n->nud_state;
+ if (n->nud_state&NUD_VALID)
+ RTA_PUT(skb, NDA_LLADDR, n->dev->addr_len, n->ha);
+ ci.ndm_used = now - n->used;
+ ci.ndm_confirmed = now - n->confirmed;
+ ci.ndm_updated = now - n->updated;
+ ci.ndm_refcnt = atomic_read(&n->refcnt) - 1;
+ read_unlock_bh(&n->lock);
+ locked=0;
+ RTA_PUT(skb, NDA_CACHEINFO, sizeof(ci), &ci);
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ if (locked)
+ read_unlock_bh(&n->lock);
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+
+static int neigh_dump_table(struct neigh_table *tbl, struct sk_buff *skb, struct netlink_callback *cb)
+{
+ struct neighbour *n;
+ int h, s_h;
+ int idx, s_idx;
+
+ s_h = cb->args[1];
+ s_idx = idx = cb->args[2];
+ for (h=0; h <= tbl->hash_mask; h++) {
+ if (h < s_h) continue;
+ if (h > s_h)
+ s_idx = 0;
+ read_lock_bh(&tbl->lock);
+ for (n = tbl->hash_buckets[h], idx = 0; n;
+ n = n->next, idx++) {
+ if (idx < s_idx)
+ continue;
+ if (neigh_fill_info(skb, n, NETLINK_CB(cb->skb).pid,
+ cb->nlh->nlmsg_seq, RTM_NEWNEIGH) <= 0) {
+ read_unlock_bh(&tbl->lock);
+ cb->args[1] = h;
+ cb->args[2] = idx;
+ return -1;
+ }
+ }
+ read_unlock_bh(&tbl->lock);
+ }
+
+ cb->args[1] = h;
+ cb->args[2] = idx;
+ return skb->len;
+}
+
+int neigh_dump_info(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int t;
+ int s_t;
+ struct neigh_table *tbl;
+ int family = ((struct rtgenmsg*)NLMSG_DATA(cb->nlh))->rtgen_family;
+
+ s_t = cb->args[0];
+
+ read_lock(&neigh_tbl_lock);
+ for (tbl=neigh_tables, t=0; tbl; tbl = tbl->next, t++) {
+ if (t < s_t) continue;
+ if (family && tbl->family != family)
+ continue;
+ if (t > s_t)
+ memset(&cb->args[1], 0, sizeof(cb->args)-sizeof(cb->args[0]));
+ if (neigh_dump_table(tbl, skb, cb) < 0)
+ break;
+ }
+ read_unlock(&neigh_tbl_lock);
+
+ cb->args[0] = t;
+
+ return skb->len;
+}
+
+void neigh_for_each(struct neigh_table *tbl, void (*cb)(struct neighbour *, void *), void *cookie)
+{
+ int chain;
+
+ read_lock_bh(&tbl->lock);
+ for (chain = 0; chain <= tbl->hash_mask; chain++) {
+ struct neighbour *n;
+
+ for (n = tbl->hash_buckets[chain]; n; n = n->next)
+ cb(n, cookie);
+ }
+ read_unlock_bh(&tbl->lock);
+}
+EXPORT_SYMBOL(neigh_for_each);
+
+/* The tbl->lock must be held as a writer and BH disabled. */
+void __neigh_for_each_release(struct neigh_table *tbl,
+ int (*cb)(struct neighbour *))
+{
+ int chain;
+
+ for (chain = 0; chain <= tbl->hash_mask; chain++) {
+ struct neighbour *n, **np;
+
+ np = &tbl->hash_buckets[chain];
+ while ((n = *np) != NULL) {
+ int release;
+
+ write_lock(&n->lock);
+ release = cb(n);
+ if (release) {
+ *np = n->next;
+ n->dead = 1;
+ } else
+ np = &n->next;
+ write_unlock(&n->lock);
+ if (release)
+ neigh_release(n);
+ }
+ }
+}
+EXPORT_SYMBOL(__neigh_for_each_release);
+
+#ifdef CONFIG_PROC_FS
+
+static struct neighbour *neigh_get_first(struct seq_file *seq)
+{
+ struct neigh_seq_state *state = seq->private;
+ struct neigh_table *tbl = state->tbl;
+ struct neighbour *n = NULL;
+ int bucket = state->bucket;
+
+ state->flags &= ~NEIGH_SEQ_IS_PNEIGH;
+ for (bucket = 0; bucket <= tbl->hash_mask; bucket++) {
+ n = tbl->hash_buckets[bucket];
+
+ while (n) {
+ if (state->neigh_sub_iter) {
+ loff_t fakep = 0;
+ void *v;
+
+ v = state->neigh_sub_iter(state, n, &fakep);
+ if (!v)
+ goto next;
+ }
+ if (!(state->flags & NEIGH_SEQ_SKIP_NOARP))
+ break;
+ if (n->nud_state & ~NUD_NOARP)
+ break;
+ next:
+ n = n->next;
+ }
+
+ if (n)
+ break;
+ }
+ state->bucket = bucket;
+
+ return n;
+}
+
+static struct neighbour *neigh_get_next(struct seq_file *seq,
+ struct neighbour *n,
+ loff_t *pos)
+{
+ struct neigh_seq_state *state = seq->private;
+ struct neigh_table *tbl = state->tbl;
+
+ if (state->neigh_sub_iter) {
+ void *v = state->neigh_sub_iter(state, n, pos);
+ if (v)
+ return n;
+ }
+ n = n->next;
+
+ while (1) {
+ while (n) {
+ if (state->neigh_sub_iter) {
+ void *v = state->neigh_sub_iter(state, n, pos);
+ if (v)
+ return n;
+ goto next;
+ }
+ if (!(state->flags & NEIGH_SEQ_SKIP_NOARP))
+ break;
+
+ if (n->nud_state & ~NUD_NOARP)
+ break;
+ next:
+ n = n->next;
+ }
+
+ if (n)
+ break;
+
+ if (++state->bucket > tbl->hash_mask)
+ break;
+
+ n = tbl->hash_buckets[state->bucket];
+ }
+
+ if (n && pos)
+ --(*pos);
+ return n;
+}
+
+static struct neighbour *neigh_get_idx(struct seq_file *seq, loff_t *pos)
+{
+ struct neighbour *n = neigh_get_first(seq);
+
+ if (n) {
+ while (*pos) {
+ n = neigh_get_next(seq, n, pos);
+ if (!n)
+ break;
+ }
+ }
+ return *pos ? NULL : n;
+}
+
+static struct pneigh_entry *pneigh_get_first(struct seq_file *seq)
+{
+ struct neigh_seq_state *state = seq->private;
+ struct neigh_table *tbl = state->tbl;
+ struct pneigh_entry *pn = NULL;
+ int bucket = state->bucket;
+
+ state->flags |= NEIGH_SEQ_IS_PNEIGH;
+ for (bucket = 0; bucket <= PNEIGH_HASHMASK; bucket++) {
+ pn = tbl->phash_buckets[bucket];
+ if (pn)
+ break;
+ }
+ state->bucket = bucket;
+
+ return pn;
+}
+
+static struct pneigh_entry *pneigh_get_next(struct seq_file *seq,
+ struct pneigh_entry *pn,
+ loff_t *pos)
+{
+ struct neigh_seq_state *state = seq->private;
+ struct neigh_table *tbl = state->tbl;
+
+ pn = pn->next;
+ while (!pn) {
+ if (++state->bucket > PNEIGH_HASHMASK)
+ break;
+ pn = tbl->phash_buckets[state->bucket];
+ if (pn)
+ break;
+ }
+
+ if (pn && pos)
+ --(*pos);
+
+ return pn;
+}
+
+static struct pneigh_entry *pneigh_get_idx(struct seq_file *seq, loff_t *pos)
+{
+ struct pneigh_entry *pn = pneigh_get_first(seq);
+
+ if (pn) {
+ while (*pos) {
+ pn = pneigh_get_next(seq, pn, pos);
+ if (!pn)
+ break;
+ }
+ }
+ return *pos ? NULL : pn;
+}
+
+static void *neigh_get_idx_any(struct seq_file *seq, loff_t *pos)
+{
+ struct neigh_seq_state *state = seq->private;
+ void *rc;
+
+ rc = neigh_get_idx(seq, pos);
+ if (!rc && !(state->flags & NEIGH_SEQ_NEIGH_ONLY))
+ rc = pneigh_get_idx(seq, pos);
+
+ return rc;
+}
+
+void *neigh_seq_start(struct seq_file *seq, loff_t *pos, struct neigh_table *tbl, unsigned int neigh_seq_flags)
+{
+ struct neigh_seq_state *state = seq->private;
+ loff_t pos_minus_one;
+
+ state->tbl = tbl;
+ state->bucket = 0;
+ state->flags = (neigh_seq_flags & ~NEIGH_SEQ_IS_PNEIGH);
+
+ read_lock_bh(&tbl->lock);
+
+ pos_minus_one = *pos - 1;
+ return *pos ? neigh_get_idx_any(seq, &pos_minus_one) : SEQ_START_TOKEN;
+}
+
+void *neigh_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+ struct neigh_seq_state *state;
+ void *rc;
+
+ if (v == SEQ_START_TOKEN) {
+ rc = neigh_get_idx(seq, pos);
+ goto out;
+ }
+
+ state = seq->private;
+ if (!(state->flags & NEIGH_SEQ_IS_PNEIGH)) {
+ rc = neigh_get_next(seq, v, NULL);
+ if (rc)
+ goto out;
+ if (!(state->flags & NEIGH_SEQ_NEIGH_ONLY))
+ rc = pneigh_get_first(seq);
+ } else {
+ BUG_ON(state->flags & NEIGH_SEQ_NEIGH_ONLY);
+ rc = pneigh_get_next(seq, v, NULL);
+ }
+out:
+ ++(*pos);
+ return rc;
+}
+
+void neigh_seq_stop(struct seq_file *seq, void *v)
+{
+ struct neigh_seq_state *state = seq->private;
+ struct neigh_table *tbl = state->tbl;
+
+ read_unlock_bh(&tbl->lock);
+}
+
+/* statistics via seq_file */
+
+static void *neigh_stat_seq_start(struct seq_file *seq, loff_t *pos)
+{
+ struct proc_dir_entry *pde = seq->private;
+ struct neigh_table *tbl = pde->data;
+ int lcpu;
+
+ if (*pos == 0)
+ return SEQ_START_TOKEN;
+
+ for (lcpu = *pos-1; lcpu < smp_num_cpus; ++lcpu) {
+ int i = cpu_logical_map(lcpu);
+ *pos = lcpu+1;
+ return &tbl->stats[i];
+ }
+ return NULL;
+}
+
+static void *neigh_stat_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+ struct proc_dir_entry *pde = seq->private;
+ struct neigh_table *tbl = pde->data;
+ int lcpu;
+
+ for (lcpu = *pos; lcpu < smp_num_cpus; ++lcpu) {
+ int i = cpu_logical_map(lcpu);
+ *pos = lcpu+1;
+ return &tbl->stats[i];
+ }
+ return NULL;
+}
+
+static void neigh_stat_seq_stop(struct seq_file *seq, void *v)
+{
+
+}
+
+static int neigh_stat_seq_show(struct seq_file *seq, void *v)
+{
+ struct proc_dir_entry *pde = seq->private;
+ struct neigh_table *tbl = pde->data;
+ struct neigh_statistics *st = v;
+
+ if (v == SEQ_START_TOKEN) {
+ seq_printf(seq, "entries allocs destroys hash_grows lookups hits res_failed rcv_probes_mcast rcv_probes_ucast periodic_gc_runs forced_gc_runs forced_gc_goal_miss\n");
+ return 0;
+ }
+
+ seq_printf(seq, "%08x %08lx %08lx %08lx %08lx %08lx %08lx "
+ "%08lx %08lx %08lx %08lx\n",
+ atomic_read(&tbl->entries),
+
+ st->allocs,
+ st->destroys,
+ st->hash_grows,
+
+ st->lookups,
+ st->hits,
+
+ st->res_failed,
+
+ st->rcv_probes_mcast,
+ st->rcv_probes_ucast,
+
+ st->periodic_gc_runs,
+ st->forced_gc_runs
+ );
+
+ return 0;
+}
+
+static struct seq_operations neigh_stat_seq_ops = {
+ .start = neigh_stat_seq_start,
+ .next = neigh_stat_seq_next,
+ .stop = neigh_stat_seq_stop,
+ .show = neigh_stat_seq_show,
+};
+
+static int neigh_stat_seq_open(struct inode *inode, struct file *file)
+{
+ int ret = seq_open(file, &neigh_stat_seq_ops);
+
+ if (!ret) {
+ struct seq_file *sf = file->private_data;
+ sf->private = PDE(inode);
+ }
+ return ret;
+};
+
+static struct file_operations neigh_stat_seq_fops = {
+ .owner = THIS_MODULE,
+ .open = neigh_stat_seq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
+
+#endif /* CONFIG_PROC_FS */
+
+#ifdef CONFIG_ARPD
+void neigh_app_ns(struct neighbour *n)
+{
+ struct sk_buff *skb;
+ struct nlmsghdr *nlh;
+ int size = NLMSG_SPACE(sizeof(struct ndmsg)+256);
+
+ skb = alloc_skb(size, GFP_ATOMIC);
+ if (!skb)
+ return;
+
+ if (neigh_fill_info(skb, n, 0, 0, RTM_GETNEIGH) < 0) {
+ kfree_skb(skb);
+ return;
+ }
+ nlh = (struct nlmsghdr*)skb->data;
+ nlh->nlmsg_flags = NLM_F_REQUEST;
+ NETLINK_CB(skb).dst_groups = RTMGRP_NEIGH;
+ netlink_broadcast(rtnl, skb, 0, RTMGRP_NEIGH, GFP_ATOMIC);
+}
+
+void neigh_app_notify(struct neighbour *n)
+{
+ struct sk_buff *skb;
+ struct nlmsghdr *nlh;
+ int size = NLMSG_SPACE(sizeof(struct ndmsg)+256);
+
+ skb = alloc_skb(size, GFP_ATOMIC);
+ if (!skb)
+ return;
+
+ if (neigh_fill_info(skb, n, 0, 0, RTM_NEWNEIGH) < 0) {
+ kfree_skb(skb);
+ return;
+ }
+ nlh = (struct nlmsghdr*)skb->data;
+ NETLINK_CB(skb).dst_groups = RTMGRP_NEIGH;
+ netlink_broadcast(rtnl, skb, 0, RTMGRP_NEIGH, GFP_ATOMIC);
+}
+
+#endif /* CONFIG_ARPD */
+
+#ifdef CONFIG_SYSCTL
+
+struct neigh_sysctl_table
+{
+ struct ctl_table_header *sysctl_header;
+ ctl_table neigh_vars[17];
+ ctl_table neigh_dev[2];
+ ctl_table neigh_neigh_dir[2];
+ ctl_table neigh_proto_dir[2];
+ ctl_table neigh_root_dir[2];
+} neigh_sysctl_template = {
+ NULL,
+ {{NET_NEIGH_MCAST_SOLICIT, "mcast_solicit",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_NEIGH_UCAST_SOLICIT, "ucast_solicit",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_NEIGH_APP_SOLICIT, "app_solicit",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_NEIGH_RETRANS_TIME, "retrans_time",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_NEIGH_REACHABLE_TIME, "base_reachable_time",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_NEIGH_DELAY_PROBE_TIME, "delay_first_probe_time",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_NEIGH_GC_STALE_TIME, "gc_stale_time",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_NEIGH_UNRES_QLEN, "unres_qlen",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_NEIGH_PROXY_QLEN, "proxy_qlen",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_NEIGH_ANYCAST_DELAY, "anycast_delay",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_NEIGH_PROXY_DELAY, "proxy_delay",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_NEIGH_LOCKTIME, "locktime",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_NEIGH_GC_INTERVAL, "gc_interval",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_NEIGH_GC_THRESH1, "gc_thresh1",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_NEIGH_GC_THRESH2, "gc_thresh2",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_NEIGH_GC_THRESH3, "gc_thresh3",
+ NULL, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {0}},
+
+ {{NET_PROTO_CONF_DEFAULT, "default", NULL, 0, 0555, NULL},{0}},
+ {{0, "neigh", NULL, 0, 0555, NULL},{0}},
+ {{0, NULL, NULL, 0, 0555, NULL},{0}},
+ {{CTL_NET, "net", NULL, 0, 0555, NULL},{0}}
+};
+
+int neigh_sysctl_register(struct net_device *dev, struct neigh_parms *p,
+ int p_id, int pdev_id, char *p_name)
+{
+ struct neigh_sysctl_table *t;
+
+ t = kmalloc(sizeof(*t), GFP_KERNEL);
+ if (t == NULL)
+ return -ENOBUFS;
+ memcpy(t, &neigh_sysctl_template, sizeof(*t));
+ t->neigh_vars[0].data = &p->mcast_probes;
+ t->neigh_vars[1].data = &p->ucast_probes;
+ t->neigh_vars[2].data = &p->app_probes;
+ t->neigh_vars[3].data = &p->retrans_time;
+ t->neigh_vars[4].data = &p->base_reachable_time;
+ t->neigh_vars[5].data = &p->delay_probe_time;
+ t->neigh_vars[6].data = &p->gc_staletime;
+ t->neigh_vars[7].data = &p->queue_len;
+ t->neigh_vars[8].data = &p->proxy_qlen;
+ t->neigh_vars[9].data = &p->anycast_delay;
+ t->neigh_vars[10].data = &p->proxy_delay;
+ t->neigh_vars[11].data = &p->locktime;
+ if (dev) {
+ t->neigh_dev[0].procname = dev->name;
+ t->neigh_dev[0].ctl_name = dev->ifindex;
+ memset(&t->neigh_vars[12], 0, sizeof(ctl_table));
+ } else {
+ t->neigh_vars[12].data = (int*)(p+1);
+ t->neigh_vars[13].data = (int*)(p+1) + 1;
+ t->neigh_vars[14].data = (int*)(p+1) + 2;
+ t->neigh_vars[15].data = (int*)(p+1) + 3;
+ }
+ t->neigh_neigh_dir[0].ctl_name = pdev_id;
+
+ t->neigh_proto_dir[0].procname = p_name;
+ t->neigh_proto_dir[0].ctl_name = p_id;
+
+ t->neigh_dev[0].child = t->neigh_vars;
+ t->neigh_neigh_dir[0].child = t->neigh_dev;
+ t->neigh_proto_dir[0].child = t->neigh_neigh_dir;
+ t->neigh_root_dir[0].child = t->neigh_proto_dir;
+
+ t->sysctl_header = register_sysctl_table(t->neigh_root_dir, 0);
+ if (t->sysctl_header == NULL) {
+ kfree(t);
+ return -ENOBUFS;
+ }
+ p->sysctl_table = t;
+ return 0;
+}
+
+void neigh_sysctl_unregister(struct neigh_parms *p)
+{
+ if (p->sysctl_table) {
+ struct neigh_sysctl_table *t = p->sysctl_table;
+ p->sysctl_table = NULL;
+ unregister_sysctl_table(t->sysctl_header);
+ kfree(t);
+ }
+}
+
+#endif /* CONFIG_SYSCTL */
diff --git a/uClinux-2.4.31-uc0/net/core/netfilter.c b/uClinux-2.4.31-uc0/net/core/netfilter.c
new file mode 100644
index 0000000..887a0fb
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/netfilter.c
@@ -0,0 +1,679 @@
+/* netfilter.c: look after the filters for various protocols.
+ * Heavily influenced by the old firewall.c by David Bonn and Alan Cox.
+ *
+ * Thanks to Rob `CmdrTaco' Malda for not influencing this code in any
+ * way.
+ *
+ * Rusty Russell (C)2000 -- This code is GPL.
+ *
+ * February 2000: Modified by James Morris to have 1 queue per protocol.
+ * 15-Mar-2000: Added NF_REPEAT --RR.
+ */
+#include <linux/config.h>
+#include <linux/netfilter.h>
+#include <net/protocol.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/wait.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/if.h>
+#include <linux/netdevice.h>
+#include <linux/brlock.h>
+#include <linux/inetdevice.h>
+#include <net/sock.h>
+#include <net/route.h>
+#include <linux/ip.h>
+
+#define __KERNEL_SYSCALLS__
+#include <linux/unistd.h>
+
+/* In this code, we can be waiting indefinitely for userspace to
+ * service a packet if a hook returns NF_QUEUE. We could keep a count
+ * of skbuffs queued for userspace, and not deregister a hook unless
+ * this is zero, but that sucks. Now, we simply check when the
+ * packets come back: if the hook is gone, the packet is discarded. */
+#ifdef CONFIG_NETFILTER_DEBUG
+#define NFDEBUG(format, args...) printk(format , ## args)
+#else
+#define NFDEBUG(format, args...)
+#endif
+
+/* Sockopts only registered and called from user context, so
+ BR_NETPROTO_LOCK would be overkill. Also, [gs]etsockopt calls may
+ sleep. */
+static DECLARE_MUTEX(nf_sockopt_mutex);
+
+struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS];
+static LIST_HEAD(nf_sockopts);
+
+/*
+ * A queue handler may be registered for each protocol. Each is protected by
+ * long term mutex. The handler must provide an an outfn() to accept packets
+ * for queueing and must reinject all packets it receives, no matter what.
+ */
+static struct nf_queue_handler_t {
+ nf_queue_outfn_t outfn;
+ void *data;
+} queue_handler[NPROTO];
+
+int nf_register_hook(struct nf_hook_ops *reg)
+{
+ struct list_head *i;
+
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ for (i = nf_hooks[reg->pf][reg->hooknum].next;
+ i != &nf_hooks[reg->pf][reg->hooknum];
+ i = i->next) {
+ if (reg->priority < ((struct nf_hook_ops *)i)->priority)
+ break;
+ }
+ list_add(&reg->list, i->prev);
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ return 0;
+}
+
+void nf_unregister_hook(struct nf_hook_ops *reg)
+{
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ list_del(&reg->list);
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+}
+
+/* Do exclusive ranges overlap? */
+static inline int overlap(int min1, int max1, int min2, int max2)
+{
+ return max1 > min2 && min1 < max2;
+}
+
+/* Functions to register sockopt ranges (exclusive). */
+int nf_register_sockopt(struct nf_sockopt_ops *reg)
+{
+ struct list_head *i;
+ int ret = 0;
+
+ if (down_interruptible(&nf_sockopt_mutex) != 0)
+ return -EINTR;
+
+ for (i = nf_sockopts.next; i != &nf_sockopts; i = i->next) {
+ struct nf_sockopt_ops *ops = (struct nf_sockopt_ops *)i;
+ if (ops->pf == reg->pf
+ && (overlap(ops->set_optmin, ops->set_optmax,
+ reg->set_optmin, reg->set_optmax)
+ || overlap(ops->get_optmin, ops->get_optmax,
+ reg->get_optmin, reg->get_optmax))) {
+ NFDEBUG("nf_sock overlap: %u-%u/%u-%u v %u-%u/%u-%u\n",
+ ops->set_optmin, ops->set_optmax,
+ ops->get_optmin, ops->get_optmax,
+ reg->set_optmin, reg->set_optmax,
+ reg->get_optmin, reg->get_optmax);
+ ret = -EBUSY;
+ goto out;
+ }
+ }
+
+ list_add(&reg->list, &nf_sockopts);
+out:
+ up(&nf_sockopt_mutex);
+ return ret;
+}
+
+void nf_unregister_sockopt(struct nf_sockopt_ops *reg)
+{
+ /* No point being interruptible: we're probably in cleanup_module() */
+ restart:
+ down(&nf_sockopt_mutex);
+ if (reg->use != 0) {
+ /* To be woken by nf_sockopt call... */
+ /* FIXME: Stuart Young's name appears gratuitously. */
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ reg->cleanup_task = current;
+ up(&nf_sockopt_mutex);
+ schedule();
+ goto restart;
+ }
+ list_del(&reg->list);
+ up(&nf_sockopt_mutex);
+}
+
+#ifdef CONFIG_NETFILTER_DEBUG
+#include <net/ip.h>
+#include <net/route.h>
+#include <net/tcp.h>
+#include <linux/netfilter_ipv4.h>
+
+static void debug_print_hooks_ip(unsigned int nf_debug)
+{
+ if (nf_debug & (1 << NF_IP_PRE_ROUTING)) {
+ printk("PRE_ROUTING ");
+ nf_debug ^= (1 << NF_IP_PRE_ROUTING);
+ }
+ if (nf_debug & (1 << NF_IP_LOCAL_IN)) {
+ printk("LOCAL_IN ");
+ nf_debug ^= (1 << NF_IP_LOCAL_IN);
+ }
+ if (nf_debug & (1 << NF_IP_FORWARD)) {
+ printk("FORWARD ");
+ nf_debug ^= (1 << NF_IP_FORWARD);
+ }
+ if (nf_debug & (1 << NF_IP_LOCAL_OUT)) {
+ printk("LOCAL_OUT ");
+ nf_debug ^= (1 << NF_IP_LOCAL_OUT);
+ }
+ if (nf_debug & (1 << NF_IP_POST_ROUTING)) {
+ printk("POST_ROUTING ");
+ nf_debug ^= (1 << NF_IP_POST_ROUTING);
+ }
+ if (nf_debug)
+ printk("Crap bits: 0x%04X", nf_debug);
+ printk("\n");
+}
+
+void nf_dump_skb(int pf, struct sk_buff *skb)
+{
+ printk("skb: pf=%i %s dev=%s len=%u\n",
+ pf,
+ skb->sk ? "(owned)" : "(unowned)",
+ skb->dev ? skb->dev->name : "(no dev)",
+ skb->len);
+ switch (pf) {
+ case PF_INET: {
+ const struct iphdr *ip = skb->nh.iph;
+ __u32 *opt = (__u32 *) (ip + 1);
+ int opti;
+ __u16 src_port = 0, dst_port = 0;
+
+ if (ip->protocol == IPPROTO_TCP
+ || ip->protocol == IPPROTO_UDP) {
+ struct tcphdr *tcp=(struct tcphdr *)((__u32 *)ip+ip->ihl);
+ src_port = ntohs(tcp->source);
+ dst_port = ntohs(tcp->dest);
+ }
+
+ printk("PROTO=%d %u.%u.%u.%u:%hu %u.%u.%u.%u:%hu"
+ " L=%hu S=0x%2.2hX I=%hu F=0x%4.4hX T=%hu",
+ ip->protocol, NIPQUAD(ip->saddr),
+ src_port, NIPQUAD(ip->daddr),
+ dst_port,
+ ntohs(ip->tot_len), ip->tos, ntohs(ip->id),
+ ntohs(ip->frag_off), ip->ttl);
+
+ for (opti = 0; opti < (ip->ihl - sizeof(struct iphdr) / 4); opti++)
+ printk(" O=0x%8.8X", *opt++);
+ printk("\n");
+ }
+ }
+}
+
+void nf_debug_ip_local_deliver(struct sk_buff *skb)
+{
+ /* If it's a loopback packet, it must have come through
+ * NF_IP_LOCAL_OUT, NF_IP_RAW_INPUT, NF_IP_PRE_ROUTING and
+ * NF_IP_LOCAL_IN. Otherwise, must have gone through
+ * NF_IP_RAW_INPUT and NF_IP_PRE_ROUTING. */
+ if (!skb->dev) {
+ printk("ip_local_deliver: skb->dev is NULL.\n");
+ }
+ else if (strcmp(skb->dev->name, "lo") == 0) {
+ if (skb->nf_debug != ((1 << NF_IP_LOCAL_OUT)
+ | (1 << NF_IP_POST_ROUTING)
+ | (1 << NF_IP_PRE_ROUTING)
+ | (1 << NF_IP_LOCAL_IN))) {
+ printk("ip_local_deliver: bad loopback skb: ");
+ debug_print_hooks_ip(skb->nf_debug);
+ nf_dump_skb(PF_INET, skb);
+ }
+ }
+ else {
+ if (skb->nf_debug != ((1<<NF_IP_PRE_ROUTING)
+ | (1<<NF_IP_LOCAL_IN))) {
+ printk("ip_local_deliver: bad non-lo skb: ");
+ debug_print_hooks_ip(skb->nf_debug);
+ nf_dump_skb(PF_INET, skb);
+ }
+ }
+}
+
+void nf_debug_ip_loopback_xmit(struct sk_buff *newskb)
+{
+ if (newskb->nf_debug != ((1 << NF_IP_LOCAL_OUT)
+ | (1 << NF_IP_POST_ROUTING))) {
+ printk("ip_dev_loopback_xmit: bad owned skb = %p: ",
+ newskb);
+ debug_print_hooks_ip(newskb->nf_debug);
+ nf_dump_skb(PF_INET, newskb);
+ }
+ /* Clear to avoid confusing input check */
+ newskb->nf_debug = 0;
+}
+
+void nf_debug_ip_finish_output2(struct sk_buff *skb)
+{
+ /* If it's owned, it must have gone through the
+ * NF_IP_LOCAL_OUT and NF_IP_POST_ROUTING.
+ * Otherwise, must have gone through
+ * NF_IP_PRE_ROUTING, NF_IP_FORWARD and NF_IP_POST_ROUTING.
+ */
+ if (skb->sk) {
+ if (skb->nf_debug != ((1 << NF_IP_LOCAL_OUT)
+ | (1 << NF_IP_POST_ROUTING))) {
+ printk("ip_finish_output: bad owned skb = %p: ", skb);
+ debug_print_hooks_ip(skb->nf_debug);
+ nf_dump_skb(PF_INET, skb);
+ }
+ } else {
+ if (skb->nf_debug != ((1 << NF_IP_PRE_ROUTING)
+ | (1 << NF_IP_FORWARD)
+ | (1 << NF_IP_POST_ROUTING))) {
+ /* Fragments, entunnelled packets, TCP RSTs
+ generated by ipt_REJECT will have no
+ owners, but still may be local */
+ if (skb->nf_debug != ((1 << NF_IP_LOCAL_OUT)
+ | (1 << NF_IP_POST_ROUTING))){
+ printk("ip_finish_output:"
+ " bad unowned skb = %p: ",skb);
+ debug_print_hooks_ip(skb->nf_debug);
+ nf_dump_skb(PF_INET, skb);
+ }
+ }
+ }
+}
+#endif /*CONFIG_NETFILTER_DEBUG*/
+
+/* Call get/setsockopt() */
+static int nf_sockopt(struct sock *sk, int pf, int val,
+ char *opt, int *len, int get)
+{
+ struct list_head *i;
+ struct nf_sockopt_ops *ops;
+ int ret;
+
+ if (down_interruptible(&nf_sockopt_mutex) != 0)
+ return -EINTR;
+
+ for (i = nf_sockopts.next; i != &nf_sockopts; i = i->next) {
+ ops = (struct nf_sockopt_ops *)i;
+ if (ops->pf == pf) {
+ if (get) {
+ if (val >= ops->get_optmin
+ && val < ops->get_optmax) {
+ ops->use++;
+ up(&nf_sockopt_mutex);
+ ret = ops->get(sk, val, opt, len);
+ goto out;
+ }
+ } else {
+ if (val >= ops->set_optmin
+ && val < ops->set_optmax) {
+ ops->use++;
+ up(&nf_sockopt_mutex);
+ ret = ops->set(sk, val, opt, *len);
+ goto out;
+ }
+ }
+ }
+ }
+ up(&nf_sockopt_mutex);
+ return -ENOPROTOOPT;
+
+ out:
+ down(&nf_sockopt_mutex);
+ ops->use--;
+ if (ops->cleanup_task)
+ wake_up_process(ops->cleanup_task);
+ up(&nf_sockopt_mutex);
+ return ret;
+}
+
+int nf_setsockopt(struct sock *sk, int pf, int val, char *opt,
+ int len)
+{
+ return nf_sockopt(sk, pf, val, opt, &len, 0);
+}
+
+int nf_getsockopt(struct sock *sk, int pf, int val, char *opt, int *len)
+{
+ return nf_sockopt(sk, pf, val, opt, len, 1);
+}
+
+static unsigned int nf_iterate(struct list_head *head,
+ struct sk_buff **skb,
+ int hook,
+ const struct net_device *indev,
+ const struct net_device *outdev,
+ struct list_head **i,
+ int (*okfn)(struct sk_buff *),
+ int hook_thresh)
+{
+ for (*i = (*i)->next; *i != head; *i = (*i)->next) {
+ struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;
+
+ if (hook_thresh > elem->priority)
+ continue;
+
+ switch (elem->hook(hook, skb, indev, outdev, okfn)) {
+ case NF_QUEUE:
+ return NF_QUEUE;
+
+ case NF_STOLEN:
+ return NF_STOLEN;
+
+ case NF_DROP:
+ return NF_DROP;
+
+ case NF_REPEAT:
+ *i = (*i)->prev;
+ break;
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ case NF_ACCEPT:
+ break;
+
+ default:
+ NFDEBUG("Evil return from %p(%u).\n",
+ elem->hook, hook);
+#endif
+ }
+ }
+ return NF_ACCEPT;
+}
+
+int nf_register_queue_handler(int pf, nf_queue_outfn_t outfn, void *data)
+{
+ int ret;
+
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ if (queue_handler[pf].outfn)
+ ret = -EBUSY;
+ else {
+ queue_handler[pf].outfn = outfn;
+ queue_handler[pf].data = data;
+ ret = 0;
+ }
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+
+ return ret;
+}
+
+/* The caller must flush their queue before this */
+int nf_unregister_queue_handler(int pf)
+{
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ queue_handler[pf].outfn = NULL;
+ queue_handler[pf].data = NULL;
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ return 0;
+}
+
+/*
+ * Any packet that leaves via this function must come back
+ * through nf_reinject().
+ */
+static void nf_queue(struct sk_buff *skb,
+ struct list_head *elem,
+ int pf, unsigned int hook,
+ struct net_device *indev,
+ struct net_device *outdev,
+ int (*okfn)(struct sk_buff *))
+{
+ int status;
+ struct nf_info *info;
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ struct net_device *physindev = NULL;
+ struct net_device *physoutdev = NULL;
+#endif
+
+ if (!queue_handler[pf].outfn) {
+ kfree_skb(skb);
+ return;
+ }
+
+ info = kmalloc(sizeof(*info), GFP_ATOMIC);
+ if (!info) {
+ if (net_ratelimit())
+ printk(KERN_ERR "OOM queueing packet %p\n",
+ skb);
+ kfree_skb(skb);
+ return;
+ }
+
+ *info = (struct nf_info) {
+ (struct nf_hook_ops *)elem, pf, hook, indev, outdev, okfn };
+
+ /* Bump dev refs so they don't vanish while packet is out */
+ if (indev) dev_hold(indev);
+ if (outdev) dev_hold(outdev);
+
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ if (skb->nf_bridge) {
+ physindev = skb->nf_bridge->physindev;
+ if (physindev) dev_hold(physindev);
+ physoutdev = skb->nf_bridge->physoutdev;
+ if (physoutdev) dev_hold(physoutdev);
+ }
+#endif
+
+ status = queue_handler[pf].outfn(skb, info, queue_handler[pf].data);
+ if (status < 0) {
+ /* James M doesn't say fuck enough. */
+ if (indev) dev_put(indev);
+ if (outdev) dev_put(outdev);
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ if (physindev) dev_put(physindev);
+ if (physoutdev) dev_put(physoutdev);
+#endif
+ kfree(info);
+ kfree_skb(skb);
+ return;
+ }
+}
+
+int nf_hook_slow(int pf, unsigned int hook, struct sk_buff *skb,
+ struct net_device *indev,
+ struct net_device *outdev,
+ int (*okfn)(struct sk_buff *),
+ int hook_thresh)
+{
+ struct list_head *elem;
+ unsigned int verdict;
+ int ret = 0;
+
+ /* This stopgap cannot be removed until all the hooks are audited. */
+ if (skb_is_nonlinear(skb) && skb_linearize(skb, GFP_ATOMIC) != 0) {
+ kfree_skb(skb);
+ return -ENOMEM;
+ }
+ if (skb->ip_summed == CHECKSUM_HW) {
+ if (outdev == NULL) {
+ skb->ip_summed = CHECKSUM_NONE;
+ } else {
+ skb_checksum_help(skb);
+ }
+ }
+
+ /* We may already have this, but read-locks nest anyway */
+ br_read_lock_bh(BR_NETPROTO_LOCK);
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ if (skb->nf_debug & (1 << hook)) {
+ printk("nf_hook: hook %i already set.\n", hook);
+ nf_dump_skb(pf, skb);
+ }
+ skb->nf_debug |= (1 << hook);
+#endif
+
+ elem = &nf_hooks[pf][hook];
+ verdict = nf_iterate(&nf_hooks[pf][hook], &skb, hook, indev,
+ outdev, &elem, okfn, hook_thresh);
+ if (verdict == NF_QUEUE) {
+ NFDEBUG("nf_hook: Verdict = QUEUE.\n");
+ nf_queue(skb, elem, pf, hook, indev, outdev, okfn);
+ }
+
+ switch (verdict) {
+ case NF_ACCEPT:
+ ret = okfn(skb);
+ break;
+
+ case NF_DROP:
+ kfree_skb(skb);
+ ret = -EPERM;
+ break;
+ }
+
+ br_read_unlock_bh(BR_NETPROTO_LOCK);
+ return ret;
+}
+
+void nf_reinject(struct sk_buff *skb, struct nf_info *info,
+ unsigned int verdict)
+{
+ struct list_head *elem = &info->elem->list;
+ struct list_head *i;
+
+ /* We don't have BR_NETPROTO_LOCK here */
+ br_read_lock_bh(BR_NETPROTO_LOCK);
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ if (skb->nf_bridge) {
+ if (skb->nf_bridge->physindev)
+ dev_put(skb->nf_bridge->physindev);
+ if (skb->nf_bridge->physoutdev)
+ dev_put(skb->nf_bridge->physoutdev);
+ }
+#endif
+ for (i = nf_hooks[info->pf][info->hook].next; i != elem; i = i->next) {
+ if (i == &nf_hooks[info->pf][info->hook]) {
+ /* The module which sent it to userspace is gone. */
+ NFDEBUG("%s: module disappeared, dropping packet.\n",
+ __FUNCTION__);
+ verdict = NF_DROP;
+ break;
+ }
+ }
+
+ /* Continue traversal iff userspace said ok... */
+ if (verdict == NF_REPEAT) {
+ elem = elem->prev;
+ verdict = NF_ACCEPT;
+ }
+
+ if (verdict == NF_ACCEPT) {
+ verdict = nf_iterate(&nf_hooks[info->pf][info->hook],
+ &skb, info->hook,
+ info->indev, info->outdev, &elem,
+ info->okfn, INT_MIN);
+ }
+
+ switch (verdict) {
+ case NF_ACCEPT:
+ info->okfn(skb);
+ break;
+
+ case NF_QUEUE:
+ nf_queue(skb, elem, info->pf, info->hook,
+ info->indev, info->outdev, info->okfn);
+ break;
+
+ case NF_DROP:
+ kfree_skb(skb);
+ break;
+ }
+ br_read_unlock_bh(BR_NETPROTO_LOCK);
+
+ /* Release those devices we held, or Alexey will kill me. */
+ if (info->indev) dev_put(info->indev);
+ if (info->outdev) dev_put(info->outdev);
+
+ kfree(info);
+ return;
+}
+
+#ifdef CONFIG_INET
+/* route_me_harder function, used by iptable_nat, iptable_mangle + ip_queue */
+int ip_route_me_harder(struct sk_buff **pskb)
+{
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct rtable *rt;
+ struct rt_key key = {};
+ struct dst_entry *odst;
+ unsigned int hh_len;
+
+ /* some non-standard hacks like ipt_REJECT.c:send_reset() can cause
+ * packets with foreign saddr to be appear on the NF_IP_LOCAL_OUT hook.
+ */
+ if (inet_addr_type(iph->saddr) == RTN_LOCAL) {
+ key.dst = iph->daddr;
+ key.src = iph->saddr;
+ key.oif = (*pskb)->sk ? (*pskb)->sk->bound_dev_if : 0;
+ key.tos = RT_TOS(iph->tos);
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ key.fwmark = (*pskb)->nfmark;
+#endif
+ if (ip_route_output_key(&rt, &key) != 0)
+ return -1;
+
+ /* Drop old route. */
+ dst_release((*pskb)->dst);
+ (*pskb)->dst = &rt->u.dst;
+ } else {
+ /* non-local src, find valid iif to satisfy
+ * rp-filter when calling ip_route_input. */
+ key.dst = iph->saddr;
+ if (ip_route_output_key(&rt, &key) != 0)
+ return -1;
+
+ odst = (*pskb)->dst;
+ if (ip_route_input(*pskb, iph->daddr, iph->saddr,
+ RT_TOS(iph->tos), rt->u.dst.dev) != 0) {
+ dst_release(&rt->u.dst);
+ return -1;
+ }
+ dst_release(&rt->u.dst);
+ dst_release(odst);
+ }
+
+ if ((*pskb)->dst->error)
+ return -1;
+
+ /* Change in oif may mean change in hh_len. */
+ hh_len = (*pskb)->dst->dev->hard_header_len;
+ if (skb_headroom(*pskb) < hh_len) {
+ struct sk_buff *nskb;
+
+ nskb = skb_realloc_headroom(*pskb, hh_len);
+ if (!nskb)
+ return -1;
+ if ((*pskb)->sk)
+ skb_set_owner_w(nskb, (*pskb)->sk);
+ kfree_skb(*pskb);
+ *pskb = nskb;
+ }
+
+ return 0;
+}
+#endif /*CONFIG_INET*/
+
+/* This does not belong here, but locally generated errors need it if connection
+ tracking in use: without this, connection may not be in hash table, and hence
+ manufactured ICMP or RST packets will not be associated with it. */
+void (*ip_ct_attach)(struct sk_buff *, struct nf_ct_info *);
+
+void nf_ct_attach(struct sk_buff *new, struct sk_buff *skb)
+{
+ void (*attach)(struct sk_buff *, struct nf_ct_info *);
+
+ if (skb->nfct && (attach = ip_ct_attach) != NULL) {
+ mb(); /* Just to be sure: must be read before executing this */
+ attach(new, skb->nfct);
+ }
+}
+
+void __init netfilter_init(void)
+{
+ int i, h;
+
+ for (i = 0; i < NPROTO; i++) {
+ for (h = 0; h < NF_MAX_HOOKS; h++)
+ INIT_LIST_HEAD(&nf_hooks[i][h]);
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/core/pktgen.c b/uClinux-2.4.31-uc0/net/core/pktgen.c
new file mode 100644
index 0000000..1465093
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/pktgen.c
@@ -0,0 +1,1405 @@
+/* -*-linux-c-*-
+ * $Id: pktgen.c,v 1.8 2002/07/15 19:30:17 robert Exp $
+ * pktgen.c: Packet Generator for performance evaluation.
+ *
+ * Copyright 2001, 2002 by Robert Olsson <robert.olsson@its.uu.se>
+ * Uppsala University, Sweden
+ *
+ * A tool for loading the network with preconfigurated packets.
+ * The tool is implemented as a linux module. Parameters are output
+ * device, IPG (interpacket gap), number of packets, and whether
+ * to use multiple SKBs or just the same one.
+ * pktgen uses the installed interface's output routine.
+ *
+ * Additional hacking by:
+ *
+ * Jens.Laas@data.slu.se
+ * Improved by ANK. 010120.
+ * Improved by ANK even more. 010212.
+ * MAC address typo fixed. 010417 --ro
+ * Integrated. 020301 --DaveM
+ * Added multiskb option 020301 --DaveM
+ * Scaling of results. 020417--sigurdur@linpro.no
+ * Significant re-work of the module:
+ * * Updated to support generation over multiple interfaces at once
+ * by creating 32 /proc/net/pg* files. Each file can be manipulated
+ * individually.
+ * * Converted many counters to __u64 to allow longer runs.
+ * * Allow configuration of ranges, like min/max IP address, MACs,
+ * and UDP-ports, for both source and destination, and can
+ * set to use a random distribution or sequentially walk the range.
+ * * Can now change some values after starting.
+ * * Place 12-byte packet in UDP payload with magic number,
+ * sequence number, and timestamp. Will write receiver next.
+ * * The new changes seem to have a performance impact of around 1%,
+ * as far as I can tell.
+ * --Ben Greear <greearb@candelatech.com>
+ *
+ * Renamed multiskb to clone_skb and cleaned up sending core for two distinct
+ * skb modes. A clone_skb=0 mode for Ben "ranges" work and a clone_skb != 0
+ * as a "fastpath" with a configurable number of clones after alloc's.
+ *
+ * clone_skb=0 means all packets are allocated this also means ranges time
+ * stamps etc can be used. clone_skb=100 means 1 malloc is followed by 100
+ * clones.
+ *
+ * Also moved to /proc/net/pktgen/
+ * --ro
+ *
+ * Fix refcount off by one if first packet fails, potential null deref,
+ * memleak 030710- KJP
+ *
+ * See Documentation/networking/pktgen.txt for how to use this.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/ptrace.h>
+#include <linux/errno.h>
+#include <linux/ioport.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/inet.h>
+#include <asm/byteorder.h>
+#include <asm/bitops.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/uaccess.h>
+
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/proc_fs.h>
+#include <linux/if_arp.h>
+#include <net/checksum.h>
+#include <asm/timex.h>
+
+#define cycles() ((u32)get_cycles())
+
+
+#define VERSION "pktgen version 1.3"
+static char version[] __initdata =
+ "pktgen.c: v1.3: Packet Generator for packet performance testing.\n";
+
+/* Used to help with determining the pkts on receive */
+
+#define PKTGEN_MAGIC 0xbe9be955
+
+
+/* Keep information per interface */
+struct pktgen_info {
+ /* Parameters */
+
+ /* If min != max, then we will either do a linear iteration, or
+ * we will do a random selection from within the range.
+ */
+ __u32 flags;
+
+#define F_IPSRC_RND (1<<0) /* IP-Src Random */
+#define F_IPDST_RND (1<<1) /* IP-Dst Random */
+#define F_UDPSRC_RND (1<<2) /* UDP-Src Random */
+#define F_UDPDST_RND (1<<3) /* UDP-Dst Random */
+#define F_MACSRC_RND (1<<4) /* MAC-Src Random */
+#define F_MACDST_RND (1<<5) /* MAC-Dst Random */
+#define F_SET_SRCMAC (1<<6) /* Specify-Src-Mac
+ (default is to use Interface's MAC Addr) */
+#define F_SET_SRCIP (1<<7) /* Specify-Src-IP
+ (default is to use Interface's IP Addr) */
+
+
+ int pkt_size; /* = ETH_ZLEN; */
+ int nfrags;
+ __u32 ipg; /* Default Interpacket gap in nsec */
+ __u64 count; /* Default No packets to send */
+ __u64 sofar; /* How many pkts we've sent so far */
+ __u64 errors; /* Errors when trying to transmit, pkts will be re-sent */
+ struct timeval started_at;
+ struct timeval stopped_at;
+ __u64 idle_acc;
+ __u32 seq_num;
+
+ int clone_skb; /* Use multiple SKBs during packet gen. If this number
+ * is greater than 1, then that many coppies of the same
+ * packet will be sent before a new packet is allocated.
+ * For instance, if you want to send 1024 identical packets
+ * before creating a new packet, set clone_skb to 1024.
+ */
+ int busy;
+ int do_run_run; /* if this changes to false, the test will stop */
+
+ char outdev[32];
+ char dst_min[32];
+ char dst_max[32];
+ char src_min[32];
+ char src_max[32];
+
+ /* If we're doing ranges, random or incremental, then this
+ * defines the min/max for those ranges.
+ */
+ __u32 saddr_min; /* inclusive, source IP address */
+ __u32 saddr_max; /* exclusive, source IP address */
+ __u32 daddr_min; /* inclusive, dest IP address */
+ __u32 daddr_max; /* exclusive, dest IP address */
+
+ __u16 udp_src_min; /* inclusive, source UDP port */
+ __u16 udp_src_max; /* exclusive, source UDP port */
+ __u16 udp_dst_min; /* inclusive, dest UDP port */
+ __u16 udp_dst_max; /* exclusive, dest UDP port */
+
+ __u32 src_mac_count; /* How many MACs to iterate through */
+ __u32 dst_mac_count; /* How many MACs to iterate through */
+
+ unsigned char dst_mac[6];
+ unsigned char src_mac[6];
+
+ __u32 cur_dst_mac_offset;
+ __u32 cur_src_mac_offset;
+ __u32 cur_saddr;
+ __u32 cur_daddr;
+ __u16 cur_udp_dst;
+ __u16 cur_udp_src;
+
+ __u8 hh[14];
+ /* = {
+ 0x00, 0x80, 0xC8, 0x79, 0xB3, 0xCB,
+
+ We fill in SRC address later
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x08, 0x00
+ };
+ */
+ __u16 pad; /* pad out the hh struct to an even 16 bytes */
+ char result[512];
+
+ /* proc file names */
+ char fname[80];
+ char busy_fname[80];
+
+ struct proc_dir_entry *proc_ent;
+ struct proc_dir_entry *busy_proc_ent;
+};
+
+struct pktgen_hdr {
+ __u32 pgh_magic;
+ __u32 seq_num;
+ struct timeval timestamp;
+};
+
+static int cpu_speed;
+static int debug;
+
+/* Module parameters, defaults. */
+static int count_d = 100000;
+static int ipg_d = 0;
+static int clone_skb_d = 0;
+
+
+#define MAX_PKTGEN 8
+static struct pktgen_info pginfos[MAX_PKTGEN];
+
+
+/** Convert to miliseconds */
+inline __u64 tv_to_ms(const struct timeval* tv) {
+ __u64 ms = tv->tv_usec / 1000;
+ ms += (__u64)tv->tv_sec * (__u64)1000;
+ return ms;
+}
+
+inline __u64 getCurMs(void) {
+ struct timeval tv;
+ do_gettimeofday(&tv);
+ return tv_to_ms(&tv);
+}
+
+#define PG_PROC_DIR "pktgen"
+static struct proc_dir_entry *proc_dir = 0;
+
+static struct net_device *setup_inject(struct pktgen_info* info)
+{
+ struct net_device *odev;
+
+ rtnl_lock();
+ odev = __dev_get_by_name(info->outdev);
+ if (!odev) {
+ sprintf(info->result, "No such netdevice: \"%s\"", info->outdev);
+ goto out_unlock;
+ }
+
+ if (odev->type != ARPHRD_ETHER) {
+ sprintf(info->result, "Not ethernet device: \"%s\"", info->outdev);
+ goto out_unlock;
+ }
+
+ if (!netif_running(odev)) {
+ sprintf(info->result, "Device is down: \"%s\"", info->outdev);
+ goto out_unlock;
+ }
+
+ /* Default to the interface's mac if not explicitly set. */
+ if (!(info->flags & F_SET_SRCMAC)) {
+ memcpy(&(info->hh[6]), odev->dev_addr, 6);
+ }
+ else {
+ memcpy(&(info->hh[6]), info->src_mac, 6);
+ }
+
+ /* Set up Dest MAC */
+ memcpy(&(info->hh[0]), info->dst_mac, 6);
+
+ info->saddr_min = 0;
+ info->saddr_max = 0;
+ if (strlen(info->src_min) == 0) {
+ if (odev->ip_ptr) {
+ struct in_device *in_dev = odev->ip_ptr;
+
+ if (in_dev->ifa_list) {
+ info->saddr_min = in_dev->ifa_list->ifa_address;
+ info->saddr_max = info->saddr_min;
+ }
+ }
+ }
+ else {
+ info->saddr_min = in_aton(info->src_min);
+ info->saddr_max = in_aton(info->src_max);
+ }
+
+ info->daddr_min = in_aton(info->dst_min);
+ info->daddr_max = in_aton(info->dst_max);
+
+ /* Initialize current values. */
+ info->cur_dst_mac_offset = 0;
+ info->cur_src_mac_offset = 0;
+ info->cur_saddr = info->saddr_min;
+ info->cur_daddr = info->daddr_min;
+ info->cur_udp_dst = info->udp_dst_min;
+ info->cur_udp_src = info->udp_src_min;
+
+ atomic_inc(&odev->refcnt);
+ rtnl_unlock();
+
+ return odev;
+
+out_unlock:
+ rtnl_unlock();
+ return NULL;
+}
+
+static void nanospin(int ipg, struct pktgen_info* info)
+{
+ u32 idle_start, idle;
+
+ idle_start = cycles();
+
+ for (;;) {
+ barrier();
+ idle = cycles() - idle_start;
+ if (idle * 1000 >= ipg * cpu_speed)
+ break;
+ }
+ info->idle_acc += idle;
+}
+
+static int calc_mhz(void)
+{
+ struct timeval start, stop;
+ u32 start_s, elapsed;
+
+ do_gettimeofday(&start);
+ start_s = cycles();
+ do {
+ barrier();
+ elapsed = cycles() - start_s;
+ if (elapsed == 0)
+ return 0;
+ } while (elapsed < 1000 * 50000);
+ do_gettimeofday(&stop);
+ return elapsed/(stop.tv_usec-start.tv_usec+1000000*(stop.tv_sec-start.tv_sec));
+}
+
+static void cycles_calibrate(void)
+{
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ int res = calc_mhz();
+ if (res > cpu_speed)
+ cpu_speed = res;
+ }
+}
+
+
+/* Increment/randomize headers according to flags and current values
+ * for IP src/dest, UDP src/dst port, MAC-Addr src/dst
+ */
+static void mod_cur_headers(struct pktgen_info* info) {
+ __u32 imn;
+ __u32 imx;
+
+ /* Deal with source MAC */
+ if (info->src_mac_count > 1) {
+ __u32 mc;
+ __u32 tmp;
+ if (info->flags & F_MACSRC_RND) {
+ mc = net_random() % (info->src_mac_count);
+ }
+ else {
+ mc = info->cur_src_mac_offset++;
+ if (info->cur_src_mac_offset > info->src_mac_count) {
+ info->cur_src_mac_offset = 0;
+ }
+ }
+
+ tmp = info->src_mac[5] + (mc & 0xFF);
+ info->hh[11] = tmp;
+ tmp = (info->src_mac[4] + ((mc >> 8) & 0xFF) + (tmp >> 8));
+ info->hh[10] = tmp;
+ tmp = (info->src_mac[3] + ((mc >> 16) & 0xFF) + (tmp >> 8));
+ info->hh[9] = tmp;
+ tmp = (info->src_mac[2] + ((mc >> 24) & 0xFF) + (tmp >> 8));
+ info->hh[8] = tmp;
+ tmp = (info->src_mac[1] + (tmp >> 8));
+ info->hh[7] = tmp;
+ }
+
+ /* Deal with Destination MAC */
+ if (info->dst_mac_count > 1) {
+ __u32 mc;
+ __u32 tmp;
+ if (info->flags & F_MACDST_RND) {
+ mc = net_random() % (info->dst_mac_count);
+ }
+ else {
+ mc = info->cur_dst_mac_offset++;
+ if (info->cur_dst_mac_offset > info->dst_mac_count) {
+ info->cur_dst_mac_offset = 0;
+ }
+ }
+
+ tmp = info->dst_mac[5] + (mc & 0xFF);
+ info->hh[5] = tmp;
+ tmp = (info->dst_mac[4] + ((mc >> 8) & 0xFF) + (tmp >> 8));
+ info->hh[4] = tmp;
+ tmp = (info->dst_mac[3] + ((mc >> 16) & 0xFF) + (tmp >> 8));
+ info->hh[3] = tmp;
+ tmp = (info->dst_mac[2] + ((mc >> 24) & 0xFF) + (tmp >> 8));
+ info->hh[2] = tmp;
+ tmp = (info->dst_mac[1] + (tmp >> 8));
+ info->hh[1] = tmp;
+ }
+
+ if (info->udp_src_min < info->udp_src_max) {
+ if (info->flags & F_UDPSRC_RND) {
+ info->cur_udp_src = ((net_random() % (info->udp_src_max - info->udp_src_min))
+ + info->udp_src_min);
+ }
+ else {
+ info->cur_udp_src++;
+ if (info->cur_udp_src >= info->udp_src_max) {
+ info->cur_udp_src = info->udp_src_min;
+ }
+ }
+ }
+
+ if (info->udp_dst_min < info->udp_dst_max) {
+ if (info->flags & F_UDPDST_RND) {
+ info->cur_udp_dst = ((net_random() % (info->udp_dst_max - info->udp_dst_min))
+ + info->udp_dst_min);
+ }
+ else {
+ info->cur_udp_dst++;
+ if (info->cur_udp_dst >= info->udp_dst_max) {
+ info->cur_udp_dst = info->udp_dst_min;
+ }
+ }
+ }
+
+ if ((imn = ntohl(info->saddr_min)) < (imx = ntohl(info->saddr_max))) {
+ __u32 t;
+ if (info->flags & F_IPSRC_RND) {
+ t = ((net_random() % (imx - imn)) + imn);
+ }
+ else {
+ t = ntohl(info->cur_saddr);
+ t++;
+ if (t >= imx) {
+ t = imn;
+ }
+ }
+ info->cur_saddr = htonl(t);
+ }
+
+ if ((imn = ntohl(info->daddr_min)) < (imx = ntohl(info->daddr_max))) {
+ __u32 t;
+ if (info->flags & F_IPDST_RND) {
+ t = ((net_random() % (imx - imn)) + imn);
+ }
+ else {
+ t = ntohl(info->cur_daddr);
+ t++;
+ if (t >= imx) {
+ t = imn;
+ }
+ }
+ info->cur_daddr = htonl(t);
+ }
+}/* mod_cur_headers */
+
+
+static struct sk_buff *fill_packet(struct net_device *odev, struct pktgen_info* info)
+{
+ struct sk_buff *skb = NULL;
+ __u8 *eth;
+ struct udphdr *udph;
+ int datalen, iplen;
+ struct iphdr *iph;
+ struct pktgen_hdr *pgh = NULL;
+
+ skb = alloc_skb(info->pkt_size + 64 + 16, GFP_ATOMIC);
+ if (!skb) {
+ sprintf(info->result, "No memory");
+ return NULL;
+ }
+
+ skb_reserve(skb, 16);
+
+ /* Reserve for ethernet and IP header */
+ eth = (__u8 *) skb_push(skb, 14);
+ iph = (struct iphdr *)skb_put(skb, sizeof(struct iphdr));
+ udph = (struct udphdr *)skb_put(skb, sizeof(struct udphdr));
+
+ /* Update any of the values, used when we're incrementing various
+ * fields.
+ */
+ mod_cur_headers(info);
+
+ memcpy(eth, info->hh, 14);
+
+ datalen = info->pkt_size - 14 - 20 - 8; /* Eth + IPh + UDPh */
+ if (datalen < sizeof(struct pktgen_hdr)) {
+ datalen = sizeof(struct pktgen_hdr);
+ }
+
+ udph->source = htons(info->cur_udp_src);
+ udph->dest = htons(info->cur_udp_dst);
+ udph->len = htons(datalen + 8); /* DATA + udphdr */
+ udph->check = 0; /* No checksum */
+
+ iph->ihl = 5;
+ iph->version = 4;
+ iph->ttl = 3;
+ iph->tos = 0;
+ iph->protocol = IPPROTO_UDP; /* UDP */
+ iph->saddr = info->cur_saddr;
+ iph->daddr = info->cur_daddr;
+ iph->frag_off = 0;
+ iplen = 20 + 8 + datalen;
+ iph->tot_len = htons(iplen);
+ iph->check = 0;
+ iph->check = ip_fast_csum((void *) iph, iph->ihl);
+ skb->protocol = __constant_htons(ETH_P_IP);
+ skb->mac.raw = ((u8 *)iph) - 14;
+ skb->dev = odev;
+ skb->pkt_type = PACKET_HOST;
+
+ if (info->nfrags <= 0) {
+ pgh = (struct pktgen_hdr *)skb_put(skb, datalen);
+ } else {
+ int frags = info->nfrags;
+ int i;
+
+ /* TODO: Verify this is OK...it sure is ugly. --Ben */
+ pgh = (struct pktgen_hdr*)(((char*)(udph)) + 8);
+
+ if (frags > MAX_SKB_FRAGS)
+ frags = MAX_SKB_FRAGS;
+ if (datalen > frags*PAGE_SIZE) {
+ skb_put(skb, datalen-frags*PAGE_SIZE);
+ datalen = frags*PAGE_SIZE;
+ }
+
+ i = 0;
+ while (datalen > 0) {
+ struct page *page = alloc_pages(GFP_KERNEL, 0);
+ skb_shinfo(skb)->frags[i].page = page;
+ skb_shinfo(skb)->frags[i].page_offset = 0;
+ skb_shinfo(skb)->frags[i].size =
+ (datalen < PAGE_SIZE ? datalen : PAGE_SIZE);
+ datalen -= skb_shinfo(skb)->frags[i].size;
+ skb->len += skb_shinfo(skb)->frags[i].size;
+ skb->data_len += skb_shinfo(skb)->frags[i].size;
+ i++;
+ skb_shinfo(skb)->nr_frags = i;
+ }
+
+ while (i < frags) {
+ int rem;
+
+ if (i == 0)
+ break;
+
+ rem = skb_shinfo(skb)->frags[i - 1].size / 2;
+ if (rem == 0)
+ break;
+
+ skb_shinfo(skb)->frags[i - 1].size -= rem;
+
+ skb_shinfo(skb)->frags[i] = skb_shinfo(skb)->frags[i - 1];
+ get_page(skb_shinfo(skb)->frags[i].page);
+ skb_shinfo(skb)->frags[i].page = skb_shinfo(skb)->frags[i - 1].page;
+ skb_shinfo(skb)->frags[i].page_offset += skb_shinfo(skb)->frags[i - 1].size;
+ skb_shinfo(skb)->frags[i].size = rem;
+ i++;
+ skb_shinfo(skb)->nr_frags = i;
+ }
+ }
+
+ /* Stamp the time, and sequence number, convert them to network byte order */
+ if (pgh) {
+ pgh->pgh_magic = htonl(PKTGEN_MAGIC);
+ do_gettimeofday(&(pgh->timestamp));
+ pgh->timestamp.tv_usec = htonl(pgh->timestamp.tv_usec);
+ pgh->timestamp.tv_sec = htonl(pgh->timestamp.tv_sec);
+ pgh->seq_num = htonl(info->seq_num);
+ }
+
+ return skb;
+}
+
+
+static void inject(struct pktgen_info* info)
+{
+ struct net_device *odev = NULL;
+ struct sk_buff *skb = NULL;
+ __u64 total = 0;
+ __u64 idle = 0;
+ __u64 lcount = 0;
+ int nr_frags = 0;
+ int last_ok = 1; /* Was last skb sent?
+ * Or a failed transmit of some sort? This will keep
+ * sequence numbers in order, for example.
+ */
+ __u64 fp = 0;
+ __u32 fp_tmp = 0;
+
+ odev = setup_inject(info);
+ if (!odev)
+ return;
+
+ info->do_run_run = 1; /* Cranke yeself! */
+ info->idle_acc = 0;
+ info->sofar = 0;
+ lcount = info->count;
+
+
+ /* Build our initial pkt and place it as a re-try pkt. */
+ skb = fill_packet(odev, info);
+ if (skb == NULL) goto out_reldev;
+
+ do_gettimeofday(&(info->started_at));
+
+ while(info->do_run_run) {
+
+ /* Set a time-stamp, so build a new pkt each time */
+
+ if (last_ok) {
+ if (++fp_tmp >= info->clone_skb ) {
+ kfree_skb(skb);
+ skb = fill_packet(odev, info);
+ if (skb == NULL) {
+ goto out_reldev;
+ }
+ fp++;
+ fp_tmp = 0; /* reset counter */
+ }
+ }
+
+ nr_frags = skb_shinfo(skb)->nr_frags;
+
+ spin_lock_bh(&odev->xmit_lock);
+ if (!netif_queue_stopped(odev)) {
+
+ atomic_inc(&skb->users);
+
+ if (odev->hard_start_xmit(skb, odev)) {
+
+ atomic_dec(&skb->users);
+ if (net_ratelimit()) {
+ printk(KERN_INFO "Hard xmit error\n");
+ }
+ info->errors++;
+ last_ok = 0;
+ }
+ else {
+ last_ok = 1;
+ info->sofar++;
+ info->seq_num++;
+ }
+ }
+ else {
+ /* Re-try it next time */
+ last_ok = 0;
+ }
+
+
+ spin_unlock_bh(&odev->xmit_lock);
+
+ if (info->ipg) {
+ /* Try not to busy-spin if we have larger sleep times.
+ * TODO: Investigate better ways to do this.
+ */
+ if (info->ipg < 10000) { /* 10 usecs or less */
+ nanospin(info->ipg, info);
+ }
+ else if (info->ipg < 10000000) { /* 10ms or less */
+ udelay(info->ipg / 1000);
+ }
+ else {
+ mdelay(info->ipg / 1000000);
+ }
+ }
+
+ if (signal_pending(current)) {
+ break;
+ }
+
+ /* If lcount is zero, then run forever */
+ if ((lcount != 0) && (--lcount == 0)) {
+ if (atomic_read(&skb->users) != 1) {
+ u32 idle_start, idle;
+
+ idle_start = cycles();
+ while (atomic_read(&skb->users) != 1) {
+ if (signal_pending(current)) {
+ break;
+ }
+ schedule();
+ }
+ idle = cycles() - idle_start;
+ info->idle_acc += idle;
+ }
+ break;
+ }
+
+ if (netif_queue_stopped(odev) || current->need_resched) {
+ u32 idle_start, idle;
+
+ idle_start = cycles();
+ do {
+ if (signal_pending(current)) {
+ info->do_run_run = 0;
+ break;
+ }
+ if (!netif_running(odev)) {
+ info->do_run_run = 0;
+ break;
+ }
+ if (current->need_resched)
+ schedule();
+ else
+ do_softirq();
+ } while (netif_queue_stopped(odev));
+ idle = cycles() - idle_start;
+ info->idle_acc += idle;
+ }
+ }/* while we should be running */
+
+ do_gettimeofday(&(info->stopped_at));
+
+ total = (info->stopped_at.tv_sec - info->started_at.tv_sec) * 1000000 +
+ info->stopped_at.tv_usec - info->started_at.tv_usec;
+
+ idle = (__u32)(info->idle_acc)/(__u32)(cpu_speed);
+
+ {
+ char *p = info->result;
+ __u64 pps = (__u32)(info->sofar * 1000) / ((__u32)(total) / 1000);
+ __u64 bps = pps * 8 * (info->pkt_size + 4); /* take 32bit ethernet CRC into account */
+ p += sprintf(p, "OK: %llu(c%llu+d%llu) usec, %llu (%dbyte,%dfrags) %llupps %lluMb/sec (%llubps) errors: %llu",
+ (unsigned long long) total,
+ (unsigned long long) (total - idle),
+ (unsigned long long) idle,
+ (unsigned long long) info->sofar,
+ skb->len + 4, /* Add 4 to account for the ethernet checksum */
+ nr_frags,
+ (unsigned long long) pps,
+ (unsigned long long) (bps / (u64) 1024 / (u64) 1024),
+ (unsigned long long) bps,
+ (unsigned long long) info->errors
+ );
+ }
+
+ kfree_skb(skb);
+
+out_reldev:
+ if (odev) {
+ dev_put(odev);
+ odev = NULL;
+ }
+
+ return;
+
+}
+
+/* proc/net/pktgen/pg */
+
+static int proc_busy_read(char *buf , char **start, off_t offset,
+ int len, int *eof, void *data)
+{
+ char *p;
+ int idx = (int)(long)(data);
+ struct pktgen_info* info = NULL;
+
+ if ((idx < 0) || (idx >= MAX_PKTGEN)) {
+ printk("ERROR: idx: %i is out of range in proc_write\n", idx);
+ return -EINVAL;
+ }
+ info = &(pginfos[idx]);
+
+ p = buf;
+ p += sprintf(p, "%d\n", info->busy);
+ *eof = 1;
+
+ return p-buf;
+}
+
+static int proc_read(char *buf , char **start, off_t offset,
+ int len, int *eof, void *data)
+{
+ char *p;
+ int i;
+ int idx = (int)(long)(data);
+ struct pktgen_info* info = NULL;
+ __u64 sa;
+ __u64 stopped;
+ __u64 now = getCurMs();
+
+ if ((idx < 0) || (idx >= MAX_PKTGEN)) {
+ printk("ERROR: idx: %i is out of range in proc_write\n", idx);
+ return -EINVAL;
+ }
+ info = &(pginfos[idx]);
+
+ p = buf;
+ p += sprintf(p, "%s\n", VERSION); /* Help with parsing compatibility */
+ p += sprintf(p, "Params: count %llu pkt_size: %u frags: %d ipg: %u clone_skb: %d odev \"%s\"\n",
+ (unsigned long long) info->count,
+ info->pkt_size, info->nfrags, info->ipg,
+ info->clone_skb, info->outdev);
+ p += sprintf(p, " dst_min: %s dst_max: %s src_min: %s src_max: %s\n",
+ info->dst_min, info->dst_max, info->src_min, info->src_max);
+ p += sprintf(p, " src_mac: ");
+ for (i = 0; i < 6; i++) {
+ p += sprintf(p, "%02X%s", info->src_mac[i], i == 5 ? " " : ":");
+ }
+ p += sprintf(p, "dst_mac: ");
+ for (i = 0; i < 6; i++) {
+ p += sprintf(p, "%02X%s", info->dst_mac[i], i == 5 ? "\n" : ":");
+ }
+ p += sprintf(p, " udp_src_min: %d udp_src_max: %d udp_dst_min: %d udp_dst_max: %d\n",
+ info->udp_src_min, info->udp_src_max, info->udp_dst_min,
+ info->udp_dst_max);
+ p += sprintf(p, " src_mac_count: %d dst_mac_count: %d\n Flags: ",
+ info->src_mac_count, info->dst_mac_count);
+ if (info->flags & F_IPSRC_RND) {
+ p += sprintf(p, "IPSRC_RND ");
+ }
+ if (info->flags & F_IPDST_RND) {
+ p += sprintf(p, "IPDST_RND ");
+ }
+ if (info->flags & F_UDPSRC_RND) {
+ p += sprintf(p, "UDPSRC_RND ");
+ }
+ if (info->flags & F_UDPDST_RND) {
+ p += sprintf(p, "UDPDST_RND ");
+ }
+ if (info->flags & F_MACSRC_RND) {
+ p += sprintf(p, "MACSRC_RND ");
+ }
+ if (info->flags & F_MACDST_RND) {
+ p += sprintf(p, "MACDST_RND ");
+ }
+ p += sprintf(p, "\n");
+
+ sa = tv_to_ms(&(info->started_at));
+ stopped = tv_to_ms(&(info->stopped_at));
+ if (info->do_run_run) {
+ stopped = now; /* not really stopped, more like last-running-at */
+ }
+ p += sprintf(p, "Current:\n pkts-sofar: %llu errors: %llu\n started: %llums stopped: %llums now: %llums idle: %lluns\n",
+ (unsigned long long) info->sofar,
+ (unsigned long long) info->errors,
+ (unsigned long long) sa,
+ (unsigned long long) stopped,
+ (unsigned long long) now,
+ (unsigned long long) info->idle_acc);
+ p += sprintf(p, " seq_num: %d cur_dst_mac_offset: %d cur_src_mac_offset: %d\n",
+ info->seq_num, info->cur_dst_mac_offset, info->cur_src_mac_offset);
+ p += sprintf(p, " cur_saddr: 0x%x cur_daddr: 0x%x cur_udp_dst: %d cur_udp_src: %d\n",
+ info->cur_saddr, info->cur_daddr, info->cur_udp_dst, info->cur_udp_src);
+
+ if (info->result[0])
+ p += sprintf(p, "Result: %s\n", info->result);
+ else
+ p += sprintf(p, "Result: Idle\n");
+ *eof = 1;
+
+ return p - buf;
+}
+
+static int count_trail_chars(const char *user_buffer, unsigned int maxlen)
+{
+ int i;
+
+ for (i = 0; i < maxlen; i++) {
+ char c;
+
+ if (get_user(c, &user_buffer[i]))
+ return -EFAULT;
+ switch (c) {
+ case '\"':
+ case '\n':
+ case '\r':
+ case '\t':
+ case ' ':
+ case '=':
+ break;
+ default:
+ goto done;
+ };
+ }
+done:
+ return i;
+}
+
+static unsigned long num_arg(const char *user_buffer, unsigned long maxlen,
+ unsigned long *num)
+{
+ int i = 0;
+
+ *num = 0;
+
+ for(; i < maxlen; i++) {
+ char c;
+
+ if (get_user(c, &user_buffer[i]))
+ return -EFAULT;
+ if ((c >= '0') && (c <= '9')) {
+ *num *= 10;
+ *num += c -'0';
+ } else
+ break;
+ }
+ return i;
+}
+
+static int strn_len(const char *user_buffer, unsigned int maxlen)
+{
+ int i = 0;
+
+ for(; i < maxlen; i++) {
+ char c;
+
+ if (get_user(c, &user_buffer[i]))
+ return -EFAULT;
+ switch (c) {
+ case '\"':
+ case '\n':
+ case '\r':
+ case '\t':
+ case ' ':
+ goto done_str;
+ default:
+ break;
+ };
+ }
+done_str:
+ return i;
+}
+
+static int proc_write(struct file *file, const char *user_buffer,
+ unsigned long count, void *data)
+{
+ int i = 0, max, len;
+ char name[16], valstr[32];
+ unsigned long value = 0;
+ int idx = (int)(long)(data);
+ struct pktgen_info* info = NULL;
+ char* result = NULL;
+ int tmp;
+
+ if ((idx < 0) || (idx >= MAX_PKTGEN)) {
+ printk("ERROR: idx: %i is out of range in proc_write\n", idx);
+ return -EINVAL;
+ }
+ info = &(pginfos[idx]);
+ result = &(info->result[0]);
+
+ if (count < 1) {
+ sprintf(result, "Wrong command format");
+ return -EINVAL;
+ }
+
+ max = count - i;
+ tmp = count_trail_chars(&user_buffer[i], max);
+ if (tmp < 0)
+ return tmp;
+ i += tmp;
+
+ /* Read variable name */
+
+ len = strn_len(&user_buffer[i], sizeof(name) - 1);
+ if (len < 0)
+ return len;
+ memset(name, 0, sizeof(name));
+ if (copy_from_user(name, &user_buffer[i], len))
+ return -EFAULT;
+ i += len;
+
+ max = count -i;
+ len = count_trail_chars(&user_buffer[i], max);
+ if (len < 0)
+ return len;
+ i += len;
+
+ if (debug)
+ printk("pg: %s,%lu\n", name, count);
+
+ if (!strcmp(name, "stop")) {
+ if (info->do_run_run) {
+ strcpy(result, "Stopping");
+ }
+ else {
+ strcpy(result, "Already stopped...\n");
+ }
+ info->do_run_run = 0;
+ return count;
+ }
+
+ if (!strcmp(name, "pkt_size")) {
+ len = num_arg(&user_buffer[i], 10, &value);
+ if (len < 0)
+ return len;
+ i += len;
+ if (value < 14+20+8)
+ value = 14+20+8;
+ info->pkt_size = value;
+ sprintf(result, "OK: pkt_size=%u", info->pkt_size);
+ return count;
+ }
+ if (!strcmp(name, "frags")) {
+ len = num_arg(&user_buffer[i], 10, &value);
+ if (len < 0)
+ return len;
+ i += len;
+ info->nfrags = value;
+ sprintf(result, "OK: frags=%u", info->nfrags);
+ return count;
+ }
+ if (!strcmp(name, "ipg")) {
+ len = num_arg(&user_buffer[i], 10, &value);
+ if (len < 0)
+ return len;
+ i += len;
+ info->ipg = value;
+ sprintf(result, "OK: ipg=%u", info->ipg);
+ return count;
+ }
+ if (!strcmp(name, "udp_src_min")) {
+ len = num_arg(&user_buffer[i], 10, &value);
+ if (len < 0)
+ return len;
+ i += len;
+ info->udp_src_min = value;
+ sprintf(result, "OK: udp_src_min=%u", info->udp_src_min);
+ return count;
+ }
+ if (!strcmp(name, "udp_dst_min")) {
+ len = num_arg(&user_buffer[i], 10, &value);
+ if (len < 0)
+ return len;
+ i += len;
+ info->udp_dst_min = value;
+ sprintf(result, "OK: udp_dst_min=%u", info->udp_dst_min);
+ return count;
+ }
+ if (!strcmp(name, "udp_src_max")) {
+ len = num_arg(&user_buffer[i], 10, &value);
+ if (len < 0)
+ return len;
+ i += len;
+ info->udp_src_max = value;
+ sprintf(result, "OK: udp_src_max=%u", info->udp_src_max);
+ return count;
+ }
+ if (!strcmp(name, "udp_dst_max")) {
+ len = num_arg(&user_buffer[i], 10, &value);
+ if (len < 0)
+ return len;
+ i += len;
+ info->udp_dst_max = value;
+ sprintf(result, "OK: udp_dst_max=%u", info->udp_dst_max);
+ return count;
+ }
+ if (!strcmp(name, "clone_skb")) {
+ len = num_arg(&user_buffer[i], 10, &value);
+ if (len < 0)
+ return len;
+ i += len;
+ info->clone_skb = value;
+
+ sprintf(result, "OK: clone_skb=%d", info->clone_skb);
+ return count;
+ }
+ if (!strcmp(name, "count")) {
+ len = num_arg(&user_buffer[i], 10, &value);
+ if (len < 0)
+ return len;
+ i += len;
+ info->count = value;
+ sprintf(result, "OK: count=%llu", (unsigned long long) info->count);
+ return count;
+ }
+ if (!strcmp(name, "src_mac_count")) {
+ len = num_arg(&user_buffer[i], 10, &value);
+ if (len < 0)
+ return len;
+ i += len;
+ info->src_mac_count = value;
+ sprintf(result, "OK: src_mac_count=%d", info->src_mac_count);
+ return count;
+ }
+ if (!strcmp(name, "dst_mac_count")) {
+ len = num_arg(&user_buffer[i], 10, &value);
+ if (len < 0)
+ return len;
+ i += len;
+ info->dst_mac_count = value;
+ sprintf(result, "OK: dst_mac_count=%d", info->dst_mac_count);
+ return count;
+ }
+ if (!strcmp(name, "odev")) {
+ len = strn_len(&user_buffer[i], sizeof(info->outdev) - 1);
+ if (len < 0)
+ return len;
+ memset(info->outdev, 0, sizeof(info->outdev));
+ if (copy_from_user(info->outdev, &user_buffer[i], len))
+ return -EFAULT;
+ i += len;
+ sprintf(result, "OK: odev=%s", info->outdev);
+ return count;
+ }
+ if (!strcmp(name, "flag")) {
+ char f[32];
+ len = strn_len(&user_buffer[i], sizeof(f) - 1);
+ if (len < 0)
+ return len;
+ memset(f, 0, 32);
+ if (copy_from_user(f, &user_buffer[i], len))
+ return -EFAULT;
+ i += len;
+ if (strcmp(f, "IPSRC_RND") == 0) {
+ info->flags |= F_IPSRC_RND;
+ }
+ else if (strcmp(f, "!IPSRC_RND") == 0) {
+ info->flags &= ~F_IPSRC_RND;
+ }
+ else if (strcmp(f, "IPDST_RND") == 0) {
+ info->flags |= F_IPDST_RND;
+ }
+ else if (strcmp(f, "!IPDST_RND") == 0) {
+ info->flags &= ~F_IPDST_RND;
+ }
+ else if (strcmp(f, "UDPSRC_RND") == 0) {
+ info->flags |= F_UDPSRC_RND;
+ }
+ else if (strcmp(f, "!UDPSRC_RND") == 0) {
+ info->flags &= ~F_UDPSRC_RND;
+ }
+ else if (strcmp(f, "UDPDST_RND") == 0) {
+ info->flags |= F_UDPDST_RND;
+ }
+ else if (strcmp(f, "!UDPDST_RND") == 0) {
+ info->flags &= ~F_UDPDST_RND;
+ }
+ else if (strcmp(f, "MACSRC_RND") == 0) {
+ info->flags |= F_MACSRC_RND;
+ }
+ else if (strcmp(f, "!MACSRC_RND") == 0) {
+ info->flags &= ~F_MACSRC_RND;
+ }
+ else if (strcmp(f, "MACDST_RND") == 0) {
+ info->flags |= F_MACDST_RND;
+ }
+ else if (strcmp(f, "!MACDST_RND") == 0) {
+ info->flags &= ~F_MACDST_RND;
+ }
+ else {
+ sprintf(result, "Flag -:%s:- unknown\nAvailable flags, (prepend ! to un-set flag):\n%s",
+ f,
+ "IPSRC_RND, IPDST_RND, UDPSRC_RND, UDPDST_RND, MACSRC_RND, MACDST_RND\n");
+ return count;
+ }
+ sprintf(result, "OK: flags=0x%x", info->flags);
+ return count;
+ }
+ if (!strcmp(name, "dst_min") || !strcmp(name, "dst")) {
+ len = strn_len(&user_buffer[i], sizeof(info->dst_min) - 1);
+ if (len < 0)
+ return len;
+ memset(info->dst_min, 0, sizeof(info->dst_min));
+ if (copy_from_user(info->dst_min, &user_buffer[i], len))
+ return -EFAULT;
+ if(debug)
+ printk("pg: dst_min set to: %s\n", info->dst_min);
+ i += len;
+ sprintf(result, "OK: dst_min=%s", info->dst_min);
+ return count;
+ }
+ if (!strcmp(name, "dst_max")) {
+ len = strn_len(&user_buffer[i], sizeof(info->dst_max) - 1);
+ if (len < 0)
+ return len;
+ memset(info->dst_max, 0, sizeof(info->dst_max));
+ if (copy_from_user(info->dst_max, &user_buffer[i], len))
+ return -EFAULT;
+ if(debug)
+ printk("pg: dst_max set to: %s\n", info->dst_max);
+ i += len;
+ sprintf(result, "OK: dst_max=%s", info->dst_max);
+ return count;
+ }
+ if (!strcmp(name, "src_min")) {
+ len = strn_len(&user_buffer[i], sizeof(info->src_min) - 1);
+ if (len < 0)
+ return len;
+ memset(info->src_min, 0, sizeof(info->src_min));
+ if (copy_from_user(info->src_min, &user_buffer[i], len))
+ return -EFAULT;
+ if(debug)
+ printk("pg: src_min set to: %s\n", info->src_min);
+ i += len;
+ sprintf(result, "OK: src_min=%s", info->src_min);
+ return count;
+ }
+ if (!strcmp(name, "src_max")) {
+ len = strn_len(&user_buffer[i], sizeof(info->src_max) - 1);
+ if (len < 0)
+ return len;
+ memset(info->src_max, 0, sizeof(info->src_max));
+ if (copy_from_user(info->src_max, &user_buffer[i], len))
+ return -EFAULT;
+ if(debug)
+ printk("pg: src_max set to: %s\n", info->src_max);
+ i += len;
+ sprintf(result, "OK: src_max=%s", info->src_max);
+ return count;
+ }
+ if (!strcmp(name, "dstmac")) {
+ char *v = valstr;
+ unsigned char *m = info->dst_mac;
+
+ len = strn_len(&user_buffer[i], sizeof(valstr) - 1);
+ if (len < 0)
+ return len;
+ memset(valstr, 0, sizeof(valstr));
+ if (copy_from_user(valstr, &user_buffer[i], len))
+ return -EFAULT;
+ i += len;
+
+ for(*m = 0;*v && m < info->dst_mac + 6; v++) {
+ if (*v >= '0' && *v <= '9') {
+ *m *= 16;
+ *m += *v - '0';
+ }
+ if (*v >= 'A' && *v <= 'F') {
+ *m *= 16;
+ *m += *v - 'A' + 10;
+ }
+ if (*v >= 'a' && *v <= 'f') {
+ *m *= 16;
+ *m += *v - 'a' + 10;
+ }
+ if (*v == ':') {
+ m++;
+ *m = 0;
+ }
+ }
+ sprintf(result, "OK: dstmac");
+ return count;
+ }
+ if (!strcmp(name, "srcmac")) {
+ char *v = valstr;
+ unsigned char *m = info->src_mac;
+
+ len = strn_len(&user_buffer[i], sizeof(valstr) - 1);
+ if (len < 0)
+ return len;
+ memset(valstr, 0, sizeof(valstr));
+ if (copy_from_user(valstr, &user_buffer[i], len))
+ return -EFAULT;
+ i += len;
+
+ for(*m = 0;*v && m < info->src_mac + 6; v++) {
+ if (*v >= '0' && *v <= '9') {
+ *m *= 16;
+ *m += *v - '0';
+ }
+ if (*v >= 'A' && *v <= 'F') {
+ *m *= 16;
+ *m += *v - 'A' + 10;
+ }
+ if (*v >= 'a' && *v <= 'f') {
+ *m *= 16;
+ *m += *v - 'a' + 10;
+ }
+ if (*v == ':') {
+ m++;
+ *m = 0;
+ }
+ }
+ sprintf(result, "OK: srcmac");
+ return count;
+ }
+
+ if (!strcmp(name, "inject") || !strcmp(name, "start")) {
+ MOD_INC_USE_COUNT;
+ if (info->busy) {
+ strcpy(info->result, "Already running...\n");
+ }
+ else {
+ info->busy = 1;
+ strcpy(info->result, "Starting");
+ inject(info);
+ info->busy = 0;
+ }
+ MOD_DEC_USE_COUNT;
+ return count;
+ }
+
+ sprintf(info->result, "No such parameter \"%s\"", name);
+ return -EINVAL;
+}
+
+
+int create_proc_dir(void)
+{
+ int len;
+ /* does proc_dir already exists */
+ len = strlen(PG_PROC_DIR);
+
+ for (proc_dir = proc_net->subdir; proc_dir;
+ proc_dir=proc_dir->next) {
+ if ((proc_dir->namelen == len) &&
+ (! memcmp(proc_dir->name, PG_PROC_DIR, len)))
+ break;
+ }
+ if (!proc_dir)
+ proc_dir = create_proc_entry(PG_PROC_DIR, S_IFDIR, proc_net);
+ if (!proc_dir) return -ENODEV;
+ return 1;
+}
+
+int remove_proc_dir(void)
+{
+ remove_proc_entry(PG_PROC_DIR, proc_net);
+ return 1;
+}
+
+static int __init init(void)
+{
+ int i;
+ printk(version);
+ cycles_calibrate();
+ if (cpu_speed == 0) {
+ printk("pktgen: Error: your machine does not have working cycle counter.\n");
+ return -EINVAL;
+ }
+
+ create_proc_dir();
+
+ for (i = 0; i<MAX_PKTGEN; i++) {
+ memset(&(pginfos[i]), 0, sizeof(pginfos[i]));
+ pginfos[i].pkt_size = ETH_ZLEN;
+ pginfos[i].nfrags = 0;
+ pginfos[i].clone_skb = clone_skb_d;
+ pginfos[i].ipg = ipg_d;
+ pginfos[i].count = count_d;
+ pginfos[i].sofar = 0;
+ pginfos[i].hh[12] = 0x08; /* fill in protocol. Rest is filled in later. */
+ pginfos[i].hh[13] = 0x00;
+ pginfos[i].udp_src_min = 9; /* sink NULL */
+ pginfos[i].udp_src_max = 9;
+ pginfos[i].udp_dst_min = 9;
+ pginfos[i].udp_dst_max = 9;
+
+ sprintf(pginfos[i].fname, "net/%s/pg%i", PG_PROC_DIR, i);
+ pginfos[i].proc_ent = create_proc_entry(pginfos[i].fname, 0600, 0);
+ if (!pginfos[i].proc_ent) {
+ printk("pktgen: Error: cannot create net/%s/pg procfs entry.\n", PG_PROC_DIR);
+ goto cleanup_mem;
+ }
+ pginfos[i].proc_ent->read_proc = proc_read;
+ pginfos[i].proc_ent->write_proc = proc_write;
+ pginfos[i].proc_ent->data = (void*)(long)(i);
+
+ sprintf(pginfos[i].busy_fname, "net/%s/pg_busy%i", PG_PROC_DIR, i);
+ pginfos[i].busy_proc_ent = create_proc_entry(pginfos[i].busy_fname, 0, 0);
+ if (!pginfos[i].busy_proc_ent) {
+ printk("pktgen: Error: cannot create net/%s/pg_busy procfs entry.\n", PG_PROC_DIR);
+ goto cleanup_mem;
+ }
+ pginfos[i].busy_proc_ent->read_proc = proc_busy_read;
+ pginfos[i].busy_proc_ent->data = (void*)(long)(i);
+ }
+ return 0;
+
+cleanup_mem:
+ for (i = 0; i<MAX_PKTGEN; i++) {
+ if (strlen(pginfos[i].fname)) {
+ remove_proc_entry(pginfos[i].fname, NULL);
+ }
+ if (strlen(pginfos[i].busy_fname)) {
+ remove_proc_entry(pginfos[i].busy_fname, NULL);
+ }
+ }
+ return -ENOMEM;
+}
+
+
+static void __exit cleanup(void)
+{
+ int i;
+ for (i = 0; i<MAX_PKTGEN; i++) {
+ if (strlen(pginfos[i].fname)) {
+ remove_proc_entry(pginfos[i].fname, NULL);
+ }
+ if (strlen(pginfos[i].busy_fname)) {
+ remove_proc_entry(pginfos[i].busy_fname, NULL);
+ }
+ }
+ remove_proc_dir();
+}
+
+module_init(init);
+module_exit(cleanup);
+
+MODULE_AUTHOR("Robert Olsson <robert.olsson@its.uu.se");
+MODULE_DESCRIPTION("Packet Generator tool");
+MODULE_LICENSE("GPL");
+MODULE_PARM(count_d, "i");
+MODULE_PARM(ipg_d, "i");
+MODULE_PARM(cpu_speed, "i");
+MODULE_PARM(clone_skb_d, "i");
+
+
+
diff --git a/uClinux-2.4.31-uc0/net/core/profile.c b/uClinux-2.4.31-uc0/net/core/profile.c
new file mode 100644
index 0000000..a3ff364
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/profile.c
@@ -0,0 +1,293 @@
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/netdevice.h>
+#include <linux/string.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <linux/ip.h>
+#include <linux/inet.h>
+#include <net/checksum.h>
+
+#include <asm/processor.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#include <net/profile.h>
+
+#ifdef CONFIG_NET_PROFILE
+
+atomic_t net_profile_active;
+struct timeval net_profile_adjust;
+
+NET_PROFILE_DEFINE(total);
+
+struct net_profile_slot *net_profile_chain = &net_prof_total;
+
+#ifdef __alpha__
+__u32 alpha_lo;
+long alpha_hi;
+
+static void alpha_tick(unsigned long);
+
+static struct timer_list alpha_timer =
+ { NULL, NULL, 0, 0L, alpha_tick };
+
+void alpha_tick(unsigned long dummy)
+{
+ struct timeval dummy_stamp;
+ net_profile_stamp(&dummy_stamp);
+ alpha_timer.expires = jiffies + 4*HZ;
+ add_timer(&alpha_timer);
+}
+
+#endif
+
+void net_profile_irq_adjust(struct timeval *entered, struct timeval* leaved)
+{
+ struct net_profile_slot *s;
+
+ net_profile_sub(entered, leaved);
+ for (s = net_profile_chain; s; s = s->next) {
+ if (s->active)
+ net_profile_add(leaved, &s->irq);
+ }
+}
+
+
+#ifdef CONFIG_PROC_FS
+static int profile_read_proc(char *buffer, char **start, off_t offset,
+ int length, int *eof, void *data)
+{
+ off_t pos=0;
+ off_t begin=0;
+ int len=0;
+ struct net_profile_slot *s;
+
+ len+= sprintf(buffer, "Slot Hits Hi Lo OnIrqHi OnIrqLo Ufl\n");
+
+ if (offset == 0) {
+ cli();
+ net_prof_total.active = 1;
+ atomic_inc(&net_profile_active);
+ NET_PROFILE_LEAVE(total);
+ sti();
+ }
+ for (s = net_profile_chain; s; s = s->next) {
+ struct net_profile_slot tmp;
+
+ cli();
+ tmp = *s;
+
+ /* Wrong, but pretty close to truth */
+
+ s->accumulator.tv_sec = 0;
+ s->accumulator.tv_usec = 0;
+ s->irq.tv_sec = 0;
+ s->irq.tv_usec = 0;
+ s->hits = 0;
+ s->underflow = 0;
+ /* Repair active count, it is possible, only if code has a bug */
+ if (s->active) {
+ s->active = 0;
+ atomic_dec(&net_profile_active);
+ }
+ sti();
+
+ net_profile_sub(&tmp.irq, &tmp.accumulator);
+
+ len += sprintf(buffer+len,"%-15s %-10d %-10ld %-10lu %-10lu %-10lu %d/%d",
+ tmp.id,
+ tmp.hits,
+ tmp.accumulator.tv_sec,
+ tmp.accumulator.tv_usec,
+ tmp.irq.tv_sec,
+ tmp.irq.tv_usec,
+ tmp.underflow, tmp.active);
+
+ buffer[len++]='\n';
+
+ pos=begin+len;
+ if(pos<offset) {
+ len=0;
+ begin=pos;
+ }
+ if(pos>offset+length)
+ goto done;
+ }
+ *eof = 1;
+
+done:
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ if (len < 0)
+ len = 0;
+ if (offset == 0) {
+ cli();
+ net_prof_total.active = 0;
+ net_prof_total.hits = 0;
+ net_profile_stamp(&net_prof_total.entered);
+ sti();
+ }
+ return len;
+}
+#endif
+
+struct iphdr whitehole_iph;
+int whitehole_count;
+
+static int whitehole_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_device_stats *stats;
+
+ stats = (struct net_device_stats *)dev->priv;
+ stats->tx_packets++;
+ stats->tx_bytes+=skb->len;
+
+ dev_kfree_skb(skb);
+ return 0;
+}
+
+static void whitehole_inject(unsigned long);
+int whitehole_init(struct net_device *dev);
+
+static struct timer_list whitehole_timer =
+ { NULL, NULL, 0, 0L, whitehole_inject };
+
+static struct net_device whitehole_dev = {
+ "whitehole", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NULL, whitehole_init, };
+
+static int whitehole_open(struct net_device *dev)
+{
+ whitehole_count = 100000;
+ whitehole_timer.expires = jiffies + 5*HZ;
+ add_timer(&whitehole_timer);
+ return 0;
+}
+
+static int whitehole_close(struct net_device *dev)
+{
+ del_timer(&whitehole_timer);
+ return 0;
+}
+
+static void whitehole_inject(unsigned long dummy)
+{
+ struct net_device_stats *stats = (struct net_device_stats *)whitehole_dev.priv;
+ extern int netdev_dropping;
+
+ do {
+ struct iphdr *iph;
+ struct sk_buff *skb = alloc_skb(128, GFP_ATOMIC);
+ if (!skb)
+ break;
+ skb_reserve(skb, 32);
+ iph = (struct iphdr*)skb_put(skb, sizeof(*iph));
+ skb->mac.raw = ((u8*)iph) - 14;
+ memcpy(iph, &whitehole_iph, sizeof(*iph));
+ skb->protocol = __constant_htons(ETH_P_IP);
+ skb->dev = &whitehole_dev;
+ skb->pkt_type = PACKET_HOST;
+ stats->rx_packets++;
+ stats->rx_bytes += skb->len;
+ netif_rx(skb);
+ whitehole_count--;
+ } while (netdev_dropping == 0 && whitehole_count>0);
+ if (whitehole_count > 0) {
+ whitehole_timer.expires = jiffies + 1;
+ add_timer(&whitehole_timer);
+ }
+}
+
+static struct net_device_stats *whitehole_get_stats(struct net_device *dev)
+{
+ struct net_device_stats *stats = (struct net_device_stats *) dev->priv;
+ return stats;
+}
+
+int __init whitehole_init(struct net_device *dev)
+{
+ dev->priv = kmalloc(sizeof(struct net_device_stats), GFP_KERNEL);
+ if (dev->priv == NULL)
+ return -ENOBUFS;
+ memset(dev->priv, 0, sizeof(struct net_device_stats));
+ dev->get_stats = whitehole_get_stats;
+ dev->hard_start_xmit = whitehole_xmit;
+ dev->open = whitehole_open;
+ dev->stop = whitehole_close;
+ ether_setup(dev);
+ dev->tx_queue_len = 0;
+ dev->flags |= IFF_NOARP;
+ dev->flags &= ~(IFF_BROADCAST|IFF_MULTICAST);
+ dev->iflink = 0;
+ whitehole_iph.ihl = 5;
+ whitehole_iph.version = 4;
+ whitehole_iph.ttl = 2;
+ whitehole_iph.saddr = in_aton("193.233.7.21");
+ whitehole_iph.daddr = in_aton("193.233.7.10");
+ whitehole_iph.tot_len = htons(20);
+ whitehole_iph.check = ip_compute_csum((void *)&whitehole_iph, 20);
+ return 0;
+}
+
+int net_profile_register(struct net_profile_slot *slot)
+{
+ cli();
+ slot->next = net_profile_chain;
+ net_profile_chain = slot;
+ sti();
+ return 0;
+}
+
+int net_profile_unregister(struct net_profile_slot *slot)
+{
+ struct net_profile_slot **sp, *s;
+
+ for (sp = &net_profile_chain; (s = *sp) != NULL; sp = &s->next) {
+ if (s == slot) {
+ cli();
+ *sp = s->next;
+ sti();
+ return 0;
+ }
+ }
+ return -ESRCH;
+}
+
+
+int __init net_profile_init(void)
+{
+ int i;
+
+#ifdef CONFIG_PROC_FS
+ create_proc_read_entry("net/profile", 0, 0, profile_read_proc, NULL);
+#endif
+
+ register_netdevice(&whitehole_dev);
+
+ printk("Evaluating net profiler cost ...");
+#ifdef __alpha__
+ alpha_tick(0);
+#endif
+ for (i=0; i<1024; i++) {
+ NET_PROFILE_ENTER(total);
+ NET_PROFILE_LEAVE(total);
+ }
+ if (net_prof_total.accumulator.tv_sec) {
+ printk(" too high!\n");
+ } else {
+ net_profile_adjust.tv_usec = net_prof_total.accumulator.tv_usec>>10;
+ printk("%ld units\n", net_profile_adjust.tv_usec);
+ }
+ net_prof_total.hits = 0;
+ net_profile_stamp(&net_prof_total.entered);
+ return 0;
+}
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/core/rtnetlink.c b/uClinux-2.4.31-uc0/net/core/rtnetlink.c
new file mode 100644
index 0000000..cb4baf9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/rtnetlink.c
@@ -0,0 +1,530 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * Routing netlink socket interface: protocol independent part.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Fixes:
+ * Vitaly E. Lavrov RTA_OK arithmetics was wrong.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/capability.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/string.h>
+
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <net/arp.h>
+#include <net/route.h>
+#include <net/udp.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+DECLARE_MUTEX(rtnl_sem);
+
+void rtnl_lock(void)
+{
+ rtnl_shlock();
+ rtnl_exlock();
+}
+
+void rtnl_unlock(void)
+{
+ rtnl_exunlock();
+ rtnl_shunlock();
+}
+
+int rtattr_parse(struct rtattr *tb[], int maxattr, struct rtattr *rta, int len)
+{
+ memset(tb, 0, sizeof(struct rtattr*)*maxattr);
+
+ while (RTA_OK(rta, len)) {
+ unsigned flavor = rta->rta_type;
+ if (flavor && flavor <= maxattr)
+ tb[flavor-1] = rta;
+ rta = RTA_NEXT(rta, len);
+ }
+ return 0;
+}
+
+struct sock *rtnl;
+
+struct rtnetlink_link * rtnetlink_links[NPROTO];
+
+static const int rtm_min[(RTM_MAX+1-RTM_BASE)/4] =
+{
+ NLMSG_LENGTH(sizeof(struct ifinfomsg)),
+ NLMSG_LENGTH(sizeof(struct ifaddrmsg)),
+ NLMSG_LENGTH(sizeof(struct rtmsg)),
+ NLMSG_LENGTH(sizeof(struct ndmsg)),
+ NLMSG_LENGTH(sizeof(struct rtmsg)),
+ NLMSG_LENGTH(sizeof(struct tcmsg)),
+ NLMSG_LENGTH(sizeof(struct tcmsg)),
+ NLMSG_LENGTH(sizeof(struct tcmsg))
+};
+
+static const int rta_max[(RTM_MAX+1-RTM_BASE)/4] =
+{
+ IFLA_MAX,
+ IFA_MAX,
+ RTA_MAX,
+ NDA_MAX,
+ RTA_MAX,
+ TCA_MAX,
+ TCA_MAX,
+ TCA_MAX
+};
+
+void __rta_fill(struct sk_buff *skb, int attrtype, int attrlen, const void *data)
+{
+ struct rtattr *rta;
+ int size = RTA_LENGTH(attrlen);
+
+ rta = (struct rtattr*)skb_put(skb, RTA_ALIGN(size));
+ rta->rta_type = attrtype;
+ rta->rta_len = size;
+ memcpy(RTA_DATA(rta), data, attrlen);
+}
+
+int rtnetlink_send(struct sk_buff *skb, u32 pid, unsigned group, int echo)
+{
+ int err = 0;
+
+ NETLINK_CB(skb).dst_groups = group;
+ if (echo)
+ atomic_inc(&skb->users);
+ netlink_broadcast(rtnl, skb, pid, group, GFP_KERNEL);
+ if (echo)
+ err = netlink_unicast(rtnl, skb, pid, MSG_DONTWAIT);
+ return err;
+}
+
+int rtnetlink_put_metrics(struct sk_buff *skb, unsigned *metrics)
+{
+ struct rtattr *mx = (struct rtattr*)skb->tail;
+ int i;
+
+ RTA_PUT(skb, RTA_METRICS, 0, NULL);
+ for (i=0; i<RTAX_MAX; i++) {
+ if (metrics[i])
+ RTA_PUT(skb, i+1, sizeof(unsigned), metrics+i);
+ }
+ mx->rta_len = skb->tail - (u8*)mx;
+ if (mx->rta_len == RTA_LENGTH(0))
+ skb_trim(skb, (u8*)mx - skb->data);
+ return 0;
+
+rtattr_failure:
+ skb_trim(skb, (u8*)mx - skb->data);
+ return -1;
+}
+
+
+static int rtnetlink_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
+ int type, u32 pid, u32 seq, u32 change)
+{
+ struct ifinfomsg *r;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+ nlh = NLMSG_PUT(skb, pid, seq, type, sizeof(*r));
+ if (pid) nlh->nlmsg_flags |= NLM_F_MULTI;
+ r = NLMSG_DATA(nlh);
+ r->ifi_family = AF_UNSPEC;
+ r->ifi_type = dev->type;
+ r->ifi_index = dev->ifindex;
+ r->ifi_flags = dev->flags;
+ r->ifi_change = change;
+
+ if (!netif_running(dev) || !netif_carrier_ok(dev))
+ r->ifi_flags &= ~IFF_RUNNING;
+ else
+ r->ifi_flags |= IFF_RUNNING;
+
+ RTA_PUT(skb, IFLA_IFNAME, strlen(dev->name)+1, dev->name);
+ if (dev->addr_len) {
+ RTA_PUT(skb, IFLA_ADDRESS, dev->addr_len, dev->dev_addr);
+ RTA_PUT(skb, IFLA_BROADCAST, dev->addr_len, dev->broadcast);
+ }
+ if (1) {
+ unsigned mtu = dev->mtu;
+ RTA_PUT(skb, IFLA_MTU, sizeof(mtu), &mtu);
+ }
+ if (dev->ifindex != dev->iflink)
+ RTA_PUT(skb, IFLA_LINK, sizeof(int), &dev->iflink);
+ if (dev->qdisc_sleeping)
+ RTA_PUT(skb, IFLA_QDISC,
+ strlen(dev->qdisc_sleeping->ops->id) + 1,
+ dev->qdisc_sleeping->ops->id);
+ if (dev->master)
+ RTA_PUT(skb, IFLA_MASTER, sizeof(int), &dev->master->ifindex);
+ if (dev->get_stats) {
+ struct net_device_stats *stats = dev->get_stats(dev);
+ if (stats)
+ RTA_PUT(skb, IFLA_STATS, sizeof(*stats), stats);
+ }
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+int rtnetlink_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int idx;
+ int s_idx = cb->args[0];
+ struct net_device *dev;
+
+ read_lock(&dev_base_lock);
+ for (dev=dev_base, idx=0; dev; dev = dev->next, idx++) {
+ if (idx < s_idx)
+ continue;
+ if (rtnetlink_fill_ifinfo(skb, dev, RTM_NEWLINK, NETLINK_CB(cb->skb).pid, cb->nlh->nlmsg_seq, 0) <= 0)
+ break;
+ }
+ read_unlock(&dev_base_lock);
+ cb->args[0] = idx;
+
+ return skb->len;
+}
+
+int rtnetlink_dump_all(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int idx;
+ int s_idx = cb->family;
+
+ if (s_idx == 0)
+ s_idx = 1;
+ for (idx=1; idx<NPROTO; idx++) {
+ int type = cb->nlh->nlmsg_type-RTM_BASE;
+ if (idx < s_idx || idx == PF_PACKET)
+ continue;
+ if (rtnetlink_links[idx] == NULL ||
+ rtnetlink_links[idx][type].dumpit == NULL)
+ continue;
+ if (idx > s_idx)
+ memset(&cb->args[0], 0, sizeof(cb->args));
+ if (rtnetlink_links[idx][type].dumpit(skb, cb))
+ break;
+ }
+ cb->family = idx;
+
+ return skb->len;
+}
+
+void rtmsg_ifinfo(int type, struct net_device *dev, unsigned change)
+{
+ struct sk_buff *skb;
+ int size = NLMSG_GOODSIZE;
+
+ skb = alloc_skb(size, GFP_KERNEL);
+ if (!skb)
+ return;
+
+ if (rtnetlink_fill_ifinfo(skb, dev, type, 0, 0, change) < 0) {
+ kfree_skb(skb);
+ return;
+ }
+ NETLINK_CB(skb).dst_groups = RTMGRP_LINK;
+ netlink_broadcast(rtnl, skb, 0, RTMGRP_LINK, GFP_KERNEL);
+}
+
+static int rtnetlink_done(struct netlink_callback *cb)
+{
+ return 0;
+}
+
+/* Process one rtnetlink message. */
+
+static __inline__ int
+rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, int *errp)
+{
+ struct rtnetlink_link *link;
+ struct rtnetlink_link *link_tab;
+ struct rtattr *rta[RTATTR_MAX];
+
+ int exclusive = 0;
+ int sz_idx, kind;
+ int min_len;
+ int family;
+ int type;
+ int err;
+
+ /* Only requests are handled by kernel now */
+ if (!(nlh->nlmsg_flags&NLM_F_REQUEST))
+ return 0;
+
+ type = nlh->nlmsg_type;
+
+ /* A control message: ignore them */
+ if (type < RTM_BASE)
+ return 0;
+
+ /* Unknown message: reply with EINVAL */
+ if (type > RTM_MAX)
+ goto err_inval;
+
+ type -= RTM_BASE;
+
+ /* All the messages must have at least 1 byte length */
+ if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(struct rtgenmsg)))
+ return 0;
+
+ family = ((struct rtgenmsg*)NLMSG_DATA(nlh))->rtgen_family;
+ if (family >= NPROTO) {
+ *errp = -EAFNOSUPPORT;
+ return -1;
+ }
+
+ link_tab = rtnetlink_links[family];
+ if (link_tab == NULL)
+ link_tab = rtnetlink_links[PF_UNSPEC];
+ link = &link_tab[type];
+
+ sz_idx = type>>2;
+ kind = type&3;
+
+ if (kind != 2 && !cap_raised(NETLINK_CB(skb).eff_cap, CAP_NET_ADMIN)) {
+ *errp = -EPERM;
+ return -1;
+ }
+
+ if (kind == 2 && nlh->nlmsg_flags&NLM_F_DUMP) {
+ u32 rlen;
+
+ if (link->dumpit == NULL)
+ link = &(rtnetlink_links[PF_UNSPEC][type]);
+
+ if (link->dumpit == NULL)
+ goto err_inval;
+
+ if ((*errp = netlink_dump_start(rtnl, skb, nlh,
+ link->dumpit,
+ rtnetlink_done)) != 0) {
+ return -1;
+ }
+ rlen = NLMSG_ALIGN(nlh->nlmsg_len);
+ if (rlen > skb->len)
+ rlen = skb->len;
+ skb_pull(skb, rlen);
+ return -1;
+ }
+
+ if (kind != 2) {
+ if (rtnl_exlock_nowait()) {
+ *errp = 0;
+ return -1;
+ }
+ exclusive = 1;
+ }
+
+ memset(&rta, 0, sizeof(rta));
+
+ min_len = rtm_min[sz_idx];
+ if (nlh->nlmsg_len < min_len)
+ goto err_inval;
+
+ if (nlh->nlmsg_len > min_len) {
+ int attrlen = nlh->nlmsg_len - NLMSG_ALIGN(min_len);
+ struct rtattr *attr = (void*)nlh + NLMSG_ALIGN(min_len);
+
+ while (RTA_OK(attr, attrlen)) {
+ unsigned flavor = attr->rta_type;
+ if (flavor) {
+ if (flavor > rta_max[sz_idx])
+ goto err_inval;
+ rta[flavor-1] = attr;
+ }
+ attr = RTA_NEXT(attr, attrlen);
+ }
+ }
+
+ if (link->doit == NULL)
+ link = &(rtnetlink_links[PF_UNSPEC][type]);
+ if (link->doit == NULL)
+ goto err_inval;
+ err = link->doit(skb, nlh, (void *)&rta);
+
+ if (exclusive)
+ rtnl_exunlock();
+ *errp = err;
+ return err;
+
+err_inval:
+ if (exclusive)
+ rtnl_exunlock();
+ *errp = -EINVAL;
+ return -1;
+}
+
+/*
+ * Process one packet of messages.
+ * Malformed skbs with wrong lengths of messages are discarded silently.
+ */
+
+static inline int rtnetlink_rcv_skb(struct sk_buff *skb)
+{
+ int err;
+ struct nlmsghdr * nlh;
+
+ while (skb->len >= NLMSG_SPACE(0)) {
+ u32 rlen;
+
+ nlh = (struct nlmsghdr *)skb->data;
+ if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len)
+ return 0;
+ rlen = NLMSG_ALIGN(nlh->nlmsg_len);
+ if (rlen > skb->len)
+ rlen = skb->len;
+ if (rtnetlink_rcv_msg(skb, nlh, &err)) {
+ /* Not error, but we must interrupt processing here:
+ * Note, that in this case we do not pull message
+ * from skb, it will be processed later.
+ */
+ if (err == 0)
+ return -1;
+ netlink_ack(skb, nlh, err);
+ } else if (nlh->nlmsg_flags&NLM_F_ACK)
+ netlink_ack(skb, nlh, 0);
+ skb_pull(skb, rlen);
+ }
+
+ return 0;
+}
+
+/*
+ * rtnetlink input queue processing routine:
+ * - try to acquire shared lock. If it is failed, defer processing.
+ * - feed skbs to rtnetlink_rcv_skb, until it refuse a message,
+ * that will occur, when a dump started and/or acquisition of
+ * exclusive lock failed.
+ */
+
+static void rtnetlink_rcv(struct sock *sk, int len)
+{
+ do {
+ struct sk_buff *skb;
+
+ if (rtnl_shlock_nowait())
+ return;
+
+ while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
+ if (rtnetlink_rcv_skb(skb)) {
+ if (skb->len)
+ skb_queue_head(&sk->receive_queue, skb);
+ else
+ kfree_skb(skb);
+ break;
+ }
+ kfree_skb(skb);
+ }
+
+ up(&rtnl_sem);
+ } while (rtnl && rtnl->receive_queue.qlen);
+}
+
+static struct rtnetlink_link link_rtnetlink_table[RTM_MAX-RTM_BASE+1] =
+{
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, rtnetlink_dump_ifinfo, },
+ { NULL, NULL, },
+
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, rtnetlink_dump_all, },
+ { NULL, NULL, },
+
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, rtnetlink_dump_all, },
+ { NULL, NULL, },
+
+ { neigh_add, NULL, },
+ { neigh_delete, NULL, },
+ { NULL, neigh_dump_info, },
+ { NULL, NULL, },
+
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+};
+
+
+static int rtnetlink_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct net_device *dev = ptr;
+ switch (event) {
+ case NETDEV_UNREGISTER:
+ rtmsg_ifinfo(RTM_DELLINK, dev, ~0U);
+ break;
+ case NETDEV_REGISTER:
+ rtmsg_ifinfo(RTM_NEWLINK, dev, ~0U);
+ break;
+ case NETDEV_UP:
+ case NETDEV_DOWN:
+ rtmsg_ifinfo(RTM_NEWLINK, dev, IFF_UP|IFF_RUNNING);
+ break;
+ case NETDEV_CHANGE:
+ case NETDEV_GOING_DOWN:
+ break;
+ default:
+ rtmsg_ifinfo(RTM_NEWLINK, dev, 0);
+ break;
+ }
+ return NOTIFY_DONE;
+}
+
+struct notifier_block rtnetlink_dev_notifier = {
+ rtnetlink_event,
+ NULL,
+ 0
+};
+
+
+void __init rtnetlink_init(void)
+{
+#ifdef RTNL_DEBUG
+ printk("Initializing RT netlink socket\n");
+#endif
+ rtnl = netlink_kernel_create(NETLINK_ROUTE, rtnetlink_rcv);
+ if (rtnl == NULL)
+ panic("rtnetlink_init: cannot initialize rtnetlink\n");
+ netlink_set_nonroot(NETLINK_ROUTE, NL_NONROOT_RECV);
+ register_netdevice_notifier(&rtnetlink_dev_notifier);
+ rtnetlink_links[PF_UNSPEC] = link_rtnetlink_table;
+ rtnetlink_links[PF_PACKET] = link_rtnetlink_table;
+}
diff --git a/uClinux-2.4.31-uc0/net/core/scm.c b/uClinux-2.4.31-uc0/net/core/scm.c
new file mode 100644
index 0000000..96e2fe0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/scm.c
@@ -0,0 +1,271 @@
+/* scm.c - Socket level control messages processing.
+ *
+ * Author: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ * Alignment and value checking mods by Craig Metz
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/signal.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/stat.h>
+#include <linux/socket.h>
+#include <linux/file.h>
+#include <linux/fcntl.h>
+#include <linux/net.h>
+#include <linux/interrupt.h>
+#include <linux/netdevice.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+#include <net/protocol.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/scm.h>
+
+
+/*
+ * Only allow a user to send credentials, that they could set with
+ * setu(g)id.
+ */
+
+static __inline__ int scm_check_creds(struct ucred *creds)
+{
+ if ((creds->pid == current->pid || capable(CAP_SYS_ADMIN)) &&
+ ((creds->uid == current->uid || creds->uid == current->euid ||
+ creds->uid == current->suid) || capable(CAP_SETUID)) &&
+ ((creds->gid == current->gid || creds->gid == current->egid ||
+ creds->gid == current->sgid) || capable(CAP_SETGID))) {
+ return 0;
+ }
+ return -EPERM;
+}
+
+static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp)
+{
+ int *fdp = (int*)CMSG_DATA(cmsg);
+ struct scm_fp_list *fpl = *fplp;
+ struct file **fpp;
+ int i, num;
+
+ num = (cmsg->cmsg_len - CMSG_ALIGN(sizeof(struct cmsghdr)))/sizeof(int);
+
+ if (num <= 0)
+ return 0;
+
+ if (num > SCM_MAX_FD)
+ return -EINVAL;
+
+ if (!fpl)
+ {
+ fpl = kmalloc(sizeof(struct scm_fp_list), GFP_KERNEL);
+ if (!fpl)
+ return -ENOMEM;
+ *fplp = fpl;
+ fpl->count = 0;
+ }
+ fpp = &fpl->fp[fpl->count];
+
+ if (fpl->count + num > SCM_MAX_FD)
+ return -EINVAL;
+
+ /*
+ * Verify the descriptors and increment the usage count.
+ */
+
+ for (i=0; i< num; i++)
+ {
+ int fd = fdp[i];
+ struct file *file;
+
+ if (fd < 0 || !(file = fget(fd)))
+ return -EBADF;
+ *fpp++ = file;
+ fpl->count++;
+ }
+ return num;
+}
+
+void __scm_destroy(struct scm_cookie *scm)
+{
+ struct scm_fp_list *fpl = scm->fp;
+ int i;
+
+ if (fpl) {
+ scm->fp = NULL;
+ for (i=fpl->count-1; i>=0; i--)
+ fput(fpl->fp[i]);
+ kfree(fpl);
+ }
+}
+
+int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p)
+{
+ struct cmsghdr *cmsg;
+ int err;
+
+ for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg))
+ {
+ err = -EINVAL;
+
+ /* Verify that cmsg_len is at least sizeof(struct cmsghdr) */
+ /* The first check was omitted in <= 2.2.5. The reasoning was
+ that parser checks cmsg_len in any case, so that
+ additional check would be work duplication.
+ But if cmsg_level is not SOL_SOCKET, we do not check
+ for too short ancillary data object at all! Oops.
+ OK, let's add it...
+ */
+ if (!CMSG_OK(msg, cmsg))
+ goto error;
+
+ if (cmsg->cmsg_level != SOL_SOCKET)
+ continue;
+
+ switch (cmsg->cmsg_type)
+ {
+ case SCM_RIGHTS:
+ err=scm_fp_copy(cmsg, &p->fp);
+ if (err<0)
+ goto error;
+ break;
+ case SCM_CREDENTIALS:
+ if (cmsg->cmsg_len != CMSG_LEN(sizeof(struct ucred)))
+ goto error;
+ memcpy(&p->creds, CMSG_DATA(cmsg), sizeof(struct ucred));
+ err = scm_check_creds(&p->creds);
+ if (err)
+ goto error;
+ break;
+ default:
+ goto error;
+ }
+ }
+
+ if (p->fp && !p->fp->count)
+ {
+ kfree(p->fp);
+ p->fp = NULL;
+ }
+ return 0;
+
+error:
+ scm_destroy(p);
+ return err;
+}
+
+int put_cmsg(struct msghdr * msg, int level, int type, int len, void *data)
+{
+ struct cmsghdr *cm = (struct cmsghdr*)msg->msg_control;
+ struct cmsghdr cmhdr;
+ int cmlen = CMSG_LEN(len);
+ int err;
+
+ if (cm==NULL || msg->msg_controllen < sizeof(*cm)) {
+ msg->msg_flags |= MSG_CTRUNC;
+ return 0; /* XXX: return error? check spec. */
+ }
+ if (msg->msg_controllen < cmlen) {
+ msg->msg_flags |= MSG_CTRUNC;
+ cmlen = msg->msg_controllen;
+ }
+ cmhdr.cmsg_level = level;
+ cmhdr.cmsg_type = type;
+ cmhdr.cmsg_len = cmlen;
+
+ err = -EFAULT;
+ if (copy_to_user(cm, &cmhdr, sizeof cmhdr))
+ goto out;
+ if (copy_to_user(CMSG_DATA(cm), data, cmlen - sizeof(struct cmsghdr)))
+ goto out;
+ cmlen = CMSG_SPACE(len);
+ msg->msg_control += cmlen;
+ msg->msg_controllen -= cmlen;
+ err = 0;
+out:
+ return err;
+}
+
+void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm)
+{
+ struct cmsghdr *cm = (struct cmsghdr*)msg->msg_control;
+
+ int fdmax = 0;
+ int fdnum = scm->fp->count;
+ struct file **fp = scm->fp->fp;
+ int *cmfptr;
+ int err = 0, i;
+
+ if (msg->msg_controllen > sizeof(struct cmsghdr))
+ fdmax = ((msg->msg_controllen - sizeof(struct cmsghdr))
+ / sizeof(int));
+
+ if (fdnum < fdmax)
+ fdmax = fdnum;
+
+ for (i=0, cmfptr=(int*)CMSG_DATA(cm); i<fdmax; i++, cmfptr++)
+ {
+ int new_fd;
+ err = get_unused_fd();
+ if (err < 0)
+ break;
+ new_fd = err;
+ err = put_user(new_fd, cmfptr);
+ if (err) {
+ put_unused_fd(new_fd);
+ break;
+ }
+ /* Bump the usage count and install the file. */
+ get_file(fp[i]);
+ fd_install(new_fd, fp[i]);
+ }
+
+ if (i > 0)
+ {
+ int cmlen = CMSG_LEN(i*sizeof(int));
+ if (!err)
+ err = put_user(SOL_SOCKET, &cm->cmsg_level);
+ if (!err)
+ err = put_user(SCM_RIGHTS, &cm->cmsg_type);
+ if (!err)
+ err = put_user(cmlen, &cm->cmsg_len);
+ if (!err) {
+ cmlen = CMSG_SPACE(i*sizeof(int));
+ msg->msg_control += cmlen;
+ msg->msg_controllen -= cmlen;
+ }
+ }
+ if (i < fdnum || (fdnum && fdmax <= 0))
+ msg->msg_flags |= MSG_CTRUNC;
+
+ /*
+ * All of the files that fit in the message have had their
+ * usage counts incremented, so we just free the list.
+ */
+ __scm_destroy(scm);
+}
+
+struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl)
+{
+ struct scm_fp_list *new_fpl;
+ int i;
+
+ if (!fpl)
+ return NULL;
+
+ new_fpl = kmalloc(sizeof(*fpl), GFP_KERNEL);
+ if (new_fpl) {
+ for (i=fpl->count-1; i>=0; i--)
+ get_file(fpl->fp[i]);
+ memcpy(new_fpl, fpl, sizeof(*fpl));
+ }
+ return new_fpl;
+}
diff --git a/uClinux-2.4.31-uc0/net/core/skbuff.c b/uClinux-2.4.31-uc0/net/core/skbuff.c
new file mode 100644
index 0000000..82b1fd2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/skbuff.c
@@ -0,0 +1,1274 @@
+/*
+ * Routines having to do with the 'struct sk_buff' memory handlers.
+ *
+ * Authors: Alan Cox <iiitac@pyr.swan.ac.uk>
+ * Florian La Roche <rzsfl@rz.uni-sb.de>
+ *
+ * Version: $Id: skbuff.c,v 1.90 2001/11/07 05:56:19 davem Exp $
+ *
+ * Fixes:
+ * Alan Cox : Fixed the worst of the load balancer bugs.
+ * Dave Platt : Interrupt stacking fix.
+ * Richard Kooijman : Timestamp fixes.
+ * Alan Cox : Changed buffer format.
+ * Alan Cox : destructor hook for AF_UNIX etc.
+ * Linus Torvalds : Better skb_clone.
+ * Alan Cox : Added skb_copy.
+ * Alan Cox : Added all the changed routines Linus
+ * only put in the headers
+ * Ray VanTassle : Fixed --skb->lock in free
+ * Alan Cox : skb_copy copy arp field
+ * Andi Kleen : slabified it.
+ *
+ * NOTE:
+ * The __skb_ routines should be called with interrupts
+ * disabled, or you better be *real* sure that the operation is atomic
+ * with respect to whatever list is being frobbed (e.g. via lock_sock()
+ * or via disabling bottom half handlers, etc).
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/*
+ * The functions in this file will not compile correctly with gcc 2.4.x
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+#include <linux/string.h>
+#include <linux/skbuff.h>
+#include <linux/cache.h>
+#include <linux/rtnetlink.h>
+#include <linux/init.h>
+#include <linux/highmem.h>
+
+#include <net/protocol.h>
+#include <net/dst.h>
+#include <net/sock.h>
+#include <net/checksum.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+int sysctl_hot_list_len = 128;
+
+static kmem_cache_t *skbuff_head_cache;
+
+static union {
+ struct sk_buff_head list;
+ char pad[SMP_CACHE_BYTES];
+} skb_head_pool[NR_CPUS];
+
+/*
+ * Keep out-of-line to prevent kernel bloat.
+ * __builtin_return_address is not used because it is not always
+ * reliable.
+ */
+
+/**
+ * skb_over_panic - private function
+ * @skb: buffer
+ * @sz: size
+ * @here: address
+ *
+ * Out of line support code for skb_put(). Not user callable.
+ */
+
+void skb_over_panic(struct sk_buff *skb, int sz, void *here)
+{
+ printk("skput:over: %p:%d put:%d dev:%s",
+ here, skb->len, sz, skb->dev ? skb->dev->name : "<NULL>");
+ BUG();
+}
+
+/**
+ * skb_under_panic - private function
+ * @skb: buffer
+ * @sz: size
+ * @here: address
+ *
+ * Out of line support code for skb_push(). Not user callable.
+ */
+
+
+void skb_under_panic(struct sk_buff *skb, int sz, void *here)
+{
+ printk("skput:under: %p:%d put:%d dev:%s",
+ here, skb->len, sz, skb->dev ? skb->dev->name : "<NULL>");
+ BUG();
+}
+
+static __inline__ struct sk_buff *skb_head_from_pool(void)
+{
+ struct sk_buff_head *list = &skb_head_pool[smp_processor_id()].list;
+
+ if (skb_queue_len(list)) {
+ struct sk_buff *skb;
+ unsigned long flags;
+
+ local_irq_save(flags);
+ skb = __skb_dequeue(list);
+ local_irq_restore(flags);
+ return skb;
+ }
+ return NULL;
+}
+
+static __inline__ void skb_head_to_pool(struct sk_buff *skb)
+{
+ struct sk_buff_head *list = &skb_head_pool[smp_processor_id()].list;
+
+ if (skb_queue_len(list) < sysctl_hot_list_len) {
+ unsigned long flags;
+
+ local_irq_save(flags);
+ __skb_queue_head(list, skb);
+ local_irq_restore(flags);
+
+ return;
+ }
+ kmem_cache_free(skbuff_head_cache, skb);
+}
+
+
+/* Allocate a new skbuff. We do this ourselves so we can fill in a few
+ * 'private' fields and also do memory statistics to find all the
+ * [BEEP] leaks.
+ *
+ */
+
+/**
+ * alloc_skb - allocate a network buffer
+ * @size: size to allocate
+ * @gfp_mask: allocation mask
+ *
+ * Allocate a new &sk_buff. The returned buffer has no headroom and a
+ * tail room of size bytes. The object has a reference count of one.
+ * The return is the buffer. On a failure the return is %NULL.
+ *
+ * Buffers may only be allocated from interrupts using a @gfp_mask of
+ * %GFP_ATOMIC.
+ */
+
+struct sk_buff *alloc_skb(unsigned int size,int gfp_mask)
+{
+ struct sk_buff *skb;
+ u8 *data;
+
+#if defined(CONFIG_ARCH_IXP425)
+ gfp_mask |= GFP_DMA;
+#endif
+
+ if (in_interrupt() && (gfp_mask & __GFP_WAIT)) {
+ static int count = 0;
+ if (++count < 5) {
+ printk(KERN_ERR "alloc_skb called nonatomically "
+ "from interrupt %p\n", NET_CALLER(size));
+ BUG();
+ }
+ gfp_mask &= ~__GFP_WAIT;
+ }
+
+ /* Get the HEAD */
+ skb = skb_head_from_pool();
+ if (skb == NULL) {
+ skb = kmem_cache_alloc(skbuff_head_cache, gfp_mask & ~__GFP_DMA);
+ if (skb == NULL)
+ goto nohead;
+ }
+
+ /* Get the DATA. Size must match skb_add_mtu(). */
+ size = SKB_DATA_ALIGN(size);
+ data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
+ if (data == NULL)
+ goto nodata;
+
+ /* XXX: does not include slab overhead */
+ skb->truesize = size + sizeof(struct sk_buff);
+
+ /* Load the data pointers. */
+ skb->head = data;
+ skb->data = data;
+ skb->tail = data;
+ skb->end = data + size;
+
+ /* Set up other state */
+ skb->len = 0;
+ skb->cloned = 0;
+#if defined(CONFIG_IMQ) || defined (CONFIG_IMQ_MODULE)
+ skb->imq_flags = 0;
+ skb->nf_info = NULL;
+#endif
+ skb->data_len = 0;
+
+ atomic_set(&skb->users, 1);
+ atomic_set(&(skb_shinfo(skb)->dataref), 1);
+ skb_shinfo(skb)->nr_frags = 0;
+ skb_shinfo(skb)->frag_list = NULL;
+ return skb;
+
+nodata:
+ skb_head_to_pool(skb);
+nohead:
+ return NULL;
+}
+
+
+/*
+ * Slab constructor for a skb head.
+ */
+static inline void skb_headerinit(void *p, kmem_cache_t *cache,
+ unsigned long flags)
+{
+ struct sk_buff *skb = p;
+
+ skb->next = NULL;
+ skb->prev = NULL;
+ skb->list = NULL;
+ skb->sk = NULL;
+ skb->stamp.tv_sec=0; /* No idea about time */
+ skb->dev = NULL;
+ skb->real_dev = NULL;
+ skb->dst = NULL;
+ memset(skb->cb, 0, sizeof(skb->cb));
+ skb->pkt_type = PACKET_HOST; /* Default type */
+ skb->ip_summed = 0;
+ skb->priority = 0;
+ skb->security = 0; /* By default packets are insecure */
+ skb->destructor = NULL;
+
+#ifdef CONFIG_NETFILTER
+ skb->nfmark = skb->nfcache = 0;
+ skb->nfct = NULL;
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug = 0;
+#endif
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ skb->nf_bridge = NULL;
+#endif
+#endif
+#ifdef CONFIG_NET_SCHED
+ skb->tc_index = 0;
+#endif
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
+ skb->imq_flags = 0;
+ skb->nf_info = NULL;
+#endif
+}
+
+static void skb_drop_fraglist(struct sk_buff *skb)
+{
+ struct sk_buff *list = skb_shinfo(skb)->frag_list;
+
+ skb_shinfo(skb)->frag_list = NULL;
+
+ do {
+ struct sk_buff *this = list;
+ list = list->next;
+ kfree_skb(this);
+ } while (list);
+}
+
+static void skb_clone_fraglist(struct sk_buff *skb)
+{
+ struct sk_buff *list;
+
+ for (list = skb_shinfo(skb)->frag_list; list; list=list->next)
+ skb_get(list);
+}
+
+static void skb_release_data(struct sk_buff *skb)
+{
+ if (!skb->cloned ||
+ atomic_dec_and_test(&(skb_shinfo(skb)->dataref))) {
+ if (skb_shinfo(skb)->nr_frags) {
+ int i;
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
+ put_page(skb_shinfo(skb)->frags[i].page);
+ }
+
+ if (skb_shinfo(skb)->frag_list)
+ skb_drop_fraglist(skb);
+
+ kfree(skb->head);
+ }
+}
+
+/*
+ * Free an skbuff by memory without cleaning the state.
+ */
+void kfree_skbmem(struct sk_buff *skb)
+{
+ skb_release_data(skb);
+ skb_head_to_pool(skb);
+}
+
+/**
+ * __kfree_skb - private function
+ * @skb: buffer
+ *
+ * Free an sk_buff. Release anything attached to the buffer.
+ * Clean the state. This is an internal helper function. Users should
+ * always call kfree_skb
+ */
+
+void __kfree_skb(struct sk_buff *skb)
+{
+ if (skb->list) {
+ printk(KERN_WARNING "Warning: kfree_skb passed an skb still "
+ "on a list (from %p).\n", NET_CALLER(skb));
+ BUG();
+ }
+
+ dst_release(skb->dst);
+ if(skb->destructor) {
+ if (in_irq()) {
+ printk(KERN_WARNING "Warning: kfree_skb on hard IRQ %p\n",
+ NET_CALLER(skb));
+ }
+ skb->destructor(skb);
+ }
+#ifdef CONFIG_NETFILTER
+ nf_conntrack_put(skb->nfct);
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ nf_bridge_put(skb->nf_bridge);
+#endif
+#endif
+ skb_headerinit(skb, NULL, 0); /* clean state */
+ kfree_skbmem(skb);
+}
+
+/**
+ * skb_clone - duplicate an sk_buff
+ * @skb: buffer to clone
+ * @gfp_mask: allocation priority
+ *
+ * Duplicate an &sk_buff. The new one is not owned by a socket. Both
+ * copies share the same packet data but not structure. The new
+ * buffer has a reference count of 1. If the allocation fails the
+ * function returns %NULL otherwise the new buffer is returned.
+ *
+ * If this function is called from an interrupt gfp_mask() must be
+ * %GFP_ATOMIC.
+ */
+
+struct sk_buff *skb_clone(struct sk_buff *skb, int gfp_mask)
+{
+ struct sk_buff *n;
+
+ n = skb_head_from_pool();
+ if (!n) {
+ n = kmem_cache_alloc(skbuff_head_cache, gfp_mask);
+ if (!n)
+ return NULL;
+ }
+
+#define C(x) n->x = skb->x
+
+ n->next = n->prev = NULL;
+ n->list = NULL;
+ n->sk = NULL;
+ C(stamp);
+ C(dev);
+ C(real_dev);
+ C(h);
+ C(nh);
+ C(mac);
+ C(dst);
+ dst_clone(n->dst);
+ memcpy(n->cb, skb->cb, sizeof(skb->cb));
+ C(len);
+ C(data_len);
+ C(csum);
+ n->cloned = 1;
+ C(pkt_type);
+ C(ip_summed);
+ C(priority);
+ atomic_set(&n->users, 1);
+ C(protocol);
+ C(security);
+ C(truesize);
+ C(head);
+ C(data);
+ C(tail);
+ C(end);
+ n->destructor = NULL;
+#ifdef CONFIG_NETFILTER
+ C(nfmark);
+ C(nfcache);
+ C(nfct);
+#ifdef CONFIG_NETFILTER_DEBUG
+ C(nf_debug);
+#endif
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ C(nf_bridge);
+#endif
+#endif /*CONFIG_NETFILTER*/
+#if defined(CONFIG_HIPPI)
+ C(private);
+#endif
+#ifdef CONFIG_NET_SCHED
+ C(tc_index);
+#endif
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
+ C(imq_flags);
+ C(nf_info);
+#endif
+
+ atomic_inc(&(skb_shinfo(skb)->dataref));
+ skb->cloned = 1;
+#ifdef CONFIG_NETFILTER
+ nf_conntrack_get(skb->nfct);
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ nf_bridge_get(skb->nf_bridge);
+#endif
+#endif
+ return n;
+}
+
+static void copy_skb_header(struct sk_buff *new, const struct sk_buff *old)
+{
+ /*
+ * Shift between the two data areas in bytes
+ */
+ unsigned long offset = new->data - old->data;
+
+ new->list=NULL;
+ new->sk=NULL;
+ new->dev=old->dev;
+ new->real_dev=old->real_dev;
+ new->priority=old->priority;
+ new->protocol=old->protocol;
+ new->dst=dst_clone(old->dst);
+ new->h.raw=old->h.raw+offset;
+ new->nh.raw=old->nh.raw+offset;
+ new->mac.raw=old->mac.raw+offset;
+ memcpy(new->cb, old->cb, sizeof(old->cb));
+ atomic_set(&new->users, 1);
+ new->pkt_type=old->pkt_type;
+ new->stamp=old->stamp;
+ new->destructor = NULL;
+ new->security=old->security;
+#ifdef CONFIG_NETFILTER
+ new->nfmark=old->nfmark;
+ new->nfcache=old->nfcache;
+ new->nfct=old->nfct;
+ nf_conntrack_get(new->nfct);
+#ifdef CONFIG_NETFILTER_DEBUG
+ new->nf_debug=old->nf_debug;
+#endif
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ new->nf_bridge=old->nf_bridge;
+ nf_bridge_get(new->nf_bridge);
+#endif
+#endif
+#ifdef CONFIG_NET_SCHED
+ new->tc_index = old->tc_index;
+#endif
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
+ new->imq_flags=old->imq_flags;
+ new->nf_info=old->nf_info;
+#endif
+}
+
+/**
+ * skb_copy - create private copy of an sk_buff
+ * @skb: buffer to copy
+ * @gfp_mask: allocation priority
+ *
+ * Make a copy of both an &sk_buff and its data. This is used when the
+ * caller wishes to modify the data and needs a private copy of the
+ * data to alter. Returns %NULL on failure or the pointer to the buffer
+ * on success. The returned buffer has a reference count of 1.
+ *
+ * As by-product this function converts non-linear &sk_buff to linear
+ * one, so that &sk_buff becomes completely private and caller is allowed
+ * to modify all the data of returned buffer. This means that this
+ * function is not recommended for use in circumstances when only
+ * header is going to be modified. Use pskb_copy() instead.
+ */
+
+struct sk_buff *skb_copy(const struct sk_buff *skb, int gfp_mask)
+{
+ struct sk_buff *n;
+ int headerlen = skb->data-skb->head;
+
+ /*
+ * Allocate the copy buffer
+ */
+ n=alloc_skb(skb->end - skb->head + skb->data_len, gfp_mask);
+ if(n==NULL)
+ return NULL;
+
+ /* Set the data pointer */
+ skb_reserve(n,headerlen);
+ /* Set the tail pointer and length */
+ skb_put(n,skb->len);
+ n->csum = skb->csum;
+ n->ip_summed = skb->ip_summed;
+
+ if (skb_copy_bits(skb, -headerlen, n->head, headerlen+skb->len))
+ BUG();
+
+ copy_skb_header(n, skb);
+
+ return n;
+}
+
+/* Keep head the same: replace data */
+int skb_linearize(struct sk_buff *skb, int gfp_mask)
+{
+ unsigned int size;
+ u8 *data;
+ long offset;
+ int headerlen = skb->data - skb->head;
+ int expand = (skb->tail+skb->data_len) - skb->end;
+
+ if (skb_shared(skb))
+ BUG();
+
+ if (expand <= 0)
+ expand = 0;
+
+ size = (skb->end - skb->head + expand);
+ size = SKB_DATA_ALIGN(size);
+ data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
+ if (data == NULL)
+ return -ENOMEM;
+
+ /* Copy entire thing */
+ if (skb_copy_bits(skb, -headerlen, data, headerlen+skb->len))
+ BUG();
+
+ /* Offset between the two in bytes */
+ offset = data - skb->head;
+
+ /* Free old data. */
+ skb_release_data(skb);
+
+ skb->head = data;
+ skb->end = data + size;
+
+ /* Set up new pointers */
+ skb->h.raw += offset;
+ skb->nh.raw += offset;
+ skb->mac.raw += offset;
+ skb->tail += offset;
+ skb->data += offset;
+
+ /* Set up shinfo */
+ atomic_set(&(skb_shinfo(skb)->dataref), 1);
+ skb_shinfo(skb)->nr_frags = 0;
+ skb_shinfo(skb)->frag_list = NULL;
+
+ /* We are no longer a clone, even if we were. */
+ skb->cloned = 0;
+
+ skb->tail += skb->data_len;
+ skb->data_len = 0;
+ return 0;
+}
+
+
+/**
+ * pskb_copy - create copy of an sk_buff with private head.
+ * @skb: buffer to copy
+ * @gfp_mask: allocation priority
+ *
+ * Make a copy of both an &sk_buff and part of its data, located
+ * in header. Fragmented data remain shared. This is used when
+ * the caller wishes to modify only header of &sk_buff and needs
+ * private copy of the header to alter. Returns %NULL on failure
+ * or the pointer to the buffer on success.
+ * The returned buffer has a reference count of 1.
+ */
+
+struct sk_buff *pskb_copy(struct sk_buff *skb, int gfp_mask)
+{
+ struct sk_buff *n;
+
+ /*
+ * Allocate the copy buffer
+ */
+ n=alloc_skb(skb->end - skb->head, gfp_mask);
+ if(n==NULL)
+ return NULL;
+
+ /* Set the data pointer */
+ skb_reserve(n,skb->data-skb->head);
+ /* Set the tail pointer and length */
+ skb_put(n,skb_headlen(skb));
+ /* Copy the bytes */
+ memcpy(n->data, skb->data, n->len);
+ n->csum = skb->csum;
+ n->ip_summed = skb->ip_summed;
+
+ n->data_len = skb->data_len;
+ n->len = skb->len;
+
+ if (skb_shinfo(skb)->nr_frags) {
+ int i;
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ skb_shinfo(n)->frags[i] = skb_shinfo(skb)->frags[i];
+ get_page(skb_shinfo(n)->frags[i].page);
+ }
+ skb_shinfo(n)->nr_frags = i;
+ }
+
+ if (skb_shinfo(skb)->frag_list) {
+ skb_shinfo(n)->frag_list = skb_shinfo(skb)->frag_list;
+ skb_clone_fraglist(n);
+ }
+
+ copy_skb_header(n, skb);
+
+ return n;
+}
+
+/**
+ * pskb_expand_head - reallocate header of &sk_buff
+ * @skb: buffer to reallocate
+ * @nhead: room to add at head
+ * @ntail: room to add at tail
+ * @gfp_mask: allocation priority
+ *
+ * Expands (or creates identical copy, if &nhead and &ntail are zero)
+ * header of skb. &sk_buff itself is not changed. &sk_buff MUST have
+ * reference count of 1. Returns zero in the case of success or error,
+ * if expansion failed. In the last case, &sk_buff is not changed.
+ *
+ * All the pointers pointing into skb header may change and must be
+ * reloaded after call to this function.
+ */
+
+int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail, int gfp_mask)
+{
+ int i;
+ u8 *data;
+ int size = nhead + (skb->end - skb->head) + ntail;
+ long off;
+
+ if (skb_shared(skb))
+ BUG();
+
+ size = SKB_DATA_ALIGN(size);
+
+ data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);
+ if (data == NULL)
+ goto nodata;
+
+ /* Copy only real data... and, alas, header. This should be
+ * optimized for the cases when header is void. */
+ memcpy(data+nhead, skb->head, skb->tail-skb->head);
+ memcpy(data+size, skb->end, sizeof(struct skb_shared_info));
+
+ for (i=0; i<skb_shinfo(skb)->nr_frags; i++)
+ get_page(skb_shinfo(skb)->frags[i].page);
+
+ if (skb_shinfo(skb)->frag_list)
+ skb_clone_fraglist(skb);
+
+ skb_release_data(skb);
+
+ off = (data+nhead) - skb->head;
+
+ skb->head = data;
+ skb->end = data+size;
+
+ skb->data += off;
+ skb->tail += off;
+ skb->mac.raw += off;
+ skb->h.raw += off;
+ skb->nh.raw += off;
+ skb->cloned = 0;
+ atomic_set(&skb_shinfo(skb)->dataref, 1);
+ return 0;
+
+nodata:
+ return -ENOMEM;
+}
+
+/* Make private copy of skb with writable head and some headroom */
+
+struct sk_buff *
+skb_realloc_headroom(struct sk_buff *skb, unsigned int headroom)
+{
+ struct sk_buff *skb2;
+ int delta = headroom - skb_headroom(skb);
+
+ if (delta <= 0)
+ return pskb_copy(skb, GFP_ATOMIC);
+
+ skb2 = skb_clone(skb, GFP_ATOMIC);
+ if (skb2 == NULL ||
+ !pskb_expand_head(skb2, SKB_DATA_ALIGN(delta), 0, GFP_ATOMIC))
+ return skb2;
+
+ kfree_skb(skb2);
+ return NULL;
+}
+
+
+/**
+ * skb_copy_expand - copy and expand sk_buff
+ * @skb: buffer to copy
+ * @newheadroom: new free bytes at head
+ * @newtailroom: new free bytes at tail
+ * @gfp_mask: allocation priority
+ *
+ * Make a copy of both an &sk_buff and its data and while doing so
+ * allocate additional space.
+ *
+ * This is used when the caller wishes to modify the data and needs a
+ * private copy of the data to alter as well as more space for new fields.
+ * Returns %NULL on failure or the pointer to the buffer
+ * on success. The returned buffer has a reference count of 1.
+ *
+ * You must pass %GFP_ATOMIC as the allocation priority if this function
+ * is called from an interrupt.
+ */
+
+
+struct sk_buff *skb_copy_expand(const struct sk_buff *skb,
+ int newheadroom,
+ int newtailroom,
+ int gfp_mask)
+{
+ struct sk_buff *n;
+
+ /*
+ * Allocate the copy buffer
+ */
+
+ n=alloc_skb(newheadroom + skb->len + newtailroom,
+ gfp_mask);
+ if(n==NULL)
+ return NULL;
+
+ skb_reserve(n,newheadroom);
+
+ /* Set the tail pointer and length */
+ skb_put(n,skb->len);
+
+ /* Copy the linear data and header. */
+ if (skb_copy_bits(skb, -newheadroom, n->head, newheadroom + skb->len))
+ BUG();
+
+ copy_skb_header(n, skb);
+ return n;
+}
+
+/**
+ * skb_pad - zero pad the tail of an skb
+ * @skb: buffer to pad
+ * @pad: space to pad
+ *
+ * Ensure that a buffer is followed by a padding area that is zero
+ * filled. Used by network drivers which may DMA or transfer data
+ * beyond the buffer end onto the wire.
+ *
+ * May return NULL in out of memory cases.
+ */
+
+struct sk_buff *skb_pad(struct sk_buff *skb, int pad)
+{
+ struct sk_buff *nskb;
+
+ /* If the skbuff is non linear tailroom is always zero.. */
+ if(skb_tailroom(skb) >= pad)
+ {
+ memset(skb->data+skb->len, 0, pad);
+ return skb;
+ }
+
+ nskb = skb_copy_expand(skb, skb_headroom(skb), skb_tailroom(skb) + pad, GFP_ATOMIC);
+ kfree_skb(skb);
+ if(nskb)
+ memset(nskb->data+nskb->len, 0, pad);
+ return nskb;
+}
+
+/* Trims skb to length len. It can change skb pointers, if "realloc" is 1.
+ * If realloc==0 and trimming is impossible without change of data,
+ * it is BUG().
+ */
+
+int ___pskb_trim(struct sk_buff *skb, unsigned int len, int realloc)
+{
+ int offset = skb_headlen(skb);
+ int nfrags = skb_shinfo(skb)->nr_frags;
+ int i;
+
+ for (i=0; i<nfrags; i++) {
+ int end = offset + skb_shinfo(skb)->frags[i].size;
+ if (end > len) {
+ if (skb_cloned(skb)) {
+ if (!realloc)
+ BUG();
+ if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
+ return -ENOMEM;
+ }
+ if (len <= offset) {
+ put_page(skb_shinfo(skb)->frags[i].page);
+ skb_shinfo(skb)->nr_frags--;
+ } else {
+ skb_shinfo(skb)->frags[i].size = len-offset;
+ }
+ }
+ offset = end;
+ }
+
+ if (offset < len) {
+ skb->data_len -= skb->len - len;
+ skb->len = len;
+ } else {
+ if (len <= skb_headlen(skb)) {
+ skb->len = len;
+ skb->data_len = 0;
+ skb->tail = skb->data + len;
+ if (skb_shinfo(skb)->frag_list && !skb_cloned(skb))
+ skb_drop_fraglist(skb);
+ } else {
+ skb->data_len -= skb->len - len;
+ skb->len = len;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * __pskb_pull_tail - advance tail of skb header
+ * @skb: buffer to reallocate
+ * @delta: number of bytes to advance tail
+ *
+ * The function makes a sense only on a fragmented &sk_buff,
+ * it expands header moving its tail forward and copying necessary
+ * data from fragmented part.
+ *
+ * &sk_buff MUST have reference count of 1.
+ *
+ * Returns %NULL (and &sk_buff does not change) if pull failed
+ * or value of new tail of skb in the case of success.
+ *
+ * All the pointers pointing into skb header may change and must be
+ * reloaded after call to this function.
+ */
+
+/* Moves tail of skb head forward, copying data from fragmented part,
+ * when it is necessary.
+ * 1. It may fail due to malloc failure.
+ * 2. It may change skb pointers.
+ *
+ * It is pretty complicated. Luckily, it is called only in exceptional cases.
+ */
+unsigned char * __pskb_pull_tail(struct sk_buff *skb, int delta)
+{
+ int i, k, eat;
+
+ /* If skb has not enough free space at tail, get new one
+ * plus 128 bytes for future expansions. If we have enough
+ * room at tail, reallocate without expansion only if skb is cloned.
+ */
+ eat = (skb->tail+delta) - skb->end;
+
+ if (eat > 0 || skb_cloned(skb)) {
+ if (pskb_expand_head(skb, 0, eat>0 ? eat+128 : 0, GFP_ATOMIC))
+ return NULL;
+ }
+
+ if (skb_copy_bits(skb, skb_headlen(skb), skb->tail, delta))
+ BUG();
+
+ /* Optimization: no fragments, no reasons to preestimate
+ * size of pulled pages. Superb.
+ */
+ if (skb_shinfo(skb)->frag_list == NULL)
+ goto pull_pages;
+
+ /* Estimate size of pulled pages. */
+ eat = delta;
+ for (i=0; i<skb_shinfo(skb)->nr_frags; i++) {
+ if (skb_shinfo(skb)->frags[i].size >= eat)
+ goto pull_pages;
+ eat -= skb_shinfo(skb)->frags[i].size;
+ }
+
+ /* If we need update frag list, we are in troubles.
+ * Certainly, it possible to add an offset to skb data,
+ * but taking into account that pulling is expected to
+ * be very rare operation, it is worth to fight against
+ * further bloating skb head and crucify ourselves here instead.
+ * Pure masohism, indeed. 8)8)
+ */
+ if (eat) {
+ struct sk_buff *list = skb_shinfo(skb)->frag_list;
+ struct sk_buff *clone = NULL;
+ struct sk_buff *insp = NULL;
+
+ do {
+ if (list == NULL)
+ BUG();
+
+ if (list->len <= eat) {
+ /* Eaten as whole. */
+ eat -= list->len;
+ list = list->next;
+ insp = list;
+ } else {
+ /* Eaten partially. */
+
+ if (skb_shared(list)) {
+ /* Sucks! We need to fork list. :-( */
+ clone = skb_clone(list, GFP_ATOMIC);
+ if (clone == NULL)
+ return NULL;
+ insp = list->next;
+ list = clone;
+ } else {
+ /* This may be pulled without
+ * problems. */
+ insp = list;
+ }
+ if (pskb_pull(list, eat) == NULL) {
+ if (clone)
+ kfree_skb(clone);
+ return NULL;
+ }
+ break;
+ }
+ } while (eat);
+
+ /* Free pulled out fragments. */
+ while ((list = skb_shinfo(skb)->frag_list) != insp) {
+ skb_shinfo(skb)->frag_list = list->next;
+ kfree_skb(list);
+ }
+ /* And insert new clone at head. */
+ if (clone) {
+ clone->next = list;
+ skb_shinfo(skb)->frag_list = clone;
+ }
+ }
+ /* Success! Now we may commit changes to skb data. */
+
+pull_pages:
+ eat = delta;
+ k = 0;
+ for (i=0; i<skb_shinfo(skb)->nr_frags; i++) {
+ if (skb_shinfo(skb)->frags[i].size <= eat) {
+ put_page(skb_shinfo(skb)->frags[i].page);
+ eat -= skb_shinfo(skb)->frags[i].size;
+ } else {
+ skb_shinfo(skb)->frags[k] = skb_shinfo(skb)->frags[i];
+ if (eat) {
+ skb_shinfo(skb)->frags[k].page_offset += eat;
+ skb_shinfo(skb)->frags[k].size -= eat;
+ eat = 0;
+ }
+ k++;
+ }
+ }
+ skb_shinfo(skb)->nr_frags = k;
+
+ skb->tail += delta;
+ skb->data_len -= delta;
+
+ return skb->tail;
+}
+
+/* Copy some data bits from skb to kernel buffer. */
+
+int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len)
+{
+ int i, copy;
+ int start = skb->len - skb->data_len;
+
+ if (offset > (int)skb->len-len)
+ goto fault;
+
+ /* Copy header. */
+ if ((copy = start-offset) > 0) {
+ if (copy > len)
+ copy = len;
+ memcpy(to, skb->data + offset, copy);
+ if ((len -= copy) == 0)
+ return 0;
+ offset += copy;
+ to += copy;
+ }
+
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ int end;
+
+ BUG_TRAP(start <= offset+len);
+
+ end = start + skb_shinfo(skb)->frags[i].size;
+ if ((copy = end-offset) > 0) {
+ u8 *vaddr;
+
+ if (copy > len)
+ copy = len;
+
+ vaddr = kmap_skb_frag(&skb_shinfo(skb)->frags[i]);
+ memcpy(to, vaddr+skb_shinfo(skb)->frags[i].page_offset+
+ offset-start, copy);
+ kunmap_skb_frag(vaddr);
+
+ if ((len -= copy) == 0)
+ return 0;
+ offset += copy;
+ to += copy;
+ }
+ start = end;
+ }
+
+ if (skb_shinfo(skb)->frag_list) {
+ struct sk_buff *list;
+
+ for (list = skb_shinfo(skb)->frag_list; list; list=list->next) {
+ int end;
+
+ BUG_TRAP(start <= offset+len);
+
+ end = start + list->len;
+ if ((copy = end-offset) > 0) {
+ if (copy > len)
+ copy = len;
+ if (skb_copy_bits(list, offset-start, to, copy))
+ goto fault;
+ if ((len -= copy) == 0)
+ return 0;
+ offset += copy;
+ to += copy;
+ }
+ start = end;
+ }
+ }
+ if (len == 0)
+ return 0;
+
+fault:
+ return -EFAULT;
+}
+
+/* Checksum skb data. */
+
+unsigned int skb_checksum(const struct sk_buff *skb, int offset, int len, unsigned int csum)
+{
+ int i, copy;
+ int start = skb->len - skb->data_len;
+ int pos = 0;
+
+ /* Checksum header. */
+ if ((copy = start-offset) > 0) {
+ if (copy > len)
+ copy = len;
+ csum = csum_partial(skb->data+offset, copy, csum);
+ if ((len -= copy) == 0)
+ return csum;
+ offset += copy;
+ pos = copy;
+ }
+
+ for (i=0; i<skb_shinfo(skb)->nr_frags; i++) {
+ int end;
+
+ BUG_TRAP(start <= offset+len);
+
+ end = start + skb_shinfo(skb)->frags[i].size;
+ if ((copy = end-offset) > 0) {
+ unsigned int csum2;
+ u8 *vaddr;
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+
+ if (copy > len)
+ copy = len;
+ vaddr = kmap_skb_frag(frag);
+ csum2 = csum_partial(vaddr + frag->page_offset +
+ offset-start, copy, 0);
+ kunmap_skb_frag(vaddr);
+ csum = csum_block_add(csum, csum2, pos);
+ if (!(len -= copy))
+ return csum;
+ offset += copy;
+ pos += copy;
+ }
+ start = end;
+ }
+
+ if (skb_shinfo(skb)->frag_list) {
+ struct sk_buff *list;
+
+ for (list = skb_shinfo(skb)->frag_list; list; list=list->next) {
+ int end;
+
+ BUG_TRAP(start <= offset+len);
+
+ end = start + list->len;
+ if ((copy = end-offset) > 0) {
+ unsigned int csum2;
+ if (copy > len)
+ copy = len;
+ csum2 = skb_checksum(list, offset-start, copy, 0);
+ csum = csum_block_add(csum, csum2, pos);
+ if ((len -= copy) == 0)
+ return csum;
+ offset += copy;
+ pos += copy;
+ }
+ start = end;
+ }
+ }
+ if (len == 0)
+ return csum;
+
+ BUG();
+ return csum;
+}
+
+/* Both of above in one bottle. */
+
+unsigned int skb_copy_and_csum_bits(const struct sk_buff *skb, int offset, u8 *to, int len, unsigned int csum)
+{
+ int i, copy;
+ int start = skb->len - skb->data_len;
+ int pos = 0;
+
+ /* Copy header. */
+ if ((copy = start-offset) > 0) {
+ if (copy > len)
+ copy = len;
+ csum = csum_partial_copy_nocheck(skb->data+offset, to, copy, csum);
+ if ((len -= copy) == 0)
+ return csum;
+ offset += copy;
+ to += copy;
+ pos = copy;
+ }
+
+ for (i=0; i<skb_shinfo(skb)->nr_frags; i++) {
+ int end;
+
+ BUG_TRAP(start <= offset+len);
+
+ end = start + skb_shinfo(skb)->frags[i].size;
+ if ((copy = end-offset) > 0) {
+ unsigned int csum2;
+ u8 *vaddr;
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+
+ if (copy > len)
+ copy = len;
+ vaddr = kmap_skb_frag(frag);
+ csum2 = csum_partial_copy_nocheck(vaddr + frag->page_offset +
+ offset-start, to, copy, 0);
+ kunmap_skb_frag(vaddr);
+ csum = csum_block_add(csum, csum2, pos);
+ if (!(len -= copy))
+ return csum;
+ offset += copy;
+ to += copy;
+ pos += copy;
+ }
+ start = end;
+ }
+
+ if (skb_shinfo(skb)->frag_list) {
+ struct sk_buff *list;
+
+ for (list = skb_shinfo(skb)->frag_list; list; list=list->next) {
+ unsigned int csum2;
+ int end;
+
+ BUG_TRAP(start <= offset+len);
+
+ end = start + list->len;
+ if ((copy = end-offset) > 0) {
+ if (copy > len)
+ copy = len;
+ csum2 = skb_copy_and_csum_bits(list, offset-start, to, copy, 0);
+ csum = csum_block_add(csum, csum2, pos);
+ if ((len -= copy) == 0)
+ return csum;
+ offset += copy;
+ to += copy;
+ pos += copy;
+ }
+ start = end;
+ }
+ }
+ if (len == 0)
+ return csum;
+
+ BUG();
+ return csum;
+}
+
+void skb_copy_and_csum_dev(const struct sk_buff *skb, u8 *to)
+{
+ unsigned int csum;
+ long csstart;
+
+ if (skb->ip_summed == CHECKSUM_HW)
+ csstart = skb->h.raw - skb->data;
+ else
+ csstart = skb->len - skb->data_len;
+
+ if (csstart > skb->len - skb->data_len)
+ BUG();
+
+ memcpy(to, skb->data, csstart);
+
+ csum = 0;
+ if (csstart != skb->len)
+ csum = skb_copy_and_csum_bits(skb, csstart, to+csstart,
+ skb->len-csstart, 0);
+
+ if (skb->ip_summed == CHECKSUM_HW) {
+ long csstuff = csstart + skb->csum;
+
+ *((unsigned short *)(to + csstuff)) = csum_fold(csum);
+ }
+}
+
+#if 0
+/*
+ * Tune the memory allocator for a new MTU size.
+ */
+void skb_add_mtu(int mtu)
+{
+ /* Must match allocation in alloc_skb */
+ mtu = SKB_DATA_ALIGN(mtu) + sizeof(struct skb_shared_info);
+
+ kmem_add_cache_size(mtu);
+}
+#endif
+
+void __init skb_init(void)
+{
+ int i;
+
+ skbuff_head_cache = kmem_cache_create("skbuff_head_cache",
+ sizeof(struct sk_buff),
+ 0,
+ SLAB_HWCACHE_ALIGN,
+ skb_headerinit, NULL);
+ if (!skbuff_head_cache)
+ panic("cannot create skbuff cache");
+
+ for (i=0; i<NR_CPUS; i++)
+ skb_queue_head_init(&skb_head_pool[i].list);
+}
diff --git a/uClinux-2.4.31-uc0/net/core/sock.c b/uClinux-2.4.31-uc0/net/core/sock.c
new file mode 100644
index 0000000..fea551f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/sock.c
@@ -0,0 +1,1232 @@
+/* $USAGI: sock.c,v 1.13 2003/12/20 18:32:15 yoshfuji Exp $ */
+
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * Generic socket support routines. Memory allocators, socket lock/release
+ * handler for protocols to use and generic option handler.
+ *
+ *
+ * Version: $Id: sock.c,v 1.116 2001/11/08 04:20:06 davem Exp $
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Florian La Roche, <flla@stud.uni-sb.de>
+ * Alan Cox, <A.Cox@swansea.ac.uk>
+ *
+ * Fixes:
+ * Alan Cox : Numerous verify_area() problems
+ * Alan Cox : Connecting on a connecting socket
+ * now returns an error for tcp.
+ * Alan Cox : sock->protocol is set correctly.
+ * and is not sometimes left as 0.
+ * Alan Cox : connect handles icmp errors on a
+ * connect properly. Unfortunately there
+ * is a restart syscall nasty there. I
+ * can't match BSD without hacking the C
+ * library. Ideas urgently sought!
+ * Alan Cox : Disallow bind() to addresses that are
+ * not ours - especially broadcast ones!!
+ * Alan Cox : Socket 1024 _IS_ ok for users. (fencepost)
+ * Alan Cox : sock_wfree/sock_rfree don't destroy sockets,
+ * instead they leave that for the DESTROY timer.
+ * Alan Cox : Clean up error flag in accept
+ * Alan Cox : TCP ack handling is buggy, the DESTROY timer
+ * was buggy. Put a remove_sock() in the handler
+ * for memory when we hit 0. Also altered the timer
+ * code. The ACK stuff can wait and needs major
+ * TCP layer surgery.
+ * Alan Cox : Fixed TCP ack bug, removed remove sock
+ * and fixed timer/inet_bh race.
+ * Alan Cox : Added zapped flag for TCP
+ * Alan Cox : Move kfree_skb into skbuff.c and tidied up surplus code
+ * Alan Cox : for new sk_buff allocations wmalloc/rmalloc now call alloc_skb
+ * Alan Cox : kfree_s calls now are kfree_skbmem so we can track skb resources
+ * Alan Cox : Supports socket option broadcast now as does udp. Packet and raw need fixing.
+ * Alan Cox : Added RCVBUF,SNDBUF size setting. It suddenly occurred to me how easy it was so...
+ * Rick Sladkey : Relaxed UDP rules for matching packets.
+ * C.E.Hawkins : IFF_PROMISC/SIOCGHWADDR support
+ * Pauline Middelink : identd support
+ * Alan Cox : Fixed connect() taking signals I think.
+ * Alan Cox : SO_LINGER supported
+ * Alan Cox : Error reporting fixes
+ * Anonymous : inet_create tidied up (sk->reuse setting)
+ * Alan Cox : inet sockets don't set sk->type!
+ * Alan Cox : Split socket option code
+ * Alan Cox : Callbacks
+ * Alan Cox : Nagle flag for Charles & Johannes stuff
+ * Alex : Removed restriction on inet fioctl
+ * Alan Cox : Splitting INET from NET core
+ * Alan Cox : Fixed bogus SO_TYPE handling in getsockopt()
+ * Adam Caldwell : Missing return in SO_DONTROUTE/SO_DEBUG code
+ * Alan Cox : Split IP from generic code
+ * Alan Cox : New kfree_skbmem()
+ * Alan Cox : Make SO_DEBUG superuser only.
+ * Alan Cox : Allow anyone to clear SO_DEBUG
+ * (compatibility fix)
+ * Alan Cox : Added optimistic memory grabbing for AF_UNIX throughput.
+ * Alan Cox : Allocator for a socket is settable.
+ * Alan Cox : SO_ERROR includes soft errors.
+ * Alan Cox : Allow NULL arguments on some SO_ opts
+ * Alan Cox : Generic socket allocation to make hooks
+ * easier (suggested by Craig Metz).
+ * Michael Pall : SO_ERROR returns positive errno again
+ * Steve Whitehouse: Added default destructor to free
+ * protocol private data.
+ * Steve Whitehouse: Added various other default routines
+ * common to several socket families.
+ * Chris Evans : Call suser() check last on F_SETOWN
+ * Jay Schulist : Added SO_ATTACH_FILTER and SO_DETACH_FILTER.
+ * Andi Kleen : Add sock_kmalloc()/sock_kfree_s()
+ * Andi Kleen : Fix write_space callback
+ * Chris Evans : Security fixes - signedness again
+ * Arnaldo C. Melo : cleanups, use skb_queue_purge
+ *
+ * To Fix:
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/poll.h>
+#include <linux/tcp.h>
+#include <linux/init.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#include <linux/netdevice.h>
+#include <net/protocol.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <linux/ipsec.h>
+
+#ifdef CONFIG_FILTER
+#include <linux/filter.h>
+#endif
+
+#ifdef CONFIG_INET
+#include <net/tcp.h>
+#endif
+
+/* Take into consideration the size of the struct sk_buff overhead in the
+ * determination of these values, since that is non-constant across
+ * platforms. This makes socket queueing behavior and performance
+ * not depend upon such differences.
+ */
+#define _SK_MEM_PACKETS 256
+#define _SK_MEM_OVERHEAD (sizeof(struct sk_buff) + 256)
+#define SK_WMEM_MAX (_SK_MEM_OVERHEAD * _SK_MEM_PACKETS)
+#define SK_RMEM_MAX (_SK_MEM_OVERHEAD * _SK_MEM_PACKETS)
+
+/* Run time adjustable parameters. */
+__u32 sysctl_wmem_max = SK_WMEM_MAX;
+__u32 sysctl_rmem_max = SK_RMEM_MAX;
+__u32 sysctl_wmem_default = SK_WMEM_MAX;
+__u32 sysctl_rmem_default = SK_RMEM_MAX;
+
+/* Maximal space eaten by iovec or ancilliary data plus some space */
+int sysctl_optmem_max = sizeof(unsigned long)*(2*UIO_MAXIOV + 512);
+
+static int sock_set_timeout(long *timeo_p, char *optval, int optlen)
+{
+ struct timeval tv;
+
+ if (optlen < sizeof(tv))
+ return -EINVAL;
+ if (copy_from_user(&tv, optval, sizeof(tv)))
+ return -EFAULT;
+
+ *timeo_p = MAX_SCHEDULE_TIMEOUT;
+ if (tv.tv_sec == 0 && tv.tv_usec == 0)
+ return 0;
+ if (tv.tv_sec < (MAX_SCHEDULE_TIMEOUT/HZ - 1))
+ *timeo_p = tv.tv_sec*HZ + (tv.tv_usec+(1000000/HZ-1))/(1000000/HZ);
+ return 0;
+}
+
+/*
+ * This is meant for all protocols to use and covers goings on
+ * at the socket level. Everything here is generic.
+ */
+
+int sock_setsockopt(struct socket *sock, int level, int optname,
+ char *optval, int optlen)
+{
+ struct sock *sk=sock->sk;
+#ifdef CONFIG_FILTER
+ struct sk_filter *filter;
+#endif
+ int val;
+ int valbool;
+ struct linger ling;
+ int ret = 0;
+
+ /*
+ * Options without arguments
+ */
+
+#ifdef SO_DONTLINGER /* Compatibility item... */
+ switch(optname)
+ {
+ case SO_DONTLINGER:
+ sk->linger=0;
+ return 0;
+ }
+#endif
+
+ if(optlen<sizeof(int))
+ return(-EINVAL);
+
+ if (get_user(val, (int *)optval))
+ return -EFAULT;
+
+ valbool = val?1:0;
+
+ lock_sock(sk);
+
+ switch(optname)
+ {
+ case SO_DEBUG:
+ if(val && !capable(CAP_NET_ADMIN))
+ {
+ ret = -EACCES;
+ }
+ else
+ sk->debug=valbool;
+ break;
+ case SO_REUSEADDR:
+ sk->reuse = valbool;
+ break;
+#ifdef SO_REUSEPORT
+ case SO_REUSEPORT:
+ sk->reuseport = valbool;
+ break;
+#endif
+ case SO_TYPE:
+ case SO_ERROR:
+ ret = -ENOPROTOOPT;
+ break;
+ case SO_DONTROUTE:
+ sk->localroute=valbool;
+ break;
+ case SO_BROADCAST:
+ sk->broadcast=valbool;
+ break;
+ case SO_SNDBUF:
+ /* Don't error on this BSD doesn't and if you think
+ about it this is right. Otherwise apps have to
+ play 'guess the biggest size' games. RCVBUF/SNDBUF
+ are treated in BSD as hints */
+
+ if (val > sysctl_wmem_max)
+ val = sysctl_wmem_max;
+
+ sk->userlocks |= SOCK_SNDBUF_LOCK;
+ if ((val * 2) < SOCK_MIN_SNDBUF)
+ sk->sndbuf = SOCK_MIN_SNDBUF;
+ else
+ sk->sndbuf = (val * 2);
+
+ /*
+ * Wake up sending tasks if we
+ * upped the value.
+ */
+ sk->write_space(sk);
+ break;
+
+ case SO_RCVBUF:
+ /* Don't error on this BSD doesn't and if you think
+ about it this is right. Otherwise apps have to
+ play 'guess the biggest size' games. RCVBUF/SNDBUF
+ are treated in BSD as hints */
+
+ if (val > sysctl_rmem_max)
+ val = sysctl_rmem_max;
+
+ sk->userlocks |= SOCK_RCVBUF_LOCK;
+ /* FIXME: is this lower bound the right one? */
+ if ((val * 2) < SOCK_MIN_RCVBUF)
+ sk->rcvbuf = SOCK_MIN_RCVBUF;
+ else
+ sk->rcvbuf = (val * 2);
+ break;
+
+ case SO_KEEPALIVE:
+#ifdef CONFIG_INET
+ if (sk->protocol == IPPROTO_TCP)
+ {
+ tcp_set_keepalive(sk, valbool);
+ }
+#endif
+ sk->keepopen = valbool;
+ break;
+
+ case SO_OOBINLINE:
+ sk->urginline = valbool;
+ break;
+
+ case SO_NO_CHECK:
+ sk->no_check = valbool;
+ break;
+
+ case SO_PRIORITY:
+ if ((val >= 0 && val <= 6) || capable(CAP_NET_ADMIN))
+ sk->priority = val;
+ else
+ ret = -EPERM;
+ break;
+
+ case SO_LINGER:
+ if(optlen<sizeof(ling)) {
+ ret = -EINVAL; /* 1003.1g */
+ break;
+ }
+ if (copy_from_user(&ling,optval,sizeof(ling))) {
+ ret = -EFAULT;
+ break;
+ }
+ if(ling.l_onoff==0) {
+ sk->linger=0;
+ } else {
+#if (BITS_PER_LONG == 32)
+ if (ling.l_linger >= MAX_SCHEDULE_TIMEOUT/HZ)
+ sk->lingertime=MAX_SCHEDULE_TIMEOUT;
+ else
+#endif
+ sk->lingertime=ling.l_linger*HZ;
+ sk->linger=1;
+ }
+ break;
+
+ case SO_BSDCOMPAT:
+ sk->bsdism = valbool;
+ break;
+
+ case SO_PASSCRED:
+ sock->passcred = valbool;
+ break;
+
+ case SO_TIMESTAMP:
+ sk->rcvtstamp = valbool;
+ break;
+
+ case SO_RCVLOWAT:
+ if (val < 0)
+ val = INT_MAX;
+ sk->rcvlowat = val ? : 1;
+ break;
+
+ case SO_RCVTIMEO:
+ ret = sock_set_timeout(&sk->rcvtimeo, optval, optlen);
+ break;
+
+ case SO_SNDTIMEO:
+ ret = sock_set_timeout(&sk->sndtimeo, optval, optlen);
+ break;
+
+#ifdef CONFIG_NETDEVICES
+ case SO_BINDTODEVICE:
+ {
+ char devname[IFNAMSIZ];
+
+ /* Sorry... */
+ if (!capable(CAP_NET_RAW)) {
+ ret = -EPERM;
+ break;
+ }
+
+ /* Bind this socket to a particular device like "eth0",
+ * as specified in the passed interface name. If the
+ * name is "" or the option length is zero the socket
+ * is not bound.
+ */
+
+ if (!valbool) {
+ sk->bound_dev_if = 0;
+ } else {
+ if (optlen > IFNAMSIZ)
+ optlen = IFNAMSIZ;
+ if (copy_from_user(devname, optval, optlen)) {
+ ret = -EFAULT;
+ break;
+ }
+
+ /* Remove any cached route for this socket. */
+ sk_dst_reset(sk);
+
+ if (devname[0] == '\0') {
+ sk->bound_dev_if = 0;
+ } else {
+ struct net_device *dev = dev_get_by_name(devname);
+ if (!dev) {
+ ret = -ENODEV;
+ break;
+ }
+ sk->bound_dev_if = dev->ifindex;
+ dev_put(dev);
+ }
+ }
+ break;
+ }
+#endif
+
+
+#ifdef CONFIG_FILTER
+ case SO_ATTACH_FILTER:
+ ret = -EINVAL;
+ if (optlen == sizeof(struct sock_fprog)) {
+ struct sock_fprog fprog;
+
+ ret = -EFAULT;
+ if (copy_from_user(&fprog, optval, sizeof(fprog)))
+ break;
+
+ ret = sk_attach_filter(&fprog, sk);
+ }
+ break;
+
+ case SO_DETACH_FILTER:
+ spin_lock_bh(&sk->lock.slock);
+ filter = sk->filter;
+ if (filter) {
+ sk->filter = NULL;
+ spin_unlock_bh(&sk->lock.slock);
+ sk_filter_release(sk, filter);
+ break;
+ }
+ spin_unlock_bh(&sk->lock.slock);
+ ret = -ENONET;
+ break;
+#endif
+ /* We implement the SO_SNDLOWAT etc to
+ not be settable (1003.1g 5.3) */
+ default:
+ ret = -ENOPROTOOPT;
+ break;
+ }
+ release_sock(sk);
+ return ret;
+}
+
+
+int sock_getsockopt(struct socket *sock, int level, int optname,
+ char *optval, int *optlen)
+{
+ struct sock *sk = sock->sk;
+
+ union
+ {
+ int val;
+ struct linger ling;
+ struct timeval tm;
+ } v;
+
+ unsigned int lv=sizeof(int),len;
+
+ if(get_user(len,optlen))
+ return -EFAULT;
+ if(len < 0)
+ return -EINVAL;
+
+ switch(optname)
+ {
+ case SO_DEBUG:
+ v.val = sk->debug;
+ break;
+
+ case SO_DONTROUTE:
+ v.val = sk->localroute;
+ break;
+
+ case SO_BROADCAST:
+ v.val= sk->broadcast;
+ break;
+
+ case SO_SNDBUF:
+ v.val=sk->sndbuf;
+ break;
+
+ case SO_RCVBUF:
+ v.val =sk->rcvbuf;
+ break;
+
+ case SO_REUSEADDR:
+ v.val = sk->reuse;
+ break;
+
+#ifdef SO_REUSEPORT
+ case SO_REUSEPORT:
+ v.val = sk->reuseport;
+ break;
+#endif
+
+ case SO_KEEPALIVE:
+ v.val = sk->keepopen;
+ break;
+
+ case SO_TYPE:
+ v.val = sk->type;
+ break;
+
+ case SO_ERROR:
+ v.val = -sock_error(sk);
+ if(v.val==0)
+ v.val=xchg(&sk->err_soft,0);
+ break;
+
+ case SO_OOBINLINE:
+ v.val = sk->urginline;
+ break;
+
+ case SO_NO_CHECK:
+ v.val = sk->no_check;
+ break;
+
+ case SO_PRIORITY:
+ v.val = sk->priority;
+ break;
+
+ case SO_LINGER:
+ lv=sizeof(v.ling);
+ v.ling.l_onoff=sk->linger;
+ v.ling.l_linger=sk->lingertime/HZ;
+ break;
+
+ case SO_BSDCOMPAT:
+ v.val = sk->bsdism;
+ break;
+
+ case SO_TIMESTAMP:
+ v.val = sk->rcvtstamp;
+ break;
+
+ case SO_RCVTIMEO:
+ lv=sizeof(struct timeval);
+ if (sk->rcvtimeo == MAX_SCHEDULE_TIMEOUT) {
+ v.tm.tv_sec = 0;
+ v.tm.tv_usec = 0;
+ } else {
+ v.tm.tv_sec = sk->rcvtimeo/HZ;
+ v.tm.tv_usec = ((sk->rcvtimeo%HZ)*1000000)/HZ;
+ }
+ break;
+
+ case SO_SNDTIMEO:
+ lv=sizeof(struct timeval);
+ if (sk->sndtimeo == MAX_SCHEDULE_TIMEOUT) {
+ v.tm.tv_sec = 0;
+ v.tm.tv_usec = 0;
+ } else {
+ v.tm.tv_sec = sk->sndtimeo/HZ;
+ v.tm.tv_usec = ((sk->sndtimeo%HZ)*1000000)/HZ;
+ }
+ break;
+
+ case SO_RCVLOWAT:
+ v.val = sk->rcvlowat;
+ break;
+
+ case SO_SNDLOWAT:
+ v.val=1;
+ break;
+
+ case SO_PASSCRED:
+ v.val = sock->passcred;
+ break;
+
+ case SO_PEERCRED:
+ if (len > sizeof(sk->peercred))
+ len = sizeof(sk->peercred);
+ if (copy_to_user(optval, &sk->peercred, len))
+ return -EFAULT;
+ goto lenout;
+
+ case SO_PEERNAME:
+ {
+ char address[128];
+
+ if (sock->ops->getname(sock, (struct sockaddr *)address, &lv, 2))
+ return -ENOTCONN;
+ if (lv < len)
+ return -EINVAL;
+ if(copy_to_user((void*)optval, address, len))
+ return -EFAULT;
+ goto lenout;
+ }
+
+ /* Dubious BSD thing... Probably nobody even uses it, but
+ * the UNIX standard wants it for whatever reason... -DaveM
+ */
+ case SO_ACCEPTCONN:
+ v.val = (sk->state == TCP_LISTEN);
+ break;
+
+ default:
+ return(-ENOPROTOOPT);
+ }
+ if (len > lv)
+ len = lv;
+ if (copy_to_user(optval, &v, len))
+ return -EFAULT;
+lenout:
+ if (put_user(len, optlen))
+ return -EFAULT;
+ return 0;
+}
+
+static kmem_cache_t *sk_cachep;
+
+/*
+ * All socket objects are allocated here. This is for future
+ * usage.
+ */
+
+struct sock *sk_alloc(int family, int priority, int zero_it)
+{
+ struct sock *sk = kmem_cache_alloc(sk_cachep, priority);
+
+ if(sk && zero_it) {
+ memset(sk, 0, sizeof(struct sock));
+ sk->family = family;
+ sock_lock_init(sk);
+ }
+
+ return sk;
+}
+
+void sk_free(struct sock *sk)
+{
+#ifdef CONFIG_FILTER
+ struct sk_filter *filter;
+#endif
+
+ if (sk->destruct)
+ sk->destruct(sk);
+
+#ifdef CONFIG_FILTER
+ filter = sk->filter;
+ if (filter) {
+ sk_filter_release(sk, filter);
+ sk->filter = NULL;
+ }
+#endif
+
+ if (atomic_read(&sk->omem_alloc))
+ printk(KERN_DEBUG "sk_free: optmem leakage (%d bytes) detected.\n", atomic_read(&sk->omem_alloc));
+
+ kmem_cache_free(sk_cachep, sk);
+}
+
+void __init sk_init(void)
+{
+ sk_cachep = kmem_cache_create("sock", sizeof(struct sock), 0,
+ SLAB_HWCACHE_ALIGN, 0, 0);
+ if (!sk_cachep)
+ printk(KERN_CRIT "sk_init: Cannot create sock SLAB cache!");
+
+ if (num_physpages <= 4096) {
+ sysctl_wmem_max = 32767;
+ sysctl_rmem_max = 32767;
+ sysctl_wmem_default = 32767;
+ sysctl_rmem_default = 32767;
+ } else if (num_physpages >= 131072) {
+ sysctl_wmem_max = 131071;
+ sysctl_rmem_max = 131071;
+ }
+}
+
+/*
+ * Simple resource managers for sockets.
+ */
+
+
+/*
+ * Write buffer destructor automatically called from kfree_skb.
+ */
+void sock_wfree(struct sk_buff *skb)
+{
+ struct sock *sk = skb->sk;
+
+ /* In case it might be waiting for more memory. */
+ atomic_sub(skb->truesize, &sk->wmem_alloc);
+ if (!sk->use_write_queue)
+ sk->write_space(sk);
+ sock_put(sk);
+}
+
+/*
+ * Read buffer destructor automatically called from kfree_skb.
+ */
+void sock_rfree(struct sk_buff *skb)
+{
+ struct sock *sk = skb->sk;
+
+ atomic_sub(skb->truesize, &sk->rmem_alloc);
+}
+
+/*
+ * Allocate a skb from the socket's send buffer.
+ */
+struct sk_buff *sock_wmalloc(struct sock *sk, unsigned long size, int force, int priority)
+{
+ if (force || atomic_read(&sk->wmem_alloc) < sk->sndbuf) {
+ struct sk_buff * skb = alloc_skb(size, priority);
+ if (skb) {
+ skb_set_owner_w(skb, sk);
+ return skb;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Allocate a skb from the socket's receive buffer.
+ */
+struct sk_buff *sock_rmalloc(struct sock *sk, unsigned long size, int force, int priority)
+{
+ if (force || atomic_read(&sk->rmem_alloc) < sk->rcvbuf) {
+ struct sk_buff *skb = alloc_skb(size, priority);
+ if (skb) {
+ skb_set_owner_r(skb, sk);
+ return skb;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Allocate a memory block from the socket's option memory buffer.
+ */
+void *sock_kmalloc(struct sock *sk, int size, int priority)
+{
+ if ((unsigned)size <= sysctl_optmem_max &&
+ atomic_read(&sk->omem_alloc)+size < sysctl_optmem_max) {
+ void *mem;
+ /* First do the add, to avoid the race if kmalloc
+ * might sleep.
+ */
+ atomic_add(size, &sk->omem_alloc);
+ mem = kmalloc(size, priority);
+ if (mem)
+ return mem;
+ atomic_sub(size, &sk->omem_alloc);
+ }
+ return NULL;
+}
+
+/*
+ * Free an option memory block.
+ */
+void sock_kfree_s(struct sock *sk, void *mem, int size)
+{
+ kfree(mem);
+ atomic_sub(size, &sk->omem_alloc);
+}
+
+/* It is almost wait_for_tcp_memory minus release_sock/lock_sock.
+ I think, these locks should be removed for datagram sockets.
+ */
+static long sock_wait_for_wmem(struct sock * sk, long timeo)
+{
+ DECLARE_WAITQUEUE(wait, current);
+
+ clear_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
+ add_wait_queue(sk->sleep, &wait);
+ for (;;) {
+ if (!timeo)
+ break;
+ if (signal_pending(current))
+ break;
+ set_bit(SOCK_NOSPACE, &sk->socket->flags);
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (atomic_read(&sk->wmem_alloc) < sk->sndbuf)
+ break;
+ if (sk->shutdown & SEND_SHUTDOWN)
+ break;
+ if (sk->err)
+ break;
+ timeo = schedule_timeout(timeo);
+ }
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(sk->sleep, &wait);
+ return timeo;
+}
+
+
+/*
+ * Generic send/receive buffer handlers
+ */
+
+struct sk_buff *sock_alloc_send_pskb(struct sock *sk, unsigned long header_len,
+ unsigned long data_len, int noblock, int *errcode)
+{
+ struct sk_buff *skb;
+ long timeo;
+ int err;
+
+ timeo = sock_sndtimeo(sk, noblock);
+ while (1) {
+ err = sock_error(sk);
+ if (err != 0)
+ goto failure;
+
+ err = -EPIPE;
+ if (sk->shutdown & SEND_SHUTDOWN)
+ goto failure;
+
+ if (atomic_read(&sk->wmem_alloc) < sk->sndbuf) {
+ skb = alloc_skb(header_len, sk->allocation);
+ if (skb) {
+ int npages;
+ int i;
+
+ /* No pages, we're done... */
+ if (!data_len)
+ break;
+
+ npages = (data_len + (PAGE_SIZE - 1)) >> PAGE_SHIFT;
+ skb->truesize += data_len;
+ skb_shinfo(skb)->nr_frags = npages;
+ for (i = 0; i < npages; i++) {
+ struct page *page;
+ skb_frag_t *frag;
+
+ page = alloc_pages(sk->allocation, 0);
+ if (!page) {
+ err = -ENOBUFS;
+ skb_shinfo(skb)->nr_frags = i;
+ kfree_skb(skb);
+ goto failure;
+ }
+
+ frag = &skb_shinfo(skb)->frags[i];
+ frag->page = page;
+ frag->page_offset = 0;
+ frag->size = (data_len >= PAGE_SIZE ?
+ PAGE_SIZE :
+ data_len);
+ data_len -= PAGE_SIZE;
+ }
+
+ /* Full success... */
+ break;
+ }
+ err = -ENOBUFS;
+ goto failure;
+ }
+ set_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
+ set_bit(SOCK_NOSPACE, &sk->socket->flags);
+ err = -EAGAIN;
+ if (!timeo)
+ goto failure;
+ if (signal_pending(current))
+ goto interrupted;
+ timeo = sock_wait_for_wmem(sk, timeo);
+ }
+
+ skb_set_owner_w(skb, sk);
+ return skb;
+
+interrupted:
+ err = sock_intr_errno(timeo);
+failure:
+ *errcode = err;
+ return NULL;
+}
+
+struct sk_buff *sock_alloc_send_skb(struct sock *sk, unsigned long size,
+ int noblock, int *errcode)
+{
+ return sock_alloc_send_pskb(sk, size, 0, noblock, errcode);
+}
+
+void __lock_sock(struct sock *sk)
+{
+ DECLARE_WAITQUEUE(wait, current);
+
+ add_wait_queue_exclusive(&sk->lock.wq, &wait);
+ for(;;) {
+ current->state = TASK_UNINTERRUPTIBLE;
+ spin_unlock_bh(&sk->lock.slock);
+ schedule();
+ spin_lock_bh(&sk->lock.slock);
+ if(!sk->lock.users)
+ break;
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&sk->lock.wq, &wait);
+}
+
+void __release_sock(struct sock *sk)
+{
+ struct sk_buff *skb = sk->backlog.head;
+
+ do {
+ sk->backlog.head = sk->backlog.tail = NULL;
+ bh_unlock_sock(sk);
+
+ do {
+ struct sk_buff *next = skb->next;
+
+ skb->next = NULL;
+ sk->backlog_rcv(sk, skb);
+ skb = next;
+ } while (skb != NULL);
+
+ bh_lock_sock(sk);
+ } while((skb = sk->backlog.head) != NULL);
+}
+
+/*
+ * Generic socket manager library. Most simpler socket families
+ * use this to manage their socket lists. At some point we should
+ * hash these. By making this generic we get the lot hashed for free.
+ *
+ * It is broken by design. All the protocols using it must be fixed. --ANK
+ */
+
+rwlock_t net_big_sklist_lock = RW_LOCK_UNLOCKED;
+
+void sklist_remove_socket(struct sock **list, struct sock *sk)
+{
+ struct sock *s;
+
+ write_lock_bh(&net_big_sklist_lock);
+
+ while ((s = *list) != NULL) {
+ if (s == sk) {
+ *list = s->next;
+ break;
+ }
+ list = &s->next;
+ }
+
+ write_unlock_bh(&net_big_sklist_lock);
+ if (s)
+ sock_put(s);
+}
+
+void sklist_insert_socket(struct sock **list, struct sock *sk)
+{
+ write_lock_bh(&net_big_sklist_lock);
+ sk->next= *list;
+ *list=sk;
+ sock_hold(sk);
+ write_unlock_bh(&net_big_sklist_lock);
+}
+
+/*
+ * This is only called from user mode. Thus it protects itself against
+ * interrupt users but doesn't worry about being called during work.
+ * Once it is removed from the queue no interrupt or bottom half will
+ * touch it and we are (fairly 8-) ) safe.
+ */
+
+void sklist_destroy_socket(struct sock **list, struct sock *sk);
+
+/*
+ * Handler for deferred kills.
+ */
+
+static void sklist_destroy_timer(unsigned long data)
+{
+ struct sock *sk=(struct sock *)data;
+ sklist_destroy_socket(NULL,sk);
+}
+
+/*
+ * Destroy a socket. We pass NULL for a list if we know the
+ * socket is not on a list.
+ */
+
+void sklist_destroy_socket(struct sock **list,struct sock *sk)
+{
+ if(list)
+ sklist_remove_socket(list, sk);
+
+ skb_queue_purge(&sk->receive_queue);
+
+ if(atomic_read(&sk->wmem_alloc) == 0 &&
+ atomic_read(&sk->rmem_alloc) == 0 &&
+ sk->dead)
+ {
+ sock_put(sk);
+ }
+ else
+ {
+ /*
+ * Someone is using our buffers still.. defer
+ */
+ init_timer(&sk->timer);
+ sk->timer.expires=jiffies+SOCK_DESTROY_TIME;
+ sk->timer.function=sklist_destroy_timer;
+ sk->timer.data = (unsigned long)sk;
+ add_timer(&sk->timer);
+ }
+}
+
+/*
+ * Set of default routines for initialising struct proto_ops when
+ * the protocol does not support a particular function. In certain
+ * cases where it makes no sense for a protocol to have a "do nothing"
+ * function, some default processing is provided.
+ */
+
+int sock_no_release(struct socket *sock)
+{
+ return 0;
+}
+
+int sock_no_bind(struct socket *sock, struct sockaddr *saddr, int len)
+{
+ return -EOPNOTSUPP;
+}
+
+int sock_no_connect(struct socket *sock, struct sockaddr *saddr,
+ int len, int flags)
+{
+ return -EOPNOTSUPP;
+}
+
+int sock_no_socketpair(struct socket *sock1, struct socket *sock2)
+{
+ return -EOPNOTSUPP;
+}
+
+int sock_no_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+ return -EOPNOTSUPP;
+}
+
+int sock_no_getname(struct socket *sock, struct sockaddr *saddr,
+ int *len, int peer)
+{
+ return -EOPNOTSUPP;
+}
+
+unsigned int sock_no_poll(struct file * file, struct socket *sock, poll_table *pt)
+{
+ return 0;
+}
+
+int sock_no_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ return -EOPNOTSUPP;
+}
+
+int sock_no_listen(struct socket *sock, int backlog)
+{
+ return -EOPNOTSUPP;
+}
+
+int sock_no_shutdown(struct socket *sock, int how)
+{
+ return -EOPNOTSUPP;
+}
+
+int sock_no_setsockopt(struct socket *sock, int level, int optname,
+ char *optval, int optlen)
+{
+ return -EOPNOTSUPP;
+}
+
+int sock_no_getsockopt(struct socket *sock, int level, int optname,
+ char *optval, int *optlen)
+{
+ return -EOPNOTSUPP;
+}
+
+/*
+ * Note: if you add something that sleeps here then change sock_fcntl()
+ * to do proper fd locking.
+ */
+int sock_no_fcntl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+
+ switch(cmd)
+ {
+ case F_SETOWN:
+ /*
+ * This is a little restrictive, but it's the only
+ * way to make sure that you can't send a sigurg to
+ * another process.
+ */
+ if (current->pgrp != -arg &&
+ current->pid != arg &&
+ !capable(CAP_KILL)) return(-EPERM);
+ sk->proc = arg;
+ return(0);
+ case F_GETOWN:
+ return(sk->proc);
+ default:
+ return(-EINVAL);
+ }
+}
+
+int sock_no_sendmsg(struct socket *sock, struct msghdr *m, int flags,
+ struct scm_cookie *scm)
+{
+ return -EOPNOTSUPP;
+}
+
+int sock_no_recvmsg(struct socket *sock, struct msghdr *m, int len, int flags,
+ struct scm_cookie *scm)
+{
+ return -EOPNOTSUPP;
+}
+
+int sock_no_mmap(struct file *file, struct socket *sock, struct vm_area_struct *vma)
+{
+ /* Mirror missing mmap method error code */
+ return -ENODEV;
+}
+
+ssize_t sock_no_sendpage(struct socket *sock, struct page *page, int offset, size_t size, int flags)
+{
+ ssize_t res;
+ struct msghdr msg;
+ struct iovec iov;
+ mm_segment_t old_fs;
+ char *kaddr;
+
+ kaddr = kmap(page);
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = flags;
+
+ iov.iov_base = kaddr + offset;
+ iov.iov_len = size;
+
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ res = sock_sendmsg(sock, &msg, size);
+ set_fs(old_fs);
+
+ kunmap(page);
+ return res;
+}
+
+/*
+ * Default Socket Callbacks
+ */
+
+void sock_def_wakeup(struct sock *sk)
+{
+ read_lock(&sk->callback_lock);
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible_all(sk->sleep);
+ read_unlock(&sk->callback_lock);
+}
+
+void sock_def_error_report(struct sock *sk)
+{
+ read_lock(&sk->callback_lock);
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible(sk->sleep);
+ sk_wake_async(sk,0,POLL_ERR);
+ read_unlock(&sk->callback_lock);
+}
+
+void sock_def_readable(struct sock *sk, int len)
+{
+ read_lock(&sk->callback_lock);
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible(sk->sleep);
+ sk_wake_async(sk,1,POLL_IN);
+ read_unlock(&sk->callback_lock);
+}
+
+void sock_def_write_space(struct sock *sk)
+{
+ read_lock(&sk->callback_lock);
+
+ /* Do not wake up a writer until he can make "significant"
+ * progress. --DaveM
+ */
+ if((atomic_read(&sk->wmem_alloc) << 1) <= sk->sndbuf) {
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible(sk->sleep);
+
+ /* Should agree with poll, otherwise some programs break */
+ if (sock_writeable(sk))
+ sk_wake_async(sk, 2, POLL_OUT);
+ }
+
+ read_unlock(&sk->callback_lock);
+}
+
+void sock_def_destruct(struct sock *sk)
+{
+ if (sk->protinfo.destruct_hook)
+ kfree(sk->protinfo.destruct_hook);
+}
+
+void sock_init_data(struct socket *sock, struct sock *sk)
+{
+ skb_queue_head_init(&sk->receive_queue);
+ skb_queue_head_init(&sk->write_queue);
+ skb_queue_head_init(&sk->error_queue);
+
+ init_timer(&sk->timer);
+
+ sk->allocation = GFP_KERNEL;
+ sk->rcvbuf = sysctl_rmem_default;
+ sk->sndbuf = sysctl_wmem_default;
+ sk->state = TCP_CLOSE;
+ sk->zapped = 1;
+ sk->socket = sock;
+
+ if(sock)
+ {
+ sk->type = sock->type;
+ sk->sleep = &sock->wait;
+ sock->sk = sk;
+ } else
+ sk->sleep = NULL;
+
+ sk->dst_lock = RW_LOCK_UNLOCKED;
+ sk->callback_lock = RW_LOCK_UNLOCKED;
+
+ sk->state_change = sock_def_wakeup;
+ sk->data_ready = sock_def_readable;
+ sk->write_space = sock_def_write_space;
+ sk->error_report = sock_def_error_report;
+ sk->destruct = sock_def_destruct;
+
+ sk->peercred.pid = 0;
+ sk->peercred.uid = -1;
+ sk->peercred.gid = -1;
+ sk->rcvlowat = 1;
+ sk->rcvtimeo = MAX_SCHEDULE_TIMEOUT;
+ sk->sndtimeo = MAX_SCHEDULE_TIMEOUT;
+
+ atomic_set(&sk->refcnt, 1);
+}
diff --git a/uClinux-2.4.31-uc0/net/core/sysctl_net_core.c b/uClinux-2.4.31-uc0/net/core/sysctl_net_core.c
new file mode 100644
index 0000000..3b95d979
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/sysctl_net_core.c
@@ -0,0 +1,98 @@
+/* -*- linux-c -*-
+ * sysctl_net_core.c: sysctl interface to net core subsystem.
+ *
+ * Begun April 1, 1996, Mike Shaver.
+ * Added /proc/sys/net/core directory entry (empty =) ). [MS]
+ */
+
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+#include <linux/config.h>
+
+#ifdef CONFIG_SYSCTL
+
+extern int netdev_max_backlog;
+extern int weight_p;
+extern int no_cong_thresh;
+extern int no_cong;
+extern int lo_cong;
+extern int mod_cong;
+extern int netdev_fastroute;
+extern int net_msg_cost;
+extern int net_msg_burst;
+
+extern __u32 sysctl_wmem_max;
+extern __u32 sysctl_rmem_max;
+extern __u32 sysctl_wmem_default;
+extern __u32 sysctl_rmem_default;
+
+extern int sysctl_core_destroy_delay;
+extern int sysctl_optmem_max;
+extern int sysctl_somaxconn;
+extern int sysctl_hot_list_len;
+
+#ifdef CONFIG_NET_DIVERT
+extern char sysctl_divert_version[];
+#endif /* CONFIG_NET_DIVERT */
+
+ctl_table core_table[] = {
+#ifdef CONFIG_NET
+ {NET_CORE_WMEM_MAX, "wmem_max",
+ &sysctl_wmem_max, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_CORE_RMEM_MAX, "rmem_max",
+ &sysctl_rmem_max, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_CORE_WMEM_DEFAULT, "wmem_default",
+ &sysctl_wmem_default, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_CORE_RMEM_DEFAULT, "rmem_default",
+ &sysctl_rmem_default, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_CORE_DEV_WEIGHT, "dev_weight",
+ &weight_p, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_CORE_MAX_BACKLOG, "netdev_max_backlog",
+ &netdev_max_backlog, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_CORE_NO_CONG_THRESH, "no_cong_thresh",
+ &no_cong_thresh, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_CORE_NO_CONG, "no_cong",
+ &no_cong, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_CORE_LO_CONG, "lo_cong",
+ &lo_cong, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_CORE_MOD_CONG, "mod_cong",
+ &mod_cong, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+#ifdef CONFIG_NET_FASTROUTE
+ {NET_CORE_FASTROUTE, "netdev_fastroute",
+ &netdev_fastroute, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+#endif
+ {NET_CORE_MSG_COST, "message_cost",
+ &net_msg_cost, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_CORE_MSG_BURST, "message_burst",
+ &net_msg_burst, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_CORE_OPTMEM_MAX, "optmem_max",
+ &sysctl_optmem_max, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_CORE_HOT_LIST_LENGTH, "hot_list_length",
+ &sysctl_hot_list_len, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+#ifdef CONFIG_NET_DIVERT
+ {NET_CORE_DIVERT_VERSION, "divert_version",
+ (void *)sysctl_divert_version, 32, 0444, NULL,
+ &proc_dostring},
+#endif /* CONFIG_NET_DIVERT */
+ {NET_CORE_SOMAXCONN, "somaxconn",
+ &sysctl_somaxconn, sizeof(int), 0644, NULL,
+ &proc_dointvec },
+#endif /* CONFIG_NET */
+ { 0 }
+};
+#endif
diff --git a/uClinux-2.4.31-uc0/net/core/utils.c b/uClinux-2.4.31-uc0/net/core/utils.c
new file mode 100644
index 0000000..3103934
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/utils.c
@@ -0,0 +1,73 @@
+/*
+ * Generic address resultion entity
+ *
+ * Authors:
+ * net_random Alan Cox
+ * net_ratelimit Andy Kleen
+ *
+ * Created by Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+
+static unsigned long net_rand_seed = 152L;
+
+unsigned long net_random(void)
+{
+ net_rand_seed=net_rand_seed*69069L+1;
+ return net_rand_seed^jiffies;
+}
+
+void net_srandom(unsigned long entropy)
+{
+ net_rand_seed ^= entropy;
+ net_random();
+}
+
+int net_msg_cost = 5*HZ;
+int net_msg_burst = 10*5*HZ;
+
+/*
+ * This enforces a rate limit: not more than one kernel message
+ * every 5secs to make a denial-of-service attack impossible.
+ *
+ * All warning printk()s should be guarded by this function.
+ */
+int net_ratelimit(void)
+{
+ static spinlock_t ratelimit_lock = SPIN_LOCK_UNLOCKED;
+ static unsigned long toks = 10*5*HZ;
+ static unsigned long last_msg;
+ static int missed;
+ unsigned long flags;
+ unsigned long now = jiffies;
+
+ spin_lock_irqsave(&ratelimit_lock, flags);
+ toks += now - last_msg;
+ last_msg = now;
+ if (toks > net_msg_burst)
+ toks = net_msg_burst;
+ if (toks >= net_msg_cost) {
+ int lost = missed;
+ missed = 0;
+ toks -= net_msg_cost;
+ spin_unlock_irqrestore(&ratelimit_lock, flags);
+ if (lost)
+ printk(KERN_WARNING "NET: %d messages suppressed.\n", lost);
+ return 1;
+ }
+ missed++;
+ spin_unlock_irqrestore(&ratelimit_lock, flags);
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/core/wireless.c b/uClinux-2.4.31-uc0/net/core/wireless.c
new file mode 100644
index 0000000..ca58046
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/core/wireless.c
@@ -0,0 +1,1282 @@
+/*
+ * This file implement the Wireless Extensions APIs.
+ *
+ * Authors : Jean Tourrilhes - HPL - <jt@hpl.hp.com>
+ * Copyright (c) 1997-2003 Jean Tourrilhes, All Rights Reserved.
+ *
+ * (As all part of the Linux kernel, this file is GPL)
+ */
+
+/************************** DOCUMENTATION **************************/
+/*
+ * API definition :
+ * --------------
+ * See <linux/wireless.h> for details of the APIs and the rest.
+ *
+ * History :
+ * -------
+ *
+ * v1 - 5.12.01 - Jean II
+ * o Created this file.
+ *
+ * v2 - 13.12.01 - Jean II
+ * o Move /proc/net/wireless stuff from net/core/dev.c to here
+ * o Make Wireless Extension IOCTLs go through here
+ * o Added iw_handler handling ;-)
+ * o Added standard ioctl description
+ * o Initial dumb commit strategy based on orinoco.c
+ *
+ * v3 - 19.12.01 - Jean II
+ * o Make sure we don't go out of standard_ioctl[] in ioctl_standard_call
+ * o Add event dispatcher function
+ * o Add event description
+ * o Propagate events as rtnetlink IFLA_WIRELESS option
+ * o Generate event on selected SET requests
+ *
+ * v4 - 18.04.02 - Jean II
+ * o Fix stupid off by one in iw_ioctl_description : IW_ESSID_MAX_SIZE + 1
+ *
+ * v5 - 21.06.02 - Jean II
+ * o Add IW_PRIV_TYPE_ADDR in priv_type_size (+cleanup)
+ * o Reshuffle IW_HEADER_TYPE_XXX to map IW_PRIV_TYPE_XXX changes
+ * o Add IWEVCUSTOM for driver specific event/scanning token
+ * o Turn on WE_STRICT_WRITE by default + kernel warning
+ * o Fix WE_STRICT_WRITE in ioctl_export_private() (32 => iw_num)
+ * o Fix off-by-one in test (extra_size <= IFNAMSIZ)
+ *
+ * v6 - 9.01.03 - Jean II
+ * o Add common spy support : iw_handler_set_spy(), wireless_spy_update()
+ * o Add enhanced spy support : iw_handler_set_thrspy() and event.
+ * o Add WIRELESS_EXT version display in /proc/net/wireless
+ */
+
+/***************************** INCLUDES *****************************/
+
+#include <asm/uaccess.h> /* copy_to_user() */
+#include <linux/config.h> /* Not needed ??? */
+#include <linux/types.h> /* off_t */
+#include <linux/netdevice.h> /* struct ifreq, dev_get_by_name() */
+#include <linux/rtnetlink.h> /* rtnetlink stuff */
+#include <linux/if_arp.h> /* ARPHRD_ETHER */
+
+#include <linux/wireless.h> /* Pretty obvious */
+#include <net/iw_handler.h> /* New driver API */
+
+/**************************** CONSTANTS ****************************/
+
+/* Enough lenience, let's make sure things are proper... */
+#define WE_STRICT_WRITE /* Check write buffer size */
+/* I'll probably drop both the define and kernel message in the next version */
+
+/* Debuging stuff */
+#undef WE_IOCTL_DEBUG /* Debug IOCTL API */
+#undef WE_EVENT_DEBUG /* Debug Event dispatcher */
+#undef WE_SPY_DEBUG /* Debug enhanced spy support */
+
+/* Options */
+#define WE_EVENT_NETLINK /* Propagate events using rtnetlink */
+#define WE_SET_EVENT /* Generate an event on some set commands */
+
+/************************* GLOBAL VARIABLES *************************/
+/*
+ * You should not use global variables, because of re-entrancy.
+ * On our case, it's only const, so it's OK...
+ */
+/*
+ * Meta-data about all the standard Wireless Extension request we
+ * know about.
+ */
+static const struct iw_ioctl_description standard_ioctl[] = {
+ /* SIOCSIWCOMMIT */
+ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0},
+ /* SIOCGIWNAME */
+ { IW_HEADER_TYPE_CHAR, 0, 0, 0, 0, IW_DESCR_FLAG_DUMP},
+ /* SIOCSIWNWID */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, IW_DESCR_FLAG_EVENT},
+ /* SIOCGIWNWID */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, IW_DESCR_FLAG_DUMP},
+ /* SIOCSIWFREQ */
+ { IW_HEADER_TYPE_FREQ, 0, 0, 0, 0, IW_DESCR_FLAG_EVENT},
+ /* SIOCGIWFREQ */
+ { IW_HEADER_TYPE_FREQ, 0, 0, 0, 0, IW_DESCR_FLAG_DUMP},
+ /* SIOCSIWMODE */
+ { IW_HEADER_TYPE_UINT, 0, 0, 0, 0, IW_DESCR_FLAG_EVENT},
+ /* SIOCGIWMODE */
+ { IW_HEADER_TYPE_UINT, 0, 0, 0, 0, IW_DESCR_FLAG_DUMP},
+ /* SIOCSIWSENS */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+ /* SIOCGIWSENS */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+ /* SIOCSIWRANGE */
+ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0},
+ /* SIOCGIWRANGE */
+ { IW_HEADER_TYPE_POINT, 0, 1, 0, sizeof(struct iw_range), IW_DESCR_FLAG_DUMP},
+ /* SIOCSIWPRIV */
+ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0},
+ /* SIOCGIWPRIV (handled directly by us) */
+ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0},
+ /* SIOCSIWSTATS */
+ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0},
+ /* SIOCGIWSTATS (handled directly by us) */
+ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, IW_DESCR_FLAG_DUMP},
+ /* SIOCSIWSPY */
+ { IW_HEADER_TYPE_POINT, 0, sizeof(struct sockaddr), 0, IW_MAX_SPY, 0},
+ /* SIOCGIWSPY */
+ { IW_HEADER_TYPE_POINT, 0, (sizeof(struct sockaddr) + sizeof(struct iw_quality)), 0, IW_MAX_SPY, 0},
+ /* SIOCSIWTHRSPY */
+ { IW_HEADER_TYPE_POINT, 0, sizeof(struct iw_thrspy), 1, 1, 0},
+ /* SIOCGIWTHRSPY */
+ { IW_HEADER_TYPE_POINT, 0, sizeof(struct iw_thrspy), 1, 1, 0},
+ /* SIOCSIWAP */
+ { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, 0},
+ /* SIOCGIWAP */
+ { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, IW_DESCR_FLAG_DUMP},
+ /* -- hole -- */
+ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0},
+ /* SIOCGIWAPLIST */
+ { IW_HEADER_TYPE_POINT, 0, (sizeof(struct sockaddr) + sizeof(struct iw_quality)), 0, IW_MAX_AP, 0},
+ /* SIOCSIWSCAN */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+ /* SIOCGIWSCAN */
+ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_SCAN_MAX_DATA, 0},
+ /* SIOCSIWESSID */
+ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE + 1, IW_DESCR_FLAG_EVENT},
+ /* SIOCGIWESSID */
+ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE + 1, IW_DESCR_FLAG_DUMP},
+ /* SIOCSIWNICKN */
+ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE + 1, 0},
+ /* SIOCGIWNICKN */
+ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ESSID_MAX_SIZE + 1, 0},
+ /* -- hole -- */
+ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0},
+ /* -- hole -- */
+ { IW_HEADER_TYPE_NULL, 0, 0, 0, 0, 0},
+ /* SIOCSIWRATE */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+ /* SIOCGIWRATE */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+ /* SIOCSIWRTS */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+ /* SIOCGIWRTS */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+ /* SIOCSIWFRAG */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+ /* SIOCGIWFRAG */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+ /* SIOCSIWTXPOW */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+ /* SIOCGIWTXPOW */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+ /* SIOCSIWRETRY */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+ /* SIOCGIWRETRY */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+ /* SIOCSIWENCODE */
+ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ENCODING_TOKEN_MAX, IW_DESCR_FLAG_EVENT | IW_DESCR_FLAG_RESTRICT},
+ /* SIOCGIWENCODE */
+ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_ENCODING_TOKEN_MAX, IW_DESCR_FLAG_DUMP | IW_DESCR_FLAG_RESTRICT},
+ /* SIOCSIWPOWER */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+ /* SIOCGIWPOWER */
+ { IW_HEADER_TYPE_PARAM, 0, 0, 0, 0, 0},
+};
+static const int standard_ioctl_num = (sizeof(standard_ioctl) /
+ sizeof(struct iw_ioctl_description));
+
+/*
+ * Meta-data about all the additional standard Wireless Extension events
+ * we know about.
+ */
+static const struct iw_ioctl_description standard_event[] = {
+ /* IWEVTXDROP */
+ { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, 0},
+ /* IWEVQUAL */
+ { IW_HEADER_TYPE_QUAL, 0, 0, 0, 0, 0},
+ /* IWEVCUSTOM */
+ { IW_HEADER_TYPE_POINT, 0, 1, 0, IW_CUSTOM_MAX, 0},
+ /* IWEVREGISTERED */
+ { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, 0},
+ /* IWEVEXPIRED */
+ { IW_HEADER_TYPE_ADDR, 0, 0, 0, 0, 0},
+};
+static const int standard_event_num = (sizeof(standard_event) /
+ sizeof(struct iw_ioctl_description));
+
+/* Size (in bytes) of the various private data types */
+static const char priv_type_size[] = {
+ 0, /* IW_PRIV_TYPE_NONE */
+ 1, /* IW_PRIV_TYPE_BYTE */
+ 1, /* IW_PRIV_TYPE_CHAR */
+ 0, /* Not defined */
+ sizeof(__u32), /* IW_PRIV_TYPE_INT */
+ sizeof(struct iw_freq), /* IW_PRIV_TYPE_FLOAT */
+ sizeof(struct sockaddr), /* IW_PRIV_TYPE_ADDR */
+ 0, /* Not defined */
+};
+
+/* Size (in bytes) of various events */
+static const int event_type_size[] = {
+ IW_EV_LCP_LEN, /* IW_HEADER_TYPE_NULL */
+ 0,
+ IW_EV_CHAR_LEN, /* IW_HEADER_TYPE_CHAR */
+ 0,
+ IW_EV_UINT_LEN, /* IW_HEADER_TYPE_UINT */
+ IW_EV_FREQ_LEN, /* IW_HEADER_TYPE_FREQ */
+ IW_EV_ADDR_LEN, /* IW_HEADER_TYPE_ADDR */
+ 0,
+ IW_EV_POINT_LEN, /* Without variable payload */
+ IW_EV_PARAM_LEN, /* IW_HEADER_TYPE_PARAM */
+ IW_EV_QUAL_LEN, /* IW_HEADER_TYPE_QUAL */
+};
+
+/************************ COMMON SUBROUTINES ************************/
+/*
+ * Stuff that may be used in various place or doesn't fit in one
+ * of the section below.
+ */
+
+/* ---------------------------------------------------------------- */
+/*
+ * Return the driver handler associated with a specific Wireless Extension.
+ * Called from various place, so make sure it remains efficient.
+ */
+static inline iw_handler get_handler(struct net_device *dev,
+ unsigned int cmd)
+{
+ /* Don't "optimise" the following variable, it will crash */
+ unsigned int index; /* *MUST* be unsigned */
+
+ /* Check if we have some wireless handlers defined */
+ if(dev->wireless_handlers == NULL)
+ return NULL;
+
+ /* Try as a standard command */
+ index = cmd - SIOCIWFIRST;
+ if(index < dev->wireless_handlers->num_standard)
+ return dev->wireless_handlers->standard[index];
+
+ /* Try as a private command */
+ index = cmd - SIOCIWFIRSTPRIV;
+ if(index < dev->wireless_handlers->num_private)
+ return dev->wireless_handlers->private[index];
+
+ /* Not found */
+ return NULL;
+}
+
+/* ---------------------------------------------------------------- */
+/*
+ * Get statistics out of the driver
+ */
+static inline struct iw_statistics *get_wireless_stats(struct net_device *dev)
+{
+ return (dev->get_wireless_stats ?
+ dev->get_wireless_stats(dev) :
+ (struct iw_statistics *) NULL);
+ /* In the future, get_wireless_stats may move from 'struct net_device'
+ * to 'struct iw_handler_def', to de-bloat struct net_device.
+ * Definitely worse a thought... */
+}
+
+/* ---------------------------------------------------------------- */
+/*
+ * Call the commit handler in the driver
+ * (if exist and if conditions are right)
+ *
+ * Note : our current commit strategy is currently pretty dumb,
+ * but we will be able to improve on that...
+ * The goal is to try to agreagate as many changes as possible
+ * before doing the commit. Drivers that will define a commit handler
+ * are usually those that need a reset after changing parameters, so
+ * we want to minimise the number of reset.
+ * A cool idea is to use a timer : at each "set" command, we re-set the
+ * timer, when the timer eventually fires, we call the driver.
+ * Hopefully, more on that later.
+ *
+ * Also, I'm waiting to see how many people will complain about the
+ * netif_running(dev) test. I'm open on that one...
+ * Hopefully, the driver will remember to do a commit in "open()" ;-)
+ */
+static inline int call_commit_handler(struct net_device * dev)
+{
+ if((netif_running(dev)) &&
+ (dev->wireless_handlers->standard[0] != NULL)) {
+ /* Call the commit handler on the driver */
+ return dev->wireless_handlers->standard[0](dev, NULL,
+ NULL, NULL);
+ } else
+ return 0; /* Command completed successfully */
+}
+
+/* ---------------------------------------------------------------- */
+/*
+ * Number of private arguments
+ */
+static inline int get_priv_size(__u16 args)
+{
+ int num = args & IW_PRIV_SIZE_MASK;
+ int type = (args & IW_PRIV_TYPE_MASK) >> 12;
+
+ return num * priv_type_size[type];
+}
+
+
+/******************** /proc/net/wireless SUPPORT ********************/
+/*
+ * The /proc/net/wireless file is a human readable user-space interface
+ * exporting various wireless specific statistics from the wireless devices.
+ * This is the most popular part of the Wireless Extensions ;-)
+ *
+ * This interface is a pure clone of /proc/net/dev (in net/core/dev.c).
+ * The content of the file is basically the content of "struct iw_statistics".
+ */
+
+#ifdef CONFIG_PROC_FS
+
+/* ---------------------------------------------------------------- */
+/*
+ * Print one entry (line) of /proc/net/wireless
+ */
+static inline int sprintf_wireless_stats(char *buffer, struct net_device *dev)
+{
+ /* Get stats from the driver */
+ struct iw_statistics *stats;
+ int size;
+
+ stats = get_wireless_stats(dev);
+ if (stats != (struct iw_statistics *) NULL) {
+ size = sprintf(buffer,
+ "%6s: %04x %3d%c %3d%c %3d%c %6d %6d %6d %6d %6d %6d\n",
+ dev->name,
+ stats->status,
+ stats->qual.qual,
+ stats->qual.updated & 1 ? '.' : ' ',
+ ((__u8) stats->qual.level),
+ stats->qual.updated & 2 ? '.' : ' ',
+ ((__u8) stats->qual.noise),
+ stats->qual.updated & 4 ? '.' : ' ',
+ stats->discard.nwid,
+ stats->discard.code,
+ stats->discard.fragment,
+ stats->discard.retries,
+ stats->discard.misc,
+ stats->miss.beacon);
+ stats->qual.updated = 0;
+ }
+ else
+ size = 0;
+
+ return size;
+}
+
+/* ---------------------------------------------------------------- */
+/*
+ * Print info for /proc/net/wireless (print all entries)
+ */
+int dev_get_wireless_info(char * buffer, char **start, off_t offset,
+ int length)
+{
+ int len = 0;
+ off_t begin = 0;
+ off_t pos = 0;
+ int size;
+
+ struct net_device * dev;
+
+ size = sprintf(buffer,
+ "Inter-| sta-| Quality | Discarded packets | Missed | WE\n"
+ " face | tus | link level noise | nwid crypt frag retry misc | beacon | %d\n",
+ WIRELESS_EXT);
+
+ pos += size;
+ len += size;
+
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev != NULL; dev = dev->next) {
+ size = sprintf_wireless_stats(buffer + len, dev);
+ len += size;
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (pos > offset + length)
+ break;
+ }
+ read_unlock(&dev_base_lock);
+
+ *start = buffer + (offset - begin); /* Start of wanted data */
+ len -= (offset - begin); /* Start slop */
+ if (len > length)
+ len = length; /* Ending slop */
+ if (len < 0)
+ len = 0;
+
+ return len;
+}
+#endif /* CONFIG_PROC_FS */
+
+/************************** IOCTL SUPPORT **************************/
+/*
+ * The original user space API to configure all those Wireless Extensions
+ * is through IOCTLs.
+ * In there, we check if we need to call the new driver API (iw_handler)
+ * or just call the driver ioctl handler.
+ */
+
+/* ---------------------------------------------------------------- */
+/*
+ * Allow programatic access to /proc/net/wireless even if /proc
+ * doesn't exist... Also more efficient...
+ */
+static inline int dev_iwstats(struct net_device *dev, struct ifreq *ifr)
+{
+ /* Get stats from the driver */
+ struct iw_statistics *stats;
+
+ stats = get_wireless_stats(dev);
+ if (stats != (struct iw_statistics *) NULL) {
+ struct iwreq * wrq = (struct iwreq *)ifr;
+
+ /* Copy statistics to the user buffer */
+ if(copy_to_user(wrq->u.data.pointer, stats,
+ sizeof(struct iw_statistics)))
+ return -EFAULT;
+
+ /* Check if we need to clear the update flag */
+ if(wrq->u.data.flags != 0)
+ stats->qual.updated = 0;
+ return 0;
+ } else
+ return -EOPNOTSUPP;
+}
+
+/* ---------------------------------------------------------------- */
+/*
+ * Export the driver private handler definition
+ * They will be picked up by tools like iwpriv...
+ */
+static inline int ioctl_export_private(struct net_device * dev,
+ struct ifreq * ifr)
+{
+ struct iwreq * iwr = (struct iwreq *) ifr;
+
+ /* Check if the driver has something to export */
+ if((dev->wireless_handlers->num_private_args == 0) ||
+ (dev->wireless_handlers->private_args == NULL))
+ return -EOPNOTSUPP;
+
+ /* Check NULL pointer */
+ if(iwr->u.data.pointer == NULL)
+ return -EFAULT;
+#ifdef WE_STRICT_WRITE
+ /* Check if there is enough buffer up there */
+ if(iwr->u.data.length < dev->wireless_handlers->num_private_args) {
+ printk(KERN_ERR "%s (WE) : Buffer for request SIOCGIWPRIV too small (%d<%d)\n", dev->name, iwr->u.data.length, dev->wireless_handlers->num_private_args);
+ return -E2BIG;
+ }
+#endif /* WE_STRICT_WRITE */
+
+ /* Set the number of available ioctls. */
+ iwr->u.data.length = dev->wireless_handlers->num_private_args;
+
+ /* Copy structure to the user buffer. */
+ if (copy_to_user(iwr->u.data.pointer,
+ dev->wireless_handlers->private_args,
+ sizeof(struct iw_priv_args) * iwr->u.data.length))
+ return -EFAULT;
+
+ return 0;
+}
+
+/* ---------------------------------------------------------------- */
+/*
+ * Wrapper to call a standard Wireless Extension handler.
+ * We do various checks and also take care of moving data between
+ * user space and kernel space.
+ */
+static inline int ioctl_standard_call(struct net_device * dev,
+ struct ifreq * ifr,
+ unsigned int cmd,
+ iw_handler handler)
+{
+ struct iwreq * iwr = (struct iwreq *) ifr;
+ const struct iw_ioctl_description * descr;
+ struct iw_request_info info;
+ int ret = -EINVAL;
+ int user_size = 0;
+
+ /* Get the description of the IOCTL */
+ if((cmd - SIOCIWFIRST) >= standard_ioctl_num)
+ return -EOPNOTSUPP;
+ descr = &(standard_ioctl[cmd - SIOCIWFIRST]);
+
+#ifdef WE_IOCTL_DEBUG
+ printk(KERN_DEBUG "%s (WE) : Found standard handler for 0x%04X\n",
+ ifr->ifr_name, cmd);
+ printk(KERN_DEBUG "%s (WE) : Header type : %d, Token type : %d, size : %d, token : %d\n", dev->name, descr->header_type, descr->token_type, descr->token_size, descr->max_tokens);
+#endif /* WE_IOCTL_DEBUG */
+
+ /* Prepare the call */
+ info.cmd = cmd;
+ info.flags = 0;
+
+ /* Check if we have a pointer to user space data or not */
+ if(descr->header_type != IW_HEADER_TYPE_POINT) {
+
+ /* No extra arguments. Trivial to handle */
+ ret = handler(dev, &info, &(iwr->u), NULL);
+
+#ifdef WE_SET_EVENT
+ /* Generate an event to notify listeners of the change */
+ if((descr->flags & IW_DESCR_FLAG_EVENT) &&
+ ((ret == 0) || (ret == -EIWCOMMIT)))
+ wireless_send_event(dev, cmd, &(iwr->u), NULL);
+#endif /* WE_SET_EVENT */
+ } else {
+ char * extra;
+ int err;
+
+ /* Check what user space is giving us */
+ if(IW_IS_SET(cmd)) {
+ /* Check NULL pointer */
+ if((iwr->u.data.pointer == NULL) &&
+ (iwr->u.data.length != 0))
+ return -EFAULT;
+ /* Check if number of token fits within bounds */
+ if(iwr->u.data.length > descr->max_tokens)
+ return -E2BIG;
+ if(iwr->u.data.length < descr->min_tokens)
+ return -EINVAL;
+ } else {
+ /* Check NULL pointer */
+ if(iwr->u.data.pointer == NULL)
+ return -EFAULT;
+ /* Save user space buffer size for checking */
+ user_size = iwr->u.data.length;
+ }
+
+#ifdef WE_IOCTL_DEBUG
+ printk(KERN_DEBUG "%s (WE) : Malloc %d bytes\n",
+ dev->name, descr->max_tokens * descr->token_size);
+#endif /* WE_IOCTL_DEBUG */
+
+ /* Always allocate for max space. Easier, and won't last
+ * long... */
+ extra = kmalloc(descr->max_tokens * descr->token_size,
+ GFP_KERNEL);
+ if (extra == NULL) {
+ return -ENOMEM;
+ }
+
+ /* If it is a SET, get all the extra data in here */
+ if(IW_IS_SET(cmd) && (iwr->u.data.length != 0)) {
+ err = copy_from_user(extra, iwr->u.data.pointer,
+ iwr->u.data.length *
+ descr->token_size);
+ if (err) {
+ kfree(extra);
+ return -EFAULT;
+ }
+#ifdef WE_IOCTL_DEBUG
+ printk(KERN_DEBUG "%s (WE) : Got %d bytes\n",
+ dev->name,
+ iwr->u.data.length * descr->token_size);
+#endif /* WE_IOCTL_DEBUG */
+ }
+
+ /* Call the handler */
+ ret = handler(dev, &info, &(iwr->u), extra);
+
+ /* If we have something to return to the user */
+ if (!ret && IW_IS_GET(cmd)) {
+#ifdef WE_STRICT_WRITE
+ /* Check if there is enough buffer up there */
+ if(user_size < iwr->u.data.length) {
+ printk(KERN_ERR "%s (WE) : Buffer for request %04X too small (%d<%d)\n", dev->name, cmd, user_size, iwr->u.data.length);
+ kfree(extra);
+ return -E2BIG;
+ }
+#endif /* WE_STRICT_WRITE */
+
+ err = copy_to_user(iwr->u.data.pointer, extra,
+ iwr->u.data.length *
+ descr->token_size);
+ if (err)
+ ret = -EFAULT;
+#ifdef WE_IOCTL_DEBUG
+ printk(KERN_DEBUG "%s (WE) : Wrote %d bytes\n",
+ dev->name,
+ iwr->u.data.length * descr->token_size);
+#endif /* WE_IOCTL_DEBUG */
+ }
+
+#ifdef WE_SET_EVENT
+ /* Generate an event to notify listeners of the change */
+ if((descr->flags & IW_DESCR_FLAG_EVENT) &&
+ ((ret == 0) || (ret == -EIWCOMMIT))) {
+ if(descr->flags & IW_DESCR_FLAG_RESTRICT)
+ /* If the event is restricted, don't
+ * export the payload */
+ wireless_send_event(dev, cmd, &(iwr->u), NULL);
+ else
+ wireless_send_event(dev, cmd, &(iwr->u),
+ extra);
+ }
+#endif /* WE_SET_EVENT */
+
+ /* Cleanup - I told you it wasn't that long ;-) */
+ kfree(extra);
+ }
+
+ /* Call commit handler if needed and defined */
+ if(ret == -EIWCOMMIT)
+ ret = call_commit_handler(dev);
+
+ /* Here, we will generate the appropriate event if needed */
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------- */
+/*
+ * Wrapper to call a private Wireless Extension handler.
+ * We do various checks and also take care of moving data between
+ * user space and kernel space.
+ * It's not as nice and slimline as the standard wrapper. The cause
+ * is struct iw_priv_args, which was not really designed for the
+ * job we are going here.
+ *
+ * IMPORTANT : This function prevent to set and get data on the same
+ * IOCTL and enforce the SET/GET convention. Not doing it would be
+ * far too hairy...
+ * If you need to set and get data at the same time, please don't use
+ * a iw_handler but process it in your ioctl handler (i.e. use the
+ * old driver API).
+ */
+static inline int ioctl_private_call(struct net_device * dev,
+ struct ifreq * ifr,
+ unsigned int cmd,
+ iw_handler handler)
+{
+ struct iwreq * iwr = (struct iwreq *) ifr;
+ struct iw_priv_args * descr = NULL;
+ struct iw_request_info info;
+ int extra_size = 0;
+ int i;
+ int ret = -EINVAL;
+
+ /* Get the description of the IOCTL */
+ for(i = 0; i < dev->wireless_handlers->num_private_args; i++)
+ if(cmd == dev->wireless_handlers->private_args[i].cmd) {
+ descr = &(dev->wireless_handlers->private_args[i]);
+ break;
+ }
+
+#ifdef WE_IOCTL_DEBUG
+ printk(KERN_DEBUG "%s (WE) : Found private handler for 0x%04X\n",
+ ifr->ifr_name, cmd);
+ if(descr) {
+ printk(KERN_DEBUG "%s (WE) : Name %s, set %X, get %X\n",
+ dev->name, descr->name,
+ descr->set_args, descr->get_args);
+ }
+#endif /* WE_IOCTL_DEBUG */
+
+ /* Compute the size of the set/get arguments */
+ if(descr != NULL) {
+ if(IW_IS_SET(cmd)) {
+ int offset = 0; /* For sub-ioctls */
+ /* Check for sub-ioctl handler */
+ if(descr->name[0] == '\0')
+ /* Reserve one int for sub-ioctl index */
+ offset = sizeof(__u32);
+
+ /* Size of set arguments */
+ extra_size = get_priv_size(descr->set_args);
+
+ /* Does it fits in iwr ? */
+ if((descr->set_args & IW_PRIV_SIZE_FIXED) &&
+ ((extra_size + offset) <= IFNAMSIZ))
+ extra_size = 0;
+ } else {
+ /* Size of set arguments */
+ extra_size = get_priv_size(descr->get_args);
+
+ /* Does it fits in iwr ? */
+ if((descr->get_args & IW_PRIV_SIZE_FIXED) &&
+ (extra_size <= IFNAMSIZ))
+ extra_size = 0;
+ }
+ }
+
+ /* Prepare the call */
+ info.cmd = cmd;
+ info.flags = 0;
+
+ /* Check if we have a pointer to user space data or not. */
+ if(extra_size == 0) {
+ /* No extra arguments. Trivial to handle */
+ ret = handler(dev, &info, &(iwr->u), (char *) &(iwr->u));
+ } else {
+ char * extra;
+ int err;
+
+ /* Check what user space is giving us */
+ if(IW_IS_SET(cmd)) {
+ /* Check NULL pointer */
+ if((iwr->u.data.pointer == NULL) &&
+ (iwr->u.data.length != 0))
+ return -EFAULT;
+
+ /* Does it fits within bounds ? */
+ if(iwr->u.data.length > (descr->set_args &
+ IW_PRIV_SIZE_MASK))
+ return -E2BIG;
+ } else {
+ /* Check NULL pointer */
+ if(iwr->u.data.pointer == NULL)
+ return -EFAULT;
+ }
+
+#ifdef WE_IOCTL_DEBUG
+ printk(KERN_DEBUG "%s (WE) : Malloc %d bytes\n",
+ dev->name, extra_size);
+#endif /* WE_IOCTL_DEBUG */
+
+ /* Always allocate for max space. Easier, and won't last
+ * long... */
+ extra = kmalloc(extra_size, GFP_KERNEL);
+ if (extra == NULL) {
+ return -ENOMEM;
+ }
+
+ /* If it is a SET, get all the extra data in here */
+ if(IW_IS_SET(cmd) && (iwr->u.data.length != 0)) {
+ err = copy_from_user(extra, iwr->u.data.pointer,
+ extra_size);
+ if (err) {
+ kfree(extra);
+ return -EFAULT;
+ }
+#ifdef WE_IOCTL_DEBUG
+ printk(KERN_DEBUG "%s (WE) : Got %d elem\n",
+ dev->name, iwr->u.data.length);
+#endif /* WE_IOCTL_DEBUG */
+ }
+
+ /* Call the handler */
+ ret = handler(dev, &info, &(iwr->u), extra);
+
+ /* If we have something to return to the user */
+ if (!ret && IW_IS_GET(cmd)) {
+ err = copy_to_user(iwr->u.data.pointer, extra,
+ extra_size);
+ if (err)
+ ret = -EFAULT;
+#ifdef WE_IOCTL_DEBUG
+ printk(KERN_DEBUG "%s (WE) : Wrote %d elem\n",
+ dev->name, iwr->u.data.length);
+#endif /* WE_IOCTL_DEBUG */
+ }
+
+ /* Cleanup - I told you it wasn't that long ;-) */
+ kfree(extra);
+ }
+
+
+ /* Call commit handler if needed and defined */
+ if(ret == -EIWCOMMIT)
+ ret = call_commit_handler(dev);
+
+ return ret;
+}
+
+/* ---------------------------------------------------------------- */
+/*
+ * Main IOCTl dispatcher. Called from the main networking code
+ * (dev_ioctl() in net/core/dev.c).
+ * Check the type of IOCTL and call the appropriate wrapper...
+ */
+int wireless_process_ioctl(struct ifreq *ifr, unsigned int cmd)
+{
+ struct net_device *dev;
+ iw_handler handler;
+
+ /* Permissions are already checked in dev_ioctl() before calling us.
+ * The copy_to/from_user() of ifr is also dealt with in there */
+
+ /* Make sure the device exist */
+ if ((dev = __dev_get_by_name(ifr->ifr_name)) == NULL)
+ return -ENODEV;
+
+ /* A bunch of special cases, then the generic case...
+ * Note that 'cmd' is already filtered in dev_ioctl() with
+ * (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) */
+ switch(cmd)
+ {
+ case SIOCGIWSTATS:
+ /* Get Wireless Stats */
+ return dev_iwstats(dev, ifr);
+
+ case SIOCGIWPRIV:
+ /* Check if we have some wireless handlers defined */
+ if(dev->wireless_handlers != NULL) {
+ /* We export to user space the definition of
+ * the private handler ourselves */
+ return ioctl_export_private(dev, ifr);
+ }
+ // ## Fall-through for old API ##
+ default:
+ /* Generic IOCTL */
+ /* Basic check */
+ if (!netif_device_present(dev))
+ return -ENODEV;
+ /* New driver API : try to find the handler */
+ handler = get_handler(dev, cmd);
+ if(handler != NULL) {
+ /* Standard and private are not the same */
+ if(cmd < SIOCIWFIRSTPRIV)
+ return ioctl_standard_call(dev,
+ ifr,
+ cmd,
+ handler);
+ else
+ return ioctl_private_call(dev,
+ ifr,
+ cmd,
+ handler);
+ }
+ /* Old driver API : call driver ioctl handler */
+ if (dev->do_ioctl) {
+ return dev->do_ioctl(dev, ifr, cmd);
+ }
+ return -EOPNOTSUPP;
+ }
+ /* Not reached */
+ return -EINVAL;
+}
+
+/************************* EVENT PROCESSING *************************/
+/*
+ * Process events generated by the wireless layer or the driver.
+ * Most often, the event will be propagated through rtnetlink
+ */
+
+#ifdef WE_EVENT_NETLINK
+/* "rtnl" is defined in net/core/rtnetlink.c, but we need it here.
+ * It is declared in <linux/rtnetlink.h> */
+
+/* ---------------------------------------------------------------- */
+/*
+ * Fill a rtnetlink message with our event data.
+ * Note that we propage only the specified event and don't dump the
+ * current wireless config. Dumping the wireless config is far too
+ * expensive (for each parameter, the driver need to query the hardware).
+ */
+static inline int rtnetlink_fill_iwinfo(struct sk_buff * skb,
+ struct net_device * dev,
+ int type,
+ char * event,
+ int event_len)
+{
+ struct ifinfomsg *r;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+ nlh = NLMSG_PUT(skb, 0, 0, type, sizeof(*r));
+ r = NLMSG_DATA(nlh);
+ r->ifi_family = AF_UNSPEC;
+ r->ifi_type = dev->type;
+ r->ifi_index = dev->ifindex;
+ r->ifi_flags = dev->flags;
+ r->ifi_change = 0; /* Wireless changes don't affect those flags */
+
+ /* Add the wireless events in the netlink packet */
+ RTA_PUT(skb, IFLA_WIRELESS,
+ event_len, event);
+
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+/* ---------------------------------------------------------------- */
+/*
+ * Create and broadcast and send it on the standard rtnetlink socket
+ * This is a pure clone rtmsg_ifinfo() in net/core/rtnetlink.c
+ * Andrzej Krzysztofowicz mandated that I used a IFLA_XXX field
+ * within a RTM_NEWLINK event.
+ */
+static inline void rtmsg_iwinfo(struct net_device * dev,
+ char * event,
+ int event_len)
+{
+ struct sk_buff *skb;
+ int size = NLMSG_GOODSIZE;
+
+ skb = alloc_skb(size, GFP_ATOMIC);
+ if (!skb)
+ return;
+
+ if (rtnetlink_fill_iwinfo(skb, dev, RTM_NEWLINK,
+ event, event_len) < 0) {
+ kfree_skb(skb);
+ return;
+ }
+ NETLINK_CB(skb).dst_groups = RTMGRP_LINK;
+ netlink_broadcast(rtnl, skb, 0, RTMGRP_LINK, GFP_ATOMIC);
+}
+#endif /* WE_EVENT_NETLINK */
+
+/* ---------------------------------------------------------------- */
+/*
+ * Main event dispatcher. Called from other parts and drivers.
+ * Send the event on the apropriate channels.
+ * May be called from interrupt context.
+ */
+void wireless_send_event(struct net_device * dev,
+ unsigned int cmd,
+ union iwreq_data * wrqu,
+ char * extra)
+{
+ const struct iw_ioctl_description * descr = NULL;
+ int extra_len = 0;
+ struct iw_event *event; /* Mallocated whole event */
+ int event_len; /* Its size */
+ int hdr_len; /* Size of the event header */
+ /* Don't "optimise" the following variable, it will crash */
+ unsigned cmd_index; /* *MUST* be unsigned */
+
+ /* Get the description of the IOCTL */
+ if(cmd <= SIOCIWLAST) {
+ cmd_index = cmd - SIOCIWFIRST;
+ if(cmd_index < standard_ioctl_num)
+ descr = &(standard_ioctl[cmd_index]);
+ } else {
+ cmd_index = cmd - IWEVFIRST;
+ if(cmd_index < standard_event_num)
+ descr = &(standard_event[cmd_index]);
+ }
+ /* Don't accept unknown events */
+ if(descr == NULL) {
+ /* Note : we don't return an error to the driver, because
+ * the driver would not know what to do about it. It can't
+ * return an error to the user, because the event is not
+ * initiated by a user request.
+ * The best the driver could do is to log an error message.
+ * We will do it ourselves instead...
+ */
+ printk(KERN_ERR "%s (WE) : Invalid/Unknown Wireless Event (0x%04X)\n",
+ dev->name, cmd);
+ return;
+ }
+#ifdef WE_EVENT_DEBUG
+ printk(KERN_DEBUG "%s (WE) : Got event 0x%04X\n",
+ dev->name, cmd);
+ printk(KERN_DEBUG "%s (WE) : Header type : %d, Token type : %d, size : %d, token : %d\n", dev->name, descr->header_type, descr->token_type, descr->token_size, descr->max_tokens);
+#endif /* WE_EVENT_DEBUG */
+
+ /* Check extra parameters and set extra_len */
+ if(descr->header_type == IW_HEADER_TYPE_POINT) {
+ /* Check if number of token fits within bounds */
+ if(wrqu->data.length > descr->max_tokens) {
+ printk(KERN_ERR "%s (WE) : Wireless Event too big (%d)\n", dev->name, wrqu->data.length);
+ return;
+ }
+ if(wrqu->data.length < descr->min_tokens) {
+ printk(KERN_ERR "%s (WE) : Wireless Event too small (%d)\n", dev->name, wrqu->data.length);
+ return;
+ }
+ /* Calculate extra_len - extra is NULL for restricted events */
+ if(extra != NULL)
+ extra_len = wrqu->data.length * descr->token_size;
+#ifdef WE_EVENT_DEBUG
+ printk(KERN_DEBUG "%s (WE) : Event 0x%04X, tokens %d, extra_len %d\n", dev->name, cmd, wrqu->data.length, extra_len);
+#endif /* WE_EVENT_DEBUG */
+ }
+
+ /* Total length of the event */
+ hdr_len = event_type_size[descr->header_type];
+ event_len = hdr_len + extra_len;
+
+#ifdef WE_EVENT_DEBUG
+ printk(KERN_DEBUG "%s (WE) : Event 0x%04X, hdr_len %d, event_len %d\n", dev->name, cmd, hdr_len, event_len);
+#endif /* WE_EVENT_DEBUG */
+
+ /* Create temporary buffer to hold the event */
+ event = kmalloc(event_len, GFP_ATOMIC);
+ if(event == NULL)
+ return;
+
+ /* Fill event */
+ event->len = event_len;
+ event->cmd = cmd;
+ memcpy(&event->u, wrqu, hdr_len - IW_EV_LCP_LEN);
+ if(extra != NULL)
+ memcpy(((char *) event) + hdr_len, extra, extra_len);
+
+#ifdef WE_EVENT_NETLINK
+ /* rtnetlink event channel */
+ rtmsg_iwinfo(dev, (char *) event, event_len);
+#endif /* WE_EVENT_NETLINK */
+
+ /* Cleanup */
+ kfree(event);
+
+ return; /* Always success, I guess ;-) */
+}
+
+/********************** ENHANCED IWSPY SUPPORT **********************/
+/*
+ * In the old days, the driver was handling spy support all by itself.
+ * Now, the driver can delegate this task to Wireless Extensions.
+ * It needs to use those standard spy iw_handler in struct iw_handler_def,
+ * push data to us via XXX and include struct iw_spy_data in its
+ * private part.
+ * One of the main advantage of centralising spy support here is that
+ * it becomes much easier to improve and extend it without having to touch
+ * the drivers. One example is the addition of the Spy-Threshold events.
+ * Note : IW_WIRELESS_SPY is defined in iw_handler.h
+ */
+
+/*------------------------------------------------------------------*/
+/*
+ * Standard Wireless Handler : set Spy List
+ */
+int iw_handler_set_spy(struct net_device * dev,
+ struct iw_request_info * info,
+ union iwreq_data * wrqu,
+ char * extra)
+{
+#ifdef IW_WIRELESS_SPY
+ struct iw_spy_data * spydata = (dev->priv +
+ dev->wireless_handlers->spy_offset);
+ struct sockaddr * address = (struct sockaddr *) extra;
+
+ /* Disable spy collection while we copy the addresses.
+ * As we don't disable interrupts, we need to do this to avoid races.
+ * As we are the only writer, this is good enough. */
+ spydata->spy_number = 0;
+
+ /* Are there are addresses to copy? */
+ if(wrqu->data.length > 0) {
+ int i;
+
+ /* Copy addresses */
+ for(i = 0; i < wrqu->data.length; i++)
+ memcpy(spydata->spy_address[i], address[i].sa_data,
+ ETH_ALEN);
+ /* Reset stats */
+ memset(spydata->spy_stat, 0,
+ sizeof(struct iw_quality) * IW_MAX_SPY);
+
+#ifdef WE_SPY_DEBUG
+ printk(KERN_DEBUG "iw_handler_set_spy() : offset %ld, spydata %p, num %d\n", dev->wireless_handlers->spy_offset, spydata, wrqu->data.length);
+ for (i = 0; i < wrqu->data.length; i++)
+ printk(KERN_DEBUG
+ "%02X:%02X:%02X:%02X:%02X:%02X \n",
+ spydata->spy_address[i][0],
+ spydata->spy_address[i][1],
+ spydata->spy_address[i][2],
+ spydata->spy_address[i][3],
+ spydata->spy_address[i][4],
+ spydata->spy_address[i][5]);
+#endif /* WE_SPY_DEBUG */
+ }
+ /* Enable addresses */
+ spydata->spy_number = wrqu->data.length;
+
+ return 0;
+#else /* IW_WIRELESS_SPY */
+ return -EOPNOTSUPP;
+#endif /* IW_WIRELESS_SPY */
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Standard Wireless Handler : get Spy List
+ */
+int iw_handler_get_spy(struct net_device * dev,
+ struct iw_request_info * info,
+ union iwreq_data * wrqu,
+ char * extra)
+{
+#ifdef IW_WIRELESS_SPY
+ struct iw_spy_data * spydata = (dev->priv +
+ dev->wireless_handlers->spy_offset);
+ struct sockaddr * address = (struct sockaddr *) extra;
+ int i;
+
+ wrqu->data.length = spydata->spy_number;
+
+ /* Copy addresses. */
+ for(i = 0; i < spydata->spy_number; i++) {
+ memcpy(address[i].sa_data, spydata->spy_address[i], ETH_ALEN);
+ address[i].sa_family = AF_UNIX;
+ }
+ /* Copy stats to the user buffer (just after). */
+ if(spydata->spy_number > 0)
+ memcpy(extra + (sizeof(struct sockaddr) *spydata->spy_number),
+ spydata->spy_stat,
+ sizeof(struct iw_quality) * spydata->spy_number);
+ /* Reset updated flags. */
+ for(i = 0; i < spydata->spy_number; i++)
+ spydata->spy_stat[i].updated = 0;
+ return 0;
+#else /* IW_WIRELESS_SPY */
+ return -EOPNOTSUPP;
+#endif /* IW_WIRELESS_SPY */
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Standard Wireless Handler : set spy threshold
+ */
+int iw_handler_set_thrspy(struct net_device * dev,
+ struct iw_request_info *info,
+ union iwreq_data * wrqu,
+ char * extra)
+{
+#ifdef IW_WIRELESS_THRSPY
+ struct iw_spy_data * spydata = (dev->priv +
+ dev->wireless_handlers->spy_offset);
+ struct iw_thrspy * threshold = (struct iw_thrspy *) extra;
+
+ /* Just do it */
+ memcpy(&(spydata->spy_thr_low), &(threshold->low),
+ 2 * sizeof(struct iw_quality));
+
+ /* Clear flag */
+ memset(spydata->spy_thr_under, '\0', sizeof(spydata->spy_thr_under));
+
+#ifdef WE_SPY_DEBUG
+ printk(KERN_DEBUG "iw_handler_set_thrspy() : low %d ; high %d\n", spydata->spy_thr_low.level, spydata->spy_thr_high.level);
+#endif /* WE_SPY_DEBUG */
+
+ return 0;
+#else /* IW_WIRELESS_THRSPY */
+ return -EOPNOTSUPP;
+#endif /* IW_WIRELESS_THRSPY */
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Standard Wireless Handler : get spy threshold
+ */
+int iw_handler_get_thrspy(struct net_device * dev,
+ struct iw_request_info *info,
+ union iwreq_data * wrqu,
+ char * extra)
+{
+#ifdef IW_WIRELESS_THRSPY
+ struct iw_spy_data * spydata = (dev->priv +
+ dev->wireless_handlers->spy_offset);
+ struct iw_thrspy * threshold = (struct iw_thrspy *) extra;
+
+ /* Just do it */
+ memcpy(&(threshold->low), &(spydata->spy_thr_low),
+ 2 * sizeof(struct iw_quality));
+
+ return 0;
+#else /* IW_WIRELESS_THRSPY */
+ return -EOPNOTSUPP;
+#endif /* IW_WIRELESS_THRSPY */
+}
+
+#ifdef IW_WIRELESS_THRSPY
+/*------------------------------------------------------------------*/
+/*
+ * Prepare and send a Spy Threshold event
+ */
+static void iw_send_thrspy_event(struct net_device * dev,
+ struct iw_spy_data * spydata,
+ unsigned char * address,
+ struct iw_quality * wstats)
+{
+ union iwreq_data wrqu;
+ struct iw_thrspy threshold;
+
+ /* Init */
+ wrqu.data.length = 1;
+ wrqu.data.flags = 0;
+ /* Copy address */
+ memcpy(threshold.addr.sa_data, address, ETH_ALEN);
+ threshold.addr.sa_family = ARPHRD_ETHER;
+ /* Copy stats */
+ memcpy(&(threshold.qual), wstats, sizeof(struct iw_quality));
+ /* Copy also thresholds */
+ memcpy(&(threshold.low), &(spydata->spy_thr_low),
+ 2 * sizeof(struct iw_quality));
+
+#ifdef WE_SPY_DEBUG
+ printk(KERN_DEBUG "iw_send_thrspy_event() : address %02X:%02X:%02X:%02X:%02X:%02X, level %d, up = %d\n",
+ threshold.addr.sa_data[0],
+ threshold.addr.sa_data[1],
+ threshold.addr.sa_data[2],
+ threshold.addr.sa_data[3],
+ threshold.addr.sa_data[4],
+ threshold.addr.sa_data[5], threshold.qual.level);
+#endif /* WE_SPY_DEBUG */
+
+ /* Send event to user space */
+ wireless_send_event(dev, SIOCGIWTHRSPY, &wrqu, (char *) &threshold);
+}
+#endif /* IW_WIRELESS_THRSPY */
+
+/* ---------------------------------------------------------------- */
+/*
+ * Call for the driver to update the spy data.
+ * For now, the spy data is a simple array. As the size of the array is
+ * small, this is good enough. If we wanted to support larger number of
+ * spy addresses, we should use something more efficient...
+ */
+void wireless_spy_update(struct net_device * dev,
+ unsigned char * address,
+ struct iw_quality * wstats)
+{
+#ifdef IW_WIRELESS_SPY
+ struct iw_spy_data * spydata = (dev->priv +
+ dev->wireless_handlers->spy_offset);
+ int i;
+ int match = -1;
+
+#ifdef WE_SPY_DEBUG
+ printk(KERN_DEBUG "wireless_spy_update() : offset %ld, spydata %p, address %02X:%02X:%02X:%02X:%02X:%02X\n", dev->wireless_handlers->spy_offset, spydata, address[0], address[1], address[2], address[3], address[4], address[5]);
+#endif /* WE_SPY_DEBUG */
+
+ /* Update all records that match */
+ for(i = 0; i < spydata->spy_number; i++)
+ if(!memcmp(address, spydata->spy_address[i], ETH_ALEN)) {
+ memcpy(&(spydata->spy_stat[i]), wstats,
+ sizeof(struct iw_quality));
+ match = i;
+ }
+#ifdef IW_WIRELESS_THRSPY
+ /* Generate an event if we cross the spy threshold.
+ * To avoid event storms, we have a simple hysteresis : we generate
+ * event only when we go under the low threshold or above the
+ * high threshold. */
+ if(match >= 0) {
+ if(spydata->spy_thr_under[match]) {
+ if(wstats->level > spydata->spy_thr_high.level) {
+ spydata->spy_thr_under[match] = 0;
+ iw_send_thrspy_event(dev, spydata,
+ address, wstats);
+ }
+ } else {
+ if(wstats->level < spydata->spy_thr_low.level) {
+ spydata->spy_thr_under[match] = 1;
+ iw_send_thrspy_event(dev, spydata,
+ address, wstats);
+ }
+ }
+ }
+#endif /* IW_WIRELESS_THRSPY */
+#endif /* IW_WIRELESS_SPY */
+}
diff --git a/uClinux-2.4.31-uc0/net/decnet/Config.in b/uClinux-2.4.31-uc0/net/decnet/Config.in
new file mode 100644
index 0000000..94422e1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/Config.in
@@ -0,0 +1,12 @@
+#
+# DECnet configuration
+#
+bool ' DECnet: SIOCGIFCONF support' CONFIG_DECNET_SIOCGIFCONF
+if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ bool ' DECnet: router support (EXPERIMENTAL)' CONFIG_DECNET_ROUTER
+ if [ "$CONFIG_DECNET_ROUTER" = "y" ]; then
+ if [ "$CONFIG_NETFILTER" = "y" ]; then
+ bool ' DECnet: use FWMARK value as routing key (EXPERIMENTAL)' CONFIG_DECNET_ROUTE_FWMARK
+ fi
+ fi
+fi
diff --git a/uClinux-2.4.31-uc0/net/decnet/Makefile b/uClinux-2.4.31-uc0/net/decnet/Makefile
new file mode 100644
index 0000000..11f7c8b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/Makefile
@@ -0,0 +1,13 @@
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := decnet.o
+obj-y := af_decnet.o dn_nsp_in.o dn_nsp_out.o dn_route.o dn_dev.o dn_neigh.o dn_timer.o
+obj-m := $(O_TARGET)
+
+obj-$(CONFIG_DECNET_ROUTER) += dn_fib.o dn_rules.o dn_table.o
+obj-$(CONFIG_DECNET_FW) += dn_fw.o
+
+obj-y += sysctl_net_decnet.o
+
+include $(TOPDIR)/Rules.make
+
diff --git a/uClinux-2.4.31-uc0/net/decnet/README b/uClinux-2.4.31-uc0/net/decnet/README
new file mode 100644
index 0000000..4d0d235
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/README
@@ -0,0 +1,8 @@
+ Linux DECnet Project
+ ======================
+
+The documentation for this kernel subsystem is available in the
+Documentation/networking subdirctory of this distribution and also
+on line at http://www.chygwyn.com/DECnet/
+
+Steve Whitehouse <SteveW@ACM.org>
diff --git a/uClinux-2.4.31-uc0/net/decnet/TODO b/uClinux-2.4.31-uc0/net/decnet/TODO
new file mode 100644
index 0000000..c8ea817
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/TODO
@@ -0,0 +1,57 @@
+Steve's quick list of things that need finishing off:
+[they are in no particular order and range from the trivial to the long winded]
+
+ o Proper timeouts on each neighbour (in routing mode) rather than
+ just the 60 second On-Ethernet cache value.
+
+ o Support for X.25 linklayer
+
+ o Support for DDCMP link layer
+
+ o The DDCMP device itself
+
+ o PPP support (rfc1762)
+
+ o Lots of testing with real applications
+
+ o Verify errors etc. against POSIX 1003.1g (draft)
+
+ o Using send/recvmsg() to get at connect/disconnect data (POSIX 1003.1g)
+ [maybe this should be done at socket level... the control data in the
+ send/recvmsg() calls should simply be a vector of set/getsockopt()
+ calls]
+
+ o check MSG_CTRUNC is set where it should be.
+
+ o Start to hack together user level software and add more DECnet support
+ in ifconfig for example.
+
+ o Test adding/deleting of routes
+
+ o Test route lookup
+
+ o Test /proc/net/decnet_route route listing works correctly (maybe I'll
+ change the format of this file... atm its very similar to the IPv4 route
+ file)
+
+ o Find all the commonality between DECnet and IPv4 routing code and extract
+ it into a small library of routines. [probably a project for 2.5.xx]
+
+ o Test ip_gre tunneling works... it did the last time I tested it and it
+ will have to if I'm to test routing properly.
+
+ o Hello messages should be generated for each primary address on each
+ interface.
+
+ o Add the routing message grabbing netfilter module [written, tested,
+ awaiting merge]
+
+ o Add perfect socket hashing - an idea suggested by Paul Koning [part written,
+ awaiting debugging and merge]
+
+ o Add session control message flow control
+
+ o Add NSP message flow control
+
+ o DECnet sendpages() function
+
diff --git a/uClinux-2.4.31-uc0/net/decnet/af_decnet.c b/uClinux-2.4.31-uc0/net/decnet/af_decnet.c
new file mode 100644
index 0000000..e9d626f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/af_decnet.c
@@ -0,0 +1,2325 @@
+
+/*
+ * DECnet An implementation of the DECnet protocol suite for the LINUX
+ * operating system. DECnet is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * DECnet Socket Layer Interface
+ *
+ * Authors: Eduardo Marcelo Serrat <emserrat@geocities.com>
+ * Patrick Caulfield <patrick@pandh.demon.co.uk>
+ *
+ * Changes:
+ * Steve Whitehouse: Copied from Eduardo Serrat and Patrick Caulfield's
+ * version of the code. Original copyright preserved
+ * below.
+ * Steve Whitehouse: Some bug fixes, cleaning up some code to make it
+ * compatible with my routing layer.
+ * Steve Whitehouse: Merging changes from Eduardo Serrat and Patrick
+ * Caulfield.
+ * Steve Whitehouse: Further bug fixes, checking module code still works
+ * with new routing layer.
+ * Steve Whitehouse: Additional set/get_sockopt() calls.
+ * Steve Whitehouse: Fixed TIOCINQ ioctl to be same as Eduardo's new
+ * code.
+ * Steve Whitehouse: recvmsg() changed to try and behave in a POSIX like
+ * way. Didn't manage it entirely, but its better.
+ * Steve Whitehouse: ditto for sendmsg().
+ * Steve Whitehouse: A selection of bug fixes to various things.
+ * Steve Whitehouse: Added TIOCOUTQ ioctl.
+ * Steve Whitehouse: Fixes to username2sockaddr & sockaddr2username.
+ * Steve Whitehouse: Fixes to connect() error returns.
+ * Patrick Caulfield: Fixes to delayed acceptance logic.
+ * David S. Miller: New socket locking
+ * Steve Whitehouse: Socket list hashing/locking
+ * Arnaldo C. Melo: use capable, not suser
+ * Steve Whitehouse: Removed unused code. Fix to use sk->allocation
+ * when required.
+ * Patrick Caulfield: /proc/net/decnet now has object name/number
+ * Steve Whitehouse: Fixed local port allocation, hashed sk list
+ */
+
+
+/******************************************************************************
+ (c) 1995-1998 E.M. Serrat emserrat@geocities.com
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+HISTORY:
+
+Version Kernel Date Author/Comments
+------- ------ ---- ---------------
+Version 0.0.1 2.0.30 01-dic-97 Eduardo Marcelo Serrat
+ (emserrat@geocities.com)
+
+ First Development of DECnet Socket La-
+ yer for Linux. Only supports outgoing
+ connections.
+
+Version 0.0.2 2.1.105 20-jun-98 Patrick J. Caulfield
+ (patrick@pandh.demon.co.uk)
+
+ Port to new kernel development version.
+
+Version 0.0.3 2.1.106 25-jun-98 Eduardo Marcelo Serrat
+ (emserrat@geocities.com)
+ _
+ Added support for incoming connections
+ so we can start developing server apps
+ on Linux.
+ -
+ Module Support
+Version 0.0.4 2.1.109 21-jul-98 Eduardo Marcelo Serrat
+ (emserrat@geocities.com)
+ _
+ Added support for X11R6.4. Now we can
+ use DECnet transport for X on Linux!!!
+ -
+Version 0.0.5 2.1.110 01-aug-98 Eduardo Marcelo Serrat
+ (emserrat@geocities.com)
+ Removed bugs on flow control
+ Removed bugs on incoming accessdata
+ order
+ -
+Version 0.0.6 2.1.110 07-aug-98 Eduardo Marcelo Serrat
+ dn_recvmsg fixes
+
+ Patrick J. Caulfield
+ dn_bind fixes
+*******************************************************************************/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/inet.h>
+#include <linux/route.h>
+#include <linux/netfilter.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <asm/ioctls.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/dn.h>
+#include <net/dn_nsp.h>
+#include <net/dn_dev.h>
+#include <net/dn_route.h>
+#include <net/dn_fib.h>
+#include <net/dn_neigh.h>
+
+static void dn_keepalive(struct sock *sk);
+
+/*
+ * decnet_address is kept in network order, decnet_ether_address is kept
+ * as a string of bytes.
+ */
+dn_address decnet_address = 0;
+unsigned char decnet_ether_address[ETH_ALEN] = { 0xAA, 0x00, 0x04, 0x00, 0x00, 0x00 };
+
+#define DN_SK_HASH_SHIFT 8
+#define DN_SK_HASH_SIZE (1 << DN_SK_HASH_SHIFT)
+#define DN_SK_HASH_MASK (DN_SK_HASH_SIZE - 1)
+
+static struct proto_ops dn_proto_ops;
+rwlock_t dn_hash_lock = RW_LOCK_UNLOCKED;
+static struct sock *dn_sk_hash[DN_SK_HASH_SIZE];
+static struct sock *dn_wild_sk;
+
+static int __dn_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen, int flags);
+static int __dn_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen, int flags);
+
+static struct sock **dn_find_list(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+
+ if (scp->addr.sdn_flags & SDF_WILD)
+ return dn_wild_sk ? NULL : &dn_wild_sk;
+
+ return &dn_sk_hash[scp->addrloc & DN_SK_HASH_MASK];
+}
+
+/*
+ * Valid ports are those greater than zero and not already in use.
+ */
+static int check_port(unsigned short port)
+{
+ struct sock *sk = dn_sk_hash[port & DN_SK_HASH_MASK];
+ if (port == 0)
+ return -1;
+ while(sk) {
+ struct dn_scp *scp = DN_SK(sk);
+ if (scp->addrloc == port)
+ return -1;
+ sk = sk->next;
+ }
+ return 0;
+}
+
+static unsigned short port_alloc(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+static unsigned short port = 0x2000;
+ unsigned short i_port = port;
+
+ while(check_port(++port) != 0) {
+ if (port == i_port)
+ return 0;
+ }
+
+ scp->addrloc = port;
+
+ return 1;
+}
+
+/*
+ * Since this is only ever called from user
+ * level, we don't need a write_lock() version
+ * of this.
+ */
+static int dn_hash_sock(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ struct sock **skp;
+ int rv = -EUSERS;
+
+ if (sk->next)
+ BUG();
+ if (sk->pprev)
+ BUG();
+
+ write_lock_bh(&dn_hash_lock);
+
+ if (!scp->addrloc && !port_alloc(sk))
+ goto out;
+
+ rv = -EADDRINUSE;
+ if ((skp = dn_find_list(sk)) == NULL)
+ goto out;
+
+ sk->next = *skp;
+ sk->pprev = skp;
+ *skp = sk;
+ rv = 0;
+out:
+ write_unlock_bh(&dn_hash_lock);
+ return rv;
+}
+
+static void dn_unhash_sock(struct sock *sk)
+{
+ struct sock **skp = sk->pprev;
+
+ if (skp == NULL)
+ return;
+
+ write_lock(&dn_hash_lock);
+ while(*skp != sk)
+ skp = &((*skp)->next);
+ *skp = sk->next;
+ write_unlock(&dn_hash_lock);
+
+ sk->next = NULL;
+ sk->pprev = NULL;
+}
+
+static void dn_unhash_sock_bh(struct sock *sk)
+{
+ struct sock **skp = sk->pprev;
+
+ if (skp == NULL)
+ return;
+
+ write_lock_bh(&dn_hash_lock);
+ while(*skp != sk)
+ skp = &((*skp)->next);
+ *skp = sk->next;
+ write_unlock_bh(&dn_hash_lock);
+
+ sk->next = NULL;
+ sk->pprev = NULL;
+}
+
+struct sock **listen_hash(struct sockaddr_dn *addr)
+{
+ int i;
+ unsigned hash = addr->sdn_objnum;
+
+ if (hash == 0) {
+ hash = addr->sdn_objnamel;
+ for(i = 0; i < addr->sdn_objnamel; i++) {
+ hash ^= addr->sdn_objname[i];
+ hash ^= (hash << 3);
+ }
+ }
+
+ return &dn_sk_hash[hash & DN_SK_HASH_MASK];
+}
+
+/*
+ * Called to transform a socket from bound (i.e. with a local address)
+ * into a listening socket (doesn't need a local port number) and rehashes
+ * based upon the object name/number.
+ */
+static void dn_rehash_sock(struct sock *sk)
+{
+ struct sock **skp = sk->pprev;
+ struct dn_scp *scp = DN_SK(sk);
+
+ if (scp->addr.sdn_flags & SDF_WILD)
+ return;
+
+ write_lock_bh(&dn_hash_lock);
+ while(*skp != sk)
+ skp = &((*skp)->next);
+ *skp = sk->next;
+
+ DN_SK(sk)->addrloc = 0;
+ skp = listen_hash(&DN_SK(sk)->addr);
+
+ sk->next = *skp;
+ sk->pprev = skp;
+ *skp = sk;
+ write_unlock_bh(&dn_hash_lock);
+}
+
+int dn_sockaddr2username(struct sockaddr_dn *sdn, unsigned char *buf, unsigned char type)
+{
+ int len = 2;
+
+ *buf++ = type;
+
+ switch(type) {
+ case 0:
+ *buf++ = sdn->sdn_objnum;
+ break;
+ case 1:
+ *buf++ = 0;
+ *buf++ = dn_ntohs(sdn->sdn_objnamel);
+ memcpy(buf, sdn->sdn_objname, dn_ntohs(sdn->sdn_objnamel));
+ len = 3 + dn_ntohs(sdn->sdn_objnamel);
+ break;
+ case 2:
+ memset(buf, 0, 5);
+ buf += 5;
+ *buf++ = dn_ntohs(sdn->sdn_objnamel);
+ memcpy(buf, sdn->sdn_objname, dn_ntohs(sdn->sdn_objnamel));
+ len = 7 + dn_ntohs(sdn->sdn_objnamel);
+ break;
+ }
+
+ return len;
+}
+
+/*
+ * On reception of usernames, we handle types 1 and 0 for destination
+ * addresses only. Types 2 and 4 are used for source addresses, but the
+ * UIC, GIC are ignored and they are both treated the same way. Type 3
+ * is never used as I've no idea what its purpose might be or what its
+ * format is.
+ */
+int dn_username2sockaddr(unsigned char *data, int len, struct sockaddr_dn *sdn, unsigned char *fmt)
+{
+ unsigned char type;
+ int size = len;
+ int namel = 12;
+
+ sdn->sdn_objnum = 0;
+ sdn->sdn_objnamel = dn_htons(0);
+ memset(sdn->sdn_objname, 0, DN_MAXOBJL);
+
+ if (len < 2)
+ return -1;
+
+ len -= 2;
+ *fmt = *data++;
+ type = *data++;
+
+ switch(*fmt) {
+ case 0:
+ sdn->sdn_objnum = type;
+ return 2;
+ case 1:
+ namel = 16;
+ break;
+ case 2:
+ len -= 4;
+ data += 4;
+ break;
+ case 4:
+ len -= 8;
+ data += 8;
+ break;
+ default:
+ return -1;
+ }
+
+ len -= 1;
+
+ if (len < 0)
+ return -1;
+
+ sdn->sdn_objnamel = dn_htons(*data++);
+ len -= dn_ntohs(sdn->sdn_objnamel);
+
+ if ((len < 0) || (dn_ntohs(sdn->sdn_objnamel) > namel))
+ return -1;
+
+ memcpy(sdn->sdn_objname, data, dn_ntohs(sdn->sdn_objnamel));
+
+ return size - len;
+}
+
+struct sock *dn_sklist_find_listener(struct sockaddr_dn *addr)
+{
+ struct sock **skp = listen_hash(addr);
+ struct sock *sk;
+
+ read_lock(&dn_hash_lock);
+ for(sk = *skp; sk != NULL; sk = sk->next) {
+ struct dn_scp *scp = DN_SK(sk);
+ if (sk->state != TCP_LISTEN)
+ continue;
+ if (scp->addr.sdn_objnum) {
+ if (scp->addr.sdn_objnum != addr->sdn_objnum)
+ continue;
+ } else {
+ if (addr->sdn_objnum)
+ continue;
+ if (scp->addr.sdn_objnamel != addr->sdn_objnamel)
+ continue;
+ if (memcmp(scp->addr.sdn_objname, addr->sdn_objname, dn_ntohs(addr->sdn_objnamel)) != 0)
+ continue;
+ }
+ sock_hold(sk);
+ read_unlock(&dn_hash_lock);
+ return sk;
+ }
+
+ if (dn_wild_sk && (dn_wild_sk->state == TCP_LISTEN))
+ sock_hold((sk = dn_wild_sk));
+
+ read_unlock(&dn_hash_lock);
+ return sk;
+}
+
+struct sock *dn_find_by_skb(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ struct sock *sk;
+ struct dn_scp *scp;
+
+ read_lock(&dn_hash_lock);
+ sk = dn_sk_hash[cb->dst_port & DN_SK_HASH_MASK];
+ for (; sk != NULL; sk = sk->next) {
+ scp = DN_SK(sk);
+ if (cb->src != dn_saddr2dn(&scp->peer))
+ continue;
+ if (cb->dst_port != scp->addrloc)
+ continue;
+ if (scp->addrrem && (cb->src_port != scp->addrrem))
+ continue;
+ break;
+ }
+
+ if (sk)
+ sock_hold(sk);
+
+ read_unlock(&dn_hash_lock);
+
+ return sk;
+}
+
+
+
+static void dn_destruct(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+
+ skb_queue_purge(&scp->data_xmit_queue);
+ skb_queue_purge(&scp->other_xmit_queue);
+ skb_queue_purge(&scp->other_receive_queue);
+
+ dst_release(xchg(&sk->dst_cache, NULL));
+
+ MOD_DEC_USE_COUNT;
+}
+
+struct sock *dn_alloc_sock(struct socket *sock, int gfp)
+{
+ struct sock *sk;
+ struct dn_scp *scp;
+
+ if ((sk = sk_alloc(PF_DECnet, gfp, 1)) == NULL)
+ goto no_sock;
+
+ if (sock) {
+ sock->ops = &dn_proto_ops;
+ }
+ sock_init_data(sock,sk);
+ scp = DN_SK(sk);
+
+ sk->backlog_rcv = dn_nsp_backlog_rcv;
+ sk->destruct = dn_destruct;
+ sk->no_check = 1;
+ sk->family = PF_DECnet;
+ sk->protocol = 0;
+ sk->allocation = gfp;
+
+ /* Initialization of DECnet Session Control Port */
+ scp->state = DN_O; /* Open */
+ scp->numdat = 1; /* Next data seg to tx */
+ scp->numoth = 1; /* Next oth data to tx */
+ scp->ackxmt_dat = 0; /* Last data seg ack'ed */
+ scp->ackxmt_oth = 0; /* Last oth data ack'ed */
+ scp->ackrcv_dat = 0; /* Highest data ack recv*/
+ scp->ackrcv_oth = 0; /* Last oth data ack rec*/
+ scp->flowrem_sw = DN_SEND;
+ scp->flowloc_sw = DN_SEND;
+ scp->flowrem_dat = 0;
+ scp->flowrem_oth = 1;
+ scp->flowloc_dat = 0;
+ scp->flowloc_oth = 1;
+ scp->services_rem = 0;
+ scp->services_loc = 1 | NSP_FC_NONE;
+ scp->info_rem = 0;
+ scp->info_loc = 0x03; /* NSP version 4.1 */
+ scp->segsize_rem = 230; /* Default: Updated by remote segsize */
+ scp->segsize_loc = 1450; /* Best guess for ethernet */
+ scp->nonagle = 0;
+ scp->multi_ireq = 1;
+ scp->accept_mode = ACC_IMMED;
+ scp->addr.sdn_family = AF_DECnet;
+ scp->peer.sdn_family = AF_DECnet;
+ scp->accessdata.acc_accl = 5;
+ memcpy(scp->accessdata.acc_acc, "LINUX", 5);
+
+ scp->max_window = NSP_MAX_WINDOW;
+ scp->snd_window = NSP_MIN_WINDOW;
+ scp->nsp_srtt = NSP_INITIAL_SRTT;
+ scp->nsp_rttvar = NSP_INITIAL_RTTVAR;
+ scp->nsp_rxtshift = 0;
+
+ skb_queue_head_init(&scp->data_xmit_queue);
+ skb_queue_head_init(&scp->other_xmit_queue);
+ skb_queue_head_init(&scp->other_receive_queue);
+
+ scp->persist = 0;
+ scp->persist_fxn = NULL;
+ scp->keepalive = 10 * HZ;
+ scp->keepalive_fxn = dn_keepalive;
+
+ init_timer(&scp->delack_timer);
+ scp->delack_pending = 0;
+ scp->delack_fxn = dn_nsp_delayed_ack;
+
+ dn_start_slow_timer(sk);
+
+ MOD_INC_USE_COUNT;
+
+ return sk;
+no_sock:
+ return NULL;
+}
+
+/*
+ * Keepalive timer.
+ * FIXME: Should respond to SO_KEEPALIVE etc.
+ */
+static void dn_keepalive(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+
+ /*
+ * By checking the other_data transmit queue is empty
+ * we are double checking that we are not sending too
+ * many of these keepalive frames.
+ */
+ if (skb_queue_len(&scp->other_xmit_queue) == 0)
+ dn_nsp_send_link(sk, DN_NOCHANGE, 0);
+}
+
+
+/*
+ * Timer for shutdown/destroyed sockets.
+ * When socket is dead & no packets have been sent for a
+ * certain amount of time, they are removed by this
+ * routine. Also takes care of sending out DI & DC
+ * frames at correct times.
+ */
+int dn_destroy_timer(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+
+ scp->persist = dn_nsp_persist(sk);
+
+ switch(scp->state) {
+ case DN_DI:
+ dn_nsp_send_disc(sk, NSP_DISCINIT, 0, GFP_ATOMIC);
+ if (scp->nsp_rxtshift >= decnet_di_count)
+ scp->state = DN_CN;
+ return 0;
+
+ case DN_DR:
+ dn_nsp_send_disc(sk, NSP_DISCINIT, 0, GFP_ATOMIC);
+ if (scp->nsp_rxtshift >= decnet_dr_count)
+ scp->state = DN_DRC;
+ return 0;
+
+ case DN_DN:
+ if (scp->nsp_rxtshift < decnet_dn_count) {
+ /* printk(KERN_DEBUG "dn_destroy_timer: DN\n"); */
+ dn_nsp_send_disc(sk, NSP_DISCCONF, NSP_REASON_DC, GFP_ATOMIC);
+ return 0;
+ }
+ }
+
+ scp->persist = (HZ * decnet_time_wait);
+
+ if (sk->socket)
+ return 0;
+
+ dn_stop_fast_timer(sk); /* unlikely, but possible that this is runninng */
+ if ((jiffies - scp->stamp) >= (HZ * decnet_time_wait)) {
+ dn_unhash_sock(sk);
+ sock_put(sk);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void dn_destroy_sock(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+
+ scp->nsp_rxtshift = 0; /* reset back off */
+
+ if (sk->socket) {
+ if (sk->socket->state != SS_UNCONNECTED)
+ sk->socket->state = SS_DISCONNECTING;
+ }
+
+ sk->state = TCP_CLOSE;
+
+ switch(scp->state) {
+ case DN_DN:
+ dn_nsp_send_disc(sk, NSP_DISCCONF, NSP_REASON_DC, sk->allocation);
+ scp->persist_fxn = dn_destroy_timer;
+ scp->persist = dn_nsp_persist(sk);
+ break;
+ case DN_CR:
+ scp->state = DN_DR;
+ goto disc_reject;
+ case DN_RUN:
+ scp->state = DN_DI;
+ case DN_DI:
+ case DN_DR:
+disc_reject:
+ dn_nsp_send_disc(sk, NSP_DISCINIT, 0, sk->allocation);
+ case DN_NC:
+ case DN_NR:
+ case DN_RJ:
+ case DN_DIC:
+ case DN_CN:
+ case DN_DRC:
+ case DN_CI:
+ case DN_CD:
+ scp->persist_fxn = dn_destroy_timer;
+ scp->persist = dn_nsp_persist(sk);
+ break;
+ default:
+ printk(KERN_DEBUG "DECnet: dn_destroy_sock passed socket in invalid state\n");
+ case DN_O:
+ dn_stop_fast_timer(sk);
+ dn_stop_slow_timer(sk);
+
+ dn_unhash_sock_bh(sk);
+ sock_put(sk);
+
+ break;
+ }
+}
+
+char *dn_addr2asc(dn_address addr, char *buf)
+{
+ unsigned short node, area;
+
+ node = addr & 0x03ff;
+ area = addr >> 10;
+ sprintf(buf, "%hd.%hd", area, node);
+
+ return buf;
+}
+
+
+static char *dn_state2asc(unsigned char state)
+{
+ switch(state) {
+ case DN_O:
+ return "OPEN";
+ case DN_CR:
+ return " CR";
+ case DN_DR:
+ return " DR";
+ case DN_DRC:
+ return " DRC";
+ case DN_CC:
+ return " CC";
+ case DN_CI:
+ return " CI";
+ case DN_NR:
+ return " NR";
+ case DN_NC:
+ return " NC";
+ case DN_CD:
+ return " CD";
+ case DN_RJ:
+ return " RJ";
+ case DN_RUN:
+ return " RUN";
+ case DN_DI:
+ return " DI";
+ case DN_DIC:
+ return " DIC";
+ case DN_DN:
+ return " DN";
+ case DN_CL:
+ return " CL";
+ case DN_CN:
+ return " CN";
+ }
+
+ return "????";
+}
+
+static int dn_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+
+ switch(sock->type) {
+ case SOCK_SEQPACKET:
+ if (protocol != DNPROTO_NSP)
+ return -EPROTONOSUPPORT;
+ break;
+ case SOCK_STREAM:
+ break;
+ default:
+ return -ESOCKTNOSUPPORT;
+ }
+
+
+ if ((sk = dn_alloc_sock(sock, GFP_KERNEL)) == NULL)
+ return -ENOBUFS;
+
+ sk->protocol = protocol;
+
+ return 0;
+}
+
+
+static int
+dn_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk) {
+ sock_orphan(sk);
+ sock_hold(sk);
+ lock_sock(sk);
+ dn_destroy_sock(sk);
+ release_sock(sk);
+ sock_put(sk);
+ }
+
+ return 0;
+}
+
+static int dn_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = DN_SK(sk);
+ struct sockaddr_dn *saddr = (struct sockaddr_dn *)uaddr;
+ struct net_device *dev;
+ int rv;
+
+ if (sk->zapped == 0)
+ return -EINVAL;
+
+ if (addr_len != sizeof(struct sockaddr_dn))
+ return -EINVAL;
+
+ if (saddr->sdn_family != AF_DECnet)
+ return -EINVAL;
+
+ if (dn_ntohs(saddr->sdn_nodeaddrl) && (dn_ntohs(saddr->sdn_nodeaddrl) != 2))
+ return -EINVAL;
+
+ if (saddr->sdn_objnum && !capable(CAP_NET_BIND_SERVICE))
+ return -EPERM;
+
+ if (dn_ntohs(saddr->sdn_objnamel) > DN_MAXOBJL)
+ return -EINVAL;
+
+ if (saddr->sdn_flags & ~SDF_WILD)
+ return -EINVAL;
+
+ if (saddr->sdn_flags & SDF_WILD) {
+ if (!capable(CAP_NET_BIND_SERVICE))
+ return -EPERM;
+ } else {
+ if (dn_ntohs(saddr->sdn_nodeaddrl)) {
+ read_lock(&dev_base_lock);
+ for(dev = dev_base; dev; dev = dev->next) {
+ if (!dev->dn_ptr)
+ continue;
+ if (dn_dev_islocal(dev, dn_saddr2dn(saddr)))
+ break;
+ }
+ read_unlock(&dev_base_lock);
+ if (dev == NULL)
+ return -EADDRNOTAVAIL;
+ }
+ }
+
+
+ memcpy(&scp->addr, saddr, addr_len);
+ sk->zapped = 0;
+
+ if ((rv = dn_hash_sock(sk)) != 0)
+ sk->zapped = 1;
+
+ return rv;
+}
+
+
+static int dn_auto_bind(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = DN_SK(sk);
+
+ sk->zapped = 0;
+
+ scp->addr.sdn_flags = 0;
+ scp->addr.sdn_objnum = 0;
+
+ /*
+ * This stuff is to keep compatibility with Eduardo's
+ * patch. I hope I can dispense with it shortly...
+ */
+ if ((scp->accessdata.acc_accl != 0) &&
+ (scp->accessdata.acc_accl <= 12)) {
+
+ scp->addr.sdn_objnamel = dn_htons(scp->accessdata.acc_accl);
+ memcpy(scp->addr.sdn_objname, scp->accessdata.acc_acc, dn_ntohs(scp->addr.sdn_objnamel));
+
+ scp->accessdata.acc_accl = 0;
+ memset(scp->accessdata.acc_acc, 0, 40);
+ }
+
+ scp->addr.sdn_add.a_len = dn_htons(2);
+ *(dn_address *)scp->addr.sdn_add.a_addr = decnet_address;
+
+ dn_hash_sock(sk);
+
+ return 0;
+}
+
+
+static int dn_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
+{
+ struct sockaddr_dn *addr = (struct sockaddr_dn *)uaddr;
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = DN_SK(sk);
+ int err = -EISCONN;
+
+ lock_sock(sk);
+
+ if (sock->state == SS_CONNECTED)
+ goto out;
+
+ if (sock->state == SS_CONNECTING) {
+ err = 0;
+ if (sk->state == TCP_ESTABLISHED)
+ goto out;
+
+ err = -ECONNREFUSED;
+ if (sk->state == TCP_CLOSE)
+ goto out;
+ }
+
+ err = -EINVAL;
+ if (DN_SK(sk)->state != DN_O)
+ goto out;
+
+ if (addr_len != sizeof(struct sockaddr_dn))
+ goto out;
+
+ if (addr->sdn_family != AF_DECnet)
+ goto out;
+
+ if (addr->sdn_flags & SDF_WILD)
+ goto out;
+
+ err = -EADDRNOTAVAIL;
+ if (sk->zapped && (err = dn_auto_bind(sock)))
+ goto out;
+
+ memcpy(&scp->peer, addr, addr_len);
+
+ err = -EHOSTUNREACH;
+ if (dn_route_output(&sk->dst_cache, dn_saddr2dn(&scp->peer), dn_saddr2dn(&scp->addr), 0) < 0)
+ goto out;
+
+ sk->state = TCP_SYN_SENT;
+ sock->state = SS_CONNECTING;
+ DN_SK(sk)->state = DN_CI;
+
+ dn_nsp_send_conninit(sk, NSP_CI);
+
+ err = -EINPROGRESS;
+ if ((sk->state == TCP_SYN_SENT) && (flags & O_NONBLOCK))
+ goto out;
+
+ while(sk->state == TCP_SYN_SENT) {
+
+ err = -ERESTARTSYS;
+ if (signal_pending(current))
+ goto out;
+
+ if ((err = sock_error(sk)) != 0) {
+ sock->state = SS_UNCONNECTED;
+ goto out;
+ }
+
+ SOCK_SLEEP_PRE(sk);
+
+ if (sk->state == TCP_SYN_SENT)
+ schedule();
+
+ SOCK_SLEEP_POST(sk);
+ }
+
+ if (sk->state != TCP_ESTABLISHED) {
+ sock->state = SS_UNCONNECTED;
+ err = sock_error(sk);
+ goto out;
+ }
+
+ err = 0;
+ sock->state = SS_CONNECTED;
+out:
+ release_sock(sk);
+
+ return err;
+}
+
+static void dn_access_copy(struct sk_buff *skb, struct accessdata_dn *acc)
+{
+ unsigned char *ptr = skb->data;
+
+ acc->acc_userl = *ptr++;
+ memcpy(&acc->acc_user, ptr, acc->acc_userl);
+ ptr += acc->acc_userl;
+
+ acc->acc_passl = *ptr++;
+ memcpy(&acc->acc_pass, ptr, acc->acc_passl);
+ ptr += acc->acc_passl;
+
+ acc->acc_accl = *ptr++;
+ memcpy(&acc->acc_acc, ptr, acc->acc_accl);
+
+ skb_pull(skb, acc->acc_accl + acc->acc_passl + acc->acc_userl + 3);
+
+}
+
+static void dn_user_copy(struct sk_buff *skb, struct optdata_dn *opt)
+{
+ unsigned char *ptr = skb->data;
+
+ opt->opt_optl = *ptr++;
+ opt->opt_status = 0;
+ memcpy(opt->opt_data, ptr, opt->opt_optl);
+ skb_pull(skb, opt->opt_optl + 1);
+
+}
+
+
+/*
+ * This is here for use in the sockopt() call as well as
+ * in accept(). Must be called with a locked socket.
+ */
+static int dn_wait_accept(struct socket *sock, int flags)
+{
+ struct sock *sk = sock->sk;
+
+ while(sk->state == TCP_LISTEN) {
+ if (flags & O_NONBLOCK) {
+ return -EAGAIN;
+ }
+
+ SOCK_SLEEP_PRE(sk)
+
+ if (sk->state == TCP_LISTEN)
+ schedule();
+
+ SOCK_SLEEP_POST(sk)
+
+ if (signal_pending(current))
+ return -ERESTARTSYS; /* But of course you don't! */
+ }
+
+ if ((DN_SK(sk)->state != DN_RUN) && (DN_SK(sk)->state != DN_DRC)) {
+ sock->state = SS_UNCONNECTED;
+ return sock_error(sk);
+ }
+
+ sock->state = SS_CONNECTED;
+
+ return 0;
+}
+
+
+static int dn_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+ struct sock *sk = sock->sk, *newsk;
+ struct sk_buff *skb = NULL;
+ struct dn_skb_cb *cb;
+ unsigned char menuver;
+ int err = 0;
+ unsigned char type;
+
+ lock_sock(sk);
+
+ if (sk->state != TCP_LISTEN) {
+ release_sock(sk);
+ return -EINVAL;
+ }
+
+ if (DN_SK(sk)->state != DN_O) {
+ release_sock(sk);
+ return -EINVAL;
+ }
+
+ do
+ {
+ if ((skb = skb_dequeue(&sk->receive_queue)) == NULL)
+ {
+ if (flags & O_NONBLOCK)
+ {
+ release_sock(sk);
+ return -EAGAIN;
+ }
+
+ SOCK_SLEEP_PRE(sk);
+
+ if (!skb_peek(&sk->receive_queue))
+ schedule();
+
+ SOCK_SLEEP_POST(sk);
+
+ if (signal_pending(current))
+ {
+ release_sock(sk);
+ return -ERESTARTSYS;
+ }
+ }
+ } while (skb == NULL);
+
+ cb = DN_SKB_CB(skb);
+
+ if ((newsk = dn_alloc_sock(newsock, sk->allocation)) == NULL) {
+ release_sock(sk);
+ kfree_skb(skb);
+ return -ENOBUFS;
+ }
+ sk->ack_backlog--;
+ release_sock(sk);
+
+ dst_release(xchg(&newsk->dst_cache, skb->dst));
+ skb->dst = NULL;
+
+ DN_SK(newsk)->state = DN_CR;
+ DN_SK(newsk)->addrrem = cb->src_port;
+ DN_SK(newsk)->services_rem = cb->services;
+ DN_SK(newsk)->info_rem = cb->info;
+ DN_SK(newsk)->segsize_rem = cb->segsize;
+ DN_SK(newsk)->accept_mode = DN_SK(sk)->accept_mode;
+
+ if (DN_SK(newsk)->segsize_rem < 230)
+ DN_SK(newsk)->segsize_rem = 230;
+
+ if ((DN_SK(newsk)->services_rem & NSP_FC_MASK) == NSP_FC_NONE)
+ DN_SK(newsk)->max_window = decnet_no_fc_max_cwnd;
+
+ newsk->state = TCP_LISTEN;
+ newsk->zapped = 0;
+
+ memcpy(&(DN_SK(newsk)->addr), &(DN_SK(sk)->addr), sizeof(struct sockaddr_dn));
+
+ /*
+ * If we are listening on a wild socket, we don't want
+ * the newly created socket on the wrong hash queue.
+ */
+ DN_SK(newsk)->addr.sdn_flags &= ~SDF_WILD;
+
+ skb_pull(skb, dn_username2sockaddr(skb->data, skb->len, &(DN_SK(newsk)->addr), &type));
+ skb_pull(skb, dn_username2sockaddr(skb->data, skb->len, &(DN_SK(newsk)->peer), &type));
+ *(dn_address *)(DN_SK(newsk)->peer.sdn_add.a_addr) = cb->src;
+ *(dn_address *)(DN_SK(newsk)->addr.sdn_add.a_addr) = cb->dst;
+
+ menuver = *skb->data;
+ skb_pull(skb, 1);
+
+ if (menuver & DN_MENUVER_ACC)
+ dn_access_copy(skb, &(DN_SK(newsk)->accessdata));
+
+ if (menuver & DN_MENUVER_USR)
+ dn_user_copy(skb, &(DN_SK(newsk)->conndata_in));
+
+ if (menuver & DN_MENUVER_PRX)
+ DN_SK(newsk)->peer.sdn_flags |= SDF_PROXY;
+
+ if (menuver & DN_MENUVER_UIC)
+ DN_SK(newsk)->peer.sdn_flags |= SDF_UICPROXY;
+
+ kfree_skb(skb);
+
+ memcpy(&(DN_SK(newsk)->conndata_out), &(DN_SK(sk)->conndata_out),
+ sizeof(struct optdata_dn));
+ memcpy(&(DN_SK(newsk)->discdata_out), &(DN_SK(sk)->discdata_out),
+ sizeof(struct optdata_dn));
+
+ lock_sock(newsk);
+ /*
+ * FIXME: This can fail if we've run out of local ports....
+ */
+ dn_hash_sock(newsk);
+
+ dn_send_conn_ack(newsk);
+
+ /*
+ * Here we use sk->allocation since although the conn conf is
+ * for the newsk, the context is the old socket.
+ */
+ if (DN_SK(newsk)->accept_mode == ACC_IMMED) {
+ DN_SK(newsk)->state = DN_CC;
+ dn_send_conn_conf(newsk, sk->allocation);
+ err = dn_wait_accept(newsock, flags);
+ }
+
+ release_sock(newsk);
+ return err;
+}
+
+
+static int dn_getname(struct socket *sock, struct sockaddr *uaddr,int *uaddr_len,int peer)
+{
+ struct sockaddr_dn *sa = (struct sockaddr_dn *)uaddr;
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = DN_SK(sk);
+
+ *uaddr_len = sizeof(struct sockaddr_dn);
+
+ lock_sock(sk);
+
+ if (peer) {
+ if ((sock->state != SS_CONNECTED &&
+ sock->state != SS_CONNECTING) &&
+ scp->accept_mode == ACC_IMMED)
+ return -ENOTCONN;
+
+ memcpy(sa, &scp->peer, sizeof(struct sockaddr_dn));
+ } else {
+ memcpy(sa, &scp->addr, sizeof(struct sockaddr_dn));
+ }
+
+ release_sock(sk);
+
+ return 0;
+}
+
+
+static unsigned int dn_poll(struct file *file, struct socket *sock, poll_table *wait)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = DN_SK(sk);
+ int mask = datagram_poll(file, sock, wait);
+
+ if (skb_queue_len(&scp->other_receive_queue))
+ mask |= POLLRDBAND;
+
+ return mask;
+}
+
+static int dn_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = DN_SK(sk);
+ int err = -EOPNOTSUPP;
+ long amount = 0;
+ struct sk_buff *skb;
+ int val;
+
+ switch(cmd)
+ {
+ case SIOCGIFADDR:
+ case SIOCSIFADDR:
+ return dn_dev_ioctl(cmd, (void *)arg);
+
+ case SIOCATMARK:
+ lock_sock(sk);
+ val = (skb_queue_len(&scp->other_receive_queue) != 0);
+ if (scp->state != DN_RUN)
+ val = -ENOTCONN;
+ release_sock(sk);
+ return val;
+
+#ifdef CONFIG_DECNET_ROUTER
+ case SIOCADDRT:
+ case SIOCDELRT:
+ return dn_fib_ioctl(sock, cmd, arg);
+#endif /* CONFIG_DECNET_ROUTER */
+
+ case OSIOCSNETADDR:
+ if (!capable(CAP_NET_ADMIN)) {
+ err = -EPERM;
+ break;
+ }
+
+ dn_dev_devices_off();
+
+ decnet_address = (unsigned short)arg;
+ dn_dn2eth(decnet_ether_address, dn_ntohs(decnet_address));
+
+ dn_dev_devices_on();
+ err = 0;
+ break;
+
+ case OSIOCGNETADDR:
+ err = put_user(decnet_address, (unsigned short *)arg);
+ break;
+ case SIOCGIFCONF:
+ case SIOCGIFFLAGS:
+ case SIOCGIFBRDADDR:
+ return dev_ioctl(cmd,(void *)arg);
+
+ case TIOCOUTQ:
+ amount = sk->sndbuf - atomic_read(&sk->wmem_alloc);
+ if (amount < 0)
+ amount = 0;
+ err = put_user(amount, (int *)arg);
+ break;
+
+ case TIOCINQ:
+ lock_sock(sk);
+ if ((skb = skb_peek(&scp->other_receive_queue)) != NULL) {
+ amount = skb->len;
+ } else {
+ struct sk_buff *skb = sk->receive_queue.next;
+ for(;;) {
+ if (skb == (struct sk_buff *)&sk->receive_queue)
+ break;
+ amount += skb->len;
+ skb = skb->next;
+ }
+ }
+ release_sock(sk);
+ err = put_user(amount, (int *)arg);
+ break;
+ }
+
+ return err;
+}
+
+static int dn_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+ int err = -EINVAL;
+
+ lock_sock(sk);
+
+ if (sk->zapped)
+ goto out;
+
+ if ((DN_SK(sk)->state != DN_O) || (sk->state == TCP_LISTEN))
+ goto out;
+
+ sk->max_ack_backlog = backlog;
+ sk->ack_backlog = 0;
+ sk->state = TCP_LISTEN;
+ err = 0;
+ dn_rehash_sock(sk);
+
+out:
+ release_sock(sk);
+
+ return err;
+}
+
+
+static int dn_shutdown(struct socket *sock, int how)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = DN_SK(sk);
+ int err = -ENOTCONN;
+
+ lock_sock(sk);
+
+ if (sock->state == SS_UNCONNECTED)
+ goto out;
+
+ err = 0;
+ if (sock->state == SS_DISCONNECTING)
+ goto out;
+
+ err = -EINVAL;
+ if (scp->state == DN_O)
+ goto out;
+
+ if (how != SHUTDOWN_MASK)
+ goto out;
+
+
+ sk->shutdown = how;
+ dn_destroy_sock(sk);
+ err = 0;
+
+out:
+ release_sock(sk);
+
+ return err;
+}
+
+static int dn_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen)
+{
+ struct sock *sk = sock->sk;
+ int err;
+
+ lock_sock(sk);
+ err = __dn_setsockopt(sock, level, optname, optval, optlen, 0);
+ release_sock(sk);
+
+ return err;
+}
+
+static int __dn_setsockopt(struct socket *sock, int level,int optname, char *optval, int optlen, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = DN_SK(sk);
+ union {
+ struct optdata_dn opt;
+ struct accessdata_dn acc;
+ int mode;
+ unsigned long win;
+ int val;
+ unsigned char services;
+ unsigned char info;
+ } u;
+ int err;
+
+ if (optlen && !optval)
+ return -EINVAL;
+
+ if (optlen > sizeof(u))
+ return -EINVAL;
+
+ if (copy_from_user(&u, optval, optlen))
+ return -EFAULT;
+
+ switch(optname) {
+ case DSO_CONDATA:
+ if (sock->state == SS_CONNECTED)
+ return -EISCONN;
+ if ((scp->state != DN_O) && (scp->state != DN_CR))
+ return -EINVAL;
+
+ if (optlen != sizeof(struct optdata_dn))
+ return -EINVAL;
+
+ if (u.opt.opt_optl > 16)
+ return -EINVAL;
+
+ memcpy(&scp->conndata_out, &u.opt, optlen);
+ break;
+
+ case DSO_DISDATA:
+ if (sock->state != SS_CONNECTED && scp->accept_mode == ACC_IMMED)
+ return -ENOTCONN;
+
+ if (optlen != sizeof(struct optdata_dn))
+ return -EINVAL;
+
+ if (u.opt.opt_optl > 16)
+ return -EINVAL;
+
+ memcpy(&scp->discdata_out, &u.opt, optlen);
+ break;
+
+ case DSO_CONACCESS:
+ if (sock->state == SS_CONNECTED)
+ return -EISCONN;
+ if (scp->state != DN_O)
+ return -EINVAL;
+
+ if (optlen != sizeof(struct accessdata_dn))
+ return -EINVAL;
+
+ if ((u.acc.acc_accl > DN_MAXACCL) ||
+ (u.acc.acc_passl > DN_MAXACCL) ||
+ (u.acc.acc_userl > DN_MAXACCL))
+ return -EINVAL;
+
+ memcpy(&scp->accessdata, &u.acc, optlen);
+ break;
+
+ case DSO_ACCEPTMODE:
+ if (sock->state == SS_CONNECTED)
+ return -EISCONN;
+ if (scp->state != DN_O)
+ return -EINVAL;
+
+ if (optlen != sizeof(int))
+ return -EINVAL;
+
+ if ((u.mode != ACC_IMMED) && (u.mode != ACC_DEFER))
+ return -EINVAL;
+
+ scp->accept_mode = (unsigned char)u.mode;
+ break;
+
+ case DSO_CONACCEPT:
+
+ if (scp->state != DN_CR)
+ return -EINVAL;
+
+ scp->state = DN_CC;
+ dn_send_conn_conf(sk, sk->allocation);
+ err = dn_wait_accept(sock, sock->file->f_flags);
+ return err;
+
+ case DSO_CONREJECT:
+
+ if (scp->state != DN_CR)
+ return -EINVAL;
+
+ scp->state = DN_DR;
+ sk->shutdown = SHUTDOWN_MASK;
+ dn_nsp_send_disc(sk, 0x38, 0, sk->allocation);
+ break;
+
+ default:
+#ifdef CONFIG_NETFILTER
+ return nf_setsockopt(sk, PF_DECnet, optname, optval, optlen);
+#endif
+ case DSO_LINKINFO:
+ case DSO_STREAM:
+ case DSO_SEQPACKET:
+ return -ENOPROTOOPT;
+
+ case DSO_MAXWINDOW:
+ if (optlen != sizeof(unsigned long))
+ return -EINVAL;
+ if (u.win > NSP_MAX_WINDOW)
+ u.win = NSP_MAX_WINDOW;
+ if (u.win == 0)
+ return -EINVAL;
+ scp->max_window = u.win;
+ if (scp->snd_window > u.win)
+ scp->snd_window = u.win;
+ break;
+
+ case DSO_NODELAY:
+ if (optlen != sizeof(int))
+ return -EINVAL;
+ if (scp->nonagle == 2)
+ return -EINVAL;
+ scp->nonagle = (u.val == 0) ? 0 : 1;
+ /* if (scp->nonagle == 1) { Push pending frames } */
+ break;
+
+ case DSO_CORK:
+ if (optlen != sizeof(int))
+ return -EINVAL;
+ if (scp->nonagle == 1)
+ return -EINVAL;
+ scp->nonagle = (u.val == 0) ? 0 : 2;
+ /* if (scp->nonagle == 0) { Push pending frames } */
+ break;
+
+ case DSO_SERVICES:
+ if (optlen != sizeof(unsigned char))
+ return -EINVAL;
+ if ((u.services & ~NSP_FC_MASK) != 0x01)
+ return -EINVAL;
+ if ((u.services & NSP_FC_MASK) == NSP_FC_MASK)
+ return -EINVAL;
+ scp->services_loc = u.services;
+ break;
+
+ case DSO_INFO:
+ if (optlen != sizeof(unsigned char))
+ return -EINVAL;
+ if (u.info & 0xfc)
+ return -EINVAL;
+ scp->info_loc = u.info;
+ break;
+ }
+
+ return 0;
+}
+
+static int dn_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen)
+{
+ struct sock *sk = sock->sk;
+ int err;
+
+ lock_sock(sk);
+ err = __dn_getsockopt(sock, level, optname, optval, optlen, 0);
+ release_sock(sk);
+
+ return err;
+}
+
+static int __dn_getsockopt(struct socket *sock, int level,int optname, char *optval,int *optlen, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = DN_SK(sk);
+ struct linkinfo_dn link;
+ unsigned int r_len;
+ void *r_data = NULL;
+ unsigned int val;
+
+ if(get_user(r_len , optlen))
+ return -EFAULT;
+
+ switch(optname) {
+ case DSO_CONDATA:
+ if (r_len > sizeof(struct optdata_dn))
+ r_len = sizeof(struct optdata_dn);
+ r_data = &scp->conndata_in;
+ break;
+
+ case DSO_DISDATA:
+ if (r_len > sizeof(struct optdata_dn))
+ r_len = sizeof(struct optdata_dn);
+ r_data = &scp->discdata_in;
+ break;
+
+ case DSO_CONACCESS:
+ if (r_len > sizeof(struct accessdata_dn))
+ r_len = sizeof(struct accessdata_dn);
+ r_data = &scp->accessdata;
+ break;
+
+ case DSO_ACCEPTMODE:
+ if (r_len > sizeof(unsigned char))
+ r_len = sizeof(unsigned char);
+ r_data = &scp->accept_mode;
+ break;
+
+ case DSO_LINKINFO:
+ if (r_len > sizeof(struct linkinfo_dn))
+ r_len = sizeof(struct linkinfo_dn);
+
+ switch(sock->state) {
+ case SS_CONNECTING:
+ link.idn_linkstate = LL_CONNECTING;
+ break;
+ case SS_DISCONNECTING:
+ link.idn_linkstate = LL_DISCONNECTING;
+ break;
+ case SS_CONNECTED:
+ link.idn_linkstate = LL_RUNNING;
+ break;
+ default:
+ link.idn_linkstate = LL_INACTIVE;
+ }
+
+ link.idn_segsize = scp->segsize_rem;
+ r_data = &link;
+ break;
+
+ default:
+#ifdef CONFIG_NETFILTER
+ {
+ int val, len;
+
+ if(get_user(len, optlen))
+ return -EFAULT;
+
+ val = nf_getsockopt(sk, PF_DECnet, optname,
+ optval, &len);
+ if (val >= 0)
+ val = put_user(len, optlen);
+ return val;
+ }
+#endif
+ case DSO_STREAM:
+ case DSO_SEQPACKET:
+ case DSO_CONACCEPT:
+ case DSO_CONREJECT:
+ return -ENOPROTOOPT;
+
+ case DSO_MAXWINDOW:
+ if (r_len > sizeof(unsigned long))
+ r_len = sizeof(unsigned long);
+ r_data = &scp->max_window;
+ break;
+
+ case DSO_NODELAY:
+ if (r_len > sizeof(int))
+ r_len = sizeof(int);
+ val = (scp->nonagle == 1);
+ r_data = &val;
+ break;
+
+ case DSO_CORK:
+ if (r_len > sizeof(int))
+ r_len = sizeof(int);
+ val = (scp->nonagle == 2);
+ r_data = &val;
+ break;
+
+ case DSO_SERVICES:
+ if (r_len > sizeof(unsigned char))
+ r_len = sizeof(unsigned char);
+ r_data = &scp->services_rem;
+ break;
+
+ case DSO_INFO:
+ if (r_len > sizeof(unsigned char))
+ r_len = sizeof(unsigned char);
+ r_data = &scp->info_rem;
+ break;
+ }
+
+ if (r_data) {
+ if (copy_to_user(optval, r_data, r_len))
+ return -EFAULT;
+ if (put_user(r_len, optlen))
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Used by send/recvmsg to wait until the socket is connected
+ * before passing data.
+ */
+static int dn_wait_run(struct sock *sk, int flags)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ int err = 0;
+
+ switch(scp->state) {
+ case DN_RUN:
+ return 0;
+
+ case DN_CR:
+ scp->state = DN_CC;
+ dn_send_conn_conf(sk, sk->allocation);
+ return dn_wait_accept(sk->socket, (flags & MSG_DONTWAIT) ? O_NONBLOCK : 0);
+ case DN_CI:
+ case DN_CC:
+ break;
+ default:
+ return -ENOTCONN;
+ }
+
+ if (flags & MSG_DONTWAIT)
+ return -EWOULDBLOCK;
+
+ do {
+ if ((err = sock_error(sk)) != 0)
+ break;
+
+ if (signal_pending(current)) {
+ err = -ERESTARTSYS;
+ break;
+ }
+
+ SOCK_SLEEP_PRE(sk)
+
+ if (scp->state != DN_RUN)
+ schedule();
+
+ SOCK_SLEEP_POST(sk)
+
+ } while(scp->state != DN_RUN);
+
+ return 0;
+}
+
+
+static int dn_data_ready(struct sock *sk, struct sk_buff_head *q, int flags, int target)
+{
+ struct sk_buff *skb = q->next;
+ int len = 0;
+
+ if (flags & MSG_OOB)
+ return skb_queue_len(q) ? 1 : 0;
+
+ while(skb != (struct sk_buff *)q) {
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ len += skb->len;
+
+ if (cb->nsp_flags & 0x40) {
+ /* SOCK_SEQPACKET reads to EOM */
+ if (sk->type == SOCK_SEQPACKET)
+ return 1;
+ /* so does SOCK_STREAM unless WAITALL is specified */
+ if (!(flags & MSG_WAITALL))
+ return 1;
+ }
+
+ /* minimum data length for read exceeded */
+ if (len >= target)
+ return 1;
+
+ skb = skb->next;
+ }
+
+ return 0;
+}
+
+
+static int dn_recvmsg(struct socket *sock, struct msghdr *msg, int size,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = DN_SK(sk);
+ struct sk_buff_head *queue = &sk->receive_queue;
+ int target = size > 1 ? 1 : 0;
+ int copied = 0;
+ int rv = 0;
+ struct sk_buff *skb, *nskb;
+ struct dn_skb_cb *cb = NULL;
+ unsigned char eor = 0;
+
+ lock_sock(sk);
+
+ if (sk->zapped) {
+ rv = -EADDRNOTAVAIL;
+ goto out;
+ }
+
+ if ((rv = dn_wait_run(sk, flags)) != 0)
+ goto out;
+
+ if (sk->shutdown & RCV_SHUTDOWN) {
+ send_sig(SIGPIPE, current, 0);
+ rv = -EPIPE;
+ goto out;
+ }
+
+ if (flags & ~(MSG_PEEK|MSG_OOB|MSG_WAITALL|MSG_DONTWAIT)) {
+ rv = -EOPNOTSUPP;
+ goto out;
+ }
+
+ if (flags & MSG_OOB)
+ queue = &scp->other_receive_queue;
+
+ if (flags & MSG_WAITALL)
+ target = size;
+
+
+ /*
+ * See if there is data ready to read, sleep if there isn't
+ */
+ for(;;) {
+ if (sk->err)
+ goto out;
+
+ if (skb_queue_len(&scp->other_receive_queue)) {
+ if (!(flags & MSG_OOB)) {
+ msg->msg_flags |= MSG_OOB;
+ if (!scp->other_report) {
+ scp->other_report = 1;
+ goto out;
+ }
+ }
+ }
+
+ if (scp->state != DN_RUN)
+ goto out;
+
+ if (signal_pending(current)) {
+ rv = -ERESTARTSYS;
+ goto out;
+ }
+
+ if (dn_data_ready(sk, queue, flags, target))
+ break;
+
+ if (flags & MSG_DONTWAIT) {
+ rv = -EWOULDBLOCK;
+ goto out;
+ }
+
+ set_bit(SOCK_ASYNC_WAITDATA, &sock->flags);
+ SOCK_SLEEP_PRE(sk)
+
+ if (!dn_data_ready(sk, queue, flags, target))
+ schedule();
+
+ SOCK_SLEEP_POST(sk)
+ clear_bit(SOCK_ASYNC_WAITDATA, &sock->flags);
+ }
+
+ for(skb = queue->next; skb != (struct sk_buff *)queue; skb = nskb) {
+ int chunk = skb->len;
+ cb = DN_SKB_CB(skb);
+
+ if ((chunk + copied) > size)
+ chunk = size - copied;
+
+ if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) {
+ rv = -EFAULT;
+ break;
+ }
+ copied += chunk;
+
+ if (!(flags & MSG_PEEK))
+ skb_pull(skb, chunk);
+
+ eor = cb->nsp_flags & 0x40;
+ nskb = skb->next;
+
+ if (skb->len == 0) {
+ skb_unlink(skb);
+ kfree_skb(skb);
+ /*
+ * N.B. Don't refer to skb or cb after this point
+ * in loop.
+ */
+ if ((scp->flowloc_sw == DN_DONTSEND) && !dn_congested(sk)) {
+ scp->flowloc_sw = DN_SEND;
+ dn_nsp_send_link(sk, DN_SEND, 0);
+ }
+ }
+
+ if (eor) {
+ if (sk->type == SOCK_SEQPACKET)
+ break;
+ if (!(flags & MSG_WAITALL))
+ break;
+ }
+
+ if (flags & MSG_OOB)
+ break;
+
+ if (copied >= target)
+ break;
+ }
+
+ rv = copied;
+
+
+ if (eor && (sk->type == SOCK_SEQPACKET))
+ msg->msg_flags |= MSG_EOR;
+
+out:
+ if (rv == 0)
+ rv = (flags & MSG_PEEK) ? -sk->err : sock_error(sk);
+
+ if ((rv >= 0) && msg->msg_name) {
+ memcpy(msg->msg_name, &scp->peer, sizeof(struct sockaddr_dn));
+ msg->msg_namelen = sizeof(struct sockaddr_dn);
+ }
+
+ release_sock(sk);
+
+ return rv;
+}
+
+
+static inline int dn_queue_too_long(struct dn_scp *scp, struct sk_buff_head *queue, int flags)
+{
+ unsigned char fctype = scp->services_rem & NSP_FC_MASK;
+ if (skb_queue_len(queue) >= scp->snd_window)
+ return 1;
+ if (fctype != NSP_FC_NONE) {
+ if (flags & MSG_OOB) {
+ if (scp->flowrem_oth == 0)
+ return 1;
+ } else {
+ if (scp->flowrem_dat == 0)
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int dn_sendmsg(struct socket *sock, struct msghdr *msg, int size,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct dn_scp *scp = DN_SK(sk);
+ int mss;
+ struct sk_buff_head *queue = &scp->data_xmit_queue;
+ int flags = msg->msg_flags;
+ int err = 0;
+ int sent = 0;
+ int addr_len = msg->msg_namelen;
+ struct sockaddr_dn *addr = (struct sockaddr_dn *)msg->msg_name;
+ struct sk_buff *skb = NULL;
+ struct dn_skb_cb *cb;
+ unsigned char msgflg;
+ unsigned char *ptr;
+ unsigned short ack;
+ int len;
+ unsigned char fctype;
+
+ if (flags & ~(MSG_TRYHARD|MSG_OOB|MSG_DONTWAIT|MSG_EOR))
+ return -EOPNOTSUPP;
+
+ if (addr_len && (addr_len != sizeof(struct sockaddr_dn)))
+ return -EINVAL;
+
+ if (sk->zapped && dn_auto_bind(sock)) {
+ err = -EADDRNOTAVAIL;
+ goto out;
+ }
+
+ if (scp->state == DN_O) {
+ if (!addr_len || !addr) {
+ err = -ENOTCONN;
+ goto out;
+ }
+
+ if ((err = dn_connect(sock, (struct sockaddr *)addr, addr_len, (flags & MSG_DONTWAIT) ? O_NONBLOCK : 0)) < 0)
+ goto out;
+ }
+
+ lock_sock(sk);
+
+ if ((err = dn_wait_run(sk, flags)) < 0)
+ goto out;
+
+ if (sk->shutdown & SEND_SHUTDOWN) {
+ send_sig(SIGPIPE, current, 0);
+ err = -EPIPE;
+ goto out;
+ }
+
+ if ((flags & MSG_TRYHARD) && sk->dst_cache)
+ dst_negative_advice(&sk->dst_cache);
+
+ mss = scp->segsize_rem;
+ fctype = scp->services_rem & NSP_FC_MASK;
+
+ if (sk->dst_cache && sk->dst_cache->neighbour) {
+ struct dn_neigh *dn = (struct dn_neigh *)sk->dst_cache->neighbour;
+ if (dn->blksize < (mss + 11))
+ mss = dn->blksize - 11;
+ }
+
+ /*
+ * The only difference between SEQPACKET & STREAM sockets under DECnet
+ * is that SEQPACKET sockets set the MSG_EOR flag for the last
+ * session control message segment.
+ */
+
+ if (flags & MSG_OOB) {
+ mss = 16;
+ queue = &scp->other_xmit_queue;
+ if (size > mss) {
+ err = -EMSGSIZE;
+ goto out;
+ }
+ }
+
+ scp->persist_fxn = dn_nsp_xmit_timeout;
+
+ while(sent < size) {
+ err = sock_error(sk);
+ if (err)
+ goto out;
+
+ if (signal_pending(current)) {
+ err = -ERESTARTSYS;
+ goto out;
+ }
+
+ /*
+ * Calculate size that we wish to send.
+ */
+ len = size - sent;
+
+ if (len > mss)
+ len = mss;
+
+ /*
+ * Wait for queue size to go down below the window
+ * size.
+ */
+ if (dn_queue_too_long(scp, queue, flags)) {
+ if (flags & MSG_DONTWAIT) {
+ err = -EWOULDBLOCK;
+ goto out;
+ }
+
+ SOCK_SLEEP_PRE(sk)
+
+ if (dn_queue_too_long(scp, queue, flags))
+ schedule();
+
+ SOCK_SLEEP_POST(sk)
+
+ continue;
+ }
+
+ /*
+ * Get a suitably sized skb.
+ */
+ skb = dn_alloc_send_skb(sk, &len, flags & MSG_DONTWAIT, &err);
+
+ if (err)
+ break;
+
+ if (!skb)
+ continue;
+
+ cb = DN_SKB_CB(skb);
+
+ ptr = skb_put(skb, 9);
+
+ if (memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len)) {
+ err = -EFAULT;
+ goto out;
+ }
+
+ if (flags & MSG_OOB) {
+ cb->segnum = scp->numoth;
+ seq_add(&scp->numoth, 1);
+ msgflg = 0x30;
+ ack = (scp->numoth_rcv & 0x0FFF) | 0x8000;
+ scp->ackxmt_oth = scp->numoth_rcv;
+ if (fctype != NSP_FC_NONE)
+ scp->flowrem_oth--;
+ } else {
+ cb->segnum = scp->numdat;
+ seq_add(&scp->numdat, 1);
+ msgflg = 0x00;
+ if (sock->type == SOCK_STREAM)
+ msgflg = 0x60;
+ if (scp->seg_total == 0)
+ msgflg |= 0x20;
+
+ scp->seg_total += len;
+
+ if (((sent + len) == size) && (flags & MSG_EOR)) {
+ msgflg |= 0x40;
+ scp->seg_total = 0;
+ if (fctype == NSP_FC_SCMC)
+ scp->flowrem_dat--;
+ }
+ ack = (scp->numdat_rcv & 0x0FFF) | 0x8000;
+ scp->ackxmt_dat = scp->numdat_rcv;
+ if (fctype == NSP_FC_SRC)
+ scp->flowrem_dat--;
+ }
+
+ *ptr++ = msgflg;
+ *(__u16 *)ptr = scp->addrrem;
+ ptr += 2;
+ *(__u16 *)ptr = scp->addrloc;
+ ptr += 2;
+ *(__u16 *)ptr = dn_htons(ack);
+ ptr += 2;
+ *(__u16 *)ptr = dn_htons(cb->segnum);
+
+ sent += len;
+ dn_nsp_queue_xmit(sk, skb, sk->allocation, flags & MSG_OOB);
+ skb = NULL;
+
+ scp->persist = dn_nsp_persist(sk);
+
+ }
+out:
+
+ if (skb)
+ kfree_skb(skb);
+
+ release_sock(sk);
+
+ return sent ? sent : err;
+}
+
+static int dn_device_event(struct notifier_block *this, unsigned long event,
+ void *ptr)
+{
+ struct net_device *dev = (struct net_device *)ptr;
+
+ switch(event) {
+ case NETDEV_UP:
+ dn_dev_up(dev);
+ break;
+ case NETDEV_DOWN:
+ dn_dev_down(dev);
+ break;
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block dn_dev_notifier = {
+ notifier_call: dn_device_event,
+};
+
+extern int dn_route_rcv(struct sk_buff *, struct net_device *, struct packet_type *);
+
+static struct packet_type dn_dix_packet_type = {
+ type: __constant_htons(ETH_P_DNA_RT),
+ dev: NULL, /* All devices */
+ func: dn_route_rcv,
+ data: (void*)1,
+};
+
+#define IS_NOT_PRINTABLE(x) ((x) < 32 || (x) > 126)
+
+static void dn_printable_object(struct sockaddr_dn *dn, unsigned char *buf)
+{
+ int i;
+
+ switch (dn_ntohs(dn->sdn_objnamel)) {
+ case 0:
+ sprintf(buf, "%d", dn->sdn_objnum);
+ break;
+ default:
+ for (i = 0; i < dn_ntohs(dn->sdn_objnamel); i++) {
+ buf[i] = dn->sdn_objname[i];
+ if (IS_NOT_PRINTABLE(buf[i]))
+ buf[i] = '.';
+ }
+ buf[i] = 0;
+ }
+}
+
+static int dn_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct sock *sk;
+ struct dn_scp *scp;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+ char buf1[DN_ASCBUF_LEN];
+ char buf2[DN_ASCBUF_LEN];
+ char local_object[DN_MAXOBJL+3];
+ char remote_object[DN_MAXOBJL+3];
+ int i;
+
+ len += sprintf(buffer + len, "Local Remote\n");
+
+ read_lock(&dn_hash_lock);
+ for(i = 0; i < DN_SK_HASH_SIZE; i++) {
+ for(sk = dn_sk_hash[i]; sk != NULL; sk = sk->next) {
+ scp = DN_SK(sk);
+
+ dn_printable_object(&scp->addr, local_object);
+ dn_printable_object(&scp->peer, remote_object);
+
+ len += sprintf(buffer + len,
+ "%6s/%04X %04d:%04d %04d:%04d %01d %-16s %6s/%04X %04d:%04d %04d:%04d %01d %-16s %4s %s\n",
+ dn_addr2asc(dn_ntohs(dn_saddr2dn(&scp->addr)), buf1),
+ scp->addrloc,
+ scp->numdat,
+ scp->numoth,
+ scp->ackxmt_dat,
+ scp->ackxmt_oth,
+ scp->flowloc_sw,
+ local_object,
+ dn_addr2asc(dn_ntohs(dn_saddr2dn(&scp->peer)), buf2),
+ scp->addrrem,
+ scp->numdat_rcv,
+ scp->numoth_rcv,
+ scp->ackrcv_dat,
+ scp->ackrcv_oth,
+ scp->flowrem_sw,
+ remote_object,
+ dn_state2asc(scp->state),
+ ((scp->accept_mode == ACC_IMMED) ? "IMMED" : "DEFER"));
+
+ pos = begin + len;
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (pos > (offset + length))
+ break;
+ }
+ }
+ read_unlock(&dn_hash_lock);
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length)
+ len = length;
+
+ return len;
+}
+
+
+static struct net_proto_family dn_family_ops = {
+ family: AF_DECnet,
+ create: dn_create,
+};
+
+static struct proto_ops dn_proto_ops = {
+ family: AF_DECnet,
+
+ release: dn_release,
+ bind: dn_bind,
+ connect: dn_connect,
+ socketpair: sock_no_socketpair,
+ accept: dn_accept,
+ getname: dn_getname,
+ poll: dn_poll,
+ ioctl: dn_ioctl,
+ listen: dn_listen,
+ shutdown: dn_shutdown,
+ setsockopt: dn_setsockopt,
+ getsockopt: dn_getsockopt,
+ sendmsg: dn_sendmsg,
+ recvmsg: dn_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+#ifdef CONFIG_SYSCTL
+void dn_register_sysctl(void);
+void dn_unregister_sysctl(void);
+#endif
+
+
+#ifdef MODULE
+EXPORT_NO_SYMBOLS;
+MODULE_DESCRIPTION("The Linux DECnet Network Protocol");
+MODULE_AUTHOR("Linux DECnet Project Team");
+MODULE_LICENSE("GPL");
+
+static int addr[2] = {0, 0};
+
+MODULE_PARM(addr, "2i");
+MODULE_PARM_DESC(addr, "The DECnet address of this machine: area,node");
+#endif
+
+static char banner[] __initdata = KERN_INFO "NET4: DECnet for Linux: V.2.4.20-pre1s (C) 1995-2002 Linux DECnet Project Team\n";
+
+static int __init decnet_init(void)
+{
+#ifdef MODULE
+ if (addr[0] > 63 || addr[0] < 0) {
+ printk(KERN_ERR "DECnet: Area must be between 0 and 63");
+ return 1;
+ }
+
+ if (addr[1] > 1023 || addr[1] < 0) {
+ printk(KERN_ERR "DECnet: Node must be between 0 and 1023");
+ return 1;
+ }
+
+ decnet_address = dn_htons((addr[0] << 10) | addr[1]);
+ dn_dn2eth(decnet_ether_address, dn_ntohs(decnet_address));
+#endif
+
+ printk(banner);
+
+ sock_register(&dn_family_ops);
+ dev_add_pack(&dn_dix_packet_type);
+ register_netdevice_notifier(&dn_dev_notifier);
+
+ proc_net_create("decnet", 0, dn_get_info);
+
+ dn_neigh_init();
+ dn_dev_init();
+ dn_route_init();
+
+#ifdef CONFIG_DECNET_ROUTER
+ dn_fib_init();
+#endif /* CONFIG_DECNET_ROUTER */
+
+#ifdef CONFIG_SYSCTL
+ dn_register_sysctl();
+#endif /* CONFIG_SYSCTL */
+
+ /*
+ * Prevent DECnet module unloading until its fixed properly.
+ * Requires an audit of the code to check for memory leaks and
+ * initialisation problems etc.
+ */
+ MOD_INC_USE_COUNT;
+
+ return 0;
+
+}
+
+#ifndef MODULE
+static int __init decnet_setup(char *str)
+{
+ unsigned short area = simple_strtoul(str, &str, 0);
+ unsigned short node = simple_strtoul(*str > 0 ? ++str : str, &str, 0);
+
+ decnet_address = dn_htons(area << 10 | node);
+ dn_dn2eth(decnet_ether_address, dn_ntohs(decnet_address));
+
+ return 1;
+}
+
+__setup("decnet=", decnet_setup);
+#endif
+
+static void __exit decnet_exit(void)
+{
+ sock_unregister(AF_DECnet);
+ dev_remove_pack(&dn_dix_packet_type);
+
+#ifdef CONFIG_SYSCTL
+ dn_unregister_sysctl();
+#endif /* CONFIG_SYSCTL */
+
+ unregister_netdevice_notifier(&dn_dev_notifier);
+
+ dn_route_cleanup();
+ dn_dev_cleanup();
+ dn_neigh_cleanup();
+
+#ifdef CONFIG_DECNET_ROUTER
+ dn_fib_cleanup();
+#endif /* CONFIG_DECNET_ROUTER */
+
+ proc_net_remove("decnet");
+}
+
+module_init(decnet_init);
+module_exit(decnet_exit);
diff --git a/uClinux-2.4.31-uc0/net/decnet/dn_dev.c b/uClinux-2.4.31-uc0/net/decnet/dn_dev.c
new file mode 100644
index 0000000..606ca48
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/dn_dev.c
@@ -0,0 +1,1257 @@
+/*
+ * DECnet An implementation of the DECnet protocol suite for the LINUX
+ * operating system. DECnet is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * DECnet Device Layer
+ *
+ * Authors: Steve Whitehouse <SteveW@ACM.org>
+ * Eduardo Marcelo Serrat <emserrat@geocities.com>
+ *
+ * Changes:
+ * Steve Whitehouse : Devices now see incoming frames so they
+ * can mark on who it came from.
+ * Steve Whitehouse : Fixed bug in creating neighbours. Each neighbour
+ * can now have a device specific setup func.
+ * Steve Whitehouse : Added /proc/sys/net/decnet/conf/<dev>/
+ * Steve Whitehouse : Fixed bug which sometimes killed timer
+ * Steve Whitehouse : Multiple ifaddr support
+ * Steve Whitehouse : SIOCGIFCONF is now a compile time option
+ * Steve Whitehouse : /proc/sys/net/decnet/conf/<sys>/forwarding
+ * Steve Whitehouse : Removed timer1 - its a user space issue now
+ * Patrick Caulfield : Fixed router hello message format
+ */
+
+#include <linux/config.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/sysctl.h>
+#include <asm/uaccess.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/dn.h>
+#include <net/dn_dev.h>
+#include <net/dn_route.h>
+#include <net/dn_neigh.h>
+#include <net/dn_fib.h>
+
+#define DN_IFREQ_SIZE (sizeof(struct ifreq) - sizeof(struct sockaddr) + sizeof(struct sockaddr_dn))
+
+static char dn_rt_all_end_mcast[ETH_ALEN] = {0xAB,0x00,0x00,0x04,0x00,0x00};
+static char dn_rt_all_rt_mcast[ETH_ALEN] = {0xAB,0x00,0x00,0x03,0x00,0x00};
+static char dn_hiord[ETH_ALEN] = {0xAA,0x00,0x04,0x00,0x00,0x00};
+static unsigned char dn_eco_version[3] = {0x02,0x00,0x00};
+
+extern struct neigh_table dn_neigh_table;
+
+struct net_device *decnet_default_device;
+
+static struct dn_dev *dn_dev_create(struct net_device *dev, int *err);
+static void dn_dev_delete(struct net_device *dev);
+static void rtmsg_ifa(int event, struct dn_ifaddr *ifa);
+
+static int dn_eth_up(struct net_device *);
+static void dn_send_brd_hello(struct net_device *dev);
+#if 0
+static void dn_send_ptp_hello(struct net_device *dev);
+#endif
+
+static struct dn_dev_parms dn_dev_list[] = {
+{
+ type: ARPHRD_ETHER, /* Ethernet */
+ mode: DN_DEV_BCAST,
+ state: DN_DEV_S_RU,
+ blksize: 1498,
+ t2: 1,
+ t3: 10,
+ name: "ethernet",
+ ctl_name: NET_DECNET_CONF_ETHER,
+ up: dn_eth_up,
+ timer3: dn_send_brd_hello,
+},
+{
+ type: ARPHRD_IPGRE, /* DECnet tunneled over GRE in IP */
+ mode: DN_DEV_BCAST,
+ state: DN_DEV_S_RU,
+ blksize: 1400,
+ t2: 1,
+ t3: 10,
+ name: "ipgre",
+ ctl_name: NET_DECNET_CONF_GRE,
+ timer3: dn_send_brd_hello,
+},
+#if 0
+{
+ type: ARPHRD_X25, /* Bog standard X.25 */
+ mode: DN_DEV_UCAST,
+ state: DN_DEV_S_DS,
+ blksize: 230,
+ t2: 1,
+ t3: 120,
+ name: "x25",
+ ctl_name: NET_DECNET_CONF_X25,
+ timer3: dn_send_ptp_hello,
+},
+#endif
+#if 0
+{
+ type: ARPHRD_PPP, /* DECnet over PPP */
+ mode: DN_DEV_BCAST,
+ state: DN_DEV_S_RU,
+ blksize: 230,
+ t2: 1,
+ t3: 10,
+ name: "ppp",
+ ctl_name: NET_DECNET_CONF_PPP,
+ timer3: dn_send_brd_hello,
+},
+#endif
+#if 0
+{
+ type: ARPHRD_DDCMP, /* DECnet over DDCMP */
+ mode: DN_DEV_UCAST,
+ state: DN_DEV_S_DS,
+ blksize: 230,
+ t2: 1,
+ t3: 120,
+ name: "ddcmp",
+ ctl_name: NET_DECNET_CONF_DDCMP,
+ timer3: dn_send_ptp_hello,
+},
+#endif
+{
+ type: ARPHRD_LOOPBACK, /* Loopback interface - always last */
+ mode: DN_DEV_BCAST,
+ state: DN_DEV_S_RU,
+ blksize: 1498,
+ t2: 1,
+ t3: 10,
+ name: "loopback",
+ ctl_name: NET_DECNET_CONF_LOOPBACK,
+ timer3: dn_send_brd_hello,
+}
+};
+
+#define DN_DEV_LIST_SIZE (sizeof(dn_dev_list)/sizeof(struct dn_dev_parms))
+
+#define DN_DEV_PARMS_OFFSET(x) ((int) ((char *) &((struct dn_dev_parms *)0)->x))
+
+#ifdef CONFIG_SYSCTL
+
+static int min_t2[] = { 1 };
+static int max_t2[] = { 60 }; /* No max specified, but this seems sensible */
+static int min_t3[] = { 1 };
+static int max_t3[] = { 8191 }; /* Must fit in 16 bits when multiplied by BCT3MULT or T3MULT */
+
+static int min_priority[1];
+static int max_priority[] = { 127 }; /* From DECnet spec */
+
+static int dn_forwarding_proc(ctl_table *, int, struct file *,
+ void *, size_t *);
+static int dn_forwarding_sysctl(ctl_table *table, int *name, int nlen,
+ void *oldval, size_t *oldlenp,
+ void *newval, size_t newlen,
+ void **context);
+
+static struct dn_dev_sysctl_table {
+ struct ctl_table_header *sysctl_header;
+ ctl_table dn_dev_vars[5];
+ ctl_table dn_dev_dev[2];
+ ctl_table dn_dev_conf_dir[2];
+ ctl_table dn_dev_proto_dir[2];
+ ctl_table dn_dev_root_dir[2];
+} dn_dev_sysctl = {
+ NULL,
+ {
+ {NET_DECNET_CONF_DEV_FORWARDING, "forwarding",
+ (void *)DN_DEV_PARMS_OFFSET(forwarding),
+ sizeof(int), 0644, NULL,
+ dn_forwarding_proc, dn_forwarding_sysctl,
+ NULL, NULL, NULL},
+ {NET_DECNET_CONF_DEV_PRIORITY, "priority",
+ (void *)DN_DEV_PARMS_OFFSET(priority),
+ sizeof(int), 0644, NULL,
+ proc_dointvec_minmax, sysctl_intvec,
+ NULL, &min_priority, &max_priority},
+ {NET_DECNET_CONF_DEV_T2, "t2", (void *)DN_DEV_PARMS_OFFSET(t2),
+ sizeof(int), 0644, NULL,
+ proc_dointvec_minmax, sysctl_intvec,
+ NULL, &min_t2, &max_t2},
+ {NET_DECNET_CONF_DEV_T3, "t3", (void *)DN_DEV_PARMS_OFFSET(t3),
+ sizeof(int), 0644, NULL,
+ proc_dointvec_minmax, sysctl_intvec,
+ NULL, &min_t3, &max_t3},
+ {0}
+ },
+ {{0, "", NULL, 0, 0555, dn_dev_sysctl.dn_dev_vars}, {0}},
+ {{NET_DECNET_CONF, "conf", NULL, 0, 0555, dn_dev_sysctl.dn_dev_dev}, {0}},
+ {{NET_DECNET, "decnet", NULL, 0, 0555, dn_dev_sysctl.dn_dev_conf_dir}, {0}},
+ {{CTL_NET, "net", NULL, 0, 0555, dn_dev_sysctl.dn_dev_proto_dir}, {0}}
+};
+
+static void dn_dev_sysctl_register(struct net_device *dev, struct dn_dev_parms *parms)
+{
+ struct dn_dev_sysctl_table *t;
+ int i;
+
+ t = kmalloc(sizeof(*t), GFP_KERNEL);
+ if (t == NULL)
+ return;
+
+ memcpy(t, &dn_dev_sysctl, sizeof(*t));
+
+ for(i = 0; i < (sizeof(t->dn_dev_vars)/sizeof(t->dn_dev_vars[0]) - 1); i++) {
+ long offset = (long)t->dn_dev_vars[i].data;
+ t->dn_dev_vars[i].data = ((char *)parms) + offset;
+ t->dn_dev_vars[i].de = NULL;
+ }
+
+ if (dev) {
+ t->dn_dev_dev[0].procname = dev->name;
+ t->dn_dev_dev[0].ctl_name = dev->ifindex;
+ } else {
+ t->dn_dev_dev[0].procname = parms->name;
+ t->dn_dev_dev[0].ctl_name = parms->ctl_name;
+ }
+
+ t->dn_dev_dev[0].child = t->dn_dev_vars;
+ t->dn_dev_dev[0].de = NULL;
+ t->dn_dev_conf_dir[0].child = t->dn_dev_dev;
+ t->dn_dev_conf_dir[0].de = NULL;
+ t->dn_dev_proto_dir[0].child = t->dn_dev_conf_dir;
+ t->dn_dev_proto_dir[0].de = NULL;
+ t->dn_dev_root_dir[0].child = t->dn_dev_proto_dir;
+ t->dn_dev_root_dir[0].de = NULL;
+ t->dn_dev_vars[0].extra1 = (void *)dev;
+
+ t->sysctl_header = register_sysctl_table(t->dn_dev_root_dir, 0);
+ if (t->sysctl_header == NULL)
+ kfree(t);
+ else
+ parms->sysctl = t;
+}
+
+static void dn_dev_sysctl_unregister(struct dn_dev_parms *parms)
+{
+ if (parms->sysctl) {
+ struct dn_dev_sysctl_table *t = parms->sysctl;
+ parms->sysctl = NULL;
+ unregister_sysctl_table(t->sysctl_header);
+ kfree(t);
+ }
+}
+
+
+static int dn_forwarding_proc(ctl_table *table, int write,
+ struct file *filep,
+ void *buffer, size_t *lenp)
+{
+#ifdef CONFIG_DECNET_ROUTER
+ struct net_device *dev = table->extra1;
+ struct dn_dev *dn_db;
+ int err;
+ int tmp, old;
+
+ if (table->extra1 == NULL)
+ return -EINVAL;
+
+ dn_db = dev->dn_ptr;
+ old = dn_db->parms.forwarding;
+
+ err = proc_dointvec(table, write, filep, buffer, lenp);
+
+ if ((err >= 0) && write) {
+ if (dn_db->parms.forwarding < 0)
+ dn_db->parms.forwarding = 0;
+ if (dn_db->parms.forwarding > 2)
+ dn_db->parms.forwarding = 2;
+ /*
+ * What an ugly hack this is... its works, just. It
+ * would be nice if sysctl/proc were just that little
+ * bit more flexible so I don't have to write a special
+ * routine, or suffer hacks like this - SJW
+ */
+ tmp = dn_db->parms.forwarding;
+ dn_db->parms.forwarding = old;
+ if (dn_db->parms.down)
+ dn_db->parms.down(dev);
+ dn_db->parms.forwarding = tmp;
+ if (dn_db->parms.up)
+ dn_db->parms.up(dev);
+ }
+
+ return err;
+#else
+ return -EINVAL;
+#endif
+}
+
+static int dn_forwarding_sysctl(ctl_table *table, int *name, int nlen,
+ void *oldval, size_t *oldlenp,
+ void *newval, size_t newlen,
+ void **context)
+{
+#ifdef CONFIG_DECNET_ROUTER
+ struct net_device *dev = table->extra1;
+ struct dn_dev *dn_db;
+ int value;
+
+ if (table->extra1 == NULL)
+ return -EINVAL;
+
+ dn_db = dev->dn_ptr;
+
+ if (newval && newlen) {
+ if (newlen != sizeof(int))
+ return -EINVAL;
+
+ if (get_user(value, (int *)newval))
+ return -EFAULT;
+ if (value < 0)
+ return -EINVAL;
+ if (value > 2)
+ return -EINVAL;
+
+ if (dn_db->parms.down)
+ dn_db->parms.down(dev);
+ dn_db->parms.forwarding = value;
+ if (dn_db->parms.up)
+ dn_db->parms.up(dev);
+ }
+
+ return 0;
+#else
+ return -EINVAL;
+#endif
+}
+
+#else /* CONFIG_SYSCTL */
+static void dn_dev_sysctl_unregister(struct dn_dev_parms *parms)
+{
+}
+static void dn_dev_sysctl_register(struct net_device *dev, struct dn_dev_parms *parms)
+{
+}
+
+#endif /* CONFIG_SYSCTL */
+
+static struct dn_ifaddr *dn_dev_alloc_ifa(void)
+{
+ struct dn_ifaddr *ifa;
+
+ ifa = kmalloc(sizeof(*ifa), GFP_KERNEL);
+
+ if (ifa) {
+ memset(ifa, 0, sizeof(*ifa));
+ }
+
+ return ifa;
+}
+
+static __inline__ void dn_dev_free_ifa(struct dn_ifaddr *ifa)
+{
+ kfree(ifa);
+}
+
+static void dn_dev_del_ifa(struct dn_dev *dn_db, struct dn_ifaddr **ifap, int destroy)
+{
+ struct dn_ifaddr *ifa1 = *ifap;
+
+ *ifap = ifa1->ifa_next;
+
+ rtmsg_ifa(RTM_DELADDR, ifa1);
+
+ if (destroy) {
+ dn_dev_free_ifa(ifa1);
+
+ if (dn_db->ifa_list == NULL)
+ dn_dev_delete(dn_db->dev);
+ }
+}
+
+static int dn_dev_insert_ifa(struct dn_dev *dn_db, struct dn_ifaddr *ifa)
+{
+ /*
+ * FIXME: Duplicate check here.
+ */
+
+ ifa->ifa_next = dn_db->ifa_list;
+ dn_db->ifa_list = ifa;
+
+ rtmsg_ifa(RTM_NEWADDR, ifa);
+
+ return 0;
+}
+
+static int dn_dev_set_ifa(struct net_device *dev, struct dn_ifaddr *ifa)
+{
+ struct dn_dev *dn_db = dev->dn_ptr;
+
+ if (dn_db == NULL) {
+ int err;
+ dn_db = dn_dev_create(dev, &err);
+ if (dn_db == NULL)
+ return err;
+ }
+
+ ifa->ifa_dev = dn_db;
+
+ if (dev->flags & IFF_LOOPBACK)
+ ifa->ifa_scope = RT_SCOPE_HOST;
+
+ return dn_dev_insert_ifa(dn_db, ifa);
+}
+
+
+int dn_dev_ioctl(unsigned int cmd, void *arg)
+{
+ char buffer[DN_IFREQ_SIZE];
+ struct ifreq *ifr = (struct ifreq *)buffer;
+ struct sockaddr_dn *sdn = (struct sockaddr_dn *)&ifr->ifr_addr;
+ struct dn_dev *dn_db;
+ struct net_device *dev;
+ struct dn_ifaddr *ifa = NULL, **ifap = NULL;
+ int exclusive = 0;
+ int ret = 0;
+
+ if (copy_from_user(ifr, arg, DN_IFREQ_SIZE))
+ return -EFAULT;
+ ifr->ifr_name[IFNAMSIZ-1] = 0;
+
+#ifdef CONFIG_KMOD
+ dev_load(ifr->ifr_name);
+#endif
+
+ switch(cmd) {
+ case SIOCGIFADDR:
+ break;
+ case SIOCSIFADDR:
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+ if (sdn->sdn_family != AF_DECnet)
+ return -EINVAL;
+ rtnl_lock();
+ exclusive = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if ((dev = __dev_get_by_name(ifr->ifr_name)) == NULL) {
+ ret = -ENODEV;
+ goto done;
+ }
+
+ if ((dn_db = dev->dn_ptr) != NULL) {
+ for (ifap = &dn_db->ifa_list; (ifa=*ifap) != NULL; ifap = &ifa->ifa_next)
+ if (strcmp(ifr->ifr_name, ifa->ifa_label) == 0)
+ break;
+ }
+
+ if (ifa == NULL && cmd != SIOCSIFADDR) {
+ ret = -EADDRNOTAVAIL;
+ goto done;
+ }
+
+ switch(cmd) {
+ case SIOCGIFADDR:
+ *((dn_address *)sdn->sdn_nodeaddr) = ifa->ifa_local;
+ goto rarok;
+
+ case SIOCSIFADDR:
+ if (!ifa) {
+ if ((ifa = dn_dev_alloc_ifa()) == NULL) {
+ ret = -ENOBUFS;
+ break;
+ }
+ memcpy(ifa->ifa_label, dev->name, IFNAMSIZ);
+ } else {
+ if (ifa->ifa_local == dn_saddr2dn(sdn))
+ break;
+ dn_dev_del_ifa(dn_db, ifap, 0);
+ }
+
+ ifa->ifa_local = dn_saddr2dn(sdn);
+
+ ret = dn_dev_set_ifa(dev, ifa);
+ }
+done:
+ if (exclusive)
+ rtnl_unlock();
+
+ return ret;
+rarok:
+ if (copy_to_user(arg, ifr, DN_IFREQ_SIZE))
+ return -EFAULT;
+
+ return 0;
+}
+
+static struct dn_dev *dn_dev_by_index(int ifindex)
+{
+ struct net_device *dev;
+ struct dn_dev *dn_dev = NULL;
+ dev = dev_get_by_index(ifindex);
+ if (dev) {
+ dn_dev = dev->dn_ptr;
+ dev_put(dev);
+ }
+
+ return dn_dev;
+}
+
+static int dn_dev_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct rtattr **rta = arg;
+ struct dn_dev *dn_db;
+ struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
+ struct dn_ifaddr *ifa, **ifap;
+
+ if ((dn_db = dn_dev_by_index(ifm->ifa_index)) == NULL)
+ return -EADDRNOTAVAIL;
+
+ for(ifap = &dn_db->ifa_list; (ifa=*ifap) != NULL; ifap = &ifa->ifa_next) {
+ void *tmp = rta[IFA_LOCAL-1];
+ if ((tmp && memcmp(RTA_DATA(tmp), &ifa->ifa_local, 2)) ||
+ (rta[IFA_LABEL-1] && strcmp(RTA_DATA(rta[IFA_LABEL-1]), ifa->ifa_label)))
+ continue;
+
+ dn_dev_del_ifa(dn_db, ifap, 1);
+ return 0;
+ }
+
+ return -EADDRNOTAVAIL;
+}
+
+static int dn_dev_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct rtattr **rta = arg;
+ struct net_device *dev;
+ struct dn_dev *dn_db;
+ struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
+ struct dn_ifaddr *ifa;
+
+ if (rta[IFA_LOCAL-1] == NULL)
+ return -EINVAL;
+
+ if ((dev = __dev_get_by_index(ifm->ifa_index)) == NULL)
+ return -ENODEV;
+
+ if ((dn_db = dev->dn_ptr) == NULL) {
+ int err;
+ dn_db = dn_dev_create(dev, &err);
+ if (!dn_db)
+ return err;
+ }
+
+ if ((ifa = dn_dev_alloc_ifa()) == NULL)
+ return -ENOBUFS;
+
+ memcpy(&ifa->ifa_local, RTA_DATA(rta[IFA_LOCAL-1]), 2);
+ ifa->ifa_flags = ifm->ifa_flags;
+ ifa->ifa_scope = ifm->ifa_scope;
+ ifa->ifa_dev = dn_db;
+ if (rta[IFA_LABEL-1])
+ memcpy(ifa->ifa_label, RTA_DATA(rta[IFA_LABEL-1]), IFNAMSIZ);
+ else
+ memcpy(ifa->ifa_label, dev->name, IFNAMSIZ);
+
+ return dn_dev_insert_ifa(dn_db, ifa);
+}
+
+static int dn_dev_fill_ifaddr(struct sk_buff *skb, struct dn_ifaddr *ifa,
+ u32 pid, u32 seq, int event)
+{
+ struct ifaddrmsg *ifm;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+ nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*ifm));
+ ifm = NLMSG_DATA(nlh);
+
+ ifm->ifa_family = AF_DECnet;
+ ifm->ifa_prefixlen = 16;
+ ifm->ifa_flags = ifa->ifa_flags | IFA_F_PERMANENT;
+ ifm->ifa_scope = ifa->ifa_scope;
+ ifm->ifa_index = ifa->ifa_dev->dev->ifindex;
+ RTA_PUT(skb, IFA_LOCAL, 2, &ifa->ifa_local);
+ if (ifa->ifa_label[0])
+ RTA_PUT(skb, IFA_LABEL, IFNAMSIZ, &ifa->ifa_label);
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static void rtmsg_ifa(int event, struct dn_ifaddr *ifa)
+{
+ struct sk_buff *skb;
+ int size = NLMSG_SPACE(sizeof(struct ifaddrmsg)+128);
+
+ skb = alloc_skb(size, GFP_KERNEL);
+ if (!skb) {
+ netlink_set_err(rtnl, 0, RTMGRP_DECnet_IFADDR, ENOBUFS);
+ return;
+ }
+ if (dn_dev_fill_ifaddr(skb, ifa, 0, 0, event) < 0) {
+ kfree_skb(skb);
+ netlink_set_err(rtnl, 0, RTMGRP_DECnet_IFADDR, EINVAL);
+ return;
+ }
+ NETLINK_CB(skb).dst_groups = RTMGRP_DECnet_IFADDR;
+ netlink_broadcast(rtnl, skb, 0, RTMGRP_DECnet_IFADDR, GFP_KERNEL);
+}
+
+static int dn_dev_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int idx, dn_idx;
+ int s_idx, s_dn_idx;
+ struct net_device *dev;
+ struct dn_dev *dn_db;
+ struct dn_ifaddr *ifa;
+
+ s_idx = cb->args[0];
+ s_dn_idx = dn_idx = cb->args[1];
+ read_lock(&dev_base_lock);
+ for(dev = dev_base, idx = 0; dev; dev = dev->next) {
+ if ((dn_db = dev->dn_ptr) == NULL)
+ continue;
+ idx++;
+ if (idx < s_idx)
+ continue;
+ if (idx > s_idx)
+ s_dn_idx = 0;
+ if ((dn_db = dev->dn_ptr) == NULL)
+ continue;
+
+ for(ifa = dn_db->ifa_list, dn_idx = 0; ifa; ifa = ifa->ifa_next, dn_idx++) {
+ if (dn_idx < s_dn_idx)
+ continue;
+
+ if (dn_dev_fill_ifaddr(skb, ifa, NETLINK_CB(cb->skb).pid, cb->nlh->nlmsg_seq, RTM_NEWADDR) <= 0)
+ goto done;
+ }
+ }
+done:
+ read_unlock(&dev_base_lock);
+ cb->args[0] = idx;
+ cb->args[1] = dn_idx;
+
+ return skb->len;
+}
+
+static void dn_send_endnode_hello(struct net_device *dev)
+{
+ struct endnode_hello_message *msg;
+ struct sk_buff *skb = NULL;
+ unsigned short int *pktlen;
+ struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
+
+ if ((skb = dn_alloc_skb(NULL, sizeof(*msg), GFP_ATOMIC)) == NULL)
+ return;
+
+ skb->dev = dev;
+
+ msg = (struct endnode_hello_message *)skb_put(skb,sizeof(*msg));
+
+ msg->msgflg = 0x0D;
+ memcpy(msg->tiver, dn_eco_version, 3);
+ memcpy(msg->id, decnet_ether_address, 6);
+ msg->iinfo = DN_RT_INFO_ENDN;
+ msg->blksize = dn_htons(dn_db->parms.blksize);
+ msg->area = 0x00;
+ memset(msg->seed, 0, 8);
+ memcpy(msg->neighbor, dn_hiord, ETH_ALEN);
+
+ if (dn_db->router) {
+ struct dn_neigh *dn = (struct dn_neigh *)dn_db->router;
+ dn_dn2eth(msg->neighbor, dn->addr);
+ }
+
+ msg->timer = dn_htons((unsigned short)dn_db->parms.t3);
+ msg->mpd = 0x00;
+ msg->datalen = 0x02;
+ memset(msg->data, 0xAA, 2);
+
+ pktlen = (unsigned short *)skb_push(skb,2);
+ *pktlen = dn_htons(skb->len - 2);
+
+ skb->nh.raw = skb->data;
+
+ dn_rt_finish_output(skb, dn_rt_all_rt_mcast);
+}
+
+
+#ifdef CONFIG_DECNET_ROUTER
+
+#define DRDELAY (5 * HZ)
+
+static int dn_am_i_a_router(struct dn_neigh *dn, struct dn_dev *dn_db)
+{
+ /* First check time since device went up */
+ if ((jiffies - dn_db->uptime) < DRDELAY)
+ return 0;
+
+ /* If there is no router, then yes... */
+ if (!dn_db->router)
+ return 1;
+
+ /* otherwise only if we have a higher priority or.. */
+ if (dn->priority < dn_db->parms.priority)
+ return 1;
+
+ /* if we have equal priority and a higher node number */
+ if (dn->priority != dn_db->parms.priority)
+ return 0;
+
+ if (dn_ntohs(dn->addr) < dn_ntohs(decnet_address))
+ return 1;
+
+ return 0;
+}
+
+static void dn_send_router_hello(struct net_device *dev)
+{
+ int n;
+ struct dn_dev *dn_db = dev->dn_ptr;
+ struct dn_neigh *dn = (struct dn_neigh *)dn_db->router;
+ struct sk_buff *skb;
+ size_t size;
+ unsigned char *ptr;
+ unsigned char *i1, *i2;
+ unsigned short *pktlen;
+
+ if (dn_db->parms.blksize < (26 + 7))
+ return;
+
+ n = dn_db->parms.blksize - 26;
+ n /= 7;
+
+ if (n > 32)
+ n = 32;
+
+ size = 2 + 26 + 7 * n;
+
+ if ((skb = dn_alloc_skb(NULL, size, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb->dev = dev;
+ ptr = skb_put(skb, size);
+
+ *ptr++ = DN_RT_PKT_CNTL | DN_RT_PKT_ERTH;
+ *ptr++ = 2; /* ECO */
+ *ptr++ = 0;
+ *ptr++ = 0;
+ memcpy(ptr, decnet_ether_address, ETH_ALEN);
+ ptr += ETH_ALEN;
+ *ptr++ = dn_db->parms.forwarding == 1 ?
+ DN_RT_INFO_L1RT : DN_RT_INFO_L2RT;
+ *((unsigned short *)ptr) = dn_htons(dn_db->parms.blksize);
+ ptr += 2;
+ *ptr++ = dn_db->parms.priority; /* Priority */
+ *ptr++ = 0; /* Area: Reserved */
+ *((unsigned short *)ptr) = dn_htons((unsigned short)dn_db->parms.t3);
+ ptr += 2;
+ *ptr++ = 0; /* MPD: Reserved */
+ i1 = ptr++;
+ memset(ptr, 0, 7); /* Name: Reserved */
+ ptr += 7;
+ i2 = ptr++;
+
+ n = dn_neigh_elist(dev, ptr, n);
+
+ *i2 = 7 * n;
+ *i1 = 8 + *i2;
+
+ skb_trim(skb, (27 + *i2));
+
+ pktlen = (unsigned short *)skb_push(skb, 2);
+ *pktlen = dn_htons(skb->len - 2);
+
+ skb->nh.raw = skb->data;
+
+ if (dn_am_i_a_router(dn, dn_db)) {
+ struct sk_buff *skb2 = skb_copy(skb, GFP_ATOMIC);
+ if (skb2) {
+ dn_rt_finish_output(skb2, dn_rt_all_end_mcast);
+ }
+ }
+
+ dn_rt_finish_output(skb, dn_rt_all_rt_mcast);
+}
+
+static void dn_send_brd_hello(struct net_device *dev)
+{
+ struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
+
+ if (dn_db->parms.forwarding == 0)
+ dn_send_endnode_hello(dev);
+ else
+ dn_send_router_hello(dev);
+}
+#else
+static void dn_send_brd_hello(struct net_device *dev)
+{
+ dn_send_endnode_hello(dev);
+}
+#endif
+
+#if 0
+static void dn_send_ptp_hello(struct net_device *dev)
+{
+ int tdlen = 16;
+ int size = dev->hard_header_len + 2 + 4 + tdlen;
+ struct sk_buff *skb = dn_alloc_skb(NULL, size, GFP_ATOMIC);
+ struct dn_dev *dn_db = dev->dn_ptr;
+ int i;
+ unsigned char *ptr;
+ struct dn_neigh *dn = (struct dn_neigh *)dn_db->router;
+
+ if (skb == NULL)
+ return ;
+
+ skb->dev = dev;
+ skb_push(skb, dev->hard_header_len);
+ ptr = skb_put(skb, 2 + 4 + tdlen);
+
+ *ptr++ = DN_RT_PKT_HELO;
+ *((dn_address *)ptr) = decnet_address;
+ ptr += 2;
+ *ptr++ = tdlen;
+
+ for(i = 0; i < tdlen; i++)
+ *ptr++ = 0252;
+
+ if (dn_am_i_a_router(dn, dn_db)) {
+ struct sk_buff *skb2 = skb_copy(skb, GFP_ATOMIC);
+ if (skb2) {
+ dn_rt_finish_output(skb2, dn_rt_all_end_mcast);
+ }
+ }
+
+ dn_rt_finish_output(skb, dn_rt_all_rt_mcast);
+}
+#endif
+
+static int dn_eth_up(struct net_device *dev)
+{
+ struct dn_dev *dn_db = dev->dn_ptr;
+
+ if (dn_db->parms.forwarding == 0)
+ dev_mc_add(dev, dn_rt_all_end_mcast, ETH_ALEN, 0);
+ else
+ dev_mc_add(dev, dn_rt_all_rt_mcast, ETH_ALEN, 0);
+
+ dev_mc_upload(dev);
+
+ dn_db->use_long = 1;
+
+ return 0;
+}
+
+static void dn_dev_set_timer(struct net_device *dev);
+
+static void dn_dev_timer_func(unsigned long arg)
+{
+ struct net_device *dev = (struct net_device *)arg;
+ struct dn_dev *dn_db = dev->dn_ptr;
+
+ if (dn_db->t3 <= dn_db->parms.t2) {
+ if (dn_db->parms.timer3)
+ dn_db->parms.timer3(dev);
+ dn_db->t3 = dn_db->parms.t3;
+ } else {
+ dn_db->t3 -= dn_db->parms.t2;
+ }
+
+ dn_dev_set_timer(dev);
+}
+
+static void dn_dev_set_timer(struct net_device *dev)
+{
+ struct dn_dev *dn_db = dev->dn_ptr;
+
+ if (dn_db->parms.t2 > dn_db->parms.t3)
+ dn_db->parms.t2 = dn_db->parms.t3;
+
+ dn_db->timer.data = (unsigned long)dev;
+ dn_db->timer.function = dn_dev_timer_func;
+ dn_db->timer.expires = jiffies + (dn_db->parms.t2 * HZ);
+
+ add_timer(&dn_db->timer);
+}
+
+struct dn_dev *dn_dev_create(struct net_device *dev, int *err)
+{
+ int i;
+ struct dn_dev_parms *p = dn_dev_list;
+ struct dn_dev *dn_db;
+
+ for(i = 0; i < DN_DEV_LIST_SIZE; i++, p++) {
+ if (p->type == dev->type)
+ break;
+ }
+
+ *err = -ENODEV;
+ if (i == DN_DEV_LIST_SIZE)
+ return NULL;
+
+ *err = -ENOBUFS;
+ if ((dn_db = kmalloc(sizeof(struct dn_dev), GFP_ATOMIC)) == NULL)
+ return NULL;
+
+ memset(dn_db, 0, sizeof(struct dn_dev));
+ memcpy(&dn_db->parms, p, sizeof(struct dn_dev_parms));
+ dev->dn_ptr = dn_db;
+ dn_db->dev = dev;
+ init_timer(&dn_db->timer);
+
+ memcpy(dn_db->addr, decnet_ether_address, ETH_ALEN); /* To go... */
+
+ dn_db->uptime = jiffies;
+ if (dn_db->parms.up) {
+ if (dn_db->parms.up(dev) < 0) {
+ dev->dn_ptr = NULL;
+ kfree(dn_db);
+ return NULL;
+ }
+ }
+
+ dn_db->neigh_parms = neigh_parms_alloc(dev, &dn_neigh_table);
+ /* dn_db->neigh_parms->neigh_setup = dn_db->parms.neigh_setup; */
+
+ dn_dev_sysctl_register(dev, &dn_db->parms);
+
+ dn_dev_set_timer(dev);
+
+ *err = 0;
+ return dn_db;
+}
+
+
+/*
+ * This processes a device up event. We only start up
+ * the loopback device & ethernet devices with correct
+ * MAC addreses automatically. Others must be started
+ * specifically.
+ */
+void dn_dev_up(struct net_device *dev)
+{
+ struct dn_ifaddr *ifa;
+
+ if ((dev->type != ARPHRD_ETHER) && (dev->type != ARPHRD_LOOPBACK))
+ return;
+
+ if (dev->type == ARPHRD_ETHER)
+ if (memcmp(dev->dev_addr, decnet_ether_address, ETH_ALEN) != 0)
+ return;
+
+ if ((ifa = dn_dev_alloc_ifa()) == NULL)
+ return;
+
+ ifa->ifa_local = decnet_address;
+ ifa->ifa_flags = 0;
+ ifa->ifa_scope = RT_SCOPE_UNIVERSE;
+ strcpy(ifa->ifa_label, dev->name);
+
+ dn_dev_set_ifa(dev, ifa);
+}
+
+static void dn_dev_delete(struct net_device *dev)
+{
+ struct dn_dev *dn_db = dev->dn_ptr;
+
+ if (dn_db == NULL)
+ return;
+
+ del_timer_sync(&dn_db->timer);
+
+ dn_dev_sysctl_unregister(&dn_db->parms);
+
+ neigh_ifdown(&dn_neigh_table, dev);
+
+ if (dev == decnet_default_device)
+ decnet_default_device = NULL;
+
+ if (dn_db->parms.down)
+ dn_db->parms.down(dev);
+
+ dev->dn_ptr = NULL;
+
+ neigh_parms_release(&dn_neigh_table, dn_db->neigh_parms);
+
+ if (dn_db->router)
+ neigh_release(dn_db->router);
+ if (dn_db->peer)
+ neigh_release(dn_db->peer);
+
+ kfree(dn_db);
+}
+
+void dn_dev_down(struct net_device *dev)
+{
+ struct dn_dev *dn_db = dev->dn_ptr;
+ struct dn_ifaddr *ifa;
+
+ if (dn_db == NULL)
+ return;
+
+ while((ifa = dn_db->ifa_list) != NULL) {
+ dn_dev_del_ifa(dn_db, &dn_db->ifa_list, 0);
+ dn_dev_free_ifa(ifa);
+ }
+
+ dn_dev_delete(dev);
+}
+
+void dn_dev_init_pkt(struct sk_buff *skb)
+{
+ return;
+}
+
+void dn_dev_veri_pkt(struct sk_buff *skb)
+{
+ return;
+}
+
+void dn_dev_hello(struct sk_buff *skb)
+{
+ return;
+}
+
+void dn_dev_devices_off(void)
+{
+ struct net_device *dev;
+
+ for(dev = dev_base; dev; dev = dev->next)
+ dn_dev_down(dev);
+
+}
+
+void dn_dev_devices_on(void)
+{
+ struct net_device *dev;
+
+ for(dev = dev_base; dev; dev = dev->next) {
+ if (dev->flags & IFF_UP)
+ dn_dev_up(dev);
+ }
+}
+
+
+#ifdef CONFIG_DECNET_SIOCGIFCONF
+/*
+ * Now we support multiple addresses per interface.
+ * Since we don't want to break existing code, you have to enable
+ * it as a compile time option. Probably you should use the
+ * rtnetlink interface instead.
+ */
+int dnet_gifconf(struct net_device *dev, char *buf, int len)
+{
+ struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
+ struct dn_ifaddr *ifa;
+ char buffer[DN_IFREQ_SIZE];
+ struct ifreq *ifr = (struct ifreq *)buffer;
+ struct sockaddr_dn *addr = (struct sockaddr_dn *)&ifr->ifr_addr;
+ int done = 0;
+
+ if ((dn_db == NULL) || ((ifa = dn_db->ifa_list) == NULL))
+ return 0;
+
+ for(; ifa; ifa = ifa->ifa_next) {
+ if (!buf) {
+ done += sizeof(DN_IFREQ_SIZE);
+ continue;
+ }
+ if (len < DN_IFREQ_SIZE)
+ return done;
+ memset(buffer, 0, DN_IFREQ_SIZE);
+
+ if (ifa->ifa_label)
+ strcpy(ifr->ifr_name, ifa->ifa_label);
+ else
+ strcpy(ifr->ifr_name, dev->name);
+
+ addr->sdn_family = AF_DECnet;
+ addr->sdn_add.a_len = 2;
+ memcpy(addr->sdn_add.a_addr, &ifa->ifa_local,
+ sizeof(dn_address));
+
+ if (copy_to_user(buf, buffer, DN_IFREQ_SIZE)) {
+ done = -EFAULT;
+ break;
+ }
+
+ buf += DN_IFREQ_SIZE;
+ len -= DN_IFREQ_SIZE;
+ done += DN_IFREQ_SIZE;
+ }
+
+ return done;
+}
+#endif /* CONFIG_DECNET_SIOCGIFCONF */
+
+
+#ifdef CONFIG_PROC_FS
+
+static char *dn_type2asc(char type)
+{
+ switch(type) {
+ case DN_DEV_BCAST:
+ return "B";
+ case DN_DEV_UCAST:
+ return "U";
+ case DN_DEV_MPOINT:
+ return "M";
+ }
+
+ return "?";
+}
+
+static int decnet_dev_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct dn_dev *dn_db;
+ struct net_device *dev;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+ char peer_buf[DN_ASCBUF_LEN];
+ char router_buf[DN_ASCBUF_LEN];
+
+
+ len += sprintf(buffer, "Name Flags T1 Timer1 T3 Timer3 BlkSize Pri State DevType Router Peer\n");
+
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev; dev = dev->next) {
+ if ((dn_db = (struct dn_dev *)dev->dn_ptr) == NULL)
+ continue;
+
+ len += sprintf(buffer + len, "%-8s %1s %04u %04u %04lu %04lu %04hu %03d %02x %-10s %-7s %-7s\n",
+ dev->name ? dev->name : "???",
+ dn_type2asc(dn_db->parms.mode),
+ 0, 0,
+ dn_db->t3, dn_db->parms.t3,
+ dn_db->parms.blksize,
+ dn_db->parms.priority,
+ dn_db->parms.state, dn_db->parms.name,
+ dn_db->router ? dn_addr2asc(dn_ntohs(*(dn_address *)dn_db->router->primary_key), router_buf) : "",
+ dn_db->peer ? dn_addr2asc(dn_ntohs(*(dn_address *)dn_db->peer->primary_key), peer_buf) : "");
+
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (pos > offset + length)
+ break;
+ }
+
+ read_unlock(&dev_base_lock);
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return(len);
+}
+
+#endif /* CONFIG_PROC_FS */
+
+static struct rtnetlink_link dnet_rtnetlink_table[RTM_MAX-RTM_BASE+1] =
+{
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+
+ { dn_dev_rtm_newaddr, NULL, },
+ { dn_dev_rtm_deladdr, NULL, },
+ { NULL, dn_dev_dump_ifaddr, },
+ { NULL, NULL, },
+
+#ifdef CONFIG_DECNET_ROUTER
+ { dn_fib_rtm_newroute, NULL, },
+ { dn_fib_rtm_delroute, NULL, },
+ { dn_cache_getroute, dn_fib_dump, },
+ { NULL, NULL, },
+#else
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { dn_cache_getroute, dn_cache_dump, },
+ { NULL, NULL, },
+#endif
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+
+#ifdef CONFIG_DECNET_ROUTER
+ { dn_fib_rtm_newrule, NULL, },
+ { dn_fib_rtm_delrule, NULL, },
+ { NULL, dn_fib_dump_rules, },
+ { NULL, NULL, }
+#else
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, }
+#endif
+};
+
+void __init dn_dev_init(void)
+{
+
+ dn_dev_devices_on();
+#ifdef CONFIG_DECNET_SIOCGIFCONF
+ register_gifconf(PF_DECnet, dnet_gifconf);
+#endif /* CONFIG_DECNET_SIOCGIFCONF */
+
+ rtnetlink_links[PF_DECnet] = dnet_rtnetlink_table;
+
+#ifdef CONFIG_PROC_FS
+ proc_net_create("decnet_dev", 0, decnet_dev_get_info);
+#endif /* CONFIG_PROC_FS */
+
+#ifdef CONFIG_SYSCTL
+ {
+ int i;
+ for(i = 0; i < DN_DEV_LIST_SIZE; i++)
+ dn_dev_sysctl_register(NULL, &dn_dev_list[i]);
+ }
+#endif /* CONFIG_SYSCTL */
+}
+
+void __exit dn_dev_cleanup(void)
+{
+ rtnetlink_links[PF_DECnet] = NULL;
+
+#ifdef CONFIG_DECNET_SIOCGIFCONF
+ unregister_gifconf(PF_DECnet);
+#endif /* CONFIG_DECNET_SIOCGIFCONF */
+
+#ifdef CONFIG_SYSCTL
+ {
+ int i;
+ for(i = 0; i < DN_DEV_LIST_SIZE; i++)
+ dn_dev_sysctl_unregister(&dn_dev_list[i]);
+ }
+#endif /* CONFIG_SYSCTL */
+
+ proc_net_remove("decnet_dev");
+
+ dn_dev_devices_off();
+}
diff --git a/uClinux-2.4.31-uc0/net/decnet/dn_fib.c b/uClinux-2.4.31-uc0/net/decnet/dn_fib.c
new file mode 100644
index 0000000..59a1c9f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/dn_fib.c
@@ -0,0 +1,661 @@
+/*
+ * DECnet An implementation of the DECnet protocol suite for the LINUX
+ * operating system. DECnet is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * DECnet Routing Forwarding Information Base (Glue/Info List)
+ *
+ * Author: Steve Whitehouse <SteveW@ACM.org>
+ *
+ *
+ * Changes:
+ * Alexey Kuznetsov : SMP locking changes
+ * Steve Whitehouse : Rewrote it... Well to be more correct, I
+ * copied most of it from the ipv4 fib code.
+ *
+ */
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/proc_fs.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/dn.h>
+#include <net/dn_route.h>
+#include <net/dn_fib.h>
+#include <net/dn_neigh.h>
+#include <net/dn_dev.h>
+
+
+#define for_fib_info() { struct dn_fib_info *fi;\
+ for(fi = dn_fib_info_list; fi; fi = fi->fib_next)
+#define endfor_fib_info() }
+
+#define for_nexthops(fi) { int nhsel; const struct dn_fib_nh *nh;\
+ for(nhsel = 0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++)
+
+#define change_nexthops(fi) { int nhsel; struct dn_fib_nh *nh;\
+ for(nhsel = 0, nh = (struct dn_fib_nh *)((fi)->fib_nh); nhsel < (fi)->fib_nhs; nh++, nhsel++)
+
+#define endfor_nexthops(fi) }
+
+extern int dn_cache_dump(struct sk_buff *skb, struct netlink_callback *cb);
+
+
+static struct dn_fib_info *dn_fib_info_list;
+static rwlock_t dn_fib_info_lock = RW_LOCK_UNLOCKED;
+int dn_fib_info_cnt;
+
+static struct
+{
+ int error;
+ u8 scope;
+} dn_fib_props[RTA_MAX+1] = {
+ { 0, RT_SCOPE_NOWHERE }, /* RTN_UNSPEC */
+ { 0, RT_SCOPE_UNIVERSE }, /* RTN_UNICAST */
+ { 0, RT_SCOPE_HOST }, /* RTN_LOCAL */
+ { -EINVAL, RT_SCOPE_NOWHERE }, /* RTN_BROADCAST */
+ { -EINVAL, RT_SCOPE_NOWHERE }, /* RTN_ANYCAST */
+ { -EINVAL, RT_SCOPE_NOWHERE }, /* RTN_MULTICAST */
+ { -EINVAL, RT_SCOPE_UNIVERSE }, /* RTN_BLACKHOLE */
+ { -EHOSTUNREACH, RT_SCOPE_UNIVERSE }, /* RTN_UNREACHABLE */
+ { -EACCES, RT_SCOPE_UNIVERSE }, /* RTN_PROHIBIT */
+ { -EAGAIN, RT_SCOPE_UNIVERSE }, /* RTN_THROW */
+ { -EINVAL, RT_SCOPE_NOWHERE }, /* RTN_NAT */
+ { -EINVAL, RT_SCOPE_NOWHERE } /* RTN_XRESOLVE */
+};
+
+void dn_fib_free_info(struct dn_fib_info *fi)
+{
+ if (fi->fib_dead == 0) {
+ printk(KERN_DEBUG "DECnet: BUG! Attempt to free alive dn_fib_info\n");
+ return;
+ }
+
+ change_nexthops(fi) {
+ if (nh->nh_dev)
+ dev_put(nh->nh_dev);
+ nh->nh_dev = NULL;
+ } endfor_nexthops(fi);
+ dn_fib_info_cnt--;
+ kfree(fi);
+}
+
+void dn_fib_release_info(struct dn_fib_info *fi)
+{
+ write_lock(&dn_fib_info_lock);
+ if (fi && --fi->fib_treeref == 0) {
+ if (fi->fib_next)
+ fi->fib_next->fib_prev = fi->fib_prev;
+ if (fi->fib_prev)
+ fi->fib_prev->fib_next = fi->fib_next;
+ if (fi == dn_fib_info_list)
+ dn_fib_info_list = fi->fib_next;
+ fi->fib_dead = 1;
+ dn_fib_info_put(fi);
+ }
+ write_unlock(&dn_fib_info_lock);
+}
+
+static __inline__ int dn_fib_nh_comp(const struct dn_fib_info *fi, const struct dn_fib_info *ofi)
+{
+ const struct dn_fib_nh *onh = ofi->fib_nh;
+
+ for_nexthops(fi) {
+ if (nh->nh_oif != onh->nh_oif ||
+ nh->nh_gw != onh->nh_gw ||
+ nh->nh_scope != onh->nh_scope ||
+ nh->nh_weight != onh->nh_weight ||
+ ((nh->nh_flags^onh->nh_flags)&~RTNH_F_DEAD))
+ return -1;
+ onh++;
+ } endfor_nexthops(fi);
+ return 0;
+}
+
+static __inline__ struct dn_fib_info *dn_fib_find_info(const struct dn_fib_info *nfi)
+{
+ for_fib_info() {
+ if (fi->fib_nhs != nfi->fib_nhs)
+ continue;
+ if (nfi->fib_protocol == fi->fib_protocol &&
+ nfi->fib_prefsrc == fi->fib_prefsrc &&
+ nfi->fib_priority == fi->fib_priority &&
+ ((nfi->fib_flags^fi->fib_flags)&~RTNH_F_DEAD) == 0 &&
+ (nfi->fib_nhs == 0 || dn_fib_nh_comp(fi, nfi) == 0))
+ return fi;
+ } endfor_fib_info();
+ return NULL;
+}
+
+u16 dn_fib_get_attr16(struct rtattr *attr, int attrlen, int type)
+{
+ while(RTA_OK(attr,attrlen)) {
+ if (attr->rta_type == type)
+ return *(u16*)RTA_DATA(attr);
+ attr = RTA_NEXT(attr, attrlen);
+ }
+
+ return 0;
+}
+
+static int dn_fib_count_nhs(struct rtattr *rta)
+{
+ int nhs = 0;
+ struct rtnexthop *nhp = RTA_DATA(rta);
+ int nhlen = RTA_PAYLOAD(rta);
+
+ while(nhlen >= (int)sizeof(struct rtnexthop)) {
+ if ((nhlen -= nhp->rtnh_len) < 0)
+ return 0;
+ nhs++;
+ nhp = RTNH_NEXT(nhp);
+ }
+
+ return nhs;
+}
+
+static int dn_fib_get_nhs(struct dn_fib_info *fi, const struct rtattr *rta, const struct rtmsg *r)
+{
+ struct rtnexthop *nhp = RTA_DATA(rta);
+ int nhlen = RTA_PAYLOAD(rta);
+
+ change_nexthops(fi) {
+ int attrlen = nhlen - sizeof(struct rtnexthop);
+ if (attrlen < 0 || (nhlen -= nhp->rtnh_len) < 0)
+ return -EINVAL;
+
+ nh->nh_flags = (r->rtm_flags&~0xFF) | nhp->rtnh_flags;
+ nh->nh_oif = nhp->rtnh_ifindex;
+ nh->nh_weight = nhp->rtnh_hops + 1;
+
+ if (attrlen) {
+ nh->nh_gw = dn_fib_get_attr16(RTNH_DATA(nhp), attrlen, RTA_GATEWAY);
+ }
+ nhp = RTNH_NEXT(nhp);
+ } endfor_nexthops(fi);
+
+ return 0;
+}
+
+
+static int dn_fib_check_nh(const struct rtmsg *r, struct dn_fib_info *fi, struct dn_fib_nh *nh)
+{
+ int err;
+
+ if (nh->nh_gw) {
+ struct dn_fib_key key;
+ struct dn_fib_res res;
+
+ if (nh->nh_flags&RTNH_F_ONLINK) {
+ struct net_device *dev;
+
+ if (r->rtm_scope >= RT_SCOPE_LINK)
+ return -EINVAL;
+ if ((dev = __dev_get_by_index(nh->nh_oif)) == NULL)
+ return -ENODEV;
+ if (!(dev->flags&IFF_UP))
+ return -ENETDOWN;
+ nh->nh_dev = dev;
+ atomic_inc(&dev->refcnt);
+ nh->nh_scope = RT_SCOPE_LINK;
+ return 0;
+ }
+
+ memset(&key, 0, sizeof(key));
+ key.dst = nh->nh_gw;
+ key.oif = nh->nh_oif;
+ key.scope = r->rtm_scope + 1;
+
+ if (key.scope < RT_SCOPE_LINK)
+ key.scope = RT_SCOPE_LINK;
+
+ if ((err = dn_fib_lookup(&key, &res)) != 0)
+ return err;
+
+ nh->nh_scope = res.scope;
+ nh->nh_oif = DN_FIB_RES_OIF(res);
+ nh->nh_dev = DN_FIB_RES_DEV(res);
+ if (nh->nh_dev)
+ atomic_inc(&nh->nh_dev->refcnt);
+ dn_fib_res_put(&res);
+ } else {
+ struct net_device *dev;
+
+ if (nh->nh_flags&(RTNH_F_PERVASIVE|RTNH_F_ONLINK))
+ return -EINVAL;
+
+ dev = __dev_get_by_index(nh->nh_oif);
+ if (dev == NULL || dev->dn_ptr == NULL)
+ return -ENODEV;
+ if (!(dev->flags&IFF_UP))
+ return -ENETDOWN;
+ nh->nh_dev = dev;
+ atomic_inc(&nh->nh_dev->refcnt);
+ nh->nh_scope = RT_SCOPE_HOST;
+ }
+
+ return 0;
+}
+
+
+struct dn_fib_info *dn_fib_create_info(const struct rtmsg *r, struct dn_kern_rta *rta, const struct nlmsghdr *nlh, int *errp)
+{
+ int err;
+ struct dn_fib_info *fi = NULL;
+ struct dn_fib_info *ofi;
+ int nhs = 1;
+
+ if (dn_fib_props[r->rtm_type].scope > r->rtm_scope)
+ goto err_inval;
+
+ if (rta->rta_mp) {
+ nhs = dn_fib_count_nhs(rta->rta_mp);
+ if (nhs == 0)
+ goto err_inval;
+ }
+
+ fi = kmalloc(sizeof(*fi)+nhs*sizeof(struct dn_fib_nh), GFP_KERNEL);
+ err = -ENOBUFS;
+ if (fi == NULL)
+ goto failure;
+ memset(fi, 0, sizeof(*fi)+nhs*sizeof(struct dn_fib_nh));
+
+ fi->fib_protocol = r->rtm_protocol;
+ fi->fib_nhs = nhs;
+ fi->fib_flags = r->rtm_flags;
+ if (rta->rta_priority)
+ fi->fib_priority = *rta->rta_priority;
+ if (rta->rta_prefsrc)
+ memcpy(&fi->fib_prefsrc, rta->rta_prefsrc, 2);
+
+ if (rta->rta_mp) {
+ if ((err = dn_fib_get_nhs(fi, rta->rta_mp, r)) != 0)
+ goto failure;
+ if (rta->rta_oif && fi->fib_nh->nh_oif != *rta->rta_oif)
+ goto err_inval;
+ if (rta->rta_gw && memcmp(&fi->fib_nh->nh_gw, rta->rta_gw, 2))
+ goto err_inval;
+ } else {
+ struct dn_fib_nh *nh = fi->fib_nh;
+ if (rta->rta_oif)
+ nh->nh_oif = *rta->rta_oif;
+ if (rta->rta_gw)
+ memcpy(&nh->nh_gw, rta->rta_gw, 2);
+ nh->nh_flags = r->rtm_flags;
+ nh->nh_weight = 1;
+ }
+
+ if (dn_fib_props[r->rtm_type].error) {
+ if (rta->rta_gw || rta->rta_oif || rta->rta_mp)
+ goto err_inval;
+ goto link_it;
+ }
+
+ if (r->rtm_scope > RT_SCOPE_HOST)
+ goto err_inval;
+
+ if (r->rtm_scope == RT_SCOPE_HOST) {
+ struct dn_fib_nh *nh = fi->fib_nh;
+
+ /* Local address is added */
+ if (nhs != 1 || nh->nh_gw)
+ goto err_inval;
+ nh->nh_scope = RT_SCOPE_NOWHERE;
+ nh->nh_dev = dev_get_by_index(fi->fib_nh->nh_oif);
+ err = -ENODEV;
+ if (nh->nh_dev == NULL)
+ goto failure;
+ } else {
+ change_nexthops(fi) {
+ if ((err = dn_fib_check_nh(r, fi, nh)) != 0)
+ goto failure;
+ } endfor_nexthops(fi)
+ }
+
+#if I_GET_AROUND_TO_FIXING_PREFSRC
+ if (fi->fib_prefsrc) {
+ if (r->rtm_type != RTN_LOCAL || rta->rta_dst == NULL ||
+ memcmp(&fi->fib_prefsrc, rta->rta_dst, 2))
+ if (dn_addr_type(fi->fib_prefsrc) != RTN_LOCAL)
+ goto err_inval;
+ }
+#endif
+
+link_it:
+ if ((ofi = dn_fib_find_info(fi)) != NULL) {
+ fi->fib_dead = 1;
+ dn_fib_free_info(fi);
+ ofi->fib_treeref++;
+ return ofi;
+ }
+
+ fi->fib_treeref++;
+ atomic_inc(&fi->fib_clntref);
+ write_lock(&dn_fib_info_lock);
+ fi->fib_next = dn_fib_info_list;
+ fi->fib_prev = NULL;
+ if (dn_fib_info_list)
+ dn_fib_info_list->fib_prev = fi;
+ dn_fib_info_list = fi;
+ dn_fib_info_cnt++;
+ write_unlock(&dn_fib_info_lock);
+ return fi;
+
+err_inval:
+ err = -EINVAL;
+
+failure:
+ *errp = err;
+ if (fi) {
+ fi->fib_dead = 1;
+ dn_fib_free_info(fi);
+ }
+
+ return NULL;
+}
+
+
+void dn_fib_select_multipath(const struct dn_fib_key *key, struct dn_fib_res *res)
+{
+ struct dn_fib_info *fi = res->fi;
+ int w;
+
+ if (fi->fib_power <= 0) {
+ int power = 0;
+ change_nexthops(fi) {
+ if (!(nh->nh_flags&RTNH_F_DEAD)) {
+ power += nh->nh_weight;
+ nh->nh_power = nh->nh_weight;
+ }
+ } endfor_nexthops(fi);
+ fi->fib_power = power;
+ }
+
+ w = jiffies % fi->fib_power;
+
+ change_nexthops(fi) {
+ if (!(nh->nh_flags&RTNH_F_DEAD) && nh->nh_power) {
+ if ((w -= nh->nh_power) <= 0) {
+ nh->nh_power--;
+ fi->fib_power--;
+ res->nh_sel = nhsel;
+ return;
+ }
+ }
+ } endfor_nexthops(fi);
+
+ printk(KERN_DEBUG "DECnet: BUG! dn_fib_select_multipath\n");
+}
+
+
+
+/*
+ * Punt to user via netlink for example, but for now
+ * we just drop it.
+ */
+int dn_fib_rt_message(struct sk_buff *skb)
+{
+ kfree_skb(skb);
+
+ return 0;
+}
+
+
+static int dn_fib_check_attr(struct rtmsg *r, struct rtattr **rta)
+{
+ int i;
+
+ for(i = 1; i <= RTA_MAX; i++) {
+ struct rtattr *attr = rta[i-1];
+ if (attr) {
+ if (RTA_PAYLOAD(attr) < 4 && RTA_PAYLOAD(attr) != 2)
+ return -EINVAL;
+ if (i != RTA_MULTIPATH && i != RTA_METRICS)
+ rta[i-1] = (struct rtattr *)RTA_DATA(attr);
+ }
+ }
+
+ return 0;
+}
+
+int dn_fib_rtm_delroute(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct dn_fib_table *tb;
+ struct rtattr **rta = arg;
+ struct rtmsg *r = NLMSG_DATA(nlh);
+
+ if (dn_fib_check_attr(r, rta))
+ return -EINVAL;
+
+ tb = dn_fib_get_table(r->rtm_table, 0);
+ if (tb)
+ return tb->delete(tb, r, (struct dn_kern_rta *)rta, nlh, &NETLINK_CB(skb));
+
+ return -ESRCH;
+}
+
+int dn_fib_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct dn_fib_table *tb;
+ struct rtattr **rta = arg;
+ struct rtmsg *r = NLMSG_DATA(nlh);
+
+ if (dn_fib_check_attr(r, rta))
+ return -EINVAL;
+
+ tb = dn_fib_get_table(r->rtm_table, 1);
+ if (tb)
+ return tb->insert(tb, r, (struct dn_kern_rta *)rta, nlh, &NETLINK_CB(skb));
+
+ return -ENOBUFS;
+}
+
+
+int dn_fib_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int t;
+ int s_t;
+ struct dn_fib_table *tb;
+
+ if (NLMSG_PAYLOAD(cb->nlh, 0) >= sizeof(struct rtmsg) &&
+ ((struct rtmsg *)NLMSG_DATA(cb->nlh))->rtm_flags&RTM_F_CLONED)
+ return dn_cache_dump(skb, cb);
+
+ s_t = cb->args[0];
+ if (s_t == 0)
+ s_t = cb->args[0] = DN_MIN_TABLE;
+
+ for(t = s_t; t < DN_NUM_TABLES; t++) {
+ if (t < s_t)
+ continue;
+ if (t > s_t)
+ memset(&cb->args[1], 0, sizeof(cb->args)-sizeof(int));
+ tb = dn_fib_get_table(t, 0);
+ if (tb == NULL)
+ continue;
+ if (tb->dump(tb, skb, cb) < 0)
+ break;
+ }
+
+ cb->args[0] = t;
+
+ return skb->len;
+}
+
+int dn_fib_sync_down(dn_address local, struct net_device *dev, int force)
+{
+ int ret = 0;
+ int scope = RT_SCOPE_NOWHERE;
+
+ if (force)
+ scope = -1;
+
+ for_fib_info() {
+ /*
+ * This makes no sense for DECnet.... we will almost
+ * certainly have more than one local address the same
+ * over all our interfaces. It needs thinking about
+ * some more.
+ */
+ if (local && fi->fib_prefsrc == local) {
+ fi->fib_flags |= RTNH_F_DEAD;
+ ret++;
+ } else if (dev && fi->fib_nhs) {
+ int dead = 0;
+
+ change_nexthops(fi) {
+ if (nh->nh_flags&RTNH_F_DEAD)
+ dead++;
+ else if (nh->nh_dev == dev &&
+ nh->nh_scope != scope) {
+ nh->nh_flags |= RTNH_F_DEAD;
+ fi->fib_power -= nh->nh_power;
+ nh->nh_power = 0;
+ dead++;
+ }
+ } endfor_nexthops(fi)
+ if (dead == fi->fib_nhs) {
+ fi->fib_flags |= RTNH_F_DEAD;
+ ret++;
+ }
+ }
+ } endfor_fib_info();
+ return ret;
+}
+
+
+int dn_fib_sync_up(struct net_device *dev)
+{
+ int ret = 0;
+
+ if (!(dev->flags&IFF_UP))
+ return 0;
+
+ for_fib_info() {
+ int alive = 0;
+
+ change_nexthops(fi) {
+ if (!(nh->nh_flags&RTNH_F_DEAD)) {
+ alive++;
+ continue;
+ }
+ if (nh->nh_dev == NULL || !(nh->nh_dev->flags&IFF_UP))
+ continue;
+ if (nh->nh_dev != dev || dev->dn_ptr == NULL)
+ continue;
+ alive++;
+ nh->nh_power = 0;
+ nh->nh_flags &= ~RTNH_F_DEAD;
+ } endfor_nexthops(fi);
+
+ if (alive == fi->fib_nhs) {
+ fi->fib_flags &= ~RTNH_F_DEAD;
+ ret++;
+ }
+ } endfor_fib_info();
+ return ret;
+}
+
+void dn_fib_flush(void)
+{
+ int flushed = 0;
+ struct dn_fib_table *tb;
+ int id;
+
+ for(id = DN_NUM_TABLES; id > 0; id--) {
+ if ((tb = dn_fib_get_table(id, 0)) == NULL)
+ continue;
+ flushed += tb->flush(tb);
+ }
+
+ if (flushed)
+ dn_rt_cache_flush(-1);
+}
+
+int dn_fib_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ switch(cmd) {
+ case SIOCADDRT:
+ case SIOCDELRT:
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+#ifdef CONFIG_PROC_FS
+
+static int decnet_rt_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int first = offset / 128;
+ char *ptr = buffer;
+ int count = (length + 127) / 128;
+ int len;
+ int i;
+ struct dn_fib_table *tb;
+
+ *start = buffer + (offset % 128);
+
+ if (--first < 0) {
+ sprintf(buffer, "%-127s\n", "Iface\tDest\tGW \tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT");
+ --count;
+ ptr += 128;
+ first = 0;
+ }
+
+
+ for(i = DN_MIN_TABLE; (i <= DN_NUM_TABLES) && (count > 0); i++) {
+ if ((tb = dn_fib_get_table(i, 0)) != NULL) {
+ int n = tb->get_info(tb, ptr, first, count);
+ count -= n;
+ ptr += n * 128;
+ }
+ }
+
+ len = ptr - *start;
+ if (len >= length)
+ return length;
+ if (len >= 0)
+ return len;
+
+ return 0;
+}
+#endif /* CONFIG_PROC_FS */
+
+void __exit dn_fib_cleanup(void)
+{
+ proc_net_remove("decnet_route");
+
+ dn_fib_table_cleanup();
+ dn_fib_rules_cleanup();
+}
+
+
+void __init dn_fib_init(void)
+{
+
+#ifdef CONFIG_PROC_FS
+ proc_net_create("decnet_route", 0, decnet_rt_get_info);
+#endif
+
+ dn_fib_table_init();
+ dn_fib_rules_init();
+}
+
+
diff --git a/uClinux-2.4.31-uc0/net/decnet/dn_neigh.c b/uClinux-2.4.31-uc0/net/decnet/dn_neigh.c
new file mode 100644
index 0000000..8bfebe6
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/dn_neigh.c
@@ -0,0 +1,606 @@
+/*
+ * DECnet An implementation of the DECnet protocol suite for the LINUX
+ * operating system. DECnet is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * DECnet Neighbour Functions (Adjacency Database and
+ * On-Ethernet Cache)
+ *
+ * Author: Steve Whitehouse <SteveW@ACM.org>
+ *
+ *
+ * Changes:
+ * Steve Whitehouse : Fixed router listing routine
+ * Steve Whitehouse : Added error_report functions
+ * Steve Whitehouse : Added default router detection
+ * Steve Whitehouse : Hop counts in outgoing messages
+ * Steve Whitehouse : Fixed src/dst in outgoing messages so
+ * forwarding now stands a good chance of
+ * working.
+ * Steve Whitehouse : Fixed neighbour states (for now anyway).
+ * Steve Whitehouse : Made error_report functions dummies. This
+ * is not the right place to return skbs.
+ * Harald Welte : Port to DaveM's generalized ncache from 2.6.x
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/net.h>
+#include <linux/module.h>
+#include <linux/socket.h>
+#include <linux/if_arp.h>
+#include <linux/if_ether.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/string.h>
+#include <linux/netfilter_decnet.h>
+#include <linux/spinlock.h>
+#include <linux/seq_file.h>
+#include <linux/jhash.h>
+#include <asm/atomic.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/dn.h>
+#include <net/dn_dev.h>
+#include <net/dn_neigh.h>
+#include <net/dn_route.h>
+
+static u32 dn_neigh_hash(const void *pkey, const struct net_device *dev);
+static int dn_neigh_construct(struct neighbour *);
+static void dn_long_error_report(struct neighbour *, struct sk_buff *);
+static void dn_short_error_report(struct neighbour *, struct sk_buff *);
+static int dn_long_output(struct sk_buff *);
+static int dn_short_output(struct sk_buff *);
+static int dn_phase3_output(struct sk_buff *);
+
+
+/*
+ * For talking to broadcast devices: Ethernet & PPP
+ */
+static struct neigh_ops dn_long_ops = {
+ family: AF_DECnet,
+ error_report: dn_long_error_report,
+ output: dn_long_output,
+ connected_output: dn_long_output,
+ hh_output: dev_queue_xmit,
+ queue_xmit: dev_queue_xmit,
+};
+
+/*
+ * For talking to pointopoint and multidrop devices: DDCMP and X.25
+ */
+static struct neigh_ops dn_short_ops = {
+ family: AF_DECnet,
+ error_report: dn_short_error_report,
+ output: dn_short_output,
+ connected_output: dn_short_output,
+ hh_output: dev_queue_xmit,
+ queue_xmit: dev_queue_xmit,
+};
+
+/*
+ * For talking to DECnet phase III nodes
+ */
+static struct neigh_ops dn_phase3_ops = {
+ family: AF_DECnet,
+ error_report: dn_short_error_report, /* Can use short version here */
+ output: dn_phase3_output,
+ connected_output: dn_phase3_output,
+ hh_output: dev_queue_xmit,
+ queue_xmit: dev_queue_xmit
+};
+
+struct neigh_table dn_neigh_table = {
+ family: PF_DECnet,
+ entry_size: sizeof(struct dn_neigh),
+ key_len: sizeof(dn_address),
+ hash: dn_neigh_hash,
+ constructor: dn_neigh_construct,
+ id: "dn_neigh_cache",
+ parms: {
+ tbl: &dn_neigh_table,
+ entries: 0,
+ base_reachable_time: 30 * HZ,
+ retrans_time: 1 * HZ,
+ gc_staletime: 60 * HZ,
+ reachable_time: 30 * HZ,
+ delay_probe_time: 5 * HZ,
+ queue_len: 3,
+ ucast_probes: 0,
+ app_probes: 0,
+ mcast_probes: 0,
+ anycast_delay: 0,
+ proxy_delay: 0,
+ proxy_qlen: 0,
+ locktime: 1 * HZ,
+ },
+ gc_interval: 30 * HZ,
+ gc_thresh1: 128,
+ gc_thresh2: 512,
+ gc_thresh3: 1024,
+};
+
+static u32 dn_neigh_hash(const void *pkey, const struct net_device *dev)
+{
+ return jhash_2words(*(dn_address *)pkey, 0, dn_neigh_table.hash_rnd);
+}
+
+static int dn_neigh_construct(struct neighbour *neigh)
+{
+ struct net_device *dev = neigh->dev;
+ struct dn_neigh *dn = (struct dn_neigh *)neigh;
+ struct dn_dev *dn_db = (struct dn_dev *)dev->dn_ptr;
+
+ if (dn_db == NULL)
+ return -EINVAL;
+
+ if (dn_db->neigh_parms)
+ neigh->parms = dn_db->neigh_parms;
+
+ if (dn_db->use_long)
+ neigh->ops = &dn_long_ops;
+ else
+ neigh->ops = &dn_short_ops;
+
+ if (dn->flags & DN_NDFLAG_P3)
+ neigh->ops = &dn_phase3_ops;
+
+ neigh->nud_state = NUD_NOARP;
+ neigh->output = neigh->ops->connected_output;
+
+ if ((dev->type == ARPHRD_IPGRE) || (dev->flags & IFF_POINTOPOINT))
+ memcpy(neigh->ha, dev->broadcast, dev->addr_len);
+ else if ((dev->type == ARPHRD_ETHER) || (dev->type == ARPHRD_LOOPBACK))
+ dn_dn2eth(neigh->ha, dn->addr);
+ else {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "Trying to create neigh for hw %d\n", dev->type);
+ return -EINVAL;
+ }
+
+ dn->blksize = 230;
+
+ return 0;
+}
+
+static void dn_long_error_report(struct neighbour *neigh, struct sk_buff *skb)
+{
+ printk(KERN_DEBUG "dn_long_error_report: called\n");
+ kfree_skb(skb);
+}
+
+
+static void dn_short_error_report(struct neighbour *neigh, struct sk_buff *skb)
+{
+ printk(KERN_DEBUG "dn_short_error_report: called\n");
+ kfree_skb(skb);
+}
+
+static int dn_neigh_output_packet(struct sk_buff *skb)
+{
+ struct dst_entry *dst = skb->dst;
+ struct neighbour *neigh = dst->neighbour;
+ struct net_device *dev = neigh->dev;
+
+ if (!dev->hard_header || dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len) >= 0)
+ return neigh->ops->queue_xmit(skb);
+
+ if (net_ratelimit())
+ printk(KERN_DEBUG "dn_neigh_output_packet: oops, can't send packet\n");
+
+ kfree_skb(skb);
+ return -EINVAL;
+}
+
+static int dn_long_output(struct sk_buff *skb)
+{
+ struct dst_entry *dst = skb->dst;
+ struct neighbour *neigh = dst->neighbour;
+ struct net_device *dev = neigh->dev;
+ int headroom = dev->hard_header_len + sizeof(struct dn_long_packet) + 3;
+ unsigned char *data;
+ struct dn_long_packet *lp;
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+
+
+ if (skb_headroom(skb) < headroom) {
+ struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom);
+ if (skb2 == NULL) {
+ if (net_ratelimit())
+ printk(KERN_CRIT "dn_long_output: no memory\n");
+ kfree_skb(skb);
+ return -ENOBUFS;
+ }
+ kfree_skb(skb);
+ skb = skb2;
+ if (net_ratelimit())
+ printk(KERN_INFO "dn_long_output: Increasing headroom\n");
+ }
+
+ data = skb_push(skb, sizeof(struct dn_long_packet) + 3);
+ lp = (struct dn_long_packet *)(data+3);
+
+ *((unsigned short *)data) = dn_htons(skb->len - 2);
+ *(data + 2) = 1 | DN_RT_F_PF; /* Padding */
+
+ lp->msgflg = DN_RT_PKT_LONG|(cb->rt_flags&(DN_RT_F_IE|DN_RT_F_RQR|DN_RT_F_RTS));
+ lp->d_area = lp->d_subarea = 0;
+ dn_dn2eth(lp->d_id, dn_ntohs(cb->dst));
+ lp->s_area = lp->s_subarea = 0;
+ dn_dn2eth(lp->s_id, dn_ntohs(cb->src));
+ lp->nl2 = 0;
+ lp->visit_ct = cb->hops & 0x3f;
+ lp->s_class = 0;
+ lp->pt = 0;
+
+ skb->nh.raw = skb->data;
+
+ return NF_HOOK(PF_DECnet, NF_DN_POST_ROUTING, skb, NULL, neigh->dev, dn_neigh_output_packet);
+}
+
+static int dn_short_output(struct sk_buff *skb)
+{
+ struct dst_entry *dst = skb->dst;
+ struct neighbour *neigh = dst->neighbour;
+ struct net_device *dev = neigh->dev;
+ int headroom = dev->hard_header_len + sizeof(struct dn_short_packet) + 2;
+ struct dn_short_packet *sp;
+ unsigned char *data;
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+
+
+ if (skb_headroom(skb) < headroom) {
+ struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom);
+ if (skb2 == NULL) {
+ if (net_ratelimit())
+ printk(KERN_CRIT "dn_short_output: no memory\n");
+ kfree_skb(skb);
+ return -ENOBUFS;
+ }
+ kfree_skb(skb);
+ skb = skb2;
+ if (net_ratelimit())
+ printk(KERN_INFO "dn_short_output: Increasing headroom\n");
+ }
+
+ data = skb_push(skb, sizeof(struct dn_short_packet) + 2);
+ *((unsigned short *)data) = dn_htons(skb->len - 2);
+ sp = (struct dn_short_packet *)(data+2);
+
+ sp->msgflg = DN_RT_PKT_SHORT|(cb->rt_flags&(DN_RT_F_RQR|DN_RT_F_RTS));
+ sp->dstnode = cb->dst;
+ sp->srcnode = cb->src;
+ sp->forward = cb->hops & 0x3f;
+
+ skb->nh.raw = skb->data;
+
+ return NF_HOOK(PF_DECnet, NF_DN_POST_ROUTING, skb, NULL, neigh->dev, dn_neigh_output_packet);
+}
+
+/*
+ * Phase 3 output is the same is short output, execpt that
+ * it clears the area bits before transmission.
+ */
+static int dn_phase3_output(struct sk_buff *skb)
+{
+ struct dst_entry *dst = skb->dst;
+ struct neighbour *neigh = dst->neighbour;
+ struct net_device *dev = neigh->dev;
+ int headroom = dev->hard_header_len + sizeof(struct dn_short_packet) + 2;
+ struct dn_short_packet *sp;
+ unsigned char *data;
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+
+ if (skb_headroom(skb) < headroom) {
+ struct sk_buff *skb2 = skb_realloc_headroom(skb, headroom);
+ if (skb2 == NULL) {
+ if (net_ratelimit())
+ printk(KERN_CRIT "dn_phase3_output: no memory\n");
+ kfree_skb(skb);
+ return -ENOBUFS;
+ }
+ kfree_skb(skb);
+ skb = skb2;
+ if (net_ratelimit())
+ printk(KERN_INFO "dn_phase3_output: Increasing headroom\n");
+ }
+
+ data = skb_push(skb, sizeof(struct dn_short_packet) + 2);
+ *((unsigned short *)data) = dn_htons(skb->len - 2);
+ sp = (struct dn_short_packet *)(data + 2);
+
+ sp->msgflg = DN_RT_PKT_SHORT|(cb->rt_flags&(DN_RT_F_RQR|DN_RT_F_RTS));
+ sp->dstnode = cb->dst & dn_htons(0x03ff);
+ sp->srcnode = cb->src & dn_htons(0x03ff);
+ sp->forward = cb->hops & 0x3f;
+
+ skb->nh.raw = skb->data;
+
+ return NF_HOOK(PF_DECnet, NF_DN_POST_ROUTING, skb, NULL, neigh->dev, dn_neigh_output_packet);
+}
+
+/*
+ * Any traffic on a pointopoint link causes the timer to be reset
+ * for the entry in the neighbour table.
+ */
+void dn_neigh_pointopoint_notify(struct sk_buff *skb)
+{
+ return;
+}
+
+/*
+ * Pointopoint link receives a hello message
+ */
+void dn_neigh_pointopoint_hello(struct sk_buff *skb)
+{
+ kfree_skb(skb);
+}
+
+/*
+ * Ethernet router hello message received
+ */
+int dn_neigh_router_hello(struct sk_buff *skb)
+{
+ struct rtnode_hello_message *msg = (struct rtnode_hello_message *)skb->data;
+
+ struct neighbour *neigh;
+ struct dn_neigh *dn;
+ struct dn_dev *dn_db;
+ dn_address src;
+
+ src = dn_htons(dn_eth2dn(msg->id));
+
+ neigh = __neigh_lookup(&dn_neigh_table, &src, skb->dev, 1);
+
+ dn = (struct dn_neigh *)neigh;
+
+ if (neigh) {
+ write_lock(&neigh->lock);
+
+ neigh->used = jiffies;
+ dn_db = (struct dn_dev *)neigh->dev->dn_ptr;
+
+ if (!(neigh->nud_state & NUD_PERMANENT)) {
+ neigh->updated = jiffies;
+
+ if (neigh->dev->type == ARPHRD_ETHER)
+ memcpy(neigh->ha, &skb->mac.ethernet->h_source, ETH_ALEN);
+
+ dn->blksize = dn_ntohs(msg->blksize);
+ dn->priority = msg->priority;
+
+ dn->flags &= ~DN_NDFLAG_P3;
+
+ switch(msg->iinfo & DN_RT_INFO_TYPE) {
+ case DN_RT_INFO_L1RT:
+ dn->flags &=~DN_NDFLAG_R2;
+ dn->flags |= DN_NDFLAG_R1;
+ break;
+ case DN_RT_INFO_L2RT:
+ dn->flags |= DN_NDFLAG_R2;
+ }
+ }
+
+ if (!dn_db->router) {
+ dn_db->router = neigh_clone(neigh);
+ } else {
+ if (msg->priority > ((struct dn_neigh *)dn_db->router)->priority)
+ neigh_release(xchg(&dn_db->router, neigh_clone(neigh)));
+ }
+ write_unlock(&neigh->lock);
+ neigh_release(neigh);
+ }
+
+ kfree_skb(skb);
+ return 0;
+}
+
+/*
+ * Endnode hello message received
+ */
+int dn_neigh_endnode_hello(struct sk_buff *skb)
+{
+ struct endnode_hello_message *msg = (struct endnode_hello_message *)skb->data;
+ struct neighbour *neigh;
+ struct dn_neigh *dn;
+ dn_address src;
+
+ src = dn_htons(dn_eth2dn(msg->id));
+
+ neigh = __neigh_lookup(&dn_neigh_table, &src, skb->dev, 1);
+
+ dn = (struct dn_neigh *)neigh;
+
+ if (neigh) {
+ write_lock(&neigh->lock);
+
+ neigh->used = jiffies;
+
+ if (!(neigh->nud_state & NUD_PERMANENT)) {
+ neigh->updated = jiffies;
+
+ if (neigh->dev->type == ARPHRD_ETHER)
+ memcpy(neigh->ha, &skb->mac.ethernet->h_source, ETH_ALEN);
+ dn->flags &= ~(DN_NDFLAG_R1 | DN_NDFLAG_R2);
+ dn->blksize = dn_ntohs(msg->blksize);
+ dn->priority = 0;
+ }
+
+ write_unlock(&neigh->lock);
+ neigh_release(neigh);
+ }
+
+ kfree_skb(skb);
+ return 0;
+}
+
+
+#ifdef CONFIG_DECNET_ROUTER
+static char *dn_find_slot(char *base, int max, int priority)
+{
+ int i;
+ unsigned char *min = NULL;
+
+ base += 6; /* skip first id */
+
+ for(i = 0; i < max; i++) {
+ if (!min || (*base < *min))
+ min = base;
+ base += 7; /* find next priority */
+ }
+
+ if (!min)
+ return NULL;
+
+ return (*min < priority) ? (min - 6) : NULL;
+}
+
+struct elist_cb_state {
+ struct net_device *dev;
+ unsigned char *ptr;
+ unsigned char *rs;
+ int t, n;
+};
+
+static void neigh_elist_cb(struct neighbour *neigh, void *_info)
+{
+ struct elist_cb_state *s = _info;
+ struct dn_dev *dn_db;
+ struct dn_neigh *dn;
+
+ if (neigh->dev != s->dev)
+ return;
+
+ dn = (struct dn_neigh *) neigh;
+ if (!(dn->flags & (DN_NDFLAG_R1|DN_NDFLAG_R2)))
+ return;
+
+ dn_db = (struct dn_dev *) s->dev->dn_ptr;
+ if (dn_db->parms.forwarding == 1 && (dn->flags & DN_NDFLAG_R2))
+ return;
+
+ if (s->t == s->n)
+ s->rs = dn_find_slot(s->ptr, s->n, dn->priority);
+ else
+ s->t++;
+ if (s->rs == NULL)
+ return;
+
+ dn_dn2eth(s->rs, dn->addr);
+ s->rs += 6;
+ read_lock(&neigh->lock);
+ *(s->rs) = neigh->nud_state & NUD_CONNECTED ? 0x80 : 0x0;
+ read_unlock(&neigh->lock);
+ *(s->rs) |= dn->priority;
+ s->rs++;
+}
+
+int dn_neigh_elist(struct net_device *dev, unsigned char *ptr, int n)
+{
+ struct elist_cb_state state;
+
+ state.dev = dev;
+ state.t = 0;
+ state.n = n;
+ state.ptr = ptr;
+ state.rs = ptr;
+
+ neigh_for_each(&dn_neigh_table, neigh_elist_cb, &state);
+
+ return state.t;
+}
+
+#endif /* CONFIG_DECNET_ROUTER */
+
+
+
+#ifdef CONFIG_PROC_FS
+
+static inline void dn_neigh_format_entry(struct seq_file *seq,
+ struct neighbour *n)
+{
+ struct dn_neigh *dn = (struct dn_neigh *) n;
+ char buf[DN_ASCBUF_LEN];
+
+ read_lock(&n->lock);
+ seq_printf(seq, "%-7s %s%s%s %02x %02d %07ld %-8s\n",
+ dn_addr2asc(dn_ntohs(dn->addr), buf),
+ (dn->flags&DN_NDFLAG_R1) ? "1" : "-",
+ (dn->flags&DN_NDFLAG_R2) ? "2" : "-",
+ (dn->flags&DN_NDFLAG_P3) ? "3" : "-",
+ dn->n.nud_state,
+ atomic_read(&dn->n.refcnt),
+ dn->blksize,
+ (dn->n.dev) ? dn->n.dev->name : "?");
+ read_unlock(&n->lock);
+}
+
+static int dn_neigh_seq_show(struct seq_file *seq, void *v)
+{
+ if (v == SEQ_START_TOKEN) {
+ seq_puts(seq, "Addr Flags State Use Blksize Dev\n");
+ } else {
+ dn_neigh_format_entry(seq, v);
+ }
+
+ return 0;
+}
+
+static void *dn_neigh_seq_start(struct seq_file *seq, loff_t *pos)
+{
+ return neigh_seq_start(seq, pos, &dn_neigh_table,
+ NEIGH_SEQ_NEIGH_ONLY);
+}
+
+static struct seq_operations dn_neigh_seq_ops = {
+ .start = dn_neigh_seq_start,
+ .next = neigh_seq_next,
+ .stop = neigh_seq_stop,
+ .show = dn_neigh_seq_show,
+};
+
+static int dn_neigh_seq_open(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq;
+ int rc = -ENOMEM;
+ struct neigh_seq_state *s = kmalloc(sizeof(*s), GFP_KERNEL);
+
+ if (!s)
+ goto out;
+
+ memset(s, 0, sizeof(*s));
+ rc = seq_open(file, &dn_neigh_seq_ops);
+ if (rc)
+ goto out_kfree;
+
+ seq = file->private_data;
+ seq->private = s;
+ memset(s, 0, sizeof(*s));
+out:
+ return rc;
+out_kfree:
+ kfree(s);
+ goto out;
+}
+
+static struct file_operations dn_neigh_seq_fops = {
+ .owner = THIS_MODULE,
+ .open = dn_neigh_seq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release_private,
+};
+
+#endif
+
+void __init dn_neigh_init(void)
+{
+ neigh_table_init(&dn_neigh_table);
+ proc_net_fops_create("decnet_neigh", S_IRUGO, &dn_neigh_seq_fops);
+}
+
+void __exit dn_neigh_cleanup(void)
+{
+ proc_net_remove("decnet_neigh");
+ neigh_table_clear(&dn_neigh_table);
+}
diff --git a/uClinux-2.4.31-uc0/net/decnet/dn_nsp_in.c b/uClinux-2.4.31-uc0/net/decnet/dn_nsp_in.c
new file mode 100644
index 0000000..cd13fe5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/dn_nsp_in.c
@@ -0,0 +1,910 @@
+/*
+ * DECnet An implementation of the DECnet protocol suite for the LINUX
+ * operating system. DECnet is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * DECnet Network Services Protocol (Input)
+ *
+ * Author: Eduardo Marcelo Serrat <emserrat@geocities.com>
+ *
+ * Changes:
+ *
+ * Steve Whitehouse: Split into dn_nsp_in.c and dn_nsp_out.c from
+ * original dn_nsp.c.
+ * Steve Whitehouse: Updated to work with my new routing architecture.
+ * Steve Whitehouse: Add changes from Eduardo Serrat's patches.
+ * Steve Whitehouse: Put all ack handling code in a common routine.
+ * Steve Whitehouse: Put other common bits into dn_nsp_rx()
+ * Steve Whitehouse: More checks on skb->len to catch bogus packets
+ * Fixed various race conditions and possible nasties.
+ * Steve Whitehouse: Now handles returned conninit frames.
+ * David S. Miller: New socket locking
+ * Steve Whitehouse: Fixed lockup when socket filtering was enabled.
+ * Paul Koning: Fix to push CC sockets into RUN when acks are
+ * received.
+ * Steve Whitehouse:
+ * Patrick Caulfield: Checking conninits for correctness & sending of error
+ * responses.
+ * Steve Whitehouse: Added backlog congestion level return codes.
+ * Patrick Caulfield:
+ * Steve Whitehouse: Added flow control support (outbound)
+ */
+
+/******************************************************************************
+ (c) 1995-1998 E.M. Serrat emserrat@geocities.com
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*******************************************************************************/
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/inet.h>
+#include <linux/route.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/termios.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/netfilter_decnet.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/dn_nsp.h>
+#include <net/dn_dev.h>
+#include <net/dn_route.h>
+
+extern int decnet_log_martians;
+
+static void dn_log_martian(struct sk_buff *skb, const char *msg)
+{
+ if (decnet_log_martians && net_ratelimit()) {
+ char *devname = skb->dev ? skb->dev->name : "???";
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ printk(KERN_INFO "DECnet: Martian packet (%s) dev=%s src=0x%04hx dst=0x%04hx srcport=0x%04hx dstport=0x%04hx\n", msg, devname, cb->src, cb->dst, cb->src_port, cb->dst_port);
+ }
+}
+
+/*
+ * For this function we've flipped the cross-subchannel bit
+ * if the message is an otherdata or linkservice message. Thus
+ * we can use it to work out what to update.
+ */
+static void dn_ack(struct sock *sk, struct sk_buff *skb, unsigned short ack)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ unsigned short type = ((ack >> 12) & 0x0003);
+ int wakeup = 0;
+
+ switch(type) {
+ case 0: /* ACK - Data */
+ if (after(ack, scp->ackrcv_dat)) {
+ scp->ackrcv_dat = ack & 0x0fff;
+ wakeup |= dn_nsp_check_xmit_queue(sk, skb, &scp->data_xmit_queue, ack);
+ }
+ break;
+ case 1: /* NAK - Data */
+ break;
+ case 2: /* ACK - OtherData */
+ if (after(ack, scp->ackrcv_oth)) {
+ scp->ackrcv_oth = ack & 0x0fff;
+ wakeup |= dn_nsp_check_xmit_queue(sk, skb, &scp->other_xmit_queue, ack);
+ }
+ break;
+ case 3: /* NAK - OtherData */
+ break;
+ }
+
+ if (wakeup && !sk->dead)
+ sk->state_change(sk);
+}
+
+/*
+ * This function is a universal ack processor.
+ */
+static int dn_process_ack(struct sock *sk, struct sk_buff *skb, int oth)
+{
+ unsigned short *ptr = (unsigned short *)skb->data;
+ int len = 0;
+ unsigned short ack;
+
+ if (skb->len < 2)
+ return len;
+
+ if ((ack = dn_ntohs(*ptr)) & 0x8000) {
+ skb_pull(skb, 2);
+ ptr++;
+ len += 2;
+ if ((ack & 0x4000) == 0) {
+ if (oth)
+ ack ^= 0x2000;
+ dn_ack(sk, skb, ack);
+ }
+ }
+
+ if (skb->len < 2)
+ return len;
+
+ if ((ack = dn_ntohs(*ptr)) & 0x8000) {
+ skb_pull(skb, 2);
+ len += 2;
+ if ((ack & 0x4000) == 0) {
+ if (oth)
+ ack ^= 0x2000;
+ dn_ack(sk, skb, ack);
+ }
+ }
+
+ return len;
+}
+
+
+/**
+ * dn_check_idf - Check an image data field format is correct.
+ * @pptr: Pointer to pointer to image data
+ * @len: Pointer to length of image data
+ * @max: The maximum allowed length of the data in the image data field
+ * @follow_on: Check that this many bytes exist beyond the end of the image data
+ *
+ * Returns: 0 if ok, -1 on error
+ */
+static inline int dn_check_idf(unsigned char **pptr, int *len, unsigned char max, unsigned char follow_on)
+{
+ unsigned char *ptr = *pptr;
+ unsigned char flen = *ptr++;
+
+ (*len)--;
+ if (flen > max)
+ return -1;
+ if ((flen + follow_on) > *len)
+ return -1;
+
+ *len -= flen;
+ *pptr = ptr + flen;
+ return 0;
+}
+
+/*
+ * Table of reason codes to pass back to node which sent us a badly
+ * formed message, plus text messages for the log. A zero entry in
+ * the reason field means "don't reply" otherwise a disc init is sent with
+ * the specified reason code.
+ */
+static struct {
+ unsigned short reason;
+ const char *text;
+} ci_err_table[] = {
+ { 0, "CI: Truncated message" },
+ { NSP_REASON_ID, "CI: Destination username error" },
+ { NSP_REASON_ID, "CI: Destination username type" },
+ { NSP_REASON_US, "CI: Source username error" },
+ { 0, "CI: Truncated at menuver" },
+ { 0, "CI: Truncated before access or user data" },
+ { NSP_REASON_IO, "CI: Access data format error" },
+ { NSP_REASON_IO, "CI: User data format error" }
+};
+
+/*
+ * This function uses a slightly different lookup method
+ * to find its sockets, since it searches on object name/number
+ * rather than port numbers. Various tests are done to ensure that
+ * the incoming data is in the correct format before it is queued to
+ * a socket.
+ */
+static struct sock *dn_find_listener(struct sk_buff *skb, unsigned short *reason)
+{
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ struct nsp_conn_init_msg *msg = (struct nsp_conn_init_msg *)skb->data;
+ struct sockaddr_dn dstaddr;
+ struct sockaddr_dn srcaddr;
+ unsigned char type = 0;
+ int dstlen;
+ int srclen;
+ unsigned char *ptr;
+ int len;
+ int err = 0;
+ unsigned char menuver;
+
+ memset(&dstaddr, 0, sizeof(struct sockaddr_dn));
+ memset(&srcaddr, 0, sizeof(struct sockaddr_dn));
+
+ /*
+ * 1. Decode & remove message header
+ */
+ cb->src_port = msg->srcaddr;
+ cb->dst_port = msg->dstaddr;
+ cb->services = msg->services;
+ cb->info = msg->info;
+ cb->segsize = dn_ntohs(msg->segsize);
+
+ if (skb->len < sizeof(*msg))
+ goto err_out;
+
+ skb_pull(skb, sizeof(*msg));
+
+ len = skb->len;
+ ptr = skb->data;
+
+ /*
+ * 2. Check destination end username format
+ */
+ dstlen = dn_username2sockaddr(ptr, len, &dstaddr, &type);
+ err++;
+ if (dstlen < 0)
+ goto err_out;
+
+ err++;
+ if (type > 1)
+ goto err_out;
+
+ len -= dstlen;
+ ptr += dstlen;
+
+ /*
+ * 3. Check source end username format
+ */
+ srclen = dn_username2sockaddr(ptr, len, &srcaddr, &type);
+ err++;
+ if (srclen < 0)
+ goto err_out;
+
+ len -= srclen;
+ ptr += srclen;
+ err++;
+ if (len < 1)
+ goto err_out;
+
+ menuver = *ptr;
+ ptr++;
+ len--;
+
+ /*
+ * 4. Check that optional data actually exists if menuver says it does
+ */
+ err++;
+ if ((menuver & (DN_MENUVER_ACC | DN_MENUVER_USR)) && (len < 1))
+ goto err_out;
+
+ /*
+ * 5. Check optional access data format
+ */
+ err++;
+ if (menuver & DN_MENUVER_ACC) {
+ if (dn_check_idf(&ptr, &len, 39, 1))
+ goto err_out;
+ if (dn_check_idf(&ptr, &len, 39, 1))
+ goto err_out;
+ if (dn_check_idf(&ptr, &len, 39, (menuver & DN_MENUVER_USR) ? 1 : 0))
+ goto err_out;
+ }
+
+ /*
+ * 6. Check optional user data format
+ */
+ err++;
+ if (menuver & DN_MENUVER_USR) {
+ if (dn_check_idf(&ptr, &len, 16, 0))
+ goto err_out;
+ }
+
+ /*
+ * 7. Look up socket based on destination end username
+ */
+ return dn_sklist_find_listener(&dstaddr);
+err_out:
+ dn_log_martian(skb, ci_err_table[err].text);
+ *reason = ci_err_table[err].reason;
+ return NULL;
+}
+
+
+static void dn_nsp_conn_init(struct sock *sk, struct sk_buff *skb)
+{
+ if (sk->ack_backlog >= sk->max_ack_backlog) {
+ kfree_skb(skb);
+ return;
+ }
+
+ sk->ack_backlog++;
+ skb_queue_tail(&sk->receive_queue, skb);
+ sk->state_change(sk);
+}
+
+static void dn_nsp_conn_conf(struct sock *sk, struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ struct dn_scp *scp = DN_SK(sk);
+ unsigned char *ptr;
+
+ if (skb->len < 4)
+ goto out;
+
+ ptr = skb->data;
+ cb->services = *ptr++;
+ cb->info = *ptr++;
+ cb->segsize = dn_ntohs(*(__u16 *)ptr);
+
+ if ((scp->state == DN_CI) || (scp->state == DN_CD)) {
+ scp->persist = 0;
+ scp->addrrem = cb->src_port;
+ sk->state = TCP_ESTABLISHED;
+ scp->state = DN_RUN;
+ scp->services_rem = cb->services;
+ scp->info_rem = cb->info;
+ scp->segsize_rem = cb->segsize;
+
+ if ((scp->services_rem & NSP_FC_MASK) == NSP_FC_NONE)
+ scp->max_window = decnet_no_fc_max_cwnd;
+
+ if (skb->len > 0) {
+ unsigned char dlen = *skb->data;
+ if ((dlen <= 16) && (dlen <= skb->len)) {
+ scp->conndata_in.opt_optl = dlen;
+ memcpy(scp->conndata_in.opt_data, skb->data + 1, dlen);
+ }
+ }
+ dn_nsp_send_link(sk, DN_NOCHANGE, 0);
+ if (!sk->dead)
+ sk->state_change(sk);
+ }
+
+out:
+ kfree_skb(skb);
+}
+
+static void dn_nsp_conn_ack(struct sock *sk, struct sk_buff *skb)
+{
+ struct dn_scp *scp = DN_SK(sk);
+
+ if (scp->state == DN_CI) {
+ scp->state = DN_CD;
+ scp->persist = 0;
+ }
+
+ kfree_skb(skb);
+}
+
+static void dn_nsp_disc_init(struct sock *sk, struct sk_buff *skb)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ unsigned short reason;
+
+ if (skb->len < 2)
+ goto out;
+
+ reason = dn_ntohs(*(__u16 *)skb->data);
+ skb_pull(skb, 2);
+
+ scp->discdata_in.opt_status = reason;
+ scp->discdata_in.opt_optl = 0;
+ memset(scp->discdata_in.opt_data, 0, 16);
+
+ if (skb->len > 0) {
+ unsigned char dlen = *skb->data;
+ if ((dlen <= 16) && (dlen <= skb->len)) {
+ scp->discdata_in.opt_optl = dlen;
+ memcpy(scp->discdata_in.opt_data, skb->data + 1, dlen);
+ }
+ }
+
+ scp->addrrem = cb->src_port;
+ sk->state = TCP_CLOSE;
+
+ switch(scp->state) {
+ case DN_CI:
+ case DN_CD:
+ scp->state = DN_RJ;
+ break;
+ case DN_RUN:
+ sk->shutdown |= SHUTDOWN_MASK;
+ scp->state = DN_DN;
+ break;
+ case DN_DI:
+ scp->state = DN_DIC;
+ break;
+ }
+
+ if (!sk->dead) {
+ if (sk->socket->state != SS_UNCONNECTED)
+ sk->socket->state = SS_DISCONNECTING;
+ sk->state_change(sk);
+ }
+
+ dn_nsp_send_disc(sk, NSP_DISCCONF, NSP_REASON_DC, GFP_ATOMIC);
+ scp->persist_fxn = dn_destroy_timer;
+ scp->persist = dn_nsp_persist(sk);
+
+out:
+ kfree_skb(skb);
+}
+
+/*
+ * disc_conf messages are also called no_resources or no_link
+ * messages depending upon the "reason" field.
+ */
+static void dn_nsp_disc_conf(struct sock *sk, struct sk_buff *skb)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ unsigned short reason;
+
+ if (skb->len != 2)
+ goto out;
+
+ reason = dn_ntohs(*(__u16 *)skb->data);
+
+ sk->state = TCP_CLOSE;
+
+ switch(scp->state) {
+ case DN_CI:
+ scp->state = DN_NR;
+ break;
+ case DN_DR:
+ if (reason == NSP_REASON_DC)
+ scp->state = DN_DRC;
+ if (reason == NSP_REASON_NL)
+ scp->state = DN_CN;
+ break;
+ case DN_DI:
+ scp->state = DN_DIC;
+ break;
+ case DN_RUN:
+ sk->shutdown |= SHUTDOWN_MASK;
+ case DN_CC:
+ scp->state = DN_CN;
+ }
+
+ if (!sk->dead) {
+ if (sk->socket->state != SS_UNCONNECTED)
+ sk->socket->state = SS_DISCONNECTING;
+ sk->state_change(sk);
+ }
+
+ scp->persist_fxn = dn_destroy_timer;
+ scp->persist = dn_nsp_persist(sk);
+
+out:
+ kfree_skb(skb);
+}
+
+static void dn_nsp_linkservice(struct sock *sk, struct sk_buff *skb)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ unsigned short segnum;
+ unsigned char lsflags;
+ char fcval;
+ int wake_up = 0;
+ char *ptr = skb->data;
+ unsigned char fctype = scp->services_rem & NSP_FC_MASK;
+
+ if (skb->len != 4)
+ goto out;
+
+ segnum = dn_ntohs(*(__u16 *)ptr);
+ ptr += 2;
+ lsflags = *(unsigned char *)ptr++;
+ fcval = *ptr;
+
+ /*
+ * Here we ignore erronous packets which should really
+ * should cause a connection abort. It is not critical
+ * for now though.
+ */
+ if (lsflags & 0xf8)
+ goto out;
+
+ if (seq_next(scp->numoth_rcv, segnum)) {
+ seq_add(&scp->numoth_rcv, 1);
+ switch(lsflags & 0x04) { /* FCVAL INT */
+ case 0x00: /* Normal Request */
+ switch(lsflags & 0x03) { /* FCVAL MOD */
+ case 0x00: /* Request count */
+ if (fcval < 0) {
+ unsigned char p_fcval = -fcval;
+ if ((scp->flowrem_dat > p_fcval) &&
+ (fctype == NSP_FC_SCMC)) {
+ scp->flowrem_dat -= p_fcval;
+ }
+ } else if (fcval > 0) {
+ scp->flowrem_dat += fcval;
+ wake_up = 1;
+ }
+ break;
+ case 0x01: /* Stop outgoing data */
+ scp->flowrem_sw = DN_DONTSEND;
+ break;
+ case 0x02: /* Ok to start again */
+ scp->flowrem_sw = DN_SEND;
+ dn_nsp_output(sk);
+ wake_up = 1;
+ }
+ break;
+ case 0x04: /* Interrupt Request */
+ if (fcval > 0) {
+ scp->flowrem_oth += fcval;
+ wake_up = 1;
+ }
+ break;
+ }
+ if (wake_up && !sk->dead)
+ sk->state_change(sk);
+ }
+
+ dn_nsp_send_oth_ack(sk);
+
+out:
+ kfree_skb(skb);
+}
+
+/*
+ * Copy of sock_queue_rcv_skb (from sock.h) without
+ * bh_lock_sock() (its already held when this is called) which
+ * also allows data and other data to be queued to a socket.
+ */
+static __inline__ int dn_queue_skb(struct sock *sk, struct sk_buff *skb, int sig, struct sk_buff_head *queue)
+{
+ int err;
+
+ /* Cast skb->rcvbuf to unsigned... It's pointless, but reduces
+ number of warnings when compiling with -W --ANK
+ */
+ if (atomic_read(&sk->rmem_alloc) + skb->truesize >= (unsigned)sk->rcvbuf) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ err = sk_filter(sk, skb, 0);
+ if (err)
+ goto out;
+
+ skb_set_owner_r(skb, sk);
+ skb_queue_tail(queue, skb);
+
+ /* This code only runs from BH or BH protected context.
+ * Therefore the plain read_lock is ok here. -DaveM
+ */
+ read_lock(&sk->callback_lock);
+ if (!sk->dead) {
+ struct socket *sock = sk->socket;
+ wake_up_interruptible(sk->sleep);
+ if (sock && sock->fasync_list &&
+ !test_bit(SOCK_ASYNC_WAITDATA, &sock->flags))
+ __kill_fasync(sock->fasync_list, sig,
+ (sig == SIGURG) ? POLL_PRI : POLL_IN);
+ }
+ read_unlock(&sk->callback_lock);
+out:
+ return err;
+}
+
+static void dn_nsp_otherdata(struct sock *sk, struct sk_buff *skb)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ unsigned short segnum;
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ int queued = 0;
+
+ if (skb->len < 2)
+ goto out;
+
+ cb->segnum = segnum = dn_ntohs(*(__u16 *)skb->data);
+ skb_pull(skb, 2);
+
+ if (seq_next(scp->numoth_rcv, segnum)) {
+
+ if (dn_queue_skb(sk, skb, SIGURG, &scp->other_receive_queue) == 0) {
+ seq_add(&scp->numoth_rcv, 1);
+ scp->other_report = 0;
+ queued = 1;
+ }
+ }
+
+ dn_nsp_send_oth_ack(sk);
+out:
+ if (!queued)
+ kfree_skb(skb);
+}
+
+static void dn_nsp_data(struct sock *sk, struct sk_buff *skb)
+{
+ int queued = 0;
+ unsigned short segnum;
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ struct dn_scp *scp = DN_SK(sk);
+
+ if (skb->len < 2)
+ goto out;
+
+ cb->segnum = segnum = dn_ntohs(*(__u16 *)skb->data);
+ skb_pull(skb, 2);
+
+ if (seq_next(scp->numdat_rcv, segnum)) {
+ if (dn_queue_skb(sk, skb, SIGIO, &sk->receive_queue) == 0) {
+ seq_add(&scp->numdat_rcv, 1);
+ queued = 1;
+ }
+
+ if ((scp->flowloc_sw == DN_SEND) && dn_congested(sk)) {
+ scp->flowloc_sw = DN_DONTSEND;
+ dn_nsp_send_link(sk, DN_DONTSEND, 0);
+ }
+ }
+
+ dn_nsp_send_data_ack(sk);
+out:
+ if (!queued)
+ kfree_skb(skb);
+}
+
+/*
+ * If one of our conninit messages is returned, this function
+ * deals with it. It puts the socket into the NO_COMMUNICATION
+ * state.
+ */
+static void dn_returned_conn_init(struct sock *sk, struct sk_buff *skb)
+{
+ struct dn_scp *scp = DN_SK(sk);
+
+ if (scp->state == DN_CI) {
+ scp->state = DN_NC;
+ sk->state = TCP_CLOSE;
+ if (!sk->dead)
+ sk->state_change(sk);
+ }
+
+ kfree_skb(skb);
+}
+
+static int dn_nsp_no_socket(struct sk_buff *skb, unsigned short reason)
+{
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ int ret = NET_RX_DROP;
+
+ /* Must not reply to returned packets */
+ if (cb->rt_flags & DN_RT_F_RTS)
+ goto out;
+
+ if ((reason != NSP_REASON_OK) && ((cb->nsp_flags & 0x0c) == 0x08)) {
+ switch(cb->nsp_flags & 0x70) {
+ case 0x10:
+ case 0x60: /* (Retransmitted) Connect Init */
+ dn_nsp_return_disc(skb, NSP_DISCINIT, reason);
+ ret = NET_RX_SUCCESS;
+ break;
+ case 0x20: /* Connect Confirm */
+ dn_nsp_return_disc(skb, NSP_DISCCONF, reason);
+ ret = NET_RX_SUCCESS;
+ break;
+ }
+ }
+
+out:
+ kfree_skb(skb);
+ return ret;
+}
+
+static int dn_nsp_rx_packet(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ struct sock *sk = NULL;
+ unsigned char *ptr = (unsigned char *)skb->data;
+ unsigned short reason = NSP_REASON_NL;
+
+ skb->h.raw = skb->data;
+ cb->nsp_flags = *ptr++;
+
+ if (decnet_debug_level & 2)
+ printk(KERN_DEBUG "dn_nsp_rx: Message type 0x%02x\n", (int)cb->nsp_flags);
+
+ if (skb->len < 2)
+ goto free_out;
+
+ if (cb->nsp_flags & 0x83)
+ goto free_out;
+
+ /*
+ * Returned packets...
+ * Swap src & dst and look up in the normal way.
+ */
+ if (cb->rt_flags & DN_RT_F_RTS) {
+ unsigned short tmp = cb->dst_port;
+ cb->dst_port = cb->src_port;
+ cb->src_port = tmp;
+ tmp = cb->dst;
+ cb->dst = cb->src;
+ cb->src = tmp;
+ sk = dn_find_by_skb(skb);
+ goto got_it;
+ }
+
+ /*
+ * Filter out conninits and useless packet types
+ */
+ if ((cb->nsp_flags & 0x0c) == 0x08) {
+ switch(cb->nsp_flags & 0x70) {
+ case 0x00: /* NOP */
+ case 0x70: /* Reserved */
+ case 0x50: /* Reserved, Phase II node init */
+ goto free_out;
+ case 0x10:
+ case 0x60:
+ sk = dn_find_listener(skb, &reason);
+ goto got_it;
+ }
+ }
+
+ if (skb->len < 3)
+ goto free_out;
+
+ /*
+ * Grab the destination address.
+ */
+ cb->dst_port = *(unsigned short *)ptr;
+ cb->src_port = 0;
+ ptr += 2;
+
+ /*
+ * If not a connack, grab the source address too.
+ */
+ if (skb->len >= 5) {
+ cb->src_port = *(unsigned short *)ptr;
+ ptr += 2;
+ skb_pull(skb, 5);
+ }
+
+ /*
+ * Find the socket to which this skb is destined.
+ */
+ sk = dn_find_by_skb(skb);
+got_it:
+ if (sk != NULL) {
+ struct dn_scp *scp = DN_SK(sk);
+ int ret;
+
+ /* Reset backoff */
+ scp->nsp_rxtshift = 0;
+
+ bh_lock_sock(sk);
+ ret = NET_RX_SUCCESS;
+ if (decnet_debug_level & 8)
+ printk(KERN_DEBUG "NSP: 0x%02x 0x%02x 0x%04x 0x%04x %d\n",
+ (int)cb->rt_flags, (int)cb->nsp_flags,
+ (int)cb->src_port, (int)cb->dst_port,
+ (int)sk->lock.users);
+ if (sk->lock.users == 0)
+ ret = dn_nsp_backlog_rcv(sk, skb);
+ else
+ sk_add_backlog(sk, skb);
+ bh_unlock_sock(sk);
+ sock_put(sk);
+
+ return ret;
+ }
+
+ return dn_nsp_no_socket(skb, reason);
+
+free_out:
+ kfree_skb(skb);
+ return NET_RX_DROP;
+}
+
+int dn_nsp_rx(struct sk_buff *skb)
+{
+ return NF_HOOK(PF_DECnet, NF_DN_LOCAL_IN, skb, skb->dev, NULL, dn_nsp_rx_packet);
+}
+
+/*
+ * This is the main receive routine for sockets. It is called
+ * from the above when the socket is not busy, and also from
+ * sock_release() when there is a backlog queued up.
+ */
+int dn_nsp_backlog_rcv(struct sock *sk, struct sk_buff *skb)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+
+ if (cb->rt_flags & DN_RT_F_RTS) {
+ dn_returned_conn_init(sk, skb);
+ return NET_RX_SUCCESS;
+ }
+
+ /*
+ * Control packet.
+ */
+ if ((cb->nsp_flags & 0x0c) == 0x08) {
+ switch(cb->nsp_flags & 0x70) {
+ case 0x10:
+ case 0x60:
+ dn_nsp_conn_init(sk, skb);
+ break;
+ case 0x20:
+ dn_nsp_conn_conf(sk, skb);
+ break;
+ case 0x30:
+ dn_nsp_disc_init(sk, skb);
+ break;
+ case 0x40:
+ dn_nsp_disc_conf(sk, skb);
+ break;
+ }
+
+ } else if (cb->nsp_flags == 0x24) {
+ /*
+ * Special for connacks, 'cos they don't have
+ * ack data or ack otherdata info.
+ */
+ dn_nsp_conn_ack(sk, skb);
+ } else {
+ int other = 1;
+
+ /* both data and ack frames can kick a CC socket into RUN */
+ if ((scp->state == DN_CC) && !sk->dead) {
+ scp->state = DN_RUN;
+ sk->state = TCP_ESTABLISHED;
+ sk->state_change(sk);
+ }
+
+ if ((cb->nsp_flags & 0x1c) == 0)
+ other = 0;
+ if (cb->nsp_flags == 0x04)
+ other = 0;
+
+ /*
+ * Read out ack data here, this applies equally
+ * to data, other data, link serivce and both
+ * ack data and ack otherdata.
+ */
+ dn_process_ack(sk, skb, other);
+
+ /*
+ * If we've some sort of data here then call a
+ * suitable routine for dealing with it, otherwise
+ * the packet is an ack and can be discarded.
+ */
+ if ((cb->nsp_flags & 0x0c) == 0) {
+
+ if (scp->state != DN_RUN)
+ goto free_out;
+
+ switch(cb->nsp_flags) {
+ case 0x10: /* LS */
+ dn_nsp_linkservice(sk, skb);
+ break;
+ case 0x30: /* OD */
+ dn_nsp_otherdata(sk, skb);
+ break;
+ default:
+ dn_nsp_data(sk, skb);
+ }
+
+ } else { /* Ack, chuck it out here */
+free_out:
+ kfree_skb(skb);
+ }
+ }
+
+ return NET_RX_SUCCESS;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/decnet/dn_nsp_out.c b/uClinux-2.4.31-uc0/net/decnet/dn_nsp_out.c
new file mode 100644
index 0000000..5e8482c
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/dn_nsp_out.c
@@ -0,0 +1,730 @@
+
+/*
+ * DECnet An implementation of the DECnet protocol suite for the LINUX
+ * operating system. DECnet is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * DECnet Network Services Protocol (Output)
+ *
+ * Author: Eduardo Marcelo Serrat <emserrat@geocities.com>
+ *
+ * Changes:
+ *
+ * Steve Whitehouse: Split into dn_nsp_in.c and dn_nsp_out.c from
+ * original dn_nsp.c.
+ * Steve Whitehouse: Updated to work with my new routing architecture.
+ * Steve Whitehouse: Added changes from Eduardo Serrat's patches.
+ * Steve Whitehouse: Now conninits have the "return" bit set.
+ * Steve Whitehouse: Fixes to check alloc'd skbs are non NULL!
+ * Moved output state machine into one function
+ * Steve Whitehouse: New output state machine
+ * Paul Koning: Connect Confirm message fix.
+ * Eduardo Serrat: Fix to stop dn_nsp_do_disc() sending malformed packets.
+ * Steve Whitehouse: dn_nsp_output() and friends needed a spring clean
+ */
+
+/******************************************************************************
+ (c) 1995-1998 E.M. Serrat emserrat@geocities.com
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*******************************************************************************/
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/inet.h>
+#include <linux/route.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/termios.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/if_packet.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/dn_nsp.h>
+#include <net/dn_dev.h>
+#include <net/dn_route.h>
+
+
+static int nsp_backoff[NSP_MAXRXTSHIFT + 1] = { 1, 2, 4, 8, 16, 32, 64, 64, 64, 64, 64, 64, 64 };
+
+/*
+ * If sk == NULL, then we assume that we are supposed to be making
+ * a routing layer skb. If sk != NULL, then we are supposed to be
+ * creating an skb for the NSP layer.
+ *
+ * The eventual aim is for each socket to have a cached header size
+ * for its outgoing packets, and to set hdr from this when sk != NULL.
+ */
+struct sk_buff *dn_alloc_skb(struct sock *sk, int size, int pri)
+{
+ struct sk_buff *skb;
+ int hdr = 64;
+
+ if ((skb = alloc_skb(size + hdr, pri)) == NULL)
+ return NULL;
+
+ skb->protocol = __constant_htons(ETH_P_DNA_RT);
+ skb->pkt_type = PACKET_OUTGOING;
+
+ if (sk)
+ skb_set_owner_w(skb, sk);
+
+ skb_reserve(skb, hdr);
+
+ return skb;
+}
+
+/*
+ * Wrapper for the above, for allocs of data skbs. We try and get the
+ * whole size thats been asked for (plus 11 bytes of header). If this
+ * fails, then we try for any size over 16 bytes for SOCK_STREAMS.
+ */
+struct sk_buff *dn_alloc_send_skb(struct sock *sk, int *size, int noblock, int *err)
+{
+ int space;
+ int len;
+ struct sk_buff *skb = NULL;
+
+ *err = 0;
+
+ while(skb == NULL) {
+ if (signal_pending(current)) {
+ *err = ERESTARTSYS;
+ break;
+ }
+
+ if (sk->shutdown & SEND_SHUTDOWN) {
+ *err = EINVAL;
+ break;
+ }
+
+ if (sk->err)
+ break;
+
+ len = *size + 11;
+ space = sk->sndbuf - atomic_read(&sk->wmem_alloc);
+
+ if (space < len) {
+ if ((sk->socket->type == SOCK_STREAM) && (space >= (16 + 11)))
+ len = space;
+ }
+
+ if (space < len) {
+ set_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
+ if (noblock) {
+ *err = EWOULDBLOCK;
+ break;
+ }
+
+ clear_bit(SOCK_ASYNC_WAITDATA, &sk->socket->flags);
+ SOCK_SLEEP_PRE(sk)
+
+ if ((sk->sndbuf - atomic_read(&sk->wmem_alloc)) < len)
+ schedule();
+
+ SOCK_SLEEP_POST(sk)
+ continue;
+ }
+
+ if ((skb = dn_alloc_skb(sk, len, sk->allocation)) == NULL)
+ continue;
+
+ *size = len - 11;
+ }
+
+ return skb;
+}
+
+/*
+ * Calculate persist timer based upon the smoothed round
+ * trip time and the variance. Backoff according to the
+ * nsp_backoff[] array.
+ */
+unsigned long dn_nsp_persist(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+
+ unsigned long t = ((scp->nsp_srtt >> 2) + scp->nsp_rttvar) >> 1;
+
+ t *= nsp_backoff[scp->nsp_rxtshift];
+
+ if (t < HZ) t = HZ;
+ if (t > (600*HZ)) t = (600*HZ);
+
+ if (scp->nsp_rxtshift < NSP_MAXRXTSHIFT)
+ scp->nsp_rxtshift++;
+
+ /* printk(KERN_DEBUG "rxtshift %lu, t=%lu\n", scp->nsp_rxtshift, t); */
+
+ return t;
+}
+
+/*
+ * This is called each time we get an estimate for the rtt
+ * on the link.
+ */
+static void dn_nsp_rtt(struct sock *sk, long rtt)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ long srtt = (long)scp->nsp_srtt;
+ long rttvar = (long)scp->nsp_rttvar;
+ long delta;
+
+ /*
+ * If the jiffies clock flips over in the middle of timestamp
+ * gathering this value might turn out negative, so we make sure
+ * that is it always positive here.
+ */
+ if (rtt < 0)
+ rtt = -rtt;
+ /*
+ * Add new rtt to smoothed average
+ */
+ delta = ((rtt << 3) - srtt);
+ srtt += (delta >> 3);
+ if (srtt >= 1)
+ scp->nsp_srtt = (unsigned long)srtt;
+ else
+ scp->nsp_srtt = 1;
+
+ /*
+ * Add new rtt varience to smoothed varience
+ */
+ delta >>= 1;
+ rttvar += ((((delta>0)?(delta):(-delta)) - rttvar) >> 2);
+ if (rttvar >= 1)
+ scp->nsp_rttvar = (unsigned long)rttvar;
+ else
+ scp->nsp_rttvar = 1;
+
+ /* printk(KERN_DEBUG "srtt=%lu rttvar=%lu\n", scp->nsp_srtt, scp->nsp_rttvar); */
+}
+
+/**
+ * dn_nsp_clone_and_send - Send a data packet by cloning it
+ * @skb: The packet to clone and transmit
+ * @gfp: memory allocation flag
+ *
+ * Clone a queued data or other data packet and transmit it.
+ *
+ * Returns: The number of times the packet has been sent previously
+ */
+static inline unsigned dn_nsp_clone_and_send(struct sk_buff *skb, int gfp)
+{
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ struct sk_buff *skb2;
+ int ret = 0;
+
+ if ((skb2 = skb_clone(skb, gfp)) != NULL) {
+ ret = cb->xmit_count;
+ cb->xmit_count++;
+ cb->stamp = jiffies;
+ skb2->sk = skb->sk;
+ dn_nsp_send(skb2);
+ }
+
+ return ret;
+}
+
+/**
+ * dn_nsp_output - Try and send something from socket queues
+ * @sk: The socket whose queues are to be investigated
+ * @gfp: The memory allocation flags
+ *
+ * Try and send the packet on the end of the data and other data queues.
+ * Other data gets priority over data, and if we retransmit a packet we
+ * reduce the window by dividing it in two.
+ *
+ */
+void dn_nsp_output(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ struct sk_buff *skb;
+ unsigned reduce_win = 0;
+
+ /*
+ * First we check for otherdata/linkservice messages
+ */
+ if ((skb = skb_peek(&scp->other_xmit_queue)) != NULL)
+ reduce_win = dn_nsp_clone_and_send(skb, GFP_ATOMIC);
+
+ /*
+ * If we may not send any data, we don't.
+ * If we are still trying to get some other data down the
+ * channel, we don't try and send any data.
+ */
+ if (reduce_win || (scp->flowrem_sw != DN_SEND))
+ goto recalc_window;
+
+ if ((skb = skb_peek(&scp->data_xmit_queue)) != NULL)
+ reduce_win = dn_nsp_clone_and_send(skb, GFP_ATOMIC);
+
+ /*
+ * If we've sent any frame more than once, we cut the
+ * send window size in half. There is always a minimum
+ * window size of one available.
+ */
+recalc_window:
+ if (reduce_win) {
+ scp->snd_window >>= 1;
+ if (scp->snd_window < NSP_MIN_WINDOW)
+ scp->snd_window = NSP_MIN_WINDOW;
+ }
+}
+
+int dn_nsp_xmit_timeout(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+
+ dn_nsp_output(sk);
+
+ if (skb_queue_len(&scp->data_xmit_queue) || skb_queue_len(&scp->other_xmit_queue))
+ scp->persist = dn_nsp_persist(sk);
+
+ return 0;
+}
+
+static inline unsigned char *dn_mk_common_header(struct dn_scp *scp, struct sk_buff *skb, unsigned char msgflag, int len)
+{
+ unsigned char *ptr = skb_push(skb, len);
+
+ if (len < 5)
+ BUG();
+
+ *ptr++ = msgflag;
+ *((unsigned short *)ptr) = scp->addrrem;
+ ptr += 2;
+ *((unsigned short *)ptr) = scp->addrloc;
+ ptr += 2;
+ return ptr;
+}
+
+static unsigned short *dn_mk_ack_header(struct sock *sk, struct sk_buff *skb, unsigned char msgflag, int hlen, int other)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ unsigned short acknum = scp->numdat_rcv & 0x0FFF;
+ unsigned short ackcrs = scp->numoth_rcv & 0x0FFF;
+ unsigned short *ptr;
+
+ if (hlen < 9)
+ BUG();
+
+ scp->ackxmt_dat = acknum;
+ scp->ackxmt_oth = ackcrs;
+ acknum |= 0x8000;
+ ackcrs |= 0x8000;
+
+ /* If this is an "other data/ack" message, swap acknum and ackcrs */
+ if (other) {
+ unsigned short tmp = acknum;
+ acknum = ackcrs;
+ ackcrs = tmp;
+ }
+
+ /* Set "cross subchannel" bit in ackcrs */
+ ackcrs |= 0x2000;
+
+ ptr = (unsigned short *)dn_mk_common_header(scp, skb, msgflag, hlen);
+
+ *ptr++ = dn_htons(acknum);
+ *ptr++ = dn_htons(ackcrs);
+
+ return ptr;
+}
+
+void dn_nsp_queue_xmit(struct sock *sk, struct sk_buff *skb, int gfp, int oth)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ unsigned long t = ((scp->nsp_srtt >> 2) + scp->nsp_rttvar) >> 1;
+
+ /*
+ * Slow start: If we have been idle for more than
+ * one RTT, then reset window to min size.
+ */
+ if ((jiffies - scp->stamp) > t)
+ scp->snd_window = NSP_MIN_WINDOW;
+
+ /* printk(KERN_DEBUG "Window: %lu\n", scp->snd_window); */
+
+ cb->xmit_count = 0;
+
+ if (oth)
+ skb_queue_tail(&scp->other_xmit_queue, skb);
+ else
+ skb_queue_tail(&scp->data_xmit_queue, skb);
+
+ if (scp->flowrem_sw != DN_SEND)
+ return;
+
+ dn_nsp_clone_and_send(skb, gfp);
+}
+
+
+int dn_nsp_check_xmit_queue(struct sock *sk, struct sk_buff *skb, struct sk_buff_head *q, unsigned short acknum)
+{
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ struct dn_scp *scp = DN_SK(sk);
+ struct sk_buff *skb2, *list, *ack = NULL;
+ int wakeup = 0;
+ int try_retrans = 0;
+ unsigned long reftime = cb->stamp;
+ unsigned long pkttime;
+ unsigned short xmit_count;
+ unsigned short segnum;
+
+ skb2 = q->next;
+ list = (struct sk_buff *)q;
+ while(list != skb2) {
+ struct dn_skb_cb *cb2 = DN_SKB_CB(skb2);
+
+ if (before_or_equal(cb2->segnum, acknum))
+ ack = skb2;
+
+ /* printk(KERN_DEBUG "ack: %s %04x %04x\n", ack ? "ACK" : "SKIP", (int)cb2->segnum, (int)acknum); */
+
+ skb2 = skb2->next;
+
+ if (ack == NULL)
+ continue;
+
+ /* printk(KERN_DEBUG "check_xmit_queue: %04x, %d\n", acknum, cb2->xmit_count); */
+
+ /* Does _last_ packet acked have xmit_count > 1 */
+ try_retrans = 0;
+ /* Remember to wake up the sending process */
+ wakeup = 1;
+ /* Keep various statistics */
+ pkttime = cb2->stamp;
+ xmit_count = cb2->xmit_count;
+ segnum = cb2->segnum;
+ /* Remove and drop ack'ed packet */
+ skb_unlink(ack);
+ kfree_skb(ack);
+ ack = NULL;
+
+ /*
+ * We don't expect to see acknowledgements for packets we
+ * haven't sent yet.
+ */
+ if (xmit_count == 0)
+ BUG();
+ /*
+ * If the packet has only been sent once, we can use it
+ * to calculate the RTT and also open the window a little
+ * further.
+ */
+ if (xmit_count == 1) {
+ if (equal(segnum, acknum))
+ dn_nsp_rtt(sk, (long)(pkttime - reftime));
+
+ if (scp->snd_window < scp->max_window)
+ scp->snd_window++;
+ }
+
+ /*
+ * Packet has been sent more than once. If this is the last
+ * packet to be acknowledged then we want to send the next
+ * packet in the send queue again (assumes the remote host does
+ * go-back-N error control).
+ */
+ if (xmit_count > 1)
+ try_retrans = 1;
+ }
+
+ if (try_retrans)
+ dn_nsp_output(sk);
+
+ return wakeup;
+}
+
+void dn_nsp_send_data_ack(struct sock *sk)
+{
+ struct sk_buff *skb = NULL;
+
+ if ((skb = dn_alloc_skb(sk, 9, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skb, 9);
+ dn_mk_ack_header(sk, skb, 0x04, 9, 0);
+ dn_nsp_send(skb);
+}
+
+void dn_nsp_send_oth_ack(struct sock *sk)
+{
+ struct sk_buff *skb = NULL;
+
+ if ((skb = dn_alloc_skb(sk, 9, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skb, 9);
+ dn_mk_ack_header(sk, skb, 0x14, 9, 1);
+ dn_nsp_send(skb);
+}
+
+
+void dn_send_conn_ack (struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ struct sk_buff *skb = NULL;
+ struct nsp_conn_ack_msg *msg;
+
+ if ((skb = dn_alloc_skb(sk, 3, sk->allocation)) == NULL)
+ return;
+
+ msg = (struct nsp_conn_ack_msg *)skb_put(skb, 3);
+ msg->msgflg = 0x24;
+ msg->dstaddr = scp->addrrem;
+
+ dn_nsp_send(skb);
+}
+
+void dn_nsp_delayed_ack(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+
+ if (scp->ackxmt_oth != scp->numoth_rcv)
+ dn_nsp_send_oth_ack(sk);
+
+ if (scp->ackxmt_dat != scp->numdat_rcv)
+ dn_nsp_send_data_ack(sk);
+}
+
+static int dn_nsp_retrans_conn_conf(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+
+ if (scp->state == DN_CC)
+ dn_send_conn_conf(sk, GFP_ATOMIC);
+
+ return 0;
+}
+
+void dn_send_conn_conf(struct sock *sk, int gfp)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ struct sk_buff *skb = NULL;
+ struct nsp_conn_init_msg *msg;
+ unsigned char len = scp->conndata_out.opt_optl;
+
+ if ((skb = dn_alloc_skb(sk, 50 + scp->conndata_out.opt_optl, gfp)) == NULL)
+ return;
+
+ msg = (struct nsp_conn_init_msg *)skb_put(skb, sizeof(*msg));
+ msg->msgflg = 0x28;
+ msg->dstaddr = scp->addrrem;
+ msg->srcaddr = scp->addrloc;
+ msg->services = scp->services_loc;
+ msg->info = scp->info_loc;
+ msg->segsize = dn_htons(scp->segsize_loc);
+
+ *skb_put(skb,1) = len;
+
+ if (len > 0)
+ memcpy(skb_put(skb, len), scp->conndata_out.opt_data, len);
+
+
+ dn_nsp_send(skb);
+
+ scp->persist = dn_nsp_persist(sk);
+ scp->persist_fxn = dn_nsp_retrans_conn_conf;
+}
+
+
+static __inline__ void dn_nsp_do_disc(struct sock *sk, unsigned char msgflg,
+ unsigned short reason, int gfp, struct dst_entry *dst,
+ int ddl, unsigned char *dd, __u16 rem, __u16 loc)
+{
+ struct sk_buff *skb = NULL;
+ int size = 7 + ddl + ((msgflg == NSP_DISCINIT) ? 1 : 0);
+ unsigned char *msg;
+
+ if ((dst == NULL) || (rem == 0)) {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "DECnet: dn_nsp_do_disc: BUG! Please report this to SteveW@ACM.org rem=%u dst=%p\n", (unsigned)rem, dst);
+ return;
+ }
+
+ if ((skb = dn_alloc_skb(sk, size, gfp)) == NULL)
+ return;
+
+ msg = skb_put(skb, size);
+ *msg++ = msgflg;
+ *(__u16 *)msg = rem;
+ msg += 2;
+ *(__u16 *)msg = loc;
+ msg += 2;
+ *(__u16 *)msg = dn_htons(reason);
+ msg += 2;
+ if (msgflg == NSP_DISCINIT)
+ *msg++ = ddl;
+
+ if (ddl) {
+ memcpy(msg, dd, ddl);
+ }
+
+ /*
+ * This doesn't go via the dn_nsp_send() fucntion since we need
+ * to be able to send disc packets out which have no socket
+ * associations.
+ */
+ skb->dst = dst_clone(dst);
+ skb->dst->output(skb);
+}
+
+
+void dn_nsp_send_disc(struct sock *sk, unsigned char msgflg,
+ unsigned short reason, int gfp)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ int ddl = 0;
+
+ if (msgflg == NSP_DISCINIT)
+ ddl = scp->discdata_out.opt_optl;
+
+ if (reason == 0)
+ reason = scp->discdata_out.opt_status;
+
+ dn_nsp_do_disc(sk, msgflg, reason, gfp, sk->dst_cache, ddl,
+ scp->discdata_out.opt_data, scp->addrrem, scp->addrloc);
+}
+
+
+void dn_nsp_return_disc(struct sk_buff *skb, unsigned char msgflg,
+ unsigned short reason)
+{
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ int ddl = 0;
+ int gfp = GFP_ATOMIC;
+
+ dn_nsp_do_disc(NULL, msgflg, reason, gfp, skb->dst, ddl,
+ NULL, cb->src_port, cb->dst_port);
+}
+
+
+void dn_nsp_send_link(struct sock *sk, unsigned char lsflags, char fcval)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ struct sk_buff *skb;
+ unsigned short *segnum;
+ unsigned char *ptr;
+ int gfp = GFP_ATOMIC;
+
+ if ((skb = dn_alloc_skb(sk, 13, gfp)) == NULL)
+ return;
+
+ skb_reserve(skb, 13);
+ segnum = dn_mk_ack_header(sk, skb, 0x10, 13, 1);
+ *segnum = dn_htons(scp->numoth);
+ DN_SKB_CB(skb)->segnum = scp->numoth;
+ seq_add(&scp->numoth, 1);
+ ptr = (unsigned char *)(segnum + 1);
+ *ptr++ = lsflags;
+ *ptr = fcval;
+
+ dn_nsp_queue_xmit(sk, skb, gfp, 1);
+
+ scp->persist = dn_nsp_persist(sk);
+ scp->persist_fxn = dn_nsp_xmit_timeout;
+}
+
+static int dn_nsp_retrans_conninit(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+
+ if (scp->state == DN_CI)
+ dn_nsp_send_conninit(sk, NSP_RCI);
+
+ return 0;
+}
+
+void dn_nsp_send_conninit(struct sock *sk, unsigned char msgflg)
+{
+ struct dn_scp *scp = DN_SK(sk);
+ struct sk_buff *skb = NULL;
+ struct nsp_conn_init_msg *msg;
+ unsigned char aux;
+ unsigned char menuver;
+ struct dn_skb_cb *cb;
+ unsigned char type = 1;
+
+ if ((skb = dn_alloc_skb(sk, 200, (msgflg == NSP_CI) ? sk->allocation : GFP_ATOMIC)) == NULL)
+ return;
+
+ cb = DN_SKB_CB(skb);
+ msg = (struct nsp_conn_init_msg *)skb_put(skb,sizeof(*msg));
+
+ msg->msgflg = msgflg;
+ msg->dstaddr = 0x0000; /* Remote Node will assign it*/
+
+ msg->srcaddr = scp->addrloc;
+ msg->services = scp->services_loc; /* Requested flow control */
+ msg->info = scp->info_loc; /* Version Number */
+ msg->segsize = dn_htons(scp->segsize_loc); /* Max segment size */
+
+ if (scp->peer.sdn_objnum)
+ type = 0;
+
+ skb_put(skb, dn_sockaddr2username(&scp->peer, skb->tail, type));
+ skb_put(skb, dn_sockaddr2username(&scp->addr, skb->tail, 2));
+
+ menuver = DN_MENUVER_ACC | DN_MENUVER_USR;
+ if (scp->peer.sdn_flags & SDF_PROXY)
+ menuver |= DN_MENUVER_PRX;
+ if (scp->peer.sdn_flags & SDF_UICPROXY)
+ menuver |= DN_MENUVER_UIC;
+
+ *skb_put(skb, 1) = menuver; /* Menu Version */
+
+ aux = scp->accessdata.acc_userl;
+ *skb_put(skb, 1) = aux;
+ if (aux > 0)
+ memcpy(skb_put(skb, aux), scp->accessdata.acc_user, aux);
+
+ aux = scp->accessdata.acc_passl;
+ *skb_put(skb, 1) = aux;
+ if (aux > 0)
+ memcpy(skb_put(skb, aux), scp->accessdata.acc_pass, aux);
+
+ aux = scp->accessdata.acc_accl;
+ *skb_put(skb, 1) = aux;
+ if (aux > 0)
+ memcpy(skb_put(skb, aux), scp->accessdata.acc_acc, aux);
+
+ aux = scp->conndata_out.opt_optl;
+ *skb_put(skb, 1) = aux;
+ if (aux > 0)
+ memcpy(skb_put(skb,aux), scp->conndata_out.opt_data, aux);
+
+ scp->persist = dn_nsp_persist(sk);
+ scp->persist_fxn = dn_nsp_retrans_conninit;
+
+ cb->rt_flags = DN_RT_F_RQR;
+
+ dn_nsp_send(skb);
+}
+
diff --git a/uClinux-2.4.31-uc0/net/decnet/dn_route.c b/uClinux-2.4.31-uc0/net/decnet/dn_route.c
new file mode 100644
index 0000000..e93af0d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/dn_route.c
@@ -0,0 +1,1306 @@
+
+/*
+ * DECnet An implementation of the DECnet protocol suite for the LINUX
+ * operating system. DECnet is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * DECnet Routing Functions (Endnode and Router)
+ *
+ * Authors: Steve Whitehouse <SteveW@ACM.org>
+ * Eduardo Marcelo Serrat <emserrat@geocities.com>
+ *
+ * Changes:
+ * Steve Whitehouse : Fixes to allow "intra-ethernet" and
+ * "return-to-sender" bits on outgoing
+ * packets.
+ * Steve Whitehouse : Timeouts for cached routes.
+ * Steve Whitehouse : Use dst cache for input routes too.
+ * Steve Whitehouse : Fixed error values in dn_send_skb.
+ * Steve Whitehouse : Rework routing functions to better fit
+ * DECnet routing design
+ * Alexey Kuznetsov : New SMP locking
+ * Steve Whitehouse : More SMP locking changes & dn_cache_dump()
+ * Steve Whitehouse : Prerouting NF hook, now really is prerouting.
+ * Fixed possible skb leak in rtnetlink funcs.
+ * Steve Whitehouse : Dave Miller's dynamic hash table sizing and
+ * Alexey Kuznetsov's finer grained locking
+ * from ipv4/route.c.
+ * Steve Whitehouse : Routing is now starting to look like a
+ * sensible set of code now, mainly due to
+ * my copying the IPv4 routing code. The
+ * hooks here are modified and will continue
+ * to evolve for a while.
+ * Steve Whitehouse : Real SMP at last :-) Also new netfilter
+ * stuff. Look out raw sockets your days
+ * are numbered!
+ * Steve Whitehouse : Added return-to-sender functions. Added
+ * backlog congestion level return codes.
+ * Steve Whitehouse : Fixed bug where routes were set up with
+ * no ref count on net devices.
+ *
+ */
+
+/******************************************************************************
+ (c) 1995-1998 E.M. Serrat emserrat@geocities.com
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+*******************************************************************************/
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/inet.h>
+#include <linux/route.h>
+#include <net/sock.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <linux/rtnetlink.h>
+#include <linux/string.h>
+#include <linux/netfilter_decnet.h>
+#include <asm/errno.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/dn.h>
+#include <net/dn_dev.h>
+#include <net/dn_nsp.h>
+#include <net/dn_route.h>
+#include <net/dn_neigh.h>
+#include <net/dn_fib.h>
+
+struct dn_rt_hash_bucket
+{
+ struct dn_route *chain;
+ rwlock_t lock;
+} __attribute__((__aligned__(8)));
+
+extern struct neigh_table dn_neigh_table;
+
+
+static unsigned char dn_hiord_addr[6] = {0xAA,0x00,0x04,0x00,0x00,0x00};
+
+int dn_rt_min_delay = 2*HZ;
+int dn_rt_max_delay = 10*HZ;
+static unsigned long dn_rt_deadline = 0;
+
+static int dn_dst_gc(void);
+static struct dst_entry *dn_dst_check(struct dst_entry *, __u32);
+static struct dst_entry *dn_dst_reroute(struct dst_entry *, struct sk_buff *skb);
+static struct dst_entry *dn_dst_negative_advice(struct dst_entry *);
+static void dn_dst_link_failure(struct sk_buff *);
+static int dn_route_input(struct sk_buff *);
+static void dn_run_flush(unsigned long dummy);
+
+static struct dn_rt_hash_bucket *dn_rt_hash_table;
+static unsigned dn_rt_hash_mask;
+
+static struct timer_list dn_route_timer;
+static struct timer_list dn_rt_flush_timer = { function: dn_run_flush };
+int decnet_dst_gc_interval = 2;
+
+static struct dst_ops dn_dst_ops = {
+ family: PF_DECnet,
+ protocol: __constant_htons(ETH_P_DNA_RT),
+ gc_thresh: 128,
+ gc: dn_dst_gc,
+ check: dn_dst_check,
+ reroute: dn_dst_reroute,
+ negative_advice: dn_dst_negative_advice,
+ link_failure: dn_dst_link_failure,
+ entry_size: sizeof(struct dn_route),
+ entries: ATOMIC_INIT(0),
+};
+
+static __inline__ unsigned dn_hash(unsigned short src, unsigned short dst)
+{
+ unsigned short tmp = src ^ dst;
+ tmp ^= (tmp >> 3);
+ tmp ^= (tmp >> 5);
+ tmp ^= (tmp >> 10);
+ return dn_rt_hash_mask & (unsigned)tmp;
+}
+
+static void SMP_TIMER_NAME(dn_dst_check_expire)(unsigned long dummy)
+{
+ int i;
+ struct dn_route *rt, **rtp;
+ unsigned long now = jiffies;
+ unsigned long expire = 120 * HZ;
+
+ for(i = 0; i <= dn_rt_hash_mask; i++) {
+ rtp = &dn_rt_hash_table[i].chain;
+
+ write_lock(&dn_rt_hash_table[i].lock);
+ while((rt=*rtp) != NULL) {
+ if (atomic_read(&rt->u.dst.__refcnt) ||
+ (now - rt->u.dst.lastuse) < expire) {
+ rtp = &rt->u.rt_next;
+ continue;
+ }
+ *rtp = rt->u.rt_next;
+ rt->u.rt_next = NULL;
+ dst_free(&rt->u.dst);
+ }
+ write_unlock(&dn_rt_hash_table[i].lock);
+
+ if ((jiffies - now) > 0)
+ break;
+ }
+
+ mod_timer(&dn_route_timer, now + decnet_dst_gc_interval * HZ);
+}
+
+SMP_TIMER_DEFINE(dn_dst_check_expire, dn_dst_task);
+
+static int dn_dst_gc(void)
+{
+ struct dn_route *rt, **rtp;
+ int i;
+ unsigned long now = jiffies;
+ unsigned long expire = 10 * HZ;
+
+ for(i = 0; i <= dn_rt_hash_mask; i++) {
+
+ write_lock_bh(&dn_rt_hash_table[i].lock);
+ rtp = &dn_rt_hash_table[i].chain;
+
+ while((rt=*rtp) != NULL) {
+ if (atomic_read(&rt->u.dst.__refcnt) ||
+ (now - rt->u.dst.lastuse) < expire) {
+ rtp = &rt->u.rt_next;
+ continue;
+ }
+ *rtp = rt->u.rt_next;
+ rt->u.rt_next = NULL;
+ dst_free(&rt->u.dst);
+ break;
+ }
+ write_unlock_bh(&dn_rt_hash_table[i].lock);
+ }
+
+ return 0;
+}
+
+static struct dst_entry *dn_dst_check(struct dst_entry *dst, __u32 cookie)
+{
+ dst_release(dst);
+ return NULL;
+}
+
+static struct dst_entry *dn_dst_reroute(struct dst_entry *dst,
+ struct sk_buff *skb)
+{
+ return NULL;
+}
+
+/*
+ * This is called through sendmsg() when you specify MSG_TRYHARD
+ * and there is already a route in cache.
+ */
+static struct dst_entry *dn_dst_negative_advice(struct dst_entry *dst)
+{
+ dst_release(dst);
+ return NULL;
+}
+
+static void dn_dst_link_failure(struct sk_buff *skb)
+{
+ return;
+}
+
+static void dn_insert_route(struct dn_route *rt, unsigned hash)
+{
+ unsigned long now = jiffies;
+
+ write_lock_bh(&dn_rt_hash_table[hash].lock);
+ rt->u.rt_next = dn_rt_hash_table[hash].chain;
+ dn_rt_hash_table[hash].chain = rt;
+
+ dst_hold(&rt->u.dst);
+ rt->u.dst.__use++;
+ rt->u.dst.lastuse = now;
+
+ write_unlock_bh(&dn_rt_hash_table[hash].lock);
+}
+
+void SMP_TIMER_NAME(dn_run_flush)(unsigned long dummy)
+{
+ int i;
+ struct dn_route *rt, *next;
+
+ for(i = 0; i < dn_rt_hash_mask; i++) {
+ write_lock_bh(&dn_rt_hash_table[i].lock);
+
+ if ((rt = xchg(&dn_rt_hash_table[i].chain, NULL)) == NULL)
+ goto nothing_to_declare;
+
+ for(; rt; rt=next) {
+ next = rt->u.rt_next;
+ rt->u.rt_next = NULL;
+ dst_free((struct dst_entry *)rt);
+ }
+
+nothing_to_declare:
+ write_unlock_bh(&dn_rt_hash_table[i].lock);
+ }
+}
+
+SMP_TIMER_DEFINE(dn_run_flush, dn_flush_task);
+
+static spinlock_t dn_rt_flush_lock = SPIN_LOCK_UNLOCKED;
+
+void dn_rt_cache_flush(int delay)
+{
+ unsigned long now = jiffies;
+ int user_mode = !in_interrupt();
+
+ if (delay < 0)
+ delay = dn_rt_min_delay;
+
+ spin_lock_bh(&dn_rt_flush_lock);
+
+ if (del_timer(&dn_rt_flush_timer) && delay > 0 && dn_rt_deadline) {
+ long tmo = (long)(dn_rt_deadline - now);
+
+ if (user_mode && tmo < dn_rt_max_delay - dn_rt_min_delay)
+ tmo = 0;
+
+ if (delay > tmo)
+ delay = tmo;
+ }
+
+ if (delay <= 0) {
+ spin_unlock_bh(&dn_rt_flush_lock);
+ dn_run_flush(0);
+ return;
+ }
+
+ if (dn_rt_deadline == 0)
+ dn_rt_deadline = now + dn_rt_max_delay;
+
+ dn_rt_flush_timer.expires = now + delay;
+ add_timer(&dn_rt_flush_timer);
+ spin_unlock_bh(&dn_rt_flush_lock);
+}
+
+/**
+ * dn_return_short - Return a short packet to its sender
+ * @skb: The packet to return
+ *
+ */
+static int dn_return_short(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb;
+ unsigned char *ptr;
+ dn_address *src;
+ dn_address *dst;
+ dn_address tmp;
+
+ /* Add back headers */
+ skb_push(skb, skb->data - skb->nh.raw);
+
+ if ((skb = skb_unshare(skb, GFP_ATOMIC)) == NULL)
+ return NET_RX_DROP;
+
+ cb = DN_SKB_CB(skb);
+ /* Skip packet length and point to flags */
+ ptr = skb->data + 2;
+ *ptr++ = (cb->rt_flags & ~DN_RT_F_RQR) | DN_RT_F_RTS;
+
+ dst = (dn_address *)ptr;
+ ptr += 2;
+ src = (dn_address *)ptr;
+ ptr += 2;
+ *ptr = 0; /* Zero hop count */
+
+ /* Swap source and destination */
+ tmp = *src;
+ *src = *dst;
+ *dst = tmp;
+
+ skb->pkt_type = PACKET_OUTGOING;
+ dn_rt_finish_output(skb, NULL);
+ return NET_RX_SUCCESS;
+}
+
+/**
+ * dn_return_long - Return a long packet to its sender
+ * @skb: The long format packet to return
+ *
+ */
+static int dn_return_long(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb;
+ unsigned char *ptr;
+ unsigned char *src_addr, *dst_addr;
+ unsigned char tmp[ETH_ALEN];
+
+ /* Add back all headers */
+ skb_push(skb, skb->data - skb->nh.raw);
+
+ if ((skb = skb_unshare(skb, GFP_ATOMIC)) == NULL)
+ return NET_RX_DROP;
+
+ cb = DN_SKB_CB(skb);
+ /* Ignore packet length and point to flags */
+ ptr = skb->data + 2;
+
+ /* Skip padding */
+ if (*ptr & DN_RT_F_PF) {
+ char padlen = (*ptr & ~DN_RT_F_PF);
+ ptr += padlen;
+ }
+
+ *ptr++ = (cb->rt_flags & ~DN_RT_F_RQR) | DN_RT_F_RTS;
+ ptr += 2;
+ dst_addr = ptr;
+ ptr += 8;
+ src_addr = ptr;
+ ptr += 6;
+ *ptr = 0; /* Zero hop count */
+
+ /* Swap source and destination */
+ memcpy(tmp, src_addr, ETH_ALEN);
+ memcpy(src_addr, dst_addr, ETH_ALEN);
+ memcpy(dst_addr, tmp, ETH_ALEN);
+
+ skb->pkt_type = PACKET_OUTGOING;
+ dn_rt_finish_output(skb, tmp);
+ return NET_RX_SUCCESS;
+}
+
+/**
+ * dn_route_rx_packet - Try and find a route for an incoming packet
+ * @skb: The packet to find a route for
+ *
+ * Returns: result of input function if route is found, error code otherwise
+ */
+static int dn_route_rx_packet(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ int err;
+
+ if ((err = dn_route_input(skb)) == 0)
+ return skb->dst->input(skb);
+
+ if (decnet_debug_level & 4) {
+ char *devname = skb->dev ? skb->dev->name : "???";
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ printk(KERN_DEBUG
+ "DECnet: dn_route_rx_packet: rt_flags=0x%02x dev=%s len=%d src=0x%04hx dst=0x%04hx err=%d type=%d\n",
+ (int)cb->rt_flags, devname, skb->len, cb->src, cb->dst,
+ err, skb->pkt_type);
+ }
+
+ if ((skb->pkt_type == PACKET_HOST) && (cb->rt_flags & DN_RT_F_RQR)) {
+ switch(cb->rt_flags & DN_RT_PKT_MSK) {
+ case DN_RT_PKT_SHORT:
+ return dn_return_short(skb);
+ case DN_RT_PKT_LONG:
+ return dn_return_long(skb);
+ }
+ }
+
+ kfree_skb(skb);
+ return NET_RX_DROP;
+}
+
+static int dn_route_rx_long(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ unsigned char *ptr = skb->data;
+
+ if (skb->len < 21) /* 20 for long header, 1 for shortest nsp */
+ goto drop_it;
+
+ skb_pull(skb, 20);
+ skb->h.raw = skb->data;
+
+ /* Destination info */
+ ptr += 2;
+ cb->dst = dn_htons(dn_eth2dn(ptr));
+ if (memcmp(ptr, dn_hiord_addr, 4) != 0)
+ goto drop_it;
+ ptr += 6;
+
+
+ /* Source info */
+ ptr += 2;
+ cb->src = dn_htons(dn_eth2dn(ptr));
+ if (memcmp(ptr, dn_hiord_addr, 4) != 0)
+ goto drop_it;
+ ptr += 6;
+ /* Other junk */
+ ptr++;
+ cb->hops = *ptr++; /* Visit Count */
+
+ return NF_HOOK(PF_DECnet, NF_DN_PRE_ROUTING, skb, skb->dev, NULL, dn_route_rx_packet);
+
+drop_it:
+ kfree_skb(skb);
+ return NET_RX_DROP;
+}
+
+
+
+static int dn_route_rx_short(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ unsigned char *ptr = skb->data;
+
+ if (skb->len < 6) /* 5 for short header + 1 for shortest nsp */
+ goto drop_it;
+
+ skb_pull(skb, 5);
+ skb->h.raw = skb->data;
+
+ cb->dst = *(dn_address *)ptr;
+ ptr += 2;
+ cb->src = *(dn_address *)ptr;
+ ptr += 2;
+ cb->hops = *ptr & 0x3f;
+
+ return NF_HOOK(PF_DECnet, NF_DN_PRE_ROUTING, skb, skb->dev, NULL, dn_route_rx_packet);
+
+drop_it:
+ kfree_skb(skb);
+ return NET_RX_DROP;
+}
+
+static int dn_route_discard(struct sk_buff *skb)
+{
+ /*
+ * I know we drop the packet here, but thats considered success in
+ * this case
+ */
+ kfree_skb(skb);
+ return NET_RX_SUCCESS;
+}
+
+static int dn_route_ptp_hello(struct sk_buff *skb)
+{
+ dn_dev_hello(skb);
+ dn_neigh_pointopoint_hello(skb);
+ return NET_RX_SUCCESS;
+}
+
+int dn_route_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
+{
+ struct dn_skb_cb *cb;
+ unsigned char flags = 0;
+ __u16 len = dn_ntohs(*(__u16 *)skb->data);
+ struct dn_dev *dn = (struct dn_dev *)dev->dn_ptr;
+ unsigned char padlen = 0;
+
+ if (dn == NULL)
+ goto dump_it;
+
+ if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
+ goto out;
+
+ skb_pull(skb, 2);
+
+ if (len > skb->len)
+ goto dump_it;
+
+ skb_trim(skb, len);
+
+ flags = *skb->data;
+
+ cb = DN_SKB_CB(skb);
+ cb->stamp = jiffies;
+ cb->iif = dev->ifindex;
+
+ /*
+ * If we have padding, remove it.
+ */
+ if (flags & DN_RT_F_PF) {
+ padlen = flags & ~DN_RT_F_PF;
+ skb_pull(skb, padlen);
+ flags = *skb->data;
+ }
+
+ skb->nh.raw = skb->data;
+
+ /*
+ * Weed out future version DECnet
+ */
+ if (flags & DN_RT_F_VER)
+ goto dump_it;
+
+ cb->rt_flags = flags;
+
+ if (decnet_debug_level & 1)
+ printk(KERN_DEBUG
+ "dn_route_rcv: got 0x%02x from %s [%d %d %d]\n",
+ (int)flags, (dev) ? dev->name : "???", len, skb->len,
+ padlen);
+
+ if (flags & DN_RT_PKT_CNTL) {
+ switch(flags & DN_RT_CNTL_MSK) {
+ case DN_RT_PKT_INIT:
+ dn_dev_init_pkt(skb);
+ break;
+ case DN_RT_PKT_VERI:
+ dn_dev_veri_pkt(skb);
+ break;
+ }
+
+ if (dn->parms.state != DN_DEV_S_RU)
+ goto dump_it;
+
+ switch(flags & DN_RT_CNTL_MSK) {
+ case DN_RT_PKT_HELO:
+ return NF_HOOK(PF_DECnet, NF_DN_HELLO, skb, skb->dev, NULL, dn_route_ptp_hello);
+
+ case DN_RT_PKT_L1RT:
+ case DN_RT_PKT_L2RT:
+ return NF_HOOK(PF_DECnet, NF_DN_ROUTE, skb, skb->dev, NULL, dn_route_discard);
+ case DN_RT_PKT_ERTH:
+ return NF_HOOK(PF_DECnet, NF_DN_HELLO, skb, skb->dev, NULL, dn_neigh_router_hello);
+
+ case DN_RT_PKT_EEDH:
+ return NF_HOOK(PF_DECnet, NF_DN_HELLO, skb, skb->dev, NULL, dn_neigh_endnode_hello);
+ }
+ } else {
+ if (dn->parms.state != DN_DEV_S_RU)
+ goto dump_it;
+
+ skb_pull(skb, 1); /* Pull flags */
+
+ switch(flags & DN_RT_PKT_MSK) {
+ case DN_RT_PKT_LONG:
+ return dn_route_rx_long(skb);
+ case DN_RT_PKT_SHORT:
+ return dn_route_rx_short(skb);
+ }
+ }
+
+dump_it:
+ kfree_skb(skb);
+out:
+ return NET_RX_DROP;
+}
+
+static int dn_output(struct sk_buff *skb)
+{
+ struct dst_entry *dst = skb->dst;
+ struct dn_route *rt = (struct dn_route *)dst;
+ struct net_device *dev = dst->dev;
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ struct neighbour *neigh;
+
+ int err = -EINVAL;
+
+ if ((neigh = dst->neighbour) == NULL)
+ goto error;
+
+ skb->dev = dev;
+
+ cb->src = rt->rt_saddr;
+ cb->dst = rt->rt_daddr;
+
+ /*
+ * Always set the Intra-Ethernet bit on all outgoing packets
+ * originated on this node. Only valid flag from upper layers
+ * is return-to-sender-requested. Set hop count to 0 too.
+ */
+ cb->rt_flags &= ~DN_RT_F_RQR;
+ cb->rt_flags |= DN_RT_F_IE;
+ cb->hops = 0;
+
+ return NF_HOOK(PF_DECnet, NF_DN_LOCAL_OUT, skb, NULL, dev, neigh->output);
+
+error:
+ if (net_ratelimit())
+ printk(KERN_DEBUG "dn_output: This should not happen\n");
+
+ kfree_skb(skb);
+
+ return err;
+}
+
+#ifdef CONFIG_DECNET_ROUTER
+static int dn_forward(struct sk_buff *skb)
+{
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ struct dst_entry *dst = skb->dst;
+ struct neighbour *neigh;
+ struct net_device *dev = skb->dev;
+ int err = -EINVAL;
+
+ if ((neigh = dst->neighbour) == NULL)
+ goto error;
+
+ /*
+ * Hop count exceeded.
+ */
+ err = NET_RX_DROP;
+ if (++cb->hops > 30)
+ goto drop;
+
+ skb->dev = dst->dev;
+
+ /*
+ * If packet goes out same interface it came in on, then set
+ * the Intra-Ethernet bit. This has no effect for short
+ * packets, so we don't need to test for them here.
+ */
+ if (cb->iif == dst->dev->ifindex)
+ cb->rt_flags |= DN_RT_F_IE;
+ else
+ cb->rt_flags &= ~DN_RT_F_IE;
+
+ return NF_HOOK(PF_DECnet, NF_DN_FORWARD, skb, dev, skb->dev, neigh->output);
+
+
+error:
+ if (net_ratelimit())
+ printk(KERN_DEBUG "dn_forward: This should not happen\n");
+drop:
+ kfree_skb(skb);
+
+ return err;
+}
+#endif
+
+/*
+ * Drop packet. This is used for endnodes and for
+ * when we should not be forwarding packets from
+ * this dest.
+ */
+static int dn_blackhole(struct sk_buff *skb)
+{
+ kfree_skb(skb);
+ return NET_RX_DROP;
+}
+
+/*
+ * Used to catch bugs. This should never normally get
+ * called.
+ */
+static int dn_rt_bug(struct sk_buff *skb)
+{
+ if (net_ratelimit()) {
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+
+ printk(KERN_DEBUG "dn_rt_bug: skb from:%04x to:%04x\n",
+ cb->src, cb->dst);
+ }
+
+ kfree_skb(skb);
+
+ return NET_RX_BAD;
+}
+
+static int dn_route_output_slow(struct dst_entry **pprt, dn_address dst, dn_address src, int flags)
+{
+ struct dn_route *rt = NULL;
+ struct net_device *dev = decnet_default_device;
+ struct neighbour *neigh = NULL;
+ struct dn_dev *dn_db;
+ unsigned hash;
+#ifdef CONFIG_DECNET_ROUTER
+ struct dn_fib_key key;
+ struct dn_fib_res res;
+ int err;
+
+ key.src = src;
+ key.dst = dst;
+ key.iif = 0;
+ key.oif = 0;
+ key.fwmark = 0;
+ key.scope = RT_SCOPE_UNIVERSE;
+
+ if ((err = dn_fib_lookup(&key, &res)) == 0) {
+ switch(res.type) {
+ case RTN_UNICAST:
+ /*
+ * This method of handling multipath
+ * routes is a hack and will change.
+ * It works for now though.
+ */
+ if (res.fi->fib_nhs)
+ dn_fib_select_multipath(&key, &res);
+ neigh = __neigh_lookup(&dn_neigh_table, &DN_FIB_RES_GW(res), DN_FIB_RES_DEV(res), 1);
+ err = -ENOBUFS;
+ if (!neigh)
+ break;
+ err = 0;
+ break;
+ case RTN_UNREACHABLE:
+ err = -EHOSTUNREACH;
+ break;
+ default:
+ err = -EINVAL;
+ }
+ dn_fib_res_put(&res);
+ if (err < 0)
+ return err;
+ goto got_route;
+ }
+
+ if (err != -ESRCH)
+ return err;
+#endif
+
+ /* Look in On-Ethernet cache first */
+ if (!(flags & MSG_TRYHARD)) {
+ if ((neigh = neigh_lookup_nodev(&dn_neigh_table, &dst)) != NULL)
+ goto got_route;
+ }
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ dn_db = dev->dn_ptr;
+
+ if (dn_db == NULL)
+ return -EINVAL;
+
+ /* Try default router */
+ if ((neigh = neigh_clone(dn_db->router)) != NULL)
+ goto got_route;
+
+ /* Send to default device (and hope for the best) if above fail */
+ if ((neigh = __neigh_lookup(&dn_neigh_table, &dst, dev, 1)) != NULL)
+ goto got_route;
+
+
+ return -EINVAL;
+
+got_route:
+
+ if ((rt = dst_alloc(&dn_dst_ops)) == NULL) {
+ neigh_release(neigh);
+ return -EINVAL;
+ }
+
+ dn_db = (struct dn_dev *)neigh->dev->dn_ptr;
+
+ rt->key.saddr = src;
+ rt->rt_saddr = src;
+ rt->key.daddr = dst;
+ rt->rt_daddr = dst;
+ rt->key.oif = neigh ? neigh->dev->ifindex : -1;
+ rt->key.iif = 0;
+ rt->key.fwmark = 0;
+
+ rt->u.dst.neighbour = neigh;
+ rt->u.dst.dev = neigh ? neigh->dev : NULL;
+ if (rt->u.dst.dev)
+ dev_hold(rt->u.dst.dev);
+ rt->u.dst.lastuse = jiffies;
+ rt->u.dst.output = dn_output;
+ rt->u.dst.input = dn_rt_bug;
+
+ if (dn_dev_islocal(neigh->dev, rt->rt_daddr))
+ rt->u.dst.input = dn_nsp_rx;
+
+ hash = dn_hash(rt->key.saddr, rt->key.daddr);
+ dn_insert_route(rt, hash);
+ *pprt = &rt->u.dst;
+
+ return 0;
+}
+
+int dn_route_output(struct dst_entry **pprt, dn_address dst, dn_address src, int flags)
+{
+ unsigned hash = dn_hash(src, dst);
+ struct dn_route *rt = NULL;
+
+ if (!(flags & MSG_TRYHARD)) {
+ read_lock_bh(&dn_rt_hash_table[hash].lock);
+ for(rt = dn_rt_hash_table[hash].chain; rt; rt = rt->u.rt_next) {
+ if ((dst == rt->key.daddr) &&
+ (src == rt->key.saddr) &&
+ (rt->key.iif == 0) &&
+ (rt->key.oif != 0)) {
+ rt->u.dst.lastuse = jiffies;
+ dst_hold(&rt->u.dst);
+ rt->u.dst.__use++;
+ read_unlock_bh(&dn_rt_hash_table[hash].lock);
+ *pprt = &rt->u.dst;
+ return 0;
+ }
+ }
+ read_unlock_bh(&dn_rt_hash_table[hash].lock);
+ }
+
+ return dn_route_output_slow(pprt, dst, src, flags);
+}
+
+static int dn_route_input_slow(struct sk_buff *skb)
+{
+ struct dn_route *rt = NULL;
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ struct net_device *dev = skb->dev;
+ struct dn_dev *dn_db;
+ struct neighbour *neigh = NULL;
+ int (*dnrt_input)(struct sk_buff *skb);
+ int (*dnrt_output)(struct sk_buff *skb);
+ u32 fwmark = 0;
+ unsigned hash;
+ dn_address saddr = cb->src;
+ dn_address daddr = cb->dst;
+#ifdef CONFIG_DECNET_ROUTER
+ struct dn_fib_key key;
+ struct dn_fib_res res;
+ int err;
+#endif
+
+ if (dev == NULL)
+ return -EINVAL;
+
+ if ((dn_db = dev->dn_ptr) == NULL)
+ return -EINVAL;
+
+ /*
+ * In this case we've just received a packet from a source
+ * outside ourselves pretending to come from us. We don't
+ * allow it any further to prevent routing loops, spoofing and
+ * other nasties. Loopback packets already have the dst attached
+ * so this only affects packets which have originated elsewhere.
+ */
+ if (dn_dev_islocal(dev, cb->src))
+ return -ENOTUNIQ;
+
+ /*
+ * Default is to create a drop everything entry
+ */
+ dnrt_input = dn_blackhole;
+ dnrt_output = dn_rt_bug;
+
+ /*
+ * Is the destination us ?
+ */
+ if (!dn_dev_islocal(dev, cb->dst))
+ goto non_local_input;
+
+ /*
+ * Local input... find source of skb
+ */
+ dnrt_input = dn_nsp_rx;
+ dnrt_output = dn_output;
+ saddr = cb->dst;
+ daddr = cb->src;
+
+ if ((neigh = neigh_lookup(&dn_neigh_table, &cb->src, dev)) != NULL)
+ goto add_entry;
+
+ if (dn_db->router && ((neigh = neigh_clone(dn_db->router)) != NULL))
+ goto add_entry;
+
+ neigh = neigh_create(&dn_neigh_table, &cb->src, dev);
+ if (!IS_ERR(neigh)) {
+ if (dev->type == ARPHRD_ETHER)
+ memcpy(neigh->ha, skb->mac.ethernet->h_source, ETH_ALEN);
+ goto add_entry;
+ }
+
+ return PTR_ERR(neigh);
+
+non_local_input:
+
+#ifdef CONFIG_DECNET_ROUTER
+ /*
+ * Destination is another node... find next hop in
+ * routing table here.
+ */
+
+ key.src = cb->src;
+ key.dst = cb->dst;
+ key.iif = dev->ifindex;
+ key.oif = 0;
+ key.scope = RT_SCOPE_UNIVERSE;
+
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+ key.fwmark = skb->nfmark;
+#else
+ key.fwmark = 0;
+#endif
+
+ if ((err = dn_fib_lookup(&key, &res)) == 0) {
+ switch(res.type) {
+ case RTN_UNICAST:
+ if (res.fi->fib_nhs)
+ dn_fib_select_multipath(&key, &res);
+ neigh = __neigh_lookup(&dn_neigh_table, &DN_FIB_RES_GW(res), DN_FIB_RES_DEV(res), 1);
+ err = -ENOBUFS;
+ if (!neigh)
+ break;
+ err = 0;
+ dnrt_input = dn_forward;
+ fwmark = key.fwmark;
+ break;
+ case RTN_UNREACHABLE:
+ dnrt_input = dn_blackhole;
+ fwmark = key.fwmark;
+ break;
+ default:
+ err = -EINVAL;
+ }
+ dn_fib_res_put(&res);
+ if (err < 0)
+ return err;
+ goto add_entry;
+ }
+
+ return err;
+
+#endif /* CONFIG_DECNET_ROUTER */
+
+add_entry:
+
+ if ((rt = dst_alloc(&dn_dst_ops)) == NULL) {
+ neigh_release(neigh);
+ return -EINVAL;
+ }
+
+ rt->key.saddr = cb->src;
+ rt->rt_saddr = saddr;
+ rt->key.daddr = cb->dst;
+ rt->rt_daddr = daddr;
+ rt->key.oif = 0;
+ rt->key.iif = dev->ifindex;
+ rt->key.fwmark = fwmark;
+
+ rt->u.dst.neighbour = neigh;
+ rt->u.dst.dev = neigh ? neigh->dev : NULL;
+ if (rt->u.dst.dev)
+ dev_hold(rt->u.dst.dev);
+ rt->u.dst.lastuse = jiffies;
+ rt->u.dst.output = dnrt_output;
+ rt->u.dst.input = dnrt_input;
+
+ hash = dn_hash(rt->key.saddr, rt->key.daddr);
+ dn_insert_route(rt, hash);
+ skb->dst = (struct dst_entry *)rt;
+
+ return 0;
+}
+
+int dn_route_input(struct sk_buff *skb)
+{
+ struct dn_route *rt;
+ struct dn_skb_cb *cb = DN_SKB_CB(skb);
+ unsigned hash = dn_hash(cb->src, cb->dst);
+
+ if (skb->dst)
+ return 0;
+
+ read_lock(&dn_rt_hash_table[hash].lock);
+ for(rt = dn_rt_hash_table[hash].chain; rt != NULL; rt = rt->u.rt_next) {
+ if ((rt->key.saddr == cb->src) &&
+ (rt->key.daddr == cb->dst) &&
+ (rt->key.oif == 0) &&
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+ (rt->key.fwmark == skb->nfmark) &&
+#endif
+ (rt->key.iif == cb->iif)) {
+ rt->u.dst.lastuse = jiffies;
+ dst_hold(&rt->u.dst);
+ rt->u.dst.__use++;
+ read_unlock(&dn_rt_hash_table[hash].lock);
+ skb->dst = (struct dst_entry *)rt;
+ return 0;
+ }
+ }
+ read_unlock(&dn_rt_hash_table[hash].lock);
+
+ return dn_route_input_slow(skb);
+}
+
+static int dn_rt_fill_info(struct sk_buff *skb, u32 pid, u32 seq, int event, int nowait)
+{
+ struct dn_route *rt = (struct dn_route *)skb->dst;
+ struct rtmsg *r;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+ nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*r));
+ r = NLMSG_DATA(nlh);
+ nlh->nlmsg_flags = nowait ? NLM_F_MULTI : 0;
+ r->rtm_family = AF_DECnet;
+ r->rtm_dst_len = 16;
+ r->rtm_src_len = 16;
+ r->rtm_tos = 0;
+ r->rtm_table = 0;
+ r->rtm_type = 0;
+ r->rtm_flags = 0;
+ r->rtm_scope = RT_SCOPE_UNIVERSE;
+ r->rtm_protocol = RTPROT_UNSPEC;
+ RTA_PUT(skb, RTA_DST, 2, &rt->rt_daddr);
+ RTA_PUT(skb, RTA_SRC, 2, &rt->rt_saddr);
+ if (rt->u.dst.dev)
+ RTA_PUT(skb, RTA_OIF, sizeof(int), &rt->u.dst.dev->ifindex);
+ if (rt->u.dst.window)
+ RTA_PUT(skb, RTAX_WINDOW, sizeof(unsigned), &rt->u.dst.window);
+ if (rt->u.dst.rtt)
+ RTA_PUT(skb, RTAX_RTT, sizeof(unsigned), &rt->u.dst.rtt);
+
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+/*
+ * This is called by both endnodes and routers now.
+ */
+int dn_cache_getroute(struct sk_buff *in_skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct rtattr **rta = arg;
+ struct dn_route *rt = NULL;
+ struct dn_skb_cb *cb;
+ dn_address dst = 0;
+ dn_address src = 0;
+ int iif = 0;
+ int err;
+ struct sk_buff *skb;
+
+ skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (skb == NULL)
+ return -ENOBUFS;
+ skb->mac.raw = skb->data;
+ cb = DN_SKB_CB(skb);
+
+ if (rta[RTA_SRC-1])
+ memcpy(&src, RTA_DATA(rta[RTA_SRC-1]), 2);
+ if (rta[RTA_DST-1])
+ memcpy(&dst, RTA_DATA(rta[RTA_DST-1]), 2);
+ if (rta[RTA_IIF-1])
+ memcpy(&iif, RTA_DATA(rta[RTA_IIF-1]), sizeof(int));
+
+ if (iif) {
+ struct net_device *dev;
+ if ((dev = dev_get_by_index(iif)) == NULL) {
+ kfree_skb(skb);
+ return -ENODEV;
+ }
+ if (!dev->dn_ptr) {
+ dev_put(dev);
+ kfree_skb(skb);
+ return -ENODEV;
+ }
+ skb->protocol = __constant_htons(ETH_P_DNA_RT);
+ skb->dev = dev;
+ cb->src = src;
+ cb->dst = dst;
+ local_bh_disable();
+ err = dn_route_input(skb);
+ local_bh_enable();
+ memset(cb, 0, sizeof(struct dn_skb_cb));
+ rt = (struct dn_route *)skb->dst;
+ } else {
+ err = dn_route_output((struct dst_entry **)&rt, dst, src, 0);
+ }
+
+ if (!err && rt->u.dst.error)
+ err = rt->u.dst.error;
+ if (skb->dev)
+ dev_put(skb->dev);
+ skb->dev = NULL;
+ if (err)
+ goto out_free;
+ skb->dst = &rt->u.dst;
+
+ NETLINK_CB(skb).dst_pid = NETLINK_CB(in_skb).pid;
+
+ err = dn_rt_fill_info(skb, NETLINK_CB(in_skb).pid, nlh->nlmsg_seq, RTM_NEWROUTE, 0);
+
+ if (err == 0)
+ goto out_free;
+ if (err < 0) {
+ err = -EMSGSIZE;
+ goto out_free;
+ }
+
+ err = netlink_unicast(rtnl, skb, NETLINK_CB(in_skb).pid, MSG_DONTWAIT);
+
+ return err;
+
+out_free:
+ kfree_skb(skb);
+ return err;
+}
+
+/*
+ * For routers, this is called from dn_fib_dump, but for endnodes its
+ * called directly from the rtnetlink dispatch table.
+ */
+int dn_cache_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ struct dn_route *rt;
+ int h, s_h;
+ int idx, s_idx;
+
+ if (NLMSG_PAYLOAD(cb->nlh, 0) < sizeof(struct rtmsg))
+ return -EINVAL;
+ if (!(((struct rtmsg *)NLMSG_DATA(cb->nlh))->rtm_flags&RTM_F_CLONED))
+ return 0;
+
+ s_h = cb->args[0];
+ s_idx = idx = cb->args[1];
+ for(h = 0; h <= dn_rt_hash_mask; h++) {
+ if (h < s_h)
+ continue;
+ if (h > s_h)
+ s_idx = 0;
+ read_lock_bh(&dn_rt_hash_table[h].lock);
+ for(rt = dn_rt_hash_table[h].chain, idx = 0; rt; rt = rt->u.rt_next, idx++) {
+ if (idx < s_idx)
+ continue;
+ skb->dst = dst_clone(&rt->u.dst);
+ if (dn_rt_fill_info(skb, NETLINK_CB(cb->skb).pid,
+ cb->nlh->nlmsg_seq, RTM_NEWROUTE, 1) <= 0) {
+ dst_release(xchg(&skb->dst, NULL));
+ read_unlock_bh(&dn_rt_hash_table[h].lock);
+ goto done;
+ }
+ dst_release(xchg(&skb->dst, NULL));
+ }
+ read_unlock_bh(&dn_rt_hash_table[h].lock);
+ }
+
+done:
+ cb->args[0] = h;
+ cb->args[1] = idx;
+ return skb->len;
+}
+
+#ifdef CONFIG_PROC_FS
+
+static int decnet_cache_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+ struct dn_route *rt;
+ int i;
+ char buf1[DN_ASCBUF_LEN], buf2[DN_ASCBUF_LEN];
+
+ for(i = 0; i <= dn_rt_hash_mask; i++) {
+ read_lock_bh(&dn_rt_hash_table[i].lock);
+ rt = dn_rt_hash_table[i].chain;
+ for(; rt != NULL; rt = rt->u.rt_next) {
+ len += sprintf(buffer + len, "%-8s %-7s %-7s %04d %04d %04d\n",
+ rt->u.dst.dev ? rt->u.dst.dev->name : "*",
+ dn_addr2asc(dn_ntohs(rt->rt_daddr), buf1),
+ dn_addr2asc(dn_ntohs(rt->rt_saddr), buf2),
+ atomic_read(&rt->u.dst.__refcnt),
+ rt->u.dst.__use,
+ (int)rt->u.dst.rtt
+ );
+
+
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (pos > offset + length)
+ break;
+ }
+ read_unlock_bh(&dn_rt_hash_table[i].lock);
+ if (pos > offset + length)
+ break;
+ }
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return(len);
+}
+
+#endif /* CONFIG_PROC_FS */
+
+void __init dn_route_init(void)
+{
+ int i, goal, order;
+
+ dn_dst_ops.kmem_cachep = kmem_cache_create("dn_dst_cache",
+ sizeof(struct dn_route),
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+
+ if (!dn_dst_ops.kmem_cachep)
+ panic("DECnet: Failed to allocate dn_dst_cache\n");
+
+ dn_route_timer.function = dn_dst_check_expire;
+ dn_route_timer.expires = jiffies + decnet_dst_gc_interval * HZ;
+ add_timer(&dn_route_timer);
+
+ goal = num_physpages >> (26 - PAGE_SHIFT);
+
+ for(order = 0; (1UL << order) < goal; order++)
+ /* NOTHING */;
+
+ /*
+ * Only want 1024 entries max, since the table is very, very unlikely
+ * to be larger than that.
+ */
+ while(order && ((((1UL << order) * PAGE_SIZE) /
+ sizeof(struct dn_rt_hash_bucket)) >= 2048))
+ order--;
+
+ do {
+ dn_rt_hash_mask = (1UL << order) * PAGE_SIZE /
+ sizeof(struct dn_rt_hash_bucket);
+ while(dn_rt_hash_mask & (dn_rt_hash_mask - 1))
+ dn_rt_hash_mask--;
+ dn_rt_hash_table = (struct dn_rt_hash_bucket *)
+ __get_free_pages(GFP_ATOMIC, order);
+ } while (dn_rt_hash_table == NULL && --order > 0);
+
+ if (!dn_rt_hash_table)
+ panic("Failed to allocate DECnet route cache hash table\n");
+
+ printk(KERN_INFO
+ "DECnet: Routing cache hash table of %u buckets, %ldKbytes\n",
+ dn_rt_hash_mask,
+ (long)(dn_rt_hash_mask*sizeof(struct dn_rt_hash_bucket))/1024);
+
+ dn_rt_hash_mask--;
+ for(i = 0; i <= dn_rt_hash_mask; i++) {
+ dn_rt_hash_table[i].lock = RW_LOCK_UNLOCKED;
+ dn_rt_hash_table[i].chain = NULL;
+ }
+
+ dn_dst_ops.gc_thresh = (dn_rt_hash_mask + 1);
+
+#ifdef CONFIG_PROC_FS
+ proc_net_create("decnet_cache",0,decnet_cache_get_info);
+#endif /* CONFIG_PROC_FS */
+}
+
+void __exit dn_route_cleanup(void)
+{
+ del_timer(&dn_route_timer);
+ dn_run_flush(0);
+
+ proc_net_remove("decnet_cache");
+}
+
diff --git a/uClinux-2.4.31-uc0/net/decnet/dn_rules.c b/uClinux-2.4.31-uc0/net/decnet/dn_rules.c
new file mode 100644
index 0000000..fee9dbd
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/dn_rules.c
@@ -0,0 +1,371 @@
+
+/*
+ * DECnet An implementation of the DECnet protocol suite for the LINUX
+ * operating system. DECnet is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * DECnet Routing Forwarding Information Base (Rules)
+ *
+ * Author: Steve Whitehouse <SteveW@ACM.org>
+ * Mostly copied from Alexey Kuznetsov's ipv4/fib_rules.c
+ *
+ *
+ * Changes:
+ *
+ */
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/proc_fs.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/dn.h>
+#include <net/dn_fib.h>
+#include <net/dn_neigh.h>
+#include <net/dn_dev.h>
+
+struct dn_fib_rule
+{
+ struct dn_fib_rule *r_next;
+ atomic_t r_clntref;
+ u32 r_preference;
+ unsigned char r_table;
+ unsigned char r_action;
+ unsigned char r_dst_len;
+ unsigned char r_src_len;
+ dn_address r_src;
+ dn_address r_srcmask;
+ dn_address r_dst;
+ dn_address r_dstmask;
+ u8 r_flags;
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+ u32 r_fwmark;
+#endif
+ int r_ifindex;
+ char r_ifname[IFNAMSIZ];
+ int r_dead;
+};
+
+static struct dn_fib_rule default_rule = {
+ r_clntref: ATOMIC_INIT(2),
+ r_preference: 0x7fff,
+ r_table: DN_DEFAULT_TABLE,
+ r_action: RTN_UNICAST
+};
+
+static struct dn_fib_rule *dn_fib_rules = &default_rule;
+static rwlock_t dn_fib_rules_lock = RW_LOCK_UNLOCKED;
+
+
+int dn_fib_rtm_delrule(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct rtattr **rta = arg;
+ struct rtmsg *rtm = NLMSG_DATA(nlh);
+ struct dn_fib_rule *r, **rp;
+ int err = -ESRCH;
+
+ for(rp=&dn_fib_rules; (r=*rp) != NULL; rp = &r->r_next) {
+ if ((!rta[RTA_SRC-1] || memcmp(RTA_DATA(rta[RTA_SRC-1]), &r->r_src, 2) == 0) &&
+ rtm->rtm_src_len == r->r_src_len &&
+ rtm->rtm_dst_len == r->r_dst_len &&
+ (!rta[RTA_DST-1] || memcmp(RTA_DATA(rta[RTA_DST-1]), &r->r_dst, 2) == 0) &&
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+ (!rta[RTA_PROTOINFO-1] || memcmp(RTA_DATA(rta[RTA_PROTOINFO-1]), &r->r_fwmark, 4) == 0) &&
+#endif
+ (!rtm->rtm_type || rtm->rtm_type == r->r_action) &&
+ (!rta[RTA_PRIORITY-1] || memcmp(RTA_DATA(rta[RTA_PRIORITY-1]), &r->r_preference, 4) == 0) &&
+ (!rta[RTA_IIF-1] || strcmp(RTA_DATA(rta[RTA_IIF-1]), r->r_ifname) == 0) &&
+ (!rtm->rtm_table || (r && rtm->rtm_table == r->r_table))) {
+
+ err = -EPERM;
+ if (r == &default_rule)
+ break;
+
+ write_lock_bh(&dn_fib_rules_lock);
+ *rp = r->r_next;
+ r->r_dead = 1;
+ write_unlock_bh(&dn_fib_rules_lock);
+ dn_fib_rule_put(r);
+ err = 0;
+ break;
+ }
+ }
+
+ return err;
+}
+
+void dn_fib_rule_put(struct dn_fib_rule *r)
+{
+ if (atomic_dec_and_test(&r->r_clntref)) {
+ if (r->r_dead)
+ kfree(r);
+ else
+ printk(KERN_DEBUG "Attempt to free alive dn_fib_rule\n");
+ }
+}
+
+
+int dn_fib_rtm_newrule(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct rtattr **rta = arg;
+ struct rtmsg *rtm = NLMSG_DATA(nlh);
+ struct dn_fib_rule *r, *new_r, **rp;
+ unsigned char table_id;
+
+ if (rtm->rtm_src_len > 16 || rtm->rtm_dst_len > 16)
+ return -EINVAL;
+
+ if (rta[RTA_IIF-1] && RTA_PAYLOAD(rta[RTA_IIF-1]) > IFNAMSIZ)
+ return -EINVAL;
+
+ if (rtm->rtm_type == RTN_NAT)
+ return -EINVAL;
+
+ table_id = rtm->rtm_table;
+ if (table_id == RT_TABLE_UNSPEC) {
+ struct dn_fib_table *tb;
+ if (rtm->rtm_type == RTN_UNICAST) {
+ if ((tb = dn_fib_empty_table()) == NULL)
+ return -ENOBUFS;
+ table_id = tb->n;
+ }
+ }
+
+ new_r = kmalloc(sizeof(*new_r), GFP_KERNEL);
+ if (!new_r)
+ return -ENOMEM;
+ memset(new_r, 0, sizeof(*new_r));
+ if (rta[RTA_SRC-1])
+ memcpy(&new_r->r_src, RTA_DATA(rta[RTA_SRC-1]), 2);
+ if (rta[RTA_DST-1])
+ memcpy(&new_r->r_dst, RTA_DATA(rta[RTA_DST-1]), 2);
+ new_r->r_src_len = rtm->rtm_src_len;
+ new_r->r_dst_len = rtm->rtm_dst_len;
+ new_r->r_srcmask = dnet_make_mask(rtm->rtm_src_len);
+ new_r->r_dstmask = dnet_make_mask(rtm->rtm_dst_len);
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+ if (rta[RTA_PROTOINFO-1])
+ memcpy(&new_r->r_fwmark, RTA_DATA(rta[RTA_PROTOINFO-1]), 4);
+#endif
+ new_r->r_action = rtm->rtm_type;
+ new_r->r_flags = rtm->rtm_flags;
+ if (rta[RTA_PRIORITY-1])
+ memcpy(&new_r->r_preference, RTA_DATA(rta[RTA_PRIORITY-1]), 4);
+ new_r->r_table = table_id;
+ if (rta[RTA_IIF-1]) {
+ struct net_device *dev;
+ memcpy(new_r->r_ifname, RTA_DATA(rta[RTA_IIF-1]), IFNAMSIZ);
+ new_r->r_ifname[IFNAMSIZ-1] = 0;
+ new_r->r_ifindex = -1;
+ dev = __dev_get_by_name(new_r->r_ifname);
+ if (dev)
+ new_r->r_ifindex = dev->ifindex;
+ }
+
+ rp = &dn_fib_rules;
+ if (!new_r->r_preference) {
+ r = dn_fib_rules;
+ if (r && (r = r->r_next) != NULL) {
+ rp = &dn_fib_rules->r_next;
+ if (r->r_preference)
+ new_r->r_preference = r->r_preference - 1;
+ }
+ }
+
+ while((r=*rp) != NULL) {
+ if (r->r_preference > new_r->r_preference)
+ break;
+ rp = &r->r_next;
+ }
+
+ new_r->r_next = r;
+ atomic_inc(&new_r->r_clntref);
+ write_lock_bh(&dn_fib_rules_lock);
+ *rp = new_r;
+ write_unlock_bh(&dn_fib_rules_lock);
+ return 0;
+}
+
+
+int dn_fib_lookup(struct dn_fib_key *key, struct dn_fib_res *res)
+{
+ struct dn_fib_rule *r, *policy;
+ struct dn_fib_table *tb;
+ dn_address saddr = key->src;
+ dn_address daddr = key->dst;
+ int err;
+
+ read_lock(&dn_fib_rules_lock);
+ for(r = dn_fib_rules; r; r = r->r_next) {
+ if (((saddr^r->r_src) & r->r_srcmask) ||
+ ((daddr^r->r_dst) & r->r_dstmask) ||
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+ (r->r_fwmark && r->r_fwmark != key->fwmark) ||
+#endif
+ (r->r_ifindex && r->r_ifindex != key->iif))
+ continue;
+
+ switch(r->r_action) {
+ case RTN_UNICAST:
+ policy = r;
+ break;
+ case RTN_UNREACHABLE:
+ read_unlock(&dn_fib_rules_lock);
+ return -ENETUNREACH;
+ default:
+ case RTN_BLACKHOLE:
+ read_unlock(&dn_fib_rules_lock);
+ return -EINVAL;
+ case RTN_PROHIBIT:
+ read_unlock(&dn_fib_rules_lock);
+ return -EACCES;
+ }
+
+ if ((tb = dn_fib_get_table(r->r_table, 0)) == NULL)
+ continue;
+ err = tb->lookup(tb, key, res);
+ if (err == 0) {
+ res->r = policy;
+ if (policy)
+ atomic_inc(&policy->r_clntref);
+ read_unlock(&dn_fib_rules_lock);
+ return 0;
+ }
+ if (err < 0 && err != -EAGAIN) {
+ read_unlock(&dn_fib_rules_lock);
+ return err;
+ }
+ }
+
+ read_unlock(&dn_fib_rules_lock);
+ return -ESRCH;
+}
+
+static void dn_fib_rules_detach(struct net_device *dev)
+{
+ struct dn_fib_rule *r;
+
+ for(r = dn_fib_rules; r; r = r->r_next) {
+ if (r->r_ifindex == dev->ifindex) {
+ write_lock_bh(&dn_fib_rules_lock);
+ r->r_ifindex = -1;
+ write_unlock_bh(&dn_fib_rules_lock);
+ }
+ }
+}
+
+static void dn_fib_rules_attach(struct net_device *dev)
+{
+ struct dn_fib_rule *r;
+
+ for(r = dn_fib_rules; r; r = r->r_next) {
+ if (r->r_ifindex == -1 && strcmp(dev->name, r->r_ifname) == 0) {
+ write_lock_bh(&dn_fib_rules_lock);
+ r->r_ifindex = dev->ifindex;
+ write_unlock_bh(&dn_fib_rules_lock);
+ }
+ }
+}
+
+static int dn_fib_rules_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct net_device *dev = ptr;
+
+ switch(event) {
+ case NETDEV_UNREGISTER:
+ dn_fib_rules_detach(dev);
+ dn_fib_sync_down(0, dev, 1);
+ case NETDEV_REGISTER:
+ dn_fib_rules_attach(dev);
+ dn_fib_sync_up(dev);
+ }
+
+ return NOTIFY_DONE;
+}
+
+
+static struct notifier_block dn_fib_rules_notifier = {
+ notifier_call: dn_fib_rules_event,
+};
+
+static int dn_fib_fill_rule(struct sk_buff *skb, struct dn_fib_rule *r, struct netlink_callback *cb)
+{
+ struct rtmsg *rtm;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+
+ nlh = NLMSG_PUT(skb, NETLINK_CREDS(cb->skb)->pid, cb->nlh->nlmsg_seq, RTM_NEWRULE, sizeof(*rtm));
+ rtm = NLMSG_DATA(nlh);
+ rtm->rtm_family = AF_DECnet;
+ rtm->rtm_dst_len = r->r_dst_len;
+ rtm->rtm_src_len = r->r_src_len;
+ rtm->rtm_tos = 0;
+#ifdef CONFIG_DECNET_ROUTE_FWMARK
+ if (r->r_fwmark)
+ RTA_PUT(skb, RTA_PROTOINFO, 4, &r->r_fwmark);
+#endif
+ rtm->rtm_table = r->r_table;
+ rtm->rtm_protocol = 0;
+ rtm->rtm_scope = 0;
+ rtm->rtm_type = r->r_action;
+ rtm->rtm_flags = r->r_flags;
+
+ if (r->r_dst_len)
+ RTA_PUT(skb, RTA_DST, 2, &r->r_dst);
+ if (r->r_src_len)
+ RTA_PUT(skb, RTA_SRC, 2, &r->r_src);
+ if (r->r_ifname[0])
+ RTA_PUT(skb, RTA_IIF, IFNAMSIZ, &r->r_ifname);
+ if (r->r_preference)
+ RTA_PUT(skb, RTA_PRIORITY, 4, &r->r_preference);
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+int dn_fib_dump_rules(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int idx;
+ int s_idx = cb->args[0];
+ struct dn_fib_rule *r;
+
+ read_lock(&dn_fib_rules_lock);
+ for(r = dn_fib_rules, idx = 0; r; r = r->r_next, idx++) {
+ if (idx < s_idx)
+ continue;
+ if (dn_fib_fill_rule(skb, r, cb) < 0)
+ break;
+ }
+ read_unlock(&dn_fib_rules_lock);
+ cb->args[0] = idx;
+
+ return skb->len;
+}
+
+void __init dn_fib_rules_init(void)
+{
+ register_netdevice_notifier(&dn_fib_rules_notifier);
+}
+
+void __exit dn_fib_rules_cleanup(void)
+{
+ unregister_netdevice_notifier(&dn_fib_rules_notifier);
+}
+
+
diff --git a/uClinux-2.4.31-uc0/net/decnet/dn_table.c b/uClinux-2.4.31-uc0/net/decnet/dn_table.c
new file mode 100644
index 0000000..ea24291
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/dn_table.c
@@ -0,0 +1,901 @@
+/*
+ * DECnet An implementation of the DECnet protocol suite for the LINUX
+ * operating system. DECnet is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * DECnet Routing Forwarding Information Base (Routing Tables)
+ *
+ * Author: Steve Whitehouse <SteveW@ACM.org>
+ * Mostly copied from the IPv4 routing code
+ *
+ *
+ * Changes:
+ *
+ */
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/proc_fs.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <linux/spinlock.h>
+#include <asm/atomic.h>
+#include <asm/uaccess.h>
+#include <linux/route.h> /* RTF_xxx */
+#include <net/neighbour.h>
+#include <net/dst.h>
+#include <net/dn.h>
+#include <net/dn_route.h>
+#include <net/dn_fib.h>
+#include <net/dn_neigh.h>
+#include <net/dn_dev.h>
+
+struct dn_zone
+{
+ struct dn_zone *dz_next;
+ struct dn_fib_node **dz_hash;
+ int dz_nent;
+ int dz_divisor;
+ u32 dz_hashmask;
+#define DZ_HASHMASK(dz) ((dz)->dz_hashmask)
+ int dz_order;
+ u32 dz_mask;
+#define DZ_MASK(dz) ((dz)->dz_mask)
+};
+
+struct dn_hash
+{
+ struct dn_zone *dh_zones[17];
+ struct dn_zone *dh_zone_list;
+};
+
+#define dz_key_0(key) ((key).datum = 0)
+#define dz_prefix(key,dz) ((key).datum)
+
+#define for_nexthops(fi) { int nhsel; const struct dn_fib_nh *nh;\
+ for(nhsel = 0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++)
+
+#define endfor_nexthops(fi) }
+
+#define DN_MAX_DIVISOR 1024
+#define DN_S_ZOMBIE 1
+#define DN_S_ACCESSED 2
+
+#define DN_FIB_SCAN(f, fp) \
+for( ; ((f) = *(fp)) != NULL; (fp) = &(f)->fn_next)
+
+#define DN_FIB_SCAN_KEY(f, fp, key) \
+for( ; ((f) = *(fp)) != NULL && dn_key_eq((f)->fn_key, (key)); (fp) = &(f)->fn_next)
+
+
+static rwlock_t dn_fib_tables_lock = RW_LOCK_UNLOCKED;
+static struct dn_fib_table *dn_fib_tables[DN_NUM_TABLES + 1];
+
+static kmem_cache_t *dn_hash_kmem;
+static int dn_fib_hash_zombies;
+
+static __inline__ dn_fib_idx_t dn_hash(dn_fib_key_t key, struct dn_zone *dz)
+{
+ u32 h = ntohs(key.datum)>>(16 - dz->dz_order);
+ h ^= (h >> 10);
+ h ^= (h >> 6);
+ h ^= (h >> 3);
+ h &= DZ_HASHMASK(dz);
+ return *(dn_fib_idx_t *)&h;
+}
+
+static __inline__ dn_fib_key_t dz_key(u16 dst, struct dn_zone *dz)
+{
+ dn_fib_key_t k;
+ k.datum = dst & DZ_MASK(dz);
+ return k;
+}
+
+static __inline__ struct dn_fib_node **dn_chain_p(dn_fib_key_t key, struct dn_zone *dz)
+{
+ return &dz->dz_hash[dn_hash(key, dz).datum];
+}
+
+static __inline__ struct dn_fib_node *dz_chain(dn_fib_key_t key, struct dn_zone *dz)
+{
+ return dz->dz_hash[dn_hash(key, dz).datum];
+}
+
+static __inline__ int dn_key_eq(dn_fib_key_t a, dn_fib_key_t b)
+{
+ return a.datum == b.datum;
+}
+
+static __inline__ int dn_key_leq(dn_fib_key_t a, dn_fib_key_t b)
+{
+ return a.datum <= b.datum;
+}
+
+static __inline__ void dn_rebuild_zone(struct dn_zone *dz,
+ struct dn_fib_node **old_ht,
+ int old_divisor)
+{
+ int i;
+ struct dn_fib_node *f, **fp, *next;
+
+ for(i = 0; i < old_divisor; i++) {
+ for(f = old_ht[i]; f; f = f->fn_next) {
+ next = f->fn_next;
+ for(fp = dn_chain_p(f->fn_key, dz);
+ *fp && dn_key_leq((*fp)->fn_key, f->fn_key);
+ fp = &(*fp)->fn_next)
+ /* NOTHING */;
+ f->fn_next = *fp;
+ *fp = f;
+ }
+ }
+}
+
+static void dn_rehash_zone(struct dn_zone *dz)
+{
+ struct dn_fib_node **ht, **old_ht;
+ int old_divisor, new_divisor;
+ u32 new_hashmask;
+
+ old_divisor = dz->dz_divisor;
+
+ switch(old_divisor) {
+ case 16:
+ new_divisor = 256;
+ new_hashmask = 0xFF;
+ break;
+ default:
+ printk(KERN_DEBUG "DECnet: dn_rehash_zone: BUG! %d\n", old_divisor);
+ case 256:
+ new_divisor = 1024;
+ new_hashmask = 0x3FF;
+ break;
+ }
+
+ ht = kmalloc(new_divisor*sizeof(struct dn_fib_node*), GFP_KERNEL);
+
+ if (ht == NULL)
+ return;
+
+ memset(ht, 0, new_divisor*sizeof(struct dn_fib_node *));
+ write_lock_bh(&dn_fib_tables_lock);
+ old_ht = dz->dz_hash;
+ dz->dz_hash = ht;
+ dz->dz_hashmask = new_hashmask;
+ dz->dz_divisor = new_divisor;
+ dn_rebuild_zone(dz, old_ht, old_divisor);
+ write_unlock_bh(&dn_fib_tables_lock);
+ kfree(old_ht);
+}
+
+static void dn_free_node(struct dn_fib_node *f)
+{
+ dn_fib_release_info(DN_FIB_INFO(f));
+ kmem_cache_free(dn_hash_kmem, f);
+}
+
+
+static struct dn_zone *dn_new_zone(struct dn_hash *table, int z)
+{
+ int i;
+ struct dn_zone *dz = kmalloc(sizeof(struct dn_zone), GFP_KERNEL);
+ if (!dz)
+ return NULL;
+
+ memset(dz, 0, sizeof(struct dn_zone));
+ if (z) {
+ dz->dz_divisor = 16;
+ dz->dz_hashmask = 0x0F;
+ } else {
+ dz->dz_divisor = 1;
+ dz->dz_hashmask = 0;
+ }
+
+ dz->dz_hash = kmalloc(dz->dz_divisor*sizeof(struct dn_fib_node *), GFP_KERNEL);
+
+ if (!dz->dz_hash) {
+ kfree(dz);
+ return NULL;
+ }
+
+ memset(dz->dz_hash, 0, dz->dz_divisor*sizeof(struct dn_fib_node*));
+ dz->dz_order = z;
+ dz->dz_mask = dnet_make_mask(z);
+
+ for(i = z + 1; i <= 16; i++)
+ if (table->dh_zones[i])
+ break;
+
+ write_lock_bh(&dn_fib_tables_lock);
+ if (i>16) {
+ dz->dz_next = table->dh_zone_list;
+ table->dh_zone_list = dz;
+ } else {
+ dz->dz_next = table->dh_zones[i]->dz_next;
+ table->dh_zones[i]->dz_next = dz;
+ }
+ table->dh_zones[z] = dz;
+ write_unlock_bh(&dn_fib_tables_lock);
+ return dz;
+}
+
+
+static int dn_fib_nh_match(struct rtmsg *r, struct nlmsghdr *nlh, struct dn_kern_rta *rta, struct dn_fib_info *fi)
+{
+ struct rtnexthop *nhp;
+ int nhlen;
+
+ if (rta->rta_priority && *rta->rta_priority != fi->fib_priority)
+ return 1;
+
+ if (rta->rta_oif || rta->rta_gw) {
+ if ((!rta->rta_oif || *rta->rta_oif == fi->fib_nh->nh_oif) &&
+ (!rta->rta_gw || memcmp(rta->rta_gw, &fi->fib_nh->nh_gw, 2) == 0))
+ return 0;
+ return 1;
+ }
+
+ if (rta->rta_mp == NULL)
+ return 0;
+
+ nhp = RTA_DATA(rta->rta_mp);
+ nhlen = RTA_PAYLOAD(rta->rta_mp);
+
+ for_nexthops(fi) {
+ int attrlen = nhlen - sizeof(struct rtnexthop);
+ dn_address gw;
+
+ if (attrlen < 0 || (nhlen -= nhp->rtnh_len) < 0)
+ return -EINVAL;
+ if (nhp->rtnh_ifindex && nhp->rtnh_ifindex != nh->nh_oif)
+ return 1;
+ if (attrlen) {
+ gw = dn_fib_get_attr16(RTNH_DATA(nhp), attrlen, RTA_GATEWAY);
+
+ if (gw && gw != nh->nh_gw)
+ return 1;
+ }
+ nhp = RTNH_NEXT(nhp);
+ } endfor_nexthops(fi);
+
+ return 0;
+}
+
+static int dn_fib_dump_info(struct sk_buff *skb, u32 pid, u32 seq, int event,
+ u8 tb_id, u8 type, u8 scope, void *dst, int dst_len,
+ struct dn_fib_info *fi)
+{
+ struct rtmsg *rtm;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+ nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*rtm));
+ rtm = NLMSG_DATA(nlh);
+ rtm->rtm_family = AF_DECnet;
+ rtm->rtm_dst_len = dst_len;
+ rtm->rtm_src_len = 0;
+ rtm->rtm_tos = 0;
+ rtm->rtm_table = tb_id;
+ rtm->rtm_flags = fi->fib_flags;
+ rtm->rtm_scope = scope;
+ rtm->rtm_type = type;
+ if (rtm->rtm_dst_len)
+ RTA_PUT(skb, RTA_DST, 2, dst);
+ rtm->rtm_protocol = fi->fib_protocol;
+ if (fi->fib_priority)
+ RTA_PUT(skb, RTA_PRIORITY, 4, &fi->fib_priority);
+ if (fi->fib_nhs == 1) {
+ if (fi->fib_nh->nh_gw)
+ RTA_PUT(skb, RTA_GATEWAY, 2, &fi->fib_nh->nh_gw);
+ if (fi->fib_nh->nh_oif)
+ RTA_PUT(skb, RTA_OIF, sizeof(int), &fi->fib_nh->nh_oif);
+ }
+ if (fi->fib_nhs > 1) {
+ struct rtnexthop *nhp;
+ struct rtattr *mp_head;
+ if (skb_tailroom(skb) <= RTA_SPACE(0))
+ goto rtattr_failure;
+ mp_head = (struct rtattr *)skb_put(skb, RTA_SPACE(0));
+
+ for_nexthops(fi) {
+ if (skb_tailroom(skb) < RTA_ALIGN(RTA_ALIGN(sizeof(*nhp)) + 4))
+ goto rtattr_failure;
+ nhp = (struct rtnexthop *)skb_put(skb, RTA_ALIGN(sizeof(*nhp)));
+ nhp->rtnh_flags = nh->nh_flags & 0xFF;
+ nhp->rtnh_hops = nh->nh_weight - 1;
+ nhp->rtnh_ifindex = nh->nh_oif;
+ if (nh->nh_gw)
+ RTA_PUT(skb, RTA_GATEWAY, 2, &nh->nh_gw);
+ nhp->rtnh_len = skb->tail - (unsigned char *)nhp;
+ } endfor_nexthops(fi);
+ mp_head->rta_type = RTA_MULTIPATH;
+ mp_head->rta_len = skb->tail - (u8*)mp_head;
+ }
+
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+
+static void dn_rtmsg_fib(int event, struct dn_fib_node *f, int z, int tb_id,
+ struct nlmsghdr *nlh, struct netlink_skb_parms *req)
+{
+ struct sk_buff *skb;
+ u32 pid = req ? req->pid : 0;
+ int size = NLMSG_SPACE(sizeof(struct rtmsg) + 256);
+
+ skb = alloc_skb(size, GFP_KERNEL);
+ if (!skb)
+ return;
+
+ if (dn_fib_dump_info(skb, pid, nlh->nlmsg_seq, event, tb_id,
+ f->fn_type, f->fn_scope, &f->fn_key, z,
+ DN_FIB_INFO(f)) < 0) {
+ kfree_skb(skb);
+ return;
+ }
+ NETLINK_CB(skb).dst_groups = RTMGRP_DECnet_ROUTE;
+ if (nlh->nlmsg_flags & NLM_F_ECHO)
+ atomic_inc(&skb->users);
+ netlink_broadcast(rtnl, skb, pid, RTMGRP_DECnet_ROUTE, GFP_KERNEL);
+ if (nlh->nlmsg_flags & NLM_F_ECHO)
+ netlink_unicast(rtnl, skb, pid, MSG_DONTWAIT);
+}
+
+static __inline__ int dn_hash_dump_bucket(struct sk_buff *skb,
+ struct netlink_callback *cb,
+ struct dn_fib_table *tb,
+ struct dn_zone *dz,
+ struct dn_fib_node *f)
+{
+ int i, s_i;
+
+ s_i = cb->args[3];
+ for(i = 0; f; i++, f = f->fn_next) {
+ if (i < s_i)
+ continue;
+ if (f->fn_state & DN_S_ZOMBIE)
+ continue;
+ if (dn_fib_dump_info(skb, NETLINK_CB(cb->skb).pid,
+ cb->nlh->nlmsg_seq,
+ RTM_NEWROUTE,
+ tb->n,
+ (f->fn_state & DN_S_ZOMBIE) ? 0 : f->fn_type,
+ f->fn_scope, &f->fn_key, dz->dz_order,
+ f->fn_info) < 0) {
+ cb->args[3] = i;
+ return -1;
+ }
+ }
+ cb->args[3] = i;
+ return skb->len;
+}
+
+static __inline__ int dn_hash_dump_zone(struct sk_buff *skb,
+ struct netlink_callback *cb,
+ struct dn_fib_table *tb,
+ struct dn_zone *dz)
+{
+ int h, s_h;
+
+ s_h = cb->args[2];
+ for(h = 0; h < dz->dz_divisor; h++) {
+ if (h < s_h)
+ continue;
+ if (h > s_h)
+ memset(&cb->args[3], 0, sizeof(cb->args) - 3*sizeof(cb->args[0]));
+ if (dz->dz_hash == NULL || dz->dz_hash[h] == NULL)
+ continue;
+ if (dn_hash_dump_bucket(skb, cb, tb, dz, dz->dz_hash[h]) < 0) {
+ cb->args[2] = h;
+ return -1;
+ }
+ }
+ cb->args[2] = h;
+ return skb->len;
+}
+
+static int dn_fib_table_dump(struct dn_fib_table *tb, struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ int m, s_m;
+ struct dn_zone *dz;
+ struct dn_hash *table = (struct dn_hash *)tb->data;
+
+ s_m = cb->args[1];
+ read_lock(&dn_fib_tables_lock);
+ for(dz = table->dh_zone_list, m = 0; dz; dz = dz->dz_next, m++) {
+ if (m < s_m)
+ continue;
+ if (m > s_m)
+ memset(&cb->args[2], 0, sizeof(cb->args) - 2*sizeof(cb->args[0]));
+
+ if (dn_hash_dump_zone(skb, cb, tb, dz) < 0) {
+ cb->args[1] = m;
+ read_unlock(&dn_fib_tables_lock);
+ return -1;
+ }
+ }
+ read_unlock(&dn_fib_tables_lock);
+ cb->args[1] = m;
+
+ return skb->len;
+}
+
+static int dn_fib_table_insert(struct dn_fib_table *tb, struct rtmsg *r, struct dn_kern_rta *rta, struct nlmsghdr *n, struct netlink_skb_parms *req)
+{
+ struct dn_hash *table = (struct dn_hash *)tb->data;
+ struct dn_fib_node *new_f, *f, **fp, **del_fp;
+ struct dn_zone *dz;
+ struct dn_fib_info *fi;
+ int z = r->rtm_dst_len;
+ int type = r->rtm_type;
+ dn_fib_key_t key;
+ int err;
+
+ if (z > 16)
+ return -EINVAL;
+
+ dz = table->dh_zones[z];
+ if (!dz && !(dz = dn_new_zone(table, z)))
+ return -ENOBUFS;
+
+ dz_key_0(key);
+ if (rta->rta_dst) {
+ dn_address dst;
+ memcpy(&dst, rta->rta_dst, 2);
+ if (dst & ~DZ_MASK(dz))
+ return -EINVAL;
+ key = dz_key(dst, dz);
+ }
+
+ if ((fi = dn_fib_create_info(r, rta, n, &err)) == NULL)
+ return err;
+
+ if (dz->dz_nent > (dz->dz_divisor << 2) &&
+ dz->dz_divisor > DN_MAX_DIVISOR &&
+ (z==16 || (1<<z) > dz->dz_divisor))
+ dn_rehash_zone(dz);
+
+ fp = dn_chain_p(key, dz);
+
+ DN_FIB_SCAN(f, fp) {
+ if (dn_key_leq(key, f->fn_key))
+ break;
+ }
+
+ del_fp = NULL;
+
+ if (f && (f->fn_state & DN_S_ZOMBIE) &&
+ dn_key_eq(f->fn_key, key)) {
+ del_fp = fp;
+ fp = &f->fn_next;
+ f = *fp;
+ goto create;
+ }
+
+ DN_FIB_SCAN_KEY(f, fp, key) {
+ if (fi->fib_priority <= DN_FIB_INFO(f)->fib_priority)
+ break;
+ }
+
+ if (f && dn_key_eq(f->fn_key, key) &&
+ fi->fib_priority == DN_FIB_INFO(f)->fib_priority) {
+ struct dn_fib_node **ins_fp;
+
+ err = -EEXIST;
+ if (n->nlmsg_flags & NLM_F_EXCL)
+ goto out;
+
+ if (n->nlmsg_flags & NLM_F_REPLACE) {
+ del_fp = fp;
+ fp = &f->fn_next;
+ f = *fp;
+ goto replace;
+ }
+
+ ins_fp = fp;
+ err = -EEXIST;
+
+ DN_FIB_SCAN_KEY(f, fp, key) {
+ if (fi->fib_priority != DN_FIB_INFO(f)->fib_priority)
+ break;
+ if (f->fn_type == type && f->fn_scope == r->rtm_scope
+ && DN_FIB_INFO(f) == fi)
+ goto out;
+ }
+
+ if (!(n->nlmsg_flags & NLM_F_APPEND)) {
+ fp = ins_fp;
+ f = *fp;
+ }
+ }
+
+create:
+ err = -ENOENT;
+ if (!(n->nlmsg_flags & NLM_F_CREATE))
+ goto out;
+
+replace:
+ err = -ENOBUFS;
+ new_f = kmem_cache_alloc(dn_hash_kmem, SLAB_KERNEL);
+ if (new_f == NULL)
+ goto out;
+
+ memset(new_f, 0, sizeof(struct dn_fib_node));
+
+ new_f->fn_key = key;
+ new_f->fn_type = type;
+ new_f->fn_scope = r->rtm_scope;
+ DN_FIB_INFO(new_f) = fi;
+
+ new_f->fn_next = f;
+ write_lock_bh(&dn_fib_tables_lock);
+ *fp = new_f;
+ write_unlock_bh(&dn_fib_tables_lock);
+ dz->dz_nent++;
+
+ if (del_fp) {
+ f = *del_fp;
+ write_lock_bh(&dn_fib_tables_lock);
+ *del_fp = f->fn_next;
+ write_unlock_bh(&dn_fib_tables_lock);
+
+ if (!(f->fn_state & DN_S_ZOMBIE))
+ dn_rtmsg_fib(RTM_DELROUTE, f, z, tb->n, n, req);
+ if (f->fn_state & DN_S_ACCESSED)
+ dn_rt_cache_flush(-1);
+ dn_free_node(f);
+ dz->dz_nent--;
+ } else {
+ dn_rt_cache_flush(-1);
+ }
+
+ dn_rtmsg_fib(RTM_NEWROUTE, new_f, z, tb->n, n, req);
+
+ return 0;
+out:
+ dn_fib_release_info(fi);
+ return err;
+}
+
+
+static int dn_fib_table_delete(struct dn_fib_table *tb, struct rtmsg *r, struct dn_kern_rta *rta, struct nlmsghdr *n, struct netlink_skb_parms *req)
+{
+ struct dn_hash *table = (struct dn_hash*)tb->data;
+ struct dn_fib_node **fp, **del_fp, *f;
+ int z = r->rtm_dst_len;
+ struct dn_zone *dz;
+ dn_fib_key_t key;
+ int matched;
+
+
+ if (z > 16)
+ return -EINVAL;
+
+ if ((dz = table->dh_zones[z]) == NULL)
+ return -ESRCH;
+
+ dz_key_0(key);
+ if (rta->rta_dst) {
+ dn_address dst;
+ memcpy(&dst, rta->rta_dst, 2);
+ if (dst & ~DZ_MASK(dz))
+ return -EINVAL;
+ key = dz_key(dst, dz);
+ }
+
+ fp = dn_chain_p(key, dz);
+
+ DN_FIB_SCAN(f, fp) {
+ if (dn_key_eq(f->fn_key, key))
+ break;
+ if (dn_key_leq(key, f->fn_key))
+ return -ESRCH;
+ }
+
+ matched = 0;
+ del_fp = NULL;
+ DN_FIB_SCAN_KEY(f, fp, key) {
+ struct dn_fib_info *fi = DN_FIB_INFO(f);
+
+ if (f->fn_state & DN_S_ZOMBIE)
+ return -ESRCH;
+
+ matched++;
+
+ if (del_fp == NULL &&
+ (!r->rtm_type || f->fn_type == r->rtm_type) &&
+ (r->rtm_scope == RT_SCOPE_NOWHERE || f->fn_scope == r->rtm_scope) &&
+ (!r->rtm_protocol ||
+ fi->fib_protocol == r->rtm_protocol) &&
+ dn_fib_nh_match(r, n, rta, fi) == 0)
+ del_fp = fp;
+ }
+
+ if (del_fp) {
+ f = *del_fp;
+ dn_rtmsg_fib(RTM_DELROUTE, f, z, tb->n, n, req);
+
+ if (matched != 1) {
+ write_lock_bh(&dn_fib_tables_lock);
+ *del_fp = f->fn_next;
+ write_unlock_bh(&dn_fib_tables_lock);
+
+ if (f->fn_state & DN_S_ACCESSED)
+ dn_rt_cache_flush(-1);
+ dn_free_node(f);
+ dz->dz_nent--;
+ } else {
+ f->fn_state |= DN_S_ZOMBIE;
+ if (f->fn_state & DN_S_ACCESSED) {
+ f->fn_state &= ~DN_S_ACCESSED;
+ dn_rt_cache_flush(-1);
+ }
+ if (++dn_fib_hash_zombies > 128)
+ dn_fib_flush();
+ }
+
+ return 0;
+ }
+
+ return -ESRCH;
+}
+
+static __inline__ int dn_flush_list(struct dn_fib_node **fp, int z, struct dn_hash *table)
+{
+ int found = 0;
+ struct dn_fib_node *f;
+
+ while((f = *fp) != NULL) {
+ struct dn_fib_info *fi = DN_FIB_INFO(f);
+
+ if (fi && ((f->fn_state & DN_S_ZOMBIE) || (fi->fib_flags & RTNH_F_DEAD))) {
+ write_lock_bh(&dn_fib_tables_lock);
+ *fp = f->fn_next;
+ write_unlock_bh(&dn_fib_tables_lock);
+
+ dn_free_node(f);
+ found++;
+ continue;
+ }
+ fp = &f->fn_next;
+ }
+
+ return found;
+}
+
+static int dn_fib_table_flush(struct dn_fib_table *tb)
+{
+ struct dn_hash *table = (struct dn_hash *)tb->data;
+ struct dn_zone *dz;
+ int found = 0;
+
+ dn_fib_hash_zombies = 0;
+ for(dz = table->dh_zone_list; dz; dz = dz->dz_next) {
+ int i;
+ int tmp = 0;
+ for(i = dz->dz_divisor-1; i >= 0; i--)
+ tmp += dn_flush_list(&dz->dz_hash[i], dz->dz_order, table);
+ dz->dz_nent -= tmp;
+ found += tmp;
+ }
+
+ return found;
+}
+
+static int dn_fib_table_lookup(struct dn_fib_table *tb, const struct dn_fib_key *
+key, struct dn_fib_res *res)
+{
+ int err;
+ struct dn_zone *dz;
+ struct dn_hash *t = (struct dn_hash *)tb->data;
+
+ read_lock(&dn_fib_tables_lock);
+ for(dz = t->dh_zone_list; dz; dz = dz->dz_next) {
+ struct dn_fib_node *f;
+ dn_fib_key_t k = dz_key(key->dst, dz);
+
+ for(f = dz_chain(k, dz); f; f = f->fn_next) {
+ if (!dn_key_leq(k, f->fn_key))
+ break;
+ else
+ continue;
+
+ f->fn_state |= DN_S_ACCESSED;
+
+ if (f->fn_state&DN_S_ZOMBIE)
+ continue;
+ if (f->fn_scope < key->scope)
+ continue;
+
+ err = dn_fib_semantic_match(f->fn_type, DN_FIB_INFO(f), key, res);
+ if (err == 0) {
+ res->type = f->fn_type;
+ res->scope = f->fn_scope;
+ res->prefixlen = dz->dz_order;
+ goto out;
+ }
+ if (err < 0)
+ goto out;
+ }
+ }
+ err = 1;
+out:
+ read_unlock(&dn_fib_tables_lock);
+ return err;
+}
+
+#ifdef CONFIG_PROC_FS
+
+static unsigned dn_fib_flag_trans(int type, int dead, u16 mask, struct dn_fib_info *fi)
+{
+ static unsigned type2flags[RTN_MAX+1] = {
+ 0, 0, 0, 0, 0, 0, 0, RTF_REJECT, RTF_REJECT, 0, 0, 0
+ };
+ unsigned flags = type2flags[type];
+
+ if (fi && fi->fib_nh->nh_gw)
+ flags |= RTF_GATEWAY;
+ if (mask == 0xFFFF)
+ flags |= RTF_HOST;
+ if (dead)
+ flags |= RTF_UP;
+ return flags;
+}
+
+static void dn_fib_node_get_info(int type, int dead, struct dn_fib_info *fi, u16 prefix, u16 mask, char *buffer)
+{
+ int len;
+ unsigned flags = dn_fib_flag_trans(type, dead, mask, fi);
+
+ if (fi) {
+ len = sprintf(buffer, "%s\t%04x\t%04x\t%04x\t%d\t%u\t%d\t%04x\t%d\t%u\t%u",
+ fi->fib_dev ? fi->fib_dev->name : "*", prefix,
+ fi->fib_nh->nh_gw, flags, 0, 0, fi->fib_priority,
+ mask, 0, 0, 0);
+ } else {
+ len = sprintf(buffer, "*\t%04x\t%04x\t%04x\t%d\t%u\t%d\t%04x\t%d\t%u\t%u",
+ prefix, 0,
+ flags, 0, 0, 0,
+ mask, 0, 0, 0);
+ }
+ memset(buffer+len, ' ', 127-len);
+ buffer[127] = '\n';
+}
+
+static int dn_fib_table_get_info(struct dn_fib_table *tb, char *buffer, int first, int count)
+{
+ struct dn_hash *table = (struct dn_hash *)tb->data;
+ struct dn_zone *dz;
+ int pos = 0;
+ int n = 0;
+
+ read_lock(&dn_fib_tables_lock);
+ for(dz = table->dh_zone_list; dz; dz = dz->dz_next) {
+ int i;
+ struct dn_fib_node *f;
+ int maxslot = dz->dz_divisor;
+ struct dn_fib_node **fp = dz->dz_hash;
+
+ if (dz->dz_nent == 0)
+ continue;
+
+ if (pos + dz->dz_nent < first) {
+ pos += dz->dz_nent;
+ continue;
+ }
+
+ for(i = 0; i < maxslot; i++, fp++) {
+ for(f = *fp; f ; f = f->fn_next) {
+ if (++pos <= first)
+ continue;
+ dn_fib_node_get_info(f->fn_type,
+ f->fn_state & DN_S_ZOMBIE,
+ DN_FIB_INFO(f),
+ dz_prefix(f->fn_key, dz),
+ DZ_MASK(dz), buffer);
+ buffer += 128;
+ if (++n >= count)
+ goto out;
+ }
+ }
+ }
+out:
+ read_unlock(&dn_fib_tables_lock);
+ return n;
+}
+#endif /* CONFIG_PROC_FS */
+
+struct dn_fib_table *dn_fib_get_table(int n, int create)
+{
+ struct dn_fib_table *t;
+
+ if (n < DN_MIN_TABLE)
+ return NULL;
+
+ if (n > DN_NUM_TABLES)
+ return NULL;
+
+ if (dn_fib_tables[n])
+ return dn_fib_tables[n];
+
+ if (!create)
+ return NULL;
+
+ if (in_interrupt() && net_ratelimit()) {
+ printk(KERN_DEBUG "DECnet: BUG! Attempt to create routing table from interrupt\n");
+ return NULL;
+ }
+ if ((t = kmalloc(sizeof(struct dn_fib_table), GFP_KERNEL)) == NULL)
+ return NULL;
+
+ memset(t, 0, sizeof(struct dn_fib_table));
+
+ t->n = n;
+ t->insert = dn_fib_table_insert;
+ t->delete = dn_fib_table_delete;
+ t->lookup = dn_fib_table_lookup;
+ t->flush = dn_fib_table_flush;
+#ifdef CONFIG_PROC_FS
+ t->get_info = dn_fib_table_get_info;
+#endif
+ t->dump = dn_fib_table_dump;
+ dn_fib_tables[n] = t;
+
+ return t;
+}
+
+static void dn_fib_del_tree(int n)
+{
+ struct dn_fib_table *t;
+
+ write_lock(&dn_fib_tables_lock);
+ t = dn_fib_tables[n];
+ dn_fib_tables[n] = NULL;
+ write_unlock(&dn_fib_tables_lock);
+
+ if (t) {
+ kfree(t);
+ }
+}
+
+struct dn_fib_table *dn_fib_empty_table(void)
+{
+ int id;
+
+ for(id = DN_MIN_TABLE; id <= DN_NUM_TABLES; id++)
+ if (dn_fib_tables[id] == NULL)
+ return dn_fib_get_table(id, 1);
+ return NULL;
+}
+
+void __init dn_fib_table_init(void)
+{
+ dn_hash_kmem = kmem_cache_create("dn_fib_info_cache",
+ sizeof(struct dn_fib_info),
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+}
+
+void __exit dn_fib_table_cleanup(void)
+{
+ int i;
+
+ for (i = 0; i < DN_NUM_TABLES + 1; ++i)
+ dn_fib_del_tree(i);
+
+ return;
+}
diff --git a/uClinux-2.4.31-uc0/net/decnet/dn_timer.c b/uClinux-2.4.31-uc0/net/decnet/dn_timer.c
new file mode 100644
index 0000000..41a4aa6
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/dn_timer.c
@@ -0,0 +1,155 @@
+/*
+ * DECnet An implementation of the DECnet protocol suite for the LINUX
+ * operating system. DECnet is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * DECnet Socket Timer Functions
+ *
+ * Author: Steve Whitehouse <SteveW@ACM.org>
+ *
+ *
+ * Changes:
+ * Steve Whitehouse : Made keepalive timer part of the same
+ * timer idea.
+ * Steve Whitehouse : Added checks for sk->sock_readers
+ * David S. Miller : New socket locking
+ * Steve Whitehouse : Timer grabs socket ref.
+ */
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <linux/spinlock.h>
+#include <net/sock.h>
+#include <asm/atomic.h>
+#include <net/dn.h>
+
+/*
+ * Fast timer is for delayed acks (200mS max)
+ * Slow timer is for everything else (n * 500mS)
+ */
+
+#define FAST_INTERVAL (HZ/5)
+#define SLOW_INTERVAL (HZ/2)
+
+static void dn_slow_timer(unsigned long arg);
+
+void dn_start_slow_timer(struct sock *sk)
+{
+ sk->timer.expires = jiffies + SLOW_INTERVAL;
+ sk->timer.function = dn_slow_timer;
+ sk->timer.data = (unsigned long)sk;
+
+ add_timer(&sk->timer);
+}
+
+void dn_stop_slow_timer(struct sock *sk)
+{
+ del_timer(&sk->timer);
+}
+
+static void dn_slow_timer(unsigned long arg)
+{
+ struct sock *sk = (struct sock *)arg;
+ struct dn_scp *scp = DN_SK(sk);
+
+ sock_hold(sk);
+ bh_lock_sock(sk);
+
+ if (sk->lock.users != 0) {
+ sk->timer.expires = jiffies + HZ / 10;
+ add_timer(&sk->timer);
+ goto out;
+ }
+
+ /*
+ * The persist timer is the standard slow timer used for retransmits
+ * in both connection establishment and disconnection as well as
+ * in the RUN state. The different states are catered for by changing
+ * the function pointer in the socket. Setting the timer to a value
+ * of zero turns it off. We allow the persist_fxn to turn the
+ * timer off in a permant way by returning non-zero, so that
+ * timer based routines may remove sockets. This is why we have a
+ * sock_hold()/sock_put() around the timer to prevent the socket
+ * going away in the middle.
+ */
+ if (scp->persist && scp->persist_fxn) {
+ if (scp->persist <= SLOW_INTERVAL) {
+ scp->persist = 0;
+
+ if (scp->persist_fxn(sk))
+ goto out;
+ } else {
+ scp->persist -= SLOW_INTERVAL;
+ }
+ }
+
+ /*
+ * Check for keepalive timeout. After the other timer 'cos if
+ * the previous timer caused a retransmit, we don't need to
+ * do this. scp->stamp is the last time that we sent a packet.
+ * The keepalive function sends a link service packet to the
+ * other end. If it remains unacknowledged, the standard
+ * socket timers will eventually shut the socket down. Each
+ * time we do this, scp->stamp will be updated, thus
+ * we won't try and send another until scp->keepalive has passed
+ * since the last successful transmission.
+ */
+ if (scp->keepalive && scp->keepalive_fxn && (scp->state == DN_RUN)) {
+ if ((jiffies - scp->stamp) >= scp->keepalive)
+ scp->keepalive_fxn(sk);
+ }
+
+ sk->timer.expires = jiffies + SLOW_INTERVAL;
+
+ add_timer(&sk->timer);
+out:
+ bh_unlock_sock(sk);
+ sock_put(sk);
+}
+
+static void dn_fast_timer(unsigned long arg)
+{
+ struct sock *sk = (struct sock *)arg;
+ struct dn_scp *scp = DN_SK(sk);
+
+ bh_lock_sock(sk);
+ if (sk->lock.users != 0) {
+ scp->delack_timer.expires = jiffies + HZ / 20;
+ add_timer(&scp->delack_timer);
+ goto out;
+ }
+
+ scp->delack_pending = 0;
+
+ if (scp->delack_fxn)
+ scp->delack_fxn(sk);
+out:
+ bh_unlock_sock(sk);
+}
+
+void dn_start_fast_timer(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+
+ if (!scp->delack_pending) {
+ scp->delack_pending = 1;
+ init_timer(&scp->delack_timer);
+ scp->delack_timer.expires = jiffies + FAST_INTERVAL;
+ scp->delack_timer.data = (unsigned long)sk;
+ scp->delack_timer.function = dn_fast_timer;
+ add_timer(&scp->delack_timer);
+ }
+}
+
+void dn_stop_fast_timer(struct sock *sk)
+{
+ struct dn_scp *scp = DN_SK(sk);
+
+ if (scp->delack_pending) {
+ scp->delack_pending = 0;
+ del_timer(&scp->delack_timer);
+ }
+}
+
diff --git a/uClinux-2.4.31-uc0/net/decnet/sysctl_net_decnet.c b/uClinux-2.4.31-uc0/net/decnet/sysctl_net_decnet.c
new file mode 100644
index 0000000..e62c9b7
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/decnet/sysctl_net_decnet.c
@@ -0,0 +1,389 @@
+/*
+ * DECnet An implementation of the DECnet protocol suite for the LINUX
+ * operating system. DECnet is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * DECnet sysctl support functions
+ *
+ * Author: Steve Whitehouse <SteveW@ACM.org>
+ *
+ *
+ * Changes:
+ *
+ */
+#include <linux/config.h>
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+#include <linux/fs.h>
+#include <linux/netdevice.h>
+#include <linux/string.h>
+#include <net/neighbour.h>
+#include <net/dst.h>
+
+#include <asm/uaccess.h>
+
+#include <net/dn.h>
+#include <net/dn_dev.h>
+#include <net/dn_route.h>
+
+
+int decnet_debug_level;
+int decnet_time_wait = 30;
+int decnet_dn_count = 1;
+int decnet_di_count = 3;
+int decnet_dr_count = 3;
+int decnet_log_martians = 1;
+int decnet_no_fc_max_cwnd = NSP_MIN_WINDOW;
+
+#ifdef CONFIG_SYSCTL
+extern int decnet_dst_gc_interval;
+static int min_decnet_time_wait[] = { 5 };
+static int max_decnet_time_wait[] = { 600 };
+static int min_state_count[] = { 1 };
+static int max_state_count[] = { NSP_MAXRXTSHIFT };
+static int min_decnet_dst_gc_interval[] = { 1 };
+static int max_decnet_dst_gc_interval[] = { 60 };
+static int min_decnet_no_fc_max_cwnd[] = { NSP_MIN_WINDOW };
+static int max_decnet_no_fc_max_cwnd[] = { NSP_MAX_WINDOW };
+static char node_name[7] = "???";
+
+static struct ctl_table_header *dn_table_header = NULL;
+
+/*
+ * ctype.h :-)
+ */
+#define ISNUM(x) (((x) >= '0') && ((x) <= '9'))
+#define ISLOWER(x) (((x) >= 'a') && ((x) <= 'z'))
+#define ISUPPER(x) (((x) >= 'A') && ((x) <= 'Z'))
+#define ISALPHA(x) (ISLOWER(x) || ISUPPER(x))
+#define INVALID_END_CHAR(x) (ISNUM(x) || ISALPHA(x))
+
+static void strip_it(char *str)
+{
+ for(;;) {
+ switch(*str) {
+ case ' ':
+ case '\n':
+ case '\r':
+ case ':':
+ *str = 0;
+ case 0:
+ return;
+ }
+ str++;
+ }
+}
+
+/*
+ * Simple routine to parse an ascii DECnet address
+ * into a network order address.
+ */
+static int parse_addr(dn_address *addr, char *str)
+{
+ dn_address area, node;
+
+ while(*str && !ISNUM(*str)) str++;
+
+ if (*str == 0)
+ return -1;
+
+ area = (*str++ - '0');
+ if (ISNUM(*str)) {
+ area *= 10;
+ area += (*str++ - '0');
+ }
+
+ if (*str++ != '.')
+ return -1;
+
+ if (!ISNUM(*str))
+ return -1;
+
+ node = *str++ - '0';
+ if (ISNUM(*str)) {
+ node *= 10;
+ node += (*str++ - '0');
+ }
+ if (ISNUM(*str)) {
+ node *= 10;
+ node += (*str++ - '0');
+ }
+ if (ISNUM(*str)) {
+ node *= 10;
+ node += (*str++ - '0');
+ }
+
+ if ((node > 1023) || (area > 63))
+ return -1;
+
+ if (INVALID_END_CHAR(*str))
+ return -1;
+
+ *addr = dn_htons((area << 10) | node);
+
+ return 0;
+}
+
+
+static int dn_node_address_strategy(ctl_table *table, int *name, int nlen,
+ void *oldval, size_t *oldlenp,
+ void *newval, size_t newlen,
+ void **context)
+{
+ size_t len;
+ dn_address addr;
+
+ if (oldval && oldlenp) {
+ if (get_user(len, oldlenp))
+ return -EFAULT;
+ if (len) {
+ if (len != sizeof(unsigned short))
+ return -EINVAL;
+ if (put_user(decnet_address, (unsigned short *)oldval))
+ return -EFAULT;
+ }
+ }
+ if (newval && newlen) {
+ if (newlen != sizeof(unsigned short))
+ return -EINVAL;
+ if (get_user(addr, (unsigned short *)newval))
+ return -EFAULT;
+
+ dn_dev_devices_off();
+
+ decnet_address = addr;
+ dn_dn2eth(decnet_ether_address, dn_ntohs(decnet_address));
+
+ dn_dev_devices_on();
+ }
+ return 0;
+}
+
+static int dn_node_address_handler(ctl_table *table, int write,
+ struct file *filp,
+ void *buffer, size_t *lenp)
+{
+ char addr[DN_ASCBUF_LEN];
+ size_t len;
+ dn_address dnaddr;
+
+ if (!*lenp || (filp->f_pos && !write)) {
+ *lenp = 0;
+ return 0;
+ }
+
+ if (write) {
+ int len = (*lenp < DN_ASCBUF_LEN) ? *lenp : (DN_ASCBUF_LEN-1);
+
+ if (copy_from_user(addr, buffer, len))
+ return -EFAULT;
+
+ addr[len] = 0;
+ strip_it(addr);
+
+ if (parse_addr(&dnaddr, addr))
+ return -EINVAL;
+
+ dn_dev_devices_off();
+
+ decnet_address = dnaddr;
+ dn_dn2eth(decnet_ether_address, dn_ntohs(decnet_address));
+
+ dn_dev_devices_on();
+
+ filp->f_pos += len;
+
+ return 0;
+ }
+
+ dn_addr2asc(dn_ntohs(decnet_address), addr);
+ len = strlen(addr);
+ addr[len++] = '\n';
+
+ if (len > *lenp) len = *lenp;
+
+ if (copy_to_user(buffer, addr, len))
+ return -EFAULT;
+
+ *lenp = len;
+ filp->f_pos += len;
+
+ return 0;
+}
+
+
+static int dn_def_dev_strategy(ctl_table *table, int *name, int nlen,
+ void *oldval, size_t *oldlenp,
+ void *newval, size_t newlen,
+ void **context)
+{
+ size_t len;
+ struct net_device *dev = decnet_default_device;
+ char devname[17];
+ size_t namel;
+
+ devname[0] = 0;
+
+ if (oldval && oldlenp) {
+ if (get_user(len, oldlenp))
+ return -EFAULT;
+ if (len) {
+ if (dev)
+ strcpy(devname, dev->name);
+
+ namel = strlen(devname) + 1;
+ if (len > namel) len = namel;
+
+ if (copy_to_user(oldval, devname, len))
+ return -EFAULT;
+
+ if (put_user(len, oldlenp))
+ return -EFAULT;
+ }
+ }
+
+ if (newval && newlen) {
+ if (newlen > 16)
+ return -E2BIG;
+
+ if (copy_from_user(devname, newval, newlen))
+ return -EFAULT;
+
+ devname[newlen] = 0;
+
+ if ((dev = __dev_get_by_name(devname)) == NULL)
+ return -ENODEV;
+
+ if (dev->dn_ptr == NULL)
+ return -ENODEV;
+
+ decnet_default_device = dev;
+ }
+
+ return 0;
+}
+
+
+static int dn_def_dev_handler(ctl_table *table, int write,
+ struct file * filp,
+ void *buffer, size_t *lenp)
+{
+ size_t len;
+ struct net_device *dev = decnet_default_device;
+ char devname[17];
+
+ if (!*lenp || (filp->f_pos && !write)) {
+ *lenp = 0;
+ return 0;
+ }
+
+ if (write) {
+ if (*lenp > 16)
+ return -E2BIG;
+
+ if (copy_from_user(devname, buffer, *lenp))
+ return -EFAULT;
+
+ devname[*lenp] = 0;
+ strip_it(devname);
+
+ if ((dev = __dev_get_by_name(devname)) == NULL)
+ return -ENODEV;
+
+ if (dev->dn_ptr == NULL)
+ return -ENODEV;
+
+ decnet_default_device = dev;
+ filp->f_pos += *lenp;
+
+ return 0;
+ }
+
+ if (dev == NULL) {
+ *lenp = 0;
+ return 0;
+ }
+
+ strcpy(devname, dev->name);
+ len = strlen(devname);
+ devname[len++] = '\n';
+
+ if (len > *lenp) len = *lenp;
+
+ if (copy_to_user(buffer, devname, len))
+ return -EFAULT;
+
+ *lenp = len;
+ filp->f_pos += len;
+
+ return 0;
+}
+
+static ctl_table dn_table[] = {
+ {NET_DECNET_NODE_ADDRESS, "node_address", NULL, 7, 0644, NULL,
+ dn_node_address_handler, dn_node_address_strategy, NULL,
+ NULL, NULL},
+ {NET_DECNET_NODE_NAME, "node_name", node_name, 7, 0644, NULL,
+ &proc_dostring, &sysctl_string, NULL, NULL, NULL},
+ {NET_DECNET_DEFAULT_DEVICE, "default_device", NULL, 16, 0644, NULL,
+ dn_def_dev_handler, dn_def_dev_strategy, NULL, NULL, NULL},
+ {NET_DECNET_TIME_WAIT, "time_wait", &decnet_time_wait,
+ sizeof(int), 0644,
+ NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_decnet_time_wait, &max_decnet_time_wait},
+ {NET_DECNET_DN_COUNT, "dn_count", &decnet_dn_count,
+ sizeof(int), 0644,
+ NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_state_count, &max_state_count},
+ {NET_DECNET_DI_COUNT, "di_count", &decnet_di_count,
+ sizeof(int), 0644,
+ NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_state_count, &max_state_count},
+ {NET_DECNET_DR_COUNT, "dr_count", &decnet_dr_count,
+ sizeof(int), 0644,
+ NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_state_count, &max_state_count},
+ {NET_DECNET_DST_GC_INTERVAL, "dst_gc_interval", &decnet_dst_gc_interval,
+ sizeof(int), 0644,
+ NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_decnet_dst_gc_interval, &max_decnet_dst_gc_interval},
+ {NET_DECNET_NO_FC_MAX_CWND, "no_fc_max_cwnd", &decnet_no_fc_max_cwnd,
+ sizeof(int), 0644,
+ NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ &min_decnet_no_fc_max_cwnd, &max_decnet_no_fc_max_cwnd},
+ {NET_DECNET_DEBUG_LEVEL, "debug", &decnet_debug_level,
+ sizeof(int), 0644,
+ NULL, &proc_dointvec, &sysctl_intvec, NULL,
+ NULL, NULL},
+ {0}
+};
+
+static ctl_table dn_dir_table[] = {
+ {NET_DECNET, "decnet", NULL, 0, 0555, dn_table},
+ {0}
+};
+
+static ctl_table dn_root_table[] = {
+ {CTL_NET, "net", NULL, 0, 0555, dn_dir_table},
+ {0}
+};
+
+void dn_register_sysctl(void)
+{
+ dn_table_header = register_sysctl_table(dn_root_table, 1);
+}
+
+void dn_unregister_sysctl(void)
+{
+ unregister_sysctl_table(dn_table_header);
+}
+
+#else /* CONFIG_SYSCTL */
+void dn_unregister_sysctl(void)
+{
+}
+void dn_register_sysctl(void)
+{
+}
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/econet/Makefile b/uClinux-2.4.31-uc0/net/econet/Makefile
new file mode 100644
index 0000000..3c04883
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/econet/Makefile
@@ -0,0 +1,16 @@
+#
+# Makefile for Econet support code.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := econet.o
+
+obj-y := af_econet.o
+obj-m := $(O_TARGET)
+
+include $(TOPDIR)/Rules.make
+
diff --git a/uClinux-2.4.31-uc0/net/econet/af_econet.c b/uClinux-2.4.31-uc0/net/econet/af_econet.c
new file mode 100644
index 0000000..5cead88
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/econet/af_econet.c
@@ -0,0 +1,1137 @@
+/*
+ * An implementation of the Acorn Econet and AUN protocols.
+ * Philip Blundell <philb@gnu.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/netdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/route.h>
+#include <linux/inet.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <linux/wireless.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/inet_common.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <linux/if_ec.h>
+#include <net/udp.h>
+#include <net/ip.h>
+#include <linux/spinlock.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+
+static struct proto_ops econet_ops;
+static struct sock *econet_sklist;
+
+/* Since there are only 256 possible network numbers (or fewer, depends
+ how you count) it makes sense to use a simple lookup table. */
+static struct net_device *net2dev_map[256];
+
+#define EC_PORT_IP 0xd2
+
+#ifdef CONFIG_ECONET_AUNUDP
+static spinlock_t aun_queue_lock;
+static struct socket *udpsock;
+#define AUN_PORT 0x8000
+
+
+struct aunhdr
+{
+ unsigned char code; /* AUN magic protocol byte */
+ unsigned char port;
+ unsigned char cb;
+ unsigned char pad;
+ unsigned long handle;
+};
+
+static unsigned long aun_seq;
+
+/* Queue of packets waiting to be transmitted. */
+static struct sk_buff_head aun_queue;
+static struct timer_list ab_cleanup_timer;
+
+#endif /* CONFIG_ECONET_AUNUDP */
+
+/* Per-packet information */
+struct ec_cb
+{
+ struct sockaddr_ec sec;
+ unsigned long cookie; /* Supplied by user. */
+#ifdef CONFIG_ECONET_AUNUDP
+ int done;
+ unsigned long seq; /* Sequencing */
+ unsigned long timeout; /* Timeout */
+ unsigned long start; /* jiffies */
+#endif
+#ifdef CONFIG_ECONET_NATIVE
+ void (*sent)(struct sk_buff *, int result);
+#endif
+};
+
+/*
+ * Pull a packet from our receive queue and hand it to the user.
+ * If necessary we block.
+ */
+
+static int econet_recvmsg(struct socket *sock, struct msghdr *msg, int len,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sk_buff *skb;
+ int copied, err;
+
+ msg->msg_namelen = sizeof(struct sockaddr_ec);
+
+ /*
+ * Call the generic datagram receiver. This handles all sorts
+ * of horrible races and re-entrancy so we can forget about it
+ * in the protocol layers.
+ *
+ * Now it will return ENETDOWN, if device have just gone down,
+ * but then it will block.
+ */
+
+ skb=skb_recv_datagram(sk,flags,flags&MSG_DONTWAIT,&err);
+
+ /*
+ * An error occurred so return it. Because skb_recv_datagram()
+ * handles the blocking we don't see and worry about blocking
+ * retries.
+ */
+
+ if(skb==NULL)
+ goto out;
+
+ /*
+ * You lose any data beyond the buffer you gave. If it worries a
+ * user program they can ask the device for its MTU anyway.
+ */
+
+ copied = skb->len;
+ if (copied > len)
+ {
+ copied=len;
+ msg->msg_flags|=MSG_TRUNC;
+ }
+
+ /* We can't use skb_copy_datagram here */
+ err = memcpy_toiovec(msg->msg_iov, skb->data, copied);
+ if (err)
+ goto out_free;
+ sk->stamp=skb->stamp;
+
+ if (msg->msg_name)
+ memcpy(msg->msg_name, skb->cb, msg->msg_namelen);
+
+ /*
+ * Free or return the buffer as appropriate. Again this
+ * hides all the races and re-entrancy issues from us.
+ */
+ err = copied;
+
+out_free:
+ skb_free_datagram(sk, skb);
+out:
+ return err;
+}
+
+/*
+ * Bind an Econet socket.
+ */
+
+static int econet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sockaddr_ec *sec = (struct sockaddr_ec *)uaddr;
+ struct sock *sk=sock->sk;
+
+ /*
+ * Check legality
+ */
+
+ if (addr_len < sizeof(struct sockaddr_ec) ||
+ sec->sec_family != AF_ECONET)
+ return -EINVAL;
+
+ sk->protinfo.af_econet->cb = sec->cb;
+ sk->protinfo.af_econet->port = sec->port;
+ sk->protinfo.af_econet->station = sec->addr.station;
+ sk->protinfo.af_econet->net = sec->addr.net;
+
+ return 0;
+}
+
+/*
+ * Queue a transmit result for the user to be told about.
+ */
+
+static void tx_result(struct sock *sk, unsigned long cookie, int result)
+{
+ struct sk_buff *skb = alloc_skb(0, GFP_ATOMIC);
+ struct ec_cb *eb;
+ struct sockaddr_ec *sec;
+
+ if (skb == NULL)
+ {
+ printk(KERN_DEBUG "ec: memory squeeze, transmit result dropped.\n");
+ return;
+ }
+
+ eb = (struct ec_cb *)&skb->cb;
+ sec = (struct sockaddr_ec *)&eb->sec;
+ memset(sec, 0, sizeof(struct sockaddr_ec));
+ sec->cookie = cookie;
+ sec->type = ECTYPE_TRANSMIT_STATUS | result;
+ sec->sec_family = AF_ECONET;
+
+ if (sock_queue_rcv_skb(sk, skb) < 0)
+ kfree_skb(skb);
+}
+
+#ifdef CONFIG_ECONET_NATIVE
+/*
+ * Called by the Econet hardware driver when a packet transmit
+ * has completed. Tell the user.
+ */
+
+static void ec_tx_done(struct sk_buff *skb, int result)
+{
+ struct ec_cb *eb = (struct ec_cb *)&skb->cb;
+ tx_result(skb->sk, eb->cookie, result);
+}
+#endif
+
+/*
+ * Send a packet. We have to work out which device it's going out on
+ * and hence whether to use real Econet or the UDP emulation.
+ */
+
+static int econet_sendmsg(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_ec *saddr=(struct sockaddr_ec *)msg->msg_name;
+ struct net_device *dev;
+ struct ec_addr addr;
+ int err;
+ unsigned char port, cb;
+ struct sk_buff *skb;
+ struct ec_cb *eb;
+#ifdef CONFIG_ECONET_NATIVE
+ unsigned short proto = 0;
+#endif
+#ifdef CONFIG_ECONET_AUNUDP
+ struct msghdr udpmsg;
+ struct iovec iov[msg->msg_iovlen+1];
+ struct aunhdr ah;
+ struct sockaddr_in udpdest;
+ __kernel_size_t size;
+ int i;
+ mm_segment_t oldfs;
+#endif
+
+ /*
+ * Check the flags.
+ */
+
+ if (msg->msg_flags&~MSG_DONTWAIT)
+ return(-EINVAL);
+
+ /*
+ * Get and verify the address.
+ */
+
+ if (saddr == NULL) {
+ addr.station = sk->protinfo.af_econet->station;
+ addr.net = sk->protinfo.af_econet->net;
+ port = sk->protinfo.af_econet->port;
+ cb = sk->protinfo.af_econet->cb;
+ } else {
+ if (msg->msg_namelen < sizeof(struct sockaddr_ec))
+ return -EINVAL;
+ addr.station = saddr->addr.station;
+ addr.net = saddr->addr.net;
+ port = saddr->port;
+ cb = saddr->cb;
+ }
+
+ /* Look for a device with the right network number. */
+ dev = net2dev_map[addr.net];
+
+ /* If not directly reachable, use some default */
+ if (dev == NULL)
+ {
+ dev = net2dev_map[0];
+ /* No interfaces at all? */
+ if (dev == NULL)
+ return -ENETDOWN;
+ }
+
+ if (dev->type == ARPHRD_ECONET)
+ {
+ /* Real hardware Econet. We're not worthy etc. */
+#ifdef CONFIG_ECONET_NATIVE
+ atomic_inc(&dev->refcnt);
+
+ skb = sock_alloc_send_skb(sk, len+dev->hard_header_len+15,
+ msg->msg_flags & MSG_DONTWAIT, &err);
+ if (skb==NULL)
+ goto out_unlock;
+
+ skb_reserve(skb, (dev->hard_header_len+15)&~15);
+ skb->nh.raw = skb->data;
+
+ eb = (struct ec_cb *)&skb->cb;
+
+ /* BUG: saddr may be NULL */
+ eb->cookie = saddr->cookie;
+ eb->sec = *saddr;
+ eb->sent = ec_tx_done;
+
+ if (dev->hard_header) {
+ int res;
+ struct ec_framehdr *fh;
+ err = -EINVAL;
+ res = dev->hard_header(skb, dev, ntohs(proto),
+ &addr, NULL, len);
+ /* Poke in our control byte and
+ port number. Hack, hack. */
+ fh = (struct ec_framehdr *)(skb->data);
+ fh->cb = cb;
+ fh->port = port;
+ if (sock->type != SOCK_DGRAM) {
+ skb->tail = skb->data;
+ skb->len = 0;
+ } else if (res < 0)
+ goto out_free;
+ }
+
+ /* Copy the data. Returns -EFAULT on error */
+ err = memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len);
+ skb->protocol = proto;
+ skb->dev = dev;
+ skb->priority = sk->priority;
+ if (err)
+ goto out_free;
+
+ err = -ENETDOWN;
+ if (!(dev->flags & IFF_UP))
+ goto out_free;
+
+ /*
+ * Now send it
+ */
+
+ dev_queue_xmit(skb);
+ dev_put(dev);
+ return(len);
+
+ out_free:
+ kfree_skb(skb);
+ out_unlock:
+ if (dev)
+ dev_put(dev);
+#else
+ err = -EPROTOTYPE;
+#endif
+ return err;
+ }
+
+#ifdef CONFIG_ECONET_AUNUDP
+ /* AUN virtual Econet. */
+
+ if (udpsock == NULL)
+ return -ENETDOWN; /* No socket - can't send */
+
+ /* Make up a UDP datagram and hand it off to some higher intellect. */
+
+ memset(&udpdest, 0, sizeof(udpdest));
+ udpdest.sin_family = AF_INET;
+ udpdest.sin_port = htons(AUN_PORT);
+
+ /* At the moment we use the stupid Acorn scheme of Econet address
+ y.x maps to IP a.b.c.x. This should be replaced with something
+ more flexible and more aware of subnet masks. */
+ {
+ struct in_device *idev = in_dev_get(dev);
+ unsigned long network = 0;
+ if (idev) {
+ read_lock(&idev->lock);
+ if (idev->ifa_list)
+ network = ntohl(idev->ifa_list->ifa_address) &
+ 0xffffff00; /* !!! */
+ read_unlock(&idev->lock);
+ in_dev_put(idev);
+ }
+ udpdest.sin_addr.s_addr = htonl(network | addr.station);
+ }
+
+ ah.port = port;
+ ah.cb = cb & 0x7f;
+ ah.code = 2; /* magic */
+ ah.pad = 0;
+
+ /* tack our header on the front of the iovec */
+ size = sizeof(struct aunhdr);
+ iov[0].iov_base = (void *)&ah;
+ iov[0].iov_len = size;
+ for (i = 0; i < msg->msg_iovlen; i++) {
+ void *base = msg->msg_iov[i].iov_base;
+ size_t len = msg->msg_iov[i].iov_len;
+ /* Check it now since we switch to KERNEL_DS later. */
+ if ((err = verify_area(VERIFY_READ, base, len)) < 0)
+ return err;
+ iov[i+1].iov_base = base;
+ iov[i+1].iov_len = len;
+ size += len;
+ }
+
+ /* Get a skbuff (no data, just holds our cb information) */
+ if ((skb = sock_alloc_send_skb(sk, 0,
+ msg->msg_flags & MSG_DONTWAIT, &err)) == NULL)
+ return err;
+
+ eb = (struct ec_cb *)&skb->cb;
+
+ eb->cookie = saddr->cookie;
+ eb->timeout = (5*HZ);
+ eb->start = jiffies;
+ ah.handle = aun_seq;
+ eb->seq = (aun_seq++);
+ eb->sec = *saddr;
+
+ skb_queue_tail(&aun_queue, skb);
+
+ udpmsg.msg_name = (void *)&udpdest;
+ udpmsg.msg_namelen = sizeof(udpdest);
+ udpmsg.msg_iov = &iov[0];
+ udpmsg.msg_iovlen = msg->msg_iovlen + 1;
+ udpmsg.msg_control = NULL;
+ udpmsg.msg_controllen = 0;
+ udpmsg.msg_flags=0;
+
+ oldfs = get_fs(); set_fs(KERNEL_DS); /* More privs :-) */
+ err = sock_sendmsg(udpsock, &udpmsg, size);
+ set_fs(oldfs);
+#else
+ err = -EPROTOTYPE;
+#endif
+ return err;
+}
+
+/*
+ * Look up the address of a socket.
+ */
+
+static int econet_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *uaddr_len, int peer)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_ec *sec = (struct sockaddr_ec *)uaddr;
+
+ if (peer)
+ return -EOPNOTSUPP;
+
+ sec->sec_family = AF_ECONET;
+ sec->port = sk->protinfo.af_econet->port;
+ sec->addr.station = sk->protinfo.af_econet->station;
+ sec->addr.net = sk->protinfo.af_econet->net;
+
+ *uaddr_len = sizeof(*sec);
+ return 0;
+}
+
+static void econet_destroy_timer(unsigned long data)
+{
+ struct sock *sk=(struct sock *)data;
+
+ if (!atomic_read(&sk->wmem_alloc) && !atomic_read(&sk->rmem_alloc)) {
+ sk_free(sk);
+ MOD_DEC_USE_COUNT;
+ return;
+ }
+
+ sk->timer.expires=jiffies+10*HZ;
+ add_timer(&sk->timer);
+ printk(KERN_DEBUG "econet socket destroy delayed\n");
+}
+
+/*
+ * Close an econet socket.
+ */
+
+static int econet_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ if (!sk)
+ return 0;
+
+ sklist_remove_socket(&econet_sklist, sk);
+
+ /*
+ * Now the socket is dead. No more input will appear.
+ */
+
+ sk->state_change(sk); /* It is useless. Just for sanity. */
+
+ sock->sk = NULL;
+ sk->socket = NULL;
+ sk->dead = 1;
+
+ /* Purge queues */
+
+ skb_queue_purge(&sk->receive_queue);
+
+ if (atomic_read(&sk->rmem_alloc) || atomic_read(&sk->wmem_alloc)) {
+ sk->timer.data=(unsigned long)sk;
+ sk->timer.expires=jiffies+HZ;
+ sk->timer.function=econet_destroy_timer;
+ add_timer(&sk->timer);
+ return 0;
+ }
+
+ sk_free(sk);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+/*
+ * Create an Econet socket
+ */
+
+static int econet_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+ int err;
+
+ /* Econet only provides datagram services. */
+ if (sock->type != SOCK_DGRAM)
+ return -ESOCKTNOSUPPORT;
+
+ sock->state = SS_UNCONNECTED;
+ MOD_INC_USE_COUNT;
+
+ err = -ENOBUFS;
+ sk = sk_alloc(PF_ECONET, GFP_KERNEL, 1);
+ if (sk == NULL)
+ goto out;
+
+ sk->reuse = 1;
+ sock->ops = &econet_ops;
+ sock_init_data(sock,sk);
+
+ sk->protinfo.af_econet = kmalloc(sizeof(struct econet_opt), GFP_KERNEL);
+ if (sk->protinfo.af_econet == NULL)
+ goto out_free;
+ memset(sk->protinfo.af_econet, 0, sizeof(struct econet_opt));
+ sk->zapped=0;
+ sk->family = PF_ECONET;
+ sk->num = protocol;
+
+ sklist_insert_socket(&econet_sklist, sk);
+ return(0);
+
+out_free:
+ sk_free(sk);
+out:
+ MOD_DEC_USE_COUNT;
+ return err;
+}
+
+/*
+ * Handle Econet specific ioctls
+ */
+
+static int ec_dev_ioctl(struct socket *sock, unsigned int cmd, void *arg)
+{
+ struct ifreq ifr;
+ struct ec_device *edev;
+ struct net_device *dev;
+ struct sockaddr_ec *sec;
+
+ /*
+ * Fetch the caller's info block into kernel space
+ */
+
+ if (copy_from_user(&ifr, arg, sizeof(struct ifreq)))
+ return -EFAULT;
+
+ if ((dev = dev_get_by_name(ifr.ifr_name)) == NULL)
+ return -ENODEV;
+
+ sec = (struct sockaddr_ec *)&ifr.ifr_addr;
+
+ switch (cmd)
+ {
+ case SIOCSIFADDR:
+ edev = dev->ec_ptr;
+ if (edev == NULL)
+ {
+ /* Magic up a new one. */
+ edev = kmalloc(sizeof(struct ec_device), GFP_KERNEL);
+ if (edev == NULL) {
+ printk("af_ec: memory squeeze.\n");
+ dev_put(dev);
+ return -ENOMEM;
+ }
+ memset(edev, 0, sizeof(struct ec_device));
+ dev->ec_ptr = edev;
+ }
+ else
+ net2dev_map[edev->net] = NULL;
+ edev->station = sec->addr.station;
+ edev->net = sec->addr.net;
+ net2dev_map[sec->addr.net] = dev;
+ if (!net2dev_map[0])
+ net2dev_map[0] = dev;
+ dev_put(dev);
+ return 0;
+
+ case SIOCGIFADDR:
+ edev = dev->ec_ptr;
+ if (edev == NULL)
+ {
+ dev_put(dev);
+ return -ENODEV;
+ }
+ memset(sec, 0, sizeof(struct sockaddr_ec));
+ sec->addr.station = edev->station;
+ sec->addr.net = edev->net;
+ sec->sec_family = AF_ECONET;
+ dev_put(dev);
+ if (copy_to_user(arg, &ifr, sizeof(struct ifreq)))
+ return -EFAULT;
+ return 0;
+ }
+
+ dev_put(dev);
+ return -EINVAL;
+}
+
+/*
+ * Handle generic ioctls
+ */
+
+static int econet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+ int pid;
+
+ switch(cmd)
+ {
+ case FIOSETOWN:
+ case SIOCSPGRP:
+ if (get_user(pid, (int *) arg))
+ return -EFAULT;
+ if (current->pid != pid && current->pgrp != -pid && !capable(CAP_NET_ADMIN))
+ return -EPERM;
+ sk->proc = pid;
+ return(0);
+ case FIOGETOWN:
+ case SIOCGPGRP:
+ return put_user(sk->proc, (int *)arg);
+ case SIOCGSTAMP:
+ if(sk->stamp.tv_sec==0)
+ return -ENOENT;
+ return copy_to_user((void *)arg, &sk->stamp, sizeof(struct timeval)) ? -EFAULT : 0;
+ case SIOCGIFFLAGS:
+ case SIOCSIFFLAGS:
+ case SIOCGIFCONF:
+ case SIOCGIFMETRIC:
+ case SIOCSIFMETRIC:
+ case SIOCGIFMEM:
+ case SIOCSIFMEM:
+ case SIOCGIFMTU:
+ case SIOCSIFMTU:
+ case SIOCSIFLINK:
+ case SIOCGIFHWADDR:
+ case SIOCSIFHWADDR:
+ case SIOCSIFMAP:
+ case SIOCGIFMAP:
+ case SIOCSIFSLAVE:
+ case SIOCGIFSLAVE:
+ case SIOCGIFINDEX:
+ case SIOCGIFNAME:
+ case SIOCGIFCOUNT:
+ case SIOCSIFHWBROADCAST:
+ return(dev_ioctl(cmd,(void *) arg));
+
+ case SIOCSIFADDR:
+ case SIOCGIFADDR:
+ return ec_dev_ioctl(sock, cmd, (void *)arg);
+ break;
+
+ default:
+ return(dev_ioctl(cmd,(void *) arg));
+ }
+ /*NOTREACHED*/
+ return 0;
+}
+
+static struct net_proto_family econet_family_ops = {
+ family: PF_ECONET,
+ create: econet_create,
+};
+
+static struct proto_ops SOCKOPS_WRAPPED(econet_ops) = {
+ family: PF_ECONET,
+
+ release: econet_release,
+ bind: econet_bind,
+ connect: sock_no_connect,
+ socketpair: sock_no_socketpair,
+ accept: sock_no_accept,
+ getname: econet_getname,
+ poll: datagram_poll,
+ ioctl: econet_ioctl,
+ listen: sock_no_listen,
+ shutdown: sock_no_shutdown,
+ setsockopt: sock_no_setsockopt,
+ getsockopt: sock_no_getsockopt,
+ sendmsg: econet_sendmsg,
+ recvmsg: econet_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+#include <linux/smp_lock.h>
+SOCKOPS_WRAP(econet, PF_ECONET);
+
+/*
+ * Find the listening socket, if any, for the given data.
+ */
+
+static struct sock *ec_listening_socket(unsigned char port, unsigned char
+ station, unsigned char net)
+{
+ struct sock *sk = econet_sklist;
+
+ while (sk)
+ {
+ struct econet_opt *opt = sk->protinfo.af_econet;
+ if ((opt->port == port || opt->port == 0) &&
+ (opt->station == station || opt->station == 0) &&
+ (opt->net == net || opt->net == 0))
+ return sk;
+
+ sk = sk->next;
+ }
+
+ return NULL;
+}
+
+/*
+ * Queue a received packet for a socket.
+ */
+
+static int ec_queue_packet(struct sock *sk, struct sk_buff *skb,
+ unsigned char stn, unsigned char net,
+ unsigned char cb, unsigned char port)
+{
+ struct ec_cb *eb = (struct ec_cb *)&skb->cb;
+ struct sockaddr_ec *sec = (struct sockaddr_ec *)&eb->sec;
+
+ memset(sec, 0, sizeof(struct sockaddr_ec));
+ sec->sec_family = AF_ECONET;
+ sec->type = ECTYPE_PACKET_RECEIVED;
+ sec->port = port;
+ sec->cb = cb;
+ sec->addr.net = net;
+ sec->addr.station = stn;
+
+ return sock_queue_rcv_skb(sk, skb);
+}
+
+#ifdef CONFIG_ECONET_AUNUDP
+
+/*
+ * Send an AUN protocol response.
+ */
+
+static void aun_send_response(__u32 addr, unsigned long seq, int code, int cb)
+{
+ struct sockaddr_in sin;
+ struct iovec iov;
+ struct aunhdr ah;
+ struct msghdr udpmsg;
+ int err;
+ mm_segment_t oldfs;
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(AUN_PORT);
+ sin.sin_addr.s_addr = addr;
+
+ ah.code = code;
+ ah.pad = 0;
+ ah.port = 0;
+ ah.cb = cb;
+ ah.handle = seq;
+
+ iov.iov_base = (void *)&ah;
+ iov.iov_len = sizeof(ah);
+
+ udpmsg.msg_name = (void *)&sin;
+ udpmsg.msg_namelen = sizeof(sin);
+ udpmsg.msg_iov = &iov;
+ udpmsg.msg_iovlen = 1;
+ udpmsg.msg_control = NULL;
+ udpmsg.msg_controllen = 0;
+ udpmsg.msg_flags=0;
+
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ err = sock_sendmsg(udpsock, &udpmsg, sizeof(ah));
+ set_fs(oldfs);
+}
+
+
+/*
+ * Handle incoming AUN packets. Work out if anybody wants them,
+ * and send positive or negative acknowledgements as appropriate.
+ */
+
+static void aun_incoming(struct sk_buff *skb, struct aunhdr *ah, size_t len)
+{
+ struct iphdr *ip = skb->nh.iph;
+ unsigned char stn = ntohl(ip->saddr) & 0xff;
+ struct sock *sk;
+ struct sk_buff *newskb;
+ struct ec_device *edev = skb->dev->ec_ptr;
+
+ if (! edev)
+ goto bad;
+
+ if ((sk = ec_listening_socket(ah->port, stn, edev->net)) == NULL)
+ goto bad; /* Nobody wants it */
+
+ newskb = alloc_skb((len - sizeof(struct aunhdr) + 15) & ~15,
+ GFP_ATOMIC);
+ if (newskb == NULL)
+ {
+ printk(KERN_DEBUG "AUN: memory squeeze, dropping packet.\n");
+ /* Send nack and hope sender tries again */
+ goto bad;
+ }
+
+ memcpy(skb_put(newskb, len - sizeof(struct aunhdr)), (void *)(ah+1),
+ len - sizeof(struct aunhdr));
+
+ if (ec_queue_packet(sk, newskb, stn, edev->net, ah->cb, ah->port))
+ {
+ /* Socket is bankrupt. */
+ kfree_skb(newskb);
+ goto bad;
+ }
+
+ aun_send_response(ip->saddr, ah->handle, 3, 0);
+ return;
+
+bad:
+ aun_send_response(ip->saddr, ah->handle, 4, 0);
+}
+
+/*
+ * Handle incoming AUN transmit acknowledgements. If the sequence
+ * number matches something in our backlog then kill it and tell
+ * the user. If the remote took too long to reply then we may have
+ * dropped the packet already.
+ */
+
+static void aun_tx_ack(unsigned long seq, int result)
+{
+ struct sk_buff *skb;
+ unsigned long flags;
+ struct ec_cb *eb;
+
+ spin_lock_irqsave(&aun_queue_lock, flags);
+ skb = skb_peek(&aun_queue);
+ while (skb && skb != (struct sk_buff *)&aun_queue)
+ {
+ struct sk_buff *newskb = skb->next;
+ eb = (struct ec_cb *)&skb->cb;
+ if (eb->seq == seq)
+ goto foundit;
+
+ skb = newskb;
+ }
+ spin_unlock_irqrestore(&aun_queue_lock, flags);
+ printk(KERN_DEBUG "AUN: unknown sequence %ld\n", seq);
+ return;
+
+foundit:
+ tx_result(skb->sk, eb->cookie, result);
+ skb_unlink(skb);
+ spin_unlock_irqrestore(&aun_queue_lock, flags);
+ kfree_skb(skb);
+}
+
+/*
+ * Deal with received AUN frames - sort out what type of thing it is
+ * and hand it to the right function.
+ */
+
+static void aun_data_available(struct sock *sk, int slen)
+{
+ int err;
+ struct sk_buff *skb;
+ unsigned char *data;
+ struct aunhdr *ah;
+ struct iphdr *ip;
+ size_t len;
+
+ while ((skb = skb_recv_datagram(sk, 0, 1, &err)) == NULL) {
+ if (err == -EAGAIN) {
+ printk(KERN_ERR "AUN: no data available?!");
+ return;
+ }
+ printk(KERN_DEBUG "AUN: recvfrom() error %d\n", -err);
+ }
+
+ data = skb->h.raw + sizeof(struct udphdr);
+ ah = (struct aunhdr *)data;
+ len = skb->len - sizeof(struct udphdr);
+ ip = skb->nh.iph;
+
+ switch (ah->code)
+ {
+ case 2:
+ aun_incoming(skb, ah, len);
+ break;
+ case 3:
+ aun_tx_ack(ah->handle, ECTYPE_TRANSMIT_OK);
+ break;
+ case 4:
+ aun_tx_ack(ah->handle, ECTYPE_TRANSMIT_NOT_LISTENING);
+ break;
+#if 0
+ /* This isn't quite right yet. */
+ case 5:
+ aun_send_response(ip->saddr, ah->handle, 6, ah->cb);
+ break;
+#endif
+ default:
+ printk(KERN_DEBUG "unknown AUN packet (type %d)\n", data[0]);
+ }
+
+ skb_free_datagram(sk, skb);
+}
+
+/*
+ * Called by the timer to manage the AUN transmit queue. If a packet
+ * was sent to a dead or nonexistent host then we will never get an
+ * acknowledgement back. After a few seconds we need to spot this and
+ * drop the packet.
+ */
+
+static void ab_cleanup(unsigned long h)
+{
+ struct sk_buff *skb;
+ unsigned long flags;
+
+ spin_lock_irqsave(&aun_queue_lock, flags);
+ skb = skb_peek(&aun_queue);
+ while (skb && skb != (struct sk_buff *)&aun_queue)
+ {
+ struct sk_buff *newskb = skb->next;
+ struct ec_cb *eb = (struct ec_cb *)&skb->cb;
+ if ((jiffies - eb->start) > eb->timeout)
+ {
+ tx_result(skb->sk, eb->cookie,
+ ECTYPE_TRANSMIT_NOT_PRESENT);
+ skb_unlink(skb);
+ kfree_skb(skb);
+ }
+ skb = newskb;
+ }
+ spin_unlock_irqrestore(&aun_queue_lock, flags);
+
+ mod_timer(&ab_cleanup_timer, jiffies + (HZ*2));
+}
+
+static int __init aun_udp_initialise(void)
+{
+ int error;
+ struct sockaddr_in sin;
+
+ skb_queue_head_init(&aun_queue);
+ spin_lock_init(&aun_queue_lock);
+ init_timer(&ab_cleanup_timer);
+ ab_cleanup_timer.expires = jiffies + (HZ*2);
+ ab_cleanup_timer.function = ab_cleanup;
+ add_timer(&ab_cleanup_timer);
+
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_port = htons(AUN_PORT);
+
+ /* We can count ourselves lucky Acorn machines are too dim to
+ speak IPv6. :-) */
+ if ((error = sock_create(PF_INET, SOCK_DGRAM, 0, &udpsock)) < 0)
+ {
+ printk("AUN: socket error %d\n", -error);
+ return error;
+ }
+
+ udpsock->sk->reuse = 1;
+ udpsock->sk->allocation = GFP_ATOMIC; /* we're going to call it
+ from interrupts */
+
+ error = udpsock->ops->bind(udpsock, (struct sockaddr *)&sin,
+ sizeof(sin));
+ if (error < 0)
+ {
+ printk("AUN: bind error %d\n", -error);
+ goto release;
+ }
+
+ udpsock->sk->data_ready = aun_data_available;
+
+ return 0;
+
+release:
+ sock_release(udpsock);
+ udpsock = NULL;
+ return error;
+}
+#endif
+
+#ifdef CONFIG_ECONET_NATIVE
+
+/*
+ * Receive an Econet frame from a device.
+ */
+
+static int econet_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
+{
+ struct ec_framehdr *hdr = (struct ec_framehdr *)skb->data;
+ struct sock *sk;
+ struct ec_device *edev = dev->ec_ptr;
+
+ if (! edev)
+ {
+ kfree_skb(skb);
+ return NET_RX_DROP;
+ }
+
+ if (skb->len < sizeof(struct ec_framehdr))
+ {
+ /* Frame is too small to be any use */
+ kfree_skb(skb);
+ return NET_RX_DROP;
+ }
+
+ /* First check for encapsulated IP */
+ if (hdr->port == EC_PORT_IP)
+ {
+ skb->protocol = htons(ETH_P_IP);
+ skb_pull(skb, sizeof(struct ec_framehdr));
+ netif_rx(skb);
+ return 0;
+ }
+
+ sk = ec_listening_socket(hdr->port, hdr->src_stn, hdr->src_net);
+ if (!sk)
+ {
+ kfree_skb(skb);
+ return NET_RX_DROP;
+ }
+
+ if (ec_queue_packet(sk, skb, edev->net, hdr->src_stn, hdr->cb,
+ hdr->port)) {
+ kfree_skb(skb);
+ return NET_RX_DROP;
+ }
+ return 0;
+}
+
+static struct packet_type econet_packet_type = {
+ type: __constant_htons(ETH_P_ECONET),
+ func: econet_rcv,
+};
+
+static void econet_hw_initialise(void)
+{
+ dev_add_pack(&econet_packet_type);
+}
+
+#endif
+
+static int econet_notifier(struct notifier_block *this, unsigned long msg, void *data)
+{
+ struct net_device *dev = (struct net_device *)data;
+ struct ec_device *edev;
+
+ switch (msg) {
+ case NETDEV_UNREGISTER:
+ /* A device has gone down - kill any data we hold for it. */
+ edev = dev->ec_ptr;
+ if (edev)
+ {
+ if (net2dev_map[0] == dev)
+ net2dev_map[0] = 0;
+ net2dev_map[edev->net] = NULL;
+ kfree(edev);
+ dev->ec_ptr = NULL;
+ }
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block econet_netdev_notifier = {
+ notifier_call: econet_notifier,
+};
+
+static void __exit econet_proto_exit(void)
+{
+#ifdef CONFIG_ECONET_AUNUDP
+ del_timer(&ab_cleanup_timer);
+ if (udpsock)
+ sock_release(udpsock);
+#endif
+ unregister_netdevice_notifier(&econet_netdev_notifier);
+ sock_unregister(econet_family_ops.family);
+}
+
+static int __init econet_proto_init(void)
+{
+ sock_register(&econet_family_ops);
+#ifdef CONFIG_ECONET_AUNUDP
+ spin_lock_init(&aun_queue_lock);
+ aun_udp_initialise();
+#endif
+#ifdef CONFIG_ECONET_NATIVE
+ econet_hw_initialise();
+#endif
+ register_netdevice_notifier(&econet_netdev_notifier);
+ return 0;
+}
+
+module_init(econet_proto_init);
+module_exit(econet_proto_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ethernet/Makefile b/uClinux-2.4.31-uc0/net/ethernet/Makefile
new file mode 100644
index 0000000..baa653b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ethernet/Makefile
@@ -0,0 +1,28 @@
+#
+# Makefile for the Linux Ethernet layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := ethernet.o
+
+OBJS := eth.o
+
+ifeq ($(CONFIG_SYSCTL),y)
+OBJS += sysctl_net_ether.o
+endif
+
+ifdef CONFIG_IPX
+OBJ2 := pe2.o
+endif
+
+ifdef CONFIG_ATALK
+OBJ2 := pe2.o
+endif
+
+obj-$(CONFIG_NET) := $(OBJS) $(OBJ2)
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/ethernet/eth.c b/uClinux-2.4.31-uc0/net/ethernet/eth.c
new file mode 100644
index 0000000..49da0f3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ethernet/eth.c
@@ -0,0 +1,257 @@
+/* $USAGI: eth.c,v 1.6 2003/08/08 13:46:37 yoshfuji Exp $ */
+
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * Ethernet-type device handling.
+ *
+ * Version: @(#)eth.c 1.0.7 05/25/93
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Mark Evans, <evansmp@uhura.aston.ac.uk>
+ * Florian La Roche, <rzsfl@rz.uni-sb.de>
+ * Alan Cox, <gw4pts@gw4pts.ampr.org>
+ *
+ * Fixes:
+ * Mr Linux : Arp problems
+ * Alan Cox : Generic queue tidyup (very tiny here)
+ * Alan Cox : eth_header ntohs should be htons
+ * Alan Cox : eth_rebuild_header missing an htons and
+ * minor other things.
+ * Tegge : Arp bug fixes.
+ * Florian : Removed many unnecessary functions, code cleanup
+ * and changes for new arp and skbuff.
+ * Alan Cox : Redid header building to reflect new format.
+ * Alan Cox : ARP only when compiled with CONFIG_INET
+ * Greg Page : 802.2 and SNAP stuff.
+ * Alan Cox : MAC layer pointers/new format.
+ * Paul Gortmaker : eth_copy_and_sum shouldn't csum padding.
+ * Alan Cox : Protect against forwarding explosions with
+ * older network drivers and IFF_ALLMULTI.
+ * Christer Weinigel : Better rebuild header message.
+ * Andrew Morton : 26Feb01: kill ether_setup() - use netdev_boot_setup().
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/ip.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <net/dst.h>
+#include <net/arp.h>
+#include <net/sock.h>
+#include <net/ipv6.h>
+#include <net/ip.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/checksum.h>
+
+extern int __init netdev_boot_setup(char *str);
+
+__setup("ether=", netdev_boot_setup);
+
+/*
+ * Create the Ethernet MAC header for an arbitrary protocol layer
+ *
+ * saddr=NULL means use device source address
+ * daddr=NULL means leave destination address (eg unresolved arp)
+ */
+
+int eth_header(struct sk_buff *skb, struct net_device *dev, unsigned short type,
+ void *daddr, void *saddr, unsigned len)
+{
+ struct ethhdr *eth = (struct ethhdr *)skb_push(skb,ETH_HLEN);
+
+ /*
+ * Set the protocol type. For a packet of type ETH_P_802_3 we put the length
+ * in here instead. It is up to the 802.2 layer to carry protocol information.
+ */
+
+ if(type!=ETH_P_802_3)
+ eth->h_proto = htons(type);
+ else
+ eth->h_proto = htons(len);
+
+ /*
+ * Set the source hardware address.
+ */
+
+ if(saddr)
+ memcpy(eth->h_source,saddr,dev->addr_len);
+ else
+ memcpy(eth->h_source,dev->dev_addr,dev->addr_len);
+
+ /*
+ * Anyway, the loopback-device should never use this function...
+ */
+
+ if (dev->flags & (IFF_LOOPBACK|IFF_NOARP))
+ {
+ memset(eth->h_dest, 0, dev->addr_len);
+ return(dev->hard_header_len);
+ }
+
+ if(daddr)
+ {
+ memcpy(eth->h_dest,daddr,dev->addr_len);
+ return dev->hard_header_len;
+ }
+
+ return -dev->hard_header_len;
+}
+
+
+/*
+ * Rebuild the Ethernet MAC header. This is called after an ARP
+ * (or in future other address resolution) has completed on this
+ * sk_buff. We now let ARP fill in the other fields.
+ *
+ * This routine CANNOT use cached dst->neigh!
+ * Really, it is used only when dst->neigh is wrong.
+ */
+
+int eth_rebuild_header(struct sk_buff *skb)
+{
+ struct ethhdr *eth = (struct ethhdr *)skb->data;
+ struct net_device *dev = skb->dev;
+
+ switch (eth->h_proto)
+ {
+#ifdef CONFIG_INET
+ case __constant_htons(ETH_P_IP):
+ return arp_find(eth->h_dest, skb);
+#endif
+ default:
+ printk(KERN_DEBUG
+ "%s: unable to resolve type %X addresses.\n",
+ dev->name, (int)eth->h_proto);
+
+ memcpy(eth->h_source, dev->dev_addr, dev->addr_len);
+ break;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Determine the packet's protocol ID. The rule here is that we
+ * assume 802.3 if the type field is short enough to be a length.
+ * This is normal practice and works for any 'now in use' protocol.
+ */
+
+unsigned short eth_type_trans(struct sk_buff *skb, struct net_device *dev)
+{
+ struct ethhdr *eth;
+ unsigned char *rawp;
+
+ skb->mac.raw=skb->data;
+ skb_pull(skb,dev->hard_header_len);
+ eth= skb->mac.ethernet;
+
+ if(*eth->h_dest&1)
+ {
+ if(memcmp(eth->h_dest,dev->broadcast, ETH_ALEN)==0)
+ skb->pkt_type=PACKET_BROADCAST;
+ else
+ skb->pkt_type=PACKET_MULTICAST;
+ }
+
+#ifdef CONFIG_PCNET32_VMWARE
+ /* For VMware hack.
+ * VMware network interfaces picks up MULTICAST packet
+ * which is generated by themselves.
+ */
+ if(*eth->h_dest&1) /* BROADCAST or MULTICAST Packet */
+ {
+ if(memcmp(dev->dev_addr,eth->h_source, ETH_ALEN)==0)
+ return 0;
+ }
+#endif
+
+ /*
+ * This ALLMULTI check should be redundant by 1.4
+ * so don't forget to remove it.
+ *
+ * Seems, you forgot to remove it. All silly devices
+ * seems to set IFF_PROMISC.
+ */
+
+ else if(1 /*dev->flags&IFF_PROMISC*/)
+ {
+ if(memcmp(eth->h_dest,dev->dev_addr, ETH_ALEN))
+ skb->pkt_type=PACKET_OTHERHOST;
+ }
+
+ if (ntohs(eth->h_proto) >= 1536)
+ return eth->h_proto;
+
+ rawp = skb->data;
+
+ /*
+ * This is a magic hack to spot IPX packets. Older Novell breaks
+ * the protocol design and runs IPX over 802.3 without an 802.2 LLC
+ * layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This
+ * won't work for fault tolerant netware but does for the rest.
+ */
+ if (*(unsigned short *)rawp == 0xFFFF)
+ return htons(ETH_P_802_3);
+
+ /*
+ * Real 802.2 LLC
+ */
+ return htons(ETH_P_802_2);
+}
+
+int eth_header_parse(struct sk_buff *skb, unsigned char *haddr)
+{
+ struct ethhdr *eth = skb->mac.ethernet;
+ memcpy(haddr, eth->h_source, ETH_ALEN);
+ return ETH_ALEN;
+}
+
+int eth_header_cache(struct neighbour *neigh, struct hh_cache *hh)
+{
+ unsigned short type = hh->hh_type;
+ struct ethhdr *eth;
+ struct net_device *dev = neigh->dev;
+
+ eth = (struct ethhdr*)
+ (((u8*)hh->hh_data) + (HH_DATA_OFF(sizeof(*eth))));
+
+ if (type == __constant_htons(ETH_P_802_3))
+ return -1;
+
+ eth->h_proto = type;
+ memcpy(eth->h_source, dev->dev_addr, dev->addr_len);
+ memcpy(eth->h_dest, neigh->ha, dev->addr_len);
+ hh->hh_len = ETH_HLEN;
+ return 0;
+}
+
+/*
+ * Called by Address Resolution module to notify changes in address.
+ */
+
+void eth_header_cache_update(struct hh_cache *hh, struct net_device *dev, unsigned char * haddr)
+{
+ memcpy(((u8*)hh->hh_data) + HH_DATA_OFF(sizeof(struct ethhdr)),
+ haddr, dev->addr_len);
+}
diff --git a/uClinux-2.4.31-uc0/net/ethernet/pe2.c b/uClinux-2.4.31-uc0/net/ethernet/pe2.c
new file mode 100644
index 0000000..0406bc6
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ethernet/pe2.c
@@ -0,0 +1,38 @@
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/datalink.h>
+#include <linux/mm.h>
+#include <linux/in.h>
+
+static void
+pEII_datalink_header(struct datalink_proto *dl,
+ struct sk_buff *skb, unsigned char *dest_node)
+{
+ struct net_device *dev = skb->dev;
+
+ skb->protocol = htons (ETH_P_IPX);
+ if(dev->hard_header)
+ dev->hard_header(skb, dev, ETH_P_IPX, dest_node, NULL, skb->len);
+}
+
+struct datalink_proto *
+make_EII_client(void)
+{
+ struct datalink_proto *proto;
+
+ proto = (struct datalink_proto *) kmalloc(sizeof(*proto), GFP_ATOMIC);
+ if (proto != NULL) {
+ proto->type_len = 0;
+ proto->header_length = 0;
+ proto->datalink_header = pEII_datalink_header;
+ proto->string_name = "EtherII";
+ }
+
+ return proto;
+}
+
+void destroy_EII_client(struct datalink_proto *dl)
+{
+ if (dl)
+ kfree(dl);
+}
diff --git a/uClinux-2.4.31-uc0/net/ethernet/sysctl_net_ether.c b/uClinux-2.4.31-uc0/net/ethernet/sysctl_net_ether.c
new file mode 100644
index 0000000..b81a6d5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ethernet/sysctl_net_ether.c
@@ -0,0 +1,13 @@
+/* -*- linux-c -*-
+ * sysctl_net_ether.c: sysctl interface to net Ethernet subsystem.
+ *
+ * Begun April 1, 1996, Mike Shaver.
+ * Added /proc/sys/net/ether directory entry (empty =) ). [MS]
+ */
+
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+
+ctl_table ether_table[] = {
+ {0}
+};
diff --git a/uClinux-2.4.31-uc0/net/ipv4/Config.in b/uClinux-2.4.31-uc0/net/ipv4/Config.in
new file mode 100644
index 0000000..f79a742
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/Config.in
@@ -0,0 +1,62 @@
+# $USAGI: Config.in,v 1.12 2003/11/12 05:12:00 yoshfuji Exp $
+
+#
+# IP configuration
+#
+bool ' IP: multicasting' CONFIG_IP_MULTICAST
+bool ' IP: advanced router' CONFIG_IP_ADVANCED_ROUTER
+if [ "$CONFIG_IP_ADVANCED_ROUTER" = "y" ]; then
+ bool ' IP: policy routing' CONFIG_IP_MULTIPLE_TABLES
+ bool ' IP: increase default route cache size' CONFIG_IP_ROUTE_BIG_RT_CACHE
+ if [ "$CONFIG_IP_MULTIPLE_TABLES" = "y" ]; then
+ if [ "$CONFIG_NETFILTER" = "y" ]; then
+ bool ' IP: use netfilter MARK value as routing key' CONFIG_IP_ROUTE_FWMARK
+ fi
+ bool ' IP: fast network address translation' CONFIG_IP_ROUTE_NAT
+ fi
+ bool ' IP: equal cost multipath' CONFIG_IP_ROUTE_MULTIPATH
+ dep_bool ' IP: Use sequential path selection.' CONFIG_IP_ROUTE_MULTIPATH_SEQUENTIAL $CONFIG_IP_ROUTE_MULTIPATH
+ bool ' IP: use TOS value as routing key' CONFIG_IP_ROUTE_TOS
+ bool ' IP: verbose route monitoring' CONFIG_IP_ROUTE_VERBOSE
+fi
+bool ' IP: kernel level autoconfiguration' CONFIG_IP_PNP
+if [ "$CONFIG_IP_PNP" = "y" ]; then
+ bool ' IP: DHCP support' CONFIG_IP_PNP_DHCP
+ bool ' IP: BOOTP support' CONFIG_IP_PNP_BOOTP
+ bool ' IP: RARP support' CONFIG_IP_PNP_RARP
+# not yet ready..
+# bool ' IP: ARP support' CONFIG_IP_PNP_ARP
+fi
+bool 'ARP MAC Limiting' CONFIG_NET_ARP_LIMIT
+if [ "$CONFIG_NET_ARP_LIMIT" = "y" ]; then
+ int 'Maximum number of MAC addresses' CONFIG_ARP_LIMIT 20
+fi
+tristate ' IP: tunneling' CONFIG_NET_IPIP
+if [ "$CONFIG_NET_IPIP" = "y" ]; then
+ if [ "$CONFIG_IPV6" != "n" ]; then
+ bool ' IP: IPv6 over IPv4 tunneling support (replaces sitXX devices)' CONFIG_NET_IPIP_IPV6
+ fi
+fi
+tristate ' IP: GRE tunnels over IP' CONFIG_NET_IPGRE
+if [ "$CONFIG_IP_MULTICAST" = "y" ]; then
+ if [ "$CONFIG_NET_IPGRE" != "n" ]; then
+ bool ' IP: broadcast GRE over IP' CONFIG_NET_IPGRE_BROADCAST
+ fi
+ bool ' IP: multicast routing' CONFIG_IP_MROUTE
+ if [ "$CONFIG_IP_MROUTE" = "y" ]; then
+ bool ' IP: PIM-SM version 1 support' CONFIG_IP_PIMSM_V1
+ bool ' IP: PIM-SM version 2 support' CONFIG_IP_PIMSM_V2
+ fi
+fi
+if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ bool ' IP: ARP daemon support (EXPERIMENTAL)' CONFIG_ARPD
+fi
+bool ' IP: TCP Explicit Congestion Notification support' CONFIG_INET_ECN
+bool ' IP: TCP syncookie support (disabled per default)' CONFIG_SYN_COOKIES
+
+if [ "$CONFIG_NETFILTER" != "n" ]; then
+ source net/ipv4/netfilter/Config.in
+fi
+if [ "$CONFIG_NETFILTER" != "n" ]; then
+ source net/ipv4/ipvs/Config.in
+fi
diff --git a/uClinux-2.4.31-uc0/net/ipv4/Makefile b/uClinux-2.4.31-uc0/net/ipv4/Makefile
new file mode 100644
index 0000000..90559f6
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/Makefile
@@ -0,0 +1,29 @@
+#
+# Makefile for the Linux TCP/IP (INET) layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := ipv4.o
+
+export-objs = ipip.o ip_gre.o
+
+obj-y := utils.o route.o inetpeer.o proc.o protocol.o \
+ ip_input.o ip_fragment.o ip_forward.o ip_options.o \
+ ip_output.o ip_sockglue.o \
+ tcp.o tcp_input.o tcp_output.o tcp_timer.o tcp_ipv4.o tcp_minisocks.o \
+ tcp_diag.o raw.o udp.o arp.o icmp.o devinet.o af_inet.o igmp.o \
+ sysctl_net_ipv4.o fib_frontend.o fib_semantics.o fib_hash.o
+
+obj-$(CONFIG_IP_MULTIPLE_TABLES) += fib_rules.o
+obj-$(CONFIG_IP_ROUTE_NAT) += ip_nat_dumb.o
+obj-$(CONFIG_IP_MROUTE) += ipmr.o
+obj-$(CONFIG_NET_IPIP) += ipip.o
+obj-$(CONFIG_NET_IPGRE) += ip_gre.o
+obj-$(CONFIG_SYN_COOKIES) += syncookies.o
+obj-$(CONFIG_IP_PNP) += ipconfig.o
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/ipv4/af_inet.c b/uClinux-2.4.31-uc0/net/ipv4/af_inet.c
new file mode 100644
index 0000000..dcf5443
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/af_inet.c
@@ -0,0 +1,1229 @@
+/* $USAGI: af_inet.c,v 1.16 2003/11/12 05:12:00 yoshfuji Exp $ */
+
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * PF_INET protocol family socket handler.
+ *
+ * Version: $Id: af_inet.c,v 1.136 2001/11/06 22:21:08 davem Exp $
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Florian La Roche, <flla@stud.uni-sb.de>
+ * Alan Cox, <A.Cox@swansea.ac.uk>
+ *
+ * Changes (see also sock.c)
+ *
+ * piggy,
+ * Karl Knutson : Socket protocol table
+ * A.N.Kuznetsov : Socket death error in accept().
+ * John Richardson : Fix non blocking error in connect()
+ * so sockets that fail to connect
+ * don't return -EINPROGRESS.
+ * Alan Cox : Asynchronous I/O support
+ * Alan Cox : Keep correct socket pointer on sock structures
+ * when accept() ed
+ * Alan Cox : Semantics of SO_LINGER aren't state moved
+ * to close when you look carefully. With
+ * this fixed and the accept bug fixed
+ * some RPC stuff seems happier.
+ * Niibe Yutaka : 4.4BSD style write async I/O
+ * Alan Cox,
+ * Tony Gale : Fixed reuse semantics.
+ * Alan Cox : bind() shouldn't abort existing but dead
+ * sockets. Stops FTP netin:.. I hope.
+ * Alan Cox : bind() works correctly for RAW sockets. Note
+ * that FreeBSD at least was broken in this respect
+ * so be careful with compatibility tests...
+ * Alan Cox : routing cache support
+ * Alan Cox : memzero the socket structure for compactness.
+ * Matt Day : nonblock connect error handler
+ * Alan Cox : Allow large numbers of pending sockets
+ * (eg for big web sites), but only if
+ * specifically application requested.
+ * Alan Cox : New buffering throughout IP. Used dumbly.
+ * Alan Cox : New buffering now used smartly.
+ * Alan Cox : BSD rather than common sense interpretation of
+ * listen.
+ * Germano Caronni : Assorted small races.
+ * Alan Cox : sendmsg/recvmsg basic support.
+ * Alan Cox : Only sendmsg/recvmsg now supported.
+ * Alan Cox : Locked down bind (see security list).
+ * Alan Cox : Loosened bind a little.
+ * Mike McLagan : ADD/DEL DLCI Ioctls
+ * Willy Konynenberg : Transparent proxying support.
+ * David S. Miller : New socket lookup architecture.
+ * Some other random speedups.
+ * Cyrus Durgin : Cleaned up file for kmod hacks.
+ * Andi Kleen : Fix inet_stream_connect TCP race.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/netfilter_ipv4.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#include <linux/smp_lock.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/brlock.h>
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <net/arp.h>
+#include <net/route.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/raw.h>
+#include <net/icmp.h>
+#include <net/ipip.h>
+#include <net/inet_common.h>
+#ifdef CONFIG_IP_MROUTE
+#include <linux/mroute.h>
+#endif
+#include <linux/if_bridge.h>
+#ifdef CONFIG_KMOD
+#include <linux/kmod.h>
+#endif
+#ifdef CONFIG_NET_DIVERT
+#include <linux/divert.h>
+#endif /* CONFIG_NET_DIVERT */
+#if defined(CONFIG_NET_RADIO) || defined(CONFIG_NET_PCMCIA_RADIO)
+#include <linux/wireless.h> /* Note : will define WIRELESS_EXT */
+#endif /* CONFIG_NET_RADIO || CONFIG_NET_PCMCIA_RADIO */
+
+struct linux_mib net_statistics[NR_CPUS*2];
+
+#ifdef INET_REFCNT_DEBUG
+atomic_t inet_sock_nr;
+#endif
+
+extern int raw_get_info(char *, char **, off_t, int);
+extern int snmp_get_info(char *, char **, off_t, int);
+extern int netstat_get_info(char *, char **, off_t, int);
+extern int afinet_get_info(char *, char **, off_t, int);
+extern int tcp_get_info(char *, char **, off_t, int);
+extern int udp_get_info(char *, char **, off_t, int);
+extern void ip_mc_drop_socket(struct sock *sk);
+
+#ifdef CONFIG_DLCI
+extern int dlci_ioctl(unsigned int, void*);
+#endif
+
+#ifdef CONFIG_DLCI_MODULE
+int (*dlci_ioctl_hook)(unsigned int, void *);
+#endif
+
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+int (*br_ioctl_hook)(unsigned long);
+#endif
+
+#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
+int (*vlan_ioctl_hook)(unsigned long arg);
+#endif
+
+/* The inetsw table contains everything that inet_create needs to
+ * build a new socket.
+ */
+struct list_head inetsw[SOCK_MAX];
+
+/* New destruction routine */
+
+void inet_sock_destruct(struct sock *sk)
+{
+ __skb_queue_purge(&sk->receive_queue);
+ __skb_queue_purge(&sk->error_queue);
+
+ if (sk->type == SOCK_STREAM && sk->state != TCP_CLOSE) {
+ printk("Attempt to release TCP socket in state %d %p\n",
+ sk->state,
+ sk);
+ return;
+ }
+ if (!sk->dead) {
+ printk("Attempt to release alive inet socket %p\n", sk);
+ return;
+ }
+
+ BUG_TRAP(atomic_read(&sk->rmem_alloc) == 0);
+ BUG_TRAP(atomic_read(&sk->wmem_alloc) == 0);
+ BUG_TRAP(sk->wmem_queued == 0);
+ BUG_TRAP(sk->forward_alloc == 0);
+
+ if (sk->protinfo.af_inet.opt)
+ kfree(sk->protinfo.af_inet.opt);
+ dst_release(sk->dst_cache);
+#ifdef INET_REFCNT_DEBUG
+ atomic_dec(&inet_sock_nr);
+ printk(KERN_DEBUG "INET socket %p released, %d are still alive\n", sk, atomic_read(&inet_sock_nr));
+#endif
+}
+
+void inet_sock_release(struct sock *sk)
+{
+ if (sk->prot->destroy)
+ sk->prot->destroy(sk);
+
+ /* Observation: when inet_sock_release is called, processes have
+ * no access to socket. But net still has.
+ * Step one, detach it from networking:
+ *
+ * A. Remove from hash tables.
+ */
+
+ sk->prot->unhash(sk);
+
+ /* In this point socket cannot receive new packets,
+ * but it is possible that some packets are in flight
+ * because some CPU runs receiver and did hash table lookup
+ * before we unhashed socket. They will achieve receive queue
+ * and will be purged by socket destructor.
+ *
+ * Also we still have packets pending on receive
+ * queue and probably, our own packets waiting in device queues.
+ * sock_destroy will drain receive queue, but transmitted
+ * packets will delay socket destruction until the last reference
+ * will be released.
+ */
+
+ sock_orphan(sk);
+
+#ifdef INET_REFCNT_DEBUG
+ if (atomic_read(&sk->refcnt) != 1) {
+ printk(KERN_DEBUG "Destruction inet %p delayed, c=%d\n", sk, atomic_read(&sk->refcnt));
+ }
+#endif
+ sock_put(sk);
+}
+
+
+/*
+ * The routines beyond this point handle the behaviour of an AF_INET
+ * socket object. Mostly it punts to the subprotocols of IP to do
+ * the work.
+ */
+
+
+/*
+ * Set socket options on an inet socket.
+ */
+
+int inet_setsockopt(struct socket *sock, int level, int optname,
+ char *optval, int optlen)
+{
+ struct sock *sk=sock->sk;
+
+ return sk->prot->setsockopt(sk,level,optname,optval,optlen);
+}
+
+/*
+ * Get a socket option on an AF_INET socket.
+ *
+ * FIX: POSIX 1003.1g is very ambiguous here. It states that
+ * asynchronous errors should be reported by getsockopt. We assume
+ * this means if you specify SO_ERROR (otherwise whats the point of it).
+ */
+
+int inet_getsockopt(struct socket *sock, int level, int optname,
+ char *optval, int *optlen)
+{
+ struct sock *sk=sock->sk;
+
+ return sk->prot->getsockopt(sk,level,optname,optval,optlen);
+}
+
+/*
+ * Automatically bind an unbound socket.
+ */
+
+static int inet_autobind(struct sock *sk)
+{
+ /* We may need to bind the socket. */
+ lock_sock(sk);
+ if (sk->num == 0) {
+ if (sk->prot->get_port(sk, 0) != 0) {
+ release_sock(sk);
+ return -EAGAIN;
+ }
+ sk->sport = htons(sk->num);
+ }
+ release_sock(sk);
+ return 0;
+}
+
+/*
+ * Move a socket into listening state.
+ */
+
+int inet_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+ unsigned char old_state;
+ int err;
+
+ lock_sock(sk);
+
+ err = -EINVAL;
+ if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
+ goto out;
+
+ old_state = sk->state;
+ if (!((1<<old_state)&(TCPF_CLOSE|TCPF_LISTEN)))
+ goto out;
+
+ /* Really, if the socket is already in listen state
+ * we can only allow the backlog to be adjusted.
+ */
+ if (old_state != TCP_LISTEN) {
+ err = tcp_listen_start(sk);
+ if (err)
+ goto out;
+ }
+ sk->max_ack_backlog = backlog;
+ err = 0;
+
+out:
+ release_sock(sk);
+ return err;
+}
+
+/*
+ * Create an inet socket.
+ */
+
+static int inet_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+ struct list_head *p;
+ struct inet_protosw *answer;
+
+ sock->state = SS_UNCONNECTED;
+ sk = sk_alloc(PF_INET, GFP_KERNEL, 1);
+ if (sk == NULL)
+ goto do_oom;
+
+ /* Look for the requested type/protocol pair. */
+ answer = NULL;
+ br_read_lock_bh(BR_NETPROTO_LOCK);
+ list_for_each(p, &inetsw[sock->type]) {
+ answer = list_entry(p, struct inet_protosw, list);
+
+ /* Check the non-wild match. */
+ if (protocol == answer->protocol) {
+ if (protocol != IPPROTO_IP)
+ break;
+ } else {
+ /* Check for the two wild cases. */
+ if (IPPROTO_IP == protocol) {
+ protocol = answer->protocol;
+ break;
+ }
+ if (IPPROTO_IP == answer->protocol)
+ break;
+ }
+ answer = NULL;
+ }
+ br_read_unlock_bh(BR_NETPROTO_LOCK);
+
+ if (!answer)
+ goto free_and_badtype;
+ if (answer->capability > 0 && !capable(answer->capability))
+ goto free_and_badperm;
+ if (!protocol)
+ goto free_and_noproto;
+
+ sock->ops = answer->ops;
+ sk->prot = answer->prot;
+ sk->no_check = answer->no_check;
+ if (INET_PROTOSW_REUSE & answer->flags)
+ sk->reuse = 1;
+
+ if (SOCK_RAW == sock->type) {
+ sk->num = protocol;
+ if (IPPROTO_RAW == protocol)
+ sk->protinfo.af_inet.hdrincl = 1;
+ }
+
+ if (ipv4_config.no_pmtu_disc)
+ sk->protinfo.af_inet.pmtudisc = IP_PMTUDISC_DONT;
+ else
+ sk->protinfo.af_inet.pmtudisc = IP_PMTUDISC_WANT;
+
+ sk->protinfo.af_inet.id = 0;
+
+ sock_init_data(sock,sk);
+
+ sk->destruct = inet_sock_destruct;
+
+ sk->zapped = 0;
+ sk->family = PF_INET;
+ sk->protocol = protocol;
+
+ sk->backlog_rcv = sk->prot->backlog_rcv;
+
+ sk->protinfo.af_inet.ttl = sysctl_ip_default_ttl;
+
+ sk->protinfo.af_inet.mc_loop = 1;
+ sk->protinfo.af_inet.mc_ttl = 1;
+ sk->protinfo.af_inet.mc_index = 0;
+ sk->protinfo.af_inet.mc_list = NULL;
+
+#ifdef INET_REFCNT_DEBUG
+ atomic_inc(&inet_sock_nr);
+#endif
+
+ if (sk->num) {
+ /* It assumes that any protocol which allows
+ * the user to assign a number at socket
+ * creation time automatically
+ * shares.
+ */
+ sk->sport = htons(sk->num);
+
+ /* Add to protocol hash chains. */
+ sk->prot->hash(sk);
+ }
+
+ if (sk->prot->init) {
+ int err = sk->prot->init(sk);
+ if (err != 0) {
+ inet_sock_release(sk);
+ return err;
+ }
+ }
+ return 0;
+
+free_and_badtype:
+ sk_free(sk);
+ return -ESOCKTNOSUPPORT;
+
+free_and_badperm:
+ sk_free(sk);
+ return -EPERM;
+
+free_and_noproto:
+ sk_free(sk);
+ return -EPROTONOSUPPORT;
+
+do_oom:
+ return -ENOBUFS;
+}
+
+
+/*
+ * The peer socket should always be NULL (or else). When we call this
+ * function we are destroying the object and from then on nobody
+ * should refer to it.
+ */
+
+int inet_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk) {
+ long timeout;
+
+ /* Applications forget to leave groups before exiting */
+ ip_mc_drop_socket(sk);
+
+ /* If linger is set, we don't return until the close
+ * is complete. Otherwise we return immediately. The
+ * actually closing is done the same either way.
+ *
+ * If the close is due to the process exiting, we never
+ * linger..
+ */
+ timeout = 0;
+ if (sk->linger && !(current->flags & PF_EXITING))
+ timeout = sk->lingertime;
+ sock->sk = NULL;
+ sk->prot->close(sk, timeout);
+ }
+ return(0);
+}
+
+/* It is off by default, see below. */
+int sysctl_ip_nonlocal_bind;
+
+int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sockaddr_in *addr=(struct sockaddr_in *)uaddr;
+ struct sock *sk=sock->sk;
+ unsigned short snum;
+ int chk_addr_ret;
+ int err;
+
+ /* If the socket has its own bind function then use it. (RAW) */
+ if(sk->prot->bind)
+ return sk->prot->bind(sk, uaddr, addr_len);
+
+ if (addr_len < sizeof(struct sockaddr_in))
+ return -EINVAL;
+
+ chk_addr_ret = inet_addr_type(addr->sin_addr.s_addr);
+
+ /* Not specified by any standard per-se, however it breaks too
+ * many applications when removed. It is unfortunate since
+ * allowing applications to make a non-local bind solves
+ * several problems with systems using dynamic addressing.
+ * (ie. your servers still start up even if your ISDN link
+ * is temporarily down)
+ */
+ if (sysctl_ip_nonlocal_bind == 0 &&
+ sk->protinfo.af_inet.freebind == 0 &&
+ addr->sin_addr.s_addr != INADDR_ANY &&
+ chk_addr_ret != RTN_LOCAL &&
+ chk_addr_ret != RTN_MULTICAST &&
+ chk_addr_ret != RTN_BROADCAST)
+ return -EADDRNOTAVAIL;
+
+ snum = ntohs(addr->sin_port);
+ if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))
+ return -EACCES;
+
+ /* We keep a pair of addresses. rcv_saddr is the one
+ * used by hash lookups, and saddr is used for transmit.
+ *
+ * In the BSD API these are the same except where it
+ * would be illegal to use them (multicast/broadcast) in
+ * which case the sending device address is used.
+ */
+ lock_sock(sk);
+
+ /* Check these errors (active socket, double bind). */
+ err = -EINVAL;
+ if ((sk->state != TCP_CLOSE) ||
+ (sk->num != 0))
+ goto out;
+
+ sk->rcv_saddr = sk->saddr = addr->sin_addr.s_addr;
+ if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
+ sk->saddr = 0; /* Use device */
+
+ /* Make sure we are allowed to bind here. */
+ if (sk->prot->get_port(sk, snum) != 0) {
+ sk->saddr = sk->rcv_saddr = 0;
+ err = -EADDRINUSE;
+ goto out;
+ }
+
+ if (sk->rcv_saddr)
+ sk->userlocks |= SOCK_BINDADDR_LOCK;
+ if (snum)
+ sk->userlocks |= SOCK_BINDPORT_LOCK;
+ sk->sport = htons(sk->num);
+ sk->daddr = 0;
+ sk->dport = 0;
+ sk_dst_reset(sk);
+ err = 0;
+out:
+ release_sock(sk);
+ return err;
+}
+
+int inet_dgram_connect(struct socket *sock, struct sockaddr * uaddr,
+ int addr_len, int flags)
+{
+ struct sock *sk=sock->sk;
+
+ if (uaddr->sa_family == AF_UNSPEC)
+ return sk->prot->disconnect(sk, flags);
+
+ if (sk->num==0 && inet_autobind(sk) != 0)
+ return -EAGAIN;
+ return sk->prot->connect(sk, (struct sockaddr *)uaddr, addr_len);
+}
+
+static long inet_wait_for_connect(struct sock *sk, long timeo)
+{
+ DECLARE_WAITQUEUE(wait, current);
+
+ __set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(sk->sleep, &wait);
+
+ /* Basic assumption: if someone sets sk->err, he _must_
+ * change state of the socket from TCP_SYN_*.
+ * Connect() does not allow to get error notifications
+ * without closing the socket.
+ */
+ while ((1<<sk->state)&(TCPF_SYN_SENT|TCPF_SYN_RECV)) {
+ release_sock(sk);
+ timeo = schedule_timeout(timeo);
+ lock_sock(sk);
+ if (signal_pending(current) || !timeo)
+ break;
+ set_current_state(TASK_INTERRUPTIBLE);
+ }
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(sk->sleep, &wait);
+ return timeo;
+}
+
+/*
+ * Connect to a remote host. There is regrettably still a little
+ * TCP 'magic' in here.
+ */
+
+int inet_stream_connect(struct socket *sock, struct sockaddr * uaddr,
+ int addr_len, int flags)
+{
+ struct sock *sk=sock->sk;
+ int err;
+ long timeo;
+
+ lock_sock(sk);
+
+ if (uaddr->sa_family == AF_UNSPEC) {
+ err = sk->prot->disconnect(sk, flags);
+ sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
+ goto out;
+ }
+
+ switch (sock->state) {
+ default:
+ err = -EINVAL;
+ goto out;
+ case SS_CONNECTED:
+ err = -EISCONN;
+ goto out;
+ case SS_CONNECTING:
+ err = -EALREADY;
+ /* Fall out of switch with err, set for this state */
+ break;
+ case SS_UNCONNECTED:
+ err = -EISCONN;
+ if (sk->state != TCP_CLOSE)
+ goto out;
+
+ err = sk->prot->connect(sk, uaddr, addr_len);
+ if (err < 0)
+ goto out;
+
+ sock->state = SS_CONNECTING;
+
+ /* Just entered SS_CONNECTING state; the only
+ * difference is that return value in non-blocking
+ * case is EINPROGRESS, rather than EALREADY.
+ */
+ err = -EINPROGRESS;
+ break;
+ }
+
+ timeo = sock_sndtimeo(sk, flags&O_NONBLOCK);
+
+ if ((1<<sk->state)&(TCPF_SYN_SENT|TCPF_SYN_RECV)) {
+ /* Error code is set above */
+ if (!timeo || !inet_wait_for_connect(sk, timeo))
+ goto out;
+
+ err = sock_intr_errno(timeo);
+ if (signal_pending(current))
+ goto out;
+ }
+
+ /* Connection was closed by RST, timeout, ICMP error
+ * or another process disconnected us.
+ */
+ if (sk->state == TCP_CLOSE)
+ goto sock_error;
+
+ /* sk->err may be not zero now, if RECVERR was ordered by user
+ * and error was received after socket entered established state.
+ * Hence, it is handled normally after connect() return successfully.
+ */
+
+ sock->state = SS_CONNECTED;
+ err = 0;
+out:
+ release_sock(sk);
+ return err;
+
+sock_error:
+ err = sock_error(sk) ? : -ECONNABORTED;
+ sock->state = SS_UNCONNECTED;
+ if (sk->prot->disconnect(sk, flags))
+ sock->state = SS_DISCONNECTING;
+ goto out;
+}
+
+/*
+ * Accept a pending connection. The TCP layer now gives BSD semantics.
+ */
+
+int inet_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+ struct sock *sk1 = sock->sk;
+ struct sock *sk2;
+ int err = -EINVAL;
+
+ if((sk2 = sk1->prot->accept(sk1,flags,&err)) == NULL)
+ goto do_err;
+
+ lock_sock(sk2);
+
+ BUG_TRAP((1<<sk2->state)&(TCPF_ESTABLISHED|TCPF_CLOSE_WAIT|TCPF_CLOSE));
+
+ sock_graft(sk2, newsock);
+
+ newsock->state = SS_CONNECTED;
+ release_sock(sk2);
+ return 0;
+
+do_err:
+ return err;
+}
+
+
+/*
+ * This does both peername and sockname.
+ */
+
+int inet_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *uaddr_len, int peer)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_in *sin = (struct sockaddr_in *)uaddr;
+
+ sin->sin_family = AF_INET;
+ if (peer) {
+ if (!sk->dport)
+ return -ENOTCONN;
+ if (((1<<sk->state)&(TCPF_CLOSE|TCPF_SYN_SENT)) && peer == 1)
+ return -ENOTCONN;
+ sin->sin_port = sk->dport;
+ sin->sin_addr.s_addr = sk->daddr;
+ } else {
+ __u32 addr = sk->rcv_saddr;
+ if (!addr)
+ addr = sk->saddr;
+ sin->sin_port = sk->sport;
+ sin->sin_addr.s_addr = addr;
+ }
+ memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
+ *uaddr_len = sizeof(*sin);
+ return(0);
+}
+
+
+
+int inet_recvmsg(struct socket *sock, struct msghdr *msg, int size,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ int addr_len = 0;
+ int err;
+
+ err = sk->prot->recvmsg(sk, msg, size, flags&MSG_DONTWAIT,
+ flags&~MSG_DONTWAIT, &addr_len);
+ if (err >= 0)
+ msg->msg_namelen = addr_len;
+ return err;
+}
+
+
+int inet_sendmsg(struct socket *sock, struct msghdr *msg, int size,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+
+ /* We may need to bind the socket. */
+ if (sk->num==0 && inet_autobind(sk) != 0)
+ return -EAGAIN;
+
+ return sk->prot->sendmsg(sk, msg, size);
+}
+
+int inet_shutdown(struct socket *sock, int how)
+{
+ struct sock *sk = sock->sk;
+ int err = 0;
+
+ /* This should really check to make sure
+ * the socket is a TCP socket. (WHY AC...)
+ */
+ how++; /* maps 0->1 has the advantage of making bit 1 rcvs and
+ 1->2 bit 2 snds.
+ 2->3 */
+ if ((how & ~SHUTDOWN_MASK) || how==0) /* MAXINT->0 */
+ return -EINVAL;
+
+ lock_sock(sk);
+ if (sock->state == SS_CONNECTING) {
+ if ((1<<sk->state)&(TCPF_SYN_SENT|TCPF_SYN_RECV|TCPF_CLOSE))
+ sock->state = SS_DISCONNECTING;
+ else
+ sock->state = SS_CONNECTED;
+ }
+
+ switch (sk->state) {
+ case TCP_CLOSE:
+ err = -ENOTCONN;
+ /* Hack to wake up other listeners, who can poll for
+ POLLHUP, even on eg. unconnected UDP sockets -- RR */
+ default:
+ sk->shutdown |= how;
+ if (sk->prot->shutdown)
+ sk->prot->shutdown(sk, how);
+ break;
+
+ /* Remaining two branches are temporary solution for missing
+ * close() in multithreaded environment. It is _not_ a good idea,
+ * but we have no choice until close() is repaired at VFS level.
+ */
+ case TCP_LISTEN:
+ if (!(how & RCV_SHUTDOWN))
+ break;
+ /* Fall through */
+ case TCP_SYN_SENT:
+ err = sk->prot->disconnect(sk, O_NONBLOCK);
+ sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
+ break;
+ }
+
+ /* Wake up anyone sleeping in poll. */
+ sk->state_change(sk);
+ release_sock(sk);
+ return err;
+}
+
+/*
+ * ioctl() calls you can issue on an INET socket. Most of these are
+ * device configuration and stuff and very rarely used. Some ioctls
+ * pass on to the socket itself.
+ *
+ * NOTE: I like the idea of a module for the config stuff. ie ifconfig
+ * loads the devconfigure module does its configuring and unloads it.
+ * There's a good 20K of config code hanging around the kernel.
+ */
+
+int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+ int err;
+ int pid;
+
+ switch(cmd) {
+ case FIOSETOWN:
+ case SIOCSPGRP:
+ err = get_user(pid, (int *) arg);
+ if (err)
+ return err;
+ if (current->pid != pid && current->pgrp != -pid &&
+ !capable(CAP_NET_ADMIN))
+ return -EPERM;
+ sk->proc = pid;
+ return(0);
+ case FIOGETOWN:
+ case SIOCGPGRP:
+ return put_user(sk->proc, (int *)arg);
+ case SIOCGSTAMP:
+ if(sk->stamp.tv_sec==0)
+ return -ENOENT;
+ err = copy_to_user((void *)arg,&sk->stamp,sizeof(struct timeval));
+ if (err)
+ err = -EFAULT;
+ return err;
+ case SIOCADDRT:
+ case SIOCDELRT:
+ case SIOCRTMSG:
+ return(ip_rt_ioctl(cmd,(void *) arg));
+ case SIOCDARP:
+ case SIOCGARP:
+ case SIOCSARP:
+ return(arp_ioctl(cmd,(void *) arg));
+ case SIOCGIFADDR:
+ case SIOCSIFADDR:
+ case SIOCGIFBRDADDR:
+ case SIOCSIFBRDADDR:
+ case SIOCGIFNETMASK:
+ case SIOCSIFNETMASK:
+ case SIOCGIFDSTADDR:
+ case SIOCSIFDSTADDR:
+ case SIOCSIFPFLAGS:
+ case SIOCGIFPFLAGS:
+ case SIOCSIFFLAGS:
+ return(devinet_ioctl(cmd,(void *) arg));
+ case SIOCGIFBR:
+ case SIOCSIFBR:
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+#ifdef CONFIG_KMOD
+ if (br_ioctl_hook == NULL)
+ request_module("bridge");
+#endif
+ if (br_ioctl_hook != NULL)
+ return br_ioctl_hook(arg);
+#endif
+ return -ENOPKG;
+
+ case SIOCGIFVLAN:
+ case SIOCSIFVLAN:
+#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
+#ifdef CONFIG_KMOD
+ if (vlan_ioctl_hook == NULL)
+ request_module("8021q");
+#endif
+ if (vlan_ioctl_hook != NULL)
+ return vlan_ioctl_hook(arg);
+#endif
+ return -ENOPKG;
+
+ case SIOCGIFDIVERT:
+ case SIOCSIFDIVERT:
+#ifdef CONFIG_NET_DIVERT
+ return divert_ioctl(cmd, (struct divert_cf *) arg);
+#else
+ return -ENOPKG;
+#endif /* CONFIG_NET_DIVERT */
+
+ case SIOCADDDLCI:
+ case SIOCDELDLCI:
+#ifdef CONFIG_DLCI
+ lock_kernel();
+ err = dlci_ioctl(cmd, (void *) arg);
+ unlock_kernel();
+ return err;
+#endif
+
+#ifdef CONFIG_DLCI_MODULE
+
+#ifdef CONFIG_KMOD
+ if (dlci_ioctl_hook == NULL)
+ request_module("dlci");
+#endif
+
+ if (dlci_ioctl_hook) {
+ lock_kernel();
+ err = (*dlci_ioctl_hook)(cmd, (void *) arg);
+ unlock_kernel();
+ return err;
+ }
+#endif
+ return -ENOPKG;
+
+ default:
+ if ((cmd >= SIOCDEVPRIVATE) &&
+ (cmd <= (SIOCDEVPRIVATE + 15)))
+ return(dev_ioctl(cmd,(void *) arg));
+
+#ifdef WIRELESS_EXT
+ if((cmd >= SIOCIWFIRST) && (cmd <= SIOCIWLAST))
+ return(dev_ioctl(cmd,(void *) arg));
+#endif /* WIRELESS_EXT */
+
+ if (sk->prot->ioctl==NULL || (err=sk->prot->ioctl(sk, cmd, arg))==-ENOIOCTLCMD)
+ return(dev_ioctl(cmd,(void *) arg));
+ return err;
+ }
+ /*NOTREACHED*/
+ return(0);
+}
+
+struct proto_ops inet_stream_ops = {
+ family: PF_INET,
+
+ release: inet_release,
+ bind: inet_bind,
+ connect: inet_stream_connect,
+ socketpair: sock_no_socketpair,
+ accept: inet_accept,
+ getname: inet_getname,
+ poll: tcp_poll,
+ ioctl: inet_ioctl,
+ listen: inet_listen,
+ shutdown: inet_shutdown,
+ setsockopt: inet_setsockopt,
+ getsockopt: inet_getsockopt,
+ sendmsg: inet_sendmsg,
+ recvmsg: inet_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: tcp_sendpage
+};
+
+struct proto_ops inet_dgram_ops = {
+ family: PF_INET,
+
+ release: inet_release,
+ bind: inet_bind,
+ connect: inet_dgram_connect,
+ socketpair: sock_no_socketpair,
+ accept: sock_no_accept,
+ getname: inet_getname,
+ poll: udp_poll,
+ ioctl: inet_ioctl,
+ listen: sock_no_listen,
+ shutdown: inet_shutdown,
+ setsockopt: inet_setsockopt,
+ getsockopt: inet_getsockopt,
+ sendmsg: inet_sendmsg,
+ recvmsg: inet_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+struct proto_ops inet_sockraw_ops = {
+ family: PF_INET,
+
+ release: inet_release,
+ bind: inet_bind,
+ connect: inet_dgram_connect,
+ socketpair: sock_no_socketpair,
+ accept: sock_no_accept,
+ getname: inet_getname,
+ poll: datagram_poll,
+ ioctl: inet_ioctl,
+ listen: sock_no_listen,
+ shutdown: inet_shutdown,
+ setsockopt: inet_setsockopt,
+ getsockopt: inet_getsockopt,
+ sendmsg: inet_sendmsg,
+ recvmsg: inet_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+struct net_proto_family inet_family_ops = {
+ family: PF_INET,
+ create: inet_create
+};
+
+
+extern void tcp_init(void);
+extern void tcp_v4_init(struct net_proto_family *);
+
+/* Upon startup we insert all the elements in inetsw_array[] into
+ * the linked list inetsw.
+ */
+static struct inet_protosw inetsw_array[] =
+{
+ {
+ type: SOCK_STREAM,
+ protocol: IPPROTO_TCP,
+ prot: &tcp_prot,
+ ops: &inet_stream_ops,
+ capability: -1,
+ no_check: 0,
+ flags: INET_PROTOSW_PERMANENT,
+ },
+
+ {
+ type: SOCK_DGRAM,
+ protocol: IPPROTO_UDP,
+ prot: &udp_prot,
+ ops: &inet_dgram_ops,
+ capability: -1,
+ no_check: UDP_CSUM_DEFAULT,
+ flags: INET_PROTOSW_PERMANENT,
+ },
+
+
+ {
+ type: SOCK_RAW,
+ protocol: IPPROTO_IP, /* wild card */
+ prot: &raw_prot,
+ ops: &inet_sockraw_ops,
+ capability: CAP_NET_RAW,
+ no_check: UDP_CSUM_DEFAULT,
+ flags: INET_PROTOSW_REUSE,
+ }
+};
+
+#define INETSW_ARRAY_LEN (sizeof(inetsw_array) / sizeof(struct inet_protosw))
+
+void
+inet_register_protosw(struct inet_protosw *p)
+{
+ struct list_head *lh;
+ struct inet_protosw *answer;
+ int protocol = p->protocol;
+ struct list_head *last_perm;
+
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+
+ if (p->type >= SOCK_MAX)
+ goto out_illegal;
+
+ /* If we are trying to override a permanent protocol, bail. */
+ answer = NULL;
+ last_perm = &inetsw[p->type];
+ list_for_each(lh, &inetsw[p->type]) {
+ answer = list_entry(lh, struct inet_protosw, list);
+
+ /* Check only the non-wild match. */
+ if (INET_PROTOSW_PERMANENT & answer->flags) {
+ if (protocol == answer->protocol)
+ break;
+ last_perm = lh;
+ }
+
+ answer = NULL;
+ }
+ if (answer)
+ goto out_permanent;
+
+ /* Add the new entry after the last permanent entry if any, so that
+ * the new entry does not override a permanent entry when matched with
+ * a wild-card protocol. But it is allowed to override any existing
+ * non-permanent entry. This means that when we remove this entry, the
+ * system automatically returns to the old behavior.
+ */
+ list_add(&p->list, last_perm);
+out:
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ return;
+
+out_permanent:
+ printk(KERN_ERR "Attempt to override permanent protocol %d.\n",
+ protocol);
+ goto out;
+
+out_illegal:
+ printk(KERN_ERR
+ "Ignoring attempt to register illegal socket type %d.\n",
+ p->type);
+ goto out;
+}
+
+void
+inet_unregister_protosw(struct inet_protosw *p)
+{
+ if (INET_PROTOSW_PERMANENT & p->flags) {
+ printk(KERN_ERR
+ "Attempt to unregister permanent protocol %d.\n",
+ p->protocol);
+ } else {
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ list_del(&p->list);
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ }
+}
+
+extern void ipfrag_init(void);
+
+/*
+ * Called by socket.c on kernel startup.
+ */
+
+static int __init inet_init(void)
+{
+ struct sk_buff *dummy_skb;
+ struct inet_protocol *p;
+ struct inet_protosw *q;
+ struct list_head *r;
+
+ printk(KERN_INFO "NET4: Linux TCP/IP 1.0 for NET4.0\n");
+
+ if (sizeof(struct inet_skb_parm) > sizeof(dummy_skb->cb)) {
+ printk(KERN_CRIT "inet_proto_init: panic\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Tell SOCKET that we are alive...
+ */
+
+ (void) sock_register(&inet_family_ops);
+
+ /*
+ * Add all the protocols.
+ */
+
+ printk(KERN_INFO "IP Protocols: ");
+ for (p = inet_protocol_base; p != NULL;) {
+ struct inet_protocol *tmp = (struct inet_protocol *) p->next;
+ inet_add_protocol(p);
+ printk("%s%s",p->name,tmp?", ":"\n");
+ p = tmp;
+ }
+
+ /* Register the socket-side information for inet_create. */
+ for(r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
+ INIT_LIST_HEAD(r);
+
+ for(q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
+ inet_register_protosw(q);
+
+ /*
+ * Set the ARP module up
+ */
+
+ arp_init();
+
+ /*
+ * Set the IP module up
+ */
+
+ ip_init();
+
+ tcp_v4_init(&inet_family_ops);
+
+ /* Setup TCP slab cache for open requests. */
+ tcp_init();
+
+
+ /*
+ * Set the ICMP layer up
+ */
+
+ icmp_init(&inet_family_ops);
+
+ /* I wish inet_add_protocol had no constructor hook...
+ I had to move IPIP from net/ipv4/protocol.c :-( --ANK
+ */
+#ifdef CONFIG_NET_IPIP
+ ipip_init();
+#endif
+#ifdef CONFIG_NET_IPGRE
+ ipgre_init();
+#endif
+
+ /*
+ * Initialise the multicast router
+ */
+#if defined(CONFIG_IP_MROUTE)
+ ip_mr_init();
+#endif
+
+ /*
+ * Create all the /proc entries.
+ */
+#ifdef CONFIG_PROC_FS
+ proc_net_create ("raw", 0, raw_get_info);
+ proc_net_create ("netstat", 0, netstat_get_info);
+ proc_net_create ("snmp", 0, snmp_get_info);
+ proc_net_create ("sockstat", 0, afinet_get_info);
+ proc_net_create ("tcp", 0, tcp_get_info);
+ proc_net_create ("udp", 0, udp_get_info);
+#endif /* CONFIG_PROC_FS */
+
+ ipfrag_init();
+
+ return 0;
+}
+module_init(inet_init);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/arp.c b/uClinux-2.4.31-uc0/net/ipv4/arp.c
new file mode 100644
index 0000000..bc05237
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/arp.c
@@ -0,0 +1,1435 @@
+/* linux/net/inet/arp.c
+ *
+ * Version: $Id: arp.c,v 1.99 2001/08/30 22:55:42 davem Exp $
+ *
+ * Copyright (C) 1994 by Florian La Roche
+ *
+ * This module implements the Address Resolution Protocol ARP (RFC 826),
+ * which is used to convert IP addresses (or in the future maybe other
+ * high-level addresses) into a low-level hardware address (like an Ethernet
+ * address).
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Fixes:
+ * Alan Cox : Removed the Ethernet assumptions in
+ * Florian's code
+ * Alan Cox : Fixed some small errors in the ARP
+ * logic
+ * Alan Cox : Allow >4K in /proc
+ * Alan Cox : Make ARP add its own protocol entry
+ * Ross Martin : Rewrote arp_rcv() and arp_get_info()
+ * Stephen Henson : Add AX25 support to arp_get_info()
+ * Alan Cox : Drop data when a device is downed.
+ * Alan Cox : Use init_timer().
+ * Alan Cox : Double lock fixes.
+ * Martin Seine : Move the arphdr structure
+ * to if_arp.h for compatibility.
+ * with BSD based programs.
+ * Andrew Tridgell : Added ARP netmask code and
+ * re-arranged proxy handling.
+ * Alan Cox : Changed to use notifiers.
+ * Niibe Yutaka : Reply for this device or proxies only.
+ * Alan Cox : Don't proxy across hardware types!
+ * Jonathan Naylor : Added support for NET/ROM.
+ * Mike Shaver : RFC1122 checks.
+ * Jonathan Naylor : Only lookup the hardware address for
+ * the correct hardware type.
+ * Germano Caronni : Assorted subtle races.
+ * Craig Schlenter : Don't modify permanent entry
+ * during arp_rcv.
+ * Russ Nelson : Tidied up a few bits.
+ * Alexey Kuznetsov: Major changes to caching and behaviour,
+ * eg intelligent arp probing and
+ * generation
+ * of host down events.
+ * Alan Cox : Missing unlock in device events.
+ * Eckes : ARP ioctl control errors.
+ * Alexey Kuznetsov: Arp free fix.
+ * Manuel Rodriguez: Gratuitous ARP.
+ * Jonathan Layes : Added arpd support through kerneld
+ * message queue (960314)
+ * Mike Shaver : /proc/sys/net/ipv4/arp_* support
+ * Mike McLagan : Routing by source
+ * Stuart Cheshire : Metricom and grat arp fixes
+ * *** FOR 2.1 clean this up ***
+ * Lawrence V. Stefani: (08/12/96) Added FDDI support.
+ * Alan Cox : Took the AP1000 nasty FDDI hack and
+ * folded into the mainstream FDDI code.
+ * Ack spit, Linus how did you allow that
+ * one in...
+ * Jes Sorensen : Make FDDI work again in 2.1.x and
+ * clean up the APFDDI & gen. FDDI bits.
+ * Alexey Kuznetsov: new arp state machine;
+ * now it is in net/core/neighbour.c.
+ * Krzysztof Halasa: Added Frame Relay ARP support.
+ * Shmulik Hen: Split arp_send to arp_create and
+ * arp_xmit so intermediate drivers like
+ * bonding can change the skb before
+ * sending (e.g. insert 8021q tag).
+ * Harald Welte : convert to make use of jenkins hash
+ */
+
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/config.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/errno.h>
+#include <linux/in.h>
+#include <linux/mm.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/fddidevice.h>
+#include <linux/if_arp.h>
+#include <linux/trdevice.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <linux/jhash.h>
+#include <linux/module.h>
+#ifdef CONFIG_SYSCTL
+#include <linux/sysctl.h>
+#endif
+
+#include <net/ip.h>
+#include <net/icmp.h>
+#include <net/route.h>
+#include <net/protocol.h>
+#include <net/tcp.h>
+#include <net/sock.h>
+#include <net/arp.h>
+#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
+#include <net/ax25.h>
+#if defined(CONFIG_NETROM) || defined(CONFIG_NETROM_MODULE)
+#include <net/netrom.h>
+#endif
+#endif
+#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
+#include <net/atmclip.h>
+struct neigh_table *clip_tbl_hook;
+#endif
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+#include <linux/netfilter_arp.h>
+
+/*
+ * Interface to generic neighbour cache.
+ */
+static u32 arp_hash(const void *pkey, const struct net_device *dev);
+static int arp_constructor(struct neighbour *neigh);
+static void arp_solicit(struct neighbour *neigh, struct sk_buff *skb);
+static void arp_error_report(struct neighbour *neigh, struct sk_buff *skb);
+static void parp_redo(struct sk_buff *skb);
+
+static struct neigh_ops arp_generic_ops = {
+ family: AF_INET,
+ solicit: arp_solicit,
+ error_report: arp_error_report,
+ output: neigh_resolve_output,
+ connected_output: neigh_connected_output,
+ hh_output: dev_queue_xmit,
+ queue_xmit: dev_queue_xmit,
+};
+
+static struct neigh_ops arp_hh_ops = {
+ family: AF_INET,
+ solicit: arp_solicit,
+ error_report: arp_error_report,
+ output: neigh_resolve_output,
+ connected_output: neigh_resolve_output,
+ hh_output: dev_queue_xmit,
+ queue_xmit: dev_queue_xmit,
+};
+
+static struct neigh_ops arp_direct_ops = {
+ family: AF_INET,
+ output: dev_queue_xmit,
+ connected_output: dev_queue_xmit,
+ hh_output: dev_queue_xmit,
+ queue_xmit: dev_queue_xmit,
+};
+
+struct neigh_ops arp_broken_ops = {
+ family: AF_INET,
+ solicit: arp_solicit,
+ error_report: arp_error_report,
+ output: neigh_compat_output,
+ connected_output: neigh_compat_output,
+ hh_output: dev_queue_xmit,
+ queue_xmit: dev_queue_xmit,
+};
+
+struct neigh_table arp_tbl = {
+ family: AF_INET,
+ entry_size: sizeof(struct neighbour) + 4,
+ key_len: 4,
+ hash: arp_hash,
+ constructor: arp_constructor,
+ proxy_redo: parp_redo,
+ id: "arp_cache",
+ parms: {
+ tbl: &arp_tbl,
+ base_reachable_time: 30 * HZ,
+ retrans_time: 1 * HZ,
+ gc_staletime: 60 * HZ,
+ reachable_time: 30 * HZ,
+ delay_probe_time: 5 * HZ,
+ queue_len: 3,
+ ucast_probes: 3,
+ mcast_probes: 3,
+ anycast_delay: 1 * HZ,
+ proxy_delay: (8 * HZ) / 10,
+ proxy_qlen: 64,
+ locktime: 1 * HZ,
+ },
+ gc_interval: 30 * HZ,
+ gc_thresh1: 128,
+ gc_thresh2: 512,
+ gc_thresh3: 1024,
+};
+
+int arp_mc_map(u32 addr, u8 *haddr, struct net_device *dev, int dir)
+{
+ switch (dev->type) {
+ case ARPHRD_ETHER:
+ case ARPHRD_FDDI:
+ case ARPHRD_IEEE802:
+ ip_eth_mc_map(addr, haddr);
+ return 0;
+ case ARPHRD_IEEE802_TR:
+ ip_tr_mc_map(addr, haddr);
+ return 0;
+ default:
+ if (dir) {
+ memcpy(haddr, dev->broadcast, dev->addr_len);
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+
+static u32 arp_hash(const void *pkey, const struct net_device *dev)
+{
+ return jhash_2words(*(u32 *)pkey, dev->ifindex, arp_tbl.hash_rnd);
+}
+
+static int arp_constructor(struct neighbour *neigh)
+{
+ u32 addr = *(u32*)neigh->primary_key;
+ struct net_device *dev = neigh->dev;
+ struct in_device *in_dev = in_dev_get(dev);
+
+ if (in_dev == NULL)
+ return -EINVAL;
+
+ neigh->type = inet_addr_type(addr);
+ if (in_dev->arp_parms)
+ neigh->parms = in_dev->arp_parms;
+
+ in_dev_put(in_dev);
+
+ if (dev->hard_header == NULL) {
+ neigh->nud_state = NUD_NOARP;
+ neigh->ops = &arp_direct_ops;
+ neigh->output = neigh->ops->queue_xmit;
+ } else {
+ /* Good devices (checked by reading texts, but only Ethernet is
+ tested)
+
+ ARPHRD_ETHER: (ethernet, apfddi)
+ ARPHRD_FDDI: (fddi)
+ ARPHRD_IEEE802: (tr)
+ ARPHRD_METRICOM: (strip)
+ ARPHRD_ARCNET:
+ etc. etc. etc.
+
+ ARPHRD_IPDDP will also work, if author repairs it.
+ I did not it, because this driver does not work even
+ in old paradigm.
+ */
+
+#if 1
+ /* So... these "amateur" devices are hopeless.
+ The only thing, that I can say now:
+ It is very sad that we need to keep ugly obsolete
+ code to make them happy.
+
+ They should be moved to more reasonable state, now
+ they use rebuild_header INSTEAD OF hard_start_xmit!!!
+ Besides that, they are sort of out of date
+ (a lot of redundant clones/copies, useless in 2.1),
+ I wonder why people believe that they work.
+ */
+ switch (dev->type) {
+ default:
+ break;
+ case ARPHRD_ROSE:
+#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
+ case ARPHRD_AX25:
+#if defined(CONFIG_NETROM) || defined(CONFIG_NETROM_MODULE)
+ case ARPHRD_NETROM:
+#endif
+ neigh->ops = &arp_broken_ops;
+ neigh->output = neigh->ops->output;
+ return 0;
+#endif
+ ;}
+#endif
+ if (neigh->type == RTN_MULTICAST) {
+ neigh->nud_state = NUD_NOARP;
+ arp_mc_map(addr, neigh->ha, dev, 1);
+ } else if (dev->flags&(IFF_NOARP|IFF_LOOPBACK)) {
+ neigh->nud_state = NUD_NOARP;
+ memcpy(neigh->ha, dev->dev_addr, dev->addr_len);
+ } else if (neigh->type == RTN_BROADCAST || dev->flags&IFF_POINTOPOINT) {
+ neigh->nud_state = NUD_NOARP;
+ memcpy(neigh->ha, dev->broadcast, dev->addr_len);
+ }
+ if (dev->hard_header_cache)
+ neigh->ops = &arp_hh_ops;
+ else
+ neigh->ops = &arp_generic_ops;
+ if (neigh->nud_state&NUD_VALID)
+ neigh->output = neigh->ops->connected_output;
+ else
+ neigh->output = neigh->ops->output;
+ }
+ return 0;
+}
+
+static void arp_error_report(struct neighbour *neigh, struct sk_buff *skb)
+{
+ dst_link_failure(skb);
+ kfree_skb(skb);
+}
+
+static void arp_solicit(struct neighbour *neigh, struct sk_buff *skb)
+{
+ u32 saddr = 0;
+ u8 *dst_ha = NULL;
+ struct net_device *dev = neigh->dev;
+ u32 target = *(u32*)neigh->primary_key;
+ int probes = atomic_read(&neigh->probes);
+ struct in_device *in_dev = in_dev_get(dev);
+
+ if (!in_dev)
+ return;
+
+ switch (IN_DEV_ARP_ANNOUNCE(in_dev)) {
+ default:
+ case 0: /* By default announce any local IP */
+ if (skb && inet_addr_type(skb->nh.iph->saddr) == RTN_LOCAL)
+ saddr = skb->nh.iph->saddr;
+ break;
+ case 1: /* Restrict announcements of saddr in same subnet */
+ if (!skb)
+ break;
+ saddr = skb->nh.iph->saddr;
+ if (inet_addr_type(saddr) == RTN_LOCAL) {
+ /* saddr should be known to target */
+ if (inet_addr_onlink(in_dev, target, saddr))
+ break;
+ }
+ saddr = 0;
+ break;
+ case 2: /* Avoid secondary IPs, get a primary/preferred one */
+ break;
+ }
+
+ if (in_dev)
+ in_dev_put(in_dev);
+ if (!saddr)
+ saddr = inet_select_addr(dev, target, RT_SCOPE_LINK);
+
+ read_lock_bh(&neigh->lock);
+ if ((probes -= neigh->parms->ucast_probes) < 0) {
+ if (!(neigh->nud_state&NUD_VALID))
+ printk(KERN_DEBUG "trying to ucast probe in NUD_INVALID\n");
+ dst_ha = neigh->ha;
+ } else if ((probes -= neigh->parms->app_probes) < 0) {
+ read_unlock_bh(&neigh->lock);
+#ifdef CONFIG_ARPD
+ neigh_app_ns(neigh);
+#endif
+ return;
+ }
+
+ arp_send(ARPOP_REQUEST, ETH_P_ARP, target, dev, saddr,
+ dst_ha, dev->dev_addr, NULL);
+ read_unlock_bh(&neigh->lock);
+}
+
+static int arp_ignore(struct in_device *in_dev, struct net_device *dev,
+ u32 sip, u32 tip)
+{
+ int scope;
+
+ switch (IN_DEV_ARP_IGNORE(in_dev)) {
+ case 0: /* Reply, the tip is already validated */
+ return 0;
+ case 1: /* Reply only if tip is configured on the incoming interface */
+ sip = 0;
+ scope = RT_SCOPE_HOST;
+ break;
+ case 2: /*
+ * Reply only if tip is configured on the incoming interface
+ * and is in same subnet as sip
+ */
+ scope = RT_SCOPE_HOST;
+ break;
+ case 3: /* Do not reply for scope host addresses */
+ sip = 0;
+ scope = RT_SCOPE_LINK;
+ dev = NULL;
+ break;
+ case 4: /* Reserved */
+ case 5:
+ case 6:
+ case 7:
+ return 0;
+ case 8: /* Do not reply */
+ return 1;
+ default:
+ return 0;
+ }
+ return !inet_confirm_addr(dev, sip, tip, scope);
+}
+
+static int arp_filter(__u32 sip, __u32 tip, struct net_device *dev)
+{
+ struct rtable *rt;
+ int flag = 0;
+ /*unsigned long now; */
+
+ if (ip_route_output(&rt, sip, tip, 0, 0) < 0)
+ return 1;
+ if (rt->u.dst.dev != dev) {
+ NET_INC_STATS_BH(ArpFilter);
+ flag = 1;
+ }
+ ip_rt_put(rt);
+ return flag;
+}
+
+/* OBSOLETE FUNCTIONS */
+
+/*
+ * Find an arp mapping in the cache. If not found, post a request.
+ *
+ * It is very UGLY routine: it DOES NOT use skb->dst->neighbour,
+ * even if it exists. It is supposed that skb->dev was mangled
+ * by a virtual device (eql, shaper). Nobody but broken devices
+ * is allowed to use this function, it is scheduled to be removed. --ANK
+ */
+
+static int arp_set_predefined(int addr_hint, unsigned char * haddr, u32 paddr, struct net_device * dev)
+{
+ switch (addr_hint) {
+ case RTN_LOCAL:
+ printk(KERN_DEBUG "ARP: arp called for own IP address\n");
+ memcpy(haddr, dev->dev_addr, dev->addr_len);
+ return 1;
+ case RTN_MULTICAST:
+ arp_mc_map(paddr, haddr, dev, 1);
+ return 1;
+ case RTN_BROADCAST:
+ memcpy(haddr, dev->broadcast, dev->addr_len);
+ return 1;
+ }
+ return 0;
+}
+
+
+int arp_find(unsigned char *haddr, struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+ u32 paddr;
+ struct neighbour *n;
+ int free_skb = 0, ret = 0;
+
+ if (!skb->dst) {
+ printk(KERN_DEBUG "arp_find is called with dst==NULL\n");
+ kfree_skb(skb);
+ return 1;
+ }
+
+ paddr = ((struct rtable*)skb->dst)->rt_gateway;
+
+ if (arp_set_predefined(inet_addr_type(paddr), haddr, paddr, dev))
+ return 0;
+
+ n = __neigh_lookup(&arp_tbl, &paddr, dev, 1);
+
+ if (n) {
+ int copy = 0;
+ write_lock_bh(&n->lock);
+ n->used = jiffies;
+ if (n->nud_state&NUD_VALID) {
+ copy = 1;
+ ret = 0;
+ free_skb = 0;
+ } else {
+ int ne_ret = __neigh_event_send(n, skb);
+ if (ne_ret == 0) {
+ copy = 1;
+ ret = 0;
+ } else if (ne_ret < 0) {
+ free_skb = 1;
+ ret = 1;
+ } else {
+ ret = 1;
+ }
+ }
+ if (copy)
+ memcpy(haddr, n->ha, dev->addr_len);
+ write_unlock_bh(&n->lock);
+ neigh_release(n);
+ } else
+ ret = 1;
+ if (free_skb)
+ kfree_skb(skb);
+ return ret;
+}
+
+/* END OF OBSOLETE FUNCTIONS */
+
+int arp_bind_neighbour(struct dst_entry *dst)
+{
+ struct net_device *dev = dst->dev;
+ struct neighbour *n = dst->neighbour;
+
+ if (dev == NULL)
+ return -EINVAL;
+ if (n == NULL) {
+ u32 nexthop = ((struct rtable*)dst)->rt_gateway;
+ if (dev->flags&(IFF_LOOPBACK|IFF_POINTOPOINT))
+ nexthop = 0;
+ n = __neigh_lookup_errno(
+#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
+ dev->type == ARPHRD_ATM ? clip_tbl_hook :
+#endif
+ &arp_tbl, &nexthop, dev);
+ if (IS_ERR(n))
+ return PTR_ERR(n);
+ dst->neighbour = n;
+ }
+ return 0;
+}
+
+/*
+ * Check if we can use proxy ARP for this path
+ */
+
+static inline int arp_fwd_proxy(struct in_device *in_dev, struct rtable *rt)
+{
+ struct in_device *out_dev;
+ int imi, omi = -1;
+
+ if (!IN_DEV_PROXY_ARP(in_dev))
+ return 0;
+
+ if ((imi = IN_DEV_MEDIUM_ID(in_dev)) == 0)
+ return 1;
+ if (imi == -1)
+ return 0;
+
+ /* place to check for proxy_arp for routes */
+
+ if ((out_dev = in_dev_get(rt->u.dst.dev)) != NULL) {
+ omi = IN_DEV_MEDIUM_ID(out_dev);
+ in_dev_put(out_dev);
+ }
+ return (omi != imi && omi != -1);
+}
+
+/*
+ * Interface to link layer: send routine and receive handler.
+ */
+
+/*
+ * Create an arp packet. If (dest_hw == NULL), we create a broadcast
+ * message.
+ */
+struct sk_buff *arp_create(int type, int ptype, u32 dest_ip,
+ struct net_device *dev, u32 src_ip,
+ unsigned char *dest_hw, unsigned char *src_hw,
+ unsigned char *target_hw)
+{
+ struct sk_buff *skb;
+ struct arphdr *arp;
+ unsigned char *arp_ptr;
+
+ /*
+ * Allocate a buffer
+ */
+
+ skb = alloc_skb(sizeof(struct arphdr)+ 2*(dev->addr_len+4)
+ + dev->hard_header_len + 15, GFP_ATOMIC);
+ if (skb == NULL)
+ return NULL;
+
+ skb_reserve(skb, (dev->hard_header_len+15)&~15);
+ skb->nh.raw = skb->data;
+ arp = (struct arphdr *) skb_put(skb,sizeof(struct arphdr) + 2*(dev->addr_len+4));
+ skb->dev = dev;
+ skb->protocol = htons (ETH_P_ARP);
+ if (src_hw == NULL)
+ src_hw = dev->dev_addr;
+ if (dest_hw == NULL)
+ dest_hw = dev->broadcast;
+
+ /*
+ * Fill the device header for the ARP frame
+ */
+ if (dev->hard_header &&
+ dev->hard_header(skb,dev,ptype,dest_hw,src_hw,skb->len) < 0)
+ goto out;
+
+ /*
+ * Fill out the arp protocol part.
+ *
+ * The arp hardware type should match the device type, except for FDDI,
+ * which (according to RFC 1390) should always equal 1 (Ethernet).
+ */
+ /*
+ * Exceptions everywhere. AX.25 uses the AX.25 PID value not the
+ * DIX code for the protocol. Make these device structure fields.
+ */
+ switch (dev->type) {
+ default:
+ arp->ar_hrd = htons(dev->type);
+ arp->ar_pro = htons(ETH_P_IP);
+ break;
+
+#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
+ case ARPHRD_AX25:
+ arp->ar_hrd = htons(ARPHRD_AX25);
+ arp->ar_pro = htons(AX25_P_IP);
+ break;
+
+#if defined(CONFIG_NETROM) || defined(CONFIG_NETROM_MODULE)
+ case ARPHRD_NETROM:
+ arp->ar_hrd = htons(ARPHRD_NETROM);
+ arp->ar_pro = htons(AX25_P_IP);
+ break;
+#endif
+#endif
+
+#ifdef CONFIG_FDDI
+ case ARPHRD_FDDI:
+ arp->ar_hrd = htons(ARPHRD_ETHER);
+ arp->ar_pro = htons(ETH_P_IP);
+ break;
+#endif
+#ifdef CONFIG_TR
+ case ARPHRD_IEEE802_TR:
+ arp->ar_hrd = htons(ARPHRD_IEEE802);
+ arp->ar_pro = htons(ETH_P_IP);
+ break;
+#endif
+ }
+
+ arp->ar_hln = dev->addr_len;
+ arp->ar_pln = 4;
+ arp->ar_op = htons(type);
+
+ arp_ptr=(unsigned char *)(arp+1);
+
+ memcpy(arp_ptr, src_hw, dev->addr_len);
+ arp_ptr+=dev->addr_len;
+ memcpy(arp_ptr, &src_ip,4);
+ arp_ptr+=4;
+ if (target_hw != NULL)
+ memcpy(arp_ptr, target_hw, dev->addr_len);
+ else
+ memset(arp_ptr, 0, dev->addr_len);
+ arp_ptr+=dev->addr_len;
+ memcpy(arp_ptr, &dest_ip, 4);
+
+ return skb;
+
+out:
+ kfree_skb(skb);
+ return NULL;
+}
+
+/*
+ * Send an arp packet.
+ */
+void arp_xmit(struct sk_buff *skb)
+{
+ /* Send it off, maybe filter it using firewalling first. */
+ NF_HOOK(NF_ARP, NF_ARP_OUT, skb, NULL, skb->dev, dev_queue_xmit);
+}
+
+/*
+ * Create and send an arp packet.
+ */
+void arp_send(int type, int ptype, u32 dest_ip,
+ struct net_device *dev, u32 src_ip,
+ unsigned char *dest_hw, unsigned char *src_hw,
+ unsigned char *target_hw)
+{
+ struct sk_buff *skb;
+
+ /*
+ * No arp on this interface.
+ */
+
+ if (dev->flags&IFF_NOARP)
+ return;
+
+ skb = arp_create(type, ptype, dest_ip, dev, src_ip,
+ dest_hw, src_hw, target_hw);
+ if (skb == NULL) {
+ return;
+ }
+
+ arp_xmit(skb);
+}
+
+static void parp_redo(struct sk_buff *skb)
+{
+ arp_rcv(skb, skb->dev, NULL);
+}
+
+/*
+ * Process an arp request.
+ */
+
+int arp_process(struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+ struct in_device *in_dev = in_dev_get(dev);
+ struct arphdr *arp;
+ unsigned char *arp_ptr;
+ struct rtable *rt;
+ unsigned char *sha, *tha;
+ u32 sip, tip;
+ u16 dev_type = dev->type;
+ int addr_type;
+ struct neighbour *n;
+
+ /* arp_rcv below verifies the ARP header, verifies the device
+ * is ARP'able, and linearizes the SKB (if needed).
+ */
+
+ if (in_dev == NULL)
+ goto out;
+
+ arp = skb->nh.arph;
+ arp_ptr= (unsigned char *)(arp+1);
+
+ switch (dev_type) {
+ default:
+ if (arp->ar_pro != htons(ETH_P_IP))
+ goto out;
+ if (htons(dev_type) != arp->ar_hrd)
+ goto out;
+ break;
+#ifdef CONFIG_NET_ETHERNET
+ case ARPHRD_ETHER:
+ /*
+ * ETHERNET devices will accept ARP hardware types of either
+ * 1 (Ethernet) or 6 (IEEE 802.2).
+ */
+ if (arp->ar_hrd != htons(ARPHRD_ETHER) &&
+ arp->ar_hrd != htons(ARPHRD_IEEE802))
+ goto out;
+ if (arp->ar_pro != htons(ETH_P_IP))
+ goto out;
+ break;
+#endif
+#ifdef CONFIG_TR
+ case ARPHRD_IEEE802_TR:
+ /*
+ * Token ring devices will accept ARP hardware types of either
+ * 1 (Ethernet) or 6 (IEEE 802.2).
+ */
+ if (arp->ar_hrd != htons(ARPHRD_ETHER) &&
+ arp->ar_hrd != htons(ARPHRD_IEEE802))
+ goto out;
+ if (arp->ar_pro != htons(ETH_P_IP))
+ goto out;
+ break;
+#endif
+#ifdef CONFIG_FDDI
+ case ARPHRD_FDDI:
+ /*
+ * According to RFC 1390, FDDI devices should accept ARP hardware types
+ * of 1 (Ethernet). However, to be more robust, we'll accept hardware
+ * types of either 1 (Ethernet) or 6 (IEEE 802.2).
+ */
+ if (arp->ar_hrd != htons(ARPHRD_ETHER) &&
+ arp->ar_hrd != htons(ARPHRD_IEEE802))
+ goto out;
+ if (arp->ar_pro != htons(ETH_P_IP))
+ goto out;
+ break;
+#endif
+#ifdef CONFIG_NET_FC
+ case ARPHRD_IEEE802:
+ /*
+ * According to RFC 2625, Fibre Channel devices (which are IEEE
+ * 802 devices) should accept ARP hardware types of 6 (IEEE 802)
+ * and 1 (Ethernet).
+ */
+ if (arp->ar_hrd != htons(ARPHRD_ETHER) &&
+ arp->ar_hrd != htons(ARPHRD_IEEE802))
+ goto out;
+ if (arp->ar_pro != htons(ETH_P_IP))
+ goto out;
+ break;
+#endif
+#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
+ case ARPHRD_AX25:
+ if (arp->ar_pro != htons(AX25_P_IP))
+ goto out;
+ if (arp->ar_hrd != htons(ARPHRD_AX25))
+ goto out;
+ break;
+#if defined(CONFIG_NETROM) || defined(CONFIG_NETROM_MODULE)
+ case ARPHRD_NETROM:
+ if (arp->ar_pro != htons(AX25_P_IP))
+ goto out;
+ if (arp->ar_hrd != htons(ARPHRD_NETROM))
+ goto out;
+ break;
+#endif
+#endif
+ }
+
+ /* Understand only these message types */
+
+ if (arp->ar_op != htons(ARPOP_REPLY) &&
+ arp->ar_op != htons(ARPOP_REQUEST))
+ goto out;
+
+/*
+ * Extract fields
+ */
+ sha=arp_ptr;
+ arp_ptr += dev->addr_len;
+ memcpy(&sip, arp_ptr, 4);
+ arp_ptr += 4;
+ tha=arp_ptr;
+ arp_ptr += dev->addr_len;
+ memcpy(&tip, arp_ptr, 4);
+/*
+ * Check for bad requests for 127.x.x.x and requests for multicast
+ * addresses. If this is one such, delete it.
+ */
+ if (LOOPBACK(tip) || MULTICAST(tip))
+ goto out;
+
+/*
+ * Special case: We must set Frame Relay source Q.922 address
+ */
+ if (dev_type == ARPHRD_DLCI)
+ sha = dev->broadcast;
+
+/*
+ * Process entry. The idea here is we want to send a reply if it is a
+ * request for us or if it is a request for someone else that we hold
+ * a proxy for. We want to add an entry to our cache if it is a reply
+ * to us or if it is a request for our address.
+ * (The assumption for this last is that if someone is requesting our
+ * address, they are probably intending to talk to us, so it saves time
+ * if we cache their address. Their address is also probably not in
+ * our cache, since ours is not in their cache.)
+ *
+ * Putting this another way, we only care about replies if they are to
+ * us, in which case we add them to the cache. For requests, we care
+ * about those for us and those for our proxies. We reply to both,
+ * and in the case of requests for us we add the requester to the arp
+ * cache.
+ */
+
+ /* Special case: IPv4 duplicate address detection packet (RFC2131) */
+ if (sip == 0) {
+ if (arp->ar_op == htons(ARPOP_REQUEST) &&
+ inet_addr_type(tip) == RTN_LOCAL &&
+ !arp_ignore(in_dev,dev,sip,tip))
+ arp_send(ARPOP_REPLY,ETH_P_ARP,tip,dev,tip,sha,dev->dev_addr,dev->dev_addr);
+ goto out;
+ }
+
+ if (arp->ar_op == htons(ARPOP_REQUEST) &&
+ ip_route_input(skb, tip, sip, 0, dev) == 0) {
+
+ rt = (struct rtable*)skb->dst;
+ addr_type = rt->rt_type;
+
+ if (addr_type == RTN_LOCAL) {
+ n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
+ if (n) {
+ int dont_send = 0;
+
+ if (!dont_send)
+ dont_send |= arp_ignore(in_dev,dev,sip,tip);
+ if (!dont_send && IN_DEV_ARPFILTER(in_dev))
+ dont_send |= arp_filter(sip,tip,dev);
+ if (!dont_send)
+ arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
+
+ neigh_release(n);
+ }
+ goto out;
+ } else if (IN_DEV_FORWARD(in_dev)) {
+ if ((rt->rt_flags&RTCF_DNAT) ||
+ (addr_type == RTN_UNICAST && rt->u.dst.dev != dev &&
+ (arp_fwd_proxy(in_dev, rt) || pneigh_lookup(&arp_tbl, &tip, dev, 0)))) {
+ n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
+ if (n)
+ neigh_release(n);
+
+ if (skb->stamp.tv_sec == 0 ||
+ skb->pkt_type == PACKET_HOST ||
+ in_dev->arp_parms->proxy_delay == 0) {
+ arp_send(ARPOP_REPLY,ETH_P_ARP,sip,dev,tip,sha,dev->dev_addr,sha);
+ } else {
+ pneigh_enqueue(&arp_tbl, in_dev->arp_parms, skb);
+ in_dev_put(in_dev);
+ return 0;
+ }
+ goto out;
+ }
+ }
+ }
+
+ /* Update our ARP tables */
+
+ n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
+
+#ifdef CONFIG_IP_ACCEPT_UNSOLICITED_ARP
+ /* Unsolicited ARP is not accepted by default.
+ It is possible, that this option should be enabled for some
+ devices (strip is candidate)
+ */
+ if (n == NULL &&
+ arp->ar_op == htons(ARPOP_REPLY) &&
+ inet_addr_type(sip) == RTN_UNICAST)
+ n = __neigh_lookup(&arp_tbl, &sip, dev, -1);
+#endif
+
+ if (n) {
+ int state = NUD_REACHABLE;
+ int override = 0;
+
+ /* If several different ARP replies follows back-to-back,
+ use the FIRST one. It is possible, if several proxy
+ agents are active. Taking the first reply prevents
+ arp trashing and chooses the fastest router.
+ */
+ if (jiffies - n->updated >= n->parms->locktime)
+ override = 1;
+
+ /* Broadcast replies and request packets
+ do not assert neighbour reachability.
+ */
+ if (arp->ar_op != htons(ARPOP_REPLY) ||
+ skb->pkt_type != PACKET_HOST)
+ state = NUD_STALE;
+#ifdef CONFIG_NET_ARP_LIMIT
+ if ((arp_tbl.entries > CONFIG_ARP_LIMIT) && !(n->nud_state & NUD_PERMANENT)) {
+ printk(KERN_ERR "***** ARP limit reached ****\n");
+ printk(KERN_ERR "* ignoring %u.%u.%u.%u *\n",
+ NIPQUAD(*(u32*)n->primary_key));
+ state = NUD_FAILED;
+ override = 1;
+ } else
+ state |= NUD_PERMANENT;
+#endif
+ neigh_update(n, sha, state, override, 1);
+ neigh_release(n);
+ }
+
+out:
+ if (in_dev)
+ in_dev_put(in_dev);
+ kfree_skb(skb);
+ return 0;
+}
+
+
+/*
+ * Receive an arp request from the device layer.
+ */
+
+int arp_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
+{
+ struct arphdr *arp;
+
+ /* ARP header, plus 2 device addresses, plus 2 IP addresses. */
+ if (!pskb_may_pull(skb, (sizeof(struct arphdr) +
+ (2 * dev->addr_len) +
+ (2 * sizeof(u32)))))
+ goto freeskb;
+
+ arp = skb->nh.arph;
+ if (arp->ar_hln != dev->addr_len ||
+ dev->flags & IFF_NOARP ||
+ skb->pkt_type == PACKET_OTHERHOST ||
+ skb->pkt_type == PACKET_LOOPBACK ||
+ arp->ar_pln != 4)
+ goto freeskb;
+
+ if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
+ goto out_of_mem;
+
+ return NF_HOOK(NF_ARP, NF_ARP_IN, skb, dev, NULL, arp_process);
+
+freeskb:
+ kfree_skb(skb);
+out_of_mem:
+ return 0;
+}
+
+/*
+ * User level interface (ioctl, /proc)
+ */
+
+/*
+ * Set (create) an ARP cache entry.
+ */
+
+int arp_req_set(struct arpreq *r, struct net_device * dev)
+{
+ u32 ip = ((struct sockaddr_in *) &r->arp_pa)->sin_addr.s_addr;
+ struct neighbour *neigh;
+ int err;
+
+ if (r->arp_flags&ATF_PUBL) {
+ u32 mask = ((struct sockaddr_in *) &r->arp_netmask)->sin_addr.s_addr;
+ if (mask && mask != 0xFFFFFFFF)
+ return -EINVAL;
+ if (!dev && (r->arp_flags & ATF_COM)) {
+ dev = dev_getbyhwaddr(r->arp_ha.sa_family, r->arp_ha.sa_data);
+ if (!dev)
+ return -ENODEV;
+ }
+ if (mask) {
+ if (pneigh_lookup(&arp_tbl, &ip, dev, 1) == NULL)
+ return -ENOBUFS;
+ return 0;
+ }
+ if (dev == NULL) {
+ ipv4_devconf.proxy_arp = 1;
+ return 0;
+ }
+ if (__in_dev_get(dev)) {
+ __in_dev_get(dev)->cnf.proxy_arp = 1;
+ return 0;
+ }
+ return -ENXIO;
+ }
+
+ if (r->arp_flags & ATF_PERM)
+ r->arp_flags |= ATF_COM;
+ if (dev == NULL) {
+ struct rtable * rt;
+ if ((err = ip_route_output(&rt, ip, 0, RTO_ONLINK, 0)) != 0)
+ return err;
+ dev = rt->u.dst.dev;
+ ip_rt_put(rt);
+ if (!dev)
+ return -EINVAL;
+ }
+ switch (dev->type) {
+#ifdef CONFIG_FDDI
+ case ARPHRD_FDDI:
+ /*
+ * According to RFC 1390, FDDI devices should accept ARP
+ * hardware types of 1 (Ethernet). However, to be more
+ * robust, we'll accept hardware types of either 1 (Ethernet)
+ * or 6 (IEEE 802.2).
+ */
+ if (r->arp_ha.sa_family != ARPHRD_FDDI &&
+ r->arp_ha.sa_family != ARPHRD_ETHER &&
+ r->arp_ha.sa_family != ARPHRD_IEEE802)
+ return -EINVAL;
+ break;
+#endif
+ default:
+ if (r->arp_ha.sa_family != dev->type)
+ return -EINVAL;
+ break;
+ }
+
+ neigh = __neigh_lookup_errno(&arp_tbl, &ip, dev);
+ err = PTR_ERR(neigh);
+ if (!IS_ERR(neigh)) {
+ unsigned state = NUD_STALE;
+ if (r->arp_flags & ATF_PERM)
+ state = NUD_PERMANENT;
+ err = neigh_update(neigh, (r->arp_flags&ATF_COM) ?
+ r->arp_ha.sa_data : NULL, state, 1, 0);
+ neigh_release(neigh);
+ }
+ return err;
+}
+
+static unsigned arp_state_to_flags(struct neighbour *neigh)
+{
+ unsigned flags = 0;
+ if (neigh->nud_state&NUD_PERMANENT)
+ flags = ATF_PERM|ATF_COM;
+ else if (neigh->nud_state&NUD_VALID)
+ flags = ATF_COM;
+ return flags;
+}
+
+/*
+ * Get an ARP cache entry.
+ */
+
+static int arp_req_get(struct arpreq *r, struct net_device *dev)
+{
+ u32 ip = ((struct sockaddr_in *) &r->arp_pa)->sin_addr.s_addr;
+ struct neighbour *neigh;
+ int err = -ENXIO;
+
+ neigh = neigh_lookup(&arp_tbl, &ip, dev);
+ if (neigh) {
+ read_lock_bh(&neigh->lock);
+ memcpy(r->arp_ha.sa_data, neigh->ha, dev->addr_len);
+ r->arp_flags = arp_state_to_flags(neigh);
+ read_unlock_bh(&neigh->lock);
+ r->arp_ha.sa_family = dev->type;
+ strncpy(r->arp_dev, dev->name, sizeof(r->arp_dev));
+ neigh_release(neigh);
+ err = 0;
+ }
+ return err;
+}
+
+int arp_req_delete(struct arpreq *r, struct net_device * dev)
+{
+ int err;
+ u32 ip = ((struct sockaddr_in *)&r->arp_pa)->sin_addr.s_addr;
+ struct neighbour *neigh;
+
+ if (r->arp_flags & ATF_PUBL) {
+ u32 mask = ((struct sockaddr_in *) &r->arp_netmask)->sin_addr.s_addr;
+ if (mask == 0xFFFFFFFF)
+ return pneigh_delete(&arp_tbl, &ip, dev);
+ if (mask == 0) {
+ if (dev == NULL) {
+ ipv4_devconf.proxy_arp = 0;
+ return 0;
+ }
+ if (__in_dev_get(dev)) {
+ __in_dev_get(dev)->cnf.proxy_arp = 0;
+ return 0;
+ }
+ return -ENXIO;
+ }
+ return -EINVAL;
+ }
+
+ if (dev == NULL) {
+ struct rtable * rt;
+ if ((err = ip_route_output(&rt, ip, 0, RTO_ONLINK, 0)) != 0)
+ return err;
+ dev = rt->u.dst.dev;
+ ip_rt_put(rt);
+ if (!dev)
+ return -EINVAL;
+ }
+ err = -ENXIO;
+ neigh = neigh_lookup(&arp_tbl, &ip, dev);
+ if (neigh) {
+ if (neigh->nud_state&~NUD_NOARP)
+ err = neigh_update(neigh, NULL, NUD_FAILED, 1, 0);
+ neigh_release(neigh);
+ }
+ return err;
+}
+
+/*
+ * Handle an ARP layer I/O control request.
+ */
+
+int arp_ioctl(unsigned int cmd, void *arg)
+{
+ int err;
+ struct arpreq r;
+ struct net_device * dev = NULL;
+
+ switch(cmd) {
+ case SIOCDARP:
+ case SIOCSARP:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ case SIOCGARP:
+ err = copy_from_user(&r, arg, sizeof(struct arpreq));
+ if (err)
+ return -EFAULT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (r.arp_pa.sa_family != AF_INET)
+ return -EPFNOSUPPORT;
+
+ if (!(r.arp_flags & ATF_PUBL) &&
+ (r.arp_flags & (ATF_NETMASK|ATF_DONTPUB)))
+ return -EINVAL;
+ if (!(r.arp_flags & ATF_NETMASK))
+ ((struct sockaddr_in *)&r.arp_netmask)->sin_addr.s_addr=htonl(0xFFFFFFFFUL);
+
+ rtnl_lock();
+ if (r.arp_dev[0]) {
+ err = -ENODEV;
+ if ((dev = __dev_get_by_name(r.arp_dev)) == NULL)
+ goto out;
+
+ /* Mmmm... It is wrong... ARPHRD_NETROM==0 */
+ if (!r.arp_ha.sa_family)
+ r.arp_ha.sa_family = dev->type;
+ err = -EINVAL;
+ if ((r.arp_flags & ATF_COM) && r.arp_ha.sa_family != dev->type)
+ goto out;
+ } else if (cmd == SIOCGARP) {
+ err = -ENODEV;
+ goto out;
+ }
+
+ switch(cmd) {
+ case SIOCDARP:
+ err = arp_req_delete(&r, dev);
+ break;
+ case SIOCSARP:
+ err = arp_req_set(&r, dev);
+ break;
+ case SIOCGARP:
+ err = arp_req_get(&r, dev);
+ if (!err && copy_to_user(arg, &r, sizeof(r)))
+ err = -EFAULT;
+ break;
+ }
+out:
+ rtnl_unlock();
+ return err;
+}
+
+#ifdef CONFIG_PROC_FS
+#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
+
+/* ------------------------------------------------------------------------ */
+/*
+ * ax25 -> ASCII conversion
+ */
+static char *ax2asc2(ax25_address *a, char *buf)
+{
+ char c, *s;
+ int n;
+
+ for (n = 0, s = buf; n < 6; n++) {
+ c = (a->ax25_call[n] >> 1) & 0x7F;
+
+ if (c != ' ') *s++ = c;
+ }
+
+ *s++ = '-';
+
+ if ((n = ((a->ax25_call[6] >> 1) & 0x0F)) > 9) {
+ *s++ = '1';
+ n -= 10;
+ }
+
+ *s++ = n + '0';
+ *s++ = '\0';
+
+ if (*buf == '\0' || *buf == '-')
+ return "*";
+
+ return buf;
+
+}
+#endif /* CONFIG_AX25 */
+
+#define HBUFFERLEN 30
+
+static void arp_format_neigh_entry(struct seq_file *seq,
+ struct neighbour *n)
+{
+ char hbuffer[HBUFFERLEN];
+ const char hexbuf[] = "0123456789ABCDEF";
+ int k, j;
+ char tbuf[16];
+ struct net_device *dev = n->dev;
+ int hatype = dev->type;
+
+ read_lock(&n->lock);
+
+ /* Convert hardware address to XX:XX:XX:XX ... form. */
+#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
+ if (hatype == ARPHRD_AX25 || hatype == ARPHRD_NETROM)
+ ax2asc2((ax25_address *)n->ha, hbuffer);
+ else {
+#endif
+ for (k=0,j=0;k<HBUFFERLEN-3 && j<dev->addr_len;j++) {
+ hbuffer[k++]=hexbuf[(n->ha[j]>>4)&15 ];
+ hbuffer[k++]=hexbuf[n->ha[j]&15 ];
+ hbuffer[k++]=':';
+ }
+ hbuffer[--k]=0;
+#if defined(CONFIG_AX25) || defined(CONFIG_AX25_MODULE)
+ }
+#endif
+ sprintf(tbuf, "%u.%u.%u.%u", NIPQUAD(*(u32*)n->primary_key));
+ seq_printf(seq, "%-16s 0x%-10x0x%-10x%s * %s\n",
+ tbuf, hatype, arp_state_to_flags(n), hbuffer, dev->name);
+ read_unlock(&n->lock);
+}
+
+static void arp_format_pneigh_entry(struct seq_file *seq,
+ struct pneigh_entry *n)
+{
+ struct net_device *dev = n->dev;
+ int hatype = dev ? dev->type : 0;
+ char tbuf[16];
+
+ sprintf(tbuf, "%u.%u.%u.%u", NIPQUAD(*(u32*)n->key));
+ seq_printf(seq, "%-16s 0x%-10x0x%-10x%s * %s\n",
+ tbuf, hatype, ATF_PUBL | ATF_PERM, "00:00:00:00:00:00",
+ dev ? dev->name : "*");
+}
+
+static int arp_seq_show(struct seq_file *seq, void *v)
+{
+ if (v == SEQ_START_TOKEN) {
+ seq_puts(seq, "IP address HW type Flags "
+ "HW address Mask Device\n");
+ } else {
+ struct neigh_seq_state *state = seq->private;
+
+ if (state->flags & NEIGH_SEQ_IS_PNEIGH)
+ arp_format_pneigh_entry(seq, v);
+ else
+ arp_format_neigh_entry(seq, v);
+ }
+
+ return 0;
+}
+
+static void *arp_seq_start(struct seq_file *seq, loff_t *pos)
+{
+ /* Don't want to confuse "arp -a" w/ magic entries,
+ * so we tell the generic iterator to skip NUD_NOARP.
+ */
+ return neigh_seq_start(seq, pos, &arp_tbl, NEIGH_SEQ_SKIP_NOARP);
+}
+
+/* ------------------------------------------------------------------------ */
+
+static struct seq_operations arp_seq_ops = {
+ .start = arp_seq_start,
+ .next = neigh_seq_next,
+ .stop = neigh_seq_stop,
+ .show = arp_seq_show,
+};
+
+static int arp_seq_open(struct inode *inode, struct file *file)
+{
+ struct seq_file *seq;
+ int rc = -ENOMEM;
+ struct neigh_seq_state *s = kmalloc(sizeof(*s), GFP_KERNEL);
+
+ if (!s)
+ goto out;
+
+ memset(s, 0, sizeof(*s));
+ rc = seq_open(file, &arp_seq_ops);
+ if (rc)
+ goto out_kfree;
+
+ seq = file->private_data;
+ seq->private = s;
+out:
+ return rc;
+out_kfree:
+ kfree(s);
+ goto out;
+}
+
+static struct file_operations arp_seq_fops = {
+ .owner = THIS_MODULE,
+ .open = arp_seq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release_private,
+};
+#endif /* CONFIG_PROC_FS */
+
+static int arp_netdev_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct net_device *dev = ptr;
+
+ switch (event) {
+ case NETDEV_CHANGEADDR:
+ neigh_changeaddr(&arp_tbl, dev);
+ rt_cache_flush(0);
+ break;
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+struct notifier_block arp_netdev_notifier = {
+ .notifier_call = arp_netdev_event,
+};
+
+/* Note, that it is not on notifier chain.
+ It is necessary, that this routine was called after route cache will be
+ flushed.
+ */
+void arp_ifdown(struct net_device *dev)
+{
+ neigh_ifdown(&arp_tbl, dev);
+}
+
+
+/*
+ * Called once on startup.
+ */
+
+static struct packet_type arp_packet_type = {
+ type: __constant_htons(ETH_P_ARP),
+ func: arp_rcv,
+ data: (void*) 1, /* understand shared skbs */
+};
+
+void __init arp_init (void)
+{
+ neigh_table_init(&arp_tbl);
+
+ dev_add_pack(&arp_packet_type);
+
+#ifdef CONFIG_PROC_FS
+ if (!proc_net_fops_create("arp", S_IRUGO, &arp_seq_fops))
+ panic("unable to create arp proc entry");
+#endif
+#ifdef CONFIG_SYSCTL
+ neigh_sysctl_register(NULL, &arp_tbl.parms, NET_IPV4, NET_IPV4_NEIGH, "ipv4");
+#endif
+ register_netdevice_notifier(&arp_netdev_notifier);
+}
+
+
diff --git a/uClinux-2.4.31-uc0/net/ipv4/devinet.c b/uClinux-2.4.31-uc0/net/ipv4/devinet.c
new file mode 100644
index 0000000..e91c2fc
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/devinet.c
@@ -0,0 +1,1278 @@
+/*
+ * NET3 IP device support routines.
+ *
+ * Version: $Id: devinet.c,v 1.44 2001/10/31 21:55:54 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Derived from the IP parts of dev.c 1.0.19
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Mark Evans, <evansmp@uhura.aston.ac.uk>
+ *
+ * Additional Authors:
+ * Alan Cox, <gw4pts@gw4pts.ampr.org>
+ * Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Changes:
+ * Alexey Kuznetsov: pa_* fields are replaced with ifaddr lists.
+ * Cyrus Durgin: updated for kmod
+ * Matthias Andree: in devinet_ioctl, compare label and
+ * address (4.4BSD alias style support),
+ * fall back to comparing just the label
+ * if no match found.
+ */
+
+#include <linux/config.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/init.h>
+#include <linux/notifier.h>
+#include <linux/inetdevice.h>
+#include <linux/igmp.h>
+#ifdef CONFIG_SYSCTL
+#include <linux/sysctl.h>
+#endif
+#include <linux/kmod.h>
+
+#include <net/ip.h>
+#include <net/route.h>
+#include <net/ip_fib.h>
+
+struct ipv4_devconf ipv4_devconf = { 1, 1, 1, 1, 0, };
+static struct ipv4_devconf ipv4_devconf_dflt = { 1, 1, 1, 1, 1, };
+
+static void rtmsg_ifa(int event, struct in_ifaddr *);
+
+static struct notifier_block *inetaddr_chain;
+static void inet_del_ifa(struct in_device *in_dev, struct in_ifaddr **ifap, int destroy);
+#ifdef CONFIG_SYSCTL
+static void devinet_sysctl_register(struct in_device *in_dev, struct ipv4_devconf *p);
+static void devinet_sysctl_unregister(struct ipv4_devconf *p);
+#endif
+
+int inet_ifa_count;
+int inet_dev_count;
+
+/* Locks all the inet devices. */
+
+rwlock_t inetdev_lock = RW_LOCK_UNLOCKED;
+
+
+static struct in_ifaddr * inet_alloc_ifa(void)
+{
+ struct in_ifaddr *ifa;
+
+ ifa = kmalloc(sizeof(*ifa), GFP_KERNEL);
+ if (ifa) {
+ memset(ifa, 0, sizeof(*ifa));
+ inet_ifa_count++;
+ }
+
+ return ifa;
+}
+
+static __inline__ void inet_free_ifa(struct in_ifaddr *ifa)
+{
+ if (ifa->ifa_dev)
+ __in_dev_put(ifa->ifa_dev);
+ kfree(ifa);
+ inet_ifa_count--;
+}
+
+void in_dev_finish_destroy(struct in_device *idev)
+{
+ struct net_device *dev = idev->dev;
+
+ BUG_TRAP(idev->ifa_list==NULL);
+ BUG_TRAP(idev->mc_list==NULL);
+#ifdef NET_REFCNT_DEBUG
+ printk(KERN_DEBUG "in_dev_finish_destroy: %p=%s\n", idev, dev ? dev->name : "NIL");
+#endif
+ dev_put(dev);
+ if (!idev->dead) {
+ printk("Freeing alive in_device %p\n", idev);
+ return;
+ }
+ inet_dev_count--;
+ kfree(idev);
+}
+
+struct in_device *inetdev_init(struct net_device *dev)
+{
+ struct in_device *in_dev;
+
+ ASSERT_RTNL();
+
+ in_dev = kmalloc(sizeof(*in_dev), GFP_KERNEL);
+ if (!in_dev)
+ return NULL;
+ memset(in_dev, 0, sizeof(*in_dev));
+ in_dev->lock = RW_LOCK_UNLOCKED;
+ memcpy(&in_dev->cnf, &ipv4_devconf_dflt, sizeof(in_dev->cnf));
+ in_dev->cnf.sysctl = NULL;
+ in_dev->dev = dev;
+ if ((in_dev->arp_parms = neigh_parms_alloc(dev, &arp_tbl)) == NULL) {
+ kfree(in_dev);
+ return NULL;
+ }
+ inet_dev_count++;
+ /* Reference in_dev->dev */
+ dev_hold(dev);
+#ifdef CONFIG_SYSCTL
+ neigh_sysctl_register(dev, in_dev->arp_parms, NET_IPV4, NET_IPV4_NEIGH, "ipv4");
+#endif
+ write_lock_bh(&inetdev_lock);
+ dev->ip_ptr = in_dev;
+ /* Account for reference dev->ip_ptr */
+ in_dev_hold(in_dev);
+ write_unlock_bh(&inetdev_lock);
+#ifdef CONFIG_SYSCTL
+ devinet_sysctl_register(in_dev, &in_dev->cnf);
+#endif
+ ip_mc_init_dev(in_dev);
+ if (dev->flags & IFF_UP)
+ ip_mc_up(in_dev);
+ return in_dev;
+}
+
+static void inetdev_destroy(struct in_device *in_dev)
+{
+ struct in_ifaddr *ifa;
+
+ ASSERT_RTNL();
+
+ in_dev->dead = 1;
+
+ ip_mc_destroy_dev(in_dev);
+
+ while ((ifa = in_dev->ifa_list) != NULL) {
+ inet_del_ifa(in_dev, &in_dev->ifa_list, 0);
+ inet_free_ifa(ifa);
+ }
+
+#ifdef CONFIG_SYSCTL
+ devinet_sysctl_unregister(&in_dev->cnf);
+#endif
+ write_lock_bh(&inetdev_lock);
+ in_dev->dev->ip_ptr = NULL;
+ /* in_dev_put following below will kill the in_device */
+ write_unlock_bh(&inetdev_lock);
+
+
+ neigh_parms_release(&arp_tbl, in_dev->arp_parms);
+ in_dev_put(in_dev);
+}
+
+int inet_addr_onlink(struct in_device *in_dev, u32 a, u32 b)
+{
+ read_lock(&in_dev->lock);
+ for_primary_ifa(in_dev) {
+ if (inet_ifa_match(a, ifa)) {
+ if (!b || inet_ifa_match(b, ifa)) {
+ read_unlock(&in_dev->lock);
+ return 1;
+ }
+ }
+ } endfor_ifa(in_dev);
+ read_unlock(&in_dev->lock);
+ return 0;
+}
+
+static void
+inet_del_ifa(struct in_device *in_dev, struct in_ifaddr **ifap, int destroy)
+{
+ struct in_ifaddr *ifa1 = *ifap;
+
+ ASSERT_RTNL();
+
+ /* 1. Deleting primary ifaddr forces deletion all secondaries */
+
+ if (!(ifa1->ifa_flags&IFA_F_SECONDARY)) {
+ struct in_ifaddr *ifa;
+ struct in_ifaddr **ifap1 = &ifa1->ifa_next;
+
+ while ((ifa=*ifap1) != NULL) {
+ if (!(ifa->ifa_flags&IFA_F_SECONDARY) ||
+ ifa1->ifa_mask != ifa->ifa_mask ||
+ !inet_ifa_match(ifa1->ifa_address, ifa)) {
+ ifap1 = &ifa->ifa_next;
+ continue;
+ }
+ write_lock_bh(&in_dev->lock);
+ *ifap1 = ifa->ifa_next;
+ write_unlock_bh(&in_dev->lock);
+
+ rtmsg_ifa(RTM_DELADDR, ifa);
+ notifier_call_chain(&inetaddr_chain, NETDEV_DOWN, ifa);
+ inet_free_ifa(ifa);
+ }
+ }
+
+ /* 2. Unlink it */
+
+ write_lock_bh(&in_dev->lock);
+ *ifap = ifa1->ifa_next;
+ write_unlock_bh(&in_dev->lock);
+
+ /* 3. Announce address deletion */
+
+ /* Send message first, then call notifier.
+ At first sight, FIB update triggered by notifier
+ will refer to already deleted ifaddr, that could confuse
+ netlink listeners. It is not true: look, gated sees
+ that route deleted and if it still thinks that ifaddr
+ is valid, it will try to restore deleted routes... Grr.
+ So that, this order is correct.
+ */
+ rtmsg_ifa(RTM_DELADDR, ifa1);
+ notifier_call_chain(&inetaddr_chain, NETDEV_DOWN, ifa1);
+ if (destroy) {
+ inet_free_ifa(ifa1);
+
+ if (in_dev->ifa_list == NULL)
+ inetdev_destroy(in_dev);
+ }
+}
+
+static int
+inet_insert_ifa(struct in_ifaddr *ifa)
+{
+ struct in_device *in_dev = ifa->ifa_dev;
+ struct in_ifaddr *ifa1, **ifap, **last_primary;
+
+ ASSERT_RTNL();
+
+ if (ifa->ifa_local == 0) {
+ inet_free_ifa(ifa);
+ return 0;
+ }
+
+ ifa->ifa_flags &= ~IFA_F_SECONDARY;
+ last_primary = &in_dev->ifa_list;
+
+ for (ifap=&in_dev->ifa_list; (ifa1=*ifap)!=NULL; ifap=&ifa1->ifa_next) {
+ if (!(ifa1->ifa_flags&IFA_F_SECONDARY) && ifa->ifa_scope <= ifa1->ifa_scope)
+ last_primary = &ifa1->ifa_next;
+ if (ifa1->ifa_mask == ifa->ifa_mask && inet_ifa_match(ifa1->ifa_address, ifa)) {
+ if (ifa1->ifa_local == ifa->ifa_local) {
+ inet_free_ifa(ifa);
+ return -EEXIST;
+ }
+ if (ifa1->ifa_scope != ifa->ifa_scope) {
+ inet_free_ifa(ifa);
+ return -EINVAL;
+ }
+ ifa->ifa_flags |= IFA_F_SECONDARY;
+ }
+ }
+
+ if (!(ifa->ifa_flags&IFA_F_SECONDARY)) {
+ net_srandom(ifa->ifa_local);
+ ifap = last_primary;
+ }
+
+ ifa->ifa_next = *ifap;
+ write_lock_bh(&in_dev->lock);
+ *ifap = ifa;
+ write_unlock_bh(&in_dev->lock);
+
+ /* Send message first, then call notifier.
+ Notifier will trigger FIB update, so that
+ listeners of netlink will know about new ifaddr */
+ rtmsg_ifa(RTM_NEWADDR, ifa);
+ notifier_call_chain(&inetaddr_chain, NETDEV_UP, ifa);
+
+ return 0;
+}
+
+static int
+inet_set_ifa(struct net_device *dev, struct in_ifaddr *ifa)
+{
+ struct in_device *in_dev = __in_dev_get(dev);
+
+ ASSERT_RTNL();
+
+ if (in_dev == NULL) {
+ in_dev = inetdev_init(dev);
+ if (in_dev == NULL) {
+ inet_free_ifa(ifa);
+ return -ENOBUFS;
+ }
+ }
+ if (ifa->ifa_dev != in_dev) {
+ BUG_TRAP(ifa->ifa_dev==NULL);
+ in_dev_hold(in_dev);
+ ifa->ifa_dev=in_dev;
+ }
+ if (LOOPBACK(ifa->ifa_local))
+ ifa->ifa_scope = RT_SCOPE_HOST;
+ return inet_insert_ifa(ifa);
+}
+
+struct in_device *inetdev_by_index(int ifindex)
+{
+ struct net_device *dev;
+ struct in_device *in_dev = NULL;
+ read_lock(&dev_base_lock);
+ dev = __dev_get_by_index(ifindex);
+ if (dev)
+ in_dev = in_dev_get(dev);
+ read_unlock(&dev_base_lock);
+ return in_dev;
+}
+
+/* Called only from RTNL semaphored context. No locks. */
+
+struct in_ifaddr *inet_ifa_byprefix(struct in_device *in_dev, u32 prefix, u32 mask)
+{
+ ASSERT_RTNL();
+
+ for_primary_ifa(in_dev) {
+ if (ifa->ifa_mask == mask && inet_ifa_match(prefix, ifa))
+ return ifa;
+ } endfor_ifa(in_dev);
+ return NULL;
+}
+
+int
+inet_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct rtattr **rta = arg;
+ struct in_device *in_dev;
+ struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
+ struct in_ifaddr *ifa, **ifap;
+
+ ASSERT_RTNL();
+
+ if ((in_dev = inetdev_by_index(ifm->ifa_index)) == NULL)
+ return -EADDRNOTAVAIL;
+ __in_dev_put(in_dev);
+
+ for (ifap=&in_dev->ifa_list; (ifa=*ifap)!=NULL; ifap=&ifa->ifa_next) {
+ if ((rta[IFA_LOCAL-1] && memcmp(RTA_DATA(rta[IFA_LOCAL-1]), &ifa->ifa_local, 4)) ||
+ (rta[IFA_LABEL-1] && strcmp(RTA_DATA(rta[IFA_LABEL-1]), ifa->ifa_label)) ||
+ (rta[IFA_ADDRESS-1] &&
+ (ifm->ifa_prefixlen != ifa->ifa_prefixlen ||
+ !inet_ifa_match(*(u32*)RTA_DATA(rta[IFA_ADDRESS-1]), ifa))))
+ continue;
+ inet_del_ifa(in_dev, ifap, 1);
+ return 0;
+ }
+
+ return -EADDRNOTAVAIL;
+}
+
+int
+inet_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct rtattr **rta = arg;
+ struct net_device *dev;
+ struct in_device *in_dev;
+ struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
+ struct in_ifaddr *ifa;
+
+ ASSERT_RTNL();
+
+ if (ifm->ifa_prefixlen > 32 || rta[IFA_LOCAL-1] == NULL)
+ return -EINVAL;
+
+ if ((dev = __dev_get_by_index(ifm->ifa_index)) == NULL)
+ return -ENODEV;
+
+ if ((in_dev = __in_dev_get(dev)) == NULL) {
+ in_dev = inetdev_init(dev);
+ if (!in_dev)
+ return -ENOBUFS;
+ }
+
+ if ((ifa = inet_alloc_ifa()) == NULL)
+ return -ENOBUFS;
+
+ if (rta[IFA_ADDRESS-1] == NULL)
+ rta[IFA_ADDRESS-1] = rta[IFA_LOCAL-1];
+ memcpy(&ifa->ifa_local, RTA_DATA(rta[IFA_LOCAL-1]), 4);
+ memcpy(&ifa->ifa_address, RTA_DATA(rta[IFA_ADDRESS-1]), 4);
+ ifa->ifa_prefixlen = ifm->ifa_prefixlen;
+ ifa->ifa_mask = inet_make_mask(ifm->ifa_prefixlen);
+ if (rta[IFA_BROADCAST-1])
+ memcpy(&ifa->ifa_broadcast, RTA_DATA(rta[IFA_BROADCAST-1]), 4);
+ if (rta[IFA_ANYCAST-1])
+ memcpy(&ifa->ifa_anycast, RTA_DATA(rta[IFA_ANYCAST-1]), 4);
+ ifa->ifa_flags = ifm->ifa_flags;
+ ifa->ifa_scope = ifm->ifa_scope;
+ in_dev_hold(in_dev);
+ ifa->ifa_dev = in_dev;
+ if (rta[IFA_LABEL-1])
+ memcpy(ifa->ifa_label, RTA_DATA(rta[IFA_LABEL-1]), IFNAMSIZ);
+ else
+ memcpy(ifa->ifa_label, dev->name, IFNAMSIZ);
+
+ return inet_insert_ifa(ifa);
+}
+
+/*
+ * Determine a default network mask, based on the IP address.
+ */
+
+static __inline__ int inet_abc_len(u32 addr)
+{
+ if (ZERONET(addr))
+ return 0;
+
+ addr = ntohl(addr);
+ if (IN_CLASSA(addr))
+ return 8;
+ if (IN_CLASSB(addr))
+ return 16;
+ if (IN_CLASSC(addr))
+ return 24;
+
+ /*
+ * Something else, probably a multicast.
+ */
+
+ return -1;
+}
+
+
+int devinet_ioctl(unsigned int cmd, void *arg)
+{
+ struct ifreq ifr;
+ struct sockaddr_in sin_orig;
+ struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;
+ struct in_device *in_dev;
+ struct in_ifaddr **ifap = NULL;
+ struct in_ifaddr *ifa = NULL;
+ struct net_device *dev;
+ char *colon;
+ int ret = 0;
+ int tryaddrmatch = 0;
+
+ /*
+ * Fetch the caller's info block into kernel space
+ */
+
+ if (copy_from_user(&ifr, arg, sizeof(struct ifreq)))
+ return -EFAULT;
+ ifr.ifr_name[IFNAMSIZ-1] = 0;
+
+ /* save original address for comparison */
+ memcpy(&sin_orig, sin, sizeof(*sin));
+
+ colon = strchr(ifr.ifr_name, ':');
+ if (colon)
+ *colon = 0;
+
+#ifdef CONFIG_KMOD
+ dev_load(ifr.ifr_name);
+#endif
+
+ switch(cmd) {
+ case SIOCGIFADDR: /* Get interface address */
+ case SIOCGIFBRDADDR: /* Get the broadcast address */
+ case SIOCGIFDSTADDR: /* Get the destination address */
+ case SIOCGIFNETMASK: /* Get the netmask for the interface */
+ /* Note that these ioctls will not sleep,
+ so that we do not impose a lock.
+ One day we will be forced to put shlock here (I mean SMP)
+ */
+ tryaddrmatch = (sin_orig.sin_family == AF_INET);
+ memset(sin, 0, sizeof(*sin));
+ sin->sin_family = AF_INET;
+ break;
+
+ case SIOCSIFFLAGS:
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+ break;
+ case SIOCSIFADDR: /* Set interface address (and family) */
+ case SIOCSIFBRDADDR: /* Set the broadcast address */
+ case SIOCSIFDSTADDR: /* Set the destination address */
+ case SIOCSIFNETMASK: /* Set the netmask for the interface */
+ if (!capable(CAP_NET_ADMIN))
+ return -EACCES;
+ if (sin->sin_family != AF_INET)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_probe_lock();
+ rtnl_lock();
+
+ if ((dev = __dev_get_by_name(ifr.ifr_name)) == NULL) {
+ ret = -ENODEV;
+ goto done;
+ }
+
+ if (colon)
+ *colon = ':';
+
+ if ((in_dev=__in_dev_get(dev)) != NULL) {
+ if (tryaddrmatch) {
+ /* Matthias Andree */
+ /* compare label and address (4.4BSD style) */
+ /* note: we only do this for a limited set of ioctls
+ and only if the original address family was AF_INET.
+ This is checked above. */
+ for (ifap=&in_dev->ifa_list; (ifa=*ifap) != NULL; ifap=&ifa->ifa_next) {
+ if ((strcmp(ifr.ifr_name, ifa->ifa_label) == 0)
+ && (sin_orig.sin_addr.s_addr == ifa->ifa_address)) {
+ break; /* found */
+ }
+ }
+ }
+ /* we didn't get a match, maybe the application is
+ 4.3BSD-style and passed in junk so we fall back to
+ comparing just the label */
+ if (ifa == NULL) {
+ for (ifap=&in_dev->ifa_list; (ifa=*ifap) != NULL; ifap=&ifa->ifa_next)
+ if (strcmp(ifr.ifr_name, ifa->ifa_label) == 0)
+ break;
+ }
+ }
+
+ if (ifa == NULL && cmd != SIOCSIFADDR && cmd != SIOCSIFFLAGS) {
+ ret = -EADDRNOTAVAIL;
+ goto done;
+ }
+
+ switch(cmd) {
+ case SIOCGIFADDR: /* Get interface address */
+ sin->sin_addr.s_addr = ifa->ifa_local;
+ goto rarok;
+
+ case SIOCGIFBRDADDR: /* Get the broadcast address */
+ sin->sin_addr.s_addr = ifa->ifa_broadcast;
+ goto rarok;
+
+ case SIOCGIFDSTADDR: /* Get the destination address */
+ sin->sin_addr.s_addr = ifa->ifa_address;
+ goto rarok;
+
+ case SIOCGIFNETMASK: /* Get the netmask for the interface */
+ sin->sin_addr.s_addr = ifa->ifa_mask;
+ goto rarok;
+
+ case SIOCSIFFLAGS:
+ if (colon) {
+ if (ifa == NULL) {
+ ret = -EADDRNOTAVAIL;
+ break;
+ }
+ if (!(ifr.ifr_flags&IFF_UP))
+ inet_del_ifa(in_dev, ifap, 1);
+ break;
+ }
+ ret = dev_change_flags(dev, ifr.ifr_flags);
+ break;
+
+ case SIOCSIFADDR: /* Set interface address (and family) */
+ if (inet_abc_len(sin->sin_addr.s_addr) < 0) {
+ ret = -EINVAL;
+ break;
+ }
+
+ if (!ifa) {
+ if ((ifa = inet_alloc_ifa()) == NULL) {
+ ret = -ENOBUFS;
+ break;
+ }
+ if (colon)
+ memcpy(ifa->ifa_label, ifr.ifr_name, IFNAMSIZ);
+ else
+ memcpy(ifa->ifa_label, dev->name, IFNAMSIZ);
+ } else {
+ ret = 0;
+ if (ifa->ifa_local == sin->sin_addr.s_addr)
+ break;
+ inet_del_ifa(in_dev, ifap, 0);
+ ifa->ifa_broadcast = 0;
+ ifa->ifa_anycast = 0;
+ }
+
+ ifa->ifa_address =
+ ifa->ifa_local = sin->sin_addr.s_addr;
+
+ if (!(dev->flags&IFF_POINTOPOINT)) {
+ ifa->ifa_prefixlen = inet_abc_len(ifa->ifa_address);
+ ifa->ifa_mask = inet_make_mask(ifa->ifa_prefixlen);
+ if ((dev->flags&IFF_BROADCAST) && ifa->ifa_prefixlen < 31)
+ ifa->ifa_broadcast = ifa->ifa_address|~ifa->ifa_mask;
+ } else {
+ ifa->ifa_prefixlen = 32;
+ ifa->ifa_mask = inet_make_mask(32);
+ }
+ ret = inet_set_ifa(dev, ifa);
+ break;
+
+ case SIOCSIFBRDADDR: /* Set the broadcast address */
+ if (ifa->ifa_broadcast != sin->sin_addr.s_addr) {
+ inet_del_ifa(in_dev, ifap, 0);
+ ifa->ifa_broadcast = sin->sin_addr.s_addr;
+ inet_insert_ifa(ifa);
+ }
+ break;
+
+ case SIOCSIFDSTADDR: /* Set the destination address */
+ if (ifa->ifa_address != sin->sin_addr.s_addr) {
+ if (inet_abc_len(sin->sin_addr.s_addr) < 0) {
+ ret = -EINVAL;
+ break;
+ }
+ inet_del_ifa(in_dev, ifap, 0);
+ ifa->ifa_address = sin->sin_addr.s_addr;
+ inet_insert_ifa(ifa);
+ }
+ break;
+
+ case SIOCSIFNETMASK: /* Set the netmask for the interface */
+
+ /*
+ * The mask we set must be legal.
+ */
+ if (bad_mask(sin->sin_addr.s_addr, 0)) {
+ ret = -EINVAL;
+ break;
+ }
+
+ if (ifa->ifa_mask != sin->sin_addr.s_addr) {
+ inet_del_ifa(in_dev, ifap, 0);
+ ifa->ifa_mask = sin->sin_addr.s_addr;
+ ifa->ifa_prefixlen =
+ inet_mask_len(ifa->ifa_mask);
+
+ /* See if current broadcast address matches
+ * with current netmask, then recalculate
+ * the broadcast address. Otherwise it's a
+ * funny address, so don't touch it since
+ * the user seems to know what (s)he's doing...
+ */
+ if ((dev->flags & IFF_BROADCAST) &&
+ (ifa->ifa_prefixlen < 31) &&
+ (ifa->ifa_broadcast ==
+ (ifa->ifa_local|~ifa->ifa_mask))) {
+ ifa->ifa_broadcast =
+ (ifa->ifa_local |
+ ~sin->sin_addr.s_addr);
+ }
+ inet_insert_ifa(ifa);
+ }
+ break;
+ }
+done:
+ rtnl_unlock();
+ dev_probe_unlock();
+ return ret;
+
+rarok:
+ rtnl_unlock();
+ dev_probe_unlock();
+ if (copy_to_user(arg, &ifr, sizeof(struct ifreq)))
+ return -EFAULT;
+ return 0;
+}
+
+static int
+inet_gifconf(struct net_device *dev, char *buf, int len)
+{
+ struct in_device *in_dev = __in_dev_get(dev);
+ struct in_ifaddr *ifa;
+ struct ifreq ifr;
+ int done=0;
+
+ if (in_dev==NULL || (ifa=in_dev->ifa_list)==NULL)
+ return 0;
+
+ for ( ; ifa; ifa = ifa->ifa_next) {
+ if (!buf) {
+ done += sizeof(ifr);
+ continue;
+ }
+ if (len < (int) sizeof(ifr))
+ return done;
+ memset(&ifr, 0, sizeof(struct ifreq));
+ if (ifa->ifa_label)
+ strcpy(ifr.ifr_name, ifa->ifa_label);
+ else
+ strcpy(ifr.ifr_name, dev->name);
+
+ (*(struct sockaddr_in *) &ifr.ifr_addr).sin_family = AF_INET;
+ (*(struct sockaddr_in *) &ifr.ifr_addr).sin_addr.s_addr = ifa->ifa_local;
+
+ if (copy_to_user(buf, &ifr, sizeof(struct ifreq)))
+ return -EFAULT;
+ buf += sizeof(struct ifreq);
+ len -= sizeof(struct ifreq);
+ done += sizeof(struct ifreq);
+ }
+ return done;
+}
+
+u32 inet_select_addr(const struct net_device *dev, u32 dst, int scope)
+{
+ u32 addr = 0;
+ struct in_device *in_dev;
+
+ read_lock(&inetdev_lock);
+ in_dev = __in_dev_get(dev);
+ if (in_dev == NULL) {
+ read_unlock(&inetdev_lock);
+ return 0;
+ }
+
+ read_lock(&in_dev->lock);
+ for_primary_ifa(in_dev) {
+ if (ifa->ifa_scope > scope)
+ continue;
+ if (!dst || inet_ifa_match(dst, ifa)) {
+ addr = ifa->ifa_local;
+ break;
+ }
+ if (!addr)
+ addr = ifa->ifa_local;
+ } endfor_ifa(in_dev);
+ read_unlock(&in_dev->lock);
+ read_unlock(&inetdev_lock);
+
+ if (addr)
+ return addr;
+
+ /* Not loopback addresses on loopback should be preferred
+ in this case. It is importnat that lo is the first interface
+ in dev_base list.
+ */
+ read_lock(&dev_base_lock);
+ read_lock(&inetdev_lock);
+ for (dev=dev_base; dev; dev=dev->next) {
+ if ((in_dev=__in_dev_get(dev)) == NULL)
+ continue;
+
+ read_lock(&in_dev->lock);
+ for_primary_ifa(in_dev) {
+ if (ifa->ifa_scope != RT_SCOPE_LINK &&
+ ifa->ifa_scope <= scope) {
+ read_unlock(&in_dev->lock);
+ read_unlock(&inetdev_lock);
+ read_unlock(&dev_base_lock);
+ return ifa->ifa_local;
+ }
+ } endfor_ifa(in_dev);
+ read_unlock(&in_dev->lock);
+ }
+ read_unlock(&inetdev_lock);
+ read_unlock(&dev_base_lock);
+
+ return 0;
+}
+
+static u32 confirm_addr_indev(struct in_device *in_dev, u32 dst,
+ u32 local, int scope)
+{
+ int same = 0;
+ u32 addr = 0;
+
+ for_ifa(in_dev) {
+ if (!addr &&
+ (local == ifa->ifa_local || !local) &&
+ ifa->ifa_scope <= scope) {
+ addr = ifa->ifa_local;
+ if (same)
+ break;
+ }
+ if (!same) {
+ same = (!local || inet_ifa_match(local, ifa)) &&
+ (!dst || inet_ifa_match(dst, ifa));
+ if (same && addr) {
+ if (local || !dst)
+ break;
+ /* Is the selected addr into dst subnet? */
+ if (inet_ifa_match(addr, ifa))
+ break;
+ /* No, then can we use new local src? */
+ if (ifa->ifa_scope <= scope) {
+ addr = ifa->ifa_local;
+ break;
+ }
+ /* search for large dst subnet for addr */
+ same = 0;
+ }
+ }
+ } endfor_ifa(in_dev);
+
+ return same? addr : 0;
+}
+
+/*
+ * Confirm that local IP address exists using wildcards:
+ * - dev: only on this interface, 0=any interface
+ * - dst: only in the same subnet as dst, 0=any dst
+ * - local: address, 0=autoselect the local address
+ * - scope: maximum allowed scope value for the local address
+ */
+u32 inet_confirm_addr(const struct net_device *dev, u32 dst, u32 local, int scope)
+{
+ u32 addr = 0;
+ struct in_device *in_dev;
+
+ if (dev) {
+ read_lock(&inetdev_lock);
+ if ((in_dev = __in_dev_get(dev))) {
+ read_lock(&in_dev->lock);
+ addr = confirm_addr_indev(in_dev, dst, local, scope);
+ read_unlock(&in_dev->lock);
+ }
+ read_unlock(&inetdev_lock);
+
+ return addr;
+ }
+
+ read_lock(&dev_base_lock);
+ read_lock(&inetdev_lock);
+ for (dev = dev_base; dev; dev = dev->next) {
+ if ((in_dev = __in_dev_get(dev))) {
+ read_lock(&in_dev->lock);
+ addr = confirm_addr_indev(in_dev, dst, local, scope);
+ read_unlock(&in_dev->lock);
+ if (addr)
+ break;
+ }
+ }
+ read_unlock(&inetdev_lock);
+ read_unlock(&dev_base_lock);
+
+ return addr;
+}
+
+/*
+ * Device notifier
+ */
+
+int register_inetaddr_notifier(struct notifier_block *nb)
+{
+ return notifier_chain_register(&inetaddr_chain, nb);
+}
+
+int unregister_inetaddr_notifier(struct notifier_block *nb)
+{
+ return notifier_chain_unregister(&inetaddr_chain,nb);
+}
+
+/* Rename ifa_labels for a device name change. Make some effort to preserve existing
+ * alias numbering and to create unique labels if possible.
+*/
+static void inetdev_changename(struct net_device *dev, struct in_device *in_dev)
+{
+ struct in_ifaddr *ifa;
+ int named = 0;
+
+ for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next) {
+ char old[IFNAMSIZ], *dot;
+
+ memcpy(old, ifa->ifa_label, IFNAMSIZ);
+ memcpy(ifa->ifa_label, dev->name, IFNAMSIZ);
+ if (named++ == 0)
+ continue;
+ dot = strchr(ifa->ifa_label, ':');
+ if (dot == NULL) {
+ sprintf(old, ":%d", named);
+ dot = old;
+ }
+ if (strlen(dot) + strlen(dev->name) < IFNAMSIZ) {
+ strcat(ifa->ifa_label, dot);
+ } else {
+ strcpy(ifa->ifa_label + (IFNAMSIZ - strlen(dot) - 1), dot);
+ }
+ }
+}
+
+/* Called only under RTNL semaphore */
+
+static int inetdev_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct net_device *dev = ptr;
+ struct in_device *in_dev = __in_dev_get(dev);
+
+ ASSERT_RTNL();
+
+ if (in_dev == NULL)
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case NETDEV_REGISTER:
+ printk(KERN_DEBUG "inetdev_event: bug\n");
+ dev->ip_ptr = NULL;
+ break;
+ case NETDEV_UP:
+ if (dev->mtu < 68)
+ break;
+ if (dev == &loopback_dev) {
+ struct in_ifaddr *ifa;
+ if ((ifa = inet_alloc_ifa()) != NULL) {
+ ifa->ifa_local =
+ ifa->ifa_address = htonl(INADDR_LOOPBACK);
+ ifa->ifa_prefixlen = 8;
+ ifa->ifa_mask = inet_make_mask(8);
+ in_dev_hold(in_dev);
+ ifa->ifa_dev = in_dev;
+ ifa->ifa_scope = RT_SCOPE_HOST;
+ memcpy(ifa->ifa_label, dev->name, IFNAMSIZ);
+ inet_insert_ifa(ifa);
+ }
+ }
+ ip_mc_up(in_dev);
+ break;
+ case NETDEV_DOWN:
+ ip_mc_down(in_dev);
+ break;
+ case NETDEV_CHANGEMTU:
+ if (dev->mtu >= 68)
+ break;
+ /* MTU falled under 68, disable IP */
+ case NETDEV_UNREGISTER:
+ inetdev_destroy(in_dev);
+ break;
+ case NETDEV_CHANGENAME:
+ /* Do not notify about label change, this event is
+ * not interesting to applications using netlink.
+ */
+ inetdev_changename(dev, in_dev);
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+struct notifier_block ip_netdev_notifier = {
+ notifier_call: inetdev_event,
+};
+
+static int inet_fill_ifaddr(struct sk_buff *skb, struct in_ifaddr *ifa,
+ u32 pid, u32 seq, int event)
+{
+ struct ifaddrmsg *ifm;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+ nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*ifm));
+ if (pid) nlh->nlmsg_flags |= NLM_F_MULTI;
+ ifm = NLMSG_DATA(nlh);
+ ifm->ifa_family = AF_INET;
+ ifm->ifa_prefixlen = ifa->ifa_prefixlen;
+ ifm->ifa_flags = ifa->ifa_flags|IFA_F_PERMANENT;
+ ifm->ifa_scope = ifa->ifa_scope;
+ ifm->ifa_index = ifa->ifa_dev->dev->ifindex;
+ if (ifa->ifa_address)
+ RTA_PUT(skb, IFA_ADDRESS, 4, &ifa->ifa_address);
+ if (ifa->ifa_local)
+ RTA_PUT(skb, IFA_LOCAL, 4, &ifa->ifa_local);
+ if (ifa->ifa_broadcast)
+ RTA_PUT(skb, IFA_BROADCAST, 4, &ifa->ifa_broadcast);
+ if (ifa->ifa_anycast)
+ RTA_PUT(skb, IFA_ANYCAST, 4, &ifa->ifa_anycast);
+ if (ifa->ifa_label[0])
+ RTA_PUT(skb, IFA_LABEL, IFNAMSIZ, &ifa->ifa_label);
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int inet_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int idx, ip_idx;
+ int s_idx, s_ip_idx;
+ struct net_device *dev;
+ struct in_device *in_dev;
+ struct in_ifaddr *ifa;
+
+ s_idx = cb->args[0];
+ s_ip_idx = ip_idx = cb->args[1];
+ read_lock(&dev_base_lock);
+ for (dev=dev_base, idx=0; dev; dev = dev->next, idx++) {
+ if (idx < s_idx)
+ continue;
+ if (idx > s_idx)
+ s_ip_idx = 0;
+ read_lock(&inetdev_lock);
+ if ((in_dev = __in_dev_get(dev)) == NULL) {
+ read_unlock(&inetdev_lock);
+ continue;
+ }
+ read_lock(&in_dev->lock);
+ for (ifa = in_dev->ifa_list, ip_idx = 0; ifa;
+ ifa = ifa->ifa_next, ip_idx++) {
+ if (ip_idx < s_ip_idx)
+ continue;
+ if (inet_fill_ifaddr(skb, ifa, NETLINK_CB(cb->skb).pid,
+ cb->nlh->nlmsg_seq, RTM_NEWADDR) <= 0) {
+ read_unlock(&in_dev->lock);
+ read_unlock(&inetdev_lock);
+ goto done;
+ }
+ }
+ read_unlock(&in_dev->lock);
+ read_unlock(&inetdev_lock);
+ }
+
+done:
+ read_unlock(&dev_base_lock);
+ cb->args[0] = idx;
+ cb->args[1] = ip_idx;
+
+ return skb->len;
+}
+
+static void rtmsg_ifa(int event, struct in_ifaddr * ifa)
+{
+ struct sk_buff *skb;
+ int size = NLMSG_SPACE(sizeof(struct ifaddrmsg)+128);
+
+ skb = alloc_skb(size, GFP_KERNEL);
+ if (!skb) {
+ netlink_set_err(rtnl, 0, RTMGRP_IPV4_IFADDR, ENOBUFS);
+ return;
+ }
+ if (inet_fill_ifaddr(skb, ifa, 0, 0, event) < 0) {
+ kfree_skb(skb);
+ netlink_set_err(rtnl, 0, RTMGRP_IPV4_IFADDR, EINVAL);
+ return;
+ }
+ NETLINK_CB(skb).dst_groups = RTMGRP_IPV4_IFADDR;
+ netlink_broadcast(rtnl, skb, 0, RTMGRP_IPV4_IFADDR, GFP_KERNEL);
+}
+
+
+static struct rtnetlink_link inet_rtnetlink_table[RTM_MAX-RTM_BASE+1] =
+{
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+
+ { inet_rtm_newaddr, NULL, },
+ { inet_rtm_deladdr, NULL, },
+ { NULL, inet_dump_ifaddr, },
+ { NULL, NULL, },
+
+ { inet_rtm_newroute, NULL, },
+ { inet_rtm_delroute, NULL, },
+ { inet_rtm_getroute, inet_dump_fib, },
+ { NULL, NULL, },
+
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+
+#ifdef CONFIG_IP_MULTIPLE_TABLES
+ { inet_rtm_newrule, NULL, },
+ { inet_rtm_delrule, NULL, },
+ { NULL, inet_dump_rules, },
+ { NULL, NULL, },
+#else
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, NULL, },
+#endif
+};
+
+
+#ifdef CONFIG_SYSCTL
+
+void inet_forward_change(int on)
+{
+ struct net_device *dev;
+
+ ipv4_devconf.accept_redirects = !on;
+ ipv4_devconf_dflt.forwarding = on;
+
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev; dev = dev->next) {
+ struct in_device *in_dev;
+ read_lock(&inetdev_lock);
+ in_dev = __in_dev_get(dev);
+ if (in_dev)
+ in_dev->cnf.forwarding = on;
+ read_unlock(&inetdev_lock);
+ }
+ read_unlock(&dev_base_lock);
+
+ rt_cache_flush(0);
+}
+
+static
+int devinet_sysctl_forward(ctl_table *ctl, int write, struct file * filp,
+ void *buffer, size_t *lenp)
+{
+ int *valp = ctl->data;
+ int val = *valp;
+ int ret;
+
+ ret = proc_dointvec(ctl, write, filp, buffer, lenp);
+
+ if (write && *valp != val) {
+ if (valp == &ipv4_devconf.forwarding)
+ inet_forward_change(*valp);
+ else if (valp != &ipv4_devconf_dflt.forwarding)
+ rt_cache_flush(0);
+ }
+
+ return ret;
+}
+
+static struct devinet_sysctl_table
+{
+ struct ctl_table_header *sysctl_header;
+ ctl_table devinet_vars[20];
+ ctl_table devinet_dev[2];
+ ctl_table devinet_conf_dir[2];
+ ctl_table devinet_proto_dir[2];
+ ctl_table devinet_root_dir[2];
+} devinet_sysctl = {
+ NULL,
+ {{NET_IPV4_CONF_FORWARDING, "forwarding",
+ &ipv4_devconf.forwarding, sizeof(int), 0644, NULL,
+ &devinet_sysctl_forward},
+ {NET_IPV4_CONF_MC_FORWARDING, "mc_forwarding",
+ &ipv4_devconf.mc_forwarding, sizeof(int), 0444, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_ACCEPT_REDIRECTS, "accept_redirects",
+ &ipv4_devconf.accept_redirects, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_SECURE_REDIRECTS, "secure_redirects",
+ &ipv4_devconf.secure_redirects, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_SHARED_MEDIA, "shared_media",
+ &ipv4_devconf.shared_media, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_RP_FILTER, "rp_filter",
+ &ipv4_devconf.rp_filter, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_SEND_REDIRECTS, "send_redirects",
+ &ipv4_devconf.send_redirects, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_ACCEPT_SOURCE_ROUTE, "accept_source_route",
+ &ipv4_devconf.accept_source_route, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_PROXY_ARP, "proxy_arp",
+ &ipv4_devconf.proxy_arp, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_MEDIUM_ID, "medium_id",
+ &ipv4_devconf.medium_id, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_BOOTP_RELAY, "bootp_relay",
+ &ipv4_devconf.bootp_relay, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_LOG_MARTIANS, "log_martians",
+ &ipv4_devconf.log_martians, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_TAG, "tag",
+ &ipv4_devconf.tag, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_ARPFILTER, "arp_filter",
+ &ipv4_devconf.arp_filter, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_ARP_ANNOUNCE, "arp_announce",
+ &ipv4_devconf.arp_announce, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_ARP_IGNORE, "arp_ignore",
+ &ipv4_devconf.arp_ignore, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_CONF_FORCE_IGMP_VERSION, "force_igmp_version",
+ &ipv4_devconf.force_igmp_version, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {0}},
+
+ {{NET_PROTO_CONF_ALL, "all", NULL, 0, 0555, devinet_sysctl.devinet_vars},{0}},
+ {{NET_IPV4_CONF, "conf", NULL, 0, 0555, devinet_sysctl.devinet_dev},{0}},
+ {{NET_IPV4, "ipv4", NULL, 0, 0555, devinet_sysctl.devinet_conf_dir},{0}},
+ {{CTL_NET, "net", NULL, 0, 0555, devinet_sysctl.devinet_proto_dir},{0}}
+};
+
+static void devinet_sysctl_register(struct in_device *in_dev, struct ipv4_devconf *p)
+{
+ int i;
+ struct net_device *dev = in_dev ? in_dev->dev : NULL;
+ struct devinet_sysctl_table *t;
+
+ t = kmalloc(sizeof(*t), GFP_KERNEL);
+ if (t == NULL)
+ return;
+ memcpy(t, &devinet_sysctl, sizeof(*t));
+ for (i=0; i<sizeof(t->devinet_vars)/sizeof(t->devinet_vars[0])-1; i++) {
+ t->devinet_vars[i].data += (char*)p - (char*)&ipv4_devconf;
+ t->devinet_vars[i].de = NULL;
+ }
+ if (dev) {
+ t->devinet_dev[0].procname = dev->name;
+ t->devinet_dev[0].ctl_name = dev->ifindex;
+ } else {
+ t->devinet_dev[0].procname = "default";
+ t->devinet_dev[0].ctl_name = NET_PROTO_CONF_DEFAULT;
+ }
+ t->devinet_dev[0].child = t->devinet_vars;
+ t->devinet_dev[0].de = NULL;
+ t->devinet_conf_dir[0].child = t->devinet_dev;
+ t->devinet_conf_dir[0].de = NULL;
+ t->devinet_proto_dir[0].child = t->devinet_conf_dir;
+ t->devinet_proto_dir[0].de = NULL;
+ t->devinet_root_dir[0].child = t->devinet_proto_dir;
+ t->devinet_root_dir[0].de = NULL;
+
+ t->sysctl_header = register_sysctl_table(t->devinet_root_dir, 0);
+ if (t->sysctl_header == NULL)
+ kfree(t);
+ else
+ p->sysctl = t;
+}
+
+static void devinet_sysctl_unregister(struct ipv4_devconf *p)
+{
+ if (p->sysctl) {
+ struct devinet_sysctl_table *t = p->sysctl;
+ p->sysctl = NULL;
+ unregister_sysctl_table(t->sysctl_header);
+ kfree(t);
+ }
+}
+#endif
+
+void __init devinet_init(void)
+{
+ register_gifconf(PF_INET, inet_gifconf);
+ register_netdevice_notifier(&ip_netdev_notifier);
+ rtnetlink_links[PF_INET] = inet_rtnetlink_table;
+#ifdef CONFIG_SYSCTL
+ devinet_sysctl.sysctl_header =
+ register_sysctl_table(devinet_sysctl.devinet_root_dir, 0);
+ devinet_sysctl_register(NULL, &ipv4_devconf_dflt);
+#endif
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/fib_frontend.c b/uClinux-2.4.31-uc0/net/ipv4/fib_frontend.c
new file mode 100644
index 0000000..069c17a
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/fib_frontend.c
@@ -0,0 +1,660 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * IPv4 Forwarding Information Base: FIB frontend.
+ *
+ * Version: $Id: fib_frontend.c,v 1.26 2001/10/31 21:55:54 davem Exp $
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/errno.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/proc_fs.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/init.h>
+
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <net/route.h>
+#include <net/tcp.h>
+#include <net/sock.h>
+#include <net/icmp.h>
+#include <net/arp.h>
+#include <net/ip_fib.h>
+
+#define FFprint(a...) printk(KERN_DEBUG a)
+
+#ifndef CONFIG_IP_MULTIPLE_TABLES
+
+#define RT_TABLE_MIN RT_TABLE_MAIN
+
+struct fib_table *local_table;
+struct fib_table *main_table;
+
+#else
+
+#define RT_TABLE_MIN 1
+
+struct fib_table *fib_tables[RT_TABLE_MAX+1];
+
+struct fib_table *__fib_new_table(int id)
+{
+ struct fib_table *tb;
+
+ tb = fib_hash_init(id);
+ if (!tb)
+ return NULL;
+ fib_tables[id] = tb;
+ return tb;
+}
+
+
+#endif /* CONFIG_IP_MULTIPLE_TABLES */
+
+
+void fib_flush(void)
+{
+ int flushed = 0;
+#ifdef CONFIG_IP_MULTIPLE_TABLES
+ struct fib_table *tb;
+ int id;
+
+ for (id = RT_TABLE_MAX; id>0; id--) {
+ if ((tb = fib_get_table(id))==NULL)
+ continue;
+ flushed += tb->tb_flush(tb);
+ }
+#else /* CONFIG_IP_MULTIPLE_TABLES */
+ flushed += main_table->tb_flush(main_table);
+ flushed += local_table->tb_flush(local_table);
+#endif /* CONFIG_IP_MULTIPLE_TABLES */
+
+ if (flushed)
+ rt_cache_flush(-1);
+}
+
+
+#ifdef CONFIG_PROC_FS
+
+/*
+ * Called from the PROCfs module. This outputs /proc/net/route.
+ *
+ * It always works in backward compatibility mode.
+ * The format of the file is not supposed to be changed.
+ */
+
+static int
+fib_get_procinfo(char *buffer, char **start, off_t offset, int length)
+{
+ int first = offset/128;
+ char *ptr = buffer;
+ int count = (length+127)/128;
+ int len;
+
+ *start = buffer + offset%128;
+
+ if (--first < 0) {
+ sprintf(buffer, "%-127s\n", "Iface\tDestination\tGateway \tFlags\tRefCnt\tUse\tMetric\tMask\t\tMTU\tWindow\tIRTT");
+ --count;
+ ptr += 128;
+ first = 0;
+ }
+
+ if (main_table && count > 0) {
+ int n = main_table->tb_get_info(main_table, ptr, first, count);
+ count -= n;
+ ptr += n*128;
+ }
+ len = ptr - *start;
+ if (len >= length)
+ return length;
+ if (len >= 0)
+ return len;
+ return 0;
+}
+
+#endif /* CONFIG_PROC_FS */
+
+/*
+ * Find the first device with a given source address.
+ */
+
+struct net_device * ip_dev_find(u32 addr)
+{
+ struct rt_key key;
+ struct fib_result res;
+ struct net_device *dev = NULL;
+
+ memset(&key, 0, sizeof(key));
+ key.dst = addr;
+#ifdef CONFIG_IP_MULTIPLE_TABLES
+ res.r = NULL;
+#endif
+
+ if (!local_table || local_table->tb_lookup(local_table, &key, &res)) {
+ return NULL;
+ }
+ if (res.type != RTN_LOCAL)
+ goto out;
+ dev = FIB_RES_DEV(res);
+
+ if (dev)
+ dev_hold(dev);
+out:
+ fib_res_put(&res);
+ return dev;
+}
+
+unsigned inet_addr_type(u32 addr)
+{
+ struct rt_key key;
+ struct fib_result res;
+ unsigned ret = RTN_BROADCAST;
+
+ if (ZERONET(addr) || BADCLASS(addr))
+ return RTN_BROADCAST;
+ if (MULTICAST(addr))
+ return RTN_MULTICAST;
+
+ memset(&key, 0, sizeof(key));
+ key.dst = addr;
+#ifdef CONFIG_IP_MULTIPLE_TABLES
+ res.r = NULL;
+#endif
+
+ if (local_table) {
+ ret = RTN_UNICAST;
+ if (local_table->tb_lookup(local_table, &key, &res) == 0) {
+ ret = res.type;
+ fib_res_put(&res);
+ }
+ }
+ return ret;
+}
+
+/* Given (packet source, input interface) and optional (dst, oif, tos):
+ - (main) check, that source is valid i.e. not broadcast or our local
+ address.
+ - figure out what "logical" interface this packet arrived
+ and calculate "specific destination" address.
+ - check, that packet arrived from expected physical interface.
+ */
+
+int fib_validate_source(u32 src, u32 dst, u8 tos, int oif,
+ struct net_device *dev, u32 *spec_dst, u32 *itag)
+{
+ struct in_device *in_dev;
+ struct rt_key key;
+ struct fib_result res;
+ int no_addr, rpf;
+ int ret;
+
+ key.dst = src;
+ key.src = dst;
+ key.tos = tos;
+ key.oif = 0;
+ key.iif = oif;
+ key.scope = RT_SCOPE_UNIVERSE;
+
+ no_addr = rpf = 0;
+ read_lock(&inetdev_lock);
+ in_dev = __in_dev_get(dev);
+ if (in_dev) {
+ no_addr = in_dev->ifa_list == NULL;
+ rpf = IN_DEV_RPFILTER(in_dev);
+ }
+ read_unlock(&inetdev_lock);
+
+ if (in_dev == NULL)
+ goto e_inval;
+
+ if (fib_lookup(&key, &res))
+ goto last_resort;
+ if (res.type != RTN_UNICAST)
+ goto e_inval_res;
+ *spec_dst = FIB_RES_PREFSRC(res);
+ fib_combine_itag(itag, &res);
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ if (FIB_RES_DEV(res) == dev || res.fi->fib_nhs > 1)
+#else
+ if (FIB_RES_DEV(res) == dev)
+#endif
+ {
+ ret = FIB_RES_NH(res).nh_scope >= RT_SCOPE_HOST;
+ fib_res_put(&res);
+ return ret;
+ }
+ fib_res_put(&res);
+ if (no_addr)
+ goto last_resort;
+ if (rpf)
+ goto e_inval;
+ key.oif = dev->ifindex;
+
+ ret = 0;
+ if (fib_lookup(&key, &res) == 0) {
+ if (res.type == RTN_UNICAST) {
+ *spec_dst = FIB_RES_PREFSRC(res);
+ ret = FIB_RES_NH(res).nh_scope >= RT_SCOPE_HOST;
+ }
+ fib_res_put(&res);
+ }
+ return ret;
+
+last_resort:
+ if (rpf)
+ goto e_inval;
+ *spec_dst = inet_select_addr(dev, 0, RT_SCOPE_UNIVERSE);
+ *itag = 0;
+ return 0;
+
+e_inval_res:
+ fib_res_put(&res);
+e_inval:
+ return -EINVAL;
+}
+
+#ifndef CONFIG_IP_NOSIOCRT
+
+/*
+ * Handle IP routing ioctl calls. These are used to manipulate the routing tables
+ */
+
+int ip_rt_ioctl(unsigned int cmd, void *arg)
+{
+ int err;
+ struct kern_rta rta;
+ struct rtentry r;
+ struct {
+ struct nlmsghdr nlh;
+ struct rtmsg rtm;
+ } req;
+
+ switch (cmd) {
+ case SIOCADDRT: /* Add a route */
+ case SIOCDELRT: /* Delete a route */
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ if (copy_from_user(&r, arg, sizeof(struct rtentry)))
+ return -EFAULT;
+ rtnl_lock();
+ err = fib_convert_rtentry(cmd, &req.nlh, &req.rtm, &rta, &r);
+ if (err == 0) {
+ if (cmd == SIOCDELRT) {
+ struct fib_table *tb = fib_get_table(req.rtm.rtm_table);
+ err = -ESRCH;
+ if (tb)
+ err = tb->tb_delete(tb, &req.rtm, &rta, &req.nlh, NULL);
+ } else {
+ struct fib_table *tb = fib_new_table(req.rtm.rtm_table);
+ err = -ENOBUFS;
+ if (tb)
+ err = tb->tb_insert(tb, &req.rtm, &rta, &req.nlh, NULL);
+ }
+ if (rta.rta_mx)
+ kfree(rta.rta_mx);
+ }
+ rtnl_unlock();
+ return err;
+ }
+ return -EINVAL;
+}
+
+#else
+
+int ip_rt_ioctl(unsigned int cmd, void *arg)
+{
+ return -EINVAL;
+}
+
+#endif
+
+static int inet_check_attr(struct rtmsg *r, struct rtattr **rta)
+{
+ int i;
+
+ for (i=1; i<=RTA_MAX; i++) {
+ struct rtattr *attr = rta[i-1];
+ if (attr) {
+ if (RTA_PAYLOAD(attr) < 4)
+ return -EINVAL;
+ if (i != RTA_MULTIPATH && i != RTA_METRICS)
+ rta[i-1] = (struct rtattr*)RTA_DATA(attr);
+ }
+ }
+ return 0;
+}
+
+int inet_rtm_delroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
+{
+ struct fib_table * tb;
+ struct rtattr **rta = arg;
+ struct rtmsg *r = NLMSG_DATA(nlh);
+
+ if (inet_check_attr(r, rta))
+ return -EINVAL;
+
+ tb = fib_get_table(r->rtm_table);
+ if (tb)
+ return tb->tb_delete(tb, r, (struct kern_rta*)rta, nlh, &NETLINK_CB(skb));
+ return -ESRCH;
+}
+
+int inet_rtm_newroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
+{
+ struct fib_table * tb;
+ struct rtattr **rta = arg;
+ struct rtmsg *r = NLMSG_DATA(nlh);
+
+ if (inet_check_attr(r, rta))
+ return -EINVAL;
+
+ tb = fib_new_table(r->rtm_table);
+ if (tb)
+ return tb->tb_insert(tb, r, (struct kern_rta*)rta, nlh, &NETLINK_CB(skb));
+ return -ENOBUFS;
+}
+
+int inet_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int t;
+ int s_t;
+ struct fib_table *tb;
+
+ if (NLMSG_PAYLOAD(cb->nlh, 0) >= sizeof(struct rtmsg) &&
+ ((struct rtmsg*)NLMSG_DATA(cb->nlh))->rtm_flags&RTM_F_CLONED)
+ return ip_rt_dump(skb, cb);
+
+ s_t = cb->args[0];
+ if (s_t == 0)
+ s_t = cb->args[0] = RT_TABLE_MIN;
+
+ for (t=s_t; t<=RT_TABLE_MAX; t++) {
+ if (t < s_t) continue;
+ if (t > s_t)
+ memset(&cb->args[1], 0, sizeof(cb->args)-sizeof(cb->args[0]));
+ if ((tb = fib_get_table(t))==NULL)
+ continue;
+ if (tb->tb_dump(tb, skb, cb) < 0)
+ break;
+ }
+
+ cb->args[0] = t;
+
+ return skb->len;
+}
+
+/* Prepare and feed intra-kernel routing request.
+ Really, it should be netlink message, but :-( netlink
+ can be not configured, so that we feed it directly
+ to fib engine. It is legal, because all events occur
+ only when netlink is already locked.
+ */
+
+static void fib_magic(int cmd, int type, u32 dst, int dst_len, struct in_ifaddr *ifa)
+{
+ struct fib_table * tb;
+ struct {
+ struct nlmsghdr nlh;
+ struct rtmsg rtm;
+ } req;
+ struct kern_rta rta;
+
+ memset(&req.rtm, 0, sizeof(req.rtm));
+ memset(&rta, 0, sizeof(rta));
+
+ if (type == RTN_UNICAST)
+ tb = fib_new_table(RT_TABLE_MAIN);
+ else
+ tb = fib_new_table(RT_TABLE_LOCAL);
+
+ if (tb == NULL)
+ return;
+
+ req.nlh.nlmsg_len = sizeof(req);
+ req.nlh.nlmsg_type = cmd;
+ req.nlh.nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE|NLM_F_APPEND;
+ req.nlh.nlmsg_pid = 0;
+ req.nlh.nlmsg_seq = 0;
+
+ req.rtm.rtm_dst_len = dst_len;
+ req.rtm.rtm_table = tb->tb_id;
+ req.rtm.rtm_protocol = RTPROT_KERNEL;
+ req.rtm.rtm_scope = (type != RTN_LOCAL ? RT_SCOPE_LINK : RT_SCOPE_HOST);
+ req.rtm.rtm_type = type;
+
+ rta.rta_dst = &dst;
+ rta.rta_prefsrc = &ifa->ifa_local;
+ rta.rta_oif = &ifa->ifa_dev->dev->ifindex;
+
+ if (cmd == RTM_NEWROUTE)
+ tb->tb_insert(tb, &req.rtm, &rta, &req.nlh, NULL);
+ else
+ tb->tb_delete(tb, &req.rtm, &rta, &req.nlh, NULL);
+}
+
+static void fib_add_ifaddr(struct in_ifaddr *ifa)
+{
+ struct in_device *in_dev = ifa->ifa_dev;
+ struct net_device *dev = in_dev->dev;
+ struct in_ifaddr *prim = ifa;
+ u32 mask = ifa->ifa_mask;
+ u32 addr = ifa->ifa_local;
+ u32 prefix = ifa->ifa_address&mask;
+
+ if (ifa->ifa_flags&IFA_F_SECONDARY) {
+ prim = inet_ifa_byprefix(in_dev, prefix, mask);
+ if (prim == NULL) {
+ printk(KERN_DEBUG "fib_add_ifaddr: bug: prim == NULL\n");
+ return;
+ }
+ }
+
+ fib_magic(RTM_NEWROUTE, RTN_LOCAL, addr, 32, prim);
+
+ if (!(dev->flags&IFF_UP))
+ return;
+
+ /* Add broadcast address, if it is explicitly assigned. */
+ if (ifa->ifa_broadcast && ifa->ifa_broadcast != 0xFFFFFFFF)
+ fib_magic(RTM_NEWROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32, prim);
+
+ if (!ZERONET(prefix) && !(ifa->ifa_flags&IFA_F_SECONDARY) &&
+ (prefix != addr || ifa->ifa_prefixlen < 32)) {
+ fib_magic(RTM_NEWROUTE, dev->flags&IFF_LOOPBACK ? RTN_LOCAL :
+ RTN_UNICAST, prefix, ifa->ifa_prefixlen, prim);
+
+ /* Add network specific broadcasts, when it takes a sense */
+ if (ifa->ifa_prefixlen < 31) {
+ fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix, 32, prim);
+ fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix|~mask, 32, prim);
+ }
+ }
+}
+
+static void fib_del_ifaddr(struct in_ifaddr *ifa)
+{
+ struct in_device *in_dev = ifa->ifa_dev;
+ struct net_device *dev = in_dev->dev;
+ struct in_ifaddr *ifa1;
+ struct in_ifaddr *prim = ifa;
+ u32 brd = ifa->ifa_address|~ifa->ifa_mask;
+ u32 any = ifa->ifa_address&ifa->ifa_mask;
+#define LOCAL_OK 1
+#define BRD_OK 2
+#define BRD0_OK 4
+#define BRD1_OK 8
+ unsigned ok = 0;
+
+ if (!(ifa->ifa_flags&IFA_F_SECONDARY))
+ fib_magic(RTM_DELROUTE, dev->flags&IFF_LOOPBACK ? RTN_LOCAL :
+ RTN_UNICAST, any, ifa->ifa_prefixlen, prim);
+ else {
+ prim = inet_ifa_byprefix(in_dev, any, ifa->ifa_mask);
+ if (prim == NULL) {
+ printk(KERN_DEBUG "fib_del_ifaddr: bug: prim == NULL\n");
+ return;
+ }
+ }
+
+ /* Deletion is more complicated than add.
+ We should take care of not to delete too much :-)
+
+ Scan address list to be sure that addresses are really gone.
+ */
+
+ for (ifa1 = in_dev->ifa_list; ifa1; ifa1 = ifa1->ifa_next) {
+ if (ifa->ifa_local == ifa1->ifa_local)
+ ok |= LOCAL_OK;
+ if (ifa->ifa_broadcast == ifa1->ifa_broadcast)
+ ok |= BRD_OK;
+ if (brd == ifa1->ifa_broadcast)
+ ok |= BRD1_OK;
+ if (any == ifa1->ifa_broadcast)
+ ok |= BRD0_OK;
+ }
+
+ if (!(ok&BRD_OK))
+ fib_magic(RTM_DELROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32, prim);
+ if (!(ok&BRD1_OK))
+ fib_magic(RTM_DELROUTE, RTN_BROADCAST, brd, 32, prim);
+ if (!(ok&BRD0_OK))
+ fib_magic(RTM_DELROUTE, RTN_BROADCAST, any, 32, prim);
+ if (!(ok&LOCAL_OK)) {
+ fib_magic(RTM_DELROUTE, RTN_LOCAL, ifa->ifa_local, 32, prim);
+
+ /* Check, that this local address finally disappeared. */
+ if (inet_addr_type(ifa->ifa_local) != RTN_LOCAL) {
+ /* And the last, but not the least thing.
+ We must flush stray FIB entries.
+
+ First of all, we scan fib_info list searching
+ for stray nexthop entries, then ignite fib_flush.
+ */
+ if (fib_sync_down(ifa->ifa_local, NULL, 0))
+ fib_flush();
+ }
+ }
+#undef LOCAL_OK
+#undef BRD_OK
+#undef BRD0_OK
+#undef BRD1_OK
+}
+
+static void fib_disable_ip(struct net_device *dev, int force)
+{
+ if (fib_sync_down(0, dev, force))
+ fib_flush();
+ rt_cache_flush(0);
+ arp_ifdown(dev);
+}
+
+static int fib_inetaddr_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct in_ifaddr *ifa = (struct in_ifaddr*)ptr;
+
+ switch (event) {
+ case NETDEV_UP:
+ fib_add_ifaddr(ifa);
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ fib_sync_up(ifa->ifa_dev->dev);
+#endif
+ rt_cache_flush(-1);
+ break;
+ case NETDEV_DOWN:
+ fib_del_ifaddr(ifa);
+ if (ifa->ifa_dev && ifa->ifa_dev->ifa_list == NULL) {
+ /* Last address was deleted from this interface.
+ Disable IP.
+ */
+ fib_disable_ip(ifa->ifa_dev->dev, 1);
+ } else {
+ rt_cache_flush(-1);
+ }
+ break;
+ }
+ return NOTIFY_DONE;
+}
+
+static int fib_netdev_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct net_device *dev = ptr;
+ struct in_device *in_dev = __in_dev_get(dev);
+
+ if (event == NETDEV_UNREGISTER) {
+ fib_disable_ip(dev, 2);
+ return NOTIFY_DONE;
+ }
+
+ if (!in_dev)
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case NETDEV_UP:
+ for_ifa(in_dev) {
+ fib_add_ifaddr(ifa);
+ } endfor_ifa(in_dev);
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ fib_sync_up(dev);
+#endif
+ rt_cache_flush(-1);
+ break;
+ case NETDEV_DOWN:
+ fib_disable_ip(dev, 0);
+ break;
+ case NETDEV_CHANGEMTU:
+ case NETDEV_CHANGE:
+ rt_cache_flush(0);
+ break;
+ }
+ return NOTIFY_DONE;
+}
+
+struct notifier_block fib_inetaddr_notifier = {
+ notifier_call: fib_inetaddr_event,
+};
+
+struct notifier_block fib_netdev_notifier = {
+ notifier_call: fib_netdev_event,
+};
+
+void __init ip_fib_init(void)
+{
+#ifdef CONFIG_PROC_FS
+ proc_net_create("route",0,fib_get_procinfo);
+#endif /* CONFIG_PROC_FS */
+
+#ifndef CONFIG_IP_MULTIPLE_TABLES
+ local_table = fib_hash_init(RT_TABLE_LOCAL);
+ main_table = fib_hash_init(RT_TABLE_MAIN);
+#else
+ fib_rules_init();
+#endif
+
+ register_netdevice_notifier(&fib_netdev_notifier);
+ register_inetaddr_notifier(&fib_inetaddr_notifier);
+}
+
diff --git a/uClinux-2.4.31-uc0/net/ipv4/fib_hash.c b/uClinux-2.4.31-uc0/net/ipv4/fib_hash.c
new file mode 100644
index 0000000..ef6dad0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/fib_hash.c
@@ -0,0 +1,942 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * IPv4 FIB: lookup engine and maintenance routines.
+ *
+ * Version: $Id: fib_hash.c,v 1.13 2001/10/31 21:55:54 davem Exp $
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/errno.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/proc_fs.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/init.h>
+
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <net/route.h>
+#include <net/tcp.h>
+#include <net/sock.h>
+#include <net/ip_fib.h>
+
+#define FTprint(a...)
+/*
+ printk(KERN_DEBUG a)
+ */
+
+static kmem_cache_t * fn_hash_kmem;
+
+/*
+ These bizarre types are just to force strict type checking.
+ When I reversed order of bytes and changed to natural mask lengths,
+ I forgot to make fixes in several places. Now I am lazy to return
+ it back.
+ */
+
+typedef struct {
+ u32 datum;
+} fn_key_t;
+
+typedef struct {
+ u32 datum;
+} fn_hash_idx_t;
+
+struct fib_node
+{
+ struct fib_node *fn_next;
+ struct fib_info *fn_info;
+#define FIB_INFO(f) ((f)->fn_info)
+ fn_key_t fn_key;
+ u8 fn_tos;
+ u8 fn_type;
+ u8 fn_scope;
+ u8 fn_state;
+};
+
+#define FN_S_ZOMBIE 1
+#define FN_S_ACCESSED 2
+
+static int fib_hash_zombies;
+
+struct fn_zone
+{
+ struct fn_zone *fz_next; /* Next not empty zone */
+ struct fib_node **fz_hash; /* Hash table pointer */
+ int fz_nent; /* Number of entries */
+
+ int fz_divisor; /* Hash divisor */
+ u32 fz_hashmask; /* (fz_divisor - 1) */
+#define FZ_HASHMASK(fz) ((fz)->fz_hashmask)
+
+ int fz_order; /* Zone order */
+ u32 fz_mask;
+#define FZ_MASK(fz) ((fz)->fz_mask)
+};
+
+/* NOTE. On fast computers evaluation of fz_hashmask and fz_mask
+ can be cheaper than memory lookup, so that FZ_* macros are used.
+ */
+
+struct fn_hash
+{
+ struct fn_zone *fn_zones[33];
+ struct fn_zone *fn_zone_list;
+};
+
+static __inline__ fn_hash_idx_t fn_hash(fn_key_t key, struct fn_zone *fz)
+{
+ u32 h = ntohl(key.datum)>>(32 - fz->fz_order);
+ h ^= (h>>20);
+ h ^= (h>>10);
+ h ^= (h>>5);
+ h &= FZ_HASHMASK(fz);
+ return *(fn_hash_idx_t*)&h;
+}
+
+#define fz_key_0(key) ((key).datum = 0)
+#define fz_prefix(key,fz) ((key).datum)
+
+static __inline__ fn_key_t fz_key(u32 dst, struct fn_zone *fz)
+{
+ fn_key_t k;
+ k.datum = dst & FZ_MASK(fz);
+ return k;
+}
+
+static __inline__ struct fib_node ** fz_chain_p(fn_key_t key, struct fn_zone *fz)
+{
+ return &fz->fz_hash[fn_hash(key, fz).datum];
+}
+
+static __inline__ struct fib_node * fz_chain(fn_key_t key, struct fn_zone *fz)
+{
+ return fz->fz_hash[fn_hash(key, fz).datum];
+}
+
+extern __inline__ int fn_key_eq(fn_key_t a, fn_key_t b)
+{
+ return a.datum == b.datum;
+}
+
+extern __inline__ int fn_key_leq(fn_key_t a, fn_key_t b)
+{
+ return a.datum <= b.datum;
+}
+
+static rwlock_t fib_hash_lock = RW_LOCK_UNLOCKED;
+
+#define FZ_MAX_DIVISOR ((PAGE_SIZE<<MAX_ORDER) / sizeof(struct fib_node *))
+
+static struct fib_node **fz_hash_alloc(int divisor)
+{
+ unsigned long size = divisor * sizeof(struct fib_node *);
+
+ if (divisor <= 1024) {
+ return kmalloc(size, GFP_KERNEL);
+ } else {
+ return (struct fib_node **)
+ __get_free_pages(GFP_KERNEL, get_order(size));
+ }
+}
+
+/* The fib hash lock must be held when this is called. */
+static __inline__ void fn_rebuild_zone(struct fn_zone *fz,
+ struct fib_node **old_ht,
+ int old_divisor)
+{
+ int i;
+ struct fib_node *f, **fp, *next;
+
+ for (i=0; i<old_divisor; i++) {
+ for (f=old_ht[i]; f; f=next) {
+ next = f->fn_next;
+ for (fp = fz_chain_p(f->fn_key, fz);
+ *fp && fn_key_leq((*fp)->fn_key, f->fn_key);
+ fp = &(*fp)->fn_next)
+ /* NONE */;
+ f->fn_next = *fp;
+ *fp = f;
+ }
+ }
+}
+
+static void fz_hash_free(struct fib_node **hash, int divisor)
+{
+ if (divisor <= 1024)
+ kfree(hash);
+ else
+ free_pages((unsigned long) hash,
+ get_order(divisor * sizeof(struct fib_node *)));
+}
+
+static void fn_rehash_zone(struct fn_zone *fz)
+{
+ struct fib_node **ht, **old_ht;
+ int old_divisor, new_divisor;
+ u32 new_hashmask;
+
+ old_divisor = fz->fz_divisor;
+
+ switch (old_divisor) {
+ case 16:
+ new_divisor = 256;
+ break;
+ case 256:
+ new_divisor = 1024;
+ break;
+ default:
+ if ((old_divisor << 1) > FZ_MAX_DIVISOR) {
+ printk(KERN_CRIT "route.c: bad divisor %d!\n", old_divisor);
+ return;
+ }
+ new_divisor = (old_divisor << 1);
+ break;
+ }
+
+ new_hashmask = (new_divisor - 1);
+
+#if RT_CACHE_DEBUG >= 2
+ printk("fn_rehash_zone: hash for zone %d grows from %d\n", fz->fz_order, old_divisor);
+#endif
+
+ ht = fz_hash_alloc(new_divisor);
+
+ if (ht) {
+ memset(ht, 0, new_divisor*sizeof(struct fib_node*));
+
+ write_lock_bh(&fib_hash_lock);
+ old_ht = fz->fz_hash;
+ fz->fz_hash = ht;
+ fz->fz_hashmask = new_hashmask;
+ fz->fz_divisor = new_divisor;
+ fn_rebuild_zone(fz, old_ht, old_divisor);
+ write_unlock_bh(&fib_hash_lock);
+
+ fz_hash_free(old_ht, old_divisor);
+ }
+}
+
+static void fn_free_node(struct fib_node * f)
+{
+ fib_release_info(FIB_INFO(f));
+ kmem_cache_free(fn_hash_kmem, f);
+}
+
+
+static struct fn_zone *
+fn_new_zone(struct fn_hash *table, int z)
+{
+ int i;
+ struct fn_zone *fz = kmalloc(sizeof(struct fn_zone), GFP_KERNEL);
+ if (!fz)
+ return NULL;
+
+ memset(fz, 0, sizeof(struct fn_zone));
+ if (z) {
+ fz->fz_divisor = 16;
+ } else {
+ fz->fz_divisor = 1;
+ }
+ fz->fz_hashmask = (fz->fz_divisor - 1);
+ fz->fz_hash = fz_hash_alloc(fz->fz_divisor);
+ if (!fz->fz_hash) {
+ kfree(fz);
+ return NULL;
+ }
+ memset(fz->fz_hash, 0, fz->fz_divisor*sizeof(struct fib_node*));
+ fz->fz_order = z;
+ fz->fz_mask = inet_make_mask(z);
+
+ /* Find the first not empty zone with more specific mask */
+ for (i=z+1; i<=32; i++)
+ if (table->fn_zones[i])
+ break;
+ write_lock_bh(&fib_hash_lock);
+ if (i>32) {
+ /* No more specific masks, we are the first. */
+ fz->fz_next = table->fn_zone_list;
+ table->fn_zone_list = fz;
+ } else {
+ fz->fz_next = table->fn_zones[i]->fz_next;
+ table->fn_zones[i]->fz_next = fz;
+ }
+ table->fn_zones[z] = fz;
+ write_unlock_bh(&fib_hash_lock);
+ return fz;
+}
+
+static int
+fn_hash_lookup(struct fib_table *tb, const struct rt_key *key, struct fib_result *res)
+{
+ int err;
+ struct fn_zone *fz;
+ struct fn_hash *t = (struct fn_hash*)tb->tb_data;
+
+ read_lock(&fib_hash_lock);
+ for (fz = t->fn_zone_list; fz; fz = fz->fz_next) {
+ struct fib_node *f;
+ fn_key_t k = fz_key(key->dst, fz);
+
+ for (f = fz_chain(k, fz); f; f = f->fn_next) {
+ if (!fn_key_eq(k, f->fn_key)) {
+ if (fn_key_leq(k, f->fn_key))
+ break;
+ else
+ continue;
+ }
+#ifdef CONFIG_IP_ROUTE_TOS
+ if (f->fn_tos && f->fn_tos != key->tos)
+ continue;
+#endif
+ f->fn_state |= FN_S_ACCESSED;
+
+ if (f->fn_state&FN_S_ZOMBIE)
+ continue;
+ if (f->fn_scope < key->scope)
+ continue;
+
+ err = fib_semantic_match(f->fn_type, FIB_INFO(f), key, res);
+ if (err == 0) {
+ res->type = f->fn_type;
+ res->scope = f->fn_scope;
+ res->prefixlen = fz->fz_order;
+ goto out;
+ }
+ if (err < 0)
+ goto out;
+ }
+ }
+ err = 1;
+out:
+ read_unlock(&fib_hash_lock);
+ return err;
+}
+
+static int fn_hash_last_dflt=-1;
+
+static int fib_detect_death(struct fib_info *fi, int order,
+ struct fib_info **last_resort, int *last_idx)
+{
+ struct neighbour *n;
+ int state = NUD_NONE;
+
+ n = neigh_lookup(&arp_tbl, &fi->fib_nh[0].nh_gw, fi->fib_dev);
+ if (n) {
+ state = n->nud_state;
+ neigh_release(n);
+ }
+ if (state==NUD_REACHABLE)
+ return 0;
+ if ((state&NUD_VALID) && order != fn_hash_last_dflt)
+ return 0;
+ if ((state&NUD_VALID) ||
+ (*last_idx<0 && order > fn_hash_last_dflt)) {
+ *last_resort = fi;
+ *last_idx = order;
+ }
+ return 1;
+}
+
+static void
+fn_hash_select_default(struct fib_table *tb, const struct rt_key *key, struct fib_result *res)
+{
+ int order, last_idx;
+ struct fib_node *f;
+ struct fib_info *fi = NULL;
+ struct fib_info *last_resort;
+ struct fn_hash *t = (struct fn_hash*)tb->tb_data;
+ struct fn_zone *fz = t->fn_zones[0];
+
+ if (fz == NULL)
+ return;
+
+ last_idx = -1;
+ last_resort = NULL;
+ order = -1;
+
+ read_lock(&fib_hash_lock);
+ for (f = fz->fz_hash[0]; f; f = f->fn_next) {
+ struct fib_info *next_fi = FIB_INFO(f);
+
+ if ((f->fn_state&FN_S_ZOMBIE) ||
+ f->fn_scope != res->scope ||
+ f->fn_type != RTN_UNICAST)
+ continue;
+
+ if (next_fi->fib_priority > res->fi->fib_priority)
+ break;
+ if (!next_fi->fib_nh[0].nh_gw || next_fi->fib_nh[0].nh_scope != RT_SCOPE_LINK)
+ continue;
+ f->fn_state |= FN_S_ACCESSED;
+
+ if (fi == NULL) {
+ if (next_fi != res->fi)
+ break;
+ } else if (!fib_detect_death(fi, order, &last_resort, &last_idx)) {
+ if (res->fi)
+ fib_info_put(res->fi);
+ res->fi = fi;
+ atomic_inc(&fi->fib_clntref);
+ fn_hash_last_dflt = order;
+ goto out;
+ }
+ fi = next_fi;
+ order++;
+ }
+
+ if (order<=0 || fi==NULL) {
+ fn_hash_last_dflt = -1;
+ goto out;
+ }
+
+ if (!fib_detect_death(fi, order, &last_resort, &last_idx)) {
+ if (res->fi)
+ fib_info_put(res->fi);
+ res->fi = fi;
+ atomic_inc(&fi->fib_clntref);
+ fn_hash_last_dflt = order;
+ goto out;
+ }
+
+ if (last_idx >= 0) {
+ if (res->fi)
+ fib_info_put(res->fi);
+ res->fi = last_resort;
+ if (last_resort)
+ atomic_inc(&last_resort->fib_clntref);
+ }
+ fn_hash_last_dflt = last_idx;
+out:
+ read_unlock(&fib_hash_lock);
+}
+
+#define FIB_SCAN(f, fp) \
+for ( ; ((f) = *(fp)) != NULL; (fp) = &(f)->fn_next)
+
+#define FIB_SCAN_KEY(f, fp, key) \
+for ( ; ((f) = *(fp)) != NULL && fn_key_eq((f)->fn_key, (key)); (fp) = &(f)->fn_next)
+
+#ifndef CONFIG_IP_ROUTE_TOS
+#define FIB_SCAN_TOS(f, fp, key, tos) FIB_SCAN_KEY(f, fp, key)
+#else
+#define FIB_SCAN_TOS(f, fp, key, tos) \
+for ( ; ((f) = *(fp)) != NULL && fn_key_eq((f)->fn_key, (key)) && \
+ (f)->fn_tos == (tos) ; (fp) = &(f)->fn_next)
+#endif
+
+
+static void rtmsg_fib(int, struct fib_node*, int, int,
+ struct nlmsghdr *n,
+ struct netlink_skb_parms *);
+
+static int
+fn_hash_insert(struct fib_table *tb, struct rtmsg *r, struct kern_rta *rta,
+ struct nlmsghdr *n, struct netlink_skb_parms *req)
+{
+ struct fn_hash *table = (struct fn_hash*)tb->tb_data;
+ struct fib_node *new_f, *f, **fp, **del_fp;
+ struct fn_zone *fz;
+ struct fib_info *fi;
+
+ int z = r->rtm_dst_len;
+ int type = r->rtm_type;
+#ifdef CONFIG_IP_ROUTE_TOS
+ u8 tos = r->rtm_tos;
+#endif
+ fn_key_t key;
+ int err;
+
+FTprint("tb(%d)_insert: %d %08x/%d %d %08x\n", tb->tb_id, r->rtm_type, rta->rta_dst ?
+*(u32*)rta->rta_dst : 0, z, rta->rta_oif ? *rta->rta_oif : -1,
+rta->rta_prefsrc ? *(u32*)rta->rta_prefsrc : 0);
+ if (z > 32)
+ return -EINVAL;
+ fz = table->fn_zones[z];
+ if (!fz && !(fz = fn_new_zone(table, z)))
+ return -ENOBUFS;
+
+ fz_key_0(key);
+ if (rta->rta_dst) {
+ u32 dst;
+ memcpy(&dst, rta->rta_dst, 4);
+ if (dst & ~FZ_MASK(fz))
+ return -EINVAL;
+ key = fz_key(dst, fz);
+ }
+
+ if ((fi = fib_create_info(r, rta, n, &err)) == NULL)
+ return err;
+
+ if (fz->fz_nent > (fz->fz_divisor<<1) &&
+ fz->fz_divisor < FZ_MAX_DIVISOR &&
+ (z==32 || (1<<z) > fz->fz_divisor))
+ fn_rehash_zone(fz);
+
+ fp = fz_chain_p(key, fz);
+
+
+ /*
+ * Scan list to find the first route with the same destination
+ */
+ FIB_SCAN(f, fp) {
+ if (fn_key_leq(key,f->fn_key))
+ break;
+ }
+
+#ifdef CONFIG_IP_ROUTE_TOS
+ /*
+ * Find route with the same destination and tos.
+ */
+ FIB_SCAN_KEY(f, fp, key) {
+ if (f->fn_tos <= tos)
+ break;
+ }
+#endif
+
+ del_fp = NULL;
+
+ if (f && (f->fn_state&FN_S_ZOMBIE) &&
+#ifdef CONFIG_IP_ROUTE_TOS
+ f->fn_tos == tos &&
+#endif
+ fn_key_eq(f->fn_key, key)) {
+ del_fp = fp;
+ fp = &f->fn_next;
+ f = *fp;
+ goto create;
+ }
+
+ FIB_SCAN_TOS(f, fp, key, tos) {
+ if (fi->fib_priority <= FIB_INFO(f)->fib_priority)
+ break;
+ }
+
+ /* Now f==*fp points to the first node with the same
+ keys [prefix,tos,priority], if such key already
+ exists or to the node, before which we will insert new one.
+ */
+
+ if (f &&
+#ifdef CONFIG_IP_ROUTE_TOS
+ f->fn_tos == tos &&
+#endif
+ fn_key_eq(f->fn_key, key) &&
+ fi->fib_priority == FIB_INFO(f)->fib_priority) {
+ struct fib_node **ins_fp;
+
+ err = -EEXIST;
+ if (n->nlmsg_flags&NLM_F_EXCL)
+ goto out;
+
+ if (n->nlmsg_flags&NLM_F_REPLACE) {
+ del_fp = fp;
+ fp = &f->fn_next;
+ f = *fp;
+ goto replace;
+ }
+
+ ins_fp = fp;
+ err = -EEXIST;
+
+ FIB_SCAN_TOS(f, fp, key, tos) {
+ if (fi->fib_priority != FIB_INFO(f)->fib_priority)
+ break;
+ if (f->fn_type == type && f->fn_scope == r->rtm_scope
+ && FIB_INFO(f) == fi)
+ goto out;
+ }
+
+ if (!(n->nlmsg_flags&NLM_F_APPEND)) {
+ fp = ins_fp;
+ f = *fp;
+ }
+ }
+
+create:
+ err = -ENOENT;
+ if (!(n->nlmsg_flags&NLM_F_CREATE))
+ goto out;
+
+replace:
+ err = -ENOBUFS;
+ new_f = kmem_cache_alloc(fn_hash_kmem, SLAB_KERNEL);
+ if (new_f == NULL)
+ goto out;
+
+ memset(new_f, 0, sizeof(struct fib_node));
+
+ new_f->fn_key = key;
+#ifdef CONFIG_IP_ROUTE_TOS
+ new_f->fn_tos = tos;
+#endif
+ new_f->fn_type = type;
+ new_f->fn_scope = r->rtm_scope;
+ FIB_INFO(new_f) = fi;
+
+ /*
+ * Insert new entry to the list.
+ */
+
+ new_f->fn_next = f;
+ write_lock_bh(&fib_hash_lock);
+ *fp = new_f;
+ write_unlock_bh(&fib_hash_lock);
+ fz->fz_nent++;
+
+ if (del_fp) {
+ f = *del_fp;
+ /* Unlink replaced node */
+ write_lock_bh(&fib_hash_lock);
+ *del_fp = f->fn_next;
+ write_unlock_bh(&fib_hash_lock);
+
+ if (!(f->fn_state&FN_S_ZOMBIE))
+ rtmsg_fib(RTM_DELROUTE, f, z, tb->tb_id, n, req);
+ if (f->fn_state&FN_S_ACCESSED)
+ rt_cache_flush(-1);
+ fn_free_node(f);
+ fz->fz_nent--;
+ } else {
+ rt_cache_flush(-1);
+ }
+ rtmsg_fib(RTM_NEWROUTE, new_f, z, tb->tb_id, n, req);
+ return 0;
+
+out:
+ fib_release_info(fi);
+ return err;
+}
+
+
+static int
+fn_hash_delete(struct fib_table *tb, struct rtmsg *r, struct kern_rta *rta,
+ struct nlmsghdr *n, struct netlink_skb_parms *req)
+{
+ struct fn_hash *table = (struct fn_hash*)tb->tb_data;
+ struct fib_node **fp, **del_fp, *f;
+ int z = r->rtm_dst_len;
+ struct fn_zone *fz;
+ fn_key_t key;
+ int matched;
+#ifdef CONFIG_IP_ROUTE_TOS
+ u8 tos = r->rtm_tos;
+#endif
+
+FTprint("tb(%d)_delete: %d %08x/%d %d\n", tb->tb_id, r->rtm_type, rta->rta_dst ?
+ *(u32*)rta->rta_dst : 0, z, rta->rta_oif ? *rta->rta_oif : -1);
+ if (z > 32)
+ return -EINVAL;
+ if ((fz = table->fn_zones[z]) == NULL)
+ return -ESRCH;
+
+ fz_key_0(key);
+ if (rta->rta_dst) {
+ u32 dst;
+ memcpy(&dst, rta->rta_dst, 4);
+ if (dst & ~FZ_MASK(fz))
+ return -EINVAL;
+ key = fz_key(dst, fz);
+ }
+
+ fp = fz_chain_p(key, fz);
+
+
+ FIB_SCAN(f, fp) {
+ if (fn_key_eq(f->fn_key, key))
+ break;
+ if (fn_key_leq(key, f->fn_key)) {
+ return -ESRCH;
+ }
+ }
+#ifdef CONFIG_IP_ROUTE_TOS
+ FIB_SCAN_KEY(f, fp, key) {
+ if (f->fn_tos == tos)
+ break;
+ }
+#endif
+
+ matched = 0;
+ del_fp = NULL;
+ FIB_SCAN_TOS(f, fp, key, tos) {
+ struct fib_info * fi = FIB_INFO(f);
+
+ if (f->fn_state&FN_S_ZOMBIE) {
+ return -ESRCH;
+ }
+ matched++;
+
+ if (del_fp == NULL &&
+ (!r->rtm_type || f->fn_type == r->rtm_type) &&
+ (r->rtm_scope == RT_SCOPE_NOWHERE || f->fn_scope == r->rtm_scope) &&
+ (!r->rtm_protocol || fi->fib_protocol == r->rtm_protocol) &&
+ fib_nh_match(r, n, rta, fi) == 0)
+ del_fp = fp;
+ }
+
+ if (del_fp) {
+ f = *del_fp;
+ rtmsg_fib(RTM_DELROUTE, f, z, tb->tb_id, n, req);
+
+ if (matched != 1) {
+ write_lock_bh(&fib_hash_lock);
+ *del_fp = f->fn_next;
+ write_unlock_bh(&fib_hash_lock);
+
+ if (f->fn_state&FN_S_ACCESSED)
+ rt_cache_flush(-1);
+ fn_free_node(f);
+ fz->fz_nent--;
+ } else {
+ f->fn_state |= FN_S_ZOMBIE;
+ if (f->fn_state&FN_S_ACCESSED) {
+ f->fn_state &= ~FN_S_ACCESSED;
+ rt_cache_flush(-1);
+ }
+ if (++fib_hash_zombies > 128)
+ fib_flush();
+ }
+
+ return 0;
+ }
+ return -ESRCH;
+}
+
+extern __inline__ int
+fn_flush_list(struct fib_node ** fp, int z, struct fn_hash *table)
+{
+ int found = 0;
+ struct fib_node *f;
+
+ while ((f = *fp) != NULL) {
+ struct fib_info *fi = FIB_INFO(f);
+
+ if (fi && ((f->fn_state&FN_S_ZOMBIE) || (fi->fib_flags&RTNH_F_DEAD))) {
+ write_lock_bh(&fib_hash_lock);
+ *fp = f->fn_next;
+ write_unlock_bh(&fib_hash_lock);
+
+ fn_free_node(f);
+ found++;
+ continue;
+ }
+ fp = &f->fn_next;
+ }
+ return found;
+}
+
+static int fn_hash_flush(struct fib_table *tb)
+{
+ struct fn_hash *table = (struct fn_hash*)tb->tb_data;
+ struct fn_zone *fz;
+ int found = 0;
+
+ fib_hash_zombies = 0;
+ for (fz = table->fn_zone_list; fz; fz = fz->fz_next) {
+ int i;
+ int tmp = 0;
+ for (i=fz->fz_divisor-1; i>=0; i--)
+ tmp += fn_flush_list(&fz->fz_hash[i], fz->fz_order, table);
+ fz->fz_nent -= tmp;
+ found += tmp;
+ }
+ return found;
+}
+
+
+#ifdef CONFIG_PROC_FS
+
+static int fn_hash_get_info(struct fib_table *tb, char *buffer, int first, int count)
+{
+ struct fn_hash *table = (struct fn_hash*)tb->tb_data;
+ struct fn_zone *fz;
+ int pos = 0;
+ int n = 0;
+
+ read_lock(&fib_hash_lock);
+ for (fz=table->fn_zone_list; fz; fz = fz->fz_next) {
+ int i;
+ struct fib_node *f;
+ int maxslot = fz->fz_divisor;
+ struct fib_node **fp = fz->fz_hash;
+
+ if (fz->fz_nent == 0)
+ continue;
+
+ if (pos + fz->fz_nent <= first) {
+ pos += fz->fz_nent;
+ continue;
+ }
+
+ for (i=0; i < maxslot; i++, fp++) {
+ for (f = *fp; f; f = f->fn_next) {
+ if (++pos <= first)
+ continue;
+ fib_node_get_info(f->fn_type,
+ f->fn_state&FN_S_ZOMBIE,
+ FIB_INFO(f),
+ fz_prefix(f->fn_key, fz),
+ FZ_MASK(fz), buffer);
+ buffer += 128;
+ if (++n >= count)
+ goto out;
+ }
+ }
+ }
+out:
+ read_unlock(&fib_hash_lock);
+ return n;
+}
+#endif
+
+
+static __inline__ int
+fn_hash_dump_bucket(struct sk_buff *skb, struct netlink_callback *cb,
+ struct fib_table *tb,
+ struct fn_zone *fz,
+ struct fib_node *f)
+{
+ int i, s_i;
+
+ s_i = cb->args[3];
+ for (i=0; f; i++, f=f->fn_next) {
+ if (i < s_i) continue;
+ if (f->fn_state&FN_S_ZOMBIE) continue;
+ if (fib_dump_info(skb, NETLINK_CB(cb->skb).pid, cb->nlh->nlmsg_seq,
+ RTM_NEWROUTE,
+ tb->tb_id, (f->fn_state&FN_S_ZOMBIE) ? 0 : f->fn_type, f->fn_scope,
+ &f->fn_key, fz->fz_order, f->fn_tos,
+ f->fn_info) < 0) {
+ cb->args[3] = i;
+ return -1;
+ }
+ }
+ cb->args[3] = i;
+ return skb->len;
+}
+
+static __inline__ int
+fn_hash_dump_zone(struct sk_buff *skb, struct netlink_callback *cb,
+ struct fib_table *tb,
+ struct fn_zone *fz)
+{
+ int h, s_h;
+
+ s_h = cb->args[2];
+ for (h=0; h < fz->fz_divisor; h++) {
+ if (h < s_h) continue;
+ if (h > s_h)
+ memset(&cb->args[3], 0, sizeof(cb->args) - 3*sizeof(cb->args[0]));
+ if (fz->fz_hash == NULL || fz->fz_hash[h] == NULL)
+ continue;
+ if (fn_hash_dump_bucket(skb, cb, tb, fz, fz->fz_hash[h]) < 0) {
+ cb->args[2] = h;
+ return -1;
+ }
+ }
+ cb->args[2] = h;
+ return skb->len;
+}
+
+static int fn_hash_dump(struct fib_table *tb, struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int m, s_m;
+ struct fn_zone *fz;
+ struct fn_hash *table = (struct fn_hash*)tb->tb_data;
+
+ s_m = cb->args[1];
+ read_lock(&fib_hash_lock);
+ for (fz = table->fn_zone_list, m=0; fz; fz = fz->fz_next, m++) {
+ if (m < s_m) continue;
+ if (m > s_m)
+ memset(&cb->args[2], 0, sizeof(cb->args) - 2*sizeof(cb->args[0]));
+ if (fn_hash_dump_zone(skb, cb, tb, fz) < 0) {
+ cb->args[1] = m;
+ read_unlock(&fib_hash_lock);
+ return -1;
+ }
+ }
+ read_unlock(&fib_hash_lock);
+ cb->args[1] = m;
+ return skb->len;
+}
+
+static void rtmsg_fib(int event, struct fib_node* f, int z, int tb_id,
+ struct nlmsghdr *n, struct netlink_skb_parms *req)
+{
+ struct sk_buff *skb;
+ u32 pid = req ? req->pid : 0;
+ int size = NLMSG_SPACE(sizeof(struct rtmsg)+256);
+
+ skb = alloc_skb(size, GFP_KERNEL);
+ if (!skb)
+ return;
+
+ if (fib_dump_info(skb, pid, n->nlmsg_seq, event, tb_id,
+ f->fn_type, f->fn_scope, &f->fn_key, z, f->fn_tos,
+ FIB_INFO(f)) < 0) {
+ kfree_skb(skb);
+ return;
+ }
+ NETLINK_CB(skb).dst_groups = RTMGRP_IPV4_ROUTE;
+ if (n->nlmsg_flags&NLM_F_ECHO)
+ atomic_inc(&skb->users);
+ netlink_broadcast(rtnl, skb, pid, RTMGRP_IPV4_ROUTE, GFP_KERNEL);
+ if (n->nlmsg_flags&NLM_F_ECHO)
+ netlink_unicast(rtnl, skb, pid, MSG_DONTWAIT);
+}
+
+#ifdef CONFIG_IP_MULTIPLE_TABLES
+struct fib_table * fib_hash_init(int id)
+#else
+struct fib_table * __init fib_hash_init(int id)
+#endif
+{
+ struct fib_table *tb;
+
+ if (fn_hash_kmem == NULL)
+ fn_hash_kmem = kmem_cache_create("ip_fib_hash",
+ sizeof(struct fib_node),
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+
+ tb = kmalloc(sizeof(struct fib_table) + sizeof(struct fn_hash), GFP_KERNEL);
+ if (tb == NULL)
+ return NULL;
+
+ tb->tb_id = id;
+ tb->tb_lookup = fn_hash_lookup;
+ tb->tb_insert = fn_hash_insert;
+ tb->tb_delete = fn_hash_delete;
+ tb->tb_flush = fn_hash_flush;
+ tb->tb_select_default = fn_hash_select_default;
+ tb->tb_dump = fn_hash_dump;
+#ifdef CONFIG_PROC_FS
+ tb->tb_get_info = fn_hash_get_info;
+#endif
+ memset(tb->tb_data, 0, sizeof(struct fn_hash));
+ return tb;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/fib_rules.c b/uClinux-2.4.31-uc0/net/ipv4/fib_rules.c
new file mode 100644
index 0000000..e9998e5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/fib_rules.c
@@ -0,0 +1,467 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * IPv4 Forwarding Information Base: policy rules.
+ *
+ * Version: $Id: fib_rules.c,v 1.17 2001/10/31 21:55:54 davem Exp $
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Fixes:
+ * Rani Assaf : local_rule cannot be deleted
+ * Marc Boucher : routing by fwmark
+ */
+
+#include <linux/config.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/errno.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/proc_fs.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/init.h>
+
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <net/route.h>
+#include <net/tcp.h>
+#include <net/sock.h>
+#include <net/ip_fib.h>
+
+#define FRprintk(a...)
+
+struct fib_rule
+{
+ struct fib_rule *r_next;
+ atomic_t r_clntref;
+ u32 r_preference;
+ unsigned char r_table;
+ unsigned char r_action;
+ unsigned char r_dst_len;
+ unsigned char r_src_len;
+ u32 r_src;
+ u32 r_srcmask;
+ u32 r_dst;
+ u32 r_dstmask;
+ u32 r_srcmap;
+ u8 r_flags;
+ u8 r_tos;
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ u32 r_fwmark;
+#endif
+ int r_ifindex;
+#ifdef CONFIG_NET_CLS_ROUTE
+ __u32 r_tclassid;
+#endif
+ char r_ifname[IFNAMSIZ];
+ int r_dead;
+};
+
+static struct fib_rule default_rule = {
+ r_clntref: ATOMIC_INIT(2),
+ r_preference: 0x7FFF,
+ r_table: RT_TABLE_DEFAULT,
+ r_action: RTN_UNICAST,
+};
+
+static struct fib_rule main_rule = {
+ r_next: &default_rule,
+ r_clntref: ATOMIC_INIT(2),
+ r_preference: 0x7FFE,
+ r_table: RT_TABLE_MAIN,
+ r_action: RTN_UNICAST,
+};
+
+static struct fib_rule local_rule = {
+ r_next: &main_rule,
+ r_clntref: ATOMIC_INIT(2),
+ r_table: RT_TABLE_LOCAL,
+ r_action: RTN_UNICAST,
+};
+
+static struct fib_rule *fib_rules = &local_rule;
+static rwlock_t fib_rules_lock = RW_LOCK_UNLOCKED;
+
+int inet_rtm_delrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
+{
+ struct rtattr **rta = arg;
+ struct rtmsg *rtm = NLMSG_DATA(nlh);
+ struct fib_rule *r, **rp;
+ int err = -ESRCH;
+
+ for (rp=&fib_rules; (r=*rp) != NULL; rp=&r->r_next) {
+ if ((!rta[RTA_SRC-1] || memcmp(RTA_DATA(rta[RTA_SRC-1]), &r->r_src, 4) == 0) &&
+ rtm->rtm_src_len == r->r_src_len &&
+ rtm->rtm_dst_len == r->r_dst_len &&
+ (!rta[RTA_DST-1] || memcmp(RTA_DATA(rta[RTA_DST-1]), &r->r_dst, 4) == 0) &&
+ rtm->rtm_tos == r->r_tos &&
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ (!rta[RTA_PROTOINFO-1] || memcmp(RTA_DATA(rta[RTA_PROTOINFO-1]), &r->r_fwmark, 4) == 0) &&
+#endif
+ (!rtm->rtm_type || rtm->rtm_type == r->r_action) &&
+ (!rta[RTA_PRIORITY-1] || memcmp(RTA_DATA(rta[RTA_PRIORITY-1]), &r->r_preference, 4) == 0) &&
+ (!rta[RTA_IIF-1] || strcmp(RTA_DATA(rta[RTA_IIF-1]), r->r_ifname) == 0) &&
+ (!rtm->rtm_table || (r && rtm->rtm_table == r->r_table))) {
+ err = -EPERM;
+ if (r == &local_rule)
+ break;
+
+ write_lock_bh(&fib_rules_lock);
+ *rp = r->r_next;
+ r->r_dead = 1;
+ write_unlock_bh(&fib_rules_lock);
+ fib_rule_put(r);
+ err = 0;
+ break;
+ }
+ }
+ return err;
+}
+
+/* Allocate new unique table id */
+
+static struct fib_table *fib_empty_table(void)
+{
+ int id;
+
+ for (id = 1; id <= RT_TABLE_MAX; id++)
+ if (fib_tables[id] == NULL)
+ return __fib_new_table(id);
+ return NULL;
+}
+
+void fib_rule_put(struct fib_rule *r)
+{
+ if (atomic_dec_and_test(&r->r_clntref)) {
+ if (r->r_dead)
+ kfree(r);
+ else
+ printk("Freeing alive rule %p\n", r);
+ }
+}
+
+int inet_rtm_newrule(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
+{
+ struct rtattr **rta = arg;
+ struct rtmsg *rtm = NLMSG_DATA(nlh);
+ struct fib_rule *r, *new_r, **rp;
+ unsigned char table_id;
+
+ if (rtm->rtm_src_len > 32 || rtm->rtm_dst_len > 32 ||
+ (rtm->rtm_tos & ~IPTOS_TOS_MASK))
+ return -EINVAL;
+
+ if (rta[RTA_IIF-1] && RTA_PAYLOAD(rta[RTA_IIF-1]) > IFNAMSIZ)
+ return -EINVAL;
+
+ table_id = rtm->rtm_table;
+ if (table_id == RT_TABLE_UNSPEC) {
+ struct fib_table *table;
+ if (rtm->rtm_type == RTN_UNICAST || rtm->rtm_type == RTN_NAT) {
+ if ((table = fib_empty_table()) == NULL)
+ return -ENOBUFS;
+ table_id = table->tb_id;
+ }
+ }
+
+ new_r = kmalloc(sizeof(*new_r), GFP_KERNEL);
+ if (!new_r)
+ return -ENOMEM;
+ memset(new_r, 0, sizeof(*new_r));
+ if (rta[RTA_SRC-1])
+ memcpy(&new_r->r_src, RTA_DATA(rta[RTA_SRC-1]), 4);
+ if (rta[RTA_DST-1])
+ memcpy(&new_r->r_dst, RTA_DATA(rta[RTA_DST-1]), 4);
+ if (rta[RTA_GATEWAY-1])
+ memcpy(&new_r->r_srcmap, RTA_DATA(rta[RTA_GATEWAY-1]), 4);
+ new_r->r_src_len = rtm->rtm_src_len;
+ new_r->r_dst_len = rtm->rtm_dst_len;
+ new_r->r_srcmask = inet_make_mask(rtm->rtm_src_len);
+ new_r->r_dstmask = inet_make_mask(rtm->rtm_dst_len);
+ new_r->r_tos = rtm->rtm_tos;
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ if (rta[RTA_PROTOINFO-1])
+ memcpy(&new_r->r_fwmark, RTA_DATA(rta[RTA_PROTOINFO-1]), 4);
+#endif
+ new_r->r_action = rtm->rtm_type;
+ new_r->r_flags = rtm->rtm_flags;
+ if (rta[RTA_PRIORITY-1])
+ memcpy(&new_r->r_preference, RTA_DATA(rta[RTA_PRIORITY-1]), 4);
+ new_r->r_table = table_id;
+ if (rta[RTA_IIF-1]) {
+ struct net_device *dev;
+ memcpy(new_r->r_ifname, RTA_DATA(rta[RTA_IIF-1]), IFNAMSIZ);
+ new_r->r_ifname[IFNAMSIZ-1] = 0;
+ new_r->r_ifindex = -1;
+ dev = __dev_get_by_name(new_r->r_ifname);
+ if (dev)
+ new_r->r_ifindex = dev->ifindex;
+ }
+#ifdef CONFIG_NET_CLS_ROUTE
+ if (rta[RTA_FLOW-1])
+ memcpy(&new_r->r_tclassid, RTA_DATA(rta[RTA_FLOW-1]), 4);
+#endif
+
+ rp = &fib_rules;
+ if (!new_r->r_preference) {
+ r = fib_rules;
+ if (r && (r = r->r_next) != NULL) {
+ rp = &fib_rules->r_next;
+ if (r->r_preference)
+ new_r->r_preference = r->r_preference - 1;
+ }
+ }
+
+ while ( (r = *rp) != NULL ) {
+ if (r->r_preference > new_r->r_preference)
+ break;
+ rp = &r->r_next;
+ }
+
+ new_r->r_next = r;
+ atomic_inc(&new_r->r_clntref);
+ write_lock_bh(&fib_rules_lock);
+ *rp = new_r;
+ write_unlock_bh(&fib_rules_lock);
+ return 0;
+}
+
+u32 fib_rules_map_destination(u32 daddr, struct fib_result *res)
+{
+ u32 mask = inet_make_mask(res->prefixlen);
+ return (daddr&~mask)|res->fi->fib_nh->nh_gw;
+}
+
+u32 fib_rules_policy(u32 saddr, struct fib_result *res, unsigned *flags)
+{
+ struct fib_rule *r = res->r;
+
+ if (r->r_action == RTN_NAT) {
+ int addrtype = inet_addr_type(r->r_srcmap);
+
+ if (addrtype == RTN_NAT) {
+ /* Packet is from translated source; remember it */
+ saddr = (saddr&~r->r_srcmask)|r->r_srcmap;
+ *flags |= RTCF_SNAT;
+ } else if (addrtype == RTN_LOCAL || r->r_srcmap == 0) {
+ /* Packet is from masqueraded source; remember it */
+ saddr = r->r_srcmap;
+ *flags |= RTCF_MASQ;
+ }
+ }
+ return saddr;
+}
+
+#ifdef CONFIG_NET_CLS_ROUTE
+u32 fib_rules_tclass(struct fib_result *res)
+{
+ if (res->r)
+ return res->r->r_tclassid;
+ return 0;
+}
+#endif
+
+
+static void fib_rules_detach(struct net_device *dev)
+{
+ struct fib_rule *r;
+
+ for (r=fib_rules; r; r=r->r_next) {
+ if (r->r_ifindex == dev->ifindex) {
+ write_lock_bh(&fib_rules_lock);
+ r->r_ifindex = -1;
+ write_unlock_bh(&fib_rules_lock);
+ }
+ }
+}
+
+static void fib_rules_attach(struct net_device *dev)
+{
+ struct fib_rule *r;
+
+ for (r=fib_rules; r; r=r->r_next) {
+ if (r->r_ifindex == -1 && strcmp(dev->name, r->r_ifname) == 0) {
+ write_lock_bh(&fib_rules_lock);
+ r->r_ifindex = dev->ifindex;
+ write_unlock_bh(&fib_rules_lock);
+ }
+ }
+}
+
+int fib_lookup(const struct rt_key *key, struct fib_result *res)
+{
+ int err;
+ struct fib_rule *r, *policy;
+ struct fib_table *tb;
+
+ u32 daddr = key->dst;
+ u32 saddr = key->src;
+
+FRprintk("Lookup: %u.%u.%u.%u <- %u.%u.%u.%u ",
+ NIPQUAD(key->dst), NIPQUAD(key->src));
+ read_lock(&fib_rules_lock);
+ for (r = fib_rules; r; r=r->r_next) {
+ if (((saddr^r->r_src) & r->r_srcmask) ||
+ ((daddr^r->r_dst) & r->r_dstmask) ||
+#ifdef CONFIG_IP_ROUTE_TOS
+ (r->r_tos && r->r_tos != key->tos) ||
+#endif
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ (r->r_fwmark && r->r_fwmark != key->fwmark) ||
+#endif
+ (r->r_ifindex && r->r_ifindex != key->iif))
+ continue;
+
+FRprintk("tb %d r %d ", r->r_table, r->r_action);
+ switch (r->r_action) {
+ case RTN_UNICAST:
+ case RTN_NAT:
+ policy = r;
+ break;
+ case RTN_UNREACHABLE:
+ read_unlock(&fib_rules_lock);
+ return -ENETUNREACH;
+ default:
+ case RTN_BLACKHOLE:
+ read_unlock(&fib_rules_lock);
+ return -EINVAL;
+ case RTN_PROHIBIT:
+ read_unlock(&fib_rules_lock);
+ return -EACCES;
+ }
+
+ if ((tb = fib_get_table(r->r_table)) == NULL)
+ continue;
+ err = tb->tb_lookup(tb, key, res);
+ if (err == 0) {
+ res->r = policy;
+ if (policy)
+ atomic_inc(&policy->r_clntref);
+ read_unlock(&fib_rules_lock);
+ return 0;
+ }
+ if (err < 0 && err != -EAGAIN) {
+ read_unlock(&fib_rules_lock);
+ return err;
+ }
+ }
+FRprintk("FAILURE\n");
+ read_unlock(&fib_rules_lock);
+ return -ENETUNREACH;
+}
+
+void fib_select_default(const struct rt_key *key, struct fib_result *res)
+{
+ if (res->r && res->r->r_action == RTN_UNICAST &&
+ FIB_RES_GW(*res) && FIB_RES_NH(*res).nh_scope == RT_SCOPE_LINK) {
+ struct fib_table *tb;
+ if ((tb = fib_get_table(res->r->r_table)) != NULL)
+ tb->tb_select_default(tb, key, res);
+ }
+}
+
+static int fib_rules_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct net_device *dev = ptr;
+
+ if (event == NETDEV_UNREGISTER)
+ fib_rules_detach(dev);
+ else if (event == NETDEV_REGISTER)
+ fib_rules_attach(dev);
+ return NOTIFY_DONE;
+}
+
+
+struct notifier_block fib_rules_notifier = {
+ notifier_call: fib_rules_event,
+};
+
+static __inline__ int inet_fill_rule(struct sk_buff *skb,
+ struct fib_rule *r,
+ struct netlink_callback *cb)
+{
+ struct rtmsg *rtm;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+ nlh = NLMSG_PUT(skb, NETLINK_CREDS(cb->skb)->pid, cb->nlh->nlmsg_seq, RTM_NEWRULE, sizeof(*rtm));
+ rtm = NLMSG_DATA(nlh);
+ rtm->rtm_family = AF_INET;
+ rtm->rtm_dst_len = r->r_dst_len;
+ rtm->rtm_src_len = r->r_src_len;
+ rtm->rtm_tos = r->r_tos;
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ if (r->r_fwmark)
+ RTA_PUT(skb, RTA_PROTOINFO, 4, &r->r_fwmark);
+#endif
+ rtm->rtm_table = r->r_table;
+ rtm->rtm_protocol = 0;
+ rtm->rtm_scope = 0;
+ rtm->rtm_type = r->r_action;
+ rtm->rtm_flags = r->r_flags;
+
+ if (r->r_dst_len)
+ RTA_PUT(skb, RTA_DST, 4, &r->r_dst);
+ if (r->r_src_len)
+ RTA_PUT(skb, RTA_SRC, 4, &r->r_src);
+ if (r->r_ifname[0])
+ RTA_PUT(skb, RTA_IIF, IFNAMSIZ, &r->r_ifname);
+ if (r->r_preference)
+ RTA_PUT(skb, RTA_PRIORITY, 4, &r->r_preference);
+ if (r->r_srcmap)
+ RTA_PUT(skb, RTA_GATEWAY, 4, &r->r_srcmap);
+#ifdef CONFIG_NET_CLS_ROUTE
+ if (r->r_tclassid)
+ RTA_PUT(skb, RTA_FLOW, 4, &r->r_tclassid);
+#endif
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+int inet_dump_rules(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int idx;
+ int s_idx = cb->args[0];
+ struct fib_rule *r;
+
+ read_lock(&fib_rules_lock);
+ for (r=fib_rules, idx=0; r; r = r->r_next, idx++) {
+ if (idx < s_idx)
+ continue;
+ if (inet_fill_rule(skb, r, cb) < 0)
+ break;
+ }
+ read_unlock(&fib_rules_lock);
+ cb->args[0] = idx;
+
+ return skb->len;
+}
+
+void __init fib_rules_init(void)
+{
+ register_netdevice_notifier(&fib_rules_notifier);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/fib_semantics.c b/uClinux-2.4.31-uc0/net/ipv4/fib_semantics.c
new file mode 100644
index 0000000..6f32185
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/fib_semantics.c
@@ -0,0 +1,1047 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * IPv4 Forwarding Information Base: semantics.
+ *
+ * Version: $Id: fib_semantics.c,v 1.18.2.2 2002/01/12 07:54:15 davem Exp $
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/errno.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/proc_fs.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/init.h>
+
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <net/route.h>
+#include <net/tcp.h>
+#include <net/sock.h>
+#include <net/ip_fib.h>
+
+#define FSprintk(a...)
+
+static struct fib_info *fib_info_list;
+static rwlock_t fib_info_lock = RW_LOCK_UNLOCKED;
+int fib_info_cnt;
+
+#define for_fib_info() { struct fib_info *fi; \
+ for (fi = fib_info_list; fi; fi = fi->fib_next)
+
+#define endfor_fib_info() }
+
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+
+static spinlock_t fib_multipath_lock = SPIN_LOCK_UNLOCKED;
+
+#define for_nexthops(fi) { int nhsel; const struct fib_nh * nh; \
+for (nhsel=0, nh = (fi)->fib_nh; nhsel < (fi)->fib_nhs; nh++, nhsel++)
+
+#define change_nexthops(fi) { int nhsel; struct fib_nh * nh; \
+for (nhsel=0, nh = (struct fib_nh*)((fi)->fib_nh); nhsel < (fi)->fib_nhs; nh++, nhsel++)
+
+#else /* CONFIG_IP_ROUTE_MULTIPATH */
+
+/* Hope, that gcc will optimize it to get rid of dummy loop */
+
+#define for_nexthops(fi) { int nhsel=0; const struct fib_nh * nh = (fi)->fib_nh; \
+for (nhsel=0; nhsel < 1; nhsel++)
+
+#define change_nexthops(fi) { int nhsel=0; struct fib_nh * nh = (struct fib_nh*)((fi)->fib_nh); \
+for (nhsel=0; nhsel < 1; nhsel++)
+
+#endif /* CONFIG_IP_ROUTE_MULTIPATH */
+
+#define endfor_nexthops(fi) }
+
+
+static struct
+{
+ int error;
+ u8 scope;
+} fib_props[RTA_MAX+1] = {
+ { 0, RT_SCOPE_NOWHERE}, /* RTN_UNSPEC */
+ { 0, RT_SCOPE_UNIVERSE}, /* RTN_UNICAST */
+ { 0, RT_SCOPE_HOST}, /* RTN_LOCAL */
+ { 0, RT_SCOPE_LINK}, /* RTN_BROADCAST */
+ { 0, RT_SCOPE_LINK}, /* RTN_ANYCAST */
+ { 0, RT_SCOPE_UNIVERSE}, /* RTN_MULTICAST */
+ { -EINVAL, RT_SCOPE_UNIVERSE}, /* RTN_BLACKHOLE */
+ { -EHOSTUNREACH, RT_SCOPE_UNIVERSE},/* RTN_UNREACHABLE */
+ { -EACCES, RT_SCOPE_UNIVERSE}, /* RTN_PROHIBIT */
+ { -EAGAIN, RT_SCOPE_UNIVERSE}, /* RTN_THROW */
+#ifdef CONFIG_IP_ROUTE_NAT
+ { 0, RT_SCOPE_HOST}, /* RTN_NAT */
+#else
+ { -EINVAL, RT_SCOPE_NOWHERE}, /* RTN_NAT */
+#endif
+ { -EINVAL, RT_SCOPE_NOWHERE} /* RTN_XRESOLVE */
+};
+
+#ifdef CONFIG_IP_ROUTE_MULTIPATH_SEQUENTIAL
+unsigned int mp_counter=0;
+#endif
+
+/* Release a nexthop info record */
+
+void free_fib_info(struct fib_info *fi)
+{
+ if (fi->fib_dead == 0) {
+ printk("Freeing alive fib_info %p\n", fi);
+ return;
+ }
+ change_nexthops(fi) {
+ if (nh->nh_dev)
+ dev_put(nh->nh_dev);
+ nh->nh_dev = NULL;
+ } endfor_nexthops(fi);
+ fib_info_cnt--;
+ kfree(fi);
+}
+
+void fib_release_info(struct fib_info *fi)
+{
+ write_lock(&fib_info_lock);
+ if (fi && --fi->fib_treeref == 0) {
+ if (fi->fib_next)
+ fi->fib_next->fib_prev = fi->fib_prev;
+ if (fi->fib_prev)
+ fi->fib_prev->fib_next = fi->fib_next;
+ if (fi == fib_info_list)
+ fib_info_list = fi->fib_next;
+ fi->fib_dead = 1;
+ fib_info_put(fi);
+ }
+ write_unlock(&fib_info_lock);
+}
+
+static __inline__ int nh_comp(const struct fib_info *fi, const struct fib_info *ofi)
+{
+ const struct fib_nh *onh = ofi->fib_nh;
+
+ for_nexthops(fi) {
+ if (nh->nh_oif != onh->nh_oif ||
+ nh->nh_gw != onh->nh_gw ||
+ nh->nh_scope != onh->nh_scope ||
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ nh->nh_weight != onh->nh_weight ||
+#endif
+#ifdef CONFIG_NET_CLS_ROUTE
+ nh->nh_tclassid != onh->nh_tclassid ||
+#endif
+ ((nh->nh_flags^onh->nh_flags)&~RTNH_F_DEAD))
+ return -1;
+ onh++;
+ } endfor_nexthops(fi);
+ return 0;
+}
+
+static __inline__ struct fib_info * fib_find_info(const struct fib_info *nfi)
+{
+ for_fib_info() {
+ if (fi->fib_nhs != nfi->fib_nhs)
+ continue;
+ if (nfi->fib_protocol == fi->fib_protocol &&
+ nfi->fib_prefsrc == fi->fib_prefsrc &&
+ nfi->fib_priority == fi->fib_priority &&
+ memcmp(nfi->fib_metrics, fi->fib_metrics, sizeof(fi->fib_metrics)) == 0 &&
+ ((nfi->fib_flags^fi->fib_flags)&~RTNH_F_DEAD) == 0 &&
+ (nfi->fib_nhs == 0 || nh_comp(fi, nfi) == 0))
+ return fi;
+ } endfor_fib_info();
+ return NULL;
+}
+
+/* Check, that the gateway is already configured.
+ Used only by redirect accept routine.
+ */
+
+int ip_fib_check_default(u32 gw, struct net_device *dev)
+{
+ read_lock(&fib_info_lock);
+ for_fib_info() {
+ if (fi->fib_flags & RTNH_F_DEAD)
+ continue;
+ for_nexthops(fi) {
+ if (nh->nh_dev == dev && nh->nh_gw == gw &&
+ nh->nh_scope == RT_SCOPE_LINK &&
+ !(nh->nh_flags&RTNH_F_DEAD)) {
+ read_unlock(&fib_info_lock);
+ return 0;
+ }
+ } endfor_nexthops(fi);
+ } endfor_fib_info();
+ read_unlock(&fib_info_lock);
+ return -1;
+}
+
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+
+static u32 fib_get_attr32(struct rtattr *attr, int attrlen, int type)
+{
+ while (RTA_OK(attr,attrlen)) {
+ if (attr->rta_type == type)
+ return *(u32*)RTA_DATA(attr);
+ attr = RTA_NEXT(attr, attrlen);
+ }
+ return 0;
+}
+
+static int
+fib_count_nexthops(struct rtattr *rta)
+{
+ int nhs = 0;
+ struct rtnexthop *nhp = RTA_DATA(rta);
+ int nhlen = RTA_PAYLOAD(rta);
+
+ while (nhlen >= (int)sizeof(struct rtnexthop)) {
+ if ((nhlen -= nhp->rtnh_len) < 0)
+ return 0;
+ nhs++;
+ nhp = RTNH_NEXT(nhp);
+ };
+ return nhs;
+}
+
+static int
+fib_get_nhs(struct fib_info *fi, const struct rtattr *rta, const struct rtmsg *r)
+{
+ struct rtnexthop *nhp = RTA_DATA(rta);
+ int nhlen = RTA_PAYLOAD(rta);
+
+ change_nexthops(fi) {
+ int attrlen = nhlen - sizeof(struct rtnexthop);
+ if (attrlen < 0 || (nhlen -= nhp->rtnh_len) < 0)
+ return -EINVAL;
+ nh->nh_flags = (r->rtm_flags&~0xFF) | nhp->rtnh_flags;
+ nh->nh_oif = nhp->rtnh_ifindex;
+ nh->nh_weight = nhp->rtnh_hops + 1;
+ if (attrlen) {
+ nh->nh_gw = fib_get_attr32(RTNH_DATA(nhp), attrlen, RTA_GATEWAY);
+#ifdef CONFIG_NET_CLS_ROUTE
+ nh->nh_tclassid = fib_get_attr32(RTNH_DATA(nhp), attrlen, RTA_FLOW);
+#endif
+ }
+ nhp = RTNH_NEXT(nhp);
+ } endfor_nexthops(fi);
+ return 0;
+}
+
+#endif
+
+int fib_nh_match(struct rtmsg *r, struct nlmsghdr *nlh, struct kern_rta *rta,
+ struct fib_info *fi)
+{
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ struct rtnexthop *nhp;
+ int nhlen;
+#endif
+
+ if (rta->rta_priority &&
+ *rta->rta_priority != fi->fib_priority)
+ return 1;
+
+ if (rta->rta_oif || rta->rta_gw) {
+ if ((!rta->rta_oif || *rta->rta_oif == fi->fib_nh->nh_oif) &&
+ (!rta->rta_gw || memcmp(rta->rta_gw, &fi->fib_nh->nh_gw, 4) == 0))
+ return 0;
+ return 1;
+ }
+
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ if (rta->rta_mp == NULL)
+ return 0;
+ nhp = RTA_DATA(rta->rta_mp);
+ nhlen = RTA_PAYLOAD(rta->rta_mp);
+
+ for_nexthops(fi) {
+ int attrlen = nhlen - sizeof(struct rtnexthop);
+ u32 gw;
+
+ if (attrlen < 0 || (nhlen -= nhp->rtnh_len) < 0)
+ return -EINVAL;
+ if (nhp->rtnh_ifindex && nhp->rtnh_ifindex != nh->nh_oif)
+ return 1;
+ if (attrlen) {
+ gw = fib_get_attr32(RTNH_DATA(nhp), attrlen, RTA_GATEWAY);
+ if (gw && gw != nh->nh_gw)
+ return 1;
+#ifdef CONFIG_NET_CLS_ROUTE
+ gw = fib_get_attr32(RTNH_DATA(nhp), attrlen, RTA_FLOW);
+ if (gw && gw != nh->nh_tclassid)
+ return 1;
+#endif
+ }
+ nhp = RTNH_NEXT(nhp);
+ } endfor_nexthops(fi);
+#endif
+ return 0;
+}
+
+
+/*
+ Picture
+ -------
+
+ Semantics of nexthop is very messy by historical reasons.
+ We have to take into account, that:
+ a) gateway can be actually local interface address,
+ so that gatewayed route is direct.
+ b) gateway must be on-link address, possibly
+ described not by an ifaddr, but also by a direct route.
+ c) If both gateway and interface are specified, they should not
+ contradict.
+ d) If we use tunnel routes, gateway could be not on-link.
+
+ Attempt to reconcile all of these (alas, self-contradictory) conditions
+ results in pretty ugly and hairy code with obscure logic.
+
+ I choosed to generalized it instead, so that the size
+ of code does not increase practically, but it becomes
+ much more general.
+ Every prefix is assigned a "scope" value: "host" is local address,
+ "link" is direct route,
+ [ ... "site" ... "interior" ... ]
+ and "universe" is true gateway route with global meaning.
+
+ Every prefix refers to a set of "nexthop"s (gw, oif),
+ where gw must have narrower scope. This recursion stops
+ when gw has LOCAL scope or if "nexthop" is declared ONLINK,
+ which means that gw is forced to be on link.
+
+ Code is still hairy, but now it is apparently logically
+ consistent and very flexible. F.e. as by-product it allows
+ to co-exists in peace independent exterior and interior
+ routing processes.
+
+ Normally it looks as following.
+
+ {universe prefix} -> (gw, oif) [scope link]
+ |
+ |-> {link prefix} -> (gw, oif) [scope local]
+ |
+ |-> {local prefix} (terminal node)
+ */
+
+static int fib_check_nh(const struct rtmsg *r, struct fib_info *fi, struct fib_nh *nh)
+{
+ int err;
+
+ if (nh->nh_gw) {
+ struct rt_key key;
+ struct fib_result res;
+
+#ifdef CONFIG_IP_ROUTE_PERVASIVE
+ if (nh->nh_flags&RTNH_F_PERVASIVE)
+ return 0;
+#endif
+ if (nh->nh_flags&RTNH_F_ONLINK) {
+ struct net_device *dev;
+
+ if (r->rtm_scope >= RT_SCOPE_LINK)
+ return -EINVAL;
+ if (inet_addr_type(nh->nh_gw) != RTN_UNICAST)
+ return -EINVAL;
+ if ((dev = __dev_get_by_index(nh->nh_oif)) == NULL)
+ return -ENODEV;
+ if (!(dev->flags&IFF_UP))
+ return -ENETDOWN;
+ nh->nh_dev = dev;
+ dev_hold(dev);
+ nh->nh_scope = RT_SCOPE_LINK;
+ return 0;
+ }
+ memset(&key, 0, sizeof(key));
+ key.dst = nh->nh_gw;
+ key.oif = nh->nh_oif;
+ key.scope = r->rtm_scope + 1;
+
+ /* It is not necessary, but requires a bit of thinking */
+ if (key.scope < RT_SCOPE_LINK)
+ key.scope = RT_SCOPE_LINK;
+ if ((err = fib_lookup(&key, &res)) != 0)
+ return err;
+ err = -EINVAL;
+ if (res.type != RTN_UNICAST && res.type != RTN_LOCAL)
+ goto out;
+ nh->nh_scope = res.scope;
+ nh->nh_oif = FIB_RES_OIF(res);
+ if ((nh->nh_dev = FIB_RES_DEV(res)) == NULL)
+ goto out;
+ dev_hold(nh->nh_dev);
+ err = -ENETDOWN;
+ if (!(nh->nh_dev->flags & IFF_UP))
+ goto out;
+ err = 0;
+out:
+ fib_res_put(&res);
+ return err;
+ } else {
+ struct in_device *in_dev;
+
+ if (nh->nh_flags&(RTNH_F_PERVASIVE|RTNH_F_ONLINK))
+ return -EINVAL;
+
+ in_dev = inetdev_by_index(nh->nh_oif);
+ if (in_dev == NULL)
+ return -ENODEV;
+ if (!(in_dev->dev->flags&IFF_UP)) {
+ in_dev_put(in_dev);
+ return -ENETDOWN;
+ }
+ nh->nh_dev = in_dev->dev;
+ dev_hold(nh->nh_dev);
+ nh->nh_scope = RT_SCOPE_HOST;
+ in_dev_put(in_dev);
+ }
+ return 0;
+}
+
+struct fib_info *
+fib_create_info(const struct rtmsg *r, struct kern_rta *rta,
+ const struct nlmsghdr *nlh, int *errp)
+{
+ int err;
+ struct fib_info *fi = NULL;
+ struct fib_info *ofi;
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ int nhs = 1;
+#else
+ const int nhs = 1;
+#endif
+
+ /* Fast check to catch the most weird cases */
+ if (fib_props[r->rtm_type].scope > r->rtm_scope)
+ goto err_inval;
+
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ if (rta->rta_mp) {
+ nhs = fib_count_nexthops(rta->rta_mp);
+ if (nhs == 0)
+ goto err_inval;
+ }
+#endif
+
+ fi = kmalloc(sizeof(*fi)+nhs*sizeof(struct fib_nh), GFP_KERNEL);
+ err = -ENOBUFS;
+ if (fi == NULL)
+ goto failure;
+ fib_info_cnt++;
+ memset(fi, 0, sizeof(*fi)+nhs*sizeof(struct fib_nh));
+
+ fi->fib_protocol = r->rtm_protocol;
+ fi->fib_nhs = nhs;
+ fi->fib_flags = r->rtm_flags;
+ if (rta->rta_priority)
+ fi->fib_priority = *rta->rta_priority;
+ if (rta->rta_mx) {
+ int attrlen = RTA_PAYLOAD(rta->rta_mx);
+ struct rtattr *attr = RTA_DATA(rta->rta_mx);
+
+ while (RTA_OK(attr, attrlen)) {
+ unsigned flavor = attr->rta_type;
+ if (flavor) {
+ if (flavor > RTAX_MAX)
+ goto err_inval;
+ fi->fib_metrics[flavor-1] = *(unsigned*)RTA_DATA(attr);
+ }
+ attr = RTA_NEXT(attr, attrlen);
+ }
+ }
+ if (rta->rta_prefsrc)
+ memcpy(&fi->fib_prefsrc, rta->rta_prefsrc, 4);
+
+ if (rta->rta_mp) {
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ if ((err = fib_get_nhs(fi, rta->rta_mp, r)) != 0)
+ goto failure;
+ if (rta->rta_oif && fi->fib_nh->nh_oif != *rta->rta_oif)
+ goto err_inval;
+ if (rta->rta_gw && memcmp(&fi->fib_nh->nh_gw, rta->rta_gw, 4))
+ goto err_inval;
+#ifdef CONFIG_NET_CLS_ROUTE
+ if (rta->rta_flow && memcmp(&fi->fib_nh->nh_tclassid, rta->rta_flow, 4))
+ goto err_inval;
+#endif
+#else
+ goto err_inval;
+#endif
+ } else {
+ struct fib_nh *nh = fi->fib_nh;
+ if (rta->rta_oif)
+ nh->nh_oif = *rta->rta_oif;
+ if (rta->rta_gw)
+ memcpy(&nh->nh_gw, rta->rta_gw, 4);
+#ifdef CONFIG_NET_CLS_ROUTE
+ if (rta->rta_flow)
+ memcpy(&nh->nh_tclassid, rta->rta_flow, 4);
+#endif
+ nh->nh_flags = r->rtm_flags;
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ nh->nh_weight = 1;
+#endif
+ }
+
+#ifdef CONFIG_IP_ROUTE_NAT
+ if (r->rtm_type == RTN_NAT) {
+ if (rta->rta_gw == NULL || nhs != 1 || rta->rta_oif)
+ goto err_inval;
+ memcpy(&fi->fib_nh->nh_gw, rta->rta_gw, 4);
+ goto link_it;
+ }
+#endif
+
+ if (fib_props[r->rtm_type].error) {
+ if (rta->rta_gw || rta->rta_oif || rta->rta_mp)
+ goto err_inval;
+ goto link_it;
+ }
+
+ if (r->rtm_scope > RT_SCOPE_HOST)
+ goto err_inval;
+
+ if (r->rtm_scope == RT_SCOPE_HOST) {
+ struct fib_nh *nh = fi->fib_nh;
+
+ /* Local address is added. */
+ if (nhs != 1 || nh->nh_gw)
+ goto err_inval;
+ nh->nh_scope = RT_SCOPE_NOWHERE;
+ nh->nh_dev = dev_get_by_index(fi->fib_nh->nh_oif);
+ err = -ENODEV;
+ if (nh->nh_dev == NULL)
+ goto failure;
+ } else {
+ change_nexthops(fi) {
+ if ((err = fib_check_nh(r, fi, nh)) != 0)
+ goto failure;
+ } endfor_nexthops(fi)
+ }
+
+ if (fi->fib_prefsrc) {
+ if (r->rtm_type != RTN_LOCAL || rta->rta_dst == NULL ||
+ memcmp(&fi->fib_prefsrc, rta->rta_dst, 4))
+ if (inet_addr_type(fi->fib_prefsrc) != RTN_LOCAL)
+ goto err_inval;
+ }
+
+link_it:
+ if ((ofi = fib_find_info(fi)) != NULL) {
+ fi->fib_dead = 1;
+ free_fib_info(fi);
+ ofi->fib_treeref++;
+ return ofi;
+ }
+
+ fi->fib_treeref++;
+ atomic_inc(&fi->fib_clntref);
+ write_lock(&fib_info_lock);
+ fi->fib_next = fib_info_list;
+ fi->fib_prev = NULL;
+ if (fib_info_list)
+ fib_info_list->fib_prev = fi;
+ fib_info_list = fi;
+ write_unlock(&fib_info_lock);
+ return fi;
+
+err_inval:
+ err = -EINVAL;
+
+failure:
+ *errp = err;
+ if (fi) {
+ fi->fib_dead = 1;
+ free_fib_info(fi);
+ }
+ return NULL;
+}
+
+int
+fib_semantic_match(int type, struct fib_info *fi, const struct rt_key *key, struct fib_result *res)
+{
+ int err = fib_props[type].error;
+
+ if (err == 0) {
+ if (fi->fib_flags&RTNH_F_DEAD)
+ return 1;
+
+ res->fi = fi;
+
+ switch (type) {
+#ifdef CONFIG_IP_ROUTE_NAT
+ case RTN_NAT:
+ FIB_RES_RESET(*res);
+ atomic_inc(&fi->fib_clntref);
+ return 0;
+#endif
+ case RTN_UNICAST:
+ case RTN_LOCAL:
+ case RTN_BROADCAST:
+ case RTN_ANYCAST:
+ case RTN_MULTICAST:
+ for_nexthops(fi) {
+ if (nh->nh_flags&RTNH_F_DEAD)
+ continue;
+ if (!key->oif || key->oif == nh->nh_oif)
+ break;
+ }
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ if (nhsel < fi->fib_nhs) {
+ res->nh_sel = nhsel;
+ atomic_inc(&fi->fib_clntref);
+ return 0;
+ }
+#else
+ if (nhsel < 1) {
+ atomic_inc(&fi->fib_clntref);
+ return 0;
+ }
+#endif
+ endfor_nexthops(fi);
+ res->fi = NULL;
+ return 1;
+ default:
+ res->fi = NULL;
+ printk(KERN_DEBUG "impossible 102\n");
+ return -EINVAL;
+ }
+ }
+ return err;
+}
+
+/* Find appropriate source address to this destination */
+
+u32 __fib_res_prefsrc(struct fib_result *res)
+{
+ return inet_select_addr(FIB_RES_DEV(*res), FIB_RES_GW(*res), res->scope);
+}
+
+int
+fib_dump_info(struct sk_buff *skb, u32 pid, u32 seq, int event,
+ u8 tb_id, u8 type, u8 scope, void *dst, int dst_len, u8 tos,
+ struct fib_info *fi)
+{
+ struct rtmsg *rtm;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+ nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*rtm));
+ rtm = NLMSG_DATA(nlh);
+ rtm->rtm_family = AF_INET;
+ rtm->rtm_dst_len = dst_len;
+ rtm->rtm_src_len = 0;
+ rtm->rtm_tos = tos;
+ rtm->rtm_table = tb_id;
+ rtm->rtm_type = type;
+ rtm->rtm_flags = fi->fib_flags;
+ rtm->rtm_scope = scope;
+ if (rtm->rtm_dst_len)
+ RTA_PUT(skb, RTA_DST, 4, dst);
+ rtm->rtm_protocol = fi->fib_protocol;
+ if (fi->fib_priority)
+ RTA_PUT(skb, RTA_PRIORITY, 4, &fi->fib_priority);
+#ifdef CONFIG_NET_CLS_ROUTE
+ if (fi->fib_nh[0].nh_tclassid)
+ RTA_PUT(skb, RTA_FLOW, 4, &fi->fib_nh[0].nh_tclassid);
+#endif
+ if (rtnetlink_put_metrics(skb, fi->fib_metrics) < 0)
+ goto rtattr_failure;
+ if (fi->fib_prefsrc)
+ RTA_PUT(skb, RTA_PREFSRC, 4, &fi->fib_prefsrc);
+ if (fi->fib_nhs == 1) {
+ if (fi->fib_nh->nh_gw)
+ RTA_PUT(skb, RTA_GATEWAY, 4, &fi->fib_nh->nh_gw);
+ if (fi->fib_nh->nh_oif)
+ RTA_PUT(skb, RTA_OIF, sizeof(int), &fi->fib_nh->nh_oif);
+ }
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ if (fi->fib_nhs > 1) {
+ struct rtnexthop *nhp;
+ struct rtattr *mp_head;
+ if (skb_tailroom(skb) <= RTA_SPACE(0))
+ goto rtattr_failure;
+ mp_head = (struct rtattr*)skb_put(skb, RTA_SPACE(0));
+
+ for_nexthops(fi) {
+ if (skb_tailroom(skb) < RTA_ALIGN(RTA_ALIGN(sizeof(*nhp)) + 4))
+ goto rtattr_failure;
+ nhp = (struct rtnexthop*)skb_put(skb, RTA_ALIGN(sizeof(*nhp)));
+ nhp->rtnh_flags = nh->nh_flags & 0xFF;
+ nhp->rtnh_hops = nh->nh_weight-1;
+ nhp->rtnh_ifindex = nh->nh_oif;
+ if (nh->nh_gw)
+ RTA_PUT(skb, RTA_GATEWAY, 4, &nh->nh_gw);
+ nhp->rtnh_len = skb->tail - (unsigned char*)nhp;
+ } endfor_nexthops(fi);
+ mp_head->rta_type = RTA_MULTIPATH;
+ mp_head->rta_len = skb->tail - (u8*)mp_head;
+ }
+#endif
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+#ifndef CONFIG_IP_NOSIOCRT
+
+int
+fib_convert_rtentry(int cmd, struct nlmsghdr *nl, struct rtmsg *rtm,
+ struct kern_rta *rta, struct rtentry *r)
+{
+ int plen;
+ u32 *ptr;
+
+ memset(rtm, 0, sizeof(*rtm));
+ memset(rta, 0, sizeof(*rta));
+
+ if (r->rt_dst.sa_family != AF_INET)
+ return -EAFNOSUPPORT;
+
+ /* Check mask for validity:
+ a) it must be contiguous.
+ b) destination must have all host bits clear.
+ c) if application forgot to set correct family (AF_INET),
+ reject request unless it is absolutely clear i.e.
+ both family and mask are zero.
+ */
+ plen = 32;
+ ptr = &((struct sockaddr_in*)&r->rt_dst)->sin_addr.s_addr;
+ if (!(r->rt_flags&RTF_HOST)) {
+ u32 mask = ((struct sockaddr_in*)&r->rt_genmask)->sin_addr.s_addr;
+ if (r->rt_genmask.sa_family != AF_INET) {
+ if (mask || r->rt_genmask.sa_family)
+ return -EAFNOSUPPORT;
+ }
+ if (bad_mask(mask, *ptr))
+ return -EINVAL;
+ plen = inet_mask_len(mask);
+ }
+
+ nl->nlmsg_flags = NLM_F_REQUEST;
+ nl->nlmsg_pid = 0;
+ nl->nlmsg_seq = 0;
+ nl->nlmsg_len = NLMSG_LENGTH(sizeof(*rtm));
+ if (cmd == SIOCDELRT) {
+ nl->nlmsg_type = RTM_DELROUTE;
+ nl->nlmsg_flags = 0;
+ } else {
+ nl->nlmsg_type = RTM_NEWROUTE;
+ nl->nlmsg_flags = NLM_F_REQUEST|NLM_F_CREATE;
+ rtm->rtm_protocol = RTPROT_BOOT;
+ }
+
+ rtm->rtm_dst_len = plen;
+ rta->rta_dst = ptr;
+
+ if (r->rt_metric) {
+ *(u32*)&r->rt_pad3 = r->rt_metric - 1;
+ rta->rta_priority = (u32*)&r->rt_pad3;
+ }
+ if (r->rt_flags&RTF_REJECT) {
+ rtm->rtm_scope = RT_SCOPE_HOST;
+ rtm->rtm_type = RTN_UNREACHABLE;
+ return 0;
+ }
+ rtm->rtm_scope = RT_SCOPE_NOWHERE;
+ rtm->rtm_type = RTN_UNICAST;
+
+ if (r->rt_dev) {
+ char *colon;
+ struct net_device *dev;
+ char devname[IFNAMSIZ];
+
+ if (copy_from_user(devname, r->rt_dev, IFNAMSIZ-1))
+ return -EFAULT;
+ devname[IFNAMSIZ-1] = 0;
+ colon = strchr(devname, ':');
+ if (colon)
+ *colon = 0;
+ dev = __dev_get_by_name(devname);
+ if (!dev)
+ return -ENODEV;
+ rta->rta_oif = &dev->ifindex;
+ if (colon) {
+ struct in_ifaddr *ifa;
+ struct in_device *in_dev = __in_dev_get(dev);
+ if (!in_dev)
+ return -ENODEV;
+ *colon = ':';
+ for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next)
+ if (strcmp(ifa->ifa_label, devname) == 0)
+ break;
+ if (ifa == NULL)
+ return -ENODEV;
+ rta->rta_prefsrc = &ifa->ifa_local;
+ }
+ }
+
+ ptr = &((struct sockaddr_in*)&r->rt_gateway)->sin_addr.s_addr;
+ if (r->rt_gateway.sa_family == AF_INET && *ptr) {
+ rta->rta_gw = ptr;
+ if (r->rt_flags&RTF_GATEWAY && inet_addr_type(*ptr) == RTN_UNICAST)
+ rtm->rtm_scope = RT_SCOPE_UNIVERSE;
+ }
+
+ if (cmd == SIOCDELRT)
+ return 0;
+
+ if (r->rt_flags&RTF_GATEWAY && rta->rta_gw == NULL)
+ return -EINVAL;
+
+ if (rtm->rtm_scope == RT_SCOPE_NOWHERE)
+ rtm->rtm_scope = RT_SCOPE_LINK;
+
+ if (r->rt_flags&(RTF_MTU|RTF_WINDOW|RTF_IRTT)) {
+ struct rtattr *rec;
+ struct rtattr *mx = kmalloc(RTA_LENGTH(3*RTA_LENGTH(4)), GFP_KERNEL);
+ if (mx == NULL)
+ return -ENOMEM;
+ rta->rta_mx = mx;
+ mx->rta_type = RTA_METRICS;
+ mx->rta_len = RTA_LENGTH(0);
+ if (r->rt_flags&RTF_MTU) {
+ rec = (void*)((char*)mx + RTA_ALIGN(mx->rta_len));
+ rec->rta_type = RTAX_ADVMSS;
+ rec->rta_len = RTA_LENGTH(4);
+ mx->rta_len += RTA_LENGTH(4);
+ *(u32*)RTA_DATA(rec) = r->rt_mtu - 40;
+ }
+ if (r->rt_flags&RTF_WINDOW) {
+ rec = (void*)((char*)mx + RTA_ALIGN(mx->rta_len));
+ rec->rta_type = RTAX_WINDOW;
+ rec->rta_len = RTA_LENGTH(4);
+ mx->rta_len += RTA_LENGTH(4);
+ *(u32*)RTA_DATA(rec) = r->rt_window;
+ }
+ if (r->rt_flags&RTF_IRTT) {
+ rec = (void*)((char*)mx + RTA_ALIGN(mx->rta_len));
+ rec->rta_type = RTAX_RTT;
+ rec->rta_len = RTA_LENGTH(4);
+ mx->rta_len += RTA_LENGTH(4);
+ *(u32*)RTA_DATA(rec) = r->rt_irtt<<3;
+ }
+ }
+ return 0;
+}
+
+#endif
+
+/*
+ Update FIB if:
+ - local address disappeared -> we must delete all the entries
+ referring to it.
+ - device went down -> we must shutdown all nexthops going via it.
+ */
+
+int fib_sync_down(u32 local, struct net_device *dev, int force)
+{
+ int ret = 0;
+ int scope = RT_SCOPE_NOWHERE;
+
+ if (force)
+ scope = -1;
+
+ for_fib_info() {
+ if (local && fi->fib_prefsrc == local) {
+ fi->fib_flags |= RTNH_F_DEAD;
+ ret++;
+ } else if (dev && fi->fib_nhs) {
+ int dead = 0;
+
+ change_nexthops(fi) {
+ if (nh->nh_flags&RTNH_F_DEAD)
+ dead++;
+ else if (nh->nh_dev == dev &&
+ nh->nh_scope != scope) {
+ nh->nh_flags |= RTNH_F_DEAD;
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ spin_lock_bh(&fib_multipath_lock);
+ fi->fib_power -= nh->nh_power;
+ nh->nh_power = 0;
+ spin_unlock_bh(&fib_multipath_lock);
+#endif
+ dead++;
+ }
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ if (force > 1 && nh->nh_dev == dev) {
+ dead = fi->fib_nhs;
+ break;
+ }
+#endif
+ } endfor_nexthops(fi)
+ if (dead == fi->fib_nhs) {
+ fi->fib_flags |= RTNH_F_DEAD;
+ ret++;
+ }
+ }
+ } endfor_fib_info();
+ return ret;
+}
+
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+
+/*
+ Dead device goes up. We wake up dead nexthops.
+ It takes sense only on multipath routes.
+ */
+
+int fib_sync_up(struct net_device *dev)
+{
+ int ret = 0;
+
+ if (!(dev->flags&IFF_UP))
+ return 0;
+
+ for_fib_info() {
+ int alive = 0;
+
+ change_nexthops(fi) {
+ if (!(nh->nh_flags&RTNH_F_DEAD)) {
+ alive++;
+ continue;
+ }
+ if (nh->nh_dev == NULL || !(nh->nh_dev->flags&IFF_UP))
+ continue;
+ if (nh->nh_dev != dev || __in_dev_get(dev) == NULL)
+ continue;
+ alive++;
+ spin_lock_bh(&fib_multipath_lock);
+ nh->nh_power = 0;
+ nh->nh_flags &= ~RTNH_F_DEAD;
+ spin_unlock_bh(&fib_multipath_lock);
+ } endfor_nexthops(fi)
+
+ if (alive > 0) {
+ fi->fib_flags &= ~RTNH_F_DEAD;
+ ret++;
+ }
+ } endfor_fib_info();
+ return ret;
+}
+
+/*
+ The algorithm is suboptimal, but it provides really
+ fair weighted route distribution.
+ */
+
+void fib_select_multipath(const struct rt_key *key, struct fib_result *res)
+{
+ struct fib_info *fi = res->fi;
+ int w;
+
+ spin_lock_bh(&fib_multipath_lock);
+ if (fi->fib_power <= 0) {
+ int power = 0;
+ change_nexthops(fi) {
+ if (!(nh->nh_flags&RTNH_F_DEAD)) {
+ power += nh->nh_weight;
+ nh->nh_power = nh->nh_weight;
+ }
+ } endfor_nexthops(fi);
+ fi->fib_power = power;
+ if (power <= 0) {
+ spin_unlock_bh(&fib_multipath_lock);
+ /* Race condition: route has just become dead. */
+ res->nh_sel = 0;
+ return;
+ }
+ }
+
+
+ /* w should be random number [0..fi->fib_power-1],
+ it is pretty bad approximation.
+ */
+
+#ifdef CONFIG_IP_ROUTE_MULTIPATH_SEQUENTIAL
+ w = mp_counter++ % fi->fib_power;
+#else
+ w = jiffies % fi->fib_power;
+#endif
+
+ change_nexthops(fi) {
+ if (!(nh->nh_flags&RTNH_F_DEAD) && nh->nh_power) {
+ if ((w -= nh->nh_power) <= 0) {
+ nh->nh_power--;
+ fi->fib_power--;
+ res->nh_sel = nhsel;
+ spin_unlock_bh(&fib_multipath_lock);
+ return;
+ }
+ }
+ } endfor_nexthops(fi);
+
+ /* Race condition: route has just become dead. */
+ res->nh_sel = 0;
+ spin_unlock_bh(&fib_multipath_lock);
+}
+#endif
+
+
+#ifdef CONFIG_PROC_FS
+
+static unsigned fib_flag_trans(int type, int dead, u32 mask, struct fib_info *fi)
+{
+ static unsigned type2flags[RTN_MAX+1] = {
+ 0, 0, 0, 0, 0, 0, 0, RTF_REJECT, RTF_REJECT, 0, 0, 0
+ };
+ unsigned flags = type2flags[type];
+
+ if (fi && fi->fib_nh->nh_gw)
+ flags |= RTF_GATEWAY;
+ if (mask == 0xFFFFFFFF)
+ flags |= RTF_HOST;
+ if (!dead)
+ flags |= RTF_UP;
+ return flags;
+}
+
+void fib_node_get_info(int type, int dead, struct fib_info *fi, u32 prefix, u32 mask, char *buffer)
+{
+ int len;
+ unsigned flags = fib_flag_trans(type, dead, mask, fi);
+
+ if (fi) {
+ len = sprintf(buffer, "%s\t%08X\t%08X\t%04X\t%d\t%u\t%d\t%08X\t%d\t%u\t%u",
+ fi->fib_dev ? fi->fib_dev->name : "*", prefix,
+ fi->fib_nh->nh_gw, flags, 0, 0, fi->fib_priority,
+ mask, (fi->fib_advmss ? fi->fib_advmss+40 : 0),
+ fi->fib_window, fi->fib_rtt>>3);
+ } else {
+ len = sprintf(buffer, "*\t%08X\t%08X\t%04X\t%d\t%u\t%d\t%08X\t%d\t%u\t%u",
+ prefix, 0,
+ flags, 0, 0, 0,
+ mask, 0, 0, 0);
+ }
+ memset(buffer+len, ' ', 127-len);
+ buffer[127] = '\n';
+}
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/ipv4/icmp.c b/uClinux-2.4.31-uc0/net/ipv4/icmp.c
new file mode 100644
index 0000000..923898b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/icmp.c
@@ -0,0 +1,1007 @@
+/*
+ * NET3: Implementation of the ICMP protocol layer.
+ *
+ * Alan Cox, <alan@redhat.com>
+ *
+ * Version: $Id: icmp.c,v 1.82.2.1 2001/12/13 08:59:27 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Some of the function names and the icmp unreach table for this
+ * module were derived from [icmp.c 1.0.11 06/02/93] by
+ * Ross Biro, Fred N. van Kempen, Mark Evans, Alan Cox, Gerhard Koerting.
+ * Other than that this module is a complete rewrite.
+ *
+ * Fixes:
+ * Clemens Fruhwirth : introduce global icmp rate limiting
+ * with icmp type masking ability instead
+ * of broken per type icmp timeouts.
+ * Mike Shaver : RFC1122 checks.
+ * Alan Cox : Multicast ping reply as self.
+ * Alan Cox : Fix atomicity lockup in ip_build_xmit
+ * call.
+ * Alan Cox : Added 216,128 byte paths to the MTU
+ * code.
+ * Martin Mares : RFC1812 checks.
+ * Martin Mares : Can be configured to follow redirects
+ * if acting as a router _without_ a
+ * routing protocol (RFC 1812).
+ * Martin Mares : Echo requests may be configured to
+ * be ignored (RFC 1812).
+ * Martin Mares : Limitation of ICMP error message
+ * transmit rate (RFC 1812).
+ * Martin Mares : TOS and Precedence set correctly
+ * (RFC 1812).
+ * Martin Mares : Now copying as much data from the
+ * original packet as we can without
+ * exceeding 576 bytes (RFC 1812).
+ * Willy Konynenberg : Transparent proxying support.
+ * Keith Owens : RFC1191 correction for 4.2BSD based
+ * path MTU bug.
+ * Thomas Quinot : ICMP Dest Unreach codes up to 15 are
+ * valid (RFC 1812).
+ * Andi Kleen : Check all packet lengths properly
+ * and moved all kfree_skb() up to
+ * icmp_rcv.
+ * Andi Kleen : Move the rate limit bookkeeping
+ * into the dest entry and use a token
+ * bucket filter (thanks to ANK). Make
+ * the rates sysctl configurable.
+ * Yu Tianli : Fixed two ugly bugs in icmp_send
+ * - IP option length was accounted wrongly
+ * - ICMP header length was not accounted at all.
+ * Tristan Greaves : Added sysctl option to ignore bogus broadcast
+ * responses from broken routers.
+ *
+ * To Fix:
+ *
+ * - Should use skb_pull() instead of all the manual checking.
+ * This would also greatly simply some upper layer error handlers. --AK
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <linux/fcntl.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/string.h>
+#include <linux/netfilter_ipv4.h>
+#include <net/snmp.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <net/protocol.h>
+#include <net/icmp.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+#include <net/raw.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <linux/errno.h>
+#include <linux/timer.h>
+#include <linux/init.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <net/checksum.h>
+
+/*
+ * Build xmit assembly blocks
+ */
+
+struct icmp_bxm
+{
+ struct sk_buff *skb;
+ int offset;
+ int data_len;
+
+ unsigned int csum;
+ struct {
+ struct icmphdr icmph;
+ __u32 times[3];
+ } data;
+ int head_len;
+ struct ip_options replyopts;
+ unsigned char optbuf[40];
+};
+
+/*
+ * Statistics
+ */
+
+struct icmp_mib icmp_statistics[NR_CPUS*2];
+
+/* An array of errno for error messages from dest unreach. */
+/* RFC 1122: 3.2.2.1 States that NET_UNREACH, HOS_UNREACH and SR_FAIELD MUST be considered 'transient errs'. */
+
+struct icmp_err icmp_err_convert[] = {
+ { ENETUNREACH, 0 }, /* ICMP_NET_UNREACH */
+ { EHOSTUNREACH, 0 }, /* ICMP_HOST_UNREACH */
+ { ENOPROTOOPT, 1 }, /* ICMP_PROT_UNREACH */
+ { ECONNREFUSED, 1 }, /* ICMP_PORT_UNREACH */
+ { EMSGSIZE, 0 }, /* ICMP_FRAG_NEEDED */
+ { EOPNOTSUPP, 0 }, /* ICMP_SR_FAILED */
+ { ENETUNREACH, 1 }, /* ICMP_NET_UNKNOWN */
+ { EHOSTDOWN, 1 }, /* ICMP_HOST_UNKNOWN */
+ { ENONET, 1 }, /* ICMP_HOST_ISOLATED */
+ { ENETUNREACH, 1 }, /* ICMP_NET_ANO */
+ { EHOSTUNREACH, 1 }, /* ICMP_HOST_ANO */
+ { ENETUNREACH, 0 }, /* ICMP_NET_UNR_TOS */
+ { EHOSTUNREACH, 0 }, /* ICMP_HOST_UNR_TOS */
+ { EHOSTUNREACH, 1 }, /* ICMP_PKT_FILTERED */
+ { EHOSTUNREACH, 1 }, /* ICMP_PREC_VIOLATION */
+ { EHOSTUNREACH, 1 } /* ICMP_PREC_CUTOFF */
+};
+
+extern int sysctl_ip_default_ttl;
+
+/* Control parameters for ECHO replies. */
+int sysctl_icmp_echo_ignore_all;
+int sysctl_icmp_echo_ignore_broadcasts;
+
+/* Control parameter - ignore bogus broadcast responses? */
+int sysctl_icmp_ignore_bogus_error_responses;
+
+/*
+ * Configurable global rate limit.
+ *
+ * ratelimit defines tokens/packet consumed for dst->rate_token bucket
+ * ratemask defines which icmp types are ratelimited by setting
+ * it's bit position.
+ *
+ * default:
+ * dest unreachable (3), source quench (4),
+ * time exceeded (11), parameter problem (12)
+ */
+
+int sysctl_icmp_ratelimit = 1*HZ;
+int sysctl_icmp_ratemask = 0x1818;
+
+/*
+ * ICMP control array. This specifies what to do with each ICMP.
+ */
+
+struct icmp_control
+{
+ unsigned long *output; /* Address to increment on output */
+ unsigned long *input; /* Address to increment on input */
+ void (*handler)(struct sk_buff *skb);
+ short error; /* This ICMP is classed as an error message */
+};
+
+static struct icmp_control icmp_pointers[NR_ICMP_TYPES+1];
+
+/*
+ * The ICMP socket(s). This is the most convenient way to flow control
+ * our ICMP output as well as maintain a clean interface throughout
+ * all layers. All Socketless IP sends will soon be gone.
+ */
+
+static struct inode __icmp_inode[NR_CPUS];
+#define icmp_socket (&__icmp_inode[smp_processor_id()].u.socket_i)
+#define icmp_socket_cpu(X) (&__icmp_inode[(X)].u.socket_i)
+
+static int icmp_xmit_lock(void)
+{
+ local_bh_disable();
+ if (unlikely(!spin_trylock(&icmp_socket->sk->lock.slock))) {
+ /* This can happen if the output path signals a
+ * dst_link_failure() for an outgoing ICMP packet.
+ */
+ local_bh_enable();
+ return 1;
+ }
+ return 0;
+}
+
+static void icmp_xmit_unlock(void)
+{
+ spin_unlock_bh(&icmp_socket->sk->lock.slock);
+}
+
+/*
+ * Send an ICMP frame.
+ */
+
+/*
+ * Check transmit rate limitation for given message.
+ * The rate information is held in the destination cache now.
+ * This function is generic and could be used for other purposes
+ * too. It uses a Token bucket filter as suggested by Alexey Kuznetsov.
+ *
+ * Note that the same dst_entry fields are modified by functions in
+ * route.c too, but these work for packet destinations while xrlim_allow
+ * works for icmp destinations. This means the rate limiting information
+ * for one "ip object" is shared - and these ICMPs are twice limited:
+ * by source and by destination.
+ *
+ * RFC 1812: 4.3.2.8 SHOULD be able to limit error message rate
+ * SHOULD allow setting of rate limits
+ *
+ * Shared between ICMPv4 and ICMPv6.
+ */
+#define XRLIM_BURST_FACTOR 6
+int xrlim_allow(struct dst_entry *dst, int timeout)
+{
+ unsigned long now;
+
+ now = jiffies;
+ dst->rate_tokens += now - dst->rate_last;
+ dst->rate_last = now;
+ if (dst->rate_tokens > XRLIM_BURST_FACTOR*timeout)
+ dst->rate_tokens = XRLIM_BURST_FACTOR*timeout;
+ if (dst->rate_tokens >= timeout) {
+ dst->rate_tokens -= timeout;
+ return 1;
+ }
+ return 0;
+}
+
+static inline int icmpv4_xrlim_allow(struct rtable *rt, int type, int code)
+{
+ struct dst_entry *dst = &rt->u.dst;
+
+ if (type > NR_ICMP_TYPES)
+ return 1;
+
+ /* Don't limit PMTU discovery. */
+ if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED)
+ return 1;
+
+ /* No rate limit on loopback */
+ if (dst->dev && (dst->dev->flags&IFF_LOOPBACK))
+ return 1;
+
+ /* Limit if icmp type is enabled in ratemask. */
+ if((1 << type) & sysctl_icmp_ratemask)
+ return xrlim_allow(dst, sysctl_icmp_ratelimit);
+ else
+ return 1;
+}
+
+/*
+ * Maintain the counters used in the SNMP statistics for outgoing ICMP
+ */
+
+static void icmp_out_count(int type)
+{
+ if (type>NR_ICMP_TYPES)
+ return;
+ (icmp_pointers[type].output)[(smp_processor_id()*2+!in_softirq())*sizeof(struct icmp_mib)/sizeof(unsigned long)]++;
+ ICMP_INC_STATS(IcmpOutMsgs);
+}
+
+/*
+ * Checksum each fragment, and on the first include the headers and final checksum.
+ */
+
+static int icmp_glue_bits(const void *p, char *to, unsigned int offset,
+ unsigned int fraglen, struct sk_buff *skb)
+{
+ struct icmp_bxm *icmp_param = (struct icmp_bxm *)p;
+ struct icmphdr *icmph;
+ unsigned int csum;
+
+ if (icmp_pointers[icmp_param->data.icmph.type].error)
+ nf_ct_attach(skb, icmp_param->skb);
+
+ if (offset) {
+ icmp_param->csum=skb_copy_and_csum_bits(icmp_param->skb,
+ icmp_param->offset+(offset-icmp_param->head_len),
+ to, fraglen,icmp_param->csum);
+ return 0;
+ }
+
+ /*
+ * First fragment includes header. Note that we've done
+ * the other fragments first, so that we get the checksum
+ * for the whole packet here.
+ */
+ csum = csum_partial_copy_nocheck((void *)&icmp_param->data,
+ to, icmp_param->head_len,
+ icmp_param->csum);
+ csum=skb_copy_and_csum_bits(icmp_param->skb,
+ icmp_param->offset,
+ to+icmp_param->head_len,
+ fraglen-icmp_param->head_len,
+ csum);
+ icmph=(struct icmphdr *)to;
+ icmph->checksum = csum_fold(csum);
+ return 0;
+}
+
+/*
+ * Driving logic for building and sending ICMP messages.
+ */
+
+static void icmp_reply(struct icmp_bxm *icmp_param, struct sk_buff *skb)
+{
+ struct sock *sk=icmp_socket->sk;
+ struct ipcm_cookie ipc;
+ struct rtable *rt = (struct rtable*)skb->dst;
+ u32 daddr;
+
+ if (ip_options_echo(&icmp_param->replyopts, skb))
+ return;
+
+ if (icmp_xmit_lock())
+ return;
+
+ icmp_param->data.icmph.checksum=0;
+ icmp_param->csum=0;
+ icmp_out_count(icmp_param->data.icmph.type);
+
+ sk->protinfo.af_inet.tos = skb->nh.iph->tos;
+ sk->protinfo.af_inet.ttl = sysctl_ip_default_ttl;
+ daddr = ipc.addr = rt->rt_src;
+ ipc.opt = NULL;
+ if (icmp_param->replyopts.optlen) {
+ ipc.opt = &icmp_param->replyopts;
+ if (ipc.opt->srr)
+ daddr = icmp_param->replyopts.faddr;
+ }
+ if (ip_route_output(&rt, daddr, rt->rt_spec_dst, RT_TOS(skb->nh.iph->tos), 0))
+ goto out;
+ if (icmpv4_xrlim_allow(rt, icmp_param->data.icmph.type,
+ icmp_param->data.icmph.code)) {
+ ip_build_xmit(sk, icmp_glue_bits, icmp_param,
+ icmp_param->data_len+icmp_param->head_len,
+ &ipc, rt, MSG_DONTWAIT);
+ }
+ ip_rt_put(rt);
+out:
+ icmp_xmit_unlock();
+}
+
+
+/*
+ * Send an ICMP message in response to a situation
+ *
+ * RFC 1122: 3.2.2 MUST send at least the IP header and 8 bytes of header. MAY send more (we do).
+ * MUST NOT change this header information.
+ * MUST NOT reply to a multicast/broadcast IP address.
+ * MUST NOT reply to a multicast/broadcast MAC address.
+ * MUST reply to only the first fragment.
+ */
+
+void icmp_send(struct sk_buff *skb_in, int type, int code, u32 info)
+{
+ struct iphdr *iph;
+ int room;
+ struct icmp_bxm icmp_param;
+ struct rtable *rt = (struct rtable*)skb_in->dst;
+ struct ipcm_cookie ipc;
+ u32 saddr;
+ u8 tos;
+
+ if (!rt)
+ return;
+
+ /*
+ * Find the original header. It is expected to be valid, of course.
+ * Check this, icmp_send is called from the most obscure devices
+ * sometimes.
+ */
+ iph = skb_in->nh.iph;
+
+ if ((u8*)iph < skb_in->head || (u8*)(iph+1) > skb_in->tail)
+ return;
+
+ /*
+ * No replies to physical multicast/broadcast
+ */
+ if (skb_in->pkt_type!=PACKET_HOST)
+ return;
+
+ /*
+ * Now check at the protocol level
+ */
+ if (rt->rt_flags&(RTCF_BROADCAST|RTCF_MULTICAST))
+ return;
+
+ /*
+ * Only reply to fragment 0. We byte re-order the constant
+ * mask for efficiency.
+ */
+ if (iph->frag_off&htons(IP_OFFSET))
+ return;
+
+ /*
+ * If we send an ICMP error to an ICMP error a mess would result..
+ */
+ if (icmp_pointers[type].error) {
+ /*
+ * We are an error, check if we are replying to an ICMP error
+ */
+ if (iph->protocol==IPPROTO_ICMP) {
+ u8 inner_type;
+
+ if (skb_copy_bits(skb_in,
+ skb_in->nh.raw + (iph->ihl<<2)
+ + offsetof(struct icmphdr, type)
+ - skb_in->data,
+ &inner_type, 1))
+ return;
+
+ /*
+ * Assume any unknown ICMP type is an error. This isn't
+ * specified by the RFC, but think about it..
+ */
+ if (inner_type>NR_ICMP_TYPES || icmp_pointers[inner_type].error)
+ return;
+ }
+ }
+
+ if (icmp_xmit_lock())
+ return;
+
+ /*
+ * Construct source address and options.
+ */
+
+#ifdef CONFIG_IP_ROUTE_NAT
+ /*
+ * Restore original addresses if packet has been translated.
+ */
+ if (rt->rt_flags&RTCF_NAT && IPCB(skb_in)->flags&IPSKB_TRANSLATED) {
+ iph->daddr = rt->key.dst;
+ iph->saddr = rt->key.src;
+ }
+#endif
+
+ saddr = iph->daddr;
+ if (!(rt->rt_flags & RTCF_LOCAL))
+ saddr = 0;
+
+ tos = icmp_pointers[type].error ?
+ ((iph->tos & IPTOS_TOS_MASK) | IPTOS_PREC_INTERNETCONTROL) :
+ iph->tos;
+
+ if (ip_route_output(&rt, iph->saddr, saddr, RT_TOS(tos), 0))
+ goto out;
+
+ if (ip_options_echo(&icmp_param.replyopts, skb_in))
+ goto ende;
+
+
+ /*
+ * Prepare data for ICMP header.
+ */
+
+ icmp_param.data.icmph.type=type;
+ icmp_param.data.icmph.code=code;
+ icmp_param.data.icmph.un.gateway = info;
+ icmp_param.data.icmph.checksum=0;
+ icmp_param.csum=0;
+ icmp_param.skb=skb_in;
+ icmp_param.offset=skb_in->nh.raw - skb_in->data;
+ icmp_out_count(icmp_param.data.icmph.type);
+ icmp_socket->sk->protinfo.af_inet.tos = tos;
+ icmp_socket->sk->protinfo.af_inet.ttl = sysctl_ip_default_ttl;
+ ipc.addr = iph->saddr;
+ ipc.opt = &icmp_param.replyopts;
+ if (icmp_param.replyopts.srr) {
+ ip_rt_put(rt);
+ if (ip_route_output(&rt, icmp_param.replyopts.faddr, saddr, RT_TOS(tos), 0))
+ goto out;
+ }
+
+ if (!icmpv4_xrlim_allow(rt, type, code))
+ goto ende;
+
+ /* RFC says return as much as we can without exceeding 576 bytes. */
+
+ room = rt->u.dst.pmtu;
+ if (room > 576)
+ room = 576;
+ room -= sizeof(struct iphdr) + icmp_param.replyopts.optlen;
+ room -= sizeof(struct icmphdr);
+
+ icmp_param.data_len=skb_in->len-icmp_param.offset;
+ if (icmp_param.data_len > room)
+ icmp_param.data_len = room;
+ icmp_param.head_len = sizeof(struct icmphdr);
+
+ ip_build_xmit(icmp_socket->sk, icmp_glue_bits, &icmp_param,
+ icmp_param.data_len+sizeof(struct icmphdr),
+ &ipc, rt, MSG_DONTWAIT);
+
+ende:
+ ip_rt_put(rt);
+out:
+ icmp_xmit_unlock();
+}
+
+
+/*
+ * Handle ICMP_DEST_UNREACH, ICMP_TIME_EXCEED, and ICMP_QUENCH.
+ */
+
+static void icmp_unreach(struct sk_buff *skb)
+{
+ struct iphdr *iph;
+ struct icmphdr *icmph;
+ int hash, protocol;
+ struct inet_protocol *ipprot;
+ struct sock *raw_sk;
+ u32 info = 0;
+
+ /*
+ * Incomplete header ?
+ * Only checks for the IP header, there should be an
+ * additional check for longer headers in upper levels.
+ */
+
+ if (!pskb_may_pull(skb, sizeof(struct iphdr))) {
+ ICMP_INC_STATS_BH(IcmpInErrors);
+ return;
+ }
+
+ icmph = skb->h.icmph;
+ iph = (struct iphdr *) skb->data;
+
+ if (iph->ihl<5) {
+ /* Mangled header, drop. */
+ ICMP_INC_STATS_BH(IcmpInErrors);
+ return;
+ }
+
+ if(icmph->type==ICMP_DEST_UNREACH) {
+ switch(icmph->code & 15) {
+ case ICMP_NET_UNREACH:
+ break;
+ case ICMP_HOST_UNREACH:
+ break;
+ case ICMP_PROT_UNREACH:
+ break;
+ case ICMP_PORT_UNREACH:
+ break;
+ case ICMP_FRAG_NEEDED:
+ if (ipv4_config.no_pmtu_disc) {
+ if (net_ratelimit())
+ printk(KERN_INFO "ICMP: %u.%u.%u.%u: fragmentation needed and DF set.\n",
+ NIPQUAD(iph->daddr));
+ } else {
+ info = ip_rt_frag_needed(iph, ntohs(icmph->un.frag.mtu));
+ if (!info)
+ goto out;
+ }
+ break;
+ case ICMP_SR_FAILED:
+ if (net_ratelimit())
+ printk(KERN_INFO "ICMP: %u.%u.%u.%u: Source Route Failed.\n", NIPQUAD(iph->daddr));
+ break;
+ default:
+ break;
+ }
+ if (icmph->code>NR_ICMP_UNREACH)
+ goto out;
+ } else if (icmph->type == ICMP_PARAMETERPROB) {
+ info = ntohl(icmph->un.gateway)>>24;
+ }
+
+ /*
+ * Throw it at our lower layers
+ *
+ * RFC 1122: 3.2.2 MUST extract the protocol ID from the passed header.
+ * RFC 1122: 3.2.2.1 MUST pass ICMP unreach messages to the transport layer.
+ * RFC 1122: 3.2.2.2 MUST pass ICMP time expired messages to transport layer.
+ */
+
+ /*
+ * Check the other end isnt violating RFC 1122. Some routers send
+ * bogus responses to broadcast frames. If you see this message
+ * first check your netmask matches at both ends, if it does then
+ * get the other vendor to fix their kit.
+ */
+
+ if (!sysctl_icmp_ignore_bogus_error_responses)
+ {
+
+ if (inet_addr_type(iph->daddr) == RTN_BROADCAST)
+ {
+ if (net_ratelimit())
+ printk(KERN_WARNING "%u.%u.%u.%u sent an invalid ICMP type %u, code %u error to a broadcast: %u.%u.%u.%u on %s\n",
+ NIPQUAD(skb->nh.iph->saddr),
+ icmph->type, icmph->code,
+ NIPQUAD(iph->daddr),
+ skb->dev->name);
+ goto out;
+ }
+ }
+
+ /* Checkin full IP header plus 8 bytes of protocol to
+ * avoid additional coding at protocol handlers.
+ */
+ if (!pskb_may_pull(skb, iph->ihl*4+8))
+ goto out;
+
+ iph = (struct iphdr *) skb->data;
+ protocol = iph->protocol;
+
+ /*
+ * Deliver ICMP message to raw sockets. Pretty useless feature?
+ */
+
+ /* Note: See raw.c and net/raw.h, RAWV4_HTABLE_SIZE==MAX_INET_PROTOS */
+ hash = protocol & (MAX_INET_PROTOS - 1);
+ read_lock(&raw_v4_lock);
+ if ((raw_sk = raw_v4_htable[hash]) != NULL)
+ {
+ while ((raw_sk = __raw_v4_lookup(raw_sk, protocol, iph->daddr,
+ iph->saddr, skb->dev->ifindex)) != NULL) {
+ raw_err(raw_sk, skb, info);
+ raw_sk = raw_sk->next;
+ iph = (struct iphdr *)skb->data;
+ }
+ }
+ read_unlock(&raw_v4_lock);
+
+ /*
+ * This can't change while we are doing it.
+ * Callers have obtained BR_NETPROTO_LOCK so
+ * we are OK.
+ */
+
+ ipprot = (struct inet_protocol *) inet_protos[hash];
+ while (ipprot) {
+ struct inet_protocol *nextip;
+
+ nextip = (struct inet_protocol *) ipprot->next;
+
+ /*
+ * Pass it off to everyone who wants it.
+ */
+
+ /* RFC1122: OK. Passes appropriate ICMP errors to the */
+ /* appropriate protocol layer (MUST), as per 3.2.2. */
+
+ if (protocol == ipprot->protocol && ipprot->err_handler)
+ ipprot->err_handler(skb, info);
+
+ ipprot = nextip;
+ }
+out:;
+}
+
+
+/*
+ * Handle ICMP_REDIRECT.
+ */
+
+static void icmp_redirect(struct sk_buff *skb)
+{
+ struct iphdr *iph;
+ unsigned long ip;
+
+ if (skb->len < sizeof(struct iphdr)) {
+ ICMP_INC_STATS_BH(IcmpInErrors);
+ return;
+ }
+
+ /*
+ * Get the copied header of the packet that caused the redirect
+ */
+ if (!pskb_may_pull(skb, sizeof(struct iphdr)))
+ return;
+
+ iph = (struct iphdr *) skb->data;
+ ip = iph->daddr;
+
+ switch (skb->h.icmph->code & 7) {
+ case ICMP_REDIR_NET:
+ case ICMP_REDIR_NETTOS:
+ /*
+ * As per RFC recommendations now handle it as
+ * a host redirect.
+ */
+
+ case ICMP_REDIR_HOST:
+ case ICMP_REDIR_HOSTTOS:
+ ip_rt_redirect(skb->nh.iph->saddr, ip, skb->h.icmph->un.gateway, iph->saddr, iph->tos, skb->dev);
+ break;
+ default:
+ break;
+ }
+}
+
+/*
+ * Handle ICMP_ECHO ("ping") requests.
+ *
+ * RFC 1122: 3.2.2.6 MUST have an echo server that answers ICMP echo requests.
+ * RFC 1122: 3.2.2.6 Data received in the ICMP_ECHO request MUST be included in the reply.
+ * RFC 1812: 4.3.3.6 SHOULD have a config option for silently ignoring echo requests, MUST have default=NOT.
+ * See also WRT handling of options once they are done and working.
+ */
+
+static void icmp_echo(struct sk_buff *skb)
+{
+ if (!sysctl_icmp_echo_ignore_all) {
+ struct icmp_bxm icmp_param;
+
+ icmp_param.data.icmph=*skb->h.icmph;
+ icmp_param.data.icmph.type=ICMP_ECHOREPLY;
+ icmp_param.skb=skb;
+ icmp_param.offset=0;
+ icmp_param.data_len=skb->len;
+ icmp_param.head_len=sizeof(struct icmphdr);
+ icmp_reply(&icmp_param, skb);
+ }
+}
+
+/*
+ * Handle ICMP Timestamp requests.
+ * RFC 1122: 3.2.2.8 MAY implement ICMP timestamp requests.
+ * SHOULD be in the kernel for minimum random latency.
+ * MUST be accurate to a few minutes.
+ * MUST be updated at least at 15Hz.
+ */
+
+static void icmp_timestamp(struct sk_buff *skb)
+{
+ struct timeval tv;
+ struct icmp_bxm icmp_param;
+
+ /*
+ * Too short.
+ */
+
+ if (skb->len < 4) {
+ ICMP_INC_STATS_BH(IcmpInErrors);
+ return;
+ }
+
+ /*
+ * Fill in the current time as ms since midnight UT:
+ */
+ do_gettimeofday(&tv);
+ icmp_param.data.times[1] = htonl((tv.tv_sec % 86400) * 1000 + tv.tv_usec / 1000);
+ icmp_param.data.times[2] = icmp_param.data.times[1];
+ if (skb_copy_bits(skb, 0, &icmp_param.data.times[0], 4))
+ BUG();
+ icmp_param.data.icmph=*skb->h.icmph;
+ icmp_param.data.icmph.type=ICMP_TIMESTAMPREPLY;
+ icmp_param.data.icmph.code=0;
+ icmp_param.skb=skb;
+ icmp_param.offset=0;
+ icmp_param.data_len=0;
+ icmp_param.head_len=sizeof(struct icmphdr)+12;
+ icmp_reply(&icmp_param, skb);
+}
+
+
+/*
+ * Handle ICMP_ADDRESS_MASK requests. (RFC950)
+ *
+ * RFC1122 (3.2.2.9). A host MUST only send replies to
+ * ADDRESS_MASK requests if it's been configured as an address mask
+ * agent. Receiving a request doesn't constitute implicit permission to
+ * act as one. Of course, implementing this correctly requires (SHOULD)
+ * a way to turn the functionality on and off. Another one for sysctl(),
+ * I guess. -- MS
+ *
+ * RFC1812 (4.3.3.9). A router MUST implement it.
+ * A router SHOULD have switch turning it on/off.
+ * This switch MUST be ON by default.
+ *
+ * Gratuitous replies, zero-source replies are not implemented,
+ * that complies with RFC. DO NOT implement them!!! All the idea
+ * of broadcast addrmask replies as specified in RFC950 is broken.
+ * The problem is that it is not uncommon to have several prefixes
+ * on one physical interface. Moreover, addrmask agent can even be
+ * not aware of existing another prefixes.
+ * If source is zero, addrmask agent cannot choose correct prefix.
+ * Gratuitous mask announcements suffer from the same problem.
+ * RFC1812 explains it, but still allows to use ADDRMASK,
+ * that is pretty silly. --ANK
+ *
+ * All these rules are so bizarre, that I removed kernel addrmask
+ * support at all. It is wrong, it is obsolete, nobody uses it in
+ * any case. --ANK
+ *
+ * Furthermore you can do it with a usermode address agent program
+ * anyway...
+ */
+
+static void icmp_address(struct sk_buff *skb)
+{
+#if 0
+ if (net_ratelimit())
+ printk(KERN_DEBUG "a guy asks for address mask. Who is it?\n");
+#endif
+}
+
+/*
+ * RFC1812 (4.3.3.9). A router SHOULD listen all replies, and complain
+ * loudly if an inconsistency is found.
+ */
+
+static void icmp_address_reply(struct sk_buff *skb)
+{
+ struct rtable *rt = (struct rtable*)skb->dst;
+ struct net_device *dev = skb->dev;
+ struct in_device *in_dev;
+ struct in_ifaddr *ifa;
+ u32 mask;
+
+ if (skb->len < 4 || !(rt->rt_flags&RTCF_DIRECTSRC))
+ return;
+
+ in_dev = in_dev_get(dev);
+ if (!in_dev)
+ return;
+ read_lock(&in_dev->lock);
+ if (in_dev->ifa_list &&
+ IN_DEV_LOG_MARTIANS(in_dev) &&
+ IN_DEV_FORWARD(in_dev)) {
+ if (skb_copy_bits(skb, 0, &mask, 4))
+ BUG();
+ for (ifa=in_dev->ifa_list; ifa; ifa = ifa->ifa_next) {
+ if (mask == ifa->ifa_mask && inet_ifa_match(rt->rt_src, ifa))
+ break;
+ }
+ if (!ifa && net_ratelimit()) {
+ printk(KERN_INFO "Wrong address mask %u.%u.%u.%u from %s/%u.%u.%u.%u\n",
+ NIPQUAD(mask), dev->name, NIPQUAD(rt->rt_src));
+ }
+ }
+ read_unlock(&in_dev->lock);
+ in_dev_put(in_dev);
+}
+
+static void icmp_discard(struct sk_buff *skb)
+{
+}
+
+/*
+ * Deal with incoming ICMP packets.
+ */
+
+int icmp_rcv(struct sk_buff *skb)
+{
+ struct icmphdr *icmph;
+ struct rtable *rt = (struct rtable*)skb->dst;
+
+ ICMP_INC_STATS_BH(IcmpInMsgs);
+
+ switch (skb->ip_summed) {
+ case CHECKSUM_HW:
+ if ((u16)csum_fold(skb->csum) == 0)
+ break;
+ NETDEBUG(if (net_ratelimit()) printk(KERN_DEBUG "icmp v4 hw csum failure\n"));
+ case CHECKSUM_NONE:
+ if ((u16)csum_fold(skb_checksum(skb, 0, skb->len, 0)))
+ goto error;
+ default:;
+ }
+
+ if (!pskb_pull(skb, sizeof(struct icmphdr)))
+ goto error;
+
+ icmph = skb->h.icmph;
+
+ /*
+ * 18 is the highest 'known' ICMP type. Anything else is a mystery
+ *
+ * RFC 1122: 3.2.2 Unknown ICMP messages types MUST be silently discarded.
+ */
+ if (icmph->type > NR_ICMP_TYPES)
+ goto error;
+
+
+ /*
+ * Parse the ICMP message
+ */
+
+ if (rt->rt_flags&(RTCF_BROADCAST|RTCF_MULTICAST)) {
+ /*
+ * RFC 1122: 3.2.2.6 An ICMP_ECHO to broadcast MAY be
+ * silently ignored (we let user decide with a sysctl).
+ * RFC 1122: 3.2.2.8 An ICMP_TIMESTAMP MAY be silently
+ * discarded if to broadcast/multicast.
+ */
+ if (icmph->type == ICMP_ECHO &&
+ sysctl_icmp_echo_ignore_broadcasts) {
+ goto error;
+ }
+ if (icmph->type != ICMP_ECHO &&
+ icmph->type != ICMP_TIMESTAMP &&
+ icmph->type != ICMP_ADDRESS &&
+ icmph->type != ICMP_ADDRESSREPLY) {
+ goto error;
+ }
+ }
+
+ icmp_pointers[icmph->type].input[smp_processor_id()*2*sizeof(struct icmp_mib)/sizeof(unsigned long)]++;
+ (icmp_pointers[icmph->type].handler)(skb);
+
+drop:
+ kfree_skb(skb);
+ return 0;
+error:
+ ICMP_INC_STATS_BH(IcmpInErrors);
+ goto drop;
+}
+
+/*
+ * This table is the definition of how we handle ICMP.
+ */
+
+static struct icmp_control icmp_pointers[NR_ICMP_TYPES+1] = {
+/* ECHO REPLY (0) */
+ { &icmp_statistics[0].IcmpOutEchoReps, &icmp_statistics[0].IcmpInEchoReps, icmp_discard, 0 },
+ { &icmp_statistics[0].dummy, &icmp_statistics[0].IcmpInErrors, icmp_discard, 1 },
+ { &icmp_statistics[0].dummy, &icmp_statistics[0].IcmpInErrors, icmp_discard, 1 },
+/* DEST UNREACH (3) */
+ { &icmp_statistics[0].IcmpOutDestUnreachs, &icmp_statistics[0].IcmpInDestUnreachs, icmp_unreach, 1 },
+/* SOURCE QUENCH (4) */
+ { &icmp_statistics[0].IcmpOutSrcQuenchs, &icmp_statistics[0].IcmpInSrcQuenchs, icmp_unreach, 1 },
+/* REDIRECT (5) */
+ { &icmp_statistics[0].IcmpOutRedirects, &icmp_statistics[0].IcmpInRedirects, icmp_redirect, 1 },
+ { &icmp_statistics[0].dummy, &icmp_statistics[0].IcmpInErrors, icmp_discard, 1 },
+ { &icmp_statistics[0].dummy, &icmp_statistics[0].IcmpInErrors, icmp_discard, 1 },
+/* ECHO (8) */
+ { &icmp_statistics[0].IcmpOutEchos, &icmp_statistics[0].IcmpInEchos, icmp_echo, 0 },
+ { &icmp_statistics[0].dummy, &icmp_statistics[0].IcmpInErrors, icmp_discard, 1 },
+ { &icmp_statistics[0].dummy, &icmp_statistics[0].IcmpInErrors, icmp_discard, 1 },
+/* TIME EXCEEDED (11) */
+ { &icmp_statistics[0].IcmpOutTimeExcds, &icmp_statistics[0].IcmpInTimeExcds, icmp_unreach, 1 },
+/* PARAMETER PROBLEM (12) */
+ { &icmp_statistics[0].IcmpOutParmProbs, &icmp_statistics[0].IcmpInParmProbs, icmp_unreach, 1 },
+/* TIMESTAMP (13) */
+ { &icmp_statistics[0].IcmpOutTimestamps, &icmp_statistics[0].IcmpInTimestamps, icmp_timestamp, 0 },
+/* TIMESTAMP REPLY (14) */
+ { &icmp_statistics[0].IcmpOutTimestampReps, &icmp_statistics[0].IcmpInTimestampReps, icmp_discard, 0 },
+/* INFO (15) */
+ { &icmp_statistics[0].dummy, &icmp_statistics[0].dummy, icmp_discard, 0 },
+/* INFO REPLY (16) */
+ { &icmp_statistics[0].dummy, &icmp_statistics[0].dummy, icmp_discard, 0 },
+/* ADDR MASK (17) */
+ { &icmp_statistics[0].IcmpOutAddrMasks, &icmp_statistics[0].IcmpInAddrMasks, icmp_address, 0 },
+/* ADDR MASK REPLY (18) */
+ { &icmp_statistics[0].IcmpOutAddrMaskReps, &icmp_statistics[0].IcmpInAddrMaskReps, icmp_address_reply, 0 }
+};
+
+void __init icmp_init(struct net_proto_family *ops)
+{
+ int err, i;
+
+ for (i = 0; i < NR_CPUS; i++) {
+ __icmp_inode[i].i_mode = S_IFSOCK;
+ __icmp_inode[i].i_sock = 1;
+ __icmp_inode[i].i_uid = 0;
+ __icmp_inode[i].i_gid = 0;
+ init_waitqueue_head(&__icmp_inode[i].i_wait);
+ init_waitqueue_head(&__icmp_inode[i].u.socket_i.wait);
+
+ icmp_socket_cpu(i)->inode = &__icmp_inode[i];
+ icmp_socket_cpu(i)->state = SS_UNCONNECTED;
+ icmp_socket_cpu(i)->type = SOCK_RAW;
+
+ if ((err=ops->create(icmp_socket_cpu(i), IPPROTO_ICMP)) < 0)
+ panic("Failed to create the ICMP control socket.\n");
+
+ icmp_socket_cpu(i)->sk->allocation=GFP_ATOMIC;
+
+ /* Enough space for 2 64K ICMP packets, including
+ * sk_buff struct overhead.
+ */
+ icmp_socket_cpu(i)->sk->sndbuf =
+ (2 * ((64 * 1024) + sizeof(struct sk_buff)));
+
+ icmp_socket_cpu(i)->sk->protinfo.af_inet.ttl = MAXTTL;
+ icmp_socket_cpu(i)->sk->protinfo.af_inet.pmtudisc = IP_PMTUDISC_DONT;
+
+ /* Unhash it so that IP input processing does not even
+ * see it, we do not wish this socket to see incoming
+ * packets.
+ */
+ icmp_socket_cpu(i)->sk->prot->unhash(icmp_socket_cpu(i)->sk);
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/igmp.c b/uClinux-2.4.31-uc0/net/ipv4/igmp.c
new file mode 100644
index 0000000..fae5153
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/igmp.c
@@ -0,0 +1,2236 @@
+/*
+ * Linux NET3: Internet Group Management Protocol [IGMP]
+ *
+ * This code implements the IGMP protocol as defined in RFC1112. There has
+ * been a further revision of this protocol since which is now supported.
+ *
+ * If you have trouble with this module be careful what gcc you have used,
+ * the older version didn't come out right using gcc 2.5.8, the newer one
+ * seems to fall out with gcc 2.6.2.
+ *
+ * Version: $Id: igmp.c,v 1.46 2001/07/27 09:27:29 davem Exp $
+ *
+ * Authors:
+ * Alan Cox <Alan.Cox@linux.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Fixes:
+ *
+ * Alan Cox : Added lots of __inline__ to optimise
+ * the memory usage of all the tiny little
+ * functions.
+ * Alan Cox : Dumped the header building experiment.
+ * Alan Cox : Minor tweaks ready for multicast routing
+ * and extended IGMP protocol.
+ * Alan Cox : Removed a load of inline directives. Gcc 2.5.8
+ * writes utterly bogus code otherwise (sigh)
+ * fixed IGMP loopback to behave in the manner
+ * desired by mrouted, fixed the fact it has been
+ * broken since 1.3.6 and cleaned up a few minor
+ * points.
+ *
+ * Chih-Jen Chang : Tried to revise IGMP to Version 2
+ * Tsu-Sheng Tsao E-mail: chihjenc@scf.usc.edu and tsusheng@scf.usc.edu
+ * The enhancements are mainly based on Steve Deering's
+ * ipmulti-3.5 source code.
+ * Chih-Jen Chang : Added the igmp_get_mrouter_info and
+ * Tsu-Sheng Tsao igmp_set_mrouter_info to keep track of
+ * the mrouted version on that device.
+ * Chih-Jen Chang : Added the max_resp_time parameter to
+ * Tsu-Sheng Tsao igmp_heard_query(). Using this parameter
+ * to identify the multicast router version
+ * and do what the IGMP version 2 specified.
+ * Chih-Jen Chang : Added a timer to revert to IGMP V2 router
+ * Tsu-Sheng Tsao if the specified time expired.
+ * Alan Cox : Stop IGMP from 0.0.0.0 being accepted.
+ * Alan Cox : Use GFP_ATOMIC in the right places.
+ * Christian Daudt : igmp timer wasn't set for local group
+ * memberships but was being deleted,
+ * which caused a "del_timer() called
+ * from %p with timer not initialized\n"
+ * message (960131).
+ * Christian Daudt : removed del_timer from
+ * igmp_timer_expire function (960205).
+ * Christian Daudt : igmp_heard_report now only calls
+ * igmp_timer_expire if tm->running is
+ * true (960216).
+ * Malcolm Beattie : ttl comparison wrong in igmp_rcv made
+ * igmp_heard_query never trigger. Expiry
+ * miscalculation fixed in igmp_heard_query
+ * and random() made to return unsigned to
+ * prevent negative expiry times.
+ * Alexey Kuznetsov: Wrong group leaving behaviour, backport
+ * fix from pending 2.1.x patches.
+ * Alan Cox: Forget to enable FDDI support earlier.
+ * Alexey Kuznetsov: Fixed leaving groups on device down.
+ * Alexey Kuznetsov: Accordance to igmp-v2-06 draft.
+ * David L Stevens: IGMPv3 support, with help from
+ * Vinay Kulkarni
+ */
+
+
+#include <linux/config.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/inetdevice.h>
+#include <linux/igmp.h>
+#include <linux/if_arp.h>
+#include <linux/rtnetlink.h>
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <net/route.h>
+#include <net/sock.h>
+#include <net/checksum.h>
+#include <linux/netfilter_ipv4.h>
+#ifdef CONFIG_IP_MROUTE
+#include <linux/mroute.h>
+#endif
+
+
+#define IP_MAX_MEMBERSHIPS 20
+#define IP_MAX_MSF 10
+
+#ifdef CONFIG_IP_MULTICAST
+/* Parameter names and values are taken from igmp-v2-06 draft */
+
+#define IGMP_V1_Router_Present_Timeout (400*HZ)
+#define IGMP_V2_Router_Present_Timeout (400*HZ)
+#define IGMP_Unsolicited_Report_Interval (10*HZ)
+#define IGMP_Query_Response_Interval (10*HZ)
+#define IGMP_Unsolicited_Report_Count 2
+
+
+#define IGMP_Initial_Report_Delay (1)
+
+/* IGMP_Initial_Report_Delay is not from IGMP specs!
+ * IGMP specs require to report membership immediately after
+ * joining a group, but we delay the first report by a
+ * small interval. It seems more natural and still does not
+ * contradict to specs provided this delay is small enough.
+ */
+
+#define IGMP_V1_SEEN(in_dev) (ipv4_devconf.force_igmp_version == 1 || \
+ (in_dev)->cnf.force_igmp_version == 1 || \
+ ((in_dev)->mr_v1_seen && \
+ time_before(jiffies, (in_dev)->mr_v1_seen)))
+#define IGMP_V2_SEEN(in_dev) (ipv4_devconf.force_igmp_version == 2 || \
+ (in_dev)->cnf.force_igmp_version == 2 || \
+ ((in_dev)->mr_v2_seen && \
+ time_before(jiffies, (in_dev)->mr_v2_seen)))
+
+static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im);
+static void igmpv3_del_delrec(struct in_device *in_dev, __u32 multiaddr);
+static void igmpv3_clear_delrec(struct in_device *in_dev);
+static int sf_setstate(struct ip_mc_list *pmc);
+static void sf_markstate(struct ip_mc_list *pmc);
+#endif
+static void ip_mc_clear_src(struct ip_mc_list *pmc);
+int ip_mc_add_src(struct in_device *in_dev, __u32 *pmca, int sfmode,
+ int sfcount, __u32 *psfsrc, int delta);
+
+static void ip_ma_put(struct ip_mc_list *im)
+{
+ if (atomic_dec_and_test(&im->refcnt)) {
+ in_dev_put(im->interface);
+ kfree(im);
+ }
+}
+
+#ifdef CONFIG_IP_MULTICAST
+
+/*
+ * Timer management
+ */
+
+static __inline__ void igmp_stop_timer(struct ip_mc_list *im)
+{
+ spin_lock_bh(&im->lock);
+ if (del_timer(&im->timer))
+ atomic_dec(&im->refcnt);
+ im->tm_running=0;
+ im->reporter = 0;
+ im->unsolicit_count = 0;
+ spin_unlock_bh(&im->lock);
+}
+
+/* It must be called with locked im->lock */
+static void igmp_start_timer(struct ip_mc_list *im, int max_delay)
+{
+ int tv=net_random() % max_delay;
+
+ im->tm_running=1;
+ if (!mod_timer(&im->timer, jiffies+tv+2))
+ atomic_inc(&im->refcnt);
+}
+
+static void igmp_gq_start_timer(struct in_device *in_dev)
+{
+ int tv = net_random() % in_dev->mr_maxdelay;
+
+ in_dev->mr_gq_running = 1;
+ if (!mod_timer(&in_dev->mr_gq_timer, jiffies+tv+2))
+ in_dev_hold(in_dev);
+}
+
+static void igmp_ifc_start_timer(struct in_device *in_dev, int delay)
+{
+ int tv = net_random() % delay;
+
+ if (!mod_timer(&in_dev->mr_ifc_timer, jiffies+tv+2))
+ in_dev_hold(in_dev);
+}
+
+static void igmp_mod_timer(struct ip_mc_list *im, int max_delay)
+{
+ spin_lock_bh(&im->lock);
+ im->unsolicit_count = 0;
+ if (del_timer(&im->timer)) {
+ if ((long)(im->timer.expires-jiffies) < max_delay) {
+ add_timer(&im->timer);
+ im->tm_running=1;
+ spin_unlock_bh(&im->lock);
+ return;
+ }
+ atomic_dec(&im->refcnt);
+ }
+ igmp_start_timer(im, max_delay);
+ spin_unlock_bh(&im->lock);
+}
+
+
+/*
+ * Send an IGMP report.
+ */
+
+#define IGMP_SIZE (sizeof(struct igmphdr)+sizeof(struct iphdr)+4)
+
+/* Don't just hand NF_HOOK skb->dst->output, in case netfilter hook
+ changes route */
+static inline int
+output_maybe_reroute(struct sk_buff *skb)
+{
+ return skb->dst->output(skb);
+}
+
+
+static int is_in(struct ip_mc_list *pmc, struct ip_sf_list *psf, int type,
+ int gdeleted, int sdeleted)
+{
+ switch (type) {
+ case IGMPV3_MODE_IS_INCLUDE:
+ case IGMPV3_MODE_IS_EXCLUDE:
+ if (gdeleted || sdeleted)
+ return 0;
+ return !(pmc->gsquery && !psf->sf_gsresp);
+ case IGMPV3_CHANGE_TO_INCLUDE:
+ if (gdeleted || sdeleted)
+ return 0;
+ return psf->sf_count[MCAST_INCLUDE] != 0;
+ case IGMPV3_CHANGE_TO_EXCLUDE:
+ if (gdeleted || sdeleted)
+ return 0;
+ if (pmc->sfcount[MCAST_EXCLUDE] == 0 ||
+ psf->sf_count[MCAST_INCLUDE])
+ return 0;
+ return pmc->sfcount[MCAST_EXCLUDE] ==
+ psf->sf_count[MCAST_EXCLUDE];
+ case IGMPV3_ALLOW_NEW_SOURCES:
+ if (gdeleted || !psf->sf_crcount)
+ return 0;
+ return (pmc->sfmode == MCAST_INCLUDE) ^ sdeleted;
+ case IGMPV3_BLOCK_OLD_SOURCES:
+ if (pmc->sfmode == MCAST_INCLUDE)
+ return gdeleted || (psf->sf_crcount && sdeleted);
+ return psf->sf_crcount && !gdeleted && !sdeleted;
+ }
+ return 0;
+}
+
+static int
+igmp_scount(struct ip_mc_list *pmc, int type, int gdeleted, int sdeleted)
+{
+ struct ip_sf_list *psf;
+ int scount = 0;
+
+ for (psf=pmc->sources; psf; psf=psf->sf_next) {
+ if (!is_in(pmc, psf, type, gdeleted, sdeleted))
+ continue;
+ scount++;
+ }
+ return scount;
+}
+
+static struct sk_buff *igmpv3_newpack(struct net_device *dev, int size)
+{
+ struct sk_buff *skb;
+ struct rtable *rt;
+ struct iphdr *pip;
+ struct igmpv3_report *pig;
+ u32 dst;
+
+ dst = IGMPV3_ALL_MCR;
+ if (ip_route_output(&rt, dst, 0, 0, dev->ifindex))
+ return 0;
+ if (rt->rt_src == 0) {
+ ip_rt_put(rt);
+ return 0;
+ }
+ skb = alloc_skb(size + dev->hard_header_len + 15, GFP_ATOMIC);
+ if (skb == NULL) {
+ ip_rt_put(rt);
+ return 0;
+ }
+
+ skb->dst = &rt->u.dst;
+ skb->dev = dev;
+
+ skb_reserve(skb, (dev->hard_header_len+15)&~15);
+
+ skb->nh.iph = pip =(struct iphdr *)skb_put(skb, sizeof(struct iphdr)+4);
+
+ pip->version = 4;
+ pip->ihl = (sizeof(struct iphdr)+4)>>2;
+ pip->tos = 0xc0;
+ pip->frag_off = htons(IP_DF);
+ pip->ttl = 1;
+ pip->daddr = rt->rt_dst;
+ pip->saddr = rt->rt_src;
+ pip->protocol = IPPROTO_IGMP;
+ pip->tot_len = 0; /* filled in later */
+ ip_select_ident(pip, &rt->u.dst, NULL);
+ ((u8*)&pip[1])[0] = IPOPT_RA;
+ ((u8*)&pip[1])[1] = 4;
+ ((u8*)&pip[1])[2] = 0;
+ ((u8*)&pip[1])[3] = 0;
+
+ pig =(struct igmpv3_report *)skb_put(skb, sizeof(*pig));
+ skb->h.igmph = (struct igmphdr *)pig;
+ pig->type = IGMPV3_HOST_MEMBERSHIP_REPORT;
+ pig->resv1 = 0;
+ pig->csum = 0;
+ pig->resv2 = 0;
+ pig->ngrec = 0;
+ return skb;
+}
+
+static int igmpv3_sendpack(struct sk_buff *skb)
+{
+ struct iphdr *pip = skb->nh.iph;
+ struct igmphdr *pig = skb->h.igmph;
+ int iplen, igmplen;
+
+ iplen = skb->tail - (unsigned char *)skb->nh.iph;
+ pip->tot_len = htons(iplen);
+ ip_send_check(pip);
+
+ igmplen = skb->tail - (unsigned char *)skb->h.igmph;
+ pig->csum = ip_compute_csum((void *)skb->h.igmph, igmplen);
+
+ return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, skb->dev,
+ output_maybe_reroute);
+}
+
+static int grec_size(struct ip_mc_list *pmc, int type, int gdel, int sdel)
+{
+ return sizeof(struct igmpv3_grec) + 4*igmp_scount(pmc,type,gdel,sdel);
+}
+
+static struct sk_buff *add_grhead(struct sk_buff *skb, struct ip_mc_list *pmc,
+ int type, struct igmpv3_grec **ppgr)
+{
+ struct net_device *dev = pmc->interface->dev;
+ struct igmpv3_report *pih;
+ struct igmpv3_grec *pgr;
+
+ if (!skb)
+ skb = igmpv3_newpack(dev, dev->mtu);
+ if (!skb)
+ return 0;
+ pgr = (struct igmpv3_grec *)skb_put(skb, sizeof(struct igmpv3_grec));
+ pgr->grec_type = type;
+ pgr->grec_auxwords = 0;
+ pgr->grec_nsrcs = 0;
+ pgr->grec_mca = pmc->multiaddr;
+ pih = (struct igmpv3_report *)skb->h.igmph;
+ pih->ngrec = htons(ntohs(pih->ngrec)+1);
+ *ppgr = pgr;
+ return skb;
+}
+
+#define AVAILABLE(skb) ((skb) ? ((skb)->dev ? (skb)->dev->mtu - (skb)->len : \
+ skb_tailroom(skb)) : 0)
+
+static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc,
+ int type, int gdeleted, int sdeleted)
+{
+ struct net_device *dev = pmc->interface->dev;
+ struct igmpv3_report *pih;
+ struct igmpv3_grec *pgr = 0;
+ struct ip_sf_list *psf, *psf_next, *psf_prev, **psf_list;
+ int scount, first, isquery, truncate;
+
+ if (pmc->multiaddr == IGMP_ALL_HOSTS)
+ return skb;
+
+ isquery = type == IGMPV3_MODE_IS_INCLUDE ||
+ type == IGMPV3_MODE_IS_EXCLUDE;
+ truncate = type == IGMPV3_MODE_IS_EXCLUDE ||
+ type == IGMPV3_CHANGE_TO_EXCLUDE;
+
+ psf_list = sdeleted ? &pmc->tomb : &pmc->sources;
+
+ if (!*psf_list) {
+ if (type == IGMPV3_ALLOW_NEW_SOURCES ||
+ type == IGMPV3_BLOCK_OLD_SOURCES)
+ return skb;
+ if (pmc->crcount || isquery) {
+ /* make sure we have room for group header and at
+ * least one source.
+ */
+ if (skb && AVAILABLE(skb) < sizeof(struct igmpv3_grec)+
+ sizeof(__u32)) {
+ igmpv3_sendpack(skb);
+ skb = 0; /* add_grhead will get a new one */
+ }
+ skb = add_grhead(skb, pmc, type, &pgr);
+ }
+ return skb;
+ }
+ pih = skb ? (struct igmpv3_report *)skb->h.igmph : 0;
+
+ /* EX and TO_EX get a fresh packet, if needed */
+ if (truncate) {
+ if (pih && pih->ngrec &&
+ AVAILABLE(skb) < grec_size(pmc, type, gdeleted, sdeleted)) {
+ if (skb)
+ igmpv3_sendpack(skb);
+ skb = igmpv3_newpack(dev, dev->mtu);
+ }
+ }
+ first = 1;
+ scount = 0;
+ psf_prev = 0;
+ for (psf=*psf_list; psf; psf=psf_next) {
+ u32 *psrc;
+
+ psf_next = psf->sf_next;
+
+ if (!is_in(pmc, psf, type, gdeleted, sdeleted)) {
+ psf_prev = psf;
+ continue;
+ }
+
+ /* clear marks on query responses */
+ if (isquery)
+ psf->sf_gsresp = 0;
+
+ if (AVAILABLE(skb) < sizeof(u32) +
+ first*sizeof(struct igmpv3_grec)) {
+ if (truncate && !first)
+ break; /* truncate these */
+ if (pgr)
+ pgr->grec_nsrcs = htons(scount);
+ if (skb)
+ igmpv3_sendpack(skb);
+ skb = igmpv3_newpack(dev, dev->mtu);
+ first = 1;
+ scount = 0;
+ }
+ if (first) {
+ skb = add_grhead(skb, pmc, type, &pgr);
+ first = 0;
+ }
+ psrc = (u32 *)skb_put(skb, sizeof(u32));
+ *psrc = psf->sf_inaddr;
+ scount++;
+ if ((type == IGMPV3_ALLOW_NEW_SOURCES ||
+ type == IGMPV3_BLOCK_OLD_SOURCES) && psf->sf_crcount) {
+ psf->sf_crcount--;
+ if ((sdeleted || gdeleted) && psf->sf_crcount == 0) {
+ if (psf_prev)
+ psf_prev->sf_next = psf->sf_next;
+ else
+ *psf_list = psf->sf_next;
+ kfree(psf);
+ continue;
+ }
+ }
+ psf_prev = psf;
+ }
+ if (pgr)
+ pgr->grec_nsrcs = htons(scount);
+
+ if (isquery)
+ pmc->gsquery = 0; /* clear query state on report */
+ return skb;
+}
+
+static int igmpv3_send_report(struct in_device *in_dev, struct ip_mc_list *pmc)
+{
+ struct sk_buff *skb = 0;
+ int type;
+
+ if (!pmc) {
+ read_lock(&in_dev->lock);
+ for (pmc=in_dev->mc_list; pmc; pmc=pmc->next) {
+ if (pmc->multiaddr == IGMP_ALL_HOSTS)
+ continue;
+ spin_lock_bh(&pmc->lock);
+ if (pmc->sfcount[MCAST_EXCLUDE])
+ type = IGMPV3_MODE_IS_EXCLUDE;
+ else
+ type = IGMPV3_MODE_IS_INCLUDE;
+ skb = add_grec(skb, pmc, type, 0, 0);
+ spin_unlock_bh(&pmc->lock);
+ }
+ read_unlock(&in_dev->lock);
+ } else {
+ spin_lock_bh(&pmc->lock);
+ if (pmc->sfcount[MCAST_EXCLUDE])
+ type = IGMPV3_MODE_IS_EXCLUDE;
+ else
+ type = IGMPV3_MODE_IS_INCLUDE;
+ skb = add_grec(skb, pmc, type, 0, 0);
+ spin_unlock_bh(&pmc->lock);
+ }
+ if (!skb)
+ return 0;
+ return igmpv3_sendpack(skb);
+}
+
+/*
+ * remove zero-count source records from a source filter list
+ */
+static void igmpv3_clear_zeros(struct ip_sf_list **ppsf)
+{
+ struct ip_sf_list *psf_prev, *psf_next, *psf;
+
+ psf_prev = 0;
+ for (psf=*ppsf; psf; psf = psf_next) {
+ psf_next = psf->sf_next;
+ if (psf->sf_crcount == 0) {
+ if (psf_prev)
+ psf_prev->sf_next = psf->sf_next;
+ else
+ *ppsf = psf->sf_next;
+ kfree(psf);
+ } else
+ psf_prev = psf;
+ }
+}
+
+static void igmpv3_send_cr(struct in_device *in_dev)
+{
+ struct ip_mc_list *pmc, *pmc_prev, *pmc_next;
+ struct sk_buff *skb = 0;
+ int type, dtype;
+
+ read_lock(&in_dev->lock);
+ write_lock_bh(&in_dev->mc_lock);
+
+ /* deleted MCA's */
+ pmc_prev = 0;
+ for (pmc=in_dev->mc_tomb; pmc; pmc=pmc_next) {
+ pmc_next = pmc->next;
+ if (pmc->sfmode == MCAST_INCLUDE) {
+ type = IGMPV3_BLOCK_OLD_SOURCES;
+ dtype = IGMPV3_BLOCK_OLD_SOURCES;
+ skb = add_grec(skb, pmc, type, 1, 0);
+ skb = add_grec(skb, pmc, dtype, 1, 1);
+ }
+ if (pmc->crcount) {
+ pmc->crcount--;
+ if (pmc->sfmode == MCAST_EXCLUDE) {
+ type = IGMPV3_CHANGE_TO_INCLUDE;
+ skb = add_grec(skb, pmc, type, 1, 0);
+ }
+ if (pmc->crcount == 0) {
+ igmpv3_clear_zeros(&pmc->tomb);
+ igmpv3_clear_zeros(&pmc->sources);
+ }
+ }
+ if (pmc->crcount == 0 && !pmc->tomb && !pmc->sources) {
+ if (pmc_prev)
+ pmc_prev->next = pmc_next;
+ else
+ in_dev->mc_tomb = pmc_next;
+ in_dev_put(pmc->interface);
+ kfree(pmc);
+ } else
+ pmc_prev = pmc;
+ }
+ write_unlock_bh(&in_dev->mc_lock);
+
+ /* change recs */
+ for (pmc=in_dev->mc_list; pmc; pmc=pmc->next) {
+ spin_lock_bh(&pmc->lock);
+ if (pmc->sfcount[MCAST_EXCLUDE]) {
+ type = IGMPV3_BLOCK_OLD_SOURCES;
+ dtype = IGMPV3_ALLOW_NEW_SOURCES;
+ } else {
+ type = IGMPV3_ALLOW_NEW_SOURCES;
+ dtype = IGMPV3_BLOCK_OLD_SOURCES;
+ }
+ skb = add_grec(skb, pmc, type, 0, 0);
+ skb = add_grec(skb, pmc, dtype, 0, 1); /* deleted sources */
+
+ /* filter mode changes */
+ if (pmc->crcount) {
+ pmc->crcount--;
+ if (pmc->sfmode == MCAST_EXCLUDE)
+ type = IGMPV3_CHANGE_TO_EXCLUDE;
+ else
+ type = IGMPV3_CHANGE_TO_INCLUDE;
+ skb = add_grec(skb, pmc, type, 0, 0);
+ }
+ spin_unlock_bh(&pmc->lock);
+ }
+ read_unlock(&in_dev->lock);
+ if (!skb)
+ return;
+ (void) igmpv3_sendpack(skb);
+}
+
+static int igmp_send_report(struct in_device *in_dev, struct ip_mc_list *pmc,
+ int type)
+{
+ struct sk_buff *skb;
+ struct iphdr *iph;
+ struct igmphdr *ih;
+ struct rtable *rt;
+ struct net_device *dev = in_dev->dev;
+ u32 group = pmc ? pmc->multiaddr : 0;
+ u32 dst;
+
+ if (type == IGMPV3_HOST_MEMBERSHIP_REPORT)
+ return igmpv3_send_report(in_dev, pmc);
+ else if (type == IGMP_HOST_LEAVE_MESSAGE)
+ dst = IGMP_ALL_ROUTER;
+ else
+ dst = group;
+
+ if (ip_route_output(&rt, dst, 0, 0, dev->ifindex))
+ return -1;
+ if (rt->rt_src == 0) {
+ ip_rt_put(rt);
+ return -1;
+ }
+
+ skb=alloc_skb(IGMP_SIZE+dev->hard_header_len+15, GFP_ATOMIC);
+ if (skb == NULL) {
+ ip_rt_put(rt);
+ return -1;
+ }
+
+ skb->dst = &rt->u.dst;
+
+ skb_reserve(skb, (dev->hard_header_len+15)&~15);
+
+ skb->nh.iph = iph = (struct iphdr *)skb_put(skb, sizeof(struct iphdr)+4);
+
+ iph->version = 4;
+ iph->ihl = (sizeof(struct iphdr)+4)>>2;
+ iph->tos = 0xc0;
+ iph->frag_off = htons(IP_DF);
+ iph->ttl = 1;
+ iph->daddr = dst;
+ iph->saddr = rt->rt_src;
+ iph->protocol = IPPROTO_IGMP;
+ iph->tot_len = htons(IGMP_SIZE);
+ ip_select_ident(iph, &rt->u.dst, NULL);
+ ((u8*)&iph[1])[0] = IPOPT_RA;
+ ((u8*)&iph[1])[1] = 4;
+ ((u8*)&iph[1])[2] = 0;
+ ((u8*)&iph[1])[3] = 0;
+ ip_send_check(iph);
+
+ ih = (struct igmphdr *)skb_put(skb, sizeof(struct igmphdr));
+ ih->type=type;
+ ih->code=0;
+ ih->csum=0;
+ ih->group=group;
+ ih->csum=ip_compute_csum((void *)ih, sizeof(struct igmphdr));
+
+ return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
+ output_maybe_reroute);
+}
+
+static void igmp_gq_timer_expire(unsigned long data)
+{
+ struct in_device *in_dev = (struct in_device *)data;
+
+ in_dev->mr_gq_running = 0;
+ igmpv3_send_report(in_dev, 0);
+ __in_dev_put(in_dev);
+}
+
+static void igmp_ifc_timer_expire(unsigned long data)
+{
+ struct in_device *in_dev = (struct in_device *)data;
+
+ igmpv3_send_cr(in_dev);
+ if (in_dev->mr_ifc_count) {
+ in_dev->mr_ifc_count--;
+ igmp_ifc_start_timer(in_dev, IGMP_Unsolicited_Report_Interval);
+ }
+ __in_dev_put(in_dev);
+}
+
+static void igmp_ifc_event(struct in_device *in_dev)
+{
+ if (IGMP_V1_SEEN(in_dev) || IGMP_V2_SEEN(in_dev))
+ return;
+ in_dev->mr_ifc_count = in_dev->mr_qrv ? in_dev->mr_qrv :
+ IGMP_Unsolicited_Report_Count;
+ igmp_ifc_start_timer(in_dev, 1);
+}
+
+
+static void igmp_timer_expire(unsigned long data)
+{
+ struct ip_mc_list *im=(struct ip_mc_list *)data;
+ struct in_device *in_dev = im->interface;
+
+ spin_lock(&im->lock);
+ im->tm_running=0;
+
+ if (im->unsolicit_count) {
+ im->unsolicit_count--;
+ igmp_start_timer(im, IGMP_Unsolicited_Report_Interval);
+ }
+ im->reporter = 1;
+ spin_unlock(&im->lock);
+
+ if (IGMP_V1_SEEN(in_dev))
+ igmp_send_report(in_dev, im, IGMP_HOST_MEMBERSHIP_REPORT);
+ else if (IGMP_V2_SEEN(in_dev))
+ igmp_send_report(in_dev, im, IGMPV2_HOST_MEMBERSHIP_REPORT);
+ else
+ igmp_send_report(in_dev, im, IGMPV3_HOST_MEMBERSHIP_REPORT);
+
+ ip_ma_put(im);
+}
+
+static void igmp_marksources(struct ip_mc_list *pmc, int nsrcs, __u32 *srcs)
+{
+ struct ip_sf_list *psf;
+ int i, scount;
+
+ scount = 0;
+ for (psf=pmc->sources; psf; psf=psf->sf_next) {
+ if (scount == nsrcs)
+ break;
+ for (i=0; i<nsrcs; i++)
+ if (srcs[i] == psf->sf_inaddr) {
+ psf->sf_gsresp = 1;
+ scount++;
+ break;
+ }
+ }
+}
+
+static void igmp_heard_report(struct in_device *in_dev, u32 group)
+{
+ struct ip_mc_list *im;
+
+ /* Timers are only set for non-local groups */
+
+ if (group == IGMP_ALL_HOSTS)
+ return;
+
+ read_lock(&in_dev->lock);
+ for (im=in_dev->mc_list; im!=NULL; im=im->next) {
+ if (im->multiaddr == group) {
+ igmp_stop_timer(im);
+ break;
+ }
+ }
+ read_unlock(&in_dev->lock);
+}
+
+static void igmp_heard_query(struct in_device *in_dev, struct igmphdr *ih,
+ int len)
+{
+ struct igmpv3_query *ih3 = (struct igmpv3_query *)ih;
+ struct ip_mc_list *im;
+ u32 group = ih->group;
+ int max_delay;
+ int mark = 0;
+
+
+ if (len == 8) {
+ if (ih->code == 0) {
+ /* Alas, old v1 router presents here. */
+
+ max_delay = IGMP_Query_Response_Interval;
+ in_dev->mr_v1_seen = jiffies +
+ IGMP_V1_Router_Present_Timeout;
+ group = 0;
+ } else {
+ /* v2 router present */
+ max_delay = ih->code*(HZ/IGMP_TIMER_SCALE);
+ in_dev->mr_v2_seen = jiffies +
+ IGMP_V2_Router_Present_Timeout;
+ }
+ /* cancel the interface change timer */
+ in_dev->mr_ifc_count = 0;
+ if (del_timer(&in_dev->mr_ifc_timer))
+ __in_dev_put(in_dev);
+ /* clear deleted report items */
+ igmpv3_clear_delrec(in_dev);
+ } else if (len < 12) {
+ return; /* ignore bogus packet; freed by caller */
+ } else { /* v3 */
+ max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE);
+ if (!max_delay)
+ max_delay = 1; /* can't mod w/ 0 */
+ in_dev->mr_maxdelay = max_delay;
+ if (ih3->qrv)
+ in_dev->mr_qrv = ih3->qrv;
+ if (!group) { /* general query */
+ if (ih3->nsrcs)
+ return; /* no sources allowed */
+ igmp_gq_start_timer(in_dev);
+ return;
+ }
+ /* mark sources to include, if group & source-specific */
+ mark = ih3->nsrcs != 0;
+ }
+
+ /*
+ * - Start the timers in all of our membership records
+ * that the query applies to for the interface on
+ * which the query arrived excl. those that belong
+ * to a "local" group (224.0.0.X)
+ * - For timers already running check if they need to
+ * be reset.
+ * - Use the igmp->igmp_code field as the maximum
+ * delay possible
+ */
+ read_lock(&in_dev->lock);
+ for (im=in_dev->mc_list; im!=NULL; im=im->next) {
+ if (group && group != im->multiaddr)
+ continue;
+ if (im->multiaddr == IGMP_ALL_HOSTS)
+ continue;
+ spin_lock_bh(&im->lock);
+ if (im->tm_running)
+ im->gsquery = im->gsquery && mark;
+ else
+ im->gsquery = mark;
+ if (im->gsquery)
+ igmp_marksources(im, ntohs(ih3->nsrcs), ih3->srcs);
+ spin_unlock_bh(&im->lock);
+ igmp_mod_timer(im, max_delay);
+ }
+ read_unlock(&in_dev->lock);
+}
+
+int igmp_rcv(struct sk_buff *skb)
+{
+ /* This basically follows the spec line by line -- see RFC1112 */
+ struct igmphdr *ih = skb->h.igmph;
+ struct in_device *in_dev = in_dev_get(skb->dev);
+ int len = skb->len;
+
+ if (in_dev==NULL) {
+ kfree_skb(skb);
+ return 0;
+ }
+
+ if (skb_is_nonlinear(skb)) {
+ if (skb_linearize(skb, GFP_ATOMIC) != 0) {
+ kfree_skb(skb);
+ return -ENOMEM;
+ }
+ ih = skb->h.igmph;
+ }
+
+ if (len < sizeof(struct igmphdr) || ip_compute_csum((void *)ih, len)) {
+ in_dev_put(in_dev);
+ kfree_skb(skb);
+ return 0;
+ }
+
+ switch (ih->type) {
+ case IGMP_HOST_MEMBERSHIP_QUERY:
+ igmp_heard_query(in_dev, ih, len);
+ break;
+ case IGMP_HOST_MEMBERSHIP_REPORT:
+ case IGMPV2_HOST_MEMBERSHIP_REPORT:
+ case IGMPV3_HOST_MEMBERSHIP_REPORT:
+ /* Is it our report looped back? */
+ if (((struct rtable*)skb->dst)->key.iif == 0)
+ break;
+ igmp_heard_report(in_dev, ih->group);
+ break;
+ case IGMP_PIM:
+#ifdef CONFIG_IP_PIMSM_V1
+ in_dev_put(in_dev);
+ return pim_rcv_v1(skb);
+#endif
+ case IGMP_DVMRP:
+ case IGMP_TRACE:
+ case IGMP_HOST_LEAVE_MESSAGE:
+ case IGMP_MTRACE:
+ case IGMP_MTRACE_RESP:
+ break;
+ default:
+ NETDEBUG(printk(KERN_DEBUG "New IGMP type=%d, why we do not know about it?\n", ih->type));
+ }
+ in_dev_put(in_dev);
+ kfree_skb(skb);
+ return 0;
+}
+
+#endif
+
+
+/*
+ * Add a filter to a device
+ */
+
+static void ip_mc_filter_add(struct in_device *in_dev, u32 addr)
+{
+ char buf[MAX_ADDR_LEN];
+ struct net_device *dev = in_dev->dev;
+
+ /* Checking for IFF_MULTICAST here is WRONG-WRONG-WRONG.
+ We will get multicast token leakage, when IFF_MULTICAST
+ is changed. This check should be done in dev->set_multicast_list
+ routine. Something sort of:
+ if (dev->mc_list && dev->flags&IFF_MULTICAST) { do it; }
+ --ANK
+ */
+ if (arp_mc_map(addr, buf, dev, 0) == 0)
+ dev_mc_add(dev,buf,dev->addr_len,0);
+}
+
+/*
+ * Remove a filter from a device
+ */
+
+static void ip_mc_filter_del(struct in_device *in_dev, u32 addr)
+{
+ char buf[MAX_ADDR_LEN];
+ struct net_device *dev = in_dev->dev;
+
+ if (arp_mc_map(addr, buf, dev, 0) == 0)
+ dev_mc_delete(dev,buf,dev->addr_len,0);
+}
+
+#ifdef CONFIG_IP_MULTICAST
+/*
+ * deleted ip_mc_list manipulation
+ */
+static void igmpv3_add_delrec(struct in_device *in_dev, struct ip_mc_list *im)
+{
+ struct ip_mc_list *pmc;
+
+ /* this is an "ip_mc_list" for convenience; only the fields below
+ * are actually used. In particular, the refcnt and users are not
+ * used for management of the delete list. Using the same structure
+ * for deleted items allows change reports to use common code with
+ * non-deleted or query-response MCA's.
+ */
+ pmc = (struct ip_mc_list *)kmalloc(sizeof(*pmc), GFP_KERNEL);
+ if (!pmc)
+ return;
+ memset(pmc, 0, sizeof(*pmc));
+ spin_lock_bh(&im->lock);
+ pmc->interface = im->interface;
+ in_dev_hold(in_dev);
+ pmc->multiaddr = im->multiaddr;
+ pmc->crcount = in_dev->mr_qrv ? in_dev->mr_qrv :
+ IGMP_Unsolicited_Report_Count;
+ pmc->sfmode = im->sfmode;
+ if (pmc->sfmode == MCAST_INCLUDE) {
+ struct ip_sf_list *psf;
+
+ pmc->tomb = im->tomb;
+ pmc->sources = im->sources;
+ im->tomb = im->sources = 0;
+ for (psf=pmc->sources; psf; psf=psf->sf_next)
+ psf->sf_crcount = pmc->crcount;
+ }
+ spin_unlock_bh(&im->lock);
+
+ write_lock_bh(&in_dev->mc_lock);
+ pmc->next = in_dev->mc_tomb;
+ in_dev->mc_tomb = pmc;
+ write_unlock_bh(&in_dev->mc_lock);
+}
+
+static void igmpv3_del_delrec(struct in_device *in_dev, __u32 multiaddr)
+{
+ struct ip_mc_list *pmc, *pmc_prev;
+ struct ip_sf_list *psf, *psf_next;
+
+ write_lock_bh(&in_dev->mc_lock);
+ pmc_prev = 0;
+ for (pmc=in_dev->mc_tomb; pmc; pmc=pmc->next) {
+ if (pmc->multiaddr == multiaddr)
+ break;
+ pmc_prev = pmc;
+ }
+ if (pmc) {
+ if (pmc_prev)
+ pmc_prev->next = pmc->next;
+ else
+ in_dev->mc_tomb = pmc->next;
+ }
+ write_unlock_bh(&in_dev->mc_lock);
+ if (pmc) {
+ for (psf=pmc->tomb; psf; psf=psf_next) {
+ psf_next = psf->sf_next;
+ kfree(psf);
+ }
+ in_dev_put(pmc->interface);
+ kfree(pmc);
+ }
+}
+
+static void igmpv3_clear_delrec(struct in_device *in_dev)
+{
+ struct ip_mc_list *pmc, *nextpmc;
+
+ write_lock_bh(&in_dev->mc_lock);
+ pmc = in_dev->mc_tomb;
+ in_dev->mc_tomb = 0;
+ write_unlock_bh(&in_dev->mc_lock);
+
+ for (; pmc; pmc = nextpmc) {
+ nextpmc = pmc->next;
+ ip_mc_clear_src(pmc);
+ in_dev_put(pmc->interface);
+ kfree(pmc);
+ }
+ /* clear dead sources, too */
+ read_lock(&in_dev->lock);
+ for (pmc=in_dev->mc_list; pmc; pmc=pmc->next) {
+ struct ip_sf_list *psf, *psf_next;
+
+ spin_lock_bh(&pmc->lock);
+ psf = pmc->tomb;
+ pmc->tomb = 0;
+ spin_unlock_bh(&pmc->lock);
+ for (; psf; psf=psf_next) {
+ psf_next = psf->sf_next;
+ kfree(psf);
+ }
+ }
+ read_unlock(&in_dev->lock);
+}
+#endif
+
+static void igmp_group_dropped(struct ip_mc_list *im)
+{
+ struct in_device *in_dev = im->interface;
+#ifdef CONFIG_IP_MULTICAST
+ int reporter;
+#endif
+
+ if (im->loaded) {
+ im->loaded = 0;
+ ip_mc_filter_del(in_dev, im->multiaddr);
+ }
+
+#ifdef CONFIG_IP_MULTICAST
+ if (im->multiaddr == IGMP_ALL_HOSTS)
+ return;
+
+ reporter = im->reporter;
+ igmp_stop_timer(im);
+
+ if (!in_dev->dead) {
+ if (IGMP_V1_SEEN(in_dev))
+ goto done;
+ if (IGMP_V2_SEEN(in_dev)) {
+ if (reporter)
+ igmp_send_report(in_dev, im, IGMP_HOST_LEAVE_MESSAGE);
+ goto done;
+ }
+ /* IGMPv3 */
+ igmpv3_add_delrec(in_dev, im);
+
+ igmp_ifc_event(in_dev);
+ }
+done:
+#endif
+ ip_mc_clear_src(im);
+}
+
+static void igmp_group_added(struct ip_mc_list *im)
+{
+ struct in_device *in_dev = im->interface;
+
+ if (im->loaded == 0) {
+ im->loaded = 1;
+ ip_mc_filter_add(in_dev, im->multiaddr);
+ }
+
+#ifdef CONFIG_IP_MULTICAST
+ if (im->multiaddr == IGMP_ALL_HOSTS)
+ return;
+
+ if (in_dev->dead)
+ return;
+ if (IGMP_V1_SEEN(in_dev) || IGMP_V2_SEEN(in_dev)) {
+ spin_lock_bh(&im->lock);
+ igmp_start_timer(im, IGMP_Initial_Report_Delay);
+ spin_unlock_bh(&im->lock);
+ return;
+ }
+ /* else, v3 */
+
+ im->crcount = in_dev->mr_qrv ? in_dev->mr_qrv :
+ IGMP_Unsolicited_Report_Count;
+ igmp_ifc_event(in_dev);
+#endif
+}
+
+
+/*
+ * Multicast list managers
+ */
+
+
+/*
+ * A socket has joined a multicast group on device dev.
+ */
+
+void ip_mc_inc_group(struct in_device *in_dev, u32 addr)
+{
+ struct ip_mc_list *im;
+
+ ASSERT_RTNL();
+
+ for (im=in_dev->mc_list; im; im=im->next) {
+ if (im->multiaddr == addr) {
+ im->users++;
+ ip_mc_add_src(in_dev, &addr, MCAST_EXCLUDE, 0, 0, 0);
+ goto out;
+ }
+ }
+
+ im = (struct ip_mc_list *)kmalloc(sizeof(*im), GFP_KERNEL);
+ if (!im)
+ goto out;
+
+ im->users=1;
+ im->interface=in_dev;
+ in_dev_hold(in_dev);
+ im->multiaddr=addr;
+ /* initial mode is (EX, empty) */
+ im->sfmode = MCAST_EXCLUDE;
+ im->sfcount[MCAST_INCLUDE] = 0;
+ im->sfcount[MCAST_EXCLUDE] = 1;
+ im->sources = 0;
+ im->tomb = 0;
+ im->crcount = 0;
+ atomic_set(&im->refcnt, 1);
+ spin_lock_init(&im->lock);
+#ifdef CONFIG_IP_MULTICAST
+ im->tm_running=0;
+ init_timer(&im->timer);
+ im->timer.data=(unsigned long)im;
+ im->timer.function=&igmp_timer_expire;
+ im->unsolicit_count = IGMP_Unsolicited_Report_Count;
+ im->reporter = 0;
+ im->gsquery = 0;
+#endif
+ im->loaded = 0;
+ write_lock_bh(&in_dev->lock);
+ im->next=in_dev->mc_list;
+ in_dev->mc_list=im;
+ write_unlock_bh(&in_dev->lock);
+#ifdef CONFIG_IP_MULTICAST
+ igmpv3_del_delrec(in_dev, im->multiaddr);
+#endif
+ igmp_group_added(im);
+ if (!in_dev->dead)
+ ip_rt_multicast_event(in_dev);
+out:
+ return;
+}
+
+/*
+ * A socket has left a multicast group on device dev
+ */
+
+void ip_mc_dec_group(struct in_device *in_dev, u32 addr)
+{
+ struct ip_mc_list *i, **ip;
+
+ ASSERT_RTNL();
+
+ for (ip=&in_dev->mc_list; (i=*ip)!=NULL; ip=&i->next) {
+ if (i->multiaddr==addr) {
+ if (--i->users == 0) {
+ write_lock_bh(&in_dev->lock);
+ *ip = i->next;
+ write_unlock_bh(&in_dev->lock);
+ igmp_group_dropped(i);
+
+ if (!in_dev->dead)
+ ip_rt_multicast_event(in_dev);
+
+ ip_ma_put(i);
+ return;
+ }
+ break;
+ }
+ }
+}
+
+/* Device going down */
+
+void ip_mc_down(struct in_device *in_dev)
+{
+ struct ip_mc_list *i;
+
+ ASSERT_RTNL();
+
+ for (i=in_dev->mc_list; i; i=i->next)
+ igmp_group_dropped(i);
+
+#ifdef CONFIG_IP_MULTICAST
+ in_dev->mr_ifc_count = 0;
+ if (del_timer(&in_dev->mr_ifc_timer))
+ __in_dev_put(in_dev);
+ in_dev->mr_gq_running = 0;
+ if (del_timer(&in_dev->mr_gq_timer))
+ __in_dev_put(in_dev);
+ igmpv3_clear_delrec(in_dev);
+#endif
+
+ ip_mc_dec_group(in_dev, IGMP_ALL_HOSTS);
+}
+
+void ip_mc_init_dev(struct in_device *in_dev)
+{
+ ASSERT_RTNL();
+
+ in_dev->mc_tomb = 0;
+#ifdef CONFIG_IP_MULTICAST
+ in_dev->mr_gq_running = 0;
+ init_timer(&in_dev->mr_gq_timer);
+ in_dev->mr_gq_timer.data=(unsigned long) in_dev;
+ in_dev->mr_gq_timer.function=&igmp_gq_timer_expire;
+ in_dev->mr_ifc_count = 0;
+ init_timer(&in_dev->mr_ifc_timer);
+ in_dev->mr_ifc_timer.data=(unsigned long) in_dev;
+ in_dev->mr_ifc_timer.function=&igmp_ifc_timer_expire;
+ in_dev->mr_qrv = IGMP_Unsolicited_Report_Count;
+#endif
+
+ in_dev->mc_lock = RW_LOCK_UNLOCKED;
+}
+
+/* Device going up */
+
+void ip_mc_up(struct in_device *in_dev)
+{
+ struct ip_mc_list *i;
+
+ ASSERT_RTNL();
+
+ ip_mc_inc_group(in_dev, IGMP_ALL_HOSTS);
+
+ for (i=in_dev->mc_list; i; i=i->next)
+ igmp_group_added(i);
+}
+
+/*
+ * Device is about to be destroyed: clean up.
+ */
+
+void ip_mc_destroy_dev(struct in_device *in_dev)
+{
+ struct ip_mc_list *i;
+
+ ASSERT_RTNL();
+
+ /* Deactivate timers */
+ ip_mc_down(in_dev);
+
+ write_lock_bh(&in_dev->lock);
+ while ((i = in_dev->mc_list) != NULL) {
+ in_dev->mc_list = i->next;
+ write_unlock_bh(&in_dev->lock);
+
+ igmp_group_dropped(i);
+ ip_ma_put(i);
+
+ write_lock_bh(&in_dev->lock);
+ }
+ write_unlock_bh(&in_dev->lock);
+}
+
+static struct in_device * ip_mc_find_dev(struct ip_mreqn *imr)
+{
+ struct rtable *rt;
+ struct net_device *dev = NULL;
+ struct in_device *idev = NULL;
+
+ if (imr->imr_ifindex) {
+ idev = inetdev_by_index(imr->imr_ifindex);
+ if (idev)
+ __in_dev_put(idev);
+ return idev;
+ }
+ if (imr->imr_address.s_addr) {
+ dev = ip_dev_find(imr->imr_address.s_addr);
+ if (!dev)
+ return NULL;
+ __dev_put(dev);
+ }
+
+ if (!dev && !ip_route_output(&rt, imr->imr_multiaddr.s_addr, 0, 0, 0)) {
+ dev = rt->u.dst.dev;
+ ip_rt_put(rt);
+ }
+ if (dev) {
+ imr->imr_ifindex = dev->ifindex;
+ idev = __in_dev_get(dev);
+ }
+ return idev;
+}
+
+/*
+ * Join a socket to a group
+ */
+int sysctl_igmp_max_memberships = IP_MAX_MEMBERSHIPS;
+int sysctl_igmp_max_msf = IP_MAX_MSF;
+
+
+static int ip_mc_del1_src(struct ip_mc_list *pmc, int sfmode,
+ __u32 *psfsrc)
+{
+ struct ip_sf_list *psf, *psf_prev;
+ int rv = 0;
+
+ psf_prev = 0;
+ for (psf=pmc->sources; psf; psf=psf->sf_next) {
+ if (psf->sf_inaddr == *psfsrc)
+ break;
+ psf_prev = psf;
+ }
+ if (!psf || psf->sf_count[sfmode] == 0) {
+ /* source filter not found, or count wrong => bug */
+ return -ESRCH;
+ }
+ psf->sf_count[sfmode]--;
+ if (psf->sf_count[sfmode] == 0) {
+ ip_rt_multicast_event(pmc->interface);
+ }
+ if (!psf->sf_count[MCAST_INCLUDE] && !psf->sf_count[MCAST_EXCLUDE]) {
+#ifdef CONFIG_IP_MULTICAST
+ struct in_device *in_dev = pmc->interface;
+#endif
+
+ /* no more filters for this source */
+ if (psf_prev)
+ psf_prev->sf_next = psf->sf_next;
+ else
+ pmc->sources = psf->sf_next;
+#ifdef CONFIG_IP_MULTICAST
+ if (psf->sf_oldin &&
+ !IGMP_V1_SEEN(in_dev) && !IGMP_V2_SEEN(in_dev)) {
+ psf->sf_crcount = in_dev->mr_qrv ? in_dev->mr_qrv :
+ IGMP_Unsolicited_Report_Count;
+ psf->sf_next = pmc->tomb;
+ pmc->tomb = psf;
+ rv = 1;
+ } else
+#endif
+ kfree(psf);
+ }
+ return rv;
+}
+
+int ip_mc_del_src(struct in_device *in_dev, __u32 *pmca, int sfmode,
+ int sfcount, __u32 *psfsrc, int delta)
+{
+ struct ip_mc_list *pmc;
+ int changerec = 0;
+ int i, err;
+
+ if (!in_dev)
+ return -ENODEV;
+ read_lock(&in_dev->lock);
+ for (pmc=in_dev->mc_list; pmc; pmc=pmc->next) {
+ if (*pmca == pmc->multiaddr)
+ break;
+ }
+ if (!pmc) {
+ /* MCA not found?? bug */
+ read_unlock(&in_dev->lock);
+ return -ESRCH;
+ }
+ spin_lock_bh(&pmc->lock);
+ read_unlock(&in_dev->lock);
+#ifdef CONFIG_IP_MULTICAST
+ sf_markstate(pmc);
+#endif
+ if (!delta) {
+ err = -EINVAL;
+ if (!pmc->sfcount[sfmode])
+ goto out_unlock;
+ pmc->sfcount[sfmode]--;
+ }
+ err = 0;
+ for (i=0; i<sfcount; i++) {
+ int rv = ip_mc_del1_src(pmc, sfmode, &psfsrc[i]);
+
+ changerec |= rv > 0;
+ if (!err && rv < 0)
+ err = rv;
+ }
+ if (pmc->sfmode == MCAST_EXCLUDE &&
+ pmc->sfcount[MCAST_EXCLUDE] == 0 &&
+ pmc->sfcount[MCAST_INCLUDE]) {
+#ifdef CONFIG_IP_MULTICAST
+ struct ip_sf_list *psf;
+#endif
+
+ /* filter mode change */
+ pmc->sfmode = MCAST_INCLUDE;
+#ifdef CONFIG_IP_MULTICAST
+ pmc->crcount = in_dev->mr_qrv ? in_dev->mr_qrv :
+ IGMP_Unsolicited_Report_Count;
+ in_dev->mr_ifc_count = pmc->crcount;
+ for (psf=pmc->sources; psf; psf = psf->sf_next)
+ psf->sf_crcount = 0;
+ igmp_ifc_event(pmc->interface);
+ } else if (sf_setstate(pmc) || changerec) {
+ igmp_ifc_event(pmc->interface);
+#endif
+ }
+out_unlock:
+ spin_unlock_bh(&pmc->lock);
+ return err;
+}
+
+/*
+ * Add multicast single-source filter to the interface list
+ */
+static int ip_mc_add1_src(struct ip_mc_list *pmc, int sfmode,
+ __u32 *psfsrc, int delta)
+{
+ struct ip_sf_list *psf, *psf_prev;
+
+ psf_prev = 0;
+ for (psf=pmc->sources; psf; psf=psf->sf_next) {
+ if (psf->sf_inaddr == *psfsrc)
+ break;
+ psf_prev = psf;
+ }
+ if (!psf) {
+ psf = (struct ip_sf_list *)kmalloc(sizeof(*psf), GFP_ATOMIC);
+ if (!psf)
+ return -ENOBUFS;
+ memset(psf, 0, sizeof(*psf));
+ psf->sf_inaddr = *psfsrc;
+ if (psf_prev) {
+ psf_prev->sf_next = psf;
+ } else
+ pmc->sources = psf;
+ }
+ psf->sf_count[sfmode]++;
+ if (psf->sf_count[sfmode] == 1) {
+ ip_rt_multicast_event(pmc->interface);
+ }
+ return 0;
+}
+
+#ifdef CONFIG_IP_MULTICAST
+static void sf_markstate(struct ip_mc_list *pmc)
+{
+ struct ip_sf_list *psf;
+ int mca_xcount = pmc->sfcount[MCAST_EXCLUDE];
+
+ for (psf=pmc->sources; psf; psf=psf->sf_next)
+ if (pmc->sfcount[MCAST_EXCLUDE]) {
+ psf->sf_oldin = mca_xcount ==
+ psf->sf_count[MCAST_EXCLUDE] &&
+ !psf->sf_count[MCAST_INCLUDE];
+ } else
+ psf->sf_oldin = psf->sf_count[MCAST_INCLUDE] != 0;
+}
+
+static int sf_setstate(struct ip_mc_list *pmc)
+{
+ struct ip_sf_list *psf;
+ int mca_xcount = pmc->sfcount[MCAST_EXCLUDE];
+ int qrv = pmc->interface->mr_qrv;
+ int new_in, rv;
+
+ rv = 0;
+ for (psf=pmc->sources; psf; psf=psf->sf_next) {
+ if (pmc->sfcount[MCAST_EXCLUDE]) {
+ new_in = mca_xcount == psf->sf_count[MCAST_EXCLUDE] &&
+ !psf->sf_count[MCAST_INCLUDE];
+ } else
+ new_in = psf->sf_count[MCAST_INCLUDE] != 0;
+ if (new_in != psf->sf_oldin) {
+ psf->sf_crcount = qrv;
+ rv++;
+ }
+ }
+ return rv;
+}
+#endif
+
+/*
+ * Add multicast source filter list to the interface list
+ */
+int ip_mc_add_src(struct in_device *in_dev, __u32 *pmca, int sfmode,
+ int sfcount, __u32 *psfsrc, int delta)
+{
+ struct ip_mc_list *pmc;
+ int isexclude;
+ int i, err;
+
+ if (!in_dev)
+ return -ENODEV;
+ read_lock(&in_dev->lock);
+ for (pmc=in_dev->mc_list; pmc; pmc=pmc->next) {
+ if (*pmca == pmc->multiaddr)
+ break;
+ }
+ if (!pmc) {
+ /* MCA not found?? bug */
+ read_unlock(&in_dev->lock);
+ return -ESRCH;
+ }
+ spin_lock_bh(&pmc->lock);
+ read_unlock(&in_dev->lock);
+
+#ifdef CONFIG_IP_MULTICAST
+ sf_markstate(pmc);
+#endif
+ isexclude = pmc->sfmode == MCAST_EXCLUDE;
+ if (!delta)
+ pmc->sfcount[sfmode]++;
+ err = 0;
+ for (i=0; i<sfcount; i++) {
+ err = ip_mc_add1_src(pmc, sfmode, &psfsrc[i], delta);
+ if (err)
+ break;
+ }
+ if (err) {
+ int j;
+
+ pmc->sfcount[sfmode]--;
+ for (j=0; j<i; j++)
+ (void) ip_mc_del1_src(pmc, sfmode, &psfsrc[i]);
+ } else if (isexclude != (pmc->sfcount[MCAST_EXCLUDE] != 0)) {
+#ifdef CONFIG_IP_MULTICAST
+ struct in_device *in_dev = pmc->interface;
+ struct ip_sf_list *psf;
+#endif
+
+ /* filter mode change */
+ if (pmc->sfcount[MCAST_EXCLUDE])
+ pmc->sfmode = MCAST_EXCLUDE;
+ else if (pmc->sfcount[MCAST_INCLUDE])
+ pmc->sfmode = MCAST_INCLUDE;
+#ifdef CONFIG_IP_MULTICAST
+ /* else no filters; keep old mode for reports */
+
+ pmc->crcount = in_dev->mr_qrv ? in_dev->mr_qrv :
+ IGMP_Unsolicited_Report_Count;
+ in_dev->mr_ifc_count = pmc->crcount;
+ for (psf=pmc->sources; psf; psf = psf->sf_next)
+ psf->sf_crcount = 0;
+ igmp_ifc_event(in_dev);
+ } else if (sf_setstate(pmc)) {
+ igmp_ifc_event(in_dev);
+#endif
+ }
+ spin_unlock_bh(&pmc->lock);
+ return err;
+}
+
+static void ip_mc_clear_src(struct ip_mc_list *pmc)
+{
+ struct ip_sf_list *psf, *nextpsf;
+
+ for (psf=pmc->tomb; psf; psf=nextpsf) {
+ nextpsf = psf->sf_next;
+ kfree(psf);
+ }
+ pmc->tomb = 0;
+ for (psf=pmc->sources; psf; psf=nextpsf) {
+ nextpsf = psf->sf_next;
+ kfree(psf);
+ }
+ pmc->sources = 0;
+ pmc->sfmode = MCAST_EXCLUDE;
+ pmc->sfcount[MCAST_EXCLUDE] = 0;
+ pmc->sfcount[MCAST_EXCLUDE] = 1;
+}
+
+
+/*
+ * Join a multicast group
+ */
+int ip_mc_join_group(struct sock *sk , struct ip_mreqn *imr)
+{
+ int err;
+ u32 addr = imr->imr_multiaddr.s_addr;
+ struct ip_mc_socklist *iml, *i;
+ struct in_device *in_dev;
+ struct inet_opt *inet = &sk->protinfo.af_inet;
+ int count = 0;
+
+ if (!MULTICAST(addr))
+ return -EINVAL;
+
+ rtnl_shlock();
+
+ in_dev = ip_mc_find_dev(imr);
+
+ if (!in_dev) {
+ iml = NULL;
+ err = -ENODEV;
+ goto done;
+ }
+
+ iml = (struct ip_mc_socklist *)sock_kmalloc(sk, sizeof(*iml), GFP_KERNEL);
+
+ err = -EADDRINUSE;
+ for (i = inet->mc_list; i; i = i->next) {
+ if (memcmp(&i->multi, imr, sizeof(*imr)) == 0) {
+ /* New style additions are reference counted */
+ if (imr->imr_address.s_addr == 0) {
+ i->count++;
+ err = 0;
+ }
+ goto done;
+ }
+ count++;
+ }
+ err = -ENOBUFS;
+ if (iml == NULL || count >= sysctl_igmp_max_memberships)
+ goto done;
+ memcpy(&iml->multi, imr, sizeof(*imr));
+ iml->next = sk->protinfo.af_inet.mc_list;
+ iml->count = 1;
+ iml->sflist = NULL;
+ iml->sfmode = MCAST_EXCLUDE;
+ inet->mc_list = iml;
+ ip_mc_inc_group(in_dev, addr);
+ iml = NULL;
+ err = 0;
+
+done:
+ rtnl_shunlock();
+ if (iml)
+ sock_kfree_s(sk, iml, sizeof(*iml));
+ return err;
+}
+
+int ip_mc_leave_src(struct sock *sk, struct ip_mc_socklist *iml,
+ struct in_device *in_dev)
+{
+ int err;
+
+ if (iml->sflist == 0) {
+ /* any-source empty exclude case */
+ return ip_mc_del_src(in_dev, &iml->multi.imr_multiaddr.s_addr,
+ iml->sfmode, 0, 0, 0);
+ }
+ err = ip_mc_del_src(in_dev, &iml->multi.imr_multiaddr.s_addr,
+ iml->sfmode, iml->sflist->sl_count,
+ iml->sflist->sl_addr, 0);
+ sock_kfree_s(sk, iml->sflist, IP_SFLSIZE(iml->sflist->sl_max));
+ iml->sflist = 0;
+ return err;
+}
+
+/*
+ * Ask a socket to leave a group.
+ */
+
+int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr)
+{
+ struct inet_opt *inet = &sk->protinfo.af_inet;
+ struct ip_mc_socklist *iml, **imlp;
+
+ rtnl_lock();
+ for (imlp = &inet->mc_list; (iml = *imlp) != NULL; imlp = &iml->next) {
+ if (iml->multi.imr_multiaddr.s_addr==imr->imr_multiaddr.s_addr &&
+ iml->multi.imr_address.s_addr==imr->imr_address.s_addr &&
+ (!imr->imr_ifindex || iml->multi.imr_ifindex==imr->imr_ifindex)) {
+ struct in_device *in_dev;
+
+ in_dev = inetdev_by_index(iml->multi.imr_ifindex);
+ if (in_dev)
+ (void) ip_mc_leave_src(sk, iml, in_dev);
+ if (--iml->count) {
+ rtnl_unlock();
+ if (in_dev)
+ in_dev_put(in_dev);
+ return 0;
+ }
+
+ *imlp = iml->next;
+
+ if (in_dev) {
+ ip_mc_dec_group(in_dev, imr->imr_multiaddr.s_addr);
+ in_dev_put(in_dev);
+ }
+ rtnl_unlock();
+ sock_kfree_s(sk, iml, sizeof(*iml));
+ return 0;
+ }
+ }
+ rtnl_unlock();
+ return -EADDRNOTAVAIL;
+}
+
+int ip_mc_source(int add, int omode, struct sock *sk, struct
+ ip_mreq_source *mreqs, int ifindex)
+{
+ int err;
+ struct ip_mreqn imr;
+ u32 addr = mreqs->imr_multiaddr;
+ struct ip_mc_socklist *pmc;
+ struct in_device *in_dev = 0;
+ struct inet_opt *inet = &sk->protinfo.af_inet;
+ struct ip_sf_socklist *psl;
+ int i, j, rv;
+
+ if (!MULTICAST(addr))
+ return -EINVAL;
+
+ rtnl_shlock();
+
+ imr.imr_multiaddr.s_addr = mreqs->imr_multiaddr;
+ imr.imr_address.s_addr = mreqs->imr_interface;
+ imr.imr_ifindex = ifindex;
+ in_dev = ip_mc_find_dev(&imr);
+
+ if (!in_dev) {
+ err = -ENODEV;
+ goto done;
+ }
+ err = -EADDRNOTAVAIL;
+
+ for (pmc=inet->mc_list; pmc; pmc=pmc->next) {
+ if (memcmp(&pmc->multi, mreqs, 2*sizeof(__u32)) == 0)
+ break;
+ }
+ if (!pmc) /* must have a prior join */
+ goto done;
+ /* if a source filter was set, must be the same mode as before */
+ if (pmc->sflist) {
+ if (pmc->sfmode != omode)
+ goto done;
+ } else if (pmc->sfmode != omode) {
+ /* allow mode switches for empty-set filters */
+ ip_mc_add_src(in_dev, &mreqs->imr_multiaddr, omode, 0, 0, 0);
+ ip_mc_del_src(in_dev, &mreqs->imr_multiaddr, pmc->sfmode, 0,
+ 0, 0);
+ pmc->sfmode = omode;
+ }
+
+ psl = pmc->sflist;
+ if (!add) {
+ if (!psl)
+ goto done;
+ rv = !0;
+ for (i=0; i<psl->sl_count; i++) {
+ rv = memcmp(&psl->sl_addr[i], &mreqs->imr_sourceaddr,
+ sizeof(__u32));
+ if (rv == 0)
+ break;
+ }
+ if (rv) /* source not found */
+ goto done;
+
+ /* update the interface filter */
+ ip_mc_del_src(in_dev, &mreqs->imr_multiaddr, omode, 1,
+ &mreqs->imr_sourceaddr, 1);
+
+ for (j=i+1; j<psl->sl_count; j++)
+ psl->sl_addr[j-1] = psl->sl_addr[j];
+ psl->sl_count--;
+ err = 0;
+ goto done;
+ }
+ /* else, add a new source to the filter */
+
+ if (psl && psl->sl_count >= sysctl_igmp_max_msf) {
+ err = -ENOBUFS;
+ goto done;
+ }
+ if (!psl || psl->sl_count == psl->sl_max) {
+ struct ip_sf_socklist *newpsl;
+ int count = IP_SFBLOCK;
+
+ if (psl)
+ count += psl->sl_max;
+ newpsl = (struct ip_sf_socklist *)sock_kmalloc(sk,
+ IP_SFLSIZE(count), GFP_KERNEL);
+ if (!newpsl) {
+ err = -ENOBUFS;
+ goto done;
+ }
+ newpsl->sl_max = count;
+ newpsl->sl_count = count - IP_SFBLOCK;
+ if (psl) {
+ for (i=0; i<psl->sl_count; i++)
+ newpsl->sl_addr[i] = psl->sl_addr[i];
+ sock_kfree_s(sk, psl, IP_SFLSIZE(psl->sl_max));
+ }
+ pmc->sflist = psl = newpsl;
+ }
+ rv = 1; /* > 0 for insert logic below if sl_count is 0 */
+ for (i=0; i<psl->sl_count; i++) {
+ rv = memcmp(&psl->sl_addr[i], &mreqs->imr_sourceaddr,
+ sizeof(__u32));
+ if (rv == 0)
+ break;
+ }
+ if (rv == 0) /* address already there is an error */
+ goto done;
+ for (j=psl->sl_count-1; j>=i; j--)
+ psl->sl_addr[j+1] = psl->sl_addr[j];
+ psl->sl_addr[i] = mreqs->imr_sourceaddr;
+ psl->sl_count++;
+ err = 0;
+ /* update the interface list */
+ ip_mc_add_src(in_dev, &mreqs->imr_multiaddr, omode, 1,
+ &mreqs->imr_sourceaddr, 1);
+done:
+ rtnl_shunlock();
+ return err;
+}
+
+int ip_mc_msfilter(struct sock *sk, struct ip_msfilter *msf, int ifindex)
+{
+ int err;
+ struct ip_mreqn imr;
+ u32 addr = msf->imsf_multiaddr;
+ struct ip_mc_socklist *pmc;
+ struct in_device *in_dev;
+ struct inet_opt *inet = &sk->protinfo.af_inet;
+ struct ip_sf_socklist *newpsl, *psl;
+
+ if (!MULTICAST(addr))
+ return -EINVAL;
+ if (msf->imsf_fmode != MCAST_INCLUDE &&
+ msf->imsf_fmode != MCAST_EXCLUDE)
+ return -EINVAL;
+
+ rtnl_shlock();
+
+ imr.imr_multiaddr.s_addr = msf->imsf_multiaddr;
+ imr.imr_address.s_addr = msf->imsf_interface;
+ imr.imr_ifindex = ifindex;
+ in_dev = ip_mc_find_dev(&imr);
+
+ if (!in_dev) {
+ err = -ENODEV;
+ goto done;
+ }
+ err = -EADDRNOTAVAIL;
+
+ for (pmc=inet->mc_list; pmc; pmc=pmc->next) {
+ if (pmc->multi.imr_multiaddr.s_addr == msf->imsf_multiaddr &&
+ pmc->multi.imr_ifindex == imr.imr_ifindex)
+ break;
+ }
+ if (!pmc) /* must have a prior join */
+ goto done;
+ if (msf->imsf_numsrc) {
+ newpsl = (struct ip_sf_socklist *)sock_kmalloc(sk,
+ IP_SFLSIZE(msf->imsf_numsrc), GFP_KERNEL);
+ if (!newpsl) {
+ err = -ENOBUFS;
+ goto done;
+ }
+ newpsl->sl_max = newpsl->sl_count = msf->imsf_numsrc;
+ memcpy(newpsl->sl_addr, msf->imsf_slist,
+ msf->imsf_numsrc * sizeof(msf->imsf_slist[0]));
+ err = ip_mc_add_src(in_dev, &msf->imsf_multiaddr,
+ msf->imsf_fmode, newpsl->sl_count, newpsl->sl_addr, 0);
+ if (err) {
+ sock_kfree_s(sk, newpsl, IP_SFLSIZE(newpsl->sl_max));
+ goto done;
+ }
+ } else
+ newpsl = 0;
+ psl = pmc->sflist;
+ if (psl) {
+ (void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode,
+ psl->sl_count, psl->sl_addr, 0);
+ sock_kfree_s(sk, psl, IP_SFLSIZE(psl->sl_max));
+ } else
+ (void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode,
+ 0, 0, 0);
+ pmc->sflist = newpsl;
+ pmc->sfmode = msf->imsf_fmode;
+done:
+ rtnl_shunlock();
+ return err;
+}
+
+int ip_mc_msfget(struct sock *sk, struct ip_msfilter *msf,
+ struct ip_msfilter *optval, int *optlen)
+{
+ int err, len, count, copycount;
+ struct ip_mreqn imr;
+ u32 addr = msf->imsf_multiaddr;
+ struct ip_mc_socklist *pmc;
+ struct in_device *in_dev;
+ struct inet_opt *inet = &sk->protinfo.af_inet;
+ struct ip_sf_socklist *psl;
+
+ if (!MULTICAST(addr))
+ return -EINVAL;
+
+ rtnl_shlock();
+
+ imr.imr_multiaddr.s_addr = msf->imsf_multiaddr;
+ imr.imr_address.s_addr = msf->imsf_interface;
+ imr.imr_ifindex = 0;
+ in_dev = ip_mc_find_dev(&imr);
+
+ if (!in_dev) {
+ err = -ENODEV;
+ goto done;
+ }
+ err = -EADDRNOTAVAIL;
+
+ for (pmc=inet->mc_list; pmc; pmc=pmc->next) {
+ if (pmc->multi.imr_multiaddr.s_addr == msf->imsf_multiaddr &&
+ pmc->multi.imr_ifindex == imr.imr_ifindex)
+ break;
+ }
+ if (!pmc) /* must have a prior join */
+ goto done;
+ msf->imsf_fmode = pmc->sfmode;
+ psl = pmc->sflist;
+ rtnl_shunlock();
+ if (!psl) {
+ len = 0;
+ count = 0;
+ } else {
+ count = psl->sl_count;
+ }
+ copycount = count < msf->imsf_numsrc ? count : msf->imsf_numsrc;
+ len = copycount * sizeof(psl->sl_addr[0]);
+ msf->imsf_numsrc = count;
+ if (put_user(IP_MSFILTER_SIZE(copycount), optlen) ||
+ copy_to_user((void *)optval, msf, IP_MSFILTER_SIZE(0))) {
+ return -EFAULT;
+ }
+ if (len &&
+ copy_to_user((void *)&optval->imsf_slist[0], psl->sl_addr, len))
+ return -EFAULT;
+ return 0;
+done:
+ rtnl_shunlock();
+ return err;
+}
+
+int ip_mc_gsfget(struct sock *sk, struct group_filter *gsf,
+ struct group_filter *optval, int *optlen)
+{
+ int err, i, count, copycount;
+ struct sockaddr_in *psin;
+ u32 addr;
+ struct ip_mc_socklist *pmc;
+ struct inet_opt *inet = &sk->protinfo.af_inet;
+ struct ip_sf_socklist *psl;
+
+ psin = (struct sockaddr_in *)&gsf->gf_group;
+ if (psin->sin_family != AF_INET)
+ return -EINVAL;
+ addr = psin->sin_addr.s_addr;
+ if (!MULTICAST(addr))
+ return -EINVAL;
+
+ rtnl_shlock();
+
+ err = -EADDRNOTAVAIL;
+
+ for (pmc=inet->mc_list; pmc; pmc=pmc->next) {
+ if (pmc->multi.imr_multiaddr.s_addr == addr &&
+ pmc->multi.imr_ifindex == gsf->gf_interface)
+ break;
+ }
+ if (!pmc) /* must have a prior join */
+ goto done;
+ gsf->gf_fmode = pmc->sfmode;
+ psl = pmc->sflist;
+ rtnl_shunlock();
+ count = psl ? psl->sl_count : 0;
+ copycount = count < gsf->gf_numsrc ? count : gsf->gf_numsrc;
+ gsf->gf_numsrc = count;
+ if (put_user(GROUP_FILTER_SIZE(copycount), optlen) ||
+ copy_to_user((void *)optval, gsf, GROUP_FILTER_SIZE(0))) {
+ return -EFAULT;
+ }
+ for (i=0; i<copycount; i++) {
+ struct sockaddr_in *psin;
+ struct sockaddr_storage ss;
+
+ psin = (struct sockaddr_in *)&ss;
+ memset(&ss, 0, sizeof(ss));
+ psin->sin_family = AF_INET;
+ psin->sin_addr.s_addr = psl->sl_addr[i];
+ if (copy_to_user((void *)&optval->gf_slist[i], &ss, sizeof(ss)))
+ return -EFAULT;
+ }
+ return 0;
+done:
+ rtnl_shunlock();
+ return err;
+}
+
+/*
+ * check if a multicast source filter allows delivery for a given <src,dst,intf>
+ */
+int ip_mc_sf_allow(struct sock *sk, u32 loc_addr, u32 rmt_addr, int dif)
+{
+ struct inet_opt *inet = &sk->protinfo.af_inet;
+ struct ip_mc_socklist *pmc;
+ struct ip_sf_socklist *psl;
+ int i;
+
+ if (!MULTICAST(loc_addr))
+ return 1;
+
+ for (pmc=inet->mc_list; pmc; pmc=pmc->next) {
+ if (pmc->multi.imr_multiaddr.s_addr == loc_addr &&
+ pmc->multi.imr_ifindex == dif)
+ break;
+ }
+ if (!pmc)
+ return 1;
+ psl = pmc->sflist;
+ if (!psl)
+ return pmc->sfmode == MCAST_EXCLUDE;
+
+ for (i=0; i<psl->sl_count; i++) {
+ if (psl->sl_addr[i] == rmt_addr)
+ break;
+ }
+ if (pmc->sfmode == MCAST_INCLUDE && i >= psl->sl_count)
+ return 0;
+ if (pmc->sfmode == MCAST_EXCLUDE && i < psl->sl_count)
+ return 0;
+ return 1;
+}
+
+/*
+ * A socket is closing.
+ */
+
+void ip_mc_drop_socket(struct sock *sk)
+{
+ struct inet_opt *inet = &sk->protinfo.af_inet;
+ struct ip_mc_socklist *iml;
+
+ if (inet->mc_list == NULL)
+ return;
+
+ rtnl_lock();
+ while ((iml = inet->mc_list) != NULL) {
+ struct in_device *in_dev;
+ inet->mc_list = iml->next;
+
+ if ((in_dev = inetdev_by_index(iml->multi.imr_ifindex)) != NULL) {
+ (void) ip_mc_leave_src(sk, iml, in_dev);
+ ip_mc_dec_group(in_dev, iml->multi.imr_multiaddr.s_addr);
+ in_dev_put(in_dev);
+ }
+ sock_kfree_s(sk, iml, sizeof(*iml));
+
+ }
+ rtnl_unlock();
+}
+
+int ip_check_mc(struct in_device *in_dev, u32 mc_addr, u32 src_addr)
+{
+ struct ip_mc_list *im;
+ struct ip_sf_list *psf;
+ int rv = 0;
+
+ read_lock(&in_dev->lock);
+ for (im=in_dev->mc_list; im; im=im->next) {
+ if (im->multiaddr == mc_addr)
+ break;
+ }
+ /*
+ * This should check the protocol and allow all IGMP packets
+ * here, but it isn't available in the call from ip_route_output()
+ * in 2.4.x. It shouldn't actually matter, since groups joined
+ * from within the kernel will have an {exclude, empty} filter.
+ * Differs from 2.5.x here. +-DLS 4/23/03
+ */
+ if (im) {
+ if (src_addr) {
+ for (psf=im->sources; psf; psf=psf->sf_next) {
+ if (psf->sf_inaddr == src_addr)
+ break;
+ }
+ if (psf)
+ rv = psf->sf_count[MCAST_INCLUDE] ||
+ psf->sf_count[MCAST_EXCLUDE] !=
+ im->sfcount[MCAST_EXCLUDE];
+ else
+ rv = im->sfcount[MCAST_EXCLUDE] != 0;
+ } else
+ rv = 1;
+ }
+ read_unlock(&in_dev->lock);
+ return rv;
+}
+
+
+int ip_mc_procinfo(char *buffer, char **start, off_t offset, int length)
+{
+ off_t pos=0, begin=0;
+ struct ip_mc_list *im;
+ int len=0;
+ struct net_device *dev;
+
+ len=sprintf(buffer,"Idx\tDevice : Count Querier\tGroup Users Timer\tReporter\n");
+
+ read_lock(&dev_base_lock);
+ for(dev = dev_base; dev; dev = dev->next) {
+ struct in_device *in_dev = in_dev_get(dev);
+ char *querier = "NONE";
+
+ if (in_dev == NULL)
+ continue;
+
+#ifdef CONFIG_IP_MULTICAST
+ querier = IGMP_V1_SEEN(in_dev) ? "V1" : IGMP_V2_SEEN(in_dev) ?
+ "V2" : "V3";
+#endif
+
+ len+=sprintf(buffer+len,"%d\t%-10s: %5d %7s\n",
+ dev->ifindex, dev->name, dev->mc_count, querier);
+
+ read_lock(&in_dev->lock);
+ for (im = in_dev->mc_list; im; im = im->next) {
+ len+=sprintf(buffer+len,
+ "\t\t\t\t%08lX %5d %d:%08lX\t\t%d\n",
+ im->multiaddr, im->users,
+ im->tm_running, im->tm_running ?
+ im->timer.expires-jiffies : 0,
+ im->reporter);
+
+ pos=begin+len;
+ if(pos<offset)
+ {
+ len=0;
+ begin=pos;
+ }
+ if(pos>offset+length) {
+ read_unlock(&in_dev->lock);
+ in_dev_put(in_dev);
+ goto done;
+ }
+ }
+ read_unlock(&in_dev->lock);
+ in_dev_put(in_dev);
+ }
+done:
+ read_unlock(&dev_base_lock);
+
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ if(len<0)
+ len=0;
+ return len;
+}
+
+int ip_mcf_procinfo(char *buffer, char **start, off_t offset, int length)
+{
+ off_t pos=0, begin=0;
+ int len=0;
+ int first = 1;
+ struct net_device *dev;
+
+ read_lock(&dev_base_lock);
+ for(dev=dev_base; dev; dev=dev->next) {
+ struct in_device *in_dev = in_dev_get(dev);
+ struct ip_mc_list *imc;
+
+ if (in_dev == NULL)
+ continue;
+
+ read_lock(&in_dev->lock);
+
+ for (imc=in_dev->mc_list; imc; imc=imc->next) {
+ struct ip_sf_list *psf;
+
+ spin_lock_bh(&imc->lock);
+ for (psf=imc->sources; psf; psf=psf->sf_next) {
+ if (first) {
+ len += sprintf(buffer+len, "%3s %6s "
+ "%10s %10s %6s %6s\n", "Idx",
+ "Device", "MCA", "SRC", "INC",
+ "EXC");
+ first = 0;
+ }
+ len += sprintf(buffer+len, "%3d %6.6s 0x%08x "
+ "0x%08x %6lu %6lu\n", dev->ifindex,
+ dev->name, ntohl(imc->multiaddr),
+ ntohl(psf->sf_inaddr),
+ psf->sf_count[MCAST_INCLUDE],
+ psf->sf_count[MCAST_EXCLUDE]);
+ pos=begin+len;
+ if(pos<offset)
+ {
+ len=0;
+ begin=pos;
+ }
+ if(pos>offset+length) {
+ spin_unlock_bh(&imc->lock);
+ read_unlock(&in_dev->lock);
+ in_dev_put(in_dev);
+ goto done;
+ }
+ }
+ spin_unlock_bh(&imc->lock);
+ }
+ read_unlock(&in_dev->lock);
+ in_dev_put(in_dev);
+ }
+done:
+ read_unlock(&dev_base_lock);
+
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ if(len<0)
+ len=0;
+ return len;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/ipv4/inetpeer.c b/uClinux-2.4.31-uc0/net/ipv4/inetpeer.c
new file mode 100644
index 0000000..bc3af58
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/inetpeer.c
@@ -0,0 +1,453 @@
+/*
+ * INETPEER - A storage for permanent information about peers
+ *
+ * This source is covered by the GNU GPL, the same as all kernel sources.
+ *
+ * Version: $Id: inetpeer.c,v 1.7 2001/09/20 21:22:50 davem Exp $
+ *
+ * Authors: Andrey V. Savochkin <saw@msu.ru>
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/random.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/time.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <net/inetpeer.h>
+
+/*
+ * Theory of operations.
+ * We keep one entry for each peer IP address. The nodes contains long-living
+ * information about the peer which doesn't depend on routes.
+ * At this moment this information consists only of ID field for the next
+ * outgoing IP packet. This field is incremented with each packet as encoded
+ * in inet_getid() function (include/net/inetpeer.h).
+ * At the moment of writing this notes identifier of IP packets is generated
+ * to be unpredictable using this code only for packets subjected
+ * (actually or potentially) to defragmentation. I.e. DF packets less than
+ * PMTU in size uses a constant ID and do not use this code (see
+ * ip_select_ident() in include/net/ip.h).
+ *
+ * Route cache entries hold references to our nodes.
+ * New cache entries get references via lookup by destination IP address in
+ * the avl tree. The reference is grabbed only when it's needed i.e. only
+ * when we try to output IP packet which needs an unpredictable ID (see
+ * __ip_select_ident() in net/ipv4/route.c).
+ * Nodes are removed only when reference counter goes to 0.
+ * When it's happened the node may be removed when a sufficient amount of
+ * time has been passed since its last use. The less-recently-used entry can
+ * also be removed if the pool is overloaded i.e. if the total amount of
+ * entries is greater-or-equal than the threshold.
+ *
+ * Node pool is organised as an AVL tree.
+ * Such an implementation has been chosen not just for fun. It's a way to
+ * prevent easy and efficient DoS attacks by creating hash collisions. A huge
+ * amount of long living nodes in a single hash slot would significantly delay
+ * lookups performed with disabled BHs.
+ *
+ * Serialisation issues.
+ * 1. Nodes may appear in the tree only with the pool write lock held.
+ * 2. Nodes may disappear from the tree only with the pool write lock held
+ * AND reference count being 0.
+ * 3. Nodes appears and disappears from unused node list only under
+ * "inet_peer_unused_lock".
+ * 4. Global variable peer_total is modified under the pool lock.
+ * 5. struct inet_peer fields modification:
+ * avl_left, avl_right, avl_parent, avl_height: pool lock
+ * unused_next, unused_prevp: unused node list lock
+ * refcnt: atomically against modifications on other CPU;
+ * usually under some other lock to prevent node disappearing
+ * dtime: unused node list lock
+ * v4daddr: unchangeable
+ * ip_id_count: idlock
+ */
+
+/* Exported for inet_getid inline function. */
+spinlock_t inet_peer_idlock = SPIN_LOCK_UNLOCKED;
+
+static kmem_cache_t *peer_cachep;
+
+#define node_height(x) x->avl_height
+static struct inet_peer peer_fake_node = {
+ avl_left : &peer_fake_node,
+ avl_right : &peer_fake_node,
+ avl_height : 0
+};
+#define peer_avl_empty (&peer_fake_node)
+static struct inet_peer *peer_root = peer_avl_empty;
+static rwlock_t peer_pool_lock = RW_LOCK_UNLOCKED;
+#define PEER_MAXDEPTH 40 /* sufficient for about 2^27 nodes */
+
+static volatile int peer_total;
+/* Exported for sysctl_net_ipv4. */
+int inet_peer_threshold = 65536 + 128; /* start to throw entries more
+ * aggressively at this stage */
+int inet_peer_minttl = 120 * HZ; /* TTL under high load: 120 sec */
+int inet_peer_maxttl = 10 * 60 * HZ; /* usual time to live: 10 min */
+
+/* Exported for inet_putpeer inline function. */
+struct inet_peer *inet_peer_unused_head,
+ **inet_peer_unused_tailp = &inet_peer_unused_head;
+spinlock_t inet_peer_unused_lock = SPIN_LOCK_UNLOCKED;
+#define PEER_MAX_CLEANUP_WORK 30
+
+static void peer_check_expire(unsigned long dummy);
+static struct timer_list peer_periodic_timer =
+ { { NULL, NULL }, 0, 0, &peer_check_expire };
+
+/* Exported for sysctl_net_ipv4. */
+int inet_peer_gc_mintime = 10 * HZ,
+ inet_peer_gc_maxtime = 120 * HZ;
+
+/* Called from ip_output.c:ip_init */
+void __init inet_initpeers(void)
+{
+ struct sysinfo si;
+
+ /* Use the straight interface to information about memory. */
+ si_meminfo(&si);
+ /* The values below were suggested by Alexey Kuznetsov
+ * <kuznet@ms2.inr.ac.ru>. I don't have any opinion about the values
+ * myself. --SAW
+ */
+ if (si.totalram <= (32768*1024)/PAGE_SIZE)
+ inet_peer_threshold >>= 1; /* max pool size about 1MB on IA32 */
+ if (si.totalram <= (16384*1024)/PAGE_SIZE)
+ inet_peer_threshold >>= 1; /* about 512KB */
+ if (si.totalram <= (8192*1024)/PAGE_SIZE)
+ inet_peer_threshold >>= 2; /* about 128KB */
+
+ peer_cachep = kmem_cache_create("inet_peer_cache",
+ sizeof(struct inet_peer),
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+
+ /* All the timers, started at system startup tend
+ to synchronize. Perturb it a bit.
+ */
+ peer_periodic_timer.expires = jiffies
+ + net_random() % inet_peer_gc_maxtime
+ + inet_peer_gc_maxtime;
+ add_timer(&peer_periodic_timer);
+}
+
+/* Called with or without local BH being disabled. */
+static void unlink_from_unused(struct inet_peer *p)
+{
+ spin_lock_bh(&inet_peer_unused_lock);
+ if (p->unused_prevp != NULL) {
+ /* On unused list. */
+ *p->unused_prevp = p->unused_next;
+ if (p->unused_next != NULL)
+ p->unused_next->unused_prevp = p->unused_prevp;
+ else
+ inet_peer_unused_tailp = p->unused_prevp;
+ p->unused_prevp = NULL; /* mark it as removed */
+ }
+ spin_unlock_bh(&inet_peer_unused_lock);
+}
+
+/* Called with local BH disabled and the pool lock held. */
+#define lookup(daddr) \
+({ \
+ struct inet_peer *u, **v; \
+ stackptr = stack; \
+ *stackptr++ = &peer_root; \
+ for (u = peer_root; u != peer_avl_empty; ) { \
+ if (daddr == u->v4daddr) \
+ break; \
+ if (daddr < u->v4daddr) \
+ v = &u->avl_left; \
+ else \
+ v = &u->avl_right; \
+ *stackptr++ = v; \
+ u = *v; \
+ } \
+ u; \
+})
+
+/* Called with local BH disabled and the pool write lock held. */
+#define lookup_rightempty(start) \
+({ \
+ struct inet_peer *u, **v; \
+ *stackptr++ = &start->avl_left; \
+ v = &start->avl_left; \
+ for (u = *v; u->avl_right != peer_avl_empty; ) { \
+ v = &u->avl_right; \
+ *stackptr++ = v; \
+ u = *v; \
+ } \
+ u; \
+})
+
+/* Called with local BH disabled and the pool write lock held.
+ * Variable names are the proof of operation correctness.
+ * Look into mm/map_avl.c for more detail description of the ideas. */
+static void peer_avl_rebalance(struct inet_peer **stack[],
+ struct inet_peer ***stackend)
+{
+ struct inet_peer **nodep, *node, *l, *r;
+ int lh, rh;
+
+ while (stackend > stack) {
+ nodep = *--stackend;
+ node = *nodep;
+ l = node->avl_left;
+ r = node->avl_right;
+ lh = node_height(l);
+ rh = node_height(r);
+ if (lh > rh + 1) { /* l: RH+2 */
+ struct inet_peer *ll, *lr, *lrl, *lrr;
+ int lrh;
+ ll = l->avl_left;
+ lr = l->avl_right;
+ lrh = node_height(lr);
+ if (lrh <= node_height(ll)) { /* ll: RH+1 */
+ node->avl_left = lr; /* lr: RH or RH+1 */
+ node->avl_right = r; /* r: RH */
+ node->avl_height = lrh + 1; /* RH+1 or RH+2 */
+ l->avl_left = ll; /* ll: RH+1 */
+ l->avl_right = node; /* node: RH+1 or RH+2 */
+ l->avl_height = node->avl_height + 1;
+ *nodep = l;
+ } else { /* ll: RH, lr: RH+1 */
+ lrl = lr->avl_left; /* lrl: RH or RH-1 */
+ lrr = lr->avl_right; /* lrr: RH or RH-1 */
+ node->avl_left = lrr; /* lrr: RH or RH-1 */
+ node->avl_right = r; /* r: RH */
+ node->avl_height = rh + 1; /* node: RH+1 */
+ l->avl_left = ll; /* ll: RH */
+ l->avl_right = lrl; /* lrl: RH or RH-1 */
+ l->avl_height = rh + 1; /* l: RH+1 */
+ lr->avl_left = l; /* l: RH+1 */
+ lr->avl_right = node; /* node: RH+1 */
+ lr->avl_height = rh + 2;
+ *nodep = lr;
+ }
+ } else if (rh > lh + 1) { /* r: LH+2 */
+ struct inet_peer *rr, *rl, *rlr, *rll;
+ int rlh;
+ rr = r->avl_right;
+ rl = r->avl_left;
+ rlh = node_height(rl);
+ if (rlh <= node_height(rr)) { /* rr: LH+1 */
+ node->avl_right = rl; /* rl: LH or LH+1 */
+ node->avl_left = l; /* l: LH */
+ node->avl_height = rlh + 1; /* LH+1 or LH+2 */
+ r->avl_right = rr; /* rr: LH+1 */
+ r->avl_left = node; /* node: LH+1 or LH+2 */
+ r->avl_height = node->avl_height + 1;
+ *nodep = r;
+ } else { /* rr: RH, rl: RH+1 */
+ rlr = rl->avl_right; /* rlr: LH or LH-1 */
+ rll = rl->avl_left; /* rll: LH or LH-1 */
+ node->avl_right = rll; /* rll: LH or LH-1 */
+ node->avl_left = l; /* l: LH */
+ node->avl_height = lh + 1; /* node: LH+1 */
+ r->avl_right = rr; /* rr: LH */
+ r->avl_left = rlr; /* rlr: LH or LH-1 */
+ r->avl_height = lh + 1; /* r: LH+1 */
+ rl->avl_right = r; /* r: LH+1 */
+ rl->avl_left = node; /* node: LH+1 */
+ rl->avl_height = lh + 2;
+ *nodep = rl;
+ }
+ } else {
+ node->avl_height = (lh > rh ? lh : rh) + 1;
+ }
+ }
+}
+
+/* Called with local BH disabled and the pool write lock held. */
+#define link_to_pool(n) \
+do { \
+ n->avl_height = 1; \
+ n->avl_left = peer_avl_empty; \
+ n->avl_right = peer_avl_empty; \
+ **--stackptr = n; \
+ peer_avl_rebalance(stack, stackptr); \
+} while(0)
+
+/* May be called with local BH enabled. */
+static void unlink_from_pool(struct inet_peer *p)
+{
+ int do_free;
+
+ do_free = 0;
+
+ write_lock_bh(&peer_pool_lock);
+ /* Check the reference counter. It was artificially incremented by 1
+ * in cleanup() function to prevent sudden disappearing. If the
+ * reference count is still 1 then the node is referenced only as `p'
+ * here and from the pool. So under the exclusive pool lock it's safe
+ * to remove the node and free it later. */
+ if (atomic_read(&p->refcnt) == 1) {
+ struct inet_peer **stack[PEER_MAXDEPTH];
+ struct inet_peer ***stackptr, ***delp;
+ if (lookup(p->v4daddr) != p)
+ BUG();
+ delp = stackptr - 1; /* *delp[0] == p */
+ if (p->avl_left == peer_avl_empty) {
+ *delp[0] = p->avl_right;
+ --stackptr;
+ } else {
+ /* look for a node to insert instead of p */
+ struct inet_peer *t;
+ t = lookup_rightempty(p);
+ if (*stackptr[-1] != t)
+ BUG();
+ **--stackptr = t->avl_left;
+ /* t is removed, t->v4daddr > x->v4daddr for any
+ * x in p->avl_left subtree.
+ * Put t in the old place of p. */
+ *delp[0] = t;
+ t->avl_left = p->avl_left;
+ t->avl_right = p->avl_right;
+ t->avl_height = p->avl_height;
+ if (delp[1] != &p->avl_left)
+ BUG();
+ delp[1] = &t->avl_left; /* was &p->avl_left */
+ }
+ peer_avl_rebalance(stack, stackptr);
+ peer_total--;
+ do_free = 1;
+ }
+ write_unlock_bh(&peer_pool_lock);
+
+ if (do_free)
+ kmem_cache_free(peer_cachep, p);
+ else
+ /* The node is used again. Decrease the reference counter
+ * back. The loop "cleanup -> unlink_from_unused
+ * -> unlink_from_pool -> putpeer -> link_to_unused
+ * -> cleanup (for the same node)"
+ * doesn't really exist because the entry will have a
+ * recent deletion time and will not be cleaned again soon. */
+ inet_putpeer(p);
+}
+
+/* May be called with local BH enabled. */
+static int cleanup_once(unsigned long ttl)
+{
+ struct inet_peer *p;
+
+ /* Remove the first entry from the list of unused nodes. */
+ spin_lock_bh(&inet_peer_unused_lock);
+ p = inet_peer_unused_head;
+ if (p != NULL) {
+ if (time_after(p->dtime + ttl, jiffies)) {
+ /* Do not prune fresh entries. */
+ spin_unlock_bh(&inet_peer_unused_lock);
+ return -1;
+ }
+ inet_peer_unused_head = p->unused_next;
+ if (p->unused_next != NULL)
+ p->unused_next->unused_prevp = p->unused_prevp;
+ else
+ inet_peer_unused_tailp = p->unused_prevp;
+ p->unused_prevp = NULL; /* mark as not on the list */
+ /* Grab an extra reference to prevent node disappearing
+ * before unlink_from_pool() call. */
+ atomic_inc(&p->refcnt);
+ }
+ spin_unlock_bh(&inet_peer_unused_lock);
+
+ if (p == NULL)
+ /* It means that the total number of USED entries has
+ * grown over inet_peer_threshold. It shouldn't really
+ * happen because of entry limits in route cache. */
+ return -1;
+
+ unlink_from_pool(p);
+ return 0;
+}
+
+/* Called with or without local BH being disabled. */
+struct inet_peer *inet_getpeer(__u32 daddr, int create)
+{
+ struct inet_peer *p, *n;
+ struct inet_peer **stack[PEER_MAXDEPTH], ***stackptr;
+
+ /* Look up for the address quickly. */
+ read_lock_bh(&peer_pool_lock);
+ p = lookup(daddr);
+ if (p != peer_avl_empty)
+ atomic_inc(&p->refcnt);
+ read_unlock_bh(&peer_pool_lock);
+
+ if (p != peer_avl_empty) {
+ /* The existing node has been found. */
+ /* Remove the entry from unused list if it was there. */
+ unlink_from_unused(p);
+ return p;
+ }
+
+ if (!create)
+ return NULL;
+
+ /* Allocate the space outside the locked region. */
+ n = kmem_cache_alloc(peer_cachep, GFP_ATOMIC);
+ if (n == NULL)
+ return NULL;
+ n->v4daddr = daddr;
+ atomic_set(&n->refcnt, 1);
+ n->ip_id_count = secure_ip_id(daddr);
+ n->tcp_ts_stamp = 0;
+
+ write_lock_bh(&peer_pool_lock);
+ /* Check if an entry has suddenly appeared. */
+ p = lookup(daddr);
+ if (p != peer_avl_empty)
+ goto out_free;
+
+ /* Link the node. */
+ link_to_pool(n);
+ n->unused_prevp = NULL; /* not on the list */
+ peer_total++;
+ write_unlock_bh(&peer_pool_lock);
+
+ if (peer_total >= inet_peer_threshold)
+ /* Remove one less-recently-used entry. */
+ cleanup_once(0);
+
+ return n;
+
+out_free:
+ /* The appropriate node is already in the pool. */
+ atomic_inc(&p->refcnt);
+ write_unlock_bh(&peer_pool_lock);
+ /* Remove the entry from unused list if it was there. */
+ unlink_from_unused(p);
+ /* Free preallocated the preallocated node. */
+ kmem_cache_free(peer_cachep, n);
+ return p;
+}
+
+/* Called with local BH disabled. */
+static void peer_check_expire(unsigned long dummy)
+{
+ int i;
+ int ttl;
+
+ if (peer_total >= inet_peer_threshold)
+ ttl = inet_peer_minttl;
+ else
+ ttl = inet_peer_maxttl
+ - (inet_peer_maxttl - inet_peer_minttl) / HZ *
+ peer_total / inet_peer_threshold * HZ;
+ for (i = 0; i < PEER_MAX_CLEANUP_WORK && !cleanup_once(ttl); i++);
+
+ /* Trigger the timer after inet_peer_gc_mintime .. inet_peer_gc_maxtime
+ * interval depending on the total number of entries (more entries,
+ * less interval). */
+ peer_periodic_timer.expires = jiffies
+ + inet_peer_gc_maxtime
+ - (inet_peer_gc_maxtime - inet_peer_gc_mintime) / HZ *
+ peer_total / inet_peer_threshold * HZ;
+ add_timer(&peer_periodic_timer);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ip_forward.c b/uClinux-2.4.31-uc0/net/ipv4/ip_forward.c
new file mode 100644
index 0000000..5ccfc52
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ip_forward.c
@@ -0,0 +1,166 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * The IP forwarding functionality.
+ *
+ * Version: $Id: ip_forward.c,v 1.48 2000/12/13 18:31:48 davem Exp $
+ *
+ * Authors: see ip.c
+ *
+ * Fixes:
+ * Many : Split from ip.c , see ip_input.c for
+ * history.
+ * Dave Gregorich : NULL ip_rt_put fix for multicast
+ * routing.
+ * Jos Vos : Add call_out_firewall before sending,
+ * use output device for accounting.
+ * Jos Vos : Call forward firewall after routing
+ * (always use output device).
+ * Mike McLagan : Routing by source
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/icmp.h>
+#include <linux/netdevice.h>
+#include <net/sock.h>
+#include <net/ip.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+#include <net/icmp.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/netfilter_ipv4.h>
+#include <net/checksum.h>
+#include <linux/route.h>
+#include <net/route.h>
+
+static inline int ip_forward_finish(struct sk_buff *skb)
+{
+ struct ip_options * opt = &(IPCB(skb)->opt);
+
+ IP_INC_STATS_BH(IpForwDatagrams);
+
+ if (opt->optlen == 0) {
+#ifdef CONFIG_NET_FASTROUTE
+ struct rtable *rt = (struct rtable*)skb->dst;
+
+ if (rt->rt_flags&RTCF_FAST && !netdev_fastroute_obstacles) {
+ struct dst_entry *old_dst;
+ unsigned h = ((*(u8*)&rt->key.dst)^(*(u8*)&rt->key.src))&NETDEV_FASTROUTE_HMASK;
+
+ write_lock_irq(&skb->dev->fastpath_lock);
+ old_dst = skb->dev->fastpath[h];
+ skb->dev->fastpath[h] = dst_clone(&rt->u.dst);
+ write_unlock_irq(&skb->dev->fastpath_lock);
+
+ dst_release(old_dst);
+ }
+#endif
+ return (ip_send(skb));
+ }
+
+ ip_forward_options(skb);
+ return (ip_send(skb));
+}
+
+int ip_forward(struct sk_buff *skb)
+{
+ struct net_device *dev2; /* Output device */
+ struct iphdr *iph; /* Our header */
+ struct rtable *rt; /* Route we use */
+ struct ip_options * opt = &(IPCB(skb)->opt);
+ unsigned short mtu;
+
+ if (IPCB(skb)->opt.router_alert && ip_call_ra_chain(skb))
+ return NET_RX_SUCCESS;
+
+ if (skb->pkt_type != PACKET_HOST)
+ goto drop;
+
+ skb->ip_summed = CHECKSUM_NONE;
+
+ /*
+ * According to the RFC, we must first decrease the TTL field. If
+ * that reaches zero, we must reply an ICMP control message telling
+ * that the packet's lifetime expired.
+ */
+
+ iph = skb->nh.iph;
+ rt = (struct rtable*)skb->dst;
+
+ if (iph->ttl <= 1)
+ goto too_many_hops;
+
+ if (opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
+ goto sr_failed;
+
+ /*
+ * Having picked a route we can now send the frame out
+ * after asking the firewall permission to do so.
+ */
+
+ skb->priority = rt_tos2priority(iph->tos);
+ dev2 = rt->u.dst.dev;
+ mtu = rt->u.dst.pmtu;
+
+ /*
+ * We now generate an ICMP HOST REDIRECT giving the route
+ * we calculated.
+ */
+ if (rt->rt_flags&RTCF_DOREDIRECT && !opt->srr)
+ ip_rt_send_redirect(skb);
+
+ /* We are about to mangle packet. Copy it! */
+ if (skb_cow(skb, dev2->hard_header_len))
+ goto drop;
+ iph = skb->nh.iph;
+
+ /* Decrease ttl after skb cow done */
+ ip_decrease_ttl(iph);
+
+ /*
+ * We now may allocate a new buffer, and copy the datagram into it.
+ * If the indicated interface is up and running, kick it.
+ */
+
+ if (skb->len > mtu && (ntohs(iph->frag_off) & IP_DF))
+ goto frag_needed;
+
+#ifdef CONFIG_IP_ROUTE_NAT
+ if (rt->rt_flags & RTCF_NAT) {
+ if (ip_do_nat(skb)) {
+ kfree_skb(skb);
+ return NET_RX_BAD;
+ }
+ }
+#endif
+
+ return NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2,
+ ip_forward_finish);
+
+frag_needed:
+ IP_INC_STATS_BH(IpFragFails);
+ icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
+ goto drop;
+
+sr_failed:
+ /*
+ * Strict routing permits no gatewaying
+ */
+ icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);
+ goto drop;
+
+too_many_hops:
+ /* Tell the sender its packet died... */
+ icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
+drop:
+ kfree_skb(skb);
+ return NET_RX_DROP;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ip_fragment.c b/uClinux-2.4.31-uc0/net/ipv4/ip_fragment.c
new file mode 100644
index 0000000..9067557
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ip_fragment.c
@@ -0,0 +1,688 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * The IP fragmentation functionality.
+ *
+ * Version: $Id: ip_fragment.c,v 1.58.2.1 2002/01/12 07:53:15 davem Exp $
+ *
+ * Authors: Fred N. van Kempen <waltje@uWalt.NL.Mugnet.ORG>
+ * Alan Cox <Alan.Cox@linux.org>
+ *
+ * Fixes:
+ * Alan Cox : Split from ip.c , see ip_input.c for history.
+ * David S. Miller : Begin massive cleanup...
+ * Andi Kleen : Add sysctls.
+ * xxxx : Overlapfrag bug.
+ * Ultima : ip_expire() kernel panic.
+ * Bill Hawes : Frag accounting and evictor fixes.
+ * John McDonald : 0 length frag bug.
+ * Alexey Kuznetsov: SMP races, threading, cleanup.
+ * Patrick McHardy : LRU queue of frag heads for evictor.
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/list.h>
+#include <linux/ip.h>
+#include <linux/icmp.h>
+#include <linux/netdevice.h>
+#include <linux/jhash.h>
+#include <linux/random.h>
+#include <net/sock.h>
+#include <net/ip.h>
+#include <net/icmp.h>
+#include <net/checksum.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/inet.h>
+#include <linux/netfilter_ipv4.h>
+
+/* NOTE. Logic of IP defragmentation is parallel to corresponding IPv6
+ * code now. If you change something here, _PLEASE_ update ipv6/reassembly.c
+ * as well. Or notify me, at least. --ANK
+ */
+
+/* Fragment cache limits. We will commit 256K at one time. Should we
+ * cross that limit we will prune down to 192K. This should cope with
+ * even the most extreme cases without allowing an attacker to measurably
+ * harm machine performance.
+ */
+int sysctl_ipfrag_high_thresh = 256*1024;
+int sysctl_ipfrag_low_thresh = 192*1024;
+
+/* Important NOTE! Fragment queue must be destroyed before MSL expires.
+ * RFC791 is wrong proposing to prolongate timer each fragment arrival by TTL.
+ */
+int sysctl_ipfrag_time = IP_FRAG_TIME;
+
+struct ipfrag_skb_cb
+{
+ struct inet_skb_parm h;
+ int offset;
+};
+
+#define FRAG_CB(skb) ((struct ipfrag_skb_cb*)((skb)->cb))
+
+/* Describe an entry in the "incomplete datagrams" queue. */
+struct ipq {
+ struct ipq *next; /* linked list pointers */
+ struct list_head lru_list; /* lru list member */
+ u32 user;
+ u32 saddr;
+ u32 daddr;
+ u16 id;
+ u8 protocol;
+ u8 last_in;
+#define COMPLETE 4
+#define FIRST_IN 2
+#define LAST_IN 1
+
+ struct sk_buff *fragments; /* linked list of received fragments */
+ int len; /* total length of original datagram */
+ int meat;
+ spinlock_t lock;
+ atomic_t refcnt;
+ struct timer_list timer; /* when will this queue expire? */
+ struct ipq **pprev;
+ int iif;
+ struct timeval stamp;
+};
+
+/* Hash table. */
+
+#define IPQ_HASHSZ 64
+
+/* Per-bucket lock is easy to add now. */
+static struct ipq *ipq_hash[IPQ_HASHSZ];
+static rwlock_t ipfrag_lock = RW_LOCK_UNLOCKED;
+static u32 ipfrag_hash_rnd;
+static LIST_HEAD(ipq_lru_list);
+int ip_frag_nqueues = 0;
+
+static __inline__ void __ipq_unlink(struct ipq *qp)
+{
+ if(qp->next)
+ qp->next->pprev = qp->pprev;
+ *qp->pprev = qp->next;
+ list_del(&qp->lru_list);
+ ip_frag_nqueues--;
+}
+
+static __inline__ void ipq_unlink(struct ipq *ipq)
+{
+ write_lock(&ipfrag_lock);
+ __ipq_unlink(ipq);
+ write_unlock(&ipfrag_lock);
+}
+
+static unsigned int ipqhashfn(u16 id, u32 saddr, u32 daddr, u8 prot)
+{
+ return jhash_3words((u32)id << 16 | prot, saddr, daddr,
+ ipfrag_hash_rnd) & (IPQ_HASHSZ - 1);
+}
+
+static struct timer_list ipfrag_secret_timer;
+int sysctl_ipfrag_secret_interval = 10 * 60 * HZ;
+
+static void ipfrag_secret_rebuild(unsigned long dummy)
+{
+ unsigned long now = jiffies;
+ int i;
+
+ write_lock(&ipfrag_lock);
+ get_random_bytes(&ipfrag_hash_rnd, sizeof(u32));
+ for (i = 0; i < IPQ_HASHSZ; i++) {
+ struct ipq *q;
+
+ q = ipq_hash[i];
+ while (q) {
+ struct ipq *next = q->next;
+ unsigned int hval = ipqhashfn(q->id, q->saddr,
+ q->daddr, q->protocol);
+
+ if (hval != i) {
+ /* Unlink. */
+ if (q->next)
+ q->next->pprev = q->pprev;
+ *q->pprev = q->next;
+
+ /* Relink to new hash chain. */
+ if ((q->next = ipq_hash[hval]) != NULL)
+ q->next->pprev = &q->next;
+ ipq_hash[hval] = q;
+ q->pprev = &ipq_hash[hval];
+ }
+
+ q = next;
+ }
+ }
+ write_unlock(&ipfrag_lock);
+
+ mod_timer(&ipfrag_secret_timer, now + sysctl_ipfrag_secret_interval);
+}
+
+atomic_t ip_frag_mem = ATOMIC_INIT(0); /* Memory used for fragments */
+
+/* Memory Tracking Functions. */
+static __inline__ void frag_kfree_skb(struct sk_buff *skb, int *work)
+{
+ if (work)
+ *work -= skb->truesize;
+ atomic_sub(skb->truesize, &ip_frag_mem);
+ kfree_skb(skb);
+}
+
+static __inline__ void frag_free_queue(struct ipq *qp, int *work)
+{
+ if (work)
+ *work -= sizeof(struct ipq);
+ atomic_sub(sizeof(struct ipq), &ip_frag_mem);
+ kfree(qp);
+}
+
+static __inline__ struct ipq *frag_alloc_queue(void)
+{
+ struct ipq *qp = kmalloc(sizeof(struct ipq), GFP_ATOMIC);
+
+ if(!qp)
+ return NULL;
+ atomic_add(sizeof(struct ipq), &ip_frag_mem);
+ return qp;
+}
+
+
+/* Destruction primitives. */
+
+/* Complete destruction of ipq. */
+static void ip_frag_destroy(struct ipq *qp, int *work)
+{
+ struct sk_buff *fp;
+
+ BUG_TRAP(qp->last_in&COMPLETE);
+ BUG_TRAP(del_timer(&qp->timer) == 0);
+
+ /* Release all fragment data. */
+ fp = qp->fragments;
+ while (fp) {
+ struct sk_buff *xp = fp->next;
+
+ frag_kfree_skb(fp, work);
+ fp = xp;
+ }
+
+ /* Finally, release the queue descriptor itself. */
+ frag_free_queue(qp, work);
+}
+
+static __inline__ void ipq_put(struct ipq *ipq, int *work)
+{
+ if (atomic_dec_and_test(&ipq->refcnt))
+ ip_frag_destroy(ipq, work);
+}
+
+/* Kill ipq entry. It is not destroyed immediately,
+ * because caller (and someone more) holds reference count.
+ */
+static __inline__ void ipq_kill(struct ipq *ipq)
+{
+ if (del_timer(&ipq->timer))
+ atomic_dec(&ipq->refcnt);
+
+ if (!(ipq->last_in & COMPLETE)) {
+ ipq_unlink(ipq);
+ atomic_dec(&ipq->refcnt);
+ ipq->last_in |= COMPLETE;
+ }
+}
+
+/* Memory limiting on fragments. Evictor trashes the oldest
+ * fragment queue until we are back under the threshold.
+ */
+static void ip_evictor(void)
+{
+ struct ipq *qp;
+ struct list_head *tmp;
+ int work;
+
+ work = atomic_read(&ip_frag_mem) - sysctl_ipfrag_low_thresh;
+ if (work <= 0)
+ return;
+
+ while (work > 0) {
+ read_lock(&ipfrag_lock);
+ if (list_empty(&ipq_lru_list)) {
+ read_unlock(&ipfrag_lock);
+ return;
+ }
+ tmp = ipq_lru_list.next;
+ qp = list_entry(tmp, struct ipq, lru_list);
+ atomic_inc(&qp->refcnt);
+ read_unlock(&ipfrag_lock);
+
+ spin_lock(&qp->lock);
+ if (!(qp->last_in&COMPLETE))
+ ipq_kill(qp);
+ spin_unlock(&qp->lock);
+
+ ipq_put(qp, &work);
+ IP_INC_STATS_BH(IpReasmFails);
+ }
+}
+
+/*
+ * Oops, a fragment queue timed out. Kill it and send an ICMP reply.
+ */
+static void ip_expire(unsigned long arg)
+{
+ struct ipq *qp = (struct ipq *) arg;
+
+ spin_lock(&qp->lock);
+
+ if (qp->last_in & COMPLETE)
+ goto out;
+
+ ipq_kill(qp);
+
+ IP_INC_STATS_BH(IpReasmTimeout);
+ IP_INC_STATS_BH(IpReasmFails);
+
+ if ((qp->last_in&FIRST_IN) && qp->fragments != NULL) {
+ struct sk_buff *head = qp->fragments;
+ /* Send an ICMP "Fragment Reassembly Timeout" message. */
+ if ((head->dev = dev_get_by_index(qp->iif)) != NULL) {
+ icmp_send(head, ICMP_TIME_EXCEEDED, ICMP_EXC_FRAGTIME, 0);
+ dev_put(head->dev);
+ }
+ }
+out:
+ spin_unlock(&qp->lock);
+ ipq_put(qp, NULL);
+}
+
+/* Creation primitives. */
+
+static struct ipq *ip_frag_intern(unsigned int hash, struct ipq *qp_in)
+{
+ struct ipq *qp;
+
+ write_lock(&ipfrag_lock);
+#ifdef CONFIG_SMP
+ /* With SMP race we have to recheck hash table, because
+ * such entry could be created on other cpu, while we
+ * promoted read lock to write lock.
+ */
+ for(qp = ipq_hash[hash]; qp; qp = qp->next) {
+ if(qp->id == qp_in->id &&
+ qp->saddr == qp_in->saddr &&
+ qp->daddr == qp_in->daddr &&
+ qp->protocol == qp_in->protocol &&
+ qp->user == qp_in->user) {
+ atomic_inc(&qp->refcnt);
+ write_unlock(&ipfrag_lock);
+ qp_in->last_in |= COMPLETE;
+ ipq_put(qp_in, NULL);
+ return qp;
+ }
+ }
+#endif
+ qp = qp_in;
+
+ if (!mod_timer(&qp->timer, jiffies + sysctl_ipfrag_time))
+ atomic_inc(&qp->refcnt);
+
+ atomic_inc(&qp->refcnt);
+ if((qp->next = ipq_hash[hash]) != NULL)
+ qp->next->pprev = &qp->next;
+ ipq_hash[hash] = qp;
+ qp->pprev = &ipq_hash[hash];
+ INIT_LIST_HEAD(&qp->lru_list);
+ list_add_tail(&qp->lru_list, &ipq_lru_list);
+ ip_frag_nqueues++;
+ write_unlock(&ipfrag_lock);
+ return qp;
+}
+
+/* Add an entry to the 'ipq' queue for a newly received IP datagram. */
+static struct ipq *ip_frag_create(unsigned hash, struct iphdr *iph, u32 user)
+{
+ struct ipq *qp;
+
+ if ((qp = frag_alloc_queue()) == NULL)
+ goto out_nomem;
+
+ qp->protocol = iph->protocol;
+ qp->last_in = 0;
+ qp->id = iph->id;
+ qp->saddr = iph->saddr;
+ qp->daddr = iph->daddr;
+ qp->user = user;
+ qp->len = 0;
+ qp->meat = 0;
+ qp->fragments = NULL;
+ qp->iif = 0;
+
+ /* Initialize a timer for this entry. */
+ init_timer(&qp->timer);
+ qp->timer.data = (unsigned long) qp; /* pointer to queue */
+ qp->timer.function = ip_expire; /* expire function */
+ qp->lock = SPIN_LOCK_UNLOCKED;
+ atomic_set(&qp->refcnt, 1);
+
+ return ip_frag_intern(hash, qp);
+
+out_nomem:
+ NETDEBUG(if (net_ratelimit()) printk(KERN_ERR "ip_frag_create: no memory left !\n"));
+ return NULL;
+}
+
+/* Find the correct entry in the "incomplete datagrams" queue for
+ * this IP datagram, and create new one, if nothing is found.
+ */
+static inline struct ipq *ip_find(struct iphdr *iph, u32 user)
+{
+ __u16 id = iph->id;
+ __u32 saddr = iph->saddr;
+ __u32 daddr = iph->daddr;
+ __u8 protocol = iph->protocol;
+ unsigned int hash = ipqhashfn(id, saddr, daddr, protocol);
+ struct ipq *qp;
+
+ read_lock(&ipfrag_lock);
+ for(qp = ipq_hash[hash]; qp; qp = qp->next) {
+ if(qp->id == id &&
+ qp->saddr == saddr &&
+ qp->daddr == daddr &&
+ qp->protocol == protocol &&
+ qp->user == user) {
+ atomic_inc(&qp->refcnt);
+ read_unlock(&ipfrag_lock);
+ return qp;
+ }
+ }
+ read_unlock(&ipfrag_lock);
+
+ return ip_frag_create(hash, iph, user);
+}
+
+/* Add new segment to existing queue. */
+static void ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
+{
+ struct sk_buff *prev, *next;
+ int flags, offset;
+ int ihl, end;
+
+ if (qp->last_in & COMPLETE)
+ goto err;
+
+ offset = ntohs(skb->nh.iph->frag_off);
+ flags = offset & ~IP_OFFSET;
+ offset &= IP_OFFSET;
+ offset <<= 3; /* offset is in 8-byte chunks */
+ ihl = skb->nh.iph->ihl * 4;
+
+ /* Determine the position of this fragment. */
+ end = offset + skb->len - ihl;
+
+ /* Is this the final fragment? */
+ if ((flags & IP_MF) == 0) {
+ /* If we already have some bits beyond end
+ * or have different end, the segment is corrrupted.
+ */
+ if (end < qp->len ||
+ ((qp->last_in & LAST_IN) && end != qp->len))
+ goto err;
+ qp->last_in |= LAST_IN;
+ qp->len = end;
+ } else {
+ if (end&7) {
+ end &= ~7;
+ if (skb->ip_summed != CHECKSUM_UNNECESSARY)
+ skb->ip_summed = CHECKSUM_NONE;
+ }
+ if (end > qp->len) {
+ /* Some bits beyond end -> corruption. */
+ if (qp->last_in & LAST_IN)
+ goto err;
+ qp->len = end;
+ }
+ }
+ if (end == offset)
+ goto err;
+
+ if (pskb_pull(skb, ihl) == NULL)
+ goto err;
+ if (pskb_trim(skb, end-offset))
+ goto err;
+
+ /* Find out which fragments are in front and at the back of us
+ * in the chain of fragments so far. We must know where to put
+ * this fragment, right?
+ */
+ prev = NULL;
+ for(next = qp->fragments; next != NULL; next = next->next) {
+ if (FRAG_CB(next)->offset >= offset)
+ break; /* bingo! */
+ prev = next;
+ }
+
+ /* We found where to put this one. Check for overlap with
+ * preceding fragment, and, if needed, align things so that
+ * any overlaps are eliminated.
+ */
+ if (prev) {
+ int i = (FRAG_CB(prev)->offset + prev->len) - offset;
+
+ if (i > 0) {
+ offset += i;
+ if (end <= offset)
+ goto err;
+ if (!pskb_pull(skb, i))
+ goto err;
+ if (skb->ip_summed != CHECKSUM_UNNECESSARY)
+ skb->ip_summed = CHECKSUM_NONE;
+ }
+ }
+
+ while (next && FRAG_CB(next)->offset < end) {
+ int i = end - FRAG_CB(next)->offset; /* overlap is 'i' bytes */
+
+ if (i < next->len) {
+ /* Eat head of the next overlapped fragment
+ * and leave the loop. The next ones cannot overlap.
+ */
+ if (!pskb_pull(next, i))
+ goto err;
+ FRAG_CB(next)->offset += i;
+ qp->meat -= i;
+ if (next->ip_summed != CHECKSUM_UNNECESSARY)
+ next->ip_summed = CHECKSUM_NONE;
+ break;
+ } else {
+ struct sk_buff *free_it = next;
+
+ /* Old fragmnet is completely overridden with
+ * new one drop it.
+ */
+ next = next->next;
+
+ if (prev)
+ prev->next = next;
+ else
+ qp->fragments = next;
+
+ qp->meat -= free_it->len;
+ frag_kfree_skb(free_it, NULL);
+ }
+ }
+
+ FRAG_CB(skb)->offset = offset;
+
+ /* Insert this fragment in the chain of fragments. */
+ skb->next = next;
+ if (prev)
+ prev->next = skb;
+ else
+ qp->fragments = skb;
+
+ if (skb->dev)
+ qp->iif = skb->dev->ifindex;
+ skb->dev = NULL;
+ qp->stamp = skb->stamp;
+ qp->meat += skb->len;
+ atomic_add(skb->truesize, &ip_frag_mem);
+ if (offset == 0)
+ qp->last_in |= FIRST_IN;
+
+ write_lock(&ipfrag_lock);
+ list_move_tail(&qp->lru_list, &ipq_lru_list);
+ write_unlock(&ipfrag_lock);
+
+ return;
+
+err:
+ kfree_skb(skb);
+}
+
+
+/* Build a new IP datagram from all its fragments. */
+
+static struct sk_buff *ip_frag_reasm(struct ipq *qp, struct net_device *dev)
+{
+ struct iphdr *iph;
+ struct sk_buff *fp, *head = qp->fragments;
+ int len;
+ int ihlen;
+
+ ipq_kill(qp);
+
+ BUG_TRAP(head != NULL);
+ BUG_TRAP(FRAG_CB(head)->offset == 0);
+
+ /* Allocate a new buffer for the datagram. */
+ ihlen = head->nh.iph->ihl*4;
+ len = ihlen + qp->len;
+
+ if(len > 65535)
+ goto out_oversize;
+
+ /* Head of list must not be cloned. */
+ if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC))
+ goto out_nomem;
+
+ /* If the first fragment is fragmented itself, we split
+ * it to two chunks: the first with data and paged part
+ * and the second, holding only fragments. */
+ if (skb_shinfo(head)->frag_list) {
+ struct sk_buff *clone;
+ int i, plen = 0;
+
+ if ((clone = alloc_skb(0, GFP_ATOMIC)) == NULL)
+ goto out_nomem;
+ clone->next = head->next;
+ head->next = clone;
+ skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list;
+ skb_shinfo(head)->frag_list = NULL;
+ for (i=0; i<skb_shinfo(head)->nr_frags; i++)
+ plen += skb_shinfo(head)->frags[i].size;
+ clone->len = clone->data_len = head->data_len - plen;
+ head->data_len -= clone->len;
+ head->len -= clone->len;
+ clone->csum = 0;
+ clone->ip_summed = head->ip_summed;
+ atomic_add(clone->truesize, &ip_frag_mem);
+ }
+
+ skb_shinfo(head)->frag_list = head->next;
+ skb_push(head, head->data - head->nh.raw);
+ atomic_sub(head->truesize, &ip_frag_mem);
+
+ for (fp=head->next; fp; fp = fp->next) {
+ head->data_len += fp->len;
+ head->len += fp->len;
+ if (head->ip_summed != fp->ip_summed)
+ head->ip_summed = CHECKSUM_NONE;
+ else if (head->ip_summed == CHECKSUM_HW)
+ head->csum = csum_add(head->csum, fp->csum);
+ head->truesize += fp->truesize;
+ atomic_sub(fp->truesize, &ip_frag_mem);
+ }
+
+ head->next = NULL;
+ head->dev = dev;
+ head->stamp = qp->stamp;
+
+ iph = head->nh.iph;
+ iph->frag_off = 0;
+ iph->tot_len = htons(len);
+ IP_INC_STATS_BH(IpReasmOKs);
+ qp->fragments = NULL;
+ return head;
+
+out_nomem:
+ NETDEBUG(if (net_ratelimit())
+ printk(KERN_ERR
+ "IP: queue_glue: no memory for gluing queue %p\n",
+ qp));
+ goto out_fail;
+out_oversize:
+ if (net_ratelimit())
+ printk(KERN_INFO
+ "Oversized IP packet from %d.%d.%d.%d.\n",
+ NIPQUAD(qp->saddr));
+out_fail:
+ IP_INC_STATS_BH(IpReasmFails);
+ return NULL;
+}
+
+/* Process an incoming IP datagram fragment. */
+struct sk_buff *ip_defrag(struct sk_buff *skb, u32 user)
+{
+ struct iphdr *iph = skb->nh.iph;
+ struct ipq *qp;
+ struct net_device *dev;
+
+ IP_INC_STATS_BH(IpReasmReqds);
+
+ /* Start by cleaning up the memory. */
+ if (atomic_read(&ip_frag_mem) > sysctl_ipfrag_high_thresh)
+ ip_evictor();
+
+ dev = skb->dev;
+
+ /* Lookup (or create) queue header */
+ if ((qp = ip_find(iph, user)) != NULL) {
+ struct sk_buff *ret = NULL;
+
+ spin_lock(&qp->lock);
+
+ ip_frag_queue(qp, skb);
+
+ if (qp->last_in == (FIRST_IN|LAST_IN) &&
+ qp->meat == qp->len)
+ ret = ip_frag_reasm(qp, dev);
+
+ spin_unlock(&qp->lock);
+ ipq_put(qp, NULL);
+ return ret;
+ }
+
+ IP_INC_STATS_BH(IpReasmFails);
+ kfree_skb(skb);
+ return NULL;
+}
+
+void ipfrag_init(void)
+{
+ ipfrag_hash_rnd = (u32) ((num_physpages ^ (num_physpages>>7)) ^
+ (jiffies ^ (jiffies >> 6)));
+
+ init_timer(&ipfrag_secret_timer);
+ ipfrag_secret_timer.function = ipfrag_secret_rebuild;
+ ipfrag_secret_timer.expires = jiffies + sysctl_ipfrag_secret_interval;
+ add_timer(&ipfrag_secret_timer);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ip_gre.c b/uClinux-2.4.31-uc0/net/ipv4/ip_gre.c
new file mode 100644
index 0000000..071ed12
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ip_gre.c
@@ -0,0 +1,1431 @@
+/* $USAGI: ip_gre.c,v 1.17 2003/11/12 05:12:00 yoshfuji Exp $ */
+
+/*
+ * Linux NET3: GRE over IP protocol decoder.
+ *
+ * Authors: Alexey Kuznetsov (kuznet@ms2.inr.ac.ru)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <asm/uaccess.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/if_arp.h>
+#include <linux/mroute.h>
+#include <linux/init.h>
+#include <linux/in6.h>
+#include <linux/inetdevice.h>
+#include <linux/igmp.h>
+#include <linux/netfilter_ipv4.h>
+
+#include <net/sock.h>
+#include <net/ip.h>
+#include <net/icmp.h>
+#include <net/protocol.h>
+#include <net/ipip.h>
+#include <net/arp.h>
+#include <net/checksum.h>
+#include <net/inet_ecn.h>
+
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+#include <net/ipv6.h>
+#include <net/ip6_fib.h>
+#include <net/ip6_route.h>
+#endif
+
+/*
+ Problems & solutions
+ --------------------
+
+ 1. The most important issue is detecting local dead loops.
+ They would cause complete host lockup in transmit, which
+ would be "resolved" by stack overflow or, if queueing is enabled,
+ with infinite looping in net_bh.
+
+ We cannot track such dead loops during route installation,
+ it is infeasible task. The most general solutions would be
+ to keep skb->encapsulation counter (sort of local ttl),
+ and silently drop packet when it expires. It is the best
+ solution, but it supposes maintaing new variable in ALL
+ skb, even if no tunneling is used.
+
+ Current solution: t->recursion lock breaks dead loops. It looks
+ like dev->tbusy flag, but I preferred new variable, because
+ the semantics is different. One day, when hard_start_xmit
+ will be multithreaded we will have to use skb->encapsulation.
+
+
+
+ 2. Networking dead loops would not kill routers, but would really
+ kill network. IP hop limit plays role of "t->recursion" in this case,
+ if we copy it from packet being encapsulated to upper header.
+ It is very good solution, but it introduces two problems:
+
+ - Routing protocols, using packets with ttl=1 (OSPF, RIP2),
+ do not work over tunnels.
+ - traceroute does not work. I planned to relay ICMP from tunnel,
+ so that this problem would be solved and traceroute output
+ would even more informative. This idea appeared to be wrong:
+ only Linux complies to rfc1812 now (yes, guys, Linux is the only
+ true router now :-)), all routers (at least, in neighbourhood of mine)
+ return only 8 bytes of payload. It is the end.
+
+ Hence, if we want that OSPF worked or traceroute said something reasonable,
+ we should search for another solution.
+
+ One of them is to parse packet trying to detect inner encapsulation
+ made by our node. It is difficult or even impossible, especially,
+ taking into account fragmentation. TO be short, tt is not solution at all.
+
+ Current solution: The solution was UNEXPECTEDLY SIMPLE.
+ We force DF flag on tunnels with preconfigured hop limit,
+ that is ALL. :-) Well, it does not remove the problem completely,
+ but exponential growth of network traffic is changed to linear
+ (branches, that exceed pmtu are pruned) and tunnel mtu
+ fastly degrades to value <68, where looping stops.
+ Yes, it is not good if there exists a router in the loop,
+ which does not force DF, even when encapsulating packets have DF set.
+ But it is not our problem! Nobody could accuse us, we made
+ all that we could make. Even if it is your gated who injected
+ fatal route to network, even if it were you who configured
+ fatal static route: you are innocent. :-)
+
+
+
+ 3. Really, ipv4/ipip.c, ipv4/ip_gre.c and ipv6/sit.c contain
+ practically identical code. It would be good to glue them
+ together, but it is not very evident, how to make them modular.
+ sit is integral part of IPv6, ipip and gre are naturally modular.
+ We could extract common parts (hash table, ioctl etc)
+ to a separate module (ip_tunnel.c).
+
+ Alexey Kuznetsov.
+ */
+
+static int ipgre_tunnel_init(struct net_device *dev);
+
+/* Fallback tunnel: no source, no destination, no key, no options */
+
+static int ipgre_fb_tunnel_init(struct net_device *dev);
+
+
+/*
+ * we need a special function to be able to be able to pull the ethernet
+ * buffer out. It is nearly the same as the eth_type_trans structure except
+ * the header size is adjusted.
+ */
+unsigned short gre_eth_type_trans(struct sk_buff *skb, struct net_device *dev);
+
+static struct net_device ipgre_fb_tunnel_dev = {
+ "gre0", 0x0, 0x0, 0x0, 0x0, 0, 0, 0, 0, 0, NULL, ipgre_fb_tunnel_init,
+};
+
+static struct ip_tunnel ipgre_fb_tunnel = {
+ NULL, &ipgre_fb_tunnel_dev, {0, }, 0, 0, 0, 0, 0, 0, 0, {"gre0", }
+};
+
+/* Tunnel hash table */
+
+/*
+ 4 hash tables:
+
+ 3: (remote,local)
+ 2: (remote,*)
+ 1: (*,local)
+ 0: (*,*)
+
+ We require exact key match i.e. if a key is present in packet
+ it will match only tunnel with the same key; if it is not present,
+ it will match only keyless tunnel.
+
+ All keysless packets, if not matched configured keyless tunnels
+ will match fallback tunnel.
+ */
+
+#define HASH_SIZE 16
+#define HASH(addr) ((addr^(addr>>4))&0xF)
+
+static struct ip_tunnel *tunnels[4][HASH_SIZE];
+
+#define tunnels_r_l (tunnels[3])
+#define tunnels_r (tunnels[2])
+#define tunnels_l (tunnels[1])
+#define tunnels_wc (tunnels[0])
+
+static rwlock_t ipgre_lock = RW_LOCK_UNLOCKED;
+
+/* Given src, dst and key, find approriate for input tunnel. */
+
+static struct ip_tunnel * ipgre_tunnel_lookup(u32 remote, u32 local, u32 key)
+{
+ unsigned h0 = HASH(remote);
+ unsigned h1 = HASH(key);
+ struct ip_tunnel *t;
+
+ for (t = tunnels_r_l[h0^h1]; t; t = t->next) {
+ if (local == t->parms.iph.saddr && remote == t->parms.iph.daddr) {
+ if (t->parms.i_key == key && (t->dev->flags&IFF_UP))
+ return t;
+ }
+ }
+ for (t = tunnels_r[h0^h1]; t; t = t->next) {
+ if (remote == t->parms.iph.daddr) {
+ if (t->parms.i_key == key && (t->dev->flags&IFF_UP))
+ return t;
+ }
+ }
+ for (t = tunnels_l[h1]; t; t = t->next) {
+ if (local == t->parms.iph.saddr ||
+ (local == t->parms.iph.daddr && MULTICAST(local))) {
+ if (t->parms.i_key == key && (t->dev->flags&IFF_UP))
+ return t;
+ }
+ }
+ for (t = tunnels_wc[h1]; t; t = t->next) {
+ if (t->parms.i_key == key && (t->dev->flags&IFF_UP))
+ return t;
+ }
+ if (ipgre_fb_tunnel_dev.flags&IFF_UP)
+ return &ipgre_fb_tunnel;
+ return NULL;
+}
+
+static __inline__ struct ip_tunnel **__ipgre_bucket(struct ip_tunnel_parm *parms)
+{
+ u32 remote = parms->iph.daddr;
+ u32 local = parms->iph.saddr;
+ u32 key = parms->i_key;
+ unsigned h = HASH(key);
+ int prio = 0;
+
+ if (local)
+ prio |= 1;
+ if (remote && !MULTICAST(remote)) {
+ prio |= 2;
+ h ^= HASH(remote);
+ }
+
+ return &tunnels[prio][h];
+}
+
+static struct ip_tunnel **ipgre_bucket(struct ip_tunnel *t)
+{
+ return __ipgre_bucket(&t->parms);
+}
+
+static void ipgre_tunnel_link(struct ip_tunnel *t)
+{
+ struct ip_tunnel **tp = ipgre_bucket(t);
+
+ t->next = *tp;
+ write_lock_bh(&ipgre_lock);
+ *tp = t;
+ write_unlock_bh(&ipgre_lock);
+}
+
+static void ipgre_tunnel_unlink(struct ip_tunnel *t)
+{
+ struct ip_tunnel **tp;
+
+ for (tp = ipgre_bucket(t); *tp; tp = &(*tp)->next) {
+ if (t == *tp) {
+ write_lock_bh(&ipgre_lock);
+ *tp = t->next;
+ write_unlock_bh(&ipgre_lock);
+ break;
+ }
+ }
+}
+
+static struct ip_tunnel * ipgre_tunnel_locate(struct ip_tunnel_parm *parms, int create)
+{
+ u32 remote = parms->iph.daddr;
+ u32 local = parms->iph.saddr;
+ u32 key = parms->i_key;
+ struct ip_tunnel *t, **tp, *nt;
+ struct net_device *dev;
+
+ for (tp = __ipgre_bucket(parms); (t = *tp) != NULL; tp = &t->next) {
+ if (local == t->parms.iph.saddr && remote == t->parms.iph.daddr) {
+ if (key == t->parms.i_key)
+ return t;
+ }
+ }
+ if (!create)
+ return NULL;
+
+ MOD_INC_USE_COUNT;
+ dev = kmalloc(sizeof(*dev) + sizeof(*t), GFP_KERNEL);
+ if (dev == NULL) {
+ MOD_DEC_USE_COUNT;
+ return NULL;
+ }
+ memset(dev, 0, sizeof(*dev) + sizeof(*t));
+ dev->priv = (void*)(dev+1);
+ nt = (struct ip_tunnel*)dev->priv;
+ nt->dev = dev;
+ dev->init = ipgre_tunnel_init;
+ dev->features |= NETIF_F_DYNALLOC;
+ memcpy(&nt->parms, parms, sizeof(*parms));
+ nt->parms.name[IFNAMSIZ-1] = '\0';
+ strcpy(dev->name, nt->parms.name);
+ if (dev->name[0] == 0) {
+ int i;
+ for (i=1; i<100; i++) {
+ sprintf(dev->name, "gre%d", i);
+ if (__dev_get_by_name(dev->name) == NULL)
+ break;
+ }
+ if (i==100)
+ goto failed;
+ memcpy(nt->parms.name, dev->name, IFNAMSIZ);
+ }
+ if (register_netdevice(dev) < 0)
+ goto failed;
+
+ dev_hold(dev);
+ ipgre_tunnel_link(nt);
+ /* Do not decrement MOD_USE_COUNT here. */
+ return nt;
+
+failed:
+ kfree(dev);
+ MOD_DEC_USE_COUNT;
+ return NULL;
+}
+
+static void ipgre_tunnel_destructor(struct net_device *dev)
+{
+ if (dev != &ipgre_fb_tunnel_dev) {
+ MOD_DEC_USE_COUNT;
+ }
+}
+
+static void ipgre_tunnel_uninit(struct net_device *dev)
+{
+ ipgre_tunnel_unlink((struct ip_tunnel*)dev->priv);
+ dev_put(dev);
+}
+
+
+void ipgre_err(struct sk_buff *skb, u32 info)
+{
+#ifndef I_WISH_WORLD_WERE_PERFECT
+
+/* It is not :-( All the routers (except for Linux) return only
+ 8 bytes of packet payload. It means, that precise relaying of
+ ICMP in the real Internet is absolutely infeasible.
+
+ Moreover, Cisco "wise men" put GRE key to the third word
+ in GRE header. It makes impossible maintaining even soft state for keyed
+ GRE tunnels with enabled checksum. Tell them "thank you".
+
+ Well, I wonder, rfc1812 was written by Cisco employee,
+ what the hell these idiots break standrads established
+ by themself???
+ */
+
+ struct iphdr *iph = (struct iphdr*)skb->data;
+ u16 *p = (u16*)(skb->data+(iph->ihl<<2));
+ int grehlen = (iph->ihl<<2) + 4;
+ int type = skb->h.icmph->type;
+ int code = skb->h.icmph->code;
+ struct ip_tunnel *t;
+ u16 flags;
+
+ flags = p[0];
+ if (flags&(GRE_CSUM|GRE_KEY|GRE_SEQ|GRE_ROUTING|GRE_VERSION)) {
+ if (flags&(GRE_VERSION|GRE_ROUTING))
+ return;
+ if (flags&GRE_KEY) {
+ grehlen += 4;
+ if (flags&GRE_CSUM)
+ grehlen += 4;
+ }
+ }
+
+ /* If only 8 bytes returned, keyed message will be dropped here */
+ if (skb_headlen(skb) < grehlen)
+ return;
+
+ switch (type) {
+ default:
+ case ICMP_PARAMETERPROB:
+ return;
+
+ case ICMP_DEST_UNREACH:
+ switch (code) {
+ case ICMP_SR_FAILED:
+ case ICMP_PORT_UNREACH:
+ /* Impossible event. */
+ return;
+ case ICMP_FRAG_NEEDED:
+ /* Soft state for pmtu is maintained by IP core. */
+ return;
+ default:
+ /* All others are translated to HOST_UNREACH.
+ rfc2003 contains "deep thoughts" about NET_UNREACH,
+ I believe they are just ether pollution. --ANK
+ */
+ break;
+ }
+ break;
+ case ICMP_TIME_EXCEEDED:
+ if (code != ICMP_EXC_TTL)
+ return;
+ break;
+ }
+
+ read_lock(&ipgre_lock);
+ t = ipgre_tunnel_lookup(iph->daddr, iph->saddr, (flags&GRE_KEY) ? *(((u32*)p) + (grehlen>>2) - 1) : 0);
+ if (t == NULL || t->parms.iph.daddr == 0 || MULTICAST(t->parms.iph.daddr))
+ goto out;
+
+ if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
+ goto out;
+
+ if (jiffies - t->err_time < IPTUNNEL_ERR_TIMEO)
+ t->err_count++;
+ else
+ t->err_count = 1;
+ t->err_time = jiffies;
+out:
+ read_unlock(&ipgre_lock);
+ return;
+#else
+ struct iphdr *iph = (struct iphdr*)dp;
+ struct iphdr *eiph;
+ u16 *p = (u16*)(dp+(iph->ihl<<2));
+ int type = skb->h.icmph->type;
+ int code = skb->h.icmph->code;
+ int rel_type = 0;
+ int rel_code = 0;
+ int rel_info = 0;
+ u16 flags;
+ int grehlen = (iph->ihl<<2) + 4;
+ struct sk_buff *skb2;
+ struct rtable *rt;
+
+ if (p[1] != htons(ETH_P_IP))
+ return;
+
+ flags = p[0];
+ if (flags&(GRE_CSUM|GRE_KEY|GRE_SEQ|GRE_ROUTING|GRE_VERSION)) {
+ if (flags&(GRE_VERSION|GRE_ROUTING))
+ return;
+ if (flags&GRE_CSUM)
+ grehlen += 4;
+ if (flags&GRE_KEY)
+ grehlen += 4;
+ if (flags&GRE_SEQ)
+ grehlen += 4;
+ }
+ if (len < grehlen + sizeof(struct iphdr))
+ return;
+ eiph = (struct iphdr*)(dp + grehlen);
+
+ switch (type) {
+ default:
+ return;
+ case ICMP_PARAMETERPROB:
+ if (skb->h.icmph->un.gateway < (iph->ihl<<2))
+ return;
+
+ /* So... This guy found something strange INSIDE encapsulated
+ packet. Well, he is fool, but what can we do ?
+ */
+ rel_type = ICMP_PARAMETERPROB;
+ rel_info = skb->h.icmph->un.gateway - grehlen;
+ break;
+
+ case ICMP_DEST_UNREACH:
+ switch (code) {
+ case ICMP_SR_FAILED:
+ case ICMP_PORT_UNREACH:
+ /* Impossible event. */
+ return;
+ case ICMP_FRAG_NEEDED:
+ /* And it is the only really necesary thing :-) */
+ rel_info = ntohs(skb->h.icmph->un.frag.mtu);
+ if (rel_info < grehlen+68)
+ return;
+ rel_info -= grehlen;
+ /* BSD 4.2 MORE DOES NOT EXIST IN NATURE. */
+ if (rel_info > ntohs(eiph->tot_len))
+ return;
+ break;
+ default:
+ /* All others are translated to HOST_UNREACH.
+ rfc2003 contains "deep thoughts" about NET_UNREACH,
+ I believe, it is just ether pollution. --ANK
+ */
+ rel_type = ICMP_DEST_UNREACH;
+ rel_code = ICMP_HOST_UNREACH;
+ break;
+ }
+ break;
+ case ICMP_TIME_EXCEEDED:
+ if (code != ICMP_EXC_TTL)
+ return;
+ break;
+ }
+
+ /* Prepare fake skb to feed it to icmp_send */
+ skb2 = skb_clone(skb, GFP_ATOMIC);
+ if (skb2 == NULL)
+ return;
+ dst_release(skb2->dst);
+ skb2->dst = NULL;
+ skb_pull(skb2, skb->data - (u8*)eiph);
+ skb2->nh.raw = skb2->data;
+
+ /* Try to guess incoming interface */
+ if (ip_route_output(&rt, eiph->saddr, 0, RT_TOS(eiph->tos), 0)) {
+ kfree_skb(skb2);
+ return;
+ }
+ skb2->dev = rt->u.dst.dev;
+
+ /* route "incoming" packet */
+ if (rt->rt_flags&RTCF_LOCAL) {
+ ip_rt_put(rt);
+ rt = NULL;
+ if (ip_route_output(&rt, eiph->daddr, eiph->saddr, eiph->tos, 0) ||
+ rt->u.dst.dev->type != ARPHRD_IPGRE) {
+ ip_rt_put(rt);
+ kfree_skb(skb2);
+ return;
+ }
+ } else {
+ ip_rt_put(rt);
+ if (ip_route_input(skb2, eiph->daddr, eiph->saddr, eiph->tos, skb2->dev) ||
+ skb2->dst->dev->type != ARPHRD_IPGRE) {
+ kfree_skb(skb2);
+ return;
+ }
+ }
+
+ /* change mtu on this route */
+ if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) {
+ if (rel_info > skb2->dst->pmtu) {
+ kfree_skb(skb2);
+ return;
+ }
+ skb2->dst->pmtu = rel_info;
+ rel_info = htonl(rel_info);
+ } else if (type == ICMP_TIME_EXCEEDED) {
+ struct ip_tunnel *t = (struct ip_tunnel*)skb2->dev->priv;
+ if (t->parms.iph.ttl) {
+ rel_type = ICMP_DEST_UNREACH;
+ rel_code = ICMP_HOST_UNREACH;
+ }
+ }
+
+ icmp_send(skb2, rel_type, rel_code, rel_info);
+ kfree_skb(skb2);
+#endif
+}
+
+static inline void ipgre_ecn_decapsulate(struct iphdr *iph, struct sk_buff *skb)
+{
+ if (INET_ECN_is_ce(iph->tos)) {
+ if (skb->protocol == htons(ETH_P_IP)) {
+ if (INET_ECN_is_not_ce(skb->nh.iph->tos))
+ IP_ECN_set_ce(skb->nh.iph);
+ } else if (skb->protocol == htons(ETH_P_IPV6)) {
+ if (INET_ECN_is_not_ce(ip6_get_dsfield(skb->nh.ipv6h)))
+ IP6_ECN_set_ce(skb->nh.ipv6h);
+ }
+ }
+}
+
+static inline u8
+ipgre_ecn_encapsulate(u8 tos, struct iphdr *old_iph, struct sk_buff *skb)
+{
+ u8 inner = 0;
+ if (skb->protocol == htons(ETH_P_IP))
+ inner = old_iph->tos;
+ else if (skb->protocol == htons(ETH_P_IPV6))
+ inner = ip6_get_dsfield((struct ipv6hdr*)old_iph);
+ return INET_ECN_encapsulate(tos, inner);
+}
+
+
+void
+br_stp_do_bpdu_lock(struct sk_buff *skb);
+
+int ipgre_rcv(struct sk_buff *skb)
+{
+ struct iphdr *iph;
+ u8 *h;
+ u16 flags;
+ u16 csum = 0;
+ u32 key = 0;
+ u32 seqno = 0;
+ struct ip_tunnel *tunnel;
+ int offset = 4;
+ unsigned short proto;
+
+ if (!pskb_may_pull(skb, 16))
+ goto drop_nolock;
+
+ iph = skb->nh.iph;
+ h = skb->data;
+ flags = *(u16*)h;
+ proto = *(u16*)(h+2);
+
+ if (flags&(GRE_CSUM|GRE_KEY|GRE_ROUTING|GRE_SEQ|GRE_VERSION)) {
+ /* - Version must be 0.
+ - We do not support routing headers.
+ */
+ if (flags&(GRE_VERSION|GRE_ROUTING))
+ goto drop_nolock;
+
+ if (flags&GRE_CSUM) {
+ if (skb->ip_summed == CHECKSUM_HW) {
+ csum = (u16)csum_fold(skb->csum);
+ if (csum)
+ skb->ip_summed = CHECKSUM_NONE;
+ }
+ if (skb->ip_summed == CHECKSUM_NONE) {
+ skb->csum = skb_checksum(skb, 0, skb->len, 0);
+ skb->ip_summed = CHECKSUM_HW;
+ csum = (u16)csum_fold(skb->csum);
+ }
+ offset += 4;
+ }
+ if (flags&GRE_KEY) {
+ key = *(u32*)(h + offset);
+ offset += 4;
+ }
+ if (flags&GRE_SEQ) {
+ seqno = ntohl(*(u32*)(h + offset));
+ offset += 4;
+ }
+ }
+
+ read_lock(&ipgre_lock);
+ if ((tunnel = ipgre_tunnel_lookup(iph->saddr, iph->daddr, key)) != NULL) {
+
+ if (((flags&GRE_CSUM) && csum) ||
+ (!(flags&GRE_CSUM) && tunnel->parms.i_flags&GRE_CSUM)) {
+ tunnel->stat.rx_crc_errors++;
+ tunnel->stat.rx_errors++;
+ goto drop;
+ }
+ if (tunnel->parms.i_flags&GRE_SEQ) {
+ if (!(flags&GRE_SEQ) ||
+ (tunnel->i_seqno && (s32)(seqno - tunnel->i_seqno) < 0)) {
+ tunnel->stat.rx_fifo_errors++;
+ tunnel->stat.rx_errors++;
+ goto drop;
+ }
+ tunnel->i_seqno = seqno + 1;
+ }
+
+#if defined(CONFIG_BRIDGE)
+ if (tunnel->dev->br_port != NULL) {
+ if (proto == GRE_P_ETH_BR) {
+
+ /* Pull off the offset. */
+ skb->mac.raw = __pskb_pull(skb, offset);
+
+ /* ensure it is linear so we can simply copy the data out */
+ skb_linearize(skb, GFP_ATOMIC);
+
+ skb->protocol = gre_eth_type_trans(skb, tunnel->dev);
+ } else if (proto == GRE_P_BPDU) {
+
+ if (!skb_pull(skb, offset)) {
+ goto drop;
+ }
+
+ skb->dev = tunnel->dev;
+ dst_release(skb->dst);
+ skb->dst = NULL;
+#ifdef CONFIG_NETFILTER
+ nf_conntrack_put(skb->nfct);
+ skb->nfct = NULL;
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug = 0;
+#endif
+#endif
+
+ br_stp_do_bpdu_lock(skb);
+ tunnel->stat.rx_packets++;
+ tunnel->stat.rx_bytes += skb->len;
+ goto drop;
+
+ } else {
+ tunnel->stat.rx_errors++;
+ goto drop;
+ }
+ } else
+#endif
+ {
+ skb->mac.raw = skb->nh.raw;
+ skb->nh.raw = __pskb_pull(skb, offset);
+ memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
+ if (skb->ip_summed == CHECKSUM_HW)
+ skb->csum = csum_sub(skb->csum,
+ csum_partial(skb->mac.raw, skb->nh.raw-skb->mac.raw, 0));
+ skb->protocol = *(u16*)(h + 2);
+ skb->pkt_type = PACKET_HOST;
+#ifdef CONFIG_NET_IPGRE_BROADCAST
+ if (MULTICAST(iph->daddr)) {
+ /* Looped back packet, drop it! */
+ if (((struct rtable*)skb->dst)->key.iif == 0)
+ goto drop;
+ tunnel->stat.multicast++;
+ skb->pkt_type = PACKET_BROADCAST;
+ }
+#endif
+ }
+ tunnel->stat.rx_packets++;
+ tunnel->stat.rx_bytes += skb->len;
+ skb->dev = tunnel->dev;
+ dst_release(skb->dst);
+ skb->dst = NULL;
+ nf_reset(skb);
+ ipgre_ecn_decapsulate(iph, skb);
+ netif_rx(skb);
+ read_unlock(&ipgre_lock);
+ return(0);
+ }
+ icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0);
+
+drop:
+ read_unlock(&ipgre_lock);
+drop_nolock:
+ kfree_skb(skb);
+ return(0);
+}
+
+/* Need this wrapper because NF_HOOK takes the function address */
+static inline int do_ip_send(struct sk_buff *skb)
+{
+ return ip_send(skb);
+}
+
+static int ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct ip_tunnel *tunnel = (struct ip_tunnel*)dev->priv;
+ struct net_device_stats *stats = &tunnel->stat;
+ struct iphdr *old_iph = skb->nh.iph;
+ struct iphdr *tiph;
+ u8 tos;
+ u16 df;
+ struct rtable *rt; /* Route to the other host */
+ struct net_device *tdev; /* Device to other host */
+ struct iphdr *iph; /* Our new IP header */
+ int max_headroom; /* The extra header space needed */
+ int gre_hlen;
+ u32 dst;
+ int mtu;
+
+// printk(KERN_INFO "xmit - %d\n", skb->len);
+
+ if (tunnel->recursion++) {
+ tunnel->stat.collisions++;
+ goto tx_error;
+ }
+
+ if (dev->hard_header) {
+ gre_hlen = 0;
+ tiph = (struct iphdr*)skb->data;
+ } else {
+ gre_hlen = tunnel->hlen;
+ tiph = &tunnel->parms.iph;
+ }
+
+ if ((dst = tiph->daddr) == 0) {
+ /* NBMA tunnel */
+
+ if (skb->dst == NULL) {
+ tunnel->stat.tx_fifo_errors++;
+ goto tx_error;
+ }
+
+ if (skb->protocol == htons(ETH_P_IP)) {
+ rt = (struct rtable*)skb->dst;
+ if ((dst = rt->rt_gateway) == 0)
+ goto tx_error_icmp;
+ }
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ else if (skb->protocol == htons(ETH_P_IPV6)) {
+ struct in6_addr *addr6;
+ struct neighbour *neigh = skb->dst->neighbour;
+
+ if (neigh == NULL)
+ goto tx_error;
+
+ addr6 = (struct in6_addr*)&neigh->primary_key;
+ if (IN6_IS_ADDR_UNSPECIFIED(addr6))
+ addr6 = &skb->nh.ipv6h->daddr;
+ if (!IN6_IS_ADDR_V4COMPAT(addr6))
+ goto tx_error_icmp;
+
+ dst = addr6->s6_addr32[3];
+ }
+#endif
+ else
+ goto tx_error;
+ }
+
+ tos = tiph->tos;
+ if (tos&1) {
+ if (skb->protocol == htons(ETH_P_IP))
+ tos = old_iph->tos;
+ tos &= ~1;
+ }
+
+ if (ip_route_output(&rt, dst, tiph->saddr, RT_TOS(tos), tunnel->parms.link)) {
+ tunnel->stat.tx_carrier_errors++;
+ goto tx_error;
+ }
+ tdev = rt->u.dst.dev;
+
+ if (tdev == dev) {
+ ip_rt_put(rt);
+ tunnel->stat.collisions++;
+ goto tx_error;
+ }
+
+ df = tiph->frag_off;
+ if (df)
+ mtu = rt->u.dst.pmtu - tunnel->hlen;
+ else
+ mtu = skb->dst ? skb->dst->pmtu : dev->mtu;
+
+ /*
+ * If we are not being used as an ethernet bridge, then we want to
+ * honour fragmentation stuff.
+ */
+ if (!dev->br_port) {
+ if (skb->protocol == __constant_htons(ETH_P_IP)) {
+ if (skb->dst && mtu < skb->dst->pmtu && mtu >= 68)
+ skb->dst->pmtu = mtu;
+
+ df |= (old_iph->frag_off&__constant_htons(IP_DF));
+
+ if ((old_iph->frag_off&__constant_htons(IP_DF)) &&
+ mtu < ntohs(old_iph->tot_len)) {
+ icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
+ ip_rt_put(rt);
+ goto tx_error;
+ }
+ }
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ else if (skb->protocol == __constant_htons(ETH_P_IPV6)) {
+ struct rt6_info *rt6 = (struct rt6_info*)skb->dst;
+#if defined(CONFIG_IPV6_MODULE_IP_GRE) && !defined(CONFIG_IPV6)
+ void (*icmpv6_send)(struct sk_buff *skb,
+ int type, int code,
+ __u32 info,
+ struct net_device *dev) = inter_module_get(IM_ICMPV6_SEND);
+ if (!icmpv6_send)
+ goto skip_ipv6;
+#endif
+ if (rt6 && mtu < rt6->u.dst.pmtu && mtu >= IPV6_MIN_MTU) {
+ if ((tunnel->parms.iph.daddr && !MULTICAST(tunnel->parms.iph.daddr)) ||
+ rt6->rt6i_dst.plen == 128) {
+ rt6->rt6i_flags |= RTF_MODIFIED;
+ skb->dst->pmtu = mtu;
+ }
+ }
+
+ if (mtu >= IPV6_MIN_MTU && mtu < skb->len - tunnel->hlen + gre_hlen) {
+#ifdef CONFIG_IPV6
+ icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, dev);
+#elif defined(CONFIG_IPV6_MODULE_IP_GRE)
+ (*icmpv6_send)(skb, ICMPV6_PKT_TOOBIG, 0, mtu, dev);
+ inter_module_put(IM_ICMPV6_SEND);
+#endif
+ ip_rt_put(rt);
+ goto tx_error;
+ }
+#if defined(CONFIG_IPV6_MODULE_IP_GRE) && !defined(CONFIG_IPV6)
+ inter_module_put(IM_ICMPV6_SEND);
+skip_ipv6:
+#endif
+ }
+#endif
+ }
+
+ if (tunnel->err_count > 0) {
+ if (jiffies - tunnel->err_time < IPTUNNEL_ERR_TIMEO) {
+ tunnel->err_count--;
+
+ dst_link_failure(skb);
+ } else
+ tunnel->err_count = 0;
+ }
+
+ /*
+ * This is only the case for ethernet frames!!
+ */
+ skb->h.raw = skb->mac.raw;
+
+ max_headroom = ((tdev->hard_header_len+15)&~15)+ gre_hlen;
+
+ if (skb_headroom(skb) < max_headroom || skb_cloned(skb) || skb_shared(skb)) {
+ struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);
+ if (!new_skb) {
+ ip_rt_put(rt);
+ stats->tx_dropped++;
+ dev_kfree_skb(skb);
+ tunnel->recursion--;
+ return 0;
+ }
+ if (skb->sk)
+ skb_set_owner_w(new_skb, skb->sk);
+ dev_kfree_skb(skb);
+ skb = new_skb;
+ old_iph = skb->nh.iph;
+ }
+
+ skb->h.raw = skb->nh.raw;
+ skb->nh.raw = skb_push(skb, gre_hlen);
+ memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
+ dst_release(skb->dst);
+ skb->dst = &rt->u.dst;
+// printk("%d-%s", skb->len, rt->u.dst.dev->name);
+
+ /*
+ * Push down and install the IPIP header.
+ */
+
+ iph = skb->nh.iph;
+ iph->version = 4;
+ iph->ihl = sizeof(struct iphdr) >> 2;
+ iph->frag_off = 0;//__constant_htons(IP_DF);
+ iph->protocol = IPPROTO_GRE;
+ iph->tos = ipgre_ecn_encapsulate(tos, old_iph, skb);
+ iph->daddr = rt->rt_dst;
+ iph->saddr = rt->rt_src;
+
+ if ((iph->ttl = tiph->ttl) == 0) {
+ if (skb->protocol == htons(ETH_P_IP))
+ if (skb->nf_bridge != NULL)
+ iph->ttl = sysctl_ip_default_ttl;
+ else
+ iph->ttl = old_iph->ttl;
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ else if (skb->protocol == htons(ETH_P_IPV6))
+ iph->ttl = ((struct ipv6hdr*)old_iph)->hop_limit;
+#endif
+ else
+ iph->ttl = sysctl_ip_default_ttl;
+ }
+
+ ((u16*)(iph+1))[0] = tunnel->parms.o_flags;
+
+ if (dev->br_port)
+ ((u16*)(iph+1))[1] = GRE_P_ETH_BR;
+ else
+ ((u16*)(iph+1))[1] = skb->protocol;
+
+ if (tunnel->parms.o_flags&(GRE_KEY|GRE_CSUM|GRE_SEQ)) {
+ u32 *ptr = (u32*)(((u8*)iph) + tunnel->hlen - 4);
+
+ if (tunnel->parms.o_flags&GRE_SEQ) {
+ ++tunnel->o_seqno;
+ *ptr = htonl(tunnel->o_seqno);
+ ptr--;
+ }
+ if (tunnel->parms.o_flags&GRE_KEY) {
+ *ptr = tunnel->parms.o_key;
+ ptr--;
+ }
+ if (tunnel->parms.o_flags&GRE_CSUM) {
+ *ptr = 0;
+ *(__u16*)ptr = ip_compute_csum((void*)(iph+1), skb->len - sizeof(struct iphdr));
+ }
+ }
+
+ nf_reset(skb);
+
+ IPTUNNEL_XMIT();
+ tunnel->recursion--;
+ return 0;
+
+tx_error_icmp:
+ dst_link_failure(skb);
+
+tx_error:
+ stats->tx_errors++;
+ dev_kfree_skb(skb);
+ tunnel->recursion--;
+ return 0;
+}
+
+static int
+ipgre_tunnel_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ int err = 0;
+ struct ip_tunnel_parm p;
+ struct ip_tunnel *t;
+
+ MOD_INC_USE_COUNT;
+
+ switch (cmd) {
+ case SIOCGETTUNNEL:
+ t = NULL;
+ if (dev == &ipgre_fb_tunnel_dev) {
+ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p))) {
+ err = -EFAULT;
+ break;
+ }
+ t = ipgre_tunnel_locate(&p, 0);
+ }
+ if (t == NULL)
+ t = (struct ip_tunnel*)dev->priv;
+ memcpy(&p, &t->parms, sizeof(p));
+ if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p)))
+ err = -EFAULT;
+ break;
+
+ case SIOCADDTUNNEL:
+ case SIOCCHGTUNNEL:
+ err = -EPERM;
+ if (!capable(CAP_NET_ADMIN))
+ goto done;
+
+ err = -EFAULT;
+ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
+ goto done;
+
+ err = -EINVAL;
+ if (p.iph.version != 4 || p.iph.protocol != IPPROTO_GRE ||
+ p.iph.ihl != 5 || (p.iph.frag_off&htons(~IP_DF)) ||
+ ((p.i_flags|p.o_flags)&(GRE_VERSION|GRE_ROUTING)))
+ goto done;
+ if (p.iph.ttl)
+ p.iph.frag_off |= htons(IP_DF);
+
+ if (!(p.i_flags&GRE_KEY))
+ p.i_key = 0;
+ if (!(p.o_flags&GRE_KEY))
+ p.o_key = 0;
+
+ t = ipgre_tunnel_locate(&p, cmd == SIOCADDTUNNEL);
+
+ if (dev != &ipgre_fb_tunnel_dev && cmd == SIOCCHGTUNNEL &&
+ t != &ipgre_fb_tunnel) {
+ if (t != NULL) {
+ if (t->dev != dev) {
+ err = -EEXIST;
+ break;
+ }
+ } else {
+ unsigned nflags=0;
+
+ t = (struct ip_tunnel*)dev->priv;
+
+ if (MULTICAST(p.iph.daddr))
+ nflags = IFF_BROADCAST;
+ else if (p.iph.daddr)
+ nflags = IFF_POINTOPOINT;
+
+ if ((dev->flags^nflags)&(IFF_POINTOPOINT|IFF_BROADCAST)) {
+ err = -EINVAL;
+ break;
+ }
+ ipgre_tunnel_unlink(t);
+ t->parms.iph.saddr = p.iph.saddr;
+ t->parms.iph.daddr = p.iph.daddr;
+ t->parms.i_key = p.i_key;
+ t->parms.o_key = p.o_key;
+ memcpy(dev->dev_addr, &p.iph.saddr, 4);
+ memcpy(dev->broadcast, &p.iph.daddr, 4);
+ ipgre_tunnel_link(t);
+ netdev_state_change(dev);
+ }
+ }
+
+ if (t) {
+ err = 0;
+ if (cmd == SIOCCHGTUNNEL) {
+ t->parms.iph.ttl = p.iph.ttl;
+ t->parms.iph.tos = p.iph.tos;
+ t->parms.iph.frag_off = p.iph.frag_off;
+ }
+ if (copy_to_user(ifr->ifr_ifru.ifru_data, &t->parms, sizeof(p)))
+ err = -EFAULT;
+ } else
+ err = (cmd == SIOCADDTUNNEL ? -ENOBUFS : -ENOENT);
+ break;
+
+ case SIOCDELTUNNEL:
+ err = -EPERM;
+ if (!capable(CAP_NET_ADMIN))
+ goto done;
+
+ if (dev == &ipgre_fb_tunnel_dev) {
+ err = -EFAULT;
+ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
+ goto done;
+ err = -ENOENT;
+ if ((t = ipgre_tunnel_locate(&p, 0)) == NULL)
+ goto done;
+ err = -EPERM;
+ if (t == &ipgre_fb_tunnel)
+ goto done;
+ dev = t->dev;
+ }
+ err = unregister_netdevice(dev);
+ break;
+
+ default:
+ err = -EINVAL;
+ }
+
+done:
+ MOD_DEC_USE_COUNT;
+ return err;
+}
+
+static struct net_device_stats *ipgre_tunnel_get_stats(struct net_device *dev)
+{
+ return &(((struct ip_tunnel*)dev->priv)->stat);
+}
+
+static int ipgre_tunnel_change_mtu(struct net_device *dev, int new_mtu)
+{
+ struct ip_tunnel *tunnel = (struct ip_tunnel*)dev->priv;
+ if (new_mtu < 68 || new_mtu > 0xFFF8 - tunnel->hlen)
+ return -EINVAL;
+ dev->mtu = new_mtu;
+ return 0;
+}
+
+#ifdef CONFIG_NET_IPGRE_BROADCAST
+/* Nice toy. Unfortunately, useless in real life :-)
+ It allows to construct virtual multiprotocol broadcast "LAN"
+ over the Internet, provided multicast routing is tuned.
+
+
+ I have no idea was this bicycle invented before me,
+ so that I had to set ARPHRD_IPGRE to a random value.
+ I have an impression, that Cisco could make something similar,
+ but this feature is apparently missing in IOS<=11.2(8).
+
+ I set up 10.66.66/24 and fec0:6666:6666::0/96 as virtual networks
+ with broadcast 224.66.66.66. If you have access to mbone, play with me :-)
+
+ ping -t 255 224.66.66.66
+
+ If nobody answers, mbone does not work.
+
+ ip tunnel add Universe mode gre remote 224.66.66.66 local <Your_real_addr> ttl 255
+ ip addr add 10.66.66.<somewhat>/24 dev Universe
+ ifconfig Universe up
+ ifconfig Universe add fe80::<Your_real_addr>/10
+ ifconfig Universe add fec0:6666:6666::<Your_real_addr>/96
+ ftp 10.66.66.66
+ ...
+ ftp fec0:6666:6666::193.233.7.65
+ ...
+
+ */
+
+static int ipgre_header(struct sk_buff *skb, struct net_device *dev, unsigned short type,
+ void *daddr, void *saddr, unsigned len)
+{
+ struct ip_tunnel *t = (struct ip_tunnel*)dev->priv;
+ struct iphdr *iph = (struct iphdr *)skb_push(skb, t->hlen);
+ u16 *p = (u16*)(iph+1);
+
+ memcpy(iph, &t->parms.iph, sizeof(struct iphdr));
+ p[0] = t->parms.o_flags;
+ p[1] = htons(type);
+
+ /*
+ * Set the source hardware address.
+ */
+
+ if (saddr)
+ memcpy(&iph->saddr, saddr, 4);
+
+ if (daddr) {
+ memcpy(&iph->daddr, daddr, 4);
+ return t->hlen;
+ }
+ if (iph->daddr && !MULTICAST(iph->daddr))
+ return t->hlen;
+
+ return -t->hlen;
+}
+
+static int ipgre_open(struct net_device *dev)
+{
+ struct ip_tunnel *t = (struct ip_tunnel*)dev->priv;
+
+ MOD_INC_USE_COUNT;
+ if (MULTICAST(t->parms.iph.daddr)) {
+ struct rtable *rt;
+ if (ip_route_output(&rt, t->parms.iph.daddr,
+ t->parms.iph.saddr, RT_TOS(t->parms.iph.tos),
+ t->parms.link)) {
+ MOD_DEC_USE_COUNT;
+ return -EADDRNOTAVAIL;
+ }
+ dev = rt->u.dst.dev;
+ ip_rt_put(rt);
+ if (__in_dev_get(dev) == NULL) {
+ MOD_DEC_USE_COUNT;
+ return -EADDRNOTAVAIL;
+ }
+ t->mlink = dev->ifindex;
+ ip_mc_inc_group(__in_dev_get(dev), t->parms.iph.daddr);
+ }
+ return 0;
+}
+
+static int ipgre_close(struct net_device *dev)
+{
+ struct ip_tunnel *t = (struct ip_tunnel*)dev->priv;
+ if (MULTICAST(t->parms.iph.daddr) && t->mlink) {
+ struct in_device *in_dev = inetdev_by_index(t->mlink);
+ if (in_dev) {
+ ip_mc_dec_group(in_dev, t->parms.iph.daddr);
+ in_dev_put(in_dev);
+ }
+ }
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+#endif
+
+static void ipgre_tunnel_init_gen(struct net_device *dev)
+{
+ struct ip_tunnel *t = (struct ip_tunnel*)dev->priv;
+
+ dev->uninit = ipgre_tunnel_uninit;
+ dev->destructor = ipgre_tunnel_destructor;
+ dev->hard_start_xmit = ipgre_tunnel_xmit;
+ dev->get_stats = ipgre_tunnel_get_stats;
+ dev->do_ioctl = ipgre_tunnel_ioctl;
+ dev->change_mtu = ipgre_tunnel_change_mtu;
+
+ dev->type = ARPHRD_IPGRE;
+ dev->hard_header_len = LL_MAX_HEADER + sizeof(struct iphdr) + 4;
+ dev->mtu = 1500 - sizeof(struct iphdr) - 4;
+ dev->flags = IFF_NOARP;
+ dev->iflink = 0;
+ dev->addr_len = 4;
+ memcpy(dev->dev_addr, &t->parms.iph.saddr, 4);
+ memcpy(dev->broadcast, &t->parms.iph.daddr, 4);
+}
+
+static int ipgre_tunnel_init(struct net_device *dev)
+{
+ struct net_device *tdev = NULL;
+ struct ip_tunnel *tunnel;
+ struct iphdr *iph;
+ int hlen = LL_MAX_HEADER;
+ int mtu = 1500;
+ int addend = sizeof(struct iphdr) + 4;
+
+ tunnel = (struct ip_tunnel*)dev->priv;
+ iph = &tunnel->parms.iph;
+
+ ipgre_tunnel_init_gen(dev);
+
+ /* Guess output device to choose reasonable mtu and hard_header_len */
+
+ if (iph->daddr) {
+ struct rtable *rt;
+ if (!ip_route_output(&rt, iph->daddr, iph->saddr, RT_TOS(iph->tos), tunnel->parms.link)) {
+ tdev = rt->u.dst.dev;
+ ip_rt_put(rt);
+ }
+
+ dev->flags |= IFF_POINTOPOINT;
+
+#ifdef CONFIG_NET_IPGRE_BROADCAST
+ if (MULTICAST(iph->daddr)) {
+ if (!iph->saddr)
+ return -EINVAL;
+ dev->flags = IFF_BROADCAST;
+ dev->hard_header = ipgre_header;
+ dev->open = ipgre_open;
+ dev->stop = ipgre_close;
+ }
+#endif
+ }
+
+ if (!tdev && tunnel->parms.link)
+ tdev = __dev_get_by_index(tunnel->parms.link);
+
+ if (tdev) {
+ hlen = tdev->hard_header_len;
+ mtu = tdev->mtu;
+ }
+ dev->iflink = tunnel->parms.link;
+
+ /* Precalculate GRE options length */
+ if (tunnel->parms.o_flags&(GRE_CSUM|GRE_KEY|GRE_SEQ)) {
+ if (tunnel->parms.o_flags&GRE_CSUM)
+ addend += 4;
+ if (tunnel->parms.o_flags&GRE_KEY)
+ addend += 4;
+ if (tunnel->parms.o_flags&GRE_SEQ)
+ addend += 4;
+ }
+ dev->hard_header_len = hlen + addend;
+ dev->mtu = mtu - addend;
+ tunnel->hlen = addend;
+ return 0;
+}
+
+#ifdef MODULE
+static int ipgre_fb_tunnel_open(struct net_device *dev)
+{
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int ipgre_fb_tunnel_close(struct net_device *dev)
+{
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+#endif
+
+int __init ipgre_fb_tunnel_init(struct net_device *dev)
+{
+ struct ip_tunnel *tunnel = (struct ip_tunnel*)dev->priv;
+ struct iphdr *iph;
+
+ ipgre_tunnel_init_gen(dev);
+#ifdef MODULE
+ dev->open = ipgre_fb_tunnel_open;
+ dev->stop = ipgre_fb_tunnel_close;
+#endif
+
+ iph = &ipgre_fb_tunnel.parms.iph;
+ iph->version = 4;
+ iph->protocol = IPPROTO_GRE;
+ iph->ihl = 5;
+ tunnel->hlen = sizeof(struct iphdr) + 4;
+
+ dev_hold(dev);
+ tunnels_wc[0] = &ipgre_fb_tunnel;
+ return 0;
+}
+
+unsigned short gre_eth_type_trans(struct sk_buff *skb, struct net_device *dev)
+{
+ struct ethhdr *eth;
+ unsigned char *rawp;
+
+ skb->mac.raw=skb->data;
+
+ /*
+ * hack - we don't actually want to pull the hard header length, we
+ * want to pull the ethernet frame header.
+ */
+ skb_pull(skb, ETH_HLEN);
+ eth= skb->mac.ethernet;
+
+ if(*eth->h_dest&1)
+ {
+ if(memcmp(eth->h_dest,dev->broadcast, ETH_ALEN)==0)
+ skb->pkt_type=PACKET_BROADCAST;
+ else
+ skb->pkt_type=PACKET_MULTICAST;
+ }
+
+ /*
+ * This ALLMULTI check should be redundant by 1.4
+ * so don't forget to remove it.
+ *
+ * Seems, you forgot to remove it. All silly devices
+ * seems to set IFF_PROMISC.
+ */
+
+ else if(1 /*dev->flags&IFF_PROMISC*/)
+ {
+ if(memcmp(eth->h_dest,dev->dev_addr, ETH_ALEN))
+ skb->pkt_type=PACKET_OTHERHOST;
+ }
+
+ if (ntohs(eth->h_proto) >= 1536)
+ return eth->h_proto;
+
+ rawp = skb->data;
+
+ /*
+ * This is a magic hack to spot IPX packets. Older Novell breaks
+ * the protocol design and runs IPX over 802.3 without an 802.2 LLC
+ * layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This
+ * won't work for fault tolerant netware but does for the rest.
+ */
+ if (*(unsigned short *)rawp == 0xFFFF)
+ return htons(ETH_P_802_3);
+
+ /*
+ * Real 802.2 LLC
+ */
+ return htons(ETH_P_802_2);
+
+}
+
+
+static struct inet_protocol ipgre_protocol = {
+ ipgre_rcv, /* GRE handler */
+ ipgre_err, /* TUNNEL error control */
+ 0, /* next */
+ IPPROTO_GRE, /* protocol ID */
+ 0, /* copy */
+ NULL, /* data */
+ "GRE" /* name */
+};
+
+
+/*
+ * And now the modules code and kernel interface.
+ */
+
+#ifdef MODULE
+int init_module(void)
+#else
+int __init ipgre_init(void)
+#endif
+{
+ printk(KERN_INFO "GRE over IPv4 tunneling driver\n");
+
+ ipgre_fb_tunnel_dev.priv = (void*)&ipgre_fb_tunnel;
+ register_netdev(&ipgre_fb_tunnel_dev);
+ inet_add_protocol(&ipgre_protocol);
+ return 0;
+}
+
+#ifdef MODULE
+
+void cleanup_module(void)
+{
+ if ( inet_del_protocol(&ipgre_protocol) < 0 )
+ printk(KERN_INFO "ipgre close: can't remove protocol\n");
+
+ unregister_netdev(&ipgre_fb_tunnel_dev);
+}
+
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ip_input.c b/uClinux-2.4.31-uc0/net/ipv4/ip_input.c
new file mode 100644
index 0000000..79515e2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ip_input.c
@@ -0,0 +1,444 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * The Internet Protocol (IP) module.
+ *
+ * Version: $Id: ip_input.c,v 1.54.2.1 2002/01/12 07:39:23 davem Exp $
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Donald Becker, <becker@super.org>
+ * Alan Cox, <Alan.Cox@linux.org>
+ * Richard Underwood
+ * Stefan Becker, <stefanb@yello.ping.de>
+ * Jorge Cwik, <jorge@laser.satlink.net>
+ * Arnt Gulbrandsen, <agulbra@nvg.unit.no>
+ *
+ *
+ * Fixes:
+ * Alan Cox : Commented a couple of minor bits of surplus code
+ * Alan Cox : Undefining IP_FORWARD doesn't include the code
+ * (just stops a compiler warning).
+ * Alan Cox : Frames with >=MAX_ROUTE record routes, strict routes or loose routes
+ * are junked rather than corrupting things.
+ * Alan Cox : Frames to bad broadcast subnets are dumped
+ * We used to process them non broadcast and
+ * boy could that cause havoc.
+ * Alan Cox : ip_forward sets the free flag on the
+ * new frame it queues. Still crap because
+ * it copies the frame but at least it
+ * doesn't eat memory too.
+ * Alan Cox : Generic queue code and memory fixes.
+ * Fred Van Kempen : IP fragment support (borrowed from NET2E)
+ * Gerhard Koerting: Forward fragmented frames correctly.
+ * Gerhard Koerting: Fixes to my fix of the above 8-).
+ * Gerhard Koerting: IP interface addressing fix.
+ * Linus Torvalds : More robustness checks
+ * Alan Cox : Even more checks: Still not as robust as it ought to be
+ * Alan Cox : Save IP header pointer for later
+ * Alan Cox : ip option setting
+ * Alan Cox : Use ip_tos/ip_ttl settings
+ * Alan Cox : Fragmentation bogosity removed
+ * (Thanks to Mark.Bush@prg.ox.ac.uk)
+ * Dmitry Gorodchanin : Send of a raw packet crash fix.
+ * Alan Cox : Silly ip bug when an overlength
+ * fragment turns up. Now frees the
+ * queue.
+ * Linus Torvalds/ : Memory leakage on fragmentation
+ * Alan Cox : handling.
+ * Gerhard Koerting: Forwarding uses IP priority hints
+ * Teemu Rantanen : Fragment problems.
+ * Alan Cox : General cleanup, comments and reformat
+ * Alan Cox : SNMP statistics
+ * Alan Cox : BSD address rule semantics. Also see
+ * UDP as there is a nasty checksum issue
+ * if you do things the wrong way.
+ * Alan Cox : Always defrag, moved IP_FORWARD to the config.in file
+ * Alan Cox : IP options adjust sk->priority.
+ * Pedro Roque : Fix mtu/length error in ip_forward.
+ * Alan Cox : Avoid ip_chk_addr when possible.
+ * Richard Underwood : IP multicasting.
+ * Alan Cox : Cleaned up multicast handlers.
+ * Alan Cox : RAW sockets demultiplex in the BSD style.
+ * Gunther Mayer : Fix the SNMP reporting typo
+ * Alan Cox : Always in group 224.0.0.1
+ * Pauline Middelink : Fast ip_checksum update when forwarding
+ * Masquerading support.
+ * Alan Cox : Multicast loopback error for 224.0.0.1
+ * Alan Cox : IP_MULTICAST_LOOP option.
+ * Alan Cox : Use notifiers.
+ * Bjorn Ekwall : Removed ip_csum (from slhc.c too)
+ * Bjorn Ekwall : Moved ip_fast_csum to ip.h (inline!)
+ * Stefan Becker : Send out ICMP HOST REDIRECT
+ * Arnt Gulbrandsen : ip_build_xmit
+ * Alan Cox : Per socket routing cache
+ * Alan Cox : Fixed routing cache, added header cache.
+ * Alan Cox : Loopback didn't work right in original ip_build_xmit - fixed it.
+ * Alan Cox : Only send ICMP_REDIRECT if src/dest are the same net.
+ * Alan Cox : Incoming IP option handling.
+ * Alan Cox : Set saddr on raw output frames as per BSD.
+ * Alan Cox : Stopped broadcast source route explosions.
+ * Alan Cox : Can disable source routing
+ * Takeshi Sone : Masquerading didn't work.
+ * Dave Bonn,Alan Cox : Faster IP forwarding whenever possible.
+ * Alan Cox : Memory leaks, tramples, misc debugging.
+ * Alan Cox : Fixed multicast (by popular demand 8))
+ * Alan Cox : Fixed forwarding (by even more popular demand 8))
+ * Alan Cox : Fixed SNMP statistics [I think]
+ * Gerhard Koerting : IP fragmentation forwarding fix
+ * Alan Cox : Device lock against page fault.
+ * Alan Cox : IP_HDRINCL facility.
+ * Werner Almesberger : Zero fragment bug
+ * Alan Cox : RAW IP frame length bug
+ * Alan Cox : Outgoing firewall on build_xmit
+ * A.N.Kuznetsov : IP_OPTIONS support throughout the kernel
+ * Alan Cox : Multicast routing hooks
+ * Jos Vos : Do accounting *before* call_in_firewall
+ * Willy Konynenberg : Transparent proxying support
+ *
+ *
+ *
+ * To Fix:
+ * IP fragmentation wants rewriting cleanly. The RFC815 algorithm is much more efficient
+ * and could be made very efficient with the addition of some virtual memory hacks to permit
+ * the allocation of a buffer that can then be 'grown' by twiddling page tables.
+ * Output fragmentation wants updating along with the buffer management to use a single
+ * interleaved copy algorithm so that fragmenting has a one copy overhead. Actual packet
+ * output should probably do its own fragmentation at the UDP/RAW layer. TCP shouldn't cause
+ * fragmentation anyway.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <asm/system.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/config.h>
+
+#include <linux/net.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+
+#include <net/snmp.h>
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/arp.h>
+#include <net/icmp.h>
+#include <net/raw.h>
+#include <net/checksum.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/mroute.h>
+#include <linux/netlink.h>
+
+/*
+ * SNMP management statistics
+ */
+
+struct ip_mib ip_statistics[NR_CPUS*2];
+
+/*
+ * Process Router Attention IP option
+ */
+int ip_call_ra_chain(struct sk_buff *skb)
+{
+ struct ip_ra_chain *ra;
+ u8 protocol = skb->nh.iph->protocol;
+ struct sock *last = NULL;
+
+ read_lock(&ip_ra_lock);
+ for (ra = ip_ra_chain; ra; ra = ra->next) {
+ struct sock *sk = ra->sk;
+
+ /* If socket is bound to an interface, only report
+ * the packet if it came from that interface.
+ */
+ if (sk && sk->num == protocol
+ && ((sk->bound_dev_if == 0)
+ || (sk->bound_dev_if == skb->dev->ifindex))) {
+ if (skb->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
+ skb = ip_defrag(skb, IP_DEFRAG_CALL_RA_CHAIN);
+ if (skb == NULL) {
+ read_unlock(&ip_ra_lock);
+ return 1;
+ }
+ }
+ if (last) {
+ struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
+ if (skb2)
+ raw_rcv(last, skb2);
+ }
+ last = sk;
+ }
+ }
+
+ if (last) {
+ raw_rcv(last, skb);
+ read_unlock(&ip_ra_lock);
+ return 1;
+ }
+ read_unlock(&ip_ra_lock);
+ return 0;
+}
+
+/* Handle this out of line, it is rare. */
+static int ip_run_ipprot(struct sk_buff *skb, struct iphdr *iph,
+ struct inet_protocol *ipprot, int force_copy)
+{
+ int ret = 0;
+
+ do {
+ if (ipprot->protocol == iph->protocol) {
+ struct sk_buff *skb2 = skb;
+ if (ipprot->copy || force_copy)
+ skb2 = skb_clone(skb, GFP_ATOMIC);
+ if(skb2 != NULL) {
+ ret = 1;
+ ipprot->handler(skb2);
+ }
+ }
+ ipprot = (struct inet_protocol *) ipprot->next;
+ } while(ipprot != NULL);
+
+ return ret;
+}
+
+static inline int ip_local_deliver_finish(struct sk_buff *skb)
+{
+ int ihl = skb->nh.iph->ihl*4;
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ nf_debug_ip_local_deliver(skb);
+#endif /*CONFIG_NETFILTER_DEBUG*/
+
+ __skb_pull(skb, ihl);
+
+ /* Free reference early: we don't need it any more, and it may
+ hold ip_conntrack module loaded indefinitely. */
+ nf_reset(skb);
+
+ /* Point into the IP datagram, just past the header. */
+ skb->h.raw = skb->data;
+
+ {
+ /* Note: See raw.c and net/raw.h, RAWV4_HTABLE_SIZE==MAX_INET_PROTOS */
+ int protocol = skb->nh.iph->protocol;
+ int hash = protocol & (MAX_INET_PROTOS - 1);
+ struct sock *raw_sk = raw_v4_htable[hash];
+ struct inet_protocol *ipprot;
+ int flag;
+
+ /* If there maybe a raw socket we must check - if not we
+ * don't care less
+ */
+ if(raw_sk != NULL)
+ raw_sk = raw_v4_input(skb, skb->nh.iph, hash);
+
+ ipprot = (struct inet_protocol *) inet_protos[hash];
+ flag = 0;
+ if(ipprot != NULL) {
+ if(raw_sk == NULL &&
+ ipprot->next == NULL &&
+ ipprot->protocol == protocol) {
+ int ret;
+
+ /* Fast path... */
+ ret = ipprot->handler(skb);
+
+ return ret;
+ } else {
+ flag = ip_run_ipprot(skb, skb->nh.iph, ipprot, (raw_sk != NULL));
+ }
+ }
+
+ /* All protocols checked.
+ * If this packet was a broadcast, we may *not* reply to it, since that
+ * causes (proven, grin) ARP storms and a leakage of memory (i.e. all
+ * ICMP reply messages get queued up for transmission...)
+ */
+ if(raw_sk != NULL) { /* Shift to last raw user */
+ raw_rcv(raw_sk, skb);
+ sock_put(raw_sk);
+ } else if (!flag) { /* Free and report errors */
+ icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0);
+ kfree_skb(skb);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Deliver IP Packets to the higher protocol layers.
+ */
+int ip_local_deliver(struct sk_buff *skb)
+{
+ /*
+ * Reassemble IP fragments.
+ */
+
+ if (skb->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
+ skb = ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER);
+ if (!skb)
+ return 0;
+ }
+
+ return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
+ ip_local_deliver_finish);
+}
+
+static inline int ip_rcv_finish(struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+ struct iphdr *iph = skb->nh.iph;
+
+ /*
+ * Initialise the virtual path cache for the packet. It describes
+ * how the packet travels inside Linux networking.
+ */
+ if (skb->dst == NULL) {
+ if (ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, dev))
+ goto drop;
+ }
+
+#ifdef CONFIG_NET_CLS_ROUTE
+ if (skb->dst->tclassid) {
+ struct ip_rt_acct *st = ip_rt_acct + 256*smp_processor_id();
+ u32 idx = skb->dst->tclassid;
+ st[idx&0xFF].o_packets++;
+ st[idx&0xFF].o_bytes+=skb->len;
+ st[(idx>>16)&0xFF].i_packets++;
+ st[(idx>>16)&0xFF].i_bytes+=skb->len;
+ }
+#endif
+
+ if (iph->ihl > 5) {
+ struct ip_options *opt;
+
+ /* It looks as overkill, because not all
+ IP options require packet mangling.
+ But it is the easiest for now, especially taking
+ into account that combination of IP options
+ and running sniffer is extremely rare condition.
+ --ANK (980813)
+ */
+
+ if (skb_cow(skb, skb_headroom(skb)))
+ goto drop;
+ iph = skb->nh.iph;
+
+ if (ip_options_compile(NULL, skb))
+ goto inhdr_error;
+
+ opt = &(IPCB(skb)->opt);
+ if (opt->srr) {
+ struct in_device *in_dev = in_dev_get(dev);
+ if (in_dev) {
+ if (!IN_DEV_SOURCE_ROUTE(in_dev)) {
+ if (IN_DEV_LOG_MARTIANS(in_dev) && net_ratelimit())
+ printk(KERN_INFO "source route option %u.%u.%u.%u -> %u.%u.%u.%u\n",
+ NIPQUAD(iph->saddr), NIPQUAD(iph->daddr));
+ in_dev_put(in_dev);
+ goto drop;
+ }
+ in_dev_put(in_dev);
+ }
+ if (ip_options_rcv_srr(skb))
+ goto drop;
+ }
+ }
+
+ return skb->dst->input(skb);
+
+inhdr_error:
+ IP_INC_STATS_BH(IpInHdrErrors);
+drop:
+ kfree_skb(skb);
+ return NET_RX_DROP;
+}
+
+/*
+ * Main IP Receive routine.
+ */
+int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
+{
+ struct iphdr *iph;
+
+ /* When the interface is in promisc. mode, drop all the crap
+ * that it receives, do not try to analyse it.
+ */
+ if (skb->pkt_type == PACKET_OTHERHOST)
+ goto drop;
+
+ IP_INC_STATS_BH(IpInReceives);
+
+ if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
+ goto out;
+
+ if (!pskb_may_pull(skb, sizeof(struct iphdr)))
+ goto inhdr_error;
+
+ iph = skb->nh.iph;
+
+ /*
+ * RFC1122: 3.1.2.2 MUST silently discard any IP frame that fails the checksum.
+ *
+ * Is the datagram acceptable?
+ *
+ * 1. Length at least the size of an ip header
+ * 2. Version of 4
+ * 3. Checksums correctly. [Speed optimisation for later, skip loopback checksums]
+ * 4. Doesn't have a bogus length
+ */
+
+ if (iph->ihl < 5 || iph->version != 4)
+ goto inhdr_error;
+
+ if (!pskb_may_pull(skb, iph->ihl*4))
+ goto inhdr_error;
+
+ iph = skb->nh.iph;
+
+ if (ip_fast_csum((u8 *)iph, iph->ihl) != 0)
+ goto inhdr_error;
+
+ {
+ __u32 len = ntohs(iph->tot_len);
+ if (skb->len < len || len < (iph->ihl<<2))
+ goto inhdr_error;
+
+ /* Our transport medium may have padded the buffer out. Now we know it
+ * is IP we can trim to the true length of the frame.
+ * Note this now means skb->len holds ntohs(iph->tot_len).
+ */
+ if (skb->len > len) {
+ __pskb_trim(skb, len);
+ if (skb->ip_summed == CHECKSUM_HW)
+ skb->ip_summed = CHECKSUM_NONE;
+ }
+ }
+
+ return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
+ ip_rcv_finish);
+
+inhdr_error:
+ IP_INC_STATS_BH(IpInHdrErrors);
+drop:
+ kfree_skb(skb);
+out:
+ return NET_RX_DROP;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ip_nat_dumb.c b/uClinux-2.4.31-uc0/net/ipv4/ip_nat_dumb.c
new file mode 100644
index 0000000..449c4f4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ip_nat_dumb.c
@@ -0,0 +1,166 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * Dumb Network Address Translation.
+ *
+ * Version: $Id: ip_nat_dumb.c,v 1.11 2000/12/13 18:31:48 davem Exp $
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Fixes:
+ * Rani Assaf : A zero checksum is a special case
+ * only in UDP
+ * Rani Assaf : Added ICMP messages rewriting
+ * Rani Assaf : Repaired wrong changes, made by ANK.
+ *
+ *
+ * NOTE: It is just working model of real NAT.
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/icmp.h>
+#include <linux/netdevice.h>
+#include <net/sock.h>
+#include <net/ip.h>
+#include <net/icmp.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <net/checksum.h>
+#include <linux/route.h>
+#include <net/route.h>
+#include <net/ip_fib.h>
+
+
+int
+ip_do_nat(struct sk_buff *skb)
+{
+ struct rtable *rt = (struct rtable*)skb->dst;
+ struct iphdr *iph = skb->nh.iph;
+ u32 odaddr = iph->daddr;
+ u32 osaddr = iph->saddr;
+ u16 check;
+
+ IPCB(skb)->flags |= IPSKB_TRANSLATED;
+
+ /* Rewrite IP header */
+ iph->daddr = rt->rt_dst_map;
+ iph->saddr = rt->rt_src_map;
+ iph->check = 0;
+ iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
+
+ /* If it is the first fragment, rewrite protocol headers */
+
+ if (!(iph->frag_off & htons(IP_OFFSET))) {
+ u16 *cksum;
+
+ switch(iph->protocol) {
+ case IPPROTO_TCP:
+ cksum = (u16*)&((struct tcphdr*)(((char*)iph) + (iph->ihl<<2)))->check;
+ if ((u8*)(cksum+1) > skb->tail)
+ goto truncated;
+ check = *cksum;
+ if (skb->ip_summed != CHECKSUM_HW)
+ check = ~check;
+ check = csum_tcpudp_magic(iph->saddr, iph->daddr, 0, 0, check);
+ check = csum_tcpudp_magic(~osaddr, ~odaddr, 0, 0, ~check);
+ if (skb->ip_summed == CHECKSUM_HW)
+ check = ~check;
+ *cksum = check;
+ break;
+ case IPPROTO_UDP:
+ cksum = (u16*)&((struct udphdr*)(((char*)iph) + (iph->ihl<<2)))->check;
+ if ((u8*)(cksum+1) > skb->tail)
+ goto truncated;
+ if ((check = *cksum) != 0) {
+ check = csum_tcpudp_magic(iph->saddr, iph->daddr, 0, 0, ~check);
+ check = csum_tcpudp_magic(~osaddr, ~odaddr, 0, 0, ~check);
+ *cksum = check ? : 0xFFFF;
+ }
+ break;
+ case IPPROTO_ICMP:
+ {
+ struct icmphdr *icmph = (struct icmphdr*)((char*)iph + (iph->ihl<<2));
+ struct iphdr *ciph;
+ u32 idaddr, isaddr;
+ int updated;
+
+ if ((icmph->type != ICMP_DEST_UNREACH) &&
+ (icmph->type != ICMP_TIME_EXCEEDED) &&
+ (icmph->type != ICMP_PARAMETERPROB))
+ break;
+
+ ciph = (struct iphdr *) (icmph + 1);
+
+ if ((u8*)(ciph+1) > skb->tail)
+ goto truncated;
+
+ isaddr = ciph->saddr;
+ idaddr = ciph->daddr;
+ updated = 0;
+
+ if (rt->rt_flags&RTCF_DNAT && ciph->saddr == odaddr) {
+ ciph->saddr = iph->daddr;
+ updated = 1;
+ }
+ if (rt->rt_flags&RTCF_SNAT) {
+ if (ciph->daddr != osaddr) {
+ struct fib_result res;
+ struct rt_key key;
+ unsigned flags = 0;
+
+ key.src = ciph->daddr;
+ key.dst = ciph->saddr;
+ key.iif = skb->dev->ifindex;
+ key.oif = 0;
+#ifdef CONFIG_IP_ROUTE_TOS
+ key.tos = RT_TOS(ciph->tos);
+#endif
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ key.fwmark = 0;
+#endif
+ /* Use fib_lookup() until we get our own
+ * hash table of NATed hosts -- Rani
+ */
+ if (fib_lookup(&key, &res) == 0) {
+ if (res.r) {
+ ciph->daddr = fib_rules_policy(ciph->daddr, &res, &flags);
+ if (ciph->daddr != idaddr)
+ updated = 1;
+ }
+ fib_res_put(&res);
+ }
+ } else {
+ ciph->daddr = iph->saddr;
+ updated = 1;
+ }
+ }
+ if (updated) {
+ cksum = &icmph->checksum;
+ /* Using tcpudp primitive. Why not? */
+ check = csum_tcpudp_magic(ciph->saddr, ciph->daddr, 0, 0, ~(*cksum));
+ *cksum = csum_tcpudp_magic(~isaddr, ~idaddr, 0, 0, ~check);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ return NET_RX_SUCCESS;
+
+truncated:
+ /* should be return NET_RX_BAD; */
+ return -EINVAL;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ip_options.c b/uClinux-2.4.31-uc0/net/ipv4/ip_options.c
new file mode 100644
index 0000000..ab13a1e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ip_options.c
@@ -0,0 +1,629 @@
+/* $USAGI: ip_options.c,v 1.5 2002/11/29 10:36:28 yoshfuji Exp $ */
+
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * The options processing module for ip.c
+ *
+ * Version: $Id: ip_options.c,v 1.21 2001/09/01 00:31:50 davem Exp $
+ *
+ * Authors: A.N.Kuznetsov
+ *
+ */
+
+#include <linux/types.h>
+#include <asm/uaccess.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/icmp.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <net/sock.h>
+#include <net/ip.h>
+#include <net/icmp.h>
+
+/*
+ * Write options to IP header, record destination address to
+ * source route option, address of outgoing interface
+ * (we should already know it, so that this function is allowed be
+ * called only after routing decision) and timestamp,
+ * if we originate this datagram.
+ *
+ * daddr is real destination address, next hop is recorded in IP header.
+ * saddr is address of outgoing interface.
+ */
+
+void ip_options_build(struct sk_buff * skb, struct ip_options * opt,
+ u32 daddr, struct rtable *rt, int is_frag)
+{
+ unsigned char * iph = skb->nh.raw;
+
+ memcpy(&(IPCB(skb)->opt), opt, sizeof(struct ip_options));
+ memcpy(iph+sizeof(struct iphdr), opt->__data, opt->optlen);
+ opt = &(IPCB(skb)->opt);
+ opt->is_data = 0;
+
+ if (opt->srr)
+ memcpy(iph+opt->srr+iph[opt->srr+1]-4, &daddr, 4);
+
+ if (!is_frag) {
+ if (opt->rr_needaddr)
+ ip_rt_get_source(iph+opt->rr+iph[opt->rr+2]-5, rt);
+ if (opt->ts_needaddr)
+ ip_rt_get_source(iph+opt->ts+iph[opt->ts+2]-9, rt);
+ if (opt->ts_needtime) {
+ struct timeval tv;
+ __u32 midtime;
+ do_gettimeofday(&tv);
+ midtime = htonl((tv.tv_sec % 86400) * 1000 + tv.tv_usec / 1000);
+ memcpy(iph+opt->ts+iph[opt->ts+2]-5, &midtime, 4);
+ }
+ return;
+ }
+ if (opt->rr) {
+ memset(iph+opt->rr, IPOPT_NOP, iph[opt->rr+1]);
+ opt->rr = 0;
+ opt->rr_needaddr = 0;
+ }
+ if (opt->ts) {
+ memset(iph+opt->ts, IPOPT_NOP, iph[opt->ts+1]);
+ opt->ts = 0;
+ opt->ts_needaddr = opt->ts_needtime = 0;
+ }
+}
+
+/*
+ * Provided (sopt, skb) points to received options,
+ * build in dopt compiled option set appropriate for answering.
+ * i.e. invert SRR option, copy anothers,
+ * and grab room in RR/TS options.
+ *
+ * NOTE: dopt cannot point to skb.
+ */
+
+int ip_options_echo(struct ip_options * dopt, struct sk_buff * skb)
+{
+ struct ip_options *sopt;
+ unsigned char *sptr, *dptr;
+ int soffset, doffset;
+ int optlen;
+ u32 daddr;
+
+ memset(dopt, 0, sizeof(struct ip_options));
+
+ dopt->is_data = 1;
+
+ sopt = &(IPCB(skb)->opt);
+
+ if (sopt->optlen == 0) {
+ dopt->optlen = 0;
+ return 0;
+ }
+
+ sptr = skb->nh.raw;
+ dptr = dopt->__data;
+
+ if (skb->dst)
+ daddr = ((struct rtable*)skb->dst)->rt_spec_dst;
+ else
+ daddr = skb->nh.iph->daddr;
+
+ if (sopt->rr) {
+ optlen = sptr[sopt->rr+1];
+ soffset = sptr[sopt->rr+2];
+ dopt->rr = dopt->optlen + sizeof(struct iphdr);
+ memcpy(dptr, sptr+sopt->rr, optlen);
+ if (sopt->rr_needaddr && soffset <= optlen) {
+ if (soffset + 3 > optlen)
+ return -EINVAL;
+ dptr[2] = soffset + 4;
+ dopt->rr_needaddr = 1;
+ }
+ dptr += optlen;
+ dopt->optlen += optlen;
+ }
+ if (sopt->ts) {
+ optlen = sptr[sopt->ts+1];
+ soffset = sptr[sopt->ts+2];
+ dopt->ts = dopt->optlen + sizeof(struct iphdr);
+ memcpy(dptr, sptr+sopt->ts, optlen);
+ if (soffset <= optlen) {
+ if (sopt->ts_needaddr) {
+ if (soffset + 3 > optlen)
+ return -EINVAL;
+ dopt->ts_needaddr = 1;
+ soffset += 4;
+ }
+ if (sopt->ts_needtime) {
+ if (soffset + 3 > optlen)
+ return -EINVAL;
+ if ((dptr[3]&0xF) != IPOPT_TS_PRESPEC) {
+ dopt->ts_needtime = 1;
+ soffset += 4;
+ } else {
+ dopt->ts_needtime = 0;
+
+ if (soffset + 8 <= optlen) {
+ __u32 addr;
+
+ memcpy(&addr, sptr+soffset-1, 4);
+ if (inet_addr_type(addr) != RTN_LOCAL) {
+ dopt->ts_needtime = 1;
+ soffset += 8;
+ }
+ }
+ }
+ }
+ dptr[2] = soffset;
+ }
+ dptr += optlen;
+ dopt->optlen += optlen;
+ }
+ if (sopt->srr) {
+ unsigned char * start = sptr+sopt->srr;
+ u32 faddr;
+
+ optlen = start[1];
+ soffset = start[2];
+ doffset = 0;
+ if (soffset > optlen)
+ soffset = optlen + 1;
+ soffset -= 4;
+ if (soffset > 3) {
+ memcpy(&faddr, &start[soffset-1], 4);
+ for (soffset-=4, doffset=4; soffset > 3; soffset-=4, doffset+=4)
+ memcpy(&dptr[doffset-1], &start[soffset-1], 4);
+ /*
+ * RFC1812 requires to fix illegal source routes.
+ */
+ if (memcmp(&skb->nh.iph->saddr, &start[soffset+3], 4) == 0)
+ doffset -= 4;
+ }
+ if (doffset > 3) {
+ memcpy(&start[doffset-1], &daddr, 4);
+ dopt->faddr = faddr;
+ dptr[0] = start[0];
+ dptr[1] = doffset+3;
+ dptr[2] = 4;
+ dptr += doffset+3;
+ dopt->srr = dopt->optlen + sizeof(struct iphdr);
+ dopt->optlen += doffset+3;
+ dopt->is_strictroute = sopt->is_strictroute;
+ }
+ }
+ while (dopt->optlen & 3) {
+ *dptr++ = IPOPT_END;
+ dopt->optlen++;
+ }
+ return 0;
+}
+
+/*
+ * Options "fragmenting", just fill options not
+ * allowed in fragments with NOOPs.
+ * Simple and stupid 8), but the most efficient way.
+ */
+
+void ip_options_fragment(struct sk_buff * skb)
+{
+ unsigned char * optptr = skb->nh.raw;
+ struct ip_options * opt = &(IPCB(skb)->opt);
+ int l = opt->optlen;
+ int optlen;
+
+ while (l > 0) {
+ switch (*optptr) {
+ case IPOPT_END:
+ return;
+ case IPOPT_NOOP:
+ l--;
+ optptr++;
+ continue;
+ }
+ if (l < 2)
+ return;
+ optlen = optptr[1];
+ if (optlen<2 || optlen>l)
+ return;
+ if (!IPOPT_COPIED(*optptr))
+ memset(optptr, IPOPT_NOOP, optlen);
+ l -= optlen;
+ optptr += optlen;
+ }
+ opt->ts = 0;
+ opt->rr = 0;
+ opt->rr_needaddr = 0;
+ opt->ts_needaddr = 0;
+ opt->ts_needtime = 0;
+ return;
+}
+
+/*
+ * Verify options and fill pointers in struct options.
+ * Caller should clear *opt, and set opt->data.
+ * If opt == NULL, then skb->data should point to IP header.
+ */
+
+int ip_options_compile(struct ip_options * opt, struct sk_buff * skb)
+{
+ int l;
+ unsigned char * iph;
+ unsigned char * optptr;
+ int optlen;
+ unsigned char * pp_ptr = NULL;
+ struct rtable *rt = skb ? (struct rtable*)skb->dst : NULL;
+
+ if (!opt) {
+ opt = &(IPCB(skb)->opt);
+ memset(opt, 0, sizeof(struct ip_options));
+ iph = skb->nh.raw;
+ opt->optlen = ((struct iphdr *)iph)->ihl*4 - sizeof(struct iphdr);
+ optptr = iph + sizeof(struct iphdr);
+ opt->is_data = 0;
+ } else {
+ optptr = opt->is_data ? opt->__data : (unsigned char*)&(skb->nh.iph[1]);
+ iph = optptr - sizeof(struct iphdr);
+ }
+
+ for (l = opt->optlen; l > 0; ) {
+ switch (*optptr) {
+ case IPOPT_END:
+ for (optptr++, l--; l>0; optptr++, l--) {
+ if (*optptr != IPOPT_END) {
+ *optptr = IPOPT_END;
+ opt->is_changed = 1;
+ }
+ }
+ goto eol;
+ case IPOPT_NOOP:
+ l--;
+ optptr++;
+ continue;
+ }
+ if (l < 2) {
+ pp_ptr = optptr;
+ goto error;
+ }
+ optlen = optptr[1];
+ if (optlen<2 || optlen>l) {
+ pp_ptr = optptr;
+ goto error;
+ }
+ switch (*optptr) {
+ case IPOPT_SSRR:
+ case IPOPT_LSRR:
+ if (optlen < 3) {
+ pp_ptr = optptr + 1;
+ goto error;
+ }
+ if (optptr[2] < 4) {
+ pp_ptr = optptr + 2;
+ goto error;
+ }
+ /* NB: cf RFC-1812 5.2.4.1 */
+ if (opt->srr) {
+ pp_ptr = optptr;
+ goto error;
+ }
+ if (!skb) {
+ if (optptr[2] != 4 || optlen < 7 || ((optlen-3) & 3)) {
+ pp_ptr = optptr + 1;
+ goto error;
+ }
+ memcpy(&opt->faddr, &optptr[3], 4);
+ if (optlen > 7)
+ memmove(&optptr[3], &optptr[7], optlen-7);
+ }
+ opt->is_strictroute = (optptr[0] == IPOPT_SSRR);
+ opt->srr = optptr - iph;
+ break;
+ case IPOPT_RR:
+ if (opt->rr) {
+ pp_ptr = optptr;
+ goto error;
+ }
+ if (optlen < 3) {
+ pp_ptr = optptr + 1;
+ goto error;
+ }
+ if (optptr[2] < 4) {
+ pp_ptr = optptr + 2;
+ goto error;
+ }
+ if (optptr[2] <= optlen) {
+ if (optptr[2]+3 > optlen) {
+ pp_ptr = optptr + 2;
+ goto error;
+ }
+ if (skb) {
+ memcpy(&optptr[optptr[2]-1], &rt->rt_spec_dst, 4);
+ opt->is_changed = 1;
+ }
+ optptr[2] += 4;
+ opt->rr_needaddr = 1;
+ }
+ opt->rr = optptr - iph;
+ break;
+ case IPOPT_TIMESTAMP:
+ if (opt->ts) {
+ pp_ptr = optptr;
+ goto error;
+ }
+ if (optlen < 4) {
+ pp_ptr = optptr + 1;
+ goto error;
+ }
+ if (optptr[2] < 5) {
+ pp_ptr = optptr + 2;
+ goto error;
+ }
+ if (optptr[2] <= optlen) {
+ __u32 * timeptr = NULL;
+ if (optptr[2]+3 > optptr[1]) {
+ pp_ptr = optptr + 2;
+ goto error;
+ }
+ switch (optptr[3]&0xF) {
+ case IPOPT_TS_TSONLY:
+ opt->ts = optptr - iph;
+ if (skb)
+ timeptr = (__u32*)&optptr[optptr[2]-1];
+ opt->ts_needtime = 1;
+ optptr[2] += 4;
+ break;
+ case IPOPT_TS_TSANDADDR:
+ if (optptr[2]+7 > optptr[1]) {
+ pp_ptr = optptr + 2;
+ goto error;
+ }
+ opt->ts = optptr - iph;
+ if (skb) {
+ memcpy(&optptr[optptr[2]-1], &rt->rt_spec_dst, 4);
+ timeptr = (__u32*)&optptr[optptr[2]+3];
+ }
+ opt->ts_needaddr = 1;
+ opt->ts_needtime = 1;
+ optptr[2] += 8;
+ break;
+ case IPOPT_TS_PRESPEC:
+ if (optptr[2]+7 > optptr[1]) {
+ pp_ptr = optptr + 2;
+ goto error;
+ }
+ opt->ts = optptr - iph;
+ {
+ u32 addr;
+ memcpy(&addr, &optptr[optptr[2]-1], 4);
+ if (inet_addr_type(addr) == RTN_UNICAST)
+ break;
+ if (skb)
+ timeptr = (__u32*)&optptr[optptr[2]+3];
+ }
+ opt->ts_needtime = 1;
+ optptr[2] += 8;
+ break;
+ default:
+ if (!skb && !capable(CAP_NET_RAW)) {
+ pp_ptr = optptr + 3;
+ goto error;
+ }
+ break;
+ }
+ if (timeptr) {
+ struct timeval tv;
+ __u32 midtime;
+ do_gettimeofday(&tv);
+ midtime = htonl((tv.tv_sec % 86400) * 1000 + tv.tv_usec / 1000);
+ memcpy(timeptr, &midtime, sizeof(__u32));
+ opt->is_changed = 1;
+ }
+ } else {
+ unsigned overflow = optptr[3]>>4;
+ if (overflow == 15) {
+ pp_ptr = optptr + 3;
+ goto error;
+ }
+ opt->ts = optptr - iph;
+ if (skb) {
+ optptr[3] = (optptr[3]&0xF)|((overflow+1)<<4);
+ opt->is_changed = 1;
+ }
+ }
+ break;
+ case IPOPT_RA:
+ if (optlen < 4) {
+ pp_ptr = optptr + 1;
+ goto error;
+ }
+ if (optptr[2] == 0 && optptr[3] == 0)
+ opt->router_alert = optptr - iph;
+ break;
+ case IPOPT_SEC:
+ case IPOPT_SID:
+ default:
+ if (!skb && !capable(CAP_NET_RAW)) {
+ pp_ptr = optptr;
+ goto error;
+ }
+ break;
+ }
+ l -= optlen;
+ optptr += optlen;
+ }
+
+eol:
+ if (!pp_ptr)
+ return 0;
+
+error:
+ if (skb) {
+ icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((pp_ptr-iph)<<24));
+ }
+ return -EINVAL;
+}
+
+
+/*
+ * Undo all the changes done by ip_options_compile().
+ */
+
+void ip_options_undo(struct ip_options * opt)
+{
+ if (opt->srr) {
+ unsigned char * optptr = opt->__data+opt->srr-sizeof(struct iphdr);
+ memmove(optptr+7, optptr+3, optptr[1]-7);
+ memcpy(optptr+3, &opt->faddr, 4);
+ }
+ if (opt->rr_needaddr) {
+ unsigned char * optptr = opt->__data+opt->rr-sizeof(struct iphdr);
+ optptr[2] -= 4;
+ memset(&optptr[optptr[2]-1], 0, 4);
+ }
+ if (opt->ts) {
+ unsigned char * optptr = opt->__data+opt->ts-sizeof(struct iphdr);
+ if (opt->ts_needtime) {
+ optptr[2] -= 4;
+ memset(&optptr[optptr[2]-1], 0, 4);
+ if ((optptr[3]&0xF) == IPOPT_TS_PRESPEC)
+ optptr[2] -= 4;
+ }
+ if (opt->ts_needaddr) {
+ optptr[2] -= 4;
+ memset(&optptr[optptr[2]-1], 0, 4);
+ }
+ }
+}
+
+int ip_options_get(struct ip_options **optp, unsigned char *data, int optlen, int user)
+{
+ struct ip_options *opt;
+
+ opt = kmalloc(sizeof(struct ip_options)+((optlen+3)&~3), GFP_KERNEL);
+ if (!opt)
+ return -ENOMEM;
+ memset(opt, 0, sizeof(struct ip_options));
+ if (optlen) {
+ if (user) {
+ if (copy_from_user(opt->__data, data, optlen)) {
+ kfree(opt);
+ return -EFAULT;
+ }
+ } else
+ memcpy(opt->__data, data, optlen);
+ }
+ while (optlen & 3)
+ opt->__data[optlen++] = IPOPT_END;
+ opt->optlen = optlen;
+ opt->is_data = 1;
+ opt->is_setbyuser = 1;
+ if (optlen && ip_options_compile(opt, NULL)) {
+ kfree(opt);
+ return -EINVAL;
+ }
+ if (*optp)
+ kfree(*optp);
+ *optp = opt;
+ return 0;
+}
+
+void ip_forward_options(struct sk_buff *skb)
+{
+ struct ip_options * opt = &(IPCB(skb)->opt);
+ unsigned char * optptr;
+ struct rtable *rt = (struct rtable*)skb->dst;
+ unsigned char *raw = skb->nh.raw;
+
+ if (opt->rr_needaddr) {
+ optptr = (unsigned char *)raw + opt->rr;
+ ip_rt_get_source(&optptr[optptr[2]-5], rt);
+ opt->is_changed = 1;
+ }
+ if (opt->srr_is_hit) {
+ int srrptr, srrspace;
+
+ optptr = raw + opt->srr;
+
+ for ( srrptr=optptr[2], srrspace = optptr[1];
+ srrptr <= srrspace;
+ srrptr += 4
+ ) {
+ if (srrptr + 3 > srrspace)
+ break;
+ if (memcmp(&rt->rt_dst, &optptr[srrptr-1], 4) == 0)
+ break;
+ }
+ if (srrptr + 3 <= srrspace) {
+ opt->is_changed = 1;
+ ip_rt_get_source(&optptr[srrptr-1], rt);
+ skb->nh.iph->daddr = rt->rt_dst;
+ optptr[2] = srrptr+4;
+ } else if (net_ratelimit())
+ printk(KERN_CRIT "ip_forward(): Argh! Destination lost!\n");
+ if (opt->ts_needaddr) {
+ optptr = raw + opt->ts;
+ ip_rt_get_source(&optptr[optptr[2]-9], rt);
+ opt->is_changed = 1;
+ }
+ }
+ if (opt->is_changed) {
+ opt->is_changed = 0;
+ ip_send_check(skb->nh.iph);
+ }
+}
+
+int ip_options_rcv_srr(struct sk_buff *skb)
+{
+ struct ip_options *opt = &(IPCB(skb)->opt);
+ int srrspace, srrptr;
+ u32 nexthop;
+ struct iphdr *iph = skb->nh.iph;
+ unsigned char * optptr = skb->nh.raw + opt->srr;
+ struct rtable *rt = (struct rtable*)skb->dst;
+ struct rtable *rt2;
+ int err;
+
+ if (!opt->srr)
+ return 0;
+
+ if (skb->pkt_type != PACKET_HOST)
+ return -EINVAL;
+ if (rt->rt_type == RTN_UNICAST) {
+ if (!opt->is_strictroute)
+ return 0;
+ icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl(16<<24));
+ return -EINVAL;
+ }
+ if (rt->rt_type != RTN_LOCAL)
+ return -EINVAL;
+
+ for (srrptr=optptr[2], srrspace = optptr[1]; srrptr <= srrspace; srrptr += 4) {
+ if (srrptr + 3 > srrspace) {
+ icmp_send(skb, ICMP_PARAMETERPROB, 0, htonl((opt->srr+2)<<24));
+ return -EINVAL;
+ }
+ memcpy(&nexthop, &optptr[srrptr-1], 4);
+
+ rt = (struct rtable*)skb->dst;
+ skb->dst = NULL;
+ err = ip_route_input(skb, nexthop, iph->saddr, iph->tos, skb->dev);
+ rt2 = (struct rtable*)skb->dst;
+ if (err || (rt2->rt_type != RTN_UNICAST && rt2->rt_type != RTN_LOCAL)) {
+ ip_rt_put(rt2);
+ skb->dst = &rt->u.dst;
+ return -EINVAL;
+ }
+ ip_rt_put(rt);
+ if (rt2->rt_type != RTN_LOCAL)
+ break;
+ /* Superfast 8) loopback forward */
+ memcpy(&iph->daddr, &optptr[srrptr-1], 4);
+ opt->is_changed = 1;
+ }
+ if (srrptr <= srrspace) {
+ opt->srr_is_hit = 1;
+ opt->is_changed = 1;
+ }
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ip_output.c b/uClinux-2.4.31-uc0/net/ipv4/ip_output.c
new file mode 100644
index 0000000..9413a90
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ip_output.c
@@ -0,0 +1,1038 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * The Internet Protocol (IP) output module.
+ *
+ * Version: $Id: ip_output.c,v 1.99.2.1 2002/03/10 04:26:08 davem Exp $
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Donald Becker, <becker@super.org>
+ * Alan Cox, <Alan.Cox@linux.org>
+ * Richard Underwood
+ * Stefan Becker, <stefanb@yello.ping.de>
+ * Jorge Cwik, <jorge@laser.satlink.net>
+ * Arnt Gulbrandsen, <agulbra@nvg.unit.no>
+ *
+ * See ip_input.c for original log
+ *
+ * Fixes:
+ * Alan Cox : Missing nonblock feature in ip_build_xmit.
+ * Mike Kilburn : htons() missing in ip_build_xmit.
+ * Bradford Johnson: Fix faulty handling of some frames when
+ * no route is found.
+ * Alexander Demenshin: Missing sk/skb free in ip_queue_xmit
+ * (in case if packet not accepted by
+ * output firewall rules)
+ * Mike McLagan : Routing by source
+ * Alexey Kuznetsov: use new route cache
+ * Andi Kleen: Fix broken PMTU recovery and remove
+ * some redundant tests.
+ * Vitaly E. Lavrov : Transparent proxy revived after year coma.
+ * Andi Kleen : Replace ip_reply with ip_send_reply.
+ * Andi Kleen : Split fast and slow ip_build_xmit path
+ * for decreased register pressure on x86
+ * and more readibility.
+ * Marc Boucher : When call_out_firewall returns FW_QUEUE,
+ * silently drop skb instead of failing with -EPERM.
+ * Detlev Wengorz : Copy protocol for fragments.
+ */
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/config.h>
+
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+
+#include <net/snmp.h>
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <net/route.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/arp.h>
+#include <net/icmp.h>
+#include <net/raw.h>
+#include <net/checksum.h>
+#include <net/inetpeer.h>
+#include <linux/igmp.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/mroute.h>
+#include <linux/netlink.h>
+
+/*
+ * Shall we try to damage output packets if routing dev changes?
+ */
+
+int sysctl_ip_dynaddr = 0;
+int sysctl_ip_default_ttl = IPDEFTTL;
+
+/* Generate a checksum for an outgoing IP datagram. */
+__inline__ void ip_send_check(struct iphdr *iph)
+{
+ iph->check = 0;
+ iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
+}
+
+/* dev_loopback_xmit for use with netfilter. */
+static int ip_dev_loopback_xmit(struct sk_buff *newskb)
+{
+ newskb->mac.raw = newskb->data;
+ __skb_pull(newskb, newskb->nh.raw - newskb->data);
+ newskb->pkt_type = PACKET_LOOPBACK;
+ newskb->ip_summed = CHECKSUM_UNNECESSARY;
+ BUG_TRAP(newskb->dst);
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ nf_debug_ip_loopback_xmit(newskb);
+#endif
+ netif_rx(newskb);
+ return 0;
+}
+
+/* Don't just hand NF_HOOK skb->dst->output, in case netfilter hook
+ changes route */
+static inline int
+output_maybe_reroute(struct sk_buff *skb)
+{
+ return skb->dst->output(skb);
+}
+
+/*
+ * Add an ip header to a skbuff and send it out.
+ */
+int ip_build_and_send_pkt(struct sk_buff *skb, struct sock *sk,
+ u32 saddr, u32 daddr, struct ip_options *opt)
+{
+ struct rtable *rt = (struct rtable *)skb->dst;
+ struct iphdr *iph;
+
+ /* Build the IP header. */
+ if (opt)
+ iph=(struct iphdr *)skb_push(skb,sizeof(struct iphdr) + opt->optlen);
+ else
+ iph=(struct iphdr *)skb_push(skb,sizeof(struct iphdr));
+
+ iph->version = 4;
+ iph->ihl = 5;
+ iph->tos = sk->protinfo.af_inet.tos;
+ if (ip_dont_fragment(sk, &rt->u.dst))
+ iph->frag_off = htons(IP_DF);
+ else
+ iph->frag_off = 0;
+ iph->ttl = sk->protinfo.af_inet.ttl;
+ iph->daddr = rt->rt_dst;
+ iph->saddr = rt->rt_src;
+ iph->protocol = sk->protocol;
+ iph->tot_len = htons(skb->len);
+ ip_select_ident(iph, &rt->u.dst, sk);
+ skb->nh.iph = iph;
+
+ if (opt && opt->optlen) {
+ iph->ihl += opt->optlen>>2;
+ ip_options_build(skb, opt, daddr, rt, 0);
+ }
+ ip_send_check(iph);
+
+ /* Send it out. */
+ return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
+ output_maybe_reroute);
+}
+
+static inline int ip_finish_output2(struct sk_buff *skb)
+{
+ struct dst_entry *dst = skb->dst;
+ struct hh_cache *hh = dst->hh;
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ nf_debug_ip_finish_output2(skb);
+#endif /*CONFIG_NETFILTER_DEBUG*/
+
+ if (hh) {
+ int hh_alen;
+
+ read_lock_bh(&hh->hh_lock);
+ hh_alen = HH_DATA_ALIGN(hh->hh_len);
+ memcpy(skb->data - hh_alen, hh->hh_data, hh_alen);
+ read_unlock_bh(&hh->hh_lock);
+ skb_push(skb, hh->hh_len);
+ return hh->hh_output(skb);
+ } else if (dst->neighbour)
+ return dst->neighbour->output(skb);
+
+ if (net_ratelimit())
+ printk(KERN_DEBUG "ip_finish_output2: No header cache and no neighbour!\n");
+ kfree_skb(skb);
+ return -EINVAL;
+}
+
+static __inline__ int __ip_finish_output(struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dst->dev;
+
+ skb->dev = dev;
+ skb->protocol = htons(ETH_P_IP);
+
+ return NF_HOOK(PF_INET, NF_IP_POST_ROUTING, skb, NULL, dev,
+ ip_finish_output2);
+}
+
+int ip_finish_output(struct sk_buff *skb)
+{
+ return __ip_finish_output(skb);
+}
+
+int ip_mc_output(struct sk_buff *skb)
+{
+ struct sock *sk = skb->sk;
+ struct rtable *rt = (struct rtable*)skb->dst;
+ struct net_device *dev = rt->u.dst.dev;
+
+ /*
+ * If the indicated interface is up and running, send the packet.
+ */
+ IP_INC_STATS(IpOutRequests);
+#ifdef CONFIG_IP_ROUTE_NAT
+ if (rt->rt_flags & RTCF_NAT)
+ ip_do_nat(skb);
+#endif
+
+ skb->dev = dev;
+ skb->protocol = htons(ETH_P_IP);
+
+ /*
+ * Multicasts are looped back for other local users
+ */
+
+ if (rt->rt_flags&RTCF_MULTICAST) {
+ if ((!sk || sk->protinfo.af_inet.mc_loop)
+#ifdef CONFIG_IP_MROUTE
+ /* Small optimization: do not loopback not local frames,
+ which returned after forwarding; they will be dropped
+ by ip_mr_input in any case.
+ Note, that local frames are looped back to be delivered
+ to local recipients.
+
+ This check is duplicated in ip_mr_input at the moment.
+ */
+ && ((rt->rt_flags&RTCF_LOCAL) || !(IPCB(skb)->flags&IPSKB_FORWARDED))
+#endif
+ ) {
+ struct sk_buff *newskb = skb_clone(skb, GFP_ATOMIC);
+ if (newskb)
+ NF_HOOK(PF_INET, NF_IP_POST_ROUTING, newskb, NULL,
+ newskb->dev,
+ ip_dev_loopback_xmit);
+ }
+
+ /* Multicasts with ttl 0 must not go beyond the host */
+
+ if (skb->nh.iph->ttl == 0) {
+ kfree_skb(skb);
+ return 0;
+ }
+ }
+
+ if (rt->rt_flags&RTCF_BROADCAST) {
+ struct sk_buff *newskb = skb_clone(skb, GFP_ATOMIC);
+ if (newskb)
+ NF_HOOK(PF_INET, NF_IP_POST_ROUTING, newskb, NULL,
+ newskb->dev, ip_dev_loopback_xmit);
+ }
+
+ return __ip_finish_output(skb);
+}
+
+int ip_output(struct sk_buff *skb)
+{
+#ifdef CONFIG_IP_ROUTE_NAT
+ struct rtable *rt = (struct rtable*)skb->dst;
+#endif
+
+ IP_INC_STATS(IpOutRequests);
+
+#ifdef CONFIG_IP_ROUTE_NAT
+ if (rt->rt_flags&RTCF_NAT)
+ ip_do_nat(skb);
+#endif
+
+ return __ip_finish_output(skb);
+}
+
+/* Queues a packet to be sent, and starts the transmitter if necessary.
+ * This routine also needs to put in the total length and compute the
+ * checksum. We use to do this in two stages, ip_build_header() then
+ * this, but that scheme created a mess when routes disappeared etc.
+ * So we do it all here, and the TCP send engine has been changed to
+ * match. (No more unroutable FIN disasters, etc. wheee...) This will
+ * most likely make other reliable transport layers above IP easier
+ * to implement under Linux.
+ */
+static inline int ip_queue_xmit2(struct sk_buff *skb)
+{
+ struct sock *sk = skb->sk;
+ struct rtable *rt = (struct rtable *)skb->dst;
+ struct net_device *dev;
+ struct iphdr *iph = skb->nh.iph;
+
+ dev = rt->u.dst.dev;
+
+ /* This can happen when the transport layer has segments queued
+ * with a cached route, and by the time we get here things are
+ * re-routed to a device with a different MTU than the original
+ * device. Sick, but we must cover it.
+ */
+ if (skb_headroom(skb) < dev->hard_header_len && dev->hard_header) {
+ struct sk_buff *skb2;
+
+ skb2 = skb_realloc_headroom(skb, (dev->hard_header_len + 15) & ~15);
+ kfree_skb(skb);
+ if (skb2 == NULL)
+ return -ENOMEM;
+ if (sk)
+ skb_set_owner_w(skb2, sk);
+ skb = skb2;
+ iph = skb->nh.iph;
+ }
+
+ if (skb->len > rt->u.dst.pmtu)
+ goto fragment;
+
+ ip_select_ident(iph, &rt->u.dst, sk);
+
+ /* Add an IP checksum. */
+ ip_send_check(iph);
+
+ skb->priority = sk->priority;
+ return skb->dst->output(skb);
+
+fragment:
+ if (ip_dont_fragment(sk, &rt->u.dst)) {
+ /* Reject packet ONLY if TCP might fragment
+ * it itself, if were careful enough.
+ */
+ NETDEBUG(printk(KERN_DEBUG "sending pkt_too_big (len[%u] pmtu[%u]) to self\n",
+ skb->len, rt->u.dst.pmtu));
+
+ icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
+ htonl(rt->u.dst.pmtu));
+ kfree_skb(skb);
+ return -EMSGSIZE;
+ }
+ ip_select_ident(iph, &rt->u.dst, sk);
+ if (skb->ip_summed == CHECKSUM_HW &&
+ (skb = skb_checksum_help(skb)) == NULL)
+ return -ENOMEM;
+ return ip_fragment(skb, skb->dst->output);
+}
+
+int ip_queue_xmit(struct sk_buff *skb, int ipfragok)
+{
+ struct sock *sk = skb->sk;
+ struct ip_options *opt = sk->protinfo.af_inet.opt;
+ struct rtable *rt;
+ struct iphdr *iph;
+
+ /* Skip all of this if the packet is already routed,
+ * f.e. by something like SCTP.
+ */
+ rt = (struct rtable *) skb->dst;
+ if (rt != NULL)
+ goto packet_routed;
+
+ /* Make sure we can route this packet. */
+ rt = (struct rtable *)__sk_dst_check(sk, 0);
+ if (rt == NULL || rt->u.dst.obsolete) {
+ u32 daddr;
+
+ /* Use correct destination address if we have options. */
+ daddr = sk->daddr;
+ if(opt && opt->srr)
+ daddr = opt->faddr;
+
+ /* If this fails, retransmit mechanism of transport layer will
+ * keep trying until route appears or the connection times itself
+ * out.
+ */
+ if (ip_route_output(&rt, daddr, sk->saddr,
+ RT_CONN_FLAGS(sk),
+ sk->bound_dev_if))
+ goto no_route;
+ __sk_dst_set(sk, &rt->u.dst);
+ sk->route_caps = rt->u.dst.dev->features;
+ }
+ skb->dst = dst_clone(&rt->u.dst);
+
+packet_routed:
+ if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
+ goto no_route;
+
+ /* OK, we know where to send it, allocate and build IP header. */
+ iph = (struct iphdr *) skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));
+ *((__u16 *)iph) = htons((4 << 12) | (5 << 8) | (sk->protinfo.af_inet.tos & 0xff));
+ iph->tot_len = htons(skb->len);
+ if (ip_dont_fragment(sk, &rt->u.dst) && !ipfragok)
+ iph->frag_off = htons(IP_DF);
+ else
+ iph->frag_off = 0;
+ iph->ttl = sk->protinfo.af_inet.ttl;
+ iph->protocol = sk->protocol;
+ iph->saddr = rt->rt_src;
+ iph->daddr = rt->rt_dst;
+ skb->nh.iph = iph;
+ /* Transport layer set skb->h.foo itself. */
+
+ if(opt && opt->optlen) {
+ iph->ihl += opt->optlen >> 2;
+ ip_options_build(skb, opt, sk->daddr, rt, 0);
+ }
+ return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
+ ip_queue_xmit2);
+
+no_route:
+ IP_INC_STATS(IpOutNoRoutes);
+ kfree_skb(skb);
+ return -EHOSTUNREACH;
+}
+
+/*
+ * Build and send a packet, with as little as one copy
+ *
+ * Doesn't care much about ip options... option length can be
+ * different for fragment at 0 and other fragments.
+ *
+ * Note that the fragment at the highest offset is sent first,
+ * so the getfrag routine can fill in the TCP/UDP checksum header
+ * field in the last fragment it sends... actually it also helps
+ * the reassemblers, they can put most packets in at the head of
+ * the fragment queue, and they know the total size in advance. This
+ * last feature will measurably improve the Linux fragment handler one
+ * day.
+ *
+ * The callback has five args, an arbitrary pointer (copy of frag),
+ * the source IP address (may depend on the routing table), the
+ * destination address (char *), the offset to copy from, and the
+ * length to be copied.
+ */
+
+static int ip_build_xmit_slow(struct sock *sk,
+ int getfrag (const void *,
+ char *,
+ unsigned int,
+ unsigned int,
+ struct sk_buff *),
+ const void *frag,
+ unsigned length,
+ struct ipcm_cookie *ipc,
+ struct rtable *rt,
+ int flags)
+{
+ unsigned int fraglen, maxfraglen, fragheaderlen;
+ int err;
+ int offset, mf;
+ int mtu;
+ u16 id;
+
+ int hh_len = (rt->u.dst.dev->hard_header_len + 15)&~15;
+ int nfrags=0;
+ struct ip_options *opt = ipc->opt;
+ int df = 0;
+
+ mtu = rt->u.dst.pmtu;
+ if (ip_dont_fragment(sk, &rt->u.dst))
+ df = htons(IP_DF);
+
+ length -= sizeof(struct iphdr);
+
+ id = sk->protinfo.af_inet.id++;
+
+ if (opt) {
+ fragheaderlen = sizeof(struct iphdr) + opt->optlen;
+ maxfraglen = ((mtu-sizeof(struct iphdr)-opt->optlen) & ~7) + fragheaderlen;
+ } else {
+ fragheaderlen = sizeof(struct iphdr);
+
+ /*
+ * Fragheaderlen is the size of 'overhead' on each buffer. Now work
+ * out the size of the frames to send.
+ */
+
+ maxfraglen = ((mtu-sizeof(struct iphdr)) & ~7) + fragheaderlen;
+ }
+
+ if (length + fragheaderlen > 0xFFFF) {
+ ip_local_error(sk, EMSGSIZE, rt->rt_dst, sk->dport, mtu);
+ return -EMSGSIZE;
+ }
+
+ /*
+ * Start at the end of the frame by handling the remainder.
+ */
+
+ offset = length - (length % (maxfraglen - fragheaderlen));
+
+ /*
+ * Amount of memory to allocate for final fragment.
+ */
+
+ fraglen = length - offset + fragheaderlen;
+
+ if (length-offset==0) {
+ fraglen = maxfraglen;
+ offset -= maxfraglen-fragheaderlen;
+ }
+
+ /*
+ * The last fragment will not have MF (more fragments) set.
+ */
+
+ mf = 0;
+
+ /*
+ * Don't fragment packets for path mtu discovery.
+ */
+
+ if (offset > 0 && sk->protinfo.af_inet.pmtudisc==IP_PMTUDISC_DO) {
+ ip_local_error(sk, EMSGSIZE, rt->rt_dst, sk->dport, mtu);
+ return -EMSGSIZE;
+ }
+ if (flags&MSG_PROBE)
+ goto out;
+
+ /*
+ * Begin outputting the bytes.
+ */
+ do {
+ char *data;
+ struct sk_buff * skb;
+
+ /*
+ * Get the memory we require with some space left for alignment.
+ */
+ if (!(flags & MSG_DONTWAIT) || nfrags == 0) {
+ skb = sock_alloc_send_skb(sk, fraglen + hh_len + 15,
+ (flags & MSG_DONTWAIT), &err);
+ } else {
+ /* On a non-blocking write, we check for send buffer
+ * usage on the first fragment only.
+ */
+ skb = sock_wmalloc(sk, fraglen + hh_len + 15, 1,
+ sk->allocation);
+ if (!skb)
+ err = -ENOBUFS;
+ }
+ if (skb == NULL)
+ goto error;
+
+ /*
+ * Fill in the control structures
+ */
+
+ skb->priority = sk->priority;
+ skb->dst = dst_clone(&rt->u.dst);
+ skb_reserve(skb, hh_len);
+
+ /*
+ * Find where to start putting bytes.
+ */
+
+ data = skb_put(skb, fraglen);
+ skb->nh.iph = (struct iphdr *)data;
+
+ /*
+ * Only write IP header onto non-raw packets
+ */
+
+ {
+ struct iphdr *iph = (struct iphdr *)data;
+
+ iph->version = 4;
+ iph->ihl = 5;
+ if (opt) {
+ iph->ihl += opt->optlen>>2;
+ ip_options_build(skb, opt,
+ ipc->addr, rt, offset);
+ }
+ iph->tos = sk->protinfo.af_inet.tos;
+ iph->tot_len = htons(fraglen - fragheaderlen + iph->ihl*4);
+ iph->frag_off = htons(offset>>3)|mf|df;
+ iph->id = id;
+ if (!mf) {
+ if (offset || !df) {
+ /* Select an unpredictable ident only
+ * for packets without DF or having
+ * been fragmented.
+ */
+ __ip_select_ident(iph, &rt->u.dst);
+ id = iph->id;
+ }
+
+ /*
+ * Any further fragments will have MF set.
+ */
+ mf = htons(IP_MF);
+ }
+ if (rt->rt_type == RTN_MULTICAST)
+ iph->ttl = sk->protinfo.af_inet.mc_ttl;
+ else
+ iph->ttl = sk->protinfo.af_inet.ttl;
+ iph->protocol = sk->protocol;
+ iph->check = 0;
+ iph->saddr = rt->rt_src;
+ iph->daddr = rt->rt_dst;
+ iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
+ data += iph->ihl*4;
+ }
+
+ /*
+ * User data callback
+ */
+
+ if (getfrag(frag, data, offset, fraglen-fragheaderlen, skb)) {
+ err = -EFAULT;
+ kfree_skb(skb);
+ goto error;
+ }
+
+ offset -= (maxfraglen-fragheaderlen);
+ fraglen = maxfraglen;
+
+ nfrags++;
+
+ err = NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL,
+ skb->dst->dev, output_maybe_reroute);
+ if (err) {
+ if (err > 0)
+ err = sk->protinfo.af_inet.recverr ? net_xmit_errno(err) : 0;
+ if (err)
+ goto error;
+ }
+ } while (offset >= 0);
+
+ if (nfrags>1)
+ ip_statistics[smp_processor_id()*2 + !in_softirq()].IpFragCreates += nfrags;
+out:
+ return 0;
+
+error:
+ IP_INC_STATS(IpOutDiscards);
+ if (nfrags>1)
+ ip_statistics[smp_processor_id()*2 + !in_softirq()].IpFragCreates += nfrags;
+ return err;
+}
+
+/*
+ * Fast path for unfragmented packets.
+ */
+int ip_build_xmit(struct sock *sk,
+ int getfrag (const void *,
+ char *,
+ unsigned int,
+ unsigned int,
+ struct sk_buff *),
+ const void *frag,
+ unsigned length,
+ struct ipcm_cookie *ipc,
+ struct rtable *rt,
+ int flags)
+{
+ int err;
+ struct sk_buff *skb;
+ int df;
+ struct iphdr *iph;
+
+ /*
+ * Try the simple case first. This leaves fragmented frames, and by
+ * choice RAW frames within 20 bytes of maximum size(rare) to the long path
+ */
+
+ if (!sk->protinfo.af_inet.hdrincl) {
+ length += sizeof(struct iphdr);
+
+ /*
+ * Check for slow path.
+ */
+ if (length > rt->u.dst.pmtu || ipc->opt != NULL)
+ return ip_build_xmit_slow(sk,getfrag,frag,length,ipc,rt,flags);
+ } else {
+ if (length > rt->u.dst.dev->mtu) {
+ ip_local_error(sk, EMSGSIZE, rt->rt_dst, sk->dport, rt->u.dst.dev->mtu);
+ return -EMSGSIZE;
+ }
+ }
+ if (flags&MSG_PROBE)
+ goto out;
+
+ /*
+ * Do path mtu discovery if needed.
+ */
+ df = 0;
+ if (ip_dont_fragment(sk, &rt->u.dst))
+ df = htons(IP_DF);
+
+ /*
+ * Fast path for unfragmented frames without options.
+ */
+ {
+ int hh_len = (rt->u.dst.dev->hard_header_len + 15)&~15;
+
+ skb = sock_alloc_send_skb(sk, length+hh_len+15,
+ flags&MSG_DONTWAIT, &err);
+ if(skb==NULL)
+ goto error;
+ skb_reserve(skb, hh_len);
+ }
+
+ skb->priority = sk->priority;
+ skb->dst = dst_clone(&rt->u.dst);
+
+ skb->nh.iph = iph = (struct iphdr *)skb_put(skb, length);
+
+ if(!sk->protinfo.af_inet.hdrincl) {
+ iph->version=4;
+ iph->ihl=5;
+ iph->tos=sk->protinfo.af_inet.tos;
+ iph->tot_len = htons(length);
+ iph->frag_off = df;
+ iph->ttl=sk->protinfo.af_inet.mc_ttl;
+ ip_select_ident(iph, &rt->u.dst, sk);
+ if (rt->rt_type != RTN_MULTICAST)
+ iph->ttl=sk->protinfo.af_inet.ttl;
+ iph->protocol=sk->protocol;
+ iph->saddr=rt->rt_src;
+ iph->daddr=rt->rt_dst;
+ iph->check=0;
+ iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
+ err = getfrag(frag, ((char *)iph)+iph->ihl*4,0, length-iph->ihl*4, skb);
+ }
+ else
+ err = getfrag(frag, (void *)iph, 0, length, skb);
+
+ if (err)
+ goto error_fault;
+
+ err = NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
+ output_maybe_reroute);
+ if (err > 0)
+ err = sk->protinfo.af_inet.recverr ? net_xmit_errno(err) : 0;
+ if (err)
+ goto error;
+out:
+ return 0;
+
+error_fault:
+ err = -EFAULT;
+ kfree_skb(skb);
+error:
+ IP_INC_STATS(IpOutDiscards);
+ return err;
+}
+
+/*
+ * This IP datagram is too large to be sent in one piece. Break it up into
+ * smaller pieces (each of size equal to IP header plus
+ * a block of the data of the original IP data part) that will yet fit in a
+ * single device frame, and queue such a frame for sending.
+ *
+ * Yes this is inefficient, feel free to submit a quicker one.
+ */
+
+int ip_fragment(struct sk_buff *skb, int (*output)(struct sk_buff*))
+{
+ struct iphdr *iph;
+ int raw = 0;
+ int ptr;
+ struct net_device *dev;
+ struct sk_buff *skb2;
+ unsigned int mtu, hlen, left, len;
+ int offset;
+ int not_last_frag;
+ struct rtable *rt = (struct rtable*)skb->dst;
+ int err = 0;
+
+ dev = rt->u.dst.dev;
+
+ /*
+ * Point into the IP datagram header.
+ */
+
+ iph = skb->nh.iph;
+
+ /*
+ * Setup starting values.
+ */
+
+ hlen = iph->ihl * 4;
+ left = skb->len - hlen; /* Space per frame */
+ mtu = rt->u.dst.pmtu - hlen; /* Size of data space */
+ ptr = raw + hlen; /* Where to start from */
+
+ /*
+ * Fragment the datagram.
+ */
+
+ offset = (ntohs(iph->frag_off) & IP_OFFSET) << 3;
+ not_last_frag = iph->frag_off & htons(IP_MF);
+
+ /*
+ * Keep copying data until we run out.
+ */
+
+ while(left > 0) {
+ len = left;
+ /* IF: it doesn't fit, use 'mtu' - the data space left */
+ if (len > mtu)
+ len = mtu;
+ /* IF: we are not sending upto and including the packet end
+ then align the next start on an eight byte boundary */
+ if (len < left) {
+ len &= ~7;
+ }
+ /*
+ * Allocate buffer.
+ */
+
+ if ((skb2 = alloc_skb(len+hlen+dev->hard_header_len+15,GFP_ATOMIC)) == NULL) {
+ NETDEBUG(printk(KERN_INFO "IP: frag: no memory for new fragment!\n"));
+ err = -ENOMEM;
+ goto fail;
+ }
+
+ /*
+ * Set up data on packet
+ */
+
+ skb2->pkt_type = skb->pkt_type;
+ skb2->priority = skb->priority;
+ skb_reserve(skb2, (dev->hard_header_len+15)&~15);
+ skb_put(skb2, len + hlen);
+ skb2->nh.raw = skb2->data;
+ skb2->h.raw = skb2->data + hlen;
+ skb2->protocol = skb->protocol;
+ skb2->security = skb->security;
+
+ /*
+ * Charge the memory for the fragment to any owner
+ * it might possess
+ */
+
+ if (skb->sk)
+ skb_set_owner_w(skb2, skb->sk);
+ skb2->dst = dst_clone(skb->dst);
+ skb2->dev = skb->dev;
+
+ /*
+ * Copy the packet header into the new buffer.
+ */
+
+ memcpy(skb2->nh.raw, skb->data, hlen);
+
+ /*
+ * Copy a block of the IP datagram.
+ */
+ if (skb_copy_bits(skb, ptr, skb2->h.raw, len))
+ BUG();
+ left -= len;
+
+ /*
+ * Fill in the new header fields.
+ */
+ iph = skb2->nh.iph;
+ iph->frag_off = htons((offset >> 3));
+
+ /* ANK: dirty, but effective trick. Upgrade options only if
+ * the segment to be fragmented was THE FIRST (otherwise,
+ * options are already fixed) and make it ONCE
+ * on the initial skb, so that all the following fragments
+ * will inherit fixed options.
+ */
+ if (offset == 0)
+ ip_options_fragment(skb);
+
+ /* Copy the flags to each fragment. */
+ IPCB(skb2)->flags = IPCB(skb)->flags;
+
+ /*
+ * Added AC : If we are fragmenting a fragment that's not the
+ * last fragment then keep MF on each bit
+ */
+ if (left > 0 || not_last_frag)
+ iph->frag_off |= htons(IP_MF);
+ ptr += len;
+ offset += len;
+
+#ifdef CONFIG_NET_SCHED
+ skb2->tc_index = skb->tc_index;
+#endif
+#ifdef CONFIG_NETFILTER
+ skb2->nfmark = skb->nfmark;
+ skb2->nfcache = skb->nfcache;
+ /* Connection association is same as pre-frag packet */
+ skb2->nfct = skb->nfct;
+ nf_conntrack_get(skb2->nfct);
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ skb2->nf_bridge = skb->nf_bridge;
+ nf_bridge_get(skb2->nf_bridge);
+#endif
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb2->nf_debug = skb->nf_debug;
+#endif
+#endif
+
+ /*
+ * Put this fragment into the sending queue.
+ */
+
+ IP_INC_STATS(IpFragCreates);
+
+ iph->tot_len = htons(len + hlen);
+
+ ip_send_check(iph);
+
+ err = output(skb2);
+ if (err)
+ goto fail;
+ }
+ kfree_skb(skb);
+ IP_INC_STATS(IpFragOKs);
+ return err;
+
+fail:
+ kfree_skb(skb);
+ IP_INC_STATS(IpFragFails);
+ return err;
+}
+
+/*
+ * Fetch data from kernel space and fill in checksum if needed.
+ */
+static int ip_reply_glue_bits(const void *dptr, char *to, unsigned int offset,
+ unsigned int fraglen, struct sk_buff *skb)
+{
+ struct ip_reply_arg *dp = (struct ip_reply_arg*)dptr;
+ u16 *pktp = (u16 *)to;
+ struct iovec *iov;
+ int len;
+ int hdrflag = 1;
+
+ iov = &dp->iov[0];
+ if (offset >= iov->iov_len) {
+ offset -= iov->iov_len;
+ iov++;
+ hdrflag = 0;
+ }
+ len = iov->iov_len - offset;
+ if (fraglen > len) { /* overlapping. */
+ dp->csum = csum_partial_copy_nocheck(iov->iov_base+offset, to, len,
+ dp->csum);
+ offset = 0;
+ fraglen -= len;
+ to += len;
+ iov++;
+ }
+
+ dp->csum = csum_partial_copy_nocheck(iov->iov_base+offset, to, fraglen,
+ dp->csum);
+
+ if (hdrflag && dp->csumoffset)
+ *(pktp + dp->csumoffset) = csum_fold(dp->csum); /* fill in checksum */
+ return 0;
+}
+
+/*
+ * Generic function to send a packet as reply to another packet.
+ * Used to send TCP resets so far. ICMP should use this function too.
+ *
+ * Should run single threaded per socket because it uses the sock
+ * structure to pass arguments.
+ */
+void ip_send_reply(struct sock *sk, struct sk_buff *skb, struct ip_reply_arg *arg,
+ unsigned int len)
+{
+ struct {
+ struct ip_options opt;
+ char data[40];
+ } replyopts;
+ struct ipcm_cookie ipc;
+ u32 daddr;
+ struct rtable *rt = (struct rtable*)skb->dst;
+
+ if (ip_options_echo(&replyopts.opt, skb))
+ return;
+
+ daddr = ipc.addr = rt->rt_src;
+ ipc.opt = NULL;
+
+ if (replyopts.opt.optlen) {
+ ipc.opt = &replyopts.opt;
+
+ if (ipc.opt->srr)
+ daddr = replyopts.opt.faddr;
+ }
+
+ if (ip_route_output(&rt, daddr, rt->rt_spec_dst, RT_TOS(skb->nh.iph->tos), 0))
+ return;
+
+ /* And let IP do all the hard work.
+
+ This chunk is not reenterable, hence spinlock.
+ Note that it uses the fact, that this function is called
+ with locally disabled BH and that sk cannot be already spinlocked.
+ */
+ bh_lock_sock(sk);
+ sk->protinfo.af_inet.tos = skb->nh.iph->tos;
+ sk->priority = skb->priority;
+ sk->protocol = skb->nh.iph->protocol;
+ ip_build_xmit(sk, ip_reply_glue_bits, arg, len, &ipc, rt, MSG_DONTWAIT);
+ bh_unlock_sock(sk);
+
+ ip_rt_put(rt);
+}
+
+/*
+ * IP protocol layer initialiser
+ */
+
+static struct packet_type ip_packet_type =
+{
+ __constant_htons(ETH_P_IP),
+ NULL, /* All devices */
+ ip_rcv,
+ (void*)1,
+ NULL,
+};
+
+/*
+ * IP registers the packet type and then calls the subprotocol initialisers
+ */
+
+void __init ip_init(void)
+{
+ dev_add_pack(&ip_packet_type);
+
+ ip_rt_init();
+ inet_initpeers();
+
+#ifdef CONFIG_IP_MULTICAST
+ proc_net_create("igmp", 0, ip_mc_procinfo);
+#endif
+ proc_net_create("mcfilter", 0, ip_mcf_procinfo);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ip_sockglue.c b/uClinux-2.4.31-uc0/net/ipv4/ip_sockglue.c
new file mode 100644
index 0000000..8c55605
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ip_sockglue.c
@@ -0,0 +1,1077 @@
+/* $USAGI: ip_sockglue.c,v 1.11 2003/08/08 13:46:37 yoshfuji Exp $ */
+
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * The IP to API glue.
+ *
+ * Version: $Id: ip_sockglue.c,v 1.61 2001/10/20 00:00:11 davem Exp $
+ *
+ * Authors: see ip.c
+ *
+ * Fixes:
+ * Many : Split from ip.c , see ip.c for history.
+ * Martin Mares : TOS setting fixed.
+ * Alan Cox : Fixed a couple of oopses in Martin's
+ * TOS tweaks.
+ * Mike McLagan : Routing by source
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/icmp.h>
+#include <linux/netdevice.h>
+#include <net/sock.h>
+#include <net/ip.h>
+#include <net/icmp.h>
+#include <net/tcp.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/igmp.h>
+#include <linux/netfilter.h>
+#include <linux/route.h>
+#include <linux/mroute.h>
+#include <net/route.h>
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+#include <net/transp_v6.h>
+#endif
+
+#include <linux/errqueue.h>
+#include <asm/uaccess.h>
+
+#define IP_CMSG_PKTINFO 1
+#define IP_CMSG_TTL 2
+#define IP_CMSG_TOS 4
+#define IP_CMSG_RECVOPTS 8
+#define IP_CMSG_RETOPTS 16
+
+/*
+ * SOL_IP control messages.
+ */
+
+static void ip_cmsg_recv_pktinfo(struct msghdr *msg, struct sk_buff *skb)
+{
+ struct in_pktinfo info;
+ struct rtable *rt = (struct rtable *)skb->dst;
+
+ info.ipi_addr.s_addr = skb->nh.iph->daddr;
+ if (rt) {
+ info.ipi_ifindex = rt->rt_iif;
+ info.ipi_spec_dst.s_addr = rt->rt_spec_dst;
+ } else {
+ info.ipi_ifindex = 0;
+ info.ipi_spec_dst.s_addr = 0;
+ }
+
+ put_cmsg(msg, SOL_IP, IP_PKTINFO, sizeof(info), &info);
+}
+
+static void ip_cmsg_recv_ttl(struct msghdr *msg, struct sk_buff *skb)
+{
+ int ttl = skb->nh.iph->ttl;
+ put_cmsg(msg, SOL_IP, IP_TTL, sizeof(int), &ttl);
+}
+
+static void ip_cmsg_recv_tos(struct msghdr *msg, struct sk_buff *skb)
+{
+ put_cmsg(msg, SOL_IP, IP_TOS, 1, &skb->nh.iph->tos);
+}
+
+static void ip_cmsg_recv_opts(struct msghdr *msg, struct sk_buff *skb)
+{
+ if (IPCB(skb)->opt.optlen == 0)
+ return;
+
+ put_cmsg(msg, SOL_IP, IP_RECVOPTS, IPCB(skb)->opt.optlen, skb->nh.iph+1);
+}
+
+
+void ip_cmsg_recv_retopts(struct msghdr *msg, struct sk_buff *skb)
+{
+ unsigned char optbuf[sizeof(struct ip_options) + 40];
+ struct ip_options * opt = (struct ip_options*)optbuf;
+
+ if (IPCB(skb)->opt.optlen == 0)
+ return;
+
+ if (ip_options_echo(opt, skb)) {
+ msg->msg_flags |= MSG_CTRUNC;
+ return;
+ }
+ ip_options_undo(opt);
+
+ put_cmsg(msg, SOL_IP, IP_RETOPTS, opt->optlen, opt->__data);
+}
+
+
+void ip_cmsg_recv(struct msghdr *msg, struct sk_buff *skb)
+{
+ unsigned flags = skb->sk->protinfo.af_inet.cmsg_flags;
+
+ /* Ordered by supposed usage frequency */
+ if (flags & 1)
+ ip_cmsg_recv_pktinfo(msg, skb);
+ if ((flags>>=1) == 0)
+ return;
+
+ if (flags & 1)
+ ip_cmsg_recv_ttl(msg, skb);
+ if ((flags>>=1) == 0)
+ return;
+
+ if (flags & 1)
+ ip_cmsg_recv_tos(msg, skb);
+ if ((flags>>=1) == 0)
+ return;
+
+ if (flags & 1)
+ ip_cmsg_recv_opts(msg, skb);
+ if ((flags>>=1) == 0)
+ return;
+
+ if (flags & 1)
+ ip_cmsg_recv_retopts(msg, skb);
+}
+
+int ip_cmsg_send(struct msghdr *msg, struct ipcm_cookie *ipc)
+{
+ int err;
+ struct cmsghdr *cmsg;
+
+ for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+ if (!CMSG_OK(msg, cmsg))
+ return -EINVAL;
+ if (cmsg->cmsg_level != SOL_IP)
+ continue;
+ switch (cmsg->cmsg_type) {
+ case IP_RETOPTS:
+ err = cmsg->cmsg_len - CMSG_ALIGN(sizeof(struct cmsghdr));
+ err = ip_options_get(&ipc->opt, CMSG_DATA(cmsg), err < 40 ? err : 40, 0);
+ if (err)
+ return err;
+ break;
+ case IP_PKTINFO:
+ {
+ struct in_pktinfo *info;
+ if (cmsg->cmsg_len != CMSG_LEN(sizeof(struct in_pktinfo)))
+ return -EINVAL;
+ info = (struct in_pktinfo *)CMSG_DATA(cmsg);
+ ipc->oif = info->ipi_ifindex;
+ ipc->addr = info->ipi_spec_dst.s_addr;
+ break;
+ }
+ default:
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+
+/* Special input handler for packets catched by router alert option.
+ They are selected only by protocol field, and then processed likely
+ local ones; but only if someone wants them! Otherwise, router
+ not running rsvpd will kill RSVP.
+
+ It is user level problem, what it will make with them.
+ I have no idea, how it will masquearde or NAT them (it is joke, joke :-)),
+ but receiver should be enough clever f.e. to forward mtrace requests,
+ sent to multicast group to reach destination designated router.
+ */
+struct ip_ra_chain *ip_ra_chain;
+rwlock_t ip_ra_lock = RW_LOCK_UNLOCKED;
+
+int ip_ra_control(struct sock *sk, unsigned char on, void (*destructor)(struct sock *))
+{
+ struct ip_ra_chain *ra, *new_ra, **rap;
+
+ if (sk->type != SOCK_RAW || sk->num == IPPROTO_RAW)
+ return -EINVAL;
+
+ new_ra = on ? kmalloc(sizeof(*new_ra), GFP_KERNEL) : NULL;
+
+ write_lock_bh(&ip_ra_lock);
+ for (rap = &ip_ra_chain; (ra=*rap) != NULL; rap = &ra->next) {
+ if (ra->sk == sk) {
+ if (on) {
+ write_unlock_bh(&ip_ra_lock);
+ if (new_ra)
+ kfree(new_ra);
+ return -EADDRINUSE;
+ }
+ *rap = ra->next;
+ write_unlock_bh(&ip_ra_lock);
+
+ if (ra->destructor)
+ ra->destructor(sk);
+ sock_put(sk);
+ kfree(ra);
+ return 0;
+ }
+ }
+ if (new_ra == NULL) {
+ write_unlock_bh(&ip_ra_lock);
+ return -ENOBUFS;
+ }
+ new_ra->sk = sk;
+ new_ra->destructor = destructor;
+
+ new_ra->next = ra;
+ *rap = new_ra;
+ sock_hold(sk);
+ write_unlock_bh(&ip_ra_lock);
+
+ return 0;
+}
+
+void ip_icmp_error(struct sock *sk, struct sk_buff *skb, int err,
+ u16 port, u32 info, u8 *payload)
+{
+ struct sock_exterr_skb *serr;
+
+ if (!sk->protinfo.af_inet.recverr)
+ return;
+
+ skb = skb_clone(skb, GFP_ATOMIC);
+ if (!skb)
+ return;
+
+ serr = SKB_EXT_ERR(skb);
+ serr->ee.ee_errno = err;
+ serr->ee.ee_origin = SO_EE_ORIGIN_ICMP;
+ serr->ee.ee_type = skb->h.icmph->type;
+ serr->ee.ee_code = skb->h.icmph->code;
+ serr->ee.ee_pad = 0;
+ serr->ee.ee_info = info;
+ serr->ee.ee_data = 0;
+ serr->addr_offset = (u8*)&(((struct iphdr*)(skb->h.icmph+1))->daddr) - skb->nh.raw;
+ serr->port = port;
+
+ skb->h.raw = payload;
+ if (!skb_pull(skb, payload - skb->data) ||
+ sock_queue_err_skb(sk, skb))
+ kfree_skb(skb);
+}
+
+void ip_local_error(struct sock *sk, int err, u32 daddr, u16 port, u32 info)
+{
+ struct sock_exterr_skb *serr;
+ struct iphdr *iph;
+ struct sk_buff *skb;
+
+ if (!sk->protinfo.af_inet.recverr)
+ return;
+
+ skb = alloc_skb(sizeof(struct iphdr), GFP_ATOMIC);
+ if (!skb)
+ return;
+
+ iph = (struct iphdr*)skb_put(skb, sizeof(struct iphdr));
+ skb->nh.iph = iph;
+ iph->daddr = daddr;
+
+ serr = SKB_EXT_ERR(skb);
+ serr->ee.ee_errno = err;
+ serr->ee.ee_origin = SO_EE_ORIGIN_LOCAL;
+ serr->ee.ee_type = 0;
+ serr->ee.ee_code = 0;
+ serr->ee.ee_pad = 0;
+ serr->ee.ee_info = info;
+ serr->ee.ee_data = 0;
+ serr->addr_offset = (u8*)&iph->daddr - skb->nh.raw;
+ serr->port = port;
+
+ skb->h.raw = skb->tail;
+ __skb_pull(skb, skb->tail - skb->data);
+
+ if (sock_queue_err_skb(sk, skb))
+ kfree_skb(skb);
+}
+
+/*
+ * Handle MSG_ERRQUEUE
+ */
+int ip_recv_error(struct sock *sk, struct msghdr *msg, int len)
+{
+ struct sock_exterr_skb *serr;
+ struct sk_buff *skb, *skb2;
+ struct sockaddr_in *sin;
+ struct {
+ struct sock_extended_err ee;
+ struct sockaddr_in offender;
+ } errhdr;
+ int err;
+ int copied;
+
+ err = -EAGAIN;
+ skb = skb_dequeue(&sk->error_queue);
+ if (skb == NULL)
+ goto out;
+
+ copied = skb->len;
+ if (copied > len) {
+ msg->msg_flags |= MSG_TRUNC;
+ copied = len;
+ }
+ err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+ if (err)
+ goto out_free_skb;
+
+ sock_recv_timestamp(msg, sk, skb);
+
+ serr = SKB_EXT_ERR(skb);
+
+ sin = (struct sockaddr_in *)msg->msg_name;
+ if (sin) {
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = *(u32*)(skb->nh.raw + serr->addr_offset);
+ sin->sin_port = serr->port;
+ memset(&sin->sin_zero, 0, sizeof(sin->sin_zero));
+ }
+
+ memcpy(&errhdr.ee, &serr->ee, sizeof(struct sock_extended_err));
+ sin = &errhdr.offender;
+ sin->sin_family = AF_UNSPEC;
+ if (serr->ee.ee_origin == SO_EE_ORIGIN_ICMP) {
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = skb->nh.iph->saddr;
+ sin->sin_port = 0;
+ memset(&sin->sin_zero, 0, sizeof(sin->sin_zero));
+ if (sk->protinfo.af_inet.cmsg_flags)
+ ip_cmsg_recv(msg, skb);
+ }
+
+ put_cmsg(msg, SOL_IP, IP_RECVERR, sizeof(errhdr), &errhdr);
+
+ /* Now we could try to dump offended packet options */
+
+ msg->msg_flags |= MSG_ERRQUEUE;
+ err = copied;
+
+ /* Reset and regenerate socket error */
+ spin_lock_irq(&sk->error_queue.lock);
+ sk->err = 0;
+ if ((skb2 = skb_peek(&sk->error_queue)) != NULL) {
+ sk->err = SKB_EXT_ERR(skb2)->ee.ee_errno;
+ spin_unlock_irq(&sk->error_queue.lock);
+ sk->error_report(sk);
+ } else {
+ spin_unlock_irq(&sk->error_queue.lock);
+ }
+
+out_free_skb:
+ kfree_skb(skb);
+out:
+ return err;
+}
+
+
+/*
+ * Socket option code for IP. This is the end of the line after any TCP,UDP etc options on
+ * an IP socket.
+ */
+
+int ip_setsockopt(struct sock *sk, int level, int optname, char *optval, int optlen)
+{
+ int val=0,err;
+
+ if (level != SOL_IP)
+ return -ENOPROTOOPT;
+
+ if (((1<<optname) & ((1<<IP_PKTINFO) | (1<<IP_RECVTTL) |
+ (1<<IP_RECVOPTS) | (1<<IP_RECVTOS) |
+ (1<<IP_RETOPTS) | (1<<IP_TOS) |
+ (1<<IP_TTL) | (1<<IP_HDRINCL) |
+ (1<<IP_MTU_DISCOVER) | (1<<IP_RECVERR) |
+ (1<<IP_ROUTER_ALERT) | (1<<IP_FREEBIND))) ||
+ optname == IP_MULTICAST_TTL ||
+ optname == IP_MULTICAST_LOOP) {
+ if (optlen >= sizeof(int)) {
+ if (get_user(val, (int *) optval))
+ return -EFAULT;
+ } else if (optlen >= sizeof(char)) {
+ unsigned char ucval;
+
+ if (get_user(ucval, (unsigned char *) optval))
+ return -EFAULT;
+ val = (int) ucval;
+ }
+ }
+
+ /* If optlen==0, it is equivalent to val == 0 */
+
+ if (level != SOL_IP)
+ return -ENOPROTOOPT;
+
+ if (optlen < 0)
+ return -EINVAL;
+
+#ifdef CONFIG_IP_MROUTE
+ if (optname >= MRT_BASE && optname <= (MRT_BASE + 10))
+ return ip_mroute_setsockopt(sk,optname,optval,optlen);
+#endif
+
+ err = 0;
+ lock_sock(sk);
+
+ switch (optname) {
+ case IP_OPTIONS:
+ {
+ struct ip_options * opt = NULL;
+ if (optlen > 40 || optlen < 0)
+ goto e_inval;
+ err = ip_options_get(&opt, optval, optlen, 1);
+ if (err)
+ break;
+ if (sk->type == SOCK_STREAM) {
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ if (sk->family == PF_INET ||
+ (!((1<<sk->state)&(TCPF_LISTEN|TCPF_CLOSE))
+ && sk->daddr != LOOPBACK4_IPV6)) {
+#endif
+ if (opt)
+ tp->ext_header_len = opt->optlen;
+ tcp_sync_mss(sk, tp->pmtu_cookie);
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ }
+#endif
+ }
+ opt = xchg(&sk->protinfo.af_inet.opt, opt);
+ if (opt)
+ kfree(opt);
+ break;
+ }
+ case IP_PKTINFO:
+ if (val)
+ sk->protinfo.af_inet.cmsg_flags |= IP_CMSG_PKTINFO;
+ else
+ sk->protinfo.af_inet.cmsg_flags &= ~IP_CMSG_PKTINFO;
+ break;
+ case IP_RECVTTL:
+ if (val)
+ sk->protinfo.af_inet.cmsg_flags |= IP_CMSG_TTL;
+ else
+ sk->protinfo.af_inet.cmsg_flags &= ~IP_CMSG_TTL;
+ break;
+ case IP_RECVTOS:
+ if (val)
+ sk->protinfo.af_inet.cmsg_flags |= IP_CMSG_TOS;
+ else
+ sk->protinfo.af_inet.cmsg_flags &= ~IP_CMSG_TOS;
+ break;
+ case IP_RECVOPTS:
+ if (val)
+ sk->protinfo.af_inet.cmsg_flags |= IP_CMSG_RECVOPTS;
+ else
+ sk->protinfo.af_inet.cmsg_flags &= ~IP_CMSG_RECVOPTS;
+ break;
+ case IP_RETOPTS:
+ if (val)
+ sk->protinfo.af_inet.cmsg_flags |= IP_CMSG_RETOPTS;
+ else
+ sk->protinfo.af_inet.cmsg_flags &= ~IP_CMSG_RETOPTS;
+ break;
+ case IP_TOS: /* This sets both TOS and Precedence */
+ if (sk->type == SOCK_STREAM) {
+ val &= ~3;
+ val |= sk->protinfo.af_inet.tos & 3;
+ }
+ if (IPTOS_PREC(val) >= IPTOS_PREC_CRITIC_ECP &&
+ !capable(CAP_NET_ADMIN)) {
+ err = -EPERM;
+ break;
+ }
+ if (sk->protinfo.af_inet.tos != val) {
+ sk->protinfo.af_inet.tos=val;
+ sk->priority = rt_tos2priority(val);
+ sk_dst_reset(sk);
+ }
+ break;
+ case IP_TTL:
+ if (optlen<1)
+ goto e_inval;
+ if(val==-1)
+ val = sysctl_ip_default_ttl;
+ if(val<1||val>255)
+ goto e_inval;
+ sk->protinfo.af_inet.ttl=val;
+ break;
+ case IP_HDRINCL:
+ if(sk->type!=SOCK_RAW) {
+ err = -ENOPROTOOPT;
+ break;
+ }
+ sk->protinfo.af_inet.hdrincl=val?1:0;
+ break;
+ case IP_MTU_DISCOVER:
+ if (val<0 || val>2)
+ goto e_inval;
+ sk->protinfo.af_inet.pmtudisc = val;
+ break;
+ case IP_RECVERR:
+ sk->protinfo.af_inet.recverr = !!val;
+ if (!val)
+ skb_queue_purge(&sk->error_queue);
+ break;
+ case IP_MULTICAST_TTL:
+ if (sk->type == SOCK_STREAM)
+ goto e_inval;
+ if (optlen<1)
+ goto e_inval;
+ if (val==-1)
+ val = 1;
+ if (val < 0 || val > 255)
+ goto e_inval;
+ sk->protinfo.af_inet.mc_ttl=val;
+ break;
+ case IP_MULTICAST_LOOP:
+ if (optlen<1)
+ goto e_inval;
+ sk->protinfo.af_inet.mc_loop = val ? 1 : 0;
+ break;
+ case IP_MULTICAST_IF:
+ {
+ struct ip_mreqn mreq;
+ struct net_device *dev = NULL;
+
+ if (sk->type == SOCK_STREAM)
+ goto e_inval;
+ /*
+ * Check the arguments are allowable
+ */
+
+ err = -EFAULT;
+ if (optlen >= sizeof(struct ip_mreqn)) {
+ if (copy_from_user(&mreq,optval,sizeof(mreq)))
+ break;
+ } else {
+ memset(&mreq, 0, sizeof(mreq));
+ if (optlen >= sizeof(struct in_addr) &&
+ copy_from_user(&mreq.imr_address,optval,sizeof(struct in_addr)))
+ break;
+ }
+
+ if (!mreq.imr_ifindex) {
+ if (mreq.imr_address.s_addr == INADDR_ANY) {
+ sk->protinfo.af_inet.mc_index = 0;
+ sk->protinfo.af_inet.mc_addr = 0;
+ err = 0;
+ break;
+ }
+ dev = ip_dev_find(mreq.imr_address.s_addr);
+ if (dev) {
+ mreq.imr_ifindex = dev->ifindex;
+ dev_put(dev);
+ }
+ } else
+ dev = __dev_get_by_index(mreq.imr_ifindex);
+
+
+ err = -EADDRNOTAVAIL;
+ if (!dev)
+ break;
+
+ err = -EINVAL;
+ if (sk->bound_dev_if && mreq.imr_ifindex != sk->bound_dev_if)
+ break;
+
+ sk->protinfo.af_inet.mc_index = mreq.imr_ifindex;
+ sk->protinfo.af_inet.mc_addr = mreq.imr_address.s_addr;
+ err = 0;
+ break;
+ }
+
+ case IP_ADD_MEMBERSHIP:
+ case IP_DROP_MEMBERSHIP:
+ {
+ struct ip_mreqn mreq;
+
+ if (optlen < sizeof(struct ip_mreq))
+ goto e_inval;
+ err = -EFAULT;
+ if (optlen >= sizeof(struct ip_mreqn)) {
+ if(copy_from_user(&mreq,optval,sizeof(mreq)))
+ break;
+ } else {
+ memset(&mreq, 0, sizeof(mreq));
+ if (copy_from_user(&mreq,optval,sizeof(struct ip_mreq)))
+ break;
+ }
+
+ if (optname == IP_ADD_MEMBERSHIP)
+ err = ip_mc_join_group(sk, &mreq);
+ else
+ err = ip_mc_leave_group(sk, &mreq);
+ break;
+ }
+ case IP_MSFILTER:
+ {
+ extern int sysctl_optmem_max;
+ extern int sysctl_igmp_max_msf;
+ struct ip_msfilter *msf;
+
+ if (optlen < IP_MSFILTER_SIZE(0))
+ goto e_inval;
+ if (optlen > sysctl_optmem_max) {
+ err = -ENOBUFS;
+ break;
+ }
+ msf = (struct ip_msfilter *)kmalloc(optlen, GFP_KERNEL);
+ if (msf == 0) {
+ err = -ENOBUFS;
+ break;
+ }
+ err = -EFAULT;
+ if (copy_from_user(msf, optval, optlen)) {
+ kfree(msf);
+ break;
+ }
+ /* numsrc >= (1G-4) overflow in 32 bits */
+ if (msf->imsf_numsrc >= 0x3ffffffcU ||
+ msf->imsf_numsrc > sysctl_igmp_max_msf) {
+ kfree(msf);
+ err = -ENOBUFS;
+ break;
+ }
+ if (IP_MSFILTER_SIZE(msf->imsf_numsrc) > optlen) {
+ kfree(msf);
+ err = -EINVAL;
+ break;
+ }
+ err = ip_mc_msfilter(sk, msf, 0);
+ kfree(msf);
+ break;
+ }
+ case IP_BLOCK_SOURCE:
+ case IP_UNBLOCK_SOURCE:
+ case IP_ADD_SOURCE_MEMBERSHIP:
+ case IP_DROP_SOURCE_MEMBERSHIP:
+ {
+ struct ip_mreq_source mreqs;
+ int omode, add;
+
+ if (optlen != sizeof(struct ip_mreq_source))
+ goto e_inval;
+ if (copy_from_user(&mreqs, optval, sizeof(mreqs))) {
+ err = -EFAULT;
+ break;
+ }
+ if (optname == IP_BLOCK_SOURCE) {
+ omode = MCAST_EXCLUDE;
+ add = 1;
+ } else if (optname == IP_UNBLOCK_SOURCE) {
+ omode = MCAST_EXCLUDE;
+ add = 0;
+ } else if (optname == IP_ADD_SOURCE_MEMBERSHIP) {
+ struct ip_mreqn mreq;
+
+ mreq.imr_multiaddr.s_addr = mreqs.imr_multiaddr;
+ mreq.imr_address.s_addr = mreqs.imr_interface;
+ mreq.imr_ifindex = 0;
+ err = ip_mc_join_group(sk, &mreq);
+ if (err)
+ break;
+ omode = MCAST_INCLUDE;
+ add = 1;
+ } else /*IP_DROP_SOURCE_MEMBERSHIP */ {
+ omode = MCAST_INCLUDE;
+ add = 0;
+ }
+ err = ip_mc_source(add, omode, sk, &mreqs, 0);
+ break;
+ }
+ case MCAST_JOIN_GROUP:
+ case MCAST_LEAVE_GROUP:
+ {
+ struct group_req greq;
+ struct sockaddr_in *psin;
+ struct ip_mreqn mreq;
+
+ if (optlen < sizeof(struct group_req))
+ goto e_inval;
+ err = -EFAULT;
+ if(copy_from_user(&greq, optval, sizeof(greq)))
+ break;
+ psin = (struct sockaddr_in *)&greq.gr_group;
+ if (psin->sin_family != AF_INET)
+ goto e_inval;
+ memset(&mreq, 0, sizeof(mreq));
+ mreq.imr_multiaddr = psin->sin_addr;
+ mreq.imr_ifindex = greq.gr_interface;
+
+ if (optname == MCAST_JOIN_GROUP)
+ err = ip_mc_join_group(sk, &mreq);
+ else
+ err = ip_mc_leave_group(sk, &mreq);
+ break;
+ }
+ case MCAST_JOIN_SOURCE_GROUP:
+ case MCAST_LEAVE_SOURCE_GROUP:
+ case MCAST_BLOCK_SOURCE:
+ case MCAST_UNBLOCK_SOURCE:
+ {
+ struct group_source_req greqs;
+ struct ip_mreq_source mreqs;
+ struct sockaddr_in *psin;
+ int omode, add;
+
+ if (optlen != sizeof(struct group_source_req))
+ goto e_inval;
+ if (copy_from_user(&greqs, optval, sizeof(greqs))) {
+ err = -EFAULT;
+ break;
+ }
+ if (greqs.gsr_group.ss_family != AF_INET ||
+ greqs.gsr_source.ss_family != AF_INET) {
+ err = -EADDRNOTAVAIL;
+ break;
+ }
+ psin = (struct sockaddr_in *)&greqs.gsr_group;
+ mreqs.imr_multiaddr = psin->sin_addr.s_addr;
+ psin = (struct sockaddr_in *)&greqs.gsr_source;
+ mreqs.imr_sourceaddr = psin->sin_addr.s_addr;
+ mreqs.imr_interface = 0; /* use index for mc_source */
+
+ if (optname == MCAST_BLOCK_SOURCE) {
+ omode = MCAST_EXCLUDE;
+ add = 1;
+ } else if (optname == MCAST_UNBLOCK_SOURCE) {
+ omode = MCAST_EXCLUDE;
+ add = 0;
+ } else if (optname == MCAST_JOIN_SOURCE_GROUP) {
+ struct ip_mreqn mreq;
+
+ psin = (struct sockaddr_in *)&greqs.gsr_group;
+ mreq.imr_multiaddr = psin->sin_addr;
+ mreq.imr_address.s_addr = 0;
+ mreq.imr_ifindex = greqs.gsr_interface;
+ err = ip_mc_join_group(sk, &mreq);
+ if (err)
+ break;
+ greqs.gsr_interface = mreq.imr_ifindex;
+ omode = MCAST_INCLUDE;
+ add = 1;
+ } else /* MCAST_LEAVE_SOURCE_GROUP */ {
+ omode = MCAST_INCLUDE;
+ add = 0;
+ }
+ err = ip_mc_source(add, omode, sk, &mreqs,
+ greqs.gsr_interface);
+ break;
+ }
+ case MCAST_MSFILTER:
+ {
+ extern int sysctl_optmem_max;
+ extern int sysctl_igmp_max_msf;
+ struct sockaddr_in *psin;
+ struct ip_msfilter *msf = 0;
+ struct group_filter *gsf = 0;
+ int msize, i, ifindex;
+
+ if (optlen < GROUP_FILTER_SIZE(0))
+ goto e_inval;
+ if (optlen > sysctl_optmem_max) {
+ err = -ENOBUFS;
+ break;
+ }
+ gsf = (struct group_filter *)kmalloc(optlen,GFP_KERNEL);
+ if (gsf == 0) {
+ err = -ENOBUFS;
+ break;
+ }
+ err = -EFAULT;
+ if (copy_from_user(gsf, optval, optlen)) {
+ goto mc_msf_out;
+ }
+ /* numsrc >= (4G-140)/128 overflow in 32 bits */
+ if (gsf->gf_numsrc >= 0x1ffffff ||
+ gsf->gf_numsrc > sysctl_igmp_max_msf) {
+ err = -ENOBUFS;
+ goto mc_msf_out;
+ }
+ if (GROUP_FILTER_SIZE(gsf->gf_numsrc) > optlen) {
+ err = -EINVAL;
+ goto mc_msf_out;
+ }
+ msize = IP_MSFILTER_SIZE(gsf->gf_numsrc);
+ msf = (struct ip_msfilter *)kmalloc(msize,GFP_KERNEL);
+ if (msf == 0) {
+ err = -ENOBUFS;
+ goto mc_msf_out;
+ }
+ ifindex = gsf->gf_interface;
+ psin = (struct sockaddr_in *)&gsf->gf_group;
+ if (psin->sin_family != AF_INET) {
+ err = -EADDRNOTAVAIL;
+ goto mc_msf_out;
+ }
+ msf->imsf_multiaddr = psin->sin_addr.s_addr;
+ msf->imsf_interface = 0;
+ msf->imsf_fmode = gsf->gf_fmode;
+ msf->imsf_numsrc = gsf->gf_numsrc;
+ err = -EADDRNOTAVAIL;
+ for (i=0; i<gsf->gf_numsrc; ++i) {
+ psin = (struct sockaddr_in *)&gsf->gf_slist[i];
+
+ if (psin->sin_family != AF_INET)
+ goto mc_msf_out;
+ msf->imsf_slist[i] = psin->sin_addr.s_addr;
+ }
+ kfree(gsf);
+ gsf = 0;
+
+ err = ip_mc_msfilter(sk, msf, ifindex);
+mc_msf_out:
+ if (msf)
+ kfree(msf);
+ if (gsf)
+ kfree(gsf);
+ break;
+ }
+ case IP_ROUTER_ALERT:
+ err = ip_ra_control(sk, val ? 1 : 0, NULL);
+ break;
+
+ case IP_FREEBIND:
+ if (optlen<1)
+ goto e_inval;
+ sk->protinfo.af_inet.freebind = !!val;
+ break;
+
+ default:
+#ifdef CONFIG_NETFILTER
+ err = nf_setsockopt(sk, PF_INET, optname, optval,
+ optlen);
+#else
+ err = -ENOPROTOOPT;
+#endif
+ break;
+ }
+ release_sock(sk);
+ return err;
+
+e_inval:
+ release_sock(sk);
+ return -EINVAL;
+}
+
+/*
+ * Get the options. Note for future reference. The GET of IP options gets the
+ * _received_ ones. The set sets the _sent_ ones.
+ */
+
+int ip_getsockopt(struct sock *sk, int level, int optname, char *optval, int *optlen)
+{
+ int val;
+ int len;
+
+ if(level!=SOL_IP)
+ return -EOPNOTSUPP;
+
+#ifdef CONFIG_IP_MROUTE
+ if(optname>=MRT_BASE && optname <=MRT_BASE+10)
+ {
+ return ip_mroute_getsockopt(sk,optname,optval,optlen);
+ }
+#endif
+
+ if(get_user(len,optlen))
+ return -EFAULT;
+ if(len < 0)
+ return -EINVAL;
+
+ lock_sock(sk);
+
+ switch(optname) {
+ case IP_OPTIONS:
+ {
+ unsigned char optbuf[sizeof(struct ip_options)+40];
+ struct ip_options * opt = (struct ip_options*)optbuf;
+ opt->optlen = 0;
+ if (sk->protinfo.af_inet.opt)
+ memcpy(optbuf, sk->protinfo.af_inet.opt,
+ sizeof(struct ip_options)+
+ sk->protinfo.af_inet.opt->optlen);
+ release_sock(sk);
+
+ if (opt->optlen == 0)
+ return put_user(0, optlen);
+
+ ip_options_undo(opt);
+
+ len = min_t(unsigned int, len, opt->optlen);
+ if(put_user(len, optlen))
+ return -EFAULT;
+ if(copy_to_user(optval, opt->__data, len))
+ return -EFAULT;
+ return 0;
+ }
+ case IP_PKTINFO:
+ val = (sk->protinfo.af_inet.cmsg_flags & IP_CMSG_PKTINFO) != 0;
+ break;
+ case IP_RECVTTL:
+ val = (sk->protinfo.af_inet.cmsg_flags & IP_CMSG_TTL) != 0;
+ break;
+ case IP_RECVTOS:
+ val = (sk->protinfo.af_inet.cmsg_flags & IP_CMSG_TOS) != 0;
+ break;
+ case IP_RECVOPTS:
+ val = (sk->protinfo.af_inet.cmsg_flags & IP_CMSG_RECVOPTS) != 0;
+ break;
+ case IP_RETOPTS:
+ val = (sk->protinfo.af_inet.cmsg_flags & IP_CMSG_RETOPTS) != 0;
+ break;
+ case IP_TOS:
+ val=sk->protinfo.af_inet.tos;
+ break;
+ case IP_TTL:
+ val=sk->protinfo.af_inet.ttl;
+ break;
+ case IP_HDRINCL:
+ val=sk->protinfo.af_inet.hdrincl;
+ break;
+ case IP_MTU_DISCOVER:
+ val=sk->protinfo.af_inet.pmtudisc;
+ break;
+ case IP_MTU:
+ {
+ struct dst_entry *dst;
+ val = 0;
+ dst = sk_dst_get(sk);
+ if (dst) {
+ val = dst->pmtu;
+ dst_release(dst);
+ }
+ if (!val) {
+ release_sock(sk);
+ return -ENOTCONN;
+ }
+ break;
+ }
+ case IP_RECVERR:
+ val=sk->protinfo.af_inet.recverr;
+ break;
+ case IP_MULTICAST_TTL:
+ val=sk->protinfo.af_inet.mc_ttl;
+ break;
+ case IP_MULTICAST_LOOP:
+ val=sk->protinfo.af_inet.mc_loop;
+ break;
+ case IP_MULTICAST_IF:
+ {
+ struct in_addr addr;
+ len = min_t(unsigned int, len, sizeof(struct in_addr));
+ addr.s_addr = sk->protinfo.af_inet.mc_addr;
+ release_sock(sk);
+
+ if(put_user(len, optlen))
+ return -EFAULT;
+ if(copy_to_user((void *)optval, &addr, len))
+ return -EFAULT;
+ return 0;
+ }
+ case IP_MSFILTER:
+ {
+ struct ip_msfilter msf;
+ int err;
+
+ if (len < IP_MSFILTER_SIZE(0)) {
+ release_sock(sk);
+ return -EINVAL;
+ }
+ if (copy_from_user(&msf, optval, IP_MSFILTER_SIZE(0))) {
+ release_sock(sk);
+ return -EFAULT;
+ }
+ err = ip_mc_msfget(sk, &msf,
+ (struct ip_msfilter *)optval, optlen);
+ release_sock(sk);
+ return err;
+ }
+ case MCAST_MSFILTER:
+ {
+ struct group_filter gsf;
+ int err;
+
+ if (len < GROUP_FILTER_SIZE(0)) {
+ release_sock(sk);
+ return -EINVAL;
+ }
+ if (copy_from_user(&gsf, optval, GROUP_FILTER_SIZE(0))) {
+ release_sock(sk);
+ return -EFAULT;
+ }
+ err = ip_mc_gsfget(sk, &gsf,
+ (struct group_filter *)optval, optlen);
+ release_sock(sk);
+ return err;
+ }
+ case IP_PKTOPTIONS:
+ {
+ struct msghdr msg;
+
+ release_sock(sk);
+
+ if (sk->type != SOCK_STREAM)
+ return -ENOPROTOOPT;
+
+ msg.msg_control = optval;
+ msg.msg_controllen = len;
+ msg.msg_flags = 0;
+
+ if (sk->protinfo.af_inet.cmsg_flags&IP_CMSG_PKTINFO) {
+ struct in_pktinfo info;
+
+ info.ipi_addr.s_addr = sk->rcv_saddr;
+ info.ipi_spec_dst.s_addr = sk->rcv_saddr;
+ info.ipi_ifindex = sk->protinfo.af_inet.mc_index;
+ put_cmsg(&msg, SOL_IP, IP_PKTINFO, sizeof(info), &info);
+ }
+ if (sk->protinfo.af_inet.cmsg_flags&IP_CMSG_TTL) {
+ int hlim = sk->protinfo.af_inet.mc_ttl;
+ put_cmsg(&msg, SOL_IP, IP_TTL, sizeof(hlim), &hlim);
+ }
+ len -= msg.msg_controllen;
+ return put_user(len, optlen);
+ }
+ case IP_FREEBIND:
+ val = sk->protinfo.af_inet.freebind;
+ break;
+ default:
+#ifdef CONFIG_NETFILTER
+ val = nf_getsockopt(sk, PF_INET, optname, optval,
+ &len);
+ release_sock(sk);
+ if (val >= 0)
+ val = put_user(len, optlen);
+ return val;
+#else
+ release_sock(sk);
+ return -ENOPROTOOPT;
+#endif
+ }
+ release_sock(sk);
+
+ if (len < sizeof(int) && len > 0 && val>=0 && val<255) {
+ unsigned char ucval = (unsigned char)val;
+ len = 1;
+ if(put_user(len, optlen))
+ return -EFAULT;
+ if(copy_to_user(optval,&ucval,1))
+ return -EFAULT;
+ } else {
+ len = min_t(unsigned int, sizeof(int), len);
+ if(put_user(len, optlen))
+ return -EFAULT;
+ if(copy_to_user(optval,&val,len))
+ return -EFAULT;
+ }
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipconfig.c b/uClinux-2.4.31-uc0/net/ipv4/ipconfig.c
new file mode 100644
index 0000000..957655b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipconfig.c
@@ -0,0 +1,1443 @@
+/*
+ * $Id: ipconfig.c,v 1.43.2.1 2001/12/13 10:39:53 davem Exp $
+ *
+ * Automatic Configuration of IP -- use DHCP, BOOTP, RARP, or
+ * user-supplied information to configure own IP address and routes.
+ *
+ * Copyright (C) 1996-1998 Martin Mares <mj@atrey.karlin.mff.cuni.cz>
+ *
+ * Derived from network configuration code in fs/nfs/nfsroot.c,
+ * originally Copyright (C) 1995, 1996 Gero Kuhlmann and me.
+ *
+ * BOOTP rewritten to construct and analyse packets itself instead
+ * of misusing the IP layer. num_bugs_causing_wrong_arp_replies--;
+ * -- MJ, December 1998
+ *
+ * Fixed ip_auto_config_setup calling at startup in the new "Linker Magic"
+ * initialization scheme.
+ * - Arnaldo Carvalho de Melo <acme@conectiva.com.br>, 08/11/1999
+ *
+ * DHCP support added. To users this looks like a whole separate
+ * protocol, but we know it's just a bag on the side of BOOTP.
+ * -- Chip Salzenberg <chip@valinux.com>, May 2000
+ *
+ * Ported DHCP support from 2.2.16 to 2.4.0-test4
+ * -- Eric Biederman <ebiederman@lnxi.com>, 30 Aug 2000
+ *
+ * Merged changes from 2.2.19 into 2.4.3
+ * -- Eric Biederman <ebiederman@lnxi.com>, 22 April Aug 2001
+ *
+ * Multipe Nameservers in /proc/net/pnp
+ * -- Josef Siemes <jsiemes@web.de>, Aug 2002
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/random.h>
+#include <linux/init.h>
+#include <linux/utsname.h>
+#include <linux/in.h>
+#include <linux/if.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/socket.h>
+#include <linux/route.h>
+#include <linux/udp.h>
+#include <linux/proc_fs.h>
+#include <linux/major.h>
+#include <net/arp.h>
+#include <net/ip.h>
+#include <net/ipconfig.h>
+
+#include <asm/uaccess.h>
+#include <asm/checksum.h>
+#include <asm/processor.h>
+
+/* Define this to allow debugging output */
+#undef IPCONFIG_DEBUG
+
+#ifdef IPCONFIG_DEBUG
+#define DBG(x) printk x
+#else
+#define DBG(x) do { } while(0)
+#endif
+
+#if defined(CONFIG_IP_PNP_DHCP)
+#define IPCONFIG_DHCP
+#endif
+#if defined(CONFIG_IP_PNP_BOOTP) || defined(CONFIG_IP_PNP_DHCP)
+#define IPCONFIG_BOOTP
+#endif
+#if defined(CONFIG_IP_PNP_RARP)
+#define IPCONFIG_RARP
+#endif
+#if defined(IPCONFIG_BOOTP) || defined(IPCONFIG_RARP)
+#define IPCONFIG_DYNAMIC
+#endif
+
+/* Define the friendly delay before and after opening net devices */
+#define CONF_PRE_OPEN (HZ/2) /* Before opening: 1/2 second */
+#define CONF_POST_OPEN (1*HZ) /* After opening: 1 second */
+
+/* Define the timeout for waiting for a DHCP/BOOTP/RARP reply */
+#define CONF_OPEN_RETRIES 2 /* (Re)open devices twice */
+#define CONF_SEND_RETRIES 6 /* Send six requests per open */
+#define CONF_INTER_TIMEOUT (HZ/2) /* Inter-device timeout: 1/2 second */
+#define CONF_BASE_TIMEOUT (HZ*2) /* Initial timeout: 2 seconds */
+#define CONF_TIMEOUT_RANDOM (HZ) /* Maximum amount of randomization */
+#define CONF_TIMEOUT_MULT *7/4 /* Rate of timeout growth */
+#define CONF_TIMEOUT_MAX (HZ*30) /* Maximum allowed timeout */
+#define CONF_NAMESERVERS_MAX 3 /* Maximum number of nameservers
+ - '3' from resolv.h */
+
+
+/*
+ * Public IP configuration
+ */
+
+/* This is used by platforms which might be able to set the ipconfig
+ * variables using firmware environment vars. If this is set, it will
+ * ignore such firmware variables.
+ */
+int ic_set_manually __initdata = 0; /* IPconfig parameters set manually */
+
+int ic_enable __initdata = 1; /* IP config enabled? */
+
+/* Protocol choice */
+int ic_proto_enabled __initdata = 0
+#ifdef IPCONFIG_BOOTP
+ | IC_BOOTP
+#endif
+#ifdef CONFIG_IP_PNP_DHCP
+ | IC_USE_DHCP
+#endif
+#ifdef IPCONFIG_RARP
+ | IC_RARP
+#endif
+ ;
+
+int ic_host_name_set __initdata = 0; /* Host name set by us? */
+
+u32 ic_myaddr = INADDR_NONE; /* My IP address */
+u32 ic_netmask = INADDR_NONE; /* Netmask for local subnet */
+u32 ic_gateway = INADDR_NONE; /* Gateway IP address */
+
+u32 ic_servaddr = INADDR_NONE; /* Boot server IP address */
+
+u32 root_server_addr = INADDR_NONE; /* Address of NFS server */
+u8 root_server_path[256] = { 0, }; /* Path to mount as root */
+
+/* Persistent data: */
+
+int ic_proto_used; /* Protocol used, if any */
+u32 ic_nameservers[CONF_NAMESERVERS_MAX]; /* DNS Server IP addresses */
+u8 ic_domain[64]; /* DNS (not NIS) domain name */
+
+/*
+ * Private state.
+ */
+
+/* Name of user-selected boot device */
+static char user_dev_name[IFNAMSIZ] __initdata = { 0, };
+
+/* Protocols supported by available interfaces */
+static int ic_proto_have_if __initdata = 0;
+
+#ifdef IPCONFIG_DYNAMIC
+static spinlock_t ic_recv_lock = SPIN_LOCK_UNLOCKED;
+static volatile int ic_got_reply __initdata = 0; /* Proto(s) that replied */
+#endif
+#ifdef IPCONFIG_DHCP
+static int ic_dhcp_msgtype __initdata = 0; /* DHCP msg type received */
+#endif
+
+
+/*
+ * Network devices
+ */
+
+struct ic_device {
+ struct ic_device *next;
+ struct net_device *dev;
+ unsigned short flags;
+ short able;
+ u32 xid;
+};
+
+static struct ic_device *ic_first_dev __initdata = NULL;/* List of open device */
+static struct net_device *ic_dev __initdata = NULL; /* Selected device */
+
+static int __init ic_open_devs(void)
+{
+ struct ic_device *d, **last;
+ struct net_device *dev;
+ unsigned short oflags;
+
+ last = &ic_first_dev;
+ rtnl_shlock();
+ for (dev = dev_base; dev; dev = dev->next) {
+ if (user_dev_name[0] ? !strcmp(dev->name, user_dev_name) :
+ (!(dev->flags & IFF_LOOPBACK) &&
+ (dev->flags & (IFF_POINTOPOINT|IFF_BROADCAST)) &&
+ strncmp(dev->name, "dummy", 5))) {
+ int able = 0;
+ if (dev->mtu >= 364)
+ able |= IC_BOOTP;
+ else
+ printk(KERN_WARNING "DHCP/BOOTP: Ignoring device %s, MTU %d too small", dev->name, dev->mtu);
+ if (!(dev->flags & IFF_NOARP))
+ able |= IC_RARP;
+ able &= ic_proto_enabled;
+ if (ic_proto_enabled && !able)
+ continue;
+ oflags = dev->flags;
+ if (dev_change_flags(dev, oflags | IFF_UP) < 0) {
+ printk(KERN_ERR "IP-Config: Failed to open %s\n", dev->name);
+ continue;
+ }
+ if (!(d = kmalloc(sizeof(struct ic_device), GFP_KERNEL))) {
+ rtnl_shunlock();
+ return -1;
+ }
+ d->dev = dev;
+ *last = d;
+ last = &d->next;
+ d->flags = oflags;
+ d->able = able;
+ if (able & IC_BOOTP)
+ get_random_bytes(&d->xid, sizeof(u32));
+ else
+ d->xid = 0;
+ ic_proto_have_if |= able;
+ DBG(("IP-Config: %s UP (able=%d, xid=%08x)\n",
+ dev->name, able, d->xid));
+ }
+ }
+ rtnl_shunlock();
+
+ *last = NULL;
+
+ if (!ic_first_dev) {
+ if (user_dev_name[0])
+ printk(KERN_ERR "IP-Config: Device `%s' not found.\n", user_dev_name);
+ else
+ printk(KERN_ERR "IP-Config: No network devices available.\n");
+ return -1;
+ }
+ return 0;
+}
+
+static void __init ic_close_devs(void)
+{
+ struct ic_device *d, *next;
+ struct net_device *dev;
+
+ rtnl_shlock();
+ next = ic_first_dev;
+ while ((d = next)) {
+ next = d->next;
+ dev = d->dev;
+ if (dev != ic_dev) {
+ DBG(("IP-Config: Downing %s\n", dev->name));
+ dev_change_flags(dev, d->flags);
+ }
+ kfree(d);
+ }
+ rtnl_shunlock();
+}
+
+/*
+ * Interface to various network functions.
+ */
+
+static inline void
+set_sockaddr(struct sockaddr_in *sin, u32 addr, u16 port)
+{
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = addr;
+ sin->sin_port = port;
+}
+
+static int __init ic_dev_ioctl(unsigned int cmd, struct ifreq *arg)
+{
+ int res;
+
+ mm_segment_t oldfs = get_fs();
+ set_fs(get_ds());
+ res = devinet_ioctl(cmd, arg);
+ set_fs(oldfs);
+ return res;
+}
+
+static int __init ic_route_ioctl(unsigned int cmd, struct rtentry *arg)
+{
+ int res;
+
+ mm_segment_t oldfs = get_fs();
+ set_fs(get_ds());
+ res = ip_rt_ioctl(cmd, arg);
+ set_fs(oldfs);
+ return res;
+}
+
+/*
+ * Set up interface addresses and routes.
+ */
+
+static int __init ic_setup_if(void)
+{
+ struct ifreq ir;
+ struct sockaddr_in *sin = (void *) &ir.ifr_ifru.ifru_addr;
+ int err;
+
+ memset(&ir, 0, sizeof(ir));
+ strcpy(ir.ifr_ifrn.ifrn_name, ic_dev->name);
+ set_sockaddr(sin, ic_myaddr, 0);
+ if ((err = ic_dev_ioctl(SIOCSIFADDR, &ir)) < 0) {
+ printk(KERN_ERR "IP-Config: Unable to set interface address (%d).\n", err);
+ return -1;
+ }
+ set_sockaddr(sin, ic_netmask, 0);
+ if ((err = ic_dev_ioctl(SIOCSIFNETMASK, &ir)) < 0) {
+ printk(KERN_ERR "IP-Config: Unable to set interface netmask (%d).\n", err);
+ return -1;
+ }
+ set_sockaddr(sin, ic_myaddr | ~ic_netmask, 0);
+ if ((err = ic_dev_ioctl(SIOCSIFBRDADDR, &ir)) < 0) {
+ printk(KERN_ERR "IP-Config: Unable to set interface broadcast address (%d).\n", err);
+ return -1;
+ }
+ return 0;
+}
+
+static int __init ic_setup_routes(void)
+{
+ /* No need to setup device routes, only the default route... */
+
+ if (ic_gateway != INADDR_NONE) {
+ struct rtentry rm;
+ int err;
+
+ memset(&rm, 0, sizeof(rm));
+ if ((ic_gateway ^ ic_myaddr) & ic_netmask) {
+ printk(KERN_ERR "IP-Config: Gateway not on directly connected network.\n");
+ return -1;
+ }
+ set_sockaddr((struct sockaddr_in *) &rm.rt_dst, 0, 0);
+ set_sockaddr((struct sockaddr_in *) &rm.rt_genmask, 0, 0);
+ set_sockaddr((struct sockaddr_in *) &rm.rt_gateway, ic_gateway, 0);
+ rm.rt_flags = RTF_UP | RTF_GATEWAY;
+ if ((err = ic_route_ioctl(SIOCADDRT, &rm)) < 0) {
+ printk(KERN_ERR "IP-Config: Cannot add default route (%d).\n", err);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Fill in default values for all missing parameters.
+ */
+
+static int __init ic_defaults(void)
+{
+ /*
+ * At this point we have no userspace running so need not
+ * claim locks on system_utsname
+ */
+
+ if (!ic_host_name_set)
+ sprintf(system_utsname.nodename, "%u.%u.%u.%u", NIPQUAD(ic_myaddr));
+
+ if (root_server_addr == INADDR_NONE)
+ root_server_addr = ic_servaddr;
+
+ if (ic_netmask == INADDR_NONE) {
+ if (IN_CLASSA(ntohl(ic_myaddr)))
+ ic_netmask = htonl(IN_CLASSA_NET);
+ else if (IN_CLASSB(ntohl(ic_myaddr)))
+ ic_netmask = htonl(IN_CLASSB_NET);
+ else if (IN_CLASSC(ntohl(ic_myaddr)))
+ ic_netmask = htonl(IN_CLASSC_NET);
+ else {
+ printk(KERN_ERR "IP-Config: Unable to guess netmask for address %u.%u.%u.%u\n",
+ NIPQUAD(ic_myaddr));
+ return -1;
+ }
+ printk("IP-Config: Guessing netmask %u.%u.%u.%u\n", NIPQUAD(ic_netmask));
+ }
+
+ return 0;
+}
+
+/*
+ * RARP support.
+ */
+
+#ifdef IPCONFIG_RARP
+
+static int ic_rarp_recv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt);
+
+static struct packet_type rarp_packet_type __initdata = {
+ type: __constant_htons(ETH_P_RARP),
+ func: ic_rarp_recv,
+};
+
+static inline void ic_rarp_init(void)
+{
+ dev_add_pack(&rarp_packet_type);
+}
+
+static inline void ic_rarp_cleanup(void)
+{
+ dev_remove_pack(&rarp_packet_type);
+}
+
+/*
+ * Process received RARP packet.
+ */
+static int __init
+ic_rarp_recv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
+{
+ struct arphdr *rarp = (struct arphdr *)skb->h.raw;
+ unsigned char *rarp_ptr = (unsigned char *) (rarp + 1);
+ unsigned long sip, tip;
+ unsigned char *sha, *tha; /* s for "source", t for "target" */
+ struct ic_device *d;
+
+ /* One reply at a time, please. */
+ spin_lock(&ic_recv_lock);
+
+ /* If we already have a reply, just drop the packet */
+ if (ic_got_reply)
+ goto drop;
+
+ /* Find the ic_device that the packet arrived on */
+ d = ic_first_dev;
+ while (d && d->dev != dev)
+ d = d->next;
+ if (!d)
+ goto drop; /* should never happen */
+
+ /* If this test doesn't pass, it's not IP, or we should ignore it anyway */
+ if (rarp->ar_hln != dev->addr_len || dev->type != ntohs(rarp->ar_hrd))
+ goto drop;
+
+ /* If it's not a RARP reply, delete it. */
+ if (rarp->ar_op != htons(ARPOP_RREPLY))
+ goto drop;
+
+ /* If it's not Ethernet, delete it. */
+ if (rarp->ar_pro != htons(ETH_P_IP))
+ goto drop;
+
+ /* Extract variable-width fields */
+ sha = rarp_ptr;
+ rarp_ptr += dev->addr_len;
+ memcpy(&sip, rarp_ptr, 4);
+ rarp_ptr += 4;
+ tha = rarp_ptr;
+ rarp_ptr += dev->addr_len;
+ memcpy(&tip, rarp_ptr, 4);
+
+ /* Discard packets which are not meant for us. */
+ if (memcmp(tha, dev->dev_addr, dev->addr_len))
+ goto drop;
+
+ /* Discard packets which are not from specified server. */
+ if (ic_servaddr != INADDR_NONE && ic_servaddr != sip)
+ goto drop;
+
+ /* We have a winner! */
+ ic_dev = dev;
+ if (ic_myaddr == INADDR_NONE)
+ ic_myaddr = tip;
+ ic_servaddr = sip;
+ ic_got_reply = IC_RARP;
+
+drop:
+ /* Show's over. Nothing to see here. */
+ spin_unlock(&ic_recv_lock);
+
+ /* Throw the packet out. */
+ kfree_skb(skb);
+ return 0;
+}
+
+
+/*
+ * Send RARP request packet over a single interface.
+ */
+static void __init ic_rarp_send_if(struct ic_device *d)
+{
+ struct net_device *dev = d->dev;
+ arp_send(ARPOP_RREQUEST, ETH_P_RARP, 0, dev, 0, NULL,
+ dev->dev_addr, dev->dev_addr);
+}
+#endif
+
+/*
+ * DHCP/BOOTP support.
+ */
+
+#ifdef IPCONFIG_BOOTP
+
+struct bootp_pkt { /* BOOTP packet format */
+ struct iphdr iph; /* IP header */
+ struct udphdr udph; /* UDP header */
+ u8 op; /* 1=request, 2=reply */
+ u8 htype; /* HW address type */
+ u8 hlen; /* HW address length */
+ u8 hops; /* Used only by gateways */
+ u32 xid; /* Transaction ID */
+ u16 secs; /* Seconds since we started */
+ u16 flags; /* Just what it says */
+ u32 client_ip; /* Client's IP address if known */
+ u32 your_ip; /* Assigned IP address */
+ u32 server_ip; /* (Next, e.g. NFS) Server's IP address */
+ u32 relay_ip; /* IP address of BOOTP relay */
+ u8 hw_addr[16]; /* Client's HW address */
+ u8 serv_name[64]; /* Server host name */
+ u8 boot_file[128]; /* Name of boot file */
+ u8 exten[312]; /* DHCP options / BOOTP vendor extensions */
+};
+
+/* packet ops */
+#define BOOTP_REQUEST 1
+#define BOOTP_REPLY 2
+
+/* DHCP message types */
+#define DHCPDISCOVER 1
+#define DHCPOFFER 2
+#define DHCPREQUEST 3
+#define DHCPDECLINE 4
+#define DHCPACK 5
+#define DHCPNAK 6
+#define DHCPRELEASE 7
+#define DHCPINFORM 8
+
+static int ic_bootp_recv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt);
+
+static struct packet_type bootp_packet_type __initdata = {
+ type: __constant_htons(ETH_P_IP),
+ func: ic_bootp_recv,
+};
+
+
+/*
+ * Initialize DHCP/BOOTP extension fields in the request.
+ */
+
+static const u8 ic_bootp_cookie[4] = { 99, 130, 83, 99 };
+
+#ifdef IPCONFIG_DHCP
+
+static void __init
+ic_dhcp_init_options(u8 *options)
+{
+ u8 mt = ((ic_servaddr == INADDR_NONE)
+ ? DHCPDISCOVER : DHCPREQUEST);
+ u8 *e = options;
+
+#ifdef IPCONFIG_DEBUG
+ printk("DHCP: Sending message type %d\n", mt);
+#endif
+
+ memcpy(e, ic_bootp_cookie, 4); /* RFC1048 Magic Cookie */
+ e += 4;
+
+ *e++ = 53; /* DHCP message type */
+ *e++ = 1;
+ *e++ = mt;
+
+ if (mt == DHCPREQUEST) {
+ *e++ = 54; /* Server ID (IP address) */
+ *e++ = 4;
+ memcpy(e, &ic_servaddr, 4);
+ e += 4;
+
+ *e++ = 50; /* Requested IP address */
+ *e++ = 4;
+ memcpy(e, &ic_myaddr, 4);
+ e += 4;
+ }
+
+ /* always? */
+ {
+ static const u8 ic_req_params[] = {
+ 1, /* Subnet mask */
+ 3, /* Default gateway */
+ 6, /* DNS server */
+ 12, /* Host name */
+ 15, /* Domain name */
+ 17, /* Boot path */
+ 40, /* NIS domain name */
+ };
+
+ *e++ = 55; /* Parameter request list */
+ *e++ = sizeof(ic_req_params);
+ memcpy(e, ic_req_params, sizeof(ic_req_params));
+ e += sizeof(ic_req_params);
+ }
+
+ *e++ = 255; /* End of the list */
+}
+
+#endif /* IPCONFIG_DHCP */
+
+static void __init ic_bootp_init_ext(u8 *e)
+{
+ memcpy(e, ic_bootp_cookie, 4); /* RFC1048 Magic Cookie */
+ e += 4;
+ *e++ = 1; /* Subnet mask request */
+ *e++ = 4;
+ e += 4;
+ *e++ = 3; /* Default gateway request */
+ *e++ = 4;
+ e += 4;
+ *e++ = 5; /* Name server reqeust */
+ *e++ = 8;
+ e += 8;
+ *e++ = 12; /* Host name request */
+ *e++ = 32;
+ e += 32;
+ *e++ = 40; /* NIS Domain name request */
+ *e++ = 32;
+ e += 32;
+ *e++ = 17; /* Boot path */
+ *e++ = 40;
+ e += 40;
+
+ *e++ = 57; /* set extension buffer size for reply */
+ *e++ = 2;
+ *e++ = 1; /* 128+236+8+20+14, see dhcpd sources */
+ *e++ = 150;
+
+ *e++ = 255; /* End of the list */
+}
+
+
+/*
+ * Initialize the DHCP/BOOTP mechanism.
+ */
+static inline void ic_bootp_init(void)
+{
+ int i;
+
+ for (i = 0; i < CONF_NAMESERVERS_MAX; i++)
+ ic_nameservers[i] = INADDR_NONE;
+
+ dev_add_pack(&bootp_packet_type);
+}
+
+
+/*
+ * DHCP/BOOTP cleanup.
+ */
+static inline void ic_bootp_cleanup(void)
+{
+ dev_remove_pack(&bootp_packet_type);
+}
+
+
+/*
+ * Send DHCP/BOOTP request to single interface.
+ */
+static void __init ic_bootp_send_if(struct ic_device *d, unsigned long jiffies_diff)
+{
+ struct net_device *dev = d->dev;
+ struct sk_buff *skb;
+ struct bootp_pkt *b;
+ int hh_len = (dev->hard_header_len + 15) & ~15;
+ struct iphdr *h;
+
+ /* Allocate packet */
+ skb = alloc_skb(sizeof(struct bootp_pkt) + hh_len + 15, GFP_KERNEL);
+ if (!skb)
+ return;
+ skb_reserve(skb, hh_len);
+ b = (struct bootp_pkt *) skb_put(skb, sizeof(struct bootp_pkt));
+ memset(b, 0, sizeof(struct bootp_pkt));
+
+ /* Construct IP header */
+ skb->nh.iph = h = &b->iph;
+ h->version = 4;
+ h->ihl = 5;
+ h->tot_len = htons(sizeof(struct bootp_pkt));
+ h->frag_off = htons(IP_DF);
+ h->ttl = 64;
+ h->protocol = IPPROTO_UDP;
+ h->daddr = INADDR_BROADCAST;
+ h->check = ip_fast_csum((unsigned char *) h, h->ihl);
+
+ /* Construct UDP header */
+ b->udph.source = htons(68);
+ b->udph.dest = htons(67);
+ b->udph.len = htons(sizeof(struct bootp_pkt) - sizeof(struct iphdr));
+ /* UDP checksum not calculated -- explicitly allowed in BOOTP RFC */
+
+ /* Construct DHCP/BOOTP header */
+ b->op = BOOTP_REQUEST;
+ if (dev->type < 256) /* check for false types */
+ b->htype = dev->type;
+ else if (dev->type == ARPHRD_IEEE802_TR) /* fix for token ring */
+ b->htype = ARPHRD_IEEE802;
+ else if (dev->type == ARPHRD_FDDI)
+ b->htype = ARPHRD_ETHER;
+ else {
+ printk("Unknown ARP type 0x%04x for device %s\n", dev->type, dev->name);
+ b->htype = dev->type; /* can cause undefined behavior */
+ }
+ b->hlen = dev->addr_len;
+ b->your_ip = INADDR_NONE;
+ b->server_ip = INADDR_NONE;
+ memcpy(b->hw_addr, dev->dev_addr, dev->addr_len);
+ b->secs = htons(jiffies_diff / HZ);
+ b->xid = d->xid;
+
+ /* add DHCP options or BOOTP extensions */
+#ifdef IPCONFIG_DHCP
+ if (ic_proto_enabled & IC_USE_DHCP)
+ ic_dhcp_init_options(b->exten);
+ else
+#endif
+ ic_bootp_init_ext(b->exten);
+
+ /* Chain packet down the line... */
+ skb->dev = dev;
+ skb->protocol = htons(ETH_P_IP);
+ if ((dev->hard_header &&
+ dev->hard_header(skb, dev, ntohs(skb->protocol), dev->broadcast, dev->dev_addr, skb->len) < 0) ||
+ dev_queue_xmit(skb) < 0)
+ printk("E");
+}
+
+
+/*
+ * Copy BOOTP-supplied string if not already set.
+ */
+static int __init ic_bootp_string(char *dest, char *src, int len, int max)
+{
+ if (!len)
+ return 0;
+ if (len > max-1)
+ len = max-1;
+ memcpy(dest, src, len);
+ dest[len] = '\0';
+ return 1;
+}
+
+
+/*
+ * Process BOOTP extensions.
+ */
+static void __init ic_do_bootp_ext(u8 *ext)
+{
+ u8 servers;
+ int i;
+
+#ifdef IPCONFIG_DEBUG
+ u8 *c;
+
+ printk("DHCP/BOOTP: Got extension %d:",*ext);
+ for(c=ext+2; c<ext+2+ext[1]; c++)
+ printk(" %02x", *c);
+ printk("\n");
+#endif
+
+ switch (*ext++) {
+ case 1: /* Subnet mask */
+ if (ic_netmask == INADDR_NONE)
+ memcpy(&ic_netmask, ext+1, 4);
+ break;
+ case 3: /* Default gateway */
+ if (ic_gateway == INADDR_NONE)
+ memcpy(&ic_gateway, ext+1, 4);
+ break;
+ case 6: /* DNS server */
+ servers= *ext/4;
+ if (servers > CONF_NAMESERVERS_MAX)
+ servers = CONF_NAMESERVERS_MAX;
+ for (i = 0; i < servers; i++) {
+ if (ic_nameservers[i] == INADDR_NONE)
+ memcpy(&ic_nameservers[i], ext+1+4*i, 4);
+ }
+ break;
+ case 12: /* Host name */
+ ic_bootp_string(system_utsname.nodename, ext+1, *ext, __NEW_UTS_LEN);
+ ic_host_name_set = 1;
+ break;
+ case 15: /* Domain name (DNS) */
+ ic_bootp_string(ic_domain, ext+1, *ext, sizeof(ic_domain));
+ break;
+ case 17: /* Root path */
+ if (!root_server_path[0])
+ ic_bootp_string(root_server_path, ext+1, *ext, sizeof(root_server_path));
+ break;
+ case 40: /* NIS Domain name (_not_ DNS) */
+ ic_bootp_string(system_utsname.domainname, ext+1, *ext, __NEW_UTS_LEN);
+ break;
+ }
+}
+
+
+/*
+ * Receive BOOTP reply.
+ */
+static int __init ic_bootp_recv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
+{
+ struct bootp_pkt *b = (struct bootp_pkt *) skb->nh.iph;
+ struct iphdr *h = &b->iph;
+ struct ic_device *d;
+ int len;
+
+ /* One reply at a time, please. */
+ spin_lock(&ic_recv_lock);
+
+ /* If we already have a reply, just drop the packet */
+ if (ic_got_reply)
+ goto drop;
+
+ /* Find the ic_device that the packet arrived on */
+ d = ic_first_dev;
+ while (d && d->dev != dev)
+ d = d->next;
+ if (!d)
+ goto drop; /* should never happen */
+
+ /* Check whether it's a BOOTP packet */
+ if (skb->pkt_type == PACKET_OTHERHOST ||
+ skb->len < sizeof(struct udphdr) + sizeof(struct iphdr) ||
+ h->ihl != 5 ||
+ h->version != 4 ||
+ ip_fast_csum((char *) h, h->ihl) != 0 ||
+ skb->len < ntohs(h->tot_len) ||
+ h->protocol != IPPROTO_UDP ||
+ b->udph.source != htons(67) ||
+ b->udph.dest != htons(68) ||
+ ntohs(h->tot_len) < ntohs(b->udph.len) + sizeof(struct iphdr))
+ goto drop;
+
+ /* Fragments are not supported */
+ if (h->frag_off & htons(IP_OFFSET | IP_MF)) {
+ printk(KERN_ERR "DHCP/BOOTP: Ignoring fragmented reply.\n");
+ goto drop;
+ }
+
+ /* Is it a reply to our BOOTP request? */
+ len = ntohs(b->udph.len) - sizeof(struct udphdr);
+ if (len < 300 || /* See RFC 951:2.1 */
+ b->op != BOOTP_REPLY ||
+ b->xid != d->xid) {
+ printk("?");
+ goto drop;
+ }
+
+ /* Parse extensions */
+ if (!memcmp(b->exten, ic_bootp_cookie, 4)) { /* Check magic cookie */
+ u8 *end = (u8 *) b + ntohs(b->iph.tot_len);
+ u8 *ext;
+
+#ifdef IPCONFIG_DHCP
+ if (ic_proto_enabled & IC_USE_DHCP) {
+ u32 server_id = INADDR_NONE;
+ int mt = 0;
+
+ ext = &b->exten[4];
+ while (ext < end && *ext != 0xff) {
+ u8 *opt = ext++;
+ if (*opt == 0) /* Padding */
+ continue;
+ ext += *ext + 1;
+ if (ext >= end)
+ break;
+ switch (*opt) {
+ case 53: /* Message type */
+ if (opt[1])
+ mt = opt[2];
+ break;
+ case 54: /* Server ID (IP address) */
+ if (opt[1] >= 4)
+ memcpy(&server_id, opt + 2, 4);
+ break;
+ };
+ }
+
+#ifdef IPCONFIG_DEBUG
+ printk("DHCP: Got message type %d\n", mt);
+#endif
+
+ switch (mt) {
+ case DHCPOFFER:
+ /* While in the process of accepting one offer,
+ * ignore all others.
+ */
+ if (ic_myaddr != INADDR_NONE)
+ goto drop;
+
+ /* Let's accept that offer. */
+ ic_myaddr = b->your_ip;
+ ic_servaddr = server_id;
+#ifdef IPCONFIG_DEBUG
+ printk("DHCP: Offered address %u.%u.%u.%u",
+ NIPQUAD(ic_myaddr));
+ printk(" by server %u.%u.%u.%u\n",
+ NIPQUAD(ic_servaddr));
+#endif
+ /* The DHCP indicated server address takes
+ * precedence over the bootp header one if
+ * they are different.
+ */
+ if ((server_id != INADDR_NONE) &&
+ (b->server_ip != server_id))
+ b->server_ip = ic_servaddr;
+ break;
+
+ case DHCPACK:
+ if (memcmp(dev->dev_addr, b->hw_addr, dev->addr_len) != 0)
+ goto drop;
+
+ /* Yeah! */
+ break;
+
+ default:
+ /* Urque. Forget it*/
+ ic_myaddr = INADDR_NONE;
+ ic_servaddr = INADDR_NONE;
+ goto drop;
+ };
+
+ ic_dhcp_msgtype = mt;
+
+ }
+#endif /* IPCONFIG_DHCP */
+
+ ext = &b->exten[4];
+ while (ext < end && *ext != 0xff) {
+ u8 *opt = ext++;
+ if (*opt == 0) /* Padding */
+ continue;
+ ext += *ext + 1;
+ if (ext < end)
+ ic_do_bootp_ext(opt);
+ }
+ }
+
+ /* We have a winner! */
+ ic_dev = dev;
+ ic_myaddr = b->your_ip;
+ ic_servaddr = b->server_ip;
+ if (ic_gateway == INADDR_NONE && b->relay_ip)
+ ic_gateway = b->relay_ip;
+ if (ic_nameservers[0] == INADDR_NONE)
+ ic_nameservers[0] = ic_servaddr;
+ ic_got_reply = IC_BOOTP;
+
+drop:
+ /* Show's over. Nothing to see here. */
+ spin_unlock(&ic_recv_lock);
+
+ /* Throw the packet out. */
+ kfree_skb(skb);
+
+ return 0;
+}
+
+
+#endif
+
+
+/*
+ * Dynamic IP configuration -- DHCP, BOOTP, RARP.
+ */
+
+#ifdef IPCONFIG_DYNAMIC
+
+static int __init ic_dynamic(void)
+{
+ int retries;
+ struct ic_device *d;
+ unsigned long start_jiffies, timeout, jiff;
+ int do_bootp = ic_proto_have_if & IC_BOOTP;
+ int do_rarp = ic_proto_have_if & IC_RARP;
+
+ /*
+ * If none of DHCP/BOOTP/RARP was selected, return with an error.
+ * This routine gets only called when some pieces of information
+ * are missing, and without DHCP/BOOTP/RARP we are unable to get it.
+ */
+ if (!ic_proto_enabled) {
+ printk(KERN_ERR "IP-Config: Incomplete network configuration information.\n");
+ return -1;
+ }
+
+#ifdef IPCONFIG_BOOTP
+ if ((ic_proto_enabled ^ ic_proto_have_if) & IC_BOOTP)
+ printk(KERN_ERR "DHCP/BOOTP: No suitable device found.\n");
+#endif
+#ifdef IPCONFIG_RARP
+ if ((ic_proto_enabled ^ ic_proto_have_if) & IC_RARP)
+ printk(KERN_ERR "RARP: No suitable device found.\n");
+#endif
+
+ if (!ic_proto_have_if)
+ /* Error message already printed */
+ return -1;
+
+ /*
+ * Setup protocols
+ */
+#ifdef IPCONFIG_BOOTP
+ if (do_bootp)
+ ic_bootp_init();
+#endif
+#ifdef IPCONFIG_RARP
+ if (do_rarp)
+ ic_rarp_init();
+#endif
+
+ /*
+ * Send requests and wait, until we get an answer. This loop
+ * seems to be a terrible waste of CPU time, but actually there is
+ * only one process running at all, so we don't need to use any
+ * scheduler functions.
+ * [Actually we could now, but the nothing else running note still
+ * applies.. - AC]
+ */
+ printk(KERN_NOTICE "Sending %s%s%s requests .",
+ do_bootp
+ ? ((ic_proto_enabled & IC_USE_DHCP) ? "DHCP" : "BOOTP") : "",
+ (do_bootp && do_rarp) ? " and " : "",
+ do_rarp ? "RARP" : "");
+
+ start_jiffies = jiffies;
+ d = ic_first_dev;
+ retries = CONF_SEND_RETRIES;
+ get_random_bytes(&timeout, sizeof(timeout));
+ timeout = CONF_BASE_TIMEOUT + (timeout % (unsigned) CONF_TIMEOUT_RANDOM);
+ for(;;) {
+#ifdef IPCONFIG_BOOTP
+ if (do_bootp && (d->able & IC_BOOTP))
+ ic_bootp_send_if(d, jiffies - start_jiffies);
+#endif
+#ifdef IPCONFIG_RARP
+ if (do_rarp && (d->able & IC_RARP))
+ ic_rarp_send_if(d);
+#endif
+
+ jiff = jiffies + (d->next ? CONF_INTER_TIMEOUT : timeout);
+ while (time_before(jiffies, jiff) && !ic_got_reply) {
+ barrier();
+ cpu_relax();
+ }
+#ifdef IPCONFIG_DHCP
+ /* DHCP isn't done until we get a DHCPACK. */
+ if ((ic_got_reply & IC_BOOTP)
+ && (ic_proto_enabled & IC_USE_DHCP)
+ && ic_dhcp_msgtype != DHCPACK)
+ {
+ ic_got_reply = 0;
+ printk(",");
+ continue;
+ }
+#endif /* IPCONFIG_DHCP */
+
+ if (ic_got_reply) {
+ printk(" OK\n");
+ break;
+ }
+
+ if ((d = d->next))
+ continue;
+
+ if (! --retries) {
+ printk(" timed out!\n");
+ break;
+ }
+
+ d = ic_first_dev;
+
+ timeout = timeout CONF_TIMEOUT_MULT;
+ if (timeout > CONF_TIMEOUT_MAX)
+ timeout = CONF_TIMEOUT_MAX;
+
+ printk(".");
+ }
+
+#ifdef IPCONFIG_BOOTP
+ if (do_bootp)
+ ic_bootp_cleanup();
+#endif
+#ifdef IPCONFIG_RARP
+ if (do_rarp)
+ ic_rarp_cleanup();
+#endif
+
+ if (!ic_got_reply)
+ return -1;
+
+ printk("IP-Config: Got %s answer from %u.%u.%u.%u, ",
+ ((ic_got_reply & IC_RARP) ? "RARP"
+ : (ic_proto_enabled & IC_USE_DHCP) ? "DHCP" : "BOOTP"),
+ NIPQUAD(ic_servaddr));
+ printk("my address is %u.%u.%u.%u\n", NIPQUAD(ic_myaddr));
+
+ return 0;
+}
+
+#endif /* IPCONFIG_DYNAMIC */
+
+#ifdef CONFIG_PROC_FS
+
+static int pnp_get_info(char *buffer, char **start,
+ off_t offset, int length)
+{
+ int len;
+ int i;
+
+ if (ic_proto_used & IC_PROTO)
+ sprintf(buffer, "#PROTO: %s\n",
+ (ic_proto_used & IC_RARP) ? "RARP"
+ : (ic_proto_used & IC_USE_DHCP) ? "DHCP" : "BOOTP");
+ else
+ strcpy(buffer, "#MANUAL\n");
+ len = strlen(buffer);
+
+ if (ic_domain[0])
+ len += sprintf(buffer + len,
+ "domain %s\n", ic_domain);
+ for (i = 0; i < CONF_NAMESERVERS_MAX; i++) {
+ if (ic_nameservers[i] != INADDR_NONE)
+ len += sprintf(buffer + len,
+ "nameserver %u.%u.%u.%u\n",
+ NIPQUAD(ic_nameservers[i]));
+ }
+ if (ic_servaddr != INADDR_NONE)
+ len += sprintf(buffer + len,
+ "bootserver %u.%u.%u.%u\n",
+ NIPQUAD(ic_servaddr));
+
+ if (offset > len)
+ offset = len;
+ *start = buffer + offset;
+
+ if (offset + length > len)
+ length = len - offset;
+ return length;
+}
+
+#endif /* CONFIG_PROC_FS */
+
+/*
+ * Extract IP address from the parameter string if needed. Note that we
+ * need to have root_server_addr set _before_ IPConfig gets called as it
+ * can override it.
+ */
+u32 __init root_nfs_parse_addr(char *name)
+{
+ u32 addr;
+ int octets = 0;
+ char *cp, *cq;
+
+ cp = cq = name;
+ while (octets < 4) {
+ while (*cp >= '0' && *cp <= '9')
+ cp++;
+ if (cp == cq || cp - cq > 3)
+ break;
+ if (*cp == '.' || octets == 3)
+ octets++;
+ if (octets < 4)
+ cp++;
+ cq = cp;
+ }
+ if (octets == 4 && (*cp == ':' || *cp == '\0')) {
+ if (*cp == ':')
+ *cp++ = '\0';
+ addr = in_aton(name);
+ memmove(name, cp, strlen(cp) + 1);
+ } else
+ addr = INADDR_NONE;
+
+ return addr;
+}
+
+/*
+ * IP Autoconfig dispatcher.
+ */
+
+static int __init ip_auto_config(void)
+{
+ unsigned long jiff;
+ u32 addr;
+#ifdef IPCONFIG_DYNAMIC
+ int retries = CONF_OPEN_RETRIES;
+#endif
+
+#ifdef CONFIG_PROC_FS
+ proc_net_create("pnp", 0, pnp_get_info);
+#endif /* CONFIG_PROC_FS */
+
+ if (!ic_enable)
+ return 0;
+
+ DBG(("IP-Config: Entered.\n"));
+
+#ifdef IPCONFIG_DYNAMIC
+ try_try_again:
+#endif
+ /* Give hardware a chance to settle */
+ jiff = jiffies + CONF_PRE_OPEN;
+ while (time_before(jiffies, jiff))
+ ;
+
+ /* Setup all network devices */
+ if (ic_open_devs() < 0)
+ return -1;
+
+ /* Give drivers a chance to settle */
+ jiff = jiffies + CONF_POST_OPEN;
+ while (time_before(jiffies, jiff))
+ ;
+
+ /*
+ * If the config information is insufficient (e.g., our IP address or
+ * IP address of the boot server is missing or we have multiple network
+ * interfaces and no default was set), use BOOTP or RARP to get the
+ * missing values.
+ */
+ if (ic_myaddr == INADDR_NONE ||
+#ifdef CONFIG_ROOT_NFS
+ (MAJOR(ROOT_DEV) == UNNAMED_MAJOR
+ && root_server_addr == INADDR_NONE
+ && ic_servaddr == INADDR_NONE) ||
+#endif
+ ic_first_dev->next) {
+#ifdef IPCONFIG_DYNAMIC
+ if (ic_dynamic() < 0) {
+ ic_close_devs();
+
+ /*
+ * I don't know why, but sometimes the
+ * eepro100 driver (at least) gets upset and
+ * doesn't work the first time it's opened.
+ * But then if you close it and reopen it, it
+ * works just fine. So we need to try that at
+ * least once before giving up.
+ *
+ * Also, if the root will be NFS-mounted, we
+ * have nowhere to go if DHCP fails. So we
+ * just have to keep trying forever.
+ *
+ * -- Chip
+ */
+#ifdef CONFIG_ROOT_NFS
+ if (ROOT_DEV == MKDEV(NFS_MAJOR, NFS_MINOR)) {
+ printk(KERN_ERR
+ "IP-Config: Retrying forever (NFS root)...\n");
+ goto try_try_again;
+ }
+#endif
+
+ if (--retries) {
+ printk(KERN_ERR
+ "IP-Config: Reopening network devices...\n");
+ goto try_try_again;
+ }
+
+ /* Oh, well. At least we tried. */
+ printk(KERN_ERR "IP-Config: Auto-configuration of network failed.\n");
+ return -1;
+ }
+#else /* !DYNAMIC */
+ printk(KERN_ERR "IP-Config: Incomplete network configuration information.\n");
+ ic_close_devs();
+ return -1;
+#endif /* IPCONFIG_DYNAMIC */
+ } else {
+ /* Device selected manually or only one device -> use it */
+ ic_dev = ic_first_dev->dev;
+ }
+
+ addr = root_nfs_parse_addr(root_server_path);
+ if (root_server_addr == INADDR_NONE)
+ root_server_addr = addr;
+
+ /*
+ * Use defaults whereever applicable.
+ */
+ if (ic_defaults() < 0)
+ return -1;
+
+ /*
+ * Close all network devices except the device we've
+ * autoconfigured and set up routes.
+ */
+ ic_close_devs();
+ if (ic_setup_if() < 0 || ic_setup_routes() < 0)
+ return -1;
+
+ /*
+ * Record which protocol was actually used.
+ */
+#ifdef IPCONFIG_DYNAMIC
+ ic_proto_used = ic_got_reply | (ic_proto_enabled & IC_USE_DHCP);
+#endif
+
+#ifndef IPCONFIG_SILENT
+ /*
+ * Clue in the operator.
+ */
+ printk("IP-Config: Complete:");
+ printk("\n device=%s", ic_dev->name);
+ printk(", addr=%u.%u.%u.%u", NIPQUAD(ic_myaddr));
+ printk(", mask=%u.%u.%u.%u", NIPQUAD(ic_netmask));
+ printk(", gw=%u.%u.%u.%u", NIPQUAD(ic_gateway));
+ printk(",\n host=%s, domain=%s, nis-domain=%s",
+ system_utsname.nodename, ic_domain, system_utsname.domainname);
+ printk(",\n bootserver=%u.%u.%u.%u", NIPQUAD(ic_servaddr));
+ printk(", rootserver=%u.%u.%u.%u", NIPQUAD(root_server_addr));
+ printk(", rootpath=%s", root_server_path);
+ printk("\n");
+#endif /* !SILENT */
+
+ return 0;
+}
+
+module_init(ip_auto_config);
+
+
+/*
+ * Decode any IP configuration options in the "ip=" or "nfsaddrs=" kernel
+ * command line parameter. It consists of option fields separated by colons in
+ * the following order:
+ *
+ * <client-ip>:<server-ip>:<gw-ip>:<netmask>:<host name>:<device>:<PROTO>
+ *
+ * Any of the fields can be empty which means to use a default value:
+ * <client-ip> - address given by BOOTP or RARP
+ * <server-ip> - address of host returning BOOTP or RARP packet
+ * <gw-ip> - none, or the address returned by BOOTP
+ * <netmask> - automatically determined from <client-ip>, or the
+ * one returned by BOOTP
+ * <host name> - <client-ip> in ASCII notation, or the name returned
+ * by BOOTP
+ * <device> - use all available devices
+ * <PROTO>:
+ * off|none - don't do autoconfig at all (DEFAULT)
+ * on|any - use any configured protocol
+ * dhcp|bootp|rarp - use only the specified protocol
+ * both - use both BOOTP and RARP (not DHCP)
+ */
+static int __init ic_proto_name(char *name)
+{
+ if (!strcmp(name, "on") || !strcmp(name, "any")) {
+ return 1;
+ }
+#ifdef CONFIG_IP_PNP_DHCP
+ else if (!strcmp(name, "dhcp")) {
+ ic_proto_enabled &= ~IC_RARP;
+ return 1;
+ }
+#endif
+#ifdef CONFIG_IP_PNP_BOOTP
+ else if (!strcmp(name, "bootp")) {
+ ic_proto_enabled &= ~(IC_RARP | IC_USE_DHCP);
+ return 1;
+ }
+#endif
+#ifdef CONFIG_IP_PNP_RARP
+ else if (!strcmp(name, "rarp")) {
+ ic_proto_enabled &= ~(IC_BOOTP | IC_USE_DHCP);
+ return 1;
+ }
+#endif
+#ifdef IPCONFIG_DYNAMIC
+ else if (!strcmp(name, "both")) {
+ ic_proto_enabled &= ~IC_USE_DHCP; /* backward compat :-( */
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+static int __init ip_auto_config_setup(char *addrs)
+{
+ char *cp, *ip, *dp;
+ int num = 0;
+
+ ic_set_manually = 1;
+
+ ic_enable = (*addrs &&
+ (strcmp(addrs, "off") != 0) &&
+ (strcmp(addrs, "none") != 0));
+ if (!ic_enable)
+ return 1;
+
+ if (ic_proto_name(addrs))
+ return 1;
+
+ /* Parse the whole string */
+ ip = addrs;
+ while (ip && *ip) {
+ if ((cp = strchr(ip, ':')))
+ *cp++ = '\0';
+ if (strlen(ip) > 0) {
+ DBG(("IP-Config: Parameter #%d: `%s'\n", num, ip));
+ switch (num) {
+ case 0:
+ if ((ic_myaddr = in_aton(ip)) == INADDR_ANY)
+ ic_myaddr = INADDR_NONE;
+ break;
+ case 1:
+ if ((ic_servaddr = in_aton(ip)) == INADDR_ANY)
+ ic_servaddr = INADDR_NONE;
+ break;
+ case 2:
+ if ((ic_gateway = in_aton(ip)) == INADDR_ANY)
+ ic_gateway = INADDR_NONE;
+ break;
+ case 3:
+ if ((ic_netmask = in_aton(ip)) == INADDR_ANY)
+ ic_netmask = INADDR_NONE;
+ break;
+ case 4:
+ if ((dp = strchr(ip, '.'))) {
+ *dp++ = '\0';
+ strncpy(system_utsname.domainname, dp, __NEW_UTS_LEN);
+ system_utsname.domainname[__NEW_UTS_LEN] = '\0';
+ }
+ strncpy(system_utsname.nodename, ip, __NEW_UTS_LEN);
+ system_utsname.nodename[__NEW_UTS_LEN] = '\0';
+ ic_host_name_set = 1;
+ break;
+ case 5:
+ strncpy(user_dev_name, ip, IFNAMSIZ);
+ user_dev_name[IFNAMSIZ-1] = '\0';
+ break;
+ case 6:
+ ic_proto_name(ip);
+ break;
+ }
+ }
+ ip = cp;
+ num++;
+ }
+
+ return 1;
+}
+
+static int __init nfsaddrs_config_setup(char *addrs)
+{
+ return ip_auto_config_setup(addrs);
+}
+
+__setup("ip=", ip_auto_config_setup);
+__setup("nfsaddrs=", nfsaddrs_config_setup);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipip.c b/uClinux-2.4.31-uc0/net/ipv4/ipip.c
new file mode 100644
index 0000000..408173e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipip.c
@@ -0,0 +1,1068 @@
+/* $USAGI: ipip.c,v 1.36 2003/11/21 20:13:13 yoshfuji Exp $ */
+
+/*
+ * Linux NET3: IP/IP protocol decoder.
+ *
+ * Version: $Id: ipip.c,v 1.50 2001/10/02 02:22:36 davem Exp $
+ *
+ * Authors:
+ * Sam Lantinga (slouken@cs.ucdavis.edu) 02/01/95
+ *
+ * Fixes:
+ * Alan Cox : Merged and made usable non modular (its so tiny its silly as
+ * a module taking up 2 pages).
+ * Alan Cox : Fixed bug with 1.3.18 and IPIP not working (now needs to set skb->h.iph)
+ * to keep ip_forward happy.
+ * Alan Cox : More fixes for 1.3.21, and firewall fix. Maybe this will work soon 8).
+ * Kai Schulte : Fixed #defines for IP_FIREWALL->FIREWALL
+ * David Woodhouse : Perform some basic ICMP handling.
+ * IPIP Routing without decapsulation.
+ * Carlos Picoto : GRE over IP support
+ * Alexey Kuznetsov: Reworked. Really, now it is truncated version of ipv4/ip_gre.c.
+ * I do not want to merge them together.
+ * Fred L. Templin : isatap support.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+/* tunnel.c: an IP tunnel driver
+
+ The purpose of this driver is to provide an IP tunnel through
+ which you can tunnel network traffic transparently across subnets.
+
+ This was written by looking at Nick Holloway's dummy driver
+ Thanks for the great code!
+
+ -Sam Lantinga (slouken@cs.ucdavis.edu) 02/01/95
+
+ Minor tweaks:
+ Cleaned up the code a little and added some pre-1.3.0 tweaks.
+ dev->hard_header/hard_header_len changed to use no headers.
+ Comments/bracketing tweaked.
+ Made the tunnels use dev->name not tunnel: when error reporting.
+ Added tx_dropped stat
+
+ -Alan Cox (Alan.Cox@linux.org) 21 March 95
+
+ Reworked:
+ Changed to tunnel to destination gateway in addition to the
+ tunnel's pointopoint address
+ Almost completely rewritten
+ Note: There is currently no firewall or ICMP handling done.
+
+ -Sam Lantinga (slouken@cs.ucdavis.edu) 02/13/96
+
+*/
+
+/* Things I wish I had known when writing the tunnel driver:
+
+ When the tunnel_xmit() function is called, the skb contains the
+ packet to be sent (plus a great deal of extra info), and dev
+ contains the tunnel device that _we_ are.
+
+ When we are passed a packet, we are expected to fill in the
+ source address with our source IP address.
+
+ What is the proper way to allocate, copy and free a buffer?
+ After you allocate it, it is a "0 length" chunk of memory
+ starting at zero. If you want to add headers to the buffer
+ later, you'll have to call "skb_reserve(skb, amount)" with
+ the amount of memory you want reserved. Then, you call
+ "skb_put(skb, amount)" with the amount of space you want in
+ the buffer. skb_put() returns a pointer to the top (#0) of
+ that buffer. skb->len is set to the amount of space you have
+ "allocated" with skb_put(). You can then write up to skb->len
+ bytes to that buffer. If you need more, you can call skb_put()
+ again with the additional amount of space you need. You can
+ find out how much more space you can allocate by calling
+ "skb_tailroom(skb)".
+ Now, to add header space, call "skb_push(skb, header_len)".
+ This creates space at the beginning of the buffer and returns
+ a pointer to this new space. If later you need to strip a
+ header from a buffer, call "skb_pull(skb, header_len)".
+ skb_headroom() will return how much space is left at the top
+ of the buffer (before the main data). Remember, this headroom
+ space must be reserved before the skb_put() function is called.
+ */
+
+/*
+ This version of net/ipv4/ipip.c is cloned of net/ipv4/ip_gre.c
+
+ For comments look at net/ipv4/ip_gre.c --ANK
+ */
+
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>
+#include <asm/uaccess.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/in.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/icmpv6.h>
+#include <linux/if_arp.h>
+#include <linux/mroute.h>
+#include <linux/init.h>
+#include <linux/netfilter_ipv4.h>
+
+#ifdef CONFIG_NET_IPIP_IPV6
+#include <net/ipv6.h>
+#include <net/addrconf.h>
+#endif
+#include <net/sock.h>
+#include <net/ip.h>
+#include <net/icmp.h>
+#include <net/protocol.h>
+#include <net/ipip.h>
+#include <net/inet_ecn.h>
+
+#define HASH_SIZE 16
+#define HASH(addr) ((addr^(addr>>4))&0xF)
+
+static int ipip_fb_tunnel_init(struct net_device *dev);
+static int ipip_tunnel_init(struct net_device *dev);
+
+static struct net_device ipip_fb_tunnel_dev = {
+ name: "tunl0",
+ init: ipip_fb_tunnel_init,
+};
+
+static struct ip_tunnel ipip_fb_tunnel = {
+ dev: &ipip_fb_tunnel_dev,
+ parms: { name: "tunl0", }
+};
+
+static struct ip_tunnel *tunnels_r_l[HASH_SIZE];
+static struct ip_tunnel *tunnels_r[HASH_SIZE];
+static struct ip_tunnel *tunnels_l[HASH_SIZE];
+static struct ip_tunnel *tunnels_wc[1];
+static struct ip_tunnel **tunnels[4] = { tunnels_wc, tunnels_l, tunnels_r, tunnels_r_l };
+
+#ifndef CONFIG_NET_IPIP_IPV6
+static
+#endif
+rwlock_t ipip_lock = RW_LOCK_UNLOCKED;
+
+#ifndef CONFIG_NET_IPIP_IPV6
+static
+#endif
+struct ip_tunnel * ipip_tunnel_lookup(u32 remote, u32 local)
+{
+ unsigned h0 = HASH(remote);
+ unsigned h1 = HASH(local);
+ struct ip_tunnel *t;
+
+ for (t = tunnels_r_l[h0^h1]; t; t = t->next) {
+ if (local == t->parms.iph.saddr &&
+ remote == t->parms.iph.daddr && (t->dev->flags&IFF_UP))
+ return t;
+ }
+ for (t = tunnels_r[h0]; t; t = t->next) {
+ if (remote == t->parms.iph.daddr && (t->dev->flags&IFF_UP))
+ return t;
+ }
+ for (t = tunnels_l[h1]; t; t = t->next) {
+ if (local == t->parms.iph.saddr && (t->dev->flags&IFF_UP))
+ return t;
+ }
+ if ((t = tunnels_wc[0]) != NULL && (t->dev->flags&IFF_UP))
+ return t;
+ return NULL;
+}
+
+static __inline__ struct ip_tunnel **__ipip_bucket(struct ip_tunnel_parm *parms)
+{
+ u32 remote = parms->iph.daddr;
+ u32 local = parms->iph.saddr;
+ unsigned h = 0;
+ int prio = 0;
+
+ if (remote) {
+ prio |= 2;
+ h ^= HASH(remote);
+ }
+ if (local) {
+ prio |= 1;
+ h ^= HASH(local);
+ }
+ return &tunnels[prio][h];
+}
+
+static struct ip_tunnel **ipip_bucket(struct ip_tunnel *t)
+{
+ return __ipip_bucket(&t->parms);
+}
+
+static void ipip_tunnel_unlink(struct ip_tunnel *t)
+{
+ struct ip_tunnel **tp;
+
+ for (tp = ipip_bucket(t); *tp; tp = &(*tp)->next) {
+ if (t == *tp) {
+ write_lock_bh(&ipip_lock);
+ *tp = t->next;
+ write_unlock_bh(&ipip_lock);
+ break;
+ }
+ }
+}
+
+static void ipip_tunnel_link(struct ip_tunnel *t)
+{
+ struct ip_tunnel **tp = ipip_bucket(t);
+
+ t->next = *tp;
+ write_lock_bh(&ipip_lock);
+ *tp = t;
+ write_unlock_bh(&ipip_lock);
+}
+
+struct ip_tunnel * ipip_tunnel_locate(struct ip_tunnel_parm *parms, int create)
+{
+ u32 remote = parms->iph.daddr;
+ u32 local = parms->iph.saddr;
+ struct ip_tunnel *t, **tp, *nt;
+ struct net_device *dev;
+
+ for (tp = __ipip_bucket(parms); (t = *tp) != NULL; tp = &t->next) {
+ if (local == t->parms.iph.saddr && remote == t->parms.iph.daddr)
+ return t;
+ }
+ if (!create)
+ return NULL;
+
+ MOD_INC_USE_COUNT;
+ dev = kmalloc(sizeof(*dev) + sizeof(*t), GFP_KERNEL);
+ if (dev == NULL) {
+ MOD_DEC_USE_COUNT;
+ return NULL;
+ }
+ memset(dev, 0, sizeof(*dev) + sizeof(*t));
+ dev->priv = (void*)(dev+1);
+ nt = (struct ip_tunnel*)dev->priv;
+ nt->dev = dev;
+ dev->init = ipip_tunnel_init;
+ dev->features |= NETIF_F_DYNALLOC;
+ memcpy(&nt->parms, parms, sizeof(*parms));
+ nt->parms.name[IFNAMSIZ-1] = '\0';
+ strcpy(dev->name, nt->parms.name);
+ if (dev->name[0] == 0) {
+ int i;
+ for (i=1; i<100; i++) {
+ sprintf(dev->name, "tunl%d", i);
+ if (__dev_get_by_name(dev->name) == NULL)
+ break;
+ }
+ if (i==100)
+ goto failed;
+ memcpy(nt->parms.name, dev->name, IFNAMSIZ);
+ }
+ if (register_netdevice(dev) < 0)
+ goto failed;
+
+ dev_hold(dev);
+ ipip_tunnel_link(nt);
+ /* Do not decrement MOD_USE_COUNT here. */
+ return nt;
+
+failed:
+ kfree(dev);
+ MOD_DEC_USE_COUNT;
+ return NULL;
+}
+
+static void ipip_tunnel_destructor(struct net_device *dev)
+{
+ if (dev != &ipip_fb_tunnel_dev) {
+ MOD_DEC_USE_COUNT;
+ }
+}
+
+static void ipip_tunnel_uninit(struct net_device *dev)
+{
+ if (dev == &ipip_fb_tunnel_dev) {
+ write_lock_bh(&ipip_lock);
+ tunnels_wc[0] = NULL;
+ write_unlock_bh(&ipip_lock);
+ } else
+ ipip_tunnel_unlink((struct ip_tunnel*)dev->priv);
+ dev_put(dev);
+}
+
+void ipip_err(struct sk_buff *skb, u32 info)
+{
+#ifndef I_WISH_WORLD_WERE_PERFECT
+
+/* It is not :-( All the routers (except for Linux) return only
+ 8 bytes of packet payload. It means, that precise relaying of
+ ICMP in the real Internet is absolutely infeasible.
+ */
+ struct iphdr *iph = (struct iphdr*)skb->data;
+ int type = skb->h.icmph->type;
+ int code = skb->h.icmph->code;
+ struct ip_tunnel *t;
+
+ switch (type) {
+ default:
+ case ICMP_PARAMETERPROB:
+ return;
+
+ case ICMP_DEST_UNREACH:
+ switch (code) {
+ case ICMP_SR_FAILED:
+ case ICMP_PORT_UNREACH:
+ /* Impossible event. */
+ return;
+ case ICMP_FRAG_NEEDED:
+ /* Soft state for pmtu is maintained by IP core. */
+ return;
+ default:
+ /* All others are translated to HOST_UNREACH.
+ rfc2003 contains "deep thoughts" about NET_UNREACH,
+ I believe they are just ether pollution. --ANK
+ */
+ break;
+ }
+ break;
+ case ICMP_TIME_EXCEEDED:
+ if (code != ICMP_EXC_TTL)
+ return;
+ break;
+ }
+
+ read_lock(&ipip_lock);
+ t = ipip_tunnel_lookup(iph->daddr, iph->saddr);
+ if (t == NULL || t->parms.iph.daddr == 0)
+ goto out;
+ if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
+ goto out;
+
+ if (jiffies - t->err_time < IPTUNNEL_ERR_TIMEO)
+ t->err_count++;
+ else
+ t->err_count = 1;
+ t->err_time = jiffies;
+out:
+ read_unlock(&ipip_lock);
+ return;
+#else
+ struct iphdr *iph = (struct iphdr*)dp;
+ int hlen = iph->ihl<<2;
+ struct iphdr *eiph;
+ int type = skb->h.icmph->type;
+ int code = skb->h.icmph->code;
+ int rel_type = 0;
+ int rel_code = 0;
+ int rel_info = 0;
+ struct sk_buff *skb2;
+ struct rtable *rt;
+
+ if (len < hlen + sizeof(struct iphdr))
+ return;
+ eiph = (struct iphdr*)(dp + hlen);
+
+ switch (type) {
+ default:
+ return;
+ case ICMP_PARAMETERPROB:
+ if (skb->h.icmph->un.gateway < hlen)
+ return;
+
+ /* So... This guy found something strange INSIDE encapsulated
+ packet. Well, he is fool, but what can we do ?
+ */
+ rel_type = ICMP_PARAMETERPROB;
+ rel_info = skb->h.icmph->un.gateway - hlen;
+ break;
+
+ case ICMP_DEST_UNREACH:
+ switch (code) {
+ case ICMP_SR_FAILED:
+ case ICMP_PORT_UNREACH:
+ /* Impossible event. */
+ return;
+ case ICMP_FRAG_NEEDED:
+ /* And it is the only really necesary thing :-) */
+ rel_info = ntohs(skb->h.icmph->un.frag.mtu);
+ if (rel_info < hlen+68)
+ return;
+ rel_info -= hlen;
+ /* BSD 4.2 MORE DOES NOT EXIST IN NATURE. */
+ if (rel_info > ntohs(eiph->tot_len))
+ return;
+ break;
+ default:
+ /* All others are translated to HOST_UNREACH.
+ rfc2003 contains "deep thoughts" about NET_UNREACH,
+ I believe, it is just ether pollution. --ANK
+ */
+ rel_type = ICMP_DEST_UNREACH;
+ rel_code = ICMP_HOST_UNREACH;
+ break;
+ }
+ break;
+ case ICMP_TIME_EXCEEDED:
+ if (code != ICMP_EXC_TTL)
+ return;
+ break;
+ }
+
+ /* Prepare fake skb to feed it to icmp_send */
+ skb2 = skb_clone(skb, GFP_ATOMIC);
+ if (skb2 == NULL)
+ return;
+ dst_release(skb2->dst);
+ skb2->dst = NULL;
+ skb_pull(skb2, skb->data - (u8*)eiph);
+ skb2->nh.raw = skb2->data;
+
+ /* Try to guess incoming interface */
+ if (ip_route_output(&rt, eiph->saddr, 0, RT_TOS(eiph->tos), 0)) {
+ kfree_skb(skb2);
+ return;
+ }
+ skb2->dev = rt->u.dst.dev;
+
+ /* route "incoming" packet */
+ if (rt->rt_flags&RTCF_LOCAL) {
+ ip_rt_put(rt);
+ rt = NULL;
+ if (ip_route_output(&rt, eiph->daddr, eiph->saddr, eiph->tos, 0) ||
+ rt->u.dst.dev->type != ARPHRD_IPGRE) {
+ ip_rt_put(rt);
+ kfree_skb(skb2);
+ return;
+ }
+ } else {
+ ip_rt_put(rt);
+ if (ip_route_input(skb2, eiph->daddr, eiph->saddr, eiph->tos, skb2->dev) ||
+ skb2->dst->dev->type != ARPHRD_IPGRE) {
+ kfree_skb(skb2);
+ return;
+ }
+ }
+
+ /* change mtu on this route */
+ if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) {
+ if (rel_info > skb2->dst->pmtu) {
+ kfree_skb(skb2);
+ return;
+ }
+ skb2->dst->pmtu = rel_info;
+ rel_info = htonl(rel_info);
+ } else if (type == ICMP_TIME_EXCEEDED) {
+ struct ip_tunnel *t = (struct ip_tunnel*)skb2->dev->priv;
+ if (t->parms.iph.ttl) {
+ rel_type = ICMP_DEST_UNREACH;
+ rel_code = ICMP_HOST_UNREACH;
+ }
+ }
+
+ icmp_send(skb2, rel_type, rel_code, rel_info);
+ kfree_skb(skb2);
+ return;
+#endif
+}
+
+#ifdef CONFIG_NET_IPIP_IPV6
+extern void ipip6_err(struct sk_buff *skb, u32 info);
+#endif
+
+static inline void ipip_ecn_decapsulate(struct iphdr *outer_iph, struct sk_buff *skb)
+{
+ struct iphdr *inner_iph = skb->nh.iph;
+
+ if (INET_ECN_is_ce(outer_iph->tos) &&
+ INET_ECN_is_not_ce(inner_iph->tos))
+ IP_ECN_set_ce(inner_iph);
+}
+
+int ipip_rcv(struct sk_buff *skb)
+{
+ struct iphdr *iph;
+ struct ip_tunnel *tunnel;
+ u16 proto;
+ int hdrlen;
+
+ switch(skb->nh.iph->protocol) {
+ case IPPROTO_IPIP:
+ proto = htons(ETH_P_IP);
+ hdrlen = sizeof(struct iphdr);
+ break;
+#ifdef CONFIG_NET_IPIP_IPV6
+ case IPPROTO_IPV6:
+ proto = htons(ETH_P_IPV6);
+ hdrlen = sizeof(struct ipv6hdr);
+ break;
+#endif
+ default:
+ if (net_ratelimit())
+ printk(KERN_WARNING
+ "ipip_rcv(): unsupported protocol (%u)\n",
+ skb->nh.iph->protocol);
+ goto protounreach;
+ }
+
+ if (!pskb_may_pull(skb, hdrlen))
+ goto out;
+
+ iph = skb->nh.iph;
+ skb->mac.raw = skb->nh.raw;
+ skb->nh.raw = skb->data;
+ memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
+ skb->protocol = proto;
+ skb->pkt_type = PACKET_HOST;
+
+ read_lock(&ipip_lock);
+ if ((tunnel = ipip_tunnel_lookup(iph->saddr, iph->daddr)) != NULL) {
+ if (tunnel->parms.iph.protocol != 0 &&
+ tunnel->parms.iph.protocol != iph->protocol) {
+ read_unlock(&ipip_lock);
+ goto protounreach;
+ }
+ tunnel->stat.rx_packets++;
+ tunnel->stat.rx_bytes += skb->len;
+ skb->dev = tunnel->dev;
+ dst_release(skb->dst);
+ skb->dst = NULL;
+ nf_reset(skb);
+ ipip_ecn_decapsulate(iph, skb);
+ netif_rx(skb);
+ read_unlock(&ipip_lock);
+ return 0;
+ }
+ read_unlock(&ipip_lock);
+protounreach:
+ icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0);
+out:
+ kfree_skb(skb);
+ return 0;
+}
+
+#ifdef CONFIG_NET_IPIP_IPV6
+extern int ipip6_rcv(struct sk_buff *skb);
+#endif
+
+/* Need this wrapper because NF_HOOK takes the function address */
+static inline int do_ip_send(struct sk_buff *skb)
+{
+ return ip_send(skb);
+}
+
+/*
+ * This function assumes it is being called from dev_queue_xmit()
+ * and that skb is filled properly by that function.
+ */
+
+static int ipip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct ip_tunnel *tunnel = (struct ip_tunnel*)dev->priv;
+ struct net_device_stats *stats = &tunnel->stat;
+ struct iphdr *tiph = &tunnel->parms.iph;
+ u8 tos = tunnel->parms.iph.tos;
+ u16 df = tiph->frag_off;
+ struct rtable *rt; /* Route to the other host */
+ struct net_device *tdev; /* Device to other host */
+ struct iphdr *old_iph = skb->nh.iph;
+#ifdef CONFIG_NET_IPIP_IPV6
+ struct ipv6hdr *iph6 = skb->nh.ipv6h;
+#endif
+ struct iphdr *iph; /* Our new IP header */
+ int max_headroom; /* The extra header space needed */
+ u32 dst = tiph->daddr;
+ int mtu;
+ u8 protocol = 0;
+
+ switch (skb->protocol) {
+ case __constant_htons(ETH_P_IP):
+ protocol = IPPROTO_IPIP;
+ break;
+#ifdef CONFIG_NET_IPIP_IPV6
+ case __constant_htons(ETH_P_IPV6):
+ protocol = IPPROTO_IPV6;
+ break;
+#endif
+ }
+
+ if (tunnel->recursion++) {
+ tunnel->stat.collisions++;
+ goto tx_error;
+ }
+
+ switch(skb->protocol) {
+ case __constant_htons(ETH_P_IP):
+ if (tunnel->parms.iph.protocol &&
+ tunnel->parms.iph.protocol != IPPROTO_IPIP)
+ goto tx_error;
+ if (tos&1)
+ tos = old_iph->tos;
+ break;
+#ifdef CONFIG_NET_IPIP_IPV6
+ case __constant_htons(ETH_P_IPV6):
+ if (tunnel->parms.iph.protocol &&
+ tunnel->parms.iph.protocol != IPPROTO_IPV6)
+ goto tx_error;
+ break;
+#endif
+ default:
+ goto tx_error;
+ }
+
+ if (!dst) {
+ switch(skb->protocol){
+ case __constant_htons(ETH_P_IP):
+ /* NBMA tunnel */
+ if ((rt = (struct rtable*)skb->dst) == NULL) {
+ tunnel->stat.tx_fifo_errors++;
+ goto tx_error;
+ }
+ dst = rt->rt_gateway;
+ break;
+#ifdef CONFIG_NET_IPIP_IPV6
+ case __constant_htons(ETH_P_IPV6):
+ {
+ struct in6_addr *addr6 = &iph6->daddr;
+ if (addr6->s6_addr16[0] == htons(0x2002)) {
+ memcpy(&dst, &addr6->s6_addr16[1], 4);
+ } else {
+ /* dst is zero */
+ struct neighbour *neigh = NULL;
+ if (skb->dst)
+ neigh = skb->dst->neighbour;
+ if (neigh == NULL) {
+ printk(KERN_DEBUG "tunl: nexthop == NULL\n");
+ goto tx_error;
+ }
+ addr6 = (struct in6_addr*)&neigh->primary_key;
+ if (IN6_IS_ADDR_UNSPECIFIED(addr6))
+ addr6 = &skb->nh.ipv6h->daddr;
+ if (IN6_IS_ADDR_V4COMPAT(addr6))
+ dst = addr6->s6_addr32[3];
+#ifdef CONFIG_IPV6_6TO4_NEXTHOP
+ else if (addr6->s6_addr16[0] == htons(0x2002))
+ memcpy(&dst, &addr6->s6_addr16[1], 4);
+#endif
+ else
+ goto tx_error_icmp;
+ }
+ break;
+ }
+#endif
+ }
+ if (!dst)
+ goto tx_error_icmp;
+ }
+
+ if (ip_route_output(&rt, dst, tiph->saddr, RT_TOS(tos), tunnel->parms.link)) {
+ tunnel->stat.tx_carrier_errors++;
+ goto tx_error_icmp;
+ }
+ tdev = rt->u.dst.dev;
+
+ if (tdev == dev) {
+ ip_rt_put(rt);
+ tunnel->stat.collisions++;
+ goto tx_error;
+ }
+
+ if (tiph->frag_off)
+ mtu = rt->u.dst.pmtu - sizeof(struct iphdr);
+ else
+ mtu = skb->dst ? skb->dst->pmtu : dev->mtu;
+
+ if (mtu < 68) {
+ tunnel->stat.collisions++;
+ ip_rt_put(rt);
+ goto tx_error;
+ }
+
+ switch(skb->protocol){
+ case __constant_htons(ETH_P_IP):
+ if (skb->dst && mtu < skb->dst->pmtu)
+ skb->dst->pmtu = mtu;
+
+ df |= (old_iph->frag_off&htons(IP_DF));
+
+ if ((old_iph->frag_off&htons(IP_DF)) && mtu < ntohs(old_iph->tot_len)) {
+ icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED, htonl(mtu));
+ ip_rt_put(rt);
+ goto tx_error;
+ }
+ break;
+
+#ifdef CONFIG_NET_IPIP_IPV6
+ case __constant_htons(ETH_P_IPV6):
+#if 0
+ if (mtu < IPV6_MIN_MTU) {
+ /* XXX: too small; we should fragment this packet? */
+ tunnel->stat.tx_carrier_errors++;
+ goto tx_error_icmp;
+ }
+#endif
+ if (skb->len > mtu && mtu > IPV6_MIN_MTU) {
+ icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, dev);
+ ip_rt_put(rt);
+ goto tx_error;
+ }
+ df = mtu > IPV6_MIN_MTU ? htons(IP_DF) : 0;
+ break;
+#endif
+ }
+ if (tunnel->err_count > 0) {
+ if (jiffies - tunnel->err_time < IPTUNNEL_ERR_TIMEO) {
+ tunnel->err_count--;
+ dst_link_failure(skb);
+ } else
+ tunnel->err_count = 0;
+ }
+
+ /*
+ * Okay, now see if we can stuff it in the buffer as-is.
+ */
+ max_headroom = (((tdev->hard_header_len+15)&~15)+sizeof(struct iphdr));
+ if (skb_headroom(skb) < max_headroom || skb_cloned(skb) || skb_shared(skb)) {
+ struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);
+ if (!new_skb) {
+ ip_rt_put(rt);
+ stats->tx_dropped++;
+ dev_kfree_skb(skb);
+ tunnel->recursion--;
+ return 0;
+ }
+ if (skb->sk)
+ skb_set_owner_w(new_skb, skb->sk);
+ dev_kfree_skb(skb);
+ skb = new_skb;
+ old_iph = skb->nh.iph;
+ }
+
+ skb->h.raw = skb->nh.raw;
+ skb->nh.raw = skb_push(skb, sizeof(struct iphdr));
+ memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
+ dst_release(skb->dst);
+ skb->dst = &rt->u.dst;
+
+ /*
+ * Push down and install the IPIP header.
+ */
+
+ iph = skb->nh.iph;
+ iph->version = 4;
+ iph->ihl = sizeof(struct iphdr)>>2;
+ iph->daddr = rt->rt_dst;
+ iph->saddr = rt->rt_src;
+
+ iph->ttl = tiph->ttl;
+ iph->frag_off = df;
+
+ switch(skb->protocol){
+ case __constant_htons(ETH_P_IP):
+ iph->protocol = protocol;
+ iph->tos = INET_ECN_encapsulate(tos, old_iph->tos);
+ if (iph->ttl == 0)
+ iph->ttl = old_iph->ttl;
+ break;
+#ifdef CONFIG_NET_IPIP_IPV6
+ case __constant_htons(ETH_P_IPV6):
+ iph->protocol = protocol;
+ iph->tos = INET_ECN_encapsulate(tos, ip6_get_dsfield(iph6));
+ if (iph->ttl == 0)
+ iph->ttl = iph6->hop_limit;
+ break;
+#endif
+ }
+
+ nf_reset(skb);
+
+ IPTUNNEL_XMIT();
+ tunnel->recursion--;
+ return 0;
+
+tx_error_icmp:
+ dst_link_failure(skb);
+tx_error:
+ stats->tx_errors++;
+ dev_kfree_skb(skb);
+ tunnel->recursion--;
+ return 0;
+}
+
+static int
+ipip_tunnel_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ int err = 0;
+ struct ip_tunnel_parm p;
+ struct ip_tunnel *t;
+
+ MOD_INC_USE_COUNT;
+
+ switch (cmd) {
+ case SIOCGETTUNNEL:
+ t = NULL;
+ if (dev == &ipip_fb_tunnel_dev) {
+ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p))) {
+ err = -EFAULT;
+ break;
+ }
+ t = ipip_tunnel_locate(&p, 0);
+ }
+ if (t == NULL)
+ t = (struct ip_tunnel*)dev->priv;
+ memcpy(&p, &t->parms, sizeof(p));
+ if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p)))
+ err = -EFAULT;
+ break;
+
+ case SIOCADDTUNNEL:
+ case SIOCCHGTUNNEL:
+ err = -EPERM;
+ if (!capable(CAP_NET_ADMIN))
+ goto done;
+
+ err = -EFAULT;
+ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
+ goto done;
+
+ err = -EINVAL;
+ if (p.iph.version != 4 ||
+ (p.iph.protocol != 0
+ && p.iph.protocol != IPPROTO_IPIP
+#ifdef CONFIG_NET_IPIP_IPV6
+ && p.iph.protocol != IPPROTO_IPV6
+#endif
+ ) || p.iph.ihl != 5 || (p.iph.frag_off&htons(~IP_DF)))
+ goto done;
+ if (p.iph.ttl)
+ p.iph.frag_off |= htons(IP_DF);
+
+ t = ipip_tunnel_locate(&p, cmd == SIOCADDTUNNEL);
+
+ if (dev != &ipip_fb_tunnel_dev && cmd == SIOCCHGTUNNEL &&
+ t != &ipip_fb_tunnel) {
+ if (t != NULL) {
+ if (t->dev != dev) {
+ err = -EEXIST;
+ break;
+ }
+ } else {
+ if (((dev->flags&IFF_POINTOPOINT) && !p.iph.daddr) ||
+ (!(dev->flags&IFF_POINTOPOINT) && p.iph.daddr)) {
+ err = -EINVAL;
+ break;
+ }
+ t = (struct ip_tunnel*)dev->priv;
+ ipip_tunnel_unlink(t);
+ t->parms.iph.saddr = p.iph.saddr;
+ t->parms.iph.daddr = p.iph.daddr;
+ memcpy(dev->dev_addr, &p.iph.saddr, 4);
+ memcpy(dev->broadcast, &p.iph.daddr, 4);
+ ipip_tunnel_link(t);
+ netdev_state_change(dev);
+ }
+ }
+
+ if (t) {
+ err = 0;
+ if (cmd == SIOCCHGTUNNEL) {
+ t->parms.iph.ttl = p.iph.ttl;
+ t->parms.iph.tos = p.iph.tos;
+ t->parms.iph.frag_off = p.iph.frag_off;
+ }
+ if (copy_to_user(ifr->ifr_ifru.ifru_data, &t->parms, sizeof(p)))
+ err = -EFAULT;
+ } else
+ err = (cmd == SIOCADDTUNNEL ? -ENOBUFS : -ENOENT);
+ break;
+
+ case SIOCDELTUNNEL:
+ err = -EPERM;
+ if (!capable(CAP_NET_ADMIN))
+ goto done;
+
+ if (dev == &ipip_fb_tunnel_dev) {
+ err = -EFAULT;
+ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
+ goto done;
+ err = -ENOENT;
+ if ((t = ipip_tunnel_locate(&p, 0)) == NULL)
+ goto done;
+ err = -EPERM;
+ if (t == &ipip_fb_tunnel)
+ goto done;
+ dev = t->dev;
+ }
+ err = unregister_netdevice(dev);
+ break;
+
+ default:
+ err = -EINVAL;
+ }
+
+done:
+ MOD_DEC_USE_COUNT;
+ return err;
+}
+
+static struct net_device_stats *ipip_tunnel_get_stats(struct net_device *dev)
+{
+ return &(((struct ip_tunnel*)dev->priv)->stat);
+}
+
+static int ipip_tunnel_change_mtu(struct net_device *dev, int new_mtu)
+{
+ if (new_mtu < 68 || new_mtu > 0xFFF8 - sizeof(struct iphdr))
+ return -EINVAL;
+ dev->mtu = new_mtu;
+ return 0;
+}
+
+static void ipip_tunnel_init_gen(struct net_device *dev)
+{
+ struct ip_tunnel *t = (struct ip_tunnel*)dev->priv;
+
+ dev->uninit = ipip_tunnel_uninit;
+ dev->destructor = ipip_tunnel_destructor;
+ dev->hard_start_xmit = ipip_tunnel_xmit;
+ dev->get_stats = ipip_tunnel_get_stats;
+ dev->do_ioctl = ipip_tunnel_ioctl;
+ dev->change_mtu = ipip_tunnel_change_mtu;
+
+ dev->type = ARPHRD_TUNNEL;
+ dev->hard_header_len = LL_MAX_HEADER + sizeof(struct iphdr);
+ dev->mtu = 1500 - sizeof(struct iphdr);
+ dev->flags = IFF_NOARP;
+ dev->iflink = 0;
+ dev->addr_len = 4;
+ memcpy(dev->dev_addr, &t->parms.iph.saddr, 4);
+ memcpy(dev->broadcast, &t->parms.iph.daddr, 4);
+}
+
+static int ipip_tunnel_init(struct net_device *dev)
+{
+ struct net_device *tdev = NULL;
+ struct ip_tunnel *tunnel;
+ struct iphdr *iph;
+
+ tunnel = (struct ip_tunnel*)dev->priv;
+ iph = &tunnel->parms.iph;
+
+ ipip_tunnel_init_gen(dev);
+
+ if (iph->daddr) {
+ struct rtable *rt;
+ if (!ip_route_output(&rt, iph->daddr, iph->saddr, RT_TOS(iph->tos), tunnel->parms.link)) {
+ tdev = rt->u.dst.dev;
+ ip_rt_put(rt);
+ }
+ dev->flags |= IFF_POINTOPOINT;
+ }
+
+ if (!tdev && tunnel->parms.link)
+ tdev = __dev_get_by_index(tunnel->parms.link);
+
+ if (tdev) {
+ dev->hard_header_len = tdev->hard_header_len + sizeof(struct iphdr);
+ dev->mtu = tdev->mtu - sizeof(struct iphdr);
+ }
+ dev->iflink = tunnel->parms.link;
+
+ return 0;
+}
+
+#ifdef MODULE
+static int ipip_fb_tunnel_open(struct net_device *dev)
+{
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int ipip_fb_tunnel_close(struct net_device *dev)
+{
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+#endif
+
+int __init ipip_fb_tunnel_init(struct net_device *dev)
+{
+ struct iphdr *iph;
+
+ ipip_tunnel_init_gen(dev);
+#ifdef MODULE
+ dev->open = ipip_fb_tunnel_open;
+ dev->stop = ipip_fb_tunnel_close;
+#endif
+
+ iph = &ipip_fb_tunnel.parms.iph;
+ iph->version = 4;
+#ifndef CONFIG_NET_IPIP_IPV6
+ iph->protocol = IPPROTO_IPIP;
+#endif
+ iph->ihl = 5;
+
+ dev_hold(dev);
+ tunnels_wc[0] = &ipip_fb_tunnel;
+ return 0;
+}
+
+static struct inet_protocol ipip_protocol = {
+ handler: ipip_rcv,
+ err_handler: ipip_err,
+ protocol: IPPROTO_IPIP,
+ name: "IPIP"
+};
+
+#ifdef CONFIG_NET_IPIP_IPV6
+static struct inet_protocol ipip6_protocol = {
+ handler: ipip6_rcv,
+ err_handler: ipip6_err,
+ protocol: IPPROTO_IPV6,
+ name: "IPv6"
+};
+#endif
+
+static char banner[] __initdata =
+#ifdef CONFIG_NET_IPIP_IPV6
+ KERN_INFO "IPv{4,6} over IPv4 tunneling driver\n";
+#else
+ KERN_INFO "IPv4 over IPv4 tunneling driver\n";
+#endif
+
+int __init ipip_init(void)
+{
+ printk(banner);
+
+ ipip_fb_tunnel_dev.priv = (void*)&ipip_fb_tunnel;
+ register_netdev(&ipip_fb_tunnel_dev);
+ inet_add_protocol(&ipip_protocol);
+#ifdef CONFIG_NET_IPIP_IPV6
+ inet_add_protocol(&ipip6_protocol);
+#endif
+ return 0;
+}
+
+static void __exit ipip_fini(void)
+{
+#ifdef CONFIG_NET_IPIP_IPV6
+ if ( inet_del_protocol(&ipip6_protocol) < 0 )
+ printk(KERN_INFO "ipip close: can't remove ipv6 protocol\n");
+#endif
+ if ( inet_del_protocol(&ipip_protocol) < 0 )
+ printk(KERN_INFO "ipip close: can't remove protocol\n");
+
+ unregister_netdev(&ipip_fb_tunnel_dev);
+}
+
+#ifdef MODULE
+module_init(ipip_init);
+#endif
+module_exit(ipip_fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipmr.c b/uClinux-2.4.31-uc0/net/ipv4/ipmr.c
new file mode 100644
index 0000000..770c74d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipmr.c
@@ -0,0 +1,1757 @@
+/*
+ * IP multicast routing support for mrouted 3.6/3.8
+ *
+ * (c) 1995 Alan Cox, <alan@redhat.com>
+ * Linux Consultancy and Custom Driver Development
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Version: $Id: ipmr.c,v 1.65 2001/10/31 21:55:54 davem Exp $
+ *
+ * Fixes:
+ * Michael Chastain : Incorrect size of copying.
+ * Alan Cox : Added the cache manager code
+ * Alan Cox : Fixed the clone/copy bug and device race.
+ * Mike McLagan : Routing by source
+ * Malcolm Beattie : Buffer handling fixes.
+ * Alexey Kuznetsov : Double buffer free and other fixes.
+ * SVR Anand : Fixed several multicast bugs and problems.
+ * Alexey Kuznetsov : Status, optimisations and more.
+ * Brad Parker : Better behaviour on mrouted upcall
+ * overflow.
+ * Carlos Picoto : PIMv1 Support
+ * Pavlin Ivanov Radoslavov: PIMv2 Registers must checksum only PIM header
+ * Relax this requrement to work with older peers.
+ *
+ */
+
+#include <linux/config.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/timer.h>
+#include <linux/mm.h>
+#include <linux/kernel.h>
+#include <linux/fcntl.h>
+#include <linux/stat.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/igmp.h>
+#include <linux/proc_fs.h>
+#include <linux/mroute.h>
+#include <linux/init.h>
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/icmp.h>
+#include <net/udp.h>
+#include <net/raw.h>
+#include <linux/notifier.h>
+#include <linux/if_arp.h>
+#include <linux/netfilter_ipv4.h>
+#include <net/ipip.h>
+#include <net/checksum.h>
+
+#if defined(CONFIG_IP_PIMSM_V1) || defined(CONFIG_IP_PIMSM_V2)
+#define CONFIG_IP_PIMSM 1
+#endif
+
+static struct sock *mroute_socket;
+
+
+/* Big lock, protecting vif table, mrt cache and mroute socket state.
+ Note that the changes are semaphored via rtnl_lock.
+ */
+
+static rwlock_t mrt_lock = RW_LOCK_UNLOCKED;
+
+/*
+ * Multicast router control variables
+ */
+
+static struct vif_device vif_table[MAXVIFS]; /* Devices */
+static int maxvif;
+
+#define VIF_EXISTS(idx) (vif_table[idx].dev != NULL)
+
+int mroute_do_assert; /* Set in PIM assert */
+int mroute_do_pim;
+
+static struct mfc_cache *mfc_cache_array[MFC_LINES]; /* Forwarding cache */
+
+static struct mfc_cache *mfc_unres_queue; /* Queue of unresolved entries */
+atomic_t cache_resolve_queue_len; /* Size of unresolved */
+
+/* Special spinlock for queue of unresolved entries */
+static spinlock_t mfc_unres_lock = SPIN_LOCK_UNLOCKED;
+
+/* We return to original Alan's scheme. Hash table of resolved
+ entries is changed only in process context and protected
+ with weak lock mrt_lock. Queue of unresolved entries is protected
+ with strong spinlock mfc_unres_lock.
+
+ In this case data path is free of exclusive locks at all.
+ */
+
+kmem_cache_t *mrt_cachep;
+
+static int ip_mr_forward(struct sk_buff *skb, struct mfc_cache *cache, int local);
+static int ipmr_cache_report(struct sk_buff *pkt, vifi_t vifi, int assert);
+static int ipmr_fill_mroute(struct sk_buff *skb, struct mfc_cache *c, struct rtmsg *rtm);
+
+extern struct inet_protocol pim_protocol;
+
+static struct timer_list ipmr_expire_timer;
+
+/* Service routines creating virtual interfaces: DVMRP tunnels and PIMREG */
+
+static
+struct net_device *ipmr_new_tunnel(struct vifctl *v)
+{
+ struct net_device *dev;
+
+ dev = __dev_get_by_name("tunl0");
+
+ if (dev) {
+ int err;
+ struct ifreq ifr;
+ mm_segment_t oldfs;
+ struct ip_tunnel_parm p;
+ struct in_device *in_dev;
+
+ memset(&p, 0, sizeof(p));
+ p.iph.daddr = v->vifc_rmt_addr.s_addr;
+ p.iph.saddr = v->vifc_lcl_addr.s_addr;
+ p.iph.version = 4;
+ p.iph.ihl = 5;
+ p.iph.protocol = IPPROTO_IPIP;
+ sprintf(p.name, "dvmrp%d", v->vifc_vifi);
+ ifr.ifr_ifru.ifru_data = (void*)&p;
+
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ err = dev->do_ioctl(dev, &ifr, SIOCADDTUNNEL);
+ set_fs(oldfs);
+
+ dev = NULL;
+
+ if (err == 0 && (dev = __dev_get_by_name(p.name)) != NULL) {
+ dev->flags |= IFF_MULTICAST;
+
+ in_dev = __in_dev_get(dev);
+ if (in_dev == NULL && (in_dev = inetdev_init(dev)) == NULL)
+ goto failure;
+ in_dev->cnf.rp_filter = 0;
+
+ if (dev_open(dev))
+ goto failure;
+ }
+ }
+ return dev;
+
+failure:
+ unregister_netdevice(dev);
+ return NULL;
+}
+
+#ifdef CONFIG_IP_PIMSM
+
+static int reg_vif_num = -1;
+
+static int reg_vif_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ read_lock(&mrt_lock);
+ ((struct net_device_stats*)dev->priv)->tx_bytes += skb->len;
+ ((struct net_device_stats*)dev->priv)->tx_packets++;
+ ipmr_cache_report(skb, reg_vif_num, IGMPMSG_WHOLEPKT);
+ read_unlock(&mrt_lock);
+ kfree_skb(skb);
+ return 0;
+}
+
+static struct net_device_stats *reg_vif_get_stats(struct net_device *dev)
+{
+ return (struct net_device_stats*)dev->priv;
+}
+
+static
+struct net_device *ipmr_reg_vif(struct vifctl *v)
+{
+ struct net_device *dev;
+ struct in_device *in_dev;
+ int size;
+
+ size = sizeof(*dev) + sizeof(struct net_device_stats);
+ dev = kmalloc(size, GFP_KERNEL);
+ if (!dev)
+ return NULL;
+
+ memset(dev, 0, size);
+
+ dev->priv = dev + 1;
+
+ strcpy(dev->name, "pimreg");
+
+ dev->type = ARPHRD_PIMREG;
+ dev->mtu = 1500 - sizeof(struct iphdr) - 8;
+ dev->flags = IFF_NOARP;
+ dev->hard_start_xmit = reg_vif_xmit;
+ dev->get_stats = reg_vif_get_stats;
+ dev->features |= NETIF_F_DYNALLOC;
+
+ if (register_netdevice(dev)) {
+ kfree(dev);
+ return NULL;
+ }
+ dev->iflink = 0;
+
+ if ((in_dev = inetdev_init(dev)) == NULL)
+ goto failure;
+
+ in_dev->cnf.rp_filter = 0;
+
+ if (dev_open(dev))
+ goto failure;
+
+ return dev;
+
+failure:
+ unregister_netdevice(dev);
+ return NULL;
+}
+#endif
+
+/*
+ * Delete a VIF entry
+ */
+
+static int vif_delete(int vifi)
+{
+ struct vif_device *v;
+ struct net_device *dev;
+ struct in_device *in_dev;
+
+ if (vifi < 0 || vifi >= maxvif)
+ return -EADDRNOTAVAIL;
+
+ v = &vif_table[vifi];
+
+ write_lock_bh(&mrt_lock);
+ dev = v->dev;
+ v->dev = NULL;
+
+ if (!dev) {
+ write_unlock_bh(&mrt_lock);
+ return -EADDRNOTAVAIL;
+ }
+
+#ifdef CONFIG_IP_PIMSM
+ if (vifi == reg_vif_num)
+ reg_vif_num = -1;
+#endif
+
+ if (vifi+1 == maxvif) {
+ int tmp;
+ for (tmp=vifi-1; tmp>=0; tmp--) {
+ if (VIF_EXISTS(tmp))
+ break;
+ }
+ maxvif = tmp+1;
+ }
+
+ write_unlock_bh(&mrt_lock);
+
+ dev_set_allmulti(dev, -1);
+
+ if ((in_dev = __in_dev_get(dev)) != NULL) {
+ in_dev->cnf.mc_forwarding--;
+ ip_rt_multicast_event(in_dev);
+ }
+
+ if (v->flags&(VIFF_TUNNEL|VIFF_REGISTER))
+ unregister_netdevice(dev);
+
+ dev_put(dev);
+ return 0;
+}
+
+/* Destroy an unresolved cache entry, killing queued skbs
+ and reporting error to netlink readers.
+ */
+
+static void ipmr_destroy_unres(struct mfc_cache *c)
+{
+ struct sk_buff *skb;
+
+ atomic_dec(&cache_resolve_queue_len);
+
+ while((skb=skb_dequeue(&c->mfc_un.unres.unresolved))) {
+ if (skb->nh.iph->version == 0) {
+ struct nlmsghdr *nlh = (struct nlmsghdr *)skb_pull(skb, sizeof(struct iphdr));
+ nlh->nlmsg_type = NLMSG_ERROR;
+ nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+ skb_trim(skb, nlh->nlmsg_len);
+ ((struct nlmsgerr*)NLMSG_DATA(nlh))->error = -ETIMEDOUT;
+ netlink_unicast(rtnl, skb, NETLINK_CB(skb).dst_pid, MSG_DONTWAIT);
+ } else
+ kfree_skb(skb);
+ }
+
+ kmem_cache_free(mrt_cachep, c);
+}
+
+
+/* Single timer process for all the unresolved queue. */
+
+void ipmr_expire_process(unsigned long dummy)
+{
+ unsigned long now;
+ unsigned long expires;
+ struct mfc_cache *c, **cp;
+
+ if (!spin_trylock(&mfc_unres_lock)) {
+ mod_timer(&ipmr_expire_timer, jiffies+HZ/10);
+ return;
+ }
+
+ if (atomic_read(&cache_resolve_queue_len) == 0)
+ goto out;
+
+ now = jiffies;
+ expires = 10*HZ;
+ cp = &mfc_unres_queue;
+
+ while ((c=*cp) != NULL) {
+ long interval = c->mfc_un.unres.expires - now;
+
+ if (interval > 0) {
+ if (interval < expires)
+ expires = interval;
+ cp = &c->next;
+ continue;
+ }
+
+ *cp = c->next;
+
+ ipmr_destroy_unres(c);
+ }
+
+ if (atomic_read(&cache_resolve_queue_len))
+ mod_timer(&ipmr_expire_timer, jiffies + expires);
+
+out:
+ spin_unlock(&mfc_unres_lock);
+}
+
+/* Fill oifs list. It is called under write locked mrt_lock. */
+
+static void ipmr_update_threshoulds(struct mfc_cache *cache, unsigned char *ttls)
+{
+ int vifi;
+
+ cache->mfc_un.res.minvif = MAXVIFS;
+ cache->mfc_un.res.maxvif = 0;
+ memset(cache->mfc_un.res.ttls, 255, MAXVIFS);
+
+ for (vifi=0; vifi<maxvif; vifi++) {
+ if (VIF_EXISTS(vifi) && ttls[vifi] && ttls[vifi] < 255) {
+ cache->mfc_un.res.ttls[vifi] = ttls[vifi];
+ if (cache->mfc_un.res.minvif > vifi)
+ cache->mfc_un.res.minvif = vifi;
+ if (cache->mfc_un.res.maxvif <= vifi)
+ cache->mfc_un.res.maxvif = vifi + 1;
+ }
+ }
+}
+
+static int vif_add(struct vifctl *vifc, int mrtsock)
+{
+ int vifi = vifc->vifc_vifi;
+ struct vif_device *v = &vif_table[vifi];
+ struct net_device *dev;
+ struct in_device *in_dev;
+
+ /* Is vif busy ? */
+ if (VIF_EXISTS(vifi))
+ return -EADDRINUSE;
+
+ switch (vifc->vifc_flags) {
+#ifdef CONFIG_IP_PIMSM
+ case VIFF_REGISTER:
+ /*
+ * Special Purpose VIF in PIM
+ * All the packets will be sent to the daemon
+ */
+ if (reg_vif_num >= 0)
+ return -EADDRINUSE;
+ dev = ipmr_reg_vif(vifc);
+ if (!dev)
+ return -ENOBUFS;
+ break;
+#endif
+ case VIFF_TUNNEL:
+ dev = ipmr_new_tunnel(vifc);
+ if (!dev)
+ return -ENOBUFS;
+ break;
+ case 0:
+ dev=ip_dev_find(vifc->vifc_lcl_addr.s_addr);
+ if (!dev)
+ return -EADDRNOTAVAIL;
+ __dev_put(dev);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if ((in_dev = __in_dev_get(dev)) == NULL)
+ return -EADDRNOTAVAIL;
+ in_dev->cnf.mc_forwarding++;
+ dev_set_allmulti(dev, +1);
+ ip_rt_multicast_event(in_dev);
+
+ /*
+ * Fill in the VIF structures
+ */
+ v->rate_limit=vifc->vifc_rate_limit;
+ v->local=vifc->vifc_lcl_addr.s_addr;
+ v->remote=vifc->vifc_rmt_addr.s_addr;
+ v->flags=vifc->vifc_flags;
+ if (!mrtsock)
+ v->flags |= VIFF_STATIC;
+ v->threshold=vifc->vifc_threshold;
+ v->bytes_in = 0;
+ v->bytes_out = 0;
+ v->pkt_in = 0;
+ v->pkt_out = 0;
+ v->link = dev->ifindex;
+ if (v->flags&(VIFF_TUNNEL|VIFF_REGISTER))
+ v->link = dev->iflink;
+
+ /* And finish update writing critical data */
+ write_lock_bh(&mrt_lock);
+ dev_hold(dev);
+ v->dev=dev;
+#ifdef CONFIG_IP_PIMSM
+ if (v->flags&VIFF_REGISTER)
+ reg_vif_num = vifi;
+#endif
+ if (vifi+1 > maxvif)
+ maxvif = vifi+1;
+ write_unlock_bh(&mrt_lock);
+ return 0;
+}
+
+static struct mfc_cache *ipmr_cache_find(__u32 origin, __u32 mcastgrp)
+{
+ int line=MFC_HASH(mcastgrp,origin);
+ struct mfc_cache *c;
+
+ for (c=mfc_cache_array[line]; c; c = c->next) {
+ if (c->mfc_origin==origin && c->mfc_mcastgrp==mcastgrp)
+ break;
+ }
+ return c;
+}
+
+/*
+ * Allocate a multicast cache entry
+ */
+static struct mfc_cache *ipmr_cache_alloc(void)
+{
+ struct mfc_cache *c=kmem_cache_alloc(mrt_cachep, GFP_KERNEL);
+ if(c==NULL)
+ return NULL;
+ memset(c, 0, sizeof(*c));
+ c->mfc_un.res.minvif = MAXVIFS;
+ return c;
+}
+
+static struct mfc_cache *ipmr_cache_alloc_unres(void)
+{
+ struct mfc_cache *c=kmem_cache_alloc(mrt_cachep, GFP_ATOMIC);
+ if(c==NULL)
+ return NULL;
+ memset(c, 0, sizeof(*c));
+ skb_queue_head_init(&c->mfc_un.unres.unresolved);
+ c->mfc_un.unres.expires = jiffies + 10*HZ;
+ return c;
+}
+
+/*
+ * A cache entry has gone into a resolved state from queued
+ */
+
+static void ipmr_cache_resolve(struct mfc_cache *uc, struct mfc_cache *c)
+{
+ struct sk_buff *skb;
+
+ /*
+ * Play the pending entries through our router
+ */
+
+ while((skb=__skb_dequeue(&uc->mfc_un.unres.unresolved))) {
+ if (skb->nh.iph->version == 0) {
+ int err;
+ struct nlmsghdr *nlh = (struct nlmsghdr *)skb_pull(skb, sizeof(struct iphdr));
+
+ if (ipmr_fill_mroute(skb, c, NLMSG_DATA(nlh)) > 0) {
+ nlh->nlmsg_len = skb->tail - (u8*)nlh;
+ } else {
+ nlh->nlmsg_type = NLMSG_ERROR;
+ nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+ skb_trim(skb, nlh->nlmsg_len);
+ ((struct nlmsgerr*)NLMSG_DATA(nlh))->error = -EMSGSIZE;
+ }
+ err = netlink_unicast(rtnl, skb, NETLINK_CB(skb).dst_pid, MSG_DONTWAIT);
+ } else
+ ip_mr_forward(skb, c, 0);
+ }
+}
+
+/*
+ * Bounce a cache query up to mrouted. We could use netlink for this but mrouted
+ * expects the following bizarre scheme.
+ *
+ * Called under mrt_lock.
+ */
+
+static int ipmr_cache_report(struct sk_buff *pkt, vifi_t vifi, int assert)
+{
+ struct sk_buff *skb;
+ int ihl = pkt->nh.iph->ihl<<2;
+ struct igmphdr *igmp;
+ struct igmpmsg *msg;
+ int ret;
+
+#ifdef CONFIG_IP_PIMSM
+ if (assert == IGMPMSG_WHOLEPKT)
+ skb = skb_realloc_headroom(pkt, sizeof(struct iphdr));
+ else
+#endif
+ skb = alloc_skb(128, GFP_ATOMIC);
+
+ if(!skb)
+ return -ENOBUFS;
+
+#ifdef CONFIG_IP_PIMSM
+ if (assert == IGMPMSG_WHOLEPKT) {
+ /* Ugly, but we have no choice with this interface.
+ Duplicate old header, fix ihl, length etc.
+ And all this only to mangle msg->im_msgtype and
+ to set msg->im_mbz to "mbz" :-)
+ */
+ msg = (struct igmpmsg*)skb_push(skb, sizeof(struct iphdr));
+ skb->nh.raw = skb->h.raw = (u8*)msg;
+ memcpy(msg, pkt->nh.raw, sizeof(struct iphdr));
+ msg->im_msgtype = IGMPMSG_WHOLEPKT;
+ msg->im_mbz = 0;
+ msg->im_vif = reg_vif_num;
+ skb->nh.iph->ihl = sizeof(struct iphdr) >> 2;
+ skb->nh.iph->tot_len = htons(ntohs(pkt->nh.iph->tot_len) + sizeof(struct iphdr));
+ } else
+#endif
+ {
+
+ /*
+ * Copy the IP header
+ */
+
+ skb->nh.iph = (struct iphdr *)skb_put(skb, ihl);
+ memcpy(skb->data,pkt->data,ihl);
+ skb->nh.iph->protocol = 0; /* Flag to the kernel this is a route add */
+ msg = (struct igmpmsg*)skb->nh.iph;
+ msg->im_vif = vifi;
+ skb->dst = dst_clone(pkt->dst);
+
+ /*
+ * Add our header
+ */
+
+ igmp=(struct igmphdr *)skb_put(skb,sizeof(struct igmphdr));
+ igmp->type =
+ msg->im_msgtype = assert;
+ igmp->code = 0;
+ skb->nh.iph->tot_len=htons(skb->len); /* Fix the length */
+ skb->h.raw = skb->nh.raw;
+ }
+
+ if (mroute_socket == NULL) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ /*
+ * Deliver to mrouted
+ */
+ if ((ret=sock_queue_rcv_skb(mroute_socket,skb))<0) {
+ if (net_ratelimit())
+ printk(KERN_WARNING "mroute: pending queue full, dropping entries.\n");
+ kfree_skb(skb);
+ }
+
+ return ret;
+}
+
+/*
+ * Queue a packet for resolution. It gets locked cache entry!
+ */
+
+static int
+ipmr_cache_unresolved(vifi_t vifi, struct sk_buff *skb)
+{
+ int err;
+ struct mfc_cache *c;
+
+ spin_lock_bh(&mfc_unres_lock);
+ for (c=mfc_unres_queue; c; c=c->next) {
+ if (c->mfc_mcastgrp == skb->nh.iph->daddr &&
+ c->mfc_origin == skb->nh.iph->saddr)
+ break;
+ }
+
+ if (c == NULL) {
+ /*
+ * Create a new entry if allowable
+ */
+
+ if (atomic_read(&cache_resolve_queue_len)>=10 ||
+ (c=ipmr_cache_alloc_unres())==NULL) {
+ spin_unlock_bh(&mfc_unres_lock);
+
+ kfree_skb(skb);
+ return -ENOBUFS;
+ }
+
+ /*
+ * Fill in the new cache entry
+ */
+ c->mfc_parent=-1;
+ c->mfc_origin=skb->nh.iph->saddr;
+ c->mfc_mcastgrp=skb->nh.iph->daddr;
+
+ /*
+ * Reflect first query at mrouted.
+ */
+ if ((err = ipmr_cache_report(skb, vifi, IGMPMSG_NOCACHE))<0) {
+ /* If the report failed throw the cache entry
+ out - Brad Parker
+ */
+ spin_unlock_bh(&mfc_unres_lock);
+
+ kmem_cache_free(mrt_cachep, c);
+ kfree_skb(skb);
+ return err;
+ }
+
+ atomic_inc(&cache_resolve_queue_len);
+ c->next = mfc_unres_queue;
+ mfc_unres_queue = c;
+
+ mod_timer(&ipmr_expire_timer, c->mfc_un.unres.expires);
+ }
+
+ /*
+ * See if we can append the packet
+ */
+ if (c->mfc_un.unres.unresolved.qlen>3) {
+ kfree_skb(skb);
+ err = -ENOBUFS;
+ } else {
+ skb_queue_tail(&c->mfc_un.unres.unresolved,skb);
+ err = 0;
+ }
+
+ spin_unlock_bh(&mfc_unres_lock);
+ return err;
+}
+
+/*
+ * MFC cache manipulation by user space mroute daemon
+ */
+
+int ipmr_mfc_delete(struct mfcctl *mfc)
+{
+ int line;
+ struct mfc_cache *c, **cp;
+
+ line=MFC_HASH(mfc->mfcc_mcastgrp.s_addr, mfc->mfcc_origin.s_addr);
+
+ for (cp=&mfc_cache_array[line]; (c=*cp) != NULL; cp = &c->next) {
+ if (c->mfc_origin == mfc->mfcc_origin.s_addr &&
+ c->mfc_mcastgrp == mfc->mfcc_mcastgrp.s_addr) {
+ write_lock_bh(&mrt_lock);
+ *cp = c->next;
+ write_unlock_bh(&mrt_lock);
+
+ kmem_cache_free(mrt_cachep, c);
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+int ipmr_mfc_add(struct mfcctl *mfc, int mrtsock)
+{
+ int line;
+ struct mfc_cache *uc, *c, **cp;
+
+ line=MFC_HASH(mfc->mfcc_mcastgrp.s_addr, mfc->mfcc_origin.s_addr);
+
+ for (cp=&mfc_cache_array[line]; (c=*cp) != NULL; cp = &c->next) {
+ if (c->mfc_origin == mfc->mfcc_origin.s_addr &&
+ c->mfc_mcastgrp == mfc->mfcc_mcastgrp.s_addr)
+ break;
+ }
+
+ if (c != NULL) {
+ write_lock_bh(&mrt_lock);
+ c->mfc_parent = mfc->mfcc_parent;
+ ipmr_update_threshoulds(c, mfc->mfcc_ttls);
+ if (!mrtsock)
+ c->mfc_flags |= MFC_STATIC;
+ write_unlock_bh(&mrt_lock);
+ return 0;
+ }
+
+ if(!MULTICAST(mfc->mfcc_mcastgrp.s_addr))
+ return -EINVAL;
+
+ c=ipmr_cache_alloc();
+ if (c==NULL)
+ return -ENOMEM;
+
+ c->mfc_origin=mfc->mfcc_origin.s_addr;
+ c->mfc_mcastgrp=mfc->mfcc_mcastgrp.s_addr;
+ c->mfc_parent=mfc->mfcc_parent;
+ ipmr_update_threshoulds(c, mfc->mfcc_ttls);
+ if (!mrtsock)
+ c->mfc_flags |= MFC_STATIC;
+
+ write_lock_bh(&mrt_lock);
+ c->next = mfc_cache_array[line];
+ mfc_cache_array[line] = c;
+ write_unlock_bh(&mrt_lock);
+
+ /*
+ * Check to see if we resolved a queued list. If so we
+ * need to send on the frames and tidy up.
+ */
+ spin_lock_bh(&mfc_unres_lock);
+ for (cp = &mfc_unres_queue; (uc=*cp) != NULL;
+ cp = &uc->next) {
+ if (uc->mfc_origin == c->mfc_origin &&
+ uc->mfc_mcastgrp == c->mfc_mcastgrp) {
+ *cp = uc->next;
+ if (atomic_dec_and_test(&cache_resolve_queue_len))
+ del_timer(&ipmr_expire_timer);
+ break;
+ }
+ }
+ spin_unlock_bh(&mfc_unres_lock);
+
+ if (uc) {
+ ipmr_cache_resolve(uc, c);
+ kmem_cache_free(mrt_cachep, uc);
+ }
+ return 0;
+}
+
+/*
+ * Close the multicast socket, and clear the vif tables etc
+ */
+
+static void mroute_clean_tables(struct sock *sk)
+{
+ int i;
+
+ /*
+ * Shut down all active vif entries
+ */
+ for(i=0; i<maxvif; i++) {
+ if (!(vif_table[i].flags&VIFF_STATIC))
+ vif_delete(i);
+ }
+
+ /*
+ * Wipe the cache
+ */
+ for (i=0;i<MFC_LINES;i++) {
+ struct mfc_cache *c, **cp;
+
+ cp = &mfc_cache_array[i];
+ while ((c = *cp) != NULL) {
+ if (c->mfc_flags&MFC_STATIC) {
+ cp = &c->next;
+ continue;
+ }
+ write_lock_bh(&mrt_lock);
+ *cp = c->next;
+ write_unlock_bh(&mrt_lock);
+
+ kmem_cache_free(mrt_cachep, c);
+ }
+ }
+
+ if (atomic_read(&cache_resolve_queue_len) != 0) {
+ struct mfc_cache *c;
+
+ spin_lock_bh(&mfc_unres_lock);
+ while (mfc_unres_queue != NULL) {
+ c = mfc_unres_queue;
+ mfc_unres_queue = c->next;
+ spin_unlock_bh(&mfc_unres_lock);
+
+ ipmr_destroy_unres(c);
+
+ spin_lock_bh(&mfc_unres_lock);
+ }
+ spin_unlock_bh(&mfc_unres_lock);
+ }
+}
+
+static void mrtsock_destruct(struct sock *sk)
+{
+ rtnl_lock();
+ if (sk == mroute_socket) {
+ ipv4_devconf.mc_forwarding--;
+
+ write_lock_bh(&mrt_lock);
+ mroute_socket=NULL;
+ write_unlock_bh(&mrt_lock);
+
+ mroute_clean_tables(sk);
+ }
+ rtnl_unlock();
+}
+
+/*
+ * Socket options and virtual interface manipulation. The whole
+ * virtual interface system is a complete heap, but unfortunately
+ * that's how BSD mrouted happens to think. Maybe one day with a proper
+ * MOSPF/PIM router set up we can clean this up.
+ */
+
+int ip_mroute_setsockopt(struct sock *sk,int optname,char *optval,int optlen)
+{
+ int ret;
+ struct vifctl vif;
+ struct mfcctl mfc;
+
+ if(optname!=MRT_INIT)
+ {
+ if(sk!=mroute_socket && !capable(CAP_NET_ADMIN))
+ return -EACCES;
+ }
+
+ switch(optname)
+ {
+ case MRT_INIT:
+ if(sk->type!=SOCK_RAW || sk->num!=IPPROTO_IGMP)
+ return -EOPNOTSUPP;
+ if(optlen!=sizeof(int))
+ return -ENOPROTOOPT;
+
+ rtnl_lock();
+ if (mroute_socket) {
+ rtnl_unlock();
+ return -EADDRINUSE;
+ }
+
+ ret = ip_ra_control(sk, 1, mrtsock_destruct);
+ if (ret == 0) {
+ write_lock_bh(&mrt_lock);
+ mroute_socket=sk;
+ write_unlock_bh(&mrt_lock);
+
+ ipv4_devconf.mc_forwarding++;
+ }
+ rtnl_unlock();
+ return ret;
+ case MRT_DONE:
+ if (sk!=mroute_socket)
+ return -EACCES;
+ return ip_ra_control(sk, 0, NULL);
+ case MRT_ADD_VIF:
+ case MRT_DEL_VIF:
+ if(optlen!=sizeof(vif))
+ return -EINVAL;
+ if (copy_from_user(&vif,optval,sizeof(vif)))
+ return -EFAULT;
+ if(vif.vifc_vifi >= MAXVIFS)
+ return -ENFILE;
+ rtnl_lock();
+ if (optname==MRT_ADD_VIF) {
+ ret = vif_add(&vif, sk==mroute_socket);
+ } else {
+ ret = vif_delete(vif.vifc_vifi);
+ }
+ rtnl_unlock();
+ return ret;
+
+ /*
+ * Manipulate the forwarding caches. These live
+ * in a sort of kernel/user symbiosis.
+ */
+ case MRT_ADD_MFC:
+ case MRT_DEL_MFC:
+ if(optlen!=sizeof(mfc))
+ return -EINVAL;
+ if (copy_from_user(&mfc,optval, sizeof(mfc)))
+ return -EFAULT;
+ rtnl_lock();
+ if (optname==MRT_DEL_MFC)
+ ret = ipmr_mfc_delete(&mfc);
+ else
+ ret = ipmr_mfc_add(&mfc, sk==mroute_socket);
+ rtnl_unlock();
+ return ret;
+ /*
+ * Control PIM assert.
+ */
+ case MRT_ASSERT:
+ {
+ int v;
+ if(get_user(v,(int *)optval))
+ return -EFAULT;
+ mroute_do_assert=(v)?1:0;
+ return 0;
+ }
+#ifdef CONFIG_IP_PIMSM
+ case MRT_PIM:
+ {
+ int v;
+ if(get_user(v,(int *)optval))
+ return -EFAULT;
+ v = (v)?1:0;
+ rtnl_lock();
+ if (v != mroute_do_pim) {
+ mroute_do_pim = v;
+ mroute_do_assert = v;
+#ifdef CONFIG_IP_PIMSM_V2
+ if (mroute_do_pim)
+ inet_add_protocol(&pim_protocol);
+ else
+ inet_del_protocol(&pim_protocol);
+#endif
+ }
+ rtnl_unlock();
+ return 0;
+ }
+#endif
+ /*
+ * Spurious command, or MRT_VERSION which you cannot
+ * set.
+ */
+ default:
+ return -ENOPROTOOPT;
+ }
+}
+
+/*
+ * Getsock opt support for the multicast routing system.
+ */
+
+int ip_mroute_getsockopt(struct sock *sk,int optname,char *optval,int *optlen)
+{
+ int olr;
+ int val;
+
+ if(optname!=MRT_VERSION &&
+#ifdef CONFIG_IP_PIMSM
+ optname!=MRT_PIM &&
+#endif
+ optname!=MRT_ASSERT)
+ return -ENOPROTOOPT;
+
+ if (get_user(olr, optlen))
+ return -EFAULT;
+
+ olr = min_t(unsigned int, olr, sizeof(int));
+ if (olr < 0)
+ return -EINVAL;
+
+ if(put_user(olr,optlen))
+ return -EFAULT;
+ if(optname==MRT_VERSION)
+ val=0x0305;
+#ifdef CONFIG_IP_PIMSM
+ else if(optname==MRT_PIM)
+ val=mroute_do_pim;
+#endif
+ else
+ val=mroute_do_assert;
+ if(copy_to_user(optval,&val,olr))
+ return -EFAULT;
+ return 0;
+}
+
+/*
+ * The IP multicast ioctl support routines.
+ */
+
+int ipmr_ioctl(struct sock *sk, int cmd, unsigned long arg)
+{
+ struct sioc_sg_req sr;
+ struct sioc_vif_req vr;
+ struct vif_device *vif;
+ struct mfc_cache *c;
+
+ switch(cmd)
+ {
+ case SIOCGETVIFCNT:
+ if (copy_from_user(&vr,(void *)arg,sizeof(vr)))
+ return -EFAULT;
+ if(vr.vifi>=maxvif)
+ return -EINVAL;
+ read_lock(&mrt_lock);
+ vif=&vif_table[vr.vifi];
+ if(VIF_EXISTS(vr.vifi)) {
+ vr.icount=vif->pkt_in;
+ vr.ocount=vif->pkt_out;
+ vr.ibytes=vif->bytes_in;
+ vr.obytes=vif->bytes_out;
+ read_unlock(&mrt_lock);
+
+ if (copy_to_user((void *)arg,&vr,sizeof(vr)))
+ return -EFAULT;
+ return 0;
+ }
+ read_unlock(&mrt_lock);
+ return -EADDRNOTAVAIL;
+ case SIOCGETSGCNT:
+ if (copy_from_user(&sr,(void *)arg,sizeof(sr)))
+ return -EFAULT;
+
+ read_lock(&mrt_lock);
+ c = ipmr_cache_find(sr.src.s_addr, sr.grp.s_addr);
+ if (c) {
+ sr.pktcnt = c->mfc_un.res.pkt;
+ sr.bytecnt = c->mfc_un.res.bytes;
+ sr.wrong_if = c->mfc_un.res.wrong_if;
+ read_unlock(&mrt_lock);
+
+ if (copy_to_user((void *)arg,&sr,sizeof(sr)))
+ return -EFAULT;
+ return 0;
+ }
+ read_unlock(&mrt_lock);
+ return -EADDRNOTAVAIL;
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+
+static int ipmr_device_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct vif_device *v;
+ int ct;
+ if (event != NETDEV_UNREGISTER)
+ return NOTIFY_DONE;
+ v=&vif_table[0];
+ for(ct=0;ct<maxvif;ct++,v++) {
+ if (v->dev==ptr)
+ vif_delete(ct);
+ }
+ return NOTIFY_DONE;
+}
+
+
+static struct notifier_block ip_mr_notifier={
+ ipmr_device_event,
+ NULL,
+ 0
+};
+
+/*
+ * Encapsulate a packet by attaching a valid IPIP header to it.
+ * This avoids tunnel drivers and other mess and gives us the speed so
+ * important for multicast video.
+ */
+
+static void ip_encap(struct sk_buff *skb, u32 saddr, u32 daddr)
+{
+ struct iphdr *iph = (struct iphdr *)skb_push(skb,sizeof(struct iphdr));
+
+ iph->version = 4;
+ iph->tos = skb->nh.iph->tos;
+ iph->ttl = skb->nh.iph->ttl;
+ iph->frag_off = 0;
+ iph->daddr = daddr;
+ iph->saddr = saddr;
+ iph->protocol = IPPROTO_IPIP;
+ iph->ihl = 5;
+ iph->tot_len = htons(skb->len);
+ ip_select_ident(iph, skb->dst, NULL);
+ ip_send_check(iph);
+
+ skb->h.ipiph = skb->nh.iph;
+ skb->nh.iph = iph;
+ memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
+ nf_reset(skb);
+}
+
+static inline int ipmr_forward_finish(struct sk_buff *skb)
+{
+ struct ip_options *opt = &(IPCB(skb)->opt);
+ struct dst_entry *dst = skb->dst;
+
+ if (unlikely(opt->optlen))
+ ip_forward_options(skb);
+
+ if (skb->len <= dst->pmtu)
+ return dst->output(skb);
+ else
+ return ip_fragment(skb, dst->output);
+}
+
+/*
+ * Processing handlers for ipmr_forward
+ */
+
+static void ipmr_queue_xmit(struct sk_buff *skb, struct mfc_cache *c,
+ int vifi, int last)
+{
+ struct iphdr *iph = skb->nh.iph;
+ struct vif_device *vif = &vif_table[vifi];
+ struct net_device *dev;
+ struct rtable *rt;
+ int encap = 0;
+ struct sk_buff *skb2;
+
+ if (vif->dev == NULL)
+ return;
+
+#ifdef CONFIG_IP_PIMSM
+ if (vif->flags & VIFF_REGISTER) {
+ vif->pkt_out++;
+ vif->bytes_out+=skb->len;
+ ((struct net_device_stats*)vif->dev->priv)->tx_bytes += skb->len;
+ ((struct net_device_stats*)vif->dev->priv)->tx_packets++;
+ ipmr_cache_report(skb, vifi, IGMPMSG_WHOLEPKT);
+ return;
+ }
+#endif
+
+ if (vif->flags&VIFF_TUNNEL) {
+ if (ip_route_output(&rt, vif->remote, vif->local, RT_TOS(iph->tos), vif->link))
+ return;
+ encap = sizeof(struct iphdr);
+ } else {
+ if (ip_route_output(&rt, iph->daddr, 0, RT_TOS(iph->tos), vif->link))
+ return;
+ }
+
+ dev = rt->u.dst.dev;
+
+ if (skb->len+encap > rt->u.dst.pmtu && (ntohs(iph->frag_off) & IP_DF)) {
+ /* Do not fragment multicasts. Alas, IPv4 does not
+ allow to send ICMP, so that packets will disappear
+ to blackhole.
+ */
+
+ IP_INC_STATS_BH(IpFragFails);
+ ip_rt_put(rt);
+ return;
+ }
+
+ encap += dev->hard_header_len;
+
+ if (skb_headroom(skb) < encap || skb_cloned(skb) || !last)
+ skb2 = skb_realloc_headroom(skb, (encap + 15)&~15);
+ else if (atomic_read(&skb->users) != 1)
+ skb2 = skb_clone(skb, GFP_ATOMIC);
+ else {
+ atomic_inc(&skb->users);
+ skb2 = skb;
+ }
+
+ if (skb2 == NULL) {
+ ip_rt_put(rt);
+ return;
+ }
+
+ vif->pkt_out++;
+ vif->bytes_out+=skb->len;
+
+ dst_release(skb2->dst);
+ skb2->dst = &rt->u.dst;
+ iph = skb2->nh.iph;
+ ip_decrease_ttl(iph);
+
+ /* FIXME: forward and output firewalls used to be called here.
+ * What do we do with netfilter? -- RR */
+ if (vif->flags & VIFF_TUNNEL) {
+ ip_encap(skb2, vif->local, vif->remote);
+ /* FIXME: extra output firewall step used to be here. --RR */
+ ((struct ip_tunnel *)vif->dev->priv)->stat.tx_packets++;
+ ((struct ip_tunnel *)vif->dev->priv)->stat.tx_bytes+=skb2->len;
+ }
+
+ IPCB(skb2)->flags |= IPSKB_FORWARDED;
+
+ /*
+ * RFC1584 teaches, that DVMRP/PIM router must deliver packets locally
+ * not only before forwarding, but after forwarding on all output
+ * interfaces. It is clear, if mrouter runs a multicasting
+ * program, it should receive packets not depending to what interface
+ * program is joined.
+ * If we will not make it, the program will have to join on all
+ * interfaces. On the other hand, multihoming host (or router, but
+ * not mrouter) cannot join to more than one interface - it will
+ * result in receiving multiple packets.
+ */
+ NF_HOOK(PF_INET, NF_IP_FORWARD, skb2, skb->dev, dev,
+ ipmr_forward_finish);
+}
+
+int ipmr_find_vif(struct net_device *dev)
+{
+ int ct;
+ for (ct=maxvif-1; ct>=0; ct--) {
+ if (vif_table[ct].dev == dev)
+ break;
+ }
+ return ct;
+}
+
+/* "local" means that we should preserve one skb (for local delivery) */
+
+int ip_mr_forward(struct sk_buff *skb, struct mfc_cache *cache, int local)
+{
+ int psend = -1;
+ int vif, ct;
+
+ vif = cache->mfc_parent;
+ cache->mfc_un.res.pkt++;
+ cache->mfc_un.res.bytes += skb->len;
+
+ /*
+ * Wrong interface: drop packet and (maybe) send PIM assert.
+ */
+ if (vif_table[vif].dev != skb->dev) {
+ int true_vifi;
+
+ if (((struct rtable*)skb->dst)->key.iif == 0) {
+ /* It is our own packet, looped back.
+ Very complicated situation...
+
+ The best workaround until routing daemons will be
+ fixed is not to redistribute packet, if it was
+ send through wrong interface. It means, that
+ multicast applications WILL NOT work for
+ (S,G), which have default multicast route pointing
+ to wrong oif. In any case, it is not a good
+ idea to use multicasting applications on router.
+ */
+ goto dont_forward;
+ }
+
+ cache->mfc_un.res.wrong_if++;
+ true_vifi = ipmr_find_vif(skb->dev);
+
+ if (true_vifi >= 0 && mroute_do_assert &&
+ /* pimsm uses asserts, when switching from RPT to SPT,
+ so that we cannot check that packet arrived on an oif.
+ It is bad, but otherwise we would need to move pretty
+ large chunk of pimd to kernel. Ough... --ANK
+ */
+ (mroute_do_pim || cache->mfc_un.res.ttls[true_vifi] < 255) &&
+ jiffies - cache->mfc_un.res.last_assert > MFC_ASSERT_THRESH) {
+ cache->mfc_un.res.last_assert = jiffies;
+ ipmr_cache_report(skb, true_vifi, IGMPMSG_WRONGVIF);
+ }
+ goto dont_forward;
+ }
+
+ vif_table[vif].pkt_in++;
+ vif_table[vif].bytes_in+=skb->len;
+
+ /*
+ * Forward the frame
+ */
+ for (ct = cache->mfc_un.res.maxvif-1; ct >= cache->mfc_un.res.minvif; ct--) {
+ if (skb->nh.iph->ttl > cache->mfc_un.res.ttls[ct]) {
+ if (psend != -1)
+ ipmr_queue_xmit(skb, cache, psend, 0);
+ psend=ct;
+ }
+ }
+ if (psend != -1)
+ ipmr_queue_xmit(skb, cache, psend, !local);
+
+dont_forward:
+ if (!local)
+ kfree_skb(skb);
+ return 0;
+}
+
+
+/*
+ * Multicast packets for forwarding arrive here
+ */
+
+int ip_mr_input(struct sk_buff *skb)
+{
+ struct mfc_cache *cache;
+ int local = ((struct rtable*)skb->dst)->rt_flags&RTCF_LOCAL;
+
+ /* Packet is looped back after forward, it should not be
+ forwarded second time, but still can be delivered locally.
+ */
+ if (IPCB(skb)->flags&IPSKB_FORWARDED)
+ goto dont_forward;
+
+ if (!local) {
+ if (IPCB(skb)->opt.router_alert) {
+ if (ip_call_ra_chain(skb))
+ return 0;
+ } else if (skb->nh.iph->protocol == IPPROTO_IGMP){
+ /* IGMPv1 (and broken IGMPv2 implementations sort of
+ Cisco IOS <= 11.2(8)) do not put router alert
+ option to IGMP packets destined to routable
+ groups. It is very bad, because it means
+ that we can forward NO IGMP messages.
+ */
+ read_lock(&mrt_lock);
+ if (mroute_socket) {
+ raw_rcv(mroute_socket, skb);
+ read_unlock(&mrt_lock);
+ return 0;
+ }
+ read_unlock(&mrt_lock);
+ }
+ }
+
+ read_lock(&mrt_lock);
+ cache = ipmr_cache_find(skb->nh.iph->saddr, skb->nh.iph->daddr);
+
+ /*
+ * No usable cache entry
+ */
+ if (cache==NULL) {
+ int vif;
+
+ if (local) {
+ struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
+ ip_local_deliver(skb);
+ if (skb2 == NULL) {
+ read_unlock(&mrt_lock);
+ return -ENOBUFS;
+ }
+ skb = skb2;
+ }
+
+ vif = ipmr_find_vif(skb->dev);
+ if (vif >= 0) {
+ int err = ipmr_cache_unresolved(vif, skb);
+ read_unlock(&mrt_lock);
+
+ return err;
+ }
+ read_unlock(&mrt_lock);
+ kfree_skb(skb);
+ return -ENODEV;
+ }
+
+ ip_mr_forward(skb, cache, local);
+
+ read_unlock(&mrt_lock);
+
+ if (local)
+ return ip_local_deliver(skb);
+
+ return 0;
+
+dont_forward:
+ if (local)
+ return ip_local_deliver(skb);
+ kfree_skb(skb);
+ return 0;
+}
+
+#ifdef CONFIG_IP_PIMSM_V1
+/*
+ * Handle IGMP messages of PIMv1
+ */
+
+int pim_rcv_v1(struct sk_buff * skb)
+{
+ struct igmphdr *pim = (struct igmphdr*)skb->h.raw;
+ struct iphdr *encap;
+ struct net_device *reg_dev = NULL;
+
+ if (skb_is_nonlinear(skb)) {
+ if (skb_linearize(skb, GFP_ATOMIC) != 0) {
+ kfree_skb(skb);
+ return -ENOMEM;
+ }
+ pim = (struct igmphdr*)skb->h.raw;
+ }
+
+ if (!mroute_do_pim ||
+ skb->len < sizeof(*pim) + sizeof(*encap) ||
+ pim->group != PIM_V1_VERSION || pim->code != PIM_V1_REGISTER) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ encap = (struct iphdr*)(skb->h.raw + sizeof(struct igmphdr));
+ /*
+ Check that:
+ a. packet is really destinted to a multicast group
+ b. packet is not a NULL-REGISTER
+ c. packet is not truncated
+ */
+ if (!MULTICAST(encap->daddr) ||
+ ntohs(encap->tot_len) == 0 ||
+ ntohs(encap->tot_len) + sizeof(*pim) > skb->len) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ read_lock(&mrt_lock);
+ if (reg_vif_num >= 0)
+ reg_dev = vif_table[reg_vif_num].dev;
+ if (reg_dev)
+ dev_hold(reg_dev);
+ read_unlock(&mrt_lock);
+
+ if (reg_dev == NULL) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ skb->mac.raw = skb->nh.raw;
+ skb_pull(skb, (u8*)encap - skb->data);
+ skb->nh.iph = (struct iphdr *)skb->data;
+ skb->dev = reg_dev;
+ memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
+ skb->protocol = htons(ETH_P_IP);
+ skb->ip_summed = 0;
+ skb->pkt_type = PACKET_HOST;
+ dst_release(skb->dst);
+ skb->dst = NULL;
+ ((struct net_device_stats*)reg_dev->priv)->rx_bytes += skb->len;
+ ((struct net_device_stats*)reg_dev->priv)->rx_packets++;
+ nf_reset(skb);
+ netif_rx(skb);
+ dev_put(reg_dev);
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_IP_PIMSM_V2
+int pim_rcv(struct sk_buff * skb)
+{
+ struct pimreghdr *pim = (struct pimreghdr*)skb->h.raw;
+ struct iphdr *encap;
+ struct net_device *reg_dev = NULL;
+
+ if (skb_is_nonlinear(skb)) {
+ if (skb_linearize(skb, GFP_ATOMIC) != 0) {
+ kfree_skb(skb);
+ return -ENOMEM;
+ }
+ pim = (struct pimreghdr*)skb->h.raw;
+ }
+
+ if (skb->len < sizeof(*pim) + sizeof(*encap) ||
+ pim->type != ((PIM_VERSION<<4)|(PIM_REGISTER)) ||
+ (pim->flags&PIM_NULL_REGISTER) ||
+ (ip_compute_csum((void *)pim, sizeof(*pim)) != 0 &&
+ ip_compute_csum((void *)pim, skb->len))) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ /* check if the inner packet is destined to mcast group */
+ encap = (struct iphdr*)(skb->h.raw + sizeof(struct pimreghdr));
+ if (!MULTICAST(encap->daddr) ||
+ ntohs(encap->tot_len) == 0 ||
+ ntohs(encap->tot_len) + sizeof(*pim) > skb->len) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ read_lock(&mrt_lock);
+ if (reg_vif_num >= 0)
+ reg_dev = vif_table[reg_vif_num].dev;
+ if (reg_dev)
+ dev_hold(reg_dev);
+ read_unlock(&mrt_lock);
+
+ if (reg_dev == NULL) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ skb->mac.raw = skb->nh.raw;
+ skb_pull(skb, (u8*)encap - skb->data);
+ skb->nh.iph = (struct iphdr *)skb->data;
+ skb->dev = reg_dev;
+ memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
+ skb->protocol = htons(ETH_P_IP);
+ skb->ip_summed = 0;
+ skb->pkt_type = PACKET_HOST;
+ dst_release(skb->dst);
+ ((struct net_device_stats*)reg_dev->priv)->rx_bytes += skb->len;
+ ((struct net_device_stats*)reg_dev->priv)->rx_packets++;
+ skb->dst = NULL;
+ nf_reset(skb);
+ netif_rx(skb);
+ dev_put(reg_dev);
+ return 0;
+}
+#endif
+
+static int
+ipmr_fill_mroute(struct sk_buff *skb, struct mfc_cache *c, struct rtmsg *rtm)
+{
+ int ct;
+ struct rtnexthop *nhp;
+ struct net_device *dev = vif_table[c->mfc_parent].dev;
+ u8 *b = skb->tail;
+ struct rtattr *mp_head;
+
+ if (dev)
+ RTA_PUT(skb, RTA_IIF, 4, &dev->ifindex);
+
+ mp_head = (struct rtattr*)skb_put(skb, RTA_LENGTH(0));
+
+ for (ct = c->mfc_un.res.minvif; ct < c->mfc_un.res.maxvif; ct++) {
+ if (c->mfc_un.res.ttls[ct] < 255) {
+ if (skb_tailroom(skb) < RTA_ALIGN(RTA_ALIGN(sizeof(*nhp)) + 4))
+ goto rtattr_failure;
+ nhp = (struct rtnexthop*)skb_put(skb, RTA_ALIGN(sizeof(*nhp)));
+ nhp->rtnh_flags = 0;
+ nhp->rtnh_hops = c->mfc_un.res.ttls[ct];
+ nhp->rtnh_ifindex = vif_table[ct].dev->ifindex;
+ nhp->rtnh_len = sizeof(*nhp);
+ }
+ }
+ mp_head->rta_type = RTA_MULTIPATH;
+ mp_head->rta_len = skb->tail - (u8*)mp_head;
+ rtm->rtm_type = RTN_MULTICAST;
+ return 1;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -EMSGSIZE;
+}
+
+int ipmr_get_route(struct sk_buff *skb, struct rtmsg *rtm, int nowait)
+{
+ int err;
+ struct mfc_cache *cache;
+ struct rtable *rt = (struct rtable*)skb->dst;
+
+ read_lock(&mrt_lock);
+ cache = ipmr_cache_find(rt->rt_src, rt->rt_dst);
+
+ if (cache==NULL) {
+ struct net_device *dev;
+ int vif;
+
+ if (nowait) {
+ read_unlock(&mrt_lock);
+ return -EAGAIN;
+ }
+
+ dev = skb->dev;
+ if (dev == NULL || (vif = ipmr_find_vif(dev)) < 0) {
+ read_unlock(&mrt_lock);
+ return -ENODEV;
+ }
+ skb->nh.raw = skb_push(skb, sizeof(struct iphdr));
+ skb->nh.iph->ihl = sizeof(struct iphdr)>>2;
+ skb->nh.iph->saddr = rt->rt_src;
+ skb->nh.iph->daddr = rt->rt_dst;
+ skb->nh.iph->version = 0;
+ err = ipmr_cache_unresolved(vif, skb);
+ read_unlock(&mrt_lock);
+ return err;
+ }
+
+ if (!nowait && (rtm->rtm_flags&RTM_F_NOTIFY))
+ cache->mfc_flags |= MFC_NOTIFY;
+ err = ipmr_fill_mroute(skb, cache, rtm);
+ read_unlock(&mrt_lock);
+ return err;
+}
+
+#ifdef CONFIG_PROC_FS
+/*
+ * The /proc interfaces to multicast routing /proc/ip_mr_cache /proc/ip_mr_vif
+ */
+
+static int ipmr_vif_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct vif_device *vif;
+ int len=0;
+ off_t pos=0;
+ off_t begin=0;
+ int size;
+ int ct;
+
+ len += sprintf(buffer,
+ "Interface BytesIn PktsIn BytesOut PktsOut Flags Local Remote\n");
+ pos=len;
+
+ read_lock(&mrt_lock);
+ for (ct=0;ct<maxvif;ct++)
+ {
+ char *name = "none";
+ vif=&vif_table[ct];
+ if(!VIF_EXISTS(ct))
+ continue;
+ if (vif->dev)
+ name = vif->dev->name;
+ size = sprintf(buffer+len, "%2d %-10s %8ld %7ld %8ld %7ld %05X %08X %08X\n",
+ ct, name, vif->bytes_in, vif->pkt_in, vif->bytes_out, vif->pkt_out,
+ vif->flags, vif->local, vif->remote);
+ len+=size;
+ pos+=size;
+ if(pos<offset)
+ {
+ len=0;
+ begin=pos;
+ }
+ if(pos>offset+length)
+ break;
+ }
+ read_unlock(&mrt_lock);
+
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ if (len<0)
+ len = 0;
+ return len;
+}
+
+static int ipmr_mfc_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct mfc_cache *mfc;
+ int len=0;
+ off_t pos=0;
+ off_t begin=0;
+ int size;
+ int ct;
+
+ len += sprintf(buffer,
+ "Group Origin Iif Pkts Bytes Wrong Oifs\n");
+ pos=len;
+
+ read_lock(&mrt_lock);
+ for (ct=0;ct<MFC_LINES;ct++)
+ {
+ for(mfc=mfc_cache_array[ct]; mfc; mfc=mfc->next)
+ {
+ int n;
+
+ /*
+ * Interface forwarding map
+ */
+ size = sprintf(buffer+len, "%08lX %08lX %-3d %8ld %8ld %8ld",
+ (unsigned long)mfc->mfc_mcastgrp,
+ (unsigned long)mfc->mfc_origin,
+ mfc->mfc_parent,
+ mfc->mfc_un.res.pkt,
+ mfc->mfc_un.res.bytes,
+ mfc->mfc_un.res.wrong_if);
+ for(n=mfc->mfc_un.res.minvif;n<mfc->mfc_un.res.maxvif;n++)
+ {
+ if(VIF_EXISTS(n) && mfc->mfc_un.res.ttls[n] < 255)
+ size += sprintf(buffer+len+size, " %2d:%-3d", n, mfc->mfc_un.res.ttls[n]);
+ }
+ size += sprintf(buffer+len+size, "\n");
+ len+=size;
+ pos+=size;
+ if(pos<offset)
+ {
+ len=0;
+ begin=pos;
+ }
+ if(pos>offset+length)
+ goto done;
+ }
+ }
+
+ spin_lock_bh(&mfc_unres_lock);
+ for(mfc=mfc_unres_queue; mfc; mfc=mfc->next) {
+ size = sprintf(buffer+len, "%08lX %08lX %-3d %8ld %8ld %8ld\n",
+ (unsigned long)mfc->mfc_mcastgrp,
+ (unsigned long)mfc->mfc_origin,
+ -1,
+ (long)mfc->mfc_un.unres.unresolved.qlen,
+ 0L, 0L);
+ len+=size;
+ pos+=size;
+ if(pos<offset)
+ {
+ len=0;
+ begin=pos;
+ }
+ if(pos>offset+length)
+ break;
+ }
+ spin_unlock_bh(&mfc_unres_lock);
+
+done:
+ read_unlock(&mrt_lock);
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ if (len < 0) {
+ len = 0;
+ }
+ return len;
+}
+
+#endif
+
+#ifdef CONFIG_IP_PIMSM_V2
+struct inet_protocol pim_protocol =
+{
+ pim_rcv, /* PIM handler */
+ NULL, /* PIM error control */
+ NULL, /* next */
+ IPPROTO_PIM, /* protocol ID */
+ 0, /* copy */
+ NULL, /* data */
+ "PIM" /* name */
+};
+#endif
+
+
+/*
+ * Setup for IP multicast routing
+ */
+
+void __init ip_mr_init(void)
+{
+ printk(KERN_INFO "Linux IP multicast router 0.06 plus PIM-SM\n");
+ mrt_cachep = kmem_cache_create("ip_mrt_cache",
+ sizeof(struct mfc_cache),
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+ init_timer(&ipmr_expire_timer);
+ ipmr_expire_timer.function=ipmr_expire_process;
+ register_netdevice_notifier(&ip_mr_notifier);
+#ifdef CONFIG_PROC_FS
+ proc_net_create("ip_mr_vif",0,ipmr_vif_info);
+ proc_net_create("ip_mr_cache",0,ipmr_mfc_info);
+#endif
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/Config.in b/uClinux-2.4.31-uc0/net/ipv4/ipvs/Config.in
new file mode 100644
index 0000000..28e15f3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/Config.in
@@ -0,0 +1,26 @@
+#
+# IP VS configuration
+#
+mainmenu_option next_comment
+comment ' IP: Virtual Server Configuration'
+
+tristate 'virtual server support (EXPERIMENTAL)' CONFIG_IP_VS
+if [ "$CONFIG_IP_VS" != "n" ]; then
+ bool ' IP virtual server debugging' CONFIG_IP_VS_DEBUG
+ int ' IPVS connection table size (the Nth power of 2)' CONFIG_IP_VS_TAB_BITS 12
+ comment 'IPVS scheduler'
+ dep_tristate ' round-robin scheduling' CONFIG_IP_VS_RR $CONFIG_IP_VS
+ dep_tristate ' weighted round-robin scheduling' CONFIG_IP_VS_WRR $CONFIG_IP_VS
+ dep_tristate ' least-connection scheduling' CONFIG_IP_VS_LC $CONFIG_IP_VS
+ dep_tristate ' weighted least-connection scheduling' CONFIG_IP_VS_WLC $CONFIG_IP_VS
+ dep_tristate ' locality-based least-connection scheduling' CONFIG_IP_VS_LBLC $CONFIG_IP_VS
+ dep_tristate ' locality-based least-connection with replication scheduling' CONFIG_IP_VS_LBLCR $CONFIG_IP_VS
+ dep_tristate ' destination hashing scheduling' CONFIG_IP_VS_DH $CONFIG_IP_VS
+ dep_tristate ' source hashing scheduling' CONFIG_IP_VS_SH $CONFIG_IP_VS
+ dep_tristate ' shortest expected delay scheduling' CONFIG_IP_VS_SED $CONFIG_IP_VS
+ dep_tristate ' never queue scheduling' CONFIG_IP_VS_NQ $CONFIG_IP_VS
+ comment 'IPVS application helper'
+ dep_tristate ' FTP protocol helper' CONFIG_IP_VS_FTP $CONFIG_IP_VS
+fi
+
+endmenu
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/Makefile b/uClinux-2.4.31-uc0/net/ipv4/ipvs/Makefile
new file mode 100644
index 0000000..cb01734
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/Makefile
@@ -0,0 +1,43 @@
+#
+# Makefile for the IPVS modules on top of IPv4.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := ipvs.o
+
+export-objs := ip_vs_core.o ip_vs_app.o
+
+ip_vs-objs := ip_vs_conn.o ip_vs_core.o ip_vs_ctl.o ip_vs_sched.o \
+ ip_vs_app.o ip_vs_sync.o ip_vs_est.o
+
+ifeq ($(CONFIG_IP_VS),y)
+ obj-y := $(ip_vs-objs)
+else
+ ifeq ($(CONFIG_IP_VS),m)
+ obj-m := ip_vs.o
+ endif
+endif
+
+# IPVS schedulers
+obj-$(CONFIG_IP_VS_RR) += ip_vs_rr.o
+obj-$(CONFIG_IP_VS_WRR) += ip_vs_wrr.o
+obj-$(CONFIG_IP_VS_LC) += ip_vs_lc.o
+obj-$(CONFIG_IP_VS_WLC) += ip_vs_wlc.o
+obj-$(CONFIG_IP_VS_LBLC) += ip_vs_lblc.o
+obj-$(CONFIG_IP_VS_LBLCR) += ip_vs_lblcr.o
+obj-$(CONFIG_IP_VS_DH) += ip_vs_dh.o
+obj-$(CONFIG_IP_VS_SH) += ip_vs_sh.o
+obj-$(CONFIG_IP_VS_SED) += ip_vs_sed.o
+obj-$(CONFIG_IP_VS_NQ) += ip_vs_nq.o
+
+# IPVS application helpers
+obj-$(CONFIG_IP_VS_FTP) += ip_vs_ftp.o
+
+include $(TOPDIR)/Rules.make
+
+ip_vs.o: $(ip_vs-objs)
+ $(LD) $(LD_RFLAG) -r -o $@ $(ip_vs-objs)
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_app.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_app.c
new file mode 100644
index 0000000..eeef744
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_app.c
@@ -0,0 +1,502 @@
+/*
+ * IPVS Application module
+ *
+ * Version: $Id: ip_vs_app.c,v 1.14 2001/11/23 14:34:10 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Most code here is taken from ip_masq_app.c in kernel 2.2. The difference
+ * is that ip_vs_app module handles the reverse direction (incoming requests
+ * and outgoing responses). The ip_vs_app modules are only used for VS/NAT.
+ *
+ * IP_MASQ_APP application masquerading module
+ *
+ * Author: Juan Jose Ciarlante, <jjciarla@raiz.uncu.edu.ar>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <net/protocol.h>
+#include <asm/system.h>
+#include <linux/stat.h>
+#include <linux/proc_fs.h>
+
+#include <net/ip_vs.h>
+
+#define IP_VS_APP_TAB_SIZE 16 /* must be power of 2 */
+
+#define IP_VS_APP_HASH(proto, port) ((port^proto) & (IP_VS_APP_TAB_SIZE-1))
+#define IP_VS_APP_TYPE(proto, port) (proto<<16 | port)
+#define IP_VS_APP_PORT(type) (type & 0xffff)
+#define IP_VS_APP_PROTO(type) ((type>>16) & 0x00ff)
+
+
+EXPORT_SYMBOL(register_ip_vs_app);
+EXPORT_SYMBOL(unregister_ip_vs_app);
+
+
+/*
+ * will hold ipvs app. hashed list heads
+ */
+static struct list_head ip_vs_app_base[IP_VS_APP_TAB_SIZE];
+
+/* lock for ip_vs_app table */
+static rwlock_t __ip_vs_app_lock = RW_LOCK_UNLOCKED;
+
+
+/*
+ * ip_vs_app registration routine
+ * port: host byte order.
+ */
+int register_ip_vs_app(struct ip_vs_app *vapp,
+ unsigned short proto, __u16 port)
+{
+ unsigned hash;
+
+ if (!vapp) {
+ IP_VS_ERR("register_ip_vs_app(): NULL arg\n");
+ return -EINVAL;
+ }
+
+ MOD_INC_USE_COUNT;
+
+ vapp->type = IP_VS_APP_TYPE(proto, port);
+ hash = IP_VS_APP_HASH(proto, port);
+
+ write_lock_bh(&__ip_vs_app_lock);
+ list_add(&vapp->n_list, &ip_vs_app_base[hash]);
+ write_unlock_bh(&__ip_vs_app_lock);
+
+ return 0;
+}
+
+
+/*
+ * ip_vs_app unregistration routine.
+ */
+int unregister_ip_vs_app(struct ip_vs_app *vapp)
+{
+ if (!vapp) {
+ IP_VS_ERR("unregister_ip_vs_app(): NULL arg\n");
+ return -EINVAL;
+ }
+
+ write_lock_bh(&__ip_vs_app_lock);
+ list_del(&vapp->n_list);
+ write_unlock_bh(&__ip_vs_app_lock);
+
+ MOD_DEC_USE_COUNT;
+
+ return 0;
+}
+
+
+/*
+ * get ip_vs_app object by its proto and port (net byte order).
+ */
+static struct ip_vs_app * ip_vs_app_get(unsigned short proto, __u16 port)
+{
+ struct list_head *e;
+ struct ip_vs_app *vapp;
+ unsigned hash;
+ unsigned type;
+
+ port = ntohs(port);
+ type = IP_VS_APP_TYPE(proto, port);
+ hash = IP_VS_APP_HASH(proto, port);
+
+ read_lock_bh(&__ip_vs_app_lock);
+
+ list_for_each(e, &ip_vs_app_base[hash]) {
+ vapp = list_entry(e, struct ip_vs_app, n_list);
+
+ /*
+ * Test and MOD_INC_USE_COUNT atomically
+ */
+ if (vapp->module && !try_inc_mod_count(vapp->module)) {
+ /*
+ * This application module is just deleted
+ */
+ continue;
+ }
+ if (type == vapp->type) {
+ read_unlock_bh(&__ip_vs_app_lock);
+ return vapp;
+ }
+
+ if (vapp->module)
+ __MOD_DEC_USE_COUNT(vapp->module);
+ }
+
+ read_unlock_bh(&__ip_vs_app_lock);
+ return NULL;
+}
+
+
+/*
+ * Bind ip_vs_conn to its ip_vs_app based on proto and dport,
+ * and call the ip_vs_app constructor.
+ */
+struct ip_vs_app * ip_vs_bind_app(struct ip_vs_conn *cp)
+{
+ struct ip_vs_app *vapp;
+
+ /* no need to bind app if its forwarding method is not NAT */
+ if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
+ return NULL;
+
+ if (cp->protocol != IPPROTO_TCP && cp->protocol != IPPROTO_UDP)
+ return NULL;
+
+ /*
+ * don't allow binding if already bound
+ */
+ if (cp->app != NULL) {
+ IP_VS_ERR("ip_vs_bind_app(): "
+ "called for already bound object.\n");
+ return cp->app;
+ }
+
+ vapp = ip_vs_app_get(cp->protocol, cp->vport);
+
+ if (vapp != NULL) {
+ cp->app = vapp;
+
+ if (vapp->init_conn)
+ vapp->init_conn(vapp, cp);
+ }
+ return vapp;
+}
+
+
+/*
+ * Unbind cp from type object and call cp destructor (does not kfree()).
+ */
+int ip_vs_unbind_app(struct ip_vs_conn *cp)
+{
+ struct ip_vs_app *vapp = cp->app;
+
+ if (cp->protocol != IPPROTO_TCP && cp->protocol != IPPROTO_UDP)
+ return 0;
+
+ if (vapp != NULL) {
+ if (vapp->done_conn)
+ vapp->done_conn(vapp, cp);
+ cp->app = NULL;
+ if (vapp->module)
+ __MOD_DEC_USE_COUNT(vapp->module);
+ }
+ return (vapp != NULL);
+}
+
+
+/*
+ * Fixes th->seq based on ip_vs_seq info.
+ */
+static inline void vs_fix_seq(const struct ip_vs_seq *vseq, struct tcphdr *th)
+{
+ __u32 seq = ntohl(th->seq);
+
+ /*
+ * Adjust seq with delta-offset for all packets after
+ * the most recent resized pkt seq and with previous_delta offset
+ * for all packets before most recent resized pkt seq.
+ */
+ if (vseq->delta || vseq->previous_delta) {
+ if(after(seq, vseq->init_seq)) {
+ th->seq = htonl(seq + vseq->delta);
+ IP_VS_DBG(9, "vs_fix_seq(): added delta (%d) to seq\n",
+ vseq->delta);
+ } else {
+ th->seq = htonl(seq + vseq->previous_delta);
+ IP_VS_DBG(9, "vs_fix_seq(): added previous_delta "
+ "(%d) to seq\n", vseq->previous_delta);
+ }
+ }
+}
+
+
+/*
+ * Fixes th->ack_seq based on ip_vs_seq info.
+ */
+static inline void
+vs_fix_ack_seq(const struct ip_vs_seq *vseq, struct tcphdr *th)
+{
+ __u32 ack_seq = ntohl(th->ack_seq);
+
+ /*
+ * Adjust ack_seq with delta-offset for
+ * the packets AFTER most recent resized pkt has caused a shift
+ * for packets before most recent resized pkt, use previous_delta
+ */
+ if (vseq->delta || vseq->previous_delta) {
+ /* since ack_seq is the number of octet that is expected
+ to receive next, so compare it with init_seq+delta */
+ if(after(ack_seq, vseq->init_seq+vseq->delta)) {
+ th->ack_seq = htonl(ack_seq - vseq->delta);
+ IP_VS_DBG(9, "vs_fix_ack_seq(): subtracted delta "
+ "(%d) from ack_seq\n", vseq->delta);
+
+ } else {
+ th->ack_seq = htonl(ack_seq - vseq->previous_delta);
+ IP_VS_DBG(9, "vs_fix_ack_seq(): subtracted "
+ "previous_delta (%d) from ack_seq\n",
+ vseq->previous_delta);
+ }
+ }
+}
+
+
+/*
+ * Updates ip_vs_seq if pkt has been resized
+ * Assumes already checked proto==IPPROTO_TCP and diff!=0.
+ */
+static inline void vs_seq_update(struct ip_vs_conn *cp, struct ip_vs_seq *vseq,
+ unsigned flag, __u32 seq, int diff)
+{
+ /* spinlock is to keep updating cp->flags atomic */
+ spin_lock(&cp->lock);
+ if ( !(cp->flags & flag) || after(seq, vseq->init_seq)) {
+ vseq->previous_delta = vseq->delta;
+ vseq->delta += diff;
+ vseq->init_seq = seq;
+ cp->flags |= flag;
+ }
+ spin_unlock(&cp->lock);
+}
+
+
+/*
+ * Output pkt hook. Will call bound ip_vs_app specific function
+ * called by ip_vs_out(), assumes previously checked cp!=NULL
+ * returns (new - old) skb->len diff.
+ */
+int ip_vs_app_pkt_out(struct ip_vs_conn *cp, struct sk_buff *skb)
+{
+ struct ip_vs_app *vapp;
+ int diff;
+ struct iphdr *iph;
+ struct tcphdr *th;
+ __u32 seq;
+
+ /*
+ * check if application module is bound to
+ * this ip_vs_conn.
+ */
+ if ((vapp = cp->app) == NULL)
+ return 0;
+
+ iph = skb->nh.iph;
+ th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
+
+ /*
+ * Remember seq number in case this pkt gets resized
+ */
+ seq = ntohl(th->seq);
+
+ /*
+ * Fix seq stuff if flagged as so.
+ */
+ if (cp->protocol == IPPROTO_TCP) {
+ if (cp->flags & IP_VS_CONN_F_OUT_SEQ)
+ vs_fix_seq(&cp->out_seq, th);
+ if (cp->flags & IP_VS_CONN_F_IN_SEQ)
+ vs_fix_ack_seq(&cp->in_seq, th);
+ }
+
+ /*
+ * Call private output hook function
+ */
+ if (vapp->pkt_out == NULL)
+ return 0;
+
+ diff = vapp->pkt_out(vapp, cp, skb);
+
+ /*
+ * Update ip_vs seq stuff if len has changed.
+ */
+ if (diff != 0 && cp->protocol == IPPROTO_TCP)
+ vs_seq_update(cp, &cp->out_seq,
+ IP_VS_CONN_F_OUT_SEQ, seq, diff);
+
+ return diff;
+}
+
+
+/*
+ * Input pkt hook. Will call bound ip_vs_app specific function
+ * called by ip_fw_demasquerade(), assumes previously checked cp!=NULL.
+ * returns (new - old) skb->len diff.
+ */
+int ip_vs_app_pkt_in(struct ip_vs_conn *cp, struct sk_buff *skb)
+{
+ struct ip_vs_app *vapp;
+ int diff;
+ struct iphdr *iph;
+ struct tcphdr *th;
+ __u32 seq;
+
+ /*
+ * check if application module is bound to
+ * this ip_vs_conn.
+ */
+ if ((vapp = cp->app) == NULL)
+ return 0;
+
+ iph = skb->nh.iph;
+ th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
+
+ /*
+ * Remember seq number in case this pkt gets resized
+ */
+ seq = ntohl(th->seq);
+
+ /*
+ * Fix seq stuff if flagged as so.
+ */
+ if (cp->protocol == IPPROTO_TCP) {
+ if (cp->flags & IP_VS_CONN_F_IN_SEQ)
+ vs_fix_seq(&cp->in_seq, th);
+ if (cp->flags & IP_VS_CONN_F_OUT_SEQ)
+ vs_fix_ack_seq(&cp->out_seq, th);
+ }
+
+ /*
+ * Call private input hook function
+ */
+ if (vapp->pkt_in == NULL)
+ return 0;
+
+ diff = vapp->pkt_in(vapp, cp, skb);
+
+ /*
+ * Update ip_vs seq stuff if len has changed.
+ */
+ if (diff != 0 && cp->protocol == IPPROTO_TCP)
+ vs_seq_update(cp, &cp->in_seq,
+ IP_VS_CONN_F_IN_SEQ, seq, diff);
+
+ return diff;
+}
+
+
+/*
+ * /proc/net/ip_vs_app entry function
+ */
+static int ip_vs_app_getinfo(char *buffer, char **start, off_t offset,
+ int length)
+{
+ off_t pos=0;
+ int len=0;
+ char temp[64];
+ int idx;
+ struct ip_vs_app *vapp;
+ struct list_head *e;
+
+ pos = 64;
+ if (pos > offset) {
+ len += sprintf(buffer+len, "%-63s\n",
+ "prot port usecnt name");
+ }
+
+ read_lock_bh(&__ip_vs_app_lock);
+ for (idx=0 ; idx < IP_VS_APP_TAB_SIZE; idx++) {
+ list_for_each (e, &ip_vs_app_base[idx]) {
+ vapp = list_entry(e, struct ip_vs_app, n_list);
+
+ pos += 64;
+ if (pos <= offset)
+ continue;
+ sprintf(temp, "%-3s %-7u %-6d %-17s",
+ ip_vs_proto_name(IP_VS_APP_PROTO(vapp->type)),
+ IP_VS_APP_PORT(vapp->type),
+ vapp->module?GET_USE_COUNT(vapp->module):0,
+ vapp->name);
+ len += sprintf(buffer+len, "%-63s\n", temp);
+ if (pos >= offset+length)
+ goto done;
+ }
+ }
+ done:
+ read_unlock_bh(&__ip_vs_app_lock);
+
+ *start = buffer+len-(pos-offset); /* Start of wanted data */
+ len = pos-offset;
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+
+/*
+ * Replace a segment of data with a new segment
+ */
+int ip_vs_skb_replace(struct sk_buff *skb, int pri,
+ char *o_buf, int o_len, char *n_buf, int n_len)
+{
+ struct iphdr *iph;
+ int diff;
+ int o_offset;
+ int o_left;
+
+ EnterFunction(9);
+
+ diff = n_len - o_len;
+ o_offset = o_buf - (char *)skb->data;
+ /* The length of left data after o_buf+o_len in the skb data */
+ o_left = skb->len - (o_offset + o_len);
+
+ if (diff <= 0) {
+ memmove(o_buf + n_len, o_buf + o_len, o_left);
+ memcpy(o_buf, n_buf, n_len);
+ skb_trim(skb, skb->len + diff);
+ } else if (diff <= skb_tailroom(skb)) {
+ skb_put(skb, diff);
+ memmove(o_buf + n_len, o_buf + o_len, o_left);
+ memcpy(o_buf, n_buf, n_len);
+ } else {
+ if (pskb_expand_head(skb, skb_headroom(skb), diff, pri))
+ return -ENOMEM;
+ skb_put(skb, diff);
+ memmove(skb->data + o_offset + n_len,
+ skb->data + o_offset + o_len, o_left);
+ memcpy(skb->data + o_offset, n_buf, n_len);
+ }
+
+ /* must update the iph total length here */
+ iph = skb->nh.iph;
+ iph->tot_len = htons(skb->len);
+
+ LeaveFunction(9);
+ return 0;
+}
+
+
+int ip_vs_app_init(void)
+{
+ int idx;
+
+ for (idx=0 ; idx < IP_VS_APP_TAB_SIZE; idx++) {
+ INIT_LIST_HEAD(&ip_vs_app_base[idx]);
+ }
+
+ /* we will replace it with proc_net_ipvs_create() soon */
+ proc_net_create("ip_vs_app", 0, ip_vs_app_getinfo);
+ return 0;
+}
+
+void ip_vs_app_cleanup(void)
+{
+ proc_net_remove("ip_vs_app");
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_conn.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_conn.c
new file mode 100644
index 0000000..ad55632
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_conn.c
@@ -0,0 +1,1569 @@
+/*
+ * IPVS An implementation of the IP virtual server support for the
+ * LINUX operating system. IPVS is now implemented as a module
+ * over the Netfilter framework. IPVS can be used to build a
+ * high-performance and highly available server based on a
+ * cluster of servers.
+ *
+ * Version: $Id: ip_vs_conn.c,v 1.28.2.5 2003/08/09 13:27:08 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
+ * Peter Kese <peter.kese@ijs.si>
+ * Julian Anastasov <ja@ssi.bg>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * The IPVS code for kernel 2.2 was done by Wensong Zhang and Peter Kese,
+ * with changes/fixes from Julian Anastasov, Lars Marowsky-Bree, Horms
+ * and others. Many code here is taken from IP MASQ code of kernel 2.2.
+ *
+ * Changes:
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/vmalloc.h>
+#include <linux/ip.h>
+#include <linux/tcp.h> /* for tcphdr */
+#include <linux/in.h>
+#include <linux/proc_fs.h> /* for proc_net_* */
+#include <asm/softirq.h> /* for local_bh_* */
+#include <net/ip.h>
+#include <net/tcp.h> /* for csum_tcpudp_magic */
+#include <net/udp.h>
+#include <net/icmp.h> /* for icmp_send */
+#include <net/route.h> /* for ip_route_output */
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/jhash.h>
+#include <linux/random.h>
+
+#include <net/ip_vs.h>
+
+
+/*
+ * Connection hash table: for input and output packets lookups of IPVS
+ */
+static struct list_head *ip_vs_conn_tab;
+
+/* SLAB cache for IPVS connections */
+static kmem_cache_t *ip_vs_conn_cachep;
+
+/* counter for current IPVS connections */
+static atomic_t ip_vs_conn_count = ATOMIC_INIT(0);
+
+/* counter for no-client-port connections */
+static atomic_t ip_vs_conn_no_cport_cnt = ATOMIC_INIT(0);
+
+/* random value for IPVS connection hash */
+static unsigned int ip_vs_conn_rnd;
+
+/*
+ * Fine locking granularity for big connection hash table
+ */
+#define CT_LOCKARRAY_BITS 4
+#define CT_LOCKARRAY_SIZE (1<<CT_LOCKARRAY_BITS)
+#define CT_LOCKARRAY_MASK (CT_LOCKARRAY_SIZE-1)
+
+struct ip_vs_aligned_lock
+{
+ rwlock_t l;
+} __attribute__((__aligned__(SMP_CACHE_BYTES)));
+
+/* lock array for conn table */
+struct ip_vs_aligned_lock
+__ip_vs_conntbl_lock_array[CT_LOCKARRAY_SIZE] __cacheline_aligned;
+
+static inline void ct_read_lock(unsigned key)
+{
+ read_lock(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
+}
+
+static inline void ct_read_unlock(unsigned key)
+{
+ read_unlock(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
+}
+
+static inline void ct_write_lock(unsigned key)
+{
+ write_lock(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
+}
+
+static inline void ct_write_unlock(unsigned key)
+{
+ write_unlock(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
+}
+
+static inline void ct_read_lock_bh(unsigned key)
+{
+ read_lock_bh(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
+}
+
+static inline void ct_read_unlock_bh(unsigned key)
+{
+ read_unlock_bh(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
+}
+
+static inline void ct_write_lock_bh(unsigned key)
+{
+ write_lock_bh(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
+}
+
+static inline void ct_write_unlock_bh(unsigned key)
+{
+ write_unlock_bh(&__ip_vs_conntbl_lock_array[key&CT_LOCKARRAY_MASK].l);
+}
+
+
+/*
+ * Returns hash value for IPVS connection entry
+ */
+static unsigned
+ip_vs_conn_hashkey(unsigned proto, __u32 addr, __u16 port)
+{
+ return jhash_3words(addr, port, proto, ip_vs_conn_rnd)
+ & IP_VS_CONN_TAB_MASK;
+}
+
+
+/*
+ * Hashes ip_vs_conn in ip_vs_conn_tab by proto,addr,port.
+ * returns bool success.
+ */
+static int ip_vs_conn_hash(struct ip_vs_conn *cp)
+{
+ unsigned hash;
+ int ret;
+
+ /* Hash by protocol, client address and port */
+ hash = ip_vs_conn_hashkey(cp->protocol, cp->caddr, cp->cport);
+
+ ct_write_lock(hash);
+
+ if (!(cp->flags & IP_VS_CONN_F_HASHED)) {
+ list_add(&cp->c_list, &ip_vs_conn_tab[hash]);
+ cp->flags |= IP_VS_CONN_F_HASHED;
+ atomic_inc(&cp->refcnt);
+ ret = 1;
+ } else {
+ IP_VS_ERR("ip_vs_conn_hash(): request for already hashed, "
+ "called from %p\n", __builtin_return_address(0));
+ ret = 0;
+ }
+
+ ct_write_unlock(hash);
+
+ return ret;
+}
+
+
+/*
+ * UNhashes ip_vs_conn from ip_vs_conn_tab.
+ * returns bool success.
+ */
+static int ip_vs_conn_unhash(struct ip_vs_conn *cp)
+{
+ unsigned hash;
+ int ret;
+
+ /* unhash it and decrease its reference counter */
+ hash = ip_vs_conn_hashkey(cp->protocol, cp->caddr, cp->cport);
+ ct_write_lock(hash);
+
+ if (cp->flags & IP_VS_CONN_F_HASHED) {
+ list_del(&cp->c_list);
+ cp->flags &= ~IP_VS_CONN_F_HASHED;
+ atomic_dec(&cp->refcnt);
+ ret = 1;
+ } else
+ ret = 0;
+
+ ct_write_unlock(hash);
+
+ return ret;
+}
+
+
+/*
+ * Gets ip_vs_conn associated with supplied parameters in the ip_vs_conn_tab.
+ * Called for pkts coming from OUTside-to-INside.
+ * s_addr, s_port: pkt source address (foreign host)
+ * d_addr, d_port: pkt dest address (load balancer)
+ */
+static inline struct ip_vs_conn *__ip_vs_conn_in_get
+(int protocol, __u32 s_addr, __u16 s_port, __u32 d_addr, __u16 d_port)
+{
+ unsigned hash;
+ struct ip_vs_conn *cp;
+ struct list_head *l,*e;
+
+ hash = ip_vs_conn_hashkey(protocol, s_addr, s_port);
+ l = &ip_vs_conn_tab[hash];
+
+ ct_read_lock(hash);
+
+ for (e=l->next; e!=l; e=e->next) {
+ cp = list_entry(e, struct ip_vs_conn, c_list);
+ if (s_addr==cp->caddr && s_port==cp->cport &&
+ d_port==cp->vport && d_addr==cp->vaddr &&
+ protocol==cp->protocol) {
+ /* HIT */
+ atomic_inc(&cp->refcnt);
+ ct_read_unlock(hash);
+ return cp;
+ }
+ }
+
+ ct_read_unlock(hash);
+
+ return NULL;
+}
+
+struct ip_vs_conn *ip_vs_conn_in_get
+(int protocol, __u32 s_addr, __u16 s_port, __u32 d_addr, __u16 d_port)
+{
+ struct ip_vs_conn *cp;
+
+ cp = __ip_vs_conn_in_get(protocol, s_addr, s_port, d_addr, d_port);
+ if (!cp && atomic_read(&ip_vs_conn_no_cport_cnt))
+ cp = __ip_vs_conn_in_get(protocol, s_addr, 0, d_addr, d_port);
+
+ IP_VS_DBG(7, "lookup/in %s %u.%u.%u.%u:%d->%u.%u.%u.%u:%d %s\n",
+ ip_vs_proto_name(protocol),
+ NIPQUAD(s_addr), ntohs(s_port),
+ NIPQUAD(d_addr), ntohs(d_port),
+ cp?"hit":"not hit");
+
+ return cp;
+}
+
+
+/*
+ * Gets ip_vs_conn associated with supplied parameters in the ip_vs_conn_tab.
+ * Called for pkts coming from inside-to-OUTside.
+ * s_addr, s_port: pkt source address (inside host)
+ * d_addr, d_port: pkt dest address (foreign host)
+ */
+struct ip_vs_conn *ip_vs_conn_out_get
+(int protocol, __u32 s_addr, __u16 s_port, __u32 d_addr, __u16 d_port)
+{
+ unsigned hash;
+ struct ip_vs_conn *cp, *ret=NULL;
+ struct list_head *l,*e;
+
+ /*
+ * Check for "full" addressed entries
+ */
+ hash = ip_vs_conn_hashkey(protocol, d_addr, d_port);
+ l = &ip_vs_conn_tab[hash];
+
+ ct_read_lock(hash);
+
+ for (e=l->next; e!=l; e=e->next) {
+ cp = list_entry(e, struct ip_vs_conn, c_list);
+ if (d_addr == cp->caddr && d_port == cp->cport &&
+ s_port == cp->dport && s_addr == cp->daddr &&
+ protocol == cp->protocol) {
+ /* HIT */
+ atomic_inc(&cp->refcnt);
+ ret = cp;
+ break;
+ }
+ }
+
+ ct_read_unlock(hash);
+
+ IP_VS_DBG(7, "lookup/out %s %u.%u.%u.%u:%d->%u.%u.%u.%u:%d %s\n",
+ ip_vs_proto_name(protocol),
+ NIPQUAD(s_addr), ntohs(s_port),
+ NIPQUAD(d_addr), ntohs(d_port),
+ ret?"hit":"not hit");
+
+ return ret;
+}
+
+
+/*
+ * Put back the conn and restart its timer with its timeout
+ */
+void ip_vs_conn_put(struct ip_vs_conn *cp)
+{
+ /* reset it expire in its timeout */
+ mod_timer(&cp->timer, jiffies+cp->timeout);
+
+ __ip_vs_conn_put(cp);
+}
+
+
+/*
+ * Timeout table[state]
+ */
+struct ip_vs_timeout_table vs_timeout_table = {
+ ATOMIC_INIT(0), /* refcnt */
+ 0, /* scale */
+ {
+ [IP_VS_S_NONE] = 30*60*HZ,
+ [IP_VS_S_ESTABLISHED] = 15*60*HZ,
+ [IP_VS_S_SYN_SENT] = 2*60*HZ,
+ [IP_VS_S_SYN_RECV] = 1*60*HZ,
+ [IP_VS_S_FIN_WAIT] = 2*60*HZ,
+ [IP_VS_S_TIME_WAIT] = 2*60*HZ,
+ [IP_VS_S_CLOSE] = 10*HZ,
+ [IP_VS_S_CLOSE_WAIT] = 60*HZ,
+ [IP_VS_S_LAST_ACK] = 30*HZ,
+ [IP_VS_S_LISTEN] = 2*60*HZ,
+ [IP_VS_S_SYNACK] = 120*HZ,
+ [IP_VS_S_UDP] = 5*60*HZ,
+ [IP_VS_S_ICMP] = 1*60*HZ,
+ [IP_VS_S_LAST] = 2*HZ,
+ }, /* timeout */
+};
+
+
+struct ip_vs_timeout_table vs_timeout_table_dos = {
+ ATOMIC_INIT(0), /* refcnt */
+ 0, /* scale */
+ {
+ [IP_VS_S_NONE] = 15*60*HZ,
+ [IP_VS_S_ESTABLISHED] = 8*60*HZ,
+ [IP_VS_S_SYN_SENT] = 60*HZ,
+ [IP_VS_S_SYN_RECV] = 10*HZ,
+ [IP_VS_S_FIN_WAIT] = 60*HZ,
+ [IP_VS_S_TIME_WAIT] = 60*HZ,
+ [IP_VS_S_CLOSE] = 10*HZ,
+ [IP_VS_S_CLOSE_WAIT] = 60*HZ,
+ [IP_VS_S_LAST_ACK] = 30*HZ,
+ [IP_VS_S_LISTEN] = 2*60*HZ,
+ [IP_VS_S_SYNACK] = 100*HZ,
+ [IP_VS_S_UDP] = 3*60*HZ,
+ [IP_VS_S_ICMP] = 1*60*HZ,
+ [IP_VS_S_LAST] = 2*HZ,
+ }, /* timeout */
+};
+
+
+/*
+ * Timeout table to use for the VS entries
+ * If NULL we use the default table (vs_timeout_table).
+ * Under flood attack we switch to vs_timeout_table_dos
+ */
+
+static struct ip_vs_timeout_table *ip_vs_timeout_table = &vs_timeout_table;
+
+static const char * state_name_table[IP_VS_S_LAST+1] = {
+ [IP_VS_S_NONE] = "NONE",
+ [IP_VS_S_ESTABLISHED] = "ESTABLISHED",
+ [IP_VS_S_SYN_SENT] = "SYN_SENT",
+ [IP_VS_S_SYN_RECV] = "SYN_RECV",
+ [IP_VS_S_FIN_WAIT] = "FIN_WAIT",
+ [IP_VS_S_TIME_WAIT] = "TIME_WAIT",
+ [IP_VS_S_CLOSE] = "CLOSE",
+ [IP_VS_S_CLOSE_WAIT] = "CLOSE_WAIT",
+ [IP_VS_S_LAST_ACK] = "LAST_ACK",
+ [IP_VS_S_LISTEN] = "LISTEN",
+ [IP_VS_S_SYNACK] = "SYNACK",
+ [IP_VS_S_UDP] = "UDP",
+ [IP_VS_S_ICMP] = "ICMP",
+ [IP_VS_S_LAST] = "BUG!",
+};
+
+#define sNO IP_VS_S_NONE
+#define sES IP_VS_S_ESTABLISHED
+#define sSS IP_VS_S_SYN_SENT
+#define sSR IP_VS_S_SYN_RECV
+#define sFW IP_VS_S_FIN_WAIT
+#define sTW IP_VS_S_TIME_WAIT
+#define sCL IP_VS_S_CLOSE
+#define sCW IP_VS_S_CLOSE_WAIT
+#define sLA IP_VS_S_LAST_ACK
+#define sLI IP_VS_S_LISTEN
+#define sSA IP_VS_S_SYNACK
+
+struct vs_tcp_states_t {
+ int next_state[IP_VS_S_LAST]; /* should be _LAST_TCP */
+};
+
+const char * ip_vs_state_name(int state)
+{
+ if (state >= IP_VS_S_LAST)
+ return "ERR!";
+ return state_name_table[state] ? state_name_table[state] : "?";
+}
+
+static struct vs_tcp_states_t vs_tcp_states [] = {
+/* INPUT */
+/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
+/*syn*/ {{sSR, sES, sES, sSR, sSR, sSR, sSR, sSR, sSR, sSR, sSR }},
+/*fin*/ {{sCL, sCW, sSS, sTW, sTW, sTW, sCL, sCW, sLA, sLI, sTW }},
+/*ack*/ {{sCL, sES, sSS, sES, sFW, sTW, sCL, sCW, sCL, sLI, sES }},
+/*rst*/ {{sCL, sCL, sCL, sSR, sCL, sCL, sCL, sCL, sLA, sLI, sSR }},
+
+/* OUTPUT */
+/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
+/*syn*/ {{sSS, sES, sSS, sSR, sSS, sSS, sSS, sSS, sSS, sLI, sSR }},
+/*fin*/ {{sTW, sFW, sSS, sTW, sFW, sTW, sCL, sTW, sLA, sLI, sTW }},
+/*ack*/ {{sES, sES, sSS, sES, sFW, sTW, sCL, sCW, sLA, sES, sES }},
+/*rst*/ {{sCL, sCL, sSS, sCL, sCL, sTW, sCL, sCL, sCL, sCL, sCL }},
+
+/* INPUT-ONLY */
+/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
+/*syn*/ {{sSR, sES, sES, sSR, sSR, sSR, sSR, sSR, sSR, sSR, sSR }},
+/*fin*/ {{sCL, sFW, sSS, sTW, sFW, sTW, sCL, sCW, sLA, sLI, sTW }},
+/*ack*/ {{sCL, sES, sSS, sES, sFW, sTW, sCL, sCW, sCL, sLI, sES }},
+/*rst*/ {{sCL, sCL, sCL, sSR, sCL, sCL, sCL, sCL, sLA, sLI, sCL }},
+};
+
+static struct vs_tcp_states_t vs_tcp_states_dos [] = {
+/* INPUT */
+/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
+/*syn*/ {{sSR, sES, sES, sSR, sSR, sSR, sSR, sSR, sSR, sSR, sSA }},
+/*fin*/ {{sCL, sCW, sSS, sTW, sTW, sTW, sCL, sCW, sLA, sLI, sSA }},
+/*ack*/ {{sCL, sES, sSS, sSR, sFW, sTW, sCL, sCW, sCL, sLI, sSA }},
+/*rst*/ {{sCL, sCL, sCL, sSR, sCL, sCL, sCL, sCL, sLA, sLI, sCL }},
+
+/* OUTPUT */
+/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
+/*syn*/ {{sSS, sES, sSS, sSA, sSS, sSS, sSS, sSS, sSS, sLI, sSA }},
+/*fin*/ {{sTW, sFW, sSS, sTW, sFW, sTW, sCL, sTW, sLA, sLI, sTW }},
+/*ack*/ {{sES, sES, sSS, sES, sFW, sTW, sCL, sCW, sLA, sES, sES }},
+/*rst*/ {{sCL, sCL, sSS, sCL, sCL, sTW, sCL, sCL, sCL, sCL, sCL }},
+
+/* INPUT-ONLY */
+/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */
+/*syn*/ {{sSA, sES, sES, sSR, sSA, sSA, sSA, sSA, sSA, sSA, sSA }},
+/*fin*/ {{sCL, sFW, sSS, sTW, sFW, sTW, sCL, sCW, sLA, sLI, sTW }},
+/*ack*/ {{sCL, sES, sSS, sES, sFW, sTW, sCL, sCW, sCL, sLI, sES }},
+/*rst*/ {{sCL, sCL, sCL, sSR, sCL, sCL, sCL, sCL, sLA, sLI, sCL }},
+};
+
+static struct vs_tcp_states_t *ip_vs_state_table = vs_tcp_states;
+
+void ip_vs_secure_tcp_set(int on)
+{
+ if (on) {
+ ip_vs_state_table = vs_tcp_states_dos;
+ ip_vs_timeout_table = &vs_timeout_table_dos;
+ } else {
+ ip_vs_state_table = vs_tcp_states;
+ ip_vs_timeout_table = &vs_timeout_table;
+ }
+}
+
+
+static inline int vs_tcp_state_idx(struct tcphdr *th, int state_off)
+{
+ /*
+ * [0-3]: input states, [4-7]: output, [8-11] input only states.
+ */
+ if (th->rst)
+ return state_off+3;
+ if (th->syn)
+ return state_off+0;
+ if (th->fin)
+ return state_off+1;
+ if (th->ack)
+ return state_off+2;
+ return -1;
+}
+
+
+static inline int vs_set_state_timeout(struct ip_vs_conn *cp, int state)
+{
+ struct ip_vs_timeout_table *vstim = cp->timeout_table;
+
+ /*
+ * Use default timeout table if no specific for this entry
+ */
+ if (!vstim)
+ vstim = &vs_timeout_table;
+
+ cp->timeout = vstim->timeout[cp->state=state];
+
+ if (vstim->scale) {
+ int scale = vstim->scale;
+
+ if (scale<0)
+ cp->timeout >>= -scale;
+ else if (scale > 0)
+ cp->timeout <<= scale;
+ }
+
+ return state;
+}
+
+
+static inline int
+vs_tcp_state(struct ip_vs_conn *cp, int state_off, struct tcphdr *th)
+{
+ int state_idx;
+ int new_state = IP_VS_S_CLOSE;
+
+ /*
+ * Update state offset to INPUT_ONLY if necessary
+ * or delete NO_OUTPUT flag if output packet detected
+ */
+ if (cp->flags & IP_VS_CONN_F_NOOUTPUT) {
+ if (state_off == VS_STATE_OUTPUT)
+ cp->flags &= ~IP_VS_CONN_F_NOOUTPUT;
+ else
+ state_off = VS_STATE_INPUT_ONLY;
+ }
+
+ if ((state_idx = vs_tcp_state_idx(th, state_off)) < 0) {
+ IP_VS_DBG(8, "vs_tcp_state_idx(%d)=%d!!!\n",
+ state_off, state_idx);
+ goto tcp_state_out;
+ }
+
+ new_state = ip_vs_state_table[state_idx].next_state[cp->state];
+
+ tcp_state_out:
+ if (new_state != cp->state) {
+ struct ip_vs_dest *dest = cp->dest;
+
+ IP_VS_DBG(8, "%s %s [%c%c%c%c] %u.%u.%u.%u:%d->"
+ "%u.%u.%u.%u:%d state: %s->%s cnt:%d\n",
+ ip_vs_proto_name(cp->protocol),
+ (state_off==VS_STATE_OUTPUT)?"output ":"input ",
+ th->syn? 'S' : '.',
+ th->fin? 'F' : '.',
+ th->ack? 'A' : '.',
+ th->rst? 'R' : '.',
+ NIPQUAD(cp->daddr), ntohs(cp->dport),
+ NIPQUAD(cp->caddr), ntohs(cp->cport),
+ ip_vs_state_name(cp->state),
+ ip_vs_state_name(new_state),
+ atomic_read(&cp->refcnt));
+ if (dest) {
+ if (!(cp->flags & IP_VS_CONN_F_INACTIVE) &&
+ (new_state != IP_VS_S_ESTABLISHED)) {
+ atomic_dec(&dest->activeconns);
+ atomic_inc(&dest->inactconns);
+ cp->flags |= IP_VS_CONN_F_INACTIVE;
+ } else if ((cp->flags & IP_VS_CONN_F_INACTIVE) &&
+ (new_state == IP_VS_S_ESTABLISHED)) {
+ atomic_inc(&dest->activeconns);
+ atomic_dec(&dest->inactconns);
+ cp->flags &= ~IP_VS_CONN_F_INACTIVE;
+ }
+ }
+ }
+
+ return vs_set_state_timeout(cp, new_state);
+}
+
+
+/*
+ * Handle state transitions
+ */
+int ip_vs_set_state(struct ip_vs_conn *cp,
+ int state_off, struct iphdr *iph, void *tp)
+{
+ int ret;
+
+ spin_lock(&cp->lock);
+ switch (iph->protocol) {
+ case IPPROTO_TCP:
+ ret = vs_tcp_state(cp, state_off, tp);
+ break;
+ case IPPROTO_UDP:
+ ret = vs_set_state_timeout(cp, IP_VS_S_UDP);
+ break;
+ case IPPROTO_ICMP:
+ ret = vs_set_state_timeout(cp, IP_VS_S_ICMP);
+ break;
+ default:
+ ret = -1;
+ }
+ spin_unlock(&cp->lock);
+
+ return ret;
+}
+
+
+/*
+ * Set LISTEN timeout. (ip_vs_conn_put will setup timer)
+ */
+int ip_vs_conn_listen(struct ip_vs_conn *cp)
+{
+ vs_set_state_timeout(cp, IP_VS_S_LISTEN);
+ return cp->timeout;
+}
+
+
+/*
+ * Bypass transmitter
+ * Let packets bypass the destination when the destination is not
+ * available, it may be only used in transparent cache cluster.
+ */
+static int ip_vs_bypass_xmit(struct sk_buff *skb, struct ip_vs_conn *cp)
+{
+ struct rtable *rt; /* Route to the other host */
+ struct iphdr *iph = skb->nh.iph;
+ u8 tos = iph->tos;
+ int mtu;
+
+ EnterFunction(10);
+
+ if (ip_route_output(&rt, iph->daddr, 0, RT_TOS(tos), 0)) {
+ IP_VS_DBG_RL("ip_vs_bypass_xmit(): ip_route_output error, "
+ "dest: %u.%u.%u.%u\n", NIPQUAD(iph->daddr));
+ goto tx_error_icmp;
+ }
+
+ /* MTU checking */
+ mtu = rt->u.dst.pmtu;
+ if ((skb->len > mtu) && (iph->frag_off&__constant_htons(IP_DF))) {
+ ip_rt_put(rt);
+ icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
+ IP_VS_DBG_RL("ip_vs_bypass_xmit(): frag needed\n");
+ goto tx_error;
+ }
+
+ /* update checksum because skb might be defragmented */
+ ip_send_check(iph);
+
+ if (unlikely(skb_headroom(skb) < rt->u.dst.dev->hard_header_len)) {
+ if (skb_cow(skb, rt->u.dst.dev->hard_header_len)) {
+ ip_rt_put(rt);
+ IP_VS_ERR_RL("ip_vs_bypass_xmit(): no memory\n");
+ goto tx_error;
+ }
+ }
+
+ /* drop old route */
+ dst_release(skb->dst);
+ skb->dst = &rt->u.dst;
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug = 1 << NF_IP_LOCAL_OUT;
+#endif /* CONFIG_NETFILTER_DEBUG */
+ skb->nfcache |= NFC_IPVS_PROPERTY;
+ ip_send(skb);
+
+ LeaveFunction(10);
+ return NF_STOLEN;
+
+ tx_error_icmp:
+ dst_link_failure(skb);
+ tx_error:
+ kfree_skb(skb);
+ return NF_STOLEN;
+}
+
+
+/*
+ * NULL transmitter (do nothing except return NF_ACCEPT)
+ */
+static int ip_vs_null_xmit(struct sk_buff *skb, struct ip_vs_conn *cp)
+{
+ return NF_ACCEPT;
+}
+
+
+/*
+ * NAT transmitter (only for outside-to-inside nat forwarding)
+ */
+static int ip_vs_nat_xmit(struct sk_buff *skb, struct ip_vs_conn *cp)
+{
+ struct rtable *rt; /* Route to the other host */
+ struct iphdr *iph;
+ union ip_vs_tphdr h;
+ int ihl;
+ unsigned short size;
+ int mtu;
+
+ EnterFunction(10);
+
+ /*
+ * If it has ip_vs_app helper, the helper may change the payload,
+ * so it needs full checksum checking and checksum calculation.
+ * If not, only the header (such as IP address and port number)
+ * will be changed, so it is fast to do incremental checksum update,
+ * and let the destination host do final checksum checking.
+ */
+
+ if (cp->app && skb_is_nonlinear(skb)
+ && skb_linearize(skb, GFP_ATOMIC) != 0)
+ return NF_DROP;
+
+ iph = skb->nh.iph;
+ ihl = iph->ihl << 2;
+ h.raw = (char*) iph + ihl;
+ size = ntohs(iph->tot_len) - ihl;
+
+ /* do TCP/UDP checksum checking if it has application helper */
+ if (cp->app && (iph->protocol != IPPROTO_UDP || h.uh->check != 0)) {
+ switch (skb->ip_summed) {
+ case CHECKSUM_NONE:
+ skb->csum = csum_partial(h.raw, size, 0);
+
+ case CHECKSUM_HW:
+ if (csum_tcpudp_magic(iph->saddr, iph->daddr, size,
+ iph->protocol, skb->csum)) {
+ IP_VS_DBG_RL("Incoming failed %s checksum "
+ "from %d.%d.%d.%d (size=%d)!\n",
+ ip_vs_proto_name(iph->protocol),
+ NIPQUAD(iph->saddr),
+ size);
+ goto tx_error;
+ }
+ break;
+ default:
+ /* CHECKSUM_UNNECESSARY */
+ break;
+ }
+ }
+
+ /*
+ * Check if it is no_cport connection ...
+ */
+ if (unlikely(cp->flags & IP_VS_CONN_F_NO_CPORT)) {
+ if (ip_vs_conn_unhash(cp)) {
+ spin_lock(&cp->lock);
+ if (cp->flags & IP_VS_CONN_F_NO_CPORT) {
+ atomic_dec(&ip_vs_conn_no_cport_cnt);
+ cp->flags &= ~IP_VS_CONN_F_NO_CPORT;
+ cp->cport = h.portp[0];
+ IP_VS_DBG(10, "filled cport=%d\n", ntohs(cp->dport));
+ }
+ spin_unlock(&cp->lock);
+
+ /* hash on new dport */
+ ip_vs_conn_hash(cp);
+ }
+ }
+
+ if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(iph->tos))))
+ goto tx_error_icmp;
+
+ /* MTU checking */
+ mtu = rt->u.dst.pmtu;
+ if ((skb->len > mtu) && (iph->frag_off&__constant_htons(IP_DF))) {
+ ip_rt_put(rt);
+ icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
+ IP_VS_DBG_RL("ip_vs_nat_xmit(): frag needed\n");
+ goto tx_error;
+ }
+
+ /* drop old route */
+ dst_release(skb->dst);
+ skb->dst = &rt->u.dst;
+
+ /* copy-on-write the packet before mangling it */
+ if (ip_vs_skb_cow(skb, rt->u.dst.dev->hard_header_len, &iph, &h.raw))
+ return NF_DROP;
+
+ /* mangle the packet */
+ iph->daddr = cp->daddr;
+ h.portp[1] = cp->dport;
+
+ /*
+ * Attempt ip_vs_app call.
+ * will fix ip_vs_conn and iph ack_seq stuff
+ */
+ if (ip_vs_app_pkt_in(cp, skb) != 0) {
+ /* skb data has probably changed, update pointers */
+ iph = skb->nh.iph;
+ h.raw = (char*) iph + ihl;
+ size = skb->len - ihl;
+ }
+
+ /*
+ * Adjust TCP/UDP checksums
+ */
+ if (!cp->app && (iph->protocol != IPPROTO_UDP || h.uh->check != 0)) {
+ /* Only port and addr are changed, do fast csum update */
+ ip_vs_fast_check_update(&h, cp->vaddr, cp->daddr,
+ cp->vport, cp->dport, iph->protocol);
+ if (skb->ip_summed == CHECKSUM_HW)
+ skb->ip_summed = CHECKSUM_NONE;
+ } else {
+ /* full checksum calculation */
+ switch (iph->protocol) {
+ case IPPROTO_TCP:
+ h.th->check = 0;
+ h.th->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
+ size, iph->protocol,
+ csum_partial(h.raw, size, 0));
+ break;
+ case IPPROTO_UDP:
+ h.uh->check = 0;
+ h.uh->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
+ size, iph->protocol,
+ csum_partial(h.raw, size, 0));
+ if (h.uh->check == 0)
+ h.uh->check = 0xFFFF;
+ break;
+ }
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ }
+ ip_send_check(iph);
+
+ IP_VS_DBG(10, "NAT to %u.%u.%u.%u:%d\n",
+ NIPQUAD(iph->daddr), ntohs(h.portp[1]));
+
+ /* FIXME: when application helper enlarges the packet and the length
+ is larger than the MTU of outgoing device, there will be still
+ MTU problem. */
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug = 1 << NF_IP_LOCAL_OUT;
+#endif /* CONFIG_NETFILTER_DEBUG */
+ skb->nfcache |= NFC_IPVS_PROPERTY;
+ ip_send(skb);
+
+ LeaveFunction(10);
+ return NF_STOLEN;
+
+ tx_error_icmp:
+ dst_link_failure(skb);
+ tx_error:
+ kfree_skb(skb);
+ return NF_STOLEN;
+}
+
+
+/*
+ * IP Tunneling transmitter
+ *
+ * This function encapsulates the packet in a new IP packet, its
+ * destination will be set to cp->daddr. Most code of this function
+ * is taken from ipip.c.
+ *
+ * It is used in VS/TUN cluster. The load balancer selects a real
+ * server from a cluster based on a scheduling algorithm,
+ * encapsulates the request packet and forwards it to the selected
+ * server. For example, all real servers are configured with
+ * "ifconfig tunl0 <Virtual IP Address> up". When the server receives
+ * the encapsulated packet, it will decapsulate the packet, processe
+ * the request and return the response packets directly to the client
+ * without passing the load balancer. This can greatly increase the
+ * scalability of virtual server.
+ */
+static int ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp)
+{
+ struct rtable *rt; /* Route to the other host */
+ struct net_device *tdev; /* Device to other host */
+ struct iphdr *old_iph = skb->nh.iph;
+ u8 tos = old_iph->tos;
+ u16 df = old_iph->frag_off;
+ struct iphdr *iph; /* Our new IP header */
+ int max_headroom; /* The extra header space needed */
+ int mtu;
+
+ EnterFunction(10);
+
+ if (skb->protocol != __constant_htons(ETH_P_IP)) {
+ IP_VS_DBG_RL("ip_vs_tunnel_xmit(): protocol error, "
+ "ETH_P_IP: %d, skb protocol: %d\n",
+ __constant_htons(ETH_P_IP), skb->protocol);
+ goto tx_error;
+ }
+
+ if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(tos))))
+ goto tx_error_icmp;
+
+ tdev = rt->u.dst.dev;
+
+ mtu = rt->u.dst.pmtu - sizeof(struct iphdr);
+ if (mtu < 68) {
+ ip_rt_put(rt);
+ IP_VS_DBG_RL("ip_vs_tunnel_xmit(): mtu less than 68\n");
+ goto tx_error;
+ }
+ if (skb->dst && mtu < skb->dst->pmtu)
+ skb->dst->pmtu = mtu;
+
+ df |= (old_iph->frag_off&__constant_htons(IP_DF));
+
+ if ((old_iph->frag_off&__constant_htons(IP_DF))
+ && mtu < ntohs(old_iph->tot_len)) {
+ icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
+ ip_rt_put(rt);
+ IP_VS_DBG_RL("ip_vs_tunnel_xmit(): frag needed\n");
+ goto tx_error;
+ }
+
+ /* update checksum because skb might be defragmented */
+ ip_send_check(old_iph);
+
+ /*
+ * Okay, now see if we can stuff it in the buffer as-is.
+ */
+ max_headroom = (((tdev->hard_header_len+15)&~15)+sizeof(struct iphdr));
+
+ if (skb_headroom(skb) < max_headroom
+ || skb_cloned(skb) || skb_shared(skb)) {
+ struct sk_buff *new_skb =
+ skb_realloc_headroom(skb, max_headroom);
+ if (!new_skb) {
+ ip_rt_put(rt);
+ IP_VS_ERR_RL("ip_vs_tunnel_xmit(): no memory\n");
+ return NF_DROP;
+ }
+ kfree_skb(skb);
+ skb = new_skb;
+ old_iph = skb->nh.iph;
+ }
+
+ skb->h.raw = skb->nh.raw;
+ skb->nh.raw = skb_push(skb, sizeof(struct iphdr));
+ memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
+
+ /* drop old route */
+ dst_release(skb->dst);
+ skb->dst = &rt->u.dst;
+
+ /*
+ * Push down and install the IPIP header.
+ */
+ iph = skb->nh.iph;
+ iph->version = 4;
+ iph->ihl = sizeof(struct iphdr)>>2;
+ iph->frag_off = df;
+ iph->protocol = IPPROTO_IPIP;
+ iph->tos = tos;
+ iph->daddr = rt->rt_dst;
+ iph->saddr = rt->rt_src;
+ iph->ttl = old_iph->ttl;
+ iph->tot_len = htons(skb->len);
+ ip_select_ident(iph, &rt->u.dst, NULL);
+ ip_send_check(iph);
+
+ skb->ip_summed = CHECKSUM_NONE;
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug = 1 << NF_IP_LOCAL_OUT;
+#endif /* CONFIG_NETFILTER_DEBUG */
+ skb->nfcache |= NFC_IPVS_PROPERTY;
+ ip_send(skb);
+
+ LeaveFunction(10);
+
+ return NF_STOLEN;
+
+ tx_error_icmp:
+ dst_link_failure(skb);
+ tx_error:
+ kfree_skb(skb);
+ return NF_STOLEN;
+}
+
+
+/*
+ * Direct Routing transmitter
+ */
+static int ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn *cp)
+{
+ struct rtable *rt; /* Route to the other host */
+ struct iphdr *iph = skb->nh.iph;
+ int mtu;
+
+ EnterFunction(10);
+
+ if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(iph->tos))))
+ goto tx_error_icmp;
+
+ /* MTU checking */
+ mtu = rt->u.dst.pmtu;
+ if ((iph->frag_off&__constant_htons(IP_DF)) && skb->len > mtu) {
+ icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
+ ip_rt_put(rt);
+ IP_VS_DBG_RL("ip_vs_dr_xmit(): frag needed\n");
+ goto tx_error;
+ }
+
+ /* update checksum because skb might be defragmented */
+ ip_send_check(iph);
+
+ if (unlikely(skb_headroom(skb) < rt->u.dst.dev->hard_header_len)) {
+ if (skb_cow(skb, rt->u.dst.dev->hard_header_len)) {
+ ip_rt_put(rt);
+ IP_VS_ERR_RL("ip_vs_dr_xmit(): no memory\n");
+ goto tx_error;
+ }
+ }
+
+ /* drop old route */
+ dst_release(skb->dst);
+ skb->dst = &rt->u.dst;
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug = 1 << NF_IP_LOCAL_OUT;
+#endif /* CONFIG_NETFILTER_DEBUG */
+ skb->nfcache |= NFC_IPVS_PROPERTY;
+ ip_send(skb);
+
+#if 0000
+ NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
+ do_ip_send);
+#endif
+ LeaveFunction(10);
+ return NF_STOLEN;
+
+ tx_error_icmp:
+ dst_link_failure(skb);
+ tx_error:
+ kfree_skb(skb);
+ return NF_STOLEN;
+}
+
+
+/*
+ * Bind a connection entry with the corresponding packet_xmit.
+ * Called by ip_vs_conn_new.
+ */
+static inline void ip_vs_bind_xmit(struct ip_vs_conn *cp)
+{
+ switch (IP_VS_FWD_METHOD(cp)) {
+ case IP_VS_CONN_F_MASQ:
+ cp->packet_xmit = ip_vs_nat_xmit;
+ break;
+
+ case IP_VS_CONN_F_TUNNEL:
+ cp->packet_xmit = ip_vs_tunnel_xmit;
+ break;
+
+ case IP_VS_CONN_F_DROUTE:
+ cp->packet_xmit = ip_vs_dr_xmit;
+ break;
+
+ case IP_VS_CONN_F_LOCALNODE:
+ cp->packet_xmit = ip_vs_null_xmit;
+ break;
+
+ case IP_VS_CONN_F_BYPASS:
+ cp->packet_xmit = ip_vs_bypass_xmit;
+ break;
+ }
+}
+
+
+/*
+ * Bind a connection entry with a virtual service destination
+ * Called just after a new connection entry is created.
+ */
+static inline void
+ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
+{
+ /* if dest is NULL, then return directly */
+ if (!dest)
+ return;
+
+ /* Increase the refcnt counter of the dest */
+ atomic_inc(&dest->refcnt);
+
+ /* Bind with the destination and its corresponding transmitter */
+ cp->flags |= atomic_read(&dest->conn_flags);
+ cp->dest = dest;
+
+ IP_VS_DBG(9, "Bind-dest %s c:%u.%u.%u.%u:%d v:%u.%u.%u.%u:%d "
+ "d:%u.%u.%u.%u:%d fwd:%c s:%s flg:%X cnt:%d destcnt:%d\n",
+ ip_vs_proto_name(cp->protocol),
+ NIPQUAD(cp->caddr), ntohs(cp->cport),
+ NIPQUAD(cp->vaddr), ntohs(cp->vport),
+ NIPQUAD(cp->daddr), ntohs(cp->dport),
+ ip_vs_fwd_tag(cp), ip_vs_state_name(cp->state),
+ cp->flags, atomic_read(&cp->refcnt),
+ atomic_read(&dest->refcnt));
+}
+
+
+/*
+ * Unbind a connection entry with its VS destination
+ * Called by the ip_vs_conn_expire function.
+ */
+static inline void ip_vs_unbind_dest(struct ip_vs_conn *cp)
+{
+ struct ip_vs_dest *dest = cp->dest;
+
+ /* if dest is NULL, then return directly */
+ if (!dest)
+ return;
+
+ IP_VS_DBG(9, "Unbind-dest %s c:%u.%u.%u.%u:%d "
+ "v:%u.%u.%u.%u:%d d:%u.%u.%u.%u:%d fwd:%c "
+ "s:%s flg:%X cnt:%d destcnt:%d",
+ ip_vs_proto_name(cp->protocol),
+ NIPQUAD(cp->caddr), ntohs(cp->cport),
+ NIPQUAD(cp->vaddr), ntohs(cp->vport),
+ NIPQUAD(cp->daddr), ntohs(cp->dport),
+ ip_vs_fwd_tag(cp), ip_vs_state_name(cp->state),
+ cp->flags, atomic_read(&cp->refcnt),
+ atomic_read(&dest->refcnt));
+
+ /*
+ * Decrease the inactconns or activeconns counter
+ * if it is not a connection template ((cp->cport!=0)
+ * || (cp->flags & IP_VS_CONN_F_NO_CPORT)).
+ */
+ if (cp->cport || (cp->flags & IP_VS_CONN_F_NO_CPORT)) {
+ if (cp->flags & IP_VS_CONN_F_INACTIVE) {
+ atomic_dec(&dest->inactconns);
+ } else {
+ atomic_dec(&dest->activeconns);
+ }
+ }
+
+ /*
+ * Simply decrease the refcnt of the dest, because the
+ * dest will be either in service's destination list
+ * or in the trash.
+ */
+ atomic_dec(&dest->refcnt);
+}
+
+
+/*
+ * Checking if the destination of a connection template is available.
+ * If available, return 1, otherwise invalidate this connection
+ * template and return 0.
+ */
+int ip_vs_check_template(struct ip_vs_conn *ct)
+{
+ struct ip_vs_dest *dest = ct->dest;
+
+ /*
+ * Checking the dest server status.
+ */
+ if ((dest == NULL) ||
+ !(dest->flags & IP_VS_DEST_F_AVAILABLE) ||
+ (sysctl_ip_vs_expire_quiescent_template &&
+ (atomic_read(&dest->weight) == 0))) {
+ IP_VS_DBG(9, "check_template: dest not available for "
+ "protocol %s s:%u.%u.%u.%u:%d v:%u.%u.%u.%u:%d "
+ "-> d:%u.%u.%u.%u:%d\n",
+ ip_vs_proto_name(ct->protocol),
+ NIPQUAD(ct->caddr), ntohs(ct->cport),
+ NIPQUAD(ct->vaddr), ntohs(ct->vport),
+ NIPQUAD(ct->daddr), ntohs(ct->dport));
+
+ /*
+ * Invalidate the connection template
+ */
+ if (ct->cport) {
+ if (ip_vs_conn_unhash(ct)) {
+ ct->dport = 65535;
+ ct->vport = 65535;
+ ct->cport = 0;
+ ip_vs_conn_hash(ct);
+ }
+ }
+
+ /*
+ * Simply decrease the refcnt of the template,
+ * don't restart its timer.
+ */
+ atomic_dec(&ct->refcnt);
+ return 0;
+ }
+ return 1;
+}
+
+
+static inline void
+ip_vs_timeout_attach(struct ip_vs_conn *cp, struct ip_vs_timeout_table *vstim)
+{
+ atomic_inc(&vstim->refcnt);
+ cp->timeout_table = vstim;
+}
+
+static inline void ip_vs_timeout_detach(struct ip_vs_conn *cp)
+{
+ struct ip_vs_timeout_table *vstim = cp->timeout_table;
+
+ if (!vstim)
+ return;
+ cp->timeout_table = NULL;
+ atomic_dec(&vstim->refcnt);
+}
+
+
+static void ip_vs_conn_expire(unsigned long data)
+{
+ struct ip_vs_conn *cp = (struct ip_vs_conn *)data;
+
+ if (cp->timeout_table)
+ cp->timeout = cp->timeout_table->timeout[IP_VS_S_TIME_WAIT];
+ else
+ cp->timeout = vs_timeout_table.timeout[IP_VS_S_TIME_WAIT];
+
+ /*
+ * hey, I'm using it
+ */
+ atomic_inc(&cp->refcnt);
+
+ /*
+ * do I control anybody?
+ */
+ if (atomic_read(&cp->n_control))
+ goto expire_later;
+
+ /*
+ * unhash it if it is hashed in the conn table
+ */
+ if (!ip_vs_conn_unhash(cp))
+ goto expire_later;
+
+ /*
+ * refcnt==1 implies I'm the only one referrer
+ */
+ if (likely(atomic_read(&cp->refcnt) == 1)) {
+ /* make sure that there is no timer on it now */
+ if (timer_pending(&cp->timer))
+ del_timer(&cp->timer);
+
+ /* does anybody control me? */
+ if (cp->control)
+ ip_vs_control_del(cp);
+
+ ip_vs_unbind_dest(cp);
+ ip_vs_unbind_app(cp);
+ ip_vs_timeout_detach(cp);
+ if (cp->flags & IP_VS_CONN_F_NO_CPORT)
+ atomic_dec(&ip_vs_conn_no_cport_cnt);
+ atomic_dec(&ip_vs_conn_count);
+
+ kmem_cache_free(ip_vs_conn_cachep, cp);
+ return;
+ }
+
+ /* hash it back to the table */
+ ip_vs_conn_hash(cp);
+
+ expire_later:
+ IP_VS_DBG(7, "delayed: refcnt-1=%d conn.n_control=%d\n",
+ atomic_read(&cp->refcnt)-1,
+ atomic_read(&cp->n_control));
+
+ ip_vs_conn_put(cp);
+}
+
+
+void ip_vs_conn_expire_now(struct ip_vs_conn *cp)
+{
+ cp->timeout = 0;
+ mod_timer(&cp->timer, jiffies);
+ __ip_vs_conn_put(cp);
+}
+
+/*
+ * Create a new connection entry and hash it into the ip_vs_conn_tab.
+ */
+struct ip_vs_conn *
+ip_vs_conn_new(int proto, __u32 caddr, __u16 cport, __u32 vaddr, __u16 vport,
+ __u32 daddr, __u16 dport, unsigned flags,
+ struct ip_vs_dest *dest)
+{
+ struct ip_vs_conn *cp;
+
+ cp = kmem_cache_alloc(ip_vs_conn_cachep, GFP_ATOMIC);
+ if (cp == NULL) {
+ IP_VS_ERR_RL("ip_vs_conn_new: no memory available.\n");
+ return NULL;
+ }
+
+ memset(cp, 0, sizeof(*cp));
+ INIT_LIST_HEAD(&cp->c_list);
+ init_timer(&cp->timer);
+ cp->timer.data = (unsigned long)cp;
+ cp->timer.function = ip_vs_conn_expire;
+ ip_vs_timeout_attach(cp, ip_vs_timeout_table);
+ cp->protocol = proto;
+ cp->caddr = caddr;
+ cp->cport = cport;
+ cp->vaddr = vaddr;
+ cp->vport = vport;
+ cp->daddr = daddr;
+ cp->dport = dport;
+ cp->flags = flags;
+ cp->app_data = NULL;
+ cp->control = NULL;
+ cp->lock = SPIN_LOCK_UNLOCKED;
+
+ atomic_set(&cp->n_control, 0);
+ atomic_set(&cp->in_pkts, 0);
+
+ atomic_inc(&ip_vs_conn_count);
+ if (flags & IP_VS_CONN_F_NO_CPORT)
+ atomic_inc(&ip_vs_conn_no_cport_cnt);
+
+ /* Bind its application helper (only for VS/NAT) if any */
+ ip_vs_bind_app(cp);
+
+ /* Bind the connection with a destination server */
+ ip_vs_bind_dest(cp, dest);
+
+ /* Set its state and timeout */
+ vs_set_state_timeout(cp, IP_VS_S_NONE);
+
+ /* Bind its packet transmitter */
+ ip_vs_bind_xmit(cp);
+
+ /*
+ * Set the entry is referenced by the current thread before hashing
+ * it in the table, so that other thread run ip_vs_random_dropentry
+ * but cannot drop this entry.
+ */
+ atomic_set(&cp->refcnt, 1);
+
+ /* Hash it in the ip_vs_conn_tab finally */
+ ip_vs_conn_hash(cp);
+
+ return cp;
+}
+
+
+/*
+ * /proc/net/ip_vs_conn entries
+ */
+static int
+ip_vs_conn_getinfo(char *buffer, char **start, off_t offset, int length)
+{
+ off_t pos=0;
+ int idx, len=0;
+ char temp[70];
+ struct ip_vs_conn *cp;
+ struct list_head *l, *e;
+
+ pos = 128;
+ if (pos > offset) {
+ len += sprintf(buffer+len, "%-127s\n",
+ "Pro FromIP FPrt ToIP TPrt DestIP DPrt State Expires");
+ }
+
+ for(idx = 0; idx < IP_VS_CONN_TAB_SIZE; idx++) {
+ /*
+ * Lock is actually only need in next loop
+ * we are called from uspace: must stop bh.
+ */
+ ct_read_lock_bh(idx);
+
+ l = &ip_vs_conn_tab[idx];
+ for (e=l->next; e!=l; e=e->next) {
+ cp = list_entry(e, struct ip_vs_conn, c_list);
+ pos += 128;
+ if (pos <= offset)
+ continue;
+ sprintf(temp,
+ "%-3s %08X %04X %08X %04X %08X %04X %-11s %7lu",
+ ip_vs_proto_name(cp->protocol),
+ ntohl(cp->caddr), ntohs(cp->cport),
+ ntohl(cp->vaddr), ntohs(cp->vport),
+ ntohl(cp->daddr), ntohs(cp->dport),
+ ip_vs_state_name(cp->state),
+ (cp->timer.expires-jiffies)/HZ);
+ len += sprintf(buffer+len, "%-127s\n", temp);
+ if (pos >= offset+length) {
+ ct_read_unlock_bh(idx);
+ goto done;
+ }
+ }
+ ct_read_unlock_bh(idx);
+ }
+
+ done:
+ *start = buffer+len-(pos-offset); /* Start of wanted data */
+ len = pos-offset;
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+
+/*
+ * Randomly drop connection entries before running out of memory
+ */
+static inline int todrop_entry(struct ip_vs_conn *cp)
+{
+ /*
+ * The drop rate array needs tuning for real environments.
+ * Called from timer bh only => no locking
+ */
+ static char todrop_rate[9] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
+ static char todrop_counter[9] = {0};
+ int i;
+
+ /* if the conn entry hasn't lasted for 60 seconds, don't drop it.
+ This will leave enough time for normal connection to get
+ through. */
+ if (cp->timeout+jiffies-cp->timer.expires < 60*HZ)
+ return 0;
+
+ /* Don't drop the entry if its number of incoming packets is not
+ located in [0, 8] */
+ i = atomic_read(&cp->in_pkts);
+ if (i > 8 || i < 0) return 0;
+
+ if (!todrop_rate[i]) return 0;
+ if (--todrop_counter[i] > 0) return 0;
+
+ todrop_counter[i] = todrop_rate[i];
+ return 1;
+}
+
+
+void ip_vs_random_dropentry(void)
+{
+ int idx;
+ struct ip_vs_conn *cp;
+ struct list_head *l,*e;
+ struct ip_vs_conn *ct;
+
+ /*
+ * Randomly scan 1/32 of the whole table every second
+ */
+ for (idx=0; idx<(IP_VS_CONN_TAB_SIZE>>5); idx++) {
+ unsigned hash = net_random()&IP_VS_CONN_TAB_MASK;
+
+ /*
+ * Lock is actually needed in this loop.
+ */
+ ct_write_lock(hash);
+
+ l = &ip_vs_conn_tab[hash];
+ for (e=l->next; e!=l; e=e->next) {
+ cp = list_entry(e, struct ip_vs_conn, c_list);
+ if (!cp->cport && !(cp->flags & IP_VS_CONN_F_NO_CPORT))
+ /* connection template */
+ continue;
+ switch(cp->state) {
+ case IP_VS_S_SYN_RECV:
+ case IP_VS_S_SYNACK:
+ break;
+
+ case IP_VS_S_ESTABLISHED:
+ case IP_VS_S_UDP:
+ if (todrop_entry(cp))
+ break;
+ continue;
+
+ default:
+ continue;
+ }
+
+ /*
+ * Drop the entry, and drop its ct if not referenced
+ */
+ atomic_inc(&cp->refcnt);
+ ct_write_unlock(hash);
+
+ if ((ct = cp->control))
+ atomic_inc(&ct->refcnt);
+ IP_VS_DBG(4, "del connection\n");
+ ip_vs_conn_expire_now(cp);
+ if (ct) {
+ IP_VS_DBG(4, "del conn template\n");
+ ip_vs_conn_expire_now(ct);
+ }
+ ct_write_lock(hash);
+ }
+ ct_write_unlock(hash);
+ }
+}
+
+
+/*
+ * Flush all the connection entries in the ip_vs_conn_tab
+ */
+static void ip_vs_conn_flush(void)
+{
+ int idx;
+ struct ip_vs_conn *cp;
+ struct list_head *l,*e;
+ struct ip_vs_conn *ct;
+
+ flush_again:
+ for (idx=0; idx<IP_VS_CONN_TAB_SIZE; idx++) {
+ /*
+ * Lock is actually needed in this loop.
+ */
+ ct_write_lock_bh(idx);
+
+ l = &ip_vs_conn_tab[idx];
+ for (e=l->next; e!=l; e=e->next) {
+ cp = list_entry(e, struct ip_vs_conn, c_list);
+ atomic_inc(&cp->refcnt);
+ ct_write_unlock(idx);
+
+ if ((ct = cp->control))
+ atomic_inc(&ct->refcnt);
+ IP_VS_DBG(4, "del connection\n");
+ ip_vs_conn_expire_now(cp);
+ if (ct) {
+ IP_VS_DBG(4, "del conn template\n");
+ ip_vs_conn_expire_now(ct);
+ }
+ ct_write_lock(idx);
+ }
+ ct_write_unlock_bh(idx);
+ }
+
+ /* the counter may be not NULL, because maybe some conn entries
+ are run by slow timer handler or unhashed but still referred */
+ if (atomic_read(&ip_vs_conn_count) != 0) {
+ schedule();
+ goto flush_again;
+ }
+}
+
+
+int ip_vs_conn_init(void)
+{
+ int idx;
+
+ /*
+ * Allocate the connection hash table and initialize its list heads
+ */
+ ip_vs_conn_tab = vmalloc(IP_VS_CONN_TAB_SIZE*sizeof(struct list_head));
+ if (!ip_vs_conn_tab)
+ return -ENOMEM;
+
+ IP_VS_INFO("Connection hash table configured "
+ "(size=%d, memory=%ldKbytes)\n",
+ IP_VS_CONN_TAB_SIZE,
+ (long)(IP_VS_CONN_TAB_SIZE*sizeof(struct list_head))/1024);
+ IP_VS_DBG(0, "Each connection entry needs %d bytes at least\n",
+ sizeof(struct ip_vs_conn));
+
+ for (idx = 0; idx < IP_VS_CONN_TAB_SIZE; idx++) {
+ INIT_LIST_HEAD(&ip_vs_conn_tab[idx]);
+ }
+
+ for (idx = 0; idx < CT_LOCKARRAY_SIZE; idx++) {
+ __ip_vs_conntbl_lock_array[idx].l = RW_LOCK_UNLOCKED;
+ }
+
+ /* Allocate ip_vs_conn slab cache */
+ ip_vs_conn_cachep = kmem_cache_create("ip_vs_conn",
+ sizeof(struct ip_vs_conn), 0,
+ SLAB_HWCACHE_ALIGN, NULL, NULL);
+ if (!ip_vs_conn_cachep) {
+ vfree(ip_vs_conn_tab);
+ return -ENOMEM;
+ }
+
+ proc_net_create("ip_vs_conn", 0, ip_vs_conn_getinfo);
+
+ /* calculate the random value for connection hash */
+ get_random_bytes(&ip_vs_conn_rnd, sizeof(ip_vs_conn_rnd));
+
+ return 0;
+}
+
+void ip_vs_conn_cleanup(void)
+{
+ /* flush all the connection entries first */
+ ip_vs_conn_flush();
+
+ /* Release the empty cache */
+ kmem_cache_destroy(ip_vs_conn_cachep);
+ proc_net_remove("ip_vs_conn");
+ vfree(ip_vs_conn_tab);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_core.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_core.c
new file mode 100644
index 0000000..bae146b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_core.c
@@ -0,0 +1,1284 @@
+/*
+ * IPVS An implementation of the IP virtual server support for the
+ * LINUX operating system. IPVS is now implemented as a module
+ * over the Netfilter framework. IPVS can be used to build a
+ * high-performance and highly available server based on a
+ * cluster of servers.
+ *
+ * Version: $Id: ip_vs_core.c,v 1.31.2.5 2003/07/29 14:37:12 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
+ * Peter Kese <peter.kese@ijs.si>
+ * Julian Anastasov <ja@ssi.bg>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * The IPVS code for kernel 2.2 was done by Wensong Zhang and Peter Kese,
+ * with changes/fixes from Julian Anastasov, Lars Marowsky-Bree, Horms
+ * and others.
+ *
+ * Changes:
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/icmp.h>
+
+#include <net/ip.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+#include <net/icmp.h> /* for icmp_send */
+#include <net/route.h>
+
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4.h>
+
+#include <net/ip_vs.h>
+
+
+EXPORT_SYMBOL(register_ip_vs_scheduler);
+EXPORT_SYMBOL(unregister_ip_vs_scheduler);
+EXPORT_SYMBOL(ip_vs_skb_replace);
+EXPORT_SYMBOL(ip_vs_proto_name);
+EXPORT_SYMBOL(ip_vs_conn_new);
+EXPORT_SYMBOL(ip_vs_conn_in_get);
+EXPORT_SYMBOL(ip_vs_conn_out_get);
+EXPORT_SYMBOL(ip_vs_conn_listen);
+EXPORT_SYMBOL(ip_vs_conn_put);
+#ifdef CONFIG_IP_VS_DEBUG
+EXPORT_SYMBOL(ip_vs_get_debug_level);
+#endif
+EXPORT_SYMBOL(check_for_ip_vs_out);
+
+
+/* ID used in ICMP lookups */
+#define icmp_id(icmph) ((icmph->un).echo.id)
+
+const char *ip_vs_proto_name(unsigned proto)
+{
+ static char buf[20];
+
+ switch (proto) {
+ case IPPROTO_IP:
+ return "IP";
+ case IPPROTO_UDP:
+ return "UDP";
+ case IPPROTO_TCP:
+ return "TCP";
+ case IPPROTO_ICMP:
+ return "ICMP";
+ default:
+ sprintf(buf, "IP_%d", proto);
+ return buf;
+ }
+}
+
+
+static inline void
+ip_vs_in_stats(struct ip_vs_conn *cp, struct sk_buff *skb)
+{
+ struct ip_vs_dest *dest = cp->dest;
+ if (dest && (dest->flags & IP_VS_DEST_F_AVAILABLE)) {
+ spin_lock(&dest->stats.lock);
+ dest->stats.inpkts++;
+ dest->stats.inbytes += skb->len;
+ spin_unlock(&dest->stats.lock);
+
+ spin_lock(&dest->svc->stats.lock);
+ dest->svc->stats.inpkts++;
+ dest->svc->stats.inbytes += skb->len;
+ spin_unlock(&dest->svc->stats.lock);
+
+ spin_lock(&ip_vs_stats.lock);
+ ip_vs_stats.inpkts++;
+ ip_vs_stats.inbytes += skb->len;
+ spin_unlock(&ip_vs_stats.lock);
+ }
+}
+
+
+static inline void
+ip_vs_out_stats(struct ip_vs_conn *cp, struct sk_buff *skb)
+{
+ struct ip_vs_dest *dest = cp->dest;
+ if (dest && (dest->flags & IP_VS_DEST_F_AVAILABLE)) {
+ spin_lock(&dest->stats.lock);
+ dest->stats.outpkts++;
+ dest->stats.outbytes += skb->len;
+ spin_unlock(&dest->stats.lock);
+
+ spin_lock(&dest->svc->stats.lock);
+ dest->svc->stats.outpkts++;
+ dest->svc->stats.outbytes += skb->len;
+ spin_unlock(&dest->svc->stats.lock);
+
+ spin_lock(&ip_vs_stats.lock);
+ ip_vs_stats.outpkts++;
+ ip_vs_stats.outbytes += skb->len;
+ spin_unlock(&ip_vs_stats.lock);
+ }
+}
+
+
+static inline void
+ip_vs_conn_stats(struct ip_vs_conn *cp, struct ip_vs_service *svc)
+{
+ spin_lock(&cp->dest->stats.lock);
+ cp->dest->stats.conns++;
+ spin_unlock(&cp->dest->stats.lock);
+
+ spin_lock(&svc->stats.lock);
+ svc->stats.conns++;
+ spin_unlock(&svc->stats.lock);
+
+ spin_lock(&ip_vs_stats.lock);
+ ip_vs_stats.conns++;
+ spin_unlock(&ip_vs_stats.lock);
+}
+
+/*
+ * IPVS persistent scheduling function
+ * It creates a connection entry according to its template if exists,
+ * or selects a server and creates a connection entry plus a template.
+ * Locking: we are svc user (svc->refcnt), so we hold all dests too
+ */
+static struct ip_vs_conn *
+ip_vs_sched_persist(struct ip_vs_service *svc, struct iphdr *iph)
+{
+ struct ip_vs_conn *cp = NULL;
+ struct ip_vs_dest *dest;
+ const __u16 *portp;
+ struct ip_vs_conn *ct;
+ __u16 dport; /* destination port to forward */
+ __u32 snet; /* source network of the client, after masking */
+
+ portp = (__u16 *)&(((char *)iph)[iph->ihl*4]);
+
+ /* Mask saddr with the netmask to adjust template granularity */
+ snet = iph->saddr & svc->netmask;
+
+ IP_VS_DBG(6, "P-schedule: src %u.%u.%u.%u:%u dest %u.%u.%u.%u:%u "
+ "mnet %u.%u.%u.%u\n",
+ NIPQUAD(iph->saddr), ntohs(portp[0]),
+ NIPQUAD(iph->daddr), ntohs(portp[1]),
+ NIPQUAD(snet));
+
+ /*
+ * As far as we know, FTP is a very complicated network protocol, and
+ * it uses control connection and data connections. For active FTP,
+ * FTP server initialize data connection to the client, its source port
+ * is often 20. For passive FTP, FTP server tells the clients the port
+ * that it passively listens to, and the client issues the data
+ * connection. In the tunneling or direct routing mode, the load
+ * balancer is on the client-to-server half of connection, the port
+ * number is unknown to the load balancer. So, a conn template like
+ * <caddr, 0, vaddr, 0, daddr, 0> is created for persistent FTP
+ * service, and a template like <caddr, 0, vaddr, vport, daddr, dport>
+ * is created for other persistent services.
+ */
+ if (portp[1] == svc->port) {
+ /* Check if a template already exists */
+ if (svc->port != FTPPORT)
+ ct = ip_vs_conn_in_get(iph->protocol, snet, 0,
+ iph->daddr, portp[1]);
+ else
+ ct = ip_vs_conn_in_get(iph->protocol, snet, 0,
+ iph->daddr, 0);
+
+ if (!ct || !ip_vs_check_template(ct)) {
+ /*
+ * No template found or the dest of the connection
+ * template is not available.
+ */
+ dest = svc->scheduler->schedule(svc, iph);
+ if (dest == NULL) {
+ IP_VS_DBG(1, "P-schedule: no dest found.\n");
+ return NULL;
+ }
+
+ /*
+ * Create a template like <protocol,caddr,0,
+ * vaddr,vport,daddr,dport> for non-ftp service,
+ * and <protocol,caddr,0,vaddr,0,daddr,0>
+ * for ftp service.
+ */
+ if (svc->port != FTPPORT)
+ ct = ip_vs_conn_new(iph->protocol,
+ snet, 0,
+ iph->daddr, portp[1],
+ dest->addr, dest->port,
+ 0,
+ dest);
+ else
+ ct = ip_vs_conn_new(iph->protocol,
+ snet, 0,
+ iph->daddr, 0,
+ dest->addr, 0,
+ 0,
+ dest);
+ if (ct == NULL)
+ return NULL;
+
+ ct->timeout = svc->timeout;
+ } else {
+ /* set destination with the found template */
+ dest = ct->dest;
+ }
+ dport = dest->port;
+ } else {
+ /*
+ * Note: persistent fwmark-based services and persistent
+ * port zero service are handled here.
+ * fwmark template: <IPPROTO_IP,caddr,0,fwmark,0,daddr,0>
+ * port zero template: <protocol,caddr,0,vaddr,0,daddr,0>
+ */
+ if (svc->fwmark)
+ ct = ip_vs_conn_in_get(IPPROTO_IP, snet, 0,
+ htonl(svc->fwmark), 0);
+ else
+ ct = ip_vs_conn_in_get(iph->protocol, snet, 0,
+ iph->daddr, 0);
+
+ if (!ct || !ip_vs_check_template(ct)) {
+ /*
+ * If it is not persistent port zero, return NULL,
+ * otherwise create a connection template.
+ */
+ if (svc->port)
+ return NULL;
+
+ dest = svc->scheduler->schedule(svc, iph);
+ if (dest == NULL) {
+ IP_VS_DBG(1, "P-schedule: no dest found.\n");
+ return NULL;
+ }
+
+ /*
+ * Create a template according to the service
+ */
+ if (svc->fwmark)
+ ct = ip_vs_conn_new(IPPROTO_IP,
+ snet, 0,
+ htonl(svc->fwmark), 0,
+ dest->addr, 0,
+ 0,
+ dest);
+ else
+ ct = ip_vs_conn_new(iph->protocol,
+ snet, 0,
+ iph->daddr, 0,
+ dest->addr, 0,
+ 0,
+ dest);
+ if (ct == NULL)
+ return NULL;
+
+ ct->timeout = svc->timeout;
+ } else {
+ /* set destination with the found template */
+ dest = ct->dest;
+ }
+ dport = portp[1];
+ }
+
+ /*
+ * Create a new connection according to the template
+ */
+ cp = ip_vs_conn_new(iph->protocol,
+ iph->saddr, portp[0],
+ iph->daddr, portp[1],
+ dest->addr, dport,
+ 0,
+ dest);
+ if (cp == NULL) {
+ ip_vs_conn_put(ct);
+ return NULL;
+ }
+
+ /*
+ * Increase the inactive connection counter
+ * because it is in Syn-Received
+ * state (inactive) when the connection is created.
+ */
+ atomic_inc(&dest->inactconns);
+
+ /*
+ * Add its control
+ */
+ ip_vs_control_add(cp, ct);
+
+ ip_vs_conn_put(ct);
+ return cp;
+}
+
+
+/*
+ * IPVS main scheduling function
+ * It selects a server according to the virtual service, and
+ * creates a connection entry.
+ */
+static struct ip_vs_conn *
+ip_vs_schedule(struct ip_vs_service *svc, struct iphdr *iph)
+{
+ struct ip_vs_conn *cp = NULL;
+ struct ip_vs_dest *dest;
+ const __u16 *portp;
+
+ /*
+ * Persistent service
+ */
+ if (svc->flags & IP_VS_SVC_F_PERSISTENT)
+ return ip_vs_sched_persist(svc, iph);
+
+ /*
+ * Non-persistent service
+ */
+ portp = (__u16 *)&(((char *)iph)[iph->ihl*4]);
+ if (!svc->fwmark && portp[1] != svc->port) {
+ if (!svc->port)
+ IP_VS_ERR("Schedule: port zero only supported "
+ "in persistent services, "
+ "check your ipvs configuration\n");
+ return NULL;
+ }
+
+ dest = svc->scheduler->schedule(svc, iph);
+ if (dest == NULL) {
+ IP_VS_DBG(1, "Schedule: no dest found.\n");
+ return NULL;
+ }
+
+ /*
+ * Create a connection entry.
+ */
+ cp = ip_vs_conn_new(iph->protocol,
+ iph->saddr, portp[0],
+ iph->daddr, portp[1],
+ dest->addr, dest->port?dest->port:portp[1],
+ 0,
+ dest);
+ if (cp == NULL)
+ return NULL;
+
+ /*
+ * Increase the inactive connection counter because it is in
+ * Syn-Received state (inactive) when the connection is created.
+ */
+ atomic_inc(&dest->inactconns);
+
+ IP_VS_DBG(6, "Schedule fwd:%c s:%s c:%u.%u.%u.%u:%u v:%u.%u.%u.%u:%u "
+ "d:%u.%u.%u.%u:%u flg:%X cnt:%d\n",
+ ip_vs_fwd_tag(cp), ip_vs_state_name(cp->state),
+ NIPQUAD(cp->caddr), ntohs(cp->cport),
+ NIPQUAD(cp->vaddr), ntohs(cp->vport),
+ NIPQUAD(cp->daddr), ntohs(cp->dport),
+ cp->flags, atomic_read(&cp->refcnt));
+
+ return cp;
+}
+
+
+/*
+ * Pass or drop the packet.
+ * Called by ip_vs_in, when the virtual service is available but
+ * no destination is available for a new connection.
+ */
+static int ip_vs_leave(struct ip_vs_service *svc, struct sk_buff *skb)
+{
+ struct iphdr *iph = skb->nh.iph;
+ __u16 *portp = (__u16 *)&(((char *)iph)[iph->ihl*4]);
+
+ /* if it is fwmark-based service, the cache_bypass sysctl is up
+ and the destination is RTN_UNICAST (and not local), then create
+ a cache_bypass connection entry */
+ if (sysctl_ip_vs_cache_bypass && svc->fwmark
+ && (inet_addr_type(iph->daddr) == RTN_UNICAST)) {
+ int ret;
+ struct ip_vs_conn *cp;
+
+ ip_vs_service_put(svc);
+
+ /* create a new connection entry */
+ IP_VS_DBG(6, "ip_vs_leave: create a cache_bypass entry\n");
+ cp = ip_vs_conn_new(iph->protocol,
+ iph->saddr, portp[0],
+ iph->daddr, portp[1],
+ 0, 0,
+ IP_VS_CONN_F_BYPASS,
+ NULL);
+ if (cp == NULL) {
+ kfree_skb(skb);
+ return NF_STOLEN;
+ }
+
+ /* statistics */
+ ip_vs_in_stats(cp, skb);
+
+ /* set state */
+ ip_vs_set_state(cp, VS_STATE_INPUT, iph, portp);
+
+ /* transmit the first SYN packet */
+ ret = cp->packet_xmit(skb, cp);
+
+ atomic_inc(&cp->in_pkts);
+ ip_vs_conn_put(cp);
+ return ret;
+ }
+
+ /*
+ * When the virtual ftp service is presented, packets destined
+ * for other services on the VIP may get here (except services
+ * listed in the ipvs table), pass the packets, because it is
+ * not ipvs job to decide to drop the packets.
+ */
+ if ((svc->port == FTPPORT) && (portp[1] != FTPPORT)) {
+ ip_vs_service_put(svc);
+ return NF_ACCEPT;
+ }
+
+ ip_vs_service_put(svc);
+
+ /*
+ * Notify the client that the destination is unreachable, and
+ * release the socket buffer.
+ * Since it is in IP layer, the TCP socket is not actually
+ * created, the TCP RST packet cannot be sent, instead that
+ * ICMP_PORT_UNREACH is sent here no matter it is TCP/UDP. --WZ
+ */
+ icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
+ kfree_skb(skb);
+ return NF_STOLEN;
+}
+
+
+/*
+ * It is hooked before NF_IP_PRI_NAT_SRC at the NF_IP_POST_ROUTING
+ * chain, and is used for VS/NAT.
+ * It detects packets for VS/NAT connections and sends the packets
+ * immediately. This can avoid that iptable_nat mangles the packets
+ * for VS/NAT.
+ */
+static unsigned int ip_vs_post_routing(unsigned int hooknum,
+ struct sk_buff **skb_p,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ struct sk_buff *skb = *skb_p;
+
+ if (!(skb->nfcache & NFC_IPVS_PROPERTY))
+ return NF_ACCEPT;
+
+ /* The packet was sent from IPVS, exit this chain */
+ (*okfn)(skb);
+
+ return NF_STOLEN;
+}
+
+
+/*
+ * Handle ICMP messages in the inside-to-outside direction (outgoing).
+ * Find any that might be relevant, check against existing connections,
+ * forward to the right destination host if relevant.
+ * Currently handles error types - unreachable, quench, ttl exceeded.
+ * (Only used in VS/NAT)
+ */
+static int ip_vs_out_icmp(struct sk_buff **skb_p)
+{
+ struct sk_buff *skb = *skb_p;
+ struct iphdr *iph;
+ struct icmphdr *icmph;
+ struct iphdr *ciph; /* The ip header contained within the ICMP */
+ __u16 *pptr; /* port numbers from TCP/UDP contained header */
+ unsigned short ihl;
+ unsigned short len;
+ unsigned short clen, csize;
+ struct ip_vs_conn *cp;
+
+ /* reassemble IP fragments, but will it happen in ICMP packets?? */
+ if (skb->nh.iph->frag_off & __constant_htons(IP_MF|IP_OFFSET)) {
+ skb = ip_defrag(skb, IP_DEFRAG_VS_OUT);
+ if (!skb)
+ return NF_STOLEN;
+ *skb_p = skb;
+ }
+
+ if (skb_is_nonlinear(skb)) {
+ if (skb_linearize(skb, GFP_ATOMIC) != 0)
+ return NF_DROP;
+ ip_send_check(skb->nh.iph);
+ }
+
+ iph = skb->nh.iph;
+ ihl = iph->ihl << 2;
+ icmph = (struct icmphdr *)((char *)iph + ihl);
+ len = ntohs(iph->tot_len) - ihl;
+ if (len < sizeof(struct icmphdr))
+ return NF_DROP;
+
+ IP_VS_DBG(12, "outgoing ICMP (%d,%d) %u.%u.%u.%u->%u.%u.%u.%u\n",
+ icmph->type, ntohs(icmp_id(icmph)),
+ NIPQUAD(iph->saddr), NIPQUAD(iph->daddr));
+
+ /*
+ * Work through seeing if this is for us.
+ * These checks are supposed to be in an order that means easy
+ * things are checked first to speed up processing.... however
+ * this means that some packets will manage to get a long way
+ * down this stack and then be rejected, but that's life.
+ */
+ if ((icmph->type != ICMP_DEST_UNREACH) &&
+ (icmph->type != ICMP_SOURCE_QUENCH) &&
+ (icmph->type != ICMP_TIME_EXCEEDED))
+ return NF_ACCEPT;
+
+ /* Now find the contained IP header */
+ clen = len - sizeof(struct icmphdr);
+ if (clen < sizeof(struct iphdr))
+ return NF_DROP;
+ ciph = (struct iphdr *) (icmph + 1);
+ csize = ciph->ihl << 2;
+ if (clen < csize)
+ return NF_DROP;
+
+ /* We are only interested ICMPs generated from TCP or UDP packets */
+ if (ciph->protocol != IPPROTO_UDP && ciph->protocol != IPPROTO_TCP)
+ return NF_ACCEPT;
+
+ /* Skip non-first embedded TCP/UDP fragments */
+ if (ciph->frag_off & __constant_htons(IP_OFFSET))
+ return NF_ACCEPT;
+
+ /* We need at least TCP/UDP ports here */
+ if (clen < csize + sizeof(struct udphdr))
+ return NF_DROP;
+
+ /*
+ * Find the ports involved - this packet was
+ * incoming so the ports are right way round
+ * (but reversed relative to outer IP header!)
+ */
+ pptr = (__u16 *)&(((char *)ciph)[csize]);
+
+ /* Ensure the checksum is correct */
+ if (ip_compute_csum((unsigned char *) icmph, len)) {
+ /* Failed checksum! */
+ IP_VS_DBG(1, "forward ICMP: failed checksum from %d.%d.%d.%d!\n",
+ NIPQUAD(iph->saddr));
+ return NF_DROP;
+ }
+
+ IP_VS_DBG(11, "Handling outgoing ICMP for "
+ "%u.%u.%u.%u:%d -> %u.%u.%u.%u:%d\n",
+ NIPQUAD(ciph->saddr), ntohs(pptr[0]),
+ NIPQUAD(ciph->daddr), ntohs(pptr[1]));
+
+ /* ciph content is actually <protocol, caddr, cport, daddr, dport> */
+ cp = ip_vs_conn_out_get(ciph->protocol, ciph->daddr, pptr[1],
+ ciph->saddr, pptr[0]);
+ if (!cp)
+ return NF_ACCEPT;
+
+ if (IP_VS_FWD_METHOD(cp) != 0) {
+ IP_VS_ERR("shouldn't reach here, because the box is on the"
+ "half connection in the tun/dr module.\n");
+ }
+
+ /* Now we do real damage to this packet...! */
+ /* First change the source IP address, and recalc checksum */
+ iph->saddr = cp->vaddr;
+ ip_send_check(iph);
+
+ /* Now change the *dest* address in the contained IP */
+ ciph->daddr = cp->vaddr;
+ ip_send_check(ciph);
+
+ /* the TCP/UDP dest port - cannot redo check */
+ pptr[1] = cp->vport;
+
+ /* And finally the ICMP checksum */
+ icmph->checksum = 0;
+ icmph->checksum = ip_compute_csum((unsigned char *) icmph, len);
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+
+ /* do the statistics and put it back */
+ ip_vs_out_stats(cp, skb);
+ ip_vs_conn_put(cp);
+
+ IP_VS_DBG(11, "Forwarding correct outgoing ICMP to "
+ "%u.%u.%u.%u:%d -> %u.%u.%u.%u:%d\n",
+ NIPQUAD(ciph->saddr), ntohs(pptr[0]),
+ NIPQUAD(ciph->daddr), ntohs(pptr[1]));
+
+ skb->nfcache |= NFC_IPVS_PROPERTY;
+
+ return NF_ACCEPT;
+}
+
+
+/*
+ * It is hooked at the NF_IP_FORWARD chain, used only for VS/NAT.
+ * Check if outgoing packet belongs to the established ip_vs_conn,
+ * rewrite addresses of the packet and send it on its way...
+ */
+static unsigned int ip_vs_out(unsigned int hooknum,
+ struct sk_buff **skb_p,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ struct sk_buff *skb = *skb_p;
+ struct iphdr *iph;
+ union ip_vs_tphdr h;
+ struct ip_vs_conn *cp;
+ int size;
+ int ihl;
+
+ EnterFunction(11);
+
+ if (skb->nfcache & NFC_IPVS_PROPERTY)
+ return NF_ACCEPT;
+
+ iph = skb->nh.iph;
+ if (iph->protocol == IPPROTO_ICMP)
+ return ip_vs_out_icmp(skb_p);
+
+ /* let it go if other IP protocols */
+ if (iph->protocol != IPPROTO_TCP && iph->protocol != IPPROTO_UDP)
+ return NF_ACCEPT;
+
+ /* reassemble IP fragments */
+ if (iph->frag_off & __constant_htons(IP_MF|IP_OFFSET)) {
+ skb = ip_defrag(skb, IP_DEFRAG_VS_OUT);
+ if (!skb)
+ return NF_STOLEN;
+ iph = skb->nh.iph;
+ *skb_p = skb;
+ }
+
+ /* make sure that protocol header available in skb data area,
+ note that skb data area may be reallocated. */
+ ihl = iph->ihl << 2;
+ if (ip_vs_header_check(skb, iph->protocol, ihl) == -1)
+ return NF_DROP;
+
+ iph = skb->nh.iph;
+ h.raw = (char*) iph + ihl;
+
+ /*
+ * Check if the packet belongs to an old entry
+ */
+ cp = ip_vs_conn_out_get(iph->protocol, iph->saddr, h.portp[0],
+ iph->daddr, h.portp[1]);
+ if (!cp) {
+ if (sysctl_ip_vs_nat_icmp_send &&
+ ip_vs_lookup_real_service(iph->protocol,
+ iph->saddr, h.portp[0])) {
+ /*
+ * Notify the real server: there is no existing
+ * entry if it is not RST packet or not TCP packet.
+ */
+ if (!h.th->rst || iph->protocol != IPPROTO_TCP) {
+ icmp_send(skb, ICMP_DEST_UNREACH,
+ ICMP_PORT_UNREACH, 0);
+ kfree_skb(skb);
+ return NF_STOLEN;
+ }
+ }
+ IP_VS_DBG(12, "packet for %s %d.%d.%d.%d:%d "
+ "continue traversal as normal.\n",
+ ip_vs_proto_name(iph->protocol),
+ NIPQUAD(iph->daddr),
+ ntohs(h.portp[1]));
+ if (skb_is_nonlinear(skb))
+ ip_send_check(iph);
+ return NF_ACCEPT;
+ }
+
+ /*
+ * If it has ip_vs_app helper, the helper may change the payload,
+ * so it needs full checksum checking and checksum calculation.
+ * If not, only the header (addr/port) is changed, so it is fast
+ * to do incremental checksum update, and let the destination host
+ * do final checksum checking.
+ */
+
+ if (cp->app && skb_is_nonlinear(skb)) {
+ if (skb_linearize(skb, GFP_ATOMIC) != 0) {
+ ip_vs_conn_put(cp);
+ return NF_DROP;
+ }
+ iph = skb->nh.iph;
+ h.raw = (char*) iph + ihl;
+ }
+
+ size = skb->len - ihl;
+ IP_VS_DBG(11, "O-pkt: %s size=%d\n",
+ ip_vs_proto_name(iph->protocol), size);
+
+ /* do TCP/UDP checksum checking if it has application helper */
+ if (cp->app && (iph->protocol != IPPROTO_UDP || h.uh->check != 0)) {
+ switch (skb->ip_summed) {
+ case CHECKSUM_NONE:
+ skb->csum = csum_partial(h.raw, size, 0);
+ case CHECKSUM_HW:
+ if (csum_tcpudp_magic(iph->saddr, iph->daddr, size,
+ iph->protocol, skb->csum)) {
+ ip_vs_conn_put(cp);
+ IP_VS_DBG_RL("Outgoing failed %s checksum "
+ "from %d.%d.%d.%d (size=%d)!\n",
+ ip_vs_proto_name(iph->protocol),
+ NIPQUAD(iph->saddr),
+ size);
+ return NF_DROP;
+ }
+ break;
+ default:
+ /* CHECKSUM_UNNECESSARY */
+ break;
+ }
+ }
+
+ IP_VS_DBG(11, "Outgoing %s %u.%u.%u.%u:%d->%u.%u.%u.%u:%d\n",
+ ip_vs_proto_name(iph->protocol),
+ NIPQUAD(iph->saddr), ntohs(h.portp[0]),
+ NIPQUAD(iph->daddr), ntohs(h.portp[1]));
+
+ /* mangle the packet */
+ iph->saddr = cp->vaddr;
+ h.portp[0] = cp->vport;
+
+ /*
+ * Call application helper if needed
+ */
+ if (ip_vs_app_pkt_out(cp, skb) != 0) {
+ /* skb data has probably changed, update pointers */
+ iph = skb->nh.iph;
+ h.raw = (char*)iph + ihl;
+ size = skb->len - ihl;
+ }
+
+ /*
+ * Adjust TCP/UDP checksums
+ */
+ if (!cp->app && (iph->protocol != IPPROTO_UDP || h.uh->check != 0)) {
+ /* Only port and addr are changed, do fast csum update */
+ ip_vs_fast_check_update(&h, cp->daddr, cp->vaddr,
+ cp->dport, cp->vport, iph->protocol);
+ if (skb->ip_summed == CHECKSUM_HW)
+ skb->ip_summed = CHECKSUM_NONE;
+ } else {
+ /* full checksum calculation */
+ switch (iph->protocol) {
+ case IPPROTO_TCP:
+ h.th->check = 0;
+ skb->csum = csum_partial(h.raw, size, 0);
+ h.th->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
+ size, iph->protocol,
+ skb->csum);
+ IP_VS_DBG(11, "O-pkt: %s O-csum=%d (+%d)\n",
+ ip_vs_proto_name(iph->protocol), h.th->check,
+ (char*)&(h.th->check) - (char*)h.raw);
+ break;
+ case IPPROTO_UDP:
+ h.uh->check = 0;
+ skb->csum = csum_partial(h.raw, size, 0);
+ h.uh->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
+ size, iph->protocol,
+ skb->csum);
+ if (h.uh->check == 0)
+ h.uh->check = 0xFFFF;
+ IP_VS_DBG(11, "O-pkt: %s O-csum=%d (+%d)\n",
+ ip_vs_proto_name(iph->protocol), h.uh->check,
+ (char*)&(h.uh->check) - (char*)h.raw);
+ break;
+ }
+ }
+ ip_send_check(iph);
+
+ ip_vs_out_stats(cp, skb);
+ ip_vs_set_state(cp, VS_STATE_OUTPUT, iph, h.portp);
+ ip_vs_conn_put(cp);
+
+ skb->nfcache |= NFC_IPVS_PROPERTY;
+
+ LeaveFunction(11);
+ return NF_ACCEPT;
+}
+
+
+/*
+ * Check if the packet is for VS/NAT connections, then send it
+ * immediately.
+ * Called by ip_fw_compact to detect packets for VS/NAT before
+ * they are changed by ipchains masquerading code.
+ */
+unsigned int check_for_ip_vs_out(struct sk_buff **skb_p,
+ int (*okfn)(struct sk_buff *))
+{
+ unsigned int ret;
+
+ ret = ip_vs_out(NF_IP_FORWARD, skb_p, NULL, NULL, NULL);
+ if (ret != NF_ACCEPT) {
+ return ret;
+ } else {
+ /* send the packet immediately if it is already mangled
+ by ip_vs_out */
+ if ((*skb_p)->nfcache & NFC_IPVS_PROPERTY) {
+ (*okfn)(*skb_p);
+ return NF_STOLEN;
+ }
+ }
+ return NF_ACCEPT;
+}
+
+
+/*
+ * Handle ICMP messages in the outside-to-inside direction (incoming)
+ * and sometimes in outgoing direction from ip_vs_forward_icmp.
+ * Find any that might be relevant, check against existing connections,
+ * forward to the right destination host if relevant.
+ * Currently handles error types - unreachable, quench, ttl exceeded.
+ */
+static int ip_vs_in_icmp(struct sk_buff **skb_p)
+{
+ struct sk_buff *skb = *skb_p;
+ struct iphdr *iph;
+ struct icmphdr *icmph;
+ struct iphdr *ciph; /* The ip header contained within the ICMP */
+ __u16 *pptr; /* port numbers from TCP/UDP contained header */
+ unsigned short len;
+ unsigned short clen, csize;
+ struct ip_vs_conn *cp;
+ struct rtable *rt; /* Route to the other host */
+ int mtu;
+
+ if (skb_is_nonlinear(skb)) {
+ if (skb_linearize(skb, GFP_ATOMIC) != 0)
+ return NF_DROP;
+ }
+
+ iph = skb->nh.iph;
+ ip_send_check(iph);
+ icmph = (struct icmphdr *)((char *)iph + (iph->ihl << 2));
+ len = ntohs(iph->tot_len) - (iph->ihl<<2);
+ if (len < sizeof(struct icmphdr))
+ return NF_DROP;
+
+ IP_VS_DBG(12, "icmp in (%d,%d) %u.%u.%u.%u -> %u.%u.%u.%u\n",
+ icmph->type, ntohs(icmp_id(icmph)),
+ NIPQUAD(iph->saddr), NIPQUAD(iph->daddr));
+
+ if ((icmph->type != ICMP_DEST_UNREACH) &&
+ (icmph->type != ICMP_SOURCE_QUENCH) &&
+ (icmph->type != ICMP_TIME_EXCEEDED))
+ return NF_ACCEPT;
+
+ /*
+ * If we get here we have an ICMP error of one of the above 3 types
+ * Now find the contained IP header
+ */
+ clen = len - sizeof(struct icmphdr);
+ if (clen < sizeof(struct iphdr))
+ return NF_DROP;
+ ciph = (struct iphdr *) (icmph + 1);
+ csize = ciph->ihl << 2;
+ if (clen < csize)
+ return NF_DROP;
+
+ /* We are only interested ICMPs generated from TCP or UDP packets */
+ if (ciph->protocol != IPPROTO_UDP && ciph->protocol != IPPROTO_TCP)
+ return NF_ACCEPT;
+
+ /* Skip non-first embedded TCP/UDP fragments */
+ if (ciph->frag_off & __constant_htons(IP_OFFSET))
+ return NF_ACCEPT;
+
+ /* We need at least TCP/UDP ports here */
+ if (clen < csize + sizeof(struct udphdr))
+ return NF_DROP;
+
+ /* Ensure the checksum is correct */
+ if (ip_compute_csum((unsigned char *) icmph, len)) {
+ /* Failed checksum! */
+ IP_VS_ERR_RL("incoming ICMP: failed checksum from "
+ "%d.%d.%d.%d!\n", NIPQUAD(iph->saddr));
+ return NF_DROP;
+ }
+
+ pptr = (__u16 *)&(((char *)ciph)[csize]);
+
+ IP_VS_DBG(11, "Handling incoming ICMP for "
+ "%u.%u.%u.%u:%d -> %u.%u.%u.%u:%d\n",
+ NIPQUAD(ciph->saddr), ntohs(pptr[0]),
+ NIPQUAD(ciph->daddr), ntohs(pptr[1]));
+
+ /* This is pretty much what ip_vs_conn_in_get() does,
+ except parameters are in the reverse order */
+ cp = ip_vs_conn_in_get(ciph->protocol,
+ ciph->daddr, pptr[1],
+ ciph->saddr, pptr[0]);
+ if (cp == NULL)
+ return NF_ACCEPT;
+
+ ip_vs_in_stats(cp, skb);
+
+ /* The ICMP packet for VS/TUN, VS/DR and LOCALNODE will be
+ forwarded directly here, because there is no need to
+ translate address/port back */
+ if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) {
+ int ret;
+ if (cp->packet_xmit)
+ ret = cp->packet_xmit(skb, cp);
+ else
+ ret = NF_ACCEPT;
+ atomic_inc(&cp->in_pkts);
+ ip_vs_conn_put(cp);
+ return ret;
+ }
+
+ /*
+ * mangle and send the packet here
+ */
+ if (!(rt = __ip_vs_get_out_rt(cp, RT_TOS(iph->tos))))
+ goto tx_error_icmp;
+
+ /* MTU checking */
+ mtu = rt->u.dst.pmtu;
+ if ((skb->len > mtu) && (iph->frag_off&__constant_htons(IP_DF))) {
+ ip_rt_put(rt);
+ icmp_send(skb, ICMP_DEST_UNREACH,ICMP_FRAG_NEEDED, htonl(mtu));
+ IP_VS_DBG_RL("ip_vs_in_icmp(): frag needed\n");
+ goto tx_error;
+ }
+
+ /* drop old route */
+ dst_release(skb->dst);
+ skb->dst = &rt->u.dst;
+
+ /* copy-on-write the packet before mangling it */
+ if (ip_vs_skb_cow(skb, rt->u.dst.dev->hard_header_len,
+ &iph, (unsigned char**)&icmph)) {
+ ip_vs_conn_put(cp);
+ return NF_DROP;
+ }
+ ciph = (struct iphdr *) (icmph + 1);
+ pptr = (__u16 *)&(((char *)ciph)[csize]);
+
+ /* The ICMP packet for VS/NAT must be written to correct addresses
+ before being forwarded to the right server */
+
+ /* First change the dest IP address, and recalc checksum */
+ iph->daddr = cp->daddr;
+ ip_send_check(iph);
+
+ /* Now change the *source* address in the contained IP */
+ ciph->saddr = cp->daddr;
+ ip_send_check(ciph);
+
+ /* the TCP/UDP source port - cannot redo check */
+ pptr[0] = cp->dport;
+
+ /* And finally the ICMP checksum */
+ icmph->checksum = 0;
+ icmph->checksum = ip_compute_csum((unsigned char *) icmph, len);
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+
+ IP_VS_DBG(11, "Forwarding incoming ICMP to "
+ "%u.%u.%u.%u:%d -> %u.%u.%u.%u:%d\n",
+ NIPQUAD(ciph->saddr), ntohs(pptr[0]),
+ NIPQUAD(ciph->daddr), ntohs(pptr[1]));
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ skb->nf_debug = 1 << NF_IP_LOCAL_OUT;
+#endif /* CONFIG_NETFILTER_DEBUG */
+ ip_send(skb);
+ ip_vs_conn_put(cp);
+ return NF_STOLEN;
+
+ tx_error_icmp:
+ dst_link_failure(skb);
+ tx_error:
+ dev_kfree_skb(skb);
+ ip_vs_conn_put(cp);
+ return NF_STOLEN;
+}
+
+
+/*
+ * Check if it's for virtual services, look it up,
+ * and send it on its way...
+ */
+static unsigned int ip_vs_in(unsigned int hooknum,
+ struct sk_buff **skb_p,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ struct sk_buff *skb = *skb_p;
+ struct iphdr *iph = skb->nh.iph;
+ union ip_vs_tphdr h;
+ struct ip_vs_conn *cp;
+ struct ip_vs_service *svc;
+ int ihl;
+ int ret;
+
+ /*
+ * Big tappo: only PACKET_HOST (nor loopback neither mcasts)
+ * ... don't know why 1st test DOES NOT include 2nd (?)
+ */
+ if (skb->pkt_type != PACKET_HOST || skb->dev == &loopback_dev) {
+ IP_VS_DBG(12, "packet type=%d proto=%d daddr=%d.%d.%d.%d ignored\n",
+ skb->pkt_type,
+ iph->protocol,
+ NIPQUAD(iph->daddr));
+ return NF_ACCEPT;
+ }
+
+ if (iph->protocol == IPPROTO_ICMP)
+ return ip_vs_in_icmp(skb_p);
+
+ /* let it go if other IP protocols */
+ if (iph->protocol != IPPROTO_TCP && iph->protocol != IPPROTO_UDP)
+ return NF_ACCEPT;
+
+ /* make sure that protocol header available in skb data area,
+ note that skb data area may be reallocated. */
+ ihl = iph->ihl << 2;
+ if (ip_vs_header_check(skb, iph->protocol, ihl) == -1)
+ return NF_DROP;
+ iph = skb->nh.iph;
+ h.raw = (char*) iph + ihl;
+
+ /*
+ * Check if the packet belongs to an existing connection entry
+ */
+ cp = ip_vs_conn_in_get(iph->protocol, iph->saddr, h.portp[0],
+ iph->daddr, h.portp[1]);
+
+ if (!cp &&
+ (h.th->syn || (iph->protocol!=IPPROTO_TCP)) &&
+ (svc = ip_vs_service_get(skb->nfmark, iph->protocol,
+ iph->daddr, h.portp[1]))) {
+ if (ip_vs_todrop()) {
+ /*
+ * It seems that we are very loaded.
+ * We have to drop this packet :(
+ */
+ ip_vs_service_put(svc);
+ return NF_DROP;
+ }
+
+ /*
+ * Let the virtual server select a real server for the
+ * incoming connection, and create a connection entry.
+ */
+ cp = ip_vs_schedule(svc, iph);
+ if (!cp)
+ return ip_vs_leave(svc, skb);
+ ip_vs_conn_stats(cp, svc);
+ ip_vs_service_put(svc);
+ }
+
+ if (!cp) {
+ /* sorry, all this trouble for a no-hit :) */
+ IP_VS_DBG(12, "packet for %s %d.%d.%d.%d:%d continue "
+ "traversal as normal.\n",
+ ip_vs_proto_name(iph->protocol),
+ NIPQUAD(iph->daddr),
+ ntohs(h.portp[1]));
+ return NF_ACCEPT;
+ }
+
+ IP_VS_DBG(11, "Incoming %s %u.%u.%u.%u:%d->%u.%u.%u.%u:%d\n",
+ ip_vs_proto_name(iph->protocol),
+ NIPQUAD(iph->saddr), ntohs(h.portp[0]),
+ NIPQUAD(iph->daddr), ntohs(h.portp[1]));
+
+ /* Check the server status */
+ if (cp->dest && !(cp->dest->flags & IP_VS_DEST_F_AVAILABLE)) {
+ /* the destination server is not available */
+
+ if (sysctl_ip_vs_expire_nodest_conn) {
+ /* try to expire the connection immediately */
+ ip_vs_conn_expire_now(cp);
+ } else {
+ /* don't restart its timer, and silently
+ drop the packet. */
+ __ip_vs_conn_put(cp);
+ }
+ return NF_DROP;
+ }
+
+ ip_vs_in_stats(cp, skb);
+ ip_vs_set_state(cp, VS_STATE_INPUT, iph, h.portp);
+ if (cp->packet_xmit)
+ ret = cp->packet_xmit(skb, cp);
+ else {
+ IP_VS_DBG_RL("warning: packet_xmit is null");
+ ret = NF_ACCEPT;
+ }
+
+ /* increase its packet counter and check if it is needed
+ to be synchronized */
+ atomic_inc(&cp->in_pkts);
+ if (ip_vs_sync_state & IP_VS_STATE_MASTER &&
+ (cp->protocol != IPPROTO_TCP ||
+ cp->state == IP_VS_S_ESTABLISHED) &&
+ (atomic_read(&cp->in_pkts) % 50 == sysctl_ip_vs_sync_threshold))
+ ip_vs_sync_conn(cp);
+
+ ip_vs_conn_put(cp);
+ return ret;
+}
+
+
+/*
+ * It is hooked at the NF_IP_FORWARD chain, in order to catch ICMP
+ * packets destined for 0.0.0.0/0.
+ * When fwmark-based virtual service is used, such as transparent
+ * cache cluster, TCP packets can be marked and routed to ip_vs_in,
+ * but ICMP destined for 0.0.0.0/0 cannot not be easily marked and
+ * sent to ip_vs_in_icmp. So, catch them at the NF_IP_FORWARD chain
+ * and send them to ip_vs_in_icmp.
+ */
+static unsigned int ip_vs_forward_icmp(unsigned int hooknum,
+ struct sk_buff **skb_p,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ struct sk_buff *skb = *skb_p;
+ struct iphdr *iph = skb->nh.iph;
+
+ if (iph->protocol != IPPROTO_ICMP)
+ return NF_ACCEPT;
+
+ if (iph->frag_off & __constant_htons(IP_MF|IP_OFFSET)) {
+ skb = ip_defrag(skb, IP_DEFRAG_VS_FWD);
+ if (!skb)
+ return NF_STOLEN;
+ *skb_p = skb;
+ }
+
+ return ip_vs_in_icmp(skb_p);
+}
+
+
+/* After packet filtering, forward packet through VS/DR, VS/TUN,
+ or VS/NAT(change destination), so that filtering rules can be
+ applied to IPVS. */
+static struct nf_hook_ops ip_vs_in_ops = {
+ { NULL, NULL },
+ ip_vs_in, PF_INET, NF_IP_LOCAL_IN, 100
+};
+
+/* After packet filtering, change source only for VS/NAT */
+static struct nf_hook_ops ip_vs_out_ops = {
+ { NULL, NULL },
+ ip_vs_out, PF_INET, NF_IP_FORWARD, 100
+};
+
+/* After packet filtering (but before ip_vs_out_icmp), catch icmp
+ destined for 0.0.0.0/0, which is for incoming IPVS connections */
+static struct nf_hook_ops ip_vs_forward_icmp_ops = {
+ { NULL, NULL },
+ ip_vs_forward_icmp, PF_INET, NF_IP_FORWARD, 99
+};
+
+/* Before the netfilter connection tracking, exit from POST_ROUTING */
+static struct nf_hook_ops ip_vs_post_routing_ops = {
+ { NULL, NULL },
+ ip_vs_post_routing, PF_INET, NF_IP_POST_ROUTING, NF_IP_PRI_NAT_SRC-1
+};
+
+
+/*
+ * Initialize IP Virtual Server
+ */
+static int __init ip_vs_init(void)
+{
+ int ret;
+
+ ret = ip_vs_control_init();
+ if (ret < 0) {
+ IP_VS_ERR("can't setup control.\n");
+ goto cleanup_nothing;
+ }
+
+ ret = ip_vs_conn_init();
+ if (ret < 0) {
+ IP_VS_ERR("can't setup connection table.\n");
+ goto cleanup_control;
+ }
+
+ ret = ip_vs_app_init();
+ if (ret < 0) {
+ IP_VS_ERR("can't setup application helper.\n");
+ goto cleanup_conn;
+ }
+
+ ret = nf_register_hook(&ip_vs_in_ops);
+ if (ret < 0) {
+ IP_VS_ERR("can't register in hook.\n");
+ goto cleanup_app;
+ }
+ ret = nf_register_hook(&ip_vs_out_ops);
+ if (ret < 0) {
+ IP_VS_ERR("can't register out hook.\n");
+ goto cleanup_inops;
+ }
+ ret = nf_register_hook(&ip_vs_post_routing_ops);
+ if (ret < 0) {
+ IP_VS_ERR("can't register post_routing hook.\n");
+ goto cleanup_outops;
+ }
+ ret = nf_register_hook(&ip_vs_forward_icmp_ops);
+ if (ret < 0) {
+ IP_VS_ERR("can't register forward_icmp hook.\n");
+ goto cleanup_postroutingops;
+ }
+
+ IP_VS_INFO("ipvs loaded.\n");
+ return ret;
+
+ cleanup_postroutingops:
+ nf_unregister_hook(&ip_vs_post_routing_ops);
+ cleanup_outops:
+ nf_unregister_hook(&ip_vs_out_ops);
+ cleanup_inops:
+ nf_unregister_hook(&ip_vs_in_ops);
+ cleanup_app:
+ ip_vs_app_cleanup();
+ cleanup_conn:
+ ip_vs_conn_cleanup();
+ cleanup_control:
+ ip_vs_control_cleanup();
+ cleanup_nothing:
+ return ret;
+}
+
+static void __exit ip_vs_cleanup(void)
+{
+ nf_unregister_hook(&ip_vs_forward_icmp_ops);
+ nf_unregister_hook(&ip_vs_post_routing_ops);
+ nf_unregister_hook(&ip_vs_out_ops);
+ nf_unregister_hook(&ip_vs_in_ops);
+ ip_vs_app_cleanup();
+ ip_vs_conn_cleanup();
+ ip_vs_control_cleanup();
+ IP_VS_INFO("ipvs unloaded.\n");
+}
+
+module_init(ip_vs_init);
+module_exit(ip_vs_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_ctl.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_ctl.c
new file mode 100644
index 0000000..298f504
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_ctl.c
@@ -0,0 +1,2174 @@
+/*
+ * IPVS An implementation of the IP virtual server support for the
+ * LINUX operating system. IPVS is now implemented as a module
+ * over the NetFilter framework. IPVS can be used to build a
+ * high-performance and highly available server based on a
+ * cluster of servers.
+ *
+ * Version: $Id: ip_vs_ctl.c,v 1.30.2.3 2003/07/29 14:37:12 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
+ * Peter Kese <peter.kese@ijs.si>
+ * Julian Anastasov <ja@ssi.bg>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes:
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/sysctl.h>
+#include <linux/proc_fs.h>
+#include <linux/timer.h>
+#include <linux/swap.h>
+#include <linux/proc_fs.h>
+
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4.h>
+
+#include <net/ip.h>
+#include <net/sock.h>
+
+#include <asm/uaccess.h>
+
+#include <net/ip_vs.h>
+
+/* semaphore for IPVS sockopts. And, [gs]etsockopt may sleep. */
+static DECLARE_MUTEX(__ip_vs_mutex);
+
+/* lock for service table */
+rwlock_t __ip_vs_svc_lock = RW_LOCK_UNLOCKED;
+
+/* lock for table with the real services */
+static rwlock_t __ip_vs_rs_lock = RW_LOCK_UNLOCKED;
+
+/* lock for state and timeout tables */
+static rwlock_t __ip_vs_securetcp_lock = RW_LOCK_UNLOCKED;
+
+/* lock for drop entry handling */
+static spinlock_t __ip_vs_dropentry_lock = SPIN_LOCK_UNLOCKED;
+
+/* lock for drop packet handling */
+static spinlock_t __ip_vs_droppacket_lock = SPIN_LOCK_UNLOCKED;
+
+/* 1/rate drop and drop-entry variables */
+int ip_vs_drop_rate = 0;
+int ip_vs_drop_counter = 0;
+atomic_t ip_vs_dropentry = ATOMIC_INIT(0);
+
+/* number of virtual services */
+static int ip_vs_num_services = 0;
+
+/* sysctl variables */
+static int sysctl_ip_vs_drop_entry = 0;
+static int sysctl_ip_vs_drop_packet = 0;
+static int sysctl_ip_vs_secure_tcp = 0;
+static int sysctl_ip_vs_amemthresh = 2048;
+static int sysctl_ip_vs_am_droprate = 10;
+int sysctl_ip_vs_cache_bypass = 0;
+int sysctl_ip_vs_expire_nodest_conn = 0;
+int sysctl_ip_vs_expire_quiescent_template = 0;
+int sysctl_ip_vs_sync_threshold = 3;
+int sysctl_ip_vs_nat_icmp_send = 0;
+
+#ifdef CONFIG_IP_VS_DEBUG
+static int sysctl_ip_vs_debug_level = 0;
+
+int ip_vs_get_debug_level(void)
+{
+ return sysctl_ip_vs_debug_level;
+}
+#endif
+
+/*
+ * update_defense_level is called from timer bh and from sysctl.
+ */
+static void update_defense_level(void)
+{
+ struct sysinfo i;
+ int availmem;
+ int nomem;
+
+ /* we only count free and buffered memory (in pages) */
+ si_meminfo(&i);
+ availmem = i.freeram + i.bufferram;
+
+ nomem = (availmem < sysctl_ip_vs_amemthresh);
+
+ /* drop_entry */
+ spin_lock(&__ip_vs_dropentry_lock);
+ switch (sysctl_ip_vs_drop_entry) {
+ case 0:
+ atomic_set(&ip_vs_dropentry, 0);
+ break;
+ case 1:
+ if (nomem) {
+ atomic_set(&ip_vs_dropentry, 1);
+ sysctl_ip_vs_drop_entry = 2;
+ } else {
+ atomic_set(&ip_vs_dropentry, 0);
+ }
+ break;
+ case 2:
+ if (nomem) {
+ atomic_set(&ip_vs_dropentry, 1);
+ } else {
+ atomic_set(&ip_vs_dropentry, 0);
+ sysctl_ip_vs_drop_entry = 1;
+ };
+ break;
+ case 3:
+ atomic_set(&ip_vs_dropentry, 1);
+ break;
+ }
+ spin_unlock(&__ip_vs_dropentry_lock);
+
+ /* drop_packet */
+ spin_lock(&__ip_vs_droppacket_lock);
+ switch (sysctl_ip_vs_drop_packet) {
+ case 0:
+ ip_vs_drop_rate = 0;
+ break;
+ case 1:
+ if (nomem) {
+ ip_vs_drop_rate = ip_vs_drop_counter
+ = sysctl_ip_vs_amemthresh /
+ (sysctl_ip_vs_amemthresh - availmem);
+ sysctl_ip_vs_drop_packet = 2;
+ } else {
+ ip_vs_drop_rate = 0;
+ }
+ break;
+ case 2:
+ if (nomem) {
+ ip_vs_drop_rate = ip_vs_drop_counter
+ = sysctl_ip_vs_amemthresh /
+ (sysctl_ip_vs_amemthresh - availmem);
+ } else {
+ ip_vs_drop_rate = 0;
+ sysctl_ip_vs_drop_packet = 1;
+ }
+ break;
+ case 3:
+ ip_vs_drop_rate = sysctl_ip_vs_am_droprate;
+ break;
+ }
+ spin_unlock(&__ip_vs_droppacket_lock);
+
+ /* secure_tcp */
+ write_lock(&__ip_vs_securetcp_lock);
+ switch (sysctl_ip_vs_secure_tcp) {
+ case 0:
+ ip_vs_secure_tcp_set(0);
+ break;
+ case 1:
+ if (nomem) {
+ ip_vs_secure_tcp_set(1);
+ sysctl_ip_vs_secure_tcp = 2;
+ } else {
+ ip_vs_secure_tcp_set(0);
+ }
+ break;
+ case 2:
+ if (nomem) {
+ ip_vs_secure_tcp_set(1);
+ } else {
+ ip_vs_secure_tcp_set(0);
+ sysctl_ip_vs_secure_tcp = 1;
+ }
+ break;
+ case 3:
+ ip_vs_secure_tcp_set(1);
+ break;
+ }
+ write_unlock(&__ip_vs_securetcp_lock);
+}
+
+
+/*
+ * Timer for checking the defense
+ */
+static struct timer_list defense_timer;
+#define DEFENSE_TIMER_PERIOD 1*HZ
+
+static void defense_timer_handler(unsigned long data)
+{
+ update_defense_level();
+ if (atomic_read(&ip_vs_dropentry))
+ ip_vs_random_dropentry();
+
+ mod_timer(&defense_timer, jiffies + DEFENSE_TIMER_PERIOD);
+}
+
+
+/*
+ * Hash table: for virtual service lookups
+ */
+#define IP_VS_SVC_TAB_BITS 8
+#define IP_VS_SVC_TAB_SIZE (1 << IP_VS_SVC_TAB_BITS)
+#define IP_VS_SVC_TAB_MASK (IP_VS_SVC_TAB_SIZE - 1)
+
+/* the service table hashed by <protocol, addr, port> */
+static struct list_head ip_vs_svc_table[IP_VS_SVC_TAB_SIZE];
+/* the service table hashed by fwmark */
+static struct list_head ip_vs_svc_fwm_table[IP_VS_SVC_TAB_SIZE];
+
+/*
+ * Hash table: for real service lookups
+ */
+#define IP_VS_RTAB_BITS 4
+#define IP_VS_RTAB_SIZE (1 << IP_VS_RTAB_BITS)
+#define IP_VS_RTAB_MASK (IP_VS_RTAB_SIZE - 1)
+
+static struct list_head ip_vs_rtable[IP_VS_RTAB_SIZE];
+
+/*
+ * Trash for destinations
+ */
+static LIST_HEAD(ip_vs_dest_trash);
+
+/*
+ * FTP & NULL virtual service counters
+ */
+static atomic_t ip_vs_ftpsvc_counter = ATOMIC_INIT(0);
+static atomic_t ip_vs_nullsvc_counter = ATOMIC_INIT(0);
+
+
+/*
+ * Returns hash value for virtual service
+ */
+static __inline__ unsigned
+ip_vs_svc_hashkey(unsigned proto, __u32 addr, __u16 port)
+{
+ register unsigned porth = ntohs(port);
+
+ return (proto^ntohl(addr)^(porth>>IP_VS_SVC_TAB_BITS)^porth)
+ & IP_VS_SVC_TAB_MASK;
+}
+
+/*
+ * Returns hash value of fwmark for virtual service lookup
+ */
+static __inline__ unsigned ip_vs_svc_fwm_hashkey(__u32 fwmark)
+{
+ return fwmark & IP_VS_SVC_TAB_MASK;
+}
+
+/*
+ * Hashes ip_vs_service in the ip_vs_svc_table by <proto,addr,port>
+ * or in the ip_vs_svc_fwm_table by fwmark.
+ * Should be called with locked tables.
+ * Returns bool success.
+ */
+static int ip_vs_svc_hash(struct ip_vs_service *svc)
+{
+ unsigned hash;
+
+ if (svc->flags & IP_VS_SVC_F_HASHED) {
+ IP_VS_ERR("ip_vs_svc_hash(): request for already hashed, "
+ "called from %p\n", __builtin_return_address(0));
+ return 0;
+ }
+
+ if (svc->fwmark == 0) {
+ /*
+ * Hash it by <protocol,addr,port> in ip_vs_svc_table
+ */
+ hash = ip_vs_svc_hashkey(svc->protocol, svc->addr, svc->port);
+ list_add(&svc->s_list, &ip_vs_svc_table[hash]);
+ } else {
+ /*
+ * Hash it by fwmark in ip_vs_svc_fwm_table
+ */
+ hash = ip_vs_svc_fwm_hashkey(svc->fwmark);
+ list_add(&svc->f_list, &ip_vs_svc_fwm_table[hash]);
+ }
+
+ svc->flags |= IP_VS_SVC_F_HASHED;
+ /* increase its refcnt because it is referenced by the svc table */
+ atomic_inc(&svc->refcnt);
+ return 1;
+}
+
+
+/*
+ * Unhashes ip_vs_service from ip_vs_svc_table/ip_vs_svc_fwm_table.
+ * Should be called with locked tables.
+ * Returns bool success.
+ */
+static int ip_vs_svc_unhash(struct ip_vs_service *svc)
+{
+ if (!(svc->flags & IP_VS_SVC_F_HASHED)) {
+ IP_VS_ERR("ip_vs_svc_unhash(): request for unhash flagged, "
+ "called from %p\n", __builtin_return_address(0));
+ return 0;
+ }
+
+ if (svc->fwmark == 0) {
+ /*
+ * Remove it from the ip_vs_svc_table table.
+ */
+ list_del(&svc->s_list);
+ } else {
+ /*
+ * Remove it from the ip_vs_svc_fwm_table table.
+ */
+ list_del(&svc->f_list);
+ }
+
+ svc->flags &= ~IP_VS_SVC_F_HASHED;
+ atomic_dec(&svc->refcnt);
+ return 1;
+}
+
+
+/*
+ * Get service by {proto,addr,port} in the service table.
+ */
+static __inline__ struct ip_vs_service *
+__ip_vs_service_get(__u16 protocol, __u32 vaddr, __u16 vport)
+{
+ unsigned hash;
+ struct ip_vs_service *svc;
+ struct list_head *l,*e;
+
+ /*
+ * Check for "full" addressed entries
+ */
+ hash = ip_vs_svc_hashkey(protocol, vaddr, vport);
+
+ l = &ip_vs_svc_table[hash];
+ for (e=l->next; e!=l; e=e->next) {
+ svc = list_entry(e, struct ip_vs_service, s_list);
+ if ((svc->addr == vaddr)
+ && (svc->port == vport)
+ && (svc->protocol == protocol)) {
+ /* HIT */
+ atomic_inc(&svc->usecnt);
+ return svc;
+ }
+ }
+
+ return NULL;
+}
+
+
+/*
+ * Get service by {fwmark} in the service table.
+ */
+static __inline__ struct ip_vs_service *__ip_vs_svc_fwm_get(__u32 fwmark)
+{
+ unsigned hash;
+ struct ip_vs_service *svc;
+ struct list_head *l,*e;
+
+ /*
+ * Check for "full" addressed entries
+ */
+ hash = ip_vs_svc_fwm_hashkey(fwmark);
+
+ l = &ip_vs_svc_fwm_table[hash];
+ for (e=l->next; e!=l; e=e->next) {
+ svc = list_entry(e, struct ip_vs_service, f_list);
+ if (svc->fwmark == fwmark) {
+ /* HIT */
+ atomic_inc(&svc->usecnt);
+ return svc;
+ }
+ }
+
+ return NULL;
+}
+
+struct ip_vs_service *
+ip_vs_service_get(__u32 fwmark, __u16 protocol, __u32 vaddr, __u16 vport)
+{
+ struct ip_vs_service *svc;
+
+ read_lock(&__ip_vs_svc_lock);
+
+ /*
+ * Check the table hashed by fwmark first
+ */
+ if (fwmark && (svc = __ip_vs_svc_fwm_get(fwmark)))
+ goto out;
+
+ /*
+ * Check the table hashed by <protocol,addr,port>
+ * for "full" addressed entries
+ */
+ svc = __ip_vs_service_get(protocol, vaddr, vport);
+
+ if (svc == NULL
+ && protocol == IPPROTO_TCP
+ && atomic_read(&ip_vs_ftpsvc_counter)
+ && (vport == FTPDATA || ntohs(vport) >= PROT_SOCK)) {
+ /*
+ * Check if ftp service entry exists, the packet
+ * might belong to FTP data connections.
+ */
+ svc = __ip_vs_service_get(protocol, vaddr, FTPPORT);
+ }
+
+ if (svc == NULL
+ && atomic_read(&ip_vs_nullsvc_counter)) {
+ /*
+ * Check if the catch-all port (port zero) exists
+ */
+ svc = __ip_vs_service_get(protocol, vaddr, 0);
+ }
+
+ out:
+ read_unlock(&__ip_vs_svc_lock);
+
+ IP_VS_DBG(6, "lookup service: fwm %u %s %u.%u.%u.%u:%u %s\n",
+ fwmark, ip_vs_proto_name(protocol),
+ NIPQUAD(vaddr), ntohs(vport),
+ svc?"hit":"not hit");
+
+ return svc;
+}
+
+
+static inline void
+__ip_vs_bind_svc(struct ip_vs_dest *dest, struct ip_vs_service *svc)
+{
+ atomic_inc(&svc->refcnt);
+ dest->svc = svc;
+}
+
+static inline void
+__ip_vs_unbind_svc(struct ip_vs_dest *dest)
+{
+ struct ip_vs_service *svc = dest->svc;
+
+ dest->svc = NULL;
+ if (atomic_dec_and_test(&svc->refcnt))
+ kfree(svc);
+}
+
+/*
+ * Returns hash value for real service
+ */
+static __inline__ unsigned ip_vs_rs_hashkey(__u32 addr, __u16 port)
+{
+ register unsigned porth = ntohs(port);
+
+ return (ntohl(addr)^(porth>>IP_VS_RTAB_BITS)^porth)
+ & IP_VS_RTAB_MASK;
+}
+
+/*
+ * Hashes ip_vs_dest in ip_vs_rtable by proto,addr,port.
+ * should be called with locked tables.
+ * returns bool success.
+ */
+static int ip_vs_rs_hash(struct ip_vs_dest *dest)
+{
+ unsigned hash;
+
+ if (!list_empty(&dest->d_list)) {
+ return 0;
+ }
+
+ /*
+ * Hash by proto,addr,port,
+ * which are the parameters of the real service.
+ */
+ hash = ip_vs_rs_hashkey(dest->addr, dest->port);
+ list_add(&dest->d_list, &ip_vs_rtable[hash]);
+
+ return 1;
+}
+
+/*
+ * UNhashes ip_vs_dest from ip_vs_rtable.
+ * should be called with locked tables.
+ * returns bool success.
+ */
+static int ip_vs_rs_unhash(struct ip_vs_dest *dest)
+{
+ /*
+ * Remove it from the ip_vs_rtable table.
+ */
+ if (!list_empty(&dest->d_list)) {
+ list_del(&dest->d_list);
+ INIT_LIST_HEAD(&dest->d_list);
+ }
+
+ return 1;
+}
+
+/*
+ * Lookup real service by {proto,addr,port} in the real service table.
+ */
+struct ip_vs_dest *
+ip_vs_lookup_real_service(__u16 protocol, __u32 daddr, __u16 dport)
+{
+ unsigned hash;
+ struct ip_vs_dest *dest;
+ struct list_head *l,*e;
+
+ /*
+ * Check for "full" addressed entries
+ * Return the first found entry
+ */
+ hash = ip_vs_rs_hashkey(daddr, dport);
+
+ l = &ip_vs_rtable[hash];
+
+ read_lock(&__ip_vs_rs_lock);
+ for (e=l->next; e!=l; e=e->next) {
+ dest = list_entry(e, struct ip_vs_dest, d_list);
+ if ((dest->addr == daddr)
+ && (dest->port == dport)
+ && ((dest->protocol == protocol) ||
+ dest->vfwmark)) {
+ /* HIT */
+ read_unlock(&__ip_vs_rs_lock);
+ return dest;
+ }
+ }
+ read_unlock(&__ip_vs_rs_lock);
+
+ return NULL;
+}
+
+/*
+ * Lookup destination by {addr,port} in the given service
+ */
+static struct ip_vs_dest *
+ip_vs_lookup_dest(struct ip_vs_service *svc, __u32 daddr, __u16 dport)
+{
+ struct ip_vs_dest *dest;
+ struct list_head *l, *e;
+
+ /*
+ * Find the destination for the given service
+ */
+ l = &svc->destinations;
+ for (e=l->next; e!=l; e=e->next) {
+ dest = list_entry(e, struct ip_vs_dest, n_list);
+ if ((dest->addr == daddr) && (dest->port == dport)) {
+ /* HIT */
+ return dest;
+ }
+ }
+
+ return NULL;
+}
+
+
+/*
+ * Lookup dest by {svc,addr,port} in the destination trash.
+ * The destination trash is used to hold the destinations that are removed
+ * from the service table but are still referenced by some conn entries.
+ * The reason to add the destination trash is when the dest is temporary
+ * down (either by administrator or by monitor program), the dest can be
+ * picked back from the trash, the remaining connections to the dest can
+ * continue, and the counting information of the dest is also useful for
+ * scheduling.
+ */
+static struct ip_vs_dest *
+ip_vs_trash_get_dest(struct ip_vs_service *svc, __u32 daddr, __u16 dport)
+{
+ struct ip_vs_dest *dest;
+ struct list_head *l, *e;
+
+ /*
+ * Find the destination in trash
+ */
+ l = &ip_vs_dest_trash;
+
+ for (e=l->next; e!=l; e=e->next) {
+ dest = list_entry(e, struct ip_vs_dest, n_list);
+ IP_VS_DBG(3, "Destination %u/%u.%u.%u.%u:%u still in trash, "
+ "refcnt=%d\n",
+ dest->vfwmark,
+ NIPQUAD(dest->addr), ntohs(dest->port),
+ atomic_read(&dest->refcnt));
+ if (dest->addr == daddr &&
+ dest->port == dport &&
+ dest->vfwmark == svc->fwmark &&
+ dest->protocol == svc->protocol &&
+ (svc->fwmark ||
+ (dest->vaddr == svc->addr &&
+ dest->vport == svc->port))) {
+ /* HIT */
+ return dest;
+ }
+
+ /*
+ * Try to purge the destination from trash if not referenced
+ */
+ if (atomic_read(&dest->refcnt) == 1) {
+ IP_VS_DBG(3, "Removing destination %u/%u.%u.%u.%u:%u "
+ "from trash\n",
+ dest->vfwmark,
+ NIPQUAD(dest->addr), ntohs(dest->port));
+ e = e->prev;
+ list_del(&dest->n_list);
+ __ip_vs_dst_reset(dest);
+ __ip_vs_unbind_svc(dest);
+ kfree(dest);
+ }
+ }
+
+ return NULL;
+}
+
+
+/*
+ * Clean up all the destinations in the trash
+ * Called by the ip_vs_control_cleanup()
+ *
+ * When the ip_vs_control_clearup is activated by ipvs module exit,
+ * the service tables must have been flushed and all the connections
+ * are expired, and the refcnt of each destination in the trash must
+ * be 1, so we simply release them here.
+ */
+static void ip_vs_trash_cleanup(void)
+{
+ struct ip_vs_dest *dest;
+ struct list_head *l;
+
+ l = &ip_vs_dest_trash;
+
+ while (l->next != l) {
+ dest = list_entry(l->next, struct ip_vs_dest, n_list);
+ list_del(&dest->n_list);
+ __ip_vs_dst_reset(dest);
+ __ip_vs_unbind_svc(dest);
+ kfree(dest);
+ }
+}
+
+
+static inline void
+__ip_vs_zero_stats(struct ip_vs_stats *stats)
+{
+ spin_lock_bh(&stats->lock);
+ memset(stats, 0, (char *)&stats->lock - (char *)stats);
+ spin_unlock_bh(&stats->lock);
+ ip_vs_zero_estimator(stats);
+}
+
+/*
+ * Update a destination in the given service
+ */
+static void __ip_vs_update_dest(struct ip_vs_service *svc,
+ struct ip_vs_dest *dest,
+ struct ip_vs_rule_user *ur)
+{
+ int conn_flags;
+
+ /*
+ * Set the weight and the flags
+ */
+ atomic_set(&dest->weight, ur->weight);
+
+ conn_flags = ur->conn_flags | IP_VS_CONN_F_INACTIVE;
+
+ /*
+ * Check if local node and update the flags
+ */
+ if (inet_addr_type(ur->daddr) == RTN_LOCAL) {
+ conn_flags = (conn_flags & ~IP_VS_CONN_F_FWD_MASK)
+ | IP_VS_CONN_F_LOCALNODE;
+ }
+
+ /*
+ * Set the IP_VS_CONN_F_NOOUTPUT flag if not masquerading
+ */
+ if ((conn_flags & IP_VS_CONN_F_FWD_MASK) != 0) {
+ conn_flags |= IP_VS_CONN_F_NOOUTPUT;
+ } else {
+ /*
+ * Put the real service in ip_vs_rtable if not present.
+ * For now only for NAT!
+ */
+ write_lock_bh(&__ip_vs_rs_lock);
+ ip_vs_rs_hash(dest);
+ write_unlock_bh(&__ip_vs_rs_lock);
+ }
+ atomic_set(&dest->conn_flags, conn_flags);
+
+ /* bind the service */
+ if (!dest->svc) {
+ __ip_vs_bind_svc(dest, svc);
+ } else {
+ if (dest->svc != svc) {
+ __ip_vs_unbind_svc(dest);
+ __ip_vs_zero_stats(&dest->stats);
+ __ip_vs_bind_svc(dest, svc);
+ }
+ }
+
+ /* set the dest status flags */
+ dest->flags |= IP_VS_DEST_F_AVAILABLE;
+}
+
+
+/*
+ * Create a destination for the given service
+ */
+static int
+ip_vs_new_dest(struct ip_vs_service *svc, struct ip_vs_rule_user *ur,
+ struct ip_vs_dest **destp)
+{
+ struct ip_vs_dest *dest;
+ unsigned atype;
+
+ EnterFunction(2);
+
+ atype = inet_addr_type(ur->daddr);
+ if (atype != RTN_LOCAL && atype != RTN_UNICAST)
+ return -EINVAL;
+
+ *destp = dest = (struct ip_vs_dest*)
+ kmalloc(sizeof(struct ip_vs_dest), GFP_ATOMIC);
+ if (dest == NULL) {
+ IP_VS_ERR("ip_vs_new_dest: kmalloc failed.\n");
+ return -ENOMEM;
+ }
+ memset(dest, 0, sizeof(struct ip_vs_dest));
+
+ dest->protocol = svc->protocol;
+ dest->vaddr = svc->addr;
+ dest->vport = svc->port;
+ dest->vfwmark = svc->fwmark;
+ dest->addr = ur->daddr;
+ dest->port = ur->dport;
+
+ atomic_set(&dest->activeconns, 0);
+ atomic_set(&dest->inactconns, 0);
+ atomic_set(&dest->refcnt, 0);
+
+ INIT_LIST_HEAD(&dest->d_list);
+ dest->dst_lock = SPIN_LOCK_UNLOCKED;
+ dest->stats.lock = SPIN_LOCK_UNLOCKED;
+ __ip_vs_update_dest(svc, dest, ur);
+ ip_vs_new_estimator(&dest->stats);
+
+ LeaveFunction(2);
+ return 0;
+}
+
+
+/*
+ * Add a destination into an existing service
+ */
+static int ip_vs_add_dest(struct ip_vs_service *svc,
+ struct ip_vs_rule_user *ur)
+{
+ struct ip_vs_dest *dest;
+ __u32 daddr = ur->daddr;
+ __u16 dport = ur->dport;
+ int ret;
+
+ EnterFunction(2);
+
+ if (ur->weight < 0) {
+ IP_VS_ERR("ip_vs_add_dest(): server weight less than zero\n");
+ return -ERANGE;
+ }
+
+ /*
+ * Check if the dest already exists in the list
+ */
+ dest = ip_vs_lookup_dest(svc, daddr, dport);
+ if (dest != NULL) {
+ IP_VS_DBG(1, "ip_vs_add_dest(): dest already exists\n");
+ return -EEXIST;
+ }
+
+ /*
+ * Check if the dest already exists in the trash and
+ * is from the same service
+ */
+ dest = ip_vs_trash_get_dest(svc, daddr, dport);
+ if (dest != NULL) {
+ IP_VS_DBG(3, "Get destination %u.%u.%u.%u:%u from trash, "
+ "refcnt=%d, service %u/%u.%u.%u.%u:%u\n",
+ NIPQUAD(daddr), ntohs(dport),
+ atomic_read(&dest->refcnt),
+ dest->vfwmark,
+ NIPQUAD(dest->vaddr),
+ ntohs(dest->vport));
+ __ip_vs_update_dest(svc, dest, ur);
+
+ /*
+ * Get the destination from the trash
+ */
+ list_del(&dest->n_list);
+
+ ip_vs_new_estimator(&dest->stats);
+
+ write_lock_bh(&__ip_vs_svc_lock);
+
+ /*
+ * Wait until all other svc users go away.
+ */
+ while (atomic_read(&svc->usecnt) > 1) {};
+
+ list_add(&dest->n_list, &svc->destinations);
+ svc->num_dests++;
+
+ /* call the update_service function of its scheduler */
+ svc->scheduler->update_service(svc);
+
+ write_unlock_bh(&__ip_vs_svc_lock);
+ return 0;
+ }
+
+ /*
+ * Allocate and initialize the dest structure
+ */
+ ret = ip_vs_new_dest(svc, ur, &dest);
+ if (ret) {
+ return ret;
+ }
+
+ /*
+ * Add the dest entry into the list
+ */
+ atomic_inc(&dest->refcnt);
+
+ write_lock_bh(&__ip_vs_svc_lock);
+
+ /*
+ * Wait until all other svc users go away.
+ */
+ while (atomic_read(&svc->usecnt) > 1) {};
+
+ list_add(&dest->n_list, &svc->destinations);
+ svc->num_dests++;
+
+ /* call the update_service function of its scheduler */
+ svc->scheduler->update_service(svc);
+
+ write_unlock_bh(&__ip_vs_svc_lock);
+
+ LeaveFunction(2);
+
+ return 0;
+}
+
+
+/*
+ * Edit a destination in the given service
+ */
+static int ip_vs_edit_dest(struct ip_vs_service *svc,
+ struct ip_vs_rule_user *ur)
+{
+ struct ip_vs_dest *dest;
+ __u32 daddr = ur->daddr;
+ __u16 dport = ur->dport;
+
+ EnterFunction(2);
+
+ if (ur->weight < 0) {
+ IP_VS_ERR("ip_vs_edit_dest(): server weight less than zero\n");
+ return -ERANGE;
+ }
+
+ /*
+ * Lookup the destination list
+ */
+ dest = ip_vs_lookup_dest(svc, daddr, dport);
+ if (dest == NULL) {
+ IP_VS_DBG(1, "ip_vs_edit_dest(): dest doesn't exist\n");
+ return -ENOENT;
+ }
+
+ __ip_vs_update_dest(svc, dest, ur);
+
+ write_lock_bh(&__ip_vs_svc_lock);
+
+ /* Wait until all other svc users go away */
+ while (atomic_read(&svc->usecnt) > 1) {};
+
+ /* call the update_service, because server weight may be changed */
+ svc->scheduler->update_service(svc);
+
+ write_unlock_bh(&__ip_vs_svc_lock);
+
+ LeaveFunction(2);
+
+ return 0;
+}
+
+
+/*
+ * Delete a destination (must be already unlinked from the service)
+ */
+static void __ip_vs_del_dest(struct ip_vs_dest *dest)
+{
+ ip_vs_kill_estimator(&dest->stats);
+
+ /*
+ * Remove it from the d-linked list with the real services.
+ */
+ write_lock_bh(&__ip_vs_rs_lock);
+ ip_vs_rs_unhash(dest);
+ write_unlock_bh(&__ip_vs_rs_lock);
+
+ /*
+ * Decrease the refcnt of the dest, and free the dest
+ * if nobody refers to it (refcnt=0). Otherwise, throw
+ * the destination into the trash.
+ */
+ if (atomic_dec_and_test(&dest->refcnt)) {
+ __ip_vs_dst_reset(dest);
+ /* simply decrease svc->refcnt here, let the caller check
+ and release the service if nobody refers to it.
+ Only user context can release destination and service,
+ and only one user context can update virtual service at a
+ time, so the operation here is OK */
+ atomic_dec(&dest->svc->refcnt);
+ kfree(dest);
+ } else {
+ IP_VS_DBG(3, "Moving dest %u.%u.%u.%u:%u into trash, refcnt=%d\n",
+ NIPQUAD(dest->addr), ntohs(dest->port),
+ atomic_read(&dest->refcnt));
+ list_add(&dest->n_list, &ip_vs_dest_trash);
+ atomic_inc(&dest->refcnt);
+ }
+}
+
+
+/*
+ * Unlink a destination from the given service
+ */
+static void __ip_vs_unlink_dest(struct ip_vs_service *svc,
+ struct ip_vs_dest *dest,
+ int svcupd)
+{
+ dest->flags &= ~IP_VS_DEST_F_AVAILABLE;
+
+ /*
+ * Remove it from the d-linked destination list.
+ */
+ list_del(&dest->n_list);
+ svc->num_dests--;
+ if (svcupd) {
+ /*
+ * Call the update_service function of its scheduler
+ */
+ svc->scheduler->update_service(svc);
+ }
+}
+
+
+/*
+ * Delete a destination server in the given service
+ */
+static int ip_vs_del_dest(struct ip_vs_service *svc,struct ip_vs_rule_user *ur)
+{
+ struct ip_vs_dest *dest;
+ __u32 daddr = ur->daddr;
+ __u16 dport = ur->dport;
+
+ EnterFunction(2);
+
+ dest = ip_vs_lookup_dest(svc, daddr, dport);
+ if (dest == NULL) {
+ IP_VS_DBG(1, "ip_vs_del_dest(): destination not found!\n");
+ return -ENOENT;
+ }
+
+ write_lock_bh(&__ip_vs_svc_lock);
+
+ /*
+ * Wait until all other svc users go away.
+ */
+ while (atomic_read(&svc->usecnt) > 1) {};
+
+ /*
+ * Unlink dest from the service
+ */
+ __ip_vs_unlink_dest(svc, dest, 1);
+
+ write_unlock_bh(&__ip_vs_svc_lock);
+
+ /*
+ * Delete the destination
+ */
+ __ip_vs_del_dest(dest);
+
+ LeaveFunction(2);
+
+ return 0;
+}
+
+
+/*
+ * Add a service into the service hash table
+ */
+static int
+ip_vs_add_service(struct ip_vs_rule_user *ur, struct ip_vs_service **svc_p)
+{
+ int ret = 0;
+ struct ip_vs_scheduler *sched;
+ struct ip_vs_service *svc = NULL;
+
+ MOD_INC_USE_COUNT;
+
+ /*
+ * Lookup the scheduler, by 'ur->sched_name'
+ */
+ sched = ip_vs_scheduler_get(ur->sched_name);
+ if (sched == NULL) {
+ IP_VS_INFO("Scheduler module ip_vs_%s.o not found\n",
+ ur->sched_name);
+ ret = -ENOENT;
+ goto out_mod_dec;
+ }
+
+ svc = (struct ip_vs_service*)
+ kmalloc(sizeof(struct ip_vs_service), GFP_ATOMIC);
+ if (svc == NULL) {
+ IP_VS_DBG(1, "ip_vs_add_service: kmalloc failed.\n");
+ ret = -ENOMEM;
+ goto out_err;
+ }
+ memset(svc, 0, sizeof(struct ip_vs_service));
+
+ svc->protocol = ur->protocol;
+ svc->addr = ur->vaddr;
+ svc->port = ur->vport;
+ svc->fwmark = ur->vfwmark;
+ svc->flags = ur->vs_flags;
+ svc->timeout = ur->timeout * HZ;
+ svc->netmask = ur->netmask;
+
+ INIT_LIST_HEAD(&svc->destinations);
+ svc->sched_lock = RW_LOCK_UNLOCKED;
+ svc->stats.lock = SPIN_LOCK_UNLOCKED;
+
+ /*
+ * Bind the scheduler
+ */
+ ret = ip_vs_bind_scheduler(svc, sched);
+ if (ret) {
+ goto out_err;
+ }
+
+ /*
+ * Update the virtual service counters
+ */
+ if (svc->port == FTPPORT)
+ atomic_inc(&ip_vs_ftpsvc_counter);
+ else if (svc->port == 0)
+ atomic_inc(&ip_vs_nullsvc_counter);
+
+ /*
+ * I'm the first user of the service
+ */
+ atomic_set(&svc->usecnt, 1);
+ atomic_set(&svc->refcnt, 0);
+
+ ip_vs_new_estimator(&svc->stats);
+ ip_vs_num_services++;
+
+ /*
+ * Hash the service into the service table
+ */
+ write_lock_bh(&__ip_vs_svc_lock);
+ ip_vs_svc_hash(svc);
+ write_unlock_bh(&__ip_vs_svc_lock);
+
+ *svc_p = svc;
+ return 0;
+
+ out_err:
+ if (svc)
+ kfree(svc);
+ ip_vs_scheduler_put(sched);
+ out_mod_dec:
+ MOD_DEC_USE_COUNT;
+ return ret;
+}
+
+
+/*
+ * Edit a service and bind it with a new scheduler
+ */
+static int ip_vs_edit_service(struct ip_vs_service *svc,
+ struct ip_vs_rule_user *ur)
+{
+ struct ip_vs_scheduler *sched, *old_sched;
+ int ret = 0;
+
+ /*
+ * Lookup the scheduler, by 'ur->sched_name'
+ */
+ sched = ip_vs_scheduler_get(ur->sched_name);
+ if (sched == NULL) {
+ IP_VS_INFO("Scheduler module ip_vs_%s.o not found\n",
+ ur->sched_name);
+ return -ENOENT;
+ }
+
+ write_lock_bh(&__ip_vs_svc_lock);
+
+ /*
+ * Wait until all other svc users go away.
+ */
+ while (atomic_read(&svc->usecnt) > 1) {};
+
+ /*
+ * Set the flags and timeout value
+ */
+ svc->flags = ur->vs_flags | IP_VS_SVC_F_HASHED;
+ svc->timeout = ur->timeout * HZ;
+ svc->netmask = ur->netmask;
+
+ old_sched = svc->scheduler;
+ if (sched != old_sched) {
+ /*
+ * Unbind the old scheduler
+ */
+ if ((ret = ip_vs_unbind_scheduler(svc))) {
+ old_sched = sched;
+ goto out;
+ }
+
+ /*
+ * Bind the new scheduler
+ */
+ if ((ret = ip_vs_bind_scheduler(svc, sched))) {
+ /*
+ * If ip_vs_bind_scheduler fails, restore the old
+ * scheduler.
+ * The main reason of failure is out of memory.
+ *
+ * The question is if the old scheduler can be
+ * restored all the time. TODO: if it cannot be
+ * restored some time, we must delete the service,
+ * otherwise the system may crash.
+ */
+ ip_vs_bind_scheduler(svc, old_sched);
+ old_sched = sched;
+ }
+ }
+
+ out:
+ write_unlock_bh(&__ip_vs_svc_lock);
+
+ if (old_sched)
+ ip_vs_scheduler_put(old_sched);
+
+ return ret;
+}
+
+
+/*
+ * Delete a service from the service list
+ * The service must be unlinked, unlocked and not referenced!
+ */
+static void __ip_vs_del_service(struct ip_vs_service *svc)
+{
+ struct list_head *l;
+ struct ip_vs_dest *dest;
+ struct ip_vs_scheduler *old_sched;
+
+ ip_vs_num_services--;
+ ip_vs_kill_estimator(&svc->stats);
+
+ /*
+ * Unbind scheduler
+ */
+ old_sched = svc->scheduler;
+ ip_vs_unbind_scheduler(svc);
+ if (old_sched && old_sched->module)
+ __MOD_DEC_USE_COUNT(old_sched->module);
+
+ /*
+ * Unlink the whole destination list
+ */
+ l = &svc->destinations;
+ while (l->next != l) {
+ dest = list_entry(l->next, struct ip_vs_dest, n_list);
+ __ip_vs_unlink_dest(svc, dest, 0);
+ __ip_vs_del_dest(dest);
+ }
+
+ /*
+ * Update the virtual service counters
+ */
+ if (svc->port == FTPPORT)
+ atomic_dec(&ip_vs_ftpsvc_counter);
+ else if (svc->port == 0)
+ atomic_dec(&ip_vs_nullsvc_counter);
+
+ /*
+ * Free the service if nobody refers to it
+ */
+ if (atomic_read(&svc->refcnt) == 0)
+ kfree(svc);
+ MOD_DEC_USE_COUNT;
+}
+
+/*
+ * Delete a service from the service list
+ */
+static int ip_vs_del_service(struct ip_vs_service *svc)
+{
+ if (svc == NULL)
+ return -EEXIST;
+
+ /*
+ * Unhash it from the service table
+ */
+ write_lock_bh(&__ip_vs_svc_lock);
+
+ ip_vs_svc_unhash(svc);
+
+ /*
+ * Wait until all the svc users go away.
+ */
+ while (atomic_read(&svc->usecnt) > 1) {};
+
+ __ip_vs_del_service(svc);
+
+ write_unlock_bh(&__ip_vs_svc_lock);
+
+ return 0;
+}
+
+
+/*
+ * Flush all the virtual services
+ */
+static int ip_vs_flush(void)
+{
+ int idx;
+ struct ip_vs_service *svc;
+ struct list_head *l;
+
+ /*
+ * Flush the service table hashed by <protocol,addr,port>
+ */
+ for(idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++) {
+ l = &ip_vs_svc_table[idx];
+ while (l->next != l) {
+ svc = list_entry(l->next,struct ip_vs_service,s_list);
+ write_lock_bh(&__ip_vs_svc_lock);
+ ip_vs_svc_unhash(svc);
+ /*
+ * Wait until all the svc users go away.
+ */
+ while (atomic_read(&svc->usecnt) > 0) {};
+ __ip_vs_del_service(svc);
+ write_unlock_bh(&__ip_vs_svc_lock);
+ }
+ }
+
+ /*
+ * Flush the service table hashed by fwmark
+ */
+ for(idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++) {
+ l = &ip_vs_svc_fwm_table[idx];
+ while (l->next != l) {
+ svc = list_entry(l->next,struct ip_vs_service,f_list);
+ write_lock_bh(&__ip_vs_svc_lock);
+ ip_vs_svc_unhash(svc);
+ /*
+ * Wait until all the svc users go away.
+ */
+ while (atomic_read(&svc->usecnt) > 0) {};
+ __ip_vs_del_service(svc);
+ write_unlock_bh(&__ip_vs_svc_lock);
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * Zero counters in a service or all services
+ */
+static int ip_vs_zero_service(struct ip_vs_service *svc)
+{
+ struct list_head *l;
+ struct ip_vs_dest *dest;
+
+ write_lock_bh(&__ip_vs_svc_lock);
+ list_for_each (l, &svc->destinations) {
+ dest = list_entry(l, struct ip_vs_dest, n_list);
+ __ip_vs_zero_stats(&dest->stats);
+ }
+ __ip_vs_zero_stats(&svc->stats);
+ write_unlock_bh(&__ip_vs_svc_lock);
+ return 0;
+}
+
+static int ip_vs_zero_all(void)
+{
+ int idx;
+ struct list_head *l;
+ struct ip_vs_service *svc;
+
+ for(idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++) {
+ list_for_each (l, &ip_vs_svc_table[idx]) {
+ svc = list_entry(l, struct ip_vs_service, s_list);
+ ip_vs_zero_service(svc);
+ }
+ }
+
+ for(idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++) {
+ list_for_each (l, &ip_vs_svc_fwm_table[idx]) {
+ svc = list_entry(l, struct ip_vs_service, f_list);
+ ip_vs_zero_service(svc);
+ }
+ }
+
+ __ip_vs_zero_stats(&ip_vs_stats);
+ return 0;
+}
+
+
+static int ip_vs_sysctl_defense_mode(ctl_table *ctl, int write,
+ struct file * filp, void *buffer, size_t *lenp)
+{
+ int *valp = ctl->data;
+ int val = *valp;
+ int ret;
+
+ ret = proc_dointvec(ctl, write, filp, buffer, lenp);
+ if (write && (*valp != val)) {
+ if ((*valp < 0) || (*valp > 3)) {
+ /* Restore the correct value */
+ *valp = val;
+ } else {
+ local_bh_disable();
+ update_defense_level();
+ local_bh_enable();
+ }
+ }
+ return ret;
+}
+
+
+/*
+ * IPVS sysctl table
+ */
+struct ip_vs_sysctl_table {
+ struct ctl_table_header *sysctl_header;
+ ctl_table vs_vars[NET_IPV4_VS_LAST];
+ ctl_table vs_dir[2];
+ ctl_table ipv4_dir[2];
+ ctl_table root_dir[2];
+};
+
+
+static struct ip_vs_sysctl_table ipv4_vs_table = {
+ NULL,
+ {{NET_IPV4_VS_AMEMTHRESH, "amemthresh",
+ &sysctl_ip_vs_amemthresh, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+#ifdef CONFIG_IP_VS_DEBUG
+ {NET_IPV4_VS_DEBUG_LEVEL, "debug_level",
+ &sysctl_ip_vs_debug_level, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+#endif
+ {NET_IPV4_VS_AMDROPRATE, "am_droprate",
+ &sysctl_ip_vs_am_droprate, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_VS_DROP_ENTRY, "drop_entry",
+ &sysctl_ip_vs_drop_entry, sizeof(int), 0644, NULL,
+ &ip_vs_sysctl_defense_mode},
+ {NET_IPV4_VS_DROP_PACKET, "drop_packet",
+ &sysctl_ip_vs_drop_packet, sizeof(int), 0644, NULL,
+ &ip_vs_sysctl_defense_mode},
+ {NET_IPV4_VS_SECURE_TCP, "secure_tcp",
+ &sysctl_ip_vs_secure_tcp, sizeof(int), 0644, NULL,
+ &ip_vs_sysctl_defense_mode},
+ {NET_IPV4_VS_TO_ES, "timeout_established",
+ &vs_timeout_table_dos.timeout[IP_VS_S_ESTABLISHED],
+ sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {NET_IPV4_VS_TO_SS, "timeout_synsent",
+ &vs_timeout_table_dos.timeout[IP_VS_S_SYN_SENT],
+ sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {NET_IPV4_VS_TO_SR, "timeout_synrecv",
+ &vs_timeout_table_dos.timeout[IP_VS_S_SYN_RECV],
+ sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {NET_IPV4_VS_TO_FW, "timeout_finwait",
+ &vs_timeout_table_dos.timeout[IP_VS_S_FIN_WAIT],
+ sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {NET_IPV4_VS_TO_TW, "timeout_timewait",
+ &vs_timeout_table_dos.timeout[IP_VS_S_TIME_WAIT],
+ sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {NET_IPV4_VS_TO_CL, "timeout_close",
+ &vs_timeout_table_dos.timeout[IP_VS_S_CLOSE],
+ sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {NET_IPV4_VS_TO_CW, "timeout_closewait",
+ &vs_timeout_table_dos.timeout[IP_VS_S_CLOSE_WAIT],
+ sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {NET_IPV4_VS_TO_LA, "timeout_lastack",
+ &vs_timeout_table_dos.timeout[IP_VS_S_LAST_ACK],
+ sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {NET_IPV4_VS_TO_LI, "timeout_listen",
+ &vs_timeout_table_dos.timeout[IP_VS_S_LISTEN],
+ sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {NET_IPV4_VS_TO_SA, "timeout_synack",
+ &vs_timeout_table_dos.timeout[IP_VS_S_SYNACK],
+ sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {NET_IPV4_VS_TO_UDP, "timeout_udp",
+ &vs_timeout_table_dos.timeout[IP_VS_S_UDP],
+ sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {NET_IPV4_VS_TO_ICMP, "timeout_icmp",
+ &vs_timeout_table_dos.timeout[IP_VS_S_ICMP],
+ sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {NET_IPV4_VS_CACHE_BYPASS, "cache_bypass",
+ &sysctl_ip_vs_cache_bypass, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_VS_EXPIRE_NODEST_CONN, "expire_nodest_conn",
+ &sysctl_ip_vs_expire_nodest_conn, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_VS_SYNC_THRESHOLD, "sync_threshold",
+ &sysctl_ip_vs_sync_threshold, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_VS_NAT_ICMP_SEND, "nat_icmp_send",
+ &sysctl_ip_vs_nat_icmp_send, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_VS_EXPIRE_QUIESCENT_TEMPLATE, "expire_quiescent_template",
+ &sysctl_ip_vs_expire_quiescent_template, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {0}},
+ {{NET_IPV4_VS, "vs", NULL, 0, 0555, ipv4_vs_table.vs_vars},
+ {0}},
+ {{NET_IPV4, "ipv4", NULL, 0, 0555, ipv4_vs_table.vs_dir},
+ {0}},
+ {{CTL_NET, "net", NULL, 0, 0555, ipv4_vs_table.ipv4_dir},
+ {0}}
+};
+
+
+/*
+ * Write the contents of the VS rule table to a PROCfs file.
+ * (It is kept just for backward compatibility)
+ */
+static inline char *ip_vs_fwd_name(unsigned flags)
+{
+ char *fwd;
+
+ switch (flags & IP_VS_CONN_F_FWD_MASK) {
+ case IP_VS_CONN_F_LOCALNODE:
+ fwd = "Local";
+ break;
+ case IP_VS_CONN_F_TUNNEL:
+ fwd = "Tunnel";
+ break;
+ case IP_VS_CONN_F_DROUTE:
+ fwd = "Route";
+ break;
+ default:
+ fwd = "Masq";
+ }
+ return fwd;
+}
+
+static int ip_vs_get_info(char *buf, char **start, off_t offset, int length)
+{
+ int len=0;
+ off_t pos=0;
+ char temp[64], temp2[32];
+ int idx;
+ struct ip_vs_service *svc;
+ struct ip_vs_dest *dest;
+ struct list_head *l, *e, *p, *q;
+
+ /*
+ * Note: since the length of the buffer is usually the multiple
+ * of 512, it is good to use fixed record of the divisor of 512,
+ * so that records won't be truncated at buffer boundary.
+ */
+ pos = 192;
+ if (pos > offset) {
+ sprintf(temp,
+ "IP Virtual Server version %d.%d.%d (size=%d)",
+ NVERSION(IP_VS_VERSION_CODE), IP_VS_CONN_TAB_SIZE);
+ len += sprintf(buf+len, "%-63s\n", temp);
+ len += sprintf(buf+len, "%-63s\n",
+ "Prot LocalAddress:Port Scheduler Flags");
+ len += sprintf(buf+len, "%-63s\n",
+ " -> RemoteAddress:Port Forward Weight ActiveConn InActConn");
+ }
+
+ read_lock_bh(&__ip_vs_svc_lock);
+
+ /* print the service table hashed by <protocol,addr,port> */
+ for (idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++) {
+ l = &ip_vs_svc_table[idx];
+ for (e=l->next; e!=l; e=e->next) {
+ svc = list_entry(e, struct ip_vs_service, s_list);
+ pos += 64;
+ if (pos > offset) {
+ if (svc->flags & IP_VS_SVC_F_PERSISTENT)
+ sprintf(temp2, "persistent %d %08X",
+ svc->timeout,
+ ntohl(svc->netmask));
+ else
+ temp2[0] = '\0';
+
+ sprintf(temp, "%s %08X:%04X %s %s",
+ ip_vs_proto_name(svc->protocol),
+ ntohl(svc->addr),
+ ntohs(svc->port),
+ svc->scheduler->name, temp2);
+ len += sprintf(buf+len, "%-63s\n", temp);
+ if (len >= length)
+ goto done;
+ }
+
+ p = &svc->destinations;
+ for (q=p->next; q!=p; q=q->next) {
+ dest = list_entry(q, struct ip_vs_dest, n_list);
+ pos += 64;
+ if (pos <= offset)
+ continue;
+ sprintf(temp,
+ " -> %08X:%04X %-7s %-6d %-10d %-10d",
+ ntohl(dest->addr),
+ ntohs(dest->port),
+ ip_vs_fwd_name(atomic_read(&dest->conn_flags)),
+ atomic_read(&dest->weight),
+ atomic_read(&dest->activeconns),
+ atomic_read(&dest->inactconns));
+ len += sprintf(buf+len, "%-63s\n", temp);
+ if (len >= length)
+ goto done;
+ }
+ }
+ }
+
+ /* print the service table hashed by fwmark */
+ for (idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++) {
+ l = &ip_vs_svc_fwm_table[idx];
+ for (e=l->next; e!=l; e=e->next) {
+ svc = list_entry(e, struct ip_vs_service, f_list);
+ pos += 64;
+ if (pos > offset) {
+ if (svc->flags & IP_VS_SVC_F_PERSISTENT)
+ sprintf(temp2, "persistent %d %08X",
+ svc->timeout,
+ ntohl(svc->netmask));
+ else
+ temp2[0] = '\0';
+
+ sprintf(temp, "FWM %08X %s %s",
+ svc->fwmark,
+ svc->scheduler->name, temp2);
+ len += sprintf(buf+len, "%-63s\n", temp);
+ if (len >= length)
+ goto done;
+ }
+
+ p = &svc->destinations;
+ for (q=p->next; q!=p; q=q->next) {
+ dest = list_entry(q, struct ip_vs_dest, n_list);
+ pos += 64;
+ if (pos <= offset)
+ continue;
+ sprintf(temp,
+ " -> %08X:%04X %-7s %-6d %-10d %-10d",
+ ntohl(dest->addr),
+ ntohs(dest->port),
+ ip_vs_fwd_name(atomic_read(&dest->conn_flags)),
+ atomic_read(&dest->weight),
+ atomic_read(&dest->activeconns),
+ atomic_read(&dest->inactconns));
+ len += sprintf(buf+len, "%-63s\n", temp);
+ if (len >= length)
+ goto done;
+ }
+ }
+ }
+
+ done:
+ read_unlock_bh(&__ip_vs_svc_lock);
+
+ *start = buf+len-(pos-offset); /* Start of wanted data */
+ len = pos-offset;
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+
+struct ip_vs_stats ip_vs_stats;
+
+static int
+ip_vs_stats_get_info(char *buf, char **start, off_t offset, int length)
+{
+ int len=0;
+ off_t pos=0;
+ char temp[64];
+
+ pos += 320;
+ if (pos > offset) {
+ len += sprintf(buf+len, "%-63s\n%-63s\n",
+/* 01234567 01234567 01234567 0123456701234567 0123456701234567 */
+ " Total Incoming Outgoing Incoming Outgoing",
+ " Conns Packets Packets Bytes Bytes");
+
+ spin_lock_bh(&ip_vs_stats.lock);
+ sprintf(temp, "%8X %8X %8X %8X%08X %8X%08X",
+ ip_vs_stats.conns,
+ ip_vs_stats.inpkts,
+ ip_vs_stats.outpkts,
+ (__u32)(ip_vs_stats.inbytes>>32),
+ (__u32)ip_vs_stats.inbytes,
+ (__u32)(ip_vs_stats.outbytes>>32),
+ (__u32)ip_vs_stats.outbytes);
+ len += sprintf(buf+len, "%-62s\n\n", temp);
+
+ len += sprintf(buf+len, "%-63s\n",
+/* 01234567 01234567 01234567 0123456701234567 0123456701234567 */
+ " Conns/s Pkts/s Pkts/s Bytes/s Bytes/s");
+ sprintf(temp, "%8X %8X %8X %16X %16X",
+ ip_vs_stats.cps,
+ ip_vs_stats.inpps,
+ ip_vs_stats.outpps,
+ ip_vs_stats.inbps,
+ ip_vs_stats.outbps);
+ len += sprintf(buf+len, "%-63s\n", temp);
+
+ spin_unlock_bh(&ip_vs_stats.lock);
+ }
+
+ *start = buf+len-(pos-offset); /* Start of wanted data */
+ len = pos-offset;
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+
+/*
+ * Set timeout values for tcp tcpfin udp in the vs_timeout_table.
+ */
+static int ip_vs_set_timeouts(struct ip_vs_rule_user *u)
+{
+ IP_VS_DBG(2, "Setting timeout tcp:%d tcpfin:%d udp:%d\n",
+ u->tcp_timeout,
+ u->tcp_fin_timeout,
+ u->udp_timeout);
+
+ if (u->tcp_timeout) {
+ vs_timeout_table.timeout[IP_VS_S_ESTABLISHED]
+ = u->tcp_timeout * HZ;
+ }
+
+ if (u->tcp_fin_timeout) {
+ vs_timeout_table.timeout[IP_VS_S_FIN_WAIT]
+ = u->tcp_fin_timeout * HZ;
+ }
+
+ if (u->udp_timeout) {
+ vs_timeout_table.timeout[IP_VS_S_UDP]
+ = u->udp_timeout * HZ;
+ }
+ return 0;
+}
+
+
+static int
+do_ip_vs_set_ctl(struct sock *sk, int cmd, void *user, unsigned int len)
+{
+ int ret;
+ struct ip_vs_rule_user *urule;
+ struct ip_vs_service *svc = NULL;
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ /*
+ * Check the size of mm, no overflow...
+ * len > 128000 is a sanity check.
+ */
+ if (len < sizeof(struct ip_vs_rule_user)) {
+ IP_VS_ERR("set_ctl: len %u < %Zu\n",
+ len, sizeof(struct ip_vs_rule_user));
+ return -EINVAL;
+ } else if (len > 128000) {
+ IP_VS_ERR("set_ctl: len %u > 128000\n", len);
+ return -EINVAL;
+ } else if ((urule = kmalloc(len, GFP_KERNEL)) == NULL) {
+ IP_VS_ERR("set_ctl: no mem for len %u\n", len);
+ return -ENOMEM;
+ } else if (copy_from_user(urule, user, len) != 0) {
+ ret = -EFAULT;
+ goto out_free;
+ }
+
+ MOD_INC_USE_COUNT;
+ if (down_interruptible(&__ip_vs_mutex)) {
+ ret = -ERESTARTSYS;
+ goto out_dec;
+ }
+
+ if (cmd == IP_VS_SO_SET_FLUSH) {
+ /* Flush the virtual service */
+ ret = ip_vs_flush();
+ goto out_unlock;
+ } else if (cmd == IP_VS_SO_SET_TIMEOUTS) {
+ /* Set timeout values for (tcp tcpfin udp) */
+ ret = ip_vs_set_timeouts(urule);
+ goto out_unlock;
+ } else if (cmd == IP_VS_SO_SET_STARTDAEMON) {
+ ret = start_sync_thread(urule->state, urule->mcast_ifn,
+ urule->syncid);
+ goto out_unlock;
+ } else if (cmd == IP_VS_SO_SET_STOPDAEMON) {
+ ret = stop_sync_thread(urule->state);
+ goto out_unlock;
+ } else if (cmd == IP_VS_SO_SET_ZERO) {
+ /* if no service address is set, zero counters in all */
+ if (!urule->vfwmark && !urule->vaddr && !urule->vport) {
+ ret = ip_vs_zero_all();
+ goto out_unlock;
+ }
+ }
+
+ /*
+ * Check for valid protocol: TCP or UDP. Even for fwmark!=0
+ */
+ if (urule->protocol!=IPPROTO_TCP && urule->protocol!=IPPROTO_UDP) {
+ IP_VS_ERR("set_ctl: invalid protocol %d %d.%d.%d.%d:%d %s\n",
+ urule->protocol, NIPQUAD(urule->vaddr),
+ ntohs(urule->vport), urule->sched_name);
+ ret = -EFAULT;
+ goto out_unlock;
+ }
+
+ /*
+ * Lookup the exact service by <protocol, vaddr, vport> or fwmark
+ */
+ if (urule->vfwmark == 0)
+ svc = __ip_vs_service_get(urule->protocol,
+ urule->vaddr, urule->vport);
+ else
+ svc = __ip_vs_svc_fwm_get(urule->vfwmark);
+
+ if (cmd != IP_VS_SO_SET_ADD
+ && (svc == NULL || svc->protocol != urule->protocol)) {
+ ret = -ESRCH;
+ goto out_unlock;
+ }
+
+ switch (cmd) {
+ case IP_VS_SO_SET_ADD:
+ if (svc != NULL)
+ ret = -EEXIST;
+ else
+ ret = ip_vs_add_service(urule, &svc);
+ break;
+ case IP_VS_SO_SET_EDIT:
+ ret = ip_vs_edit_service(svc, urule);
+ break;
+ case IP_VS_SO_SET_DEL:
+ ret = ip_vs_del_service(svc);
+ if (!ret)
+ goto out_unlock;
+ break;
+ case IP_VS_SO_SET_ADDDEST:
+ ret = ip_vs_add_dest(svc, urule);
+ break;
+ case IP_VS_SO_SET_EDITDEST:
+ ret = ip_vs_edit_dest(svc, urule);
+ break;
+ case IP_VS_SO_SET_DELDEST:
+ ret = ip_vs_del_dest(svc, urule);
+ break;
+ case IP_VS_SO_SET_ZERO:
+ ret = ip_vs_zero_service(svc);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ if (svc)
+ ip_vs_service_put(svc);
+
+ out_unlock:
+ up(&__ip_vs_mutex);
+ out_dec:
+ MOD_DEC_USE_COUNT;
+ out_free:
+ kfree(urule);
+ return ret;
+}
+
+
+static inline void
+__ip_vs_copy_stats(struct ip_vs_stats_user *dst, struct ip_vs_stats *src)
+{
+ spin_lock_bh(&src->lock);
+ memcpy(dst, src, (char*)&src->lock - (char*)src);
+ spin_unlock_bh(&src->lock);
+}
+
+static inline int
+__ip_vs_get_service_entries(const struct ip_vs_get_services *get,
+ struct ip_vs_get_services *uptr)
+{
+ int idx, count=0;
+ struct ip_vs_service *svc;
+ struct list_head *l;
+ struct ip_vs_service_user entry;
+ int ret = 0;
+
+ for (idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++) {
+ list_for_each (l, &ip_vs_svc_table[idx]) {
+ if (count >= get->num_services)
+ goto out;
+ svc = list_entry(l, struct ip_vs_service, s_list);
+ entry.protocol = svc->protocol;
+ entry.addr = svc->addr;
+ entry.port = svc->port;
+ entry.fwmark = svc->fwmark;
+ strncpy(entry.sched_name, svc->scheduler->name, sizeof(entry.sched_name));
+ entry.sched_name[sizeof(entry.sched_name) - 1] = 0;
+ entry.flags = svc->flags;
+ entry.timeout = svc->timeout / HZ;
+ entry.netmask = svc->netmask;
+ entry.num_dests = svc->num_dests;
+ __ip_vs_copy_stats(&entry.stats, &svc->stats);
+ if (copy_to_user(&uptr->entrytable[count],
+ &entry, sizeof(entry))) {
+ ret = -EFAULT;
+ goto out;
+ }
+ count++;
+ }
+ }
+
+ for (idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++) {
+ list_for_each (l, &ip_vs_svc_fwm_table[idx]) {
+ if (count >= get->num_services)
+ goto out;
+ svc = list_entry(l, struct ip_vs_service, f_list);
+ entry.protocol = svc->protocol;
+ entry.addr = svc->addr;
+ entry.port = svc->port;
+ entry.fwmark = svc->fwmark;
+ strncpy(entry.sched_name, svc->scheduler->name, sizeof(entry.sched_name));
+ entry.sched_name[sizeof(entry.sched_name) - 1] = 0;
+ entry.flags = svc->flags;
+ entry.timeout = svc->timeout / HZ;
+ entry.netmask = svc->netmask;
+ entry.num_dests = svc->num_dests;
+ __ip_vs_copy_stats(&entry.stats, &svc->stats);
+ if (copy_to_user(&uptr->entrytable[count],
+ &entry, sizeof(entry))) {
+ ret = -EFAULT;
+ goto out;
+ }
+ count++;
+ }
+ }
+ out:
+ return ret;
+}
+
+static inline int
+__ip_vs_get_dest_entries(const struct ip_vs_get_dests *get,
+ struct ip_vs_get_dests *uptr)
+{
+ struct ip_vs_service *svc;
+ int ret = 0;
+
+ if (get->fwmark)
+ svc = __ip_vs_svc_fwm_get(get->fwmark);
+ else
+ svc = __ip_vs_service_get(get->protocol,
+ get->addr, get->port);
+ if (svc) {
+ int count = 0;
+ struct ip_vs_dest *dest;
+ struct list_head *l, *e;
+ struct ip_vs_dest_user entry;
+
+ l = &svc->destinations;
+ for (e=l->next; e!=l; e=e->next) {
+ if (count >= get->num_dests)
+ break;
+ dest = list_entry(e, struct ip_vs_dest, n_list);
+ entry.addr = dest->addr;
+ entry.port = dest->port;
+ entry.flags = atomic_read(&dest->conn_flags);
+ entry.weight = atomic_read(&dest->weight);
+ entry.activeconns = atomic_read(&dest->activeconns);
+ entry.inactconns = atomic_read(&dest->inactconns);
+ __ip_vs_copy_stats(&entry.stats, &dest->stats);
+ if (copy_to_user(&uptr->entrytable[count],
+ &entry, sizeof(entry))) {
+ ret = -EFAULT;
+ break;
+ }
+ count++;
+ }
+ ip_vs_service_put(svc);
+ } else
+ ret = -ESRCH;
+ return ret;
+}
+
+static inline void
+__ip_vs_get_timeouts(struct ip_vs_timeout_user *u)
+{
+ u->tcp_timeout = vs_timeout_table.timeout[IP_VS_S_ESTABLISHED] / HZ;
+ u->tcp_fin_timeout = vs_timeout_table.timeout[IP_VS_S_FIN_WAIT] / HZ;
+ u->udp_timeout = vs_timeout_table.timeout[IP_VS_S_UDP] / HZ;
+}
+
+static int
+do_ip_vs_get_ctl(struct sock *sk, int cmd, void *user, int *len)
+{
+ int ret = 0;
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (down_interruptible(&__ip_vs_mutex))
+ return -ERESTARTSYS;
+
+ switch (cmd) {
+ case IP_VS_SO_GET_VERSION:
+ {
+ char buf[64];
+
+ sprintf(buf, "IP Virtual Server version %d.%d.%d (size=%d)",
+ NVERSION(IP_VS_VERSION_CODE), IP_VS_CONN_TAB_SIZE);
+ if (*len < strlen(buf)+1) {
+ ret = -EINVAL;
+ goto out;
+ }
+ if (copy_to_user(user, buf, strlen(buf)+1) != 0) {
+ ret = -EFAULT;
+ goto out;
+ }
+ *len = strlen(buf)+1;
+ }
+ break;
+
+ case IP_VS_SO_GET_INFO:
+ {
+ struct ip_vs_getinfo info;
+ info.version = IP_VS_VERSION_CODE;
+ info.size = IP_VS_CONN_TAB_SIZE;
+ info.num_services = ip_vs_num_services;
+ if (copy_to_user(user, &info, sizeof(info)) != 0)
+ ret = -EFAULT;
+ }
+ break;
+
+ case IP_VS_SO_GET_SERVICES:
+ {
+ struct ip_vs_get_services get;
+
+ if (*len < sizeof(get)) {
+ IP_VS_ERR("length: %u < %Zu\n", *len, sizeof(get));
+ ret = -EINVAL;
+ goto out;
+ }
+ if (copy_from_user(&get, user, sizeof(get))) {
+ ret = -EFAULT;
+ goto out;
+ }
+ if (*len != (sizeof(get)+sizeof(struct ip_vs_service_user)*get.num_services)) {
+ IP_VS_ERR("length: %u != %Zu\n", *len,
+ sizeof(get)+sizeof(struct ip_vs_service_user)*get.num_services);
+ ret = -EINVAL;
+ goto out;
+ }
+ ret = __ip_vs_get_service_entries(&get, user);
+ }
+ break;
+
+ case IP_VS_SO_GET_SERVICE:
+ {
+ struct ip_vs_service_user get;
+ struct ip_vs_service *svc;
+
+ if (*len != sizeof(get)) {
+ IP_VS_ERR("length: %u != %Zu\n", *len, sizeof(get));
+ ret = -EINVAL;
+ goto out;
+ }
+ if (copy_from_user(&get, user, sizeof(get))) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ if (get.fwmark)
+ svc = __ip_vs_svc_fwm_get(get.fwmark);
+ else
+ svc = __ip_vs_service_get(get.protocol,
+ get.addr, get.port);
+ if (svc) {
+ strncpy(get.sched_name, svc->scheduler->name, sizeof(get.sched_name));
+ get.sched_name[sizeof(get.sched_name) - 1] = 0;
+ get.flags = svc->flags;
+ get.timeout = svc->timeout / HZ;
+ get.netmask = svc->netmask;
+ get.num_dests = svc->num_dests;
+ __ip_vs_copy_stats(&get.stats, &svc->stats);
+ if (copy_to_user(user, &get, *len) != 0)
+ ret = -EFAULT;
+ ip_vs_service_put(svc);
+ } else
+ ret = -ESRCH;
+ }
+ break;
+
+ case IP_VS_SO_GET_DESTS:
+ {
+ struct ip_vs_get_dests get;
+
+ if (*len < sizeof(get)) {
+ IP_VS_ERR("length: %u < %Zu\n", *len, sizeof(get));
+ ret = -EINVAL;
+ goto out;
+ }
+ if (copy_from_user(&get, user, sizeof(get))) {
+ ret = -EFAULT;
+ goto out;
+ }
+ if (*len != (sizeof(get) +
+ sizeof(struct ip_vs_dest_user)*get.num_dests)) {
+ IP_VS_ERR("length: %u != %Zu\n", *len,
+ sizeof(get)+sizeof(struct ip_vs_dest_user)*get.num_dests);
+ ret = -EINVAL;
+ goto out;
+ }
+ ret = __ip_vs_get_dest_entries(&get, user);
+ }
+ break;
+
+ case IP_VS_SO_GET_TIMEOUTS:
+ {
+ struct ip_vs_timeout_user u;
+
+ if (*len < sizeof(u)) {
+ IP_VS_ERR("length: %u < %Zu\n", *len, sizeof(u));
+ ret = -EINVAL;
+ goto out;
+ }
+ __ip_vs_get_timeouts(&u);
+ if (copy_to_user(user, &u, sizeof(u)) != 0)
+ ret = -EFAULT;
+ }
+ break;
+
+ case IP_VS_SO_GET_DAEMON:
+ {
+ struct ip_vs_daemon_user u;
+
+ if (*len < sizeof(u)) {
+ IP_VS_ERR("length: %u < %Zu\n", *len, sizeof(u));
+ ret = -EINVAL;
+ goto out;
+ }
+ u.state = ip_vs_sync_state;
+ if (ip_vs_sync_state & IP_VS_STATE_MASTER) {
+ strncpy(u.mcast_master_ifn, ip_vs_mcast_master_ifn, sizeof(u.mcast_master_ifn));
+ u.mcast_master_ifn[sizeof(u.mcast_master_ifn) - 1] = 0;
+ }
+ if (ip_vs_sync_state & IP_VS_STATE_BACKUP) {
+ strncpy(u.mcast_backup_ifn, ip_vs_mcast_backup_ifn, sizeof(u.mcast_backup_ifn));
+ u.mcast_backup_ifn[sizeof(u.mcast_backup_ifn) - 1] = 0;
+ }
+ if (copy_to_user(user, &u, sizeof(u)) != 0)
+ ret = -EFAULT;
+ }
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ out:
+ up(&__ip_vs_mutex);
+ return ret;
+}
+
+
+static struct nf_sockopt_ops ip_vs_sockopts = {
+ { NULL, NULL }, PF_INET,
+ IP_VS_BASE_CTL, IP_VS_SO_SET_MAX+1, do_ip_vs_set_ctl,
+ IP_VS_BASE_CTL, IP_VS_SO_GET_MAX+1, do_ip_vs_get_ctl
+};
+
+
+int ip_vs_control_init(void)
+{
+ int ret;
+ int idx;
+
+ EnterFunction(2);
+
+ ret = nf_register_sockopt(&ip_vs_sockopts);
+ if (ret) {
+ IP_VS_ERR("cannot register sockopt.\n");
+ return ret;
+ }
+
+ proc_net_create("ip_vs", 0, ip_vs_get_info);
+ proc_net_create("ip_vs_stats", 0, ip_vs_stats_get_info);
+
+ ipv4_vs_table.sysctl_header =
+ register_sysctl_table(ipv4_vs_table.root_dir, 0);
+ /*
+ * Initilize ip_vs_svc_table, ip_vs_svc_fwm_table, ip_vs_rtable,
+ * ip_vs_schedulers.
+ */
+ for(idx = 0; idx < IP_VS_SVC_TAB_SIZE; idx++) {
+ INIT_LIST_HEAD(&ip_vs_svc_table[idx]);
+ INIT_LIST_HEAD(&ip_vs_svc_fwm_table[idx]);
+ }
+ for(idx = 0; idx < IP_VS_RTAB_SIZE; idx++) {
+ INIT_LIST_HEAD(&ip_vs_rtable[idx]);
+ }
+
+ memset(&ip_vs_stats, 0, sizeof(ip_vs_stats));
+ ip_vs_stats.lock = SPIN_LOCK_UNLOCKED;
+ ip_vs_new_estimator(&ip_vs_stats);
+
+ /* Hook the defense timer */
+ init_timer(&defense_timer);
+ defense_timer.function = defense_timer_handler;
+ defense_timer.expires = jiffies + DEFENSE_TIMER_PERIOD;
+ add_timer(&defense_timer);
+
+ LeaveFunction(2);
+ return 0;
+}
+
+void ip_vs_control_cleanup(void)
+{
+ EnterFunction(2);
+ ip_vs_trash_cleanup();
+ del_timer_sync(&defense_timer);
+ ip_vs_kill_estimator(&ip_vs_stats);
+ unregister_sysctl_table(ipv4_vs_table.sysctl_header);
+ proc_net_remove("ip_vs_stats");
+ proc_net_remove("ip_vs");
+ nf_unregister_sockopt(&ip_vs_sockopts);
+ LeaveFunction(2);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_dh.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_dh.c
new file mode 100644
index 0000000..61a7c75
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_dh.c
@@ -0,0 +1,261 @@
+/*
+ * IPVS: Destination Hashing scheduling module
+ *
+ * Version: $Id: ip_vs_dh.c,v 1.4 2001/10/19 15:05:17 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@gnuchina.org>
+ *
+ * Inspired by the consistent hashing scheduler patch from
+ * Thomas Proell <proellt@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes:
+ *
+ */
+
+/*
+ * The dh algorithm is to select server by the hash key of destination IP
+ * address. The pseudo code is as follows:
+ *
+ * n <- servernode[dest_ip];
+ * if (n is dead) OR
+ * (n is overloaded, such as n.conns>2*n.weight) then
+ * return NULL;
+ *
+ * return n;
+ *
+ * Notes that servernode is a 256-bucket hash table that maps the hash
+ * index derived from packet destination IP address to the current server
+ * array. If the dh scheduler is used in cache cluster, it is good to
+ * combine it with cache_bypass feature. When the statically assigned
+ * server is dead or overloaded, the load balancer can bypass the cache
+ * server and send requests to the original server directly.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include <net/ip_vs.h>
+
+
+/*
+ * IPVS DH bucket
+ */
+struct ip_vs_dh_bucket {
+ struct ip_vs_dest *dest; /* real server (cache) */
+};
+
+/*
+ * for IPVS DH entry hash table
+ */
+#ifndef CONFIG_IP_VS_DH_TAB_BITS
+#define CONFIG_IP_VS_DH_TAB_BITS 8
+#endif
+#define IP_VS_DH_TAB_BITS CONFIG_IP_VS_DH_TAB_BITS
+#define IP_VS_DH_TAB_SIZE (1 << IP_VS_DH_TAB_BITS)
+#define IP_VS_DH_TAB_MASK (IP_VS_DH_TAB_SIZE - 1)
+
+
+/*
+ * Returns hash value for IPVS DH entry
+ */
+static inline unsigned ip_vs_dh_hashkey(__u32 addr)
+{
+ return (ntohl(addr)*2654435761UL) & IP_VS_DH_TAB_MASK;
+}
+
+
+/*
+ * Get ip_vs_dest associated with supplied parameters.
+ */
+static inline struct ip_vs_dest *
+ip_vs_dh_get(struct ip_vs_dh_bucket *tbl, __u32 addr)
+{
+ return (tbl[ip_vs_dh_hashkey(addr)]).dest;
+}
+
+
+/*
+ * Assign all the hash buckets of the specified table with the service.
+ */
+static int
+ip_vs_dh_assign(struct ip_vs_dh_bucket *tbl, struct ip_vs_service *svc)
+{
+ int i;
+ struct ip_vs_dh_bucket *b;
+ struct list_head *p;
+ struct ip_vs_dest *dest;
+
+ b = tbl;
+ p = &svc->destinations;
+ for (i=0; i<IP_VS_DH_TAB_SIZE; i++) {
+ if (list_empty(p)) {
+ b->dest = NULL;
+ } else {
+ if (p == &svc->destinations)
+ p = p->next;
+
+ dest = list_entry(p, struct ip_vs_dest, n_list);
+ atomic_inc(&dest->refcnt);
+ b->dest = dest;
+
+ p = p->next;
+ }
+ b++;
+ }
+ return 0;
+}
+
+
+/*
+ * Flush all the hash buckets of the specified table.
+ */
+static void ip_vs_dh_flush(struct ip_vs_dh_bucket *tbl)
+{
+ int i;
+ struct ip_vs_dh_bucket *b;
+
+ b = tbl;
+ for (i=0; i<IP_VS_DH_TAB_SIZE; i++) {
+ if (b->dest) {
+ atomic_dec(&b->dest->refcnt);
+ b->dest = NULL;
+ }
+ b++;
+ }
+}
+
+
+static int ip_vs_dh_init_svc(struct ip_vs_service *svc)
+{
+ struct ip_vs_dh_bucket *tbl;
+
+ /* allocate the DH table for this service */
+ tbl = kmalloc(sizeof(struct ip_vs_dh_bucket)*IP_VS_DH_TAB_SIZE,
+ GFP_ATOMIC);
+ if (tbl == NULL) {
+ IP_VS_ERR("ip_vs_dh_init_svc(): no memory\n");
+ return -ENOMEM;
+ }
+ svc->sched_data = tbl;
+ IP_VS_DBG(6, "DH hash table (memory=%dbytes) allocated for "
+ "current service\n",
+ sizeof(struct ip_vs_dh_bucket)*IP_VS_DH_TAB_SIZE);
+
+ /* assign the hash buckets with the updated service */
+ ip_vs_dh_assign(tbl, svc);
+
+ return 0;
+}
+
+
+static int ip_vs_dh_done_svc(struct ip_vs_service *svc)
+{
+ struct ip_vs_dh_bucket *tbl = svc->sched_data;
+
+ /* got to clean up hash buckets here */
+ ip_vs_dh_flush(tbl);
+
+ /* release the table itself */
+ kfree(svc->sched_data);
+ IP_VS_DBG(6, "DH hash table (memory=%dbytes) released\n",
+ sizeof(struct ip_vs_dh_bucket)*IP_VS_DH_TAB_SIZE);
+
+ return 0;
+}
+
+
+static int ip_vs_dh_update_svc(struct ip_vs_service *svc)
+{
+ struct ip_vs_dh_bucket *tbl = svc->sched_data;
+
+ /* got to clean up hash buckets here */
+ ip_vs_dh_flush(tbl);
+
+ /* assign the hash buckets with the updated service */
+ ip_vs_dh_assign(tbl, svc);
+
+ return 0;
+}
+
+
+/*
+ * If the number of active connections is twice larger than its weight,
+ * consider that the server is overloaded here.
+ */
+static inline int is_overloaded(struct ip_vs_dest *dest)
+{
+ if (atomic_read(&dest->activeconns) > atomic_read(&dest->weight)*2) {
+ return 1;
+ }
+ return 0;
+}
+
+
+/*
+ * Destination hashing scheduling
+ */
+static struct ip_vs_dest *
+ip_vs_dh_schedule(struct ip_vs_service *svc, struct iphdr *iph)
+{
+ struct ip_vs_dest *dest;
+ struct ip_vs_dh_bucket *tbl;
+
+ IP_VS_DBG(6, "ip_vs_dh_schedule(): Scheduling...\n");
+
+ tbl = (struct ip_vs_dh_bucket *)svc->sched_data;
+ dest = ip_vs_dh_get(tbl, iph->daddr);
+ if (!dest
+ || !(dest->flags & IP_VS_DEST_F_AVAILABLE)
+ || atomic_read(&dest->weight) <= 0
+ || is_overloaded(dest)) {
+ return NULL;
+ }
+
+ IP_VS_DBG(6, "DH: destination IP address %u.%u.%u.%u "
+ "--> server %u.%u.%u.%u:%d\n",
+ NIPQUAD(iph->daddr),
+ NIPQUAD(dest->addr),
+ ntohs(dest->port));
+
+ return dest;
+}
+
+
+/*
+ * IPVS DH Scheduler structure
+ */
+static struct ip_vs_scheduler ip_vs_dh_scheduler =
+{
+ {0}, /* n_list */
+ "dh", /* name */
+ ATOMIC_INIT(0), /* refcnt */
+ THIS_MODULE, /* this module */
+ ip_vs_dh_init_svc, /* service initializer */
+ ip_vs_dh_done_svc, /* service done */
+ ip_vs_dh_update_svc, /* service updater */
+ ip_vs_dh_schedule, /* select a server from the destination list */
+};
+
+
+static int __init ip_vs_dh_init(void)
+{
+ INIT_LIST_HEAD(&ip_vs_dh_scheduler.n_list);
+ return register_ip_vs_scheduler(&ip_vs_dh_scheduler);
+}
+
+
+static void __exit ip_vs_dh_cleanup(void)
+{
+ unregister_ip_vs_scheduler(&ip_vs_dh_scheduler);
+}
+
+
+module_init(ip_vs_dh_init);
+module_exit(ip_vs_dh_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_est.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_est.c
new file mode 100644
index 0000000..aba8bc8
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_est.c
@@ -0,0 +1,200 @@
+/*
+ * ip_vs_est.c Simple rate estimator for IPVS
+ *
+ * Version: $Id: ip_vs_est.c,v 1.3.2.1 2003/07/29 14:37:13 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes:
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+#include <net/ip_vs.h>
+
+/*
+ This code is to estimate rate in a shorter interval (such as 8
+ seconds) for virtual services and real servers. For measure rate in a
+ long interval, it is easy to implement a user level daemon which
+ periodically reads those statistical counters and measure rate.
+
+ Currently, the measurement is activated by slow timer handler. Hope
+ this measurement will not introduce too much load.
+
+ We measure rate during the last 8 seconds every 2 seconds:
+
+ avgrate = avgrate*(1-W) + rate*W
+
+ where W = 2^(-2)
+
+ NOTES.
+
+ * The stored value for average bps is scaled by 2^5, so that maximal
+ rate is ~2.15Gbits/s, average pps and cps are scaled by 2^10.
+
+ * A lot code is taken from net/sched/estimator.c
+ */
+
+
+struct ip_vs_estimator
+{
+ struct ip_vs_estimator *next;
+ struct ip_vs_stats *stats;
+
+ u32 last_conns;
+ u32 last_inpkts;
+ u32 last_outpkts;
+ u64 last_inbytes;
+ u64 last_outbytes;
+
+ u32 cps;
+ u32 inpps;
+ u32 outpps;
+ u32 inbps;
+ u32 outbps;
+};
+
+
+static struct ip_vs_estimator *est_list = NULL;
+static rwlock_t est_lock = RW_LOCK_UNLOCKED;
+static struct timer_list est_timer;
+
+static void estimation_timer(unsigned long arg)
+{
+ struct ip_vs_estimator *e;
+ struct ip_vs_stats *s;
+ u32 n_conns;
+ u32 n_inpkts, n_outpkts;
+ u64 n_inbytes, n_outbytes;
+ u32 rate;
+
+ read_lock(&est_lock);
+ for (e = est_list; e; e = e->next) {
+ s = e->stats;
+
+ spin_lock(&s->lock);
+ n_conns = s->conns;
+ n_inpkts = s->inpkts;
+ n_outpkts = s->outpkts;
+ n_inbytes = s->inbytes;
+ n_outbytes = s->outbytes;
+
+ /* scaled by 2^10, but divided 2 seconds */
+ rate = (n_conns - e->last_conns)<<9;
+ e->last_conns = n_conns;
+ e->cps += ((long)rate - (long)e->cps)>>2;
+ s->cps = (e->cps+0x1FF)>>10;
+
+ rate = (n_inpkts - e->last_inpkts)<<9;
+ e->last_inpkts = n_inpkts;
+ e->inpps += ((long)rate - (long)e->inpps)>>2;
+ s->inpps = (e->inpps+0x1FF)>>10;
+
+ rate = (n_outpkts - e->last_outpkts)<<9;
+ e->last_outpkts = n_outpkts;
+ e->outpps += ((long)rate - (long)e->outpps)>>2;
+ s->outpps = (e->outpps+0x1FF)>>10;
+
+ rate = (n_inbytes - e->last_inbytes)<<4;
+ e->last_inbytes = n_inbytes;
+ e->inbps += ((long)rate - (long)e->inbps)>>2;
+ s->inbps = (e->inbps+0xF)>>5;
+
+ rate = (n_outbytes - e->last_outbytes)<<4;
+ e->last_outbytes = n_outbytes;
+ e->outbps += ((long)rate - (long)e->outbps)>>2;
+ s->outbps = (e->outbps+0xF)>>5;
+ spin_unlock(&s->lock);
+ }
+ read_unlock(&est_lock);
+ mod_timer(&est_timer, jiffies + 2*HZ);
+}
+
+int ip_vs_new_estimator(struct ip_vs_stats *stats)
+{
+ struct ip_vs_estimator *est;
+
+ est = kmalloc(sizeof(*est), GFP_KERNEL);
+ if (est == NULL)
+ return -ENOMEM;
+
+ memset(est, 0, sizeof(*est));
+ est->stats = stats;
+ est->last_conns = stats->conns;
+ est->cps = stats->cps<<10;
+
+ est->last_inpkts = stats->inpkts;
+ est->inpps = stats->inpps<<10;
+
+ est->last_outpkts = stats->outpkts;
+ est->outpps = stats->outpps<<10;
+
+ est->last_inbytes = stats->inbytes;
+ est->inbps = stats->inbps<<5;
+
+ est->last_outbytes = stats->outbytes;
+ est->outbps = stats->outbps<<5;
+
+ est->next = est_list;
+ if (est->next == NULL) {
+ init_timer(&est_timer);
+ est_timer.expires = jiffies + 2*HZ;
+ est_timer.function = estimation_timer;
+ add_timer(&est_timer);
+ }
+ write_lock_bh(&est_lock);
+ est_list = est;
+ write_unlock_bh(&est_lock);
+ return 0;
+}
+
+void ip_vs_kill_estimator(struct ip_vs_stats *stats)
+{
+ struct ip_vs_estimator *est, **pest;
+ int killed = 0;
+
+ write_lock_bh(&est_lock);
+ pest = &est_list;
+ while ((est=*pest) != NULL) {
+ if (est->stats != stats) {
+ pest = &est->next;
+ continue;
+ }
+ *pest = est->next;
+ kfree(est);
+ killed++;
+ }
+ if (killed && est_list == NULL)
+ del_timer_sync(&est_timer);
+ write_unlock_bh(&est_lock);
+}
+
+void ip_vs_zero_estimator(struct ip_vs_stats *stats)
+{
+ struct ip_vs_estimator *e;
+
+ write_lock_bh(&est_lock);
+ for (e = est_list; e; e = e->next) {
+ if (e->stats != stats)
+ continue;
+
+ /* set counters zero */
+ e->last_conns = 0;
+ e->last_inpkts = 0;
+ e->last_outpkts = 0;
+ e->last_inbytes = 0;
+ e->last_outbytes = 0;
+ e->cps = 0;
+ e->inpps = 0;
+ e->outpps = 0;
+ e->inbps = 0;
+ e->outbps = 0;
+ }
+ write_unlock_bh(&est_lock);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_ftp.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_ftp.c
new file mode 100644
index 0000000..7268f1a
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_ftp.c
@@ -0,0 +1,408 @@
+/*
+ * IP_VS ftp application module
+ *
+ * Version: $Id: ip_vs_ftp.c,v 1.12 2002/08/10 04:32:35 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
+ *
+ * Changes:
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Most code here is taken from ip_masq_ftp.c in kernel 2.2. The difference
+ * is that ip_vs_ftp module handles the reverse direction to ip_masq_ftp.
+ *
+ * IP_MASQ_FTP ftp masquerading module
+ *
+ * Version: @(#)ip_masq_ftp.c 0.04 02/05/96
+ *
+ * Author: Wouter Gadeyne
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <net/protocol.h>
+#include <net/tcp.h>
+
+#include <net/ip_vs.h>
+
+
+#define SERVER_STRING "227 Entering Passive Mode ("
+#define CLIENT_STRING "PORT "
+
+
+/*
+ * List of ports (up to IP_VS_APP_MAX_PORTS) to be handled by helper
+ * First port is set to the default port.
+ */
+static int ports[IP_VS_APP_MAX_PORTS] = {21, 0};
+struct ip_vs_app *incarnations[IP_VS_APP_MAX_PORTS];
+
+/*
+ * Debug level
+ */
+#ifdef CONFIG_IP_VS_DEBUG
+static int debug=0;
+MODULE_PARM(debug, "i");
+#endif
+
+MODULE_PARM(ports, "1-" __MODULE_STRING(IP_VS_APP_MAX_PORTS) "i");
+
+/* Dummy variable */
+static int ip_vs_ftp_pasv;
+
+
+static int
+ip_vs_ftp_init_conn(struct ip_vs_app *vapp, struct ip_vs_conn *cp)
+{
+ return 0;
+}
+
+
+static int
+ip_vs_ftp_done_conn(struct ip_vs_app *vapp, struct ip_vs_conn *cp)
+{
+ return 0;
+}
+
+
+/*
+ * Get <addr,port> from the string "xxx.xxx.xxx.xxx,ppp,ppp", started
+ * with the "pattern" and terminated with the "term" character.
+ * <addr,port> is in network order.
+ */
+static int ip_vs_ftp_get_addrport(char *data, char *data_limit,
+ const char *pattern, size_t plen, char term,
+ __u32 *addr, __u16 *port,
+ char **start, char **end)
+{
+ unsigned char p[6];
+ int i = 0;
+
+ if (data_limit - data < plen) {
+ /* check if there is partial match */
+ if (strnicmp(data, pattern, data_limit - data) == 0)
+ return -1;
+ else
+ return 0;
+ }
+
+ if (strnicmp(data, pattern, plen) != 0) {
+ return 0;
+ }
+ *start = data + plen;
+
+ for (data = *start; *data != term; data++) {
+ if (data == data_limit)
+ return -1;
+ }
+ *end = data;
+
+ memset(p, 0, sizeof(p));
+ for (data = *start; data != *end; data++) {
+ if (*data >= '0' && *data <= '9') {
+ p[i] = p[i]*10 + *data - '0';
+ } else if (*data == ',' && i < 5) {
+ i++;
+ } else {
+ /* unexpected character */
+ return -1;
+ }
+ }
+
+ if (i != 5)
+ return -1;
+
+ *addr = (p[3]<<24) | (p[2]<<16) | (p[1]<<8) | p[0];
+ *port = (p[5]<<8) | p[4];
+ return 1;
+}
+
+
+/*
+ * Look at outgoing ftp packets to catch the response to a PASV command
+ * from the server (inside-to-outside).
+ * When we see one, we build a connection entry with the client address,
+ * client port 0 (unknown at the moment), the server address and the
+ * server port. Mark the current connection entry as a control channel
+ * of the new entry. All this work is just to make the data connection
+ * can be scheduled to the right server later.
+ *
+ * The outgoing packet should be something like
+ * "227 Entering Passive Mode (xxx,xxx,xxx,xxx,ppp,ppp)".
+ * xxx,xxx,xxx,xxx is the server address, ppp,ppp is the server port number.
+ */
+static int ip_vs_ftp_out(struct ip_vs_app *vapp,
+ struct ip_vs_conn *cp, struct sk_buff *skb)
+{
+ struct iphdr *iph;
+ struct tcphdr *th;
+ char *data, *data_limit;
+ char *start, *end;
+ __u32 from;
+ __u16 port;
+ struct ip_vs_conn *n_cp;
+ char buf[24]; /* xxx.xxx.xxx.xxx,ppp,ppp\000 */
+ unsigned buf_len;
+ int diff;
+
+ /* Only useful for established sessions */
+ if (cp->state != IP_VS_S_ESTABLISHED)
+ return 0;
+
+ if (cp->app_data == &ip_vs_ftp_pasv) {
+ iph = skb->nh.iph;
+ th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
+ data = (char *)th + (th->doff << 2);
+ data_limit = skb->tail;
+
+ if (ip_vs_ftp_get_addrport(data, data_limit,
+ SERVER_STRING,
+ sizeof(SERVER_STRING)-1, ')',
+ &from, &port,
+ &start, &end) != 1)
+ return 0;
+
+ IP_VS_DBG(1-debug, "PASV response (%u.%u.%u.%u:%d) -> "
+ "%u.%u.%u.%u:%d detected\n",
+ NIPQUAD(from), ntohs(port), NIPQUAD(cp->caddr), 0);
+
+ /*
+ * Now update or create an connection entry for it
+ */
+ n_cp = ip_vs_conn_out_get(iph->protocol, from, port,
+ cp->caddr, 0);
+ if (!n_cp) {
+ n_cp = ip_vs_conn_new(IPPROTO_TCP,
+ cp->caddr, 0,
+ cp->vaddr, port,
+ from, port,
+ IP_VS_CONN_F_NO_CPORT,
+ cp->dest);
+ if (!n_cp)
+ return 0;
+
+ /* add its controller */
+ ip_vs_control_add(n_cp, cp);
+
+ /* increase dest's inactive connection counter */
+ if (cp->dest)
+ atomic_inc(&cp->dest->inactconns);
+ }
+
+ /*
+ * Replace the old passive address with the new one
+ */
+ from = n_cp->vaddr;
+ port = n_cp->vport;
+ sprintf(buf,"%d,%d,%d,%d,%d,%d", NIPQUAD(from),
+ port&255, port>>8&255);
+ buf_len = strlen(buf);
+
+ /*
+ * Calculate required delta-offset to keep TCP happy
+ */
+ diff = buf_len - (end-start);
+
+ if (diff == 0) {
+ /* simply replace it with new passive address */
+ memcpy(start, buf, buf_len);
+ } else {
+ /* fixme: return value isn't checked here */
+ ip_vs_skb_replace(skb, GFP_ATOMIC, start,
+ end-start, buf, buf_len);
+ }
+
+ cp->app_data = NULL;
+ ip_vs_conn_listen(n_cp);
+ ip_vs_conn_put(n_cp);
+ return diff;
+ }
+ return 0;
+}
+
+
+/*
+ * Look at incoming ftp packets to catch the PASV/PORT command
+ * (outside-to-inside).
+ *
+ * The incoming packet having the PORT command should be something like
+ * "PORT xxx,xxx,xxx,xxx,ppp,ppp\n".
+ * xxx,xxx,xxx,xxx is the client address, ppp,ppp is the client port number.
+ * In this case, we create a connection entry using the client address and
+ * port, so that the active ftp data connection from the server can reach
+ * the client.
+ */
+static int ip_vs_ftp_in(struct ip_vs_app *vapp,
+ struct ip_vs_conn *cp, struct sk_buff *skb)
+{
+ struct iphdr *iph;
+ struct tcphdr *th;
+ char *data, *data_start, *data_limit;
+ char *start, *end;
+ __u32 to;
+ __u16 port;
+ struct ip_vs_conn *n_cp;
+
+ /* Only useful for established sessions */
+ if (cp->state != IP_VS_S_ESTABLISHED)
+ return 0;
+
+ /*
+ * Detecting whether it is passive
+ */
+ iph = skb->nh.iph;
+ th = (struct tcphdr *)&(((char *)iph)[iph->ihl*4]);
+
+ /* Since there may be OPTIONS in the TCP packet and the HLEN is
+ the length of the header in 32-bit multiples, it is accurate
+ to calculate data address by th+HLEN*4 */
+ data = data_start = (char *)th + (th->doff << 2);
+ data_limit = skb->tail;
+
+ while (data <= data_limit - 6) {
+ if (strnicmp(data, "PASV\r\n", 6) == 0) {
+ IP_VS_DBG(1-debug, "got PASV at %d of %d\n",
+ data - data_start,
+ data_limit - data_start);
+ cp->app_data = &ip_vs_ftp_pasv;
+ return 0;
+ }
+ data++;
+ }
+
+ /*
+ * To support virtual FTP server, the scenerio is as follows:
+ * FTP client ----> Load Balancer ----> FTP server
+ * First detect the port number in the application data,
+ * then create a new connection entry for the coming data
+ * connection.
+ */
+ if (ip_vs_ftp_get_addrport(data_start, data_limit,
+ CLIENT_STRING, sizeof(CLIENT_STRING)-1,
+ '\r', &to, &port,
+ &start, &end) != 1)
+ return 0;
+
+ IP_VS_DBG(1-debug, "PORT %u.%u.%u.%u:%d detected\n",
+ NIPQUAD(to), ntohs(port));
+
+ /*
+ * Now update or create a connection entry for it
+ */
+ IP_VS_DBG(1-debug, "protocol %s %u.%u.%u.%u:%d %u.%u.%u.%u:%d\n",
+ ip_vs_proto_name(iph->protocol),
+ NIPQUAD(to), ntohs(port),
+ NIPQUAD(cp->vaddr), ntohs(cp->vport) - 1);
+
+ n_cp = ip_vs_conn_in_get(iph->protocol,
+ to, port,
+ cp->vaddr, htons(ntohs(cp->vport)-1));
+ if (!n_cp) {
+ n_cp = ip_vs_conn_new(IPPROTO_TCP,
+ to, port,
+ cp->vaddr, htons(ntohs(cp->vport)-1),
+ cp->daddr, htons(ntohs(cp->dport)-1),
+ 0,
+ cp->dest);
+ if (!n_cp)
+ return 0;
+
+ /* add its controller */
+ ip_vs_control_add(n_cp, cp);
+
+ /* increase dest's inactive connection counter */
+ if (cp->dest)
+ atomic_inc(&cp->dest->inactconns);
+ }
+
+ /*
+ * Move tunnel to listen state
+ */
+ ip_vs_conn_listen(n_cp);
+ ip_vs_conn_put(n_cp);
+
+ /* no diff required for incoming packets */
+ return 0;
+}
+
+
+static struct ip_vs_app ip_vs_ftp = {
+ {0}, /* n_list */
+ "ftp", /* name */
+ 0, /* type */
+ THIS_MODULE, /* this module */
+ ip_vs_ftp_init_conn, /* ip_vs_init_conn */
+ ip_vs_ftp_done_conn, /* ip_vs_done_conn */
+ ip_vs_ftp_out, /* pkt_out */
+ ip_vs_ftp_in, /* pkt_in */
+};
+
+
+/*
+ * ip_vs_ftp initialization
+ */
+static int __init ip_vs_ftp_init(void)
+{
+ int i, j;
+
+ for (i=0; i<IP_VS_APP_MAX_PORTS; i++) {
+ if (ports[i]) {
+ if (!(incarnations[i] =
+ kmalloc(sizeof(struct ip_vs_app), GFP_KERNEL)))
+ return -ENOMEM;
+
+ memcpy(incarnations[i], &ip_vs_ftp,
+ sizeof(struct ip_vs_app));
+ if ((j = register_ip_vs_app(incarnations[i],
+ IPPROTO_TCP,
+ ports[i]))) {
+ return j;
+ }
+ IP_VS_DBG(1-debug,
+ "Ftp: loaded support on port[%d] = %d\n",
+ i, ports[i]);
+ } else {
+ /* To be safe, force the incarnation table entry
+ to be NULL */
+ incarnations[i] = NULL;
+ }
+ }
+ return 0;
+}
+
+
+/*
+ * ip_vs_ftp finish.
+ */
+static void __exit ip_vs_ftp_exit(void)
+{
+ int i, j, k;
+
+ k=0;
+ for (i=0; i<IP_VS_APP_MAX_PORTS; i++) {
+ if (incarnations[i]) {
+ if ((j = unregister_ip_vs_app(incarnations[i]))) {
+ k = j;
+ } else {
+ kfree(incarnations[i]);
+ incarnations[i] = NULL;
+ IP_VS_DBG(1-debug, "Ftp: unloaded support on port[%d] = %d\n",
+ i, ports[i]);
+ }
+ }
+ }
+}
+
+
+module_init(ip_vs_ftp_init);
+module_exit(ip_vs_ftp_exit);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_lblc.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_lblc.c
new file mode 100644
index 0000000..9f6f683
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_lblc.c
@@ -0,0 +1,620 @@
+/*
+ * IPVS: Locality-Based Least-Connection scheduling module
+ *
+ * Version: $Id: ip_vs_lblc.c,v 1.9 2002/03/25 12:44:35 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@gnuchina.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes:
+ * Martin Hamilton : fixed the terrible locking bugs
+ * *lock(tbl->lock) ==> *lock(&tbl->lock)
+ * Wensong Zhang : fixed the uninitilized tbl->lock bug
+ * Wensong Zhang : added doing full expiration check to
+ * collect stale entries of 24+ hours when
+ * no partial expire check in a half hour
+ * Julian Anastasov : replaced del_timer call with del_timer_sync
+ * to avoid the possible race between timer
+ * handler and del_timer thread in SMP
+ *
+ */
+
+/*
+ * The lblc algorithm is as follows (pseudo code):
+ *
+ * if cachenode[dest_ip] is null then
+ * n, cachenode[dest_ip] <- {weighted least-conn node};
+ * else
+ * n <- cachenode[dest_ip];
+ * if (n is dead) OR
+ * (n.conns>n.weight AND
+ * there is a node m with m.conns<m.weight/2) then
+ * n, cachenode[dest_ip] <- {weighted least-conn node};
+ *
+ * return n;
+ *
+ * Thanks must go to Wenzhuo Zhang for talking WCCP to me and pushing
+ * me to write this module.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+/* for sysctl */
+#include <linux/fs.h>
+#include <linux/sysctl.h>
+
+#include <net/ip_vs.h>
+
+
+/*
+ * It is for garbage collection of stale IPVS lblc entries,
+ * when the table is full.
+ */
+#define CHECK_EXPIRE_INTERVAL (60*HZ)
+#define ENTRY_TIMEOUT (6*60*HZ)
+
+/*
+ * It is for full expiration check.
+ * When there is no partial expiration check (garbage collection)
+ * in a half hour, do a full expiration check to collect stale
+ * entries that haven't been touched for a day.
+ */
+#define COUNT_FOR_FULL_EXPIRATION 30
+static int sysctl_ip_vs_lblc_expiration = 24*60*60*HZ;
+
+
+/*
+ * for IPVS lblc entry hash table
+ */
+#ifndef CONFIG_IP_VS_LBLC_TAB_BITS
+#define CONFIG_IP_VS_LBLC_TAB_BITS 10
+#endif
+#define IP_VS_LBLC_TAB_BITS CONFIG_IP_VS_LBLC_TAB_BITS
+#define IP_VS_LBLC_TAB_SIZE (1 << IP_VS_LBLC_TAB_BITS)
+#define IP_VS_LBLC_TAB_MASK (IP_VS_LBLC_TAB_SIZE - 1)
+
+
+/*
+ * IPVS lblc entry represents an association between destination
+ * IP address and its destination server
+ */
+struct ip_vs_lblc_entry {
+ struct list_head list;
+ __u32 addr; /* destination IP address */
+ struct ip_vs_dest *dest; /* real server (cache) */
+ unsigned long lastuse; /* last used time */
+};
+
+
+/*
+ * IPVS lblc hash table
+ */
+struct ip_vs_lblc_table {
+ rwlock_t lock; /* lock for this table */
+ struct list_head bucket[IP_VS_LBLC_TAB_SIZE]; /* hash bucket */
+ atomic_t entries; /* number of entries */
+ int max_size; /* maximum size of entries */
+ struct timer_list periodic_timer; /* collect stale entries */
+ int rover; /* rover for expire check */
+ int counter; /* counter for no expire */
+};
+
+
+/*
+ * IPVS LBLC sysctl table
+ */
+struct ip_vs_lblc_sysctl_table {
+ struct ctl_table_header *sysctl_header;
+ ctl_table vs_vars[2];
+ ctl_table vs_dir[2];
+ ctl_table ipv4_dir[2];
+ ctl_table root_dir[2];
+};
+
+
+static struct ip_vs_lblc_sysctl_table lblc_sysctl_table = {
+ NULL,
+ {{NET_IPV4_VS_LBLC_EXPIRE, "lblc_expiration",
+ &sysctl_ip_vs_lblc_expiration,
+ sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {0}},
+ {{NET_IPV4_VS, "vs", NULL, 0, 0555, lblc_sysctl_table.vs_vars},
+ {0}},
+ {{NET_IPV4, "ipv4", NULL, 0, 0555, lblc_sysctl_table.vs_dir},
+ {0}},
+ {{CTL_NET, "net", NULL, 0, 0555, lblc_sysctl_table.ipv4_dir},
+ {0}}
+};
+
+
+/*
+ * new/free a ip_vs_lblc_entry, which is a mapping of a destionation
+ * IP address to a server.
+ */
+static inline struct ip_vs_lblc_entry *
+ip_vs_lblc_new(__u32 daddr, struct ip_vs_dest *dest)
+{
+ struct ip_vs_lblc_entry *en;
+
+ en = kmalloc(sizeof(struct ip_vs_lblc_entry), GFP_ATOMIC);
+ if (en == NULL) {
+ IP_VS_ERR("ip_vs_lblc_new(): no memory\n");
+ return NULL;
+ }
+
+ INIT_LIST_HEAD(&en->list);
+ en->addr = daddr;
+
+ atomic_inc(&dest->refcnt);
+ en->dest = dest;
+
+ return en;
+}
+
+
+static inline void ip_vs_lblc_free(struct ip_vs_lblc_entry *en)
+{
+ list_del(&en->list);
+ /*
+ * We don't kfree dest because it is refered either by its service
+ * or the trash dest list.
+ */
+ atomic_dec(&en->dest->refcnt);
+ kfree(en);
+}
+
+
+/*
+ * Returns hash value for IPVS LBLC entry
+ */
+static inline unsigned ip_vs_lblc_hashkey(__u32 addr)
+{
+ return (ntohl(addr)*2654435761UL) & IP_VS_LBLC_TAB_MASK;
+}
+
+
+/*
+ * Hash an entry in the ip_vs_lblc_table.
+ * returns bool success.
+ */
+static int
+ip_vs_lblc_hash(struct ip_vs_lblc_table *tbl, struct ip_vs_lblc_entry *en)
+{
+ unsigned hash;
+
+ if (!list_empty(&en->list)) {
+ IP_VS_ERR("ip_vs_lblc_hash(): request for already hashed, "
+ "called from %p\n", __builtin_return_address(0));
+ return 0;
+ }
+
+ /*
+ * Hash by destination IP address
+ */
+ hash = ip_vs_lblc_hashkey(en->addr);
+
+ write_lock(&tbl->lock);
+ list_add(&en->list, &tbl->bucket[hash]);
+ atomic_inc(&tbl->entries);
+ write_unlock(&tbl->lock);
+
+ return 1;
+}
+
+
+#if 0000
+/*
+ * Unhash ip_vs_lblc_entry from ip_vs_lblc_table.
+ * returns bool success.
+ */
+static int ip_vs_lblc_unhash(struct ip_vs_lblc_table *tbl,
+ struct ip_vs_lblc_entry *en)
+{
+ if (list_empty(&en->list)) {
+ IP_VS_ERR("ip_vs_lblc_unhash(): request for not hashed entry, "
+ "called from %p\n", __builtin_return_address(0));
+ return 0;
+ }
+
+ /*
+ * Remove it from the table
+ */
+ write_lock(&tbl->lock);
+ list_del(&en->list);
+ INIT_LIST_HEAD(&en->list);
+ write_unlock(&tbl->lock);
+
+ return 1;
+}
+#endif
+
+
+/*
+ * Get ip_vs_lblc_entry associated with supplied parameters.
+ */
+static inline struct ip_vs_lblc_entry *
+ip_vs_lblc_get(struct ip_vs_lblc_table *tbl, __u32 addr)
+{
+ unsigned hash;
+ struct ip_vs_lblc_entry *en;
+ struct list_head *l,*e;
+
+ hash = ip_vs_lblc_hashkey(addr);
+ l = &tbl->bucket[hash];
+
+ read_lock(&tbl->lock);
+
+ for (e=l->next; e!=l; e=e->next) {
+ en = list_entry(e, struct ip_vs_lblc_entry, list);
+ if (en->addr == addr) {
+ /* HIT */
+ read_unlock(&tbl->lock);
+ return en;
+ }
+ }
+
+ read_unlock(&tbl->lock);
+
+ return NULL;
+}
+
+
+/*
+ * Flush all the entries of the specified table.
+ */
+static void ip_vs_lblc_flush(struct ip_vs_lblc_table *tbl)
+{
+ int i;
+ struct list_head *l;
+ struct ip_vs_lblc_entry *en;
+
+ for (i=0; i<IP_VS_LBLC_TAB_SIZE; i++) {
+ write_lock(&tbl->lock);
+ for (l=&tbl->bucket[i]; l->next!=l; ) {
+ en = list_entry(l->next,
+ struct ip_vs_lblc_entry, list);
+ ip_vs_lblc_free(en);
+ atomic_dec(&tbl->entries);
+ }
+ write_unlock(&tbl->lock);
+ }
+}
+
+
+static inline void ip_vs_lblc_full_check(struct ip_vs_lblc_table *tbl)
+{
+ unsigned long now = jiffies;
+ int i, j;
+ struct list_head *l, *e;
+ struct ip_vs_lblc_entry *en;
+
+ for (i=0, j=tbl->rover; i<IP_VS_LBLC_TAB_SIZE; i++) {
+ j = (j + 1) & IP_VS_LBLC_TAB_MASK;
+ e = l = &tbl->bucket[j];
+ write_lock(&tbl->lock);
+ while (e->next != l) {
+ en = list_entry(e->next,
+ struct ip_vs_lblc_entry, list);
+ if ((now - en->lastuse) <
+ sysctl_ip_vs_lblc_expiration) {
+ e = e->next;
+ continue;
+ }
+ ip_vs_lblc_free(en);
+ atomic_dec(&tbl->entries);
+ }
+ write_unlock(&tbl->lock);
+ }
+ tbl->rover = j;
+}
+
+
+/*
+ * Periodical timer handler for IPVS lblc table
+ * It is used to collect stale entries when the number of entries
+ * exceeds the maximum size of the table.
+ *
+ * Fixme: we probably need more complicated algorithm to collect
+ * entries that have not been used for a long time even
+ * if the number of entries doesn't exceed the maximum size
+ * of the table.
+ * The full expiration check is for this purpose now.
+ */
+static void ip_vs_lblc_check_expire(unsigned long data)
+{
+ struct ip_vs_lblc_table *tbl;
+ unsigned long now = jiffies;
+ int goal;
+ int i, j;
+ struct list_head *l, *e;
+ struct ip_vs_lblc_entry *en;
+
+ tbl = (struct ip_vs_lblc_table *)data;
+
+ if ((tbl->counter % COUNT_FOR_FULL_EXPIRATION) == 0) {
+ /* do full expiration check */
+ ip_vs_lblc_full_check(tbl);
+ tbl->counter = 1;
+ goto out;
+ }
+
+ if (atomic_read(&tbl->entries) <= tbl->max_size) {
+ tbl->counter++;
+ goto out;
+ }
+
+ goal = (atomic_read(&tbl->entries) - tbl->max_size)*4/3;
+ if (goal > tbl->max_size/2)
+ goal = tbl->max_size/2;
+
+ for (i=0, j=tbl->rover; i<IP_VS_LBLC_TAB_SIZE; i++) {
+ j = (j + 1) & IP_VS_LBLC_TAB_MASK;
+ e = l = &tbl->bucket[j];
+ write_lock(&tbl->lock);
+ while (e->next != l) {
+ en = list_entry(e->next,
+ struct ip_vs_lblc_entry, list);
+ if ((now - en->lastuse) < ENTRY_TIMEOUT) {
+ e = e->next;
+ continue;
+ }
+ ip_vs_lblc_free(en);
+ atomic_dec(&tbl->entries);
+ goal--;
+ }
+ write_unlock(&tbl->lock);
+ if (goal <= 0)
+ break;
+ }
+ tbl->rover = j;
+
+ out:
+ mod_timer(&tbl->periodic_timer, jiffies+CHECK_EXPIRE_INTERVAL);
+}
+
+
+static int ip_vs_lblc_init_svc(struct ip_vs_service *svc)
+{
+ int i;
+ struct ip_vs_lblc_table *tbl;
+
+ /*
+ * Allocate the ip_vs_lblc_table for this service
+ */
+ tbl = kmalloc(sizeof(struct ip_vs_lblc_table), GFP_ATOMIC);
+ if (tbl == NULL) {
+ IP_VS_ERR("ip_vs_lblc_init_svc(): no memory\n");
+ return -ENOMEM;
+ }
+ svc->sched_data = tbl;
+ IP_VS_DBG(6, "LBLC hash table (memory=%dbytes) allocated for "
+ "current service\n",
+ sizeof(struct ip_vs_lblc_table));
+
+ /*
+ * Initialize the hash buckets
+ */
+ for (i=0; i<IP_VS_LBLC_TAB_SIZE; i++) {
+ INIT_LIST_HEAD(&tbl->bucket[i]);
+ }
+ tbl->lock = RW_LOCK_UNLOCKED;
+ tbl->max_size = IP_VS_LBLC_TAB_SIZE*16;
+ tbl->rover = 0;
+ tbl->counter = 1;
+
+ /*
+ * Hook periodic timer for garbage collection
+ */
+ init_timer(&tbl->periodic_timer);
+ tbl->periodic_timer.data = (unsigned long)tbl;
+ tbl->periodic_timer.function = ip_vs_lblc_check_expire;
+ tbl->periodic_timer.expires = jiffies+CHECK_EXPIRE_INTERVAL;
+ add_timer(&tbl->periodic_timer);
+
+ return 0;
+}
+
+
+static int ip_vs_lblc_done_svc(struct ip_vs_service *svc)
+{
+ struct ip_vs_lblc_table *tbl = svc->sched_data;
+
+ /* remove periodic timer */
+ del_timer_sync(&tbl->periodic_timer);
+
+ /* got to clean up table entries here */
+ ip_vs_lblc_flush(tbl);
+
+ /* release the table itself */
+ kfree(svc->sched_data);
+ IP_VS_DBG(6, "LBLC hash table (memory=%dbytes) released\n",
+ sizeof(struct ip_vs_lblc_table));
+
+ return 0;
+}
+
+
+static int ip_vs_lblc_update_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static inline struct ip_vs_dest *
+__ip_vs_wlc_schedule(struct ip_vs_service *svc, struct iphdr *iph)
+{
+ register struct list_head *l, *e;
+ struct ip_vs_dest *dest, *least;
+ int loh, doh;
+
+ /*
+ * We think the overhead of processing active connections is fifty
+ * times higher than that of inactive connections in average. (This
+ * fifty times might not be accurate, we will change it later.) We
+ * use the following formula to estimate the overhead:
+ * dest->activeconns*50 + dest->inactconns
+ * and the load:
+ * (dest overhead) / dest->weight
+ *
+ * Remember -- no floats in kernel mode!!!
+ * The comparison of h1*w2 > h2*w1 is equivalent to that of
+ * h1/w1 > h2/w2
+ * if every weight is larger than zero.
+ *
+ * The server with weight=0 is quiesced and will not receive any
+ * new connection.
+ */
+
+ l = &svc->destinations;
+ for (e=l->next; e!=l; e=e->next) {
+ least = list_entry(e, struct ip_vs_dest, n_list);
+ if (atomic_read(&least->weight) > 0) {
+ loh = atomic_read(&least->activeconns) * 50
+ + atomic_read(&least->inactconns);
+ goto nextstage;
+ }
+ }
+ return NULL;
+
+ /*
+ * Find the destination with the least load.
+ */
+ nextstage:
+ for (e=e->next; e!=l; e=e->next) {
+ dest = list_entry(e, struct ip_vs_dest, n_list);
+ doh = atomic_read(&dest->activeconns) * 50
+ + atomic_read(&dest->inactconns);
+ if (loh * atomic_read(&dest->weight) >
+ doh * atomic_read(&least->weight)) {
+ least = dest;
+ loh = doh;
+ }
+ }
+
+ IP_VS_DBG(6, "LBLC: server %d.%d.%d.%d:%d "
+ "activeconns %d refcnt %d weight %d overhead %d\n",
+ NIPQUAD(least->addr), ntohs(least->port),
+ atomic_read(&least->activeconns),
+ atomic_read(&least->refcnt),
+ atomic_read(&least->weight), loh);
+
+ return least;
+}
+
+
+/*
+ * If this destination server is overloaded and there is a less loaded
+ * server, then return true.
+ */
+static inline int
+is_overloaded(struct ip_vs_dest *dest, struct ip_vs_service *svc)
+{
+ if (atomic_read(&dest->activeconns) > atomic_read(&dest->weight)) {
+ register struct list_head *l, *e;
+ struct ip_vs_dest *d;
+
+ l = &svc->destinations;
+ for (e=l->next; e!=l; e=e->next) {
+ d = list_entry(e, struct ip_vs_dest, n_list);
+ if (atomic_read(&d->activeconns)*2
+ < atomic_read(&d->weight)) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+
+/*
+ * Locality-Based (weighted) Least-Connection scheduling
+ */
+static struct ip_vs_dest *
+ip_vs_lblc_schedule(struct ip_vs_service *svc, struct iphdr *iph)
+{
+ struct ip_vs_dest *dest;
+ struct ip_vs_lblc_table *tbl;
+ struct ip_vs_lblc_entry *en;
+
+ IP_VS_DBG(6, "ip_vs_lblc_schedule(): Scheduling...\n");
+
+ tbl = (struct ip_vs_lblc_table *)svc->sched_data;
+ en = ip_vs_lblc_get(tbl, iph->daddr);
+ if (en == NULL) {
+ dest = __ip_vs_wlc_schedule(svc, iph);
+ if (dest == NULL) {
+ IP_VS_DBG(1, "no destination available\n");
+ return NULL;
+ }
+ en = ip_vs_lblc_new(iph->daddr, dest);
+ if (en == NULL) {
+ return NULL;
+ }
+ ip_vs_lblc_hash(tbl, en);
+ } else {
+ dest = en->dest;
+ if (!(dest->flags & IP_VS_DEST_F_AVAILABLE)
+ || atomic_read(&dest->weight) <= 0
+ || is_overloaded(dest, svc)) {
+ dest = __ip_vs_wlc_schedule(svc, iph);
+ if (dest == NULL) {
+ IP_VS_DBG(1, "no destination available\n");
+ return NULL;
+ }
+ atomic_dec(&en->dest->refcnt);
+ atomic_inc(&dest->refcnt);
+ en->dest = dest;
+ }
+ }
+ en->lastuse = jiffies;
+
+ IP_VS_DBG(6, "LBLC: destination IP address %u.%u.%u.%u "
+ "--> server %u.%u.%u.%u:%d\n",
+ NIPQUAD(en->addr),
+ NIPQUAD(dest->addr),
+ ntohs(dest->port));
+
+ return dest;
+}
+
+
+/*
+ * IPVS LBLC Scheduler structure
+ */
+static struct ip_vs_scheduler ip_vs_lblc_scheduler =
+{
+ {0}, /* n_list */
+ "lblc", /* name */
+ ATOMIC_INIT(0), /* refcnt */
+ THIS_MODULE, /* this module */
+ ip_vs_lblc_init_svc, /* service initializer */
+ ip_vs_lblc_done_svc, /* service done */
+ ip_vs_lblc_update_svc, /* service updater */
+ ip_vs_lblc_schedule, /* select a server from the destination list */
+};
+
+
+static int __init ip_vs_lblc_init(void)
+{
+ INIT_LIST_HEAD(&ip_vs_lblc_scheduler.n_list);
+ lblc_sysctl_table.sysctl_header =
+ register_sysctl_table(lblc_sysctl_table.root_dir, 0);
+ return register_ip_vs_scheduler(&ip_vs_lblc_scheduler);
+}
+
+
+static void __exit ip_vs_lblc_cleanup(void)
+{
+ unregister_sysctl_table(lblc_sysctl_table.sysctl_header);
+ unregister_ip_vs_scheduler(&ip_vs_lblc_scheduler);
+}
+
+
+module_init(ip_vs_lblc_init);
+module_exit(ip_vs_lblc_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_lblcr.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_lblcr.c
new file mode 100644
index 0000000..ba8998d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_lblcr.c
@@ -0,0 +1,880 @@
+/*
+ * IPVS: Locality-Based Least-Connection with Replication scheduler
+ *
+ * Version: $Id: ip_vs_lblcr.c,v 1.10 2002/03/25 12:44:35 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@gnuchina.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes:
+ * Julian Anastasov : Added the missing (dest->weight>0)
+ * condition in the ip_vs_dest_set_max.
+ *
+ */
+
+/*
+ * The lblc/r algorithm is as follows (pseudo code):
+ *
+ * if serverSet[dest_ip] is null then
+ * n, serverSet[dest_ip] <- {weighted least-conn node};
+ * else
+ * n <- {least-conn (alive) node in serverSet[dest_ip]};
+ * if (n is null) OR
+ * (n.conns>n.weight AND
+ * there is a node m with m.conns<m.weight/2) then
+ * n <- {weighted least-conn node};
+ * add n to serverSet[dest_ip];
+ * if |serverSet[dest_ip]| > 1 AND
+ * now - serverSet[dest_ip].lastMod > T then
+ * m <- {most conn node in serverSet[dest_ip]};
+ * remove m from serverSet[dest_ip];
+ * if serverSet[dest_ip] changed then
+ * serverSet[dest_ip].lastMod <- now;
+ *
+ * return n;
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+/* for sysctl */
+#include <linux/fs.h>
+#include <linux/sysctl.h>
+/* for proc_net_create/proc_net_remove */
+#include <linux/proc_fs.h>
+
+#include <net/ip_vs.h>
+
+
+/*
+ * It is for garbage collection of stale IPVS lblcr entries,
+ * when the table is full.
+ */
+#define CHECK_EXPIRE_INTERVAL (60*HZ)
+#define ENTRY_TIMEOUT (6*60*HZ)
+
+/*
+ * It is for full expiration check.
+ * When there is no partial expiration check (garbage collection)
+ * in a half hour, do a full expiration check to collect stale
+ * entries that haven't been touched for a day.
+ */
+#define COUNT_FOR_FULL_EXPIRATION 30
+static int sysctl_ip_vs_lblcr_expiration = 24*60*60*HZ;
+
+
+/*
+ * for IPVS lblcr entry hash table
+ */
+#ifndef CONFIG_IP_VS_LBLCR_TAB_BITS
+#define CONFIG_IP_VS_LBLCR_TAB_BITS 10
+#endif
+#define IP_VS_LBLCR_TAB_BITS CONFIG_IP_VS_LBLCR_TAB_BITS
+#define IP_VS_LBLCR_TAB_SIZE (1 << IP_VS_LBLCR_TAB_BITS)
+#define IP_VS_LBLCR_TAB_MASK (IP_VS_LBLCR_TAB_SIZE - 1)
+
+
+/*
+ * IPVS destination set structure and operations
+ */
+struct ip_vs_dest_list {
+ struct ip_vs_dest_list *next; /* list link */
+ struct ip_vs_dest *dest; /* destination server */
+};
+
+struct ip_vs_dest_set {
+ atomic_t size; /* set size */
+ unsigned long lastmod; /* last modified time */
+ struct ip_vs_dest_list *list; /* destination list */
+ rwlock_t lock; /* lock for this list */
+};
+
+
+static struct ip_vs_dest_list *
+ip_vs_dest_set_insert(struct ip_vs_dest_set *set, struct ip_vs_dest *dest)
+{
+ struct ip_vs_dest_list *e;
+
+ for (e=set->list; e!=NULL; e=e->next) {
+ if (e->dest == dest)
+ /* already existed */
+ return NULL;
+ }
+
+ e = kmalloc(sizeof(struct ip_vs_dest_list), GFP_ATOMIC);
+ if (e == NULL) {
+ IP_VS_ERR("ip_vs_dest_set_insert(): no memory\n");
+ return NULL;
+ }
+
+ atomic_inc(&dest->refcnt);
+ e->dest = dest;
+
+ /* link it to the list */
+ write_lock(&set->lock);
+ e->next = set->list;
+ set->list = e;
+ atomic_inc(&set->size);
+ write_unlock(&set->lock);
+
+ set->lastmod = jiffies;
+ return e;
+}
+
+static void
+ip_vs_dest_set_erase(struct ip_vs_dest_set *set, struct ip_vs_dest *dest)
+{
+ struct ip_vs_dest_list *e, **ep;
+
+ write_lock(&set->lock);
+ for (ep=&set->list, e=*ep; e!=NULL; e=*ep) {
+ if (e->dest == dest) {
+ /* HIT */
+ *ep = e->next;
+ atomic_dec(&set->size);
+ set->lastmod = jiffies;
+ atomic_dec(&e->dest->refcnt);
+ kfree(e);
+ break;
+ }
+ ep = &e->next;
+ }
+ write_unlock(&set->lock);
+}
+
+static void ip_vs_dest_set_eraseall(struct ip_vs_dest_set *set)
+{
+ struct ip_vs_dest_list *e, **ep;
+
+ write_lock(&set->lock);
+ for (ep=&set->list, e=*ep; e!=NULL; e=*ep) {
+ *ep = e->next;
+ /*
+ * We don't kfree dest because it is refered either
+ * by its service or by the trash dest list.
+ */
+ atomic_dec(&e->dest->refcnt);
+ kfree(e);
+ }
+ write_unlock(&set->lock);
+}
+
+/* get weighted least-connection node in the destination set */
+static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set)
+{
+ register struct ip_vs_dest_list *e;
+ struct ip_vs_dest *dest, *least;
+ int loh, doh;
+
+ if (set == NULL)
+ return NULL;
+
+ read_lock(&set->lock);
+ /* select the first destination server, whose weight > 0 */
+ for (e=set->list; e!=NULL; e=e->next) {
+ least = e->dest;
+ if ((atomic_read(&least->weight) > 0)
+ && (least->flags & IP_VS_DEST_F_AVAILABLE)) {
+ loh = atomic_read(&least->activeconns) * 50
+ + atomic_read(&least->inactconns);
+ goto nextstage;
+ }
+ }
+ read_unlock(&set->lock);
+ return NULL;
+
+ /* find the destination with the weighted least load */
+ nextstage:
+ for (e=e->next; e!=NULL; e=e->next) {
+ dest = e->dest;
+ doh = atomic_read(&dest->activeconns) * 50
+ + atomic_read(&dest->inactconns);
+ if ((loh * atomic_read(&dest->weight) >
+ doh * atomic_read(&least->weight))
+ && (dest->flags & IP_VS_DEST_F_AVAILABLE)) {
+ least = dest;
+ loh = doh;
+ }
+ }
+ read_unlock(&set->lock);
+
+ IP_VS_DBG(6, "ip_vs_dest_set_min: server %d.%d.%d.%d:%d "
+ "activeconns %d refcnt %d weight %d overhead %d\n",
+ NIPQUAD(least->addr), ntohs(least->port),
+ atomic_read(&least->activeconns),
+ atomic_read(&least->refcnt),
+ atomic_read(&least->weight), loh);
+ return least;
+}
+
+
+/* get weighted most-connection node in the destination set */
+static inline struct ip_vs_dest *ip_vs_dest_set_max(struct ip_vs_dest_set *set)
+{
+ register struct ip_vs_dest_list *e;
+ struct ip_vs_dest *dest, *most;
+ int moh, doh;
+
+ if (set == NULL)
+ return NULL;
+
+ read_lock(&set->lock);
+ /* select the first destination server, whose weight > 0 */
+ for (e=set->list; e!=NULL; e=e->next) {
+ most = e->dest;
+ if (atomic_read(&most->weight) > 0) {
+ moh = atomic_read(&most->activeconns) * 50
+ + atomic_read(&most->inactconns);
+ goto nextstage;
+ }
+ }
+ read_unlock(&set->lock);
+ return NULL;
+
+ /* find the destination with the weighted most load */
+ nextstage:
+ for (e=e->next; e!=NULL; e=e->next) {
+ dest = e->dest;
+ doh = atomic_read(&dest->activeconns) * 50
+ + atomic_read(&dest->inactconns);
+ /* moh/mw < doh/dw ==> moh*dw < doh*mw, where mw,dw>0 */
+ if ((moh * atomic_read(&dest->weight) <
+ doh * atomic_read(&most->weight))
+ && (atomic_read(&dest->weight) > 0)) {
+ most = dest;
+ moh = doh;
+ }
+ }
+ read_unlock(&set->lock);
+
+ IP_VS_DBG(6, "ip_vs_dest_set_max: server %d.%d.%d.%d:%d "
+ "activeconns %d refcnt %d weight %d overhead %d\n",
+ NIPQUAD(most->addr), ntohs(most->port),
+ atomic_read(&most->activeconns),
+ atomic_read(&most->refcnt),
+ atomic_read(&most->weight), moh);
+ return most;
+}
+
+
+/*
+ * IPVS lblcr entry represents an association between destination
+ * IP address and its destination server set
+ */
+struct ip_vs_lblcr_entry {
+ struct list_head list;
+ __u32 addr; /* destination IP address */
+ struct ip_vs_dest_set set; /* destination server set */
+ unsigned long lastuse; /* last used time */
+};
+
+
+/*
+ * IPVS lblcr hash table
+ */
+struct ip_vs_lblcr_table {
+ rwlock_t lock; /* lock for this table */
+ struct list_head bucket[IP_VS_LBLCR_TAB_SIZE]; /* hash bucket */
+ atomic_t entries; /* number of entries */
+ int max_size; /* maximum size of entries */
+ struct timer_list periodic_timer; /* collect stale entries */
+ int rover; /* rover for expire check */
+ int counter; /* counter for no expire */
+};
+
+
+/*
+ * IPVS LBLCR sysctl table
+ */
+struct ip_vs_lblcr_sysctl_table {
+ struct ctl_table_header *sysctl_header;
+ ctl_table vs_vars[2];
+ ctl_table vs_dir[2];
+ ctl_table ipv4_dir[2];
+ ctl_table root_dir[2];
+};
+
+
+static struct ip_vs_lblcr_sysctl_table lblcr_sysctl_table = {
+ NULL,
+ {{NET_IPV4_VS_LBLCR_EXPIRE, "lblcr_expiration",
+ &sysctl_ip_vs_lblcr_expiration,
+ sizeof(int), 0644, NULL, &proc_dointvec_jiffies},
+ {0}},
+ {{NET_IPV4_VS, "vs", NULL, 0, 0555, lblcr_sysctl_table.vs_vars},
+ {0}},
+ {{NET_IPV4, "ipv4", NULL, 0, 0555, lblcr_sysctl_table.vs_dir},
+ {0}},
+ {{CTL_NET, "net", NULL, 0, 0555, lblcr_sysctl_table.ipv4_dir},
+ {0}}
+};
+
+
+/*
+ * new/free a ip_vs_lblcr_entry, which is a mapping of a destination
+ * IP address to a server.
+ */
+static inline struct ip_vs_lblcr_entry *ip_vs_lblcr_new(__u32 daddr)
+{
+ struct ip_vs_lblcr_entry *en;
+
+ en = kmalloc(sizeof(struct ip_vs_lblcr_entry), GFP_ATOMIC);
+ if (en == NULL) {
+ IP_VS_ERR("ip_vs_lblcr_new(): no memory\n");
+ return NULL;
+ }
+
+ INIT_LIST_HEAD(&en->list);
+ en->addr = daddr;
+
+ /* initilize its dest set */
+ atomic_set(&(en->set.size), 0);
+ en->set.list = NULL;
+ en->set.lock = RW_LOCK_UNLOCKED;
+
+ return en;
+}
+
+
+static inline void ip_vs_lblcr_free(struct ip_vs_lblcr_entry *en)
+{
+ list_del(&en->list);
+ ip_vs_dest_set_eraseall(&en->set);
+ kfree(en);
+}
+
+
+/*
+ * Returns hash value for IPVS LBLCR entry
+ */
+static inline unsigned ip_vs_lblcr_hashkey(__u32 addr)
+{
+ return (ntohl(addr)*2654435761UL) & IP_VS_LBLCR_TAB_MASK;
+}
+
+
+/*
+ * Hash an entry in the ip_vs_lblcr_table.
+ * returns bool success.
+ */
+static int
+ip_vs_lblcr_hash(struct ip_vs_lblcr_table *tbl, struct ip_vs_lblcr_entry *en)
+{
+ unsigned hash;
+
+ if (!list_empty(&en->list)) {
+ IP_VS_ERR("ip_vs_lblcr_hash(): request for already hashed, "
+ "called from %p\n", __builtin_return_address(0));
+ return 0;
+ }
+
+ /*
+ * Hash by destination IP address
+ */
+ hash = ip_vs_lblcr_hashkey(en->addr);
+
+ write_lock(&tbl->lock);
+ list_add(&en->list, &tbl->bucket[hash]);
+ atomic_inc(&tbl->entries);
+ write_unlock(&tbl->lock);
+
+ return 1;
+}
+
+
+#if 0000
+/*
+ * Unhash ip_vs_lblcr_entry from ip_vs_lblcr_table.
+ * returns bool success.
+ */
+static int ip_vs_lblcr_unhash(struct ip_vs_lblcr_table *tbl,
+ struct ip_vs_lblcr_entry *en)
+{
+ if (list_empty(&en->list)) {
+ IP_VS_ERR("ip_vs_lblcr_unhash(): request for not hashed entry, "
+ "called from %p\n", __builtin_return_address(0));
+ return 0;
+ }
+
+ /*
+ * Remove it from the table
+ */
+ write_lock(&tbl->lock);
+ list_del(&en->list);
+ INIT_LIST_HEAD(&en->list);
+ write_unlock(&tbl->lock);
+
+ return 1;
+}
+#endif
+
+
+/*
+ * Get ip_vs_lblcr_entry associated with supplied parameters.
+ */
+static inline struct ip_vs_lblcr_entry *
+ip_vs_lblcr_get(struct ip_vs_lblcr_table *tbl, __u32 addr)
+{
+ unsigned hash;
+ struct ip_vs_lblcr_entry *en;
+ struct list_head *l,*e;
+
+ hash = ip_vs_lblcr_hashkey(addr);
+ l = &tbl->bucket[hash];
+
+ read_lock(&tbl->lock);
+
+ for (e=l->next; e!=l; e=e->next) {
+ en = list_entry(e, struct ip_vs_lblcr_entry, list);
+ if (en->addr == addr) {
+ /* HIT */
+ read_unlock(&tbl->lock);
+ return en;
+ }
+ }
+
+ read_unlock(&tbl->lock);
+
+ return NULL;
+}
+
+
+/*
+ * Flush all the entries of the specified table.
+ */
+static void ip_vs_lblcr_flush(struct ip_vs_lblcr_table *tbl)
+{
+ int i;
+ struct list_head *l;
+ struct ip_vs_lblcr_entry *en;
+
+ for (i=0; i<IP_VS_LBLCR_TAB_SIZE; i++) {
+ write_lock(&tbl->lock);
+ for (l=&tbl->bucket[i]; l->next!=l; ) {
+ en = list_entry(l->next,
+ struct ip_vs_lblcr_entry, list);
+ ip_vs_lblcr_free(en);
+ atomic_dec(&tbl->entries);
+ }
+ write_unlock(&tbl->lock);
+ }
+}
+
+
+static inline void ip_vs_lblcr_full_check(struct ip_vs_lblcr_table *tbl)
+{
+ unsigned long now = jiffies;
+ int i, j;
+ struct list_head *l, *e;
+ struct ip_vs_lblcr_entry *en;
+
+ for (i=0, j=tbl->rover; i<IP_VS_LBLCR_TAB_SIZE; i++) {
+ j = (j + 1) & IP_VS_LBLCR_TAB_MASK;
+ e = l = &tbl->bucket[j];
+ write_lock(&tbl->lock);
+ while (e->next != l) {
+ en = list_entry(e->next,
+ struct ip_vs_lblcr_entry, list);
+ if ((now - en->lastuse) <
+ sysctl_ip_vs_lblcr_expiration) {
+ e = e->next;
+ continue;
+ }
+ ip_vs_lblcr_free(en);
+ atomic_dec(&tbl->entries);
+ }
+ write_unlock(&tbl->lock);
+ }
+ tbl->rover = j;
+}
+
+
+/*
+ * Periodical timer handler for IPVS lblcr table
+ * It is used to collect stale entries when the number of entries
+ * exceeds the maximum size of the table.
+ *
+ * Fixme: we probably need more complicated algorithm to collect
+ * entries that have not been used for a long time even
+ * if the number of entries doesn't exceed the maximum size
+ * of the table.
+ * The full expiration check is for this purpose now.
+ */
+static void ip_vs_lblcr_check_expire(unsigned long data)
+{
+ struct ip_vs_lblcr_table *tbl;
+ unsigned long now = jiffies;
+ int goal;
+ int i, j;
+ struct list_head *l, *e;
+ struct ip_vs_lblcr_entry *en;
+
+ tbl = (struct ip_vs_lblcr_table *)data;
+
+ if ((tbl->counter % COUNT_FOR_FULL_EXPIRATION) == 0) {
+ /* do full expiration check */
+ ip_vs_lblcr_full_check(tbl);
+ tbl->counter = 1;
+ goto out;
+ }
+
+ if (atomic_read(&tbl->entries) <= tbl->max_size) {
+ tbl->counter++;
+ goto out;
+ }
+
+ goal = (atomic_read(&tbl->entries) - tbl->max_size)*4/3;
+ if (goal > tbl->max_size/2)
+ goal = tbl->max_size/2;
+
+ for (i=0, j=tbl->rover; i<IP_VS_LBLCR_TAB_SIZE; i++) {
+ j = (j + 1) & IP_VS_LBLCR_TAB_MASK;
+ e = l = &tbl->bucket[j];
+ write_lock(&tbl->lock);
+ while (e->next != l) {
+ en = list_entry(e->next,
+ struct ip_vs_lblcr_entry, list);
+ if ((now - en->lastuse) < ENTRY_TIMEOUT) {
+ e = e->next;
+ continue;
+ }
+ ip_vs_lblcr_free(en);
+ atomic_dec(&tbl->entries);
+ goal--;
+ }
+ write_unlock(&tbl->lock);
+ if (goal <= 0)
+ break;
+ }
+ tbl->rover = j;
+
+ out:
+ mod_timer(&tbl->periodic_timer, jiffies+CHECK_EXPIRE_INTERVAL);
+}
+
+
+#ifdef CONFIG_IP_VS_LBLCR_DEBUG
+static struct ip_vs_lblcr_table *lblcr_table_list;
+
+/*
+ * /proc/net/ip_vs_lblcr to display the mappings of
+ * destination IP address <==> its serverSet
+ */
+static int
+ip_vs_lblcr_getinfo(char *buffer, char **start, off_t offset, int length)
+{
+ off_t pos=0, begin;
+ int len=0, size;
+ struct ip_vs_lblcr_table *tbl;
+ unsigned long now = jiffies;
+ int i;
+ struct list_head *l, *e;
+ struct ip_vs_lblcr_entry *en;
+
+ tbl = lblcr_table_list;
+
+ size = sprintf(buffer, "LastTime Dest IP address Server set\n");
+ pos += size;
+ len += size;
+
+ for (i=0; i<IP_VS_LBLCR_TAB_SIZE; i++) {
+ l = &tbl->bucket[i];
+ read_lock_bh(&tbl->lock);
+ for (e=l->next; e!=l; e=e->next) {
+ char tbuf[16];
+ struct ip_vs_dest_list *d;
+
+ en = list_entry(e, struct ip_vs_lblcr_entry, list);
+ sprintf(tbuf, "%u.%u.%u.%u", NIPQUAD(en->addr));
+ size = sprintf(buffer+len, "%8lu %-16s ",
+ now-en->lastuse, tbuf);
+
+ read_lock(&en->set.lock);
+ for (d=en->set.list; d!=NULL; d=d->next) {
+ size += sprintf(buffer+len+size,
+ "%u.%u.%u.%u ",
+ NIPQUAD(d->dest->addr));
+ }
+ read_unlock(&en->set.lock);
+ size += sprintf(buffer+len+size, "\n");
+ len += size;
+ pos += size;
+ if (pos <= offset)
+ len=0;
+ if (pos >= offset+length) {
+ read_unlock_bh(&tbl->lock);
+ goto done;
+ }
+ }
+ read_unlock_bh(&tbl->lock);
+ }
+
+ done:
+ begin = len - (pos - offset);
+ *start = buffer + begin;
+ len -= begin;
+ if(len>length)
+ len = length;
+ return len;
+}
+#endif
+
+
+static int ip_vs_lblcr_init_svc(struct ip_vs_service *svc)
+{
+ int i;
+ struct ip_vs_lblcr_table *tbl;
+
+ /*
+ * Allocate the ip_vs_lblcr_table for this service
+ */
+ tbl = kmalloc(sizeof(struct ip_vs_lblcr_table), GFP_ATOMIC);
+ if (tbl == NULL) {
+ IP_VS_ERR("ip_vs_lblcr_init_svc(): no memory\n");
+ return -ENOMEM;
+ }
+ svc->sched_data = tbl;
+ IP_VS_DBG(6, "LBLCR hash table (memory=%dbytes) allocated for "
+ "current service\n",
+ sizeof(struct ip_vs_lblcr_table));
+
+ /*
+ * Initialize the hash buckets
+ */
+ for (i=0; i<IP_VS_LBLCR_TAB_SIZE; i++) {
+ INIT_LIST_HEAD(&tbl->bucket[i]);
+ }
+ tbl->lock = RW_LOCK_UNLOCKED;
+ tbl->max_size = IP_VS_LBLCR_TAB_SIZE*16;
+ tbl->rover = 0;
+ tbl->counter = 1;
+
+ /*
+ * Hook periodic timer for garbage collection
+ */
+ init_timer(&tbl->periodic_timer);
+ tbl->periodic_timer.data = (unsigned long)tbl;
+ tbl->periodic_timer.function = ip_vs_lblcr_check_expire;
+ tbl->periodic_timer.expires = jiffies+CHECK_EXPIRE_INTERVAL;
+ add_timer(&tbl->periodic_timer);
+
+#ifdef CONFIG_IP_VS_LBLCR_DEBUG
+ lblcr_table_list = tbl;
+#endif
+ return 0;
+}
+
+
+static int ip_vs_lblcr_done_svc(struct ip_vs_service *svc)
+{
+ struct ip_vs_lblcr_table *tbl = svc->sched_data;
+
+ /* remove periodic timer */
+ del_timer_sync(&tbl->periodic_timer);
+
+ /* got to clean up table entries here */
+ ip_vs_lblcr_flush(tbl);
+
+ /* release the table itself */
+ kfree(svc->sched_data);
+ IP_VS_DBG(6, "LBLCR hash table (memory=%dbytes) released\n",
+ sizeof(struct ip_vs_lblcr_table));
+
+ return 0;
+}
+
+
+static int ip_vs_lblcr_update_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static inline struct ip_vs_dest *
+__ip_vs_wlc_schedule(struct ip_vs_service *svc, struct iphdr *iph)
+{
+ register struct list_head *l, *e;
+ struct ip_vs_dest *dest, *least;
+ int loh, doh;
+
+ /*
+ * We think the overhead of processing active connections is fifty
+ * times higher than that of inactive connections in average. (This
+ * fifty times might not be accurate, we will change it later.) We
+ * use the following formula to estimate the overhead:
+ * dest->activeconns*50 + dest->inactconns
+ * and the load:
+ * (dest overhead) / dest->weight
+ *
+ * Remember -- no floats in kernel mode!!!
+ * The comparison of h1*w2 > h2*w1 is equivalent to that of
+ * h1/w1 > h2/w2
+ * if every weight is larger than zero.
+ *
+ * The server with weight=0 is quiesced and will not receive any
+ * new connection.
+ */
+
+ l = &svc->destinations;
+ for (e=l->next; e!=l; e=e->next) {
+ least = list_entry(e, struct ip_vs_dest, n_list);
+ if (atomic_read(&least->weight) > 0) {
+ loh = atomic_read(&least->activeconns) * 50
+ + atomic_read(&least->inactconns);
+ goto nextstage;
+ }
+ }
+ return NULL;
+
+ /*
+ * Find the destination with the least load.
+ */
+ nextstage:
+ for (e=e->next; e!=l; e=e->next) {
+ dest = list_entry(e, struct ip_vs_dest, n_list);
+ doh = atomic_read(&dest->activeconns) * 50
+ + atomic_read(&dest->inactconns);
+ if (loh * atomic_read(&dest->weight) >
+ doh * atomic_read(&least->weight)) {
+ least = dest;
+ loh = doh;
+ }
+ }
+
+ IP_VS_DBG(6, "LBLCR: server %d.%d.%d.%d:%d "
+ "activeconns %d refcnt %d weight %d overhead %d\n",
+ NIPQUAD(least->addr), ntohs(least->port),
+ atomic_read(&least->activeconns),
+ atomic_read(&least->refcnt),
+ atomic_read(&least->weight), loh);
+
+ return least;
+}
+
+
+/*
+ * If this destination server is overloaded and there is a less loaded
+ * server, then return true.
+ */
+static inline int
+is_overloaded(struct ip_vs_dest *dest, struct ip_vs_service *svc)
+{
+ if (atomic_read(&dest->activeconns) > atomic_read(&dest->weight)) {
+ register struct list_head *l, *e;
+ struct ip_vs_dest *d;
+
+ l = &svc->destinations;
+ for (e=l->next; e!=l; e=e->next) {
+ d = list_entry(e, struct ip_vs_dest, n_list);
+ if (atomic_read(&d->activeconns)*2
+ < atomic_read(&d->weight)) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+
+/*
+ * Locality-Based (weighted) Least-Connection scheduling
+ */
+static struct ip_vs_dest *
+ip_vs_lblcr_schedule(struct ip_vs_service *svc, struct iphdr *iph)
+{
+ struct ip_vs_dest *dest;
+ struct ip_vs_lblcr_table *tbl;
+ struct ip_vs_lblcr_entry *en;
+
+ IP_VS_DBG(6, "ip_vs_lblcr_schedule(): Scheduling...\n");
+
+ tbl = (struct ip_vs_lblcr_table *)svc->sched_data;
+ en = ip_vs_lblcr_get(tbl, iph->daddr);
+ if (en == NULL) {
+ dest = __ip_vs_wlc_schedule(svc, iph);
+ if (dest == NULL) {
+ IP_VS_DBG(1, "no destination available\n");
+ return NULL;
+ }
+ en = ip_vs_lblcr_new(iph->daddr);
+ if (en == NULL) {
+ return NULL;
+ }
+ ip_vs_dest_set_insert(&en->set, dest);
+ ip_vs_lblcr_hash(tbl, en);
+ } else {
+ dest = ip_vs_dest_set_min(&en->set);
+ if (!dest || is_overloaded(dest, svc)) {
+ dest = __ip_vs_wlc_schedule(svc, iph);
+ if (dest == NULL) {
+ IP_VS_DBG(1, "no destination available\n");
+ return NULL;
+ }
+ ip_vs_dest_set_insert(&en->set, dest);
+ }
+ if (atomic_read(&en->set.size) > 1 &&
+ jiffies-en->set.lastmod > sysctl_ip_vs_lblcr_expiration) {
+ struct ip_vs_dest *m;
+ m = ip_vs_dest_set_max(&en->set);
+ if (m)
+ ip_vs_dest_set_erase(&en->set, m);
+ }
+ }
+ en->lastuse = jiffies;
+
+ IP_VS_DBG(6, "LBLCR: destination IP address %u.%u.%u.%u "
+ "--> server %u.%u.%u.%u:%d\n",
+ NIPQUAD(en->addr),
+ NIPQUAD(dest->addr),
+ ntohs(dest->port));
+
+ return dest;
+}
+
+
+/*
+ * IPVS LBLCR Scheduler structure
+ */
+static struct ip_vs_scheduler ip_vs_lblcr_scheduler =
+{
+ {0}, /* n_list */
+ "lblcr", /* name */
+ ATOMIC_INIT(0), /* refcnt */
+ THIS_MODULE, /* this module */
+ ip_vs_lblcr_init_svc, /* service initializer */
+ ip_vs_lblcr_done_svc, /* service done */
+ ip_vs_lblcr_update_svc, /* service updater */
+ ip_vs_lblcr_schedule, /* select a server from the destination list */
+};
+
+
+static int __init ip_vs_lblcr_init(void)
+{
+ INIT_LIST_HEAD(&ip_vs_lblcr_scheduler.n_list);
+ lblcr_sysctl_table.sysctl_header =
+ register_sysctl_table(lblcr_sysctl_table.root_dir, 0);
+#ifdef CONFIG_IP_VS_LBLCR_DEBUG
+ proc_net_create("ip_vs_lblcr", 0, ip_vs_lblcr_getinfo);
+#endif
+ return register_ip_vs_scheduler(&ip_vs_lblcr_scheduler);
+}
+
+
+static void __exit ip_vs_lblcr_cleanup(void)
+{
+#ifdef CONFIG_IP_VS_LBLCR_DEBUG
+ proc_net_remove("ip_vs_lblcr");
+#endif
+ unregister_sysctl_table(lblcr_sysctl_table.sysctl_header);
+ unregister_ip_vs_scheduler(&ip_vs_lblcr_scheduler);
+}
+
+
+module_init(ip_vs_lblcr_init);
+module_exit(ip_vs_lblcr_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_lc.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_lc.c
new file mode 100644
index 0000000..52f3166
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_lc.c
@@ -0,0 +1,138 @@
+/*
+ * IPVS: Least-Connection Scheduling module
+ *
+ * Version: $Id: ip_vs_lc.c,v 1.8.2.1 2003/04/11 14:02:35 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes:
+ * Wensong Zhang : added the ip_vs_lc_update_svc
+ * Wensong Zhang : added any dest with weight=0 is quiesced
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include <net/ip_vs.h>
+
+
+static int ip_vs_lc_init_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static int ip_vs_lc_done_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static int ip_vs_lc_update_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static inline unsigned int
+ip_vs_lc_dest_overhead(struct ip_vs_dest *dest)
+{
+ /*
+ * We think the overhead of processing active connections is 256
+ * times higher than that of inactive connections in average. (This
+ * 256 times might not be accurate, we will change it later) We
+ * use the following formula to estimate the overhead now:
+ * dest->activeconns*256 + dest->inactconns
+ */
+ return (atomic_read(&dest->activeconns) << 8) +
+ atomic_read(&dest->inactconns);
+}
+
+
+/*
+ * Least Connection scheduling
+ */
+static struct ip_vs_dest *
+ip_vs_lc_schedule(struct ip_vs_service *svc, struct iphdr *iph)
+{
+ struct list_head *l, *e;
+ struct ip_vs_dest *dest, *least;
+ unsigned int loh, doh;
+
+ IP_VS_DBG(6, "ip_vs_lc_schedule(): Scheduling...\n");
+
+ /*
+ * Simply select the server with the least number of
+ * (activeconns<<5) + inactconns
+ * Except whose weight is equal to zero.
+ * If the weight is equal to zero, it means that the server is
+ * quiesced, the existing connections to the server still get
+ * served, but no new connection is assigned to the server.
+ */
+
+ l = &svc->destinations;
+ for (e=l->next; e!=l; e=e->next) {
+ least = list_entry (e, struct ip_vs_dest, n_list);
+ if (atomic_read(&least->weight) > 0) {
+ loh = ip_vs_lc_dest_overhead(least);
+ goto nextstage;
+ }
+ }
+ return NULL;
+
+ /*
+ * Find the destination with the least load.
+ */
+ nextstage:
+ for (e=e->next; e!=l; e=e->next) {
+ dest = list_entry(e, struct ip_vs_dest, n_list);
+ if (atomic_read(&dest->weight) == 0)
+ continue;
+ doh = ip_vs_lc_dest_overhead(dest);
+ if (doh < loh) {
+ least = dest;
+ loh = doh;
+ }
+ }
+
+ IP_VS_DBG(6, "LC: server %u.%u.%u.%u:%u activeconns %d inactconns %d\n",
+ NIPQUAD(least->addr), ntohs(least->port),
+ atomic_read(&least->activeconns),
+ atomic_read(&least->inactconns));
+
+ return least;
+}
+
+
+static struct ip_vs_scheduler ip_vs_lc_scheduler = {
+ {0}, /* n_list */
+ "lc", /* name */
+ ATOMIC_INIT(0), /* refcnt */
+ THIS_MODULE, /* this module */
+ ip_vs_lc_init_svc, /* service initializer */
+ ip_vs_lc_done_svc, /* service done */
+ ip_vs_lc_update_svc, /* service updater */
+ ip_vs_lc_schedule, /* select a server from the destination list */
+};
+
+
+static int __init ip_vs_lc_init(void)
+{
+ INIT_LIST_HEAD(&ip_vs_lc_scheduler.n_list);
+ return register_ip_vs_scheduler(&ip_vs_lc_scheduler) ;
+}
+
+static void __exit ip_vs_lc_cleanup(void)
+{
+ unregister_ip_vs_scheduler(&ip_vs_lc_scheduler);
+}
+
+module_init(ip_vs_lc_init);
+module_exit(ip_vs_lc_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_nq.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_nq.c
new file mode 100644
index 0000000..5ad502e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_nq.c
@@ -0,0 +1,173 @@
+/*
+ * IPVS: Never Queue scheduling module
+ *
+ * Version: $Id: ip_vs_nq.c,v 1.1.2.1 2003/05/20 17:05:02 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes:
+ *
+ */
+
+/*
+ * The NQ algorithm adopts a two-speed model. When there is an idle server
+ * available, the job will be sent to the idle server, instead of waiting
+ * for a fast one. When there is no idle server available, the job will be
+ * sent to the server that minimize its expected delay (The Shortest
+ * Expected Delay scheduling algorithm).
+ *
+ * See the following paper for more information:
+ * A. Weinrib and S. Shenker, Greed is not enough: Adaptive load sharing
+ * in large heterogeneous systems. In Proceedings IEEE INFOCOM'88,
+ * pages 986-994, 1988.
+ *
+ * Thanks must go to Marko Buuri <marko@buuri.name> for talking NQ to me.
+ *
+ * The difference between NQ and SED is that NQ can improve overall
+ * system utilization.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include <net/ip_vs.h>
+
+
+static int
+ip_vs_nq_init_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static int
+ip_vs_nq_done_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static int
+ip_vs_nq_update_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static inline unsigned int
+ip_vs_nq_dest_overhead(struct ip_vs_dest *dest)
+{
+ /*
+ * We only use the active connection number in the cost
+ * calculation here.
+ */
+ return atomic_read(&dest->activeconns) + 1;
+}
+
+
+/*
+ * Weighted Least Connection scheduling
+ */
+static struct ip_vs_dest *
+ip_vs_nq_schedule(struct ip_vs_service *svc, struct iphdr *iph)
+{
+ register struct list_head *l, *e;
+ struct ip_vs_dest *dest, *least;
+ unsigned int loh, doh;
+
+ IP_VS_DBG(6, "ip_vs_nq_schedule(): Scheduling...\n");
+
+ /*
+ * We calculate the load of each dest server as follows:
+ * (server expected overhead) / dest->weight
+ *
+ * Remember -- no floats in kernel mode!!!
+ * The comparison of h1*w2 > h2*w1 is equivalent to that of
+ * h1/w1 > h2/w2
+ * if every weight is larger than zero.
+ *
+ * The server with weight=0 is quiesced and will not receive any
+ * new connections.
+ */
+
+ l = &svc->destinations;
+ for (e=l->next; e!=l; e=e->next) {
+ least = list_entry(e, struct ip_vs_dest, n_list);
+ if (atomic_read(&least->weight) > 0) {
+ loh = ip_vs_nq_dest_overhead(least);
+
+ /* return the server directly if it is idle */
+ if (atomic_read(&least->activeconns) == 0)
+ goto out;
+
+ goto nextstage;
+ }
+ }
+ return NULL;
+
+ /*
+ * Find the destination with the least load.
+ */
+ nextstage:
+ for (e=e->next; e!=l; e=e->next) {
+ dest = list_entry(e, struct ip_vs_dest, n_list);
+ doh = ip_vs_nq_dest_overhead(dest);
+
+ /* return the server directly if it is idle */
+ if (atomic_read(&dest->activeconns) == 0) {
+ least = dest;
+ loh = doh;
+ goto out;
+ }
+
+ if (loh * atomic_read(&dest->weight) >
+ doh * atomic_read(&least->weight)) {
+ least = dest;
+ loh = doh;
+ }
+ }
+
+ out:
+ IP_VS_DBG(6, "NQ: server %u.%u.%u.%u:%u "
+ "activeconns %d refcnt %d weight %d overhead %d\n",
+ NIPQUAD(least->addr), ntohs(least->port),
+ atomic_read(&least->activeconns),
+ atomic_read(&least->refcnt),
+ atomic_read(&least->weight), loh);
+
+ return least;
+}
+
+
+static struct ip_vs_scheduler ip_vs_nq_scheduler =
+{
+ .name = "nq",
+ .refcnt = ATOMIC_INIT(0),
+ .module = THIS_MODULE,
+ .init_service = ip_vs_nq_init_svc,
+ .done_service = ip_vs_nq_done_svc,
+ .update_service = ip_vs_nq_update_svc,
+ .schedule = ip_vs_nq_schedule,
+};
+
+
+static int __init ip_vs_nq_init(void)
+{
+ INIT_LIST_HEAD(&ip_vs_nq_scheduler.n_list);
+ return register_ip_vs_scheduler(&ip_vs_nq_scheduler);
+}
+
+static void __exit ip_vs_nq_cleanup(void)
+{
+ unregister_ip_vs_scheduler(&ip_vs_nq_scheduler);
+}
+
+module_init(ip_vs_nq_init);
+module_exit(ip_vs_nq_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_rr.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_rr.c
new file mode 100644
index 0000000..a7d2f0e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_rr.c
@@ -0,0 +1,116 @@
+/*
+ * IPVS: Round-Robin Scheduling module
+ *
+ * Version: $Id: ip_vs_rr.c,v 1.8 2001/10/19 15:05:17 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
+ * Peter Kese <peter.kese@ijs.si>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Fixes/Changes:
+ * Wensong Zhang : changed the ip_vs_rr_schedule to return dest
+ * Julian Anastasov : fixed the NULL pointer access bug in debugging
+ * Wensong Zhang : changed some comestics things for debugging
+ * Wensong Zhang : changed for the d-linked destination list
+ * Wensong Zhang : added the ip_vs_rr_update_svc
+ * Wensong Zhang : added any dest with weight=0 is quiesced
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include <net/ip_vs.h>
+
+
+static int ip_vs_rr_init_svc(struct ip_vs_service *svc)
+{
+ svc->sched_data = &svc->destinations;
+ return 0;
+}
+
+
+static int ip_vs_rr_done_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static int ip_vs_rr_update_svc(struct ip_vs_service *svc)
+{
+ svc->sched_data = &svc->destinations;
+ return 0;
+}
+
+
+/*
+ * Round-Robin Scheduling
+ */
+static struct ip_vs_dest *
+ip_vs_rr_schedule(struct ip_vs_service *svc, struct iphdr *iph)
+{
+ register struct list_head *p, *q;
+ struct ip_vs_dest *dest;
+
+ IP_VS_DBG(6, "ip_vs_rr_schedule(): Scheduling...\n");
+
+ write_lock(&svc->sched_lock);
+ p = (struct list_head *)svc->sched_data;
+ p = p->next;
+ q = p;
+ do {
+ if (q == &svc->destinations) {
+ q = q->next;
+ continue;
+ }
+ dest = list_entry(q, struct ip_vs_dest, n_list);
+ if (atomic_read(&dest->weight) > 0)
+ /* HIT */
+ goto out;
+ q = q->next;
+ } while (q != p);
+ write_unlock(&svc->sched_lock);
+ return NULL;
+
+ out:
+ svc->sched_data = q;
+ write_unlock(&svc->sched_lock);
+ IP_VS_DBG(6, "RR: server %u.%u.%u.%u:%u "
+ "activeconns %d refcnt %d weight %d\n",
+ NIPQUAD(dest->addr), ntohs(dest->port),
+ atomic_read(&dest->activeconns),
+ atomic_read(&dest->refcnt), atomic_read(&dest->weight));
+
+ return dest;
+}
+
+
+static struct ip_vs_scheduler ip_vs_rr_scheduler = {
+ {0}, /* n_list */
+ "rr", /* name */
+ ATOMIC_INIT(0), /* refcnt */
+ THIS_MODULE, /* this module */
+ ip_vs_rr_init_svc, /* service initializer */
+ ip_vs_rr_done_svc, /* service done */
+ ip_vs_rr_update_svc, /* service updater */
+ ip_vs_rr_schedule, /* select a server from the destination list */
+};
+
+static int __init ip_vs_rr_init(void)
+{
+ INIT_LIST_HEAD(&ip_vs_rr_scheduler.n_list);
+ return register_ip_vs_scheduler(&ip_vs_rr_scheduler);
+}
+
+static void __exit ip_vs_rr_cleanup(void)
+{
+ unregister_ip_vs_scheduler(&ip_vs_rr_scheduler);
+}
+
+module_init(ip_vs_rr_init);
+module_exit(ip_vs_rr_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sched.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sched.c
new file mode 100644
index 0000000..7282b67
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sched.c
@@ -0,0 +1,257 @@
+/*
+ * IPVS An implementation of the IP virtual server support for the
+ * LINUX operating system. IPVS is now implemented as a module
+ * over the Netfilter framework. IPVS can be used to build a
+ * high-performance and highly available server based on a
+ * cluster of servers.
+ *
+ * Version: $Id: ip_vs_sched.c,v 1.11 2001/11/04 08:58:43 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
+ * Peter Kese <peter.kese@ijs.si>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes:
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <asm/softirq.h> /* for local_bh_* */
+#include <asm/string.h>
+#include <linux/kmod.h>
+
+#include <net/ip_vs.h>
+
+/*
+ * IPVS scheduler list
+ */
+static LIST_HEAD(ip_vs_schedulers);
+
+/* lock for service table */
+static rwlock_t __ip_vs_sched_lock = RW_LOCK_UNLOCKED;
+
+
+/*
+ * Bind a service with a scheduler
+ */
+int ip_vs_bind_scheduler(struct ip_vs_service *svc,
+ struct ip_vs_scheduler *scheduler)
+{
+ int ret;
+
+ if (svc == NULL) {
+ IP_VS_ERR("ip_vs_bind_scheduler(): svc arg NULL\n");
+ return -EINVAL;
+ }
+ if (scheduler == NULL) {
+ IP_VS_ERR("ip_vs_bind_scheduler(): scheduler arg NULL\n");
+ return -EINVAL;
+ }
+
+ svc->scheduler = scheduler;
+
+ if (scheduler->init_service) {
+ ret = scheduler->init_service(svc);
+ if (ret) {
+ IP_VS_ERR("ip_vs_bind_scheduler(): init error\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * Unbind a service with its scheduler
+ */
+int ip_vs_unbind_scheduler(struct ip_vs_service *svc)
+{
+ struct ip_vs_scheduler *sched;
+
+ if (svc == NULL) {
+ IP_VS_ERR("ip_vs_unbind_scheduler(): svc arg NULL\n");
+ return -EINVAL;
+ }
+
+ sched = svc->scheduler;
+ if (sched == NULL) {
+ IP_VS_ERR("ip_vs_unbind_scheduler(): svc isn't bound\n");
+ return -EINVAL;
+ }
+
+ if (sched->done_service) {
+ if (sched->done_service(svc) != 0) {
+ IP_VS_ERR("ip_vs_unbind_scheduler(): done error\n");
+ return -EINVAL;
+ }
+ }
+
+ svc->scheduler = NULL;
+ return 0;
+}
+
+
+/*
+ * Get scheduler in the scheduler list by name
+ */
+static struct ip_vs_scheduler *ip_vs_sched_getbyname(const char *sched_name)
+{
+ struct ip_vs_scheduler *sched;
+ struct list_head *l, *e;
+
+ IP_VS_DBG(2, "ip_vs_sched_getbyname(): sched_name \"%s\"\n",
+ sched_name);
+
+ l = &ip_vs_schedulers;
+
+ read_lock_bh(&__ip_vs_sched_lock);
+
+ for (e=l->next; e!=l; e=e->next) {
+ sched = list_entry(e, struct ip_vs_scheduler, n_list);
+
+ /*
+ * Test and MOD_INC_USE_COUNT atomically
+ */
+ if (sched->module && !try_inc_mod_count(sched->module)) {
+ /*
+ * This scheduler is just deleted
+ */
+ continue;
+ }
+ if (strcmp(sched_name, sched->name)==0) {
+ /* HIT */
+ read_unlock_bh(&__ip_vs_sched_lock);
+ return sched;
+ }
+ if (sched->module)
+ __MOD_DEC_USE_COUNT(sched->module);
+ }
+
+ read_unlock_bh(&__ip_vs_sched_lock);
+ return NULL;
+}
+
+
+/*
+ * Lookup scheduler and try to load it if it doesn't exist
+ */
+struct ip_vs_scheduler *ip_vs_scheduler_get(const char *sched_name)
+{
+ struct ip_vs_scheduler *sched;
+
+ /*
+ * Search for the scheduler by sched_name
+ */
+ sched = ip_vs_sched_getbyname(sched_name);
+
+ /*
+ * If scheduler not found, load the module and search again
+ */
+ if (sched == NULL) {
+ char module_name[IP_VS_SCHEDNAME_MAXLEN+8];
+ snprintf(module_name, sizeof(module_name), "ip_vs_%s", sched_name);
+ request_module(module_name);
+ sched = ip_vs_sched_getbyname(sched_name);
+ }
+
+ return sched;
+}
+
+void ip_vs_scheduler_put(struct ip_vs_scheduler *scheduler)
+{
+ if (scheduler->module)
+ __MOD_DEC_USE_COUNT(scheduler->module);
+}
+
+
+/*
+ * Register a scheduler in the scheduler list
+ */
+int register_ip_vs_scheduler(struct ip_vs_scheduler *scheduler)
+{
+ struct ip_vs_scheduler *sched;
+
+ if (!scheduler) {
+ IP_VS_ERR("register_ip_vs_scheduler(): NULL arg\n");
+ return -EINVAL;
+ }
+
+ if (!scheduler->name) {
+ IP_VS_ERR("register_ip_vs_scheduler(): NULL scheduler_name\n");
+ return -EINVAL;
+ }
+
+ MOD_INC_USE_COUNT;
+
+ /*
+ * Make sure that the scheduler with this name doesn't exist
+ * in the scheduler list.
+ */
+ sched = ip_vs_sched_getbyname(scheduler->name);
+ if (sched) {
+ ip_vs_scheduler_put(sched);
+ MOD_DEC_USE_COUNT;
+ IP_VS_ERR("register_ip_vs_scheduler(): [%s] scheduler "
+ "already existed in the system\n", scheduler->name);
+ return -EINVAL;
+ }
+
+ write_lock_bh(&__ip_vs_sched_lock);
+
+ if (scheduler->n_list.next != &scheduler->n_list) {
+ write_unlock_bh(&__ip_vs_sched_lock);
+ MOD_DEC_USE_COUNT;
+ IP_VS_ERR("register_ip_vs_scheduler(): [%s] scheduler "
+ "already linked\n", scheduler->name);
+ return -EINVAL;
+ }
+
+ /*
+ * Add it into the d-linked scheduler list
+ */
+ list_add(&scheduler->n_list, &ip_vs_schedulers);
+ write_unlock_bh(&__ip_vs_sched_lock);
+
+ IP_VS_INFO("[%s] scheduler registered.\n", scheduler->name);
+
+ return 0;
+}
+
+
+/*
+ * Unregister a scheduler from the scheduler list
+ */
+int unregister_ip_vs_scheduler(struct ip_vs_scheduler *scheduler)
+{
+ if (!scheduler) {
+ IP_VS_ERR( "unregister_ip_vs_scheduler(): NULL arg\n");
+ return -EINVAL;
+ }
+
+ write_lock_bh(&__ip_vs_sched_lock);
+ if (scheduler->n_list.next == &scheduler->n_list) {
+ write_unlock_bh(&__ip_vs_sched_lock);
+ IP_VS_ERR("unregister_ip_vs_scheduler(): [%s] scheduler "
+ "is not in the list. failed\n", scheduler->name);
+ return -EINVAL;
+ }
+
+ /*
+ * Remove it from the d-linked scheduler list
+ */
+ list_del(&scheduler->n_list);
+ write_unlock_bh(&__ip_vs_sched_lock);
+
+ MOD_DEC_USE_COUNT;
+
+ IP_VS_INFO("[%s] scheduler unregistered.\n", scheduler->name);
+
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sed.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sed.c
new file mode 100644
index 0000000..55d9ef6
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sed.c
@@ -0,0 +1,163 @@
+/*
+ * IPVS: Shortest Expected Delay scheduling module
+ *
+ * Version: $Id: ip_vs_sed.c,v 1.1.2.1 2003/05/20 17:05:02 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes:
+ *
+ */
+
+/*
+ * The SED algorithm attempts to minimize each job's expected delay until
+ * completion. The expected delay that the job will experience is
+ * (Ci + 1) / Ui if sent to the ith server, in which Ci is the number of
+ * jobs on the the ith server and Ui is the fixed service rate (weight) of
+ * the ith server. The SED algorithm adopts a greedy policy that each does
+ * what is in its own best interest, i.e. to join the queue which would
+ * minimize its expected delay of completion.
+ *
+ * See the following paper for more information:
+ * A. Weinrib and S. Shenker, Greed is not enough: Adaptive load sharing
+ * in large heterogeneous systems. In Proceedings IEEE INFOCOM'88,
+ * pages 986-994, 1988.
+ *
+ * Thanks must go to Marko Buuri <marko@buuri.name> for talking SED to me.
+ *
+ * The difference between SED and WLC is that SED includes the incoming
+ * job in the cost function (the increment of 1). SED may outperform
+ * WLC, while scheduling big jobs under larger heterogeneous systems
+ * (the server weight varies a lot).
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include <net/ip_vs.h>
+
+
+static int
+ip_vs_sed_init_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static int
+ip_vs_sed_done_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static int
+ip_vs_sed_update_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static inline unsigned int
+ip_vs_sed_dest_overhead(struct ip_vs_dest *dest)
+{
+ /*
+ * We only use the active connection number in the cost
+ * calculation here.
+ */
+ return atomic_read(&dest->activeconns) + 1;
+}
+
+
+/*
+ * Weighted Least Connection scheduling
+ */
+static struct ip_vs_dest *
+ip_vs_sed_schedule(struct ip_vs_service *svc, struct iphdr *iph)
+{
+ register struct list_head *l, *e;
+ struct ip_vs_dest *dest, *least;
+ unsigned int loh, doh;
+
+ IP_VS_DBG(6, "ip_vs_sed_schedule(): Scheduling...\n");
+
+ /*
+ * We calculate the load of each dest server as follows:
+ * (server expected overhead) / dest->weight
+ *
+ * Remember -- no floats in kernel mode!!!
+ * The comparison of h1*w2 > h2*w1 is equivalent to that of
+ * h1/w1 > h2/w2
+ * if every weight is larger than zero.
+ *
+ * The server with weight=0 is quiesced and will not receive any
+ * new connections.
+ */
+
+ l = &svc->destinations;
+ for (e=l->next; e!=l; e=e->next) {
+ least = list_entry(e, struct ip_vs_dest, n_list);
+ if (atomic_read(&least->weight) > 0) {
+ loh = ip_vs_sed_dest_overhead(least);
+ goto nextstage;
+ }
+ }
+ return NULL;
+
+ /*
+ * Find the destination with the least load.
+ */
+ nextstage:
+ for (e=e->next; e!=l; e=e->next) {
+ dest = list_entry(e, struct ip_vs_dest, n_list);
+ doh = ip_vs_sed_dest_overhead(dest);
+ if (loh * atomic_read(&dest->weight) >
+ doh * atomic_read(&least->weight)) {
+ least = dest;
+ loh = doh;
+ }
+ }
+
+ IP_VS_DBG(6, "SED: server %u.%u.%u.%u:%u "
+ "activeconns %d refcnt %d weight %d overhead %d\n",
+ NIPQUAD(least->addr), ntohs(least->port),
+ atomic_read(&least->activeconns),
+ atomic_read(&least->refcnt),
+ atomic_read(&least->weight), loh);
+
+ return least;
+}
+
+
+static struct ip_vs_scheduler ip_vs_sed_scheduler =
+{
+ .name = "sed",
+ .refcnt = ATOMIC_INIT(0),
+ .module = THIS_MODULE,
+ .init_service = ip_vs_sed_init_svc,
+ .done_service = ip_vs_sed_done_svc,
+ .update_service = ip_vs_sed_update_svc,
+ .schedule = ip_vs_sed_schedule,
+};
+
+
+static int __init ip_vs_sed_init(void)
+{
+ INIT_LIST_HEAD(&ip_vs_sed_scheduler.n_list);
+ return register_ip_vs_scheduler(&ip_vs_sed_scheduler);
+}
+
+static void __exit ip_vs_sed_cleanup(void)
+{
+ unregister_ip_vs_scheduler(&ip_vs_sed_scheduler);
+}
+
+module_init(ip_vs_sed_init);
+module_exit(ip_vs_sed_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sh.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sh.c
new file mode 100644
index 0000000..915f8fc
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sh.c
@@ -0,0 +1,258 @@
+/*
+ * IPVS: Source Hashing scheduling module
+ *
+ * Version: $Id: ip_vs_sh.c,v 1.4 2001/10/19 15:05:17 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@gnuchina.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes:
+ *
+ */
+
+/*
+ * The sh algorithm is to select server by the hash key of source IP
+ * address. The pseudo code is as follows:
+ *
+ * n <- servernode[src_ip];
+ * if (n is dead) OR
+ * (n is overloaded, such as n.conns>2*n.weight) then
+ * return NULL;
+ *
+ * return n;
+ *
+ * Notes that servernode is a 256-bucket hash table that maps the hash
+ * index derived from packet source IP address to the current server
+ * array. If the sh scheduler is used in cache cluster, it is good to
+ * combine it with cache_bypass feature. When the statically assigned
+ * server is dead or overloaded, the load balancer can bypass the cache
+ * server and send requests to the original server directly.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include <net/ip_vs.h>
+
+
+/*
+ * IPVS SH bucket
+ */
+struct ip_vs_sh_bucket {
+ struct ip_vs_dest *dest; /* real server (cache) */
+};
+
+/*
+ * for IPVS SH entry hash table
+ */
+#ifndef CONFIG_IP_VS_SH_TAB_BITS
+#define CONFIG_IP_VS_SH_TAB_BITS 8
+#endif
+#define IP_VS_SH_TAB_BITS CONFIG_IP_VS_SH_TAB_BITS
+#define IP_VS_SH_TAB_SIZE (1 << IP_VS_SH_TAB_BITS)
+#define IP_VS_SH_TAB_MASK (IP_VS_SH_TAB_SIZE - 1)
+
+
+/*
+ * Returns hash value for IPVS SH entry
+ */
+static inline unsigned ip_vs_sh_hashkey(__u32 addr)
+{
+ return (ntohl(addr)*2654435761UL) & IP_VS_SH_TAB_MASK;
+}
+
+
+/*
+ * Get ip_vs_dest associated with supplied parameters.
+ */
+static inline struct ip_vs_dest *
+ip_vs_sh_get(struct ip_vs_sh_bucket *tbl, __u32 addr)
+{
+ return (tbl[ip_vs_sh_hashkey(addr)]).dest;
+}
+
+
+/*
+ * Assign all the hash buckets of the specified table with the service.
+ */
+static int
+ip_vs_sh_assign(struct ip_vs_sh_bucket *tbl, struct ip_vs_service *svc)
+{
+ int i;
+ struct ip_vs_sh_bucket *b;
+ struct list_head *p;
+ struct ip_vs_dest *dest;
+
+ b = tbl;
+ p = &svc->destinations;
+ for (i=0; i<IP_VS_SH_TAB_SIZE; i++) {
+ if (list_empty(p)) {
+ b->dest = NULL;
+ } else {
+ if (p == &svc->destinations)
+ p = p->next;
+
+ dest = list_entry(p, struct ip_vs_dest, n_list);
+ atomic_inc(&dest->refcnt);
+ b->dest = dest;
+
+ p = p->next;
+ }
+ b++;
+ }
+ return 0;
+}
+
+
+/*
+ * Flush all the hash buckets of the specified table.
+ */
+static void ip_vs_sh_flush(struct ip_vs_sh_bucket *tbl)
+{
+ int i;
+ struct ip_vs_sh_bucket *b;
+
+ b = tbl;
+ for (i=0; i<IP_VS_SH_TAB_SIZE; i++) {
+ if (b->dest) {
+ atomic_dec(&b->dest->refcnt);
+ b->dest = NULL;
+ }
+ b++;
+ }
+}
+
+
+static int ip_vs_sh_init_svc(struct ip_vs_service *svc)
+{
+ struct ip_vs_sh_bucket *tbl;
+
+ /* allocate the SH table for this service */
+ tbl = kmalloc(sizeof(struct ip_vs_sh_bucket)*IP_VS_SH_TAB_SIZE,
+ GFP_ATOMIC);
+ if (tbl == NULL) {
+ IP_VS_ERR("ip_vs_sh_init_svc(): no memory\n");
+ return -ENOMEM;
+ }
+ svc->sched_data = tbl;
+ IP_VS_DBG(6, "SH hash table (memory=%dbytes) allocated for "
+ "current service\n",
+ sizeof(struct ip_vs_sh_bucket)*IP_VS_SH_TAB_SIZE);
+
+ /* assign the hash buckets with the updated service */
+ ip_vs_sh_assign(tbl, svc);
+
+ return 0;
+}
+
+
+static int ip_vs_sh_done_svc(struct ip_vs_service *svc)
+{
+ struct ip_vs_sh_bucket *tbl = svc->sched_data;
+
+ /* got to clean up hash buckets here */
+ ip_vs_sh_flush(tbl);
+
+ /* release the table itself */
+ kfree(svc->sched_data);
+ IP_VS_DBG(6, "SH hash table (memory=%dbytes) released\n",
+ sizeof(struct ip_vs_sh_bucket)*IP_VS_SH_TAB_SIZE);
+
+ return 0;
+}
+
+
+static int ip_vs_sh_update_svc(struct ip_vs_service *svc)
+{
+ struct ip_vs_sh_bucket *tbl = svc->sched_data;
+
+ /* got to clean up hash buckets here */
+ ip_vs_sh_flush(tbl);
+
+ /* assign the hash buckets with the updated service */
+ ip_vs_sh_assign(tbl, svc);
+
+ return 0;
+}
+
+
+/*
+ * If the number of active connections is twice larger than its weight,
+ * consider that the server is overloaded here.
+ */
+static inline int is_overloaded(struct ip_vs_dest *dest)
+{
+ if (atomic_read(&dest->activeconns) > atomic_read(&dest->weight)*2) {
+ return 1;
+ }
+ return 0;
+}
+
+
+/*
+ * Source Hashing scheduling
+ */
+static struct ip_vs_dest *
+ip_vs_sh_schedule(struct ip_vs_service *svc, struct iphdr *iph)
+{
+ struct ip_vs_dest *dest;
+ struct ip_vs_sh_bucket *tbl;
+
+ IP_VS_DBG(6, "ip_vs_sh_schedule(): Scheduling...\n");
+
+ tbl = (struct ip_vs_sh_bucket *)svc->sched_data;
+ dest = ip_vs_sh_get(tbl, iph->saddr);
+ if (!dest
+ || !(dest->flags & IP_VS_DEST_F_AVAILABLE)
+ || atomic_read(&dest->weight) <= 0
+ || is_overloaded(dest)) {
+ return NULL;
+ }
+
+ IP_VS_DBG(6, "SH: source IP address %u.%u.%u.%u "
+ "--> server %u.%u.%u.%u:%d\n",
+ NIPQUAD(iph->saddr),
+ NIPQUAD(dest->addr),
+ ntohs(dest->port));
+
+ return dest;
+}
+
+
+/*
+ * IPVS SH Scheduler structure
+ */
+static struct ip_vs_scheduler ip_vs_sh_scheduler =
+{
+ {0}, /* n_list */
+ "sh", /* name */
+ ATOMIC_INIT(0), /* refcnt */
+ THIS_MODULE, /* this module */
+ ip_vs_sh_init_svc, /* service initializer */
+ ip_vs_sh_done_svc, /* service done */
+ ip_vs_sh_update_svc, /* service updater */
+ ip_vs_sh_schedule, /* select a server from the destination list */
+};
+
+
+static int __init ip_vs_sh_init(void)
+{
+ INIT_LIST_HEAD(&ip_vs_sh_scheduler.n_list);
+ return register_ip_vs_scheduler(&ip_vs_sh_scheduler);
+}
+
+
+static void __exit ip_vs_sh_cleanup(void)
+{
+ unregister_ip_vs_scheduler(&ip_vs_sh_scheduler);
+}
+
+
+module_init(ip_vs_sh_init);
+module_exit(ip_vs_sh_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sync.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sync.c
new file mode 100644
index 0000000..e61fff2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_sync.c
@@ -0,0 +1,864 @@
+/*
+ * IPVS An implementation of the IP virtual server support for the
+ * LINUX operating system. IPVS is now implemented as a module
+ * over the NetFilter framework. IPVS can be used to build a
+ * high-performance and highly available server based on a
+ * cluster of servers.
+ *
+ * Version: $Id: ip_vs_sync.c,v 1.8 2002/08/17 14:06:02 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
+ *
+ * ip_vs_sync: sync connection info from master load balancer to backups
+ * through multicast
+ *
+ * Changes:
+ * Alexandre Cassen : Added master & backup support at a time.
+ * Alexandre Cassen : Added SyncID support for incoming sync
+ * messages filtering.
+ * Justin Ossevoort : Fix endian problem on sync message size.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/net.h>
+
+#include <linux/skbuff.h>
+#include <linux/in.h>
+#include <linux/igmp.h> /* for ip_mc_join_group */
+
+#include <net/ip.h>
+#include <net/sock.h>
+#include <asm/uaccess.h> /* for get_fs and set_fs */
+
+#include <net/ip_vs.h>
+
+#define IP_VS_SYNC_GROUP 0xe0000051 /* multicast addr - 224.0.0.81 */
+#define IP_VS_SYNC_PORT 8848 /* multicast port */
+
+
+/*
+ * IPVS sync connection entry
+ */
+struct ip_vs_sync_conn {
+ __u8 reserved;
+
+ /* Protocol, addresses and port numbers */
+ __u8 protocol; /* Which protocol (TCP/UDP) */
+ __u16 cport;
+ __u16 vport;
+ __u16 dport;
+ __u32 caddr; /* client address */
+ __u32 vaddr; /* virtual address */
+ __u32 daddr; /* destination address */
+
+ /* Flags and state transition */
+ __u16 flags; /* status flags */
+ __u16 state; /* state info */
+
+ /* The sequence options start here */
+};
+
+struct ip_vs_sync_conn_options {
+ struct ip_vs_seq in_seq; /* incoming seq. struct */
+ struct ip_vs_seq out_seq; /* outgoing seq. struct */
+};
+
+#define IP_VS_SYNC_CONN_TIMEOUT (3*60*HZ)
+#define SIMPLE_CONN_SIZE (sizeof(struct ip_vs_sync_conn))
+#define FULL_CONN_SIZE \
+(sizeof(struct ip_vs_sync_conn) + sizeof(struct ip_vs_sync_conn_options))
+
+
+/*
+ The master mulitcasts messages to the backup load balancers in the
+ following format.
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Count Conns | Sync ID | Size |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ | IPVS Sync Connection (1) |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | . |
+ | . |
+ | . |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ | IPVS Sync Connection (n) |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ Count Conns : Number of IPVS sync Connection entries.
+ Sync ID : IPVS sync group we belong to.
+ Size : Size of packet.
+
+*/
+#define SYNC_MESG_MAX_SIZE (24*50+4)
+struct ip_vs_sync_mesg {
+ __u8 nr_conns;
+ __u8 syncid;
+ __u16 size;
+
+ /* ip_vs_sync_conn entries start here */
+};
+
+
+struct ip_vs_sync_buff {
+ struct list_head list;
+ unsigned long firstuse;
+
+ /* pointers for the message data */
+ struct ip_vs_sync_mesg *mesg;
+ unsigned char *head;
+ unsigned char *end;
+};
+
+
+/* the sync_buff list head and the lock */
+static LIST_HEAD(ip_vs_sync_queue);
+static spinlock_t ip_vs_sync_lock = SPIN_LOCK_UNLOCKED;
+
+/* current sync_buff for accepting new conn entries */
+static struct ip_vs_sync_buff *curr_sb = NULL;
+static spinlock_t curr_sb_lock = SPIN_LOCK_UNLOCKED;
+
+/* ipvs sync daemon state */
+volatile int ip_vs_sync_state = IP_VS_STATE_NONE;
+volatile int ip_vs_master_syncid = 0;
+volatile int ip_vs_backup_syncid = 0;
+
+/* multicast interface name */
+char ip_vs_mcast_master_ifn[IP_VS_IFNAME_MAXLEN];
+char ip_vs_mcast_backup_ifn[IP_VS_IFNAME_MAXLEN];
+
+/* multicast addr */
+static struct sockaddr_in mcast_addr;
+
+static inline void sb_queue_tail(struct ip_vs_sync_buff *sb)
+{
+ spin_lock(&ip_vs_sync_lock);
+ list_add_tail(&sb->list, &ip_vs_sync_queue);
+ spin_unlock(&ip_vs_sync_lock);
+}
+
+static inline struct ip_vs_sync_buff * sb_dequeue(void)
+{
+ struct ip_vs_sync_buff *sb;
+
+ spin_lock_bh(&ip_vs_sync_lock);
+ if (list_empty(&ip_vs_sync_queue)) {
+ sb = NULL;
+ } else {
+ sb = list_entry(ip_vs_sync_queue.next,
+ struct ip_vs_sync_buff,
+ list);
+ list_del(&sb->list);
+ }
+ spin_unlock_bh(&ip_vs_sync_lock);
+
+ return sb;
+}
+
+static inline struct ip_vs_sync_buff * ip_vs_sync_buff_create(void)
+{
+ struct ip_vs_sync_buff *sb;
+
+ if (!(sb=kmalloc(sizeof(struct ip_vs_sync_buff), GFP_ATOMIC)))
+ return NULL;
+
+ if (!(sb->mesg=kmalloc(SYNC_MESG_MAX_SIZE, GFP_ATOMIC))) {
+ kfree(sb);
+ return NULL;
+ }
+ sb->mesg->nr_conns = 0;
+ sb->mesg->syncid = ip_vs_master_syncid;
+ sb->mesg->size = 4;
+ sb->head = (unsigned char *)sb->mesg + 4;
+ sb->end = (unsigned char *)sb->mesg + SYNC_MESG_MAX_SIZE;
+ sb->firstuse = jiffies;
+ return sb;
+}
+
+static inline void ip_vs_sync_buff_release(struct ip_vs_sync_buff *sb)
+{
+ kfree(sb->mesg);
+ kfree(sb);
+}
+
+/*
+ * Get the current sync buffer if it has been created for more
+ * than the specified time or the specified time is zero.
+ */
+static inline struct ip_vs_sync_buff *
+get_curr_sync_buff(unsigned long time)
+{
+ struct ip_vs_sync_buff *sb;
+
+ spin_lock_bh(&curr_sb_lock);
+ if (curr_sb &&
+ (jiffies - curr_sb->firstuse > time || time == 0)) {
+ sb = curr_sb;
+ curr_sb = NULL;
+ } else
+ sb = NULL;
+ spin_unlock_bh(&curr_sb_lock);
+ return sb;
+}
+
+
+/*
+ * Add an ip_vs_conn information into the current sync_buff.
+ * Called by ip_vs_in.
+ */
+void ip_vs_sync_conn(struct ip_vs_conn *cp)
+{
+ struct ip_vs_sync_mesg *m;
+ struct ip_vs_sync_conn *s;
+ int len;
+
+ spin_lock(&curr_sb_lock);
+ if (!curr_sb) {
+ if (!(curr_sb=ip_vs_sync_buff_create())) {
+ spin_unlock(&curr_sb_lock);
+ IP_VS_ERR("ip_vs_sync_buff_create failed.\n");
+ return;
+ }
+ }
+
+ len = (cp->flags & IP_VS_CONN_F_SEQ_MASK) ? FULL_CONN_SIZE :
+ SIMPLE_CONN_SIZE;
+ m = curr_sb->mesg;
+ s = (struct ip_vs_sync_conn *)curr_sb->head;
+
+ /* copy members */
+ s->protocol = cp->protocol;
+ s->cport = cp->cport;
+ s->vport = cp->vport;
+ s->dport = cp->dport;
+ s->caddr = cp->caddr;
+ s->vaddr = cp->vaddr;
+ s->daddr = cp->daddr;
+ s->flags = htons(cp->flags & ~IP_VS_CONN_F_HASHED);
+ s->state = htons(cp->state);
+ if (cp->flags & IP_VS_CONN_F_SEQ_MASK) {
+ struct ip_vs_sync_conn_options *opt =
+ (struct ip_vs_sync_conn_options *)&s[1];
+ memcpy(opt, &cp->in_seq, sizeof(*opt));
+ }
+
+ m->nr_conns++;
+ m->size += len;
+ curr_sb->head += len;
+
+ /* check if there is a space for next one */
+ if (curr_sb->head+FULL_CONN_SIZE > curr_sb->end) {
+ sb_queue_tail(curr_sb);
+ curr_sb = NULL;
+ }
+ spin_unlock(&curr_sb_lock);
+
+ /* synchronize its controller if it has */
+ if (cp->control)
+ ip_vs_sync_conn(cp->control);
+}
+
+
+/*
+ * Process received multicast message and create the corresponding
+ * ip_vs_conn entries.
+ */
+static void ip_vs_process_message(const char *buffer, const size_t buflen)
+{
+ struct ip_vs_sync_mesg *m = (struct ip_vs_sync_mesg *)buffer;
+ struct ip_vs_sync_conn *s;
+ struct ip_vs_sync_conn_options *opt;
+ struct ip_vs_conn *cp;
+ char *p;
+ int i;
+
+ /* Convert size back to host byte order */
+ m->size = ntohs(m->size);
+
+ if (buflen != m->size) {
+ IP_VS_ERR("bogus message\n");
+ return;
+ }
+
+ /* SyncID sanity check */
+ if (ip_vs_backup_syncid != 0 && m->syncid != ip_vs_backup_syncid) {
+ IP_VS_DBG(7, "Ignoring incoming msg with syncid = %d\n",
+ m->syncid);
+ return;
+ }
+
+ p = (char *)buffer + sizeof(struct ip_vs_sync_mesg);
+ for (i=0; i<m->nr_conns; i++) {
+ s = (struct ip_vs_sync_conn *)p;
+ cp = ip_vs_conn_in_get(s->protocol,
+ s->caddr, s->cport,
+ s->vaddr, s->vport);
+ if (!cp) {
+ cp = ip_vs_conn_new(s->protocol,
+ s->caddr, s->cport,
+ s->vaddr, s->vport,
+ s->daddr, s->dport,
+ ntohs(s->flags), NULL);
+ if (!cp) {
+ IP_VS_ERR("ip_vs_conn_new failed\n");
+ return;
+ }
+ cp->state = ntohs(s->state);
+ } else if (!cp->dest) {
+ /* it is an entry created by the synchronization */
+ cp->state = ntohs(s->state);
+ cp->flags = ntohs(s->flags) | IP_VS_CONN_F_HASHED;
+ } /* Note that we don't touch its state and flags
+ if it is a normal entry. */
+
+ if (ntohs(s->flags) & IP_VS_CONN_F_SEQ_MASK) {
+ opt = (struct ip_vs_sync_conn_options *)&s[1];
+ memcpy(&cp->in_seq, opt, sizeof(*opt));
+ p += FULL_CONN_SIZE;
+ } else
+ p += SIMPLE_CONN_SIZE;
+
+ atomic_set(&cp->in_pkts, sysctl_ip_vs_sync_threshold);
+ cp->timeout = IP_VS_SYNC_CONN_TIMEOUT;
+ ip_vs_conn_put(cp);
+
+ if (p > buffer+buflen) {
+ IP_VS_ERR("bogus message\n");
+ return;
+ }
+ }
+}
+
+
+/*
+ * Setup loopback of outgoing multicasts on a sending socket
+ */
+static void set_mcast_loop(struct sock *sk, u_char loop)
+{
+ /* setsockopt(sock, SOL_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)); */
+ lock_sock(sk);
+ sk->protinfo.af_inet.mc_loop = loop ? 1 : 0;
+ release_sock(sk);
+}
+
+/*
+ * Specify TTL for outgoing multicasts on a sending socket
+ */
+static void set_mcast_ttl(struct sock *sk, u_char ttl)
+{
+ /* setsockopt(sock, SOL_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); */
+ lock_sock(sk);
+ sk->protinfo.af_inet.mc_ttl = ttl;
+ release_sock(sk);
+}
+
+/*
+ * Specifiy default interface for outgoing multicasts
+ */
+static int set_mcast_if(struct sock *sk, char *ifname)
+{
+ struct net_device *dev;
+
+ if ((dev = __dev_get_by_name(ifname)) == NULL)
+ return -ENODEV;
+
+ if (sk->bound_dev_if && dev->ifindex != sk->bound_dev_if)
+ return -EINVAL;
+
+ lock_sock(sk);
+ sk->protinfo.af_inet.mc_index = dev->ifindex;
+ /* sk->protinfo.af_inet.mc_addr = 0; */
+ release_sock(sk);
+
+ return 0;
+}
+
+/*
+ * Join a multicast group.
+ * the group is specified by a class D multicast address 224.0.0.0/8
+ * in the in_addr structure passed in as a parameter.
+ */
+static int
+join_mcast_group(struct sock *sk, struct in_addr *addr, char *ifname)
+{
+ struct ip_mreqn mreq;
+ struct net_device *dev;
+ int ret;
+
+ memset(&mreq, 0, sizeof(mreq));
+ memcpy(&mreq.imr_multiaddr, addr, sizeof(struct in_addr));
+
+ if ((dev = __dev_get_by_name(ifname)) == NULL)
+ return -ENODEV;
+ if (sk->bound_dev_if && dev->ifindex != sk->bound_dev_if)
+ return -EINVAL;
+
+ mreq.imr_ifindex = dev->ifindex;
+
+ lock_sock(sk);
+ ret = ip_mc_join_group(sk, &mreq);
+ release_sock(sk);
+
+ return ret;
+}
+
+
+static int bind_mcastif_addr(struct socket *sock, char *ifname)
+{
+ struct net_device *dev;
+ u32 addr;
+ struct sockaddr_in sin;
+
+ if ((dev = __dev_get_by_name(ifname)) == NULL)
+ return -ENODEV;
+
+ addr = inet_select_addr(dev, 0, RT_SCOPE_UNIVERSE);
+ if (!addr)
+ IP_VS_ERR("You probably need to specify IP address on "
+ "multicast interface.\n");
+
+ IP_VS_DBG(7, "binding socket with (%s) %u.%u.%u.%u\n",
+ ifname, NIPQUAD(addr));
+
+ /* Now bind the socket with the address of multicast interface */
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = addr;
+ sin.sin_port = 0;
+
+ return sock->ops->bind(sock, (struct sockaddr*)&sin, sizeof(sin));
+}
+
+/*
+ * Set up sending multicast socket over UDP
+ */
+static struct socket * make_send_sock(void)
+{
+ struct socket *sock;
+
+ /* First create a socket */
+ if (sock_create(PF_INET, SOCK_DGRAM, IPPROTO_UDP, &sock) < 0) {
+ IP_VS_ERR("Error during creation of socket; terminating\n");
+ return NULL;
+ }
+
+ if (set_mcast_if(sock->sk, ip_vs_mcast_master_ifn) < 0) {
+ IP_VS_ERR("Error setting outbound mcast interface\n");
+ goto error;
+ }
+
+ set_mcast_loop(sock->sk, 0);
+ set_mcast_ttl(sock->sk, 1);
+
+ if (bind_mcastif_addr(sock, ip_vs_mcast_master_ifn) < 0) {
+ IP_VS_ERR("Error binding address of the mcast interface\n");
+ goto error;
+ }
+
+ if (sock->ops->connect(sock,
+ (struct sockaddr*)&mcast_addr,
+ sizeof(struct sockaddr), 0) < 0) {
+ IP_VS_ERR("Error connecting to the multicast addr\n");
+ goto error;
+ }
+
+ return sock;
+
+ error:
+ sock_release(sock);
+ return NULL;
+}
+
+
+/*
+ * Set up receiving multicast socket over UDP
+ */
+static struct socket * make_receive_sock(void)
+{
+ struct socket *sock;
+
+ /* First create a socket */
+ if (sock_create(PF_INET, SOCK_DGRAM, IPPROTO_UDP, &sock) < 0) {
+ IP_VS_ERR("Error during creation of socket; terminating\n");
+ return NULL;
+ }
+
+ /* it is equivalent to the REUSEADDR option in user-space */
+ sock->sk->reuse = 1;
+
+ if (sock->ops->bind(sock,
+ (struct sockaddr*)&mcast_addr,
+ sizeof(struct sockaddr)) < 0) {
+ IP_VS_ERR("Error binding to the multicast addr\n");
+ goto error;
+ }
+
+ /* join the multicast group */
+ if (join_mcast_group(sock->sk,
+ (struct in_addr*)&mcast_addr.sin_addr,
+ ip_vs_mcast_backup_ifn) < 0) {
+ IP_VS_ERR("Error joining to the multicast group\n");
+ goto error;
+ }
+
+ return sock;
+
+ error:
+ sock_release(sock);
+ return NULL;
+}
+
+
+static int
+ip_vs_send_async(struct socket *sock, const char *buffer, const size_t length)
+{
+ struct msghdr msg;
+ mm_segment_t oldfs;
+ struct iovec iov;
+ int len;
+
+ EnterFunction(7);
+ iov.iov_base = (void *)buffer;
+ iov.iov_len = length;
+ msg.msg_name = 0;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = MSG_DONTWAIT|MSG_NOSIGNAL;
+
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ len = sock_sendmsg(sock, &msg, (size_t)(length));
+ set_fs(oldfs);
+
+ LeaveFunction(7);
+ return len;
+}
+
+static void
+ip_vs_send_sync_msg(struct socket *sock, struct ip_vs_sync_mesg *msg)
+{
+ int msize;
+
+ msize = msg->size;
+
+ /* Put size in network byte order */
+ msg->size = htons(msg->size);
+
+ if (ip_vs_send_async(sock, (char *)msg, msize) != msize)
+ IP_VS_ERR("ip_vs_send_async error\n");
+}
+
+static int
+ip_vs_receive(struct socket *sock, char *buffer, const size_t buflen)
+{
+ struct msghdr msg;
+ struct iovec iov;
+ int len;
+ mm_segment_t oldfs;
+
+ EnterFunction(7);
+
+ /* Receive a packet */
+ iov.iov_base = buffer;
+ iov.iov_len = (size_t)buflen;
+ msg.msg_name = 0;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ len = sock_recvmsg(sock, &msg, buflen, 0);
+ set_fs(oldfs);
+
+ if (len < 0)
+ return -1;
+
+ LeaveFunction(7);
+ return len;
+}
+
+
+static DECLARE_WAIT_QUEUE_HEAD(sync_wait);
+static pid_t sync_master_pid = 0;
+static pid_t sync_backup_pid = 0;
+
+static DECLARE_WAIT_QUEUE_HEAD(stop_sync_wait);
+static int stop_master_sync = 0;
+static int stop_backup_sync = 0;
+
+static void sync_master_loop(void)
+{
+ struct socket *sock;
+ struct ip_vs_sync_buff *sb;
+
+ /* create the sending multicast socket */
+ sock = make_send_sock();
+ if (!sock)
+ return;
+
+ IP_VS_INFO("sync thread started: state = MASTER, mcast_ifn = %s, "
+ "syncid = %d\n",
+ ip_vs_mcast_master_ifn, ip_vs_master_syncid);
+
+ for (;;) {
+ while ((sb=sb_dequeue())) {
+ ip_vs_send_sync_msg(sock, sb->mesg);
+ ip_vs_sync_buff_release(sb);
+ }
+
+ /* check if entries stay in curr_sb for 2 seconds */
+ if ((sb = get_curr_sync_buff(2*HZ))) {
+ ip_vs_send_sync_msg(sock, sb->mesg);
+ ip_vs_sync_buff_release(sb);
+ }
+
+ if (stop_master_sync)
+ break;
+
+ __set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(HZ);
+ __set_current_state(TASK_RUNNING);
+ }
+
+ /* clean up the sync_buff queue */
+ while ((sb=sb_dequeue())) {
+ ip_vs_sync_buff_release(sb);
+ }
+
+ /* clean up the current sync_buff */
+ if ((sb = get_curr_sync_buff(0))) {
+ ip_vs_sync_buff_release(sb);
+ }
+
+ /* release the sending multicast socket */
+ sock_release(sock);
+}
+
+
+static void sync_backup_loop(void)
+{
+ struct socket *sock;
+ char *buf;
+ int len;
+
+ if (!(buf=kmalloc(SYNC_MESG_MAX_SIZE, GFP_ATOMIC))) {
+ IP_VS_ERR("sync_backup_loop: kmalloc error\n");
+ return;
+ }
+
+ /* create the receiving multicast socket */
+ sock = make_receive_sock();
+ if (!sock)
+ goto out;
+
+ IP_VS_INFO("sync thread started: state = BACKUP, mcast_ifn = %s, "
+ "syncid = %d\n",
+ ip_vs_mcast_backup_ifn, ip_vs_backup_syncid);
+
+ for (;;) {
+ /* do you have data now? */
+ while (!skb_queue_empty(&(sock->sk->receive_queue))) {
+ if ((len=ip_vs_receive(sock, buf,
+ SYNC_MESG_MAX_SIZE))<=0) {
+ IP_VS_ERR("receiving message error\n");
+ break;
+ }
+ /* disable bottom half, because it accessed the data
+ shared by softirq while getting/creating conns */
+ local_bh_disable();
+ ip_vs_process_message(buf, len);
+ local_bh_enable();
+ }
+
+ if (stop_backup_sync)
+ break;
+
+ __set_current_state(TASK_INTERRUPTIBLE);
+ schedule_timeout(HZ);
+ __set_current_state(TASK_RUNNING);
+ }
+
+ /* release the sending multicast socket */
+ sock_release(sock);
+
+ out:
+ kfree(buf);
+}
+
+static void sync_pid_set(int sync_state, pid_t sync_pid)
+{
+ if (sync_state == IP_VS_STATE_MASTER)
+ sync_master_pid = sync_pid;
+ else if (sync_state == IP_VS_STATE_BACKUP)
+ sync_backup_pid = sync_pid;
+}
+
+static void sync_stop_set(int sync_state, int set)
+{
+ if (sync_state == IP_VS_STATE_MASTER)
+ stop_master_sync = set;
+ else if (sync_state == IP_VS_STATE_BACKUP)
+ stop_backup_sync = set;
+ else {
+ stop_master_sync = set;
+ stop_backup_sync = set;
+ }
+}
+
+static int sync_thread(void *startup)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ mm_segment_t oldmm;
+ int state = IP_VS_STATE_NONE;
+
+ MOD_INC_USE_COUNT;
+ daemonize();
+
+ oldmm = get_fs();
+ set_fs(KERNEL_DS);
+
+ if (ip_vs_sync_state & IP_VS_STATE_MASTER && !sync_master_pid) {
+ state = IP_VS_STATE_MASTER;
+ sprintf(current->comm, "ipvs_syncmaster");
+ } else if (ip_vs_sync_state & IP_VS_STATE_BACKUP) {
+ state = IP_VS_STATE_BACKUP;
+ sprintf(current->comm, "ipvs_syncbackup");
+ } else IP_VS_BUG();
+
+ /* Block all signals */
+ spin_lock_irq(&current->sigmask_lock);
+ siginitsetinv(&current->blocked, 0);
+ recalc_sigpending(current);
+ spin_unlock_irq(&current->sigmask_lock);
+
+ /* set up multicast address */
+ mcast_addr.sin_family = AF_INET;
+ mcast_addr.sin_port = htons(IP_VS_SYNC_PORT);
+ mcast_addr.sin_addr.s_addr = htonl(IP_VS_SYNC_GROUP);
+
+ add_wait_queue(&sync_wait, &wait);
+
+ sync_pid_set(state, current->pid);
+ complete((struct completion *)startup);
+
+ /* processing master/backup loop here */
+ if (state == IP_VS_STATE_MASTER)
+ sync_master_loop();
+ else if (state == IP_VS_STATE_BACKUP)
+ sync_backup_loop();
+ else IP_VS_BUG();
+
+ remove_wait_queue(&sync_wait, &wait);
+
+ /* thread exits */
+ sync_pid_set(state, 0);
+ IP_VS_INFO("sync thread stopped!\n");
+
+ set_fs(oldmm);
+ MOD_DEC_USE_COUNT;
+
+ sync_stop_set(state, 0);
+ wake_up(&stop_sync_wait);
+
+ return 0;
+}
+
+
+static int fork_sync_thread(void *startup)
+{
+ pid_t pid;
+
+ /* fork the sync thread here, then the parent process of the
+ sync thread is the init process after this thread exits. */
+ repeat:
+ if ((pid = kernel_thread(sync_thread, startup, 0)) < 0) {
+ IP_VS_ERR("could not create sync_thread due to %d... "
+ "retrying.\n", pid);
+ current->state = TASK_UNINTERRUPTIBLE;
+ schedule_timeout(HZ);
+ goto repeat;
+ }
+
+ return 0;
+}
+
+
+int start_sync_thread(int state, char *mcast_ifn, __u8 syncid)
+{
+ DECLARE_COMPLETION(startup);
+ pid_t pid;
+
+ if ((state == IP_VS_STATE_MASTER && sync_master_pid) ||
+ (state == IP_VS_STATE_BACKUP && sync_backup_pid))
+ return -EEXIST;
+
+ IP_VS_DBG(7, "%s: pid %d\n", __FUNCTION__, current->pid);
+ IP_VS_DBG(7, "Each ip_vs_sync_conn entry need %d bytes\n",
+ sizeof(struct ip_vs_sync_conn));
+
+ ip_vs_sync_state |= state;
+ if (state == IP_VS_STATE_MASTER) {
+ strncpy(ip_vs_mcast_master_ifn, mcast_ifn, sizeof(ip_vs_mcast_master_ifn));
+ ip_vs_mcast_master_ifn[sizeof(ip_vs_mcast_master_ifn) - 1] = 0;
+ ip_vs_master_syncid = syncid;
+ } else {
+ strncpy(ip_vs_mcast_backup_ifn, mcast_ifn, sizeof(ip_vs_mcast_backup_ifn));
+ ip_vs_mcast_backup_ifn[sizeof(ip_vs_mcast_backup_ifn) - 1] = 0;
+ ip_vs_backup_syncid = syncid;
+ }
+
+ repeat:
+ if ((pid = kernel_thread(fork_sync_thread, &startup, 0)) < 0) {
+ IP_VS_ERR("could not create fork_sync_thread due to %d... "
+ "retrying.\n", pid);
+ current->state = TASK_UNINTERRUPTIBLE;
+ schedule_timeout(HZ);
+ goto repeat;
+ }
+
+ wait_for_completion(&startup);
+
+ return 0;
+}
+
+
+int stop_sync_thread(int state)
+{
+ DECLARE_WAITQUEUE(wait, current);
+
+ if ((state == IP_VS_STATE_MASTER && !sync_master_pid) ||
+ (state == IP_VS_STATE_BACKUP && !sync_backup_pid))
+ return -ESRCH;
+
+ IP_VS_DBG(7, "%s: pid %d\n", __FUNCTION__, current->pid);
+ IP_VS_INFO("stopping sync thread %d ...\n",
+ (state == IP_VS_STATE_MASTER) ? sync_master_pid : sync_backup_pid);
+
+ __set_current_state(TASK_UNINTERRUPTIBLE);
+ add_wait_queue(&stop_sync_wait, &wait);
+ sync_stop_set(state, 1);
+ ip_vs_sync_state -= state;
+ wake_up(&sync_wait);
+ schedule();
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&stop_sync_wait, &wait);
+
+ /* Note: no need to reap the sync thread, because its parent
+ process is the init process */
+
+ if ((state == IP_VS_STATE_MASTER && stop_master_sync) ||
+ (state == IP_VS_STATE_BACKUP && stop_backup_sync))
+ IP_VS_BUG();
+
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_wlc.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_wlc.c
new file mode 100644
index 0000000..8c1ab94
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_wlc.c
@@ -0,0 +1,153 @@
+/*
+ * IPVS: Weighted Least-Connection Scheduling module
+ *
+ * Version: $Id: ip_vs_wlc.c,v 1.10.2.1 2003/04/11 14:02:35 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
+ * Peter Kese <peter.kese@ijs.si>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes:
+ * Wensong Zhang : changed the ip_vs_wlc_schedule to return dest
+ * Wensong Zhang : changed to use the inactconns in scheduling
+ * Wensong Zhang : changed some comestics things for debugging
+ * Wensong Zhang : changed for the d-linked destination list
+ * Wensong Zhang : added the ip_vs_wlc_update_svc
+ * Wensong Zhang : added any dest with weight=0 is quiesced
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include <net/ip_vs.h>
+
+
+static int
+ip_vs_wlc_init_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static int
+ip_vs_wlc_done_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static int
+ip_vs_wlc_update_svc(struct ip_vs_service *svc)
+{
+ return 0;
+}
+
+
+static inline unsigned int
+ip_vs_wlc_dest_overhead(struct ip_vs_dest *dest)
+{
+ /*
+ * We think the overhead of processing active connections is 256
+ * times higher than that of inactive connections in average. (This
+ * 256 times might not be accurate, we will change it later) We
+ * use the following formula to estimate the overhead now:
+ * dest->activeconns*256 + dest->inactconns
+ */
+ return (atomic_read(&dest->activeconns) << 8) +
+ atomic_read(&dest->inactconns);
+}
+
+
+/*
+ * Weighted Least Connection scheduling
+ */
+static struct ip_vs_dest *
+ip_vs_wlc_schedule(struct ip_vs_service *svc, struct iphdr *iph)
+{
+ register struct list_head *l, *e;
+ struct ip_vs_dest *dest, *least;
+ unsigned int loh, doh;
+
+ IP_VS_DBG(6, "ip_vs_wlc_schedule(): Scheduling...\n");
+
+ /*
+ * We calculate the load of each dest server as follows:
+ * (dest overhead) / dest->weight
+ *
+ * Remember -- no floats in kernel mode!!!
+ * The comparison of h1*w2 > h2*w1 is equivalent to that of
+ * h1/w1 > h2/w2
+ * if every weight is larger than zero.
+ *
+ * The server with weight=0 is quiesced and will not receive any
+ * new connections.
+ */
+
+ l = &svc->destinations;
+ for (e=l->next; e!=l; e=e->next) {
+ least = list_entry(e, struct ip_vs_dest, n_list);
+ if (atomic_read(&least->weight) > 0) {
+ loh = ip_vs_wlc_dest_overhead(least);
+ goto nextstage;
+ }
+ }
+ return NULL;
+
+ /*
+ * Find the destination with the least load.
+ */
+ nextstage:
+ for (e=e->next; e!=l; e=e->next) {
+ dest = list_entry(e, struct ip_vs_dest, n_list);
+
+ doh = ip_vs_wlc_dest_overhead(dest);
+ if (loh * atomic_read(&dest->weight) >
+ doh * atomic_read(&least->weight)) {
+ least = dest;
+ loh = doh;
+ }
+ }
+
+ IP_VS_DBG(6, "WLC: server %u.%u.%u.%u:%u "
+ "activeconns %d refcnt %d weight %d overhead %d\n",
+ NIPQUAD(least->addr), ntohs(least->port),
+ atomic_read(&least->activeconns),
+ atomic_read(&least->refcnt),
+ atomic_read(&least->weight), loh);
+
+ return least;
+}
+
+
+static struct ip_vs_scheduler ip_vs_wlc_scheduler =
+{
+ {0}, /* n_list */
+ "wlc", /* name */
+ ATOMIC_INIT(0), /* refcnt */
+ THIS_MODULE, /* this module */
+ ip_vs_wlc_init_svc, /* service initializer */
+ ip_vs_wlc_done_svc, /* service done */
+ ip_vs_wlc_update_svc, /* service updater */
+ ip_vs_wlc_schedule, /* select a server from the destination list */
+};
+
+
+static int __init ip_vs_wlc_init(void)
+{
+ INIT_LIST_HEAD(&ip_vs_wlc_scheduler.n_list);
+ return register_ip_vs_scheduler(&ip_vs_wlc_scheduler);
+}
+
+static void __exit ip_vs_wlc_cleanup(void)
+{
+ unregister_ip_vs_scheduler(&ip_vs_wlc_scheduler);
+}
+
+module_init(ip_vs_wlc_init);
+module_exit(ip_vs_wlc_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_wrr.c b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_wrr.c
new file mode 100644
index 0000000..4e98a94
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/ipvs/ip_vs_wrr.c
@@ -0,0 +1,238 @@
+/*
+ * IPVS: Weighted Round-Robin Scheduling module
+ *
+ * Version: $Id: ip_vs_wrr.c,v 1.11 2002/03/25 12:44:35 wensong Exp $
+ *
+ * Authors: Wensong Zhang <wensong@linuxvirtualserver.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes:
+ * Wensong Zhang : changed the ip_vs_wrr_schedule to return dest
+ * Wensong Zhang : changed some comestics things for debugging
+ * Wensong Zhang : changed for the d-linked destination list
+ * Wensong Zhang : added the ip_vs_wrr_update_svc
+ * Julian Anastasov : fixed the bug of returning destination
+ * with weight 0 when all weights are zero
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+
+#include <net/ip_vs.h>
+
+/*
+ * current destination pointer for weighted round-robin scheduling
+ */
+struct ip_vs_wrr_mark {
+ struct list_head *cl; /* current list head */
+ int cw; /* current weight */
+ int mw; /* maximum weight */
+ int di; /* decreasing interval */
+};
+
+
+/*
+ * Get the gcd of server weights
+ */
+static int gcd(int a, int b)
+{
+ int c;
+
+ while ((c = a % b)) {
+ a = b;
+ b = c;
+ }
+ return b;
+}
+
+static int ip_vs_wrr_gcd_weight(struct ip_vs_service *svc)
+{
+ register struct list_head *l, *e;
+ struct ip_vs_dest *dest;
+ int weight;
+ int g = 1;
+
+ l = &svc->destinations;
+ for (e=l->next; e!=l; e=e->next) {
+ dest = list_entry(e, struct ip_vs_dest, n_list);
+ weight = atomic_read(&dest->weight);
+ if (weight > 0) {
+ g = weight;
+ break;
+ }
+ }
+ if (e == l)
+ return g;
+
+ for (e=e->next; e!=l; e=e->next) {
+ dest = list_entry(e, struct ip_vs_dest, n_list);
+ weight = atomic_read(&dest->weight);
+ if (weight > 0)
+ g = gcd(weight, g);
+ }
+
+ return g;
+}
+
+
+/*
+ * Get the maximum weight of the service destinations.
+ */
+static int ip_vs_wrr_max_weight(struct ip_vs_service *svc)
+{
+ register struct list_head *l, *e;
+ struct ip_vs_dest *dest;
+ int weight = 0;
+
+ l = &svc->destinations;
+ for (e=l->next; e!=l; e=e->next) {
+ dest = list_entry(e, struct ip_vs_dest, n_list);
+ if (atomic_read(&dest->weight) > weight)
+ weight = atomic_read(&dest->weight);
+ }
+
+ return weight;
+}
+
+
+static int ip_vs_wrr_init_svc(struct ip_vs_service *svc)
+{
+ struct ip_vs_wrr_mark *mark;
+
+ /*
+ * Allocate the mark variable for WRR scheduling
+ */
+ mark = kmalloc(sizeof(struct ip_vs_wrr_mark), GFP_ATOMIC);
+ if (mark == NULL) {
+ IP_VS_ERR("ip_vs_wrr_init_svc(): no memory\n");
+ return -ENOMEM;
+ }
+ mark->cl = &svc->destinations;
+ mark->cw = 0;
+ mark->mw = ip_vs_wrr_max_weight(svc);
+ mark->di = ip_vs_wrr_gcd_weight(svc);
+ svc->sched_data = mark;
+
+ return 0;
+}
+
+
+static int ip_vs_wrr_done_svc(struct ip_vs_service *svc)
+{
+ /*
+ * Release the mark variable
+ */
+ kfree(svc->sched_data);
+
+ return 0;
+}
+
+
+static int ip_vs_wrr_update_svc(struct ip_vs_service *svc)
+{
+ struct ip_vs_wrr_mark *mark = svc->sched_data;
+
+ mark->cl = &svc->destinations;
+ mark->mw = ip_vs_wrr_max_weight(svc);
+ mark->di = ip_vs_wrr_gcd_weight(svc);
+ if (mark->cw > mark->mw)
+ mark->cw = 0;
+ return 0;
+}
+
+
+/*
+ * Weighted Round-Robin Scheduling
+ */
+static struct ip_vs_dest *
+ip_vs_wrr_schedule(struct ip_vs_service *svc, struct iphdr *iph)
+{
+ struct ip_vs_dest *dest;
+ struct ip_vs_wrr_mark *mark = svc->sched_data;
+
+ IP_VS_DBG(6, "ip_vs_wrr_schedule(): Scheduling...\n");
+
+ /*
+ * This loop will always terminate, because 0<mark->cw<max_weight,
+ * and at least one server has its weight equal to max_weight.
+ */
+ write_lock(&svc->sched_lock);
+ while (1) {
+ if (mark->cl == &svc->destinations) {
+ /* it is at the head of the destination list */
+
+ if (mark->cl == mark->cl->next) {
+ /* no dest entry */
+ write_unlock(&svc->sched_lock);
+ return NULL;
+ }
+
+ mark->cl = svc->destinations.next;
+ mark->cw -= mark->di;
+ if (mark->cw <= 0) {
+ mark->cw = mark->mw;
+ /*
+ * Still zero, which means no available servers.
+ */
+ if (mark->cw == 0) {
+ mark->cl = &svc->destinations;
+ write_unlock(&svc->sched_lock);
+ IP_VS_INFO("ip_vs_wrr_schedule(): "
+ "no available servers\n");
+ return NULL;
+ }
+ }
+ }
+ else mark->cl = mark->cl->next;
+
+ if (mark->cl != &svc->destinations) {
+ /* not at the head of the list */
+ dest = list_entry(mark->cl, struct ip_vs_dest, n_list);
+ if (atomic_read(&dest->weight) >= mark->cw) {
+ write_unlock(&svc->sched_lock);
+ break;
+ }
+ }
+ }
+
+ IP_VS_DBG(6, "WRR: server %u.%u.%u.%u:%u "
+ "activeconns %d refcnt %d weight %d\n",
+ NIPQUAD(dest->addr), ntohs(dest->port),
+ atomic_read(&dest->activeconns),
+ atomic_read(&dest->refcnt),
+ atomic_read(&dest->weight));
+
+ return dest;
+}
+
+
+static struct ip_vs_scheduler ip_vs_wrr_scheduler = {
+ {0}, /* n_list */
+ "wrr", /* name */
+ ATOMIC_INIT(0), /* refcnt */
+ THIS_MODULE, /* this module */
+ ip_vs_wrr_init_svc, /* service initializer */
+ ip_vs_wrr_done_svc, /* service done */
+ ip_vs_wrr_update_svc, /* service updater */
+ ip_vs_wrr_schedule, /* select a server from the destination list */
+};
+
+static int __init ip_vs_wrr_init(void)
+{
+ INIT_LIST_HEAD(&ip_vs_wrr_scheduler.n_list);
+ return register_ip_vs_scheduler(&ip_vs_wrr_scheduler) ;
+}
+
+static void __exit ip_vs_wrr_cleanup(void)
+{
+ unregister_ip_vs_scheduler(&ip_vs_wrr_scheduler);
+}
+
+module_init(ip_vs_wrr_init);
+module_exit(ip_vs_wrr_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/Config.in b/uClinux-2.4.31-uc0/net/ipv4/netfilter/Config.in
new file mode 100644
index 0000000..1686966
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/Config.in
@@ -0,0 +1,181 @@
+#
+# IP netfilter configuration
+#
+mainmenu_option next_comment
+comment ' IP: Netfilter Configuration'
+
+tristate 'Connection tracking (required for masq/NAT)' CONFIG_IP_NF_CONNTRACK
+if [ "$CONFIG_IP_NF_CONNTRACK" != "n" ]; then
+ dep_tristate ' FTP protocol support' CONFIG_IP_NF_FTP $CONFIG_IP_NF_CONNTRACK
+ bool ' Connection mark tracking support' CONFIG_IP_NF_CONNTRACK_MARK
+ dep_tristate ' H.323 (netmeeting) support' CONFIG_IP_NF_H323 $CONFIG_IP_NF_CONNTRACK
+ dep_tristate ' Amanda protocol support' CONFIG_IP_NF_AMANDA $CONFIG_IP_NF_CONNTRACK
+ dep_tristate ' TFTP protocol support' CONFIG_IP_NF_TFTP $CONFIG_IP_NF_CONNTRACK
+ dep_tristate ' IRC protocol support' CONFIG_IP_NF_IRC $CONFIG_IP_NF_CONNTRACK
+ dep_tristate ' GRE protocol support' CONFIG_IP_NF_CT_PROTO_GRE $CONFIG_IP_NF_CONNTRACK
+ dep_tristate ' PPTP protocol support' CONFIG_IP_NF_PPTP $CONFIG_IP_NF_CT_PROTO_GRE
+ if [ "$CONFIG_IP_NF_PPTP" != "n" ]; then
+ bool ' PPTP verbose debug' CONFIG_IP_NF_PPTP_DEBUG
+ fi
+ bool ' Connection tracking flow accounting' CONFIG_IP_NF_CT_ACCT
+ bool ' Connection tracking events' CONFIG_IP_NF_CONNTRACK_EVENTS
+fi
+
+if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ tristate 'Userspace queueing via NETLINK (EXPERIMENTAL)' CONFIG_IP_NF_QUEUE
+fi
+tristate 'IP tables support (required for filtering/masq/NAT)' CONFIG_IP_NF_IPTABLES
+if [ "$CONFIG_IP_NF_IPTABLES" != "n" ]; then
+# The simple matches.
+ dep_tristate ' limit match support' CONFIG_IP_NF_MATCH_LIMIT $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' IP range match support' CONFIG_IP_NF_MATCH_IPRANGE $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' MAC address match support' CONFIG_IP_NF_MATCH_MAC $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' Packet type match support' CONFIG_IP_NF_MATCH_PKTTYPE $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' netfilter MARK match support' CONFIG_IP_NF_MATCH_MARK $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' Multiple port match support' CONFIG_IP_NF_MATCH_MULTIPORT $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' TOS match support' CONFIG_IP_NF_MATCH_TOS $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' TIME match support (EXPERIMENTAL)' CONFIG_IP_NF_MATCH_TIME $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' recent match support' CONFIG_IP_NF_MATCH_RECENT $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' ECN match support' CONFIG_IP_NF_MATCH_ECN $CONFIG_IP_NF_IPTABLES
+
+ dep_tristate ' DSCP match support' CONFIG_IP_NF_MATCH_DSCP $CONFIG_IP_NF_IPTABLES
+
+ dep_tristate ' AH/ESP match support' CONFIG_IP_NF_MATCH_AH_ESP $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' LENGTH match support' CONFIG_IP_NF_MATCH_LENGTH $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' TTL match support' CONFIG_IP_NF_MATCH_TTL $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' tcpmss match support' CONFIG_IP_NF_MATCH_TCPMSS $CONFIG_IP_NF_IPTABLES
+ if [ "$CONFIG_IP_NF_CONNTRACK" != "n" ]; then
+ dep_tristate ' Helper match support' CONFIG_IP_NF_MATCH_HELPER $CONFIG_IP_NF_IPTABLES
+ fi
+ if [ "$CONFIG_IP_NF_CONNTRACK" != "n" ]; then
+ dep_tristate ' Connection state match support' CONFIG_IP_NF_MATCH_STATE $CONFIG_IP_NF_CONNTRACK $CONFIG_IP_NF_IPTABLES
+ if [ "$CONFIG_IP_NF_CONNTRACK_MARK" != "n" ]; then
+ dep_tristate ' Connection mark match support' CONFIG_IP_NF_MATCH_CONNMARK $CONFIG_IP_NF_IPTABLES
+ fi
+ dep_tristate ' Connection tracking match support' CONFIG_IP_NF_MATCH_CONNTRACK $CONFIG_IP_NF_CONNTRACK $CONFIG_IP_NF_IPTABLES
+ fi
+ if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ dep_tristate ' Unclean match support (EXPERIMENTAL)' CONFIG_IP_NF_MATCH_UNCLEAN $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' String match support (EXPERIMENTAL)' CONFIG_IP_NF_MATCH_STRING $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' Owner match support (EXPERIMENTAL)' CONFIG_IP_NF_MATCH_OWNER $CONFIG_IP_NF_IPTABLES
+ fi
+ if [ "$CONFIG_BRIDGE" != "n" ]; then
+ dep_tristate ' Physdev match support' CONFIG_IP_NF_MATCH_PHYSDEV $CONFIG_IP_NF_IPTABLES
+ fi
+# The targets
+ dep_tristate ' Packet filtering' CONFIG_IP_NF_FILTER $CONFIG_IP_NF_IPTABLES
+ if [ "$CONFIG_IP_NF_FILTER" != "n" ]; then
+ dep_tristate ' REJECT target support' CONFIG_IP_NF_TARGET_REJECT $CONFIG_IP_NF_FILTER
+ if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ dep_tristate ' MIRROR target support (EXPERIMENTAL)' CONFIG_IP_NF_TARGET_MIRROR $CONFIG_IP_NF_FILTER
+ fi
+ fi
+
+ if [ "$CONFIG_IP_NF_CONNTRACK" != "n" ]; then
+ bool ' Increase default conntrack table size' CONFIG_IP_NF_BIG_CONNTRACK
+ dep_tristate ' Full NAT' CONFIG_IP_NF_NAT $CONFIG_IP_NF_IPTABLES $CONFIG_IP_NF_CONNTRACK
+ if [ "$CONFIG_IP_NF_NAT" != "n" ]; then
+ define_bool CONFIG_IP_NF_NAT_NEEDED y
+ dep_tristate ' MASQUERADE target support' CONFIG_IP_NF_TARGET_MASQUERADE $CONFIG_IP_NF_NAT
+ dep_tristate ' REDIRECT target support' CONFIG_IP_NF_TARGET_REDIRECT $CONFIG_IP_NF_NAT
+ dep_tristate ' NETMAP target support' CONFIG_IP_NF_TARGET_NETMAP $CONFIG_IP_NF_NAT
+ if [ "$CONFIG_IP_NF_H323" = "m" ]; then
+ define_tristate CONFIG_IP_NF_NAT_H323 m
+ else
+ if [ "$CONFIG_IP_NF_H323" = "y" ]; then
+ define_tristate CONFIG_IP_NF_NAT_H323 $CONFIG_IP_NF_NAT
+ fi
+ fi
+ if [ "$CONFIG_IP_NF_AMANDA" = "m" ]; then
+ define_tristate CONFIG_IP_NF_NAT_AMANDA m
+ else
+ if [ "$CONFIG_IP_NF_AMANDA" = "y" ]; then
+ define_tristate CONFIG_IP_NF_NAT_AMANDA $CONFIG_IP_NF_NAT
+ fi
+ fi
+ if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ dep_tristate ' Basic SNMP-ALG support (EXPERIMENTAL)' CONFIG_IP_NF_NAT_SNMP_BASIC $CONFIG_IP_NF_NAT
+ fi
+ if [ "$CONFIG_IP_NF_IRC" = "m" ]; then
+ define_tristate CONFIG_IP_NF_NAT_IRC m
+ else
+ if [ "$CONFIG_IP_NF_IRC" = "y" ]; then
+ define_tristate CONFIG_IP_NF_NAT_IRC $CONFIG_IP_NF_NAT
+ fi
+ fi
+ # If they want FTP, set to $CONFIG_IP_NF_NAT (m or y),
+ # or $CONFIG_IP_NF_FTP (m or y), whichever is weaker. Argh.
+ if [ "$CONFIG_IP_NF_FTP" = "m" ]; then
+ define_tristate CONFIG_IP_NF_NAT_FTP m
+ else
+ if [ "$CONFIG_IP_NF_FTP" = "y" ]; then
+ define_tristate CONFIG_IP_NF_NAT_FTP $CONFIG_IP_NF_NAT
+ fi
+ fi
+ if [ "$CONFIG_IP_NF_PPTP" = "m" ]; then
+ define_tristate CONFIG_IP_NF_NAT_PPTP m
+ else
+ if [ "$CONFIG_IP_NF_PPTP" = "y" ]; then
+ define_tristate CONFIG_IP_NF_NAT_PPTP $CONFIG_IP_NF_NAT
+ fi
+ fi
+ if [ "$CONFIG_IP_NF_CT_PROTO_GRE" = "m" ]; then
+ define_tristate CONFIG_IP_NF_NAT_PROTO_GRE m
+ else
+ if [ "$CONFIG_IP_NF_CT_PROTO_GRE" = "y" ]; then
+ define_tristate CONFIG_IP_NF_NAT_PROTO_GRE $CONFIG_IP_NF_NAT
+ fi
+ fi
+ if [ "$CONFIG_IP_NF_TFTP" = "m" ]; then
+ define_tristate CONFIG_IP_NF_NAT_TFTP m
+ else
+ if [ "$CONFIG_IP_NF_TFTP" = "y" ]; then
+ define_tristate CONFIG_IP_NF_NAT_TFTP $CONFIG_IP_NF_NAT
+ fi
+ fi
+ fi
+ fi
+
+ dep_tristate ' Packet mangling' CONFIG_IP_NF_MANGLE $CONFIG_IP_NF_IPTABLES
+ if [ "$CONFIG_IP_NF_MANGLE" != "n" ]; then
+ dep_tristate ' TOS target support' CONFIG_IP_NF_TARGET_TOS $CONFIG_IP_NF_MANGLE
+ dep_tristate ' ECN target support' CONFIG_IP_NF_TARGET_ECN $CONFIG_IP_NF_MANGLE
+
+ dep_tristate ' DSCP target support' CONFIG_IP_NF_TARGET_DSCP $CONFIG_IP_NF_MANGLE
+
+ dep_tristate ' MARK target support' CONFIG_IP_NF_TARGET_MARK $CONFIG_IP_NF_MANGLE
+ dep_tristate ' IMQ target support' CONFIG_IP_NF_TARGET_IMQ $CONFIG_IP_NF_MANGLE
+ fi
+ dep_tristate ' LOG target support' CONFIG_IP_NF_TARGET_LOG $CONFIG_IP_NF_IPTABLES
+ if [ "$CONFIG_IP_NF_CONNTRACK_MARK" != "n" ]; then
+ dep_tristate ' CONNMARK target support' CONFIG_IP_NF_TARGET_CONNMARK $CONFIG_IP_NF_IPTABLES
+ fi
+ dep_tristate ' CONNLOG target support' CONFIG_IP_NF_TARGET_CONNLOG $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' ULOG target support' CONFIG_IP_NF_TARGET_ULOG $CONFIG_IP_NF_IPTABLES
+ dep_tristate ' TCPMSS target support' CONFIG_IP_NF_TARGET_TCPMSS $CONFIG_IP_NF_IPTABLES
+fi
+
+tristate 'ARP tables support' CONFIG_IP_NF_ARPTABLES
+if [ "$CONFIG_IP_NF_ARPTABLES" != "n" ]; then
+ dep_tristate ' ARP packet filtering' CONFIG_IP_NF_ARPFILTER $CONFIG_IP_NF_ARPTABLES
+fi
+if [ "$CONFIG_IP_NF_ARPTABLES" != "n" ]; then
+ dep_tristate ' ARP payload mangling' CONFIG_IP_NF_ARP_MANGLE $CONFIG_IP_NF_ARPTABLES
+fi
+
+# Backwards compatibility modules: only if you don't build in the others.
+if [ "$CONFIG_IP_NF_CONNTRACK" != "y" ]; then
+ if [ "$CONFIG_IP_NF_IPTABLES" != "y" ]; then
+ tristate 'ipchains (2.2-style) support' CONFIG_IP_NF_COMPAT_IPCHAINS
+ if [ "$CONFIG_IP_NF_COMPAT_IPCHAINS" != "n" ]; then
+ define_bool CONFIG_IP_NF_NAT_NEEDED y
+ fi
+ if [ "$CONFIG_IP_NF_COMPAT_IPCHAINS" != "y" ]; then
+ tristate 'ipfwadm (2.0-style) support' CONFIG_IP_NF_COMPAT_IPFWADM
+ if [ "$CONFIG_IP_NF_COMPAT_IPFWADM" != "n" ]; then
+ define_bool CONFIG_IP_NF_NAT_NEEDED y
+ fi
+ fi
+ fi
+fi
+endmenu
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/Makefile b/uClinux-2.4.31-uc0/net/ipv4/netfilter/Makefile
new file mode 100644
index 0000000..56466c1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/Makefile
@@ -0,0 +1,163 @@
+#
+# Makefile for the netfilter modules on top of IPv4.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := netfilter.o
+
+export-objs = ip_conntrack_standalone.o ip_fw_compat.o ip_nat_standalone.o ip_tables.o arp_tables.o
+
+# Multipart objects.
+list-multi := ip_conntrack.o iptable_nat.o ipfwadm.o ipchains.o
+
+# objects for the conntrack and NAT core (used by standalone and backw. compat)
+ip_nf_conntrack-objs := ip_conntrack_core.o ip_conntrack_proto_generic.o ip_conntrack_proto_tcp.o ip_conntrack_proto_udp.o ip_conntrack_proto_icmp.o
+ip_nf_nat-objs := ip_nat_core.o ip_nat_helper.o ip_nat_proto_unknown.o ip_nat_proto_tcp.o ip_nat_proto_udp.o ip_nat_proto_icmp.o
+
+# objects for the standalone - connection tracking / NAT
+ip_conntrack-objs := ip_conntrack_standalone.o $(ip_nf_conntrack-objs)
+iptable_nat-objs := ip_nat_standalone.o ip_nat_rule.o $(ip_nf_nat-objs)
+
+# objects for backwards compatibility mode
+ip_nf_compat-objs := ip_fw_compat.o ip_fw_compat_redir.o ip_fw_compat_masq.o $(ip_nf_conntrack-objs) $(ip_nf_nat-objs)
+
+ipfwadm-objs := $(ip_nf_compat-objs) ipfwadm_core.o
+ipchains-objs := $(ip_nf_compat-objs) ipchains_core.o
+
+# connection tracking
+obj-$(CONFIG_IP_NF_CONNTRACK) += ip_conntrack.o
+
+# H.323 support
+obj-$(CONFIG_IP_NF_H323) += ip_conntrack_h323.o
+obj-$(CONFIG_IP_NF_NAT_H323) += ip_nat_h323.o
+ifdef CONFIG_IP_NF_NAT_H323
+ export-objs += ip_conntrack_h323.o
+endif
+
+
+# connection tracking protocol helpers
+obj-$(CONFIG_IP_NF_CT_PROTO_GRE) += ip_conntrack_proto_gre.o
+ifdef CONFIG_IP_NF_CT_PROTO_GRE
+ export-objs += ip_conntrack_proto_gre.o
+endif
+
+# NAT protocol helpers
+obj-$(CONFIG_IP_NF_NAT_PROTO_GRE) += ip_nat_proto_gre.o
+
+# connection tracking helpers
+obj-$(CONFIG_IP_NF_PPTP) += ip_conntrack_pptp.o
+ifdef CONFIG_IP_NF_NAT_PPTP
+ export-objs += ip_conntrack_pptp.o
+endif
+obj-$(CONFIG_IP_NF_AMANDA) += ip_conntrack_amanda.o
+ifdef CONFIG_IP_NF_AMANDA
+ export-objs += ip_conntrack_amanda.o
+endif
+
+obj-$(CONFIG_IP_NF_TFTP) += ip_conntrack_tftp.o
+obj-$(CONFIG_IP_NF_FTP) += ip_conntrack_ftp.o
+ifdef CONFIG_IP_NF_FTP
+ export-objs += ip_conntrack_ftp.o
+endif
+
+obj-$(CONFIG_IP_NF_IRC) += ip_conntrack_irc.o
+ifdef CONFIG_IP_NF_IRC
+ export-objs += ip_conntrack_irc.o
+endif
+
+# NAT helpers
+obj-$(CONFIG_IP_NF_NAT_PPTP) += ip_nat_pptp.o
+obj-$(CONFIG_IP_NF_NAT_AMANDA) += ip_nat_amanda.o
+obj-$(CONFIG_IP_NF_NAT_TFTP) += ip_nat_tftp.o
+obj-$(CONFIG_IP_NF_NAT_FTP) += ip_nat_ftp.o
+obj-$(CONFIG_IP_NF_NAT_IRC) += ip_nat_irc.o
+
+# generic IP tables
+obj-$(CONFIG_IP_NF_IPTABLES) += ip_tables.o
+
+# the three instances of ip_tables
+obj-$(CONFIG_IP_NF_FILTER) += iptable_filter.o
+obj-$(CONFIG_IP_NF_MANGLE) += iptable_mangle.o
+obj-$(CONFIG_IP_NF_NAT) += iptable_nat.o
+
+# matches
+obj-$(CONFIG_IP_NF_MATCH_HELPER) += ipt_helper.o
+obj-$(CONFIG_IP_NF_MATCH_LIMIT) += ipt_limit.o
+obj-$(CONFIG_IP_NF_MATCH_IPRANGE) += ipt_iprange.o
+obj-$(CONFIG_IP_NF_MATCH_MARK) += ipt_mark.o
+obj-$(CONFIG_IP_NF_MATCH_MAC) += ipt_mac.o
+
+obj-$(CONFIG_IP_NF_MATCH_PKTTYPE) += ipt_pkttype.o
+obj-$(CONFIG_IP_NF_MATCH_MULTIPORT) += ipt_multiport.o
+obj-$(CONFIG_IP_NF_MATCH_OWNER) += ipt_owner.o
+obj-$(CONFIG_IP_NF_MATCH_TOS) += ipt_tos.o
+
+obj-$(CONFIG_IP_NF_MATCH_TIME) += ipt_time.o
+
+
+obj-$(CONFIG_IP_NF_MATCH_RECENT) += ipt_recent.o
+
+obj-$(CONFIG_IP_NF_MATCH_ECN) += ipt_ecn.o
+obj-$(CONFIG_IP_NF_MATCH_DSCP) += ipt_dscp.o
+obj-$(CONFIG_IP_NF_MATCH_AH_ESP) += ipt_ah.o ipt_esp.o
+
+obj-$(CONFIG_IP_NF_MATCH_LENGTH) += ipt_length.o
+
+obj-$(CONFIG_IP_NF_MATCH_TTL) += ipt_ttl.o
+obj-$(CONFIG_IP_NF_MATCH_STATE) += ipt_state.o
+obj-$(CONFIG_IP_NF_MATCH_CONNMARK) += ipt_connmark.o
+obj-$(CONFIG_IP_NF_MATCH_CONNTRACK) += ipt_conntrack.o
+obj-$(CONFIG_IP_NF_MATCH_UNCLEAN) += ipt_unclean.o
+obj-$(CONFIG_IP_NF_MATCH_STRING) += ipt_string.o
+obj-$(CONFIG_IP_NF_MATCH_TCPMSS) += ipt_tcpmss.o
+
+obj-$(CONFIG_IP_NF_MATCH_PHYSDEV) += ipt_physdev.o
+
+# targets
+obj-$(CONFIG_IP_NF_TARGET_REJECT) += ipt_REJECT.o
+obj-$(CONFIG_IP_NF_TARGET_MIRROR) += ipt_MIRROR.o
+obj-$(CONFIG_IP_NF_TARGET_TOS) += ipt_TOS.o
+obj-$(CONFIG_IP_NF_TARGET_ECN) += ipt_ECN.o
+obj-$(CONFIG_IP_NF_TARGET_DSCP) += ipt_DSCP.o
+obj-$(CONFIG_IP_NF_TARGET_MARK) += ipt_MARK.o
+obj-$(CONFIG_IP_NF_TARGET_IMQ) += ipt_IMQ.o
+obj-$(CONFIG_IP_NF_TARGET_MASQUERADE) += ipt_MASQUERADE.o
+obj-$(CONFIG_IP_NF_TARGET_REDIRECT) += ipt_REDIRECT.o
+obj-$(CONFIG_IP_NF_TARGET_NETMAP) += ipt_NETMAP.o
+obj-$(CONFIG_IP_NF_NAT_SNMP_BASIC) += ip_nat_snmp_basic.o
+obj-$(CONFIG_IP_NF_TARGET_LOG) += ipt_LOG.o
+obj-$(CONFIG_IP_NF_TARGET_CONNLOG) += ipt_CONNLOG.o
+obj-$(CONFIG_IP_NF_TARGET_CONNMARK) += ipt_CONNMARK.o
+obj-$(CONFIG_IP_NF_TARGET_ULOG) += ipt_ULOG.o
+obj-$(CONFIG_IP_NF_TARGET_TCPMSS) += ipt_TCPMSS.o
+
+# generic ARP tables
+obj-$(CONFIG_IP_NF_ARPTABLES) += arp_tables.o
+obj-$(CONFIG_IP_NF_ARP_MANGLE) += arpt_mangle.o
+
+# just filtering instance of ARP tables for now
+obj-$(CONFIG_IP_NF_ARPFILTER) += arptable_filter.o
+
+# backwards compatibility
+obj-$(CONFIG_IP_NF_COMPAT_IPCHAINS) += ipchains.o
+obj-$(CONFIG_IP_NF_COMPAT_IPFWADM) += ipfwadm.o
+
+obj-$(CONFIG_IP_NF_QUEUE) += ip_queue.o
+
+include $(TOPDIR)/Rules.make
+
+ip_conntrack.o: $(ip_conntrack-objs)
+ $(LD) -r -o $@ $(ip_conntrack-objs)
+
+iptable_nat.o: $(iptable_nat-objs)
+ $(LD) -r -o $@ $(iptable_nat-objs)
+
+ipfwadm.o: $(ipfwadm-objs)
+ $(LD) -r -o $@ $(ipfwadm-objs)
+
+ipchains.o: $(ipchains-objs)
+ $(LD) -r -o $@ $(ipchains-objs)
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/arp_tables.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/arp_tables.c
new file mode 100644
index 0000000..bc2e3a5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/arp_tables.c
@@ -0,0 +1,1316 @@
+/*
+ * Packet matching code for ARP packets.
+ *
+ * Based heavily, if not almost entirely, upon ip_tables.c framework.
+ *
+ * Some ARP specific bits are:
+ *
+ * Copyright (C) 2002 David S. Miller (davem@redhat.com)
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/kmod.h>
+#include <linux/vmalloc.h>
+#include <linux/proc_fs.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <asm/uaccess.h>
+#include <asm/semaphore.h>
+
+#include <linux/netfilter_arp/arp_tables.h>
+
+/*#define DEBUG_ARP_TABLES*/
+/*#define DEBUG_ARP_TABLES_USER*/
+
+#ifdef DEBUG_ARP_TABLES
+#define dprintf(format, args...) printk(format , ## args)
+#else
+#define dprintf(format, args...)
+#endif
+
+#ifdef DEBUG_ARP_TABLES_USER
+#define duprintf(format, args...) printk(format , ## args)
+#else
+#define duprintf(format, args...)
+#endif
+
+#ifdef CONFIG_NETFILTER_DEBUG
+#define ARP_NF_ASSERT(x) \
+do { \
+ if (!(x)) \
+ printk("ARP_NF_ASSERT: %s:%s:%u\n", \
+ __FUNCTION__, __FILE__, __LINE__); \
+} while(0)
+#else
+#define ARP_NF_ASSERT(x)
+#endif
+#define SMP_ALIGN(x) (((x) + SMP_CACHE_BYTES-1) & ~(SMP_CACHE_BYTES-1))
+
+
+#define ASSERT_READ_LOCK(x) ARP_NF_ASSERT(down_trylock(&arpt_mutex) != 0)
+#define ASSERT_WRITE_LOCK(x) ARP_NF_ASSERT(down_trylock(&arpt_mutex) != 0)
+#include <linux/netfilter_ipv4/lockhelp.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+struct arpt_table_info {
+ unsigned int size;
+ unsigned int number;
+ unsigned int initial_entries;
+ unsigned int hook_entry[NF_ARP_NUMHOOKS];
+ unsigned int underflow[NF_ARP_NUMHOOKS];
+ char entries[0] __attribute__((aligned(SMP_CACHE_BYTES)));
+};
+
+static LIST_HEAD(arpt_target);
+static LIST_HEAD(arpt_tables);
+#define ADD_COUNTER(c,b,p) do { (c).bcnt += (b); (c).pcnt += (p); } while(0)
+
+#ifdef CONFIG_SMP
+#define TABLE_OFFSET(t,p) (SMP_ALIGN((t)->size)*(p))
+#else
+#define TABLE_OFFSET(t,p) 0
+#endif
+
+static inline int arp_devaddr_compare(const struct arpt_devaddr_info *ap,
+ char *hdr_addr, int len)
+{
+ int i, ret;
+
+ if (len > ARPT_DEV_ADDR_LEN_MAX)
+ len = ARPT_DEV_ADDR_LEN_MAX;
+
+ ret = 0;
+ for (i = 0; i < len; i++)
+ ret |= (hdr_addr[i] ^ ap->addr[i]) & ap->mask[i];
+
+ return (ret != 0);
+}
+
+/* Returns whether packet matches rule or not. */
+static inline int arp_packet_match(const struct arphdr *arphdr,
+ struct net_device *dev,
+ const char *indev,
+ const char *outdev,
+ const struct arpt_arp *arpinfo)
+{
+ char *arpptr = (char *)(arphdr + 1);
+ char *src_devaddr, *tgt_devaddr;
+ u32 src_ipaddr, tgt_ipaddr;
+ int i, ret;
+
+#define FWINV(bool,invflg) ((bool) ^ !!(arpinfo->invflags & invflg))
+
+ if (FWINV((arphdr->ar_op & arpinfo->arpop_mask) != arpinfo->arpop,
+ ARPT_INV_ARPOP)) {
+ dprintf("ARP operation field mismatch.\n");
+ dprintf("ar_op: %04x info->arpop: %04x info->arpop_mask: %04x\n",
+ arphdr->ar_op, arpinfo->arpop, arpinfo->arpop_mask);
+ return 0;
+ }
+
+ if (FWINV((arphdr->ar_hrd & arpinfo->arhrd_mask) != arpinfo->arhrd,
+ ARPT_INV_ARPHRD)) {
+ dprintf("ARP hardware address format mismatch.\n");
+ dprintf("ar_hrd: %04x info->arhrd: %04x info->arhrd_mask: %04x\n",
+ arphdr->ar_hrd, arpinfo->arhrd, arpinfo->arhrd_mask);
+ return 0;
+ }
+
+ if (FWINV((arphdr->ar_pro & arpinfo->arpro_mask) != arpinfo->arpro,
+ ARPT_INV_ARPPRO)) {
+ dprintf("ARP protocol address format mismatch.\n");
+ dprintf("ar_pro: %04x info->arpro: %04x info->arpro_mask: %04x\n",
+ arphdr->ar_pro, arpinfo->arpro, arpinfo->arpro_mask);
+ return 0;
+ }
+
+ if (FWINV((arphdr->ar_hln & arpinfo->arhln_mask) != arpinfo->arhln,
+ ARPT_INV_ARPHLN)) {
+ dprintf("ARP hardware address length mismatch.\n");
+ dprintf("ar_hln: %02x info->arhln: %02x info->arhln_mask: %02x\n",
+ arphdr->ar_hln, arpinfo->arhln, arpinfo->arhln_mask);
+ return 0;
+ }
+
+ src_devaddr = arpptr;
+ arpptr += dev->addr_len;
+ memcpy(&src_ipaddr, arpptr, sizeof(u32));
+ arpptr += sizeof(u32);
+ tgt_devaddr = arpptr;
+ arpptr += dev->addr_len;
+ memcpy(&tgt_ipaddr, arpptr, sizeof(u32));
+
+ if (FWINV(arp_devaddr_compare(&arpinfo->src_devaddr, src_devaddr, dev->addr_len),
+ ARPT_INV_SRCDEVADDR) ||
+ FWINV(arp_devaddr_compare(&arpinfo->tgt_devaddr, tgt_devaddr, dev->addr_len),
+ ARPT_INV_TGTDEVADDR)) {
+ dprintf("Source or target device address mismatch.\n");
+
+ return 0;
+ }
+
+ if (FWINV(((src_ipaddr) & arpinfo->smsk.s_addr) != arpinfo->src.s_addr,
+ ARPT_INV_SRCIP) ||
+ FWINV((((tgt_ipaddr) & arpinfo->tmsk.s_addr) != arpinfo->tgt.s_addr),
+ ARPT_INV_TGTIP)) {
+ dprintf("Source or target IP address mismatch.\n");
+
+ dprintf("SRC: %u.%u.%u.%u. Mask: %u.%u.%u.%u. Target: %u.%u.%u.%u.%s\n",
+ NIPQUAD(src_ipaddr),
+ NIPQUAD(arpinfo->smsk.s_addr),
+ NIPQUAD(arpinfo->src.s_addr),
+ arpinfo->invflags & ARPT_INV_SRCIP ? " (INV)" : "");
+ dprintf("TGT: %u.%u.%u.%u Mask: %u.%u.%u.%u Target: %u.%u.%u.%u.%s\n",
+ NIPQUAD(tgt_ipaddr),
+ NIPQUAD(arpinfo->tmsk.s_addr),
+ NIPQUAD(arpinfo->tgt.s_addr),
+ arpinfo->invflags & ARPT_INV_TGTIP ? " (INV)" : "");
+ return 0;
+ }
+
+ /* Look for ifname matches. */
+ for (i = 0, ret = 0; i < IFNAMSIZ; i++) {
+ ret |= (indev[i] ^ arpinfo->iniface[i])
+ & arpinfo->iniface_mask[i];
+ }
+
+ if (FWINV(ret != 0, ARPT_INV_VIA_IN)) {
+ dprintf("VIA in mismatch (%s vs %s).%s\n",
+ indev, arpinfo->iniface,
+ arpinfo->invflags&ARPT_INV_VIA_IN ?" (INV)":"");
+ return 0;
+ }
+
+ for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) {
+ unsigned long odev;
+ memcpy(&odev, outdev + i*sizeof(unsigned long),
+ sizeof(unsigned long));
+ ret |= (odev
+ ^ ((const unsigned long *)arpinfo->outiface)[i])
+ & ((const unsigned long *)arpinfo->outiface_mask)[i];
+ }
+
+ if (FWINV(ret != 0, ARPT_INV_VIA_OUT)) {
+ dprintf("VIA out mismatch (%s vs %s).%s\n",
+ outdev, arpinfo->outiface,
+ arpinfo->invflags&ARPT_INV_VIA_OUT ?" (INV)":"");
+ return 0;
+ }
+
+ return 1;
+}
+
+static inline int arp_checkentry(const struct arpt_arp *arp)
+{
+ if (arp->flags & ~ARPT_F_MASK) {
+ duprintf("Unknown flag bits set: %08X\n",
+ arp->flags & ~ARPT_F_MASK);
+ return 0;
+ }
+ if (arp->invflags & ~ARPT_INV_MASK) {
+ duprintf("Unknown invflag bits set: %08X\n",
+ arp->invflags & ~ARPT_INV_MASK);
+ return 0;
+ }
+
+ return 1;
+}
+
+static unsigned int arpt_error(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ if (net_ratelimit())
+ printk("arp_tables: error: '%s'\n", (char *)targinfo);
+
+ return NF_DROP;
+}
+
+static inline struct arpt_entry *get_entry(void *base, unsigned int offset)
+{
+ return (struct arpt_entry *)(base + offset);
+}
+
+unsigned int arpt_do_table(struct sk_buff **pskb,
+ unsigned int hook,
+ const struct net_device *in,
+ const struct net_device *out,
+ struct arpt_table *table,
+ void *userdata)
+{
+ static const char nulldevname[IFNAMSIZ] = { 0 };
+ unsigned int verdict = NF_DROP;
+ struct arphdr *arp = (*pskb)->nh.arph;
+ int hotdrop = 0;
+ struct arpt_entry *e, *back;
+ const char *indev, *outdev;
+ void *table_base;
+
+ indev = in ? in->name : nulldevname;
+ outdev = out ? out->name : nulldevname;
+
+ read_lock_bh(&table->lock);
+ table_base = (void *)table->private->entries
+ + TABLE_OFFSET(table->private,
+ cpu_number_map(smp_processor_id()));
+ e = get_entry(table_base, table->private->hook_entry[hook]);
+ back = get_entry(table_base, table->private->underflow[hook]);
+
+ do {
+ if (arp_packet_match(arp, (*pskb)->dev, indev, outdev, &e->arp)) {
+ struct arpt_entry_target *t;
+ int hdr_len;
+
+ hdr_len = sizeof(*arp) + (2 * sizeof(struct in_addr)) +
+ (2 * (*pskb)->dev->addr_len);
+ ADD_COUNTER(e->counters, hdr_len, 1);
+
+ t = arpt_get_target(e);
+
+ /* Standard target? */
+ if (!t->u.kernel.target->target) {
+ int v;
+
+ v = ((struct arpt_standard_target *)t)->verdict;
+ if (v < 0) {
+ /* Pop from stack? */
+ if (v != ARPT_RETURN) {
+ verdict = (unsigned)(-v) - 1;
+ break;
+ }
+ e = back;
+ back = get_entry(table_base,
+ back->comefrom);
+ continue;
+ }
+ if (table_base + v
+ != (void *)e + e->next_offset) {
+ /* Save old back ptr in next entry */
+ struct arpt_entry *next
+ = (void *)e + e->next_offset;
+ next->comefrom =
+ (void *)back - table_base;
+
+ /* set back pointer to next entry */
+ back = next;
+ }
+
+ e = get_entry(table_base, v);
+ } else {
+ /* Targets which reenter must return
+ * abs. verdicts
+ */
+ verdict = t->u.kernel.target->target(pskb,
+ hook,
+ in, out,
+ t->data,
+ userdata);
+
+ /* Target might have changed stuff. */
+ arp = (*pskb)->nh.arph;
+
+ if (verdict == ARPT_CONTINUE)
+ e = (void *)e + e->next_offset;
+ else
+ /* Verdict */
+ break;
+ }
+ } else {
+ e = (void *)e + e->next_offset;
+ }
+ } while (!hotdrop);
+ read_unlock_bh(&table->lock);
+
+ if (hotdrop)
+ return NF_DROP;
+ else
+ return verdict;
+}
+
+static inline void *find_inlist_lock_noload(struct list_head *head,
+ const char *name,
+ int *error,
+ struct semaphore *mutex)
+{
+ void *ret;
+
+ *error = down_interruptible(mutex);
+ if (*error != 0)
+ return NULL;
+
+ ret = list_named_find(head, name);
+ if (!ret) {
+ *error = -ENOENT;
+ up(mutex);
+ }
+ return ret;
+}
+
+#ifndef CONFIG_KMOD
+#define find_inlist_lock(h,n,p,e,m) find_inlist_lock_noload((h),(n),(e),(m))
+#else
+static void *
+find_inlist_lock(struct list_head *head,
+ const char *name,
+ const char *prefix,
+ int *error,
+ struct semaphore *mutex)
+{
+ void *ret;
+
+ ret = find_inlist_lock_noload(head, name, error, mutex);
+ if (!ret) {
+ char modulename[ARPT_FUNCTION_MAXNAMELEN + strlen(prefix) + 1];
+ strcpy(modulename, prefix);
+ strcat(modulename, name);
+ duprintf("find_inlist: loading `%s'.\n", modulename);
+ request_module(modulename);
+ ret = find_inlist_lock_noload(head, name, error, mutex);
+ }
+
+ return ret;
+}
+#endif
+
+static inline struct arpt_table *arpt_find_table_lock(const char *name, int *error, struct semaphore *mutex)
+{
+ return find_inlist_lock(&arpt_tables, name, "arptable_", error, mutex);
+}
+
+struct arpt_target *arpt_find_target_lock(const char *name, int *error, struct semaphore *mutex)
+{
+ return find_inlist_lock(&arpt_target, name, "arpt_", error, mutex);
+}
+
+/* All zeroes == unconditional rule. */
+static inline int unconditional(const struct arpt_arp *arp)
+{
+ unsigned int i;
+
+ for (i = 0; i < sizeof(*arp)/sizeof(__u32); i++)
+ if (((__u32 *)arp)[i])
+ return 0;
+
+ return 1;
+}
+
+/* Figures out from what hook each rule can be called: returns 0 if
+ * there are loops. Puts hook bitmask in comefrom.
+ */
+static int mark_source_chains(struct arpt_table_info *newinfo, unsigned int valid_hooks)
+{
+ unsigned int hook;
+
+ /* No recursion; use packet counter to save back ptrs (reset
+ * to 0 as we leave), and comefrom to save source hook bitmask.
+ */
+ for (hook = 0; hook < NF_ARP_NUMHOOKS; hook++) {
+ unsigned int pos = newinfo->hook_entry[hook];
+ struct arpt_entry *e
+ = (struct arpt_entry *)(newinfo->entries + pos);
+
+ if (!(valid_hooks & (1 << hook)))
+ continue;
+
+ /* Set initial back pointer. */
+ e->counters.pcnt = pos;
+
+ for (;;) {
+ struct arpt_standard_target *t
+ = (void *)arpt_get_target(e);
+
+ if (e->comefrom & (1 << NF_ARP_NUMHOOKS)) {
+ printk("arptables: loop hook %u pos %u %08X.\n",
+ hook, pos, e->comefrom);
+ return 0;
+ }
+ e->comefrom
+ |= ((1 << hook) | (1 << NF_ARP_NUMHOOKS));
+
+ /* Unconditional return/END. */
+ if (e->target_offset == sizeof(struct arpt_entry)
+ && (strcmp(t->target.u.user.name,
+ ARPT_STANDARD_TARGET) == 0)
+ && t->verdict < 0
+ && unconditional(&e->arp)) {
+ unsigned int oldpos, size;
+
+ /* Return: backtrack through the last
+ * big jump.
+ */
+ do {
+ e->comefrom ^= (1<<NF_ARP_NUMHOOKS);
+ oldpos = pos;
+ pos = e->counters.pcnt;
+ e->counters.pcnt = 0;
+
+ /* We're at the start. */
+ if (pos == oldpos)
+ goto next;
+
+ e = (struct arpt_entry *)
+ (newinfo->entries + pos);
+ } while (oldpos == pos + e->next_offset);
+
+ /* Move along one */
+ size = e->next_offset;
+ e = (struct arpt_entry *)
+ (newinfo->entries + pos + size);
+ e->counters.pcnt = pos;
+ pos += size;
+ } else {
+ int newpos = t->verdict;
+
+ if (strcmp(t->target.u.user.name,
+ ARPT_STANDARD_TARGET) == 0
+ && newpos >= 0) {
+ /* This a jump; chase it. */
+ duprintf("Jump rule %u -> %u\n",
+ pos, newpos);
+ } else {
+ /* ... this is a fallthru */
+ newpos = pos + e->next_offset;
+ }
+ e = (struct arpt_entry *)
+ (newinfo->entries + newpos);
+ e->counters.pcnt = pos;
+ pos = newpos;
+ }
+ }
+ next:
+ duprintf("Finished chain %u\n", hook);
+ }
+ return 1;
+}
+
+static inline int standard_check(const struct arpt_entry_target *t,
+ unsigned int max_offset)
+{
+ struct arpt_standard_target *targ = (void *)t;
+
+ /* Check standard info. */
+ if (t->u.target_size
+ != ARPT_ALIGN(sizeof(struct arpt_standard_target))) {
+ duprintf("arpt_standard_check: target size %u != %Zu\n",
+ t->u.target_size,
+ ARPT_ALIGN(sizeof(struct arpt_standard_target)));
+ return 0;
+ }
+
+ if (targ->verdict >= 0
+ && targ->verdict > max_offset - sizeof(struct arpt_entry)) {
+ duprintf("arpt_standard_check: bad verdict (%i)\n",
+ targ->verdict);
+ return 0;
+ }
+
+ if (targ->verdict < -NF_MAX_VERDICT - 1) {
+ duprintf("arpt_standard_check: bad negative verdict (%i)\n",
+ targ->verdict);
+ return 0;
+ }
+ return 1;
+}
+
+static struct arpt_target arpt_standard_target;
+
+static inline int check_entry(struct arpt_entry *e, const char *name, unsigned int size,
+ unsigned int *i)
+{
+ struct arpt_entry_target *t;
+ struct arpt_target *target;
+ int ret;
+
+ if (!arp_checkentry(&e->arp)) {
+ duprintf("arp_tables: arp check failed %p %s.\n", e, name);
+ return -EINVAL;
+ }
+
+ t = arpt_get_target(e);
+ target = arpt_find_target_lock(t->u.user.name, &ret, &arpt_mutex);
+ if (!target) {
+ duprintf("check_entry: `%s' not found\n", t->u.user.name);
+ goto out;
+ }
+ if (target->me)
+ __MOD_INC_USE_COUNT(target->me);
+ t->u.kernel.target = target;
+ up(&arpt_mutex);
+
+ if (t->u.kernel.target == &arpt_standard_target) {
+ if (!standard_check(t, size)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ } else if (t->u.kernel.target->checkentry
+ && !t->u.kernel.target->checkentry(name, e, t->data,
+ t->u.target_size
+ - sizeof(*t),
+ e->comefrom)) {
+ if (t->u.kernel.target->me)
+ __MOD_DEC_USE_COUNT(t->u.kernel.target->me);
+ duprintf("arp_tables: check failed for `%s'.\n",
+ t->u.kernel.target->name);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ (*i)++;
+ return 0;
+
+out:
+ return ret;
+}
+
+static inline int check_entry_size_and_hooks(struct arpt_entry *e,
+ struct arpt_table_info *newinfo,
+ unsigned char *base,
+ unsigned char *limit,
+ const unsigned int *hook_entries,
+ const unsigned int *underflows,
+ unsigned int *i)
+{
+ unsigned int h;
+
+ if ((unsigned long)e % __alignof__(struct arpt_entry) != 0
+ || (unsigned char *)e + sizeof(struct arpt_entry) >= limit) {
+ duprintf("Bad offset %p\n", e);
+ return -EINVAL;
+ }
+
+ if (e->next_offset
+ < sizeof(struct arpt_entry) + sizeof(struct arpt_entry_target)) {
+ duprintf("checking: element %p size %u\n",
+ e, e->next_offset);
+ return -EINVAL;
+ }
+
+ /* Check hooks & underflows */
+ for (h = 0; h < NF_ARP_NUMHOOKS; h++) {
+ if ((unsigned char *)e - base == hook_entries[h])
+ newinfo->hook_entry[h] = hook_entries[h];
+ if ((unsigned char *)e - base == underflows[h])
+ newinfo->underflow[h] = underflows[h];
+ }
+
+ /* FIXME: underflows must be unconditional, standard verdicts
+ < 0 (not ARPT_RETURN). --RR */
+
+ /* Clear counters and comefrom */
+ e->counters = ((struct arpt_counters) { 0, 0 });
+ e->comefrom = 0;
+
+ (*i)++;
+ return 0;
+}
+
+static inline int cleanup_entry(struct arpt_entry *e, unsigned int *i)
+{
+ struct arpt_entry_target *t;
+
+ if (i && (*i)-- == 0)
+ return 1;
+
+ t = arpt_get_target(e);
+ if (t->u.kernel.target->destroy)
+ t->u.kernel.target->destroy(t->data,
+ t->u.target_size - sizeof(*t));
+ if (t->u.kernel.target->me)
+ __MOD_DEC_USE_COUNT(t->u.kernel.target->me);
+
+ return 0;
+}
+
+/* Checks and translates the user-supplied table segment (held in
+ * newinfo).
+ */
+static int translate_table(const char *name,
+ unsigned int valid_hooks,
+ struct arpt_table_info *newinfo,
+ unsigned int size,
+ unsigned int number,
+ const unsigned int *hook_entries,
+ const unsigned int *underflows)
+{
+ unsigned int i;
+ int ret;
+
+ newinfo->size = size;
+ newinfo->number = number;
+
+ /* Init all hooks to impossible value. */
+ for (i = 0; i < NF_ARP_NUMHOOKS; i++) {
+ newinfo->hook_entry[i] = 0xFFFFFFFF;
+ newinfo->underflow[i] = 0xFFFFFFFF;
+ }
+
+ duprintf("translate_table: size %u\n", newinfo->size);
+ i = 0;
+
+ /* Walk through entries, checking offsets. */
+ ret = ARPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
+ check_entry_size_and_hooks,
+ newinfo,
+ newinfo->entries,
+ newinfo->entries + size,
+ hook_entries, underflows, &i);
+ duprintf("translate_table: ARPT_ENTRY_ITERATE gives %d\n", ret);
+ if (ret != 0)
+ return ret;
+
+ if (i != number) {
+ duprintf("translate_table: %u not %u entries\n",
+ i, number);
+ return -EINVAL;
+ }
+
+ /* Check hooks all assigned */
+ for (i = 0; i < NF_ARP_NUMHOOKS; i++) {
+ /* Only hooks which are valid */
+ if (!(valid_hooks & (1 << i)))
+ continue;
+ if (newinfo->hook_entry[i] == 0xFFFFFFFF) {
+ duprintf("Invalid hook entry %u %u\n",
+ i, hook_entries[i]);
+ return -EINVAL;
+ }
+ if (newinfo->underflow[i] == 0xFFFFFFFF) {
+ duprintf("Invalid underflow %u %u\n",
+ i, underflows[i]);
+ return -EINVAL;
+ }
+ }
+
+ if (!mark_source_chains(newinfo, valid_hooks)) {
+ duprintf("Looping hook\n");
+ return -ELOOP;
+ }
+
+ /* Finally, each sanity check must pass */
+ i = 0;
+ ret = ARPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
+ check_entry, name, size, &i);
+
+ if (ret != 0) {
+ ARPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
+ cleanup_entry, &i);
+ return ret;
+ }
+
+ /* And one copy for every other CPU */
+ for (i = 1; i < smp_num_cpus; i++) {
+ memcpy(newinfo->entries + SMP_ALIGN(newinfo->size)*i,
+ newinfo->entries,
+ SMP_ALIGN(newinfo->size));
+ }
+
+ return ret;
+}
+
+static struct arpt_table_info *replace_table(struct arpt_table *table,
+ unsigned int num_counters,
+ struct arpt_table_info *newinfo,
+ int *error)
+{
+ struct arpt_table_info *oldinfo;
+
+ /* Do the substitution. */
+ write_lock_bh(&table->lock);
+ /* Check inside lock: is the old number correct? */
+ if (num_counters != table->private->number) {
+ duprintf("num_counters != table->private->number (%u/%u)\n",
+ num_counters, table->private->number);
+ write_unlock_bh(&table->lock);
+ *error = -EAGAIN;
+ return NULL;
+ }
+ oldinfo = table->private;
+ table->private = newinfo;
+ newinfo->initial_entries = oldinfo->initial_entries;
+ write_unlock_bh(&table->lock);
+
+ return oldinfo;
+}
+
+/* Gets counters. */
+static inline int add_entry_to_counter(const struct arpt_entry *e,
+ struct arpt_counters total[],
+ unsigned int *i)
+{
+ ADD_COUNTER(total[*i], e->counters.bcnt, e->counters.pcnt);
+
+ (*i)++;
+ return 0;
+}
+
+static void get_counters(const struct arpt_table_info *t,
+ struct arpt_counters counters[])
+{
+ unsigned int cpu;
+ unsigned int i;
+
+ for (cpu = 0; cpu < smp_num_cpus; cpu++) {
+ i = 0;
+ ARPT_ENTRY_ITERATE(t->entries + TABLE_OFFSET(t, cpu),
+ t->size,
+ add_entry_to_counter,
+ counters,
+ &i);
+ }
+}
+
+static int copy_entries_to_user(unsigned int total_size,
+ struct arpt_table *table,
+ void *userptr)
+{
+ unsigned int off, num, countersize;
+ struct arpt_entry *e;
+ struct arpt_counters *counters;
+ int ret = 0;
+
+ /* We need atomic snapshot of counters: rest doesn't change
+ * (other than comefrom, which userspace doesn't care
+ * about).
+ */
+ countersize = sizeof(struct arpt_counters) * table->private->number;
+ counters = vmalloc(countersize);
+
+ if (counters == NULL)
+ return -ENOMEM;
+
+ /* First, sum counters... */
+ memset(counters, 0, countersize);
+ write_lock_bh(&table->lock);
+ get_counters(table->private, counters);
+ write_unlock_bh(&table->lock);
+
+ /* ... then copy entire thing from CPU 0... */
+ if (copy_to_user(userptr, table->private->entries, total_size) != 0) {
+ ret = -EFAULT;
+ goto free_counters;
+ }
+
+ /* FIXME: use iterator macros --RR */
+ /* ... then go back and fix counters and names */
+ for (off = 0, num = 0; off < total_size; off += e->next_offset, num++){
+ struct arpt_entry_target *t;
+
+ e = (struct arpt_entry *)(table->private->entries + off);
+ if (copy_to_user(userptr + off
+ + offsetof(struct arpt_entry, counters),
+ &counters[num],
+ sizeof(counters[num])) != 0) {
+ ret = -EFAULT;
+ goto free_counters;
+ }
+
+ t = arpt_get_target(e);
+ if (copy_to_user(userptr + off + e->target_offset
+ + offsetof(struct arpt_entry_target,
+ u.user.name),
+ t->u.kernel.target->name,
+ strlen(t->u.kernel.target->name)+1) != 0) {
+ ret = -EFAULT;
+ goto free_counters;
+ }
+ }
+
+ free_counters:
+ vfree(counters);
+ return ret;
+}
+
+static int get_entries(const struct arpt_get_entries *entries,
+ struct arpt_get_entries *uptr)
+{
+ int ret;
+ struct arpt_table *t;
+
+ t = arpt_find_table_lock(entries->name, &ret, &arpt_mutex);
+ if (t) {
+ duprintf("t->private->number = %u\n",
+ t->private->number);
+ if (entries->size == t->private->size)
+ ret = copy_entries_to_user(t->private->size,
+ t, uptr->entrytable);
+ else {
+ duprintf("get_entries: I've got %u not %u!\n",
+ t->private->size,
+ entries->size);
+ ret = -EINVAL;
+ }
+ up(&arpt_mutex);
+ } else
+ duprintf("get_entries: Can't find %s!\n",
+ entries->name);
+
+ return ret;
+}
+
+static int do_replace(void *user, unsigned int len)
+{
+ int ret;
+ struct arpt_replace tmp;
+ struct arpt_table *t;
+ struct arpt_table_info *newinfo, *oldinfo;
+ struct arpt_counters *counters;
+
+ if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
+ return -EFAULT;
+
+ /* Hack: Causes ipchains to give correct error msg --RR */
+ if (len != sizeof(tmp) + tmp.size)
+ return -ENOPROTOOPT;
+
+ /* Pedantry: prevent them from hitting BUG() in vmalloc.c --RR */
+ if ((SMP_ALIGN(tmp.size) >> PAGE_SHIFT) + 2 > num_physpages)
+ return -ENOMEM;
+
+ newinfo = vmalloc(sizeof(struct arpt_table_info)
+ + SMP_ALIGN(tmp.size) * smp_num_cpus);
+ if (!newinfo)
+ return -ENOMEM;
+
+ if (copy_from_user(newinfo->entries, user + sizeof(tmp),
+ tmp.size) != 0) {
+ ret = -EFAULT;
+ goto free_newinfo;
+ }
+
+ counters = vmalloc(tmp.num_counters * sizeof(struct arpt_counters));
+ if (!counters) {
+ ret = -ENOMEM;
+ goto free_newinfo;
+ }
+ memset(counters, 0, tmp.num_counters * sizeof(struct arpt_counters));
+
+ ret = translate_table(tmp.name, tmp.valid_hooks,
+ newinfo, tmp.size, tmp.num_entries,
+ tmp.hook_entry, tmp.underflow);
+ if (ret != 0)
+ goto free_newinfo_counters;
+
+ duprintf("arp_tables: Translated table\n");
+
+ t = arpt_find_table_lock(tmp.name, &ret, &arpt_mutex);
+ if (!t)
+ goto free_newinfo_counters_untrans;
+
+ /* You lied! */
+ if (tmp.valid_hooks != t->valid_hooks) {
+ duprintf("Valid hook crap: %08X vs %08X\n",
+ tmp.valid_hooks, t->valid_hooks);
+ ret = -EINVAL;
+ goto free_newinfo_counters_untrans_unlock;
+ }
+
+ oldinfo = replace_table(t, tmp.num_counters, newinfo, &ret);
+ if (!oldinfo)
+ goto free_newinfo_counters_untrans_unlock;
+
+ /* Update module usage count based on number of rules */
+ duprintf("do_replace: oldnum=%u, initnum=%u, newnum=%u\n",
+ oldinfo->number, oldinfo->initial_entries, newinfo->number);
+ if (t->me && (oldinfo->number <= oldinfo->initial_entries) &&
+ (newinfo->number > oldinfo->initial_entries))
+ __MOD_INC_USE_COUNT(t->me);
+ else if (t->me && (oldinfo->number > oldinfo->initial_entries) &&
+ (newinfo->number <= oldinfo->initial_entries))
+ __MOD_DEC_USE_COUNT(t->me);
+
+ /* Get the old counters. */
+ get_counters(oldinfo, counters);
+ /* Decrease module usage counts and free resource */
+ ARPT_ENTRY_ITERATE(oldinfo->entries, oldinfo->size, cleanup_entry,NULL);
+ vfree(oldinfo);
+ /* Silent error: too late now. */
+ copy_to_user(tmp.counters, counters,
+ sizeof(struct arpt_counters) * tmp.num_counters);
+ vfree(counters);
+ up(&arpt_mutex);
+ return 0;
+
+ free_newinfo_counters_untrans_unlock:
+ up(&arpt_mutex);
+ free_newinfo_counters_untrans:
+ ARPT_ENTRY_ITERATE(newinfo->entries, newinfo->size, cleanup_entry, NULL);
+ free_newinfo_counters:
+ vfree(counters);
+ free_newinfo:
+ vfree(newinfo);
+ return ret;
+}
+
+/* We're lazy, and add to the first CPU; overflow works its fey magic
+ * and everything is OK.
+ */
+static inline int add_counter_to_entry(struct arpt_entry *e,
+ const struct arpt_counters addme[],
+ unsigned int *i)
+{
+
+ ADD_COUNTER(e->counters, addme[*i].bcnt, addme[*i].pcnt);
+
+ (*i)++;
+ return 0;
+}
+
+static int do_add_counters(void *user, unsigned int len)
+{
+ unsigned int i;
+ struct arpt_counters_info tmp, *paddc;
+ struct arpt_table *t;
+ int ret;
+
+ if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
+ return -EFAULT;
+
+ if (len != sizeof(tmp) + tmp.num_counters*sizeof(struct arpt_counters))
+ return -EINVAL;
+
+ paddc = vmalloc(len);
+ if (!paddc)
+ return -ENOMEM;
+
+ if (copy_from_user(paddc, user, len) != 0) {
+ ret = -EFAULT;
+ goto free;
+ }
+
+ t = arpt_find_table_lock(tmp.name, &ret, &arpt_mutex);
+ if (!t)
+ goto free;
+
+ write_lock_bh(&t->lock);
+ if (t->private->number != paddc->num_counters) {
+ ret = -EINVAL;
+ goto unlock_up_free;
+ }
+
+ i = 0;
+ ARPT_ENTRY_ITERATE(t->private->entries,
+ t->private->size,
+ add_counter_to_entry,
+ paddc->counters,
+ &i);
+ unlock_up_free:
+ write_unlock_bh(&t->lock);
+ up(&arpt_mutex);
+ free:
+ vfree(paddc);
+
+ return ret;
+}
+
+static int do_arpt_set_ctl(struct sock *sk, int cmd, void *user, unsigned int len)
+{
+ int ret;
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ switch (cmd) {
+ case ARPT_SO_SET_REPLACE:
+ ret = do_replace(user, len);
+ break;
+
+ case ARPT_SO_SET_ADD_COUNTERS:
+ ret = do_add_counters(user, len);
+ break;
+
+ default:
+ duprintf("do_arpt_set_ctl: unknown request %i\n", cmd);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int do_arpt_get_ctl(struct sock *sk, int cmd, void *user, int *len)
+{
+ int ret;
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ switch (cmd) {
+ case ARPT_SO_GET_INFO: {
+ char name[ARPT_TABLE_MAXNAMELEN];
+ struct arpt_table *t;
+
+ if (*len != sizeof(struct arpt_getinfo)) {
+ duprintf("length %u != %Zu\n", *len,
+ sizeof(struct arpt_getinfo));
+ ret = -EINVAL;
+ break;
+ }
+
+ if (copy_from_user(name, user, sizeof(name)) != 0) {
+ ret = -EFAULT;
+ break;
+ }
+ name[ARPT_TABLE_MAXNAMELEN-1] = '\0';
+ t = arpt_find_table_lock(name, &ret, &arpt_mutex);
+ if (t) {
+ struct arpt_getinfo info;
+
+ info.valid_hooks = t->valid_hooks;
+ memcpy(info.hook_entry, t->private->hook_entry,
+ sizeof(info.hook_entry));
+ memcpy(info.underflow, t->private->underflow,
+ sizeof(info.underflow));
+ info.num_entries = t->private->number;
+ info.size = t->private->size;
+ strcpy(info.name, name);
+
+ if (copy_to_user(user, &info, *len) != 0)
+ ret = -EFAULT;
+ else
+ ret = 0;
+
+ up(&arpt_mutex);
+ }
+ }
+ break;
+
+ case ARPT_SO_GET_ENTRIES: {
+ struct arpt_get_entries get;
+
+ if (*len < sizeof(get)) {
+ duprintf("get_entries: %u < %Zu\n", *len, sizeof(get));
+ ret = -EINVAL;
+ } else if (copy_from_user(&get, user, sizeof(get)) != 0) {
+ ret = -EFAULT;
+ } else if (*len != sizeof(struct arpt_get_entries) + get.size) {
+ duprintf("get_entries: %u != %Zu\n", *len,
+ sizeof(struct arpt_get_entries) + get.size);
+ ret = -EINVAL;
+ } else
+ ret = get_entries(&get, user);
+ break;
+ }
+
+ default:
+ duprintf("do_arpt_get_ctl: unknown request %i\n", cmd);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/* Registration hooks for targets. */
+int arpt_register_target(struct arpt_target *target)
+{
+ int ret;
+
+ MOD_INC_USE_COUNT;
+ ret = down_interruptible(&arpt_mutex);
+ if (ret != 0) {
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+ if (!list_named_insert(&arpt_target, target)) {
+ duprintf("arpt_register_target: `%s' already in list!\n",
+ target->name);
+ ret = -EINVAL;
+ MOD_DEC_USE_COUNT;
+ }
+ up(&arpt_mutex);
+ return ret;
+}
+
+void arpt_unregister_target(struct arpt_target *target)
+{
+ down(&arpt_mutex);
+ LIST_DELETE(&arpt_target, target);
+ up(&arpt_mutex);
+ MOD_DEC_USE_COUNT;
+}
+
+int arpt_register_table(struct arpt_table *table)
+{
+ int ret;
+ struct arpt_table_info *newinfo;
+ static struct arpt_table_info bootstrap
+ = { 0, 0, 0, { 0 }, { 0 }, { } };
+
+ MOD_INC_USE_COUNT;
+ newinfo = vmalloc(sizeof(struct arpt_table_info)
+ + SMP_ALIGN(table->table->size) * smp_num_cpus);
+ if (!newinfo) {
+ ret = -ENOMEM;
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+ memcpy(newinfo->entries, table->table->entries, table->table->size);
+
+ ret = translate_table(table->name, table->valid_hooks,
+ newinfo, table->table->size,
+ table->table->num_entries,
+ table->table->hook_entry,
+ table->table->underflow);
+ duprintf("arpt_register_table: translate table gives %d\n", ret);
+ if (ret != 0) {
+ vfree(newinfo);
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+
+ ret = down_interruptible(&arpt_mutex);
+ if (ret != 0) {
+ vfree(newinfo);
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+
+ /* Don't autoload: we'd eat our tail... */
+ if (list_named_find(&arpt_tables, table->name)) {
+ ret = -EEXIST;
+ goto free_unlock;
+ }
+
+ /* Simplifies replace_table code. */
+ table->private = &bootstrap;
+ if (!replace_table(table, 0, newinfo, &ret))
+ goto free_unlock;
+
+ duprintf("table->private->number = %u\n",
+ table->private->number);
+
+ /* save number of initial entries */
+ table->private->initial_entries = table->private->number;
+
+ table->lock = RW_LOCK_UNLOCKED;
+ list_prepend(&arpt_tables, table);
+
+ unlock:
+ up(&arpt_mutex);
+ return ret;
+
+ free_unlock:
+ vfree(newinfo);
+ MOD_DEC_USE_COUNT;
+ goto unlock;
+}
+
+void arpt_unregister_table(struct arpt_table *table)
+{
+ down(&arpt_mutex);
+ LIST_DELETE(&arpt_tables, table);
+ up(&arpt_mutex);
+
+ /* Decrease module usage counts and free resources */
+ ARPT_ENTRY_ITERATE(table->private->entries, table->private->size,
+ cleanup_entry, NULL);
+ vfree(table->private);
+ MOD_DEC_USE_COUNT;
+}
+
+/* The built-in targets: standard (NULL) and error. */
+static struct arpt_target arpt_standard_target
+= { { NULL, NULL }, ARPT_STANDARD_TARGET, NULL, NULL, NULL };
+static struct arpt_target arpt_error_target
+= { { NULL, NULL }, ARPT_ERROR_TARGET, arpt_error, NULL, NULL };
+
+static struct nf_sockopt_ops arpt_sockopts
+= { { NULL, NULL }, PF_INET, ARPT_BASE_CTL, ARPT_SO_SET_MAX+1, do_arpt_set_ctl,
+ ARPT_BASE_CTL, ARPT_SO_GET_MAX+1, do_arpt_get_ctl, 0, NULL };
+
+#ifdef CONFIG_PROC_FS
+static inline int print_name(const struct arpt_table *t,
+ off_t start_offset, char *buffer, int length,
+ off_t *pos, unsigned int *count)
+{
+ if ((*count)++ >= start_offset) {
+ unsigned int namelen;
+
+ namelen = sprintf(buffer + *pos, "%s\n", t->name);
+ if (*pos + namelen > length) {
+ /* Stop iterating */
+ return 1;
+ }
+ *pos += namelen;
+ }
+ return 0;
+}
+
+static int arpt_get_tables(char *buffer, char **start, off_t offset, int length)
+{
+ off_t pos = 0;
+ unsigned int count = 0;
+
+ if (down_interruptible(&arpt_mutex) != 0)
+ return 0;
+
+ LIST_FIND(&arpt_tables, print_name, struct arpt_table *,
+ offset, buffer, length, &pos, &count);
+
+ up(&arpt_mutex);
+
+ /* `start' hack - see fs/proc/generic.c line ~105 */
+ *start=(char *)((unsigned long)count-offset);
+ return pos;
+}
+#endif /*CONFIG_PROC_FS*/
+
+static int __init init(void)
+{
+ int ret;
+
+ /* Noone else will be downing sem now, so we won't sleep */
+ down(&arpt_mutex);
+ list_append(&arpt_target, &arpt_standard_target);
+ list_append(&arpt_target, &arpt_error_target);
+ up(&arpt_mutex);
+
+ /* Register setsockopt */
+ ret = nf_register_sockopt(&arpt_sockopts);
+ if (ret < 0) {
+ duprintf("Unable to register sockopts.\n");
+ return ret;
+ }
+
+#ifdef CONFIG_PROC_FS
+ {
+ struct proc_dir_entry *proc;
+
+ proc = proc_net_create("arp_tables_names", 0, arpt_get_tables);
+ if (!proc) {
+ nf_unregister_sockopt(&arpt_sockopts);
+ return -ENOMEM;
+ }
+ proc->owner = THIS_MODULE;
+ }
+#endif
+
+ printk("arp_tables: (C) 2002 David S. Miller\n");
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ nf_unregister_sockopt(&arpt_sockopts);
+#ifdef CONFIG_PROC_FS
+ proc_net_remove("arp_tables_names");
+#endif
+}
+
+EXPORT_SYMBOL(arpt_register_table);
+EXPORT_SYMBOL(arpt_unregister_table);
+EXPORT_SYMBOL(arpt_do_table);
+EXPORT_SYMBOL(arpt_find_target_lock);
+EXPORT_SYMBOL(arpt_register_target);
+EXPORT_SYMBOL(arpt_unregister_target);
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/arpt_mangle.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/arpt_mangle.c
new file mode 100644
index 0000000..cf572ea
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/arpt_mangle.c
@@ -0,0 +1,101 @@
+/* module that allows mangling of the arp payload */
+#include <linux/module.h>
+#include <linux/netfilter_arp/arpt_mangle.h>
+#include <net/sock.h>
+
+static unsigned int
+target(struct sk_buff **pskb, unsigned int hooknum, const struct net_device *in,
+ const struct net_device *out, const void *targinfo, void *userinfo)
+{
+ const struct arpt_mangle *mangle = targinfo;
+ struct arphdr *arp;
+ unsigned char *arpptr;
+ int pln, hln;
+
+ if (skb_shared(*pskb) || skb_cloned(*pskb)) {
+ struct sk_buff *nskb;
+
+ nskb = skb_copy(*pskb, GFP_ATOMIC);
+ if (!nskb)
+ return NF_DROP;
+ if ((*pskb)->sk)
+ skb_set_owner_w(nskb, (*pskb)->sk);
+ kfree_skb(*pskb);
+ *pskb = nskb;
+ }
+
+ arp = (*pskb)->nh.arph;
+ arpptr = (*pskb)->nh.raw + sizeof(*arp);
+ pln = arp->ar_pln;
+ hln = arp->ar_hln;
+ /* We assume that hln was checked in the match */
+ if (mangle->flags & ARPT_MANGLE_SDEV) {
+ if (ARPT_DEV_ADDR_LEN_MAX < hln ||
+ (arpptr + hln > (**pskb).tail))
+ return NF_DROP;
+ memcpy(arpptr, mangle->src_devaddr, hln);
+ }
+ arpptr += hln;
+ if (mangle->flags & ARPT_MANGLE_SIP) {
+ if (ARPT_MANGLE_ADDR_LEN_MAX < pln ||
+ (arpptr + pln > (**pskb).tail))
+ return NF_DROP;
+ memcpy(arpptr, &mangle->u_s.src_ip, pln);
+ }
+ arpptr += pln;
+ if (mangle->flags & ARPT_MANGLE_TDEV) {
+ if (ARPT_DEV_ADDR_LEN_MAX < hln ||
+ (arpptr + hln > (**pskb).tail))
+ return NF_DROP;
+ memcpy(arpptr, mangle->tgt_devaddr, hln);
+ }
+ arpptr += hln;
+ if (mangle->flags & ARPT_MANGLE_TIP) {
+ if (ARPT_MANGLE_ADDR_LEN_MAX < pln ||
+ (arpptr + pln > (**pskb).tail))
+ return NF_DROP;
+ memcpy(arpptr, &mangle->u_t.tgt_ip, pln);
+ }
+ return mangle->target;
+}
+
+static int
+checkentry(const char *tablename, const struct arpt_entry *e, void *targinfo,
+ unsigned int targinfosize, unsigned int hook_mask)
+{
+ const struct arpt_mangle *mangle = targinfo;
+
+ if (mangle->flags & ~ARPT_MANGLE_MASK ||
+ !(mangle->flags & ARPT_MANGLE_MASK))
+ return 0;
+
+ if (mangle->target != NF_DROP && mangle->target != NF_ACCEPT &&
+ mangle->target != ARPT_CONTINUE)
+ return 0;
+ return 1;
+}
+
+static struct arpt_target arpt_mangle_reg
+= {
+ .name = "mangle",
+ .target = target,
+ .checkentry = checkentry,
+ .me = THIS_MODULE,
+};
+
+static int __init init(void)
+{
+ if (arpt_register_target(&arpt_mangle_reg))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ arpt_unregister_target(&arpt_mangle_reg);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/arptable_filter.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/arptable_filter.c
new file mode 100644
index 0000000..4e11e5b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/arptable_filter.c
@@ -0,0 +1,174 @@
+/*
+ * Filtering ARP tables module.
+ *
+ * Copyright (C) 2002 David S. Miller (davem@redhat.com)
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/netfilter_arp/arp_tables.h>
+
+#define FILTER_VALID_HOOKS ((1 << NF_ARP_IN) | (1 << NF_ARP_OUT))
+
+/* Standard entry. */
+struct arpt_standard
+{
+ struct arpt_entry entry;
+ struct arpt_standard_target target;
+};
+
+struct arpt_error_target
+{
+ struct arpt_entry_target target;
+ char errorname[ARPT_FUNCTION_MAXNAMELEN];
+};
+
+struct arpt_error
+{
+ struct arpt_entry entry;
+ struct arpt_error_target target;
+};
+
+static struct
+{
+ struct arpt_replace repl;
+ struct arpt_standard entries[2];
+ struct arpt_error term;
+} initial_table __initdata
+= { { "filter", FILTER_VALID_HOOKS, 3,
+ sizeof(struct arpt_standard) * 2 + sizeof(struct arpt_error),
+ { [NF_ARP_IN] 0,
+ [NF_ARP_OUT] sizeof(struct arpt_standard) },
+ { [NF_ARP_IN] 0,
+ [NF_ARP_OUT] sizeof(struct arpt_standard), },
+ 0, NULL, { } },
+ {
+ /* ARP_IN */
+ {
+ {
+ {
+ { 0 }, { 0 }, { 0 }, { 0 },
+ 0, 0,
+ { { 0, }, { 0, } },
+ { { 0, }, { 0, } },
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ "", "", { 0 }, { 0 },
+ 0, 0
+ },
+ sizeof(struct arpt_entry),
+ sizeof(struct arpt_standard),
+ 0,
+ { 0, 0 }, { } },
+ { { { { ARPT_ALIGN(sizeof(struct arpt_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 }
+ },
+ /* ARP_OUT */
+ {
+ {
+ {
+ { 0 }, { 0 }, { 0 }, { 0 },
+ 0, 0,
+ { { 0, }, { 0, } },
+ { { 0, }, { 0, } },
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ "", "", { 0 }, { 0 },
+ 0, 0
+ },
+ sizeof(struct arpt_entry),
+ sizeof(struct arpt_standard),
+ 0,
+ { 0, 0 }, { } },
+ { { { { ARPT_ALIGN(sizeof(struct arpt_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 }
+ }
+ },
+ /* ERROR */
+ {
+ {
+ {
+ { 0 }, { 0 }, { 0 }, { 0 },
+ 0, 0,
+ { { 0, }, { 0, } },
+ { { 0, }, { 0, } },
+ 0, 0,
+ 0, 0,
+ 0, 0,
+ "", "", { 0 }, { 0 },
+ 0, 0
+ },
+ sizeof(struct arpt_entry),
+ sizeof(struct arpt_error),
+ 0,
+ { 0, 0 }, { } },
+ { { { { ARPT_ALIGN(sizeof(struct arpt_error_target)), ARPT_ERROR_TARGET } },
+ { } },
+ "ERROR"
+ }
+ }
+};
+
+static struct arpt_table packet_filter
+= { { NULL, NULL }, "filter", &initial_table.repl,
+ FILTER_VALID_HOOKS, RW_LOCK_UNLOCKED, NULL, THIS_MODULE };
+
+/* The work comes in here from netfilter.c */
+static unsigned int arpt_hook(unsigned int hook,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ return arpt_do_table(pskb, hook, in, out, &packet_filter, NULL);
+}
+
+static struct nf_hook_ops arpt_ops[]
+= { { { NULL, NULL }, arpt_hook, NF_ARP, NF_ARP_IN, 0 },
+ { { NULL, NULL }, arpt_hook, NF_ARP, NF_ARP_OUT, 0 }
+};
+
+static int __init init(void)
+{
+ int ret;
+
+ /* Register table */
+ ret = arpt_register_table(&packet_filter);
+ if (ret < 0)
+ return ret;
+
+ /* Register hooks */
+ ret = nf_register_hook(&arpt_ops[0]);
+ if (ret < 0)
+ goto cleanup_table;
+
+ ret = nf_register_hook(&arpt_ops[1]);
+ if (ret < 0)
+ goto cleanup_hook0;
+
+ return ret;
+
+cleanup_hook0:
+ nf_unregister_hook(&arpt_ops[0]);
+
+cleanup_table:
+ arpt_unregister_table(&packet_filter);
+
+ return ret;
+}
+
+static void __exit fini(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < sizeof(arpt_ops)/sizeof(struct nf_hook_ops); i++)
+ nf_unregister_hook(&arpt_ops[i]);
+
+ arpt_unregister_table(&packet_filter);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_amanda.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_amanda.c
new file mode 100644
index 0000000..3b6e066
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_amanda.c
@@ -0,0 +1,144 @@
+/* Amanda extension for IP connection tracking, Version 0.2
+ * (C) 2002 by Brian J. Murrell <netfilter@interlinx.bc.ca>
+ * based on HW's ip_conntrack_irc.c as well as other modules
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Module load syntax:
+ * insmod ip_conntrack_amanda.o [master_timeout=n]
+ *
+ * Where master_timeout is the timeout (in seconds) of the master
+ * connection (port 10080). This defaults to 5 minutes but if
+ * your clients take longer than 5 minutes to do their work
+ * before getting back to the Amanda server, you can increase
+ * this value.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/ip.h>
+#include <net/checksum.h>
+#include <net/udp.h>
+
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_amanda.h>
+
+static unsigned int master_timeout = 300;
+
+MODULE_AUTHOR("Brian J. Murrell <netfilter@interlinx.bc.ca>");
+MODULE_DESCRIPTION("Amanda connection tracking module");
+MODULE_LICENSE("GPL");
+MODULE_PARM(master_timeout, "i");
+MODULE_PARM_DESC(master_timeout, "timeout for the master connection");
+
+static struct { char *match; int len; } conns[] = {
+ { "DATA ", 5},
+ { "MESG ", 5},
+ { "INDEX ", 6},
+};
+
+#define NUM_MSGS 3
+
+
+static int help(const struct iphdr *iph, size_t len,
+ struct ip_conntrack *ct, enum ip_conntrack_info ctinfo)
+{
+ struct ip_conntrack_expect exp;
+ struct ip_ct_amanda_expect *exp_amanda_info;
+ struct udphdr *udph = (void *)iph + iph->ihl * 4;
+ u_int32_t udplen = len - iph->ihl * 4;
+ u_int32_t datalen = udplen - sizeof(struct udphdr);
+ char *data = (char *)udph + sizeof(struct udphdr);
+ char *data_limit = data + datalen;
+ char *start = data, *tmp;
+ int i;
+
+ /* Only look at packets from the Amanda server */
+ if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL)
+ return NF_ACCEPT;
+
+ if (udplen < sizeof(struct udphdr)) {
+ if (net_ratelimit())
+ printk("amanda_help: udplen = %u\n", udplen);
+ return NF_ACCEPT;
+ }
+
+ if (udph->check &&
+ csum_tcpudp_magic(iph->saddr, iph->daddr, udplen, IPPROTO_UDP,
+ csum_partial((char *)udph, udplen, 0)))
+ return NF_ACCEPT;
+
+ /* increase the UDP timeout of the master connection as replies from
+ * Amanda clients to the server can be quite delayed */
+ ip_ct_refresh_acct(ct, ctinfo, NULL, master_timeout * HZ);
+
+ /* Search for "CONNECT " string */
+ do {
+ if (data + 8 >= data_limit)
+ return NF_ACCEPT;
+ if (!memcmp(data, "CONNECT ", 8)) {
+ data += 8;
+ break;
+ }
+ data++;
+ } while(1);
+
+ memset(&exp, 0, sizeof(exp));
+ exp.tuple.src.ip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
+ exp.tuple.dst.ip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
+ exp.tuple.dst.protonum = IPPROTO_TCP;
+ exp.mask.src.ip = 0xFFFFFFFF;
+ exp.mask.dst.ip = 0xFFFFFFFF;
+ exp.mask.dst.protonum = 0xFFFF;
+ exp.mask.dst.u.tcp.port = 0xFFFF;
+
+ exp_amanda_info = &exp.help.exp_amanda_info;
+ for (i = 0; data + conns[i].len < data_limit && *data != '\n'; data++) {
+ if (memcmp(data, conns[i].match, conns[i].len))
+ continue;
+ tmp = data += conns[i].len;
+ exp_amanda_info->offset = data - start;
+ exp_amanda_info->port = simple_strtoul(data, &data, 10);
+ exp_amanda_info->len = data - tmp;
+ if (exp_amanda_info->port == 0 || exp_amanda_info->len > 5)
+ break;
+
+ exp.tuple.dst.u.tcp.port = htons(exp_amanda_info->port);
+ ip_conntrack_expect_related(ct, &exp);
+ if (++i == NUM_MSGS)
+ break;
+ }
+
+ return NF_ACCEPT;
+}
+
+static struct ip_conntrack_helper amanda_helper;
+
+static void __exit fini(void)
+{
+ ip_conntrack_helper_unregister(&amanda_helper);
+}
+
+static int __init init(void)
+{
+ amanda_helper.tuple.src.u.udp.port = htons(10080);
+ amanda_helper.tuple.dst.protonum = IPPROTO_UDP;
+ amanda_helper.mask.src.u.udp.port = 0xFFFF;
+ amanda_helper.mask.dst.protonum = 0xFFFF;
+ amanda_helper.max_expected = NUM_MSGS;
+ amanda_helper.timeout = 180;
+ amanda_helper.flags = IP_CT_HELPER_F_REUSE_EXPECT;
+ amanda_helper.me = THIS_MODULE;
+ amanda_helper.help = help;
+ amanda_helper.name = "amanda";
+
+ return ip_conntrack_helper_register(&amanda_helper);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_core.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_core.c
new file mode 100644
index 0000000..155cf58
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_core.c
@@ -0,0 +1,1517 @@
+/* Connection state tracking for netfilter. This is separated from,
+ but required by, the NAT layer; it can also be used by an iptables
+ extension. */
+
+/* (c) 1999 Paul `Rusty' Russell. Licenced under the GNU General
+ * Public Licence.
+ *
+ * 23 Apr 2001: Harald Welte <laforge@gnumonks.org>
+ * - new API and handling of conntrack/nat helpers
+ * - now capable of multiple expectations for one master
+ * 16 Jul 2002: Harald Welte <laforge@gnumonks.org>
+ * - add usage/reference counts to ip_conntrack_expect
+ * - export ip_conntrack[_expect]_{find_get,put} functions
+ * */
+
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/ip.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/vmalloc.h>
+#include <linux/brlock.h>
+#include <net/checksum.h>
+#include <linux/stddef.h>
+#include <linux/sysctl.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <linux/jhash.h>
+/* For ERR_PTR(). Yeah, I know... --RR */
+#include <linux/fs.h>
+#include <linux/notifier.h>
+
+/* This rwlock protects the main hash table, protocol/helper/expected
+ registrations, conntrack timers*/
+#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_conntrack_lock)
+#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_conntrack_lock)
+
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+#include <linux/netfilter_ipv4/ip_conntrack_protocol.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_core.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#define IP_CONNTRACK_VERSION "2.1"
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+DECLARE_RWLOCK(ip_conntrack_lock);
+DECLARE_RWLOCK(ip_conntrack_expect_tuple_lock);
+
+void (*ip_conntrack_destroyed)(struct ip_conntrack *conntrack) = NULL;
+LIST_HEAD(ip_conntrack_expect_list);
+LIST_HEAD(protocol_list);
+static LIST_HEAD(helpers);
+unsigned int ip_conntrack_htable_size = 0;
+int ip_conntrack_max = 0;
+static atomic_t ip_conntrack_count = ATOMIC_INIT(0);
+struct list_head *ip_conntrack_hash;
+static kmem_cache_t *ip_conntrack_cachep;
+static LIST_HEAD(unconfirmed);
+
+#ifdef CONFIG_IP_NF_CONNTRACK_EVENTS
+struct notifier_block *ip_conntrack_chain;
+#endif /* CONFIG_IP_NF_CONNTRACK_EVENTS */
+
+extern struct ip_conntrack_protocol ip_conntrack_generic_protocol;
+
+static inline int proto_cmpfn(const struct ip_conntrack_protocol *curr,
+ u_int8_t protocol)
+{
+ return protocol == curr->proto;
+}
+
+struct ip_conntrack_protocol *__ip_ct_find_proto(u_int8_t protocol)
+{
+ struct ip_conntrack_protocol *p;
+
+ MUST_BE_READ_LOCKED(&ip_conntrack_lock);
+ p = LIST_FIND(&protocol_list, proto_cmpfn,
+ struct ip_conntrack_protocol *, protocol);
+ if (!p)
+ p = &ip_conntrack_generic_protocol;
+
+ return p;
+}
+
+struct ip_conntrack_protocol *ip_ct_find_proto(u_int8_t protocol)
+{
+ struct ip_conntrack_protocol *p;
+
+ READ_LOCK(&ip_conntrack_lock);
+ p = __ip_ct_find_proto(protocol);
+ READ_UNLOCK(&ip_conntrack_lock);
+ return p;
+}
+
+inline void
+ip_conntrack_put(struct ip_conntrack *ct)
+{
+ IP_NF_ASSERT(ct);
+ IP_NF_ASSERT(ct->infos[0].master);
+ /* nf_conntrack_put wants to go via an info struct, so feed it
+ one at random. */
+ nf_conntrack_put(&ct->infos[0]);
+}
+
+static int ip_conntrack_hash_rnd_initted;
+static unsigned int ip_conntrack_hash_rnd;
+
+static u_int32_t
+hash_conntrack(const struct ip_conntrack_tuple *tuple)
+{
+#if 0
+ dump_tuple(tuple);
+#endif
+ return (jhash_3words(tuple->src.ip,
+ (tuple->dst.ip ^ tuple->dst.protonum),
+ (tuple->src.u.all | (tuple->dst.u.all << 16)),
+ ip_conntrack_hash_rnd) % ip_conntrack_htable_size);
+}
+
+inline int
+ip_ct_get_tuple(const struct iphdr *iph, size_t len,
+ struct ip_conntrack_tuple *tuple,
+ struct ip_conntrack_protocol *protocol)
+{
+ int ret;
+
+ /* Never happen */
+ if (iph->frag_off & htons(IP_OFFSET)) {
+ printk("ip_conntrack_core: Frag of proto %u.\n",
+ iph->protocol);
+ return 0;
+ }
+ /* Guarantee 8 protocol bytes: if more wanted, use len param */
+ else if (iph->ihl * 4 + 8 > len)
+ return 0;
+
+ tuple->src.ip = iph->saddr;
+ tuple->dst.ip = iph->daddr;
+ tuple->dst.protonum = iph->protocol;
+
+ tuple->src.u.all = tuple->dst.u.all = 0;
+
+ ret = protocol->pkt_to_tuple((u_int32_t *)iph + iph->ihl,
+ len - 4*iph->ihl,
+ tuple);
+ return ret;
+}
+
+static int
+invert_tuple(struct ip_conntrack_tuple *inverse,
+ const struct ip_conntrack_tuple *orig,
+ const struct ip_conntrack_protocol *protocol)
+{
+ inverse->src.ip = orig->dst.ip;
+ inverse->dst.ip = orig->src.ip;
+ inverse->dst.protonum = orig->dst.protonum;
+
+ inverse->src.u.all = inverse->dst.u.all = 0;
+
+ return protocol->invert_tuple(inverse, orig);
+}
+
+
+/* ip_conntrack_expect helper functions */
+
+/* Compare tuple parts depending on mask. */
+static inline int expect_cmp(const struct ip_conntrack_expect *i,
+ const struct ip_conntrack_tuple *tuple)
+{
+ MUST_BE_READ_LOCKED(&ip_conntrack_expect_tuple_lock);
+ return ip_ct_tuple_mask_cmp(tuple, &i->tuple, &i->mask);
+}
+
+static void
+destroy_expect(struct ip_conntrack_expect *exp)
+{
+ DEBUGP("destroy_expect(%p) use=%d\n", exp, atomic_read(&exp->use));
+ IP_NF_ASSERT(atomic_read(&exp->use) == 0);
+ IP_NF_ASSERT(!timer_pending(&exp->timeout));
+
+ kfree(exp);
+}
+
+inline void ip_conntrack_expect_put(struct ip_conntrack_expect *exp)
+{
+ IP_NF_ASSERT(exp);
+
+ if (atomic_dec_and_test(&exp->use)) {
+ /* usage count dropped to zero */
+ destroy_expect(exp);
+ }
+}
+
+static inline struct ip_conntrack_expect *
+__ip_ct_expect_find(const struct ip_conntrack_tuple *tuple)
+{
+ MUST_BE_READ_LOCKED(&ip_conntrack_lock);
+ MUST_BE_READ_LOCKED(&ip_conntrack_expect_tuple_lock);
+ return LIST_FIND(&ip_conntrack_expect_list, expect_cmp,
+ struct ip_conntrack_expect *, tuple);
+}
+
+/* Find a expectation corresponding to a tuple. */
+struct ip_conntrack_expect *
+ip_conntrack_expect_find_get(const struct ip_conntrack_tuple *tuple)
+{
+ struct ip_conntrack_expect *exp;
+
+ READ_LOCK(&ip_conntrack_lock);
+ READ_LOCK(&ip_conntrack_expect_tuple_lock);
+ exp = __ip_ct_expect_find(tuple);
+ if (exp)
+ atomic_inc(&exp->use);
+ READ_UNLOCK(&ip_conntrack_expect_tuple_lock);
+ READ_UNLOCK(&ip_conntrack_lock);
+
+ return exp;
+}
+
+/* remove one specific expectation from all lists and drop refcount,
+ * does _NOT_ delete the timer. */
+static void __unexpect_related(struct ip_conntrack_expect *expect)
+{
+ DEBUGP("unexpect_related(%p)\n", expect);
+ MUST_BE_WRITE_LOCKED(&ip_conntrack_lock);
+
+ /* we're not allowed to unexpect a confirmed expectation! */
+ IP_NF_ASSERT(!expect->sibling);
+
+ /* delete from global and local lists */
+ list_del(&expect->list);
+ list_del(&expect->expected_list);
+
+ /* decrement expect-count of master conntrack */
+ if (expect->expectant)
+ expect->expectant->expecting--;
+
+ ip_conntrack_expect_put(expect);
+}
+
+/* remove one specific expecatation from all lists, drop refcount
+ * and expire timer.
+ * This function can _NOT_ be called for confirmed expects! */
+static void unexpect_related(struct ip_conntrack_expect *expect)
+{
+ IP_NF_ASSERT(expect->expectant);
+ IP_NF_ASSERT(expect->expectant->helper);
+ /* if we are supposed to have a timer, but we can't delete
+ * it: race condition. __unexpect_related will
+ * be calledd by timeout function */
+ if (expect->expectant->helper->timeout
+ && !del_timer(&expect->timeout))
+ return;
+
+ __unexpect_related(expect);
+}
+
+/* delete all unconfirmed expectations for this conntrack */
+static void remove_expectations(struct ip_conntrack *ct, int drop_refcount)
+{
+ struct list_head *exp_entry, *next;
+ struct ip_conntrack_expect *exp;
+
+ DEBUGP("remove_expectations(%p)\n", ct);
+
+ list_for_each_safe(exp_entry, next, &ct->sibling_list) {
+ exp = list_entry(exp_entry, struct ip_conntrack_expect,
+ expected_list);
+
+ /* we skip established expectations, as we want to delete
+ * the un-established ones only */
+ if (exp->sibling) {
+ DEBUGP("remove_expectations: skipping established %p of %p\n", exp->sibling, ct);
+ if (drop_refcount) {
+ /* Indicate that this expectations parent is dead */
+ ip_conntrack_put(exp->expectant);
+ exp->expectant = NULL;
+ }
+ continue;
+ }
+
+ IP_NF_ASSERT(list_inlist(&ip_conntrack_expect_list, exp));
+ IP_NF_ASSERT(exp->expectant == ct);
+
+ /* delete expectation from global and private lists */
+ unexpect_related(exp);
+ }
+}
+
+static void
+clean_from_lists(struct ip_conntrack *ct)
+{
+ unsigned int ho, hr;
+
+ DEBUGP("clean_from_lists(%p)\n", ct);
+ MUST_BE_WRITE_LOCKED(&ip_conntrack_lock);
+
+ ho = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
+ hr = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
+ LIST_DELETE(&ip_conntrack_hash[ho], &ct->tuplehash[IP_CT_DIR_ORIGINAL]);
+ LIST_DELETE(&ip_conntrack_hash[hr], &ct->tuplehash[IP_CT_DIR_REPLY]);
+
+ /* Destroy all un-established, pending expectations */
+ remove_expectations(ct, 1);
+}
+
+static void
+destroy_conntrack(struct nf_conntrack *nfct)
+{
+ struct ip_conntrack *ct = (struct ip_conntrack *)nfct, *master = NULL;
+ struct ip_conntrack_protocol *proto;
+
+ DEBUGP("destroy_conntrack(%p)\n", ct);
+ IP_NF_ASSERT(atomic_read(&nfct->use) == 0);
+ IP_NF_ASSERT(!timer_pending(&ct->timeout));
+
+ set_bit(IPS_DESTROYED_BIT, &ct->status);
+
+ /* To make sure we don't get any weird locking issues here:
+ * destroy_conntrack() MUST NOT be called with a write lock
+ * to ip_conntrack_lock!!! -HW */
+ proto = ip_ct_find_proto(ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.protonum);
+ if (proto && proto->destroy)
+ proto->destroy(ct);
+
+ if (ip_conntrack_destroyed)
+ ip_conntrack_destroyed(ct);
+
+ WRITE_LOCK(&ip_conntrack_lock);
+ /* Make sure don't leave any orphaned expectations lying around */
+ if (ct->expecting)
+ remove_expectations(ct, 1);
+
+ /* We overload first tuple to link into unconfirmed list. */
+ if (!is_confirmed(ct)) {
+ BUG_ON(list_empty(&ct->tuplehash[IP_CT_DIR_ORIGINAL].list));
+ list_del(&ct->tuplehash[IP_CT_DIR_ORIGINAL].list);
+ }
+
+ /* Delete our master expectation */
+ if (ct->master) {
+ if (ct->master->expectant) {
+ /* can't call __unexpect_related here,
+ * since it would screw up expect_list */
+ list_del(&ct->master->expected_list);
+ master = ct->master->expectant;
+ }
+ kfree(ct->master);
+ }
+ WRITE_UNLOCK(&ip_conntrack_lock);
+
+ if (master)
+ ip_conntrack_put(master);
+
+ DEBUGP("destroy_conntrack: returning ct=%p to slab\n", ct);
+ kmem_cache_free(ip_conntrack_cachep, ct);
+ atomic_dec(&ip_conntrack_count);
+}
+
+static void death_by_timeout(unsigned long ul_conntrack)
+{
+ struct ip_conntrack *ct = (void *)ul_conntrack;
+
+ ip_conntrack_event(IPCT_DESTROY, ct);
+ WRITE_LOCK(&ip_conntrack_lock);
+ clean_from_lists(ct);
+ WRITE_UNLOCK(&ip_conntrack_lock);
+ ip_conntrack_put(ct);
+}
+
+static inline int
+conntrack_tuple_cmp(const struct ip_conntrack_tuple_hash *i,
+ const struct ip_conntrack_tuple *tuple,
+ const struct ip_conntrack *ignored_conntrack)
+{
+ MUST_BE_READ_LOCKED(&ip_conntrack_lock);
+ return i->ctrack != ignored_conntrack
+ && ip_ct_tuple_equal(tuple, &i->tuple);
+}
+
+static struct ip_conntrack_tuple_hash *
+__ip_conntrack_find(const struct ip_conntrack_tuple *tuple,
+ const struct ip_conntrack *ignored_conntrack)
+{
+ struct ip_conntrack_tuple_hash *h;
+ unsigned int hash = hash_conntrack(tuple);
+
+ MUST_BE_READ_LOCKED(&ip_conntrack_lock);
+ h = LIST_FIND(&ip_conntrack_hash[hash],
+ conntrack_tuple_cmp,
+ struct ip_conntrack_tuple_hash *,
+ tuple, ignored_conntrack);
+ return h;
+}
+
+/* Find a connection corresponding to a tuple. */
+struct ip_conntrack_tuple_hash *
+ip_conntrack_find_get(const struct ip_conntrack_tuple *tuple,
+ const struct ip_conntrack *ignored_conntrack)
+{
+ struct ip_conntrack_tuple_hash *h;
+
+ READ_LOCK(&ip_conntrack_lock);
+ h = __ip_conntrack_find(tuple, ignored_conntrack);
+ if (h)
+ atomic_inc(&h->ctrack->ct_general.use);
+ READ_UNLOCK(&ip_conntrack_lock);
+
+ return h;
+}
+
+static inline struct ip_conntrack *
+__ip_conntrack_get(struct nf_ct_info *nfct, enum ip_conntrack_info *ctinfo)
+{
+ struct ip_conntrack *ct
+ = (struct ip_conntrack *)nfct->master;
+
+ /* ctinfo is the index of the nfct inside the conntrack */
+ *ctinfo = nfct - ct->infos;
+ IP_NF_ASSERT(*ctinfo >= 0 && *ctinfo < IP_CT_NUMBER);
+ return ct;
+}
+
+/* Return conntrack and conntrack_info given skb->nfct->master */
+struct ip_conntrack *
+ip_conntrack_get(struct sk_buff *skb, enum ip_conntrack_info *ctinfo)
+{
+ if (skb->nfct)
+ return __ip_conntrack_get(skb->nfct, ctinfo);
+ return NULL;
+}
+
+/* Confirm a connection given skb->nfct; places it in hash table */
+int
+__ip_conntrack_confirm(struct sk_buff *skb)
+{
+ unsigned int hash, repl_hash;
+ struct ip_conntrack *ct;
+ enum ip_conntrack_info ctinfo;
+
+ ct = ip_conntrack_get(skb, &ctinfo);
+
+ /* ipt_REJECT uses ip_conntrack_attach to attach related
+ ICMP/TCP RST packets in other direction. Actual packet
+ which created connection will be IP_CT_NEW or for an
+ expected connection, IP_CT_RELATED. */
+ if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL)
+ return NF_ACCEPT;
+
+ hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
+ repl_hash = hash_conntrack(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
+
+ /* We're not in hash table, and we refuse to set up related
+ connections for unconfirmed conns. But packet copies and
+ REJECT will give spurious warnings here. */
+ /* IP_NF_ASSERT(atomic_read(&ct->ct_general.use) == 1); */
+
+ /* No external references means noone else could have
+ confirmed us. */
+ IP_NF_ASSERT(!is_confirmed(ct));
+ DEBUGP("Confirming conntrack %p\n", ct);
+
+ WRITE_LOCK(&ip_conntrack_lock);
+ /* See if there's one in the list already, including reverse:
+ NAT could have grabbed it without realizing, since we're
+ not in the hash. If there is, we lost race. */
+ if (!LIST_FIND(&ip_conntrack_hash[hash],
+ conntrack_tuple_cmp,
+ struct ip_conntrack_tuple_hash *,
+ &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, NULL)
+ && !LIST_FIND(&ip_conntrack_hash[repl_hash],
+ conntrack_tuple_cmp,
+ struct ip_conntrack_tuple_hash *,
+ &ct->tuplehash[IP_CT_DIR_REPLY].tuple, NULL)) {
+ /* Remove from unconfirmed list */
+ list_del(&ct->tuplehash[IP_CT_DIR_ORIGINAL].list);
+
+ list_prepend(&ip_conntrack_hash[hash],
+ &ct->tuplehash[IP_CT_DIR_ORIGINAL]);
+ list_prepend(&ip_conntrack_hash[repl_hash],
+ &ct->tuplehash[IP_CT_DIR_REPLY]);
+ /* Timer relative to confirmation time, not original
+ setting time, otherwise we'd get timer wrap in
+ weird delay cases. */
+ ct->timeout.expires += jiffies;
+ add_timer(&ct->timeout);
+ atomic_inc(&ct->ct_general.use);
+ set_bit(IPS_CONFIRMED_BIT, &ct->status);
+ WRITE_UNLOCK(&ip_conntrack_lock);
+ ip_conntrack_event_cache(master_ct(ct) ?
+ IPCT_RELATED : IPCT_NEW, skb);
+
+ return NF_ACCEPT;
+ }
+
+ WRITE_UNLOCK(&ip_conntrack_lock);
+ return NF_DROP;
+}
+
+/* Returns true if a connection correspondings to the tuple (required
+ for NAT). */
+int
+ip_conntrack_tuple_taken(const struct ip_conntrack_tuple *tuple,
+ const struct ip_conntrack *ignored_conntrack)
+{
+ struct ip_conntrack_tuple_hash *h;
+
+ READ_LOCK(&ip_conntrack_lock);
+ h = __ip_conntrack_find(tuple, ignored_conntrack);
+ READ_UNLOCK(&ip_conntrack_lock);
+
+ return h != NULL;
+}
+
+/* Returns conntrack if it dealt with ICMP, and filled in skb fields */
+struct ip_conntrack *
+icmp_error_track(struct sk_buff *skb,
+ enum ip_conntrack_info *ctinfo,
+ unsigned int hooknum)
+{
+ const struct iphdr *iph = skb->nh.iph;
+ struct icmphdr *hdr;
+ struct ip_conntrack_tuple innertuple, origtuple;
+ struct iphdr *inner;
+ size_t datalen;
+ struct ip_conntrack_protocol *innerproto;
+ struct ip_conntrack_tuple_hash *h;
+
+ IP_NF_ASSERT(iph->protocol == IPPROTO_ICMP);
+ IP_NF_ASSERT(skb->nfct == NULL);
+
+ hdr = (struct icmphdr *)((u_int32_t *)iph + iph->ihl);
+ inner = (struct iphdr *)(hdr + 1);
+ datalen = skb->len - iph->ihl*4 - sizeof(*hdr);
+
+ if (skb->len < iph->ihl * 4 + sizeof(*hdr) + sizeof(*iph)) {
+ DEBUGP("icmp_error_track: too short\n");
+ return NULL;
+ }
+
+ if (hdr->type != ICMP_DEST_UNREACH
+ && hdr->type != ICMP_SOURCE_QUENCH
+ && hdr->type != ICMP_TIME_EXCEEDED
+ && hdr->type != ICMP_PARAMETERPROB
+ && hdr->type != ICMP_REDIRECT)
+ return NULL;
+
+ /* Ignore ICMP's containing fragments (shouldn't happen) */
+ if (inner->frag_off & htons(IP_OFFSET)) {
+ DEBUGP("icmp_error_track: fragment of proto %u\n",
+ inner->protocol);
+ return NULL;
+ }
+
+ /* Ignore it if the checksum's bogus. */
+ if (ip_compute_csum((unsigned char *)hdr, sizeof(*hdr) + datalen)) {
+ DEBUGP("icmp_error_track: bad csum\n");
+ return NULL;
+ }
+
+ innerproto = ip_ct_find_proto(inner->protocol);
+ /* Are they talking about one of our connections? */
+ if (inner->ihl * 4 + 8 > datalen
+ || !ip_ct_get_tuple(inner, datalen, &origtuple, innerproto)) {
+ DEBUGP("icmp_error: ! get_tuple p=%u (%u*4+%u dlen=%u)\n",
+ inner->protocol, inner->ihl, 8,
+ datalen);
+ return NULL;
+ }
+
+ /* Ordinarily, we'd expect the inverted tupleproto, but it's
+ been preserved inside the ICMP. */
+ if (!invert_tuple(&innertuple, &origtuple, innerproto)) {
+ DEBUGP("icmp_error_track: Can't invert tuple\n");
+ return NULL;
+ }
+
+ *ctinfo = IP_CT_RELATED;
+
+ h = ip_conntrack_find_get(&innertuple, NULL);
+ if (!h) {
+ /* Locally generated ICMPs will match inverted if they
+ haven't been SNAT'ed yet */
+ /* FIXME: NAT code has to handle half-done double NAT --RR */
+ if (hooknum == NF_IP_LOCAL_OUT)
+ h = ip_conntrack_find_get(&origtuple, NULL);
+
+ if (!h) {
+ DEBUGP("icmp_error_track: no match\n");
+ return NULL;
+ }
+ /* Reverse direction from that found */
+ if (DIRECTION(h) != IP_CT_DIR_REPLY)
+ *ctinfo += IP_CT_IS_REPLY;
+ } else {
+ if (DIRECTION(h) == IP_CT_DIR_REPLY)
+ *ctinfo += IP_CT_IS_REPLY;
+ }
+
+ /* Update skb to refer to this connection */
+ skb->nfct = &h->ctrack->infos[*ctinfo];
+ return h->ctrack;
+}
+
+/* There's a small race here where we may free a just-assured
+ connection. Too bad: we're in trouble anyway. */
+static inline int unreplied(const struct ip_conntrack_tuple_hash *i)
+{
+ return !(test_bit(IPS_ASSURED_BIT, &i->ctrack->status));
+}
+
+static int early_drop(struct list_head *chain)
+{
+ /* Traverse backwards: gives us oldest, which is roughly LRU */
+ struct ip_conntrack_tuple_hash *h;
+ int dropped = 0;
+
+ READ_LOCK(&ip_conntrack_lock);
+ h = LIST_FIND_B(chain, unreplied, struct ip_conntrack_tuple_hash *);
+ if (h)
+ atomic_inc(&h->ctrack->ct_general.use);
+ READ_UNLOCK(&ip_conntrack_lock);
+
+ if (!h)
+ return dropped;
+
+ if (del_timer(&h->ctrack->timeout)) {
+ death_by_timeout((unsigned long)h->ctrack);
+ dropped = 1;
+ }
+ ip_conntrack_put(h->ctrack);
+ return dropped;
+}
+
+static inline int helper_cmp(const struct ip_conntrack_helper *i,
+ const struct ip_conntrack_tuple *rtuple)
+{
+ return ip_ct_tuple_mask_cmp(rtuple, &i->tuple, &i->mask);
+}
+
+struct ip_conntrack_helper *ip_ct_find_helper(const struct ip_conntrack_tuple *tuple)
+{
+ return LIST_FIND(&helpers, helper_cmp,
+ struct ip_conntrack_helper *,
+ tuple);
+}
+
+/* Allocate a new conntrack: we return -ENOMEM if classification
+ failed due to stress. Otherwise it really is unclassifiable. */
+static struct ip_conntrack_tuple_hash *
+init_conntrack(const struct ip_conntrack_tuple *tuple,
+ struct ip_conntrack_protocol *protocol,
+ struct sk_buff *skb)
+{
+ struct ip_conntrack *conntrack;
+ struct ip_conntrack_tuple repl_tuple;
+ size_t hash;
+ struct ip_conntrack_expect *expected;
+ int i;
+ static unsigned int drop_next = 0;
+
+ if (!ip_conntrack_hash_rnd_initted) {
+ get_random_bytes(&ip_conntrack_hash_rnd, 4);
+ ip_conntrack_hash_rnd_initted = 1;
+ }
+
+ hash = hash_conntrack(tuple);
+
+ if (ip_conntrack_max &&
+ atomic_read(&ip_conntrack_count) >= ip_conntrack_max) {
+ /* Try dropping from random chain, or else from the
+ chain about to put into (in case they're trying to
+ bomb one hash chain). */
+ unsigned int next = (drop_next++)%ip_conntrack_htable_size;
+
+ if (!early_drop(&ip_conntrack_hash[next])
+ && !early_drop(&ip_conntrack_hash[hash])) {
+ if (net_ratelimit())
+ printk(KERN_WARNING
+ "ip_conntrack: table full, dropping"
+ " packet.\n");
+ return ERR_PTR(-ENOMEM);
+ }
+ }
+
+ if (!invert_tuple(&repl_tuple, tuple, protocol)) {
+ DEBUGP("Can't invert tuple.\n");
+ return NULL;
+ }
+
+ conntrack = kmem_cache_alloc(ip_conntrack_cachep, GFP_ATOMIC);
+ if (!conntrack) {
+ DEBUGP("Can't allocate conntrack.\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ memset(conntrack, 0, sizeof(*conntrack));
+ atomic_set(&conntrack->ct_general.use, 1);
+ conntrack->ct_general.destroy = destroy_conntrack;
+ conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple = *tuple;
+ conntrack->tuplehash[IP_CT_DIR_ORIGINAL].ctrack = conntrack;
+ conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = repl_tuple;
+ conntrack->tuplehash[IP_CT_DIR_REPLY].ctrack = conntrack;
+ for (i=0; i < IP_CT_NUMBER; i++)
+ conntrack->infos[i].master = &conntrack->ct_general;
+
+ if (!protocol->new(conntrack, skb->nh.iph, skb->len)) {
+ kmem_cache_free(ip_conntrack_cachep, conntrack);
+ return NULL;
+ }
+ /* Don't set timer yet: wait for confirmation */
+ init_timer(&conntrack->timeout);
+ conntrack->timeout.data = (unsigned long)conntrack;
+ conntrack->timeout.function = death_by_timeout;
+
+ INIT_LIST_HEAD(&conntrack->sibling_list);
+
+ WRITE_LOCK(&ip_conntrack_lock);
+ /* Need finding and deleting of expected ONLY if we win race */
+ READ_LOCK(&ip_conntrack_expect_tuple_lock);
+ expected = LIST_FIND(&ip_conntrack_expect_list, expect_cmp,
+ struct ip_conntrack_expect *, tuple);
+ READ_UNLOCK(&ip_conntrack_expect_tuple_lock);
+
+ /* If master is not in hash table yet (ie. packet hasn't left
+ this machine yet), how can other end know about expected?
+ Hence these are not the droids you are looking for (if
+ master ct never got confirmed, we'd hold a reference to it
+ and weird things would happen to future packets). */
+ if (expected && !is_confirmed(expected->expectant))
+ expected = NULL;
+
+ /* Look up the conntrack helper for master connections only */
+ if (!expected)
+ conntrack->helper = ip_ct_find_helper(&repl_tuple);
+
+ /* If the expectation is dying, then this is a looser. */
+ if (expected && expected->expectant && expected->expectant->helper
+ && expected->expectant->helper->timeout
+ && ! del_timer(&expected->timeout))
+ expected = NULL;
+
+ if (expected) {
+ DEBUGP("conntrack: expectation arrives ct=%p exp=%p\n",
+ conntrack, expected);
+ /* Welcome, Mr. Bond. We've been expecting you... */
+ __set_bit(IPS_EXPECTED_BIT, &conntrack->status);
+ conntrack->master = expected;
+ expected->sibling = conntrack;
+#if CONFIG_IP_NF_CONNTRACK_MARK
+ conntrack->mark = expected->expectant->mark;
+#endif
+ LIST_DELETE(&ip_conntrack_expect_list, expected);
+ expected->expectant->expecting--;
+ nf_conntrack_get(&master_ct(conntrack)->infos[0]);
+ }
+ /* Overload tuple linked list to put us in unconfirmed list. */
+ list_add(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL].list,
+ &unconfirmed);
+
+ atomic_inc(&ip_conntrack_count);
+ WRITE_UNLOCK(&ip_conntrack_lock);
+
+ if (expected && expected->expectfn)
+ expected->expectfn(conntrack);
+ return &conntrack->tuplehash[IP_CT_DIR_ORIGINAL];
+}
+
+/* On success, returns conntrack ptr, sets skb->nfct and ctinfo */
+static inline struct ip_conntrack *
+resolve_normal_ct(struct sk_buff *skb,
+ struct ip_conntrack_protocol *proto,
+ int *set_reply,
+ unsigned int hooknum,
+ enum ip_conntrack_info *ctinfo)
+{
+ struct ip_conntrack_tuple tuple;
+ struct ip_conntrack_tuple_hash *h;
+
+ IP_NF_ASSERT((skb->nh.iph->frag_off & htons(IP_OFFSET)) == 0);
+
+ if (!ip_ct_get_tuple(skb->nh.iph, skb->len, &tuple, proto))
+ return NULL;
+
+ /* look for tuple match */
+ h = ip_conntrack_find_get(&tuple, NULL);
+ if (!h) {
+ h = init_conntrack(&tuple, proto, skb);
+ if (!h)
+ return NULL;
+ if (IS_ERR(h))
+ return (void *)h;
+ }
+
+ /* It exists; we have (non-exclusive) reference. */
+ if (DIRECTION(h) == IP_CT_DIR_REPLY) {
+ *ctinfo = IP_CT_ESTABLISHED + IP_CT_IS_REPLY;
+ /* Please set reply bit if this packet OK */
+ *set_reply = 1;
+ } else {
+ /* Once we've had two way comms, always ESTABLISHED. */
+ if (test_bit(IPS_SEEN_REPLY_BIT, &h->ctrack->status)) {
+ DEBUGP("ip_conntrack_in: normal packet for %p\n",
+ h->ctrack);
+ *ctinfo = IP_CT_ESTABLISHED;
+ } else if (test_bit(IPS_EXPECTED_BIT, &h->ctrack->status)) {
+ DEBUGP("ip_conntrack_in: related packet for %p\n",
+ h->ctrack);
+ *ctinfo = IP_CT_RELATED;
+ } else {
+ DEBUGP("ip_conntrack_in: new packet for %p\n",
+ h->ctrack);
+ *ctinfo = IP_CT_NEW;
+ }
+ *set_reply = 0;
+ }
+ skb->nfct = &h->ctrack->infos[*ctinfo];
+ return h->ctrack;
+}
+
+/* Netfilter hook itself. */
+unsigned int ip_conntrack_in(unsigned int hooknum,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ struct ip_conntrack *ct;
+ enum ip_conntrack_info ctinfo;
+ struct ip_conntrack_protocol *proto;
+ int set_reply;
+ int ret;
+
+ /* FIXME: Do this right please. --RR */
+ (*pskb)->nfcache |= NFC_UNKNOWN;
+
+ ip_conntrack_event_cache_init(*pskb);
+
+/* Doesn't cover locally-generated broadcast, so not worth it. */
+#if 0
+ /* Ignore broadcast: no `connection'. */
+ if ((*pskb)->pkt_type == PACKET_BROADCAST) {
+ printk("Broadcast packet!\n");
+ return NF_ACCEPT;
+ } else if (((*pskb)->nh.iph->daddr & htonl(0x000000FF))
+ == htonl(0x000000FF)) {
+ printk("Should bcast: %u.%u.%u.%u->%u.%u.%u.%u (sk=%p, ptype=%u)\n",
+ NIPQUAD((*pskb)->nh.iph->saddr),
+ NIPQUAD((*pskb)->nh.iph->daddr),
+ (*pskb)->sk, (*pskb)->pkt_type);
+ }
+#endif
+
+ /* Previously seen (loopback)? Ignore. Do this before
+ fragment check. */
+ if ((*pskb)->nfct)
+ return NF_ACCEPT;
+
+ /* Gather fragments. */
+ if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
+ *pskb = ip_ct_gather_frags(*pskb,
+ hooknum == NF_IP_PRE_ROUTING ?
+ IP_DEFRAG_CONNTRACK_IN :
+ IP_DEFRAG_CONNTRACK_OUT);
+ if (!*pskb)
+ return NF_STOLEN;
+ }
+
+ proto = ip_ct_find_proto((*pskb)->nh.iph->protocol);
+
+ /* It may be an icmp error... */
+ if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP
+ && icmp_error_track(*pskb, &ctinfo, hooknum))
+ return NF_ACCEPT;
+
+ if (!(ct = resolve_normal_ct(*pskb, proto,&set_reply,hooknum,&ctinfo)))
+ /* Not valid part of a connection */
+ return NF_ACCEPT;
+
+ if (IS_ERR(ct))
+ /* Too stressed to deal. */
+ return NF_DROP;
+
+ IP_NF_ASSERT((*pskb)->nfct);
+
+ ret = proto->packet(ct, (*pskb)->nh.iph, (*pskb)->len, ctinfo);
+ if (ret == -1) {
+ /* Invalid */
+ nf_conntrack_put((*pskb)->nfct);
+ (*pskb)->nfct = NULL;
+ return NF_ACCEPT;
+ }
+
+ if (ret != NF_DROP && ct->helper) {
+ ret = ct->helper->help((*pskb)->nh.iph, (*pskb)->len,
+ ct, ctinfo);
+ if (ret == -1) {
+ /* Invalid */
+ nf_conntrack_put((*pskb)->nfct);
+ (*pskb)->nfct = NULL;
+ return NF_ACCEPT;
+ }
+ }
+ if (set_reply)
+ set_bit(IPS_SEEN_REPLY_BIT, &ct->status);
+
+ return ret;
+}
+
+int invert_tuplepr(struct ip_conntrack_tuple *inverse,
+ const struct ip_conntrack_tuple *orig)
+{
+ return invert_tuple(inverse, orig, ip_ct_find_proto(orig->dst.protonum));
+}
+
+static inline int resent_expect(const struct ip_conntrack_expect *i,
+ const struct ip_conntrack_tuple *tuple,
+ const struct ip_conntrack_tuple *mask)
+{
+ DEBUGP("resent_expect\n");
+ DEBUGP(" tuple: "); DUMP_TUPLE(&i->tuple);
+ DEBUGP("ct_tuple: "); DUMP_TUPLE(&i->ct_tuple);
+ DEBUGP("test tuple: "); DUMP_TUPLE(tuple);
+ return (((i->ct_tuple.dst.protonum == 0 && ip_ct_tuple_equal(&i->tuple, tuple))
+ || (i->ct_tuple.dst.protonum && ip_ct_tuple_equal(&i->ct_tuple, tuple)))
+ && ip_ct_tuple_equal(&i->mask, mask));
+}
+
+/* Would two expected things clash? */
+static inline int expect_clash(const struct ip_conntrack_expect *i,
+ const struct ip_conntrack_tuple *tuple,
+ const struct ip_conntrack_tuple *mask)
+{
+ /* Part covered by intersection of masks must be unequal,
+ otherwise they clash */
+ struct ip_conntrack_tuple intersect_mask
+ = { { i->mask.src.ip & mask->src.ip,
+ { i->mask.src.u.all & mask->src.u.all } },
+ { i->mask.dst.ip & mask->dst.ip,
+ { i->mask.dst.u.all & mask->dst.u.all },
+ i->mask.dst.protonum & mask->dst.protonum } };
+
+ return ip_ct_tuple_mask_cmp(&i->tuple, tuple, &intersect_mask);
+}
+
+inline void ip_conntrack_unexpect_related(struct ip_conntrack_expect *expect)
+{
+ WRITE_LOCK(&ip_conntrack_lock);
+ unexpect_related(expect);
+ WRITE_UNLOCK(&ip_conntrack_lock);
+}
+
+static void expectation_timed_out(unsigned long ul_expect)
+{
+ struct ip_conntrack_expect *expect = (void *) ul_expect;
+
+ DEBUGP("expectation %p timed out\n", expect);
+ WRITE_LOCK(&ip_conntrack_lock);
+ __unexpect_related(expect);
+ WRITE_UNLOCK(&ip_conntrack_lock);
+}
+
+/* Add a related connection. */
+int ip_conntrack_expect_related(struct ip_conntrack *related_to,
+ struct ip_conntrack_expect *expect)
+{
+ struct ip_conntrack_expect *old, *new;
+ int ret = 0;
+
+ WRITE_LOCK(&ip_conntrack_lock);
+ /* Because of the write lock, no reader can walk the lists,
+ * so there is no need to use the tuple lock too */
+
+ DEBUGP("ip_conntrack_expect_related %p\n", related_to);
+ DEBUGP("tuple: "); DUMP_TUPLE_RAW(&expect->tuple);
+ DEBUGP("mask: "); DUMP_TUPLE_RAW(&expect->mask);
+
+ old = LIST_FIND(&ip_conntrack_expect_list, resent_expect,
+ struct ip_conntrack_expect *, &expect->tuple,
+ &expect->mask);
+ if (old) {
+ /* Helper private data may contain offsets but no pointers
+ pointing into the payload - otherwise we should have to copy
+ the data filled out by the helper over the old one */
+ DEBUGP("expect_related: resent packet\n");
+ if (old->expectant == related_to &&
+ related_to->helper->timeout) {
+ if (!del_timer(&old->timeout)) {
+ /* expectation is dying. Fall through */
+ old = NULL;
+ } else {
+ old->timeout.expires = jiffies +
+ related_to->helper->timeout * HZ;
+ add_timer(&old->timeout);
+ }
+ }
+
+ if (old) {
+ WRITE_UNLOCK(&ip_conntrack_lock);
+ return -EEXIST;
+ }
+ } else if (related_to->helper->max_expected &&
+ related_to->expecting >= related_to->helper->max_expected) {
+ /* old == NULL */
+ if (!(related_to->helper->flags &
+ IP_CT_HELPER_F_REUSE_EXPECT)) {
+ WRITE_UNLOCK(&ip_conntrack_lock);
+ if (net_ratelimit())
+ printk(KERN_WARNING
+ "ip_conntrack: max number of expected "
+ "connections %i of %s reached for "
+ "%u.%u.%u.%u->%u.%u.%u.%u\n",
+ related_to->helper->max_expected,
+ related_to->helper->name,
+ NIPQUAD(related_to->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip),
+ NIPQUAD(related_to->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip));
+ return -EPERM;
+ }
+ DEBUGP("ip_conntrack: max number of expected "
+ "connections %i of %s reached for "
+ "%u.%u.%u.%u->%u.%u.%u.%u, reusing\n",
+ related_to->helper->max_expected,
+ related_to->helper->name,
+ NIPQUAD(related_to->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip),
+ NIPQUAD(related_to->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip));
+
+ /* choose the the oldest expectation to evict */
+ list_for_each_entry(old, &related_to->sibling_list,
+ expected_list)
+ if (old->sibling == NULL)
+ break;
+
+ /* We cannot fail since related_to->expecting is the number
+ * of unconfirmed expectations */
+ IP_NF_ASSERT(old && old->sibling == NULL);
+
+ /* newnat14 does not reuse the real allocated memory
+ * structures but rather unexpects the old and
+ * allocates a new. unexpect_related will decrement
+ * related_to->expecting.
+ */
+ unexpect_related(old);
+ ret = -EPERM;
+ } else if (LIST_FIND(&ip_conntrack_expect_list, expect_clash,
+ struct ip_conntrack_expect *, &expect->tuple,
+ &expect->mask)) {
+ WRITE_UNLOCK(&ip_conntrack_lock);
+ DEBUGP("expect_related: busy!\n");
+ return -EBUSY;
+ }
+
+ new = (struct ip_conntrack_expect *)
+ kmalloc(sizeof(struct ip_conntrack_expect), GFP_ATOMIC);
+ if (!new) {
+ WRITE_UNLOCK(&ip_conntrack_lock);
+ DEBUGP("expect_relaed: OOM allocating expect\n");
+ return -ENOMEM;
+ }
+
+ DEBUGP("new expectation %p of conntrack %p\n", new, related_to);
+ memcpy(new, expect, sizeof(*expect));
+ new->expectant = related_to;
+ new->sibling = NULL;
+ atomic_set(&new->use, 1);
+
+ /* add to expected list for this connection */
+ list_add_tail(&new->expected_list, &related_to->sibling_list);
+ /* add to global list of expectations */
+ list_prepend(&ip_conntrack_expect_list, &new->list);
+ /* add and start timer if required */
+ if (related_to->helper->timeout) {
+ init_timer(&new->timeout);
+ new->timeout.data = (unsigned long)new;
+ new->timeout.function = expectation_timed_out;
+ new->timeout.expires = jiffies +
+ related_to->helper->timeout * HZ;
+ add_timer(&new->timeout);
+ }
+ related_to->expecting++;
+
+ WRITE_UNLOCK(&ip_conntrack_lock);
+
+ return ret;
+}
+
+/* Change tuple in an existing expectation */
+int ip_conntrack_change_expect(struct ip_conntrack_expect *expect,
+ struct ip_conntrack_tuple *newtuple)
+{
+ int ret;
+
+ MUST_BE_READ_LOCKED(&ip_conntrack_lock);
+ WRITE_LOCK(&ip_conntrack_expect_tuple_lock);
+ DEBUGP("change_expect:\n");
+ DEBUGP("exp tuple: "); DUMP_TUPLE_RAW(&expect->tuple);
+ DEBUGP("exp mask: "); DUMP_TUPLE_RAW(&expect->mask);
+ DEBUGP("newtuple: "); DUMP_TUPLE_RAW(newtuple);
+ if (expect->ct_tuple.dst.protonum == 0) {
+ /* Never seen before */
+ DEBUGP("change expect: never seen before\n");
+ if (!ip_ct_tuple_mask_cmp(&expect->tuple, newtuple, &expect->mask)
+ && LIST_FIND(&ip_conntrack_expect_list, expect_clash,
+ struct ip_conntrack_expect *, newtuple, &expect->mask)) {
+ /* Force NAT to find an unused tuple */
+ ret = -1;
+ } else {
+ memcpy(&expect->ct_tuple, &expect->tuple, sizeof(expect->tuple));
+ memcpy(&expect->tuple, newtuple, sizeof(expect->tuple));
+ ret = 0;
+ }
+ } else {
+ /* Resent packet */
+ DEBUGP("change expect: resent packet\n");
+ if (ip_ct_tuple_equal(&expect->tuple, newtuple)) {
+ ret = 0;
+ } else {
+ /* Force NAT to choose again the same port */
+ ret = -1;
+ }
+ }
+ WRITE_UNLOCK(&ip_conntrack_expect_tuple_lock);
+
+ return ret;
+}
+
+/* Alter reply tuple (maybe alter helper). If it's already taken,
+ return 0 and don't do alteration. */
+int ip_conntrack_alter_reply(struct ip_conntrack *conntrack,
+ const struct ip_conntrack_tuple *newreply)
+{
+ WRITE_LOCK(&ip_conntrack_lock);
+ if (__ip_conntrack_find(newreply, conntrack)) {
+ WRITE_UNLOCK(&ip_conntrack_lock);
+ return 0;
+ }
+ /* Should be unconfirmed, so not in hash table yet */
+ IP_NF_ASSERT(!is_confirmed(conntrack));
+
+ DEBUGP("Altering reply tuple of %p to ", conntrack);
+ DUMP_TUPLE(newreply);
+
+ conntrack->tuplehash[IP_CT_DIR_REPLY].tuple = *newreply;
+ if (!conntrack->master && list_empty(&conntrack->sibling_list))
+ conntrack->helper = ip_ct_find_helper(newreply);
+ WRITE_UNLOCK(&ip_conntrack_lock);
+
+ return 1;
+}
+
+int ip_conntrack_helper_register(struct ip_conntrack_helper *me)
+{
+ MOD_INC_USE_COUNT;
+
+ WRITE_LOCK(&ip_conntrack_lock);
+ list_prepend(&helpers, me);
+ WRITE_UNLOCK(&ip_conntrack_lock);
+
+ return 0;
+}
+
+static inline int unhelp(struct ip_conntrack_tuple_hash *i,
+ const struct ip_conntrack_helper *me)
+{
+ if (i->ctrack->helper == me) {
+ /* Get rid of any expected. */
+ remove_expectations(i->ctrack, 0);
+ /* And *then* set helper to NULL */
+ i->ctrack->helper = NULL;
+ }
+ return 0;
+}
+
+void ip_conntrack_helper_unregister(struct ip_conntrack_helper *me)
+{
+ unsigned int i;
+
+ /* Need write lock here, to delete helper. */
+ WRITE_LOCK(&ip_conntrack_lock);
+ LIST_DELETE(&helpers, me);
+
+ /* Get rid of expecteds, set helpers to NULL. */
+ LIST_FIND_W(&unconfirmed, unhelp, struct ip_conntrack_tuple_hash*, me);
+ for (i = 0; i < ip_conntrack_htable_size; i++)
+ LIST_FIND_W(&ip_conntrack_hash[i], unhelp,
+ struct ip_conntrack_tuple_hash *, me);
+ WRITE_UNLOCK(&ip_conntrack_lock);
+
+ /* Someone could be still looking at the helper in a bh. */
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+
+ MOD_DEC_USE_COUNT;
+}
+
+static inline void ct_add_counters(struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo,
+ struct iphdr *iph)
+{
+#ifdef CONFIG_IP_NF_CT_ACCT
+ if (iph) {
+ ct->counters[CTINFO2DIR(ctinfo)].packets++;
+ ct->counters[CTINFO2DIR(ctinfo)].bytes += ntohs(iph->tot_len);
+ }
+#endif
+}
+
+/* Refresh conntrack for this many jiffies and do accounting (if iph != NULL) */
+void ip_ct_refresh_acct(struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo,
+ struct iphdr *iph,
+ unsigned long extra_jiffies)
+{
+ IP_NF_ASSERT(ct->timeout.data == (unsigned long)ct);
+
+ WRITE_LOCK(&ip_conntrack_lock);
+ /* If not in hash table, timer will not be active yet */
+ if (!is_confirmed(ct)) {
+ ct->timeout.expires = extra_jiffies;
+ ct_add_counters(ct, ctinfo, iph);
+ }
+ else {
+ /* Need del_timer for race avoidance (may already be dying). */
+ if (del_timer(&ct->timeout)) {
+ ct->timeout.expires = jiffies + extra_jiffies;
+ add_timer(&ct->timeout);
+ }
+ ct_add_counters(ct, ctinfo, iph);
+ }
+ WRITE_UNLOCK(&ip_conntrack_lock);
+}
+
+/* Returns new sk_buff, or NULL */
+struct sk_buff *
+ip_ct_gather_frags(struct sk_buff *skb, u_int32_t user)
+{
+ struct sock *sk = skb->sk;
+#ifdef CONFIG_NETFILTER_DEBUG
+ unsigned int olddebug = skb->nf_debug;
+#endif
+
+ if (sk) {
+ sock_hold(sk);
+ skb_orphan(skb);
+ }
+
+ local_bh_disable();
+ skb = ip_defrag(skb, user);
+ local_bh_enable();
+
+ if (!skb) {
+ if (sk) sock_put(sk);
+ return skb;
+ } else if (skb_is_nonlinear(skb) && skb_linearize(skb, GFP_ATOMIC) != 0) {
+ kfree_skb(skb);
+ if (sk) sock_put(sk);
+ return NULL;
+ }
+
+ if (sk) {
+ skb_set_owner_w(skb, sk);
+ sock_put(sk);
+ }
+
+ ip_send_check(skb->nh.iph);
+ skb->nfcache |= NFC_ALTERED;
+#ifdef CONFIG_NETFILTER_DEBUG
+ /* Packet path as if nothing had happened. */
+ skb->nf_debug = olddebug;
+#endif
+ return skb;
+}
+
+/* Used by ipt_REJECT. */
+static void ip_conntrack_attach(struct sk_buff *nskb, struct nf_ct_info *nfct)
+{
+ struct ip_conntrack *ct;
+ enum ip_conntrack_info ctinfo;
+
+ ct = __ip_conntrack_get(nfct, &ctinfo);
+
+ /* This ICMP is in reverse direction to the packet which
+ caused it */
+ if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL)
+ ctinfo = IP_CT_RELATED + IP_CT_IS_REPLY;
+ else
+ ctinfo = IP_CT_RELATED;
+
+ /* Attach new skbuff, and increment count */
+ nskb->nfct = &ct->infos[ctinfo];
+ atomic_inc(&ct->ct_general.use);
+}
+
+static inline int
+do_iter(const struct ip_conntrack_tuple_hash *i,
+ int (*iter)(struct ip_conntrack *i, void *data),
+ void *data)
+{
+ return iter(i->ctrack, data);
+}
+
+/* Bring out ya dead! */
+static struct ip_conntrack_tuple_hash *
+get_next_corpse(int (*iter)(struct ip_conntrack *i, void *data),
+ void *data, unsigned int *bucket)
+{
+ struct ip_conntrack_tuple_hash *h = NULL;
+
+ WRITE_LOCK(&ip_conntrack_lock);
+ for (; *bucket < ip_conntrack_htable_size; (*bucket)++) {
+ h = LIST_FIND_W(&ip_conntrack_hash[*bucket], do_iter,
+ struct ip_conntrack_tuple_hash *, iter, data);
+ if (h)
+ break;
+ }
+ if (!h)
+ h = LIST_FIND_W(&unconfirmed, do_iter,
+ struct ip_conntrack_tuple_hash *, iter, data);
+ if (h)
+ atomic_inc(&h->ctrack->ct_general.use);
+ WRITE_UNLOCK(&ip_conntrack_lock);
+
+ return h;
+}
+
+void
+ip_ct_iterate_cleanup(int (*iter)(struct ip_conntrack *i, void *), void *data)
+{
+ struct ip_conntrack_tuple_hash *h;
+ unsigned int bucket = 0;
+
+ while ((h = get_next_corpse(iter, data, &bucket)) != NULL) {
+ /* Time to push up daises... */
+ if (del_timer(&h->ctrack->timeout))
+ death_by_timeout((unsigned long)h->ctrack);
+ /* ... else the timer will get him soon. */
+
+ ip_conntrack_put(h->ctrack);
+ }
+}
+
+/* Fast function for those who don't want to parse /proc (and I don't
+ blame them). */
+/* Reversing the socket's dst/src point of view gives us the reply
+ mapping. */
+static int
+getorigdst(struct sock *sk, int optval, void *user, int *len)
+{
+ struct ip_conntrack_tuple_hash *h;
+ struct ip_conntrack_tuple tuple;
+
+ IP_CT_TUPLE_U_BLANK(&tuple);
+ tuple.src.ip = sk->rcv_saddr;
+ tuple.src.u.tcp.port = sk->sport;
+ tuple.dst.ip = sk->daddr;
+ tuple.dst.u.tcp.port = sk->dport;
+ tuple.dst.protonum = IPPROTO_TCP;
+
+ /* We only do TCP at the moment: is there a better way? */
+ if (strcmp(sk->prot->name, "TCP") != 0) {
+ DEBUGP("SO_ORIGINAL_DST: Not a TCP socket\n");
+ return -ENOPROTOOPT;
+ }
+
+ if ((unsigned int) *len < sizeof(struct sockaddr_in)) {
+ DEBUGP("SO_ORIGINAL_DST: len %u not %u\n",
+ *len, sizeof(struct sockaddr_in));
+ return -EINVAL;
+ }
+
+ h = ip_conntrack_find_get(&tuple, NULL);
+ if (h) {
+ struct sockaddr_in sin;
+
+ sin.sin_family = AF_INET;
+ sin.sin_port = h->ctrack->tuplehash[IP_CT_DIR_ORIGINAL]
+ .tuple.dst.u.tcp.port;
+ sin.sin_addr.s_addr = h->ctrack->tuplehash[IP_CT_DIR_ORIGINAL]
+ .tuple.dst.ip;
+
+ DEBUGP("SO_ORIGINAL_DST: %u.%u.%u.%u %u\n",
+ NIPQUAD(sin.sin_addr.s_addr), ntohs(sin.sin_port));
+ ip_conntrack_put(h->ctrack);
+ if (copy_to_user(user, &sin, sizeof(sin)) != 0)
+ return -EFAULT;
+ else
+ return 0;
+ }
+ DEBUGP("SO_ORIGINAL_DST: Can't find %u.%u.%u.%u/%u-%u.%u.%u.%u/%u.\n",
+ NIPQUAD(tuple.src.ip), ntohs(tuple.src.u.tcp.port),
+ NIPQUAD(tuple.dst.ip), ntohs(tuple.dst.u.tcp.port));
+ return -ENOENT;
+}
+
+static struct nf_sockopt_ops so_getorigdst
+= { { NULL, NULL }, PF_INET,
+ 0, 0, NULL, /* Setsockopts */
+ SO_ORIGINAL_DST, SO_ORIGINAL_DST+1, &getorigdst,
+ 0, NULL };
+
+static int kill_all(struct ip_conntrack *i, void *data)
+{
+ return 1;
+}
+
+/* Mishearing the voices in his head, our hero wonders how he's
+ supposed to kill the mall. */
+void ip_conntrack_cleanup(void)
+{
+ ip_ct_attach = NULL;
+ /* This makes sure all current packets have passed through
+ netfilter framework. Roll on, two-stage module
+ delete... */
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+
+ i_see_dead_people:
+ ip_ct_iterate_cleanup(kill_all, NULL);
+ if (atomic_read(&ip_conntrack_count) != 0) {
+ schedule();
+ goto i_see_dead_people;
+ }
+
+ kmem_cache_destroy(ip_conntrack_cachep);
+ vfree(ip_conntrack_hash);
+ nf_unregister_sockopt(&so_getorigdst);
+}
+
+#define HASHRATIO 8
+static int hashsize = 0;
+MODULE_PARM(hashsize, "i");
+
+int __init ip_conntrack_init(void)
+{
+ unsigned int i;
+ int ret;
+
+ /* Idea from tcp.c: use 1/16384 of memory. On i386: 32MB
+ * machine has 256 buckets. >= 1GB machines have 8192 buckets. */
+ if (hashsize) {
+ ip_conntrack_htable_size = hashsize;
+ } else {
+#ifdef CONFIG_IP_NF_BIG_CONNTRACK
+ /* Increase hash table size for dedicated routers.
+ * Allow all of memory to be used; it's just as bad
+ * as having a full conntrack table. */
+ ip_conntrack_htable_size
+ = (((num_physpages << PAGE_SHIFT) / HASHRATIO)
+ / sizeof(struct ip_conntrack));
+#else
+ ip_conntrack_htable_size
+ = (((num_physpages << PAGE_SHIFT) / 16384)
+ / sizeof(struct list_head));
+ if (num_physpages > (1024 * 1024 * 1024 / PAGE_SIZE))
+ ip_conntrack_htable_size = 8192;
+ if (ip_conntrack_htable_size < 16)
+ ip_conntrack_htable_size = 16;
+#endif
+ }
+ ip_conntrack_max = HASHRATIO * ip_conntrack_htable_size;
+
+ printk("ip_conntrack version %s (%u buckets, %d max)"
+ " - %Zd bytes per conntrack\n", IP_CONNTRACK_VERSION,
+ ip_conntrack_htable_size, ip_conntrack_max,
+ sizeof(struct ip_conntrack));
+
+ ret = nf_register_sockopt(&so_getorigdst);
+ if (ret != 0) {
+ printk(KERN_ERR "Unable to register netfilter socket option\n");
+ return ret;
+ }
+
+ ip_conntrack_hash = vmalloc(sizeof(struct list_head)
+ * ip_conntrack_htable_size);
+ if (!ip_conntrack_hash) {
+ printk(KERN_ERR "Unable to create ip_conntrack_hash\n");
+ goto err_unreg_sockopt;
+ }
+
+ ip_conntrack_cachep = kmem_cache_create("ip_conntrack",
+ sizeof(struct ip_conntrack), 0,
+ SLAB_HWCACHE_ALIGN, NULL, NULL);
+ if (!ip_conntrack_cachep) {
+ printk(KERN_ERR "Unable to create ip_conntrack slab cache\n");
+ goto err_free_hash;
+ }
+ /* Don't NEED lock here, but good form anyway. */
+ WRITE_LOCK(&ip_conntrack_lock);
+ /* Sew in builtin protocols. */
+ list_append(&protocol_list, &ip_conntrack_protocol_tcp);
+ list_append(&protocol_list, &ip_conntrack_protocol_udp);
+ list_append(&protocol_list, &ip_conntrack_protocol_icmp);
+ WRITE_UNLOCK(&ip_conntrack_lock);
+
+ for (i = 0; i < ip_conntrack_htable_size; i++)
+ INIT_LIST_HEAD(&ip_conntrack_hash[i]);
+
+ /* For use by ipt_REJECT */
+ ip_ct_attach = ip_conntrack_attach;
+ return ret;
+
+err_free_hash:
+ vfree(ip_conntrack_hash);
+err_unreg_sockopt:
+ nf_unregister_sockopt(&so_getorigdst);
+
+ return -ENOMEM;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_ftp.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_ftp.c
new file mode 100644
index 0000000..149deef
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_ftp.c
@@ -0,0 +1,439 @@
+/* FTP extension for IP connection tracking. */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/ip.h>
+#include <linux/ctype.h>
+#include <net/checksum.h>
+#include <net/tcp.h>
+
+#include <linux/netfilter_ipv4/lockhelp.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_ftp.h>
+
+static DECLARE_LOCK(ip_ftp_lock);
+struct module *ip_conntrack_ftp = THIS_MODULE;
+
+#define MAX_PORTS 8
+static int ports[MAX_PORTS];
+static int ports_c = 0;
+#ifdef MODULE_PARM
+MODULE_PARM(ports, "1-" __MODULE_STRING(MAX_PORTS) "i");
+#endif
+
+static int loose = 0;
+MODULE_PARM(loose, "i");
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+static int try_rfc959(const char *, size_t, u_int32_t [], char);
+static int try_eprt(const char *, size_t, u_int32_t [], char);
+static int try_epsv_response(const char *, size_t, u_int32_t [], char);
+
+static struct ftp_search {
+ enum ip_conntrack_dir dir;
+ const char *pattern;
+ size_t plen;
+ char skip;
+ char term;
+ enum ip_ct_ftp_type ftptype;
+ int (*getnum)(const char *, size_t, u_int32_t[], char);
+} search[] = {
+ {
+ IP_CT_DIR_ORIGINAL,
+ "PORT", sizeof("PORT") - 1, ' ', '\r',
+ IP_CT_FTP_PORT,
+ try_rfc959,
+ },
+ {
+ IP_CT_DIR_REPLY,
+ "227 ", sizeof("227 ") - 1, '(', ')',
+ IP_CT_FTP_PASV,
+ try_rfc959,
+ },
+ {
+ IP_CT_DIR_ORIGINAL,
+ "EPRT", sizeof("EPRT") - 1, ' ', '\r',
+ IP_CT_FTP_EPRT,
+ try_eprt,
+ },
+ {
+ IP_CT_DIR_REPLY,
+ "229 ", sizeof("229 ") - 1, '(', ')',
+ IP_CT_FTP_EPSV,
+ try_epsv_response,
+ },
+};
+
+static int try_number(const char *data, size_t dlen, u_int32_t array[],
+ int array_size, char sep, char term)
+{
+ u_int32_t i, len;
+
+ memset(array, 0, sizeof(array[0])*array_size);
+
+ /* Keep data pointing at next char. */
+ for (i = 0, len = 0; len < dlen && i < array_size; len++, data++) {
+ if (*data >= '0' && *data <= '9') {
+ array[i] = array[i]*10 + *data - '0';
+ }
+ else if (*data == sep)
+ i++;
+ else {
+ /* Unexpected character; true if it's the
+ terminator and we're finished. */
+ if (*data == term && i == array_size - 1)
+ return len;
+
+ DEBUGP("Char %u (got %u nums) `%u' unexpected\n",
+ len, i, *data);
+ return 0;
+ }
+ }
+ DEBUGP("Failed to fill %u numbers separated by %c\n", array_size, sep);
+
+ return 0;
+}
+
+/* Returns 0, or length of numbers: 192,168,1,1,5,6 */
+static int try_rfc959(const char *data, size_t dlen, u_int32_t array[6],
+ char term)
+{
+ return try_number(data, dlen, array, 6, ',', term);
+}
+
+/* Grab port: number up to delimiter */
+static int get_port(const char *data, int start, size_t dlen, char delim,
+ u_int32_t array[2])
+{
+ u_int16_t port = 0;
+ int i;
+
+ for (i = start; i < dlen; i++) {
+ /* Finished? */
+ if (data[i] == delim) {
+ if (port == 0)
+ break;
+ array[0] = port >> 8;
+ array[1] = port;
+ return i + 1;
+ }
+ else if (data[i] >= '0' && data[i] <= '9')
+ port = port*10 + data[i] - '0';
+ else /* Some other crap */
+ break;
+ }
+ return 0;
+}
+
+/* Returns 0, or length of numbers: |1|132.235.1.2|6275| */
+static int try_eprt(const char *data, size_t dlen, u_int32_t array[6],
+ char term)
+{
+ char delim;
+ int length;
+
+ /* First character is delimiter, then "1" for IPv4, then
+ delimiter again. */
+ if (dlen <= 3) return 0;
+ delim = data[0];
+ if (isdigit(delim) || delim < 33 || delim > 126
+ || data[1] != '1' || data[2] != delim)
+ return 0;
+
+ DEBUGP("EPRT: Got |1|!\n");
+ /* Now we have IP address. */
+ length = try_number(data + 3, dlen - 3, array, 4, '.', delim);
+ if (length == 0)
+ return 0;
+
+ DEBUGP("EPRT: Got IP address!\n");
+ /* Start offset includes initial "|1|", and trailing delimiter */
+ return get_port(data, 3 + length + 1, dlen, delim, array+4);
+}
+
+/* Returns 0, or length of numbers: |||6446| */
+static int try_epsv_response(const char *data, size_t dlen, u_int32_t array[6],
+ char term)
+{
+ char delim;
+
+ /* Three delimiters. */
+ if (dlen <= 3) return 0;
+ delim = data[0];
+ if (isdigit(delim) || delim < 33 || delim > 126
+ || data[1] != delim || data[2] != delim)
+ return 0;
+
+ return get_port(data, 3, dlen, delim, array+4);
+}
+
+/* Return 1 for match, 0 for accept, -1 for partial. */
+static int find_pattern(const char *data, size_t dlen,
+ const char *pattern, size_t plen,
+ char skip, char term,
+ unsigned int *numoff,
+ unsigned int *numlen,
+ u_int32_t array[6],
+ int (*getnum)(const char *, size_t, u_int32_t[], char))
+{
+ size_t i;
+
+ DEBUGP("find_pattern `%s': dlen = %u\n", pattern, dlen);
+ if (dlen == 0)
+ return 0;
+
+ if (dlen <= plen) {
+ /* Short packet: try for partial? */
+ if (strnicmp(data, pattern, dlen) == 0)
+ return -1;
+ else return 0;
+ }
+
+ if (strnicmp(data, pattern, plen) != 0) {
+#if 0
+ size_t i;
+
+ DEBUGP("ftp: string mismatch\n");
+ for (i = 0; i < plen; i++) {
+ DEBUGP("ftp:char %u `%c'(%u) vs `%c'(%u)\n",
+ i, data[i], data[i],
+ pattern[i], pattern[i]);
+ }
+#endif
+ return 0;
+ }
+
+ DEBUGP("Pattern matches!\n");
+ /* Now we've found the constant string, try to skip
+ to the 'skip' character */
+ for (i = plen; data[i] != skip; i++)
+ if (i == dlen - 1) return -1;
+
+ /* Skip over the last character */
+ i++;
+
+ DEBUGP("Skipped up to `%c'!\n", skip);
+
+ *numoff = i;
+ *numlen = getnum(data + i, dlen - i, array, term);
+ if (!*numlen)
+ return -1;
+
+ DEBUGP("Match succeeded!\n");
+ return 1;
+}
+
+/* FIXME: This should be in userspace. Later. */
+static int help(const struct iphdr *iph, size_t len,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo)
+{
+ /* tcplen not negative guaranteed by ip_conntrack_tcp.c */
+ struct tcphdr *tcph = (void *)iph + iph->ihl * 4;
+ const char *data = (const char *)tcph + tcph->doff * 4;
+ unsigned int tcplen = len - iph->ihl * 4;
+ unsigned int datalen = tcplen - tcph->doff * 4;
+ u_int32_t old_seq_aft_nl;
+ int old_seq_aft_nl_set;
+ u_int32_t array[6] = { 0 };
+ int dir = CTINFO2DIR(ctinfo);
+ unsigned int matchlen, matchoff;
+ struct ip_ct_ftp_master *ct_ftp_info = &ct->help.ct_ftp_info;
+ struct ip_conntrack_expect expect, *exp = &expect;
+ struct ip_ct_ftp_expect *exp_ftp_info = &exp->help.exp_ftp_info;
+
+ unsigned int i;
+ int found = 0;
+
+ /* Until there's been traffic both ways, don't look in packets. */
+ if (ctinfo != IP_CT_ESTABLISHED
+ && ctinfo != IP_CT_ESTABLISHED+IP_CT_IS_REPLY) {
+ DEBUGP("ftp: Conntrackinfo = %u\n", ctinfo);
+ return NF_ACCEPT;
+ }
+
+ /* Not whole TCP header? */
+ if (tcplen < sizeof(struct tcphdr) || tcplen < tcph->doff*4) {
+ DEBUGP("ftp: tcplen = %u\n", (unsigned)tcplen);
+ return NF_ACCEPT;
+ }
+
+ /* Checksum invalid? Ignore. */
+ /* FIXME: Source route IP option packets --RR */
+ if (tcp_v4_check(tcph, tcplen, iph->saddr, iph->daddr,
+ csum_partial((char *)tcph, tcplen, 0))) {
+ DEBUGP("ftp_help: bad csum: %p %u %u.%u.%u.%u %u.%u.%u.%u\n",
+ tcph, tcplen, NIPQUAD(iph->saddr),
+ NIPQUAD(iph->daddr));
+ return NF_ACCEPT;
+ }
+
+ LOCK_BH(&ip_ftp_lock);
+ old_seq_aft_nl_set = ct_ftp_info->seq_aft_nl_set[dir];
+ old_seq_aft_nl = ct_ftp_info->seq_aft_nl[dir];
+
+ DEBUGP("conntrack_ftp: datalen %u\n", datalen);
+ if ((datalen > 0) && (data[datalen-1] == '\n')) {
+ DEBUGP("conntrack_ftp: datalen %u ends in \\n\n", datalen);
+ if (!old_seq_aft_nl_set
+ || after(ntohl(tcph->seq) + datalen, old_seq_aft_nl)) {
+ DEBUGP("conntrack_ftp: updating nl to %u\n",
+ ntohl(tcph->seq) + datalen);
+ ct_ftp_info->seq_aft_nl[dir] =
+ ntohl(tcph->seq) + datalen;
+ ct_ftp_info->seq_aft_nl_set[dir] = 1;
+ }
+ }
+ UNLOCK_BH(&ip_ftp_lock);
+
+ if(!old_seq_aft_nl_set ||
+ (ntohl(tcph->seq) != old_seq_aft_nl)) {
+ DEBUGP("ip_conntrack_ftp_help: wrong seq pos %s(%u)\n",
+ old_seq_aft_nl_set ? "":"(UNSET) ", old_seq_aft_nl);
+ return NF_ACCEPT;
+ }
+
+ /* Initialize IP array to expected address (it's not mentioned
+ in EPSV responses) */
+ array[0] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 24) & 0xFF;
+ array[1] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 16) & 0xFF;
+ array[2] = (ntohl(ct->tuplehash[dir].tuple.src.ip) >> 8) & 0xFF;
+ array[3] = ntohl(ct->tuplehash[dir].tuple.src.ip) & 0xFF;
+
+ for (i = 0; i < sizeof(search) / sizeof(search[0]); i++) {
+ if (search[i].dir != dir) continue;
+
+ found = find_pattern(data, datalen,
+ search[i].pattern,
+ search[i].plen,
+ search[i].skip,
+ search[i].term,
+ &matchoff, &matchlen,
+ array,
+ search[i].getnum);
+ if (found) break;
+ }
+ if (found == -1) {
+ /* We don't usually drop packets. After all, this is
+ connection tracking, not packet filtering.
+ However, it is neccessary for accurate tracking in
+ this case. */
+ if (net_ratelimit())
+ printk("conntrack_ftp: partial %s %u+%u\n",
+ search[i].pattern,
+ ntohl(tcph->seq), datalen);
+ return NF_DROP;
+ } else if (found == 0) /* No match */
+ return NF_ACCEPT;
+
+ DEBUGP("conntrack_ftp: match `%.*s' (%u bytes at %u)\n",
+ (int)matchlen, data + matchoff,
+ matchlen, ntohl(tcph->seq) + matchoff);
+
+ memset(&expect, 0, sizeof(expect));
+
+ /* Update the ftp info */
+ if (htonl((array[0] << 24) | (array[1] << 16) | (array[2] << 8) | array[3])
+ == ct->tuplehash[dir].tuple.src.ip) {
+ exp->seq = ntohl(tcph->seq) + matchoff;
+ exp_ftp_info->len = matchlen;
+ exp_ftp_info->ftptype = search[i].ftptype;
+ exp_ftp_info->port = array[4] << 8 | array[5];
+ } else {
+ /* Enrico Scholz's passive FTP to partially RNAT'd ftp
+ server: it really wants us to connect to a
+ different IP address. Simply don't record it for
+ NAT. */
+ DEBUGP("conntrack_ftp: NOT RECORDING: %u,%u,%u,%u != %u.%u.%u.%u\n",
+ array[0], array[1], array[2], array[3],
+ NIPQUAD(ct->tuplehash[dir].tuple.src.ip));
+
+ /* Thanks to Cristiano Lincoln Mattos
+ <lincoln@cesar.org.br> for reporting this potential
+ problem (DMZ machines opening holes to internal
+ networks, or the packet filter itself). */
+ if (!loose)
+ return NF_ACCEPT;
+ }
+
+ exp->tuple = ((struct ip_conntrack_tuple)
+ { { ct->tuplehash[!dir].tuple.src.ip,
+ { 0 } },
+ { htonl((array[0] << 24) | (array[1] << 16)
+ | (array[2] << 8) | array[3]),
+ { .tcp = { htons(array[4] << 8 | array[5]) } },
+ IPPROTO_TCP }});
+ exp->mask = ((struct ip_conntrack_tuple)
+ { { 0xFFFFFFFF, { 0 } },
+ { 0xFFFFFFFF, { .tcp = { 0xFFFF } }, 0xFFFF }});
+
+ exp->expectfn = NULL;
+
+ /* Ignore failure; should only happen with NAT */
+ ip_conntrack_expect_related(ct, &expect);
+ return NF_ACCEPT;
+}
+
+static struct ip_conntrack_helper ftp[MAX_PORTS];
+static char ftp_names[MAX_PORTS][10];
+
+/* Not __exit: called from init() */
+static void fini(void)
+{
+ int i;
+ for (i = 0; i < ports_c; i++) {
+ DEBUGP("ip_ct_ftp: unregistering helper for port %d\n",
+ ports[i]);
+ ip_conntrack_helper_unregister(&ftp[i]);
+ }
+}
+
+static int __init init(void)
+{
+ int i, ret;
+ char *tmpname;
+
+ if (ports[0] == 0)
+ ports[0] = FTP_PORT;
+
+ for (i = 0; (i < MAX_PORTS) && ports[i]; i++) {
+ ftp[i].tuple.src.u.tcp.port = htons(ports[i]);
+ ftp[i].tuple.dst.protonum = IPPROTO_TCP;
+ ftp[i].mask.src.u.tcp.port = 0xFFFF;
+ ftp[i].mask.dst.protonum = 0xFFFF;
+ ftp[i].max_expected = 1;
+ ftp[i].timeout = 0;
+ ftp[i].flags = IP_CT_HELPER_F_REUSE_EXPECT;
+ ftp[i].me = ip_conntrack_ftp;
+ ftp[i].help = help;
+
+ tmpname = &ftp_names[i][0];
+ if (ports[i] == FTP_PORT)
+ sprintf(tmpname, "ftp");
+ else
+ sprintf(tmpname, "ftp-%d", ports[i]);
+ ftp[i].name = tmpname;
+
+ DEBUGP("ip_ct_ftp: registering helper for port %d\n",
+ ports[i]);
+ ret = ip_conntrack_helper_register(&ftp[i]);
+
+ if (ret) {
+ fini();
+ return ret;
+ }
+ ports_c++;
+ }
+ return 0;
+}
+
+EXPORT_SYMBOL(ip_ftp_lock);
+
+MODULE_LICENSE("GPL");
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_h323.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_h323.c
new file mode 100644
index 0000000..9382345
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_h323.c
@@ -0,0 +1,313 @@
+/*
+ * H.323 'brute force' extension for H.323 connection tracking.
+ * Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ * Based on ip_masq_h323.c for 2.2 kernels from CoRiTel, Sofia project.
+ * (http://www.coritel.it/projects/sofia/nat/)
+ * Uses Sampsa Ranta's excellent idea on using expectfn to 'bind'
+ * the unregistered helpers to the conntrack entries.
+ */
+
+
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/ip.h>
+#include <net/checksum.h>
+#include <net/tcp.h>
+
+#include <linux/netfilter_ipv4/lockhelp.h>
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+#include <linux/netfilter_ipv4/ip_conntrack_core.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_tuple.h>
+#include <linux/netfilter_ipv4/ip_conntrack_h323.h>
+
+MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>");
+MODULE_DESCRIPTION("H.323 'brute force' connection tracking module");
+MODULE_LICENSE("GPL");
+
+DECLARE_LOCK(ip_h323_lock);
+struct module *ip_conntrack_h323 = THIS_MODULE;
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+/* FIXME: This should be in userspace. Later. */
+static int h245_help(const struct iphdr *iph, size_t len,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo)
+{
+ struct tcphdr *tcph = (void *)iph + iph->ihl * 4;
+ unsigned char *data = (unsigned char *) tcph + tcph->doff * 4;
+ unsigned char *data_limit;
+ u_int32_t tcplen = len - iph->ihl * 4;
+ u_int32_t datalen = tcplen - tcph->doff * 4;
+ int dir = CTINFO2DIR(ctinfo);
+ struct ip_ct_h225_master *info = &ct->help.ct_h225_info;
+ struct ip_conntrack_expect expect, *exp = &expect;
+ struct ip_ct_h225_expect *exp_info = &exp->help.exp_h225_info;
+ u_int16_t data_port;
+ u_int32_t data_ip;
+ unsigned int i;
+
+ DEBUGP("ct_h245_help: help entered %u.%u.%u.%u:%u->%u.%u.%u.%u:%u\n",
+ NIPQUAD(iph->saddr), ntohs(tcph->source),
+ NIPQUAD(iph->daddr), ntohs(tcph->dest));
+
+ /* Can't track connections formed before we registered */
+ if (!info)
+ return NF_ACCEPT;
+
+ /* Until there's been traffic both ways, don't look in packets. */
+ if (ctinfo != IP_CT_ESTABLISHED
+ && ctinfo != IP_CT_ESTABLISHED + IP_CT_IS_REPLY) {
+ DEBUGP("ct_h245_help: Conntrackinfo = %u\n", ctinfo);
+ return NF_ACCEPT;
+ }
+
+ /* Not whole TCP header or too short packet? */
+ if (tcplen < sizeof(struct tcphdr) || tcplen < tcph->doff * 4 + 5) {
+ DEBUGP("ct_h245_help: tcplen = %u\n", (unsigned)tcplen);
+ return NF_ACCEPT;
+ }
+
+ /* Checksum invalid? Ignore. */
+ /* FIXME: Source route IP option packets --RR */
+ if (tcp_v4_check(tcph, tcplen, iph->saddr, iph->daddr,
+ csum_partial((char *)tcph, tcplen, 0))) {
+ DEBUGP("ct_h245_help: bad csum: %p %u %u.%u.%u.%u %u.%u.%u.%u\n",
+ tcph, tcplen, NIPQUAD(iph->saddr),
+ NIPQUAD(iph->daddr));
+ return NF_ACCEPT;
+ }
+
+ data_limit = (unsigned char *) data + datalen;
+ /* bytes: 0123 45
+ ipadrr port */
+ for (i = 0; data < (data_limit - 5); data++, i++) {
+ data_ip = *((u_int32_t *)data);
+ if (data_ip == iph->saddr) {
+ data_port = *((u_int16_t *)(data + 4));
+ memset(&expect, 0, sizeof(expect));
+ /* update the H.225 info */
+ DEBUGP("ct_h245_help: new RTCP/RTP requested %u.%u.%u.%u:->%u.%u.%u.%u:%u\n",
+ NIPQUAD(ct->tuplehash[!dir].tuple.src.ip),
+ NIPQUAD(iph->saddr), ntohs(data_port));
+ LOCK_BH(&ip_h323_lock);
+ info->is_h225 = H225_PORT + 1;
+ exp_info->port = data_port;
+ exp_info->dir = dir;
+ exp_info->offset = i;
+
+ exp->seq = ntohl(tcph->seq) + i;
+
+ exp->tuple = ((struct ip_conntrack_tuple)
+ { { ct->tuplehash[!dir].tuple.src.ip,
+ { 0 } },
+ { data_ip,
+ { udp: { port: data_port } },
+ IPPROTO_UDP }});
+ exp->mask = ((struct ip_conntrack_tuple)
+ { { 0xFFFFFFFF, { 0 } },
+ { 0xFFFFFFFF,
+ { udp: { port: 0xFFFF } }, 0xFFFF }});
+
+ exp->expectfn = NULL;
+
+ /* Ignore failure; should only happen with NAT */
+ ip_conntrack_expect_related(ct, exp);
+
+ UNLOCK_BH(&ip_h323_lock);
+ }
+ }
+
+ return NF_ACCEPT;
+
+}
+
+/* H.245 helper is not registered! */
+static struct ip_conntrack_helper h245 =
+ { { NULL, NULL },
+ "H.245", /* name */
+ IP_CT_HELPER_F_REUSE_EXPECT, /* flags */
+ NULL, /* module */
+ 16, /* max_ expected */
+ 240, /* timeout */
+ { { 0, { 0 } }, /* tuple */
+ { 0, { 0 }, IPPROTO_TCP } },
+ { { 0, { tcp: { port: 0xFFFF } } }, /* mask */
+ { 0, { 0 }, 0xFFFF } },
+ h245_help /* helper */
+ };
+
+static int h225_expect(struct ip_conntrack *ct)
+{
+ WRITE_LOCK(&ip_conntrack_lock);
+ ct->helper = &h245;
+ DEBUGP("h225_expect: helper for %p added\n", ct);
+ WRITE_UNLOCK(&ip_conntrack_lock);
+
+ return NF_ACCEPT; /* unused */
+}
+
+/* FIXME: This should be in userspace. Later. */
+static int h225_help(const struct iphdr *iph, size_t len,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo)
+{
+ struct tcphdr *tcph = (void *)iph + iph->ihl * 4;
+ unsigned char *data = (unsigned char *) tcph + tcph->doff * 4;
+ unsigned char *data_limit;
+ u_int32_t tcplen = len - iph->ihl * 4;
+ u_int32_t datalen = tcplen - tcph->doff * 4;
+ int dir = CTINFO2DIR(ctinfo);
+ struct ip_ct_h225_master *info = &ct->help.ct_h225_info;
+ struct ip_conntrack_expect expect, *exp = &expect;
+ struct ip_ct_h225_expect *exp_info = &exp->help.exp_h225_info;
+ u_int16_t data_port;
+ u_int32_t data_ip;
+ unsigned int i;
+
+ DEBUGP("ct_h225_help: help entered %u.%u.%u.%u:%u->%u.%u.%u.%u:%u\n",
+ NIPQUAD(iph->saddr), ntohs(tcph->source),
+ NIPQUAD(iph->daddr), ntohs(tcph->dest));
+
+ /* Can't track connections formed before we registered */
+ if (!info)
+ return NF_ACCEPT;
+
+ /* Until there's been traffic both ways, don't look in packets. */
+ if (ctinfo != IP_CT_ESTABLISHED
+ && ctinfo != IP_CT_ESTABLISHED + IP_CT_IS_REPLY) {
+ DEBUGP("ct_h225_help: Conntrackinfo = %u\n", ctinfo);
+ return NF_ACCEPT;
+ }
+
+ /* Not whole TCP header or too short packet? */
+ if (tcplen < sizeof(struct tcphdr) || tcplen < tcph->doff * 4 + 5) {
+ DEBUGP("ct_h225_help: tcplen = %u\n", (unsigned)tcplen);
+ return NF_ACCEPT;
+ }
+
+ /* Checksum invalid? Ignore. */
+ /* FIXME: Source route IP option packets --RR */
+ if (tcp_v4_check(tcph, tcplen, iph->saddr, iph->daddr,
+ csum_partial((char *)tcph, tcplen, 0))) {
+ DEBUGP("ct_h225_help: bad csum: %p %u %u.%u.%u.%u %u.%u.%u.%u\n",
+ tcph, tcplen, NIPQUAD(iph->saddr),
+ NIPQUAD(iph->daddr));
+ return NF_ACCEPT;
+ }
+
+ data_limit = (unsigned char *) data + datalen;
+ /* bytes: 0123 45
+ ipadrr port */
+ for (i = 0; data < (data_limit - 5); data++, i++) {
+ data_ip = *((u_int32_t *)data);
+ if (data_ip == iph->saddr) {
+ data_port = *((u_int16_t *)(data + 4));
+ if ((data_port == tcph->source) || (data_port == 0)) {
+ /* Signal address */
+ DEBUGP("ct_h225_help: sourceCallSignalAddress from %u.%u.%u.%u\n",
+ NIPQUAD(iph->saddr));
+ /* Update the H.225 info so that NAT can mangle the address/port
+ even when we have no expected connection! */
+#ifdef CONFIG_IP_NF_NAT_NEEDED
+ LOCK_BH(&ip_h323_lock);
+ info->dir = dir;
+ info->seq[IP_CT_DIR_ORIGINAL] = ntohl(tcph->seq) + i;
+ info->offset[IP_CT_DIR_ORIGINAL] = i;
+ UNLOCK_BH(&ip_h323_lock);
+#endif
+ } else {
+ memset(&expect, 0, sizeof(expect));
+
+ /* update the H.225 info */
+ LOCK_BH(&ip_h323_lock);
+ info->is_h225 = H225_PORT;
+ exp_info->port = data_port;
+ exp_info->dir = dir;
+ exp_info->offset = i;
+
+ exp->seq = ntohl(tcph->seq) + i;
+
+ exp->tuple = ((struct ip_conntrack_tuple)
+ { { ct->tuplehash[!dir].tuple.src.ip,
+ { 0 } },
+ { data_ip,
+ { tcp: { port: data_port } },
+ IPPROTO_TCP }});
+ exp->mask = ((struct ip_conntrack_tuple)
+ { { 0xFFFFFFFF, { 0 } },
+ { 0xFFFFFFFF,
+ { tcp: { port: 0xFFFF } },
+ 0xFFFF }});
+
+ exp->expectfn = h225_expect;
+
+ /* Ignore failure */
+ ip_conntrack_expect_related(ct, exp);
+
+ DEBUGP("ct_h225_help: new H.245 requested %u.%u.%u.%u->%u.%u.%u.%u:%u\n",
+ NIPQUAD(ct->tuplehash[!dir].tuple.src.ip),
+ NIPQUAD(iph->saddr), ntohs(data_port));
+
+ UNLOCK_BH(&ip_h323_lock);
+ }
+#ifdef CONFIG_IP_NF_NAT_NEEDED
+ } else if (data_ip == iph->daddr) {
+ data_port = *((u_int16_t *)(data + 4));
+ if (data_port == tcph->dest) {
+ /* Signal address */
+ DEBUGP("ct_h225_help: destCallSignalAddress %u.%u.%u.%u\n",
+ NIPQUAD(iph->daddr));
+ /* Update the H.225 info so that NAT can mangle the address/port
+ even when we have no expected connection! */
+ LOCK_BH(&ip_h323_lock);
+ info->dir = dir;
+ info->seq[IP_CT_DIR_REPLY] = ntohl(tcph->seq) + i;
+ info->offset[IP_CT_DIR_REPLY] = i;
+ UNLOCK_BH(&ip_h323_lock);
+ }
+#endif
+ }
+ }
+
+ return NF_ACCEPT;
+
+}
+
+static struct ip_conntrack_helper h225 =
+ { { NULL, NULL },
+ "H.225", /* name */
+ IP_CT_HELPER_F_REUSE_EXPECT, /* flags */
+ THIS_MODULE, /* module */
+ 2, /* max_expected */
+ 240, /* timeout */
+ { { 0, { tcp: { port: __constant_htons(H225_PORT) } } },
+ { 0, { 0 }, IPPROTO_TCP } }, /* tuple */
+ { { 0, { tcp: { port: 0xFFFF } } }, /* mask */
+ { 0, { 0 }, 0xFFFF } },
+ h225_help /* helper */
+ };
+
+static int __init init(void)
+{
+ return ip_conntrack_helper_register(&h225);
+}
+
+static void __exit fini(void)
+{
+ /* Unregister H.225 helper */
+ ip_conntrack_helper_unregister(&h225);
+}
+
+#ifdef CONFIG_IP_NF_NAT_NEEDED
+EXPORT_SYMBOL(ip_h323_lock);
+#endif
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_irc.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_irc.c
new file mode 100644
index 0000000..d446eab
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_irc.c
@@ -0,0 +1,313 @@
+/* IRC extension for IP connection tracking, Version 1.21
+ * (C) 2000-2002 by Harald Welte <laforge@gnumonks.org>
+ * based on RR's ip_conntrack_ftp.c
+ *
+ * ip_conntrack_irc.c,v 1.21 2002/02/05 14:49:26 laforge Exp
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ **
+ * Module load syntax:
+ * insmod ip_conntrack_irc.o ports=port1,port2,...port<MAX_PORTS>
+ * max_dcc_channels=n dcc_timeout=secs
+ *
+ * please give the ports of all IRC servers You wish to connect to.
+ * If You don't specify ports, the default will be port 6667.
+ * With max_dcc_channels you can define the maximum number of not
+ * yet answered DCC channels per IRC session (default 8).
+ * With dcc_timeout you can specify how long the system waits for
+ * an expected DCC channel (default 300 seconds).
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/ip.h>
+#include <net/checksum.h>
+#include <net/tcp.h>
+
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_irc.h>
+
+#define MAX_PORTS 8
+static int ports[MAX_PORTS];
+static int ports_c = 0;
+static int max_dcc_channels = 8;
+static unsigned int dcc_timeout = 300;
+
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("IRC (DCC) connection tracking module");
+MODULE_LICENSE("GPL");
+#ifdef MODULE_PARM
+MODULE_PARM(ports, "1-" __MODULE_STRING(MAX_PORTS) "i");
+MODULE_PARM_DESC(ports, "port numbers of IRC servers");
+MODULE_PARM(max_dcc_channels, "i");
+MODULE_PARM_DESC(max_dcc_channels, "max number of expected DCC channels per IRC session");
+MODULE_PARM(dcc_timeout, "i");
+MODULE_PARM_DESC(dcc_timeout, "timeout on for unestablished DCC channels");
+#endif
+
+#define NUM_DCCPROTO 5
+struct dccproto dccprotos[NUM_DCCPROTO] = {
+ {"SEND ", 5},
+ {"CHAT ", 5},
+ {"MOVE ", 5},
+ {"TSEND ", 6},
+ {"SCHAT ", 6}
+};
+#define MINMATCHLEN 5
+
+struct module *ip_conntrack_irc = THIS_MODULE;
+
+#if 0
+#define DEBUGP(format, args...) printk(KERN_DEBUG __FILE__ ":" __FUNCTION__ \
+ ":" format, ## args)
+#else
+#define DEBUGP(format, args...)
+#endif
+
+int parse_dcc(char *data, char *data_end, u_int32_t * ip, u_int16_t * port,
+ char **ad_beg_p, char **ad_end_p)
+/* tries to get the ip_addr and port out of a dcc command
+ return value: -1 on failure, 0 on success
+ data pointer to first byte of DCC command data
+ data_end pointer to last byte of dcc command data
+ ip returns parsed ip of dcc command
+ port returns parsed port of dcc command
+ ad_beg_p returns pointer to first byte of addr data
+ ad_end_p returns pointer to last byte of addr data */
+{
+
+ /* at least 12: "AAAAAAAA P\1\n" */
+ while (*data++ != ' ')
+ if (data > data_end - 12)
+ return -1;
+
+ *ad_beg_p = data;
+ *ip = simple_strtoul(data, &data, 10);
+
+ /* skip blanks between ip and port */
+ while (*data == ' ') {
+ if (data >= data_end)
+ return -1;
+ data++;
+ }
+
+ *port = simple_strtoul(data, &data, 10);
+ *ad_end_p = data;
+
+ return 0;
+}
+
+
+/* FIXME: This should be in userspace. Later. */
+static int help(const struct iphdr *iph, size_t len,
+ struct ip_conntrack *ct, enum ip_conntrack_info ctinfo)
+{
+ /* tcplen not negative guarenteed by ip_conntrack_tcp.c */
+ struct tcphdr *tcph = (void *) iph + iph->ihl * 4;
+ const char *data = (const char *) tcph + tcph->doff * 4;
+ const char *_data = data;
+ char *data_limit;
+ u_int32_t tcplen = len - iph->ihl * 4;
+ u_int32_t datalen = tcplen - tcph->doff * 4;
+ int dir = CTINFO2DIR(ctinfo);
+ struct ip_conntrack_expect expect, *exp = &expect;
+ struct ip_ct_irc_expect *exp_irc_info = &exp->help.exp_irc_info;
+
+ u_int32_t dcc_ip;
+ u_int16_t dcc_port;
+ int i;
+ char *addr_beg_p, *addr_end_p;
+
+ DEBUGP("entered\n");
+
+ /* If packet is coming from IRC server */
+ if (dir == IP_CT_DIR_REPLY)
+ return NF_ACCEPT;
+
+ /* Until there's been traffic both ways, don't look in packets. */
+ if (ctinfo != IP_CT_ESTABLISHED
+ && ctinfo != IP_CT_ESTABLISHED + IP_CT_IS_REPLY) {
+ DEBUGP("Conntrackinfo = %u\n", ctinfo);
+ return NF_ACCEPT;
+ }
+
+ /* Not whole TCP header? */
+ if (tcplen < sizeof(struct tcphdr) || tcplen < tcph->doff * 4) {
+ DEBUGP("tcplen = %u\n", (unsigned) tcplen);
+ return NF_ACCEPT;
+ }
+
+ /* Checksum invalid? Ignore. */
+ /* FIXME: Source route IP option packets --RR */
+ if (tcp_v4_check(tcph, tcplen, iph->saddr, iph->daddr,
+ csum_partial((char *) tcph, tcplen, 0))) {
+ DEBUGP("bad csum: %p %u %u.%u.%u.%u %u.%u.%u.%u\n",
+ tcph, tcplen, NIPQUAD(iph->saddr),
+ NIPQUAD(iph->daddr));
+ return NF_ACCEPT;
+ }
+
+ data_limit = (char *) data + datalen;
+
+ /* strlen("\1DCC SEND t AAAAAAAA P\1\n")=24
+ * 5+MINMATCHLEN+strlen("t AAAAAAAA P\1\n")=14 */
+ while (data < (data_limit - (19 + MINMATCHLEN))) {
+ if (memcmp(data, "\1DCC ", 5)) {
+ data++;
+ continue;
+ }
+
+ data += 5;
+ /* we have at least (19+MINMATCHLEN)-5 bytes valid data left */
+
+ DEBUGP("DCC found in master %u.%u.%u.%u:%u %u.%u.%u.%u:%u...\n",
+ NIPQUAD(iph->saddr), ntohs(tcph->source),
+ NIPQUAD(iph->daddr), ntohs(tcph->dest));
+
+ for (i = 0; i < NUM_DCCPROTO; i++) {
+ if (memcmp(data, dccprotos[i].match,
+ dccprotos[i].matchlen)) {
+ /* no match */
+ continue;
+ }
+
+ DEBUGP("DCC %s detected\n", dccprotos[i].match);
+ data += dccprotos[i].matchlen;
+ /* we have at least
+ * (19+MINMATCHLEN)-5-dccprotos[i].matchlen bytes valid
+ * data left (== 14/13 bytes) */
+ if (parse_dcc((char *) data, data_limit, &dcc_ip,
+ &dcc_port, &addr_beg_p, &addr_end_p)) {
+ /* unable to parse */
+ DEBUGP("unable to parse dcc command\n");
+ continue;
+ }
+ DEBUGP("DCC bound ip/port: %u.%u.%u.%u:%u\n",
+ HIPQUAD(dcc_ip), dcc_port);
+
+ /* dcc_ip can be the internal OR external (NAT'ed) IP
+ * Tiago Sousa <mirage@kaotik.org> */
+ if (ct->tuplehash[dir].tuple.src.ip != htonl(dcc_ip)
+ && ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip != htonl(dcc_ip)) {
+ if (net_ratelimit())
+ printk(KERN_WARNING
+ "Forged DCC command from "
+ "%u.%u.%u.%u: %u.%u.%u.%u:%u\n",
+ NIPQUAD(ct->tuplehash[dir].tuple.src.ip),
+ HIPQUAD(dcc_ip), dcc_port);
+
+ continue;
+ }
+
+ memset(&expect, 0, sizeof(expect));
+
+ /* save position of address in dcc string,
+ * neccessary for NAT */
+ DEBUGP("tcph->seq = %u\n", tcph->seq);
+ exp->seq = ntohl(tcph->seq) + (addr_beg_p - _data);
+ exp_irc_info->len = (addr_end_p - addr_beg_p);
+ exp_irc_info->port = dcc_port;
+ DEBUGP("wrote info seq=%u (ofs=%u), len=%d\n",
+ exp->seq, (addr_end_p - _data), exp_irc_info->len);
+
+ exp->tuple = ((struct ip_conntrack_tuple)
+ { { 0, { 0 } },
+ { ct->tuplehash[dir].tuple.src.ip, { .tcp = { htons(dcc_port) } },
+ IPPROTO_TCP }});
+ exp->mask = ((struct ip_conntrack_tuple)
+ { { 0, { 0 } },
+ { 0xFFFFFFFF, { .tcp = { 0xFFFF } }, 0xFFFF }});
+
+ exp->expectfn = NULL;
+
+ DEBUGP("expect_related %u.%u.%u.%u:%u-%u.%u.%u.%u:%u\n",
+ NIPQUAD(exp->tuple.src.ip),
+ ntohs(exp->tuple.src.u.tcp.port),
+ NIPQUAD(exp->tuple.dst.ip),
+ ntohs(exp->tuple.dst.u.tcp.port));
+
+ ip_conntrack_expect_related(ct, &expect);
+ return NF_ACCEPT;
+ } /* for .. NUM_DCCPROTO */
+ } /* while data < ... */
+
+ return NF_ACCEPT;
+}
+
+static struct ip_conntrack_helper irc_helpers[MAX_PORTS];
+static char irc_names[MAX_PORTS][10];
+
+static void fini(void);
+
+static int __init init(void)
+{
+ int i, ret;
+ struct ip_conntrack_helper *hlpr;
+ char *tmpname;
+
+ if (max_dcc_channels < 1) {
+ printk("ip_conntrack_irc: max_dcc_channels must be a positive integer\n");
+ return -EBUSY;
+ }
+ if (dcc_timeout < 0) {
+ printk("ip_conntrack_irc: dcc_timeout must be a positive integer\n");
+ return -EBUSY;
+ }
+
+ /* If no port given, default to standard irc port */
+ if (ports[0] == 0)
+ ports[0] = IRC_PORT;
+
+ for (i = 0; (i < MAX_PORTS) && ports[i]; i++) {
+ hlpr = &irc_helpers[i];
+ hlpr->tuple.src.u.tcp.port = htons(ports[i]);
+ hlpr->tuple.dst.protonum = IPPROTO_TCP;
+ hlpr->mask.src.u.tcp.port = 0xFFFF;
+ hlpr->mask.dst.protonum = 0xFFFF;
+ hlpr->max_expected = max_dcc_channels;
+ hlpr->timeout = dcc_timeout;
+ hlpr->flags = IP_CT_HELPER_F_REUSE_EXPECT;
+ hlpr->me = ip_conntrack_irc;
+ hlpr->help = help;
+
+ tmpname = &irc_names[i][0];
+ if (ports[i] == IRC_PORT)
+ sprintf(tmpname, "irc");
+ else
+ sprintf(tmpname, "irc-%d", i);
+ hlpr->name = tmpname;
+
+ DEBUGP("port #%d: %d\n", i, ports[i]);
+
+ ret = ip_conntrack_helper_register(hlpr);
+
+ if (ret) {
+ printk("ip_conntrack_irc: ERROR registering port %d\n",
+ ports[i]);
+ fini();
+ return -EBUSY;
+ }
+ ports_c++;
+ }
+ return 0;
+}
+
+/* This function is intentionally _NOT_ defined as __exit, because
+ * it is needed by the init function */
+static void fini(void)
+{
+ int i;
+ for (i = 0; i < ports_c; i++) {
+ DEBUGP("unregistering port %d\n",
+ ports[i]);
+ ip_conntrack_helper_unregister(&irc_helpers[i]);
+ }
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_pptp.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_pptp.c
new file mode 100644
index 0000000..258aee8
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_pptp.c
@@ -0,0 +1,637 @@
+/*
+ * ip_conntrack_pptp.c - Version $Revision: 1.8 $
+ *
+ * Connection tracking support for PPTP (Point to Point Tunneling Protocol).
+ * PPTP is a a protocol for creating virtual private networks.
+ * It is a specification defined by Microsoft and some vendors
+ * working with Microsoft. PPTP is built on top of a modified
+ * version of the Internet Generic Routing Encapsulation Protocol.
+ * GRE is defined in RFC 1701 and RFC 1702. Documentation of
+ * PPTP can be found in RFC 2637
+ *
+ * (C) 2000-2003 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Development of this code funded by Astaro AG (http://www.astaro.com/)
+ *
+ * Limitations:
+ * - We blindly assume that control connections are always
+ * established in PNS->PAC direction. This is a violation
+ * of RFFC2673
+ *
+ * TODO: - finish support for multiple calls within one session
+ * (needs expect reservations in newnat)
+ * - testing of incoming PPTP calls
+ *
+ * Changes:
+ * 2002-02-05 - Version 1.3
+ * - Call ip_conntrack_unexpect_related() from
+ * pptp_timeout_related() to destroy expectations in case
+ * CALL_DISCONNECT_NOTIFY or tcp fin packet was seen
+ * (Philip Craig <philipc@snapgear.com>)
+ * - Add Version information at module loadtime
+ * 2002-02-10 - Version 1.6
+ * - move to C99 style initializers
+ * - remove second expectation if first arrives
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/ip.h>
+#include <net/checksum.h>
+#include <net/tcp.h>
+
+#include <linux/netfilter_ipv4/lockhelp.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h>
+#include <linux/netfilter_ipv4/ip_conntrack_pptp.h>
+
+#define IP_CT_PPTP_VERSION "$Revision: 1.8 $"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("Netfilter connection tracking helper module for PPTP");
+
+DECLARE_LOCK(ip_pptp_lock);
+
+#if 0
+#include "ip_conntrack_pptp_priv.h"
+#define DEBUGP(format, args...) printk(KERN_DEBUG __FILE__ ":" __FUNCTION__ \
+ ": " format, ## args)
+#else
+#define DEBUGP(format, args...)
+#endif
+
+#define SECS *HZ
+#define MINS * 60 SECS
+#define HOURS * 60 MINS
+#define DAYS * 24 HOURS
+
+#define PPTP_GRE_TIMEOUT (10 MINS)
+#define PPTP_GRE_STREAM_TIMEOUT (5 DAYS)
+
+static int pptp_expectfn(struct ip_conntrack *ct)
+{
+ struct ip_conntrack *master;
+ struct ip_conntrack_expect *exp;
+
+ DEBUGP("increasing timeouts\n");
+ /* increase timeout of GRE data channel conntrack entry */
+ ct->proto.gre.timeout = PPTP_GRE_TIMEOUT;
+ ct->proto.gre.stream_timeout = PPTP_GRE_STREAM_TIMEOUT;
+
+ master = master_ct(ct);
+ if (!master) {
+ DEBUGP(" no master!!!\n");
+ return 0;
+ }
+
+ exp = ct->master;
+ if (!exp) {
+ DEBUGP("no expectation!!\n");
+ return 0;
+ }
+
+ DEBUGP("completing tuples with ct info\n");
+ /* we can do this, since we're unconfirmed */
+ if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.gre.key ==
+ htonl(master->help.ct_pptp_info.pac_call_id)) {
+ /* assume PNS->PAC */
+ ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.gre.key =
+ htonl(master->help.ct_pptp_info.pns_call_id);
+ ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.gre.key =
+ htonl(master->help.ct_pptp_info.pns_call_id);
+ } else {
+ /* assume PAC->PNS */
+ ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.gre.key =
+ htonl(master->help.ct_pptp_info.pac_call_id);
+ ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.gre.key =
+ htonl(master->help.ct_pptp_info.pac_call_id);
+ }
+
+ /* delete other expectation */
+ if (exp->expected_list.next != &exp->expected_list) {
+ struct ip_conntrack_expect *other_exp;
+ struct list_head *cur_item, *next;
+
+ for (cur_item = master->sibling_list.next;
+ cur_item != &master->sibling_list; cur_item = next) {
+ next = cur_item->next;
+ other_exp = list_entry(cur_item,
+ struct ip_conntrack_expect,
+ expected_list);
+ /* remove only if occurred at same sequence number */
+ if (other_exp != exp && other_exp->seq == exp->seq) {
+ DEBUGP("unexpecting other direction\n");
+ ip_ct_gre_keymap_destroy(other_exp);
+ ip_conntrack_unexpect_related(other_exp);
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* timeout GRE data connections */
+static int pptp_timeout_related(struct ip_conntrack *ct, enum ip_conntrack_info ctinfo)
+{
+ struct list_head *cur_item, *next;
+ struct ip_conntrack_expect *exp;
+
+ /* FIXME: do we have to lock something ? */
+ for (cur_item = ct->sibling_list.next;
+ cur_item != &ct->sibling_list; cur_item = next) {
+ next = cur_item->next;
+ exp = list_entry(cur_item, struct ip_conntrack_expect,
+ expected_list);
+
+ ip_ct_gre_keymap_destroy(exp);
+ if (!exp->sibling) {
+ ip_conntrack_unexpect_related(exp);
+ continue;
+ }
+
+ DEBUGP("setting timeout of conntrack %p to 0\n",
+ exp->sibling);
+ exp->sibling->proto.gre.timeout = 0;
+ exp->sibling->proto.gre.stream_timeout = 0;
+ ip_ct_refresh_acct(exp->sibling, ctinfo, NULL, 0);
+ }
+
+ return 0;
+}
+
+/* expect GRE connections (PNS->PAC and PAC->PNS direction) */
+static inline int
+exp_gre(struct ip_conntrack *master,
+ u_int32_t seq,
+ u_int16_t callid,
+ u_int16_t peer_callid)
+{
+ struct ip_conntrack_expect exp;
+ struct ip_conntrack_tuple inv_tuple;
+
+ memset(&exp, 0, sizeof(exp));
+ /* tuple in original direction, PNS->PAC */
+ exp.tuple.src.ip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
+ exp.tuple.src.u.gre.key = htonl(ntohs(peer_callid));
+ exp.tuple.dst.ip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
+ exp.tuple.dst.u.gre.key = htonl(ntohs(callid));
+ exp.tuple.dst.u.gre.protocol = __constant_htons(GRE_PROTOCOL_PPTP);
+ exp.tuple.dst.u.gre.version = GRE_VERSION_PPTP;
+ exp.tuple.dst.protonum = IPPROTO_GRE;
+
+ exp.mask.src.ip = 0xffffffff;
+ exp.mask.src.u.all = 0;
+ exp.mask.dst.u.all = 0;
+ exp.mask.dst.u.gre.key = 0xffffffff;
+ exp.mask.dst.u.gre.version = 0xff;
+ exp.mask.dst.u.gre.protocol = 0xffff;
+ exp.mask.dst.ip = 0xffffffff;
+ exp.mask.dst.protonum = 0xffff;
+
+ exp.seq = seq;
+ exp.expectfn = pptp_expectfn;
+
+ exp.help.exp_pptp_info.pac_call_id = ntohs(callid);
+ exp.help.exp_pptp_info.pns_call_id = ntohs(peer_callid);
+
+ DEBUGP("calling expect_related ");
+ DUMP_TUPLE_RAW(&exp.tuple);
+
+ /* Add GRE keymap entries */
+ if (ip_ct_gre_keymap_add(&exp, &exp.tuple, 0) != 0)
+ return 1;
+
+ invert_tuplepr(&inv_tuple, &exp.tuple);
+ if (ip_ct_gre_keymap_add(&exp, &inv_tuple, 1) != 0) {
+ ip_ct_gre_keymap_destroy(&exp);
+ return 1;
+ }
+
+ if (ip_conntrack_expect_related(master, &exp) != 0) {
+ ip_ct_gre_keymap_destroy(&exp);
+ DEBUGP("cannot expect_related()\n");
+ return 1;
+ }
+
+ /* tuple in reply direction, PAC->PNS */
+ exp.tuple.src.ip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip;
+ exp.tuple.src.u.gre.key = htonl(ntohs(callid));
+ exp.tuple.dst.ip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+ exp.tuple.dst.u.gre.key = htonl(ntohs(peer_callid));
+
+ DEBUGP("calling expect_related ");
+ DUMP_TUPLE_RAW(&exp.tuple);
+
+ /* Add GRE keymap entries */
+ ip_ct_gre_keymap_add(&exp, &exp.tuple, 0);
+ invert_tuplepr(&inv_tuple, &exp.tuple);
+ ip_ct_gre_keymap_add(&exp, &inv_tuple, 1);
+ /* FIXME: cannot handle error correctly, since we need to free
+ * the above keymap :( */
+
+ if (ip_conntrack_expect_related(master, &exp) != 0) {
+ /* free the second pair of keypmaps */
+ ip_ct_gre_keymap_destroy(&exp);
+ DEBUGP("cannot expect_related():\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+static inline int
+pptp_inbound_pkt(struct tcphdr *tcph,
+ struct pptp_pkt_hdr *pptph,
+ size_t datalen,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo)
+{
+ struct PptpControlHeader *ctlh;
+ union pptp_ctrl_union pptpReq;
+
+ struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info;
+ u_int16_t msg, *cid, *pcid;
+ u_int32_t seq;
+
+ ctlh = (struct PptpControlHeader *)
+ ((char *) pptph + sizeof(struct pptp_pkt_hdr));
+ pptpReq.rawreq = (void *)
+ ((char *) ctlh + sizeof(struct PptpControlHeader));
+
+ msg = ntohs(ctlh->messageType);
+ DEBUGP("inbound control message %s\n", strMName[msg]);
+
+ switch (msg) {
+ case PPTP_START_SESSION_REPLY:
+ /* server confirms new control session */
+ if (info->sstate < PPTP_SESSION_REQUESTED) {
+ DEBUGP("%s without START_SESS_REQUEST\n",
+ strMName[msg]);
+ break;
+ }
+ if (pptpReq.srep->resultCode == PPTP_START_OK)
+ info->sstate = PPTP_SESSION_CONFIRMED;
+ else
+ info->sstate = PPTP_SESSION_ERROR;
+ break;
+
+ case PPTP_STOP_SESSION_REPLY:
+ /* server confirms end of control session */
+ if (info->sstate > PPTP_SESSION_STOPREQ) {
+ DEBUGP("%s without STOP_SESS_REQUEST\n",
+ strMName[msg]);
+ break;
+ }
+ if (pptpReq.strep->resultCode == PPTP_STOP_OK)
+ info->sstate = PPTP_SESSION_NONE;
+ else
+ info->sstate = PPTP_SESSION_ERROR;
+ break;
+
+ case PPTP_OUT_CALL_REPLY:
+ /* server accepted call, we now expect GRE frames */
+ if (info->sstate != PPTP_SESSION_CONFIRMED) {
+ DEBUGP("%s but no session\n", strMName[msg]);
+ break;
+ }
+ if (info->cstate != PPTP_CALL_OUT_REQ &&
+ info->cstate != PPTP_CALL_OUT_CONF) {
+ DEBUGP("%s without OUTCALL_REQ\n", strMName[msg]);
+ break;
+ }
+ if (pptpReq.ocack->resultCode != PPTP_OUTCALL_CONNECT) {
+ info->cstate = PPTP_CALL_NONE;
+ break;
+ }
+
+ cid = &pptpReq.ocack->callID;
+ pcid = &pptpReq.ocack->peersCallID;
+
+ info->pac_call_id = ntohs(*cid);
+
+ if (htons(info->pns_call_id) != *pcid) {
+ DEBUGP("%s for unknown callid %u\n",
+ strMName[msg], ntohs(*pcid));
+ break;
+ }
+
+ DEBUGP("%s, CID=%X, PCID=%X\n", strMName[msg],
+ ntohs(*cid), ntohs(*pcid));
+
+ info->cstate = PPTP_CALL_OUT_CONF;
+
+ seq = ntohl(tcph->seq) + ((void *)pcid - (void *)pptph);
+ if (exp_gre(ct, seq, *cid, *pcid) != 0)
+ printk("ip_conntrack_pptp: error during exp_gre\n");
+ break;
+
+ case PPTP_IN_CALL_REQUEST:
+ /* server tells us about incoming call request */
+ if (info->sstate != PPTP_SESSION_CONFIRMED) {
+ DEBUGP("%s but no session\n", strMName[msg]);
+ break;
+ }
+ pcid = &pptpReq.icack->peersCallID;
+ DEBUGP("%s, PCID=%X\n", strMName[msg], ntohs(*pcid));
+ info->cstate = PPTP_CALL_IN_REQ;
+ info->pac_call_id= ntohs(*pcid);
+ break;
+
+ case PPTP_IN_CALL_CONNECT:
+ /* server tells us about incoming call established */
+ if (info->sstate != PPTP_SESSION_CONFIRMED) {
+ DEBUGP("%s but no session\n", strMName[msg]);
+ break;
+ }
+ if (info->sstate != PPTP_CALL_IN_REP
+ && info->sstate != PPTP_CALL_IN_CONF) {
+ DEBUGP("%s but never sent IN_CALL_REPLY\n",
+ strMName[msg]);
+ break;
+ }
+
+ pcid = &pptpReq.iccon->peersCallID;
+ cid = &info->pac_call_id;
+
+ if (info->pns_call_id != ntohs(*pcid)) {
+ DEBUGP("%s for unknown CallID %u\n",
+ strMName[msg], ntohs(*cid));
+ break;
+ }
+
+ DEBUGP("%s, PCID=%X\n", strMName[msg], ntohs(*pcid));
+ info->cstate = PPTP_CALL_IN_CONF;
+
+ /* we expect a GRE connection from PAC to PNS */
+ seq = ntohl(tcph->seq) + ((void *)pcid - (void *)pptph);
+ if (exp_gre(ct, seq, *cid, *pcid) != 0)
+ printk("ip_conntrack_pptp: error during exp_gre\n");
+
+ break;
+
+ case PPTP_CALL_DISCONNECT_NOTIFY:
+ /* server confirms disconnect */
+ cid = &pptpReq.disc->callID;
+ DEBUGP("%s, CID=%X\n", strMName[msg], ntohs(*cid));
+ info->cstate = PPTP_CALL_NONE;
+
+ /* untrack this call id, unexpect GRE packets */
+ pptp_timeout_related(ct, ctinfo);
+ break;
+
+ case PPTP_WAN_ERROR_NOTIFY:
+ break;
+
+ case PPTP_ECHO_REQUEST:
+ case PPTP_ECHO_REPLY:
+ /* I don't have to explain these ;) */
+ break;
+ default:
+ DEBUGP("invalid %s (TY=%d)\n", (msg <= PPTP_MSG_MAX)
+ ? strMName[msg]:strMName[0], msg);
+ break;
+ }
+
+ return NF_ACCEPT;
+
+}
+
+static inline int
+pptp_outbound_pkt(struct tcphdr *tcph,
+ struct pptp_pkt_hdr *pptph,
+ size_t datalen,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo)
+{
+ struct PptpControlHeader *ctlh;
+ union pptp_ctrl_union pptpReq;
+ struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info;
+ u_int16_t msg, *cid, *pcid;
+
+ ctlh = (struct PptpControlHeader *) ((void *) pptph + sizeof(*pptph));
+ pptpReq.rawreq = (void *) ((void *) ctlh + sizeof(*ctlh));
+
+ msg = ntohs(ctlh->messageType);
+ DEBUGP("outbound control message %s\n", strMName[msg]);
+
+ switch (msg) {
+ case PPTP_START_SESSION_REQUEST:
+ /* client requests for new control session */
+ if (info->sstate != PPTP_SESSION_NONE) {
+ DEBUGP("%s but we already have one",
+ strMName[msg]);
+ }
+ info->sstate = PPTP_SESSION_REQUESTED;
+ break;
+ case PPTP_STOP_SESSION_REQUEST:
+ /* client requests end of control session */
+ info->sstate = PPTP_SESSION_STOPREQ;
+ break;
+
+ case PPTP_OUT_CALL_REQUEST:
+ /* client initiating connection to server */
+ if (info->sstate != PPTP_SESSION_CONFIRMED) {
+ DEBUGP("%s but no session\n",
+ strMName[msg]);
+ break;
+ }
+ info->cstate = PPTP_CALL_OUT_REQ;
+ /* track PNS call id */
+ cid = &pptpReq.ocreq->callID;
+ DEBUGP("%s, CID=%X\n", strMName[msg], ntohs(*cid));
+ info->pns_call_id = ntohs(*cid);
+ break;
+ case PPTP_IN_CALL_REPLY:
+ /* client answers incoming call */
+ if (info->cstate != PPTP_CALL_IN_REQ
+ && info->cstate != PPTP_CALL_IN_REP) {
+ DEBUGP("%s without incall_req\n",
+ strMName[msg]);
+ break;
+ }
+ if (pptpReq.icack->resultCode != PPTP_INCALL_ACCEPT) {
+ info->cstate = PPTP_CALL_NONE;
+ break;
+ }
+ pcid = &pptpReq.icack->peersCallID;
+ if (info->pac_call_id != ntohs(*pcid)) {
+ DEBUGP("%s for unknown call %u\n",
+ strMName[msg], ntohs(*pcid));
+ break;
+ }
+ DEBUGP("%s, CID=%X\n", strMName[msg], ntohs(*pcid));
+ /* part two of the three-way handshake */
+ info->cstate = PPTP_CALL_IN_REP;
+ info->pns_call_id = ntohs(pptpReq.icack->callID);
+ break;
+
+ case PPTP_CALL_CLEAR_REQUEST:
+ /* client requests hangup of call */
+ if (info->sstate != PPTP_SESSION_CONFIRMED) {
+ DEBUGP("CLEAR_CALL but no session\n");
+ break;
+ }
+ /* FUTURE: iterate over all calls and check if
+ * call ID is valid. We don't do this without newnat,
+ * because we only know about last call */
+ info->cstate = PPTP_CALL_CLEAR_REQ;
+ break;
+ case PPTP_SET_LINK_INFO:
+ break;
+ case PPTP_ECHO_REQUEST:
+ case PPTP_ECHO_REPLY:
+ /* I don't have to explain these ;) */
+ break;
+ default:
+ DEBUGP("invalid %s (TY=%d)\n", (msg <= PPTP_MSG_MAX)?
+ strMName[msg]:strMName[0], msg);
+ /* unknown: no need to create GRE masq table entry */
+ break;
+ }
+
+ return NF_ACCEPT;
+}
+
+
+/* track caller id inside control connection, call expect_related */
+static int
+conntrack_pptp_help(const struct iphdr *iph, size_t len,
+ struct ip_conntrack *ct, enum ip_conntrack_info ctinfo)
+
+{
+ struct pptp_pkt_hdr *pptph;
+
+ struct tcphdr *tcph = (void *) iph + iph->ihl * 4;
+ u_int32_t tcplen = len - iph->ihl * 4;
+ u_int32_t datalen = tcplen - tcph->doff * 4;
+ void *datalimit;
+ int dir = CTINFO2DIR(ctinfo);
+ struct ip_ct_pptp_master *info = &ct->help.ct_pptp_info;
+
+ int oldsstate, oldcstate;
+ int ret;
+
+ /* don't do any tracking before tcp handshake complete */
+ if (ctinfo != IP_CT_ESTABLISHED
+ && ctinfo != IP_CT_ESTABLISHED+IP_CT_IS_REPLY) {
+ DEBUGP("ctinfo = %u, skipping\n", ctinfo);
+ return NF_ACCEPT;
+ }
+
+ /* not a complete TCP header? */
+ if (tcplen < sizeof(struct tcphdr) || tcplen < tcph->doff * 4) {
+ DEBUGP("tcplen = %u\n", tcplen);
+ return NF_ACCEPT;
+ }
+
+ /* checksum invalid? */
+ if (tcp_v4_check(tcph, tcplen, iph->saddr, iph->daddr,
+ csum_partial((char *) tcph, tcplen, 0))) {
+ printk(KERN_NOTICE __FILE__ ": bad csum\n");
+ /* W2K PPTP server sends TCP packets with wrong checksum :(( */
+ //return NF_ACCEPT;
+ }
+
+ if (tcph->fin || tcph->rst) {
+ DEBUGP("RST/FIN received, timeouting GRE\n");
+ /* can't do this after real newnat */
+ info->cstate = PPTP_CALL_NONE;
+
+ /* untrack this call id, unexpect GRE packets */
+ pptp_timeout_related(ct, ctinfo);
+ }
+
+
+ pptph = (struct pptp_pkt_hdr *) ((void *) tcph + tcph->doff * 4);
+ datalimit = (void *) pptph + datalen;
+
+ /* not a full pptp packet header? */
+ if ((void *) pptph+sizeof(*pptph) >= datalimit) {
+ DEBUGP("no full PPTP header, can't track\n");
+ return NF_ACCEPT;
+ }
+
+ /* if it's not a control message we can't do anything with it */
+ if (ntohs(pptph->packetType) != PPTP_PACKET_CONTROL ||
+ ntohl(pptph->magicCookie) != PPTP_MAGIC_COOKIE) {
+ DEBUGP("not a control packet\n");
+ return NF_ACCEPT;
+ }
+
+ oldsstate = info->sstate;
+ oldcstate = info->cstate;
+
+ LOCK_BH(&ip_pptp_lock);
+
+ /* FIXME: We just blindly assume that the control connection is always
+ * established from PNS->PAC. However, RFC makes no guarantee */
+ if (dir == IP_CT_DIR_ORIGINAL)
+ /* client -> server (PNS -> PAC) */
+ ret = pptp_outbound_pkt(tcph, pptph, datalen, ct, ctinfo);
+ else
+ /* server -> client (PAC -> PNS) */
+ ret = pptp_inbound_pkt(tcph, pptph, datalen, ct, ctinfo);
+ DEBUGP("sstate: %d->%d, cstate: %d->%d\n",
+ oldsstate, info->sstate, oldcstate, info->cstate);
+ UNLOCK_BH(&ip_pptp_lock);
+
+ return ret;
+}
+
+/* control protocol helper */
+static struct ip_conntrack_helper pptp = {
+ .list = { NULL, NULL },
+ .name = "pptp",
+ .flags = IP_CT_HELPER_F_REUSE_EXPECT,
+ .me = THIS_MODULE,
+ .max_expected = 2,
+ .timeout = 0,
+ .tuple = { .src = { .ip = 0,
+ .u = { .tcp = { .port =
+ __constant_htons(PPTP_CONTROL_PORT) } }
+ },
+ .dst = { .ip = 0,
+ .u = { .all = 0 },
+ .protonum = IPPROTO_TCP
+ }
+ },
+ .mask = { .src = { .ip = 0,
+ .u = { .tcp = { .port = 0xffff } }
+ },
+ .dst = { .ip = 0,
+ .u = { .all = 0 },
+ .protonum = 0xffff
+ }
+ },
+ .help = conntrack_pptp_help
+};
+
+/* ip_conntrack_pptp initialization */
+static int __init init(void)
+{
+ int retcode;
+
+ DEBUGP(__FILE__ ": registering helper\n");
+ if ((retcode = ip_conntrack_helper_register(&pptp))) {
+ printk(KERN_ERR "Unable to register conntrack application "
+ "helper for pptp: %d\n", retcode);
+ return -EIO;
+ }
+
+ printk("ip_conntrack_pptp version %s loaded\n", IP_CT_PPTP_VERSION);
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ip_conntrack_helper_unregister(&pptp);
+ printk("ip_conntrack_pptp version %s unloaded\n", IP_CT_PPTP_VERSION);
+}
+
+module_init(init);
+module_exit(fini);
+
+EXPORT_SYMBOL(ip_pptp_lock);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_pptp_priv.h b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_pptp_priv.h
new file mode 100644
index 0000000..6b52564
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_pptp_priv.h
@@ -0,0 +1,24 @@
+#ifndef _IP_CT_PPTP_PRIV_H
+#define _IP_CT_PPTP_PRIV_H
+
+/* PptpControlMessageType names */
+static const char *strMName[] = {
+ "UNKNOWN_MESSAGE",
+ "START_SESSION_REQUEST",
+ "START_SESSION_REPLY",
+ "STOP_SESSION_REQUEST",
+ "STOP_SESSION_REPLY",
+ "ECHO_REQUEST",
+ "ECHO_REPLY",
+ "OUT_CALL_REQUEST",
+ "OUT_CALL_REPLY",
+ "IN_CALL_REQUEST",
+ "IN_CALL_REPLY",
+ "IN_CALL_CONNECT",
+ "CALL_CLEAR_REQUEST",
+ "CALL_DISCONNECT_NOTIFY",
+ "WAN_ERROR_NOTIFY",
+ "SET_LINK_INFO"
+};
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_generic.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_generic.c
new file mode 100644
index 0000000..956ed81
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_generic.c
@@ -0,0 +1,61 @@
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4/ip_conntrack_protocol.h>
+
+unsigned long ip_ct_generic_timeout = 600*HZ;
+
+static int generic_pkt_to_tuple(const void *datah, size_t datalen,
+ struct ip_conntrack_tuple *tuple)
+{
+ tuple->src.u.all = 0;
+ tuple->dst.u.all = 0;
+
+ return 1;
+}
+
+static int generic_invert_tuple(struct ip_conntrack_tuple *tuple,
+ const struct ip_conntrack_tuple *orig)
+{
+ tuple->src.u.all = 0;
+ tuple->dst.u.all = 0;
+
+ return 1;
+}
+
+/* Print out the per-protocol part of the tuple. */
+static unsigned int generic_print_tuple(char *buffer,
+ const struct ip_conntrack_tuple *tuple)
+{
+ return 0;
+}
+
+/* Print out the private part of the conntrack. */
+static unsigned int generic_print_conntrack(char *buffer,
+ const struct ip_conntrack *state)
+{
+ return 0;
+}
+
+/* Returns verdict for packet, or -1 for invalid. */
+static int established(struct ip_conntrack *conntrack,
+ struct iphdr *iph, size_t len,
+ enum ip_conntrack_info ctinfo)
+{
+ ip_ct_refresh_acct(conntrack, ctinfo, iph, ip_ct_generic_timeout);
+ return NF_ACCEPT;
+}
+
+/* Called when a new connection for this protocol found. */
+static int
+new(struct ip_conntrack *conntrack, struct iphdr *iph, size_t len)
+{
+ return 1;
+}
+
+struct ip_conntrack_protocol ip_conntrack_generic_protocol
+= { { NULL, NULL }, 0, "unknown",
+ generic_pkt_to_tuple, generic_invert_tuple, generic_print_tuple,
+ generic_print_conntrack, established, new, NULL, NULL, NULL };
+
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_gre.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_gre.c
new file mode 100644
index 0000000..ab3eeae
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_gre.c
@@ -0,0 +1,345 @@
+/*
+ * ip_conntrack_proto_gre.c - Version 1.2
+ *
+ * Connection tracking protocol helper module for GRE.
+ *
+ * GRE is a generic encapsulation protocol, which is generally not very
+ * suited for NAT, as it has no protocol-specific part as port numbers.
+ *
+ * It has an optional key field, which may help us distinguishing two
+ * connections between the same two hosts.
+ *
+ * GRE is defined in RFC 1701 and RFC 1702, as well as RFC 2784
+ *
+ * PPTP is built on top of a modified version of GRE, and has a mandatory
+ * field called "CallID", which serves us for the same purpose as the key
+ * field in plain GRE.
+ *
+ * Documentation about PPTP can be found in RFC 2637
+ *
+ * (C) 2000-2003 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Development of this code funded by Astaro AG (http://www.astaro.com/)
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/ip.h>
+#include <linux/in.h>
+#include <linux/list.h>
+
+#include <linux/netfilter_ipv4/lockhelp.h>
+
+DECLARE_RWLOCK(ip_ct_gre_lock);
+#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_ct_gre_lock)
+#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_ct_gre_lock)
+
+#include <linux/netfilter_ipv4/listhelp.h>
+#include <linux/netfilter_ipv4/ip_conntrack_protocol.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_core.h>
+
+#include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h>
+#include <linux/netfilter_ipv4/ip_conntrack_pptp.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("netfilter connection tracking protocol helper for GRE");
+
+/* shamelessly stolen from ip_conntrack_proto_udp.c */
+#define GRE_TIMEOUT (30*HZ)
+#define GRE_STREAM_TIMEOUT (180*HZ)
+
+#if 0
+#define DEBUGP(format, args...) printk(KERN_DEBUG __FILE__ ":" __FUNCTION__ \
+ ": " format, ## args)
+#define DUMP_TUPLE_GRE(x) printk("%u.%u.%u.%u:0x%x -> %u.%u.%u.%u:0x%x:%u:0x%x\n", \
+ NIPQUAD((x)->src.ip), ntohl((x)->src.u.gre.key), \
+ NIPQUAD((x)->dst.ip), ntohl((x)->dst.u.gre.key), \
+ (x)->dst.u.gre.version, \
+ ntohs((x)->dst.u.gre.protocol))
+#else
+#define DEBUGP(x, args...)
+#define DUMP_TUPLE_GRE(x)
+#endif
+
+/* GRE KEYMAP HANDLING FUNCTIONS */
+static LIST_HEAD(gre_keymap_list);
+
+static inline int gre_key_cmpfn(const struct ip_ct_gre_keymap *km,
+ const struct ip_conntrack_tuple *t)
+{
+ return ((km->tuple.src.ip == t->src.ip) &&
+ (km->tuple.dst.ip == t->dst.ip) &&
+ (km->tuple.dst.protonum == t->dst.protonum) &&
+ (km->tuple.dst.u.all == t->dst.u.all));
+}
+
+/* look up the source key for a given tuple */
+static u_int32_t gre_keymap_lookup(struct ip_conntrack_tuple *t)
+{
+ struct ip_ct_gre_keymap *km;
+ u_int32_t key;
+
+ READ_LOCK(&ip_ct_gre_lock);
+ km = LIST_FIND(&gre_keymap_list, gre_key_cmpfn,
+ struct ip_ct_gre_keymap *, t);
+ if (!km) {
+ READ_UNLOCK(&ip_ct_gre_lock);
+ return 0;
+ }
+
+ key = km->tuple.src.u.gre.key;
+ READ_UNLOCK(&ip_ct_gre_lock);
+
+ return key;
+}
+
+/* add a single keymap entry, associate with specified expect */
+int ip_ct_gre_keymap_add(struct ip_conntrack_expect *exp,
+ struct ip_conntrack_tuple *t, int reply)
+{
+ struct ip_ct_gre_keymap *km;
+
+ km = kmalloc(sizeof(*km), GFP_ATOMIC);
+ if (!km)
+ return -1;
+
+ /* initializing list head should be sufficient */
+ memset(km, 0, sizeof(*km));
+
+ memcpy(&km->tuple, t, sizeof(*t));
+
+ if (!reply)
+ exp->proto.gre.keymap_orig = km;
+ else
+ exp->proto.gre.keymap_reply = km;
+
+ DEBUGP("adding new entry %p: ", km);
+ DUMP_TUPLE_GRE(&km->tuple);
+
+ WRITE_LOCK(&ip_ct_gre_lock);
+ list_append(&gre_keymap_list, km);
+ WRITE_UNLOCK(&ip_ct_gre_lock);
+
+ return 0;
+}
+
+/* change the tuple of a keymap entry (used by nat helper) */
+void ip_ct_gre_keymap_change(struct ip_ct_gre_keymap *km,
+ struct ip_conntrack_tuple *t)
+{
+ DEBUGP("changing entry %p to: ", km);
+ DUMP_TUPLE_GRE(t);
+
+ WRITE_LOCK(&ip_ct_gre_lock);
+ memcpy(&km->tuple, t, sizeof(km->tuple));
+ WRITE_UNLOCK(&ip_ct_gre_lock);
+}
+
+/* destroy the keymap entries associated with specified expect */
+void ip_ct_gre_keymap_destroy(struct ip_conntrack_expect *exp)
+{
+ DEBUGP("entering for exp %p\n", exp);
+ WRITE_LOCK(&ip_ct_gre_lock);
+ if (exp->proto.gre.keymap_orig) {
+ DEBUGP("removing %p from list\n", exp->proto.gre.keymap_orig);
+ list_del(&exp->proto.gre.keymap_orig->list);
+ kfree(exp->proto.gre.keymap_orig);
+ exp->proto.gre.keymap_orig = NULL;
+ }
+ if (exp->proto.gre.keymap_reply) {
+ DEBUGP("removing %p from list\n", exp->proto.gre.keymap_reply);
+ list_del(&exp->proto.gre.keymap_reply->list);
+ kfree(exp->proto.gre.keymap_reply);
+ exp->proto.gre.keymap_reply = NULL;
+ }
+ WRITE_UNLOCK(&ip_ct_gre_lock);
+}
+
+
+/* PUBLIC CONNTRACK PROTO HELPER FUNCTIONS */
+
+/* invert gre part of tuple */
+static int gre_invert_tuple(struct ip_conntrack_tuple *tuple,
+ const struct ip_conntrack_tuple *orig)
+{
+ tuple->dst.u.gre.protocol = orig->dst.u.gre.protocol;
+ tuple->dst.u.gre.version = orig->dst.u.gre.version;
+
+ tuple->dst.u.gre.key = orig->src.u.gre.key;
+ tuple->src.u.gre.key = orig->dst.u.gre.key;
+
+ return 1;
+}
+
+/* gre hdr info to tuple */
+static int gre_pkt_to_tuple(const void *datah, size_t datalen,
+ struct ip_conntrack_tuple *tuple)
+{
+ struct gre_hdr *grehdr = (struct gre_hdr *) datah;
+ struct gre_hdr_pptp *pgrehdr = (struct gre_hdr_pptp *) datah;
+ u_int32_t srckey;
+
+ /* core guarantees 8 protocol bytes, no need for size check */
+
+ tuple->dst.u.gre.version = grehdr->version;
+ tuple->dst.u.gre.protocol = grehdr->protocol;
+
+ switch (grehdr->version) {
+ case GRE_VERSION_1701:
+ if (!grehdr->key) {
+ DEBUGP("Can't track multiple tunnels without key\n");
+ tuple->dst.u.gre.key = 0;
+ } else {
+ tuple->dst.u.gre.key = *(gre_key(grehdr));
+ }
+ break;
+
+ case GRE_VERSION_PPTP:
+ if (ntohs(grehdr->protocol) != GRE_PROTOCOL_PPTP) {
+ DEBUGP("GRE_VERSION_PPTP but unknown proto\n");
+ return 0;
+ }
+ tuple->dst.u.gre.key = htonl(ntohs(pgrehdr->call_id));
+ break;
+
+ default:
+ printk(KERN_WARNING "unknown GRE version %hu\n",
+ tuple->dst.u.gre.version);
+ return 0;
+ }
+
+ srckey = gre_keymap_lookup(tuple);
+
+#if 0
+ DEBUGP("found src key %x for tuple ", ntohl(srckey));
+ DUMP_TUPLE_GRE(tuple);
+#endif
+ tuple->src.u.gre.key = srckey;
+
+ return 1;
+}
+
+/* print gre part of tuple */
+static unsigned int gre_print_tuple(char *buffer,
+ const struct ip_conntrack_tuple *tuple)
+{
+ return sprintf(buffer, "version=%d protocol=0x%04x srckey=0x%x dstkey=0x%x ",
+ tuple->dst.u.gre.version,
+ ntohs(tuple->dst.u.gre.protocol),
+ ntohl(tuple->src.u.gre.key),
+ ntohl(tuple->dst.u.gre.key));
+}
+
+/* print private data for conntrack */
+static unsigned int gre_print_conntrack(char *buffer,
+ const struct ip_conntrack *ct)
+{
+ return sprintf(buffer, "timeout=%u, stream_timeout=%u ",
+ (ct->proto.gre.timeout / HZ),
+ (ct->proto.gre.stream_timeout / HZ));
+}
+
+/* Returns verdict for packet, and may modify conntrack */
+static int gre_packet(struct ip_conntrack *ct,
+ struct iphdr *iph, size_t len,
+ enum ip_conntrack_info ctinfo)
+{
+ /* If we've seen traffic both ways, this is a GRE connection.
+ * Extend timeout. */
+ if (ct->status & IPS_SEEN_REPLY) {
+ ip_ct_refresh_acct(ct, ctinfo, iph,
+ ct->proto.gre.stream_timeout);
+ /* Also, more likely to be important, and not a probe. */
+ set_bit(IPS_ASSURED_BIT, &ct->status);
+ } else
+ ip_ct_refresh_acct(ct, ctinfo, iph, ct->proto.gre.timeout);
+
+ return NF_ACCEPT;
+}
+
+/* Called when a new connection for this protocol found. */
+static int gre_new(struct ip_conntrack *ct,
+ struct iphdr *iph, size_t len)
+{
+ DEBUGP(": ");
+ DUMP_TUPLE_GRE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
+
+ /* initialize to sane value. Ideally a conntrack helper
+ * (e.g. in case of pptp) is increasing them */
+ ct->proto.gre.stream_timeout = GRE_STREAM_TIMEOUT;
+ ct->proto.gre.timeout = GRE_TIMEOUT;
+
+ return 1;
+}
+
+/* Called when a conntrack entry has already been removed from the hashes
+ * and is about to be deleted from memory */
+static void gre_destroy(struct ip_conntrack *ct)
+{
+ struct ip_conntrack_expect *master = ct->master;
+
+ DEBUGP(" entering\n");
+
+ if (!master) {
+ DEBUGP("no master exp for ct %p\n", ct);
+ return;
+ }
+
+ ip_ct_gre_keymap_destroy(master);
+}
+
+/* protocol helper struct */
+static struct ip_conntrack_protocol gre = { { NULL, NULL }, IPPROTO_GRE,
+ "gre",
+ gre_pkt_to_tuple,
+ gre_invert_tuple,
+ gre_print_tuple,
+ gre_print_conntrack,
+ gre_packet,
+ gre_new,
+ gre_destroy,
+ NULL,
+ THIS_MODULE };
+
+/* ip_conntrack_proto_gre initialization */
+static int __init init(void)
+{
+ int retcode;
+
+ if ((retcode = ip_conntrack_protocol_register(&gre))) {
+ printk(KERN_ERR "Unable to register conntrack protocol "
+ "helper for gre: %d\n", retcode);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ struct list_head *pos, *n;
+
+ /* delete all keymap entries */
+ WRITE_LOCK(&ip_ct_gre_lock);
+ list_for_each_safe(pos, n, &gre_keymap_list) {
+ DEBUGP("deleting keymap %p at module unload time\n", pos);
+ list_del(pos);
+ kfree(pos);
+ }
+ WRITE_UNLOCK(&ip_ct_gre_lock);
+
+ ip_conntrack_protocol_unregister(&gre);
+}
+
+EXPORT_SYMBOL(ip_ct_gre_keymap_add);
+EXPORT_SYMBOL(ip_ct_gre_keymap_change);
+EXPORT_SYMBOL(ip_ct_gre_keymap_destroy);
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_icmp.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_icmp.c
new file mode 100644
index 0000000..67d5412
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_icmp.c
@@ -0,0 +1,116 @@
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/in.h>
+#include <linux/icmp.h>
+#include <linux/netfilter_ipv4/ip_conntrack_protocol.h>
+
+unsigned long ip_ct_icmp_timeout = 30*HZ;
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+static int icmp_pkt_to_tuple(const void *datah, size_t datalen,
+ struct ip_conntrack_tuple *tuple)
+{
+ const struct icmphdr *hdr = datah;
+
+ tuple->dst.u.icmp.type = hdr->type;
+ tuple->src.u.icmp.id = hdr->un.echo.id;
+ tuple->dst.u.icmp.code = hdr->code;
+
+ return 1;
+}
+
+static int icmp_invert_tuple(struct ip_conntrack_tuple *tuple,
+ const struct ip_conntrack_tuple *orig)
+{
+ /* Add 1; spaces filled with 0. */
+ static u_int8_t invmap[]
+ = { [ICMP_ECHO] = ICMP_ECHOREPLY + 1,
+ [ICMP_ECHOREPLY] = ICMP_ECHO + 1,
+ [ICMP_TIMESTAMP] = ICMP_TIMESTAMPREPLY + 1,
+ [ICMP_TIMESTAMPREPLY] = ICMP_TIMESTAMP + 1,
+ [ICMP_INFO_REQUEST] = ICMP_INFO_REPLY + 1,
+ [ICMP_INFO_REPLY] = ICMP_INFO_REQUEST + 1,
+ [ICMP_ADDRESS] = ICMP_ADDRESSREPLY + 1,
+ [ICMP_ADDRESSREPLY] = ICMP_ADDRESS + 1};
+
+ if (orig->dst.u.icmp.type >= sizeof(invmap)
+ || !invmap[orig->dst.u.icmp.type])
+ return 0;
+
+ tuple->src.u.icmp.id = orig->src.u.icmp.id;
+ tuple->dst.u.icmp.type = invmap[orig->dst.u.icmp.type] - 1;
+ tuple->dst.u.icmp.code = orig->dst.u.icmp.code;
+ return 1;
+}
+
+/* Print out the per-protocol part of the tuple. */
+static unsigned int icmp_print_tuple(char *buffer,
+ const struct ip_conntrack_tuple *tuple)
+{
+ return sprintf(buffer, "type=%u code=%u id=%u ",
+ tuple->dst.u.icmp.type,
+ tuple->dst.u.icmp.code,
+ ntohs(tuple->src.u.icmp.id));
+}
+
+/* Print out the private part of the conntrack. */
+static unsigned int icmp_print_conntrack(char *buffer,
+ const struct ip_conntrack *conntrack)
+{
+ return 0;
+}
+
+/* Returns verdict for packet, or -1 for invalid. */
+static int icmp_packet(struct ip_conntrack *ct,
+ struct iphdr *iph, size_t len,
+ enum ip_conntrack_info ctinfo)
+{
+ /* Try to delete connection immediately after all replies:
+ won't actually vanish as we still have skb, and del_timer
+ means this will only run once even if count hits zero twice
+ (theoretically possible with SMP) */
+ if (CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY) {
+ if (atomic_dec_and_test(&ct->proto.icmp.count)
+ && del_timer(&ct->timeout))
+ ct->timeout.function((unsigned long)ct);
+ } else {
+ atomic_inc(&ct->proto.icmp.count);
+ ip_ct_refresh_acct(ct, ctinfo, iph, ip_ct_icmp_timeout);
+ }
+
+ return NF_ACCEPT;
+}
+
+/* Called when a new connection for this protocol found. */
+static int icmp_new(struct ip_conntrack *conntrack,
+ struct iphdr *iph, size_t len)
+{
+ static u_int8_t valid_new[]
+ = { [ICMP_ECHO] = 1,
+ [ICMP_TIMESTAMP] = 1,
+ [ICMP_INFO_REQUEST] = 1,
+ [ICMP_ADDRESS] = 1 };
+
+ if (conntrack->tuplehash[0].tuple.dst.u.icmp.type >= sizeof(valid_new)
+ || !valid_new[conntrack->tuplehash[0].tuple.dst.u.icmp.type]) {
+ /* Can't create a new ICMP `conn' with this. */
+ DEBUGP("icmp: can't create new conn with type %u\n",
+ conntrack->tuplehash[0].tuple.dst.u.icmp.type);
+ DUMP_TUPLE(&conntrack->tuplehash[0].tuple);
+ return 0;
+ }
+ atomic_set(&conntrack->proto.icmp.count, 0);
+ return 1;
+}
+
+struct ip_conntrack_protocol ip_conntrack_protocol_icmp
+= { { NULL, NULL }, IPPROTO_ICMP, "icmp",
+ icmp_pkt_to_tuple, icmp_invert_tuple, icmp_print_tuple,
+ icmp_print_conntrack, icmp_packet, icmp_new, NULL, NULL, NULL };
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_tcp.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_tcp.c
new file mode 100644
index 0000000..25e49f1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_tcp.c
@@ -0,0 +1,258 @@
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/module.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/string.h>
+
+#include <net/tcp.h>
+
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+#include <linux/netfilter_ipv4/ip_conntrack_protocol.h>
+#include <linux/netfilter_ipv4/lockhelp.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+/* Protects conntrack->proto.tcp */
+static DECLARE_RWLOCK(tcp_lock);
+
+/* FIXME: Examine ipfilter's timeouts and conntrack transitions more
+ closely. They're more complex. --RR */
+
+/* Actually, I believe that neither ipmasq (where this code is stolen
+ from) nor ipfilter do it exactly right. A new conntrack machine taking
+ into account packet loss (which creates uncertainty as to exactly
+ the conntrack of the connection) is required. RSN. --RR */
+
+static const char *tcp_conntrack_names[] = {
+ "NONE",
+ "ESTABLISHED",
+ "SYN_SENT",
+ "SYN_RECV",
+ "FIN_WAIT",
+ "TIME_WAIT",
+ "CLOSE",
+ "CLOSE_WAIT",
+ "LAST_ACK",
+ "LISTEN"
+};
+
+#define SECS *HZ
+#define MINS * 60 SECS
+#define HOURS * 60 MINS
+#define DAYS * 24 HOURS
+
+unsigned long ip_ct_tcp_timeout_syn_sent = 2 MINS;
+unsigned long ip_ct_tcp_timeout_syn_recv = 60 SECS;
+unsigned long ip_ct_tcp_timeout_established = 5 DAYS;
+unsigned long ip_ct_tcp_timeout_fin_wait = 2 MINS;
+unsigned long ip_ct_tcp_timeout_close_wait = 60 SECS;
+unsigned long ip_ct_tcp_timeout_last_ack = 30 SECS;
+unsigned long ip_ct_tcp_timeout_time_wait = 2 MINS;
+unsigned long ip_ct_tcp_timeout_close = 10 SECS;
+
+static unsigned long * tcp_timeouts[]
+= { 0, /* TCP_CONNTRACK_NONE */
+ &ip_ct_tcp_timeout_established, /* TCP_CONNTRACK_ESTABLISHED, */
+ &ip_ct_tcp_timeout_syn_sent, /* TCP_CONNTRACK_SYN_SENT, */
+ &ip_ct_tcp_timeout_syn_recv, /* TCP_CONNTRACK_SYN_RECV, */
+ &ip_ct_tcp_timeout_fin_wait, /* TCP_CONNTRACK_FIN_WAIT, */
+ &ip_ct_tcp_timeout_time_wait, /* TCP_CONNTRACK_TIME_WAIT, */
+ &ip_ct_tcp_timeout_close, /* TCP_CONNTRACK_CLOSE, */
+ &ip_ct_tcp_timeout_close_wait, /* TCP_CONNTRACK_CLOSE_WAIT, */
+ &ip_ct_tcp_timeout_last_ack, /* TCP_CONNTRACK_LAST_ACK, */
+ 0, /* TCP_CONNTRACK_LISTEN */
+ };
+
+#define sNO TCP_CONNTRACK_NONE
+#define sES TCP_CONNTRACK_ESTABLISHED
+#define sSS TCP_CONNTRACK_SYN_SENT
+#define sSR TCP_CONNTRACK_SYN_RECV
+#define sFW TCP_CONNTRACK_FIN_WAIT
+#define sTW TCP_CONNTRACK_TIME_WAIT
+#define sCL TCP_CONNTRACK_CLOSE
+#define sCW TCP_CONNTRACK_CLOSE_WAIT
+#define sLA TCP_CONNTRACK_LAST_ACK
+#define sLI TCP_CONNTRACK_LISTEN
+#define sIV TCP_CONNTRACK_MAX
+
+static enum tcp_conntrack tcp_conntracks[2][5][TCP_CONNTRACK_MAX] = {
+ {
+/* ORIGINAL */
+/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI */
+/*syn*/ {sSS, sES, sSS, sSR, sSS, sSS, sSS, sSS, sSS, sLI },
+/*fin*/ {sTW, sFW, sSS, sTW, sFW, sTW, sCL, sTW, sLA, sLI },
+/*ack*/ {sES, sES, sSS, sES, sFW, sTW, sCL, sCW, sLA, sES },
+/*rst*/ {sCL, sCL, sSS, sCL, sCL, sTW, sCL, sCL, sCL, sCL },
+/*none*/{sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV }
+ },
+ {
+/* REPLY */
+/* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI */
+/*syn*/ {sSR, sES, sSR, sSR, sSR, sSR, sSR, sSR, sSR, sSR },
+/*fin*/ {sCL, sCW, sSS, sTW, sTW, sTW, sCL, sCW, sLA, sLI },
+/*ack*/ {sCL, sES, sSS, sSR, sFW, sTW, sCL, sCW, sCL, sLI },
+/*rst*/ {sCL, sCL, sCL, sCL, sCL, sCL, sCL, sCL, sLA, sLI },
+/*none*/{sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV, sIV }
+ }
+};
+
+static int tcp_pkt_to_tuple(const void *datah, size_t datalen,
+ struct ip_conntrack_tuple *tuple)
+{
+ const struct tcphdr *hdr = datah;
+
+ tuple->src.u.tcp.port = hdr->source;
+ tuple->dst.u.tcp.port = hdr->dest;
+
+ return 1;
+}
+
+static int tcp_invert_tuple(struct ip_conntrack_tuple *tuple,
+ const struct ip_conntrack_tuple *orig)
+{
+ tuple->src.u.tcp.port = orig->dst.u.tcp.port;
+ tuple->dst.u.tcp.port = orig->src.u.tcp.port;
+ return 1;
+}
+
+/* Print out the per-protocol part of the tuple. */
+static unsigned int tcp_print_tuple(char *buffer,
+ const struct ip_conntrack_tuple *tuple)
+{
+ return sprintf(buffer, "sport=%hu dport=%hu ",
+ ntohs(tuple->src.u.tcp.port),
+ ntohs(tuple->dst.u.tcp.port));
+}
+
+/* Print out the private part of the conntrack. */
+static unsigned int tcp_print_conntrack(char *buffer,
+ const struct ip_conntrack *conntrack)
+{
+ enum tcp_conntrack state;
+
+ READ_LOCK(&tcp_lock);
+ state = conntrack->proto.tcp.state;
+ READ_UNLOCK(&tcp_lock);
+
+ return sprintf(buffer, "%s ", tcp_conntrack_names[state]);
+}
+
+static unsigned int get_conntrack_index(const struct tcphdr *tcph)
+{
+ if (tcph->rst) return 3;
+ else if (tcph->syn) return 0;
+ else if (tcph->fin) return 1;
+ else if (tcph->ack) return 2;
+ else return 4;
+}
+
+/* Returns verdict for packet, or -1 for invalid. */
+static int tcp_packet(struct ip_conntrack *conntrack,
+ struct iphdr *iph, size_t len,
+ enum ip_conntrack_info ctinfo)
+{
+ enum tcp_conntrack newconntrack, oldtcpstate;
+ struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph + iph->ihl);
+
+ /* We're guaranteed to have the base header, but maybe not the
+ options. */
+ if (len < (iph->ihl + tcph->doff) * 4) {
+ DEBUGP("ip_conntrack_tcp: Truncated packet.\n");
+ return -1;
+ }
+
+ WRITE_LOCK(&tcp_lock);
+ oldtcpstate = conntrack->proto.tcp.state;
+ newconntrack
+ = tcp_conntracks
+ [CTINFO2DIR(ctinfo)]
+ [get_conntrack_index(tcph)][oldtcpstate];
+
+ /* Invalid */
+ if (newconntrack == TCP_CONNTRACK_MAX) {
+ DEBUGP("ip_conntrack_tcp: Invalid dir=%i index=%u conntrack=%u\n",
+ CTINFO2DIR(ctinfo), get_conntrack_index(tcph),
+ conntrack->proto.tcp.state);
+ WRITE_UNLOCK(&tcp_lock);
+ return -1;
+ }
+
+ conntrack->proto.tcp.state = newconntrack;
+
+ /* Poor man's window tracking: record SYN/ACK for handshake check */
+ if (oldtcpstate == TCP_CONNTRACK_SYN_SENT
+ && CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY
+ && tcph->syn && tcph->ack)
+ conntrack->proto.tcp.handshake_ack
+ = htonl(ntohl(tcph->seq) + 1);
+
+ /* If only reply is a RST, we can consider ourselves not to
+ have an established connection: this is a fairly common
+ problem case, so we can delete the conntrack
+ immediately. --RR */
+ if (!test_bit(IPS_SEEN_REPLY_BIT, &conntrack->status) && tcph->rst) {
+ WRITE_UNLOCK(&tcp_lock);
+ if (del_timer(&conntrack->timeout))
+ conntrack->timeout.function((unsigned long)conntrack);
+ } else {
+ /* Set ASSURED if we see see valid ack in ESTABLISHED after SYN_RECV */
+ if (oldtcpstate == TCP_CONNTRACK_SYN_RECV
+ && CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL
+ && tcph->ack && !tcph->syn
+ && tcph->ack_seq == conntrack->proto.tcp.handshake_ack)
+ set_bit(IPS_ASSURED_BIT, &conntrack->status);
+
+ WRITE_UNLOCK(&tcp_lock);
+ ip_ct_refresh_acct(conntrack, ctinfo, iph,
+ *tcp_timeouts[newconntrack]);
+ }
+
+ return NF_ACCEPT;
+}
+
+/* Called when a new connection for this protocol found. */
+static int tcp_new(struct ip_conntrack *conntrack,
+ struct iphdr *iph, size_t len)
+{
+ enum tcp_conntrack newconntrack;
+ struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph + iph->ihl);
+
+ /* Don't need lock here: this conntrack not in circulation yet */
+ newconntrack
+ = tcp_conntracks[0][get_conntrack_index(tcph)]
+ [TCP_CONNTRACK_NONE];
+
+ /* Invalid: delete conntrack */
+ if (newconntrack == TCP_CONNTRACK_MAX) {
+ DEBUGP("ip_conntrack_tcp: invalid new deleting.\n");
+ return 0;
+ }
+
+ conntrack->proto.tcp.state = newconntrack;
+ return 1;
+}
+
+static int tcp_exp_matches_pkt(struct ip_conntrack_expect *exp,
+ struct sk_buff **pskb)
+{
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph + iph->ihl);
+ unsigned int datalen;
+
+ datalen = (*pskb)->len - iph->ihl*4 - tcph->doff*4;
+
+ return between(exp->seq, ntohl(tcph->seq), ntohl(tcph->seq) + datalen);
+}
+
+struct ip_conntrack_protocol ip_conntrack_protocol_tcp
+= { { NULL, NULL }, IPPROTO_TCP, "tcp",
+ tcp_pkt_to_tuple, tcp_invert_tuple, tcp_print_tuple, tcp_print_conntrack,
+ tcp_packet, tcp_new, NULL, tcp_exp_matches_pkt, NULL };
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_udp.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_udp.c
new file mode 100644
index 0000000..1a47112
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_proto_udp.c
@@ -0,0 +1,75 @@
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/netfilter.h>
+#include <linux/in.h>
+#include <linux/udp.h>
+#include <linux/netfilter_ipv4/ip_conntrack_protocol.h>
+
+unsigned long ip_ct_udp_timeout = 30*HZ;
+unsigned long ip_ct_udp_timeout_stream = 180*HZ;
+
+static int udp_pkt_to_tuple(const void *datah, size_t datalen,
+ struct ip_conntrack_tuple *tuple)
+{
+ const struct udphdr *hdr = datah;
+
+ tuple->src.u.udp.port = hdr->source;
+ tuple->dst.u.udp.port = hdr->dest;
+
+ return 1;
+}
+
+static int udp_invert_tuple(struct ip_conntrack_tuple *tuple,
+ const struct ip_conntrack_tuple *orig)
+{
+ tuple->src.u.udp.port = orig->dst.u.udp.port;
+ tuple->dst.u.udp.port = orig->src.u.udp.port;
+ return 1;
+}
+
+/* Print out the per-protocol part of the tuple. */
+static unsigned int udp_print_tuple(char *buffer,
+ const struct ip_conntrack_tuple *tuple)
+{
+ return sprintf(buffer, "sport=%hu dport=%hu ",
+ ntohs(tuple->src.u.udp.port),
+ ntohs(tuple->dst.u.udp.port));
+}
+
+/* Print out the private part of the conntrack. */
+static unsigned int udp_print_conntrack(char *buffer,
+ const struct ip_conntrack *conntrack)
+{
+ return 0;
+}
+
+/* Returns verdict for packet, and may modify conntracktype */
+static int udp_packet(struct ip_conntrack *conntrack,
+ struct iphdr *iph, size_t len,
+ enum ip_conntrack_info ctinfo)
+{
+ /* If we've seen traffic both ways, this is some kind of UDP
+ stream. Extend timeout. */
+ if (test_bit(IPS_SEEN_REPLY_BIT, &conntrack->status)) {
+ ip_ct_refresh_acct(conntrack, ctinfo, iph,
+ ip_ct_udp_timeout_stream);
+ /* Also, more likely to be important, and not a probe */
+ set_bit(IPS_ASSURED_BIT, &conntrack->status);
+ } else
+ ip_ct_refresh_acct(conntrack, ctinfo, iph, ip_ct_udp_timeout);
+
+ return NF_ACCEPT;
+}
+
+/* Called when a new connection for this protocol found. */
+static int udp_new(struct ip_conntrack *conntrack,
+ struct iphdr *iph, size_t len)
+{
+ return 1;
+}
+
+struct ip_conntrack_protocol ip_conntrack_protocol_udp
+= { { NULL, NULL }, IPPROTO_UDP, "udp",
+ udp_pkt_to_tuple, udp_invert_tuple, udp_print_tuple, udp_print_conntrack,
+ udp_packet, udp_new, NULL, NULL, NULL };
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_standalone.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_standalone.c
new file mode 100644
index 0000000..8f27744
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_standalone.c
@@ -0,0 +1,636 @@
+/* This file contains all the functions required for the standalone
+ ip_conntrack module.
+
+ These are not required by the compatibility layer.
+*/
+
+/* (c) 1999 Paul `Rusty' Russell. Licenced under the GNU General
+ Public Licence. */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/ip.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/version.h>
+#include <linux/brlock.h>
+#ifdef CONFIG_SYSCTL
+#include <linux/sysctl.h>
+#endif
+#include <net/checksum.h>
+
+#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_conntrack_lock)
+#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_conntrack_lock)
+
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+#include <linux/netfilter_ipv4/ip_conntrack_protocol.h>
+#include <linux/netfilter_ipv4/ip_conntrack_core.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+MODULE_LICENSE("GPL");
+
+static int kill_proto(struct ip_conntrack *i, void *data)
+{
+ return (i->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum ==
+ *((u_int8_t *) data));
+}
+
+static unsigned int
+print_tuple(char *buffer, const struct ip_conntrack_tuple *tuple,
+ struct ip_conntrack_protocol *proto)
+{
+ int len;
+
+ len = sprintf(buffer, "src=%u.%u.%u.%u dst=%u.%u.%u.%u ",
+ NIPQUAD(tuple->src.ip), NIPQUAD(tuple->dst.ip));
+
+ len += proto->print_tuple(buffer + len, tuple);
+
+ return len;
+}
+
+/* FIXME: Don't print source proto part. --RR */
+static unsigned int
+print_expect(char *buffer, const struct ip_conntrack_expect *expect)
+{
+ unsigned int len;
+
+ if (expect->expectant->helper->timeout)
+ len = sprintf(buffer, "EXPECTING: %lu ",
+ timer_pending(&expect->timeout)
+ ? (expect->timeout.expires - jiffies)/HZ : 0);
+ else
+ len = sprintf(buffer, "EXPECTING: - ");
+ len += sprintf(buffer + len, "use=%u proto=%u ",
+ atomic_read(&expect->use), expect->tuple.dst.protonum);
+ len += print_tuple(buffer + len, &expect->tuple,
+ __ip_ct_find_proto(expect->tuple.dst.protonum));
+ len += sprintf(buffer + len, "\n");
+ return len;
+}
+
+#ifdef CONFIG_IP_NF_CT_ACCT
+static unsigned int
+print_counters(char *buffer, struct ip_conntrack_counter *counter)
+{
+ return sprintf(buffer, "packets=%llu bytes=%llu ",
+ counter->packets, counter->bytes);
+}
+#else
+#define print_counters(x, y) 0
+#endif
+
+static unsigned int
+print_conntrack(char *buffer, struct ip_conntrack *conntrack)
+{
+ unsigned int len;
+ struct ip_conntrack_protocol *proto
+ = __ip_ct_find_proto(conntrack->tuplehash[IP_CT_DIR_ORIGINAL]
+ .tuple.dst.protonum);
+
+ len = sprintf(buffer, "%-8s %u %lu ",
+ proto->name,
+ conntrack->tuplehash[IP_CT_DIR_ORIGINAL]
+ .tuple.dst.protonum,
+ timer_pending(&conntrack->timeout)
+ ? (conntrack->timeout.expires - jiffies)/HZ : 0);
+
+ len += proto->print_conntrack(buffer + len, conntrack);
+ len += print_tuple(buffer + len,
+ &conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple,
+ proto);
+ len += print_counters(buffer + len,
+ &conntrack->counters[IP_CT_DIR_ORIGINAL]);
+ if (!(test_bit(IPS_SEEN_REPLY_BIT, &conntrack->status)))
+ len += sprintf(buffer + len, "[UNREPLIED] ");
+ len += print_tuple(buffer + len,
+ &conntrack->tuplehash[IP_CT_DIR_REPLY].tuple,
+ proto);
+ len += print_counters(buffer + len,
+ &conntrack->counters[IP_CT_DIR_REPLY]);
+ if (test_bit(IPS_ASSURED_BIT, &conntrack->status))
+ len += sprintf(buffer + len, "[ASSURED] ");
+ len += sprintf(buffer + len, "use=%u ",
+ atomic_read(&conntrack->ct_general.use));
+#if defined(CONFIG_IP_NF_CONNTRACK_MARK)
+ len += sprintf(buffer + len, "mark=%ld ", conntrack->mark);
+#endif
+ len += sprintf(buffer + len, "\n");
+
+ return len;
+}
+
+/* Returns true when finished. */
+static inline int
+conntrack_iterate(const struct ip_conntrack_tuple_hash *hash,
+ char *buffer, off_t offset, off_t *upto,
+ unsigned int *len, unsigned int maxlen)
+{
+ unsigned int newlen;
+ IP_NF_ASSERT(hash->ctrack);
+
+ MUST_BE_READ_LOCKED(&ip_conntrack_lock);
+
+ /* Only count originals */
+ if (DIRECTION(hash))
+ return 0;
+
+ if (*upto < offset) {
+ (*upto)++;
+ return 0;
+ }
+
+ newlen = print_conntrack(buffer + *len, hash->ctrack);
+ if (*len + newlen > maxlen)
+ return 1;
+
+ *len += newlen;
+ (*upto)++;
+ return 0;
+}
+
+static int
+list_conntracks(char *buffer, char **start, off_t offset, int length)
+{
+ unsigned int i;
+ unsigned int len = 0;
+ off_t upto = 0;
+ struct list_head *e;
+
+ READ_LOCK(&ip_conntrack_lock);
+ /* Traverse hash; print originals then reply. */
+ for (i = 0; i < ip_conntrack_htable_size; i++) {
+ if (LIST_FIND(&ip_conntrack_hash[i], conntrack_iterate,
+ struct ip_conntrack_tuple_hash *,
+ buffer, offset, &upto, &len, length))
+ goto finished;
+ }
+
+ /* Now iterate through expecteds. */
+ for (e = ip_conntrack_expect_list.next;
+ e != &ip_conntrack_expect_list; e = e->next) {
+ unsigned int last_len;
+ struct ip_conntrack_expect *expect
+ = (struct ip_conntrack_expect *)e;
+ if (upto < offset) {
+ upto++;
+ continue;
+ }
+
+ last_len = len;
+ len += print_expect(buffer + len, expect);
+ if (len > length) {
+ len = last_len;
+ goto finished;
+ }
+ upto++;
+ }
+
+ finished:
+ READ_UNLOCK(&ip_conntrack_lock);
+
+ /* `start' hack - see fs/proc/generic.c line ~165 */
+ *start = (char *)((unsigned int)upto - offset);
+ return len;
+}
+
+/* Map sparse protocol list to dense stats array for proto's we care about */
+static int proto_map[IPPROTO_MAX];
+
+static int
+list_conntracks_stats(char *buffer, char **start, off_t offset, int length)
+{
+ unsigned int i;
+ unsigned int len = 0;
+ struct list_head *p;
+ struct ip_conntrack_tuple_hash *hash;
+
+ unsigned int ct = 0; /* number of conntracks found */
+ unsigned int ctreply = 0; /* number of reply conntracks found */
+ unsigned int expect = 0; /* number of expects found */
+ unsigned int unreplied = 0; /* count unreplied conntracks */
+ unsigned int hashmax = 0; /* max ct per hash bucket found */
+ unsigned int hashmin = 9999; /* min ct per hash bucket found */
+ unsigned int hashcnt; /* count ct in current hash bucket */
+ unsigned int proto; /* temp protocol number */
+#define MAXSTATS 32
+ unsigned int hashfreq[MAXSTATS]; /* frequency count of list depth */
+ unsigned int protonum[MAXSTATS]; /* frequency count of protocols */
+ unsigned int prototime[MAXSTATS]; /* timeout count of protocols */
+
+ /* proto_map should only be initialized once...
+ * but then this is all ridiculously inefficent... */
+ for (i = 0; i < IPPROTO_MAX; i++) {
+ proto_map[i] = 0;
+ }
+ i = 1;
+ proto_map[IPPROTO_ICMP] = i++;
+ proto_map[IPPROTO_TCP] = i++;
+ proto_map[IPPROTO_UDP] = i++;
+ proto_map[IPPROTO_GRE] = i++;
+ proto_map[IPPROTO_IPV6] = i++;
+ proto_map[IPPROTO_ESP] = i++;
+ proto_map[IPPROTO_AH] = i++;
+
+ for (i = 0; i < MAXSTATS; i++) {
+ hashfreq[i] = protonum[i] = prototime[i] = 0;
+ }
+
+ READ_LOCK(&ip_conntrack_lock);
+
+ /*
+ * count hash-table entries
+ */
+ for (i = 0; i < ip_conntrack_htable_size; i++) {
+ hashcnt = 0;
+ list_for_each(p, &ip_conntrack_hash[i]) {
+ hashcnt++;
+
+ /* count originals differently */
+ hash = (struct ip_conntrack_tuple_hash *)p;
+ if (DIRECTION(hash)) {
+ ctreply++;
+ continue;
+ }
+ ct++;
+
+ proto = hash->ctrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum;
+ proto = (proto < IPPROTO_MAX) ? proto_map[proto] : 0;
+ protonum[proto]++;
+ if (timer_pending(&hash->ctrack->timeout))
+ prototime[proto] += (hash->ctrack->timeout.expires - jiffies)/HZ;
+
+ if (!(test_bit(IPS_SEEN_REPLY_BIT, &hash->ctrack->status)))
+ unreplied++;
+ }
+ if (hashmin > hashcnt)
+ hashmin = hashcnt;
+ if (hashmax < hashcnt)
+ hashmax = hashcnt;
+ /* all counts above 31 accumulate in bucket 31 */
+ if (hashcnt >= MAXSTATS)
+ hashcnt = MAXSTATS-1;
+ hashfreq[hashcnt]++;
+ }
+
+ /*
+ * count the expect list
+ */
+ list_for_each(p, &ip_conntrack_expect_list) {
+ expect++;
+ }
+
+ READ_UNLOCK(&ip_conntrack_lock);
+
+ len = sprintf(buffer,
+ "ct=%d ctreply=%d expect=%d unreplied=%d\n"
+ "hash min=%d max=%d avg=%d\n"
+ "f0=%d f1=%d f2=%d f3=%d f4=%d f5=%d f6=%d fmax=%d\n"
+ "count othr=%d icmp=%d tcp=%d udp=%d gre=%d ip6=%d esp=%d ah=%d\n"
+ "avtmo othr=%d icmp=%d tcp=%d udp=%d gre=%d ip6=%d esp=%d ah=%d\n",
+ ct, ctreply, expect, unreplied,
+ hashmin, hashmax, (ct+ctreply)/ip_conntrack_htable_size,
+ hashfreq[0], hashfreq[1], hashfreq[2], hashfreq[3],
+ hashfreq[4], hashfreq[5], hashfreq[6], hashfreq[MAXSTATS-1],
+ protonum[0], protonum[1], protonum[2], protonum[3],
+ protonum[4], protonum[5], protonum[6], protonum[7],
+ protonum[0] ? prototime[0]/protonum[0] : 0,
+ protonum[1] ? prototime[1]/protonum[1] : 0,
+ protonum[2] ? prototime[2]/protonum[2] : 0,
+ protonum[3] ? prototime[3]/protonum[3] : 0,
+ protonum[4] ? prototime[4]/protonum[4] : 0,
+ protonum[5] ? prototime[5]/protonum[5] : 0,
+ protonum[6] ? prototime[6]/protonum[6] : 0,
+ protonum[7] ? prototime[7]/protonum[7] : 0);
+
+ len -= offset;
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+
+ *start = buffer + offset;
+ return len;
+}
+
+static unsigned int ip_confirm(unsigned int hooknum,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ /* We've seen it coming out the other side: confirm it */
+ return ip_conntrack_confirm(*pskb);
+}
+
+static unsigned int ip_refrag(unsigned int hooknum,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ struct rtable *rt = (struct rtable *)(*pskb)->dst;
+
+ /* We've seen it coming out the other side: confirm */
+ if (ip_confirm(hooknum, pskb, in, out, okfn) != NF_ACCEPT)
+ return NF_DROP;
+
+ /* Local packets are never produced too large for their
+ interface. We degfragment them at LOCAL_OUT, however,
+ so we have to refragment them here. */
+ if ((*pskb)->len > rt->u.dst.pmtu) {
+ /* No hook can be after us, so this should be OK. */
+ ip_fragment(*pskb, okfn);
+ return NF_STOLEN;
+ }
+ return NF_ACCEPT;
+}
+
+static unsigned int ip_conntrack_local(unsigned int hooknum,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ /* root is playing with raw sockets. */
+ if ((*pskb)->len < sizeof(struct iphdr)
+ || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr)) {
+ if (net_ratelimit())
+ printk("ipt_hook: happy cracking.\n");
+ return NF_ACCEPT;
+ }
+ return ip_conntrack_in(hooknum, pskb, in, out, okfn);
+}
+
+/* Connection tracking may drop packets, but never alters them, so
+ make it the first hook. */
+static struct nf_hook_ops ip_conntrack_in_ops
+= { { NULL, NULL }, ip_conntrack_in, PF_INET, NF_IP_PRE_ROUTING,
+ NF_IP_PRI_CONNTRACK };
+static struct nf_hook_ops ip_conntrack_local_out_ops
+= { { NULL, NULL }, ip_conntrack_local, PF_INET, NF_IP_LOCAL_OUT,
+ NF_IP_PRI_CONNTRACK };
+/* Refragmenter; last chance. */
+static struct nf_hook_ops ip_conntrack_out_ops
+= { { NULL, NULL }, ip_refrag, PF_INET, NF_IP_POST_ROUTING, NF_IP_PRI_LAST };
+static struct nf_hook_ops ip_conntrack_local_in_ops
+= { { NULL, NULL }, ip_confirm, PF_INET, NF_IP_LOCAL_IN, NF_IP_PRI_LAST-1 };
+
+/* Sysctl support */
+
+#ifdef CONFIG_SYSCTL
+
+/* From ip_conntrack_core.c */
+extern int ip_conntrack_max;
+extern unsigned int ip_conntrack_htable_size;
+
+/* From ip_conntrack_proto_tcp.c */
+extern unsigned long ip_ct_tcp_timeout_syn_sent;
+extern unsigned long ip_ct_tcp_timeout_syn_recv;
+extern unsigned long ip_ct_tcp_timeout_established;
+extern unsigned long ip_ct_tcp_timeout_fin_wait;
+extern unsigned long ip_ct_tcp_timeout_close_wait;
+extern unsigned long ip_ct_tcp_timeout_last_ack;
+extern unsigned long ip_ct_tcp_timeout_time_wait;
+extern unsigned long ip_ct_tcp_timeout_close;
+
+/* From ip_conntrack_proto_udp.c */
+extern unsigned long ip_ct_udp_timeout;
+extern unsigned long ip_ct_udp_timeout_stream;
+
+/* From ip_conntrack_proto_icmp.c */
+extern unsigned long ip_ct_icmp_timeout;
+
+/* From ip_conntrack_proto_icmp.c */
+extern unsigned long ip_ct_generic_timeout;
+
+static struct ctl_table_header *ip_ct_sysctl_header;
+
+static ctl_table ip_ct_sysctl_table[] = {
+ {NET_IPV4_NF_CONNTRACK_MAX, "ip_conntrack_max",
+ &ip_conntrack_max, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_NF_CONNTRACK_BUCKETS, "ip_conntrack_buckets",
+ &ip_conntrack_htable_size, sizeof(unsigned int), 0444, NULL,
+ &proc_dointvec},
+ {NET_IPV4_NF_CONNTRACK_TCP_TIMEOUT_SYN_SENT, "ip_conntrack_tcp_timeout_syn_sent",
+ &ip_ct_tcp_timeout_syn_sent, sizeof(unsigned int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_IPV4_NF_CONNTRACK_TCP_TIMEOUT_SYN_RECV, "ip_conntrack_tcp_timeout_syn_recv",
+ &ip_ct_tcp_timeout_syn_recv, sizeof(unsigned int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_IPV4_NF_CONNTRACK_TCP_TIMEOUT_ESTABLISHED, "ip_conntrack_tcp_timeout_established",
+ &ip_ct_tcp_timeout_established, sizeof(unsigned int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_IPV4_NF_CONNTRACK_TCP_TIMEOUT_FIN_WAIT, "ip_conntrack_tcp_timeout_fin_wait",
+ &ip_ct_tcp_timeout_fin_wait, sizeof(unsigned int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_IPV4_NF_CONNTRACK_TCP_TIMEOUT_CLOSE_WAIT, "ip_conntrack_tcp_timeout_close_wait",
+ &ip_ct_tcp_timeout_close_wait, sizeof(unsigned int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_IPV4_NF_CONNTRACK_TCP_TIMEOUT_LAST_ACK, "ip_conntrack_tcp_timeout_last_ack",
+ &ip_ct_tcp_timeout_last_ack, sizeof(unsigned int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_IPV4_NF_CONNTRACK_TCP_TIMEOUT_TIME_WAIT, "ip_conntrack_tcp_timeout_time_wait",
+ &ip_ct_tcp_timeout_time_wait, sizeof(unsigned int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_IPV4_NF_CONNTRACK_TCP_TIMEOUT_CLOSE, "ip_conntrack_tcp_timeout_close",
+ &ip_ct_tcp_timeout_close, sizeof(unsigned int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_IPV4_NF_CONNTRACK_UDP_TIMEOUT, "ip_conntrack_udp_timeout",
+ &ip_ct_udp_timeout, sizeof(unsigned int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_IPV4_NF_CONNTRACK_UDP_TIMEOUT_STREAM, "ip_conntrack_udp_timeout_stream",
+ &ip_ct_udp_timeout_stream, sizeof(unsigned int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_IPV4_NF_CONNTRACK_ICMP_TIMEOUT, "ip_conntrack_icmp_timeout",
+ &ip_ct_icmp_timeout, sizeof(unsigned int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {NET_IPV4_NF_CONNTRACK_GENERIC_TIMEOUT, "ip_conntrack_generic_timeout",
+ &ip_ct_generic_timeout, sizeof(unsigned int), 0644, NULL,
+ &proc_dointvec_jiffies},
+ {0}
+};
+
+#define NET_IP_CONNTRACK_MAX 2089
+
+static ctl_table ip_ct_netfilter_table[] = {
+ {NET_IPV4_NETFILTER, "netfilter", NULL, 0, 0555, ip_ct_sysctl_table, 0, 0, 0, 0, 0},
+ {NET_IP_CONNTRACK_MAX, "ip_conntrack_max",
+ &ip_conntrack_max, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {0}
+};
+
+static ctl_table ip_ct_ipv4_table[] = {
+ {NET_IPV4, "ipv4", NULL, 0, 0555, ip_ct_netfilter_table, 0, 0, 0, 0, 0},
+ {0}
+};
+
+static ctl_table ip_ct_net_table[] = {
+ {CTL_NET, "net", NULL, 0, 0555, ip_ct_ipv4_table, 0, 0, 0, 0, 0},
+ {0}
+};
+#endif
+static int init_or_cleanup(int init)
+{
+ struct proc_dir_entry *proc;
+ int ret = 0;
+
+ if (!init) goto cleanup;
+
+ ret = ip_conntrack_init();
+ if (ret < 0)
+ goto cleanup_nothing;
+
+ proc = proc_net_create("ip_conntrack", 0440, list_conntracks);
+ if (!proc) goto cleanup_init;
+ proc->owner = THIS_MODULE;
+
+ proc = proc_net_create("ip_conntrack_stat",0,list_conntracks_stats);
+ if (!proc) goto cleanup_init;
+ proc->owner = THIS_MODULE;
+
+ ret = nf_register_hook(&ip_conntrack_in_ops);
+ if (ret < 0) {
+ printk("ip_conntrack: can't register pre-routing hook.\n");
+ goto cleanup_proc;
+ }
+ ret = nf_register_hook(&ip_conntrack_local_out_ops);
+ if (ret < 0) {
+ printk("ip_conntrack: can't register local out hook.\n");
+ goto cleanup_inops;
+ }
+ ret = nf_register_hook(&ip_conntrack_out_ops);
+ if (ret < 0) {
+ printk("ip_conntrack: can't register post-routing hook.\n");
+ goto cleanup_inandlocalops;
+ }
+ ret = nf_register_hook(&ip_conntrack_local_in_ops);
+ if (ret < 0) {
+ printk("ip_conntrack: can't register local in hook.\n");
+ goto cleanup_inoutandlocalops;
+ }
+#ifdef CONFIG_SYSCTL
+ ip_ct_sysctl_header = register_sysctl_table(ip_ct_net_table, 0);
+ if (ip_ct_sysctl_header == NULL) {
+ printk("ip_conntrack: can't register to sysctl.\n");
+ goto cleanup;
+ }
+#endif
+
+ return ret;
+
+ cleanup:
+#ifdef CONFIG_SYSCTL
+ unregister_sysctl_table(ip_ct_sysctl_header);
+#endif
+ nf_unregister_hook(&ip_conntrack_local_in_ops);
+ cleanup_inoutandlocalops:
+ nf_unregister_hook(&ip_conntrack_out_ops);
+ cleanup_inandlocalops:
+ nf_unregister_hook(&ip_conntrack_local_out_ops);
+ cleanup_inops:
+ nf_unregister_hook(&ip_conntrack_in_ops);
+ cleanup_proc:
+ proc_net_remove("ip_conntrack");
+ proc_net_remove("ip_conntrack_stat");
+ cleanup_init:
+ ip_conntrack_cleanup();
+ cleanup_nothing:
+ return ret;
+}
+
+/* FIXME: Allow NULL functions and sub in pointers to generic for
+ them. --RR */
+int ip_conntrack_protocol_register(struct ip_conntrack_protocol *proto)
+{
+ int ret = 0;
+ struct list_head *i;
+
+ WRITE_LOCK(&ip_conntrack_lock);
+ for (i = protocol_list.next; i != &protocol_list; i = i->next) {
+ if (((struct ip_conntrack_protocol *)i)->proto
+ == proto->proto) {
+ ret = -EBUSY;
+ goto out;
+ }
+ }
+
+ list_prepend(&protocol_list, proto);
+ MOD_INC_USE_COUNT;
+
+ out:
+ WRITE_UNLOCK(&ip_conntrack_lock);
+ return ret;
+}
+
+void ip_conntrack_protocol_unregister(struct ip_conntrack_protocol *proto)
+{
+ WRITE_LOCK(&ip_conntrack_lock);
+
+ /* ip_ct_find_proto() returns proto_generic in case there is no protocol
+ * helper. So this should be enough - HW */
+ LIST_DELETE(&protocol_list, proto);
+ WRITE_UNLOCK(&ip_conntrack_lock);
+
+ /* Somebody could be still looking at the proto in bh. */
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+
+ /* Remove all contrack entries for this protocol */
+ ip_ct_iterate_cleanup(kill_proto, &proto->proto);
+
+ MOD_DEC_USE_COUNT;
+}
+
+static int __init init(void)
+{
+ return init_or_cleanup(1);
+}
+
+static void __exit fini(void)
+{
+ init_or_cleanup(0);
+}
+
+module_init(init);
+module_exit(fini);
+
+EXPORT_SYMBOL(ip_conntrack_protocol_register);
+EXPORT_SYMBOL(ip_conntrack_protocol_unregister);
+EXPORT_SYMBOL(invert_tuplepr);
+EXPORT_SYMBOL(ip_ct_get_tuple);
+EXPORT_SYMBOL(ip_conntrack_alter_reply);
+EXPORT_SYMBOL(ip_conntrack_destroyed);
+EXPORT_SYMBOL(ip_conntrack_get);
+EXPORT_SYMBOL(ip_conntrack_helper_register);
+EXPORT_SYMBOL(ip_conntrack_helper_unregister);
+EXPORT_SYMBOL(ip_ct_refresh_acct);
+EXPORT_SYMBOL(ip_ct_iterate_cleanup);
+EXPORT_SYMBOL(ip_ct_find_proto);
+EXPORT_SYMBOL(__ip_ct_find_proto);
+EXPORT_SYMBOL(ip_ct_find_helper);
+EXPORT_SYMBOL(ip_conntrack_expect_related);
+EXPORT_SYMBOL(ip_conntrack_change_expect);
+EXPORT_SYMBOL(ip_conntrack_unexpect_related);
+EXPORT_SYMBOL_GPL(ip_conntrack_expect_find_get);
+EXPORT_SYMBOL_GPL(ip_conntrack_expect_put);
+EXPORT_SYMBOL(ip_conntrack_tuple_taken);
+EXPORT_SYMBOL(ip_ct_gather_frags);
+EXPORT_SYMBOL(ip_conntrack_htable_size);
+EXPORT_SYMBOL(ip_conntrack_expect_list);
+EXPORT_SYMBOL(ip_conntrack_lock);
+EXPORT_SYMBOL(ip_conntrack_hash);
+EXPORT_SYMBOL_GPL(ip_conntrack_find_get);
+EXPORT_SYMBOL_GPL(ip_conntrack_put);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_tftp.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_tftp.c
new file mode 100644
index 0000000..0afdc11
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_conntrack_tftp.c
@@ -0,0 +1,136 @@
+/*
+ * Licensed under GNU GPL version 2 Copyright Magnus Boden <mb@ozaba.mine.nu>
+ * Version: 0.0.7
+ *
+ * Thu 21 Mar 2002 Harald Welte <laforge@gnumonks.org>
+ * - port to newnat API
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_tftp.h>
+
+MODULE_AUTHOR("Magnus Boden <mb@ozaba.mine.nu>");
+MODULE_DESCRIPTION("Netfilter connection tracking module for tftp");
+MODULE_LICENSE("GPL");
+
+#define MAX_PORTS 8
+static int ports[MAX_PORTS];
+static int ports_c = 0;
+#ifdef MODULE_PARM
+MODULE_PARM(ports, "1-" __MODULE_STRING(MAX_PORTS) "i");
+MODULE_PARM_DESC(ports, "port numbers of tftp servers");
+#endif
+
+#if 0
+#define DEBUGP(format, args...) printk(__FILE__ ":" __FUNCTION__ ": " \
+ format, ## args)
+#else
+#define DEBUGP(format, args...)
+#endif
+
+static int tftp_help(const struct iphdr *iph, size_t len,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo)
+{
+ struct udphdr *udph = (void *)iph + iph->ihl * 4;
+ struct tftphdr *tftph = (void *)udph + 8;
+ struct ip_conntrack_expect exp;
+
+ switch (ntohs(tftph->opcode)) {
+ /* RRQ and WRQ works the same way */
+ case TFTP_OPCODE_READ:
+ case TFTP_OPCODE_WRITE:
+ DEBUGP("");
+ DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
+ DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
+ memset(&exp, 0, sizeof(exp));
+
+ exp.tuple = ct->tuplehash[IP_CT_DIR_REPLY].tuple;
+ exp.mask.src.ip = 0xffffffff;
+ exp.mask.dst.ip = 0xffffffff;
+ exp.mask.dst.u.udp.port = 0xffff;
+ exp.mask.dst.protonum = 0xffff;
+ exp.expectfn = NULL;
+
+ DEBUGP("expect: ");
+ DUMP_TUPLE(&exp.tuple);
+ DUMP_TUPLE(&exp.mask);
+ ip_conntrack_expect_related(ct, &exp);
+ break;
+ case TFTP_OPCODE_DATA:
+ case TFTP_OPCODE_ACK:
+ DEBUGP("Data/ACK opcode\n");
+ break;
+ case TFTP_OPCODE_ERROR:
+ DEBUGP("Error opcode\n");
+ break;
+ default:
+ DEBUGP("Unknown opcode\n");
+ }
+ return NF_ACCEPT;
+}
+
+static struct ip_conntrack_helper tftp[MAX_PORTS];
+static char tftp_names[MAX_PORTS][10];
+
+static void fini(void)
+{
+ int i;
+
+ for (i = 0 ; i < ports_c; i++) {
+ DEBUGP("unregistering helper for port %d\n",
+ ports[i]);
+ ip_conntrack_helper_unregister(&tftp[i]);
+ }
+}
+
+static int __init init(void)
+{
+ int i, ret;
+ char *tmpname;
+
+ if (!ports[0])
+ ports[0]=TFTP_PORT;
+
+ for (i = 0 ; (i < MAX_PORTS) && ports[i] ; i++) {
+ /* Create helper structure */
+ tftp[i].tuple.dst.protonum = IPPROTO_UDP;
+ tftp[i].tuple.src.u.udp.port = htons(ports[i]);
+ tftp[i].mask.dst.protonum = 0xFFFF;
+ tftp[i].mask.src.u.udp.port = 0xFFFF;
+ tftp[i].max_expected = 1;
+ tftp[i].timeout = 0;
+ tftp[i].flags = IP_CT_HELPER_F_REUSE_EXPECT;
+ tftp[i].me = THIS_MODULE;
+ tftp[i].help = tftp_help;
+
+ tmpname = &tftp_names[i][0];
+ if (ports[i] == TFTP_PORT)
+ sprintf(tmpname, "tftp");
+ else
+ sprintf(tmpname, "tftp-%d", i);
+ tftp[i].name = tmpname;
+
+ DEBUGP("port #%d: %d\n", i, ports[i]);
+
+ ret=ip_conntrack_helper_register(&tftp[i]);
+ if (ret) {
+ printk("ERROR registering helper for port %d\n",
+ ports[i]);
+ fini();
+ return(ret);
+ }
+ ports_c++;
+ }
+ return(0);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_fw_compat.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_fw_compat.c
new file mode 100644
index 0000000..238b07e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_fw_compat.c
@@ -0,0 +1,308 @@
+/* Compatibility framework for ipchains and ipfwadm support; designed
+ to look as much like the 2.2 infrastructure as possible. */
+struct notifier_block;
+
+#include <linux/netfilter_ipv4.h>
+#include <linux/ip.h>
+#include <net/icmp.h>
+#include <linux/if.h>
+#include <linux/inetdevice.h>
+#include <linux/netdevice.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/netfilter_ipv4/compat_firewall.h>
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+#include <linux/netfilter_ipv4/ip_conntrack_core.h>
+
+/* Theoretically, we could one day use 2.4 helpers, but for now it
+ just confuses depmod --RR */
+EXPORT_NO_SYMBOLS;
+
+static struct firewall_ops *fwops;
+
+/* From ip_fw_compat_redir.c */
+extern unsigned int
+do_redirect(struct sk_buff *skb,
+ const struct net_device *dev,
+ u_int16_t redirpt);
+
+extern void
+check_for_redirect(struct sk_buff *skb);
+
+extern void
+check_for_unredirect(struct sk_buff *skb);
+
+/* From ip_fw_compat_masq.c */
+extern unsigned int
+do_masquerade(struct sk_buff **pskb, const struct net_device *dev);
+
+extern unsigned int
+check_for_masq_error(struct sk_buff *pskb);
+
+extern unsigned int
+check_for_demasq(struct sk_buff **pskb);
+
+extern int __init masq_init(void);
+extern void masq_cleanup(void);
+
+#ifdef CONFIG_IP_VS
+/* From ip_vs_core.c */
+extern unsigned int
+check_for_ip_vs_out(struct sk_buff **skb_p, int (*okfn)(struct sk_buff *));
+#endif
+
+/* They call these; we do what they want. */
+int register_firewall(int pf, struct firewall_ops *fw)
+{
+ if (pf != PF_INET) {
+ printk("Attempt to register non-IP firewall module.\n");
+ return -EINVAL;
+ }
+ if (fwops) {
+ printk("Attempt to register multiple firewall modules.\n");
+ return -EBUSY;
+ }
+
+ fwops = fw;
+ return 0;
+}
+
+int unregister_firewall(int pf, struct firewall_ops *fw)
+{
+ fwops = NULL;
+ return 0;
+}
+
+static unsigned int
+fw_in(unsigned int hooknum,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ int ret = FW_BLOCK;
+ u_int16_t redirpt;
+
+ /* Assume worse case: any hook could change packet */
+ (*pskb)->nfcache |= NFC_UNKNOWN | NFC_ALTERED;
+ if ((*pskb)->ip_summed == CHECKSUM_HW)
+ (*pskb)->ip_summed = CHECKSUM_NONE;
+
+ /* Firewall rules can alter TOS: raw socket (tcpdump) may have
+ clone of incoming skb: don't disturb it --RR */
+ if (skb_cloned(*pskb) && !(*pskb)->sk) {
+ struct sk_buff *nskb = skb_copy(*pskb, GFP_ATOMIC);
+ if (!nskb)
+ return NF_DROP;
+ kfree_skb(*pskb);
+ *pskb = nskb;
+ }
+
+ switch (hooknum) {
+ case NF_IP_PRE_ROUTING:
+ if (fwops->fw_acct_in)
+ fwops->fw_acct_in(fwops, PF_INET,
+ (struct net_device *)in,
+ (*pskb)->nh.raw, &redirpt, pskb);
+
+ if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
+ *pskb = ip_ct_gather_frags(*pskb, IP_DEFRAG_CONNTRACK_IN);
+
+ if (!*pskb)
+ return NF_STOLEN;
+ }
+
+ ret = fwops->fw_input(fwops, PF_INET, (struct net_device *)in,
+ (*pskb)->nh.raw, &redirpt, pskb);
+ break;
+
+ case NF_IP_FORWARD:
+ /* Connection will only be set if it was
+ demasqueraded: if so, skip forward chain. */
+ if ((*pskb)->nfct)
+ ret = FW_ACCEPT;
+ else ret = fwops->fw_forward(fwops, PF_INET,
+ (struct net_device *)out,
+ (*pskb)->nh.raw, &redirpt, pskb);
+ break;
+
+ case NF_IP_POST_ROUTING:
+ ret = fwops->fw_output(fwops, PF_INET,
+ (struct net_device *)out,
+ (*pskb)->nh.raw, &redirpt, pskb);
+ if (ret == FW_ACCEPT || ret == FW_SKIP) {
+ if (fwops->fw_acct_out)
+ fwops->fw_acct_out(fwops, PF_INET,
+ (struct net_device *)out,
+ (*pskb)->nh.raw, &redirpt,
+ pskb);
+
+ /* ip_conntrack_confirm return NF_DROP or NF_ACCEPT */
+ if (ip_conntrack_confirm(*pskb) == NF_DROP)
+ ret = FW_BLOCK;
+ }
+ break;
+ }
+
+ switch (ret) {
+ case FW_REJECT: {
+ /* Alexey says:
+ *
+ * Generally, routing is THE FIRST thing to make, when
+ * packet enters IP stack. Before packet is routed you
+ * cannot call any service routines from IP stack. */
+ struct iphdr *iph = (*pskb)->nh.iph;
+
+ if ((*pskb)->dst != NULL
+ || ip_route_input(*pskb, iph->daddr, iph->saddr, iph->tos,
+ (struct net_device *)in) == 0)
+ icmp_send(*pskb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH,
+ 0);
+ return NF_DROP;
+ }
+
+ case FW_ACCEPT:
+ case FW_SKIP:
+ if (hooknum == NF_IP_PRE_ROUTING) {
+ check_for_demasq(pskb);
+ check_for_redirect(*pskb);
+ } else if (hooknum == NF_IP_POST_ROUTING) {
+ check_for_unredirect(*pskb);
+ /* Handle ICMP errors from client here */
+ if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP
+ && (*pskb)->nfct)
+ check_for_masq_error(*pskb);
+ }
+ return NF_ACCEPT;
+
+ case FW_MASQUERADE:
+ if (hooknum == NF_IP_FORWARD) {
+#ifdef CONFIG_IP_VS
+ /* check if it is for ip_vs */
+ if (check_for_ip_vs_out(pskb, okfn) == NF_STOLEN)
+ return NF_STOLEN;
+#endif
+ return do_masquerade(pskb, out);
+ }
+ else return NF_ACCEPT;
+
+ case FW_REDIRECT:
+ if (hooknum == NF_IP_PRE_ROUTING)
+ return do_redirect(*pskb, in, redirpt);
+ else return NF_ACCEPT;
+
+ default:
+ /* FW_BLOCK */
+ return NF_DROP;
+ }
+}
+
+static unsigned int fw_confirm(unsigned int hooknum,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ return ip_conntrack_confirm(*pskb);
+}
+
+extern int ip_fw_ctl(int optval, void *m, unsigned int len);
+
+static int sock_fn(struct sock *sk, int optval, void *user, unsigned int len)
+{
+ /* MAX of:
+ 2.2: sizeof(struct ip_fwtest) (~14x4 + 3x4 = 17x4)
+ 2.2: sizeof(struct ip_fwnew) (~1x4 + 15x4 + 3x4 + 3x4 = 22x4)
+ 2.0: sizeof(struct ip_fw) (~25x4)
+
+ We can't include both 2.0 and 2.2 headers, they conflict.
+ Hence, 200 is a good number. --RR */
+ char tmp_fw[200];
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (len > sizeof(tmp_fw) || len < 1)
+ return -EINVAL;
+
+ if (copy_from_user(&tmp_fw, user, len))
+ return -EFAULT;
+
+ return -ip_fw_ctl(optval, &tmp_fw, len);
+}
+
+static struct nf_hook_ops preroute_ops
+= { { NULL, NULL }, fw_in, PF_INET, NF_IP_PRE_ROUTING, NF_IP_PRI_FILTER };
+
+static struct nf_hook_ops postroute_ops
+= { { NULL, NULL }, fw_in, PF_INET, NF_IP_POST_ROUTING, NF_IP_PRI_FILTER };
+
+static struct nf_hook_ops forward_ops
+= { { NULL, NULL }, fw_in, PF_INET, NF_IP_FORWARD, NF_IP_PRI_FILTER };
+
+static struct nf_hook_ops local_in_ops
+= { { NULL, NULL }, fw_confirm, PF_INET, NF_IP_LOCAL_IN, NF_IP_PRI_LAST - 1 };
+
+static struct nf_sockopt_ops sock_ops
+= { { NULL, NULL }, PF_INET, 64, 64 + 1024 + 1, &sock_fn, 0, 0, NULL,
+ 0, NULL };
+
+extern int ipfw_init_or_cleanup(int init);
+
+static int init_or_cleanup(int init)
+{
+ int ret = 0;
+
+ if (!init) goto cleanup;
+
+ ret = nf_register_sockopt(&sock_ops);
+
+ if (ret < 0)
+ goto cleanup_nothing;
+
+ ret = ipfw_init_or_cleanup(1);
+ if (ret < 0)
+ goto cleanup_sockopt;
+
+ ret = masq_init();
+ if (ret < 0)
+ goto cleanup_ipfw;
+
+ nf_register_hook(&preroute_ops);
+ nf_register_hook(&postroute_ops);
+ nf_register_hook(&forward_ops);
+ nf_register_hook(&local_in_ops);
+
+ return ret;
+
+ cleanup:
+ nf_unregister_hook(&preroute_ops);
+ nf_unregister_hook(&postroute_ops);
+ nf_unregister_hook(&forward_ops);
+ nf_unregister_hook(&local_in_ops);
+
+ masq_cleanup();
+
+ cleanup_ipfw:
+ ipfw_init_or_cleanup(0);
+
+ cleanup_sockopt:
+ nf_unregister_sockopt(&sock_ops);
+
+ cleanup_nothing:
+ return ret;
+}
+
+static int __init init(void)
+{
+ return init_or_cleanup(1);
+}
+
+static void __exit fini(void)
+{
+ init_or_cleanup(0);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_fw_compat_masq.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_fw_compat_masq.c
new file mode 100644
index 0000000..4061860
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_fw_compat_masq.c
@@ -0,0 +1,339 @@
+/* Masquerading compatibility layer.
+
+ Note that there are no restrictions on other programs binding to
+ ports 61000:65095 (in 2.0 and 2.2 they get EADDRINUSE). Just DONT
+ DO IT.
+ */
+#include <linux/skbuff.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/icmp.h>
+#include <linux/udp.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/netdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <net/ip.h>
+#include <net/route.h>
+
+#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_conntrack_lock)
+#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_conntrack_lock)
+
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+#include <linux/netfilter_ipv4/ip_conntrack_core.h>
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_core.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+unsigned int
+do_masquerade(struct sk_buff **pskb, const struct net_device *dev)
+{
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct ip_nat_info *info;
+ enum ip_conntrack_info ctinfo;
+ struct ip_conntrack *ct;
+ unsigned int ret;
+
+ /* Sorry, only ICMP, TCP and UDP. */
+ if (iph->protocol != IPPROTO_ICMP
+ && iph->protocol != IPPROTO_TCP
+ && iph->protocol != IPPROTO_UDP)
+ return NF_DROP;
+
+ /* Feed it to connection tracking; in fact we're in NF_IP_FORWARD,
+ but connection tracking doesn't expect that */
+ ret = ip_conntrack_in(NF_IP_POST_ROUTING, pskb, dev, NULL, NULL);
+ if (ret != NF_ACCEPT) {
+ DEBUGP("ip_conntrack_in returned %u.\n", ret);
+ return ret;
+ }
+
+ ct = ip_conntrack_get(*pskb, &ctinfo);
+
+ if (!ct) {
+ DEBUGP("ip_conntrack_in set to invalid conntrack.\n");
+ return NF_DROP;
+ }
+
+ info = &ct->nat.info;
+
+ WRITE_LOCK(&ip_nat_lock);
+ /* Setup the masquerade, if not already */
+ if (!info->initialized) {
+ u_int32_t newsrc;
+ struct rtable *rt;
+ struct ip_nat_multi_range range;
+
+ /* Pass 0 instead of saddr, since it's going to be changed
+ anyway. */
+ if (ip_route_output(&rt, iph->daddr, 0, 0, 0) != 0) {
+ DEBUGP("ipnat_rule_masquerade: Can't reroute.\n");
+ return NF_DROP;
+ }
+ newsrc = inet_select_addr(rt->u.dst.dev, rt->rt_gateway,
+ RT_SCOPE_UNIVERSE);
+ ip_rt_put(rt);
+ range = ((struct ip_nat_multi_range)
+ { 1,
+ {{IP_NAT_RANGE_MAP_IPS|IP_NAT_RANGE_PROTO_SPECIFIED,
+ newsrc, newsrc,
+ { htons(61000) }, { htons(65095) } } } });
+
+ ret = ip_nat_setup_info(ct, &range, NF_IP_POST_ROUTING);
+ if (ret != NF_ACCEPT) {
+ WRITE_UNLOCK(&ip_nat_lock);
+ return ret;
+ }
+ } else
+ DEBUGP("Masquerading already done on this conn.\n");
+ WRITE_UNLOCK(&ip_nat_lock);
+
+ return do_bindings(ct, ctinfo, info, NF_IP_POST_ROUTING, pskb);
+}
+
+void
+check_for_masq_error(struct sk_buff *skb)
+{
+ enum ip_conntrack_info ctinfo;
+ struct ip_conntrack *ct;
+
+ ct = ip_conntrack_get(skb, &ctinfo);
+ /* Wouldn't be here if not tracked already => masq'ed ICMP
+ ping or error related to masq'd connection */
+ IP_NF_ASSERT(ct);
+ if (ctinfo == IP_CT_RELATED) {
+ icmp_reply_translation(skb, ct, NF_IP_PRE_ROUTING,
+ CTINFO2DIR(ctinfo));
+ icmp_reply_translation(skb, ct, NF_IP_POST_ROUTING,
+ CTINFO2DIR(ctinfo));
+ }
+}
+
+unsigned int
+check_for_demasq(struct sk_buff **pskb)
+{
+ struct ip_conntrack_tuple tuple;
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct ip_conntrack_protocol *protocol;
+ struct ip_conntrack_tuple_hash *h;
+ enum ip_conntrack_info ctinfo;
+ struct ip_conntrack *ct;
+ int ret;
+
+ protocol = ip_ct_find_proto(iph->protocol);
+
+ /* We don't feed packets to conntrack system unless we know
+ they're part of an connection already established by an
+ explicit masq command. */
+ switch (iph->protocol) {
+ case IPPROTO_ICMP:
+ /* ICMP errors. */
+ ct = icmp_error_track(*pskb, &ctinfo, NF_IP_PRE_ROUTING);
+ if (ct) {
+ /* We only do SNAT in the compatibility layer.
+ So we can manipulate ICMP errors from
+ server here (== DNAT). Do SNAT icmp manips
+ in POST_ROUTING handling. */
+ if (CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY) {
+ icmp_reply_translation(*pskb, ct,
+ NF_IP_PRE_ROUTING,
+ CTINFO2DIR(ctinfo));
+ icmp_reply_translation(*pskb, ct,
+ NF_IP_POST_ROUTING,
+ CTINFO2DIR(ctinfo));
+ }
+ return NF_ACCEPT;
+ }
+ /* Fall thru... */
+ case IPPROTO_TCP:
+ case IPPROTO_UDP:
+ IP_NF_ASSERT(((*pskb)->nh.iph->frag_off & htons(IP_OFFSET)) == 0);
+
+ if (!ip_ct_get_tuple(iph, (*pskb)->len, &tuple, protocol)) {
+ if (net_ratelimit())
+ printk("ip_fw_compat_masq: Can't get tuple\n");
+ return NF_ACCEPT;
+ }
+ break;
+
+ default:
+ /* Not ours... */
+ return NF_ACCEPT;
+ }
+ h = ip_conntrack_find_get(&tuple, NULL);
+
+ /* MUST be found, and MUST be reply. */
+ if (h && DIRECTION(h) == 1) {
+ ret = ip_conntrack_in(NF_IP_PRE_ROUTING, pskb,
+ NULL, NULL, NULL);
+
+ /* Put back the reference gained from find_get */
+ nf_conntrack_put(&h->ctrack->infos[0]);
+ if (ret == NF_ACCEPT) {
+ struct ip_conntrack *ct;
+ ct = ip_conntrack_get(*pskb, &ctinfo);
+
+ if (ct) {
+ struct ip_nat_info *info = &ct->nat.info;
+
+ do_bindings(ct, ctinfo, info,
+ NF_IP_PRE_ROUTING,
+ pskb);
+ } else
+ if (net_ratelimit())
+ printk("ip_fw_compat_masq: conntrack"
+ " didn't like\n");
+ }
+ } else {
+ if (h)
+ /* Put back the reference gained from find_get */
+ nf_conntrack_put(&h->ctrack->infos[0]);
+ ret = NF_ACCEPT;
+ }
+
+ return ret;
+}
+
+int ip_fw_masq_timeouts(void *user, int len)
+{
+ printk("Sorry: masquerading timeouts set 5DAYS/2MINS/60SECS\n");
+ return 0;
+}
+
+static const char *masq_proto_name(u_int16_t protonum)
+{
+ switch (protonum) {
+ case IPPROTO_TCP: return "TCP";
+ case IPPROTO_UDP: return "UDP";
+ case IPPROTO_ICMP: return "ICMP";
+ default: return "MORE-CAFFIENE-FOR-RUSTY";
+ }
+}
+
+static unsigned int
+print_masq(char *buffer, const struct ip_conntrack *conntrack)
+{
+ char temp[129];
+
+ /* This is for backwards compatibility, but ick!.
+ We should never export jiffies to userspace.
+ */
+ sprintf(temp,"%s %08X:%04X %08X:%04X %04X %08X %6d %6d %7lu",
+ masq_proto_name(conntrack->tuplehash[0].tuple.dst.protonum),
+ ntohl(conntrack->tuplehash[0].tuple.src.ip),
+ ntohs(conntrack->tuplehash[0].tuple.src.u.all),
+ ntohl(conntrack->tuplehash[0].tuple.dst.ip),
+ ntohs(conntrack->tuplehash[0].tuple.dst.u.all),
+ ntohs(conntrack->tuplehash[1].tuple.dst.u.all),
+ /* Sorry, no init_seq, delta or previous_delta (yet). */
+ 0, 0, 0,
+ conntrack->timeout.expires - jiffies);
+
+ return sprintf(buffer, "%-127s\n", temp);
+}
+
+/* Returns true when finished. */
+static int
+masq_iterate(const struct ip_conntrack_tuple_hash *hash,
+ char *buffer, off_t offset, off_t *upto,
+ unsigned int *len, unsigned int maxlen)
+{
+ unsigned int newlen;
+
+ IP_NF_ASSERT(hash->ctrack);
+
+ /* Only count originals */
+ if (DIRECTION(hash))
+ return 0;
+
+ if (*upto < offset) {
+ (*upto)++;
+ return 0;
+ }
+
+ newlen = print_masq(buffer + *len, hash->ctrack);
+ if (*len + newlen > maxlen)
+ return 1;
+
+ *len += newlen;
+ (*upto)++;
+ return 0;
+}
+
+/* Everything in the hash is masqueraded. */
+static int
+masq_procinfo(char *buffer, char **start, off_t offset, int length)
+{
+ unsigned int i;
+ int len = 0;
+ off_t upto = 0;
+
+ /* Header: first record */
+ if (offset == 0) {
+ char temp[128];
+
+ sprintf(temp,
+ "Prc FromIP FPrt ToIP TPrt Masq Init-seq Delta PDelta Expires (free=0,0,0)");
+ len = sprintf(buffer, "%-127s\n", temp);
+ if (len > length) {
+ len = 0;
+ goto finished;
+ }
+ }
+ upto++;
+
+ READ_LOCK(&ip_conntrack_lock);
+ /* Traverse hash; print originals then reply. */
+ for (i = 0; i < ip_conntrack_htable_size; i++) {
+ if (LIST_FIND(&ip_conntrack_hash[i], masq_iterate,
+ struct ip_conntrack_tuple_hash *,
+ buffer, offset, &upto, &len, length))
+ break;
+ }
+ READ_UNLOCK(&ip_conntrack_lock);
+
+ finished:
+ /* `start' hack - see fs/proc/generic.c line ~165 */
+ *start = (char *)((unsigned int)upto - offset);
+ return len;
+}
+
+int __init masq_init(void)
+{
+ int ret;
+ struct proc_dir_entry *proc;
+
+ ret = ip_conntrack_init();
+ if (ret == 0) {
+ ret = ip_nat_init();
+ if (ret == 0) {
+ proc = proc_net_create("ip_masquerade",
+ 0, masq_procinfo);
+ if (proc)
+ proc->owner = THIS_MODULE;
+ else {
+ ip_nat_cleanup();
+ ip_conntrack_cleanup();
+ ret = -ENOMEM;
+ }
+ } else
+ ip_conntrack_cleanup();
+ }
+
+ return ret;
+}
+
+void masq_cleanup(void)
+{
+ ip_nat_cleanup();
+ ip_conntrack_cleanup();
+ proc_net_remove("ip_masquerade");
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_fw_compat_redir.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_fw_compat_redir.c
new file mode 100644
index 0000000..0540d87
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_fw_compat_redir.c
@@ -0,0 +1,307 @@
+/* This is a file to handle the "simple" NAT cases (redirect and
+ masquerade) required for the compatibility layer.
+
+ `bind to foreign address' and `getpeername' hacks are not
+ supported.
+
+ FIXME: Timing is overly simplistic. If anyone complains, make it
+ use conntrack.
+*/
+#include <linux/config.h>
+#include <linux/netfilter.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+#include <linux/tcp.h>
+#include <net/checksum.h>
+#include <linux/timer.h>
+#include <linux/netdevice.h>
+#include <linux/if.h>
+#include <linux/in.h>
+
+#include <linux/netfilter_ipv4/lockhelp.h>
+
+/* Very simple timeout pushed back by each packet */
+#define REDIR_TIMEOUT (240*HZ)
+
+static DECLARE_LOCK(redir_lock);
+#define ASSERT_READ_LOCK(x) MUST_BE_LOCKED(&redir_lock)
+#define ASSERT_WRITE_LOCK(x) MUST_BE_LOCKED(&redir_lock)
+
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+#ifdef CONFIG_NETFILTER_DEBUG
+#define IP_NF_ASSERT(x) \
+do { \
+ if (!(x)) \
+ /* Wooah! I'm tripping my conntrack in a frenzy of \
+ netplay... */ \
+ printk("ASSERT: %s:%i(%s)\n", \
+ __FILE__, __LINE__, __FUNCTION__); \
+} while(0)
+#else
+#define IP_NF_ASSERT(x)
+#endif
+
+static u_int16_t
+cheat_check(u_int32_t oldvalinv, u_int32_t newval, u_int16_t oldcheck)
+{
+ u_int32_t diffs[] = { oldvalinv, newval };
+ return csum_fold(csum_partial((char *)diffs, sizeof(diffs),
+ oldcheck^0xFFFF));
+}
+
+struct redir_core {
+ u_int32_t orig_srcip, orig_dstip;
+ u_int16_t orig_sport, orig_dport;
+
+ u_int32_t new_dstip;
+ u_int16_t new_dport;
+};
+
+struct redir
+{
+ struct list_head list;
+ struct redir_core core;
+ struct timer_list destroyme;
+};
+
+static LIST_HEAD(redirs);
+
+static int
+redir_cmp(const struct redir *i,
+ u_int32_t orig_srcip, u_int32_t orig_dstip,
+ u_int16_t orig_sport, u_int16_t orig_dport)
+{
+ return (i->core.orig_srcip == orig_srcip
+ && i->core.orig_dstip == orig_dstip
+ && i->core.orig_sport == orig_sport
+ && i->core.orig_dport == orig_dport);
+}
+
+/* Search for an existing redirection of the TCP packet. */
+static struct redir *
+find_redir(u_int32_t orig_srcip, u_int32_t orig_dstip,
+ u_int16_t orig_sport, u_int16_t orig_dport)
+{
+ return LIST_FIND(&redirs, redir_cmp, struct redir *,
+ orig_srcip, orig_dstip, orig_sport, orig_dport);
+}
+
+static void do_tcp_redir(struct sk_buff *skb, struct redir *redir)
+{
+ struct iphdr *iph = skb->nh.iph;
+ struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
+ + iph->ihl);
+
+ tcph->check = cheat_check(~redir->core.orig_dstip,
+ redir->core.new_dstip,
+ cheat_check(redir->core.orig_dport ^ 0xFFFF,
+ redir->core.new_dport,
+ tcph->check));
+ iph->check = cheat_check(~redir->core.orig_dstip,
+ redir->core.new_dstip, iph->check);
+ tcph->dest = redir->core.new_dport;
+ iph->daddr = redir->core.new_dstip;
+
+ skb->nfcache |= NFC_ALTERED;
+}
+
+static int
+unredir_cmp(const struct redir *i,
+ u_int32_t new_dstip, u_int32_t orig_srcip,
+ u_int16_t new_dport, u_int16_t orig_sport)
+{
+ return (i->core.orig_srcip == orig_srcip
+ && i->core.new_dstip == new_dstip
+ && i->core.orig_sport == orig_sport
+ && i->core.new_dport == new_dport);
+}
+
+/* Match reply packet against redir */
+static struct redir *
+find_unredir(u_int32_t new_dstip, u_int32_t orig_srcip,
+ u_int16_t new_dport, u_int16_t orig_sport)
+{
+ return LIST_FIND(&redirs, unredir_cmp, struct redir *,
+ new_dstip, orig_srcip, new_dport, orig_sport);
+}
+
+/* `unredir' a reply packet. */
+static void do_tcp_unredir(struct sk_buff *skb, struct redir *redir)
+{
+ struct iphdr *iph = skb->nh.iph;
+ struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
+ + iph->ihl);
+
+ tcph->check = cheat_check(~redir->core.new_dstip,
+ redir->core.orig_dstip,
+ cheat_check(redir->core.new_dport ^ 0xFFFF,
+ redir->core.orig_dport,
+ tcph->check));
+ iph->check = cheat_check(~redir->core.new_dstip,
+ redir->core.orig_dstip,
+ iph->check);
+ tcph->source = redir->core.orig_dport;
+ iph->saddr = redir->core.orig_dstip;
+
+ skb->nfcache |= NFC_ALTERED;
+}
+
+static void destroyme(unsigned long me)
+{
+ LOCK_BH(&redir_lock);
+ LIST_DELETE(&redirs, (struct redir *)me);
+ UNLOCK_BH(&redir_lock);
+ kfree((struct redir *)me);
+}
+
+/* REDIRECT a packet. */
+unsigned int
+do_redirect(struct sk_buff *skb,
+ const struct net_device *dev,
+ u_int16_t redirpt)
+{
+ struct iphdr *iph = skb->nh.iph;
+ u_int32_t newdst;
+
+ /* Figure out address: not loopback. */
+ if (!dev)
+ return NF_DROP;
+
+ /* Grab first address on interface. */
+ newdst = ((struct in_device *)dev->ip_ptr)->ifa_list->ifa_local;
+
+ switch (iph->protocol) {
+ case IPPROTO_UDP: {
+ /* Simple mangle. */
+ struct udphdr *udph = (struct udphdr *)((u_int32_t *)iph
+ + iph->ihl);
+
+ /* Must have whole header */
+ if (skb->len < iph->ihl*4 + sizeof(*udph))
+ return NF_DROP;
+
+ if (udph->check) /* 0 is a special case meaning no checksum */
+ udph->check = cheat_check(~iph->daddr, newdst,
+ cheat_check(udph->dest ^ 0xFFFF,
+ redirpt,
+ udph->check));
+ iph->check = cheat_check(~iph->daddr, newdst, iph->check);
+ udph->dest = redirpt;
+ iph->daddr = newdst;
+
+ skb->nfcache |= NFC_ALTERED;
+ return NF_ACCEPT;
+ }
+ case IPPROTO_TCP: {
+ /* Mangle, maybe record. */
+ struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
+ + iph->ihl);
+ struct redir *redir;
+ int ret;
+
+ /* Must have whole header */
+ if (skb->len < iph->ihl*4 + sizeof(*tcph))
+ return NF_DROP;
+
+ DEBUGP("Doing tcp redirect. %08X:%u %08X:%u -> %08X:%u\n",
+ iph->saddr, tcph->source, iph->daddr, tcph->dest,
+ newdst, redirpt);
+ LOCK_BH(&redir_lock);
+ redir = find_redir(iph->saddr, iph->daddr,
+ tcph->source, tcph->dest);
+
+ if (!redir) {
+ redir = kmalloc(sizeof(struct redir), GFP_ATOMIC);
+ if (!redir) {
+ ret = NF_DROP;
+ goto out;
+ }
+ list_prepend(&redirs, redir);
+ init_timer(&redir->destroyme);
+ redir->destroyme.function = destroyme;
+ redir->destroyme.data = (unsigned long)redir;
+ redir->destroyme.expires = jiffies + REDIR_TIMEOUT;
+ add_timer(&redir->destroyme);
+ }
+ /* In case mangling has changed, rewrite this part. */
+ redir->core = ((struct redir_core)
+ { iph->saddr, iph->daddr,
+ tcph->source, tcph->dest,
+ newdst, redirpt });
+ do_tcp_redir(skb, redir);
+ ret = NF_ACCEPT;
+
+ out:
+ UNLOCK_BH(&redir_lock);
+ return ret;
+ }
+
+ default: /* give up if not TCP or UDP. */
+ return NF_DROP;
+ }
+}
+
+/* Incoming packet: is it a reply to a masqueraded connection, or
+ part of an already-redirected TCP connection? */
+void
+check_for_redirect(struct sk_buff *skb)
+{
+ struct iphdr *iph = skb->nh.iph;
+ struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
+ + iph->ihl);
+ struct redir *redir;
+
+ if (iph->protocol != IPPROTO_TCP)
+ return;
+
+ /* Must have whole header */
+ if (skb->len < iph->ihl*4 + sizeof(*tcph))
+ return;
+
+ LOCK_BH(&redir_lock);
+ redir = find_redir(iph->saddr, iph->daddr, tcph->source, tcph->dest);
+ if (redir) {
+ DEBUGP("Doing tcp redirect again.\n");
+ do_tcp_redir(skb, redir);
+ if (del_timer(&redir->destroyme)) {
+ redir->destroyme.expires = jiffies + REDIR_TIMEOUT;
+ add_timer(&redir->destroyme);
+ }
+ }
+ UNLOCK_BH(&redir_lock);
+}
+
+void
+check_for_unredirect(struct sk_buff *skb)
+{
+ struct iphdr *iph = skb->nh.iph;
+ struct tcphdr *tcph = (struct tcphdr *)((u_int32_t *)iph
+ + iph->ihl);
+ struct redir *redir;
+
+ if (iph->protocol != IPPROTO_TCP)
+ return;
+
+ /* Must have whole header */
+ if (skb->len < iph->ihl*4 + sizeof(*tcph))
+ return;
+
+ LOCK_BH(&redir_lock);
+ redir = find_unredir(iph->saddr, iph->daddr, tcph->source, tcph->dest);
+ if (redir) {
+ DEBUGP("Doing tcp unredirect.\n");
+ do_tcp_unredir(skb, redir);
+ if (del_timer(&redir->destroyme)) {
+ redir->destroyme.expires = jiffies + REDIR_TIMEOUT;
+ add_timer(&redir->destroyme);
+ }
+ }
+ UNLOCK_BH(&redir_lock);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_amanda.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_amanda.c
new file mode 100644
index 0000000..789317e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_amanda.c
@@ -0,0 +1,148 @@
+/* Amanda extension for TCP NAT alteration.
+ * (C) 2002 by Brian J. Murrell <netfilter@interlinx.bc.ca>
+ * based on a copy of HW's ip_nat_irc.c as well as other modules
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Module load syntax:
+ * insmod ip_nat_amanda.o
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+
+#include <linux/netfilter_ipv4.h>
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_amanda.h>
+
+
+MODULE_AUTHOR("Brian J. Murrell <netfilter@interlinx.bc.ca>");
+MODULE_DESCRIPTION("Amanda NAT helper");
+MODULE_LICENSE("GPL");
+
+static unsigned int
+amanda_nat_expected(struct sk_buff **pskb,
+ unsigned int hooknum,
+ struct ip_conntrack *ct,
+ struct ip_nat_info *info)
+{
+ struct ip_conntrack *master = master_ct(ct);
+ struct ip_ct_amanda_expect *exp_amanda_info;
+ struct ip_nat_multi_range mr;
+ u_int32_t newip;
+
+ IP_NF_ASSERT(info);
+ IP_NF_ASSERT(master);
+ IP_NF_ASSERT(!(info->initialized & (1 << HOOK2MANIP(hooknum))));
+
+ if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC)
+ newip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+ else
+ newip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip;
+
+ mr.rangesize = 1;
+ /* We don't want to manip the per-protocol, just the IPs. */
+ mr.range[0].flags = IP_NAT_RANGE_MAP_IPS;
+ mr.range[0].min_ip = mr.range[0].max_ip = newip;
+
+ if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST) {
+ exp_amanda_info = &ct->master->help.exp_amanda_info;
+ mr.range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
+ mr.range[0].min = mr.range[0].max
+ = ((union ip_conntrack_manip_proto)
+ { .udp = { htons(exp_amanda_info->port) } });
+ }
+
+ return ip_nat_setup_info(ct, &mr, hooknum);
+}
+
+static int amanda_data_fixup(struct ip_conntrack *ct,
+ struct sk_buff **pskb,
+ enum ip_conntrack_info ctinfo,
+ struct ip_conntrack_expect *exp)
+{
+ struct ip_ct_amanda_expect *exp_amanda_info;
+ struct ip_conntrack_tuple t = exp->tuple;
+ char buffer[sizeof("65535")];
+ u_int16_t port;
+
+ /* Alter conntrack's expectations. */
+ exp_amanda_info = &exp->help.exp_amanda_info;
+ t.dst.ip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
+ for (port = exp_amanda_info->port; port != 0; port++) {
+ t.dst.u.tcp.port = htons(port);
+ if (ip_conntrack_change_expect(exp, &t) == 0)
+ break;
+ }
+ if (port == 0)
+ return 0;
+
+ sprintf(buffer, "%u", port);
+ return ip_nat_mangle_udp_packet(pskb, ct, ctinfo,
+ exp_amanda_info->offset,
+ exp_amanda_info->len,
+ buffer, strlen(buffer));
+}
+
+static unsigned int help(struct ip_conntrack *ct,
+ struct ip_conntrack_expect *exp,
+ struct ip_nat_info *info,
+ enum ip_conntrack_info ctinfo,
+ unsigned int hooknum,
+ struct sk_buff **pskb)
+{
+ int dir = CTINFO2DIR(ctinfo);
+ int ret = NF_ACCEPT;
+
+ /* Only mangle things once: original direction in POST_ROUTING
+ and reply direction on PRE_ROUTING. */
+ if (!((hooknum == NF_IP_POST_ROUTING && dir == IP_CT_DIR_ORIGINAL)
+ || (hooknum == NF_IP_PRE_ROUTING && dir == IP_CT_DIR_REPLY)))
+ return NF_ACCEPT;
+
+ /* if this exectation has a "offset" the packet needs to be mangled */
+ if (exp->help.exp_amanda_info.offset != 0)
+ if (!amanda_data_fixup(ct, pskb, ctinfo, exp))
+ ret = NF_DROP;
+ exp->help.exp_amanda_info.offset = 0;
+
+ return ret;
+}
+
+static struct ip_nat_helper ip_nat_amanda_helper;
+
+static void __exit fini(void)
+{
+ ip_nat_helper_unregister(&ip_nat_amanda_helper);
+}
+
+static int __init init(void)
+{
+ struct ip_nat_helper *hlpr = &ip_nat_amanda_helper;
+
+ hlpr->tuple.dst.protonum = IPPROTO_UDP;
+ hlpr->tuple.src.u.udp.port = htons(10080);
+ hlpr->mask.src.u.udp.port = 0xFFFF;
+ hlpr->mask.dst.protonum = 0xFFFF;
+ hlpr->help = help;
+ hlpr->flags = 0;
+ hlpr->me = THIS_MODULE;
+ hlpr->expect = amanda_nat_expected;
+ hlpr->name = "amanda";
+
+ return ip_nat_helper_register(hlpr);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_core.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_core.c
new file mode 100644
index 0000000..5c0d942
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_core.c
@@ -0,0 +1,1070 @@
+/* NAT for netfilter; shared with compatibility layer. */
+
+/* (c) 1999 Paul `Rusty' Russell. Licenced under the GNU General
+ Public Licence. */
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/brlock.h>
+#include <linux/vmalloc.h>
+#include <net/checksum.h>
+#include <net/icmp.h>
+#include <net/ip.h>
+#include <net/tcp.h> /* For tcp_prot in getorigdst */
+
+#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_nat_lock)
+#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_nat_lock)
+
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+#include <linux/netfilter_ipv4/ip_conntrack_core.h>
+#include <linux/netfilter_ipv4/ip_conntrack_protocol.h>
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_protocol.h>
+#include <linux/netfilter_ipv4/ip_nat_core.h>
+#include <linux/netfilter_ipv4/ip_nat_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+DECLARE_RWLOCK(ip_nat_lock);
+DECLARE_RWLOCK_EXTERN(ip_conntrack_lock);
+
+/* Calculated at init based on memory size */
+static unsigned int ip_nat_htable_size;
+
+static struct list_head *bysource;
+static struct list_head *byipsproto;
+LIST_HEAD(protos);
+LIST_HEAD(helpers);
+
+extern struct ip_nat_protocol unknown_nat_protocol;
+
+/* We keep extra hashes for each conntrack, for fast searching. */
+static inline size_t
+hash_by_ipsproto(u_int32_t src, u_int32_t dst, u_int16_t proto)
+{
+ /* Modified src and dst, to ensure we don't create two
+ identical streams. */
+ return (src + dst + proto) % ip_nat_htable_size;
+}
+
+static inline size_t
+hash_by_src(const struct ip_conntrack_manip *manip, u_int16_t proto)
+{
+ /* Original src, to ensure we map it consistently if poss. */
+ return (manip->ip + manip->u.all + proto) % ip_nat_htable_size;
+}
+
+/* Noone using conntrack by the time this called. */
+static void ip_nat_cleanup_conntrack(struct ip_conntrack *conn)
+{
+ struct ip_nat_info *info = &conn->nat.info;
+ unsigned int hs, hp;
+
+ if (!info->initialized)
+ return;
+
+ IP_NF_ASSERT(info->bysource.conntrack);
+ IP_NF_ASSERT(info->byipsproto.conntrack);
+
+ hs = hash_by_src(&conn->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src,
+ conn->tuplehash[IP_CT_DIR_ORIGINAL]
+ .tuple.dst.protonum);
+
+ hp = hash_by_ipsproto(conn->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip,
+ conn->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip,
+ conn->tuplehash[IP_CT_DIR_REPLY]
+ .tuple.dst.protonum);
+
+ WRITE_LOCK(&ip_nat_lock);
+ LIST_DELETE(&bysource[hs], &info->bysource);
+ LIST_DELETE(&byipsproto[hp], &info->byipsproto);
+ WRITE_UNLOCK(&ip_nat_lock);
+}
+
+/* We do checksum mangling, so if they were wrong before they're still
+ * wrong. Also works for incomplete packets (eg. ICMP dest
+ * unreachables.) */
+u_int16_t
+ip_nat_cheat_check(u_int32_t oldvalinv, u_int32_t newval, u_int16_t oldcheck)
+{
+ u_int32_t diffs[] = { oldvalinv, newval };
+ return csum_fold(csum_partial((char *)diffs, sizeof(diffs),
+ oldcheck^0xFFFF));
+}
+
+static inline int cmp_proto(const struct ip_nat_protocol *i, int proto)
+{
+ return i->protonum == proto;
+}
+
+struct ip_nat_protocol *
+find_nat_proto(u_int16_t protonum)
+{
+ struct ip_nat_protocol *i;
+
+ MUST_BE_READ_LOCKED(&ip_nat_lock);
+ i = LIST_FIND(&protos, cmp_proto, struct ip_nat_protocol *, protonum);
+ if (!i)
+ i = &unknown_nat_protocol;
+ return i;
+}
+
+/* Is this tuple already taken? (not by us) */
+int
+ip_nat_used_tuple(const struct ip_conntrack_tuple *tuple,
+ const struct ip_conntrack *ignored_conntrack)
+{
+ /* Conntrack tracking doesn't keep track of outgoing tuples; only
+ incoming ones. NAT means they don't have a fixed mapping,
+ so we invert the tuple and look for the incoming reply.
+
+ We could keep a separate hash if this proves too slow. */
+ struct ip_conntrack_tuple reply;
+
+ invert_tuplepr(&reply, tuple);
+ return ip_conntrack_tuple_taken(&reply, ignored_conntrack);
+}
+
+/* Does tuple + the source manip come within the range mr */
+static int
+in_range(const struct ip_conntrack_tuple *tuple,
+ const struct ip_conntrack_manip *manip,
+ const struct ip_nat_multi_range *mr)
+{
+ struct ip_nat_protocol *proto = find_nat_proto(tuple->dst.protonum);
+ unsigned int i;
+ struct ip_conntrack_tuple newtuple = { *manip, tuple->dst };
+
+ for (i = 0; i < mr->rangesize; i++) {
+ /* If we are allowed to map IPs, then we must be in the
+ range specified, otherwise we must be unchanged. */
+ if (mr->range[i].flags & IP_NAT_RANGE_MAP_IPS) {
+ if (ntohl(newtuple.src.ip) < ntohl(mr->range[i].min_ip)
+ || (ntohl(newtuple.src.ip)
+ > ntohl(mr->range[i].max_ip)))
+ continue;
+ } else {
+ if (newtuple.src.ip != tuple->src.ip)
+ continue;
+ }
+
+ if (!(mr->range[i].flags & IP_NAT_RANGE_PROTO_SPECIFIED)
+ || proto->in_range(&newtuple, IP_NAT_MANIP_SRC,
+ &mr->range[i].min, &mr->range[i].max))
+ return 1;
+ }
+ return 0;
+}
+
+static inline int
+src_cmp(const struct ip_nat_hash *i,
+ const struct ip_conntrack_tuple *tuple,
+ const struct ip_nat_multi_range *mr)
+{
+ return (i->conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum
+ == tuple->dst.protonum
+ && i->conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip
+ == tuple->src.ip
+ && i->conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.all
+ == tuple->src.u.all
+ && in_range(tuple,
+ &i->conntrack->tuplehash[IP_CT_DIR_ORIGINAL]
+ .tuple.src,
+ mr));
+}
+
+/* Only called for SRC manip */
+static struct ip_conntrack_manip *
+find_appropriate_src(const struct ip_conntrack_tuple *tuple,
+ const struct ip_nat_multi_range *mr)
+{
+ unsigned int h = hash_by_src(&tuple->src, tuple->dst.protonum);
+ struct ip_nat_hash *i;
+
+ MUST_BE_READ_LOCKED(&ip_nat_lock);
+ i = LIST_FIND(&bysource[h], src_cmp, struct ip_nat_hash *, tuple, mr);
+ if (i)
+ return &i->conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src;
+ else
+ return NULL;
+}
+
+/* If it's really a local destination manip, it may need to do a
+ source manip too. */
+static int
+do_extra_mangle(u_int32_t var_ip, u_int32_t *other_ipp)
+{
+ struct rtable *rt;
+
+ /* FIXME: IPTOS_TOS(iph->tos) --RR */
+ if (ip_route_output(&rt, var_ip, 0, 0, 0) != 0) {
+ DEBUGP("do_extra_mangle: Can't get route to %u.%u.%u.%u\n",
+ NIPQUAD(var_ip));
+ return 0;
+ }
+
+ *other_ipp = rt->rt_src;
+ ip_rt_put(rt);
+ return 1;
+}
+
+/* Simple way to iterate through all. */
+static inline int fake_cmp(const struct ip_nat_hash *i,
+ u_int32_t src, u_int32_t dst, u_int16_t protonum,
+ unsigned int *score,
+ const struct ip_conntrack *conntrack)
+{
+ /* Compare backwards: we're dealing with OUTGOING tuples, and
+ inside the conntrack is the REPLY tuple. Don't count this
+ conntrack. */
+ if (i->conntrack != conntrack
+ && i->conntrack->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip == dst
+ && i->conntrack->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip == src
+ && (i->conntrack->tuplehash[IP_CT_DIR_REPLY].tuple.dst.protonum
+ == protonum))
+ (*score)++;
+ return 0;
+}
+
+static inline unsigned int
+count_maps(u_int32_t src, u_int32_t dst, u_int16_t protonum,
+ const struct ip_conntrack *conntrack)
+{
+ unsigned int score = 0;
+ unsigned int h;
+
+ MUST_BE_READ_LOCKED(&ip_nat_lock);
+ h = hash_by_ipsproto(src, dst, protonum);
+ LIST_FIND(&byipsproto[h], fake_cmp, struct ip_nat_hash *,
+ src, dst, protonum, &score, conntrack);
+
+ return score;
+}
+
+/* For [FUTURE] fragmentation handling, we want the least-used
+ src-ip/dst-ip/proto triple. Fairness doesn't come into it. Thus
+ if the range specifies 1.2.3.4 ports 10000-10005 and 1.2.3.5 ports
+ 1-65535, we don't do pro-rata allocation based on ports; we choose
+ the ip with the lowest src-ip/dst-ip/proto usage.
+
+ If an allocation then fails (eg. all 6 ports used in the 1.2.3.4
+ range), we eliminate that and try again. This is not the most
+ efficient approach, but if you're worried about that, don't hand us
+ ranges you don't really have. */
+static struct ip_nat_range *
+find_best_ips_proto(struct ip_conntrack_tuple *tuple,
+ const struct ip_nat_multi_range *mr,
+ const struct ip_conntrack *conntrack,
+ unsigned int hooknum)
+{
+ unsigned int i;
+ struct {
+ const struct ip_nat_range *range;
+ unsigned int score;
+ struct ip_conntrack_tuple tuple;
+ } best = { NULL, 0xFFFFFFFF };
+ u_int32_t *var_ipp, *other_ipp, saved_ip, orig_dstip;
+ static unsigned int randomness = 0;
+
+ if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC) {
+ var_ipp = &tuple->src.ip;
+ saved_ip = tuple->dst.ip;
+ other_ipp = &tuple->dst.ip;
+ } else {
+ var_ipp = &tuple->dst.ip;
+ saved_ip = tuple->src.ip;
+ other_ipp = &tuple->src.ip;
+ }
+ /* Don't do do_extra_mangle unless neccessary (overrides
+ explicit socket bindings, for example) */
+ orig_dstip = tuple->dst.ip;
+
+ IP_NF_ASSERT(mr->rangesize >= 1);
+ for (i = 0; i < mr->rangesize; i++) {
+ /* Host order */
+ u_int32_t minip, maxip, j;
+
+ /* Don't do ranges which are already eliminated. */
+ if (mr->range[i].flags & IP_NAT_RANGE_FULL) {
+ continue;
+ }
+
+ if (mr->range[i].flags & IP_NAT_RANGE_MAP_IPS) {
+ minip = ntohl(mr->range[i].min_ip);
+ maxip = ntohl(mr->range[i].max_ip);
+ } else
+ minip = maxip = ntohl(*var_ipp);
+
+ randomness++;
+ for (j = 0; j < maxip - minip + 1; j++) {
+ unsigned int score;
+
+ *var_ipp = htonl(minip + (randomness + j)
+ % (maxip - minip + 1));
+
+ /* Reset the other ip in case it was mangled by
+ * do_extra_mangle last time. */
+ *other_ipp = saved_ip;
+
+ if (hooknum == NF_IP_LOCAL_OUT
+ && *var_ipp != orig_dstip
+ && !do_extra_mangle(*var_ipp, other_ipp)) {
+ DEBUGP("Range %u %u.%u.%u.%u rt failed!\n",
+ i, NIPQUAD(*var_ipp));
+ /* Can't route? This whole range part is
+ * probably screwed, but keep trying
+ * anyway. */
+ continue;
+ }
+
+ /* Count how many others map onto this. */
+ score = count_maps(tuple->src.ip, tuple->dst.ip,
+ tuple->dst.protonum, conntrack);
+ if (score < best.score) {
+ /* Optimization: doesn't get any better than
+ this. */
+ if (score == 0)
+ return (struct ip_nat_range *)
+ &mr->range[i];
+
+ best.score = score;
+ best.tuple = *tuple;
+ best.range = &mr->range[i];
+ }
+ }
+ }
+ *tuple = best.tuple;
+
+ /* Discard const. */
+ return (struct ip_nat_range *)best.range;
+}
+
+/* Fast version doesn't iterate through hash chains, but only handles
+ common case of single IP address (null NAT, masquerade) */
+static struct ip_nat_range *
+find_best_ips_proto_fast(struct ip_conntrack_tuple *tuple,
+ const struct ip_nat_multi_range *mr,
+ const struct ip_conntrack *conntrack,
+ unsigned int hooknum)
+{
+ if (mr->rangesize != 1
+ || (mr->range[0].flags & IP_NAT_RANGE_FULL)
+ || ((mr->range[0].flags & IP_NAT_RANGE_MAP_IPS)
+ && mr->range[0].min_ip != mr->range[0].max_ip))
+ return find_best_ips_proto(tuple, mr, conntrack, hooknum);
+
+ if (mr->range[0].flags & IP_NAT_RANGE_MAP_IPS) {
+ if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC)
+ tuple->src.ip = mr->range[0].min_ip;
+ else {
+ /* Only do extra mangle when required (breaks
+ socket binding) */
+ if (tuple->dst.ip != mr->range[0].min_ip
+ && hooknum == NF_IP_LOCAL_OUT
+ && !do_extra_mangle(mr->range[0].min_ip,
+ &tuple->src.ip))
+ return NULL;
+ tuple->dst.ip = mr->range[0].min_ip;
+ }
+ }
+
+ /* Discard const. */
+ return (struct ip_nat_range *)&mr->range[0];
+}
+
+static int
+get_unique_tuple(struct ip_conntrack_tuple *tuple,
+ const struct ip_conntrack_tuple *orig_tuple,
+ const struct ip_nat_multi_range *mrr,
+ struct ip_conntrack *conntrack,
+ unsigned int hooknum)
+{
+ struct ip_nat_protocol *proto
+ = find_nat_proto(orig_tuple->dst.protonum);
+ struct ip_nat_range *rptr;
+ unsigned int i;
+ int ret;
+
+ /* We temporarily use flags for marking full parts, but we
+ always clean up afterwards */
+ struct ip_nat_multi_range *mr = (void *)mrr;
+
+ /* 1) If this srcip/proto/src-proto-part is currently mapped,
+ and that same mapping gives a unique tuple within the given
+ range, use that.
+
+ This is only required for source (ie. NAT/masq) mappings.
+ So far, we don't do local source mappings, so multiple
+ manips not an issue. */
+ if (hooknum == NF_IP_POST_ROUTING) {
+ struct ip_conntrack_manip *manip;
+
+ manip = find_appropriate_src(orig_tuple, mr);
+ if (manip) {
+ /* Apply same source manipulation. */
+ *tuple = ((struct ip_conntrack_tuple)
+ { *manip, orig_tuple->dst });
+ DEBUGP("get_unique_tuple: Found current src map\n");
+ return 1;
+ }
+ }
+
+ /* 2) Select the least-used IP/proto combination in the given
+ range.
+ */
+ *tuple = *orig_tuple;
+ while ((rptr = find_best_ips_proto_fast(tuple, mr, conntrack, hooknum))
+ != NULL) {
+ DEBUGP("Found best for "); DUMP_TUPLE_RAW(tuple);
+ /* 3) The per-protocol part of the manip is made to
+ map into the range to make a unique tuple. */
+
+ /* Only bother mapping if it's not already in range
+ and unique */
+ if ((!(rptr->flags & IP_NAT_RANGE_PROTO_SPECIFIED)
+ || proto->in_range(tuple, HOOK2MANIP(hooknum),
+ &rptr->min, &rptr->max))
+ && !ip_nat_used_tuple(tuple, conntrack)) {
+ ret = 1;
+ goto clear_fulls;
+ } else {
+ if (proto->unique_tuple(tuple, rptr,
+ HOOK2MANIP(hooknum),
+ conntrack)) {
+ /* Must be unique. */
+ IP_NF_ASSERT(!ip_nat_used_tuple(tuple,
+ conntrack));
+ ret = 1;
+ goto clear_fulls;
+ } else if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST) {
+ /* Try implicit source NAT; protocol
+ may be able to play with ports to
+ make it unique. */
+ struct ip_nat_range r
+ = { IP_NAT_RANGE_MAP_IPS,
+ tuple->src.ip, tuple->src.ip,
+ { 0 }, { 0 } };
+ DEBUGP("Trying implicit mapping\n");
+ if (proto->unique_tuple(tuple, &r,
+ IP_NAT_MANIP_SRC,
+ conntrack)) {
+ /* Must be unique. */
+ IP_NF_ASSERT(!ip_nat_used_tuple
+ (tuple, conntrack));
+ ret = 1;
+ goto clear_fulls;
+ }
+ }
+ DEBUGP("Protocol can't get unique tuple %u.\n",
+ hooknum);
+ }
+
+ /* Eliminate that from range, and try again. */
+ rptr->flags |= IP_NAT_RANGE_FULL;
+ *tuple = *orig_tuple;
+ }
+
+ ret = 0;
+
+ clear_fulls:
+ /* Clear full flags. */
+ IP_NF_ASSERT(mr->rangesize >= 1);
+ for (i = 0; i < mr->rangesize; i++)
+ mr->range[i].flags &= ~IP_NAT_RANGE_FULL;
+
+ return ret;
+}
+
+static inline int
+helper_cmp(const struct ip_nat_helper *helper,
+ const struct ip_conntrack_tuple *tuple)
+{
+ return ip_ct_tuple_mask_cmp(tuple, &helper->tuple, &helper->mask);
+}
+
+/* Where to manip the reply packets (will be reverse manip). */
+static unsigned int opposite_hook[NF_IP_NUMHOOKS]
+= { [NF_IP_PRE_ROUTING] = NF_IP_POST_ROUTING,
+ [NF_IP_POST_ROUTING] = NF_IP_PRE_ROUTING,
+ [NF_IP_LOCAL_OUT] = NF_IP_LOCAL_IN,
+ [NF_IP_LOCAL_IN] = NF_IP_LOCAL_OUT,
+};
+
+unsigned int
+ip_nat_setup_info(struct ip_conntrack *conntrack,
+ const struct ip_nat_multi_range *mr,
+ unsigned int hooknum)
+{
+ struct ip_conntrack_tuple new_tuple, inv_tuple, reply;
+ struct ip_conntrack_tuple orig_tp;
+ struct ip_nat_info *info = &conntrack->nat.info;
+ int in_hashes = info->initialized;
+ int cnt = 0;
+
+ MUST_BE_WRITE_LOCKED(&ip_nat_lock);
+ IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING
+ || hooknum == NF_IP_POST_ROUTING
+ || hooknum == NF_IP_LOCAL_IN
+ || hooknum == NF_IP_LOCAL_OUT);
+ IP_NF_ASSERT(info->num_manips < IP_NAT_MAX_MANIPS);
+ IP_NF_ASSERT(!(info->initialized & (1 << HOOK2MANIP(hooknum))));
+
+ /* What we've got will look like inverse of reply. Normally
+ this is what is in the conntrack, except for prior
+ manipulations (future optimization: if num_manips == 0,
+ orig_tp =
+ conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple) */
+ invert_tuplepr(&orig_tp,
+ &conntrack->tuplehash[IP_CT_DIR_REPLY].tuple);
+
+#if 0
+ {
+ unsigned int i;
+
+ DEBUGP("Hook %u (%s), ", hooknum,
+ HOOK2MANIP(hooknum)==IP_NAT_MANIP_SRC ? "SRC" : "DST");
+ DUMP_TUPLE(&orig_tp);
+ DEBUGP("Range %p: ", mr);
+ for (i = 0; i < mr->rangesize; i++) {
+ DEBUGP("%u:%s%s%s %u.%u.%u.%u - %u.%u.%u.%u %u - %u\n",
+ i,
+ (mr->range[i].flags & IP_NAT_RANGE_MAP_IPS)
+ ? " MAP_IPS" : "",
+ (mr->range[i].flags
+ & IP_NAT_RANGE_PROTO_SPECIFIED)
+ ? " PROTO_SPECIFIED" : "",
+ (mr->range[i].flags & IP_NAT_RANGE_FULL)
+ ? " FULL" : "",
+ NIPQUAD(mr->range[i].min_ip),
+ NIPQUAD(mr->range[i].max_ip),
+ mr->range[i].min.all,
+ mr->range[i].max.all);
+ }
+ }
+#endif
+
+ do {
+ cnt++;
+ if (!get_unique_tuple(&new_tuple, &orig_tp, mr, conntrack,
+ hooknum)) {
+ DEBUGP("ip_nat_setup_info: Can't get unique for %p.\n",
+ conntrack);
+ return NF_DROP;
+ }
+
+#if 0
+ DEBUGP("Hook %u (%s) %p\n", hooknum,
+ HOOK2MANIP(hooknum)==IP_NAT_MANIP_SRC ? "SRC" : "DST",
+ conntrack);
+ DEBUGP("Original: ");
+ DUMP_TUPLE_RAW(&orig_tp);
+ DEBUGP("New: ");
+ DUMP_TUPLE_RAW(&new_tuple);
+#endif
+
+ /* We now have two tuples (SRCIP/SRCPT/DSTIP/DSTPT):
+ the original (A/B/C/D') and the mangled one (E/F/G/H').
+
+ We're only allowed to work with the SRC per-proto
+ part, so we create inverses of both to start, then
+ derive the other fields we need. */
+
+ /* Reply connection: simply invert the new tuple
+ (G/H/E/F') */
+ invert_tuplepr(&reply, &new_tuple);
+
+ /* Alter conntrack table so it recognizes replies.
+ If fail this race (reply tuple now used), repeat. */
+ } while (!ip_conntrack_alter_reply(conntrack, &reply) && (cnt < 200));
+
+ if (cnt>=200) {
+ /*
+ * Print out some useful information.
+ */
+ printk(KERN_ERR, "Failed getting result for packet\n", conntrack);
+
+ /*
+ * It appears as though we have a tuple which is marked
+ * as free but a conntrack entry exists for it!!
+ */
+#undef DEBUGP
+#define DEBUGP printk
+ printk("Hook %u (%s) %p\n", hooknum,
+ HOOK2MANIP(hooknum)==IP_NAT_MANIP_SRC ? "SRC" : "DST",
+ conntrack);
+ printk("Original: ");
+ DUMP_TUPLE(&orig_tp);
+ printk("New: ");
+ DUMP_TUPLE(&new_tuple);
+#undef DEBUGP
+#define DEBUGP(format, args...)
+
+ return NF_DROP;
+ }
+
+ /* FIXME: We can simply used existing conntrack reply tuple
+ here --RR */
+ /* Create inverse of original: C/D/A/B' */
+ invert_tuplepr(&inv_tuple, &orig_tp);
+
+ /* Has source changed?. */
+ if (!ip_ct_tuple_src_equal(&new_tuple, &orig_tp)) {
+ /* In this direction, a source manip. */
+ info->manips[info->num_manips++] =
+ ((struct ip_nat_info_manip)
+ { IP_CT_DIR_ORIGINAL, hooknum,
+ IP_NAT_MANIP_SRC, new_tuple.src });
+
+ IP_NF_ASSERT(info->num_manips < IP_NAT_MAX_MANIPS);
+
+ /* In the reverse direction, a destination manip. */
+ info->manips[info->num_manips++] =
+ ((struct ip_nat_info_manip)
+ { IP_CT_DIR_REPLY, opposite_hook[hooknum],
+ IP_NAT_MANIP_DST, orig_tp.src });
+ IP_NF_ASSERT(info->num_manips <= IP_NAT_MAX_MANIPS);
+ }
+
+ /* Has destination changed? */
+ if (!ip_ct_tuple_dst_equal(&new_tuple, &orig_tp)) {
+ /* In this direction, a destination manip */
+ info->manips[info->num_manips++] =
+ ((struct ip_nat_info_manip)
+ { IP_CT_DIR_ORIGINAL, hooknum,
+ IP_NAT_MANIP_DST, reply.src });
+
+ IP_NF_ASSERT(info->num_manips < IP_NAT_MAX_MANIPS);
+
+ /* In the reverse direction, a source manip. */
+ info->manips[info->num_manips++] =
+ ((struct ip_nat_info_manip)
+ { IP_CT_DIR_REPLY, opposite_hook[hooknum],
+ IP_NAT_MANIP_SRC, inv_tuple.src });
+ IP_NF_ASSERT(info->num_manips <= IP_NAT_MAX_MANIPS);
+ }
+
+ /* If there's a helper, assign it; based on new tuple. */
+ if (!conntrack->master)
+ info->helper = LIST_FIND(&helpers, helper_cmp, struct ip_nat_helper *,
+ &reply);
+
+ /* It's done. */
+ info->initialized |= (1 << HOOK2MANIP(hooknum));
+
+ if (in_hashes) {
+ IP_NF_ASSERT(info->bysource.conntrack);
+ replace_in_hashes(conntrack, info);
+ } else {
+ place_in_hashes(conntrack, info);
+ }
+
+ return NF_ACCEPT;
+}
+
+void replace_in_hashes(struct ip_conntrack *conntrack,
+ struct ip_nat_info *info)
+{
+ /* Source has changed, so replace in hashes. */
+ unsigned int srchash
+ = hash_by_src(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL]
+ .tuple.src,
+ conntrack->tuplehash[IP_CT_DIR_ORIGINAL]
+ .tuple.dst.protonum);
+ /* We place packet as seen OUTGOUNG in byips_proto hash
+ (ie. reverse dst and src of reply packet. */
+ unsigned int ipsprotohash
+ = hash_by_ipsproto(conntrack->tuplehash[IP_CT_DIR_REPLY]
+ .tuple.dst.ip,
+ conntrack->tuplehash[IP_CT_DIR_REPLY]
+ .tuple.src.ip,
+ conntrack->tuplehash[IP_CT_DIR_REPLY]
+ .tuple.dst.protonum);
+
+ IP_NF_ASSERT(info->bysource.conntrack == conntrack);
+ MUST_BE_WRITE_LOCKED(&ip_nat_lock);
+
+ list_del(&info->bysource.list);
+ list_del(&info->byipsproto.list);
+
+ list_prepend(&bysource[srchash], &info->bysource);
+ list_prepend(&byipsproto[ipsprotohash], &info->byipsproto);
+}
+
+void place_in_hashes(struct ip_conntrack *conntrack,
+ struct ip_nat_info *info)
+{
+ unsigned int srchash
+ = hash_by_src(&conntrack->tuplehash[IP_CT_DIR_ORIGINAL]
+ .tuple.src,
+ conntrack->tuplehash[IP_CT_DIR_ORIGINAL]
+ .tuple.dst.protonum);
+ /* We place packet as seen OUTGOUNG in byips_proto hash
+ (ie. reverse dst and src of reply packet. */
+ unsigned int ipsprotohash
+ = hash_by_ipsproto(conntrack->tuplehash[IP_CT_DIR_REPLY]
+ .tuple.dst.ip,
+ conntrack->tuplehash[IP_CT_DIR_REPLY]
+ .tuple.src.ip,
+ conntrack->tuplehash[IP_CT_DIR_REPLY]
+ .tuple.dst.protonum);
+
+ IP_NF_ASSERT(!info->bysource.conntrack);
+
+ MUST_BE_WRITE_LOCKED(&ip_nat_lock);
+ info->byipsproto.conntrack = conntrack;
+ info->bysource.conntrack = conntrack;
+
+ list_prepend(&bysource[srchash], &info->bysource);
+ list_prepend(&byipsproto[ipsprotohash], &info->byipsproto);
+}
+
+static void
+manip_pkt(u_int16_t proto, struct iphdr *iph, size_t len,
+ const struct ip_conntrack_manip *manip,
+ enum ip_nat_manip_type maniptype,
+ __u32 *nfcache)
+{
+ *nfcache |= NFC_ALTERED;
+ find_nat_proto(proto)->manip_pkt(iph, len, manip, maniptype);
+
+ if (maniptype == IP_NAT_MANIP_SRC) {
+ iph->check = ip_nat_cheat_check(~iph->saddr, manip->ip,
+ iph->check);
+ iph->saddr = manip->ip;
+ } else {
+ iph->check = ip_nat_cheat_check(~iph->daddr, manip->ip,
+ iph->check);
+ iph->daddr = manip->ip;
+ }
+#if 0
+ if (ip_fast_csum((u8 *)iph, iph->ihl) != 0)
+ DEBUGP("IP: checksum on packet bad.\n");
+
+ if (proto == IPPROTO_TCP) {
+ void *th = (u_int32_t *)iph + iph->ihl;
+ if (tcp_v4_check(th, len - 4*iph->ihl, iph->saddr, iph->daddr,
+ csum_partial((char *)th, len-4*iph->ihl, 0)))
+ DEBUGP("TCP: checksum on packet bad\n");
+ }
+#endif
+}
+
+static inline int exp_for_packet(struct ip_conntrack_expect *exp,
+ struct sk_buff **pskb)
+{
+ struct ip_conntrack_protocol *proto;
+ int ret = 1;
+
+ MUST_BE_READ_LOCKED(&ip_conntrack_lock);
+ proto = __ip_ct_find_proto((*pskb)->nh.iph->protocol);
+ if (proto->exp_matches_pkt)
+ ret = proto->exp_matches_pkt(exp, pskb);
+
+ return ret;
+}
+
+/* Do packet manipulations according to binding. */
+unsigned int
+do_bindings(struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo,
+ struct ip_nat_info *info,
+ unsigned int hooknum,
+ struct sk_buff **pskb)
+{
+ unsigned int i;
+ struct ip_nat_helper *helper;
+ enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
+ int is_tcp = (*pskb)->nh.iph->protocol == IPPROTO_TCP;
+
+ /* Need nat lock to protect against modification, but neither
+ conntrack (referenced) and helper (deleted with
+ synchronize_bh()) can vanish. */
+ READ_LOCK(&ip_nat_lock);
+ for (i = 0; i < info->num_manips; i++) {
+ /* raw socket (tcpdump) may have clone of incoming
+ skb: don't disturb it --RR */
+ if (skb_cloned(*pskb) && !(*pskb)->sk) {
+ struct sk_buff *nskb = skb_copy(*pskb, GFP_ATOMIC);
+ if (!nskb) {
+ READ_UNLOCK(&ip_nat_lock);
+ return NF_DROP;
+ }
+ kfree_skb(*pskb);
+ *pskb = nskb;
+ }
+
+ if (info->manips[i].direction == dir
+ && info->manips[i].hooknum == hooknum) {
+ DEBUGP("Mangling %p: %s to %u.%u.%u.%u %u\n",
+ *pskb,
+ info->manips[i].maniptype == IP_NAT_MANIP_SRC
+ ? "SRC" : "DST",
+ NIPQUAD(info->manips[i].manip.ip),
+ htons(info->manips[i].manip.u.all));
+ manip_pkt((*pskb)->nh.iph->protocol,
+ (*pskb)->nh.iph,
+ (*pskb)->len,
+ &info->manips[i].manip,
+ info->manips[i].maniptype,
+ &(*pskb)->nfcache);
+ }
+ }
+ helper = info->helper;
+ READ_UNLOCK(&ip_nat_lock);
+
+ if (helper) {
+ struct ip_conntrack_expect *exp = NULL;
+ struct list_head *cur_item;
+ int ret = NF_ACCEPT;
+ int helper_called = 0;
+
+ DEBUGP("do_bindings: helper existing for (%p)\n", ct);
+
+ /* Always defragged for helpers */
+ IP_NF_ASSERT(!((*pskb)->nh.iph->frag_off
+ & htons(IP_MF|IP_OFFSET)));
+
+ /* Have to grab read lock before sibling_list traversal */
+ READ_LOCK(&ip_conntrack_lock);
+ list_for_each_prev(cur_item, &ct->sibling_list) {
+ exp = list_entry(cur_item, struct ip_conntrack_expect,
+ expected_list);
+
+ /* if this expectation is already established, skip */
+ if (exp->sibling)
+ continue;
+
+ if (exp_for_packet(exp, pskb)) {
+ /* FIXME: May be true multiple times in the
+ * case of UDP!! */
+ DEBUGP("calling nat helper (exp=%p) for packet\n", exp);
+ ret = helper->help(ct, exp, info, ctinfo,
+ hooknum, pskb);
+ if (ret != NF_ACCEPT) {
+ READ_UNLOCK(&ip_conntrack_lock);
+ return ret;
+ }
+ helper_called = 1;
+ }
+ }
+ /* Helper might want to manip the packet even when there is no
+ * matching expectation for this packet */
+ if (!helper_called && helper->flags & IP_NAT_HELPER_F_ALWAYS) {
+ DEBUGP("calling nat helper for packet without expectation\n");
+ ret = helper->help(ct, NULL, info, ctinfo,
+ hooknum, pskb);
+ if (ret != NF_ACCEPT) {
+ READ_UNLOCK(&ip_conntrack_lock);
+ return ret;
+ }
+ }
+ READ_UNLOCK(&ip_conntrack_lock);
+
+ /* Adjust sequence number only once per packet
+ * (helper is called at all hooks) */
+ if (is_tcp && (hooknum == NF_IP_POST_ROUTING
+ || hooknum == NF_IP_LOCAL_IN)) {
+ DEBUGP("ip_nat_core: adjusting sequence number\n");
+ /* future: put this in a l4-proto specific function,
+ * and call this function here. */
+ ip_nat_seq_adjust(*pskb, ct, ctinfo);
+ }
+
+ return ret;
+
+ } else
+ return NF_ACCEPT;
+
+ /* not reached */
+}
+
+static inline int tuple_src_equal_dst(const struct ip_conntrack_tuple *t1,
+ const struct ip_conntrack_tuple *t2)
+{
+ if (t1->dst.protonum != t2->dst.protonum || t1->src.ip != t2->dst.ip)
+ return 0;
+ if (t1->dst.protonum != IPPROTO_ICMP)
+ return t1->src.u.all == t2->dst.u.all;
+ else {
+ struct ip_conntrack_tuple inv;
+
+ /* ICMP tuples are asymetric */
+ invert_tuplepr(&inv, t1);
+ return inv.src.u.all == t2->src.u.all &&
+ inv.dst.u.all == t2->dst.u.all;
+ }
+}
+
+unsigned int
+icmp_reply_translation(struct sk_buff *skb,
+ struct ip_conntrack *conntrack,
+ unsigned int hooknum,
+ int dir)
+{
+ struct iphdr *iph = skb->nh.iph;
+ struct icmphdr *hdr = (struct icmphdr *)((u_int32_t *)iph + iph->ihl);
+ struct iphdr *inner = (struct iphdr *)(hdr + 1);
+ size_t datalen = skb->len - ((void *)inner - (void *)iph);
+ unsigned int i;
+ struct ip_nat_info *info = &conntrack->nat.info;
+ struct ip_conntrack_tuple *cttuple, innertuple;
+
+ IP_NF_ASSERT(skb->len >= iph->ihl*4 + sizeof(struct icmphdr));
+ /* Must be RELATED */
+ IP_NF_ASSERT(skb->nfct
+ - ((struct ip_conntrack *)skb->nfct->master)->infos
+ == IP_CT_RELATED
+ || skb->nfct
+ - ((struct ip_conntrack *)skb->nfct->master)->infos
+ == IP_CT_RELATED+IP_CT_IS_REPLY);
+
+ /* Redirects on non-null nats must be dropped, else they'll
+ start talking to each other without our translation, and be
+ confused... --RR */
+ if (hdr->type == ICMP_REDIRECT) {
+ /* Don't care about races here. */
+ if (info->initialized
+ != ((1 << IP_NAT_MANIP_SRC) | (1 << IP_NAT_MANIP_DST))
+ || info->num_manips != 0)
+ return NF_DROP;
+ }
+
+ DEBUGP("icmp_reply_translation: translating error %p hook %u dir %s\n",
+ skb, hooknum, dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY");
+ /* Note: May not be from a NAT'd host, but probably safest to
+ do translation always as if it came from the host itself
+ (even though a "host unreachable" coming from the host
+ itself is a bit weird).
+
+ More explanation: some people use NAT for anonymizing.
+ Also, CERT recommends dropping all packets from private IP
+ addresses (although ICMP errors from internal links with
+ such addresses are not too uncommon, as Alan Cox points
+ out) */
+
+ if (!ip_ct_get_tuple(inner, datalen, &innertuple,
+ ip_ct_find_proto(inner->protocol)))
+ return 0;
+ cttuple = &conntrack->tuplehash[dir].tuple;
+
+ READ_LOCK(&ip_nat_lock);
+ for (i = 0; i < info->num_manips; i++) {
+ DEBUGP("icmp_reply: manip %u dir %s hook %u\n",
+ i, info->manips[i].direction == IP_CT_DIR_ORIGINAL ?
+ "ORIG" : "REPLY", info->manips[i].hooknum);
+
+ if (info->manips[i].direction != dir)
+ continue;
+
+ /* Mapping the inner packet is just like a normal packet, except
+ * it was never src/dst reversed, so where we would normally
+ * apply a dst manip, we apply a src, and vice versa. */
+
+ /* Only true for forwarded packets, locally generated packets
+ * never hit PRE_ROUTING, we need to apply their PRE_ROUTING
+ * manips in LOCAL_OUT. */
+ if (hooknum == NF_IP_LOCAL_OUT &&
+ info->manips[i].hooknum == NF_IP_PRE_ROUTING)
+ hooknum = info->manips[i].hooknum;
+
+ if (info->manips[i].hooknum != hooknum)
+ continue;
+
+ /* ICMP errors may be generated locally for packets that
+ * don't have all NAT manips applied yet. Verify manips
+ * have been applied before reversing them */
+ if (info->manips[i].maniptype == IP_NAT_MANIP_SRC) {
+ if (!tuple_src_equal_dst(cttuple, &innertuple))
+ continue;
+ } else {
+ if (!tuple_src_equal_dst(&innertuple, cttuple))
+ continue;
+ }
+
+ DEBUGP("icmp_reply: inner %s -> %u.%u.%u.%u %u\n",
+ info->manips[i].maniptype == IP_NAT_MANIP_SRC
+ ? "DST" : "SRC", NIPQUAD(info->manips[i].manip.ip),
+ ntohs(info->manips[i].manip.u.udp.port));
+ manip_pkt(inner->protocol, inner,
+ skb->len - ((void *)inner - (void *)iph),
+ &info->manips[i].manip, !info->manips[i].maniptype,
+ &skb->nfcache);
+ /* Outer packet needs to have IP header NATed like
+ it's a reply. */
+
+ /* Use mapping to map outer packet: 0 give no
+ per-proto mapping */
+ DEBUGP("icmp_reply: outer %s -> %u.%u.%u.%u\n",
+ info->manips[i].maniptype == IP_NAT_MANIP_SRC
+ ? "SRC" : "DST", NIPQUAD(info->manips[i].manip.ip));
+ manip_pkt(0, iph, skb->len, &info->manips[i].manip,
+ info->manips[i].maniptype, &skb->nfcache);
+ }
+ READ_UNLOCK(&ip_nat_lock);
+
+ /* Since we mangled inside ICMP packet, recalculate its
+ checksum from scratch. (Hence the handling of incorrect
+ checksums in conntrack, so we don't accidentally fix one.) */
+ hdr->checksum = 0;
+ hdr->checksum = ip_compute_csum((unsigned char *)hdr,
+ sizeof(*hdr) + datalen);
+
+ return NF_ACCEPT;
+}
+
+int __init ip_nat_init(void)
+{
+ size_t i;
+
+ /* Leave them the same for the moment. */
+ ip_nat_htable_size = ip_conntrack_htable_size;
+
+ /* One vmalloc for both hash tables */
+ bysource = vmalloc(sizeof(struct list_head) * ip_nat_htable_size*2);
+ if (!bysource) {
+ return -ENOMEM;
+ }
+ byipsproto = bysource + ip_nat_htable_size;
+
+ /* Sew in builtin protocols. */
+ WRITE_LOCK(&ip_nat_lock);
+ list_append(&protos, &ip_nat_protocol_tcp);
+ list_append(&protos, &ip_nat_protocol_udp);
+ list_append(&protos, &ip_nat_protocol_icmp);
+ WRITE_UNLOCK(&ip_nat_lock);
+
+ for (i = 0; i < ip_nat_htable_size; i++) {
+ INIT_LIST_HEAD(&bysource[i]);
+ INIT_LIST_HEAD(&byipsproto[i]);
+ }
+
+ /* FIXME: Man, this is a hack. <SIGH> */
+ IP_NF_ASSERT(ip_conntrack_destroyed == NULL);
+ ip_conntrack_destroyed = &ip_nat_cleanup_conntrack;
+
+ return 0;
+}
+
+/* Clear NAT section of all conntracks, in case we're loaded again. */
+static int clean_nat(struct ip_conntrack *i, void *data)
+{
+ memset(&i->nat, 0, sizeof(i->nat));
+ return 0;
+}
+
+/* Not __exit: called from ip_nat_standalone.c:init_or_cleanup() --RR */
+void ip_nat_cleanup(void)
+{
+ ip_ct_iterate_cleanup(&clean_nat, NULL);
+ ip_conntrack_destroyed = NULL;
+ vfree(bysource);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_ftp.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_ftp.c
new file mode 100644
index 0000000..1f14af0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_ftp.c
@@ -0,0 +1,325 @@
+/* FTP extension for TCP NAT alteration. */
+#include <linux/module.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <net/tcp.h>
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_helper.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+#include <linux/netfilter_ipv4/ip_conntrack_ftp.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+#define MAX_PORTS 8
+static int ports[MAX_PORTS];
+static int ports_c = 0;
+
+#ifdef MODULE_PARM
+MODULE_PARM(ports, "1-" __MODULE_STRING(MAX_PORTS) "i");
+#endif
+
+/* FIXME: Time out? --RR */
+
+static unsigned int
+ftp_nat_expected(struct sk_buff **pskb,
+ unsigned int hooknum,
+ struct ip_conntrack *ct,
+ struct ip_nat_info *info)
+{
+ struct ip_nat_multi_range mr;
+ u_int32_t newdstip, newsrcip, newip;
+ struct ip_ct_ftp_expect *exp_ftp_info;
+
+ struct ip_conntrack *master = master_ct(ct);
+
+ IP_NF_ASSERT(info);
+ IP_NF_ASSERT(master);
+
+ IP_NF_ASSERT(!(info->initialized & (1<<HOOK2MANIP(hooknum))));
+
+ DEBUGP("nat_expected: We have a connection!\n");
+ exp_ftp_info = &ct->master->help.exp_ftp_info;
+
+ if (exp_ftp_info->ftptype == IP_CT_FTP_PORT
+ || exp_ftp_info->ftptype == IP_CT_FTP_EPRT) {
+ /* PORT command: make connection go to the client. */
+ newdstip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
+ newsrcip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
+ DEBUGP("nat_expected: PORT cmd. %u.%u.%u.%u->%u.%u.%u.%u\n",
+ NIPQUAD(newsrcip), NIPQUAD(newdstip));
+ } else {
+ /* PASV command: make the connection go to the server */
+ newdstip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip;
+ newsrcip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+ DEBUGP("nat_expected: PASV cmd. %u.%u.%u.%u->%u.%u.%u.%u\n",
+ NIPQUAD(newsrcip), NIPQUAD(newdstip));
+ }
+
+ if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC)
+ newip = newsrcip;
+ else
+ newip = newdstip;
+
+ DEBUGP("nat_expected: IP to %u.%u.%u.%u\n", NIPQUAD(newip));
+
+ mr.rangesize = 1;
+ /* We don't want to manip the per-protocol, just the IPs... */
+ mr.range[0].flags = IP_NAT_RANGE_MAP_IPS;
+ mr.range[0].min_ip = mr.range[0].max_ip = newip;
+
+ /* ... unless we're doing a MANIP_DST, in which case, make
+ sure we map to the correct port */
+ if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST) {
+ mr.range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
+ mr.range[0].min = mr.range[0].max
+ = ((union ip_conntrack_manip_proto)
+ { .tcp = { htons(exp_ftp_info->port) } });
+ }
+ return ip_nat_setup_info(ct, &mr, hooknum);
+}
+
+static int
+mangle_rfc959_packet(struct sk_buff **pskb,
+ u_int32_t newip,
+ u_int16_t port,
+ unsigned int matchoff,
+ unsigned int matchlen,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo)
+{
+ char buffer[sizeof("nnn,nnn,nnn,nnn,nnn,nnn")];
+
+ sprintf(buffer, "%u,%u,%u,%u,%u,%u",
+ NIPQUAD(newip), port>>8, port&0xFF);
+
+ DEBUGP("calling ip_nat_mangle_tcp_packet\n");
+
+ return ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, matchoff,
+ matchlen, buffer, strlen(buffer));
+}
+
+/* |1|132.235.1.2|6275| */
+static int
+mangle_eprt_packet(struct sk_buff **pskb,
+ u_int32_t newip,
+ u_int16_t port,
+ unsigned int matchoff,
+ unsigned int matchlen,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo)
+{
+ char buffer[sizeof("|1|255.255.255.255|65535|")];
+
+ sprintf(buffer, "|1|%u.%u.%u.%u|%u|", NIPQUAD(newip), port);
+
+ DEBUGP("calling ip_nat_mangle_tcp_packet\n");
+
+ return ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, matchoff,
+ matchlen, buffer, strlen(buffer));
+}
+
+/* |1|132.235.1.2|6275| */
+static int
+mangle_epsv_packet(struct sk_buff **pskb,
+ u_int32_t newip,
+ u_int16_t port,
+ unsigned int matchoff,
+ unsigned int matchlen,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo)
+{
+ char buffer[sizeof("|||65535|")];
+
+ sprintf(buffer, "|||%u|", port);
+
+ DEBUGP("calling ip_nat_mangle_tcp_packet\n");
+
+ return ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, matchoff,
+ matchlen, buffer, strlen(buffer));
+}
+
+static int (*mangle[])(struct sk_buff **, u_int32_t, u_int16_t,
+ unsigned int,
+ unsigned int,
+ struct ip_conntrack *,
+ enum ip_conntrack_info)
+= { [IP_CT_FTP_PORT] mangle_rfc959_packet,
+ [IP_CT_FTP_PASV] mangle_rfc959_packet,
+ [IP_CT_FTP_EPRT] mangle_eprt_packet,
+ [IP_CT_FTP_EPSV] mangle_epsv_packet
+};
+
+static int ftp_data_fixup(const struct ip_ct_ftp_expect *exp_ftp_info,
+ struct ip_conntrack *ct,
+ struct sk_buff **pskb,
+ enum ip_conntrack_info ctinfo,
+ struct ip_conntrack_expect *expect)
+{
+ u_int32_t newip;
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct tcphdr *tcph = (void *)iph + iph->ihl*4;
+ u_int16_t port;
+ struct ip_conntrack_tuple newtuple;
+
+ DEBUGP("FTP_NAT: seq %u + %u in %u\n",
+ expect->seq, exp_ftp_info->len,
+ ntohl(tcph->seq));
+
+ /* Change address inside packet to match way we're mapping
+ this connection. */
+ if (exp_ftp_info->ftptype == IP_CT_FTP_PASV
+ || exp_ftp_info->ftptype == IP_CT_FTP_EPSV) {
+ /* PASV/EPSV response: must be where client thinks server
+ is */
+ newip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
+ /* Expect something from client->server */
+ newtuple.src.ip =
+ ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
+ newtuple.dst.ip =
+ ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
+ } else {
+ /* PORT command: must be where server thinks client is */
+ newip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+ /* Expect something from server->client */
+ newtuple.src.ip =
+ ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip;
+ newtuple.dst.ip =
+ ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+ }
+ newtuple.dst.protonum = IPPROTO_TCP;
+ newtuple.src.u.tcp.port = expect->tuple.src.u.tcp.port;
+
+ /* Try to get same port: if not, try to change it. */
+ for (port = exp_ftp_info->port; port != 0; port++) {
+ newtuple.dst.u.tcp.port = htons(port);
+
+ if (ip_conntrack_change_expect(expect, &newtuple) == 0)
+ break;
+ }
+ if (port == 0)
+ return 0;
+
+ if (!mangle[exp_ftp_info->ftptype](pskb, newip, port,
+ expect->seq - ntohl(tcph->seq),
+ exp_ftp_info->len, ct, ctinfo))
+ return 0;
+
+ return 1;
+}
+
+static unsigned int help(struct ip_conntrack *ct,
+ struct ip_conntrack_expect *exp,
+ struct ip_nat_info *info,
+ enum ip_conntrack_info ctinfo,
+ unsigned int hooknum,
+ struct sk_buff **pskb)
+{
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct tcphdr *tcph = (void *)iph + iph->ihl*4;
+ unsigned int datalen;
+ int dir;
+ struct ip_ct_ftp_expect *exp_ftp_info;
+
+ if (!exp)
+ DEBUGP("ip_nat_ftp: no exp!!");
+
+ exp_ftp_info = &exp->help.exp_ftp_info;
+
+ /* Only mangle things once: original direction in POST_ROUTING
+ and reply direction on PRE_ROUTING. */
+ dir = CTINFO2DIR(ctinfo);
+ if (!((hooknum == NF_IP_POST_ROUTING && dir == IP_CT_DIR_ORIGINAL)
+ || (hooknum == NF_IP_PRE_ROUTING && dir == IP_CT_DIR_REPLY))) {
+ DEBUGP("nat_ftp: Not touching dir %s at hook %s\n",
+ dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY",
+ hooknum == NF_IP_POST_ROUTING ? "POSTROUTING"
+ : hooknum == NF_IP_PRE_ROUTING ? "PREROUTING"
+ : hooknum == NF_IP_LOCAL_OUT ? "OUTPUT" : "???");
+ return NF_ACCEPT;
+ }
+
+ datalen = (*pskb)->len - iph->ihl * 4 - tcph->doff * 4;
+ /* If it's in the right range... */
+ if (between(exp->seq + exp_ftp_info->len,
+ ntohl(tcph->seq),
+ ntohl(tcph->seq) + datalen)) {
+ if (!ftp_data_fixup(exp_ftp_info, ct, pskb, ctinfo, exp))
+ return NF_DROP;
+ } else {
+ /* Half a match? This means a partial retransmisison.
+ It's a cracker being funky. */
+ if (net_ratelimit()) {
+ printk("FTP_NAT: partial packet %u/%u in %u/%u\n",
+ exp->seq, exp_ftp_info->len,
+ ntohl(tcph->seq),
+ ntohl(tcph->seq) + datalen);
+ }
+ return NF_DROP;
+ }
+ return NF_ACCEPT;
+}
+
+static struct ip_nat_helper ftp[MAX_PORTS];
+static char ftp_names[MAX_PORTS][10];
+
+/* Not __exit: called from init() */
+static void fini(void)
+{
+ int i;
+
+ for (i = 0; i < ports_c; i++) {
+ DEBUGP("ip_nat_ftp: unregistering port %d\n", ports[i]);
+ ip_nat_helper_unregister(&ftp[i]);
+ }
+}
+
+static int __init init(void)
+{
+ int i, ret = 0;
+ char *tmpname;
+
+ if (ports[0] == 0)
+ ports[0] = FTP_PORT;
+
+ for (i = 0; (i < MAX_PORTS) && ports[i]; i++) {
+ ftp[i].tuple.dst.protonum = IPPROTO_TCP;
+ ftp[i].tuple.src.u.tcp.port = htons(ports[i]);
+ ftp[i].mask.dst.protonum = 0xFFFF;
+ ftp[i].mask.src.u.tcp.port = 0xFFFF;
+ ftp[i].help = help;
+ ftp[i].me = THIS_MODULE;
+ ftp[i].flags = 0;
+ ftp[i].expect = ftp_nat_expected;
+
+ tmpname = &ftp_names[i][0];
+ if (ports[i] == FTP_PORT)
+ sprintf(tmpname, "ftp");
+ else
+ sprintf(tmpname, "ftp-%d", i);
+ ftp[i].name = tmpname;
+
+ DEBUGP("ip_nat_ftp: Trying to register for port %d\n",
+ ports[i]);
+ ret = ip_nat_helper_register(&ftp[i]);
+
+ if (ret) {
+ printk("ip_nat_ftp: error registering "
+ "helper for port %d\n", ports[i]);
+ fini();
+ return ret;
+ }
+ ports_c++;
+ }
+
+ return ret;
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_h323.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_h323.c
new file mode 100644
index 0000000..29e071c
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_h323.c
@@ -0,0 +1,419 @@
+/*
+ * H.323 'brute force' extension for NAT alteration.
+ * Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ * Based on ip_masq_h323.c for 2.2 kernels from CoRiTel, Sofia project.
+ * (http://www.coritel.it/projects/sofia/nat.html)
+ * Uses Sampsa Ranta's excellent idea on using expectfn to 'bind'
+ * the unregistered helpers to the conntrack entries.
+ */
+
+
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/ip.h>
+#include <net/checksum.h>
+#include <net/tcp.h>
+
+#include <linux/netfilter_ipv4/lockhelp.h>
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_helper.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+#include <linux/netfilter_ipv4/ip_conntrack_tuple.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_h323.h>
+
+MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>");
+MODULE_DESCRIPTION("H.323 'brute force' connection tracking module");
+MODULE_LICENSE("GPL");
+
+DECLARE_LOCK_EXTERN(ip_h323_lock);
+struct module *ip_nat_h323 = THIS_MODULE;
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+/* FIXME: Time out? --RR */
+
+static unsigned int
+h225_nat_expected(struct sk_buff **pskb,
+ unsigned int hooknum,
+ struct ip_conntrack *ct,
+ struct ip_nat_info *info);
+
+static unsigned int h225_nat_help(struct ip_conntrack *ct,
+ struct ip_conntrack_expect *exp,
+ struct ip_nat_info *info,
+ enum ip_conntrack_info ctinfo,
+ unsigned int hooknum,
+ struct sk_buff **pskb);
+
+static struct ip_nat_helper h245 =
+ { { NULL, NULL },
+ "H.245", /* name */
+ 0, /* flags */
+ NULL, /* module */
+ { { 0, { 0 } }, /* tuple */
+ { 0, { 0 }, IPPROTO_TCP } },
+ { { 0, { tcp: { port: 0xFFFF } } }, /* mask */
+ { 0, { 0 }, 0xFFFF } },
+ h225_nat_help, /* helper */
+ h225_nat_expected /* expectfn */
+ };
+
+static unsigned int
+h225_nat_expected(struct sk_buff **pskb,
+ unsigned int hooknum,
+ struct ip_conntrack *ct,
+ struct ip_nat_info *info)
+{
+ struct ip_nat_multi_range mr;
+ u_int32_t newdstip, newsrcip, newip;
+ u_int16_t port;
+ struct ip_ct_h225_expect *exp_info;
+ struct ip_ct_h225_master *master_info;
+ struct ip_conntrack *master = master_ct(ct);
+ unsigned int is_h225, ret;
+
+ IP_NF_ASSERT(info);
+ IP_NF_ASSERT(master);
+
+ IP_NF_ASSERT(!(info->initialized & (1<<HOOK2MANIP(hooknum))));
+
+ DEBUGP("h225_nat_expected: We have a connection!\n");
+ master_info = &ct->master->expectant->help.ct_h225_info;
+ exp_info = &ct->master->help.exp_h225_info;
+
+ LOCK_BH(&ip_h323_lock);
+
+ DEBUGP("master: ");
+ DUMP_TUPLE_RAW(&master->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
+ DUMP_TUPLE_RAW(&master->tuplehash[IP_CT_DIR_REPLY].tuple);
+ DEBUGP("conntrack: ");
+ DUMP_TUPLE_RAW(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
+ if (exp_info->dir == IP_CT_DIR_ORIGINAL) {
+ /* Make connection go to the client. */
+ newdstip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
+ newsrcip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
+ DEBUGP("h225_nat_expected: %u.%u.%u.%u->%u.%u.%u.%u (to client)\n",
+ NIPQUAD(newsrcip), NIPQUAD(newdstip));
+ } else {
+ /* Make the connection go to the server */
+ newdstip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip;
+ newsrcip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+ DEBUGP("h225_nat_expected: %u.%u.%u.%u->%u.%u.%u.%u (to server)\n",
+ NIPQUAD(newsrcip), NIPQUAD(newdstip));
+ }
+ port = exp_info->port;
+ is_h225 = master_info->is_h225 == H225_PORT;
+ UNLOCK_BH(&ip_h323_lock);
+
+ if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC)
+ newip = newsrcip;
+ else
+ newip = newdstip;
+
+ DEBUGP("h225_nat_expected: IP to %u.%u.%u.%u\n", NIPQUAD(newip));
+
+ mr.rangesize = 1;
+ /* We don't want to manip the per-protocol, just the IPs... */
+ mr.range[0].flags = IP_NAT_RANGE_MAP_IPS;
+ mr.range[0].min_ip = mr.range[0].max_ip = newip;
+
+ /* ... unless we're doing a MANIP_DST, in which case, make
+ sure we map to the correct port */
+ if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST) {
+ mr.range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
+ mr.range[0].min = mr.range[0].max
+ = ((union ip_conntrack_manip_proto)
+ { tcp: { port: port } });
+ }
+
+ ret = ip_nat_setup_info(ct, &mr, hooknum);
+
+ if (is_h225) {
+ DEBUGP("h225_nat_expected: H.225, setting NAT helper for %p\n", ct);
+ /* NAT expectfn called with ip_nat_lock write-locked */
+ info->helper = &h245;
+ }
+ return ret;
+}
+
+static int h323_signal_address_fixup(struct ip_conntrack *ct,
+ struct sk_buff **pskb,
+ enum ip_conntrack_info ctinfo)
+{
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct tcphdr *tcph = (void *)iph + iph->ihl*4;
+ char *data = (char *) tcph + tcph->doff * 4;
+ u_int32_t tcplen = (*pskb)->len - iph->ihl*4;
+ u_int32_t datalen = tcplen - tcph->doff*4;
+ struct ip_ct_h225_master *info = &ct->help.ct_h225_info;
+ u_int32_t newip;
+ u_int16_t port;
+ int i;
+
+ MUST_BE_LOCKED(&ip_h323_lock);
+
+ DEBUGP("h323_signal_address_fixup: %s %s\n",
+ between(info->seq[IP_CT_DIR_ORIGINAL], ntohl(tcph->seq), ntohl(tcph->seq) + datalen)
+ ? "yes" : "no",
+ between(info->seq[IP_CT_DIR_REPLY], ntohl(tcph->seq), ntohl(tcph->seq) + datalen)
+ ? "yes" : "no");
+ if (!(between(info->seq[IP_CT_DIR_ORIGINAL], ntohl(tcph->seq), ntohl(tcph->seq) + datalen)
+ || between(info->seq[IP_CT_DIR_REPLY], ntohl(tcph->seq), ntohl(tcph->seq) + datalen)))
+ return 1;
+
+ DEBUGP("h323_signal_address_fixup: offsets %u + 6 and %u + 6 in %u\n",
+ info->offset[IP_CT_DIR_ORIGINAL],
+ info->offset[IP_CT_DIR_REPLY],
+ tcplen);
+ DUMP_TUPLE_RAW(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
+ DUMP_TUPLE_RAW(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
+
+ for (i = 0; i < IP_CT_DIR_MAX; i++) {
+ DEBUGP("h323_signal_address_fixup: %s %s\n",
+ info->dir == IP_CT_DIR_ORIGINAL ? "original" : "reply",
+ i == IP_CT_DIR_ORIGINAL ? "caller" : "callee");
+ if (!between(info->seq[i], ntohl(tcph->seq),
+ ntohl(tcph->seq) + datalen))
+ continue;
+ if (!between(info->seq[i] + 6, ntohl(tcph->seq),
+ ntohl(tcph->seq) + datalen)) {
+ /* Partial retransmisison. It's a cracker being funky. */
+ if (net_ratelimit()) {
+ printk("H.323_NAT: partial packet %u/6 in %u/%u\n",
+ info->seq[i],
+ ntohl(tcph->seq),
+ ntohl(tcph->seq) + datalen);
+ }
+ return 0;
+ }
+
+ /* Change address inside packet to match way we're mapping
+ this connection. */
+ if (i == IP_CT_DIR_ORIGINAL) {
+ newip = ct->tuplehash[!info->dir].tuple.dst.ip;
+ port = ct->tuplehash[!info->dir].tuple.dst.u.tcp.port;
+ } else {
+ newip = ct->tuplehash[!info->dir].tuple.src.ip;
+ port = ct->tuplehash[!info->dir].tuple.src.u.tcp.port;
+ }
+
+ DEBUGP("h323_signal_address_fixup: orig %s IP:port %u.%u.%u.%u:%u\n",
+ i == IP_CT_DIR_ORIGINAL ? "source" : "dest ",
+ NIPQUAD(*((u_int32_t *)(data + info->offset[i]))),
+ ntohs(*((u_int16_t *)(data + info->offset[i] + 4))));
+
+ /* Modify the packet */
+ *(u_int32_t *)(data + info->offset[i]) = newip;
+ *(u_int16_t *)(data + info->offset[i] + 4) = port;
+
+ DEBUGP("h323_signal_address_fixup: new %s IP:port %u.%u.%u.%u:%u\n",
+ i == IP_CT_DIR_ORIGINAL ? "source" : "dest ",
+ NIPQUAD(*((u_int32_t *)(data + info->offset[i]))),
+ ntohs(*((u_int16_t *)(data + info->offset[i] + 4))));
+ }
+
+ /* fix checksum information */
+
+ (*pskb)->csum = csum_partial((char *)tcph + tcph->doff*4,
+ datalen, 0);
+
+ tcph->check = 0;
+ tcph->check = tcp_v4_check(tcph, tcplen, iph->saddr, iph->daddr,
+ csum_partial((char *)tcph, tcph->doff*4,
+ (*pskb)->csum));
+ ip_send_check(iph);
+
+ return 1;
+}
+
+static int h323_data_fixup(struct ip_ct_h225_expect *info,
+ struct ip_conntrack *ct,
+ struct sk_buff **pskb,
+ enum ip_conntrack_info ctinfo,
+ struct ip_conntrack_expect *expect)
+{
+ u_int32_t newip;
+ u_int16_t port;
+ struct ip_conntrack_tuple newtuple;
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct tcphdr *tcph = (void *)iph + iph->ihl*4;
+ char *data = (char *) tcph + tcph->doff * 4;
+ u_int32_t tcplen = (*pskb)->len - iph->ihl*4;
+ struct ip_ct_h225_master *master_info = &ct->help.ct_h225_info;
+ int is_h225;
+
+ MUST_BE_LOCKED(&ip_h323_lock);
+ DEBUGP("h323_data_fixup: offset %u + 6 in %u\n", info->offset, tcplen);
+ DUMP_TUPLE_RAW(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
+ DUMP_TUPLE_RAW(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
+
+ if (!between(expect->seq + 6, ntohl(tcph->seq),
+ ntohl(tcph->seq) + tcplen - tcph->doff * 4)) {
+ /* Partial retransmisison. It's a cracker being funky. */
+ if (net_ratelimit()) {
+ printk("H.323_NAT: partial packet %u/6 in %u/%u\n",
+ expect->seq,
+ ntohl(tcph->seq),
+ ntohl(tcph->seq) + tcplen - tcph->doff * 4);
+ }
+ return 0;
+ }
+
+ /* Change address inside packet to match way we're mapping
+ this connection. */
+ if (info->dir == IP_CT_DIR_REPLY) {
+ /* Must be where client thinks server is */
+ newip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
+ /* Expect something from client->server */
+ newtuple.src.ip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
+ newtuple.dst.ip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
+ } else {
+ /* Must be where server thinks client is */
+ newip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+ /* Expect something from server->client */
+ newtuple.src.ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip;
+ newtuple.dst.ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+ }
+
+ is_h225 = (master_info->is_h225 == H225_PORT);
+
+ if (is_h225) {
+ newtuple.dst.protonum = IPPROTO_TCP;
+ newtuple.src.u.tcp.port = expect->tuple.src.u.tcp.port;
+ } else {
+ newtuple.dst.protonum = IPPROTO_UDP;
+ newtuple.src.u.udp.port = expect->tuple.src.u.udp.port;
+ }
+
+ /* Try to get same port: if not, try to change it. */
+ for (port = ntohs(info->port); port != 0; port++) {
+ if (is_h225)
+ newtuple.dst.u.tcp.port = htons(port);
+ else
+ newtuple.dst.u.udp.port = htons(port);
+
+ if (ip_conntrack_change_expect(expect, &newtuple) == 0)
+ break;
+ }
+ if (port == 0) {
+ DEBUGP("h323_data_fixup: no free port found!\n");
+ return 0;
+ }
+
+ port = htons(port);
+
+ DEBUGP("h323_data_fixup: orig IP:port %u.%u.%u.%u:%u\n",
+ NIPQUAD(*((u_int32_t *)(data + info->offset))),
+ ntohs(*((u_int16_t *)(data + info->offset + 4))));
+
+ /* Modify the packet */
+ *(u_int32_t *)(data + info->offset) = newip;
+ *(u_int16_t *)(data + info->offset + 4) = port;
+
+ DEBUGP("h323_data_fixup: new IP:port %u.%u.%u.%u:%u\n",
+ NIPQUAD(*((u_int32_t *)(data + info->offset))),
+ ntohs(*((u_int16_t *)(data + info->offset + 4))));
+
+ /* fix checksum information */
+ /* FIXME: usually repeated multiple times in the case of H.245! */
+
+ (*pskb)->csum = csum_partial((char *)tcph + tcph->doff*4,
+ tcplen - tcph->doff*4, 0);
+
+ tcph->check = 0;
+ tcph->check = tcp_v4_check(tcph, tcplen, iph->saddr, iph->daddr,
+ csum_partial((char *)tcph, tcph->doff*4,
+ (*pskb)->csum));
+ ip_send_check(iph);
+
+ return 1;
+}
+
+static unsigned int h225_nat_help(struct ip_conntrack *ct,
+ struct ip_conntrack_expect *exp,
+ struct ip_nat_info *info,
+ enum ip_conntrack_info ctinfo,
+ unsigned int hooknum,
+ struct sk_buff **pskb)
+{
+ int dir;
+ struct ip_ct_h225_expect *exp_info;
+
+ /* Only mangle things once: original direction in POST_ROUTING
+ and reply direction on PRE_ROUTING. */
+ dir = CTINFO2DIR(ctinfo);
+ DEBUGP("nat_h323: dir %s at hook %s\n",
+ dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY",
+ hooknum == NF_IP_POST_ROUTING ? "POSTROUTING"
+ : hooknum == NF_IP_PRE_ROUTING ? "PREROUTING"
+ : hooknum == NF_IP_LOCAL_OUT ? "OUTPUT" : "???");
+ if (!((hooknum == NF_IP_POST_ROUTING && dir == IP_CT_DIR_ORIGINAL)
+ || (hooknum == NF_IP_PRE_ROUTING && dir == IP_CT_DIR_REPLY))) {
+ DEBUGP("nat_h323: Not touching dir %s at hook %s\n",
+ dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY",
+ hooknum == NF_IP_POST_ROUTING ? "POSTROUTING"
+ : hooknum == NF_IP_PRE_ROUTING ? "PREROUTING"
+ : hooknum == NF_IP_LOCAL_OUT ? "OUTPUT" : "???");
+ return NF_ACCEPT;
+ }
+
+ if (!exp) {
+ LOCK_BH(&ip_h323_lock);
+ if (!h323_signal_address_fixup(ct, pskb, ctinfo)) {
+ UNLOCK_BH(&ip_h323_lock);
+ return NF_DROP;
+ }
+ UNLOCK_BH(&ip_h323_lock);
+ return NF_ACCEPT;
+ }
+
+ exp_info = &exp->help.exp_h225_info;
+
+ LOCK_BH(&ip_h323_lock);
+ if (!h323_data_fixup(exp_info, ct, pskb, ctinfo, exp)) {
+ UNLOCK_BH(&ip_h323_lock);
+ return NF_DROP;
+ }
+ UNLOCK_BH(&ip_h323_lock);
+
+ return NF_ACCEPT;
+}
+
+static struct ip_nat_helper h225 =
+ { { NULL, NULL },
+ "H.225", /* name */
+ IP_NAT_HELPER_F_ALWAYS, /* flags */
+ THIS_MODULE, /* module */
+ { { 0, { tcp: { port: __constant_htons(H225_PORT) } } },
+ { 0, { 0 }, IPPROTO_TCP } }, /* tuple */
+ { { 0, { tcp: { port: 0xFFFF } } }, /* mask */
+ { 0, { 0 }, 0xFFFF } },
+ h225_nat_help, /* helper */
+ h225_nat_expected /* expectfn */
+ };
+
+static int __init init(void)
+{
+ int ret;
+
+ ret = ip_nat_helper_register(&h225);
+
+ if (ret != 0)
+ printk("ip_nat_h323: cannot initialize the module!\n");
+
+ return ret;
+}
+
+static void __exit fini(void)
+{
+ ip_nat_helper_unregister(&h225);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_helper.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_helper.c
new file mode 100644
index 0000000..6298d96
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_helper.c
@@ -0,0 +1,575 @@
+/* ip_nat_mangle.c - generic support functions for NAT helpers
+ *
+ * (C) 2000-2002 by Harald Welte <laforge@gnumonks.org>
+ *
+ * distributed under the terms of GNU GPL
+ *
+ * 14 Jan 2002 Harald Welte <laforge@gnumonks.org>:
+ * - add support for SACK adjustment
+ * 14 Mar 2002 Harald Welte <laforge@gnumonks.org>:
+ * - merge SACK support into newnat API
+ * 16 Aug 2002 Brian J. Murrell <netfilter@interlinx.bc.ca>:
+ * - make ip_nat_resize_packet more generic (TCP and UDP)
+ * - add ip_nat_mangle_udp_packet
+ */
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kmod.h>
+#include <linux/types.h>
+#include <linux/timer.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/brlock.h>
+#include <net/checksum.h>
+#include <net/icmp.h>
+#include <net/ip.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+
+#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_nat_lock)
+#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_nat_lock)
+
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_protocol.h>
+#include <linux/netfilter_ipv4/ip_nat_core.h>
+#include <linux/netfilter_ipv4/ip_nat_helper.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#if 0
+#define DEBUGP printk
+#define DUMP_OFFSET(x) printk("offset_before=%d, offset_after=%d, correction_pos=%u\n", x->offset_before, x->offset_after, x->correction_pos);
+#else
+#define DEBUGP(format, args...)
+#define DUMP_OFFSET(x)
+#endif
+
+DECLARE_LOCK(ip_nat_seqofs_lock);
+
+static inline int
+ip_nat_resize_packet(struct sk_buff **skb,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo,
+ int new_size)
+{
+ struct iphdr *iph;
+ int dir;
+ struct ip_nat_seq *this_way, *other_way;
+
+ DEBUGP("ip_nat_resize_packet: old_size = %u, new_size = %u\n",
+ (*skb)->len, new_size);
+
+ dir = CTINFO2DIR(ctinfo);
+
+ this_way = &ct->nat.info.seq[dir];
+ other_way = &ct->nat.info.seq[!dir];
+
+ if (new_size > (*skb)->len + skb_tailroom(*skb)) {
+ struct sk_buff *newskb;
+ newskb = skb_copy_expand(*skb, skb_headroom(*skb),
+ new_size - (*skb)->len,
+ GFP_ATOMIC);
+
+ if (!newskb) {
+ printk("ip_nat_resize_packet: oom\n");
+ return 0;
+ } else {
+ kfree_skb(*skb);
+ *skb = newskb;
+ }
+ }
+
+ iph = (*skb)->nh.iph;
+ if (iph->protocol == IPPROTO_TCP) {
+ struct tcphdr *tcph = (void *)iph + iph->ihl*4;
+
+ DEBUGP("ip_nat_resize_packet: Seq_offset before: ");
+ DUMP_OFFSET(this_way);
+
+ LOCK_BH(&ip_nat_seqofs_lock);
+
+ /* SYN adjust. If it's uninitialized, of this is after last
+ * correction, record it: we don't handle more than one
+ * adjustment in the window, but do deal with common case of a
+ * retransmit */
+ if (this_way->offset_before == this_way->offset_after
+ || before(this_way->correction_pos, ntohl(tcph->seq))) {
+ this_way->correction_pos = ntohl(tcph->seq);
+ this_way->offset_before = this_way->offset_after;
+ this_way->offset_after = (int32_t)
+ this_way->offset_before + new_size -
+ (*skb)->len;
+ }
+
+ UNLOCK_BH(&ip_nat_seqofs_lock);
+
+ DEBUGP("ip_nat_resize_packet: Seq_offset after: ");
+ DUMP_OFFSET(this_way);
+ }
+
+ return 1;
+}
+
+
+/* Generic function for mangling variable-length address changes inside
+ * NATed TCP connections (like the PORT XXX,XXX,XXX,XXX,XXX,XXX
+ * command in FTP).
+ *
+ * Takes care about all the nasty sequence number changes, checksumming,
+ * skb enlargement, ...
+ *
+ * */
+int
+ip_nat_mangle_tcp_packet(struct sk_buff **skb,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo,
+ unsigned int match_offset,
+ unsigned int match_len,
+ char *rep_buffer,
+ unsigned int rep_len)
+{
+ struct iphdr *iph = (*skb)->nh.iph;
+ struct tcphdr *tcph;
+ unsigned char *data;
+ u_int32_t tcplen, newlen, newtcplen;
+
+ tcplen = (*skb)->len - iph->ihl*4;
+ newtcplen = tcplen - match_len + rep_len;
+ newlen = iph->ihl*4 + newtcplen;
+
+ if (newlen > 65535) {
+ if (net_ratelimit())
+ printk("ip_nat_mangle_tcp_packet: nat'ed packet "
+ "exceeds maximum packet size\n");
+ return 0;
+ }
+
+ if ((*skb)->len != newlen) {
+ if (!ip_nat_resize_packet(skb, ct, ctinfo, newlen)) {
+ printk("resize_packet failed!!\n");
+ return 0;
+ }
+ }
+
+ /* Alexey says: if a hook changes _data_ ... it can break
+ original packet sitting in tcp queue and this is fatal */
+ if (skb_cloned(*skb)) {
+ struct sk_buff *nskb = skb_copy(*skb, GFP_ATOMIC);
+ if (!nskb) {
+ if (net_ratelimit())
+ printk("Out of memory cloning TCP packet\n");
+ return 0;
+ }
+ /* Rest of kernel will get very unhappy if we pass it
+ a suddenly-orphaned skbuff */
+ if ((*skb)->sk)
+ skb_set_owner_w(nskb, (*skb)->sk);
+ kfree_skb(*skb);
+ *skb = nskb;
+ }
+
+ /* skb may be copied !! */
+ iph = (*skb)->nh.iph;
+ tcph = (void *)iph + iph->ihl*4;
+ data = (void *)tcph + tcph->doff*4;
+
+ if (rep_len != match_len)
+ /* move post-replacement */
+ memmove(data + match_offset + rep_len,
+ data + match_offset + match_len,
+ (*skb)->tail - (data + match_offset + match_len));
+
+ /* insert data from buffer */
+ memcpy(data + match_offset, rep_buffer, rep_len);
+
+ /* update skb info */
+ if (newlen > (*skb)->len) {
+ DEBUGP("ip_nat_mangle_tcp_packet: Extending packet by "
+ "%u to %u bytes\n", newlen - (*skb)->len, newlen);
+ skb_put(*skb, newlen - (*skb)->len);
+ } else {
+ DEBUGP("ip_nat_mangle_tcp_packet: Shrinking packet from "
+ "%u to %u bytes\n", (*skb)->len, newlen);
+ skb_trim(*skb, newlen);
+ }
+
+ iph->tot_len = htons(newlen);
+ /* fix checksum information */
+ tcph->check = 0;
+ tcph->check = tcp_v4_check(tcph, newtcplen, iph->saddr, iph->daddr,
+ csum_partial((char *)tcph, newtcplen, 0));
+ ip_send_check(iph);
+
+ return 1;
+}
+
+/* Generic function for mangling variable-length address changes inside
+ * NATed UDP connections (like the CONNECT DATA XXXXX MESG XXXXX INDEX XXXXX
+ * command in the Amanda protocol)
+ *
+ * Takes care about all the nasty sequence number changes, checksumming,
+ * skb enlargement, ...
+ *
+ * XXX - This function could be merged with ip_nat_mangle_tcp_packet which
+ * should be fairly easy to do.
+ */
+int
+ip_nat_mangle_udp_packet(struct sk_buff **skb,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo,
+ unsigned int match_offset,
+ unsigned int match_len,
+ char *rep_buffer,
+ unsigned int rep_len)
+{
+ struct iphdr *iph = (*skb)->nh.iph;
+ struct udphdr *udph = (void *)iph + iph->ihl * 4;
+ unsigned char *data;
+ u_int32_t udplen, newlen, newudplen;
+
+ udplen = (*skb)->len - iph->ihl*4;
+ newudplen = udplen - match_len + rep_len;
+ newlen = iph->ihl*4 + newudplen;
+
+ /* UDP helpers might accidentally mangle the wrong packet */
+ if (udplen < sizeof(*udph) + match_offset + match_len) {
+ if (net_ratelimit())
+ printk("ip_nat_mangle_udp_packet: undersized packet\n");
+ return 0;
+ }
+
+ if (newlen > 65535) {
+ if (net_ratelimit())
+ printk("ip_nat_mangle_udp_packet: nat'ed packet "
+ "exceeds maximum packet size\n");
+ return 0;
+ }
+
+ if ((*skb)->len != newlen) {
+ if (!ip_nat_resize_packet(skb, ct, ctinfo, newlen)) {
+ printk("resize_packet failed!!\n");
+ return 0;
+ }
+ }
+
+ /* Alexey says: if a hook changes _data_ ... it can break
+ original packet sitting in tcp queue and this is fatal */
+ if (skb_cloned(*skb)) {
+ struct sk_buff *nskb = skb_copy(*skb, GFP_ATOMIC);
+ if (!nskb) {
+ if (net_ratelimit())
+ printk("Out of memory cloning TCP packet\n");
+ return 0;
+ }
+ /* Rest of kernel will get very unhappy if we pass it
+ a suddenly-orphaned skbuff */
+ if ((*skb)->sk)
+ skb_set_owner_w(nskb, (*skb)->sk);
+ kfree_skb(*skb);
+ *skb = nskb;
+ }
+
+ /* skb may be copied !! */
+ iph = (*skb)->nh.iph;
+ udph = (void *)iph + iph->ihl*4;
+ data = (void *)udph + sizeof(struct udphdr);
+
+ if (rep_len != match_len)
+ /* move post-replacement */
+ memmove(data + match_offset + rep_len,
+ data + match_offset + match_len,
+ (*skb)->tail - (data + match_offset + match_len));
+
+ /* insert data from buffer */
+ memcpy(data + match_offset, rep_buffer, rep_len);
+
+ /* update skb info */
+ if (newlen > (*skb)->len) {
+ DEBUGP("ip_nat_mangle_udp_packet: Extending packet by "
+ "%u to %u bytes\n", newlen - (*skb)->len, newlen);
+ skb_put(*skb, newlen - (*skb)->len);
+ } else {
+ DEBUGP("ip_nat_mangle_udp_packet: Shrinking packet from "
+ "%u to %u bytes\n", (*skb)->len, newlen);
+ skb_trim(*skb, newlen);
+ }
+
+ /* update the length of the UDP and IP packets to the new values*/
+ udph->len = htons((*skb)->len - iph->ihl*4);
+ iph->tot_len = htons(newlen);
+
+ /* fix udp checksum if udp checksum was previously calculated */
+ if (udph->check != 0) {
+ udph->check = 0;
+ udph->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
+ newudplen, IPPROTO_UDP,
+ csum_partial((char *)udph,
+ newudplen, 0));
+ }
+
+ ip_send_check(iph);
+
+ return 1;
+}
+
+/* Adjust one found SACK option including checksum correction */
+static void
+sack_adjust(struct tcphdr *tcph,
+ unsigned char *ptr,
+ struct ip_nat_seq *natseq)
+{
+ struct tcp_sack_block *sp = (struct tcp_sack_block *)(ptr+2);
+ int num_sacks = (ptr[1] - TCPOLEN_SACK_BASE)>>3;
+ int i;
+
+ for (i = 0; i < num_sacks; i++, sp++) {
+ u_int32_t new_start_seq, new_end_seq;
+
+ if (after(ntohl(sp->start_seq) - natseq->offset_before,
+ natseq->correction_pos))
+ new_start_seq = ntohl(sp->start_seq)
+ - natseq->offset_after;
+ else
+ new_start_seq = ntohl(sp->start_seq)
+ - natseq->offset_before;
+ new_start_seq = htonl(new_start_seq);
+
+ if (after(ntohl(sp->end_seq) - natseq->offset_before,
+ natseq->correction_pos))
+ new_end_seq = ntohl(sp->end_seq)
+ - natseq->offset_after;
+ else
+ new_end_seq = ntohl(sp->end_seq)
+ - natseq->offset_before;
+ new_end_seq = htonl(new_end_seq);
+
+ DEBUGP("sack_adjust: start_seq: %d->%d, end_seq: %d->%d\n",
+ ntohl(sp->start_seq), new_start_seq,
+ ntohl(sp->end_seq), new_end_seq);
+
+ tcph->check =
+ ip_nat_cheat_check(~sp->start_seq, new_start_seq,
+ ip_nat_cheat_check(~sp->end_seq,
+ new_end_seq,
+ tcph->check));
+
+ sp->start_seq = new_start_seq;
+ sp->end_seq = new_end_seq;
+ }
+}
+
+
+/* TCP SACK sequence number adjustment. */
+static inline void
+ip_nat_sack_adjust(struct sk_buff *skb,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo)
+{
+ struct tcphdr *tcph;
+ unsigned char *ptr, *optend;
+ unsigned int dir;
+
+ tcph = (void *)skb->nh.iph + skb->nh.iph->ihl*4;
+ optend = (unsigned char *)tcph + tcph->doff*4;
+ ptr = (unsigned char *)(tcph+1);
+
+ dir = CTINFO2DIR(ctinfo);
+
+ while (ptr < optend) {
+ int opcode = ptr[0];
+ int opsize;
+
+ switch (opcode) {
+ case TCPOPT_EOL:
+ return;
+ case TCPOPT_NOP:
+ ptr++;
+ continue;
+ default:
+ opsize = ptr[1];
+ /* no partial opts */
+ if (ptr + opsize > optend || opsize < 2)
+ return;
+ if (opcode == TCPOPT_SACK) {
+ /* found SACK */
+ if((opsize >= (TCPOLEN_SACK_BASE
+ +TCPOLEN_SACK_PERBLOCK)) &&
+ !((opsize - TCPOLEN_SACK_BASE)
+ % TCPOLEN_SACK_PERBLOCK))
+ sack_adjust(tcph, ptr,
+ &ct->nat.info.seq[!dir]);
+ }
+ ptr += opsize;
+ }
+ }
+}
+
+/* TCP sequence number adjustment */
+int
+ip_nat_seq_adjust(struct sk_buff *skb,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo)
+{
+ struct iphdr *iph;
+ struct tcphdr *tcph;
+ int dir, newseq, newack;
+ struct ip_nat_seq *this_way, *other_way;
+
+ iph = skb->nh.iph;
+ tcph = (void *)iph + iph->ihl*4;
+
+ dir = CTINFO2DIR(ctinfo);
+
+ this_way = &ct->nat.info.seq[dir];
+ other_way = &ct->nat.info.seq[!dir];
+
+ if (after(ntohl(tcph->seq), this_way->correction_pos))
+ newseq = ntohl(tcph->seq) + this_way->offset_after;
+ else
+ newseq = ntohl(tcph->seq) + this_way->offset_before;
+ newseq = htonl(newseq);
+
+ if (after(ntohl(tcph->ack_seq) - other_way->offset_before,
+ other_way->correction_pos))
+ newack = ntohl(tcph->ack_seq) - other_way->offset_after;
+ else
+ newack = ntohl(tcph->ack_seq) - other_way->offset_before;
+ newack = htonl(newack);
+
+ tcph->check = ip_nat_cheat_check(~tcph->seq, newseq,
+ ip_nat_cheat_check(~tcph->ack_seq,
+ newack,
+ tcph->check));
+
+ DEBUGP("Adjusting sequence number from %u->%u, ack from %u->%u\n",
+ ntohl(tcph->seq), ntohl(newseq), ntohl(tcph->ack_seq),
+ ntohl(newack));
+
+ tcph->seq = newseq;
+ tcph->ack_seq = newack;
+
+ ip_nat_sack_adjust(skb, ct, ctinfo);
+
+ return 0;
+}
+
+static inline int
+helper_cmp(const struct ip_nat_helper *helper,
+ const struct ip_conntrack_tuple *tuple)
+{
+ return ip_ct_tuple_mask_cmp(tuple, &helper->tuple, &helper->mask);
+}
+
+#define MODULE_MAX_NAMELEN 32
+
+int ip_nat_helper_register(struct ip_nat_helper *me)
+{
+ int ret = 0;
+
+ if (me->me && !(me->flags & IP_NAT_HELPER_F_STANDALONE)) {
+ struct ip_conntrack_helper *ct_helper;
+
+ if ((ct_helper = ip_ct_find_helper(&me->tuple))
+ && ct_helper->me) {
+ __MOD_INC_USE_COUNT(ct_helper->me);
+ } else {
+
+ /* We are a NAT helper for protocol X. If we need
+ * respective conntrack helper for protoccol X, compute
+ * conntrack helper name and try to load module */
+ char name[MODULE_MAX_NAMELEN];
+ const char *tmp = me->me->name;
+
+ if (strlen(tmp) + 6 > MODULE_MAX_NAMELEN) {
+ printk("%s: unable to "
+ "compute conntrack helper name "
+ "from %s\n", __FUNCTION__, tmp);
+ return -EBUSY;
+ }
+ tmp += 6;
+ sprintf(name, "ip_conntrack%s", tmp);
+#ifdef CONFIG_KMOD
+ if (!request_module(name)
+ && (ct_helper = ip_ct_find_helper(&me->tuple))
+ && ct_helper->me) {
+ __MOD_INC_USE_COUNT(ct_helper->me);
+ } else {
+ printk("unable to load module %s\n", name);
+ return -EBUSY;
+ }
+#else
+ printk("unable to load module %s automatically "
+ "because kernel was compiled without kernel "
+ "module loader support\n", name);
+ return -EBUSY;
+#endif
+ }
+ }
+ WRITE_LOCK(&ip_nat_lock);
+ if (LIST_FIND(&helpers, helper_cmp, struct ip_nat_helper *,&me->tuple))
+ ret = -EBUSY;
+ else {
+ list_prepend(&helpers, me);
+ MOD_INC_USE_COUNT;
+ }
+ WRITE_UNLOCK(&ip_nat_lock);
+
+ return ret;
+}
+
+static int
+kill_helper(struct ip_conntrack *i, void *helper)
+{
+ int ret;
+
+ READ_LOCK(&ip_nat_lock);
+ ret = (i->nat.info.helper == helper);
+ READ_UNLOCK(&ip_nat_lock);
+
+ return ret;
+}
+
+void ip_nat_helper_unregister(struct ip_nat_helper *me)
+{
+ int found = 0;
+
+ WRITE_LOCK(&ip_nat_lock);
+ /* Autoloading conntrack helper might have failed */
+ if (LIST_FIND(&helpers, helper_cmp, struct ip_nat_helper *,&me->tuple)) {
+ LIST_DELETE(&helpers, me);
+ found = 1;
+ }
+ WRITE_UNLOCK(&ip_nat_lock);
+
+ /* Someone could be still looking at the helper in a bh. */
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+
+ /* Find anything using it, and umm, kill them. We can't turn
+ them into normal connections: if we've adjusted SYNs, then
+ they'll ackstorm. So we just drop it. We used to just
+ bump module count when a connection existed, but that
+ forces admins to gen fake RSTs or bounce box, either of
+ which is just a long-winded way of making things
+ worse. --RR */
+ ip_ct_iterate_cleanup(kill_helper, me);
+
+ if (found)
+ MOD_DEC_USE_COUNT;
+
+ /* If we are no standalone NAT helper, we need to decrement usage count
+ * on our conntrack helper */
+ if (me->me && !(me->flags & IP_NAT_HELPER_F_STANDALONE)) {
+ struct ip_conntrack_helper *ct_helper;
+
+ if ((ct_helper = ip_ct_find_helper(&me->tuple))
+ && ct_helper->me) {
+ __MOD_DEC_USE_COUNT(ct_helper->me);
+ } else
+ printk("%s: unable to decrement usage count"
+ " of conntrack helper %s\n",
+ __FUNCTION__, me->me->name);
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_irc.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_irc.c
new file mode 100644
index 0000000..4b11602
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_irc.c
@@ -0,0 +1,265 @@
+/* IRC extension for TCP NAT alteration.
+ * (C) 2000-2001 by Harald Welte <laforge@gnumonks.org>
+ * based on a copy of RR's ip_nat_ftp.c
+ *
+ * ip_nat_irc.c,v 1.16 2001/12/06 07:42:10 laforge Exp
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Module load syntax:
+ * insmod ip_nat_irc.o ports=port1,port2,...port<MAX_PORTS>
+ *
+ * please give the ports of all IRC servers You wish to connect to.
+ * If You don't specify ports, the default will be port 6667
+ */
+
+#include <linux/module.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/kernel.h>
+#include <net/tcp.h>
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_helper.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+#include <linux/netfilter_ipv4/ip_conntrack_irc.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+#define MAX_PORTS 8
+static int ports[MAX_PORTS];
+static int ports_c = 0;
+
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("IRC (DCC) network address translation module");
+MODULE_LICENSE("GPL");
+#ifdef MODULE_PARM
+MODULE_PARM(ports, "1-" __MODULE_STRING(MAX_PORTS) "i");
+MODULE_PARM_DESC(ports, "port numbers of IRC servers");
+#endif
+
+/* FIXME: Time out? --RR */
+
+static unsigned int
+irc_nat_expected(struct sk_buff **pskb,
+ unsigned int hooknum,
+ struct ip_conntrack *ct,
+ struct ip_nat_info *info)
+{
+ struct ip_nat_multi_range mr;
+ u_int32_t newdstip, newsrcip, newip;
+
+ struct ip_conntrack *master = master_ct(ct);
+
+ IP_NF_ASSERT(info);
+ IP_NF_ASSERT(master);
+
+ IP_NF_ASSERT(!(info->initialized & (1 << HOOK2MANIP(hooknum))));
+
+ DEBUGP("nat_expected: We have a connection!\n");
+
+ newdstip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
+ newsrcip = ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
+ DEBUGP("nat_expected: DCC cmd. %u.%u.%u.%u->%u.%u.%u.%u\n",
+ NIPQUAD(newsrcip), NIPQUAD(newdstip));
+
+ if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC)
+ newip = newsrcip;
+ else
+ newip = newdstip;
+
+ DEBUGP("nat_expected: IP to %u.%u.%u.%u\n", NIPQUAD(newip));
+
+ mr.rangesize = 1;
+ /* We don't want to manip the per-protocol, just the IPs. */
+ mr.range[0].flags = IP_NAT_RANGE_MAP_IPS;
+ mr.range[0].min_ip = mr.range[0].max_ip = newip;
+
+ return ip_nat_setup_info(ct, &mr, hooknum);
+}
+
+static int irc_data_fixup(const struct ip_ct_irc_expect *exp_irc_info,
+ struct ip_conntrack *ct,
+ struct sk_buff **pskb,
+ enum ip_conntrack_info ctinfo,
+ struct ip_conntrack_expect *expect)
+{
+ u_int32_t newip;
+ struct ip_conntrack_tuple t;
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct tcphdr *tcph = (void *) iph + iph->ihl * 4;
+ u_int16_t port;
+
+ /* "4294967296 65635 " */
+ char buffer[18];
+
+ DEBUGP("IRC_NAT: info (seq %u + %u) in %u\n",
+ expect->seq, exp_irc_info->len,
+ ntohl(tcph->seq));
+
+ newip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+
+ /* Alter conntrack's expectations. */
+ t = expect->tuple;
+ t.dst.ip = newip;
+ for (port = exp_irc_info->port; port != 0; port++) {
+ t.dst.u.tcp.port = htons(port);
+ if (ip_conntrack_change_expect(expect, &t) == 0) {
+ DEBUGP("using port %d", port);
+ break;
+ }
+
+ }
+ if (port == 0)
+ return 0;
+
+ /* strlen("\1DCC CHAT chat AAAAAAAA P\1\n")=27
+ * strlen("\1DCC SCHAT chat AAAAAAAA P\1\n")=28
+ * strlen("\1DCC SEND F AAAAAAAA P S\1\n")=26
+ * strlen("\1DCC MOVE F AAAAAAAA P S\1\n")=26
+ * strlen("\1DCC TSEND F AAAAAAAA P S\1\n")=27
+ * AAAAAAAAA: bound addr (1.0.0.0==16777216, min 8 digits,
+ * 255.255.255.255==4294967296, 10 digits)
+ * P: bound port (min 1 d, max 5d (65635))
+ * F: filename (min 1 d )
+ * S: size (min 1 d )
+ * 0x01, \n: terminators
+ */
+
+ sprintf(buffer, "%u %u", ntohl(newip), port);
+ DEBUGP("ip_nat_irc: Inserting '%s' == %u.%u.%u.%u, port %u\n",
+ buffer, NIPQUAD(newip), port);
+
+ return ip_nat_mangle_tcp_packet(pskb, ct, ctinfo,
+ expect->seq - ntohl(tcph->seq),
+ exp_irc_info->len, buffer,
+ strlen(buffer));
+}
+
+static unsigned int help(struct ip_conntrack *ct,
+ struct ip_conntrack_expect *exp,
+ struct ip_nat_info *info,
+ enum ip_conntrack_info ctinfo,
+ unsigned int hooknum,
+ struct sk_buff **pskb)
+{
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct tcphdr *tcph = (void *) iph + iph->ihl * 4;
+ unsigned int datalen;
+ int dir;
+ struct ip_ct_irc_expect *exp_irc_info;
+
+ if (!exp)
+ DEBUGP("ip_nat_irc: no exp!!");
+
+ exp_irc_info = &exp->help.exp_irc_info;
+
+ /* Only mangle things once: original direction in POST_ROUTING
+ and reply direction on PRE_ROUTING. */
+ dir = CTINFO2DIR(ctinfo);
+ if (!((hooknum == NF_IP_POST_ROUTING && dir == IP_CT_DIR_ORIGINAL)
+ || (hooknum == NF_IP_PRE_ROUTING && dir == IP_CT_DIR_REPLY))) {
+ DEBUGP("nat_irc: Not touching dir %s at hook %s\n",
+ dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY",
+ hooknum == NF_IP_POST_ROUTING ? "POSTROUTING"
+ : hooknum == NF_IP_PRE_ROUTING ? "PREROUTING"
+ : hooknum == NF_IP_LOCAL_OUT ? "OUTPUT" : "???");
+ return NF_ACCEPT;
+ }
+ DEBUGP("got beyond not touching\n");
+
+ datalen = (*pskb)->len - iph->ihl * 4 - tcph->doff * 4;
+ /* Check wether the whole IP/address pattern is carried in the payload */
+ if (between(exp->seq + exp_irc_info->len,
+ ntohl(tcph->seq),
+ ntohl(tcph->seq) + datalen)) {
+ if (!irc_data_fixup(exp_irc_info, ct, pskb, ctinfo, exp))
+ return NF_DROP;
+ } else {
+ /* Half a match? This means a partial retransmisison.
+ It's a cracker being funky. */
+ if (net_ratelimit()) {
+ printk
+ ("IRC_NAT: partial packet %u/%u in %u/%u\n",
+ exp->seq, exp_irc_info->len,
+ ntohl(tcph->seq),
+ ntohl(tcph->seq) + datalen);
+ }
+ return NF_DROP;
+ }
+ return NF_ACCEPT;
+}
+
+static struct ip_nat_helper ip_nat_irc_helpers[MAX_PORTS];
+static char irc_names[MAX_PORTS][10];
+
+/* This function is intentionally _NOT_ defined as __exit, because
+ * it is needed by init() */
+static void fini(void)
+{
+ int i;
+
+ for (i = 0; i < ports_c; i++) {
+ DEBUGP("ip_nat_irc: unregistering helper for port %d\n",
+ ports[i]);
+ ip_nat_helper_unregister(&ip_nat_irc_helpers[i]);
+ }
+}
+
+static int __init init(void)
+{
+ int ret = 0;
+ int i;
+ struct ip_nat_helper *hlpr;
+ char *tmpname;
+
+ if (ports[0] == 0) {
+ ports[0] = IRC_PORT;
+ }
+
+ for (i = 0; (i < MAX_PORTS) && ports[i] != 0; i++) {
+ hlpr = &ip_nat_irc_helpers[i];
+ hlpr->tuple.dst.protonum = IPPROTO_TCP;
+ hlpr->tuple.src.u.tcp.port = htons(ports[i]);
+ hlpr->mask.src.u.tcp.port = 0xFFFF;
+ hlpr->mask.dst.protonum = 0xFFFF;
+ hlpr->help = help;
+ hlpr->flags = 0;
+ hlpr->me = THIS_MODULE;
+ hlpr->expect = irc_nat_expected;
+
+ tmpname = &irc_names[i][0];
+ if (ports[i] == IRC_PORT)
+ sprintf(tmpname, "irc");
+ else
+ sprintf(tmpname, "irc-%d", i);
+ hlpr->name = tmpname;
+
+ DEBUGP
+ ("ip_nat_irc: Trying to register helper for port %d: name %s\n",
+ ports[i], hlpr->name);
+ ret = ip_nat_helper_register(hlpr);
+
+ if (ret) {
+ printk
+ ("ip_nat_irc: error registering helper for port %d\n",
+ ports[i]);
+ fini();
+ return 1;
+ }
+ ports_c++;
+ }
+ return ret;
+}
+
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_pptp.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_pptp.c
new file mode 100644
index 0000000..359588b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_pptp.c
@@ -0,0 +1,478 @@
+/*
+ * ip_nat_pptp.c - Version $Revision: 1.4 $
+ *
+ * NAT support for PPTP (Point to Point Tunneling Protocol).
+ * PPTP is a a protocol for creating virtual private networks.
+ * It is a specification defined by Microsoft and some vendors
+ * working with Microsoft. PPTP is built on top of a modified
+ * version of the Internet Generic Routing Encapsulation Protocol.
+ * GRE is defined in RFC 1701 and RFC 1702. Documentation of
+ * PPTP can be found in RFC 2637
+ *
+ * (C) 2000-2003 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Development of this code funded by Astaro AG (http://www.astaro.com/)
+ *
+ * TODO: - Support for multiple calls within one session
+ * (needs netfilter newnat code)
+ * - NAT to a unique tuple, not to TCP source port
+ * (needs netfilter tuple reservation)
+ *
+ * Changes:
+ * 2002-02-10 - Version 1.3
+ * - Use ip_nat_mangle_tcp_packet() because of cloned skb's
+ * in local connections (Philip Craig <philipc@snapgear.com>)
+ * - add checks for magicCookie and pptp version
+ * - make argument list of pptp_{out,in}bound_packet() shorter
+ * - move to C99 style initializers
+ * - print version number at module loadtime
+ * 2003-09-22 - Version 1.5
+ * - use SNATed tcp sourceport as callid, since we get called before
+ * TCP header is mangled (Philip Craig <philipc@snapgear.com>)
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <net/tcp.h>
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+#include <linux/netfilter_ipv4/ip_nat_helper.h>
+#include <linux/netfilter_ipv4/ip_nat_pptp.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h>
+#include <linux/netfilter_ipv4/ip_conntrack_pptp.h>
+
+#define IP_NAT_PPTP_VERSION "$Revision: 1.4 $"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("Netfilter NAT helper module for PPTP");
+
+
+#if 0
+#include "ip_conntrack_pptp_priv.h"
+#define DEBUGP(format, args...) printk(KERN_DEBUG __FILE__ ":" __FUNCTION__ \
+ ": " format, ## args)
+#else
+#define DEBUGP(format, args...)
+#endif
+
+static unsigned int
+pptp_nat_expected(struct sk_buff **pskb,
+ unsigned int hooknum,
+ struct ip_conntrack *ct,
+ struct ip_nat_info *info)
+{
+ struct ip_conntrack *master = master_ct(ct);
+ struct ip_nat_multi_range mr;
+ struct ip_ct_pptp_master *ct_pptp_info;
+ struct ip_nat_pptp *nat_pptp_info;
+ u_int32_t newip, newcid;
+ int ret;
+
+ IP_NF_ASSERT(info);
+ IP_NF_ASSERT(master);
+ IP_NF_ASSERT(!(info->initialized & (1 << HOOK2MANIP(hooknum))));
+
+ DEBUGP("we have a connection!\n");
+
+ LOCK_BH(&ip_pptp_lock);
+ ct_pptp_info = &master->help.ct_pptp_info;
+ nat_pptp_info = &master->nat.help.nat_pptp_info;
+
+ /* need to alter GRE tuple because conntrack expectfn() used 'wrong'
+ * (unmanipulated) values */
+ if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST) {
+ DEBUGP("completing tuples with NAT info \n");
+ /* we can do this, since we're unconfirmed */
+ if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.gre.key ==
+ htonl(ct_pptp_info->pac_call_id)) {
+ /* assume PNS->PAC */
+ ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.gre.key =
+ htonl(nat_pptp_info->pns_call_id);
+ ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.gre.key =
+ htonl(nat_pptp_info->pns_call_id);
+ newip = master->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip;
+ newcid = htonl(nat_pptp_info->pac_call_id);
+ } else {
+ /* assume PAC->PNS */
+ ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.u.gre.key =
+ htonl(nat_pptp_info->pac_call_id);
+ ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.gre.key =
+ htonl(nat_pptp_info->pac_call_id);
+ newip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip;
+ newcid = htonl(nat_pptp_info->pns_call_id);
+ }
+ } else {
+ if (ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.u.gre.key ==
+ htonl(ct_pptp_info->pac_call_id)) {
+ /* assume PNS->PAC */
+ newip = master->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+ newcid = htonl(ct_pptp_info->pns_call_id);
+ }
+ else {
+ /* assume PAC->PNS */
+ newip = master->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip;
+ newcid = htonl(ct_pptp_info->pac_call_id);
+ }
+ }
+
+ mr.rangesize = 1;
+ mr.range[0].flags = IP_NAT_RANGE_MAP_IPS | IP_NAT_RANGE_PROTO_SPECIFIED;
+ mr.range[0].min_ip = mr.range[0].max_ip = newip;
+ mr.range[0].min = mr.range[0].max =
+ ((union ip_conntrack_manip_proto ) { newcid });
+ DEBUGP("change ip to %u.%u.%u.%u\n",
+ NIPQUAD(newip));
+ DEBUGP("change key to 0x%x\n", ntohl(newcid));
+ ret = ip_nat_setup_info(ct, &mr, hooknum);
+
+ UNLOCK_BH(&ip_pptp_lock);
+
+ return ret;
+
+}
+
+/* outbound packets == from PNS to PAC */
+static inline unsigned int
+pptp_outbound_pkt(struct sk_buff **pskb,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo,
+ struct ip_conntrack_expect *exp)
+
+{
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct tcphdr *tcph = (void *) iph + iph->ihl*4;
+ struct pptp_pkt_hdr *pptph = (struct pptp_pkt_hdr *)
+ ((void *)tcph + tcph->doff*4);
+
+ struct PptpControlHeader *ctlh;
+ union pptp_ctrl_union pptpReq;
+ struct ip_ct_pptp_master *ct_pptp_info = &ct->help.ct_pptp_info;
+ struct ip_nat_pptp *nat_pptp_info = &ct->nat.help.nat_pptp_info;
+
+ u_int16_t msg, *cid = NULL, new_callid;
+
+ /* FIXME: size checks !!! */
+ ctlh = (struct PptpControlHeader *) ((void *) pptph + sizeof(*pptph));
+ pptpReq.rawreq = (void *) ((void *) ctlh + sizeof(*ctlh));
+
+ new_callid = htons(ct_pptp_info->pns_call_id);
+
+ switch (msg = ntohs(ctlh->messageType)) {
+ case PPTP_OUT_CALL_REQUEST:
+ cid = &pptpReq.ocreq->callID;
+ /* FIXME: ideally we would want to reserve a call ID
+ * here. current netfilter NAT core is not able to do
+ * this :( For now we use TCP source port. This breaks
+ * multiple calls within one control session */
+
+ /* save original call ID in nat_info */
+ nat_pptp_info->pns_call_id = ct_pptp_info->pns_call_id;
+
+ /* don't use tcph->source since we are at a DSTmanip
+ * hook (e.g. PREROUTING) and pkt is not mangled yet */
+ new_callid = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.u.tcp.port;
+
+ /* save new call ID in ct info */
+ ct_pptp_info->pns_call_id = ntohs(new_callid);
+ break;
+ case PPTP_IN_CALL_REPLY:
+ cid = &pptpReq.icreq->callID;
+ break;
+ case PPTP_CALL_CLEAR_REQUEST:
+ cid = &pptpReq.clrreq->callID;
+ break;
+ default:
+ DEBUGP("unknown outbound packet 0x%04x:%s\n", msg,
+ (msg <= PPTP_MSG_MAX)? strMName[msg]:strMName[0]);
+ /* fall through */
+
+ case PPTP_SET_LINK_INFO:
+ /* only need to NAT in case PAC is behind NAT box */
+ case PPTP_START_SESSION_REQUEST:
+ case PPTP_START_SESSION_REPLY:
+ case PPTP_STOP_SESSION_REQUEST:
+ case PPTP_STOP_SESSION_REPLY:
+ case PPTP_ECHO_REQUEST:
+ case PPTP_ECHO_REPLY:
+ /* no need to alter packet */
+ return NF_ACCEPT;
+ }
+
+ IP_NF_ASSERT(cid);
+
+ DEBUGP("altering call id from 0x%04x to 0x%04x\n",
+ ntohs(*cid), ntohs(new_callid));
+
+ /* mangle packet */
+ ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, (void *)cid - (void *)pptph,
+ sizeof(new_callid), (char *)&new_callid,
+ sizeof(new_callid));
+
+ return NF_ACCEPT;
+}
+
+/* inbound packets == from PAC to PNS */
+static inline unsigned int
+pptp_inbound_pkt(struct sk_buff **pskb,
+ struct ip_conntrack *ct,
+ enum ip_conntrack_info ctinfo,
+ struct ip_conntrack_expect *oldexp)
+{
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct tcphdr *tcph = (void *) iph + iph->ihl*4;
+ struct pptp_pkt_hdr *pptph = (struct pptp_pkt_hdr *)
+ ((void *)tcph + tcph->doff*4);
+
+ struct PptpControlHeader *ctlh;
+ union pptp_ctrl_union pptpReq;
+ struct ip_ct_pptp_master *ct_pptp_info = &ct->help.ct_pptp_info;
+ struct ip_nat_pptp *nat_pptp_info = &ct->nat.help.nat_pptp_info;
+
+ u_int16_t msg, new_cid = 0, new_pcid, *pcid = NULL, *cid = NULL;
+ u_int32_t old_dst_ip;
+
+ struct ip_conntrack_tuple t, inv_t;
+ struct ip_conntrack_tuple *orig_t, *reply_t;
+
+ /* FIXME: size checks !!! */
+ ctlh = (struct PptpControlHeader *) ((void *) pptph + sizeof(*pptph));
+ pptpReq.rawreq = (void *) ((void *) ctlh + sizeof(*ctlh));
+
+ new_pcid = htons(nat_pptp_info->pns_call_id);
+
+ switch (msg = ntohs(ctlh->messageType)) {
+ case PPTP_OUT_CALL_REPLY:
+ pcid = &pptpReq.ocack->peersCallID;
+ cid = &pptpReq.ocack->callID;
+ if (!oldexp) {
+ DEBUGP("outcall but no expectation\n");
+ break;
+ }
+ old_dst_ip = oldexp->tuple.dst.ip;
+ t = oldexp->tuple;
+ invert_tuplepr(&inv_t, &t);
+
+ /* save original PAC call ID in nat_info */
+ nat_pptp_info->pac_call_id = ct_pptp_info->pac_call_id;
+
+ /* alter expectation */
+ orig_t = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
+ reply_t = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;
+ if (t.src.ip == orig_t->src.ip && t.dst.ip == orig_t->dst.ip) {
+ /* expectation for PNS->PAC direction */
+ t.src.u.gre.key = htonl(nat_pptp_info->pns_call_id);
+ t.dst.u.gre.key = htonl(ct_pptp_info->pac_call_id);
+ inv_t.src.ip = reply_t->src.ip;
+ inv_t.dst.ip = reply_t->dst.ip;
+ inv_t.src.u.gre.key = htonl(nat_pptp_info->pac_call_id);
+ inv_t.dst.u.gre.key = htonl(ct_pptp_info->pns_call_id);
+ } else {
+ /* expectation for PAC->PNS direction */
+ t.src.u.gre.key = htonl(nat_pptp_info->pac_call_id);
+ t.dst.u.gre.key = htonl(ct_pptp_info->pns_call_id);
+ inv_t.src.ip = orig_t->src.ip;
+ inv_t.dst.ip = orig_t->dst.ip;
+ inv_t.src.u.gre.key = htonl(nat_pptp_info->pns_call_id);
+ inv_t.dst.u.gre.key = htonl(ct_pptp_info->pac_call_id);
+ }
+
+ if (!ip_conntrack_change_expect(oldexp, &t)) {
+ DEBUGP("successfully changed expect\n");
+ } else {
+ DEBUGP("can't change expect\n");
+ }
+ ip_ct_gre_keymap_change(oldexp->proto.gre.keymap_orig, &t);
+ ip_ct_gre_keymap_change(oldexp->proto.gre.keymap_reply, &inv_t);
+ break;
+ case PPTP_IN_CALL_CONNECT:
+ pcid = &pptpReq.iccon->peersCallID;
+ if (!oldexp)
+ break;
+ old_dst_ip = oldexp->tuple.dst.ip;
+ t = oldexp->tuple;
+
+ /* alter expectation, no need for callID */
+ if (t.dst.ip == ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip) {
+ /* expectation for PNS->PAC direction */
+ t.src.ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+ } else {
+ /* expectation for PAC->PNS direction */
+ t.dst.ip = ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip;
+ }
+
+ if (!ip_conntrack_change_expect(oldexp, &t)) {
+ DEBUGP("successfully changed expect\n");
+ } else {
+ DEBUGP("can't change expect\n");
+ }
+ break;
+ case PPTP_IN_CALL_REQUEST:
+ /* only need to nat in case PAC is behind NAT box */
+ break;
+ case PPTP_WAN_ERROR_NOTIFY:
+ pcid = &pptpReq.wanerr->peersCallID;
+ break;
+ case PPTP_CALL_DISCONNECT_NOTIFY:
+ pcid = &pptpReq.disc->callID;
+ break;
+ case PPTP_SET_LINK_INFO:
+ pcid = &pptpReq.setlink->peersCallID;
+ break;
+
+ default:
+ DEBUGP("unknown inbound packet %s\n",
+ (msg <= PPTP_MSG_MAX)? strMName[msg]:strMName[0]);
+ /* fall through */
+
+ case PPTP_START_SESSION_REQUEST:
+ case PPTP_START_SESSION_REPLY:
+ case PPTP_STOP_SESSION_REQUEST:
+ case PPTP_STOP_SESSION_REPLY:
+ case PPTP_ECHO_REQUEST:
+ case PPTP_ECHO_REPLY:
+ /* no need to alter packet */
+ return NF_ACCEPT;
+ }
+
+ /* mangle packet */
+ IP_NF_ASSERT(pcid);
+ DEBUGP("altering peer call id from 0x%04x to 0x%04x\n",
+ ntohs(*pcid), ntohs(new_pcid));
+ ip_nat_mangle_tcp_packet(pskb, ct, ctinfo, (void *)pcid - (void *)pptph,
+ sizeof(new_pcid), (char *)&new_pcid,
+ sizeof(new_pcid));
+
+ if (new_cid) {
+ IP_NF_ASSERT(cid);
+ DEBUGP("altering call id from 0x%04x to 0x%04x\n",
+ ntohs(*cid), ntohs(new_cid));
+ ip_nat_mangle_tcp_packet(pskb, ct, ctinfo,
+ (void *)cid - (void *)pptph,
+ sizeof(new_cid), (char *)&new_cid,
+ sizeof(new_cid));
+ }
+
+ /* great, at least we don't need to resize packets */
+ return NF_ACCEPT;
+}
+
+
+static unsigned int tcp_help(struct ip_conntrack *ct,
+ struct ip_conntrack_expect *exp,
+ struct ip_nat_info *info,
+ enum ip_conntrack_info ctinfo,
+ unsigned int hooknum, struct sk_buff **pskb)
+{
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct tcphdr *tcph = (void *) iph + iph->ihl*4;
+ unsigned int datalen = (*pskb)->len - iph->ihl*4 - tcph->doff*4;
+ struct pptp_pkt_hdr *pptph;
+
+ int dir;
+
+ DEBUGP("entering\n");
+
+ /* Only mangle things once: DST for original direction
+ and SRC for reply direction. */
+ dir = CTINFO2DIR(ctinfo);
+ if (!((HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC
+ && dir == IP_CT_DIR_ORIGINAL)
+ || (HOOK2MANIP(hooknum) == IP_NAT_MANIP_DST
+ && dir == IP_CT_DIR_REPLY))) {
+ DEBUGP("Not touching dir %s at hook %s\n",
+ dir == IP_CT_DIR_ORIGINAL ? "ORIG" : "REPLY",
+ hooknum == NF_IP_POST_ROUTING ? "POSTROUTING"
+ : hooknum == NF_IP_PRE_ROUTING ? "PREROUTING"
+ : hooknum == NF_IP_LOCAL_OUT ? "OUTPUT"
+ : hooknum == NF_IP_LOCAL_IN ? "INPUT" : "???");
+ return NF_ACCEPT;
+ }
+
+ /* if packet is too small, just skip it */
+ if (datalen < sizeof(struct pptp_pkt_hdr)+
+ sizeof(struct PptpControlHeader)) {
+ DEBUGP("pptp packet too short\n");
+ return NF_ACCEPT;
+ }
+
+ pptph = (struct pptp_pkt_hdr *) ((void *)tcph + tcph->doff*4);
+
+ /* if it's not a control message, we can't handle it */
+ if (ntohs(pptph->packetType) != PPTP_PACKET_CONTROL ||
+ ntohl(pptph->magicCookie) != PPTP_MAGIC_COOKIE) {
+ DEBUGP("not a pptp control packet\n");
+ return NF_ACCEPT;
+ }
+
+ LOCK_BH(&ip_pptp_lock);
+
+ if (dir == IP_CT_DIR_ORIGINAL) {
+ /* reuqests sent by client to server (PNS->PAC) */
+ pptp_outbound_pkt(pskb, ct, ctinfo, exp);
+ } else {
+ /* response from the server to the client (PAC->PNS) */
+ pptp_inbound_pkt(pskb, ct, ctinfo, exp);
+ }
+
+ UNLOCK_BH(&ip_pptp_lock);
+
+ return NF_ACCEPT;
+}
+
+/* nat helper struct for control connection */
+static struct ip_nat_helper pptp_tcp_helper = {
+ .list = { NULL, NULL },
+ .name = "pptp",
+ .flags = IP_NAT_HELPER_F_ALWAYS,
+ .me = THIS_MODULE,
+ .tuple = { .src = { .ip = 0,
+ .u = { .tcp = { .port =
+ __constant_htons(PPTP_CONTROL_PORT) }
+ }
+ },
+ .dst = { .ip = 0,
+ .u = { .all = 0 },
+ .protonum = IPPROTO_TCP
+ }
+ },
+
+ .mask = { .src = { .ip = 0,
+ .u = { .tcp = { .port = 0xFFFF } }
+ },
+ .dst = { .ip = 0,
+ .u = { .all = 0 },
+ .protonum = 0xFFFF
+ }
+ },
+ .help = tcp_help,
+ .expect = pptp_nat_expected
+};
+
+
+static int __init init(void)
+{
+ DEBUGP("%s: registering NAT helper\n", __FILE__);
+ if (ip_nat_helper_register(&pptp_tcp_helper)) {
+ printk(KERN_ERR "Unable to register NAT application helper "
+ "for pptp\n");
+ return -EIO;
+ }
+
+ printk("ip_nat_pptp version %s loaded\n", IP_NAT_PPTP_VERSION);
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ DEBUGP("cleanup_module\n" );
+ ip_nat_helper_unregister(&pptp_tcp_helper);
+ printk("ip_nat_pptp version %s unloaded\n", IP_NAT_PPTP_VERSION);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_gre.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_gre.c
new file mode 100644
index 0000000..1496b7e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_gre.c
@@ -0,0 +1,225 @@
+/*
+ * ip_nat_proto_gre.c - Version $Revision: 1.3 $
+ *
+ * NAT protocol helper module for GRE.
+ *
+ * GRE is a generic encapsulation protocol, which is generally not very
+ * suited for NAT, as it has no protocol-specific part as port numbers.
+ *
+ * It has an optional key field, which may help us distinguishing two
+ * connections between the same two hosts.
+ *
+ * GRE is defined in RFC 1701 and RFC 1702, as well as RFC 2784
+ *
+ * PPTP is built on top of a modified version of GRE, and has a mandatory
+ * field called "CallID", which serves us for the same purpose as the key
+ * field in plain GRE.
+ *
+ * Documentation about PPTP can be found in RFC 2637
+ *
+ * (C) 2000-2003 by Harald Welte <laforge@gnumonks.org>
+ *
+ * Development of this code funded by Astaro AG (http://www.astaro.com/)
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+#include <linux/netfilter_ipv4/ip_nat_protocol.h>
+#include <linux/netfilter_ipv4/ip_conntrack_proto_gre.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("Netfilter NAT protocol helper module for GRE");
+
+#if 0
+#define DEBUGP(format, args...) printk(KERN_DEBUG __FILE__ ":" __FUNCTION__ \
+ ": " format, ## args)
+#else
+#define DEBUGP(x, args...)
+#endif
+
+/* is key in given range between min and max */
+static int
+gre_in_range(const struct ip_conntrack_tuple *tuple,
+ enum ip_nat_manip_type maniptype,
+ const union ip_conntrack_manip_proto *min,
+ const union ip_conntrack_manip_proto *max)
+{
+ u_int32_t key;
+
+ if (maniptype == IP_NAT_MANIP_SRC)
+ key = tuple->src.u.gre.key;
+ else
+ key = tuple->dst.u.gre.key;
+
+ return ntohl(key) >= ntohl(min->gre.key)
+ && ntohl(key) <= ntohl(max->gre.key);
+}
+
+/* generate unique tuple ... */
+static int
+gre_unique_tuple(struct ip_conntrack_tuple *tuple,
+ const struct ip_nat_range *range,
+ enum ip_nat_manip_type maniptype,
+ const struct ip_conntrack *conntrack)
+{
+ u_int32_t min, i, range_size;
+ u_int32_t key = 0, *keyptr;
+
+ if (maniptype == IP_NAT_MANIP_SRC)
+ keyptr = &tuple->src.u.gre.key;
+ else
+ keyptr = &tuple->dst.u.gre.key;
+
+ if (!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED)) {
+
+ switch (tuple->dst.u.gre.version) {
+ case 0:
+ DEBUGP("NATing GRE version 0 (ct=%p)\n",
+ conntrack);
+ min = 1;
+ range_size = 0xffffffff;
+ break;
+ case GRE_VERSION_PPTP:
+ DEBUGP("%p: NATing GRE PPTP\n",
+ conntrack);
+ min = 1;
+ range_size = 0xffff;
+ break;
+ default:
+ printk(KERN_WARNING "nat_gre: unknown GRE version\n");
+ return 0;
+ break;
+ }
+
+ } else {
+ min = ntohl(range->min.gre.key);
+ range_size = ntohl(range->max.gre.key) - min + 1;
+ }
+
+ DEBUGP("min = %u, range_size = %u\n", min, range_size);
+
+ for (i = 0; i < range_size; i++, key++) {
+ *keyptr = htonl(min + key % range_size);
+ if (!ip_nat_used_tuple(tuple, conntrack))
+ return 1;
+ }
+
+ DEBUGP("%p: no NAT mapping\n", conntrack);
+
+ return 0;
+}
+
+/* manipulate a GRE packet according to maniptype */
+static void
+gre_manip_pkt(struct iphdr *iph, size_t len,
+ const struct ip_conntrack_manip *manip,
+ enum ip_nat_manip_type maniptype)
+{
+ struct gre_hdr *greh = (struct gre_hdr *)((u_int32_t *)iph+iph->ihl);
+ struct gre_hdr_pptp *pgreh = (struct gre_hdr_pptp *) greh;
+
+ /* we only have destination manip of a packet, since 'source key'
+ * is not present in the packet itself */
+ if (maniptype == IP_NAT_MANIP_DST) {
+ /* key manipulation is always dest */
+ switch (greh->version) {
+ case 0:
+ if (!greh->key) {
+ DEBUGP("can't nat GRE w/o key\n");
+ break;
+ }
+ if (greh->csum) {
+ /* FIXME: Never tested this code... */
+ *(gre_csum(greh)) =
+ ip_nat_cheat_check(~*(gre_key(greh)),
+ manip->u.gre.key,
+ *(gre_csum(greh)));
+ }
+ *(gre_key(greh)) = manip->u.gre.key;
+ break;
+ case GRE_VERSION_PPTP:
+ DEBUGP("call_id -> 0x%04x\n",
+ ntohl(manip->u.gre.key));
+ pgreh->call_id = htons(ntohl(manip->u.gre.key));
+ break;
+ default:
+ DEBUGP("can't nat unknown GRE version\n");
+ break;
+ }
+ }
+}
+
+/* print out a nat tuple */
+static unsigned int
+gre_print(char *buffer,
+ const struct ip_conntrack_tuple *match,
+ const struct ip_conntrack_tuple *mask)
+{
+ unsigned int len = 0;
+
+ if (mask->dst.u.gre.version)
+ len += sprintf(buffer + len, "version=%d ",
+ ntohs(match->dst.u.gre.version));
+
+ if (mask->dst.u.gre.protocol)
+ len += sprintf(buffer + len, "protocol=0x%x ",
+ ntohs(match->dst.u.gre.protocol));
+
+ if (mask->src.u.gre.key)
+ len += sprintf(buffer + len, "srckey=0x%x ",
+ ntohl(match->src.u.gre.key));
+
+ if (mask->dst.u.gre.key)
+ len += sprintf(buffer + len, "dstkey=0x%x ",
+ ntohl(match->src.u.gre.key));
+
+ return len;
+}
+
+/* print a range of keys */
+static unsigned int
+gre_print_range(char *buffer, const struct ip_nat_range *range)
+{
+ if (range->min.gre.key != 0
+ || range->max.gre.key != 0xFFFF) {
+ if (range->min.gre.key == range->max.gre.key)
+ return sprintf(buffer, "key 0x%x ",
+ ntohl(range->min.gre.key));
+ else
+ return sprintf(buffer, "keys 0x%u-0x%u ",
+ ntohl(range->min.gre.key),
+ ntohl(range->max.gre.key));
+ } else
+ return 0;
+}
+
+/* nat helper struct */
+static struct ip_nat_protocol gre =
+ { { NULL, NULL }, "GRE", IPPROTO_GRE,
+ gre_manip_pkt,
+ gre_in_range,
+ gre_unique_tuple,
+ gre_print,
+ gre_print_range
+ };
+
+static int __init init(void)
+{
+ if (ip_nat_protocol_register(&gre))
+ return -EIO;
+
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ip_nat_protocol_unregister(&gre);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_icmp.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_icmp.c
new file mode 100644
index 0000000..9bc7427
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_icmp.c
@@ -0,0 +1,97 @@
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/netfilter.h>
+#include <linux/ip.h>
+#include <linux/icmp.h>
+#include <linux/if.h>
+
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+#include <linux/netfilter_ipv4/ip_nat_protocol.h>
+
+static int
+icmp_in_range(const struct ip_conntrack_tuple *tuple,
+ enum ip_nat_manip_type maniptype,
+ const union ip_conntrack_manip_proto *min,
+ const union ip_conntrack_manip_proto *max)
+{
+ return (tuple->src.u.icmp.id >= min->icmp.id
+ && tuple->src.u.icmp.id <= max->icmp.id);
+}
+
+static int
+icmp_unique_tuple(struct ip_conntrack_tuple *tuple,
+ const struct ip_nat_range *range,
+ enum ip_nat_manip_type maniptype,
+ const struct ip_conntrack *conntrack)
+{
+ static u_int16_t id = 0;
+ unsigned int range_size
+ = (unsigned int)range->max.icmp.id - range->min.icmp.id + 1;
+ unsigned int i;
+
+ /* If no range specified... */
+ if (!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED))
+ range_size = 0xFFFF;
+
+ for (i = 0; i < range_size; i++, id++) {
+ tuple->src.u.icmp.id = range->min.icmp.id + (id % range_size);
+ if (!ip_nat_used_tuple(tuple, conntrack))
+ return 1;
+ }
+ return 0;
+}
+
+static void
+icmp_manip_pkt(struct iphdr *iph, size_t len,
+ const struct ip_conntrack_manip *manip,
+ enum ip_nat_manip_type maniptype)
+{
+ struct icmphdr *hdr = (struct icmphdr *)((u_int32_t *)iph + iph->ihl);
+
+ hdr->checksum = ip_nat_cheat_check(hdr->un.echo.id ^ 0xFFFF,
+ manip->u.icmp.id,
+ hdr->checksum);
+ hdr->un.echo.id = manip->u.icmp.id;
+}
+
+static unsigned int
+icmp_print(char *buffer,
+ const struct ip_conntrack_tuple *match,
+ const struct ip_conntrack_tuple *mask)
+{
+ unsigned int len = 0;
+
+ if (mask->src.u.icmp.id)
+ len += sprintf(buffer + len, "id=%u ",
+ ntohs(match->src.u.icmp.id));
+
+ if (mask->dst.u.icmp.type)
+ len += sprintf(buffer + len, "type=%u ",
+ ntohs(match->dst.u.icmp.type));
+
+ if (mask->dst.u.icmp.code)
+ len += sprintf(buffer + len, "code=%u ",
+ ntohs(match->dst.u.icmp.code));
+
+ return len;
+}
+
+static unsigned int
+icmp_print_range(char *buffer, const struct ip_nat_range *range)
+{
+ if (range->min.icmp.id != 0 || range->max.icmp.id != 0xFFFF)
+ return sprintf(buffer, "id %u-%u ",
+ ntohs(range->min.icmp.id),
+ ntohs(range->max.icmp.id));
+ else return 0;
+}
+
+struct ip_nat_protocol ip_nat_protocol_icmp
+= { { NULL, NULL }, "ICMP", IPPROTO_ICMP,
+ icmp_manip_pkt,
+ icmp_in_range,
+ icmp_unique_tuple,
+ icmp_print,
+ icmp_print_range
+};
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_tcp.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_tcp.c
new file mode 100644
index 0000000..d27959e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_tcp.c
@@ -0,0 +1,149 @@
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/netfilter.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/if.h>
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+#include <linux/netfilter_ipv4/ip_nat_protocol.h>
+
+static int
+tcp_in_range(const struct ip_conntrack_tuple *tuple,
+ enum ip_nat_manip_type maniptype,
+ const union ip_conntrack_manip_proto *min,
+ const union ip_conntrack_manip_proto *max)
+{
+ u_int16_t port;
+
+ if (maniptype == IP_NAT_MANIP_SRC)
+ port = tuple->src.u.tcp.port;
+ else
+ port = tuple->dst.u.tcp.port;
+
+ return ntohs(port) >= ntohs(min->tcp.port)
+ && ntohs(port) <= ntohs(max->tcp.port);
+}
+
+static int
+tcp_unique_tuple(struct ip_conntrack_tuple *tuple,
+ const struct ip_nat_range *range,
+ enum ip_nat_manip_type maniptype,
+ const struct ip_conntrack *conntrack)
+{
+ static u_int16_t port = 0, *portptr;
+ unsigned int range_size, min, i;
+
+ if (maniptype == IP_NAT_MANIP_SRC)
+ portptr = &tuple->src.u.tcp.port;
+ else
+ portptr = &tuple->dst.u.tcp.port;
+
+ /* If no range specified... */
+ if (!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED)) {
+ /* If it's dst rewrite, can't change port */
+ if (maniptype == IP_NAT_MANIP_DST)
+ return 0;
+
+ /* Map privileged onto privileged. */
+ if (ntohs(*portptr) < 1024) {
+ /* Loose convention: >> 512 is credential passing */
+ if (ntohs(*portptr)<512) {
+ min = 1;
+ range_size = 511 - min + 1;
+ } else {
+ min = 600;
+ range_size = 1023 - min + 1;
+ }
+ } else {
+ min = 1024;
+ range_size = 65535 - 1024 + 1;
+ }
+ } else {
+ min = ntohs(range->min.tcp.port);
+ range_size = ntohs(range->max.tcp.port) - min + 1;
+ }
+
+ for (i = 0; i < range_size; i++, port++) {
+ *portptr = htons(min + port % range_size);
+ if (!ip_nat_used_tuple(tuple, conntrack)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void
+tcp_manip_pkt(struct iphdr *iph, size_t len,
+ const struct ip_conntrack_manip *manip,
+ enum ip_nat_manip_type maniptype)
+{
+ struct tcphdr *hdr = (struct tcphdr *)((u_int32_t *)iph + iph->ihl);
+ u_int32_t oldip;
+ u_int16_t *portptr;
+
+ if (maniptype == IP_NAT_MANIP_SRC) {
+ /* Get rid of src ip and src pt */
+ oldip = iph->saddr;
+ portptr = &hdr->source;
+ } else {
+ /* Get rid of dst ip and dst pt */
+ oldip = iph->daddr;
+ portptr = &hdr->dest;
+ }
+
+ /* this could be a inner header returned in icmp packet; in such
+ cases we cannot update the checksum field since it is outside of
+ the 8 bytes of transport layer headers we are guaranteed */
+ if(((void *)&hdr->check + sizeof(hdr->check) - (void *)iph) <= len) {
+ hdr->check = ip_nat_cheat_check(~oldip, manip->ip,
+ ip_nat_cheat_check(*portptr ^ 0xFFFF,
+ manip->u.tcp.port,
+ hdr->check));
+ }
+
+ *portptr = manip->u.tcp.port;
+}
+
+static unsigned int
+tcp_print(char *buffer,
+ const struct ip_conntrack_tuple *match,
+ const struct ip_conntrack_tuple *mask)
+{
+ unsigned int len = 0;
+
+ if (mask->src.u.tcp.port)
+ len += sprintf(buffer + len, "srcpt=%u ",
+ ntohs(match->src.u.tcp.port));
+
+
+ if (mask->dst.u.tcp.port)
+ len += sprintf(buffer + len, "dstpt=%u ",
+ ntohs(match->dst.u.tcp.port));
+
+ return len;
+}
+
+static unsigned int
+tcp_print_range(char *buffer, const struct ip_nat_range *range)
+{
+ if (range->min.tcp.port != 0 || range->max.tcp.port != 0xFFFF) {
+ if (range->min.tcp.port == range->max.tcp.port)
+ return sprintf(buffer, "port %u ",
+ ntohs(range->min.tcp.port));
+ else
+ return sprintf(buffer, "ports %u-%u ",
+ ntohs(range->min.tcp.port),
+ ntohs(range->max.tcp.port));
+ }
+ else return 0;
+}
+
+struct ip_nat_protocol ip_nat_protocol_tcp
+= { { NULL, NULL }, "TCP", IPPROTO_TCP,
+ tcp_manip_pkt,
+ tcp_in_range,
+ tcp_unique_tuple,
+ tcp_print,
+ tcp_print_range
+};
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_udp.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_udp.c
new file mode 100644
index 0000000..622aee0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_udp.c
@@ -0,0 +1,142 @@
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/netfilter.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+#include <linux/if.h>
+
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+#include <linux/netfilter_ipv4/ip_nat_protocol.h>
+
+static int
+udp_in_range(const struct ip_conntrack_tuple *tuple,
+ enum ip_nat_manip_type maniptype,
+ const union ip_conntrack_manip_proto *min,
+ const union ip_conntrack_manip_proto *max)
+{
+ u_int16_t port;
+
+ if (maniptype == IP_NAT_MANIP_SRC)
+ port = tuple->src.u.udp.port;
+ else
+ port = tuple->dst.u.udp.port;
+
+ return ntohs(port) >= ntohs(min->udp.port)
+ && ntohs(port) <= ntohs(max->udp.port);
+}
+
+static int
+udp_unique_tuple(struct ip_conntrack_tuple *tuple,
+ const struct ip_nat_range *range,
+ enum ip_nat_manip_type maniptype,
+ const struct ip_conntrack *conntrack)
+{
+ static u_int16_t port = 0, *portptr;
+ unsigned int range_size, min, i;
+
+ if (maniptype == IP_NAT_MANIP_SRC)
+ portptr = &tuple->src.u.udp.port;
+ else
+ portptr = &tuple->dst.u.udp.port;
+
+ /* If no range specified... */
+ if (!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED)) {
+ /* If it's dst rewrite, can't change port */
+ if (maniptype == IP_NAT_MANIP_DST)
+ return 0;
+
+ if (ntohs(*portptr) < 1024) {
+ /* Loose convention: >> 512 is credential passing */
+ if (ntohs(*portptr)<512) {
+ min = 1;
+ range_size = 511 - min + 1;
+ } else {
+ min = 600;
+ range_size = 1023 - min + 1;
+ }
+ } else {
+ min = 1024;
+ range_size = 65535 - 1024 + 1;
+ }
+ } else {
+ min = ntohs(range->min.udp.port);
+ range_size = ntohs(range->max.udp.port) - min + 1;
+ }
+
+ for (i = 0; i < range_size; i++, port++) {
+ *portptr = htons(min + port % range_size);
+ if (!ip_nat_used_tuple(tuple, conntrack))
+ return 1;
+ }
+ return 0;
+}
+
+static void
+udp_manip_pkt(struct iphdr *iph, size_t len,
+ const struct ip_conntrack_manip *manip,
+ enum ip_nat_manip_type maniptype)
+{
+ struct udphdr *hdr = (struct udphdr *)((u_int32_t *)iph + iph->ihl);
+ u_int32_t oldip;
+ u_int16_t *portptr;
+
+ if (maniptype == IP_NAT_MANIP_SRC) {
+ /* Get rid of src ip and src pt */
+ oldip = iph->saddr;
+ portptr = &hdr->source;
+ } else {
+ /* Get rid of dst ip and dst pt */
+ oldip = iph->daddr;
+ portptr = &hdr->dest;
+ }
+ if (hdr->check) /* 0 is a special case meaning no checksum */
+ hdr->check = ip_nat_cheat_check(~oldip, manip->ip,
+ ip_nat_cheat_check(*portptr ^ 0xFFFF,
+ manip->u.udp.port,
+ hdr->check));
+ *portptr = manip->u.udp.port;
+}
+
+static unsigned int
+udp_print(char *buffer,
+ const struct ip_conntrack_tuple *match,
+ const struct ip_conntrack_tuple *mask)
+{
+ unsigned int len = 0;
+
+ if (mask->src.u.udp.port)
+ len += sprintf(buffer + len, "srcpt=%u ",
+ ntohs(match->src.u.udp.port));
+
+
+ if (mask->dst.u.udp.port)
+ len += sprintf(buffer + len, "dstpt=%u ",
+ ntohs(match->dst.u.udp.port));
+
+ return len;
+}
+
+static unsigned int
+udp_print_range(char *buffer, const struct ip_nat_range *range)
+{
+ if (range->min.udp.port != 0 || range->max.udp.port != 0xFFFF) {
+ if (range->min.udp.port == range->max.udp.port)
+ return sprintf(buffer, "port %u ",
+ ntohs(range->min.udp.port));
+ else
+ return sprintf(buffer, "ports %u-%u ",
+ ntohs(range->min.udp.port),
+ ntohs(range->max.udp.port));
+ }
+ else return 0;
+}
+
+struct ip_nat_protocol ip_nat_protocol_udp
+= { { NULL, NULL }, "UDP", IPPROTO_UDP,
+ udp_manip_pkt,
+ udp_in_range,
+ udp_unique_tuple,
+ udp_print,
+ udp_print_range
+};
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_unknown.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_unknown.c
new file mode 100644
index 0000000..a2b5de6
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_proto_unknown.c
@@ -0,0 +1,61 @@
+/* The "unknown" protocol. This is what is used for protocols we
+ * don't understand. It's returned by ip_ct_find_proto().
+ */
+
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/netfilter.h>
+#include <linux/if.h>
+
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+#include <linux/netfilter_ipv4/ip_nat_protocol.h>
+
+static int unknown_in_range(const struct ip_conntrack_tuple *tuple,
+ enum ip_nat_manip_type manip_type,
+ const union ip_conntrack_manip_proto *min,
+ const union ip_conntrack_manip_proto *max)
+{
+ return 1;
+}
+
+static int unknown_unique_tuple(struct ip_conntrack_tuple *tuple,
+ const struct ip_nat_range *range,
+ enum ip_nat_manip_type maniptype,
+ const struct ip_conntrack *conntrack)
+{
+ /* Sorry: we can't help you; if it's not unique, we can't frob
+ anything. */
+ return 0;
+}
+
+static void
+unknown_manip_pkt(struct iphdr *iph, size_t len,
+ const struct ip_conntrack_manip *manip,
+ enum ip_nat_manip_type maniptype)
+{
+ return;
+}
+
+static unsigned int
+unknown_print(char *buffer,
+ const struct ip_conntrack_tuple *match,
+ const struct ip_conntrack_tuple *mask)
+{
+ return 0;
+}
+
+static unsigned int
+unknown_print_range(char *buffer, const struct ip_nat_range *range)
+{
+ return 0;
+}
+
+struct ip_nat_protocol unknown_nat_protocol = {
+ { NULL, NULL }, "unknown", 0,
+ unknown_manip_pkt,
+ unknown_in_range,
+ unknown_unique_tuple,
+ unknown_print,
+ unknown_print_range
+};
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_rule.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_rule.c
new file mode 100644
index 0000000..2d54f84
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_rule.c
@@ -0,0 +1,298 @@
+/* Everything about the rules for NAT. */
+#include <linux/types.h>
+#include <linux/ip.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/module.h>
+#include <linux/kmod.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <net/checksum.h>
+#include <linux/bitops.h>
+#include <linux/version.h>
+
+#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_nat_lock)
+#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_nat_lock)
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_core.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+#define NAT_VALID_HOOKS ((1<<NF_IP_PRE_ROUTING) | (1<<NF_IP_POST_ROUTING) | (1<<NF_IP_LOCAL_OUT))
+
+/* Standard entry. */
+struct ipt_standard
+{
+ struct ipt_entry entry;
+ struct ipt_standard_target target;
+};
+
+struct ipt_error_target
+{
+ struct ipt_entry_target target;
+ char errorname[IPT_FUNCTION_MAXNAMELEN];
+};
+
+struct ipt_error
+{
+ struct ipt_entry entry;
+ struct ipt_error_target target;
+};
+
+static struct
+{
+ struct ipt_replace repl;
+ struct ipt_standard entries[3];
+ struct ipt_error term;
+} nat_initial_table __initdata
+= { { "nat", NAT_VALID_HOOKS, 4,
+ sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error),
+ { [NF_IP_PRE_ROUTING] 0,
+ [NF_IP_POST_ROUTING] sizeof(struct ipt_standard),
+ [NF_IP_LOCAL_OUT] sizeof(struct ipt_standard) * 2 },
+ { [NF_IP_PRE_ROUTING] 0,
+ [NF_IP_POST_ROUTING] sizeof(struct ipt_standard),
+ [NF_IP_LOCAL_OUT] sizeof(struct ipt_standard) * 2 },
+ 0, NULL, { } },
+ {
+ /* PRE_ROUTING */
+ { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ipt_entry),
+ sizeof(struct ipt_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ /* POST_ROUTING */
+ { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ipt_entry),
+ sizeof(struct ipt_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ /* LOCAL_OUT */
+ { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ipt_entry),
+ sizeof(struct ipt_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } }
+ },
+ /* ERROR */
+ { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ipt_entry),
+ sizeof(struct ipt_error),
+ 0, { 0, 0 }, { } },
+ { { { { IPT_ALIGN(sizeof(struct ipt_error_target)), IPT_ERROR_TARGET } },
+ { } },
+ "ERROR"
+ }
+ }
+};
+
+static struct ipt_table nat_table
+= { { NULL, NULL }, "nat", &nat_initial_table.repl,
+ NAT_VALID_HOOKS, RW_LOCK_UNLOCKED, NULL, THIS_MODULE };
+
+/* Source NAT */
+static unsigned int ipt_snat_target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ struct ip_conntrack *ct;
+ enum ip_conntrack_info ctinfo;
+
+ IP_NF_ASSERT(hooknum == NF_IP_POST_ROUTING);
+
+ ct = ip_conntrack_get(*pskb, &ctinfo);
+
+ /* Connection must be valid and new. */
+ IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED
+ || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));
+ IP_NF_ASSERT(out);
+
+ return ip_nat_setup_info(ct, targinfo, hooknum);
+}
+
+static unsigned int ipt_dnat_target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ struct ip_conntrack *ct;
+ enum ip_conntrack_info ctinfo;
+
+ IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING
+ || hooknum == NF_IP_LOCAL_OUT);
+
+ ct = ip_conntrack_get(*pskb, &ctinfo);
+
+ /* Connection must be valid and new. */
+ IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED));
+
+ return ip_nat_setup_info(ct, targinfo, hooknum);
+}
+
+static int ipt_snat_checkentry(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ struct ip_nat_multi_range *mr = targinfo;
+
+ /* Must be a valid range */
+ if (targinfosize < sizeof(struct ip_nat_multi_range)) {
+ DEBUGP("SNAT: Target size %u too small\n", targinfosize);
+ return 0;
+ }
+
+ if (targinfosize != IPT_ALIGN((sizeof(struct ip_nat_multi_range)
+ + (sizeof(struct ip_nat_range)
+ * (mr->rangesize - 1))))) {
+ DEBUGP("SNAT: Target size %u wrong for %u ranges\n",
+ targinfosize, mr->rangesize);
+ return 0;
+ }
+
+ /* Only allow these for NAT. */
+ if (strcmp(tablename, "nat") != 0) {
+ DEBUGP("SNAT: wrong table %s\n", tablename);
+ return 0;
+ }
+
+ if (hook_mask & ~(1 << NF_IP_POST_ROUTING)) {
+ DEBUGP("SNAT: hook mask 0x%x bad\n", hook_mask);
+ return 0;
+ }
+ return 1;
+}
+
+static int ipt_dnat_checkentry(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ struct ip_nat_multi_range *mr = targinfo;
+
+ /* Must be a valid range */
+ if (targinfosize < sizeof(struct ip_nat_multi_range)) {
+ DEBUGP("DNAT: Target size %u too small\n", targinfosize);
+ return 0;
+ }
+
+ if (targinfosize != IPT_ALIGN((sizeof(struct ip_nat_multi_range)
+ + (sizeof(struct ip_nat_range)
+ * (mr->rangesize - 1))))) {
+ DEBUGP("DNAT: Target size %u wrong for %u ranges\n",
+ targinfosize, mr->rangesize);
+ return 0;
+ }
+
+ /* Only allow these for NAT. */
+ if (strcmp(tablename, "nat") != 0) {
+ DEBUGP("DNAT: wrong table %s\n", tablename);
+ return 0;
+ }
+
+ if (hook_mask & ~((1 << NF_IP_PRE_ROUTING) | (1 << NF_IP_LOCAL_OUT))) {
+ DEBUGP("DNAT: hook mask 0x%x bad\n", hook_mask);
+ return 0;
+ }
+
+ return 1;
+}
+
+inline unsigned int
+alloc_null_binding(struct ip_conntrack *conntrack,
+ struct ip_nat_info *info,
+ unsigned int hooknum)
+{
+ /* Force range to this IP; let proto decide mapping for
+ per-proto parts (hence not IP_NAT_RANGE_PROTO_SPECIFIED).
+ Use reply in case it's already been mangled (eg local packet).
+ */
+ u_int32_t ip
+ = (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC
+ ? conntrack->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip
+ : conntrack->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip);
+ struct ip_nat_multi_range mr
+ = { 1, { { IP_NAT_RANGE_MAP_IPS, ip, ip, { 0 }, { 0 } } } };
+
+ DEBUGP("Allocating NULL binding for %p (%u.%u.%u.%u)\n", conntrack,
+ NIPQUAD(ip));
+ return ip_nat_setup_info(conntrack, &mr, hooknum);
+}
+
+int ip_nat_rule_find(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ struct ip_conntrack *ct,
+ struct ip_nat_info *info)
+{
+ int ret;
+
+ ret = ipt_do_table(pskb, hooknum, in, out, &nat_table, NULL);
+
+ if (ret == NF_ACCEPT) {
+ if (!(info->initialized & (1 << HOOK2MANIP(hooknum))))
+ /* NUL mapping */
+ ret = alloc_null_binding(ct, info, hooknum);
+ }
+ return ret;
+}
+
+static struct ipt_target ipt_snat_reg
+= { { NULL, NULL }, "SNAT", ipt_snat_target, ipt_snat_checkentry, NULL };
+static struct ipt_target ipt_dnat_reg
+= { { NULL, NULL }, "DNAT", ipt_dnat_target, ipt_dnat_checkentry, NULL };
+
+int __init ip_nat_rule_init(void)
+{
+ int ret;
+
+ ret = ipt_register_table(&nat_table);
+ if (ret != 0)
+ return ret;
+ ret = ipt_register_target(&ipt_snat_reg);
+ if (ret != 0)
+ goto unregister_table;
+
+ ret = ipt_register_target(&ipt_dnat_reg);
+ if (ret != 0)
+ goto unregister_snat;
+
+ return ret;
+
+ unregister_snat:
+ ipt_unregister_target(&ipt_snat_reg);
+ unregister_table:
+ ipt_unregister_table(&nat_table);
+
+ return ret;
+}
+
+void ip_nat_rule_cleanup(void)
+{
+ ipt_unregister_target(&ipt_dnat_reg);
+ ipt_unregister_target(&ipt_snat_reg);
+ ipt_unregister_table(&nat_table);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_snmp_basic.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_snmp_basic.c
new file mode 100644
index 0000000..3ee0477
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_snmp_basic.c
@@ -0,0 +1,1363 @@
+/*
+ * ip_nat_snmp_basic.c
+ *
+ * Basic SNMP Application Layer Gateway
+ *
+ * This IP NAT module is intended for use with SNMP network
+ * discovery and monitoring applications where target networks use
+ * conflicting private address realms.
+ *
+ * Static NAT is used to remap the networks from the view of the network
+ * management system at the IP layer, and this module remaps some application
+ * layer addresses to match.
+ *
+ * The simplest form of ALG is performed, where only tagged IP addresses
+ * are modified. The module does not need to be MIB aware and only scans
+ * messages at the ASN.1/BER level.
+ *
+ * Currently, only SNMPv1 and SNMPv2 are supported.
+ *
+ * More information on ALG and associated issues can be found in
+ * RFC 2962
+ *
+ * The ASB.1/BER parsing code is derived from the gxsnmp package by Gregory
+ * McLean & Jochen Friedrich, stripped down for use in the kernel.
+ *
+ * Copyright (c) 2000 RP Internet (www.rpi.net.au).
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * Author: James Morris <jmorris@intercode.com.au>
+ *
+ * Updates:
+ * 2000-08-06: Convert to new helper API (Harald Welte).
+ *
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_helper.h>
+#include <linux/brlock.h>
+#include <linux/types.h>
+#include <linux/ip.h>
+#include <net/checksum.h>
+#include <net/udp.h>
+#include <asm/uaccess.h>
+
+
+
+#define SNMP_PORT 161
+#define SNMP_TRAP_PORT 162
+#define NOCT1(n) (u_int8_t )((n) & 0xff)
+
+static int debug = 0;
+static spinlock_t snmp_lock = SPIN_LOCK_UNLOCKED;
+
+/*
+ * Application layer address mapping mimics the NAT mapping, but
+ * only for the first octet in this case (a more flexible system
+ * can be implemented if needed).
+ */
+struct oct1_map
+{
+ u_int8_t from;
+ u_int8_t to;
+};
+
+
+/*****************************************************************************
+ *
+ * Basic ASN.1 decoding routines (gxsnmp author Dirk Wisse)
+ *
+ *****************************************************************************/
+
+/* Class */
+#define ASN1_UNI 0 /* Universal */
+#define ASN1_APL 1 /* Application */
+#define ASN1_CTX 2 /* Context */
+#define ASN1_PRV 3 /* Private */
+
+/* Tag */
+#define ASN1_EOC 0 /* End Of Contents */
+#define ASN1_BOL 1 /* Boolean */
+#define ASN1_INT 2 /* Integer */
+#define ASN1_BTS 3 /* Bit String */
+#define ASN1_OTS 4 /* Octet String */
+#define ASN1_NUL 5 /* Null */
+#define ASN1_OJI 6 /* Object Identifier */
+#define ASN1_OJD 7 /* Object Description */
+#define ASN1_EXT 8 /* External */
+#define ASN1_SEQ 16 /* Sequence */
+#define ASN1_SET 17 /* Set */
+#define ASN1_NUMSTR 18 /* Numerical String */
+#define ASN1_PRNSTR 19 /* Printable String */
+#define ASN1_TEXSTR 20 /* Teletext String */
+#define ASN1_VIDSTR 21 /* Video String */
+#define ASN1_IA5STR 22 /* IA5 String */
+#define ASN1_UNITIM 23 /* Universal Time */
+#define ASN1_GENTIM 24 /* General Time */
+#define ASN1_GRASTR 25 /* Graphical String */
+#define ASN1_VISSTR 26 /* Visible String */
+#define ASN1_GENSTR 27 /* General String */
+
+/* Primitive / Constructed methods*/
+#define ASN1_PRI 0 /* Primitive */
+#define ASN1_CON 1 /* Constructed */
+
+/*
+ * Error codes.
+ */
+#define ASN1_ERR_NOERROR 0
+#define ASN1_ERR_DEC_EMPTY 2
+#define ASN1_ERR_DEC_EOC_MISMATCH 3
+#define ASN1_ERR_DEC_LENGTH_MISMATCH 4
+#define ASN1_ERR_DEC_BADVALUE 5
+
+/*
+ * ASN.1 context.
+ */
+struct asn1_ctx
+{
+ int error; /* Error condition */
+ unsigned char *pointer; /* Octet just to be decoded */
+ unsigned char *begin; /* First octet */
+ unsigned char *end; /* Octet after last octet */
+};
+
+/*
+ * Octet string (not null terminated)
+ */
+struct asn1_octstr
+{
+ unsigned char *data;
+ unsigned int len;
+};
+
+static void asn1_open(struct asn1_ctx *ctx,
+ unsigned char *buf,
+ unsigned int len)
+{
+ ctx->begin = buf;
+ ctx->end = buf + len;
+ ctx->pointer = buf;
+ ctx->error = ASN1_ERR_NOERROR;
+}
+
+static unsigned char asn1_octet_decode(struct asn1_ctx *ctx, unsigned char *ch)
+{
+ if (ctx->pointer >= ctx->end) {
+ ctx->error = ASN1_ERR_DEC_EMPTY;
+ return 0;
+ }
+ *ch = *(ctx->pointer)++;
+ return 1;
+}
+
+static unsigned char asn1_tag_decode(struct asn1_ctx *ctx, unsigned int *tag)
+{
+ unsigned char ch;
+
+ *tag = 0;
+
+ do
+ {
+ if (!asn1_octet_decode(ctx, &ch))
+ return 0;
+ *tag <<= 7;
+ *tag |= ch & 0x7F;
+ } while ((ch & 0x80) == 0x80);
+ return 1;
+}
+
+static unsigned char asn1_id_decode(struct asn1_ctx *ctx,
+ unsigned int *cls,
+ unsigned int *con,
+ unsigned int *tag)
+{
+ unsigned char ch;
+
+ if (!asn1_octet_decode(ctx, &ch))
+ return 0;
+
+ *cls = (ch & 0xC0) >> 6;
+ *con = (ch & 0x20) >> 5;
+ *tag = (ch & 0x1F);
+
+ if (*tag == 0x1F) {
+ if (!asn1_tag_decode(ctx, tag))
+ return 0;
+ }
+ return 1;
+}
+
+static unsigned char asn1_length_decode(struct asn1_ctx *ctx,
+ unsigned int *def,
+ unsigned int *len)
+{
+ unsigned char ch, cnt;
+
+ if (!asn1_octet_decode(ctx, &ch))
+ return 0;
+
+ if (ch == 0x80)
+ *def = 0;
+ else {
+ *def = 1;
+
+ if (ch < 0x80)
+ *len = ch;
+ else {
+ cnt = (unsigned char) (ch & 0x7F);
+ *len = 0;
+
+ while (cnt > 0) {
+ if (!asn1_octet_decode(ctx, &ch))
+ return 0;
+ *len <<= 8;
+ *len |= ch;
+ cnt--;
+ }
+ }
+ }
+ return 1;
+}
+
+static unsigned char asn1_header_decode(struct asn1_ctx *ctx,
+ unsigned char **eoc,
+ unsigned int *cls,
+ unsigned int *con,
+ unsigned int *tag)
+{
+ unsigned int def, len;
+
+ if (!asn1_id_decode(ctx, cls, con, tag))
+ return 0;
+
+ if (!asn1_length_decode(ctx, &def, &len))
+ return 0;
+
+ if (def)
+ *eoc = ctx->pointer + len;
+ else
+ *eoc = 0;
+ return 1;
+}
+
+static unsigned char asn1_eoc_decode(struct asn1_ctx *ctx, unsigned char *eoc)
+{
+ unsigned char ch;
+
+ if (eoc == 0) {
+ if (!asn1_octet_decode(ctx, &ch))
+ return 0;
+
+ if (ch != 0x00) {
+ ctx->error = ASN1_ERR_DEC_EOC_MISMATCH;
+ return 0;
+ }
+
+ if (!asn1_octet_decode(ctx, &ch))
+ return 0;
+
+ if (ch != 0x00) {
+ ctx->error = ASN1_ERR_DEC_EOC_MISMATCH;
+ return 0;
+ }
+ return 1;
+ } else {
+ if (ctx->pointer != eoc) {
+ ctx->error = ASN1_ERR_DEC_LENGTH_MISMATCH;
+ return 0;
+ }
+ return 1;
+ }
+}
+
+static unsigned char asn1_null_decode(struct asn1_ctx *ctx, unsigned char *eoc)
+{
+ ctx->pointer = eoc;
+ return 1;
+}
+
+static unsigned char asn1_long_decode(struct asn1_ctx *ctx,
+ unsigned char *eoc,
+ long *integer)
+{
+ unsigned char ch;
+ unsigned int len;
+
+ if (!asn1_octet_decode(ctx, &ch))
+ return 0;
+
+ *integer = (signed char) ch;
+ len = 1;
+
+ while (ctx->pointer < eoc) {
+ if (++len > sizeof (long)) {
+ ctx->error = ASN1_ERR_DEC_BADVALUE;
+ return 0;
+ }
+
+ if (!asn1_octet_decode(ctx, &ch))
+ return 0;
+
+ *integer <<= 8;
+ *integer |= ch;
+ }
+ return 1;
+}
+
+static unsigned char asn1_uint_decode(struct asn1_ctx *ctx,
+ unsigned char *eoc,
+ unsigned int *integer)
+{
+ unsigned char ch;
+ unsigned int len;
+
+ if (!asn1_octet_decode(ctx, &ch))
+ return 0;
+
+ *integer = ch;
+ if (ch == 0) len = 0;
+ else len = 1;
+
+ while (ctx->pointer < eoc) {
+ if (++len > sizeof (unsigned int)) {
+ ctx->error = ASN1_ERR_DEC_BADVALUE;
+ return 0;
+ }
+
+ if (!asn1_octet_decode(ctx, &ch))
+ return 0;
+
+ *integer <<= 8;
+ *integer |= ch;
+ }
+ return 1;
+}
+
+static unsigned char asn1_ulong_decode(struct asn1_ctx *ctx,
+ unsigned char *eoc,
+ unsigned long *integer)
+{
+ unsigned char ch;
+ unsigned int len;
+
+ if (!asn1_octet_decode(ctx, &ch))
+ return 0;
+
+ *integer = ch;
+ if (ch == 0) len = 0;
+ else len = 1;
+
+ while (ctx->pointer < eoc) {
+ if (++len > sizeof (unsigned long)) {
+ ctx->error = ASN1_ERR_DEC_BADVALUE;
+ return 0;
+ }
+
+ if (!asn1_octet_decode(ctx, &ch))
+ return 0;
+
+ *integer <<= 8;
+ *integer |= ch;
+ }
+ return 1;
+}
+
+static unsigned char asn1_octets_decode(struct asn1_ctx *ctx,
+ unsigned char *eoc,
+ unsigned char **octets,
+ unsigned int *len)
+{
+ unsigned char *ptr;
+
+ *len = 0;
+
+ *octets = kmalloc(eoc - ctx->pointer, GFP_ATOMIC);
+ if (*octets == NULL) {
+ if (net_ratelimit())
+ printk("OOM in bsalg (%d)\n", __LINE__);
+ return 0;
+ }
+
+ ptr = *octets;
+ while (ctx->pointer < eoc) {
+ if (!asn1_octet_decode(ctx, (unsigned char *)ptr++)) {
+ kfree(*octets);
+ *octets = NULL;
+ return 0;
+ }
+ (*len)++;
+ }
+ return 1;
+}
+
+static unsigned char asn1_subid_decode(struct asn1_ctx *ctx,
+ unsigned long *subid)
+{
+ unsigned char ch;
+
+ *subid = 0;
+
+ do {
+ if (!asn1_octet_decode(ctx, &ch))
+ return 0;
+
+ *subid <<= 7;
+ *subid |= ch & 0x7F;
+ } while ((ch & 0x80) == 0x80);
+ return 1;
+}
+
+static unsigned char asn1_oid_decode(struct asn1_ctx *ctx,
+ unsigned char *eoc,
+ unsigned long **oid,
+ unsigned int *len)
+{
+ unsigned long subid;
+ unsigned int size;
+ unsigned long *optr;
+
+ size = eoc - ctx->pointer + 1;
+ *oid = kmalloc(size * sizeof(unsigned long), GFP_ATOMIC);
+ if (*oid == NULL) {
+ if (net_ratelimit())
+ printk("OOM in bsalg (%d)\n", __LINE__);
+ return 0;
+ }
+
+ optr = *oid;
+
+ if (!asn1_subid_decode(ctx, &subid)) {
+ kfree(*oid);
+ *oid = NULL;
+ return 0;
+ }
+
+ if (subid < 40) {
+ optr [0] = 0;
+ optr [1] = subid;
+ } else if (subid < 80) {
+ optr [0] = 1;
+ optr [1] = subid - 40;
+ } else {
+ optr [0] = 2;
+ optr [1] = subid - 80;
+ }
+
+ *len = 2;
+ optr += 2;
+
+ while (ctx->pointer < eoc) {
+ if (++(*len) > size) {
+ ctx->error = ASN1_ERR_DEC_BADVALUE;
+ kfree(*oid);
+ *oid = NULL;
+ return 0;
+ }
+
+ if (!asn1_subid_decode(ctx, optr++)) {
+ kfree(*oid);
+ *oid = NULL;
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/*****************************************************************************
+ *
+ * SNMP decoding routines (gxsnmp author Dirk Wisse)
+ *
+ *****************************************************************************/
+
+/* SNMP Versions */
+#define SNMP_V1 0
+#define SNMP_V2C 1
+#define SNMP_V2 2
+#define SNMP_V3 3
+
+/* Default Sizes */
+#define SNMP_SIZE_COMM 256
+#define SNMP_SIZE_OBJECTID 128
+#define SNMP_SIZE_BUFCHR 256
+#define SNMP_SIZE_BUFINT 128
+#define SNMP_SIZE_SMALLOBJECTID 16
+
+/* Requests */
+#define SNMP_PDU_GET 0
+#define SNMP_PDU_NEXT 1
+#define SNMP_PDU_RESPONSE 2
+#define SNMP_PDU_SET 3
+#define SNMP_PDU_TRAP1 4
+#define SNMP_PDU_BULK 5
+#define SNMP_PDU_INFORM 6
+#define SNMP_PDU_TRAP2 7
+
+/* Errors */
+#define SNMP_NOERROR 0
+#define SNMP_TOOBIG 1
+#define SNMP_NOSUCHNAME 2
+#define SNMP_BADVALUE 3
+#define SNMP_READONLY 4
+#define SNMP_GENERROR 5
+#define SNMP_NOACCESS 6
+#define SNMP_WRONGTYPE 7
+#define SNMP_WRONGLENGTH 8
+#define SNMP_WRONGENCODING 9
+#define SNMP_WRONGVALUE 10
+#define SNMP_NOCREATION 11
+#define SNMP_INCONSISTENTVALUE 12
+#define SNMP_RESOURCEUNAVAILABLE 13
+#define SNMP_COMMITFAILED 14
+#define SNMP_UNDOFAILED 15
+#define SNMP_AUTHORIZATIONERROR 16
+#define SNMP_NOTWRITABLE 17
+#define SNMP_INCONSISTENTNAME 18
+
+/* General SNMP V1 Traps */
+#define SNMP_TRAP_COLDSTART 0
+#define SNMP_TRAP_WARMSTART 1
+#define SNMP_TRAP_LINKDOWN 2
+#define SNMP_TRAP_LINKUP 3
+#define SNMP_TRAP_AUTFAILURE 4
+#define SNMP_TRAP_EQPNEIGHBORLOSS 5
+#define SNMP_TRAP_ENTSPECIFIC 6
+
+/* SNMPv1 Types */
+#define SNMP_NULL 0
+#define SNMP_INTEGER 1 /* l */
+#define SNMP_OCTETSTR 2 /* c */
+#define SNMP_DISPLAYSTR 2 /* c */
+#define SNMP_OBJECTID 3 /* ul */
+#define SNMP_IPADDR 4 /* uc */
+#define SNMP_COUNTER 5 /* ul */
+#define SNMP_GAUGE 6 /* ul */
+#define SNMP_TIMETICKS 7 /* ul */
+#define SNMP_OPAQUE 8 /* c */
+
+/* Additional SNMPv2 Types */
+#define SNMP_UINTEGER 5 /* ul */
+#define SNMP_BITSTR 9 /* uc */
+#define SNMP_NSAP 10 /* uc */
+#define SNMP_COUNTER64 11 /* ul */
+#define SNMP_NOSUCHOBJECT 12
+#define SNMP_NOSUCHINSTANCE 13
+#define SNMP_ENDOFMIBVIEW 14
+
+union snmp_syntax
+{
+ unsigned char uc[0]; /* 8 bit unsigned */
+ char c[0]; /* 8 bit signed */
+ unsigned long ul[0]; /* 32 bit unsigned */
+ long l[0]; /* 32 bit signed */
+};
+
+struct snmp_object
+{
+ unsigned long *id;
+ unsigned int id_len;
+ unsigned short type;
+ unsigned int syntax_len;
+ union snmp_syntax syntax;
+};
+
+struct snmp_request
+{
+ unsigned long id;
+ unsigned int error_status;
+ unsigned int error_index;
+};
+
+struct snmp_v1_trap
+{
+ unsigned long *id;
+ unsigned int id_len;
+ unsigned long ip_address; /* pointer */
+ unsigned int general;
+ unsigned int specific;
+ unsigned long time;
+};
+
+/* SNMP types */
+#define SNMP_IPA 0
+#define SNMP_CNT 1
+#define SNMP_GGE 2
+#define SNMP_TIT 3
+#define SNMP_OPQ 4
+#define SNMP_C64 6
+
+/* SNMP errors */
+#define SERR_NSO 0
+#define SERR_NSI 1
+#define SERR_EOM 2
+
+static void inline mangle_address(unsigned char *begin,
+ unsigned char *addr,
+ const struct oct1_map *map,
+ u_int16_t *check);
+struct snmp_cnv
+{
+ unsigned int class;
+ unsigned int tag;
+ int syntax;
+};
+
+static struct snmp_cnv snmp_conv [] =
+{
+ {ASN1_UNI, ASN1_NUL, SNMP_NULL},
+ {ASN1_UNI, ASN1_INT, SNMP_INTEGER},
+ {ASN1_UNI, ASN1_OTS, SNMP_OCTETSTR},
+ {ASN1_UNI, ASN1_OTS, SNMP_DISPLAYSTR},
+ {ASN1_UNI, ASN1_OJI, SNMP_OBJECTID},
+ {ASN1_APL, SNMP_IPA, SNMP_IPADDR},
+ {ASN1_APL, SNMP_CNT, SNMP_COUNTER}, /* Counter32 */
+ {ASN1_APL, SNMP_GGE, SNMP_GAUGE}, /* Gauge32 == Unsigned32 */
+ {ASN1_APL, SNMP_TIT, SNMP_TIMETICKS},
+ {ASN1_APL, SNMP_OPQ, SNMP_OPAQUE},
+
+ /* SNMPv2 data types and errors */
+ {ASN1_UNI, ASN1_BTS, SNMP_BITSTR},
+ {ASN1_APL, SNMP_C64, SNMP_COUNTER64},
+ {ASN1_CTX, SERR_NSO, SNMP_NOSUCHOBJECT},
+ {ASN1_CTX, SERR_NSI, SNMP_NOSUCHINSTANCE},
+ {ASN1_CTX, SERR_EOM, SNMP_ENDOFMIBVIEW},
+ {0, 0, -1}
+};
+
+static unsigned char snmp_tag_cls2syntax(unsigned int tag,
+ unsigned int cls,
+ unsigned short *syntax)
+{
+ struct snmp_cnv *cnv;
+
+ cnv = snmp_conv;
+
+ while (cnv->syntax != -1) {
+ if (cnv->tag == tag && cnv->class == cls) {
+ *syntax = cnv->syntax;
+ return 1;
+ }
+ cnv++;
+ }
+ return 0;
+}
+
+static unsigned char snmp_object_decode(struct asn1_ctx *ctx,
+ struct snmp_object **obj)
+{
+ unsigned int cls, con, tag, len, idlen;
+ unsigned short type;
+ unsigned char *eoc, *end, *p;
+ unsigned long *lp, *id;
+ unsigned long ul;
+ long l;
+
+ *obj = NULL;
+ id = NULL;
+
+ if (!asn1_header_decode(ctx, &eoc, &cls, &con, &tag))
+ return 0;
+
+ if (cls != ASN1_UNI || con != ASN1_CON || tag != ASN1_SEQ)
+ return 0;
+
+ if (!asn1_header_decode(ctx, &end, &cls, &con, &tag))
+ return 0;
+
+ if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_OJI)
+ return 0;
+
+ if (!asn1_oid_decode(ctx, end, &id, &idlen))
+ return 0;
+
+ if (!asn1_header_decode(ctx, &end, &cls, &con, &tag)) {
+ kfree(id);
+ return 0;
+ }
+
+ if (con != ASN1_PRI) {
+ kfree(id);
+ return 0;
+ }
+
+ if (!snmp_tag_cls2syntax(tag, cls, &type)) {
+ kfree(id);
+ return 0;
+ }
+
+ switch (type) {
+ case SNMP_INTEGER:
+ len = sizeof(long);
+ if (!asn1_long_decode(ctx, end, &l)) {
+ kfree(id);
+ return 0;
+ }
+ *obj = kmalloc(sizeof(struct snmp_object) + len,
+ GFP_ATOMIC);
+ if (*obj == NULL) {
+ kfree(id);
+ if (net_ratelimit())
+ printk("OOM in bsalg (%d)\n", __LINE__);
+ return 0;
+ }
+ (*obj)->syntax.l[0] = l;
+ break;
+ case SNMP_OCTETSTR:
+ case SNMP_OPAQUE:
+ if (!asn1_octets_decode(ctx, end, &p, &len)) {
+ kfree(id);
+ return 0;
+ }
+ *obj = kmalloc(sizeof(struct snmp_object) + len,
+ GFP_ATOMIC);
+ if (*obj == NULL) {
+ kfree(id);
+ if (net_ratelimit())
+ printk("OOM in bsalg (%d)\n", __LINE__);
+ return 0;
+ }
+ memcpy((*obj)->syntax.c, p, len);
+ kfree(p);
+ break;
+ case SNMP_NULL:
+ case SNMP_NOSUCHOBJECT:
+ case SNMP_NOSUCHINSTANCE:
+ case SNMP_ENDOFMIBVIEW:
+ len = 0;
+ *obj = kmalloc(sizeof(struct snmp_object), GFP_ATOMIC);
+ if (*obj == NULL) {
+ kfree(id);
+ if (net_ratelimit())
+ printk("OOM in bsalg (%d)\n", __LINE__);
+ return 0;
+ }
+ if (!asn1_null_decode(ctx, end)) {
+ kfree(id);
+ kfree(*obj);
+ *obj = NULL;
+ return 0;
+ }
+ break;
+ case SNMP_OBJECTID:
+ if (!asn1_oid_decode(ctx, end, (unsigned long **)&lp, &len)) {
+ kfree(id);
+ return 0;
+ }
+ len *= sizeof(unsigned long);
+ *obj = kmalloc(sizeof(struct snmp_object) + len, GFP_ATOMIC);
+ if (*obj == NULL) {
+ kfree(id);
+ if (net_ratelimit())
+ printk("OOM in bsalg (%d)\n", __LINE__);
+ return 0;
+ }
+ memcpy((*obj)->syntax.ul, lp, len);
+ kfree(lp);
+ break;
+ case SNMP_IPADDR:
+ if (!asn1_octets_decode(ctx, end, &p, &len)) {
+ kfree(id);
+ return 0;
+ }
+ if (len != 4) {
+ kfree(p);
+ kfree(id);
+ return 0;
+ }
+ *obj = kmalloc(sizeof(struct snmp_object) + len, GFP_ATOMIC);
+ if (*obj == NULL) {
+ kfree(p);
+ kfree(id);
+ if (net_ratelimit())
+ printk("OOM in bsalg (%d)\n", __LINE__);
+ return 0;
+ }
+ memcpy((*obj)->syntax.uc, p, len);
+ kfree(p);
+ break;
+ case SNMP_COUNTER:
+ case SNMP_GAUGE:
+ case SNMP_TIMETICKS:
+ len = sizeof(unsigned long);
+ if (!asn1_ulong_decode(ctx, end, &ul)) {
+ kfree(id);
+ return 0;
+ }
+ *obj = kmalloc(sizeof(struct snmp_object) + len, GFP_ATOMIC);
+ if (*obj == NULL) {
+ kfree(id);
+ if (net_ratelimit())
+ printk("OOM in bsalg (%d)\n", __LINE__);
+ return 0;
+ }
+ (*obj)->syntax.ul[0] = ul;
+ break;
+ default:
+ kfree(id);
+ return 0;
+ }
+
+ (*obj)->syntax_len = len;
+ (*obj)->type = type;
+ (*obj)->id = id;
+ (*obj)->id_len = idlen;
+
+ if (!asn1_eoc_decode(ctx, eoc)) {
+ kfree(id);
+ kfree(*obj);
+ *obj = NULL;
+ return 0;
+ }
+ return 1;
+}
+
+static unsigned char snmp_request_decode(struct asn1_ctx *ctx,
+ struct snmp_request *request)
+{
+ unsigned int cls, con, tag;
+ unsigned char *end;
+
+ if (!asn1_header_decode(ctx, &end, &cls, &con, &tag))
+ return 0;
+
+ if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_INT)
+ return 0;
+
+ if (!asn1_ulong_decode(ctx, end, &request->id))
+ return 0;
+
+ if (!asn1_header_decode(ctx, &end, &cls, &con, &tag))
+ return 0;
+
+ if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_INT)
+ return 0;
+
+ if (!asn1_uint_decode(ctx, end, &request->error_status))
+ return 0;
+
+ if (!asn1_header_decode(ctx, &end, &cls, &con, &tag))
+ return 0;
+
+ if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_INT)
+ return 0;
+
+ if (!asn1_uint_decode(ctx, end, &request->error_index))
+ return 0;
+
+ return 1;
+}
+
+/*
+ * Fast checksum update for possibly oddly-aligned UDP byte, from the
+ * code example in the draft.
+ */
+static void fast_csum(unsigned char *csum,
+ const unsigned char *optr,
+ const unsigned char *nptr,
+ int odd)
+{
+ long x, old, new;
+
+ x = csum[0] * 256 + csum[1];
+
+ x =~ x & 0xFFFF;
+
+ if (odd) old = optr[0] * 256;
+ else old = optr[0];
+
+ x -= old & 0xFFFF;
+ if (x <= 0) {
+ x--;
+ x &= 0xFFFF;
+ }
+
+ if (odd) new = nptr[0] * 256;
+ else new = nptr[0];
+
+ x += new & 0xFFFF;
+ if (x & 0x10000) {
+ x++;
+ x &= 0xFFFF;
+ }
+
+ x =~ x & 0xFFFF;
+ csum[0] = x / 256;
+ csum[1] = x & 0xFF;
+}
+
+/*
+ * Mangle IP address.
+ * - begin points to the start of the snmp messgae
+ * - addr points to the start of the address
+ */
+static void inline mangle_address(unsigned char *begin,
+ unsigned char *addr,
+ const struct oct1_map *map,
+ u_int16_t *check)
+{
+ if (map->from == NOCT1(*addr)) {
+ u_int32_t old;
+
+ if (debug)
+ memcpy(&old, (unsigned char *)addr, sizeof(old));
+
+ *addr = map->to;
+
+ /* Update UDP checksum if being used */
+ if (*check) {
+ unsigned char odd = !((addr - begin) % 2);
+
+ fast_csum((unsigned char *)check,
+ &map->from, &map->to, odd);
+
+ }
+
+ if (debug)
+ printk(KERN_DEBUG "bsalg: mapped %u.%u.%u.%u to "
+ "%u.%u.%u.%u\n", NIPQUAD(old), NIPQUAD(*addr));
+ }
+}
+
+static unsigned char snmp_trap_decode(struct asn1_ctx *ctx,
+ struct snmp_v1_trap *trap,
+ const struct oct1_map *map,
+ u_int16_t *check)
+{
+ unsigned int cls, con, tag, len;
+ unsigned char *end;
+
+ if (!asn1_header_decode(ctx, &end, &cls, &con, &tag))
+ return 0;
+
+ if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_OJI)
+ return 0;
+
+ if (!asn1_oid_decode(ctx, end, &trap->id, &trap->id_len))
+ return 0;
+
+ if (!asn1_header_decode(ctx, &end, &cls, &con, &tag))
+ goto err_id_free;
+
+ if (!((cls == ASN1_APL && con == ASN1_PRI && tag == SNMP_IPA) ||
+ (cls == ASN1_UNI && con == ASN1_PRI && tag == ASN1_OTS)))
+ goto err_id_free;
+
+ if (!asn1_octets_decode(ctx, end, (unsigned char **)&trap->ip_address, &len))
+ goto err_id_free;
+
+ /* IPv4 only */
+ if (len != 4)
+ goto err_addr_free;
+
+ mangle_address(ctx->begin, ctx->pointer - 4, map, check);
+
+ if (!asn1_header_decode(ctx, &end, &cls, &con, &tag))
+ goto err_addr_free;
+
+ if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_INT)
+ goto err_addr_free;;
+
+ if (!asn1_uint_decode(ctx, end, &trap->general))
+ goto err_addr_free;;
+
+ if (!asn1_header_decode(ctx, &end, &cls, &con, &tag))
+ goto err_addr_free;
+
+ if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_INT)
+ goto err_addr_free;
+
+ if (!asn1_uint_decode(ctx, end, &trap->specific))
+ goto err_addr_free;
+
+ if (!asn1_header_decode(ctx, &end, &cls, &con, &tag))
+ goto err_addr_free;
+
+ if (!((cls == ASN1_APL && con == ASN1_PRI && tag == SNMP_TIT) ||
+ (cls == ASN1_UNI && con == ASN1_PRI && tag == ASN1_INT)))
+ goto err_addr_free;
+
+ if (!asn1_ulong_decode(ctx, end, &trap->time))
+ goto err_addr_free;
+
+ return 1;
+
+err_id_free:
+ kfree(trap->id);
+
+err_addr_free:
+ kfree((unsigned long *)trap->ip_address);
+
+ return 0;
+}
+
+/*****************************************************************************
+ *
+ * Misc. routines
+ *
+ *****************************************************************************/
+
+static void hex_dump(unsigned char *buf, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ if (i && !(i % 16))
+ printk("\n");
+ printk("%02x ", *(buf + i));
+ }
+ printk("\n");
+}
+
+/*
+ * Parse and mangle SNMP message according to mapping.
+ * (And this is the fucking 'basic' method).
+ */
+static int snmp_parse_mangle(unsigned char *msg,
+ u_int16_t len,
+ const struct oct1_map *map,
+ u_int16_t *check)
+{
+ unsigned char *eoc, *end;
+ unsigned int cls, con, tag, vers, pdutype;
+ struct asn1_ctx ctx;
+ struct asn1_octstr comm;
+ struct snmp_object **obj;
+
+ if (debug > 1)
+ hex_dump(msg, len);
+
+ asn1_open(&ctx, msg, len);
+
+ /*
+ * Start of SNMP message.
+ */
+ if (!asn1_header_decode(&ctx, &eoc, &cls, &con, &tag))
+ return 0;
+ if (cls != ASN1_UNI || con != ASN1_CON || tag != ASN1_SEQ)
+ return 0;
+
+ /*
+ * Version 1 or 2 handled.
+ */
+ if (!asn1_header_decode(&ctx, &end, &cls, &con, &tag))
+ return 0;
+ if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_INT)
+ return 0;
+ if (!asn1_uint_decode (&ctx, end, &vers))
+ return 0;
+ if (debug > 1)
+ printk(KERN_DEBUG "bsalg: snmp version: %u\n", vers + 1);
+ if (vers > 1)
+ return 1;
+
+ /*
+ * Community.
+ */
+ if (!asn1_header_decode (&ctx, &end, &cls, &con, &tag))
+ return 0;
+ if (cls != ASN1_UNI || con != ASN1_PRI || tag != ASN1_OTS)
+ return 0;
+ if (!asn1_octets_decode(&ctx, end, &comm.data, &comm.len))
+ return 0;
+ if (debug > 1) {
+ unsigned int i;
+
+ printk(KERN_DEBUG "bsalg: community: ");
+ for (i = 0; i < comm.len; i++)
+ printk("%c", comm.data[i]);
+ printk("\n");
+ }
+ kfree(comm.data);
+
+ /*
+ * PDU type
+ */
+ if (!asn1_header_decode(&ctx, &eoc, &cls, &con, &pdutype))
+ return 0;
+ if (cls != ASN1_CTX || con != ASN1_CON)
+ return 0;
+ if (debug > 1) {
+ unsigned char *pdus[] = {
+ [SNMP_PDU_GET] = "get",
+ [SNMP_PDU_NEXT] = "get-next",
+ [SNMP_PDU_RESPONSE] = "response",
+ [SNMP_PDU_SET] = "set",
+ [SNMP_PDU_TRAP1] = "trapv1",
+ [SNMP_PDU_BULK] = "bulk",
+ [SNMP_PDU_INFORM] = "inform",
+ [SNMP_PDU_TRAP2] = "trapv2"
+ };
+
+ if (pdutype > SNMP_PDU_TRAP2)
+ printk(KERN_DEBUG "bsalg: bad pdu type %u\n", pdutype);
+ else
+ printk(KERN_DEBUG "bsalg: pdu: %s\n", pdus[pdutype]);
+ }
+ if (pdutype != SNMP_PDU_RESPONSE &&
+ pdutype != SNMP_PDU_TRAP1 && pdutype != SNMP_PDU_TRAP2)
+ return 1;
+
+ /*
+ * Request header or v1 trap
+ */
+ if (pdutype == SNMP_PDU_TRAP1) {
+ struct snmp_v1_trap trap;
+ unsigned char ret = snmp_trap_decode(&ctx, &trap, map, check);
+
+ /* Discard trap allocations regardless */
+ kfree(trap.id);
+ kfree((unsigned long *)trap.ip_address);
+
+ if (!ret)
+ return ret;
+
+ } else {
+ struct snmp_request req;
+
+ if (!snmp_request_decode(&ctx, &req))
+ return 0;
+
+ if (debug > 1)
+ printk(KERN_DEBUG "bsalg: request: id=0x%lx error_status=%u "
+ "error_index=%u\n", req.id, req.error_status,
+ req.error_index);
+ }
+
+ /*
+ * Loop through objects, look for IP addresses to mangle.
+ */
+ if (!asn1_header_decode(&ctx, &eoc, &cls, &con, &tag))
+ return 0;
+
+ if (cls != ASN1_UNI || con != ASN1_CON || tag != ASN1_SEQ)
+ return 0;
+
+ obj = kmalloc(sizeof(struct snmp_object), GFP_ATOMIC);
+ if (obj == NULL) {
+ if (net_ratelimit())
+ printk(KERN_WARNING "OOM in bsalg(%d)\n", __LINE__);
+ return 0;
+ }
+
+ while (!asn1_eoc_decode(&ctx, eoc)) {
+ unsigned int i;
+
+ if (!snmp_object_decode(&ctx, obj)) {
+ if (*obj) {
+ if ((*obj)->id)
+ kfree((*obj)->id);
+ kfree(*obj);
+ }
+ kfree(obj);
+ return 0;
+ }
+
+ if (debug > 1) {
+ printk(KERN_DEBUG "bsalg: object: ");
+ for (i = 0; i < (*obj)->id_len; i++) {
+ if (i > 0)
+ printk(".");
+ printk("%lu", (*obj)->id[i]);
+ }
+ printk(": type=%u\n", (*obj)->type);
+
+ }
+
+ if ((*obj)->type == SNMP_IPADDR)
+ mangle_address(ctx.begin, ctx.pointer - 4 , map, check);
+
+ kfree((*obj)->id);
+ kfree(*obj);
+ }
+ kfree(obj);
+
+ if (!asn1_eoc_decode(&ctx, eoc))
+ return 0;
+
+ return 1;
+}
+
+/*****************************************************************************
+ *
+ * NAT routines.
+ *
+ *****************************************************************************/
+
+/*
+ * SNMP translation routine.
+ */
+static int snmp_translate(struct ip_conntrack *ct,
+ struct ip_nat_info *info,
+ enum ip_conntrack_info ctinfo,
+ unsigned int hooknum,
+ struct sk_buff **pskb)
+{
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct udphdr *udph = (struct udphdr *)((u_int32_t *)iph + iph->ihl);
+ u_int16_t udplen = ntohs(udph->len);
+ u_int16_t paylen = udplen - sizeof(struct udphdr);
+ int dir = CTINFO2DIR(ctinfo);
+ struct oct1_map map;
+
+ /*
+ * Determine mappping for application layer addresses based
+ * on NAT manipulations for the packet.
+ */
+ if (dir == IP_CT_DIR_ORIGINAL) {
+ /* SNAT traps */
+ map.from = NOCT1(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip);
+ map.to = NOCT1(ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip);
+ } else {
+ /* DNAT replies */
+ map.from = NOCT1(ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip);
+ map.to = NOCT1(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip);
+ }
+
+ if (map.from == map.to)
+ return NF_ACCEPT;
+
+ if (!snmp_parse_mangle((unsigned char *)udph + sizeof(struct udphdr),
+ paylen, &map, &udph->check)) {
+ printk(KERN_WARNING "bsalg: parser failed\n");
+ return NF_DROP;
+ }
+ return NF_ACCEPT;
+}
+
+/*
+ * NAT helper function, packets arrive here from NAT code.
+ */
+static unsigned int nat_help(struct ip_conntrack *ct,
+ struct ip_conntrack_expect *exp,
+ struct ip_nat_info *info,
+ enum ip_conntrack_info ctinfo,
+ unsigned int hooknum,
+ struct sk_buff **pskb)
+{
+ int dir = CTINFO2DIR(ctinfo);
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct udphdr *udph = (struct udphdr *)((u_int32_t *)iph + iph->ihl);
+
+ spin_lock_bh(&snmp_lock);
+
+ /*
+ * Translate snmp replies on pre-routing (DNAT) and snmp traps
+ * on post routing (SNAT).
+ */
+ if (!((dir == IP_CT_DIR_REPLY && hooknum == NF_IP_PRE_ROUTING &&
+ udph->source == ntohs(SNMP_PORT)) ||
+ (dir == IP_CT_DIR_ORIGINAL && hooknum == NF_IP_POST_ROUTING &&
+ udph->dest == ntohs(SNMP_TRAP_PORT)))) {
+ spin_unlock_bh(&snmp_lock);
+ return NF_ACCEPT;
+ }
+
+ if (debug > 1) {
+ printk(KERN_DEBUG "bsalg: dir=%s hook=%d manip=%s len=%d "
+ "src=%u.%u.%u.%u:%u dst=%u.%u.%u.%u:%u "
+ "osrc=%u.%u.%u.%u odst=%u.%u.%u.%u "
+ "rsrc=%u.%u.%u.%u rdst=%u.%u.%u.%u "
+ "\n",
+ dir == IP_CT_DIR_REPLY ? "reply" : "orig", hooknum,
+ HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC ? "snat" :
+ "dnat", (*pskb)->len,
+ NIPQUAD(iph->saddr), ntohs(udph->source),
+ NIPQUAD(iph->daddr), ntohs(udph->dest),
+ NIPQUAD(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip),
+ NIPQUAD(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip),
+ NIPQUAD(ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip),
+ NIPQUAD(ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip));
+ }
+
+ /*
+ * Make sure the packet length is ok. So far, we were only guaranteed
+ * to have a valid length IP header plus 8 bytes, which means we have
+ * enough room for a UDP header. Just verify the UDP length field so we
+ * can mess around with the payload.
+ */
+ if (ntohs(udph->len) == (*pskb)->len - (iph->ihl << 2)) {
+ int ret = snmp_translate(ct, info, ctinfo, hooknum, pskb);
+ spin_unlock_bh(&snmp_lock);
+ return ret;
+ }
+
+ if (net_ratelimit())
+ printk(KERN_WARNING "bsalg: dropping malformed packet "
+ "src=%u.%u.%u.%u dst=%u.%u.%u.%u\n",
+ NIPQUAD(iph->saddr), NIPQUAD(iph->daddr));
+ spin_unlock_bh(&snmp_lock);
+ return NF_DROP;
+}
+
+static struct ip_nat_helper snmp = {
+ { NULL, NULL },
+ "snmp",
+ IP_NAT_HELPER_F_STANDALONE,
+ THIS_MODULE,
+ { { 0, { .udp = { __constant_htons(SNMP_PORT) } } },
+ { 0, { 0 }, IPPROTO_UDP } },
+ { { 0, { .udp = { 0xFFFF } } },
+ { 0, { 0 }, 0xFFFF } },
+ nat_help, NULL };
+
+static struct ip_nat_helper snmp_trap = {
+ { NULL, NULL },
+ "snmp_trap",
+ IP_NAT_HELPER_F_STANDALONE,
+ THIS_MODULE,
+ { { 0, { .udp = { __constant_htons(SNMP_TRAP_PORT) } } },
+ { 0, { 0 }, IPPROTO_UDP } },
+ { { 0, { .udp = { 0xFFFF } } },
+ { 0, { 0 }, 0xFFFF } },
+ nat_help, NULL };
+
+/*****************************************************************************
+ *
+ * Module stuff.
+ *
+ *****************************************************************************/
+
+static int __init init(void)
+{
+ int ret = 0;
+
+ ret = ip_nat_helper_register(&snmp);
+ if (ret < 0)
+ return ret;
+ ret = ip_nat_helper_register(&snmp_trap);
+ if (ret < 0) {
+ ip_nat_helper_unregister(&snmp);
+ return ret;
+ }
+ return ret;
+}
+
+static void __exit fini(void)
+{
+ ip_nat_helper_unregister(&snmp);
+ ip_nat_helper_unregister(&snmp_trap);
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+}
+
+module_init(init);
+module_exit(fini);
+
+MODULE_PARM(debug, "i");
+MODULE_DESCRIPTION("Basic SNMP Application Layer Gateway");
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_standalone.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_standalone.c
new file mode 100644
index 0000000..21d60ef
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_standalone.c
@@ -0,0 +1,368 @@
+/* This file contains all the functions required for the standalone
+ ip_nat module.
+
+ These are not required by the compatibility layer.
+*/
+
+/* (c) 1999 Paul `Rusty' Russell. Licenced under the GNU General
+ * Public Licence.
+ *
+ * 23 Apr 2001: Harald Welte <laforge@gnumonks.org>
+ * - new API and handling of conntrack/nat helpers
+ * - now capable of multiple expectations for one master
+ * */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/ip.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <net/checksum.h>
+#include <linux/spinlock.h>
+#include <linux/version.h>
+#include <linux/brlock.h>
+
+#define ASSERT_READ_LOCK(x) MUST_BE_READ_LOCKED(&ip_nat_lock)
+#define ASSERT_WRITE_LOCK(x) MUST_BE_WRITE_LOCKED(&ip_nat_lock)
+
+#include <linux/netfilter_ipv4/ip_nat.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+#include <linux/netfilter_ipv4/ip_nat_protocol.h>
+#include <linux/netfilter_ipv4/ip_nat_core.h>
+#include <linux/netfilter_ipv4/ip_nat_helper.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ip_conntrack_core.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+#define HOOKNAME(hooknum) ((hooknum) == NF_IP_POST_ROUTING ? "POST_ROUTING" \
+ : ((hooknum) == NF_IP_PRE_ROUTING ? "PRE_ROUTING" \
+ : ((hooknum) == NF_IP_LOCAL_OUT ? "LOCAL_OUT" \
+ : ((hooknum) == NF_IP_LOCAL_IN ? "LOCAL_IN" \
+ : "*ERROR*")))
+
+static inline int call_expect(struct ip_conntrack *master,
+ struct sk_buff **pskb,
+ unsigned int hooknum,
+ struct ip_conntrack *ct,
+ struct ip_nat_info *info)
+{
+ return master->nat.info.helper->expect(pskb, hooknum, ct, info);
+}
+
+static unsigned int
+ip_nat_fn(unsigned int hooknum,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ struct ip_conntrack *ct;
+ enum ip_conntrack_info ctinfo;
+ struct ip_nat_info *info;
+ /* maniptype == SRC for postrouting. */
+ enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum);
+
+ /* We never see fragments: conntrack defrags on pre-routing
+ and local-out, and ip_nat_out protects post-routing. */
+ IP_NF_ASSERT(!((*pskb)->nh.iph->frag_off
+ & htons(IP_MF|IP_OFFSET)));
+
+ (*pskb)->nfcache |= NFC_UNKNOWN;
+
+ /* If we had a hardware checksum before, it's now invalid */
+ if ((*pskb)->ip_summed == CHECKSUM_HW)
+ (*pskb)->ip_summed = CHECKSUM_NONE;
+
+ ct = ip_conntrack_get(*pskb, &ctinfo);
+ /* Can't track? It's not due to stress, or conntrack would
+ have dropped it. Hence it's the user's responsibilty to
+ packet filter it out, or implement conntrack/NAT for that
+ protocol. 8) --RR */
+ if (!ct) {
+ /* Exception: ICMP redirect to new connection (not in
+ hash table yet). We must not let this through, in
+ case we're doing NAT to the same network. */
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct icmphdr *hdr = (struct icmphdr *)
+ ((u_int32_t *)iph + iph->ihl);
+ if (iph->protocol == IPPROTO_ICMP
+ && hdr->type == ICMP_REDIRECT)
+ return NF_DROP;
+ return NF_ACCEPT;
+ }
+
+ switch (ctinfo) {
+ case IP_CT_RELATED:
+ case IP_CT_RELATED+IP_CT_IS_REPLY:
+ if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
+ return icmp_reply_translation(*pskb, ct, hooknum,
+ CTINFO2DIR(ctinfo));
+ }
+ /* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */
+ case IP_CT_NEW:
+ info = &ct->nat.info;
+
+ WRITE_LOCK(&ip_nat_lock);
+ /* Seen it before? This can happen for loopback, retrans,
+ or local packets.. */
+ if (!(info->initialized & (1 << maniptype))) {
+ unsigned int ret;
+
+ if (ct->master
+ && master_ct(ct)->nat.info.helper
+ && master_ct(ct)->nat.info.helper->expect) {
+ ret = call_expect(master_ct(ct), pskb,
+ hooknum, ct, info);
+ } else {
+ /* LOCAL_IN hook doesn't have a chain! */
+ if (hooknum == NF_IP_LOCAL_IN)
+ ret = alloc_null_binding(ct, info,
+ hooknum);
+ else
+ ret = ip_nat_rule_find(pskb, hooknum,
+ in, out,
+ ct, info);
+ }
+
+ if (ret != NF_ACCEPT) {
+ WRITE_UNLOCK(&ip_nat_lock);
+ return ret;
+ }
+ } else
+ DEBUGP("Already setup manip %s for ct %p\n",
+ maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST",
+ ct);
+ WRITE_UNLOCK(&ip_nat_lock);
+ break;
+
+ default:
+ /* ESTABLISHED */
+ IP_NF_ASSERT(ctinfo == IP_CT_ESTABLISHED
+ || ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));
+ info = &ct->nat.info;
+ }
+
+ IP_NF_ASSERT(info);
+ return do_bindings(ct, ctinfo, info, hooknum, pskb);
+}
+
+static unsigned int
+ip_nat_in(unsigned int hooknum,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ u_int32_t saddr, daddr;
+ unsigned int ret;
+
+ saddr = (*pskb)->nh.iph->saddr;
+ daddr = (*pskb)->nh.iph->daddr;
+
+ ret = ip_nat_fn(hooknum, pskb, in, out, okfn);
+ if (ret != NF_DROP && ret != NF_STOLEN
+ && ((*pskb)->nh.iph->saddr != saddr
+ || (*pskb)->nh.iph->daddr != daddr)) {
+ dst_release((*pskb)->dst);
+ (*pskb)->dst = NULL;
+ }
+ return ret;
+}
+
+static unsigned int
+ip_nat_out(unsigned int hooknum,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ /* root is playing with raw sockets. */
+ if ((*pskb)->len < sizeof(struct iphdr)
+ || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))
+ return NF_ACCEPT;
+
+ /* We can hit fragment here; forwarded packets get
+ defragmented by connection tracking coming in, then
+ fragmented (grr) by the forward code.
+
+ In future: If we have nfct != NULL, AND we have NAT
+ initialized, AND there is no helper, then we can do full
+ NAPT on the head, and IP-address-only NAT on the rest.
+
+ I'm starting to have nightmares about fragments. */
+
+ if ((*pskb)->nh.iph->frag_off & htons(IP_MF|IP_OFFSET)) {
+ *pskb = ip_ct_gather_frags(*pskb, IP_DEFRAG_NAT_OUT);
+
+ if (!*pskb)
+ return NF_STOLEN;
+ }
+
+ return ip_nat_fn(hooknum, pskb, in, out, okfn);
+}
+
+static unsigned int
+ip_nat_local_fn(unsigned int hooknum,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ u_int32_t saddr, daddr;
+ unsigned int ret;
+
+ /* root is playing with raw sockets. */
+ if ((*pskb)->len < sizeof(struct iphdr)
+ || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))
+ return NF_ACCEPT;
+
+ saddr = (*pskb)->nh.iph->saddr;
+ daddr = (*pskb)->nh.iph->daddr;
+
+ ret = ip_nat_fn(hooknum, pskb, in, out, okfn);
+ if (ret != NF_DROP && ret != NF_STOLEN
+ && ((*pskb)->nh.iph->saddr != saddr
+ || (*pskb)->nh.iph->daddr != daddr))
+ return ip_route_me_harder(pskb) == 0 ? ret : NF_DROP;
+ return ret;
+}
+
+/* We must be after connection tracking and before packet filtering. */
+
+/* Before packet filtering, change destination */
+static struct nf_hook_ops ip_nat_in_ops
+= { { NULL, NULL }, ip_nat_in, PF_INET, NF_IP_PRE_ROUTING, NF_IP_PRI_NAT_DST };
+/* After packet filtering, change source */
+static struct nf_hook_ops ip_nat_out_ops
+= { { NULL, NULL }, ip_nat_out, PF_INET, NF_IP_POST_ROUTING, NF_IP_PRI_NAT_SRC};
+/* Before packet filtering, change destination */
+static struct nf_hook_ops ip_nat_local_out_ops
+= { { NULL, NULL }, ip_nat_local_fn, PF_INET, NF_IP_LOCAL_OUT, NF_IP_PRI_NAT_DST };
+/* After packet filtering, change source for reply packets of LOCAL_OUT DNAT */
+static struct nf_hook_ops ip_nat_local_in_ops
+= { { NULL, NULL }, ip_nat_fn, PF_INET, NF_IP_LOCAL_IN, NF_IP_PRI_NAT_SRC };
+
+/* Protocol registration. */
+int ip_nat_protocol_register(struct ip_nat_protocol *proto)
+{
+ int ret = 0;
+ struct list_head *i;
+
+ WRITE_LOCK(&ip_nat_lock);
+ for (i = protos.next; i != &protos; i = i->next) {
+ if (((struct ip_nat_protocol *)i)->protonum
+ == proto->protonum) {
+ ret = -EBUSY;
+ goto out;
+ }
+ }
+
+ list_prepend(&protos, proto);
+ MOD_INC_USE_COUNT;
+
+ out:
+ WRITE_UNLOCK(&ip_nat_lock);
+ return ret;
+}
+
+/* Noone stores the protocol anywhere; simply delete it. */
+void ip_nat_protocol_unregister(struct ip_nat_protocol *proto)
+{
+ WRITE_LOCK(&ip_nat_lock);
+ LIST_DELETE(&protos, proto);
+ WRITE_UNLOCK(&ip_nat_lock);
+
+ /* Someone could be still looking at the proto in a bh. */
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+
+ MOD_DEC_USE_COUNT;
+}
+
+static int init_or_cleanup(int init)
+{
+ int ret = 0;
+
+ if (!init) goto cleanup;
+
+ ret = ip_nat_rule_init();
+ if (ret < 0) {
+ printk("ip_nat_init: can't setup rules.\n");
+ goto cleanup_nothing;
+ }
+ ret = ip_nat_init();
+ if (ret < 0) {
+ printk("ip_nat_init: can't setup rules.\n");
+ goto cleanup_rule_init;
+ }
+ ret = nf_register_hook(&ip_nat_in_ops);
+ if (ret < 0) {
+ printk("ip_nat_init: can't register in hook.\n");
+ goto cleanup_nat;
+ }
+ ret = nf_register_hook(&ip_nat_out_ops);
+ if (ret < 0) {
+ printk("ip_nat_init: can't register out hook.\n");
+ goto cleanup_inops;
+ }
+ ret = nf_register_hook(&ip_nat_local_out_ops);
+ if (ret < 0) {
+ printk("ip_nat_init: can't register local out hook.\n");
+ goto cleanup_outops;
+ }
+ ret = nf_register_hook(&ip_nat_local_in_ops);
+ if (ret < 0) {
+ printk("ip_nat_init: can't register local in hook.\n");
+ goto cleanup_localoutops;
+ }
+ return ret;
+
+ cleanup:
+ nf_unregister_hook(&ip_nat_local_in_ops);
+ cleanup_localoutops:
+ nf_unregister_hook(&ip_nat_local_out_ops);
+ cleanup_outops:
+ nf_unregister_hook(&ip_nat_out_ops);
+ cleanup_inops:
+ nf_unregister_hook(&ip_nat_in_ops);
+ cleanup_nat:
+ ip_nat_cleanup();
+ cleanup_rule_init:
+ ip_nat_rule_cleanup();
+ cleanup_nothing:
+ MUST_BE_READ_WRITE_UNLOCKED(&ip_nat_lock);
+ return ret;
+}
+
+static int __init init(void)
+{
+ return init_or_cleanup(1);
+}
+
+static void __exit fini(void)
+{
+ init_or_cleanup(0);
+}
+
+module_init(init);
+module_exit(fini);
+
+EXPORT_SYMBOL(ip_nat_setup_info);
+EXPORT_SYMBOL(ip_nat_protocol_register);
+EXPORT_SYMBOL(ip_nat_protocol_unregister);
+EXPORT_SYMBOL(ip_nat_helper_register);
+EXPORT_SYMBOL(ip_nat_helper_unregister);
+EXPORT_SYMBOL(ip_nat_cheat_check);
+EXPORT_SYMBOL(ip_nat_mangle_tcp_packet);
+EXPORT_SYMBOL(ip_nat_mangle_udp_packet);
+EXPORT_SYMBOL(ip_nat_used_tuple);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_tftp.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_tftp.c
new file mode 100644
index 0000000..936999e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_nat_tftp.c
@@ -0,0 +1,195 @@
+/*
+ * Licensed under GNU GPL version 2 Copyright Magnus Boden <mb@ozaba.mine.nu>
+ * Version: 0.0.7
+ *
+ * Thu 21 Mar 2002 Harald Welte <laforge@gnumonks.org>
+ * - Port to newnat API
+ *
+ * This module currently supports DNAT:
+ * iptables -t nat -A PREROUTING -d x.x.x.x -j DNAT --to-dest x.x.x.y
+ *
+ * and SNAT:
+ * iptables -t nat -A POSTROUTING { -j MASQUERADE , -j SNAT --to-source x.x.x.x }
+ *
+ * It has not been tested with
+ * -j SNAT --to-source x.x.x.x-x.x.x.y since I only have one external ip
+ * If you do test this please let me know if it works or not.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_conntrack_tftp.h>
+#include <linux/netfilter_ipv4/ip_nat_helper.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+
+MODULE_AUTHOR("Magnus Boden <mb@ozaba.mine.nu>");
+MODULE_DESCRIPTION("Netfilter NAT helper for tftp");
+MODULE_LICENSE("GPL");
+
+#define MAX_PORTS 8
+
+static int ports[MAX_PORTS];
+static int ports_c = 0;
+#ifdef MODULE_PARM
+MODULE_PARM(ports,"1-" __MODULE_STRING(MAX_PORTS) "i");
+MODULE_PARM_DESC(ports, "port numbers of tftp servers");
+#endif
+
+#if 0
+#define DEBUGP(format, args...) printk(__FILE__ ":" __FUNCTION__ ": " \
+ format, ## args)
+#else
+#define DEBUGP(format, args...)
+#endif
+static unsigned int
+tftp_nat_help(struct ip_conntrack *ct,
+ struct ip_conntrack_expect *exp,
+ struct ip_nat_info *info,
+ enum ip_conntrack_info ctinfo,
+ unsigned int hooknum,
+ struct sk_buff **pskb)
+{
+ int dir = CTINFO2DIR(ctinfo);
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct udphdr *udph = (void *)iph + iph->ihl * 4;
+ struct tftphdr *tftph = (void *)udph + 8;
+ struct ip_conntrack_tuple repl;
+
+ if (!((hooknum == NF_IP_POST_ROUTING && dir == IP_CT_DIR_ORIGINAL)
+ || (hooknum == NF_IP_PRE_ROUTING && dir == IP_CT_DIR_REPLY)))
+ return NF_ACCEPT;
+
+ if (!exp) {
+ DEBUGP("no conntrack expectation to modify\n");
+ return NF_ACCEPT;
+ }
+
+ switch (ntohs(tftph->opcode)) {
+ /* RRQ and WRQ works the same way */
+ case TFTP_OPCODE_READ:
+ case TFTP_OPCODE_WRITE:
+ repl = ct->tuplehash[IP_CT_DIR_REPLY].tuple;
+ DEBUGP("");
+ DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
+ DUMP_TUPLE(&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
+ DEBUGP("expecting: ");
+ DUMP_TUPLE(&repl);
+ DUMP_TUPLE(&exp->mask);
+ ip_conntrack_change_expect(exp, &repl);
+ break;
+ default:
+ DEBUGP("Unknown opcode\n");
+ }
+
+ return NF_ACCEPT;
+}
+
+static unsigned int
+tftp_nat_expected(struct sk_buff **pskb,
+ unsigned int hooknum,
+ struct ip_conntrack *ct,
+ struct ip_nat_info *info)
+{
+ const struct ip_conntrack *master = ct->master->expectant;
+ const struct ip_conntrack_tuple *orig =
+ &master->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
+ struct ip_nat_multi_range mr;
+#if 0
+ const struct ip_conntrack_tuple *repl =
+ &master->tuplehash[IP_CT_DIR_REPLY].tuple;
+ struct iphdr *iph = (*pskb)->nh.iph;
+ struct udphdr *udph = (void *)iph + iph->ihl*4;
+#endif
+
+ IP_NF_ASSERT(info);
+ IP_NF_ASSERT(master);
+ IP_NF_ASSERT(!(info->initialized & (1 << HOOK2MANIP(hooknum))));
+
+ mr.rangesize = 1;
+ mr.range[0].flags = IP_NAT_RANGE_MAP_IPS;
+
+ if (HOOK2MANIP(hooknum) == IP_NAT_MANIP_SRC) {
+ mr.range[0].min_ip = mr.range[0].max_ip = orig->dst.ip;
+ DEBUGP("orig: %u.%u.%u.%u:%u <-> %u.%u.%u.%u:%u "
+ "newsrc: %u.%u.%u.%u\n",
+ NIPQUAD((*pskb)->nh.iph->saddr), ntohs(udph->source),
+ NIPQUAD((*pskb)->nh.iph->daddr), ntohs(udph->dest),
+ NIPQUAD(orig->dst.ip));
+ } else {
+ mr.range[0].min_ip = mr.range[0].max_ip = orig->src.ip;
+ mr.range[0].min.udp.port = mr.range[0].max.udp.port =
+ orig->src.u.udp.port;
+ mr.range[0].flags |= IP_NAT_RANGE_PROTO_SPECIFIED;
+
+ DEBUGP("orig: %u.%u.%u.%u:%u <-> %u.%u.%u.%u:%u "
+ "newdst: %u.%u.%u.%u:%u\n",
+ NIPQUAD((*pskb)->nh.iph->saddr), ntohs(udph->source),
+ NIPQUAD((*pskb)->nh.iph->daddr), ntohs(udph->dest),
+ NIPQUAD(orig->src.ip), ntohs(orig->src.u.udp.port));
+ }
+
+ return ip_nat_setup_info(ct,&mr,hooknum);
+}
+
+static struct ip_nat_helper tftp[MAX_PORTS];
+static char tftp_names[MAX_PORTS][10];
+
+static void fini(void)
+{
+ int i;
+
+ for (i = 0 ; i < ports_c; i++) {
+ DEBUGP("unregistering helper for port %d\n", ports[i]);
+ ip_nat_helper_unregister(&tftp[i]);
+ }
+}
+
+static int __init init(void)
+{
+ int i, ret = 0;
+ char *tmpname;
+
+ if (!ports[0])
+ ports[0] = TFTP_PORT;
+
+ for (i = 0 ; (i < MAX_PORTS) && ports[i] ; i++) {
+ tftp[i].tuple.dst.protonum = IPPROTO_UDP;
+ tftp[i].tuple.src.u.udp.port = htons(ports[i]);
+ tftp[i].mask.dst.protonum = 0xFFFF;
+ tftp[i].mask.src.u.udp.port = 0xFFFF;
+ tftp[i].help = tftp_nat_help;
+ tftp[i].flags = 0;
+ tftp[i].me = THIS_MODULE;
+ tftp[i].expect = tftp_nat_expected;
+
+ tmpname = &tftp_names[i][0];
+ if (ports[i] == TFTP_PORT)
+ sprintf(tmpname, "tftp");
+ else
+ sprintf(tmpname, "tftp-%d", i);
+ tftp[i].name = tmpname;
+
+ DEBUGP("ip_nat_tftp: registering for port %d: name %s\n",
+ ports[i], tftp[i].name);
+ ret = ip_nat_helper_register(&tftp[i]);
+
+ if (ret) {
+ printk("ip_nat_tftp: unable to register for port %d\n",
+ ports[i]);
+ fini();
+ return ret;
+ }
+ ports_c++;
+ }
+ return ret;
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_queue.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_queue.c
new file mode 100644
index 0000000..e8b6601
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_queue.c
@@ -0,0 +1,707 @@
+/*
+ * This is a module which is used for queueing IPv4 packets and
+ * communicating with userspace via netlink.
+ *
+ * (C) 2000-2002 James Morris, this code is GPL.
+ *
+ * 2000-03-27: Simplified code (thanks to Andi Kleen for clues).
+ * 2000-05-20: Fixed notifier problems (following Miguel Freitas' report).
+ * 2000-06-19: Fixed so nfmark is copied to metadata (reported by Sebastian
+ * Zander).
+ * 2000-08-01: Added Nick Williams' MAC support.
+ * 2002-06-25: Code cleanup.
+ *
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+#include <linux/ip.h>
+#include <linux/notifier.h>
+#include <linux/netdevice.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4/ip_queue.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netlink.h>
+#include <linux/spinlock.h>
+#include <linux/brlock.h>
+#include <linux/sysctl.h>
+#include <linux/proc_fs.h>
+#include <net/sock.h>
+#include <net/route.h>
+
+#define IPQ_QMAX_DEFAULT 1024
+#define IPQ_PROC_FS_NAME "ip_queue"
+#define NET_IPQ_QMAX 2088
+#define NET_IPQ_QMAX_NAME "ip_queue_maxlen"
+
+struct ipq_rt_info {
+ __u8 tos;
+ __u32 daddr;
+ __u32 saddr;
+};
+
+struct ipq_queue_entry {
+ struct list_head list;
+ struct nf_info *info;
+ struct sk_buff *skb;
+ struct ipq_rt_info rt_info;
+};
+
+typedef int (*ipq_cmpfn)(struct ipq_queue_entry *, unsigned long);
+
+static unsigned char copy_mode = IPQ_COPY_NONE;
+static unsigned int queue_maxlen = IPQ_QMAX_DEFAULT;
+static rwlock_t queue_lock = RW_LOCK_UNLOCKED;
+static int peer_pid;
+static unsigned int copy_range;
+static unsigned int queue_total;
+static struct sock *ipqnl;
+static LIST_HEAD(queue_list);
+static DECLARE_MUTEX(ipqnl_sem);
+
+static void
+ipq_issue_verdict(struct ipq_queue_entry *entry, int verdict)
+{
+ nf_reinject(entry->skb, entry->info, verdict);
+ kfree(entry);
+}
+
+static inline int
+__ipq_enqueue_entry(struct ipq_queue_entry *entry)
+{
+ if (queue_total >= queue_maxlen) {
+ if (net_ratelimit())
+ printk(KERN_WARNING "ip_queue: full at %d entries, "
+ "dropping packet(s).\n", queue_total);
+ return -ENOSPC;
+ }
+ list_add(&entry->list, &queue_list);
+ queue_total++;
+ return 0;
+}
+
+/*
+ * Find and return a queued entry matched by cmpfn, or return the last
+ * entry if cmpfn is NULL.
+ */
+static inline struct ipq_queue_entry *
+__ipq_find_entry(ipq_cmpfn cmpfn, unsigned long data)
+{
+ struct list_head *p;
+
+ list_for_each_prev(p, &queue_list) {
+ struct ipq_queue_entry *entry = (struct ipq_queue_entry *)p;
+
+ if (!cmpfn || cmpfn(entry, data))
+ return entry;
+ }
+ return NULL;
+}
+
+static inline void
+__ipq_dequeue_entry(struct ipq_queue_entry *entry)
+{
+ list_del(&entry->list);
+ queue_total--;
+}
+
+static inline struct ipq_queue_entry *
+__ipq_find_dequeue_entry(ipq_cmpfn cmpfn, unsigned long data)
+{
+ struct ipq_queue_entry *entry;
+
+ entry = __ipq_find_entry(cmpfn, data);
+ if (entry == NULL)
+ return NULL;
+
+ __ipq_dequeue_entry(entry);
+ return entry;
+}
+
+
+static inline void
+__ipq_flush(int verdict)
+{
+ struct ipq_queue_entry *entry;
+
+ while ((entry = __ipq_find_dequeue_entry(NULL, 0)))
+ ipq_issue_verdict(entry, verdict);
+}
+
+static inline int
+__ipq_set_mode(unsigned char mode, unsigned int range)
+{
+ int status = 0;
+
+ switch(mode) {
+ case IPQ_COPY_NONE:
+ case IPQ_COPY_META:
+ copy_mode = mode;
+ copy_range = 0;
+ break;
+
+ case IPQ_COPY_PACKET:
+ copy_mode = mode;
+ copy_range = range;
+ if (copy_range > 0xFFFF)
+ copy_range = 0xFFFF;
+ break;
+
+ default:
+ status = -EINVAL;
+
+ }
+ return status;
+}
+
+static inline void
+__ipq_reset(void)
+{
+ peer_pid = 0;
+ __ipq_set_mode(IPQ_COPY_NONE, 0);
+ __ipq_flush(NF_DROP);
+}
+
+static struct ipq_queue_entry *
+ipq_find_dequeue_entry(ipq_cmpfn cmpfn, unsigned long data)
+{
+ struct ipq_queue_entry *entry;
+
+ write_lock_bh(&queue_lock);
+ entry = __ipq_find_dequeue_entry(cmpfn, data);
+ write_unlock_bh(&queue_lock);
+ return entry;
+}
+
+static void
+ipq_flush(int verdict)
+{
+ write_lock_bh(&queue_lock);
+ __ipq_flush(verdict);
+ write_unlock_bh(&queue_lock);
+}
+
+static struct sk_buff *
+ipq_build_packet_message(struct ipq_queue_entry *entry, int *errp)
+{
+ unsigned char *old_tail;
+ size_t size = 0;
+ size_t data_len = 0;
+ struct sk_buff *skb;
+ struct ipq_packet_msg *pmsg;
+ struct nlmsghdr *nlh;
+
+ read_lock_bh(&queue_lock);
+
+ switch (copy_mode) {
+ case IPQ_COPY_META:
+ case IPQ_COPY_NONE:
+ size = NLMSG_SPACE(sizeof(*pmsg));
+ data_len = 0;
+ break;
+
+ case IPQ_COPY_PACKET:
+ if (copy_range == 0 || copy_range > entry->skb->len)
+ data_len = entry->skb->len;
+ else
+ data_len = copy_range;
+
+ size = NLMSG_SPACE(sizeof(*pmsg) + data_len);
+ break;
+
+ default:
+ *errp = -EINVAL;
+ read_unlock_bh(&queue_lock);
+ return NULL;
+ }
+
+ read_unlock_bh(&queue_lock);
+
+ skb = alloc_skb(size, GFP_ATOMIC);
+ if (!skb)
+ goto nlmsg_failure;
+
+ old_tail= skb->tail;
+ nlh = NLMSG_PUT(skb, 0, 0, IPQM_PACKET, size - sizeof(*nlh));
+ pmsg = NLMSG_DATA(nlh);
+ memset(pmsg, 0, sizeof(*pmsg));
+
+ pmsg->packet_id = (unsigned long )entry;
+ pmsg->data_len = data_len;
+ pmsg->timestamp_sec = entry->skb->stamp.tv_sec;
+ pmsg->timestamp_usec = entry->skb->stamp.tv_usec;
+ pmsg->mark = entry->skb->nfmark;
+ pmsg->hook = entry->info->hook;
+ pmsg->hw_protocol = entry->skb->protocol;
+
+ if (entry->info->indev)
+ strcpy(pmsg->indev_name, entry->info->indev->name);
+ else
+ pmsg->indev_name[0] = '\0';
+
+ if (entry->info->outdev)
+ strcpy(pmsg->outdev_name, entry->info->outdev->name);
+ else
+ pmsg->outdev_name[0] = '\0';
+
+ if (entry->info->indev && entry->skb->dev) {
+ pmsg->hw_type = entry->skb->dev->type;
+ if (entry->skb->dev->hard_header_parse)
+ pmsg->hw_addrlen =
+ entry->skb->dev->hard_header_parse(entry->skb,
+ pmsg->hw_addr);
+ }
+
+ if (data_len)
+ memcpy(pmsg->payload, entry->skb->data, data_len);
+
+ nlh->nlmsg_len = skb->tail - old_tail;
+ return skb;
+
+nlmsg_failure:
+ if (skb)
+ kfree_skb(skb);
+ *errp = -EINVAL;
+ printk(KERN_ERR "ip_queue: error creating packet message\n");
+ return NULL;
+}
+
+static int
+ipq_enqueue_packet(struct sk_buff *skb, struct nf_info *info, void *data)
+{
+ int status = -EINVAL;
+ struct sk_buff *nskb;
+ struct ipq_queue_entry *entry;
+
+ if (copy_mode == IPQ_COPY_NONE)
+ return -EAGAIN;
+
+ entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
+ if (entry == NULL) {
+ printk(KERN_ERR "ip_queue: OOM in ipq_enqueue_packet()\n");
+ return -ENOMEM;
+ }
+
+ entry->info = info;
+ entry->skb = skb;
+
+ if (entry->info->hook == NF_IP_LOCAL_OUT) {
+ struct iphdr *iph = skb->nh.iph;
+
+ entry->rt_info.tos = iph->tos;
+ entry->rt_info.daddr = iph->daddr;
+ entry->rt_info.saddr = iph->saddr;
+ }
+
+ nskb = ipq_build_packet_message(entry, &status);
+ if (nskb == NULL)
+ goto err_out_free;
+
+ write_lock_bh(&queue_lock);
+
+ if (!peer_pid)
+ goto err_out_free_nskb;
+
+ /* netlink_unicast will either free the nskb or attach it to a socket */
+ status = netlink_unicast(ipqnl, nskb, peer_pid, MSG_DONTWAIT);
+ if (status < 0)
+ goto err_out_unlock;
+
+ status = __ipq_enqueue_entry(entry);
+ if (status < 0)
+ goto err_out_unlock;
+
+ write_unlock_bh(&queue_lock);
+ return status;
+
+err_out_free_nskb:
+ kfree_skb(nskb);
+
+err_out_unlock:
+ write_unlock_bh(&queue_lock);
+
+err_out_free:
+ kfree(entry);
+ return status;
+}
+
+static int
+ipq_mangle_ipv4(ipq_verdict_msg_t *v, struct ipq_queue_entry *e)
+{
+ int diff;
+ struct iphdr *user_iph = (struct iphdr *)v->payload;
+
+ if (v->data_len < sizeof(*user_iph))
+ return 0;
+ diff = v->data_len - e->skb->len;
+ if (diff < 0)
+ skb_trim(e->skb, v->data_len);
+ else if (diff > 0) {
+ if (v->data_len > 0xFFFF)
+ return -EINVAL;
+ if (diff > skb_tailroom(e->skb)) {
+ struct sk_buff *newskb;
+
+ newskb = skb_copy_expand(e->skb,
+ skb_headroom(e->skb),
+ diff,
+ GFP_ATOMIC);
+ if (newskb == NULL) {
+ printk(KERN_WARNING "ip_queue: OOM "
+ "in mangle, dropping packet\n");
+ return -ENOMEM;
+ }
+ if (e->skb->sk)
+ skb_set_owner_w(newskb, e->skb->sk);
+ kfree_skb(e->skb);
+ e->skb = newskb;
+ }
+ skb_put(e->skb, diff);
+ }
+ memcpy(e->skb->data, v->payload, v->data_len);
+ e->skb->nfcache |= NFC_ALTERED;
+
+ /*
+ * Extra routing may needed on local out, as the QUEUE target never
+ * returns control to the table.
+ */
+ if (e->info->hook == NF_IP_LOCAL_OUT) {
+ struct iphdr *iph = e->skb->nh.iph;
+
+ if (!(iph->tos == e->rt_info.tos
+ && iph->daddr == e->rt_info.daddr
+ && iph->saddr == e->rt_info.saddr))
+ return ip_route_me_harder(&e->skb);
+ }
+ return 0;
+}
+
+static inline int
+id_cmp(struct ipq_queue_entry *e, unsigned long id)
+{
+ return (id == (unsigned long )e);
+}
+
+static int
+ipq_set_verdict(struct ipq_verdict_msg *vmsg, unsigned int len)
+{
+ struct ipq_queue_entry *entry;
+
+ if (vmsg->value > NF_MAX_VERDICT)
+ return -EINVAL;
+
+ entry = ipq_find_dequeue_entry(id_cmp, vmsg->id);
+ if (entry == NULL)
+ return -ENOENT;
+ else {
+ int verdict = vmsg->value;
+
+ if (vmsg->data_len && vmsg->data_len == len)
+ if (ipq_mangle_ipv4(vmsg, entry) < 0)
+ verdict = NF_DROP;
+
+ ipq_issue_verdict(entry, verdict);
+ return 0;
+ }
+}
+
+static int
+ipq_set_mode(unsigned char mode, unsigned int range)
+{
+ int status;
+
+ write_lock_bh(&queue_lock);
+ status = __ipq_set_mode(mode, range);
+ write_unlock_bh(&queue_lock);
+ return status;
+}
+
+static int
+ipq_receive_peer(struct ipq_peer_msg *pmsg,
+ unsigned char type, unsigned int len)
+{
+ int status = 0;
+
+ if (len < sizeof(*pmsg))
+ return -EINVAL;
+
+ switch (type) {
+ case IPQM_MODE:
+ status = ipq_set_mode(pmsg->msg.mode.value,
+ pmsg->msg.mode.range);
+ break;
+
+ case IPQM_VERDICT:
+ if (pmsg->msg.verdict.value > NF_MAX_VERDICT)
+ status = -EINVAL;
+ else
+ status = ipq_set_verdict(&pmsg->msg.verdict,
+ len - sizeof(*pmsg));
+ break;
+ default:
+ status = -EINVAL;
+ }
+ return status;
+}
+
+static int
+dev_cmp(struct ipq_queue_entry *entry, unsigned long ifindex)
+{
+ if (entry->info->indev)
+ if (entry->info->indev->ifindex == ifindex)
+ return 1;
+
+ if (entry->info->outdev)
+ if (entry->info->outdev->ifindex == ifindex)
+ return 1;
+
+ return 0;
+}
+
+static void
+ipq_dev_drop(int ifindex)
+{
+ struct ipq_queue_entry *entry;
+
+ while ((entry = ipq_find_dequeue_entry(dev_cmp, ifindex)) != NULL)
+ ipq_issue_verdict(entry, NF_DROP);
+}
+
+#define RCV_SKB_FAIL(err) do { netlink_ack(skb, nlh, (err)); return; } while (0)
+
+static inline void
+ipq_rcv_skb(struct sk_buff *skb)
+{
+ int status, type, pid, flags, nlmsglen, skblen;
+ struct nlmsghdr *nlh;
+
+ skblen = skb->len;
+ if (skblen < sizeof(*nlh))
+ return;
+
+ nlh = (struct nlmsghdr *)skb->data;
+ nlmsglen = nlh->nlmsg_len;
+ if (nlmsglen < sizeof(*nlh) || skblen < nlmsglen)
+ return;
+
+ pid = nlh->nlmsg_pid;
+ flags = nlh->nlmsg_flags;
+
+ if(pid <= 0 || !(flags & NLM_F_REQUEST) || flags & NLM_F_MULTI)
+ RCV_SKB_FAIL(-EINVAL);
+
+ if (flags & MSG_TRUNC)
+ RCV_SKB_FAIL(-ECOMM);
+
+ type = nlh->nlmsg_type;
+ if (type < NLMSG_NOOP || type >= IPQM_MAX)
+ RCV_SKB_FAIL(-EINVAL);
+
+ if (type <= IPQM_BASE)
+ return;
+
+ if(!cap_raised(NETLINK_CB(skb).eff_cap, CAP_NET_ADMIN))
+ RCV_SKB_FAIL(-EPERM);
+
+ write_lock_bh(&queue_lock);
+
+ if (peer_pid) {
+ if (peer_pid != pid) {
+ write_unlock_bh(&queue_lock);
+ RCV_SKB_FAIL(-EBUSY);
+ }
+ }
+ else
+ peer_pid = pid;
+
+ write_unlock_bh(&queue_lock);
+
+ status = ipq_receive_peer(NLMSG_DATA(nlh), type,
+ skblen - NLMSG_LENGTH(0));
+ if (status < 0)
+ RCV_SKB_FAIL(status);
+
+ if (flags & NLM_F_ACK)
+ netlink_ack(skb, nlh, 0);
+ return;
+}
+
+static void
+ipq_rcv_sk(struct sock *sk, int len)
+{
+ do {
+ struct sk_buff *skb;
+
+ if (down_trylock(&ipqnl_sem))
+ return;
+
+ while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
+ ipq_rcv_skb(skb);
+ kfree_skb(skb);
+ }
+
+ up(&ipqnl_sem);
+
+ } while (ipqnl && ipqnl->receive_queue.qlen);
+}
+
+static int
+ipq_rcv_dev_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = ptr;
+
+ /* Drop any packets associated with the downed device */
+ if (event == NETDEV_DOWN)
+ ipq_dev_drop(dev->ifindex);
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block ipq_dev_notifier = {
+ ipq_rcv_dev_event,
+ NULL,
+ 0
+};
+
+static int
+ipq_rcv_nl_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct netlink_notify *n = ptr;
+
+ if (event == NETLINK_URELEASE &&
+ n->protocol == NETLINK_FIREWALL && n->pid) {
+ write_lock_bh(&queue_lock);
+ if (n->pid == peer_pid)
+ __ipq_reset();
+ write_unlock_bh(&queue_lock);
+ }
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block ipq_nl_notifier = {
+ ipq_rcv_nl_event,
+ NULL,
+ 0
+};
+
+static struct ctl_table_header *ipq_sysctl_header;
+
+static ctl_table ipq_table[] = {
+ { NET_IPQ_QMAX, NET_IPQ_QMAX_NAME, &queue_maxlen,
+ sizeof(queue_maxlen), 0644, NULL, proc_dointvec },
+ { 0 }
+};
+
+static ctl_table ipq_dir_table[] = {
+ {NET_IPV4, "ipv4", NULL, 0, 0555, ipq_table, 0, 0, 0, 0, 0},
+ { 0 }
+};
+
+static ctl_table ipq_root_table[] = {
+ {CTL_NET, "net", NULL, 0, 0555, ipq_dir_table, 0, 0, 0, 0, 0},
+ { 0 }
+};
+
+static int
+ipq_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int len;
+
+ read_lock_bh(&queue_lock);
+
+ len = sprintf(buffer,
+ "Peer PID : %d\n"
+ "Copy mode : %hu\n"
+ "Copy range : %u\n"
+ "Queue length : %u\n"
+ "Queue max. length : %u\n",
+ peer_pid,
+ copy_mode,
+ copy_range,
+ queue_total,
+ queue_maxlen);
+
+ read_unlock_bh(&queue_lock);
+
+ *start = buffer + offset;
+ len -= offset;
+ if (len > length)
+ len = length;
+ else if (len < 0)
+ len = 0;
+ return len;
+}
+
+static int
+init_or_cleanup(int init)
+{
+ int status = -ENOMEM;
+ struct proc_dir_entry *proc;
+
+ if (!init)
+ goto cleanup;
+
+ netlink_register_notifier(&ipq_nl_notifier);
+ ipqnl = netlink_kernel_create(NETLINK_FIREWALL, ipq_rcv_sk);
+ if (ipqnl == NULL) {
+ printk(KERN_ERR "ip_queue: failed to create netlink socket\n");
+ goto cleanup_netlink_notifier;
+ }
+
+ proc = proc_net_create(IPQ_PROC_FS_NAME, 0, ipq_get_info);
+ if (proc)
+ proc->owner = THIS_MODULE;
+ else {
+ printk(KERN_ERR "ip_queue: failed to create proc entry\n");
+ goto cleanup_ipqnl;
+ }
+
+ register_netdevice_notifier(&ipq_dev_notifier);
+ ipq_sysctl_header = register_sysctl_table(ipq_root_table, 0);
+
+ status = nf_register_queue_handler(PF_INET, ipq_enqueue_packet, NULL);
+ if (status < 0) {
+ printk(KERN_ERR "ip_queue: failed to register queue handler\n");
+ goto cleanup_sysctl;
+ }
+ return status;
+
+cleanup:
+ nf_unregister_queue_handler(PF_INET);
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ ipq_flush(NF_DROP);
+
+cleanup_sysctl:
+ unregister_sysctl_table(ipq_sysctl_header);
+ unregister_netdevice_notifier(&ipq_dev_notifier);
+ proc_net_remove(IPQ_PROC_FS_NAME);
+
+cleanup_ipqnl:
+ sock_release(ipqnl->socket);
+ down(&ipqnl_sem);
+ up(&ipqnl_sem);
+
+cleanup_netlink_notifier:
+ netlink_unregister_notifier(&ipq_nl_notifier);
+ return status;
+}
+
+static int __init init(void)
+{
+
+ return init_or_cleanup(1);
+}
+
+static void __exit fini(void)
+{
+ init_or_cleanup(0);
+}
+
+MODULE_DESCRIPTION("IPv4 packet queue handler");
+MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>");
+MODULE_LICENSE("GPL");
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_tables.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_tables.c
new file mode 100644
index 0000000..fdbb445
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ip_tables.c
@@ -0,0 +1,1894 @@
+/*
+ * Packet matching code.
+ *
+ * Copyright (C) 1999 Paul `Rusty' Russell & Michael J. Neuling
+ * Copyright (C) 2009-2002 Netfilter core team <coreteam@netfilter.org>
+ *
+ * 19 Jan 2002 Harald Welte <laforge@gnumonks.org>
+ * - increase module usage count as soon as we have rules inside
+ * a table
+ */
+#include <linux/config.h>
+#include <linux/cache.h>
+#include <linux/skbuff.h>
+#include <linux/kmod.h>
+#include <linux/vmalloc.h>
+#include <linux/netdevice.h>
+#include <linux/module.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/icmp.h>
+#include <net/ip.h>
+#include <asm/uaccess.h>
+#include <asm/semaphore.h>
+#include <linux/proc_fs.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+/*#define DEBUG_IP_FIREWALL*/
+/*#define DEBUG_ALLOW_ALL*/ /* Useful for remote debugging */
+/*#define DEBUG_IP_FIREWALL_USER*/
+
+#ifdef DEBUG_IP_FIREWALL
+#define dprintf(format, args...) printk(format , ## args)
+#else
+#define dprintf(format, args...)
+#endif
+
+#ifdef DEBUG_IP_FIREWALL_USER
+#define duprintf(format, args...) printk(format , ## args)
+#else
+#define duprintf(format, args...)
+#endif
+
+#ifdef CONFIG_NETFILTER_DEBUG
+#define IP_NF_ASSERT(x) \
+do { \
+ if (!(x)) \
+ printk("IP_NF_ASSERT: %s:%s:%u\n", \
+ __FUNCTION__, __FILE__, __LINE__); \
+} while(0)
+#else
+#define IP_NF_ASSERT(x)
+#endif
+#define SMP_ALIGN(x) (((x) + SMP_CACHE_BYTES-1) & ~(SMP_CACHE_BYTES-1))
+
+static DECLARE_MUTEX(ipt_mutex);
+
+/* Must have mutex */
+#define ASSERT_READ_LOCK(x) IP_NF_ASSERT(down_trylock(&ipt_mutex) != 0)
+#define ASSERT_WRITE_LOCK(x) IP_NF_ASSERT(down_trylock(&ipt_mutex) != 0)
+#include <linux/netfilter_ipv4/lockhelp.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#if 0
+/* All the better to debug you with... */
+#define static
+#define inline
+#endif
+
+/*
+ We keep a set of rules for each CPU, so we can avoid write-locking
+ them in the softirq when updating the counters and therefore
+ only need to read-lock in the softirq; doing a write_lock_bh() in user
+ context stops packets coming through and allows user context to read
+ the counters or update the rules.
+
+ To be cache friendly on SMP, we arrange them like so:
+ [ n-entries ]
+ ... cache-align padding ...
+ [ n-entries ]
+
+ Hence the start of any table is given by get_table() below. */
+
+/* The table itself */
+struct ipt_table_info
+{
+ /* Size per table */
+ unsigned int size;
+ /* Number of entries: FIXME. --RR */
+ unsigned int number;
+ /* Initial number of entries. Needed for module usage count */
+ unsigned int initial_entries;
+
+ /* Entry points and underflows */
+ unsigned int hook_entry[NF_IP_NUMHOOKS];
+ unsigned int underflow[NF_IP_NUMHOOKS];
+
+ /* ipt_entry tables: one per CPU */
+ char entries[0] ____cacheline_aligned;
+};
+
+static LIST_HEAD(ipt_target);
+static LIST_HEAD(ipt_match);
+static LIST_HEAD(ipt_tables);
+#define ADD_COUNTER(c,b,p) do { (c).bcnt += (b); (c).pcnt += (p); } while(0)
+
+#ifdef CONFIG_SMP
+#define TABLE_OFFSET(t,p) (SMP_ALIGN((t)->size)*(p))
+#else
+#define TABLE_OFFSET(t,p) 0
+#endif
+
+#if 0
+#define down(x) do { printk("DOWN:%u:" #x "\n", __LINE__); down(x); } while(0)
+#define down_interruptible(x) ({ int __r; printk("DOWNi:%u:" #x "\n", __LINE__); __r = down_interruptible(x); if (__r != 0) printk("ABORT-DOWNi:%u\n", __LINE__); __r; })
+#define up(x) do { printk("UP:%u:" #x "\n", __LINE__); up(x); } while(0)
+#endif
+
+/* Returns whether matches rule or not. */
+static inline int
+ip_packet_match(const struct iphdr *ip,
+ const char *indev,
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ const char *physindev,
+#endif
+ const char *outdev,
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ const char *physoutdev,
+#endif
+ const struct ipt_ip *ipinfo,
+ int isfrag)
+{
+ size_t i;
+ unsigned long ret;
+ unsigned long ret2 = 1;
+
+#define FWINV(bool,invflg) ((bool) ^ !!(ipinfo->invflags & invflg))
+
+ if (FWINV((ip->saddr&ipinfo->smsk.s_addr) != ipinfo->src.s_addr,
+ IPT_INV_SRCIP)
+ || FWINV((ip->daddr&ipinfo->dmsk.s_addr) != ipinfo->dst.s_addr,
+ IPT_INV_DSTIP)) {
+ dprintf("Source or dest mismatch.\n");
+
+ dprintf("SRC: %u.%u.%u.%u. Mask: %u.%u.%u.%u. Target: %u.%u.%u.%u.%s\n",
+ NIPQUAD(ip->saddr),
+ NIPQUAD(ipinfo->smsk.s_addr),
+ NIPQUAD(ipinfo->src.s_addr),
+ ipinfo->invflags & IPT_INV_SRCIP ? " (INV)" : "");
+ dprintf("DST: %u.%u.%u.%u Mask: %u.%u.%u.%u Target: %u.%u.%u.%u.%s\n",
+ NIPQUAD(ip->daddr),
+ NIPQUAD(ipinfo->dmsk.s_addr),
+ NIPQUAD(ipinfo->dst.s_addr),
+ ipinfo->invflags & IPT_INV_DSTIP ? " (INV)" : "");
+ return 0;
+ }
+
+ /* Look for ifname matches; this should unroll nicely. */
+ for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) {
+ ret |= (((const unsigned long *)indev)[i]
+ ^ ((const unsigned long *)ipinfo->iniface)[i])
+ & ((const unsigned long *)ipinfo->iniface_mask)[i];
+ }
+
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ for (i = 0, ret2 = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) {
+ ret2 |= (((const unsigned long *)physindev)[i]
+ ^ ((const unsigned long *)ipinfo->iniface)[i])
+ & ((const unsigned long *)ipinfo->iniface_mask)[i];
+ }
+#endif
+
+ if (FWINV(ret != 0 && ret2 != 0, IPT_INV_VIA_IN)) {
+ dprintf("VIA in mismatch (%s vs %s).%s\n",
+ indev, ipinfo->iniface,
+ ipinfo->invflags&IPT_INV_VIA_IN ?" (INV)":"");
+ return 0;
+ }
+
+ for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) {
+ ret |= (((const unsigned long *)outdev)[i]
+ ^ ((const unsigned long *)ipinfo->outiface)[i])
+ & ((const unsigned long *)ipinfo->outiface_mask)[i];
+ }
+
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ for (i = 0, ret2 = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) {
+ ret2 |= (((const unsigned long *)physoutdev)[i]
+ ^ ((const unsigned long *)ipinfo->outiface)[i])
+ & ((const unsigned long *)ipinfo->outiface_mask)[i];
+ }
+#endif
+
+ if (FWINV(ret != 0 && ret2 != 0, IPT_INV_VIA_OUT)) {
+ dprintf("VIA out mismatch (%s vs %s).%s\n",
+ outdev, ipinfo->outiface,
+ ipinfo->invflags&IPT_INV_VIA_OUT ?" (INV)":"");
+ return 0;
+ }
+
+ /* Check specific protocol */
+ if (ipinfo->proto
+ && FWINV(ip->protocol != ipinfo->proto, IPT_INV_PROTO)) {
+ dprintf("Packet protocol %hi does not match %hi.%s\n",
+ ip->protocol, ipinfo->proto,
+ ipinfo->invflags&IPT_INV_PROTO ? " (INV)":"");
+ return 0;
+ }
+
+ /* If we have a fragment rule but the packet is not a fragment
+ * then we return zero */
+ if (FWINV((ipinfo->flags&IPT_F_FRAG) && !isfrag, IPT_INV_FRAG)) {
+ dprintf("Fragment rule but not fragment.%s\n",
+ ipinfo->invflags & IPT_INV_FRAG ? " (INV)" : "");
+ return 0;
+ }
+
+ return 1;
+}
+
+static inline int
+ip_checkentry(const struct ipt_ip *ip)
+{
+ if (ip->flags & ~IPT_F_MASK) {
+ duprintf("Unknown flag bits set: %08X\n",
+ ip->flags & ~IPT_F_MASK);
+ return 0;
+ }
+ if (ip->invflags & ~IPT_INV_MASK) {
+ duprintf("Unknown invflag bits set: %08X\n",
+ ip->invflags & ~IPT_INV_MASK);
+ return 0;
+ }
+ return 1;
+}
+
+static unsigned int
+ipt_error(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ if (net_ratelimit())
+ printk("ip_tables: error: `%s'\n", (char *)targinfo);
+
+ return NF_DROP;
+}
+
+static inline
+int do_match(struct ipt_entry_match *m,
+ const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ /* Stop iteration if it doesn't match */
+ if (!m->u.kernel.match->match(skb, in, out, m->data,
+ offset, hdr, datalen, hotdrop))
+ return 1;
+ else
+ return 0;
+}
+
+static inline struct ipt_entry *
+get_entry(void *base, unsigned int offset)
+{
+ return (struct ipt_entry *)(base + offset);
+}
+
+/* Returns one of the generic firewall policies, like NF_ACCEPT. */
+unsigned int
+ipt_do_table(struct sk_buff **pskb,
+ unsigned int hook,
+ const struct net_device *in,
+ const struct net_device *out,
+ struct ipt_table *table,
+ void *userdata)
+{
+ static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long)))) = { 0 };
+ u_int16_t offset;
+ struct iphdr *ip;
+ void *protohdr;
+ u_int16_t datalen;
+ int hotdrop = 0;
+ /* Initializing verdict to NF_DROP keeps gcc happy. */
+ unsigned int verdict = NF_DROP;
+ const char *indev, *outdev;
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ const char *physindev, *physoutdev;
+#endif
+ void *table_base;
+ struct ipt_entry *e, *back;
+
+ /* Initialization */
+ ip = (*pskb)->nh.iph;
+ protohdr = (u_int32_t *)ip + ip->ihl;
+ datalen = (*pskb)->len - ip->ihl * 4;
+ indev = in ? in->name : nulldevname;
+ outdev = out ? out->name : nulldevname;
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ physindev = ((*pskb)->nf_bridge && (*pskb)->nf_bridge->physindev) ?
+ (*pskb)->nf_bridge->physindev->name : nulldevname;
+ physoutdev = ((*pskb)->nf_bridge && (*pskb)->nf_bridge->physoutdev) ?
+ (*pskb)->nf_bridge->physoutdev->name : nulldevname;
+#endif
+
+ /* We handle fragments by dealing with the first fragment as
+ * if it was a normal packet. All other fragments are treated
+ * normally, except that they will NEVER match rules that ask
+ * things we don't know, ie. tcp syn flag or ports). If the
+ * rule is also a fragment-specific rule, non-fragments won't
+ * match it. */
+ offset = ntohs(ip->frag_off) & IP_OFFSET;
+
+ read_lock_bh(&table->lock);
+ IP_NF_ASSERT(table->valid_hooks & (1 << hook));
+ table_base = (void *)table->private->entries
+ + TABLE_OFFSET(table->private,
+ cpu_number_map(smp_processor_id()));
+ e = get_entry(table_base, table->private->hook_entry[hook]);
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ /* Check noone else using our table */
+ if (((struct ipt_entry *)table_base)->comefrom != 0xdead57ac
+ && ((struct ipt_entry *)table_base)->comefrom != 0xeeeeeeec) {
+ printk("ASSERT: CPU #%u, %s comefrom(%p) = %X\n",
+ smp_processor_id(),
+ table->name,
+ &((struct ipt_entry *)table_base)->comefrom,
+ ((struct ipt_entry *)table_base)->comefrom);
+ }
+ ((struct ipt_entry *)table_base)->comefrom = 0x57acc001;
+#endif
+
+ /* For return from builtin chain */
+ back = get_entry(table_base, table->private->underflow[hook]);
+
+ do {
+ IP_NF_ASSERT(e);
+ IP_NF_ASSERT(back);
+ if (ip_packet_match(ip, indev,
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ physindev,
+#endif
+ outdev,
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ physoutdev,
+#endif
+ &e->ip, offset)) {
+ struct ipt_entry_target *t;
+
+ if (IPT_MATCH_ITERATE(e, do_match,
+ *pskb, in, out,
+ offset, protohdr,
+ datalen, &hotdrop) != 0)
+ goto no_match;
+
+ ADD_COUNTER(e->counters, ntohs(ip->tot_len), 1);
+
+ t = ipt_get_target(e);
+ IP_NF_ASSERT(t->u.kernel.target);
+ /* Standard target? */
+ if (!t->u.kernel.target->target) {
+ int v;
+
+ v = ((struct ipt_standard_target *)t)->verdict;
+ if (v < 0) {
+ /* Pop from stack? */
+ if (v != IPT_RETURN) {
+ verdict = (unsigned)(-v) - 1;
+ break;
+ }
+ e = back;
+ back = get_entry(table_base,
+ back->comefrom);
+ continue;
+ }
+ if (table_base + v
+ != (void *)e + e->next_offset) {
+ /* Save old back ptr in next entry */
+ struct ipt_entry *next
+ = (void *)e + e->next_offset;
+ next->comefrom
+ = (void *)back - table_base;
+ /* set back pointer to next entry */
+ back = next;
+ }
+
+ e = get_entry(table_base, v);
+ } else {
+ /* Targets which reenter must return
+ abs. verdicts */
+#ifdef CONFIG_NETFILTER_DEBUG
+ ((struct ipt_entry *)table_base)->comefrom
+ = 0xeeeeeeec;
+#endif
+ verdict = t->u.kernel.target->target(pskb,
+ hook,
+ in, out,
+ t->data,
+ userdata);
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ if (((struct ipt_entry *)table_base)->comefrom
+ != 0xeeeeeeec
+ && verdict == IPT_CONTINUE) {
+ printk("Target %s reentered!\n",
+ t->u.kernel.target->name);
+ verdict = NF_DROP;
+ }
+ ((struct ipt_entry *)table_base)->comefrom
+ = 0x57acc001;
+#endif
+ /* Target might have changed stuff. */
+ ip = (*pskb)->nh.iph;
+ protohdr = (u_int32_t *)ip + ip->ihl;
+ datalen = (*pskb)->len - ip->ihl * 4;
+
+ if (verdict == IPT_CONTINUE)
+ e = (void *)e + e->next_offset;
+ else
+ /* Verdict */
+ break;
+ }
+ } else {
+
+ no_match:
+ e = (void *)e + e->next_offset;
+ }
+ } while (!hotdrop);
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ ((struct ipt_entry *)table_base)->comefrom = 0xdead57ac;
+#endif
+ read_unlock_bh(&table->lock);
+
+#ifdef DEBUG_ALLOW_ALL
+ return NF_ACCEPT;
+#else
+ if (hotdrop)
+ return NF_DROP;
+ else return verdict;
+#endif
+}
+
+/* If it succeeds, returns element and locks mutex */
+static inline void *
+find_inlist_lock_noload(struct list_head *head,
+ const char *name,
+ int *error,
+ struct semaphore *mutex)
+{
+ void *ret;
+
+#if 0
+ duprintf("find_inlist: searching for `%s' in %s.\n",
+ name, head == &ipt_target ? "ipt_target"
+ : head == &ipt_match ? "ipt_match"
+ : head == &ipt_tables ? "ipt_tables" : "UNKNOWN");
+#endif
+
+ *error = down_interruptible(mutex);
+ if (*error != 0)
+ return NULL;
+
+ ret = list_named_find(head, name);
+ if (!ret) {
+ *error = -ENOENT;
+ up(mutex);
+ }
+ return ret;
+}
+
+#ifndef CONFIG_KMOD
+#define find_inlist_lock(h,n,p,e,m) find_inlist_lock_noload((h),(n),(e),(m))
+#else
+static void *
+find_inlist_lock(struct list_head *head,
+ const char *name,
+ const char *prefix,
+ int *error,
+ struct semaphore *mutex)
+{
+ void *ret;
+
+ ret = find_inlist_lock_noload(head, name, error, mutex);
+ if (!ret) {
+ char modulename[IPT_FUNCTION_MAXNAMELEN + strlen(prefix) + 1];
+ strcpy(modulename, prefix);
+ strcat(modulename, name);
+ duprintf("find_inlist: loading `%s'.\n", modulename);
+ request_module(modulename);
+ ret = find_inlist_lock_noload(head, name, error, mutex);
+ }
+
+ return ret;
+}
+#endif
+
+static inline struct ipt_table *
+ipt_find_table_lock(const char *name, int *error, struct semaphore *mutex)
+{
+ return find_inlist_lock(&ipt_tables, name, "iptable_", error, mutex);
+}
+
+static inline struct ipt_match *
+find_match_lock(const char *name, int *error, struct semaphore *mutex)
+{
+ return find_inlist_lock(&ipt_match, name, "ipt_", error, mutex);
+}
+
+struct ipt_target *
+ipt_find_target_lock(const char *name, int *error, struct semaphore *mutex)
+{
+ return find_inlist_lock(&ipt_target, name, "ipt_", error, mutex);
+}
+
+/* All zeroes == unconditional rule. */
+static inline int
+unconditional(const struct ipt_ip *ip)
+{
+ unsigned int i;
+
+ for (i = 0; i < sizeof(*ip)/sizeof(__u32); i++)
+ if (((__u32 *)ip)[i])
+ return 0;
+
+ return 1;
+}
+
+/* Figures out from what hook each rule can be called: returns 0 if
+ there are loops. Puts hook bitmask in comefrom. */
+static int
+mark_source_chains(struct ipt_table_info *newinfo, unsigned int valid_hooks)
+{
+ unsigned int hook;
+
+ /* No recursion; use packet counter to save back ptrs (reset
+ to 0 as we leave), and comefrom to save source hook bitmask */
+ for (hook = 0; hook < NF_IP_NUMHOOKS; hook++) {
+ unsigned int pos = newinfo->hook_entry[hook];
+ struct ipt_entry *e
+ = (struct ipt_entry *)(newinfo->entries + pos);
+
+ if (!(valid_hooks & (1 << hook)))
+ continue;
+
+ /* Set initial back pointer. */
+ e->counters.pcnt = pos;
+
+ for (;;) {
+ struct ipt_standard_target *t
+ = (void *)ipt_get_target(e);
+
+ if (e->comefrom & (1 << NF_IP_NUMHOOKS)) {
+ printk("iptables: loop hook %u pos %u %08X.\n",
+ hook, pos, e->comefrom);
+ return 0;
+ }
+ e->comefrom
+ |= ((1 << hook) | (1 << NF_IP_NUMHOOKS));
+
+ /* Unconditional return/END. */
+ if (e->target_offset == sizeof(struct ipt_entry)
+ && (strcmp(t->target.u.user.name,
+ IPT_STANDARD_TARGET) == 0)
+ && t->verdict < 0
+ && unconditional(&e->ip)) {
+ unsigned int oldpos, size;
+
+ /* Return: backtrack through the last
+ big jump. */
+ do {
+ e->comefrom ^= (1<<NF_IP_NUMHOOKS);
+#ifdef DEBUG_IP_FIREWALL_USER
+ if (e->comefrom
+ & (1 << NF_IP_NUMHOOKS)) {
+ duprintf("Back unset "
+ "on hook %u "
+ "rule %u\n",
+ hook, pos);
+ }
+#endif
+ oldpos = pos;
+ pos = e->counters.pcnt;
+ e->counters.pcnt = 0;
+
+ /* We're at the start. */
+ if (pos == oldpos)
+ goto next;
+
+ e = (struct ipt_entry *)
+ (newinfo->entries + pos);
+ } while (oldpos == pos + e->next_offset);
+
+ /* Move along one */
+ size = e->next_offset;
+ e = (struct ipt_entry *)
+ (newinfo->entries + pos + size);
+ e->counters.pcnt = pos;
+ pos += size;
+ } else {
+ int newpos = t->verdict;
+
+ if (strcmp(t->target.u.user.name,
+ IPT_STANDARD_TARGET) == 0
+ && newpos >= 0) {
+ /* This a jump; chase it. */
+ duprintf("Jump rule %u -> %u\n",
+ pos, newpos);
+ } else {
+ /* ... this is a fallthru */
+ newpos = pos + e->next_offset;
+ }
+ e = (struct ipt_entry *)
+ (newinfo->entries + newpos);
+ e->counters.pcnt = pos;
+ pos = newpos;
+ }
+ }
+ next:
+ duprintf("Finished chain %u\n", hook);
+ }
+ return 1;
+}
+
+static inline int
+cleanup_match(struct ipt_entry_match *m, unsigned int *i)
+{
+ if (i && (*i)-- == 0)
+ return 1;
+
+ if (m->u.kernel.match->destroy)
+ m->u.kernel.match->destroy(m->data,
+ m->u.match_size - sizeof(*m));
+
+ if (m->u.kernel.match->me)
+ __MOD_DEC_USE_COUNT(m->u.kernel.match->me);
+
+ return 0;
+}
+
+static inline int
+standard_check(const struct ipt_entry_target *t,
+ unsigned int max_offset)
+{
+ struct ipt_standard_target *targ = (void *)t;
+
+ /* Check standard info. */
+ if (t->u.target_size
+ != IPT_ALIGN(sizeof(struct ipt_standard_target))) {
+ duprintf("standard_check: target size %u != %u\n",
+ t->u.target_size,
+ IPT_ALIGN(sizeof(struct ipt_standard_target)));
+ return 0;
+ }
+
+ if (targ->verdict >= 0
+ && targ->verdict > max_offset - sizeof(struct ipt_entry)) {
+ duprintf("ipt_standard_check: bad verdict (%i)\n",
+ targ->verdict);
+ return 0;
+ }
+
+ if (targ->verdict < -NF_MAX_VERDICT - 1) {
+ duprintf("ipt_standard_check: bad negative verdict (%i)\n",
+ targ->verdict);
+ return 0;
+ }
+ return 1;
+}
+
+static inline int
+check_match(struct ipt_entry_match *m,
+ const char *name,
+ const struct ipt_ip *ip,
+ unsigned int hookmask,
+ unsigned int *i)
+{
+ int ret;
+ struct ipt_match *match;
+
+ match = find_match_lock(m->u.user.name, &ret, &ipt_mutex);
+ if (!match) {
+ duprintf("check_match: `%s' not found\n", m->u.user.name);
+ return ret;
+ }
+ if (match->me)
+ __MOD_INC_USE_COUNT(match->me);
+ m->u.kernel.match = match;
+ up(&ipt_mutex);
+
+ if (m->u.kernel.match->checkentry
+ && !m->u.kernel.match->checkentry(name, ip, m->data,
+ m->u.match_size - sizeof(*m),
+ hookmask)) {
+ if (m->u.kernel.match->me)
+ __MOD_DEC_USE_COUNT(m->u.kernel.match->me);
+ duprintf("ip_tables: check failed for `%s'.\n",
+ m->u.kernel.match->name);
+ return -EINVAL;
+ }
+
+ (*i)++;
+ return 0;
+}
+
+static struct ipt_target ipt_standard_target;
+
+static inline int
+check_entry(struct ipt_entry *e, const char *name, unsigned int size,
+ unsigned int *i)
+{
+ struct ipt_entry_target *t;
+ struct ipt_target *target;
+ int ret;
+ unsigned int j;
+
+ if (!ip_checkentry(&e->ip)) {
+ duprintf("ip_tables: ip check failed %p %s.\n", e, name);
+ return -EINVAL;
+ }
+
+ j = 0;
+ ret = IPT_MATCH_ITERATE(e, check_match, name, &e->ip, e->comefrom, &j);
+ if (ret != 0)
+ goto cleanup_matches;
+
+ t = ipt_get_target(e);
+ target = ipt_find_target_lock(t->u.user.name, &ret, &ipt_mutex);
+ if (!target) {
+ duprintf("check_entry: `%s' not found\n", t->u.user.name);
+ goto cleanup_matches;
+ }
+ if (target->me)
+ __MOD_INC_USE_COUNT(target->me);
+ t->u.kernel.target = target;
+ up(&ipt_mutex);
+
+ if (t->u.kernel.target == &ipt_standard_target) {
+ if (!standard_check(t, size)) {
+ ret = -EINVAL;
+ goto cleanup_matches;
+ }
+ } else if (t->u.kernel.target->checkentry
+ && !t->u.kernel.target->checkentry(name, e, t->data,
+ t->u.target_size
+ - sizeof(*t),
+ e->comefrom)) {
+ if (t->u.kernel.target->me)
+ __MOD_DEC_USE_COUNT(t->u.kernel.target->me);
+ duprintf("ip_tables: check failed for `%s'.\n",
+ t->u.kernel.target->name);
+ ret = -EINVAL;
+ goto cleanup_matches;
+ }
+
+ (*i)++;
+ return 0;
+
+ cleanup_matches:
+ IPT_MATCH_ITERATE(e, cleanup_match, &j);
+ return ret;
+}
+
+static inline int
+check_entry_size_and_hooks(struct ipt_entry *e,
+ struct ipt_table_info *newinfo,
+ unsigned char *base,
+ unsigned char *limit,
+ const unsigned int *hook_entries,
+ const unsigned int *underflows,
+ unsigned int *i)
+{
+ unsigned int h;
+
+ if ((unsigned long)e % __alignof__(struct ipt_entry) != 0
+ || (unsigned char *)e + sizeof(struct ipt_entry) >= limit) {
+ duprintf("Bad offset %p\n", e);
+ return -EINVAL;
+ }
+
+ if (e->next_offset
+ < sizeof(struct ipt_entry) + sizeof(struct ipt_entry_target)) {
+ duprintf("checking: element %p size %u\n",
+ e, e->next_offset);
+ return -EINVAL;
+ }
+
+ /* Check hooks & underflows */
+ for (h = 0; h < NF_IP_NUMHOOKS; h++) {
+ if ((unsigned char *)e - base == hook_entries[h])
+ newinfo->hook_entry[h] = hook_entries[h];
+ if ((unsigned char *)e - base == underflows[h])
+ newinfo->underflow[h] = underflows[h];
+ }
+
+ /* FIXME: underflows must be unconditional, standard verdicts
+ < 0 (not IPT_RETURN). --RR */
+
+ /* Clear counters and comefrom */
+ e->counters = ((struct ipt_counters) { 0, 0 });
+ e->comefrom = 0;
+
+ (*i)++;
+ return 0;
+}
+
+static inline int
+cleanup_entry(struct ipt_entry *e, unsigned int *i)
+{
+ struct ipt_entry_target *t;
+
+ if (i && (*i)-- == 0)
+ return 1;
+
+ /* Cleanup all matches */
+ IPT_MATCH_ITERATE(e, cleanup_match, NULL);
+ t = ipt_get_target(e);
+ if (t->u.kernel.target->destroy)
+ t->u.kernel.target->destroy(t->data,
+ t->u.target_size - sizeof(*t));
+ if (t->u.kernel.target->me)
+ __MOD_DEC_USE_COUNT(t->u.kernel.target->me);
+
+ return 0;
+}
+
+/* Checks and translates the user-supplied table segment (held in
+ newinfo) */
+static int
+translate_table(const char *name,
+ unsigned int valid_hooks,
+ struct ipt_table_info *newinfo,
+ unsigned int size,
+ unsigned int number,
+ const unsigned int *hook_entries,
+ const unsigned int *underflows)
+{
+ unsigned int i;
+ int ret;
+
+ newinfo->size = size;
+ newinfo->number = number;
+
+ /* Init all hooks to impossible value. */
+ for (i = 0; i < NF_IP_NUMHOOKS; i++) {
+ newinfo->hook_entry[i] = 0xFFFFFFFF;
+ newinfo->underflow[i] = 0xFFFFFFFF;
+ }
+
+ duprintf("translate_table: size %u\n", newinfo->size);
+ i = 0;
+ /* Walk through entries, checking offsets. */
+ ret = IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
+ check_entry_size_and_hooks,
+ newinfo,
+ newinfo->entries,
+ newinfo->entries + size,
+ hook_entries, underflows, &i);
+ if (ret != 0)
+ return ret;
+
+ if (i != number) {
+ duprintf("translate_table: %u not %u entries\n",
+ i, number);
+ return -EINVAL;
+ }
+
+ /* Check hooks all assigned */
+ for (i = 0; i < NF_IP_NUMHOOKS; i++) {
+ /* Only hooks which are valid */
+ if (!(valid_hooks & (1 << i)))
+ continue;
+ if (newinfo->hook_entry[i] == 0xFFFFFFFF) {
+ duprintf("Invalid hook entry %u %u\n",
+ i, hook_entries[i]);
+ return -EINVAL;
+ }
+ if (newinfo->underflow[i] == 0xFFFFFFFF) {
+ duprintf("Invalid underflow %u %u\n",
+ i, underflows[i]);
+ return -EINVAL;
+ }
+ }
+
+ if (!mark_source_chains(newinfo, valid_hooks))
+ return -ELOOP;
+
+ /* Finally, each sanity check must pass */
+ i = 0;
+ ret = IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
+ check_entry, name, size, &i);
+
+ if (ret != 0) {
+ IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size,
+ cleanup_entry, &i);
+ return ret;
+ }
+
+ /* And one copy for every other CPU */
+ for (i = 1; i < smp_num_cpus; i++) {
+ memcpy(newinfo->entries + SMP_ALIGN(newinfo->size)*i,
+ newinfo->entries,
+ SMP_ALIGN(newinfo->size));
+ }
+
+ return ret;
+}
+
+static struct ipt_table_info *
+replace_table(struct ipt_table *table,
+ unsigned int num_counters,
+ struct ipt_table_info *newinfo,
+ int *error)
+{
+ struct ipt_table_info *oldinfo;
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ {
+ struct ipt_entry *table_base;
+ unsigned int i;
+
+ for (i = 0; i < smp_num_cpus; i++) {
+ table_base =
+ (void *)newinfo->entries
+ + TABLE_OFFSET(newinfo, i);
+
+ table_base->comefrom = 0xdead57ac;
+ }
+ }
+#endif
+
+ /* Do the substitution. */
+ write_lock_bh(&table->lock);
+ /* Check inside lock: is the old number correct? */
+ if (num_counters != table->private->number) {
+ duprintf("num_counters != table->private->number (%u/%u)\n",
+ num_counters, table->private->number);
+ write_unlock_bh(&table->lock);
+ *error = -EAGAIN;
+ return NULL;
+ }
+ oldinfo = table->private;
+ table->private = newinfo;
+ newinfo->initial_entries = oldinfo->initial_entries;
+ write_unlock_bh(&table->lock);
+
+ return oldinfo;
+}
+
+/* Gets counters. */
+static inline int
+add_entry_to_counter(const struct ipt_entry *e,
+ struct ipt_counters total[],
+ unsigned int *i)
+{
+ ADD_COUNTER(total[*i], e->counters.bcnt, e->counters.pcnt);
+
+ (*i)++;
+ return 0;
+}
+
+static void
+get_counters(const struct ipt_table_info *t,
+ struct ipt_counters counters[])
+{
+ unsigned int cpu;
+ unsigned int i;
+
+ for (cpu = 0; cpu < smp_num_cpus; cpu++) {
+ i = 0;
+ IPT_ENTRY_ITERATE(t->entries + TABLE_OFFSET(t, cpu),
+ t->size,
+ add_entry_to_counter,
+ counters,
+ &i);
+ }
+}
+
+static int
+copy_entries_to_user(unsigned int total_size,
+ struct ipt_table *table,
+ void *userptr)
+{
+ unsigned int off, num, countersize;
+ struct ipt_entry *e;
+ struct ipt_counters *counters;
+ int ret = 0;
+
+ /* We need atomic snapshot of counters: rest doesn't change
+ (other than comefrom, which userspace doesn't care
+ about). */
+ countersize = sizeof(struct ipt_counters) * table->private->number;
+ counters = vmalloc(countersize);
+
+ if (counters == NULL)
+ return -ENOMEM;
+
+ /* First, sum counters... */
+ memset(counters, 0, countersize);
+ write_lock_bh(&table->lock);
+ get_counters(table->private, counters);
+ write_unlock_bh(&table->lock);
+
+ /* ... then copy entire thing from CPU 0... */
+ if (copy_to_user(userptr, table->private->entries, total_size) != 0) {
+ ret = -EFAULT;
+ goto free_counters;
+ }
+
+ /* FIXME: use iterator macros --RR */
+ /* ... then go back and fix counters and names */
+ for (off = 0, num = 0; off < total_size; off += e->next_offset, num++){
+ unsigned int i;
+ struct ipt_entry_match *m;
+ struct ipt_entry_target *t;
+
+ e = (struct ipt_entry *)(table->private->entries + off);
+ if (copy_to_user(userptr + off
+ + offsetof(struct ipt_entry, counters),
+ &counters[num],
+ sizeof(counters[num])) != 0) {
+ ret = -EFAULT;
+ goto free_counters;
+ }
+
+ for (i = sizeof(struct ipt_entry);
+ i < e->target_offset;
+ i += m->u.match_size) {
+ m = (void *)e + i;
+
+ if (copy_to_user(userptr + off + i
+ + offsetof(struct ipt_entry_match,
+ u.user.name),
+ m->u.kernel.match->name,
+ strlen(m->u.kernel.match->name)+1)
+ != 0) {
+ ret = -EFAULT;
+ goto free_counters;
+ }
+ }
+
+ t = ipt_get_target(e);
+ if (copy_to_user(userptr + off + e->target_offset
+ + offsetof(struct ipt_entry_target,
+ u.user.name),
+ t->u.kernel.target->name,
+ strlen(t->u.kernel.target->name)+1) != 0) {
+ ret = -EFAULT;
+ goto free_counters;
+ }
+ }
+
+ free_counters:
+ vfree(counters);
+ return ret;
+}
+
+static int
+get_entries(const struct ipt_get_entries *entries,
+ struct ipt_get_entries *uptr)
+{
+ int ret;
+ struct ipt_table *t;
+
+ t = ipt_find_table_lock(entries->name, &ret, &ipt_mutex);
+ if (t) {
+ duprintf("t->private->number = %u\n",
+ t->private->number);
+ if (entries->size == t->private->size)
+ ret = copy_entries_to_user(t->private->size,
+ t, uptr->entrytable);
+ else {
+ duprintf("get_entries: I've got %u not %u!\n",
+ t->private->size,
+ entries->size);
+ ret = -EINVAL;
+ }
+ up(&ipt_mutex);
+ } else
+ duprintf("get_entries: Can't find %s!\n",
+ entries->name);
+
+ return ret;
+}
+
+static int
+do_replace(void *user, unsigned int len)
+{
+ int ret;
+ struct ipt_replace tmp;
+ struct ipt_table *t;
+ struct ipt_table_info *newinfo, *oldinfo;
+ struct ipt_counters *counters;
+
+ if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
+ return -EFAULT;
+
+ /* Hack: Causes ipchains to give correct error msg --RR */
+ if (len != sizeof(tmp) + tmp.size)
+ return -ENOPROTOOPT;
+
+ /* Pedantry: prevent them from hitting BUG() in vmalloc.c --RR */
+ if ((SMP_ALIGN(tmp.size) >> PAGE_SHIFT) + 2 > num_physpages)
+ return -ENOMEM;
+
+ newinfo = vmalloc(sizeof(struct ipt_table_info)
+ + SMP_ALIGN(tmp.size) * smp_num_cpus);
+ if (!newinfo)
+ return -ENOMEM;
+
+ if (copy_from_user(newinfo->entries, user + sizeof(tmp),
+ tmp.size) != 0) {
+ ret = -EFAULT;
+ goto free_newinfo;
+ }
+
+ counters = vmalloc(tmp.num_counters * sizeof(struct ipt_counters));
+ if (!counters) {
+ ret = -ENOMEM;
+ goto free_newinfo;
+ }
+ memset(counters, 0, tmp.num_counters * sizeof(struct ipt_counters));
+
+ ret = translate_table(tmp.name, tmp.valid_hooks,
+ newinfo, tmp.size, tmp.num_entries,
+ tmp.hook_entry, tmp.underflow);
+ if (ret != 0)
+ goto free_newinfo_counters;
+
+ duprintf("ip_tables: Translated table\n");
+
+ t = ipt_find_table_lock(tmp.name, &ret, &ipt_mutex);
+ if (!t)
+ goto free_newinfo_counters_untrans;
+
+ /* You lied! */
+ if (tmp.valid_hooks != t->valid_hooks) {
+ duprintf("Valid hook crap: %08X vs %08X\n",
+ tmp.valid_hooks, t->valid_hooks);
+ ret = -EINVAL;
+ goto free_newinfo_counters_untrans_unlock;
+ }
+
+ oldinfo = replace_table(t, tmp.num_counters, newinfo, &ret);
+ if (!oldinfo)
+ goto free_newinfo_counters_untrans_unlock;
+
+ /* Update module usage count based on number of rules */
+ duprintf("do_replace: oldnum=%u, initnum=%u, newnum=%u\n",
+ oldinfo->number, oldinfo->initial_entries, newinfo->number);
+ if (t->me && (oldinfo->number <= oldinfo->initial_entries) &&
+ (newinfo->number > oldinfo->initial_entries))
+ __MOD_INC_USE_COUNT(t->me);
+ else if (t->me && (oldinfo->number > oldinfo->initial_entries) &&
+ (newinfo->number <= oldinfo->initial_entries))
+ __MOD_DEC_USE_COUNT(t->me);
+
+ /* Get the old counters. */
+ get_counters(oldinfo, counters);
+ /* Decrease module usage counts and free resource */
+ IPT_ENTRY_ITERATE(oldinfo->entries, oldinfo->size, cleanup_entry,NULL);
+ vfree(oldinfo);
+ /* Silent error: too late now. */
+ copy_to_user(tmp.counters, counters,
+ sizeof(struct ipt_counters) * tmp.num_counters);
+ vfree(counters);
+ up(&ipt_mutex);
+ return 0;
+
+ free_newinfo_counters_untrans_unlock:
+ up(&ipt_mutex);
+ free_newinfo_counters_untrans:
+ IPT_ENTRY_ITERATE(newinfo->entries, newinfo->size, cleanup_entry,NULL);
+ free_newinfo_counters:
+ vfree(counters);
+ free_newinfo:
+ vfree(newinfo);
+ return ret;
+}
+
+/* We're lazy, and add to the first CPU; overflow works its fey magic
+ * and everything is OK. */
+static inline int
+add_counter_to_entry(struct ipt_entry *e,
+ const struct ipt_counters addme[],
+ unsigned int *i)
+{
+#if 0
+ duprintf("add_counter: Entry %u %lu/%lu + %lu/%lu\n",
+ *i,
+ (long unsigned int)e->counters.pcnt,
+ (long unsigned int)e->counters.bcnt,
+ (long unsigned int)addme[*i].pcnt,
+ (long unsigned int)addme[*i].bcnt);
+#endif
+
+ ADD_COUNTER(e->counters, addme[*i].bcnt, addme[*i].pcnt);
+
+ (*i)++;
+ return 0;
+}
+
+static int
+do_add_counters(void *user, unsigned int len)
+{
+ unsigned int i;
+ struct ipt_counters_info tmp, *paddc;
+ struct ipt_table *t;
+ int ret;
+
+ if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
+ return -EFAULT;
+
+ if (len != sizeof(tmp) + tmp.num_counters*sizeof(struct ipt_counters))
+ return -EINVAL;
+
+ paddc = vmalloc(len);
+ if (!paddc)
+ return -ENOMEM;
+
+ if (copy_from_user(paddc, user, len) != 0) {
+ ret = -EFAULT;
+ goto free;
+ }
+
+ t = ipt_find_table_lock(tmp.name, &ret, &ipt_mutex);
+ if (!t)
+ goto free;
+
+ write_lock_bh(&t->lock);
+ if (t->private->number != paddc->num_counters) {
+ ret = -EINVAL;
+ goto unlock_up_free;
+ }
+
+ i = 0;
+ IPT_ENTRY_ITERATE(t->private->entries,
+ t->private->size,
+ add_counter_to_entry,
+ paddc->counters,
+ &i);
+ unlock_up_free:
+ write_unlock_bh(&t->lock);
+ up(&ipt_mutex);
+ free:
+ vfree(paddc);
+
+ return ret;
+}
+
+static int
+do_ipt_set_ctl(struct sock *sk, int cmd, void *user, unsigned int len)
+{
+ int ret;
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ switch (cmd) {
+ case IPT_SO_SET_REPLACE:
+ ret = do_replace(user, len);
+ break;
+
+ case IPT_SO_SET_ADD_COUNTERS:
+ ret = do_add_counters(user, len);
+ break;
+
+ default:
+ duprintf("do_ipt_set_ctl: unknown request %i\n", cmd);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int
+do_ipt_get_ctl(struct sock *sk, int cmd, void *user, int *len)
+{
+ int ret;
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ switch (cmd) {
+ case IPT_SO_GET_INFO: {
+ char name[IPT_TABLE_MAXNAMELEN];
+ struct ipt_table *t;
+
+ if (*len != sizeof(struct ipt_getinfo)) {
+ duprintf("length %u != %u\n", *len,
+ sizeof(struct ipt_getinfo));
+ ret = -EINVAL;
+ break;
+ }
+
+ if (copy_from_user(name, user, sizeof(name)) != 0) {
+ ret = -EFAULT;
+ break;
+ }
+ name[IPT_TABLE_MAXNAMELEN-1] = '\0';
+ t = ipt_find_table_lock(name, &ret, &ipt_mutex);
+ if (t) {
+ struct ipt_getinfo info;
+
+ info.valid_hooks = t->valid_hooks;
+ memcpy(info.hook_entry, t->private->hook_entry,
+ sizeof(info.hook_entry));
+ memcpy(info.underflow, t->private->underflow,
+ sizeof(info.underflow));
+ info.num_entries = t->private->number;
+ info.size = t->private->size;
+ memcpy(info.name, name, sizeof(info.name));
+
+ if (copy_to_user(user, &info, *len) != 0)
+ ret = -EFAULT;
+ else
+ ret = 0;
+
+ up(&ipt_mutex);
+ }
+ }
+ break;
+
+ case IPT_SO_GET_ENTRIES: {
+ struct ipt_get_entries get;
+
+ if (*len < sizeof(get)) {
+ duprintf("get_entries: %u < %u\n", *len, sizeof(get));
+ ret = -EINVAL;
+ } else if (copy_from_user(&get, user, sizeof(get)) != 0) {
+ ret = -EFAULT;
+ } else if (*len != sizeof(struct ipt_get_entries) + get.size) {
+ duprintf("get_entries: %u != %u\n", *len,
+ sizeof(struct ipt_get_entries) + get.size);
+ ret = -EINVAL;
+ } else
+ ret = get_entries(&get, user);
+ break;
+ }
+
+ default:
+ duprintf("do_ipt_get_ctl: unknown request %i\n", cmd);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/* Registration hooks for targets. */
+int
+ipt_register_target(struct ipt_target *target)
+{
+ int ret;
+
+ MOD_INC_USE_COUNT;
+ ret = down_interruptible(&ipt_mutex);
+ if (ret != 0) {
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+ if (!list_named_insert(&ipt_target, target)) {
+ duprintf("ipt_register_target: `%s' already in list!\n",
+ target->name);
+ ret = -EINVAL;
+ MOD_DEC_USE_COUNT;
+ }
+ up(&ipt_mutex);
+ return ret;
+}
+
+void
+ipt_unregister_target(struct ipt_target *target)
+{
+ down(&ipt_mutex);
+ LIST_DELETE(&ipt_target, target);
+ up(&ipt_mutex);
+ MOD_DEC_USE_COUNT;
+}
+
+int
+ipt_register_match(struct ipt_match *match)
+{
+ int ret;
+
+ MOD_INC_USE_COUNT;
+ ret = down_interruptible(&ipt_mutex);
+ if (ret != 0) {
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+ if (!list_named_insert(&ipt_match, match)) {
+ duprintf("ipt_register_match: `%s' already in list!\n",
+ match->name);
+ MOD_DEC_USE_COUNT;
+ ret = -EINVAL;
+ }
+ up(&ipt_mutex);
+
+ return ret;
+}
+
+void
+ipt_unregister_match(struct ipt_match *match)
+{
+ down(&ipt_mutex);
+ LIST_DELETE(&ipt_match, match);
+ up(&ipt_mutex);
+ MOD_DEC_USE_COUNT;
+}
+
+int ipt_register_table(struct ipt_table *table)
+{
+ int ret;
+ struct ipt_table_info *newinfo;
+ static struct ipt_table_info bootstrap
+ = { 0, 0, 0, { 0 }, { 0 }, { } };
+
+ MOD_INC_USE_COUNT;
+ newinfo = vmalloc(sizeof(struct ipt_table_info)
+ + SMP_ALIGN(table->table->size) * smp_num_cpus);
+ if (!newinfo) {
+ ret = -ENOMEM;
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+ memcpy(newinfo->entries, table->table->entries, table->table->size);
+
+ ret = translate_table(table->name, table->valid_hooks,
+ newinfo, table->table->size,
+ table->table->num_entries,
+ table->table->hook_entry,
+ table->table->underflow);
+ if (ret != 0) {
+ vfree(newinfo);
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+
+ ret = down_interruptible(&ipt_mutex);
+ if (ret != 0) {
+ vfree(newinfo);
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+
+ /* Don't autoload: we'd eat our tail... */
+ if (list_named_find(&ipt_tables, table->name)) {
+ ret = -EEXIST;
+ goto free_unlock;
+ }
+
+ /* Simplifies replace_table code. */
+ table->private = &bootstrap;
+ if (!replace_table(table, 0, newinfo, &ret))
+ goto free_unlock;
+
+ duprintf("table->private->number = %u\n",
+ table->private->number);
+
+ /* save number of initial entries */
+ table->private->initial_entries = table->private->number;
+
+ table->lock = RW_LOCK_UNLOCKED;
+ list_prepend(&ipt_tables, table);
+
+ unlock:
+ up(&ipt_mutex);
+ return ret;
+
+ free_unlock:
+ vfree(newinfo);
+ MOD_DEC_USE_COUNT;
+ goto unlock;
+}
+
+void ipt_unregister_table(struct ipt_table *table)
+{
+ down(&ipt_mutex);
+ LIST_DELETE(&ipt_tables, table);
+ up(&ipt_mutex);
+
+ /* Decrease module usage counts and free resources */
+ IPT_ENTRY_ITERATE(table->private->entries, table->private->size,
+ cleanup_entry, NULL);
+ vfree(table->private);
+ MOD_DEC_USE_COUNT;
+}
+
+/* Returns 1 if the port is matched by the range, 0 otherwise */
+static inline int
+port_match(u_int16_t min, u_int16_t max, u_int16_t port, int invert)
+{
+ int ret;
+
+ ret = (port >= min && port <= max) ^ invert;
+ return ret;
+}
+
+static int
+tcp_find_option(u_int8_t option,
+ const struct tcphdr *tcp,
+ u_int16_t datalen,
+ int invert,
+ int *hotdrop)
+{
+ unsigned int i = sizeof(struct tcphdr);
+ const u_int8_t *opt = (u_int8_t *)tcp;
+
+ duprintf("tcp_match: finding option\n");
+ /* If we don't have the whole header, drop packet. */
+ if (tcp->doff * 4 < sizeof(struct tcphdr) ||
+ tcp->doff * 4 > datalen) {
+ *hotdrop = 1;
+ return 0;
+ }
+
+ while (i < tcp->doff * 4) {
+ if (opt[i] == option) return !invert;
+ if (opt[i] < 2) i++;
+ else i += opt[i+1]?:1;
+ }
+
+ return invert;
+}
+
+static int
+tcp_match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct tcphdr *tcp = hdr;
+ const struct ipt_tcp *tcpinfo = matchinfo;
+
+ /* To quote Alan:
+
+ Don't allow a fragment of TCP 8 bytes in. Nobody normal
+ causes this. Its a cracker trying to break in by doing a
+ flag overwrite to pass the direction checks.
+ */
+
+ if (offset == 1) {
+ duprintf("Dropping evil TCP offset=1 frag.\n");
+ *hotdrop = 1;
+ return 0;
+ } else if (offset == 0 && datalen < sizeof(struct tcphdr)) {
+ /* We've been asked to examine this packet, and we
+ can't. Hence, no choice but to drop. */
+ duprintf("Dropping evil TCP offset=0 tinygram.\n");
+ *hotdrop = 1;
+ return 0;
+ }
+
+ /* FIXME: Try tcp doff >> packet len against various stacks --RR */
+
+#define FWINVTCP(bool,invflg) ((bool) ^ !!(tcpinfo->invflags & invflg))
+
+ /* Must not be a fragment. */
+ return !offset
+ && port_match(tcpinfo->spts[0], tcpinfo->spts[1],
+ ntohs(tcp->source),
+ !!(tcpinfo->invflags & IPT_TCP_INV_SRCPT))
+ && port_match(tcpinfo->dpts[0], tcpinfo->dpts[1],
+ ntohs(tcp->dest),
+ !!(tcpinfo->invflags & IPT_TCP_INV_DSTPT))
+ && FWINVTCP((((unsigned char *)tcp)[13]
+ & tcpinfo->flg_mask)
+ == tcpinfo->flg_cmp,
+ IPT_TCP_INV_FLAGS)
+ && (!tcpinfo->option
+ || tcp_find_option(tcpinfo->option, tcp, datalen,
+ tcpinfo->invflags
+ & IPT_TCP_INV_OPTION,
+ hotdrop));
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+tcp_checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ const struct ipt_tcp *tcpinfo = matchinfo;
+
+ /* Must specify proto == TCP, and no unknown invflags */
+ return ip->proto == IPPROTO_TCP
+ && !(ip->invflags & IPT_INV_PROTO)
+ && matchsize == IPT_ALIGN(sizeof(struct ipt_tcp))
+ && !(tcpinfo->invflags & ~IPT_TCP_INV_MASK);
+}
+
+static int
+udp_match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct udphdr *udp = hdr;
+ const struct ipt_udp *udpinfo = matchinfo;
+
+ if (offset == 0 && datalen < sizeof(struct udphdr)) {
+ /* We've been asked to examine this packet, and we
+ can't. Hence, no choice but to drop. */
+ duprintf("Dropping evil UDP tinygram.\n");
+ *hotdrop = 1;
+ return 0;
+ }
+
+ /* Must not be a fragment. */
+ return !offset
+ && port_match(udpinfo->spts[0], udpinfo->spts[1],
+ ntohs(udp->source),
+ !!(udpinfo->invflags & IPT_UDP_INV_SRCPT))
+ && port_match(udpinfo->dpts[0], udpinfo->dpts[1],
+ ntohs(udp->dest),
+ !!(udpinfo->invflags & IPT_UDP_INV_DSTPT));
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+udp_checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchinfosize,
+ unsigned int hook_mask)
+{
+ const struct ipt_udp *udpinfo = matchinfo;
+
+ /* Must specify proto == UDP, and no unknown invflags */
+ if (ip->proto != IPPROTO_UDP || (ip->invflags & IPT_INV_PROTO)) {
+ duprintf("ipt_udp: Protocol %u != %u\n", ip->proto,
+ IPPROTO_UDP);
+ return 0;
+ }
+ if (matchinfosize != IPT_ALIGN(sizeof(struct ipt_udp))) {
+ duprintf("ipt_udp: matchsize %u != %u\n",
+ matchinfosize, IPT_ALIGN(sizeof(struct ipt_udp)));
+ return 0;
+ }
+ if (udpinfo->invflags & ~IPT_UDP_INV_MASK) {
+ duprintf("ipt_udp: unknown flags %X\n",
+ udpinfo->invflags);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Returns 1 if the type and code is matched by the range, 0 otherwise */
+static inline int
+icmp_type_code_match(u_int8_t test_type, u_int8_t min_code, u_int8_t max_code,
+ u_int8_t type, u_int8_t code,
+ int invert)
+{
+ return ((test_type == 0xFF) || (type == test_type && code >= min_code && code <= max_code))
+ ^ invert;
+}
+
+static int
+icmp_match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct icmphdr *icmp = hdr;
+ const struct ipt_icmp *icmpinfo = matchinfo;
+
+ if (offset == 0 && datalen < 2) {
+ /* We've been asked to examine this packet, and we
+ can't. Hence, no choice but to drop. */
+ duprintf("Dropping evil ICMP tinygram.\n");
+ *hotdrop = 1;
+ return 0;
+ }
+
+ /* Must not be a fragment. */
+ return !offset
+ && icmp_type_code_match(icmpinfo->type,
+ icmpinfo->code[0],
+ icmpinfo->code[1],
+ icmp->type, icmp->code,
+ !!(icmpinfo->invflags&IPT_ICMP_INV));
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+icmp_checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ const struct ipt_icmp *icmpinfo = matchinfo;
+
+ /* Must specify proto == ICMP, and no unknown invflags */
+ return ip->proto == IPPROTO_ICMP
+ && !(ip->invflags & IPT_INV_PROTO)
+ && matchsize == IPT_ALIGN(sizeof(struct ipt_icmp))
+ && !(icmpinfo->invflags & ~IPT_ICMP_INV);
+}
+
+/* The built-in targets: standard (NULL) and error. */
+static struct ipt_target ipt_standard_target
+= { { NULL, NULL }, IPT_STANDARD_TARGET, NULL, NULL, NULL };
+static struct ipt_target ipt_error_target
+= { { NULL, NULL }, IPT_ERROR_TARGET, ipt_error, NULL, NULL };
+
+static struct nf_sockopt_ops ipt_sockopts
+= { { NULL, NULL }, PF_INET, IPT_BASE_CTL, IPT_SO_SET_MAX+1, do_ipt_set_ctl,
+ IPT_BASE_CTL, IPT_SO_GET_MAX+1, do_ipt_get_ctl, 0, NULL };
+
+static struct ipt_match tcp_matchstruct
+= { { NULL, NULL }, "tcp", &tcp_match, &tcp_checkentry, NULL };
+static struct ipt_match udp_matchstruct
+= { { NULL, NULL }, "udp", &udp_match, &udp_checkentry, NULL };
+static struct ipt_match icmp_matchstruct
+= { { NULL, NULL }, "icmp", &icmp_match, &icmp_checkentry, NULL };
+
+#ifdef CONFIG_PROC_FS
+static inline int print_name(const char *i,
+ off_t start_offset, char *buffer, int length,
+ off_t *pos, unsigned int *count)
+{
+ if (*count >= start_offset) {
+ unsigned int namelen;
+
+ namelen = sprintf(buffer + *pos, "%s\n",
+ i + sizeof(struct list_head));
+ if (*pos + namelen > length) {
+ /* Stop iterating */
+ return 1;
+ }
+ *pos += namelen;
+ }
+ (*count)++;
+ return 0;
+}
+
+static inline int print_target(const struct ipt_target *t,
+ off_t start_offset, char *buffer, int length,
+ off_t *pos, unsigned int *count)
+{
+ if (t == &ipt_standard_target || t == &ipt_error_target)
+ return 0;
+ return print_name((char *)t, start_offset, buffer, length, pos, count);
+}
+
+static int ipt_get_tables(char *buffer, char **start, off_t offset, int length)
+{
+ off_t pos = 0;
+ unsigned int count = 0;
+
+ if (down_interruptible(&ipt_mutex) != 0)
+ return 0;
+
+ LIST_FIND(&ipt_tables, print_name, void *,
+ offset, buffer, length, &pos, &count);
+
+ up(&ipt_mutex);
+
+ /* `start' hack - see fs/proc/generic.c line ~105 */
+ *start=(char *)((unsigned long)count-offset);
+ return pos;
+}
+
+static int ipt_get_targets(char *buffer, char **start, off_t offset, int length)
+{
+ off_t pos = 0;
+ unsigned int count = 0;
+
+ if (down_interruptible(&ipt_mutex) != 0)
+ return 0;
+
+ LIST_FIND(&ipt_target, print_target, struct ipt_target *,
+ offset, buffer, length, &pos, &count);
+
+ up(&ipt_mutex);
+
+ *start = (char *)((unsigned long)count - offset);
+ return pos;
+}
+
+static int ipt_get_matches(char *buffer, char **start, off_t offset, int length)
+{
+ off_t pos = 0;
+ unsigned int count = 0;
+
+ if (down_interruptible(&ipt_mutex) != 0)
+ return 0;
+
+ LIST_FIND(&ipt_match, print_name, void *,
+ offset, buffer, length, &pos, &count);
+
+ up(&ipt_mutex);
+
+ *start = (char *)((unsigned long)count - offset);
+ return pos;
+}
+
+static struct { char *name; get_info_t *get_info; } ipt_proc_entry[] =
+{ { "ip_tables_names", ipt_get_tables },
+ { "ip_tables_targets", ipt_get_targets },
+ { "ip_tables_matches", ipt_get_matches },
+ { NULL, NULL} };
+#endif /*CONFIG_PROC_FS*/
+
+static int __init init(void)
+{
+ int ret;
+
+ /* Noone else will be downing sem now, so we won't sleep */
+ down(&ipt_mutex);
+ list_append(&ipt_target, &ipt_standard_target);
+ list_append(&ipt_target, &ipt_error_target);
+ list_append(&ipt_match, &tcp_matchstruct);
+ list_append(&ipt_match, &udp_matchstruct);
+ list_append(&ipt_match, &icmp_matchstruct);
+ up(&ipt_mutex);
+
+ /* Register setsockopt */
+ ret = nf_register_sockopt(&ipt_sockopts);
+ if (ret < 0) {
+ duprintf("Unable to register sockopts.\n");
+ return ret;
+ }
+
+#ifdef CONFIG_PROC_FS
+ {
+ struct proc_dir_entry *proc;
+ int i;
+
+ for (i = 0; ipt_proc_entry[i].name; i++) {
+ proc = proc_net_create(ipt_proc_entry[i].name, 0,
+ ipt_proc_entry[i].get_info);
+ if (!proc) {
+ while (--i >= 0)
+ proc_net_remove(ipt_proc_entry[i].name);
+ nf_unregister_sockopt(&ipt_sockopts);
+ return -ENOMEM;
+ }
+ proc->owner = THIS_MODULE;
+ }
+ }
+#endif
+
+ printk("ip_tables: (C) 2000-2002 Netfilter core team\n");
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ nf_unregister_sockopt(&ipt_sockopts);
+#ifdef CONFIG_PROC_FS
+ {
+ int i;
+ for (i = 0; ipt_proc_entry[i].name; i++)
+ proc_net_remove(ipt_proc_entry[i].name);
+ }
+#endif
+}
+
+EXPORT_SYMBOL(ipt_register_table);
+EXPORT_SYMBOL(ipt_unregister_table);
+EXPORT_SYMBOL(ipt_register_match);
+EXPORT_SYMBOL(ipt_unregister_match);
+EXPORT_SYMBOL(ipt_do_table);
+EXPORT_SYMBOL(ipt_register_target);
+EXPORT_SYMBOL(ipt_unregister_target);
+EXPORT_SYMBOL(ipt_find_target_lock);
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipchains_core.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipchains_core.c
new file mode 100644
index 0000000..f95aa17
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipchains_core.c
@@ -0,0 +1,1771 @@
+/* Minor modifications to fit on compatibility framework:
+ Rusty.Russell@rustcorp.com.au
+*/
+
+/*
+ * This code is heavily based on the code on the old ip_fw.c code; see below for
+ * copyrights and attributions of the old code. This code is basically GPL.
+ *
+ * 15-Aug-1997: Major changes to allow graphs for firewall rules.
+ * Paul Russell <Paul.Russell@rustcorp.com.au> and
+ * Michael Neuling <Michael.Neuling@rustcorp.com.au>
+ * 24-Aug-1997: Generalised protocol handling (not just TCP/UDP/ICMP).
+ * Added explicit RETURN from chains.
+ * Removed TOS mangling (done in ipchains 1.0.1).
+ * Fixed read & reset bug by reworking proc handling.
+ * Paul Russell <Paul.Russell@rustcorp.com.au>
+ * 28-Sep-1997: Added packet marking for net sched code.
+ * Removed fw_via comparisons: all done on device name now,
+ * similar to changes in ip_fw.c in DaveM's CVS970924 tree.
+ * Paul Russell <Paul.Russell@rustcorp.com.au>
+ * 2-Nov-1997: Moved types across to __u16, etc.
+ * Added inverse flags.
+ * Fixed fragment bug (in args to port_match).
+ * Changed mark to only one flag (MARKABS).
+ * 21-Nov-1997: Added ability to test ICMP code.
+ * 19-Jan-1998: Added wildcard interfaces.
+ * 6-Feb-1998: Merged 2.0 and 2.1 versions.
+ * Initialised ip_masq for 2.0.x version.
+ * Added explicit NETLINK option for 2.1.x version.
+ * Added packet and byte counters for policy matches.
+ * 26-Feb-1998: Fixed race conditions, added SMP support.
+ * 18-Mar-1998: Fix SMP, fix race condition fix.
+ * 1-May-1998: Remove caching of device pointer.
+ * 12-May-1998: Allow tiny fragment case for TCP/UDP.
+ * 15-May-1998: Treat short packets as fragments, don't just block.
+ * 3-Jan-1999: Fixed serious procfs security hole -- users should never
+ * be allowed to view the chains!
+ * Marc Santoro <ultima@snicker.emoti.com>
+ * 29-Jan-1999: Locally generated bogus IPs dealt with, rather than crash
+ * during dump_packet. --RR.
+ * 19-May-1999: Star Wars: The Phantom Menace opened. Rule num
+ * printed in log (modified from Michael Hasenstein's patch).
+ * Added SYN in log message. --RR
+ * 23-Jul-1999: Fixed small fragment security exposure opened on 15-May-1998.
+ * John McDonald <jm@dataprotect.com>
+ * Thomas Lopatic <tl@dataprotect.com>
+ */
+
+/*
+ *
+ * The origina Linux port was done Alan Cox, with changes/fixes from
+ * Pauline Middlelink, Jos Vos, Thomas Quinot, Wouter Gadeyne, Juan
+ * Jose Ciarlante, Bernd Eckenfels, Keith Owens and others.
+ *
+ * Copyright from the original FreeBSD version follows:
+ *
+ * Copyright (c) 1993 Daniel Boulet
+ * Copyright (c) 1994 Ugen J.S.Antsilevich
+ *
+ * Redistribution and use in source forms, with and without modification,
+ * are permitted provided that this entire comment appears intact.
+ *
+ * Redistribution in binary form may occur without any restrictions.
+ * Obviously, it would be nice if you gave credit where credit is due
+ * but requiring it would be too onerous.
+ *
+ * This software is provided ``AS IS'' without any warranties of any kind. */
+
+#include <linux/config.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/icmp.h>
+#include <linux/udp.h>
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <net/route.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+#include <net/sock.h>
+#include <net/icmp.h>
+#include <linux/netlink.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4/compat_firewall.h>
+#include <linux/netfilter_ipv4/ipchains_core.h>
+
+#include <net/checksum.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+
+/* Understanding locking in this code: (thanks to Alan Cox for using
+ * little words to explain this to me). -- PR
+ *
+ * In UP, there can be two packets traversing the chains:
+ * 1) A packet from the current userspace context
+ * 2) A packet off the bh handlers (timer or net).
+ *
+ * For SMP (kernel v2.1+), multiply this by # CPUs.
+ *
+ * [Note that this in not correct for 2.2 - because the socket code always
+ * uses lock_kernel() to serialize, and bottom halves (timers and net_bhs)
+ * only run on one CPU at a time. This will probably change for 2.3.
+ * It is still good to use spinlocks because that avoids the global cli()
+ * for updating the tables, which is rather costly in SMP kernels -AK]
+ *
+ * This means counters and backchains can get corrupted if no precautions
+ * are taken.
+ *
+ * To actually alter a chain on UP, we need only do a cli(), as this will
+ * stop a bh handler firing, as we are in the current userspace context
+ * (coming from a setsockopt()).
+ *
+ * On SMP, we need a write_lock_irqsave(), which is a simple cli() in
+ * UP.
+ *
+ * For backchains and counters, we use an array, indexed by
+ * [cpu_number_map[smp_processor_id()]*2 + !in_interrupt()]; the array is of
+ * size [smp_num_cpus*2]. For v2.0, smp_num_cpus is effectively 1. So,
+ * confident of uniqueness, we modify counters even though we only
+ * have a read lock (to read the counters, you need a write lock,
+ * though). */
+
+/* Why I didn't use straight locking... -- PR
+ *
+ * The backchains can be separated out of the ip_chains structure, and
+ * allocated as needed inside ip_fw_check().
+ *
+ * The counters, however, can't. Trying to lock these means blocking
+ * interrupts every time we want to access them. This would suck HARD
+ * performance-wise. Not locking them leads to possible corruption,
+ * made worse on 32-bit machines (counters are 64-bit). */
+
+/*#define DEBUG_IP_FIREWALL*/
+/*#define DEBUG_ALLOW_ALL*/ /* Useful for remote debugging */
+/*#define DEBUG_IP_FIREWALL_USER*/
+/*#define DEBUG_IP_FIREWALL_LOCKING*/
+
+#if defined(CONFIG_NETLINK_DEV) || defined(CONFIG_NETLINK_DEV_MODULE)
+static struct sock *ipfwsk;
+#endif
+
+#ifdef CONFIG_SMP
+#define SLOT_NUMBER() (cpu_number_map(smp_processor_id())*2 + !in_interrupt())
+#else /* !SMP */
+#define SLOT_NUMBER() (!in_interrupt())
+#endif /* CONFIG_SMP */
+#define NUM_SLOTS (smp_num_cpus*2)
+
+#define SIZEOF_STRUCT_IP_CHAIN (sizeof(struct ip_chain) \
+ + NUM_SLOTS*sizeof(struct ip_reent))
+#define SIZEOF_STRUCT_IP_FW_KERNEL (sizeof(struct ip_fwkernel) \
+ + NUM_SLOTS*sizeof(struct ip_counters))
+
+#ifdef DEBUG_IP_FIREWALL_LOCKING
+static unsigned int fwc_rlocks, fwc_wlocks;
+#define FWC_DEBUG_LOCK(d) \
+do { \
+ FWC_DONT_HAVE_LOCK(d); \
+ d |= (1 << SLOT_NUMBER()); \
+} while (0)
+
+#define FWC_DEBUG_UNLOCK(d) \
+do { \
+ FWC_HAVE_LOCK(d); \
+ d &= ~(1 << SLOT_NUMBER()); \
+} while (0)
+
+#define FWC_DONT_HAVE_LOCK(d) \
+do { \
+ if ((d) & (1 << SLOT_NUMBER())) \
+ printk("%s:%i: Got lock on %i already!\n", \
+ __FILE__, __LINE__, SLOT_NUMBER()); \
+} while(0)
+
+#define FWC_HAVE_LOCK(d) \
+do { \
+ if (!((d) & (1 << SLOT_NUMBER()))) \
+ printk("%s:%i:No lock on %i!\n", \
+ __FILE__, __LINE__, SLOT_NUMBER()); \
+} while (0)
+
+#else
+#define FWC_DEBUG_LOCK(d) do { } while(0)
+#define FWC_DEBUG_UNLOCK(d) do { } while(0)
+#define FWC_DONT_HAVE_LOCK(d) do { } while(0)
+#define FWC_HAVE_LOCK(d) do { } while(0)
+#endif /*DEBUG_IP_FIRWALL_LOCKING*/
+
+#define FWC_READ_LOCK(l) do { FWC_DEBUG_LOCK(fwc_rlocks); read_lock(l); } while (0)
+#define FWC_WRITE_LOCK(l) do { FWC_DEBUG_LOCK(fwc_wlocks); write_lock(l); } while (0)
+#define FWC_READ_LOCK_IRQ(l,f) do { FWC_DEBUG_LOCK(fwc_rlocks); read_lock_irqsave(l,f); } while (0)
+#define FWC_WRITE_LOCK_IRQ(l,f) do { FWC_DEBUG_LOCK(fwc_wlocks); write_lock_irqsave(l,f); } while (0)
+#define FWC_READ_UNLOCK(l) do { FWC_DEBUG_UNLOCK(fwc_rlocks); read_unlock(l); } while (0)
+#define FWC_WRITE_UNLOCK(l) do { FWC_DEBUG_UNLOCK(fwc_wlocks); write_unlock(l); } while (0)
+#define FWC_READ_UNLOCK_IRQ(l,f) do { FWC_DEBUG_UNLOCK(fwc_rlocks); read_unlock_irqrestore(l,f); } while (0)
+#define FWC_WRITE_UNLOCK_IRQ(l,f) do { FWC_DEBUG_UNLOCK(fwc_wlocks); write_unlock_irqrestore(l,f); } while (0)
+
+struct ip_chain;
+
+struct ip_counters
+{
+ __u64 pcnt, bcnt; /* Packet and byte counters */
+};
+
+struct ip_fwkernel
+{
+ struct ip_fw ipfw;
+ struct ip_fwkernel *next; /* where to go next if current
+ * rule doesn't match */
+ struct ip_chain *branch; /* which branch to jump to if
+ * current rule matches */
+ int simplebranch; /* Use this if branch == NULL */
+ struct ip_counters counters[0]; /* Actually several of these */
+};
+
+struct ip_reent
+{
+ struct ip_chain *prevchain; /* Pointer to referencing chain */
+ struct ip_fwkernel *prevrule; /* Pointer to referencing rule */
+ struct ip_counters counters;
+};
+
+struct ip_chain
+{
+ ip_chainlabel label; /* Defines the label for each block */
+ struct ip_chain *next; /* Pointer to next block */
+ struct ip_fwkernel *chain; /* Pointer to first rule in block */
+ __u32 refcount; /* Number of refernces to block */
+ int policy; /* Default rule for chain. Only *
+ * used in built in chains */
+ struct ip_reent reent[0]; /* Actually several of these */
+};
+
+/*
+ * Implement IP packet firewall
+ */
+
+#ifdef DEBUG_IP_FIREWALL
+#define dprintf(format, args...) printk(format , ## args)
+#else
+#define dprintf(format, args...)
+#endif
+
+#ifdef DEBUG_IP_FIREWALL_USER
+#define duprintf(format, args...) printk(format , ## args)
+#else
+#define duprintf(format, args...)
+#endif
+
+/* Lock around ip_fw_chains linked list structure */
+rwlock_t ip_fw_lock = RW_LOCK_UNLOCKED;
+
+/* Head of linked list of fw rules */
+static struct ip_chain *ip_fw_chains;
+
+#define IP_FW_INPUT_CHAIN ip_fw_chains
+#define IP_FW_FORWARD_CHAIN (ip_fw_chains->next)
+#define IP_FW_OUTPUT_CHAIN (ip_fw_chains->next->next)
+
+/* Returns 1 if the port is matched by the range, 0 otherwise */
+extern inline int port_match(__u16 min, __u16 max, __u16 port,
+ int frag, int invert)
+{
+ if (frag) /* Fragments fail ANY port test. */
+ return (min == 0 && max == 0xFFFF);
+ else return (port >= min && port <= max) ^ invert;
+}
+
+/* Returns whether matches rule or not. */
+static int ip_rule_match(struct ip_fwkernel *f,
+ const char *ifname,
+ struct iphdr *ip,
+ char tcpsyn,
+ __u16 src_port, __u16 dst_port,
+ char isfrag)
+{
+#define FWINV(bool,invflg) ((bool) ^ !!(f->ipfw.fw_invflg & invflg))
+ /*
+ * This is a bit simpler as we don't have to walk
+ * an interface chain as you do in BSD - same logic
+ * however.
+ */
+
+ if (FWINV((ip->saddr&f->ipfw.fw_smsk.s_addr) != f->ipfw.fw_src.s_addr,
+ IP_FW_INV_SRCIP)
+ || FWINV((ip->daddr&f->ipfw.fw_dmsk.s_addr)!=f->ipfw.fw_dst.s_addr,
+ IP_FW_INV_DSTIP)) {
+ dprintf("Source or dest mismatch.\n");
+
+ dprintf("SRC: %u. Mask: %u. Target: %u.%s\n", ip->saddr,
+ f->ipfw.fw_smsk.s_addr, f->ipfw.fw_src.s_addr,
+ f->ipfw.fw_invflg & IP_FW_INV_SRCIP ? " (INV)" : "");
+ dprintf("DST: %u. Mask: %u. Target: %u.%s\n", ip->daddr,
+ f->ipfw.fw_dmsk.s_addr, f->ipfw.fw_dst.s_addr,
+ f->ipfw.fw_invflg & IP_FW_INV_DSTIP ? " (INV)" : "");
+ return 0;
+ }
+
+ /*
+ * Look for a VIA device match
+ */
+ if (f->ipfw.fw_flg & IP_FW_F_WILDIF) {
+ if (FWINV(strncmp(ifname, f->ipfw.fw_vianame,
+ strlen(f->ipfw.fw_vianame)) != 0,
+ IP_FW_INV_VIA)) {
+ dprintf("Wildcard interface mismatch.%s\n",
+ f->ipfw.fw_invflg & IP_FW_INV_VIA ? " (INV)" : "");
+ return 0; /* Mismatch */
+ }
+ }
+ else if (FWINV(strcmp(ifname, f->ipfw.fw_vianame) != 0,
+ IP_FW_INV_VIA)) {
+ dprintf("Interface name does not match.%s\n",
+ f->ipfw.fw_invflg & IP_FW_INV_VIA
+ ? " (INV)" : "");
+ return 0; /* Mismatch */
+ }
+
+ /*
+ * Ok the chain addresses match.
+ */
+
+ /* If we have a fragment rule but the packet is not a fragment
+ * the we return zero */
+ if (FWINV((f->ipfw.fw_flg&IP_FW_F_FRAG) && !isfrag, IP_FW_INV_FRAG)) {
+ dprintf("Fragment rule but not fragment.%s\n",
+ f->ipfw.fw_invflg & IP_FW_INV_FRAG ? " (INV)" : "");
+ return 0;
+ }
+
+ /* Fragment NEVER passes a SYN test, even an inverted one. */
+ if (FWINV((f->ipfw.fw_flg&IP_FW_F_TCPSYN) && !tcpsyn, IP_FW_INV_SYN)
+ || (isfrag && (f->ipfw.fw_flg&IP_FW_F_TCPSYN))) {
+ dprintf("Rule requires SYN and packet has no SYN.%s\n",
+ f->ipfw.fw_invflg & IP_FW_INV_SYN ? " (INV)" : "");
+ return 0;
+ }
+
+ if (f->ipfw.fw_proto) {
+ /*
+ * Specific firewall - packet's protocol
+ * must match firewall's.
+ */
+
+ if (FWINV(ip->protocol!=f->ipfw.fw_proto, IP_FW_INV_PROTO)) {
+ dprintf("Packet protocol %hi does not match %hi.%s\n",
+ ip->protocol, f->ipfw.fw_proto,
+ f->ipfw.fw_invflg&IP_FW_INV_PROTO ? " (INV)":"");
+ return 0;
+ }
+
+ /* For non TCP/UDP/ICMP, port range is max anyway. */
+ if (!port_match(f->ipfw.fw_spts[0],
+ f->ipfw.fw_spts[1],
+ src_port, isfrag,
+ !!(f->ipfw.fw_invflg&IP_FW_INV_SRCPT))
+ || !port_match(f->ipfw.fw_dpts[0],
+ f->ipfw.fw_dpts[1],
+ dst_port, isfrag,
+ !!(f->ipfw.fw_invflg
+ &IP_FW_INV_DSTPT))) {
+ dprintf("Port match failed.\n");
+ return 0;
+ }
+ }
+
+ dprintf("Match succeeded.\n");
+ return 1;
+}
+
+static const char *branchname(struct ip_chain *branch,int simplebranch)
+{
+ if (branch)
+ return branch->label;
+ switch (simplebranch)
+ {
+ case FW_BLOCK: return IP_FW_LABEL_BLOCK;
+ case FW_ACCEPT: return IP_FW_LABEL_ACCEPT;
+ case FW_REJECT: return IP_FW_LABEL_REJECT;
+ case FW_REDIRECT: return IP_FW_LABEL_REDIRECT;
+ case FW_MASQUERADE: return IP_FW_LABEL_MASQUERADE;
+ case FW_SKIP: return "-";
+ case FW_SKIP+1: return IP_FW_LABEL_RETURN;
+ default:
+ return "UNKNOWN";
+ }
+}
+
+/*
+ * VERY ugly piece of code which actually
+ * makes kernel printf for matching packets...
+ */
+static void dump_packet(const struct iphdr *ip,
+ const char *ifname,
+ struct ip_fwkernel *f,
+ const ip_chainlabel chainlabel,
+ __u16 src_port,
+ __u16 dst_port,
+ unsigned int count,
+ int syn)
+{
+ __u32 *opt = (__u32 *) (ip + 1);
+ int opti;
+
+ if (f) {
+ printk(KERN_INFO "Packet log: %s ",chainlabel);
+ printk("%s ",branchname(f->branch,f->simplebranch));
+ if (f->simplebranch==FW_REDIRECT)
+ printk("%d ",f->ipfw.fw_redirpt);
+ }
+
+ printk("%s PROTO=%d %u.%u.%u.%u:%hu %u.%u.%u.%u:%hu"
+ " L=%hu S=0x%2.2hX I=%hu F=0x%4.4hX T=%hu",
+ ifname, ip->protocol, NIPQUAD(ip->saddr),
+ src_port, NIPQUAD(ip->daddr),
+ dst_port,
+ ntohs(ip->tot_len), ip->tos, ntohs(ip->id),
+ ntohs(ip->frag_off), ip->ttl);
+
+ for (opti = 0; opti < (ip->ihl - sizeof(struct iphdr) / 4); opti++)
+ printk(" O=0x%8.8X", *opt++);
+ printk(" %s(#%d)\n", syn ? "SYN " : /* "PENANCE" */ "", count);
+}
+
+/* function for checking chain labels for user space. */
+static int check_label(ip_chainlabel label)
+{
+ unsigned int i;
+ /* strlen must be < IP_FW_MAX_LABEL_LENGTH. */
+ for (i = 0; i < IP_FW_MAX_LABEL_LENGTH + 1; i++)
+ if (label[i] == '\0') return 1;
+
+ return 0;
+}
+
+/* This function returns a pointer to the first chain with a label
+ * that matches the one given. */
+static struct ip_chain *find_label(ip_chainlabel label)
+{
+ struct ip_chain *tmp;
+ FWC_HAVE_LOCK(fwc_rlocks | fwc_wlocks);
+ for (tmp = ip_fw_chains; tmp; tmp = tmp->next)
+ if (strcmp(tmp->label,label) == 0)
+ break;
+ return tmp;
+}
+
+/* This function returns a boolean which when true sets answer to one
+ of the FW_*. */
+static int find_special(ip_chainlabel label, int *answer)
+{
+ if (label[0] == '\0') {
+ *answer = FW_SKIP; /* => pass-through rule */
+ return 1;
+ } else if (strcmp(label,IP_FW_LABEL_ACCEPT) == 0) {
+ *answer = FW_ACCEPT;
+ return 1;
+ } else if (strcmp(label,IP_FW_LABEL_BLOCK) == 0) {
+ *answer = FW_BLOCK;
+ return 1;
+ } else if (strcmp(label,IP_FW_LABEL_REJECT) == 0) {
+ *answer = FW_REJECT;
+ return 1;
+ } else if (strcmp(label,IP_FW_LABEL_REDIRECT) == 0) {
+ *answer = FW_REDIRECT;
+ return 1;
+ } else if (strcmp(label,IP_FW_LABEL_MASQUERADE) == 0) {
+ *answer = FW_MASQUERADE;
+ return 1;
+ } else if (strcmp(label, IP_FW_LABEL_RETURN) == 0) {
+ *answer = FW_SKIP+1;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/* This function cleans up the prevchain and prevrule. If the verbose
+ * flag is set then he names of the chains will be printed as it
+ * cleans up. */
+static void cleanup(struct ip_chain *chain,
+ const int verbose,
+ unsigned int slot)
+{
+ struct ip_chain *tmpchain = chain->reent[slot].prevchain;
+ if (verbose)
+ printk(KERN_ERR "Chain backtrace: ");
+ while (tmpchain) {
+ if (verbose)
+ printk("%s<-",chain->label);
+ chain->reent[slot].prevchain = NULL;
+ chain = tmpchain;
+ tmpchain = chain->reent[slot].prevchain;
+ }
+ if (verbose)
+ printk("%s\n",chain->label);
+}
+
+static inline int
+ip_fw_domatch(struct ip_fwkernel *f,
+ struct iphdr *ip,
+ const char *rif,
+ const ip_chainlabel label,
+ struct sk_buff *skb,
+ unsigned int slot,
+ __u16 src_port, __u16 dst_port,
+ unsigned int count,
+ int tcpsyn)
+{
+ f->counters[slot].bcnt+=ntohs(ip->tot_len);
+ f->counters[slot].pcnt++;
+ if (f->ipfw.fw_flg & IP_FW_F_PRN) {
+ dump_packet(ip,rif,f,label,src_port,dst_port,count,tcpsyn);
+ }
+ ip->tos = (ip->tos & f->ipfw.fw_tosand) ^ f->ipfw.fw_tosxor;
+
+/* This functionality is useless in stock 2.0.x series, but we don't
+ * discard the mark thing altogether, to avoid breaking ipchains (and,
+ * more importantly, the ipfwadm wrapper) --PR */
+ if (f->ipfw.fw_flg & IP_FW_F_MARKABS) {
+ skb->nfmark = f->ipfw.fw_mark;
+ } else {
+ skb->nfmark += f->ipfw.fw_mark;
+ }
+ if (f->ipfw.fw_flg & IP_FW_F_NETLINK) {
+#if defined(CONFIG_NETLINK_DEV) || defined(CONFIG_NETLINK_DEV_MODULE)
+ size_t len = min_t(unsigned int, f->ipfw.fw_outputsize, ntohs(ip->tot_len))
+ + sizeof(__u32) + sizeof(skb->nfmark) + IFNAMSIZ;
+ struct sk_buff *outskb=alloc_skb(len, GFP_ATOMIC);
+
+ duprintf("Sending packet out NETLINK (length = %u).\n",
+ (unsigned int)len);
+ if (outskb) {
+ /* Prepend length, mark & interface */
+ skb_put(outskb, len);
+ *((__u32 *)outskb->data) = (__u32)len;
+ *((__u32 *)(outskb->data+sizeof(__u32))) = skb->nfmark;
+ strcpy(outskb->data+sizeof(__u32)*2, rif);
+ memcpy(outskb->data+sizeof(__u32)*2+IFNAMSIZ, ip,
+ len-(sizeof(__u32)*2+IFNAMSIZ));
+ netlink_broadcast(ipfwsk, outskb, 0, ~0, GFP_ATOMIC);
+ }
+ else {
+#endif
+ if (net_ratelimit())
+ printk(KERN_WARNING "ip_fw: packet drop due to "
+ "netlink failure\n");
+ return 0;
+#if defined(CONFIG_NETLINK_DEV) || defined(CONFIG_NETLINK_DEV_MODULE)
+ }
+#endif
+ }
+ return 1;
+}
+
+/*
+ * Returns one of the generic firewall policies, like FW_ACCEPT.
+ *
+ * The testing is either false for normal firewall mode or true for
+ * user checking mode (counters are not updated, TOS & mark not done).
+ */
+static int
+ip_fw_check(struct iphdr *ip,
+ const char *rif,
+ __u16 *redirport,
+ struct ip_chain *chain,
+ struct sk_buff *skb,
+ unsigned int slot,
+ int testing)
+{
+ struct tcphdr *tcp=(struct tcphdr *)((__u32 *)ip+ip->ihl);
+ struct udphdr *udp=(struct udphdr *)((__u32 *)ip+ip->ihl);
+ struct icmphdr *icmp=(struct icmphdr *)((__u32 *)ip+ip->ihl);
+ __u32 src, dst;
+ __u16 src_port = 0xFFFF, dst_port = 0xFFFF;
+ char tcpsyn=0;
+ __u16 offset;
+ unsigned char oldtos;
+ struct ip_fwkernel *f;
+ int ret = FW_SKIP+2;
+ unsigned int count;
+
+ /* We handle fragments by dealing with the first fragment as
+ * if it was a normal packet. All other fragments are treated
+ * normally, except that they will NEVER match rules that ask
+ * things we don't know, ie. tcp syn flag or ports). If the
+ * rule is also a fragment-specific rule, non-fragments won't
+ * match it. */
+
+ offset = ntohs(ip->frag_off) & IP_OFFSET;
+
+ /*
+ * Don't allow a fragment of TCP 8 bytes in. Nobody
+ * normal causes this. Its a cracker trying to break
+ * in by doing a flag overwrite to pass the direction
+ * checks.
+ */
+ if (offset == 1 && ip->protocol == IPPROTO_TCP) {
+ if (!testing && net_ratelimit()) {
+ printk("Suspect TCP fragment.\n");
+ dump_packet(ip,rif,NULL,NULL,0,0,0,0);
+ }
+ return FW_BLOCK;
+ }
+
+ /* If we can't investigate ports, treat as fragment. It's
+ * either a trucated whole packet, or a truncated first
+ * fragment, or a TCP first fragment of length 8-15, in which
+ * case the above rule stops reassembly.
+ */
+ if (offset == 0) {
+ unsigned int size_req;
+ switch (ip->protocol) {
+ case IPPROTO_TCP:
+ /* Don't care about things past flags word */
+ size_req = 16;
+ break;
+
+ case IPPROTO_UDP:
+ case IPPROTO_ICMP:
+ size_req = 8;
+ break;
+
+ default:
+ size_req = 0;
+ }
+
+ /* If it is a truncated first fragment then it can be
+ * used to rewrite port information, and thus should
+ * be blocked.
+ */
+ if (ntohs(ip->tot_len) < (ip->ihl<<2)+size_req) {
+ if (!testing && net_ratelimit()) {
+ printk("Suspect short first fragment.\n");
+ dump_packet(ip,rif,NULL,NULL,0,0,0,0);
+ }
+ return FW_BLOCK;
+ }
+ }
+
+ src = ip->saddr;
+ dst = ip->daddr;
+ oldtos = ip->tos;
+
+ /*
+ * If we got interface from which packet came
+ * we can use the address directly. Linux 2.1 now uses address
+ * chains per device too, but unlike BSD we first check if the
+ * incoming packet matches a device address and the routing
+ * table before calling the firewall.
+ */
+
+ dprintf("Packet ");
+ switch(ip->protocol)
+ {
+ case IPPROTO_TCP:
+ dprintf("TCP ");
+ if (!offset) {
+ src_port=ntohs(tcp->source);
+ dst_port=ntohs(tcp->dest);
+
+ /* Connection initilisation can only
+ * be made when the syn bit is set and
+ * neither of the ack or reset is
+ * set. */
+ if(tcp->syn && !(tcp->ack || tcp->rst))
+ tcpsyn=1;
+ }
+ break;
+ case IPPROTO_UDP:
+ dprintf("UDP ");
+ if (!offset) {
+ src_port=ntohs(udp->source);
+ dst_port=ntohs(udp->dest);
+ }
+ break;
+ case IPPROTO_ICMP:
+ if (!offset) {
+ src_port=(__u16)icmp->type;
+ dst_port=(__u16)icmp->code;
+ }
+ dprintf("ICMP ");
+ break;
+ default:
+ dprintf("p=%d ",ip->protocol);
+ break;
+ }
+#ifdef DEBUG_IP_FIREWALL
+ print_ip(ip->saddr);
+
+ if (offset)
+ dprintf(":fragment (%i) ", ((int)offset)<<2);
+ else if (ip->protocol==IPPROTO_TCP || ip->protocol==IPPROTO_UDP
+ || ip->protocol==IPPROTO_ICMP)
+ dprintf(":%hu:%hu", src_port, dst_port);
+ dprintf("\n");
+#endif
+
+ if (!testing) FWC_READ_LOCK(&ip_fw_lock);
+ else FWC_HAVE_LOCK(fwc_rlocks);
+
+ f = chain->chain;
+ do {
+ count = 0;
+ for (; f; f = f->next) {
+ count++;
+ if (ip_rule_match(f,rif,ip,
+ tcpsyn,src_port,dst_port,offset)) {
+ if (!testing
+ && !ip_fw_domatch(f, ip, rif, chain->label,
+ skb, slot,
+ src_port, dst_port,
+ count, tcpsyn)) {
+ ret = FW_BLOCK;
+ cleanup(chain, 0, slot);
+ goto out;
+ }
+ break;
+ }
+ }
+ if (f) {
+ if (f->branch) {
+ /* Do sanity check to see if we have
+ * already set prevchain and if so we
+ * must be in a loop */
+ if (f->branch->reent[slot].prevchain) {
+ if (!testing) {
+ printk(KERN_ERR
+ "IP firewall: "
+ "Loop detected "
+ "at `%s'.\n",
+ f->branch->label);
+ cleanup(chain, 1, slot);
+ ret = FW_BLOCK;
+ } else {
+ cleanup(chain, 0, slot);
+ ret = FW_SKIP+1;
+ }
+ }
+ else {
+ f->branch->reent[slot].prevchain
+ = chain;
+ f->branch->reent[slot].prevrule
+ = f->next;
+ chain = f->branch;
+ f = chain->chain;
+ }
+ }
+ else if (f->simplebranch == FW_SKIP)
+ f = f->next;
+ else if (f->simplebranch == FW_SKIP+1) {
+ /* Just like falling off the chain */
+ goto fall_off_chain;
+ } else {
+ cleanup(chain, 0, slot);
+ ret = f->simplebranch;
+ }
+ } /* f == NULL */
+ else {
+ fall_off_chain:
+ if (chain->reent[slot].prevchain) {
+ struct ip_chain *tmp = chain;
+ f = chain->reent[slot].prevrule;
+ chain = chain->reent[slot].prevchain;
+ tmp->reent[slot].prevchain = NULL;
+ }
+ else {
+ ret = chain->policy;
+ if (!testing) {
+ chain->reent[slot].counters.pcnt++;
+ chain->reent[slot].counters.bcnt
+ += ntohs(ip->tot_len);
+ }
+ }
+ }
+ } while (ret == FW_SKIP+2);
+
+ out:
+ if (!testing) FWC_READ_UNLOCK(&ip_fw_lock);
+
+ /* Recalculate checksum if not going to reject, and TOS changed. */
+ if (ip->tos != oldtos
+ && ret != FW_REJECT && ret != FW_BLOCK
+ && !testing)
+ ip_send_check(ip);
+
+ if (ret == FW_REDIRECT && redirport) {
+ if ((*redirport = htons(f->ipfw.fw_redirpt)) == 0) {
+ /* Wildcard redirection.
+ * Note that redirport will become
+ * 0xFFFF for non-TCP/UDP packets.
+ */
+ *redirport = htons(dst_port);
+ }
+ }
+
+#ifdef DEBUG_ALLOW_ALL
+ return (testing ? ret : FW_ACCEPT);
+#else
+ return ret;
+#endif
+}
+
+/* Must have write lock & interrupts off for any of these */
+
+/* This function sets all the byte counters in a chain to zero. The
+ * input is a pointer to the chain required for zeroing */
+static int zero_fw_chain(struct ip_chain *chainptr)
+{
+ struct ip_fwkernel *i;
+
+ FWC_HAVE_LOCK(fwc_wlocks);
+ for (i = chainptr->chain; i; i = i->next)
+ memset(i->counters, 0, sizeof(struct ip_counters)*NUM_SLOTS);
+ return 0;
+}
+
+static int clear_fw_chain(struct ip_chain *chainptr)
+{
+ struct ip_fwkernel *i= chainptr->chain;
+
+ FWC_HAVE_LOCK(fwc_wlocks);
+ chainptr->chain=NULL;
+
+ while (i) {
+ struct ip_fwkernel *tmp = i->next;
+ if (i->branch)
+ i->branch->refcount--;
+ kfree(i);
+ i = tmp;
+ MOD_DEC_USE_COUNT;
+ }
+ return 0;
+}
+
+static int replace_in_chain(struct ip_chain *chainptr,
+ struct ip_fwkernel *frwl,
+ __u32 position)
+{
+ struct ip_fwkernel *f = chainptr->chain;
+
+ FWC_HAVE_LOCK(fwc_wlocks);
+
+ while (--position && f != NULL) f = f->next;
+ if (f == NULL)
+ return EINVAL;
+
+ if (f->branch) f->branch->refcount--;
+ if (frwl->branch) frwl->branch->refcount++;
+
+ frwl->next = f->next;
+ memcpy(f,frwl,sizeof(struct ip_fwkernel));
+ kfree(frwl);
+ return 0;
+}
+
+static int append_to_chain(struct ip_chain *chainptr, struct ip_fwkernel *rule)
+{
+ struct ip_fwkernel *i;
+
+ FWC_HAVE_LOCK(fwc_wlocks);
+ /* Special case if no rules already present */
+ if (chainptr->chain == NULL) {
+
+ /* If pointer writes are atomic then turning off
+ * interrupts is not necessary. */
+ chainptr->chain = rule;
+ if (rule->branch) rule->branch->refcount++;
+ goto append_successful;
+ }
+
+ /* Find the rule before the end of the chain */
+ for (i = chainptr->chain; i->next; i = i->next);
+ i->next = rule;
+ if (rule->branch) rule->branch->refcount++;
+
+append_successful:
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+/* This function inserts a rule at the position of position in the
+ * chain refenced by chainptr. If position is 1 then this rule will
+ * become the new rule one. */
+static int insert_in_chain(struct ip_chain *chainptr,
+ struct ip_fwkernel *frwl,
+ __u32 position)
+{
+ struct ip_fwkernel *f = chainptr->chain;
+
+ FWC_HAVE_LOCK(fwc_wlocks);
+ /* special case if the position is number 1 */
+ if (position == 1) {
+ frwl->next = chainptr->chain;
+ if (frwl->branch) frwl->branch->refcount++;
+ chainptr->chain = frwl;
+ goto insert_successful;
+ }
+ position--;
+ while (--position && f != NULL) f = f->next;
+ if (f == NULL)
+ return EINVAL;
+ if (frwl->branch) frwl->branch->refcount++;
+ frwl->next = f->next;
+
+ f->next = frwl;
+
+insert_successful:
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+/* This function deletes the a rule from a given rulenum and chain.
+ * With rulenum = 1 is the first rule is deleted. */
+
+static int del_num_from_chain(struct ip_chain *chainptr, __u32 rulenum)
+{
+ struct ip_fwkernel *i=chainptr->chain,*tmp;
+
+ FWC_HAVE_LOCK(fwc_wlocks);
+
+ if (!chainptr->chain)
+ return ENOENT;
+
+ /* Need a special case for the first rule */
+ if (rulenum == 1) {
+ /* store temp to allow for freeing up of memory */
+ tmp = chainptr->chain;
+ if (chainptr->chain->branch) chainptr->chain->branch->refcount--;
+ chainptr->chain = chainptr->chain->next;
+ kfree(tmp); /* free memory that is now unused */
+ } else {
+ rulenum--;
+ while (--rulenum && i->next ) i = i->next;
+ if (!i->next)
+ return ENOENT;
+ tmp = i->next;
+ if (i->next->branch)
+ i->next->branch->refcount--;
+ i->next = i->next->next;
+ kfree(tmp);
+ }
+
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+
+/* This function deletes the a rule from a given rule and chain.
+ * The rule that is deleted is the first occursance of that rule. */
+static int del_rule_from_chain(struct ip_chain *chainptr,
+ struct ip_fwkernel *frwl)
+{
+ struct ip_fwkernel *ltmp,*ftmp = chainptr->chain ;
+ int was_found;
+
+ FWC_HAVE_LOCK(fwc_wlocks);
+
+ /* Sure, we should compare marks, but since the `ipfwadm'
+ * script uses it for an unholy hack... well, life is easier
+ * this way. We also mask it out of the flags word. --PR */
+ for (ltmp=NULL, was_found=0;
+ !was_found && ftmp != NULL;
+ ltmp = ftmp,ftmp = ftmp->next) {
+ if (ftmp->ipfw.fw_src.s_addr!=frwl->ipfw.fw_src.s_addr
+ || ftmp->ipfw.fw_dst.s_addr!=frwl->ipfw.fw_dst.s_addr
+ || ftmp->ipfw.fw_smsk.s_addr!=frwl->ipfw.fw_smsk.s_addr
+ || ftmp->ipfw.fw_dmsk.s_addr!=frwl->ipfw.fw_dmsk.s_addr
+#if 0
+ || ftmp->ipfw.fw_flg!=frwl->ipfw.fw_flg
+#else
+ || ((ftmp->ipfw.fw_flg & ~IP_FW_F_MARKABS)
+ != (frwl->ipfw.fw_flg & ~IP_FW_F_MARKABS))
+#endif
+ || ftmp->ipfw.fw_invflg!=frwl->ipfw.fw_invflg
+ || ftmp->ipfw.fw_proto!=frwl->ipfw.fw_proto
+#if 0
+ || ftmp->ipfw.fw_mark!=frwl->ipfw.fw_mark
+#endif
+ || ftmp->ipfw.fw_redirpt!=frwl->ipfw.fw_redirpt
+ || ftmp->ipfw.fw_spts[0]!=frwl->ipfw.fw_spts[0]
+ || ftmp->ipfw.fw_spts[1]!=frwl->ipfw.fw_spts[1]
+ || ftmp->ipfw.fw_dpts[0]!=frwl->ipfw.fw_dpts[0]
+ || ftmp->ipfw.fw_dpts[1]!=frwl->ipfw.fw_dpts[1]
+ || ftmp->ipfw.fw_outputsize!=frwl->ipfw.fw_outputsize) {
+ duprintf("del_rule_from_chain: mismatch:"
+ "src:%u/%u dst:%u/%u smsk:%u/%u dmsk:%u/%u "
+ "flg:%hX/%hX invflg:%hX/%hX proto:%u/%u "
+ "mark:%u/%u "
+ "ports:%hu-%hu/%hu-%hu %hu-%hu/%hu-%hu "
+ "outputsize:%hu-%hu\n",
+ ftmp->ipfw.fw_src.s_addr,
+ frwl->ipfw.fw_src.s_addr,
+ ftmp->ipfw.fw_dst.s_addr,
+ frwl->ipfw.fw_dst.s_addr,
+ ftmp->ipfw.fw_smsk.s_addr,
+ frwl->ipfw.fw_smsk.s_addr,
+ ftmp->ipfw.fw_dmsk.s_addr,
+ frwl->ipfw.fw_dmsk.s_addr,
+ ftmp->ipfw.fw_flg,
+ frwl->ipfw.fw_flg,
+ ftmp->ipfw.fw_invflg,
+ frwl->ipfw.fw_invflg,
+ ftmp->ipfw.fw_proto,
+ frwl->ipfw.fw_proto,
+ ftmp->ipfw.fw_mark,
+ frwl->ipfw.fw_mark,
+ ftmp->ipfw.fw_spts[0],
+ frwl->ipfw.fw_spts[0],
+ ftmp->ipfw.fw_spts[1],
+ frwl->ipfw.fw_spts[1],
+ ftmp->ipfw.fw_dpts[0],
+ frwl->ipfw.fw_dpts[0],
+ ftmp->ipfw.fw_dpts[1],
+ frwl->ipfw.fw_dpts[1],
+ ftmp->ipfw.fw_outputsize,
+ frwl->ipfw.fw_outputsize);
+ continue;
+ }
+
+ if (strncmp(ftmp->ipfw.fw_vianame,
+ frwl->ipfw.fw_vianame,
+ IFNAMSIZ)) {
+ duprintf("del_rule_from_chain: if mismatch: %s/%s\n",
+ ftmp->ipfw.fw_vianame,
+ frwl->ipfw.fw_vianame);
+ continue;
+ }
+ if (ftmp->branch != frwl->branch) {
+ duprintf("del_rule_from_chain: branch mismatch: "
+ "%s/%s\n",
+ ftmp->branch?ftmp->branch->label:"(null)",
+ frwl->branch?frwl->branch->label:"(null)");
+ continue;
+ }
+ if (ftmp->branch == NULL
+ && ftmp->simplebranch != frwl->simplebranch) {
+ duprintf("del_rule_from_chain: simplebranch mismatch: "
+ "%i/%i\n",
+ ftmp->simplebranch, frwl->simplebranch);
+ continue;
+ }
+ was_found = 1;
+ if (ftmp->branch)
+ ftmp->branch->refcount--;
+ if (ltmp)
+ ltmp->next = ftmp->next;
+ else
+ chainptr->chain = ftmp->next;
+ kfree(ftmp);
+ MOD_DEC_USE_COUNT;
+ break;
+ }
+
+ if (was_found)
+ return 0;
+ else {
+ duprintf("del_rule_from_chain: no matching rule found\n");
+ return EINVAL;
+ }
+}
+
+/* This function takes the label of a chain and deletes the first
+ * chain with that name. No special cases required for the built in
+ * chains as they have their refcount initilised to 1 so that they are
+ * never deleted. */
+static int del_chain(ip_chainlabel label)
+{
+ struct ip_chain *tmp,*tmp2;
+
+ FWC_HAVE_LOCK(fwc_wlocks);
+ /* Corner case: return EBUSY not ENOENT for first elem ("input") */
+ if (strcmp(label, ip_fw_chains->label) == 0)
+ return EBUSY;
+
+ for (tmp = ip_fw_chains; tmp->next; tmp = tmp->next)
+ if(strcmp(tmp->next->label,label) == 0)
+ break;
+
+ tmp2 = tmp->next;
+ if (!tmp2)
+ return ENOENT;
+
+ if (tmp2->refcount)
+ return EBUSY;
+
+ if (tmp2->chain)
+ return ENOTEMPTY;
+
+ tmp->next = tmp2->next;
+ kfree(tmp2);
+
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+/* This is a function to initilise a chain. Built in rules start with
+ * refcount = 1 so that they cannot be deleted. User defined rules
+ * start with refcount = 0 so they can be deleted. */
+static struct ip_chain *ip_init_chain(ip_chainlabel name,
+ __u32 ref,
+ int policy)
+{
+ unsigned int i;
+ struct ip_chain *label
+ = kmalloc(SIZEOF_STRUCT_IP_CHAIN, GFP_KERNEL);
+ if (label == NULL)
+ panic("Can't kmalloc for firewall chains.\n");
+ strcpy(label->label,name);
+ label->next = NULL;
+ label->chain = NULL;
+ label->refcount = ref;
+ label->policy = policy;
+ for (i = 0; i < smp_num_cpus*2; i++) {
+ label->reent[i].counters.pcnt = label->reent[i].counters.bcnt
+ = 0;
+ label->reent[i].prevchain = NULL;
+ label->reent[i].prevrule = NULL;
+ }
+
+ return label;
+}
+
+/* This is a function for reating a new chain. The chains is not
+ * created if a chain of the same name already exists */
+static int create_chain(ip_chainlabel label)
+{
+ struct ip_chain *tmp;
+
+ if (!check_label(label))
+ return EINVAL;
+
+ FWC_HAVE_LOCK(fwc_wlocks);
+ for (tmp = ip_fw_chains; tmp->next; tmp = tmp->next)
+ if (strcmp(tmp->label,label) == 0)
+ return EEXIST;
+
+ if (strcmp(tmp->label,label) == 0)
+ return EEXIST;
+
+ tmp->next = ip_init_chain(label, 0, FW_SKIP); /* refcount is
+ * zero since this is a
+ * user defined chain *
+ * and therefore can be
+ * deleted */
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+/* This function simply changes the policy on one of the built in
+ * chains. checking must be done before this is call to ensure that
+ * chainptr is pointing to one of the three possible chains */
+static int change_policy(struct ip_chain *chainptr, int policy)
+{
+ FWC_HAVE_LOCK(fwc_wlocks);
+ chainptr->policy = policy;
+ return 0;
+}
+
+/* This function takes an ip_fwuser and converts it to a ip_fwkernel. It also
+ * performs some checks in the structure. */
+static struct ip_fwkernel *convert_ipfw(struct ip_fwuser *fwuser, int *errno)
+{
+ struct ip_fwkernel *fwkern;
+
+ if ( (fwuser->ipfw.fw_flg & ~IP_FW_F_MASK) != 0 ) {
+ duprintf("convert_ipfw: undefined flag bits set (flags=%x)\n",
+ fwuser->ipfw.fw_flg);
+ *errno = EINVAL;
+ return NULL;
+ }
+
+#ifdef DEBUG_IP_FIREWALL_USER
+ /* These are sanity checks that don't really matter.
+ * We can get rid of these once testing is complete.
+ */
+ if ((fwuser->ipfw.fw_flg & IP_FW_F_TCPSYN)
+ && ((fwuser->ipfw.fw_invflg & IP_FW_INV_PROTO)
+ || fwuser->ipfw.fw_proto != IPPROTO_TCP)) {
+ duprintf("convert_ipfw: TCP SYN flag set but proto != TCP!\n");
+ *errno = EINVAL;
+ return NULL;
+ }
+
+ if (strcmp(fwuser->label, IP_FW_LABEL_REDIRECT) != 0
+ && fwuser->ipfw.fw_redirpt != 0) {
+ duprintf("convert_ipfw: Target not REDIR but redirpt != 0!\n");
+ *errno = EINVAL;
+ return NULL;
+ }
+
+ if ((!(fwuser->ipfw.fw_flg & IP_FW_F_FRAG)
+ && (fwuser->ipfw.fw_invflg & IP_FW_INV_FRAG))
+ || (!(fwuser->ipfw.fw_flg & IP_FW_F_TCPSYN)
+ && (fwuser->ipfw.fw_invflg & IP_FW_INV_SYN))) {
+ duprintf("convert_ipfw: Can't have INV flag if flag unset!\n");
+ *errno = EINVAL;
+ return NULL;
+ }
+
+ if (((fwuser->ipfw.fw_invflg & IP_FW_INV_SRCPT)
+ && fwuser->ipfw.fw_spts[0] == 0
+ && fwuser->ipfw.fw_spts[1] == 0xFFFF)
+ || ((fwuser->ipfw.fw_invflg & IP_FW_INV_DSTPT)
+ && fwuser->ipfw.fw_dpts[0] == 0
+ && fwuser->ipfw.fw_dpts[1] == 0xFFFF)
+ || ((fwuser->ipfw.fw_invflg & IP_FW_INV_VIA)
+ && (fwuser->ipfw.fw_vianame)[0] == '\0')
+ || ((fwuser->ipfw.fw_invflg & IP_FW_INV_SRCIP)
+ && fwuser->ipfw.fw_smsk.s_addr == 0)
+ || ((fwuser->ipfw.fw_invflg & IP_FW_INV_DSTIP)
+ && fwuser->ipfw.fw_dmsk.s_addr == 0)) {
+ duprintf("convert_ipfw: INV flag makes rule unmatchable!\n");
+ *errno = EINVAL;
+ return NULL;
+ }
+
+ if ((fwuser->ipfw.fw_flg & IP_FW_F_FRAG)
+ && !(fwuser->ipfw.fw_invflg & IP_FW_INV_FRAG)
+ && (fwuser->ipfw.fw_spts[0] != 0
+ || fwuser->ipfw.fw_spts[1] != 0xFFFF
+ || fwuser->ipfw.fw_dpts[0] != 0
+ || fwuser->ipfw.fw_dpts[1] != 0xFFFF
+ || (fwuser->ipfw.fw_flg & IP_FW_F_TCPSYN))) {
+ duprintf("convert_ipfw: Can't test ports or SYN with frag!\n");
+ *errno = EINVAL;
+ return NULL;
+ }
+#endif
+
+ if ((fwuser->ipfw.fw_spts[0] != 0
+ || fwuser->ipfw.fw_spts[1] != 0xFFFF
+ || fwuser->ipfw.fw_dpts[0] != 0
+ || fwuser->ipfw.fw_dpts[1] != 0xFFFF)
+ && ((fwuser->ipfw.fw_invflg & IP_FW_INV_PROTO)
+ || (fwuser->ipfw.fw_proto != IPPROTO_TCP
+ && fwuser->ipfw.fw_proto != IPPROTO_UDP
+ && fwuser->ipfw.fw_proto != IPPROTO_ICMP))) {
+ duprintf("convert_ipfw: Can only test ports for TCP/UDP/ICMP!\n");
+ *errno = EINVAL;
+ return NULL;
+ }
+
+ fwkern = kmalloc(SIZEOF_STRUCT_IP_FW_KERNEL, GFP_ATOMIC);
+ if (!fwkern) {
+ duprintf("convert_ipfw: kmalloc failed!\n");
+ *errno = ENOMEM;
+ return NULL;
+ }
+ memcpy(&fwkern->ipfw,&fwuser->ipfw,sizeof(struct ip_fw));
+
+ if (!find_special(fwuser->label, &fwkern->simplebranch)) {
+ fwkern->branch = find_label(fwuser->label);
+ if (!fwkern->branch) {
+ duprintf("convert_ipfw: chain doesn't exist `%s'.\n",
+ fwuser->label);
+ kfree(fwkern);
+ *errno = ENOENT;
+ return NULL;
+ } else if (fwkern->branch == IP_FW_INPUT_CHAIN
+ || fwkern->branch == IP_FW_FORWARD_CHAIN
+ || fwkern->branch == IP_FW_OUTPUT_CHAIN) {
+ duprintf("convert_ipfw: Can't branch to builtin chain `%s'.\n",
+ fwuser->label);
+ kfree(fwkern);
+ *errno = ENOENT;
+ return NULL;
+ }
+ } else
+ fwkern->branch = NULL;
+ memset(fwkern->counters, 0, sizeof(struct ip_counters)*NUM_SLOTS);
+
+ /* Handle empty vianame by making it a wildcard */
+ if ((fwkern->ipfw.fw_vianame)[0] == '\0')
+ fwkern->ipfw.fw_flg |= IP_FW_F_WILDIF;
+
+ fwkern->next = NULL;
+ return fwkern;
+}
+
+int ip_fw_ctl(int cmd, void *m, int len)
+{
+ int ret;
+ struct ip_chain *chain;
+ unsigned long flags;
+
+ FWC_WRITE_LOCK_IRQ(&ip_fw_lock, flags);
+
+ switch (cmd) {
+ case IP_FW_FLUSH:
+ if (len != sizeof(ip_chainlabel) || !check_label(m))
+ ret = EINVAL;
+ else if ((chain = find_label(m)) == NULL)
+ ret = ENOENT;
+ else ret = clear_fw_chain(chain);
+ break;
+
+ case IP_FW_ZERO:
+ if (len != sizeof(ip_chainlabel) || !check_label(m))
+ ret = EINVAL;
+ else if ((chain = find_label(m)) == NULL)
+ ret = ENOENT;
+ else ret = zero_fw_chain(chain);
+ break;
+
+ case IP_FW_CHECK: {
+ struct ip_fwtest *new = m;
+ struct iphdr *ip;
+
+ /* Don't need write lock. */
+ FWC_WRITE_UNLOCK_IRQ(&ip_fw_lock, flags);
+
+ if (len != sizeof(struct ip_fwtest) || !check_label(m))
+ return EINVAL;
+
+ /* Need readlock to do find_label */
+ FWC_READ_LOCK(&ip_fw_lock);
+
+ if ((chain = find_label(new->fwt_label)) == NULL)
+ ret = ENOENT;
+ else {
+ ip = &(new->fwt_packet.fwp_iph);
+
+ if (ip->ihl != sizeof(struct iphdr) / sizeof(int)) {
+ duprintf("ip_fw_ctl: ip->ihl=%d, want %d\n",
+ ip->ihl,
+ sizeof(struct iphdr) / sizeof(int));
+ ret = EINVAL;
+ }
+ else {
+ ret = ip_fw_check(ip, new->fwt_packet.fwp_vianame,
+ NULL, chain,
+ NULL, SLOT_NUMBER(), 1);
+ switch (ret) {
+ case FW_ACCEPT:
+ ret = 0; break;
+ case FW_REDIRECT:
+ ret = ECONNABORTED; break;
+ case FW_MASQUERADE:
+ ret = ECONNRESET; break;
+ case FW_REJECT:
+ ret = ECONNREFUSED; break;
+ /* Hack to help diag; these only get
+ returned when testing. */
+ case FW_SKIP+1:
+ ret = ELOOP; break;
+ case FW_SKIP:
+ ret = ENFILE; break;
+ default: /* FW_BLOCK */
+ ret = ETIMEDOUT; break;
+ }
+ }
+ }
+ FWC_READ_UNLOCK(&ip_fw_lock);
+ return ret;
+ }
+
+ case IP_FW_MASQ_TIMEOUTS: {
+ ret = ip_fw_masq_timeouts(m, len);
+ }
+ break;
+
+ case IP_FW_REPLACE: {
+ struct ip_fwkernel *ip_fwkern;
+ struct ip_fwnew *new = m;
+
+ if (len != sizeof(struct ip_fwnew)
+ || !check_label(new->fwn_label))
+ ret = EINVAL;
+ else if ((chain = find_label(new->fwn_label)) == NULL)
+ ret = ENOENT;
+ else if ((ip_fwkern = convert_ipfw(&new->fwn_rule, &ret))
+ != NULL)
+ ret = replace_in_chain(chain, ip_fwkern,
+ new->fwn_rulenum);
+ }
+ break;
+
+ case IP_FW_APPEND: {
+ struct ip_fwchange *new = m;
+ struct ip_fwkernel *ip_fwkern;
+
+ if (len != sizeof(struct ip_fwchange)
+ || !check_label(new->fwc_label))
+ ret = EINVAL;
+ else if ((chain = find_label(new->fwc_label)) == NULL)
+ ret = ENOENT;
+ else if ((ip_fwkern = convert_ipfw(&new->fwc_rule, &ret))
+ != NULL)
+ ret = append_to_chain(chain, ip_fwkern);
+ }
+ break;
+
+ case IP_FW_INSERT: {
+ struct ip_fwkernel *ip_fwkern;
+ struct ip_fwnew *new = m;
+
+ if (len != sizeof(struct ip_fwnew)
+ || !check_label(new->fwn_label))
+ ret = EINVAL;
+ else if ((chain = find_label(new->fwn_label)) == NULL)
+ ret = ENOENT;
+ else if ((ip_fwkern = convert_ipfw(&new->fwn_rule, &ret))
+ != NULL)
+ ret = insert_in_chain(chain, ip_fwkern,
+ new->fwn_rulenum);
+ }
+ break;
+
+ case IP_FW_DELETE: {
+ struct ip_fwchange *new = m;
+ struct ip_fwkernel *ip_fwkern;
+
+ if (len != sizeof(struct ip_fwchange)
+ || !check_label(new->fwc_label))
+ ret = EINVAL;
+ else if ((chain = find_label(new->fwc_label)) == NULL)
+ ret = ENOENT;
+ else if ((ip_fwkern = convert_ipfw(&new->fwc_rule, &ret))
+ != NULL) {
+ ret = del_rule_from_chain(chain, ip_fwkern);
+ kfree(ip_fwkern);
+ }
+ }
+ break;
+
+ case IP_FW_DELETE_NUM: {
+ struct ip_fwdelnum *new = m;
+
+ if (len != sizeof(struct ip_fwdelnum)
+ || !check_label(new->fwd_label))
+ ret = EINVAL;
+ else if ((chain = find_label(new->fwd_label)) == NULL)
+ ret = ENOENT;
+ else ret = del_num_from_chain(chain, new->fwd_rulenum);
+ }
+ break;
+
+ case IP_FW_CREATECHAIN: {
+ if (len != sizeof(ip_chainlabel)) {
+ duprintf("create_chain: bad size %i\n", len);
+ ret = EINVAL;
+ }
+ else ret = create_chain(m);
+ }
+ break;
+
+ case IP_FW_DELETECHAIN: {
+ if (len != sizeof(ip_chainlabel)) {
+ duprintf("delete_chain: bad size %i\n", len);
+ ret = EINVAL;
+ }
+ else ret = del_chain(m);
+ }
+ break;
+
+ case IP_FW_POLICY: {
+ struct ip_fwpolicy *new = m;
+
+ if (len != sizeof(struct ip_fwpolicy)
+ || !check_label(new->fwp_label))
+ ret = EINVAL;
+ else if ((chain = find_label(new->fwp_label)) == NULL)
+ ret = ENOENT;
+ else if (chain != IP_FW_INPUT_CHAIN
+ && chain != IP_FW_FORWARD_CHAIN
+ && chain != IP_FW_OUTPUT_CHAIN) {
+ duprintf("change_policy: can't change policy on user"
+ " defined chain.\n");
+ ret = EINVAL;
+ }
+ else {
+ int pol = FW_SKIP;
+ find_special(new->fwp_policy, &pol);
+
+ switch(pol) {
+ case FW_MASQUERADE:
+ if (chain != IP_FW_FORWARD_CHAIN) {
+ ret = EINVAL;
+ break;
+ }
+ /* Fall thru... */
+ case FW_BLOCK:
+ case FW_ACCEPT:
+ case FW_REJECT:
+ ret = change_policy(chain, pol);
+ break;
+ default:
+ duprintf("change_policy: bad policy `%s'\n",
+ new->fwp_policy);
+ ret = EINVAL;
+ }
+ }
+ break;
+ }
+ default:
+ duprintf("ip_fw_ctl: unknown request %d\n",cmd);
+ ret = ENOPROTOOPT;
+ }
+
+ FWC_WRITE_UNLOCK_IRQ(&ip_fw_lock, flags);
+ return ret;
+}
+
+/* Returns bytes used - doesn't NUL terminate */
+static int dump_rule(char *buffer,
+ const char *chainlabel,
+ const struct ip_fwkernel *rule)
+{
+ int len;
+ unsigned int i;
+ __u64 packets = 0, bytes = 0;
+
+ FWC_HAVE_LOCK(fwc_wlocks);
+ for (i = 0; i < NUM_SLOTS; i++) {
+ packets += rule->counters[i].pcnt;
+ bytes += rule->counters[i].bcnt;
+ }
+
+ len=sprintf(buffer,
+ "%9s " /* Chain name */
+ "%08X/%08X->%08X/%08X " /* Source & Destination IPs */
+ "%.16s " /* Interface */
+ "%X %X " /* fw_flg and fw_invflg fields */
+ "%u " /* Protocol */
+ "%-9u %-9u %-9u %-9u " /* Packet & byte counters */
+ "%u-%u %u-%u " /* Source & Dest port ranges */
+ "A%02X X%02X " /* TOS and and xor masks */
+ "%08X " /* Redirection port */
+ "%u " /* fw_mark field */
+ "%u " /* output size */
+ "%9s\n", /* Target */
+ chainlabel,
+ ntohl(rule->ipfw.fw_src.s_addr),
+ ntohl(rule->ipfw.fw_smsk.s_addr),
+ ntohl(rule->ipfw.fw_dst.s_addr),
+ ntohl(rule->ipfw.fw_dmsk.s_addr),
+ (rule->ipfw.fw_vianame)[0] ? rule->ipfw.fw_vianame : "-",
+ rule->ipfw.fw_flg,
+ rule->ipfw.fw_invflg,
+ rule->ipfw.fw_proto,
+ (__u32)(packets >> 32), (__u32)packets,
+ (__u32)(bytes >> 32), (__u32)bytes,
+ rule->ipfw.fw_spts[0], rule->ipfw.fw_spts[1],
+ rule->ipfw.fw_dpts[0], rule->ipfw.fw_dpts[1],
+ rule->ipfw.fw_tosand, rule->ipfw.fw_tosxor,
+ rule->ipfw.fw_redirpt,
+ rule->ipfw.fw_mark,
+ rule->ipfw.fw_outputsize,
+ branchname(rule->branch,rule->simplebranch));
+
+ duprintf("dump_rule: %i bytes done.\n", len);
+ return len;
+}
+
+/* File offset is actually in records, not bytes. */
+static int ip_chain_procinfo(char *buffer, char **start,
+ off_t offset, int length)
+{
+ struct ip_chain *i;
+ struct ip_fwkernel *j = ip_fw_chains->chain;
+ unsigned long flags;
+ int len = 0;
+ int last_len = 0;
+ off_t upto = 0;
+
+ duprintf("Offset starts at %lu\n", offset);
+ duprintf("ip_fw_chains is 0x%0lX\n", (unsigned long int)ip_fw_chains);
+
+ /* Need a write lock to lock out ``readers'' which update counters. */
+ FWC_WRITE_LOCK_IRQ(&ip_fw_lock, flags);
+
+ for (i = ip_fw_chains; i; i = i->next) {
+ for (j = i->chain; j; j = j->next) {
+ if (upto == offset) break;
+ duprintf("Skipping rule in chain `%s'\n",
+ i->label);
+ upto++;
+ }
+ if (upto == offset) break;
+ }
+
+ /* Don't init j first time, or once i = NULL */
+ for (; i; (void)((i = i->next) && (j = i->chain))) {
+ duprintf("Dumping chain `%s'\n", i->label);
+ for (; j; j = j->next, upto++, last_len = len)
+ {
+ len += dump_rule(buffer+len, i->label, j);
+ if (len > length) {
+ duprintf("Dumped to %i (past %i). "
+ "Moving back to %i.\n",
+ len, length, last_len);
+ len = last_len;
+ goto outside;
+ }
+ }
+ }
+outside:
+ FWC_WRITE_UNLOCK_IRQ(&ip_fw_lock, flags);
+ buffer[len] = '\0';
+
+ duprintf("ip_chain_procinfo: Length = %i (of %i). Offset = %li.\n",
+ len, length, upto);
+ /* `start' hack - see fs/proc/generic.c line ~165 */
+ *start=(char *)((unsigned int)upto-offset);
+ return len;
+}
+
+static int ip_chain_name_procinfo(char *buffer, char **start,
+ off_t offset, int length)
+{
+ struct ip_chain *i;
+ int len = 0,last_len = 0;
+ off_t pos = 0,begin = 0;
+ unsigned long flags;
+
+ /* Need a write lock to lock out ``readers'' which update counters. */
+ FWC_WRITE_LOCK_IRQ(&ip_fw_lock, flags);
+
+ for (i = ip_fw_chains; i; i = i->next)
+ {
+ unsigned int j;
+ __u32 packetsHi = 0, packetsLo = 0, bytesHi = 0, bytesLo = 0;
+
+ for (j = 0; j < NUM_SLOTS; j++) {
+ packetsLo += i->reent[j].counters.pcnt & 0xFFFFFFFF;
+ packetsHi += ((i->reent[j].counters.pcnt >> 32)
+ & 0xFFFFFFFF);
+ bytesLo += i->reent[j].counters.bcnt & 0xFFFFFFFF;
+ bytesHi += ((i->reent[j].counters.bcnt >> 32)
+ & 0xFFFFFFFF);
+ }
+
+ /* print the label and the policy */
+ len+=sprintf(buffer+len,"%s %s %i %u %u %u %u\n",
+ i->label,branchname(NULL, i->policy),i->refcount,
+ packetsHi, packetsLo, bytesHi, bytesLo);
+ pos=begin+len;
+ if(pos<offset) {
+ len=0;
+ begin=pos;
+ }
+ else if(pos>offset+length) {
+ len = last_len;
+ break;
+ }
+
+ last_len = len;
+ }
+ FWC_WRITE_UNLOCK_IRQ(&ip_fw_lock, flags);
+
+ *start = buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ return len;
+}
+
+/*
+ * Interface to the generic firewall chains.
+ */
+int ipfw_input_check(struct firewall_ops *this, int pf,
+ struct net_device *dev, void *phdr, void *arg,
+ struct sk_buff **pskb)
+{
+ return ip_fw_check(phdr, dev->name,
+ arg, IP_FW_INPUT_CHAIN, *pskb, SLOT_NUMBER(), 0);
+}
+
+int ipfw_output_check(struct firewall_ops *this, int pf,
+ struct net_device *dev, void *phdr, void *arg,
+ struct sk_buff **pskb)
+{
+ /* Locally generated bogus packets by root. <SIGH>. */
+ if (((struct iphdr *)phdr)->ihl * 4 < sizeof(struct iphdr)
+ || (*pskb)->len < sizeof(struct iphdr))
+ return FW_ACCEPT;
+ return ip_fw_check(phdr, dev->name,
+ arg, IP_FW_OUTPUT_CHAIN, *pskb, SLOT_NUMBER(), 0);
+}
+
+int ipfw_forward_check(struct firewall_ops *this, int pf,
+ struct net_device *dev, void *phdr, void *arg,
+ struct sk_buff **pskb)
+{
+ return ip_fw_check(phdr, dev->name,
+ arg, IP_FW_FORWARD_CHAIN, *pskb, SLOT_NUMBER(), 0);
+}
+
+struct firewall_ops ipfw_ops=
+{
+ NULL,
+ ipfw_forward_check,
+ ipfw_input_check,
+ ipfw_output_check,
+ NULL,
+ NULL
+};
+
+int ipfw_init_or_cleanup(int init)
+{
+ struct proc_dir_entry *proc;
+ int ret = 0;
+ unsigned long flags;
+
+ if (!init) goto cleanup;
+
+#ifdef DEBUG_IP_FIREWALL_LOCKING
+ fwc_wlocks = fwc_rlocks = 0;
+#endif
+
+#if defined(CONFIG_NETLINK_DEV) || defined(CONFIG_NETLINK_DEV_MODULE)
+ ipfwsk = netlink_kernel_create(NETLINK_FIREWALL, NULL);
+ if (ipfwsk == NULL)
+ goto cleanup_nothing;
+#endif
+
+ ret = register_firewall(PF_INET, &ipfw_ops);
+ if (ret < 0)
+ goto cleanup_netlink;
+
+ proc = proc_net_create(IP_FW_PROC_CHAINS, S_IFREG | S_IRUSR | S_IWUSR,
+ ip_chain_procinfo);
+ if (proc) proc->owner = THIS_MODULE;
+ proc = proc_net_create(IP_FW_PROC_CHAIN_NAMES,
+ S_IFREG | S_IRUSR | S_IWUSR,
+ ip_chain_name_procinfo);
+ if (proc) proc->owner = THIS_MODULE;
+
+ IP_FW_INPUT_CHAIN = ip_init_chain(IP_FW_LABEL_INPUT, 1, FW_ACCEPT);
+ IP_FW_FORWARD_CHAIN = ip_init_chain(IP_FW_LABEL_FORWARD, 1, FW_ACCEPT);
+ IP_FW_OUTPUT_CHAIN = ip_init_chain(IP_FW_LABEL_OUTPUT, 1, FW_ACCEPT);
+
+ return ret;
+
+ cleanup:
+ unregister_firewall(PF_INET, &ipfw_ops);
+
+ FWC_WRITE_LOCK_IRQ(&ip_fw_lock, flags);
+ while (ip_fw_chains) {
+ struct ip_chain *next = ip_fw_chains->next;
+
+ clear_fw_chain(ip_fw_chains);
+ kfree(ip_fw_chains);
+ ip_fw_chains = next;
+ }
+ FWC_WRITE_UNLOCK_IRQ(&ip_fw_lock, flags);
+
+ proc_net_remove(IP_FW_PROC_CHAINS);
+ proc_net_remove(IP_FW_PROC_CHAIN_NAMES);
+
+ cleanup_netlink:
+#if defined(CONFIG_NETLINK_DEV) || defined(CONFIG_NETLINK_DEV_MODULE)
+ sock_release(ipfwsk->socket);
+
+ cleanup_nothing:
+#endif
+ return ret;
+}
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipfwadm_core.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipfwadm_core.c
new file mode 100644
index 0000000..a279df3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipfwadm_core.c
@@ -0,0 +1,1368 @@
+/* Minor modifications to fit on compatibility framework:
+ Rusty.Russell@rustcorp.com.au
+*/
+
+#include <linux/config.h>
+#define CONFIG_IP_FIREWALL
+#define CONFIG_IP_FIREWALL_VERBOSE
+#define CONFIG_IP_MASQUERADE
+#define CONFIG_IP_ACCT
+#define CONFIG_IP_TRANSPARENT_PROXY
+#if defined(CONFIG_NETLINK_DEV) || defined(CONFIG_NETLINK_DEV_MODULE)
+#define CONFIG_IP_FIREWALL_NETLINK
+#endif
+
+/*
+ * IP firewalling code. This is taken from 4.4BSD. Please note the
+ * copyright message below. As per the GPL it must be maintained
+ * and the licenses thus do not conflict. While this port is subject
+ * to the GPL I also place my modifications under the original
+ * license in recognition of the original copyright.
+ * -- Alan Cox.
+ *
+ * $Id: ipfwadm_core.c,v 1.9.2.2 2002/01/24 15:50:42 davem Exp $
+ *
+ * Ported from BSD to Linux,
+ * Alan Cox 22/Nov/1994.
+ * Zeroing /proc and other additions
+ * Jos Vos 4/Feb/1995.
+ * Merged and included the FreeBSD-Current changes at Ugen's request
+ * (but hey it's a lot cleaner now). Ugen would prefer in some ways
+ * we waited for his final product but since Linux 1.2.0 is about to
+ * appear it's not practical - Read: It works, it's not clean but please
+ * don't consider it to be his standard of finished work.
+ * Alan Cox 12/Feb/1995
+ * Porting bidirectional entries from BSD, fixing accounting issues,
+ * adding struct ip_fwpkt for checking packets with interface address
+ * Jos Vos 5/Mar/1995.
+ * Established connections (ACK check), ACK check on bidirectional rules,
+ * ICMP type check.
+ * Wilfred Mollenvanger 7/7/1995.
+ * TCP attack protection.
+ * Alan Cox 25/8/95, based on information from bugtraq.
+ * ICMP type printk, IP_FW_F_APPEND
+ * Bernd Eckenfels 1996-01-31
+ * Split blocking chain into input and output chains, add new "insert" and
+ * "append" commands to replace semi-intelligent "add" command, let "delete".
+ * only delete the first matching entry, use 0xFFFF (0xFF) as ports (ICMP
+ * types) when counting packets being 2nd and further fragments.
+ * Jos Vos <jos@xos.nl> 8/2/1996.
+ * Add support for matching on device names.
+ * Jos Vos <jos@xos.nl> 15/2/1996.
+ * Transparent proxying support.
+ * Willy Konynenberg <willy@xos.nl> 10/5/96.
+ * Make separate accounting on incoming and outgoing packets possible.
+ * Jos Vos <jos@xos.nl> 18/5/1996.
+ * Added trap out of bad frames.
+ * Alan Cox <alan@cymru.net> 17/11/1996
+ *
+ *
+ * Masquerading functionality
+ *
+ * Copyright (c) 1994 Pauline Middelink
+ *
+ * The pieces which added masquerading functionality are totally
+ * my responsibility and have nothing to with the original authors
+ * copyright or doing.
+ *
+ * Parts distributed under GPL.
+ *
+ * Fixes:
+ * Pauline Middelink : Added masquerading.
+ * Alan Cox : Fixed an error in the merge.
+ * Thomas Quinot : Fixed port spoofing.
+ * Alan Cox : Cleaned up retransmits in spoofing.
+ * Alan Cox : Cleaned up length setting.
+ * Wouter Gadeyne : Fixed masquerading support of ftp PORT commands
+ *
+ * Juan Jose Ciarlante : Masquerading code moved to ip_masq.c
+ * Andi Kleen : Print frag_offsets and the ip flags properly.
+ *
+ * All the real work was done by .....
+ *
+ */
+
+
+/*
+ * Copyright (c) 1993 Daniel Boulet
+ * Copyright (c) 1994 Ugen J.S.Antsilevich
+ *
+ * Redistribution and use in source forms, with and without modification,
+ * are permitted provided that this entire comment appears intact.
+ *
+ * Redistribution in binary form may occur without any restrictions.
+ * Obviously, it would be nice if you gave credit where credit is due
+ * but requiring it would be too onerous.
+ *
+ * This software is provided ``AS IS'' without any warranties of any kind.
+ */
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/icmp.h>
+#include <linux/udp.h>
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <net/route.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+#include <net/sock.h>
+#include <net/icmp.h>
+#include <linux/netlink.h>
+#include <linux/init.h>
+#include <linux/netfilter_ipv4/ipfwadm_core.h>
+#include <linux/netfilter_ipv4/compat_firewall.h>
+
+#include <net/checksum.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/version.h>
+
+/*
+ * Implement IP packet firewall
+ */
+
+#ifdef DEBUG_IP_FIREWALL
+#define dprintf1(a) printk(a)
+#define dprintf2(a1,a2) printk(a1,a2)
+#define dprintf3(a1,a2,a3) printk(a1,a2,a3)
+#define dprintf4(a1,a2,a3,a4) printk(a1,a2,a3,a4)
+#else
+#define dprintf1(a)
+#define dprintf2(a1,a2)
+#define dprintf3(a1,a2,a3)
+#define dprintf4(a1,a2,a3,a4)
+#endif
+
+#define print_ip(a) printk("%u.%u.%u.%u", NIPQUAD(a));
+
+#ifdef DEBUG_IP_FIREWALL
+#define dprint_ip(a) print_ip(a)
+#else
+#define dprint_ip(a)
+#endif
+
+#if defined(CONFIG_IP_ACCT) || defined(CONFIG_IP_FIREWALL)
+
+struct ip_fw *ip_fw_fwd_chain;
+struct ip_fw *ip_fw_in_chain;
+struct ip_fw *ip_fw_out_chain;
+struct ip_fw *ip_acct_chain;
+struct ip_fw *ip_masq_chain;
+
+static struct ip_fw **chains[] =
+ {&ip_fw_fwd_chain, &ip_fw_in_chain, &ip_fw_out_chain, &ip_acct_chain,
+ &ip_masq_chain
+ };
+#endif /* CONFIG_IP_ACCT || CONFIG_IP_FIREWALL */
+
+#ifdef CONFIG_IP_FIREWALL
+int ip_fw_fwd_policy=IP_FW_F_ACCEPT;
+int ip_fw_in_policy=IP_FW_F_ACCEPT;
+int ip_fw_out_policy=IP_FW_F_ACCEPT;
+
+static int *policies[] =
+ {&ip_fw_fwd_policy, &ip_fw_in_policy, &ip_fw_out_policy};
+
+#endif
+
+#ifdef CONFIG_IP_FIREWALL_NETLINK
+struct sock *ipfwsk;
+#endif
+
+/*
+ * Returns 1 if the port is matched by the vector, 0 otherwise
+ */
+
+extern inline int port_match(unsigned short *portptr,int nports,unsigned short port,int range_flag)
+{
+ if (!nports)
+ return 1;
+ if ( range_flag )
+ {
+ if ( portptr[0] <= port && port <= portptr[1] )
+ {
+ return( 1 );
+ }
+ nports -= 2;
+ portptr += 2;
+ }
+ while ( nports-- > 0 )
+ {
+ if ( *portptr++ == port )
+ {
+ return( 1 );
+ }
+ }
+ return(0);
+}
+
+#if defined(CONFIG_IP_ACCT) || defined(CONFIG_IP_FIREWALL)
+
+#ifdef CONFIG_IP_FIREWALL_VERBOSE
+
+/*
+ * VERY ugly piece of code which actually makes kernel printf for
+ * matching packets.
+ */
+
+static char *chain_name(struct ip_fw *chain, int mode)
+{
+ switch (mode) {
+ case IP_FW_MODE_ACCT_IN: return "acct in";
+ case IP_FW_MODE_ACCT_OUT: return "acct out";
+ default:
+ if (chain == ip_fw_fwd_chain)
+ return "fw-fwd";
+ else if (chain == ip_fw_in_chain)
+ return "fw-in";
+ else
+ return "fw-out";
+ }
+}
+
+static char *rule_name(struct ip_fw *f, int mode, char *buf)
+{
+ if (mode == IP_FW_MODE_ACCT_IN || mode == IP_FW_MODE_ACCT_OUT)
+ return "";
+
+ if(f->fw_flg&IP_FW_F_ACCEPT) {
+ if(f->fw_flg&IP_FW_F_REDIR) {
+ sprintf(buf, "acc/r%d ", f->fw_pts[f->fw_nsp+f->fw_ndp]);
+ return buf;
+ } else if(f->fw_flg&IP_FW_F_MASQ)
+ return "acc/masq ";
+ else
+ return "acc ";
+ } else if(f->fw_flg&IP_FW_F_ICMPRPL) {
+ return "rej ";
+ } else {
+ return "deny ";
+ }
+}
+
+static void print_packet(struct iphdr *ip,
+ u16 src_port, u16 dst_port, u16 icmp_type,
+ char *chain, char *rule, char *devname)
+{
+ __u32 *opt = (__u32 *) (ip + 1);
+ int opti;
+ __u16 foff = ntohs(ip->frag_off);
+
+ printk(KERN_INFO "IP %s %s%s", chain, rule, devname);
+
+ switch(ip->protocol)
+ {
+ case IPPROTO_TCP:
+ printk(" TCP ");
+ break;
+ case IPPROTO_UDP:
+ printk(" UDP ");
+ break;
+ case IPPROTO_ICMP:
+ printk(" ICMP/%d ", icmp_type);
+ break;
+ default:
+ printk(" PROTO=%d ", ip->protocol);
+ break;
+ }
+ print_ip(ip->saddr);
+ if(ip->protocol == IPPROTO_TCP || ip->protocol == IPPROTO_UDP)
+ printk(":%hu", src_port);
+ printk(" ");
+ print_ip(ip->daddr);
+ if(ip->protocol == IPPROTO_TCP || ip->protocol == IPPROTO_UDP)
+ printk(":%hu", dst_port);
+ printk(" L=%hu S=0x%2.2hX I=%hu FO=0x%4.4hX T=%hu",
+ ntohs(ip->tot_len), ip->tos, ntohs(ip->id),
+ foff & IP_OFFSET, ip->ttl);
+ if (foff & IP_DF) printk(" DF=1");
+ if (foff & IP_MF) printk(" MF=1");
+ for (opti = 0; opti < (ip->ihl - sizeof(struct iphdr) / 4); opti++)
+ printk(" O=0x%8.8X", *opt++);
+ printk("\n");
+}
+#endif
+
+/*
+ * Returns one of the generic firewall policies, like FW_ACCEPT.
+ * Also does accounting so you can feed it the accounting chain.
+ *
+ * The modes is either IP_FW_MODE_FW (normal firewall mode),
+ * IP_FW_MODE_ACCT_IN or IP_FW_MODE_ACCT_OUT (accounting mode,
+ * steps through the entire chain and handles fragments
+ * differently), or IP_FW_MODE_CHK (handles user-level check,
+ * counters are not updated).
+ */
+
+
+int ip_fw_chk(struct iphdr *ip, struct net_device *rif, __u16 *redirport,
+ struct ip_fw *chain, int policy, int mode)
+{
+ struct ip_fw *f;
+ struct tcphdr *tcp=(struct tcphdr *)((__u32 *)ip+ip->ihl);
+ struct udphdr *udp=(struct udphdr *)((__u32 *)ip+ip->ihl);
+ struct icmphdr *icmp=(struct icmphdr *)((__u32 *)ip+ip->ihl);
+ __u32 src, dst;
+ __u16 src_port=0xFFFF, dst_port=0xFFFF, icmp_type=0xFF;
+ unsigned short f_prt=0, prt;
+ char notcpsyn=0, notcpack=0, match;
+ unsigned short offset;
+ int answer;
+ unsigned char tosand, tosxor;
+
+ /*
+ * If the chain is empty follow policy. The BSD one
+ * accepts anything giving you a time window while
+ * flushing and rebuilding the tables.
+ */
+
+ src = ip->saddr;
+ dst = ip->daddr;
+
+ /*
+ * This way we handle fragmented packets.
+ * we ignore all fragments but the first one
+ * so the whole packet can't be reassembled.
+ * This way we relay on the full info which
+ * stored only in first packet.
+ *
+ * Note that this theoretically allows partial packet
+ * spoofing. Not very dangerous but paranoid people may
+ * wish to play with this. It also allows the so called
+ * "fragment bomb" denial of service attack on some types
+ * of system.
+ */
+
+ offset = ntohs(ip->frag_off) & IP_OFFSET;
+
+ /*
+ * Don't allow a fragment of TCP 8 bytes in. Nobody
+ * normal causes this. Its a cracker trying to break
+ * in by doing a flag overwrite to pass the direction
+ * checks.
+ */
+
+ if (offset == 1 && ip->protocol == IPPROTO_TCP)
+ return FW_BLOCK;
+
+ if (offset!=0 && !(mode & (IP_FW_MODE_ACCT_IN|IP_FW_MODE_ACCT_OUT)) &&
+ (ip->protocol == IPPROTO_TCP || ip->protocol == IPPROTO_UDP ||
+ ip->protocol == IPPROTO_ICMP))
+ return FW_ACCEPT;
+
+ /*
+ * Header fragment for TCP is too small to check the bits.
+ */
+
+ if(ip->protocol==IPPROTO_TCP && (ip->ihl<<2)+16 > ntohs(ip->tot_len))
+ return FW_BLOCK;
+
+ /*
+ * Too short.
+ *
+ * But only too short for a packet with ports...
+ */
+
+ else if((ntohs(ip->tot_len)<8+(ip->ihl<<2))&&(ip->protocol==IPPROTO_TCP || ip->protocol==IPPROTO_UDP))
+ return FW_BLOCK;
+
+ src = ip->saddr;
+ dst = ip->daddr;
+
+ /*
+ * If we got interface from which packet came
+ * we can use the address directly. This is unlike
+ * 4.4BSD derived systems that have an address chain
+ * per device. We have a device per address with dummy
+ * devices instead.
+ */
+
+ dprintf1("Packet ");
+ switch(ip->protocol)
+ {
+ case IPPROTO_TCP:
+ dprintf1("TCP ");
+ /* ports stay 0xFFFF if it is not the first fragment */
+ if (!offset) {
+ src_port=ntohs(tcp->source);
+ dst_port=ntohs(tcp->dest);
+ if(!tcp->ack && !tcp->rst)
+ /* We do NOT have ACK, value TRUE */
+ notcpack=1;
+ if(!tcp->syn || !notcpack)
+ /* We do NOT have SYN, value TRUE */
+ notcpsyn=1;
+ }
+ prt=IP_FW_F_TCP;
+ break;
+ case IPPROTO_UDP:
+ dprintf1("UDP ");
+ /* ports stay 0xFFFF if it is not the first fragment */
+ if (!offset) {
+ src_port=ntohs(udp->source);
+ dst_port=ntohs(udp->dest);
+ }
+ prt=IP_FW_F_UDP;
+ break;
+ case IPPROTO_ICMP:
+ /* icmp_type stays 255 if it is not the first fragment */
+ if (!offset)
+ icmp_type=(__u16)(icmp->type);
+ dprintf2("ICMP:%d ",icmp_type);
+ prt=IP_FW_F_ICMP;
+ break;
+ default:
+ dprintf2("p=%d ",ip->protocol);
+ prt=IP_FW_F_ALL;
+ break;
+ }
+#ifdef DEBUG_IP_FIREWALL
+ dprint_ip(ip->saddr);
+
+ if (ip->protocol==IPPROTO_TCP || ip->protocol==IPPROTO_UDP)
+ /* This will print 65535 when it is not the first fragment! */
+ dprintf2(":%d ", src_port);
+ dprint_ip(ip->daddr);
+ if (ip->protocol==IPPROTO_TCP || ip->protocol==IPPROTO_UDP)
+ /* This will print 65535 when it is not the first fragment! */
+ dprintf2(":%d ",dst_port);
+ dprintf1("\n");
+#endif
+
+ for (f=chain;f;f=f->fw_next)
+ {
+ /*
+ * This is a bit simpler as we don't have to walk
+ * an interface chain as you do in BSD - same logic
+ * however.
+ */
+
+ /*
+ * Match can become 0x01 (a "normal" match was found),
+ * 0x02 (a reverse match was found), and 0x03 (the
+ * IP addresses match in both directions).
+ * Now we know in which direction(s) we should look
+ * for a match for the TCP/UDP ports. Both directions
+ * might match (e.g., when both addresses are on the
+ * same network for which an address/mask is given), but
+ * the ports might only match in one direction.
+ * This was obviously wrong in the original BSD code.
+ */
+ match = 0x00;
+
+ if ((src&f->fw_smsk.s_addr)==f->fw_src.s_addr
+ && (dst&f->fw_dmsk.s_addr)==f->fw_dst.s_addr)
+ /* normal direction */
+ match |= 0x01;
+
+ if ((f->fw_flg & IP_FW_F_BIDIR) &&
+ (dst&f->fw_smsk.s_addr)==f->fw_src.s_addr
+ && (src&f->fw_dmsk.s_addr)==f->fw_dst.s_addr)
+ /* reverse direction */
+ match |= 0x02;
+
+ if (!match)
+ continue;
+
+ /*
+ * Look for a VIA device match
+ */
+ if(f->fw_viadev)
+ {
+ if(rif!=f->fw_viadev)
+ continue; /* Mismatch */
+ }
+
+ /* This looks stupid, because we scan almost static
+ list, searching for static key. However, this way seems
+ to be only reasonable way of handling fw_via rules
+ (btw bsd makes the same thing).
+
+ It will not affect performance if you will follow
+ the following simple rules:
+
+ - if inteface is aliased, ALWAYS specify fw_viadev,
+ so that previous check will guarantee, that we will
+ not waste time when packet arrive on another interface.
+
+ - avoid using fw_via.s_addr if fw_via.s_addr is owned
+ by an aliased interface.
+
+ --ANK
+ */
+ if (f->fw_via.s_addr && rif) {
+ struct in_ifaddr *ifa;
+
+ if (rif->ip_ptr == NULL)
+ continue; /* Mismatch */
+
+ for (ifa = ((struct in_device*)(rif->ip_ptr))->ifa_list;
+ ifa; ifa = ifa->ifa_next) {
+ if (ifa->ifa_local == f->fw_via.s_addr)
+ goto ifa_ok;
+ }
+ continue; /* Mismatch */
+
+ ifa_ok:;
+ }
+
+ /*
+ * Ok the chain addresses match.
+ */
+
+#ifdef CONFIG_IP_ACCT
+ /*
+ * See if we're in accounting mode and only want to
+ * count incoming or outgoing packets.
+ */
+
+ if (mode & (IP_FW_MODE_ACCT_IN|IP_FW_MODE_ACCT_OUT) &&
+ ((mode == IP_FW_MODE_ACCT_IN && f->fw_flg&IP_FW_F_ACCTOUT) ||
+ (mode == IP_FW_MODE_ACCT_OUT && f->fw_flg&IP_FW_F_ACCTIN)))
+ continue;
+
+#endif
+ /*
+ * For all non-TCP packets and/or non-first fragments,
+ * notcpsyn and notcpack will always be FALSE,
+ * so the IP_FW_F_TCPSYN and IP_FW_F_TCPACK flags
+ * are actually ignored for these packets.
+ */
+
+ if((f->fw_flg&IP_FW_F_TCPSYN) && notcpsyn)
+ continue;
+
+ if((f->fw_flg&IP_FW_F_TCPACK) && notcpack)
+ continue;
+
+ f_prt=f->fw_flg&IP_FW_F_KIND;
+ if (f_prt!=IP_FW_F_ALL)
+ {
+ /*
+ * Specific firewall - packet's protocol
+ * must match firewall's.
+ */
+
+ if(prt!=f_prt)
+ continue;
+
+ if((prt==IP_FW_F_ICMP &&
+ ! port_match(&f->fw_pts[0], f->fw_nsp,
+ icmp_type,f->fw_flg&IP_FW_F_SRNG)) ||
+ !(prt==IP_FW_F_ICMP || ((match & 0x01) &&
+ port_match(&f->fw_pts[0], f->fw_nsp, src_port,
+ f->fw_flg&IP_FW_F_SRNG) &&
+ port_match(&f->fw_pts[f->fw_nsp], f->fw_ndp, dst_port,
+ f->fw_flg&IP_FW_F_DRNG)) || ((match & 0x02) &&
+ port_match(&f->fw_pts[0], f->fw_nsp, dst_port,
+ f->fw_flg&IP_FW_F_SRNG) &&
+ port_match(&f->fw_pts[f->fw_nsp], f->fw_ndp, src_port,
+ f->fw_flg&IP_FW_F_DRNG))))
+ {
+ continue;
+ }
+ }
+
+#ifdef CONFIG_IP_FIREWALL_VERBOSE
+ if (f->fw_flg & IP_FW_F_PRN)
+ {
+ char buf[16];
+
+ print_packet(ip, src_port, dst_port, icmp_type,
+ chain_name(chain, mode),
+ rule_name(f, mode, buf),
+ rif ? rif->name : "-");
+ }
+#endif
+ if (mode != IP_FW_MODE_CHK) {
+ f->fw_bcnt+=ntohs(ip->tot_len);
+ f->fw_pcnt++;
+ }
+ if (!(mode & (IP_FW_MODE_ACCT_IN|IP_FW_MODE_ACCT_OUT)))
+ break;
+ } /* Loop */
+
+ if (!(mode & (IP_FW_MODE_ACCT_IN|IP_FW_MODE_ACCT_OUT))) {
+
+ /*
+ * We rely on policy defined in the rejecting entry or, if no match
+ * was found, we rely on the general policy variable for this type
+ * of firewall.
+ */
+
+ if (f!=NULL) {
+ policy=f->fw_flg;
+ tosand=f->fw_tosand;
+ tosxor=f->fw_tosxor;
+ } else {
+ tosand=0xFF;
+ tosxor=0x00;
+ }
+
+ if (policy&IP_FW_F_ACCEPT) {
+ /* Adjust priority and recompute checksum */
+ __u8 old_tos = ip->tos;
+ ip->tos = (old_tos & tosand) ^ tosxor;
+ if (ip->tos != old_tos)
+ ip_send_check(ip);
+#ifdef CONFIG_IP_TRANSPARENT_PROXY
+ if (policy&IP_FW_F_REDIR) {
+ if (redirport)
+ if ((*redirport = htons(f->fw_pts[f->fw_nsp+f->fw_ndp])) == 0) {
+ /* Wildcard redirection.
+ * Note that redirport will become
+ * 0xFFFF for non-TCP/UDP packets.
+ */
+ *redirport = htons(dst_port);
+ }
+ answer = FW_REDIRECT;
+ } else
+#endif
+#ifdef CONFIG_IP_MASQUERADE
+ if (policy&IP_FW_F_MASQ)
+ answer = FW_MASQUERADE;
+ else
+#endif
+ answer = FW_ACCEPT;
+
+ } else if(policy&IP_FW_F_ICMPRPL)
+ answer = FW_REJECT;
+ else
+ answer = FW_BLOCK;
+
+#ifdef CONFIG_IP_FIREWALL_NETLINK
+ if((policy&IP_FW_F_PRN) && (answer == FW_REJECT || answer == FW_BLOCK))
+ {
+ struct sk_buff *skb=alloc_skb(128, GFP_ATOMIC);
+ if(skb)
+ {
+ int len = min_t(unsigned int,
+ 128, ntohs(ip->tot_len));
+
+ skb_put(skb,len);
+ memcpy(skb->data,ip,len);
+ if(netlink_post(NETLINK_FIREWALL, skb))
+ kfree_skb(skb);
+ }
+ }
+#endif
+ return answer;
+ } else
+ /* we're doing accounting, always ok */
+ return 0;
+}
+
+
+static void zero_fw_chain(struct ip_fw *chainptr)
+{
+ struct ip_fw *ctmp=chainptr;
+ while(ctmp)
+ {
+ ctmp->fw_pcnt=0L;
+ ctmp->fw_bcnt=0L;
+ ctmp=ctmp->fw_next;
+ }
+}
+
+static void free_fw_chain(struct ip_fw *volatile* chainptr)
+{
+ unsigned long flags;
+ save_flags(flags);
+ cli();
+ while ( *chainptr != NULL )
+ {
+ struct ip_fw *ftmp;
+ ftmp = *chainptr;
+ *chainptr = ftmp->fw_next;
+ kfree(ftmp);
+ MOD_DEC_USE_COUNT;
+ }
+ restore_flags(flags);
+}
+
+/* Volatiles to keep some of the compiler versions amused */
+
+static int insert_in_chain(struct ip_fw *volatile* chainptr, struct ip_fw *frwl,int len)
+{
+ struct ip_fw *ftmp;
+ unsigned long flags;
+
+ save_flags(flags);
+
+ ftmp = kmalloc( sizeof(struct ip_fw), GFP_ATOMIC );
+ if ( ftmp == NULL )
+ {
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: malloc said no\n");
+#endif
+ return( ENOMEM );
+ }
+
+ memcpy(ftmp, frwl, len);
+ /*
+ * Allow the more recent "minimise cost" flag to be
+ * set. [Rob van Nieuwkerk]
+ */
+ ftmp->fw_tosand |= 0x01;
+ ftmp->fw_tosxor &= 0xFE;
+ ftmp->fw_pcnt=0L;
+ ftmp->fw_bcnt=0L;
+
+ cli();
+
+ if ((ftmp->fw_vianame)[0]) {
+ if (!(ftmp->fw_viadev = dev_get_by_name(ftmp->fw_vianame)))
+ ftmp->fw_viadev = (struct net_device *) -1;
+ } else
+ ftmp->fw_viadev = NULL;
+
+ ftmp->fw_next = *chainptr;
+ *chainptr=ftmp;
+ restore_flags(flags);
+ MOD_INC_USE_COUNT;
+ return(0);
+}
+
+static int append_to_chain(struct ip_fw *volatile* chainptr, struct ip_fw *frwl,int len)
+{
+ struct ip_fw *ftmp;
+ struct ip_fw *chtmp=NULL;
+ struct ip_fw *volatile chtmp_prev=NULL;
+ unsigned long flags;
+
+ save_flags(flags);
+
+ ftmp = kmalloc( sizeof(struct ip_fw), GFP_ATOMIC );
+ if ( ftmp == NULL )
+ {
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: malloc said no\n");
+#endif
+ return( ENOMEM );
+ }
+
+ memcpy(ftmp, frwl, len);
+ /*
+ * Allow the more recent "minimise cost" flag to be
+ * set. [Rob van Nieuwkerk]
+ */
+ ftmp->fw_tosand |= 0x01;
+ ftmp->fw_tosxor &= 0xFE;
+ ftmp->fw_pcnt=0L;
+ ftmp->fw_bcnt=0L;
+
+ ftmp->fw_next = NULL;
+
+ cli();
+
+ if ((ftmp->fw_vianame)[0]) {
+ if (!(ftmp->fw_viadev = dev_get_by_name(ftmp->fw_vianame)))
+ ftmp->fw_viadev = (struct net_device *) -1;
+ } else
+ ftmp->fw_viadev = NULL;
+
+ chtmp_prev=NULL;
+ for (chtmp=*chainptr;chtmp!=NULL;chtmp=chtmp->fw_next)
+ chtmp_prev=chtmp;
+
+ if (chtmp_prev)
+ chtmp_prev->fw_next=ftmp;
+ else
+ *chainptr=ftmp;
+ restore_flags(flags);
+ MOD_INC_USE_COUNT;
+ return(0);
+}
+
+static int del_from_chain(struct ip_fw *volatile*chainptr, struct ip_fw *frwl)
+{
+ struct ip_fw *ftmp,*ltmp;
+ unsigned short tport1,tport2,tmpnum;
+ char matches,was_found;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ ftmp=*chainptr;
+
+ if ( ftmp == NULL )
+ {
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: chain is empty\n");
+#endif
+ restore_flags(flags);
+ return( EINVAL );
+ }
+
+ ltmp=NULL;
+ was_found=0;
+
+ while( !was_found && ftmp != NULL )
+ {
+ matches=1;
+ if (ftmp->fw_src.s_addr!=frwl->fw_src.s_addr
+ || ftmp->fw_dst.s_addr!=frwl->fw_dst.s_addr
+ || ftmp->fw_smsk.s_addr!=frwl->fw_smsk.s_addr
+ || ftmp->fw_dmsk.s_addr!=frwl->fw_dmsk.s_addr
+ || ftmp->fw_via.s_addr!=frwl->fw_via.s_addr
+ || ftmp->fw_flg!=frwl->fw_flg)
+ matches=0;
+
+ tport1=ftmp->fw_nsp+ftmp->fw_ndp;
+ tport2=frwl->fw_nsp+frwl->fw_ndp;
+ if (tport1!=tport2)
+ matches=0;
+ else if (tport1!=0)
+ {
+ for (tmpnum=0;tmpnum < tport1 && tmpnum < IP_FW_MAX_PORTS;tmpnum++)
+ if (ftmp->fw_pts[tmpnum]!=frwl->fw_pts[tmpnum])
+ matches=0;
+ }
+ if (strncmp(ftmp->fw_vianame, frwl->fw_vianame, IFNAMSIZ))
+ matches=0;
+ if(matches)
+ {
+ was_found=1;
+ if (ltmp)
+ {
+ ltmp->fw_next=ftmp->fw_next;
+ kfree(ftmp);
+ ftmp=ltmp->fw_next;
+ }
+ else
+ {
+ *chainptr=ftmp->fw_next;
+ kfree(ftmp);
+ ftmp=*chainptr;
+ }
+ }
+ else
+ {
+ ltmp = ftmp;
+ ftmp = ftmp->fw_next;
+ }
+ }
+ restore_flags(flags);
+ if (was_found) {
+ MOD_DEC_USE_COUNT;
+ return 0;
+ } else
+ return(EINVAL);
+}
+
+#endif /* CONFIG_IP_ACCT || CONFIG_IP_FIREWALL */
+
+struct ip_fw *check_ipfw_struct(struct ip_fw *frwl, int len)
+{
+
+ if ( len != sizeof(struct ip_fw) )
+ {
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: len=%d, want %d\n",len, sizeof(struct ip_fw));
+#endif
+ return(NULL);
+ }
+
+ if ( (frwl->fw_flg & ~IP_FW_F_MASK) != 0 )
+ {
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: undefined flag bits set (flags=%x)\n",
+ frwl->fw_flg);
+#endif
+ return(NULL);
+ }
+
+#ifndef CONFIG_IP_TRANSPARENT_PROXY
+ if (frwl->fw_flg & IP_FW_F_REDIR) {
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: unsupported flag IP_FW_F_REDIR\n");
+#endif
+ return(NULL);
+ }
+#endif
+
+#ifndef CONFIG_IP_MASQUERADE
+ if (frwl->fw_flg & IP_FW_F_MASQ) {
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: unsupported flag IP_FW_F_MASQ\n");
+#endif
+ return(NULL);
+ }
+#endif
+
+ if ( (frwl->fw_flg & IP_FW_F_SRNG) && frwl->fw_nsp < 2 )
+ {
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: src range set but fw_nsp=%d\n",
+ frwl->fw_nsp);
+#endif
+ return(NULL);
+ }
+
+ if ( (frwl->fw_flg & IP_FW_F_DRNG) && frwl->fw_ndp < 2 )
+ {
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: dst range set but fw_ndp=%d\n",
+ frwl->fw_ndp);
+#endif
+ return(NULL);
+ }
+
+ if ( frwl->fw_nsp + frwl->fw_ndp > (frwl->fw_flg & IP_FW_F_REDIR ? IP_FW_MAX_PORTS - 1 : IP_FW_MAX_PORTS) )
+ {
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: too many ports (%d+%d)\n",
+ frwl->fw_nsp,frwl->fw_ndp);
+#endif
+ return(NULL);
+ }
+
+ return frwl;
+}
+
+
+
+
+#ifdef CONFIG_IP_ACCT
+
+int ip_acct_ctl(int stage, void *m, int len)
+{
+ if ( stage == IP_ACCT_FLUSH )
+ {
+ free_fw_chain(&ip_acct_chain);
+ return(0);
+ }
+ if ( stage == IP_ACCT_ZERO )
+ {
+ zero_fw_chain(ip_acct_chain);
+ return(0);
+ }
+ if ( stage == IP_ACCT_INSERT || stage == IP_ACCT_APPEND ||
+ stage == IP_ACCT_DELETE )
+ {
+ struct ip_fw *frwl;
+
+ if (!(frwl=check_ipfw_struct(m,len)))
+ return (EINVAL);
+
+ switch (stage)
+ {
+ case IP_ACCT_INSERT:
+ return( insert_in_chain(&ip_acct_chain,frwl,len));
+ case IP_ACCT_APPEND:
+ return( append_to_chain(&ip_acct_chain,frwl,len));
+ case IP_ACCT_DELETE:
+ return( del_from_chain(&ip_acct_chain,frwl));
+ default:
+ /*
+ * Should be panic but... (Why ??? - AC)
+ */
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_acct_ctl: unknown request %d\n",stage);
+#endif
+ return(EINVAL);
+ }
+ }
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_acct_ctl: unknown request %d\n",stage);
+#endif
+ return(EINVAL);
+}
+#endif
+
+#ifdef CONFIG_IP_FIREWALL
+int ip_fw_ctl(int stage, void *m, int len)
+{
+ int cmd, fwtype;
+
+ cmd = stage & IP_FW_COMMAND;
+ fwtype = (stage & IP_FW_TYPE) >> IP_FW_SHIFT;
+
+ if ( cmd == IP_FW_FLUSH )
+ {
+ free_fw_chain(chains[fwtype]);
+ return(0);
+ }
+
+ if ( cmd == IP_FW_ZERO )
+ {
+ zero_fw_chain(*chains[fwtype]);
+ return(0);
+ }
+
+ if ( cmd == IP_FW_POLICY )
+ {
+ int *tmp_policy_ptr;
+ tmp_policy_ptr=(int *)m;
+ *policies[fwtype] = *tmp_policy_ptr;
+ return 0;
+ }
+
+ if ( cmd == IP_FW_CHECK )
+ {
+ struct net_device *viadev;
+ struct ip_fwpkt *ipfwp;
+ struct iphdr *ip;
+
+ if ( len != sizeof(struct ip_fwpkt) )
+ {
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: length=%d, expected %d\n",
+ len, sizeof(struct ip_fwpkt));
+#endif
+ return( EINVAL );
+ }
+
+ ipfwp = (struct ip_fwpkt *)m;
+ ip = &(ipfwp->fwp_iph);
+
+ if ( !(viadev = dev_get_by_name(ipfwp->fwp_vianame)) ) {
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: invalid device \"%s\"\n", ipfwp->fwp_vianame);
+#endif
+ return(EINVAL);
+ } else if ( ip->ihl != sizeof(struct iphdr) / sizeof(int)) {
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: ip->ihl=%d, want %d\n",ip->ihl,
+ sizeof(struct iphdr)/sizeof(int));
+#endif
+ return(EINVAL);
+ }
+
+ switch (ip_fw_chk(ip, viadev, NULL, *chains[fwtype],
+ *policies[fwtype], IP_FW_MODE_CHK))
+ {
+ case FW_ACCEPT:
+ return(0);
+ case FW_REDIRECT:
+ return(ECONNABORTED);
+ case FW_MASQUERADE:
+ return(ECONNRESET);
+ case FW_REJECT:
+ return(ECONNREFUSED);
+ default: /* FW_BLOCK */
+ return(ETIMEDOUT);
+ }
+ }
+
+ if ( cmd == IP_FW_MASQ_TIMEOUTS )
+ return ip_fw_masq_timeouts(m, len);
+
+/*
+ * Here we really working hard-adding new elements
+ * to blocking/forwarding chains or deleting 'em
+ */
+
+ if ( cmd == IP_FW_INSERT || cmd == IP_FW_APPEND || cmd == IP_FW_DELETE )
+ {
+ struct ip_fw *frwl;
+ int fwtype;
+
+ frwl=check_ipfw_struct(m,len);
+ if (frwl==NULL)
+ return (EINVAL);
+ fwtype = (stage & IP_FW_TYPE) >> IP_FW_SHIFT;
+
+ switch (cmd)
+ {
+ case IP_FW_INSERT:
+ return(insert_in_chain(chains[fwtype],frwl,len));
+ case IP_FW_APPEND:
+ return(append_to_chain(chains[fwtype],frwl,len));
+ case IP_FW_DELETE:
+ return(del_from_chain(chains[fwtype],frwl));
+ default:
+ /*
+ * Should be panic but... (Why are BSD people panic obsessed ??)
+ */
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: unknown request %d\n",stage);
+#endif
+ return(EINVAL);
+ }
+ }
+
+#ifdef DEBUG_IP_FIREWALL
+ printk("ip_fw_ctl: unknown request %d\n",stage);
+#endif
+ return(ENOPROTOOPT);
+}
+#endif /* CONFIG_IP_FIREWALL */
+
+#if defined(CONFIG_IP_FIREWALL) || defined(CONFIG_IP_ACCT)
+static int ip_chain_procinfo(int stage, char *buffer, char **start,
+ off_t offset, int length)
+{
+ off_t pos=0, begin=0;
+ struct ip_fw *i;
+ unsigned long flags;
+ int len, p;
+ int last_len = 0;
+
+
+ switch(stage)
+ {
+#ifdef CONFIG_IP_FIREWALL
+ case IP_FW_IN:
+ i = ip_fw_in_chain;
+ len=sprintf(buffer, "IP firewall input rules, default %d\n",
+ ip_fw_in_policy);
+ break;
+ case IP_FW_OUT:
+ i = ip_fw_out_chain;
+ len=sprintf(buffer, "IP firewall output rules, default %d\n",
+ ip_fw_out_policy);
+ break;
+ case IP_FW_FWD:
+ i = ip_fw_fwd_chain;
+ len=sprintf(buffer, "IP firewall forward rules, default %d\n",
+ ip_fw_fwd_policy);
+ break;
+#endif
+#ifdef CONFIG_IP_ACCT
+ case IP_FW_ACCT:
+ i = ip_acct_chain;
+ len=sprintf(buffer,"IP accounting rules\n");
+ break;
+#endif
+ default:
+ /* this should never be reached, but safety first... */
+ i = NULL;
+ len=0;
+ break;
+ }
+
+ save_flags(flags);
+ cli();
+
+ while(i!=NULL)
+ {
+ len+=sprintf(buffer+len,"%08X/%08X->%08X/%08X %.16s %08X %X ",
+ ntohl(i->fw_src.s_addr),ntohl(i->fw_smsk.s_addr),
+ ntohl(i->fw_dst.s_addr),ntohl(i->fw_dmsk.s_addr),
+ (i->fw_vianame)[0] ? i->fw_vianame : "-",
+ ntohl(i->fw_via.s_addr), i->fw_flg);
+ /* 10 is enough for a 32 bit box but the counters are 64bit on
+ the Alpha and Ultrapenguin */
+ len+=sprintf(buffer+len,"%u %u %-20lu %-20lu",
+ i->fw_nsp,i->fw_ndp, i->fw_pcnt,i->fw_bcnt);
+ for (p = 0; p < IP_FW_MAX_PORTS; p++)
+ len+=sprintf(buffer+len, " %u", i->fw_pts[p]);
+ len+=sprintf(buffer+len, " A%02X X%02X", i->fw_tosand, i->fw_tosxor);
+ buffer[len++]='\n';
+ buffer[len]='\0';
+ pos=begin+len;
+ if(pos<offset)
+ {
+ len=0;
+ begin=pos;
+ }
+ else if(pos>offset+length)
+ {
+ len = last_len;
+ break;
+ }
+ last_len = len;
+ i=i->fw_next;
+ }
+ restore_flags(flags);
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ return len;
+}
+#endif
+
+#ifdef CONFIG_IP_ACCT
+static int ip_acct_procinfo(char *buffer, char **start, off_t offset,
+ int length)
+{
+ return ip_chain_procinfo(IP_FW_ACCT, buffer,start, offset,length);
+}
+#endif
+
+#ifdef CONFIG_IP_FIREWALL
+static int ip_fw_in_procinfo(char *buffer, char **start, off_t offset,
+ int length)
+{
+ return ip_chain_procinfo(IP_FW_IN, buffer,start,offset,length);
+}
+
+static int ip_fw_out_procinfo(char *buffer, char **start, off_t offset,
+ int length)
+{
+ return ip_chain_procinfo(IP_FW_OUT, buffer,start,offset,length);
+}
+
+static int ip_fw_fwd_procinfo(char *buffer, char **start, off_t offset,
+ int length)
+{
+ return ip_chain_procinfo(IP_FW_FWD, buffer,start,offset,length);
+}
+#endif
+
+
+#ifdef CONFIG_IP_FIREWALL
+/*
+ * Interface to the generic firewall chains.
+ */
+
+int ipfw_input_check(struct firewall_ops *this, int pf,
+ struct net_device *dev, void *phdr, void *arg,
+ struct sk_buff **pskb)
+{
+ return ip_fw_chk(phdr, dev, arg, ip_fw_in_chain, ip_fw_in_policy,
+ IP_FW_MODE_FW);
+}
+
+int ipfw_output_check(struct firewall_ops *this, int pf,
+ struct net_device *dev, void *phdr, void *arg,
+ struct sk_buff **pskb)
+{
+ return ip_fw_chk(phdr, dev, arg, ip_fw_out_chain, ip_fw_out_policy,
+ IP_FW_MODE_FW);
+}
+
+int ipfw_forward_check(struct firewall_ops *this, int pf,
+ struct net_device *dev, void *phdr, void *arg,
+ struct sk_buff **pskb)
+{
+ return ip_fw_chk(phdr, dev, arg, ip_fw_fwd_chain, ip_fw_fwd_policy,
+ IP_FW_MODE_FW);
+}
+
+#ifdef CONFIG_IP_ACCT
+int ipfw_acct_in(struct firewall_ops *this, int pf, struct net_device *dev,
+ void *phdr, void *arg, struct sk_buff **pskb)
+{
+ return ip_fw_chk(phdr,dev,NULL,ip_acct_chain,0,IP_FW_MODE_ACCT_IN);
+}
+
+int ipfw_acct_out(struct firewall_ops *this, int pf, struct net_device *dev,
+ void *phdr, void *arg, struct sk_buff **pskb)
+{
+ return ip_fw_chk(phdr,dev,NULL,ip_acct_chain,0,IP_FW_MODE_ACCT_OUT);
+}
+#endif
+
+struct firewall_ops ipfw_ops=
+{
+ NULL,
+ ipfw_forward_check,
+ ipfw_input_check,
+ ipfw_output_check,
+#ifdef CONFIG_IP_ACCT
+ ipfw_acct_in,
+ ipfw_acct_out
+#else
+ NULL,
+ NULL
+#endif
+};
+
+#endif
+
+#if defined(CONFIG_IP_ACCT) || defined(CONFIG_IP_FIREWALL)
+
+int ipfw_device_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct net_device *dev=ptr;
+ char *devname = dev->name;
+ unsigned long flags;
+ struct ip_fw *fw;
+ int chn;
+
+ save_flags(flags);
+ cli();
+
+ if (event == NETDEV_UP) {
+ for (chn = 0; chn < IP_FW_CHAINS; chn++)
+ for (fw = *chains[chn]; fw; fw = fw->fw_next)
+ if ((fw->fw_vianame)[0] && !strncmp(devname,
+ fw->fw_vianame, IFNAMSIZ))
+ fw->fw_viadev = dev;
+ } else if (event == NETDEV_DOWN) {
+ for (chn = 0; chn < IP_FW_CHAINS; chn++)
+ for (fw = *chains[chn]; fw; fw = fw->fw_next)
+ /* we could compare just the pointers ... */
+ if ((fw->fw_vianame)[0] && !strncmp(devname,
+ fw->fw_vianame, IFNAMSIZ))
+ fw->fw_viadev = (struct net_device*)-1;
+ }
+
+ restore_flags(flags);
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block ipfw_dev_notifier={
+ ipfw_device_event,
+ NULL,
+ 0
+};
+
+#endif
+
+int ipfw_init_or_cleanup(int init)
+{
+ int ret = 0;
+
+ if (!init)
+ goto cleanup;
+
+ ret = register_firewall(PF_INET, &ipfw_ops);
+ if (ret < 0)
+ goto cleanup_nothing;
+
+#ifdef CONFIG_IP_ACCT
+ proc_net_create("ip_acct", S_IFREG | S_IRUGO | S_IWUSR, ip_acct_procinfo);
+#endif
+ proc_net_create("ip_input", S_IFREG | S_IRUGO | S_IWUSR, ip_fw_in_procinfo);
+ proc_net_create("ip_output", S_IFREG | S_IRUGO | S_IWUSR, ip_fw_out_procinfo);
+ proc_net_create("ip_forward", S_IFREG | S_IRUGO | S_IWUSR, ip_fw_fwd_procinfo);
+
+ /* Register for device up/down reports */
+ register_netdevice_notifier(&ipfw_dev_notifier);
+
+#ifdef CONFIG_IP_FIREWALL_NETLINK
+ ipfwsk = netlink_kernel_create(NETLINK_FIREWALL, NULL);
+#endif
+ return ret;
+
+ cleanup:
+#ifdef CONFIG_IP_FIREWALL_NETLINK
+ sock_release(ipfwsk->socket);
+#endif
+ unregister_netdevice_notifier(&ipfw_dev_notifier);
+
+#ifdef CONFIG_IP_ACCT
+ proc_net_remove("ip_acct");
+#endif
+ proc_net_remove("ip_input");
+ proc_net_remove("ip_output");
+ proc_net_remove("ip_forward");
+
+ free_fw_chain(chains[IP_FW_FWD]);
+ free_fw_chain(chains[IP_FW_IN]);
+ free_fw_chain(chains[IP_FW_OUT]);
+ free_fw_chain(chains[IP_FW_ACCT]);
+
+ unregister_firewall(PF_INET, &ipfw_ops);
+
+ cleanup_nothing:
+ return ret;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_CONNLOG.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_CONNLOG.c
new file mode 100644
index 0000000..55a937a
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_CONNLOG.c
@@ -0,0 +1,148 @@
+/* Optionally log start and end of connections.
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+#include <linux/netfilter_ipv4/ip_conntrack_protocol.h>
+#include <linux/netfilter_ipv4/ip_conntrack_core.h>
+
+/* Use lock to serialize, so printks don't overlap */
+static spinlock_t log_lock = SPIN_LOCK_UNLOCKED;
+
+static unsigned int
+target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ enum ip_conntrack_info ctinfo;
+ struct ip_conntrack *ct = ip_conntrack_get((*pskb), &ctinfo);
+
+ if (ct)
+ set_bit(IPS_LOG_BIT, &ct->status);
+
+ return IPT_CONTINUE;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ if (targinfosize != IPT_ALIGN(0))
+ return 0;
+
+ return 1;
+}
+
+static struct ipt_target ipt_connlog_reg = {
+ .name = "CONNLOG",
+ .target = target,
+ .checkentry = checkentry,
+ .me = THIS_MODULE,
+};
+
+static void dump_tuple(char *buffer, const struct ip_conntrack_tuple *tuple,
+ struct ip_conntrack_protocol *proto)
+{
+ printk("src=%u.%u.%u.%u dst=%u.%u.%u.%u ",
+ NIPQUAD(tuple->src.ip), NIPQUAD(tuple->dst.ip));
+
+ if (proto->print_tuple(buffer, tuple))
+ printk("%s", buffer);
+}
+
+#ifdef CONFIG_IP_NF_CT_ACCT
+static void dump_counters(struct ip_conntrack_counter *counter)
+{
+ printk("packets=%llu bytes=%llu ", counter->packets, counter->bytes);
+}
+#else
+static inline void dump_counters(struct ip_conntrack_counter *counter) {}
+#endif
+
+static void dump_conntrack(struct ip_conntrack *ct)
+{
+ struct ip_conntrack_protocol *proto
+ = __ip_ct_find_proto(ct->tuplehash[IP_CT_DIR_ORIGINAL]
+ .tuple.dst.protonum);
+ char buffer[256];
+
+ printk("proto=%s ", proto->name);
+
+ if (proto->print_conntrack(buffer, ct))
+ printk("%s", buffer);
+
+ dump_tuple(buffer, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, proto);
+ dump_counters(&ct->counters[IP_CT_DIR_ORIGINAL]);
+
+ dump_tuple(buffer, &ct->tuplehash[IP_CT_DIR_REPLY].tuple, proto);
+ dump_counters(&ct->counters[IP_CT_DIR_REPLY]);
+
+#if defined(CONFIG_IP_NF_CONNTRACK_MARK)
+ printk("mark=%ld ", ct->mark);
+#endif
+}
+
+static int conntrack_event(struct notifier_block *this,
+ unsigned long event,
+ void *ptr)
+{
+ struct ip_conntrack *ct = ptr;
+ const char *log_prefix = NULL;
+
+ if (event & (IPCT_NEW|IPCT_RELATED))
+ log_prefix = "create";
+ else if (event & (IPCT_DESTROY))
+ log_prefix = "destroy";
+
+ if (log_prefix && test_bit(IPS_LOG_BIT, &ct->status)) {
+ spin_lock_bh(&log_lock);
+ printk(KERN_INFO "conntrack %s: ", log_prefix);
+ dump_conntrack(ct);
+ printk("\n");
+ spin_unlock_bh(&log_lock);
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block conntrack_notifier = {
+ .notifier_call = conntrack_event,
+};
+
+static int __init init(void)
+{
+ int ret;
+
+ ret = ipt_register_target(&ipt_connlog_reg);
+ if (ret != 0)
+ return ret;
+
+ ret = ip_conntrack_register_notifier(&conntrack_notifier);
+ if (ret != 0)
+ goto unregister_connlog;
+
+ return ret;
+
+unregister_connlog:
+ ipt_unregister_target(&ipt_connlog_reg);
+
+ return ret;
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&ipt_connlog_reg);
+ ip_conntrack_unregister_notifier(&conntrack_notifier);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_CONNMARK.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_CONNMARK.c
new file mode 100644
index 0000000..2b96256
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_CONNMARK.c
@@ -0,0 +1,87 @@
+/* This is a module which is used for setting/remembering the mark field of
+ * an connection, or optionally restore it to the skb
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <net/checksum.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_CONNMARK.h>
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+
+static unsigned int
+target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ const struct ipt_connmark_target_info *markinfo = targinfo;
+
+ enum ip_conntrack_info ctinfo;
+ struct ip_conntrack *ct = ip_conntrack_get((*pskb), &ctinfo);
+ if (ct) {
+ switch(markinfo->mode) {
+ case IPT_CONNMARK_SET:
+ ct->mark = markinfo->mark;
+ break;
+ case IPT_CONNMARK_SAVE:
+ ct->mark = (*pskb)->nfmark;
+ break;
+ case IPT_CONNMARK_RESTORE:
+ if (ct->mark != (*pskb)->nfmark) {
+ (*pskb)->nfmark = ct->mark;
+ (*pskb)->nfcache |= NFC_ALTERED;
+ }
+ break;
+ }
+ }
+
+ return IPT_CONTINUE;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ struct ipt_connmark_target_info *matchinfo = targinfo;
+ if (targinfosize != IPT_ALIGN(sizeof(struct ipt_connmark_target_info))) {
+ printk(KERN_WARNING "CONNMARK: targinfosize %u != %Zu\n",
+ targinfosize,
+ IPT_ALIGN(sizeof(struct ipt_connmark_target_info)));
+ return 0;
+ }
+
+ if (matchinfo->mode == IPT_CONNMARK_RESTORE) {
+ if (strcmp(tablename, "mangle") != 0) {
+ printk(KERN_WARNING "CONNMARK: restore can only be called from \"mangle\" table, not \"%s\"\n", tablename);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static struct ipt_target ipt_connmark_reg
+= { { NULL, NULL }, "CONNMARK", target, checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ if (ipt_register_target(&ipt_connmark_reg))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&ipt_connmark_reg);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_DSCP.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_DSCP.c
new file mode 100644
index 0000000..0087dd8
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_DSCP.c
@@ -0,0 +1,108 @@
+/* iptables module for setting the IPv4 DSCP field, Version 1.8
+ *
+ * (C) 2002 by Harald Welte <laforge@gnumonks.org>
+ * based on ipt_FTOS.c (C) 2000 by Matthew G. Marsh <mgm@paktronix.com>
+ * This software is distributed under GNU GPL v2, 1991
+ *
+ * See RFC2474 for a description of the DSCP field within the IP Header.
+ *
+ * ipt_DSCP.c,v 1.8 2002/08/06 18:41:57 laforge Exp
+*/
+
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <net/checksum.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_DSCP.h>
+
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("IP tables DSCP modification module");
+MODULE_LICENSE("GPL");
+
+static unsigned int
+target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ struct iphdr *iph = (*pskb)->nh.iph;
+ const struct ipt_DSCP_info *dinfo = targinfo;
+ u_int8_t sh_dscp = ((dinfo->dscp << IPT_DSCP_SHIFT) & IPT_DSCP_MASK);
+
+
+ if ((iph->tos & IPT_DSCP_MASK) != sh_dscp) {
+ u_int16_t diffs[2];
+
+ /* raw socket (tcpdump) may have clone of incoming
+ * skb: don't disturb it --RR */
+ if (skb_cloned(*pskb) && !(*pskb)->sk) {
+ struct sk_buff *nskb = skb_copy(*pskb, GFP_ATOMIC);
+ if (!nskb)
+ return NF_DROP;
+ kfree_skb(*pskb);
+ *pskb = nskb;
+ iph = (*pskb)->nh.iph;
+ }
+
+ diffs[0] = htons(iph->tos) ^ 0xFFFF;
+ iph->tos = (iph->tos & ~IPT_DSCP_MASK) | sh_dscp;
+ diffs[1] = htons(iph->tos);
+ iph->check = csum_fold(csum_partial((char *)diffs,
+ sizeof(diffs),
+ iph->check^0xFFFF));
+ (*pskb)->nfcache |= NFC_ALTERED;
+ }
+ return IPT_CONTINUE;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ const u_int8_t dscp = ((struct ipt_DSCP_info *)targinfo)->dscp;
+
+ if (targinfosize != IPT_ALIGN(sizeof(struct ipt_DSCP_info))) {
+ printk(KERN_WARNING "DSCP: targinfosize %u != %Zu\n",
+ targinfosize,
+ IPT_ALIGN(sizeof(struct ipt_DSCP_info)));
+ return 0;
+ }
+
+ if (strcmp(tablename, "mangle") != 0) {
+ printk(KERN_WARNING "DSCP: can only be called from \"mangle\" table, not \"%s\"\n", tablename);
+ return 0;
+ }
+
+ if ((dscp > IPT_DSCP_MAX)) {
+ printk(KERN_WARNING "DSCP: dscp %x out of range\n", dscp);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ipt_target ipt_dscp_reg
+= { { NULL, NULL }, "DSCP", target, checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ if (ipt_register_target(&ipt_dscp_reg))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&ipt_dscp_reg);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ECN.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ECN.c
new file mode 100644
index 0000000..a106863
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ECN.c
@@ -0,0 +1,186 @@
+/* iptables module for the IPv4 and TCP ECN bits, Version 1.2
+ *
+ * (C) 2002 by Harald Welte <laforge@gnumonks.org>
+ *
+ * This software is distributed under GNU GPL v2, 1991
+ *
+ * ipt_ECN.c,v 1.7 2003/12/15 15:18:06 laforge Exp
+*/
+
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <net/checksum.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_ECN.h>
+
+MODULE_LICENSE("GPL");
+
+/* set ECT codepoint from IP header.
+ * return 0 in case there was no ECT codepoint
+ * return 1 in case ECT codepoint has been overwritten
+ * return < 0 in case there was error */
+static int inline
+set_ect_ip(struct sk_buff **pskb, struct iphdr *iph,
+ const struct ipt_ECN_info *einfo)
+{
+ if ((iph->tos & IPT_ECN_IP_MASK)
+ != (einfo->ip_ect & IPT_ECN_IP_MASK)) {
+ u_int16_t diffs[2];
+
+ /* raw socket (tcpdump) may have clone of incoming
+ * skb: don't disturb it --RR */
+ if (skb_cloned(*pskb) && !(*pskb)->sk) {
+ struct sk_buff *nskb = skb_copy(*pskb, GFP_ATOMIC);
+ if (!nskb)
+ return NF_DROP;
+ kfree_skb(*pskb);
+ *pskb = nskb;
+ iph = (*pskb)->nh.iph;
+ }
+
+ diffs[0] = htons(iph->tos) ^ 0xFFFF;
+ iph->tos = iph->tos & ~IPT_ECN_IP_MASK;
+ iph->tos = iph->tos | (einfo->ip_ect & IPT_ECN_IP_MASK);
+ diffs[1] = htons(iph->tos);
+ iph->check = csum_fold(csum_partial((char *)diffs,
+ sizeof(diffs),
+ iph->check^0xFFFF));
+ (*pskb)->nfcache |= NFC_ALTERED;
+
+ return 1;
+ }
+ return 0;
+}
+
+static int inline
+set_ect_tcp(struct sk_buff **pskb, struct iphdr *iph,
+ const struct ipt_ECN_info *einfo)
+{
+
+ struct tcphdr *tcph;
+ u_int16_t *tcpflags;
+ u_int16_t diffs[2];
+
+ /* raw socket (tcpdump) may have clone of incoming
+ * skb: don't disturb it --RR */
+ if (skb_cloned(*pskb) && !(*pskb)->sk) {
+ struct sk_buff *nskb = skb_copy(*pskb, GFP_ATOMIC);
+ if (!nskb)
+ return NF_DROP;
+ kfree_skb(*pskb);
+ *pskb = nskb;
+ }
+
+ iph = (*pskb)->nh.iph;
+ tcph = (void *) iph + iph->ihl * 4;
+ tcpflags = (u_int16_t *)tcph + 6;
+
+ diffs[0] = *tcpflags;
+
+ if (einfo->operation & IPT_ECN_OP_SET_ECE
+ && tcph->ece != einfo->proto.tcp.ece) {
+ tcph->ece = einfo->proto.tcp.ece;
+ }
+
+ if (einfo->operation & IPT_ECN_OP_SET_CWR
+ && tcph->cwr != einfo->proto.tcp.cwr) {
+ tcph->cwr = einfo->proto.tcp.cwr;
+ }
+
+ if (diffs[0] != *tcpflags) {
+ diffs[0] = diffs[0] ^ 0xFFFF;
+ diffs[1] = *tcpflags;
+ tcph->check = csum_fold(csum_partial((char *)diffs,
+ sizeof(diffs),
+ tcph->check^0xFFFF));
+ (*pskb)->nfcache |= NFC_ALTERED;
+
+ return 1;
+ }
+
+ return 0;
+}
+
+static unsigned int
+target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ struct iphdr *iph = (*pskb)->nh.iph;
+ const struct ipt_ECN_info *einfo = targinfo;
+
+ if (einfo->operation & IPT_ECN_OP_SET_IP)
+ set_ect_ip(pskb, iph, einfo);
+
+ if (einfo->operation & (IPT_ECN_OP_SET_ECE | IPT_ECN_OP_SET_CWR)
+ && iph->protocol == IPPROTO_TCP)
+ set_ect_tcp(pskb, iph, einfo);
+
+ return IPT_CONTINUE;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ const struct ipt_ECN_info *einfo = (struct ipt_ECN_info *)targinfo;
+
+ if (targinfosize != IPT_ALIGN(sizeof(struct ipt_ECN_info))) {
+ printk(KERN_WARNING "ECN: targinfosize %u != %Zu\n",
+ targinfosize,
+ IPT_ALIGN(sizeof(struct ipt_ECN_info)));
+ return 0;
+ }
+
+ if (strcmp(tablename, "mangle") != 0) {
+ printk(KERN_WARNING "ECN: can only be called from \"mangle\" table, not \"%s\"\n", tablename);
+ return 0;
+ }
+
+ if (einfo->operation & IPT_ECN_OP_MASK) {
+ printk(KERN_WARNING "ECN: unsupported ECN operation %x\n",
+ einfo->operation);
+ return 0;
+ }
+ if (einfo->ip_ect & ~IPT_ECN_IP_MASK) {
+ printk(KERN_WARNING "ECN: new ECT codepoint %x out of mask\n",
+ einfo->ip_ect);
+ return 0;
+ }
+
+ if ((einfo->operation & (IPT_ECN_OP_SET_ECE|IPT_ECN_OP_SET_CWR))
+ && e->ip.proto != IPPROTO_TCP) {
+ printk(KERN_WARNING "ECN: cannot use TCP operations on a "
+ "non-tcp rule\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ipt_target ipt_ecn_reg
+= { { NULL, NULL }, "ECN", target, checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ if (ipt_register_target(&ipt_ecn_reg))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&ipt_ecn_reg);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_IMQ.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_IMQ.c
new file mode 100644
index 0000000..2ba068b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_IMQ.c
@@ -0,0 +1,78 @@
+/* This target marks packets to be enqueued to an imq device */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_IMQ.h>
+#include <linux/imq.h>
+
+static unsigned int imq_target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ struct ipt_imq_info *mr = (struct ipt_imq_info*)targinfo;
+
+ (*pskb)->imq_flags = mr->todev | IMQ_F_ENQUEUE;
+ (*pskb)->nfcache |= NFC_ALTERED;
+
+ return IPT_CONTINUE;
+}
+
+static int imq_checkentry(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ struct ipt_imq_info *mr;
+
+ if (targinfosize != IPT_ALIGN(sizeof(struct ipt_imq_info))) {
+ printk(KERN_WARNING "IMQ: invalid targinfosize\n");
+ return 0;
+ }
+ mr = (struct ipt_imq_info*)targinfo;
+
+ if (strcmp(tablename, "mangle") != 0) {
+ printk(KERN_WARNING
+ "IMQ: IMQ can only be called from \"mangle\" table, not \"%s\"\n",
+ tablename);
+ return 0;
+ }
+
+ if (mr->todev > IMQ_MAX_DEVS) {
+ printk(KERN_WARNING
+ "IMQ: invalid device specified, highest is %u\n",
+ IMQ_MAX_DEVS);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ipt_target ipt_imq_reg = {
+ { NULL, NULL},
+ "IMQ",
+ imq_target,
+ imq_checkentry,
+ NULL,
+ THIS_MODULE
+};
+
+static int __init init(void)
+{
+ if (ipt_register_target(&ipt_imq_reg))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&ipt_imq_reg);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_LOG.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_LOG.c
new file mode 100644
index 0000000..eaab83f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_LOG.c
@@ -0,0 +1,400 @@
+/*
+ * This is a module which is used for logging packets.
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/ip.h>
+#include <net/icmp.h>
+#include <net/udp.h>
+#include <net/tcp.h>
+#include <net/route.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_LOG.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+/* FIXME: move to ip.h like in 2.5 */
+struct ahhdr {
+ __u8 nexthdr;
+ __u8 hdrlen;
+ __u16 reserved;
+ __u32 spi;
+ __u32 seq_no;
+};
+
+struct esphdr {
+ __u32 spi;
+ __u32 seq_no;
+};
+
+/* Use lock to serialize, so printks don't overlap */
+static spinlock_t log_lock = SPIN_LOCK_UNLOCKED;
+
+/* One level of recursion won't kill us */
+static void dump_packet(const struct ipt_log_info *info,
+ struct iphdr *iph, unsigned int len, int recurse)
+{
+ void *protoh = (u_int32_t *)iph + iph->ihl;
+ unsigned int datalen = len - iph->ihl * 4;
+
+ /* Important fields:
+ * TOS, len, DF/MF, fragment offset, TTL, src, dst, options. */
+ /* Max length: 40 "SRC=255.255.255.255 DST=255.255.255.255 " */
+ printk("SRC=%u.%u.%u.%u DST=%u.%u.%u.%u ",
+ NIPQUAD(iph->saddr), NIPQUAD(iph->daddr));
+
+ /* Max length: 46 "LEN=65535 TOS=0xFF PREC=0xFF TTL=255 ID=65535 " */
+ printk("LEN=%u TOS=0x%02X PREC=0x%02X TTL=%u ID=%u ",
+ ntohs(iph->tot_len), iph->tos & IPTOS_TOS_MASK,
+ iph->tos & IPTOS_PREC_MASK, iph->ttl, ntohs(iph->id));
+
+ /* Max length: 6 "CE DF MF " */
+ if (ntohs(iph->frag_off) & IP_CE)
+ printk("CE ");
+ if (ntohs(iph->frag_off) & IP_DF)
+ printk("DF ");
+ if (ntohs(iph->frag_off) & IP_MF)
+ printk("MF ");
+
+ /* Max length: 11 "FRAG:65535 " */
+ if (ntohs(iph->frag_off) & IP_OFFSET)
+ printk("FRAG:%u ", ntohs(iph->frag_off) & IP_OFFSET);
+
+ if ((info->logflags & IPT_LOG_IPOPT)
+ && iph->ihl * 4 > sizeof(struct iphdr)
+ && iph->ihl * 4 <= len) {
+ unsigned int i;
+
+ /* Max length: 127 "OPT (" 15*4*2chars ") " */
+ printk("OPT (");
+ for (i = sizeof(struct iphdr); i < iph->ihl * 4; i++)
+ printk("%02X", ((u_int8_t *)iph)[i]);
+ printk(") ");
+ }
+
+ switch (iph->protocol) {
+ case IPPROTO_TCP: {
+ struct tcphdr *tcph = protoh;
+
+ /* Max length: 10 "PROTO=TCP " */
+ printk("PROTO=TCP ");
+
+ if (ntohs(iph->frag_off) & IP_OFFSET)
+ break;
+
+ /* Max length: 25 "INCOMPLETE [65535 bytes] " */
+ if (datalen < sizeof (*tcph)) {
+ printk("INCOMPLETE [%u bytes] ", datalen);
+ break;
+ }
+
+ /* Max length: 20 "SPT=65535 DPT=65535 " */
+ printk("SPT=%u DPT=%u ",
+ ntohs(tcph->source), ntohs(tcph->dest));
+ /* Max length: 30 "SEQ=4294967295 ACK=4294967295 " */
+ if (info->logflags & IPT_LOG_TCPSEQ)
+ printk("SEQ=%u ACK=%u ",
+ ntohl(tcph->seq), ntohl(tcph->ack_seq));
+ /* Max length: 13 "WINDOW=65535 " */
+ printk("WINDOW=%u ", ntohs(tcph->window));
+ /* Max length: 9 "RES=0x3F " */
+ printk("RES=0x%02x ", (u_int8_t)(ntohl(tcp_flag_word(tcph) & TCP_RESERVED_BITS) >> 22));
+ /* Max length: 32 "CWR ECE URG ACK PSH RST SYN FIN " */
+ if (tcph->cwr)
+ printk("CWR ");
+ if (tcph->ece)
+ printk("ECE ");
+ if (tcph->urg)
+ printk("URG ");
+ if (tcph->ack)
+ printk("ACK ");
+ if (tcph->psh)
+ printk("PSH ");
+ if (tcph->rst)
+ printk("RST ");
+ if (tcph->syn)
+ printk("SYN ");
+ if (tcph->fin)
+ printk("FIN ");
+ /* Max length: 11 "URGP=65535 " */
+ printk("URGP=%u ", ntohs(tcph->urg_ptr));
+
+ if ((info->logflags & IPT_LOG_TCPOPT)
+ && tcph->doff * 4 > sizeof(struct tcphdr)
+ && tcph->doff * 4 <= datalen) {
+ unsigned int i;
+
+ /* Max length: 127 "OPT (" 15*4*2chars ") " */
+ printk("OPT (");
+ for (i =sizeof(struct tcphdr); i < tcph->doff * 4; i++)
+ printk("%02X", ((u_int8_t *)tcph)[i]);
+ printk(") ");
+ }
+ break;
+ }
+ case IPPROTO_UDP: {
+ struct udphdr *udph = protoh;
+
+ /* Max length: 10 "PROTO=UDP " */
+ printk("PROTO=UDP ");
+
+ if (ntohs(iph->frag_off) & IP_OFFSET)
+ break;
+
+ /* Max length: 25 "INCOMPLETE [65535 bytes] " */
+ if (datalen < sizeof (*udph)) {
+ printk("INCOMPLETE [%u bytes] ", datalen);
+ break;
+ }
+
+ /* Max length: 20 "SPT=65535 DPT=65535 " */
+ printk("SPT=%u DPT=%u LEN=%u ",
+ ntohs(udph->source), ntohs(udph->dest),
+ ntohs(udph->len));
+ break;
+ }
+ case IPPROTO_ICMP: {
+ struct icmphdr *icmph = protoh;
+ static size_t required_len[NR_ICMP_TYPES+1]
+ = { [ICMP_ECHOREPLY] = 4,
+ [ICMP_DEST_UNREACH]
+ = 8 + sizeof(struct iphdr) + 8,
+ [ICMP_SOURCE_QUENCH]
+ = 8 + sizeof(struct iphdr) + 8,
+ [ICMP_REDIRECT]
+ = 8 + sizeof(struct iphdr) + 8,
+ [ICMP_ECHO] = 4,
+ [ICMP_TIME_EXCEEDED]
+ = 8 + sizeof(struct iphdr) + 8,
+ [ICMP_PARAMETERPROB]
+ = 8 + sizeof(struct iphdr) + 8,
+ [ICMP_TIMESTAMP] = 20,
+ [ICMP_TIMESTAMPREPLY] = 20,
+ [ICMP_ADDRESS] = 12,
+ [ICMP_ADDRESSREPLY] = 12 };
+
+ /* Max length: 11 "PROTO=ICMP " */
+ printk("PROTO=ICMP ");
+
+ if (ntohs(iph->frag_off) & IP_OFFSET)
+ break;
+
+ /* Max length: 25 "INCOMPLETE [65535 bytes] " */
+ if (datalen < 4) {
+ printk("INCOMPLETE [%u bytes] ", datalen);
+ break;
+ }
+
+ /* Max length: 18 "TYPE=255 CODE=255 " */
+ printk("TYPE=%u CODE=%u ", icmph->type, icmph->code);
+
+ /* Max length: 25 "INCOMPLETE [65535 bytes] " */
+ if (icmph->type <= NR_ICMP_TYPES
+ && required_len[icmph->type]
+ && datalen < required_len[icmph->type]) {
+ printk("INCOMPLETE [%u bytes] ", datalen);
+ break;
+ }
+
+ switch (icmph->type) {
+ case ICMP_ECHOREPLY:
+ case ICMP_ECHO:
+ /* Max length: 19 "ID=65535 SEQ=65535 " */
+ printk("ID=%u SEQ=%u ",
+ ntohs(icmph->un.echo.id),
+ ntohs(icmph->un.echo.sequence));
+ break;
+
+ case ICMP_PARAMETERPROB:
+ /* Max length: 14 "PARAMETER=255 " */
+ printk("PARAMETER=%u ",
+ ntohl(icmph->un.gateway) >> 24);
+ break;
+ case ICMP_REDIRECT:
+ /* Max length: 24 "GATEWAY=255.255.255.255 " */
+ printk("GATEWAY=%u.%u.%u.%u ", NIPQUAD(icmph->un.gateway));
+ /* Fall through */
+ case ICMP_DEST_UNREACH:
+ case ICMP_SOURCE_QUENCH:
+ case ICMP_TIME_EXCEEDED:
+ /* Max length: 3+maxlen */
+ if (recurse) {
+ printk("[");
+ dump_packet(info,
+ (struct iphdr *)(icmph + 1),
+ datalen-sizeof(struct icmphdr),
+ 0);
+ printk("] ");
+ }
+
+ /* Max length: 10 "MTU=65535 " */
+ if (icmph->type == ICMP_DEST_UNREACH
+ && icmph->code == ICMP_FRAG_NEEDED)
+ printk("MTU=%u ", ntohs(icmph->un.frag.mtu));
+ }
+ break;
+ }
+ /* Max Length */
+ case IPPROTO_AH: {
+ struct ahhdr *ah = protoh;
+
+ /* Max length: 9 "PROTO=AH " */
+ printk("PROTO=AH ");
+
+ if (ntohs(iph->frag_off) & IP_OFFSET)
+ break;
+
+ /* Max length: 25 "INCOMPLETE [65535 bytes] " */
+ if (datalen < sizeof (*ah)) {
+ printk("INCOMPLETE [%u bytes] ", datalen);
+ break;
+ }
+
+ /* Length: 15 "SPI=0xF1234567 " */
+ printk("SPI=0x%x ", ntohl(ah->spi) );
+ break;
+ }
+ case IPPROTO_ESP: {
+ struct esphdr *esph = protoh;
+
+ /* Max length: 10 "PROTO=ESP " */
+ printk("PROTO=ESP ");
+
+ if (ntohs(iph->frag_off) & IP_OFFSET)
+ break;
+
+ /* Max length: 25 "INCOMPLETE [65535 bytes] " */
+ if (datalen < sizeof (*esph)) {
+ printk("INCOMPLETE [%u bytes] ", datalen);
+ break;
+ }
+
+ /* Length: 15 "SPI=0xF1234567 " */
+ printk("SPI=0x%x ", ntohl(esph->spi) );
+ break;
+ }
+ /* Max length: 10 "PROTO 255 " */
+ default:
+ printk("PROTO=%u ", iph->protocol);
+ }
+
+ /* Proto Max log string length */
+ /* IP: 40+46+6+11+127 = 230 */
+ /* TCP: 10+max(25,20+30+13+9+32+11+127) = 252 */
+ /* UDP: 10+max(25,20) = 35 */
+ /* ICMP: 11+max(25, 18+25+max(19,14,24+3+n+10,3+n+10)) = 91+n */
+ /* ESP: 10+max(25)+15 = 50 */
+ /* AH: 9+max(25)+15 = 49 */
+ /* unknown: 10 */
+
+ /* (ICMP allows recursion one level deep) */
+ /* maxlen = IP + ICMP + IP + max(TCP,UDP,ICMP,unknown) */
+ /* maxlen = 230+ 91 + 230 + 252 = 803 */
+}
+
+static unsigned int
+ipt_log_target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ struct iphdr *iph = (*pskb)->nh.iph;
+ const struct ipt_log_info *loginfo = targinfo;
+ char level_string[4] = "< >";
+
+ level_string[1] = '0' + (loginfo->level % 8);
+ spin_lock_bh(&log_lock);
+ printk(level_string);
+ printk("%sIN=%s OUT=%s ",
+ loginfo->prefix,
+ in ? in->name : "",
+ out ? out->name : "");
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+ if ((*pskb)->nf_bridge) {
+ struct net_device *physindev = (*pskb)->nf_bridge->physindev;
+ struct net_device *physoutdev = (*pskb)->nf_bridge->physoutdev;
+
+ if (physindev && in != physindev)
+ printk("PHYSIN=%s ", physindev->name);
+ if (physoutdev && out != physoutdev)
+ printk("PHYSOUT=%s ", physoutdev->name);
+ }
+#endif
+
+ if (in && !out) {
+ /* MAC logging for input chain only. */
+ printk("MAC=");
+ if ((*pskb)->dev && (*pskb)->dev->hard_header_len && (*pskb)->mac.raw != (void*)iph) {
+ int i;
+ unsigned char *p = (*pskb)->mac.raw;
+ for (i = 0; i < (*pskb)->dev->hard_header_len; i++,p++)
+ printk("%02x%c", *p,
+ i==(*pskb)->dev->hard_header_len - 1
+ ? ' ':':');
+ } else
+ printk(" ");
+ }
+
+ dump_packet(loginfo, iph, (*pskb)->len, 1);
+ printk("\n");
+ spin_unlock_bh(&log_lock);
+
+ return IPT_CONTINUE;
+}
+
+static int ipt_log_checkentry(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ const struct ipt_log_info *loginfo = targinfo;
+
+ if (targinfosize != IPT_ALIGN(sizeof(struct ipt_log_info))) {
+ DEBUGP("LOG: targinfosize %u != %u\n",
+ targinfosize, IPT_ALIGN(sizeof(struct ipt_log_info)));
+ return 0;
+ }
+
+ if (loginfo->level >= 8) {
+ DEBUGP("LOG: level %u >= 8\n", loginfo->level);
+ return 0;
+ }
+
+ if (loginfo->prefix[sizeof(loginfo->prefix)-1] != '\0') {
+ DEBUGP("LOG: prefix term %i\n",
+ loginfo->prefix[sizeof(loginfo->prefix)-1]);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ipt_target ipt_log_reg
+= { { NULL, NULL }, "LOG", ipt_log_target, ipt_log_checkentry, NULL,
+ THIS_MODULE };
+
+static int __init init(void)
+{
+ if (ipt_register_target(&ipt_log_reg))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&ipt_log_reg);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_MARK.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_MARK.c
new file mode 100644
index 0000000..63a998d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_MARK.c
@@ -0,0 +1,67 @@
+/* This is a module which is used for setting the NFMARK field of an skb. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <net/checksum.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_MARK.h>
+
+static unsigned int
+target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ const struct ipt_mark_target_info *markinfo = targinfo;
+
+ if((*pskb)->nfmark != markinfo->mark) {
+ (*pskb)->nfmark = markinfo->mark;
+ (*pskb)->nfcache |= NFC_ALTERED;
+ }
+ return IPT_CONTINUE;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ if (targinfosize != IPT_ALIGN(sizeof(struct ipt_mark_target_info))) {
+ printk(KERN_WARNING "MARK: targinfosize %u != %Zu\n",
+ targinfosize,
+ IPT_ALIGN(sizeof(struct ipt_mark_target_info)));
+ return 0;
+ }
+
+ if (strcmp(tablename, "mangle") != 0) {
+ printk(KERN_WARNING "MARK: can only be called from \"mangle\" table, not \"%s\"\n", tablename);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ipt_target ipt_mark_reg
+= { { NULL, NULL }, "MARK", target, checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ if (ipt_register_target(&ipt_mark_reg))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&ipt_mark_reg);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_MASQUERADE.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_MASQUERADE.c
new file mode 100644
index 0000000..6bd7a64
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_MASQUERADE.c
@@ -0,0 +1,213 @@
+/* Masquerade. Simple mapping which alters range to a local IP address
+ (depending on route). */
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/ip.h>
+#include <linux/timer.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <net/protocol.h>
+#include <net/checksum.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+/* Lock protects masq region inside conntrack */
+static DECLARE_RWLOCK(masq_lock);
+
+/* FIXME: Multiple targets. --RR */
+static int
+masquerade_check(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ const struct ip_nat_multi_range *mr = targinfo;
+
+ if (strcmp(tablename, "nat") != 0) {
+ DEBUGP("masquerade_check: bad table `%s'.\n", tablename);
+ return 0;
+ }
+ if (targinfosize != IPT_ALIGN(sizeof(*mr))) {
+ DEBUGP("masquerade_check: size %u != %u.\n",
+ targinfosize, sizeof(*mr));
+ return 0;
+ }
+ if (hook_mask & ~(1 << NF_IP_POST_ROUTING)) {
+ DEBUGP("masquerade_check: bad hooks %x.\n", hook_mask);
+ return 0;
+ }
+ if (mr->range[0].flags & IP_NAT_RANGE_MAP_IPS) {
+ DEBUGP("masquerade_check: bad MAP_IPS.\n");
+ return 0;
+ }
+ if (mr->rangesize != 1) {
+ DEBUGP("masquerade_check: bad rangesize %u.\n", mr->rangesize);
+ return 0;
+ }
+ return 1;
+}
+
+static unsigned int
+masquerade_target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ struct ip_conntrack *ct;
+ enum ip_conntrack_info ctinfo;
+ const struct ip_nat_multi_range *mr;
+ struct ip_nat_multi_range newrange;
+ u_int32_t newsrc;
+ struct rtable *rt;
+ struct rt_key key;
+
+ IP_NF_ASSERT(hooknum == NF_IP_POST_ROUTING);
+
+ /* FIXME: For the moment, don't do local packets, breaks
+ testsuite for 2.3.49 --RR */
+ if ((*pskb)->sk)
+ return NF_ACCEPT;
+
+ ct = ip_conntrack_get(*pskb, &ctinfo);
+ IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED
+ || ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));
+
+ mr = targinfo;
+
+ key.dst = (*pskb)->nh.iph->daddr;
+ key.src = 0; /* Unknown: that's what we're trying to establish */
+ key.tos = RT_TOS((*pskb)->nh.iph->tos)|RTO_CONN;
+ key.oif = 0;
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ key.fwmark = (*pskb)->nfmark;
+#endif
+ if (ip_route_output_key(&rt, &key) != 0) {
+ /* Funky routing can do this. */
+ if (net_ratelimit())
+ printk("MASQUERADE:"
+ " No route.\n");
+ return NF_DROP;
+ }
+ if (rt->u.dst.dev != out) {
+ if (net_ratelimit())
+ printk("MASQUERADE:"
+ " Route sent us somewhere else.\n");
+ ip_rt_put(rt);
+ return NF_DROP;
+ }
+
+ newsrc = rt->rt_src;
+ DEBUGP("newsrc = %u.%u.%u.%u\n", NIPQUAD(newsrc));
+ ip_rt_put(rt);
+
+ WRITE_LOCK(&masq_lock);
+ ct->nat.masq_index = out->ifindex;
+ WRITE_UNLOCK(&masq_lock);
+
+ /* Transfer from original range. */
+ newrange = ((struct ip_nat_multi_range)
+ { 1, { { mr->range[0].flags | IP_NAT_RANGE_MAP_IPS,
+ newsrc, newsrc,
+ mr->range[0].min, mr->range[0].max } } });
+
+ /* Hand modified range to generic setup. */
+ return ip_nat_setup_info(ct, &newrange, hooknum);
+}
+
+static inline int
+device_cmp(struct ip_conntrack *i, void *ifindex)
+{
+ int ret;
+
+ READ_LOCK(&masq_lock);
+ ret = (i->nat.masq_index == (int)(long)ifindex);
+ READ_UNLOCK(&masq_lock);
+
+ return ret;
+}
+
+static int masq_device_event(struct notifier_block *this,
+ unsigned long event,
+ void *ptr)
+{
+ struct net_device *dev = ptr;
+
+ if (event == NETDEV_DOWN) {
+ /* Device was downed. Search entire table for
+ conntracks which were associated with that device,
+ and forget them. */
+ IP_NF_ASSERT(dev->ifindex != 0);
+
+ ip_ct_iterate_cleanup(device_cmp, (void *)(long)dev->ifindex);
+ }
+
+ return NOTIFY_DONE;
+}
+
+
+static int masq_inet_event(struct notifier_block *this,
+ unsigned long event,
+ void *ptr)
+{
+ struct net_device *dev = ((struct in_ifaddr *)ptr)->ifa_dev->dev;
+
+ if (event == NETDEV_DOWN) {
+ /* IP address was deleted. Search entire table for
+ conntracks which were associated with that device,
+ and forget them. */
+ IP_NF_ASSERT(dev->ifindex != 0);
+
+ ip_ct_iterate_cleanup(device_cmp, (void *)(long)dev->ifindex);
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block masq_dev_notifier = {
+ .notifier_call = masq_device_event,
+};
+
+static struct notifier_block masq_inet_notifier = {
+ .notifier_call = masq_inet_event
+};
+
+static struct ipt_target masquerade
+= { { NULL, NULL }, "MASQUERADE", masquerade_target, masquerade_check, NULL,
+ THIS_MODULE };
+
+static int __init init(void)
+{
+ int ret;
+
+ ret = ipt_register_target(&masquerade);
+
+ if (ret == 0) {
+ /* Register for device down reports */
+ register_netdevice_notifier(&masq_dev_notifier);
+ /* Register IP address change reports */
+ register_inetaddr_notifier(&masq_inet_notifier);
+ }
+
+ return ret;
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&masquerade);
+ unregister_netdevice_notifier(&masq_dev_notifier);
+ unregister_inetaddr_notifier(&masq_inet_notifier);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_MIRROR.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_MIRROR.c
new file mode 100644
index 0000000..8f35bd3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_MIRROR.c
@@ -0,0 +1,209 @@
+/*
+ This is a module which is used for resending packets with inverted src and dst.
+
+ Based on code from: ip_nat_dumb.c,v 1.9 1999/08/20
+ and various sources.
+
+ Copyright (C) 2000 Emmanuel Roger <winfield@freegates.be>
+
+ Changes:
+ 25 Aug 2001 Harald Welte <laforge@gnumonks.org>
+ - decrement and check TTL if not called from FORWARD hook
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 2 of the License, or (at your
+ option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <net/ip.h>
+#include <net/icmp.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netdevice.h>
+#include <linux/route.h>
+#include <net/route.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+static inline struct rtable *route_mirror(struct sk_buff *skb, int local)
+{
+ struct iphdr *iph = skb->nh.iph;
+ struct dst_entry *odst;
+ struct rt_key key = {};
+ struct rtable *rt;
+
+ if (local) {
+ key.dst = iph->saddr;
+ key.src = iph->daddr;
+ key.tos = RT_TOS(iph->tos);
+
+ if (ip_route_output_key(&rt, &key) != 0)
+ return NULL;
+ } else {
+ /* non-local src, find valid iif to satisfy
+ * rp-filter when calling ip_route_input. */
+ key.dst = iph->daddr;
+ if (ip_route_output_key(&rt, &key) != 0)
+ return NULL;
+
+ odst = skb->dst;
+ if (ip_route_input(skb, iph->saddr, iph->daddr,
+ RT_TOS(iph->tos), rt->u.dst.dev) != 0) {
+ dst_release(&rt->u.dst);
+ return NULL;
+ }
+ dst_release(&rt->u.dst);
+ rt = (struct rtable *)skb->dst;
+ skb->dst = odst;
+ }
+
+ if (rt->u.dst.error) {
+ dst_release(&rt->u.dst);
+ rt = NULL;
+ }
+
+ return rt;
+}
+
+static inline void ip_rewrite(struct sk_buff *skb)
+{
+ struct iphdr *iph = skb->nh.iph;
+ u32 odaddr = iph->saddr;
+ u32 osaddr = iph->daddr;
+
+ skb->nfcache |= NFC_ALTERED;
+
+ /* Rewrite IP header */
+ iph->daddr = odaddr;
+ iph->saddr = osaddr;
+}
+
+/* Stolen from ip_finish_output2 */
+static void ip_direct_send(struct sk_buff *skb)
+{
+ struct dst_entry *dst = skb->dst;
+ struct hh_cache *hh = dst->hh;
+
+ if (hh) {
+ int hh_alen;
+
+ read_lock_bh(&hh->hh_lock);
+ hh_alen = HH_DATA_ALIGN(hh->hh_len);
+ memcpy(skb->data - hh_alen, hh->hh_data, hh_alen);
+ read_unlock_bh(&hh->hh_lock);
+ skb_push(skb, hh->hh_len);
+ hh->hh_output(skb);
+ } else if (dst->neighbour)
+ dst->neighbour->output(skb);
+ else {
+ printk(KERN_DEBUG "khm in MIRROR\n");
+ kfree_skb(skb);
+ }
+}
+
+static unsigned int ipt_mirror_target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ struct rtable *rt;
+ struct sk_buff *nskb;
+ unsigned int hh_len;
+
+ /* If we are not at FORWARD hook (INPUT/PREROUTING),
+ * the TTL isn't decreased by the IP stack */
+ if (hooknum != NF_IP_FORWARD) {
+ struct iphdr *iph = (*pskb)->nh.iph;
+ if (iph->ttl <= 1) {
+ /* this will traverse normal stack, and
+ * thus call conntrack on the icmp packet */
+ icmp_send(*pskb, ICMP_TIME_EXCEEDED,
+ ICMP_EXC_TTL, 0);
+ return NF_DROP;
+ }
+ ip_decrease_ttl(iph);
+ }
+
+ if ((rt = route_mirror(*pskb, hooknum == NF_IP_LOCAL_IN)) == NULL)
+ return NF_DROP;
+
+ hh_len = (rt->u.dst.dev->hard_header_len + 15) & ~15;
+
+ /* Copy skb (even if skb is about to be dropped, we can't just
+ * clone it because there may be other things, such as tcpdump,
+ * interested in it). We also need to expand headroom in case
+ * hh_len of incoming interface < hh_len of outgoing interface */
+ nskb = skb_copy_expand(*pskb, hh_len, skb_tailroom(*pskb), GFP_ATOMIC);
+ if (nskb == NULL) {
+ dst_release(&rt->u.dst);
+ return NF_DROP;
+ }
+
+ dst_release(nskb->dst);
+ nskb->dst = &rt->u.dst;
+
+ ip_rewrite(nskb);
+ /* Don't let conntrack code see this packet:
+ it will think we are starting a new
+ connection! --RR */
+ ip_direct_send(nskb);
+
+ return NF_DROP;
+}
+
+static int ipt_mirror_checkentry(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ /* Only on INPUT, FORWARD or PRE_ROUTING, otherwise loop danger. */
+ if (hook_mask & ~((1 << NF_IP_PRE_ROUTING)
+ | (1 << NF_IP_FORWARD)
+ | (1 << NF_IP_LOCAL_IN))) {
+ DEBUGP("MIRROR: bad hook\n");
+ return 0;
+ }
+
+ if (targinfosize != IPT_ALIGN(0)) {
+ DEBUGP("MIRROR: targinfosize %u != 0\n", targinfosize);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ipt_target ipt_mirror_reg
+= { { NULL, NULL }, "MIRROR", ipt_mirror_target, ipt_mirror_checkentry, NULL,
+ THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_target(&ipt_mirror_reg);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&ipt_mirror_reg);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_NETMAP.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_NETMAP.c
new file mode 100644
index 0000000..925adc9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_NETMAP.c
@@ -0,0 +1,109 @@
+/* NETMAP - static NAT mapping of IP network addresses (1:1).
+ The mapping can be applied to source (POSTROUTING),
+ destination (PREROUTING), or both (with separate rules).
+
+ Author: Svenning Soerensen <svenning@post5.tele.dk>
+*/
+
+#include <linux/config.h>
+#include <linux/ip.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+
+#define MODULENAME "NETMAP"
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Svenning Soerensen <svenning@post5.tele.dk>");
+MODULE_DESCRIPTION("iptables 1:1 NAT mapping of IP networks target");
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+static int
+check(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ const struct ip_nat_multi_range *mr = targinfo;
+
+ if (strcmp(tablename, "nat") != 0) {
+ DEBUGP(MODULENAME":check: bad table `%s'.\n", tablename);
+ return 0;
+ }
+ if (targinfosize != IPT_ALIGN(sizeof(*mr))) {
+ DEBUGP(MODULENAME":check: size %u.\n", targinfosize);
+ return 0;
+ }
+ if (hook_mask & ~((1 << NF_IP_PRE_ROUTING) | (1 << NF_IP_POST_ROUTING))) {
+ DEBUGP(MODULENAME":check: bad hooks %x.\n", hook_mask);
+ return 0;
+ }
+ if (!(mr->range[0].flags & IP_NAT_RANGE_MAP_IPS)) {
+ DEBUGP(MODULENAME":check: bad MAP_IPS.\n");
+ return 0;
+ }
+ if (mr->rangesize != 1) {
+ DEBUGP(MODULENAME":check: bad rangesize %u.\n", mr->rangesize);
+ return 0;
+ }
+ return 1;
+}
+
+static unsigned int
+target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ struct ip_conntrack *ct;
+ enum ip_conntrack_info ctinfo;
+ u_int32_t new_ip, netmask;
+ const struct ip_nat_multi_range *mr = targinfo;
+ struct ip_nat_multi_range newrange;
+
+ IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING
+ || hooknum == NF_IP_POST_ROUTING);
+ ct = ip_conntrack_get(*pskb, &ctinfo);
+
+ netmask = ~(mr->range[0].min_ip ^ mr->range[0].max_ip);
+
+ if (hooknum == NF_IP_PRE_ROUTING)
+ new_ip = (*pskb)->nh.iph->daddr & ~netmask;
+ else
+ new_ip = (*pskb)->nh.iph->saddr & ~netmask;
+ new_ip |= mr->range[0].min_ip & netmask;
+
+ newrange = ((struct ip_nat_multi_range)
+ { 1, { { mr->range[0].flags | IP_NAT_RANGE_MAP_IPS,
+ new_ip, new_ip,
+ mr->range[0].min, mr->range[0].max } } });
+
+ /* Hand modified range to generic setup. */
+ return ip_nat_setup_info(ct, &newrange, hooknum);
+}
+
+static struct ipt_target target_module
+= { { NULL, NULL }, MODULENAME, target, check, NULL,
+ THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_target(&target_module);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&target_module);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_REDIRECT.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_REDIRECT.c
new file mode 100644
index 0000000..c0d6d15
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_REDIRECT.c
@@ -0,0 +1,115 @@
+/* Redirect. Simple mapping which alters dst to a local IP address. */
+#include <linux/types.h>
+#include <linux/ip.h>
+#include <linux/timer.h>
+#include <linux/module.h>
+#include <linux/netfilter.h>
+#include <linux/netdevice.h>
+#include <linux/if.h>
+#include <linux/inetdevice.h>
+#include <net/protocol.h>
+#include <net/checksum.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/netfilter_ipv4/ip_nat_rule.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+/* FIXME: Take multiple ranges --RR */
+static int
+redirect_check(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ const struct ip_nat_multi_range *mr = targinfo;
+
+ if (strcmp(tablename, "nat") != 0) {
+ DEBUGP("redirect_check: bad table `%s'.\n", table);
+ return 0;
+ }
+ if (targinfosize != IPT_ALIGN(sizeof(*mr))) {
+ DEBUGP("redirect_check: size %u.\n", targinfosize);
+ return 0;
+ }
+ if (hook_mask & ~((1 << NF_IP_PRE_ROUTING) | (1 << NF_IP_LOCAL_OUT))) {
+ DEBUGP("redirect_check: bad hooks %x.\n", hook_mask);
+ return 0;
+ }
+ if (mr->range[0].flags & IP_NAT_RANGE_MAP_IPS) {
+ DEBUGP("redirect_check: bad MAP_IPS.\n");
+ return 0;
+ }
+ if (mr->rangesize != 1) {
+ DEBUGP("redirect_check: bad rangesize %u.\n", mr->rangesize);
+ return 0;
+ }
+ return 1;
+}
+
+static unsigned int
+redirect_target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ struct ip_conntrack *ct;
+ enum ip_conntrack_info ctinfo;
+ u_int32_t newdst;
+ const struct ip_nat_multi_range *mr = targinfo;
+ struct ip_nat_multi_range newrange;
+
+ IP_NF_ASSERT(hooknum == NF_IP_PRE_ROUTING
+ || hooknum == NF_IP_LOCAL_OUT);
+
+ ct = ip_conntrack_get(*pskb, &ctinfo);
+ IP_NF_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED));
+
+ /* Local packets: make them go to loopback */
+ if (hooknum == NF_IP_LOCAL_OUT)
+ newdst = htonl(0x7F000001);
+ else {
+ struct in_device *indev;
+
+ /* Device might not have an associated in_device. */
+ indev = (struct in_device *)(*pskb)->dev->ip_ptr;
+ if (indev == NULL || indev->ifa_list == NULL)
+ return NF_DROP;
+
+ /* Grab first address on interface. */
+ newdst = indev->ifa_list->ifa_local;
+ }
+
+ /* Transfer from original range. */
+ newrange = ((struct ip_nat_multi_range)
+ { 1, { { mr->range[0].flags | IP_NAT_RANGE_MAP_IPS,
+ newdst, newdst,
+ mr->range[0].min, mr->range[0].max } } });
+
+ /* Hand modified range to generic setup. */
+ return ip_nat_setup_info(ct, &newrange, hooknum);
+}
+
+static struct ipt_target redirect_reg
+= { { NULL, NULL }, "REDIRECT", redirect_target, redirect_check, NULL,
+ THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_target(&redirect_reg);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&redirect_reg);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_REJECT.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_REJECT.c
new file mode 100644
index 0000000..678c979
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_REJECT.c
@@ -0,0 +1,426 @@
+/*
+ * This is a module which is used for rejecting packets.
+ * Added support for customized reject packets (Jozsef Kadlecsik).
+ * Added support for ICMP type-3-code-13 (Maciej Soltysiak). [RFC 1812]
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+#include <linux/icmp.h>
+#include <net/icmp.h>
+#include <net/ip.h>
+#include <net/tcp.h>
+#include <net/route.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_REJECT.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+static inline struct rtable *route_reverse(struct sk_buff *skb, int hook)
+{
+ struct iphdr *iph = skb->nh.iph;
+ struct dst_entry *odst;
+ struct rt_key key = {};
+ struct rtable *rt;
+
+ if (hook != NF_IP_FORWARD) {
+ key.dst = iph->saddr;
+ if (hook == NF_IP_LOCAL_IN)
+ key.src = iph->daddr;
+ key.tos = RT_TOS(iph->tos);
+
+ if (ip_route_output_key(&rt, &key) != 0)
+ return NULL;
+ } else {
+ /* non-local src, find valid iif to satisfy
+ * rp-filter when calling ip_route_input. */
+ key.dst = iph->daddr;
+ if (ip_route_output_key(&rt, &key) != 0)
+ return NULL;
+
+ odst = skb->dst;
+ if (ip_route_input(skb, iph->saddr, iph->daddr,
+ RT_TOS(iph->tos), rt->u.dst.dev) != 0) {
+ dst_release(&rt->u.dst);
+ return NULL;
+ }
+ dst_release(&rt->u.dst);
+ rt = (struct rtable *)skb->dst;
+ skb->dst = odst;
+ }
+
+ if (rt->u.dst.error) {
+ dst_release(&rt->u.dst);
+ rt = NULL;
+ }
+
+ return rt;
+}
+
+/* Send RST reply */
+static void send_reset(struct sk_buff *oldskb, int hook)
+{
+ struct sk_buff *nskb;
+ struct tcphdr *otcph, *tcph;
+ struct rtable *rt;
+ unsigned int otcplen;
+ u_int16_t tmp_port;
+ u_int32_t tmp_addr;
+ int needs_ack;
+ int hh_len;
+
+ /* IP header checks: fragment, too short. */
+ if (oldskb->nh.iph->frag_off & htons(IP_OFFSET)
+ || oldskb->len < (oldskb->nh.iph->ihl<<2) + sizeof(struct tcphdr))
+ return;
+
+ otcph = (struct tcphdr *)((u_int32_t*)oldskb->nh.iph + oldskb->nh.iph->ihl);
+ otcplen = oldskb->len - oldskb->nh.iph->ihl*4;
+
+ /* No RST for RST. */
+ if (otcph->rst)
+ return;
+
+ /* Check checksum. */
+ if (tcp_v4_check(otcph, otcplen, oldskb->nh.iph->saddr,
+ oldskb->nh.iph->daddr,
+ csum_partial((char *)otcph, otcplen, 0)) != 0)
+ return;
+
+ if ((rt = route_reverse(oldskb, hook)) == NULL)
+ return;
+
+ hh_len = (rt->u.dst.dev->hard_header_len + 15)&~15;
+
+
+ /* Copy skb (even if skb is about to be dropped, we can't just
+ clone it because there may be other things, such as tcpdump,
+ interested in it). We also need to expand headroom in case
+ hh_len of incoming interface < hh_len of outgoing interface */
+ nskb = skb_copy_expand(oldskb, hh_len, skb_tailroom(oldskb),
+ GFP_ATOMIC);
+ if (!nskb) {
+ dst_release(&rt->u.dst);
+ return;
+ }
+
+ dst_release(nskb->dst);
+ nskb->dst = &rt->u.dst;
+
+ /* This packet will not be the same as the other: clear nf fields */
+ nf_reset(nskb);
+ nskb->nfcache = 0;
+ nskb->nfmark = 0;
+
+ tcph = (struct tcphdr *)((u_int32_t*)nskb->nh.iph + nskb->nh.iph->ihl);
+
+ /* Swap source and dest */
+ tmp_addr = nskb->nh.iph->saddr;
+ nskb->nh.iph->saddr = nskb->nh.iph->daddr;
+ nskb->nh.iph->daddr = tmp_addr;
+ tmp_port = tcph->source;
+ tcph->source = tcph->dest;
+ tcph->dest = tmp_port;
+
+ /* Truncate to length (no data) */
+ tcph->doff = sizeof(struct tcphdr)/4;
+ skb_trim(nskb, nskb->nh.iph->ihl*4 + sizeof(struct tcphdr));
+ nskb->nh.iph->tot_len = htons(nskb->len);
+
+ if (tcph->ack) {
+ needs_ack = 0;
+ tcph->seq = otcph->ack_seq;
+ tcph->ack_seq = 0;
+ } else {
+ needs_ack = 1;
+ tcph->ack_seq = htonl(ntohl(otcph->seq) + otcph->syn + otcph->fin
+ + otcplen - (otcph->doff<<2));
+ tcph->seq = 0;
+ }
+
+ /* Reset flags */
+ ((u_int8_t *)tcph)[13] = 0;
+ tcph->rst = 1;
+ tcph->ack = needs_ack;
+
+ tcph->window = 0;
+ tcph->urg_ptr = 0;
+
+ /* Adjust TCP checksum */
+ tcph->check = 0;
+ tcph->check = tcp_v4_check(tcph, sizeof(struct tcphdr),
+ nskb->nh.iph->saddr,
+ nskb->nh.iph->daddr,
+ csum_partial((char *)tcph,
+ sizeof(struct tcphdr), 0));
+
+ /* Adjust IP TTL, DF */
+ nskb->nh.iph->ttl = MAXTTL;
+ /* Set DF, id = 0 */
+ nskb->nh.iph->frag_off = htons(IP_DF);
+ nskb->nh.iph->id = 0;
+
+ /* Adjust IP checksum */
+ nskb->nh.iph->check = 0;
+ nskb->nh.iph->check = ip_fast_csum((unsigned char *)nskb->nh.iph,
+ nskb->nh.iph->ihl);
+
+ /* "Never happens" */
+ if (nskb->len > nskb->dst->pmtu)
+ goto free_nskb;
+
+ nf_ct_attach(nskb, oldskb);
+
+ NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, nskb, NULL, nskb->dst->dev,
+ ip_finish_output);
+ return;
+
+ free_nskb:
+ kfree_skb(nskb);
+}
+
+static void send_unreach(struct sk_buff *skb_in, int code)
+{
+ struct iphdr *iph;
+ struct udphdr *udph;
+ struct icmphdr *icmph;
+ struct sk_buff *nskb;
+ u32 saddr;
+ u8 tos;
+ int hh_len, length;
+ struct rtable *rt = (struct rtable*)skb_in->dst;
+ unsigned char *data;
+
+ if (!rt)
+ return;
+
+ /* FIXME: Use sysctl number. --RR */
+ if (!xrlim_allow(&rt->u.dst, 1*HZ))
+ return;
+
+ iph = skb_in->nh.iph;
+
+ /* No replies to physical multicast/broadcast */
+ if (skb_in->pkt_type!=PACKET_HOST)
+ return;
+
+ /* Now check at the protocol level */
+ if (rt->rt_flags&(RTCF_BROADCAST|RTCF_MULTICAST))
+ return;
+
+ /* Only reply to fragment 0. */
+ if (iph->frag_off&htons(IP_OFFSET))
+ return;
+
+ /* if UDP checksum is set, verify it's correct */
+ if (iph->protocol == IPPROTO_UDP
+ && skb_in->tail-(u8*)iph >= sizeof(struct udphdr)) {
+ int datalen = skb_in->len - (iph->ihl<<2);
+ udph = (struct udphdr *)((char *)iph + (iph->ihl<<2));
+ if (udph->check
+ && csum_tcpudp_magic(iph->saddr, iph->daddr,
+ datalen, IPPROTO_UDP,
+ csum_partial((char *)udph, datalen,
+ 0)) != 0)
+ return;
+ }
+
+ /* If we send an ICMP error to an ICMP error a mess would result.. */
+ if (iph->protocol == IPPROTO_ICMP
+ && skb_in->tail-(u8*)iph >= sizeof(struct icmphdr)) {
+ icmph = (struct icmphdr *)((char *)iph + (iph->ihl<<2));
+ /* Between echo-reply (0) and timestamp (13),
+ everything except echo-request (8) is an error.
+ Also, anything greater than NR_ICMP_TYPES is
+ unknown, and hence should be treated as an error... */
+ if ((icmph->type < ICMP_TIMESTAMP
+ && icmph->type != ICMP_ECHOREPLY
+ && icmph->type != ICMP_ECHO)
+ || icmph->type > NR_ICMP_TYPES)
+ return;
+ }
+
+ saddr = iph->daddr;
+ if (!(rt->rt_flags & RTCF_LOCAL))
+ saddr = 0;
+
+ tos = (iph->tos & IPTOS_TOS_MASK) | IPTOS_PREC_INTERNETCONTROL;
+
+ if (ip_route_output(&rt, iph->saddr, saddr, RT_TOS(tos), 0))
+ return;
+
+ /* RFC says return as much as we can without exceeding 576 bytes. */
+ length = skb_in->len + sizeof(struct iphdr) + sizeof(struct icmphdr);
+
+ if (length > rt->u.dst.pmtu)
+ length = rt->u.dst.pmtu;
+ if (length > 576)
+ length = 576;
+
+ hh_len = (rt->u.dst.dev->hard_header_len + 15)&~15;
+
+ nskb = alloc_skb(hh_len+15+length, GFP_ATOMIC);
+ if (!nskb) {
+ ip_rt_put(rt);
+ return;
+ }
+
+ nskb->priority = 0;
+ nskb->dst = &rt->u.dst;
+ skb_reserve(nskb, hh_len);
+
+ /* Set up IP header */
+ iph = nskb->nh.iph
+ = (struct iphdr *)skb_put(nskb, sizeof(struct iphdr));
+ iph->version=4;
+ iph->ihl=5;
+ iph->tos=tos;
+ iph->tot_len = htons(length);
+
+ /* PMTU discovery never applies to ICMP packets. */
+ iph->frag_off = 0;
+
+ iph->ttl = MAXTTL;
+ ip_select_ident(iph, &rt->u.dst, NULL);
+ iph->protocol=IPPROTO_ICMP;
+ iph->saddr=rt->rt_src;
+ iph->daddr=rt->rt_dst;
+ iph->check=0;
+ iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
+
+ /* Set up ICMP header. */
+ icmph = nskb->h.icmph
+ = (struct icmphdr *)skb_put(nskb, sizeof(struct icmphdr));
+ icmph->type = ICMP_DEST_UNREACH;
+ icmph->code = code;
+ icmph->un.gateway = 0;
+ icmph->checksum = 0;
+
+ /* Copy as much of original packet as will fit */
+ data = skb_put(nskb,
+ length - sizeof(struct iphdr) - sizeof(struct icmphdr));
+ /* FIXME: won't work with nonlinear skbs --RR */
+ memcpy(data, skb_in->nh.iph,
+ length - sizeof(struct iphdr) - sizeof(struct icmphdr));
+ icmph->checksum = ip_compute_csum((unsigned char *)icmph,
+ length - sizeof(struct iphdr));
+
+ nf_ct_attach(nskb, skb_in);
+
+ NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, nskb, NULL, nskb->dst->dev,
+ ip_finish_output);
+}
+
+static unsigned int reject(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ const struct ipt_reject_info *reject = targinfo;
+
+ /* Our naive response construction doesn't deal with IP
+ options, and probably shouldn't try. */
+ if ((*pskb)->nh.iph->ihl<<2 != sizeof(struct iphdr))
+ return NF_DROP;
+
+ /* WARNING: This code causes reentry within iptables.
+ This means that the iptables jump stack is now crap. We
+ must return an absolute verdict. --RR */
+ switch (reject->with) {
+ case IPT_ICMP_NET_UNREACHABLE:
+ send_unreach(*pskb, ICMP_NET_UNREACH);
+ break;
+ case IPT_ICMP_HOST_UNREACHABLE:
+ send_unreach(*pskb, ICMP_HOST_UNREACH);
+ break;
+ case IPT_ICMP_PROT_UNREACHABLE:
+ send_unreach(*pskb, ICMP_PROT_UNREACH);
+ break;
+ case IPT_ICMP_PORT_UNREACHABLE:
+ send_unreach(*pskb, ICMP_PORT_UNREACH);
+ break;
+ case IPT_ICMP_NET_PROHIBITED:
+ send_unreach(*pskb, ICMP_NET_ANO);
+ break;
+ case IPT_ICMP_HOST_PROHIBITED:
+ send_unreach(*pskb, ICMP_HOST_ANO);
+ break;
+ case IPT_ICMP_ADMIN_PROHIBITED:
+ send_unreach(*pskb, ICMP_PKT_FILTERED);
+ break;
+ case IPT_TCP_RESET:
+ send_reset(*pskb, hooknum);
+ case IPT_ICMP_ECHOREPLY:
+ /* Doesn't happen. */
+ break;
+ }
+
+ return NF_DROP;
+}
+
+static int check(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ const struct ipt_reject_info *rejinfo = targinfo;
+
+ if (targinfosize != IPT_ALIGN(sizeof(struct ipt_reject_info))) {
+ DEBUGP("REJECT: targinfosize %u != 0\n", targinfosize);
+ return 0;
+ }
+
+ /* Only allow these for packet filtering. */
+ if (strcmp(tablename, "filter") != 0) {
+ DEBUGP("REJECT: bad table `%s'.\n", tablename);
+ return 0;
+ }
+ if ((hook_mask & ~((1 << NF_IP_LOCAL_IN)
+ | (1 << NF_IP_FORWARD)
+ | (1 << NF_IP_LOCAL_OUT))) != 0) {
+ DEBUGP("REJECT: bad hook mask %X\n", hook_mask);
+ return 0;
+ }
+
+ if (rejinfo->with == IPT_ICMP_ECHOREPLY) {
+ printk("REJECT: ECHOREPLY no longer supported.\n");
+ return 0;
+ } else if (rejinfo->with == IPT_TCP_RESET) {
+ /* Must specify that it's a TCP packet */
+ if (e->ip.proto != IPPROTO_TCP
+ || (e->ip.invflags & IPT_INV_PROTO)) {
+ DEBUGP("REJECT: TCP_RESET illegal for non-tcp\n");
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static struct ipt_target ipt_reject_reg
+= { { NULL, NULL }, "REJECT", reject, check, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ if (ipt_register_target(&ipt_reject_reg))
+ return -EINVAL;
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&ipt_reject_reg);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_TCPMSS.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_TCPMSS.c
new file mode 100644
index 0000000..ca55ca2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_TCPMSS.c
@@ -0,0 +1,269 @@
+/*
+ * This is a module which is used for setting the MSS option in TCP packets.
+ *
+ * Copyright (c) 2000 Marc Boucher
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+
+#include <linux/ip.h>
+#include <net/tcp.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_TCPMSS.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+static u_int16_t
+cheat_check(u_int32_t oldvalinv, u_int32_t newval, u_int16_t oldcheck)
+{
+ u_int32_t diffs[] = { oldvalinv, newval };
+ return csum_fold(csum_partial((char *)diffs, sizeof(diffs),
+ oldcheck^0xFFFF));
+}
+
+static inline unsigned int
+optlen(const u_int8_t *opt, unsigned int offset)
+{
+ /* Beware zero-length options: make finite progress */
+ if (opt[offset] <= TCPOPT_NOP || opt[offset+1] == 0) return 1;
+ else return opt[offset+1];
+}
+
+static unsigned int
+ipt_tcpmss_target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ const struct ipt_tcpmss_info *tcpmssinfo = targinfo;
+ struct tcphdr *tcph;
+ struct iphdr *iph;
+ u_int16_t tcplen, newtotlen, oldval, newmss, mtu;
+ unsigned int i;
+ u_int8_t *opt;
+
+ /* raw socket (tcpdump) may have clone of incoming skb: don't
+ disturb it --RR */
+ if (skb_cloned(*pskb) && !(*pskb)->sk) {
+ struct sk_buff *nskb = skb_copy(*pskb, GFP_ATOMIC);
+ if (!nskb)
+ return NF_DROP;
+ kfree_skb(*pskb);
+ *pskb = nskb;
+ }
+
+ iph = (*pskb)->nh.iph;
+ tcplen = (*pskb)->len - iph->ihl*4;
+
+ tcph = (void *)iph + iph->ihl*4;
+
+ /* Since it passed flags test in tcp match, we know it is is
+ not a fragment, and has data >= tcp header length. SYN
+ packets should not contain data: if they did, then we risk
+ running over MTU, sending Frag Needed and breaking things
+ badly. --RR */
+ if (tcplen != tcph->doff*4) {
+ if (net_ratelimit())
+ printk(KERN_ERR
+ "ipt_tcpmss_target: bad length (%d bytes)\n",
+ (*pskb)->len);
+ return NF_DROP;
+ }
+
+ if(tcpmssinfo->mss == IPT_TCPMSS_CLAMP_PMTU) {
+ if(!(*pskb)->dst) {
+ if (net_ratelimit())
+ printk(KERN_ERR
+ "ipt_tcpmss_target: no dst?! can't determine path-MTU\n");
+ return NF_DROP; /* or IPT_CONTINUE ?? */
+ }
+
+ if((*pskb)->dst->pmtu <= (sizeof(struct iphdr) + sizeof(struct tcphdr))) {
+ if (net_ratelimit())
+ printk(KERN_ERR
+ "ipt_tcpmss_target: unknown or invalid path-MTU (%d)\n", (*pskb)->dst->pmtu);
+ return NF_DROP; /* or IPT_CONTINUE ?? */
+ }
+ mtu = (*pskb)->dst->pmtu;
+
+ if (in) {
+ if (in->mtu <= (sizeof(struct iphdr) + sizeof(struct tcphdr))) {
+ if (net_ratelimit())
+ printk(KERN_ERR
+ "ipt_tcpmss_target: invalid interface MTU (%d)\n", in->mtu);
+ return NF_DROP; /* or IPT_CONTINUE ?? */
+ }
+ if (in->mtu < mtu)
+ mtu = in->mtu;
+ }
+
+ newmss = mtu - sizeof(struct iphdr) - sizeof(struct tcphdr);
+ } else
+ newmss = tcpmssinfo->mss;
+
+ opt = (u_int8_t *)tcph;
+ for (i = sizeof(struct tcphdr); i < tcph->doff*4; i += optlen(opt, i)){
+ if ((opt[i] == TCPOPT_MSS) &&
+ ((tcph->doff*4 - i) >= TCPOLEN_MSS) &&
+ (opt[i+1] == TCPOLEN_MSS)) {
+ u_int16_t oldmss;
+
+ oldmss = (opt[i+2] << 8) | opt[i+3];
+
+ if((tcpmssinfo->mss == IPT_TCPMSS_CLAMP_PMTU) &&
+ (oldmss <= newmss))
+ return IPT_CONTINUE;
+
+ opt[i+2] = (newmss & 0xff00) >> 8;
+ opt[i+3] = (newmss & 0x00ff);
+
+ tcph->check = cheat_check(htons(oldmss)^0xFFFF,
+ htons(newmss),
+ tcph->check);
+
+ DEBUGP(KERN_INFO "ipt_tcpmss_target: %u.%u.%u.%u:%hu"
+ "->%u.%u.%u.%u:%hu changed TCP MSS option"
+ " (from %u to %u)\n",
+ NIPQUAD((*pskb)->nh.iph->saddr),
+ ntohs(tcph->source),
+ NIPQUAD((*pskb)->nh.iph->daddr),
+ ntohs(tcph->dest),
+ oldmss, newmss);
+ goto retmodified;
+ }
+ }
+
+ /*
+ * MSS Option not found ?! add it..
+ */
+ if (skb_tailroom((*pskb)) < TCPOLEN_MSS) {
+ struct sk_buff *newskb;
+
+ newskb = skb_copy_expand(*pskb, skb_headroom(*pskb),
+ TCPOLEN_MSS, GFP_ATOMIC);
+ if (!newskb) {
+ if (net_ratelimit())
+ printk(KERN_ERR "ipt_tcpmss_target:"
+ " unable to allocate larger skb\n");
+ return NF_DROP;
+ }
+
+ kfree_skb(*pskb);
+ *pskb = newskb;
+ iph = (*pskb)->nh.iph;
+ tcph = (void *)iph + iph->ihl*4;
+ }
+
+ skb_put((*pskb), TCPOLEN_MSS);
+
+ opt = (u_int8_t *)tcph + sizeof(struct tcphdr);
+ memmove(opt + TCPOLEN_MSS, opt, tcplen - sizeof(struct tcphdr));
+
+ tcph->check = cheat_check(htons(tcplen) ^ 0xFFFF,
+ htons(tcplen + TCPOLEN_MSS), tcph->check);
+ tcplen += TCPOLEN_MSS;
+
+ opt[0] = TCPOPT_MSS;
+ opt[1] = TCPOLEN_MSS;
+ opt[2] = (newmss & 0xff00) >> 8;
+ opt[3] = (newmss & 0x00ff);
+
+ tcph->check = cheat_check(~0, *((u_int32_t *)opt), tcph->check);
+
+ oldval = ((u_int16_t *)tcph)[6];
+ tcph->doff += TCPOLEN_MSS/4;
+ tcph->check = cheat_check(oldval ^ 0xFFFF,
+ ((u_int16_t *)tcph)[6], tcph->check);
+
+ newtotlen = htons(ntohs(iph->tot_len) + TCPOLEN_MSS);
+ iph->check = cheat_check(iph->tot_len ^ 0xFFFF,
+ newtotlen, iph->check);
+ iph->tot_len = newtotlen;
+
+ DEBUGP(KERN_INFO "ipt_tcpmss_target: %u.%u.%u.%u:%hu"
+ "->%u.%u.%u.%u:%hu added TCP MSS option (%u)\n",
+ NIPQUAD((*pskb)->nh.iph->saddr),
+ ntohs(tcph->source),
+ NIPQUAD((*pskb)->nh.iph->daddr),
+ ntohs(tcph->dest),
+ newmss);
+
+ retmodified:
+ /* If we had a hardware checksum before, it's now invalid */
+ (*pskb)->ip_summed = CHECKSUM_NONE;
+ (*pskb)->nfcache |= NFC_UNKNOWN | NFC_ALTERED;
+ return IPT_CONTINUE;
+}
+
+#define TH_SYN 0x02
+
+static inline int find_syn_match(const struct ipt_entry_match *m)
+{
+ const struct ipt_tcp *tcpinfo = (const struct ipt_tcp *)m->data;
+
+ if (strcmp(m->u.kernel.match->name, "tcp") == 0
+ && (tcpinfo->flg_cmp & TH_SYN)
+ && !(tcpinfo->invflags & IPT_TCP_INV_FLAGS))
+ return 1;
+
+ return 0;
+}
+
+/* Must specify -p tcp --syn/--tcp-flags SYN */
+static int
+ipt_tcpmss_checkentry(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ const struct ipt_tcpmss_info *tcpmssinfo = targinfo;
+
+ if (targinfosize != IPT_ALIGN(sizeof(struct ipt_tcpmss_info))) {
+ DEBUGP("ipt_tcpmss_checkentry: targinfosize %u != %u\n",
+ targinfosize, IPT_ALIGN(sizeof(struct ipt_tcpmss_info)));
+ return 0;
+ }
+
+
+ if((tcpmssinfo->mss == IPT_TCPMSS_CLAMP_PMTU) &&
+ ((hook_mask & ~((1 << NF_IP_FORWARD)
+ | (1 << NF_IP_LOCAL_OUT)
+ | (1 << NF_IP_POST_ROUTING))) != 0)) {
+ printk("TCPMSS: path-MTU clamping only supported in FORWARD, OUTPUT and POSTROUTING hooks\n");
+ return 0;
+ }
+
+ if (e->ip.proto == IPPROTO_TCP
+ && !(e->ip.invflags & IPT_INV_PROTO)
+ && IPT_MATCH_ITERATE(e, find_syn_match))
+ return 1;
+
+ printk("TCPMSS: Only works on TCP SYN packets\n");
+ return 0;
+}
+
+static struct ipt_target ipt_tcpmss_reg
+= { { NULL, NULL }, "TCPMSS",
+ ipt_tcpmss_target, ipt_tcpmss_checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_target(&ipt_tcpmss_reg);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&ipt_tcpmss_reg);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_TOS.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_TOS.c
new file mode 100644
index 0000000..90d7173
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_TOS.c
@@ -0,0 +1,97 @@
+/* This is a module which is used for setting the TOS field of a packet. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <net/checksum.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_TOS.h>
+
+static unsigned int
+target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ struct iphdr *iph = (*pskb)->nh.iph;
+ const struct ipt_tos_target_info *tosinfo = targinfo;
+
+ if ((iph->tos & IPTOS_TOS_MASK) != tosinfo->tos) {
+ u_int16_t diffs[2];
+
+ /* raw socket (tcpdump) may have clone of incoming
+ skb: don't disturb it --RR */
+ if (skb_cloned(*pskb) && !(*pskb)->sk) {
+ struct sk_buff *nskb = skb_copy(*pskb, GFP_ATOMIC);
+ if (!nskb)
+ return NF_DROP;
+ kfree_skb(*pskb);
+ *pskb = nskb;
+ iph = (*pskb)->nh.iph;
+ }
+
+ diffs[0] = htons(iph->tos) ^ 0xFFFF;
+ iph->tos = (iph->tos & IPTOS_PREC_MASK) | tosinfo->tos;
+ diffs[1] = htons(iph->tos);
+ iph->check = csum_fold(csum_partial((char *)diffs,
+ sizeof(diffs),
+ iph->check^0xFFFF));
+ (*pskb)->nfcache |= NFC_ALTERED;
+ }
+ return IPT_CONTINUE;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ const u_int8_t tos = ((struct ipt_tos_target_info *)targinfo)->tos;
+
+ if (targinfosize != IPT_ALIGN(sizeof(struct ipt_tos_target_info))) {
+ printk(KERN_WARNING "TOS: targinfosize %u != %Zu\n",
+ targinfosize,
+ IPT_ALIGN(sizeof(struct ipt_tos_target_info)));
+ return 0;
+ }
+
+ if (strcmp(tablename, "mangle") != 0) {
+ printk(KERN_WARNING "TOS: can only be called from \"mangle\" table, not \"%s\"\n", tablename);
+ return 0;
+ }
+
+ if (tos != IPTOS_LOWDELAY
+ && tos != IPTOS_THROUGHPUT
+ && tos != IPTOS_RELIABILITY
+ && tos != IPTOS_MINCOST
+ && tos != IPTOS_NORMALSVC) {
+ printk(KERN_WARNING "TOS: bad tos value %#x\n", tos);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ipt_target ipt_tos_reg
+= { { NULL, NULL }, "TOS", target, checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ if (ipt_register_target(&ipt_tos_reg))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_target(&ipt_tos_reg);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ULOG.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ULOG.c
new file mode 100644
index 0000000..cde628f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ULOG.c
@@ -0,0 +1,371 @@
+/*
+ * netfilter module for userspace packet logging daemons
+ *
+ * (C) 2000-2004 by Harald Welte <laforge@netfilter.org>
+ *
+ * 2000/09/22 ulog-cprange feature added
+ * 2001/01/04 in-kernel queue as proposed by Sebastian Zander
+ * <zander@fokus.gmd.de>
+ * 2001/01/30 per-rule nlgroup conflicts with global queue.
+ * nlgroup now global (sysctl)
+ * 2001/04/19 ulog-queue reworked, now fixed buffer size specified at
+ * module loadtime -HW
+ * 2002/07/07 remove broken nflog_rcv() function -HW
+ * 2002/08/29 fix shifted/unshifted nlgroup bug -HW
+ * 2002/10/30 fix uninitialized mac_len field - <Anders K. Pedersen>
+ * 2004/10/25 fix erroneous calculation of 'len' parameter to NLMSG_PUT
+ * resulting in bogus 'error during NLMSG_PUT' messages.
+ *
+ * Released under the terms of the GPL
+ *
+ * This module accepts two parameters:
+ *
+ * nlbufsiz:
+ * The parameter specifies how big the buffer for each netlink multicast
+ * group is. e.g. If you say nlbufsiz=8192, up to eight kb of packets will
+ * get accumulated in the kernel until they are sent to userspace. It is
+ * NOT possible to allocate more than 128kB, and it is strongly discouraged,
+ * because atomically allocating 128kB inside the network rx softirq is not
+ * reliable. Please also keep in mind that this buffer size is allocated for
+ * each nlgroup you are using, so the total kernel memory usage increases
+ * by that factor.
+ *
+ * flushtimeout:
+ * Specify, after how many clock ticks (intel: 100 per second) the queue
+ * should be flushed even if it is not full yet.
+ *
+ * ipt_ULOG.c,v 1.22 2002/10/30 09:07:31 laforge Exp
+ */
+
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/spinlock.h>
+#include <linux/socket.h>
+#include <linux/skbuff.h>
+#include <linux/kernel.h>
+#include <linux/timer.h>
+#include <linux/netlink.h>
+#include <linux/netdevice.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_ULOG.h>
+#include <linux/netfilter_ipv4/lockhelp.h>
+#include <net/sock.h>
+#include <linux/bitops.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("IP tables userspace logging module");
+
+#define ULOG_NL_EVENT 111 /* Harald's favorite number */
+#define ULOG_MAXNLGROUPS 32 /* numer of nlgroups */
+
+#if 0
+#define DEBUGP(format, args...) printk(__FILE__ ":" __FUNCTION__ ":" \
+ format, ## args)
+#else
+#define DEBUGP(format, args...)
+#endif
+
+#define PRINTR(format, args...) do { if (net_ratelimit()) printk(format, ## args); } while (0)
+
+static unsigned int nlbufsiz = 4096;
+MODULE_PARM(nlbufsiz, "i");
+MODULE_PARM_DESC(nlbufsiz, "netlink buffer size");
+
+static unsigned int flushtimeout = 10 * HZ;
+MODULE_PARM(flushtimeout, "i");
+MODULE_PARM_DESC(flushtimeout, "buffer flush timeout");
+
+/* global data structures */
+
+typedef struct {
+ unsigned int qlen; /* number of nlmsgs' in the skb */
+ struct nlmsghdr *lastnlh; /* netlink header of last msg in skb */
+ struct sk_buff *skb; /* the pre-allocated skb */
+ struct timer_list timer; /* the timer function */
+} ulog_buff_t;
+
+static ulog_buff_t ulog_buffers[ULOG_MAXNLGROUPS]; /* array of buffers */
+
+static struct sock *nflognl; /* our socket */
+DECLARE_LOCK(ulog_lock); /* spinlock */
+
+/* send one ulog_buff_t to userspace */
+static void ulog_send(unsigned int nlgroupnum)
+{
+ ulog_buff_t *ub = &ulog_buffers[nlgroupnum];
+
+ if (timer_pending(&ub->timer)) {
+ DEBUGP("ipt_ULOG: ulog_send: timer was pending, deleting\n");
+ del_timer(&ub->timer);
+ }
+
+ /* last nlmsg needs NLMSG_DONE */
+ if (ub->qlen > 1)
+ ub->lastnlh->nlmsg_type = NLMSG_DONE;
+
+ NETLINK_CB(ub->skb).dst_groups = (1 << nlgroupnum);
+ DEBUGP("ipt_ULOG: throwing %d packets to netlink mask %u\n",
+ ub->qlen, nlgroupnum);
+ netlink_broadcast(nflognl, ub->skb, 0, (1 << nlgroupnum), GFP_ATOMIC);
+
+ ub->qlen = 0;
+ ub->skb = NULL;
+ ub->lastnlh = NULL;
+
+}
+
+
+/* timer function to flush queue in flushtimeout time */
+static void ulog_timer(unsigned long data)
+{
+ DEBUGP("ipt_ULOG: timer function called, calling ulog_send\n");
+
+ /* lock to protect against somebody modifying our structure
+ * from ipt_ulog_target at the same time */
+ LOCK_BH(&ulog_lock);
+ ulog_send(data);
+ UNLOCK_BH(&ulog_lock);
+}
+
+struct sk_buff *ulog_alloc_skb(unsigned int size)
+{
+ struct sk_buff *skb;
+
+ /* alloc skb which should be big enough for a whole
+ * multipart message. WARNING: has to be <= 131000
+ * due to slab allocator restrictions */
+
+ skb = alloc_skb(nlbufsiz, GFP_ATOMIC);
+ if (!skb) {
+ PRINTR("ipt_ULOG: can't alloc whole buffer %ub!\n",
+ nlbufsiz);
+
+ /* try to allocate only as much as we need for
+ * current packet */
+
+ skb = alloc_skb(size, GFP_ATOMIC);
+ if (!skb)
+ PRINTR("ipt_ULOG: can't even allocate %ub\n", size);
+ }
+
+ return skb;
+}
+
+static unsigned int ipt_ulog_target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo, void *userinfo)
+{
+ ulog_buff_t *ub;
+ ulog_packet_msg_t *pm;
+ size_t size, copy_len;
+ struct nlmsghdr *nlh;
+ struct ipt_ulog_info *loginfo = (struct ipt_ulog_info *) targinfo;
+
+ /* ffs == find first bit set, necessary because userspace
+ * is already shifting groupnumber, but we need unshifted.
+ * ffs() returns [1..32], we need [0..31] */
+ unsigned int groupnum = ffs(loginfo->nl_group) - 1;
+
+ /* calculate the size of the skb needed */
+ if ((loginfo->copy_range == 0) ||
+ (loginfo->copy_range > (*pskb)->len)) {
+ copy_len = (*pskb)->len;
+ } else {
+ copy_len = loginfo->copy_range;
+ }
+
+ size = NLMSG_SPACE(sizeof(*pm) + copy_len);
+
+ ub = &ulog_buffers[groupnum];
+
+ LOCK_BH(&ulog_lock);
+
+ if (!ub->skb) {
+ if (!(ub->skb = ulog_alloc_skb(size)))
+ goto alloc_failure;
+ } else if (ub->qlen >= loginfo->qthreshold ||
+ size > skb_tailroom(ub->skb)) {
+ /* either the queue len is too high or we don't have
+ * enough room in nlskb left. send it to userspace. */
+
+ ulog_send(groupnum);
+
+ if (!(ub->skb = ulog_alloc_skb(size)))
+ goto alloc_failure;
+ }
+
+ DEBUGP("ipt_ULOG: qlen %d, qthreshold %d\n", ub->qlen,
+ loginfo->qthreshold);
+
+ /* NLMSG_PUT contains a hidden goto nlmsg_failure !!! */
+ nlh = NLMSG_PUT(ub->skb, 0, ub->qlen, ULOG_NL_EVENT,
+ sizeof(*pm)+copy_len);
+ ub->qlen++;
+
+ pm = NLMSG_DATA(nlh);
+
+ /* copy hook, prefix, timestamp, payload, etc. */
+ pm->data_len = copy_len;
+ pm->timestamp_sec = (*pskb)->stamp.tv_sec;
+ pm->timestamp_usec = (*pskb)->stamp.tv_usec;
+ pm->mark = (*pskb)->nfmark;
+ pm->hook = hooknum;
+ if (loginfo->prefix[0] != '\0')
+ strncpy(pm->prefix, loginfo->prefix, sizeof(pm->prefix));
+ else
+ *(pm->prefix) = '\0';
+
+ if (in && in->hard_header_len > 0
+ && (*pskb)->mac.raw != (void *) (*pskb)->nh.iph
+ && in->hard_header_len <= ULOG_MAC_LEN) {
+ memcpy(pm->mac, (*pskb)->mac.raw, in->hard_header_len);
+ pm->mac_len = in->hard_header_len;
+ } else
+ pm->mac_len = 0;
+
+ if (in)
+ strncpy(pm->indev_name, in->name, sizeof(pm->indev_name));
+ else
+ pm->indev_name[0] = '\0';
+
+ if (out)
+ strncpy(pm->outdev_name, out->name, sizeof(pm->outdev_name));
+ else
+ pm->outdev_name[0] = '\0';
+
+ if (copy_len)
+ memcpy(pm->payload, (*pskb)->data, copy_len);
+
+ /* check if we are building multi-part messages */
+ if (ub->qlen > 1) {
+ ub->lastnlh->nlmsg_flags |= NLM_F_MULTI;
+ }
+
+ ub->lastnlh = nlh;
+
+ /* if timer isn't already running, start it */
+ if (!timer_pending(&ub->timer)) {
+ ub->timer.expires = jiffies + flushtimeout;
+ add_timer(&ub->timer);
+ }
+
+ /* if threshold is reached, send message to userspace */
+ if (ub->qlen >= loginfo->qthreshold) {
+ if (loginfo->qthreshold > 1)
+ nlh->nlmsg_type = NLMSG_DONE;
+ ulog_send(groupnum);
+ }
+
+ UNLOCK_BH(&ulog_lock);
+
+ return IPT_CONTINUE;
+
+
+nlmsg_failure:
+ PRINTR("ipt_ULOG: error during NLMSG_PUT\n");
+
+alloc_failure:
+ PRINTR("ipt_ULOG: Error building netlink message\n");
+
+ UNLOCK_BH(&ulog_lock);
+
+ return IPT_CONTINUE;
+}
+
+static int ipt_ulog_checkentry(const char *tablename,
+ const struct ipt_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hookmask)
+{
+ struct ipt_ulog_info *loginfo = (struct ipt_ulog_info *) targinfo;
+
+ if (targinfosize != IPT_ALIGN(sizeof(struct ipt_ulog_info))) {
+ DEBUGP("ipt_ULOG: targinfosize %u != 0\n", targinfosize);
+ return 0;
+ }
+
+ if (loginfo->prefix[sizeof(loginfo->prefix) - 1] != '\0') {
+ DEBUGP("ipt_ULOG: prefix term %i\n",
+ loginfo->prefix[sizeof(loginfo->prefix) - 1]);
+ return 0;
+ }
+
+ if (loginfo->qthreshold > ULOG_MAX_QLEN) {
+ DEBUGP("ipt_ULOG: queue threshold %i > MAX_QLEN\n",
+ loginfo->qthreshold);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ipt_target ipt_ulog_reg =
+ { {NULL, NULL}, "ULOG", ipt_ulog_target, ipt_ulog_checkentry, NULL,
+THIS_MODULE
+};
+
+static int __init init(void)
+{
+ int i;
+
+ DEBUGP("ipt_ULOG: init module\n");
+
+ if (nlbufsiz >= 128*1024) {
+ printk("Netlink buffer has to be <= 128kB\n");
+ return -EINVAL;
+ }
+
+ /* initialize ulog_buffers */
+ for (i = 0; i < ULOG_MAXNLGROUPS; i++) {
+ init_timer(&ulog_buffers[i].timer);
+ ulog_buffers[i].timer.function = ulog_timer;
+ ulog_buffers[i].timer.data = i;
+ }
+
+ nflognl = netlink_kernel_create(NETLINK_NFLOG, NULL);
+ if (!nflognl)
+ return -ENOMEM;
+
+ if (ipt_register_target(&ipt_ulog_reg) != 0) {
+ sock_release(nflognl->socket);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ulog_buff_t *ub;
+ int i;
+
+ DEBUGP("ipt_ULOG: cleanup_module\n");
+
+ ipt_unregister_target(&ipt_ulog_reg);
+ sock_release(nflognl->socket);
+
+ /* remove pending timers and free allocated skb's */
+ for (i = 0; i < ULOG_MAXNLGROUPS; i++) {
+ ub = &ulog_buffers[i];
+ if (timer_pending(&ub->timer)) {
+ DEBUGP("timer was pending, deleting\n");
+ del_timer(&ub->timer);
+ }
+
+ if (ub->skb) {
+ kfree_skb(ub->skb);
+ ub->skb = NULL;
+ }
+ }
+
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ah.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ah.c
new file mode 100644
index 0000000..a5cd83f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ah.c
@@ -0,0 +1,109 @@
+/* Kernel module to match AH parameters. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+
+#include <linux/netfilter_ipv4/ipt_ah.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
+
+#ifdef DEBUG_CONNTRACK
+#define duprintf(format, args...) printk(format , ## args)
+#else
+#define duprintf(format, args...)
+#endif
+
+struct ahhdr {
+ __u8 nexthdr;
+ __u8 hdrlen;
+ __u16 reserved;
+ __u32 spi;
+ __u32 seq_no;
+};
+
+/* Returns 1 if the spi is matched by the range, 0 otherwise */
+static inline int
+spi_match(u_int32_t min, u_int32_t max, u_int32_t spi, int invert)
+{
+ int r=0;
+ duprintf("ah spi_match:%c 0x%x <= 0x%x <= 0x%x",invert? '!':' ',
+ min,spi,max);
+ r=(spi >= min && spi <= max) ^ invert;
+ duprintf(" result %s\n",r? "PASS" : "FAILED");
+ return r;
+}
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ahhdr *ah = hdr;
+ const struct ipt_ah *ahinfo = matchinfo;
+
+ if (offset == 0 && datalen < sizeof(struct ahhdr)) {
+ /* We've been asked to examine this packet, and we
+ can't. Hence, no choice but to drop. */
+ duprintf("Dropping evil AH tinygram.\n");
+ *hotdrop = 1;
+ return 0;
+ }
+
+ /* Must not be a fragment. */
+ return !offset
+ && spi_match(ahinfo->spis[0], ahinfo->spis[1],
+ ntohl(ah->spi),
+ !!(ahinfo->invflags & IPT_AH_INV_SPI));
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchinfosize,
+ unsigned int hook_mask)
+{
+ const struct ipt_ah *ahinfo = matchinfo;
+
+ /* Must specify proto == AH, and no unknown invflags */
+ if (ip->proto != IPPROTO_AH || (ip->invflags & IPT_INV_PROTO)) {
+ duprintf("ipt_ah: Protocol %u != %u\n", ip->proto,
+ IPPROTO_AH);
+ return 0;
+ }
+ if (matchinfosize != IPT_ALIGN(sizeof(struct ipt_ah))) {
+ duprintf("ipt_ah: matchsize %u != %u\n",
+ matchinfosize, IPT_ALIGN(sizeof(struct ipt_ah)));
+ return 0;
+ }
+ if (ahinfo->invflags & ~IPT_AH_INV_MASK) {
+ duprintf("ipt_ah: unknown flags %X\n",
+ ahinfo->invflags);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ipt_match ah_match
+= { { NULL, NULL }, "ah", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&ah_match);
+}
+
+static void __exit cleanup(void)
+{
+ ipt_unregister_match(&ah_match);
+}
+
+module_init(init);
+module_exit(cleanup);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_connmark.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_connmark.c
new file mode 100644
index 0000000..43ace06
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_connmark.c
@@ -0,0 +1,55 @@
+/* Kernel module to match connection mark values. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_connmark.h>
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_connmark_info *info = matchinfo;
+ enum ip_conntrack_info ctinfo;
+ struct ip_conntrack *ct = ip_conntrack_get((struct sk_buff *)skb, &ctinfo);
+ if (!ct)
+ return 0;
+
+ return ((ct->mark & info->mask) == info->mark) ^ info->invert;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_connmark_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ipt_match connmark_match
+= { { NULL, NULL }, "connmark", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&connmark_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&connmark_match);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_conntrack.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_conntrack.c
new file mode 100644
index 0000000..8ba89be
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_conntrack.c
@@ -0,0 +1,122 @@
+/* Kernel module to match connection tracking information.
+ * Superset of Rusty's minimalistic state match.
+ * GPL (C) 2001 Marc Boucher (marc@mbsi.ca).
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_conntrack.h>
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_conntrack_info *sinfo = matchinfo;
+ struct ip_conntrack *ct;
+ enum ip_conntrack_info ctinfo;
+ unsigned int statebit;
+
+ ct = ip_conntrack_get((struct sk_buff *)skb, &ctinfo);
+
+#define FWINV(bool,invflg) ((bool) ^ !!(sinfo->invflags & invflg))
+
+ if (ct)
+ statebit = IPT_CONNTRACK_STATE_BIT(ctinfo);
+ else
+ statebit = IPT_CONNTRACK_STATE_INVALID;
+
+ if(sinfo->flags & IPT_CONNTRACK_STATE) {
+ if (ct) {
+ if(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip !=
+ ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip)
+ statebit |= IPT_CONNTRACK_STATE_SNAT;
+
+ if(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip !=
+ ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip)
+ statebit |= IPT_CONNTRACK_STATE_DNAT;
+ }
+
+ if (FWINV((statebit & sinfo->statemask) == 0, IPT_CONNTRACK_STATE))
+ return 0;
+ }
+
+ if(sinfo->flags & IPT_CONNTRACK_PROTO) {
+ if (!ct || FWINV(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.protonum != sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.protonum, IPT_CONNTRACK_PROTO))
+ return 0;
+ }
+
+ if(sinfo->flags & IPT_CONNTRACK_ORIGSRC) {
+ if (!ct || FWINV((ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.ip&sinfo->sipmsk[IP_CT_DIR_ORIGINAL].s_addr) != sinfo->tuple[IP_CT_DIR_ORIGINAL].src.ip, IPT_CONNTRACK_ORIGSRC))
+ return 0;
+ }
+
+ if(sinfo->flags & IPT_CONNTRACK_ORIGDST) {
+ if (!ct || FWINV((ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.dst.ip&sinfo->dipmsk[IP_CT_DIR_ORIGINAL].s_addr) != sinfo->tuple[IP_CT_DIR_ORIGINAL].dst.ip, IPT_CONNTRACK_ORIGDST))
+ return 0;
+ }
+
+ if(sinfo->flags & IPT_CONNTRACK_REPLSRC) {
+ if (!ct || FWINV((ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.ip&sinfo->sipmsk[IP_CT_DIR_REPLY].s_addr) != sinfo->tuple[IP_CT_DIR_REPLY].src.ip, IPT_CONNTRACK_REPLSRC))
+ return 0;
+ }
+
+ if(sinfo->flags & IPT_CONNTRACK_REPLDST) {
+ if (!ct || FWINV((ct->tuplehash[IP_CT_DIR_REPLY].tuple.dst.ip&sinfo->dipmsk[IP_CT_DIR_REPLY].s_addr) != sinfo->tuple[IP_CT_DIR_REPLY].dst.ip, IPT_CONNTRACK_REPLDST))
+ return 0;
+ }
+
+ if(sinfo->flags & IPT_CONNTRACK_STATUS) {
+ if (!ct || FWINV((ct->status & sinfo->statusmask) == 0, IPT_CONNTRACK_STATUS))
+ return 0;
+ }
+
+ if(sinfo->flags & IPT_CONNTRACK_EXPIRES) {
+ unsigned long expires;
+
+ if(!ct)
+ return 0;
+
+ expires = timer_pending(&ct->timeout) ? (ct->timeout.expires - jiffies)/HZ : 0;
+
+ if (FWINV(!(expires >= sinfo->expires_min && expires <= sinfo->expires_max), IPT_CONNTRACK_EXPIRES))
+ return 0;
+ }
+
+ return 1;
+}
+
+static int check(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_conntrack_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ipt_match conntrack_match
+= { { NULL, NULL }, "conntrack", &match, &check, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&conntrack_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&conntrack_match);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_dscp.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_dscp.c
new file mode 100644
index 0000000..dabee1a
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_dscp.c
@@ -0,0 +1,58 @@
+/* IP tables module for matching the value of the IPv4 DSCP field
+ *
+ * ipt_dscp.c,v 1.3 2002/08/05 19:00:21 laforge Exp
+ *
+ * (C) 2002 by Harald Welte <laforge@gnumonks.org>
+ *
+ * This software is distributed under the terms GNU GPL
+ */
+
+#include <linux/module.h>
+#include <linux/skbuff.h>
+
+#include <linux/netfilter_ipv4/ipt_dscp.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("IP tables DSCP matching module");
+MODULE_LICENSE("GPL");
+
+static int match(const struct sk_buff *skb, const struct net_device *in,
+ const struct net_device *out, const void *matchinfo,
+ int offset, const void *hdr, u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_dscp_info *info = matchinfo;
+ const struct iphdr *iph = skb->nh.iph;
+
+ u_int8_t sh_dscp = ((info->dscp << IPT_DSCP_SHIFT) & IPT_DSCP_MASK);
+
+ return ((iph->tos&IPT_DSCP_MASK) == sh_dscp) ^ info->invert;
+}
+
+static int checkentry(const char *tablename, const struct ipt_ip *ip,
+ void *matchinfo, unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_dscp_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ipt_match dscp_match = { { NULL, NULL }, "dscp", &match,
+ &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&dscp_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&dscp_match);
+
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ecn.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ecn.c
new file mode 100644
index 0000000..0a0f539
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ecn.c
@@ -0,0 +1,118 @@
+/* IP tables module for matching the value of the IPv4 and TCP ECN bits
+ *
+ * ipt_ecn.c,v 1.3 2002/05/29 15:09:00 laforge Exp
+ *
+ * (C) 2002 by Harald Welte <laforge@gnumonks.org>
+ *
+ * This software is distributed under the terms GNU GPL v2
+ */
+
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/tcp.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_ecn.h>
+
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("IP tables ECN matching module");
+MODULE_LICENSE("GPL");
+
+static inline int match_ip(const struct sk_buff *skb,
+ const struct iphdr *iph,
+ const struct ipt_ecn_info *einfo)
+{
+ return ((iph->tos&IPT_ECN_IP_MASK) == einfo->ip_ect);
+}
+
+static inline int match_tcp(const struct sk_buff *skb,
+ const struct iphdr *iph,
+ const struct ipt_ecn_info *einfo)
+{
+ struct tcphdr *tcph = (void *)iph + iph->ihl*4;
+
+ if (einfo->operation & IPT_ECN_OP_MATCH_ECE) {
+ if (einfo->invert & IPT_ECN_OP_MATCH_ECE) {
+ if (tcph->ece == 1)
+ return 0;
+ } else {
+ if (tcph->ece == 0)
+ return 0;
+ }
+ }
+
+ if (einfo->operation & IPT_ECN_OP_MATCH_CWR) {
+ if (einfo->invert & IPT_ECN_OP_MATCH_CWR) {
+ if (tcph->cwr == 1)
+ return 0;
+ } else {
+ if (tcph->cwr == 0)
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int match(const struct sk_buff *skb, const struct net_device *in,
+ const struct net_device *out, const void *matchinfo,
+ int offset, const void *hdr, u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_ecn_info *info = matchinfo;
+ const struct iphdr *iph = skb->nh.iph;
+
+ if (info->operation & IPT_ECN_OP_MATCH_IP)
+ if (!match_ip(skb, iph, info))
+ return 0;
+
+ if (info->operation & (IPT_ECN_OP_MATCH_ECE|IPT_ECN_OP_MATCH_CWR)) {
+ if (iph->protocol != IPPROTO_TCP)
+ return 0;
+ if (!match_tcp(skb, iph, info))
+ return 0;
+ }
+
+ return 1;
+}
+
+static int checkentry(const char *tablename, const struct ipt_ip *ip,
+ void *matchinfo, unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ const struct ipt_ecn_info *info = matchinfo;
+
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_ecn_info)))
+ return 0;
+
+ if (info->operation & IPT_ECN_OP_MATCH_MASK)
+ return 0;
+
+ if (info->invert & IPT_ECN_OP_MATCH_MASK)
+ return 0;
+
+ if (info->operation & (IPT_ECN_OP_MATCH_ECE|IPT_ECN_OP_MATCH_CWR)
+ && ip->proto != IPPROTO_TCP) {
+ printk(KERN_WARNING "ipt_ecn: can't match TCP bits in rule for"
+ " non-tcp packets\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ipt_match ecn_match = { { NULL, NULL }, "ecn", &match,
+ &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&ecn_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&ecn_match);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_esp.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_esp.c
new file mode 100644
index 0000000..4a4edc5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_esp.c
@@ -0,0 +1,106 @@
+/* Kernel module to match ESP parameters. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+
+#include <linux/netfilter_ipv4/ipt_esp.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
+
+#ifdef DEBUG_CONNTRACK
+#define duprintf(format, args...) printk(format , ## args)
+#else
+#define duprintf(format, args...)
+#endif
+
+struct esphdr {
+ __u32 spi;
+ __u32 seq_no;
+};
+
+/* Returns 1 if the spi is matched by the range, 0 otherwise */
+static inline int
+spi_match(u_int32_t min, u_int32_t max, u_int32_t spi, int invert)
+{
+ int r=0;
+ duprintf("esp spi_match:%c 0x%x <= 0x%x <= 0x%x",invert? '!':' ',
+ min,spi,max);
+ r=(spi >= min && spi <= max) ^ invert;
+ duprintf(" result %s\n",r? "PASS" : "FAILED");
+ return r;
+}
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct esphdr *esp = hdr;
+ const struct ipt_esp *espinfo = matchinfo;
+
+ if (offset == 0 && datalen < sizeof(struct esphdr)) {
+ /* We've been asked to examine this packet, and we
+ can't. Hence, no choice but to drop. */
+ duprintf("Dropping evil ESP tinygram.\n");
+ *hotdrop = 1;
+ return 0;
+ }
+
+ /* Must not be a fragment. */
+ return !offset
+ && spi_match(espinfo->spis[0], espinfo->spis[1],
+ ntohl(esp->spi),
+ !!(espinfo->invflags & IPT_ESP_INV_SPI));
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchinfosize,
+ unsigned int hook_mask)
+{
+ const struct ipt_esp *espinfo = matchinfo;
+
+ /* Must specify proto == ESP, and no unknown invflags */
+ if (ip->proto != IPPROTO_ESP || (ip->invflags & IPT_INV_PROTO)) {
+ duprintf("ipt_esp: Protocol %u != %u\n", ip->proto,
+ IPPROTO_ESP);
+ return 0;
+ }
+ if (matchinfosize != IPT_ALIGN(sizeof(struct ipt_esp))) {
+ duprintf("ipt_esp: matchsize %u != %u\n",
+ matchinfosize, IPT_ALIGN(sizeof(struct ipt_esp)));
+ return 0;
+ }
+ if (espinfo->invflags & ~IPT_ESP_INV_MASK) {
+ duprintf("ipt_esp: unknown flags %X\n",
+ espinfo->invflags);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ipt_match esp_match
+= { { NULL, NULL }, "esp", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&esp_match);
+}
+
+static void __exit cleanup(void)
+{
+ ipt_unregister_match(&esp_match);
+}
+
+module_init(init);
+module_exit(cleanup);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_helper.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_helper.c
new file mode 100644
index 0000000..a7836d5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_helper.c
@@ -0,0 +1,112 @@
+/*
+ * iptables module to match on related connections
+ * (c) 2001 Martin Josefsson <gandalf@wlug.westbo.se>
+ *
+ * Released under the terms of GNU GPLv2.
+ *
+ * 19 Mar 2002 Harald Welte <laforge@gnumonks.org>:
+ * - Port to newnat infrastructure
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+#include <linux/netfilter_ipv4/ip_conntrack_core.h>
+#include <linux/netfilter_ipv4/ip_conntrack_helper.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_helper.h>
+
+MODULE_LICENSE("GPL");
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_helper_info *info = matchinfo;
+ struct ip_conntrack_expect *exp;
+ struct ip_conntrack *ct;
+ enum ip_conntrack_info ctinfo;
+ int ret = info->invert;
+
+ ct = ip_conntrack_get((struct sk_buff *)skb, &ctinfo);
+ if (!ct) {
+ DEBUGP("ipt_helper: Eek! invalid conntrack?\n");
+ return ret;
+ }
+
+ if (!ct->master) {
+ DEBUGP("ipt_helper: conntrack %p has no master\n", ct);
+ return ret;
+ }
+
+ exp = ct->master;
+ READ_LOCK(&ip_conntrack_lock);
+ if (!exp->expectant) {
+ DEBUGP("ipt_helper: expectation %p without expectant !?!\n",
+ exp);
+ goto out_unlock;
+ }
+
+ if (!exp->expectant->helper) {
+ DEBUGP("ipt_helper: master ct %p has no helper\n",
+ exp->expectant);
+ goto out_unlock;
+ }
+
+ DEBUGP("master's name = %s , info->name = %s\n",
+ exp->expectant->helper->name, info->name);
+
+ if (info->name[0] == '\0')
+ ret ^= 1;
+ else
+ ret ^= !strncmp(exp->expectant->helper->name, info->name,
+ strlen(exp->expectant->helper->name));
+out_unlock:
+ READ_UNLOCK(&ip_conntrack_lock);
+ return ret;
+}
+
+static int check(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ struct ipt_helper_info *info = matchinfo;
+
+ info->name[29] = '\0';
+
+ /* verify size */
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_helper_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ipt_match helper_match
+= { { NULL, NULL }, "helper", &match, &check, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&helper_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&helper_match);
+}
+
+module_init(init);
+module_exit(fini);
+
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_iprange.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_iprange.c
new file mode 100644
index 0000000..3890252
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_iprange.c
@@ -0,0 +1,101 @@
+/*
+ * iptables module to match IP address ranges
+ * (c) 2003 Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>
+ *
+ * Released under the terms of GNU GPLv2.
+ *
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_iprange.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>");
+MODULE_DESCRIPTION("iptables arbitrary IP range match module");
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_iprange_info *info = matchinfo;
+ const struct iphdr *iph = skb->nh.iph;
+
+
+ if (info->flags & IPRANGE_SRC) {
+ if (((ntohl(iph->saddr) < ntohl(info->src.min_ip))
+ || (ntohl(iph->saddr) > ntohl(info->src.max_ip)))
+ ^ !!(info->flags & IPRANGE_SRC_INV)) {
+ DEBUGP("src IP %u.%u.%u.%u NOT in range %s"
+ "%u.%u.%u.%u-%u.%u.%u.%u\n",
+ NIPQUAD(iph->saddr),
+ info->flags & IPRANGE_SRC_INV ? "(INV) " : "",
+ NIPQUAD(info->src.min_ip),
+ NIPQUAD(info->src.max_ip));
+ return 0;
+ }
+ }
+ if (info->flags & IPRANGE_DST) {
+ if (((ntohl(iph->daddr) < ntohl(info->dst.min_ip))
+ || (ntohl(iph->daddr) > ntohl(info->dst.max_ip)))
+ ^ !!(info->flags & IPRANGE_DST_INV)) {
+ DEBUGP("dst IP %u.%u.%u.%u NOT in range %s"
+ "%u.%u.%u.%u-%u.%u.%u.%u\n",
+ NIPQUAD(iph->daddr),
+ info->flags & IPRANGE_DST_INV ? "(INV) " : "",
+ NIPQUAD(info->dst.min_ip),
+ NIPQUAD(info->dst.max_ip));
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static int check(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ /* verify size */
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_iprange_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ipt_match iprange_match =
+{
+ .list = { NULL, NULL },
+ .name = "iprange",
+ .match = &match,
+ .checkentry = &check,
+ .destroy = NULL,
+ .me = THIS_MODULE
+};
+
+static int __init init(void)
+{
+ return ipt_register_match(&iprange_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&iprange_match);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_length.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_length.c
new file mode 100644
index 0000000..0cc00f1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_length.c
@@ -0,0 +1,55 @@
+/* Kernel module to match packet length. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+
+#include <linux/netfilter_ipv4/ipt_length.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+MODULE_AUTHOR("James Morris <jmorris@intercode.com.au>");
+MODULE_DESCRIPTION("IP tables packet length matching module");
+MODULE_LICENSE("GPL");
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_length_info *info = matchinfo;
+ u_int16_t pktlen = ntohs(skb->nh.iph->tot_len);
+
+ return (pktlen >= info->min && pktlen <= info->max) ^ info->invert;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_length_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ipt_match length_match
+= { { NULL, NULL }, "length", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&length_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&length_match);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_limit.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_limit.c
new file mode 100644
index 0000000..ed92091
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_limit.c
@@ -0,0 +1,147 @@
+/* Kernel module to control the rate
+ *
+ * Jérôme de Vivie <devivie@info.enserb.u-bordeaux.fr>
+ * Hervé Eychenne <eychenne@info.enserb.u-bordeaux.fr>
+ *
+ * 2 September 1999: Changed from the target RATE to the match
+ * `limit', removed logging. Did I mention that
+ * Alexey is a fucking genius?
+ * Rusty Russell (rusty@rustcorp.com.au). */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_limit.h>
+
+/* The algorithm used is the Simple Token Bucket Filter (TBF)
+ * see net/sched/sch_tbf.c in the linux source tree
+ */
+
+static spinlock_t limit_lock = SPIN_LOCK_UNLOCKED;
+
+/* Rusty: This is my (non-mathematically-inclined) understanding of
+ this algorithm. The `average rate' in jiffies becomes your initial
+ amount of credit `credit' and the most credit you can ever have
+ `credit_cap'. The `peak rate' becomes the cost of passing the
+ test, `cost'.
+
+ `prev' tracks the last packet hit: you gain one credit per jiffy.
+ If you get credit balance more than this, the extra credit is
+ discarded. Every time the match passes, you lose `cost' credits;
+ if you don't have that many, the test fails.
+
+ See Alexey's formal explanation in net/sched/sch_tbf.c.
+
+ To get the maxmum range, we multiply by this factor (ie. you get N
+ credits per jiffy). We want to allow a rate as low as 1 per day
+ (slowest userspace tool allows), which means
+ CREDITS_PER_JIFFY*HZ*60*60*24 < 2^32. ie. */
+#define MAX_CPJ (0xFFFFFFFF / (HZ*60*60*24))
+
+/* Repeated shift and or gives us all 1s, final shift and add 1 gives
+ * us the power of 2 below the theoretical max, so GCC simply does a
+ * shift. */
+#define _POW2_BELOW2(x) ((x)|((x)>>1))
+#define _POW2_BELOW4(x) (_POW2_BELOW2(x)|_POW2_BELOW2((x)>>2))
+#define _POW2_BELOW8(x) (_POW2_BELOW4(x)|_POW2_BELOW4((x)>>4))
+#define _POW2_BELOW16(x) (_POW2_BELOW8(x)|_POW2_BELOW8((x)>>8))
+#define _POW2_BELOW32(x) (_POW2_BELOW16(x)|_POW2_BELOW16((x)>>16))
+#define POW2_BELOW32(x) ((_POW2_BELOW32(x)>>1) + 1)
+
+#define CREDITS_PER_JIFFY POW2_BELOW32(MAX_CPJ)
+
+static int
+ipt_limit_match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ struct ipt_rateinfo *r = ((struct ipt_rateinfo *)matchinfo)->master;
+ unsigned long now = jiffies;
+
+ spin_lock_bh(&limit_lock);
+ r->credit += (now - xchg(&r->prev, now)) * CREDITS_PER_JIFFY;
+ if (r->credit > r->credit_cap)
+ r->credit = r->credit_cap;
+
+ if (r->credit >= r->cost) {
+ /* We're not limited. */
+ r->credit -= r->cost;
+ spin_unlock_bh(&limit_lock);
+ return 1;
+ }
+
+ spin_unlock_bh(&limit_lock);
+ return 0;
+}
+
+/* Precision saver. */
+static u_int32_t
+user2credits(u_int32_t user)
+{
+ /* If multiplying would overflow... */
+ if (user > 0xFFFFFFFF / (HZ*CREDITS_PER_JIFFY))
+ /* Divide first. */
+ return (user / IPT_LIMIT_SCALE) * HZ * CREDITS_PER_JIFFY;
+
+ return (user * HZ * CREDITS_PER_JIFFY) / IPT_LIMIT_SCALE;
+}
+
+static int
+ipt_limit_checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ struct ipt_rateinfo *r = matchinfo;
+
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_rateinfo)))
+ return 0;
+
+ /* Check for overflow. */
+ if (r->burst == 0
+ || user2credits(r->avg * r->burst) < user2credits(r->avg)) {
+ printk("Overflow in ipt_limit, try lower: %u/%u\n",
+ r->avg, r->burst);
+ return 0;
+ }
+
+ /* User avg in seconds * IPT_LIMIT_SCALE: convert to jiffies *
+ 128. */
+ r->prev = jiffies;
+ r->credit = user2credits(r->avg * r->burst); /* Credits full. */
+ r->credit_cap = user2credits(r->avg * r->burst); /* Credits full. */
+ r->cost = user2credits(r->avg);
+
+ /* For SMP, we only want to use one set of counters. */
+ r->master = r;
+
+ return 1;
+}
+
+static struct ipt_match ipt_limit_reg
+= { { NULL, NULL }, "limit", ipt_limit_match, ipt_limit_checkentry, NULL,
+ THIS_MODULE };
+
+static int __init init(void)
+{
+ if (ipt_register_match(&ipt_limit_reg))
+ return -EINVAL;
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&ipt_limit_reg);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_mac.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_mac.c
new file mode 100644
index 0000000..b320e29
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_mac.c
@@ -0,0 +1,65 @@
+/* Kernel module to match MAC address parameters. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/if_ether.h>
+
+#include <linux/netfilter_ipv4/ipt_mac.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_mac_info *info = matchinfo;
+
+ /* Is mac pointer valid? */
+ return (skb->mac.raw >= skb->head
+ && (skb->mac.raw + ETH_HLEN) <= skb->data
+ /* If so, compare... */
+ && ((memcmp(skb->mac.ethernet->h_source, info->srcaddr, ETH_ALEN)
+ == 0) ^ info->invert));
+}
+
+static int
+ipt_mac_checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ /* FORWARD isn't always valid, but it's nice to be able to do --RR */
+ if (hook_mask
+ & ~((1 << NF_IP_PRE_ROUTING) | (1 << NF_IP_LOCAL_IN)
+ | (1 << NF_IP_FORWARD))) {
+ printk("ipt_mac: only valid for PRE_ROUTING, LOCAL_IN or FORWARD.\n");
+ return 0;
+ }
+
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_mac_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ipt_match mac_match
+= { { NULL, NULL }, "mac", &match, &ipt_mac_checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&mac_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&mac_match);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_mark.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_mark.c
new file mode 100644
index 0000000..0506653
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_mark.c
@@ -0,0 +1,51 @@
+/* Kernel module to match NFMARK values. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+
+#include <linux/netfilter_ipv4/ipt_mark.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_mark_info *info = matchinfo;
+
+ return ((skb->nfmark & info->mask) == info->mark) ^ info->invert;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_mark_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ipt_match mark_match
+= { { NULL, NULL }, "mark", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&mark_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&mark_match);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_multiport.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_multiport.c
new file mode 100644
index 0000000..5efaf9d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_multiport.c
@@ -0,0 +1,104 @@
+/* Kernel module to match one of a list of TCP/UDP ports: ports are in
+ the same place so we can treat them as equal. */
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <linux/skbuff.h>
+
+#include <linux/netfilter_ipv4/ipt_multiport.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+#if 0
+#define duprintf(format, args...) printk(format , ## args)
+#else
+#define duprintf(format, args...)
+#endif
+
+/* Returns 1 if the port is matched by the test, 0 otherwise. */
+static inline int
+ports_match(const u_int16_t *portlist, enum ipt_multiport_flags flags,
+ u_int8_t count, u_int16_t src, u_int16_t dst)
+{
+ unsigned int i;
+ for (i=0; i<count; i++) {
+ if (flags != IPT_MULTIPORT_DESTINATION
+ && portlist[i] == src)
+ return 1;
+
+ if (flags != IPT_MULTIPORT_SOURCE
+ && portlist[i] == dst)
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct udphdr *udp = hdr;
+ const struct ipt_multiport *multiinfo = matchinfo;
+
+ /* Must be big enough to read ports. */
+ if (offset == 0 && datalen < sizeof(struct udphdr)) {
+ /* We've been asked to examine this packet, and we
+ can't. Hence, no choice but to drop. */
+ duprintf("ipt_multiport:"
+ " Dropping evil offset=0 tinygram.\n");
+ *hotdrop = 1;
+ return 0;
+ }
+
+ /* Must not be a fragment. */
+ return !offset
+ && ports_match(multiinfo->ports,
+ multiinfo->flags, multiinfo->count,
+ ntohs(udp->source), ntohs(udp->dest));
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ const struct ipt_multiport *multiinfo = matchinfo;
+
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_multiport)))
+ return 0;
+
+ /* Must specify proto == TCP/UDP, no unknown flags or bad count */
+ return (ip->proto == IPPROTO_TCP || ip->proto == IPPROTO_UDP)
+ && !(ip->invflags & IPT_INV_PROTO)
+ && matchsize == IPT_ALIGN(sizeof(struct ipt_multiport))
+ && (multiinfo->flags == IPT_MULTIPORT_SOURCE
+ || multiinfo->flags == IPT_MULTIPORT_DESTINATION
+ || multiinfo->flags == IPT_MULTIPORT_EITHER)
+ && multiinfo->count <= IPT_MULTI_PORTS;
+}
+
+static struct ipt_match multiport_match
+= { { NULL, NULL }, "multiport", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&multiport_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&multiport_match);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_owner.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_owner.c
new file mode 100644
index 0000000..b561636
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_owner.c
@@ -0,0 +1,201 @@
+/* Kernel module to match various things tied to sockets associated with
+ locally generated outgoing packets.
+
+ Copyright (C) 2000 Marc Boucher
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/file.h>
+#include <net/sock.h>
+
+#include <linux/netfilter_ipv4/ipt_owner.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+static int
+match_comm(const struct sk_buff *skb, const char *comm)
+{
+ struct task_struct *p;
+ struct files_struct *files;
+ int i;
+
+ read_lock(&tasklist_lock);
+ for_each_task(p) {
+ if(strncmp(p->comm, comm, sizeof(p->comm)))
+ continue;
+
+ task_lock(p);
+ files = p->files;
+ if(files) {
+ read_lock(&files->file_lock);
+ for (i=0; i < files->max_fds; i++) {
+ if (fcheck_files(files, i) == skb->sk->socket->file) {
+ read_unlock(&files->file_lock);
+ task_unlock(p);
+ read_unlock(&tasklist_lock);
+ return 1;
+ }
+ }
+ read_unlock(&files->file_lock);
+ }
+ task_unlock(p);
+ }
+ read_unlock(&tasklist_lock);
+ return 0;
+}
+
+static int
+match_pid(const struct sk_buff *skb, pid_t pid)
+{
+ struct task_struct *p;
+ struct files_struct *files;
+ int i;
+
+ read_lock(&tasklist_lock);
+ p = find_task_by_pid(pid);
+ if (!p)
+ goto out;
+ task_lock(p);
+ files = p->files;
+ if(files) {
+ read_lock(&files->file_lock);
+ for (i=0; i < files->max_fds; i++) {
+ if (fcheck_files(files, i) == skb->sk->socket->file) {
+ read_unlock(&files->file_lock);
+ task_unlock(p);
+ read_unlock(&tasklist_lock);
+ return 1;
+ }
+ }
+ read_unlock(&files->file_lock);
+ }
+ task_unlock(p);
+out:
+ read_unlock(&tasklist_lock);
+ return 0;
+}
+
+static int
+match_sid(const struct sk_buff *skb, pid_t sid)
+{
+ struct task_struct *p;
+ struct file *file = skb->sk->socket->file;
+ int i, found=0;
+
+ read_lock(&tasklist_lock);
+ for_each_task(p) {
+ struct files_struct *files;
+ if (p->session != sid)
+ continue;
+
+ task_lock(p);
+ files = p->files;
+ if (files) {
+ read_lock(&files->file_lock);
+ for (i=0; i < files->max_fds; i++) {
+ if (fcheck_files(files, i) == file) {
+ found = 1;
+ break;
+ }
+ }
+ read_unlock(&files->file_lock);
+ }
+ task_unlock(p);
+ if(found)
+ break;
+ }
+ read_unlock(&tasklist_lock);
+
+ return found;
+}
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_owner_info *info = matchinfo;
+
+ if (!skb->sk || !skb->sk->socket || !skb->sk->socket->file)
+ return 0;
+
+ if(info->match & IPT_OWNER_UID) {
+ if((skb->sk->socket->file->f_uid != info->uid) ^
+ !!(info->invert & IPT_OWNER_UID))
+ return 0;
+ }
+
+ if(info->match & IPT_OWNER_GID) {
+ if((skb->sk->socket->file->f_gid != info->gid) ^
+ !!(info->invert & IPT_OWNER_GID))
+ return 0;
+ }
+
+ if(info->match & IPT_OWNER_PID) {
+ if (!match_pid(skb, info->pid) ^
+ !!(info->invert & IPT_OWNER_PID))
+ return 0;
+ }
+
+ if(info->match & IPT_OWNER_SID) {
+ if (!match_sid(skb, info->sid) ^
+ !!(info->invert & IPT_OWNER_SID))
+ return 0;
+ }
+
+ if(info->match & IPT_OWNER_COMM) {
+ if (!match_comm(skb, info->comm) ^
+ !!(info->invert & IPT_OWNER_COMM))
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (hook_mask
+ & ~((1 << NF_IP_LOCAL_OUT) | (1 << NF_IP_POST_ROUTING))) {
+ printk("ipt_owner: only valid for LOCAL_OUT or POST_ROUTING.\n");
+ return 0;
+ }
+
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_owner_info)))
+ return 0;
+#ifdef CONFIG_SMP
+ /* files->file_lock can not be used in a BH */
+ if (((struct ipt_owner_info *)matchinfo)->match
+ & (IPT_OWNER_PID|IPT_OWNER_SID|IPT_OWNER_COMM)) {
+ printk("ipt_owner: pid, sid and command matching is broken "
+ "on SMP.\n");
+ return 0;
+ }
+#endif
+ return 1;
+}
+
+static struct ipt_match owner_match
+= { { NULL, NULL }, "owner", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&owner_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&owner_match);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_physdev.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_physdev.c
new file mode 100644
index 0000000..762c0c9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_physdev.c
@@ -0,0 +1,127 @@
+/* Kernel module to match the bridge port in and
+ * out device for IP packets coming into contact with a bridge. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter_ipv4/ipt_physdev.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_bridge.h>
+#include <linux/netdevice.h>
+#define MATCH 1
+#define NOMATCH 0
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ int i;
+ static const char nulldevname[IFNAMSIZ] = { 0 };
+ const struct ipt_physdev_info *info = matchinfo;
+ unsigned long ret;
+ const char *indev, *outdev;
+ struct nf_bridge_info *nf_bridge;
+
+ /* Not a bridged IP packet or no info available yet:
+ * LOCAL_OUT/mangle and LOCAL_OUT/nat don't know if
+ * the destination device will be a bridge. */
+ if (!(nf_bridge = skb->nf_bridge)) {
+ /* Return MATCH if the invert flags of the used options are on */
+ if ((info->bitmask & IPT_PHYSDEV_OP_BRIDGED) &&
+ !(info->invert & IPT_PHYSDEV_OP_BRIDGED))
+ return NOMATCH;
+ if ((info->bitmask & IPT_PHYSDEV_OP_ISIN) &&
+ !(info->invert & IPT_PHYSDEV_OP_ISIN))
+ return NOMATCH;
+ if ((info->bitmask & IPT_PHYSDEV_OP_ISOUT) &&
+ !(info->invert & IPT_PHYSDEV_OP_ISOUT))
+ return NOMATCH;
+ if ((info->bitmask & IPT_PHYSDEV_OP_IN) &&
+ !(info->invert & IPT_PHYSDEV_OP_IN))
+ return NOMATCH;
+ if ((info->bitmask & IPT_PHYSDEV_OP_OUT) &&
+ !(info->invert & IPT_PHYSDEV_OP_OUT))
+ return NOMATCH;
+ return MATCH;
+ }
+
+ /* This only makes sense in the FORWARD and POSTROUTING chains */
+ if ((info->bitmask & IPT_PHYSDEV_OP_BRIDGED) &&
+ (!!(nf_bridge->mask & BRNF_BRIDGED) ^
+ !(info->invert & IPT_PHYSDEV_OP_BRIDGED)))
+ return NOMATCH;
+
+ if ((info->bitmask & IPT_PHYSDEV_OP_ISIN &&
+ (!nf_bridge->physindev ^ !!(info->invert & IPT_PHYSDEV_OP_ISIN))) ||
+ (info->bitmask & IPT_PHYSDEV_OP_ISOUT &&
+ (!nf_bridge->physoutdev ^ !!(info->invert & IPT_PHYSDEV_OP_ISOUT))))
+ return NOMATCH;
+
+ if (!(info->bitmask & IPT_PHYSDEV_OP_IN))
+ goto match_outdev;
+ indev = nf_bridge->physindev ? nf_bridge->physindev->name : nulldevname;
+ for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) {
+ ret |= (((const unsigned long *)indev)[i]
+ ^ ((const unsigned long *)info->physindev)[i])
+ & ((const unsigned long *)info->in_mask)[i];
+ }
+
+ if ((ret == 0) ^ !(info->invert & IPT_PHYSDEV_OP_IN))
+ return NOMATCH;
+
+match_outdev:
+ if (!(info->bitmask & IPT_PHYSDEV_OP_OUT))
+ return MATCH;
+ outdev = nf_bridge->physoutdev ?
+ nf_bridge->physoutdev->name : nulldevname;
+ for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) {
+ ret |= (((const unsigned long *)outdev)[i]
+ ^ ((const unsigned long *)info->physoutdev)[i])
+ & ((const unsigned long *)info->out_mask)[i];
+ }
+
+ return (ret != 0) ^ !(info->invert & IPT_PHYSDEV_OP_OUT);
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ const struct ipt_physdev_info *info = matchinfo;
+
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_physdev_info)))
+ return 0;
+ if (!(info->bitmask & IPT_PHYSDEV_OP_MASK) ||
+ info->bitmask & ~IPT_PHYSDEV_OP_MASK)
+ return 0;
+ return 1;
+}
+
+static struct ipt_match physdev_match = {
+ .name = "physdev",
+ .match = &match,
+ .checkentry = &checkentry,
+ .me = THIS_MODULE,
+};
+
+static int __init init(void)
+{
+ return ipt_register_match(&physdev_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&physdev_match);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
+EXPORT_NO_SYMBOLS;
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_pkttype.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_pkttype.c
new file mode 100644
index 0000000..3b6ccd7
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_pkttype.c
@@ -0,0 +1,59 @@
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+
+#include <linux/netfilter_ipv4/ipt_pkttype.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+MODULE_LICENSE("GPL");
+
+static int match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_pkttype_info *info = matchinfo;
+
+ return (skb->pkt_type == info->pkttype) ^ info->invert;
+}
+
+static int checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+/*
+ if (hook_mask
+ & ~((1 << NF_IP_PRE_ROUTING) | (1 << NF_IP_LOCAL_IN)
+ | (1 << NF_IP_FORWARD))) {
+ printk("ipt_pkttype: only valid for PRE_ROUTING, LOCAL_IN or FORWARD.\n");
+ return 0;
+ }
+*/
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_pkttype_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ipt_match pkttype_match
+= { { NULL, NULL }, "pkttype", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&pkttype_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&pkttype_match);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_recent.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_recent.c
new file mode 100644
index 0000000..d379a26
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_recent.c
@@ -0,0 +1,1005 @@
+/* Kernel module to check if the source address has been seen recently. */
+/* Copyright 2002-2003, Stephen Frost */
+/* Author: Stephen Frost <sfrost@snowman.net> */
+/* Project Page: http://snowman.net/projects/ipt_recent/ */
+/* This software is distributed under the terms of the GPL, Version 2 */
+/* This copyright does not cover user programs that use kernel services
+ * by normal system calls. */
+
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/proc_fs.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <asm/uaccess.h>
+#include <linux/ctype.h>
+#include <linux/ip.h>
+#include <linux/vmalloc.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_recent.h>
+
+#undef DEBUG
+#define HASH_LOG 9
+
+/* Defaults, these can be overridden on the module command-line. */
+static int ip_list_tot = 100;
+static int ip_pkt_list_tot = 20;
+static int ip_list_hash_size = 0;
+static int ip_list_perms = 0644;
+#ifdef DEBUG
+static int debug = 1;
+#endif
+
+static char version[] =
+KERN_INFO RECENT_NAME " " RECENT_VER ": Stephen Frost <sfrost@snowman.net>. http://snowman.net/projects/ipt_recent/\n";
+
+MODULE_AUTHOR("Stephen Frost <sfrost@snowman.net>");
+MODULE_DESCRIPTION("IP tables recently seen matching module " RECENT_VER);
+MODULE_LICENSE("GPL");
+MODULE_PARM(ip_list_tot,"i");
+MODULE_PARM(ip_pkt_list_tot,"i");
+MODULE_PARM(ip_list_hash_size,"i");
+MODULE_PARM(ip_list_perms,"i");
+#ifdef DEBUG
+MODULE_PARM(debug,"i");
+MODULE_PARM_DESC(debug,"debugging level, defaults to 1");
+#endif
+MODULE_PARM_DESC(ip_list_tot,"number of IPs to remember per list");
+MODULE_PARM_DESC(ip_pkt_list_tot,"number of packets per IP to remember");
+MODULE_PARM_DESC(ip_list_hash_size,"size of hash table used to look up IPs");
+MODULE_PARM_DESC(ip_list_perms,"permissions on /proc/net/ipt_recent/* files");
+
+/* Structure of our list of recently seen addresses. */
+struct recent_ip_list {
+ u_int32_t addr;
+ u_int8_t ttl;
+ u_int32_t last_seen;
+ u_int32_t *last_pkts;
+ u_int32_t oldest_pkt;
+ u_int32_t hash_entry;
+ u_int32_t time_pos;
+};
+
+struct time_info_list {
+ u_int32_t position;
+ u_int32_t time;
+};
+
+/* Structure of our linked list of tables of recent lists. */
+struct recent_ip_tables {
+ char name[IPT_RECENT_NAME_LEN];
+ int count;
+ int time_pos;
+ struct recent_ip_list *table;
+ struct recent_ip_tables *next;
+ spinlock_t list_lock;
+ int *hash_table;
+ struct time_info_list *time_info;
+#ifdef CONFIG_PROC_FS
+ struct proc_dir_entry *status_proc;
+#endif /* CONFIG_PROC_FS */
+};
+
+/* Our current list of addresses we have recently seen.
+ * Only added to on a --set, and only updated on --set || --update
+ */
+static struct recent_ip_tables *r_tables = NULL;
+
+/* We protect r_list with this spinlock so two processors are not modifying
+ * the list at the same time.
+ */
+static spinlock_t recent_lock = SPIN_LOCK_UNLOCKED;
+
+#ifdef CONFIG_PROC_FS
+/* Our /proc/net/ipt_recent entry */
+static struct proc_dir_entry *proc_net_ipt_recent = NULL;
+#endif
+
+/* Function declaration for later. */
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop);
+
+/* Function to hash a given address into the hash table of table_size size */
+static int hash_func(unsigned int addr, int table_size)
+{
+ int result = 0;
+ unsigned int value = addr;
+ do { result ^= value; } while((value >>= HASH_LOG));
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": %d = hash_func(%u,%d)\n",
+ result & (table_size - 1),
+ addr,
+ table_size);
+#endif
+
+ return(result & (table_size - 1));
+}
+
+#ifdef CONFIG_PROC_FS
+/* This is the function which produces the output for our /proc output
+ * interface which lists each IP address, the last seen time and the
+ * other recent times the address was seen.
+ */
+
+static int ip_recent_get_info(char *buffer, char **start, off_t offset, int length, int *eof, void *data)
+{
+ int len = 0, count, last_len = 0, pkt_count;
+ off_t pos = 0;
+ off_t begin = 0;
+ struct recent_ip_tables *curr_table;
+
+ curr_table = (struct recent_ip_tables*) data;
+
+ spin_lock_bh(&curr_table->list_lock);
+ for(count = 0; count < ip_list_tot; count++) {
+ if(!curr_table->table[count].addr) continue;
+ last_len = len;
+ len += sprintf(buffer+len,"src=%u.%u.%u.%u ",NIPQUAD(curr_table->table[count].addr));
+ len += sprintf(buffer+len,"ttl: %u ",curr_table->table[count].ttl);
+ len += sprintf(buffer+len,"last_seen: %u ",curr_table->table[count].last_seen);
+ len += sprintf(buffer+len,"oldest_pkt: %u ",curr_table->table[count].oldest_pkt);
+ len += sprintf(buffer+len,"last_pkts: %u",curr_table->table[count].last_pkts[0]);
+ for(pkt_count = 1; pkt_count < ip_pkt_list_tot; pkt_count++) {
+ if(!curr_table->table[count].last_pkts[pkt_count]) break;
+ len += sprintf(buffer+len,", %u",curr_table->table[count].last_pkts[pkt_count]);
+ }
+ len += sprintf(buffer+len,"\n");
+ pos = begin + len;
+ if(pos < offset) { len = 0; begin = pos; }
+ if(pos > offset + length) { len = last_len; break; }
+ }
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+ if(len > length) len = length;
+
+ spin_unlock_bh(&curr_table->list_lock);
+ return len;
+}
+
+/* ip_recent_ctrl provides an interface for users to modify the table
+ * directly. This allows adding entries, removing entries, and
+ * flushing the entire table.
+ * This is done by opening up the appropriate table for writing and
+ * sending one of:
+ * xx.xx.xx.xx -- Add entry to table with current time
+ * +xx.xx.xx.xx -- Add entry to table with current time
+ * -xx.xx.xx.xx -- Remove entry from table
+ * clear -- Flush table, remove all entries
+ */
+
+static int ip_recent_ctrl(struct file *file, const char *input, unsigned long size, void *data)
+{
+ static const u_int32_t max[4] = { 0xffffffff, 0xffffff, 0xffff, 0xff };
+ u_int32_t val;
+ int base, used = 0;
+ char c, *cp;
+ union iaddr {
+ uint8_t bytes[4];
+ uint32_t word;
+ } res;
+ uint8_t *pp = res.bytes;
+ int digit;
+
+ char buffer[20];
+ int len, check_set = 0, count;
+ u_int32_t addr = 0;
+ struct sk_buff *skb;
+ struct ipt_recent_info *info;
+ struct recent_ip_tables *curr_table;
+
+ curr_table = (struct recent_ip_tables*) data;
+
+ if(size > 20) len = 20; else len = size;
+
+ if(copy_from_user(buffer,input,len)) return -EFAULT;
+
+ if(len < 20) buffer[len] = '\0';
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": ip_recent_ctrl len: %d, input: `%.20s'\n",len,buffer);
+#endif
+
+ cp = buffer;
+ while(isspace(*cp)) { cp++; used++; if(used >= len-5) return used; }
+
+ /* Check if we are asked to flush the entire table */
+ if(!memcmp(cp,"clear",5)) {
+ used += 5;
+ spin_lock_bh(&curr_table->list_lock);
+ curr_table->time_pos = 0;
+ for(count = 0; count < ip_list_hash_size; count++) {
+ curr_table->hash_table[count] = -1;
+ }
+ for(count = 0; count < ip_list_tot; count++) {
+ curr_table->table[count].last_seen = 0;
+ curr_table->table[count].addr = 0;
+ curr_table->table[count].ttl = 0;
+ memset(curr_table->table[count].last_pkts,0,ip_pkt_list_tot*sizeof(u_int32_t));
+ curr_table->table[count].oldest_pkt = 0;
+ curr_table->table[count].time_pos = 0;
+ curr_table->time_info[count].position = count;
+ curr_table->time_info[count].time = 0;
+ }
+ spin_unlock_bh(&curr_table->list_lock);
+ return used;
+ }
+
+ check_set = IPT_RECENT_SET;
+ switch(*cp) {
+ case '+': check_set = IPT_RECENT_SET; cp++; used++; break;
+ case '-': check_set = IPT_RECENT_REMOVE; cp++; used++; break;
+ default: if(!isdigit(*cp)) return (used+1); break;
+ }
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": ip_recent_ctrl cp: `%c', check_set: %d\n",*cp,check_set);
+#endif
+ /* Get addr (effectively inet_aton()) */
+ /* Shamelessly stolen from libc, a function in the kernel for doing
+ * this would, of course, be greatly preferred, but our options appear
+ * to be rather limited, so we will just do it ourselves here.
+ */
+ res.word = 0;
+
+ c = *cp;
+ for(;;) {
+ if(!isdigit(c)) return used;
+ val = 0; base = 10; digit = 0;
+ if(c == '0') {
+ c = *++cp;
+ if(c == 'x' || c == 'X') base = 16, c = *++cp;
+ else { base = 8; digit = 1; }
+ }
+ for(;;) {
+ if(isascii(c) && isdigit(c)) {
+ if(base == 8 && (c == '8' || c == '0')) return used;
+ val = (val * base) + (c - '0');
+ c = *++cp;
+ digit = 1;
+ } else if(base == 16 && isascii(c) && isxdigit(c)) {
+ val = (val << 4) | (c + 10 - (islower(c) ? 'a' : 'A'));
+ c = *++cp;
+ digit = 1;
+ } else break;
+ }
+ if(c == '.') {
+ if(pp > res.bytes + 2 || val > 0xff) return used;
+ *pp++ = val;
+ c = *++cp;
+ } else break;
+ }
+ used = cp - buffer;
+ if(c != '\0' && (!isascii(c) || !isspace(c))) return used;
+ if(c == '\n') used++;
+ if(!digit) return used;
+
+ if(val > max[pp - res.bytes]) return used;
+ addr = res.word | htonl(val);
+
+ if(!addr && check_set == IPT_RECENT_SET) return used;
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": ip_recent_ctrl c: %c, addr: %u used: %d\n",c,addr,used);
+#endif
+
+ /* Set up and just call match */
+ info = kmalloc(sizeof(struct ipt_recent_info),GFP_KERNEL);
+ if(!info) { return -ENOMEM; }
+ info->seconds = 0;
+ info->hit_count = 0;
+ info->check_set = check_set;
+ info->invert = 0;
+ info->side = IPT_RECENT_SOURCE;
+ strncpy(info->name,curr_table->name,IPT_RECENT_NAME_LEN);
+ info->name[IPT_RECENT_NAME_LEN-1] = '\0';
+
+ skb = kmalloc(sizeof(struct sk_buff),GFP_KERNEL);
+ if (!skb) {
+ used = -ENOMEM;
+ goto out_free_info;
+ }
+ skb->nh.iph = kmalloc(sizeof(struct iphdr),GFP_KERNEL);
+ if (!skb->nh.iph) {
+ used = -ENOMEM;
+ goto out_free_skb;
+ }
+
+ skb->nh.iph->saddr = addr;
+ skb->nh.iph->daddr = 0;
+ /* Clear ttl since we have no way of knowing it */
+ skb->nh.iph->ttl = 0;
+ match(skb,NULL,NULL,info,0,NULL,sizeof(struct ipt_recent_info),NULL);
+
+ kfree(skb->nh.iph);
+out_free_skb:
+ kfree(skb);
+out_free_info:
+ kfree(info);
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": Leaving ip_recent_ctrl addr: %u used: %d\n",addr,used);
+#endif
+ return used;
+}
+
+#endif /* CONFIG_PROC_FS */
+
+/* 'match' is our primary function, called by the kernel whenever a rule is
+ * hit with our module as an option to it.
+ * What this function does depends on what was specifically asked of it by
+ * the user:
+ * --set -- Add or update last seen time of the source address of the packet
+ * -- matchinfo->check_set == IPT_RECENT_SET
+ * --rcheck -- Just check if the source address is in the list
+ * -- matchinfo->check_set == IPT_RECENT_CHECK
+ * --update -- If the source address is in the list, update last_seen
+ * -- matchinfo->check_set == IPT_RECENT_UPDATE
+ * --remove -- If the source address is in the list, remove it
+ * -- matchinfo->check_set == IPT_RECENT_REMOVE
+ * --seconds -- Option to --rcheck/--update, only match if last_seen within seconds
+ * -- matchinfo->seconds
+ * --hitcount -- Option to --rcheck/--update, only match if seen hitcount times
+ * -- matchinfo->hit_count
+ * --seconds and --hitcount can be combined
+ */
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ int pkt_count, hits_found, ans;
+ unsigned long now;
+ const struct ipt_recent_info *info = matchinfo;
+ u_int32_t addr = 0, time_temp;
+ u_int8_t ttl = skb->nh.iph->ttl;
+ int *hash_table;
+ int orig_hash_result, hash_result, temp, location = 0, time_loc, end_collision_chain = -1;
+ struct time_info_list *time_info;
+ struct recent_ip_tables *curr_table;
+ struct recent_ip_tables *last_table;
+ struct recent_ip_list *r_list;
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match() called\n");
+#endif
+
+ /* Default is false ^ info->invert */
+ ans = info->invert;
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match(): name = '%s'\n",info->name);
+#endif
+
+ /* if out != NULL then routing has been done and TTL changed.
+ * We change it back here internally for match what came in before routing. */
+ if(out) ttl++;
+
+ /* Find the right table */
+ spin_lock_bh(&recent_lock);
+ curr_table = r_tables;
+ while( (last_table = curr_table) && strncmp(info->name,curr_table->name,IPT_RECENT_NAME_LEN) && (curr_table = curr_table->next) );
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match(): table found('%s')\n",info->name);
+#endif
+
+ spin_unlock_bh(&recent_lock);
+
+ /* Table with this name not found, match impossible */
+ if(!curr_table) { return ans; }
+
+ /* Make sure no one is changing the list while we work with it */
+ spin_lock_bh(&curr_table->list_lock);
+
+ r_list = curr_table->table;
+ if(info->side == IPT_RECENT_DEST) addr = skb->nh.iph->daddr; else addr = skb->nh.iph->saddr;
+
+ if(!addr) {
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match() address (%u) invalid, leaving.\n",addr);
+#endif
+ spin_unlock_bh(&curr_table->list_lock);
+ return ans;
+ }
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match(): checking table, addr: %u, ttl: %u, orig_ttl: %u\n",addr,ttl,skb->nh.iph->ttl);
+#endif
+
+ /* Get jiffies now in case they changed while we were waiting for a lock */
+ now = jiffies;
+ hash_table = curr_table->hash_table;
+ time_info = curr_table->time_info;
+
+ orig_hash_result = hash_result = hash_func(addr,ip_list_hash_size);
+ /* Hash entry at this result used */
+ /* Check for TTL match if requested. If TTL is zero then a match would never
+ * happen, so match regardless of existing TTL in that case. Zero means the
+ * entry was added via the /proc interface anyway, so we will just use the
+ * first TTL we get for that IP address. */
+ if(info->check_set & IPT_RECENT_TTL) {
+ while(hash_table[hash_result] != -1 && !(r_list[hash_table[hash_result]].addr == addr &&
+ (!r_list[hash_table[hash_result]].ttl || r_list[hash_table[hash_result]].ttl == ttl))) {
+ /* Collision in hash table */
+ hash_result = (hash_result + 1) % ip_list_hash_size;
+ }
+ } else {
+ while(hash_table[hash_result] != -1 && r_list[hash_table[hash_result]].addr != addr) {
+ /* Collision in hash table */
+ hash_result = (hash_result + 1) % ip_list_hash_size;
+ }
+ }
+
+ if(hash_table[hash_result] == -1 && !(info->check_set & IPT_RECENT_SET)) {
+ /* IP not in list and not asked to SET */
+ spin_unlock_bh(&curr_table->list_lock);
+ return ans;
+ }
+
+ /* Check if we need to handle the collision, do not need to on REMOVE */
+ if(orig_hash_result != hash_result && !(info->check_set & IPT_RECENT_REMOVE)) {
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match(): Collision in hash table. (or: %d,hr: %d,oa: %u,ha: %u)\n",
+ orig_hash_result,
+ hash_result,
+ r_list[hash_table[orig_hash_result]].addr,
+ addr);
+#endif
+
+ /* We had a collision.
+ * orig_hash_result is where we started, hash_result is where we ended up.
+ * So, swap them because we are likely to see the same guy again sooner */
+#ifdef DEBUG
+ if(debug) {
+ printk(KERN_INFO RECENT_NAME ": match(): Collision; hash_table[orig_hash_result] = %d\n",hash_table[orig_hash_result]);
+ printk(KERN_INFO RECENT_NAME ": match(): Collision; r_list[hash_table[orig_hash_result]].hash_entry = %d\n",
+ r_list[hash_table[orig_hash_result]].hash_entry);
+ }
+#endif
+
+ r_list[hash_table[orig_hash_result]].hash_entry = hash_result;
+
+
+ temp = hash_table[orig_hash_result];
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match(): Collision; hash_table[hash_result] = %d\n",hash_table[hash_result]);
+#endif
+ hash_table[orig_hash_result] = hash_table[hash_result];
+ hash_table[hash_result] = temp;
+ temp = hash_result;
+ hash_result = orig_hash_result;
+ orig_hash_result = temp;
+ time_info[r_list[hash_table[orig_hash_result]].time_pos].position = hash_table[orig_hash_result];
+ if(hash_table[hash_result] != -1) {
+ r_list[hash_table[hash_result]].hash_entry = hash_result;
+ time_info[r_list[hash_table[hash_result]].time_pos].position = hash_table[hash_result];
+ }
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match(): Collision handled.\n");
+#endif
+ }
+
+ if(hash_table[hash_result] == -1) {
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match(): New table entry. (hr: %d,ha: %u)\n",
+ hash_result, addr);
+#endif
+
+ /* New item found and IPT_RECENT_SET, so we need to add it */
+ location = time_info[curr_table->time_pos].position;
+ hash_table[r_list[location].hash_entry] = -1;
+ hash_table[hash_result] = location;
+ memset(r_list[location].last_pkts,0,ip_pkt_list_tot*sizeof(u_int32_t));
+ r_list[location].time_pos = curr_table->time_pos;
+ r_list[location].addr = addr;
+ r_list[location].ttl = ttl;
+ r_list[location].last_seen = now;
+ r_list[location].oldest_pkt = 1;
+ r_list[location].last_pkts[0] = now;
+ r_list[location].hash_entry = hash_result;
+ time_info[curr_table->time_pos].time = r_list[location].last_seen;
+ curr_table->time_pos = (curr_table->time_pos + 1) % ip_list_tot;
+
+ ans = !info->invert;
+ } else {
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match(): Existing table entry. (hr: %d,ha: %u)\n",
+ hash_result,
+ addr);
+#endif
+
+ /* Existing item found */
+ location = hash_table[hash_result];
+ /* We have a match on address, now to make sure it meets all requirements for a
+ * full match. */
+ if(info->check_set & IPT_RECENT_CHECK || info->check_set & IPT_RECENT_UPDATE) {
+ if(!info->seconds && !info->hit_count) ans = !info->invert; else ans = info->invert;
+ if(info->seconds && !info->hit_count) {
+ if(time_before_eq(now,r_list[location].last_seen+info->seconds*HZ)) ans = !info->invert; else ans = info->invert;
+ }
+ if(info->seconds && info->hit_count) {
+ for(pkt_count = 0, hits_found = 0; pkt_count < ip_pkt_list_tot; pkt_count++) {
+ if(time_before_eq(now,r_list[location].last_pkts[pkt_count]+info->seconds*HZ)) hits_found++;
+ }
+ if(hits_found >= info->hit_count) ans = !info->invert; else ans = info->invert;
+ }
+ if(info->hit_count && !info->seconds) {
+ for(pkt_count = 0, hits_found = 0; pkt_count < ip_pkt_list_tot; pkt_count++) {
+ if(r_list[location].last_pkts[pkt_count] == 0) break;
+ hits_found++;
+ }
+ if(hits_found >= info->hit_count) ans = !info->invert; else ans = info->invert;
+ }
+ }
+#ifdef DEBUG
+ if(debug) {
+ if(ans)
+ printk(KERN_INFO RECENT_NAME ": match(): match addr: %u\n",addr);
+ else
+ printk(KERN_INFO RECENT_NAME ": match(): no match addr: %u\n",addr);
+ }
+#endif
+
+ /* If and only if we have been asked to SET, or to UPDATE (on match) do we add the
+ * current timestamp to the last_seen. */
+ if((info->check_set & IPT_RECENT_SET && (ans = !info->invert)) || (info->check_set & IPT_RECENT_UPDATE && ans)) {
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match(): SET or UPDATE; updating time info.\n");
+#endif
+ /* Have to update our time info */
+ time_loc = r_list[location].time_pos;
+ time_info[time_loc].time = now;
+ time_info[time_loc].position = location;
+ while((time_info[(time_loc+1) % ip_list_tot].time < time_info[time_loc].time) && ((time_loc+1) % ip_list_tot) != curr_table->time_pos) {
+ time_temp = time_info[time_loc].time;
+ time_info[time_loc].time = time_info[(time_loc+1)%ip_list_tot].time;
+ time_info[(time_loc+1)%ip_list_tot].time = time_temp;
+ time_temp = time_info[time_loc].position;
+ time_info[time_loc].position = time_info[(time_loc+1)%ip_list_tot].position;
+ time_info[(time_loc+1)%ip_list_tot].position = time_temp;
+ r_list[time_info[time_loc].position].time_pos = time_loc;
+ r_list[time_info[(time_loc+1)%ip_list_tot].position].time_pos = (time_loc+1)%ip_list_tot;
+ time_loc = (time_loc+1) % ip_list_tot;
+ }
+ r_list[location].time_pos = time_loc;
+ r_list[location].ttl = ttl;
+ r_list[location].last_pkts[r_list[location].oldest_pkt] = now;
+ r_list[location].oldest_pkt = ++r_list[location].oldest_pkt % ip_pkt_list_tot;
+ r_list[location].last_seen = now;
+ }
+ /* If we have been asked to remove the entry from the list, just set it to 0 */
+ if(info->check_set & IPT_RECENT_REMOVE) {
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match(): REMOVE; clearing entry (or: %d, hr: %d).\n",orig_hash_result,hash_result);
+#endif
+ /* Check if this is part of a collision chain */
+ while(hash_table[(orig_hash_result+1) % ip_list_hash_size] != -1) {
+ orig_hash_result++;
+ if(hash_func(r_list[hash_table[orig_hash_result]].addr,ip_list_hash_size) == hash_result) {
+ /* Found collision chain, how deep does this rabbit hole go? */
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match(): REMOVE; found collision chain.\n");
+#endif
+ end_collision_chain = orig_hash_result;
+ }
+ }
+ if(end_collision_chain != -1) {
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match(): REMOVE; part of collision chain, moving to end.\n");
+#endif
+ /* Part of a collision chain, swap it with the end of the chain
+ * before removing. */
+ r_list[hash_table[end_collision_chain]].hash_entry = hash_result;
+ temp = hash_table[end_collision_chain];
+ hash_table[end_collision_chain] = hash_table[hash_result];
+ hash_table[hash_result] = temp;
+ time_info[r_list[hash_table[hash_result]].time_pos].position = hash_table[hash_result];
+ hash_result = end_collision_chain;
+ r_list[hash_table[hash_result]].hash_entry = hash_result;
+ time_info[r_list[hash_table[hash_result]].time_pos].position = hash_table[hash_result];
+ }
+ location = hash_table[hash_result];
+ hash_table[r_list[location].hash_entry] = -1;
+ time_loc = r_list[location].time_pos;
+ time_info[time_loc].time = 0;
+ time_info[time_loc].position = location;
+ while((time_info[(time_loc+1) % ip_list_tot].time < time_info[time_loc].time) && ((time_loc+1) % ip_list_tot) != curr_table->time_pos) {
+ time_temp = time_info[time_loc].time;
+ time_info[time_loc].time = time_info[(time_loc+1)%ip_list_tot].time;
+ time_info[(time_loc+1)%ip_list_tot].time = time_temp;
+ time_temp = time_info[time_loc].position;
+ time_info[time_loc].position = time_info[(time_loc+1)%ip_list_tot].position;
+ time_info[(time_loc+1)%ip_list_tot].position = time_temp;
+ r_list[time_info[time_loc].position].time_pos = time_loc;
+ r_list[time_info[(time_loc+1)%ip_list_tot].position].time_pos = (time_loc+1)%ip_list_tot;
+ time_loc = (time_loc+1) % ip_list_tot;
+ }
+ r_list[location].time_pos = time_loc;
+ r_list[location].last_seen = 0;
+ r_list[location].addr = 0;
+ r_list[location].ttl = 0;
+ memset(r_list[location].last_pkts,0,ip_pkt_list_tot*sizeof(u_int32_t));
+ r_list[location].oldest_pkt = 0;
+ ans = !info->invert;
+ }
+ spin_unlock_bh(&curr_table->list_lock);
+ return ans;
+ }
+
+ spin_unlock_bh(&curr_table->list_lock);
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": match() left.\n");
+#endif
+ return ans;
+}
+
+/* This function is to verify that the rule given during the userspace iptables
+ * command is correct.
+ * If the command is valid then we check if the table name referred to by the
+ * rule exists, if not it is created.
+ */
+static int
+checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ int flag = 0, c;
+ u_int32_t *hold;
+ const struct ipt_recent_info *info = matchinfo;
+ struct recent_ip_tables *curr_table, *find_table, *last_table;
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": checkentry() entered.\n");
+#endif
+
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_recent_info))) return 0;
+
+ /* seconds and hit_count only valid for CHECK/UPDATE */
+ if(info->check_set & IPT_RECENT_SET) { flag++; if(info->seconds || info->hit_count) return 0; }
+ if(info->check_set & IPT_RECENT_REMOVE) { flag++; if(info->seconds || info->hit_count) return 0; }
+ if(info->check_set & IPT_RECENT_CHECK) flag++;
+ if(info->check_set & IPT_RECENT_UPDATE) flag++;
+
+ /* One and only one of these should ever be set */
+ if(flag != 1) return 0;
+
+ /* Name must be set to something */
+ if(!info->name || !info->name[0]) return 0;
+
+ /* Things look good, create a list for this if it does not exist */
+ /* Lock the linked list while we play with it */
+ spin_lock_bh(&recent_lock);
+
+ /* Look for an entry with this name already created */
+ /* Finds the end of the list and the entry before the end if current name does not exist */
+ find_table = r_tables;
+ while( (last_table = find_table) && strncmp(info->name,find_table->name,IPT_RECENT_NAME_LEN) && (find_table = find_table->next) );
+
+ /* If a table already exists just increment the count on that table and return */
+ if(find_table) {
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": checkentry: table found (%s), incrementing count.\n",info->name);
+#endif
+ find_table->count++;
+ spin_unlock_bh(&recent_lock);
+ return 1;
+ }
+
+ spin_unlock_bh(&recent_lock);
+
+ /* Table with this name not found */
+ /* Allocate memory for new linked list item */
+
+#ifdef DEBUG
+ if(debug) {
+ printk(KERN_INFO RECENT_NAME ": checkentry: no table found (%s)\n",info->name);
+ printk(KERN_INFO RECENT_NAME ": checkentry: Allocationg %d for link-list entry.\n",sizeof(struct recent_ip_tables));
+ }
+#endif
+
+ curr_table = vmalloc(sizeof(struct recent_ip_tables));
+ if(curr_table == NULL) return 0;
+
+ curr_table->list_lock = SPIN_LOCK_UNLOCKED;
+ curr_table->next = NULL;
+ curr_table->count = 1;
+ curr_table->time_pos = 0;
+ strncpy(curr_table->name,info->name,IPT_RECENT_NAME_LEN);
+ curr_table->name[IPT_RECENT_NAME_LEN-1] = '\0';
+
+ /* Allocate memory for this table and the list of packets in each entry. */
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": checkentry: Allocating %d for table (%s).\n",
+ sizeof(struct recent_ip_list)*ip_list_tot,
+ info->name);
+#endif
+
+ curr_table->table = vmalloc(sizeof(struct recent_ip_list)*ip_list_tot);
+ if(curr_table->table == NULL) { vfree(curr_table); return 0; }
+ memset(curr_table->table,0,sizeof(struct recent_ip_list)*ip_list_tot);
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": checkentry: Allocating %d for pkt_list.\n",
+ sizeof(u_int32_t)*ip_pkt_list_tot*ip_list_tot);
+#endif
+
+ hold = vmalloc(sizeof(u_int32_t)*ip_pkt_list_tot*ip_list_tot);
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": checkentry: After pkt_list allocation.\n");
+#endif
+ if(hold == NULL) {
+ printk(KERN_INFO RECENT_NAME ": checkentry: unable to allocate for pkt_list.\n");
+ vfree(curr_table->table);
+ vfree(curr_table);
+ return 0;
+ }
+ for(c = 0; c < ip_list_tot; c++) {
+ curr_table->table[c].last_pkts = hold + c*ip_pkt_list_tot;
+ }
+
+ /* Allocate memory for the hash table */
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": checkentry: Allocating %d for hash_table.\n",
+ sizeof(int)*ip_list_hash_size);
+#endif
+
+ curr_table->hash_table = vmalloc(sizeof(int)*ip_list_hash_size);
+ if(!curr_table->hash_table) {
+ printk(KERN_INFO RECENT_NAME ": checkentry: unable to allocate for hash_table.\n");
+ vfree(hold);
+ vfree(curr_table->table);
+ vfree(curr_table);
+ return 0;
+ }
+
+ for(c = 0; c < ip_list_hash_size; c++) {
+ curr_table->hash_table[c] = -1;
+ }
+
+ /* Allocate memory for the time info */
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": checkentry: Allocating %d for time_info.\n",
+ sizeof(struct time_info_list)*ip_list_tot);
+#endif
+
+ curr_table->time_info = vmalloc(sizeof(struct time_info_list)*ip_list_tot);
+ if(!curr_table->time_info) {
+ printk(KERN_INFO RECENT_NAME ": checkentry: unable to allocate for time_info.\n");
+ vfree(curr_table->hash_table);
+ vfree(hold);
+ vfree(curr_table->table);
+ vfree(curr_table);
+ return 0;
+ }
+ for(c = 0; c < ip_list_tot; c++) {
+ curr_table->time_info[c].position = c;
+ curr_table->time_info[c].time = 0;
+ }
+
+ /* Put the new table in place */
+ spin_lock_bh(&recent_lock);
+ find_table = r_tables;
+ while( (last_table = find_table) && strncmp(info->name,find_table->name,IPT_RECENT_NAME_LEN) && (find_table = find_table->next) );
+
+ /* If a table already exists just increment the count on that table and return */
+ if(find_table) {
+ find_table->count++;
+ spin_unlock_bh(&recent_lock);
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": checkentry: table found (%s), created by other process.\n",info->name);
+#endif
+ vfree(curr_table->time_info);
+ vfree(curr_table->hash_table);
+ vfree(hold);
+ vfree(curr_table->table);
+ vfree(curr_table);
+ return 1;
+ }
+ if(!last_table) r_tables = curr_table; else last_table->next = curr_table;
+
+ spin_unlock_bh(&recent_lock);
+
+#ifdef CONFIG_PROC_FS
+ /* Create our proc 'status' entry. */
+ curr_table->status_proc = create_proc_entry(curr_table->name, ip_list_perms, proc_net_ipt_recent);
+ if (!curr_table->status_proc) {
+ printk(KERN_INFO RECENT_NAME ": checkentry: unable to allocate for /proc entry.\n");
+ /* Destroy the created table */
+ spin_lock_bh(&recent_lock);
+ last_table = NULL;
+ curr_table = r_tables;
+ if(!curr_table) {
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": checkentry() create_proc failed, no tables.\n");
+#endif
+ spin_unlock_bh(&recent_lock);
+ return 0;
+ }
+ while( strncmp(info->name,curr_table->name,IPT_RECENT_NAME_LEN) && (last_table = curr_table) && (curr_table = curr_table->next) );
+ if(!curr_table) {
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": checkentry() create_proc failed, table already destroyed.\n");
+#endif
+ spin_unlock_bh(&recent_lock);
+ return 0;
+ }
+ if(last_table) last_table->next = curr_table->next; else r_tables = curr_table->next;
+ spin_unlock_bh(&recent_lock);
+ vfree(curr_table->time_info);
+ vfree(curr_table->hash_table);
+ vfree(hold);
+ vfree(curr_table->table);
+ vfree(curr_table);
+ return 0;
+ }
+
+ curr_table->status_proc->owner = THIS_MODULE;
+ curr_table->status_proc->data = curr_table;
+ wmb();
+ curr_table->status_proc->read_proc = ip_recent_get_info;
+ curr_table->status_proc->write_proc = ip_recent_ctrl;
+#endif /* CONFIG_PROC_FS */
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": checkentry() left.\n");
+#endif
+
+ return 1;
+}
+
+/* This function is called in the event that a rule matching this module is
+ * removed.
+ * When this happens we need to check if there are no other rules matching
+ * the table given. If that is the case then we remove the table and clean
+ * up its memory.
+ */
+static void
+destroy(void *matchinfo, unsigned int matchsize)
+{
+ const struct ipt_recent_info *info = matchinfo;
+ struct recent_ip_tables *curr_table, *last_table;
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": destroy() entered.\n");
+#endif
+
+ if(matchsize != IPT_ALIGN(sizeof(struct ipt_recent_info))) return;
+
+ /* Lock the linked list while we play with it */
+ spin_lock_bh(&recent_lock);
+
+ /* Look for an entry with this name already created */
+ /* Finds the end of the list and the entry before the end if current name does not exist */
+ last_table = NULL;
+ curr_table = r_tables;
+ if(!curr_table) {
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": destroy() No tables found, leaving.\n");
+#endif
+ spin_unlock_bh(&recent_lock);
+ return;
+ }
+ while( strncmp(info->name,curr_table->name,IPT_RECENT_NAME_LEN) && (last_table = curr_table) && (curr_table = curr_table->next) );
+
+ /* If a table does not exist then do nothing and return */
+ if(!curr_table) {
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": destroy() table not found, leaving.\n");
+#endif
+ spin_unlock_bh(&recent_lock);
+ return;
+ }
+
+ curr_table->count--;
+
+ /* If count is still non-zero then there are still rules referenceing it so we do nothing */
+ if(curr_table->count) {
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": destroy() table found, non-zero count, leaving.\n");
+#endif
+ spin_unlock_bh(&recent_lock);
+ return;
+ }
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": destroy() table found, zero count, removing.\n");
+#endif
+
+ /* Count must be zero so we remove this table from the list */
+ if(last_table) last_table->next = curr_table->next; else r_tables = curr_table->next;
+
+ spin_unlock_bh(&recent_lock);
+
+ /* lock to make sure any late-runners still using this after we removed it from
+ * the list finish up then remove everything */
+ spin_lock_bh(&curr_table->list_lock);
+ spin_unlock_bh(&curr_table->list_lock);
+
+#ifdef CONFIG_PROC_FS
+ if(curr_table->status_proc) remove_proc_entry(curr_table->name,proc_net_ipt_recent);
+#endif /* CONFIG_PROC_FS */
+ vfree(curr_table->table[0].last_pkts);
+ vfree(curr_table->table);
+ vfree(curr_table->hash_table);
+ vfree(curr_table->time_info);
+ vfree(curr_table);
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": destroy() left.\n");
+#endif
+
+ return;
+}
+
+/* This is the structure we pass to ipt_register to register our
+ * module with iptables.
+ */
+static struct ipt_match recent_match = {
+ .name = "recent",
+ .match = &match,
+ .checkentry = &checkentry,
+ .destroy = &destroy,
+ .me = THIS_MODULE
+};
+
+/* Kernel module initialization. */
+static int __init init(void)
+{
+ int err, count;
+
+ printk(version);
+#ifdef CONFIG_PROC_FS
+ proc_net_ipt_recent = proc_mkdir("ipt_recent",proc_net);
+ if(!proc_net_ipt_recent) return -ENOMEM;
+#endif
+
+ if(ip_list_hash_size && ip_list_hash_size <= ip_list_tot) {
+ printk(KERN_WARNING RECENT_NAME ": ip_list_hash_size too small, resetting to default.\n");
+ ip_list_hash_size = 0;
+ }
+
+ if(!ip_list_hash_size) {
+ ip_list_hash_size = ip_list_tot*3;
+ count = 2*2;
+ while(ip_list_hash_size > count) count = count*2;
+ ip_list_hash_size = count;
+ }
+
+#ifdef DEBUG
+ if(debug) printk(KERN_INFO RECENT_NAME ": ip_list_hash_size: %d\n",ip_list_hash_size);
+#endif
+
+ err = ipt_register_match(&recent_match);
+ if (err)
+ remove_proc_entry("ipt_recent", proc_net);
+ return err;
+}
+
+/* Kernel module destruction. */
+static void __exit fini(void)
+{
+ ipt_unregister_match(&recent_match);
+
+ remove_proc_entry("ipt_recent",proc_net);
+}
+
+/* Register our module with the kernel. */
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_state.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_state.c
new file mode 100644
index 0000000..6c2f940
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_state.c
@@ -0,0 +1,59 @@
+/* Kernel module to match connection tracking information.
+ * GPL (C) 1999 Rusty Russell (rusty@rustcorp.com.au).
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter_ipv4/ip_conntrack.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_state.h>
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_state_info *sinfo = matchinfo;
+ enum ip_conntrack_info ctinfo;
+ unsigned int statebit;
+
+ if (!ip_conntrack_get((struct sk_buff *)skb, &ctinfo))
+ statebit = IPT_STATE_INVALID;
+ else
+ statebit = IPT_STATE_BIT(ctinfo);
+
+ return (sinfo->statemask & statebit);
+}
+
+static int check(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_state_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ipt_match state_match
+= { { NULL, NULL }, "state", &match, &check, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&state_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&state_match);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_string.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_string.c
new file mode 100644
index 0000000..b9dfe66
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_string.c
@@ -0,0 +1,216 @@
+/* Kernel module to match a string into a packet.
+ *
+ * Copyright (C) 2000 Emmanuel Roger <winfield@freegates.be>
+ *
+ * ChangeLog
+ * 19.02.2002: Gianni Tedesco <gianni@ecsc.co.uk>
+ * Fixed SMP re-entrancy problem using per-cpu data areas
+ * for the skip/shift tables.
+ * 02.05.2001: Gianni Tedesco <gianni@ecsc.co.uk>
+ * Fixed kernel panic, due to overrunning boyer moore string
+ * tables. Also slightly tweaked heuristic for deciding what
+ * search algo to use.
+ * 27.01.2001: Gianni Tedesco <gianni@ecsc.co.uk>
+ * Implemented Boyer Moore Sublinear search algorithm
+ * alongside the existing linear search based on memcmp().
+ * Also a quick check to decide which method to use on a per
+ * packet basis.
+ */
+
+#include <linux/smp.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/file.h>
+#include <net/sock.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_string.h>
+
+struct string_per_cpu {
+ int *skip;
+ int *shift;
+ int *len;
+};
+
+struct string_per_cpu *bm_string_data=NULL;
+
+/* Boyer Moore Sublinear string search - VERY FAST */
+char *search_sublinear (char *needle, char *haystack, int needle_len, int haystack_len)
+{
+ int M1, right_end, sk, sh;
+ int ended, j, i;
+
+ int *skip, *shift, *len;
+
+ /* use data suitable for this CPU */
+ shift=bm_string_data[smp_processor_id()].shift;
+ skip=bm_string_data[smp_processor_id()].skip;
+ len=bm_string_data[smp_processor_id()].len;
+
+ /* Setup skip/shift tables */
+ M1 = right_end = needle_len-1;
+ for (i = 0; i < BM_MAX_HLEN; i++) skip[i] = needle_len;
+ for (i = 0; needle[i]; i++) skip[needle[i]] = M1 - i;
+
+ for (i = 1; i < needle_len; i++) {
+ for (j = 0; j < needle_len && needle[M1 - j] == needle[M1 - i - j]; j++);
+ len[i] = j;
+ }
+
+ shift[0] = 1;
+ for (i = 1; i < needle_len; i++) shift[i] = needle_len;
+ for (i = M1; i > 0; i--) shift[len[i]] = i;
+ ended = 0;
+
+ for (i = 0; i < needle_len; i++) {
+ if (len[i] == M1 - i) ended = i;
+ if (ended) shift[i] = ended;
+ }
+
+ /* Do the search*/
+ while (right_end < haystack_len)
+ {
+ for (i = 0; i < needle_len && haystack[right_end - i] == needle[M1 - i]; i++);
+ if (i == needle_len) {
+ return haystack+(right_end - M1);
+ }
+
+ sk = skip[haystack[right_end - i]];
+ sh = shift[i];
+ right_end = max(right_end - i + sk, right_end + sh);
+ }
+
+ return NULL;
+}
+
+/* Linear string search based on memcmp() */
+char *search_linear (char *needle, char *haystack, int needle_len, int haystack_len)
+{
+ char *k = haystack + (haystack_len-needle_len);
+ char *t = haystack;
+
+ while ( t <= k ) {
+ if (memcmp(t, needle, needle_len) == 0)
+ return t;
+ t++;
+ }
+
+ return NULL;
+}
+
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_string_info *info = matchinfo;
+ struct iphdr *ip = skb->nh.iph;
+ int hlen, nlen;
+ char *needle, *haystack;
+ proc_ipt_search search=search_linear;
+
+ if ( !ip ) return 0;
+
+ /* get lenghts, and validate them */
+ nlen=info->len;
+ hlen=ntohs(ip->tot_len)-(ip->ihl*4);
+ if ( nlen > hlen ) return 0;
+
+ needle=(char *)&info->string;
+ haystack=(char *)ip+(ip->ihl*4);
+
+ /* The sublinear search comes in to its own
+ * on the larger packets */
+ if ( (hlen>IPT_STRING_HAYSTACK_THRESH) &&
+ (nlen>IPT_STRING_NEEDLE_THRESH) ) {
+ if ( hlen < BM_MAX_HLEN ) {
+ search=search_sublinear;
+ }else{
+ if (net_ratelimit())
+ printk(KERN_INFO "ipt_string: Packet too big "
+ "to attempt sublinear string search "
+ "(%d bytes)\n", hlen );
+ }
+ }
+
+ return ((search(needle, haystack, nlen, hlen)!=NULL) ^ info->invert);
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_string_info)))
+ return 0;
+
+ return 1;
+}
+
+void string_freeup_data(void)
+{
+ int c;
+
+ if ( bm_string_data ) {
+ for(c=0; c<smp_num_cpus; c++) {
+ if ( bm_string_data[c].shift ) kfree(bm_string_data[c].shift);
+ if ( bm_string_data[c].skip ) kfree(bm_string_data[c].skip);
+ if ( bm_string_data[c].len ) kfree(bm_string_data[c].len);
+ }
+ kfree(bm_string_data);
+ }
+}
+
+static struct ipt_match string_match
+= { { NULL, NULL }, "string", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ int c;
+ size_t tlen;
+ size_t alen;
+
+ tlen=sizeof(struct string_per_cpu)*smp_num_cpus;
+ alen=sizeof(int)*BM_MAX_HLEN;
+
+ /* allocate array of structures */
+ if ( !(bm_string_data=kmalloc(tlen,GFP_KERNEL)) ) {
+ return 0;
+ }
+
+ memset(bm_string_data, 0, tlen);
+
+ /* allocate our skip/shift tables */
+ for(c=0; c<smp_num_cpus; c++) {
+ if ( !(bm_string_data[c].shift=kmalloc(alen, GFP_KERNEL)) )
+ goto alloc_fail;
+ if ( !(bm_string_data[c].skip=kmalloc(alen, GFP_KERNEL)) )
+ goto alloc_fail;
+ if ( !(bm_string_data[c].len=kmalloc(alen, GFP_KERNEL)) )
+ goto alloc_fail;
+ }
+
+ return ipt_register_match(&string_match);
+
+alloc_fail:
+ string_freeup_data();
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&string_match);
+ string_freeup_data();
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_tcpmss.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_tcpmss.c
new file mode 100644
index 0000000..001f7a8
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_tcpmss.c
@@ -0,0 +1,109 @@
+/* Kernel module to match TCP MSS values. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <net/tcp.h>
+
+#include <linux/netfilter_ipv4/ipt_tcpmss.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+#define TH_SYN 0x02
+
+/* Returns 1 if the mss option is set and matched by the range, 0 otherwise */
+static inline int
+mssoption_match(u_int16_t min, u_int16_t max,
+ const struct tcphdr *tcp,
+ u_int16_t datalen,
+ int invert,
+ int *hotdrop)
+{
+ unsigned int i;
+ const u_int8_t *opt = (u_int8_t *)tcp;
+
+ /* If we don't have the whole header, drop packet. */
+ if (tcp->doff * 4 > datalen) {
+ *hotdrop = 1;
+ return 0;
+ }
+
+ for (i = sizeof(struct tcphdr); i < tcp->doff * 4; ) {
+ if ((opt[i] == TCPOPT_MSS)
+ && ((tcp->doff * 4 - i) >= TCPOLEN_MSS)
+ && (opt[i+1] == TCPOLEN_MSS)) {
+ u_int16_t mssval;
+
+ mssval = (opt[i+2] << 8) | opt[i+3];
+
+ return (mssval >= min && mssval <= max) ^ invert;
+ }
+ if (opt[i] < 2) i++;
+ else i += opt[i+1]?:1;
+ }
+
+ return invert;
+}
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_tcpmss_match_info *info = matchinfo;
+ const struct tcphdr *tcph = (void *)skb->nh.iph + skb->nh.iph->ihl*4;
+
+ return mssoption_match(info->mss_min, info->mss_max, tcph,
+ skb->len - skb->nh.iph->ihl*4,
+ info->invert, hotdrop);
+}
+
+static inline int find_syn_match(const struct ipt_entry_match *m)
+{
+ const struct ipt_tcp *tcpinfo = (const struct ipt_tcp *)m->data;
+
+ if (strcmp(m->u.kernel.match->name, "tcp") == 0
+ && (tcpinfo->flg_cmp & TH_SYN)
+ && !(tcpinfo->invflags & IPT_TCP_INV_FLAGS))
+ return 1;
+
+ return 0;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_tcpmss_match_info)))
+ return 0;
+
+ /* Must specify -p tcp */
+ if (ip->proto != IPPROTO_TCP || (ip->invflags & IPT_INV_PROTO)) {
+ printk("tcpmss: Only works on TCP packets\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ipt_match tcpmss_match
+= { { NULL, NULL }, "tcpmss", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&tcpmss_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&tcpmss_match);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_time.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_time.c
new file mode 100644
index 0000000..1a71d5d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_time.c
@@ -0,0 +1,189 @@
+/*
+ This is a module which is used for time matching
+ It is using some modified code from dietlibc (localtime() function)
+ that you can find at http://www.fefe.de/dietlibc/
+ This file is distributed under the terms of the GNU General Public
+ License (GPL). Copies of the GPL can be obtained from: ftp://prep.ai.mit.edu/pub/gnu/GPL
+ 2001-05-04 Fabrice MARIE <fabrice@netfilter.org> : initial development.
+ 2001-21-05 Fabrice MARIE <fabrice@netfilter.org> : bug fix in the match code,
+ thanks to "Zeng Yu" <zengy@capitel.com.cn> for bug report.
+ 2001-26-09 Fabrice MARIE <fabrice@netfilter.org> : force the match to be in LOCAL_IN or PRE_ROUTING only.
+ 2001-30-11 Fabrice : added the possibility to use the match in FORWARD/OUTPUT with a little hack,
+ added Nguyen Dang Phuoc Dong <dongnd@tlnet.com.vn> patch to support timezones.
+ 2004-05-02 Fabrice : added support for date matching, from an idea of Fabien COELHO.
+*/
+
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv4/ipt_time.h>
+#include <linux/time.h>
+
+MODULE_AUTHOR("Fabrice MARIE <fabrice@netfilter.org>");
+MODULE_DESCRIPTION("Match arrival timestamp/date");
+MODULE_LICENSE("GPL");
+
+struct tm
+{
+ int tm_sec; /* Seconds. [0-60] (1 leap second) */
+ int tm_min; /* Minutes. [0-59] */
+ int tm_hour; /* Hours. [0-23] */
+ int tm_mday; /* Day. [1-31] */
+ int tm_mon; /* Month. [0-11] */
+ int tm_year; /* Year - 1900. */
+ int tm_wday; /* Day of week. [0-6] */
+ int tm_yday; /* Days in year.[0-365] */
+ int tm_isdst; /* DST. [-1/0/1]*/
+
+ long int tm_gmtoff; /* we don't care, we count from GMT */
+ const char *tm_zone; /* we don't care, we count from GMT */
+};
+
+void
+localtime(const time_t *timepr, struct tm *r);
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_time_info *info = matchinfo; /* match info for rule */
+ struct tm currenttime; /* time human readable */
+ u_int8_t days_of_week[7] = {64, 32, 16, 8, 4, 2, 1};
+ u_int16_t packet_time;
+ struct timeval kerneltimeval;
+ time_t packet_local_time;
+
+ /* if kerneltime=1, we don't read the skb->timestamp but kernel time instead */
+ if (info->kerneltime)
+ {
+ do_gettimeofday(&kerneltimeval);
+ packet_local_time = kerneltimeval.tv_sec;
+ }
+ else
+ packet_local_time = skb->stamp.tv_sec;
+
+ /* First we make sure we are in the date start-stop boundaries */
+ if ((packet_local_time < info->date_start) || (packet_local_time > info->date_stop))
+ return 0; /* We are outside the date boundaries */
+
+ /* Transform the timestamp of the packet, in a human readable form */
+ localtime(&packet_local_time, &currenttime);
+
+ /* check if we match this timestamp, we start by the days... */
+ if ((days_of_week[currenttime.tm_wday] & info->days_match) != days_of_week[currenttime.tm_wday])
+ return 0; /* the day doesn't match */
+
+ /* ... check the time now */
+ packet_time = (currenttime.tm_hour * 60) + currenttime.tm_min;
+ if ((packet_time < info->time_start) || (packet_time > info->time_stop))
+ return 0;
+
+ /* here we match ! */
+ return 1;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ struct ipt_time_info *info = matchinfo; /* match info for rule */
+
+ /* First, check that we are in the correct hooks */
+ if (hook_mask
+ & ~((1 << NF_IP_PRE_ROUTING) | (1 << NF_IP_LOCAL_IN) | (1 << NF_IP_FORWARD) | (1 << NF_IP_LOCAL_OUT)))
+ {
+ printk("ipt_time: error, only valid for PRE_ROUTING, LOCAL_IN, FORWARD and OUTPUT)\n");
+ return 0;
+ }
+ /* we use the kerneltime if we are in forward or output */
+ info->kerneltime = 1;
+ if (hook_mask & ~((1 << NF_IP_FORWARD) | (1 << NF_IP_LOCAL_OUT)))
+ /* we use the skb time */
+ info->kerneltime = 0;
+
+ /* Check the size */
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_time_info)))
+ return 0;
+ /* Now check the coherence of the data ... */
+ if ((info->time_start > 1439) || /* 23*60+59 = 1439*/
+ (info->time_stop > 1439))
+ {
+ printk(KERN_WARNING "ipt_time: invalid argument\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ipt_match time_match
+= { { NULL, NULL }, "time", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ printk("ipt_time loading\n");
+ return ipt_register_match(&time_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&time_match);
+ printk("ipt_time unloaded\n");
+}
+
+module_init(init);
+module_exit(fini);
+
+
+/* The part below is borowed and modified from dietlibc */
+
+/* seconds per day */
+#define SPD 24*60*60
+
+void
+localtime(const time_t *timepr, struct tm *r) {
+ time_t i;
+ time_t timep;
+ extern struct timezone sys_tz;
+ const unsigned int __spm[12] =
+ { 0,
+ (31),
+ (31+28),
+ (31+28+31),
+ (31+28+31+30),
+ (31+28+31+30+31),
+ (31+28+31+30+31+30),
+ (31+28+31+30+31+30+31),
+ (31+28+31+30+31+30+31+31),
+ (31+28+31+30+31+30+31+31+30),
+ (31+28+31+30+31+30+31+31+30+31),
+ (31+28+31+30+31+30+31+31+30+31+30),
+ };
+ register time_t work;
+
+ timep = (*timepr) - (sys_tz.tz_minuteswest * 60);
+ work=timep%(SPD);
+ r->tm_sec=work%60; work/=60;
+ r->tm_min=work%60; r->tm_hour=work/60;
+ work=timep/(SPD);
+ r->tm_wday=(4+work)%7;
+ for (i=1970; ; ++i) {
+ register time_t k= (!(i%4) && ((i%100) || !(i%400)))?366:365;
+ if (work>k)
+ work-=k;
+ else
+ break;
+ }
+ r->tm_year=i-1900;
+ for (i=11; i && __spm[i]>work; --i) ;
+ r->tm_mon=i;
+ r->tm_mday=work-__spm[i]+1;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_tos.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_tos.c
new file mode 100644
index 0000000..4f51305
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_tos.c
@@ -0,0 +1,52 @@
+/* Kernel module to match TOS values. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+
+#include <linux/netfilter_ipv4/ipt_tos.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_tos_info *info = matchinfo;
+ const struct iphdr *iph = skb->nh.iph;
+
+ return (iph->tos == info->tos) ^ info->invert;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_tos_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ipt_match tos_match
+= { { NULL, NULL }, "tos", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&tos_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&tos_match);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ttl.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ttl.c
new file mode 100644
index 0000000..f4227e5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_ttl.c
@@ -0,0 +1,75 @@
+/* IP tables module for matching the value of the TTL
+ *
+ * ipt_ttl.c,v 1.5 2000/11/13 11:16:08 laforge Exp
+ *
+ * (C) 2000,2001 by Harald Welte <laforge@gnumonks.org>
+ *
+ * This software is distributed under the terms GNU GPL
+ */
+
+#include <linux/module.h>
+#include <linux/skbuff.h>
+
+#include <linux/netfilter_ipv4/ipt_ttl.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+MODULE_AUTHOR("Harald Welte <laforge@gnumonks.org>");
+MODULE_DESCRIPTION("IP tables TTL matching module");
+MODULE_LICENSE("GPL");
+
+static int match(const struct sk_buff *skb, const struct net_device *in,
+ const struct net_device *out, const void *matchinfo,
+ int offset, const void *hdr, u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ipt_ttl_info *info = matchinfo;
+ const struct iphdr *iph = skb->nh.iph;
+
+ switch (info->mode) {
+ case IPT_TTL_EQ:
+ return (iph->ttl == info->ttl);
+ break;
+ case IPT_TTL_NE:
+ return (!(iph->ttl == info->ttl));
+ break;
+ case IPT_TTL_LT:
+ return (iph->ttl < info->ttl);
+ break;
+ case IPT_TTL_GT:
+ return (iph->ttl > info->ttl);
+ break;
+ default:
+ printk(KERN_WARNING "ipt_ttl: unknown mode %d\n",
+ info->mode);
+ return 0;
+ }
+
+ return 0;
+}
+
+static int checkentry(const char *tablename, const struct ipt_ip *ip,
+ void *matchinfo, unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (matchsize != IPT_ALIGN(sizeof(struct ipt_ttl_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ipt_match ttl_match = { { NULL, NULL }, "ttl", &match,
+ &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&ttl_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&ttl_match);
+
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_unclean.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_unclean.c
new file mode 100644
index 0000000..7cec769
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/ipt_unclean.c
@@ -0,0 +1,595 @@
+/* Kernel module to match suspect packets. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/udp.h>
+#include <linux/tcp.h>
+#include <linux/icmp.h>
+#include <net/checksum.h>
+
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+#define limpk(format, args...) \
+do { \
+ if (net_ratelimit()) \
+ printk("ipt_unclean: %s" format, \
+ embedded ? "(embedded packet) " : "" , ## args); \
+} while(0)
+
+enum icmp_error_status
+{
+ ICMP_MAY_BE_ERROR,
+ ICMP_IS_ERROR,
+ ICMP_NOT_ERROR
+};
+
+struct icmp_info
+{
+ size_t min_len, max_len;
+ enum icmp_error_status err;
+ u_int8_t min_code, max_code;
+};
+
+static int
+check_ip(struct iphdr *iph, size_t length, int embedded);
+
+/* ICMP-specific checks. */
+static int
+check_icmp(const struct icmphdr *icmph,
+ u_int16_t datalen,
+ unsigned int offset,
+ int more_frags,
+ int embedded)
+{
+ static struct icmp_info info[]
+ = { [ICMP_ECHOREPLY]
+ = { 8, 65536, ICMP_NOT_ERROR, 0, 0 },
+ [ICMP_DEST_UNREACH]
+ = { 8 + 28, 65536, ICMP_IS_ERROR, 0, 15 },
+ [ICMP_SOURCE_QUENCH]
+ = { 8 + 28, 65536, ICMP_IS_ERROR, 0, 0 },
+ [ICMP_REDIRECT]
+ = { 8 + 28, 65536, ICMP_IS_ERROR, 0, 3 },
+ [ICMP_ECHO]
+ = { 8, 65536, ICMP_NOT_ERROR, 0, 0 },
+ /* Router advertisement. */
+ [9]
+ = { 8, 8 + 255 * 8, ICMP_NOT_ERROR, 0, 0 },
+ /* Router solicitation. */
+ [10]
+ = { 8, 8, ICMP_NOT_ERROR, 0, 0 },
+ [ICMP_TIME_EXCEEDED]
+ = { 8 + 28, 65536, ICMP_IS_ERROR, 0, 1 },
+ [ICMP_PARAMETERPROB]
+ = { 8 + 28, 65536, ICMP_IS_ERROR, 0, 1 },
+ [ICMP_TIMESTAMP]
+ = { 20, 20, ICMP_NOT_ERROR, 0, 0 },
+ [ICMP_TIMESTAMPREPLY]
+ = { 20, 20, ICMP_NOT_ERROR, 0, 0 },
+ [ICMP_INFO_REQUEST]
+ = { 8, 65536, ICMP_NOT_ERROR, 0, 0 },
+ [ICMP_INFO_REPLY]
+ = { 8, 65536, ICMP_NOT_ERROR, 0, 0 },
+ [ICMP_ADDRESS]
+ = { 12, 12, ICMP_NOT_ERROR, 0, 0 },
+ [ICMP_ADDRESSREPLY]
+ = { 12, 12, ICMP_NOT_ERROR, 0, 0 } };
+
+ /* Can't do anything if it's a fragment. */
+ if (offset)
+ return 1;
+
+ /* Must cover type and code. */
+ if (datalen < 2) {
+ limpk("ICMP len=%u too short\n", datalen);
+ return 0;
+ }
+
+ /* If not embedded. */
+ if (!embedded) {
+ /* Bad checksum? Don't print, just ignore. */
+ if (!more_frags
+ && ip_compute_csum((unsigned char *) icmph, datalen) != 0)
+ return 0;
+
+ /* CHECK: Truncated ICMP (even if first fragment). */
+ if (icmph->type < sizeof(info)/sizeof(struct icmp_info)
+ && info[icmph->type].min_len != 0
+ && datalen < info[icmph->type].min_len) {
+ limpk("ICMP type %u len %u too short\n",
+ icmph->type, datalen);
+ return 0;
+ }
+
+ /* CHECK: Check within known error ICMPs. */
+ if (icmph->type < sizeof(info)/sizeof(struct icmp_info)
+ && info[icmph->type].err == ICMP_IS_ERROR) {
+ /* CHECK: Embedded packet must be at least
+ length of iph + 8 bytes. */
+ struct iphdr *inner = (void *)icmph + 8;
+
+ /* datalen > 8 since all ICMP_IS_ERROR types
+ have min length > 8 */
+ if (datalen - 8 < sizeof(struct iphdr)) {
+ limpk("ICMP error internal way too short\n");
+ return 0;
+ }
+ if (datalen - 8 < inner->ihl*4 + 8) {
+ limpk("ICMP error internal too short\n");
+ return 0;
+ }
+ if (!check_ip(inner, datalen - 8, 1))
+ return 0;
+ }
+ } else {
+ /* CHECK: Can't embed ICMP unless known non-error. */
+ if (icmph->type >= sizeof(info)/sizeof(struct icmp_info)
+ || info[icmph->type].err != ICMP_NOT_ERROR) {
+ limpk("ICMP type %u not embeddable\n",
+ icmph->type);
+ return 0;
+ }
+ }
+
+ /* CHECK: Invalid ICMP codes. */
+ if (icmph->type < sizeof(info)/sizeof(struct icmp_info)
+ && (icmph->code < info[icmph->type].min_code
+ || icmph->code > info[icmph->type].max_code)) {
+ limpk("ICMP type=%u code=%u\n",
+ icmph->type, icmph->code);
+ return 0;
+ }
+
+ /* CHECK: Above maximum length. */
+ if (icmph->type < sizeof(info)/sizeof(struct icmp_info)
+ && info[icmph->type].max_len != 0
+ && datalen > info[icmph->type].max_len) {
+ limpk("ICMP type=%u too long: %u bytes\n",
+ icmph->type, datalen);
+ return 0;
+ }
+
+ switch (icmph->type) {
+ case ICMP_PARAMETERPROB: {
+ /* CHECK: Problem param must be within error packet's
+ * IP header. */
+ struct iphdr *iph = (void *)icmph + 8;
+ u_int32_t arg = ntohl(icmph->un.gateway);
+
+ if (icmph->code == 0) {
+ /* Code 0 means that upper 8 bits is pointer
+ to problem. */
+ if ((arg >> 24) >= iph->ihl*4) {
+ limpk("ICMP PARAMETERPROB ptr = %u\n",
+ ntohl(icmph->un.gateway) >> 24);
+ return 0;
+ }
+ arg &= 0x00FFFFFF;
+ }
+
+ /* CHECK: Rest must be zero. */
+ if (arg) {
+ limpk("ICMP PARAMETERPROB nonzero arg = %u\n",
+ arg);
+ return 0;
+ }
+ break;
+ }
+
+ case ICMP_TIME_EXCEEDED:
+ case ICMP_SOURCE_QUENCH:
+ /* CHECK: Unused must be zero. */
+ if (icmph->un.gateway != 0) {
+ limpk("ICMP type=%u unused = %u\n",
+ icmph->type, ntohl(icmph->un.gateway));
+ return 0;
+ }
+ break;
+ }
+
+ return 1;
+}
+
+/* UDP-specific checks. */
+static int
+check_udp(const struct iphdr *iph,
+ const struct udphdr *udph,
+ u_int16_t datalen,
+ unsigned int offset,
+ int more_frags,
+ int embedded)
+{
+ /* Can't do anything if it's a fragment. */
+ if (offset)
+ return 1;
+
+ /* CHECK: Must cover UDP header. */
+ if (datalen < sizeof(struct udphdr)) {
+ limpk("UDP len=%u too short\n", datalen);
+ return 0;
+ }
+
+ /* Bad checksum? Don't print, just say it's unclean. */
+ /* FIXME: SRC ROUTE packets won't match checksum --RR */
+ if (!more_frags && !embedded && udph->check
+ && csum_tcpudp_magic(iph->saddr, iph->daddr, datalen, IPPROTO_UDP,
+ csum_partial((char *)udph, datalen, 0)) != 0)
+ return 0;
+
+ /* CHECK: Destination port can't be zero. */
+ if (!udph->dest) {
+ limpk("UDP zero destination port\n");
+ return 0;
+ }
+
+ if (!more_frags) {
+ if (!embedded) {
+ /* CHECK: UDP length must match. */
+ if (ntohs(udph->len) != datalen) {
+ limpk("UDP len too short %u vs %u\n",
+ ntohs(udph->len), datalen);
+ return 0;
+ }
+ } else {
+ /* CHECK: UDP length be >= this truncated pkt. */
+ if (ntohs(udph->len) < datalen) {
+ limpk("UDP len too long %u vs %u\n",
+ ntohs(udph->len), datalen);
+ return 0;
+ }
+ }
+ } else {
+ /* CHECK: UDP length must be > this frag's length. */
+ if (ntohs(udph->len) <= datalen) {
+ limpk("UDP fragment len too short %u vs %u\n",
+ ntohs(udph->len), datalen);
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+#define TH_FIN 0x01
+#define TH_SYN 0x02
+#define TH_RST 0x04
+#define TH_PUSH 0x08
+#define TH_ACK 0x10
+#define TH_URG 0x20
+#define TH_ECE 0x40
+#define TH_CWR 0x80
+
+/* table of valid flag combinations - ECE and CWR are always valid */
+static u8 tcp_valid_flags[(TH_FIN|TH_SYN|TH_RST|TH_PUSH|TH_ACK|TH_URG) + 1] =
+{
+ [TH_SYN] = 1,
+ [TH_SYN|TH_ACK] = 1,
+ [TH_RST] = 1,
+ [TH_RST|TH_ACK] = 1,
+ [TH_RST|TH_ACK|TH_PUSH] = 1,
+ [TH_FIN|TH_ACK] = 1,
+ [TH_ACK] = 1,
+ [TH_ACK|TH_PUSH] = 1,
+ [TH_ACK|TH_URG] = 1,
+ [TH_ACK|TH_URG|TH_PUSH] = 1,
+ [TH_FIN|TH_ACK|TH_PUSH] = 1,
+ [TH_FIN|TH_ACK|TH_URG] = 1,
+ [TH_FIN|TH_ACK|TH_URG|TH_PUSH] = 1
+};
+
+/* TCP-specific checks. */
+static int
+check_tcp(const struct iphdr *iph,
+ const struct tcphdr *tcph,
+ u_int16_t datalen,
+ unsigned int offset,
+ int more_frags,
+ int embedded)
+{
+ u_int8_t *opt = (u_int8_t *)tcph;
+ u_int8_t *endhdr = (u_int8_t *)tcph + tcph->doff * 4;
+ u_int8_t tcpflags;
+ int end_of_options = 0;
+ size_t i;
+
+ /* CHECK: Can't have offset=1: used to override TCP syn-checks. */
+ /* In fact, this is caught below (offset < 516). */
+
+ /* Can't do anything if it's a fragment. */
+ if (offset)
+ return 1;
+
+ /* CHECK: Smaller than minimal TCP hdr. */
+ if (datalen < sizeof(struct tcphdr)) {
+ if (!embedded) {
+ limpk("Packet length %u < TCP header.\n", datalen);
+ return 0;
+ }
+ /* Must have ports available (datalen >= 8), from
+ check_icmp which set embedded = 1 */
+ /* CHECK: TCP ports inside ICMP error */
+ if (!tcph->source || !tcph->dest) {
+ limpk("Zero TCP ports %u/%u.\n",
+ htons(tcph->source), htons(tcph->dest));
+ return 0;
+ }
+ return 1;
+ }
+
+ /* CHECK: Smaller than actual TCP hdr. */
+ if (datalen < tcph->doff * 4) {
+ if (!embedded) {
+ limpk("Packet length %u < actual TCP header.\n",
+ datalen);
+ return 0;
+ } else
+ return 1;
+ }
+
+ /* Bad checksum? Don't print, just say it's unclean. */
+ /* FIXME: SRC ROUTE packets won't match checksum --RR */
+ if (!more_frags && !embedded
+ && csum_tcpudp_magic(iph->saddr, iph->daddr, datalen, IPPROTO_TCP,
+ csum_partial((char *)tcph, datalen, 0)) != 0)
+ return 0;
+
+ /* CHECK: TCP ports non-zero */
+ if (!tcph->source || !tcph->dest) {
+ limpk("Zero TCP ports %u/%u.\n",
+ htons(tcph->source), htons(tcph->dest));
+ return 0;
+ }
+
+ /* CHECK: TCP reserved bits zero. */
+ if(tcp_flag_word(tcph) & TCP_RESERVED_BITS) {
+ limpk("TCP reserved bits not zero\n");
+ return 0;
+ }
+
+ /* CHECK: TCP flags. */
+ tcpflags = (((u_int8_t *)tcph)[13] & ~(TH_ECE|TH_CWR));
+ if (!tcp_valid_flags[tcpflags]) {
+ limpk("TCP flags bad: %u\n", tcpflags);
+ return 0;
+ }
+
+ for (i = sizeof(struct tcphdr); i < tcph->doff * 4; ) {
+ switch (opt[i]) {
+ case 0:
+ end_of_options = 1;
+ i++;
+ break;
+ case 1:
+ i++;
+ break;
+ default:
+ /* CHECK: options after EOO. */
+ if (end_of_options) {
+ limpk("TCP option %u after end\n",
+ opt[i]);
+ return 0;
+ }
+ /* CHECK: options at tail. */
+ else if (i+1 >= tcph->doff * 4) {
+ limpk("TCP option %u at tail\n",
+ opt[i]);
+ return 0;
+ }
+ /* CHECK: zero-length options. */
+ else if (opt[i+1] == 0) {
+ limpk("TCP option %u 0 len\n",
+ opt[i]);
+ return 0;
+ }
+ /* CHECK: oversize options. */
+ else if (&opt[i] + opt[i+1] > endhdr) {
+ limpk("TCP option %u at %Zu too long\n",
+ (unsigned int) opt[i], i);
+ return 0;
+ }
+ /* Move to next option */
+ i += opt[i+1];
+ }
+ }
+
+ return 1;
+}
+
+/* Returns 1 if ok */
+/* Standard IP checks. */
+static int
+check_ip(struct iphdr *iph, size_t length, int embedded)
+{
+ u_int8_t *opt = (u_int8_t *)iph;
+ u_int8_t *endhdr = (u_int8_t *)iph + iph->ihl * 4;
+ int end_of_options = 0;
+ void *protoh;
+ size_t datalen;
+ unsigned int i;
+ unsigned int offset;
+
+ /* Should only happen for local outgoing raw-socket packets. */
+ /* CHECK: length >= ip header. */
+ if (length < sizeof(struct iphdr) || length < iph->ihl * 4) {
+ limpk("Packet length %Zu < IP header.\n", length);
+ return 0;
+ }
+
+ offset = ntohs(iph->frag_off) & IP_OFFSET;
+ protoh = (void *)iph + iph->ihl * 4;
+ datalen = length - iph->ihl * 4;
+
+ /* CHECK: Embedded fragment. */
+ if (embedded && offset) {
+ limpk("Embedded fragment.\n");
+ return 0;
+ }
+
+ for (i = sizeof(struct iphdr); i < iph->ihl * 4; ) {
+ switch (opt[i]) {
+ case 0:
+ end_of_options = 1;
+ i++;
+ break;
+ case 1:
+ i++;
+ break;
+ default:
+ /* CHECK: options after EOO. */
+ if (end_of_options) {
+ limpk("IP option %u after end\n",
+ opt[i]);
+ return 0;
+ }
+ /* CHECK: options at tail. */
+ else if (i+1 >= iph->ihl * 4) {
+ limpk("IP option %u at tail\n",
+ opt[i]);
+ return 0;
+ }
+ /* CHECK: zero-length or one-length options. */
+ else if (opt[i+1] < 2) {
+ limpk("IP option %u %u len\n",
+ opt[i], opt[i+1]);
+ return 0;
+ }
+ /* CHECK: oversize options. */
+ else if (&opt[i] + opt[i+1] > endhdr) {
+ limpk("IP option %u at %u too long\n",
+ opt[i], i);
+ return 0;
+ }
+ /* Move to next option */
+ i += opt[i+1];
+ }
+ }
+
+ /* Fragment checks. */
+
+ /* CHECK: More fragments, but doesn't fill 8-byte boundary. */
+ if ((ntohs(iph->frag_off) & IP_MF) && datalen % 8 != 0) {
+ limpk("Truncated fragment %u long.\n", datalen);
+ return 0;
+ }
+
+ /* CHECK: Oversize fragment a-la Ping of Death. */
+ if (offset * 8 + datalen > 65535) {
+ limpk("Oversize fragment to %u.\n", offset * 8);
+ return 0;
+ }
+
+ /* CHECK: Zero-sized fragments. */
+ if ((offset || (ntohs(iph->frag_off) & IP_MF))
+ && datalen == 0) {
+ limpk("Zero size fragment offset=%u\n", offset);
+ return 0;
+ }
+
+ /* Note: we can have even middle fragments smaller than this:
+ consider a large packet passing through a 600MTU then
+ 576MTU link: this gives a fragment of 24 data bytes. But
+ everyone packs fragments largest first, hence a fragment
+ can't START before 576 - MAX_IP_HEADER_LEN. */
+
+ /* Used to be min-size 576: I recall Alan Cox saying ax25 goes
+ down to 128 (576 taken from RFC 791: All hosts must be
+ prepared to accept datagrams of up to 576 octets). Use 128
+ here. */
+#define MIN_LIKELY_MTU 128
+ /* CHECK: Min size of first frag = 128. */
+ if ((ntohs(iph->frag_off) & IP_MF)
+ && offset == 0
+ && ntohs(iph->tot_len) < MIN_LIKELY_MTU) {
+ limpk("First fragment size %u < %u\n", ntohs(iph->tot_len),
+ MIN_LIKELY_MTU);
+ return 0;
+ }
+
+ /* CHECK: Min offset of frag = 128 - IP hdr len. */
+ if (offset && offset * 8 < MIN_LIKELY_MTU - iph->ihl * 4) {
+ limpk("Fragment starts at %u < %u\n", offset * 8,
+ MIN_LIKELY_MTU - iph->ihl * 4);
+ return 0;
+ }
+
+ /* CHECK: Protocol specification non-zero. */
+ if (iph->protocol == 0) {
+ limpk("Zero protocol\n");
+ return 0;
+ }
+
+ /* CHECK: Do not use what is unused.
+ * First bit of fragmentation flags should be unused.
+ * May be used by OS fingerprinting tools.
+ * 04 Jun 2002, Maciej Soltysiak, solt@dns.toxicfilms.tv
+ */
+ if (ntohs(iph->frag_off)>>15) {
+ limpk("IP unused bit set\n");
+ return 0;
+ }
+
+ /* Per-protocol checks. */
+ switch (iph->protocol) {
+ case IPPROTO_ICMP:
+ return check_icmp(protoh, datalen, offset,
+ (ntohs(iph->frag_off) & IP_MF),
+ embedded);
+
+ case IPPROTO_UDP:
+ return check_udp(iph, protoh, datalen, offset,
+ (ntohs(iph->frag_off) & IP_MF),
+ embedded);
+
+ case IPPROTO_TCP:
+ return check_tcp(iph, protoh, datalen, offset,
+ (ntohs(iph->frag_off) & IP_MF),
+ embedded);
+ default:
+ /* Ignorance is bliss. */
+ return 1;
+ }
+}
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ return !check_ip(skb->nh.iph, skb->len, 0);
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+checkentry(const char *tablename,
+ const struct ipt_ip *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (matchsize != IPT_ALIGN(0))
+ return 0;
+
+ return 1;
+}
+
+static struct ipt_match unclean_match
+= { { NULL, NULL }, "unclean", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ipt_register_match(&unclean_match);
+}
+
+static void __exit fini(void)
+{
+ ipt_unregister_match(&unclean_match);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/iptable_filter.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/iptable_filter.c
new file mode 100644
index 0000000..22ff7d9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/iptable_filter.c
@@ -0,0 +1,182 @@
+/*
+ * This is the 1999 rewrite of IP Firewalling, aiming for kernel 2.3.x.
+ *
+ * Copyright (C) 1999 Paul `Rusty' Russell & Michael J. Neuling
+ */
+#include <linux/module.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+
+#define FILTER_VALID_HOOKS ((1 << NF_IP_LOCAL_IN) | (1 << NF_IP_FORWARD) | (1 << NF_IP_LOCAL_OUT))
+
+/* Standard entry. */
+struct ipt_standard
+{
+ struct ipt_entry entry;
+ struct ipt_standard_target target;
+};
+
+struct ipt_error_target
+{
+ struct ipt_entry_target target;
+ char errorname[IPT_FUNCTION_MAXNAMELEN];
+};
+
+struct ipt_error
+{
+ struct ipt_entry entry;
+ struct ipt_error_target target;
+};
+
+static struct
+{
+ struct ipt_replace repl;
+ struct ipt_standard entries[3];
+ struct ipt_error term;
+} initial_table __initdata
+= { { "filter", FILTER_VALID_HOOKS, 4,
+ sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error),
+ { [NF_IP_LOCAL_IN] 0,
+ [NF_IP_FORWARD] sizeof(struct ipt_standard),
+ [NF_IP_LOCAL_OUT] sizeof(struct ipt_standard) * 2 },
+ { [NF_IP_LOCAL_IN] 0,
+ [NF_IP_FORWARD] sizeof(struct ipt_standard),
+ [NF_IP_LOCAL_OUT] sizeof(struct ipt_standard) * 2 },
+ 0, NULL, { } },
+ {
+ /* LOCAL_IN */
+ { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ipt_entry),
+ sizeof(struct ipt_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ /* FORWARD */
+ { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ipt_entry),
+ sizeof(struct ipt_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ /* LOCAL_OUT */
+ { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ipt_entry),
+ sizeof(struct ipt_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } }
+ },
+ /* ERROR */
+ { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ipt_entry),
+ sizeof(struct ipt_error),
+ 0, { 0, 0 }, { } },
+ { { { { IPT_ALIGN(sizeof(struct ipt_error_target)), IPT_ERROR_TARGET } },
+ { } },
+ "ERROR"
+ }
+ }
+};
+
+static struct ipt_table packet_filter
+= { { NULL, NULL }, "filter", &initial_table.repl,
+ FILTER_VALID_HOOKS, RW_LOCK_UNLOCKED, NULL, THIS_MODULE };
+
+/* The work comes in here from netfilter.c. */
+static unsigned int
+ipt_hook(unsigned int hook,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ return ipt_do_table(pskb, hook, in, out, &packet_filter, NULL);
+}
+
+static unsigned int
+ipt_local_out_hook(unsigned int hook,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ /* root is playing with raw sockets. */
+ if ((*pskb)->len < sizeof(struct iphdr)
+ || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr)) {
+ if (net_ratelimit())
+ printk("ipt_hook: happy cracking.\n");
+ return NF_ACCEPT;
+ }
+
+ return ipt_do_table(pskb, hook, in, out, &packet_filter, NULL);
+}
+
+static struct nf_hook_ops ipt_ops[]
+= { { { NULL, NULL }, ipt_hook, PF_INET, NF_IP_LOCAL_IN, NF_IP_PRI_FILTER },
+ { { NULL, NULL }, ipt_hook, PF_INET, NF_IP_FORWARD, NF_IP_PRI_FILTER },
+ { { NULL, NULL }, ipt_local_out_hook, PF_INET, NF_IP_LOCAL_OUT,
+ NF_IP_PRI_FILTER }
+};
+
+/* Default to forward because I got too much mail already. */
+static int forward = NF_ACCEPT;
+MODULE_PARM(forward, "i");
+
+static int __init init(void)
+{
+ int ret;
+
+ if (forward < 0 || forward > NF_MAX_VERDICT) {
+ printk("iptables forward must be 0 or 1\n");
+ return -EINVAL;
+ }
+
+ /* Entry 1 is the FORWARD hook */
+ initial_table.entries[1].target.verdict = -forward - 1;
+
+ /* Register table */
+ ret = ipt_register_table(&packet_filter);
+ if (ret < 0)
+ return ret;
+
+ /* Register hooks */
+ ret = nf_register_hook(&ipt_ops[0]);
+ if (ret < 0)
+ goto cleanup_table;
+
+ ret = nf_register_hook(&ipt_ops[1]);
+ if (ret < 0)
+ goto cleanup_hook0;
+
+ ret = nf_register_hook(&ipt_ops[2]);
+ if (ret < 0)
+ goto cleanup_hook1;
+
+ return ret;
+
+ cleanup_hook1:
+ nf_unregister_hook(&ipt_ops[1]);
+ cleanup_hook0:
+ nf_unregister_hook(&ipt_ops[0]);
+ cleanup_table:
+ ipt_unregister_table(&packet_filter);
+
+ return ret;
+}
+
+static void __exit fini(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < sizeof(ipt_ops)/sizeof(struct nf_hook_ops); i++)
+ nf_unregister_hook(&ipt_ops[i]);
+
+ ipt_unregister_table(&packet_filter);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/netfilter/iptable_mangle.c b/uClinux-2.4.31-uc0/net/ipv4/netfilter/iptable_mangle.c
new file mode 100644
index 0000000..1af3c5d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/netfilter/iptable_mangle.c
@@ -0,0 +1,243 @@
+/*
+ * This is the 1999 rewrite of IP Firewalling, aiming for kernel 2.3.x.
+ *
+ * Copyright (C) 1999 Paul `Rusty' Russell & Michael J. Neuling
+ *
+ * Extended to all five netfilter hooks by Brad Chapman & Harald Welte
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/route.h>
+#include <linux/ip.h>
+
+#define MANGLE_VALID_HOOKS ((1 << NF_IP_PRE_ROUTING) | \
+ (1 << NF_IP_LOCAL_IN) | \
+ (1 << NF_IP_FORWARD) | \
+ (1 << NF_IP_LOCAL_OUT) | \
+ (1 << NF_IP_POST_ROUTING))
+
+/* Standard entry. */
+struct ipt_standard
+{
+ struct ipt_entry entry;
+ struct ipt_standard_target target;
+};
+
+struct ipt_error_target
+{
+ struct ipt_entry_target target;
+ char errorname[IPT_FUNCTION_MAXNAMELEN];
+};
+
+struct ipt_error
+{
+ struct ipt_entry entry;
+ struct ipt_error_target target;
+};
+
+/* Ouch - five different hooks? Maybe this should be a config option..... -- BC */
+static struct
+{
+ struct ipt_replace repl;
+ struct ipt_standard entries[5];
+ struct ipt_error term;
+} initial_table __initdata
+= { { "mangle", MANGLE_VALID_HOOKS, 6,
+ sizeof(struct ipt_standard) * 5 + sizeof(struct ipt_error),
+ { [NF_IP_PRE_ROUTING] 0,
+ [NF_IP_LOCAL_IN] sizeof(struct ipt_standard),
+ [NF_IP_FORWARD] sizeof(struct ipt_standard) * 2,
+ [NF_IP_LOCAL_OUT] sizeof(struct ipt_standard) * 3,
+ [NF_IP_POST_ROUTING] sizeof(struct ipt_standard) * 4 },
+ { [NF_IP_PRE_ROUTING] 0,
+ [NF_IP_LOCAL_IN] sizeof(struct ipt_standard),
+ [NF_IP_FORWARD] sizeof(struct ipt_standard) * 2,
+ [NF_IP_LOCAL_OUT] sizeof(struct ipt_standard) * 3,
+ [NF_IP_POST_ROUTING] sizeof(struct ipt_standard) * 4 },
+ 0, NULL, { } },
+ {
+ /* PRE_ROUTING */
+ { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ipt_entry),
+ sizeof(struct ipt_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ /* LOCAL_IN */
+ { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ipt_entry),
+ sizeof(struct ipt_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ /* FORWARD */
+ { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ipt_entry),
+ sizeof(struct ipt_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ /* LOCAL_OUT */
+ { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ipt_entry),
+ sizeof(struct ipt_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ /* POST_ROUTING */
+ { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ipt_entry),
+ sizeof(struct ipt_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IPT_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ },
+ /* ERROR */
+ { { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ipt_entry),
+ sizeof(struct ipt_error),
+ 0, { 0, 0 }, { } },
+ { { { { IPT_ALIGN(sizeof(struct ipt_error_target)), IPT_ERROR_TARGET } },
+ { } },
+ "ERROR"
+ }
+ }
+};
+
+static struct ipt_table packet_mangler
+= { { NULL, NULL }, "mangle", &initial_table.repl,
+ MANGLE_VALID_HOOKS, RW_LOCK_UNLOCKED, NULL, THIS_MODULE };
+
+/* The work comes in here from netfilter.c. */
+static unsigned int
+ipt_route_hook(unsigned int hook,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ return ipt_do_table(pskb, hook, in, out, &packet_mangler, NULL);
+}
+
+static unsigned int
+ipt_local_hook(unsigned int hook,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ unsigned int ret;
+ u_int8_t tos;
+ u_int32_t saddr, daddr;
+ unsigned long nfmark;
+
+ /* root is playing with raw sockets. */
+ if ((*pskb)->len < sizeof(struct iphdr)
+ || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr)) {
+ if (net_ratelimit())
+ printk("ipt_hook: happy cracking.\n");
+ return NF_ACCEPT;
+ }
+
+ /* Save things which could affect route */
+ nfmark = (*pskb)->nfmark;
+ saddr = (*pskb)->nh.iph->saddr;
+ daddr = (*pskb)->nh.iph->daddr;
+ tos = (*pskb)->nh.iph->tos;
+
+ ret = ipt_do_table(pskb, hook, in, out, &packet_mangler, NULL);
+ /* Reroute for ANY change. */
+ if (ret != NF_DROP && ret != NF_STOLEN && ret != NF_QUEUE
+ && ((*pskb)->nh.iph->saddr != saddr
+ || (*pskb)->nh.iph->daddr != daddr
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ || (*pskb)->nfmark != nfmark
+#endif
+ || (*pskb)->nh.iph->tos != tos))
+ return ip_route_me_harder(pskb) == 0 ? ret : NF_DROP;
+
+ return ret;
+}
+
+static struct nf_hook_ops ipt_ops[]
+= { { { NULL, NULL }, ipt_route_hook, PF_INET, NF_IP_PRE_ROUTING,
+ NF_IP_PRI_MANGLE },
+ { { NULL, NULL }, ipt_route_hook, PF_INET, NF_IP_LOCAL_IN,
+ NF_IP_PRI_MANGLE },
+ { { NULL, NULL }, ipt_route_hook, PF_INET, NF_IP_FORWARD,
+ NF_IP_PRI_MANGLE },
+ { { NULL, NULL }, ipt_local_hook, PF_INET, NF_IP_LOCAL_OUT,
+ NF_IP_PRI_MANGLE },
+ { { NULL, NULL }, ipt_route_hook, PF_INET, NF_IP_POST_ROUTING,
+ NF_IP_PRI_MANGLE }
+};
+
+static int __init init(void)
+{
+ int ret;
+
+ /* Register table */
+ ret = ipt_register_table(&packet_mangler);
+ if (ret < 0)
+ return ret;
+
+ /* Register hooks */
+ ret = nf_register_hook(&ipt_ops[0]);
+ if (ret < 0)
+ goto cleanup_table;
+
+ ret = nf_register_hook(&ipt_ops[1]);
+ if (ret < 0)
+ goto cleanup_hook0;
+
+ ret = nf_register_hook(&ipt_ops[2]);
+ if (ret < 0)
+ goto cleanup_hook1;
+
+ ret = nf_register_hook(&ipt_ops[3]);
+ if (ret < 0)
+ goto cleanup_hook2;
+
+ ret = nf_register_hook(&ipt_ops[4]);
+ if (ret < 0)
+ goto cleanup_hook3;
+
+ return ret;
+
+ cleanup_hook3:
+ nf_unregister_hook(&ipt_ops[3]);
+ cleanup_hook2:
+ nf_unregister_hook(&ipt_ops[2]);
+ cleanup_hook1:
+ nf_unregister_hook(&ipt_ops[1]);
+ cleanup_hook0:
+ nf_unregister_hook(&ipt_ops[0]);
+ cleanup_table:
+ ipt_unregister_table(&packet_mangler);
+
+ return ret;
+}
+
+static void __exit fini(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < sizeof(ipt_ops)/sizeof(struct nf_hook_ops); i++)
+ nf_unregister_hook(&ipt_ops[i]);
+
+ ipt_unregister_table(&packet_mangler);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv4/proc.c b/uClinux-2.4.31-uc0/net/ipv4/proc.c
new file mode 100644
index 0000000..df84f57
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/proc.c
@@ -0,0 +1,222 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * This file implements the various access functions for the
+ * PROC file system. It is mainly used for debugging and
+ * statistics.
+ *
+ * Version: $Id: proc.c,v 1.45 2001/05/16 16:45:35 davem Exp $
+ *
+ * Authors: Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Gerald J. Heim, <heim@peanuts.informatik.uni-tuebingen.de>
+ * Fred Baumgarten, <dc6iq@insu1.etec.uni-karlsruhe.de>
+ * Erik Schoenfelder, <schoenfr@ibr.cs.tu-bs.de>
+ *
+ * Fixes:
+ * Alan Cox : UDP sockets show the rxqueue/txqueue
+ * using hint flag for the netinfo.
+ * Pauline Middelink : identd support
+ * Alan Cox : Make /proc safer.
+ * Erik Schoenfelder : /proc/net/snmp
+ * Alan Cox : Handle dead sockets properly.
+ * Gerhard Koerting : Show both timers
+ * Alan Cox : Allow inode to be NULL (kernel socket)
+ * Andi Kleen : Add support for open_requests and
+ * split functions for more readibility.
+ * Andi Kleen : Add support for /proc/net/netstat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+#include <asm/system.h>
+#include <linux/sched.h>
+#include <linux/socket.h>
+#include <linux/net.h>
+#include <linux/un.h>
+#include <linux/in.h>
+#include <linux/param.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <net/ip.h>
+#include <net/icmp.h>
+#include <net/protocol.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/raw.h>
+
+static int fold_prot_inuse(struct proto *proto)
+{
+ int res = 0;
+ int cpu;
+
+ for (cpu=0; cpu<smp_num_cpus; cpu++)
+ res += proto->stats[cpu_logical_map(cpu)].inuse;
+
+ return res;
+}
+
+/*
+ * Report socket allocation statistics [mea@utu.fi]
+ */
+int afinet_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ /* From net/socket.c */
+ extern int socket_get_info(char *, char **, off_t, int);
+
+ int len = socket_get_info(buffer,start,offset,length);
+
+ len += sprintf(buffer+len,"TCP: inuse %d orphan %d tw %d alloc %d mem %d\n",
+ fold_prot_inuse(&tcp_prot),
+ atomic_read(&tcp_orphan_count), tcp_tw_count,
+ atomic_read(&tcp_sockets_allocated),
+ atomic_read(&tcp_memory_allocated));
+ len += sprintf(buffer+len,"UDP: inuse %d\n",
+ fold_prot_inuse(&udp_prot));
+ len += sprintf(buffer+len,"RAW: inuse %d\n",
+ fold_prot_inuse(&raw_prot));
+ len += sprintf(buffer+len, "FRAG: inuse %d memory %d\n",
+ ip_frag_nqueues, atomic_read(&ip_frag_mem));
+ if (offset >= len)
+ {
+ *start = buffer;
+ return 0;
+ }
+ *start = buffer + offset;
+ len -= offset;
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+static unsigned long fold_field(unsigned long *begin, int sz, int nr)
+{
+ unsigned long res = 0;
+ int i;
+
+ sz /= sizeof(unsigned long);
+
+ for (i=0; i<smp_num_cpus; i++) {
+ res += begin[2*cpu_logical_map(i)*sz + nr];
+ res += begin[(2*cpu_logical_map(i)+1)*sz + nr];
+ }
+ return res;
+}
+
+/*
+ * Called from the PROCfs module. This outputs /proc/net/snmp.
+ */
+
+int snmp_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ extern int sysctl_ip_default_ttl;
+ int len, i;
+
+ len = sprintf (buffer,
+ "Ip: Forwarding DefaultTTL InReceives InHdrErrors InAddrErrors ForwDatagrams InUnknownProtos InDiscards InDelivers OutRequests OutDiscards OutNoRoutes ReasmTimeout ReasmReqds ReasmOKs ReasmFails FragOKs FragFails FragCreates\n"
+ "Ip: %d %d", ipv4_devconf.forwarding ? 1 : 2, sysctl_ip_default_ttl);
+ for (i=0; i<offsetof(struct ip_mib, __pad)/sizeof(unsigned long); i++)
+ len += sprintf(buffer+len, " %lu", fold_field((unsigned long*)ip_statistics, sizeof(struct ip_mib), i));
+
+ len += sprintf (buffer + len,
+ "\nIcmp: InMsgs InErrors InDestUnreachs InTimeExcds InParmProbs InSrcQuenchs InRedirects InEchos InEchoReps InTimestamps InTimestampReps InAddrMasks InAddrMaskReps OutMsgs OutErrors OutDestUnreachs OutTimeExcds OutParmProbs OutSrcQuenchs OutRedirects OutEchos OutEchoReps OutTimestamps OutTimestampReps OutAddrMasks OutAddrMaskReps\n"
+ "Icmp:");
+ for (i=0; i<offsetof(struct icmp_mib, dummy)/sizeof(unsigned long); i++)
+ len += sprintf(buffer+len, " %lu", fold_field((unsigned long*)icmp_statistics, sizeof(struct icmp_mib), i));
+
+ len += sprintf (buffer + len,
+ "\nTcp: RtoAlgorithm RtoMin RtoMax MaxConn ActiveOpens PassiveOpens AttemptFails EstabResets CurrEstab InSegs OutSegs RetransSegs InErrs OutRsts\n"
+ "Tcp:");
+ for (i=0; i<offsetof(struct tcp_mib, __pad)/sizeof(unsigned long); i++) {
+ if (i == (offsetof(struct tcp_mib, TcpMaxConn) / sizeof(unsigned long)))
+ /* MaxConn field is negative, RFC 2012 */
+ len += sprintf(buffer+len, " %ld",
+ fold_field((unsigned long*)tcp_statistics,
+ sizeof(struct tcp_mib), i));
+ else
+ len += sprintf(buffer+len, " %lu",
+ fold_field((unsigned long*)tcp_statistics,
+ sizeof(struct tcp_mib), i));
+ }
+ len += sprintf (buffer + len,
+ "\nUdp: InDatagrams NoPorts InErrors OutDatagrams\n"
+ "Udp:");
+ for (i=0; i<offsetof(struct udp_mib, __pad)/sizeof(unsigned long); i++)
+ len += sprintf(buffer+len, " %lu", fold_field((unsigned long*)udp_statistics, sizeof(struct udp_mib), i));
+
+ len += sprintf (buffer + len, "\n");
+
+ if (offset >= len)
+ {
+ *start = buffer;
+ return 0;
+ }
+ *start = buffer + offset;
+ len -= offset;
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+/*
+ * Output /proc/net/netstat
+ */
+
+int netstat_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int len, i;
+
+ len = sprintf(buffer,
+ "TcpExt: SyncookiesSent SyncookiesRecv SyncookiesFailed"
+ " EmbryonicRsts PruneCalled RcvPruned OfoPruned"
+ " OutOfWindowIcmps LockDroppedIcmps ArpFilter"
+ " TW TWRecycled TWKilled"
+ " PAWSPassive PAWSActive PAWSEstab"
+ " DelayedACKs DelayedACKLocked DelayedACKLost"
+ " ListenOverflows ListenDrops"
+ " TCPPrequeued TCPDirectCopyFromBacklog"
+ " TCPDirectCopyFromPrequeue TCPPrequeueDropped"
+ " TCPHPHits TCPHPHitsToUser"
+ " TCPPureAcks TCPHPAcks"
+ " TCPRenoRecovery TCPSackRecovery"
+ " TCPSACKReneging"
+ " TCPFACKReorder TCPSACKReorder TCPRenoReorder TCPTSReorder"
+ " TCPFullUndo TCPPartialUndo TCPDSACKUndo TCPLossUndo"
+ " TCPLoss TCPLostRetransmit"
+ " TCPRenoFailures TCPSackFailures TCPLossFailures"
+ " TCPFastRetrans TCPForwardRetrans TCPSlowStartRetrans"
+ " TCPTimeouts"
+ " TCPRenoRecoveryFail TCPSackRecoveryFail"
+ " TCPSchedulerFailed TCPRcvCollapsed"
+ " TCPDSACKOldSent TCPDSACKOfoSent TCPDSACKRecv TCPDSACKOfoRecv"
+ " TCPAbortOnSyn TCPAbortOnData TCPAbortOnClose"
+ " TCPAbortOnMemory TCPAbortOnTimeout TCPAbortOnLinger"
+ " TCPAbortFailed TCPMemoryPressures\n"
+ "TcpExt:");
+ for (i=0; i<offsetof(struct linux_mib, __pad)/sizeof(unsigned long); i++)
+ len += sprintf(buffer+len, " %lu", fold_field((unsigned long*)net_statistics, sizeof(struct linux_mib), i));
+
+ len += sprintf (buffer + len, "\n");
+
+ if (offset >= len)
+ {
+ *start = buffer;
+ return 0;
+ }
+ *start = buffer + offset;
+ len -= offset;
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/protocol.c b/uClinux-2.4.31-uc0/net/ipv4/protocol.c
new file mode 100644
index 0000000..a379dec
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/protocol.c
@@ -0,0 +1,181 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * INET protocol dispatch tables.
+ *
+ * Version: $Id: protocol.c,v 1.14 2001/05/18 02:25:49 davem Exp $
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ *
+ * Fixes:
+ * Alan Cox : Ahah! udp icmp errors don't work because
+ * udp_err is never called!
+ * Alan Cox : Added new fields for init and ready for
+ * proper fragmentation (_NO_ 4K limits!)
+ * Richard Colella : Hang on hash collision
+ * Vince Laviano : Modified inet_del_protocol() to correctly
+ * maintain copy bit.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/config.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/timer.h>
+#include <linux/brlock.h>
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <net/tcp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/icmp.h>
+#include <net/udp.h>
+#include <net/ipip.h>
+#include <linux/igmp.h>
+
+#define IPPROTO_PREVIOUS NULL
+
+#ifdef CONFIG_IP_MULTICAST
+
+static struct inet_protocol igmp_protocol = {
+ handler: igmp_rcv,
+ next: IPPROTO_PREVIOUS,
+ protocol: IPPROTO_IGMP,
+ name: "IGMP"
+};
+
+#undef IPPROTO_PREVIOUS
+#define IPPROTO_PREVIOUS &igmp_protocol
+
+#endif
+
+static struct inet_protocol tcp_protocol = {
+ handler: tcp_v4_rcv,
+ err_handler: tcp_v4_err,
+ next: IPPROTO_PREVIOUS,
+ protocol: IPPROTO_TCP,
+ name: "TCP"
+};
+
+#undef IPPROTO_PREVIOUS
+#define IPPROTO_PREVIOUS &tcp_protocol
+
+static struct inet_protocol udp_protocol = {
+ handler: udp_rcv,
+ err_handler: udp_err,
+ next: IPPROTO_PREVIOUS,
+ protocol: IPPROTO_UDP,
+ name: "UDP"
+};
+
+#undef IPPROTO_PREVIOUS
+#define IPPROTO_PREVIOUS &udp_protocol
+
+static struct inet_protocol icmp_protocol = {
+ handler: icmp_rcv,
+ next: IPPROTO_PREVIOUS,
+ protocol: IPPROTO_ICMP,
+ name: "ICMP"
+};
+
+#undef IPPROTO_PREVIOUS
+#define IPPROTO_PREVIOUS &icmp_protocol
+
+
+struct inet_protocol *inet_protocol_base = IPPROTO_PREVIOUS;
+
+struct inet_protocol *inet_protos[MAX_INET_PROTOS];
+
+/*
+ * Add a protocol handler to the hash tables
+ */
+
+void inet_add_protocol(struct inet_protocol *prot)
+{
+ unsigned char hash;
+ struct inet_protocol *p2;
+
+ hash = prot->protocol & (MAX_INET_PROTOS - 1);
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ prot ->next = inet_protos[hash];
+ inet_protos[hash] = prot;
+ prot->copy = 0;
+
+ /*
+ * Set the copy bit if we need to.
+ */
+
+ p2 = (struct inet_protocol *) prot->next;
+ while (p2) {
+ if (p2->protocol == prot->protocol) {
+ prot->copy = 1;
+ break;
+ }
+ p2 = (struct inet_protocol *) p2->next;
+ }
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+}
+
+/*
+ * Remove a protocol from the hash tables.
+ */
+
+int inet_del_protocol(struct inet_protocol *prot)
+{
+ struct inet_protocol *p;
+ struct inet_protocol *lp = NULL;
+ unsigned char hash;
+
+ hash = prot->protocol & (MAX_INET_PROTOS - 1);
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ if (prot == inet_protos[hash]) {
+ inet_protos[hash] = (struct inet_protocol *) inet_protos[hash]->next;
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ return 0;
+ }
+
+ p = (struct inet_protocol *) inet_protos[hash];
+
+ if (p != NULL && p->protocol == prot->protocol)
+ lp = p;
+
+ while (p) {
+ /*
+ * We have to worry if the protocol being deleted is
+ * the last one on the list, then we may need to reset
+ * someone's copied bit.
+ */
+ if (p->next && p->next == prot) {
+ /*
+ * if we are the last one with this protocol and
+ * there is a previous one, reset its copy bit.
+ */
+ if (prot->copy == 0 && lp != NULL)
+ lp->copy = 0;
+ p->next = prot->next;
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ return 0;
+ }
+ if (p->next != NULL && p->next->protocol == prot->protocol)
+ lp = p->next;
+
+ p = (struct inet_protocol *) p->next;
+ }
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ return -1;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/raw.c b/uClinux-2.4.31-uc0/net/ipv4/raw.c
new file mode 100644
index 0000000..e60e3e2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/raw.c
@@ -0,0 +1,703 @@
+/* $USAGI: raw.c,v 1.12 2003/08/08 13:46:37 yoshfuji Exp $ */
+
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * RAW - implementation of IP "raw" sockets.
+ *
+ * Version: $Id: raw.c,v 1.63.2.1 2002/03/05 12:47:34 davem Exp $
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ *
+ * Fixes:
+ * Alan Cox : verify_area() fixed up
+ * Alan Cox : ICMP error handling
+ * Alan Cox : EMSGSIZE if you send too big a packet
+ * Alan Cox : Now uses generic datagrams and shared
+ * skbuff library. No more peek crashes,
+ * no more backlogs
+ * Alan Cox : Checks sk->broadcast.
+ * Alan Cox : Uses skb_free_datagram/skb_copy_datagram
+ * Alan Cox : Raw passes ip options too
+ * Alan Cox : Setsocketopt added
+ * Alan Cox : Fixed error return for broadcasts
+ * Alan Cox : Removed wake_up calls
+ * Alan Cox : Use ttl/tos
+ * Alan Cox : Cleaned up old debugging
+ * Alan Cox : Use new kernel side addresses
+ * Arnt Gulbrandsen : Fixed MSG_DONTROUTE in raw sockets.
+ * Alan Cox : BSD style RAW socket demultiplexing.
+ * Alan Cox : Beginnings of mrouted support.
+ * Alan Cox : Added IP_HDRINCL option.
+ * Alan Cox : Skip broadcast check if BSDism set.
+ * David S. Miller : New socket lookup architecture.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/ioctls.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/timer.h>
+#include <linux/mm.h>
+#include <linux/kernel.h>
+#include <linux/fcntl.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/mroute.h>
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/icmp.h>
+#include <net/udp.h>
+#include <net/raw.h>
+#include <net/inet_common.h>
+#include <net/checksum.h>
+
+struct sock *raw_v4_htable[RAWV4_HTABLE_SIZE];
+rwlock_t raw_v4_lock = RW_LOCK_UNLOCKED;
+
+static void raw_v4_hash(struct sock *sk)
+{
+ struct sock **skp = &raw_v4_htable[sk->num & (RAWV4_HTABLE_SIZE - 1)];
+
+ write_lock_bh(&raw_v4_lock);
+ if ((sk->next = *skp) != NULL)
+ (*skp)->pprev = &sk->next;
+ *skp = sk;
+ sk->pprev = skp;
+ sock_prot_inc_use(sk->prot);
+ sock_hold(sk);
+ write_unlock_bh(&raw_v4_lock);
+}
+
+static void raw_v4_unhash(struct sock *sk)
+{
+ write_lock_bh(&raw_v4_lock);
+ if (sk->pprev) {
+ if (sk->next)
+ sk->next->pprev = sk->pprev;
+ *sk->pprev = sk->next;
+ sk->pprev = NULL;
+ sock_prot_dec_use(sk->prot);
+ __sock_put(sk);
+ }
+ write_unlock_bh(&raw_v4_lock);
+}
+
+struct sock *__raw_v4_lookup(struct sock *sk, unsigned short num,
+ unsigned long raddr, unsigned long laddr,
+ int dif)
+{
+ struct sock *s = sk;
+
+ for (s = sk; s; s = s->next) {
+ if (s->num == num &&
+ !(s->daddr && s->daddr != raddr) &&
+ !(s->rcv_saddr && s->rcv_saddr != laddr) &&
+ !(s->bound_dev_if && s->bound_dev_if != dif))
+ break; /* gotcha */
+ }
+ return s;
+}
+
+/*
+ * 0 - deliver
+ * 1 - block
+ */
+static __inline__ int icmp_filter(struct sock *sk, struct sk_buff *skb)
+{
+ int type;
+
+ type = skb->h.icmph->type;
+ if (type < 32) {
+ __u32 data = sk->tp_pinfo.tp_raw4.filter.data;
+
+ return ((1 << type) & data) != 0;
+ }
+
+ /* Do not block unknown ICMP types */
+ return 0;
+}
+
+/* IP input processing comes here for RAW socket delivery.
+ * This is fun as to avoid copies we want to make no surplus
+ * copies.
+ *
+ * RFC 1122: SHOULD pass TOS value up to the transport layer.
+ * -> It does. And not only TOS, but all IP header.
+ */
+struct sock *raw_v4_input(struct sk_buff *skb, struct iphdr *iph, int hash)
+{
+ struct sock *sk;
+
+ read_lock(&raw_v4_lock);
+ if ((sk = raw_v4_htable[hash]) == NULL)
+ goto out;
+ sk = __raw_v4_lookup(sk, iph->protocol,
+ iph->saddr, iph->daddr,
+ skb->dev->ifindex);
+
+ while (sk) {
+ struct sock *sknext = __raw_v4_lookup(sk->next, iph->protocol,
+ iph->saddr, iph->daddr,
+ skb->dev->ifindex);
+ if (iph->protocol != IPPROTO_ICMP ||
+ !icmp_filter(sk, skb)) {
+ struct sk_buff *clone;
+
+ if (!sknext)
+ break;
+ clone = skb_clone(skb, GFP_ATOMIC);
+ /* Not releasing hash table! */
+ if (clone)
+ raw_rcv(sk, clone);
+ }
+ sk = sknext;
+ }
+out:
+ if (sk)
+ sock_hold(sk);
+ read_unlock(&raw_v4_lock);
+
+ return sk;
+}
+
+void raw_err (struct sock *sk, struct sk_buff *skb, u32 info)
+{
+ int type = skb->h.icmph->type;
+ int code = skb->h.icmph->code;
+ int err = 0;
+ int harderr = 0;
+
+ /* Report error on raw socket, if:
+ 1. User requested ip_recverr.
+ 2. Socket is connected (otherwise the error indication
+ is useless without ip_recverr and error is hard.
+ */
+ if (!sk->protinfo.af_inet.recverr && sk->state != TCP_ESTABLISHED)
+ return;
+
+ switch (type) {
+ default:
+ case ICMP_TIME_EXCEEDED:
+ err = EHOSTUNREACH;
+ break;
+ case ICMP_SOURCE_QUENCH:
+ return;
+ case ICMP_PARAMETERPROB:
+ err = EPROTO;
+ harderr = 1;
+ break;
+ case ICMP_DEST_UNREACH:
+ err = EHOSTUNREACH;
+ if (code > NR_ICMP_UNREACH)
+ break;
+ err = icmp_err_convert[code].errno;
+ harderr = icmp_err_convert[code].fatal;
+ if (code == ICMP_FRAG_NEEDED) {
+ harderr = sk->protinfo.af_inet.pmtudisc !=
+ IP_PMTUDISC_DONT;
+ err = EMSGSIZE;
+ }
+ }
+
+ if (sk->protinfo.af_inet.recverr) {
+ struct iphdr *iph = (struct iphdr*)skb->data;
+ u8 *payload = skb->data + (iph->ihl << 2);
+
+ if (sk->protinfo.af_inet.hdrincl)
+ payload = skb->data;
+ ip_icmp_error(sk, skb, err, 0, info, payload);
+ }
+
+ if (sk->protinfo.af_inet.recverr || harderr) {
+ sk->err = err;
+ sk->error_report(sk);
+ }
+}
+
+static int raw_rcv_skb(struct sock * sk, struct sk_buff * skb)
+{
+ /* Charge it to the socket. */
+
+ if (sock_queue_rcv_skb(sk, skb) < 0) {
+ IP_INC_STATS(IpInDiscards);
+ kfree_skb(skb);
+ return NET_RX_DROP;
+ }
+
+ IP_INC_STATS(IpInDelivers);
+ return NET_RX_SUCCESS;
+}
+
+int raw_rcv(struct sock *sk, struct sk_buff *skb)
+{
+ skb_push(skb, skb->data - skb->nh.raw);
+
+ raw_rcv_skb(sk, skb);
+ return 0;
+}
+
+struct rawfakehdr
+{
+ struct iovec *iov;
+ u32 saddr;
+ struct dst_entry *dst;
+};
+
+/*
+ * Send a RAW IP packet.
+ */
+
+/*
+ * Callback support is trivial for SOCK_RAW
+ */
+
+static int raw_getfrag(const void *p, char *to, unsigned int offset,
+ unsigned int fraglen, struct sk_buff *skb)
+{
+ struct rawfakehdr *rfh = (struct rawfakehdr *) p;
+ return memcpy_fromiovecend(to, rfh->iov, offset, fraglen);
+}
+
+/*
+ * IPPROTO_RAW needs extra work.
+ */
+
+static int raw_getrawfrag(const void *p, char *to, unsigned int offset,
+ unsigned int fraglen, struct sk_buff *skb)
+{
+ struct rawfakehdr *rfh = (struct rawfakehdr *) p;
+
+ if (memcpy_fromiovecend(to, rfh->iov, offset, fraglen))
+ return -EFAULT;
+
+ if (!offset) {
+ struct iphdr *iph = (struct iphdr *)to;
+ if (!iph->saddr)
+ iph->saddr = rfh->saddr;
+ iph->check = 0;
+ iph->tot_len = htons(fraglen); /* This is right as you can't
+ frag RAW packets */
+ /*
+ * Deliberate breach of modularity to keep
+ * ip_build_xmit clean (well less messy).
+ */
+ if (!iph->id)
+ ip_select_ident(iph, rfh->dst, NULL);
+ iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);
+ }
+ return 0;
+}
+
+static int raw_sendmsg(struct sock *sk, struct msghdr *msg, int len)
+{
+ struct ipcm_cookie ipc;
+ struct rawfakehdr rfh;
+ struct rtable *rt = NULL;
+ int free = 0;
+ u32 daddr;
+ u8 tos;
+ int err;
+
+ /* This check is ONLY to check for arithmetic overflow
+ on integer(!) len. Not more! Real check will be made
+ in ip_build_xmit --ANK
+
+ BTW socket.c -> af_*.c -> ... make multiple
+ invalid conversions size_t -> int. We MUST repair it f.e.
+ by replacing all of them with size_t and revise all
+ the places sort of len += sizeof(struct iphdr)
+ If len was ULONG_MAX-10 it would be cathastrophe --ANK
+ */
+
+ err = -EMSGSIZE;
+ if (len < 0 || len > 0xFFFF)
+ goto out;
+
+ /*
+ * Check the flags.
+ */
+
+ err = -EOPNOTSUPP;
+ if (msg->msg_flags & MSG_OOB) /* Mirror BSD error message */
+ goto out; /* compatibility */
+
+ /*
+ * Get and verify the address.
+ */
+
+ if (msg->msg_namelen) {
+ struct sockaddr_in *usin = (struct sockaddr_in*)msg->msg_name;
+ err = -EINVAL;
+ if (msg->msg_namelen < sizeof(*usin))
+ goto out;
+ if (usin->sin_family != AF_INET) {
+ static int complained;
+ if (!complained++)
+ printk(KERN_INFO "%s forgot to set AF_INET in "
+ "raw sendmsg. Fix it!\n",
+ current->comm);
+ err = -EINVAL;
+ if (usin->sin_family)
+ goto out;
+ }
+ daddr = usin->sin_addr.s_addr;
+ /* ANK: I did not forget to get protocol from port field.
+ * I just do not know, who uses this weirdness.
+ * IP_HDRINCL is much more convenient.
+ */
+ } else {
+ err = -EDESTADDRREQ;
+ if (sk->state != TCP_ESTABLISHED)
+ goto out;
+ daddr = sk->daddr;
+ }
+
+ ipc.addr = sk->saddr;
+ ipc.opt = NULL;
+ ipc.oif = sk->bound_dev_if;
+
+ if (msg->msg_controllen) {
+ err = ip_cmsg_send(msg, &ipc);
+ if (err)
+ goto out;
+ if (ipc.opt)
+ free = 1;
+ }
+
+ rfh.saddr = ipc.addr;
+ ipc.addr = daddr;
+
+ if (!ipc.opt)
+ ipc.opt = sk->protinfo.af_inet.opt;
+
+ if (ipc.opt) {
+ err = -EINVAL;
+ /* Linux does not mangle headers on raw sockets,
+ * so that IP options + IP_HDRINCL is non-sense.
+ */
+ if (sk->protinfo.af_inet.hdrincl)
+ goto done;
+ if (ipc.opt->srr) {
+ if (!daddr)
+ goto done;
+ daddr = ipc.opt->faddr;
+ }
+ }
+ tos = RT_TOS(sk->protinfo.af_inet.tos) | sk->localroute;
+ if (msg->msg_flags & MSG_DONTROUTE)
+ tos |= RTO_ONLINK;
+
+ if (MULTICAST(daddr)) {
+ if (!ipc.oif)
+ ipc.oif = sk->protinfo.af_inet.mc_index;
+ if (!rfh.saddr)
+ rfh.saddr = sk->protinfo.af_inet.mc_addr;
+ }
+
+ err = ip_route_output(&rt, daddr, rfh.saddr, tos, ipc.oif);
+
+ if (err)
+ goto done;
+
+ err = -EACCES;
+ if (rt->rt_flags & RTCF_BROADCAST && !sk->broadcast)
+ goto done;
+
+ if (msg->msg_flags & MSG_CONFIRM)
+ goto do_confirm;
+back_from_confirm:
+
+ rfh.iov = msg->msg_iov;
+ rfh.saddr = rt->rt_src;
+ rfh.dst = &rt->u.dst;
+ if (!ipc.addr)
+ ipc.addr = rt->rt_dst;
+ err = ip_build_xmit(sk, sk->protinfo.af_inet.hdrincl ? raw_getrawfrag :
+ raw_getfrag, &rfh, len, &ipc, rt, msg->msg_flags);
+
+done:
+ if (free)
+ kfree(ipc.opt);
+ ip_rt_put(rt);
+
+out: return err < 0 ? err : len;
+
+do_confirm:
+ dst_confirm(&rt->u.dst);
+ if (!(msg->msg_flags & MSG_PROBE) || len)
+ goto back_from_confirm;
+ err = 0;
+ goto done;
+}
+
+static void raw_close(struct sock *sk, long timeout)
+{
+ /*
+ * Raw sockets may have direct kernel refereneces. Kill them.
+ */
+ ip_ra_control(sk, 0, NULL);
+
+ inet_sock_release(sk);
+}
+
+/* This gets rid of all the nasties in af_inet. -DaveM */
+static int raw_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
+{
+ struct sockaddr_in *addr = (struct sockaddr_in *) uaddr;
+ int ret = -EINVAL;
+ int chk_addr_ret;
+
+ if (sk->state != TCP_CLOSE || addr_len < sizeof(struct sockaddr_in))
+ goto out;
+ chk_addr_ret = inet_addr_type(addr->sin_addr.s_addr);
+ ret = -EADDRNOTAVAIL;
+ if (addr->sin_addr.s_addr && chk_addr_ret != RTN_LOCAL &&
+ chk_addr_ret != RTN_MULTICAST && chk_addr_ret != RTN_BROADCAST)
+ goto out;
+ sk->rcv_saddr = sk->saddr = addr->sin_addr.s_addr;
+ if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
+ sk->saddr = 0; /* Use device */
+ sk_dst_reset(sk);
+ ret = 0;
+out: return ret;
+}
+
+/*
+ * This should be easy, if there is something there
+ * we return it, otherwise we block.
+ */
+
+int raw_recvmsg(struct sock *sk, struct msghdr *msg, int len,
+ int noblock, int flags, int *addr_len)
+{
+ int copied = 0;
+ int err = -EOPNOTSUPP;
+ struct sockaddr_in *sin = (struct sockaddr_in *)msg->msg_name;
+ struct sk_buff *skb;
+
+ if (flags & MSG_OOB)
+ goto out;
+
+ if (addr_len)
+ *addr_len = sizeof(*sin);
+
+ if (flags & MSG_ERRQUEUE) {
+ err = ip_recv_error(sk, msg, len);
+ goto out;
+ }
+
+ skb = skb_recv_datagram(sk, flags, noblock, &err);
+ if (!skb)
+ goto out;
+
+ copied = skb->len;
+ if (len < copied) {
+ msg->msg_flags |= MSG_TRUNC;
+ copied = len;
+ }
+
+ err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+ if (err)
+ goto done;
+
+ sock_recv_timestamp(msg, sk, skb);
+
+ /* Copy the address. */
+ if (sin) {
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = skb->nh.iph->saddr;
+ sin->sin_port = 0;
+ memset(&sin->sin_zero, 0, sizeof(sin->sin_zero));
+ }
+ if (sk->protinfo.af_inet.cmsg_flags)
+ ip_cmsg_recv(msg, skb);
+ if (flags & MSG_TRUNC)
+ copied = skb->len;
+done:
+ skb_free_datagram(sk, skb);
+out: return err ? : copied;
+}
+
+static int raw_init(struct sock *sk)
+{
+ struct raw_opt *tp = &(sk->tp_pinfo.tp_raw4);
+ if (sk->num == IPPROTO_ICMP)
+ memset(&tp->filter, 0, sizeof(tp->filter));
+ return 0;
+}
+
+static int raw_seticmpfilter(struct sock *sk, char *optval, int optlen)
+{
+ if (optlen > sizeof(struct icmp_filter))
+ optlen = sizeof(struct icmp_filter);
+ if (copy_from_user(&sk->tp_pinfo.tp_raw4.filter, optval, optlen))
+ return -EFAULT;
+ return 0;
+}
+
+static int raw_geticmpfilter(struct sock *sk, char *optval, int *optlen)
+{
+ int len, ret = -EFAULT;
+
+ if (get_user(len, optlen))
+ goto out;
+ ret = -EINVAL;
+ if (len < 0)
+ goto out;
+ if (len > sizeof(struct icmp_filter))
+ len = sizeof(struct icmp_filter);
+ ret = -EFAULT;
+ if (put_user(len, optlen) ||
+ copy_to_user(optval, &sk->tp_pinfo.tp_raw4.filter, len))
+ goto out;
+ ret = 0;
+out: return ret;
+}
+
+static int raw_setsockopt(struct sock *sk, int level, int optname,
+ char *optval, int optlen)
+{
+ if (level != SOL_RAW)
+ return ip_setsockopt(sk, level, optname, optval, optlen);
+
+ if (optname == ICMP_FILTER) {
+ if (sk->num != IPPROTO_ICMP)
+ return -EOPNOTSUPP;
+ else
+ return raw_seticmpfilter(sk, optval, optlen);
+ }
+ return -ENOPROTOOPT;
+}
+
+static int raw_getsockopt(struct sock *sk, int level, int optname,
+ char *optval, int *optlen)
+{
+ if (level != SOL_RAW)
+ return ip_getsockopt(sk, level, optname, optval, optlen);
+
+ if (optname == ICMP_FILTER) {
+ if (sk->num != IPPROTO_ICMP)
+ return -EOPNOTSUPP;
+ else
+ return raw_geticmpfilter(sk, optval, optlen);
+ }
+ return -ENOPROTOOPT;
+}
+
+static int raw_ioctl(struct sock *sk, int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case SIOCOUTQ: {
+ int amount = atomic_read(&sk->wmem_alloc);
+ return put_user(amount, (int *)arg);
+ }
+ case SIOCINQ: {
+ struct sk_buff *skb;
+ int amount = 0;
+
+ spin_lock_irq(&sk->receive_queue.lock);
+ skb = skb_peek(&sk->receive_queue);
+ if (skb != NULL)
+ amount = skb->len;
+ spin_unlock_irq(&sk->receive_queue.lock);
+ return put_user(amount, (int *)arg);
+ }
+
+ default:
+#ifdef CONFIG_IP_MROUTE
+ return ipmr_ioctl(sk, cmd, arg);
+#else
+ return -ENOIOCTLCMD;
+#endif
+ }
+}
+
+static void get_raw_sock(struct sock *sp, char *tmpbuf, int i)
+{
+ unsigned int dest = sp->daddr,
+ src = sp->rcv_saddr;
+ __u16 destp = 0,
+ srcp = sp->num;
+
+ sprintf(tmpbuf, "%4d: %08X:%04X %08X:%04X"
+ " %02X %08X:%08X %02X:%08lX %08X %5d %8d %lu %d %p",
+ i, src, srcp, dest, destp, sp->state,
+ atomic_read(&sp->wmem_alloc), atomic_read(&sp->rmem_alloc),
+ 0, 0L, 0,
+ sock_i_uid(sp), 0,
+ sock_i_ino(sp),
+ atomic_read(&sp->refcnt), sp);
+}
+
+int raw_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int len = 0, num = 0, i;
+ off_t pos = 128;
+ off_t begin;
+ char tmpbuf[129];
+
+ if (offset < 128)
+ len += sprintf(buffer, "%-127s\n",
+ " sl local_address rem_address st tx_queue "
+ "rx_queue tr tm->when retrnsmt uid timeout "
+ "inode");
+ read_lock(&raw_v4_lock);
+ for (i = 0; i < RAWV4_HTABLE_SIZE; i++) {
+ struct sock *sk;
+
+ for (sk = raw_v4_htable[i]; sk; sk = sk->next, num++) {
+ if (sk->family != PF_INET)
+ continue;
+ pos += 128;
+ if (pos <= offset)
+ continue;
+ get_raw_sock(sk, tmpbuf, i);
+ len += sprintf(buffer + len, "%-127s\n", tmpbuf);
+ if (len >= length)
+ goto out;
+ }
+ }
+out:
+ read_unlock(&raw_v4_lock);
+ begin = len - (pos - offset);
+ *start = buffer + begin;
+ len -= begin;
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+struct proto raw_prot = {
+ name: "RAW",
+ close: raw_close,
+ connect: udp_connect,
+ disconnect: udp_disconnect,
+ ioctl: raw_ioctl,
+ init: raw_init,
+ setsockopt: raw_setsockopt,
+ getsockopt: raw_getsockopt,
+ sendmsg: raw_sendmsg,
+ recvmsg: raw_recvmsg,
+ bind: raw_bind,
+ backlog_rcv: raw_rcv_skb,
+ hash: raw_v4_hash,
+ unhash: raw_v4_unhash,
+};
diff --git a/uClinux-2.4.31-uc0/net/ipv4/route.c b/uClinux-2.4.31-uc0/net/ipv4/route.c
new file mode 100644
index 0000000..9a93992
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/route.c
@@ -0,0 +1,2643 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * ROUTE - implementation of the IP router.
+ *
+ * Version: $Id: route.c,v 1.102.2.1 2002/01/12 07:43:57 davem Exp $
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Alan Cox, <gw4pts@gw4pts.ampr.org>
+ * Linus Torvalds, <Linus.Torvalds@helsinki.fi>
+ * Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Fixes:
+ * Alan Cox : Verify area fixes.
+ * Alan Cox : cli() protects routing changes
+ * Rui Oliveira : ICMP routing table updates
+ * (rco@di.uminho.pt) Routing table insertion and update
+ * Linus Torvalds : Rewrote bits to be sensible
+ * Alan Cox : Added BSD route gw semantics
+ * Alan Cox : Super /proc >4K
+ * Alan Cox : MTU in route table
+ * Alan Cox : MSS actually. Also added the window
+ * clamper.
+ * Sam Lantinga : Fixed route matching in rt_del()
+ * Alan Cox : Routing cache support.
+ * Alan Cox : Removed compatibility cruft.
+ * Alan Cox : RTF_REJECT support.
+ * Alan Cox : TCP irtt support.
+ * Jonathan Naylor : Added Metric support.
+ * Miquel van Smoorenburg : BSD API fixes.
+ * Miquel van Smoorenburg : Metrics.
+ * Alan Cox : Use __u32 properly
+ * Alan Cox : Aligned routing errors more closely with BSD
+ * our system is still very different.
+ * Alan Cox : Faster /proc handling
+ * Alexey Kuznetsov : Massive rework to support tree based routing,
+ * routing caches and better behaviour.
+ *
+ * Olaf Erb : irtt wasn't being copied right.
+ * Bjorn Ekwall : Kerneld route support.
+ * Alan Cox : Multicast fixed (I hope)
+ * Pavel Krauz : Limited broadcast fixed
+ * Mike McLagan : Routing by source
+ * Alexey Kuznetsov : End of old history. Splitted to fib.c and
+ * route.c and rewritten from scratch.
+ * Andi Kleen : Load-limit warning messages.
+ * Vitaly E. Lavrov : Transparent proxy revived after year coma.
+ * Vitaly E. Lavrov : Race condition in ip_route_input_slow.
+ * Tobias Ringstrom : Uninitialized res.type in ip_route_output_slow.
+ * Vladimir V. Ivanov : IP rule info (flowid) is really useful.
+ * Marc Boucher : routing by fwmark
+ * Robert Olsson : Added rt_cache statistics
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/errno.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/inetdevice.h>
+#include <linux/igmp.h>
+#include <linux/pkt_sched.h>
+#include <linux/mroute.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/random.h>
+#include <linux/jhash.h>
+#include <net/protocol.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <net/inetpeer.h>
+#include <net/sock.h>
+#include <net/ip_fib.h>
+#include <net/arp.h>
+#include <net/tcp.h>
+#include <net/icmp.h>
+#ifdef CONFIG_SYSCTL
+#include <linux/sysctl.h>
+#endif
+
+#define IP_MAX_MTU 0xFFF0
+
+#define RT_GC_TIMEOUT (300*HZ)
+
+int ip_rt_min_delay = 2 * HZ;
+int ip_rt_max_delay = 10 * HZ;
+int ip_rt_max_size;
+int ip_rt_gc_timeout = RT_GC_TIMEOUT;
+int ip_rt_gc_interval = 60 * HZ;
+int ip_rt_gc_min_interval = HZ / 2;
+int ip_rt_redirect_number = 9;
+int ip_rt_redirect_load = HZ / 50;
+int ip_rt_redirect_silence = ((HZ / 50) << (9 + 1));
+int ip_rt_error_cost = HZ;
+int ip_rt_error_burst = 5 * HZ;
+int ip_rt_gc_elasticity = 8;
+int ip_rt_mtu_expires = 10 * 60 * HZ;
+int ip_rt_min_pmtu = 512 + 20 + 20;
+int ip_rt_min_advmss = 256;
+int ip_rt_secret_interval = 10 * 60 * HZ;
+static unsigned long rt_deadline;
+
+#define RTprint(a...) printk(KERN_DEBUG a)
+
+static struct timer_list rt_flush_timer;
+static struct timer_list rt_periodic_timer;
+static struct timer_list rt_secret_timer;
+
+/*
+ * Interface to generic destination cache.
+ */
+
+static struct dst_entry *ipv4_dst_check(struct dst_entry *dst, u32 cookie);
+static struct dst_entry *ipv4_dst_reroute(struct dst_entry *dst,
+ struct sk_buff *skb);
+static void ipv4_dst_destroy(struct dst_entry *dst);
+static struct dst_entry *ipv4_negative_advice(struct dst_entry *dst);
+static void ipv4_link_failure(struct sk_buff *skb);
+static int rt_garbage_collect(void);
+
+
+struct dst_ops ipv4_dst_ops = {
+ family: AF_INET,
+ protocol: __constant_htons(ETH_P_IP),
+ gc: rt_garbage_collect,
+ check: ipv4_dst_check,
+ reroute: ipv4_dst_reroute,
+ destroy: ipv4_dst_destroy,
+ negative_advice: ipv4_negative_advice,
+ link_failure: ipv4_link_failure,
+ entry_size: sizeof(struct rtable),
+};
+
+#define ECN_OR_COST(class) TC_PRIO_##class
+
+__u8 ip_tos2prio[16] = {
+ TC_PRIO_BESTEFFORT,
+ ECN_OR_COST(FILLER),
+ TC_PRIO_BESTEFFORT,
+ ECN_OR_COST(BESTEFFORT),
+ TC_PRIO_BULK,
+ ECN_OR_COST(BULK),
+ TC_PRIO_BULK,
+ ECN_OR_COST(BULK),
+ TC_PRIO_INTERACTIVE,
+ ECN_OR_COST(INTERACTIVE),
+ TC_PRIO_INTERACTIVE,
+ ECN_OR_COST(INTERACTIVE),
+ TC_PRIO_INTERACTIVE_BULK,
+ ECN_OR_COST(INTERACTIVE_BULK),
+ TC_PRIO_INTERACTIVE_BULK,
+ ECN_OR_COST(INTERACTIVE_BULK)
+};
+
+
+/*
+ * Route cache.
+ */
+
+/* The locking scheme is rather straight forward:
+ *
+ * 1) A BH protected rwlocks protect buckets of the central route hash.
+ * 2) Only writers remove entries, and they hold the lock
+ * as they look at rtable reference counts.
+ * 3) Only readers acquire references to rtable entries,
+ * they do so with atomic increments and with the
+ * lock held.
+ */
+
+struct rt_hash_bucket {
+ struct rtable *chain;
+ rwlock_t lock;
+} __attribute__((__aligned__(8)));
+
+static struct rt_hash_bucket *rt_hash_table;
+static unsigned rt_hash_mask;
+static int rt_hash_log;
+static unsigned int rt_hash_rnd;
+
+struct rt_cache_stat rt_cache_stat[NR_CPUS];
+
+static int rt_intern_hash(unsigned hash, struct rtable *rth,
+ struct rtable **res);
+
+static unsigned int rt_hash_code(u32 daddr, u32 saddr, u8 tos)
+{
+ return (jhash_3words(daddr, saddr, (u32) tos, rt_hash_rnd)
+ & rt_hash_mask);
+}
+
+static int rt_cache_get_info(char *buffer, char **start, off_t offset,
+ int length)
+{
+ int len = 0;
+ off_t pos = 128;
+ char temp[256];
+ struct rtable *r;
+ int i;
+
+ if (offset < 128) {
+ sprintf(buffer, "%-127s\n",
+ "Iface\tDestination\tGateway \tFlags\t\tRefCnt\tUse\t"
+ "Metric\tSource\t\tMTU\tWindow\tIRTT\tTOS\tHHRef\t"
+ "HHUptod\tSpecDst");
+ len = 128;
+ }
+
+ for (i = rt_hash_mask; i >= 0; i--) {
+ read_lock_bh(&rt_hash_table[i].lock);
+ for (r = rt_hash_table[i].chain; r; r = r->u.rt_next) {
+ /*
+ * Spin through entries until we are ready
+ */
+ pos += 128;
+
+ if (pos <= offset) {
+ len = 0;
+ continue;
+ }
+ sprintf(temp, "%s\t%08lX\t%08lX\t%8X\t%d\t%u\t%d\t"
+ "%08lX\t%d\t%u\t%u\t%02X\t%d\t%1d\t%08X",
+ r->u.dst.dev ? r->u.dst.dev->name : "*",
+ (unsigned long)r->rt_dst,
+ (unsigned long)r->rt_gateway,
+ r->rt_flags,
+ atomic_read(&r->u.dst.__refcnt),
+ r->u.dst.__use,
+ 0,
+ (unsigned long)r->rt_src,
+ (r->u.dst.advmss ?
+ (int) r->u.dst.advmss + 40 : 0),
+ r->u.dst.window,
+ (int)((r->u.dst.rtt >> 3) + r->u.dst.rttvar),
+ r->key.tos,
+ r->u.dst.hh ?
+ atomic_read(&r->u.dst.hh->hh_refcnt) :
+ -1,
+ r->u.dst.hh ?
+ (r->u.dst.hh->hh_output ==
+ dev_queue_xmit) : 0,
+ r->rt_spec_dst);
+ sprintf(buffer + len, "%-127s\n", temp);
+ len += 128;
+ if (pos >= offset+length) {
+ read_unlock_bh(&rt_hash_table[i].lock);
+ goto done;
+ }
+ }
+ read_unlock_bh(&rt_hash_table[i].lock);
+ }
+
+done:
+ *start = buffer + len - (pos - offset);
+ len = pos - offset;
+ if (len > length)
+ len = length;
+ return len;
+}
+
+static int rt_cache_stat_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ unsigned int dst_entries = atomic_read(&ipv4_dst_ops.entries);
+ int i, lcpu;
+ int len = 0;
+
+ len += sprintf(buffer+len, "entries in_hit in_slow_tot in_slow_mc in_no_route in_brd in_martian_dst in_martian_src out_hit out_slow_tot out_slow_mc gc_total gc_ignored gc_goal_miss gc_dst_overflow in_hlist_search out_hlist_search\n");
+ for (lcpu = 0; lcpu < smp_num_cpus; lcpu++) {
+ i = cpu_logical_map(lcpu);
+
+ len += sprintf(buffer+len, "%08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x \n",
+ dst_entries,
+ rt_cache_stat[i].in_hit,
+ rt_cache_stat[i].in_slow_tot,
+ rt_cache_stat[i].in_slow_mc,
+ rt_cache_stat[i].in_no_route,
+ rt_cache_stat[i].in_brd,
+ rt_cache_stat[i].in_martian_dst,
+ rt_cache_stat[i].in_martian_src,
+
+ rt_cache_stat[i].out_hit,
+ rt_cache_stat[i].out_slow_tot,
+ rt_cache_stat[i].out_slow_mc,
+
+ rt_cache_stat[i].gc_total,
+ rt_cache_stat[i].gc_ignored,
+ rt_cache_stat[i].gc_goal_miss,
+ rt_cache_stat[i].gc_dst_overflow,
+ rt_cache_stat[i].in_hlist_search,
+ rt_cache_stat[i].out_hlist_search
+
+ );
+ }
+ len -= offset;
+
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+
+ *start = buffer + offset;
+ return len;
+}
+
+static __inline__ void rt_free(struct rtable *rt)
+{
+ dst_free(&rt->u.dst);
+}
+
+static __inline__ void rt_drop(struct rtable *rt)
+{
+ ip_rt_put(rt);
+ dst_free(&rt->u.dst);
+}
+
+static __inline__ int rt_fast_clean(struct rtable *rth)
+{
+ /* Kill broadcast/multicast entries very aggresively, if they
+ collide in hash table with more useful entries */
+ return (rth->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) &&
+ rth->key.iif && rth->u.rt_next;
+}
+
+static __inline__ int rt_valuable(struct rtable *rth)
+{
+ return (rth->rt_flags & (RTCF_REDIRECTED | RTCF_NOTIFY)) ||
+ rth->u.dst.expires;
+}
+
+static __inline__ int rt_may_expire(struct rtable *rth, unsigned long tmo1, unsigned long tmo2)
+{
+ unsigned long age;
+ int ret = 0;
+
+ if (atomic_read(&rth->u.dst.__refcnt))
+ goto out;
+
+ ret = 1;
+ if (rth->u.dst.expires &&
+ time_after_eq(jiffies, rth->u.dst.expires))
+ goto out;
+
+ age = jiffies - rth->u.dst.lastuse;
+ ret = 0;
+ if ((age <= tmo1 && !rt_fast_clean(rth)) ||
+ (age <= tmo2 && rt_valuable(rth)))
+ goto out;
+ ret = 1;
+out: return ret;
+}
+
+/* Bits of score are:
+ * 31: very valuable
+ * 30: not quite useless
+ * 29..0: usage counter
+ */
+static inline u32 rt_score(struct rtable *rt)
+{
+ u32 score = jiffies - rt->u.dst.lastuse;
+
+ score = ~score & ~(3<<30);
+
+ if (rt_valuable(rt))
+ score |= (1<<31);
+
+ if (!rt->key.iif ||
+ !(rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST|RTCF_LOCAL)))
+ score |= (1<<30);
+
+ return score;
+}
+
+/* This runs via a timer and thus is always in BH context. */
+static void SMP_TIMER_NAME(rt_check_expire)(unsigned long dummy)
+{
+ static int rover;
+ int i = rover, t;
+ struct rtable *rth, **rthp;
+ unsigned long now = jiffies;
+
+ for (t = ip_rt_gc_interval << rt_hash_log; t >= 0;
+ t -= ip_rt_gc_timeout) {
+ unsigned long tmo = ip_rt_gc_timeout;
+
+ i = (i + 1) & rt_hash_mask;
+ rthp = &rt_hash_table[i].chain;
+
+ write_lock(&rt_hash_table[i].lock);
+ while ((rth = *rthp) != NULL) {
+ if (rth->u.dst.expires) {
+ /* Entry is expired even if it is in use */
+ if (time_before_eq(now, rth->u.dst.expires)) {
+ tmo >>= 1;
+ rthp = &rth->u.rt_next;
+ continue;
+ }
+ } else if (!rt_may_expire(rth, tmo, ip_rt_gc_timeout)) {
+ tmo >>= 1;
+ rthp = &rth->u.rt_next;
+ continue;
+ }
+
+ /* Cleanup aged off entries. */
+ *rthp = rth->u.rt_next;
+ rt_free(rth);
+ }
+ write_unlock(&rt_hash_table[i].lock);
+
+ /* Fallback loop breaker. */
+ if (time_after(jiffies, now))
+ break;
+ }
+ rover = i;
+ mod_timer(&rt_periodic_timer, now + ip_rt_gc_interval);
+}
+
+SMP_TIMER_DEFINE(rt_check_expire, rt_gc_task);
+
+/* This can run from both BH and non-BH contexts, the latter
+ * in the case of a forced flush event.
+ */
+static void SMP_TIMER_NAME(rt_run_flush)(unsigned long dummy)
+{
+ int i;
+ struct rtable *rth, *next;
+
+ rt_deadline = 0;
+
+ get_random_bytes(&rt_hash_rnd, 4);
+
+ for (i = rt_hash_mask; i >= 0; i--) {
+ write_lock_bh(&rt_hash_table[i].lock);
+ rth = rt_hash_table[i].chain;
+ if (rth)
+ rt_hash_table[i].chain = NULL;
+ write_unlock_bh(&rt_hash_table[i].lock);
+
+ for (; rth; rth = next) {
+ next = rth->u.rt_next;
+ rt_free(rth);
+ }
+ }
+}
+
+SMP_TIMER_DEFINE(rt_run_flush, rt_cache_flush_task);
+
+static spinlock_t rt_flush_lock = SPIN_LOCK_UNLOCKED;
+
+void rt_cache_flush(int delay)
+{
+ unsigned long now = jiffies;
+ int user_mode = !in_softirq();
+
+ if (delay < 0)
+ delay = ip_rt_min_delay;
+
+ spin_lock_bh(&rt_flush_lock);
+
+ if (del_timer(&rt_flush_timer) && delay > 0 && rt_deadline) {
+ long tmo = (long)(rt_deadline - now);
+
+ /* If flush timer is already running
+ and flush request is not immediate (delay > 0):
+
+ if deadline is not achieved, prolongate timer to "delay",
+ otherwise fire it at deadline time.
+ */
+
+ if (user_mode && tmo < ip_rt_max_delay-ip_rt_min_delay)
+ tmo = 0;
+
+ if (delay > tmo)
+ delay = tmo;
+ }
+
+ if (delay <= 0) {
+ spin_unlock_bh(&rt_flush_lock);
+ SMP_TIMER_NAME(rt_run_flush)(0);
+ return;
+ }
+
+ if (rt_deadline == 0)
+ rt_deadline = now + ip_rt_max_delay;
+
+ mod_timer(&rt_flush_timer, now+delay);
+ spin_unlock_bh(&rt_flush_lock);
+}
+
+static void rt_secret_rebuild(unsigned long dummy)
+{
+ unsigned long now = jiffies;
+
+ rt_cache_flush(0);
+ mod_timer(&rt_secret_timer, now + ip_rt_secret_interval);
+}
+
+/*
+ Short description of GC goals.
+
+ We want to build algorithm, which will keep routing cache
+ at some equilibrium point, when number of aged off entries
+ is kept approximately equal to newly generated ones.
+
+ Current expiration strength is variable "expire".
+ We try to adjust it dynamically, so that if networking
+ is idle expires is large enough to keep enough of warm entries,
+ and when load increases it reduces to limit cache size.
+ */
+
+static int rt_garbage_collect(void)
+{
+ static unsigned long expire = RT_GC_TIMEOUT;
+ static unsigned long last_gc;
+ static int rover;
+ static int equilibrium;
+ struct rtable *rth, **rthp;
+ unsigned long now = jiffies;
+ int goal;
+
+ /*
+ * Garbage collection is pretty expensive,
+ * do not make it too frequently.
+ */
+
+ rt_cache_stat[smp_processor_id()].gc_total++;
+
+ if (now - last_gc < ip_rt_gc_min_interval &&
+ atomic_read(&ipv4_dst_ops.entries) < ip_rt_max_size) {
+ rt_cache_stat[smp_processor_id()].gc_ignored++;
+ goto out;
+ }
+
+ /* Calculate number of entries, which we want to expire now. */
+ goal = atomic_read(&ipv4_dst_ops.entries) -
+ (ip_rt_gc_elasticity << rt_hash_log);
+ if (goal <= 0) {
+ if (equilibrium < ipv4_dst_ops.gc_thresh)
+ equilibrium = ipv4_dst_ops.gc_thresh;
+ goal = atomic_read(&ipv4_dst_ops.entries) - equilibrium;
+ if (goal > 0) {
+ equilibrium += min_t(unsigned int, goal / 2, rt_hash_mask + 1);
+ goal = atomic_read(&ipv4_dst_ops.entries) - equilibrium;
+ }
+ } else {
+ /* We are in dangerous area. Try to reduce cache really
+ * aggressively.
+ */
+ goal = max_t(unsigned int, goal / 2, rt_hash_mask + 1);
+ equilibrium = atomic_read(&ipv4_dst_ops.entries) - goal;
+ }
+
+ if (now - last_gc >= ip_rt_gc_min_interval)
+ last_gc = now;
+
+ if (goal <= 0) {
+ equilibrium += goal;
+ goto work_done;
+ }
+
+ do {
+ int i, k;
+
+ for (i = rt_hash_mask, k = rover; i >= 0; i--) {
+ unsigned long tmo = expire;
+
+ k = (k + 1) & rt_hash_mask;
+ rthp = &rt_hash_table[k].chain;
+ write_lock_bh(&rt_hash_table[k].lock);
+ while ((rth = *rthp) != NULL) {
+ if (!rt_may_expire(rth, tmo, expire)) {
+ tmo >>= 1;
+ rthp = &rth->u.rt_next;
+ continue;
+ }
+ *rthp = rth->u.rt_next;
+ rt_free(rth);
+ goal--;
+ }
+ write_unlock_bh(&rt_hash_table[k].lock);
+ if (goal <= 0)
+ break;
+ }
+ rover = k;
+
+ if (goal <= 0)
+ goto work_done;
+
+ /* Goal is not achieved. We stop process if:
+
+ - if expire reduced to zero. Otherwise, expire is halfed.
+ - if table is not full.
+ - if we are called from interrupt.
+ - jiffies check is just fallback/debug loop breaker.
+ We will not spin here for long time in any case.
+ */
+
+ rt_cache_stat[smp_processor_id()].gc_goal_miss++;
+
+ if (expire == 0)
+ break;
+
+ expire >>= 1;
+#if RT_CACHE_DEBUG >= 2
+ printk(KERN_DEBUG "expire>> %u %d %d %d\n", expire,
+ atomic_read(&ipv4_dst_ops.entries), goal, i);
+#endif
+
+ if (atomic_read(&ipv4_dst_ops.entries) < ip_rt_max_size)
+ goto out;
+ } while (!in_softirq() && time_before_eq(jiffies, now));
+
+ if (atomic_read(&ipv4_dst_ops.entries) < ip_rt_max_size)
+ goto out;
+ if (net_ratelimit())
+ printk(KERN_WARNING "dst cache overflow\n");
+ rt_cache_stat[smp_processor_id()].gc_dst_overflow++;
+ return 1;
+
+work_done:
+ expire += ip_rt_gc_min_interval;
+ if (expire > ip_rt_gc_timeout ||
+ atomic_read(&ipv4_dst_ops.entries) < ipv4_dst_ops.gc_thresh)
+ expire = ip_rt_gc_timeout;
+#if RT_CACHE_DEBUG >= 2
+ printk(KERN_DEBUG "expire++ %u %d %d %d\n", expire,
+ atomic_read(&ipv4_dst_ops.entries), goal, rover);
+#endif
+out: return 0;
+}
+
+static int rt_intern_hash(unsigned hash, struct rtable *rt, struct rtable **rp)
+{
+ struct rtable *rth, **rthp;
+ unsigned long now;
+ struct rtable *cand, **candp;
+ u32 min_score;
+ int chain_length;
+ int attempts = !in_softirq();
+
+restart:
+ chain_length = 0;
+ min_score = ~(u32)0;
+ cand = NULL;
+ candp = NULL;
+ now = jiffies;
+
+ rthp = &rt_hash_table[hash].chain;
+
+ write_lock_bh(&rt_hash_table[hash].lock);
+ while ((rth = *rthp) != NULL) {
+ if (memcmp(&rth->key, &rt->key, sizeof(rt->key)) == 0) {
+ /* Put it first */
+ *rthp = rth->u.rt_next;
+ rth->u.rt_next = rt_hash_table[hash].chain;
+ rt_hash_table[hash].chain = rth;
+
+ rth->u.dst.__use++;
+ dst_hold(&rth->u.dst);
+ rth->u.dst.lastuse = now;
+ write_unlock_bh(&rt_hash_table[hash].lock);
+
+ rt_drop(rt);
+ *rp = rth;
+ return 0;
+ }
+
+ if (!atomic_read(&rth->u.dst.__refcnt)) {
+ u32 score = rt_score(rth);
+
+ if (score <= min_score) {
+ cand = rth;
+ candp = rthp;
+ min_score = score;
+ }
+ }
+
+ chain_length++;
+
+ rthp = &rth->u.rt_next;
+ }
+
+ if (cand) {
+ /* ip_rt_gc_elasticity used to be average length of chain
+ * length, when exceeded gc becomes really aggressive.
+ *
+ * The second limit is less certain. At the moment it allows
+ * only 2 entries per bucket. We will see.
+ */
+ if (chain_length > ip_rt_gc_elasticity) {
+ *candp = cand->u.rt_next;
+ rt_free(cand);
+ }
+ }
+
+ /* Try to bind route to arp only if it is output
+ route or unicast forwarding path.
+ */
+ if (rt->rt_type == RTN_UNICAST || rt->key.iif == 0) {
+ int err = arp_bind_neighbour(&rt->u.dst);
+ if (err) {
+ write_unlock_bh(&rt_hash_table[hash].lock);
+
+ if (err != -ENOBUFS) {
+ rt_drop(rt);
+ return err;
+ }
+
+ /* Neighbour tables are full and nothing
+ can be released. Try to shrink route cache,
+ it is most likely it holds some neighbour records.
+ */
+ if (attempts-- > 0) {
+ int saved_elasticity = ip_rt_gc_elasticity;
+ int saved_int = ip_rt_gc_min_interval;
+ ip_rt_gc_elasticity = 1;
+ ip_rt_gc_min_interval = 0;
+ rt_garbage_collect();
+ ip_rt_gc_min_interval = saved_int;
+ ip_rt_gc_elasticity = saved_elasticity;
+ goto restart;
+ }
+
+ if (net_ratelimit())
+ printk(KERN_WARNING "Neighbour table overflow.\n");
+ rt_drop(rt);
+ return -ENOBUFS;
+ }
+ }
+
+ rt->u.rt_next = rt_hash_table[hash].chain;
+#if RT_CACHE_DEBUG >= 2
+ if (rt->u.rt_next) {
+ struct rtable *trt;
+ printk(KERN_DEBUG "rt_cache @%02x: %u.%u.%u.%u", hash,
+ NIPQUAD(rt->rt_dst));
+ for (trt = rt->u.rt_next; trt; trt = trt->u.rt_next)
+ printk(" . %u.%u.%u.%u", NIPQUAD(trt->rt_dst));
+ printk("\n");
+ }
+#endif
+ rt_hash_table[hash].chain = rt;
+ write_unlock_bh(&rt_hash_table[hash].lock);
+ *rp = rt;
+ return 0;
+}
+
+void rt_bind_peer(struct rtable *rt, int create)
+{
+ static spinlock_t rt_peer_lock = SPIN_LOCK_UNLOCKED;
+ struct inet_peer *peer;
+
+ peer = inet_getpeer(rt->rt_dst, create);
+
+ spin_lock_bh(&rt_peer_lock);
+ if (rt->peer == NULL) {
+ rt->peer = peer;
+ peer = NULL;
+ }
+ spin_unlock_bh(&rt_peer_lock);
+ if (peer)
+ inet_putpeer(peer);
+}
+
+/*
+ * Peer allocation may fail only in serious out-of-memory conditions. However
+ * we still can generate some output.
+ * Random ID selection looks a bit dangerous because we have no chances to
+ * select ID being unique in a reasonable period of time.
+ * But broken packet identifier may be better than no packet at all.
+ */
+static void ip_select_fb_ident(struct iphdr *iph)
+{
+ static spinlock_t ip_fb_id_lock = SPIN_LOCK_UNLOCKED;
+ static u32 ip_fallback_id;
+ u32 salt;
+
+ spin_lock_bh(&ip_fb_id_lock);
+ salt = secure_ip_id(ip_fallback_id ^ iph->daddr);
+ iph->id = htons(salt & 0xFFFF);
+ ip_fallback_id = salt;
+ spin_unlock_bh(&ip_fb_id_lock);
+}
+
+void __ip_select_ident(struct iphdr *iph, struct dst_entry *dst)
+{
+ struct rtable *rt = (struct rtable *) dst;
+
+ if (rt) {
+ if (rt->peer == NULL)
+ rt_bind_peer(rt, 1);
+
+ /* If peer is attached to destination, it is never detached,
+ so that we need not to grab a lock to dereference it.
+ */
+ if (rt->peer) {
+ iph->id = htons(inet_getid(rt->peer));
+ return;
+ }
+ } else
+ printk(KERN_DEBUG "rt_bind_peer(0) @%p\n", NET_CALLER(iph));
+
+ ip_select_fb_ident(iph);
+}
+
+static void rt_del(unsigned hash, struct rtable *rt)
+{
+ struct rtable **rthp;
+
+ write_lock_bh(&rt_hash_table[hash].lock);
+ ip_rt_put(rt);
+ for (rthp = &rt_hash_table[hash].chain; *rthp;
+ rthp = &(*rthp)->u.rt_next)
+ if (*rthp == rt) {
+ *rthp = rt->u.rt_next;
+ rt_free(rt);
+ break;
+ }
+ write_unlock_bh(&rt_hash_table[hash].lock);
+}
+
+void ip_rt_redirect(u32 old_gw, u32 daddr, u32 new_gw,
+ u32 saddr, u8 tos, struct net_device *dev)
+{
+ int i, k;
+ struct in_device *in_dev = in_dev_get(dev);
+ struct rtable *rth, **rthp;
+ u32 skeys[2] = { saddr, 0 };
+ int ikeys[2] = { dev->ifindex, 0 };
+
+ tos &= IPTOS_RT_MASK;
+
+ if (!in_dev)
+ return;
+
+ if (new_gw == old_gw || !IN_DEV_RX_REDIRECTS(in_dev)
+ || MULTICAST(new_gw) || BADCLASS(new_gw) || ZERONET(new_gw))
+ goto reject_redirect;
+
+ if (!IN_DEV_SHARED_MEDIA(in_dev)) {
+ if (!inet_addr_onlink(in_dev, new_gw, old_gw))
+ goto reject_redirect;
+ if (IN_DEV_SEC_REDIRECTS(in_dev) && ip_fib_check_default(new_gw, dev))
+ goto reject_redirect;
+ } else {
+ if (inet_addr_type(new_gw) != RTN_UNICAST)
+ goto reject_redirect;
+ }
+
+ for (i = 0; i < 2; i++) {
+ for (k = 0; k < 2; k++) {
+ unsigned hash = rt_hash_code(daddr,
+ skeys[i] ^ (ikeys[k] << 5),
+ tos);
+
+ rthp=&rt_hash_table[hash].chain;
+
+ read_lock(&rt_hash_table[hash].lock);
+ while ((rth = *rthp) != NULL) {
+ struct rtable *rt;
+
+ if (rth->key.dst != daddr ||
+ rth->key.src != skeys[i] ||
+ rth->key.tos != tos ||
+ rth->key.oif != ikeys[k] ||
+ rth->key.iif != 0) {
+ rthp = &rth->u.rt_next;
+ continue;
+ }
+
+ if (rth->rt_dst != daddr ||
+ rth->rt_src != saddr ||
+ rth->u.dst.error ||
+ rth->rt_gateway != old_gw ||
+ rth->u.dst.dev != dev)
+ break;
+
+ dst_hold(&rth->u.dst);
+ read_unlock(&rt_hash_table[hash].lock);
+
+ rt = dst_alloc(&ipv4_dst_ops);
+ if (rt == NULL) {
+ ip_rt_put(rth);
+ in_dev_put(in_dev);
+ return;
+ }
+
+ /* Copy all the information. */
+ *rt = *rth;
+ rt->u.dst.__use = 1;
+ atomic_set(&rt->u.dst.__refcnt, 1);
+ if (rt->u.dst.dev)
+ dev_hold(rt->u.dst.dev);
+ rt->u.dst.lastuse = jiffies;
+ rt->u.dst.neighbour = NULL;
+ rt->u.dst.hh = NULL;
+ rt->u.dst.obsolete = 0;
+
+ rt->rt_flags |= RTCF_REDIRECTED;
+
+ /* Gateway is different ... */
+ rt->rt_gateway = new_gw;
+
+ /* Redirect received -> path was valid */
+ dst_confirm(&rth->u.dst);
+
+ if (rt->peer)
+ atomic_inc(&rt->peer->refcnt);
+
+ if (arp_bind_neighbour(&rt->u.dst) ||
+ !(rt->u.dst.neighbour->nud_state &
+ NUD_VALID)) {
+ if (rt->u.dst.neighbour)
+ neigh_event_send(rt->u.dst.neighbour, NULL);
+ ip_rt_put(rth);
+ rt_drop(rt);
+ goto do_next;
+ }
+
+ rt_del(hash, rth);
+ if (!rt_intern_hash(hash, rt, &rt))
+ ip_rt_put(rt);
+ goto do_next;
+ }
+ read_unlock(&rt_hash_table[hash].lock);
+ do_next:
+ ;
+ }
+ }
+ in_dev_put(in_dev);
+ return;
+
+reject_redirect:
+#ifdef CONFIG_IP_ROUTE_VERBOSE
+ if (IN_DEV_LOG_MARTIANS(in_dev) && net_ratelimit())
+ printk(KERN_INFO "Redirect from %u.%u.%u.%u on %s about "
+ "%u.%u.%u.%u ignored.\n"
+ " Advised path = %u.%u.%u.%u -> %u.%u.%u.%u, "
+ "tos %02x\n",
+ NIPQUAD(old_gw), dev->name, NIPQUAD(new_gw),
+ NIPQUAD(saddr), NIPQUAD(daddr), tos);
+#endif
+ in_dev_put(in_dev);
+}
+
+static struct dst_entry *ipv4_negative_advice(struct dst_entry *dst)
+{
+ struct rtable *rt = (struct rtable*)dst;
+ struct dst_entry *ret = dst;
+
+ if (rt) {
+ if (dst->obsolete) {
+ ip_rt_put(rt);
+ ret = NULL;
+ } else if ((rt->rt_flags & RTCF_REDIRECTED) ||
+ rt->u.dst.expires) {
+ unsigned hash = rt_hash_code(rt->key.dst,
+ rt->key.src ^
+ (rt->key.oif << 5),
+ rt->key.tos);
+#if RT_CACHE_DEBUG >= 1
+ printk(KERN_DEBUG "ip_rt_advice: redirect to "
+ "%u.%u.%u.%u/%02x dropped\n",
+ NIPQUAD(rt->rt_dst), rt->key.tos);
+#endif
+ rt_del(hash, rt);
+ ret = NULL;
+ }
+ }
+ return ret;
+}
+
+/*
+ * Algorithm:
+ * 1. The first ip_rt_redirect_number redirects are sent
+ * with exponential backoff, then we stop sending them at all,
+ * assuming that the host ignores our redirects.
+ * 2. If we did not see packets requiring redirects
+ * during ip_rt_redirect_silence, we assume that the host
+ * forgot redirected route and start to send redirects again.
+ *
+ * This algorithm is much cheaper and more intelligent than dumb load limiting
+ * in icmp.c.
+ *
+ * NOTE. Do not forget to inhibit load limiting for redirects (redundant)
+ * and "frag. need" (breaks PMTU discovery) in icmp.c.
+ */
+
+void ip_rt_send_redirect(struct sk_buff *skb)
+{
+ struct rtable *rt = (struct rtable*)skb->dst;
+ struct in_device *in_dev = in_dev_get(rt->u.dst.dev);
+
+ if (!in_dev)
+ return;
+
+ if (!IN_DEV_TX_REDIRECTS(in_dev))
+ goto out;
+
+ /* No redirected packets during ip_rt_redirect_silence;
+ * reset the algorithm.
+ */
+ if (time_after(jiffies, rt->u.dst.rate_last + ip_rt_redirect_silence))
+ rt->u.dst.rate_tokens = 0;
+
+ /* Too many ignored redirects; do not send anything
+ * set u.dst.rate_last to the last seen redirected packet.
+ */
+ if (rt->u.dst.rate_tokens >= ip_rt_redirect_number) {
+ rt->u.dst.rate_last = jiffies;
+ goto out;
+ }
+
+ /* Check for load limit; set rate_last to the latest sent
+ * redirect.
+ */
+ if (time_after(jiffies,
+ (rt->u.dst.rate_last +
+ (ip_rt_redirect_load << rt->u.dst.rate_tokens)))) {
+ icmp_send(skb, ICMP_REDIRECT, ICMP_REDIR_HOST, rt->rt_gateway);
+ rt->u.dst.rate_last = jiffies;
+ ++rt->u.dst.rate_tokens;
+#ifdef CONFIG_IP_ROUTE_VERBOSE
+ if (IN_DEV_LOG_MARTIANS(in_dev) &&
+ rt->u.dst.rate_tokens == ip_rt_redirect_number &&
+ net_ratelimit())
+ printk(KERN_WARNING "host %u.%u.%u.%u/if%d ignores "
+ "redirects for %u.%u.%u.%u to %u.%u.%u.%u.\n",
+ NIPQUAD(rt->rt_src), rt->rt_iif,
+ NIPQUAD(rt->rt_dst), NIPQUAD(rt->rt_gateway));
+#endif
+ }
+out:
+ in_dev_put(in_dev);
+}
+
+static int ip_error(struct sk_buff *skb)
+{
+ struct rtable *rt = (struct rtable*)skb->dst;
+ unsigned long now;
+ int code;
+
+ switch (rt->u.dst.error) {
+ case EINVAL:
+ default:
+ goto out;
+ case EHOSTUNREACH:
+ code = ICMP_HOST_UNREACH;
+ break;
+ case ENETUNREACH:
+ code = ICMP_NET_UNREACH;
+ break;
+ case EACCES:
+ code = ICMP_PKT_FILTERED;
+ break;
+ }
+
+ now = jiffies;
+ rt->u.dst.rate_tokens += now - rt->u.dst.rate_last;
+ if (rt->u.dst.rate_tokens > ip_rt_error_burst)
+ rt->u.dst.rate_tokens = ip_rt_error_burst;
+ rt->u.dst.rate_last = now;
+ if (rt->u.dst.rate_tokens >= ip_rt_error_cost) {
+ rt->u.dst.rate_tokens -= ip_rt_error_cost;
+ icmp_send(skb, ICMP_DEST_UNREACH, code, 0);
+ }
+
+out: kfree_skb(skb);
+ return 0;
+}
+
+/*
+ * The last two values are not from the RFC but
+ * are needed for AMPRnet AX.25 paths.
+ */
+
+static unsigned short mtu_plateau[] =
+{32000, 17914, 8166, 4352, 2002, 1492, 576, 296, 216, 128 };
+
+static __inline__ unsigned short guess_mtu(unsigned short old_mtu)
+{
+ int i;
+
+ for (i = 0; i < sizeof(mtu_plateau) / sizeof(mtu_plateau[0]); i++)
+ if (old_mtu > mtu_plateau[i])
+ return mtu_plateau[i];
+ return 68;
+}
+
+unsigned short ip_rt_frag_needed(struct iphdr *iph, unsigned short new_mtu)
+{
+ int i;
+ unsigned short old_mtu = ntohs(iph->tot_len);
+ struct rtable *rth;
+ u32 skeys[2] = { iph->saddr, 0, };
+ u32 daddr = iph->daddr;
+ u8 tos = iph->tos & IPTOS_RT_MASK;
+ unsigned short est_mtu = 0;
+
+ if (ipv4_config.no_pmtu_disc)
+ return 0;
+
+ for (i = 0; i < 2; i++) {
+ unsigned hash = rt_hash_code(daddr, skeys[i], tos);
+
+ read_lock(&rt_hash_table[hash].lock);
+ for (rth = rt_hash_table[hash].chain; rth;
+ rth = rth->u.rt_next) {
+ if (rth->key.dst == daddr &&
+ rth->key.src == skeys[i] &&
+ rth->rt_dst == daddr &&
+ rth->rt_src == iph->saddr &&
+ rth->key.tos == tos &&
+ rth->key.iif == 0 &&
+ !(rth->u.dst.mxlock & (1 << RTAX_MTU))) {
+ unsigned short mtu = new_mtu;
+
+ if (new_mtu < 68 || new_mtu >= old_mtu) {
+
+ /* BSD 4.2 compatibility hack :-( */
+ if (mtu == 0 &&
+ old_mtu >= rth->u.dst.pmtu &&
+ old_mtu >= 68 + (iph->ihl << 2))
+ old_mtu -= iph->ihl << 2;
+
+ mtu = guess_mtu(old_mtu);
+ }
+ if (mtu <= rth->u.dst.pmtu) {
+ if (mtu < rth->u.dst.pmtu) {
+ dst_confirm(&rth->u.dst);
+ if (mtu < ip_rt_min_pmtu) {
+ mtu = ip_rt_min_pmtu;
+ rth->u.dst.mxlock |=
+ (1 << RTAX_MTU);
+ }
+ rth->u.dst.pmtu = mtu;
+ dst_set_expires(&rth->u.dst,
+ ip_rt_mtu_expires);
+ }
+ est_mtu = mtu;
+ }
+ }
+ }
+ read_unlock(&rt_hash_table[hash].lock);
+ }
+ return est_mtu ? : new_mtu;
+}
+
+void ip_rt_update_pmtu(struct dst_entry *dst, unsigned mtu)
+{
+ if (dst->pmtu > mtu && mtu >= 68 &&
+ !(dst->mxlock & (1 << RTAX_MTU))) {
+ if (mtu < ip_rt_min_pmtu) {
+ mtu = ip_rt_min_pmtu;
+ dst->mxlock |= (1 << RTAX_MTU);
+ }
+ dst->pmtu = mtu;
+ dst_set_expires(dst, ip_rt_mtu_expires);
+ }
+}
+
+static struct dst_entry *ipv4_dst_check(struct dst_entry *dst, u32 cookie)
+{
+ dst_release(dst);
+ return NULL;
+}
+
+static struct dst_entry *ipv4_dst_reroute(struct dst_entry *dst,
+ struct sk_buff *skb)
+{
+ return NULL;
+}
+
+static void ipv4_dst_destroy(struct dst_entry *dst)
+{
+ struct rtable *rt = (struct rtable *) dst;
+ struct inet_peer *peer = rt->peer;
+
+ if (peer) {
+ rt->peer = NULL;
+ inet_putpeer(peer);
+ }
+}
+
+static void ipv4_link_failure(struct sk_buff *skb)
+{
+ struct rtable *rt;
+
+ icmp_send(skb, ICMP_DEST_UNREACH, ICMP_HOST_UNREACH, 0);
+
+ rt = (struct rtable *) skb->dst;
+ if (rt)
+ dst_set_expires(&rt->u.dst, 0);
+}
+
+static int ip_rt_bug(struct sk_buff *skb)
+{
+ printk(KERN_DEBUG "ip_rt_bug: %u.%u.%u.%u -> %u.%u.%u.%u, %s\n",
+ NIPQUAD(skb->nh.iph->saddr), NIPQUAD(skb->nh.iph->daddr),
+ skb->dev ? skb->dev->name : "?");
+ kfree_skb(skb);
+ return 0;
+}
+
+/*
+ We do not cache source address of outgoing interface,
+ because it is used only by IP RR, TS and SRR options,
+ so that it out of fast path.
+
+ BTW remember: "addr" is allowed to be not aligned
+ in IP options!
+ */
+
+void ip_rt_get_source(u8 *addr, struct rtable *rt)
+{
+ u32 src;
+ struct fib_result res;
+
+ if (rt->key.iif == 0)
+ src = rt->rt_src;
+ else if (fib_lookup(&rt->key, &res) == 0) {
+#ifdef CONFIG_IP_ROUTE_NAT
+ if (res.type == RTN_NAT)
+ src = inet_select_addr(rt->u.dst.dev, rt->rt_gateway,
+ RT_SCOPE_UNIVERSE);
+ else
+#endif
+ src = FIB_RES_PREFSRC(res);
+ fib_res_put(&res);
+ } else
+ src = inet_select_addr(rt->u.dst.dev, rt->rt_gateway,
+ RT_SCOPE_UNIVERSE);
+ memcpy(addr, &src, 4);
+}
+
+#ifdef CONFIG_NET_CLS_ROUTE
+static void set_class_tag(struct rtable *rt, u32 tag)
+{
+ if (!(rt->u.dst.tclassid & 0xFFFF))
+ rt->u.dst.tclassid |= tag & 0xFFFF;
+ if (!(rt->u.dst.tclassid & 0xFFFF0000))
+ rt->u.dst.tclassid |= tag & 0xFFFF0000;
+}
+#endif
+
+static void rt_set_nexthop(struct rtable *rt, struct fib_result *res, u32 itag)
+{
+ struct fib_info *fi = res->fi;
+
+ if (fi) {
+ if (FIB_RES_GW(*res) &&
+ FIB_RES_NH(*res).nh_scope == RT_SCOPE_LINK)
+ rt->rt_gateway = FIB_RES_GW(*res);
+ memcpy(&rt->u.dst.mxlock, fi->fib_metrics,
+ sizeof(fi->fib_metrics));
+ if (fi->fib_mtu == 0) {
+ rt->u.dst.pmtu = rt->u.dst.dev->mtu;
+ if (rt->u.dst.mxlock & (1 << RTAX_MTU) &&
+ rt->rt_gateway != rt->rt_dst &&
+ rt->u.dst.pmtu > 576)
+ rt->u.dst.pmtu = 576;
+ }
+#ifdef CONFIG_NET_CLS_ROUTE
+ rt->u.dst.tclassid = FIB_RES_NH(*res).nh_tclassid;
+#endif
+ } else
+ rt->u.dst.pmtu = rt->u.dst.dev->mtu;
+
+ if (rt->u.dst.pmtu > IP_MAX_MTU)
+ rt->u.dst.pmtu = IP_MAX_MTU;
+ if (rt->u.dst.advmss == 0)
+ rt->u.dst.advmss = max_t(unsigned int, rt->u.dst.dev->mtu - 40,
+ ip_rt_min_advmss);
+ if (rt->u.dst.advmss > 65535 - 40)
+ rt->u.dst.advmss = 65535 - 40;
+
+#ifdef CONFIG_NET_CLS_ROUTE
+#ifdef CONFIG_IP_MULTIPLE_TABLES
+ set_class_tag(rt, fib_rules_tclass(res));
+#endif
+ set_class_tag(rt, itag);
+#endif
+ rt->rt_type = res->type;
+}
+
+static int ip_route_input_mc(struct sk_buff *skb, u32 daddr, u32 saddr,
+ u8 tos, struct net_device *dev, int our)
+{
+ unsigned hash;
+ struct rtable *rth;
+ u32 spec_dst;
+ struct in_device *in_dev = in_dev_get(dev);
+ u32 itag = 0;
+
+ /* Primary sanity checks. */
+
+ if (in_dev == NULL)
+ return -EINVAL;
+
+ if (MULTICAST(saddr) || BADCLASS(saddr) || LOOPBACK(saddr) ||
+ skb->protocol != htons(ETH_P_IP))
+ goto e_inval;
+
+ if (ZERONET(saddr)) {
+ if (!LOCAL_MCAST(daddr))
+ goto e_inval;
+ spec_dst = inet_select_addr(dev, 0, RT_SCOPE_LINK);
+ } else if (fib_validate_source(saddr, 0, tos, 0,
+ dev, &spec_dst, &itag) < 0)
+ goto e_inval;
+
+ rth = dst_alloc(&ipv4_dst_ops);
+ if (!rth)
+ goto e_nobufs;
+
+ rth->u.dst.output= ip_rt_bug;
+
+ atomic_set(&rth->u.dst.__refcnt, 1);
+ rth->u.dst.flags= DST_HOST;
+ rth->key.dst = daddr;
+ rth->rt_dst = daddr;
+ rth->key.tos = tos;
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ rth->key.fwmark = skb->nfmark;
+#endif
+ rth->key.src = saddr;
+ rth->rt_src = saddr;
+#ifdef CONFIG_IP_ROUTE_NAT
+ rth->rt_dst_map = daddr;
+ rth->rt_src_map = saddr;
+#endif
+#ifdef CONFIG_NET_CLS_ROUTE
+ rth->u.dst.tclassid = itag;
+#endif
+ rth->rt_iif =
+ rth->key.iif = dev->ifindex;
+ rth->u.dst.dev = &loopback_dev;
+ dev_hold(rth->u.dst.dev);
+ rth->key.oif = 0;
+ rth->rt_gateway = daddr;
+ rth->rt_spec_dst= spec_dst;
+ rth->rt_type = RTN_MULTICAST;
+ rth->rt_flags = RTCF_MULTICAST;
+ if (our) {
+ rth->u.dst.input= ip_local_deliver;
+ rth->rt_flags |= RTCF_LOCAL;
+ }
+
+#ifdef CONFIG_IP_MROUTE
+ if (!LOCAL_MCAST(daddr) && IN_DEV_MFORWARD(in_dev))
+ rth->u.dst.input = ip_mr_input;
+#endif
+ rt_cache_stat[smp_processor_id()].in_slow_mc++;
+
+ in_dev_put(in_dev);
+ hash = rt_hash_code(daddr, saddr ^ (dev->ifindex << 5), tos);
+ return rt_intern_hash(hash, rth, (struct rtable**) &skb->dst);
+
+e_nobufs:
+ in_dev_put(in_dev);
+ return -ENOBUFS;
+
+e_inval:
+ in_dev_put(in_dev);
+ return -EINVAL;
+}
+
+/*
+ * NOTE. We drop all the packets that has local source
+ * addresses, because every properly looped back packet
+ * must have correct destination already attached by output routine.
+ *
+ * Such approach solves two big problems:
+ * 1. Not simplex devices are handled properly.
+ * 2. IP spoofing attempts are filtered with 100% of guarantee.
+ */
+
+int ip_route_input_slow(struct sk_buff *skb, u32 daddr, u32 saddr,
+ u8 tos, struct net_device *dev)
+{
+ struct rt_key key;
+ struct fib_result res;
+ struct in_device *in_dev = in_dev_get(dev);
+ struct in_device *out_dev = NULL;
+ unsigned flags = 0;
+ u32 itag = 0;
+ struct rtable * rth;
+ unsigned hash;
+ u32 spec_dst;
+ int err = -EINVAL;
+ int free_res = 0;
+
+ /* IP on this device is disabled. */
+
+ if (!in_dev)
+ goto out;
+
+ key.dst = daddr;
+ key.src = saddr;
+ key.tos = tos;
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ key.fwmark = skb->nfmark;
+#endif
+ key.iif = dev->ifindex;
+ key.oif = 0;
+ key.scope = RT_SCOPE_UNIVERSE;
+
+ hash = rt_hash_code(daddr, saddr ^ (key.iif << 5), tos);
+
+ /* Check for the most weird martians, which can be not detected
+ by fib_lookup.
+ */
+
+ if (MULTICAST(saddr) || BADCLASS(saddr) || LOOPBACK(saddr))
+ goto martian_source;
+
+ if (daddr == 0xFFFFFFFF || (saddr == 0 && daddr == 0))
+ goto brd_input;
+
+ /* Accept zero addresses only to limited broadcast;
+ * I even do not know to fix it or not. Waiting for complains :-)
+ */
+ if (ZERONET(saddr))
+ goto martian_source;
+
+ if (BADCLASS(daddr) || ZERONET(daddr) || LOOPBACK(daddr))
+ goto martian_destination;
+
+ /*
+ * Now we are ready to route packet.
+ */
+ if ((err = fib_lookup(&key, &res)) != 0) {
+ if (!IN_DEV_FORWARD(in_dev))
+ goto e_inval;
+ goto no_route;
+ }
+ free_res = 1;
+
+ rt_cache_stat[smp_processor_id()].in_slow_tot++;
+
+#ifdef CONFIG_IP_ROUTE_NAT
+ /* Policy is applied before mapping destination,
+ but rerouting after map should be made with old source.
+ */
+
+ if (1) {
+ u32 src_map = saddr;
+ if (res.r)
+ src_map = fib_rules_policy(saddr, &res, &flags);
+
+ if (res.type == RTN_NAT) {
+ key.dst = fib_rules_map_destination(daddr, &res);
+ fib_res_put(&res);
+ free_res = 0;
+ if (fib_lookup(&key, &res))
+ goto e_inval;
+ free_res = 1;
+ if (res.type != RTN_UNICAST)
+ goto e_inval;
+ flags |= RTCF_DNAT;
+ }
+ key.src = src_map;
+ }
+#endif
+
+ if (res.type == RTN_BROADCAST)
+ goto brd_input;
+
+ if (res.type == RTN_LOCAL) {
+ int result;
+ result = fib_validate_source(saddr, daddr, tos,
+ loopback_dev.ifindex,
+ dev, &spec_dst, &itag);
+ if (result < 0)
+ goto martian_source;
+ if (result)
+ flags |= RTCF_DIRECTSRC;
+ spec_dst = daddr;
+ goto local_input;
+ }
+
+ if (!IN_DEV_FORWARD(in_dev))
+ goto e_inval;
+ if (res.type != RTN_UNICAST)
+ goto martian_destination;
+
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ if (res.fi->fib_nhs > 1 && key.oif == 0)
+ fib_select_multipath(&key, &res);
+#endif
+ out_dev = in_dev_get(FIB_RES_DEV(res));
+ if (out_dev == NULL) {
+ if (net_ratelimit())
+ printk(KERN_CRIT "Bug in ip_route_input_slow(). "
+ "Please, report\n");
+ goto e_inval;
+ }
+
+ err = fib_validate_source(saddr, daddr, tos, FIB_RES_OIF(res), dev,
+ &spec_dst, &itag);
+ if (err < 0)
+ goto martian_source;
+
+ if (err)
+ flags |= RTCF_DIRECTSRC;
+
+ if (out_dev == in_dev && err && !(flags & (RTCF_NAT | RTCF_MASQ)) &&
+ (IN_DEV_SHARED_MEDIA(out_dev) ||
+ inet_addr_onlink(out_dev, saddr, FIB_RES_GW(res))))
+ flags |= RTCF_DOREDIRECT;
+
+ if (skb->protocol != htons(ETH_P_IP)) {
+ /* Not IP (i.e. ARP). Do not create route, if it is
+ * invalid for proxy arp. DNAT routes are always valid.
+ */
+ if (out_dev == in_dev && !(flags & RTCF_DNAT))
+ goto e_inval;
+ }
+
+ rth = dst_alloc(&ipv4_dst_ops);
+ if (!rth)
+ goto e_nobufs;
+
+ atomic_set(&rth->u.dst.__refcnt, 1);
+ rth->u.dst.flags= DST_HOST;
+ rth->key.dst = daddr;
+ rth->rt_dst = daddr;
+ rth->key.tos = tos;
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ rth->key.fwmark = skb->nfmark;
+#endif
+ rth->key.src = saddr;
+ rth->rt_src = saddr;
+ rth->rt_gateway = daddr;
+#ifdef CONFIG_IP_ROUTE_NAT
+ rth->rt_src_map = key.src;
+ rth->rt_dst_map = key.dst;
+ if (flags&RTCF_DNAT)
+ rth->rt_gateway = key.dst;
+#endif
+ rth->rt_iif =
+ rth->key.iif = dev->ifindex;
+ rth->u.dst.dev = out_dev->dev;
+ dev_hold(rth->u.dst.dev);
+ rth->key.oif = 0;
+ rth->rt_spec_dst= spec_dst;
+
+ rth->u.dst.input = ip_forward;
+ rth->u.dst.output = ip_output;
+
+ rt_set_nexthop(rth, &res, itag);
+
+ rth->rt_flags = flags;
+
+#ifdef CONFIG_NET_FASTROUTE
+ if (netdev_fastroute && !(flags&(RTCF_NAT|RTCF_MASQ|RTCF_DOREDIRECT))) {
+ struct net_device *odev = rth->u.dst.dev;
+ if (odev != dev &&
+ dev->accept_fastpath &&
+ odev->mtu >= dev->mtu &&
+ dev->accept_fastpath(dev, &rth->u.dst) == 0)
+ rth->rt_flags |= RTCF_FAST;
+ }
+#endif
+
+intern:
+ err = rt_intern_hash(hash, rth, (struct rtable**)&skb->dst);
+done:
+ in_dev_put(in_dev);
+ if (out_dev)
+ in_dev_put(out_dev);
+ if (free_res)
+ fib_res_put(&res);
+out: return err;
+
+brd_input:
+ if (skb->protocol != htons(ETH_P_IP))
+ goto e_inval;
+
+ if (ZERONET(saddr))
+ spec_dst = inet_select_addr(dev, 0, RT_SCOPE_LINK);
+ else {
+ err = fib_validate_source(saddr, 0, tos, 0, dev, &spec_dst,
+ &itag);
+ if (err < 0)
+ goto martian_source;
+ if (err)
+ flags |= RTCF_DIRECTSRC;
+ }
+ flags |= RTCF_BROADCAST;
+ res.type = RTN_BROADCAST;
+ rt_cache_stat[smp_processor_id()].in_brd++;
+
+local_input:
+ rth = dst_alloc(&ipv4_dst_ops);
+ if (!rth)
+ goto e_nobufs;
+
+ rth->u.dst.output= ip_rt_bug;
+
+ atomic_set(&rth->u.dst.__refcnt, 1);
+ rth->u.dst.flags= DST_HOST;
+ rth->key.dst = daddr;
+ rth->rt_dst = daddr;
+ rth->key.tos = tos;
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ rth->key.fwmark = skb->nfmark;
+#endif
+ rth->key.src = saddr;
+ rth->rt_src = saddr;
+#ifdef CONFIG_IP_ROUTE_NAT
+ rth->rt_dst_map = key.dst;
+ rth->rt_src_map = key.src;
+#endif
+#ifdef CONFIG_NET_CLS_ROUTE
+ rth->u.dst.tclassid = itag;
+#endif
+ rth->rt_iif =
+ rth->key.iif = dev->ifindex;
+ rth->u.dst.dev = &loopback_dev;
+ dev_hold(rth->u.dst.dev);
+ rth->key.oif = 0;
+ rth->rt_gateway = daddr;
+ rth->rt_spec_dst= spec_dst;
+ rth->u.dst.input= ip_local_deliver;
+ rth->rt_flags = flags|RTCF_LOCAL;
+ if (res.type == RTN_UNREACHABLE) {
+ rth->u.dst.input= ip_error;
+ rth->u.dst.error= -err;
+ rth->rt_flags &= ~RTCF_LOCAL;
+ }
+ rth->rt_type = res.type;
+ goto intern;
+
+no_route:
+ rt_cache_stat[smp_processor_id()].in_no_route++;
+ spec_dst = inet_select_addr(dev, 0, RT_SCOPE_UNIVERSE);
+ res.type = RTN_UNREACHABLE;
+ goto local_input;
+
+ /*
+ * Do not cache martian addresses: they should be logged (RFC1812)
+ */
+martian_destination:
+ rt_cache_stat[smp_processor_id()].in_martian_dst++;
+#ifdef CONFIG_IP_ROUTE_VERBOSE
+ if (IN_DEV_LOG_MARTIANS(in_dev) && net_ratelimit())
+ printk(KERN_WARNING "martian destination %u.%u.%u.%u from "
+ "%u.%u.%u.%u, dev %s\n",
+ NIPQUAD(daddr), NIPQUAD(saddr), dev->name);
+#endif
+e_inval:
+ err = -EINVAL;
+ goto done;
+
+e_nobufs:
+ err = -ENOBUFS;
+ goto done;
+
+martian_source:
+
+ rt_cache_stat[smp_processor_id()].in_martian_src++;
+#ifdef CONFIG_IP_ROUTE_VERBOSE
+ if (IN_DEV_LOG_MARTIANS(in_dev) && net_ratelimit()) {
+ /*
+ * RFC1812 recommendation, if source is martian,
+ * the only hint is MAC header.
+ */
+ printk(KERN_WARNING "martian source %u.%u.%u.%u from "
+ "%u.%u.%u.%u, on dev %s\n",
+ NIPQUAD(daddr), NIPQUAD(saddr), dev->name);
+ if (dev->hard_header_len) {
+ int i;
+ unsigned char *p = skb->mac.raw;
+ printk(KERN_WARNING "ll header: ");
+ for (i = 0; i < dev->hard_header_len; i++, p++) {
+ printk("%02x", *p);
+ if (i < (dev->hard_header_len - 1))
+ printk(":");
+ }
+ printk("\n");
+ }
+ }
+#endif
+ goto e_inval;
+}
+
+int ip_route_input(struct sk_buff *skb, u32 daddr, u32 saddr,
+ u8 tos, struct net_device *dev)
+{
+ struct rtable * rth;
+ unsigned hash;
+ int iif = dev->ifindex;
+
+ tos &= IPTOS_RT_MASK;
+ hash = rt_hash_code(daddr, saddr ^ (iif << 5), tos);
+
+ read_lock(&rt_hash_table[hash].lock);
+ for (rth = rt_hash_table[hash].chain; rth; rth = rth->u.rt_next) {
+ if (rth->key.dst == daddr &&
+ rth->key.src == saddr &&
+ rth->key.iif == iif &&
+ rth->key.oif == 0 &&
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ rth->key.fwmark == skb->nfmark &&
+#endif
+ rth->key.tos == tos) {
+ rth->u.dst.lastuse = jiffies;
+ dst_hold(&rth->u.dst);
+ rth->u.dst.__use++;
+ rt_cache_stat[smp_processor_id()].in_hit++;
+ read_unlock(&rt_hash_table[hash].lock);
+ skb->dst = (struct dst_entry*)rth;
+ return 0;
+ }
+ rt_cache_stat[smp_processor_id()].in_hlist_search++;
+ }
+ read_unlock(&rt_hash_table[hash].lock);
+
+ /* Multicast recognition logic is moved from route cache to here.
+ The problem was that too many Ethernet cards have broken/missing
+ hardware multicast filters :-( As result the host on multicasting
+ network acquires a lot of useless route cache entries, sort of
+ SDR messages from all the world. Now we try to get rid of them.
+ Really, provided software IP multicast filter is organized
+ reasonably (at least, hashed), it does not result in a slowdown
+ comparing with route cache reject entries.
+ Note, that multicast routers are not affected, because
+ route cache entry is created eventually.
+ */
+ if (MULTICAST(daddr)) {
+ struct in_device *in_dev;
+
+ read_lock(&inetdev_lock);
+ if ((in_dev = __in_dev_get(dev)) != NULL) {
+ int our = ip_check_mc(in_dev, daddr, saddr);
+ if (our
+#ifdef CONFIG_IP_MROUTE
+ || (!LOCAL_MCAST(daddr) && IN_DEV_MFORWARD(in_dev))
+#endif
+ ) {
+ read_unlock(&inetdev_lock);
+ return ip_route_input_mc(skb, daddr, saddr,
+ tos, dev, our);
+ }
+ }
+ read_unlock(&inetdev_lock);
+ return -EINVAL;
+ }
+ return ip_route_input_slow(skb, daddr, saddr, tos, dev);
+}
+
+/*
+ * Major route resolver routine.
+ */
+
+int ip_route_output_slow(struct rtable **rp, const struct rt_key *oldkey)
+{
+ struct rt_key key;
+ struct fib_result res;
+ unsigned flags = 0;
+ struct rtable *rth;
+ struct net_device *dev_out = NULL;
+ unsigned hash;
+ int free_res = 0;
+ int err;
+ u32 tos;
+
+ tos = oldkey->tos & (IPTOS_RT_MASK | RTO_ONLINK);
+ key.dst = oldkey->dst;
+ key.src = oldkey->src;
+ key.tos = tos & IPTOS_RT_MASK;
+ key.iif = loopback_dev.ifindex;
+ key.oif = oldkey->oif;
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ key.fwmark = oldkey->fwmark;
+#endif
+ key.scope = (tos & RTO_ONLINK) ? RT_SCOPE_LINK :
+ RT_SCOPE_UNIVERSE;
+ res.fi = NULL;
+#ifdef CONFIG_IP_MULTIPLE_TABLES
+ res.r = NULL;
+#endif
+
+ if (oldkey->src) {
+ err = -EINVAL;
+ if (MULTICAST(oldkey->src) ||
+ BADCLASS(oldkey->src) ||
+ ZERONET(oldkey->src))
+ goto out;
+
+ /* It is equivalent to inet_addr_type(saddr) == RTN_LOCAL */
+ dev_out = ip_dev_find(oldkey->src);
+ if (dev_out == NULL)
+ goto out;
+
+ /* I removed check for oif == dev_out->oif here.
+ It was wrong by three reasons:
+ 1. ip_dev_find(saddr) can return wrong iface, if saddr is
+ assigned to multiple interfaces.
+ 2. Moreover, we are allowed to send packets with saddr
+ of another iface. --ANK
+ */
+
+ if (oldkey->oif == 0
+ && (MULTICAST(oldkey->dst) || oldkey->dst == 0xFFFFFFFF)) {
+ /* Special hack: user can direct multicasts
+ and limited broadcast via necessary interface
+ without fiddling with IP_MULTICAST_IF or IP_PKTINFO.
+ This hack is not just for fun, it allows
+ vic,vat and friends to work.
+ They bind socket to loopback, set ttl to zero
+ and expect that it will work.
+ From the viewpoint of routing cache they are broken,
+ because we are not allowed to build multicast path
+ with loopback source addr (look, routing cache
+ cannot know, that ttl is zero, so that packet
+ will not leave this host and route is valid).
+ Luckily, this hack is good workaround.
+ */
+
+ key.oif = dev_out->ifindex;
+ goto make_route;
+ }
+ if (dev_out)
+ dev_put(dev_out);
+ dev_out = NULL;
+ }
+ if (oldkey->oif) {
+ dev_out = dev_get_by_index(oldkey->oif);
+ err = -ENODEV;
+ if (dev_out == NULL)
+ goto out;
+ if (__in_dev_get(dev_out) == NULL) {
+ dev_put(dev_out);
+ goto out; /* Wrong error code */
+ }
+
+ if (LOCAL_MCAST(oldkey->dst) || oldkey->dst == 0xFFFFFFFF) {
+ if (!key.src)
+ key.src = inet_select_addr(dev_out, 0,
+ RT_SCOPE_LINK);
+ goto make_route;
+ }
+ if (!key.src) {
+ if (MULTICAST(oldkey->dst))
+ key.src = inet_select_addr(dev_out, 0,
+ key.scope);
+ else if (!oldkey->dst)
+ key.src = inet_select_addr(dev_out, 0,
+ RT_SCOPE_HOST);
+ }
+ }
+
+ if (!key.dst) {
+ key.dst = key.src;
+ if (!key.dst)
+ key.dst = key.src = htonl(INADDR_LOOPBACK);
+ if (dev_out)
+ dev_put(dev_out);
+ dev_out = &loopback_dev;
+ dev_hold(dev_out);
+ key.oif = loopback_dev.ifindex;
+ res.type = RTN_LOCAL;
+ flags |= RTCF_LOCAL;
+ goto make_route;
+ }
+
+ if (fib_lookup(&key, &res)) {
+ res.fi = NULL;
+ if (oldkey->oif) {
+ /* Apparently, routing tables are wrong. Assume,
+ that the destination is on link.
+
+ WHY? DW.
+ Because we are allowed to send to iface
+ even if it has NO routes and NO assigned
+ addresses. When oif is specified, routing
+ tables are looked up with only one purpose:
+ to catch if destination is gatewayed, rather than
+ direct. Moreover, if MSG_DONTROUTE is set,
+ we send packet, ignoring both routing tables
+ and ifaddr state. --ANK
+
+
+ We could make it even if oif is unknown,
+ likely IPv6, but we do not.
+ */
+
+ if (key.src == 0)
+ key.src = inet_select_addr(dev_out, 0,
+ RT_SCOPE_LINK);
+ res.type = RTN_UNICAST;
+ goto make_route;
+ }
+ if (dev_out)
+ dev_put(dev_out);
+ err = -ENETUNREACH;
+ goto out;
+ }
+ free_res = 1;
+
+ if (res.type == RTN_NAT)
+ goto e_inval;
+
+ if (res.type == RTN_LOCAL) {
+ if (!key.src)
+ key.src = key.dst;
+ if (dev_out)
+ dev_put(dev_out);
+ dev_out = &loopback_dev;
+ dev_hold(dev_out);
+ key.oif = dev_out->ifindex;
+ if (res.fi)
+ fib_info_put(res.fi);
+ res.fi = NULL;
+ flags |= RTCF_LOCAL;
+ goto make_route;
+ }
+
+#ifdef CONFIG_IP_ROUTE_MULTIPATH
+ if (res.fi->fib_nhs > 1 && key.oif == 0)
+ fib_select_multipath(&key, &res);
+ else
+#endif
+ if (!res.prefixlen && res.type == RTN_UNICAST && !key.oif)
+ fib_select_default(&key, &res);
+
+ if (!key.src)
+ key.src = FIB_RES_PREFSRC(res);
+
+ if (dev_out)
+ dev_put(dev_out);
+ dev_out = FIB_RES_DEV(res);
+ dev_hold(dev_out);
+ key.oif = dev_out->ifindex;
+
+make_route:
+ if (LOOPBACK(key.src) && !(dev_out->flags&IFF_LOOPBACK))
+ goto e_inval;
+
+ if (key.dst == 0xFFFFFFFF)
+ res.type = RTN_BROADCAST;
+ else if (MULTICAST(key.dst))
+ res.type = RTN_MULTICAST;
+ else if (BADCLASS(key.dst) || ZERONET(key.dst))
+ goto e_inval;
+
+ if (dev_out->flags & IFF_LOOPBACK)
+ flags |= RTCF_LOCAL;
+
+ if (res.type == RTN_BROADCAST) {
+ flags |= RTCF_BROADCAST | RTCF_LOCAL;
+ if (res.fi) {
+ fib_info_put(res.fi);
+ res.fi = NULL;
+ }
+ } else if (res.type == RTN_MULTICAST) {
+ flags |= RTCF_MULTICAST|RTCF_LOCAL;
+ read_lock(&inetdev_lock);
+ if (!__in_dev_get(dev_out) ||
+ !ip_check_mc(__in_dev_get(dev_out),oldkey->dst,oldkey->src))
+ flags &= ~RTCF_LOCAL;
+ read_unlock(&inetdev_lock);
+ /* If multicast route do not exist use
+ default one, but do not gateway in this case.
+ Yes, it is hack.
+ */
+ if (res.fi && res.prefixlen < 4) {
+ fib_info_put(res.fi);
+ res.fi = NULL;
+ }
+ }
+
+ rth = dst_alloc(&ipv4_dst_ops);
+ if (!rth)
+ goto e_nobufs;
+
+ atomic_set(&rth->u.dst.__refcnt, 1);
+ rth->u.dst.flags= DST_HOST;
+ rth->key.dst = oldkey->dst;
+ rth->key.tos = tos;
+ rth->key.src = oldkey->src;
+ rth->key.iif = 0;
+ rth->key.oif = oldkey->oif;
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ rth->key.fwmark = oldkey->fwmark;
+#endif
+ rth->rt_dst = key.dst;
+ rth->rt_src = key.src;
+#ifdef CONFIG_IP_ROUTE_NAT
+ rth->rt_dst_map = key.dst;
+ rth->rt_src_map = key.src;
+#endif
+ rth->rt_iif = oldkey->oif ? : dev_out->ifindex;
+ rth->u.dst.dev = dev_out;
+ dev_hold(dev_out);
+ rth->rt_gateway = key.dst;
+ rth->rt_spec_dst= key.src;
+
+ rth->u.dst.output=ip_output;
+
+ rt_cache_stat[smp_processor_id()].out_slow_tot++;
+
+ if (flags & RTCF_LOCAL) {
+ rth->u.dst.input = ip_local_deliver;
+ rth->rt_spec_dst = key.dst;
+ }
+ if (flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {
+ rth->rt_spec_dst = key.src;
+ if (flags & RTCF_LOCAL && !(dev_out->flags & IFF_LOOPBACK)) {
+ rth->u.dst.output = ip_mc_output;
+ rt_cache_stat[smp_processor_id()].out_slow_mc++;
+ }
+#ifdef CONFIG_IP_MROUTE
+ if (res.type == RTN_MULTICAST) {
+ struct in_device *in_dev = in_dev_get(dev_out);
+ if (in_dev) {
+ if (IN_DEV_MFORWARD(in_dev) &&
+ !LOCAL_MCAST(oldkey->dst)) {
+ rth->u.dst.input = ip_mr_input;
+ rth->u.dst.output = ip_mc_output;
+ }
+ in_dev_put(in_dev);
+ }
+ }
+#endif
+ }
+
+ rt_set_nexthop(rth, &res, 0);
+
+ rth->rt_flags = flags;
+
+ hash = rt_hash_code(oldkey->dst, oldkey->src ^ (oldkey->oif << 5), tos);
+ err = rt_intern_hash(hash, rth, rp);
+done:
+ if (free_res)
+ fib_res_put(&res);
+ if (dev_out)
+ dev_put(dev_out);
+out: return err;
+
+e_inval:
+ err = -EINVAL;
+ goto done;
+e_nobufs:
+ err = -ENOBUFS;
+ goto done;
+}
+
+int ip_route_output_key(struct rtable **rp, const struct rt_key *key)
+{
+ unsigned hash;
+ struct rtable *rth;
+
+ hash = rt_hash_code(key->dst, key->src ^ (key->oif << 5), key->tos);
+
+ read_lock_bh(&rt_hash_table[hash].lock);
+ for (rth = rt_hash_table[hash].chain; rth; rth = rth->u.rt_next) {
+ if (rth->key.dst == key->dst &&
+ rth->key.src == key->src &&
+ rth->key.iif == 0 &&
+ rth->key.oif == key->oif &&
+#ifdef CONFIG_IP_ROUTE_FWMARK
+ rth->key.fwmark == key->fwmark &&
+#endif
+ !((rth->key.tos ^ key->tos) &
+ (IPTOS_RT_MASK | RTO_ONLINK))) {
+ rth->u.dst.lastuse = jiffies;
+ dst_hold(&rth->u.dst);
+ rth->u.dst.__use++;
+ rt_cache_stat[smp_processor_id()].out_hit++;
+ read_unlock_bh(&rt_hash_table[hash].lock);
+ *rp = rth;
+ return 0;
+ }
+ rt_cache_stat[smp_processor_id()].out_hlist_search++;
+ }
+ read_unlock_bh(&rt_hash_table[hash].lock);
+
+ return ip_route_output_slow(rp, key);
+}
+
+static int rt_fill_info(struct sk_buff *skb, u32 pid, u32 seq, int event,
+ int nowait)
+{
+ struct rtable *rt = (struct rtable*)skb->dst;
+ struct rtmsg *r;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+ struct rta_cacheinfo ci;
+#ifdef CONFIG_IP_MROUTE
+ struct rtattr *eptr;
+#endif
+ nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*r));
+ r = NLMSG_DATA(nlh);
+ nlh->nlmsg_flags = (nowait && pid) ? NLM_F_MULTI : 0;
+ r->rtm_family = AF_INET;
+ r->rtm_dst_len = 32;
+ r->rtm_src_len = 0;
+ r->rtm_tos = rt->key.tos;
+ r->rtm_table = RT_TABLE_MAIN;
+ r->rtm_type = rt->rt_type;
+ r->rtm_scope = RT_SCOPE_UNIVERSE;
+ r->rtm_protocol = RTPROT_UNSPEC;
+ r->rtm_flags = (rt->rt_flags & ~0xFFFF) | RTM_F_CLONED;
+ if (rt->rt_flags & RTCF_NOTIFY)
+ r->rtm_flags |= RTM_F_NOTIFY;
+ RTA_PUT(skb, RTA_DST, 4, &rt->rt_dst);
+ if (rt->key.src) {
+ r->rtm_src_len = 32;
+ RTA_PUT(skb, RTA_SRC, 4, &rt->key.src);
+ }
+ if (rt->u.dst.dev)
+ RTA_PUT(skb, RTA_OIF, sizeof(int), &rt->u.dst.dev->ifindex);
+#ifdef CONFIG_NET_CLS_ROUTE
+ if (rt->u.dst.tclassid)
+ RTA_PUT(skb, RTA_FLOW, 4, &rt->u.dst.tclassid);
+#endif
+ if (rt->key.iif)
+ RTA_PUT(skb, RTA_PREFSRC, 4, &rt->rt_spec_dst);
+ else if (rt->rt_src != rt->key.src)
+ RTA_PUT(skb, RTA_PREFSRC, 4, &rt->rt_src);
+ if (rt->rt_dst != rt->rt_gateway)
+ RTA_PUT(skb, RTA_GATEWAY, 4, &rt->rt_gateway);
+ if (rtnetlink_put_metrics(skb, &rt->u.dst.mxlock) < 0)
+ goto rtattr_failure;
+ ci.rta_lastuse = jiffies - rt->u.dst.lastuse;
+ ci.rta_used = rt->u.dst.__use;
+ ci.rta_clntref = atomic_read(&rt->u.dst.__refcnt);
+ if (rt->u.dst.expires)
+ ci.rta_expires = rt->u.dst.expires - jiffies;
+ else
+ ci.rta_expires = 0;
+ ci.rta_error = rt->u.dst.error;
+ ci.rta_id = ci.rta_ts = ci.rta_tsage = 0;
+ if (rt->peer) {
+ ci.rta_id = rt->peer->ip_id_count;
+ if (rt->peer->tcp_ts_stamp) {
+ ci.rta_ts = rt->peer->tcp_ts;
+ ci.rta_tsage = xtime.tv_sec - rt->peer->tcp_ts_stamp;
+ }
+ }
+#ifdef CONFIG_IP_MROUTE
+ eptr = (struct rtattr*)skb->tail;
+#endif
+ RTA_PUT(skb, RTA_CACHEINFO, sizeof(ci), &ci);
+ if (rt->key.iif) {
+#ifdef CONFIG_IP_MROUTE
+ u32 dst = rt->rt_dst;
+
+ if (MULTICAST(dst) && !LOCAL_MCAST(dst) &&
+ ipv4_devconf.mc_forwarding) {
+ int err = ipmr_get_route(skb, r, nowait);
+ if (err <= 0) {
+ if (!nowait) {
+ if (err == 0)
+ return 0;
+ goto nlmsg_failure;
+ } else {
+ if (err == -EMSGSIZE)
+ goto nlmsg_failure;
+ ((struct rta_cacheinfo*)RTA_DATA(eptr))->rta_error = err;
+ }
+ }
+ } else
+#endif
+ RTA_PUT(skb, RTA_IIF, sizeof(int), &rt->key.iif);
+ }
+
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+int inet_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr* nlh, void *arg)
+{
+ struct rtattr **rta = arg;
+ struct rtmsg *rtm = NLMSG_DATA(nlh);
+ struct rtable *rt = NULL;
+ u32 dst = 0;
+ u32 src = 0;
+ int iif = 0;
+ int err = -ENOBUFS;
+ struct sk_buff *skb;
+
+ skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!skb)
+ goto out;
+
+ /* Reserve room for dummy headers, this skb can pass
+ through good chunk of routing engine.
+ */
+ skb->mac.raw = skb->data;
+ skb_reserve(skb, MAX_HEADER + sizeof(struct iphdr));
+
+ if (rta[RTA_SRC - 1])
+ memcpy(&src, RTA_DATA(rta[RTA_SRC - 1]), 4);
+ if (rta[RTA_DST - 1])
+ memcpy(&dst, RTA_DATA(rta[RTA_DST - 1]), 4);
+ if (rta[RTA_IIF - 1])
+ memcpy(&iif, RTA_DATA(rta[RTA_IIF - 1]), sizeof(int));
+
+ if (iif) {
+ struct net_device *dev = __dev_get_by_index(iif);
+ err = -ENODEV;
+ if (!dev)
+ goto out_free;
+ skb->protocol = htons(ETH_P_IP);
+ skb->dev = dev;
+ local_bh_disable();
+ err = ip_route_input(skb, dst, src, rtm->rtm_tos, dev);
+ local_bh_enable();
+ rt = (struct rtable*)skb->dst;
+ if (!err && rt->u.dst.error)
+ err = -rt->u.dst.error;
+ } else {
+ int oif = 0;
+ if (rta[RTA_OIF - 1])
+ memcpy(&oif, RTA_DATA(rta[RTA_OIF - 1]), sizeof(int));
+ err = ip_route_output(&rt, dst, src, rtm->rtm_tos, oif);
+ }
+ if (err)
+ goto out_free;
+
+ skb->dst = &rt->u.dst;
+ if (rtm->rtm_flags & RTM_F_NOTIFY)
+ rt->rt_flags |= RTCF_NOTIFY;
+
+ NETLINK_CB(skb).dst_pid = NETLINK_CB(in_skb).pid;
+
+ err = rt_fill_info(skb, NETLINK_CB(in_skb).pid, nlh->nlmsg_seq,
+ RTM_NEWROUTE, 0);
+ if (!err)
+ goto out_free;
+ if (err < 0) {
+ err = -EMSGSIZE;
+ goto out_free;
+ }
+
+ err = netlink_unicast(rtnl, skb, NETLINK_CB(in_skb).pid, MSG_DONTWAIT);
+ if (err > 0)
+ err = 0;
+out: return err;
+
+out_free:
+ kfree_skb(skb);
+ goto out;
+}
+
+int ip_rt_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ struct rtable *rt;
+ int h, s_h;
+ int idx, s_idx;
+
+ s_h = cb->args[0];
+ s_idx = idx = cb->args[1];
+ for (h = 0; h <= rt_hash_mask; h++) {
+ if (h < s_h) continue;
+ if (h > s_h)
+ s_idx = 0;
+ read_lock_bh(&rt_hash_table[h].lock);
+ for (rt = rt_hash_table[h].chain, idx = 0; rt;
+ rt = rt->u.rt_next, idx++) {
+ if (idx < s_idx)
+ continue;
+ skb->dst = dst_clone(&rt->u.dst);
+ if (rt_fill_info(skb, NETLINK_CB(cb->skb).pid,
+ cb->nlh->nlmsg_seq,
+ RTM_NEWROUTE, 1) <= 0) {
+ dst_release(xchg(&skb->dst, NULL));
+ read_unlock_bh(&rt_hash_table[h].lock);
+ goto done;
+ }
+ dst_release(xchg(&skb->dst, NULL));
+ }
+ read_unlock_bh(&rt_hash_table[h].lock);
+ }
+
+done:
+ cb->args[0] = h;
+ cb->args[1] = idx;
+ return skb->len;
+}
+
+void ip_rt_multicast_event(struct in_device *in_dev)
+{
+ rt_cache_flush(0);
+}
+
+#ifdef CONFIG_SYSCTL
+static int flush_delay;
+
+static int ipv4_sysctl_rtcache_flush(ctl_table *ctl, int write,
+ struct file *filp, void *buffer,
+ size_t *lenp)
+{
+ if (write) {
+ proc_dointvec(ctl, write, filp, buffer, lenp);
+ rt_cache_flush(flush_delay);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int ipv4_sysctl_rtcache_flush_strategy(ctl_table *table, int *name,
+ int nlen, void *oldval,
+ size_t *oldlenp, void *newval,
+ size_t newlen, void **context)
+{
+ int delay;
+ if (newlen != sizeof(int))
+ return -EINVAL;
+ if (get_user(delay, (int *)newval))
+ return -EFAULT;
+ rt_cache_flush(delay);
+ return 0;
+}
+
+ctl_table ipv4_route_table[] = {
+ {
+ ctl_name: NET_IPV4_ROUTE_FLUSH,
+ procname: "flush",
+ data: &flush_delay,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &ipv4_sysctl_rtcache_flush,
+ strategy: &ipv4_sysctl_rtcache_flush_strategy,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_MIN_DELAY,
+ procname: "min_delay",
+ data: &ip_rt_min_delay,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec_jiffies,
+ strategy: &sysctl_jiffies,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_MAX_DELAY,
+ procname: "max_delay",
+ data: &ip_rt_max_delay,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec_jiffies,
+ strategy: &sysctl_jiffies,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_GC_THRESH,
+ procname: "gc_thresh",
+ data: &ipv4_dst_ops.gc_thresh,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_MAX_SIZE,
+ procname: "max_size",
+ data: &ip_rt_max_size,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_GC_MIN_INTERVAL,
+ procname: "gc_min_interval",
+ data: &ip_rt_gc_min_interval,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec_jiffies,
+ strategy: &sysctl_jiffies,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_GC_TIMEOUT,
+ procname: "gc_timeout",
+ data: &ip_rt_gc_timeout,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec_jiffies,
+ strategy: &sysctl_jiffies,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_GC_INTERVAL,
+ procname: "gc_interval",
+ data: &ip_rt_gc_interval,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec_jiffies,
+ strategy: &sysctl_jiffies,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_REDIRECT_LOAD,
+ procname: "redirect_load",
+ data: &ip_rt_redirect_load,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_REDIRECT_NUMBER,
+ procname: "redirect_number",
+ data: &ip_rt_redirect_number,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_REDIRECT_SILENCE,
+ procname: "redirect_silence",
+ data: &ip_rt_redirect_silence,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_ERROR_COST,
+ procname: "error_cost",
+ data: &ip_rt_error_cost,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_ERROR_BURST,
+ procname: "error_burst",
+ data: &ip_rt_error_burst,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_GC_ELASTICITY,
+ procname: "gc_elasticity",
+ data: &ip_rt_gc_elasticity,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_MTU_EXPIRES,
+ procname: "mtu_expires",
+ data: &ip_rt_mtu_expires,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec_jiffies,
+ strategy: &sysctl_jiffies,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_MIN_PMTU,
+ procname: "min_pmtu",
+ data: &ip_rt_min_pmtu,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_MIN_ADVMSS,
+ procname: "min_adv_mss",
+ data: &ip_rt_min_advmss,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec,
+ },
+ {
+ ctl_name: NET_IPV4_ROUTE_SECRET_INTERVAL,
+ procname: "secret_interval",
+ data: &ip_rt_secret_interval,
+ maxlen: sizeof(int),
+ mode: 0644,
+ proc_handler: &proc_dointvec_jiffies,
+ strategy: &sysctl_jiffies,
+ },
+ { 0 }
+};
+#endif
+
+#ifdef CONFIG_NET_CLS_ROUTE
+struct ip_rt_acct *ip_rt_acct;
+
+/* This code sucks. But you should have seen it before! --RR */
+
+/* IP route accounting ptr for this logical cpu number. */
+#define IP_RT_ACCT_CPU(i) (ip_rt_acct + cpu_logical_map(i) * 256)
+
+static int ip_rt_acct_read(char *buffer, char **start, off_t offset,
+ int length, int *eof, void *data)
+{
+ unsigned int i;
+
+ if ((offset & 3) || (length & 3))
+ return -EIO;
+
+ if (offset >= sizeof(struct ip_rt_acct) * 256) {
+ *eof = 1;
+ return 0;
+ }
+
+ if (offset + length >= sizeof(struct ip_rt_acct) * 256) {
+ length = sizeof(struct ip_rt_acct) * 256 - offset;
+ *eof = 1;
+ }
+
+ offset /= sizeof(u32);
+
+ if (length > 0) {
+ u32 *src = ((u32 *) IP_RT_ACCT_CPU(0)) + offset;
+ u32 *dst = (u32 *) buffer;
+
+ /* Copy first cpu. */
+ *start = buffer;
+ memcpy(dst, src, length);
+
+ /* Add the other cpus in, one int at a time */
+ for (i = 1; i < smp_num_cpus; i++) {
+ unsigned int j;
+
+ src = ((u32 *) IP_RT_ACCT_CPU(i)) + offset;
+
+ for (j = 0; j < length/4; j++)
+ dst[j] += src[j];
+ }
+ }
+ return length;
+}
+#endif
+
+void __init ip_rt_init(void)
+{
+ int i, order, goal;
+
+ rt_hash_rnd = (int) ((num_physpages ^ (num_physpages>>8)) ^
+ (jiffies ^ (jiffies >> 7)));
+
+#ifdef CONFIG_NET_CLS_ROUTE
+ for (order = 0;
+ (PAGE_SIZE << order) < 256 * sizeof(struct ip_rt_acct) * NR_CPUS; order++)
+ /* NOTHING */;
+ ip_rt_acct = (struct ip_rt_acct *)__get_free_pages(GFP_KERNEL, order);
+ if (!ip_rt_acct)
+ panic("IP: failed to allocate ip_rt_acct\n");
+ memset(ip_rt_acct, 0, PAGE_SIZE << order);
+#endif
+
+ ipv4_dst_ops.kmem_cachep = kmem_cache_create("ip_dst_cache",
+ sizeof(struct rtable),
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+
+ if (!ipv4_dst_ops.kmem_cachep)
+ panic("IP: failed to allocate ip_dst_cache\n");
+
+#if CONFIG_IP_ROUTE_BIG_RT_CACHE
+ /* Increase default for dedicated routers. */
+ goal = (num_physpages << PAGE_SHIFT) >> 11;
+#else
+ goal = (num_physpages << PAGE_SHIFT) >> 14;
+#endif
+ ip_rt_max_size = goal / sizeof(struct rt_hash_bucket) * 16;
+
+ for (order = 0; (1UL << order) < (goal >> PAGE_SHIFT); order++)
+ /* NOTHING */;
+
+ do {
+ rt_hash_mask = (1UL << order) * PAGE_SIZE /
+ sizeof(struct rt_hash_bucket);
+ while (rt_hash_mask & (rt_hash_mask - 1))
+ rt_hash_mask--;
+ rt_hash_table = (struct rt_hash_bucket *)
+ __get_free_pages(GFP_ATOMIC, order);
+ } while (rt_hash_table == NULL && --order > 0);
+
+ if (!rt_hash_table)
+ panic("Failed to allocate IP route cache hash table\n");
+
+ printk(KERN_INFO "IP: routing cache hash table of %u buckets, %ldKbytes\n",
+ rt_hash_mask,
+ (long) (rt_hash_mask * sizeof(struct rt_hash_bucket)) / 1024);
+
+ for (rt_hash_log = 0; (1 << rt_hash_log) != rt_hash_mask; rt_hash_log++)
+ /* NOTHING */;
+
+ rt_hash_mask--;
+ for (i = 0; i <= rt_hash_mask; i++) {
+ rt_hash_table[i].lock = RW_LOCK_UNLOCKED;
+ rt_hash_table[i].chain = NULL;
+ }
+
+ ipv4_dst_ops.gc_thresh = (rt_hash_mask + 1);
+ if (ip_rt_max_size > (rt_hash_mask + 1) * 16)
+ ip_rt_max_size = (rt_hash_mask + 1) * 16;
+ else if (ip_rt_max_size < rt_hash_mask + 1)
+ ip_rt_max_size = rt_hash_mask + 1;
+
+ devinet_init();
+ ip_fib_init();
+
+ rt_flush_timer.function = rt_run_flush;
+ rt_periodic_timer.function = rt_check_expire;
+ rt_secret_timer.function = rt_secret_rebuild;
+
+ /* All the timers, started at system startup tend
+ to synchronize. Perturb it a bit.
+ */
+ rt_periodic_timer.expires = jiffies + net_random() % ip_rt_gc_interval +
+ ip_rt_gc_interval;
+ add_timer(&rt_periodic_timer);
+
+ rt_secret_timer.expires = jiffies + net_random() % ip_rt_secret_interval +
+ ip_rt_secret_interval;
+ add_timer(&rt_secret_timer);
+
+ proc_net_create ("rt_cache", 0, rt_cache_get_info);
+ create_proc_info_entry ("rt_cache", 0, proc_net_stat,
+ rt_cache_stat_get_info);
+#ifdef CONFIG_NET_CLS_ROUTE
+ create_proc_read_entry("net/rt_acct", 0, 0, ip_rt_acct_read, NULL);
+#endif
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/syncookies.c b/uClinux-2.4.31-uc0/net/ipv4/syncookies.c
new file mode 100644
index 0000000..2d75f45
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/syncookies.c
@@ -0,0 +1,192 @@
+/*
+ * Syncookies implementation for the Linux kernel
+ *
+ * Copyright (C) 1997 Andi Kleen
+ * Based on ideas by D.J.Bernstein and Eric Schenk.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * $Id: syncookies.c,v 1.17 2001/10/26 14:55:41 davem Exp $
+ *
+ * Missing: IPv6 support.
+ */
+
+#include <linux/tcp.h>
+#include <linux/slab.h>
+#include <linux/random.h>
+#include <net/tcp.h>
+
+extern int sysctl_tcp_syncookies;
+
+/*
+ * This table has to be sorted and terminated with (__u16)-1.
+ * XXX generate a better table.
+ * Unresolved Issues: HIPPI with a 64k MSS is not well supported.
+ */
+static __u16 const msstab[] = {
+ 64 - 1,
+ 256 - 1,
+ 512 - 1,
+ 536 - 1,
+ 1024 - 1,
+ 1440 - 1,
+ 1460 - 1,
+ 4312 - 1,
+ (__u16)-1
+};
+/* The number doesn't include the -1 terminator */
+#define NUM_MSS (sizeof(msstab)/sizeof(msstab[0]) - 1)
+
+/*
+ * Generate a syncookie. mssp points to the mss, which is returned
+ * rounded down to the value encoded in the cookie.
+ */
+__u32 cookie_v4_init_sequence(struct sock *sk, struct sk_buff *skb, __u16 *mssp)
+{
+ int mssind;
+ const __u16 mss = *mssp;
+
+
+ sk->tp_pinfo.af_tcp.last_synq_overflow = jiffies;
+
+ /* XXX sort msstab[] by probability? Binary search? */
+ for (mssind = 0; mss > msstab[mssind + 1]; mssind++)
+ ;
+ *mssp = msstab[mssind] + 1;
+
+ NET_INC_STATS_BH(SyncookiesSent);
+
+ return secure_tcp_syn_cookie(skb->nh.iph->saddr, skb->nh.iph->daddr,
+ skb->h.th->source, skb->h.th->dest,
+ ntohl(skb->h.th->seq),
+ jiffies / (HZ * 60), mssind);
+}
+
+/*
+ * This (misnamed) value is the age of syncookie which is permitted.
+ * Its ideal value should be dependent on TCP_TIMEOUT_INIT and
+ * sysctl_tcp_retries1. It's a rather complicated formula (exponential
+ * backoff) to compute at runtime so it's currently hardcoded here.
+ */
+#define COUNTER_TRIES 4
+/*
+ * Check if a ack sequence number is a valid syncookie.
+ * Return the decoded mss if it is, or 0 if not.
+ */
+static inline int cookie_check(struct sk_buff *skb, __u32 cookie)
+{
+ __u32 seq;
+ __u32 mssind;
+
+ seq = ntohl(skb->h.th->seq)-1;
+ mssind = check_tcp_syn_cookie(cookie,
+ skb->nh.iph->saddr, skb->nh.iph->daddr,
+ skb->h.th->source, skb->h.th->dest,
+ seq, jiffies / (HZ * 60), COUNTER_TRIES);
+
+ return mssind < NUM_MSS ? msstab[mssind] + 1 : 0;
+}
+
+extern struct or_calltable or_ipv4;
+
+static inline struct sock *get_cookie_sock(struct sock *sk, struct sk_buff *skb,
+ struct open_request *req,
+ struct dst_entry *dst)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sock *child;
+
+ child = tp->af_specific->syn_recv_sock(sk, skb, req, dst);
+ if (child)
+ tcp_acceptq_queue(sk, req, child);
+ else
+ tcp_openreq_free(req);
+
+ return child;
+}
+
+struct sock *cookie_v4_check(struct sock *sk, struct sk_buff *skb,
+ struct ip_options *opt)
+{
+ __u32 cookie = ntohl(skb->h.th->ack_seq) - 1;
+ struct sock *ret = sk;
+ struct open_request *req;
+ int mss;
+ struct rtable *rt;
+ __u8 rcv_wscale;
+
+ if (!sysctl_tcp_syncookies || !skb->h.th->ack)
+ goto out;
+
+ if (time_after(jiffies, sk->tp_pinfo.af_tcp.last_synq_overflow + TCP_TIMEOUT_INIT) ||
+ (mss = cookie_check(skb, cookie)) == 0) {
+ NET_INC_STATS_BH(SyncookiesFailed);
+ goto out;
+ }
+
+ NET_INC_STATS_BH(SyncookiesRecv);
+
+ req = tcp_openreq_alloc();
+ ret = NULL;
+ if (!req)
+ goto out;
+
+ req->rcv_isn = htonl(skb->h.th->seq) - 1;
+ req->snt_isn = cookie;
+ req->mss = mss;
+ req->rmt_port = skb->h.th->source;
+ req->af.v4_req.loc_addr = skb->nh.iph->daddr;
+ req->af.v4_req.rmt_addr = skb->nh.iph->saddr;
+ req->class = &or_ipv4; /* for savety */
+ req->af.v4_req.opt = NULL;
+
+ /* We throwed the options of the initial SYN away, so we hope
+ * the ACK carries the same options again (see RFC1122 4.2.3.8)
+ */
+ if (opt && opt->optlen) {
+ int opt_size = sizeof(struct ip_options) + opt->optlen;
+
+ req->af.v4_req.opt = kmalloc(opt_size, GFP_ATOMIC);
+ if (req->af.v4_req.opt) {
+ if (ip_options_echo(req->af.v4_req.opt, skb)) {
+ kfree(req->af.v4_req.opt);
+ req->af.v4_req.opt = NULL;
+ }
+ }
+ }
+
+ req->snd_wscale = req->rcv_wscale = req->tstamp_ok = 0;
+ req->wscale_ok = req->sack_ok = 0;
+ req->expires = 0UL;
+ req->retrans = 0;
+
+ /*
+ * We need to lookup the route here to get at the correct
+ * window size. We should better make sure that the window size
+ * hasn't changed since we received the original syn, but I see
+ * no easy way to do this.
+ */
+ if (ip_route_output(&rt,
+ opt &&
+ opt->srr ? opt->faddr : req->af.v4_req.rmt_addr,
+ req->af.v4_req.loc_addr,
+ RT_CONN_FLAGS(sk),
+ 0)) {
+ tcp_openreq_free(req);
+ goto out;
+ }
+
+ /* Try to redo what tcp_v4_send_synack did. */
+ req->window_clamp = rt->u.dst.window;
+ tcp_select_initial_window(tcp_full_space(sk), req->mss,
+ &req->rcv_wnd, &req->window_clamp,
+ 0, &rcv_wscale);
+ /* BTW win scale with syncookies is 0 by definition */
+ req->rcv_wscale = rcv_wscale;
+
+ ret = get_cookie_sock(sk, skb, req, &rt->u.dst);
+out: return ret;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/sysctl_net_ipv4.c b/uClinux-2.4.31-uc0/net/ipv4/sysctl_net_ipv4.c
new file mode 100644
index 0000000..acdb779
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/sysctl_net_ipv4.c
@@ -0,0 +1,277 @@
+/*
+ * sysctl_net_ipv4.c: sysctl interface to net IPV4 subsystem.
+ *
+ * $Id: sysctl_net_ipv4.c,v 1.50 2001/10/20 00:00:11 davem Exp $
+ *
+ * Begun April 1, 1996, Mike Shaver.
+ * Added /proc/sys/net/ipv4 directory entry (empty =) ). [MS]
+ */
+
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+#include <linux/config.h>
+#include <net/snmp.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <net/tcp.h>
+
+/* From af_inet.c */
+extern int sysctl_ip_nonlocal_bind;
+
+/* From icmp.c */
+extern int sysctl_icmp_echo_ignore_all;
+extern int sysctl_icmp_echo_ignore_broadcasts;
+extern int sysctl_icmp_ignore_bogus_error_responses;
+
+/* From ip_fragment.c */
+extern int sysctl_ipfrag_low_thresh;
+extern int sysctl_ipfrag_high_thresh;
+extern int sysctl_ipfrag_time;
+extern int sysctl_ipfrag_secret_interval;
+
+/* From ip_output.c */
+extern int sysctl_ip_dynaddr;
+
+/* From icmp.c */
+extern int sysctl_icmp_ratelimit;
+extern int sysctl_icmp_ratemask;
+
+/* From igmp.c */
+extern int sysctl_igmp_max_memberships;
+extern int sysctl_igmp_max_msf;
+
+/* From inetpeer.c */
+extern int inet_peer_threshold;
+extern int inet_peer_minttl;
+extern int inet_peer_maxttl;
+extern int inet_peer_gc_mintime;
+extern int inet_peer_gc_maxtime;
+
+#ifdef CONFIG_SYSCTL
+static int tcp_retr1_max = 255;
+static int ip_local_port_range_min[] = { 1, 1 };
+static int ip_local_port_range_max[] = { 65535, 65535 };
+#endif
+
+/* From tcp_input.c */
+extern int sysctl_tcp_westwood;
+
+struct ipv4_config ipv4_config;
+
+extern ctl_table ipv4_route_table[];
+
+#ifdef CONFIG_SYSCTL
+
+static
+int ipv4_sysctl_forward(ctl_table *ctl, int write, struct file * filp,
+ void *buffer, size_t *lenp)
+{
+ int val = ipv4_devconf.forwarding;
+ int ret;
+
+ ret = proc_dointvec(ctl, write, filp, buffer, lenp);
+
+ if (write && ipv4_devconf.forwarding != val)
+ inet_forward_change(ipv4_devconf.forwarding);
+
+ return ret;
+}
+
+static int ipv4_sysctl_forward_strategy(ctl_table *table, int *name, int nlen,
+ void *oldval, size_t *oldlenp,
+ void *newval, size_t newlen,
+ void **context)
+{
+ int new;
+ if (newlen != sizeof(int))
+ return -EINVAL;
+ if (get_user(new,(int *)newval))
+ return -EFAULT;
+ if (new != ipv4_devconf.forwarding)
+ inet_forward_change(new);
+ return 0; /* caller does change again and handles handles oldval */
+}
+
+ctl_table ipv4_table[] = {
+ {NET_IPV4_TCP_TIMESTAMPS, "tcp_timestamps",
+ &sysctl_tcp_timestamps, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_TCP_WINDOW_SCALING, "tcp_window_scaling",
+ &sysctl_tcp_window_scaling, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_TCP_SACK, "tcp_sack",
+ &sysctl_tcp_sack, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_TCP_RETRANS_COLLAPSE, "tcp_retrans_collapse",
+ &sysctl_tcp_retrans_collapse, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_FORWARD, "ip_forward",
+ &ipv4_devconf.forwarding, sizeof(int), 0644, NULL,
+ &ipv4_sysctl_forward,&ipv4_sysctl_forward_strategy},
+ {NET_IPV4_DEFAULT_TTL, "ip_default_ttl",
+ &sysctl_ip_default_ttl, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_AUTOCONFIG, "ip_autoconfig",
+ &ipv4_config.autoconfig, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_NO_PMTU_DISC, "ip_no_pmtu_disc",
+ &ipv4_config.no_pmtu_disc, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_NONLOCAL_BIND, "ip_nonlocal_bind",
+ &sysctl_ip_nonlocal_bind, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_TCP_SYN_RETRIES, "tcp_syn_retries",
+ &sysctl_tcp_syn_retries, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_SYNACK_RETRIES, "tcp_synack_retries",
+ &sysctl_tcp_synack_retries, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_MAX_ORPHANS, "tcp_max_orphans",
+ &sysctl_tcp_max_orphans, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_MAX_TW_BUCKETS, "tcp_max_tw_buckets",
+ &sysctl_tcp_max_tw_buckets, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_IPV4_IPFRAG_HIGH_THRESH, "ipfrag_high_thresh",
+ &sysctl_ipfrag_high_thresh, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_IPV4_IPFRAG_LOW_THRESH, "ipfrag_low_thresh",
+ &sysctl_ipfrag_low_thresh, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_IPV4_DYNADDR, "ip_dynaddr",
+ &sysctl_ip_dynaddr, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_IPV4_IPFRAG_TIME, "ipfrag_time",
+ &sysctl_ipfrag_time, sizeof(int), 0644, NULL, &proc_dointvec_jiffies,
+ &sysctl_jiffies},
+ {NET_IPV4_TCP_KEEPALIVE_TIME, "tcp_keepalive_time",
+ &sysctl_tcp_keepalive_time, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies, &sysctl_jiffies},
+ {NET_IPV4_TCP_KEEPALIVE_PROBES, "tcp_keepalive_probes",
+ &sysctl_tcp_keepalive_probes, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_TCP_KEEPALIVE_INTVL, "tcp_keepalive_intvl",
+ &sysctl_tcp_keepalive_intvl, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies, &sysctl_jiffies},
+ {NET_IPV4_TCP_RETRIES1, "tcp_retries1",
+ &sysctl_tcp_retries1, sizeof(int), 0644, NULL, &proc_dointvec_minmax,
+ &sysctl_intvec, NULL, NULL, &tcp_retr1_max},
+ {NET_IPV4_TCP_RETRIES2, "tcp_retries2",
+ &sysctl_tcp_retries2, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_IPV4_TCP_FIN_TIMEOUT, "tcp_fin_timeout",
+ &sysctl_tcp_fin_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies, &sysctl_jiffies},
+#ifdef CONFIG_SYN_COOKIES
+ {NET_TCP_SYNCOOKIES, "tcp_syncookies",
+ &sysctl_tcp_syncookies, sizeof(int), 0644, NULL, &proc_dointvec},
+#endif
+ {NET_TCP_TW_RECYCLE, "tcp_tw_recycle",
+ &sysctl_tcp_tw_recycle, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_ABORT_ON_OVERFLOW, "tcp_abort_on_overflow",
+ &sysctl_tcp_abort_on_overflow, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_STDURG, "tcp_stdurg", &sysctl_tcp_stdurg,
+ sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_RFC1337, "tcp_rfc1337", &sysctl_tcp_rfc1337,
+ sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_MAX_SYN_BACKLOG, "tcp_max_syn_backlog", &sysctl_max_syn_backlog,
+ sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_IPV4_LOCAL_PORT_RANGE, "ip_local_port_range",
+ &sysctl_local_port_range, sizeof(sysctl_local_port_range), 0644,
+ NULL, &proc_dointvec_minmax, &sysctl_intvec, NULL,
+ ip_local_port_range_min, ip_local_port_range_max },
+ {NET_IPV4_ICMP_ECHO_IGNORE_ALL, "icmp_echo_ignore_all",
+ &sysctl_icmp_echo_ignore_all, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_ICMP_ECHO_IGNORE_BROADCASTS, "icmp_echo_ignore_broadcasts",
+ &sysctl_icmp_echo_ignore_broadcasts, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_ICMP_IGNORE_BOGUS_ERROR_RESPONSES, "icmp_ignore_bogus_error_responses",
+ &sysctl_icmp_ignore_bogus_error_responses, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV4_ROUTE, "route", NULL, 0, 0555, ipv4_route_table},
+#ifdef CONFIG_IP_MULTICAST
+ {NET_IPV4_IGMP_MAX_MEMBERSHIPS, "igmp_max_memberships",
+ &sysctl_igmp_max_memberships, sizeof(int), 0644, NULL, &proc_dointvec},
+#endif
+ {NET_IPV4_IGMP_MAX_MSF, "igmp_max_msf",
+ &sysctl_igmp_max_msf, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_IPV4_INET_PEER_THRESHOLD, "inet_peer_threshold",
+ &inet_peer_threshold, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_IPV4_INET_PEER_MINTTL, "inet_peer_minttl",
+ &inet_peer_minttl, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies, &sysctl_jiffies},
+ {NET_IPV4_INET_PEER_MAXTTL, "inet_peer_maxttl",
+ &inet_peer_maxttl, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies, &sysctl_jiffies},
+ {NET_IPV4_INET_PEER_GC_MINTIME, "inet_peer_gc_mintime",
+ &inet_peer_gc_mintime, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies, &sysctl_jiffies},
+ {NET_IPV4_INET_PEER_GC_MAXTIME, "inet_peer_gc_maxtime",
+ &inet_peer_gc_maxtime, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies, &sysctl_jiffies},
+ {NET_TCP_ORPHAN_RETRIES, "tcp_orphan_retries",
+ &sysctl_tcp_orphan_retries, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_FACK, "tcp_fack",
+ &sysctl_tcp_fack, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_REORDERING, "tcp_reordering",
+ &sysctl_tcp_reordering, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_ECN, "tcp_ecn",
+ &sysctl_tcp_ecn, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_DSACK, "tcp_dsack",
+ &sysctl_tcp_dsack, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_MEM, "tcp_mem",
+ &sysctl_tcp_mem, sizeof(sysctl_tcp_mem), 0644, NULL, &proc_dointvec},
+ {NET_TCP_WMEM, "tcp_wmem",
+ &sysctl_tcp_wmem, sizeof(sysctl_tcp_wmem), 0644, NULL, &proc_dointvec},
+ {NET_TCP_RMEM, "tcp_rmem",
+ &sysctl_tcp_rmem, sizeof(sysctl_tcp_rmem), 0644, NULL, &proc_dointvec},
+ {NET_TCP_APP_WIN, "tcp_app_win",
+ &sysctl_tcp_app_win, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_ADV_WIN_SCALE, "tcp_adv_win_scale",
+ &sysctl_tcp_adv_win_scale, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_IPV4_ICMP_RATELIMIT, "icmp_ratelimit",
+ &sysctl_icmp_ratelimit, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_IPV4_ICMP_RATEMASK, "icmp_ratemask",
+ &sysctl_icmp_ratemask, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_TW_REUSE, "tcp_tw_reuse",
+ &sysctl_tcp_tw_reuse, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_FRTO, "tcp_frto",
+ &sysctl_tcp_frto, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_TCP_LOW_LATENCY, "tcp_low_latency",
+ &sysctl_tcp_low_latency, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_IPV4_IPFRAG_SECRET_INTERVAL, "ipfrag_secret_interval",
+ &sysctl_ipfrag_secret_interval, sizeof(int), 0644, NULL, &proc_dointvec_jiffies,
+ &sysctl_jiffies},
+ {NET_TCP_WESTWOOD, "tcp_westwood",
+ &sysctl_tcp_westwood, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_TCP_NO_METRICS_SAVE, "tcp_no_metrics_save",
+ &sysctl_tcp_nometrics_save, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_TCP_VEGAS, "tcp_vegas_cong_avoid",
+ &sysctl_tcp_vegas_cong_avoid, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_TCP_VEGAS_ALPHA, "tcp_vegas_alpha",
+ &sysctl_tcp_vegas_alpha, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_TCP_VEGAS_BETA, "tcp_vegas_beta",
+ &sysctl_tcp_vegas_beta, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_TCP_VEGAS_GAMMA, "tcp_vegas_gamma",
+ &sysctl_tcp_vegas_gamma, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_TCP_BIC, "tcp_bic",
+ &sysctl_tcp_bic, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_TCP_BIC_FAST_CONVERGENCE, "tcp_bic_fast_convergence",
+ &sysctl_tcp_bic_fast_convergence, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_TCP_BIC_LOW_WINDOW, "tcp_bic_low_window",
+ &sysctl_tcp_bic_low_window, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_TCP_DEFAULT_WIN_SCALE, "tcp_default_win_scale",
+ &sysctl_tcp_default_win_scale, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_TCP_MODERATE_RCVBUF, "tcp_moderate_rcvbuf",
+ &sysctl_tcp_moderate_rcvbuf, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_TCP_BIC_BETA, "tcp_bic_beta",
+ &sysctl_tcp_bic_beta, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {0}
+};
+
+#endif /* CONFIG_SYSCTL */
diff --git a/uClinux-2.4.31-uc0/net/ipv4/tcp.c b/uClinux-2.4.31-uc0/net/ipv4/tcp.c
new file mode 100644
index 0000000..a67015f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/tcp.c
@@ -0,0 +1,2652 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * Implementation of the Transmission Control Protocol(TCP).
+ *
+ * Version: $Id: tcp.c,v 1.215 2001/10/31 08:17:58 davem Exp $
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Mark Evans, <evansmp@uhura.aston.ac.uk>
+ * Corey Minyard <wf-rch!minyard@relay.EU.net>
+ * Florian La Roche, <flla@stud.uni-sb.de>
+ * Charles Hedrick, <hedrick@klinzhai.rutgers.edu>
+ * Linus Torvalds, <torvalds@cs.helsinki.fi>
+ * Alan Cox, <gw4pts@gw4pts.ampr.org>
+ * Matthew Dillon, <dillon@apollo.west.oic.com>
+ * Arnt Gulbrandsen, <agulbra@nvg.unit.no>
+ * Jorge Cwik, <jorge@laser.satlink.net>
+ *
+ * Fixes:
+ * Alan Cox : Numerous verify_area() calls
+ * Alan Cox : Set the ACK bit on a reset
+ * Alan Cox : Stopped it crashing if it closed while
+ * sk->inuse=1 and was trying to connect
+ * (tcp_err()).
+ * Alan Cox : All icmp error handling was broken
+ * pointers passed where wrong and the
+ * socket was looked up backwards. Nobody
+ * tested any icmp error code obviously.
+ * Alan Cox : tcp_err() now handled properly. It
+ * wakes people on errors. poll
+ * behaves and the icmp error race
+ * has gone by moving it into sock.c
+ * Alan Cox : tcp_send_reset() fixed to work for
+ * everything not just packets for
+ * unknown sockets.
+ * Alan Cox : tcp option processing.
+ * Alan Cox : Reset tweaked (still not 100%) [Had
+ * syn rule wrong]
+ * Herp Rosmanith : More reset fixes
+ * Alan Cox : No longer acks invalid rst frames.
+ * Acking any kind of RST is right out.
+ * Alan Cox : Sets an ignore me flag on an rst
+ * receive otherwise odd bits of prattle
+ * escape still
+ * Alan Cox : Fixed another acking RST frame bug.
+ * Should stop LAN workplace lockups.
+ * Alan Cox : Some tidyups using the new skb list
+ * facilities
+ * Alan Cox : sk->keepopen now seems to work
+ * Alan Cox : Pulls options out correctly on accepts
+ * Alan Cox : Fixed assorted sk->rqueue->next errors
+ * Alan Cox : PSH doesn't end a TCP read. Switched a
+ * bit to skb ops.
+ * Alan Cox : Tidied tcp_data to avoid a potential
+ * nasty.
+ * Alan Cox : Added some better commenting, as the
+ * tcp is hard to follow
+ * Alan Cox : Removed incorrect check for 20 * psh
+ * Michael O'Reilly : ack < copied bug fix.
+ * Johannes Stille : Misc tcp fixes (not all in yet).
+ * Alan Cox : FIN with no memory -> CRASH
+ * Alan Cox : Added socket option proto entries.
+ * Also added awareness of them to accept.
+ * Alan Cox : Added TCP options (SOL_TCP)
+ * Alan Cox : Switched wakeup calls to callbacks,
+ * so the kernel can layer network
+ * sockets.
+ * Alan Cox : Use ip_tos/ip_ttl settings.
+ * Alan Cox : Handle FIN (more) properly (we hope).
+ * Alan Cox : RST frames sent on unsynchronised
+ * state ack error.
+ * Alan Cox : Put in missing check for SYN bit.
+ * Alan Cox : Added tcp_select_window() aka NET2E
+ * window non shrink trick.
+ * Alan Cox : Added a couple of small NET2E timer
+ * fixes
+ * Charles Hedrick : TCP fixes
+ * Toomas Tamm : TCP window fixes
+ * Alan Cox : Small URG fix to rlogin ^C ack fight
+ * Charles Hedrick : Rewrote most of it to actually work
+ * Linus : Rewrote tcp_read() and URG handling
+ * completely
+ * Gerhard Koerting: Fixed some missing timer handling
+ * Matthew Dillon : Reworked TCP machine states as per RFC
+ * Gerhard Koerting: PC/TCP workarounds
+ * Adam Caldwell : Assorted timer/timing errors
+ * Matthew Dillon : Fixed another RST bug
+ * Alan Cox : Move to kernel side addressing changes.
+ * Alan Cox : Beginning work on TCP fastpathing
+ * (not yet usable)
+ * Arnt Gulbrandsen: Turbocharged tcp_check() routine.
+ * Alan Cox : TCP fast path debugging
+ * Alan Cox : Window clamping
+ * Michael Riepe : Bug in tcp_check()
+ * Matt Dillon : More TCP improvements and RST bug fixes
+ * Matt Dillon : Yet more small nasties remove from the
+ * TCP code (Be very nice to this man if
+ * tcp finally works 100%) 8)
+ * Alan Cox : BSD accept semantics.
+ * Alan Cox : Reset on closedown bug.
+ * Peter De Schrijver : ENOTCONN check missing in tcp_sendto().
+ * Michael Pall : Handle poll() after URG properly in
+ * all cases.
+ * Michael Pall : Undo the last fix in tcp_read_urg()
+ * (multi URG PUSH broke rlogin).
+ * Michael Pall : Fix the multi URG PUSH problem in
+ * tcp_readable(), poll() after URG
+ * works now.
+ * Michael Pall : recv(...,MSG_OOB) never blocks in the
+ * BSD api.
+ * Alan Cox : Changed the semantics of sk->socket to
+ * fix a race and a signal problem with
+ * accept() and async I/O.
+ * Alan Cox : Relaxed the rules on tcp_sendto().
+ * Yury Shevchuk : Really fixed accept() blocking problem.
+ * Craig I. Hagan : Allow for BSD compatible TIME_WAIT for
+ * clients/servers which listen in on
+ * fixed ports.
+ * Alan Cox : Cleaned the above up and shrank it to
+ * a sensible code size.
+ * Alan Cox : Self connect lockup fix.
+ * Alan Cox : No connect to multicast.
+ * Ross Biro : Close unaccepted children on master
+ * socket close.
+ * Alan Cox : Reset tracing code.
+ * Alan Cox : Spurious resets on shutdown.
+ * Alan Cox : Giant 15 minute/60 second timer error
+ * Alan Cox : Small whoops in polling before an
+ * accept.
+ * Alan Cox : Kept the state trace facility since
+ * it's handy for debugging.
+ * Alan Cox : More reset handler fixes.
+ * Alan Cox : Started rewriting the code based on
+ * the RFC's for other useful protocol
+ * references see: Comer, KA9Q NOS, and
+ * for a reference on the difference
+ * between specifications and how BSD
+ * works see the 4.4lite source.
+ * A.N.Kuznetsov : Don't time wait on completion of tidy
+ * close.
+ * Linus Torvalds : Fin/Shutdown & copied_seq changes.
+ * Linus Torvalds : Fixed BSD port reuse to work first syn
+ * Alan Cox : Reimplemented timers as per the RFC
+ * and using multiple timers for sanity.
+ * Alan Cox : Small bug fixes, and a lot of new
+ * comments.
+ * Alan Cox : Fixed dual reader crash by locking
+ * the buffers (much like datagram.c)
+ * Alan Cox : Fixed stuck sockets in probe. A probe
+ * now gets fed up of retrying without
+ * (even a no space) answer.
+ * Alan Cox : Extracted closing code better
+ * Alan Cox : Fixed the closing state machine to
+ * resemble the RFC.
+ * Alan Cox : More 'per spec' fixes.
+ * Jorge Cwik : Even faster checksumming.
+ * Alan Cox : tcp_data() doesn't ack illegal PSH
+ * only frames. At least one pc tcp stack
+ * generates them.
+ * Alan Cox : Cache last socket.
+ * Alan Cox : Per route irtt.
+ * Matt Day : poll()->select() match BSD precisely on error
+ * Alan Cox : New buffers
+ * Marc Tamsky : Various sk->prot->retransmits and
+ * sk->retransmits misupdating fixed.
+ * Fixed tcp_write_timeout: stuck close,
+ * and TCP syn retries gets used now.
+ * Mark Yarvis : In tcp_read_wakeup(), don't send an
+ * ack if state is TCP_CLOSED.
+ * Alan Cox : Look up device on a retransmit - routes may
+ * change. Doesn't yet cope with MSS shrink right
+ * but its a start!
+ * Marc Tamsky : Closing in closing fixes.
+ * Mike Shaver : RFC1122 verifications.
+ * Alan Cox : rcv_saddr errors.
+ * Alan Cox : Block double connect().
+ * Alan Cox : Small hooks for enSKIP.
+ * Alexey Kuznetsov: Path MTU discovery.
+ * Alan Cox : Support soft errors.
+ * Alan Cox : Fix MTU discovery pathological case
+ * when the remote claims no mtu!
+ * Marc Tamsky : TCP_CLOSE fix.
+ * Colin (G3TNE) : Send a reset on syn ack replies in
+ * window but wrong (fixes NT lpd problems)
+ * Pedro Roque : Better TCP window handling, delayed ack.
+ * Joerg Reuter : No modification of locked buffers in
+ * tcp_do_retransmit()
+ * Eric Schenk : Changed receiver side silly window
+ * avoidance algorithm to BSD style
+ * algorithm. This doubles throughput
+ * against machines running Solaris,
+ * and seems to result in general
+ * improvement.
+ * Stefan Magdalinski : adjusted tcp_readable() to fix FIONREAD
+ * Willy Konynenberg : Transparent proxying support.
+ * Mike McLagan : Routing by source
+ * Keith Owens : Do proper merging with partial SKB's in
+ * tcp_do_sendmsg to avoid burstiness.
+ * Eric Schenk : Fix fast close down bug with
+ * shutdown() followed by close().
+ * Andi Kleen : Make poll agree with SIGIO
+ * Salvatore Sanfilippo : Support SO_LINGER with linger == 1 and
+ * lingertime == 0 (RFC 793 ABORT Call)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or(at your option) any later version.
+ *
+ * Description of States:
+ *
+ * TCP_SYN_SENT sent a connection request, waiting for ack
+ *
+ * TCP_SYN_RECV received a connection request, sent ack,
+ * waiting for final ack in three-way handshake.
+ *
+ * TCP_ESTABLISHED connection established
+ *
+ * TCP_FIN_WAIT1 our side has shutdown, waiting to complete
+ * transmission of remaining buffered data
+ *
+ * TCP_FIN_WAIT2 all buffered data sent, waiting for remote
+ * to shutdown
+ *
+ * TCP_CLOSING both sides have shutdown but we still have
+ * data we have to finish sending
+ *
+ * TCP_TIME_WAIT timeout to catch resent junk before entering
+ * closed, can only be entered from FIN_WAIT2
+ * or CLOSING. Required because the other end
+ * may not have gotten our last ACK causing it
+ * to retransmit the data packet (which we ignore)
+ *
+ * TCP_CLOSE_WAIT remote side has shutdown and is waiting for
+ * us to finish writing our data and to shutdown
+ * (we have to close() to move on to LAST_ACK)
+ *
+ * TCP_LAST_ACK out side has shutdown after remote has
+ * shutdown. There may still be data in our
+ * buffer that we have to finish sending
+ *
+ * TCP_CLOSE socket is finished
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/fs.h>
+#include <linux/random.h>
+
+#include <net/icmp.h>
+#include <net/tcp.h>
+
+#include <asm/uaccess.h>
+#include <asm/ioctls.h>
+
+int sysctl_tcp_fin_timeout = TCP_FIN_TIMEOUT;
+
+struct tcp_mib tcp_statistics[NR_CPUS*2];
+
+kmem_cache_t *tcp_openreq_cachep;
+kmem_cache_t *tcp_bucket_cachep;
+kmem_cache_t *tcp_timewait_cachep;
+
+atomic_t tcp_orphan_count = ATOMIC_INIT(0);
+
+int sysctl_tcp_default_win_scale = 0;
+
+int sysctl_tcp_mem[3];
+int sysctl_tcp_wmem[3] = { 4*1024, 16*1024, 128*1024 };
+int sysctl_tcp_rmem[3] = { 4*1024, 87380, 87380*2 };
+
+atomic_t tcp_memory_allocated; /* Current allocated memory. */
+atomic_t tcp_sockets_allocated; /* Current number of TCP sockets. */
+
+/* Pressure flag: try to collapse.
+ * Technical note: it is used by multiple contexts non atomically.
+ * All the tcp_mem_schedule() is of this nature: accounting
+ * is strict, actions are advisory and have some latency. */
+int tcp_memory_pressure;
+
+#define TCP_PAGES(amt) (((amt)+TCP_MEM_QUANTUM-1)/TCP_MEM_QUANTUM)
+
+int tcp_mem_schedule(struct sock *sk, int size, int kind)
+{
+ int amt = TCP_PAGES(size);
+
+ sk->forward_alloc += amt*TCP_MEM_QUANTUM;
+ atomic_add(amt, &tcp_memory_allocated);
+
+ /* Under limit. */
+ if (atomic_read(&tcp_memory_allocated) < sysctl_tcp_mem[0]) {
+ if (tcp_memory_pressure)
+ tcp_memory_pressure = 0;
+ return 1;
+ }
+
+ /* Over hard limit. */
+ if (atomic_read(&tcp_memory_allocated) > sysctl_tcp_mem[2]) {
+ tcp_enter_memory_pressure();
+ goto suppress_allocation;
+ }
+
+ /* Under pressure. */
+ if (atomic_read(&tcp_memory_allocated) > sysctl_tcp_mem[1])
+ tcp_enter_memory_pressure();
+
+ if (kind) {
+ if (atomic_read(&sk->rmem_alloc) < sysctl_tcp_rmem[0])
+ return 1;
+ } else {
+ if (sk->wmem_queued < sysctl_tcp_wmem[0])
+ return 1;
+ }
+
+ if (!tcp_memory_pressure ||
+ sysctl_tcp_mem[2] > atomic_read(&tcp_sockets_allocated)
+ * TCP_PAGES(sk->wmem_queued+atomic_read(&sk->rmem_alloc)+
+ sk->forward_alloc))
+ return 1;
+
+suppress_allocation:
+
+ if (kind == 0) {
+ tcp_moderate_sndbuf(sk);
+
+ /* Fail only if socket is _under_ its sndbuf.
+ * In this case we cannot block, so that we have to fail.
+ */
+ if (sk->wmem_queued+size >= sk->sndbuf)
+ return 1;
+ }
+
+ /* Alas. Undo changes. */
+ sk->forward_alloc -= amt*TCP_MEM_QUANTUM;
+ atomic_sub(amt, &tcp_memory_allocated);
+ return 0;
+}
+
+void __tcp_mem_reclaim(struct sock *sk)
+{
+ if (sk->forward_alloc >= TCP_MEM_QUANTUM) {
+ atomic_sub(sk->forward_alloc/TCP_MEM_QUANTUM, &tcp_memory_allocated);
+ sk->forward_alloc &= (TCP_MEM_QUANTUM-1);
+ if (tcp_memory_pressure &&
+ atomic_read(&tcp_memory_allocated) < sysctl_tcp_mem[0])
+ tcp_memory_pressure = 0;
+ }
+}
+
+void tcp_rfree(struct sk_buff *skb)
+{
+ struct sock *sk = skb->sk;
+
+ atomic_sub(skb->truesize, &sk->rmem_alloc);
+ sk->forward_alloc += skb->truesize;
+}
+
+/*
+ * LISTEN is a special case for poll..
+ */
+static __inline__ unsigned int tcp_listen_poll(struct sock *sk, poll_table *wait)
+{
+ return sk->tp_pinfo.af_tcp.accept_queue ? (POLLIN | POLLRDNORM) : 0;
+}
+
+/*
+ * Wait for a TCP event.
+ *
+ * Note that we don't need to lock the socket, as the upper poll layers
+ * take care of normal races (between the test and the event) and we don't
+ * go look at any of the socket buffers directly.
+ */
+unsigned int tcp_poll(struct file * file, struct socket *sock, poll_table *wait)
+{
+ unsigned int mask;
+ struct sock *sk = sock->sk;
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ poll_wait(file, sk->sleep, wait);
+ if (sk->state == TCP_LISTEN)
+ return tcp_listen_poll(sk, wait);
+
+ /* Socket is not locked. We are protected from async events
+ by poll logic and correct handling of state changes
+ made by another threads is impossible in any case.
+ */
+
+ mask = 0;
+ if (sk->err)
+ mask = POLLERR;
+
+ /*
+ * POLLHUP is certainly not done right. But poll() doesn't
+ * have a notion of HUP in just one direction, and for a
+ * socket the read side is more interesting.
+ *
+ * Some poll() documentation says that POLLHUP is incompatible
+ * with the POLLOUT/POLLWR flags, so somebody should check this
+ * all. But careful, it tends to be safer to return too many
+ * bits than too few, and you can easily break real applications
+ * if you don't tell them that something has hung up!
+ *
+ * Check-me.
+ *
+ * Check number 1. POLLHUP is _UNMASKABLE_ event (see UNIX98 and
+ * our fs/select.c). It means that after we received EOF,
+ * poll always returns immediately, making impossible poll() on write()
+ * in state CLOSE_WAIT. One solution is evident --- to set POLLHUP
+ * if and only if shutdown has been made in both directions.
+ * Actually, it is interesting to look how Solaris and DUX
+ * solve this dilemma. I would prefer, if PULLHUP were maskable,
+ * then we could set it on SND_SHUTDOWN. BTW examples given
+ * in Stevens' books assume exactly this behaviour, it explains
+ * why PULLHUP is incompatible with POLLOUT. --ANK
+ *
+ * NOTE. Check for TCP_CLOSE is added. The goal is to prevent
+ * blocking on fresh not-connected or disconnected socket. --ANK
+ */
+ if (sk->shutdown == SHUTDOWN_MASK || sk->state == TCP_CLOSE)
+ mask |= POLLHUP;
+ if (sk->shutdown & RCV_SHUTDOWN)
+ mask |= POLLIN | POLLRDNORM;
+
+ /* Connected? */
+ if ((1 << sk->state) & ~(TCPF_SYN_SENT|TCPF_SYN_RECV)) {
+ /* Potential race condition. If read of tp below will
+ * escape above sk->state, we can be illegally awaken
+ * in SYN_* states. */
+ if ((tp->rcv_nxt != tp->copied_seq) &&
+ (tp->urg_seq != tp->copied_seq ||
+ tp->rcv_nxt != tp->copied_seq+1 ||
+ sk->urginline || !tp->urg_data))
+ mask |= POLLIN | POLLRDNORM;
+
+ if (!(sk->shutdown & SEND_SHUTDOWN)) {
+ if (tcp_wspace(sk) >= tcp_min_write_space(sk)) {
+ mask |= POLLOUT | POLLWRNORM;
+ } else { /* send SIGIO later */
+ set_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
+ set_bit(SOCK_NOSPACE, &sk->socket->flags);
+
+ /* Race breaker. If space is freed after
+ * wspace test but before the flags are set,
+ * IO signal will be lost.
+ */
+ if (tcp_wspace(sk) >= tcp_min_write_space(sk))
+ mask |= POLLOUT | POLLWRNORM;
+ }
+ }
+
+ if (tp->urg_data & TCP_URG_VALID)
+ mask |= POLLPRI;
+ }
+ return mask;
+}
+
+/*
+ * TCP socket write_space callback.
+ */
+void tcp_write_space(struct sock *sk)
+{
+ struct socket *sock = sk->socket;
+
+ if (tcp_wspace(sk) >= tcp_min_write_space(sk) && sock) {
+ clear_bit(SOCK_NOSPACE, &sock->flags);
+
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible(sk->sleep);
+
+ if (sock->fasync_list && !(sk->shutdown&SEND_SHUTDOWN))
+ sock_wake_async(sock, 2, POLL_OUT);
+ }
+}
+
+int tcp_ioctl(struct sock *sk, int cmd, unsigned long arg)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int answ;
+
+ switch(cmd) {
+ case SIOCINQ:
+ if (sk->state == TCP_LISTEN)
+ return(-EINVAL);
+
+ lock_sock(sk);
+ if ((1<<sk->state) & (TCPF_SYN_SENT|TCPF_SYN_RECV))
+ answ = 0;
+ else if (sk->urginline || !tp->urg_data ||
+ before(tp->urg_seq,tp->copied_seq) ||
+ !before(tp->urg_seq,tp->rcv_nxt)) {
+ answ = tp->rcv_nxt - tp->copied_seq;
+
+ /* Subtract 1, if FIN is in queue. */
+ if (answ && !skb_queue_empty(&sk->receive_queue))
+ answ -= ((struct sk_buff*)sk->receive_queue.prev)->h.th->fin;
+ } else
+ answ = tp->urg_seq - tp->copied_seq;
+ release_sock(sk);
+ break;
+ case SIOCATMARK:
+ {
+ answ = tp->urg_data && tp->urg_seq == tp->copied_seq;
+ break;
+ }
+ case SIOCOUTQ:
+ if (sk->state == TCP_LISTEN)
+ return(-EINVAL);
+
+ if ((1<<sk->state) & (TCPF_SYN_SENT|TCPF_SYN_RECV))
+ answ = 0;
+ else
+ answ = tp->write_seq - tp->snd_una;
+ break;
+ default:
+ return(-ENOIOCTLCMD);
+ };
+
+ return put_user(answ, (int *)arg);
+}
+
+
+int tcp_listen_start(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct tcp_listen_opt *lopt;
+
+ sk->max_ack_backlog = 0;
+ sk->ack_backlog = 0;
+ tp->accept_queue = tp->accept_queue_tail = NULL;
+ tp->syn_wait_lock = RW_LOCK_UNLOCKED;
+ tcp_delack_init(tp);
+
+ lopt = kmalloc(sizeof(struct tcp_listen_opt), GFP_KERNEL);
+ if (!lopt)
+ return -ENOMEM;
+
+ memset(lopt, 0, sizeof(struct tcp_listen_opt));
+ for (lopt->max_qlen_log = 6; ; lopt->max_qlen_log++)
+ if ((1<<lopt->max_qlen_log) >= sysctl_max_syn_backlog)
+ break;
+ get_random_bytes(&lopt->hash_rnd, 4);
+
+ write_lock_bh(&tp->syn_wait_lock);
+ tp->listen_opt = lopt;
+ write_unlock_bh(&tp->syn_wait_lock);
+
+ /* There is race window here: we announce ourselves listening,
+ * but this transition is still not validated by get_port().
+ * It is OK, because this socket enters to hash table only
+ * after validation is complete.
+ */
+ sk->state = TCP_LISTEN;
+ if (sk->prot->get_port(sk, sk->num) == 0) {
+ sk->sport = htons(sk->num);
+
+ sk_dst_reset(sk);
+ sk->prot->hash(sk);
+
+ return 0;
+ }
+
+ sk->state = TCP_CLOSE;
+ write_lock_bh(&tp->syn_wait_lock);
+ tp->listen_opt = NULL;
+ write_unlock_bh(&tp->syn_wait_lock);
+ kfree(lopt);
+ return -EADDRINUSE;
+}
+
+/*
+ * This routine closes sockets which have been at least partially
+ * opened, but not yet accepted.
+ */
+
+static void tcp_listen_stop (struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct tcp_listen_opt *lopt = tp->listen_opt;
+ struct open_request *acc_req = tp->accept_queue;
+ struct open_request *req;
+ int i;
+
+ tcp_delete_keepalive_timer(sk);
+
+ /* make all the listen_opt local to us */
+ write_lock_bh(&tp->syn_wait_lock);
+ tp->listen_opt =NULL;
+ write_unlock_bh(&tp->syn_wait_lock);
+ tp->accept_queue = tp->accept_queue_tail = NULL;
+
+ if (lopt->qlen) {
+ for (i=0; i<TCP_SYNQ_HSIZE; i++) {
+ while ((req = lopt->syn_table[i]) != NULL) {
+ lopt->syn_table[i] = req->dl_next;
+ lopt->qlen--;
+ tcp_openreq_free(req);
+
+ /* Following specs, it would be better either to send FIN
+ * (and enter FIN-WAIT-1, it is normal close)
+ * or to send active reset (abort).
+ * Certainly, it is pretty dangerous while synflood, but it is
+ * bad justification for our negligence 8)
+ * To be honest, we are not able to make either
+ * of the variants now. --ANK
+ */
+ }
+ }
+ }
+ BUG_TRAP(lopt->qlen == 0);
+
+ kfree(lopt);
+
+ while ((req=acc_req) != NULL) {
+ struct sock *child = req->sk;
+
+ acc_req = req->dl_next;
+
+ local_bh_disable();
+ bh_lock_sock(child);
+ BUG_TRAP(child->lock.users==0);
+ sock_hold(child);
+
+ tcp_disconnect(child, O_NONBLOCK);
+
+ sock_orphan(child);
+
+ atomic_inc(&tcp_orphan_count);
+
+ tcp_destroy_sock(child);
+
+ bh_unlock_sock(child);
+ local_bh_enable();
+ sock_put(child);
+
+ tcp_acceptq_removed(sk);
+ tcp_openreq_fastfree(req);
+ }
+ BUG_TRAP(sk->ack_backlog == 0);
+}
+
+/*
+ * Wait for a socket to get into the connected state
+ *
+ * Note: Must be called with the socket locked.
+ */
+static int wait_for_tcp_connect(struct sock * sk, int flags, long *timeo_p)
+{
+ struct task_struct *tsk = current;
+ DECLARE_WAITQUEUE(wait, tsk);
+
+ while((1 << sk->state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT)) {
+ if(sk->err)
+ return sock_error(sk);
+ if((1 << sk->state) &
+ ~(TCPF_SYN_SENT | TCPF_SYN_RECV))
+ return -EPIPE;
+ if(!*timeo_p)
+ return -EAGAIN;
+ if(signal_pending(tsk))
+ return sock_intr_errno(*timeo_p);
+
+ __set_task_state(tsk, TASK_INTERRUPTIBLE);
+ add_wait_queue(sk->sleep, &wait);
+ sk->tp_pinfo.af_tcp.write_pending++;
+
+ release_sock(sk);
+ *timeo_p = schedule_timeout(*timeo_p);
+ lock_sock(sk);
+
+ __set_task_state(tsk, TASK_RUNNING);
+ remove_wait_queue(sk->sleep, &wait);
+ sk->tp_pinfo.af_tcp.write_pending--;
+ }
+ return 0;
+}
+
+static inline int tcp_memory_free(struct sock *sk)
+{
+ return sk->wmem_queued < sk->sndbuf;
+}
+
+/*
+ * Wait for more memory for a socket
+ */
+static int wait_for_tcp_memory(struct sock * sk, long *timeo)
+{
+ int err = 0;
+ long vm_wait = 0;
+ long current_timeo = *timeo;
+ DECLARE_WAITQUEUE(wait, current);
+
+ if (tcp_memory_free(sk))
+ current_timeo = vm_wait = (net_random()%(HZ/5))+2;
+
+ add_wait_queue(sk->sleep, &wait);
+ for (;;) {
+ set_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
+
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (sk->err || (sk->shutdown & SEND_SHUTDOWN))
+ goto do_error;
+ if (!*timeo)
+ goto do_nonblock;
+ if (signal_pending(current))
+ goto do_interrupted;
+ clear_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
+ if (tcp_memory_free(sk) && !vm_wait)
+ break;
+
+ set_bit(SOCK_NOSPACE, &sk->socket->flags);
+ sk->tp_pinfo.af_tcp.write_pending++;
+ release_sock(sk);
+ if (!tcp_memory_free(sk) || vm_wait)
+ current_timeo = schedule_timeout(current_timeo);
+ lock_sock(sk);
+ sk->tp_pinfo.af_tcp.write_pending--;
+
+ if (vm_wait) {
+ vm_wait -= current_timeo;
+ current_timeo = *timeo;
+ if (current_timeo != MAX_SCHEDULE_TIMEOUT &&
+ (current_timeo -= vm_wait) < 0)
+ current_timeo = 0;
+ vm_wait = 0;
+ }
+ *timeo = current_timeo;
+ }
+out:
+ current->state = TASK_RUNNING;
+ remove_wait_queue(sk->sleep, &wait);
+ return err;
+
+do_error:
+ err = -EPIPE;
+ goto out;
+do_nonblock:
+ err = -EAGAIN;
+ goto out;
+do_interrupted:
+ err = sock_intr_errno(*timeo);
+ goto out;
+}
+
+ssize_t do_tcp_sendpages(struct sock *sk, struct page **pages, int poffset, size_t psize, int flags);
+
+static inline int
+can_coalesce(struct sk_buff *skb, int i, struct page *page, int off)
+{
+ if (i) {
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i-1];
+ return page == frag->page &&
+ off == frag->page_offset+frag->size;
+ }
+ return 0;
+}
+
+static inline void
+fill_page_desc(struct sk_buff *skb, int i, struct page *page, int off, int size)
+{
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+ frag->page = page;
+ frag->page_offset = off;
+ frag->size = size;
+ skb_shinfo(skb)->nr_frags = i+1;
+}
+
+static inline void tcp_mark_push(struct tcp_opt *tp, struct sk_buff *skb)
+{
+ TCP_SKB_CB(skb)->flags |= TCPCB_FLAG_PSH;
+ tp->pushed_seq = tp->write_seq;
+}
+
+static inline int forced_push(struct tcp_opt *tp)
+{
+ return after(tp->write_seq, tp->pushed_seq + (tp->max_window>>1));
+}
+
+static inline void
+skb_entail(struct sock *sk, struct tcp_opt *tp, struct sk_buff *skb)
+{
+ skb->csum = 0;
+ TCP_SKB_CB(skb)->seq = tp->write_seq;
+ TCP_SKB_CB(skb)->end_seq = tp->write_seq;
+ TCP_SKB_CB(skb)->flags = TCPCB_FLAG_ACK;
+ TCP_SKB_CB(skb)->sacked = 0;
+ __skb_queue_tail(&sk->write_queue, skb);
+ tcp_charge_skb(sk, skb);
+ if (tp->send_head == NULL)
+ tp->send_head = skb;
+}
+
+static inline void
+tcp_mark_urg(struct tcp_opt *tp, int flags, struct sk_buff *skb)
+{
+ if (flags & MSG_OOB) {
+ tp->urg_mode = 1;
+ tp->snd_up = tp->write_seq;
+ TCP_SKB_CB(skb)->sacked |= TCPCB_URG;
+ }
+}
+
+static inline void
+tcp_push(struct sock *sk, struct tcp_opt *tp, int flags, int mss_now, int nonagle)
+{
+ if (tp->send_head) {
+ struct sk_buff *skb = sk->write_queue.prev;
+ if (!(flags&MSG_MORE) || forced_push(tp))
+ tcp_mark_push(tp, skb);
+ tcp_mark_urg(tp, flags, skb);
+ __tcp_push_pending_frames(sk, tp, mss_now, (flags&MSG_MORE) ? 2 : nonagle);
+ }
+}
+
+static int tcp_error(struct sock *sk, int flags, int err)
+{
+ if (err == -EPIPE)
+ err = sock_error(sk) ? : -EPIPE;
+ if (err == -EPIPE && !(flags&MSG_NOSIGNAL))
+ send_sig(SIGPIPE, current, 0);
+ return err;
+}
+
+ssize_t do_tcp_sendpages(struct sock *sk, struct page **pages, int poffset, size_t psize, int flags)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int mss_now;
+ int err;
+ ssize_t copied;
+ long timeo = sock_sndtimeo(sk, flags&MSG_DONTWAIT);
+
+ /* Wait for a connection to finish. */
+ if ((1 << sk->state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
+ if((err = wait_for_tcp_connect(sk, 0, &timeo)) != 0)
+ goto out_err;
+
+ clear_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
+
+ mss_now = tcp_current_mss(sk);
+ copied = 0;
+
+ err = -EPIPE;
+ if (sk->err || (sk->shutdown & SEND_SHUTDOWN))
+ goto do_error;
+
+ while (psize > 0) {
+ struct sk_buff *skb = sk->write_queue.prev;
+ int offset, size, copy, i;
+ struct page *page;
+
+ page = pages[poffset/PAGE_SIZE];
+ offset = poffset % PAGE_SIZE;
+ size = min_t(size_t, psize, PAGE_SIZE-offset);
+
+ if (tp->send_head==NULL || (copy = mss_now - skb->len) <= 0) {
+new_segment:
+ if (!tcp_memory_free(sk))
+ goto wait_for_sndbuf;
+
+ skb = tcp_alloc_pskb(sk, 0, tp->mss_cache, sk->allocation);
+ if (skb == NULL)
+ goto wait_for_memory;
+
+ skb_entail(sk, tp, skb);
+ copy = mss_now;
+ }
+
+ if (copy > size)
+ copy = size;
+
+ i = skb_shinfo(skb)->nr_frags;
+ if (can_coalesce(skb, i, page, offset)) {
+ skb_shinfo(skb)->frags[i-1].size += copy;
+ } else if (i < MAX_SKB_FRAGS) {
+ get_page(page);
+ fill_page_desc(skb, i, page, offset, copy);
+ } else {
+ tcp_mark_push(tp, skb);
+ goto new_segment;
+ }
+
+ skb->len += copy;
+ skb->data_len += copy;
+ skb->ip_summed = CHECKSUM_HW;
+ tp->write_seq += copy;
+ TCP_SKB_CB(skb)->end_seq += copy;
+
+ if (!copied)
+ TCP_SKB_CB(skb)->flags &= ~TCPCB_FLAG_PSH;
+
+ copied += copy;
+ poffset += copy;
+ if (!(psize -= copy))
+ goto out;
+
+ if (skb->len != mss_now || (flags&MSG_OOB))
+ continue;
+
+ if (forced_push(tp)) {
+ tcp_mark_push(tp, skb);
+ __tcp_push_pending_frames(sk, tp, mss_now, 1);
+ } else if (skb == tp->send_head)
+ tcp_push_one(sk, mss_now);
+ continue;
+
+wait_for_sndbuf:
+ set_bit(SOCK_NOSPACE, &sk->socket->flags);
+wait_for_memory:
+ if (copied)
+ tcp_push(sk, tp, flags&~MSG_MORE, mss_now, 1);
+
+ if ((err = wait_for_tcp_memory(sk, &timeo)) != 0)
+ goto do_error;
+
+ mss_now = tcp_current_mss(sk);
+ }
+
+out:
+ if (copied)
+ tcp_push(sk, tp, flags, mss_now, tp->nonagle);
+ return copied;
+
+do_error:
+ if (copied)
+ goto out;
+out_err:
+ return tcp_error(sk, flags, err);
+}
+
+ssize_t tcp_sendpage(struct socket *sock, struct page *page, int offset, size_t size, int flags)
+{
+ ssize_t res;
+ struct sock *sk = sock->sk;
+
+#define TCP_ZC_CSUM_FLAGS (NETIF_F_IP_CSUM|NETIF_F_NO_CSUM|NETIF_F_HW_CSUM)
+
+ if (!(sk->route_caps & NETIF_F_SG) ||
+ !(sk->route_caps & TCP_ZC_CSUM_FLAGS))
+ return sock_no_sendpage(sock, page, offset, size, flags);
+
+#undef TCP_ZC_CSUM_FLAGS
+
+ lock_sock(sk);
+ TCP_CHECK_TIMER(sk);
+ res = do_tcp_sendpages(sk, &page, offset, size, flags);
+ TCP_CHECK_TIMER(sk);
+ release_sock(sk);
+ return res;
+}
+
+#define TCP_PAGE(sk) (sk->tp_pinfo.af_tcp.sndmsg_page)
+#define TCP_OFF(sk) (sk->tp_pinfo.af_tcp.sndmsg_off)
+
+static inline int
+tcp_copy_to_page(struct sock *sk, char *from, struct sk_buff *skb,
+ struct page *page, int off, int copy)
+{
+ int err = 0;
+ unsigned int csum;
+
+ csum = csum_and_copy_from_user(from, page_address(page)+off,
+ copy, 0, &err);
+ if (!err) {
+ if (skb->ip_summed == CHECKSUM_NONE)
+ skb->csum = csum_block_add(skb->csum, csum, skb->len);
+ skb->len += copy;
+ skb->data_len += copy;
+ skb->truesize += copy;
+ sk->wmem_queued += copy;
+ sk->forward_alloc -= copy;
+ }
+ return err;
+}
+
+static inline int
+skb_add_data(struct sk_buff *skb, char *from, int copy)
+{
+ int err = 0;
+ unsigned int csum;
+ int off = skb->len;
+
+ csum = csum_and_copy_from_user(from, skb_put(skb, copy),
+ copy, 0, &err);
+ if (!err) {
+ skb->csum = csum_block_add(skb->csum, csum, off);
+ return 0;
+ }
+
+ __skb_trim(skb, off);
+ return -EFAULT;
+}
+
+static inline int select_size(struct sock *sk, struct tcp_opt *tp)
+{
+ int tmp = tp->mss_cache;
+
+ if (sk->route_caps&NETIF_F_SG) {
+ int pgbreak = SKB_MAX_HEAD(MAX_TCP_HEADER);
+
+ if (tmp >= pgbreak && tmp <= pgbreak + (MAX_SKB_FRAGS-1)*PAGE_SIZE)
+ tmp = pgbreak;
+ }
+ return tmp;
+}
+
+int tcp_sendmsg(struct sock *sk, struct msghdr *msg, int size)
+{
+ struct iovec *iov;
+ struct tcp_opt *tp;
+ struct sk_buff *skb;
+ int iovlen, flags;
+ int mss_now;
+ int err, copied;
+ long timeo;
+
+ tp = &(sk->tp_pinfo.af_tcp);
+
+ lock_sock(sk);
+ TCP_CHECK_TIMER(sk);
+
+ flags = msg->msg_flags;
+ timeo = sock_sndtimeo(sk, flags&MSG_DONTWAIT);
+
+ /* Wait for a connection to finish. */
+ if ((1 << sk->state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
+ if((err = wait_for_tcp_connect(sk, flags, &timeo)) != 0)
+ goto out_err;
+
+ /* This should be in poll */
+ clear_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
+
+ mss_now = tcp_current_mss(sk);
+
+ /* Ok commence sending. */
+ iovlen = msg->msg_iovlen;
+ iov = msg->msg_iov;
+ copied = 0;
+
+ err = -EPIPE;
+ if (sk->err || (sk->shutdown&SEND_SHUTDOWN))
+ goto do_error;
+
+ while (--iovlen >= 0) {
+ int seglen=iov->iov_len;
+ unsigned char * from=iov->iov_base;
+
+ iov++;
+
+ while (seglen > 0) {
+ int copy;
+
+ skb = sk->write_queue.prev;
+
+ if (tp->send_head == NULL ||
+ (copy = mss_now - skb->len) <= 0) {
+
+new_segment:
+ /* Allocate new segment. If the interface is SG,
+ * allocate skb fitting to single page.
+ */
+ if (!tcp_memory_free(sk))
+ goto wait_for_sndbuf;
+
+ skb = tcp_alloc_pskb(sk, select_size(sk, tp), 0, sk->allocation);
+ if (skb == NULL)
+ goto wait_for_memory;
+
+ skb_entail(sk, tp, skb);
+ copy = mss_now;
+ }
+
+ /* Try to append data to the end of skb. */
+ if (copy > seglen)
+ copy = seglen;
+
+ /* Where to copy to? */
+ if (skb_tailroom(skb) > 0) {
+ /* We have some space in skb head. Superb! */
+ if (copy > skb_tailroom(skb))
+ copy = skb_tailroom(skb);
+ if ((err = skb_add_data(skb, from, copy)) != 0)
+ goto do_fault;
+ } else {
+ int merge = 0;
+ int i = skb_shinfo(skb)->nr_frags;
+ struct page *page = TCP_PAGE(sk);
+ int off = TCP_OFF(sk);
+
+ if (can_coalesce(skb, i, page, off) && off != PAGE_SIZE) {
+ /* We can extend the last page fragment. */
+ merge = 1;
+ } else if (i == MAX_SKB_FRAGS ||
+ (i == 0 && !(sk->route_caps&NETIF_F_SG))) {
+ /* Need to add new fragment and cannot
+ * do this because interface is non-SG,
+ * or because all the page slots are busy.
+ */
+ tcp_mark_push(tp, skb);
+ goto new_segment;
+ } else if (page) {
+ /* If page is cached, align
+ * offset to L1 cache boundary
+ */
+ off = (off+L1_CACHE_BYTES-1)&~(L1_CACHE_BYTES-1);
+ if (off == PAGE_SIZE) {
+ put_page(page);
+ TCP_PAGE(sk) = page = NULL;
+ }
+ }
+
+ if (!page) {
+ /* Allocate new cache page. */
+ if (!(page=tcp_alloc_page(sk)))
+ goto wait_for_memory;
+ off = 0;
+ }
+
+ if (copy > PAGE_SIZE-off)
+ copy = PAGE_SIZE-off;
+
+ /* Time to copy data. We are close to the end! */
+ err = tcp_copy_to_page(sk, from, skb, page, off, copy);
+ if (err) {
+ /* If this page was new, give it to the
+ * socket so it does not get leaked.
+ */
+ if (TCP_PAGE(sk) == NULL) {
+ TCP_PAGE(sk) = page;
+ TCP_OFF(sk) = 0;
+ }
+ goto do_error;
+ }
+
+ /* Update the skb. */
+ if (merge) {
+ skb_shinfo(skb)->frags[i-1].size += copy;
+ } else {
+ fill_page_desc(skb, i, page, off, copy);
+ if (TCP_PAGE(sk)) {
+ get_page(page);
+ } else if (off + copy < PAGE_SIZE) {
+ get_page(page);
+ TCP_PAGE(sk) = page;
+ }
+ }
+
+ TCP_OFF(sk) = off+copy;
+ }
+
+ if (!copied)
+ TCP_SKB_CB(skb)->flags &= ~TCPCB_FLAG_PSH;
+
+ tp->write_seq += copy;
+ TCP_SKB_CB(skb)->end_seq += copy;
+
+ from += copy;
+ copied += copy;
+ if ((seglen -= copy) == 0 && iovlen == 0)
+ goto out;
+
+ if (skb->len != mss_now || (flags&MSG_OOB))
+ continue;
+
+ if (forced_push(tp)) {
+ tcp_mark_push(tp, skb);
+ __tcp_push_pending_frames(sk, tp, mss_now, 1);
+ } else if (skb == tp->send_head)
+ tcp_push_one(sk, mss_now);
+ continue;
+
+wait_for_sndbuf:
+ set_bit(SOCK_NOSPACE, &sk->socket->flags);
+wait_for_memory:
+ if (copied)
+ tcp_push(sk, tp, flags&~MSG_MORE, mss_now, 1);
+
+ if ((err = wait_for_tcp_memory(sk, &timeo)) != 0)
+ goto do_error;
+
+ mss_now = tcp_current_mss(sk);
+ }
+ }
+
+out:
+ if (copied)
+ tcp_push(sk, tp, flags, mss_now, tp->nonagle);
+ TCP_CHECK_TIMER(sk);
+ release_sock(sk);
+ return copied;
+
+do_fault:
+ if (skb->len == 0) {
+ if (tp->send_head == skb)
+ tp->send_head = NULL;
+ __skb_unlink(skb, skb->list);
+ tcp_free_skb(sk, skb);
+ }
+
+do_error:
+ if (copied)
+ goto out;
+out_err:
+ err = tcp_error(sk, flags, err);
+ TCP_CHECK_TIMER(sk);
+ release_sock(sk);
+ return err;
+}
+
+/*
+ * Handle reading urgent data. BSD has very simple semantics for
+ * this, no blocking and very strange errors 8)
+ */
+
+static int tcp_recv_urg(struct sock * sk, long timeo,
+ struct msghdr *msg, int len, int flags,
+ int *addr_len)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ /* No URG data to read. */
+ if (sk->urginline || !tp->urg_data || tp->urg_data == TCP_URG_READ)
+ return -EINVAL; /* Yes this is right ! */
+
+ if (sk->state==TCP_CLOSE && !sk->done)
+ return -ENOTCONN;
+
+ if (tp->urg_data & TCP_URG_VALID) {
+ int err = 0;
+ char c = tp->urg_data;
+
+ if (!(flags & MSG_PEEK))
+ tp->urg_data = TCP_URG_READ;
+
+ /* Read urgent data. */
+ msg->msg_flags|=MSG_OOB;
+
+ if(len>0) {
+ if (!(flags & MSG_TRUNC))
+ err = memcpy_toiovec(msg->msg_iov, &c, 1);
+ len = 1;
+ } else
+ msg->msg_flags|=MSG_TRUNC;
+
+ return err ? -EFAULT : len;
+ }
+
+ if (sk->state == TCP_CLOSE || (sk->shutdown & RCV_SHUTDOWN))
+ return 0;
+
+ /* Fixed the recv(..., MSG_OOB) behaviour. BSD docs and
+ * the available implementations agree in this case:
+ * this call should never block, independent of the
+ * blocking state of the socket.
+ * Mike <pall@rz.uni-karlsruhe.de>
+ */
+ return -EAGAIN;
+}
+
+/*
+ * Release a skb if it is no longer needed. This routine
+ * must be called with interrupts disabled or with the
+ * socket locked so that the sk_buff queue operation is ok.
+ */
+
+static inline void tcp_eat_skb(struct sock *sk, struct sk_buff * skb)
+{
+ __skb_unlink(skb, &sk->receive_queue);
+ __kfree_skb(skb);
+}
+
+/* Clean up the receive buffer for full frames taken by the user,
+ * then send an ACK if necessary. COPIED is the number of bytes
+ * tcp_recvmsg has given to the user so far, it speeds up the
+ * calculation of whether or not we must ACK for the sake of
+ * a window update.
+ */
+static void cleanup_rbuf(struct sock *sk, int copied)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int time_to_ack = 0;
+
+#if TCP_DEBUG
+ struct sk_buff *skb = skb_peek(&sk->receive_queue);
+
+ BUG_TRAP(skb==NULL || before(tp->copied_seq, TCP_SKB_CB(skb)->end_seq));
+#endif
+
+ if (tcp_ack_scheduled(tp)) {
+ /* Delayed ACKs frequently hit locked sockets during bulk receive. */
+ if (tp->ack.blocked
+ /* Once-per-two-segments ACK was not sent by tcp_input.c */
+ || tp->rcv_nxt - tp->rcv_wup > tp->ack.rcv_mss
+ /*
+ * If this read emptied read buffer, we send ACK, if
+ * connection is not bidirectional, user drained
+ * receive buffer and there was a small segment
+ * in queue.
+ */
+ || (copied > 0 &&
+ (tp->ack.pending&TCP_ACK_PUSHED) &&
+ !tp->ack.pingpong &&
+ atomic_read(&sk->rmem_alloc) == 0)) {
+ time_to_ack = 1;
+ }
+ }
+
+ /* We send an ACK if we can now advertise a non-zero window
+ * which has been raised "significantly".
+ *
+ * Even if window raised up to infinity, do not send window open ACK
+ * in states, where we will not receive more. It is useless.
+ */
+ if(copied > 0 && !time_to_ack && !(sk->shutdown&RCV_SHUTDOWN)) {
+ __u32 rcv_window_now = tcp_receive_window(tp);
+
+ /* Optimize, __tcp_select_window() is not cheap. */
+ if (2*rcv_window_now <= tp->window_clamp) {
+ __u32 new_window = __tcp_select_window(sk);
+
+ /* Send ACK now, if this read freed lots of space
+ * in our buffer. Certainly, new_window is new window.
+ * We can advertise it now, if it is not less than current one.
+ * "Lots" means "at least twice" here.
+ */
+ if(new_window && new_window >= 2*rcv_window_now)
+ time_to_ack = 1;
+ }
+ }
+ if (time_to_ack)
+ tcp_send_ack(sk);
+}
+
+/* Now socket state including sk->err is changed only under lock,
+ * hence we may omit checks after joining wait queue.
+ * We check receive queue before schedule() only as optimization;
+ * it is very likely that release_sock() added new data.
+ */
+
+static long tcp_data_wait(struct sock *sk, long timeo)
+{
+ DECLARE_WAITQUEUE(wait, current);
+
+ add_wait_queue(sk->sleep, &wait);
+
+ __set_current_state(TASK_INTERRUPTIBLE);
+
+ set_bit(SOCK_ASYNC_WAITDATA, &sk->socket->flags);
+ release_sock(sk);
+
+ if (skb_queue_empty(&sk->receive_queue))
+ timeo = schedule_timeout(timeo);
+
+ lock_sock(sk);
+ clear_bit(SOCK_ASYNC_WAITDATA, &sk->socket->flags);
+
+ remove_wait_queue(sk->sleep, &wait);
+ __set_current_state(TASK_RUNNING);
+ return timeo;
+}
+
+static void tcp_prequeue_process(struct sock *sk)
+{
+ struct sk_buff *skb;
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ net_statistics[smp_processor_id()*2+1].TCPPrequeued += skb_queue_len(&tp->ucopy.prequeue);
+
+ /* RX process wants to run with disabled BHs, though it is not necessary */
+ local_bh_disable();
+ while ((skb = __skb_dequeue(&tp->ucopy.prequeue)) != NULL)
+ sk->backlog_rcv(sk, skb);
+ local_bh_enable();
+
+ /* Clear memory counter. */
+ tp->ucopy.memory = 0;
+}
+
+static inline
+struct sk_buff *tcp_recv_skb(struct sock *sk, u32 seq, u32 *off)
+{
+ struct sk_buff *skb;
+ u32 offset;
+
+ skb_queue_walk(&sk->receive_queue, skb) {
+ offset = seq - TCP_SKB_CB(skb)->seq;
+ if (skb->h.th->syn)
+ offset--;
+ if (offset < skb->len || skb->h.th->fin) {
+ *off = offset;
+ return skb;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * This routine provides an alternative to tcp_recvmsg() for routines
+ * that would like to handle copying from skbuffs directly in 'sendfile'
+ * fashion.
+ * Note:
+ * - It is assumed that the socket was locked by the caller.
+ * - The routine does not block.
+ * - At present, there is no support for reading OOB data
+ * or for 'peeking' the socket using this routine
+ * (although both would be easy to implement).
+ */
+int tcp_read_sock(struct sock *sk, read_descriptor_t *desc,
+ sk_read_actor_t recv_actor)
+{
+ struct sk_buff *skb;
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ u32 seq = tp->copied_seq;
+ u32 offset;
+ int copied = 0;
+
+ if (sk->state == TCP_LISTEN)
+ return -ENOTCONN;
+ while ((skb = tcp_recv_skb(sk, seq, &offset)) != NULL) {
+ if (offset < skb->len) {
+ size_t used, len;
+
+ len = skb->len - offset;
+ /* Stop reading if we hit a patch of urgent data */
+ if (tp->urg_data) {
+ u32 urg_offset = tp->urg_seq - seq;
+ if (urg_offset < len)
+ len = urg_offset;
+ if (!len)
+ break;
+ }
+ used = recv_actor(desc, skb, offset, len);
+ if (used <= len) {
+ seq += used;
+ copied += used;
+ offset += used;
+ }
+ if (offset != skb->len)
+ break;
+ }
+ if (skb->h.th->fin) {
+ tcp_eat_skb(sk, skb);
+ ++seq;
+ break;
+ }
+ tcp_eat_skb(sk, skb);
+ if (!desc->count)
+ break;
+ }
+ tp->copied_seq = seq;
+
+ tcp_rcv_space_adjust(sk);
+
+ /* Clean up data we have read: This will do ACK frames. */
+ if (copied)
+ cleanup_rbuf(sk, copied);
+ return copied;
+}
+
+/*
+ * This routine copies from a sock struct into the user buffer.
+ *
+ * Technical note: in 2.3 we work on _locked_ socket, so that
+ * tricks with *seq access order and skb->users are not required.
+ * Probably, code can be easily improved even more.
+ */
+
+int tcp_recvmsg(struct sock *sk, struct msghdr *msg,
+ int len, int nonblock, int flags, int *addr_len)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int copied = 0;
+ u32 peek_seq;
+ u32 *seq;
+ unsigned long used;
+ int err;
+ int target; /* Read at least this many bytes */
+ long timeo;
+ struct task_struct *user_recv = NULL;
+
+ lock_sock(sk);
+
+ TCP_CHECK_TIMER(sk);
+
+ err = -ENOTCONN;
+ if (sk->state == TCP_LISTEN)
+ goto out;
+
+ timeo = sock_rcvtimeo(sk, nonblock);
+
+ /* Urgent data needs to be handled specially. */
+ if (flags & MSG_OOB)
+ goto recv_urg;
+
+ seq = &tp->copied_seq;
+ if (flags & MSG_PEEK) {
+ peek_seq = tp->copied_seq;
+ seq = &peek_seq;
+ }
+
+ target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);
+
+ do {
+ struct sk_buff * skb;
+ u32 offset;
+
+ /* Are we at urgent data? Stop if we have read anything or have SIGURG pending. */
+ if (tp->urg_data && tp->urg_seq == *seq) {
+ if (copied)
+ break;
+ if (signal_pending(current)) {
+ copied = timeo ? sock_intr_errno(timeo) : -EAGAIN;
+ break;
+ }
+ }
+
+ /* Next get a buffer. */
+
+ skb = skb_peek(&sk->receive_queue);
+ do {
+ if (!skb)
+ break;
+
+ /* Now that we have two receive queues this
+ * shouldn't happen.
+ */
+ if (before(*seq, TCP_SKB_CB(skb)->seq)) {
+ printk(KERN_INFO "recvmsg bug: copied %X seq %X\n",
+ *seq, TCP_SKB_CB(skb)->seq);
+ break;
+ }
+ offset = *seq - TCP_SKB_CB(skb)->seq;
+ if (skb->h.th->syn)
+ offset--;
+ if (offset < skb->len)
+ goto found_ok_skb;
+ if (skb->h.th->fin)
+ goto found_fin_ok;
+ BUG_TRAP(flags&MSG_PEEK);
+ skb = skb->next;
+ } while (skb != (struct sk_buff *)&sk->receive_queue);
+
+ /* Well, if we have backlog, try to process it now yet. */
+
+ if (copied >= target && sk->backlog.tail == NULL)
+ break;
+
+ if (copied) {
+ if (sk->err ||
+ sk->state == TCP_CLOSE ||
+ (sk->shutdown & RCV_SHUTDOWN) ||
+ !timeo ||
+ signal_pending(current) ||
+ (flags & MSG_PEEK))
+ break;
+ } else {
+ if (sk->done)
+ break;
+
+ if (sk->err) {
+ copied = sock_error(sk);
+ break;
+ }
+
+ if (sk->shutdown & RCV_SHUTDOWN)
+ break;
+
+ if (sk->state == TCP_CLOSE) {
+ if (!sk->done) {
+ /* This occurs when user tries to read
+ * from never connected socket.
+ */
+ copied = -ENOTCONN;
+ break;
+ }
+ break;
+ }
+
+ if (!timeo) {
+ copied = -EAGAIN;
+ break;
+ }
+
+ if (signal_pending(current)) {
+ copied = sock_intr_errno(timeo);
+ break;
+ }
+ }
+
+ cleanup_rbuf(sk, copied);
+
+ if (tp->ucopy.task == user_recv) {
+ /* Install new reader */
+ if (user_recv == NULL && !(flags&(MSG_TRUNC|MSG_PEEK))) {
+ user_recv = current;
+ tp->ucopy.task = user_recv;
+ tp->ucopy.iov = msg->msg_iov;
+ }
+
+ tp->ucopy.len = len;
+
+ BUG_TRAP(tp->copied_seq == tp->rcv_nxt || (flags&(MSG_PEEK|MSG_TRUNC)));
+
+ /* Ugly... If prequeue is not empty, we have to
+ * process it before releasing socket, otherwise
+ * order will be broken at second iteration.
+ * More elegant solution is required!!!
+ *
+ * Look: we have the following (pseudo)queues:
+ *
+ * 1. packets in flight
+ * 2. backlog
+ * 3. prequeue
+ * 4. receive_queue
+ *
+ * Each queue can be processed only if the next ones
+ * are empty. At this point we have empty receive_queue.
+ * But prequeue _can_ be not empty after second iteration,
+ * when we jumped to start of loop because backlog
+ * processing added something to receive_queue.
+ * We cannot release_sock(), because backlog contains
+ * packets arrived _after_ prequeued ones.
+ *
+ * Shortly, algorithm is clear --- to process all
+ * the queues in order. We could make it more directly,
+ * requeueing packets from backlog to prequeue, if
+ * is not empty. It is more elegant, but eats cycles,
+ * unfortunately.
+ */
+ if (skb_queue_len(&tp->ucopy.prequeue))
+ goto do_prequeue;
+
+ /* __ Set realtime policy in scheduler __ */
+ }
+
+ if (copied >= target) {
+ /* Do not sleep, just process backlog. */
+ release_sock(sk);
+ lock_sock(sk);
+ } else {
+ timeo = tcp_data_wait(sk, timeo);
+ }
+
+ if (user_recv) {
+ int chunk;
+
+ /* __ Restore normal policy in scheduler __ */
+
+ if ((chunk = len - tp->ucopy.len) != 0) {
+ net_statistics[smp_processor_id()*2+1].TCPDirectCopyFromBacklog += chunk;
+ len -= chunk;
+ copied += chunk;
+ }
+
+ if (tp->rcv_nxt == tp->copied_seq &&
+ skb_queue_len(&tp->ucopy.prequeue)) {
+do_prequeue:
+ tcp_prequeue_process(sk);
+
+ if ((chunk = len - tp->ucopy.len) != 0) {
+ net_statistics[smp_processor_id()*2+1].TCPDirectCopyFromPrequeue += chunk;
+ len -= chunk;
+ copied += chunk;
+ }
+ }
+ }
+ if ((flags & MSG_PEEK) && peek_seq != tp->copied_seq) {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "TCP(%s:%d): Application bug, race in MSG_PEEK.\n",
+ current->comm, current->pid);
+ peek_seq = tp->copied_seq;
+ }
+ continue;
+
+ found_ok_skb:
+ /* Ok so how much can we use? */
+ used = skb->len - offset;
+ if (len < used)
+ used = len;
+
+ /* Do we have urgent data here? */
+ if (tp->urg_data) {
+ u32 urg_offset = tp->urg_seq - *seq;
+ if (urg_offset < used) {
+ if (!urg_offset) {
+ if (!sk->urginline) {
+ ++*seq;
+ offset++;
+ used--;
+ if (!used)
+ goto skip_copy;
+ }
+ } else
+ used = urg_offset;
+ }
+ }
+
+ if (!(flags&MSG_TRUNC)) {
+ err = skb_copy_datagram_iovec(skb, offset, msg->msg_iov, used);
+ if (err) {
+ /* Exception. Bailout! */
+ if (!copied)
+ copied = -EFAULT;
+ break;
+ }
+ }
+
+ *seq += used;
+ copied += used;
+ len -= used;
+
+ tcp_rcv_space_adjust(sk);
+
+skip_copy:
+ if (tp->urg_data && after(tp->copied_seq,tp->urg_seq)) {
+ tp->urg_data = 0;
+ tcp_fast_path_check(sk, tp);
+ }
+ if (used + offset < skb->len)
+ continue;
+
+ if (skb->h.th->fin)
+ goto found_fin_ok;
+ if (!(flags & MSG_PEEK))
+ tcp_eat_skb(sk, skb);
+ continue;
+
+ found_fin_ok:
+ /* Process the FIN. */
+ ++*seq;
+ if (!(flags & MSG_PEEK))
+ tcp_eat_skb(sk, skb);
+ break;
+ } while (len > 0);
+
+ if (user_recv) {
+ if (skb_queue_len(&tp->ucopy.prequeue)) {
+ int chunk;
+
+ tp->ucopy.len = copied > 0 ? len : 0;
+
+ tcp_prequeue_process(sk);
+
+ if (copied > 0 && (chunk = len - tp->ucopy.len) != 0) {
+ net_statistics[smp_processor_id()*2+1].TCPDirectCopyFromPrequeue += chunk;
+ len -= chunk;
+ copied += chunk;
+ }
+ }
+
+ tp->ucopy.task = NULL;
+ tp->ucopy.len = 0;
+ }
+
+ /* According to UNIX98, msg_name/msg_namelen are ignored
+ * on connected socket. I was just happy when found this 8) --ANK
+ */
+
+ /* Clean up data we have read: This will do ACK frames. */
+ cleanup_rbuf(sk, copied);
+
+ TCP_CHECK_TIMER(sk);
+ release_sock(sk);
+ return copied;
+
+out:
+ TCP_CHECK_TIMER(sk);
+ release_sock(sk);
+ return err;
+
+recv_urg:
+ err = tcp_recv_urg(sk, timeo, msg, len, flags, addr_len);
+ goto out;
+}
+
+/*
+ * State processing on a close. This implements the state shift for
+ * sending our FIN frame. Note that we only send a FIN for some
+ * states. A shutdown() may have already sent the FIN, or we may be
+ * closed.
+ */
+
+static unsigned char new_state[16] = {
+ /* current state: new state: action: */
+ /* (Invalid) */ TCP_CLOSE,
+ /* TCP_ESTABLISHED */ TCP_FIN_WAIT1 | TCP_ACTION_FIN,
+ /* TCP_SYN_SENT */ TCP_CLOSE,
+ /* TCP_SYN_RECV */ TCP_FIN_WAIT1 | TCP_ACTION_FIN,
+ /* TCP_FIN_WAIT1 */ TCP_FIN_WAIT1,
+ /* TCP_FIN_WAIT2 */ TCP_FIN_WAIT2,
+ /* TCP_TIME_WAIT */ TCP_CLOSE,
+ /* TCP_CLOSE */ TCP_CLOSE,
+ /* TCP_CLOSE_WAIT */ TCP_LAST_ACK | TCP_ACTION_FIN,
+ /* TCP_LAST_ACK */ TCP_LAST_ACK,
+ /* TCP_LISTEN */ TCP_CLOSE,
+ /* TCP_CLOSING */ TCP_CLOSING,
+};
+
+static int tcp_close_state(struct sock *sk)
+{
+ int next = (int) new_state[sk->state];
+ int ns = (next & TCP_STATE_MASK);
+
+ tcp_set_state(sk, ns);
+
+ return (next & TCP_ACTION_FIN);
+}
+
+/*
+ * Shutdown the sending side of a connection. Much like close except
+ * that we don't receive shut down or set sk->dead.
+ */
+
+void tcp_shutdown(struct sock *sk, int how)
+{
+ /* We need to grab some memory, and put together a FIN,
+ * and then put it into the queue to be sent.
+ * Tim MacKenzie(tym@dibbler.cs.monash.edu.au) 4 Dec '92.
+ */
+ if (!(how & SEND_SHUTDOWN))
+ return;
+
+ /* If we've already sent a FIN, or it's a closed state, skip this. */
+ if ((1 << sk->state) &
+ (TCPF_ESTABLISHED|TCPF_SYN_SENT|TCPF_SYN_RECV|TCPF_CLOSE_WAIT)) {
+ /* Clear out any half completed packets. FIN if needed. */
+ if (tcp_close_state(sk))
+ tcp_send_fin(sk);
+ }
+}
+
+
+/*
+ * Return 1 if we still have things to send in our buffers.
+ */
+
+static inline int closing(struct sock * sk)
+{
+ return ((1 << sk->state) & (TCPF_FIN_WAIT1|TCPF_CLOSING|TCPF_LAST_ACK));
+}
+
+static __inline__ void tcp_kill_sk_queues(struct sock *sk)
+{
+ /* First the read buffer. */
+ __skb_queue_purge(&sk->receive_queue);
+
+ /* Next, the error queue. */
+ __skb_queue_purge(&sk->error_queue);
+
+ /* Next, the write queue. */
+ BUG_TRAP(skb_queue_empty(&sk->write_queue));
+
+ /* Account for returned memory. */
+ tcp_mem_reclaim(sk);
+
+ BUG_TRAP(sk->wmem_queued == 0);
+ BUG_TRAP(sk->forward_alloc == 0);
+
+ /* It is _impossible_ for the backlog to contain anything
+ * when we get here. All user references to this socket
+ * have gone away, only the net layer knows can touch it.
+ */
+}
+
+/*
+ * At this point, there should be no process reference to this
+ * socket, and thus no user references at all. Therefore we
+ * can assume the socket waitqueue is inactive and nobody will
+ * try to jump onto it.
+ */
+void tcp_destroy_sock(struct sock *sk)
+{
+ BUG_TRAP(sk->state==TCP_CLOSE);
+ BUG_TRAP(sk->dead);
+
+ /* It cannot be in hash table! */
+ BUG_TRAP(sk->pprev==NULL);
+
+ /* If it has not 0 sk->num, it must be bound */
+ BUG_TRAP(!sk->num || sk->prev!=NULL);
+
+#ifdef TCP_DEBUG
+ if (sk->zapped) {
+ printk(KERN_DEBUG "TCP: double destroy sk=%p\n", sk);
+ sock_hold(sk);
+ }
+ sk->zapped = 1;
+#endif
+
+ sk->prot->destroy(sk);
+
+ tcp_kill_sk_queues(sk);
+
+#ifdef INET_REFCNT_DEBUG
+ if (atomic_read(&sk->refcnt) != 1) {
+ printk(KERN_DEBUG "Destruction TCP %p delayed, c=%d\n", sk, atomic_read(&sk->refcnt));
+ }
+#endif
+
+ atomic_dec(&tcp_orphan_count);
+ sock_put(sk);
+}
+
+void tcp_close(struct sock *sk, long timeout)
+{
+ struct sk_buff *skb;
+ int data_was_unread = 0;
+
+ lock_sock(sk);
+ sk->shutdown = SHUTDOWN_MASK;
+
+ if(sk->state == TCP_LISTEN) {
+ tcp_set_state(sk, TCP_CLOSE);
+
+ /* Special case. */
+ tcp_listen_stop(sk);
+
+ goto adjudge_to_death;
+ }
+
+ /* We need to flush the recv. buffs. We do this only on the
+ * descriptor close, not protocol-sourced closes, because the
+ * reader process may not have drained the data yet!
+ */
+ while((skb=__skb_dequeue(&sk->receive_queue))!=NULL) {
+ u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq - skb->h.th->fin;
+ data_was_unread += len;
+ __kfree_skb(skb);
+ }
+
+ tcp_mem_reclaim(sk);
+
+ /* As outlined in draft-ietf-tcpimpl-prob-03.txt, section
+ * 3.10, we send a RST here because data was lost. To
+ * witness the awful effects of the old behavior of always
+ * doing a FIN, run an older 2.1.x kernel or 2.0.x, start
+ * a bulk GET in an FTP client, suspend the process, wait
+ * for the client to advertise a zero window, then kill -9
+ * the FTP client, wheee... Note: timeout is always zero
+ * in such a case.
+ */
+ if(data_was_unread != 0) {
+ /* Unread data was tossed, zap the connection. */
+ NET_INC_STATS_USER(TCPAbortOnClose);
+ tcp_set_state(sk, TCP_CLOSE);
+ tcp_send_active_reset(sk, GFP_KERNEL);
+ } else if (sk->linger && sk->lingertime==0) {
+ /* Check zero linger _after_ checking for unread data. */
+ sk->prot->disconnect(sk, 0);
+ NET_INC_STATS_USER(TCPAbortOnData);
+ } else if (tcp_close_state(sk)) {
+ /* We FIN if the application ate all the data before
+ * zapping the connection.
+ */
+
+ /* RED-PEN. Formally speaking, we have broken TCP state
+ * machine. State transitions:
+ *
+ * TCP_ESTABLISHED -> TCP_FIN_WAIT1
+ * TCP_SYN_RECV -> TCP_FIN_WAIT1 (forget it, it's impossible)
+ * TCP_CLOSE_WAIT -> TCP_LAST_ACK
+ *
+ * are legal only when FIN has been sent (i.e. in window),
+ * rather than queued out of window. Purists blame.
+ *
+ * F.e. "RFC state" is ESTABLISHED,
+ * if Linux state is FIN-WAIT-1, but FIN is still not sent.
+ *
+ * The visible declinations are that sometimes
+ * we enter time-wait state, when it is not required really
+ * (harmless), do not send active resets, when they are
+ * required by specs (TCP_ESTABLISHED, TCP_CLOSE_WAIT, when
+ * they look as CLOSING or LAST_ACK for Linux)
+ * Probably, I missed some more holelets.
+ * --ANK
+ */
+ tcp_send_fin(sk);
+ }
+
+ if (timeout) {
+ struct task_struct *tsk = current;
+ DECLARE_WAITQUEUE(wait, current);
+
+ add_wait_queue(sk->sleep, &wait);
+
+ do {
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (!closing(sk))
+ break;
+ release_sock(sk);
+ timeout = schedule_timeout(timeout);
+ lock_sock(sk);
+ } while (!signal_pending(tsk) && timeout);
+
+ tsk->state = TASK_RUNNING;
+ remove_wait_queue(sk->sleep, &wait);
+ }
+
+adjudge_to_death:
+ /* It is the last release_sock in its life. It will remove backlog. */
+ release_sock(sk);
+
+
+ /* Now socket is owned by kernel and we acquire BH lock
+ to finish close. No need to check for user refs.
+ */
+ local_bh_disable();
+ bh_lock_sock(sk);
+ BUG_TRAP(sk->lock.users==0);
+
+ sock_hold(sk);
+ sock_orphan(sk);
+
+ /* This is a (useful) BSD violating of the RFC. There is a
+ * problem with TCP as specified in that the other end could
+ * keep a socket open forever with no application left this end.
+ * We use a 3 minute timeout (about the same as BSD) then kill
+ * our end. If they send after that then tough - BUT: long enough
+ * that we won't make the old 4*rto = almost no time - whoops
+ * reset mistake.
+ *
+ * Nope, it was not mistake. It is really desired behaviour
+ * f.e. on http servers, when such sockets are useless, but
+ * consume significant resources. Let's do it with special
+ * linger2 option. --ANK
+ */
+
+ if (sk->state == TCP_FIN_WAIT2) {
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ if (tp->linger2 < 0) {
+ tcp_set_state(sk, TCP_CLOSE);
+ tcp_send_active_reset(sk, GFP_ATOMIC);
+ NET_INC_STATS_BH(TCPAbortOnLinger);
+ } else {
+ int tmo = tcp_fin_time(tp);
+
+ if (tmo > TCP_TIMEWAIT_LEN) {
+ tcp_reset_keepalive_timer(sk, tcp_fin_time(tp));
+ } else {
+ atomic_inc(&tcp_orphan_count);
+ tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
+ goto out;
+ }
+ }
+ }
+ if (sk->state != TCP_CLOSE) {
+ tcp_mem_reclaim(sk);
+ if (atomic_read(&tcp_orphan_count) > sysctl_tcp_max_orphans ||
+ (sk->wmem_queued > SOCK_MIN_SNDBUF &&
+ atomic_read(&tcp_memory_allocated) > sysctl_tcp_mem[2])) {
+ if (net_ratelimit())
+ printk(KERN_INFO "TCP: too many of orphaned sockets\n");
+ tcp_set_state(sk, TCP_CLOSE);
+ tcp_send_active_reset(sk, GFP_ATOMIC);
+ NET_INC_STATS_BH(TCPAbortOnMemory);
+ }
+ }
+ atomic_inc(&tcp_orphan_count);
+
+ if (sk->state == TCP_CLOSE)
+ tcp_destroy_sock(sk);
+ /* Otherwise, socket is reprieved until protocol close. */
+
+out:
+ bh_unlock_sock(sk);
+ local_bh_enable();
+ sock_put(sk);
+}
+
+/* These states need RST on ABORT according to RFC793 */
+
+static inline int tcp_need_reset(int state)
+{
+ return ((1 << state) &
+ (TCPF_ESTABLISHED|TCPF_CLOSE_WAIT|TCPF_FIN_WAIT1|
+ TCPF_FIN_WAIT2|TCPF_SYN_RECV));
+}
+
+int tcp_disconnect(struct sock *sk, int flags)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ int old_state;
+ int err = 0;
+
+ old_state = sk->state;
+ if (old_state != TCP_CLOSE)
+ tcp_set_state(sk, TCP_CLOSE);
+
+ /* ABORT function of RFC793 */
+ if (old_state == TCP_LISTEN) {
+ tcp_listen_stop(sk);
+ } else if (tcp_need_reset(old_state) ||
+ (tp->snd_nxt != tp->write_seq &&
+ (1<<old_state)&(TCPF_CLOSING|TCPF_LAST_ACK))) {
+ /* The last check adjusts for discrepance of Linux wrt. RFC
+ * states
+ */
+ tcp_send_active_reset(sk, gfp_any());
+ sk->err = ECONNRESET;
+ } else if (old_state == TCP_SYN_SENT)
+ sk->err = ECONNRESET;
+
+ tcp_clear_xmit_timers(sk);
+ __skb_queue_purge(&sk->receive_queue);
+ tcp_writequeue_purge(sk);
+ __skb_queue_purge(&tp->out_of_order_queue);
+
+ sk->dport = 0;
+
+ if (!(sk->userlocks&SOCK_BINDADDR_LOCK)) {
+ sk->rcv_saddr = 0;
+ sk->saddr = 0;
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ memset(&sk->net_pinfo.af_inet6.saddr, 0, 16);
+ memset(&sk->net_pinfo.af_inet6.rcv_saddr, 0, 16);
+#endif
+ }
+
+ sk->shutdown = 0;
+ sk->done = 0;
+ tp->srtt = 0;
+ if ((tp->write_seq += tp->max_window+2) == 0)
+ tp->write_seq = 1;
+ tp->backoff = 0;
+ tp->snd_cwnd = 2;
+ tp->probes_out = 0;
+ tp->packets_out = 0;
+ tp->snd_ssthresh = 0x7fffffff;
+ tp->snd_cwnd_cnt = 0;
+ tcp_set_ca_state(tp, TCP_CA_Open);
+ tcp_clear_retrans(tp);
+ tcp_delack_init(tp);
+ tp->send_head = NULL;
+ tp->saw_tstamp = 0;
+ tcp_sack_reset(tp);
+ __sk_dst_reset(sk);
+
+ BUG_TRAP(!sk->num || sk->prev);
+
+ sk->error_report(sk);
+ return err;
+}
+
+/*
+ * Wait for an incoming connection, avoid race
+ * conditions. This must be called with the socket locked.
+ */
+static int wait_for_connect(struct sock * sk, long timeo)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ int err;
+
+ /*
+ * True wake-one mechanism for incoming connections: only
+ * one process gets woken up, not the 'whole herd'.
+ * Since we do not 'race & poll' for established sockets
+ * anymore, the common case will execute the loop only once.
+ *
+ * Subtle issue: "add_wait_queue_exclusive()" will be added
+ * after any current non-exclusive waiters, and we know that
+ * it will always _stay_ after any new non-exclusive waiters
+ * because all non-exclusive waiters are added at the
+ * beginning of the wait-queue. As such, it's ok to "drop"
+ * our exclusiveness temporarily when we get woken up without
+ * having to remove and re-insert us on the wait queue.
+ */
+ add_wait_queue_exclusive(sk->sleep, &wait);
+ for (;;) {
+ current->state = TASK_INTERRUPTIBLE;
+ release_sock(sk);
+ if (sk->tp_pinfo.af_tcp.accept_queue == NULL)
+ timeo = schedule_timeout(timeo);
+ lock_sock(sk);
+ err = 0;
+ if (sk->tp_pinfo.af_tcp.accept_queue)
+ break;
+ err = -EINVAL;
+ if (sk->state != TCP_LISTEN)
+ break;
+ err = sock_intr_errno(timeo);
+ if (signal_pending(current))
+ break;
+ err = -EAGAIN;
+ if (!timeo)
+ break;
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(sk->sleep, &wait);
+ return err;
+}
+
+/*
+ * This will accept the next outstanding connection.
+ */
+
+struct sock *tcp_accept(struct sock *sk, int flags, int *err)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ struct open_request *req;
+ struct sock *newsk;
+ int error;
+
+ lock_sock(sk);
+
+ /* We need to make sure that this socket is listening,
+ * and that it has something pending.
+ */
+ error = -EINVAL;
+ if (sk->state != TCP_LISTEN)
+ goto out;
+
+ /* Find already established connection */
+ if (!tp->accept_queue) {
+ long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
+
+ /* If this is a non blocking socket don't sleep */
+ error = -EAGAIN;
+ if (!timeo)
+ goto out;
+
+ error = wait_for_connect(sk, timeo);
+ if (error)
+ goto out;
+ }
+
+ req = tp->accept_queue;
+ if ((tp->accept_queue = req->dl_next) == NULL)
+ tp->accept_queue_tail = NULL;
+
+ newsk = req->sk;
+ tcp_acceptq_removed(sk);
+ tcp_openreq_fastfree(req);
+ BUG_TRAP(newsk->state != TCP_SYN_RECV);
+ release_sock(sk);
+ return newsk;
+
+out:
+ release_sock(sk);
+ *err = error;
+ return NULL;
+}
+
+/*
+ * Socket option code for TCP.
+ */
+
+int tcp_setsockopt(struct sock *sk, int level, int optname, char *optval,
+ int optlen)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int val;
+ int err = 0;
+
+ if (level != SOL_TCP)
+ return tp->af_specific->setsockopt(sk, level, optname,
+ optval, optlen);
+
+ if(optlen<sizeof(int))
+ return -EINVAL;
+
+ if (get_user(val, (int *)optval))
+ return -EFAULT;
+
+ lock_sock(sk);
+
+ switch(optname) {
+ case TCP_MAXSEG:
+ /* values greater than interface MTU won't take effect. however at
+ * the point when this call is done we typically don't yet know
+ * which interface is going to be used
+ */
+ if(val < 8 || val > MAX_TCP_WINDOW) {
+ err = -EINVAL;
+ break;
+ }
+ tp->user_mss = val;
+ break;
+
+ case TCP_NODELAY:
+ /* You cannot try to use this and TCP_CORK in
+ * tandem, so let the user know.
+ */
+ if (tp->nonagle == 2) {
+ err = -EINVAL;
+ break;
+ }
+ tp->nonagle = (val == 0) ? 0 : 1;
+ if (val)
+ tcp_push_pending_frames(sk, tp);
+ break;
+
+ case TCP_CORK:
+ /* When set indicates to always queue non-full frames.
+ * Later the user clears this option and we transmit
+ * any pending partial frames in the queue. This is
+ * meant to be used alongside sendfile() to get properly
+ * filled frames when the user (for example) must write
+ * out headers with a write() call first and then use
+ * sendfile to send out the data parts.
+ *
+ * You cannot try to use TCP_NODELAY and this mechanism
+ * at the same time, so let the user know.
+ */
+ if (tp->nonagle == 1) {
+ err = -EINVAL;
+ break;
+ }
+ if (val != 0) {
+ tp->nonagle = 2;
+ } else {
+ tp->nonagle = 0;
+
+ tcp_push_pending_frames(sk, tp);
+ }
+ break;
+
+ case TCP_KEEPIDLE:
+ if (val < 1 || val > MAX_TCP_KEEPIDLE)
+ err = -EINVAL;
+ else {
+ tp->keepalive_time = val * HZ;
+ if (sk->keepopen && !((1<<sk->state)&(TCPF_CLOSE|TCPF_LISTEN))) {
+ __u32 elapsed = tcp_time_stamp - tp->rcv_tstamp;
+ if (tp->keepalive_time > elapsed)
+ elapsed = tp->keepalive_time - elapsed;
+ else
+ elapsed = 0;
+ tcp_reset_keepalive_timer(sk, elapsed);
+ }
+ }
+ break;
+ case TCP_KEEPINTVL:
+ if (val < 1 || val > MAX_TCP_KEEPINTVL)
+ err = -EINVAL;
+ else
+ tp->keepalive_intvl = val * HZ;
+ break;
+ case TCP_KEEPCNT:
+ if (val < 1 || val > MAX_TCP_KEEPCNT)
+ err = -EINVAL;
+ else
+ tp->keepalive_probes = val;
+ break;
+ case TCP_SYNCNT:
+ if (val < 1 || val > MAX_TCP_SYNCNT)
+ err = -EINVAL;
+ else
+ tp->syn_retries = val;
+ break;
+
+ case TCP_LINGER2:
+ if (val < 0)
+ tp->linger2 = -1;
+ else if (val > sysctl_tcp_fin_timeout/HZ)
+ tp->linger2 = 0;
+ else
+ tp->linger2 = val*HZ;
+ break;
+
+ case TCP_DEFER_ACCEPT:
+ tp->defer_accept = 0;
+ if (val > 0) {
+ /* Translate value in seconds to number of retransmits */
+ while (tp->defer_accept < 32 && val > ((TCP_TIMEOUT_INIT/HZ)<<tp->defer_accept))
+ tp->defer_accept++;
+ tp->defer_accept++;
+ }
+ break;
+
+ case TCP_WINDOW_CLAMP:
+ if (val==0) {
+ if (sk->state != TCP_CLOSE) {
+ err = -EINVAL;
+ break;
+ }
+ tp->window_clamp = 0;
+ } else {
+ tp->window_clamp = val<SOCK_MIN_RCVBUF/2 ?
+ SOCK_MIN_RCVBUF/2 : val;
+ }
+ break;
+
+ case TCP_QUICKACK:
+ if (!val) {
+ tp->ack.pingpong = 1;
+ } else {
+ tp->ack.pingpong = 0;
+ if ((1<<sk->state)&(TCPF_ESTABLISHED|TCPF_CLOSE_WAIT) &&
+ tcp_ack_scheduled(tp)) {
+ tp->ack.pending |= TCP_ACK_PUSHED;
+ cleanup_rbuf(sk, 1);
+ if (!(val & 1))
+ tp->ack.pingpong = 1;
+ }
+ }
+ break;
+
+ default:
+ err = -ENOPROTOOPT;
+ break;
+ };
+ release_sock(sk);
+ return err;
+}
+
+int tcp_getsockopt(struct sock *sk, int level, int optname, char *optval,
+ int *optlen)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int val, len;
+
+ if(level != SOL_TCP)
+ return tp->af_specific->getsockopt(sk, level, optname,
+ optval, optlen);
+
+ if(get_user(len,optlen))
+ return -EFAULT;
+
+ len = min_t(unsigned int, len, sizeof(int));
+
+ if(len < 0)
+ return -EINVAL;
+
+ switch(optname) {
+ case TCP_MAXSEG:
+ val = tp->mss_cache;
+ if (val == 0 && ((1<<sk->state)&(TCPF_CLOSE|TCPF_LISTEN)))
+ val = tp->user_mss;
+ break;
+ case TCP_NODELAY:
+ val = (tp->nonagle == 1);
+ break;
+ case TCP_CORK:
+ val = (tp->nonagle == 2);
+ break;
+ case TCP_KEEPIDLE:
+ val = (tp->keepalive_time ? : sysctl_tcp_keepalive_time)/HZ;
+ break;
+ case TCP_KEEPINTVL:
+ val = (tp->keepalive_intvl ? : sysctl_tcp_keepalive_intvl)/HZ;
+ break;
+ case TCP_KEEPCNT:
+ val = tp->keepalive_probes ? : sysctl_tcp_keepalive_probes;
+ break;
+ case TCP_SYNCNT:
+ val = tp->syn_retries ? : sysctl_tcp_syn_retries;
+ break;
+ case TCP_LINGER2:
+ val = tp->linger2;
+ if (val >= 0)
+ val = (val ? : sysctl_tcp_fin_timeout)/HZ;
+ break;
+ case TCP_DEFER_ACCEPT:
+ val = tp->defer_accept == 0 ? 0 : ((TCP_TIMEOUT_INIT/HZ)<<(tp->defer_accept-1));
+ break;
+ case TCP_WINDOW_CLAMP:
+ val = tp->window_clamp;
+ break;
+ case TCP_INFO:
+ {
+ struct tcp_info info;
+ u32 now = tcp_time_stamp;
+
+ if(get_user(len,optlen))
+ return -EFAULT;
+ info.tcpi_state = sk->state;
+ info.tcpi_ca_state = tp->ca_state;
+ info.tcpi_retransmits = tp->retransmits;
+ info.tcpi_probes = tp->probes_out;
+ info.tcpi_backoff = tp->backoff;
+ info.tcpi_options = 0;
+ if (tp->tstamp_ok)
+ info.tcpi_options |= TCPI_OPT_TIMESTAMPS;
+ if (tp->sack_ok)
+ info.tcpi_options |= TCPI_OPT_SACK;
+ if (tp->wscale_ok) {
+ info.tcpi_options |= TCPI_OPT_WSCALE;
+ info.tcpi_snd_wscale = tp->snd_wscale;
+ info.tcpi_rcv_wscale = tp->rcv_wscale;
+ } else {
+ info.tcpi_snd_wscale = 0;
+ info.tcpi_rcv_wscale = 0;
+ }
+ if (tp->ecn_flags&TCP_ECN_OK)
+ info.tcpi_options |= TCPI_OPT_ECN;
+
+ info.tcpi_rto = (1000000*tp->rto)/HZ;
+ info.tcpi_ato = (1000000*tp->ack.ato)/HZ;
+ info.tcpi_snd_mss = tp->mss_cache;
+ info.tcpi_rcv_mss = tp->ack.rcv_mss;
+
+ info.tcpi_unacked = tp->packets_out;
+ info.tcpi_sacked = tp->sacked_out;
+ info.tcpi_lost = tp->lost_out;
+ info.tcpi_retrans = tp->retrans_out;
+ info.tcpi_fackets = tp->fackets_out;
+
+ info.tcpi_last_data_sent = ((now - tp->lsndtime)*1000)/HZ;
+ info.tcpi_last_ack_sent = 0;
+ info.tcpi_last_data_recv = ((now - tp->ack.lrcvtime)*1000)/HZ;
+ info.tcpi_last_ack_recv = ((now - tp->rcv_tstamp)*1000)/HZ;
+
+ info.tcpi_pmtu = tp->pmtu_cookie;
+ info.tcpi_rcv_ssthresh = tp->rcv_ssthresh;
+ info.tcpi_rtt = ((1000000*tp->srtt)/HZ)>>3;
+ info.tcpi_rttvar = ((1000000*tp->mdev)/HZ)>>2;
+ info.tcpi_snd_ssthresh = tp->snd_ssthresh;
+ info.tcpi_snd_cwnd = tp->snd_cwnd;
+ info.tcpi_advmss = tp->advmss;
+ info.tcpi_reordering = tp->reordering;
+
+ len = min_t(unsigned int, len, sizeof(info));
+ if(put_user(len, optlen))
+ return -EFAULT;
+ if(copy_to_user(optval, &info,len))
+ return -EFAULT;
+ return 0;
+ }
+ case TCP_QUICKACK:
+ val = !tp->ack.pingpong;
+ break;
+ default:
+ return -ENOPROTOOPT;
+ };
+
+ if(put_user(len, optlen))
+ return -EFAULT;
+ if(copy_to_user(optval, &val,len))
+ return -EFAULT;
+ return 0;
+}
+
+
+extern void __skb_cb_too_small_for_tcp(int, int);
+extern void tcpdiag_init(void);
+
+void __init tcp_init(void)
+{
+ struct sk_buff *skb = NULL;
+ unsigned long goal;
+ int order, i;
+
+ if(sizeof(struct tcp_skb_cb) > sizeof(skb->cb))
+ __skb_cb_too_small_for_tcp(sizeof(struct tcp_skb_cb),
+ sizeof(skb->cb));
+
+ tcp_openreq_cachep = kmem_cache_create("tcp_open_request",
+ sizeof(struct open_request),
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+ if(!tcp_openreq_cachep)
+ panic("tcp_init: Cannot alloc open_request cache.");
+
+ tcp_bucket_cachep = kmem_cache_create("tcp_bind_bucket",
+ sizeof(struct tcp_bind_bucket),
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+ if(!tcp_bucket_cachep)
+ panic("tcp_init: Cannot alloc tcp_bind_bucket cache.");
+
+ tcp_timewait_cachep = kmem_cache_create("tcp_tw_bucket",
+ sizeof(struct tcp_tw_bucket),
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+ if(!tcp_timewait_cachep)
+ panic("tcp_init: Cannot alloc tcp_tw_bucket cache.");
+
+ /* Size and allocate the main established and bind bucket
+ * hash tables.
+ *
+ * The methodology is similar to that of the buffer cache.
+ */
+ if (num_physpages >= (128 * 1024))
+ goal = num_physpages >> (21 - PAGE_SHIFT);
+ else
+ goal = num_physpages >> (23 - PAGE_SHIFT);
+
+ for(order = 0; (1UL << order) < goal; order++)
+ ;
+ do {
+ tcp_ehash_size = (1UL << order) * PAGE_SIZE /
+ sizeof(struct tcp_ehash_bucket);
+ tcp_ehash_size >>= 1;
+ while (tcp_ehash_size & (tcp_ehash_size-1))
+ tcp_ehash_size--;
+ tcp_ehash = (struct tcp_ehash_bucket *)
+ __get_free_pages(GFP_ATOMIC, order);
+ } while (tcp_ehash == NULL && --order > 0);
+
+ if (!tcp_ehash)
+ panic("Failed to allocate TCP established hash table\n");
+ for (i = 0; i < (tcp_ehash_size<<1); i++) {
+ tcp_ehash[i].lock = RW_LOCK_UNLOCKED;
+ tcp_ehash[i].chain = NULL;
+ }
+
+ do {
+ tcp_bhash_size = (1UL << order) * PAGE_SIZE /
+ sizeof(struct tcp_bind_hashbucket);
+ if ((tcp_bhash_size > (64 * 1024)) && order > 0)
+ continue;
+ tcp_bhash = (struct tcp_bind_hashbucket *)
+ __get_free_pages(GFP_ATOMIC, order);
+ } while (tcp_bhash == NULL && --order >= 0);
+
+ if (!tcp_bhash)
+ panic("Failed to allocate TCP bind hash table\n");
+ for (i = 0; i < tcp_bhash_size; i++) {
+ tcp_bhash[i].lock = SPIN_LOCK_UNLOCKED;
+ tcp_bhash[i].chain = NULL;
+ }
+
+ /* Try to be a bit smarter and adjust defaults depending
+ * on available memory.
+ */
+ if (order > 4) {
+ sysctl_local_port_range[0] = 32768;
+ sysctl_local_port_range[1] = 61000;
+ sysctl_tcp_max_tw_buckets = 180000;
+ sysctl_tcp_max_orphans = 4096<<(order-4);
+ sysctl_max_syn_backlog = 1024;
+ } else if (order < 3) {
+ sysctl_local_port_range[0] = 1024*(3-order);
+ sysctl_tcp_max_tw_buckets >>= (3-order);
+ sysctl_tcp_max_orphans >>= (3-order);
+ sysctl_max_syn_backlog = 128;
+ }
+ tcp_port_rover = sysctl_local_port_range[0] - 1;
+
+ sysctl_tcp_mem[0] = 768<<order;
+ sysctl_tcp_mem[1] = 1024<<order;
+ sysctl_tcp_mem[2] = 1536<<order;
+
+ if (order < 3) {
+ sysctl_tcp_wmem[2] = 64*1024;
+ sysctl_tcp_rmem[0] = PAGE_SIZE;
+ sysctl_tcp_rmem[1] = 43689;
+ sysctl_tcp_rmem[2] = 2*43689;
+ }
+
+ printk(KERN_INFO "TCP: Hash tables configured (established %d bind %d)\n",
+ tcp_ehash_size<<1, tcp_bhash_size);
+
+ (void) tcp_mib_init();
+ tcpdiag_init();
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/tcp_diag.c b/uClinux-2.4.31-uc0/net/ipv4/tcp_diag.c
new file mode 100644
index 0000000..2631da3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/tcp_diag.c
@@ -0,0 +1,630 @@
+/*
+ * tcp_diag.c Module for monitoring TCP sockets.
+ *
+ * Version: $Id: tcp_diag.c,v 1.2 2001/11/05 09:42:22 davem Exp $
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/random.h>
+#include <linux/cache.h>
+#include <linux/init.h>
+
+#include <net/icmp.h>
+#include <net/tcp.h>
+#include <net/ipv6.h>
+#include <net/inet_common.h>
+
+#include <linux/inet.h>
+#include <linux/stddef.h>
+
+#include <linux/tcp_diag.h>
+
+static struct sock *tcpnl;
+
+
+#define TCPDIAG_PUT(skb, attrtype, attrlen) \
+({ int rtalen = RTA_LENGTH(attrlen); \
+ struct rtattr *rta; \
+ if (skb_tailroom(skb) < RTA_ALIGN(rtalen)) goto nlmsg_failure; \
+ rta = (void*)__skb_put(skb, RTA_ALIGN(rtalen)); \
+ rta->rta_type = attrtype; \
+ rta->rta_len = rtalen; \
+ RTA_DATA(rta); })
+
+static int tcpdiag_fill(struct sk_buff *skb, struct sock *sk,
+ int ext, u32 pid, u32 seq)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ struct tcpdiagmsg *r;
+ struct nlmsghdr *nlh;
+ struct tcp_info *info = NULL;
+ struct tcpdiag_meminfo *minfo = NULL;
+ struct tcpvegas_info *vinfo = NULL;
+ unsigned char *b = skb->tail;
+
+ nlh = NLMSG_PUT(skb, pid, seq, TCPDIAG_GETSOCK, sizeof(*r));
+ r = NLMSG_DATA(nlh);
+ if (sk->state != TCP_TIME_WAIT) {
+ if (ext & (1<<(TCPDIAG_MEMINFO-1)))
+ minfo = TCPDIAG_PUT(skb, TCPDIAG_MEMINFO, sizeof(*minfo));
+ if (ext & (1<<(TCPDIAG_INFO-1)))
+ info = TCPDIAG_PUT(skb, TCPDIAG_INFO, sizeof(*info));
+
+ if ((tcp_is_westwood(tp) || tcp_is_vegas(tp))
+ && (ext & (1<<(TCPDIAG_VEGASINFO-1))))
+ vinfo = TCPDIAG_PUT(skb, TCPDIAG_VEGASINFO, sizeof(*vinfo));
+ }
+ r->tcpdiag_family = sk->family;
+ r->tcpdiag_state = sk->state;
+ r->tcpdiag_timer = 0;
+ r->tcpdiag_retrans = 0;
+
+ r->id.tcpdiag_sport = sk->sport;
+ r->id.tcpdiag_dport = sk->dport;
+ r->id.tcpdiag_src[0] = sk->rcv_saddr;
+ r->id.tcpdiag_dst[0] = sk->daddr;
+ r->id.tcpdiag_if = sk->bound_dev_if;
+ r->id.tcpdiag_cookie[0] = (u32)(unsigned long)sk;
+ r->id.tcpdiag_cookie[1] = (u32)(((unsigned long)sk >> 31) >> 1);
+
+ if (r->tcpdiag_state == TCP_TIME_WAIT) {
+ struct tcp_tw_bucket *tw = (struct tcp_tw_bucket*)sk;
+ long tmo = tw->ttd - jiffies;
+ if (tmo < 0)
+ tmo = 0;
+
+ r->tcpdiag_state = tw->substate;
+ r->tcpdiag_timer = 3;
+ r->tcpdiag_expires = (tmo*1000+HZ-1)/HZ;
+ r->tcpdiag_rqueue = 0;
+ r->tcpdiag_wqueue = 0;
+ r->tcpdiag_uid = 0;
+ r->tcpdiag_inode = 0;
+#ifdef CONFIG_IPV6
+ if (r->tcpdiag_family == AF_INET6) {
+ memcpy(r->id.tcpdiag_src, &tw->v6_rcv_saddr, 16);
+ memcpy(r->id.tcpdiag_dst, &tw->v6_daddr, 16);
+ }
+#endif
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+ }
+
+#ifdef CONFIG_IPV6
+ if (r->tcpdiag_family == AF_INET6) {
+ memcpy(r->id.tcpdiag_src, &sk->net_pinfo.af_inet6.rcv_saddr, 16);
+ memcpy(r->id.tcpdiag_dst, &sk->net_pinfo.af_inet6.daddr, 16);
+ }
+#endif
+
+#define EXPIRES_IN_MS(tmo) ((tmo-jiffies)*1000+HZ-1)/HZ
+
+ if (tp->pending == TCP_TIME_RETRANS) {
+ r->tcpdiag_timer = 1;
+ r->tcpdiag_retrans = tp->retransmits;
+ r->tcpdiag_expires = EXPIRES_IN_MS(tp->timeout);
+ } else if (tp->pending == TCP_TIME_PROBE0) {
+ r->tcpdiag_timer = 4;
+ r->tcpdiag_retrans = tp->probes_out;
+ r->tcpdiag_expires = EXPIRES_IN_MS(tp->timeout);
+ } else if (timer_pending(&sk->timer)) {
+ r->tcpdiag_timer = 2;
+ r->tcpdiag_retrans = tp->probes_out;
+ r->tcpdiag_expires = EXPIRES_IN_MS(sk->timer.expires);
+ } else {
+ r->tcpdiag_timer = 0;
+ r->tcpdiag_expires = 0;
+ }
+#undef EXPIRES_IN_MS
+
+ r->tcpdiag_rqueue = tp->rcv_nxt - tp->copied_seq;
+ r->tcpdiag_wqueue = tp->write_seq - tp->snd_una;
+ r->tcpdiag_uid = sock_i_uid(sk);
+ r->tcpdiag_inode = sock_i_ino(sk);
+
+ if (minfo) {
+ minfo->tcpdiag_rmem = atomic_read(&sk->rmem_alloc);
+ minfo->tcpdiag_wmem = sk->wmem_queued;
+ minfo->tcpdiag_fmem = sk->forward_alloc;
+ minfo->tcpdiag_tmem = atomic_read(&sk->wmem_alloc);
+ }
+
+ if (info) {
+ u32 now = tcp_time_stamp;
+
+ info->tcpi_state = sk->state;
+ info->tcpi_ca_state = tp->ca_state;
+ info->tcpi_retransmits = tp->retransmits;
+ info->tcpi_probes = tp->probes_out;
+ info->tcpi_backoff = tp->backoff;
+ info->tcpi_options = 0;
+ if (tp->tstamp_ok)
+ info->tcpi_options |= TCPI_OPT_TIMESTAMPS;
+ if (tp->sack_ok)
+ info->tcpi_options |= TCPI_OPT_SACK;
+ if (tp->wscale_ok) {
+ info->tcpi_options |= TCPI_OPT_WSCALE;
+ info->tcpi_snd_wscale = tp->snd_wscale;
+ info->tcpi_rcv_wscale = tp->rcv_wscale;
+ } else {
+ info->tcpi_snd_wscale = 0;
+ info->tcpi_rcv_wscale = 0;
+ }
+#ifdef CONFIG_INET_ECN
+ if (tp->ecn_flags&TCP_ECN_OK)
+ info->tcpi_options |= TCPI_OPT_ECN;
+#endif
+
+ info->tcpi_rto = (1000000*tp->rto)/HZ;
+ info->tcpi_ato = (1000000*tp->ack.ato)/HZ;
+ info->tcpi_snd_mss = tp->mss_cache;
+ info->tcpi_rcv_mss = tp->ack.rcv_mss;
+
+ info->tcpi_unacked = tp->packets_out;
+ info->tcpi_sacked = tp->sacked_out;
+ info->tcpi_lost = tp->lost_out;
+ info->tcpi_retrans = tp->retrans_out;
+ info->tcpi_fackets = tp->fackets_out;
+
+ info->tcpi_last_data_sent = ((now - tp->lsndtime)*1000)/HZ;
+ info->tcpi_last_ack_sent = 0;
+ info->tcpi_last_data_recv = ((now - tp->ack.lrcvtime)*1000)/HZ;
+ info->tcpi_last_ack_recv = ((now - tp->rcv_tstamp)*1000)/HZ;
+
+ info->tcpi_pmtu = tp->pmtu_cookie;
+ info->tcpi_rcv_ssthresh = tp->rcv_ssthresh;
+ info->tcpi_rtt = ((1000000*tp->srtt)/HZ)>>3;
+ info->tcpi_rttvar = ((1000000*tp->mdev)/HZ)>>2;
+ info->tcpi_snd_ssthresh = tp->snd_ssthresh;
+ info->tcpi_snd_cwnd = tp->snd_cwnd;
+ info->tcpi_advmss = tp->advmss;
+ info->tcpi_reordering = tp->reordering;
+ }
+
+ if (vinfo) {
+ if (tcp_is_vegas(tp)) {
+ vinfo->tcpv_enabled = tp->vegas.doing_vegas_now;
+ vinfo->tcpv_rttcnt = tp->vegas.cntRTT;
+ vinfo->tcpv_rtt = (1000000*tp->vegas.baseRTT)/HZ;
+ vinfo->tcpv_minrtt = (1000000*tp->vegas.minRTT)/HZ;
+ } else {
+ vinfo->tcpv_enabled = 0;
+ vinfo->tcpv_rttcnt = 0;
+ vinfo->tcpv_rtt = (1000000*tp->westwood.rtt)/HZ;
+ vinfo->tcpv_minrtt = (1000000*tp->westwood.rtt_min)/HZ;
+ }
+ }
+
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+extern struct sock *tcp_v4_lookup(u32 saddr, u16 sport, u32 daddr, u16 dport, int dif);
+#ifdef CONFIG_IPV6
+extern struct sock *tcp_v6_lookup(struct in6_addr *saddr, u16 sport,
+ struct in6_addr *daddr, u16 dport,
+ int dif);
+#endif
+
+static int tcpdiag_get_exact(struct sk_buff *in_skb, struct nlmsghdr *nlh)
+{
+ int err;
+ struct sock *sk;
+ struct tcpdiagreq *req = NLMSG_DATA(nlh);
+ struct sk_buff *rep;
+
+ if (req->tcpdiag_family == AF_INET) {
+ sk = tcp_v4_lookup(req->id.tcpdiag_dst[0], req->id.tcpdiag_dport,
+ req->id.tcpdiag_src[0], req->id.tcpdiag_sport,
+ req->id.tcpdiag_if);
+ }
+#ifdef CONFIG_IPV6
+ else if (req->tcpdiag_family == AF_INET6) {
+ sk = tcp_v6_lookup((struct in6_addr*)req->id.tcpdiag_dst, req->id.tcpdiag_dport,
+ (struct in6_addr*)req->id.tcpdiag_src, req->id.tcpdiag_sport,
+ req->id.tcpdiag_if);
+ }
+#endif
+ else {
+ return -EINVAL;
+ }
+
+ if (sk == NULL)
+ return -ENOENT;
+
+ err = -ESTALE;
+ if ((req->id.tcpdiag_cookie[0] != TCPDIAG_NOCOOKIE ||
+ req->id.tcpdiag_cookie[1] != TCPDIAG_NOCOOKIE) &&
+ ((u32)(unsigned long)sk != req->id.tcpdiag_cookie[0] ||
+ (u32)((((unsigned long)sk) >> 31) >> 1) != req->id.tcpdiag_cookie[1]))
+ goto out;
+
+ err = -ENOMEM;
+ rep = alloc_skb(NLMSG_SPACE(sizeof(struct tcpdiagmsg)+
+ sizeof(struct tcpdiag_meminfo)+
+ sizeof(struct tcp_info)+64), GFP_KERNEL);
+ if (!rep)
+ goto out;
+
+ if (tcpdiag_fill(rep, sk, req->tcpdiag_ext,
+ NETLINK_CB(in_skb).pid,
+ nlh->nlmsg_seq) <= 0)
+ BUG();
+
+ err = netlink_unicast(tcpnl, rep, NETLINK_CB(in_skb).pid, MSG_DONTWAIT);
+ if (err > 0)
+ err = 0;
+
+out:
+ if (sk) {
+ if (sk->state == TCP_TIME_WAIT)
+ tcp_tw_put((struct tcp_tw_bucket*)sk);
+ else
+ sock_put(sk);
+ }
+ return err;
+}
+
+int bitstring_match(u32 *a1, u32 *a2, int bits)
+{
+ int words = bits >> 5;
+
+ bits &= 0x1f;
+
+ if (words) {
+ if (memcmp(a1, a2, words << 2))
+ return 0;
+ }
+ if (bits) {
+ __u32 w1, w2;
+ __u32 mask;
+
+ w1 = a1[words];
+ w2 = a2[words];
+
+ mask = htonl((0xffffffff) << (32 - bits));
+
+ if ((w1 ^ w2) & mask)
+ return 0;
+ }
+
+ return 1;
+}
+
+
+int tcpdiag_bc_run(char *bc, int len, struct sock *sk)
+{
+ while (len > 0) {
+ int yes = 1;
+ struct tcpdiag_bc_op *op = (struct tcpdiag_bc_op*)bc;
+
+ switch (op->code) {
+ case TCPDIAG_BC_NOP:
+ break;
+ case TCPDIAG_BC_JMP:
+ yes = 0;
+ break;
+ case TCPDIAG_BC_S_GE:
+ yes = (sk->num >= op[1].no);
+ break;
+ case TCPDIAG_BC_S_LE:
+ yes = (sk->num <= op[1].no);
+ break;
+ case TCPDIAG_BC_D_GE:
+ yes = (ntohs(sk->dport) >= op[1].no);
+ break;
+ case TCPDIAG_BC_D_LE:
+ yes = (ntohs(sk->dport) <= op[1].no);
+ break;
+ case TCPDIAG_BC_AUTO:
+ yes = !(sk->userlocks&SOCK_BINDPORT_LOCK);
+ break;
+ case TCPDIAG_BC_S_COND:
+ case TCPDIAG_BC_D_COND:
+ {
+ struct tcpdiag_hostcond *cond = (struct tcpdiag_hostcond*)(op+1);
+ u32 *addr;
+
+ if (cond->port != -1 &&
+ cond->port != (op->code == TCPDIAG_BC_S_COND ? sk->num : ntohs(sk->dport))) {
+ yes = 0;
+ break;
+ }
+
+ if (cond->prefix_len == 0)
+ break;
+
+#ifdef CONFIG_IPV6
+ if (sk->family == AF_INET6) {
+ if (op->code == TCPDIAG_BC_S_COND)
+ addr = (u32*)&sk->net_pinfo.af_inet6.rcv_saddr;
+ else
+ addr = (u32*)&sk->net_pinfo.af_inet6.daddr;
+ } else
+#endif
+ {
+ if (op->code == TCPDIAG_BC_S_COND)
+ addr = &sk->rcv_saddr;
+ else
+ addr = &sk->daddr;
+ }
+
+ if (bitstring_match(addr, cond->addr, cond->prefix_len))
+ break;
+ if (sk->family == AF_INET6 && cond->family == AF_INET) {
+ if (addr[0] == 0 && addr[1] == 0 &&
+ addr[2] == htonl(0xffff) &&
+ bitstring_match(addr+3, cond->addr, cond->prefix_len))
+ break;
+ }
+ yes = 0;
+ break;
+ }
+ }
+
+ if (yes) {
+ len -= op->yes;
+ bc += op->yes;
+ } else {
+ len -= op->no;
+ bc += op->no;
+ }
+ }
+ return (len == 0);
+}
+
+int valid_cc(char *bc, int len, int cc)
+{
+ while (len >= 0) {
+ struct tcpdiag_bc_op *op = (struct tcpdiag_bc_op*)bc;
+
+ if (cc > len)
+ return 0;
+ if (cc == len)
+ return 1;
+ if (op->yes < 4)
+ return 0;
+ len -= op->yes;
+ bc += op->yes;
+ }
+ return 0;
+}
+
+int tcpdiag_bc_audit(char *bytecode, int bytecode_len)
+{
+ char *bc = bytecode;
+ int len = bytecode_len;
+
+ while (len > 0) {
+ struct tcpdiag_bc_op *op = (struct tcpdiag_bc_op*)bc;
+
+//printk("BC: %d %d %d {%d} / %d\n", op->code, op->yes, op->no, op[1].no, len);
+ switch (op->code) {
+ case TCPDIAG_BC_AUTO:
+ case TCPDIAG_BC_S_COND:
+ case TCPDIAG_BC_D_COND:
+ case TCPDIAG_BC_S_GE:
+ case TCPDIAG_BC_S_LE:
+ case TCPDIAG_BC_D_GE:
+ case TCPDIAG_BC_D_LE:
+ if (op->yes < 4 || op->yes > len+4)
+ return -EINVAL;
+ case TCPDIAG_BC_JMP:
+ if (op->no < 4 || op->no > len+4)
+ return -EINVAL;
+ if (op->no < len &&
+ !valid_cc(bytecode, bytecode_len, len-op->no))
+ return -EINVAL;
+ break;
+ case TCPDIAG_BC_NOP:
+ if (op->yes < 4 || op->yes > len+4)
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ bc += op->yes;
+ len -= op->yes;
+ }
+ return len == 0 ? 0 : -EINVAL;
+}
+
+
+int tcpdiag_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int i, num;
+ int s_i, s_num;
+ struct tcpdiagreq *r = NLMSG_DATA(cb->nlh);
+ struct rtattr *bc = NULL;
+
+ if (cb->nlh->nlmsg_len > 4+NLMSG_SPACE(sizeof(struct tcpdiagreq)))
+ bc = (struct rtattr*)(r+1);
+
+ s_i = cb->args[1];
+ s_num = num = cb->args[2];
+
+ if (cb->args[0] == 0) {
+ if (!(r->tcpdiag_states&(TCPF_LISTEN|TCPF_SYN_RECV)))
+ goto skip_listen_ht;
+ tcp_listen_lock();
+ for (i = s_i; i < TCP_LHTABLE_SIZE; i++) {
+ struct sock *sk = tcp_listening_hash[i];
+
+ if (i > s_i)
+ s_num = 0;
+
+ for (sk = tcp_listening_hash[i], num = 0;
+ sk != NULL;
+ sk = sk->next, num++) {
+ if (num < s_num)
+ continue;
+ if (!(r->tcpdiag_states&TCPF_LISTEN) ||
+ r->id.tcpdiag_dport)
+ continue;
+ if (r->id.tcpdiag_sport != sk->sport && r->id.tcpdiag_sport)
+ continue;
+ if (bc && !tcpdiag_bc_run(RTA_DATA(bc), RTA_PAYLOAD(bc), sk))
+ continue;
+ if (tcpdiag_fill(skb, sk, r->tcpdiag_ext,
+ NETLINK_CB(cb->skb).pid,
+ cb->nlh->nlmsg_seq) <= 0) {
+ tcp_listen_unlock();
+ goto done;
+ }
+ }
+ }
+ tcp_listen_unlock();
+skip_listen_ht:
+ cb->args[0] = 1;
+ s_i = num = s_num = 0;
+ }
+
+ if (!(r->tcpdiag_states&~(TCPF_LISTEN|TCPF_SYN_RECV)))
+ return skb->len;
+
+ for (i = s_i; i < tcp_ehash_size; i++) {
+ struct tcp_ehash_bucket *head = &tcp_ehash[i];
+ struct sock *sk;
+
+ if (i > s_i)
+ s_num = 0;
+
+ read_lock_bh(&head->lock);
+
+ for (sk = head->chain, num = 0;
+ sk != NULL;
+ sk = sk->next, num++) {
+ if (num < s_num)
+ continue;
+ if (!(r->tcpdiag_states&(1<<sk->state)))
+ continue;
+ if (r->id.tcpdiag_sport != sk->sport && r->id.tcpdiag_sport)
+ continue;
+ if (r->id.tcpdiag_dport != sk->dport && r->id.tcpdiag_dport)
+ continue;
+ if (bc && !tcpdiag_bc_run(RTA_DATA(bc), RTA_PAYLOAD(bc), sk))
+ continue;
+ if (tcpdiag_fill(skb, sk, r->tcpdiag_ext,
+ NETLINK_CB(cb->skb).pid,
+ cb->nlh->nlmsg_seq) <= 0) {
+ read_unlock_bh(&head->lock);
+ goto done;
+ }
+ }
+
+ if (r->tcpdiag_states&TCPF_TIME_WAIT) {
+ for (sk = tcp_ehash[i+tcp_ehash_size].chain;
+ sk != NULL;
+ sk = sk->next, num++) {
+ if (num < s_num)
+ continue;
+ if (!(r->tcpdiag_states&(1<<sk->zapped)))
+ continue;
+ if (r->id.tcpdiag_sport != sk->sport && r->id.tcpdiag_sport)
+ continue;
+ if (r->id.tcpdiag_dport != sk->dport && r->id.tcpdiag_dport)
+ continue;
+ if (bc && !tcpdiag_bc_run(RTA_DATA(bc), RTA_PAYLOAD(bc), sk))
+ continue;
+ if (tcpdiag_fill(skb, sk, r->tcpdiag_ext,
+ NETLINK_CB(cb->skb).pid,
+ cb->nlh->nlmsg_seq) <= 0) {
+ read_unlock_bh(&head->lock);
+ goto done;
+ }
+ }
+ }
+ read_unlock_bh(&head->lock);
+ }
+
+done:
+ cb->args[1] = i;
+ cb->args[2] = num;
+ return skb->len;
+}
+
+static int tcpdiag_dump_done(struct netlink_callback *cb)
+{
+ return 0;
+}
+
+
+static __inline__ int
+tcpdiag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+{
+ if (!(nlh->nlmsg_flags&NLM_F_REQUEST))
+ return 0;
+
+ if (nlh->nlmsg_type != TCPDIAG_GETSOCK)
+ goto err_inval;
+
+ if (NLMSG_LENGTH(sizeof(struct tcpdiagreq)) > skb->len)
+ goto err_inval;
+
+ if (nlh->nlmsg_flags&NLM_F_DUMP) {
+ if (nlh->nlmsg_len > 4 + NLMSG_SPACE(sizeof(struct tcpdiagreq))) {
+ struct rtattr *rta = (struct rtattr*)(NLMSG_DATA(nlh) + sizeof(struct tcpdiagreq));
+ if (rta->rta_type != TCPDIAG_REQ_BYTECODE ||
+ rta->rta_len < 8 ||
+ rta->rta_len > nlh->nlmsg_len - NLMSG_SPACE(sizeof(struct tcpdiagreq)))
+ goto err_inval;
+ if (tcpdiag_bc_audit(RTA_DATA(rta), RTA_PAYLOAD(rta)))
+ goto err_inval;
+ }
+ return netlink_dump_start(tcpnl, skb, nlh,
+ tcpdiag_dump,
+ tcpdiag_dump_done);
+ } else {
+ return tcpdiag_get_exact(skb, nlh);
+ }
+
+err_inval:
+ return -EINVAL;
+}
+
+
+static inline void tcpdiag_rcv_skb(struct sk_buff *skb)
+{
+ int err;
+ struct nlmsghdr * nlh;
+
+ if (skb->len >= NLMSG_SPACE(0)) {
+ nlh = (struct nlmsghdr *)skb->data;
+ if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len)
+ return;
+ err = tcpdiag_rcv_msg(skb, nlh);
+ if (err || nlh->nlmsg_flags & NLM_F_ACK)
+ netlink_ack(skb, nlh, err);
+ }
+}
+
+static void tcpdiag_rcv(struct sock *sk, int len)
+{
+ struct sk_buff *skb;
+
+ while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
+ tcpdiag_rcv_skb(skb);
+ kfree_skb(skb);
+ }
+}
+
+void __init tcpdiag_init(void)
+{
+ tcpnl = netlink_kernel_create(NETLINK_TCPDIAG, tcpdiag_rcv);
+ if (tcpnl == NULL)
+ panic("tcpdiag_init: Cannot create netlink socket.");
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/tcp_input.c b/uClinux-2.4.31-uc0/net/ipv4/tcp_input.c
new file mode 100644
index 0000000..0fa0e8f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/tcp_input.c
@@ -0,0 +1,4800 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * Implementation of the Transmission Control Protocol(TCP).
+ *
+ * Version: $Id: tcp_input.c,v 1.241.2.1 2002/02/13 05:37:15 davem Exp $
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Mark Evans, <evansmp@uhura.aston.ac.uk>
+ * Corey Minyard <wf-rch!minyard@relay.EU.net>
+ * Florian La Roche, <flla@stud.uni-sb.de>
+ * Charles Hedrick, <hedrick@klinzhai.rutgers.edu>
+ * Linus Torvalds, <torvalds@cs.helsinki.fi>
+ * Alan Cox, <gw4pts@gw4pts.ampr.org>
+ * Matthew Dillon, <dillon@apollo.west.oic.com>
+ * Arnt Gulbrandsen, <agulbra@nvg.unit.no>
+ * Jorge Cwik, <jorge@laser.satlink.net>
+ */
+
+/*
+ * Changes:
+ * Pedro Roque : Fast Retransmit/Recovery.
+ * Two receive queues.
+ * Retransmit queue handled by TCP.
+ * Better retransmit timer handling.
+ * New congestion avoidance.
+ * Header prediction.
+ * Variable renaming.
+ *
+ * Eric : Fast Retransmit.
+ * Randy Scott : MSS option defines.
+ * Eric Schenk : Fixes to slow start algorithm.
+ * Eric Schenk : Yet another double ACK bug.
+ * Eric Schenk : Delayed ACK bug fixes.
+ * Eric Schenk : Floyd style fast retrans war avoidance.
+ * David S. Miller : Don't allow zero congestion window.
+ * Eric Schenk : Fix retransmitter so that it sends
+ * next packet on ack of previous packet.
+ * Andi Kleen : Moved open_request checking here
+ * and process RSTs for open_requests.
+ * Andi Kleen : Better prune_queue, and other fixes.
+ * Andrey Savochkin: Fix RTT measurements in the presnce of
+ * timestamps.
+ * Andrey Savochkin: Check sequence numbers correctly when
+ * removing SACKs due to in sequence incoming
+ * data segments.
+ * Andi Kleen: Make sure we never ack data there is not
+ * enough room for. Also make this condition
+ * a fatal error if it might still happen.
+ * Andi Kleen: Add tcp_measure_rcv_mss to make
+ * connections with MSS<min(MTU,ann. MSS)
+ * work without delayed acks.
+ * Andi Kleen: Process packets with PSH set in the
+ * fast path.
+ * J Hadi Salim: ECN support
+ * Andrei Gurtov,
+ * Pasi Sarolahti,
+ * Panu Kuhlberg: Experimental audit of TCP (re)transmission
+ * engine. Lots of bugs are found.
+ * Pasi Sarolahti: F-RTO for dealing with spurious RTOs
+ * Angelo Dell'Aera: TCP Westwood+ support
+ */
+
+#include <linux/config.h>
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+#include <net/tcp.h>
+#include <net/inet_common.h>
+#include <linux/ipsec.h>
+
+int sysctl_tcp_timestamps = 1;
+int sysctl_tcp_window_scaling = 1;
+int sysctl_tcp_sack = 1;
+int sysctl_tcp_fack = 1;
+int sysctl_tcp_reordering = TCP_FASTRETRANS_THRESH;
+#ifdef CONFIG_INET_ECN
+int sysctl_tcp_ecn = 1;
+#else
+int sysctl_tcp_ecn = 0;
+#endif
+int sysctl_tcp_dsack = 1;
+int sysctl_tcp_app_win = 31;
+int sysctl_tcp_adv_win_scale = 2;
+
+int sysctl_tcp_stdurg = 0;
+int sysctl_tcp_rfc1337 = 0;
+int sysctl_tcp_max_orphans = NR_FILE;
+int sysctl_tcp_frto = 0;
+
+int sysctl_tcp_nometrics_save = 0;
+
+int sysctl_tcp_westwood = 0;
+int sysctl_tcp_vegas_cong_avoid = 0;
+
+int sysctl_tcp_moderate_rcvbuf = 0;
+
+/* Default values of the Vegas variables, in fixed-point representation
+ * with V_PARAM_SHIFT bits to the right of the binary point.
+ */
+#define V_PARAM_SHIFT 1
+int sysctl_tcp_vegas_alpha = 1<<V_PARAM_SHIFT;
+int sysctl_tcp_vegas_beta = 3<<V_PARAM_SHIFT;
+int sysctl_tcp_vegas_gamma = 1<<V_PARAM_SHIFT;
+int sysctl_tcp_bic;
+int sysctl_tcp_bic_fast_convergence = 1;
+int sysctl_tcp_bic_low_window = 14;
+int sysctl_tcp_bic_beta = 819; /* = 819/1024 (BICTCP_BETA_SCALE) */
+
+#define FLAG_DATA 0x01 /* Incoming frame contained data. */
+#define FLAG_WIN_UPDATE 0x02 /* Incoming ACK was a window update. */
+#define FLAG_DATA_ACKED 0x04 /* This ACK acknowledged new data. */
+#define FLAG_RETRANS_DATA_ACKED 0x08 /* "" "" some of which was retransmitted. */
+#define FLAG_SYN_ACKED 0x10 /* This ACK acknowledged SYN. */
+#define FLAG_DATA_SACKED 0x20 /* New SACK. */
+#define FLAG_ECE 0x40 /* ECE in this ACK */
+#define FLAG_DATA_LOST 0x80 /* SACK detected data lossage. */
+#define FLAG_SLOWPATH 0x100 /* Do not skip RFC checks for window update.*/
+
+#define FLAG_ACKED (FLAG_DATA_ACKED|FLAG_SYN_ACKED)
+#define FLAG_NOT_DUP (FLAG_DATA|FLAG_WIN_UPDATE|FLAG_ACKED)
+#define FLAG_CA_ALERT (FLAG_DATA_SACKED|FLAG_ECE)
+#define FLAG_FORWARD_PROGRESS (FLAG_ACKED|FLAG_DATA_SACKED)
+
+#define IsReno(tp) ((tp)->sack_ok == 0)
+#define IsFack(tp) ((tp)->sack_ok & 2)
+#define IsDSack(tp) ((tp)->sack_ok & 4)
+
+#define TCP_REMNANT (TCP_FLAG_FIN|TCP_FLAG_URG|TCP_FLAG_SYN|TCP_FLAG_PSH)
+
+/* Adapt the MSS value used to make delayed ack decision to the
+ * real world.
+ */
+static __inline__ void tcp_measure_rcv_mss(struct tcp_opt *tp, struct sk_buff *skb)
+{
+ unsigned int len, lss;
+
+ lss = tp->ack.last_seg_size;
+ tp->ack.last_seg_size = 0;
+
+ /* skb->len may jitter because of SACKs, even if peer
+ * sends good full-sized frames.
+ */
+ len = skb->len;
+ if (len >= tp->ack.rcv_mss) {
+ tp->ack.rcv_mss = len;
+ } else {
+ /* Otherwise, we make more careful check taking into account,
+ * that SACKs block is variable.
+ *
+ * "len" is invariant segment length, including TCP header.
+ */
+ len += skb->data - skb->h.raw;
+ if (len >= TCP_MIN_RCVMSS + sizeof(struct tcphdr) ||
+ /* If PSH is not set, packet should be
+ * full sized, provided peer TCP is not badly broken.
+ * This observation (if it is correct 8)) allows
+ * to handle super-low mtu links fairly.
+ */
+ (len >= TCP_MIN_MSS + sizeof(struct tcphdr) &&
+ !(tcp_flag_word(skb->h.th)&TCP_REMNANT))) {
+ /* Subtract also invariant (if peer is RFC compliant),
+ * tcp header plus fixed timestamp option length.
+ * Resulting "len" is MSS free of SACK jitter.
+ */
+ len -= tp->tcp_header_len;
+ tp->ack.last_seg_size = len;
+ if (len == lss) {
+ tp->ack.rcv_mss = len;
+ return;
+ }
+ }
+ tp->ack.pending |= TCP_ACK_PUSHED;
+ }
+}
+
+static void tcp_incr_quickack(struct tcp_opt *tp)
+{
+ unsigned quickacks = tp->rcv_wnd/(2*tp->ack.rcv_mss);
+
+ if (quickacks==0)
+ quickacks=2;
+ if (quickacks > tp->ack.quick)
+ tp->ack.quick = min(quickacks, TCP_MAX_QUICKACKS);
+}
+
+void tcp_enter_quickack_mode(struct tcp_opt *tp)
+{
+ tcp_incr_quickack(tp);
+ tp->ack.pingpong = 0;
+ tp->ack.ato = TCP_ATO_MIN;
+}
+
+/* Send ACKs quickly, if "quick" count is not exhausted
+ * and the session is not interactive.
+ */
+
+static __inline__ int tcp_in_quickack_mode(struct tcp_opt *tp)
+{
+ return (tp->ack.quick && !tp->ack.pingpong);
+}
+
+/* Buffer size and advertised window tuning.
+ *
+ * 1. Tuning sk->sndbuf, when connection enters established state.
+ */
+
+static void tcp_fixup_sndbuf(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int sndmem = tp->mss_clamp+MAX_TCP_HEADER+16+sizeof(struct sk_buff);
+
+ if (sk->sndbuf < 3*sndmem)
+ sk->sndbuf = min(3*sndmem, sysctl_tcp_wmem[2]);
+}
+
+/* 2. Tuning advertised window (window_clamp, rcv_ssthresh)
+ *
+ * All tcp_full_space() is split to two parts: "network" buffer, allocated
+ * forward and advertised in receiver window (tp->rcv_wnd) and
+ * "application buffer", required to isolate scheduling/application
+ * latencies from network.
+ * window_clamp is maximal advertised window. It can be less than
+ * tcp_full_space(), in this case tcp_full_space() - window_clamp
+ * is reserved for "application" buffer. The less window_clamp is
+ * the smoother our behaviour from viewpoint of network, but the lower
+ * throughput and the higher sensitivity of the connection to losses. 8)
+ *
+ * rcv_ssthresh is more strict window_clamp used at "slow start"
+ * phase to predict further behaviour of this connection.
+ * It is used for two goals:
+ * - to enforce header prediction at sender, even when application
+ * requires some significant "application buffer". It is check #1.
+ * - to prevent pruning of receive queue because of misprediction
+ * of receiver window. Check #2.
+ *
+ * The scheme does not work when sender sends good segments opening
+ * window and then starts to feed us spagetti. But it should work
+ * in common situations. Otherwise, we have to rely on queue collapsing.
+ */
+
+/* Slow part of check#2. */
+static int
+__tcp_grow_window(struct sock *sk, struct tcp_opt *tp, struct sk_buff *skb)
+{
+ /* Optimize this! */
+ int truesize = tcp_win_from_space(skb->truesize)/2;
+ int window = tcp_full_space(sk)/2;
+
+ while (tp->rcv_ssthresh <= window) {
+ if (truesize <= skb->len)
+ return 2*tp->ack.rcv_mss;
+
+ truesize >>= 1;
+ window >>= 1;
+ }
+ return 0;
+}
+
+static __inline__ void
+tcp_grow_window(struct sock *sk, struct tcp_opt *tp, struct sk_buff *skb)
+{
+ /* Check #1 */
+ if (tp->rcv_ssthresh < tp->window_clamp &&
+ (int)tp->rcv_ssthresh < tcp_space(sk) &&
+ !tcp_memory_pressure) {
+ int incr;
+
+ /* Check #2. Increase window, if skb with such overhead
+ * will fit to rcvbuf in future.
+ */
+ if (tcp_win_from_space(skb->truesize) <= skb->len)
+ incr = 2*tp->advmss;
+ else
+ incr = __tcp_grow_window(sk, tp, skb);
+
+ if (incr) {
+ tp->rcv_ssthresh = min(tp->rcv_ssthresh + incr, tp->window_clamp);
+ tp->ack.quick |= 1;
+ }
+ }
+}
+
+/* 3. Tuning rcvbuf, when connection enters established state. */
+
+static void tcp_fixup_rcvbuf(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int rcvmem = tp->advmss+MAX_TCP_HEADER+16+sizeof(struct sk_buff);
+
+ /* Try to select rcvbuf so that 4 mss-sized segments
+ * will fit to window and correspoding skbs will fit to our rcvbuf.
+ * (was 3; 4 is minimum to allow fast retransmit to work.)
+ */
+ while (tcp_win_from_space(rcvmem) < tp->advmss)
+ rcvmem += 128;
+ if (sk->rcvbuf < 4*rcvmem)
+ sk->rcvbuf = min(4*rcvmem, sysctl_tcp_rmem[2]);
+}
+
+/* 4. Try to fixup all. It is made iimediately after connection enters
+ * established state.
+ */
+static void tcp_init_buffer_space(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int maxwin;
+
+ if (!(sk->userlocks&SOCK_RCVBUF_LOCK))
+ tcp_fixup_rcvbuf(sk);
+ if (!(sk->userlocks&SOCK_SNDBUF_LOCK))
+ tcp_fixup_sndbuf(sk);
+
+ tp->rcvq_space.space = tp->rcv_wnd;
+
+ maxwin = tcp_full_space(sk);
+
+ if (tp->window_clamp >= maxwin) {
+ tp->window_clamp = maxwin;
+
+ if (sysctl_tcp_app_win && maxwin>4*tp->advmss)
+ tp->window_clamp = max(maxwin-(maxwin>>sysctl_tcp_app_win), 4*tp->advmss);
+ }
+
+ /* Force reservation of one segment. */
+ if (sysctl_tcp_app_win &&
+ tp->window_clamp > 2*tp->advmss &&
+ tp->window_clamp + tp->advmss > maxwin)
+ tp->window_clamp = max(2*tp->advmss, maxwin-tp->advmss);
+
+ tp->rcv_ssthresh = min(tp->rcv_ssthresh, tp->window_clamp);
+ tp->snd_cwnd_stamp = tcp_time_stamp;
+}
+
+static void init_bictcp(struct tcp_opt *tp)
+{
+ tp->bictcp.cnt = 0;
+
+ tp->bictcp.last_max_cwnd = 0;
+ tp->bictcp.last_cwnd = 0;
+ tp->bictcp.last_stamp = 0;
+}
+
+/* 5. Recalculate window clamp after socket hit its memory bounds. */
+static void tcp_clamp_window(struct sock *sk, struct tcp_opt *tp)
+{
+ struct sk_buff *skb;
+ unsigned int app_win = tp->rcv_nxt - tp->copied_seq;
+ int ofo_win = 0;
+
+ tp->ack.quick = 0;
+
+ skb_queue_walk(&tp->out_of_order_queue, skb) {
+ ofo_win += skb->len;
+ }
+
+ /* If overcommit is due to out of order segments,
+ * do not clamp window. Try to expand rcvbuf instead.
+ */
+ if (ofo_win) {
+ if (sk->rcvbuf < sysctl_tcp_rmem[2] &&
+ !(sk->userlocks&SOCK_RCVBUF_LOCK) &&
+ !tcp_memory_pressure &&
+ atomic_read(&tcp_memory_allocated) < sysctl_tcp_mem[0])
+ sk->rcvbuf = min(atomic_read(&sk->rmem_alloc), sysctl_tcp_rmem[2]);
+ }
+ if (atomic_read(&sk->rmem_alloc) > sk->rcvbuf) {
+ app_win += ofo_win;
+ if (atomic_read(&sk->rmem_alloc) >= 2*sk->rcvbuf)
+ app_win >>= 1;
+ if (app_win > tp->ack.rcv_mss)
+ app_win -= tp->ack.rcv_mss;
+ app_win = max(app_win, 2U*tp->advmss);
+
+ if (!ofo_win)
+ tp->window_clamp = min(tp->window_clamp, app_win);
+ tp->rcv_ssthresh = min(tp->window_clamp, 2U*tp->advmss);
+ }
+}
+
+/* Receiver "autotuning" code.
+ *
+ * The algorithm for RTT estimation w/o timestamps is based on
+ * Dynamic Right-Sizing (DRS) by Wu Feng and Mike Fisk of LANL.
+ * <http://www.lanl.gov/radiant/website/pubs/drs/lacsi2001.ps>
+ *
+ * More detail on this code can be found at
+ * <http://www.psc.edu/~jheffner/senior_thesis.ps>,
+ * though this reference is out of date. A new paper
+ * is pending.
+ */
+static void tcp_rcv_rtt_update(struct tcp_opt *tp, u32 sample, int win_dep)
+{
+ u32 new_sample = tp->rcv_rtt_est.rtt;
+ long m = sample;
+
+ if (m == 0)
+ m = 1;
+
+ if (new_sample != 0) {
+ /* If we sample in larger samples in the non-timestamp
+ * case, we could grossly overestimate the RTT especially
+ * with chatty applications or bulk transfer apps which
+ * are stalled on filesystem I/O.
+ *
+ * Also, since we are only going for a minimum in the
+ * non-timestamp case, we do not smoothe things out
+ * else with timestamps disabled convergance takes too
+ * long.
+ */
+ if (!win_dep) {
+ m -= (new_sample >> 3);
+ new_sample += m;
+ } else if (m < new_sample)
+ new_sample = m << 3;
+ } else {
+ /* No previous mesaure. */
+ new_sample = m << 3;
+ }
+
+ if (tp->rcv_rtt_est.rtt != new_sample)
+ tp->rcv_rtt_est.rtt = new_sample;
+}
+
+static inline void tcp_rcv_rtt_measure(struct tcp_opt *tp)
+{
+ if (tp->rcv_rtt_est.time == 0)
+ goto new_measure;
+ if (before(tp->rcv_nxt, tp->rcv_rtt_est.seq))
+ return;
+ tcp_rcv_rtt_update(tp,
+ jiffies - tp->rcv_rtt_est.time,
+ 1);
+
+new_measure:
+ tp->rcv_rtt_est.seq = tp->rcv_nxt + tp->rcv_wnd;
+ tp->rcv_rtt_est.time = tcp_time_stamp;
+}
+
+static inline void tcp_rcv_rtt_measure_ts(struct tcp_opt *tp, struct sk_buff *skb)
+{
+ if (tp->rcv_tsecr &&
+ (TCP_SKB_CB(skb)->end_seq -
+ TCP_SKB_CB(skb)->seq >= tp->ack.rcv_mss))
+ tcp_rcv_rtt_update(tp, tcp_time_stamp - tp->rcv_tsecr, 0);
+}
+
+/*
+ * This function should be called every time data is copied to user space.
+ * It calculates the appropriate TCP receive buffer space.
+ */
+void tcp_rcv_space_adjust(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int time;
+ int space;
+
+ if (tp->rcvq_space.time == 0)
+ goto new_measure;
+
+ time = tcp_time_stamp - tp->rcvq_space.time;
+ if (time < (tp->rcv_rtt_est.rtt >> 3) ||
+ tp->rcv_rtt_est.rtt == 0)
+ return;
+
+ space = 2 * (tp->copied_seq - tp->rcvq_space.seq);
+
+ space = max(tp->rcvq_space.space, space);
+
+ if (tp->rcvq_space.space != space) {
+ int rcvmem;
+
+ tp->rcvq_space.space = space;
+
+ if (sysctl_tcp_moderate_rcvbuf) {
+ int new_clamp = space;
+
+ /* Receive space grows, normalize in order to
+ * take into account packet headers and sk_buff
+ * structure overhead.
+ */
+ space /= tp->advmss;
+ if (!space)
+ space = 1;
+ rcvmem = (tp->advmss + MAX_TCP_HEADER +
+ 16 + sizeof(struct sk_buff));
+ while (tcp_win_from_space(rcvmem) < tp->advmss)
+ rcvmem += 128;
+ space *= rcvmem;
+ space = min(space, sysctl_tcp_rmem[2]);
+ if (space > sk->rcvbuf) {
+ sk->rcvbuf = space;
+
+ /* Make the window clamp follow along. */
+ tp->window_clamp = new_clamp;
+ }
+ }
+ }
+
+new_measure:
+ tp->rcvq_space.seq = tp->copied_seq;
+ tp->rcvq_space.time = tcp_time_stamp;
+}
+
+/* There is something which you must keep in mind when you analyze the
+ * behavior of the tp->ato delayed ack timeout interval. When a
+ * connection starts up, we want to ack as quickly as possible. The
+ * problem is that "good" TCP's do slow start at the beginning of data
+ * transmission. The means that until we send the first few ACK's the
+ * sender will sit on his end and only queue most of his data, because
+ * he can only send snd_cwnd unacked packets at any given time. For
+ * each ACK we send, he increments snd_cwnd and transmits more of his
+ * queue. -DaveM
+ */
+static void tcp_event_data_recv(struct sock *sk, struct tcp_opt *tp, struct sk_buff *skb)
+{
+ u32 now;
+
+ tcp_schedule_ack(tp);
+
+ tcp_measure_rcv_mss(tp, skb);
+
+ tcp_rcv_rtt_measure(tp);
+
+ now = tcp_time_stamp;
+
+ if (!tp->ack.ato) {
+ /* The _first_ data packet received, initialize
+ * delayed ACK engine.
+ */
+ tcp_incr_quickack(tp);
+ tp->ack.ato = TCP_ATO_MIN;
+ } else {
+ int m = now - tp->ack.lrcvtime;
+
+ if (m <= TCP_ATO_MIN/2) {
+ /* The fastest case is the first. */
+ tp->ack.ato = (tp->ack.ato>>1) + TCP_ATO_MIN/2;
+ } else if (m < tp->ack.ato) {
+ tp->ack.ato = (tp->ack.ato>>1) + m;
+ if (tp->ack.ato > tp->rto)
+ tp->ack.ato = tp->rto;
+ } else if (m > tp->rto) {
+ /* Too long gap. Apparently sender falled to
+ * restart window, so that we send ACKs quickly.
+ */
+ tcp_incr_quickack(tp);
+ tcp_mem_reclaim(sk);
+ }
+ }
+ tp->ack.lrcvtime = now;
+
+ TCP_ECN_check_ce(tp, skb);
+
+ if (skb->len >= 128)
+ tcp_grow_window(sk, tp, skb);
+}
+
+/* When starting a new connection, pin down the current choice of
+ * congestion algorithm.
+ */
+void tcp_ca_init(struct tcp_opt *tp)
+{
+ if (sysctl_tcp_westwood)
+ tp->adv_cong = TCP_WESTWOOD;
+ else if (sysctl_tcp_bic)
+ tp->adv_cong = TCP_BIC;
+ else if (sysctl_tcp_vegas_cong_avoid) {
+ tp->adv_cong = TCP_VEGAS;
+ tp->vegas.baseRTT = 0x7fffffff;
+ tcp_vegas_enable(tp);
+ }
+}
+
+/* Do RTT sampling needed for Vegas.
+ * Basically we:
+ * o min-filter RTT samples from within an RTT to get the current
+ * propagation delay + queuing delay (we are min-filtering to try to
+ * avoid the effects of delayed ACKs)
+ * o min-filter RTT samples from a much longer window (forever for now)
+ * to find the propagation delay (baseRTT)
+ */
+static inline void vegas_rtt_calc(struct tcp_opt *tp, __u32 rtt)
+{
+ __u32 vrtt = rtt + 1; /* Never allow zero rtt or baseRTT */
+
+ /* Filter to find propagation delay: */
+ if (vrtt < tp->vegas.baseRTT)
+ tp->vegas.baseRTT = vrtt;
+
+ /* Find the min RTT during the last RTT to find
+ * the current prop. delay + queuing delay:
+ */
+ tp->vegas.minRTT = min(tp->vegas.minRTT, vrtt);
+ tp->vegas.cntRTT++;
+}
+
+/* Called to compute a smoothed rtt estimate. The data fed to this
+ * routine either comes from timestamps, or from segments that were
+ * known _not_ to have been retransmitted [see Karn/Partridge
+ * Proceedings SIGCOMM 87]. The algorithm is from the SIGCOMM 88
+ * piece by Van Jacobson.
+ * NOTE: the next three routines used to be one big routine.
+ * To save cycles in the RFC 1323 implementation it was better to break
+ * it up into three procedures. -- erics
+ */
+static __inline__ void tcp_rtt_estimator(struct tcp_opt *tp, __u32 mrtt)
+{
+ long m = mrtt; /* RTT */
+
+ if (tcp_vegas_enabled(tp))
+ vegas_rtt_calc(tp, mrtt);
+
+ /* The following amusing code comes from Jacobson's
+ * article in SIGCOMM '88. Note that rtt and mdev
+ * are scaled versions of rtt and mean deviation.
+ * This is designed to be as fast as possible
+ * m stands for "measurement".
+ *
+ * On a 1990 paper the rto value is changed to:
+ * RTO = rtt + 4 * mdev
+ *
+ * Funny. This algorithm seems to be very broken.
+ * These formulae increase RTO, when it should be decreased, increase
+ * too slowly, when it should be incresed fastly, decrease too fastly
+ * etc. I guess in BSD RTO takes ONE value, so that it is absolutely
+ * does not matter how to _calculate_ it. Seems, it was trap
+ * that VJ failed to avoid. 8)
+ */
+ if(m == 0)
+ m = 1;
+ if (tp->srtt != 0) {
+ m -= (tp->srtt >> 3); /* m is now error in rtt est */
+ tp->srtt += m; /* rtt = 7/8 rtt + 1/8 new */
+ if (m < 0) {
+ m = -m; /* m is now abs(error) */
+ m -= (tp->mdev >> 2); /* similar update on mdev */
+ /* This is similar to one of Eifel findings.
+ * Eifel blocks mdev updates when rtt decreases.
+ * This solution is a bit different: we use finer gain
+ * for mdev in this case (alpha*beta).
+ * Like Eifel it also prevents growth of rto,
+ * but also it limits too fast rto decreases,
+ * happening in pure Eifel.
+ */
+ if (m > 0)
+ m >>= 3;
+ } else {
+ m -= (tp->mdev >> 2); /* similar update on mdev */
+ }
+ tp->mdev += m; /* mdev = 3/4 mdev + 1/4 new */
+ if (tp->mdev > tp->mdev_max) {
+ tp->mdev_max = tp->mdev;
+ if (tp->mdev_max > tp->rttvar)
+ tp->rttvar = tp->mdev_max;
+ }
+ if (after(tp->snd_una, tp->rtt_seq)) {
+ if (tp->mdev_max < tp->rttvar)
+ tp->rttvar -= (tp->rttvar-tp->mdev_max)>>2;
+ tp->rtt_seq = tp->snd_nxt;
+ tp->mdev_max = TCP_RTO_MIN;
+ }
+ } else {
+ /* no previous measure. */
+ tp->srtt = m<<3; /* take the measured time to be rtt */
+ tp->mdev = m<<1; /* make sure rto = 3*rtt */
+ tp->mdev_max = tp->rttvar = max(tp->mdev, TCP_RTO_MIN);
+ tp->rtt_seq = tp->snd_nxt;
+ }
+
+ tcp_westwood_update_rtt(tp, tp->srtt >> 3);
+}
+
+/* Calculate rto without backoff. This is the second half of Van Jacobson's
+ * routine referred to above.
+ */
+static __inline__ void tcp_set_rto(struct tcp_opt *tp)
+{
+ /* Old crap is replaced with new one. 8)
+ *
+ * More seriously:
+ * 1. If rtt variance happened to be less 50msec, it is hallucination.
+ * It cannot be less due to utterly erratic ACK generation made
+ * at least by solaris and freebsd. "Erratic ACKs" has _nothing_
+ * to do with delayed acks, because at cwnd>2 true delack timeout
+ * is invisible. Actually, Linux-2.4 also generates erratic
+ * ACKs in some curcumstances.
+ */
+ tp->rto = (tp->srtt >> 3) + tp->rttvar;
+
+ /* 2. Fixups made earlier cannot be right.
+ * If we do not estimate RTO correctly without them,
+ * all the algo is pure shit and should be replaced
+ * with correct one. It is exaclty, which we pretend to do.
+ */
+}
+
+/* NOTE: clamping at TCP_RTO_MIN is not required, current algo
+ * guarantees that rto is higher.
+ */
+static __inline__ void tcp_bound_rto(struct tcp_opt *tp)
+{
+ if (tp->rto > TCP_RTO_MAX)
+ tp->rto = TCP_RTO_MAX;
+}
+
+/* Save metrics learned by this TCP session.
+ This function is called only, when TCP finishes successfully
+ i.e. when it enters TIME-WAIT or goes from LAST-ACK to CLOSE.
+ */
+void tcp_update_metrics(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct dst_entry *dst = __sk_dst_get(sk);
+
+ if (sysctl_tcp_nometrics_save)
+ return;
+
+ dst_confirm(dst);
+
+ if (dst && (dst->flags&DST_HOST)) {
+ int m;
+
+ if (tp->backoff || !tp->srtt) {
+ /* This session failed to estimate rtt. Why?
+ * Probably, no packets returned in time.
+ * Reset our results.
+ */
+ if (!(dst->mxlock&(1<<RTAX_RTT)))
+ dst->rtt = 0;
+ return;
+ }
+
+ m = dst->rtt - tp->srtt;
+
+ /* If newly calculated rtt larger than stored one,
+ * store new one. Otherwise, use EWMA. Remember,
+ * rtt overestimation is always better than underestimation.
+ */
+ if (!(dst->mxlock&(1<<RTAX_RTT))) {
+ if (m <= 0)
+ dst->rtt = tp->srtt;
+ else
+ dst->rtt -= (m>>3);
+ }
+
+ if (!(dst->mxlock&(1<<RTAX_RTTVAR))) {
+ if (m < 0)
+ m = -m;
+
+ /* Scale deviation to rttvar fixed point */
+ m >>= 1;
+ if (m < tp->mdev)
+ m = tp->mdev;
+
+ if (m >= dst->rttvar)
+ dst->rttvar = m;
+ else
+ dst->rttvar -= (dst->rttvar - m)>>2;
+ }
+
+ if (tp->snd_ssthresh >= 0xFFFF) {
+ /* Slow start still did not finish. */
+ if (dst->ssthresh &&
+ !(dst->mxlock&(1<<RTAX_SSTHRESH)) &&
+ (tp->snd_cwnd>>1) > dst->ssthresh)
+ dst->ssthresh = (tp->snd_cwnd>>1);
+ if (!(dst->mxlock&(1<<RTAX_CWND)) &&
+ tp->snd_cwnd > dst->cwnd)
+ dst->cwnd = tp->snd_cwnd;
+ } else if (tp->snd_cwnd > tp->snd_ssthresh &&
+ tp->ca_state == TCP_CA_Open) {
+ /* Cong. avoidance phase, cwnd is reliable. */
+ if (!(dst->mxlock&(1<<RTAX_SSTHRESH)))
+ dst->ssthresh = max(tp->snd_cwnd>>1, tp->snd_ssthresh);
+ if (!(dst->mxlock&(1<<RTAX_CWND)))
+ dst->cwnd = (dst->cwnd + tp->snd_cwnd)>>1;
+ } else {
+ /* Else slow start did not finish, cwnd is non-sense,
+ ssthresh may be also invalid.
+ */
+ if (!(dst->mxlock&(1<<RTAX_CWND)))
+ dst->cwnd = (dst->cwnd + tp->snd_ssthresh)>>1;
+ if (dst->ssthresh &&
+ !(dst->mxlock&(1<<RTAX_SSTHRESH)) &&
+ tp->snd_ssthresh > dst->ssthresh)
+ dst->ssthresh = tp->snd_ssthresh;
+ }
+
+ if (!(dst->mxlock&(1<<RTAX_REORDERING))) {
+ if (dst->reordering < tp->reordering &&
+ tp->reordering != sysctl_tcp_reordering)
+ dst->reordering = tp->reordering;
+ }
+ }
+}
+
+/* Increase initial CWND conservatively: if estimated
+ * RTT is low enough (<20msec) or if we have some preset ssthresh.
+ *
+ * Numbers are taken from RFC2414.
+ */
+__u32 tcp_init_cwnd(struct tcp_opt *tp)
+{
+ __u32 cwnd;
+
+ if (tp->mss_cache > 1460)
+ return 2;
+
+ cwnd = (tp->mss_cache > 1095) ? 3 : 4;
+
+ if (!tp->srtt || (tp->snd_ssthresh >= 0xFFFF && tp->srtt > ((HZ/50)<<3)))
+ cwnd = 2;
+ else if (cwnd > tp->snd_ssthresh)
+ cwnd = tp->snd_ssthresh;
+
+ return min_t(__u32, cwnd, tp->snd_cwnd_clamp);
+}
+
+/* Initialize metrics on socket. */
+
+static void tcp_init_metrics(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct dst_entry *dst = __sk_dst_get(sk);
+
+ if (dst == NULL)
+ goto reset;
+
+ dst_confirm(dst);
+
+ if (dst->mxlock&(1<<RTAX_CWND))
+ tp->snd_cwnd_clamp = dst->cwnd;
+ if (dst->ssthresh) {
+ tp->snd_ssthresh = dst->ssthresh;
+ if (tp->snd_ssthresh > tp->snd_cwnd_clamp)
+ tp->snd_ssthresh = tp->snd_cwnd_clamp;
+ }
+ if (dst->reordering && tp->reordering != dst->reordering) {
+ tp->sack_ok &= ~2;
+ tp->reordering = dst->reordering;
+ }
+
+ if (dst->rtt == 0)
+ goto reset;
+
+ if (!tp->srtt && dst->rtt < (TCP_TIMEOUT_INIT<<3))
+ goto reset;
+
+ /* Initial rtt is determined from SYN,SYN-ACK.
+ * The segment is small and rtt may appear much
+ * less than real one. Use per-dst memory
+ * to make it more realistic.
+ *
+ * A bit of theory. RTT is time passed after "normal" sized packet
+ * is sent until it is ACKed. In normal curcumstances sending small
+ * packets force peer to delay ACKs and calculation is correct too.
+ * The algorithm is adaptive and, provided we follow specs, it
+ * NEVER underestimate RTT. BUT! If peer tries to make some clever
+ * tricks sort of "quick acks" for time long enough to decrease RTT
+ * to low value, and then abruptly stops to do it and starts to delay
+ * ACKs, wait for troubles.
+ */
+ if (dst->rtt > tp->srtt) {
+ tp->srtt = dst->rtt;
+ tp->rtt_seq = tp->snd_nxt;
+ }
+ if (dst->rttvar > tp->mdev) {
+ tp->mdev = dst->rttvar;
+ tp->mdev_max = tp->rttvar = max(tp->mdev, TCP_RTO_MIN);
+ }
+ tcp_set_rto(tp);
+ tcp_bound_rto(tp);
+ if (tp->rto < TCP_TIMEOUT_INIT && !tp->saw_tstamp)
+ goto reset;
+ tp->snd_cwnd = tcp_init_cwnd(tp);
+ tp->snd_cwnd_stamp = tcp_time_stamp;
+ return;
+
+reset:
+ /* Play conservative. If timestamps are not
+ * supported, TCP will fail to recalculate correct
+ * rtt, if initial rto is too small. FORGET ALL AND RESET!
+ */
+ if (!tp->saw_tstamp && tp->srtt) {
+ tp->srtt = 0;
+ tp->mdev = tp->mdev_max = tp->rttvar = TCP_TIMEOUT_INIT;
+ tp->rto = TCP_TIMEOUT_INIT;
+ }
+}
+
+static void tcp_update_reordering(struct tcp_opt *tp, int metric, int ts)
+{
+ if (metric > tp->reordering) {
+ tp->reordering = min(TCP_MAX_REORDERING, metric);
+
+ /* This exciting event is worth to be remembered. 8) */
+ if (ts)
+ NET_INC_STATS_BH(TCPTSReorder);
+ else if (IsReno(tp))
+ NET_INC_STATS_BH(TCPRenoReorder);
+ else if (IsFack(tp))
+ NET_INC_STATS_BH(TCPFACKReorder);
+ else
+ NET_INC_STATS_BH(TCPSACKReorder);
+#if FASTRETRANS_DEBUG > 1
+ printk(KERN_DEBUG "Disorder%d %d %u f%u s%u rr%d\n",
+ tp->sack_ok, tp->ca_state,
+ tp->reordering, tp->fackets_out, tp->sacked_out,
+ tp->undo_marker ? tp->undo_retrans : 0);
+#endif
+ /* Disable FACK yet. */
+ tp->sack_ok &= ~2;
+ }
+}
+
+/* This procedure tags the retransmission queue when SACKs arrive.
+ *
+ * We have three tag bits: SACKED(S), RETRANS(R) and LOST(L).
+ * Packets in queue with these bits set are counted in variables
+ * sacked_out, retrans_out and lost_out, correspondingly.
+ *
+ * Valid combinations are:
+ * Tag InFlight Description
+ * 0 1 - orig segment is in flight.
+ * S 0 - nothing flies, orig reached receiver.
+ * L 0 - nothing flies, orig lost by net.
+ * R 2 - both orig and retransmit are in flight.
+ * L|R 1 - orig is lost, retransmit is in flight.
+ * S|R 1 - orig reached receiver, retrans is still in flight.
+ * (L|S|R is logically valid, it could occur when L|R is sacked,
+ * but it is equivalent to plain S and code short-curcuits it to S.
+ * L|S is logically invalid, it would mean -1 packet in flight 8))
+ *
+ * These 6 states form finite state machine, controlled by the following events:
+ * 1. New ACK (+SACK) arrives. (tcp_sacktag_write_queue())
+ * 2. Retransmission. (tcp_retransmit_skb(), tcp_xmit_retransmit_queue())
+ * 3. Loss detection event of one of three flavors:
+ * A. Scoreboard estimator decided the packet is lost.
+ * A'. Reno "three dupacks" marks head of queue lost.
+ * A''. Its FACK modfication, head until snd.fack is lost.
+ * B. SACK arrives sacking data transmitted after never retransmitted
+ * hole was sent out.
+ * C. SACK arrives sacking SND.NXT at the moment, when the
+ * segment was retransmitted.
+ * 4. D-SACK added new rule: D-SACK changes any tag to S.
+ *
+ * It is pleasant to note, that state diagram turns out to be commutative,
+ * so that we are allowed not to be bothered by order of our actions,
+ * when multiple events arrive simultaneously. (see the function below).
+ *
+ * Reordering detection.
+ * --------------------
+ * Reordering metric is maximal distance, which a packet can be displaced
+ * in packet stream. With SACKs we can estimate it:
+ *
+ * 1. SACK fills old hole and the corresponding segment was not
+ * ever retransmitted -> reordering. Alas, we cannot use it
+ * when segment was retransmitted.
+ * 2. The last flaw is solved with D-SACK. D-SACK arrives
+ * for retransmitted and already SACKed segment -> reordering..
+ * Both of these heuristics are not used in Loss state, when we cannot
+ * account for retransmits accurately.
+ */
+static int
+tcp_sacktag_write_queue(struct sock *sk, struct sk_buff *ack_skb, u32 prior_snd_una)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ unsigned char *ptr = ack_skb->h.raw + TCP_SKB_CB(ack_skb)->sacked;
+ struct tcp_sack_block *sp = (struct tcp_sack_block *)(ptr+2);
+ int num_sacks = (ptr[1] - TCPOLEN_SACK_BASE)>>3;
+ int reord = tp->packets_out;
+ int prior_fackets;
+ u32 lost_retrans = 0;
+ int flag = 0;
+ int i;
+
+ if (!tp->sacked_out)
+ tp->fackets_out = 0;
+ prior_fackets = tp->fackets_out;
+
+ for (i=0; i<num_sacks; i++, sp++) {
+ struct sk_buff *skb;
+ __u32 start_seq = ntohl(sp->start_seq);
+ __u32 end_seq = ntohl(sp->end_seq);
+ int fack_count = 0;
+ int dup_sack = 0;
+
+ /* Check for D-SACK. */
+ if (i == 0) {
+ u32 ack = TCP_SKB_CB(ack_skb)->ack_seq;
+
+ if (before(start_seq, ack)) {
+ dup_sack = 1;
+ tp->sack_ok |= 4;
+ NET_INC_STATS_BH(TCPDSACKRecv);
+ } else if (num_sacks > 1 &&
+ !after(end_seq, ntohl(sp[1].end_seq)) &&
+ !before(start_seq, ntohl(sp[1].start_seq))) {
+ dup_sack = 1;
+ tp->sack_ok |= 4;
+ NET_INC_STATS_BH(TCPDSACKOfoRecv);
+ }
+
+ /* D-SACK for already forgotten data...
+ * Do dumb counting. */
+ if (dup_sack &&
+ !after(end_seq, prior_snd_una) &&
+ after(end_seq, tp->undo_marker))
+ tp->undo_retrans--;
+
+ /* Eliminate too old ACKs, but take into
+ * account more or less fresh ones, they can
+ * contain valid SACK info.
+ */
+ if (before(ack, prior_snd_una-tp->max_window))
+ return 0;
+ }
+
+ /* Event "B" in the comment above. */
+ if (after(end_seq, tp->high_seq))
+ flag |= FLAG_DATA_LOST;
+
+ for_retrans_queue(skb, sk, tp) {
+ u8 sacked = TCP_SKB_CB(skb)->sacked;
+ int in_sack;
+
+ /* The retransmission queue is always in order, so
+ * we can short-circuit the walk early.
+ */
+ if(!before(TCP_SKB_CB(skb)->seq, end_seq))
+ break;
+
+ fack_count++;
+
+ in_sack = !after(start_seq, TCP_SKB_CB(skb)->seq) &&
+ !before(end_seq, TCP_SKB_CB(skb)->end_seq);
+
+ /* Account D-SACK for retransmitted packet. */
+ if ((dup_sack && in_sack) &&
+ (sacked & TCPCB_RETRANS) &&
+ after(TCP_SKB_CB(skb)->end_seq, tp->undo_marker))
+ tp->undo_retrans--;
+
+ /* The frame is ACKed. */
+ if (!after(TCP_SKB_CB(skb)->end_seq, tp->snd_una)) {
+ if (sacked&TCPCB_RETRANS) {
+ if ((dup_sack && in_sack) &&
+ (sacked&TCPCB_SACKED_ACKED))
+ reord = min(fack_count, reord);
+ } else {
+ /* If it was in a hole, we detected reordering. */
+ if (fack_count < prior_fackets &&
+ !(sacked&TCPCB_SACKED_ACKED))
+ reord = min(fack_count, reord);
+ }
+
+ /* Nothing to do; acked frame is about to be dropped. */
+ continue;
+ }
+
+ if ((sacked&TCPCB_SACKED_RETRANS) &&
+ after(end_seq, TCP_SKB_CB(skb)->ack_seq) &&
+ (!lost_retrans || after(end_seq, lost_retrans)))
+ lost_retrans = end_seq;
+
+ if (!in_sack)
+ continue;
+
+ if (!(sacked&TCPCB_SACKED_ACKED)) {
+ if (sacked & TCPCB_SACKED_RETRANS) {
+ /* If the segment is not tagged as lost,
+ * we do not clear RETRANS, believing
+ * that retransmission is still in flight.
+ */
+ if (sacked & TCPCB_LOST) {
+ TCP_SKB_CB(skb)->sacked &= ~(TCPCB_LOST|TCPCB_SACKED_RETRANS);
+ tp->lost_out--;
+ tp->retrans_out--;
+ }
+ } else {
+ /* New sack for not retransmitted frame,
+ * which was in hole. It is reordering.
+ */
+ if (!(sacked & TCPCB_RETRANS) &&
+ fack_count < prior_fackets)
+ reord = min(fack_count, reord);
+
+ if (sacked & TCPCB_LOST) {
+ TCP_SKB_CB(skb)->sacked &= ~TCPCB_LOST;
+ tp->lost_out--;
+ }
+ }
+
+ TCP_SKB_CB(skb)->sacked |= TCPCB_SACKED_ACKED;
+ flag |= FLAG_DATA_SACKED;
+ tp->sacked_out++;
+
+ if (fack_count > tp->fackets_out)
+ tp->fackets_out = fack_count;
+ } else {
+ if (dup_sack && (sacked&TCPCB_RETRANS))
+ reord = min(fack_count, reord);
+ }
+
+ /* D-SACK. We can detect redundant retransmission
+ * in S|R and plain R frames and clear it.
+ * undo_retrans is decreased above, L|R frames
+ * are accounted above as well.
+ */
+ if (dup_sack &&
+ (TCP_SKB_CB(skb)->sacked&TCPCB_SACKED_RETRANS)) {
+ TCP_SKB_CB(skb)->sacked &= ~TCPCB_SACKED_RETRANS;
+ tp->retrans_out--;
+ }
+ }
+ }
+
+ /* Check for lost retransmit. This superb idea is
+ * borrowed from "ratehalving". Event "C".
+ * Later note: FACK people cheated me again 8),
+ * we have to account for reordering! Ugly,
+ * but should help.
+ */
+ if (lost_retrans && tp->ca_state == TCP_CA_Recovery) {
+ struct sk_buff *skb;
+
+ for_retrans_queue(skb, sk, tp) {
+ if (after(TCP_SKB_CB(skb)->seq, lost_retrans))
+ break;
+ if (!after(TCP_SKB_CB(skb)->end_seq, tp->snd_una))
+ continue;
+ if ((TCP_SKB_CB(skb)->sacked&TCPCB_SACKED_RETRANS) &&
+ after(lost_retrans, TCP_SKB_CB(skb)->ack_seq) &&
+ (IsFack(tp) ||
+ !before(lost_retrans, TCP_SKB_CB(skb)->ack_seq+tp->reordering*tp->mss_cache))) {
+ TCP_SKB_CB(skb)->sacked &= ~TCPCB_SACKED_RETRANS;
+ tp->retrans_out--;
+
+ if (!(TCP_SKB_CB(skb)->sacked&(TCPCB_LOST|TCPCB_SACKED_ACKED))) {
+ tp->lost_out++;
+ TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;
+ flag |= FLAG_DATA_SACKED;
+ NET_INC_STATS_BH(TCPLostRetransmit);
+ }
+ }
+ }
+ }
+
+ tp->left_out = tp->sacked_out + tp->lost_out;
+
+ if (reord < tp->fackets_out && tp->ca_state != TCP_CA_Loss)
+ tcp_update_reordering(tp, (tp->fackets_out+1)-reord, 0);
+
+#if FASTRETRANS_DEBUG > 0
+ BUG_TRAP((int)tp->sacked_out >= 0);
+ BUG_TRAP((int)tp->lost_out >= 0);
+ BUG_TRAP((int)tp->retrans_out >= 0);
+ BUG_TRAP((int)tcp_packets_in_flight(tp) >= 0);
+#endif
+ return flag;
+}
+
+/* RTO occurred, but do not yet enter loss state. Instead, transmit two new
+ * segments to see from the next ACKs whether any data was really missing.
+ * If the RTO was spurious, new ACKs should arrive.
+ */
+void tcp_enter_frto(struct sock *sk)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ struct sk_buff *skb;
+
+ tp->frto_counter = 1;
+
+ if (tp->ca_state <= TCP_CA_Disorder ||
+ tp->snd_una == tp->high_seq ||
+ (tp->ca_state == TCP_CA_Loss && !tp->retransmits)) {
+ tp->prior_ssthresh = tcp_current_ssthresh(tp);
+ tp->snd_ssthresh = tcp_recalc_ssthresh(tp);
+ }
+
+ /* Have to clear retransmission markers here to keep the bookkeeping
+ * in shape, even though we are not yet in Loss state.
+ * If something was really lost, it is eventually caught up
+ * in tcp_enter_frto_loss.
+ */
+ tp->retrans_out = 0;
+ tp->undo_marker = tp->snd_una;
+ tp->undo_retrans = 0;
+
+ for_retrans_queue(skb, sk, tp) {
+ TCP_SKB_CB(skb)->sacked &= ~TCPCB_RETRANS;
+ }
+ tcp_sync_left_out(tp);
+
+ tcp_set_ca_state(tp, TCP_CA_Open);
+ tp->frto_highmark = tp->snd_nxt;
+}
+
+/* Enter Loss state after F-RTO was applied. Dupack arrived after RTO,
+ * which indicates that we should follow the traditional RTO recovery,
+ * i.e. mark everything lost and do go-back-N retransmission.
+ */
+void tcp_enter_frto_loss(struct sock *sk)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ struct sk_buff *skb;
+ int cnt = 0;
+
+ tp->sacked_out = 0;
+ tp->lost_out = 0;
+ tp->fackets_out = 0;
+
+ for_retrans_queue(skb, sk, tp) {
+ cnt++;
+ TCP_SKB_CB(skb)->sacked &= ~TCPCB_LOST;
+ if (!(TCP_SKB_CB(skb)->sacked&TCPCB_SACKED_ACKED)) {
+
+ /* Do not mark those segments lost that were
+ * forward transmitted after RTO
+ */
+ if(!after(TCP_SKB_CB(skb)->end_seq,
+ tp->frto_highmark)) {
+ TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;
+ tp->lost_out++;
+ }
+ } else {
+ tp->sacked_out++;
+ tp->fackets_out = cnt;
+ }
+ }
+ tcp_sync_left_out(tp);
+
+ tp->snd_cwnd = tp->frto_counter + tcp_packets_in_flight(tp)+1;
+ tp->snd_cwnd_cnt = 0;
+ tp->snd_cwnd_stamp = tcp_time_stamp;
+ tp->undo_marker = 0;
+ tp->frto_counter = 0;
+
+ tp->reordering = min_t(unsigned int, tp->reordering,
+ sysctl_tcp_reordering);
+ tcp_set_ca_state(tp, TCP_CA_Loss);
+ tp->high_seq = tp->frto_highmark;
+ TCP_ECN_queue_cwr(tp);
+
+ init_bictcp(tp);
+}
+
+void tcp_clear_retrans(struct tcp_opt *tp)
+{
+ tp->left_out = 0;
+ tp->retrans_out = 0;
+
+ tp->fackets_out = 0;
+ tp->sacked_out = 0;
+ tp->lost_out = 0;
+
+ tp->undo_marker = 0;
+ tp->undo_retrans = 0;
+}
+
+/* Enter Loss state. If "how" is not zero, forget all SACK information
+ * and reset tags completely, otherwise preserve SACKs. If receiver
+ * dropped its ofo queue, we will know this due to reneging detection.
+ */
+void tcp_enter_loss(struct sock *sk, int how)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ struct sk_buff *skb;
+ int cnt = 0;
+
+ /* Reduce ssthresh if it has not yet been made inside this window. */
+ if (tp->ca_state <= TCP_CA_Disorder ||
+ tp->snd_una == tp->high_seq ||
+ (tp->ca_state == TCP_CA_Loss && !tp->retransmits)) {
+ tp->prior_ssthresh = tcp_current_ssthresh(tp);
+
+ if (!(tcp_westwood_ssthresh(tp)))
+ tp->snd_ssthresh = tcp_recalc_ssthresh(tp);
+ }
+ tp->snd_cwnd = 1;
+ tp->snd_cwnd_cnt = 0;
+ tp->snd_cwnd_stamp = tcp_time_stamp;
+
+ tcp_clear_retrans(tp);
+
+ /* Push undo marker, if it was plain RTO and nothing
+ * was retransmitted. */
+ if (!how)
+ tp->undo_marker = tp->snd_una;
+
+ for_retrans_queue(skb, sk, tp) {
+ cnt++;
+ if (TCP_SKB_CB(skb)->sacked&TCPCB_RETRANS)
+ tp->undo_marker = 0;
+ TCP_SKB_CB(skb)->sacked &= (~TCPCB_TAGBITS)|TCPCB_SACKED_ACKED;
+ if (!(TCP_SKB_CB(skb)->sacked&TCPCB_SACKED_ACKED) || how) {
+ TCP_SKB_CB(skb)->sacked &= ~TCPCB_SACKED_ACKED;
+ TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;
+ tp->lost_out++;
+ } else {
+ tp->sacked_out++;
+ tp->fackets_out = cnt;
+ }
+ }
+ tcp_sync_left_out(tp);
+
+ tp->reordering = min_t(unsigned int, tp->reordering, sysctl_tcp_reordering);
+ tcp_set_ca_state(tp, TCP_CA_Loss);
+ tp->high_seq = tp->snd_nxt;
+ TCP_ECN_queue_cwr(tp);
+}
+
+static int tcp_check_sack_reneging(struct sock *sk, struct tcp_opt *tp)
+{
+ struct sk_buff *skb;
+
+ /* If ACK arrived pointing to a remembered SACK,
+ * it means that our remembered SACKs do not reflect
+ * real state of receiver i.e.
+ * receiver _host_ is heavily congested (or buggy).
+ * Do processing similar to RTO timeout.
+ */
+ if ((skb = skb_peek(&sk->write_queue)) != NULL &&
+ (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED)) {
+ NET_INC_STATS_BH(TCPSACKReneging);
+
+ tcp_enter_loss(sk, 1);
+ tp->retransmits++;
+ tcp_retransmit_skb(sk, skb_peek(&sk->write_queue));
+ tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
+ return 1;
+ }
+ return 0;
+}
+
+static inline int tcp_fackets_out(struct tcp_opt *tp)
+{
+ return IsReno(tp) ? tp->sacked_out+1 : tp->fackets_out;
+}
+
+static inline int tcp_skb_timedout(struct tcp_opt *tp, struct sk_buff *skb)
+{
+ return (tcp_time_stamp - TCP_SKB_CB(skb)->when > tp->rto);
+}
+
+static inline int tcp_head_timedout(struct sock *sk, struct tcp_opt *tp)
+{
+ return tp->packets_out && tcp_skb_timedout(tp, skb_peek(&sk->write_queue));
+}
+
+/* Linux NewReno/SACK/FACK/ECN state machine.
+ * --------------------------------------
+ *
+ * "Open" Normal state, no dubious events, fast path.
+ * "Disorder" In all the respects it is "Open",
+ * but requires a bit more attention. It is entered when
+ * we see some SACKs or dupacks. It is split of "Open"
+ * mainly to move some processing from fast path to slow one.
+ * "CWR" CWND was reduced due to some Congestion Notification event.
+ * It can be ECN, ICMP source quench, local device congestion.
+ * "Recovery" CWND was reduced, we are fast-retransmitting.
+ * "Loss" CWND was reduced due to RTO timeout or SACK reneging.
+ *
+ * tcp_fastretrans_alert() is entered:
+ * - each incoming ACK, if state is not "Open"
+ * - when arrived ACK is unusual, namely:
+ * * SACK
+ * * Duplicate ACK.
+ * * ECN ECE.
+ *
+ * Counting packets in flight is pretty simple.
+ *
+ * in_flight = packets_out - left_out + retrans_out
+ *
+ * packets_out is SND.NXT-SND.UNA counted in packets.
+ *
+ * retrans_out is number of retransmitted segments.
+ *
+ * left_out is number of segments left network, but not ACKed yet.
+ *
+ * left_out = sacked_out + lost_out
+ *
+ * sacked_out: Packets, which arrived to receiver out of order
+ * and hence not ACKed. With SACKs this number is simply
+ * amount of SACKed data. Even without SACKs
+ * it is easy to give pretty reliable estimate of this number,
+ * counting duplicate ACKs.
+ *
+ * lost_out: Packets lost by network. TCP has no explicit
+ * "loss notification" feedback from network (for now).
+ * It means that this number can be only _guessed_.
+ * Actually, it is the heuristics to predict lossage that
+ * distinguishes different algorithms.
+ *
+ * F.e. after RTO, when all the queue is considered as lost,
+ * lost_out = packets_out and in_flight = retrans_out.
+ *
+ * Essentially, we have now two algorithms counting
+ * lost packets.
+ *
+ * FACK: It is the simplest heuristics. As soon as we decided
+ * that something is lost, we decide that _all_ not SACKed
+ * packets until the most forward SACK are lost. I.e.
+ * lost_out = fackets_out - sacked_out and left_out = fackets_out.
+ * It is absolutely correct estimate, if network does not reorder
+ * packets. And it loses any connection to reality when reordering
+ * takes place. We use FACK by default until reordering
+ * is suspected on the path to this destination.
+ *
+ * NewReno: when Recovery is entered, we assume that one segment
+ * is lost (classic Reno). While we are in Recovery and
+ * a partial ACK arrives, we assume that one more packet
+ * is lost (NewReno). This heuristics are the same in NewReno
+ * and SACK.
+ *
+ * Imagine, that's all! Forget about all this shamanism about CWND inflation
+ * deflation etc. CWND is real congestion window, never inflated, changes
+ * only according to classic VJ rules.
+ *
+ * Really tricky (and requiring careful tuning) part of algorithm
+ * is hidden in functions tcp_time_to_recover() and tcp_xmit_retransmit_queue().
+ * The first determines the moment _when_ we should reduce CWND and,
+ * hence, slow down forward transmission. In fact, it determines the moment
+ * when we decide that hole is caused by loss, rather than by a reorder.
+ *
+ * tcp_xmit_retransmit_queue() decides, _what_ we should retransmit to fill
+ * holes, caused by lost packets.
+ *
+ * And the most logically complicated part of algorithm is undo
+ * heuristics. We detect false retransmits due to both too early
+ * fast retransmit (reordering) and underestimated RTO, analyzing
+ * timestamps and D-SACKs. When we detect that some segments were
+ * retransmitted by mistake and CWND reduction was wrong, we undo
+ * window reduction and abort recovery phase. This logic is hidden
+ * inside several functions named tcp_try_undo_<something>.
+ */
+
+/* This function decides, when we should leave Disordered state
+ * and enter Recovery phase, reducing congestion window.
+ *
+ * Main question: may we further continue forward transmission
+ * with the same cwnd?
+ */
+static int
+tcp_time_to_recover(struct sock *sk, struct tcp_opt *tp)
+{
+ /* Trick#1: The loss is proven. */
+ if (tp->lost_out)
+ return 1;
+
+ /* Not-A-Trick#2 : Classic rule... */
+ if (tcp_fackets_out(tp) > tp->reordering)
+ return 1;
+
+ /* Trick#3 : when we use RFC2988 timer restart, fast
+ * retransmit can be triggered by timeout of queue head.
+ */
+ if (tcp_head_timedout(sk, tp))
+ return 1;
+
+ /* Trick#4: It is still not OK... But will it be useful to delay
+ * recovery more?
+ */
+ if (tp->packets_out <= tp->reordering &&
+ tp->sacked_out >= max_t(__u32, tp->packets_out/2, sysctl_tcp_reordering) &&
+ !tcp_may_send_now(sk, tp)) {
+ /* We have nothing to send. This connection is limited
+ * either by receiver window or by application.
+ */
+ return 1;
+ }
+
+ return 0;
+}
+
+/* If we receive more dupacks than we expected counting segments
+ * in assumption of absent reordering, interpret this as reordering.
+ * The only another reason could be bug in receiver TCP.
+ */
+static void tcp_check_reno_reordering(struct tcp_opt *tp, int addend)
+{
+ u32 holes;
+
+ holes = max(tp->lost_out, 1U);
+ holes = min(holes, tp->packets_out);
+
+ if (tp->sacked_out + holes > tp->packets_out) {
+ tp->sacked_out = tp->packets_out - holes;
+ tcp_update_reordering(tp, tp->packets_out+addend, 0);
+ }
+}
+
+/* Emulate SACKs for SACKless connection: account for a new dupack. */
+
+static void tcp_add_reno_sack(struct tcp_opt *tp)
+{
+ ++tp->sacked_out;
+ tcp_check_reno_reordering(tp, 0);
+ tcp_sync_left_out(tp);
+}
+
+/* Account for ACK, ACKing some data in Reno Recovery phase. */
+
+static void tcp_remove_reno_sacks(struct sock *sk, struct tcp_opt *tp, int acked)
+{
+ if (acked > 0) {
+ /* One ACK acked hole. The rest eat duplicate ACKs. */
+ if (acked-1 >= tp->sacked_out)
+ tp->sacked_out = 0;
+ else
+ tp->sacked_out -= acked-1;
+ }
+ tcp_check_reno_reordering(tp, acked);
+ tcp_sync_left_out(tp);
+}
+
+static inline void tcp_reset_reno_sack(struct tcp_opt *tp)
+{
+ tp->sacked_out = 0;
+ tp->left_out = tp->lost_out;
+}
+
+/* Mark head of queue up as lost. */
+static void
+tcp_mark_head_lost(struct sock *sk, struct tcp_opt *tp, int packets, u32 high_seq)
+{
+ struct sk_buff *skb;
+ int cnt = packets;
+
+ BUG_TRAP(cnt <= tp->packets_out);
+
+ for_retrans_queue(skb, sk, tp) {
+ if (--cnt < 0 || after(TCP_SKB_CB(skb)->end_seq, high_seq))
+ break;
+ if (!(TCP_SKB_CB(skb)->sacked&TCPCB_TAGBITS)) {
+ TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;
+ tp->lost_out++;
+ }
+ }
+ tcp_sync_left_out(tp);
+}
+
+/* Account newly detected lost packet(s) */
+
+static void tcp_update_scoreboard(struct sock *sk, struct tcp_opt *tp)
+{
+ if (IsFack(tp)) {
+ int lost = tp->fackets_out - tp->reordering;
+ if (lost <= 0)
+ lost = 1;
+ tcp_mark_head_lost(sk, tp, lost, tp->high_seq);
+ } else {
+ tcp_mark_head_lost(sk, tp, 1, tp->high_seq);
+ }
+
+ /* New heuristics: it is possible only after we switched
+ * to restart timer each time when something is ACKed.
+ * Hence, we can detect timed out packets during fast
+ * retransmit without falling to slow start.
+ */
+ if (tcp_head_timedout(sk, tp)) {
+ struct sk_buff *skb;
+
+ for_retrans_queue(skb, sk, tp) {
+ if (tcp_skb_timedout(tp, skb) &&
+ !(TCP_SKB_CB(skb)->sacked&TCPCB_TAGBITS)) {
+ TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;
+ tp->lost_out++;
+ }
+ }
+ tcp_sync_left_out(tp);
+ }
+}
+
+/* CWND moderation, preventing bursts due to too big ACKs
+ * in dubious situations.
+ */
+static __inline__ void tcp_moderate_cwnd(struct tcp_opt *tp)
+{
+ tp->snd_cwnd = min(tp->snd_cwnd,
+ tcp_packets_in_flight(tp)+tcp_max_burst(tp));
+ tp->snd_cwnd_stamp = tcp_time_stamp;
+}
+
+/* Decrease cwnd each second ack. */
+
+static void tcp_cwnd_down(struct tcp_opt *tp)
+{
+ int decr = tp->snd_cwnd_cnt + 1;
+ __u32 limit;
+
+ /*
+ * TCP Westwood
+ * Here limit is evaluated as BWestimation*RTTmin (for obtaining it
+ * in packets we use mss_cache). If sysctl_tcp_westwood is off
+ * tcp_westwood_bw_rttmin() returns 0. In such case snd_ssthresh is
+ * still used as usual. It prevents other strange cases in which
+ * BWE*RTTmin could assume value 0. It should not happen but...
+ */
+
+ if (!(limit = tcp_westwood_bw_rttmin(tp)))
+ limit = tp->snd_ssthresh/2;
+
+ tp->snd_cwnd_cnt = decr&1;
+ decr >>= 1;
+
+ if (decr && tp->snd_cwnd > limit)
+ tp->snd_cwnd -= decr;
+
+ tp->snd_cwnd = min(tp->snd_cwnd, tcp_packets_in_flight(tp)+1);
+ tp->snd_cwnd_stamp = tcp_time_stamp;
+}
+
+/* Nothing was retransmitted or returned timestamp is less
+ * than timestamp of the first retransmission.
+ */
+static __inline__ int tcp_packet_delayed(struct tcp_opt *tp)
+{
+ return !tp->retrans_stamp ||
+ (tp->saw_tstamp && tp->rcv_tsecr &&
+ (__s32)(tp->rcv_tsecr - tp->retrans_stamp) < 0);
+}
+
+/* Undo procedures. */
+
+#if FASTRETRANS_DEBUG > 1
+static void DBGUNDO(struct sock *sk, struct tcp_opt *tp, const char *msg)
+{
+ printk(KERN_DEBUG "Undo %s %u.%u.%u.%u/%u c%u l%u ss%u/%u p%u\n",
+ msg,
+ NIPQUAD(sk->daddr), ntohs(sk->dport),
+ tp->snd_cwnd, tp->left_out,
+ tp->snd_ssthresh, tp->prior_ssthresh, tp->packets_out);
+}
+#else
+#define DBGUNDO(x...) do { } while (0)
+#endif
+
+static void tcp_undo_cwr(struct tcp_opt *tp, int undo)
+{
+ if (tp->prior_ssthresh) {
+ if (tcp_is_bic(tp))
+ tp->snd_cwnd = max(tp->snd_cwnd, tp->bictcp.last_max_cwnd);
+ else
+ tp->snd_cwnd = max(tp->snd_cwnd, tp->snd_ssthresh<<1);
+
+ if (undo && tp->prior_ssthresh > tp->snd_ssthresh) {
+ tp->snd_ssthresh = tp->prior_ssthresh;
+ TCP_ECN_withdraw_cwr(tp);
+ }
+ } else {
+ tp->snd_cwnd = max(tp->snd_cwnd, tp->snd_ssthresh);
+ }
+ tcp_moderate_cwnd(tp);
+ tp->snd_cwnd_stamp = tcp_time_stamp;
+}
+
+static inline int tcp_may_undo(struct tcp_opt *tp)
+{
+ return tp->undo_marker &&
+ (!tp->undo_retrans || tcp_packet_delayed(tp));
+}
+
+/* People celebrate: "We love our President!" */
+static int tcp_try_undo_recovery(struct sock *sk, struct tcp_opt *tp)
+{
+ if (tcp_may_undo(tp)) {
+ /* Happy end! We did not retransmit anything
+ * or our original transmission succeeded.
+ */
+ DBGUNDO(sk, tp, tp->ca_state == TCP_CA_Loss ? "loss" : "retrans");
+ tcp_undo_cwr(tp, 1);
+ if (tp->ca_state == TCP_CA_Loss)
+ NET_INC_STATS_BH(TCPLossUndo);
+ else
+ NET_INC_STATS_BH(TCPFullUndo);
+ tp->undo_marker = 0;
+ }
+ if (tp->snd_una == tp->high_seq && IsReno(tp)) {
+ /* Hold old state until something *above* high_seq
+ * is ACKed. For Reno it is MUST to prevent false
+ * fast retransmits (RFC2582). SACK TCP is safe. */
+ tcp_moderate_cwnd(tp);
+ return 1;
+ }
+ tcp_set_ca_state(tp, TCP_CA_Open);
+ return 0;
+}
+
+/* Try to undo cwnd reduction, because D-SACKs acked all retransmitted data */
+static void tcp_try_undo_dsack(struct sock *sk, struct tcp_opt *tp)
+{
+ if (tp->undo_marker && !tp->undo_retrans) {
+ DBGUNDO(sk, tp, "D-SACK");
+ tcp_undo_cwr(tp, 1);
+ tp->undo_marker = 0;
+ NET_INC_STATS_BH(TCPDSACKUndo);
+ }
+}
+
+/* Undo during fast recovery after partial ACK. */
+
+static int tcp_try_undo_partial(struct sock *sk, struct tcp_opt *tp, int acked)
+{
+ /* Partial ACK arrived. Force Hoe's retransmit. */
+ int failed = IsReno(tp) || tp->fackets_out>tp->reordering;
+
+ if (tcp_may_undo(tp)) {
+ /* Plain luck! Hole if filled with delayed
+ * packet, rather than with a retransmit.
+ */
+ if (tp->retrans_out == 0)
+ tp->retrans_stamp = 0;
+
+ tcp_update_reordering(tp, tcp_fackets_out(tp)+acked, 1);
+
+ DBGUNDO(sk, tp, "Hoe");
+ tcp_undo_cwr(tp, 0);
+ NET_INC_STATS_BH(TCPPartialUndo);
+
+ /* So... Do not make Hoe's retransmit yet.
+ * If the first packet was delayed, the rest
+ * ones are most probably delayed as well.
+ */
+ failed = 0;
+ }
+ return failed;
+}
+
+/* Undo during loss recovery after partial ACK. */
+static int tcp_try_undo_loss(struct sock *sk, struct tcp_opt *tp)
+{
+ if (tcp_may_undo(tp)) {
+ struct sk_buff *skb;
+ for_retrans_queue(skb, sk, tp) {
+ TCP_SKB_CB(skb)->sacked &= ~TCPCB_LOST;
+ }
+ DBGUNDO(sk, tp, "partial loss");
+ tp->lost_out = 0;
+ tp->left_out = tp->sacked_out;
+ tcp_undo_cwr(tp, 1);
+ NET_INC_STATS_BH(TCPLossUndo);
+ tp->retransmits = 0;
+ tp->undo_marker = 0;
+ if (!IsReno(tp))
+ tcp_set_ca_state(tp, TCP_CA_Open);
+ return 1;
+ }
+ return 0;
+}
+
+static __inline__ void tcp_complete_cwr(struct tcp_opt *tp)
+{
+ if (!(tcp_westwood_complete_cwr(tp)))
+ tp->snd_cwnd = min(tp->snd_cwnd, tp->snd_ssthresh);
+ tp->snd_cwnd_stamp = tcp_time_stamp;
+}
+
+static void tcp_try_to_open(struct sock *sk, struct tcp_opt *tp, int flag)
+{
+ tp->left_out = tp->sacked_out;
+
+ if (tp->retrans_out == 0)
+ tp->retrans_stamp = 0;
+
+ if (flag&FLAG_ECE)
+ tcp_enter_cwr(tp);
+
+ if (tp->ca_state != TCP_CA_CWR) {
+ int state = TCP_CA_Open;
+
+ if (tp->left_out ||
+ tp->retrans_out ||
+ tp->undo_marker)
+ state = TCP_CA_Disorder;
+
+ if (tp->ca_state != state) {
+ tcp_set_ca_state(tp, state);
+ tp->high_seq = tp->snd_nxt;
+ }
+ tcp_moderate_cwnd(tp);
+ } else {
+ tcp_cwnd_down(tp);
+ }
+}
+
+/* Process an event, which can update packets-in-flight not trivially.
+ * Main goal of this function is to calculate new estimate for left_out,
+ * taking into account both packets sitting in receiver's buffer and
+ * packets lost by network.
+ *
+ * Besides that it does CWND reduction, when packet loss is detected
+ * and changes state of machine.
+ *
+ * It does _not_ decide what to send, it is made in function
+ * tcp_xmit_retransmit_queue().
+ */
+static void
+tcp_fastretrans_alert(struct sock *sk, u32 prior_snd_una,
+ int prior_packets, int flag)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int is_dupack = (tp->snd_una == prior_snd_una && !(flag&FLAG_NOT_DUP));
+
+ /* Some technical things:
+ * 1. Reno does not count dupacks (sacked_out) automatically. */
+ if (!tp->packets_out)
+ tp->sacked_out = 0;
+ /* 2. SACK counts snd_fack in packets inaccurately. */
+ if (tp->sacked_out == 0)
+ tp->fackets_out = 0;
+
+ /* Now state machine starts.
+ * A. ECE, hence prohibit cwnd undoing, the reduction is required. */
+ if (flag&FLAG_ECE)
+ tp->prior_ssthresh = 0;
+
+ /* B. In all the states check for reneging SACKs. */
+ if (tp->sacked_out && tcp_check_sack_reneging(sk, tp))
+ return;
+
+ /* C. Process data loss notification, provided it is valid. */
+ if ((flag&FLAG_DATA_LOST) &&
+ before(tp->snd_una, tp->high_seq) &&
+ tp->ca_state != TCP_CA_Open &&
+ tp->fackets_out > tp->reordering) {
+ tcp_mark_head_lost(sk, tp, tp->fackets_out-tp->reordering, tp->high_seq);
+ NET_INC_STATS_BH(TCPLoss);
+ }
+
+ /* D. Synchronize left_out to current state. */
+ tcp_sync_left_out(tp);
+
+ /* E. Check state exit conditions. State can be terminated
+ * when high_seq is ACKed. */
+ if (tp->ca_state == TCP_CA_Open) {
+ if (!sysctl_tcp_frto)
+ BUG_TRAP(tp->retrans_out == 0);
+ tp->retrans_stamp = 0;
+ } else if (!before(tp->snd_una, tp->high_seq)) {
+ switch (tp->ca_state) {
+ case TCP_CA_Loss:
+ tp->retransmits = 0;
+ if (tcp_try_undo_recovery(sk, tp))
+ return;
+ break;
+
+ case TCP_CA_CWR:
+ /* CWR is to be held something *above* high_seq
+ * is ACKed for CWR bit to reach receiver. */
+ if (tp->snd_una != tp->high_seq) {
+ tcp_complete_cwr(tp);
+ tcp_set_ca_state(tp, TCP_CA_Open);
+ }
+ break;
+
+ case TCP_CA_Disorder:
+ tcp_try_undo_dsack(sk, tp);
+ if (!tp->undo_marker ||
+ /* For SACK case do not Open to allow to undo
+ * catching for all duplicate ACKs. */
+ IsReno(tp) || tp->snd_una != tp->high_seq) {
+ tp->undo_marker = 0;
+ tcp_set_ca_state(tp, TCP_CA_Open);
+ }
+ break;
+
+ case TCP_CA_Recovery:
+ if (IsReno(tp))
+ tcp_reset_reno_sack(tp);
+ if (tcp_try_undo_recovery(sk, tp))
+ return;
+ tcp_complete_cwr(tp);
+ break;
+ }
+ }
+
+ /* F. Process state. */
+ switch (tp->ca_state) {
+ case TCP_CA_Recovery:
+ if (prior_snd_una == tp->snd_una) {
+ if (IsReno(tp) && is_dupack)
+ tcp_add_reno_sack(tp);
+ } else {
+ int acked = prior_packets - tp->packets_out;
+ if (IsReno(tp))
+ tcp_remove_reno_sacks(sk, tp, acked);
+ is_dupack = tcp_try_undo_partial(sk, tp, acked);
+ }
+ break;
+ case TCP_CA_Loss:
+ if (flag&FLAG_DATA_ACKED)
+ tp->retransmits = 0;
+ if (!tcp_try_undo_loss(sk, tp)) {
+ tcp_moderate_cwnd(tp);
+ tcp_xmit_retransmit_queue(sk);
+ return;
+ }
+ if (tp->ca_state != TCP_CA_Open)
+ return;
+ /* Loss is undone; fall through to processing in Open state. */
+ default:
+ if (IsReno(tp)) {
+ if (tp->snd_una != prior_snd_una)
+ tcp_reset_reno_sack(tp);
+ if (is_dupack)
+ tcp_add_reno_sack(tp);
+ }
+
+ if (tp->ca_state == TCP_CA_Disorder)
+ tcp_try_undo_dsack(sk, tp);
+
+ if (!tcp_time_to_recover(sk, tp)) {
+ tcp_try_to_open(sk, tp, flag);
+ return;
+ }
+
+ /* Otherwise enter Recovery state */
+
+ if (IsReno(tp))
+ NET_INC_STATS_BH(TCPRenoRecovery);
+ else
+ NET_INC_STATS_BH(TCPSackRecovery);
+
+ tp->high_seq = tp->snd_nxt;
+ tp->prior_ssthresh = 0;
+ tp->undo_marker = tp->snd_una;
+ tp->undo_retrans = tp->retrans_out;
+
+ if (tp->ca_state < TCP_CA_CWR) {
+ if (!(flag&FLAG_ECE))
+ tp->prior_ssthresh = tcp_current_ssthresh(tp);
+ tp->snd_ssthresh = tcp_recalc_ssthresh(tp);
+ TCP_ECN_queue_cwr(tp);
+ }
+
+ tp->snd_cwnd_cnt = 0;
+ tcp_set_ca_state(tp, TCP_CA_Recovery);
+ }
+
+ if (is_dupack || tcp_head_timedout(sk, tp))
+ tcp_update_scoreboard(sk, tp);
+ tcp_cwnd_down(tp);
+ tcp_xmit_retransmit_queue(sk);
+}
+
+/* Read draft-ietf-tcplw-high-performance before mucking
+ * with this code. (Superceeds RFC1323)
+ */
+static void tcp_ack_saw_tstamp(struct tcp_opt *tp, int flag)
+{
+ __u32 seq_rtt;
+
+ /* RTTM Rule: A TSecr value received in a segment is used to
+ * update the averaged RTT measurement only if the segment
+ * acknowledges some new data, i.e., only if it advances the
+ * left edge of the send window.
+ *
+ * See draft-ietf-tcplw-high-performance-00, section 3.3.
+ * 1998/04/10 Andrey V. Savochkin <saw@msu.ru>
+ *
+ * Changed: reset backoff as soon as we see the first valid sample.
+ * If we do not, we get strongly overstimated rto. With timestamps
+ * samples are accepted even from very old segments: f.e., when rtt=1
+ * increases to 8, we retransmit 5 times and after 8 seconds delayed
+ * answer arrives rto becomes 120 seconds! If at least one of segments
+ * in window is lost... Voila. --ANK (010210)
+ */
+ seq_rtt = tcp_time_stamp - tp->rcv_tsecr;
+ tcp_rtt_estimator(tp, seq_rtt);
+ tcp_set_rto(tp);
+ tp->backoff = 0;
+ tcp_bound_rto(tp);
+}
+
+static void tcp_ack_no_tstamp(struct tcp_opt *tp, u32 seq_rtt, int flag)
+{
+ /* We don't have a timestamp. Can only use
+ * packets that are not retransmitted to determine
+ * rtt estimates. Also, we must not reset the
+ * backoff for rto until we get a non-retransmitted
+ * packet. This allows us to deal with a situation
+ * where the network delay has increased suddenly.
+ * I.e. Karn's algorithm. (SIGCOMM '87, p5.)
+ */
+
+ if (flag & FLAG_RETRANS_DATA_ACKED)
+ return;
+
+ tcp_rtt_estimator(tp, seq_rtt);
+ tcp_set_rto(tp);
+ tp->backoff = 0;
+ tcp_bound_rto(tp);
+}
+
+static __inline__ void
+tcp_ack_update_rtt(struct tcp_opt *tp, int flag, s32 seq_rtt)
+{
+ /* Note that peer MAY send zero echo. In this case it is ignored. (rfc1323) */
+ if (tp->saw_tstamp && tp->rcv_tsecr)
+ tcp_ack_saw_tstamp(tp, flag);
+ else if (seq_rtt >= 0)
+ tcp_ack_no_tstamp(tp, seq_rtt, flag);
+}
+
+/*
+ * Compute congestion window to use.
+ *
+ * This is from the implementation of BICTCP in
+ * Lison-Xu, Kahaled Harfoush, and Injog Rhee.
+ * "Binary Increase Congestion Control for Fast, Long Distance
+ * Networks" in InfoComm 2004
+ * Available from:
+ * http://www.csc.ncsu.edu/faculty/rhee/export/bitcp.pdf
+ *
+ * Unless BIC is enabled and congestion window is large
+ * this behaves the same as the original Reno.
+ */
+static inline __u32 bictcp_cwnd(struct tcp_opt *tp)
+{
+ /* orignal Reno behaviour */
+ if (!tcp_is_bic(tp))
+ return tp->snd_cwnd;
+
+ if (tp->bictcp.last_cwnd == tp->snd_cwnd &&
+ (s32)(tcp_time_stamp - tp->bictcp.last_stamp) <= (HZ>>5))
+ return tp->bictcp.cnt;
+
+ tp->bictcp.last_cwnd = tp->snd_cwnd;
+ tp->bictcp.last_stamp = tcp_time_stamp;
+
+ /* start off normal */
+ if (tp->snd_cwnd <= sysctl_tcp_bic_low_window)
+ tp->bictcp.cnt = tp->snd_cwnd;
+
+ /* binary increase */
+ else if (tp->snd_cwnd < tp->bictcp.last_max_cwnd) {
+ __u32 dist = (tp->bictcp.last_max_cwnd - tp->snd_cwnd)
+ / BICTCP_B;
+
+ if (dist > BICTCP_MAX_INCREMENT)
+ /* linear increase */
+ tp->bictcp.cnt = tp->snd_cwnd / BICTCP_MAX_INCREMENT;
+ else if (dist <= 1U)
+ /* binary search increase */
+ tp->bictcp.cnt = tp->snd_cwnd * BICTCP_FUNC_OF_MIN_INCR
+ / BICTCP_B;
+ else
+ /* binary search increase */
+ tp->bictcp.cnt = tp->snd_cwnd / dist;
+ } else {
+ /* slow start amd linear increase */
+ if (tp->snd_cwnd < tp->bictcp.last_max_cwnd + BICTCP_B)
+ /* slow start */
+ tp->bictcp.cnt = tp->snd_cwnd * BICTCP_FUNC_OF_MIN_INCR
+ / BICTCP_B;
+ else if (tp->snd_cwnd < tp->bictcp.last_max_cwnd
+ + BICTCP_MAX_INCREMENT*(BICTCP_B-1))
+ /* slow start */
+ tp->bictcp.cnt = tp->snd_cwnd * (BICTCP_B-1)
+ / (tp->snd_cwnd-tp->bictcp.last_max_cwnd);
+ else
+ /* linear increase */
+ tp->bictcp.cnt = tp->snd_cwnd / BICTCP_MAX_INCREMENT;
+ }
+ return tp->bictcp.cnt;
+}
+
+/* This is Jacobson's slow start and congestion avoidance.
+ * SIGCOMM '88, p. 328.
+ */
+static __inline__ void reno_cong_avoid(struct tcp_opt *tp)
+{
+ if (tp->snd_cwnd <= tp->snd_ssthresh) {
+ /* In "safe" area, increase. */
+ if (tp->snd_cwnd < tp->snd_cwnd_clamp)
+ tp->snd_cwnd++;
+ } else {
+ /* In dangerous area, increase slowly.
+ * In theory this is tp->snd_cwnd += 1 / tp->snd_cwnd
+ */
+ if (tp->snd_cwnd_cnt >= bictcp_cwnd(tp)) {
+ if (tp->snd_cwnd < tp->snd_cwnd_clamp)
+ tp->snd_cwnd++;
+ tp->snd_cwnd_cnt=0;
+ } else
+ tp->snd_cwnd_cnt++;
+ }
+ tp->snd_cwnd_stamp = tcp_time_stamp;
+}
+
+/* This is based on the congestion detection/avoidance scheme described in
+ * Lawrence S. Brakmo and Larry L. Peterson.
+ * "TCP Vegas: End to end congestion avoidance on a global internet."
+ * IEEE Journal on Selected Areas in Communication, 13(8):1465--1480,
+ * October 1995. Available from:
+ * ftp://ftp.cs.arizona.edu/xkernel/Papers/jsac.ps
+ *
+ * See http://www.cs.arizona.edu/xkernel/ for their implementation.
+ * The main aspects that distinguish this implementation from the
+ * Arizona Vegas implementation are:
+ * o We do not change the loss detection or recovery mechanisms of
+ * Linux in any way. Linux already recovers from losses quite well,
+ * using fine-grained timers, NewReno, and FACK.
+ * o To avoid the performance penalty imposed by increasing cwnd
+ * only every-other RTT during slow start, we increase during
+ * every RTT during slow start, just like Reno.
+ * o Largely to allow continuous cwnd growth during slow start,
+ * we use the rate at which ACKs come back as the "actual"
+ * rate, rather than the rate at which data is sent.
+ * o To speed convergence to the right rate, we set the cwnd
+ * to achieve the right ("actual") rate when we exit slow start.
+ * o To filter out the noise caused by delayed ACKs, we use the
+ * minimum RTT sample observed during the last RTT to calculate
+ * the actual rate.
+ * o When the sender re-starts from idle, it waits until it has
+ * received ACKs for an entire flight of new data before making
+ * a cwnd adjustment decision. The original Vegas implementation
+ * assumed senders never went idle.
+ */
+static void vegas_cong_avoid(struct tcp_opt *tp, u32 ack, u32 seq_rtt)
+{
+ /* The key players are v_beg_snd_una and v_beg_snd_nxt.
+ *
+ * These are so named because they represent the approximate values
+ * of snd_una and snd_nxt at the beginning of the current RTT. More
+ * precisely, they represent the amount of data sent during the RTT.
+ * At the end of the RTT, when we receive an ACK for v_beg_snd_nxt,
+ * we will calculate that (v_beg_snd_nxt - v_beg_snd_una) outstanding
+ * bytes of data have been ACKed during the course of the RTT, giving
+ * an "actual" rate of:
+ *
+ * (v_beg_snd_nxt - v_beg_snd_una) / (rtt duration)
+ *
+ * Unfortunately, v_beg_snd_una is not exactly equal to snd_una,
+ * because delayed ACKs can cover more than one segment, so they
+ * don't line up nicely with the boundaries of RTTs.
+ *
+ * Another unfortunate fact of life is that delayed ACKs delay the
+ * advance of the left edge of our send window, so that the number
+ * of bytes we send in an RTT is often less than our cwnd will allow.
+ * So we keep track of our cwnd separately, in v_beg_snd_cwnd.
+ */
+
+ if (after(ack, tp->vegas.beg_snd_nxt)) {
+ /* Do the Vegas once-per-RTT cwnd adjustment. */
+ u32 old_wnd, old_snd_cwnd;
+
+
+ /* Here old_wnd is essentially the window of data that was
+ * sent during the previous RTT, and has all
+ * been acknowledged in the course of the RTT that ended
+ * with the ACK we just received. Likewise, old_snd_cwnd
+ * is the cwnd during the previous RTT.
+ */
+ old_wnd = (tp->vegas.beg_snd_nxt - tp->vegas.beg_snd_una) /
+ tp->mss_cache;
+ old_snd_cwnd = tp->vegas.beg_snd_cwnd;
+
+ /* Save the extent of the current window so we can use this
+ * at the end of the next RTT.
+ */
+ tp->vegas.beg_snd_una = tp->vegas.beg_snd_nxt;
+ tp->vegas.beg_snd_nxt = tp->snd_nxt;
+ tp->vegas.beg_snd_cwnd = tp->snd_cwnd;
+
+ /* Take into account the current RTT sample too, to
+ * decrease the impact of delayed acks. This double counts
+ * this sample since we count it for the next window as well,
+ * but that's not too awful, since we're taking the min,
+ * rather than averaging.
+ */
+ vegas_rtt_calc(tp, seq_rtt);
+
+ /* We do the Vegas calculations only if we got enough RTT
+ * samples that we can be reasonably sure that we got
+ * at least one RTT sample that wasn't from a delayed ACK.
+ * If we only had 2 samples total,
+ * then that means we're getting only 1 ACK per RTT, which
+ * means they're almost certainly delayed ACKs.
+ * If we have 3 samples, we should be OK.
+ */
+
+ if (tp->vegas.cntRTT <= 2) {
+ /* We don't have enough RTT samples to do the Vegas
+ * calculation, so we'll behave like Reno.
+ */
+ if (tp->snd_cwnd > tp->snd_ssthresh)
+ tp->snd_cwnd++;
+ } else {
+ u32 rtt, target_cwnd, diff;
+
+ /* We have enough RTT samples, so, using the Vegas
+ * algorithm, we determine if we should increase or
+ * decrease cwnd, and by how much.
+ */
+
+ /* Pluck out the RTT we are using for the Vegas
+ * calculations. This is the min RTT seen during the
+ * last RTT. Taking the min filters out the effects
+ * of delayed ACKs, at the cost of noticing congestion
+ * a bit later.
+ */
+ rtt = tp->vegas.minRTT;
+
+ /* Calculate the cwnd we should have, if we weren't
+ * going too fast.
+ *
+ * This is:
+ * (actual rate in segments) * baseRTT
+ * We keep it as a fixed point number with
+ * V_PARAM_SHIFT bits to the right of the binary point.
+ */
+ target_cwnd = ((old_wnd * tp->vegas.baseRTT)
+ << V_PARAM_SHIFT) / rtt;
+
+ /* Calculate the difference between the window we had,
+ * and the window we would like to have. This quantity
+ * is the "Diff" from the Arizona Vegas papers.
+ *
+ * Again, this is a fixed point number with
+ * V_PARAM_SHIFT bits to the right of the binary
+ * point.
+ */
+ diff = (old_wnd << V_PARAM_SHIFT) - target_cwnd;
+
+ if (tp->snd_cwnd < tp->snd_ssthresh) {
+ /* Slow start. */
+ if (diff > sysctl_tcp_vegas_gamma) {
+ /* Going too fast. Time to slow down
+ * and switch to congestion avoidance.
+ */
+ tp->snd_ssthresh = 2;
+
+ /* Set cwnd to match the actual rate
+ * exactly:
+ * cwnd = (actual rate) * baseRTT
+ * Then we add 1 because the integer
+ * truncation robs us of full link
+ * utilization.
+ */
+ tp->snd_cwnd = min(tp->snd_cwnd,
+ (target_cwnd >>
+ V_PARAM_SHIFT)+1);
+
+ }
+ } else {
+ /* Congestion avoidance. */
+ u32 next_snd_cwnd;
+
+ /* Figure out where we would like cwnd
+ * to be.
+ */
+ if (diff > sysctl_tcp_vegas_beta) {
+ /* The old window was too fast, so
+ * we slow down.
+ */
+ next_snd_cwnd = old_snd_cwnd - 1;
+ } else if (diff < sysctl_tcp_vegas_alpha) {
+ /* We don't have enough extra packets
+ * in the network, so speed up.
+ */
+ next_snd_cwnd = old_snd_cwnd + 1;
+ } else {
+ /* Sending just as fast as we
+ * should be.
+ */
+ next_snd_cwnd = old_snd_cwnd;
+ }
+
+ /* Adjust cwnd upward or downward, toward the
+ * desired value.
+ */
+ if (next_snd_cwnd > tp->snd_cwnd)
+ tp->snd_cwnd++;
+ else if (next_snd_cwnd < tp->snd_cwnd)
+ tp->snd_cwnd--;
+ }
+ }
+
+ /* Wipe the slate clean for the next RTT. */
+ tp->vegas.cntRTT = 0;
+ tp->vegas.minRTT = 0x7fffffff;
+ }
+
+ /* The following code is executed for every ack we receive,
+ * except for conditions checked in should_advance_cwnd()
+ * before the call to tcp_cong_avoid(). Mainly this means that
+ * we only execute this code if the ack actually acked some
+ * data.
+ */
+
+ /* If we are in slow start, increase our cwnd in response to this ACK.
+ * (If we are not in slow start then we are in congestion avoidance,
+ * and adjust our congestion window only once per RTT. See the code
+ * above.)
+ */
+ if (tp->snd_cwnd <= tp->snd_ssthresh)
+ tp->snd_cwnd++;
+
+ /* to keep cwnd from growing without bound */
+ tp->snd_cwnd = min_t(u32, tp->snd_cwnd, tp->snd_cwnd_clamp);
+
+ /* Make sure that we are never so timid as to reduce our cwnd below
+ * 2 MSS.
+ *
+ * Going below 2 MSS would risk huge delayed ACKs from our receiver.
+ */
+ tp->snd_cwnd = max(tp->snd_cwnd, 2U);
+
+ tp->snd_cwnd_stamp = tcp_time_stamp;
+}
+
+static inline void tcp_cong_avoid(struct tcp_opt *tp, u32 ack, u32 seq_rtt)
+{
+ if (tcp_vegas_enabled(tp))
+ vegas_cong_avoid(tp, ack, seq_rtt);
+ else
+ reno_cong_avoid(tp);
+}
+
+/* Restart timer after forward progress on connection.
+ * RFC2988 recommends to restart timer to now+rto.
+ */
+
+static __inline__ void tcp_ack_packets_out(struct sock *sk, struct tcp_opt *tp)
+{
+ if (tp->packets_out==0) {
+ tcp_clear_xmit_timer(sk, TCP_TIME_RETRANS);
+ } else {
+ tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
+ }
+}
+
+/* Remove acknowledged frames from the retransmission queue. */
+static int tcp_clean_rtx_queue(struct sock *sk, __s32 *seq_rtt_p)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sk_buff *skb;
+ __u32 now = tcp_time_stamp;
+ int acked = 0;
+ __s32 seq_rtt = -1;
+
+ while((skb=skb_peek(&sk->write_queue)) && (skb != tp->send_head)) {
+ struct tcp_skb_cb *scb = TCP_SKB_CB(skb);
+ __u8 sacked = scb->sacked;
+
+ /* If our packet is before the ack sequence we can
+ * discard it as it's confirmed to have arrived at
+ * the other end.
+ */
+ if (after(scb->end_seq, tp->snd_una))
+ break;
+
+ /* Initial outgoing SYN's get put onto the write_queue
+ * just like anything else we transmit. It is not
+ * true data, and if we misinform our callers that
+ * this ACK acks real data, we will erroneously exit
+ * connection startup slow start one packet too
+ * quickly. This is severely frowned upon behavior.
+ */
+ if(!(scb->flags & TCPCB_FLAG_SYN)) {
+ acked |= FLAG_DATA_ACKED;
+ } else {
+ acked |= FLAG_SYN_ACKED;
+ tp->retrans_stamp = 0;
+ }
+
+ if (sacked) {
+ if(sacked & TCPCB_RETRANS) {
+ if(sacked & TCPCB_SACKED_RETRANS)
+ tp->retrans_out--;
+ acked |= FLAG_RETRANS_DATA_ACKED;
+ seq_rtt = -1;
+ } else if (seq_rtt < 0)
+ seq_rtt = now - scb->when;
+ if(sacked & TCPCB_SACKED_ACKED)
+ tp->sacked_out--;
+ if(sacked & TCPCB_LOST)
+ tp->lost_out--;
+ if(sacked & TCPCB_URG) {
+ if (tp->urg_mode &&
+ !before(scb->end_seq, tp->snd_up))
+ tp->urg_mode = 0;
+ }
+ } else if (seq_rtt < 0)
+ seq_rtt = now - scb->when;
+ if(tp->fackets_out)
+ tp->fackets_out--;
+ tp->packets_out--;
+ __skb_unlink(skb, skb->list);
+ tcp_free_skb(sk, skb);
+ }
+
+ if (acked&FLAG_ACKED) {
+ tcp_ack_update_rtt(tp, acked, seq_rtt);
+ tcp_ack_packets_out(sk, tp);
+ }
+
+#if FASTRETRANS_DEBUG > 0
+ BUG_TRAP((int)tp->sacked_out >= 0);
+ BUG_TRAP((int)tp->lost_out >= 0);
+ BUG_TRAP((int)tp->retrans_out >= 0);
+ if (tp->packets_out==0 && tp->sack_ok) {
+ if (tp->lost_out) {
+ printk(KERN_DEBUG "Leak l=%u %d\n", tp->lost_out, tp->ca_state);
+ tp->lost_out = 0;
+ }
+ if (tp->sacked_out) {
+ printk(KERN_DEBUG "Leak s=%u %d\n", tp->sacked_out, tp->ca_state);
+ tp->sacked_out = 0;
+ }
+ if (tp->retrans_out) {
+ printk(KERN_DEBUG "Leak r=%u %d\n", tp->retrans_out, tp->ca_state);
+ tp->retrans_out = 0;
+ }
+ }
+#endif
+ *seq_rtt_p = seq_rtt;
+ return acked;
+}
+
+static void tcp_ack_probe(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ /* Was it a usable window open? */
+
+ if (!after(TCP_SKB_CB(tp->send_head)->end_seq, tp->snd_una + tp->snd_wnd)) {
+ tp->backoff = 0;
+ tcp_clear_xmit_timer(sk, TCP_TIME_PROBE0);
+ /* Socket must be waked up by subsequent tcp_data_snd_check().
+ * This function is not for random using!
+ */
+ } else {
+ tcp_reset_xmit_timer(sk, TCP_TIME_PROBE0,
+ min(tp->rto << tp->backoff, TCP_RTO_MAX));
+ }
+}
+
+static __inline__ int tcp_ack_is_dubious(struct tcp_opt *tp, int flag)
+{
+ return (!(flag & FLAG_NOT_DUP) || (flag & FLAG_CA_ALERT) ||
+ tp->ca_state != TCP_CA_Open);
+}
+
+static __inline__ int tcp_may_raise_cwnd(struct tcp_opt *tp, int flag)
+{
+ return (!(flag & FLAG_ECE) || tp->snd_cwnd < tp->snd_ssthresh) &&
+ !((1<<tp->ca_state)&(TCPF_CA_Recovery|TCPF_CA_CWR));
+}
+
+/* Check that window update is acceptable.
+ * The function assumes that snd_una<=ack<=snd_next.
+ */
+static __inline__ int
+tcp_may_update_window(struct tcp_opt *tp, u32 ack, u32 ack_seq, u32 nwin)
+{
+ return (after(ack, tp->snd_una) ||
+ after(ack_seq, tp->snd_wl1) ||
+ (ack_seq == tp->snd_wl1 && nwin > tp->snd_wnd));
+}
+
+/* Update our send window.
+ *
+ * Window update algorithm, described in RFC793/RFC1122 (used in linux-2.2
+ * and in FreeBSD. NetBSD's one is even worse.) is wrong.
+ */
+static int tcp_ack_update_window(struct sock *sk, struct tcp_opt *tp,
+ struct sk_buff *skb, u32 ack, u32 ack_seq)
+{
+ int flag = 0;
+ u32 nwin = ntohs(skb->h.th->window);
+
+ if (likely(!skb->h.th->syn))
+ nwin <<= tp->snd_wscale;
+
+ if (tcp_may_update_window(tp, ack, ack_seq, nwin)) {
+ flag |= FLAG_WIN_UPDATE;
+ tcp_update_wl(tp, ack, ack_seq);
+
+ if (tp->snd_wnd != nwin) {
+ tp->snd_wnd = nwin;
+
+ /* Note, it is the only place, where
+ * fast path is recovered for sending TCP.
+ */
+ tcp_fast_path_check(sk, tp);
+
+ if (nwin > tp->max_window) {
+ tp->max_window = nwin;
+ tcp_sync_mss(sk, tp->pmtu_cookie);
+ }
+ }
+ }
+
+ tp->snd_una = ack;
+
+ return flag;
+}
+
+static void tcp_process_frto(struct sock *sk, u32 prior_snd_una)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+
+ tcp_sync_left_out(tp);
+
+ if (tp->snd_una == prior_snd_una ||
+ !before(tp->snd_una, tp->frto_highmark)) {
+ /* RTO was caused by loss, start retransmitting in
+ * go-back-N slow start
+ */
+ tcp_enter_frto_loss(sk);
+ return;
+ }
+
+ if (tp->frto_counter == 1) {
+ /* First ACK after RTO advances the window: allow two new
+ * segments out.
+ */
+ tp->snd_cwnd = tcp_packets_in_flight(tp) + 2;
+ } else {
+ /* Also the second ACK after RTO advances the window.
+ * The RTO was likely spurious. Reduce cwnd and continue
+ * in congestion avoidance
+ */
+ tp->snd_cwnd = min(tp->snd_cwnd, tp->snd_ssthresh);
+ tcp_moderate_cwnd(tp);
+ }
+
+ /* F-RTO affects on two new ACKs following RTO.
+ * At latest on third ACK the TCP behavor is back to normal.
+ */
+ tp->frto_counter = (tp->frto_counter + 1) % 3;
+}
+
+/*
+ * TCP Westwood+
+ */
+
+/*
+ * @westwood_do_filter
+ * Low-pass filter. Implemented using constant coeffients.
+ */
+
+static inline __u32 westwood_do_filter(__u32 a, __u32 b)
+{
+ return (((7 * a) + b) >> 3);
+}
+
+static void westwood_filter(struct sock *sk, __u32 delta)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ tp->westwood.bw_ns_est =
+ westwood_do_filter(tp->westwood.bw_ns_est,
+ tp->westwood.bk / delta);
+ tp->westwood.bw_est =
+ westwood_do_filter(tp->westwood.bw_est,
+ tp->westwood.bw_ns_est);
+}
+
+/* @westwood_update_rttmin
+ * It is used to update RTTmin. In this case we MUST NOT use
+ * WESTWOOD_RTT_MIN minimum bound since we could be on a LAN!
+ */
+
+static inline __u32 westwood_update_rttmin(const struct sock *sk)
+{
+ const struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ __u32 rttmin = tp->westwood.rtt_min;
+
+ if (tp->westwood.rtt != 0 &&
+ (tp->westwood.rtt < tp->westwood.rtt_min || !rttmin))
+ rttmin = tp->westwood.rtt;
+
+ return rttmin;
+}
+
+/*
+ * @westwood_acked
+ * Evaluate increases for dk.
+ */
+
+static __u32 westwood_acked(const struct sock *sk)
+{
+ const struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ return ((tp->snd_una) - (tp->westwood.snd_una));
+}
+
+/*
+ * @westwood_new_window
+ * It evaluates if we are receiving data inside the same RTT window as
+ * when we started.
+ * Return value:
+ * It returns 0 if we are still evaluating samples in the same RTT
+ * window, 1 if the sample has to be considered in the next window.
+ */
+
+static int westwood_new_window(const struct sock *sk)
+{
+ const struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ __u32 left_bound;
+ __u32 rtt;
+ int ret = 0;
+
+ left_bound = tp->westwood.rtt_win_sx;
+ rtt = max(tp->westwood.rtt, (__u32)TCP_WESTWOOD_RTT_MIN);
+
+ /*
+ * A RTT-window has passed. Be careful since if RTT is less than
+ * 50ms we don't filter but we continue 'building the sample'.
+ * This minimum limit was choosen since an estimation on small
+ * time intervals is better to avoid...
+ * Obvioulsy on a LAN we reasonably will always have
+ * right_bound = left_bound + WESTWOOD_RTT_MIN
+ */
+
+ if ((left_bound + rtt) < tcp_time_stamp)
+ ret = 1;
+
+ return ret;
+}
+
+/*
+ * @westwood_update_window
+ * It updates RTT evaluation window if it is the right moment to do
+ * it. If so it calls filter for evaluating bandwidth.
+ */
+
+static void __westwood_update_window(struct sock *sk, __u32 now)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ __u32 delta = now - tp->westwood.rtt_win_sx;
+
+ if (delta) {
+ if (tp->westwood.rtt)
+ westwood_filter(sk, delta);
+
+ tp->westwood.bk = 0;
+ tp->westwood.rtt_win_sx = tcp_time_stamp;
+ }
+}
+
+static void westwood_update_window(struct sock *sk, __u32 now)
+{
+ if (westwood_new_window(sk))
+ __westwood_update_window(sk, now);
+}
+
+/*
+ * @__tcp_westwood_fast_bw
+ * It is called when we are in fast path. In particular it is called when
+ * header prediction is successfull. In such case infact update is
+ * straight forward and doesn't need any particular care.
+ */
+
+void __tcp_westwood_fast_bw(struct sock *sk, struct sk_buff *skb)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ westwood_update_window(sk, tcp_time_stamp);
+
+ tp->westwood.bk += westwood_acked(sk);
+ tp->westwood.snd_una = tp->snd_una;
+ tp->westwood.rtt_min = westwood_update_rttmin(sk);
+}
+
+/*
+ * @westwood_mss
+ * This function was inserted just to have the possibility to evaluate
+ * which value of MSS is better. Infact we can use neither mss_cache or
+ * mss_cache. Just testing we will know it!
+ */
+
+static inline __u32 westwood_mss(struct tcp_opt *tp)
+{
+ return ((__u32)(tp->mss_cache));
+}
+
+/*
+ * @tcp_westwood_dupack_update
+ * It updates accounted and cumul_ack when receiving a dupack.
+ */
+
+static void westwood_dupack_update(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ tp->westwood.accounted += westwood_mss(tp);
+ tp->westwood.cumul_ack = westwood_mss(tp);
+}
+
+static inline int westwood_may_change_cumul(struct tcp_opt *tp)
+{
+ return (tp->westwood.cumul_ack > westwood_mss(tp));
+}
+
+static inline void westwood_partial_update(struct tcp_opt *tp)
+{
+ tp->westwood.accounted -= tp->westwood.cumul_ack;
+ tp->westwood.cumul_ack = westwood_mss(tp);
+}
+
+static inline void westwood_complete_update(struct tcp_opt *tp)
+{
+ tp->westwood.cumul_ack -= tp->westwood.accounted;
+ tp->westwood.accounted = 0;
+}
+
+/*
+ * @westwood_acked_count
+ * This function evaluates cumul_ack for evaluating dk in case of
+ * delayed or partial acks.
+ */
+
+static inline __u32 westwood_acked_count(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ tp->westwood.cumul_ack = westwood_acked(sk);
+
+ /* If cumul_ack is 0 this is a dupack since it's not moving
+ * tp->snd_una.
+ */
+ if (!(tp->westwood.cumul_ack))
+ westwood_dupack_update(sk);
+
+ if (westwood_may_change_cumul(tp)) {
+ /* Partial or delayed ack */
+ if (tp->westwood.accounted >= tp->westwood.cumul_ack)
+ westwood_partial_update(tp);
+ else
+ westwood_complete_update(tp);
+ }
+
+ tp->westwood.snd_una = tp->snd_una;
+
+ return tp->westwood.cumul_ack;
+}
+
+/*
+ * @__tcp_westwood_slow_bw
+ * It is called when something is going wrong..even if there could
+ * be no problems! Infact a simple delayed packet may trigger a
+ * dupack. But we need to be careful in such case.
+ */
+
+void __tcp_westwood_slow_bw(struct sock *sk, struct sk_buff *skb)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ westwood_update_window(sk, tcp_time_stamp);
+
+ tp->westwood.bk += westwood_acked_count(sk);
+ tp->westwood.rtt_min = westwood_update_rttmin(sk);
+}
+
+/* TCP Westwood+ routines end here */
+
+/* This routine deals with incoming acks, but not outgoing ones. */
+static int tcp_ack(struct sock *sk, struct sk_buff *skb, int flag)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ u32 prior_snd_una = tp->snd_una;
+ u32 ack_seq = TCP_SKB_CB(skb)->seq;
+ u32 ack = TCP_SKB_CB(skb)->ack_seq;
+ u32 prior_in_flight;
+ s32 seq_rtt;
+ int prior_packets;
+
+ /* If the ack is newer than sent or older than previous acks
+ * then we can probably ignore it.
+ */
+ if (after(ack, tp->snd_nxt))
+ goto uninteresting_ack;
+
+ if (before(ack, prior_snd_una))
+ goto old_ack;
+
+ if (!(flag&FLAG_SLOWPATH) && after(ack, prior_snd_una)) {
+ /* Window is constant, pure forward advance.
+ * No more checks are required.
+ * Note, we use the fact that SND.UNA>=SND.WL2.
+ */
+ tcp_update_wl(tp, ack, ack_seq);
+ tp->snd_una = ack;
+ tcp_westwood_fast_bw(sk, skb);
+ flag |= FLAG_WIN_UPDATE;
+
+ NET_INC_STATS_BH(TCPHPAcks);
+ } else {
+ if (ack_seq != TCP_SKB_CB(skb)->end_seq)
+ flag |= FLAG_DATA;
+ else
+ NET_INC_STATS_BH(TCPPureAcks);
+
+ flag |= tcp_ack_update_window(sk, tp, skb, ack, ack_seq);
+
+ if (TCP_SKB_CB(skb)->sacked)
+ flag |= tcp_sacktag_write_queue(sk, skb, prior_snd_una);
+
+ if (TCP_ECN_rcv_ecn_echo(tp, skb->h.th))
+ flag |= FLAG_ECE;
+
+ tcp_westwood_slow_bw(sk, skb);
+ }
+
+ /* We passed data and got it acked, remove any soft error
+ * log. Something worked...
+ */
+ sk->err_soft = 0;
+ tp->rcv_tstamp = tcp_time_stamp;
+ if ((prior_packets = tp->packets_out) == 0)
+ goto no_queue;
+
+ prior_in_flight = tcp_packets_in_flight(tp);
+
+ /* See if we can take anything off of the retransmit queue. */
+ flag |= tcp_clean_rtx_queue(sk, &seq_rtt);
+
+ if (tp->frto_counter)
+ tcp_process_frto(sk, prior_snd_una);
+
+ if (tcp_ack_is_dubious(tp, flag)) {
+ /* Advanve CWND, if state allows this. */
+ if ((flag&FLAG_DATA_ACKED) &&
+ (tcp_vegas_enabled(tp) || prior_in_flight >= tp->snd_cwnd) &&
+ tcp_may_raise_cwnd(tp, flag))
+ tcp_cong_avoid(tp, ack, seq_rtt);
+ tcp_fastretrans_alert(sk, prior_snd_una, prior_packets, flag);
+ } else {
+ if ((flag & FLAG_DATA_ACKED) &&
+ (tcp_vegas_enabled(tp) || prior_in_flight >= tp->snd_cwnd))
+ tcp_cong_avoid(tp, ack, seq_rtt);
+ }
+
+ if ((flag & FLAG_FORWARD_PROGRESS) || !(flag&FLAG_NOT_DUP))
+ dst_confirm(sk->dst_cache);
+
+ return 1;
+
+no_queue:
+ tp->probes_out = 0;
+
+ /* If this ack opens up a zero window, clear backoff. It was
+ * being used to time the probes, and is probably far higher than
+ * it needs to be for normal retransmission.
+ */
+ if (tp->send_head)
+ tcp_ack_probe(sk);
+ return 1;
+
+old_ack:
+ if (TCP_SKB_CB(skb)->sacked)
+ tcp_sacktag_write_queue(sk, skb, prior_snd_una);
+
+uninteresting_ack:
+ SOCK_DEBUG(sk, "Ack %u out of %u:%u\n", ack, tp->snd_una, tp->snd_nxt);
+ return 0;
+}
+
+
+/* Look for tcp options. Normally only called on SYN and SYNACK packets.
+ * But, this can also be called on packets in the established flow when
+ * the fast version below fails.
+ */
+void tcp_parse_options(struct sk_buff *skb, struct tcp_opt *tp, int estab)
+{
+ unsigned char *ptr;
+ struct tcphdr *th = skb->h.th;
+ int length=(th->doff*4)-sizeof(struct tcphdr);
+
+ ptr = (unsigned char *)(th + 1);
+ tp->saw_tstamp = 0;
+
+ while(length>0) {
+ int opcode=*ptr++;
+ int opsize;
+
+ switch (opcode) {
+ case TCPOPT_EOL:
+ return;
+ case TCPOPT_NOP: /* Ref: RFC 793 section 3.1 */
+ length--;
+ continue;
+ default:
+ opsize=*ptr++;
+ if (opsize < 2) /* "silly options" */
+ return;
+ if (opsize > length)
+ return; /* don't parse partial options */
+ switch(opcode) {
+ case TCPOPT_MSS:
+ if(opsize==TCPOLEN_MSS && th->syn && !estab) {
+ u16 in_mss = ntohs(*(__u16 *)ptr);
+ if (in_mss) {
+ if (tp->user_mss && tp->user_mss < in_mss)
+ in_mss = tp->user_mss;
+ tp->mss_clamp = in_mss;
+ }
+ }
+ break;
+ case TCPOPT_WINDOW:
+ if(opsize==TCPOLEN_WINDOW && th->syn && !estab)
+ if (sysctl_tcp_window_scaling) {
+ tp->wscale_ok = 1;
+ tp->snd_wscale = *(__u8 *)ptr;
+ if(tp->snd_wscale > 14) {
+ if(net_ratelimit())
+ printk(KERN_INFO "tcp_parse_options: Illegal window "
+ "scaling value %d >14 received.\n",
+ tp->snd_wscale);
+ tp->snd_wscale = 14;
+ }
+ }
+ break;
+ case TCPOPT_TIMESTAMP:
+ if(opsize==TCPOLEN_TIMESTAMP) {
+ if ((estab && tp->tstamp_ok) ||
+ (!estab && sysctl_tcp_timestamps)) {
+ tp->saw_tstamp = 1;
+ tp->rcv_tsval = ntohl(*(__u32 *)ptr);
+ tp->rcv_tsecr = ntohl(*(__u32 *)(ptr+4));
+ }
+ }
+ break;
+ case TCPOPT_SACK_PERM:
+ if(opsize==TCPOLEN_SACK_PERM && th->syn && !estab) {
+ if (sysctl_tcp_sack) {
+ tp->sack_ok = 1;
+ tcp_sack_reset(tp);
+ }
+ }
+ break;
+
+ case TCPOPT_SACK:
+ if((opsize >= (TCPOLEN_SACK_BASE + TCPOLEN_SACK_PERBLOCK)) &&
+ !((opsize - TCPOLEN_SACK_BASE) % TCPOLEN_SACK_PERBLOCK) &&
+ tp->sack_ok) {
+ TCP_SKB_CB(skb)->sacked = (ptr - 2) - (unsigned char *)th;
+ }
+ };
+ ptr+=opsize-2;
+ length-=opsize;
+ };
+ }
+}
+
+/* Fast parse options. This hopes to only see timestamps.
+ * If it is wrong it falls back on tcp_parse_options().
+ */
+static __inline__ int tcp_fast_parse_options(struct sk_buff *skb, struct tcphdr *th, struct tcp_opt *tp)
+{
+ if (th->doff == sizeof(struct tcphdr)>>2) {
+ tp->saw_tstamp = 0;
+ return 0;
+ } else if (tp->tstamp_ok &&
+ th->doff == (sizeof(struct tcphdr)>>2)+(TCPOLEN_TSTAMP_ALIGNED>>2)) {
+ __u32 *ptr = (__u32 *)(th + 1);
+ if (*ptr == ntohl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16)
+ | (TCPOPT_TIMESTAMP << 8) | TCPOLEN_TIMESTAMP)) {
+ tp->saw_tstamp = 1;
+ ++ptr;
+ tp->rcv_tsval = ntohl(*ptr);
+ ++ptr;
+ tp->rcv_tsecr = ntohl(*ptr);
+ return 1;
+ }
+ }
+ tcp_parse_options(skb, tp, 1);
+ return 1;
+}
+
+extern __inline__ void
+tcp_store_ts_recent(struct tcp_opt *tp)
+{
+ tp->ts_recent = tp->rcv_tsval;
+ tp->ts_recent_stamp = xtime.tv_sec;
+}
+
+extern __inline__ void
+tcp_replace_ts_recent(struct tcp_opt *tp, u32 seq)
+{
+ if (tp->saw_tstamp && !after(seq, tp->rcv_wup)) {
+ /* PAWS bug workaround wrt. ACK frames, the PAWS discard
+ * extra check below makes sure this can only happen
+ * for pure ACK frames. -DaveM
+ *
+ * Not only, also it occurs for expired timestamps.
+ */
+
+ if((s32)(tp->rcv_tsval - tp->ts_recent) >= 0 ||
+ xtime.tv_sec >= tp->ts_recent_stamp + TCP_PAWS_24DAYS)
+ tcp_store_ts_recent(tp);
+ }
+}
+
+/* Sorry, PAWS as specified is broken wrt. pure-ACKs -DaveM
+ *
+ * It is not fatal. If this ACK does _not_ change critical state (seqs, window)
+ * it can pass through stack. So, the following predicate verifies that
+ * this segment is not used for anything but congestion avoidance or
+ * fast retransmit. Moreover, we even are able to eliminate most of such
+ * second order effects, if we apply some small "replay" window (~RTO)
+ * to timestamp space.
+ *
+ * All these measures still do not guarantee that we reject wrapped ACKs
+ * on networks with high bandwidth, when sequence space is recycled fastly,
+ * but it guarantees that such events will be very rare and do not affect
+ * connection seriously. This doesn't look nice, but alas, PAWS is really
+ * buggy extension.
+ *
+ * [ Later note. Even worse! It is buggy for segments _with_ data. RFC
+ * states that events when retransmit arrives after original data are rare.
+ * It is a blatant lie. VJ forgot about fast retransmit! 8)8) It is
+ * the biggest problem on large power networks even with minor reordering.
+ * OK, let's give it small replay window. If peer clock is even 1hz, it is safe
+ * up to bandwidth of 18Gigabit/sec. 8) ]
+ */
+
+static int tcp_disordered_ack(struct tcp_opt *tp, struct sk_buff *skb)
+{
+ struct tcphdr *th = skb->h.th;
+ u32 seq = TCP_SKB_CB(skb)->seq;
+ u32 ack = TCP_SKB_CB(skb)->ack_seq;
+
+ return (/* 1. Pure ACK with correct sequence number. */
+ (th->ack && seq == TCP_SKB_CB(skb)->end_seq && seq == tp->rcv_nxt) &&
+
+ /* 2. ... and duplicate ACK. */
+ ack == tp->snd_una &&
+
+ /* 3. ... and does not update window. */
+ !tcp_may_update_window(tp, ack, seq, ntohs(th->window)<<tp->snd_wscale) &&
+
+ /* 4. ... and sits in replay window. */
+ (s32)(tp->ts_recent - tp->rcv_tsval) <= (tp->rto*1024)/HZ);
+}
+
+extern __inline__ int tcp_paws_discard(struct tcp_opt *tp, struct sk_buff *skb)
+{
+ return ((s32)(tp->ts_recent - tp->rcv_tsval) > TCP_PAWS_WINDOW &&
+ xtime.tv_sec < tp->ts_recent_stamp + TCP_PAWS_24DAYS &&
+ !tcp_disordered_ack(tp, skb));
+}
+
+/* Check segment sequence number for validity.
+ *
+ * Segment controls are considered valid, if the segment
+ * fits to the window after truncation to the window. Acceptability
+ * of data (and SYN, FIN, of course) is checked separately.
+ * See tcp_data_queue(), for example.
+ *
+ * Also, controls (RST is main one) are accepted using RCV.WUP instead
+ * of RCV.NXT. Peer still did not advance his SND.UNA when we
+ * delayed ACK, so that hisSND.UNA<=ourRCV.WUP.
+ * (borrowed from freebsd)
+ */
+
+static inline int tcp_sequence(struct tcp_opt *tp, u32 seq, u32 end_seq)
+{
+ return !before(end_seq, tp->rcv_wup) &&
+ !after(seq, tp->rcv_nxt + tcp_receive_window(tp));
+}
+
+/* When we get a reset we do this. */
+static void tcp_reset(struct sock *sk)
+{
+ /* We want the right error as BSD sees it (and indeed as we do). */
+ switch (sk->state) {
+ case TCP_SYN_SENT:
+ sk->err = ECONNREFUSED;
+ break;
+ case TCP_CLOSE_WAIT:
+ sk->err = EPIPE;
+ break;
+ case TCP_CLOSE:
+ return;
+ default:
+ sk->err = ECONNRESET;
+ }
+
+ if (!sk->dead)
+ sk->error_report(sk);
+
+ tcp_done(sk);
+}
+
+/*
+ * Process the FIN bit. This now behaves as it is supposed to work
+ * and the FIN takes effect when it is validly part of sequence
+ * space. Not before when we get holes.
+ *
+ * If we are ESTABLISHED, a received fin moves us to CLOSE-WAIT
+ * (and thence onto LAST-ACK and finally, CLOSE, we never enter
+ * TIME-WAIT)
+ *
+ * If we are in FINWAIT-1, a received FIN indicates simultaneous
+ * close and we go into CLOSING (and later onto TIME-WAIT)
+ *
+ * If we are in FINWAIT-2, a received FIN moves us to TIME-WAIT.
+ */
+static void tcp_fin(struct sk_buff *skb, struct sock *sk, struct tcphdr *th)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ tcp_schedule_ack(tp);
+
+ sk->shutdown |= RCV_SHUTDOWN;
+ sk->done = 1;
+
+ switch(sk->state) {
+ case TCP_SYN_RECV:
+ case TCP_ESTABLISHED:
+ /* Move to CLOSE_WAIT */
+ tcp_set_state(sk, TCP_CLOSE_WAIT);
+ tp->ack.pingpong = 1;
+ break;
+
+ case TCP_CLOSE_WAIT:
+ case TCP_CLOSING:
+ /* Received a retransmission of the FIN, do
+ * nothing.
+ */
+ break;
+ case TCP_LAST_ACK:
+ /* RFC793: Remain in the LAST-ACK state. */
+ break;
+
+ case TCP_FIN_WAIT1:
+ /* This case occurs when a simultaneous close
+ * happens, we must ack the received FIN and
+ * enter the CLOSING state.
+ */
+ tcp_send_ack(sk);
+ tcp_set_state(sk, TCP_CLOSING);
+ break;
+ case TCP_FIN_WAIT2:
+ /* Received a FIN -- send ACK and enter TIME_WAIT. */
+ tcp_send_ack(sk);
+ tcp_time_wait(sk, TCP_TIME_WAIT, 0);
+ break;
+ default:
+ /* Only TCP_LISTEN and TCP_CLOSE are left, in these
+ * cases we should never reach this piece of code.
+ */
+ printk(KERN_ERR "tcp_fin: Impossible, sk->state=%d\n", sk->state);
+ break;
+ };
+
+ /* It _is_ possible, that we have something out-of-order _after_ FIN.
+ * Probably, we should reset in this case. For now drop them.
+ */
+ __skb_queue_purge(&tp->out_of_order_queue);
+ if (tp->sack_ok)
+ tcp_sack_reset(tp);
+ tcp_mem_reclaim(sk);
+
+ if (!sk->dead) {
+ sk->state_change(sk);
+
+ /* Do not send POLL_HUP for half duplex close. */
+ if (sk->shutdown == SHUTDOWN_MASK || sk->state == TCP_CLOSE)
+ sk_wake_async(sk, 1, POLL_HUP);
+ else
+ sk_wake_async(sk, 1, POLL_IN);
+ }
+}
+
+static __inline__ int
+tcp_sack_extend(struct tcp_sack_block *sp, u32 seq, u32 end_seq)
+{
+ if (!after(seq, sp->end_seq) && !after(sp->start_seq, end_seq)) {
+ if (before(seq, sp->start_seq))
+ sp->start_seq = seq;
+ if (after(end_seq, sp->end_seq))
+ sp->end_seq = end_seq;
+ return 1;
+ }
+ return 0;
+}
+
+static __inline__ void tcp_dsack_set(struct tcp_opt *tp, u32 seq, u32 end_seq)
+{
+ if (tp->sack_ok && sysctl_tcp_dsack) {
+ if (before(seq, tp->rcv_nxt))
+ NET_INC_STATS_BH(TCPDSACKOldSent);
+ else
+ NET_INC_STATS_BH(TCPDSACKOfoSent);
+
+ tp->dsack = 1;
+ tp->duplicate_sack[0].start_seq = seq;
+ tp->duplicate_sack[0].end_seq = end_seq;
+ tp->eff_sacks = min(tp->num_sacks+1, 4-tp->tstamp_ok);
+ }
+}
+
+static __inline__ void tcp_dsack_extend(struct tcp_opt *tp, u32 seq, u32 end_seq)
+{
+ if (!tp->dsack)
+ tcp_dsack_set(tp, seq, end_seq);
+ else
+ tcp_sack_extend(tp->duplicate_sack, seq, end_seq);
+}
+
+static void tcp_send_dupack(struct sock *sk, struct sk_buff *skb)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
+ before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
+ NET_INC_STATS_BH(DelayedACKLost);
+ tcp_enter_quickack_mode(tp);
+
+ if (tp->sack_ok && sysctl_tcp_dsack) {
+ u32 end_seq = TCP_SKB_CB(skb)->end_seq;
+
+ if (after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt))
+ end_seq = tp->rcv_nxt;
+ tcp_dsack_set(tp, TCP_SKB_CB(skb)->seq, end_seq);
+ }
+ }
+
+ tcp_send_ack(sk);
+}
+
+/* These routines update the SACK block as out-of-order packets arrive or
+ * in-order packets close up the sequence space.
+ */
+static void tcp_sack_maybe_coalesce(struct tcp_opt *tp)
+{
+ int this_sack;
+ struct tcp_sack_block *sp = &tp->selective_acks[0];
+ struct tcp_sack_block *swalk = sp+1;
+
+ /* See if the recent change to the first SACK eats into
+ * or hits the sequence space of other SACK blocks, if so coalesce.
+ */
+ for (this_sack = 1; this_sack < tp->num_sacks; ) {
+ if (tcp_sack_extend(sp, swalk->start_seq, swalk->end_seq)) {
+ int i;
+
+ /* Zap SWALK, by moving every further SACK up by one slot.
+ * Decrease num_sacks.
+ */
+ tp->num_sacks--;
+ tp->eff_sacks = min(tp->num_sacks+tp->dsack, 4-tp->tstamp_ok);
+ for(i=this_sack; i < tp->num_sacks; i++)
+ sp[i] = sp[i+1];
+ continue;
+ }
+ this_sack++, swalk++;
+ }
+}
+
+static __inline__ void tcp_sack_swap(struct tcp_sack_block *sack1, struct tcp_sack_block *sack2)
+{
+ __u32 tmp;
+
+ tmp = sack1->start_seq;
+ sack1->start_seq = sack2->start_seq;
+ sack2->start_seq = tmp;
+
+ tmp = sack1->end_seq;
+ sack1->end_seq = sack2->end_seq;
+ sack2->end_seq = tmp;
+}
+
+static void tcp_sack_new_ofo_skb(struct sock *sk, u32 seq, u32 end_seq)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct tcp_sack_block *sp = &tp->selective_acks[0];
+ int cur_sacks = tp->num_sacks;
+ int this_sack;
+
+ if (!cur_sacks)
+ goto new_sack;
+
+ for (this_sack=0; this_sack<cur_sacks; this_sack++, sp++) {
+ if (tcp_sack_extend(sp, seq, end_seq)) {
+ /* Rotate this_sack to the first one. */
+ for (; this_sack>0; this_sack--, sp--)
+ tcp_sack_swap(sp, sp-1);
+ if (cur_sacks > 1)
+ tcp_sack_maybe_coalesce(tp);
+ return;
+ }
+ }
+
+ /* Could not find an adjacent existing SACK, build a new one,
+ * put it at the front, and shift everyone else down. We
+ * always know there is at least one SACK present already here.
+ *
+ * If the sack array is full, forget about the last one.
+ */
+ if (this_sack >= 4) {
+ this_sack--;
+ tp->num_sacks--;
+ sp--;
+ }
+ for(; this_sack > 0; this_sack--, sp--)
+ *sp = *(sp-1);
+
+new_sack:
+ /* Build the new head SACK, and we're done. */
+ sp->start_seq = seq;
+ sp->end_seq = end_seq;
+ tp->num_sacks++;
+ tp->eff_sacks = min(tp->num_sacks+tp->dsack, 4-tp->tstamp_ok);
+}
+
+/* RCV.NXT advances, some SACKs should be eaten. */
+
+static void tcp_sack_remove(struct tcp_opt *tp)
+{
+ struct tcp_sack_block *sp = &tp->selective_acks[0];
+ int num_sacks = tp->num_sacks;
+ int this_sack;
+
+ /* Empty ofo queue, hence, all the SACKs are eaten. Clear. */
+ if (skb_queue_len(&tp->out_of_order_queue) == 0) {
+ tp->num_sacks = 0;
+ tp->eff_sacks = tp->dsack;
+ return;
+ }
+
+ for(this_sack = 0; this_sack < num_sacks; ) {
+ /* Check if the start of the sack is covered by RCV.NXT. */
+ if (!before(tp->rcv_nxt, sp->start_seq)) {
+ int i;
+
+ /* RCV.NXT must cover all the block! */
+ BUG_TRAP(!before(tp->rcv_nxt, sp->end_seq));
+
+ /* Zap this SACK, by moving forward any other SACKS. */
+ for (i=this_sack+1; i < num_sacks; i++)
+ tp->selective_acks[i-1] = tp->selective_acks[i];
+ num_sacks--;
+ continue;
+ }
+ this_sack++;
+ sp++;
+ }
+ if (num_sacks != tp->num_sacks) {
+ tp->num_sacks = num_sacks;
+ tp->eff_sacks = min(tp->num_sacks+tp->dsack, 4-tp->tstamp_ok);
+ }
+}
+
+/* This one checks to see if we can put data from the
+ * out_of_order queue into the receive_queue.
+ */
+static void tcp_ofo_queue(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ __u32 dsack_high = tp->rcv_nxt;
+ struct sk_buff *skb;
+
+ while ((skb = skb_peek(&tp->out_of_order_queue)) != NULL) {
+ if (after(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))
+ break;
+
+ if (before(TCP_SKB_CB(skb)->seq, dsack_high)) {
+ __u32 dsack = dsack_high;
+ if (before(TCP_SKB_CB(skb)->end_seq, dsack_high))
+ dsack_high = TCP_SKB_CB(skb)->end_seq;
+ tcp_dsack_extend(tp, TCP_SKB_CB(skb)->seq, dsack);
+ }
+
+ if (!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt)) {
+ SOCK_DEBUG(sk, "ofo packet was already received \n");
+ __skb_unlink(skb, skb->list);
+ __kfree_skb(skb);
+ continue;
+ }
+ SOCK_DEBUG(sk, "ofo requeuing : rcv_next %X seq %X - %X\n",
+ tp->rcv_nxt, TCP_SKB_CB(skb)->seq,
+ TCP_SKB_CB(skb)->end_seq);
+
+ __skb_unlink(skb, skb->list);
+ __skb_queue_tail(&sk->receive_queue, skb);
+ tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
+ if(skb->h.th->fin)
+ tcp_fin(skb, sk, skb->h.th);
+ }
+}
+
+static inline int tcp_rmem_schedule(struct sock *sk, struct sk_buff *skb)
+{
+ return (int)skb->truesize <= sk->forward_alloc ||
+ tcp_mem_schedule(sk, skb->truesize, 1);
+}
+
+static int tcp_prune_queue(struct sock *sk);
+
+static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
+{
+ struct tcphdr *th = skb->h.th;
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int eaten = -1;
+
+ if (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq)
+ goto drop;
+
+ th = skb->h.th;
+ __skb_pull(skb, th->doff*4);
+
+ TCP_ECN_accept_cwr(tp, skb);
+
+ if (tp->dsack) {
+ tp->dsack = 0;
+ tp->eff_sacks = min_t(unsigned int, tp->num_sacks, 4-tp->tstamp_ok);
+ }
+
+ /* Queue data for delivery to the user.
+ * Packets in sequence go to the receive queue.
+ * Out of sequence packets to the out_of_order_queue.
+ */
+ if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {
+ if (tcp_receive_window(tp) == 0)
+ goto out_of_window;
+
+ /* Ok. In sequence. In window. */
+ if (tp->ucopy.task == current &&
+ tp->copied_seq == tp->rcv_nxt &&
+ tp->ucopy.len &&
+ sk->lock.users &&
+ !tp->urg_data) {
+ int chunk = min_t(unsigned int, skb->len, tp->ucopy.len);
+
+ __set_current_state(TASK_RUNNING);
+
+ local_bh_enable();
+ if (!skb_copy_datagram_iovec(skb, 0, tp->ucopy.iov, chunk)) {
+ tp->ucopy.len -= chunk;
+ tp->copied_seq += chunk;
+ eaten = (chunk == skb->len && !th->fin);
+ tcp_rcv_space_adjust(sk);
+ }
+ local_bh_disable();
+ }
+
+ if (eaten <= 0) {
+queue_and_out:
+ if (eaten < 0 &&
+ (atomic_read(&sk->rmem_alloc) > sk->rcvbuf ||
+ !tcp_rmem_schedule(sk, skb))) {
+ if (tcp_prune_queue(sk) < 0 || !tcp_rmem_schedule(sk, skb))
+ goto drop;
+ }
+ tcp_set_owner_r(skb, sk);
+ __skb_queue_tail(&sk->receive_queue, skb);
+ }
+ tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
+ if(skb->len)
+ tcp_event_data_recv(sk, tp, skb);
+ if(th->fin)
+ tcp_fin(skb, sk, th);
+
+ if (skb_queue_len(&tp->out_of_order_queue)) {
+ tcp_ofo_queue(sk);
+
+ /* RFC2581. 4.2. SHOULD send immediate ACK, when
+ * gap in queue is filled.
+ */
+ if (skb_queue_len(&tp->out_of_order_queue) == 0)
+ tp->ack.pingpong = 0;
+ }
+
+ if(tp->num_sacks)
+ tcp_sack_remove(tp);
+
+ tcp_fast_path_check(sk, tp);
+
+ if (eaten > 0) {
+ __kfree_skb(skb);
+ } else if (!sk->dead)
+ sk->data_ready(sk, 0);
+ return;
+ }
+
+ if (!after(TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt)) {
+ /* A retransmit, 2nd most common case. Force an immediate ack. */
+ NET_INC_STATS_BH(DelayedACKLost);
+ tcp_dsack_set(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq);
+
+out_of_window:
+ tcp_enter_quickack_mode(tp);
+ tcp_schedule_ack(tp);
+drop:
+ __kfree_skb(skb);
+ return;
+ }
+
+ /* Out of window. F.e. zero window probe. */
+ if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt+tcp_receive_window(tp)))
+ goto out_of_window;
+
+ tcp_enter_quickack_mode(tp);
+
+ if (before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
+ /* Partial packet, seq < rcv_next < end_seq */
+ SOCK_DEBUG(sk, "partial packet: rcv_next %X seq %X - %X\n",
+ tp->rcv_nxt, TCP_SKB_CB(skb)->seq,
+ TCP_SKB_CB(skb)->end_seq);
+
+ tcp_dsack_set(tp, TCP_SKB_CB(skb)->seq, tp->rcv_nxt);
+
+ /* If window is closed, drop tail of packet. But after
+ * remembering D-SACK for its head made in previous line.
+ */
+ if (!tcp_receive_window(tp))
+ goto out_of_window;
+ goto queue_and_out;
+ }
+
+ TCP_ECN_check_ce(tp, skb);
+
+ if (atomic_read(&sk->rmem_alloc) > sk->rcvbuf ||
+ !tcp_rmem_schedule(sk, skb)) {
+ if (tcp_prune_queue(sk) < 0 || !tcp_rmem_schedule(sk, skb))
+ goto drop;
+ }
+
+ /* Disable header prediction. */
+ tp->pred_flags = 0;
+ tcp_schedule_ack(tp);
+
+ SOCK_DEBUG(sk, "out of order segment: rcv_next %X seq %X - %X\n",
+ tp->rcv_nxt, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq);
+
+ tcp_set_owner_r(skb, sk);
+
+ if (skb_peek(&tp->out_of_order_queue) == NULL) {
+ /* Initial out of order segment, build 1 SACK. */
+ if(tp->sack_ok) {
+ tp->num_sacks = 1;
+ tp->dsack = 0;
+ tp->eff_sacks = 1;
+ tp->selective_acks[0].start_seq = TCP_SKB_CB(skb)->seq;
+ tp->selective_acks[0].end_seq = TCP_SKB_CB(skb)->end_seq;
+ }
+ __skb_queue_head(&tp->out_of_order_queue,skb);
+ } else {
+ struct sk_buff *skb1=tp->out_of_order_queue.prev;
+ u32 seq = TCP_SKB_CB(skb)->seq;
+ u32 end_seq = TCP_SKB_CB(skb)->end_seq;
+
+ if (seq == TCP_SKB_CB(skb1)->end_seq) {
+ __skb_append(skb1, skb);
+
+ if (tp->num_sacks == 0 ||
+ tp->selective_acks[0].end_seq != seq)
+ goto add_sack;
+
+ /* Common case: data arrive in order after hole. */
+ tp->selective_acks[0].end_seq = end_seq;
+ return;
+ }
+
+ /* Find place to insert this segment. */
+ do {
+ if (!after(TCP_SKB_CB(skb1)->seq, seq))
+ break;
+ } while ((skb1=skb1->prev) != (struct sk_buff*)&tp->out_of_order_queue);
+
+ /* Do skb overlap to previous one? */
+ if (skb1 != (struct sk_buff*)&tp->out_of_order_queue &&
+ before(seq, TCP_SKB_CB(skb1)->end_seq)) {
+ if (!after(end_seq, TCP_SKB_CB(skb1)->end_seq)) {
+ /* All the bits are present. Drop. */
+ __kfree_skb(skb);
+ tcp_dsack_set(tp, seq, end_seq);
+ goto add_sack;
+ }
+ if (after(seq, TCP_SKB_CB(skb1)->seq)) {
+ /* Partial overlap. */
+ tcp_dsack_set(tp, seq, TCP_SKB_CB(skb1)->end_seq);
+ } else {
+ skb1 = skb1->prev;
+ }
+ }
+ __skb_insert(skb, skb1, skb1->next, &tp->out_of_order_queue);
+
+ /* And clean segments covered by new one as whole. */
+ while ((skb1 = skb->next) != (struct sk_buff*)&tp->out_of_order_queue &&
+ after(end_seq, TCP_SKB_CB(skb1)->seq)) {
+ if (before(end_seq, TCP_SKB_CB(skb1)->end_seq)) {
+ tcp_dsack_extend(tp, TCP_SKB_CB(skb1)->seq, end_seq);
+ break;
+ }
+ __skb_unlink(skb1, skb1->list);
+ tcp_dsack_extend(tp, TCP_SKB_CB(skb1)->seq, TCP_SKB_CB(skb1)->end_seq);
+ __kfree_skb(skb1);
+ }
+
+add_sack:
+ if (tp->sack_ok)
+ tcp_sack_new_ofo_skb(sk, seq, end_seq);
+ }
+}
+
+/* Collapse contiguous sequence of skbs head..tail with
+ * sequence numbers start..end.
+ * Segments with FIN/SYN are not collapsed (only because this
+ * simplifies code)
+ */
+static void
+tcp_collapse(struct sock *sk, struct sk_buff *head,
+ struct sk_buff *tail, u32 start, u32 end)
+{
+ struct sk_buff *skb;
+
+ /* First, check that queue is collapsable and find
+ * the point where collapsing can be useful. */
+ for (skb = head; skb != tail; ) {
+ /* No new bits? It is possible on ofo queue. */
+ if (!before(start, TCP_SKB_CB(skb)->end_seq)) {
+ struct sk_buff *next = skb->next;
+ __skb_unlink(skb, skb->list);
+ __kfree_skb(skb);
+ NET_INC_STATS_BH(TCPRcvCollapsed);
+ skb = next;
+ continue;
+ }
+
+ /* The first skb to collapse is:
+ * - not SYN/FIN and
+ * - bloated or contains data before "start" or
+ * overlaps to the next one.
+ */
+ if (!skb->h.th->syn && !skb->h.th->fin &&
+ (tcp_win_from_space(skb->truesize) > skb->len ||
+ before(TCP_SKB_CB(skb)->seq, start) ||
+ (skb->next != tail &&
+ TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb->next)->seq)))
+ break;
+
+ /* Decided to skip this, advance start seq. */
+ start = TCP_SKB_CB(skb)->end_seq;
+ skb = skb->next;
+ }
+ if (skb == tail || skb->h.th->syn || skb->h.th->fin)
+ return;
+
+ while (before(start, end)) {
+ struct sk_buff *nskb;
+ int header = skb_headroom(skb);
+ int copy = SKB_MAX_ORDER(header, 0);
+
+ /* Too big header? This can happen with IPv6. */
+ if (copy < 0)
+ return;
+ if (end-start < copy)
+ copy = end-start;
+ nskb = alloc_skb(copy+header, GFP_ATOMIC);
+ if (!nskb)
+ return;
+ skb_reserve(nskb, header);
+ memcpy(nskb->head, skb->head, header);
+ nskb->nh.raw = nskb->head + (skb->nh.raw-skb->head);
+ nskb->h.raw = nskb->head + (skb->h.raw-skb->head);
+ nskb->mac.raw = nskb->head + (skb->mac.raw-skb->head);
+ memcpy(nskb->cb, skb->cb, sizeof(skb->cb));
+ TCP_SKB_CB(nskb)->seq = TCP_SKB_CB(nskb)->end_seq = start;
+ __skb_insert(nskb, skb->prev, skb, skb->list);
+ tcp_set_owner_r(nskb, sk);
+
+ /* Copy data, releasing collapsed skbs. */
+ while (copy > 0) {
+ int offset = start - TCP_SKB_CB(skb)->seq;
+ int size = TCP_SKB_CB(skb)->end_seq - start;
+
+ if (offset < 0) BUG();
+ if (size > 0) {
+ size = min(copy, size);
+ if (skb_copy_bits(skb, offset, skb_put(nskb, size), size))
+ BUG();
+ TCP_SKB_CB(nskb)->end_seq += size;
+ copy -= size;
+ start += size;
+ }
+ if (!before(start, TCP_SKB_CB(skb)->end_seq)) {
+ struct sk_buff *next = skb->next;
+ __skb_unlink(skb, skb->list);
+ __kfree_skb(skb);
+ NET_INC_STATS_BH(TCPRcvCollapsed);
+ skb = next;
+ if (skb == tail || skb->h.th->syn || skb->h.th->fin)
+ return;
+ }
+ }
+ }
+}
+
+/* Collapse ofo queue. Algorithm: select contiguous sequence of skbs
+ * and tcp_collapse() them until all the queue is collapsed.
+ */
+static void tcp_collapse_ofo_queue(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sk_buff *skb = skb_peek(&tp->out_of_order_queue);
+ struct sk_buff *head;
+ u32 start, end;
+
+ if (skb == NULL)
+ return;
+
+ start = TCP_SKB_CB(skb)->seq;
+ end = TCP_SKB_CB(skb)->end_seq;
+ head = skb;
+
+ for (;;) {
+ skb = skb->next;
+
+ /* Segment is terminated when we see gap or when
+ * we are at the end of all the queue. */
+ if (skb == (struct sk_buff *)&tp->out_of_order_queue ||
+ after(TCP_SKB_CB(skb)->seq, end) ||
+ before(TCP_SKB_CB(skb)->end_seq, start)) {
+ tcp_collapse(sk, head, skb, start, end);
+ head = skb;
+ if (skb == (struct sk_buff *)&tp->out_of_order_queue)
+ break;
+ /* Start new segment */
+ start = TCP_SKB_CB(skb)->seq;
+ end = TCP_SKB_CB(skb)->end_seq;
+ } else {
+ if (before(TCP_SKB_CB(skb)->seq, start))
+ start = TCP_SKB_CB(skb)->seq;
+ if (after(TCP_SKB_CB(skb)->end_seq, end))
+ end = TCP_SKB_CB(skb)->end_seq;
+ }
+ }
+}
+
+/* Reduce allocated memory if we can, trying to get
+ * the socket within its memory limits again.
+ *
+ * Return less than zero if we should start dropping frames
+ * until the socket owning process reads some of the data
+ * to stabilize the situation.
+ */
+static int tcp_prune_queue(struct sock *sk)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+
+ SOCK_DEBUG(sk, "prune_queue: c=%x\n", tp->copied_seq);
+
+ NET_INC_STATS_BH(PruneCalled);
+
+ if (atomic_read(&sk->rmem_alloc) >= sk->rcvbuf)
+ tcp_clamp_window(sk, tp);
+ else if (tcp_memory_pressure)
+ tp->rcv_ssthresh = min(tp->rcv_ssthresh, 4U*tp->advmss);
+
+ tcp_collapse_ofo_queue(sk);
+ tcp_collapse(sk, sk->receive_queue.next,
+ (struct sk_buff*)&sk->receive_queue,
+ tp->copied_seq, tp->rcv_nxt);
+ tcp_mem_reclaim(sk);
+
+ if (atomic_read(&sk->rmem_alloc) <= sk->rcvbuf)
+ return 0;
+
+ /* Collapsing did not help, destructive actions follow.
+ * This must not ever occur. */
+
+ /* First, purge the out_of_order queue. */
+ if (skb_queue_len(&tp->out_of_order_queue)) {
+ net_statistics[smp_processor_id()*2].OfoPruned += skb_queue_len(&tp->out_of_order_queue);
+ __skb_queue_purge(&tp->out_of_order_queue);
+
+ /* Reset SACK state. A conforming SACK implementation will
+ * do the same at a timeout based retransmit. When a connection
+ * is in a sad state like this, we care only about integrity
+ * of the connection not performance.
+ */
+ if(tp->sack_ok)
+ tcp_sack_reset(tp);
+ tcp_mem_reclaim(sk);
+ }
+
+ if(atomic_read(&sk->rmem_alloc) <= sk->rcvbuf)
+ return 0;
+
+ /* If we are really being abused, tell the caller to silently
+ * drop receive data on the floor. It will get retransmitted
+ * and hopefully then we'll have sufficient space.
+ */
+ NET_INC_STATS_BH(RcvPruned);
+
+ /* Massive buffer overcommit. */
+ tp->pred_flags = 0;
+ return -1;
+}
+
+
+/* RFC2861, slow part. Adjust cwnd, after it was not full during one rto.
+ * As additional protections, we do not touch cwnd in retransmission phases,
+ * and if application hit its sndbuf limit recently.
+ */
+void tcp_cwnd_application_limited(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ if (tp->ca_state == TCP_CA_Open &&
+ sk->socket && !test_bit(SOCK_NOSPACE, &sk->socket->flags)) {
+ /* Limited by application or receiver window. */
+ u32 win_used = max(tp->snd_cwnd_used, 2U);
+ if (win_used < tp->snd_cwnd) {
+ tp->snd_ssthresh = tcp_current_ssthresh(tp);
+ tp->snd_cwnd = (tp->snd_cwnd+win_used)>>1;
+ }
+ tp->snd_cwnd_used = 0;
+ }
+ tp->snd_cwnd_stamp = tcp_time_stamp;
+}
+
+
+/* When incoming ACK allowed to free some skb from write_queue,
+ * we remember this event in flag tp->queue_shrunk and wake up socket
+ * on the exit from tcp input handler.
+ */
+static void tcp_new_space(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ if (tp->packets_out < tp->snd_cwnd &&
+ !(sk->userlocks&SOCK_SNDBUF_LOCK) &&
+ !tcp_memory_pressure &&
+ atomic_read(&tcp_memory_allocated) < sysctl_tcp_mem[0]) {
+ int sndmem, demanded;
+
+ sndmem = tp->mss_clamp+MAX_TCP_HEADER+16+sizeof(struct sk_buff);
+ demanded = max_t(unsigned int, tp->snd_cwnd, tp->reordering+1);
+ sndmem *= 2*demanded;
+ if (sndmem > sk->sndbuf)
+ sk->sndbuf = min(sndmem, sysctl_tcp_wmem[2]);
+ tp->snd_cwnd_stamp = tcp_time_stamp;
+ }
+
+ sk->write_space(sk);
+}
+
+static inline void tcp_check_space(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ if (tp->queue_shrunk) {
+ tp->queue_shrunk = 0;
+ if (sk->socket && test_bit(SOCK_NOSPACE, &sk->socket->flags))
+ tcp_new_space(sk);
+ }
+}
+
+static void __tcp_data_snd_check(struct sock *sk, struct sk_buff *skb)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ if (after(TCP_SKB_CB(skb)->end_seq, tp->snd_una + tp->snd_wnd) ||
+ tcp_packets_in_flight(tp) >= tp->snd_cwnd ||
+ tcp_write_xmit(sk, tp->nonagle))
+ tcp_check_probe_timer(sk, tp);
+}
+
+static __inline__ void tcp_data_snd_check(struct sock *sk)
+{
+ struct sk_buff *skb = sk->tp_pinfo.af_tcp.send_head;
+
+ if (skb != NULL)
+ __tcp_data_snd_check(sk, skb);
+ tcp_check_space(sk);
+}
+
+/*
+ * Check if sending an ack is needed.
+ */
+static __inline__ void __tcp_ack_snd_check(struct sock *sk, int ofo_possible)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ /* More than one full frame received... */
+ if (((tp->rcv_nxt - tp->rcv_wup) > tp->ack.rcv_mss
+ /* ... and right edge of window advances far enough.
+ * (tcp_recvmsg() will send ACK otherwise). Or...
+ */
+ && __tcp_select_window(sk) >= tp->rcv_wnd) ||
+ /* We ACK each frame or... */
+ tcp_in_quickack_mode(tp) ||
+ /* We have out of order data. */
+ (ofo_possible &&
+ skb_peek(&tp->out_of_order_queue) != NULL)) {
+ /* Then ack it now */
+ tcp_send_ack(sk);
+ } else {
+ /* Else, send delayed ack. */
+ tcp_send_delayed_ack(sk);
+ }
+}
+
+static __inline__ void tcp_ack_snd_check(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ if (!tcp_ack_scheduled(tp)) {
+ /* We sent a data segment already. */
+ return;
+ }
+ __tcp_ack_snd_check(sk, 1);
+}
+
+/*
+ * This routine is only called when we have urgent data
+ * signalled. Its the 'slow' part of tcp_urg. It could be
+ * moved inline now as tcp_urg is only called from one
+ * place. We handle URGent data wrong. We have to - as
+ * BSD still doesn't use the correction from RFC961.
+ * For 1003.1g we should support a new option TCP_STDURG to permit
+ * either form (or just set the sysctl tcp_stdurg).
+ */
+
+static void tcp_check_urg(struct sock * sk, struct tcphdr * th)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ u32 ptr = ntohs(th->urg_ptr);
+
+ if (ptr && !sysctl_tcp_stdurg)
+ ptr--;
+ ptr += ntohl(th->seq);
+
+ /* Ignore urgent data that we've already seen and read. */
+ if (after(tp->copied_seq, ptr))
+ return;
+
+ /* Do not replay urg ptr.
+ *
+ * NOTE: interesting situation not covered by specs.
+ * Misbehaving sender may send urg ptr, pointing to segment,
+ * which we already have in ofo queue. We are not able to fetch
+ * such data and will stay in TCP_URG_NOTYET until will be eaten
+ * by recvmsg(). Seems, we are not obliged to handle such wicked
+ * situations. But it is worth to think about possibility of some
+ * DoSes using some hypothetical application level deadlock.
+ */
+ if (before(ptr, tp->rcv_nxt))
+ return;
+
+ /* Do we already have a newer (or duplicate) urgent pointer? */
+ if (tp->urg_data && !after(ptr, tp->urg_seq))
+ return;
+
+ /* Tell the world about our new urgent pointer. */
+ if (sk->proc != 0) {
+ if (sk->proc > 0)
+ kill_proc(sk->proc, SIGURG, 1);
+ else
+ kill_pg(-sk->proc, SIGURG, 1);
+ sk_wake_async(sk, 3, POLL_PRI);
+ }
+
+ /* We may be adding urgent data when the last byte read was
+ * urgent. To do this requires some care. We cannot just ignore
+ * tp->copied_seq since we would read the last urgent byte again
+ * as data, nor can we alter copied_seq until this data arrives
+ * or we break the sematics of SIOCATMARK (and thus sockatmark())
+ *
+ * NOTE. Double Dutch. Rendering to plain English: author of comment
+ * above did something sort of send("A", MSG_OOB); send("B", MSG_OOB);
+ * and expect that both A and B disappear from stream. This is _wrong_.
+ * Though this happens in BSD with high probability, this is occasional.
+ * Any application relying on this is buggy. Note also, that fix "works"
+ * only in this artificial test. Insert some normal data between A and B and we will
+ * decline of BSD again. Verdict: it is better to remove to trap
+ * buggy users.
+ */
+ if (tp->urg_seq == tp->copied_seq && tp->urg_data &&
+ !sk->urginline &&
+ tp->copied_seq != tp->rcv_nxt) {
+ struct sk_buff *skb = skb_peek(&sk->receive_queue);
+ tp->copied_seq++;
+ if (skb && !before(tp->copied_seq, TCP_SKB_CB(skb)->end_seq)) {
+ __skb_unlink(skb, skb->list);
+ __kfree_skb(skb);
+ }
+ }
+
+ tp->urg_data = TCP_URG_NOTYET;
+ tp->urg_seq = ptr;
+
+ /* Disable header prediction. */
+ tp->pred_flags = 0;
+}
+
+/* This is the 'fast' part of urgent handling. */
+static inline void tcp_urg(struct sock *sk, struct sk_buff *skb, struct tcphdr *th)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ /* Check if we get a new urgent pointer - normally not. */
+ if (th->urg)
+ tcp_check_urg(sk,th);
+
+ /* Do we wait for any urgent data? - normally not... */
+ if (tp->urg_data == TCP_URG_NOTYET) {
+ u32 ptr = tp->urg_seq - ntohl(th->seq) + (th->doff*4) - th->syn;
+
+ /* Is the urgent pointer pointing into this packet? */
+ if (ptr < skb->len) {
+ u8 tmp;
+ if (skb_copy_bits(skb, ptr, &tmp, 1))
+ BUG();
+ tp->urg_data = TCP_URG_VALID | tmp;
+ if (!sk->dead)
+ sk->data_ready(sk,0);
+ }
+ }
+}
+
+static int tcp_copy_to_iovec(struct sock *sk, struct sk_buff *skb, int hlen)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int chunk = skb->len - hlen;
+ int err;
+
+ local_bh_enable();
+ if (skb->ip_summed==CHECKSUM_UNNECESSARY)
+ err = skb_copy_datagram_iovec(skb, hlen, tp->ucopy.iov, chunk);
+ else
+ err = skb_copy_and_csum_datagram_iovec(skb, hlen, tp->ucopy.iov);
+
+ if (!err) {
+ tp->ucopy.len -= chunk;
+ tp->copied_seq += chunk;
+ tcp_rcv_space_adjust(sk);
+ }
+
+ local_bh_disable();
+ return err;
+}
+
+static int __tcp_checksum_complete_user(struct sock *sk, struct sk_buff *skb)
+{
+ int result;
+
+ if (sk->lock.users) {
+ local_bh_enable();
+ result = __tcp_checksum_complete(skb);
+ local_bh_disable();
+ } else {
+ result = __tcp_checksum_complete(skb);
+ }
+ return result;
+}
+
+static __inline__ int
+tcp_checksum_complete_user(struct sock *sk, struct sk_buff *skb)
+{
+ return skb->ip_summed != CHECKSUM_UNNECESSARY &&
+ __tcp_checksum_complete_user(sk, skb);
+}
+
+/*
+ * TCP receive function for the ESTABLISHED state.
+ *
+ * It is split into a fast path and a slow path. The fast path is
+ * disabled when:
+ * - A zero window was announced from us - zero window probing
+ * is only handled properly in the slow path.
+ * - Out of order segments arrived.
+ * - Urgent data is expected.
+ * - There is no buffer space left
+ * - Unexpected TCP flags/window values/header lengths are received
+ * (detected by checking the TCP header against pred_flags)
+ * - Data is sent in both directions. Fast path only supports pure senders
+ * or pure receivers (this means either the sequence number or the ack
+ * value must stay constant)
+ * - Unexpected TCP option.
+ *
+ * When these conditions are not satisfied it drops into a standard
+ * receive procedure patterned after RFC793 to handle all cases.
+ * The first three cases are guaranteed by proper pred_flags setting,
+ * the rest is checked inline. Fast processing is turned on in
+ * tcp_data_queue when everything is OK.
+ */
+int tcp_rcv_established(struct sock *sk, struct sk_buff *skb,
+ struct tcphdr *th, unsigned len)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ /*
+ * Header prediction.
+ * The code loosely follows the one in the famous
+ * "30 instruction TCP receive" Van Jacobson mail.
+ *
+ * Van's trick is to deposit buffers into socket queue
+ * on a device interrupt, to call tcp_recv function
+ * on the receive process context and checksum and copy
+ * the buffer to user space. smart...
+ *
+ * Our current scheme is not silly either but we take the
+ * extra cost of the net_bh soft interrupt processing...
+ * We do checksum and copy also but from device to kernel.
+ */
+
+ tp->saw_tstamp = 0;
+
+ /* pred_flags is 0xS?10 << 16 + snd_wnd
+ * if header_predition is to be made
+ * 'S' will always be tp->tcp_header_len >> 2
+ * '?' will be 0 for the fast path, otherwise pred_flags is 0 to
+ * turn it off (when there are holes in the receive
+ * space for instance)
+ * PSH flag is ignored.
+ */
+
+ if ((tcp_flag_word(th) & TCP_HP_BITS) == tp->pred_flags &&
+ TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {
+ int tcp_header_len = tp->tcp_header_len;
+
+ /* Timestamp header prediction: tcp_header_len
+ * is automatically equal to th->doff*4 due to pred_flags
+ * match.
+ */
+
+ /* Check timestamp */
+ if (tcp_header_len == sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) {
+ __u32 *ptr = (__u32 *)(th + 1);
+
+ /* No? Slow path! */
+ if (*ptr != ntohl((TCPOPT_NOP << 24) | (TCPOPT_NOP << 16)
+ | (TCPOPT_TIMESTAMP << 8) | TCPOLEN_TIMESTAMP))
+ goto slow_path;
+
+ tp->saw_tstamp = 1;
+ ++ptr;
+ tp->rcv_tsval = ntohl(*ptr);
+ ++ptr;
+ tp->rcv_tsecr = ntohl(*ptr);
+
+ /* If PAWS failed, check it more carefully in slow path */
+ if ((s32)(tp->rcv_tsval - tp->ts_recent) < 0)
+ goto slow_path;
+
+ /* DO NOT update ts_recent here, if checksum fails
+ * and timestamp was corrupted part, it will result
+ * in a hung connection since we will drop all
+ * future packets due to the PAWS test.
+ */
+ }
+
+ if (len <= tcp_header_len) {
+ /* Bulk data transfer: sender */
+ if (len == tcp_header_len) {
+ /* Predicted packet is in window by definition.
+ * seq == rcv_nxt and rcv_wup <= rcv_nxt.
+ * Hence, check seq<=rcv_wup reduces to:
+ */
+ if (tcp_header_len ==
+ (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
+ tp->rcv_nxt == tp->rcv_wup)
+ tcp_store_ts_recent(tp);
+
+ tcp_rcv_rtt_measure_ts(tp, skb);
+
+ /* We know that such packets are checksummed
+ * on entry.
+ */
+ tcp_ack(sk, skb, 0);
+ __kfree_skb(skb);
+ tcp_data_snd_check(sk);
+ return 0;
+ } else { /* Header too small */
+ TCP_INC_STATS_BH(TcpInErrs);
+ goto discard;
+ }
+ } else {
+ int eaten = 0;
+
+ if (tp->ucopy.task == current &&
+ tp->copied_seq == tp->rcv_nxt &&
+ len - tcp_header_len <= tp->ucopy.len &&
+ sk->lock.users) {
+ __set_current_state(TASK_RUNNING);
+
+ if (!tcp_copy_to_iovec(sk, skb, tcp_header_len)) {
+ /* Predicted packet is in window by definition.
+ * seq == rcv_nxt and rcv_wup <= rcv_nxt.
+ * Hence, check seq<=rcv_wup reduces to:
+ */
+ if (tcp_header_len ==
+ (sizeof(struct tcphdr) +
+ TCPOLEN_TSTAMP_ALIGNED) &&
+ tp->rcv_nxt == tp->rcv_wup)
+ tcp_store_ts_recent(tp);
+
+ tcp_rcv_rtt_measure_ts(tp, skb);
+
+ __skb_pull(skb, tcp_header_len);
+ tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
+ NET_INC_STATS_BH(TCPHPHitsToUser);
+ eaten = 1;
+ }
+ }
+ if (!eaten) {
+ if (tcp_checksum_complete_user(sk, skb))
+ goto csum_error;
+
+ /* Predicted packet is in window by definition.
+ * seq == rcv_nxt and rcv_wup <= rcv_nxt.
+ * Hence, check seq<=rcv_wup reduces to:
+ */
+ if (tcp_header_len ==
+ (sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
+ tp->rcv_nxt == tp->rcv_wup)
+ tcp_store_ts_recent(tp);
+
+ tcp_rcv_rtt_measure_ts(tp, skb);
+
+ if ((int)skb->truesize > sk->forward_alloc)
+ goto step5;
+
+ NET_INC_STATS_BH(TCPHPHits);
+
+ /* Bulk data transfer: receiver */
+ __skb_pull(skb,tcp_header_len);
+ __skb_queue_tail(&sk->receive_queue, skb);
+ tcp_set_owner_r(skb, sk);
+ tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
+ }
+
+ tcp_event_data_recv(sk, tp, skb);
+
+ if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) {
+ /* Well, only one small jumplet in fast path... */
+ tcp_ack(sk, skb, FLAG_DATA);
+ tcp_data_snd_check(sk);
+ if (!tcp_ack_scheduled(tp))
+ goto no_ack;
+ }
+
+ __tcp_ack_snd_check(sk, 0);
+no_ack:
+ if (eaten)
+ __kfree_skb(skb);
+ else
+ sk->data_ready(sk, 0);
+ return 0;
+ }
+ }
+
+slow_path:
+ if (len < (th->doff<<2) || tcp_checksum_complete_user(sk, skb))
+ goto csum_error;
+
+ /*
+ * RFC1323: H1. Apply PAWS check first.
+ */
+ if (tcp_fast_parse_options(skb, th, tp) && tp->saw_tstamp &&
+ tcp_paws_discard(tp, skb)) {
+ if (!th->rst) {
+ NET_INC_STATS_BH(PAWSEstabRejected);
+ tcp_send_dupack(sk, skb);
+ goto discard;
+ }
+ /* Resets are accepted even if PAWS failed.
+
+ ts_recent update must be made after we are sure
+ that the packet is in window.
+ */
+ }
+
+ /*
+ * Standard slow path.
+ */
+
+ if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
+ /* RFC793, page 37: "In all states except SYN-SENT, all reset
+ * (RST) segments are validated by checking their SEQ-fields."
+ * And page 69: "If an incoming segment is not acceptable,
+ * an acknowledgment should be sent in reply (unless the RST bit
+ * is set, if so drop the segment and return)".
+ */
+ if (!th->rst)
+ tcp_send_dupack(sk, skb);
+ goto discard;
+ }
+
+ if(th->rst) {
+ tcp_reset(sk);
+ goto discard;
+ }
+
+ tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq);
+
+ if (th->syn && !before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
+ TCP_INC_STATS_BH(TcpInErrs);
+ NET_INC_STATS_BH(TCPAbortOnSyn);
+ tcp_reset(sk);
+ return 1;
+ }
+
+step5:
+ if(th->ack)
+ tcp_ack(sk, skb, FLAG_SLOWPATH);
+
+ tcp_rcv_rtt_measure_ts(tp, skb);
+
+ /* Process urgent data. */
+ tcp_urg(sk, skb, th);
+
+ /* step 7: process the segment text */
+ tcp_data_queue(sk, skb);
+
+ tcp_data_snd_check(sk);
+ tcp_ack_snd_check(sk);
+ return 0;
+
+csum_error:
+ TCP_INC_STATS_BH(TcpInErrs);
+
+discard:
+ __kfree_skb(skb);
+ return 0;
+}
+
+static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
+ struct tcphdr *th, unsigned len)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int saved_clamp = tp->mss_clamp;
+
+ tcp_parse_options(skb, tp, 0);
+
+ if (th->ack) {
+ /* rfc793:
+ * "If the state is SYN-SENT then
+ * first check the ACK bit
+ * If the ACK bit is set
+ * If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send
+ * a reset (unless the RST bit is set, if so drop
+ * the segment and return)"
+ *
+ * We do not send data with SYN, so that RFC-correct
+ * test reduces to:
+ */
+ if (TCP_SKB_CB(skb)->ack_seq != tp->snd_nxt)
+ goto reset_and_undo;
+
+ if (tp->saw_tstamp && tp->rcv_tsecr &&
+ !between(tp->rcv_tsecr, tp->retrans_stamp, tcp_time_stamp)) {
+ NET_INC_STATS_BH(PAWSActiveRejected);
+ goto reset_and_undo;
+ }
+
+ /* Now ACK is acceptable.
+ *
+ * "If the RST bit is set
+ * If the ACK was acceptable then signal the user "error:
+ * connection reset", drop the segment, enter CLOSED state,
+ * delete TCB, and return."
+ */
+
+ if (th->rst) {
+ tcp_reset(sk);
+ goto discard;
+ }
+
+ /* rfc793:
+ * "fifth, if neither of the SYN or RST bits is set then
+ * drop the segment and return."
+ *
+ * See note below!
+ * --ANK(990513)
+ */
+ if (!th->syn)
+ goto discard_and_undo;
+
+ /* rfc793:
+ * "If the SYN bit is on ...
+ * are acceptable then ...
+ * (our SYN has been ACKed), change the connection
+ * state to ESTABLISHED..."
+ */
+
+ TCP_ECN_rcv_synack(tp, th);
+
+ tp->snd_wl1 = TCP_SKB_CB(skb)->seq;
+ tcp_ack(sk, skb, FLAG_SLOWPATH);
+
+ /* Ok.. it's good. Set up sequence numbers and
+ * move to established.
+ */
+ tp->rcv_nxt = TCP_SKB_CB(skb)->seq+1;
+ tp->rcv_wup = TCP_SKB_CB(skb)->seq+1;
+
+ /* RFC1323: The window in SYN & SYN/ACK segments is
+ * never scaled.
+ */
+ tp->snd_wnd = ntohs(th->window);
+ tcp_init_wl(tp, TCP_SKB_CB(skb)->ack_seq, TCP_SKB_CB(skb)->seq);
+
+ if (tp->wscale_ok == 0) {
+ tp->snd_wscale = tp->rcv_wscale = 0;
+ tp->window_clamp = min(tp->window_clamp, 65535U);
+ }
+
+ if (tp->saw_tstamp) {
+ tp->tstamp_ok = 1;
+ tp->tcp_header_len =
+ sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
+ tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;
+ tcp_store_ts_recent(tp);
+ } else {
+ tp->tcp_header_len = sizeof(struct tcphdr);
+ }
+
+ if (tp->sack_ok && sysctl_tcp_fack)
+ tp->sack_ok |= 2;
+
+ tcp_sync_mss(sk, tp->pmtu_cookie);
+ tcp_initialize_rcv_mss(sk);
+ tcp_init_metrics(sk);
+ tcp_init_buffer_space(sk);
+
+ if (sk->keepopen)
+ tcp_reset_keepalive_timer(sk, keepalive_time_when(tp));
+
+ if (tp->snd_wscale == 0)
+ __tcp_fast_path_on(tp, tp->snd_wnd);
+ else
+ tp->pred_flags = 0;
+
+ /* Remember, tcp_poll() does not lock socket!
+ * Change state from SYN-SENT only after copied_seq
+ * is initialized. */
+ tp->copied_seq = tp->rcv_nxt;
+ mb();
+ tcp_set_state(sk, TCP_ESTABLISHED);
+
+ if(!sk->dead) {
+ sk->state_change(sk);
+ sk_wake_async(sk, 0, POLL_OUT);
+ }
+
+ if (tp->write_pending || tp->defer_accept || tp->ack.pingpong) {
+ /* Save one ACK. Data will be ready after
+ * several ticks, if write_pending is set.
+ *
+ * It may be deleted, but with this feature tcpdumps
+ * look so _wonderfully_ clever, that I was not able
+ * to stand against the temptation 8) --ANK
+ */
+ tcp_schedule_ack(tp);
+ tp->ack.lrcvtime = tcp_time_stamp;
+ tp->ack.ato = TCP_ATO_MIN;
+ tcp_incr_quickack(tp);
+ tcp_enter_quickack_mode(tp);
+ tcp_reset_xmit_timer(sk, TCP_TIME_DACK, TCP_DELACK_MAX);
+
+discard:
+ __kfree_skb(skb);
+ return 0;
+ } else {
+ tcp_send_ack(sk);
+ }
+ return -1;
+ }
+
+ /* No ACK in the segment */
+
+ if (th->rst) {
+ /* rfc793:
+ * "If the RST bit is set
+ *
+ * Otherwise (no ACK) drop the segment and return."
+ */
+
+ goto discard_and_undo;
+ }
+
+ /* PAWS check. */
+ if (tp->ts_recent_stamp && tp->saw_tstamp && tcp_paws_check(tp, 0))
+ goto discard_and_undo;
+
+ if (th->syn) {
+ /* We see SYN without ACK. It is attempt of
+ * simultaneous connect with crossed SYNs.
+ * Particularly, it can be connect to self.
+ */
+ tcp_set_state(sk, TCP_SYN_RECV);
+
+ if (tp->saw_tstamp) {
+ tp->tstamp_ok = 1;
+ tcp_store_ts_recent(tp);
+ tp->tcp_header_len =
+ sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
+ } else {
+ tp->tcp_header_len = sizeof(struct tcphdr);
+ }
+
+ tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;
+ tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;
+
+ /* RFC1323: The window in SYN & SYN/ACK segments is
+ * never scaled.
+ */
+ tp->snd_wnd = ntohs(th->window);
+ tp->snd_wl1 = TCP_SKB_CB(skb)->seq;
+ tp->max_window = tp->snd_wnd;
+
+ tcp_sync_mss(sk, tp->pmtu_cookie);
+ tcp_initialize_rcv_mss(sk);
+
+ TCP_ECN_rcv_syn(tp, th);
+
+ tcp_send_synack(sk);
+#if 0
+ /* Note, we could accept data and URG from this segment.
+ * There are no obstacles to make this.
+ *
+ * However, if we ignore data in ACKless segments sometimes,
+ * we have no reasons to accept it sometimes.
+ * Also, seems the code doing it in step6 of tcp_rcv_state_process
+ * is not flawless. So, discard packet for sanity.
+ * Uncomment this return to process the data.
+ */
+ return -1;
+#else
+ goto discard;
+#endif
+ }
+ /* "fifth, if neither of the SYN or RST bits is set then
+ * drop the segment and return."
+ */
+
+discard_and_undo:
+ tcp_clear_options(tp);
+ tp->mss_clamp = saved_clamp;
+ goto discard;
+
+reset_and_undo:
+ tcp_clear_options(tp);
+ tp->mss_clamp = saved_clamp;
+ return 1;
+}
+
+/*
+ * This function implements the receiving procedure of RFC 793 for
+ * all states except ESTABLISHED and TIME_WAIT.
+ * It's called from both tcp_v4_rcv and tcp_v6_rcv and should be
+ * address independent.
+ */
+
+int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
+ struct tcphdr *th, unsigned len)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int queued = 0;
+
+ tp->saw_tstamp = 0;
+
+ switch (sk->state) {
+ case TCP_CLOSE:
+ goto discard;
+
+ case TCP_LISTEN:
+ if(th->ack)
+ return 1;
+
+ if(th->rst)
+ goto discard;
+
+ if(th->syn) {
+ if(tp->af_specific->conn_request(sk, skb) < 0)
+ return 1;
+
+ tcp_init_westwood(sk);
+ init_bictcp(tp);
+
+ /* Now we have several options: In theory there is
+ * nothing else in the frame. KA9Q has an option to
+ * send data with the syn, BSD accepts data with the
+ * syn up to the [to be] advertised window and
+ * Solaris 2.1 gives you a protocol error. For now
+ * we just ignore it, that fits the spec precisely
+ * and avoids incompatibilities. It would be nice in
+ * future to drop through and process the data.
+ *
+ * Now that TTCP is starting to be used we ought to
+ * queue this data.
+ * But, this leaves one open to an easy denial of
+ * service attack, and SYN cookies can't defend
+ * against this problem. So, we drop the data
+ * in the interest of security over speed.
+ */
+ goto discard;
+ }
+ goto discard;
+
+ case TCP_SYN_SENT:
+ tcp_init_westwood(sk);
+ init_bictcp(tp);
+
+ queued = tcp_rcv_synsent_state_process(sk, skb, th, len);
+ if (queued >= 0)
+ return queued;
+
+ /* Do step6 onward by hand. */
+ tcp_urg(sk, skb, th);
+ __kfree_skb(skb);
+ tcp_data_snd_check(sk);
+ return 0;
+ }
+
+ if (tcp_fast_parse_options(skb, th, tp) && tp->saw_tstamp &&
+ tcp_paws_discard(tp, skb)) {
+ if (!th->rst) {
+ NET_INC_STATS_BH(PAWSEstabRejected);
+ tcp_send_dupack(sk, skb);
+ goto discard;
+ }
+ /* Reset is accepted even if it did not pass PAWS. */
+ }
+
+ /* step 1: check sequence number */
+ if (!tcp_sequence(tp, TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq)) {
+ if (!th->rst)
+ tcp_send_dupack(sk, skb);
+ goto discard;
+ }
+
+ /* step 2: check RST bit */
+ if(th->rst) {
+ tcp_reset(sk);
+ goto discard;
+ }
+
+ tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq);
+
+ /* step 3: check security and precedence [ignored] */
+
+ /* step 4:
+ *
+ * Check for a SYN in window.
+ */
+ if (th->syn && !before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
+ NET_INC_STATS_BH(TCPAbortOnSyn);
+ tcp_reset(sk);
+ return 1;
+ }
+
+ /* step 5: check the ACK field */
+ if (th->ack) {
+ int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH);
+
+ switch(sk->state) {
+ case TCP_SYN_RECV:
+ if (acceptable) {
+ tp->copied_seq = tp->rcv_nxt;
+ mb();
+ tcp_set_state(sk, TCP_ESTABLISHED);
+ sk->state_change(sk);
+
+ /* Note, that this wakeup is only for marginal
+ * crossed SYN case. Passively open sockets
+ * are not waked up, because sk->sleep == NULL
+ * and sk->socket == NULL.
+ */
+ if (sk->socket) {
+ sk_wake_async(sk,0,POLL_OUT);
+ }
+
+ tp->snd_una = TCP_SKB_CB(skb)->ack_seq;
+ tp->snd_wnd = ntohs(th->window) << tp->snd_wscale;
+ tcp_init_wl(tp, TCP_SKB_CB(skb)->ack_seq, TCP_SKB_CB(skb)->seq);
+
+ /* tcp_ack considers this ACK as duplicate
+ * and does not calculate rtt.
+ * Fix it at least with timestamps.
+ */
+ if (tp->saw_tstamp && tp->rcv_tsecr && !tp->srtt)
+ tcp_ack_saw_tstamp(tp, 0);
+
+ if (tp->tstamp_ok)
+ tp->advmss -= TCPOLEN_TSTAMP_ALIGNED;
+
+ tcp_init_metrics(sk);
+ tcp_initialize_rcv_mss(sk);
+ tcp_init_buffer_space(sk);
+ tcp_fast_path_on(tp);
+ } else {
+ return 1;
+ }
+ break;
+
+ case TCP_FIN_WAIT1:
+ if (tp->snd_una == tp->write_seq) {
+ tcp_set_state(sk, TCP_FIN_WAIT2);
+ sk->shutdown |= SEND_SHUTDOWN;
+ dst_confirm(sk->dst_cache);
+
+ if (!sk->dead) {
+ /* Wake up lingering close() */
+ sk->state_change(sk);
+ } else {
+ int tmo;
+
+ if (tp->linger2 < 0 ||
+ (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
+ after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt))) {
+ tcp_done(sk);
+ NET_INC_STATS_BH(TCPAbortOnData);
+ return 1;
+ }
+
+ tmo = tcp_fin_time(tp);
+ if (tmo > TCP_TIMEWAIT_LEN) {
+ tcp_reset_keepalive_timer(sk, tmo - TCP_TIMEWAIT_LEN);
+ } else if (th->fin || sk->lock.users) {
+ /* Bad case. We could lose such FIN otherwise.
+ * It is not a big problem, but it looks confusing
+ * and not so rare event. We still can lose it now,
+ * if it spins in bh_lock_sock(), but it is really
+ * marginal case.
+ */
+ tcp_reset_keepalive_timer(sk, tmo);
+ } else {
+ tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
+ goto discard;
+ }
+ }
+ }
+ break;
+
+ case TCP_CLOSING:
+ if (tp->snd_una == tp->write_seq) {
+ tcp_time_wait(sk, TCP_TIME_WAIT, 0);
+ goto discard;
+ }
+ break;
+
+ case TCP_LAST_ACK:
+ if (tp->snd_una == tp->write_seq) {
+ tcp_update_metrics(sk);
+ tcp_done(sk);
+ goto discard;
+ }
+ break;
+ }
+ } else
+ goto discard;
+
+ /* step 6: check the URG bit */
+ tcp_urg(sk, skb, th);
+
+ /* step 7: process the segment text */
+ switch (sk->state) {
+ case TCP_CLOSE_WAIT:
+ case TCP_CLOSING:
+ case TCP_LAST_ACK:
+ if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt))
+ break;
+ case TCP_FIN_WAIT1:
+ case TCP_FIN_WAIT2:
+ /* RFC 793 says to queue data in these states,
+ * RFC 1122 says we MUST send a reset.
+ * BSD 4.4 also does reset.
+ */
+ if (sk->shutdown & RCV_SHUTDOWN) {
+ if (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
+ after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt)) {
+ NET_INC_STATS_BH(TCPAbortOnData);
+ tcp_reset(sk);
+ return 1;
+ }
+ }
+ /* Fall through */
+ case TCP_ESTABLISHED:
+ tcp_data_queue(sk, skb);
+ queued = 1;
+ break;
+ }
+
+ /* tcp_data could move socket to TIME-WAIT */
+ if (sk->state != TCP_CLOSE) {
+ tcp_data_snd_check(sk);
+ tcp_ack_snd_check(sk);
+ }
+
+ if (!queued) {
+discard:
+ __kfree_skb(skb);
+ }
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/tcp_ipv4.c b/uClinux-2.4.31-uc0/net/ipv4/tcp_ipv4.c
new file mode 100644
index 0000000..73bcb99
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/tcp_ipv4.c
@@ -0,0 +1,2520 @@
+/* $USAGI: tcp_ipv4.c,v 1.81 2003/11/12 05:12:00 yoshfuji Exp $ */
+
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * Implementation of the Transmission Control Protocol(TCP).
+ *
+ * Version: $Id: tcp_ipv4.c,v 1.237.2.1 2002/01/15 08:49:49 davem Exp $
+ *
+ * IPv4 specific functions
+ *
+ *
+ * code split from:
+ * linux/ipv4/tcp.c
+ * linux/ipv4/tcp_input.c
+ * linux/ipv4/tcp_output.c
+ *
+ * See tcp.c for author information
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/*
+ * Changes:
+ * David S. Miller : New socket lookup architecture.
+ * This code is dedicated to John Dyson.
+ * David S. Miller : Change semantics of established hash,
+ * half is devoted to TIME_WAIT sockets
+ * and the rest go in the other half.
+ * Andi Kleen : Add support for syncookies and fixed
+ * some bugs: ip options weren't passed to
+ * the TCP layer, missed a check for an ACK bit.
+ * Andi Kleen : Implemented fast path mtu discovery.
+ * Fixed many serious bugs in the
+ * open_request handling and moved
+ * most of it into the af independent code.
+ * Added tail drop and some other bugfixes.
+ * Added new listen sematics.
+ * Mike McLagan : Routing by source
+ * Juan Jose Ciarlante: ip_dynaddr bits
+ * Andi Kleen: various fixes.
+ * Vitaly E. Lavrov : Transparent proxy revived after year coma.
+ * Andi Kleen : Fix new listen.
+ * Andi Kleen : Fix accept error reporting.
+ * yoshfuji@USAGI : Reworked bind(2) behavior, including:
+ * - Allow ipv6 and ipv4 bind(2) to the
+ * same port.
+ * - Don't allow narrow binding unless
+ * later uid is the same as before:
+ * CONFIG_NET_RESTRICTED_REUSE
+ * - Don't allow binding to the same
+ * address unless it is one of multi-
+ * cast address even if SO_REUSEADDR
+ * is set.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/random.h>
+#include <linux/cache.h>
+#include <linux/jhash.h>
+#include <linux/init.h>
+
+#include <net/icmp.h>
+#include <net/tcp.h>
+#include <net/ipv6.h>
+#include <net/inet_common.h>
+
+#include <linux/inet.h>
+#include <linux/stddef.h>
+#include <linux/ipsec.h>
+
+extern int sysctl_ip_dynaddr;
+extern int sysctl_ip_default_ttl;
+int sysctl_tcp_tw_reuse = 0;
+int sysctl_tcp_low_latency = 0;
+
+/* Check TCP sequence numbers in ICMP packets. */
+#define ICMP_MIN_LENGTH 8
+
+/* Socket used for sending RSTs */
+static struct inode tcp_inode;
+static struct socket *tcp_socket=&tcp_inode.u.socket_i;
+
+void tcp_v4_send_check(struct sock *sk, struct tcphdr *th, int len,
+ struct sk_buff *skb);
+
+/*
+ * ALL members must be initialised to prevent gcc-2.7.2.3 miscompilation
+ */
+struct tcp_hashinfo __cacheline_aligned tcp_hashinfo = {
+ __tcp_ehash: NULL,
+ __tcp_bhash: NULL,
+ __tcp_bhash_size: 0,
+ __tcp_ehash_size: 0,
+ __tcp_listening_hash: { NULL, },
+ __tcp_lhash_lock: RW_LOCK_UNLOCKED,
+ __tcp_lhash_users: ATOMIC_INIT(0),
+ __tcp_lhash_wait:
+ __WAIT_QUEUE_HEAD_INITIALIZER(tcp_hashinfo.__tcp_lhash_wait),
+ __tcp_portalloc_lock: SPIN_LOCK_UNLOCKED
+};
+
+/*
+ * This array holds the first and last local port number.
+ * For high-usage systems, use sysctl to change this to
+ * 32768-61000
+ */
+int sysctl_local_port_range[2] = { 1024, 4999 };
+int tcp_port_rover = (1024 - 1);
+
+static __inline__ int tcp_hashfn(__u32 laddr, __u16 lport,
+ __u32 faddr, __u16 fport)
+{
+ int h = ((laddr ^ lport) ^ (faddr ^ fport));
+ h ^= h>>16;
+ h ^= h>>8;
+ return h & (tcp_ehash_size - 1);
+}
+
+static __inline__ int tcp_sk_hashfn(struct sock *sk)
+{
+ __u32 laddr = sk->rcv_saddr;
+ __u16 lport = sk->num;
+ __u32 faddr = sk->daddr;
+ __u16 fport = sk->dport;
+
+ return tcp_hashfn(laddr, lport, faddr, fport);
+}
+
+/* Allocate and initialize a new TCP local port bind bucket.
+ * The bindhash mutex for snum's hash chain must be held here.
+ */
+struct tcp_bind_bucket *tcp_bucket_create(struct tcp_bind_hashbucket *head,
+ unsigned short snum)
+{
+ struct tcp_bind_bucket *tb;
+
+ tb = kmem_cache_alloc(tcp_bucket_cachep, SLAB_ATOMIC);
+ if(tb != NULL) {
+ tb->port = snum;
+ tb->fastreuse = 0;
+ tb->uid = (uid_t)-1;
+ tb->owners = NULL;
+ if((tb->next = head->chain) != NULL)
+ tb->next->pprev = &tb->next;
+ head->chain = tb;
+ tb->pprev = &head->chain;
+ }
+ return tb;
+}
+
+/* Caller must disable local BH processing. */
+static __inline__ void __tcp_inherit_port(struct sock *sk, struct sock *child)
+{
+ struct tcp_bind_hashbucket *head = &tcp_bhash[tcp_bhashfn(child->num)];
+ struct tcp_bind_bucket *tb;
+
+ spin_lock(&head->lock);
+ tb = (struct tcp_bind_bucket *)sk->prev;
+ if ((child->bind_next = tb->owners) != NULL)
+ tb->owners->bind_pprev = &child->bind_next;
+ tb->owners = child;
+ child->bind_pprev = &tb->owners;
+ child->prev = (struct sock *) tb;
+ spin_unlock(&head->lock);
+}
+
+inline void tcp_inherit_port(struct sock *sk, struct sock *child)
+{
+ local_bh_disable();
+ __tcp_inherit_port(sk, child);
+ local_bh_enable();
+}
+
+static inline void tcp_bind_hash(struct sock *sk, struct tcp_bind_bucket *tb, unsigned short snum)
+{
+ sk->num = snum;
+ if ((sk->bind_next = tb->owners) != NULL)
+ tb->owners->bind_pprev = &sk->bind_next;
+ tb->owners = sk;
+ sk->bind_pprev = &tb->owners;
+ sk->prev = (struct sock *) tb;
+}
+
+static inline int tcp_bind_conflict(struct sock *sk, struct tcp_bind_bucket *tb)
+{
+ struct sock *sk2 = tb->owners;
+ int sk_reuse, sk2_reuse;
+ int addr_type2;
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ uid_t sk_uid = sk->state != TCP_TIME_WAIT ? sock_i_uid_t(sk) : ((struct tcp_tw_bucket *)sk)->uid;
+#endif
+#if defined(CONFIG_IPV6_IM) && defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ int *sysctl_ipv6_bindv6only_restriction = inter_module_get(IM_IPV6_SYSCTL_BINDV6ONLY_RESTRICTION);
+#endif
+ int ret;
+
+ sk_reuse = 0;
+ if (sk->reuse)
+ sk_reuse |= 1;
+#ifdef SO_REUSEPORT
+ if (sk->reuseport)
+ sk_reuse |= 2;
+#endif
+#if 0
+ if (sk_reuse &&
+ MULTICAST(sk->rcv_saddr))
+ sk_reuse |= 4;
+#endif
+
+ for( ; sk2 != NULL; sk2 = sk2->bind_next) {
+#if 1 /* XXX: should be recoded like 2.4.21 */
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ uid_t sk2_uid;
+ int uid_ok;
+#endif
+ int both_specified = 0;
+
+ if (sk2 == sk ||
+ (sk2->bound_dev_if && sk->bound_dev_if &&
+ sk2->bound_dev_if != sk->bound_dev_if))
+ continue;
+#if 0
+ if (sk2->family != AF_INET6 && sk2->family != AF_INET)
+ continue;
+#endif
+
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ if (sk2->family == AF_INET6) {
+ struct in6_addr *sk2_rcv_saddr6 = sk2->state != TCP_TIME_WAIT ?
+ &sk2->net_pinfo.af_inet6.rcv_saddr :
+ &((struct tcp_tw_bucket*)sk2)->v6_rcv_saddr;
+ if (IN6_IS_ADDR_UNSPECIFIED(sk2_rcv_saddr6))
+ addr_type2 = IPV6_ADDR_ANY;
+ else if (IN6_IS_ADDR_V4MAPPED(sk2_rcv_saddr6))
+ addr_type2 = IPV6_ADDR_MAPPED;
+ else
+ addr_type2 = IPV6_ADDR_UNICAST; /*XXX*/
+ } else
+ addr_type2 = IPV6_ADDR_MAPPED;
+#else
+ addr_type2 = IPV6_ADDR_MAPPED;
+#endif
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ sk2_uid = sk2->state != TCP_TIME_WAIT ? sock_i_uid(sk2) : ((struct tcp_tw_bucket *)sk2)->uid;
+#endif
+ if ((addr_type2 != IPV6_ADDR_MAPPED ? addr_type2 != IPV6_ADDR_ANY : sk2->rcv_saddr) &&
+ sk->rcv_saddr) {
+ if (sk2->rcv_saddr != sk->rcv_saddr)
+ continue;
+ both_specified = 1;
+ }
+
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ uid_ok = sk2_uid == (uid_t) -1 || sk_uid == sk2_uid;
+#endif
+
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ if (addr_type2 != IPV6_ADDR_MAPPED && __ipv6_only_sock(sk2)) {
+#ifdef CONFIG_IPV6_RESTRICTED_DOUBLE_BIND
+#ifndef CONFIG_IPV6_IM
+ if (sysctl_ipv6_bindv6only_restriction == 0 || uid_ok)
+ continue;
+#else
+ if ((sysctl_ipv6_bindv6only_restriction && *sysctl_ipv6_bindv6only_restriction == 0) || uid_ok)
+ continue;
+#endif
+#else
+ continue;
+#endif
+ }
+#endif
+
+ sk2_reuse = 0;
+ if (sk2->reuse)
+ sk2_reuse |= 1;
+#ifdef SO_REUSEPORT
+ if (sk2->reuseport)
+ sk2_reuse |= 2;
+#endif
+#if 0
+ if (sk2_reuse &&
+ (addr_type2 != IPV6_ADDR_MAPPED ? (addr_type2 & IPV6_ADDR_MULTICAST) : MULTICAST(sk2->rcv_saddr)))
+ sk2_reuse |= 4;
+#endif
+
+ if (sk2_reuse & sk_reuse & 3) { /* NOT && */
+ ret = 1;
+#if 0
+ if (sk2_reuse & sk_reuse & 4)
+ continue;
+#endif
+#ifdef CONFIG_NET_RESTRICTED_REUSE
+ if (!uid_ok)
+ goto failed;
+#endif
+#ifdef SO_REUSEPORT
+ if (sk2_reuse & sk_reuse & 2)
+ continue;
+#endif
+ if (both_specified) {
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ struct in6_addr *sk2_daddr6 = sk2->state != TCP_TIME_WAIT ?
+ &sk2->net_pinfo.af_inet6.daddr :
+ &((struct tcp_tw_bucket*)sk2)->v6_daddr;
+#endif
+ int addr_type2d;
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ if (sk2->family == AF_INET6) {
+ if (IN6_IS_ADDR_UNSPECIFIED(sk2_daddr6))
+ addr_type2d = IPV6_ADDR_ANY;
+ else if (IN6_IS_ADDR_V4MAPPED(sk2_daddr6))
+ addr_type2d = IPV6_ADDR_MAPPED;
+ else
+ addr_type2d = IPV6_ADDR_UNICAST; /*XXX*/
+ } else
+ addr_type2d = IPV6_ADDR_MAPPED;
+#else
+ addr_type2d = IPV6_ADDR_MAPPED;
+#endif
+ if (addr_type2d != IPV6_ADDR_MAPPED ? addr_type2d != IPV6_ADDR_ANY : sk2->daddr)
+ continue;
+ } else {
+ if ((addr_type2 != IPV6_ADDR_MAPPED ? addr_type2 != IPV6_ADDR_ANY : sk2->rcv_saddr) ||
+ sk->rcv_saddr)
+ continue;
+#else /* XXX: should be recoded like 2.4.21 */
+ if (sk != sk2 &&
+ sk2->reuse <= 1 &&
+ !ipv6_only_sock(sk2) &&
+ (!sk->bound_dev_if ||
+ !sk2->bound_dev_if ||
+ sk->bound_dev_if == sk2->bound_dev_if)) {
+ if (!sk_reuse ||
+ !sk2->reuse ||
+ sk2->state == TCP_LISTEN) {
+ if (!sk2->rcv_saddr ||
+ !sk->rcv_saddr ||
+ (sk2->rcv_saddr == sk->rcv_saddr))
+ break;
+#endif /* XXX: should be recoded like 2.4.21 */
+ }
+ }
+ ret = 1;
+ goto failed;
+ }
+ /* If we found a conflict, fail. */
+ ret = sk2 != NULL;
+failed:
+#if defined(CONFIG_IPV6_IM) && defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ if (sysctl_ipv6_bindv6only_restriction)
+ inter_module_put(IM_IPV6_SYSCTL_BINDV6ONLY_RESTRICTION);
+#endif
+ return ret;
+}
+
+/* Obtain a reference to a local port for the given sock,
+ * if snum is zero it means select any available local port.
+ */
+static int tcp_v4_get_port(struct sock *sk, unsigned short snum)
+{
+ struct tcp_bind_hashbucket *head;
+ struct tcp_bind_bucket *tb;
+#if defined(CONFIG_NET_RESTRICTED_REUSE)
+ uid_t sk_uid = sk->state != TCP_TIME_WAIT ? sock_i_uid_t(sk) : ((struct tcp_tw_bucket *)sk)->uid;
+#endif
+ int ret;
+
+ local_bh_disable();
+ if (snum == 0) {
+ int low = sysctl_local_port_range[0];
+ int high = sysctl_local_port_range[1];
+ int remaining = (high - low) + 1;
+ int rover;
+
+ spin_lock(&tcp_portalloc_lock);
+ rover = tcp_port_rover;
+ do { rover++;
+ if ((rover < low) || (rover > high))
+ rover = low;
+ head = &tcp_bhash[tcp_bhashfn(rover)];
+ spin_lock(&head->lock);
+ for (tb = head->chain; tb; tb = tb->next)
+ if (tb->port == rover)
+ goto next;
+ break;
+ next:
+ spin_unlock(&head->lock);
+ } while (--remaining > 0);
+ tcp_port_rover = rover;
+ spin_unlock(&tcp_portalloc_lock);
+
+ /* Exhausted local port range during search? */
+ ret = 1;
+ if (remaining <= 0)
+ goto fail;
+
+ /* OK, here is the one we will use. HEAD is
+ * non-NULL and we hold it's mutex.
+ */
+ snum = rover;
+ tb = NULL;
+ } else {
+ head = &tcp_bhash[tcp_bhashfn(snum)];
+ spin_lock(&head->lock);
+ for (tb = head->chain; tb != NULL; tb = tb->next)
+ if (tb->port == snum)
+ break;
+ }
+ if (tb != NULL && tb->owners != NULL) {
+ ret = 1;
+ if (tb->fastreuse > 0 &&
+ (sk->reuse != 0
+#ifdef SO_REUSEPORT
+ || sk->reuseport != 0
+#endif
+ ) &&
+#if defined(CONFIG_NET_RESTRICTED_REUSE)
+ sk_uid == tb->uid &&
+#endif
+ sk->state != TCP_LISTEN) {
+ goto success;
+ } else {
+ ret = 1;
+ if (tcp_bind_conflict(sk, tb))
+ goto fail_unlock;
+ }
+ }
+ ret = 1;
+ if (tb == NULL &&
+ (tb = tcp_bucket_create(head, snum)) == NULL)
+ goto fail_unlock;
+ if (tb->owners == NULL) {
+ if ((sk->reuse
+#ifdef SO_REUSEPORT
+ || sk->reuseport
+#endif
+ ) && sk->state != TCP_LISTEN) {
+ tb->fastreuse = 1;
+#if defined(CONFIG_NET_RESTRICTED_REUSE)
+ tb->uid = sk_uid;
+#endif
+ } else
+ tb->fastreuse = 0;
+ } else if (tb->fastreuse &&
+ ((sk->reuse == 0
+#ifdef SO_REUSEPORT
+ && sk_reuseport == 0
+#endif
+ ) ||
+#if defined(CONFIG_NET_RESTRICTED_REUSE)
+ (sk_uid != tb->uid) ||
+#endif
+ (sk->state == TCP_LISTEN)))
+ tb->fastreuse = 0;
+success:
+ if (sk->prev == NULL)
+ tcp_bind_hash(sk, tb, snum);
+ BUG_TRAP(sk->prev == (struct sock *) tb);
+ ret = 0;
+
+fail_unlock:
+ spin_unlock(&head->lock);
+fail:
+ local_bh_enable();
+ return ret;
+}
+
+/* Get rid of any references to a local port held by the
+ * given sock.
+ */
+inline void __tcp_put_port(struct sock *sk)
+{
+ struct tcp_bind_hashbucket *head = &tcp_bhash[tcp_bhashfn(sk->num)];
+ struct tcp_bind_bucket *tb;
+
+ spin_lock(&head->lock);
+ tb = (struct tcp_bind_bucket *) sk->prev;
+ if (sk->bind_next)
+ sk->bind_next->bind_pprev = sk->bind_pprev;
+ *(sk->bind_pprev) = sk->bind_next;
+ sk->prev = NULL;
+ sk->num = 0;
+ if (tb->owners == NULL) {
+ if (tb->next)
+ tb->next->pprev = tb->pprev;
+ *(tb->pprev) = tb->next;
+ kmem_cache_free(tcp_bucket_cachep, tb);
+ }
+ spin_unlock(&head->lock);
+}
+
+void tcp_put_port(struct sock *sk)
+{
+ local_bh_disable();
+ __tcp_put_port(sk);
+ local_bh_enable();
+}
+
+/* This lock without WQ_FLAG_EXCLUSIVE is good on UP and it can be very bad on SMP.
+ * Look, when several writers sleep and reader wakes them up, all but one
+ * immediately hit write lock and grab all the cpus. Exclusive sleep solves
+ * this, _but_ remember, it adds useless work on UP machines (wake up each
+ * exclusive lock release). It should be ifdefed really.
+ */
+
+void tcp_listen_wlock(void)
+{
+ write_lock(&tcp_lhash_lock);
+
+ if (atomic_read(&tcp_lhash_users)) {
+ DECLARE_WAITQUEUE(wait, current);
+
+ add_wait_queue_exclusive(&tcp_lhash_wait, &wait);
+ for (;;) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ if (atomic_read(&tcp_lhash_users) == 0)
+ break;
+ write_unlock_bh(&tcp_lhash_lock);
+ schedule();
+ write_lock_bh(&tcp_lhash_lock);
+ }
+
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&tcp_lhash_wait, &wait);
+ }
+}
+
+static __inline__ void __tcp_v4_hash(struct sock *sk, const int listen_possible)
+{
+ struct sock **skp;
+ rwlock_t *lock;
+
+ BUG_TRAP(sk->pprev==NULL);
+ if(listen_possible && sk->state == TCP_LISTEN) {
+ skp = &tcp_listening_hash[tcp_sk_listen_hashfn(sk)];
+ lock = &tcp_lhash_lock;
+ tcp_listen_wlock();
+ } else {
+ skp = &tcp_ehash[(sk->hashent = tcp_sk_hashfn(sk))].chain;
+ lock = &tcp_ehash[sk->hashent].lock;
+ write_lock(lock);
+ }
+ if((sk->next = *skp) != NULL)
+ (*skp)->pprev = &sk->next;
+ *skp = sk;
+ sk->pprev = skp;
+ sock_prot_inc_use(sk->prot);
+ write_unlock(lock);
+ if (listen_possible && sk->state == TCP_LISTEN)
+ wake_up(&tcp_lhash_wait);
+}
+
+static void tcp_v4_hash(struct sock *sk)
+{
+ if (sk->state != TCP_CLOSE) {
+ local_bh_disable();
+ __tcp_v4_hash(sk, 1);
+ local_bh_enable();
+ }
+}
+
+void tcp_unhash(struct sock *sk)
+{
+ rwlock_t *lock;
+
+ if (!sk->pprev)
+ goto ende;
+
+ if (sk->state == TCP_LISTEN) {
+ local_bh_disable();
+ tcp_listen_wlock();
+ lock = &tcp_lhash_lock;
+ } else {
+ struct tcp_ehash_bucket *head = &tcp_ehash[sk->hashent];
+ lock = &head->lock;
+ write_lock_bh(&head->lock);
+ }
+
+ if(sk->pprev) {
+ if(sk->next)
+ sk->next->pprev = sk->pprev;
+ *sk->pprev = sk->next;
+ sk->pprev = NULL;
+ sock_prot_dec_use(sk->prot);
+ }
+ write_unlock_bh(lock);
+
+ ende:
+ if (sk->state == TCP_LISTEN)
+ wake_up(&tcp_lhash_wait);
+}
+
+/* Don't inline this cruft. Here are some nice properties to
+ * exploit here. The BSD API does not allow a listening TCP
+ * to specify the remote port nor the remote address for the
+ * connection. So always assume those are both wildcarded
+ * during the search since they can never be otherwise.
+ */
+static struct sock *__tcp_v4_lookup_listener(struct sock *sk, u32 daddr, unsigned short hnum, int dif)
+{
+ struct sock *result = NULL;
+ int score, hiscore;
+
+ hiscore=-1;
+ for(; sk; sk = sk->next) {
+ if(sk->num == hnum && !ipv6_only_sock(sk)) {
+ __u32 rcv_saddr = sk->rcv_saddr;
+
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ score = sk->family == PF_INET ? 1 : 0;
+#else
+ score = 1;
+#endif
+ if(rcv_saddr) {
+ if (rcv_saddr != daddr)
+ continue;
+ score+=2;
+ }
+ if (sk->bound_dev_if) {
+ if (sk->bound_dev_if != dif)
+ continue;
+ score+=2;
+ }
+ if (score == 5)
+ return sk;
+ if (score > hiscore) {
+ hiscore = score;
+ result = sk;
+ }
+ }
+ }
+ return result;
+}
+
+/* Optimize the common listener case. */
+inline struct sock *tcp_v4_lookup_listener(u32 daddr, unsigned short hnum, int dif)
+{
+ struct sock *sk;
+
+ read_lock(&tcp_lhash_lock);
+ sk = tcp_listening_hash[tcp_lhashfn(hnum)];
+ if (sk) {
+ if (sk->num == hnum &&
+ sk->next == NULL &&
+ (!sk->rcv_saddr || sk->rcv_saddr == daddr) &&
+ (sk->family == PF_INET || !ipv6_only_sock(sk)) &&
+ !sk->bound_dev_if)
+ goto sherry_cache;
+ sk = __tcp_v4_lookup_listener(sk, daddr, hnum, dif);
+ }
+ if (sk) {
+sherry_cache:
+ sock_hold(sk);
+ }
+ read_unlock(&tcp_lhash_lock);
+ return sk;
+}
+
+/* Sockets in TCP_CLOSE state are _always_ taken out of the hash, so
+ * we need not check it for TCP lookups anymore, thanks Alexey. -DaveM
+ *
+ * Local BH must be disabled here.
+ */
+
+static inline struct sock *__tcp_v4_lookup_established(u32 saddr, u16 sport,
+ u32 daddr, u16 hnum, int dif)
+{
+ struct tcp_ehash_bucket *head;
+ TCP_V4_ADDR_COOKIE(acookie, saddr, daddr)
+ __u32 ports = TCP_COMBINED_PORTS(sport, hnum);
+ struct sock *sk;
+ int hash;
+
+ /* Optimize here for direct hit, only listening connections can
+ * have wildcards anyways.
+ */
+ hash = tcp_hashfn(daddr, hnum, saddr, sport);
+ head = &tcp_ehash[hash];
+ read_lock(&head->lock);
+ for(sk = head->chain; sk; sk = sk->next) {
+ if(TCP_IPV4_MATCH(sk, acookie, saddr, daddr, ports, dif))
+ goto hit; /* You sunk my battleship! */
+ }
+
+ /* Must check for a TIME_WAIT'er before going to listener hash. */
+ for(sk = (head + tcp_ehash_size)->chain; sk; sk = sk->next)
+ if(TCP_IPV4_MATCH(sk, acookie, saddr, daddr, ports, dif))
+ goto hit;
+ read_unlock(&head->lock);
+
+ return NULL;
+
+hit:
+ sock_hold(sk);
+ read_unlock(&head->lock);
+ return sk;
+}
+
+static inline struct sock *__tcp_v4_lookup(u32 saddr, u16 sport,
+ u32 daddr, u16 hnum, int dif)
+{
+ struct sock *sk;
+
+ sk = __tcp_v4_lookup_established(saddr, sport, daddr, hnum, dif);
+
+ if (sk)
+ return sk;
+
+ return tcp_v4_lookup_listener(daddr, hnum, dif);
+}
+
+inline struct sock *tcp_v4_lookup(u32 saddr, u16 sport, u32 daddr, u16 dport, int dif)
+{
+ struct sock *sk;
+
+ local_bh_disable();
+ sk = __tcp_v4_lookup(saddr, sport, daddr, ntohs(dport), dif);
+ local_bh_enable();
+
+ return sk;
+}
+
+static inline __u32 tcp_v4_init_sequence(struct sock *sk, struct sk_buff *skb)
+{
+ return secure_tcp_sequence_number(skb->nh.iph->daddr,
+ skb->nh.iph->saddr,
+ skb->h.th->dest,
+ skb->h.th->source);
+}
+
+/* called with local bh disabled */
+static int __tcp_v4_check_established(struct sock *sk, __u16 lport,
+ struct tcp_tw_bucket **twp)
+{
+ u32 daddr = sk->rcv_saddr;
+ u32 saddr = sk->daddr;
+ int dif = sk->bound_dev_if;
+ TCP_V4_ADDR_COOKIE(acookie, saddr, daddr)
+ __u32 ports = TCP_COMBINED_PORTS(sk->dport, lport);
+ int hash = tcp_hashfn(daddr, lport, saddr, sk->dport);
+ struct tcp_ehash_bucket *head = &tcp_ehash[hash];
+ struct sock *sk2, **skp;
+ struct tcp_tw_bucket *tw;
+
+ write_lock(&head->lock);
+
+ /* Check TIME-WAIT sockets first. */
+ for(skp = &(head + tcp_ehash_size)->chain; (sk2=*skp) != NULL;
+ skp = &sk2->next) {
+ tw = (struct tcp_tw_bucket*)sk2;
+
+ if(TCP_IPV4_MATCH(sk2, acookie, saddr, daddr, ports, dif)) {
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ /* With PAWS, it is safe from the viewpoint
+ of data integrity. Even without PAWS it
+ is safe provided sequence spaces do not
+ overlap i.e. at data rates <= 80Mbit/sec.
+
+ Actually, the idea is close to VJ's one,
+ only timestamp cache is held not per host,
+ but per port pair and TW bucket is used
+ as state holder.
+
+ If TW bucket has been already destroyed we
+ fall back to VJ's scheme and use initial
+ timestamp retrieved from peer table.
+ */
+ if (tw->ts_recent_stamp &&
+ (!twp || (sysctl_tcp_tw_reuse &&
+ xtime.tv_sec - tw->ts_recent_stamp > 1))) {
+ if ((tp->write_seq = tw->snd_nxt+65535+2) == 0)
+ tp->write_seq = 1;
+ tp->ts_recent = tw->ts_recent;
+ tp->ts_recent_stamp = tw->ts_recent_stamp;
+ sock_hold(sk2);
+ skp = &head->chain;
+ goto unique;
+ } else
+ goto not_unique;
+ }
+ }
+ tw = NULL;
+
+ /* And established part... */
+ for(skp = &head->chain; (sk2=*skp)!=NULL; skp = &sk2->next) {
+ if(TCP_IPV4_MATCH(sk2, acookie, saddr, daddr, ports, dif))
+ goto not_unique;
+ }
+
+unique:
+ /* Must record num and sport now. Otherwise we will see
+ * in hash table socket with a funny identity. */
+ sk->num = lport;
+ sk->sport = htons(lport);
+ BUG_TRAP(sk->pprev==NULL);
+ if ((sk->next = *skp) != NULL)
+ (*skp)->pprev = &sk->next;
+
+ *skp = sk;
+ sk->pprev = skp;
+ sk->hashent = hash;
+ sock_prot_inc_use(sk->prot);
+ write_unlock(&head->lock);
+
+ if (twp) {
+ *twp = tw;
+ NET_INC_STATS_BH(TimeWaitRecycled);
+ } else if (tw) {
+ /* Silly. Should hash-dance instead... */
+ tcp_tw_deschedule(tw);
+ tcp_timewait_kill(tw);
+ NET_INC_STATS_BH(TimeWaitRecycled);
+
+ tcp_tw_put(tw);
+ }
+
+ return 0;
+
+not_unique:
+ write_unlock(&head->lock);
+ return -EADDRNOTAVAIL;
+}
+
+/*
+ * Bind a port for a connect operation and hash it.
+ */
+static int tcp_v4_hash_connect(struct sock *sk)
+{
+ unsigned short snum = sk->num;
+ struct tcp_bind_hashbucket *head;
+ struct tcp_bind_bucket *tb;
+
+ if (snum == 0) {
+ int rover;
+ int low = sysctl_local_port_range[0];
+ int high = sysctl_local_port_range[1];
+ int remaining = (high - low) + 1;
+ struct tcp_tw_bucket *tw = NULL;
+
+ local_bh_disable();
+
+ /* TODO. Actually it is not so bad idea to remove
+ * tcp_portalloc_lock before next submission to Linus.
+ * As soon as we touch this place at all it is time to think.
+ *
+ * Now it protects single _advisory_ variable tcp_port_rover,
+ * hence it is mostly useless.
+ * Code will work nicely if we just delete it, but
+ * I am afraid in contented case it will work not better or
+ * even worse: another cpu just will hit the same bucket
+ * and spin there.
+ * So some cpu salt could remove both contention and
+ * memory pingpong. Any ideas how to do this in a nice way?
+ */
+ spin_lock(&tcp_portalloc_lock);
+ rover = tcp_port_rover;
+
+ do {
+ rover++;
+ if ((rover < low) || (rover > high))
+ rover = low;
+ head = &tcp_bhash[tcp_bhashfn(rover)];
+ spin_lock(&head->lock);
+
+ /* Does not bother with rcv_saddr checks,
+ * because the established check is already
+ * unique enough.
+ */
+ for (tb = head->chain; tb; tb = tb->next) {
+ if (tb->port == rover) {
+ BUG_TRAP(tb->owners != NULL);
+ if (tb->fastreuse >= 0)
+ goto next_port;
+ if (!__tcp_v4_check_established(sk, rover, &tw))
+ goto ok;
+ goto next_port;
+ }
+ }
+
+ tb = tcp_bucket_create(head, rover);
+ if (!tb) {
+ spin_unlock(&head->lock);
+ break;
+ }
+ tb->fastreuse = -1;
+ goto ok;
+
+ next_port:
+ spin_unlock(&head->lock);
+ } while (--remaining > 0);
+ tcp_port_rover = rover;
+ spin_unlock(&tcp_portalloc_lock);
+
+ local_bh_enable();
+
+ return -EADDRNOTAVAIL;
+
+ ok:
+ /* All locks still held and bhs disabled */
+ tcp_port_rover = rover;
+ spin_unlock(&tcp_portalloc_lock);
+
+ tcp_bind_hash(sk, tb, rover);
+ if (!sk->pprev) {
+ sk->sport = htons(rover);
+ __tcp_v4_hash(sk, 0);
+ }
+ spin_unlock(&head->lock);
+
+ if (tw) {
+ tcp_tw_deschedule(tw);
+ tcp_timewait_kill(tw);
+ tcp_tw_put(tw);
+ }
+
+ local_bh_enable();
+ return 0;
+ }
+
+ head = &tcp_bhash[tcp_bhashfn(snum)];
+ tb = (struct tcp_bind_bucket *)sk->prev;
+ spin_lock_bh(&head->lock);
+ if (tb->owners == sk && sk->bind_next == NULL) {
+ __tcp_v4_hash(sk, 0);
+ spin_unlock_bh(&head->lock);
+ return 0;
+ } else {
+ int ret;
+ spin_unlock(&head->lock);
+ /* No definite answer... Walk to established hash table */
+ ret = __tcp_v4_check_established(sk, snum, NULL);
+ local_bh_enable();
+ return ret;
+ }
+}
+
+/* This will initiate an outgoing connection. */
+int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sockaddr_in *usin = (struct sockaddr_in *) uaddr;
+ struct rtable *rt;
+ u32 daddr, nexthop;
+ int tmp;
+ int err;
+
+ if (addr_len < sizeof(struct sockaddr_in))
+ return(-EINVAL);
+
+ if (usin->sin_family != AF_INET)
+ return(-EAFNOSUPPORT);
+
+ nexthop = daddr = usin->sin_addr.s_addr;
+ if (sk->protinfo.af_inet.opt && sk->protinfo.af_inet.opt->srr) {
+ if (daddr == 0)
+ return -EINVAL;
+ nexthop = sk->protinfo.af_inet.opt->faddr;
+ }
+
+ tmp = ip_route_connect(&rt, nexthop, sk->saddr,
+ RT_CONN_FLAGS(sk), sk->bound_dev_if);
+ if (tmp < 0)
+ return tmp;
+
+ if (rt->rt_flags&(RTCF_MULTICAST|RTCF_BROADCAST)) {
+ ip_rt_put(rt);
+ return -ENETUNREACH;
+ }
+
+ __sk_dst_set(sk, &rt->u.dst);
+ sk->route_caps = rt->u.dst.dev->features;
+
+ if (!sk->protinfo.af_inet.opt || !sk->protinfo.af_inet.opt->srr)
+ daddr = rt->rt_dst;
+
+ if (!sk->saddr)
+ sk->saddr = rt->rt_src;
+ sk->rcv_saddr = sk->saddr;
+
+ if (tp->ts_recent_stamp && sk->daddr != daddr) {
+ /* Reset inherited state */
+ tp->ts_recent = 0;
+ tp->ts_recent_stamp = 0;
+ tp->write_seq = 0;
+ }
+
+ if (sysctl_tcp_tw_recycle &&
+ !tp->ts_recent_stamp &&
+ rt->rt_dst == daddr) {
+ struct inet_peer *peer = rt_get_peer(rt);
+
+ /* VJ's idea. We save last timestamp seen from
+ * the destination in peer table, when entering state TIME-WAIT
+ * and initialize ts_recent from it, when trying new connection.
+ */
+
+ if (peer && peer->tcp_ts_stamp + TCP_PAWS_MSL >= xtime.tv_sec) {
+ tp->ts_recent_stamp = peer->tcp_ts_stamp;
+ tp->ts_recent = peer->tcp_ts;
+ }
+ }
+
+ sk->dport = usin->sin_port;
+ sk->daddr = daddr;
+
+ tp->ext_header_len = 0;
+ if (sk->protinfo.af_inet.opt)
+ tp->ext_header_len = sk->protinfo.af_inet.opt->optlen;
+
+ tp->mss_clamp = 536;
+
+ /* Socket identity is still unknown (sport may be zero).
+ * However we set state to SYN-SENT and not releasing socket
+ * lock select source port, enter ourselves into the hash tables and
+ * complete initalization after this.
+ */
+ tcp_set_state(sk, TCP_SYN_SENT);
+ err = tcp_v4_hash_connect(sk);
+ if (err)
+ goto failure;
+
+ if (!tp->write_seq)
+ tp->write_seq = secure_tcp_sequence_number(sk->saddr, sk->daddr,
+ sk->sport, usin->sin_port);
+
+ sk->protinfo.af_inet.id = tp->write_seq^jiffies;
+
+ err = tcp_connect(sk);
+ if (err)
+ goto failure;
+
+ return 0;
+
+failure:
+ tcp_set_state(sk, TCP_CLOSE);
+ __sk_dst_reset(sk);
+ sk->route_caps = 0;
+ sk->dport = 0;
+ return err;
+}
+
+static __inline__ int tcp_v4_iif(struct sk_buff *skb)
+{
+ return ((struct rtable*)skb->dst)->rt_iif;
+}
+
+static __inline__ u32 tcp_v4_synq_hash(u32 raddr, u16 rport, u32 rnd)
+{
+ return (jhash_2words(raddr, (u32) rport, rnd) & (TCP_SYNQ_HSIZE - 1));
+}
+
+static struct open_request *tcp_v4_search_req(struct tcp_opt *tp,
+ struct open_request ***prevp,
+ __u16 rport,
+ __u32 raddr, __u32 laddr)
+{
+ struct tcp_listen_opt *lopt = tp->listen_opt;
+ struct open_request *req, **prev;
+
+ for (prev = &lopt->syn_table[tcp_v4_synq_hash(raddr, rport, lopt->hash_rnd)];
+ (req = *prev) != NULL;
+ prev = &req->dl_next) {
+ if (req->rmt_port == rport &&
+ req->af.v4_req.rmt_addr == raddr &&
+ req->af.v4_req.loc_addr == laddr &&
+ TCP_INET_FAMILY(req->class->family)) {
+ BUG_TRAP(req->sk == NULL);
+ *prevp = prev;
+ return req;
+ }
+ }
+
+ return NULL;
+}
+
+static void tcp_v4_synq_add(struct sock *sk, struct open_request *req)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ struct tcp_listen_opt *lopt = tp->listen_opt;
+ u32 h = tcp_v4_synq_hash(req->af.v4_req.rmt_addr, req->rmt_port, lopt->hash_rnd);
+
+ req->expires = jiffies + TCP_TIMEOUT_INIT;
+ req->retrans = 0;
+ req->sk = NULL;
+ req->dl_next = lopt->syn_table[h];
+
+ write_lock(&tp->syn_wait_lock);
+ lopt->syn_table[h] = req;
+ write_unlock(&tp->syn_wait_lock);
+
+ tcp_synq_added(sk);
+}
+
+
+/*
+ * This routine does path mtu discovery as defined in RFC1191.
+ */
+static inline void do_pmtu_discovery(struct sock *sk, struct iphdr *ip, unsigned mtu)
+{
+ struct dst_entry *dst;
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+
+ /* We are not interested in TCP_LISTEN and open_requests (SYN-ACKs
+ * send out by Linux are always <576bytes so they should go through
+ * unfragmented).
+ */
+ if (sk->state == TCP_LISTEN)
+ return;
+
+ /* We don't check in the destentry if pmtu discovery is forbidden
+ * on this route. We just assume that no packet_to_big packets
+ * are send back when pmtu discovery is not active.
+ * There is a small race when the user changes this flag in the
+ * route, but I think that's acceptable.
+ */
+ if ((dst = __sk_dst_check(sk, 0)) == NULL)
+ return;
+
+ ip_rt_update_pmtu(dst, mtu);
+
+ /* Something is about to be wrong... Remember soft error
+ * for the case, if this connection will not able to recover.
+ */
+ if (mtu < dst->pmtu && ip_dont_fragment(sk, dst))
+ sk->err_soft = EMSGSIZE;
+
+ if (sk->protinfo.af_inet.pmtudisc != IP_PMTUDISC_DONT &&
+ tp->pmtu_cookie > dst->pmtu) {
+ tcp_sync_mss(sk, dst->pmtu);
+
+ /* Resend the TCP packet because it's
+ * clear that the old packet has been
+ * dropped. This is the new "fast" path mtu
+ * discovery.
+ */
+ tcp_simple_retransmit(sk);
+ } /* else let the usual retransmit timer handle it */
+}
+
+/*
+ * This routine is called by the ICMP module when it gets some
+ * sort of error condition. If err < 0 then the socket should
+ * be closed and the error returned to the user. If err > 0
+ * it's just the icmp type << 8 | icmp code. After adjustment
+ * header points to the first 8 bytes of the tcp header. We need
+ * to find the appropriate port.
+ *
+ * The locking strategy used here is very "optimistic". When
+ * someone else accesses the socket the ICMP is just dropped
+ * and for some paths there is no check at all.
+ * A more general error queue to queue errors for later handling
+ * is probably better.
+ *
+ */
+
+void tcp_v4_err(struct sk_buff *skb, u32 info)
+{
+ struct iphdr *iph = (struct iphdr*)skb->data;
+ struct tcphdr *th = (struct tcphdr*)(skb->data+(iph->ihl<<2));
+ struct tcp_opt *tp;
+ int type = skb->h.icmph->type;
+ int code = skb->h.icmph->code;
+ struct sock *sk;
+ __u32 seq;
+ int err;
+
+ if (skb->len < (iph->ihl << 2) + 8) {
+ ICMP_INC_STATS_BH(IcmpInErrors);
+ return;
+ }
+
+ sk = tcp_v4_lookup(iph->daddr, th->dest, iph->saddr, th->source, tcp_v4_iif(skb));
+ if (sk == NULL) {
+ ICMP_INC_STATS_BH(IcmpInErrors);
+ return;
+ }
+ if (sk->state == TCP_TIME_WAIT) {
+ tcp_tw_put((struct tcp_tw_bucket*)sk);
+ return;
+ }
+
+ bh_lock_sock(sk);
+ /* If too many ICMPs get dropped on busy
+ * servers this needs to be solved differently.
+ */
+ if (sk->lock.users != 0)
+ NET_INC_STATS_BH(LockDroppedIcmps);
+
+ if (sk->state == TCP_CLOSE)
+ goto out;
+
+ tp = &sk->tp_pinfo.af_tcp;
+ seq = ntohl(th->seq);
+ if (sk->state != TCP_LISTEN && !between(seq, tp->snd_una, tp->snd_nxt)) {
+ NET_INC_STATS(OutOfWindowIcmps);
+ goto out;
+ }
+
+ switch (type) {
+ case ICMP_SOURCE_QUENCH:
+ /* Just silently ignore these. */
+ goto out;
+ case ICMP_PARAMETERPROB:
+ err = EPROTO;
+ break;
+ case ICMP_DEST_UNREACH:
+ if (code > NR_ICMP_UNREACH)
+ goto out;
+
+ if (code == ICMP_FRAG_NEEDED) { /* PMTU discovery (RFC1191) */
+ if (sk->lock.users == 0)
+ do_pmtu_discovery(sk, iph, info);
+ goto out;
+ }
+
+ err = icmp_err_convert[code].errno;
+ break;
+ case ICMP_TIME_EXCEEDED:
+ err = EHOSTUNREACH;
+ break;
+ default:
+ goto out;
+ }
+
+ switch (sk->state) {
+ struct open_request *req, **prev;
+ case TCP_LISTEN:
+ if (sk->lock.users != 0)
+ goto out;
+
+ req = tcp_v4_search_req(tp, &prev,
+ th->dest,
+ iph->daddr, iph->saddr);
+ if (!req)
+ goto out;
+
+ /* ICMPs are not backlogged, hence we cannot get
+ an established socket here.
+ */
+ BUG_TRAP(req->sk == NULL);
+
+ if (seq != req->snt_isn) {
+ NET_INC_STATS_BH(OutOfWindowIcmps);
+ goto out;
+ }
+
+ /*
+ * Still in SYN_RECV, just remove it silently.
+ * There is no good way to pass the error to the newly
+ * created socket, and POSIX does not want network
+ * errors returned from accept().
+ */
+ tcp_synq_drop(sk, req, prev);
+ goto out;
+
+ case TCP_SYN_SENT:
+ case TCP_SYN_RECV: /* Cannot happen.
+ It can f.e. if SYNs crossed.
+ */
+ if (sk->lock.users == 0) {
+ TCP_INC_STATS_BH(TcpAttemptFails);
+ sk->err = err;
+
+ sk->error_report(sk);
+
+ tcp_done(sk);
+ } else {
+ sk->err_soft = err;
+ }
+ goto out;
+ }
+
+ /* If we've already connected we will keep trying
+ * until we time out, or the user gives up.
+ *
+ * rfc1122 4.2.3.9 allows to consider as hard errors
+ * only PROTO_UNREACH and PORT_UNREACH (well, FRAG_FAILED too,
+ * but it is obsoleted by pmtu discovery).
+ *
+ * Note, that in modern internet, where routing is unreliable
+ * and in each dark corner broken firewalls sit, sending random
+ * errors ordered by their masters even this two messages finally lose
+ * their original sense (even Linux sends invalid PORT_UNREACHs)
+ *
+ * Now we are in compliance with RFCs.
+ * --ANK (980905)
+ */
+
+ if (sk->lock.users == 0 && sk->protinfo.af_inet.recverr) {
+ sk->err = err;
+ sk->error_report(sk);
+ } else { /* Only an error on timeout */
+ sk->err_soft = err;
+ }
+
+out:
+ bh_unlock_sock(sk);
+ sock_put(sk);
+}
+
+/* This routine computes an IPv4 TCP checksum. */
+void tcp_v4_send_check(struct sock *sk, struct tcphdr *th, int len,
+ struct sk_buff *skb)
+{
+ if (skb->ip_summed == CHECKSUM_HW) {
+ th->check = ~tcp_v4_check(th, len, sk->saddr, sk->daddr, 0);
+ skb->csum = offsetof(struct tcphdr, check);
+ } else {
+ th->check = tcp_v4_check(th, len, sk->saddr, sk->daddr,
+ csum_partial((char *)th, th->doff<<2, skb->csum));
+ }
+}
+
+/*
+ * This routine will send an RST to the other tcp.
+ *
+ * Someone asks: why I NEVER use socket parameters (TOS, TTL etc.)
+ * for reset.
+ * Answer: if a packet caused RST, it is not for a socket
+ * existing in our system, if it is matched to a socket,
+ * it is just duplicate segment or bug in other side's TCP.
+ * So that we build reply only basing on parameters
+ * arrived with segment.
+ * Exception: precedence violation. We do not implement it in any case.
+ */
+
+static void tcp_v4_send_reset(struct sk_buff *skb)
+{
+ struct tcphdr *th = skb->h.th;
+ struct tcphdr rth;
+ struct ip_reply_arg arg;
+
+ /* Never send a reset in response to a reset. */
+ if (th->rst)
+ return;
+
+ if (((struct rtable*)skb->dst)->rt_type != RTN_LOCAL)
+ return;
+
+ /* Swap the send and the receive. */
+ memset(&rth, 0, sizeof(struct tcphdr));
+ rth.dest = th->source;
+ rth.source = th->dest;
+ rth.doff = sizeof(struct tcphdr)/4;
+ rth.rst = 1;
+
+ if (th->ack) {
+ rth.seq = th->ack_seq;
+ } else {
+ rth.ack = 1;
+ rth.ack_seq = htonl(ntohl(th->seq) + th->syn + th->fin
+ + skb->len - (th->doff<<2));
+ }
+
+ memset(&arg, 0, sizeof arg);
+ arg.iov[0].iov_base = (unsigned char *)&rth;
+ arg.iov[0].iov_len = sizeof rth;
+ arg.csum = csum_tcpudp_nofold(skb->nh.iph->daddr,
+ skb->nh.iph->saddr, /*XXX*/
+ sizeof(struct tcphdr),
+ IPPROTO_TCP,
+ 0);
+ arg.n_iov = 1;
+ arg.csumoffset = offsetof(struct tcphdr, check) / 2;
+
+ tcp_socket->sk->protinfo.af_inet.ttl = sysctl_ip_default_ttl;
+ ip_send_reply(tcp_socket->sk, skb, &arg, sizeof rth);
+
+ TCP_INC_STATS_BH(TcpOutSegs);
+ TCP_INC_STATS_BH(TcpOutRsts);
+}
+
+/* The code following below sending ACKs in SYN-RECV and TIME-WAIT states
+ outside socket context is ugly, certainly. What can I do?
+ */
+
+static void tcp_v4_send_ack(struct sk_buff *skb, u32 seq, u32 ack, u32 win, u32 ts)
+{
+ struct tcphdr *th = skb->h.th;
+ struct {
+ struct tcphdr th;
+ u32 tsopt[3];
+ } rep;
+ struct ip_reply_arg arg;
+
+ memset(&rep.th, 0, sizeof(struct tcphdr));
+ memset(&arg, 0, sizeof arg);
+
+ arg.iov[0].iov_base = (unsigned char *)&rep;
+ arg.iov[0].iov_len = sizeof(rep.th);
+ arg.n_iov = 1;
+ if (ts) {
+ rep.tsopt[0] = htonl((TCPOPT_NOP << 24) |
+ (TCPOPT_NOP << 16) |
+ (TCPOPT_TIMESTAMP << 8) |
+ TCPOLEN_TIMESTAMP);
+ rep.tsopt[1] = htonl(tcp_time_stamp);
+ rep.tsopt[2] = htonl(ts);
+ arg.iov[0].iov_len = sizeof(rep);
+ }
+
+ /* Swap the send and the receive. */
+ rep.th.dest = th->source;
+ rep.th.source = th->dest;
+ rep.th.doff = arg.iov[0].iov_len/4;
+ rep.th.seq = htonl(seq);
+ rep.th.ack_seq = htonl(ack);
+ rep.th.ack = 1;
+ rep.th.window = htons(win);
+
+ arg.csum = csum_tcpudp_nofold(skb->nh.iph->daddr,
+ skb->nh.iph->saddr, /*XXX*/
+ arg.iov[0].iov_len,
+ IPPROTO_TCP,
+ 0);
+ arg.csumoffset = offsetof(struct tcphdr, check) / 2;
+
+ ip_send_reply(tcp_socket->sk, skb, &arg, arg.iov[0].iov_len);
+
+ TCP_INC_STATS_BH(TcpOutSegs);
+}
+
+static void tcp_v4_timewait_ack(struct sock *sk, struct sk_buff *skb)
+{
+ struct tcp_tw_bucket *tw = (struct tcp_tw_bucket *)sk;
+
+ tcp_v4_send_ack(skb, tw->snd_nxt, tw->rcv_nxt,
+ tw->rcv_wnd>>tw->rcv_wscale, tw->ts_recent);
+
+ tcp_tw_put(tw);
+}
+
+static void tcp_v4_or_send_ack(struct sk_buff *skb, struct open_request *req)
+{
+ tcp_v4_send_ack(skb, req->snt_isn+1, req->rcv_isn+1, req->rcv_wnd,
+ req->ts_recent);
+}
+
+static struct dst_entry* tcp_v4_route_req(struct sock *sk, struct open_request *req)
+{
+ struct rtable *rt;
+ struct ip_options *opt;
+
+ opt = req->af.v4_req.opt;
+ if(ip_route_output(&rt, ((opt && opt->srr) ?
+ opt->faddr :
+ req->af.v4_req.rmt_addr),
+ req->af.v4_req.loc_addr,
+ RT_CONN_FLAGS(sk), sk->bound_dev_if)) {
+ IP_INC_STATS_BH(IpOutNoRoutes);
+ return NULL;
+ }
+ if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway) {
+ ip_rt_put(rt);
+ IP_INC_STATS_BH(IpOutNoRoutes);
+ return NULL;
+ }
+ return &rt->u.dst;
+}
+
+/*
+ * Send a SYN-ACK after having received an ACK.
+ * This still operates on a open_request only, not on a big
+ * socket.
+ */
+static int tcp_v4_send_synack(struct sock *sk, struct open_request *req,
+ struct dst_entry *dst)
+{
+ int err = -1;
+ struct sk_buff * skb;
+
+ /* First, grab a route. */
+ if (dst == NULL &&
+ (dst = tcp_v4_route_req(sk, req)) == NULL)
+ goto out;
+
+ skb = tcp_make_synack(sk, dst, req);
+
+ if (skb) {
+ struct tcphdr *th = skb->h.th;
+
+ th->check = tcp_v4_check(th, skb->len,
+ req->af.v4_req.loc_addr, req->af.v4_req.rmt_addr,
+ csum_partial((char *)th, skb->len, skb->csum));
+
+ err = ip_build_and_send_pkt(skb, sk, req->af.v4_req.loc_addr,
+ req->af.v4_req.rmt_addr, req->af.v4_req.opt);
+ if (err == NET_XMIT_CN)
+ err = 0;
+ }
+
+out:
+ dst_release(dst);
+ return err;
+}
+
+/*
+ * IPv4 open_request destructor.
+ */
+static void tcp_v4_or_free(struct open_request *req)
+{
+ if (req->af.v4_req.opt)
+ kfree(req->af.v4_req.opt);
+}
+
+static inline void syn_flood_warning(struct sk_buff *skb)
+{
+ static unsigned long warntime;
+
+ if (jiffies - warntime > HZ*60) {
+ warntime = jiffies;
+ printk(KERN_INFO
+ "possible SYN flooding on port %d. Sending cookies.\n",
+ ntohs(skb->h.th->dest));
+ }
+}
+
+/*
+ * Save and compile IPv4 options into the open_request if needed.
+ */
+static inline struct ip_options *
+tcp_v4_save_options(struct sock *sk, struct sk_buff *skb)
+{
+ struct ip_options *opt = &(IPCB(skb)->opt);
+ struct ip_options *dopt = NULL;
+
+ if (opt && opt->optlen) {
+ int opt_size = optlength(opt);
+ dopt = kmalloc(opt_size, GFP_ATOMIC);
+ if (dopt) {
+ if (ip_options_echo(dopt, skb)) {
+ kfree(dopt);
+ dopt = NULL;
+ }
+ }
+ }
+ return dopt;
+}
+
+/*
+ * Maximum number of SYN_RECV sockets in queue per LISTEN socket.
+ * One SYN_RECV socket costs about 80bytes on a 32bit machine.
+ * It would be better to replace it with a global counter for all sockets
+ * but then some measure against one socket starving all other sockets
+ * would be needed.
+ *
+ * It was 128 by default. Experiments with real servers show, that
+ * it is absolutely not enough even at 100conn/sec. 256 cures most
+ * of problems. This value is adjusted to 128 for very small machines
+ * (<=32Mb of memory) and to 1024 on normal or better ones (>=256Mb).
+ * Further increasing requires to change hash table size.
+ */
+int sysctl_max_syn_backlog = 256;
+
+struct or_calltable or_ipv4 = {
+ PF_INET,
+ tcp_v4_send_synack,
+ tcp_v4_or_send_ack,
+ tcp_v4_or_free,
+ tcp_v4_send_reset
+};
+
+int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
+{
+ struct tcp_opt tp;
+ struct open_request *req;
+ __u32 saddr = skb->nh.iph->saddr;
+ __u32 daddr = skb->nh.iph->daddr;
+ __u32 isn = TCP_SKB_CB(skb)->when;
+ struct dst_entry *dst = NULL;
+#ifdef CONFIG_SYN_COOKIES
+ int want_cookie = 0;
+#else
+#define want_cookie 0 /* Argh, why doesn't gcc optimize this :( */
+#endif
+
+ /* Never answer to SYNs send to broadcast or multicast */
+ if (((struct rtable *)skb->dst)->rt_flags &
+ (RTCF_BROADCAST|RTCF_MULTICAST))
+ goto drop;
+
+ /* TW buckets are converted to open requests without
+ * limitations, they conserve resources and peer is
+ * evidently real one.
+ */
+ if (tcp_synq_is_full(sk) && !isn) {
+#ifdef CONFIG_SYN_COOKIES
+ if (sysctl_tcp_syncookies) {
+ want_cookie = 1;
+ } else
+#endif
+ goto drop;
+ }
+
+ /* Accept backlog is full. If we have already queued enough
+ * of warm entries in syn queue, drop request. It is better than
+ * clogging syn queue with openreqs with exponentially increasing
+ * timeout.
+ */
+ if (tcp_acceptq_is_full(sk) && tcp_synq_young(sk) > 1)
+ goto drop;
+
+ req = tcp_openreq_alloc();
+ if (req == NULL)
+ goto drop;
+
+ tcp_clear_options(&tp);
+ tp.mss_clamp = 536;
+ tp.user_mss = sk->tp_pinfo.af_tcp.user_mss;
+
+ tcp_parse_options(skb, &tp, 0);
+
+ if (want_cookie) {
+ tcp_clear_options(&tp);
+ tp.saw_tstamp = 0;
+ }
+
+ if (tp.saw_tstamp && tp.rcv_tsval == 0) {
+ /* Some OSes (unknown ones, but I see them on web server, which
+ * contains information interesting only for windows'
+ * users) do not send their stamp in SYN. It is easy case.
+ * We simply do not advertise TS support.
+ */
+ tp.saw_tstamp = 0;
+ tp.tstamp_ok = 0;
+ }
+ tp.tstamp_ok = tp.saw_tstamp;
+
+ tcp_openreq_init(req, &tp, skb);
+
+ req->af.v4_req.loc_addr = daddr;
+ req->af.v4_req.rmt_addr = saddr;
+ req->af.v4_req.opt = tcp_v4_save_options(sk, skb);
+ req->class = &or_ipv4;
+ if (!want_cookie)
+ TCP_ECN_create_request(req, skb->h.th);
+
+ if (want_cookie) {
+#ifdef CONFIG_SYN_COOKIES
+ syn_flood_warning(skb);
+#endif
+ isn = cookie_v4_init_sequence(sk, skb, &req->mss);
+ } else if (isn == 0) {
+ struct inet_peer *peer = NULL;
+
+ /* VJ's idea. We save last timestamp seen
+ * from the destination in peer table, when entering
+ * state TIME-WAIT, and check against it before
+ * accepting new connection request.
+ *
+ * If "isn" is not zero, this request hit alive
+ * timewait bucket, so that all the necessary checks
+ * are made in the function processing timewait state.
+ */
+ if (tp.saw_tstamp &&
+ sysctl_tcp_tw_recycle &&
+ (dst = tcp_v4_route_req(sk, req)) != NULL &&
+ (peer = rt_get_peer((struct rtable*)dst)) != NULL &&
+ peer->v4daddr == saddr) {
+ if (xtime.tv_sec < peer->tcp_ts_stamp + TCP_PAWS_MSL &&
+ (s32)(peer->tcp_ts - req->ts_recent) > TCP_PAWS_WINDOW) {
+ NET_INC_STATS_BH(PAWSPassiveRejected);
+ dst_release(dst);
+ goto drop_and_free;
+ }
+ }
+ /* Kill the following clause, if you dislike this way. */
+ else if (!sysctl_tcp_syncookies &&
+ (sysctl_max_syn_backlog - tcp_synq_len(sk)
+ < (sysctl_max_syn_backlog>>2)) &&
+ (!peer || !peer->tcp_ts_stamp) &&
+ (!dst || !dst->rtt)) {
+ /* Without syncookies last quarter of
+ * backlog is filled with destinations, proven to be alive.
+ * It means that we continue to communicate
+ * to destinations, already remembered
+ * to the moment of synflood.
+ */
+ NETDEBUG(if (net_ratelimit()) \
+ printk(KERN_DEBUG "TCP: drop open request from %u.%u.%u.%u/%u\n", \
+ NIPQUAD(saddr), ntohs(skb->h.th->source)));
+ dst_release(dst);
+ goto drop_and_free;
+ }
+
+ isn = tcp_v4_init_sequence(sk, skb);
+ }
+ req->snt_isn = isn;
+
+ if (tcp_v4_send_synack(sk, req, dst))
+ goto drop_and_free;
+
+ if (want_cookie) {
+ tcp_openreq_free(req);
+ } else {
+ tcp_v4_synq_add(sk, req);
+ }
+ return 0;
+
+drop_and_free:
+ tcp_openreq_free(req);
+drop:
+ TCP_INC_STATS_BH(TcpAttemptFails);
+ return 0;
+}
+
+
+/*
+ * The three way handshake has completed - we got a valid synack -
+ * now create the new socket.
+ */
+struct sock * tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
+ struct open_request *req,
+ struct dst_entry *dst)
+{
+ struct tcp_opt *newtp;
+ struct sock *newsk;
+
+ if (tcp_acceptq_is_full(sk))
+ goto exit_overflow;
+
+ if (dst == NULL &&
+ (dst = tcp_v4_route_req(sk, req)) == NULL)
+ goto exit;
+
+ newsk = tcp_create_openreq_child(sk, req, skb);
+ if (!newsk)
+ goto exit;
+
+ newsk->dst_cache = dst;
+ newsk->route_caps = dst->dev->features;
+
+ newtp = &(newsk->tp_pinfo.af_tcp);
+ newsk->daddr = req->af.v4_req.rmt_addr;
+ newsk->saddr = req->af.v4_req.loc_addr;
+ newsk->rcv_saddr = req->af.v4_req.loc_addr;
+ newsk->protinfo.af_inet.opt = req->af.v4_req.opt;
+ req->af.v4_req.opt = NULL;
+ newsk->protinfo.af_inet.mc_index = tcp_v4_iif(skb);
+ newsk->protinfo.af_inet.mc_ttl = skb->nh.iph->ttl;
+ newtp->ext_header_len = 0;
+ if (newsk->protinfo.af_inet.opt)
+ newtp->ext_header_len = newsk->protinfo.af_inet.opt->optlen;
+ newsk->protinfo.af_inet.id = newtp->write_seq^jiffies;
+
+ tcp_sync_mss(newsk, dst->pmtu);
+ newtp->advmss = dst->advmss;
+ tcp_initialize_rcv_mss(newsk);
+
+ __tcp_v4_hash(newsk, 0);
+ __tcp_inherit_port(sk, newsk);
+
+ return newsk;
+
+exit_overflow:
+ NET_INC_STATS_BH(ListenOverflows);
+exit:
+ NET_INC_STATS_BH(ListenDrops);
+ dst_release(dst);
+ return NULL;
+}
+
+static struct sock *tcp_v4_hnd_req(struct sock *sk,struct sk_buff *skb)
+{
+ struct open_request *req, **prev;
+ struct tcphdr *th = skb->h.th;
+ struct iphdr *iph = skb->nh.iph;
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sock *nsk;
+
+ /* Find possible connection requests. */
+ req = tcp_v4_search_req(tp, &prev,
+ th->source,
+ iph->saddr, iph->daddr);
+ if (req)
+ return tcp_check_req(sk, skb, req, prev);
+
+ nsk = __tcp_v4_lookup_established(skb->nh.iph->saddr,
+ th->source,
+ skb->nh.iph->daddr,
+ ntohs(th->dest),
+ tcp_v4_iif(skb));
+
+ if (nsk) {
+ if (nsk->state != TCP_TIME_WAIT) {
+ bh_lock_sock(nsk);
+ return nsk;
+ }
+ tcp_tw_put((struct tcp_tw_bucket*)nsk);
+ return NULL;
+ }
+
+#ifdef CONFIG_SYN_COOKIES
+ if (!th->rst && !th->syn && th->ack)
+ sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));
+#endif
+ return sk;
+}
+
+static int tcp_v4_checksum_init(struct sk_buff *skb)
+{
+ if (skb->ip_summed == CHECKSUM_HW) {
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ if (!tcp_v4_check(skb->h.th,skb->len,skb->nh.iph->saddr,
+ skb->nh.iph->daddr,skb->csum))
+ return 0;
+
+ NETDEBUG(if (net_ratelimit()) printk(KERN_DEBUG "hw tcp v4 csum failed\n"));
+ skb->ip_summed = CHECKSUM_NONE;
+ }
+ if (skb->len <= 76) {
+ if (tcp_v4_check(skb->h.th,skb->len,skb->nh.iph->saddr,
+ skb->nh.iph->daddr,
+ skb_checksum(skb, 0, skb->len, 0)))
+ return -1;
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ } else {
+ skb->csum = ~tcp_v4_check(skb->h.th,skb->len,skb->nh.iph->saddr,
+ skb->nh.iph->daddr,0);
+ }
+ return 0;
+}
+
+
+/* The socket must have it's spinlock held when we get
+ * here.
+ *
+ * We have a potential double-lock case here, so even when
+ * doing backlog processing we use the BH locking scheme.
+ * This is because we cannot sleep with the original spinlock
+ * held.
+ */
+int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
+{
+ IP_INC_STATS_BH(IpInDelivers);
+
+ if (sk->state == TCP_ESTABLISHED) { /* Fast path */
+ TCP_CHECK_TIMER(sk);
+ if (tcp_rcv_established(sk, skb, skb->h.th, skb->len))
+ goto reset;
+ TCP_CHECK_TIMER(sk);
+ return 0;
+ }
+
+ if (skb->len < (skb->h.th->doff<<2) || tcp_checksum_complete(skb))
+ goto csum_err;
+
+ if (sk->state == TCP_LISTEN) {
+ struct sock *nsk = tcp_v4_hnd_req(sk, skb);
+ if (!nsk)
+ goto discard;
+
+ if (nsk != sk) {
+ if (tcp_child_process(sk, nsk, skb))
+ goto reset;
+ return 0;
+ }
+ }
+
+ TCP_CHECK_TIMER(sk);
+ if (tcp_rcv_state_process(sk, skb, skb->h.th, skb->len))
+ goto reset;
+ TCP_CHECK_TIMER(sk);
+ return 0;
+
+reset:
+ tcp_v4_send_reset(skb);
+discard:
+ kfree_skb(skb);
+ /* Be careful here. If this function gets more complicated and
+ * gcc suffers from register pressure on the x86, sk (in %ebx)
+ * might be destroyed here. This current version compiles correctly,
+ * but you have been warned.
+ */
+ return 0;
+
+csum_err:
+ TCP_INC_STATS_BH(TcpInErrs);
+ goto discard;
+}
+
+/*
+ * From tcp_input.c
+ */
+
+int tcp_v4_rcv(struct sk_buff *skb)
+{
+ struct tcphdr *th;
+ struct sock *sk;
+ int ret;
+
+ if (skb->pkt_type!=PACKET_HOST)
+ goto discard_it;
+
+ /* Count it even if it's bad */
+ TCP_INC_STATS_BH(TcpInSegs);
+
+ if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
+ goto discard_it;
+
+ th = skb->h.th;
+
+ if (th->doff < sizeof(struct tcphdr)/4)
+ goto bad_packet;
+ if (!pskb_may_pull(skb, th->doff*4))
+ goto discard_it;
+
+ /* An explanation is required here, I think.
+ * Packet length and doff are validated by header prediction,
+ * provided case of th->doff==0 is elimineted.
+ * So, we defer the checks. */
+ if ((skb->ip_summed != CHECKSUM_UNNECESSARY &&
+ tcp_v4_checksum_init(skb) < 0))
+ goto bad_packet;
+
+ th = skb->h.th;
+ TCP_SKB_CB(skb)->seq = ntohl(th->seq);
+ TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
+ skb->len - th->doff*4);
+ TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
+ TCP_SKB_CB(skb)->when = 0;
+ TCP_SKB_CB(skb)->flags = skb->nh.iph->tos;
+ TCP_SKB_CB(skb)->sacked = 0;
+
+ sk = __tcp_v4_lookup(skb->nh.iph->saddr, th->source,
+ skb->nh.iph->daddr, ntohs(th->dest), tcp_v4_iif(skb));
+
+ if (!sk)
+ goto no_tcp_socket;
+
+process:
+ if(!ipsec_sk_policy(sk,skb))
+ goto discard_and_relse;
+
+ if (sk->state == TCP_TIME_WAIT)
+ goto do_time_wait;
+
+ if (sk_filter(sk, skb, 0))
+ goto discard_and_relse;
+
+ skb->dev = NULL;
+
+ bh_lock_sock(sk);
+ ret = 0;
+ if (!sk->lock.users) {
+ if (!tcp_prequeue(sk, skb))
+ ret = tcp_v4_do_rcv(sk, skb);
+ } else
+ sk_add_backlog(sk, skb);
+ bh_unlock_sock(sk);
+
+ sock_put(sk);
+
+ return ret;
+
+no_tcp_socket:
+ if (skb->len < (th->doff<<2) || tcp_checksum_complete(skb)) {
+bad_packet:
+ TCP_INC_STATS_BH(TcpInErrs);
+ } else {
+ tcp_v4_send_reset(skb);
+ }
+
+discard_it:
+ /* Discard frame. */
+ kfree_skb(skb);
+ return 0;
+
+discard_and_relse:
+ sock_put(sk);
+ goto discard_it;
+
+do_time_wait:
+ if (skb->len < (th->doff<<2) || tcp_checksum_complete(skb)) {
+ TCP_INC_STATS_BH(TcpInErrs);
+ tcp_tw_put((struct tcp_tw_bucket *) sk);
+ goto discard_it;
+ }
+ switch(tcp_timewait_state_process((struct tcp_tw_bucket *)sk,
+ skb, th, skb->len)) {
+ case TCP_TW_SYN:
+ {
+ struct sock *sk2;
+
+ sk2 = tcp_v4_lookup_listener(skb->nh.iph->daddr, ntohs(th->dest), tcp_v4_iif(skb));
+ if (sk2 != NULL) {
+ tcp_tw_deschedule((struct tcp_tw_bucket *)sk);
+ tcp_timewait_kill((struct tcp_tw_bucket *)sk);
+ tcp_tw_put((struct tcp_tw_bucket *)sk);
+ sk = sk2;
+ goto process;
+ }
+ /* Fall through to ACK */
+ }
+ case TCP_TW_ACK:
+ tcp_v4_timewait_ack(sk, skb);
+ break;
+ case TCP_TW_RST:
+ goto no_tcp_socket;
+ case TCP_TW_SUCCESS:;
+ }
+ goto discard_it;
+}
+
+/* With per-bucket locks this operation is not-atomic, so that
+ * this version is not worse.
+ */
+static void __tcp_v4_rehash(struct sock *sk)
+{
+ sk->prot->unhash(sk);
+ sk->prot->hash(sk);
+}
+
+static int tcp_v4_reselect_saddr(struct sock *sk)
+{
+ int err;
+ struct rtable *rt;
+ __u32 old_saddr = sk->saddr;
+ __u32 new_saddr;
+ __u32 daddr = sk->daddr;
+
+ if(sk->protinfo.af_inet.opt && sk->protinfo.af_inet.opt->srr)
+ daddr = sk->protinfo.af_inet.opt->faddr;
+
+ /* Query new route. */
+ err = ip_route_connect(&rt, daddr, 0,
+ RT_TOS(sk->protinfo.af_inet.tos)|sk->localroute,
+ sk->bound_dev_if);
+ if (err)
+ return err;
+
+ __sk_dst_set(sk, &rt->u.dst);
+ sk->route_caps = rt->u.dst.dev->features;
+
+ new_saddr = rt->rt_src;
+
+ if (new_saddr == old_saddr)
+ return 0;
+
+ if (sysctl_ip_dynaddr > 1) {
+ printk(KERN_INFO "tcp_v4_rebuild_header(): shifting sk->saddr "
+ "from %d.%d.%d.%d to %d.%d.%d.%d\n",
+ NIPQUAD(old_saddr),
+ NIPQUAD(new_saddr));
+ }
+
+ sk->saddr = new_saddr;
+ sk->rcv_saddr = new_saddr;
+
+ /* XXX The only one ugly spot where we need to
+ * XXX really change the sockets identity after
+ * XXX it has entered the hashes. -DaveM
+ *
+ * Besides that, it does not check for connection
+ * uniqueness. Wait for troubles.
+ */
+ __tcp_v4_rehash(sk);
+ return 0;
+}
+
+int tcp_v4_rebuild_header(struct sock *sk)
+{
+ struct rtable *rt = (struct rtable *)__sk_dst_check(sk, 0);
+ u32 daddr;
+ int err;
+
+ /* Route is OK, nothing to do. */
+ if (rt != NULL)
+ return 0;
+
+ /* Reroute. */
+ daddr = sk->daddr;
+ if(sk->protinfo.af_inet.opt && sk->protinfo.af_inet.opt->srr)
+ daddr = sk->protinfo.af_inet.opt->faddr;
+
+ err = ip_route_output(&rt, daddr, sk->saddr,
+ RT_CONN_FLAGS(sk), sk->bound_dev_if);
+ if (!err) {
+ __sk_dst_set(sk, &rt->u.dst);
+ sk->route_caps = rt->u.dst.dev->features;
+ return 0;
+ }
+
+ /* Routing failed... */
+ sk->route_caps = 0;
+
+ if (!sysctl_ip_dynaddr ||
+ sk->state != TCP_SYN_SENT ||
+ (sk->userlocks & SOCK_BINDADDR_LOCK) ||
+ (err = tcp_v4_reselect_saddr(sk)) != 0)
+ sk->err_soft=-err;
+
+ return err;
+}
+
+static void v4_addr2sockaddr(struct sock *sk, struct sockaddr * uaddr)
+{
+ struct sockaddr_in *sin = (struct sockaddr_in *) uaddr;
+
+ sin->sin_family = AF_INET;
+ sin->sin_addr.s_addr = sk->daddr;
+ sin->sin_port = sk->dport;
+ memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
+}
+
+/* VJ's idea. Save last timestamp seen from this destination
+ * and hold it at least for normal timewait interval to use for duplicate
+ * segment detection in subsequent connections, before they enter synchronized
+ * state.
+ */
+
+int tcp_v4_remember_stamp(struct sock *sk)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ struct rtable *rt = (struct rtable*)__sk_dst_get(sk);
+ struct inet_peer *peer = NULL;
+ int release_it = 0;
+
+ if (rt == NULL || rt->rt_dst != sk->daddr) {
+ peer = inet_getpeer(sk->daddr, 1);
+ release_it = 1;
+ } else {
+ if (rt->peer == NULL)
+ rt_bind_peer(rt, 1);
+ peer = rt->peer;
+ }
+
+ if (peer) {
+ if ((s32)(peer->tcp_ts - tp->ts_recent) <= 0 ||
+ (peer->tcp_ts_stamp + TCP_PAWS_MSL < xtime.tv_sec &&
+ peer->tcp_ts_stamp <= tp->ts_recent_stamp)) {
+ peer->tcp_ts_stamp = tp->ts_recent_stamp;
+ peer->tcp_ts = tp->ts_recent;
+ }
+ if (release_it)
+ inet_putpeer(peer);
+ return 1;
+ }
+
+ return 0;
+}
+
+int tcp_v4_tw_remember_stamp(struct tcp_tw_bucket *tw)
+{
+ struct inet_peer *peer = NULL;
+
+ peer = inet_getpeer(tw->daddr, 1);
+
+ if (peer) {
+ if ((s32)(peer->tcp_ts - tw->ts_recent) <= 0 ||
+ (peer->tcp_ts_stamp + TCP_PAWS_MSL < xtime.tv_sec &&
+ peer->tcp_ts_stamp <= tw->ts_recent_stamp)) {
+ peer->tcp_ts_stamp = tw->ts_recent_stamp;
+ peer->tcp_ts = tw->ts_recent;
+ }
+ inet_putpeer(peer);
+ return 1;
+ }
+
+ return 0;
+}
+
+struct tcp_func ipv4_specific = {
+ ip_queue_xmit,
+ tcp_v4_send_check,
+ tcp_v4_rebuild_header,
+ tcp_v4_conn_request,
+ tcp_v4_syn_recv_sock,
+ tcp_v4_remember_stamp,
+ sizeof(struct iphdr),
+
+ ip_setsockopt,
+ ip_getsockopt,
+ v4_addr2sockaddr,
+ sizeof(struct sockaddr_in)
+};
+
+/* NOTE: A lot of things set to zero explicitly by call to
+ * sk_alloc() so need not be done here.
+ */
+static int tcp_v4_init_sock(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ skb_queue_head_init(&tp->out_of_order_queue);
+ tcp_init_xmit_timers(sk);
+ tcp_prequeue_init(tp);
+
+ tp->rto = TCP_TIMEOUT_INIT;
+ tp->mdev = TCP_TIMEOUT_INIT;
+
+ /* So many TCP implementations out there (incorrectly) count the
+ * initial SYN frame in their delayed-ACK and congestion control
+ * algorithms that we must have the following bandaid to talk
+ * efficiently to them. -DaveM
+ */
+ tp->snd_cwnd = 2;
+
+ /* See draft-stevens-tcpca-spec-01 for discussion of the
+ * initialization of these values.
+ */
+ tp->snd_ssthresh = 0x7fffffff; /* Infinity */
+ tp->snd_cwnd_clamp = ~0;
+ tp->mss_cache = 536;
+
+ tp->reordering = sysctl_tcp_reordering;
+
+ sk->state = TCP_CLOSE;
+
+ sk->write_space = tcp_write_space;
+ sk->use_write_queue = 1;
+
+ sk->tp_pinfo.af_tcp.af_specific = &ipv4_specific;
+
+ sk->sndbuf = sysctl_tcp_wmem[1];
+ sk->rcvbuf = sysctl_tcp_rmem[1];
+
+ atomic_inc(&tcp_sockets_allocated);
+
+ return 0;
+}
+
+static int tcp_v4_destroy_sock(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ tcp_clear_xmit_timers(sk);
+
+ /* Cleanup up the write buffer. */
+ tcp_writequeue_purge(sk);
+
+ /* Cleans up our, hopefully empty, out_of_order_queue. */
+ __skb_queue_purge(&tp->out_of_order_queue);
+
+ /* Clean prequeue, it must be empty really */
+ __skb_queue_purge(&tp->ucopy.prequeue);
+
+ /* Clean up a referenced TCP bind bucket. */
+ if(sk->prev != NULL)
+ tcp_put_port(sk);
+
+ /* If sendmsg cached page exists, toss it. */
+ if (tp->sndmsg_page != NULL)
+ __free_page(tp->sndmsg_page);
+
+ atomic_dec(&tcp_sockets_allocated);
+
+ return 0;
+}
+
+/* Proc filesystem TCP sock list dumping. */
+static void get_openreq(struct sock *sk, struct open_request *req, char *tmpbuf, int i, int uid)
+{
+ int ttd = req->expires - jiffies;
+
+ sprintf(tmpbuf, "%4d: %08X:%04X %08X:%04X"
+ " %02X %08X:%08X %02X:%08X %08X %5d %8d %u %d %p",
+ i,
+ req->af.v4_req.loc_addr,
+ ntohs(sk->sport),
+ req->af.v4_req.rmt_addr,
+ ntohs(req->rmt_port),
+ TCP_SYN_RECV,
+ 0,0, /* could print option size, but that is af dependent. */
+ 1, /* timers active (only the expire timer) */
+ ttd,
+ req->retrans,
+ uid,
+ 0, /* non standard timer */
+ 0, /* open_requests have no inode */
+ atomic_read(&sk->refcnt),
+ req
+ );
+}
+
+static void get_tcp_sock(struct sock *sp, char *tmpbuf, int i)
+{
+ unsigned int dest, src;
+ __u16 destp, srcp;
+ int timer_active;
+ unsigned long timer_expires;
+ struct tcp_opt *tp = &sp->tp_pinfo.af_tcp;
+
+ dest = sp->daddr;
+ src = sp->rcv_saddr;
+ destp = ntohs(sp->dport);
+ srcp = ntohs(sp->sport);
+ if (tp->pending == TCP_TIME_RETRANS) {
+ timer_active = 1;
+ timer_expires = tp->timeout;
+ } else if (tp->pending == TCP_TIME_PROBE0) {
+ timer_active = 4;
+ timer_expires = tp->timeout;
+ } else if (timer_pending(&sp->timer)) {
+ timer_active = 2;
+ timer_expires = sp->timer.expires;
+ } else {
+ timer_active = 0;
+ timer_expires = jiffies;
+ }
+
+ sprintf(tmpbuf, "%4d: %08X:%04X %08X:%04X"
+ " %02X %08X:%08X %02X:%08lX %08X %5d %8d %lu %d %p %u %u %u %u %d",
+ i, src, srcp, dest, destp, sp->state,
+ tp->write_seq-tp->snd_una, tp->rcv_nxt-tp->copied_seq,
+ timer_active, timer_expires-jiffies,
+ tp->retransmits,
+ sock_i_uid(sp),
+ tp->probes_out,
+ sock_i_ino(sp),
+ atomic_read(&sp->refcnt), sp,
+ tp->rto, tp->ack.ato, (tp->ack.quick<<1)|tp->ack.pingpong,
+ tp->snd_cwnd, tp->snd_ssthresh>=0xFFFF?-1:tp->snd_ssthresh
+ );
+}
+
+static void get_timewait_sock(struct tcp_tw_bucket *tw, char *tmpbuf, int i)
+{
+ unsigned int dest, src;
+ __u16 destp, srcp;
+ int ttd = tw->ttd - jiffies;
+
+ if (ttd < 0)
+ ttd = 0;
+
+ dest = tw->daddr;
+ src = tw->rcv_saddr;
+ destp = ntohs(tw->dport);
+ srcp = ntohs(tw->sport);
+
+ sprintf(tmpbuf, "%4d: %08X:%04X %08X:%04X"
+ " %02X %08X:%08X %02X:%08X %08X %5d %8d %d %d %p",
+ i, src, srcp, dest, destp, tw->substate, 0, 0,
+ 3, ttd, 0, 0, 0, 0,
+ atomic_read(&tw->refcnt), tw);
+}
+
+#define TMPSZ 150
+
+int tcp_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int len = 0, num = 0, i;
+ off_t begin, pos = 0;
+ char tmpbuf[TMPSZ+1];
+
+ if (offset < TMPSZ)
+ len += sprintf(buffer, "%-*s\n", TMPSZ-1,
+ " sl local_address rem_address st tx_queue "
+ "rx_queue tr tm->when retrnsmt uid timeout inode");
+
+ pos = TMPSZ;
+
+ /* First, walk listening socket table. */
+ tcp_listen_lock();
+ for(i = 0; i < TCP_LHTABLE_SIZE; i++) {
+ struct sock *sk;
+ struct tcp_listen_opt *lopt;
+ int k;
+
+ for (sk = tcp_listening_hash[i]; sk; sk = sk->next, num++) {
+ struct open_request *req;
+ int uid;
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ if (!TCP_INET_FAMILY(sk->family))
+ goto skip_listen;
+
+ pos += TMPSZ;
+ if (pos >= offset) {
+ get_tcp_sock(sk, tmpbuf, num);
+ len += sprintf(buffer+len, "%-*s\n", TMPSZ-1, tmpbuf);
+ if (pos >= offset + length) {
+ tcp_listen_unlock();
+ goto out_no_bh;
+ }
+ }
+
+skip_listen:
+ uid = sock_i_uid(sk);
+ read_lock_bh(&tp->syn_wait_lock);
+ lopt = tp->listen_opt;
+ if (lopt && lopt->qlen != 0) {
+ for (k=0; k<TCP_SYNQ_HSIZE; k++) {
+ for (req = lopt->syn_table[k]; req; req = req->dl_next, num++) {
+ if (!TCP_INET_FAMILY(req->class->family))
+ continue;
+
+ pos += TMPSZ;
+ if (pos <= offset)
+ continue;
+ get_openreq(sk, req, tmpbuf, num, uid);
+ len += sprintf(buffer+len, "%-*s\n", TMPSZ-1, tmpbuf);
+ if (pos >= offset + length) {
+ read_unlock_bh(&tp->syn_wait_lock);
+ tcp_listen_unlock();
+ goto out_no_bh;
+ }
+ }
+ }
+ }
+ read_unlock_bh(&tp->syn_wait_lock);
+
+ /* Completed requests are in normal socket hash table */
+ }
+ }
+ tcp_listen_unlock();
+
+ local_bh_disable();
+
+ /* Next, walk established hash chain. */
+ for (i = 0; i < tcp_ehash_size; i++) {
+ struct tcp_ehash_bucket *head = &tcp_ehash[i];
+ struct sock *sk;
+ struct tcp_tw_bucket *tw;
+
+ read_lock(&head->lock);
+ for(sk = head->chain; sk; sk = sk->next, num++) {
+ if (!TCP_INET_FAMILY(sk->family))
+ continue;
+ pos += TMPSZ;
+ if (pos <= offset)
+ continue;
+ get_tcp_sock(sk, tmpbuf, num);
+ len += sprintf(buffer+len, "%-*s\n", TMPSZ-1, tmpbuf);
+ if (pos >= offset + length) {
+ read_unlock(&head->lock);
+ goto out;
+ }
+ }
+ for (tw = (struct tcp_tw_bucket *)tcp_ehash[i+tcp_ehash_size].chain;
+ tw != NULL;
+ tw = (struct tcp_tw_bucket *)tw->next, num++) {
+ if (!TCP_INET_FAMILY(tw->family))
+ continue;
+ pos += TMPSZ;
+ if (pos <= offset)
+ continue;
+ get_timewait_sock(tw, tmpbuf, num);
+ len += sprintf(buffer+len, "%-*s\n", TMPSZ-1, tmpbuf);
+ if (pos >= offset + length) {
+ read_unlock(&head->lock);
+ goto out;
+ }
+ }
+ read_unlock(&head->lock);
+ }
+
+out:
+ local_bh_enable();
+out_no_bh:
+
+ begin = len - (pos - offset);
+ *start = buffer + begin;
+ len -= begin;
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+struct proto tcp_prot = {
+ name: "TCP",
+ close: tcp_close,
+ connect: tcp_v4_connect,
+ disconnect: tcp_disconnect,
+ accept: tcp_accept,
+ ioctl: tcp_ioctl,
+ init: tcp_v4_init_sock,
+ destroy: tcp_v4_destroy_sock,
+ shutdown: tcp_shutdown,
+ setsockopt: tcp_setsockopt,
+ getsockopt: tcp_getsockopt,
+ sendmsg: tcp_sendmsg,
+ recvmsg: tcp_recvmsg,
+ backlog_rcv: tcp_v4_do_rcv,
+ hash: tcp_v4_hash,
+ unhash: tcp_unhash,
+ get_port: tcp_v4_get_port,
+};
+
+
+
+void __init tcp_v4_init(struct net_proto_family *ops)
+{
+ int err;
+
+ tcp_inode.i_mode = S_IFSOCK;
+ tcp_inode.i_sock = 1;
+ tcp_inode.i_uid = 0;
+ tcp_inode.i_gid = 0;
+ init_waitqueue_head(&tcp_inode.i_wait);
+ init_waitqueue_head(&tcp_inode.u.socket_i.wait);
+
+ tcp_socket->inode = &tcp_inode;
+ tcp_socket->state = SS_UNCONNECTED;
+ tcp_socket->type=SOCK_RAW;
+
+ if ((err=ops->create(tcp_socket, IPPROTO_TCP))<0)
+ panic("Failed to create the TCP control socket.\n");
+ tcp_socket->sk->allocation=GFP_ATOMIC;
+ tcp_socket->sk->protinfo.af_inet.ttl = MAXTTL;
+
+ /* Unhash it so that IP input processing does not even
+ * see it, we do not wish this socket to see incoming
+ * packets.
+ */
+ tcp_socket->sk->prot->unhash(tcp_socket->sk);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/tcp_minisocks.c b/uClinux-2.4.31-uc0/net/ipv4/tcp_minisocks.c
new file mode 100644
index 0000000..9aa7ab9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/tcp_minisocks.c
@@ -0,0 +1,1023 @@
+/* $USAGI: tcp_minisocks.c,v 1.12 2003/08/08 13:46:37 yoshfuji Exp $ */
+
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * Implementation of the Transmission Control Protocol(TCP).
+ *
+ * Version: $Id: tcp_minisocks.c,v 1.14.2.1 2002/03/05 04:30:08 davem Exp $
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Mark Evans, <evansmp@uhura.aston.ac.uk>
+ * Corey Minyard <wf-rch!minyard@relay.EU.net>
+ * Florian La Roche, <flla@stud.uni-sb.de>
+ * Charles Hedrick, <hedrick@klinzhai.rutgers.edu>
+ * Linus Torvalds, <torvalds@cs.helsinki.fi>
+ * Alan Cox, <gw4pts@gw4pts.ampr.org>
+ * Matthew Dillon, <dillon@apollo.west.oic.com>
+ * Arnt Gulbrandsen, <agulbra@nvg.unit.no>
+ * Jorge Cwik, <jorge@laser.satlink.net>
+ */
+
+#include <linux/config.h>
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+#include <net/tcp.h>
+#include <net/inet_common.h>
+
+#ifdef CONFIG_SYSCTL
+#define SYNC_INIT 0 /* let the user enable it */
+#else
+#define SYNC_INIT 1
+#endif
+
+int sysctl_tcp_tw_recycle = 0;
+int sysctl_tcp_max_tw_buckets = NR_FILE*2;
+
+int sysctl_tcp_syncookies = SYNC_INIT;
+int sysctl_tcp_abort_on_overflow = 0;
+
+static __inline__ int tcp_in_window(u32 seq, u32 end_seq, u32 s_win, u32 e_win)
+{
+ if (seq == s_win)
+ return 1;
+ if (after(end_seq, s_win) && before(seq, e_win))
+ return 1;
+ return (seq == e_win && seq == end_seq);
+}
+
+/* New-style handling of TIME_WAIT sockets. */
+
+int tcp_tw_count = 0;
+
+
+/* Must be called with locally disabled BHs. */
+void tcp_timewait_kill(struct tcp_tw_bucket *tw)
+{
+ struct tcp_ehash_bucket *ehead;
+ struct tcp_bind_hashbucket *bhead;
+ struct tcp_bind_bucket *tb;
+
+ /* Unlink from established hashes. */
+ ehead = &tcp_ehash[tw->hashent];
+ write_lock(&ehead->lock);
+ if (!tw->pprev) {
+ write_unlock(&ehead->lock);
+ return;
+ }
+ if(tw->next)
+ tw->next->pprev = tw->pprev;
+ *(tw->pprev) = tw->next;
+ tw->pprev = NULL;
+ write_unlock(&ehead->lock);
+
+ /* Disassociate with bind bucket. */
+ bhead = &tcp_bhash[tcp_bhashfn(tw->num)];
+ spin_lock(&bhead->lock);
+ tb = tw->tb;
+ if(tw->bind_next)
+ tw->bind_next->bind_pprev = tw->bind_pprev;
+ *(tw->bind_pprev) = tw->bind_next;
+ tw->tb = NULL;
+ if (tb->owners == NULL) {
+ if (tb->next)
+ tb->next->pprev = tb->pprev;
+ *(tb->pprev) = tb->next;
+ kmem_cache_free(tcp_bucket_cachep, tb);
+ }
+ spin_unlock(&bhead->lock);
+
+#ifdef INET_REFCNT_DEBUG
+ if (atomic_read(&tw->refcnt) != 1) {
+ printk(KERN_DEBUG "tw_bucket %p refcnt=%d\n", tw, atomic_read(&tw->refcnt));
+ }
+#endif
+ tcp_tw_put(tw);
+}
+
+/*
+ * * Main purpose of TIME-WAIT state is to close connection gracefully,
+ * when one of ends sits in LAST-ACK or CLOSING retransmitting FIN
+ * (and, probably, tail of data) and one or more our ACKs are lost.
+ * * What is TIME-WAIT timeout? It is associated with maximal packet
+ * lifetime in the internet, which results in wrong conclusion, that
+ * it is set to catch "old duplicate segments" wandering out of their path.
+ * It is not quite correct. This timeout is calculated so that it exceeds
+ * maximal retransmission timeout enough to allow to lose one (or more)
+ * segments sent by peer and our ACKs. This time may be calculated from RTO.
+ * * When TIME-WAIT socket receives RST, it means that another end
+ * finally closed and we are allowed to kill TIME-WAIT too.
+ * * Second purpose of TIME-WAIT is catching old duplicate segments.
+ * Well, certainly it is pure paranoia, but if we load TIME-WAIT
+ * with this semantics, we MUST NOT kill TIME-WAIT state with RSTs.
+ * * If we invented some more clever way to catch duplicates
+ * (f.e. based on PAWS), we could truncate TIME-WAIT to several RTOs.
+ *
+ * The algorithm below is based on FORMAL INTERPRETATION of RFCs.
+ * When you compare it to RFCs, please, read section SEGMENT ARRIVES
+ * from the very beginning.
+ *
+ * NOTE. With recycling (and later with fin-wait-2) TW bucket
+ * is _not_ stateless. It means, that strictly speaking we must
+ * spinlock it. I do not want! Well, probability of misbehaviour
+ * is ridiculously low and, seems, we could use some mb() tricks
+ * to avoid misread sequence numbers, states etc. --ANK
+ */
+enum tcp_tw_status
+tcp_timewait_state_process(struct tcp_tw_bucket *tw, struct sk_buff *skb,
+ struct tcphdr *th, unsigned len)
+{
+ struct tcp_opt tp;
+ int paws_reject = 0;
+
+ tp.saw_tstamp = 0;
+ if (th->doff > (sizeof(struct tcphdr)>>2) && tw->ts_recent_stamp) {
+ tcp_parse_options(skb, &tp, 0);
+
+ if (tp.saw_tstamp) {
+ tp.ts_recent = tw->ts_recent;
+ tp.ts_recent_stamp = tw->ts_recent_stamp;
+ paws_reject = tcp_paws_check(&tp, th->rst);
+ }
+ }
+
+ if (tw->substate == TCP_FIN_WAIT2) {
+ /* Just repeat all the checks of tcp_rcv_state_process() */
+
+ /* Out of window, send ACK */
+ if (paws_reject ||
+ !tcp_in_window(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq,
+ tw->rcv_nxt, tw->rcv_nxt + tw->rcv_wnd))
+ return TCP_TW_ACK;
+
+ if (th->rst)
+ goto kill;
+
+ if (th->syn && !before(TCP_SKB_CB(skb)->seq, tw->rcv_nxt))
+ goto kill_with_rst;
+
+ /* Dup ACK? */
+ if (!after(TCP_SKB_CB(skb)->end_seq, tw->rcv_nxt) ||
+ TCP_SKB_CB(skb)->end_seq == TCP_SKB_CB(skb)->seq) {
+ tcp_tw_put(tw);
+ return TCP_TW_SUCCESS;
+ }
+
+ /* New data or FIN. If new data arrive after half-duplex close,
+ * reset.
+ */
+ if (!th->fin || TCP_SKB_CB(skb)->end_seq != tw->rcv_nxt+1) {
+kill_with_rst:
+ tcp_tw_deschedule(tw);
+ tcp_timewait_kill(tw);
+ tcp_tw_put(tw);
+ return TCP_TW_RST;
+ }
+
+ /* FIN arrived, enter true time-wait state. */
+ tw->substate = TCP_TIME_WAIT;
+ tw->rcv_nxt = TCP_SKB_CB(skb)->end_seq;
+ if (tp.saw_tstamp) {
+ tw->ts_recent_stamp = xtime.tv_sec;
+ tw->ts_recent = tp.rcv_tsval;
+ }
+
+ /* I am shamed, but failed to make it more elegant.
+ * Yes, it is direct reference to IP, which is impossible
+ * to generalize to IPv6. Taking into account that IPv6
+ * do not undertsnad recycling in any case, it not
+ * a big problem in practice. --ANK */
+ if (tw->family == AF_INET &&
+ sysctl_tcp_tw_recycle && tw->ts_recent_stamp &&
+ tcp_v4_tw_remember_stamp(tw))
+ tcp_tw_schedule(tw, tw->timeout);
+ else
+ tcp_tw_schedule(tw, TCP_TIMEWAIT_LEN);
+ return TCP_TW_ACK;
+ }
+
+ /*
+ * Now real TIME-WAIT state.
+ *
+ * RFC 1122:
+ * "When a connection is [...] on TIME-WAIT state [...]
+ * [a TCP] MAY accept a new SYN from the remote TCP to
+ * reopen the connection directly, if it:
+ *
+ * (1) assigns its initial sequence number for the new
+ * connection to be larger than the largest sequence
+ * number it used on the previous connection incarnation,
+ * and
+ *
+ * (2) returns to TIME-WAIT state if the SYN turns out
+ * to be an old duplicate".
+ */
+
+ if (!paws_reject &&
+ (TCP_SKB_CB(skb)->seq == tw->rcv_nxt &&
+ (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq || th->rst))) {
+ /* In window segment, it may be only reset or bare ack. */
+
+ if (th->rst) {
+ /* This is TIME_WAIT assasination, in two flavors.
+ * Oh well... nobody has a sufficient solution to this
+ * protocol bug yet.
+ */
+ if (sysctl_tcp_rfc1337 == 0) {
+kill:
+ tcp_tw_deschedule(tw);
+ tcp_timewait_kill(tw);
+ tcp_tw_put(tw);
+ return TCP_TW_SUCCESS;
+ }
+ }
+ tcp_tw_schedule(tw, TCP_TIMEWAIT_LEN);
+
+ if (tp.saw_tstamp) {
+ tw->ts_recent = tp.rcv_tsval;
+ tw->ts_recent_stamp = xtime.tv_sec;
+ }
+
+ tcp_tw_put(tw);
+ return TCP_TW_SUCCESS;
+ }
+
+ /* Out of window segment.
+
+ All the segments are ACKed immediately.
+
+ The only exception is new SYN. We accept it, if it is
+ not old duplicate and we are not in danger to be killed
+ by delayed old duplicates. RFC check is that it has
+ newer sequence number works at rates <40Mbit/sec.
+ However, if paws works, it is reliable AND even more,
+ we even may relax silly seq space cutoff.
+
+ RED-PEN: we violate main RFC requirement, if this SYN will appear
+ old duplicate (i.e. we receive RST in reply to SYN-ACK),
+ we must return socket to time-wait state. It is not good,
+ but not fatal yet.
+ */
+
+ if (th->syn && !th->rst && !th->ack && !paws_reject &&
+ (after(TCP_SKB_CB(skb)->seq, tw->rcv_nxt) ||
+ (tp.saw_tstamp && (s32)(tw->ts_recent - tp.rcv_tsval) < 0))) {
+ u32 isn = tw->snd_nxt+65535+2;
+ if (isn == 0)
+ isn++;
+ TCP_SKB_CB(skb)->when = isn;
+ return TCP_TW_SYN;
+ }
+
+ if (paws_reject)
+ NET_INC_STATS_BH(PAWSEstabRejected);
+
+ if(!th->rst) {
+ /* In this case we must reset the TIMEWAIT timer.
+ *
+ * If it is ACKless SYN it may be both old duplicate
+ * and new good SYN with random sequence number <rcv_nxt.
+ * Do not reschedule in the last case.
+ */
+ if (paws_reject || th->ack)
+ tcp_tw_schedule(tw, TCP_TIMEWAIT_LEN);
+
+ /* Send ACK. Note, we do not put the bucket,
+ * it will be released by caller.
+ */
+ return TCP_TW_ACK;
+ }
+ tcp_tw_put(tw);
+ return TCP_TW_SUCCESS;
+}
+
+/* Enter the time wait state. This is called with locally disabled BH.
+ * Essentially we whip up a timewait bucket, copy the
+ * relevant info into it from the SK, and mess with hash chains
+ * and list linkage.
+ */
+static void __tcp_tw_hashdance(struct sock *sk, struct tcp_tw_bucket *tw)
+{
+ struct tcp_ehash_bucket *ehead = &tcp_ehash[sk->hashent];
+ struct tcp_bind_hashbucket *bhead;
+ struct sock **head, *sktw;
+
+ /* Step 1: Put TW into bind hash. Original socket stays there too.
+ Note, that any socket with sk->num!=0 MUST be bound in binding
+ cache, even if it is closed.
+ */
+ bhead = &tcp_bhash[tcp_bhashfn(sk->num)];
+ spin_lock(&bhead->lock);
+ tw->tb = (struct tcp_bind_bucket *)sk->prev;
+ BUG_TRAP(sk->prev!=NULL);
+ if ((tw->bind_next = tw->tb->owners) != NULL)
+ tw->tb->owners->bind_pprev = &tw->bind_next;
+ tw->tb->owners = (struct sock*)tw;
+ tw->bind_pprev = &tw->tb->owners;
+ spin_unlock(&bhead->lock);
+
+ write_lock(&ehead->lock);
+
+ /* Step 2: Remove SK from established hash. */
+ if (sk->pprev) {
+ if(sk->next)
+ sk->next->pprev = sk->pprev;
+ *sk->pprev = sk->next;
+ sk->pprev = NULL;
+ sock_prot_dec_use(sk->prot);
+ }
+
+ /* Step 3: Hash TW into TIMEWAIT half of established hash table. */
+ head = &(ehead + tcp_ehash_size)->chain;
+ sktw = (struct sock *)tw;
+ if((sktw->next = *head) != NULL)
+ (*head)->pprev = &sktw->next;
+ *head = sktw;
+ sktw->pprev = head;
+ atomic_inc(&tw->refcnt);
+
+ write_unlock(&ehead->lock);
+}
+
+/*
+ * Move a socket to time-wait or dead fin-wait-2 state.
+ */
+void tcp_time_wait(struct sock *sk, int state, int timeo)
+{
+ struct tcp_tw_bucket *tw = NULL;
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int recycle_ok = 0;
+
+ if (sysctl_tcp_tw_recycle && tp->ts_recent_stamp)
+ recycle_ok = tp->af_specific->remember_stamp(sk);
+
+ if (tcp_tw_count < sysctl_tcp_max_tw_buckets)
+ tw = kmem_cache_alloc(tcp_timewait_cachep, SLAB_ATOMIC);
+
+ if(tw != NULL) {
+ int rto = (tp->rto<<2) - (tp->rto>>1);
+
+ /* Give us an identity. */
+ tw->daddr = sk->daddr;
+ tw->rcv_saddr = sk->rcv_saddr;
+ tw->bound_dev_if= sk->bound_dev_if;
+ tw->num = sk->num;
+ tw->state = TCP_TIME_WAIT;
+ tw->substate = state;
+ tw->sport = sk->sport;
+ tw->dport = sk->dport;
+ tw->family = sk->family;
+ tw->reuse = sk->reuse;
+#ifdef SO_REUSEPORT
+ tw->reuseport = sk->reuseport;
+#endif
+ tw->rcv_wscale = tp->rcv_wscale;
+ atomic_set(&tw->refcnt, 1);
+
+ tw->hashent = sk->hashent;
+ tw->rcv_nxt = tp->rcv_nxt;
+ tw->snd_nxt = tp->snd_nxt;
+ tw->rcv_wnd = tcp_receive_window(tp);
+ tw->ts_recent = tp->ts_recent;
+ tw->ts_recent_stamp= tp->ts_recent_stamp;
+ tw->pprev_death = NULL;
+
+ tw->uid = sock_i_uid_t(sk);
+
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ if(tw->family == PF_INET6) {
+ memcpy(&tw->v6_daddr,
+ &sk->net_pinfo.af_inet6.daddr,
+ sizeof(struct in6_addr));
+ memcpy(&tw->v6_rcv_saddr,
+ &sk->net_pinfo.af_inet6.rcv_saddr,
+ sizeof(struct in6_addr));
+ }
+#endif
+ /* Linkage updates. */
+ __tcp_tw_hashdance(sk, tw);
+
+ /* Get the TIME_WAIT timeout firing. */
+ if (timeo < rto)
+ timeo = rto;
+
+ if (recycle_ok) {
+ tw->timeout = rto;
+ } else {
+ tw->timeout = TCP_TIMEWAIT_LEN;
+ if (state == TCP_TIME_WAIT)
+ timeo = TCP_TIMEWAIT_LEN;
+ }
+
+ tcp_tw_schedule(tw, timeo);
+ tcp_tw_put(tw);
+ } else {
+ /* Sorry, if we're out of memory, just CLOSE this
+ * socket up. We've got bigger problems than
+ * non-graceful socket closings.
+ */
+ if (net_ratelimit())
+ printk(KERN_INFO "TCP: time wait bucket table overflow\n");
+ }
+
+ tcp_update_metrics(sk);
+ tcp_done(sk);
+}
+
+/* Kill off TIME_WAIT sockets once their lifetime has expired. */
+static int tcp_tw_death_row_slot = 0;
+
+static void tcp_twkill(unsigned long);
+
+static struct tcp_tw_bucket *tcp_tw_death_row[TCP_TWKILL_SLOTS];
+static spinlock_t tw_death_lock = SPIN_LOCK_UNLOCKED;
+static struct timer_list tcp_tw_timer = { function: tcp_twkill };
+
+static void SMP_TIMER_NAME(tcp_twkill)(unsigned long dummy)
+{
+ struct tcp_tw_bucket *tw;
+ int killed = 0;
+
+ /* NOTE: compare this to previous version where lock
+ * was released after detaching chain. It was racy,
+ * because tw buckets are scheduled in not serialized context
+ * in 2.3 (with netfilter), and with softnet it is common, because
+ * soft irqs are not sequenced.
+ */
+ spin_lock(&tw_death_lock);
+
+ if (tcp_tw_count == 0)
+ goto out;
+
+ while((tw = tcp_tw_death_row[tcp_tw_death_row_slot]) != NULL) {
+ tcp_tw_death_row[tcp_tw_death_row_slot] = tw->next_death;
+ if (tw->next_death)
+ tw->next_death->pprev_death = tw->pprev_death;
+ tw->pprev_death = NULL;
+ spin_unlock(&tw_death_lock);
+
+ tcp_timewait_kill(tw);
+ tcp_tw_put(tw);
+
+ killed++;
+
+ spin_lock(&tw_death_lock);
+ }
+ tcp_tw_death_row_slot =
+ ((tcp_tw_death_row_slot + 1) & (TCP_TWKILL_SLOTS - 1));
+
+ if ((tcp_tw_count -= killed) != 0)
+ mod_timer(&tcp_tw_timer, jiffies+TCP_TWKILL_PERIOD);
+ net_statistics[smp_processor_id()*2].TimeWaited += killed;
+out:
+ spin_unlock(&tw_death_lock);
+}
+
+SMP_TIMER_DEFINE(tcp_twkill, tcp_twkill_task);
+
+/* These are always called from BH context. See callers in
+ * tcp_input.c to verify this.
+ */
+
+/* This is for handling early-kills of TIME_WAIT sockets. */
+void tcp_tw_deschedule(struct tcp_tw_bucket *tw)
+{
+ spin_lock(&tw_death_lock);
+ if (tw->pprev_death) {
+ if(tw->next_death)
+ tw->next_death->pprev_death = tw->pprev_death;
+ *tw->pprev_death = tw->next_death;
+ tw->pprev_death = NULL;
+ tcp_tw_put(tw);
+ if (--tcp_tw_count == 0)
+ del_timer(&tcp_tw_timer);
+ }
+ spin_unlock(&tw_death_lock);
+}
+
+/* Short-time timewait calendar */
+
+static int tcp_twcal_hand = -1;
+static int tcp_twcal_jiffie;
+static void tcp_twcal_tick(unsigned long);
+static struct timer_list tcp_twcal_timer = {function: tcp_twcal_tick};
+static struct tcp_tw_bucket *tcp_twcal_row[TCP_TW_RECYCLE_SLOTS];
+
+void tcp_tw_schedule(struct tcp_tw_bucket *tw, int timeo)
+{
+ struct tcp_tw_bucket **tpp;
+ int slot;
+
+ /* timeout := RTO * 3.5
+ *
+ * 3.5 = 1+2+0.5 to wait for two retransmits.
+ *
+ * RATIONALE: if FIN arrived and we entered TIME-WAIT state,
+ * our ACK acking that FIN can be lost. If N subsequent retransmitted
+ * FINs (or previous seqments) are lost (probability of such event
+ * is p^(N+1), where p is probability to lose single packet and
+ * time to detect the loss is about RTO*(2^N - 1) with exponential
+ * backoff). Normal timewait length is calculated so, that we
+ * waited at least for one retransmitted FIN (maximal RTO is 120sec).
+ * [ BTW Linux. following BSD, violates this requirement waiting
+ * only for 60sec, we should wait at least for 240 secs.
+ * Well, 240 consumes too much of resources 8)
+ * ]
+ * This interval is not reduced to catch old duplicate and
+ * responces to our wandering segments living for two MSLs.
+ * However, if we use PAWS to detect
+ * old duplicates, we can reduce the interval to bounds required
+ * by RTO, rather than MSL. So, if peer understands PAWS, we
+ * kill tw bucket after 3.5*RTO (it is important that this number
+ * is greater than TS tick!) and detect old duplicates with help
+ * of PAWS.
+ */
+ slot = (timeo + (1<<TCP_TW_RECYCLE_TICK) - 1) >> TCP_TW_RECYCLE_TICK;
+
+ spin_lock(&tw_death_lock);
+
+ /* Unlink it, if it was scheduled */
+ if (tw->pprev_death) {
+ if(tw->next_death)
+ tw->next_death->pprev_death = tw->pprev_death;
+ *tw->pprev_death = tw->next_death;
+ tw->pprev_death = NULL;
+ tcp_tw_count--;
+ } else
+ atomic_inc(&tw->refcnt);
+
+ if (slot >= TCP_TW_RECYCLE_SLOTS) {
+ /* Schedule to slow timer */
+ if (timeo >= TCP_TIMEWAIT_LEN) {
+ slot = TCP_TWKILL_SLOTS-1;
+ } else {
+ slot = (timeo + TCP_TWKILL_PERIOD-1) / TCP_TWKILL_PERIOD;
+ if (slot >= TCP_TWKILL_SLOTS)
+ slot = TCP_TWKILL_SLOTS-1;
+ }
+ tw->ttd = jiffies + timeo;
+ slot = (tcp_tw_death_row_slot + slot) & (TCP_TWKILL_SLOTS - 1);
+ tpp = &tcp_tw_death_row[slot];
+ } else {
+ tw->ttd = jiffies + (slot<<TCP_TW_RECYCLE_TICK);
+
+ if (tcp_twcal_hand < 0) {
+ tcp_twcal_hand = 0;
+ tcp_twcal_jiffie = jiffies;
+ tcp_twcal_timer.expires = tcp_twcal_jiffie + (slot<<TCP_TW_RECYCLE_TICK);
+ add_timer(&tcp_twcal_timer);
+ } else {
+ if ((long)(tcp_twcal_timer.expires - jiffies) > (slot<<TCP_TW_RECYCLE_TICK))
+ mod_timer(&tcp_twcal_timer, jiffies + (slot<<TCP_TW_RECYCLE_TICK));
+ slot = (tcp_twcal_hand + slot)&(TCP_TW_RECYCLE_SLOTS-1);
+ }
+ tpp = &tcp_twcal_row[slot];
+ }
+
+ if((tw->next_death = *tpp) != NULL)
+ (*tpp)->pprev_death = &tw->next_death;
+ *tpp = tw;
+ tw->pprev_death = tpp;
+
+ if (tcp_tw_count++ == 0)
+ mod_timer(&tcp_tw_timer, jiffies+TCP_TWKILL_PERIOD);
+ spin_unlock(&tw_death_lock);
+}
+
+void SMP_TIMER_NAME(tcp_twcal_tick)(unsigned long dummy)
+{
+ int n, slot;
+ unsigned long j;
+ unsigned long now = jiffies;
+ int killed = 0;
+ int adv = 0;
+
+ spin_lock(&tw_death_lock);
+ if (tcp_twcal_hand < 0)
+ goto out;
+
+ slot = tcp_twcal_hand;
+ j = tcp_twcal_jiffie;
+
+ for (n=0; n<TCP_TW_RECYCLE_SLOTS; n++) {
+ if ((long)(j - now) <= 0) {
+ struct tcp_tw_bucket *tw;
+
+ while((tw = tcp_twcal_row[slot]) != NULL) {
+ tcp_twcal_row[slot] = tw->next_death;
+ tw->pprev_death = NULL;
+
+ tcp_timewait_kill(tw);
+ tcp_tw_put(tw);
+ killed++;
+ }
+ } else {
+ if (!adv) {
+ adv = 1;
+ tcp_twcal_jiffie = j;
+ tcp_twcal_hand = slot;
+ }
+
+ if (tcp_twcal_row[slot] != NULL) {
+ mod_timer(&tcp_twcal_timer, j);
+ goto out;
+ }
+ }
+ j += (1<<TCP_TW_RECYCLE_TICK);
+ slot = (slot+1)&(TCP_TW_RECYCLE_SLOTS-1);
+ }
+ tcp_twcal_hand = -1;
+
+out:
+ if ((tcp_tw_count -= killed) == 0)
+ del_timer(&tcp_tw_timer);
+ net_statistics[smp_processor_id()*2].TimeWaitKilled += killed;
+ spin_unlock(&tw_death_lock);
+}
+
+SMP_TIMER_DEFINE(tcp_twcal_tick, tcp_twcal_tasklet);
+
+
+/* This is not only more efficient than what we used to do, it eliminates
+ * a lot of code duplication between IPv4/IPv6 SYN recv processing. -DaveM
+ *
+ * Actually, we could lots of memory writes here. tp of listening
+ * socket contains all necessary default parameters.
+ */
+struct sock *tcp_create_openreq_child(struct sock *sk, struct open_request *req, struct sk_buff *skb)
+{
+ struct sock *newsk = sk_alloc(PF_INET, GFP_ATOMIC, 0);
+
+ if(newsk != NULL) {
+ struct tcp_opt *newtp;
+#ifdef CONFIG_FILTER
+ struct sk_filter *filter;
+#endif
+
+ memcpy(newsk, sk, sizeof(*newsk));
+ newsk->state = TCP_SYN_RECV;
+
+ /* SANITY */
+ newsk->pprev = NULL;
+ newsk->prev = NULL;
+
+ /* Clone the TCP header template */
+ newsk->dport = req->rmt_port;
+
+ sock_lock_init(newsk);
+ bh_lock_sock(newsk);
+
+ newsk->dst_lock = RW_LOCK_UNLOCKED;
+ atomic_set(&newsk->rmem_alloc, 0);
+ skb_queue_head_init(&newsk->receive_queue);
+ atomic_set(&newsk->wmem_alloc, 0);
+ skb_queue_head_init(&newsk->write_queue);
+ atomic_set(&newsk->omem_alloc, 0);
+ newsk->wmem_queued = 0;
+ newsk->forward_alloc = 0;
+
+ newsk->done = 0;
+ newsk->userlocks = sk->userlocks & ~SOCK_BINDPORT_LOCK;
+ newsk->proc = 0;
+ newsk->backlog.head = newsk->backlog.tail = NULL;
+ newsk->callback_lock = RW_LOCK_UNLOCKED;
+ skb_queue_head_init(&newsk->error_queue);
+ newsk->write_space = tcp_write_space;
+#ifdef CONFIG_FILTER
+ if ((filter = newsk->filter) != NULL)
+ sk_filter_charge(newsk, filter);
+#endif
+
+ /* Now setup tcp_opt */
+ newtp = &(newsk->tp_pinfo.af_tcp);
+ newtp->pred_flags = 0;
+ newtp->rcv_nxt = req->rcv_isn + 1;
+ newtp->snd_nxt = req->snt_isn + 1;
+ newtp->snd_una = req->snt_isn + 1;
+ newtp->snd_sml = req->snt_isn + 1;
+
+ tcp_prequeue_init(newtp);
+
+ tcp_init_wl(newtp, req->snt_isn, req->rcv_isn);
+
+ newtp->retransmits = 0;
+ newtp->backoff = 0;
+ newtp->srtt = 0;
+ newtp->mdev = TCP_TIMEOUT_INIT;
+ newtp->rto = TCP_TIMEOUT_INIT;
+
+ newtp->packets_out = 0;
+ newtp->left_out = 0;
+ newtp->retrans_out = 0;
+ newtp->sacked_out = 0;
+ newtp->fackets_out = 0;
+ newtp->snd_ssthresh = 0x7fffffff;
+
+ /* So many TCP implementations out there (incorrectly) count the
+ * initial SYN frame in their delayed-ACK and congestion control
+ * algorithms that we must have the following bandaid to talk
+ * efficiently to them. -DaveM
+ */
+ newtp->snd_cwnd = 2;
+ newtp->snd_cwnd_cnt = 0;
+
+ newtp->frto_counter = 0;
+ newtp->frto_highmark = 0;
+
+ tcp_set_ca_state(newtp, TCP_CA_Open);
+ tcp_init_xmit_timers(newsk);
+ skb_queue_head_init(&newtp->out_of_order_queue);
+ newtp->send_head = NULL;
+ newtp->rcv_wup = req->rcv_isn + 1;
+ newtp->write_seq = req->snt_isn + 1;
+ newtp->pushed_seq = newtp->write_seq;
+ newtp->copied_seq = req->rcv_isn + 1;
+
+ newtp->saw_tstamp = 0;
+
+ newtp->dsack = 0;
+ newtp->eff_sacks = 0;
+
+ newtp->probes_out = 0;
+ newtp->num_sacks = 0;
+ newtp->urg_data = 0;
+ newtp->listen_opt = NULL;
+ newtp->accept_queue = newtp->accept_queue_tail = NULL;
+ /* Deinitialize syn_wait_lock to trap illegal accesses. */
+ memset(&newtp->syn_wait_lock, 0, sizeof(newtp->syn_wait_lock));
+
+ /* Back to base struct sock members. */
+ newsk->err = 0;
+ newsk->priority = 0;
+ atomic_set(&newsk->refcnt, 2);
+#ifdef INET_REFCNT_DEBUG
+ atomic_inc(&inet_sock_nr);
+#endif
+ atomic_inc(&tcp_sockets_allocated);
+
+ if (newsk->keepopen)
+ tcp_reset_keepalive_timer(newsk, keepalive_time_when(newtp));
+ newsk->socket = NULL;
+ newsk->sleep = NULL;
+
+ newtp->tstamp_ok = req->tstamp_ok;
+ if((newtp->sack_ok = req->sack_ok) != 0) {
+ if (sysctl_tcp_fack)
+ newtp->sack_ok |= 2;
+ }
+ newtp->window_clamp = req->window_clamp;
+ newtp->rcv_ssthresh = req->rcv_wnd;
+ newtp->rcv_wnd = req->rcv_wnd;
+ newtp->wscale_ok = req->wscale_ok;
+ if (newtp->wscale_ok) {
+ newtp->snd_wscale = req->snd_wscale;
+ newtp->rcv_wscale = req->rcv_wscale;
+ } else {
+ newtp->snd_wscale = newtp->rcv_wscale = 0;
+ newtp->window_clamp = min(newtp->window_clamp, 65535U);
+ }
+ newtp->snd_wnd = ntohs(skb->h.th->window) << newtp->snd_wscale;
+ newtp->max_window = newtp->snd_wnd;
+
+ if (newtp->tstamp_ok) {
+ newtp->ts_recent = req->ts_recent;
+ newtp->ts_recent_stamp = xtime.tv_sec;
+ newtp->tcp_header_len = sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED;
+ } else {
+ newtp->ts_recent_stamp = 0;
+ newtp->tcp_header_len = sizeof(struct tcphdr);
+ }
+ if (skb->len >= TCP_MIN_RCVMSS+newtp->tcp_header_len)
+ newtp->ack.last_seg_size = skb->len-newtp->tcp_header_len;
+ newtp->mss_clamp = req->mss;
+ TCP_ECN_openreq_child(newtp, req);
+
+ tcp_ca_init(newtp);
+ TCP_INC_STATS_BH(TcpPassiveOpens);
+ }
+ return newsk;
+}
+
+/*
+ * Process an incoming packet for SYN_RECV sockets represented
+ * as an open_request.
+ */
+
+struct sock *tcp_check_req(struct sock *sk,struct sk_buff *skb,
+ struct open_request *req,
+ struct open_request **prev)
+{
+ struct tcphdr *th = skb->h.th;
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ u32 flg = tcp_flag_word(th) & (TCP_FLAG_RST|TCP_FLAG_SYN|TCP_FLAG_ACK);
+ int paws_reject = 0;
+ struct tcp_opt ttp;
+ struct sock *child;
+
+ ttp.saw_tstamp = 0;
+ if (th->doff > (sizeof(struct tcphdr)>>2)) {
+ tcp_parse_options(skb, &ttp, 0);
+
+ if (ttp.saw_tstamp) {
+ ttp.ts_recent = req->ts_recent;
+ /* We do not store true stamp, but it is not required,
+ * it can be estimated (approximately)
+ * from another data.
+ */
+ ttp.ts_recent_stamp = xtime.tv_sec - ((TCP_TIMEOUT_INIT/HZ)<<req->retrans);
+ paws_reject = tcp_paws_check(&ttp, th->rst);
+ }
+ }
+
+ /* Check for pure retransmitted SYN. */
+ if (TCP_SKB_CB(skb)->seq == req->rcv_isn &&
+ flg == TCP_FLAG_SYN &&
+ !paws_reject) {
+ /*
+ * RFC793 draws (Incorrectly! It was fixed in RFC1122)
+ * this case on figure 6 and figure 8, but formal
+ * protocol description says NOTHING.
+ * To be more exact, it says that we should send ACK,
+ * because this segment (at least, if it has no data)
+ * is out of window.
+ *
+ * CONCLUSION: RFC793 (even with RFC1122) DOES NOT
+ * describe SYN-RECV state. All the description
+ * is wrong, we cannot believe to it and should
+ * rely only on common sense and implementation
+ * experience.
+ *
+ * Enforce "SYN-ACK" according to figure 8, figure 6
+ * of RFC793, fixed by RFC1122.
+ */
+ req->class->rtx_syn_ack(sk, req, NULL);
+ return NULL;
+ }
+
+ /* Further reproduces section "SEGMENT ARRIVES"
+ for state SYN-RECEIVED of RFC793.
+ It is broken, however, it does not work only
+ when SYNs are crossed.
+
+ You would think that SYN crossing is impossible here, since
+ we should have a SYN_SENT socket (from connect()) on our end,
+ but this is not true if the crossed SYNs were sent to both
+ ends by a malicious third party. We must defend against this,
+ and to do that we first verify the ACK (as per RFC793, page
+ 36) and reset if it is invalid. Is this a true full defense?
+ To convince ourselves, let us consider a way in which the ACK
+ test can still pass in this 'malicious crossed SYNs' case.
+ Malicious sender sends identical SYNs (and thus identical sequence
+ numbers) to both A and B:
+
+ A: gets SYN, seq=7
+ B: gets SYN, seq=7
+
+ By our good fortune, both A and B select the same initial
+ send sequence number of seven :-)
+
+ A: sends SYN|ACK, seq=7, ack_seq=8
+ B: sends SYN|ACK, seq=7, ack_seq=8
+
+ So we are now A eating this SYN|ACK, ACK test passes. So
+ does sequence test, SYN is truncated, and thus we consider
+ it a bare ACK.
+
+ If tp->defer_accept, we silently drop this bare ACK. Otherwise,
+ we create an established connection. Both ends (listening sockets)
+ accept the new incoming connection and try to talk to each other. 8-)
+
+ Note: This case is both harmless, and rare. Possibility is about the
+ same as us discovering intelligent life on another plant tomorrow.
+
+ But generally, we should (RFC lies!) to accept ACK
+ from SYNACK both here and in tcp_rcv_state_process().
+ tcp_rcv_state_process() does not, hence, we do not too.
+
+ Note that the case is absolutely generic:
+ we cannot optimize anything here without
+ violating protocol. All the checks must be made
+ before attempt to create socket.
+ */
+
+ /* RFC793 page 36: "If the connection is in any non-synchronized state ...
+ * and the incoming segment acknowledges something not yet
+ * sent (the segment carries an unaccaptable ACK) ...
+ * a reset is sent."
+ *
+ * Invalid ACK: reset will be sent by listening socket
+ */
+ if ((flg & TCP_FLAG_ACK) &&
+ (TCP_SKB_CB(skb)->ack_seq != req->snt_isn+1))
+ return sk;
+
+ /* Also, it would be not so bad idea to check rcv_tsecr, which
+ * is essentially ACK extension and too early or too late values
+ * should cause reset in unsynchronized states.
+ */
+
+ /* RFC793: "first check sequence number". */
+
+ if (paws_reject || !tcp_in_window(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq,
+ req->rcv_isn+1, req->rcv_isn+1+req->rcv_wnd)) {
+ /* Out of window: send ACK and drop. */
+ if (!(flg & TCP_FLAG_RST))
+ req->class->send_ack(skb, req);
+ if (paws_reject)
+ NET_INC_STATS_BH(PAWSEstabRejected);
+ return NULL;
+ }
+
+ /* In sequence, PAWS is OK. */
+
+ if (ttp.saw_tstamp && !after(TCP_SKB_CB(skb)->seq, req->rcv_isn+1))
+ req->ts_recent = ttp.rcv_tsval;
+
+ if (TCP_SKB_CB(skb)->seq == req->rcv_isn) {
+ /* Truncate SYN, it is out of window starting
+ at req->rcv_isn+1. */
+ flg &= ~TCP_FLAG_SYN;
+ }
+
+ /* RFC793: "second check the RST bit" and
+ * "fourth, check the SYN bit"
+ */
+ if (flg & (TCP_FLAG_RST|TCP_FLAG_SYN))
+ goto embryonic_reset;
+
+ /* ACK sequence verified above, just make sure ACK is
+ * set. If ACK not set, just silently drop the packet.
+ */
+ if (!(flg & TCP_FLAG_ACK))
+ return NULL;
+
+ /* If TCP_DEFER_ACCEPT is set, drop bare ACK. */
+ if (tp->defer_accept && TCP_SKB_CB(skb)->end_seq == req->rcv_isn+1) {
+ req->acked = 1;
+ return NULL;
+ }
+
+ /* OK, ACK is valid, create big socket and
+ * feed this segment to it. It will repeat all
+ * the tests. THIS SEGMENT MUST MOVE SOCKET TO
+ * ESTABLISHED STATE. If it will be dropped after
+ * socket is created, wait for troubles.
+ */
+ child = tp->af_specific->syn_recv_sock(sk, skb, req, NULL);
+ if (child == NULL)
+ goto listen_overflow;
+
+ tcp_synq_unlink(tp, req, prev);
+ tcp_synq_removed(sk, req);
+
+ tcp_acceptq_queue(sk, req, child);
+ return child;
+
+listen_overflow:
+ if (!sysctl_tcp_abort_on_overflow) {
+ req->acked = 1;
+ return NULL;
+ }
+
+embryonic_reset:
+ NET_INC_STATS_BH(EmbryonicRsts);
+ if (!(flg & TCP_FLAG_RST))
+ req->class->send_reset(skb);
+
+ tcp_synq_drop(sk, req, prev);
+ return NULL;
+}
+
+/*
+ * Queue segment on the new socket if the new socket is active,
+ * otherwise we just shortcircuit this and continue with
+ * the new socket.
+ */
+
+int tcp_child_process(struct sock *parent, struct sock *child,
+ struct sk_buff *skb)
+{
+ int ret = 0;
+ int state = child->state;
+
+ if (child->lock.users == 0) {
+ ret = tcp_rcv_state_process(child, skb, skb->h.th, skb->len);
+
+ /* Wakeup parent, send SIGIO */
+ if (state == TCP_SYN_RECV && child->state != state)
+ parent->data_ready(parent, 0);
+ } else {
+ /* Alas, it is possible again, because we do lookup
+ * in main socket hash table and lock on listening
+ * socket does not protect us more.
+ */
+ sk_add_backlog(child, skb);
+ }
+
+ bh_unlock_sock(child);
+ sock_put(child);
+ return ret;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/tcp_output.c b/uClinux-2.4.31-uc0/net/ipv4/tcp_output.c
new file mode 100644
index 0000000..150f7db
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/tcp_output.c
@@ -0,0 +1,1469 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * Implementation of the Transmission Control Protocol(TCP).
+ *
+ * Version: $Id: tcp_output.c,v 1.144 2001/11/06 22:21:08 davem Exp $
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Mark Evans, <evansmp@uhura.aston.ac.uk>
+ * Corey Minyard <wf-rch!minyard@relay.EU.net>
+ * Florian La Roche, <flla@stud.uni-sb.de>
+ * Charles Hedrick, <hedrick@klinzhai.rutgers.edu>
+ * Linus Torvalds, <torvalds@cs.helsinki.fi>
+ * Alan Cox, <gw4pts@gw4pts.ampr.org>
+ * Matthew Dillon, <dillon@apollo.west.oic.com>
+ * Arnt Gulbrandsen, <agulbra@nvg.unit.no>
+ * Jorge Cwik, <jorge@laser.satlink.net>
+ */
+
+/*
+ * Changes: Pedro Roque : Retransmit queue handled by TCP.
+ * : Fragmentation on mtu decrease
+ * : Segment collapse on retransmit
+ * : AF independence
+ *
+ * Linus Torvalds : send_delayed_ack
+ * David S. Miller : Charge memory using the right skb
+ * during syn/ack processing.
+ * David S. Miller : Output engine completely rewritten.
+ * Andrea Arcangeli: SYNACK carry ts_recent in tsecr.
+ * Cacophonix Gaul : draft-minshall-nagle-01
+ * J Hadi Salim : ECN support
+ *
+ */
+
+#include <net/tcp.h>
+
+#include <linux/compiler.h>
+#include <linux/smp_lock.h>
+
+/* People can turn this off for buggy TCP's found in printers etc. */
+int sysctl_tcp_retrans_collapse = 1;
+
+static __inline__
+void update_send_head(struct sock *sk, struct tcp_opt *tp, struct sk_buff *skb)
+{
+ tp->send_head = skb->next;
+ if (tp->send_head == (struct sk_buff *) &sk->write_queue)
+ tp->send_head = NULL;
+ tp->snd_nxt = TCP_SKB_CB(skb)->end_seq;
+ if (tp->packets_out++ == 0)
+ tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
+}
+
+/* SND.NXT, if window was not shrunk.
+ * If window has been shrunk, what should we make? It is not clear at all.
+ * Using SND.UNA we will fail to open window, SND.NXT is out of window. :-(
+ * Anything in between SND.UNA...SND.UNA+SND.WND also can be already
+ * invalid. OK, let's make this for now:
+ */
+static __inline__ __u32 tcp_acceptable_seq(struct sock *sk, struct tcp_opt *tp)
+{
+ if (!before(tp->snd_una+tp->snd_wnd, tp->snd_nxt))
+ return tp->snd_nxt;
+ else
+ return tp->snd_una+tp->snd_wnd;
+}
+
+/* Calculate mss to advertise in SYN segment.
+ * RFC1122, RFC1063, draft-ietf-tcpimpl-pmtud-01 state that:
+ *
+ * 1. It is independent of path mtu.
+ * 2. Ideally, it is maximal possible segment size i.e. 65535-40.
+ * 3. For IPv4 it is reasonable to calculate it from maximal MTU of
+ * attached devices, because some buggy hosts are confused by
+ * large MSS.
+ * 4. We do not make 3, we advertise MSS, calculated from first
+ * hop device mtu, but allow to raise it to ip_rt_min_advmss.
+ * This may be overriden via information stored in routing table.
+ * 5. Value 65535 for MSS is valid in IPv6 and means "as large as possible,
+ * probably even Jumbo".
+ */
+static __u16 tcp_advertise_mss(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct dst_entry *dst = __sk_dst_get(sk);
+ int mss = tp->advmss;
+
+ if (dst && dst->advmss < mss) {
+ mss = dst->advmss;
+ tp->advmss = mss;
+ }
+
+ return (__u16)mss;
+}
+
+/* RFC2861. Reset CWND after idle period longer RTO to "restart window".
+ * This is the first part of cwnd validation mechanism. */
+static void tcp_cwnd_restart(struct tcp_opt *tp)
+{
+ s32 delta = tcp_time_stamp - tp->lsndtime;
+ u32 restart_cwnd = tcp_init_cwnd(tp);
+ u32 cwnd = tp->snd_cwnd;
+
+ if (tcp_is_vegas(tp))
+ tcp_vegas_enable(tp);
+
+ tp->snd_ssthresh = tcp_current_ssthresh(tp);
+ restart_cwnd = min(restart_cwnd, cwnd);
+
+ while ((delta -= tp->rto) > 0 && cwnd > restart_cwnd)
+ cwnd >>= 1;
+ tp->snd_cwnd = max(cwnd, restart_cwnd);
+ tp->snd_cwnd_stamp = tcp_time_stamp;
+ tp->snd_cwnd_used = 0;
+}
+
+static __inline__ void tcp_event_data_sent(struct tcp_opt *tp, struct sk_buff *skb)
+{
+ u32 now = tcp_time_stamp;
+
+ if (!tp->packets_out && (s32)(now - tp->lsndtime) > tp->rto)
+ tcp_cwnd_restart(tp);
+
+ tp->lsndtime = now;
+
+ /* If it is a reply for ato after last received
+ * packet, enter pingpong mode.
+ */
+ if ((u32)(now - tp->ack.lrcvtime) < tp->ack.ato)
+ tp->ack.pingpong = 1;
+}
+
+static __inline__ void tcp_event_ack_sent(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ tcp_dec_quickack_mode(tp);
+ tcp_clear_xmit_timer(sk, TCP_TIME_DACK);
+}
+
+/* Chose a new window to advertise, update state in tcp_opt for the
+ * socket, and return result with RFC1323 scaling applied. The return
+ * value can be stuffed directly into th->window for an outgoing
+ * frame.
+ */
+static __inline__ u16 tcp_select_window(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ u32 cur_win = tcp_receive_window(tp);
+ u32 new_win = __tcp_select_window(sk);
+
+ /* Never shrink the offered window */
+ if(new_win < cur_win) {
+ /* Danger Will Robinson!
+ * Don't update rcv_wup/rcv_wnd here or else
+ * we will not be able to advertise a zero
+ * window in time. --DaveM
+ *
+ * Relax Will Robinson.
+ */
+ new_win = cur_win;
+ }
+ tp->rcv_wnd = new_win;
+ tp->rcv_wup = tp->rcv_nxt;
+
+ /* RFC1323 scaling applied */
+ new_win >>= tp->rcv_wscale;
+
+ /* If we advertise zero window, disable fast path. */
+ if (new_win == 0)
+ tp->pred_flags = 0;
+
+ return new_win;
+}
+
+
+/* This routine actually transmits TCP packets queued in by
+ * tcp_do_sendmsg(). This is used by both the initial
+ * transmission and possible later retransmissions.
+ * All SKB's seen here are completely headerless. It is our
+ * job to build the TCP header, and pass the packet down to
+ * IP so it can do the same plus pass the packet off to the
+ * device.
+ *
+ * We are working here with either a clone of the original
+ * SKB, or a fresh unique copy made by the retransmit engine.
+ */
+int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb)
+{
+ if(skb != NULL) {
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct tcp_skb_cb *tcb = TCP_SKB_CB(skb);
+ int tcp_header_size = tp->tcp_header_len;
+ struct tcphdr *th;
+ int sysctl_flags;
+ int err;
+
+#define SYSCTL_FLAG_TSTAMPS 0x1
+#define SYSCTL_FLAG_WSCALE 0x2
+#define SYSCTL_FLAG_SACK 0x4
+
+ sysctl_flags = 0;
+ if (tcb->flags & TCPCB_FLAG_SYN) {
+ tcp_header_size = sizeof(struct tcphdr) + TCPOLEN_MSS;
+ if(sysctl_tcp_timestamps) {
+ tcp_header_size += TCPOLEN_TSTAMP_ALIGNED;
+ sysctl_flags |= SYSCTL_FLAG_TSTAMPS;
+ }
+ if(sysctl_tcp_window_scaling) {
+ tcp_header_size += TCPOLEN_WSCALE_ALIGNED;
+ sysctl_flags |= SYSCTL_FLAG_WSCALE;
+ }
+ if(sysctl_tcp_sack) {
+ sysctl_flags |= SYSCTL_FLAG_SACK;
+ if(!(sysctl_flags & SYSCTL_FLAG_TSTAMPS))
+ tcp_header_size += TCPOLEN_SACKPERM_ALIGNED;
+ }
+ } else if (tp->eff_sacks) {
+ /* A SACK is 2 pad bytes, a 2 byte header, plus
+ * 2 32-bit sequence numbers for each SACK block.
+ */
+ tcp_header_size += (TCPOLEN_SACK_BASE_ALIGNED +
+ (tp->eff_sacks * TCPOLEN_SACK_PERBLOCK));
+ }
+
+ /*
+ * If the connection is idle and we are restarting,
+ * then we don't want to do any Vegas calculations
+ * until we get fresh RTT samples. So when we
+ * restart, we reset our Vegas state to a clean
+ * slate. After we get acks for this flight of
+ * packets, _then_ we can make Vegas calculations
+ * again.
+ */
+ if (tcp_is_vegas(tp) && tcp_packets_in_flight(tp) == 0)
+ tcp_vegas_enable(tp);
+
+ th = (struct tcphdr *) skb_push(skb, tcp_header_size);
+ skb->h.th = th;
+ skb_set_owner_w(skb, sk);
+
+ /* Build TCP header and checksum it. */
+ th->source = sk->sport;
+ th->dest = sk->dport;
+ th->seq = htonl(tcb->seq);
+ th->ack_seq = htonl(tp->rcv_nxt);
+ *(((__u16 *)th) + 6) = htons(((tcp_header_size >> 2) << 12) | tcb->flags);
+ if (tcb->flags & TCPCB_FLAG_SYN) {
+ /* RFC1323: The window in SYN & SYN/ACK segments
+ * is never scaled.
+ */
+ th->window = htons(tp->rcv_wnd);
+ } else {
+ th->window = htons(tcp_select_window(sk));
+ }
+ th->check = 0;
+ th->urg_ptr = 0;
+
+ if (tp->urg_mode &&
+ between(tp->snd_up, tcb->seq+1, tcb->seq+0xFFFF)) {
+ th->urg_ptr = htons(tp->snd_up-tcb->seq);
+ th->urg = 1;
+ }
+
+ if (tcb->flags & TCPCB_FLAG_SYN) {
+ tcp_syn_build_options((__u32 *)(th + 1),
+ tcp_advertise_mss(sk),
+ (sysctl_flags & SYSCTL_FLAG_TSTAMPS),
+ (sysctl_flags & SYSCTL_FLAG_SACK),
+ (sysctl_flags & SYSCTL_FLAG_WSCALE),
+ tp->rcv_wscale,
+ tcb->when,
+ tp->ts_recent);
+ } else {
+ tcp_build_and_update_options((__u32 *)(th + 1),
+ tp, tcb->when);
+
+ TCP_ECN_send(sk, tp, skb, tcp_header_size);
+ }
+ tp->af_specific->send_check(sk, th, skb->len, skb);
+
+ if (tcb->flags & TCPCB_FLAG_ACK)
+ tcp_event_ack_sent(sk);
+
+ if (skb->len != tcp_header_size)
+ tcp_event_data_sent(tp, skb);
+
+ TCP_INC_STATS(TcpOutSegs);
+
+ err = tp->af_specific->queue_xmit(skb, 0);
+ if (err <= 0)
+ return err;
+
+ tcp_enter_cwr(tp);
+
+ /* NET_XMIT_CN is special. It does not guarantee,
+ * that this packet is lost. It tells that device
+ * is about to start to drop packets or already
+ * drops some packets of the same priority and
+ * invokes us to send less aggressively.
+ */
+ return err == NET_XMIT_CN ? 0 : err;
+ }
+ return -ENOBUFS;
+#undef SYSCTL_FLAG_TSTAMPS
+#undef SYSCTL_FLAG_WSCALE
+#undef SYSCTL_FLAG_SACK
+}
+
+
+/* This is the main buffer sending routine. We queue the buffer
+ * and decide whether to queue or transmit now.
+ *
+ * NOTE: probe0 timer is not checked, do not forget tcp_push_pending_frames,
+ * otherwise socket can stall.
+ */
+void tcp_send_skb(struct sock *sk, struct sk_buff *skb, int force_queue, unsigned cur_mss)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ /* Advance write_seq and place onto the write_queue. */
+ tp->write_seq = TCP_SKB_CB(skb)->end_seq;
+ __skb_queue_tail(&sk->write_queue, skb);
+ tcp_charge_skb(sk, skb);
+
+ if (!force_queue && tp->send_head == NULL && tcp_snd_test(tp, skb, cur_mss, tp->nonagle)) {
+ /* Send it out now. */
+ TCP_SKB_CB(skb)->when = tcp_time_stamp;
+ if (tcp_transmit_skb(sk, skb_clone(skb, sk->allocation)) == 0) {
+ tp->snd_nxt = TCP_SKB_CB(skb)->end_seq;
+ tcp_minshall_update(tp, cur_mss, skb);
+ if (tp->packets_out++ == 0)
+ tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
+ return;
+ }
+ }
+ /* Queue it, remembering where we must start sending. */
+ if (tp->send_head == NULL)
+ tp->send_head = skb;
+}
+
+/* Send _single_ skb sitting at the send head. This function requires
+ * true push pending frames to setup probe timer etc.
+ */
+void tcp_push_one(struct sock *sk, unsigned cur_mss)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sk_buff *skb = tp->send_head;
+
+ if (tcp_snd_test(tp, skb, cur_mss, 1)) {
+ /* Send it out now. */
+ TCP_SKB_CB(skb)->when = tcp_time_stamp;
+ if (tcp_transmit_skb(sk, skb_clone(skb, sk->allocation)) == 0) {
+ tp->send_head = NULL;
+ tp->snd_nxt = TCP_SKB_CB(skb)->end_seq;
+ if (tp->packets_out++ == 0)
+ tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
+ return;
+ }
+ }
+}
+
+/* Split fragmented skb to two parts at length len. */
+
+static void skb_split(struct sk_buff *skb, struct sk_buff *skb1, u32 len)
+{
+ int i;
+ int pos = skb->len - skb->data_len;
+
+ if (len < pos) {
+ /* Split line is inside header. */
+ memcpy(skb_put(skb1, pos-len), skb->data + len, pos-len);
+
+ /* And move data appendix as is. */
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++)
+ skb_shinfo(skb1)->frags[i] = skb_shinfo(skb)->frags[i];
+
+ skb_shinfo(skb1)->nr_frags = skb_shinfo(skb)->nr_frags;
+ skb_shinfo(skb)->nr_frags = 0;
+
+ skb1->data_len = skb->data_len;
+ skb1->len += skb1->data_len;
+ skb->data_len = 0;
+ skb->len = len;
+ skb->tail = skb->data+len;
+ } else {
+ int k = 0;
+ int nfrags = skb_shinfo(skb)->nr_frags;
+
+ /* Second chunk has no header, nothing to copy. */
+
+ skb_shinfo(skb)->nr_frags = 0;
+ skb1->len = skb1->data_len = skb->len - len;
+ skb->len = len;
+ skb->data_len = len - pos;
+
+ for (i=0; i<nfrags; i++) {
+ int size = skb_shinfo(skb)->frags[i].size;
+ if (pos + size > len) {
+ skb_shinfo(skb1)->frags[k] = skb_shinfo(skb)->frags[i];
+
+ if (pos < len) {
+ /* Split frag.
+ * We have to variants in this case:
+ * 1. Move all the frag to the second
+ * part, if it is possible. F.e.
+ * this approach is mandatory for TUX,
+ * where splitting is expensive.
+ * 2. Split is accurately. We make this.
+ */
+ get_page(skb_shinfo(skb)->frags[i].page);
+ skb_shinfo(skb1)->frags[0].page_offset += (len-pos);
+ skb_shinfo(skb1)->frags[0].size -= (len-pos);
+ skb_shinfo(skb)->frags[i].size = len-pos;
+ skb_shinfo(skb)->nr_frags++;
+ }
+ k++;
+ } else {
+ skb_shinfo(skb)->nr_frags++;
+ }
+ pos += size;
+ }
+ skb_shinfo(skb1)->nr_frags = k;
+ }
+}
+
+/* Function to create two new TCP segments. Shrinks the given segment
+ * to the specified size and appends a new segment with the rest of the
+ * packet to the list. This won't be called frequently, I hope.
+ * Remember, these are still headerless SKBs at this point.
+ */
+static int tcp_fragment(struct sock *sk, struct sk_buff *skb, u32 len)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ struct sk_buff *buff;
+ int nsize = skb->len - len;
+ u16 flags;
+
+ if (skb_cloned(skb) &&
+ skb_is_nonlinear(skb) &&
+ pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
+ return -ENOMEM;
+
+ /* Get a new skb... force flag on. */
+ buff = tcp_alloc_skb(sk, nsize, GFP_ATOMIC);
+ if (buff == NULL)
+ return -ENOMEM; /* We'll just try again later. */
+ tcp_charge_skb(sk, buff);
+
+ /* Correct the sequence numbers. */
+ TCP_SKB_CB(buff)->seq = TCP_SKB_CB(skb)->seq + len;
+ TCP_SKB_CB(buff)->end_seq = TCP_SKB_CB(skb)->end_seq;
+ TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(buff)->seq;
+
+ /* PSH and FIN should only be set in the second packet. */
+ flags = TCP_SKB_CB(skb)->flags;
+ TCP_SKB_CB(skb)->flags = flags & ~(TCPCB_FLAG_FIN|TCPCB_FLAG_PSH);
+ TCP_SKB_CB(buff)->flags = flags;
+ TCP_SKB_CB(buff)->sacked = TCP_SKB_CB(skb)->sacked&(TCPCB_LOST|TCPCB_EVER_RETRANS|TCPCB_AT_TAIL);
+ if (TCP_SKB_CB(buff)->sacked&TCPCB_LOST) {
+ tp->lost_out++;
+ tp->left_out++;
+ }
+ TCP_SKB_CB(skb)->sacked &= ~TCPCB_AT_TAIL;
+
+ if (!skb_shinfo(skb)->nr_frags && skb->ip_summed != CHECKSUM_HW) {
+ /* Copy and checksum data tail into the new buffer. */
+ buff->csum = csum_partial_copy_nocheck(skb->data + len, skb_put(buff, nsize),
+ nsize, 0);
+
+ skb_trim(skb, len);
+
+ skb->csum = csum_block_sub(skb->csum, buff->csum, len);
+ } else {
+ skb->ip_summed = CHECKSUM_HW;
+ skb_split(skb, buff, len);
+ }
+
+ buff->ip_summed = skb->ip_summed;
+
+ /* Looks stupid, but our code really uses when of
+ * skbs, which it never sent before. --ANK
+ */
+ TCP_SKB_CB(buff)->when = TCP_SKB_CB(skb)->when;
+
+ /* Link BUFF into the send queue. */
+ __skb_append(skb, buff);
+
+ return 0;
+}
+
+/* This function synchronize snd mss to current pmtu/exthdr set.
+
+ tp->user_mss is mss set by user by TCP_MAXSEG. It does NOT counts
+ for TCP options, but includes only bare TCP header.
+
+ tp->mss_clamp is mss negotiated at connection setup.
+ It is minumum of user_mss and mss received with SYN.
+ It also does not include TCP options.
+
+ tp->pmtu_cookie is last pmtu, seen by this function.
+
+ tp->mss_cache is current effective sending mss, including
+ all tcp options except for SACKs. It is evaluated,
+ taking into account current pmtu, but never exceeds
+ tp->mss_clamp.
+
+ NOTE1. rfc1122 clearly states that advertised MSS
+ DOES NOT include either tcp or ip options.
+
+ NOTE2. tp->pmtu_cookie and tp->mss_cache are READ ONLY outside
+ this function. --ANK (980731)
+ */
+
+int tcp_sync_mss(struct sock *sk, u32 pmtu)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ int mss_now;
+
+ /* Calculate base mss without TCP options:
+ It is MMS_S - sizeof(tcphdr) of rfc1122
+ */
+
+ mss_now = pmtu - tp->af_specific->net_header_len - sizeof(struct tcphdr);
+
+ /* Clamp it (mss_clamp does not include tcp options) */
+ if (mss_now > tp->mss_clamp)
+ mss_now = tp->mss_clamp;
+
+ /* Now subtract optional transport overhead */
+ mss_now -= tp->ext_header_len;
+
+ /* Then reserve room for full set of TCP options and 8 bytes of data */
+ if (mss_now < 48)
+ mss_now = 48;
+
+ /* Now subtract TCP options size, not including SACKs */
+ mss_now -= tp->tcp_header_len - sizeof(struct tcphdr);
+
+ /* Bound mss with half of window */
+ if (tp->max_window && mss_now > (tp->max_window>>1))
+ mss_now = max((tp->max_window>>1), 68U - tp->tcp_header_len);
+
+ /* And store cached results */
+ tp->pmtu_cookie = pmtu;
+ tp->mss_cache = mss_now;
+ return mss_now;
+}
+
+
+/* This routine writes packets to the network. It advances the
+ * send_head. This happens as incoming acks open up the remote
+ * window for us.
+ *
+ * Returns 1, if no segments are in flight and we have queued segments, but
+ * cannot send anything now because of SWS or another problem.
+ */
+int tcp_write_xmit(struct sock *sk, int nonagle)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ unsigned int mss_now;
+
+ /* If we are closed, the bytes will have to remain here.
+ * In time closedown will finish, we empty the write queue and all
+ * will be happy.
+ */
+ if(sk->state != TCP_CLOSE) {
+ struct sk_buff *skb;
+ int sent_pkts = 0;
+
+ /* Account for SACKS, we may need to fragment due to this.
+ * It is just like the real MSS changing on us midstream.
+ * We also handle things correctly when the user adds some
+ * IP options mid-stream. Silly to do, but cover it.
+ */
+ mss_now = tcp_current_mss(sk);
+
+ while((skb = tp->send_head) &&
+ tcp_snd_test(tp, skb, mss_now, tcp_skb_is_last(sk, skb) ? nonagle : 1)) {
+ if (skb->len > mss_now) {
+ if (tcp_fragment(sk, skb, mss_now))
+ break;
+ }
+
+ TCP_SKB_CB(skb)->when = tcp_time_stamp;
+ if (tcp_transmit_skb(sk, skb_clone(skb, GFP_ATOMIC)))
+ break;
+ /* Advance the send_head. This one is sent out. */
+ update_send_head(sk, tp, skb);
+ tcp_minshall_update(tp, mss_now, skb);
+ sent_pkts = 1;
+ }
+
+ if (sent_pkts) {
+ tcp_cwnd_validate(sk, tp);
+ return 0;
+ }
+
+ return !tp->packets_out && tp->send_head;
+ }
+ return 0;
+}
+
+/* This function returns the amount that we can raise the
+ * usable window based on the following constraints
+ *
+ * 1. The window can never be shrunk once it is offered (RFC 793)
+ * 2. We limit memory per socket
+ *
+ * RFC 1122:
+ * "the suggested [SWS] avoidance algorithm for the receiver is to keep
+ * RECV.NEXT + RCV.WIN fixed until:
+ * RCV.BUFF - RCV.USER - RCV.WINDOW >= min(1/2 RCV.BUFF, MSS)"
+ *
+ * i.e. don't raise the right edge of the window until you can raise
+ * it at least MSS bytes.
+ *
+ * Unfortunately, the recommended algorithm breaks header prediction,
+ * since header prediction assumes th->window stays fixed.
+ *
+ * Strictly speaking, keeping th->window fixed violates the receiver
+ * side SWS prevention criteria. The problem is that under this rule
+ * a stream of single byte packets will cause the right side of the
+ * window to always advance by a single byte.
+ *
+ * Of course, if the sender implements sender side SWS prevention
+ * then this will not be a problem.
+ *
+ * BSD seems to make the following compromise:
+ *
+ * If the free space is less than the 1/4 of the maximum
+ * space available and the free space is less than 1/2 mss,
+ * then set the window to 0.
+ * [ Actually, bsd uses MSS and 1/4 of maximal _window_ ]
+ * Otherwise, just prevent the window from shrinking
+ * and from being larger than the largest representable value.
+ *
+ * This prevents incremental opening of the window in the regime
+ * where TCP is limited by the speed of the reader side taking
+ * data out of the TCP receive queue. It does nothing about
+ * those cases where the window is constrained on the sender side
+ * because the pipeline is full.
+ *
+ * BSD also seems to "accidentally" limit itself to windows that are a
+ * multiple of MSS, at least until the free space gets quite small.
+ * This would appear to be a side effect of the mbuf implementation.
+ * Combining these two algorithms results in the observed behavior
+ * of having a fixed window size at almost all times.
+ *
+ * Below we obtain similar behavior by forcing the offered window to
+ * a multiple of the mss when it is feasible to do so.
+ *
+ * Note, we don't "adjust" for TIMESTAMP or SACK option bytes.
+ * Regular options like TIMESTAMP are taken into account.
+ */
+u32 __tcp_select_window(struct sock *sk)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ /* MSS for the peer's data. Previous verions used mss_clamp
+ * here. I don't know if the value based on our guesses
+ * of peer's MSS is better for the performance. It's more correct
+ * but may be worse for the performance because of rcv_mss
+ * fluctuations. --SAW 1998/11/1
+ */
+ int mss = tp->ack.rcv_mss;
+ int free_space = tcp_space(sk);
+ int full_space = min_t(int, tp->window_clamp, tcp_full_space(sk));
+ int window;
+
+ if (mss > full_space)
+ mss = full_space;
+
+ if (free_space < full_space/2) {
+ tp->ack.quick = 0;
+
+ if (tcp_memory_pressure)
+ tp->rcv_ssthresh = min(tp->rcv_ssthresh, 4U*tp->advmss);
+
+ if (free_space < mss)
+ return 0;
+ }
+
+ if (free_space > tp->rcv_ssthresh)
+ free_space = tp->rcv_ssthresh;
+
+ /* Get the largest window that is a nice multiple of mss.
+ * Window clamp already applied above.
+ * If our current window offering is within 1 mss of the
+ * free space we just keep it. This prevents the divide
+ * and multiply from happening most of the time.
+ * We also don't do any window rounding when the free space
+ * is too small.
+ */
+ window = tp->rcv_wnd;
+ if (window <= free_space - mss || window > free_space)
+ window = (free_space/mss)*mss;
+
+ return window;
+}
+
+/* Attempt to collapse two adjacent SKB's during retransmission. */
+static void tcp_retrans_try_collapse(struct sock *sk, struct sk_buff *skb, int mss_now)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ struct sk_buff *next_skb = skb->next;
+
+ /* The first test we must make is that neither of these two
+ * SKB's are still referenced by someone else.
+ */
+ if(!skb_cloned(skb) && !skb_cloned(next_skb)) {
+ int skb_size = skb->len, next_skb_size = next_skb->len;
+ u16 flags = TCP_SKB_CB(skb)->flags;
+
+ /* Also punt if next skb has been SACK'd. */
+ if(TCP_SKB_CB(next_skb)->sacked & TCPCB_SACKED_ACKED)
+ return;
+
+ /* Next skb is out of window. */
+ if (after(TCP_SKB_CB(next_skb)->end_seq, tp->snd_una+tp->snd_wnd))
+ return;
+
+ /* Punt if not enough space exists in the first SKB for
+ * the data in the second, or the total combined payload
+ * would exceed the MSS.
+ */
+ if ((next_skb_size > skb_tailroom(skb)) ||
+ ((skb_size + next_skb_size) > mss_now))
+ return;
+
+ /* Ok. We will be able to collapse the packet. */
+ __skb_unlink(next_skb, next_skb->list);
+
+ memcpy(skb_put(skb, next_skb_size), next_skb->data, next_skb_size);
+
+ if (next_skb->ip_summed == CHECKSUM_HW)
+ skb->ip_summed = CHECKSUM_HW;
+
+ if (skb->ip_summed != CHECKSUM_HW)
+ skb->csum = csum_block_add(skb->csum, next_skb->csum, skb_size);
+
+ /* Update sequence range on original skb. */
+ TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(next_skb)->end_seq;
+
+ /* Merge over control information. */
+ flags |= TCP_SKB_CB(next_skb)->flags; /* This moves PSH/FIN etc. over */
+ TCP_SKB_CB(skb)->flags = flags;
+
+ /* All done, get rid of second SKB and account for it so
+ * packet counting does not break.
+ */
+ TCP_SKB_CB(skb)->sacked |= TCP_SKB_CB(next_skb)->sacked&(TCPCB_EVER_RETRANS|TCPCB_AT_TAIL);
+ if (TCP_SKB_CB(next_skb)->sacked&TCPCB_SACKED_RETRANS)
+ tp->retrans_out--;
+ if (TCP_SKB_CB(next_skb)->sacked&TCPCB_LOST) {
+ tp->lost_out--;
+ tp->left_out--;
+ }
+ /* Reno case is special. Sigh... */
+ if (!tp->sack_ok && tp->sacked_out) {
+ tp->sacked_out--;
+ tp->left_out--;
+ }
+
+ /* Not quite right: it can be > snd.fack, but
+ * it is better to underestimate fackets.
+ */
+ if (tp->fackets_out)
+ tp->fackets_out--;
+ tcp_free_skb(sk, next_skb);
+ tp->packets_out--;
+ }
+}
+
+/* Do a simple retransmit without using the backoff mechanisms in
+ * tcp_timer. This is used for path mtu discovery.
+ * The socket is already locked here.
+ */
+void tcp_simple_retransmit(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sk_buff *skb;
+ unsigned int mss = tcp_current_mss(sk);
+ int lost = 0;
+
+ for_retrans_queue(skb, sk, tp) {
+ if (skb->len > mss &&
+ !(TCP_SKB_CB(skb)->sacked&TCPCB_SACKED_ACKED)) {
+ if (TCP_SKB_CB(skb)->sacked&TCPCB_SACKED_RETRANS) {
+ TCP_SKB_CB(skb)->sacked &= ~TCPCB_SACKED_RETRANS;
+ tp->retrans_out--;
+ }
+ if (!(TCP_SKB_CB(skb)->sacked&TCPCB_LOST)) {
+ TCP_SKB_CB(skb)->sacked |= TCPCB_LOST;
+ tp->lost_out++;
+ lost = 1;
+ }
+ }
+ }
+
+ if (!lost)
+ return;
+
+ tcp_sync_left_out(tp);
+
+ /* Don't muck with the congestion window here.
+ * Reason is that we do not increase amount of _data_
+ * in network, but units changed and effective
+ * cwnd/ssthresh really reduced now.
+ */
+ if (tp->ca_state != TCP_CA_Loss) {
+ tp->high_seq = tp->snd_nxt;
+ tp->snd_ssthresh = tcp_current_ssthresh(tp);
+ tp->prior_ssthresh = 0;
+ tp->undo_marker = 0;
+ tcp_set_ca_state(tp, TCP_CA_Loss);
+ }
+ tcp_xmit_retransmit_queue(sk);
+}
+
+/* This retransmits one SKB. Policy decisions and retransmit queue
+ * state updates are done by the caller. Returns non-zero if an
+ * error occurred which prevented the send.
+ */
+int tcp_retransmit_skb(struct sock *sk, struct sk_buff *skb)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ unsigned int cur_mss = tcp_current_mss(sk);
+ int err;
+
+ /* Do not sent more than we queued. 1/4 is reserved for possible
+ * copying overhead: frgagmentation, tunneling, mangling etc.
+ */
+ if (atomic_read(&sk->wmem_alloc) > min(sk->wmem_queued+(sk->wmem_queued>>2),sk->sndbuf))
+ return -EAGAIN;
+
+ /* If receiver has shrunk his window, and skb is out of
+ * new window, do not retransmit it. The exception is the
+ * case, when window is shrunk to zero. In this case
+ * our retransmit serves as a zero window probe.
+ */
+ if (!before(TCP_SKB_CB(skb)->seq, tp->snd_una+tp->snd_wnd)
+ && TCP_SKB_CB(skb)->seq != tp->snd_una)
+ return -EAGAIN;
+
+ if(skb->len > cur_mss) {
+ if(tcp_fragment(sk, skb, cur_mss))
+ return -ENOMEM; /* We'll try again later. */
+
+ /* New SKB created, account for it. */
+ tp->packets_out++;
+ }
+
+ /* Collapse two adjacent packets if worthwhile and we can. */
+ if(!(TCP_SKB_CB(skb)->flags & TCPCB_FLAG_SYN) &&
+ (skb->len < (cur_mss >> 1)) &&
+ (skb->next != tp->send_head) &&
+ (skb->next != (struct sk_buff *)&sk->write_queue) &&
+ (skb_shinfo(skb)->nr_frags == 0 && skb_shinfo(skb->next)->nr_frags == 0) &&
+ (sysctl_tcp_retrans_collapse != 0))
+ tcp_retrans_try_collapse(sk, skb, cur_mss);
+
+ if(tp->af_specific->rebuild_header(sk))
+ return -EHOSTUNREACH; /* Routing failure or similar. */
+
+ /* Some Solaris stacks overoptimize and ignore the FIN on a
+ * retransmit when old data is attached. So strip it off
+ * since it is cheap to do so and saves bytes on the network.
+ */
+ if(skb->len > 0 &&
+ (TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN) &&
+ tp->snd_una == (TCP_SKB_CB(skb)->end_seq - 1)) {
+ if (!pskb_trim(skb, 0)) {
+ TCP_SKB_CB(skb)->seq = TCP_SKB_CB(skb)->end_seq - 1;
+ skb->ip_summed = CHECKSUM_NONE;
+ skb->csum = 0;
+ }
+ }
+
+ /* Make a copy, if the first transmission SKB clone we made
+ * is still in somebody's hands, else make a clone.
+ */
+ TCP_SKB_CB(skb)->when = tcp_time_stamp;
+
+ err = tcp_transmit_skb(sk, (skb_cloned(skb) ?
+ pskb_copy(skb, GFP_ATOMIC):
+ skb_clone(skb, GFP_ATOMIC)));
+
+ if (err == 0) {
+ /* Update global TCP statistics. */
+ TCP_INC_STATS(TcpRetransSegs);
+
+#if FASTRETRANS_DEBUG > 0
+ if (TCP_SKB_CB(skb)->sacked&TCPCB_SACKED_RETRANS) {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "retrans_out leaked.\n");
+ }
+#endif
+ TCP_SKB_CB(skb)->sacked |= TCPCB_RETRANS;
+ tp->retrans_out++;
+
+ /* Save stamp of the first retransmit. */
+ if (!tp->retrans_stamp)
+ tp->retrans_stamp = TCP_SKB_CB(skb)->when;
+
+ tp->undo_retrans++;
+
+ /* snd_nxt is stored to detect loss of retransmitted segment,
+ * see tcp_input.c tcp_sacktag_write_queue().
+ */
+ TCP_SKB_CB(skb)->ack_seq = tp->snd_nxt;
+ }
+ return err;
+}
+
+/* This gets called after a retransmit timeout, and the initially
+ * retransmitted data is acknowledged. It tries to continue
+ * resending the rest of the retransmit queue, until either
+ * we've sent it all or the congestion window limit is reached.
+ * If doing SACK, the first ACK which comes back for a timeout
+ * based retransmit packet might feed us FACK information again.
+ * If so, we use it to avoid unnecessarily retransmissions.
+ */
+void tcp_xmit_retransmit_queue(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sk_buff *skb;
+ int packet_cnt = tp->lost_out;
+
+ /* First pass: retransmit lost packets. */
+ if (packet_cnt) {
+ for_retrans_queue(skb, sk, tp) {
+ __u8 sacked = TCP_SKB_CB(skb)->sacked;
+
+ if (tcp_packets_in_flight(tp) >= tp->snd_cwnd)
+ return;
+
+ if (sacked&TCPCB_LOST) {
+ if (!(sacked&(TCPCB_SACKED_ACKED|TCPCB_SACKED_RETRANS))) {
+ if (tcp_retransmit_skb(sk, skb))
+ return;
+ if (tp->ca_state != TCP_CA_Loss)
+ NET_INC_STATS_BH(TCPFastRetrans);
+ else
+ NET_INC_STATS_BH(TCPSlowStartRetrans);
+
+ if (skb == skb_peek(&sk->write_queue))
+ tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
+ }
+
+ if (--packet_cnt <= 0)
+ break;
+ }
+ }
+ }
+
+ /* OK, demanded retransmission is finished. */
+
+ /* Forward retransmissions are possible only during Recovery. */
+ if (tp->ca_state != TCP_CA_Recovery)
+ return;
+
+ /* No forward retransmissions in Reno are possible. */
+ if (!tp->sack_ok)
+ return;
+
+ /* Yeah, we have to make difficult choice between forward transmission
+ * and retransmission... Both ways have their merits...
+ *
+ * For now we do not retrnamsit anything, while we have some new
+ * segments to send.
+ */
+
+ if (tcp_may_send_now(sk, tp))
+ return;
+
+ packet_cnt = 0;
+
+ for_retrans_queue(skb, sk, tp) {
+ if(++packet_cnt > tp->fackets_out)
+ break;
+
+ if (tcp_packets_in_flight(tp) >= tp->snd_cwnd)
+ break;
+
+ if(TCP_SKB_CB(skb)->sacked & TCPCB_TAGBITS)
+ continue;
+
+ /* Ok, retransmit it. */
+ if(tcp_retransmit_skb(sk, skb))
+ break;
+
+ if (skb == skb_peek(&sk->write_queue))
+ tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
+
+ NET_INC_STATS_BH(TCPForwardRetrans);
+ }
+}
+
+
+/* Send a fin. The caller locks the socket for us. This cannot be
+ * allowed to fail queueing a FIN frame under any circumstances.
+ */
+void tcp_send_fin(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sk_buff *skb = skb_peek_tail(&sk->write_queue);
+ unsigned int mss_now;
+
+ /* Optimization, tack on the FIN if we have a queue of
+ * unsent frames. But be careful about outgoing SACKS
+ * and IP options.
+ */
+ mss_now = tcp_current_mss(sk);
+
+ if(tp->send_head != NULL) {
+ TCP_SKB_CB(skb)->flags |= TCPCB_FLAG_FIN;
+ TCP_SKB_CB(skb)->end_seq++;
+ tp->write_seq++;
+ } else {
+ /* Socket is locked, keep trying until memory is available. */
+ for (;;) {
+ skb = alloc_skb(MAX_TCP_HEADER, GFP_KERNEL);
+ if (skb)
+ break;
+ yield();
+ }
+
+ /* Reserve space for headers and prepare control bits. */
+ skb_reserve(skb, MAX_TCP_HEADER);
+ skb->csum = 0;
+ TCP_SKB_CB(skb)->flags = (TCPCB_FLAG_ACK | TCPCB_FLAG_FIN);
+ TCP_SKB_CB(skb)->sacked = 0;
+
+ /* FIN eats a sequence byte, write_seq advanced by tcp_send_skb(). */
+ TCP_SKB_CB(skb)->seq = tp->write_seq;
+ TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(skb)->seq + 1;
+ tcp_send_skb(sk, skb, 1, mss_now);
+ }
+ __tcp_push_pending_frames(sk, tp, mss_now, 1);
+}
+
+/* We get here when a process closes a file descriptor (either due to
+ * an explicit close() or as a byproduct of exit()'ing) and there
+ * was unread data in the receive queue. This behavior is recommended
+ * by draft-ietf-tcpimpl-prob-03.txt section 3.10. -DaveM
+ */
+void tcp_send_active_reset(struct sock *sk, int priority)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sk_buff *skb;
+
+ /* NOTE: No TCP options attached and we never retransmit this. */
+ skb = alloc_skb(MAX_TCP_HEADER, priority);
+ if (!skb) {
+ NET_INC_STATS(TCPAbortFailed);
+ return;
+ }
+
+ /* Reserve space for headers and prepare control bits. */
+ skb_reserve(skb, MAX_TCP_HEADER);
+ skb->csum = 0;
+ TCP_SKB_CB(skb)->flags = (TCPCB_FLAG_ACK | TCPCB_FLAG_RST);
+ TCP_SKB_CB(skb)->sacked = 0;
+
+ /* Send it off. */
+ TCP_SKB_CB(skb)->seq = tcp_acceptable_seq(sk, tp);
+ TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(skb)->seq;
+ TCP_SKB_CB(skb)->when = tcp_time_stamp;
+ if (tcp_transmit_skb(sk, skb))
+ NET_INC_STATS(TCPAbortFailed);
+}
+
+/* WARNING: This routine must only be called when we have already sent
+ * a SYN packet that crossed the incoming SYN that caused this routine
+ * to get called. If this assumption fails then the initial rcv_wnd
+ * and rcv_wscale values will not be correct.
+ */
+int tcp_send_synack(struct sock *sk)
+{
+ struct sk_buff* skb;
+
+ skb = skb_peek(&sk->write_queue);
+ if (skb == NULL || !(TCP_SKB_CB(skb)->flags&TCPCB_FLAG_SYN)) {
+ printk(KERN_DEBUG "tcp_send_synack: wrong queue state\n");
+ return -EFAULT;
+ }
+ if (!(TCP_SKB_CB(skb)->flags&TCPCB_FLAG_ACK)) {
+ if (skb_cloned(skb)) {
+ struct sk_buff *nskb = skb_copy(skb, GFP_ATOMIC);
+ if (nskb == NULL)
+ return -ENOMEM;
+ __skb_unlink(skb, &sk->write_queue);
+ __skb_queue_head(&sk->write_queue, nskb);
+ tcp_free_skb(sk, skb);
+ tcp_charge_skb(sk, nskb);
+ skb = nskb;
+ }
+
+ TCP_SKB_CB(skb)->flags |= TCPCB_FLAG_ACK;
+ TCP_ECN_send_synack(&sk->tp_pinfo.af_tcp, skb);
+ }
+ TCP_SKB_CB(skb)->when = tcp_time_stamp;
+ return tcp_transmit_skb(sk, skb_clone(skb, GFP_ATOMIC));
+}
+
+/*
+ * Prepare a SYN-ACK.
+ */
+struct sk_buff * tcp_make_synack(struct sock *sk, struct dst_entry *dst,
+ struct open_request *req)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct tcphdr *th;
+ int tcp_header_size;
+ struct sk_buff *skb;
+
+ skb = sock_wmalloc(sk, MAX_TCP_HEADER + 15, 1, GFP_ATOMIC);
+ if (skb == NULL)
+ return NULL;
+
+ /* Reserve space for headers. */
+ skb_reserve(skb, MAX_TCP_HEADER);
+
+ skb->dst = dst_clone(dst);
+
+ tcp_header_size = (sizeof(struct tcphdr) + TCPOLEN_MSS +
+ (req->tstamp_ok ? TCPOLEN_TSTAMP_ALIGNED : 0) +
+ (req->wscale_ok ? TCPOLEN_WSCALE_ALIGNED : 0) +
+ /* SACK_PERM is in the place of NOP NOP of TS */
+ ((req->sack_ok && !req->tstamp_ok) ? TCPOLEN_SACKPERM_ALIGNED : 0));
+ skb->h.th = th = (struct tcphdr *) skb_push(skb, tcp_header_size);
+
+ memset(th, 0, sizeof(struct tcphdr));
+ th->syn = 1;
+ th->ack = 1;
+ TCP_ECN_make_synack(req, th);
+ th->source = sk->sport;
+ th->dest = req->rmt_port;
+ TCP_SKB_CB(skb)->seq = req->snt_isn;
+ TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(skb)->seq + 1;
+ th->seq = htonl(TCP_SKB_CB(skb)->seq);
+ th->ack_seq = htonl(req->rcv_isn + 1);
+ if (req->rcv_wnd == 0) { /* ignored for retransmitted syns */
+ __u8 rcv_wscale;
+ /* Set this up on the first call only */
+ req->window_clamp = tp->window_clamp ? : dst->window;
+ /* tcp_full_space because it is guaranteed to be the first packet */
+ tcp_select_initial_window(tcp_full_space(sk),
+ dst->advmss - (req->tstamp_ok ? TCPOLEN_TSTAMP_ALIGNED : 0),
+ &req->rcv_wnd,
+ &req->window_clamp,
+ req->wscale_ok,
+ &rcv_wscale);
+ req->rcv_wscale = rcv_wscale;
+ }
+
+ /* RFC1323: The window in SYN & SYN/ACK segments is never scaled. */
+ th->window = htons(req->rcv_wnd);
+
+ TCP_SKB_CB(skb)->when = tcp_time_stamp;
+ tcp_syn_build_options((__u32 *)(th + 1), dst->advmss, req->tstamp_ok,
+ req->sack_ok, req->wscale_ok, req->rcv_wscale,
+ TCP_SKB_CB(skb)->when,
+ req->ts_recent);
+
+ skb->csum = 0;
+ th->doff = (tcp_header_size >> 2);
+ TCP_INC_STATS(TcpOutSegs);
+ return skb;
+}
+
+/*
+ * Do all connect socket setups that can be done AF independent.
+ */
+static inline void tcp_connect_init(struct sock *sk)
+{
+ struct dst_entry *dst = __sk_dst_get(sk);
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ /* We'll fix this up when we get a response from the other end.
+ * See tcp_input.c:tcp_rcv_state_process case TCP_SYN_SENT.
+ */
+ tp->tcp_header_len = sizeof(struct tcphdr) +
+ (sysctl_tcp_timestamps ? TCPOLEN_TSTAMP_ALIGNED : 0);
+
+ /* If user gave his TCP_MAXSEG, record it to clamp */
+ if (tp->user_mss)
+ tp->mss_clamp = tp->user_mss;
+ tp->max_window = 0;
+ tcp_sync_mss(sk, dst->pmtu);
+
+ if (!tp->window_clamp)
+ tp->window_clamp = dst->window;
+ tp->advmss = dst->advmss;
+ tcp_initialize_rcv_mss(sk);
+ tcp_ca_init(tp);
+
+ tcp_select_initial_window(tcp_full_space(sk),
+ tp->advmss - (tp->ts_recent_stamp ? tp->tcp_header_len - sizeof(struct tcphdr) : 0),
+ &tp->rcv_wnd,
+ &tp->window_clamp,
+ sysctl_tcp_window_scaling,
+ &tp->rcv_wscale);
+
+ tp->rcv_ssthresh = tp->rcv_wnd;
+
+ sk->err = 0;
+ sk->done = 0;
+ tp->snd_wnd = 0;
+ tcp_init_wl(tp, tp->write_seq, 0);
+ tp->snd_una = tp->write_seq;
+ tp->snd_sml = tp->write_seq;
+ tp->rcv_nxt = 0;
+ tp->rcv_wup = 0;
+ tp->copied_seq = 0;
+
+ tp->rto = TCP_TIMEOUT_INIT;
+ tp->retransmits = 0;
+ tcp_clear_retrans(tp);
+}
+
+/*
+ * Build a SYN and send it off.
+ */
+int tcp_connect(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sk_buff *buff;
+
+ tcp_connect_init(sk);
+
+ buff = alloc_skb(MAX_TCP_HEADER + 15, sk->allocation);
+ if (unlikely(buff == NULL))
+ return -ENOBUFS;
+
+ /* Reserve space for headers. */
+ skb_reserve(buff, MAX_TCP_HEADER);
+
+ TCP_SKB_CB(buff)->flags = TCPCB_FLAG_SYN;
+ TCP_ECN_send_syn(tp, buff);
+ TCP_SKB_CB(buff)->sacked = 0;
+ buff->csum = 0;
+ TCP_SKB_CB(buff)->seq = tp->write_seq++;
+ TCP_SKB_CB(buff)->end_seq = tp->write_seq;
+ tp->snd_nxt = tp->write_seq;
+ tp->pushed_seq = tp->write_seq;
+ tcp_ca_init(tp);
+
+ /* Send it off. */
+ TCP_SKB_CB(buff)->when = tcp_time_stamp;
+ tp->retrans_stamp = TCP_SKB_CB(buff)->when;
+ __skb_queue_tail(&sk->write_queue, buff);
+ tcp_charge_skb(sk, buff);
+ tp->packets_out++;
+ tcp_transmit_skb(sk, skb_clone(buff, GFP_KERNEL));
+ TCP_INC_STATS(TcpActiveOpens);
+
+ /* Timer for repeating the SYN until an answer. */
+ tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
+ return 0;
+}
+
+/* Send out a delayed ack, the caller does the policy checking
+ * to see if we should even be here. See tcp_input.c:tcp_ack_snd_check()
+ * for details.
+ */
+void tcp_send_delayed_ack(struct sock *sk)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ int ato = tp->ack.ato;
+ unsigned long timeout;
+
+ if (ato > TCP_DELACK_MIN) {
+ int max_ato = HZ/2;
+
+ if (tp->ack.pingpong || (tp->ack.pending&TCP_ACK_PUSHED))
+ max_ato = TCP_DELACK_MAX;
+
+ /* Slow path, intersegment interval is "high". */
+
+ /* If some rtt estimate is known, use it to bound delayed ack.
+ * Do not use tp->rto here, use results of rtt measurements
+ * directly.
+ */
+ if (tp->srtt) {
+ int rtt = max(tp->srtt>>3, TCP_DELACK_MIN);
+
+ if (rtt < max_ato)
+ max_ato = rtt;
+ }
+
+ ato = min(ato, max_ato);
+ }
+
+ /* Stay within the limit we were given */
+ timeout = jiffies + ato;
+
+ /* Use new timeout only if there wasn't a older one earlier. */
+ if (tp->ack.pending&TCP_ACK_TIMER) {
+ /* If delack timer was blocked or is about to expire,
+ * send ACK now.
+ */
+ if (tp->ack.blocked || time_before_eq(tp->ack.timeout, jiffies+(ato>>2))) {
+ tcp_send_ack(sk);
+ return;
+ }
+
+ if (!time_before(timeout, tp->ack.timeout))
+ timeout = tp->ack.timeout;
+ }
+ tp->ack.pending |= TCP_ACK_SCHED|TCP_ACK_TIMER;
+ tp->ack.timeout = timeout;
+ if (!mod_timer(&tp->delack_timer, timeout))
+ sock_hold(sk);
+}
+
+/* This routine sends an ack and also updates the window. */
+void tcp_send_ack(struct sock *sk)
+{
+ /* If we have been reset, we may not send again. */
+ if(sk->state != TCP_CLOSE) {
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sk_buff *buff;
+
+ /* We are not putting this on the write queue, so
+ * tcp_transmit_skb() will set the ownership to this
+ * sock.
+ */
+ buff = alloc_skb(MAX_TCP_HEADER, GFP_ATOMIC);
+ if (buff == NULL) {
+ tcp_schedule_ack(tp);
+ tp->ack.ato = TCP_ATO_MIN;
+ tcp_reset_xmit_timer(sk, TCP_TIME_DACK, TCP_DELACK_MAX);
+ return;
+ }
+
+ /* Reserve space for headers and prepare control bits. */
+ skb_reserve(buff, MAX_TCP_HEADER);
+ buff->csum = 0;
+ TCP_SKB_CB(buff)->flags = TCPCB_FLAG_ACK;
+ TCP_SKB_CB(buff)->sacked = 0;
+
+ /* Send it off, this clears delayed acks for us. */
+ TCP_SKB_CB(buff)->seq = TCP_SKB_CB(buff)->end_seq = tcp_acceptable_seq(sk, tp);
+ TCP_SKB_CB(buff)->when = tcp_time_stamp;
+ tcp_transmit_skb(sk, buff);
+ }
+}
+
+/* This routine sends a packet with an out of date sequence
+ * number. It assumes the other end will try to ack it.
+ *
+ * Question: what should we make while urgent mode?
+ * 4.4BSD forces sending single byte of data. We cannot send
+ * out of window data, because we have SND.NXT==SND.MAX...
+ *
+ * Current solution: to send TWO zero-length segments in urgent mode:
+ * one is with SEG.SEQ=SND.UNA to deliver urgent pointer, another is
+ * out-of-date with SND.UNA-1 to probe window.
+ */
+static int tcp_xmit_probe_skb(struct sock *sk, int urgent)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sk_buff *skb;
+
+ /* We don't queue it, tcp_transmit_skb() sets ownership. */
+ skb = alloc_skb(MAX_TCP_HEADER, GFP_ATOMIC);
+ if (skb == NULL)
+ return -1;
+
+ /* Reserve space for headers and set control bits. */
+ skb_reserve(skb, MAX_TCP_HEADER);
+ skb->csum = 0;
+ TCP_SKB_CB(skb)->flags = TCPCB_FLAG_ACK;
+ TCP_SKB_CB(skb)->sacked = urgent;
+
+ /* Use a previous sequence. This should cause the other
+ * end to send an ack. Don't queue or clone SKB, just
+ * send it.
+ */
+ TCP_SKB_CB(skb)->seq = urgent ? tp->snd_una : tp->snd_una - 1;
+ TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(skb)->seq;
+ TCP_SKB_CB(skb)->when = tcp_time_stamp;
+ return tcp_transmit_skb(sk, skb);
+}
+
+int tcp_write_wakeup(struct sock *sk)
+{
+ if (sk->state != TCP_CLOSE) {
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sk_buff *skb;
+
+ if ((skb = tp->send_head) != NULL &&
+ before(TCP_SKB_CB(skb)->seq, tp->snd_una+tp->snd_wnd)) {
+ int err;
+ int mss = tcp_current_mss(sk);
+ int seg_size = tp->snd_una+tp->snd_wnd-TCP_SKB_CB(skb)->seq;
+
+ if (before(tp->pushed_seq, TCP_SKB_CB(skb)->end_seq))
+ tp->pushed_seq = TCP_SKB_CB(skb)->end_seq;
+
+ /* We are probing the opening of a window
+ * but the window size is != 0
+ * must have been a result SWS avoidance ( sender )
+ */
+ if (seg_size < TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq ||
+ skb->len > mss) {
+ seg_size = min(seg_size, mss);
+ TCP_SKB_CB(skb)->flags |= TCPCB_FLAG_PSH;
+ if (tcp_fragment(sk, skb, seg_size))
+ return -1;
+ }
+ TCP_SKB_CB(skb)->flags |= TCPCB_FLAG_PSH;
+ TCP_SKB_CB(skb)->when = tcp_time_stamp;
+ err = tcp_transmit_skb(sk, skb_clone(skb, GFP_ATOMIC));
+ if (!err) {
+ update_send_head(sk, tp, skb);
+ }
+ return err;
+ } else {
+ if (tp->urg_mode &&
+ between(tp->snd_up, tp->snd_una+1, tp->snd_una+0xFFFF))
+ tcp_xmit_probe_skb(sk, TCPCB_URG);
+ return tcp_xmit_probe_skb(sk, 0);
+ }
+ }
+ return -1;
+}
+
+/* A window probe timeout has occurred. If window is not closed send
+ * a partial packet else a zero probe.
+ */
+void tcp_send_probe0(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int err;
+
+ err = tcp_write_wakeup(sk);
+
+ if (tp->packets_out || !tp->send_head) {
+ /* Cancel probe timer, if it is not required. */
+ tp->probes_out = 0;
+ tp->backoff = 0;
+ return;
+ }
+
+ if (err <= 0) {
+ if (tp->backoff < sysctl_tcp_retries2)
+ tp->backoff++;
+ tp->probes_out++;
+ tcp_reset_xmit_timer (sk, TCP_TIME_PROBE0,
+ min(tp->rto << tp->backoff, TCP_RTO_MAX));
+ } else {
+ /* If packet was not sent due to local congestion,
+ * do not backoff and do not remember probes_out.
+ * Let local senders to fight for local resources.
+ *
+ * Use accumulated backoff yet.
+ */
+ if (!tp->probes_out)
+ tp->probes_out=1;
+ tcp_reset_xmit_timer (sk, TCP_TIME_PROBE0,
+ min(tp->rto << tp->backoff, TCP_RESOURCE_PROBE_INTERVAL));
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/tcp_timer.c b/uClinux-2.4.31-uc0/net/ipv4/tcp_timer.c
new file mode 100644
index 0000000..73f9c18
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/tcp_timer.c
@@ -0,0 +1,655 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * Implementation of the Transmission Control Protocol(TCP).
+ *
+ * Version: $Id: tcp_timer.c,v 1.87 2001/09/21 21:27:34 davem Exp $
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Mark Evans, <evansmp@uhura.aston.ac.uk>
+ * Corey Minyard <wf-rch!minyard@relay.EU.net>
+ * Florian La Roche, <flla@stud.uni-sb.de>
+ * Charles Hedrick, <hedrick@klinzhai.rutgers.edu>
+ * Linus Torvalds, <torvalds@cs.helsinki.fi>
+ * Alan Cox, <gw4pts@gw4pts.ampr.org>
+ * Matthew Dillon, <dillon@apollo.west.oic.com>
+ * Arnt Gulbrandsen, <agulbra@nvg.unit.no>
+ * Jorge Cwik, <jorge@laser.satlink.net>
+ */
+
+#include <net/tcp.h>
+
+int sysctl_tcp_syn_retries = TCP_SYN_RETRIES;
+int sysctl_tcp_synack_retries = TCP_SYNACK_RETRIES;
+int sysctl_tcp_keepalive_time = TCP_KEEPALIVE_TIME;
+int sysctl_tcp_keepalive_probes = TCP_KEEPALIVE_PROBES;
+int sysctl_tcp_keepalive_intvl = TCP_KEEPALIVE_INTVL;
+int sysctl_tcp_retries1 = TCP_RETR1;
+int sysctl_tcp_retries2 = TCP_RETR2;
+int sysctl_tcp_orphan_retries;
+
+static void tcp_write_timer(unsigned long);
+static void tcp_delack_timer(unsigned long);
+static void tcp_keepalive_timer (unsigned long data);
+
+const char timer_bug_msg[] = KERN_DEBUG "tcpbug: unknown timer value\n";
+
+/*
+ * Using different timers for retransmit, delayed acks and probes
+ * We may wish use just one timer maintaining a list of expire jiffies
+ * to optimize.
+ */
+
+void tcp_init_xmit_timers(struct sock *sk)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+
+ init_timer(&tp->retransmit_timer);
+ tp->retransmit_timer.function=&tcp_write_timer;
+ tp->retransmit_timer.data = (unsigned long) sk;
+ tp->pending = 0;
+
+ init_timer(&tp->delack_timer);
+ tp->delack_timer.function=&tcp_delack_timer;
+ tp->delack_timer.data = (unsigned long) sk;
+ tp->ack.pending = 0;
+
+ init_timer(&sk->timer);
+ sk->timer.function=&tcp_keepalive_timer;
+ sk->timer.data = (unsigned long) sk;
+}
+
+void tcp_clear_xmit_timers(struct sock *sk)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+
+ tp->pending = 0;
+ if (timer_pending(&tp->retransmit_timer) &&
+ del_timer(&tp->retransmit_timer))
+ __sock_put(sk);
+
+ tp->ack.pending = 0;
+ tp->ack.blocked = 0;
+ if (timer_pending(&tp->delack_timer) &&
+ del_timer(&tp->delack_timer))
+ __sock_put(sk);
+
+ if(timer_pending(&sk->timer) && del_timer(&sk->timer))
+ __sock_put(sk);
+}
+
+static void tcp_write_err(struct sock *sk)
+{
+ sk->err = sk->err_soft ? : ETIMEDOUT;
+ sk->error_report(sk);
+
+ tcp_done(sk);
+ NET_INC_STATS_BH(TCPAbortOnTimeout);
+}
+
+/* Do not allow orphaned sockets to eat all our resources.
+ * This is direct violation of TCP specs, but it is required
+ * to prevent DoS attacks. It is called when a retransmission timeout
+ * or zero probe timeout occurs on orphaned socket.
+ *
+ * Criterium is still not confirmed experimentally and may change.
+ * We kill the socket, if:
+ * 1. If number of orphaned sockets exceeds an administratively configured
+ * limit.
+ * 2. If we have strong memory pressure.
+ */
+static int tcp_out_of_resources(struct sock *sk, int do_reset)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int orphans = atomic_read(&tcp_orphan_count);
+
+ /* If peer does not open window for long time, or did not transmit
+ * anything for long time, penalize it. */
+ if ((s32)(tcp_time_stamp - tp->lsndtime) > 2*TCP_RTO_MAX || !do_reset)
+ orphans <<= 1;
+
+ /* If some dubious ICMP arrived, penalize even more. */
+ if (sk->err_soft)
+ orphans <<= 1;
+
+ if (orphans >= sysctl_tcp_max_orphans ||
+ (sk->wmem_queued > SOCK_MIN_SNDBUF &&
+ atomic_read(&tcp_memory_allocated) > sysctl_tcp_mem[2])) {
+ if (net_ratelimit())
+ printk(KERN_INFO "Out of socket memory\n");
+
+ /* Catch exceptional cases, when connection requires reset.
+ * 1. Last segment was sent recently. */
+ if ((s32)(tcp_time_stamp - tp->lsndtime) <= TCP_TIMEWAIT_LEN ||
+ /* 2. Window is closed. */
+ (!tp->snd_wnd && !tp->packets_out))
+ do_reset = 1;
+ if (do_reset)
+ tcp_send_active_reset(sk, GFP_ATOMIC);
+ tcp_done(sk);
+ NET_INC_STATS_BH(TCPAbortOnMemory);
+ return 1;
+ }
+ return 0;
+}
+
+/* Calculate maximal number or retries on an orphaned socket. */
+static int tcp_orphan_retries(struct sock *sk, int alive)
+{
+ int retries = sysctl_tcp_orphan_retries; /* May be zero. */
+
+ /* We know from an ICMP that something is wrong. */
+ if (sk->err_soft && !alive)
+ retries = 0;
+
+ /* However, if socket sent something recently, select some safe
+ * number of retries. 8 corresponds to >100 seconds with minimal
+ * RTO of 200msec. */
+ if (retries == 0 && alive)
+ retries = 8;
+ return retries;
+}
+
+/* A write timeout has occurred. Process the after effects. */
+static int tcp_write_timeout(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ int retry_until;
+
+ if ((1<<sk->state)&(TCPF_SYN_SENT|TCPF_SYN_RECV)) {
+ if (tp->retransmits)
+ dst_negative_advice(&sk->dst_cache);
+ retry_until = tp->syn_retries ? : sysctl_tcp_syn_retries;
+ } else {
+ if (tp->retransmits >= sysctl_tcp_retries1) {
+ /* NOTE. draft-ietf-tcpimpl-pmtud-01.txt requires pmtu black
+ hole detection. :-(
+
+ It is place to make it. It is not made. I do not want
+ to make it. It is disguisting. It does not work in any
+ case. Let me to cite the same draft, which requires for
+ us to implement this:
+
+ "The one security concern raised by this memo is that ICMP black holes
+ are often caused by over-zealous security administrators who block
+ all ICMP messages. It is vitally important that those who design and
+ deploy security systems understand the impact of strict filtering on
+ upper-layer protocols. The safest web site in the world is worthless
+ if most TCP implementations cannot transfer data from it. It would
+ be far nicer to have all of the black holes fixed rather than fixing
+ all of the TCP implementations."
+
+ Golden words :-).
+ */
+
+ dst_negative_advice(&sk->dst_cache);
+ }
+
+ retry_until = sysctl_tcp_retries2;
+ if (sk->dead) {
+ int alive = (tp->rto < TCP_RTO_MAX);
+
+ retry_until = tcp_orphan_retries(sk, alive);
+
+ if (tcp_out_of_resources(sk, alive || tp->retransmits < retry_until))
+ return 1;
+ }
+ }
+
+ if (tp->retransmits >= retry_until) {
+ /* Has it gone just too far? */
+ tcp_write_err(sk);
+ return 1;
+ }
+ return 0;
+}
+
+static void tcp_delack_timer(unsigned long data)
+{
+ struct sock *sk = (struct sock*)data;
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ bh_lock_sock(sk);
+ if (sk->lock.users) {
+ /* Try again later. */
+ tp->ack.blocked = 1;
+ NET_INC_STATS_BH(DelayedACKLocked);
+ if (!mod_timer(&tp->delack_timer, jiffies + TCP_DELACK_MIN))
+ sock_hold(sk);
+ goto out_unlock;
+ }
+
+ tcp_mem_reclaim(sk);
+
+ if (sk->state == TCP_CLOSE || !(tp->ack.pending&TCP_ACK_TIMER))
+ goto out;
+
+ if ((long)(tp->ack.timeout - jiffies) > 0) {
+ if (!mod_timer(&tp->delack_timer, tp->ack.timeout))
+ sock_hold(sk);
+ goto out;
+ }
+ tp->ack.pending &= ~TCP_ACK_TIMER;
+
+ if (skb_queue_len(&tp->ucopy.prequeue)) {
+ struct sk_buff *skb;
+
+ net_statistics[smp_processor_id()*2].TCPSchedulerFailed += skb_queue_len(&tp->ucopy.prequeue);
+
+ while ((skb = __skb_dequeue(&tp->ucopy.prequeue)) != NULL)
+ sk->backlog_rcv(sk, skb);
+
+ tp->ucopy.memory = 0;
+ }
+
+ if (tcp_ack_scheduled(tp)) {
+ if (!tp->ack.pingpong) {
+ /* Delayed ACK missed: inflate ATO. */
+ tp->ack.ato = min(tp->ack.ato << 1, tp->rto);
+ } else {
+ /* Delayed ACK missed: leave pingpong mode and
+ * deflate ATO.
+ */
+ tp->ack.pingpong = 0;
+ tp->ack.ato = TCP_ATO_MIN;
+ }
+ tcp_send_ack(sk);
+ NET_INC_STATS_BH(DelayedACKs);
+ }
+ TCP_CHECK_TIMER(sk);
+
+out:
+ if (tcp_memory_pressure)
+ tcp_mem_reclaim(sk);
+out_unlock:
+ bh_unlock_sock(sk);
+ sock_put(sk);
+}
+
+static void tcp_probe_timer(struct sock *sk)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ int max_probes;
+
+ if (tp->packets_out || !tp->send_head) {
+ tp->probes_out = 0;
+ return;
+ }
+
+ /* *WARNING* RFC 1122 forbids this
+ *
+ * It doesn't AFAIK, because we kill the retransmit timer -AK
+ *
+ * FIXME: We ought not to do it, Solaris 2.5 actually has fixing
+ * this behaviour in Solaris down as a bug fix. [AC]
+ *
+ * Let me to explain. probes_out is zeroed by incoming ACKs
+ * even if they advertise zero window. Hence, connection is killed only
+ * if we received no ACKs for normal connection timeout. It is not killed
+ * only because window stays zero for some time, window may be zero
+ * until armageddon and even later. We are in full accordance
+ * with RFCs, only probe timer combines both retransmission timeout
+ * and probe timeout in one bottle. --ANK
+ */
+ max_probes = sysctl_tcp_retries2;
+
+ if (sk->dead) {
+ int alive = ((tp->rto<<tp->backoff) < TCP_RTO_MAX);
+
+ max_probes = tcp_orphan_retries(sk, alive);
+
+ if (tcp_out_of_resources(sk, alive || tp->probes_out <= max_probes))
+ return;
+ }
+
+ if (tp->probes_out > max_probes) {
+ tcp_write_err(sk);
+ } else {
+ /* Only send another probe if we didn't close things up. */
+ tcp_send_probe0(sk);
+ }
+}
+
+/*
+ * The TCP retransmit timer.
+ */
+
+static void tcp_retransmit_timer(struct sock *sk)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+
+ if (tp->packets_out == 0)
+ goto out;
+
+ BUG_TRAP(!skb_queue_empty(&sk->write_queue));
+
+ if (tp->snd_wnd == 0 && !sk->dead &&
+ !((1<<sk->state)&(TCPF_SYN_SENT|TCPF_SYN_RECV))) {
+ /* Receiver dastardly shrinks window. Our retransmits
+ * become zero probes, but we should not timeout this
+ * connection. If the socket is an orphan, time it out,
+ * we cannot allow such beasts to hang infinitely.
+ */
+#ifdef TCP_DEBUG
+ if (net_ratelimit())
+ printk(KERN_DEBUG "TCP: Treason uncloaked! Peer %u.%u.%u.%u:%u/%u shrinks window %u:%u. Repaired.\n",
+ NIPQUAD(sk->daddr), htons(sk->dport), sk->num,
+ tp->snd_una, tp->snd_nxt);
+#endif
+ if (tcp_time_stamp - tp->rcv_tstamp > TCP_RTO_MAX) {
+ tcp_write_err(sk);
+ goto out;
+ }
+ tcp_enter_loss(sk, 0);
+ tcp_retransmit_skb(sk, skb_peek(&sk->write_queue));
+ __sk_dst_reset(sk);
+ goto out_reset_timer;
+ }
+
+ if (tcp_write_timeout(sk))
+ goto out;
+
+ if (tp->retransmits == 0) {
+ if (tp->ca_state == TCP_CA_Disorder || tp->ca_state == TCP_CA_Recovery) {
+ if (tp->sack_ok) {
+ if (tp->ca_state == TCP_CA_Recovery)
+ NET_INC_STATS_BH(TCPSackRecoveryFail);
+ else
+ NET_INC_STATS_BH(TCPSackFailures);
+ } else {
+ if (tp->ca_state == TCP_CA_Recovery)
+ NET_INC_STATS_BH(TCPRenoRecoveryFail);
+ else
+ NET_INC_STATS_BH(TCPRenoFailures);
+ }
+ } else if (tp->ca_state == TCP_CA_Loss) {
+ NET_INC_STATS_BH(TCPLossFailures);
+ } else {
+ NET_INC_STATS_BH(TCPTimeouts);
+ }
+ }
+
+ if (tcp_use_frto(sk)) {
+ tcp_enter_frto(sk);
+ } else {
+ tcp_enter_loss(sk, 0);
+ }
+
+ if (tcp_retransmit_skb(sk, skb_peek(&sk->write_queue)) > 0) {
+ /* Retransmission failed because of local congestion,
+ * do not backoff.
+ */
+ if (!tp->retransmits)
+ tp->retransmits=1;
+ tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS,
+ min(tp->rto, TCP_RESOURCE_PROBE_INTERVAL));
+ goto out;
+ }
+
+ /* Increase the timeout each time we retransmit. Note that
+ * we do not increase the rtt estimate. rto is initialized
+ * from rtt, but increases here. Jacobson (SIGCOMM 88) suggests
+ * that doubling rto each time is the least we can get away with.
+ * In KA9Q, Karn uses this for the first few times, and then
+ * goes to quadratic. netBSD doubles, but only goes up to *64,
+ * and clamps at 1 to 64 sec afterwards. Note that 120 sec is
+ * defined in the protocol as the maximum possible RTT. I guess
+ * we'll have to use something other than TCP to talk to the
+ * University of Mars.
+ *
+ * PAWS allows us longer timeouts and large windows, so once
+ * implemented ftp to mars will work nicely. We will have to fix
+ * the 120 second clamps though!
+ */
+ tp->backoff++;
+ tp->retransmits++;
+
+out_reset_timer:
+ tp->rto = min(tp->rto << 1, TCP_RTO_MAX);
+ tcp_reset_xmit_timer(sk, TCP_TIME_RETRANS, tp->rto);
+ if (tp->retransmits > sysctl_tcp_retries1)
+ __sk_dst_reset(sk);
+
+out:;
+}
+
+static void tcp_write_timer(unsigned long data)
+{
+ struct sock *sk = (struct sock*)data;
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ int event;
+
+ bh_lock_sock(sk);
+ if (sk->lock.users) {
+ /* Try again later */
+ if (!mod_timer(&tp->retransmit_timer, jiffies + (HZ/20)))
+ sock_hold(sk);
+ goto out_unlock;
+ }
+
+ if (sk->state == TCP_CLOSE || !tp->pending)
+ goto out;
+
+ if ((long)(tp->timeout - jiffies) > 0) {
+ if (!mod_timer(&tp->retransmit_timer, tp->timeout))
+ sock_hold(sk);
+ goto out;
+ }
+
+ event = tp->pending;
+ tp->pending = 0;
+
+ switch (event) {
+ case TCP_TIME_RETRANS:
+ tcp_retransmit_timer(sk);
+ break;
+ case TCP_TIME_PROBE0:
+ tcp_probe_timer(sk);
+ break;
+ }
+ TCP_CHECK_TIMER(sk);
+
+out:
+ tcp_mem_reclaim(sk);
+out_unlock:
+ bh_unlock_sock(sk);
+ sock_put(sk);
+}
+
+/*
+ * Timer for listening sockets
+ */
+
+static void tcp_synack_timer(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct tcp_listen_opt *lopt = tp->listen_opt;
+ int max_retries = tp->syn_retries ? : sysctl_tcp_synack_retries;
+ int thresh = max_retries;
+ unsigned long now = jiffies;
+ struct open_request **reqp, *req;
+ int i, budget;
+
+ if (lopt == NULL || lopt->qlen == 0)
+ return;
+
+ /* Normally all the openreqs are young and become mature
+ * (i.e. converted to established socket) for first timeout.
+ * If synack was not acknowledged for 3 seconds, it means
+ * one of the following things: synack was lost, ack was lost,
+ * rtt is high or nobody planned to ack (i.e. synflood).
+ * When server is a bit loaded, queue is populated with old
+ * open requests, reducing effective size of queue.
+ * When server is well loaded, queue size reduces to zero
+ * after several minutes of work. It is not synflood,
+ * it is normal operation. The solution is pruning
+ * too old entries overriding normal timeout, when
+ * situation becomes dangerous.
+ *
+ * Essentially, we reserve half of room for young
+ * embrions; and abort old ones without pity, if old
+ * ones are about to clog our table.
+ */
+ if (lopt->qlen>>(lopt->max_qlen_log-1)) {
+ int young = (lopt->qlen_young<<1);
+
+ while (thresh > 2) {
+ if (lopt->qlen < young)
+ break;
+ thresh--;
+ young <<= 1;
+ }
+ }
+
+ if (tp->defer_accept)
+ max_retries = tp->defer_accept;
+
+ budget = 2*(TCP_SYNQ_HSIZE/(TCP_TIMEOUT_INIT/TCP_SYNQ_INTERVAL));
+ i = lopt->clock_hand;
+
+ do {
+ reqp=&lopt->syn_table[i];
+ while ((req = *reqp) != NULL) {
+ if ((long)(now - req->expires) >= 0) {
+ if ((req->retrans < thresh ||
+ (req->acked && req->retrans < max_retries))
+ && !req->class->rtx_syn_ack(sk, req, NULL)) {
+ unsigned long timeo;
+
+ if (req->retrans++ == 0)
+ lopt->qlen_young--;
+ timeo = min((TCP_TIMEOUT_INIT << req->retrans),
+ TCP_RTO_MAX);
+ req->expires = now + timeo;
+ reqp = &req->dl_next;
+ continue;
+ }
+
+ /* Drop this request */
+ write_lock(&tp->syn_wait_lock);
+ *reqp = req->dl_next;
+ write_unlock(&tp->syn_wait_lock);
+ lopt->qlen--;
+ if (req->retrans == 0)
+ lopt->qlen_young--;
+ tcp_openreq_free(req);
+ continue;
+ }
+ reqp = &req->dl_next;
+ }
+
+ i = (i+1)&(TCP_SYNQ_HSIZE-1);
+
+ } while (--budget > 0);
+
+ lopt->clock_hand = i;
+
+ if (lopt->qlen)
+ tcp_reset_keepalive_timer(sk, TCP_SYNQ_INTERVAL);
+}
+
+void tcp_delete_keepalive_timer (struct sock *sk)
+{
+ if (timer_pending(&sk->timer) && del_timer (&sk->timer))
+ __sock_put(sk);
+}
+
+void tcp_reset_keepalive_timer (struct sock *sk, unsigned long len)
+{
+ if (!mod_timer(&sk->timer, jiffies+len))
+ sock_hold(sk);
+}
+
+void tcp_set_keepalive(struct sock *sk, int val)
+{
+ if ((1<<sk->state)&(TCPF_CLOSE|TCPF_LISTEN))
+ return;
+
+ if (val && !sk->keepopen)
+ tcp_reset_keepalive_timer(sk, keepalive_time_when(&sk->tp_pinfo.af_tcp));
+ else if (!val)
+ tcp_delete_keepalive_timer(sk);
+}
+
+
+static void tcp_keepalive_timer (unsigned long data)
+{
+ struct sock *sk = (struct sock *) data;
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ __u32 elapsed;
+
+ /* Only process if socket is not in use. */
+ bh_lock_sock(sk);
+ if (sk->lock.users) {
+ /* Try again later. */
+ tcp_reset_keepalive_timer (sk, HZ/20);
+ goto out;
+ }
+
+ if (sk->state == TCP_LISTEN) {
+ tcp_synack_timer(sk);
+ goto out;
+ }
+
+ if (sk->state == TCP_FIN_WAIT2 && sk->dead) {
+ if (tp->linger2 >= 0) {
+ int tmo = tcp_fin_time(tp) - TCP_TIMEWAIT_LEN;
+
+ if (tmo > 0) {
+ tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
+ goto out;
+ }
+ }
+ tcp_send_active_reset(sk, GFP_ATOMIC);
+ goto death;
+ }
+
+ if (!sk->keepopen || sk->state == TCP_CLOSE)
+ goto out;
+
+ elapsed = keepalive_time_when(tp);
+
+ /* It is alive without keepalive 8) */
+ if (tp->packets_out || tp->send_head)
+ goto resched;
+
+ elapsed = tcp_time_stamp - tp->rcv_tstamp;
+
+ if (elapsed >= keepalive_time_when(tp)) {
+ if ((!tp->keepalive_probes && tp->probes_out >= sysctl_tcp_keepalive_probes) ||
+ (tp->keepalive_probes && tp->probes_out >= tp->keepalive_probes)) {
+ tcp_send_active_reset(sk, GFP_ATOMIC);
+ tcp_write_err(sk);
+ goto out;
+ }
+ if (tcp_write_wakeup(sk) <= 0) {
+ tp->probes_out++;
+ elapsed = keepalive_intvl_when(tp);
+ } else {
+ /* If keepalive was lost due to local congestion,
+ * try harder.
+ */
+ elapsed = TCP_RESOURCE_PROBE_INTERVAL;
+ }
+ } else {
+ /* It is tp->rcv_tstamp + keepalive_time_when(tp) */
+ elapsed = keepalive_time_when(tp) - elapsed;
+ }
+
+ TCP_CHECK_TIMER(sk);
+ tcp_mem_reclaim(sk);
+
+resched:
+ tcp_reset_keepalive_timer (sk, elapsed);
+ goto out;
+
+death:
+ tcp_done(sk);
+
+out:
+ bh_unlock_sock(sk);
+ sock_put(sk);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv4/udp.c b/uClinux-2.4.31-uc0/net/ipv4/udp.c
new file mode 100644
index 0000000..96870c1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/udp.c
@@ -0,0 +1,1339 @@
+/* $USAGI: udp.c,v 1.59 2003/11/12 05:12:00 yoshfuji Exp $ */
+
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * The User Datagram Protocol (UDP).
+ *
+ * Version: $Id: udp.c,v 1.100.2.4 2002/03/05 12:47:34 davem Exp $
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Arnt Gulbrandsen, <agulbra@nvg.unit.no>
+ * Alan Cox, <Alan.Cox@linux.org>
+ *
+ * Fixes:
+ * Alan Cox : verify_area() calls
+ * Alan Cox : stopped close while in use off icmp
+ * messages. Not a fix but a botch that
+ * for udp at least is 'valid'.
+ * Alan Cox : Fixed icmp handling properly
+ * Alan Cox : Correct error for oversized datagrams
+ * Alan Cox : Tidied select() semantics.
+ * Alan Cox : udp_err() fixed properly, also now
+ * select and read wake correctly on errors
+ * Alan Cox : udp_send verify_area moved to avoid mem leak
+ * Alan Cox : UDP can count its memory
+ * Alan Cox : send to an unknown connection causes
+ * an ECONNREFUSED off the icmp, but
+ * does NOT close.
+ * Alan Cox : Switched to new sk_buff handlers. No more backlog!
+ * Alan Cox : Using generic datagram code. Even smaller and the PEEK
+ * bug no longer crashes it.
+ * Fred Van Kempen : Net2e support for sk->broadcast.
+ * Alan Cox : Uses skb_free_datagram
+ * Alan Cox : Added get/set sockopt support.
+ * Alan Cox : Broadcasting without option set returns EACCES.
+ * Alan Cox : No wakeup calls. Instead we now use the callbacks.
+ * Alan Cox : Use ip_tos and ip_ttl
+ * Alan Cox : SNMP Mibs
+ * Alan Cox : MSG_DONTROUTE, and 0.0.0.0 support.
+ * Matt Dillon : UDP length checks.
+ * Alan Cox : Smarter af_inet used properly.
+ * Alan Cox : Use new kernel side addressing.
+ * Alan Cox : Incorrect return on truncated datagram receive.
+ * Arnt Gulbrandsen : New udp_send and stuff
+ * Alan Cox : Cache last socket
+ * Alan Cox : Route cache
+ * Jon Peatfield : Minor efficiency fix to sendto().
+ * Mike Shaver : RFC1122 checks.
+ * Alan Cox : Nonblocking error fix.
+ * Willy Konynenberg : Transparent proxying support.
+ * Mike McLagan : Routing by source
+ * David S. Miller : New socket lookup architecture.
+ * Last socket cache retained as it
+ * does have a high hit rate.
+ * Olaf Kirch : Don't linearise iovec on sendmsg.
+ * Andi Kleen : Some cleanups, cache destination entry
+ * for connect.
+ * Vitaly E. Lavrov : Transparent proxy revived after year coma.
+ * Melvin Smith : Check msg_name not msg_namelen in sendto(),
+ * return ENOTCONN for unconnected sockets (POSIX)
+ * Janos Farkas : don't deliver multi/broadcasts to a different
+ * bound-to-device socket
+ * yoshfuji@USAGI : Reworked bind(2) behavior, including:
+ * - Allow ipv6 and ipv4 bind(2) to the
+ * same port.
+ * - Don't allow narrow binding unless
+ * later uid is the same as before:
+ * CONFIG_NET_RESTRICTED_REUSE
+ * - Don't allow binding to the same
+ * address unless it is one of multi-
+ * cast address even if SO_REUSEADDR
+ * is set.
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/ioctls.h>
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/module.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/timer.h>
+#include <linux/mm.h>
+#include <linux/config.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <net/snmp.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/udp.h>
+#include <net/icmp.h>
+#include <net/route.h>
+#include <net/inet_common.h>
+#include <net/checksum.h>
+
+/*
+ * Snmp MIB for the UDP layer
+ */
+
+struct udp_mib udp_statistics[NR_CPUS*2];
+
+struct sock *udp_hash[UDP_HTABLE_SIZE];
+rwlock_t udp_hash_lock = RW_LOCK_UNLOCKED;
+
+/* Shared by v4/v6 udp. */
+int udp_port_rover;
+
+static int udp_v4_get_port(struct sock *sk, unsigned short snum)
+{
+#if defined(CONFIG_IPV6_IM) && defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ int *sysctl_ipv6_bindv6only_restriction = inter_module_get(IM_IPV6_SYSCTL_BINDV6ONLY_RESTRICTION);
+#endif
+ write_lock_bh(&udp_hash_lock);
+ if (snum == 0) {
+ int best_size_so_far, best, result, i;
+
+ if (udp_port_rover > sysctl_local_port_range[1] ||
+ udp_port_rover < sysctl_local_port_range[0])
+ udp_port_rover = sysctl_local_port_range[0];
+ best_size_so_far = 32767;
+ best = result = udp_port_rover;
+ for (i = 0; i < UDP_HTABLE_SIZE; i++, result++) {
+ struct sock *sk;
+ int size;
+
+ sk = udp_hash[result & (UDP_HTABLE_SIZE - 1)];
+ if (!sk) {
+ if (result > sysctl_local_port_range[1])
+ result = sysctl_local_port_range[0] +
+ ((result - sysctl_local_port_range[0]) &
+ (UDP_HTABLE_SIZE - 1));
+ goto gotit;
+ }
+ size = 0;
+ do {
+ if (++size >= best_size_so_far)
+ goto next;
+ } while ((sk = sk->next) != NULL);
+ best_size_so_far = size;
+ best = result;
+ next:;
+ }
+ result = best;
+ for(i = 0; i < (1 << 16) / UDP_HTABLE_SIZE; i++, result += UDP_HTABLE_SIZE) {
+ if (result > sysctl_local_port_range[1])
+ result = sysctl_local_port_range[0]
+ + ((result - sysctl_local_port_range[0]) &
+ (UDP_HTABLE_SIZE - 1));
+ if (!udp_lport_inuse(result))
+ break;
+ }
+ if (i >= (1 << 16) / UDP_HTABLE_SIZE)
+ goto fail;
+gotit:
+ udp_port_rover = snum = result;
+ } else {
+ struct sock *sk2;
+ int sk_reuse, sk2_reuse;
+ int addr_type2;
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ uid_t sk_uid = sock_i_uid_t(sk),
+ sk2_uid;
+#endif
+
+ sk_reuse = 0;
+ if (sk->reuse)
+ sk_reuse |= 1;
+#ifdef SO_REUSEPORT
+ if (sk->reuseport)
+ sk_reuse |= 2;
+#endif
+ if (sk_reuse &&
+ MULTICAST(sk->rcv_saddr))
+ sk_reuse |= 4;
+
+ for (sk2 = udp_hash[snum & (UDP_HTABLE_SIZE - 1)];
+ sk2 != NULL;
+ sk2 = sk2->next) {
+#if 1 /* XXX: linux-2.4.21 style */
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ int uid_ok;
+#endif
+ int both_specified = 0;
+
+ if (sk2->num != snum ||
+ sk2 == sk ||
+ (sk2->bound_dev_if && sk->bound_dev_if &&
+ sk2->bound_dev_if != sk->bound_dev_if))
+ continue;
+#if 0
+ if (sk2->family != AF_INET6 && sk2->family != AF_INET)
+ continue;
+#endif
+
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ if (sk2->family == AF_INET6) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&sk2->net_pinfo.af_inet6.rcv_saddr))
+ addr_type2 = IPV6_ADDR_ANY;
+ else if (IN6_IS_ADDR_V4MAPPED(&sk2->net_pinfo.af_inet6.rcv_saddr))
+ addr_type2 = IPV6_ADDR_MAPPED;
+ else
+ addr_type2 = IPV6_ADDR_UNICAST; /*XXX*/
+ } else
+ addr_type2 = IPV6_ADDR_MAPPED;
+#else
+ addr_type2 = IPV6_ADDR_MAPPED;
+#endif
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ sk2_uid = sock_i_uid_t(sk2);
+#endif
+
+ if ((addr_type2 != IPV6_ADDR_MAPPED ? addr_type2 != IPV6_ADDR_ANY : sk2->rcv_saddr) &&
+ sk->rcv_saddr) {
+ if (sk2->rcv_saddr != sk->rcv_saddr)
+ continue;
+ both_specified = 1;
+ }
+
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ uid_ok = sk2_uid == (uid_t) -1 || sk_uid == sk2_uid;
+#endif
+
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ if (addr_type2 != IPV6_ADDR_MAPPED && __ipv6_only_sock(sk2)) {
+#ifdef CONFIG_IPV6_RESTRICTED_DOUBLE_BIND
+#ifndef CONFIG_IPV6_IM
+ if (sysctl_ipv6_bindv6only_restriction == 0 || uid_ok)
+ continue;
+#else
+ if ((sysctl_ipv6_bindv6only_restriction && *sysctl_ipv6_bindv6only_restriction == 0) || uid_ok)
+ continue;
+#endif
+#else
+ continue;
+#endif
+ }
+#endif
+
+ sk2_reuse = 0;
+ if (sk2->reuse)
+ sk2_reuse |= 1;
+#ifdef SO_REUSEPORT
+ if (sk2->reuseport)
+ sk2_reuse |= 2;
+#endif
+ if (sk2_reuse &&
+ (addr_type2 != IPV6_ADDR_MAPPED ? (addr_type2 & IPV6_ADDR_MULTICAST) : MULTICAST(sk2->rcv_saddr)))
+ sk2_reuse |= 4;
+
+ if (sk2_reuse & sk_reuse & 3) { /* NOT && */
+ if (sk2_reuse & sk_reuse & 4)
+ continue;
+#ifdef CONFIG_NET_RESTRICTED_REUSE
+ if (!uid_ok)
+ goto fail;
+#endif
+#ifdef SO_REUSEPORT
+ if (sk2_reuse & sk_reuse & 2)
+ continue;
+#endif
+ if (both_specified) {
+ int addr_type2d;
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ if (sk2->family == AF_INET6) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&sk2->net_pinfo.af_inet6.daddr))
+ addr_type2d = IPV6_ADDR_ANY;
+ else if (IN6_IS_ADDR_V4MAPPED(&sk2->net_pinfo.af_inet6.daddr))
+ addr_type2d = IPV6_ADDR_MAPPED;
+ else
+ addr_type2d = IPV6_ADDR_UNICAST; /*XXX*/
+ } else
+ addr_type2d = IPV6_ADDR_MAPPED;
+#else
+ addr_type2d = IPV6_ADDR_MAPPED;
+#endif
+ if (addr_type2d != IPV6_ADDR_MAPPED ? addr_type2d != IPV6_ADDR_ANY : sk2->daddr)
+ continue;
+ } else {
+ if ((addr_type2 != IPV6_ADDR_MAPPED ? addr_type2 != IPV6_ADDR_ANY : sk2->rcv_saddr) ||
+ sk->rcv_saddr)
+ continue;
+ }
+ }
+ goto fail;
+#else /* XXX: linux-2.4.21 style */
+ if (sk2->num == snum &&
+ sk2 != sk &&
+ !ipv6_only_sock(sk2) &&
+ (!sk2->bound_dev_if ||
+ !sk->bound_dev_if ||
+ sk2->bound_dev_if == sk->bound_dev_if) &&
+ (!sk2->rcv_saddr ||
+ !sk->rcv_saddr ||
+ sk2->rcv_saddr == sk->rcv_saddr) &&
+ (!sk2->reuse || !sk->reuse))
+ goto fail;
+#endif /* XXX: linux-2.4.21 style */
+ }
+ }
+ sk->num = snum;
+ if (sk->pprev == NULL) {
+ struct sock **skp = &udp_hash[snum & (UDP_HTABLE_SIZE - 1)];
+ if ((sk->next = *skp) != NULL)
+ (*skp)->pprev = &sk->next;
+ *skp = sk;
+ sk->pprev = skp;
+ sock_prot_inc_use(sk->prot);
+ sock_hold(sk);
+ }
+ write_unlock_bh(&udp_hash_lock);
+#if defined(CONFIG_IPV6_IM) && defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ if (sysctl_ipv6_bindv6only_restriction)
+ inter_module_put(IM_IPV6_SYSCTL_BINDV6ONLY_RESTRICTION);
+#endif
+ return 0;
+
+fail:
+ write_unlock_bh(&udp_hash_lock);
+#if defined(CONFIG_IPV6_IM) && defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ if (sysctl_ipv6_bindv6only_restriction)
+ inter_module_put(IM_IPV6_SYSCTL_BINDV6ONLY_RESTRICTION);
+#endif
+ return 1;
+}
+
+static void udp_v4_hash(struct sock *sk)
+{
+ BUG();
+}
+
+static void udp_v4_unhash(struct sock *sk)
+{
+ write_lock_bh(&udp_hash_lock);
+ if (sk->pprev) {
+ if (sk->next)
+ sk->next->pprev = sk->pprev;
+ *sk->pprev = sk->next;
+ sk->pprev = NULL;
+ sk->num = 0;
+ sock_prot_dec_use(sk->prot);
+ __sock_put(sk);
+ }
+ write_unlock_bh(&udp_hash_lock);
+}
+
+/* UDP is nearly always wildcards out the wazoo, it makes no sense to try
+ * harder than this. -DaveM
+ */
+struct sock *udp_v4_lookup_longway(u32 saddr, u16 sport, u32 daddr, u16 dport, int dif)
+{
+ struct sock *sk, *result = NULL;
+ unsigned short hnum = ntohs(dport);
+ int badness = -1;
+
+ for(sk = udp_hash[hnum & (UDP_HTABLE_SIZE - 1)]; sk != NULL; sk = sk->next) {
+ if(sk->num == hnum && !ipv6_only_sock(sk)) {
+ int score;
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ score = sk->family == PF_INET ? 1 : 0;
+#else
+ score = 1;
+#endif
+ if(sk->rcv_saddr) {
+ if(sk->rcv_saddr != daddr)
+ continue;
+ score+=2;
+ }
+ if(sk->daddr) {
+ if(sk->daddr != saddr)
+ continue;
+ score+=2;
+ }
+ if(sk->dport) {
+ if(sk->dport != sport)
+ continue;
+ score+=2;
+ }
+ if(sk->bound_dev_if) {
+ if(sk->bound_dev_if != dif)
+ continue;
+ score+=2;
+ }
+ if(score == 9) {
+ result = sk;
+ break;
+ } else if(score > badness) {
+ result = sk;
+ badness = score;
+ }
+ }
+ }
+ return result;
+}
+
+__inline__ struct sock *udp_v4_lookup(u32 saddr, u16 sport, u32 daddr, u16 dport, int dif)
+{
+ struct sock *sk;
+
+ read_lock(&udp_hash_lock);
+ sk = udp_v4_lookup_longway(saddr, sport, daddr, dport, dif);
+ if (sk)
+ sock_hold(sk);
+ read_unlock(&udp_hash_lock);
+ return sk;
+}
+
+extern int ip_mc_sf_allow(struct sock *sk, u32 local, u32 rmt, int dif);
+
+static inline struct sock *udp_v4_mcast_next(struct sock *sk,
+ u16 loc_port, u32 loc_addr,
+ u16 rmt_port, u32 rmt_addr,
+ int dif)
+{
+ struct sock *s = sk;
+ unsigned short hnum = ntohs(loc_port);
+ for(; s; s = s->next) {
+ if ((s->num != hnum) ||
+ (s->daddr && s->daddr!=rmt_addr) ||
+ (s->dport != rmt_port && s->dport != 0) ||
+ (s->rcv_saddr && s->rcv_saddr != loc_addr) ||
+ ipv6_only_sock(s) ||
+ (s->bound_dev_if && s->bound_dev_if != dif))
+ continue;
+ if (!ip_mc_sf_allow(s, loc_addr, rmt_addr, dif))
+ continue;
+ break;
+ }
+ return s;
+}
+
+/*
+ * This routine is called by the ICMP module when it gets some
+ * sort of error condition. If err < 0 then the socket should
+ * be closed and the error returned to the user. If err > 0
+ * it's just the icmp type << 8 | icmp code.
+ * Header points to the ip header of the error packet. We move
+ * on past this. Then (as it used to claim before adjustment)
+ * header points to the first 8 bytes of the udp header. We need
+ * to find the appropriate port.
+ */
+
+void udp_err(struct sk_buff *skb, u32 info)
+{
+ struct iphdr *iph = (struct iphdr*)skb->data;
+ struct udphdr *uh = (struct udphdr*)(skb->data+(iph->ihl<<2));
+ int type = skb->h.icmph->type;
+ int code = skb->h.icmph->code;
+ struct sock *sk;
+ int harderr;
+ int err;
+
+ sk = udp_v4_lookup(iph->daddr, uh->dest, iph->saddr, uh->source, skb->dev->ifindex);
+ if (sk == NULL) {
+ ICMP_INC_STATS_BH(IcmpInErrors);
+ return; /* No socket for error */
+ }
+
+ err = 0;
+ harderr = 0;
+
+ switch (type) {
+ default:
+ case ICMP_TIME_EXCEEDED:
+ err = EHOSTUNREACH;
+ break;
+ case ICMP_SOURCE_QUENCH:
+ goto out;
+ case ICMP_PARAMETERPROB:
+ err = EPROTO;
+ harderr = 1;
+ break;
+ case ICMP_DEST_UNREACH:
+ if (code == ICMP_FRAG_NEEDED) { /* Path MTU discovery */
+ if (sk->protinfo.af_inet.pmtudisc != IP_PMTUDISC_DONT) {
+ err = EMSGSIZE;
+ harderr = 1;
+ break;
+ }
+ goto out;
+ }
+ err = EHOSTUNREACH;
+ if (code <= NR_ICMP_UNREACH) {
+ harderr = icmp_err_convert[code].fatal;
+ err = icmp_err_convert[code].errno;
+ }
+ break;
+ }
+
+ /*
+ * RFC1122: OK. Passes ICMP errors back to application, as per
+ * 4.1.3.3.
+ */
+ if (!sk->protinfo.af_inet.recverr) {
+ if (!harderr || sk->state != TCP_ESTABLISHED)
+ goto out;
+ } else {
+ ip_icmp_error(sk, skb, err, uh->dest, info, (u8*)(uh+1));
+ }
+ sk->err = err;
+ sk->error_report(sk);
+out:
+ sock_put(sk);
+}
+
+static unsigned short udp_check(struct udphdr *uh, int len, unsigned long saddr, unsigned long daddr, unsigned long base)
+{
+ return(csum_tcpudp_magic(saddr, daddr, len, IPPROTO_UDP, base));
+}
+
+struct udpfakehdr
+{
+ struct udphdr uh;
+ u32 saddr;
+ u32 daddr;
+ struct iovec *iov;
+ u32 wcheck;
+};
+
+/*
+ * Copy and checksum a UDP packet from user space into a buffer.
+ */
+
+static int udp_getfrag(const void *p, char * to, unsigned int offset,
+ unsigned int fraglen, struct sk_buff *skb)
+{
+ struct udpfakehdr *ufh = (struct udpfakehdr *)p;
+ if (offset==0) {
+ if (csum_partial_copy_fromiovecend(to+sizeof(struct udphdr), ufh->iov, offset,
+ fraglen-sizeof(struct udphdr), &ufh->wcheck))
+ return -EFAULT;
+ ufh->wcheck = csum_partial((char *)ufh, sizeof(struct udphdr),
+ ufh->wcheck);
+ ufh->uh.check = csum_tcpudp_magic(ufh->saddr, ufh->daddr,
+ ntohs(ufh->uh.len),
+ IPPROTO_UDP, ufh->wcheck);
+ if (ufh->uh.check == 0)
+ ufh->uh.check = -1;
+ memcpy(to, ufh, sizeof(struct udphdr));
+ return 0;
+ }
+ if (csum_partial_copy_fromiovecend(to, ufh->iov, offset-sizeof(struct udphdr),
+ fraglen, &ufh->wcheck))
+ return -EFAULT;
+ return 0;
+}
+
+/*
+ * Copy a UDP packet from user space into a buffer without checksumming.
+ */
+
+static int udp_getfrag_nosum(const void *p, char * to, unsigned int offset,
+ unsigned int fraglen, struct sk_buff *skb)
+{
+ struct udpfakehdr *ufh = (struct udpfakehdr *)p;
+
+ if (offset==0) {
+ memcpy(to, ufh, sizeof(struct udphdr));
+ return memcpy_fromiovecend(to+sizeof(struct udphdr), ufh->iov, offset,
+ fraglen-sizeof(struct udphdr));
+ }
+ return memcpy_fromiovecend(to, ufh->iov, offset-sizeof(struct udphdr),
+ fraglen);
+}
+
+int udp_sendmsg(struct sock *sk, struct msghdr *msg, int len)
+{
+ int ulen = len + sizeof(struct udphdr);
+ struct ipcm_cookie ipc;
+ struct udpfakehdr ufh;
+ struct rtable *rt = NULL;
+ int free = 0;
+ int connected = 0;
+ u32 daddr;
+ u8 tos;
+ int err;
+
+ /* This check is ONLY to check for arithmetic overflow
+ on integer(!) len. Not more! Real check will be made
+ in ip_build_xmit --ANK
+
+ BTW socket.c -> af_*.c -> ... make multiple
+ invalid conversions size_t -> int. We MUST repair it f.e.
+ by replacing all of them with size_t and revise all
+ the places sort of len += sizeof(struct iphdr)
+ If len was ULONG_MAX-10 it would be cathastrophe --ANK
+ */
+
+ if (len < 0 || len > 0xFFFF)
+ return -EMSGSIZE;
+
+ /*
+ * Check the flags.
+ */
+
+ if (msg->msg_flags&MSG_OOB) /* Mirror BSD error message compatibility */
+ return -EOPNOTSUPP;
+
+ /*
+ * Get and verify the address.
+ */
+
+ if (msg->msg_name) {
+ struct sockaddr_in * usin = (struct sockaddr_in*)msg->msg_name;
+ if (msg->msg_namelen < sizeof(*usin))
+ return -EINVAL;
+ if (usin->sin_family != AF_INET) {
+ if (usin->sin_family != AF_UNSPEC)
+ return -EINVAL;
+ }
+
+ ufh.daddr = usin->sin_addr.s_addr;
+ ufh.uh.dest = usin->sin_port;
+ if (ufh.uh.dest == 0)
+ return -EINVAL;
+ } else {
+ if (sk->state != TCP_ESTABLISHED)
+ return -EDESTADDRREQ;
+ ufh.daddr = sk->daddr;
+ ufh.uh.dest = sk->dport;
+ /* Open fast path for connected socket.
+ Route will not be used, if at least one option is set.
+ */
+ connected = 1;
+ }
+ ipc.addr = sk->saddr;
+ ufh.uh.source = sk->sport;
+
+ ipc.opt = NULL;
+ ipc.oif = sk->bound_dev_if;
+ if (msg->msg_controllen) {
+ err = ip_cmsg_send(msg, &ipc);
+ if (err)
+ return err;
+ if (ipc.opt)
+ free = 1;
+ connected = 0;
+ }
+ if (!ipc.opt)
+ ipc.opt = sk->protinfo.af_inet.opt;
+
+ ufh.saddr = ipc.addr;
+ ipc.addr = daddr = ufh.daddr;
+
+ if (ipc.opt && ipc.opt->srr) {
+ if (!daddr)
+ return -EINVAL;
+ daddr = ipc.opt->faddr;
+ connected = 0;
+ }
+ tos = RT_TOS(sk->protinfo.af_inet.tos);
+ if (sk->localroute || (msg->msg_flags&MSG_DONTROUTE) ||
+ (ipc.opt && ipc.opt->is_strictroute)) {
+ tos |= RTO_ONLINK;
+ connected = 0;
+ }
+
+ if (MULTICAST(daddr)) {
+ if (!ipc.oif)
+ ipc.oif = sk->protinfo.af_inet.mc_index;
+ if (!ufh.saddr)
+ ufh.saddr = sk->protinfo.af_inet.mc_addr;
+ connected = 0;
+ }
+
+ if (connected)
+ rt = (struct rtable*)sk_dst_check(sk, 0);
+
+ if (rt == NULL) {
+ err = ip_route_output(&rt, daddr, ufh.saddr, tos, ipc.oif);
+ if (err)
+ goto out;
+
+ err = -EACCES;
+ if (rt->rt_flags&RTCF_BROADCAST && !sk->broadcast)
+ goto out;
+ if (connected)
+ sk_dst_set(sk, dst_clone(&rt->u.dst));
+ }
+
+ if (msg->msg_flags&MSG_CONFIRM)
+ goto do_confirm;
+back_from_confirm:
+
+ ufh.saddr = rt->rt_src;
+ if (!ipc.addr)
+ ufh.daddr = ipc.addr = rt->rt_dst;
+ ufh.uh.len = htons(ulen);
+ ufh.uh.check = 0;
+ ufh.iov = msg->msg_iov;
+ ufh.wcheck = 0;
+
+ /* RFC1122: OK. Provides the checksumming facility (MUST) as per */
+ /* 4.1.3.4. It's configurable by the application via setsockopt() */
+ /* (MAY) and it defaults to on (MUST). */
+
+ err = ip_build_xmit(sk,
+ (sk->no_check == UDP_CSUM_NOXMIT ?
+ udp_getfrag_nosum :
+ udp_getfrag),
+ &ufh, ulen, &ipc, rt, msg->msg_flags);
+
+out:
+ ip_rt_put(rt);
+ if (free)
+ kfree(ipc.opt);
+ if (!err) {
+ UDP_INC_STATS_USER(UdpOutDatagrams);
+ return len;
+ }
+ return err;
+
+do_confirm:
+ dst_confirm(&rt->u.dst);
+ if (!(msg->msg_flags&MSG_PROBE) || len)
+ goto back_from_confirm;
+ err = 0;
+ goto out;
+}
+
+/*
+ * IOCTL requests applicable to the UDP protocol
+ */
+
+int udp_ioctl(struct sock *sk, int cmd, unsigned long arg)
+{
+ switch(cmd)
+ {
+ case SIOCOUTQ:
+ {
+ int amount = atomic_read(&sk->wmem_alloc);
+ return put_user(amount, (int *)arg);
+ }
+
+ case SIOCINQ:
+ {
+ struct sk_buff *skb;
+ unsigned long amount;
+
+ amount = 0;
+ spin_lock_irq(&sk->receive_queue.lock);
+ skb = skb_peek(&sk->receive_queue);
+ if (skb != NULL) {
+ /*
+ * We will only return the amount
+ * of this packet since that is all
+ * that will be read.
+ */
+ amount = skb->len - sizeof(struct udphdr);
+ }
+ spin_unlock_irq(&sk->receive_queue.lock);
+ return put_user(amount, (int *)arg);
+ }
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return(0);
+}
+
+static __inline__ int __udp_checksum_complete(struct sk_buff *skb)
+{
+ return (unsigned short)csum_fold(skb_checksum(skb, 0, skb->len, skb->csum));
+}
+
+static __inline__ int udp_checksum_complete(struct sk_buff *skb)
+{
+ return skb->ip_summed != CHECKSUM_UNNECESSARY &&
+ __udp_checksum_complete(skb);
+}
+
+
+/**
+ * udp_poll - wait for a UDP event.
+ * @file - file struct
+ * @sock - socket
+ * @wait - poll table
+ *
+ * This is same as datagram poll, except for the special case of
+ * blocking sockets. If application is using a blocking fd
+ * and a packet with checksum error is in the queue;
+ * then it could get return from select indicating data available
+ * but then block when reading it. Add special case code
+ * to work around these arguably broken applications.
+ */
+unsigned int udp_poll(struct file *file, struct socket *sock, poll_table *wait)
+{
+ unsigned int mask = datagram_poll(file, sock, wait);
+ struct sock *sk = sock->sk;
+
+ /* Check for false positives due to checksum errors */
+ if ( (mask & POLLRDNORM) &&
+ !(file->f_flags & O_NONBLOCK) &&
+ !(sk->shutdown & RCV_SHUTDOWN)){
+ struct sk_buff_head *rcvq = &sk->receive_queue;
+ struct sk_buff *skb;
+
+ spin_lock_irq(&rcvq->lock);
+ while ((skb = skb_peek(rcvq)) != NULL) {
+ if (udp_checksum_complete(skb)) {
+ UDP_INC_STATS_BH(UdpInErrors);
+ IP_INC_STATS_BH(IpInDiscards);
+ __skb_unlink(skb, rcvq);
+ kfree_skb(skb);
+ } else {
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ break;
+ }
+ }
+ spin_unlock_irq(&rcvq->lock);
+
+ /* nothing to see, move along */
+ if (skb == NULL)
+ mask &= ~(POLLIN | POLLRDNORM);
+ }
+
+ return mask;
+
+}
+
+/*
+ * This should be easy, if there is something there we
+ * return it, otherwise we block.
+ */
+
+int udp_recvmsg(struct sock *sk, struct msghdr *msg, int len,
+ int noblock, int flags, int *addr_len)
+{
+ struct sockaddr_in *sin = (struct sockaddr_in *)msg->msg_name;
+ struct sk_buff *skb;
+ int copied, err;
+
+ /*
+ * Check any passed addresses
+ */
+ if (addr_len)
+ *addr_len=sizeof(*sin);
+
+ if (flags & MSG_ERRQUEUE)
+ return ip_recv_error(sk, msg, len);
+
+try_again:
+ skb = skb_recv_datagram(sk, flags, noblock, &err);
+ if (!skb)
+ goto out;
+
+ copied = skb->len - sizeof(struct udphdr);
+ if (copied > len) {
+ copied = len;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+
+ if (skb->ip_summed==CHECKSUM_UNNECESSARY) {
+ err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov,
+ copied);
+ } else if (msg->msg_flags&MSG_TRUNC) {
+ if (__udp_checksum_complete(skb))
+ goto csum_copy_err;
+ err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov,
+ copied);
+ } else {
+ err = skb_copy_and_csum_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov);
+
+ if (err == -EINVAL)
+ goto csum_copy_err;
+ }
+
+ if (err)
+ goto out_free;
+
+ sock_recv_timestamp(msg, sk, skb);
+
+ /* Copy the address. */
+ if (sin)
+ {
+ sin->sin_family = AF_INET;
+ sin->sin_port = skb->h.uh->source;
+ sin->sin_addr.s_addr = skb->nh.iph->saddr;
+ memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
+ }
+ if (sk->protinfo.af_inet.cmsg_flags)
+ ip_cmsg_recv(msg, skb);
+
+ err = copied;
+ if (flags & MSG_TRUNC)
+ err = skb->len - sizeof(struct udphdr);
+
+out_free:
+ skb_free_datagram(sk, skb);
+out:
+ return err;
+
+csum_copy_err:
+ UDP_INC_STATS_BH(UdpInErrors);
+
+ /* Clear queue. */
+ if (flags&MSG_PEEK) {
+ int clear = 0;
+ spin_lock_irq(&sk->receive_queue.lock);
+ if (skb == skb_peek(&sk->receive_queue)) {
+ __skb_unlink(skb, &sk->receive_queue);
+ clear = 1;
+ }
+ spin_unlock_irq(&sk->receive_queue.lock);
+ if (clear)
+ kfree_skb(skb);
+ }
+
+ skb_free_datagram(sk, skb);
+
+ if (noblock)
+ return -EAGAIN;
+ goto try_again;
+}
+
+int udp_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
+{
+ struct sockaddr_in *usin = (struct sockaddr_in *) uaddr;
+ struct rtable *rt;
+ u32 saddr;
+ int oif;
+ int err;
+
+
+ if (addr_len < sizeof(*usin))
+ return -EINVAL;
+
+ if (usin->sin_family != AF_INET)
+ return -EAFNOSUPPORT;
+
+ sk_dst_reset(sk);
+
+ oif = sk->bound_dev_if;
+ saddr = sk->saddr;
+ if (MULTICAST(usin->sin_addr.s_addr)) {
+ if (!oif)
+ oif = sk->protinfo.af_inet.mc_index;
+ if (!saddr)
+ saddr = sk->protinfo.af_inet.mc_addr;
+ }
+ err = ip_route_connect(&rt, usin->sin_addr.s_addr, saddr,
+ RT_CONN_FLAGS(sk), oif);
+ if (err)
+ return err;
+ if ((rt->rt_flags&RTCF_BROADCAST) && !sk->broadcast) {
+ ip_rt_put(rt);
+ return -EACCES;
+ }
+ if(!sk->saddr)
+ sk->saddr = rt->rt_src; /* Update source address */
+ if(!sk->rcv_saddr)
+ sk->rcv_saddr = rt->rt_src;
+ sk->daddr = rt->rt_dst;
+ sk->dport = usin->sin_port;
+ sk->state = TCP_ESTABLISHED;
+ sk->protinfo.af_inet.id = jiffies;
+
+ sk_dst_set(sk, &rt->u.dst);
+ return(0);
+}
+
+int udp_disconnect(struct sock *sk, int flags)
+{
+ /*
+ * 1003.1g - break association.
+ */
+
+ sk->state = TCP_CLOSE;
+ sk->daddr = 0;
+ sk->dport = 0;
+ sk->bound_dev_if = 0;
+ if (!(sk->userlocks&SOCK_BINDADDR_LOCK)) {
+ sk->rcv_saddr = 0;
+ sk->saddr = 0;
+#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
+ memset(&sk->net_pinfo.af_inet6.saddr, 0, 16);
+ memset(&sk->net_pinfo.af_inet6.rcv_saddr, 0, 16);
+#endif
+ }
+ if (!(sk->userlocks&SOCK_BINDPORT_LOCK)) {
+ sk->prot->unhash(sk);
+ sk->sport = 0;
+ }
+ sk_dst_reset(sk);
+ return 0;
+}
+
+static void udp_close(struct sock *sk, long timeout)
+{
+ inet_sock_release(sk);
+}
+
+static int udp_queue_rcv_skb(struct sock * sk, struct sk_buff *skb)
+{
+#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
+ struct udp_opt *tp = &(sk->tp_pinfo.af_udp);
+#endif
+ /*
+ * Charge it to the socket, dropping if the queue is full.
+ */
+
+#if defined(CONFIG_FILTER)
+ if (sk->filter && skb->ip_summed != CHECKSUM_UNNECESSARY) {
+ if (__udp_checksum_complete(skb)) {
+ UDP_INC_STATS_BH(UdpInErrors);
+ IP_INC_STATS_BH(IpInDiscards);
+ ip_statistics[smp_processor_id()*2].IpInDelivers--;
+ kfree_skb(skb);
+ return -1;
+ }
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ }
+#endif
+#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
+ if (tp->esp_in_udp) {
+ /*
+ * Set skb->sk and xmit packet to ipsec_rcv.
+ *
+ * If ret != 0, ipsec_rcv refused the packet (not ESPinUDP),
+ * restore skb->sk and fall back to sock_queue_rcv_skb
+ */
+ struct inet_protocol *esp = NULL;
+
+#ifdef CONFIG_IPSEC_MODULE
+ for (esp = (struct inet_protocol *)inet_protos[IPPROTO_ESP & (MAX_INET_PROTOS - 1)];
+ (esp) && (esp->protocol != IPPROTO_ESP);
+ esp = esp->next);
+#else
+ extern struct inet_protocol esp_protocol;
+ esp = &esp_protocol;
+#endif
+
+ if (esp && esp->handler) {
+ struct sock *sav_sk = skb->sk;
+ skb->sk = sk;
+ if (esp->handler(skb) == 0) {
+ skb->sk = sav_sk;
+ /* not sure we might count ESPinUDP as UDP... */
+ UDP_INC_STATS_BH(UdpInDatagrams);
+ return 0;
+ }
+ skb->sk = sav_sk;
+ }
+ }
+#endif
+ if (sock_queue_rcv_skb(sk,skb)<0) {
+ UDP_INC_STATS_BH(UdpInErrors);
+ IP_INC_STATS_BH(IpInDiscards);
+ ip_statistics[smp_processor_id()*2].IpInDelivers--;
+ kfree_skb(skb);
+ return -1;
+ }
+ UDP_INC_STATS_BH(UdpInDatagrams);
+ return 0;
+}
+
+/*
+ * Multicasts and broadcasts go to each listener.
+ *
+ * Note: called only from the BH handler context,
+ * so we don't need to lock the hashes.
+ */
+static int udp_v4_mcast_deliver(struct sk_buff *skb, struct udphdr *uh,
+ u32 saddr, u32 daddr)
+{
+ struct sock *sk;
+ int dif;
+
+ read_lock(&udp_hash_lock);
+ sk = udp_hash[ntohs(uh->dest) & (UDP_HTABLE_SIZE - 1)];
+ dif = skb->dev->ifindex;
+ sk = udp_v4_mcast_next(sk, uh->dest, daddr, uh->source, saddr, dif);
+ if (sk) {
+ struct sock *sknext = NULL;
+
+ do {
+ struct sk_buff *skb1 = skb;
+
+ sknext = udp_v4_mcast_next(sk->next, uh->dest, daddr,
+ uh->source, saddr, dif);
+ if(sknext)
+ skb1 = skb_clone(skb, GFP_ATOMIC);
+
+ if(skb1)
+ udp_queue_rcv_skb(sk, skb1);
+ sk = sknext;
+ } while(sknext);
+ } else
+ kfree_skb(skb);
+ read_unlock(&udp_hash_lock);
+ return 0;
+}
+
+/* Initialize UDP checksum. If exited with zero value (success),
+ * CHECKSUM_UNNECESSARY means, that no more checks are required.
+ * Otherwise, csum completion requires chacksumming packet body,
+ * including udp header and folding it to skb->csum.
+ */
+static int udp_checksum_init(struct sk_buff *skb, struct udphdr *uh,
+ unsigned short ulen, u32 saddr, u32 daddr)
+{
+ if (uh->check == 0) {
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ } else if (skb->ip_summed == CHECKSUM_HW) {
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ if (!udp_check(uh, ulen, saddr, daddr, skb->csum))
+ return 0;
+ NETDEBUG(if (net_ratelimit()) printk(KERN_DEBUG "udp v4 hw csum failure.\n"));
+ skb->ip_summed = CHECKSUM_NONE;
+ }
+ if (skb->ip_summed != CHECKSUM_UNNECESSARY)
+ skb->csum = csum_tcpudp_nofold(saddr, daddr, ulen, IPPROTO_UDP, 0);
+ /* Probably, we should checksum udp header (it should be in cache
+ * in any case) and data in tiny packets (< rx copybreak).
+ */
+ return 0;
+}
+
+/*
+ * All we need to do is get the socket, and then do a checksum.
+ */
+
+int udp_rcv(struct sk_buff *skb)
+{
+ struct sock *sk;
+ struct udphdr *uh;
+ unsigned short ulen;
+ struct rtable *rt = (struct rtable*)skb->dst;
+ u32 saddr = skb->nh.iph->saddr;
+ u32 daddr = skb->nh.iph->daddr;
+ int len = skb->len;
+
+ IP_INC_STATS_BH(IpInDelivers);
+
+ /*
+ * Validate the packet and the UDP length.
+ */
+ if (!pskb_may_pull(skb, sizeof(struct udphdr)))
+ goto no_header;
+
+ uh = skb->h.uh;
+
+ ulen = ntohs(uh->len);
+
+ if (ulen > len || ulen < sizeof(*uh))
+ goto short_packet;
+
+ if (pskb_trim(skb, ulen))
+ goto short_packet;
+
+ if (udp_checksum_init(skb, uh, ulen, saddr, daddr) < 0)
+ goto csum_error;
+
+ if(rt->rt_flags & (RTCF_BROADCAST|RTCF_MULTICAST))
+ return udp_v4_mcast_deliver(skb, uh, saddr, daddr);
+
+ sk = udp_v4_lookup(saddr, uh->source, daddr, uh->dest, skb->dev->ifindex);
+
+ if (sk != NULL) {
+ udp_queue_rcv_skb(sk, skb);
+ sock_put(sk);
+ return 0;
+ }
+
+ /* No socket. Drop packet silently, if checksum is wrong */
+ if (udp_checksum_complete(skb))
+ goto csum_error;
+
+ UDP_INC_STATS_BH(UdpNoPorts);
+ icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
+
+ /*
+ * Hmm. We got an UDP packet to a port to which we
+ * don't wanna listen. Ignore it.
+ */
+ kfree_skb(skb);
+ return(0);
+
+short_packet:
+ NETDEBUG(if (net_ratelimit())
+ printk(KERN_DEBUG "UDP: short packet: %u.%u.%u.%u:%u %d/%d to %u.%u.%u.%u:%u\n",
+ NIPQUAD(saddr),
+ ntohs(uh->source),
+ ulen,
+ len,
+ NIPQUAD(daddr),
+ ntohs(uh->dest)));
+no_header:
+ UDP_INC_STATS_BH(UdpInErrors);
+ kfree_skb(skb);
+ return(0);
+
+csum_error:
+ /*
+ * RFC1122: OK. Discards the bad packet silently (as far as
+ * the network is concerned, anyway) as per 4.1.3.4 (MUST).
+ */
+ NETDEBUG(if (net_ratelimit())
+ printk(KERN_DEBUG "UDP: bad checksum. From %d.%d.%d.%d:%d to %d.%d.%d.%d:%d ulen %d\n",
+ NIPQUAD(saddr),
+ ntohs(uh->source),
+ NIPQUAD(daddr),
+ ntohs(uh->dest),
+ ulen));
+ UDP_INC_STATS_BH(UdpInErrors);
+ kfree_skb(skb);
+ return(0);
+}
+
+static void get_udp_sock(struct sock *sp, char *tmpbuf, int i)
+{
+ unsigned int dest, src;
+ __u16 destp, srcp;
+
+ dest = sp->daddr;
+ src = sp->rcv_saddr;
+ destp = ntohs(sp->dport);
+ srcp = ntohs(sp->sport);
+ sprintf(tmpbuf, "%4d: %08X:%04X %08X:%04X"
+ " %02X %08X:%08X %02X:%08lX %08X %5d %8d %lu %d %p",
+ i, src, srcp, dest, destp, sp->state,
+ atomic_read(&sp->wmem_alloc), atomic_read(&sp->rmem_alloc),
+ 0, 0L, 0,
+ sock_i_uid(sp), 0,
+ sock_i_ino(sp),
+ atomic_read(&sp->refcnt), sp);
+}
+
+int udp_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int len = 0, num = 0, i;
+ off_t pos = 0;
+ off_t begin;
+ char tmpbuf[129];
+
+ if (offset < 128)
+ len += sprintf(buffer, "%-127s\n",
+ " sl local_address rem_address st tx_queue "
+ "rx_queue tr tm->when retrnsmt uid timeout inode");
+ pos = 128;
+ read_lock(&udp_hash_lock);
+ for (i = 0; i < UDP_HTABLE_SIZE; i++) {
+ struct sock *sk;
+
+ for (sk = udp_hash[i]; sk; sk = sk->next, num++) {
+ if (sk->family != PF_INET)
+ continue;
+ pos += 128;
+ if (pos <= offset)
+ continue;
+ get_udp_sock(sk, tmpbuf, i);
+ len += sprintf(buffer+len, "%-127s\n", tmpbuf);
+ if(len >= length)
+ goto out;
+ }
+ }
+out:
+ read_unlock(&udp_hash_lock);
+ begin = len - (pos - offset);
+ *start = buffer + begin;
+ len -= begin;
+ if(len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+#if 1
+static int udp_setsockopt(struct sock *sk, int level, int optname,
+ char *optval, int optlen)
+{
+ struct udp_opt *tp = &(sk->tp_pinfo.af_udp);
+ int val;
+ int err = 0;
+
+ if (level != SOL_UDP)
+ return ip_setsockopt(sk, level, optname, optval, optlen);
+
+ if(optlen<sizeof(int))
+ return -EINVAL;
+
+ if (get_user(val, (int *)optval))
+ return -EFAULT;
+
+ lock_sock(sk);
+
+ switch(optname) {
+#ifdef CONFIG_IPSEC_NAT_TRAVERSAL
+#ifndef UDP_ESPINUDP
+#define UDP_ESPINUDP 100
+#endif
+ case UDP_ESPINUDP:
+ tp->esp_in_udp = val;
+ break;
+#endif
+ default:
+ err = -ENOPROTOOPT;
+ break;
+ }
+
+ release_sock(sk);
+ return err;
+}
+#endif
+
+struct proto udp_prot = {
+ name: "UDP",
+ close: udp_close,
+ connect: udp_connect,
+ disconnect: udp_disconnect,
+ ioctl: udp_ioctl,
+#if 1
+ setsockopt: udp_setsockopt,
+#else
+ setsockopt: ip_setsockopt,
+#endif
+ getsockopt: ip_getsockopt,
+ sendmsg: udp_sendmsg,
+ recvmsg: udp_recvmsg,
+ backlog_rcv: udp_queue_rcv_skb,
+ hash: udp_v4_hash,
+ unhash: udp_v4_unhash,
+ get_port: udp_v4_get_port,
+};
diff --git a/uClinux-2.4.31-uc0/net/ipv4/utils.c b/uClinux-2.4.31-uc0/net/ipv4/utils.c
new file mode 100644
index 0000000..7c535d4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv4/utils.c
@@ -0,0 +1,74 @@
+/* $USAGI: utils.c,v 1.10 2003/08/24 01:16:44 yoshfuji Exp $ */
+
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * Various kernel-resident INET utility functions; mainly
+ * for format conversion and debugging output.
+ *
+ * Version: $Id: utils.c,v 1.8 2000/10/03 07:29:01 anton Exp $
+ *
+ * Author: Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ *
+ * Fixes:
+ * Alan Cox : verify_area check.
+ * Alan Cox : removed old debugging.
+ * Andi Kleen : add net_ratelimit()
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <asm/byteorder.h>
+#include <linux/kernel.h>
+
+/*
+ * Display an IP address in readable format.
+ */
+
+char *in_ntop(struct in_addr *in, char *buff)
+{
+ char *p = (char *)in;
+ sprintf(buff, "%d.%d.%d.%d",
+ (p[0] & 255), (p[1] & 255), (p[2] & 255), (p[3] & 255));
+ return buff;
+}
+
+/*
+ * Convert an ASCII string to binary IP.
+ */
+
+__u32 in_aton(const char *str)
+{
+ unsigned long l;
+ unsigned int val;
+ int i;
+
+ l = 0;
+ for (i = 0; i < 4; i++)
+ {
+ l <<= 8;
+ if (*str != '\0')
+ {
+ val = 0;
+ while (*str != '\0' && *str != '.')
+ {
+ val *= 10;
+ val += *str - '0';
+ str++;
+ }
+ l |= val;
+ if (*str != '\0')
+ str++;
+ }
+ }
+ return(htonl(l));
+}
+
diff --git a/uClinux-2.4.31-uc0/net/ipv6/Config.in b/uClinux-2.4.31-uc0/net/ipv6/Config.in
new file mode 100644
index 0000000..70563b3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/Config.in
@@ -0,0 +1,92 @@
+# $USAGI: Config.in,v 1.80 2004/01/31 09:27:11 yoshfuji Exp $
+#
+# IPv6 configuration
+#
+
+#mainmenu_option next_comment
+#comment ' IPv6 Configuration'
+
+# --- overall ---
+bool ' IPv6: Verbose debugging messages' CONFIG_IPV6_DEBUG
+
+# --- module interface ---
+if [ "$CONFIG_IPV6" = "m" ]; then
+ bool ' IPv6: inter-module support.' CONFIG_IPV6_IM
+ if [ "$CONFIG_NET_IPGRE" != "n" ]; then
+ bool ' IPv6: enable gre tunnel support with modulized ipv6' CONFIG_IPV6_MODULE_IP_GRE
+ fi
+fi
+
+# --- IPv6 Specification (RFC2460) and Addressing Architecture (RFC2373) ---
+bool ' IPv6: Scoped routing' CONFIG_IPV6_ZONE
+if [ "$CONFIG_IPV6_ZONE" = "y" ]; then
+ bool ' IPv6: Site-border routing by default' CONFIG_IPV6_ZONE_SITELOCAL
+fi
+#bool ' IPv6: Loose scope_id' CONFIG_IPV6_LOOSE_SCOPE_ID
+bool ' IPv6: drop packets with fake ipv4-mapped address(es)' CONFIG_IPV6_DROP_FAKE_V4MAPPED
+if [ "$CONFIG_IPV6" = "y" -o "$CONFIG_IPV6_IM" = "y" ]; then
+ bool ' IPv6: Restrict "double binding" only for same user' CONFIG_IPV6_RESTRICTED_DOUBLE_BIND
+fi
+bool ' IPv6: 6to4-address in nexthop support.' CONFIG_IPV6_6TO4_NEXTHOP
+bool ' IPv6: Privacy Extensions (RFC 3041) support' CONFIG_IPV6_PRIVACY
+dep_tristate ' IPv6: IPv6 over IPv6 Tunneling (EXPERIMENTAL)' CONFIG_IPV6_IPV6_TUNNEL m
+
+# --- NDP (RFC2461) ---
+bool ' IPv6: Prefix List support' CONFIG_IPV6_PREFIXLIST
+define_bool CONFIG_IPV6_NDISC_NEW y
+if [ "$CONFIG_IPV6_DEBUG" = "y" ]; then
+ bool ' IPv6: Neighbor Discovery debugging' CONFIG_IPV6_NDISC_DEBUG
+fi
+
+# --- Stateless Address Configuration (RFC2462) ---
+if [ "$CONFIG_IPV6_DEBUG" = "y" ]; then
+ bool ' IPv6: Address Autoconfigration debugging' CONFIG_IPV6_ACONF_DEBUG
+ if [ "$CONFIG_IPV6_ACONF_DEBUG" = "y" ]; then
+ bool ' IPv6: Debug on source address selection' CONFIG_IPV6_ACONF_DEBUG_SADDR
+ fi
+fi
+
+# --- Routing ---
+if [ "$CONFIG_IPV6_DEBUG" = "y" ]; then
+ bool ' IPv6: Routing Information debugging' CONFIG_IPV6_RT6_DEBUG
+fi
+bool ' IPv6: sub-tree in routing table support (just for testing)' CONFIG_IPV6_SUBTREES
+bool ' IPv6: default router preference' CONFIG_IPV6_ROUTER_PREF
+if [ "$CONFIG_IPV6_ROUTER_PREF" != "n" ]; then
+ #bool ' IPv6: new round-robin architecture' CONFIG_IPV6_NEW_ROUNDROBIN
+ define_bool CONFIG_IPV6_NEW_ROUNDROBIN y
+ bool ' IPv6: Route Information Option support' CONFIG_IPV6_ROUTE_INFO
+fi
+
+# --- MLD (RFC2710) ---
+if [ "$CONFIG_IPV6_DEBUG" = "y" ]; then
+ bool ' IPv6: Multicast Listener Discovery debugging' CONFIG_IPV6_MLD6_DEBUG
+fi
+bool ' IPv6: disable optimization MLD6 Done message' CONFIG_IPV6_MLD6_ALL_DONE
+
+# --- Node Information Queries (Internet Draft) ---
+bool ' IPv6: enable Node Information Queries' CONFIG_IPV6_NODEINFO
+if [ "$CONFIG_IPV6_NODEINFO" = "y" ]; then
+ if [ "$CONFIG_IPV6_DEBUG" = "y" ]; then
+ bool ' IPv6: Node Information Queries debugging' CONFIG_IPV6_NODEINFO_DEBUG
+ fi
+ bool ' IPv6: regard NIS domain as DNS domain' CONFIG_IPV6_NODEINFO_USE_UTS_DOMAIN
+fi
+
+# --- Socket Interface (RFC2292/3542, RFC2553/3493) ---
+
+# --- Filtering ---
+if [ "$CONFIG_NETFILTER" != "n" ]; then
+ source net/ipv6/netfilter/Config.in
+fi
+
+# --- Misc. ---
+#
+#bool ' IPv6: flow policy support' CONFIG_RT6_POLICY
+#bool ' IPv6: firewall support' CONFIG_IPV6_FIREWALL
+
+# -- IPsec --
+
+# --- Mobile IP ---
+
+#endmenu
diff --git a/uClinux-2.4.31-uc0/net/ipv6/Makefile b/uClinux-2.4.31-uc0/net/ipv6/Makefile
new file mode 100644
index 0000000..94cfba4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/Makefile
@@ -0,0 +1,31 @@
+# $USAGI: Makefile,v 1.27 2003/11/12 05:12:01 yoshfuji Exp $
+#
+# Makefile for the Linux TCP/IP (INET6) layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+
+
+O_TARGET := ipv6.o
+
+export-objs := ipv6_tunnel.o
+
+obj-y := af_inet6.o anycast.o ip6_output.o ip6_input.o addrconf.o sit.o \
+ route.o ip6_fib.o ipv6_sockglue.o ndisc.o udp.o raw.o \
+ protocol.o icmp.o mcast.o reassembly.o tcp_ipv6.o \
+ exthdrs.o sysctl_net_ipv6.o datagram.o proc.o \
+ ip6_flowlabel.o ipv6_syms.o utils.o
+
+export-objs := ipv6_syms.o
+
+obj-$(CONFIG_IPV6_IPV6_TUNNEL) += ipv6_tunnel.o
+
+ifneq ($(CONFIG_IPV6),y)
+ifneq ($(CONFIG_IPV6),)
+obj-m += $(O_TARGET)
+endif
+endif
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/ipv6/README b/uClinux-2.4.31-uc0/net/ipv6/README
new file mode 100644
index 0000000..a3f3f57
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/README
@@ -0,0 +1,8 @@
+To join in the work on Linux IPv6 send mail to:
+
+ majordomo@oss.sgi.com
+
+and in the body of the message include:
+
+subscribe netdev
+
diff --git a/uClinux-2.4.31-uc0/net/ipv6/addrconf.c b/uClinux-2.4.31-uc0/net/ipv6/addrconf.c
new file mode 100644
index 0000000..261db77
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/addrconf.c
@@ -0,0 +1,4086 @@
+/* $USAGI: addrconf.c,v 1.235 2004/01/15 11:35:15 yoshfuji Exp $ */
+
+/*
+ * IPv6 Address [auto]configuration
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ * Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
+ *
+ * $Id: addrconf.c,v 1.69 2001/10/31 21:55:54 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/*
+ * Changes:
+ *
+ * Janos Farkas : delete timer on ifdown
+ * <chexum@bankinf.banki.hu>
+ * Andi Kleen : kill double kfree on module
+ * unload.
+ * Maciej W. Rozycki : FDDI support
+ * sekiya@USAGI : Don't send too many RS
+ * packets.
+ * yoshfuji@USAGI : Fixed interval between DAD
+ * packets.
+ * YOSHIFUJI Hideaki @USAGI : improved accuracy of
+ * address validation timer.
+ * Yuji SEKIYA @USAGI : Don't assign a same IPv6
+ * address on a same interface.
+ * YOSHIFUJI Hideaki @USAGI : ARCnet support
+ * YOSHIFUJI Hideaki @USAGI : improved source address
+ * selection; consider scope,
+ * status etc.
+ * yoshfuji@USAGI : Privacy Extensions (RFC 3041)
+ * support.
+ * Antonio Tapiador : Label table user level
+ * <atapiador@dit.upm.es> configuration
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/random.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/sched.h>
+#include <linux/net.h>
+#include <linux/in6.h>
+#ifdef CONFIG_IPV6_NODEINFO
+#include <linux/icmpv6.h>
+#endif
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/if_arcnet.h>
+#include <linux/route.h>
+#include <linux/inetdevice.h>
+#include <linux/init.h>
+#ifdef CONFIG_SYSCTL
+#include <linux/sysctl.h>
+#endif
+#include <linux/delay.h>
+#include <linux/notifier.h>
+
+#include <linux/proc_fs.h>
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/addrconf.h>
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/ndisc.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+#include <net/ip.h>
+#include <linux/if_tunnel.h>
+#include <linux/rtnetlink.h>
+
+#ifdef CONFIG_IPV6_PRIVACY
+#include <linux/crypto.h>
+#include <asm/scatterlist.h>
+#endif
+
+#include <asm/uaccess.h>
+
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+#include <linux/inet.h>
+#endif
+
+/* Set to 3 to get tracing... */
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+#define ACONF_DEBUG 3
+#else
+#define ACONF_DEBUG 1
+#endif
+
+#define ADBG(x) printk x
+#define NOADBG(x) do { ; } while(0)
+#if ACONF_DEBUG >= 3
+#define ADBG3(x) ADBG(x)
+#else
+#define ADBG3(x) NOADBG(x)
+#endif
+
+#if ACONF_DEBUG >= 2
+#define ADBG2(x) ADBG(x)
+#else
+#define ADBG2(x) NOADBG(x)
+#endif
+
+#if ACONF_DEBUG >=1
+#define ADBG1(x) ADBG(x)
+#else
+#define ADBG1(x) NOADBG(x)
+#endif
+
+/* /proc/net/snmp6, /proc/net/dev_snmp6/ things */
+#ifdef CONFIG_PROC_FS
+extern struct proc_dir_entry *proc_net_devsnmp6;
+extern int afinet6_read_devsnmp(char *buffer,
+ char **start, off_t offset, int length,
+ int *eof, void *data);
+#endif
+
+#if HZ == 100
+#define timeticks(jif) (jif)
+#elif HZ < 100
+#define timeticks(jif) ({ unsigned long _j=(jif); \
+ unsigned long _j1 = _j/HZ, _j2 = _j%HZ; \
+ (100/HZ)*_j1*HZ + \
+ (100/HZ)*_j2+_j1*(100%HZ) + \
+ ((100%HZ)*_j2)/HZ; \
+ })
+#else
+#define timeticks(jif) ({ unsigned long _j=(jif); \
+ (_j/HZ)*(100%HZ)+((100%HZ)*(_j%HZ))/HZ; \
+ })
+#endif
+
+#define INFINITE 0xffffffff
+
+#ifdef CONFIG_SYSCTL
+static void addrconf_sysctl_register(struct inet6_dev *idev, struct ipv6_devconf *p);
+static void addrconf_sysctl_unregister(struct ipv6_devconf *p);
+#endif
+
+int inet6_dev_count;
+int inet6_ifa_count;
+
+#ifdef CONFIG_IPV6_PRIVACY
+static int __ipv6_regen_rndid(struct inet6_dev *idev);
+static int __ipv6_try_regen_rndid(struct inet6_dev *idev, struct in6_addr *tmpaddr);
+static void ipv6_regen_rndid(unsigned long data);
+
+static int desync_factor = MAX_DESYNC_FACTOR * HZ;
+static struct crypto_tfm *md5_tfm;
+static spinlock_t md5_tfm_lock = SPIN_LOCK_UNLOCKED;
+#endif
+
+int ipv6_count_addresses(struct inet6_dev *idev);
+static u32 ipv6_addrselect_label_lookup(const struct in6_addr *addr, int ifindex);
+
+struct ipv6_addrselect_label
+{
+ struct ipv6_addrselect_label *next;
+ struct in6_addr addr;
+ u16 plen;
+ u32 ifindex;
+ u32 label;
+};
+
+struct ipv6_addrselect_label *ipv6_addrselect_label_table;
+rwlock_t addrselect_label_lock = RW_LOCK_UNLOCKED;
+
+/*
+ * Configured unicast address hash table
+ */
+static struct inet6_ifaddr *inet6_addr_lst[IN6_ADDR_HSIZE];
+static rwlock_t addrconf_hash_lock = RW_LOCK_UNLOCKED;
+
+/* Protects inet6 devices */
+rwlock_t addrconf_lock = RW_LOCK_UNLOCKED;
+
+void addrconf_verify(unsigned long);
+
+static struct timer_list addr_chk_timer = { function: addrconf_verify };
+static spinlock_t addrconf_verify_lock = SPIN_LOCK_UNLOCKED;
+
+static int addrconf_ifdown(struct net_device *dev, int how);
+
+static void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags);
+static void addrconf_dad_timer(unsigned long data);
+static void addrconf_dad_completed(struct inet6_ifaddr *ifp);
+static void addrconf_rs_timer(unsigned long data);
+static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifa);
+
+static int ipv6_chk_same_addr(const struct in6_addr *addr, struct net_device *dev);
+
+static struct notifier_block *inet6addr_chain;
+
+struct ipv6_devconf ipv6_devconf =
+{
+ 0, /* forwarding */
+ IPV6_DEFAULT_HOPLIMIT, /* hop limit */
+ IPV6_MIN_MTU, /* mtu */
+ 1, /* accept RAs */
+ 1, /* accept redirects */
+ 1, /* autoconfiguration */
+ 1, /* dad transmits */
+ MAX_RTR_SOLICITATIONS, /* router solicits */
+ RTR_SOLICITATION_INTERVAL, /* rtr solicit interval */
+ MAX_RTR_SOLICITATION_DELAY, /* rtr solicit delay */
+ max_addresses: IPV6_MAX_ADDRESSES,
+#ifdef CONFIG_IPV6_NODEINFO
+ accept_ni: 1,
+#endif
+#ifdef CONFIG_IPV6_PRIVACY
+ use_tempaddr: 1,
+ temp_valid_lft: TEMP_VALID_LIFETIME,
+ temp_prefered_lft: TEMP_PREFERRED_LIFETIME,
+ regen_max_retry: REGEN_MAX_RETRY,
+ max_desync_factor: MAX_DESYNC_FACTOR,
+#endif
+};
+
+static struct ipv6_devconf ipv6_devconf_dflt =
+{
+ 0, /* forwarding */
+ IPV6_DEFAULT_HOPLIMIT, /* hop limit */
+ IPV6_MIN_MTU, /* mtu */
+ 1, /* accept RAs */
+ 1, /* accept redirects */
+ 1, /* autoconfiguration */
+ 1, /* dad transmits */
+ MAX_RTR_SOLICITATIONS, /* router solicits */
+ RTR_SOLICITATION_INTERVAL, /* rtr solicit interval */
+ MAX_RTR_SOLICITATION_DELAY, /* rtr solicit delay */
+ max_addresses: IPV6_MAX_ADDRESSES,
+#ifdef CONFIG_IPV6_NODEINFO
+ accept_ni: 1,
+#endif
+#ifdef CONFIG_IPV6_PRIVACY
+ use_tempaddr: 1,
+ temp_valid_lft: TEMP_VALID_LIFETIME,
+ temp_prefered_lft: TEMP_PREFERRED_LIFETIME,
+ regen_max_retry: REGEN_MAX_RETRY,
+ max_desync_factor: MAX_DESYNC_FACTOR,
+#endif
+};
+
+/* IPv6 Wildcard Address and Loopback Address defined by RFC3493 */
+const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
+const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT;
+
+int __ipv6_addr_type(const struct in6_addr *addr)
+{
+ int type;
+ u32 st;
+
+ st = addr->s6_addr32[0];
+
+ if ((st & htonl(0xFF000000)) == htonl(0xFF000000)) {
+ /* multicast */
+
+ type = IPV6_ADDR_MC_SCOPE(addr)<<16; /* addr-select 3.1 */
+ switch(type) {
+ case IPV6_ADDR_SCOPE_NODELOCAL<<16:
+ type |= IPV6_ADDR_LOOPBACK;
+ break;
+
+ case IPV6_ADDR_SCOPE_LINKLOCAL<<16:
+ type |= IPV6_ADDR_LINKLOCAL;
+ break;
+
+ case IPV6_ADDR_SCOPE_SITELOCAL<<16:
+ type |= IPV6_ADDR_SITELOCAL;
+ break;
+#if 0
+ default:
+ /* XXX: orglocal, global and other scopes */
+#endif
+ }
+ type |= IPV6_ADDR_MULTICAST;
+ return type;
+ }
+
+ if ((st & htonl(0xE0000000)) == htonl(0x00000000)) {
+ if (st == htonl(0x00000000)) {
+ /* ipv4-compatible, ipv4-mapped and reserved addresses */
+ if ((addr->s6_addr32[1]|addr->s6_addr32[2]) == htonl(0x00000000)) {
+ if (addr->s6_addr32[3] == htonl(0x00000000))
+ return IPV6_ADDR_ANY;
+ if (addr->s6_addr32[3] == htonl(0x00000001))
+ return (IPV6_ADDR_LOOPBACK | IPV6_ADDR_UNICAST |
+ IPV6_ADDR_SCOPE_LINKLOCAL<<16); /* addr-select 3.4 */
+ return (IPV6_ADDR_COMPATv4 |
+ IPV6_ADDR_SCOPE_GLOBAL<<16); /* addr-select 3.3 */
+ }
+ if (addr->s6_addr32[1] == htonl(0x00000000) &&
+ addr->s6_addr32[2] == htonl(0x0000ffff)) {
+ return (IPV6_ADDR_MAPPED |
+ IPV6_ADDR_SCOPE_GLOBAL<<16); /* addr-select 3.3 */
+ }
+ }
+ /* NSAP and IPX addresses, too */
+ return (IPV6_ADDR_RESERVED |
+ IPV6_ADDR_SCOPE_GLOBAL<<16); /* addr-select 3.3 */
+ }
+
+ /* check for reserved anycast addresses */
+ if ((st & htonl(0xE0000000)) &&
+ ((addr->s6_addr32[2] == htonl(0xFDFFFFFF) &&
+ (addr->s6_addr32[3] | htonl(0x0000007F)) == (u32)~0) ||
+ (addr->s6_addr32[2] == 0 && addr->s6_addr32[3] == 0))) {
+ type = IPV6_ADDR_ANYCAST;
+ } else
+ type = IPV6_ADDR_UNICAST;
+
+ /* check for local-use unicasts */
+ if ((st & htonl(0xE0000000)) == htonl(0xE0000000)) {
+ switch(st & htonl(0xFFC00000)) {
+ case __constant_htonl(0xFE800000):
+ type |= IPV6_ADDR_LINKLOCAL | IPV6_ADDR_SCOPE_LINKLOCAL<<16;
+ break;
+ case __constant_htonl(0xFEC00000):
+ type |= IPV6_ADDR_SITELOCAL | IPV6_ADDR_SCOPE_SITELOCAL<<16;
+ break;
+ default:
+ return IPV6_ADDR_RESERVED | IPV6_ADDR_SCOPE_GLOBAL<<16;
+ }
+ } else {
+ type |= IPV6_ADDR_SCOPE_GLOBAL<<16; /* addr-select 3.3 */
+ }
+
+ return type;
+}
+
+static void addrconf_del_timer(struct inet6_ifaddr *ifp)
+{
+ ADBG3((KERN_DEBUG
+ "addrconf_del_timer(ifp=%p)\n",
+ ifp));
+
+ if (del_timer(&ifp->timer))
+ __in6_ifa_put(ifp);
+}
+
+enum addrconf_timer_t
+{
+ AC_NONE,
+ AC_DAD,
+ AC_RS,
+};
+
+static void addrconf_mod_timer(struct inet6_ifaddr *ifp,
+ enum addrconf_timer_t what,
+ unsigned long when)
+{
+ ADBG3((KERN_DEBUG
+ "addrconf_mod_timer(ifp=%p, what=%d, when=%lu): jiffies=%lu\n",
+ ifp, what, when, jiffies));
+
+ if (!del_timer(&ifp->timer))
+ in6_ifa_hold(ifp);
+
+ switch (what) {
+ case AC_DAD:
+ ifp->timer.function = addrconf_dad_timer;
+ ADBG3((KERN_DEBUG "AC_DAD timer = %d\n", ifp->probes));
+ break;
+ case AC_RS:
+ ifp->timer.function = addrconf_rs_timer;
+ ADBG3((KERN_DEBUG "AC_RS timer = %d\n", ifp->probes));
+
+ /* Check whether entering AC_RS function after receiving RA */
+ if (ifp->idev->if_flags & IF_RA_RCVD)
+ ADBG1((KERN_DEBUG "AC_RS timer is called when IF_RA_RCVD is on.\n"));
+ break;
+ default:;
+ }
+ ifp->timer.expires = jiffies + when;
+ add_timer(&ifp->timer);
+}
+
+/* Nobody refers to this device, we may destroy it. */
+
+void in6_dev_finish_destroy(struct inet6_dev *idev)
+{
+ struct net_device *dev = idev->dev;
+ BUG_TRAP(idev->addr_list==NULL);
+ BUG_TRAP(idev->mc_list==NULL);
+#ifdef CONFIG_IPV6_PREFIXLIST
+ BUG_TRAP(list_empty(&idev->prefix_list) == 1);
+#endif
+#ifdef NET_REFCNT_DEBUG
+ ADBG3((KERN_DEBUG "in6_dev_finish_destroy: %s\n", dev ? dev->name : "NIL"));
+#endif
+ dev_put(dev);
+ if (!idev->dead) {
+ ADBG1((KERN_WARNING
+ "Freeing alive inet6 device %p\n", idev));
+ return;
+ }
+#ifdef CONFIG_PROC_FS
+ if (idev->stats.proc_dir_entry){
+ remove_proc_entry(idev->stats.proc_dir_entry->name,
+ proc_net_devsnmp6);
+ }
+#endif
+ ipv6_statistics[0].Ip6LastChange = timeticks(jiffies);
+ inet6_dev_count--;
+ kfree(idev);
+}
+
+static struct inet6_dev * ipv6_add_dev(struct net_device *dev)
+{
+ struct inet6_dev *ndev;
+
+ ASSERT_RTNL();
+
+ ADBG3((KERN_DEBUG
+ "ipv6_add_dev(dev=%p(%s))\n",
+ dev, dev->name));
+
+ if (dev->mtu < IPV6_MIN_MTU)
+ return NULL;
+
+ ndev = kmalloc(sizeof(struct inet6_dev), GFP_KERNEL);
+
+ if (ndev) {
+#ifdef CONFIG_IPV6_ZONE
+ int i;
+#endif
+ memset(ndev, 0, sizeof(struct inet6_dev));
+
+ INIT_LIST_HEAD(&ndev->prefix_list);
+ ndev->prefix_lock = RW_LOCK_UNLOCKED;
+ ndev->lock = RW_LOCK_UNLOCKED;
+ ndev->dev = dev;
+ ndev->prefix_lock = RW_LOCK_UNLOCKED;
+ INIT_LIST_HEAD(&ndev->prefix_list);
+ memcpy(&ndev->cnf, &ipv6_devconf_dflt, sizeof(ndev->cnf));
+ ndev->cnf.mtu6 = dev->mtu;
+ ndev->cnf.sysctl = NULL;
+ ndev->nd_parms = neigh_parms_alloc(dev, &nd_tbl);
+ if (ndev->nd_parms == NULL) {
+ kfree(ndev);
+ return NULL;
+ }
+
+ inet6_dev_count++;
+ /* We refer to the device */
+ dev_hold(dev);
+
+#ifdef CONFIG_IPV6_ZONE
+ memset(&ndev->zone, 0, sizeof(ndev->zone));
+ for (i=0; i<16; i++) {
+ if (
+#ifndef CONFIG_IPV6_ZONE_SITELOCAL
+ i <= IPV6_ADDR_SCOPE_LINKLOCAL
+#else
+ i <= IPV6_ADDR_SCOPE_SITELOCAL
+#endif
+ )
+ ndev->zone.zoneid[i] = dev->ifindex;
+ else
+ ndev->zone.zoneid[i] = 0x0000001;
+ }
+#endif
+
+ if ((dev->flags&IFF_LOOPBACK) ||
+#ifdef CONFIG_NET_IPIP_IPV6
+ dev->type == ARPHRD_TUNNEL
+#else
+ dev->type == ARPHRD_SIT
+#endif
+ ) {
+ }
+
+ /* One reference from device */
+ in6_dev_hold(ndev);
+
+#ifdef CONFIG_IPV6_PRIVACY
+ get_random_bytes(ndev->rndid, sizeof(ndev->rndid));
+ get_random_bytes(ndev->entropy, sizeof(ndev->entropy));
+ init_timer(&ndev->regen_timer);
+ ndev->regen_timer.function = ipv6_regen_rndid;
+ ndev->regen_timer.data = (unsigned long) ndev;
+ if ((dev->flags&IFF_LOOPBACK) ||
+ dev->type == ARPHRD_TUNNEL ||
+ dev->type == ARPHRD_SIT) {
+ ADBG2((KERN_DEBUG
+ "Disabled Privacy Extensions on device %p(%s)\n",
+ dev, dev->name));
+ ndev->cnf.use_tempaddr = -1;
+ } else {
+ __ipv6_regen_rndid(ndev);
+ }
+#endif
+
+ write_lock_bh(&addrconf_lock);
+ dev->ip6_ptr = ndev;
+ write_unlock_bh(&addrconf_lock);
+ ipv6_statistics[0].Ip6LastChange = timeticks(jiffies);
+ ndev->stats.ipv6[0].Ip6LastChange = timeticks(jiffies);
+
+#ifdef CONFIG_PROC_FS
+ ndev->stats.proc_dir_entry = create_proc_read_entry(dev->name, 0,
+ proc_net_devsnmp6,
+ afinet6_read_devsnmp,
+ ndev);
+ if (!ndev->stats.proc_dir_entry)
+ ADBG1((KERN_WARNING
+ "addrconf_notify(): cannot create /proc/net/dev_snmp6/%s\n",
+ dev->name));
+#endif
+
+ ipv6_mc_init_dev(ndev);
+
+#ifdef CONFIG_SYSCTL
+ neigh_sysctl_register(dev, ndev->nd_parms, NET_IPV6, NET_IPV6_NEIGH, "ipv6");
+ addrconf_sysctl_register(ndev, &ndev->cnf);
+#endif
+ }
+ return ndev;
+}
+
+static struct inet6_dev * ipv6_find_idev(struct net_device *dev)
+{
+ struct inet6_dev *idev;
+
+ ASSERT_RTNL();
+
+ ADBG3((KERN_DEBUG
+ "ipv6_find_idev(dev=%p(%s))\n",
+ dev, dev->name));
+
+ if ((idev = __in6_dev_get(dev)) == NULL) {
+ if ((idev = ipv6_add_dev(dev)) == NULL)
+ return NULL;
+ }
+ if (dev->flags&IFF_UP)
+ ipv6_mc_up(idev);
+ return idev;
+}
+
+static void dev_forward_change(struct inet6_dev *idev)
+{
+ struct net_device *dev;
+ struct inet6_ifaddr *ifa;
+ struct in6_addr addr;
+
+ ADBG3((KERN_DEBUG
+ "dev_forward_change(idev=%p)\n", idev));
+
+ if (!idev || idev->dead)
+ return;
+
+ dev = idev->dev;
+ if (!dev)
+ return;
+
+ ipv6_addr_all_routers(&addr);
+
+ read_lock(&idev->lock);
+ if (idev->cnf.forwarding) {
+ read_unlock(&idev->lock);
+ ipv6_dev_mc_inc(dev, &addr); /* XXX: race */
+ } else {
+ read_unlock(&idev->lock);
+ ipv6_dev_mc_dec(dev, &addr); /* XXX: race */
+ }
+
+ for (ifa=idev->addr_list; ifa; ifa=ifa->if_next) {
+ ipv6_addr_prefix(&addr, &ifa->addr, ifa->prefix_len);
+ if (ipv6_addr_any(&addr))
+ continue;
+ read_lock(&idev->lock);
+ if (idev->cnf.forwarding) {
+ read_unlock(&idev->lock);
+ ipv6_dev_ac_inc(idev->dev, &addr);
+ } else {
+ read_unlock(&idev->lock);
+ ipv6_dev_ac_dec(idev->dev, &addr);
+ }
+ }
+}
+
+static void addrconf_forward_change(void)
+{
+ struct net_device *dev;
+
+ ADBG3((KERN_DEBUG "addrconf_forward_change()\n"));
+
+ read_lock(&dev_base_lock);
+ read_lock(&addrconf_lock);
+ for (dev=dev_base; dev; dev=dev->next) {
+ struct inet6_dev *idev = __in6_dev_get(dev);
+ if (idev) {
+ int changed = 0;
+ write_lock(&idev->lock);
+ if (!idev->dead) {
+ changed = idev->cnf.forwarding != ipv6_devconf.forwarding;
+ idev->cnf.forwarding = ipv6_devconf.forwarding;
+ }
+ write_unlock(&idev->lock);
+ if (changed)
+ dev_forward_change(idev);
+ }
+ }
+ ipv6_devconf_dflt.forwarding = ipv6_devconf.forwarding;
+ read_unlock(&addrconf_lock);
+ read_unlock(&dev_base_lock);
+}
+
+
+/* Nobody refers to this ifaddr, destroy it */
+
+void inet6_ifa_finish_destroy(struct inet6_ifaddr *ifp)
+{
+ ADBG3((KERN_DEBUG
+ "inet6_ifa_finish_destroy(ifp=%p)\n",
+ ifp));
+
+ BUG_TRAP(ifp->if_next==NULL);
+ BUG_TRAP(ifp->lst_next==NULL);
+#ifdef NET_REFCNT_DEBUG
+ ADBG3((KERN_DEBUG "inet6_ifa_finish_destroy\n"));
+#endif
+
+ in6_dev_put(ifp->idev);
+
+ if (del_timer(&ifp->timer))
+ ADBG1((KERN_WARNING
+ "Timer is still running, when freeing ifa=%p\n", ifp));
+
+ if (!ifp->dead) {
+ ADBG1((KERN_WARNING
+ "Freeing alive inet6 address %p\n", ifp));
+ return;
+ }
+ inet6_ifa_count--;
+ kfree(ifp);
+}
+
+/* On success it returns ifp with increased reference count */
+
+static struct inet6_ifaddr *
+ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, int pfxlen,
+ int scope, unsigned flags)
+{
+ struct inet6_ifaddr *ifa;
+ int hash;
+ static spinlock_t lock = SPIN_LOCK_UNLOCKED;
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ char abuf[128];
+
+ in6_ntop(addr, abuf);
+ ADBG3((KERN_DEBUG
+ "ipv6_add_addr(idev=%p, addr=%s, pfxlen=%d, scope=%d, flags=%08X)\n",
+ idev, abuf, pfxlen, scope, flags));
+#endif
+
+ spin_lock_bh(&lock);
+
+ /* Ignore adding duplicate addresses on an interface */
+ if (ipv6_chk_same_addr(addr, idev->dev)) {
+ spin_unlock_bh(&lock);
+ ADBG(("ipv6_add_addr: already assigned\n"));
+ return ERR_PTR(-EEXIST);
+ }
+
+ ifa = kmalloc(sizeof(struct inet6_ifaddr), GFP_ATOMIC);
+
+ if (ifa == NULL) {
+ spin_unlock_bh(&lock);
+ ADBG1((KERN_WARNING
+ "ipv6_add_addr: malloc failed\n"));
+ return NULL;
+ }
+
+ memset(ifa, 0, sizeof(struct inet6_ifaddr));
+ ipv6_addr_copy(&ifa->addr, addr);
+
+ spin_lock_init(&ifa->lock);
+ init_timer(&ifa->timer);
+ ifa->timer.data = (unsigned long) ifa;
+ ifa->scope = scope;
+ ifa->prefix_len = pfxlen;
+ ifa->flags = flags | IFA_F_TENTATIVE;
+
+ read_lock(&addrconf_lock);
+ if (idev->dead) {
+ read_unlock(&addrconf_lock);
+ spin_unlock_bh(&lock);
+ kfree(ifa);
+ return ERR_PTR(-ENODEV); /*XXX*/
+ }
+
+ inet6_ifa_count++;
+ ifa->idev = idev;
+ in6_dev_hold(idev);
+ /* For caller */
+ in6_ifa_hold(ifa);
+
+ /* Add to big hash table */
+ hash = ipv6_addr_hash(addr);
+
+ write_lock_bh(&addrconf_hash_lock);
+ ifa->lst_next = inet6_addr_lst[hash];
+ inet6_addr_lst[hash] = ifa;
+ in6_ifa_hold(ifa);
+ write_unlock_bh(&addrconf_hash_lock);
+
+ write_lock_bh(&idev->lock);
+ /* Add to inet6_dev unicast addr list. */
+ ifa->if_next = idev->addr_list;
+ idev->addr_list = ifa;
+ in6_ifa_hold(ifa);
+#ifdef CONFIG_IPV6_PRIVACY
+ ifa->regen_count = 0;
+ if (ifa->flags&IFA_F_TEMPORARY) {
+ ifa->tmp_next = idev->tempaddr_list;
+ idev->tempaddr_list = ifa;
+ in6_ifa_hold(ifa);
+ } else {
+ ifa->tmp_next = NULL;
+ }
+#endif
+ write_unlock_bh(&idev->lock);
+ read_unlock(&addrconf_lock);
+ spin_unlock_bh(&lock);
+
+ notifier_call_chain(&inet6addr_chain,NETDEV_UP,ifa);
+
+ return ifa;
+}
+
+/* This function wants to get referenced ifp and releases it before return */
+
+static void ipv6_del_addr(struct inet6_ifaddr *ifp)
+{
+ struct inet6_ifaddr *ifa, **ifap;
+ struct inet6_dev *idev = ifp->idev;
+ int hash;
+
+ ADBG3((KERN_DEBUG
+ "ipv6_del_addr(ifp=%p)\n", ifp));
+
+ ifp->dead = 1;
+
+#ifdef CONFIG_IPV6_PRIVACY
+ spin_lock_bh(&ifp->lock);
+ if (ifp->ifpub) {
+ __in6_ifa_put(ifp->ifpub);
+ ifp->ifpub = NULL;
+ }
+ spin_unlock_bh(&ifp->lock);
+#endif
+
+ hash = ipv6_addr_hash(&ifp->addr);
+
+ write_lock_bh(&addrconf_hash_lock);
+ for (ifap = &inet6_addr_lst[hash]; (ifa=*ifap) != NULL;
+ ifap = &ifa->lst_next) {
+ if (ifa == ifp) {
+ *ifap = ifa->lst_next;
+ __in6_ifa_put(ifp);
+ ifa->lst_next = NULL;
+ break;
+ }
+ }
+ write_unlock_bh(&addrconf_hash_lock);
+
+ write_lock_bh(&idev->lock);
+#ifdef CONFIG_IPV6_PRIVACY
+ if (ifp->flags&IFA_F_TEMPORARY) {
+ for (ifap = &idev->tempaddr_list; (ifa=*ifap) != NULL;
+ ifap = &ifa->tmp_next) {
+ if (ifa == ifp) {
+ *ifap = ifa->tmp_next;
+ if (ifp->ifpub) {
+ __in6_ifa_put(ifp->ifpub);
+ ifp->ifpub = NULL;
+ }
+ __in6_ifa_put(ifp);
+ ifa->tmp_next = NULL;
+ break;
+ }
+ }
+ }
+#endif
+ for (ifap = &idev->addr_list; (ifa=*ifap) != NULL;
+ ifap = &ifa->if_next) {
+ if (ifa == ifp) {
+ *ifap = ifa->if_next;
+ __in6_ifa_put(ifp);
+ ifa->if_next = NULL;
+ break;
+ }
+ }
+
+ /* XXX: if ifp is public, also delete tmporary adddress */
+
+ write_unlock_bh(&idev->lock);
+
+ ipv6_ifa_notify(RTM_DELADDR, ifp);
+
+ notifier_call_chain(&inet6addr_chain,NETDEV_DOWN,ifp);
+
+ addrconf_del_timer(ifp);
+
+ in6_ifa_put(ifp);
+}
+
+#ifdef CONFIG_IPV6_PRIVACY
+static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, struct inet6_ifaddr *ift)
+{
+ struct inet6_dev *idev;
+ struct in6_addr addr, *tmpaddr;
+ unsigned long tmp_prefered_lft, tmp_valid_lft;
+ int tmp_plen;
+ int ret = 0;
+ int max_addresses;
+
+ ADBG3((KERN_DEBUG
+ "ipv6_create_tempaddr(ifp=%p, ift=%p)\n",
+ ifp, ift));
+
+ if (ift) {
+ spin_lock_bh(&ift->lock);
+ memcpy(&addr.s6_addr[8], &ift->addr.s6_addr[8], 8);
+ spin_unlock_bh(&ift->lock);
+ tmpaddr = &addr;
+ } else {
+ tmpaddr = NULL;
+ }
+retry:
+ spin_lock_bh(&ifp->lock);
+ in6_ifa_hold(ifp);
+ idev = ifp->idev;
+ in6_dev_hold(idev);
+ memcpy(addr.s6_addr, ifp->addr.s6_addr, 8);
+ write_lock(&idev->lock);
+ if (idev->cnf.use_tempaddr <= 0) {
+ write_unlock(&idev->lock);
+ spin_unlock_bh(&ifp->lock);
+ ADBG2((KERN_INFO
+ "ipv6_create_tempaddr(): use_tempaddr is disabled.\n"));
+ in6_dev_put(idev);
+ in6_ifa_put(ifp);
+ ret = -1;
+ goto out;
+ }
+ if (ifp->regen_count++ >= idev->cnf.regen_max_retry) {
+ idev->cnf.use_tempaddr = -1; /*XXX*/
+ write_unlock(&idev->lock);
+ spin_unlock_bh(&ifp->lock);
+ ADBG1((KERN_WARNING
+ "ipv6_create_tempaddr(): regeneration time exceeded. disabled temporary address support.\n"));
+ in6_dev_put(idev);
+ in6_ifa_put(ifp);
+ ret = -1;
+ goto out;
+ }
+ if (__ipv6_try_regen_rndid(idev, tmpaddr) < 0) {
+ write_unlock(&idev->lock);
+ spin_unlock_bh(&ifp->lock);
+ ADBG1((KERN_WARNING
+ "ipv6_create_tempaddr(): regeneration of randomized interface id failed.\n"));
+ in6_dev_put(idev);
+ in6_ifa_put(ifp);
+ ret = -1;
+ goto out;
+ }
+ memcpy(&addr.s6_addr[8], idev->rndid, 8);
+ tmp_valid_lft = min_t(__u32,
+ ifp->valid_lft,
+ idev->cnf.temp_valid_lft);
+ tmp_prefered_lft = min_t(__u32,
+ ifp->prefered_lft,
+ idev->cnf.temp_prefered_lft - desync_factor / HZ);
+ tmp_plen = ifp->prefix_len;
+ max_addresses = idev->cnf.max_addresses;
+ write_unlock(&idev->lock);
+ spin_unlock_bh(&ifp->lock);
+ ift = !max_addresses ||
+ ipv6_count_addresses(idev) < max_addresses ?
+ ipv6_add_addr(idev, &addr, tmp_plen,
+ ipv6_addr_type(&addr)&IPV6_ADDR_SCOPE_MASK, IFA_F_TEMPORARY) : 0;
+ if (!ift) {
+ in6_dev_put(idev);
+ in6_ifa_put(ifp);
+ ADBG2((KERN_WARNING
+ "ipv6_create_tempaddr(): retry temporary address regeneration.\n"));
+ tmpaddr = &addr;
+ goto retry;
+ }
+ spin_lock_bh(&ift->lock);
+ ift->ifpub = ifp;
+ ift->valid_lft = tmp_valid_lft;
+ ift->prefered_lft = tmp_prefered_lft;
+ ift->tstamp = ifp->tstamp;
+ spin_unlock_bh(&ift->lock);
+ addrconf_dad_start(ift, RTF_ADDRCONF|RTF_PREFIX_RT);
+ in6_ifa_put(ift);
+ in6_dev_put(idev);
+out:
+ return ret;
+}
+
+#endif
+
+int ipv6_get_prefix_entries(struct prefix_info **plist, int ifindex, int plen)
+{
+ int count;
+ struct net_device *dev;
+ struct inet6_dev *idev;
+ struct list_head *head;
+ struct prefix_element *p;
+
+ if (plist == NULL) {
+ BUG_TRAP(plist != NULL);
+ return -EINVAL;
+ }
+ if ((dev = dev_get_by_index(ifindex)) == NULL) {
+ printk(KERN_WARNING "Bad I/F (%d) in ipv6_get_prefix_entries\n",
+ ifindex);
+ return -EINVAL;
+ }
+
+ if ((idev = __in6_dev_get(dev)) == NULL) {
+ dev_put(dev);
+ return -EINVAL;
+ }
+
+ read_lock_bh(&idev->lock);
+ if (!(count = idev->prefix_count)) {
+ /* No elements on list */
+ goto out;
+ }
+ if ((*plist = kmalloc(count * sizeof(struct prefix_info),
+ GFP_ATOMIC)) == NULL) {
+ count = -ENOMEM;
+ goto out;
+ }
+ count = 0;
+ spin_lock_bh(&idev->prefix_lock);
+ list_for_each(head, &idev->prefix_list) {
+ p = list_entry(head, struct prefix_element, list);
+ if (plen == 0 || p->pinfo.prefix_len == plen) {
+ memcpy(*plist + count, &p->pinfo,
+ sizeof(struct prefix_info));
+ count++;
+ }
+ }
+ spin_unlock_bh(&idev->prefix_lock);
+ out:
+ read_unlock_bh(&idev->lock);
+ dev_put(dev);
+ return count;
+}
+
+/*
+ * Choose an appropriate source address
+ * draft-ietf-ipv6-default-addr-select-09.txt
+ */
+#define IPV6_SADDRSELECT_SELF 0x01
+#define IPV6_SADDRSELECT_PREFERRED 0x02
+#define IPV6_SADDRSELECT_HOME 0x04
+#define IPV6_SADDRSELECT_PUBLIC 0x08
+#define IPV6_SADDRSELECT_INTERFACE 0x10
+#define IPV6_SADDRSELECT_LABEL 0x20
+
+struct addrselect_attrs {
+ struct inet6_ifaddr *ifp;
+ u16 flags;
+ s16 matchlen;
+ u8 scope;
+};
+
+static int __inline__ ipv6_addrselect_preferred(int type)
+{
+#if 0
+ if (type == IPV6_ADDR_ANY)
+ return -1;
+#endif
+ /* section 3.3, 3.4 */
+ if (type&(IPV6_ADDR_MAPPED|IPV6_ADDR_COMPATv4|
+ IPV6_ADDR_LOOPBACK|IPV6_ADDR_RESERVED))
+ return 1;
+ return 0;
+}
+
+int ipv6_dev_get_saddr(struct net_device *daddr_dev,
+ struct in6_addr *daddr, struct in6_addr *saddr,
+ int pref_privacy)
+{
+ int daddr_type, daddr_scope;
+ u32 daddr_label;
+ struct inet6_ifaddr *ifp0, *ifp = NULL;
+ struct net_device *dev;
+ struct inet6_dev *idev;
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ char daddrbuf[128];
+ char saddrbuf[128];
+#endif
+
+ int err;
+ int update;
+ struct addrselect_attrs candidate = {NULL,0,0,0};
+#if defined(CONFIG_IPV6_PRIVACY)
+ u16 invpref = 0;
+#endif
+
+#ifdef CONFIG_IPV6_PRIVACY
+ if (pref_privacy > 0 ||
+ (pref_privacy == 0 && ipv6_devconf.use_tempaddr > 1))
+ invpref |= IPV6_SADDRSELECT_PUBLIC;
+#endif
+
+ daddr_type = __ipv6_addr_type(daddr);
+ daddr_scope = __ipv6_addr_src_scope(daddr_type);
+ daddr_label = ipv6_addrselect_label_lookup(daddr, daddr_dev?daddr_dev->ifindex:0);
+
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ in6_ntop(daddr, daddrbuf);
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(daddr_dev=%p(%s), daddr=%s, saddr=%p, pref_privacy=%d)\n",
+ daddr_dev, daddr_dev ? daddr_dev->name : "<null>",
+ daddrbuf, saddr, pref_privacy));
+#endif
+
+ read_lock(&dev_base_lock);
+ read_lock(&addrconf_lock);
+ for (dev = dev_base; dev; dev=dev->next) {
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): dev=%p(%s)\n", dev, dev->name));
+#endif
+
+ /* Rule 0: Candidate Source Address (section 4)
+ * - multicast and link-local destination address,
+ * the set of candidate source address MUST only
+ * include addresses assigned to interfaces
+ * belonging to the same link as the outgoing
+ * interface.
+ * (- For site-local destination addresses, the
+ * set of candidate source addresses MUST only
+ * include addresses assigned to interfaces
+ * belonging to the same site as the outgoing
+ * interface.)
+ */
+ if ((daddr_type&IPV6_ADDR_MULTICAST ||
+ daddr_scope <= IPV6_ADDR_SCOPE_LINKLOCAL) &&
+ daddr_dev && dev != daddr_dev)
+ continue;
+
+ idev = __in6_dev_get(dev);
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): idev=%p\n", idev));
+#endif
+ if (!idev)
+ continue;
+
+ read_lock_bh(&idev->lock);
+ ifp0 = idev->addr_list;
+ for (ifp=ifp0; ifp; ifp=ifp->if_next) {
+ struct addrselect_attrs temp = {NULL,0,0,0};
+ int addr_type;
+ update = 0;
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ in6_ntop(&ifp->addr, saddrbuf);
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): ifp=%p(ifp->addr=%s)\n", ifp, saddrbuf));
+#endif
+
+ /* Rule 0: Candidate Source Address (section 4)
+ * - In any case, anycast addresses, multicast
+ * addresses, and the unspecified address MUST
+ * NOT be included in a candidate set.
+ */
+ addr_type = __ipv6_addr_type(&ifp->addr);
+ if (addr_type == IPV6_ADDR_ANY ||
+ addr_type&IPV6_ADDR_MULTICAST)
+ continue;
+
+ /* Rule 1: Prefer same address */
+ if (ipv6_addr_cmp(&ifp->addr, daddr) == 0)
+ temp.flags |= IPV6_SADDRSELECT_SELF;
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): update=%d, Rule 1: %d vs %d\n",
+ update,
+ temp.flags&IPV6_SADDRSELECT_SELF?1:0,
+ candidate.flags&IPV6_SADDRSELECT_SELF?1:0));
+#endif
+ if ((temp.flags^candidate.flags)&IPV6_SADDRSELECT_SELF) {
+ update = temp.flags&IPV6_SADDRSELECT_SELF;
+ if (!update) {
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): lose at Rule 1\n"));
+#endif
+ continue;
+ }
+ }
+
+ /* Rule 2: Prefer appropriate scope */
+ temp.scope = __ipv6_addr_src_scope(addr_type);
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): update=%d, Rule 2: %u vs %u\n",
+ update,
+ temp.scope, candidate.scope));
+#endif
+ if (!update) {
+ update = temp.scope - candidate.scope;
+ if (update > 0) {
+ update = candidate.scope < daddr_scope ? 1 : -1;
+ } else if (update < 0) {
+ update = temp.scope < daddr_scope ? -1 : 1;
+ }
+ if (update < 0) {
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): lose at Rule 2\n"));
+#endif
+ continue;
+ }
+ }
+
+ /* Rule 3: Avoid deprecated address */
+ if (ipv6_addrselect_preferred(addr_type) ||
+ !(ifp->flags & IFA_F_DEPRECATED))
+ temp.flags |= IPV6_SADDRSELECT_PREFERRED;
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): update=%d, Rule 3: %d vs %d\n",
+ update,
+ temp.flags&IPV6_SADDRSELECT_PREFERRED?1:0,
+ candidate.flags&IPV6_SADDRSELECT_PREFERRED?1:0));
+#endif
+ if (!update && (temp.flags^candidate.flags)&IPV6_SADDRSELECT_PREFERRED) {
+ update = temp.flags&IPV6_SADDRSELECT_PREFERRED;
+ if (!update) {
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): lose at Rule 3\n"));
+#endif
+ continue;
+ }
+ }
+
+ /* XXX: Rule 4: Prefer home address */
+
+ /* Rule 5: Prefer outgoing interface */
+ if (daddr_dev == NULL || daddr_dev == dev)
+ temp.flags |= IPV6_SADDRSELECT_INTERFACE;
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): update=%d, Rule 5: %d vs %d\n",
+ update,
+ temp.flags&IPV6_SADDRSELECT_INTERFACE?1:0,
+ candidate.flags&IPV6_SADDRSELECT_INTERFACE?1:0));
+#endif
+ if (!update && (temp.flags^candidate.flags)&IPV6_SADDRSELECT_INTERFACE) {
+ update = temp.flags&IPV6_SADDRSELECT_INTERFACE;
+ if (!update) {
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): lose at Rule 5\n"));
+#endif
+ continue;
+ }
+ }
+
+ /* Rule 6: Prefer matching label */
+ if (ipv6_addrselect_label_lookup(&ifp->addr, dev->ifindex) == daddr_label)
+ temp.flags |= IPV6_SADDRSELECT_LABEL;
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): update=%d, Rule 6: %d vs %d\n",
+ update,
+ temp.flags&IPV6_SADDRSELECT_LABEL?1:0,
+ candidate.flags&IPV6_SADDRSELECT_LABEL?1:0));
+#endif
+ if (!update && (temp.flags^candidate.flags)&IPV6_SADDRSELECT_LABEL) {
+ update = temp.flags&IPV6_SADDRSELECT_LABEL;
+ if (!update) {
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): lose at Rule 6\n"));
+#endif
+ continue;
+ }
+ }
+
+ /* Rule 7: Prefer public address */
+#ifdef CONFIG_IPV6_PRIVACY
+ if (!(ifp->flags & IFA_F_TEMPORARY))
+ temp.flags |= IPV6_SADDRSELECT_PUBLIC;
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): update=%d, Rule 7: %d vs %d\n",
+ update,
+ temp.flags&IPV6_SADDRSELECT_PUBLIC?1:0,
+ candidate.flags&IPV6_SADDRSELECT_PUBLIC?1:0));
+#endif
+ if (!update && (temp.flags^candidate.flags)&IPV6_SADDRSELECT_PUBLIC) {
+ update = (temp.flags^invpref)&IPV6_SADDRSELECT_PUBLIC;
+ if (!update) {
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): lose at Rule 7\n"));
+#endif
+ continue;
+ }
+ }
+#endif
+
+ /* Rule 8: Use longest matching prefix */
+ temp.matchlen = ipv6_addr_diff(&ifp->addr, daddr);
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): update=%d, Rule 8: %d vs %d\n",
+ update,
+ temp.matchlen,candidate.matchlen));
+#endif
+ if (!update) {
+ update = temp.matchlen - candidate.matchlen;
+ if (update < 0) {
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): lose at Rule 8\n"));
+#endif
+ continue;
+ }
+ }
+
+ /* Final Rule */
+ if (!update && candidate.ifp) {
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): candidate already exists\n"));
+ continue;
+ }
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_dev_get_saddr(): update=%d, candidate updated; %s\n",
+ update,
+ saddrbuf));
+#endif
+
+ /* update candidate */
+ temp.ifp = ifp;
+ in6_ifa_hold(ifp);
+ if (candidate.ifp)
+ in6_ifa_put(candidate.ifp);
+ candidate = temp;
+ }
+ read_unlock_bh(&idev->lock);
+ }
+ read_unlock(&addrconf_lock);
+ read_unlock(&dev_base_lock);
+
+ if (candidate.ifp) {
+ ipv6_addr_copy(saddr, &candidate.ifp->addr);
+ in6_ifa_put(candidate.ifp);
+ err = 0;
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ in6_ntop(saddr, saddrbuf);
+ ADBG3((KERN_DEBUG "ipv6_dev_get_saddr(): %s is selected\n", saddrbuf));
+#endif
+ } else {
+ err = -EADDRNOTAVAIL;
+ }
+ return err;
+}
+
+int ipv6_get_saddr(struct dst_entry *dst,
+ struct in6_addr *daddr, struct in6_addr *saddr,
+ int pref_privacy)
+{
+ return ipv6_dev_get_saddr(dst ? ((struct rt6_info *)dst)->rt6i_dev : NULL,
+ daddr, saddr, pref_privacy);
+}
+
+int ipv6_get_lladdr(struct net_device *dev, struct in6_addr *addr)
+{
+ struct inet6_dev *idev;
+ int err = -EADDRNOTAVAIL;
+
+ read_lock(&addrconf_lock);
+ if ((idev = __in6_dev_get(dev)) != NULL) {
+ struct inet6_ifaddr *ifp;
+
+ read_lock_bh(&idev->lock);
+ for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
+ if (ifp->scope == IFA_LINK && !(ifp->flags&IFA_F_TENTATIVE)) {
+ ipv6_addr_copy(addr, &ifp->addr);
+ err = 0;
+ break;
+ }
+ }
+ read_unlock_bh(&idev->lock);
+ }
+ read_unlock(&addrconf_lock);
+ return err;
+}
+
+static int __ipv6_count_addresses(struct inet6_dev *idev)
+{
+ int cnt = 0;
+ struct inet6_ifaddr *ifp;
+
+ for (ifp=idev->addr_list; ifp; ifp=ifp->if_next)
+ cnt++;
+ return cnt;
+}
+
+int ipv6_count_addresses(struct inet6_dev *idev)
+{
+ int cnt;
+
+ read_lock_bh(&idev->lock);
+ cnt = __ipv6_count_addresses(idev);
+ read_unlock_bh(&idev->lock);
+ return cnt;
+}
+
+int ipv6_chk_addr(struct in6_addr *addr, struct net_device *dev)
+{
+ struct inet6_ifaddr * ifp;
+ u8 hash = ipv6_addr_hash(addr);
+
+ read_lock_bh(&addrconf_hash_lock);
+ for(ifp = inet6_addr_lst[hash]; ifp; ifp=ifp->lst_next) {
+ if (ipv6_addr_cmp(&ifp->addr, addr) == 0 &&
+ !(ifp->flags&IFA_F_TENTATIVE)) {
+ if (dev == NULL || ifp->idev->dev == dev ||
+ !(ifp->scope&(IFA_LINK|IFA_HOST)))
+ break;
+ }
+ }
+ read_unlock_bh(&addrconf_hash_lock);
+ return ifp != NULL;
+}
+
+static
+int ipv6_chk_same_addr(const struct in6_addr *addr, struct net_device *dev)
+{
+ struct inet6_ifaddr * ifp;
+ u8 hash = ipv6_addr_hash(addr);
+
+ read_lock_bh(&addrconf_hash_lock);
+ for(ifp = inet6_addr_lst[hash]; ifp; ifp=ifp->lst_next) {
+ if (ipv6_addr_cmp(&ifp->addr, addr) == 0) {
+ if (dev == NULL || ifp->idev->dev == dev)
+ break;
+ }
+ }
+ read_unlock_bh(&addrconf_hash_lock);
+ return ifp != NULL;
+}
+
+struct inet6_ifaddr * ipv6_get_ifaddr(struct in6_addr *addr, struct net_device *dev)
+{
+ struct inet6_ifaddr * ifp;
+ u8 hash = ipv6_addr_hash(addr);
+
+ read_lock_bh(&addrconf_hash_lock);
+ for(ifp = inet6_addr_lst[hash]; ifp; ifp=ifp->lst_next) {
+ if (ipv6_addr_cmp(&ifp->addr, addr) == 0) {
+ if (dev == NULL || ifp->idev->dev == dev) {
+ if (!(ifp->scope&IFA_HOST)) {
+ in6_ifa_hold(ifp);
+ break;
+ }
+ }
+ }
+ }
+ read_unlock_bh(&addrconf_hash_lock);
+
+ return ifp;
+}
+
+/* address selection: policy label */
+/* default policy table in rfc 3484 */
+int ipv6_addrselect_add_label(int ifindex, struct in6_addr *addr, int plen, int label)
+{
+ struct ipv6_addrselect_label *p, *p_last;
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ char abuf[128];
+
+ in6_ntop(addr, abuf);
+ ADBG3((KERN_DEBUG
+ "ipv6_addrselect_add_label(ifindex=%d, addr=%s, plen=%d, label=%d)\n",
+ ifindex, abuf, plen, label));
+#endif
+
+ /* Update label on duplicate addresses */
+ write_lock_bh(&addrselect_label_lock);
+ p_last = ipv6_addrselect_label_table;
+ for (p = ipv6_addrselect_label_table;
+ p;
+ p = p->next) {
+ if (!ipv6_prefix_cmp(addr, &p->addr, p->plen) && plen == p->plen) {
+ p->ifindex = ifindex;
+ p->label = label;
+ write_unlock_bh(&addrselect_label_lock);
+ return 0;
+ }
+ p_last = p;
+ }
+
+ p = kmalloc(sizeof(struct ipv6_addrselect_label), GFP_KERNEL);
+ if (p == NULL) {
+ ADBG1((KERN_WARNING
+ "ipv6_addrselect_add_label: malloc failed\n"));
+ write_unlock_bh(&addrselect_label_lock);
+ return -ENOMEM;
+ }
+
+ memset(p, 0, sizeof(struct ipv6_addrselect_label));
+
+ if (!ipv6_addrselect_label_table)
+ ipv6_addrselect_label_table = p;
+ else
+ p_last->next = p;
+ p->addr = *addr;
+ p->plen = plen;
+ p->ifindex = ifindex;
+ p->label = label;
+ write_unlock_bh(&addrselect_label_lock);
+ return 0;
+}
+
+int ipv6_addrselect_del_label(struct in6_addr *addr, int plen)
+{
+ struct ipv6_addrselect_label *p, *p_last;
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ char abuf[128];
+
+ in6_ntop(addr, abuf);
+ ADBG3((KERN_DEBUG
+ "ipv6_addrselect_del_label(addr=%s, plen=%d)\n",
+ abuf, plen));
+#endif
+
+ write_lock_bh(&addrselect_label_lock);
+ p_last = ipv6_addrselect_label_table;
+ for (p = ipv6_addrselect_label_table;
+ p;
+ p = p->next) {
+ if (!ipv6_prefix_cmp(addr, &p->addr, p->plen) && plen == p->plen) {
+
+ if (ipv6_addrselect_label_table == p) {
+ ipv6_addrselect_label_table = p->next;
+ } else {
+ p_last->next = p->next;
+ }
+ kfree(p);
+ write_unlock_bh(&addrselect_label_lock);
+ return 0;
+ }
+ p_last = p;
+ }
+ write_unlock_bh(&addrselect_label_lock);
+ return -ENOENT;
+}
+
+int addrconf_label_ioctl(unsigned int cmd, void *arg)
+{
+ struct in6_addrlabelreq req;
+ int err = -EINVAL;
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ switch(cmd) {
+ case SIOCSDADDRLABEL:
+ err = copy_from_user(&req, arg, sizeof(struct in6_addrlabelreq));
+ if (err)
+ return -EFAULT;
+ err = ipv6_addrselect_add_label(req.ifindex, &req.addr, req.plen, req.label);
+ break;
+
+ case SIOCDDADDRLABEL:
+ err = copy_from_user(&req, arg, sizeof(struct in6_addrlabelreq));
+ if (err)
+ return -EFAULT;
+ err = ipv6_addrselect_del_label(&req.addr, req.plen);
+ break;
+ }
+ return err;
+}
+
+static u32 ipv6_addrselect_label_lookup(const struct in6_addr *addr, int ifindex)
+{
+ struct ipv6_addrselect_label *p;
+ int plen, matchlen = -1;
+ u32 label = 0xffffffff;
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ char buf[64];
+
+ in6_ntop(addr, buf);
+ ADBG3((KERN_DEBUG
+ "ipv6_addrselect_label_lookup(addr=%s, ifindex=%d)\n",
+ buf, ifindex));
+#endif
+
+ read_lock_bh(&addrselect_label_lock);
+ for (p = ipv6_addrselect_label_table;
+ p;
+ p = p->next) {
+ if (ifindex && p->ifindex && ifindex != p->ifindex)
+ continue;
+ plen = ipv6_addr_diff(addr, &p->addr);
+ if (plen < p->plen || plen < matchlen)
+ continue;
+ matchlen = plen;
+ label = p->label;
+ }
+#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
+ ADBG3((KERN_DEBUG
+ "ipv6_addrselect_label_lookup() = %u\n",
+ label));
+#endif
+ read_unlock_bh(&addrselect_label_lock);
+ return label;
+}
+
+
+static struct ipv6_addrselect_label ipv6_addrselect_label_init_table[] = {
+ /* prefix = ::1/128, label = 0 */
+ {
+ addr: {{{ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 }}},
+ plen: 128,
+ label: 0,
+ },
+ /* prefix = ::/0, label = 1 */
+ {
+ label: 1,
+ },
+ /* prefix = 2002::/16, label = 2 */
+ {
+ addr: {{{ 0x20,0x02,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }}},
+ plen: 16,
+ label: 2,
+ },
+ /* prefix = ::/96, label = 3 */
+ {
+ plen: 96,
+ label: 3,
+ },
+ /* prefix = ::ffff:0:0/96, label = 4 */
+ {
+ addr: {{{ 0,0,0,0,0,0,0,0,0,0xff,0xff,0,0,0,0 }}},
+ plen: 96,
+ label: 4,
+ },
+ /* sentinel */
+ {
+ label: 0xffffffff,
+ }
+};
+
+void ipv6_addrselect_init(void)
+{
+ struct ipv6_addrselect_label **pnext, *p0;
+
+ for (p0 = ipv6_addrselect_label_init_table,
+ pnext = &ipv6_addrselect_label_table;
+ p0->label != 0xffffffff;
+ p0++,
+ pnext = &(*pnext)->next) {
+ *pnext = kmalloc(sizeof(**pnext), GFP_ATOMIC);
+ if (*pnext == NULL) {
+ printk(KERN_WARNING
+ "%s(): failed to allocate memory\n",
+ __FUNCTION__);
+ break;
+ }
+ memcpy(*pnext, p0, sizeof(**pnext));
+ }
+};
+
+#ifdef CONFIG_PROC_FS
+static int ipv6_addrselect_label_proc_info(char *buffer, char **start, off_t offset,
+ int length)
+{
+ struct ipv6_addrselect_label *p;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+
+ read_lock_bh(&addrselect_label_lock);
+ for (p = ipv6_addrselect_label_table;
+ p;
+ p = p->next) {
+ int i;
+ for (i = 0; i < 16; i++) {
+ sprintf(buffer + len, "%02x", p->addr.s6_addr[i]);
+ len += 2;
+ }
+
+ len += sprintf(buffer + len,
+ " %02x %02x %02x\n",
+ p->plen,
+ p->ifindex,
+ p->label);
+ pos = begin + len;
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if(pos > offset + length) {
+ read_unlock_bh(&addrselect_label_lock);
+ goto done;
+ }
+ }
+ read_unlock_bh(&addrselect_label_lock);
+
+ done:
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+#endif /* CONFIG_PROC_FS */
+
+
+/* Gets referenced address, destroys ifaddr */
+
+void addrconf_dad_failure(struct inet6_ifaddr *ifp)
+{
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ ADBG3((KERN_DEBUG
+ "addrconf_dad_failure(ifp=%p): jiffies=%lu\n",
+ ifp, jiffies));
+#endif
+ if (net_ratelimit())
+ ADBG1((KERN_WARNING
+ "%s: duplicate address detected!\n",
+ ifp->idev->dev->name));
+ spin_lock_bh(&ifp->lock);
+ if (ifp->flags&IFA_F_PERMANENT) {
+ addrconf_del_timer(ifp);
+ ifp->flags |= IFA_F_TENTATIVE;
+ spin_unlock_bh(&ifp->lock);
+ in6_ifa_put(ifp);
+#ifdef CONFIG_IPV6_PRIVACY
+ } else if (ifp->flags&IFA_F_TEMPORARY) {
+ struct inet6_ifaddr *ifpub;
+ ifpub = ifp->ifpub;
+ if (ifpub) {
+ in6_ifa_hold(ifpub);
+ spin_unlock_bh(&ifp->lock);
+ ipv6_create_tempaddr(ifpub, ifp);
+ in6_ifa_put(ifpub);
+ } else {
+ spin_unlock_bh(&ifp->lock);
+ }
+ ipv6_del_addr(ifp);
+#endif
+ } else {
+ spin_unlock_bh(&ifp->lock);
+ ipv6_del_addr(ifp);
+ }
+}
+
+
+/* Join to solicited addr multicast group. */
+
+void addrconf_join_solict(struct net_device *dev, struct in6_addr *addr)
+{
+ struct in6_addr maddr;
+
+ if (dev->flags&(IFF_LOOPBACK|IFF_NOARP))
+ return;
+
+ addrconf_addr_solict_mult(addr, &maddr);
+ ipv6_dev_mc_inc(dev, &maddr);
+}
+
+void addrconf_leave_solict(struct net_device *dev, struct in6_addr *addr)
+{
+ struct in6_addr maddr;
+
+ if (dev->flags&(IFF_LOOPBACK|IFF_NOARP))
+ return;
+
+ addrconf_addr_solict_mult(addr, &maddr);
+ ipv6_dev_mc_dec(dev, &maddr);
+}
+
+
+static int ipv6_generate_eui64(u8 *eui, struct net_device *dev)
+{
+ switch (dev->type) {
+ case ARPHRD_ETHER:
+ case ARPHRD_FDDI:
+ case ARPHRD_IEEE802_TR:
+ if (dev->addr_len != ETH_ALEN)
+ return -1;
+ memcpy(eui, dev->dev_addr, 3);
+ memcpy(eui + 5, dev->dev_addr+3, 3);
+ eui[3] = 0xFF;
+ eui[4] = 0xFE;
+ eui[0] ^= 2;
+ return 0;
+ case ARPHRD_ARCNET:
+ /* XXX: inherit EUI-64 fro mother interface -- yoshfuji */
+ if (dev->addr_len != ARCNET_ALEN)
+ return -1;
+ memset(eui, 0, 7);
+ eui[7] = *(u8*)dev->dev_addr;
+ return 0;
+ }
+ return -1;
+}
+
+static int ipv6_inherit_eui64(u8 *eui, struct inet6_dev *idev)
+{
+ int err = -1;
+ struct inet6_ifaddr *ifp;
+
+ read_lock_bh(&idev->lock);
+ for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
+ if (ifp->scope == IFA_LINK && !(ifp->flags&IFA_F_TENTATIVE)) {
+ memcpy(eui, ifp->addr.s6_addr+8, 8);
+ err = 0;
+ break;
+ }
+ }
+ read_unlock_bh(&idev->lock);
+ return err;
+}
+
+#ifdef CONFIG_IPV6_PRIVACY
+/* (re)generation of randomized interface identifier (RFC 3041 3.2, 3.5) */
+static int __ipv6_regen_rndid(struct inet6_dev *idev)
+{
+ struct net_device *dev;
+ u8 eui64[8];
+ u8 digest[16];
+ struct scatterlist sg[2];
+
+ ADBG3((KERN_DEBUG
+ "__ipv6_regen_rndid(idev=%p)\n",
+ idev));
+
+ if (!del_timer(&idev->regen_timer))
+ in6_dev_hold(idev);
+
+ dev = idev->dev;
+#if 0
+ if (!dev) {
+ panic("__ipv6_regen_rndid(idev=%p): idev->dev=NULL\n");
+ }
+#endif
+ if (ipv6_generate_eui64(eui64, dev)) {
+ ADBG1((KERN_WARNING
+ "__ipv6_regen_rndid(idev=%p): cannot get EUI64 identifier; use random bytes.\n",
+ idev));
+ get_random_bytes(eui64, sizeof(eui64));
+ }
+
+ sg[0].page = virt_to_page(idev->entropy);
+ sg[0].offset = ((long) idev->entropy & ~PAGE_MASK);
+ sg[0].length = 8;
+ sg[1].page = virt_to_page(eui64);
+ sg[1].offset = ((long) eui64 & ~PAGE_MASK);
+ sg[1].length = 8;
+regen:
+ spin_lock(&md5_tfm_lock);
+ if (md5_tfm == NULL) {
+ spin_unlock(&md5_tfm_lock);
+ in6_dev_put(idev);
+ return -1;
+ }
+ crypto_digest_init(md5_tfm);
+ crypto_digest_update(md5_tfm, sg, 2);
+ crypto_digest_final(md5_tfm, digest);
+ spin_unlock(&md5_tfm_lock);
+
+ memcpy(idev->rndid, &digest[0], 8);
+ idev->rndid[0] &= ~0x02;
+ memcpy(idev->entropy, &digest[8], 8);
+
+ /*
+ * <draft-ietf-ipngwg-temp-addresses-v2-00.txt>:
+ * check if generated address is not inappropriate
+ *
+ * - Reserved subnet anycast (RFC 2526)
+ * 11111101 11....11 1xxxxxxx
+ * - ISATAP (draft-ietf-ngtrans-isatap-01.txt) 4.3
+ * 00-00-5E-FE-xx-xx-xx-xx
+ * - value 0
+ * - XXX: already assigned to an address on the device
+ */
+ if (idev->rndid[0] == 0xfd &&
+ (idev->rndid[1]&idev->rndid[2]&idev->rndid[3]&idev->rndid[4]&idev->rndid[5]&idev->rndid[6]) == 0xff &&
+ (idev->rndid[7]&0x80))
+ goto regen;
+ if ((idev->rndid[0]|idev->rndid[1]) == 0) {
+ if (idev->rndid[2] == 0x5e && idev->rndid[3] == 0xfe)
+ goto regen;
+ if ((idev->rndid[2]|idev->rndid[3]|idev->rndid[4]|idev->rndid[5]|idev->rndid[6]|idev->rndid[7]) == 0x00)
+ goto regen;
+ }
+
+ ADBG3((KERN_INFO
+ "__ipv6_regen_rndid(): new rndid for %s = %02x%02x:%02x%02x:%02x%02x:%02x%02x\n",
+ idev->dev->name,
+ idev->rndid[0], idev->rndid[1], idev->rndid[2], idev->rndid[3],
+ idev->rndid[4], idev->rndid[5], idev->rndid[6], idev->rndid[7]));
+
+ idev->regen_timer.expires = jiffies +
+ idev->cnf.temp_prefered_lft * HZ -
+ idev->cnf.regen_max_retry * idev->cnf.dad_transmits * idev->nd_parms->retrans_time - desync_factor;
+ if (time_before(idev->regen_timer.expires, jiffies)) {
+ idev->regen_timer.expires = 0;
+ ADBG1((KERN_WARNING
+ "__ipv6_regen_rndid(): too short regeneration interval; timer diabled for %s.\n",
+ idev->dev->name));
+ in6_dev_put(idev);
+ return -1;
+ }
+
+ ADBG3((KERN_DEBUG
+ "__ipv6_regen_rndid(): next timer = %lu sec(s); temp_preferred_lft = %u\n",
+ (idev->regen_timer.expires - jiffies) / HZ,
+ idev->cnf.temp_prefered_lft));
+
+ add_timer(&idev->regen_timer);
+ return 0;
+}
+
+static void ipv6_regen_rndid(unsigned long data)
+{
+ struct inet6_dev *idev = (struct inet6_dev *) data;
+
+ ADBG3((KERN_DEBUG
+ "ipv6_regen_rndid(idev=%p)\n",
+ idev));
+
+ read_lock_bh(&addrconf_lock);
+ write_lock_bh(&idev->lock);
+ if (!idev->dead)
+ __ipv6_regen_rndid(idev);
+ write_unlock_bh(&idev->lock);
+ read_unlock_bh(&addrconf_lock);
+}
+
+static int __ipv6_try_regen_rndid(struct inet6_dev *idev, struct in6_addr *tmpaddr) {
+ int ret = 0;
+
+ ADBG3((KERN_DEBUG
+ "__ipv6_try_regen_rndid(idev=%p,tmpaddr=%p)\n",
+ idev, tmpaddr));
+
+ if (tmpaddr && memcmp(idev->rndid, &tmpaddr->s6_addr[8], 8) == 0)
+ ret = __ipv6_regen_rndid(idev);
+ return ret;
+}
+#endif
+
+/*
+ * Add prefix route.
+ */
+
+static void
+addrconf_prefix_route(struct in6_addr *pfx, int plen, struct net_device *dev,
+ unsigned long expires, unsigned flags)
+{
+ struct in6_rtmsg rtmsg;
+
+ memset(&rtmsg, 0, sizeof(rtmsg));
+ ipv6_addr_copy(&rtmsg.rtmsg_dst, pfx);
+ rtmsg.rtmsg_dst_len = plen;
+ rtmsg.rtmsg_metric = IP6_RT_PRIO_ADDRCONF;
+ rtmsg.rtmsg_ifindex = dev->ifindex;
+ rtmsg.rtmsg_info = expires;
+ rtmsg.rtmsg_flags = RTF_UP|flags;
+ rtmsg.rtmsg_type = RTMSG_NEWROUTE;
+
+ /* Prevent useless cloning on PtP SIT.
+ This thing is done here expecting that the whole
+ class of non-broadcast devices need not cloning.
+ */
+ if (
+#ifdef CONFIG_NET_IPIP_IPV6
+ dev->type == ARPHRD_TUNNEL
+#else
+ dev->type == ARPHRD_SIT
+#endif
+ && (dev->flags&IFF_POINTOPOINT))
+ rtmsg.rtmsg_flags |= RTF_NONEXTHOP;
+
+ ip6_route_add(&rtmsg, NULL);
+}
+
+/* Create "default" multicast route to the interface */
+
+static void addrconf_add_mroute(struct net_device *dev)
+{
+ struct in6_rtmsg rtmsg;
+
+ memset(&rtmsg, 0, sizeof(rtmsg));
+ ipv6_addr_set(&rtmsg.rtmsg_dst,
+ htonl(0xFF000000), 0, 0, 0);
+ rtmsg.rtmsg_dst_len = 8;
+ rtmsg.rtmsg_metric = IP6_RT_PRIO_ADDRCONF;
+ rtmsg.rtmsg_ifindex = dev->ifindex;
+ rtmsg.rtmsg_flags = RTF_UP;
+ rtmsg.rtmsg_type = RTMSG_NEWROUTE;
+ ip6_route_add(&rtmsg, NULL);
+}
+
+static void sit_route_add(struct net_device *dev)
+{
+ struct in6_rtmsg rtmsg;
+
+ memset(&rtmsg, 0, sizeof(rtmsg));
+
+ rtmsg.rtmsg_type = RTMSG_NEWROUTE;
+ rtmsg.rtmsg_metric = IP6_RT_PRIO_ADDRCONF;
+
+ /* prefix length - 96 bits "::d.d.d.d" */
+ rtmsg.rtmsg_dst_len = 96;
+ rtmsg.rtmsg_flags = RTF_UP|RTF_NONEXTHOP;
+ rtmsg.rtmsg_ifindex = dev->ifindex;
+
+ ip6_route_add(&rtmsg, NULL);
+}
+
+static void addrconf_add_lroute(struct net_device *dev)
+{
+ struct in6_addr addr;
+
+ ipv6_addr_set(&addr, htonl(0xFE800000), 0, 0, 0);
+ addrconf_prefix_route(&addr, 64, dev, 0, 0);
+}
+
+static struct inet6_dev *addrconf_add_dev(struct net_device *dev)
+{
+ struct inet6_dev *idev;
+
+ ASSERT_RTNL();
+
+ if ((idev = ipv6_find_idev(dev)) == NULL)
+ return NULL;
+
+ /* Add default multicast route */
+ addrconf_add_mroute(dev);
+
+ /* Add link local route */
+ addrconf_add_lroute(dev);
+ return idev;
+}
+
+#if 0
+#ifdef CONFIG_IPV6_PREFIXLIST
+static int ipv6_add_prefix(struct inet6_dev *idev, struct in6_addr *addr,
+ __u8 prefix_len, __u32 lifetime)
+{
+ struct in6_addr prefix;
+ struct list_head *pos;
+ struct inet6_prefix *pfx;
+#ifdef CONFIG_IPV6_PREFIXLIST_DEBUG
+ char abuf[64];
+#endif
+
+ ipv6_addr_prefix(&prefix, addr, (int) prefix_len);
+
+#ifdef CONFIG_IPV6_PREFIXLIST_DEBUG
+ in6_ntop(&prefix, abuf);
+#endif
+
+ /* Check if the prefix already exists in the list */
+ read_lock_bh(&idev->lock);
+ write_lock_bh(&idev->prefix_lock);
+ list_for_each(pos, &idev->prefix_list) {
+ pfx = list_entry(pos, struct inet6_prefix, list);
+ if (pfx->prefix_len == prefix_len && ipv6_addr_cmp(&pfx->prefix,
+ &prefix) == 0) {
+ pfx->lifetime = lifetime;
+ pfx->timestamp = jiffies;
+ write_unlock_bh(&idev->prefix_lock);
+ read_unlock_bh(&idev->lock);
+#ifdef CONFIG_IPV6_PREFIXLIST_DEBUG
+ printk(KERN_INFO "%s: changing prefix %s/%d, lifetime = %d\n",
+ __FUNCTION__, abuf, prefix_len, lifetime);
+#endif
+ return 0;
+ }
+ }
+
+ /* New Prefix, allocate one and fill in */
+ if ((pfx = kmalloc(sizeof(struct inet6_prefix), GFP_ATOMIC)) == NULL) {
+ ADBG(("ipv6_add_prefix: malloc failed\n"));
+ return -1;
+ }
+ INIT_LIST_HEAD(&pfx->list);
+ pfx->lifetime = lifetime;
+ pfx->timestamp = jiffies;
+ pfx->prefix_len = prefix_len;
+ ipv6_addr_copy(&pfx->prefix, &prefix);
+
+ list_add(&pfx->list, idev->prefix_list.prev); /* add to end of list */
+#ifdef CONFIG_IPV6_PREFIXLIST_DEBUG
+ printk(KERN_INFO "%s: adding prefix %s/%d, lifetime = %d\n",
+ __FUNCTION__, abuf, prefix_len, lifetime);
+#endif
+ write_unlock_bh(&idev->prefix_lock);
+ read_unlock_bh(&idev->lock);
+ return 0;
+}
+#endif
+#endif
+
+void ipv6_wash_prefix(struct in6_addr *pfx, int plen)
+{
+ int b = plen&0x7;
+ int o = (plen + 7)>>3;
+
+ if (o < 16)
+ memset(pfx->s6_addr + o, 0, 16 - o);
+ if (b != 0)
+ pfx->s6_addr[plen>>3] &= (0xFF<<(8-b));
+}
+
+#ifdef CONFIG_IPV6_PREFIXLIST
+static int ipv6_add_prefix(struct inet6_dev *idev, struct prefix_info *pinfo)
+{
+ int ifindex;
+ struct in6_addr prefix;
+ struct list_head *head;
+ struct prefix_element *p;
+
+ ipv6_addr_prefix(&prefix, &pinfo->prefix, (int)pinfo->prefix_len);
+
+ /* Check if the prefix already exists in the list */
+ read_lock_bh(&idev->lock);
+ spin_lock_bh(&idev->prefix_lock);
+
+ ifindex = idev->dev->ifindex;
+
+ list_for_each(head, &idev->prefix_list) {
+ p = list_entry(head, struct prefix_element, list);
+ if (p->pinfo.prefix_len == pinfo->prefix_len
+ && ipv6_addr_cmp(&p->pinfo.prefix, &prefix) == 0) {
+ p->pinfo.valid = pinfo->valid;
+ p->timestamp = jiffies;
+ spin_unlock_bh(&idev->prefix_lock);
+ read_unlock_bh(&idev->lock);
+ return 0;
+ }
+ }
+
+ /* New Prefix, allocate one and fill in */
+ if ((p = kmalloc(sizeof(struct prefix_element), GFP_ATOMIC)) == NULL) {
+ ADBG(("ipv6_add_prefix: malloc failed\n"));
+ spin_unlock_bh(&idev->prefix_lock);
+ read_unlock_bh(&idev->lock);
+ return -1;
+ }
+
+ memcpy(&p->pinfo, pinfo, sizeof(struct prefix_info));
+ ipv6_wash_prefix(&p->pinfo.prefix, pinfo->prefix_len);
+ p->timestamp = jiffies;
+
+ idev->prefix_count++;
+ list_add(&p->list, idev->prefix_list.prev); /* add to end of list */
+
+ spin_unlock_bh(&idev->prefix_lock);
+ read_unlock_bh(&idev->lock);
+
+ return 0;
+}
+#endif
+
+void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len)
+{
+ struct prefix_info *pinfo;
+ __u32 valid_lft;
+ __u32 prefered_lft;
+ int addr_type;
+ struct inet6_dev *in6_dev;
+
+ pinfo = (struct prefix_info *) opt;
+
+ if (len < sizeof(struct prefix_info)) {
+ ADBG2((KERN_WARNING
+ "addrconf: prefix option too short\n"));
+ return;
+ }
+
+ /*
+ * Validation checks ([ADDRCONF], page 19)
+ */
+
+ addr_type = ipv6_addr_type(&pinfo->prefix);
+
+ if (addr_type & (IPV6_ADDR_MULTICAST|IPV6_ADDR_LINKLOCAL))
+ return;
+
+ valid_lft = ntohl(pinfo->valid);
+ prefered_lft = ntohl(pinfo->prefered);
+ ADBG3((KERN_INFO
+ "Valid Lifetime of Received Prefix = %d\n", valid_lft));
+ ADBG3((KERN_INFO
+ "Prefered Lifetime of Received Prefix = %d\n", prefered_lft));
+
+ if (prefered_lft > valid_lft) {
+ if (net_ratelimit())
+ ADBG2((KERN_WARNING "addrconf: prefix option has invalid lifetime\n"));
+ return;
+ }
+
+ if (valid_lft >= 0x7FFFFFFF/HZ) {
+ /* Avoid arithemtic overflow */
+ if (net_ratelimit())
+ ADBG1((KERN_DEBUG "addrconf: valid lifetime %u is too long; adjusted to %u.\n",
+ valid_lft, 0x7FFFFFFF/HZ-1));
+ valid_lft = 0x7FFFFFFF/HZ-1;
+ if (prefered_lft > valid_lft)
+ prefered_lft = valid_lft;
+ }
+
+ in6_dev = in6_dev_get(dev);
+
+ if (in6_dev == NULL) {
+ if (net_ratelimit())
+ ADBG1((KERN_DEBUG
+ "addrconf: device %s not configured\n", dev->name));
+ return;
+ }
+
+ /*
+ * Two things going on here:
+ * 1) Add routes for on-link prefixes
+ * 2) Configure prefixes with the auto flag set
+ */
+
+ if (pinfo->onlink) {
+ struct rt6_info *rt;
+ unsigned long rt_expires = jiffies + valid_lft + HZ;
+
+ rt = rt6_lookup(&pinfo->prefix, NULL, dev->ifindex,
+ RT6_LOOKUP_FLAG_STRICT|RT6_LOOKUP_FLAG_NOUSE);
+
+ if (rt && ((rt->rt6i_flags & (RTF_GATEWAY | RTF_DEFAULT)) == 0)) {
+ if (rt->rt6i_flags&RTF_EXPIRES) {
+ if (valid_lft) {
+ rt->rt6i_expires = rt_expires;
+ } else {
+ ip6_del_rt(rt, NULL);
+ rt = NULL;
+ }
+ }
+ } else if (valid_lft) {
+ addrconf_prefix_route(&pinfo->prefix, pinfo->prefix_len,
+ dev, rt_expires, RTF_ADDRCONF|RTF_EXPIRES|RTF_PREFIX_RT);
+ }
+ if (rt)
+ dst_release(&rt->u.dst);
+#ifdef CONFIG_IPV6_PREFIXLIST
+ /* Add this prefix to the list of prefixes on this interface */
+ ipv6_add_prefix(in6_dev, pinfo);
+#endif
+ }
+
+ /* Try to figure out our local address for this prefix */
+
+ if (pinfo->autoconf && in6_dev->cnf.autoconf) {
+ struct inet6_ifaddr *ifp = NULL;
+#ifdef CONFIG_IPV6_PRIVACY
+ struct inet6_ifaddr *ift = NULL;
+#endif
+ int flags;
+ struct in6_addr addr;
+ int update_lft = 0, create = 0;
+ unsigned long now, stored_lft;
+
+ if (pinfo->prefix_len != 64) {
+ if (net_ratelimit())
+ ADBG2((KERN_DEBUG
+ "IPv6 addrconf: prefix with wrong length %d\n",
+ pinfo->prefix_len));
+ goto autoconf_fail;
+ }
+
+ memcpy(&addr, &pinfo->prefix, 8);
+ if (ipv6_generate_eui64(addr.s6_addr + 8, dev) &&
+ ipv6_inherit_eui64(addr.s6_addr + 8, in6_dev))
+ goto autoconf_fail;
+
+ ifp = ipv6_get_ifaddr(&addr, dev);
+
+ if (!ifp) {
+ int max_addresses = in6_dev->cnf.max_addresses;
+
+ if (!valid_lft)
+ goto autoconf_fail;
+
+ if (!max_addresses ||
+ ipv6_count_addresses(in6_dev) < max_addresses)
+ ifp = ipv6_add_addr(in6_dev, &addr, pinfo->prefix_len,
+ addr_type&IPV6_ADDR_SCOPE_MASK, 0);
+ if (!ifp)
+ goto autoconf_fail;
+
+ addrconf_dad_start(ifp, RTF_ADDRCONF|RTF_PREFIX_RT);
+ update_lft = 1;
+ create = 1;
+ }
+
+ spin_lock(&ifp->lock);
+ now = jiffies;
+ if (ifp->valid_lft > (now - ifp->tstamp) / HZ)
+ stored_lft = ifp->valid_lft - (now - ifp->tstamp) / HZ;
+ else
+ stored_lft = 0;
+#define TWO_HOURS 7200
+ if (!update_lft && stored_lft) {
+ if (valid_lft > TWO_HOURS ||
+ valid_lft > stored_lft) {
+ update_lft = 1;
+ } else if (stored_lft <= TWO_HOURS
+#if 0 /* this rule is logically redundant */
+ && valid_lft <= stored_lft
+#endif
+ ) {
+ update_lft = 0;
+ } else {
+ valid_lft = TWO_HOURS;
+ if (valid_lft < prefered_lft)
+ prefered_lft = valid_lft;
+ update_lft = 1;
+ }
+ }
+ if (update_lft) {
+ ifp->valid_lft = valid_lft;
+ ifp->prefered_lft = prefered_lft;
+ ifp->tstamp = jiffies;
+ flags = ifp->flags;
+ ifp->flags &= ~IFA_F_DEPRECATED;
+ spin_unlock(&ifp->lock);
+
+ if (!(flags&IFA_F_TENTATIVE))
+ ipv6_ifa_notify((flags&IFA_F_DEPRECATED) ?
+ 0 : RTM_NEWADDR, ifp);
+ } else {
+ spin_unlock(&ifp->lock);
+ }
+#ifdef CONFIG_IPV6_PRIVACY
+ read_lock_bh(&in6_dev->lock);
+
+ /* update all temporary addresses in the list */
+ for (ift=in6_dev->tempaddr_list; ift; ift=ift->tmp_next) {
+ /*
+ * When adjusting the lifetimes of an existing
+ * temporary address, only lower the lifetimes.
+ * Implementations must not increase the
+ * lifetimes of an existing temporary address
+ * when processing a Prefix Information Option.
+ */
+ spin_lock(&ift->lock);
+ flags = ift->flags;
+ if (ift->valid_lft > valid_lft &&
+ ift->valid_lft - valid_lft > (jiffies - ift->tstamp) / HZ)
+ ift->valid_lft = valid_lft + (jiffies - ift->tstamp) / HZ;
+ if (ift->prefered_lft > prefered_lft &&
+ ift->prefered_lft - prefered_lft > (jiffies - ift->tstamp) / HZ)
+ ift->prefered_lft = prefered_lft + (jiffies - ift->tstamp) / HZ;
+ spin_unlock(&ift->lock);
+ if (!(flags&IFA_F_TENTATIVE))
+ ipv6_ifa_notify(0, ift);
+ }
+
+ if (create && in6_dev->cnf.use_tempaddr > 0) {
+ /*
+ * When a new public address is created as described in [ADDRCONF],
+ * also create a new temporary address.
+ */
+ read_unlock_bh(&in6_dev->lock);
+ ipv6_create_tempaddr(ifp, NULL);
+ } else {
+ read_unlock_bh(&in6_dev->lock);
+ }
+#endif
+ addrconf_verify(0);
+ in6_ifa_put(ifp);
+ }
+autoconf_fail:
+ in6_dev_put(in6_dev);
+}
+
+/*
+ * Set destination address.
+ * Special case for SIT interfaces where we create a new "virtual"
+ * device.
+ */
+int addrconf_set_dstaddr(void *arg)
+{
+ struct in6_ifreq ireq;
+ struct net_device *dev;
+ int err = -EINVAL;
+
+ rtnl_lock();
+
+ err = -EFAULT;
+ if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
+ goto err_exit;
+
+ dev = __dev_get_by_index(ireq.ifr6_ifindex);
+
+ err = -ENODEV;
+ if (dev == NULL)
+ goto err_exit;
+
+#ifdef CONFIG_NET_IPIP_IPV6
+ if (dev->type == ARPHRD_TUNNEL)
+#else
+ if (dev->type == ARPHRD_SIT)
+#endif
+ {
+ struct ifreq ifr;
+ mm_segment_t oldfs;
+ struct ip_tunnel_parm p;
+
+ err = -EADDRNOTAVAIL;
+ if (!(ipv6_addr_type(&ireq.ifr6_addr) & IPV6_ADDR_COMPATv4))
+ goto err_exit;
+
+ memset(&p, 0, sizeof(p));
+ p.iph.daddr = ireq.ifr6_addr.s6_addr32[3];
+ p.iph.saddr = 0;
+ p.iph.version = 4;
+ p.iph.ihl = 5;
+ p.iph.protocol = IPPROTO_IPV6;
+ p.iph.ttl = 64;
+ ifr.ifr_ifru.ifru_data = (void*)&p;
+
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ err = dev->do_ioctl(dev, &ifr, SIOCADDTUNNEL);
+ set_fs(oldfs);
+
+ if (err == 0) {
+ err = -ENOBUFS;
+ if ((dev = __dev_get_by_name(p.name)) == NULL)
+ goto err_exit;
+ err = dev_open(dev);
+ }
+ }
+
+err_exit:
+ rtnl_unlock();
+ return err;
+}
+
+/*
+ * Manual configuration of address on an interface
+ */
+static int inet6_addr_add(int ifindex, struct in6_addr *pfx, int plen)
+{
+ struct inet6_ifaddr *ifp;
+ struct inet6_dev *idev;
+ struct net_device *dev;
+ int type, scope;
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ char abuf[128];
+
+ in6_ntop(pfx, abuf);
+ ADBG3((KERN_DEBUG
+ "inet6_addr_addr(ifindex=%d, pfx=%s, plen=%d)\n",
+ ifindex, abuf, plen));
+#endif
+
+ ASSERT_RTNL();
+
+ if ((dev = __dev_get_by_index(ifindex)) == NULL)
+ return -ENODEV;
+
+ if (!(dev->flags&IFF_UP))
+ return -ENETDOWN;
+
+ if ((idev = addrconf_add_dev(dev)) == NULL)
+ return -ENOBUFS;
+
+ type = ipv6_addr_type(pfx);
+ if (type & IPV6_ADDR_MULTICAST)
+ return -EINVAL;
+
+ scope = ipv6_addr_scope(pfx);
+
+ ifp = ipv6_add_addr(idev, pfx, plen, scope, IFA_F_PERMANENT);
+ if (!IS_ERR(ifp)) {
+ addrconf_dad_start(ifp, 0);
+ in6_ifa_put(ifp);
+ return 0;
+ }
+
+ return PTR_ERR(ifp);
+}
+
+static int inet6_addr_del(int ifindex, struct in6_addr *pfx, int plen)
+{
+ struct inet6_ifaddr *ifp;
+ struct inet6_dev *idev;
+ struct net_device *dev;
+
+ if ((dev = __dev_get_by_index(ifindex)) == NULL)
+ return -ENODEV;
+
+ if ((idev = __in6_dev_get(dev)) == NULL)
+ return -ENXIO;
+
+ read_lock_bh(&idev->lock);
+ for (ifp = idev->addr_list; ifp; ifp=ifp->if_next) {
+ if (ifp->prefix_len == plen &&
+ (!memcmp(pfx, &ifp->addr, sizeof(struct in6_addr)))) {
+ in6_ifa_hold(ifp);
+ read_unlock_bh(&idev->lock);
+
+ ipv6_del_addr(ifp);
+
+ /* If the last address is deleted administratively,
+ disable IPv6 on this interface.
+ */
+ if (idev->addr_list == NULL)
+ addrconf_ifdown(idev->dev, 1);
+ return 0;
+ }
+ }
+ read_unlock_bh(&idev->lock);
+ return -EADDRNOTAVAIL;
+}
+
+
+int addrconf_add_ifaddr(void *arg)
+{
+ struct in6_ifreq ireq;
+ int err;
+
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ ADBG3((KERN_DEBUG
+ "addrconf_add_ifaddr(arg=%p)\n",
+ arg));
+#endif
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
+ return -EFAULT;
+
+ rtnl_lock();
+ err = inet6_addr_add(ireq.ifr6_ifindex, &ireq.ifr6_addr, ireq.ifr6_prefixlen);
+ rtnl_unlock();
+ return err;
+}
+
+int addrconf_del_ifaddr(void *arg)
+{
+ struct in6_ifreq ireq;
+ int err;
+
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ ADBG3((KERN_DEBUG
+ "addrconf_add_ifaddr(arg=%p)\n",
+ arg));
+#endif
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
+ return -EFAULT;
+
+ rtnl_lock();
+ err = inet6_addr_del(ireq.ifr6_ifindex, &ireq.ifr6_addr, ireq.ifr6_prefixlen);
+ rtnl_unlock();
+ return err;
+}
+
+static void __sit_add_v4_addr(struct inet6_dev *idev, struct in6_addr *addr, int plen, int scope)
+{
+ struct inet6_ifaddr *ifp = ipv6_add_addr(idev, addr, plen, scope, IFA_F_PERMANENT);
+ if (!IS_ERR(ifp)) {
+ spin_lock_bh(&ifp->lock);
+ ifp->flags &= ~IFA_F_TENTATIVE;
+ spin_unlock_bh(&ifp->lock);
+ ipv6_ifa_notify(RTM_NEWADDR, ifp);
+ in6_ifa_put(ifp);
+ }
+}
+
+static void sit_add_v4_addrs(struct inet6_dev *idev)
+{
+ struct in6_addr addr;
+ int plen;
+ struct net_device *dev;
+ int scope;
+ __u32 dev_addr = *(__u32 *)idev->dev->dev_addr;
+
+ ASSERT_RTNL();
+
+ if (idev->dev->flags&IFF_POINTOPOINT) {
+ ipv6_addr_set(&addr,
+ htonl(0xfe800000), 0, 0, dev_addr);
+ plen = addr.s6_addr32[3] ? 128 : 64;
+ scope = IFA_LINK;
+ } else {
+ ipv6_addr_set(&addr,
+ 0, 0, 0, dev_addr);
+ plen = 96;
+ scope = IPV6_ADDR_COMPATv4;
+ }
+
+ if (addr.s6_addr32[3]) {
+ __sit_add_v4_addr(idev, &addr, 128, scope);
+ return;
+ }
+
+ for (dev = dev_base; dev != NULL; dev = dev->next) {
+ struct in_device * in_dev = __in_dev_get(dev);
+ if (in_dev && (dev->flags & IFF_UP)) {
+ struct in_ifaddr * ifa;
+
+ int flag = scope;
+
+ for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next) {
+ addr.s6_addr32[3] = ifa->ifa_local;
+
+ if (ifa->ifa_scope == RT_SCOPE_LINK)
+ continue;
+ if (ifa->ifa_scope >= RT_SCOPE_HOST) {
+ if (idev->dev->flags&IFF_POINTOPOINT)
+ continue;
+ flag |= IFA_HOST;
+ }
+ __sit_add_v4_addr(idev, &addr, plen, flag);
+ }
+ }
+ }
+}
+
+static struct inet6_dev * init_loopback(struct net_device *dev)
+{
+ struct inet6_dev *idev;
+ struct inet6_ifaddr * ifp;
+
+ /* ::1 */
+
+ ASSERT_RTNL();
+
+ if ((idev = ipv6_find_idev(dev)) == NULL) {
+ ADBG1((KERN_WARNING "init loopback: add_dev failed\n"));
+ return NULL;
+ }
+
+ ipv6_mc_up(idev);
+
+ ifp = ipv6_add_addr(idev, &in6addr_loopback, 128, IFA_HOST, IFA_F_PERMANENT);
+ if (!IS_ERR(ifp)) {
+ spin_lock_bh(&ifp->lock);
+ ifp->flags &= ~IFA_F_TENTATIVE;
+ spin_unlock_bh(&ifp->lock);
+ ipv6_ifa_notify(RTM_NEWADDR, ifp);
+ in6_ifa_put(ifp);
+ }
+
+ return idev;
+}
+
+static void addrconf_add_linklocal(struct inet6_dev *idev, struct in6_addr *addr)
+{
+ struct inet6_ifaddr * ifp;
+
+ ifp = ipv6_add_addr(idev, addr, 64, IFA_LINK, IFA_F_PERMANENT);
+ if (!IS_ERR(ifp)) {
+ addrconf_dad_start(ifp, 0);
+ in6_ifa_put(ifp);
+ }
+}
+
+static struct inet6_dev * addrconf_dev_config(struct net_device *dev)
+{
+ struct in6_addr addr;
+ struct inet6_dev * idev;
+
+ ASSERT_RTNL();
+
+ switch(dev->type){
+ case ARPHRD_ETHER:
+ case ARPHRD_FDDI:
+ case ARPHRD_IEEE802_TR:
+ case ARPHRD_ARCNET:
+ break;
+ default:
+ /* Alas, we support only Ethernet autoconfiguration. */
+ return NULL;
+ }
+
+ idev = addrconf_add_dev(dev);
+ if (idev == NULL)
+ return NULL;
+
+ ipv6_addr_set(&addr, htonl(0xfe800000), 0, 0, 0);
+
+ if (ipv6_generate_eui64(addr.s6_addr + 8, dev) == 0)
+ addrconf_add_linklocal(idev, &addr);
+
+ return idev;
+}
+
+static struct inet6_dev * addrconf_sit_config(struct net_device *dev)
+{
+ struct inet6_dev *idev;
+
+ ASSERT_RTNL();
+
+ /*
+ * Configure the tunnel with one of our IPv4
+ * addresses... we should configure all of
+ * our v4 addrs in the tunnel
+ */
+
+ if ((idev = ipv6_find_idev(dev)) == NULL) {
+ ADBG1((KERN_WARNING "init sit: add_dev failed\n"));
+ return NULL;
+ }
+ ipv6_mc_up(idev);
+
+ sit_add_v4_addrs(idev);
+
+ if (dev->flags&IFF_POINTOPOINT) {
+ addrconf_add_mroute(dev);
+ addrconf_add_lroute(dev);
+ } else
+ sit_route_add(dev);
+
+ return idev;
+}
+
+
+/**
+ * addrconf_ipv6_tunnel_config - configure IPv6 tunnel device
+ * @dev: tunnel device
+ **/
+
+static void addrconf_ipv6_tunnel_config(struct net_device *dev)
+{
+ struct inet6_dev *idev;
+
+ ASSERT_RTNL();
+
+ /* Assign inet6_dev structure to tunnel device */
+ if ((idev = ipv6_find_idev(dev)) == NULL) {
+ printk(KERN_DEBUG "init ipv6 tunnel: add_dev failed\n");
+ return;
+ }
+}
+
+
+int addrconf_notify(struct notifier_block *this, unsigned long event,
+ void * data)
+{
+ struct net_device *dev = (struct net_device *) data;
+
+ switch(event) {
+ case NETDEV_UP:
+ {
+ struct inet6_dev *idev = NULL;
+ switch(dev->type) {
+#ifdef CONFIG_NET_IPIP_IPV6
+ case ARPHRD_TUNNEL:
+#else
+ case ARPHRD_SIT:
+#endif
+ {
+ idev = addrconf_sit_config(dev);
+ break;
+ }
+
+ case ARPHRD_IPV6_IPV6_TUNNEL:
+ addrconf_ipv6_tunnel_config(dev);
+ break;
+
+ case ARPHRD_LOOPBACK:
+ idev = init_loopback(dev);
+ break;
+
+ default:
+ idev = addrconf_dev_config(dev);
+ break;
+ };
+ if (idev) {
+ idev->stats.ipv6[0].Ip6LastChange = timeticks(jiffies);
+
+ /* If the MTU changed during the interface down, when the
+ interface up, the changed MTU must be reflected in the
+ idev as well as routers.
+ */
+ if (idev->cnf.mtu6 != dev->mtu && dev->mtu >= IPV6_MIN_MTU) {
+ rt6_mtu_change(dev, dev->mtu);
+ idev->cnf.mtu6 = dev->mtu;
+ }
+ /* If the changed mtu during down is lower than IPV6_MIN_MTU
+ stop IPv6 on this interface.
+ */
+ if (dev->mtu < IPV6_MIN_MTU)
+ addrconf_ifdown(dev, event != NETDEV_DOWN);
+#ifdef CONFIG_IPV6_NODEINFO
+ icmpv6_ni_join_nigroup_dev(dev);
+#endif
+ }
+ break;
+ }
+ case NETDEV_CHANGEMTU:
+ {
+ struct inet6_dev *idev = __in6_dev_get(dev);
+ if ( idev && dev->mtu >= IPV6_MIN_MTU) {
+ rt6_mtu_change(dev, dev->mtu);
+ idev->cnf.mtu6 = dev->mtu;
+ break;
+ }
+
+ /* MTU falled under IPV6_MIN_MTU. Stop IPv6 on this interface. */
+ }
+ case NETDEV_DOWN:
+ case NETDEV_UNREGISTER:
+ {
+ struct inet6_dev *idev = __in6_dev_get(dev);
+ /*
+ * Remove all addresses from this interface.
+ */
+ if (idev)
+ idev->stats.ipv6[0].Ip6LastChange = timeticks(jiffies);
+ addrconf_ifdown(dev, event != NETDEV_DOWN);
+ break;
+ }
+ case NETDEV_CHANGE:
+ break;
+ };
+
+ return NOTIFY_OK;
+}
+
+static int addrconf_ifdown(struct net_device *dev, int how)
+{
+ struct inet6_dev *idev;
+ struct inet6_ifaddr *ifa, **bifa;
+#ifdef CONFIG_IPV6_PREFIXLIST
+ struct list_head *head, *next;
+ struct prefix_element *p;
+#endif
+ int i;
+
+ ADBG3((KERN_DEBUG
+ "addrconf_ifdown(dev=%p(%s), how=%d)\n",
+ dev, dev->name, how));
+
+ ASSERT_RTNL();
+
+ rt6_ifdown(dev);
+ neigh_ifdown(&nd_tbl, dev);
+
+ idev = __in6_dev_get(dev);
+ if (idev == NULL)
+ return -ENODEV;
+
+ /* Step 1: remove reference to ipv6 device from parent device.
+ Do not dev_put!
+ */
+ if (how == 1) {
+ write_lock_bh(&addrconf_lock);
+ dev->ip6_ptr = NULL;
+ idev->dead = 1;
+ write_unlock_bh(&addrconf_lock);
+ }
+
+ /* Step 2: clear hash table */
+ for (i=0; i<IN6_ADDR_HSIZE; i++) {
+ bifa = &inet6_addr_lst[i];
+
+ write_lock_bh(&addrconf_hash_lock);
+ while ((ifa = *bifa) != NULL) {
+ if (ifa->idev == idev) {
+ *bifa = ifa->lst_next;
+ ifa->lst_next = NULL;
+ addrconf_del_timer(ifa);
+ in6_ifa_put(ifa);
+ continue;
+ }
+ bifa = &ifa->lst_next;
+ }
+ write_unlock_bh(&addrconf_hash_lock);
+ }
+
+ write_lock_bh(&idev->lock);
+
+ /* Step 3: clear flags for stateless addrconf */
+ if (how != 1)
+ idev->if_flags &= ~(IF_RS_SENT|IF_RA_RCVD);
+
+ /* Step 4: clear address list */
+#ifdef CONFIG_IPV6_PRIVACY
+ if (del_timer(&idev->regen_timer))
+ in6_dev_put(idev);
+
+ /* clear tempaddr list */
+ while ((ifa = idev->tempaddr_list) != NULL) {
+ idev->tempaddr_list = ifa->tmp_next;
+ ifa->tmp_next = NULL;
+ ifa->dead = 1;
+ write_unlock_bh(&idev->lock);
+ spin_lock_bh(&ifa->lock);
+ if (ifa->ifpub) {
+ in6_ifa_put(ifa->ifpub);
+ ifa->ifpub = NULL;
+ }
+ spin_unlock_bh(&ifa->lock);
+ in6_ifa_put(ifa);
+ write_lock_bh(&idev->lock);
+ }
+#endif
+ while ((ifa = idev->addr_list) != NULL) {
+ idev->addr_list = ifa->if_next;
+ ifa->if_next = NULL;
+ ifa->dead = 1;
+ addrconf_del_timer(ifa);
+ write_unlock_bh(&idev->lock);
+
+ ipv6_ifa_notify(RTM_DELADDR, ifa);
+ in6_ifa_put(ifa);
+
+ write_lock_bh(&idev->lock);
+ }
+ write_unlock_bh(&idev->lock);
+
+ /* Step 5: Discard multicast list */
+
+ if (how == 1)
+ ipv6_mc_destroy_dev(idev);
+ else
+ ipv6_mc_down(idev);
+
+#ifdef CONFIG_IPV6_PREFIXLIST
+ /* Step 6: Free up Prefix List */
+ spin_lock_bh(&idev->prefix_lock);
+ list_for_each_safe(head, next, &idev->prefix_list) {
+ p = list_entry(head, struct prefix_element, list);
+#ifdef CONFIG_IPV6_PREFIXLIST_DEBUG
+ {
+ char abuf[64];
+ struct inet6_prefix *pl = (struct inet6_prefix *)pfx;
+ in6_ntop(&pl->prefix, abuf);
+ printk(KERN_INFO "%s: deleting prefix %s/%d, lifetime = %d\n",
+ __FUNCTION__, abuf, pl->prefix_len, pl->lifetime);
+ }
+#endif
+ kfree(p);
+ }
+ INIT_LIST_HEAD(&idev->prefix_list);
+ spin_unlock_bh(&idev->prefix_lock);
+
+#endif
+ /* Shot the device (if unregistered) */
+
+ if (how == 1) {
+ neigh_parms_release(&nd_tbl, idev->nd_parms);
+#ifdef CONFIG_SYSCTL
+ addrconf_sysctl_unregister(&idev->cnf);
+#endif
+ in6_dev_put(idev);
+ }
+ return 0;
+}
+
+static void addrconf_rs_timer(unsigned long data)
+{
+ struct inet6_ifaddr *ifp = (struct inet6_ifaddr *) data;
+
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ ADBG3((KERN_DEBUG
+ "addrconf_rs_timer(ifp=%p): jiffies=%lu\n", ifp, jiffies));
+#endif
+
+ if (ifp->idev->cnf.forwarding)
+ goto out;
+
+ if (ifp->idev->if_flags & IF_RA_RCVD) {
+ /*
+ * Announcement received after solicitation
+ * was sent
+ */
+ goto out;
+ }
+
+ spin_lock(&ifp->lock);
+ if (ifp->probes++ < ifp->idev->cnf.rtr_solicits) {
+ struct in6_addr all_routers;
+
+ /* The wait after the last probe can be shorter */
+ addrconf_mod_timer(ifp, AC_RS,
+ (ifp->probes == ifp->idev->cnf.rtr_solicits) ?
+ ifp->idev->cnf.rtr_solicit_delay :
+ ifp->idev->cnf.rtr_solicit_interval);
+ spin_unlock(&ifp->lock);
+
+ ipv6_addr_all_routers(&all_routers);
+
+ ndisc_send_rs(ifp->idev->dev, &ifp->addr, &all_routers);
+ } else {
+ struct in6_rtmsg rtmsg;
+
+ spin_unlock(&ifp->lock);
+
+ ADBG1((KERN_DEBUG "%s: no IPv6 routers present\n",
+ ifp->idev->dev->name));
+
+ memset(&rtmsg, 0, sizeof(struct in6_rtmsg));
+ rtmsg.rtmsg_type = RTMSG_NEWROUTE;
+ rtmsg.rtmsg_metric = IP6_RT_PRIO_ADDRCONF;
+ rtmsg.rtmsg_flags = (RTF_ALLONLINK | RTF_ADDRCONF |
+ RTF_DEFAULT | RTF_UP);
+
+ rtmsg.rtmsg_ifindex = ifp->idev->dev->ifindex;
+
+ ip6_route_add(&rtmsg, NULL);
+ }
+
+out:
+ in6_ifa_put(ifp);
+}
+
+/*
+ * Duplicate Address Detection
+ */
+static void addrconf_dad_start(struct inet6_ifaddr *ifp, int flags)
+{
+ struct net_device *dev;
+ unsigned long rand_num;
+
+ dev = ifp->idev->dev;
+
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ ADBG3((KERN_DEBUG
+ "addrconf_dad_start(ifp=%p): jiffies=%lu\n", ifp, jiffies));
+#endif
+
+ /* Join his own solicit address */
+ addrconf_join_solict(dev, &ifp->addr);
+
+ if (ifp->prefix_len != 128 && (ifp->flags&IFA_F_PERMANENT))
+ addrconf_prefix_route(&ifp->addr, ifp->prefix_len, dev, 0, flags);
+
+ get_random_bytes(&rand_num, sizeof(rand_num));
+ rand_num %= (ifp->idev->cnf.rtr_solicit_delay ? : 1);
+
+ spin_lock_bh(&ifp->lock);
+
+ if (dev->flags&(IFF_NOARP|IFF_LOOPBACK) ||
+ !(ifp->flags&IFA_F_TENTATIVE)) {
+ ifp->flags &= ~IFA_F_TENTATIVE;
+ spin_unlock_bh(&ifp->lock);
+
+ addrconf_dad_completed(ifp);
+ return;
+ }
+
+ ifp->probes = ifp->idev->cnf.dad_transmits;
+ addrconf_mod_timer(ifp, AC_DAD, rand_num);
+
+ spin_unlock_bh(&ifp->lock);
+}
+
+static void addrconf_dad_timer(unsigned long data)
+{
+ struct inet6_ifaddr *ifp = (struct inet6_ifaddr *) data;
+ struct in6_addr unspec;
+ struct in6_addr mcaddr;
+
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ ADBG3((KERN_DEBUG
+ "addrconf_dad_timer(ifp=%p): jiffies=%lu\n", ifp, jiffies));
+#endif
+
+ spin_lock_bh(&ifp->lock);
+ if (ifp->probes == 0) {
+ /*
+ * DAD was successful
+ */
+
+ ifp->flags &= ~IFA_F_TENTATIVE;
+ spin_unlock_bh(&ifp->lock);
+
+ addrconf_dad_completed(ifp);
+
+ in6_ifa_put(ifp);
+ return;
+ }
+
+ ifp->probes--;
+ addrconf_mod_timer(ifp, AC_DAD, ifp->idev->nd_parms->retrans_time);
+ spin_unlock_bh(&ifp->lock);
+
+ /* send a neighbour solicitation for our addr */
+ memset(&unspec, 0, sizeof(unspec));
+ addrconf_addr_solict_mult(&ifp->addr, &mcaddr);
+ ndisc_send_ns(ifp->idev->dev, NULL, &ifp->addr, &mcaddr, &unspec, 1);
+
+ in6_ifa_put(ifp);
+}
+
+static void addrconf_dad_completed(struct inet6_ifaddr *ifp)
+{
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ ADBG3((KERN_DEBUG
+ "addrconf_dad_completed(ifp=%p): jiffies=%lu\n", ifp, jiffies));
+#endif
+
+ /*
+ * Configure the address for reception. Now it is valid.
+ */
+
+ ipv6_ifa_notify(RTM_NEWADDR, ifp);
+
+ /* If added prefix is link local and forwarding is off,
+ start sending router solicitations.
+ */
+
+#ifdef CONFIG_IPV6_PRIVACY
+ spin_lock_bh(&ifp->lock);
+ if (ifp->flags&IFA_F_TEMPORARY) {
+ if (!ifp->ifpub) {
+ ADBG1((KERN_WARNING
+ "addrconf_dad_completed(): ifp->ifpub is NULL.\n"));
+ } else {
+ spin_lock(&ifp->ifpub->lock);
+ ifp->ifpub->regen_count = 0;
+ spin_unlock(&ifp->ifpub->lock);
+ }
+ spin_unlock_bh(&ifp->lock);
+ return;
+ }
+ spin_unlock_bh(&ifp->lock);
+#endif
+
+ if (ifp->idev->cnf.forwarding == 0 &&
+ ifp->idev->cnf.accept_ra &&
+ (ipv6_addr_type(&ifp->addr) & IPV6_ADDR_LINKLOCAL)) {
+ struct in6_addr all_routers;
+
+ ipv6_addr_all_routers(&all_routers);
+
+ /*
+ * If a host as already performed a random delay
+ * [...] as part of DAD [...] there is no need
+ * to delay again before sending the first RS
+ */
+ ndisc_send_rs(ifp->idev->dev, &ifp->addr, &all_routers);
+
+ spin_lock_bh(&ifp->lock);
+ ifp->probes = 1;
+ ifp->idev->if_flags |= IF_RS_SENT;
+ addrconf_mod_timer(ifp, AC_RS, ifp->idev->cnf.rtr_solicit_interval);
+ spin_unlock_bh(&ifp->lock);
+ }
+
+ if (ifp->idev->cnf.forwarding) {
+ struct in6_addr addr;
+
+ ipv6_addr_prefix(&addr, &ifp->addr, ifp->prefix_len);
+ if (!ipv6_addr_any(&addr)) {
+ ipv6_dev_ac_inc(ifp->idev->dev, &addr);
+ }
+ }
+}
+
+#ifdef CONFIG_PROC_FS
+static int iface_proc_info(char *buffer, char **start, off_t offset,
+ int length)
+{
+ struct inet6_ifaddr *ifp;
+ int i;
+ int len = 0;
+ off_t pos=0;
+ off_t begin=0;
+
+ for (i=0; i < IN6_ADDR_HSIZE; i++) {
+ read_lock_bh(&addrconf_hash_lock);
+ for (ifp=inet6_addr_lst[i]; ifp; ifp=ifp->lst_next) {
+ int j;
+
+ for (j=0; j<16; j++) {
+ sprintf(buffer + len, "%02x",
+ ifp->addr.s6_addr[j]);
+ len += 2;
+ }
+
+ len += sprintf(buffer + len,
+ " %02x %02x %02x %02x %8s\n",
+ ifp->idev->dev->ifindex,
+ ifp->prefix_len,
+ ifp->scope,
+ ifp->flags,
+ ifp->idev->dev->name);
+ pos=begin+len;
+ if(pos<offset) {
+ len=0;
+ begin=pos;
+ }
+ if(pos>offset+length) {
+ read_unlock_bh(&addrconf_hash_lock);
+ goto done;
+ }
+ }
+ read_unlock_bh(&addrconf_hash_lock);
+ }
+
+done:
+
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ if(len<0)
+ len=0;
+ return len;
+}
+
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+
+#define print_ifa_flag(_ifp,_flag) do { \
+ if ((_ifp)->flags&IFA_F_ ## _flag) { \
+ len += sprintf(buffer + len, "%s%s", \
+ fcount ? "," : "", \
+ # _flag); \
+ fcount++; \
+ } \
+} while(0);
+
+static int iface_debug_proc_info(char *buffer, char **start, off_t offset,
+ int length)
+{
+ struct inet6_ifaddr *ifp;
+ int i;
+ int len = 0;
+ off_t pos=0;
+ off_t begin=0;
+
+ for (i=0; i < IN6_ADDR_HSIZE; i++) {
+ read_lock_bh(&addrconf_hash_lock);
+ for (ifp=inet6_addr_lst[i]; ifp; ifp=ifp->lst_next) {
+ int j;
+ int fcount = 0;
+
+ for (j=0; j<16; j++) {
+ sprintf(buffer + len, "%02x",
+ ifp->addr.s6_addr[j]);
+ len += 2;
+ }
+
+ len += sprintf(buffer + len,
+ " %08lx %08x %08x "
+ "%8s ",
+ (jiffies - ifp->tstamp) / HZ,
+ ifp->prefered_lft, ifp->valid_lft,
+ ifp->idev->dev->name);
+ print_ifa_flag(ifp,TENTATIVE);
+ print_ifa_flag(ifp,PERMANENT);
+ print_ifa_flag(ifp,TEMPORARY);
+ print_ifa_flag(ifp,DEPRECATED);
+ len += sprintf(buffer + len, "\n");
+ pos=begin+len;
+ if(pos<offset) {
+ len=0;
+ begin=pos;
+ }
+ if(pos>offset+length) {
+ read_unlock_bh(&addrconf_hash_lock);
+ goto done;
+ }
+ }
+ read_unlock_bh(&addrconf_hash_lock);
+ }
+
+done:
+
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ if(len<0)
+ len=0;
+ return len;
+}
+#endif
+
+#endif /* CONFIG_PROC_FS */
+
+#ifdef CONFIG_IPV6_PREFIXLIST
+static void ipv6_expire_prefixes(void)
+{
+ unsigned long now = jiffies;
+ struct net_device *dev;
+ struct inet6_dev *idev;
+ struct list_head *head, *next;
+ struct prefix_element *p;
+ unsigned long age;
+
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev; dev = dev->next) {
+ if (!(idev = __in6_dev_get(dev))) {
+ continue;
+ }
+ read_lock_bh(&idev->lock);
+ spin_lock_bh(&idev->prefix_lock);
+ if (list_empty(&idev->prefix_list)) {
+ spin_unlock_bh(&idev->prefix_lock);
+ read_unlock_bh(&idev->lock);
+ continue;
+ }
+ list_for_each_safe(head, next, &idev->prefix_list) {
+ p = list_entry(head, struct prefix_element, list);
+ if (p->pinfo.valid != INFINITE) {
+ age = (now - p->timestamp) / HZ;
+ if (age > ntohl(p->pinfo.valid)) {
+ idev->prefix_count--;
+ list_del(&p->list);
+ kfree(p);
+ }
+ }
+ }
+ spin_unlock_bh(&idev->prefix_lock);
+ read_unlock_bh(&idev->lock);
+ }
+ read_unlock(&dev_base_lock);
+}
+#endif
+
+/*
+ * Periodic address status verification
+ */
+
+void addrconf_verify(unsigned long foo)
+{
+ struct inet6_ifaddr *ifp;
+ unsigned long now, next;
+ int i;
+#ifdef CONFIG_IPV6_PREFIXLIST
+ struct net_device *dev;
+ struct inet6_dev *idev;
+ struct list_head *pos, *n;
+ struct inet6_prefix *pfx;
+#endif
+
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ char abuf[128];
+#endif
+
+ spin_lock_bh(&addrconf_verify_lock);
+ now = jiffies;
+ next = now + ADDR_CHECK_FREQUENCY;
+
+ del_timer(&addr_chk_timer);
+
+ for (i=0; i < IN6_ADDR_HSIZE; i++) {
+
+restart:
+ write_lock(&addrconf_hash_lock);
+ for (ifp=inet6_addr_lst[i]; ifp; ifp=ifp->lst_next) {
+ unsigned long age;
+#ifdef CONFIG_IPV6_PRIVACY
+ unsigned long regen_advance;
+#endif
+
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ in6_ntop(&ifp->addr, abuf);
+#endif
+
+#ifdef CONFIG_IPV6_PRIVACY
+ read_lock(&ifp->idev->lock);
+ regen_advance = ifp->idev->cnf.regen_max_retry * ifp->idev->cnf.dad_transmits * ifp->idev->nd_parms->retrans_time / HZ;
+ read_unlock(&ifp->idev->lock);
+#endif
+
+ if (ifp->flags & IFA_F_PERMANENT) {
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ ADBG3((KERN_DEBUG
+ "addrconf_verify(): ifp->addr=%s, IFA_F_PERMANENT\n",
+ abuf));
+#endif
+ continue;
+ }
+
+ spin_lock(&ifp->lock);
+ age = (now - ifp->tstamp) / HZ;
+
+ if (age >= ifp->valid_lft) {
+ /* jiffies - ifp->tsamp > age >= ifp->valid_lft */
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ ADBG3((KERN_DEBUG
+ "addrconf_verify(): ifp->addr=%s, valid_lft(%u) <= age(%lu)\n",
+ abuf, ifp->valid_lft, age));
+#endif
+ spin_unlock(&ifp->lock);
+ in6_ifa_hold(ifp);
+ write_unlock(&addrconf_hash_lock);
+ ipv6_del_addr(ifp);
+ goto restart;
+ } else if (age >= ifp->prefered_lft) {
+ /* jiffies - ifp->tsamp > age >= ifp->prefered_lft */
+ int deprecate = 0;
+
+ if (!(ifp->flags&IFA_F_DEPRECATED)) {
+ deprecate = 1;
+ ifp->flags |= IFA_F_DEPRECATED;
+ }
+
+ if (time_before(ifp->tstamp + ifp->valid_lft * HZ, next))
+ next = ifp->tstamp + ifp->valid_lft * HZ;
+
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ ADBG3((KERN_DEBUG
+ "addrconf_verify(): "
+ "ifp->addr=%s, "
+ "prefered_lft(%u) <= age(%lu) < valid_lft(%u), "
+ "deprecate=%d, next=%ld\n",
+ abuf, ifp->prefered_lft, age, ifp->valid_lft, deprecate, (long)next-(long)now));
+#endif
+ spin_unlock(&ifp->lock);
+
+ if (deprecate) {
+ in6_ifa_hold(ifp);
+ write_unlock(&addrconf_hash_lock);
+
+ ipv6_ifa_notify(0, ifp);
+ in6_ifa_put(ifp);
+ goto restart;
+ }
+#ifdef CONFIG_IPV6_PRIVACY
+ } else if ((ifp->flags&IFA_F_TEMPORARY) &&
+ !(ifp->flags&IFA_F_TENTATIVE)) {
+ if (age >= ifp->prefered_lft - regen_advance) {
+ struct inet6_ifaddr *ifpub = ifp->ifpub;
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ ADBG3((KERN_DEBUG
+ "addrconf_verify(): "
+ "ifp->addr=%s, "
+ "prefered_lft(%u)-regen_advance(%lu) <= age(%lu) < prefered_lft(%u), "
+ "regen_count=%d, next=%ld\n",
+ abuf, ifp->prefered_lft, regen_advance, age,
+ ifp->prefered_lft, ifp->regen_count, (long)next-(long)now));
+#endif
+ if (time_before(ifp->tstamp + ifp->prefered_lft * HZ, next))
+ next = ifp->tstamp + ifp->prefered_lft * HZ;
+ if (!ifp->regen_count && ifpub) {
+ ifp->regen_count++;
+ in6_ifa_hold(ifp);
+ in6_ifa_hold(ifpub);
+ spin_unlock(&ifp->lock);
+ write_unlock(&addrconf_hash_lock);
+ ipv6_create_tempaddr(ifpub, ifp);
+ in6_ifa_put(ifpub);
+ in6_ifa_put(ifp);
+ goto restart;
+ }
+ } else if (time_before(ifp->tstamp + ifp->prefered_lft * HZ - regen_advance * HZ, next))
+ next = ifp->tstamp + ifp->prefered_lft * HZ - regen_advance * HZ;
+ spin_unlock(&ifp->lock);
+#endif
+ } else {
+ /* ifp->prefered_lft <= ifp->valid_lft */
+ if (time_before(ifp->tstamp + ifp->prefered_lft * HZ, next))
+ next = ifp->tstamp + ifp->prefered_lft * HZ;
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ ADBG3((KERN_DEBUG
+ "addrconf_verify(): "
+ "ifp->addr=%s, "
+ "age(%lu) <= prefered_lft(%u) (<= valid_lft(%u)), "
+ "next=%ld\n",
+ abuf, age, ifp->prefered_lft, ifp->valid_lft, (long)next-(long)now));
+#endif
+ spin_unlock(&ifp->lock);
+ }
+ }
+ write_unlock(&addrconf_hash_lock);
+ }
+#ifdef CONFIG_IPV6_PREFIXLIST
+ /*
+ * We need to expire prefixes even if no addresses are deleted in the
+ * loop above, since autoconfiguration may not be set in all router
+ * advertisements.
+ */
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev; dev = dev->next) {
+ unsigned long age;
+ if (!(idev = __in6_dev_get(dev))) {
+ continue;
+ }
+ read_lock_bh(&idev->lock);
+ write_lock_bh(&idev->prefix_lock);
+ if (list_empty(&idev->prefix_list)) {
+ write_unlock_bh(&idev->prefix_lock);
+ read_unlock_bh(&idev->lock);
+ continue;
+ }
+ list_for_each_safe(pos, n, &idev->prefix_list) {
+ pfx = list_entry(pos, struct inet6_prefix, list);
+ if (pfx->lifetime != PINFO_VALID_LIFETIME_INFINITE) {
+#ifdef CONFIG_IPV6_PREFIXLIST_DEBUG
+#ifndef CONFIG_IPV6_ACONF_DEBUG
+ char abuf[64];
+#endif
+ in6_ntop(&pfx->prefix, abuf);
+#endif
+ age = (now - pfx->timestamp) / HZ;
+ if (age > pfx->lifetime) {
+#ifdef CONFIG_IPV6_PREFIXLIST_DEBUG
+ printk(KERN_INFO "%s: deleting prefix %04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x/%d, lifetime = %d\n",
+ __FUNCTION__, abuf, pfx->prefix_len, pfx->lifetime);
+#endif
+ list_del(&pfx->list);
+ kfree(pfx);
+ } else if (time_before(pfx->timestamp + pfx->lifetime * HZ, next))
+ next = pfx->timestamp + pfx->lifetime * HZ;
+ }
+ }
+ write_unlock_bh(&idev->prefix_lock);
+ read_unlock_bh(&idev->lock);
+ }
+ read_unlock(&dev_base_lock);
+#endif
+
+ if (time_before(now + HZ/2, jiffies)) {
+ ADBG1((KERN_WARNING
+ "addrconf_verify(): too slow; jiffies - now = %lu\n",
+ jiffies - now));
+ }
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ /* don't fire too frequently */
+ ADBG3((KERN_DEBUG
+ "addrconf_verify(): new timer: %lu (jiffies = %lu, after %ld sec)\n",
+ time_before(next, jiffies + HZ/2) ? jiffies + HZ/2 : next,
+ jiffies,
+ time_before(next, jiffies + HZ/2) ? HZ/2 : ((long)next - (long)jiffies)/HZ
+ ));
+#endif
+#ifdef CONFIG_IPV6_PREFIXLIST
+ ipv6_expire_prefixes();
+#endif
+ addr_chk_timer.expires = time_before(next, jiffies + HZ/2) ? jiffies + HZ/2 : next;
+ add_timer(&addr_chk_timer);
+ spin_unlock_bh(&addrconf_verify_lock);
+}
+
+static int
+inet6_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct rtattr **rta = arg;
+ struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
+ struct in6_addr *pfx;
+
+ pfx = NULL;
+ if (rta[IFA_ADDRESS-1]) {
+ if (RTA_PAYLOAD(rta[IFA_ADDRESS-1]) < sizeof(*pfx))
+ return -EINVAL;
+ pfx = RTA_DATA(rta[IFA_ADDRESS-1]);
+ }
+ if (rta[IFA_LOCAL-1]) {
+ if (pfx && memcmp(pfx, RTA_DATA(rta[IFA_LOCAL-1]), sizeof(*pfx)))
+ return -EINVAL;
+ pfx = RTA_DATA(rta[IFA_LOCAL-1]);
+ }
+ if (pfx == NULL)
+ return -EINVAL;
+
+ return inet6_addr_del(ifm->ifa_index, pfx, ifm->ifa_prefixlen);
+}
+
+static int
+inet6_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+ struct rtattr **rta = arg;
+ struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
+ struct in6_addr *pfx;
+
+ pfx = NULL;
+ if (rta[IFA_ADDRESS-1]) {
+ if (RTA_PAYLOAD(rta[IFA_ADDRESS-1]) < sizeof(*pfx))
+ return -EINVAL;
+ pfx = RTA_DATA(rta[IFA_ADDRESS-1]);
+ }
+ if (rta[IFA_LOCAL-1]) {
+ if (pfx && memcmp(pfx, RTA_DATA(rta[IFA_LOCAL-1]), sizeof(*pfx)))
+ return -EINVAL;
+ pfx = RTA_DATA(rta[IFA_LOCAL-1]);
+ }
+ if (pfx == NULL)
+ return -EINVAL;
+
+ return inet6_addr_add(ifm->ifa_index, pfx, ifm->ifa_prefixlen);
+}
+
+static int inet6_fill_ifaddr(struct sk_buff *skb, struct inet6_ifaddr *ifa,
+ u32 pid, u32 seq, int event)
+{
+ struct ifaddrmsg *ifm;
+ struct nlmsghdr *nlh;
+ struct ifa_cacheinfo ci;
+ unsigned char *b = skb->tail;
+
+ nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*ifm));
+ if (pid) nlh->nlmsg_flags |= NLM_F_MULTI;
+ ifm = NLMSG_DATA(nlh);
+ ifm->ifa_family = AF_INET6;
+ ifm->ifa_prefixlen = ifa->prefix_len;
+ ifm->ifa_flags = ifa->flags;
+ ifm->ifa_scope = RT_SCOPE_UNIVERSE;
+ if (ifa->scope&IFA_HOST)
+ ifm->ifa_scope = RT_SCOPE_HOST;
+ else if (ifa->scope&IFA_LINK)
+ ifm->ifa_scope = RT_SCOPE_LINK;
+ else if (ifa->scope&IFA_SITE)
+ ifm->ifa_scope = RT_SCOPE_SITE;
+ ifm->ifa_index = ifa->idev->dev->ifindex;
+ RTA_PUT(skb, IFA_ADDRESS, 16, &ifa->addr);
+ if (!(ifa->flags&IFA_F_PERMANENT)) {
+ ci.ifa_prefered = ifa->prefered_lft;
+ ci.ifa_valid = ifa->valid_lft;
+ if (ci.ifa_prefered != 0xFFFFFFFF) {
+ long tval = (jiffies - ifa->tstamp)/HZ;
+ ci.ifa_prefered -= tval;
+ if (ci.ifa_valid != 0xFFFFFFFF)
+ ci.ifa_valid -= tval;
+ }
+ RTA_PUT(skb, IFA_CACHEINFO, sizeof(ci), &ci);
+ }
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int inet6_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int idx, ip_idx;
+ int s_idx, s_ip_idx;
+ struct inet6_ifaddr *ifa;
+
+ s_idx = cb->args[0];
+ s_ip_idx = ip_idx = cb->args[1];
+
+ for (idx=0; idx < IN6_ADDR_HSIZE; idx++) {
+ if (idx < s_idx)
+ continue;
+ if (idx > s_idx)
+ s_ip_idx = 0;
+ read_lock_bh(&addrconf_hash_lock);
+ for (ifa=inet6_addr_lst[idx], ip_idx = 0; ifa;
+ ifa = ifa->lst_next, ip_idx++) {
+ if (ip_idx < s_ip_idx)
+ continue;
+ if (inet6_fill_ifaddr(skb, ifa, NETLINK_CB(cb->skb).pid,
+ cb->nlh->nlmsg_seq, RTM_NEWADDR) <= 0) {
+ read_unlock_bh(&addrconf_hash_lock);
+ goto done;
+ }
+ }
+ read_unlock_bh(&addrconf_hash_lock);
+ }
+done:
+ cb->args[0] = idx;
+ cb->args[1] = ip_idx;
+
+ return skb->len;
+}
+
+static void inet6_ifa_notify(int event, struct inet6_ifaddr *ifa)
+{
+ struct sk_buff *skb;
+ int size = NLMSG_SPACE(sizeof(struct ifaddrmsg)+128);
+
+ skb = alloc_skb(size, GFP_ATOMIC);
+ if (!skb) {
+ netlink_set_err(rtnl, 0, RTMGRP_IPV6_IFADDR, ENOBUFS);
+ return;
+ }
+ if (inet6_fill_ifaddr(skb, ifa, 0, 0, event) < 0) {
+ kfree_skb(skb);
+ netlink_set_err(rtnl, 0, RTMGRP_IPV6_IFADDR, EINVAL);
+ return;
+ }
+ NETLINK_CB(skb).dst_groups = RTMGRP_IPV6_IFADDR;
+ netlink_broadcast(rtnl, skb, 0, RTMGRP_IPV6_IFADDR, GFP_ATOMIC);
+}
+
+static void inline ipv6_store_devconf(struct ipv6_devconf *cnf,
+ __s32 *array, int bytes)
+{
+ memset(array, 0, bytes);
+ array[DEVCONF_FORWARDING] = cnf->forwarding;
+ array[DEVCONF_HOPLIMIT] = cnf->hop_limit;
+ array[DEVCONF_MTU6] = cnf->mtu6;
+ array[DEVCONF_ACCEPT_RA] = cnf->accept_ra;
+ array[DEVCONF_ACCEPT_REDIRECTS] = cnf->accept_redirects;
+ array[DEVCONF_AUTOCONF] = cnf->autoconf;
+ array[DEVCONF_DAD_TRANSMITS] = cnf->dad_transmits;
+ array[DEVCONF_RTR_SOLICITS] = cnf->rtr_solicits;
+ array[DEVCONF_RTR_SOLICIT_INTERVAL] = cnf->rtr_solicit_interval;
+ array[DEVCONF_RTR_SOLICIT_DELAY] = cnf->rtr_solicit_delay;
+#ifdef CONFIG_IPV6_PRIVACY
+ array[DEVCONF_USE_TEMPADDR] = cnf->use_tempaddr;
+ array[DEVCONF_TEMP_VALID_LFT] = cnf->temp_valid_lft;
+ array[DEVCONF_TEMP_PREFERED_LFT] = cnf->temp_prefered_lft;
+ array[DEVCONF_REGEN_MAX_RETRY] = cnf->regen_max_retry;
+ array[DEVCONF_MAX_DESYNC_FACTOR] = cnf->max_desync_factor;
+#endif
+ array[DEVCONF_MAX_ADDRESSES] = cnf->max_addresses;
+#ifdef CONFIG_IPV6_NODEINFO
+ array[DEVCONF_ACCEPT_NI] = cnf->accept_ni;
+#endif
+}
+
+static int inet6_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
+ struct inet6_dev *idev,
+ int type, u32 pid, u32 seq)
+{
+ __s32 *array = NULL;
+ struct ifinfomsg *r;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+ struct rtattr *subattr;
+
+ nlh = NLMSG_PUT(skb, pid, seq, type, sizeof(*r));
+ if (pid) nlh->nlmsg_flags |= NLM_F_MULTI;
+ r = NLMSG_DATA(nlh);
+ r->ifi_family = AF_INET6;
+ r->ifi_type = dev->type;
+ r->ifi_index = dev->ifindex;
+ r->ifi_flags = dev->flags;
+ r->ifi_change = 0;
+ if (!netif_running(dev) || !netif_carrier_ok(dev))
+ r->ifi_flags &= ~IFF_RUNNING;
+ else
+ r->ifi_flags |= IFF_RUNNING;
+
+ RTA_PUT(skb, IFLA_IFNAME, strlen(dev->name)+1, dev->name);
+
+ subattr = (struct rtattr*)skb->tail;
+
+ RTA_PUT(skb, IFLA_PROTINFO, 0, NULL);
+
+ /* return the device flags */
+ RTA_PUT(skb, IFLA_INET6_FLAGS, sizeof(__u32), &idev->if_flags);
+
+ /* return the device sysctl params */
+ if ((array = kmalloc(DEVCONF_MAX * sizeof(*array), GFP_ATOMIC)) == NULL)
+ goto rtattr_failure;
+ ipv6_store_devconf(&idev->cnf, array, DEVCONF_MAX * sizeof(*array));
+ RTA_PUT(skb, IFLA_INET6_CONF, DEVCONF_MAX * sizeof(*array), array);
+
+ /* XXX - Statistics/MC not implemented */
+ subattr->rta_len = skb->tail - (u8*)subattr;
+
+ nlh->nlmsg_len = skb->tail - b;
+ kfree(array);
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ if (array)
+ kfree(array);
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int inet6_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int idx, err;
+ int s_idx = cb->args[0];
+ struct net_device *dev;
+ struct inet6_dev *idev;
+
+ read_lock(&dev_base_lock);
+ for (dev=dev_base, idx=0; dev; dev = dev->next, idx++) {
+ if (idx < s_idx)
+ continue;
+ if ((idev = in6_dev_get(dev)) == NULL)
+ continue;
+ err = inet6_fill_ifinfo(skb, dev, idev, RTM_NEWLINK,
+ NETLINK_CB(cb->skb).pid, cb->nlh->nlmsg_seq);
+ in6_dev_put(idev);
+ if (err <= 0)
+ break;
+ }
+ read_unlock(&dev_base_lock);
+ cb->args[0] = idx;
+
+ return skb->len;
+}
+
+static struct rtnetlink_link inet6_rtnetlink_table[RTM_MAX-RTM_BASE+1] =
+{
+ { NULL, NULL, },
+ { NULL, NULL, },
+ { NULL, inet6_dump_ifinfo, },
+ { NULL, NULL, },
+
+ { inet6_rtm_newaddr, NULL, },
+ { inet6_rtm_deladdr, NULL, },
+ { NULL, inet6_dump_ifaddr, },
+ { NULL, NULL, },
+
+ { inet6_rtm_newroute, NULL, },
+ { inet6_rtm_delroute, NULL, },
+ { inet6_rtm_getroute, inet6_dump_fib, },
+ { NULL, NULL, },
+};
+
+static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp)
+{
+ inet6_ifa_notify(event ? : RTM_NEWADDR, ifp);
+
+ switch (event) {
+ case RTM_NEWADDR:
+ ip6_rt_addr_add(&ifp->addr, ifp->idev->dev);
+ break;
+ case RTM_DELADDR:
+ addrconf_leave_solict(ifp->idev->dev, &ifp->addr);
+ if (ifp->idev->cnf.forwarding) {
+ struct in6_addr addr;
+
+ ipv6_addr_prefix(&addr, &ifp->addr, ifp->prefix_len);
+ if (!ipv6_addr_any(&addr)) {
+ ipv6_dev_ac_dec(ifp->idev->dev, &addr);
+ }
+ }
+ if (!ipv6_chk_addr(&ifp->addr, NULL))
+ ip6_rt_addr_del(&ifp->addr, ifp->idev->dev);
+ break;
+ }
+}
+
+#ifdef CONFIG_SYSCTL
+
+static
+int addrconf_sysctl_forward(ctl_table *ctl, int write, struct file * filp,
+ void *buffer, size_t *lenp)
+{
+ /* XXX: race */
+ int *valp = ctl->data;
+ int val = *valp;
+ int ret;
+
+ ret = proc_dointvec(ctl, write, filp, buffer, lenp);
+
+ if (write && *valp != val && valp != &ipv6_devconf_dflt.forwarding) {
+ if (valp == &ipv6_devconf.forwarding)
+ addrconf_forward_change();
+ else
+ dev_forward_change((struct inet6_dev*)ctl->extra1);
+ if (*valp) {
+ ADBG3((KERN_DEBUG "Purge default routes(0)\n"));
+ rt6_purge_dflt_routers(0);
+ }
+ }
+
+ return ret;
+}
+
+static struct addrconf_sysctl_table
+{
+ struct ctl_table_header *sysctl_header;
+ ctl_table addrconf_vars[19];
+ ctl_table addrconf_dev[2];
+ ctl_table addrconf_conf_dir[2];
+ ctl_table addrconf_proto_dir[2];
+ ctl_table addrconf_root_dir[2];
+} addrconf_sysctl = {
+ NULL,
+ {{NET_IPV6_FORWARDING, "forwarding",
+ &ipv6_devconf.forwarding, sizeof(int), 0644, NULL,
+ &addrconf_sysctl_forward},
+
+ {NET_IPV6_HOP_LIMIT, "hop_limit",
+ &ipv6_devconf.hop_limit, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+
+ {NET_IPV6_MTU, "mtu",
+ &ipv6_devconf.mtu6, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+
+ {NET_IPV6_ACCEPT_RA, "accept_ra",
+ &ipv6_devconf.accept_ra, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+
+ {NET_IPV6_ACCEPT_REDIRECTS, "accept_redirects",
+ &ipv6_devconf.accept_redirects, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+
+ {NET_IPV6_AUTOCONF, "autoconf",
+ &ipv6_devconf.autoconf, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+
+ {NET_IPV6_DAD_TRANSMITS, "dad_transmits",
+ &ipv6_devconf.dad_transmits, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+
+ {NET_IPV6_RTR_SOLICITS, "router_solicitations",
+ &ipv6_devconf.rtr_solicits, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+
+ {NET_IPV6_RTR_SOLICIT_INTERVAL, "router_solicitation_interval",
+ &ipv6_devconf.rtr_solicit_interval, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies},
+
+ {NET_IPV6_RTR_SOLICIT_DELAY, "router_solicitation_delay",
+ &ipv6_devconf.rtr_solicit_delay, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies},
+
+#ifdef CONFIG_IPV6_NODEINFO
+ {NET_IPV6_ACCEPT_NI, "accept_ni",
+ &ipv6_devconf.accept_ni, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+#endif
+
+#ifdef CONFIG_IPV6_PRIVACY
+ {NET_IPV6_USE_TEMPADDR, "use_tempaddr",
+ &ipv6_devconf.use_tempaddr, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+
+ {NET_IPV6_TEMP_VALID_LFT, "temp_valid_lft",
+ &ipv6_devconf.temp_valid_lft, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+
+ {NET_IPV6_TEMP_PREFERED_LFT, "temp_prefered_lft",
+ &ipv6_devconf.temp_prefered_lft, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+
+ {NET_IPV6_REGEN_MAX_RETRY, "regen_max_retry",
+ &ipv6_devconf.regen_max_retry, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+
+ {NET_IPV6_MAX_DESYNC_FACTOR, "max_desync_factor",
+ &ipv6_devconf.max_desync_factor, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+#endif
+ {NET_IPV6_MAX_ADDRESSES, "max_addresses",
+ &ipv6_devconf.max_addresses, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+
+ {0}},
+
+ {{NET_PROTO_CONF_ALL, "all", NULL, 0, 0555, addrconf_sysctl.addrconf_vars},{0}},
+ {{NET_IPV6_CONF, "conf", NULL, 0, 0555, addrconf_sysctl.addrconf_dev},{0}},
+ {{NET_IPV6, "ipv6", NULL, 0, 0555, addrconf_sysctl.addrconf_conf_dir},{0}},
+ {{CTL_NET, "net", NULL, 0, 0555, addrconf_sysctl.addrconf_proto_dir},{0}}
+};
+
+static void addrconf_sysctl_register(struct inet6_dev *idev, struct ipv6_devconf *p)
+{
+ int i;
+ struct net_device *dev = idev ? idev->dev : NULL;
+ struct addrconf_sysctl_table *t;
+
+ t = kmalloc(sizeof(*t), GFP_KERNEL);
+ if (t == NULL)
+ return;
+ memcpy(t, &addrconf_sysctl, sizeof(*t));
+ for (i=0; t->addrconf_vars[i].ctl_name; i++) {
+ t->addrconf_vars[i].data += (char*)p - (char*)&ipv6_devconf;
+ t->addrconf_vars[i].de = NULL;
+ t->addrconf_vars[i].extra1 = idev; /* embedded; no ref */
+ }
+ if (dev) {
+ t->addrconf_dev[0].procname = dev->name;
+ t->addrconf_dev[0].ctl_name = dev->ifindex;
+ } else {
+ t->addrconf_dev[0].procname = "default";
+ t->addrconf_dev[0].ctl_name = NET_PROTO_CONF_DEFAULT;
+ }
+ t->addrconf_dev[0].child = t->addrconf_vars;
+ t->addrconf_dev[0].de = NULL;
+ t->addrconf_conf_dir[0].child = t->addrconf_dev;
+ t->addrconf_conf_dir[0].de = NULL;
+ t->addrconf_proto_dir[0].child = t->addrconf_conf_dir;
+ t->addrconf_proto_dir[0].de = NULL;
+ t->addrconf_root_dir[0].child = t->addrconf_proto_dir;
+ t->addrconf_root_dir[0].de = NULL;
+
+ t->sysctl_header = register_sysctl_table(t->addrconf_root_dir, 0);
+ if (t->sysctl_header == NULL)
+ kfree(t);
+ else
+ p->sysctl = t;
+}
+
+static void addrconf_sysctl_unregister(struct ipv6_devconf *p)
+{
+ if (p->sysctl) {
+ struct addrconf_sysctl_table *t = p->sysctl;
+ p->sysctl = NULL;
+ unregister_sysctl_table(t->sysctl_header);
+ kfree(t);
+ }
+}
+
+
+#endif
+
+/*
+ * Device notifier
+ */
+
+int register_inet6addr_notifier(struct notifier_block *nb)
+{
+ return notifier_chain_register(&inet6addr_chain, nb);
+}
+
+int unregister_inet6addr_notifier(struct notifier_block *nb)
+{
+ return notifier_chain_unregister(&inet6addr_chain,nb);
+}
+
+/*
+ * Init / cleanup code
+ */
+
+void __init addrconf_init(void)
+{
+#ifdef CONFIG_IPV6_PRIVACY
+ unsigned long regen_advance;
+#endif
+#ifdef MODULE
+ struct net_device *dev;
+#endif
+
+#ifdef CONFIG_IPV6_PRIVACY
+ struct crypto_tfm *tfm = crypto_alloc_tfm("md5", 0);
+ if (tfm != NULL) {
+ spin_lock(&md5_tfm_lock);
+ if (md5_tfm == NULL) {
+ md5_tfm = tfm;
+ spin_unlock(&md5_tfm_lock);
+ } else {
+ spin_unlock(&md5_tfm_lock);
+ crypto_free_tfm(tfm);
+ }
+ } else {
+ printk(KERN_WARNING
+ "failed to load transform for md5\n");
+ }
+
+ regen_advance = ipv6_devconf.regen_max_retry * ipv6_devconf.dad_transmits * HZ; /* XXX */
+ get_random_bytes(&desync_factor, sizeof(desync_factor));
+ desync_factor %= MAX_DESYNC_FACTOR * HZ < TEMP_VALID_LIFETIME * HZ - regen_advance ?
+ MAX_DESYNC_FACTOR * HZ : TEMP_VALID_LIFETIME * HZ - regen_advance;
+#endif
+
+#ifdef MODULE
+ /* This takes sense only during module load. */
+ rtnl_lock();
+ for (dev = dev_base; dev; dev = dev->next) {
+ if (!(dev->flags&IFF_UP))
+ continue;
+
+ switch (dev->type) {
+ case ARPHRD_LOOPBACK:
+ init_loopback(dev);
+ break;
+ case ARPHRD_ETHER:
+ case ARPHRD_FDDI:
+ case ARPHRD_IEEE802_TR:
+ case ARPHRD_ARCNET:
+ addrconf_dev_config(dev);
+ break;
+ default:;
+ /* Ignore all other */
+ }
+ }
+ rtnl_unlock();
+#endif
+
+#ifdef CONFIG_PROC_FS
+ proc_net_create("if_inet6", 0, iface_proc_info);
+ proc_net_create("addrselect_label_table", 0, ipv6_addrselect_label_proc_info);
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ proc_net_create("if_inet6_debug", 0, iface_debug_proc_info);
+#endif
+#endif
+
+ addrconf_verify(0);
+ rtnetlink_links[PF_INET6] = inet6_rtnetlink_table;
+#ifdef CONFIG_SYSCTL
+ addrconf_sysctl.sysctl_header =
+ register_sysctl_table(addrconf_sysctl.addrconf_root_dir, 0);
+ addrconf_sysctl_register(NULL, &ipv6_devconf_dflt);
+#endif
+ ipv6_addrselect_init();
+}
+
+#ifdef MODULE
+void addrconf_cleanup(void)
+{
+ struct net_device *dev;
+ struct inet6_dev *idev;
+ struct inet6_ifaddr *ifa;
+ int i;
+
+ rtnetlink_links[PF_INET6] = NULL;
+#ifdef CONFIG_SYSCTL
+ addrconf_sysctl_unregister(&ipv6_devconf_dflt);
+ addrconf_sysctl_unregister(&ipv6_devconf);
+#endif
+
+ rtnl_lock();
+
+ /*
+ * clean dev list.
+ */
+
+ for (dev=dev_base; dev; dev=dev->next) {
+ if ((idev = __in6_dev_get(dev)) == NULL)
+ continue;
+ addrconf_ifdown(dev, 1);
+ }
+
+ /*
+ * Check hash table.
+ */
+
+ write_lock_bh(&addrconf_hash_lock);
+ for (i=0; i < IN6_ADDR_HSIZE; i++) {
+ for (ifa=inet6_addr_lst[i]; ifa; ) {
+ struct inet6_ifaddr *bifa;
+
+ bifa = ifa;
+ ifa = ifa->lst_next;
+ ADBG1((KERN_DEBUG
+ "bug: IPv6 address leakage detected: ifa=%p\n",
+ bifa));
+ /* Do not free it; something is wrong.
+ Now we can investigate it with debugger.
+ */
+ }
+ }
+ write_unlock_bh(&addrconf_hash_lock);
+
+ del_timer(&addr_chk_timer);
+
+ rtnl_unlock();
+
+#ifdef CONFIG_IPV6_PRIVACY
+ spin_unlock(&md5_tfm_lock);
+ tfm = md5_tfm;
+ md5_tfm = NULL;
+ spin_unlock(&md5_tfm_lock);
+ if (tfm != NULL)
+ crypto_free_tfm(tfm);
+#endif
+
+#ifdef CONFIG_PROC_FS
+#ifdef CONFIG_IPV6_ACONF_DEBUG
+ proc_net_remove("if_inet6_debug");
+#endif
+ proc_net_remove("if_inet6");
+#endif
+}
+#endif /* MODULE */
diff --git a/uClinux-2.4.31-uc0/net/ipv6/af_inet6.c b/uClinux-2.4.31-uc0/net/ipv6/af_inet6.c
new file mode 100644
index 0000000..e3943e7
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/af_inet6.c
@@ -0,0 +1,826 @@
+/* $USAGI: af_inet6.c,v 1.42 2003/11/12 05:12:01 yoshfuji Exp $ */
+
+/*
+ * PF_INET6 socket protocol family
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ *
+ * Adapted from linux/net/ipv4/af_inet.c
+ *
+ * $Id: af_inet6.c,v 1.65 2001/10/02 02:22:36 davem Exp $
+ *
+ * Fixes:
+ * piggy, Karl Knutson : Socket protocol table
+ * Hideaki YOSHIFUJI : sin6_scope_id support
+ * Arnaldo Melo : check proc_net_create return, cleanups
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <linux/version.h>
+#include <linux/usagi-version.h>
+
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/icmpv6.h>
+#include <linux/brlock.h>
+#include <linux/smp_lock.h>
+
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/udp.h>
+#include <net/tcp.h>
+#include <net/ipip.h>
+#include <net/protocol.h>
+#include <net/inet_common.h>
+#include <net/transp_v6.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+#ifdef MODULE
+static int unloadable = 0; /* XX: Turn to one when all is ok within the
+ module for allowing unload */
+#endif
+
+#if defined(MODULE) && LINUX_VERSION_CODE > 0x20115
+MODULE_AUTHOR("Cast of dozens");
+MODULE_DESCRIPTION("IPv6 protocol stack for Linux");
+MODULE_PARM(unloadable, "i");
+#endif
+
+/* IPv6 procfs goodies... */
+
+#ifdef CONFIG_PROC_FS
+extern int afinet6_getversion(char *, char **, off_t, int);
+extern int anycast6_get_info(char *, char **, off_t, int);
+extern int raw6_get_info(char *, char **, off_t, int);
+extern int tcp6_get_info(char *, char **, off_t, int);
+extern int udp6_get_info(char *, char **, off_t, int);
+extern int afinet6_get_info(char *, char **, off_t, int);
+extern int afinet6_get_snmp(char *, char **, off_t, int);
+extern int rt6_get_dfltrt(char *, char **, off_t, int);
+struct proc_dir_entry *proc_net_devsnmp6 = NULL;
+#endif
+
+#ifdef CONFIG_IPV6_ZONE
+int ipv6_zone_ioctl(unsigned int cmd, void *arg);
+#endif
+
+#ifdef CONFIG_SYSCTL
+extern void ipv6_sysctl_register(void);
+extern void ipv6_sysctl_unregister(void);
+
+int sysctl_ipv6_bindv6only;
+#ifdef CONFIG_IPV6_RESTRICTED_DOUBLE_BIND
+int sysctl_ipv6_bindv6only_restriction;
+#endif
+#endif
+
+int sysctl_ipv6_bindv6only;
+
+#ifdef INET_REFCNT_DEBUG
+atomic_t inet6_sock_nr;
+#endif
+
+/* The inetsw table contains everything that inet_create needs to
+ * build a new socket.
+ */
+struct list_head inetsw6[SOCK_MAX];
+
+static void inet6_sock_destruct(struct sock *sk)
+{
+ inet_sock_destruct(sk);
+
+#ifdef INET_REFCNT_DEBUG
+ atomic_dec(&inet6_sock_nr);
+#endif
+ MOD_DEC_USE_COUNT;
+}
+
+static int inet6_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+ struct list_head *p;
+ struct inet_protosw *answer;
+
+ sk = sk_alloc(PF_INET6, GFP_KERNEL, 1);
+ if (sk == NULL)
+ goto do_oom;
+
+ /* Look for the requested type/protocol pair. */
+ answer = NULL;
+ br_read_lock_bh(BR_NETPROTO_LOCK);
+ list_for_each(p, &inetsw6[sock->type]) {
+ answer = list_entry(p, struct inet_protosw, list);
+
+ /* Check the non-wild match. */
+ if (protocol == answer->protocol) {
+ if (protocol != IPPROTO_IP)
+ break;
+ } else {
+ /* Check for the two wild cases. */
+ if (IPPROTO_IP == protocol) {
+ protocol = answer->protocol;
+ break;
+ }
+ if (IPPROTO_IP == answer->protocol)
+ break;
+ }
+ answer = NULL;
+ }
+ br_read_unlock_bh(BR_NETPROTO_LOCK);
+
+ if (!answer)
+ goto free_and_badtype;
+ if (answer->capability > 0 && !capable(answer->capability))
+ goto free_and_badperm;
+ if (!protocol)
+ goto free_and_noproto;
+
+ sock->ops = answer->ops;
+ sock_init_data(sock, sk);
+
+ sk->prot = answer->prot;
+ sk->no_check = answer->no_check;
+ if (INET_PROTOSW_REUSE & answer->flags)
+ sk->reuse = 1;
+
+ if (SOCK_RAW == sock->type) {
+ sk->num = protocol;
+ if (IPPROTO_RAW == protocol)
+ sk->protinfo.af_inet.hdrincl = 1;
+ }
+
+ sk->destruct = inet6_sock_destruct;
+ sk->zapped = 0;
+ sk->family = PF_INET6;
+ sk->protocol = protocol;
+
+ sk->backlog_rcv = answer->prot->backlog_rcv;
+
+ sk->net_pinfo.af_inet6.hop_limit = -1;
+ sk->net_pinfo.af_inet6.mcast_hops = -1;
+ sk->net_pinfo.af_inet6.mc_loop = 1;
+ sk->net_pinfo.af_inet6.pmtudisc = IPV6_PMTUDISC_WANT;
+
+ sk->net_pinfo.af_inet6.ipv6only = sysctl_ipv6_bindv6only;
+
+ /* Init the ipv4 part of the socket since we can have sockets
+ * using v6 API for ipv4.
+ */
+ sk->protinfo.af_inet.ttl = 64;
+
+ sk->protinfo.af_inet.mc_loop = 1;
+ sk->protinfo.af_inet.mc_ttl = 1;
+ sk->protinfo.af_inet.mc_index = 0;
+ sk->protinfo.af_inet.mc_list = NULL;
+
+ if (ipv4_config.no_pmtu_disc)
+ sk->protinfo.af_inet.pmtudisc = IP_PMTUDISC_DONT;
+ else
+ sk->protinfo.af_inet.pmtudisc = IP_PMTUDISC_WANT;
+
+
+#ifdef INET_REFCNT_DEBUG
+ atomic_inc(&inet6_sock_nr);
+ atomic_inc(&inet_sock_nr);
+#endif
+ MOD_INC_USE_COUNT;
+
+ if (sk->num) {
+ /* It assumes that any protocol which allows
+ * the user to assign a number at socket
+ * creation time automatically shares.
+ */
+ sk->sport = ntohs(sk->num);
+ sk->prot->hash(sk);
+ }
+ if (sk->prot->init) {
+ int err = sk->prot->init(sk);
+ if (err != 0) {
+ MOD_DEC_USE_COUNT;
+ inet_sock_release(sk);
+ return err;
+ }
+ }
+ return 0;
+
+free_and_badtype:
+ sk_free(sk);
+ return -ESOCKTNOSUPPORT;
+free_and_badperm:
+ sk_free(sk);
+ return -EPERM;
+free_and_noproto:
+ sk_free(sk);
+ return -EPROTONOSUPPORT;
+do_oom:
+ return -ENOBUFS;
+}
+
+
+/* bind for INET6 API */
+int inet6_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sockaddr_in6 *addr=(struct sockaddr_in6 *)uaddr;
+ struct sock *sk = sock->sk;
+ __u32 v4addr = 0;
+ unsigned short snum;
+ int addr_type = 0;
+
+ /* If the socket has its own bind function then use it. */
+ if(sk->prot->bind)
+ return sk->prot->bind(sk, uaddr, addr_len);
+
+ if (addr_len < SIN6_LEN_RFC2133)
+ return -EINVAL;
+ addr_type = ipv6_addr_type(&addr->sin6_addr);
+ if ((addr_type & IPV6_ADDR_MULTICAST) && sock->type == SOCK_STREAM)
+ return -EINVAL;
+
+ /* Check if the address belongs to the host. */
+ if (addr_type == IPV6_ADDR_MAPPED) {
+ if (sk->net_pinfo.af_inet6.ipv6only)
+ return -EADDRNOTAVAIL;
+ v4addr = addr->sin6_addr.s6_addr32[3];
+ if (inet_addr_type(v4addr) != RTN_LOCAL)
+ return -EADDRNOTAVAIL;
+ } else {
+ if (addr_type != IPV6_ADDR_ANY) {
+ /* ipv4 addr of the socket is invalid. Only the
+ * unspecified and mapped address have a v4 equivalent.
+ */
+ v4addr = LOOPBACK4_IPV6;
+ if (!(addr_type & IPV6_ADDR_MULTICAST)) {
+ if (!ipv6_chk_addr(&addr->sin6_addr, NULL))
+ return -EADDRNOTAVAIL;
+ }
+ }
+ }
+
+ snum = ntohs(addr->sin6_port);
+ if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))
+ return -EACCES;
+
+ lock_sock(sk);
+
+ /* Check these errors (active socket, double bind). */
+ if ((sk->state != TCP_CLOSE) ||
+ (sk->num != 0)) {
+ release_sock(sk);
+ return -EINVAL;
+ }
+
+ if (addr_type & IPV6_ADDR_LINKLOCAL) {
+ if (addr_len >= sizeof(struct sockaddr_in6) &&
+ addr->sin6_scope_id) {
+ /* Override any existing binding, if another one
+ * is supplied by user.
+ */
+ sk->bound_dev_if = addr->sin6_scope_id;
+ }
+
+#ifndef CONFIG_IPV6_LOOSE_SCOPE_ID
+ /* Binding to link-local address requires an interface */
+ if (sk->bound_dev_if == 0) {
+ release_sock(sk);
+ return -EINVAL;
+ }
+#endif
+ }
+
+ sk->rcv_saddr = v4addr;
+ sk->saddr = v4addr;
+
+ ipv6_addr_copy(&sk->net_pinfo.af_inet6.rcv_saddr, &addr->sin6_addr);
+
+ if (!(addr_type & IPV6_ADDR_MULTICAST))
+ ipv6_addr_copy(&sk->net_pinfo.af_inet6.saddr, &addr->sin6_addr);
+
+ /* Make sure we are allowed to bind here. */
+ if (sk->prot->get_port(sk, snum) != 0) {
+ sk->rcv_saddr = 0;
+ sk->saddr = 0;
+ memset(&sk->net_pinfo.af_inet6.rcv_saddr, 0, sizeof(struct in6_addr));
+ memset(&sk->net_pinfo.af_inet6.saddr, 0, sizeof(struct in6_addr));
+
+ release_sock(sk);
+ return -EADDRINUSE;
+ }
+
+ if (addr_type != IPV6_ADDR_ANY)
+ sk->userlocks |= SOCK_BINDADDR_LOCK;
+ if (snum)
+ sk->userlocks |= SOCK_BINDPORT_LOCK;
+ sk->sport = ntohs(sk->num);
+ sk->dport = 0;
+ sk->daddr = 0;
+
+ release_sock(sk);
+
+ return 0;
+}
+
+int inet6_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk == NULL)
+ return -EINVAL;
+
+ /* Free mc lists */
+ ipv6_sock_mc_close(sk);
+
+ /* Free ac lists */
+ ipv6_sock_ac_close(sk);
+
+ return inet_release(sock);
+}
+
+int inet6_destroy_sock(struct sock *sk)
+{
+ struct sk_buff *skb;
+ struct ipv6_txoptions *opt;
+
+ /*
+ * Release destination entry
+ */
+
+ sk_dst_reset(sk);
+
+ /* Release rx options */
+
+ if ((skb = xchg(&sk->net_pinfo.af_inet6.pktoptions, NULL)) != NULL)
+ kfree_skb(skb);
+
+ /* Free flowlabels */
+ fl6_free_socklist(sk);
+
+ /* Free tx options */
+
+ if ((opt = xchg(&sk->net_pinfo.af_inet6.opt, NULL)) != NULL)
+ sock_kfree_s(sk, opt, opt->tot_len);
+
+ return 0;
+}
+
+/*
+ * This does both peername and sockname.
+ */
+
+int inet6_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *uaddr_len, int peer)
+{
+ struct sockaddr_in6 *sin=(struct sockaddr_in6 *)uaddr;
+ struct sock *sk = sock->sk;
+
+ sin->sin6_family = AF_INET6;
+ sin->sin6_flowinfo = 0;
+ sin->sin6_scope_id = 0;
+ if (peer) {
+ if (!sk->dport)
+ return -ENOTCONN;
+ if (((1<<sk->state)&(TCPF_CLOSE|TCPF_SYN_SENT)) && peer == 1)
+ return -ENOTCONN;
+ sin->sin6_port = sk->dport;
+ ipv6_addr_copy(&sin->sin6_addr, &sk->net_pinfo.af_inet6.daddr);
+ if (sk->net_pinfo.af_inet6.sndflow)
+ sin->sin6_flowinfo = sk->net_pinfo.af_inet6.flow_label;
+ } else {
+ if (ipv6_addr_type(&sk->net_pinfo.af_inet6.rcv_saddr) == IPV6_ADDR_ANY)
+ ipv6_addr_copy(&sin->sin6_addr, &sk->net_pinfo.af_inet6.saddr);
+ else
+ ipv6_addr_copy(&sin->sin6_addr, &sk->net_pinfo.af_inet6.rcv_saddr);
+
+ sin->sin6_port = sk->sport;
+ }
+ if (ipv6_addr_type(&sin->sin6_addr) & IPV6_ADDR_LINKLOCAL)
+ sin->sin6_scope_id = sk->bound_dev_if;
+ *uaddr_len = sizeof(*sin);
+ return(0);
+}
+
+int inet6_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+ int err = -EINVAL;
+ int pid;
+
+ switch(cmd)
+ {
+ case FIOSETOWN:
+ case SIOCSPGRP:
+ if (get_user(pid, (int *) arg))
+ return -EFAULT;
+ /* see sock_no_fcntl */
+ if (current->pid != pid && current->pgrp != -pid &&
+ !capable(CAP_NET_ADMIN))
+ return -EPERM;
+ sk->proc = pid;
+ return(0);
+ case FIOGETOWN:
+ case SIOCGPGRP:
+ return put_user(sk->proc,(int *)arg);
+ case SIOCGSTAMP:
+ if(sk->stamp.tv_sec==0)
+ return -ENOENT;
+ err = copy_to_user((void *)arg, &sk->stamp,
+ sizeof(struct timeval));
+ if (err)
+ return -EFAULT;
+ return 0;
+
+ case SIOCADDRT:
+ case SIOCDELRT:
+
+ return(ipv6_route_ioctl(cmd,(void *)arg));
+
+ case SIOCSIFADDR:
+ return addrconf_add_ifaddr((void *) arg);
+ case SIOCDIFADDR:
+ return addrconf_del_ifaddr((void *) arg);
+ case SIOCSIFDSTADDR:
+ return addrconf_set_dstaddr((void *) arg);
+#ifdef CONFIG_IPV6_ZONE
+ case SIOCSIFZONE:
+ return ipv6_zone_ioctl(cmd, (void *) arg);
+ case SIOCGIFZONE:
+ return ipv6_zone_ioctl(cmd, (void *) arg);
+#endif
+ case SIOCSDADDRLABEL:
+ return addrconf_label_ioctl(cmd, (void *) arg);
+ case SIOCDDADDRLABEL:
+ return addrconf_label_ioctl(cmd, (void *) arg);
+ default:
+ if ((cmd >= SIOCDEVPRIVATE) &&
+ (cmd <= (SIOCDEVPRIVATE + 15)))
+ return(dev_ioctl(cmd,(void *) arg));
+
+ if(sk->prot->ioctl==0 || (err=sk->prot->ioctl(sk, cmd, arg))==-ENOIOCTLCMD)
+ return(dev_ioctl(cmd,(void *) arg));
+ return err;
+ }
+ /*NOTREACHED*/
+ return(0);
+}
+
+struct proto_ops inet6_stream_ops = {
+ family: PF_INET6,
+
+ release: inet6_release,
+ bind: inet6_bind,
+ connect: inet_stream_connect, /* ok */
+ socketpair: sock_no_socketpair, /* a do nothing */
+ accept: inet_accept, /* ok */
+ getname: inet6_getname,
+ poll: tcp_poll, /* ok */
+ ioctl: inet6_ioctl, /* must change */
+ listen: inet_listen, /* ok */
+ shutdown: inet_shutdown, /* ok */
+ setsockopt: inet_setsockopt, /* ok */
+ getsockopt: inet_getsockopt, /* ok */
+ sendmsg: inet_sendmsg, /* ok */
+ recvmsg: inet_recvmsg, /* ok */
+ mmap: sock_no_mmap,
+ sendpage: tcp_sendpage
+};
+
+struct proto_ops inet6_dgram_ops = {
+ family: PF_INET6,
+
+ release: inet6_release,
+ bind: inet6_bind,
+ connect: inet_dgram_connect, /* ok */
+ socketpair: sock_no_socketpair, /* a do nothing */
+ accept: sock_no_accept, /* a do nothing */
+ getname: inet6_getname,
+ poll: udp_poll, /* ok */
+ ioctl: inet6_ioctl, /* must change */
+ listen: sock_no_listen, /* ok */
+ shutdown: inet_shutdown, /* ok */
+ setsockopt: inet_setsockopt, /* ok */
+ getsockopt: inet_getsockopt, /* ok */
+ sendmsg: inet_sendmsg, /* ok */
+ recvmsg: inet_recvmsg, /* ok */
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+struct proto_ops inet6_sockraw_ops = {
+ family: PF_INET6,
+
+ release: inet6_release,
+ bind: inet6_bind,
+ connect: inet_dgram_connect, /* ok */
+ socketpair: sock_no_socketpair, /* a do nothing */
+ accept: sock_no_accept, /* a do nothing */
+ getname: inet6_getname,
+ poll: datagram_poll, /* ok */
+ ioctl: inet6_ioctl, /* must change */
+ listen: sock_no_listen, /* ok */
+ shutdown: inet_shutdown, /* ok */
+ setsockopt: inet_setsockopt, /* ok */
+ getsockopt: inet_getsockopt, /* ok */
+ sendmsg: inet_sendmsg, /* ok */
+ recvmsg: inet_recvmsg, /* ok */
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+struct net_proto_family inet6_family_ops = {
+ PF_INET6,
+ inet6_create
+};
+
+#ifdef MODULE
+int ipv6_unload(void)
+{
+#ifdef CONFIG_IPV6_DEBUG
+ printk(KERN_DEBUG "%s: unloadable=%d, usecount=%d\n",
+ __FUNCTION__,
+ unloadable, atomic_read(&(__this_module.uc.usecount)));
+#endif
+ if (!unloadable) return 1;
+ /* We keep internally 3 raw sockets */
+ return atomic_read(&(__this_module.uc.usecount)) == 3 ? 0 : -EBUSY;
+}
+#endif
+
+#if defined(MODULE) && defined(CONFIG_SYSCTL)
+extern void ipv6_sysctl_register(void);
+extern void ipv6_sysctl_unregister(void);
+#endif
+
+static struct inet_protosw rawv6_protosw = {
+ type: SOCK_RAW,
+ protocol: IPPROTO_IP, /* wild card */
+ prot: &rawv6_prot,
+ ops: &inet6_sockraw_ops,
+ capability: CAP_NET_RAW,
+ no_check: UDP_CSUM_DEFAULT,
+ flags: INET_PROTOSW_REUSE,
+};
+
+#define INETSW6_ARRAY_LEN (sizeof(inetsw6_array) / sizeof(struct inet_protosw))
+
+void
+inet6_register_protosw(struct inet_protosw *p)
+{
+ struct list_head *lh;
+ struct inet_protosw *answer;
+ int protocol = p->protocol;
+ struct list_head *last_perm;
+
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+
+ if (p->type >= SOCK_MAX)
+ goto out_illegal;
+
+ /* If we are trying to override a permanent protocol, bail. */
+ answer = NULL;
+ last_perm = &inetsw6[p->type];
+ list_for_each(lh, &inetsw6[p->type]) {
+ answer = list_entry(lh, struct inet_protosw, list);
+
+ /* Check only the non-wild match. */
+ if (INET_PROTOSW_PERMANENT & answer->flags) {
+ if (protocol == answer->protocol)
+ break;
+ last_perm = lh;
+ }
+
+ answer = NULL;
+ }
+ if (answer)
+ goto out_permanent;
+
+ /* Add the new entry after the last permanent entry if any, so that
+ * the new entry does not override a permanent entry when matched with
+ * a wild-card protocol. But it is allowed to override any existing
+ * non-permanent entry. This means that when we remove this entry, the
+ * system automatically returns to the old behavior.
+ */
+ list_add(&p->list, last_perm);
+out:
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ return;
+
+out_permanent:
+ printk(KERN_ERR "Attempt to override permanent protocol %d.\n",
+ protocol);
+ goto out;
+
+out_illegal:
+ printk(KERN_ERR
+ "Ignoring attempt to register illegal socket type %d.\n",
+ p->type);
+ goto out;
+}
+
+void
+inet6_unregister_protosw(struct inet_protosw *p)
+{
+ inet_unregister_protosw(p);
+}
+
+static int __init inet6_init(void)
+{
+ struct sk_buff *dummy_skb;
+ struct list_head *r;
+ int err;
+
+#ifdef MODULE
+ if (!mod_member_present(&__this_module, can_unload))
+ return -EINVAL;
+
+ __this_module.can_unload = &ipv6_unload;
+#endif
+
+ printk(KERN_INFO "IPv6 v0.8 (" USAGI_RELEASE ") for NET4.0\n");
+
+ if (sizeof(struct inet6_skb_parm) > sizeof(dummy_skb->cb))
+ {
+ printk(KERN_CRIT "inet6_proto_init: size fault\n");
+ return -EINVAL;
+ }
+
+ /* Register the socket-side information for inet6_create. */
+ for(r = &inetsw6[0]; r < &inetsw6[SOCK_MAX]; ++r)
+ INIT_LIST_HEAD(r);
+
+ /* We MUST register RAW sockets before we create the ICMP6,
+ * IGMP6, or NDISC control sockets.
+ */
+ inet6_register_protosw(&rawv6_protosw);
+
+ /*
+ * ipngwg API draft makes clear that the correct semantics
+ * for TCP and UDP is to consider one TCP and UDP instance
+ * in a host available by both INET and INET6 APIs and
+ * able to communicate via both network protocols.
+ */
+
+#if defined(MODULE) && defined(CONFIG_SYSCTL)
+ ipv6_sysctl_register();
+#endif
+ err = icmpv6_init(&inet6_family_ops);
+ if (err)
+ goto icmp_fail;
+ err = ndisc_init(&inet6_family_ops);
+ if (err)
+ goto ndisc_fail;
+ err = igmp6_init(&inet6_family_ops);
+ if (err)
+ goto igmp_fail;
+ /* Create /proc/foo6 entries. */
+#ifdef CONFIG_PROC_FS
+ err = -ENOMEM;
+ if (!proc_net_create("inet6_version", 0, afinet6_getversion))
+ goto proc_inet6version_fail;
+ if (!proc_net_create("raw6", 0, raw6_get_info))
+ goto proc_raw6_fail;
+ if (!proc_net_create("tcp6", 0, tcp6_get_info))
+ goto proc_tcp6_fail;
+ if (!proc_net_create("udp6", 0, udp6_get_info))
+ goto proc_udp6_fail;
+ if (!proc_net_create("sockstat6", 0, afinet6_get_info))
+ goto proc_sockstat6_fail;
+ if (!proc_net_create("snmp6", 0, afinet6_get_snmp))
+ goto proc_snmp6_fail;
+ if (!proc_net_create("anycast6", 0, anycast6_get_info))
+ goto proc_anycast6_fail;
+ if (!(proc_net_create("rt6_default", 0, rt6_get_dfltrt)))
+ goto proc_dfltrt6_fail;
+ if (!(proc_net_devsnmp6 = proc_mkdir("dev_snmp6", proc_net)))
+ goto proc_devsnmp6_fail;
+#endif
+ ipv6_netdev_notif_init();
+ ipv6_packet_init();
+ ip6_route_init();
+ ip6_flowlabel_init();
+ addrconf_init();
+#ifndef CONFIG_NET_IPIP_IPV6
+ sit_init();
+#endif
+ ipv6_frag_init();
+
+ /* Init v6 transport protocols. */
+ udpv6_init();
+ tcpv6_init();
+
+#if !defined(CONFIG_IPV6) && defined(CONFIG_IPV6_IM)
+ inter_module_register(IM_ICMPV6_SEND, THIS_MODULE, &icmpv6_send);
+ inter_module_register(IM_IPV6_DEVCONF, THIS_MODULE, &ipv6_devconf);
+#endif
+
+ /* Now the userspace is allowed to create INET6 sockets. */
+ (void) sock_register(&inet6_family_ops);
+
+#ifdef CONFIG_IPV6_IPV6_TUNNEL
+ ipv6_ipv6_tunnel_init();
+#endif
+
+ return 0;
+
+#ifdef CONFIG_PROC_FS
+proc_devsnmp6_fail:
+ proc_net_remove("snmp6");
+ proc_dfltrt6_fail:
+ proc_net_remove("rt6_default");
+proc_anycast6_fail:
+ proc_net_remove("anycast6");
+proc_snmp6_fail:
+ proc_net_remove("sockstat6");
+proc_sockstat6_fail:
+ proc_net_remove("udp6");
+proc_udp6_fail:
+ proc_net_remove("tcp6");
+proc_tcp6_fail:
+ proc_net_remove("raw6");
+proc_raw6_fail:
+ proc_net_remove("inet6_version");
+proc_inet6version_fail:
+ igmp6_cleanup();
+#endif
+igmp_fail:
+ ndisc_cleanup();
+ndisc_fail:
+ icmpv6_cleanup();
+icmp_fail:
+#if defined(MODULE) && defined(CONFIG_SYSCTL)
+ ipv6_sysctl_unregister();
+#endif
+ return err;
+}
+module_init(inet6_init);
+
+
+#ifdef MODULE
+static void inet6_exit(void)
+{
+ /* First of all disallow new sockets creation. */
+ sock_unregister(PF_INET6);
+#if !defined(CONFIG_IPV6) && defined(CONFIG_IPV6_IM)
+ inter_module_unregister(IM_IPV6_DEVCONF);
+ inter_module_unregister(IM_ICMPV6_SEND);
+#endif
+#ifdef CONFIG_PROC_FS
+ proc_net_remove("inet6_version");
+ proc_net_remove("raw6");
+ proc_net_remove("tcp6");
+ proc_net_remove("udp6");
+ proc_net_remove("sockstat6");
+ proc_net_remove("snmp6");
+ proc_net_remove("anycast6");
+ proc_net_remove("rt6_default");
+ if (proc_net_devsnmp6)
+ remove_proc_entry("dev_snmp6", proc_net);
+#endif
+#ifdef CONFIG_IPV6_IPV6_TUNNEL
+ ipv6_ipv6_tunnel_exit();
+#endif
+ /* Cleanup code parts. */
+ sit_cleanup();
+ ipv6_netdev_notif_cleanup();
+ ip6_flowlabel_cleanup();
+ addrconf_cleanup();
+ ip6_route_cleanup();
+ ipv6_packet_cleanup();
+ igmp6_cleanup();
+ ndisc_cleanup();
+ icmpv6_cleanup();
+#ifdef CONFIG_SYSCTL
+ ipv6_sysctl_unregister();
+#endif
+}
+module_exit(inet6_exit);
+#endif /* MODULE */
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv6/anycast.c b/uClinux-2.4.31-uc0/net/ipv6/anycast.c
new file mode 100644
index 0000000..7a983bd
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/anycast.c
@@ -0,0 +1,466 @@
+/*
+ * Anycast support for IPv6
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * David L Stevens (dlstevens@us.ibm.com)
+ *
+ * based heavily on net/ipv6/mcast.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/random.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/sched.h>
+#include <linux/net.h>
+#include <linux/in6.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/route.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/if_inet6.h>
+#include <net/ndisc.h>
+#include <net/addrconf.h>
+#include <net/ip6_route.h>
+
+#include <net/checksum.h>
+
+/* Big ac list lock for all the sockets */
+static rwlock_t ipv6_sk_ac_lock = RW_LOCK_UNLOCKED;
+
+/* XXX ip6_onlink() really belong in net/core.c */
+
+static int
+ip6_onlink(struct in6_addr *addr, struct net_device *dev)
+{
+ struct inet6_dev *idev;
+ struct inet6_ifaddr *ifa;
+ int onlink;
+
+ onlink = 0;
+ read_lock(&addrconf_lock);
+ idev = __in6_dev_get(dev);
+ if (idev) {
+ read_lock_bh(&idev->lock);
+ for (ifa=idev->addr_list; ifa; ifa=ifa->if_next) {
+ onlink = !ipv6_prefix_cmp(addr, &ifa->addr,
+ ifa->prefix_len);
+ if (onlink)
+ break;
+ }
+ read_unlock_bh(&idev->lock);
+ }
+ read_unlock(&addrconf_lock);
+ return onlink;
+}
+
+
+/*
+ * socket join an anycast group
+ */
+
+int ipv6_sock_ac_join(struct sock *sk, int ifindex, struct in6_addr *addr)
+{
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct net_device *dev = NULL;
+ struct inet6_dev *idev;
+ struct ipv6_ac_socklist *pac;
+ int ishost = !ipv6_devconf.forwarding;
+ int err = 0;
+
+ if (ipv6_addr_type(addr) & IPV6_ADDR_MULTICAST)
+ return -EINVAL;
+
+ pac = sock_kmalloc(sk, sizeof(struct ipv6_ac_socklist), GFP_KERNEL);
+ if (pac == NULL)
+ return -ENOMEM;
+ pac->acl_next = NULL;
+ ipv6_addr_copy(&pac->acl_addr, addr);
+
+ if (ifindex == 0) {
+ struct rt6_info *rt;
+
+ rt = rt6_lookup(addr, NULL, 0, 0);
+ if (rt) {
+ dev = rt->rt6i_dev;
+ dev_hold(dev);
+ dst_release(&rt->u.dst);
+ } else if (ishost) {
+ err = -EADDRNOTAVAIL;
+ goto out_free_pac;
+ } else {
+ /* router, no matching interface: just pick one */
+
+ dev = dev_get_by_flags(IFF_UP, IFF_UP|IFF_LOOPBACK);
+ }
+ } else
+ dev = dev_get_by_index(ifindex);
+
+ if (dev == NULL) {
+ err = -ENODEV;
+ goto out_free_pac;
+ }
+
+ idev = in6_dev_get(dev);
+ if (!idev) {
+ if (ifindex)
+ err = -ENODEV;
+ else
+ err = -EADDRNOTAVAIL;
+ goto out_dev_put;
+ }
+ /* reset ishost, now that we have a specific device */
+ ishost = !idev->cnf.forwarding;
+ in6_dev_put(idev);
+
+ pac->acl_ifindex = dev->ifindex;
+
+ /* XXX
+ * For hosts, allow link-local or matching prefix anycasts.
+ * This obviates the need for propagating anycast routes while
+ * still allowing some non-router anycast participation.
+ *
+ * allow anyone to join anycasts that don't require a special route
+ * and can't be spoofs of unicast addresses (reserved anycast only)
+ */
+ if (!ip6_onlink(addr, dev)) {
+ if (ishost)
+ err = -EADDRNOTAVAIL;
+ else if (!capable(CAP_NET_ADMIN))
+ err = -EPERM;
+ if (err)
+ goto out_dev_put;
+ } else if (!(ipv6_addr_type(addr) & IPV6_ADDR_ANYCAST) &&
+ !capable(CAP_NET_ADMIN)) {
+ err = -EPERM;
+ goto out_dev_put;
+ }
+
+ err = ipv6_dev_ac_inc(dev, addr);
+ if (err)
+ goto out_dev_put;
+
+ write_lock_bh(&ipv6_sk_ac_lock);
+ pac->acl_next = np->ipv6_ac_list;
+ np->ipv6_ac_list = pac;
+ write_unlock_bh(&ipv6_sk_ac_lock);
+
+ dev_put(dev);
+
+ return 0;
+
+out_dev_put:
+ dev_put(dev);
+out_free_pac:
+ sock_kfree_s(sk, pac, sizeof(*pac));
+ return err;
+}
+
+/*
+ * socket leave an anycast group
+ */
+int ipv6_sock_ac_drop(struct sock *sk, int ifindex, struct in6_addr *addr)
+{
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct net_device *dev;
+ struct ipv6_ac_socklist *pac, *prev_pac;
+
+ write_lock_bh(&ipv6_sk_ac_lock);
+ prev_pac = 0;
+ for (pac = np->ipv6_ac_list; pac; pac = pac->acl_next) {
+ if ((ifindex == 0 || pac->acl_ifindex == ifindex) &&
+ ipv6_addr_cmp(&pac->acl_addr, addr) == 0)
+ break;
+ prev_pac = pac;
+ }
+ if (!pac) {
+ write_unlock_bh(&ipv6_sk_ac_lock);
+ return -ENOENT;
+ }
+ if (prev_pac)
+ prev_pac->acl_next = pac->acl_next;
+ else
+ np->ipv6_ac_list = pac->acl_next;
+
+ write_unlock_bh(&ipv6_sk_ac_lock);
+
+ dev = dev_get_by_index(pac->acl_ifindex);
+ if (dev) {
+ ipv6_dev_ac_dec(dev, &pac->acl_addr);
+ dev_put(dev);
+ }
+ sock_kfree_s(sk, pac, sizeof(*pac));
+ return 0;
+}
+
+void ipv6_sock_ac_close(struct sock *sk)
+{
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct net_device *dev = 0;
+ struct ipv6_ac_socklist *pac;
+ int prev_index;
+
+ write_lock_bh(&ipv6_sk_ac_lock);
+ pac = np->ipv6_ac_list;
+ np->ipv6_ac_list = 0;
+ write_unlock_bh(&ipv6_sk_ac_lock);
+
+ prev_index = 0;
+ while (pac) {
+ struct ipv6_ac_socklist *next = pac->acl_next;
+
+ if (pac->acl_ifindex != prev_index) {
+ if (dev)
+ dev_put(dev);
+ dev = dev_get_by_index(pac->acl_ifindex);
+ prev_index = pac->acl_ifindex;
+ }
+ if (dev)
+ ipv6_dev_ac_dec(dev, &pac->acl_addr);
+ sock_kfree_s(sk, pac, sizeof(*pac));
+ pac = next;
+ }
+ if (dev)
+ dev_put(dev);
+}
+
+int inet6_ac_check(struct sock *sk, struct in6_addr *addr, int ifindex)
+{
+ struct ipv6_ac_socklist *pac;
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ int found;
+
+ found = 0;
+ read_lock(&ipv6_sk_ac_lock);
+ for (pac=np->ipv6_ac_list; pac; pac=pac->acl_next) {
+ if (ifindex && pac->acl_ifindex != ifindex)
+ continue;
+ found = ipv6_addr_cmp(&pac->acl_addr, addr) == 0;
+ if (found)
+ break;
+ }
+ read_unlock(&ipv6_sk_ac_lock);
+
+ return found;
+}
+
+static void aca_put(struct ifacaddr6 *ac)
+{
+ if (atomic_dec_and_test(&ac->aca_refcnt)) {
+ in6_dev_put(ac->aca_idev);
+ kfree(ac);
+ }
+}
+
+/*
+ * device anycast group inc (add if not found)
+ */
+int ipv6_dev_ac_inc(struct net_device *dev, struct in6_addr *addr)
+{
+ struct ifacaddr6 *aca;
+ struct inet6_dev *idev;
+
+ idev = in6_dev_get(dev);
+
+ if (idev == NULL)
+ return -EINVAL;
+
+ write_lock_bh(&idev->lock);
+ if (idev->dead) {
+ write_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ return -ENODEV;
+ }
+
+ for (aca = idev->ac_list; aca; aca = aca->aca_next) {
+ if (ipv6_addr_cmp(&aca->aca_addr, addr) == 0) {
+ aca->aca_users++;
+ write_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ return 0;
+ }
+ }
+
+ /*
+ * not found: create a new one.
+ */
+
+ aca = kmalloc(sizeof(struct ifacaddr6), GFP_ATOMIC);
+
+ if (aca == NULL) {
+ write_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ return -ENOMEM;
+ }
+
+ memset(aca, 0, sizeof(struct ifacaddr6));
+
+ ipv6_addr_copy(&aca->aca_addr, addr);
+ aca->aca_idev = idev;
+ aca->aca_users = 1;
+ atomic_set(&aca->aca_refcnt, 2);
+ aca->aca_lock = SPIN_LOCK_UNLOCKED;
+
+ aca->aca_next = idev->ac_list;
+ idev->ac_list = aca;
+ write_unlock_bh(&idev->lock);
+
+ ip6_rt_addr_add(&aca->aca_addr, dev);
+
+ addrconf_join_solict(dev, &aca->aca_addr);
+
+ aca_put(aca);
+ return 0;
+}
+
+/*
+ * device anycast group decrement
+ */
+int ipv6_dev_ac_dec(struct net_device *dev, struct in6_addr *addr)
+{
+ struct inet6_dev *idev;
+ struct ifacaddr6 *aca, *prev_aca;
+
+ idev = in6_dev_get(dev);
+ if (idev == NULL)
+ return -ENODEV;
+
+ write_lock_bh(&idev->lock);
+ prev_aca = 0;
+ for (aca = idev->ac_list; aca; aca = aca->aca_next) {
+ if (ipv6_addr_cmp(&aca->aca_addr, addr) == 0)
+ break;
+ prev_aca = aca;
+ }
+ if (!aca) {
+ write_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ return -ENOENT;
+ }
+ if (--aca->aca_users > 0) {
+ write_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ return 0;
+ }
+ if (prev_aca)
+ prev_aca->aca_next = aca->aca_next;
+ else
+ idev->ac_list = aca->aca_next;
+ write_unlock_bh(&idev->lock);
+ addrconf_leave_solict(dev, &aca->aca_addr);
+
+ ip6_rt_addr_del(&aca->aca_addr, dev);
+
+ aca_put(aca);
+ in6_dev_put(idev);
+ return 0;
+}
+
+/*
+ * check if the interface has this anycast address
+ */
+static int ipv6_chk_acast_dev(struct net_device *dev, struct in6_addr *addr)
+{
+ struct inet6_dev *idev;
+ struct ifacaddr6 *aca;
+
+ idev = in6_dev_get(dev);
+ if (idev) {
+ read_lock_bh(&idev->lock);
+ for (aca = idev->ac_list; aca; aca = aca->aca_next)
+ if (ipv6_addr_cmp(&aca->aca_addr, addr) == 0)
+ break;
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ return aca != 0;
+ }
+ return 0;
+}
+
+/*
+ * check if given interface (or any, if dev==0) has this anycast address
+ */
+int ipv6_chk_acast_addr(struct net_device *dev, struct in6_addr *addr)
+{
+ if (dev)
+ return ipv6_chk_acast_dev(dev, addr);
+ read_lock(&dev_base_lock);
+ for (dev=dev_base; dev; dev=dev->next)
+ if (ipv6_chk_acast_dev(dev, addr))
+ break;
+ read_unlock(&dev_base_lock);
+ return dev != 0;
+}
+
+
+#ifdef CONFIG_PROC_FS
+int anycast6_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ off_t pos=0, begin=0;
+ struct ifacaddr6 *im;
+ int len=0;
+ struct net_device *dev;
+
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev; dev = dev->next) {
+ struct inet6_dev *idev;
+
+ if ((idev = in6_dev_get(dev)) == NULL)
+ continue;
+
+ read_lock_bh(&idev->lock);
+ for (im = idev->ac_list; im; im = im->aca_next) {
+ int i;
+
+ len += sprintf(buffer+len,"%-4d %-15s ", dev->ifindex, dev->name);
+
+ for (i=0; i<16; i++)
+ len += sprintf(buffer+len, "%02x", im->aca_addr.s6_addr[i]);
+
+ len += sprintf(buffer+len, " %5d\n", im->aca_users);
+
+ pos=begin+len;
+ if (pos < offset) {
+ len=0;
+ begin=pos;
+ }
+ if (pos > offset+length) {
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ goto done;
+ }
+ }
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ }
+
+done:
+ read_unlock(&dev_base_lock);
+
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ if (len<0)
+ len=0;
+ return len;
+}
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/ipv6/datagram.c b/uClinux-2.4.31-uc0/net/ipv6/datagram.c
new file mode 100644
index 0000000..97c4b6b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/datagram.c
@@ -0,0 +1,456 @@
+/* $USAGI: datagram.c,v 1.10 2002/10/03 23:05:05 yoshfuji Exp $ */
+
+/*
+ * common UDP/RAW code
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ *
+ * $Id: datagram.c,v 1.23 2001/09/01 00:31:50 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in6.h>
+#include <linux/ipv6.h>
+#include <linux/route.h>
+
+#include <net/ipv6.h>
+#include <net/ndisc.h>
+#include <net/addrconf.h>
+#include <net/transp_v6.h>
+
+#include <linux/errqueue.h>
+#include <asm/uaccess.h>
+
+void ipv6_icmp_error(struct sock *sk, struct sk_buff *skb, int err,
+ u16 port, u32 info, u8 *payload)
+{
+ struct icmp6hdr *icmph = (struct icmp6hdr *)skb->h.raw;
+ struct sock_exterr_skb *serr;
+
+ if (!sk->net_pinfo.af_inet6.recverr)
+ return;
+
+ skb = skb_clone(skb, GFP_ATOMIC);
+ if (!skb)
+ return;
+
+ serr = SKB_EXT_ERR(skb);
+ serr->ee.ee_errno = err;
+ serr->ee.ee_origin = SO_EE_ORIGIN_ICMP6;
+ serr->ee.ee_type = icmph->icmp6_type;
+ serr->ee.ee_code = icmph->icmp6_code;
+ serr->ee.ee_pad = 0;
+ serr->ee.ee_info = info;
+ serr->ee.ee_data = 0;
+ serr->addr_offset = (u8*)&(((struct ipv6hdr*)(icmph+1))->daddr) - skb->nh.raw;
+ serr->port = port;
+
+ skb->h.raw = payload;
+ __skb_pull(skb, payload - skb->data);
+
+ if (sock_queue_err_skb(sk, skb))
+ kfree_skb(skb);
+}
+
+void ipv6_local_error(struct sock *sk, int err, struct flowi *fl, u32 info)
+{
+ struct sock_exterr_skb *serr;
+ struct ipv6hdr *iph;
+ struct sk_buff *skb;
+
+ if (!sk->net_pinfo.af_inet6.recverr)
+ return;
+
+ skb = alloc_skb(sizeof(struct ipv6hdr), GFP_ATOMIC);
+ if (!skb)
+ return;
+
+ iph = (struct ipv6hdr*)skb_put(skb, sizeof(struct ipv6hdr));
+ skb->nh.ipv6h = iph;
+ ipv6_addr_copy(&iph->daddr, fl->fl6_dst);
+
+ serr = SKB_EXT_ERR(skb);
+ serr->ee.ee_errno = err;
+ serr->ee.ee_origin = SO_EE_ORIGIN_LOCAL;
+ serr->ee.ee_type = 0;
+ serr->ee.ee_code = 0;
+ serr->ee.ee_pad = 0;
+ serr->ee.ee_info = info;
+ serr->ee.ee_data = 0;
+ serr->addr_offset = (u8*)&iph->daddr - skb->nh.raw;
+ serr->port = fl->uli_u.ports.dport;
+
+ skb->h.raw = skb->tail;
+ __skb_pull(skb, skb->tail - skb->data);
+
+ if (sock_queue_err_skb(sk, skb))
+ kfree_skb(skb);
+}
+
+/*
+ * Handle MSG_ERRQUEUE
+ */
+int ipv6_recv_error(struct sock *sk, struct msghdr *msg, int len)
+{
+ struct sock_exterr_skb *serr;
+ struct sk_buff *skb, *skb2;
+ struct sockaddr_in6 *sin;
+ struct {
+ struct sock_extended_err ee;
+ struct sockaddr_in6 offender;
+ } errhdr;
+ int err;
+ int copied;
+
+ err = -EAGAIN;
+ skb = skb_dequeue(&sk->error_queue);
+ if (skb == NULL)
+ goto out;
+
+ copied = skb->len;
+ if (copied > len) {
+ msg->msg_flags |= MSG_TRUNC;
+ copied = len;
+ }
+ err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+ if (err)
+ goto out_free_skb;
+
+ sock_recv_timestamp(msg, sk, skb);
+
+ serr = SKB_EXT_ERR(skb);
+
+ sin = (struct sockaddr_in6 *)msg->msg_name;
+ if (sin) {
+ sin->sin6_family = AF_INET6;
+ sin->sin6_flowinfo = 0;
+ sin->sin6_port = serr->port;
+ sin->sin6_scope_id = 0;
+ if (serr->ee.ee_origin == SO_EE_ORIGIN_ICMP6) {
+ ipv6_addr_copy(&sin->sin6_addr,
+ (struct in6_addr *)(skb->nh.raw + serr->addr_offset));
+ if (sk->net_pinfo.af_inet6.sndflow)
+ sin->sin6_flowinfo = *(u32*)(skb->nh.raw + serr->addr_offset - 24) & IPV6_FLOWINFO_MASK;
+ if (ipv6_addr_type(&sin->sin6_addr) & IPV6_ADDR_LINKLOCAL) {
+ struct inet6_skb_parm *opt = (struct inet6_skb_parm *) skb->cb;
+ sin->sin6_scope_id = opt->iif;
+ }
+ } else {
+ ipv6_addr_set(&sin->sin6_addr, 0, 0,
+ htonl(0xffff),
+ *(u32*)(skb->nh.raw + serr->addr_offset));
+ }
+ }
+
+ memcpy(&errhdr.ee, &serr->ee, sizeof(struct sock_extended_err));
+ sin = &errhdr.offender;
+ sin->sin6_family = AF_UNSPEC;
+ if (serr->ee.ee_origin != SO_EE_ORIGIN_LOCAL) {
+ sin->sin6_family = AF_INET6;
+ sin->sin6_flowinfo = 0;
+ sin->sin6_scope_id = 0;
+ if (serr->ee.ee_origin == SO_EE_ORIGIN_ICMP6) {
+ ipv6_addr_copy(&sin->sin6_addr, &skb->nh.ipv6h->saddr);
+ if (sk->net_pinfo.af_inet6.rxopt.all)
+ datagram_recv_ctl(sk, msg, skb);
+ if (ipv6_addr_type(&sin->sin6_addr) & IPV6_ADDR_LINKLOCAL) {
+ struct inet6_skb_parm *opt = (struct inet6_skb_parm *) skb->cb;
+ sin->sin6_scope_id = opt->iif;
+ }
+ } else {
+ ipv6_addr_set(&sin->sin6_addr, 0, 0,
+ htonl(0xffff),
+ skb->nh.iph->saddr);
+ if (sk->protinfo.af_inet.cmsg_flags)
+ ip_cmsg_recv(msg, skb);
+ }
+ }
+
+ put_cmsg(msg, SOL_IPV6, IPV6_RECVERR, sizeof(errhdr), &errhdr);
+
+ /* Now we could try to dump offended packet options */
+
+ msg->msg_flags |= MSG_ERRQUEUE;
+ err = copied;
+
+ /* Reset and regenerate socket error */
+ spin_lock_irq(&sk->error_queue.lock);
+ sk->err = 0;
+ if ((skb2 = skb_peek(&sk->error_queue)) != NULL) {
+ sk->err = SKB_EXT_ERR(skb2)->ee.ee_errno;
+ spin_unlock_irq(&sk->error_queue.lock);
+ sk->error_report(sk);
+ } else {
+ spin_unlock_irq(&sk->error_queue.lock);
+ }
+
+out_free_skb:
+ kfree_skb(skb);
+out:
+ return err;
+}
+
+
+
+int datagram_recv_ctl(struct sock *sk, struct msghdr *msg, struct sk_buff *skb)
+{
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct inet6_skb_parm *opt = (struct inet6_skb_parm *) skb->cb;
+
+ if (np->rxopt.bits.rxinfo) {
+ struct in6_pktinfo src_info;
+
+ src_info.ipi6_ifindex = opt->iif;
+ ipv6_addr_copy(&src_info.ipi6_addr, &skb->nh.ipv6h->daddr);
+ put_cmsg(msg, SOL_IPV6, IPV6_PKTINFO, sizeof(src_info), &src_info);
+ }
+
+ if (np->rxopt.bits.rxhlim) {
+ int hlim = skb->nh.ipv6h->hop_limit;
+ put_cmsg(msg, SOL_IPV6, IPV6_HOPLIMIT, sizeof(hlim), &hlim);
+ }
+
+ if (np->rxopt.bits.rxflow && (*(u32*)skb->nh.raw & IPV6_FLOWINFO_MASK)) {
+ u32 flowinfo = *(u32*)skb->nh.raw & IPV6_FLOWINFO_MASK;
+ put_cmsg(msg, SOL_IPV6, IPV6_FLOWINFO, sizeof(flowinfo), &flowinfo);
+ }
+ if (np->rxopt.bits.hopopts && opt->hop) {
+ u8 *ptr = skb->nh.raw + opt->hop;
+ put_cmsg(msg, SOL_IPV6, IPV6_HOPOPTS, (ptr[1]+1)<<3, ptr);
+ }
+ if (np->rxopt.bits.dstopts && opt->dst0) {
+ u8 *ptr = skb->nh.raw + opt->dst0;
+ put_cmsg(msg, SOL_IPV6, IPV6_DSTOPTS, (ptr[1]+1)<<3, ptr);
+ }
+ if (np->rxopt.bits.srcrt && opt->srcrt) {
+ struct ipv6_rt_hdr *rthdr = (struct ipv6_rt_hdr *)(skb->nh.raw + opt->srcrt);
+ put_cmsg(msg, SOL_IPV6, IPV6_RTHDR, (rthdr->hdrlen+1) << 3, rthdr);
+ }
+ if (np->rxopt.bits.authhdr && opt->auth) {
+ u8 *ptr = skb->nh.raw + opt->auth;
+ put_cmsg(msg, SOL_IPV6, IPV6_AUTHHDR, (ptr[1]+1)<<2, ptr);
+ }
+ if (np->rxopt.bits.dstopts && opt->dst1) {
+ u8 *ptr = skb->nh.raw + opt->dst1;
+ put_cmsg(msg, SOL_IPV6, IPV6_DSTOPTS, (ptr[1]+1)<<3, ptr);
+ }
+
+ if (np->recvopt.tclass) {
+ struct ipv6hdr *ipv6h = skb->nh.ipv6h;
+ int tclass = ((ipv6h->tclass1 << 4) | (ipv6h->tclass2_flow[0] >> 4)) & 0xff;
+ put_cmsg(msg, SOL_IPV6, IPV6_TCLASS, sizeof(tclass), &tclass);
+ }
+
+ return 0;
+}
+
+int datagram_send_ctl(struct msghdr *msg, struct flowi *fl,
+ struct ipv6_txoptions *opt,
+ int *hlimit, int *tclass)
+{
+ struct in6_pktinfo *src_info;
+ struct cmsghdr *cmsg;
+ struct ipv6_rt_hdr *rthdr;
+ struct ipv6_opt_hdr *hdr;
+ int len;
+ int err = 0;
+
+ for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+
+ if (!CMSG_OK(msg, cmsg)) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+
+ if (cmsg->cmsg_level != SOL_IPV6)
+ continue;
+
+ switch (cmsg->cmsg_type) {
+ case IPV6_PKTINFO:
+ if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct in6_pktinfo))) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+
+ src_info = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+
+ if (src_info->ipi6_ifindex) {
+ if (fl->oif && src_info->ipi6_ifindex != fl->oif)
+ return -EINVAL;
+ fl->oif = src_info->ipi6_ifindex;
+ }
+
+ if (!ipv6_addr_any(&src_info->ipi6_addr)) {
+ if (!ipv6_chk_addr(&src_info->ipi6_addr, NULL)) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+
+ fl->fl6_src = &src_info->ipi6_addr;
+ }
+
+ break;
+
+ case IPV6_FLOWINFO:
+ if (cmsg->cmsg_len < CMSG_LEN(4)) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+
+ if (fl->fl6_flowlabel&IPV6_FLOWINFO_MASK) {
+ if ((fl->fl6_flowlabel^*(u32 *)CMSG_DATA(cmsg))&~IPV6_FLOWINFO_MASK) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+ }
+ fl->fl6_flowlabel = IPV6_FLOWINFO_MASK & *(u32 *)CMSG_DATA(cmsg);
+ break;
+
+ case IPV6_HOPOPTS:
+ if (opt->hopopt || cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_opt_hdr))) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+
+ hdr = (struct ipv6_opt_hdr *)CMSG_DATA(cmsg);
+ len = ((hdr->hdrlen + 1) << 3);
+ if (cmsg->cmsg_len < CMSG_LEN(len)) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+ if (!capable(CAP_NET_RAW)) {
+ err = -EPERM;
+ goto exit_f;
+ }
+ opt->opt_nflen += len;
+ opt->hopopt = hdr;
+ break;
+
+ case IPV6_DSTOPTS:
+ if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_opt_hdr))) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+
+ hdr = (struct ipv6_opt_hdr *)CMSG_DATA(cmsg);
+ len = ((hdr->hdrlen + 1) << 3);
+ if (cmsg->cmsg_len < CMSG_LEN(len)) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+ if (!capable(CAP_NET_RAW)) {
+ err = -EPERM;
+ goto exit_f;
+ }
+ if (opt->dst1opt) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+ opt->opt_flen += len;
+ opt->dst1opt = hdr;
+ break;
+
+ case IPV6_AUTHHDR:
+ if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_opt_hdr))) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+
+ hdr = (struct ipv6_opt_hdr *)CMSG_DATA(cmsg);
+ len = ((hdr->hdrlen + 2) << 2);
+ if (cmsg->cmsg_len < CMSG_LEN(len)) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+ if (len & ~7) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+ opt->opt_flen += len;
+ opt->auth = hdr;
+ break;
+
+ case IPV6_RTHDR:
+ if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_rt_hdr))) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+
+ rthdr = (struct ipv6_rt_hdr *)CMSG_DATA(cmsg);
+
+ /*
+ * TYPE 0
+ */
+ if (rthdr->type) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+
+ len = ((rthdr->hdrlen + 1) << 3);
+
+ if (cmsg->cmsg_len < CMSG_LEN(len)) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+
+ /* segments left must also match */
+ if ((rthdr->hdrlen >> 1) != rthdr->segments_left) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+
+ opt->opt_nflen += len;
+ opt->srcrt = rthdr;
+
+ if (opt->dst1opt) {
+ int dsthdrlen = ((opt->dst1opt->hdrlen+1)<<3);
+
+ opt->opt_nflen += dsthdrlen;
+ opt->dst0opt = opt->dst1opt;
+ opt->dst1opt = NULL;
+ opt->opt_flen -= dsthdrlen;
+ }
+
+ break;
+
+ case IPV6_HOPLIMIT:
+ if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+
+ *hlimit = *(int *)CMSG_DATA(cmsg);
+ break;
+
+ case IPV6_TCLASS:
+ if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {
+ err = -EINVAL;
+ goto exit_f;
+ }
+ *tclass = *(int *)CMSG_DATA(cmsg);
+ break;
+
+ default:
+ if (net_ratelimit())
+ printk(KERN_DEBUG "invalid cmsg type: %d\n", cmsg->cmsg_type);
+ err = -EINVAL;
+ break;
+ };
+ }
+
+exit_f:
+ return err;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv6/exthdrs.c b/uClinux-2.4.31-uc0/net/ipv6/exthdrs.c
new file mode 100644
index 0000000..16a554b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/exthdrs.c
@@ -0,0 +1,858 @@
+/* $USAGI: exthdrs.c,v 1.40 2003/08/08 13:46:38 yoshfuji Exp $ */
+
+/*
+ * Extension Header handling for IPv6
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ * Andi Kleen <ak@muc.de>
+ * Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
+ *
+ * $Id: exthdrs.c,v 1.13 2001/06/19 15:58:56 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/* Changes:
+ * yoshfuji : ensure not to overrun while parsing
+ * tlv options.
+ */
+
+#include <linux/autoconf.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/sched.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/in6.h>
+#include <linux/icmpv6.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/transp_v6.h>
+#include <net/rawv6.h>
+#include <net/ndisc.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+
+#include <asm/uaccess.h>
+
+
+
+/*
+ * Parsing inbound headers.
+ *
+ * Parsing function "func" returns offset wrt skb->nh of the place,
+ * where next nexthdr value is stored or NULL, if parsing
+ * failed. It should also update skb->h tp point at the next header.
+ */
+
+struct hdrtype_proc
+{
+ int type;
+ int (*func) (struct sk_buff **, int offset);
+};
+
+/*
+ * Parsing tlv encoded headers.
+ *
+ * Parsing function "func" returns 1, if parsing succeed
+ * and 0, if it failed.
+ * It MUST NOT touch skb->h.
+ */
+
+struct tlvtype_proc
+{
+ int type;
+ int (*func) (struct sk_buff *, int offset);
+};
+
+/*********************
+ Generic functions
+ *********************/
+
+/* An unknown option is detected, decide what to do */
+
+int ip6_tlvopt_unknown(struct sk_buff *skb, int optoff)
+{
+ switch ((skb->nh.raw[optoff] & 0xC0) >> 6) {
+ case 0: /* ignore */
+ return 1;
+
+ case 1: /* drop packet */
+ break;
+
+ case 3: /* Send ICMP if not a multicast address and drop packet */
+ /* Actually, it is redundant check. icmp_send
+ will recheck in any case.
+ */
+ if (ipv6_addr_is_multicast(&skb->nh.ipv6h->daddr))
+ break;
+ case 2: /* send ICMP PARM PROB regardless and drop packet */
+ icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, optoff);
+ return 0;
+ };
+
+ kfree_skb(skb);
+ return 0;
+}
+
+/* Parse tlv encoded option header (hop-by-hop or destination) */
+
+static int ip6_parse_tlv(struct tlvtype_proc *procs, struct sk_buff *skb)
+{
+ struct tlvtype_proc *curr;
+ int off = skb->h.raw - skb->nh.raw;
+ int len = ((skb->h.raw[1]+1)<<3);
+
+ if ((skb->h.raw + len) - skb->data > skb_headlen(skb))
+ goto bad;
+
+ off += 2;
+ len -= 2;
+
+ while (len > 0) {
+ int optlen;
+
+ switch (skb->nh.raw[off]) {
+ case IPV6_TLV_PAD0:
+ optlen = 1;
+ break;
+
+ case IPV6_TLV_PADN:
+ if (len < 2)
+ goto bad;
+ optlen = skb->nh.raw[off+1]+2;
+ if (len < optlen)
+ goto bad;
+ break;
+
+ default: /* Other TLV code so scan list */
+ if (len < 2)
+ goto bad;
+ optlen = skb->nh.raw[off+1]+2;
+ if (len < optlen)
+ goto bad;
+ for (curr=procs; curr->type >= 0; curr++) {
+ if (curr->type == skb->nh.raw[off]) {
+ /* type specific length/alignment
+ checks will be perfomed in the
+ func(). */
+ if (curr->func(skb, off) == 0)
+ return 0;
+ break;
+ }
+ }
+ if (curr->type < 0) {
+ if (ip6_tlvopt_unknown(skb, off) == 0)
+ return 0;
+ }
+ break;
+ }
+ off += optlen;
+ len -= optlen;
+ }
+ if (len == 0)
+ return 1;
+bad:
+ kfree_skb(skb);
+ return 0;
+}
+
+/*****************************
+ Destination options header.
+ *****************************/
+
+struct tlvtype_proc tlvprocdestopt_lst[] = {
+ {-1, NULL}
+};
+
+static int ipv6_dest_opt(struct sk_buff **skb_ptr, int nhoff)
+{
+ struct sk_buff *skb=*skb_ptr;
+ struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb;
+
+ if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8) ||
+ !pskb_may_pull(skb, (skb->h.raw-skb->data)+((skb->h.raw[1]+1)<<3))) {
+ kfree_skb(skb);
+ return -1;
+ }
+
+ opt->dst1 = skb->h.raw - skb->nh.raw;
+
+ if (ip6_parse_tlv(tlvprocdestopt_lst, skb)) {
+ skb->h.raw += ((skb->h.raw[1]+1)<<3);
+ return opt->dst1;
+ }
+
+ return -1;
+}
+
+/********************************
+ NONE header. No data in packet.
+ ********************************/
+
+static int ipv6_nodata(struct sk_buff **skb_ptr, int nhoff)
+{
+ kfree_skb(*skb_ptr);
+ return -1;
+}
+
+/********************************
+ Routing header.
+ ********************************/
+
+static int ipv6_routing_header(struct sk_buff **skb_ptr, int nhoff)
+{
+ struct sk_buff *skb = *skb_ptr;
+ struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb;
+ struct in6_addr *addr;
+ struct in6_addr daddr;
+ int n, i;
+
+ struct ipv6_rt_hdr *hdr;
+ struct rt0_hdr *rthdr;
+
+ if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8) ||
+ !pskb_may_pull(skb, (skb->h.raw-skb->data)+((skb->h.raw[1]+1)<<3))) {
+ struct inet6_dev *idev = in6_dev_get(skb->dev);
+ IP6_INC_STATS_BH(idev,Ip6InHdrErrors);
+ if (idev)
+ in6_dev_put(idev);
+ kfree_skb(skb);
+ return -1;
+ }
+
+ hdr = (struct ipv6_rt_hdr *) skb->h.raw;
+
+ if ((ipv6_addr_type(&skb->nh.ipv6h->daddr)&IPV6_ADDR_MULTICAST) ||
+ skb->pkt_type != PACKET_HOST) {
+ kfree_skb(skb);
+ return -1;
+ }
+
+looped_back:
+ if (hdr->segments_left == 0) {
+ opt->srcrt = skb->h.raw - skb->nh.raw;
+ skb->h.raw += (hdr->hdrlen + 1) << 3;
+ opt->dst0 = opt->dst1;
+ opt->dst1 = 0;
+ return (&hdr->nexthdr) - skb->nh.raw;
+ }
+
+ if (hdr->type != IPV6_SRCRT_TYPE_0) {
+ icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, (&hdr->type) - skb->nh.raw);
+ return -1;
+ }
+
+ if (hdr->hdrlen & 0x01) {
+ icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, (&hdr->hdrlen) - skb->nh.raw);
+ return -1;
+ }
+
+ /*
+ * This is the routing header forwarding algorithm from
+ * RFC 2460, page 15-16 in Section 4.4: Routing Header.
+ */
+
+ n = hdr->hdrlen >> 1;
+
+ if (hdr->segments_left > n) {
+ icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, (&hdr->segments_left) - skb->nh.raw);
+ return -1;
+ }
+
+ /* We are about to mangle packet header. Be careful!
+ Do not damage packets queued somewhere.
+ */
+ if (skb_cloned(skb)) {
+ struct sk_buff *skb2 = skb_copy(skb, GFP_ATOMIC);
+ kfree_skb(skb);
+ if (skb2 == NULL)
+ return -1;
+ *skb_ptr = skb = skb2;
+ opt = (struct inet6_skb_parm *)skb2->cb;
+ hdr = (struct ipv6_rt_hdr *) skb2->h.raw;
+ }
+
+ if (skb->ip_summed == CHECKSUM_HW)
+ skb->ip_summed = CHECKSUM_NONE;
+
+ i = n - --hdr->segments_left;
+
+ rthdr = (struct rt0_hdr *) hdr;
+ addr = rthdr->addr;
+ addr += i - 1;
+
+ if ((ipv6_addr_type(addr) & IPV6_ADDR_MULTICAST) ||
+ (ipv6_addr_type(&skb->nh.ipv6h->daddr) & IPV6_ADDR_MULTICAST)) {
+ kfree_skb(skb);
+ return -1;
+ }
+
+ ipv6_addr_copy(&daddr, addr);
+ ipv6_addr_copy(addr, &skb->nh.ipv6h->daddr);
+ ipv6_addr_copy(&skb->nh.ipv6h->daddr, &daddr);
+
+ dst_release(xchg(&skb->dst, NULL));
+ ip6_route_input(skb);
+ if (skb->dst->error) {
+ skb_push(skb, skb->data - skb->nh.raw);
+ skb->dst->input(skb);
+ return -1;
+ }
+
+ if (skb->dst->dev->flags&IFF_LOOPBACK) {
+ if (skb->nh.ipv6h->hop_limit <= 1) {
+ icmpv6_send(skb, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT,
+ 0, skb->dev);
+ kfree_skb(skb);
+ return -1;
+ }
+ skb->nh.ipv6h->hop_limit--;
+ goto looped_back;
+ }
+
+ skb_push(skb, skb->data - skb->nh.raw);
+ skb->dst->input(skb);
+ return -1;
+}
+
+/*
+ This function inverts received rthdr.
+ NOTE: specs allow to make it automatically only if
+ packet authenticated.
+
+ I will not discuss it here (though, I am really pissed off at
+ this stupid requirement making rthdr idea useless)
+
+ Actually, it creates severe problems for us.
+ Embrionic requests has no associated sockets,
+ so that user have no control over it and
+ cannot not only to set reply options, but
+ even to know, that someone wants to connect
+ without success. :-(
+
+ For now we need to test the engine, so that I created
+ temporary (or permanent) backdoor.
+ If listening socket set IPV6_RTHDR to 2, then we invert header.
+ --ANK (980729)
+
+ By the Mobile IPv6 specification Type 2 routing header MUST NOT be
+ inverted.
+ --AJT (20020917)
+ */
+
+struct ipv6_txoptions *
+ipv6_invert_rthdr(struct sock *sk, struct ipv6_rt_hdr *hdr)
+{
+ /* Received rthdr:
+
+ [ H1 -> H2 -> ... H_prev ] daddr=ME
+
+ Inverted result:
+ [ H_prev -> ... -> H1 ] daddr =sender
+
+ Note, that IP output engine will rewrire this rthdr
+ by rotating it left by one addr.
+ */
+
+ int n, i;
+ struct rt0_hdr *rthdr = (struct rt0_hdr*)hdr;
+ struct rt0_hdr *irthdr;
+ struct ipv6_txoptions *opt;
+ int hdrlen = ipv6_optlen(hdr);
+
+ if (hdr->segments_left ||
+ hdr->type != IPV6_SRCRT_TYPE_0 ||
+ hdr->hdrlen & 0x01)
+ return NULL;
+
+ n = hdr->hdrlen >> 1;
+ opt = sock_kmalloc(sk, sizeof(*opt) + hdrlen, GFP_ATOMIC);
+ if (opt == NULL)
+ return NULL;
+ memset(opt, 0, sizeof(*opt));
+ opt->tot_len = sizeof(*opt) + hdrlen;
+ opt->srcrt = (void*)(opt+1);
+ opt->opt_nflen = hdrlen;
+
+ memcpy(opt->srcrt, hdr, sizeof(*hdr));
+ irthdr = (struct rt0_hdr*)opt->srcrt;
+ /* Obsolete field, MBZ, when originated by us */
+ irthdr->reserved = 0;
+ opt->srcrt->segments_left = n;
+ for (i=0; i<n; i++)
+ memcpy(irthdr->addr+i, rthdr->addr+(n-1-i), 16);
+ return opt;
+}
+
+/********************************
+ AUTH header.
+ ********************************/
+
+/*
+ rfc1826 said, that if a host does not implement AUTH header
+ it MAY ignore it. We use this hole 8)
+
+ Actually, now we can implement OSPFv6 without kernel IPsec.
+ Authentication for poors may be done in user space with the same success.
+
+ Yes, it means, that we allow application to send/receive
+ raw authentication header. Apparently, we suppose, that it knows
+ what it does and calculates authentication data correctly.
+ Certainly, it is possible only for udp and raw sockets, but not for tcp.
+
+ AUTH header has 4byte granular length, which kills all the idea
+ behind AUTOMATIC 64bit alignment of IPv6. Now we will lose
+ cpu ticks, checking that sender did not something stupid
+ and opt->hdrlen is even. Shit! --ANK (980730)
+ */
+
+static int ipv6_auth_hdr(struct sk_buff **skb_ptr, int nhoff)
+{
+ struct sk_buff *skb=*skb_ptr;
+ struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb;
+ int len;
+
+ if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8))
+ goto fail;
+
+ /*
+ * RFC2402 2.2 Payload Length
+ * The 8-bit field specifies the length of AH in 32-bit words
+ * (4-byte units), minus "2".
+ * -- Noriaki Takamiya @USAGI Project
+ */
+ len = (skb->h.raw[1]+2)<<2;
+
+ if (len&7)
+ goto fail;
+
+ if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+len))
+ goto fail;
+
+ opt->auth = skb->h.raw - skb->nh.raw;
+ skb->h.raw += len;
+ return opt->auth;
+
+fail:
+ kfree_skb(skb);
+ return -1;
+}
+
+/* This list MUST NOT contain entry for NEXTHDR_HOP.
+ It is parsed immediately after packet received
+ and if it occurs somewhere in another place we must
+ generate error.
+ */
+
+struct hdrtype_proc hdrproc_lst[] = {
+ {NEXTHDR_FRAGMENT, ipv6_reassembly},
+ {NEXTHDR_ROUTING, ipv6_routing_header},
+ {NEXTHDR_DEST, ipv6_dest_opt},
+ {NEXTHDR_NONE, ipv6_nodata},
+ {NEXTHDR_AUTH, ipv6_auth_hdr},
+ /*
+ {NEXTHDR_ESP, ipv6_esp_hdr},
+ */
+ {-1, NULL}
+};
+
+int ipv6_parse_exthdrs(struct sk_buff **skb_in, int nhoff, u8* nexthdr)
+{
+ struct hdrtype_proc *hdrt;
+
+restart:
+ for (hdrt=hdrproc_lst; hdrt->type >= 0; hdrt++) {
+ if (hdrt->type == *nexthdr) {
+ if ((nhoff = hdrt->func(skb_in, nhoff)) >= 0) {
+ *nexthdr = (*skb_in)->nh.raw[nhoff];
+ goto restart;
+ }
+ return -1;
+ }
+ }
+ return nhoff;
+}
+
+
+/**********************************
+ Hop-by-hop options.
+ **********************************/
+
+
+/* RFC 2711 : IPv6 Router Alert Option */
+
+static int ipv6_hop_ra(struct sk_buff *skb, int optoff)
+{
+ if (skb->nh.raw[optoff+1] == 2) {
+ ((struct inet6_skb_parm*)skb->cb)->ra = optoff;
+ return 1;
+ }
+ if (net_ratelimit())
+ printk(KERN_DEBUG "ipv6_hop_ra: wrong RA length %d\n", skb->nh.raw[optoff+1]);
+ kfree_skb(skb);
+ return 0;
+}
+
+/* Jumbo payload */
+
+static int ipv6_hop_jumbo(struct sk_buff *skb, int optoff)
+{
+ u32 pkt_len;
+
+ if (skb->nh.raw[optoff+1] != 4 || (optoff&3) != 2) {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "ipv6_hop_jumbo: wrong jumbo opt length/alignment %d\n", skb->nh.raw[optoff+1]);
+ goto drop;
+ }
+
+ pkt_len = ntohl(*(u32*)(skb->nh.raw+optoff+2));
+ if (pkt_len < 0x10000) {
+ icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff+2);
+ return 0;
+ }
+ if (skb->nh.ipv6h->payload_len) {
+ icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff);
+ return 0;
+ }
+
+ if (pkt_len > skb->len - sizeof(struct ipv6hdr)) {
+ struct inet6_dev *idev = in6_dev_get(skb->dev);
+ IP6_INC_STATS_BH(idev,Ip6InTruncatedPkts);
+ if (idev)
+ in6_dev_put(idev);
+ goto drop;
+ }
+ if (pkt_len + sizeof(struct ipv6hdr) < skb->len) {
+ __pskb_trim(skb, pkt_len + sizeof(struct ipv6hdr));
+ if (skb->ip_summed == CHECKSUM_HW)
+ skb->ip_summed = CHECKSUM_NONE;
+ }
+ return 1;
+
+drop:
+ kfree_skb(skb);
+ return 0;
+}
+
+struct tlvtype_proc tlvprochopopt_lst[] = {
+ {IPV6_TLV_ROUTERALERT, ipv6_hop_ra},
+ {IPV6_TLV_JUMBO, ipv6_hop_jumbo},
+ {-1, NULL}
+};
+
+int ipv6_parse_hopopts(struct sk_buff *skb, int nhoff)
+{
+ ((struct inet6_skb_parm*)skb->cb)->hop = sizeof(struct ipv6hdr);
+ if (ip6_parse_tlv(tlvprochopopt_lst, skb))
+ return sizeof(struct ipv6hdr);
+ return -1;
+}
+
+/*
+ * Creating outbound headers.
+ *
+ * "build" functions work when skb is filled from head to tail (datagram)
+ * "push" functions work when headers are added from tail to head (tcp)
+ *
+ * In both cases we assume, that caller reserved enough room
+ * for headers.
+ */
+
+u8 *ipv6_build_rthdr(struct sk_buff *skb, u8 *prev_hdr,
+ struct ipv6_rt_hdr *opt, struct in6_addr *addr)
+{
+ struct inet6_skb_parm *parm = (struct inet6_skb_parm*)skb->cb;
+ struct rt0_hdr *phdr, *ihdr;
+ int hops;
+
+ ihdr = (struct rt0_hdr *) opt;
+
+ phdr = (struct rt0_hdr *) skb_put(skb, (ihdr->rt_hdr.hdrlen + 1) << 3);
+ memcpy(phdr, ihdr, sizeof(struct rt0_hdr));
+
+ hops = ihdr->rt_hdr.hdrlen >> 1;
+
+ if (hops > 1)
+ memcpy(phdr->addr, ihdr->addr + 1,
+ (hops - 1) * sizeof(struct in6_addr));
+
+ ipv6_addr_copy(phdr->addr + (hops - 1), addr);
+
+ phdr->rt_hdr.nexthdr = *prev_hdr;
+ *prev_hdr = NEXTHDR_ROUTING;
+ parm->srcrt = (unsigned char*)phdr - skb->nh.raw;
+ return &phdr->rt_hdr.nexthdr;
+}
+
+static u8 *ipv6_build_exthdr(struct sk_buff *skb, u8 *prev_hdr, u8 type, struct ipv6_opt_hdr *opt, __u16 *optoff)
+{
+ struct ipv6_opt_hdr *h = (struct ipv6_opt_hdr *)skb_put(skb, ipv6_optlen(opt));
+
+ memcpy(h, opt, ipv6_optlen(opt));
+ h->nexthdr = *prev_hdr;
+ *prev_hdr = type;
+ *optoff = (unsigned char*)h - skb->nh.raw;
+ return &h->nexthdr;
+}
+
+static u8 *ipv6_build_authhdr(struct sk_buff *skb, u8 *prev_hdr, struct ipv6_opt_hdr *opt)
+{
+ struct inet6_skb_parm *parm = (struct inet6_skb_parm*)skb->cb;
+ struct ipv6_opt_hdr *h = (struct ipv6_opt_hdr *)skb_put(skb, (opt->hdrlen+2)<<2);
+
+ memcpy(h, opt, (opt->hdrlen+2)<<2);
+ h->nexthdr = *prev_hdr;
+ *prev_hdr = NEXTHDR_AUTH;
+ parm->auth = (unsigned char*)h - skb->nh.raw;
+ return &h->nexthdr;
+}
+
+
+u8 *ipv6_build_nfrag_opts(struct sk_buff *skb, u8 *prev_hdr, struct ipv6_txoptions *opt,
+ struct in6_addr *daddr, u32 jumbolen)
+{
+ struct inet6_skb_parm *parm = (struct inet6_skb_parm*)skb->cb;
+ struct ipv6_opt_hdr *h = (struct ipv6_opt_hdr *)skb->data;
+
+ if (opt && opt->hopopt)
+ prev_hdr = ipv6_build_exthdr(skb, prev_hdr, NEXTHDR_HOP, opt->hopopt, &parm->hop);
+
+ if (jumbolen) {
+ u8 *jumboopt = (u8 *)skb_put(skb, 8);
+
+ if (opt && opt->hopopt) {
+ *jumboopt++ = IPV6_TLV_PADN;
+ *jumboopt++ = 0;
+ h->hdrlen++;
+ } else {
+ h = (struct ipv6_opt_hdr *)jumboopt;
+ h->nexthdr = *prev_hdr;
+ h->hdrlen = 0;
+ jumboopt += 2;
+ *prev_hdr = NEXTHDR_HOP;
+ prev_hdr = &h->nexthdr;
+ }
+ jumboopt[0] = IPV6_TLV_JUMBO;
+ jumboopt[1] = 4;
+ *(u32*)(jumboopt+2) = htonl(jumbolen);
+ }
+ if (opt) {
+ if (opt->dst0opt)
+ prev_hdr = ipv6_build_exthdr(skb, prev_hdr, NEXTHDR_DEST, opt->dst0opt, &parm->dst0);
+ if (opt->srcrt)
+ prev_hdr = ipv6_build_rthdr(skb, prev_hdr, opt->srcrt, daddr);
+ }
+ return prev_hdr;
+}
+
+u8 *ipv6_build_frag_opts(struct sk_buff *skb, u8 *prev_hdr, struct ipv6_txoptions *opt)
+{
+ struct inet6_skb_parm *parm = (struct inet6_skb_parm*)skb->cb;
+
+ if (opt->auth)
+ prev_hdr = ipv6_build_authhdr(skb, prev_hdr, opt->auth);
+ if (opt->dst1opt)
+ prev_hdr = ipv6_build_exthdr(skb, prev_hdr, NEXTHDR_DEST, opt->dst1opt, &parm->dst1);
+ return prev_hdr;
+}
+
+static void ipv6_push_rthdr(struct sk_buff *skb, u8 *proto,
+ struct ipv6_rt_hdr **opt,
+ struct in6_addr **addr_p)
+{
+ struct rt0_hdr *phdr, *ihdr;
+ int hops;
+
+ ihdr = (struct rt0_hdr *) *opt;
+
+ phdr = (struct rt0_hdr *) skb_push(skb, (ihdr->rt_hdr.hdrlen + 1) << 3);
+ memcpy(phdr, ihdr, sizeof(struct rt0_hdr));
+
+ hops = ihdr->rt_hdr.hdrlen >> 1;
+
+ if (hops > 1)
+ memcpy(phdr->addr, ihdr->addr + 1,
+ (hops - 1) * sizeof(struct in6_addr));
+
+ ipv6_addr_copy(phdr->addr + (hops - 1), *addr_p);
+ *addr_p = ihdr->addr;
+
+ phdr->rt_hdr.nexthdr = *proto;
+ *proto = NEXTHDR_ROUTING;
+}
+
+static void ipv6_push_exthdr(struct sk_buff *skb, u8 *proto, u8 type, struct ipv6_opt_hdr **opt)
+{
+ struct ipv6_opt_hdr *h = (struct ipv6_opt_hdr *)skb_push(skb, ipv6_optlen(*opt));
+
+ memcpy(h, *opt, ipv6_optlen(*opt));
+ h->nexthdr = *proto;
+ *proto = type;
+}
+
+static void ipv6_push_authhdr(struct sk_buff *skb, u8 *proto, struct ipv6_opt_hdr **opt)
+{
+ struct ipv6_opt_hdr *h = (struct ipv6_opt_hdr *)skb_push(skb, ((*opt)->hdrlen+2)<<2);
+
+ memcpy(h, *opt, ((*opt)->hdrlen+2)<<2);
+ h->nexthdr = *proto;
+ *proto = NEXTHDR_AUTH;
+}
+
+void ipv6_push_nfrag_opts(struct sk_buff *skb, struct ipv6_txoptions *opt,
+ u8 *proto,
+ struct in6_addr **daddr)
+{
+ if (opt->srcrt)
+ ipv6_push_rthdr(skb, proto, &opt->srcrt, daddr);
+ if (opt->dst0opt)
+ ipv6_push_exthdr(skb, proto, NEXTHDR_DEST, &opt->dst0opt);
+ if (opt->hopopt)
+ ipv6_push_exthdr(skb, proto, NEXTHDR_HOP, &opt->hopopt);
+}
+
+void ipv6_push_frag_opts(struct sk_buff *skb, struct ipv6_txoptions *opt, u8 *proto)
+{
+ if (opt->dst1opt)
+ ipv6_push_exthdr(skb, proto, NEXTHDR_DEST, &opt->dst1opt);
+ if (opt->auth)
+ ipv6_push_authhdr(skb, proto, &opt->auth);
+}
+
+struct ipv6_txoptions *
+ipv6_dup_options(struct sock *sk, struct ipv6_txoptions *opt)
+{
+ struct ipv6_txoptions *opt2;
+
+ opt2 = sock_kmalloc(sk, opt->tot_len, GFP_ATOMIC);
+ if (opt2) {
+ long dif = (char*)opt2 - (char*)opt;
+ memcpy(opt2, opt, opt->tot_len);
+ if (opt2->hopopt)
+ *((char**)&opt2->hopopt) += dif;
+ if (opt2->dst0opt)
+ *((char**)&opt2->dst0opt) += dif;
+ if (opt2->dst1opt)
+ *((char**)&opt2->dst1opt) += dif;
+ if (opt2->auth)
+ *((char**)&opt2->auth) += dif;
+ if (opt2->srcrt)
+ *((char**)&opt2->srcrt) += dif;
+ }
+ return opt2;
+}
+
+
+/*
+ * find out if nexthdr is a well-known extension header or a protocol
+ */
+
+int ipv6_ext_hdr(u8 nexthdr)
+{
+ /*
+ * find out if nexthdr is an extension header or a protocol
+ */
+ return ( (nexthdr == NEXTHDR_HOP) ||
+ (nexthdr == NEXTHDR_ROUTING) ||
+ (nexthdr == NEXTHDR_FRAGMENT) ||
+ (nexthdr == NEXTHDR_AUTH) ||
+ (nexthdr == NEXTHDR_ESP) ||
+ (nexthdr == NEXTHDR_NONE) ||
+ (nexthdr == NEXTHDR_DEST) );
+}
+
+/*
+ * Skip any extension headers. This is used by the ICMP module.
+ *
+ * Note that strictly speaking this conflicts with RFC1883 4.0:
+ * ...The contents and semantics of each extension header determine whether
+ * or not to proceed to the next header. Therefore, extension headers must
+ * be processed strictly in the order they appear in the packet; a
+ * receiver must not, for example, scan through a packet looking for a
+ * particular kind of extension header and process that header prior to
+ * processing all preceding ones.
+ *
+ * We do exactly this. This is a protocol bug. We can't decide after a
+ * seeing an unknown discard-with-error flavour TLV option if it's a
+ * ICMP error message or not (errors should never be send in reply to
+ * ICMP error messages).
+ *
+ * But I see no other way to do this. This might need to be reexamined
+ * when Linux implements ESP (and maybe AUTH) headers.
+ * --AK
+ *
+ * This function parses (probably truncated) exthdr set "hdr"
+ * of length "len". "nexthdrp" initially points to some place,
+ * where type of the first header can be found.
+ *
+ * It skips all well-known exthdrs, and returns pointer to the start
+ * of unparsable area i.e. the first header with unknown type.
+ * If it is not NULL *nexthdr is updated by type/protocol of this header.
+ *
+ * NOTES: - if packet terminated with NEXTHDR_NONE it returns NULL.
+ * - it may return pointer pointing beyond end of packet,
+ * if the last recognized header is truncated in the middle.
+ * - if packet is truncated, so that all parsed headers are skipped,
+ * it returns NULL.
+ * - First fragment header is skipped, not-first ones
+ * are considered as unparsable.
+ * - ESP is unparsable for now and considered like
+ * normal payload protocol.
+ * - Note also special handling of AUTH header. Thanks to IPsec wizards.
+ *
+ * --ANK (980726)
+ */
+
+int ipv6_skip_exthdr(const struct sk_buff *skb, int start, u8 *nexthdrp, int len)
+{
+ u8 nexthdr = *nexthdrp;
+
+ while (ipv6_ext_hdr(nexthdr)) {
+ struct ipv6_opt_hdr hdr;
+ int hdrlen;
+
+ if (len < (int)sizeof(struct ipv6_opt_hdr))
+ return -1;
+ if (nexthdr == NEXTHDR_NONE)
+ return -1;
+ if (skb_copy_bits(skb, start, &hdr, sizeof(hdr)))
+ BUG();
+ if (nexthdr == NEXTHDR_FRAGMENT) {
+ unsigned short frag_off;
+ if (skb_copy_bits(skb,
+ start+offsetof(struct frag_hdr,
+ frag_off),
+ &frag_off,
+ sizeof(frag_off))) {
+ return -1;
+ }
+
+ if (ntohs(frag_off) & ~0x7)
+ break;
+ hdrlen = 8;
+ } else if (nexthdr == NEXTHDR_AUTH)
+ hdrlen = (hdr.hdrlen+2)<<2;
+ else
+ hdrlen = ipv6_optlen(&hdr);
+
+ nexthdr = hdr.nexthdr;
+ len -= hdrlen;
+ start += hdrlen;
+ }
+
+ *nexthdrp = nexthdr;
+ return start;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/ipv6/icmp.c b/uClinux-2.4.31-uc0/net/ipv6/icmp.c
new file mode 100644
index 0000000..b975730
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/icmp.c
@@ -0,0 +1,1759 @@
+/* $USAGI: icmp.c,v 1.169 2003/11/24 11:03:18 yoshfuji Exp $ */
+
+/*
+ * Internet Control Message Protocol (ICMPv6)
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ *
+ * $Id: icmp.c,v 1.37 2001/09/18 22:29:10 davem Exp $
+ *
+ * Based on net/ipv4/icmp.c
+ *
+ * RFC 1885
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/*
+ * Changes:
+ *
+ * Andi Kleen : exception handling
+ * Andi Kleen add rate limits. never reply to a icmp.
+ * add more length checks and other fixes.
+ * yoshfuji : ensure to sent parameter problem for
+ * fragments.
+ * YOSHIFUJI Hideaki @USAGI: added sysctl for icmp rate limit.
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+
+#ifdef CONFIG_SYSCTL
+#include <linux/sysctl.h>
+#endif
+
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/icmpv6.h>
+
+#ifdef CONFIG_IPV6_NODEINFO
+#include <linux/utsname.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/crypto.h>
+#include <asm/scatterlist.h>
+#endif
+
+#include <net/ip.h>
+#include <net/sock.h>
+
+#include <net/ipv6.h>
+#include <net/checksum.h>
+#include <net/protocol.h>
+#include <net/raw.h>
+#include <net/rawv6.h>
+#include <net/transp_v6.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+#include <net/icmp.h>
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+
+struct icmpv6_mib icmpv6_statistics[NR_CPUS*2];
+
+/*
+ * ICMP socket(s) for flow control.
+ */
+
+static struct socket *__icmpv6_socket[NR_CPUS];
+#define icmpv6_socket __icmpv6_socket[smp_processor_id()]
+#define icmpv6_socket_cpu(X) __icmpv6_socket[(X)]
+
+int icmpv6_rcv(struct sk_buff *skb);
+
+static struct inet6_protocol icmpv6_protocol =
+{
+ icmpv6_rcv, /* handler */
+ NULL, /* error control */
+ NULL, /* next */
+ IPPROTO_ICMPV6, /* protocol ID */
+ 0, /* copy */
+ NULL, /* data */
+ "ICMPv6" /* name */
+};
+
+struct icmpv6_msg {
+ struct icmp6hdr icmph;
+ struct sk_buff *skb;
+ int offset;
+ struct in6_addr *daddr;
+ int len;
+ __u32 csum;
+};
+
+
+static int icmpv6_xmit_lock(void)
+{
+ local_bh_disable();
+ if (unlikely(!spin_trylock(&icmpv6_socket->sk->lock.slock))) {
+ /* This can happen if the output path (f.e. SIT or
+ * ip6ip6 tunnel) signals dst_link_failure() for an
+ * outgoing ICMP6 packet.
+ */
+ local_bh_enable();
+ return 1;
+ }
+ return 0;
+}
+
+static void icmpv6_xmit_unlock(void)
+{
+ spin_unlock_bh(&icmpv6_socket->sk->lock.slock);
+}
+
+/*
+ * getfrag callback
+ */
+
+static int icmpv6_getfrag(const void *data, struct in6_addr *saddr,
+ char *buff, unsigned int offset, unsigned int len)
+{
+ struct icmpv6_msg *msg = (struct icmpv6_msg *) data;
+ struct icmp6hdr *icmph;
+ __u32 csum;
+
+ if (offset) {
+ csum = skb_copy_and_csum_bits(msg->skb, msg->offset +
+ (offset - sizeof(struct icmp6hdr)),
+ buff, len, msg->csum);
+ msg->csum = csum;
+ return 0;
+ }
+
+ csum = csum_partial_copy_nocheck((void *) &msg->icmph, buff,
+ sizeof(struct icmp6hdr), msg->csum);
+
+ csum = skb_copy_and_csum_bits(msg->skb, msg->offset,
+ buff + sizeof(struct icmp6hdr),
+ len - sizeof(struct icmp6hdr), csum);
+
+ icmph = (struct icmp6hdr *) buff;
+
+ icmph->icmp6_cksum = csum_ipv6_magic(saddr, msg->daddr, msg->len,
+ IPPROTO_ICMPV6, csum);
+ return 0;
+}
+
+
+/*
+ * Slightly more convenient version of icmpv6_send.
+ */
+void icmpv6_param_prob(struct sk_buff *skb, int code, int pos)
+{
+ icmpv6_send(skb, ICMPV6_PARAMPROB, code, pos, skb->dev);
+ kfree_skb(skb);
+}
+
+/*
+ * Figure out, may we reply to this packet with icmp error.
+ *
+ * We do not reply, if:
+ * - it was icmp error message.
+ * - it is truncated, so that it is known, that protocol is ICMPV6
+ * (i.e. in the middle of some exthdr)
+ *
+ * --ANK (980726)
+ *
+ * Note: We MUST send Parameter Problem even if it is not for the first
+ * fragment. See RFC2460.
+ *
+ * --yoshfuji (2001/01/05)
+ */
+
+static int is_ineligible(struct sk_buff *skb)
+{
+ int ptr = (u8*)(skb->nh.ipv6h+1) - skb->data;
+ int len = skb->len - ptr;
+ __u8 nexthdr = skb->nh.ipv6h->nexthdr;
+
+ if (len < 0)
+ return 1;
+
+ ptr = ipv6_skip_exthdr(skb, ptr, &nexthdr, len);
+ if (ptr < 0)
+ return 0;
+ if (nexthdr == IPPROTO_ICMPV6) {
+ u8 type;
+ if (skb_copy_bits(skb, ptr+offsetof(struct icmp6hdr, icmp6_type),
+ &type, 1)
+ || !(type & ICMPV6_INFOMSG_MASK))
+ return 1;
+ }
+ return 0;
+}
+
+int sysctl_icmpv6_time = 1*HZ;
+
+/*
+ * Check the ICMP output rate limit
+ */
+static inline int icmpv6_xrlim_allow(struct sock *sk, int type, int code,
+ struct flowi *fl)
+{
+ struct dst_entry *dst;
+ int res = 0;
+
+ /* Informational messages are not limited. */
+ if (type & ICMPV6_INFOMSG_MASK) {
+ /* but 'REFUSED' or 'UNKNOWN' NI Reply should be limited */
+ if (type != ICMPV6_NI_REPLY)
+ return 1;
+ if (!(code == ICMPV6_NI_REFUSED || code == ICMPV6_NI_UNKNOWN))
+ return 1;
+ }
+
+ /* Do not limit pmtu discovery, it would break it. */
+ if (type == ICMPV6_PKT_TOOBIG)
+ return 1;
+
+ /*
+ * Look up the output route.
+ * XXX: perhaps the expire for routing entries cloned by
+ * this lookup should be more aggressive (not longer than timeout).
+ */
+ dst = ip6_route_output(sk, fl);
+ if (dst->error) {
+ IP6_INC_STATS(((struct inet6_dev *)NULL),Ip6OutNoRoutes); /* XXX(?) */
+ } else if (dst->dev && (dst->dev->flags&IFF_LOOPBACK)) {
+ res = 1;
+ } else {
+ struct rt6_info *rt = (struct rt6_info *)dst;
+ int tmo = sysctl_icmpv6_time;
+
+ /* Give more bandwidth to wider prefixes. */
+ if (rt->rt6i_dst.plen < 128)
+ tmo >>= ((128 - rt->rt6i_dst.plen)>>5);
+
+ res = xrlim_allow(dst, tmo);
+ }
+ dst_release(dst);
+ return res;
+}
+
+/*
+ * an inline helper for the "simple" if statement below
+ * checks if parameter problem report is caused by an
+ * unrecognized IPv6 option that has the Option Type
+ * highest-order two bits set to 10
+ */
+
+static __inline__ int opt_unrec(struct sk_buff *skb, __u32 offset)
+{
+ u8 optval;
+
+ offset += skb->nh.raw - skb->data;
+ if (skb_copy_bits(skb, offset, &optval, 1))
+ return 1;
+ return (optval&0xC0) == 0x80;
+}
+
+/*
+ * Send an ICMP message in response to a packet in error
+ */
+
+void icmpv6_send(struct sk_buff *skb, int type, int code, __u32 info,
+ struct net_device *dev)
+{
+ struct ipv6hdr *hdr = skb->nh.ipv6h;
+ struct sock *sk = icmpv6_socket->sk;
+ struct inet6_dev *idev;
+ struct in6_addr *saddr = NULL;
+ int iif = 0;
+ struct icmpv6_msg msg;
+ struct flowi fl;
+ int addr_type = 0;
+ int len;
+
+ /*
+ * sanity check pointer in case of parameter problem
+ */
+
+ if ((u8*)hdr < skb->head || (u8*)(hdr+1) > skb->tail)
+ return;
+
+ /*
+ * Make sure we respect the rules
+ * i.e. RFC 1885 2.4(e)
+ * Rule (e.1) is enforced by not using icmpv6_send
+ * in any code that processes icmp errors.
+ */
+ addr_type = ipv6_addr_type(&hdr->daddr);
+
+ if (ipv6_chk_addr(&hdr->daddr, skb->dev))
+ saddr = &hdr->daddr;
+
+ /*
+ * Dest addr check
+ */
+
+ if ((addr_type & IPV6_ADDR_MULTICAST || skb->pkt_type != PACKET_HOST)) {
+ if (type != ICMPV6_PKT_TOOBIG &&
+ !(type == ICMPV6_PARAMPROB &&
+ code == ICMPV6_UNK_OPTION &&
+ (opt_unrec(skb, info))))
+ return;
+
+ saddr = NULL;
+ }
+
+ addr_type = ipv6_addr_type(&hdr->saddr);
+
+ /*
+ * Source addr check
+ */
+
+ if (addr_type & IPV6_ADDR_LINKLOCAL)
+ iif = skb->dev->ifindex;
+
+ /*
+ * Must not send if we know that source is Anycast also.
+ * for now we don't know that.
+ */
+ if ((addr_type == IPV6_ADDR_ANY) ||
+ (addr_type & (IPV6_ADDR_MULTICAST|IPV6_ADDR_ANYCAST)) ||
+ (saddr && ipv6_chk_acast_addr(0, saddr))) {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "icmpv6_send: unspec/anycast/mcast source\n");
+ return;
+ }
+
+ /*
+ * Never answer to a ICMP packet.
+ */
+ if (is_ineligible(skb)) {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "icmpv6_send: no reply to icmp error\n");
+ return;
+ }
+
+ fl.proto = IPPROTO_ICMPV6;
+ fl.nl_u.ip6_u.daddr = &hdr->saddr;
+ fl.nl_u.ip6_u.saddr = saddr;
+ fl.oif = iif;
+ fl.fl6_flowlabel = 0;
+ fl.uli_u.icmpt.type = type;
+ fl.uli_u.icmpt.code = code;
+
+ if (icmpv6_xmit_lock())
+ return;
+
+ if (!icmpv6_xrlim_allow(sk, type, code, &fl))
+ goto out;
+
+ /*
+ * ok. kick it. checksum will be provided by the
+ * getfrag_t callback.
+ */
+
+ msg.icmph.icmp6_type = type;
+ msg.icmph.icmp6_code = code;
+ msg.icmph.icmp6_cksum = 0;
+ msg.icmph.icmp6_pointer = htonl(info);
+
+ msg.skb = skb;
+ msg.offset = skb->nh.raw - skb->data;
+ msg.csum = 0;
+ msg.daddr = &hdr->saddr;
+
+ len = skb->len - msg.offset + sizeof(struct icmp6hdr);
+ len = min_t(unsigned int, len, IPV6_MIN_MTU - sizeof(struct ipv6hdr));
+
+ if (len < 0) {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "icmp: len problem\n");
+ goto out;
+ }
+
+ msg.len = len;
+
+ ip6_build_xmit(sk, icmpv6_getfrag, &msg, &fl, len, NULL, -1, -1,
+ MSG_DONTWAIT);
+ idev = in6_dev_get(dev);
+ if (type >= ICMPV6_DEST_UNREACH && type <= ICMPV6_PARAMPROB) {
+ (&(icmpv6_statistics[smp_processor_id()*2].Icmp6OutDestUnreachs))[type-ICMPV6_DEST_UNREACH]++;
+ (&(idev->stats.icmpv6[smp_processor_id()*2].Icmp6OutDestUnreachs))[type-ICMPV6_DEST_UNREACH]++;
+ }
+ if (type == ICMPV6_DEST_UNREACH && code == ICMPV6_ADM_PROHIBITED)
+ ICMP6_INC_STATS_BH(idev,Icmp6OutAdminProhibs);
+ ICMP6_INC_STATS_BH(idev,Icmp6OutMsgs);
+ if (idev)
+ in6_dev_put(idev);
+out:
+ icmpv6_xmit_unlock();
+}
+
+static void icmpv6_echo_reply(struct sk_buff *skb)
+{
+ struct sock *sk = icmpv6_socket->sk;
+ struct inet6_dev *idev;
+ struct icmp6hdr *icmph = (struct icmp6hdr *) skb->h.raw;
+ struct in6_addr *saddr;
+ struct icmpv6_msg msg;
+ struct flowi fl;
+
+ saddr = &skb->nh.ipv6h->daddr;
+
+ if ((ipv6_addr_type(saddr) & (IPV6_ADDR_MULTICAST|IPV6_ADDR_ANYCAST)) ||
+ ipv6_chk_acast_addr(0, saddr))
+ saddr = NULL;
+
+ msg.icmph.icmp6_type = ICMPV6_ECHO_REPLY;
+ msg.icmph.icmp6_code = 0;
+ msg.icmph.icmp6_cksum = 0;
+ msg.icmph.icmp6_identifier = icmph->icmp6_identifier;
+ msg.icmph.icmp6_sequence = icmph->icmp6_sequence;
+
+ msg.skb = skb;
+ msg.offset = 0;
+ msg.csum = 0;
+ msg.len = skb->len + sizeof(struct icmp6hdr);
+ msg.daddr = &skb->nh.ipv6h->saddr;
+
+ fl.proto = IPPROTO_ICMPV6;
+ fl.nl_u.ip6_u.daddr = msg.daddr;
+ fl.nl_u.ip6_u.saddr = saddr;
+ fl.oif = skb->dev->ifindex;
+ fl.fl6_flowlabel = 0;
+ fl.uli_u.icmpt.type = ICMPV6_ECHO_REPLY;
+ fl.uli_u.icmpt.code = 0;
+
+ if (icmpv6_xmit_lock())
+ return;
+
+ ip6_build_xmit(sk, icmpv6_getfrag, &msg, &fl, msg.len, NULL, -1, -1,
+ MSG_DONTWAIT);
+ idev = in6_dev_get(skb->dev);
+ ICMP6_INC_STATS_BH(idev,Icmp6OutEchoReplies);
+ ICMP6_INC_STATS_BH(idev,Icmp6OutMsgs);
+ if (idev)
+ in6_dev_put(idev);
+
+ icmpv6_xmit_unlock();
+}
+
+#ifdef CONFIG_IPV6_NODEINFO
+/*
+ * IPv6 Node Information Queries
+ * <draft-ietf-ipngwg-icmp-name-lookups-07.txt>
+ */
+/*
+ * Authors:
+ * Koichi KUNITAKE
+ * Hideaki YOSHIFUJI
+ */
+
+#define __UTS_NODENAME_NONE "(none)"
+#define NI_NONCE_LEN 8
+#define NI_TTL_LEN 4 /* sizeof(__u32) */
+#define NI_MAX_SYSNAME_LEN (sizeof(system_utsname.domainname) + \
+ sizeof(system_utsname.nodename) + 1)
+#define NI_MAX_REPLY_LEN (IPV6_MIN_MTU - sizeof(struct ipv6hdr))
+
+static struct crypto_tfm *md5_tfm;
+
+static int icmpv6_ni_getname(struct sk_buff *skb,
+ __u8 *subj, int subjlen,
+ struct net_device **devp,
+ struct in6_addr *daddr, __u16 *flagsp);
+static int icmpv6_ni_getaddrs6(struct sk_buff *skb,
+ __u8 *subj, int subjlen,
+ struct net_device **devp,
+ struct in6_addr *daddr, __u16 *flagsp);
+static int icmpv6_ni_getaddrs4(struct sk_buff *skb,
+ __u8 *subj, int subjlen,
+ struct net_device **devp,
+ struct in6_addr *daddr, __u16 *flagsp);
+
+struct icmpv6_ni_table {
+ const char *name;
+ int (*getinfo)(struct sk_buff *skb,
+ __u8 *subj, int subjlen,
+ struct net_device **devp,
+ struct in6_addr *daddr,
+ __u16 *flagsp);
+};
+
+struct icmpv6_ni_table icmpv6_ni_code_table[] = {
+ { "IPV6", icmpv6_ni_getaddrs6, },
+ { "FQDN", icmpv6_ni_getname, },
+ { "IPV4", icmpv6_ni_getaddrs4, },
+};
+
+struct icmpv6_ni_table icmpv6_ni_qtype_table[] = {
+ { "NOOP", NULL, },
+ { "SUPTYPES", NULL, },
+ { "FQDN", icmpv6_ni_getname, },
+ { "NODEADDR", icmpv6_ni_getaddrs6, },
+ { "IPV4ADDR", icmpv6_ni_getaddrs4, },
+};
+
+struct in6_addr icmpv6_ni_in6;
+DECLARE_RWSEM(icmpv6_ni_in6_sem);
+
+static __inline__ const char *icmpv6_ni_qtypename(__u16 qtype)
+{
+ return ((qtype < sizeof(icmpv6_ni_qtype_table)/sizeof(icmpv6_ni_qtype_table[0])) ?
+ icmpv6_ni_qtype_table[qtype].name : "unknown");
+}
+
+static size_t str2lower(char *dst, const char *src, size_t len)
+{
+ const char *p;
+ char *q;
+ size_t i;
+ for (p = src, q = dst, i = 0; *p && i + 1 < len; p++, i++)
+ *q++ = (isascii(*p) && isupper(*p)) ? tolower(*p) : *p;
+ *q = '\0';
+ return i;
+}
+
+static struct in6_addr icmpv6_ni_group(const char *name)
+{
+ char *p;
+ __u8 digest[16];
+ char hbuf[NI_MAX_SYSNAME_LEN];
+ struct in6_addr in6;
+ int len;
+ struct scatterlist sg[1];
+
+ if (!md5_tfm)
+ goto err;
+
+ (void)str2lower(&hbuf[1], name, sizeof(hbuf)-1);
+ p = strchr(&hbuf[1], '.');
+ len = p ? (p - &hbuf[1]) : strlen(&hbuf[1]);
+ if (len >= 0x40)
+ goto err;
+ hbuf[0] = (char)len;
+
+ sg[0].page = virt_to_page(hbuf);
+ sg[0].offset = ((long) hbuf & ~PAGE_MASK);
+ sg[0].length = 1 + len;
+
+ crypto_digest_init(md5_tfm);
+ crypto_digest_update(md5_tfm, sg, 1);
+ crypto_digest_final(md5_tfm, digest);
+
+ ipv6_addr_set (&in6,
+ htonl(0xff020000), htonl(0x00000000),
+ htonl(0x00000002), *(__u32 *)digest);
+ return in6;
+err:
+ memset(&in6, 0, sizeof(in6));
+ return in6;
+}
+
+void icmpv6_ni_join_nigroup_dev(struct net_device *dev)
+{
+ struct sock *sock = icmpv6_socket->sk;
+ if (!(dev->flags&IFF_UP))
+ return;
+ down_read(&icmpv6_ni_in6_sem);
+ if (icmpv6_ni_in6.s6_addr32[0] == htonl(0x00000000)) {
+ up_read(&icmpv6_ni_in6_sem);
+ return;
+ }
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ printk(KERN_DEBUG
+ "ni_join_nigroup_dev(): %s (index=%d)\n",
+ dev->name ? dev->name : "(null)",
+ dev->ifindex);
+#endif
+ ipv6_sock_mc_join(sock, dev->ifindex, &icmpv6_ni_in6);
+ up_read(&icmpv6_ni_in6_sem);
+}
+
+static void icmpv6_ni_joinleave_group(struct in6_addr *nigroup, int join)
+{
+ struct sock *sock = icmpv6_socket->sk;
+ struct net_device *dev;
+ if (nigroup->s6_addr32[0] == htonl(0x00000000))
+ return;
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev; dev = dev->next) {
+ if (!(dev->flags&IFF_UP))
+ continue;
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ printk(KERN_DEBUG
+ "ni_joinleave_group(): %s, %s (index=%d)\n",
+ join ? "join" : "leave",
+ dev->name ? dev->name : "(null)",
+ dev->ifindex);
+#endif
+ if (join) {
+ ipv6_sock_mc_join(sock, dev->ifindex, nigroup);
+ } else {
+ ipv6_sock_mc_drop(sock, dev->ifindex, nigroup);
+ }
+ }
+ read_unlock(&dev_base_lock);
+}
+
+static void __icmpv6_sethostname_hook(struct new_utsname *sysname)
+{
+ struct in6_addr new;
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ char addrbuf[128];
+#endif
+ down_write(&icmpv6_ni_in6_sem);
+ if (strcmp(sysname->nodename, __UTS_NODENAME_NONE) == 0) {
+ memset(&new, 0, sizeof(new));
+ } else {
+ new = icmpv6_ni_group(sysname->nodename);
+ }
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ in6_ntop(&icmpv6_ni_in6, addrbuf);
+ printk(KERN_DEBUG
+ "icmpv6_sethostname_hook: old = %s\n",
+ addrbuf);
+ in6_ntop(&new, addrbuf);
+ printk(KERN_DEBUG
+ "icmpv6_sethostname_hook: new = %s\n",
+ addrbuf);
+#endif
+ if (ipv6_addr_cmp(&icmpv6_ni_in6, &new)) {
+ if (new.s6_addr32[0] != htonl(0x00000000)) {
+ icmpv6_ni_joinleave_group(&new, 1);
+ }
+ if (icmpv6_ni_in6.s6_addr32[0] != htonl(0x00000000)) {
+ icmpv6_ni_joinleave_group(&icmpv6_ni_in6, 0);
+ }
+ icmpv6_ni_in6 = new;
+ }
+ up_write(&icmpv6_ni_in6_sem);
+}
+
+static int icmpv6_ni_dnscmp(__u8 *p, int plen, __u8 *q, int qlen, int qtrunc)
+{
+ /* be sure that p is subject, q is encoded system name */
+ __u8 *p0 = p, *q0 = q;
+ int done = 0, retcode = 0;
+ if (plen < 1 || qlen < 1)
+ return -1; /* invalid length */
+ /* simple case */
+ if (plen == qlen && memcmp(p0, q0, plen) == 0)
+ return 0;
+ if (*(p0 + plen - 1) || *(q0 + qlen - 1))
+ return -1; /* invalid termination */
+ while(p < p0 + plen && q < q0 + qlen) {
+ if (*p >= 0x40 || *q >= 0x40)
+ return -1; /* DNS compression cannot be used in subject */
+ if (p + *p + 1 > p0 + plen || q + *q + 1 > q0 + qlen)
+ return -1; /* overrun */
+ if (*p == '\0') {
+ if (p == p0 + plen - 1)
+ break; /* FQDN */
+ else if (p + 1 == p0 + plen - 1)
+ return retcode; /* truncated */
+ else
+ return -1; /* more than one subject ?! */
+ }
+ if (!done) {
+ if (*q == '\0') {
+ if (q == q0 + qlen - 1) {
+ done = 1; /* FQDN */
+ } else if (q + 1 == q0 + qlen - 1) {
+ retcode = qtrunc;
+ done = 1;
+ } else
+ return -1; /* more than one subject ?! */
+ } else {
+ if (*p != *q) {
+ retcode = 1;
+ done = 1;
+ } else {
+ if (memcmp(p+1, q+1, *p)) {
+ retcode = 1;
+ done = 1;
+ }
+ }
+ }
+ }
+ p += *p + 1;
+ q += done ? 0 : (*q + 1);
+ }
+ return retcode;
+}
+
+static int icmpv6_ni_getname(struct sk_buff *skb,
+ __u8 *subj, int subjlen,
+ struct net_device **devp,
+ struct in6_addr *daddr, __u16 *flagsp)
+{
+ __u8 tmpbuf[NI_MAX_SYSNAME_LEN];
+ __u8 *dst;
+ int sysname_known = 0, sysname_len = 0;
+ int buflen;
+ int ret;
+ if (subj && subjlen < 2)
+ return -1; /* too short subject */
+ if (skb) {
+ buflen = skb_tailroom(skb) - NI_TTL_LEN;
+ if (buflen < 0) {
+ printk(KERN_WARNING
+ "icmpv6_ni_getname(): too short skb %p\n", skb);
+ return -1;
+ }
+ memset(skb_put(skb, NI_TTL_LEN), 0, NI_TTL_LEN);
+ dst = skb->tail; /* XXX */
+ } else {
+ dst = tmpbuf;
+ buflen = sizeof(tmpbuf);
+ }
+ down_read(&uts_sem);
+ if (strcmp(system_utsname.nodename, __UTS_NODENAME_NONE)) {
+ char *cp = (char *)dst;
+ int dcnt = 0;
+#ifdef CONFIG_IPV6_NODEINFO_USE_UTS_DOMAIN
+ size_t nodelen = str2lower(cp + 1, system_utsname.nodename, sizeof(system_utsname.nodename));
+ if (strcmp(system_utsname.domainname, __UTS_NODENAME_NONE)) {
+ *(cp + 1 + nodelen) = '.';
+ (void)str2lower(cp + 1 + nodelen + 1, system_utsname.domainname, sizeof(system_utsname.domainname));
+ }
+#else
+ (void)str2lower(cp + 1, system_utsname.nodename, sizeof(system_utsname.nodename));
+#endif
+ up_read(&uts_sem);
+ sysname_known = 1;
+ while (cp) {
+ char *p;
+ size_t l;
+ p = strchr(cp + 1, '.');
+ l = p ? ((size_t)(p - (cp + 1))) : strlen(cp + 1);
+ /* RFC1035 limits size of single component */
+ if (l == 0 || l >= 0x40) {
+ if (net_ratelimit()) {
+ printk(KERN_WARNING
+ "ICMPv6 NI: invalid length of single component of your hostname (%d); %s.\n",
+ l, l ? "too large" : "zero");
+ }
+ sysname_known = -1; /* truncated by an error */
+ break;
+ }
+ /* RFC1035 limits size of names */
+ /* XXX: currently cannot be happeded because nodename is 64 bytes at max. */
+ if (sysname_len + l + 1 > 255 || sysname_len + l + 1 > buflen) {
+ if (net_ratelimit()) {
+ printk(KERN_WARNING
+ "ICMPv6 NI: length of your hostname (%d) is too large\n",
+ 1);
+ }
+ sysname_known = -1; /* truncated by an error */
+ break;
+ }
+ *cp = (char)l;
+ sysname_len += l + 1;
+ if (p) {
+ dcnt++;
+ }
+ cp = p;
+ }
+ if (sysname_len > 0) {
+ dst[sysname_len++] = '\0';
+ if (dcnt < 2 || sysname_known < 0) {
+ dst[sysname_len] = '\0';
+ sysname_len++;
+ }
+ }
+ if (subj && icmpv6_ni_dnscmp(subj, subjlen, dst, sysname_len, sysname_known > 0 ? 0 : 1)) {
+ ret = -1;
+ } else {
+ if (skb)
+ skb_put(skb, sysname_len);
+ ret = NI_TTL_LEN + sysname_len;
+ }
+ } else {
+ up_read(&uts_sem);
+ if (net_ratelimit()) {
+ printk(KERN_WARNING "ICMPv6 NI: your hostname is unknown\n");
+ }
+ ret = -1;
+ }
+ return ret;
+}
+
+static int __icmpv6_ni_getaddrs6(struct sk_buff *skb,
+ struct in6_addr *in6,
+ struct net_device *dev,
+ __u16 *flagsp, int *subjokp)
+{
+ struct inet6_dev *idev;
+ struct inet6_ifaddr *ifp;
+ __u16 flags = flagsp ? *flagsp : 0;
+ int len = 0;
+ int subjok = in6 ? (subjokp ? *subjokp : 0) : 1;
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ char addrbuf[128];
+ if (in6) {
+ in6_ntop(in6, addrbuf);
+ printk(KERN_DEBUG
+ "ni_getaddrs6(): subject address: %s, device=%s\n", addrbuf, dev ? dev->name : "(null)");
+ } else {
+ printk(KERN_DEBUG
+ "ni_getaddrs6(): device=%s\n", dev ? dev->name : "(null)");
+ }
+ if (!skb) {
+ printk(KERN_DEBUG
+ "ni_getaddrs6():- result is not needed\n");
+ }
+#endif
+ idev = in6_dev_get(dev);
+ if (!idev)
+ return -1; /* XXX */
+
+ read_lock(&idev->lock);
+ for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
+ int copy_flag = 0;
+ if (subjok >= 0 && in6) {
+ /* we don't allow loopback address as a subject; XXX: REFUSED? */
+ if (ifp->scope != IFA_HOST && !ipv6_addr_cmp(in6, &ifp->addr)) {
+ subjok = ifp->flags&IFA_F_TEMPORARY ? -1 : 1;
+ }
+ }
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ in6_ntop(&ifp->addr, addrbuf);
+ printk(KERN_DEBUG
+ "ni_getaddrs6():- address: %s, subjok=%d\n", addrbuf, subjok);
+#endif
+ if ((!skb && subjok) || subjok < 0)
+ break;
+ spin_lock_bh(&ifp->lock);
+ if (skb && !(ifp->flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE|IFA_F_TEMPORARY))) {
+ switch (ifp->scope) {
+ case IPV6_ADDR_COMPATv4:
+ /* If IPv4-compatible addresses are requested, set copy_flag to 1. */
+ if (flags & ICMPV6_NI_NODEADDR_FLAG_COMPAT) {
+ copy_flag = 1;
+ }
+ break;
+ case IFA_LINK:
+ /* If Link-scope addresses are requested, set copy_flag to 1. */
+ if (flags & ICMPV6_NI_NODEADDR_FLAG_LINKLOCAL) {
+ copy_flag = 1;
+ }
+ break;
+ case IFA_SITE:
+ /* If Site-local addresses are requested, set copy_flag to 1. */
+ if (flags & ICMPV6_NI_NODEADDR_FLAG_SITELOCAL) {
+ copy_flag = 1;
+ }
+ break;
+ case IFA_GLOBAL:
+ /* If Global-scope addresses are requested, set copy_flag to 1. */
+ if (flags & ICMPV6_NI_NODEADDR_FLAG_GLOBAL) {
+ copy_flag = 1;
+ }
+ break;
+ default:
+ copy_flag = 0;
+ break;
+ }
+
+ if (copy_flag) {
+ if (skb_tailroom(skb) < NI_TTL_LEN + sizeof(struct in6_addr)) {
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ printk(KERN_DEBUG
+ "ni_getaddrs6():- to be copied but truncated\n");
+#endif
+ flags |= ICMPV6_NI_NODEADDR_FLAG_TRUNCATE;
+ if (subjok)
+ break;
+ } else {
+ if (skb) {
+ __u8 *p = skb_put(skb, NI_TTL_LEN + sizeof(struct in6_addr));
+ __u32 ttl;
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ printk(KERN_DEBUG
+ "ni_getaddrs6():- copied\n");
+#endif
+ if (ifp->valid_lft) {
+ if (ifp->valid_lft > (jiffies - ifp->tstamp) / HZ)
+ ttl = ifp->valid_lft - (jiffies - ifp->tstamp) / HZ;
+ else
+ ttl = 0;
+ if (ttl > 0x7fffffff)
+ ttl = 0x7fffffff;
+ } else {
+ ttl = 0x7fffffff;
+ }
+ ttl = htonl(ttl);
+ memcpy(p, &ttl, sizeof(ttl));
+ ipv6_addr_copy((struct in6_addr *)(p + NI_TTL_LEN), &ifp->addr);
+ len += NI_TTL_LEN + sizeof(struct in6_addr);
+ }
+ }
+ }
+ }
+ spin_unlock_bh(&ifp->lock);
+ }
+ read_unlock(&idev->lock);
+ if (flagsp) {
+ *flagsp = flags;
+ }
+ if (subjok && subjokp) {
+ *subjokp = subjok;
+ }
+ if (!subjok && !(flags & ICMPV6_NI_NODEADDR_FLAG_ALL)) {
+ len = -1;
+ } else if (subjok < 0) {
+ len = -2;
+ }
+
+
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ printk(KERN_DEBUG
+ "ni_getaddrs6():- len=%d, flags=%04x\n", len, ntohs(flags));
+#endif
+ in6_dev_put(idev);
+ return len;
+}
+
+static int icmpv6_ni_getaddrs6(struct sk_buff *skb,
+ __u8 *subj, int subjlen,
+ struct net_device **devp,
+ struct in6_addr *daddr, __u16 *flagsp)
+{
+ struct in6_addr *in6 = (struct in6_addr *)subj;
+ struct net_device *dev = devp ? *devp : NULL;
+ __u16 flags = flagsp ? *flagsp : 0;
+ int len = 0;
+ int subjok = in6 ? 0 : 1;
+
+ if (subj && subjlen != sizeof(struct in6_addr))
+ return -1; /* too short subject */
+
+ flags &= (ICMPV6_NI_NODEADDR_FLAG_ALL|
+ ICMPV6_NI_NODEADDR_FLAG_LINKLOCAL|
+ ICMPV6_NI_NODEADDR_FLAG_SITELOCAL|
+ ICMPV6_NI_NODEADDR_FLAG_GLOBAL|
+ ICMPV6_NI_NODEADDR_FLAG_COMPAT);
+
+ if (!subjok) {
+ subjok = !ipv6_addr_cmp(in6, daddr); /* for special case */
+ }
+
+ if (dev && !(flags & ICMPV6_NI_NODEADDR_FLAG_ALL)) {
+ len = __icmpv6_ni_getaddrs6(skb, in6, dev, flagsp ? &flags : NULL, &subjok);
+ } else {
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev; dev = dev->next) {
+ int sublen = __icmpv6_ni_getaddrs6(skb, in6, dev, flagsp ? &flags : NULL, &subjok);
+ if (sublen >= 0) {
+ len += sublen;
+ if (devp && !*devp) {
+ dev_hold(dev);
+ *devp = dev;
+ }
+ } else if (subjok < 0) {
+ len = -2;
+ break;
+ }
+ }
+ read_unlock(&dev_base_lock);
+ }
+ if (!subjok) {
+ len = -1;
+ flags &= ~ICMPV6_NI_NODEADDR_FLAG_TRUNCATE;
+ }
+ if (flagsp) {
+ *flagsp = flags;
+ }
+
+ return len;
+}
+
+static int __icmpv6_ni_getaddrs4(struct sk_buff *skb,
+ struct in_addr *in,
+ struct net_device *dev,
+ __u16 *flagsp, int *subjokp)
+{
+ struct in_device *in_dev;
+ struct in_ifaddr *ifa;
+ __u16 flags = 0;
+ int len = 0;
+ int subjok = in ? (subjokp ? *subjokp : 0) : 1;
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ if (in) {
+ printk(KERN_DEBUG
+ "ni_getaddrs4(): subject address: %d.%d.%d.%d, device=%s\n",
+ NIPQUAD(*(u32*)in), dev ? dev->name : "(null)");
+ } else {
+ printk(KERN_DEBUG
+ "ni_getaddrs4(): device=%s\n", dev ? dev->name : "(null)");
+ }
+ if (!skb) {
+ printk(KERN_DEBUG
+ "ni_getaddrs4():- result is not needed\n");
+ }
+#endif
+ read_lock(&inetdev_lock);
+ if ((in_dev = __in_dev_get(dev)) == NULL) {
+ read_unlock(&inetdev_lock);
+ return -1; /*XXX*/
+ }
+
+ read_lock(&in_dev->lock);
+ for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next) {
+ int copy_flag = 0;
+ if (!subjok && in) {
+ /* we don't allow loopback address as a subject; XXX: REFUSED? */
+ if (ifa->ifa_scope != RT_SCOPE_HOST && memcmp(in, &ifa->ifa_address, sizeof(*in)) == 0) {
+ subjok = 1;
+ }
+ }
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ printk(KERN_DEBUG
+ "ni_getaddrs4():- address: %d.%d.%d.%d, subjok=%d\n", NIPQUAD(ifa->ifa_address), subjok);
+#endif
+
+ if (!skb && subjok)
+ break;
+ if (skb && !(ifa->ifa_flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE))) {
+ switch (ifa->ifa_scope) {
+ case RT_SCOPE_HOST:
+ break; /*XXX*/
+ default:
+ copy_flag = 1;
+ }
+ }
+ if (copy_flag) {
+ if (skb_tailroom(skb) < NI_TTL_LEN + sizeof(struct in_addr)) {
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ printk(KERN_DEBUG
+ "ni_getaddrs4():- to be copied but truncated\n");
+#endif
+ flags |= ICMPV6_NI_IPV4ADDR_FLAG_TRUNCATE;
+ if (subjok)
+ break;
+ } else {
+ if (skb) {
+ __u8 *p = skb_put(skb, NI_TTL_LEN + sizeof(struct in_addr));
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ printk(KERN_DEBUG
+ "ni_getaddrs4():- copied\n");
+#endif
+ memset(p, 0, NI_TTL_LEN); /* XXX */
+ memcpy(p + NI_TTL_LEN, &ifa->ifa_address, sizeof(struct in_addr));
+ len += NI_TTL_LEN + sizeof(struct in_addr);
+ }
+ }
+ }
+ }
+ read_unlock(&in_dev->lock);
+ read_unlock(&inetdev_lock);
+ if (flagsp) {
+ *flagsp = flags;
+ }
+ if (subjok && subjokp) {
+ *subjokp = 1;
+ }
+ if (!subjok && !(flags & ICMPV6_NI_IPV4ADDR_FLAG_ALL)) {
+ len = -1;
+ }
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ printk(KERN_DEBUG
+ "ni_getaddrs4():- len=%d, flags=%04x\n", len, ntohs(flags));
+#endif
+ return len;
+}
+
+static int icmpv6_ni_getaddrs4(struct sk_buff *skb,
+ __u8 *subj, int subjlen,
+ struct net_device **devp,
+ struct in6_addr *daddr, __u16 *flagsp)
+{
+ struct in_addr *in = (struct in_addr *)subj;
+ struct net_device *dev = devp ? *devp : NULL;
+ __u16 flags = flagsp ? *flagsp : 0;
+ int len = 0;
+ int subjok = in ? 0 : 1;
+
+ if (subj && subjlen != sizeof(struct in_addr))
+ return -1; /* too short subject */
+
+ flags &= ICMPV6_NI_IPV4ADDR_FLAG_ALL;
+
+ if (dev && !(flags & ICMPV6_NI_IPV4ADDR_FLAG_ALL)) {
+ len = __icmpv6_ni_getaddrs4(skb, in, dev, flagsp ? &flags : NULL, &subjok);
+ } else {
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev; dev = dev->next) {
+ int sublen = __icmpv6_ni_getaddrs4(skb, in, dev, flagsp ? &flags : NULL, &subjok);
+ if (sublen >= 0) {
+ len += sublen;
+ if (devp && !*devp) {
+ dev_hold(dev);
+ *devp = dev;
+ }
+ }
+ }
+ read_unlock(&dev_base_lock);
+ }
+ if (!subjok) {
+ len = -1;
+ flags &= ~ICMPV6_NI_IPV4ADDR_FLAG_TRUNCATE;
+ }
+ if (flagsp) {
+ *flagsp = flags;
+ }
+
+ return len;
+}
+
+static void icmpv6_ni_reply(struct sk_buff *skb, int config_accept_ni)
+{
+ struct sock *sk = icmpv6_socket->sk;
+ struct ipv6hdr *hdr = skb->nh.ipv6h;
+ struct icmp6hdr *icmph = (struct icmp6hdr *) skb->h.raw;
+ struct in6_addr *saddr;
+ struct icmpv6_msg msg;
+ struct flowi fl;
+ __u8 *data = (__u8 *) (icmph + 1);
+ int datalen = skb->tail - (unsigned char *)data;
+ __u8 *subject;
+ int subjlen;
+ __u16 qtype = ntohs(icmph->icmp6_qtype);
+ struct sk_buff *ni_reply_skb = NULL;
+ int len = 0;
+ __u8 ni_code;
+ __u16 ni_flags = 0;
+ int flen = 0;
+ __u16 fni_flags = 0;
+ struct net_device *dev = NULL;
+ struct inet6_dev *idev = NULL;
+ struct icmpv6_ni_table *code_table = NULL, *qtype_table = NULL;
+
+
+#ifdef CONFIG_SYSCTL
+ /*
+ * [Step 0] Decide our policy through sysctl and/or /proc fs
+ * > 0 : accept NI
+ * == 0 : just ignore NI
+ * < 0 : refuse NI
+ */
+ if (config_accept_ni > 0) {
+ idev = in6_dev_get(skb->dev);
+ if (idev == NULL) {
+ /* Do I need this part? */
+ if (net_ratelimit()) {
+ printk(KERN_WARNING
+ "ICMPv6 NI: can't find in6 device\n");
+ }
+ goto ni_return;
+ }
+ read_lock(&idev->lock);
+ config_accept_ni = idev->cnf.accept_ni;
+ read_unlock(&idev->lock);
+ }
+ if (!config_accept_ni)
+ goto ni_return;
+#endif
+
+ /*
+ * [Step 1] Check the size of query packet
+ */
+ if (datalen < NI_NONCE_LEN) {
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ if (net_ratelimit()) {
+ printk(KERN_WARNING
+ "ICMPv6 NI: invalid query packet length(%d): code=%u, qtype=%u, flags=0x%04x\n",
+ datalen, icmph->icmp6_code, qtype, icmph->icmp6_flags);
+ }
+#endif
+ goto ni_return;
+ }
+ subject = data + NI_NONCE_LEN; /* subject */
+ subjlen = datalen - NI_NONCE_LEN; /* subject length */
+
+ ni_reply_skb = alloc_skb(NI_MAX_REPLY_LEN, GFP_ATOMIC);
+ if (ni_reply_skb == NULL)
+ goto ni_return;
+ skb_reserve(ni_reply_skb, NI_NONCE_LEN);
+
+ /*
+ * [Step 2] Code / qtype / subject validation
+ * (1) qtype validation (set ni_code to ICMPV6_NI_SUCCESS if it is
+ * known)
+ * (2) code validation for codes except for NOOP and SUPTYPES
+ * - reply nothing if it is unknown.
+ * (3) subject validation
+ * a. qtype NOOP and SUPTYPES
+ * - only subject name with no data is valid.
+ * - we must ignore code for NOOP.
+ * - it is not clarified, but we should ignore code for
+ * SUPTYPES.
+ * b. qtype FQDN, NODEADDR and IPV4ADDR
+ * 1) IPV6 subject must have one ipv6 address.
+ * this should be one of our address.
+ * 2) FQDN subject must match up to our system name.
+ * 3) IPV4 subject must have one ipv4 address.
+ * this should be one of our ipv4 address.
+ * NOTE: we do not supply any reply for malformed query;
+ * spec. says we must silently dicard the query
+ * if subject does not match up to our configuration.
+ */
+ ni_code = ICMPV6_NI_UNKNOWN;
+ switch(qtype) {
+ case ICMPV6_NI_QTYPE_NOOP:
+ case ICMPV6_NI_QTYPE_SUPTYPES:
+ /* simple case: code should be ICMPV6_NI_SUBJ_FQDN and must have no subject */
+ switch(icmph->icmp6_code) {
+ case ICMPV6_NI_SUBJ_FQDN:
+ if (subjlen) {
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ if (net_ratelimit()) {
+ printk(KERN_WARNING
+ "ICMPv6 NI (%s): invalid subject length (%d)\n",
+ icmpv6_ni_qtypename(qtype), subjlen);
+ }
+#endif
+ goto ni_return;
+ }
+ break;
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ default:
+ if(net_ratelimit()) {
+ printk(KERN_WARNING
+ "ICMPv6 NI (%s): invalid/unknown code(%u)\n",
+ icmpv6_ni_qtypename(qtype), icmph->icmp6_code);
+ }
+#endif
+ /* we ignore code */
+ }
+ ni_code = ICMPV6_NI_SUCCESS;
+ break;
+ default:
+ if (qtype < sizeof(icmpv6_ni_qtype_table)/sizeof(icmpv6_ni_qtype_table[0])) {
+ qtype_table = &icmpv6_ni_qtype_table[qtype];
+ ni_code = ICMPV6_NI_SUCCESS;
+ }
+ if (icmph->icmp6_code < sizeof(icmpv6_ni_code_table)/sizeof(icmpv6_ni_code_table[0])) {
+ code_table = &icmpv6_ni_code_table[icmph->icmp6_code];
+ fni_flags = icmph->icmp6_flags;
+ if (qtype_table && qtype_table->getinfo == code_table->getinfo) {
+ flen = code_table->getinfo(ni_reply_skb,
+ subject, subjlen,
+ NULL,
+ &hdr->daddr,
+ &fni_flags);
+ } else {
+ flen = code_table->getinfo(NULL,
+ subject, subjlen,
+ &dev,
+ &hdr->daddr,
+ NULL);
+ }
+ if (flen < -1) {
+ config_accept_ni = -1; /*refused*/
+ } else if (flen < 0) {
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ if (net_ratelimit()) {
+ printk(KERN_WARNING
+ "ICMPv6 NI (%s): invalid subject %s, length %d\n",
+ icmpv6_ni_qtypename(qtype),
+ code_table->name, subjlen);
+ }
+#endif
+ goto ni_return;
+ }
+ } else {
+#ifdef CONFIG_IPV6_NODEINFO_DEBUG
+ if(net_ratelimit()) {
+ printk(KERN_WARNING
+ "ICMPv6 NI (%s): unknown code(%d)\n",
+ icmpv6_ni_qtypename(qtype), icmph->icmp6_code);
+ }
+#endif
+ goto ni_return;
+ }
+ }
+
+ /*
+ * [Step 3] Fill common field
+ */
+ memcpy(skb_push(ni_reply_skb, NI_NONCE_LEN), data, NI_NONCE_LEN);
+
+ /*
+ * [Step 4] Unsuccessful reply
+ * - UNKNOWN reply should be done before policy based refusal (section 3)
+ */
+ if (ni_code != ICMPV6_NI_SUCCESS)
+ goto ni_unknown;
+ if (config_accept_ni < 0) {
+ ni_code = ICMPV6_NI_REFUSED;
+ goto ni_refused;
+ }
+
+ /*
+ * [Step 5] Fill the reply
+ */
+ switch(qtype) {
+ case ICMPV6_NI_QTYPE_NOOP:
+ break;
+ case ICMPV6_NI_QTYPE_SUPTYPES:
+ {
+ /* supports NOOP, SUPTYPES, FQDN and NODEADDR */
+ __u32 suptypes = htonl(0x0000001f); /* 00000000 00000000 00000000 00011111 */
+ memcpy(skb_put(ni_reply_skb, sizeof(suptypes)), &suptypes, sizeof(suptypes));
+ len += sizeof(suptypes);
+ ni_flags &= ~ICMPV6_NI_SUPTYPE_FLAG_COMPRESS;
+ break;
+ }
+ default:
+ if (qtype_table->getinfo != code_table->getinfo) {
+ flen = icmpv6_ni_qtype_table[qtype].getinfo(ni_reply_skb,
+ NULL, 0,
+ &dev, NULL,
+ &fni_flags);
+ }
+ len += flen;
+ ni_flags = fni_flags;
+ }
+
+ni_unknown:
+ni_refused:
+ /*
+ * [Step 6] Send reply
+ */
+ saddr = &hdr->daddr;
+
+ if ((ipv6_addr_type(saddr) & (IPV6_ADDR_MULTICAST|IPV6_ADDR_ANYCAST)) ||
+ ipv6_chk_acast_addr(0, saddr))
+ saddr = NULL;
+
+ msg.icmph.icmp6_type = ICMPV6_NI_REPLY;
+ msg.icmph.icmp6_code = ni_code;
+ msg.icmph.icmp6_cksum = 0;
+ msg.icmph.icmp6_qtype = icmph->icmp6_qtype;
+ msg.icmph.icmp6_flags = ni_flags;
+
+ msg.skb = ni_reply_skb;
+ msg.offset = 0;
+ msg.csum = 0;
+ msg.len = sizeof(struct icmp6hdr) + ni_reply_skb->len;
+ msg.daddr = &hdr->saddr;
+
+ fl.proto = IPPROTO_ICMPV6;
+ fl.nl_u.ip6_u.daddr = &hdr->saddr;
+ fl.nl_u.ip6_u.saddr = saddr;
+ fl.oif = skb->dev->ifindex;
+ fl.fl6_flowlabel = 0;
+ fl.uli_u.icmpt.type = ICMPV6_NI_REPLY;
+ fl.uli_u.icmpt.code = ni_code;
+
+ icmpv6_xmit_lock();
+
+ /* rate-limit */
+ if (!icmpv6_xrlim_allow(sk, ICMPV6_NI_REPLY, ni_code, &fl))
+ goto out;
+
+ ip6_build_xmit(sk, icmpv6_getfrag, &msg, &fl, msg.len, NULL, -1, -1,
+ MSG_DONTWAIT);
+
+#ifndef CONFIG_SYSCTL
+ idev = in6_dev_get(skb->dev);
+#endif
+ ICMP6_INC_STATS_BH(idev,Icmp6OutMsgs);
+out:
+ icmpv6_xmit_unlock();
+ni_return:
+ if (ni_reply_skb)
+ kfree_skb(ni_reply_skb);
+ if (idev) {
+ in6_dev_put(idev);
+ }
+ if (dev) {
+ dev_put(dev);
+ }
+}
+#endif
+
+static void icmpv6_notify(struct sk_buff *skb, int type, int code, u32 info)
+{
+ struct in6_addr *saddr, *daddr;
+ struct inet6_protocol *ipprot;
+ struct sock *sk;
+ int inner_offset;
+ int hash;
+ u8 nexthdr;
+
+ if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
+ return;
+
+ nexthdr = ((struct ipv6hdr *)skb->data)->nexthdr;
+ if (ipv6_ext_hdr(nexthdr)) {
+ /* now skip over extension headers */
+ inner_offset = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr, skb->len - sizeof(struct ipv6hdr));
+ if (inner_offset<0)
+ return;
+ } else {
+ inner_offset = sizeof(struct ipv6hdr);
+ }
+
+ /* Checkin header including 8 bytes of inner protocol header. */
+ if (!pskb_may_pull(skb, inner_offset+8))
+ return;
+
+ saddr = &skb->nh.ipv6h->saddr;
+ daddr = &skb->nh.ipv6h->daddr;
+
+ /* BUGGG_FUTURE: we should try to parse exthdrs in this packet.
+ Without this we will not able f.e. to make source routed
+ pmtu discovery.
+ Corresponding argument (opt) to notifiers is already added.
+ --ANK (980726)
+ */
+
+ hash = nexthdr & (MAX_INET_PROTOS - 1);
+
+ for (ipprot = (struct inet6_protocol *) inet6_protos[hash];
+ ipprot != NULL;
+ ipprot=(struct inet6_protocol *)ipprot->next) {
+ if (ipprot->protocol != nexthdr)
+ continue;
+
+ if (ipprot->err_handler)
+ ipprot->err_handler(skb, NULL, type, code, inner_offset, info);
+ }
+
+ read_lock(&raw_v6_lock);
+ if ((sk = raw_v6_htable[hash]) != NULL) {
+ while((sk = __raw_v6_lookup(sk, nexthdr, daddr, saddr))) {
+ rawv6_err(sk, skb, NULL, type, code, inner_offset, info);
+ sk = sk->next;
+ }
+ }
+ read_unlock(&raw_v6_lock);
+}
+
+/*
+ * Handle icmp messages
+ */
+
+int icmpv6_rcv(struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+ struct inet6_dev *idev = in6_dev_get(dev);
+ struct in6_addr *saddr, *daddr;
+ struct ipv6hdr *orig_hdr;
+ struct icmp6hdr *hdr;
+ int type;
+
+ ICMP6_INC_STATS_BH(idev,Icmp6InMsgs);
+
+ saddr = &skb->nh.ipv6h->saddr;
+ daddr = &skb->nh.ipv6h->daddr;
+
+ /* Perform checksum. */
+ if (skb->ip_summed == CHECKSUM_HW) {
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ if (csum_ipv6_magic(saddr, daddr, skb->len, IPPROTO_ICMPV6,
+ skb->csum)) {
+ if (net_ratelimit())
+ printk(KERN_WARNING "ICMPv6 hw checksum failed\n");
+ skb->ip_summed = CHECKSUM_NONE;
+ }
+ }
+ if (skb->ip_summed == CHECKSUM_NONE) {
+ if (csum_ipv6_magic(saddr, daddr, skb->len, IPPROTO_ICMPV6,
+ skb_checksum(skb, 0, skb->len, 0))) {
+ char sbuf[64], dbuf[64];
+ in6_ntop(saddr, sbuf);
+ in6_ntop(daddr, dbuf);
+ if (net_ratelimit())
+ printk(KERN_WARNING "ICMPv6 checksum failed [%s > %s]\n",
+ sbuf, dbuf);
+ goto discard_it;
+ }
+ }
+
+ if (!pskb_pull(skb, sizeof(struct icmp6hdr)))
+ goto discard_it;
+
+ hdr = (struct icmp6hdr *) skb->h.raw;
+
+ type = hdr->icmp6_type;
+
+ if (type >= ICMPV6_DEST_UNREACH && type <= ICMPV6_PARAMPROB) {
+ (&icmpv6_statistics[smp_processor_id()*2].Icmp6InDestUnreachs)[type-ICMPV6_DEST_UNREACH]++;
+ if (idev)
+ (&idev->stats.icmpv6[smp_processor_id()*2].Icmp6InDestUnreachs)[type-ICMPV6_DEST_UNREACH]++;
+ } else if (type >= ICMPV6_ECHO_REQUEST && type <= NDISC_REDIRECT) {
+ (&icmpv6_statistics[smp_processor_id()*2].Icmp6InEchos)[type-ICMPV6_ECHO_REQUEST]++;
+ if (idev)
+ (&idev->stats.icmpv6[smp_processor_id()*2].Icmp6InEchos)[type-ICMPV6_ECHO_REQUEST]++;
+ }
+
+ switch (type) {
+ case ICMPV6_ECHO_REQUEST:
+ icmpv6_echo_reply(skb);
+ break;
+
+ case ICMPV6_ECHO_REPLY:
+ /* we coulnd't care less */
+ break;
+
+ case ICMPV6_PKT_TOOBIG:
+ /* BUGGG_FUTURE: if packet contains rthdr, we cannot update
+ standard destination cache. Seems, only "advanced"
+ destination cache will allow to solve this problem
+ --ANK (980726)
+ */
+ if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
+ goto discard_it;
+ hdr = (struct icmp6hdr *) skb->h.raw;
+ orig_hdr = (struct ipv6hdr *) (hdr + 1);
+ rt6_pmtu_discovery(&orig_hdr->daddr, &orig_hdr->saddr, dev,
+ ntohl(hdr->icmp6_mtu));
+
+ case ICMPV6_DEST_UNREACH:
+ if (hdr->icmp6_code == ICMPV6_ADM_PROHIBITED)
+ ICMP6_INC_STATS_BH(idev,Icmp6InAdminProhibs);
+ case ICMPV6_TIME_EXCEED:
+ case ICMPV6_PARAMPROB:
+ icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu);
+ break;
+
+ case NDISC_ROUTER_SOLICITATION:
+ case NDISC_ROUTER_ADVERTISEMENT:
+ case NDISC_NEIGHBOUR_SOLICITATION:
+ case NDISC_NEIGHBOUR_ADVERTISEMENT:
+ case NDISC_REDIRECT:
+ if (skb_is_nonlinear(skb) &&
+ skb_linearize(skb, GFP_ATOMIC) != 0) {
+ kfree_skb(skb);
+ return 0;
+ }
+
+ ndisc_rcv(skb);
+ break;
+
+ case ICMPV6_MGM_QUERY:
+ igmp6_event_query(skb);
+ break;
+
+ case ICMPV6_MGM_REPORT:
+ igmp6_event_report(skb);
+ break;
+
+ case ICMPV6_MGM_REDUCTION:
+ case ICMPV6_MLD2_REPORT:
+ break;
+
+ case ICMPV6_NI_QUERY:
+ {
+#ifdef CONFIG_IPV6_NODEINFO
+#ifdef CONFIG_SYSCTL
+ int config_accept_ni = ipv6_devconf.accept_ni;
+ if (!config_accept_ni)
+ break;
+ icmpv6_ni_reply(skb, config_accept_ni);
+#else
+ icmpv6_ni_reply(skb, 1);
+#endif
+#endif
+ break;
+ }
+
+ case ICMPV6_NI_REPLY:
+ break;
+
+ default:
+#ifdef CONFIG_IPV6_DEBUG
+ if (net_ratelimit()) {
+ char abuf[64];
+ in6_ntop(saddr, abuf);
+ printk(KERN_DEBUG "icmpv6: msg of unknown type %d code %d from %s\n", type, hdr->icmp6_code, abuf);
+ }
+#endif
+
+ /* informational */
+ if (type & ICMPV6_INFOMSG_MASK)
+ break;
+
+ /*
+ * error of unknown type.
+ * must pass to upper level
+ */
+
+ icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu);
+ };
+ if (idev)
+ in6_dev_put(idev);
+ kfree_skb(skb);
+ return 0;
+
+discard_it:
+ ICMP6_INC_STATS_BH(idev,Icmp6InErrors);
+ if (idev)
+ in6_dev_put(idev);
+ kfree_skb(skb);
+ return 0;
+}
+
+int __init icmpv6_init(struct net_proto_family *ops)
+{
+ struct sock *sk;
+ int err, i, j;
+#ifdef CONFIG_IPV6_NODEINFO
+ struct crypto_tfm *tfm;
+#endif
+
+ for (i = 0; i < NR_CPUS; i++) {
+ icmpv6_socket_cpu(i) = sock_alloc();
+ if (icmpv6_socket_cpu(i) == NULL) {
+ printk(KERN_ERR
+ "Failed to create the ICMP6 control socket.\n");
+ err = -1;
+ goto fail;
+ }
+ icmpv6_socket_cpu(i)->inode->i_uid = 0;
+ icmpv6_socket_cpu(i)->inode->i_gid = 0;
+ icmpv6_socket_cpu(i)->type = SOCK_RAW;
+
+ if ((err = ops->create(icmpv6_socket_cpu(i), IPPROTO_ICMPV6)) < 0) {
+ printk(KERN_ERR
+ "Failed to initialize the ICMP6 control socket "
+ "(err %d).\n",
+ err);
+ goto fail;
+ }
+
+ sk = icmpv6_socket_cpu(i)->sk;
+ sk->allocation = GFP_ATOMIC;
+
+ /* Enough space for 2 64K ICMP packets, including
+ * sk_buff struct overhead.
+ */
+ sk->sndbuf =
+ (2 * ((64 * 1024) + sizeof(struct sk_buff)));
+
+ sk->prot->unhash(sk);
+ }
+
+ inet6_add_protocol(&icmpv6_protocol);
+
+#ifdef CONFIG_IPV6_NODEINFO
+ tfm = crypto_alloc_tfm("md5", 0);
+
+ down_write(&icmpv6_sethostname_hook_sem);
+ if (tfm) {
+ if (md5_tfm == NULL)
+ md5_tfm = tfm;
+ else
+ crypto_free_tfm(tfm);
+ }
+ icmpv6_sethostname_hook = __icmpv6_sethostname_hook;
+ up_write(&icmpv6_sethostname_hook_sem);
+#endif
+
+ return 0;
+fail:
+ for (j = 0; j < i; j++) {
+ sock_release(icmpv6_socket_cpu(j));
+ icmpv6_socket_cpu(j) = NULL;
+ }
+ return err;
+}
+
+void icmpv6_cleanup(void)
+{
+ int i;
+
+#ifdef CONFIG_IPV6_NODEINFO
+ struct crypto_tfm *tfm;
+
+ down_write(&icmpv6_sethostname_hook_sem);
+ tfm = md5_tfm;
+ md5_tfm = NULL;
+ icmpv6_sethostname_hook = NULL;
+ up_write(&icmpv6_sethostname_hook_sem);
+
+ if (tfm != NULL)
+ crypto_free_tfm(tfm);
+#endif
+
+
+ for (i = 0; i < NR_CPUS; i++) {
+ sock_release(icmpv6_socket_cpu(i));
+ icmpv6_socket_cpu(i) = NULL;
+ }
+ inet6_del_protocol(&icmpv6_protocol);
+}
+
+static struct icmp6_err {
+ int err;
+ int fatal;
+} tab_unreach[] = {
+ { ENETUNREACH, 0}, /* NOROUTE */
+ { EACCES, 1}, /* ADM_PROHIBITED */
+ { EHOSTUNREACH, 0}, /* Was NOT_NEIGHBOUR, now reserved */
+ { EHOSTUNREACH, 0}, /* ADDR_UNREACH */
+ { ECONNREFUSED, 1}, /* PORT_UNREACH */
+};
+
+int icmpv6_err_convert(int type, int code, int *err)
+{
+ int fatal = 0;
+
+ *err = EPROTO;
+
+ switch (type) {
+ case ICMPV6_DEST_UNREACH:
+ fatal = 1;
+ if (code <= ICMPV6_PORT_UNREACH) {
+ *err = tab_unreach[code].err;
+ fatal = tab_unreach[code].fatal;
+ }
+ break;
+
+ case ICMPV6_PKT_TOOBIG:
+ *err = EMSGSIZE;
+ break;
+
+ case ICMPV6_PARAMPROB:
+ *err = EPROTO;
+ fatal = 1;
+ break;
+
+ case ICMPV6_TIME_EXCEED:
+ *err = EHOSTUNREACH;
+ break;
+ };
+
+ return fatal;
+}
+
+#ifdef CONFIG_SYSCTL
+ctl_table ipv6_icmp_table[] = {
+ {NET_IPV6_ICMP_RATELIMIT, "ratelimit",
+ &sysctl_icmpv6_time, sizeof(int), 0644, NULL, &proc_dointvec},
+ {0},
+};
+#endif
+
diff --git a/uClinux-2.4.31-uc0/net/ipv6/ip6_fib.c b/uClinux-2.4.31-uc0/net/ipv6/ip6_fib.c
new file mode 100644
index 0000000..10ced4b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/ip6_fib.c
@@ -0,0 +1,1243 @@
+/* $USAGI: ip6_fib.c,v 1.32 2004/01/14 14:11:38 yoshfuji Exp $ */
+
+/*
+ * Linux INET6 implementation
+ * Forwarding Information Database
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ *
+ * $Id: ip6_fib.c,v 1.25 2001/10/31 21:55:55 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/*
+ * Changes:
+ * Changes:
+ * Yuji SEKIYA @USAGI: Support default route on router node;
+ * remove ip6_null_entry from the top of
+ * routing table.
+ * Ville Nuorvala: Fixes to source address sub trees
+ */
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/net.h>
+#include <linux/route.h>
+#include <linux/netdevice.h>
+#include <linux/in6.h>
+#include <linux/init.h>
+
+#ifdef CONFIG_PROC_FS
+#include <linux/proc_fs.h>
+#endif
+
+#include <net/ipv6.h>
+#include <net/ndisc.h>
+#include <net/addrconf.h>
+
+#include <net/ip6_fib.h>
+#include <net/ip6_route.h>
+
+#if RT6_DEBUG >= 3
+#define RT6_TRACE(x...) printk(KERN_DEBUG x)
+#else
+#define RT6_TRACE(x...) do { ; } while (0)
+#endif
+
+struct rt6_statistics rt6_stats;
+
+#if !defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+extern struct rt6_info *rt6_dflt_pointer;
+extern spinlock_t rt6_dflt_lock;
+#endif
+
+static kmem_cache_t * fib6_node_kmem;
+
+enum fib_walk_state_t
+{
+#ifdef CONFIG_IPV6_SUBTREES
+ FWS_S,
+#endif
+ FWS_L,
+ FWS_R,
+ FWS_C,
+ FWS_U
+};
+
+struct fib6_cleaner_t
+{
+ struct fib6_walker_t w;
+ int (*func)(struct rt6_info *, void *arg);
+ void *arg;
+};
+
+rwlock_t fib6_walker_lock = RW_LOCK_UNLOCKED;
+
+
+#ifdef CONFIG_IPV6_SUBTREES
+#define FWS_INIT FWS_S
+#define SUBTREE(fn) ((fn)->subtree)
+#else
+#define FWS_INIT FWS_L
+#define SUBTREE(fn) NULL
+#endif
+
+static struct rt6_info * fib6_find_prefix(struct fib6_node *fn);
+static void fib6_prune_clones(struct fib6_node *fn, struct rt6_info *rt);
+static struct fib6_node * fib6_repair_tree(struct fib6_node *fn);
+
+#ifdef CONFIG_IPV6_SUBTREES
+static struct in6_addr fib6_addr_any;
+#endif
+
+/*
+ * A routing update causes an increase of the serial number on the
+ * afected subtree. This allows for cached routes to be asynchronously
+ * tested when modifications are made to the destination cache as a
+ * result of redirects, path MTU changes, etc.
+ */
+
+static __u32 rt_sernum = 0;
+
+static struct timer_list ip6_fib_timer = { function: fib6_run_gc };
+
+static struct fib6_walker_t fib6_walker_list = {
+ &fib6_walker_list, &fib6_walker_list,
+};
+
+#define FOR_WALKERS(w) for ((w)=fib6_walker_list.next; (w) != &fib6_walker_list; (w)=(w)->next)
+
+static __inline__ u32 fib6_new_sernum(void)
+{
+ u32 n = ++rt_sernum;
+ if ((__s32)n <= 0)
+ rt_sernum = n = 1;
+ return n;
+}
+
+/*
+ * test bit
+ */
+
+static __inline__ int addr_bit_set(const void *token, int fn_bit)
+{
+ const __u32 *addr = token;
+
+ return htonl(1 << ((~fn_bit)&0x1F)) & addr[fn_bit>>5];
+}
+
+static __inline__ struct fib6_node * node_alloc(void)
+{
+ struct fib6_node *fn;
+
+ if ((fn = kmem_cache_alloc(fib6_node_kmem, SLAB_ATOMIC)) != NULL)
+ memset(fn, 0, sizeof(struct fib6_node));
+
+ return fn;
+}
+
+static __inline__ void node_free(struct fib6_node * fn)
+{
+ kmem_cache_free(fib6_node_kmem, fn);
+}
+
+static __inline__ void rt6_release(struct rt6_info *rt)
+{
+ if (atomic_dec_and_test(&rt->rt6i_ref))
+ dst_free(&rt->u.dst);
+}
+
+
+/*
+ * Routing Table
+ *
+ * return the apropriate node for a routing tree "add" operation
+ * by either creating and inserting or by returning an existing
+ * node.
+ */
+
+static struct fib6_node * fib6_add_1(struct fib6_node *root, void *addr,
+ int addrlen, int plen,
+ int offset)
+{
+ struct fib6_node *fn, *in, *ln;
+ struct fib6_node *pn = NULL;
+ struct rt6key *key;
+ int bit;
+ int dir = 0;
+ __u32 sernum = fib6_new_sernum();
+
+ RT6_TRACE("fib6_add_1\n");
+
+ /* insert node in tree */
+
+ fn = root;
+
+ do {
+ key = (struct rt6key *)((u8 *)fn->leaf + offset);
+
+ /*
+ * Prefix match
+ */
+ if (plen < fn->fn_bit ||
+ u32_prefix_cmp(&key->addr, addr, fn->fn_bit))
+ goto insert_above;
+
+ /*
+ * Exact match ?
+ */
+
+ if (plen == fn->fn_bit) {
+ /* clean up an intermediate node */
+ if ((fn->fn_flags & RTN_RTINFO) == 0) {
+ rt6_release(fn->leaf);
+ fn->leaf = NULL;
+ }
+
+ fn->fn_sernum = sernum;
+
+ return fn;
+ }
+
+ /*
+ * We have more bits to go
+ */
+
+ /* Try to walk down on tree. */
+ fn->fn_sernum = sernum;
+ dir = addr_bit_set(addr, fn->fn_bit);
+ pn = fn;
+ fn = dir ? fn->right: fn->left;
+ } while (fn);
+
+ /*
+ * We walked to the bottom of tree.
+ * Create new leaf node without children.
+ */
+
+ ln = node_alloc();
+
+ if (ln == NULL)
+ return NULL;
+ ln->fn_bit = plen;
+
+ ln->parent = pn;
+ ln->fn_sernum = sernum;
+
+ if (dir)
+ pn->right = ln;
+ else
+ pn->left = ln;
+
+ return ln;
+
+
+insert_above:
+ /*
+ * split since we don't have a common prefix anymore or
+ * we have a less significant route.
+ * we've to insert an intermediate node on the list
+ * this new node will point to the one we need to create
+ * and the current
+ */
+
+ pn = fn->parent;
+
+ /*
+ * find 1st bit in difference between the 2 addrs.
+ *
+ * Retuned value is ignored if returned valud is
+ * greater than prefix length.
+ * --ANK (980803)
+ */
+
+ bit = u32_addr_diff(addr, &key->addr, addrlen);
+
+ /*
+ * (intermediate)[in]
+ * / \
+ * (new leaf node)[ln] (old node)[fn]
+ */
+ if (plen > bit) {
+ in = node_alloc();
+ ln = node_alloc();
+
+ if (in == NULL || ln == NULL) {
+ if (in)
+ node_free(in);
+ if (ln)
+ node_free(ln);
+ return NULL;
+ }
+
+ /*
+ * new intermediate node.
+ * RTN_RTINFO will
+ * be off since that an address that chooses one of
+ * the branches would not match less specific routes
+ * in the other branch
+ */
+
+ in->fn_bit = bit;
+
+ in->parent = pn;
+ in->leaf = fn->leaf;
+ atomic_inc(&in->leaf->rt6i_ref);
+
+ in->fn_sernum = sernum;
+
+ /* update parent pointer */
+ if (dir)
+ pn->right = in;
+ else
+ pn->left = in;
+
+ ln->fn_bit = plen;
+
+ ln->parent = in;
+ fn->parent = in;
+
+ ln->fn_sernum = sernum;
+
+ if (addr_bit_set(addr, bit)) {
+ in->right = ln;
+ in->left = fn;
+ } else {
+ in->left = ln;
+ in->right = fn;
+ }
+ } else { /* plen <= bit */
+
+ /*
+ * (new leaf node)[ln]
+ * / \
+ * (old node)[fn] NULL
+ */
+
+ ln = node_alloc();
+
+ if (ln == NULL)
+ return NULL;
+
+ ln->fn_bit = plen;
+
+ ln->parent = pn;
+
+ ln->fn_sernum = sernum;
+
+ if (dir)
+ pn->right = ln;
+ else
+ pn->left = ln;
+
+ if (addr_bit_set(&key->addr, plen))
+ ln->right = fn;
+ else
+ ln->left = fn;
+
+ fn->parent = ln;
+ }
+ return ln;
+}
+
+/*
+ * Insert routing information in a node.
+ */
+
+static int fib6_add_rt2node(struct fib6_node *fn, struct rt6_info *rt,
+ struct nlmsghdr *nlh)
+{
+ struct rt6_info *iter = NULL;
+ struct rt6_info **ins;
+
+ ins = &fn->leaf;
+
+ if (fn->fn_flags&RTN_TL_ROOT &&
+ fn->leaf == &ip6_null_entry &&
+ !(rt->rt6i_flags & (RTF_DEFAULT | RTF_ADDRCONF | RTF_ALLONLINK)) ){
+ /*
+ * The top fib of ip6 routing table includes ip6_null_entry.
+ */
+ RT6_TRACE(
+ "fib6_add_rt2node : Try to insert default route into RTN_TL_ROOT.\n");
+ fn->leaf = rt;
+ rt->u.next = NULL;
+ goto out;
+ }
+
+ for (iter = fn->leaf; iter; iter=iter->u.next) {
+ /*
+ * Search for duplicates
+ */
+
+ if (iter->rt6i_metric == rt->rt6i_metric) {
+ /*
+ * Same priority level
+ */
+
+ if ((iter->rt6i_dev == rt->rt6i_dev) &&
+ (iter->rt6i_flowr == rt->rt6i_flowr) &&
+ (ipv6_addr_cmp(&iter->rt6i_gateway,
+ &rt->rt6i_gateway) == 0)) {
+ if (!(iter->rt6i_flags&RTF_EXPIRES))
+ return -EEXIST;
+ iter->rt6i_expires = rt->rt6i_expires;
+ if (!(rt->rt6i_flags&RTF_EXPIRES)) {
+ iter->rt6i_flags &= ~RTF_EXPIRES;
+ iter->rt6i_expires = 0;
+ }
+ return -EEXIST;
+ }
+ }
+
+ if (iter->rt6i_metric > rt->rt6i_metric)
+ break;
+
+ ins = &iter->u.next;
+ }
+
+ /*
+ * insert node
+ */
+
+out:
+ rt->u.next = iter;
+ *ins = rt;
+ rt->rt6i_node = fn;
+ atomic_inc(&rt->rt6i_ref);
+ inet6_rt_notify(RTM_NEWROUTE, rt, nlh);
+ rt6_stats.fib_rt_entries++;
+
+ if ((fn->fn_flags & RTN_RTINFO) == 0) {
+ rt6_stats.fib_route_nodes++;
+ fn->fn_flags |= RTN_RTINFO;
+ }
+
+ return 0;
+}
+
+static __inline__ void fib6_start_gc(struct rt6_info *rt)
+{
+ if (ip6_fib_timer.expires == 0 &&
+ (rt->rt6i_flags & (RTF_EXPIRES|RTF_CACHE)))
+ mod_timer(&ip6_fib_timer, jiffies + ip6_rt_gc_interval);
+}
+
+static struct rt6_info * fib6_find_prefix(struct fib6_node *fn);
+
+/*
+ * Add routing information to the routing tree.
+ * <destination addr>/<source addr>
+ * with source addr info in sub-trees
+ */
+
+int fib6_add(struct fib6_node *root, struct rt6_info *rt, struct nlmsghdr *nlh)
+{
+ struct fib6_node *fn = NULL;
+ int err = -ENOMEM;
+#ifdef CONFIG_IPV6_SUBTREES
+ struct fib6_node *pn = NULL;
+ if (&rt->rt6i_src)
+ fn = fib6_add_1(root, &rt->rt6i_src.addr, sizeof(struct in6_addr),
+ rt->rt6i_src.plen, (u8*) &rt->rt6i_src - (u8*) rt);
+
+ if (fn == NULL)
+ goto out;
+
+ if (rt->rt6i_dst.plen) {
+ struct fib6_node *sn;
+ if (!fn->leaf) {
+ RT6_TRACE("Adding ip6_null_entry to fn->leaf at fib6_add\n");
+ fn->leaf = &ip6_null_entry;
+ atomic_inc(&ip6_null_entry.rt6i_ref);
+ }
+ if (fn->subtree == NULL) {
+ struct fib6_node *sfn;
+
+ /*
+ * Create subtree.
+ *
+ * fn[main tree]
+ * |
+ * sfn[subtree root]
+ * \
+ * sn[new leaf node]
+ */
+
+ /* Create subtree root node */
+ sfn = node_alloc();
+ if (sfn == NULL)
+ goto st_failure;
+
+ sfn->leaf = &ip6_null_entry;
+ atomic_inc(&ip6_null_entry.rt6i_ref);
+ sfn->fn_flags = RTN_ROOT;
+ sfn->fn_sernum = fib6_new_sernum();
+
+ /* Now add the first leaf node to new subtree */
+
+ sn = fib6_add_1(sfn, &rt->rt6i_dst.addr,
+ sizeof(struct in6_addr), rt->rt6i_dst.plen,
+ (u8*) &rt->rt6i_dst - (u8*) rt);
+
+ if (sn == NULL) {
+ /* If it is failed, discard just allocated
+ root, and then (in st_failure) stale node
+ in main tree.
+ */
+ node_free(sfn);
+ goto st_failure;
+ }
+
+ /* Now link new subtree to main tree */
+ sfn->parent = fn;
+ fn->subtree = sfn;
+ } else {
+ sn = fib6_add_1(fn->subtree, &rt->rt6i_dst.addr,
+ sizeof(struct in6_addr), rt->rt6i_dst.plen,
+ (u8*) &rt->rt6i_dst - (u8*) rt);
+
+ if (sn == NULL)
+ goto st_failure;
+ }
+
+ /* fib6_add_1 might have cleared the old leaf pointer */
+ if (fn->leaf == NULL) {
+ fn->leaf = rt;
+ atomic_inc(&rt->rt6i_ref);
+ }
+
+ pn = fn;
+ fn = sn;
+ }
+#else
+ fn = fib6_add_1(root, &rt->rt6i_dst.addr, sizeof(struct in6_addr),
+ rt->rt6i_dst.plen, (u8*) &rt->rt6i_dst - (u8*) rt);
+
+ if (fn == NULL)
+ goto out;
+#endif
+
+ err = fib6_add_rt2node(fn, rt, nlh);
+
+ if (err == 0) {
+ fib6_start_gc(rt);
+ if (!(rt->rt6i_flags&RTF_CACHE))
+ fib6_prune_clones(fn, rt);
+ }
+
+out:
+ if (err) {
+#ifdef CONFIG_IPV6_SUBTREES
+ /* If fib6_add_1 has cleared the old leaf pointer in the
+ * super-tree leaf node, we have to find a new one for it.
+ *
+ * This situation will never arise in the sub-tree since
+ * the node will at least have the duplicate route that
+ * caused fib6_add_rt2node to fail in the first place.
+ */
+
+ if (pn && !(pn->fn_flags & RTN_RTINFO)) {
+ pn->leaf = fib6_find_prefix(pn);
+#if RT6_DEBUG >= 2
+ if (!pn->leaf) {
+ BUG_TRAP(pn->leaf);
+ pn->leaf = &ip6_null_entry;
+ }
+#endif
+ atomic_inc(&pn->leaf->rt6i_ref);
+ }
+#endif
+ dst_free(&rt->u.dst);
+ }
+ return err;
+
+#ifdef CONFIG_IPV6_SUBTREES
+ /* Subtree creation failed, probably main tree node
+ is orphan. If it is, shoot it.
+ */
+st_failure:
+ if (fn && !(fn->fn_flags&(RTN_RTINFO|RTN_ROOT)))
+ fib6_repair_tree(fn);
+ dst_free(&rt->u.dst);
+ return err;
+#endif
+}
+
+/*
+ * Routing tree lookup
+ *
+ */
+
+struct lookup_args {
+ int offset; /* key offset on rt6_info */
+ const struct in6_addr *addr; /* search key */
+};
+
+static struct fib6_node * fib6_lookup_1(struct fib6_node *root,
+ struct lookup_args *args)
+{
+ struct fib6_node *fn;
+ int dir = 0;
+
+ /*
+ * Descend on a tree
+ */
+
+ fn = root;
+
+ for (;;) {
+ struct fib6_node *next;
+ if (args->addr)
+ dir = addr_bit_set(args->addr, fn->fn_bit);
+
+ next = dir ? fn->right : fn->left;
+
+ if (next) {
+ fn = next;
+ continue;
+ }
+
+ break;
+ }
+
+ for(;;) {
+#ifdef CONFIG_IPV6_SUBTREES
+ if (fn->subtree) {
+ struct rt6key *key;
+
+ key = (struct rt6key *) ((u8 *) fn->leaf +
+ args->offset);
+
+ if (!u32_prefix_cmp(&key->addr, args->addr, key->plen)) {
+ struct fib6_node *st;
+ struct lookup_args *narg = args + 1;
+ if (!ipv6_addr_any(narg->addr)) {
+ st = fib6_lookup_1(fn->subtree, narg);
+
+ if (st && !(st->fn_flags & RTN_ROOT))
+ return st;
+ }
+ }
+ }
+#endif
+ if (fn->fn_flags & RTN_ROOT)
+ break;
+
+ if (fn->fn_flags & RTN_RTINFO) {
+ struct rt6key *key;
+
+ key = (struct rt6key *) ((u8 *) fn->leaf +
+ args->offset);
+
+ if (!u32_prefix_cmp(&key->addr, args->addr, key->plen))
+ return fn;
+ }
+
+ fn = fn->parent;
+ }
+
+ RT6_TRACE("no fib found, return NULL\n");
+ return NULL;
+}
+
+struct fib6_node * fib6_lookup(struct fib6_node *root, struct in6_addr *daddr,
+ struct in6_addr *saddr)
+{
+ struct lookup_args args[2];
+ struct rt6_info *rt = NULL;
+ struct fib6_node *fn = NULL;
+#ifdef CONFIG_IPV6_SUBTREES
+ if (saddr == NULL)
+ saddr = &fib6_addr_any;
+
+ args[0].offset = (u8*) &rt->rt6i_src - (u8*) rt;
+ args[0].addr = saddr;
+ args[1].offset = (u8*) &rt->rt6i_dst - (u8*) rt;
+ args[1].addr = daddr;
+#else
+ args[0].offset = (u8*) &rt->rt6i_dst - (u8*) rt;
+ args[0].addr = daddr;
+#endif
+
+ fn = fib6_lookup_1(root, args);
+
+ if (fn == NULL || fn->fn_flags & RTN_TL_ROOT)
+ fn = root;
+
+ return fn;
+}
+
+/*
+ * Get node with sepciafied destination prefix (and source prefix,
+ * if subtrees are used)
+ */
+
+
+static struct fib6_node * fib6_locate_1(struct fib6_node *root,
+ struct in6_addr *addr,
+ int plen, int offset)
+{
+ struct fib6_node *fn;
+
+ for (fn = root; fn ; ) {
+ struct rt6key *key = (struct rt6key *)((u8 *)fn->leaf + offset);
+
+ /*
+ * Prefix match
+ */
+ if (plen < fn->fn_bit ||
+ u32_prefix_cmp(&key->addr, addr, fn->fn_bit))
+ return NULL;
+
+ if (plen == fn->fn_bit)
+ return fn;
+
+ /*
+ * We have more bits to go
+ */
+ if (addr_bit_set(addr, fn->fn_bit))
+ fn = fn->right;
+ else
+ fn = fn->left;
+ }
+ return NULL;
+}
+
+struct fib6_node * fib6_locate(struct fib6_node *root,
+ struct in6_addr *daddr, int dst_len,
+ struct in6_addr *saddr, int src_len)
+{
+ struct rt6_info *rt = NULL;
+ struct fib6_node *fn;
+
+#ifdef CONFIG_IPV6_SUBTREES
+ if (saddr == NULL)
+ saddr = &fib6_addr_any;
+
+ fn = fib6_locate_1(root, saddr, src_len,
+ (u8*) &rt->rt6i_src - (u8*) rt);
+ if (dst_len) {
+ if (fn)
+ fn = fib6_locate_1(fn->subtree, daddr, dst_len,
+ (u8*) &rt->rt6i_dst - (u8*) rt);
+ else
+ return NULL;
+ }
+#else
+ fn = fib6_locate_1(root, daddr, dst_len,
+ (u8*) &rt->rt6i_dst - (u8*) rt);
+#endif
+
+ if (fn && fn->fn_flags&RTN_RTINFO)
+ return fn;
+
+ return NULL;
+}
+
+
+/*
+ * Deletion
+ *
+ */
+
+static struct rt6_info * fib6_find_prefix(struct fib6_node *fn)
+{
+ if (fn->fn_flags&RTN_ROOT)
+ return &ip6_null_entry;
+
+ while(fn) {
+ if(fn->left)
+ return fn->left->leaf;
+
+ if(fn->right)
+ return fn->right->leaf;
+
+ fn = SUBTREE(fn);
+ }
+ return NULL;
+}
+
+/*
+ * Called to trim the tree of intermediate nodes when possible. "fn"
+ * is the node we want to try and remove.
+ */
+
+static struct fib6_node * fib6_repair_tree(struct fib6_node *fn)
+{
+ int children;
+ int nstate;
+ struct fib6_node *child, *pn;
+ struct fib6_walker_t *w;
+ int iter = 0;
+
+ for (;;) {
+ RT6_TRACE("fixing tree: plen=%d iter=%d\n", fn->fn_bit, iter);
+ iter++;
+
+ BUG_TRAP(!(fn->fn_flags&RTN_RTINFO));
+ BUG_TRAP(!(fn->fn_flags&RTN_TL_ROOT));
+ BUG_TRAP(fn->leaf==NULL);
+
+ children = 0;
+ child = NULL;
+ if (fn->right) child = fn->right, children |= 1;
+ if (fn->left) child = fn->left, children |= 2;
+
+ if (children == 3 || SUBTREE(fn)
+#ifdef CONFIG_IPV6_SUBTREES
+ /* Subtree root (i.e. fn) may have one child */
+ || (children && fn->fn_flags&RTN_ROOT)
+#endif
+ ) {
+ fn->leaf = fib6_find_prefix(fn);
+#if RT6_DEBUG >= 2
+ if (fn->leaf==NULL) {
+ BUG_TRAP(fn->leaf);
+ fn->leaf = &ip6_null_entry;
+ }
+#endif
+ atomic_inc(&fn->leaf->rt6i_ref);
+ return fn->parent;
+ }
+
+ pn = fn->parent;
+#ifdef CONFIG_IPV6_SUBTREES
+ if (SUBTREE(pn) == fn) {
+ BUG_TRAP(fn->fn_flags&RTN_ROOT);
+ SUBTREE(pn) = NULL;
+ nstate = FWS_L;
+ } else {
+ BUG_TRAP(!(fn->fn_flags&RTN_ROOT));
+#endif
+ if (pn->right == fn) pn->right = child;
+ else if (pn->left == fn) pn->left = child;
+#if RT6_DEBUG >= 2
+ else BUG_TRAP(0);
+#endif
+ if (child)
+ child->parent = pn;
+ nstate = FWS_R;
+#ifdef CONFIG_IPV6_SUBTREES
+ }
+#endif
+
+ read_lock(&fib6_walker_lock);
+ FOR_WALKERS(w) {
+ if (child == NULL) {
+ if (w->root == fn) {
+ w->root = w->node = NULL;
+ RT6_TRACE("W %p adjusted by delroot 1\n", w);
+ } else if (w->node == fn) {
+ RT6_TRACE("W %p adjusted by delnode 1, s=%d/%d\n", w, w->state, nstate);
+ w->node = pn;
+ w->state = nstate;
+ }
+ } else {
+ if (w->root == fn) {
+ w->root = child;
+ RT6_TRACE("W %p adjusted by delroot 2\n", w);
+ }
+ if (w->node == fn) {
+ w->node = child;
+ if (children&2) {
+ RT6_TRACE("W %p adjusted by delnode 2, s=%d\n", w, w->state);
+ w->state = w->state>=FWS_R ? FWS_U : FWS_INIT;
+ } else {
+ RT6_TRACE("W %p adjusted by delnode 2, s=%d\n", w, w->state);
+ w->state = w->state>=FWS_C ? FWS_U : FWS_INIT;
+ }
+ }
+ }
+ }
+ read_unlock(&fib6_walker_lock);
+
+ node_free(fn);
+ if (pn->fn_flags&RTN_RTINFO || SUBTREE(pn))
+ return pn;
+
+ rt6_release(pn->leaf);
+ pn->leaf = NULL;
+ fn = pn;
+ }
+}
+
+static void fib6_del_route(struct fib6_node *fn, struct rt6_info **rtp,
+ struct nlmsghdr *nlh)
+{
+ struct fib6_walker_t *w;
+ struct rt6_info *rt = *rtp;
+
+ RT6_TRACE("fib6_del_route\n");
+
+ /* Unlink it */
+ *rtp = rt->u.next;
+ rt->rt6i_node = NULL;
+ rt6_stats.fib_rt_entries--;
+
+ /* Adjust walkers */
+ read_lock(&fib6_walker_lock);
+ FOR_WALKERS(w) {
+ if (w->state == FWS_C && w->leaf == rt) {
+ RT6_TRACE("walker %p adjusted by delroute\n", w);
+ w->leaf = rt->u.next;
+ if (w->leaf == NULL)
+ w->state = FWS_U;
+ }
+ }
+ read_unlock(&fib6_walker_lock);
+
+ rt->u.next = NULL;
+
+ if (fn->leaf == NULL && fn->fn_flags&RTN_TL_ROOT)
+ fn->leaf = &ip6_null_entry;
+
+ /* If it was last route, expunge its radix tree node */
+ if (fn->leaf == NULL) {
+ fn->fn_flags &= ~RTN_RTINFO;
+ rt6_stats.fib_route_nodes--;
+ fn = fib6_repair_tree(fn);
+ }
+
+ if (atomic_read(&rt->rt6i_ref) != 1) {
+ /* This route is used as dummy address holder in some split
+ * nodes. It is not leaked, but it still holds other resources,
+ * which must be released in time. So, scan ascendant nodes
+ * and replace dummy references to this route with references
+ * to still alive ones.
+ */
+ while (fn) {
+ if (!(fn->fn_flags&RTN_RTINFO) && fn->leaf == rt) {
+ fn->leaf = fib6_find_prefix(fn);
+ atomic_inc(&fn->leaf->rt6i_ref);
+ rt6_release(rt);
+ }
+ fn = fn->parent;
+ }
+ /* No more references are possible at this point. */
+ if (atomic_read(&rt->rt6i_ref) != 1) BUG();
+ }
+
+ inet6_rt_notify(RTM_DELROUTE, rt, nlh);
+ rt6_release(rt);
+}
+
+int fib6_del(struct rt6_info *rt, struct nlmsghdr *nlh)
+{
+ struct fib6_node *fn = rt->rt6i_node;
+ struct rt6_info **rtp;
+
+#if RT6_DEBUG >= 2
+ if (rt->u.dst.obsolete>0) {
+ BUG_TRAP(fn==NULL);
+ return -ENOENT;
+ }
+#endif
+ if (fn == NULL || rt == &ip6_null_entry)
+ return -ENOENT;
+
+ BUG_TRAP(fn->fn_flags&RTN_RTINFO);
+
+ if (!(rt->rt6i_flags&RTF_CACHE))
+ fib6_prune_clones(fn, rt);
+
+ /*
+ * Walk the leaf entries looking for ourself
+ */
+
+ for (rtp = &fn->leaf; *rtp; rtp = &(*rtp)->u.next) {
+ if (*rtp == rt) {
+ fib6_del_route(fn, rtp, nlh);
+ return 0;
+ }
+ }
+ return -ENOENT;
+}
+
+/*
+ * Tree traversal function.
+ *
+ * Certainly, it is not interrupt safe.
+ * However, it is internally reenterable wrt itself and fib6_add/fib6_del.
+ * It means, that we can modify tree during walking
+ * and use this function for garbage collection, clone pruning,
+ * cleaning tree when a device goes down etc. etc.
+ *
+ * It guarantees that every node will be traversed,
+ * and that it will be traversed only once.
+ *
+ * Callback function w->func may return:
+ * 0 -> continue walking.
+ * positive value -> walking is suspended (used by tree dumps,
+ * and probably by gc, if it will be split to several slices)
+ * negative value -> terminate walking.
+ *
+ * The function itself returns:
+ * 0 -> walk is complete.
+ * >0 -> walk is incomplete (i.e. suspended)
+ * <0 -> walk is terminated by an error.
+ */
+
+int fib6_walk_continue(struct fib6_walker_t *w)
+{
+ struct fib6_node *fn, *pn;
+
+ for (;;) {
+ fn = w->node;
+ if (fn == NULL)
+ return 0;
+
+ if (w->prune && fn != w->root &&
+ fn->fn_flags&RTN_RTINFO && w->state < FWS_C) {
+ w->state = FWS_C;
+ w->leaf = fn->leaf;
+ }
+ switch (w->state) {
+#ifdef CONFIG_IPV6_SUBTREES
+ case FWS_S:
+ if (SUBTREE(fn)) {
+ w->node = SUBTREE(fn);
+ continue;
+ }
+ w->state = FWS_L;
+#endif
+ case FWS_L:
+ if (fn->left) {
+ w->node = fn->left;
+ w->state = FWS_INIT;
+ continue;
+ }
+ w->state = FWS_R;
+ case FWS_R:
+ if (fn->right) {
+ w->node = fn->right;
+ w->state = FWS_INIT;
+ continue;
+ }
+ w->state = FWS_C;
+ w->leaf = fn->leaf;
+ case FWS_C:
+ if (w->leaf && fn->fn_flags&RTN_RTINFO) {
+ int err = w->func(w);
+ if (err)
+ return err;
+ continue;
+ }
+ w->state = FWS_U;
+ case FWS_U:
+ if (fn == w->root)
+ return 0;
+ pn = fn->parent;
+ w->node = pn;
+#ifdef CONFIG_IPV6_SUBTREES
+ if (SUBTREE(pn) == fn) {
+ BUG_TRAP(fn->fn_flags&RTN_ROOT);
+ w->state = FWS_L;
+ continue;
+ }
+#endif
+ if (pn->left == fn) {
+ w->state = FWS_R;
+ continue;
+ }
+ if (pn->right == fn) {
+ w->state = FWS_C;
+ w->leaf = w->node->leaf;
+ continue;
+ }
+#if RT6_DEBUG >= 2
+ BUG_TRAP(0);
+#endif
+ }
+ }
+}
+
+int fib6_walk(struct fib6_walker_t *w)
+{
+ int res;
+
+ w->state = FWS_INIT;
+ w->node = w->root;
+
+ fib6_walker_link(w);
+ res = fib6_walk_continue(w);
+ if (res <= 0)
+ fib6_walker_unlink(w);
+ return res;
+}
+
+static int fib6_clean_node(struct fib6_walker_t *w)
+{
+ int res;
+ struct rt6_info *rt;
+ struct fib6_cleaner_t *c = (struct fib6_cleaner_t*)w;
+
+ for (rt = w->leaf; rt; rt = rt->u.next) {
+ res = c->func(rt, c->arg);
+ if (res < 0) {
+ w->leaf = rt;
+ res = fib6_del(rt, NULL);
+ if (res) {
+#if RT6_DEBUG >= 2
+ printk(KERN_DEBUG "fib6_clean_node: del failed: rt=%p@%p err=%d\n", rt, rt->rt6i_node, res);
+#endif
+ continue;
+ }
+ return 0;
+ }
+ BUG_TRAP(res==0);
+ }
+ w->leaf = rt;
+ return 0;
+}
+
+/*
+ * Convenient frontend to tree walker.
+ *
+ * func is called on each route.
+ * It may return -1 -> delete this route.
+ * 0 -> continue walking
+ *
+ * prune==1 -> only immediate children of node (certainly,
+ * ignoring pure split nodes) will be scanned.
+ */
+
+void fib6_clean_tree(struct fib6_node *root,
+ int (*func)(struct rt6_info *, void *arg),
+ int prune, void *arg)
+{
+ struct fib6_cleaner_t c;
+
+ c.w.root = root;
+ c.w.func = fib6_clean_node;
+ c.w.prune = prune;
+ c.func = func;
+ c.arg = arg;
+
+ fib6_walk(&c.w);
+}
+
+static int fib6_prune_clone(struct rt6_info *rt, void *arg)
+{
+ if (rt->rt6i_flags & RTF_CACHE) {
+ RT6_TRACE("pruning clone %p\n", rt);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void fib6_prune_clones(struct fib6_node *fn, struct rt6_info *rt)
+{
+ fib6_clean_tree(fn, fib6_prune_clone, 1, rt);
+}
+
+/*
+ * Garbage collection
+ */
+
+static struct fib6_gc_args
+{
+ int timeout;
+ int more;
+} gc_args;
+
+static int fib6_age(struct rt6_info *rt, void *arg)
+{
+ unsigned long now = jiffies;
+
+ /* Age clones. Note, that clones are aged out
+ only if they are not in use now.
+ */
+
+ /*
+ * check addrconf expiration here.
+ * They are expired even if they are in use.
+ */
+
+ if (rt->rt6i_flags&RTF_EXPIRES && rt->rt6i_expires) {
+ if (time_after(now, rt->rt6i_expires)) {
+ RT6_TRACE("expiring %p\n", rt);
+#if !defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ spin_lock_bh(&rt6_dflt_lock);
+ if (rt == rt6_dflt_pointer)
+ rt6_dflt_pointer = NULL;
+ spin_unlock_bh(&rt6_dflt_lock);
+#endif
+ return -1;
+ }
+ gc_args.more++;
+ } else if (rt->rt6i_flags & RTF_CACHE) {
+ if (atomic_read(&rt->u.dst.__refcnt) == 0 &&
+ time_after_eq(now, rt->u.dst.lastuse + gc_args.timeout)) {
+ RT6_TRACE("aging clone %p\n", rt);
+ return -1;
+ }
+ gc_args.more++;
+ }
+
+ return 0;
+}
+
+static spinlock_t fib6_gc_lock = SPIN_LOCK_UNLOCKED;
+
+void fib6_run_gc(unsigned long dummy)
+{
+ if (dummy != ~0UL) {
+ spin_lock_bh(&fib6_gc_lock);
+ gc_args.timeout = (int)dummy;
+ } else {
+ local_bh_disable();
+ if (!spin_trylock(&fib6_gc_lock)) {
+ mod_timer(&ip6_fib_timer, jiffies + HZ);
+ local_bh_enable();
+ return;
+ }
+ gc_args.timeout = ip6_rt_gc_interval;
+ }
+ gc_args.more = 0;
+
+
+ write_lock_bh(&rt6_lock);
+ fib6_clean_tree(&ip6_routing_table, fib6_age, 0, NULL);
+ write_unlock_bh(&rt6_lock);
+
+ if (gc_args.more)
+ mod_timer(&ip6_fib_timer, jiffies + ip6_rt_gc_interval);
+ else {
+ del_timer(&ip6_fib_timer);
+ ip6_fib_timer.expires = 0;
+ }
+ spin_unlock_bh(&fib6_gc_lock);
+}
+
+void __init fib6_init(void)
+{
+ if (!fib6_node_kmem)
+ fib6_node_kmem = kmem_cache_create("fib6_nodes",
+ sizeof(struct fib6_node),
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+}
+
+#ifdef MODULE
+void fib6_gc_cleanup(void)
+{
+ del_timer(&ip6_fib_timer);
+ if (kmem_cache_destroy(fib6_node_kmem) == 0) {
+ RT6_TRACE("%s(): kmem_cache %p for fib6_nodes destroyed\n",
+ __FUNCTION__, fib6_node_kmem);
+ fib6_node_kmem = NULL;
+ } else {
+ RT6_TRACE("%s(): failed to destroy kmem_cache %p for fib6_nodes.\n",
+ __FUNCTION__, fib6_node_kmem);
+ }
+}
+#endif
+
+
diff --git a/uClinux-2.4.31-uc0/net/ipv6/ip6_flowlabel.c b/uClinux-2.4.31-uc0/net/ipv6/ip6_flowlabel.c
new file mode 100644
index 0000000..ee4d447
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/ip6_flowlabel.c
@@ -0,0 +1,629 @@
+/* $USAGI: ip6_flowlabel.c,v 1.4 2000/12/29 07:06:58 yoshfuji Exp $ */
+
+/*
+ * ip6_flowlabel.c IPv6 flowlabel manager.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/random.h>
+#include <linux/socket.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/in6.h>
+#include <linux/route.h>
+#include <linux/proc_fs.h>
+
+#include <net/sock.h>
+
+#include <net/ipv6.h>
+#include <net/ndisc.h>
+#include <net/protocol.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+#include <net/rawv6.h>
+#include <net/icmp.h>
+#include <net/transp_v6.h>
+
+#include <asm/uaccess.h>
+
+#define FL_MIN_LINGER 6 /* Minimal linger. It is set to 6sec specified
+ in old IPv6 RFC. Well, it was reasonable value.
+ */
+#define FL_MAX_LINGER 60 /* Maximal linger timeout */
+
+/* FL hash table */
+
+#define FL_MAX_PER_SOCK 32
+#define FL_MAX_SIZE 4096
+#define FL_HASH_MASK 255
+#define FL_HASH(l) (ntohl(l)&FL_HASH_MASK)
+
+static atomic_t fl_size = ATOMIC_INIT(0);
+static struct ip6_flowlabel *fl_ht[FL_HASH_MASK+1];
+
+static struct timer_list ip6_fl_gc_timer;
+
+/* FL hash table lock: it protects only of GC */
+
+static rwlock_t ip6_fl_lock = RW_LOCK_UNLOCKED;
+
+/* Big socket sock */
+
+static rwlock_t ip6_sk_fl_lock = RW_LOCK_UNLOCKED;
+
+
+static __inline__ struct ip6_flowlabel * __fl_lookup(u32 label)
+{
+ struct ip6_flowlabel *fl;
+
+ for (fl=fl_ht[FL_HASH(label)]; fl; fl = fl->next) {
+ if (fl->label == label)
+ return fl;
+ }
+ return NULL;
+}
+
+static struct ip6_flowlabel * fl_lookup(u32 label)
+{
+ struct ip6_flowlabel *fl;
+
+ read_lock_bh(&ip6_fl_lock);
+ fl = __fl_lookup(label);
+ if (fl)
+ atomic_inc(&fl->users);
+ read_unlock_bh(&ip6_fl_lock);
+ return fl;
+}
+
+
+static void fl_free(struct ip6_flowlabel *fl)
+{
+ if (fl->opt)
+ kfree(fl->opt);
+ kfree(fl);
+}
+
+static void fl_release(struct ip6_flowlabel *fl)
+{
+ fl->lastuse = jiffies;
+ if (atomic_dec_and_test(&fl->users)) {
+ unsigned long ttd = fl->lastuse + fl->linger;
+ if ((long)(ttd - fl->expires) > 0)
+ fl->expires = ttd;
+ ttd = fl->expires;
+ if (fl->opt && fl->share == IPV6_FL_S_EXCL) {
+ struct ipv6_txoptions *opt = fl->opt;
+ fl->opt = NULL;
+ kfree(opt);
+ }
+ if (!del_timer(&ip6_fl_gc_timer) ||
+ (long)(ip6_fl_gc_timer.expires - ttd) > 0)
+ ip6_fl_gc_timer.expires = ttd;
+ add_timer(&ip6_fl_gc_timer);
+ }
+}
+
+static void ip6_fl_gc(unsigned long dummy)
+{
+ int i;
+ unsigned long now = jiffies;
+ unsigned long sched = 0;
+
+ write_lock(&ip6_fl_lock);
+
+ for (i=0; i<=FL_HASH_MASK; i++) {
+ struct ip6_flowlabel *fl, **flp;
+ flp = &fl_ht[i];
+ while ((fl=*flp) != NULL) {
+ if (atomic_read(&fl->users) == 0) {
+ unsigned long ttd = fl->lastuse + fl->linger;
+ if ((long)(ttd - fl->expires) > 0)
+ fl->expires = ttd;
+ ttd = fl->expires;
+ if ((long)(now - ttd) >= 0) {
+ *flp = fl->next;
+ fl_free(fl);
+ atomic_dec(&fl_size);
+ continue;
+ }
+ if (!sched || (long)(ttd - sched) < 0)
+ sched = ttd;
+ }
+ flp = &fl->next;
+ }
+ }
+ if (!sched && atomic_read(&fl_size))
+ sched = now + FL_MAX_LINGER;
+ if (sched) {
+ ip6_fl_gc_timer.expires = sched;
+ add_timer(&ip6_fl_gc_timer);
+ }
+ write_unlock(&ip6_fl_lock);
+}
+
+static int fl_intern(struct ip6_flowlabel *fl, __u32 label)
+{
+ fl->label = label & IPV6_FLOWLABEL_MASK;
+
+ write_lock_bh(&ip6_fl_lock);
+ if (label == 0) {
+ for (;;) {
+ get_random_bytes(&fl->label, sizeof(fl->label));
+ fl->label &= IPV6_FLOWLABEL_MASK;
+ if (fl->label) {
+ struct ip6_flowlabel *lfl;
+ lfl = __fl_lookup(fl->label);
+ if (lfl == NULL)
+ break;
+ }
+ }
+ }
+
+ fl->lastuse = jiffies;
+ fl->next = fl_ht[FL_HASH(fl->label)];
+ fl_ht[FL_HASH(fl->label)] = fl;
+ atomic_inc(&fl_size);
+ write_unlock_bh(&ip6_fl_lock);
+ return 0;
+}
+
+
+
+/* Socket flowlabel lists */
+
+struct ip6_flowlabel * fl6_sock_lookup(struct sock *sk, u32 label)
+{
+ struct ipv6_fl_socklist *sfl;
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+
+ label &= IPV6_FLOWLABEL_MASK;
+
+ for (sfl=np->ipv6_fl_list; sfl; sfl = sfl->next) {
+ struct ip6_flowlabel *fl = sfl->fl;
+ if (fl->label == label) {
+ fl->lastuse = jiffies;
+ atomic_inc(&fl->users);
+ return fl;
+ }
+ }
+ return NULL;
+}
+
+void fl6_free_socklist(struct sock *sk)
+{
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct ipv6_fl_socklist *sfl;
+
+ while ((sfl = np->ipv6_fl_list) != NULL) {
+ np->ipv6_fl_list = sfl->next;
+ fl_release(sfl->fl);
+ kfree(sfl);
+ }
+}
+
+/* Service routines */
+
+
+/*
+ It is the only difficult place. flowlabel enforces equal headers
+ before and including routing header, however user may supply options
+ following rthdr.
+ */
+
+struct ipv6_txoptions *fl6_merge_options(struct ipv6_txoptions * opt_space,
+ struct ip6_flowlabel * fl,
+ struct ipv6_txoptions * fopt)
+{
+ struct ipv6_txoptions * fl_opt = fl->opt;
+
+ if (fopt == NULL || fopt->opt_flen == 0)
+ return fl_opt;
+
+ if (fl_opt != NULL) {
+ opt_space->hopopt = fl_opt->hopopt;
+ opt_space->dst0opt = fl_opt->dst0opt;
+ opt_space->srcrt = fl_opt->srcrt;
+ opt_space->opt_nflen = fl_opt->opt_nflen;
+ } else {
+ if (fopt->opt_nflen == 0)
+ return fopt;
+ opt_space->hopopt = NULL;
+ opt_space->dst0opt = NULL;
+ opt_space->srcrt = NULL;
+ opt_space->opt_nflen = 0;
+ }
+ opt_space->dst1opt = fopt->dst1opt;
+ opt_space->auth = fopt->auth;
+ opt_space->opt_flen = fopt->opt_flen;
+ return opt_space;
+}
+
+static __u32 check_linger(__u16 ttl)
+{
+ if (ttl < FL_MIN_LINGER)
+ return FL_MIN_LINGER*HZ;
+ if (ttl > FL_MAX_LINGER && !capable(CAP_NET_ADMIN))
+ return 0;
+ return ttl*HZ;
+}
+
+static int fl6_renew(struct ip6_flowlabel *fl, unsigned linger, unsigned expires)
+{
+ linger = check_linger(linger);
+ if (!linger)
+ return -EPERM;
+ expires = check_linger(expires);
+ if (!expires)
+ return -EPERM;
+ fl->lastuse = jiffies;
+ if (fl->linger < linger)
+ fl->linger = linger;
+ if (expires < fl->linger)
+ expires = fl->linger;
+ if ((long)(fl->expires - (fl->lastuse+expires)) < 0)
+ fl->expires = fl->lastuse + expires;
+ return 0;
+}
+
+static struct ip6_flowlabel *
+fl_create(struct in6_flowlabel_req *freq, char *optval, int optlen, int *err_p)
+{
+ struct ip6_flowlabel *fl;
+ int olen;
+ int addr_type;
+ int err;
+
+ err = -ENOMEM;
+ fl = kmalloc(sizeof(*fl), GFP_KERNEL);
+ if (fl == NULL)
+ goto done;
+ memset(fl, 0, sizeof(*fl));
+
+ olen = optlen - CMSG_ALIGN(sizeof(*freq));
+ if (olen > 0) {
+ struct msghdr msg;
+ struct flowi flowi;
+ int junk;
+
+ err = -ENOMEM;
+ fl->opt = kmalloc(sizeof(*fl->opt) + olen, GFP_KERNEL);
+ if (fl->opt == NULL)
+ goto done;
+
+ memset(fl->opt, 0, sizeof(*fl->opt));
+ fl->opt->tot_len = sizeof(*fl->opt) + olen;
+ err = -EFAULT;
+ if (copy_from_user(fl->opt+1, optval+CMSG_ALIGN(sizeof(*freq)), olen))
+ goto done;
+
+ msg.msg_controllen = olen;
+ msg.msg_control = (void*)(fl->opt+1);
+ flowi.oif = 0;
+
+ err = datagram_send_ctl(&msg, &flowi, fl->opt, &junk, &junk);
+ if (err)
+ goto done;
+ err = -EINVAL;
+ if (fl->opt->opt_flen)
+ goto done;
+ if (fl->opt->opt_nflen == 0) {
+ kfree(fl->opt);
+ fl->opt = NULL;
+ }
+ }
+
+ fl->expires = jiffies;
+ err = fl6_renew(fl, freq->flr_linger, freq->flr_expires);
+ if (err)
+ goto done;
+ fl->share = freq->flr_share;
+ addr_type = ipv6_addr_type(&freq->flr_dst);
+ if ((addr_type&IPV6_ADDR_MAPPED)
+ || addr_type == IPV6_ADDR_ANY)
+ goto done;
+ ipv6_addr_copy(&fl->dst, &freq->flr_dst);
+ atomic_set(&fl->users, 1);
+ switch (fl->share) {
+ case IPV6_FL_S_EXCL:
+ case IPV6_FL_S_ANY:
+ break;
+ case IPV6_FL_S_PROCESS:
+ fl->owner = current->pid;
+ break;
+ case IPV6_FL_S_USER:
+ fl->owner = current->euid;
+ break;
+ default:
+ err = -EINVAL;
+ goto done;
+ }
+ return fl;
+
+done:
+ if (fl)
+ fl_free(fl);
+ *err_p = err;
+ return NULL;
+}
+
+static int mem_check(struct sock *sk)
+{
+ struct ipv6_fl_socklist *sfl;
+ int room = FL_MAX_SIZE - atomic_read(&fl_size);
+ int count = 0;
+
+ if (room > FL_MAX_SIZE - FL_MAX_PER_SOCK)
+ return 0;
+
+ for (sfl = sk->net_pinfo.af_inet6.ipv6_fl_list; sfl; sfl = sfl->next)
+ count++;
+
+ if (room <= 0 ||
+ ((count >= FL_MAX_PER_SOCK ||
+ (count > 0 && room < FL_MAX_SIZE/2) || room < FL_MAX_SIZE/4)
+ && !capable(CAP_NET_ADMIN)))
+ return -ENOBUFS;
+
+ return 0;
+}
+
+static int ipv6_hdr_cmp(struct ipv6_opt_hdr *h1, struct ipv6_opt_hdr *h2)
+{
+ if (h1 == h2)
+ return 0;
+ if (h1 == NULL || h2 == NULL)
+ return 1;
+ if (h1->hdrlen != h2->hdrlen)
+ return 1;
+ return memcmp(h1+1, h2+1, ((h1->hdrlen+1)<<3) - sizeof(*h1));
+}
+
+static int ipv6_opt_cmp(struct ipv6_txoptions *o1, struct ipv6_txoptions *o2)
+{
+ if (o1 == o2)
+ return 0;
+ if (o1 == NULL || o2 == NULL)
+ return 1;
+ if (o1->opt_nflen != o2->opt_nflen)
+ return 1;
+ if (ipv6_hdr_cmp(o1->hopopt, o2->hopopt))
+ return 1;
+ if (ipv6_hdr_cmp(o1->dst0opt, o2->dst0opt))
+ return 1;
+ if (ipv6_hdr_cmp((struct ipv6_opt_hdr *)o1->srcrt, (struct ipv6_opt_hdr *)o2->srcrt))
+ return 1;
+ return 0;
+}
+
+int ipv6_flowlabel_opt(struct sock *sk, char *optval, int optlen)
+{
+ int err;
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct in6_flowlabel_req freq;
+ struct ipv6_fl_socklist *sfl1=NULL;
+ struct ipv6_fl_socklist *sfl, **sflp;
+ struct ip6_flowlabel *fl;
+
+ if (optlen < sizeof(freq))
+ return -EINVAL;
+
+ if (copy_from_user(&freq, optval, sizeof(freq)))
+ return -EFAULT;
+
+ switch (freq.flr_action) {
+ case IPV6_FL_A_PUT:
+ write_lock_bh(&ip6_sk_fl_lock);
+ for (sflp = &np->ipv6_fl_list; (sfl=*sflp)!=NULL; sflp = &sfl->next) {
+ if (sfl->fl->label == freq.flr_label) {
+ if (freq.flr_label == (np->flow_label&IPV6_FLOWLABEL_MASK))
+ np->flow_label &= ~IPV6_FLOWLABEL_MASK;
+ *sflp = sfl->next;
+ write_unlock_bh(&ip6_sk_fl_lock);
+ fl_release(sfl->fl);
+ kfree(sfl);
+ return 0;
+ }
+ }
+ write_unlock_bh(&ip6_sk_fl_lock);
+ return -ESRCH;
+
+ case IPV6_FL_A_RENEW:
+ read_lock_bh(&ip6_sk_fl_lock);
+ for (sfl = np->ipv6_fl_list; sfl; sfl = sfl->next) {
+ if (sfl->fl->label == freq.flr_label) {
+ err = fl6_renew(sfl->fl, freq.flr_linger, freq.flr_expires);
+ read_unlock_bh(&ip6_sk_fl_lock);
+ return err;
+ }
+ }
+ read_unlock_bh(&ip6_sk_fl_lock);
+
+ if (freq.flr_share == IPV6_FL_S_NONE && capable(CAP_NET_ADMIN)) {
+ fl = fl_lookup(freq.flr_label);
+ if (fl) {
+ err = fl6_renew(fl, freq.flr_linger, freq.flr_expires);
+ fl_release(fl);
+ return err;
+ }
+ }
+ return -ESRCH;
+
+ case IPV6_FL_A_GET:
+ if (freq.flr_label & ~IPV6_FLOWLABEL_MASK)
+ return -EINVAL;
+
+ fl = fl_create(&freq, optval, optlen, &err);
+ if (fl == NULL)
+ return err;
+ sfl1 = kmalloc(sizeof(*sfl1), GFP_KERNEL);
+
+ if (freq.flr_label) {
+ struct ip6_flowlabel *fl1 = NULL;
+
+ err = -EEXIST;
+ read_lock_bh(&ip6_sk_fl_lock);
+ for (sfl = np->ipv6_fl_list; sfl; sfl = sfl->next) {
+ if (sfl->fl->label == freq.flr_label) {
+ if (freq.flr_flags&IPV6_FL_F_EXCL) {
+ read_unlock_bh(&ip6_sk_fl_lock);
+ goto done;
+ }
+ fl1 = sfl->fl;
+ atomic_inc(&fl->users);
+ break;
+ }
+ }
+ read_unlock_bh(&ip6_sk_fl_lock);
+
+ if (fl1 == NULL)
+ fl1 = fl_lookup(freq.flr_label);
+ if (fl1) {
+ err = -EEXIST;
+ if (freq.flr_flags&IPV6_FL_F_EXCL)
+ goto release;
+ err = -EPERM;
+ if (fl1->share == IPV6_FL_S_EXCL ||
+ fl1->share != fl->share ||
+ fl1->owner != fl->owner)
+ goto release;
+
+ err = -EINVAL;
+ if (ipv6_addr_cmp(&fl1->dst, &fl->dst) ||
+ ipv6_opt_cmp(fl1->opt, fl->opt))
+ goto release;
+
+ err = -ENOMEM;
+ if (sfl1 == NULL)
+ goto release;
+ if (fl->linger > fl1->linger)
+ fl1->linger = fl->linger;
+ if ((long)(fl->expires - fl1->expires) > 0)
+ fl1->expires = fl->expires;
+ write_lock_bh(&ip6_sk_fl_lock);
+ sfl1->fl = fl1;
+ sfl1->next = np->ipv6_fl_list;
+ np->ipv6_fl_list = sfl1;
+ write_unlock_bh(&ip6_sk_fl_lock);
+ fl_free(fl);
+ return 0;
+
+release:
+ fl_release(fl1);
+ goto done;
+ }
+ }
+ err = -ENOENT;
+ if (!(freq.flr_flags&IPV6_FL_F_CREATE))
+ goto done;
+
+ err = -ENOMEM;
+ if (sfl1 == NULL || (err = mem_check(sk)) != 0)
+ goto done;
+
+ err = fl_intern(fl, freq.flr_label);
+ if (err)
+ goto done;
+
+ /* Do not check for fault */
+ if (!freq.flr_label)
+ copy_to_user(optval + ((u8*)&freq.flr_label - (u8*)&freq), &fl->label, sizeof(fl->label));
+
+ sfl1->fl = fl;
+ sfl1->next = np->ipv6_fl_list;
+ np->ipv6_fl_list = sfl1;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+
+done:
+ if (fl)
+ fl_free(fl);
+ if (sfl1)
+ kfree(sfl1);
+ return err;
+}
+
+#ifdef CONFIG_PROC_FS
+
+
+static int ip6_fl_read_proc(char *buffer, char **start, off_t offset,
+ int length, int *eof, void *data)
+{
+ off_t pos=0;
+ off_t begin=0;
+ int len=0;
+ int i, k;
+ struct ip6_flowlabel *fl;
+
+ len+= sprintf(buffer,"Label S Owner Users Linger Expires "
+ "Dst Opt\n");
+
+ read_lock_bh(&ip6_fl_lock);
+ for (i=0; i<=FL_HASH_MASK; i++) {
+ for (fl = fl_ht[i]; fl; fl = fl->next) {
+ len+=sprintf(buffer+len,"%05X %-1d %-6d %-6d %-6d %-8ld ",
+ (unsigned)ntohl(fl->label),
+ fl->share,
+ (unsigned)fl->owner,
+ atomic_read(&fl->users),
+ fl->linger/HZ,
+ (long)(fl->expires - jiffies)/HZ);
+
+ for (k=0; k<16; k++)
+ len+=sprintf(buffer+len, "%02x", fl->dst.s6_addr[k]);
+ buffer[len++]=' ';
+ len+=sprintf(buffer+len, "%-4d", fl->opt ? fl->opt->opt_nflen : 0);
+ buffer[len++]='\n';
+
+ pos=begin+len;
+ if(pos<offset) {
+ len=0;
+ begin=pos;
+ }
+ if(pos>offset+length)
+ goto done;
+ }
+ }
+ *eof = 1;
+
+done:
+ read_unlock_bh(&ip6_fl_lock);
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ if(len<0)
+ len=0;
+ return len;
+}
+#endif
+
+
+void ip6_flowlabel_init()
+{
+ init_timer(&ip6_fl_gc_timer);
+ ip6_fl_gc_timer.function = ip6_fl_gc;
+#ifdef CONFIG_PROC_FS
+ create_proc_read_entry("net/ip6_flowlabel", 0, 0, ip6_fl_read_proc, NULL);
+#endif
+}
+
+void ip6_flowlabel_cleanup()
+{
+ del_timer(&ip6_fl_gc_timer);
+#ifdef CONFIG_PROC_FS
+ remove_proc_entry("net/ip6_flowlabel", 0);
+#endif
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv6/ip6_fw.c b/uClinux-2.4.31-uc0/net/ipv6/ip6_fw.c
new file mode 100644
index 0000000..aa1234e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/ip6_fw.c
@@ -0,0 +1,390 @@
+/*
+ * IPv6 Firewall
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <roque@di.fc.ul.pt>
+ *
+ * $Id: ip6_fw.c,v 1.16 2001/10/31 08:17:58 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/route.h>
+#include <linux/netdevice.h>
+#include <linux/in6.h>
+#include <linux/udp.h>
+#include <linux/init.h>
+
+#include <net/ipv6.h>
+#include <net/ip6_route.h>
+#include <net/ip6_fw.h>
+#include <net/netlink.h>
+
+static unsigned long ip6_fw_rule_cnt;
+static struct ip6_fw_rule ip6_fw_rule_list = {
+ {0},
+ NULL, NULL,
+ {0},
+ IP6_FW_REJECT
+};
+
+static int ip6_fw_accept(struct dst_entry *dst, struct fl_acc_args *args);
+
+struct flow_rule_ops ip6_fw_ops = {
+ ip6_fw_accept
+};
+
+
+static struct rt6_info ip6_fw_null_entry = {
+ {{NULL, 0, 0, NULL,
+ 0, 0, 0, 0, 0, 0, 0, 0, -ENETUNREACH, NULL, NULL,
+ ip6_pkt_discard, ip6_pkt_discard, NULL}},
+ NULL, {{{0}}}, 256, RTF_REJECT|RTF_NONEXTHOP, ~0UL,
+ 0, &ip6_fw_rule_list, {{{{0}}}, 128}, {{{{0}}}, 128}
+};
+
+static struct fib6_node ip6_fw_fib = {
+ NULL, NULL, NULL, NULL,
+ &ip6_fw_null_entry,
+ 0, RTN_ROOT|RTN_TL_ROOT, 0
+};
+
+rwlock_t ip6_fw_lock = RW_LOCK_UNLOCKED;
+
+
+static void ip6_rule_add(struct ip6_fw_rule *rl)
+{
+ struct ip6_fw_rule *next;
+
+ write_lock_bh(&ip6_fw_lock);
+ ip6_fw_rule_cnt++;
+ next = &ip6_fw_rule_list;
+ rl->next = next;
+ rl->prev = next->prev;
+ rl->prev->next = rl;
+ next->prev = rl;
+ write_unlock_bh(&ip6_fw_lock);
+}
+
+static void ip6_rule_del(struct ip6_fw_rule *rl)
+{
+ struct ip6_fw_rule *next, *prev;
+
+ write_lock_bh(&ip6_fw_lock);
+ ip6_fw_rule_cnt--;
+ next = rl->next;
+ prev = rl->prev;
+ next->prev = prev;
+ prev->next = next;
+ write_unlock_bh(&ip6_fw_lock);
+}
+
+static __inline__ struct ip6_fw_rule * ip6_fwrule_alloc(void)
+{
+ struct ip6_fw_rule *rl;
+
+ rl = kmalloc(sizeof(struct ip6_fw_rule), GFP_ATOMIC);
+ if (rl)
+ {
+ memset(rl, 0, sizeof(struct ip6_fw_rule));
+ rl->flowr.ops = &ip6_fw_ops;
+ }
+ return rl;
+}
+
+static __inline__ void ip6_fwrule_free(struct ip6_fw_rule * rl)
+{
+ kfree(rl);
+}
+
+static __inline__ int port_match(int rl_port, int fl_port)
+{
+ int res = 0;
+ if (rl_port == 0 || (rl_port == fl_port))
+ res = 1;
+ return res;
+}
+
+static int ip6_fw_accept_trans(struct ip6_fw_rule *rl,
+ struct fl_acc_args *args)
+{
+ int res = FLOWR_NODECISION;
+ int proto = 0;
+ int sport = 0;
+ int dport = 0;
+
+ switch (args->type) {
+ case FL_ARG_FORWARD:
+ {
+ struct sk_buff *skb = args->fl_u.skb;
+ struct ipv6hdr *hdr = skb->nh.ipv6h;
+ int len;
+
+ len = skb->len - sizeof(struct ipv6hdr);
+
+ proto = hdr->nexthdr;
+
+ switch (proto) {
+ case IPPROTO_TCP:
+ {
+ struct tcphdr *th;
+
+ if (len < sizeof(struct tcphdr)) {
+ res = FLOWR_ERROR;
+ goto out;
+ }
+ th = (struct tcphdr *)(hdr + 1);
+ sport = th->source;
+ dport = th->dest;
+ break;
+ }
+ case IPPROTO_UDP:
+ {
+ struct udphdr *uh;
+
+ if (len < sizeof(struct udphdr)) {
+ res = FLOWR_ERROR;
+ goto out;
+ }
+ uh = (struct udphdr *)(hdr + 1);
+ sport = uh->source;
+ dport = uh->dest;
+ break;
+ }
+ default:
+ goto out;
+ };
+ break;
+ }
+
+ case FL_ARG_ORIGIN:
+ {
+ proto = args->fl_u.fl_o.flow->proto;
+
+ if (proto == IPPROTO_ICMPV6) {
+ goto out;
+ } else {
+ sport = args->fl_u.fl_o.flow->uli_u.ports.sport;
+ dport = args->fl_u.fl_o.flow->uli_u.ports.dport;
+ }
+ break;
+ }
+
+ if (proto == rl->info.proto &&
+ port_match(args->fl_u.fl_o.flow->uli_u.ports.sport, sport) &&
+ port_match(args->fl_u.fl_o.flow->uli_u.ports.dport, dport)) {
+ if (rl->policy & IP6_FW_REJECT)
+ res = FLOWR_SELECT;
+ else
+ res = FLOWR_CLEAR;
+ }
+
+ default:
+#if IP6_FW_DEBUG >= 1
+ printk(KERN_DEBUG "ip6_fw_accept: unknown arg type\n");
+#endif
+ goto out;
+ };
+
+out:
+ return res;
+}
+
+static int ip6_fw_accept(struct dst_entry *dst, struct fl_acc_args *args)
+{
+ struct rt6_info *rt;
+ struct ip6_fw_rule *rl;
+ int proto;
+ int res = FLOWR_NODECISION;
+
+ rt = (struct rt6_info *) dst;
+ rl = (struct ip6_fw_rule *) rt->rt6i_flowr;
+
+ proto = rl->info.proto;
+
+ switch (proto) {
+ case 0:
+ if (rl->policy & IP6_FW_REJECT)
+ res = FLOWR_SELECT;
+ else
+ res = FLOWR_CLEAR;
+ break;
+ case IPPROTO_TCP:
+ case IPPROTO_UDP:
+ res = ip6_fw_accept_trans(rl, args);
+ break;
+ case IPPROTO_ICMPV6:
+ };
+
+ return res;
+}
+
+static struct dst_entry * ip6_fw_dup(struct dst_entry *frule,
+ struct dst_entry *rt,
+ struct fl_acc_args *args)
+{
+ struct ip6_fw_rule *rl;
+ struct rt6_info *nrt;
+ struct rt6_info *frt;
+
+ frt = (struct rt6_info *) frule;
+
+ rl = (struct ip6_fw_rule *) frt->rt6i_flowr;
+
+ nrt = ip6_rt_copy((struct rt6_info *) rt);
+
+ if (nrt) {
+ nrt->u.dst.input = frule->input;
+ nrt->u.dst.output = frule->output;
+
+ nrt->rt6i_flowr = flow_clone(frt->rt6i_flowr);
+
+ nrt->rt6i_flags |= RTF_CACHE;
+ nrt->rt6i_tstamp = jiffies;
+ }
+
+ return (struct dst_entry *) nrt;
+}
+
+int ip6_fw_reject(struct sk_buff *skb)
+{
+#if IP6_FW_DEBUG >= 1
+ printk(KERN_DEBUG "packet rejected: \n");
+#endif
+
+ icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_ADM_PROHIBITED, 0,
+ skb->dev);
+ /*
+ * send it via netlink, as (rule, skb)
+ */
+
+ kfree_skb(skb);
+ return 0;
+}
+
+int ip6_fw_discard(struct sk_buff *skb)
+{
+ printk(KERN_DEBUG "ip6_fw: BUG fw_reject called\n");
+ kfree_skb(skb);
+ return 0;
+}
+
+int ip6_fw_msg_add(struct ip6_fw_msg *msg)
+{
+ struct in6_rtmsg rtmsg;
+ struct ip6_fw_rule *rl;
+ struct rt6_info *rt;
+ int err;
+
+ ipv6_addr_copy(&rtmsg.rtmsg_dst, &msg->dst);
+ ipv6_addr_copy(&rtmsg.rtmsg_src, &msg->src);
+ rtmsg.rtmsg_dst_len = msg->dst_len;
+ rtmsg.rtmsg_src_len = msg->src_len;
+ rtmsg.rtmsg_metric = IP6_RT_PRIO_FW;
+
+ rl = ip6_fwrule_alloc();
+
+ if (rl == NULL)
+ return -ENOMEM;
+
+ rl->policy = msg->policy;
+ rl->info.proto = msg->proto;
+ rl->info.uli_u.data = msg->u.data;
+
+ rtmsg.rtmsg_flags = RTF_NONEXTHOP|RTF_POLICY;
+ err = ip6_route_add(&rtmsg);
+
+ if (err) {
+ ip6_fwrule_free(rl);
+ return err;
+ }
+
+ /* The rest will not work for now. --ABK (989725) */
+
+#ifndef notdef
+ ip6_fwrule_free(rl);
+ return -EPERM;
+#else
+ rt->u.dst.error = -EPERM;
+
+ if (msg->policy == IP6_FW_ACCEPT) {
+ /*
+ * Accept rules are never selected
+ * (i.e. packets use normal forwarding)
+ */
+ rt->u.dst.input = ip6_fw_discard;
+ rt->u.dst.output = ip6_fw_discard;
+ } else {
+ rt->u.dst.input = ip6_fw_reject;
+ rt->u.dst.output = ip6_fw_reject;
+ }
+
+ ip6_rule_add(rl);
+
+ rt->rt6i_flowr = flow_clone((struct flow_rule *)rl);
+
+ return 0;
+#endif
+}
+
+static int ip6_fw_msgrcv(int unit, struct sk_buff *skb)
+{
+ int count = 0;
+
+ while (skb->len) {
+ struct ip6_fw_msg *msg;
+
+ if (skb->len < sizeof(struct ip6_fw_msg)) {
+ count = -EINVAL;
+ break;
+ }
+
+ msg = (struct ip6_fw_msg *) skb->data;
+ skb_pull(skb, sizeof(struct ip6_fw_msg));
+ count += sizeof(struct ip6_fw_msg);
+
+ switch (msg->action) {
+ case IP6_FW_MSG_ADD:
+ ip6_fw_msg_add(msg);
+ break;
+ case IP6_FW_MSG_DEL:
+ break;
+ default:
+ return -EINVAL;
+ };
+ }
+
+ return count;
+}
+
+static void ip6_fw_destroy(struct flow_rule *rl)
+{
+ ip6_fwrule_free((struct ip6_fw_rule *)rl);
+}
+
+#ifdef MODULE
+#define ip6_fw_init module_init
+#endif
+
+void __init ip6_fw_init(void)
+{
+ netlink_attach(NETLINK_IP6_FW, ip6_fw_msgrcv);
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+ netlink_detach(NETLINK_IP6_FW);
+}
+#endif
diff --git a/uClinux-2.4.31-uc0/net/ipv6/ip6_input.c b/uClinux-2.4.31-uc0/net/ipv6/ip6_input.c
new file mode 100644
index 0000000..a61229c
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/ip6_input.c
@@ -0,0 +1,300 @@
+/* $USAGI: ip6_input.c,v 1.22 2003/08/08 13:46:38 yoshfuji Exp $ */
+
+/*
+ * IPv6 input
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ * Ian P. Morris <I.P.Morris@soton.ac.uk>
+ *
+ * $Id: ip6_input.c,v 1.19 2000/12/13 18:31:50 davem Exp $
+ *
+ * Based in linux/net/ipv4/ip_input.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/sched.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/in6.h>
+#include <linux/icmpv6.h>
+
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/transp_v6.h>
+#include <net/rawv6.h>
+#include <net/ndisc.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+
+
+
+static inline int ip6_rcv_finish( struct sk_buff *skb)
+{
+ if (skb->dst == NULL)
+ ip6_route_input(skb);
+
+ return skb->dst->input(skb);
+}
+
+int ipv6_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
+{
+ struct ipv6hdr *hdr;
+ u32 pkt_len;
+ struct inet6_dev *idev = NULL;
+ int saddr_type, daddr_type;
+
+ if (skb->pkt_type == PACKET_OTHERHOST)
+ goto drop;
+
+ idev = in6_dev_get(dev);
+
+ IP6_INC_STATS_BH(idev,Ip6InReceives);
+
+ if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
+ goto out;
+
+ /* Store incoming device index. When the packet will
+ be queued, we cannot refer to skb->dev anymore.
+ */
+ ((struct inet6_skb_parm *)skb->cb)->iif = dev->ifindex;
+
+ if (skb->len < sizeof(struct ipv6hdr))
+ goto err;
+
+ if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
+ goto drop;
+
+ hdr = skb->nh.ipv6h;
+
+ if (hdr->version != 6)
+ goto err;
+
+ saddr_type = ipv6_addr_type(&hdr->saddr);
+ daddr_type = ipv6_addr_type(&hdr->daddr);
+
+ if ((saddr_type & IPV6_ADDR_MULTICAST) ||
+ (daddr_type == IPV6_ADDR_ANY))
+ goto drop; /*XXX*/
+
+ if (((saddr_type & IPV6_ADDR_LOOPBACK) ||
+ (daddr_type & IPV6_ADDR_LOOPBACK)) &&
+ !(dev->flags & IFF_LOOPBACK))
+ goto drop; /*XXX*/
+
+#ifdef CONFIG_IPV6_DROP_FAKE_V4MAPPED
+ if (saddr_type == IPV6_ADDR_MAPPED ||
+ daddr_type == IPV6_ADDR_MAPPED)
+ goto drop; /*XXX*/
+#endif
+
+ pkt_len = ntohs(hdr->payload_len);
+
+ /* pkt_len may be zero if Jumbo payload option is present */
+ if (pkt_len || hdr->nexthdr != NEXTHDR_HOP) {
+ if (pkt_len + sizeof(struct ipv6hdr) > skb->len)
+ goto truncated;
+ if (pkt_len + sizeof(struct ipv6hdr) < skb->len) {
+ if (__pskb_trim(skb, pkt_len + sizeof(struct ipv6hdr)))
+ goto drop;
+ hdr = skb->nh.ipv6h;
+ if (skb->ip_summed == CHECKSUM_HW)
+ skb->ip_summed = CHECKSUM_NONE;
+ }
+ }
+
+ if (hdr->nexthdr == NEXTHDR_HOP) {
+ skb->h.raw = (u8*)(hdr+1);
+ if (ipv6_parse_hopopts(skb, offsetof(struct ipv6hdr, nexthdr)) < 0) {
+ IP6_INC_STATS_BH(idev,Ip6InHdrErrors);
+ if (idev)
+ in6_dev_put(idev);
+ return 0;
+ }
+ hdr = skb->nh.ipv6h;
+ }
+ if (idev)
+ in6_dev_put(idev);
+
+ return NF_HOOK(PF_INET6,NF_IP6_PRE_ROUTING, skb, dev, NULL, ip6_rcv_finish);
+truncated:
+ IP6_INC_STATS_BH(idev,Ip6InTruncatedPkts);
+err:
+ IP6_INC_STATS_BH(idev,Ip6InHdrErrors);
+drop:
+ kfree_skb(skb);
+out:
+ if (idev)
+ in6_dev_put(idev);
+ return 0;
+}
+
+/*
+ * Deliver the packet to the host
+ */
+
+
+static inline int ip6_input_finish(struct sk_buff *skb)
+{
+ struct ipv6hdr *hdr = skb->nh.ipv6h;
+ struct inet6_protocol *ipprot;
+ struct sock *raw_sk;
+ int nhoff;
+ u8 nexthdr;
+ int found = 0;
+ u8 hash;
+
+ skb->h.raw = skb->nh.raw + sizeof(struct ipv6hdr);
+
+ /*
+ * Parse extension headers
+ */
+
+ nexthdr = hdr->nexthdr;
+ nhoff = offsetof(struct ipv6hdr, nexthdr);
+
+ /* Skip hop-by-hop options, they are already parsed. */
+ if (nexthdr == NEXTHDR_HOP) {
+ nhoff = sizeof(struct ipv6hdr);
+ nexthdr = skb->h.raw[0];
+ skb->h.raw += (skb->h.raw[1]+1)<<3;
+ }
+
+ /* This check is sort of optimization.
+ It would be stupid to detect for optional headers,
+ which are missing with probability of 200%
+ */
+ if (nexthdr != IPPROTO_TCP && nexthdr != IPPROTO_UDP) {
+ nexthdr = skb->nh.raw[nhoff];
+ nhoff = ipv6_parse_exthdrs(&skb, nhoff, &nexthdr);
+ if (nhoff < 0)
+ return 0;
+ hdr = skb->nh.ipv6h;
+ }
+
+ if (!pskb_pull(skb, skb->h.raw - skb->data))
+ goto discard;
+
+ if (skb->ip_summed == CHECKSUM_HW)
+ skb->csum = csum_sub(skb->csum,
+ csum_partial(skb->nh.raw, skb->h.raw-skb->nh.raw, 0));
+
+ raw_sk = raw_v6_htable[nexthdr&(MAX_INET_PROTOS-1)];
+ if (raw_sk)
+ raw_sk = ipv6_raw_deliver(skb, nexthdr);
+
+ hash = nexthdr & (MAX_INET_PROTOS - 1);
+ for (ipprot = (struct inet6_protocol *) inet6_protos[hash];
+ ipprot != NULL;
+ ipprot = (struct inet6_protocol *) ipprot->next) {
+ struct sk_buff *buff = skb;
+
+ if (ipprot->protocol != nexthdr)
+ continue;
+
+ if (ipprot->copy || raw_sk)
+ buff = skb_clone(skb, GFP_ATOMIC);
+
+ if (buff)
+ ipprot->handler(buff);
+ found = 1;
+ }
+
+ if (raw_sk) {
+ rawv6_rcv(raw_sk, skb);
+ sock_put(raw_sk);
+ found = 1;
+ }
+
+ /*
+ * not found: send ICMP parameter problem back
+ */
+ if (!found) {
+ struct inet6_dev *idev = in6_dev_get(skb->dev);
+ IP6_INC_STATS_BH(idev,Ip6InUnknownProtos);
+ if (idev)
+ in6_dev_put(idev);
+ icmpv6_param_prob(skb, ICMPV6_UNK_NEXTHDR, nhoff);
+ }
+
+ return 0;
+
+discard:
+ kfree_skb(skb);
+ return 0;
+}
+
+
+int ip6_input(struct sk_buff *skb)
+{
+ return NF_HOOK(PF_INET6,NF_IP6_LOCAL_IN, skb, skb->dev, NULL, ip6_input_finish);
+}
+
+int ip6_mc_input(struct sk_buff *skb)
+{
+ struct ipv6hdr *hdr;
+ int deliver = 0;
+ int discard = 1;
+ struct inet6_dev *idev = in6_dev_get(skb->dev);
+
+ IP6_INC_STATS_BH(idev,Ip6InMcastPkts);
+
+ hdr = skb->nh.ipv6h;
+ if (ipv6_chk_mcast_addr(skb->dev, &hdr->daddr, &hdr->saddr))
+ deliver = 1;
+
+ /*
+ * IPv6 multicast router mode isnt currently supported.
+ */
+#if 0
+ if (ipv6_config.multicast_route) {
+ int addr_type;
+
+ addr_type = ipv6_addr_type(&hdr->daddr);
+
+ if (!(addr_type & (IPV6_ADDR_LOOPBACK | IPV6_ADDR_LINKLOCAL))) {
+ struct sk_buff *skb2;
+ struct dst_entry *dst;
+
+ dst = skb->dst;
+
+ if (deliver) {
+ skb2 = skb_clone(skb, GFP_ATOMIC);
+ } else {
+ discard = 0;
+ skb2 = skb;
+ }
+
+ dst->output(skb2);
+ }
+ }
+#endif
+
+ if (deliver) {
+ discard = 0;
+ ip6_input(skb);
+ }
+
+ if (discard)
+ kfree_skb(skb);
+
+ if (idev)
+ in6_dev_put(idev);
+
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv6/ip6_output.c b/uClinux-2.4.31-uc0/net/ipv6/ip6_output.c
new file mode 100644
index 0000000..a6e07a0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/ip6_output.c
@@ -0,0 +1,945 @@
+/* $USAGI: ip6_output.c,v 1.70 2003/08/08 13:46:38 yoshfuji Exp $ */
+
+/*
+ * IPv6 output functions
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ *
+ * $Id: ip6_output.c,v 1.33 2001/09/20 00:35:35 davem Exp $
+ *
+ * Based on linux/net/ipv4/ip_output.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes:
+ * A.N.Kuznetsov : airthmetics in fragmentation.
+ * extension headers are implemented.
+ * route changes now work.
+ * ip6_forward does not confuse sniffers.
+ * etc.
+ *
+ * H. von Brand : Added missing #include <linux/string.h>
+ * S. Saaristo : Support for setting of traffic class.
+ * Imran Patel : frag id should be in NBO
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/in6.h>
+#include <linux/route.h>
+
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/ndisc.h>
+#include <net/protocol.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+#include <net/rawv6.h>
+#include <net/icmp.h>
+
+static __inline__ void ipv6_select_ident(struct sk_buff *skb, struct frag_hdr *fhdr)
+{
+ static u32 ipv6_fragmentation_id = 1;
+ static spinlock_t ip6_id_lock = SPIN_LOCK_UNLOCKED;
+
+ spin_lock_bh(&ip6_id_lock);
+ fhdr->identification = htonl(ipv6_fragmentation_id);
+ if (++ipv6_fragmentation_id == 0)
+ ipv6_fragmentation_id = 1;
+ spin_unlock_bh(&ip6_id_lock);
+}
+
+static inline int ip6_output_finish(struct sk_buff *skb)
+{
+
+ struct dst_entry *dst = skb->dst;
+ struct hh_cache *hh = dst->hh;
+
+ if (hh) {
+ int hh_alen;
+
+ read_lock_bh(&hh->hh_lock);
+ hh_alen = HH_DATA_ALIGN(hh->hh_len);
+ memcpy(skb->data - hh_alen, hh->hh_data, hh_alen);
+ read_unlock_bh(&hh->hh_lock);
+ skb_push(skb, hh->hh_len);
+ return hh->hh_output(skb);
+ } else if (dst->neighbour)
+ return dst->neighbour->output(skb);
+
+ kfree_skb(skb);
+ return -EINVAL;
+
+}
+
+/* dev_loopback_xmit for use with netfilter. */
+static int ip6_dev_loopback_xmit(struct sk_buff *newskb)
+{
+ newskb->mac.raw = newskb->data;
+ __skb_pull(newskb, newskb->nh.raw - newskb->data);
+ newskb->pkt_type = PACKET_LOOPBACK;
+ newskb->ip_summed = CHECKSUM_UNNECESSARY;
+ BUG_TRAP(newskb->dst);
+
+ netif_rx(newskb);
+ return 0;
+}
+
+
+int ip6_output(struct sk_buff *skb)
+{
+ struct dst_entry *dst = skb->dst;
+ struct net_device *dev = dst->dev;
+
+ skb->protocol = htons(ETH_P_IPV6);
+ skb->dev = dev;
+
+ if (ipv6_addr_is_multicast(&skb->nh.ipv6h->daddr)) {
+ struct inet6_dev *idev;
+
+ if (!(dev->flags&IFF_LOOPBACK) &&
+ (skb->sk == NULL || skb->sk->net_pinfo.af_inet6.mc_loop) &&
+ ipv6_chk_mcast_addr(dev, &skb->nh.ipv6h->daddr,
+ &skb->nh.ipv6h->saddr)) {
+ struct sk_buff *newskb = skb_clone(skb, GFP_ATOMIC);
+
+ /* Do not check for IFF_ALLMULTI; multicast routing
+ is not supported in any case.
+ */
+ if (newskb)
+ NF_HOOK(PF_INET6, NF_IP6_POST_ROUTING, newskb, NULL,
+ newskb->dev,
+ ip6_dev_loopback_xmit);
+
+ if (skb->nh.ipv6h->hop_limit == 0) {
+ kfree_skb(skb);
+ return 0;
+ }
+ }
+
+ idev = in6_dev_get(dev);
+ IP6_INC_STATS(idev,Ip6OutMcastPkts);
+ if (idev)
+ in6_dev_put(idev);
+ }
+
+ return NF_HOOK(PF_INET6, NF_IP6_POST_ROUTING, skb,NULL, skb->dev,ip6_output_finish);
+}
+
+
+#ifdef CONFIG_NETFILTER
+int ip6_route_me_harder(struct sk_buff *skb)
+{
+ struct ipv6hdr *iph = skb->nh.ipv6h;
+ struct dst_entry *dst;
+ struct flowi fl;
+
+ fl.proto = iph->nexthdr;
+ fl.fl6_dst = &iph->daddr;
+ fl.fl6_src = &iph->saddr;
+ fl.oif = skb->sk ? skb->sk->bound_dev_if : 0;
+ fl.fl6_flowlabel = 0;
+ fl.uli_u.ports.dport = 0;
+ fl.uli_u.ports.sport = 0;
+
+ dst = ip6_route_output(skb->sk, &fl);
+
+ if (dst->error) {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "ip6_route_me_harder: No more route.\n");
+ dst_release(dst);
+ return -EINVAL;
+ }
+
+ /* Drop old route. */
+ dst_release(skb->dst);
+
+ skb->dst = dst;
+ return 0;
+}
+#endif
+
+static inline int ip6_maybe_reroute(struct sk_buff *skb)
+{
+#ifdef CONFIG_NETFILTER
+ if (skb->nfcache & NFC_ALTERED){
+ if (ip6_route_me_harder(skb) != 0){
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+ }
+#endif /* CONFIG_NETFILTER */
+ return skb->dst->output(skb);
+}
+
+/*
+ * xmit an sk_buff (used by TCP)
+ * (this does the real work)
+ */
+
+int ip6_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl,
+ struct ipv6_txoptions *opt)
+{
+ struct ipv6_pinfo * np = sk ? &sk->net_pinfo.af_inet6 : NULL;
+ struct in6_addr *first_hop = fl->nl_u.ip6_u.daddr;
+ struct dst_entry *dst = skb->dst;
+ struct ipv6hdr *hdr;
+ u8 proto = fl->proto;
+ int seg_len = skb->len;
+ int hlimit = -1;
+ u8 tclass = 0;
+ int retval = 0;
+
+ if (opt) {
+ int head_room = 0;
+
+ /* First: exthdrs may take lots of space (~8K for now)
+ MAX_HEADER is not enough.
+ */
+ head_room += opt->opt_nflen + opt->opt_flen;
+ seg_len += head_room;
+ head_room += sizeof(struct ipv6hdr) + ((dst->dev->hard_header_len + 15)&~15);
+
+ if (skb_headroom(skb) < head_room) {
+ struct sk_buff *skb2 = skb_realloc_headroom(skb, head_room);
+ kfree_skb(skb);
+ skb = skb2;
+ if (skb == NULL) {
+ printk(KERN_DEBUG "Could not allocate new skb!\n");
+ retval = -ENOBUFS;
+ goto out;
+ }
+ if (sk)
+ skb_set_owner_w(skb, sk);
+ }
+ if (opt->opt_flen)
+ ipv6_push_frag_opts(skb, opt, &proto);
+ if (opt->opt_nflen)
+ ipv6_push_nfrag_opts(skb, opt, &proto, &first_hop);
+ }
+
+ if (np) {
+ hlimit = np->hop_limit;
+ tclass = np->tclass;
+ }
+
+ hdr = skb->nh.ipv6h = (struct ipv6hdr*)skb_push(skb, sizeof(struct ipv6hdr));
+
+ /*
+ * Fill in the IPv6 header
+ */
+
+ if (fl->fl6_flowlabel)
+ *(u32*)hdr = htonl(0x60000000) | fl->fl6_flowlabel;
+ else
+ *(u32*)hdr = htonl(0x60000000 | (tclass << 20));
+
+ if (hlimit < 0)
+ hlimit = ((struct rt6_info*)dst)->rt6i_hoplimit;
+
+ hdr->payload_len = htons(seg_len);
+ hdr->nexthdr = proto;
+ hdr->hop_limit = hlimit;
+
+ ipv6_addr_copy(&hdr->saddr, fl->nl_u.ip6_u.saddr);
+ ipv6_addr_copy(&hdr->daddr, first_hop);
+
+ if (skb->len <= dst->pmtu) {
+ struct inet6_dev *idev = in6_dev_get(dst->dev);
+ IP6_INC_STATS(idev,Ip6OutRequests);
+ if (idev)
+ in6_dev_put(idev);
+ retval = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute);
+ goto out;
+ }
+
+ /* packet is too big */
+ if (net_ratelimit())
+ printk(KERN_DEBUG "IPv6: sending pkt_too_big to self\n");
+ icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, dst->pmtu, &loopback_dev);
+ kfree_skb(skb);
+
+ retval = -EMSGSIZE;
+out:
+ return retval;
+}
+
+/*
+ * To avoid extra problems ND packets are send through this
+ * routine. It's code duplication but I really want to avoid
+ * extra checks since ipv6_build_header is used by TCP (which
+ * is for us performace critical)
+ */
+
+int ip6_nd_hdr(struct sock *sk, struct sk_buff *skb, struct net_device *dev,
+ struct in6_addr *saddr, struct in6_addr *daddr,
+ int proto, int len)
+{
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct ipv6hdr *hdr;
+ int totlen;
+
+ skb->protocol = htons(ETH_P_IPV6);
+ skb->dev = dev;
+
+ totlen = len + sizeof(struct ipv6hdr);
+
+ hdr = (struct ipv6hdr *) skb_put(skb, sizeof(struct ipv6hdr));
+ skb->nh.ipv6h = hdr;
+
+ *(u32*)hdr = htonl(0x60000000 | (np->tclass << 20));
+
+ hdr->payload_len = htons(len);
+ hdr->nexthdr = proto;
+ hdr->hop_limit = np->hop_limit;
+
+ ipv6_addr_copy(&hdr->saddr, saddr);
+ ipv6_addr_copy(&hdr->daddr, daddr);
+
+ return 0;
+}
+
+static struct ipv6hdr * ip6_bld_1(struct sock *sk, struct sk_buff *skb, struct flowi *fl,
+ int hlimit, u8 tclass, unsigned pktlength)
+{
+ struct ipv6hdr *hdr;
+
+ skb->nh.raw = skb_put(skb, sizeof(struct ipv6hdr));
+ hdr = skb->nh.ipv6h;
+
+ if (fl->fl6_flowlabel)
+ *(u32*)hdr = htonl(0x60000000) | fl->fl6_flowlabel;
+ else
+ *(u32*)hdr = htonl(0x60000000 | (tclass << 20));
+
+ hdr->payload_len = htons(pktlength - sizeof(struct ipv6hdr));
+ hdr->hop_limit = hlimit;
+ hdr->nexthdr = fl->proto;
+
+ ipv6_addr_copy(&hdr->saddr, fl->nl_u.ip6_u.saddr);
+ ipv6_addr_copy(&hdr->daddr, fl->nl_u.ip6_u.daddr);
+ return hdr;
+}
+
+static __inline__ u8 * ipv6_build_fraghdr(struct sk_buff *skb, u8* prev_hdr, unsigned offset)
+{
+ struct frag_hdr *fhdr;
+
+ fhdr = (struct frag_hdr *) skb_put(skb, sizeof(struct frag_hdr));
+
+ fhdr->nexthdr = *prev_hdr;
+ *prev_hdr = NEXTHDR_FRAGMENT;
+ prev_hdr = &fhdr->nexthdr;
+
+ fhdr->reserved = 0;
+ fhdr->frag_off = htons(offset);
+ ipv6_select_ident(skb, fhdr);
+ return &fhdr->nexthdr;
+}
+
+static int ip6_frag_xmit(struct sock *sk, inet_getfrag_t getfrag,
+ const void *data, struct dst_entry *dst,
+ struct flowi *fl, struct ipv6_txoptions *opt,
+ struct in6_addr *final_dst,
+ int hlimit, u8 tclass,
+ int flags, unsigned length, int mtu)
+{
+ struct ipv6hdr *hdr;
+ struct sk_buff *last_skb;
+ struct inet6_dev *idev = in6_dev_get(dst->dev);
+ u8 *prev_hdr;
+ int unfrag_len;
+ int frag_len;
+ int last_len;
+ int nfrags;
+ int fhdr_dist;
+ int frag_off;
+ int data_off;
+ int err;
+
+ /*
+ * Fragmentation
+ *
+ * Extension header order:
+ * Hop-by-hop -> Dest0 -> Routing -> Fragment -> Auth -> Dest1 -> rest (...)
+ *
+ * We must build the non-fragmented part that
+ * will be in every packet... this also means
+ * that other extension headers (Dest, Auth, etc)
+ * must be considered in the data to be fragmented
+ */
+
+ unfrag_len = sizeof(struct ipv6hdr) + sizeof(struct frag_hdr);
+ last_len = length;
+
+ if (opt) {
+ unfrag_len += opt->opt_nflen;
+ last_len += opt->opt_flen;
+ }
+
+ /*
+ * Length of fragmented part on every packet but
+ * the last must be an:
+ * "integer multiple of 8 octects".
+ */
+
+ frag_len = (mtu - unfrag_len) & ~0x7;
+
+ /* Unfragmentable part exceeds mtu. */
+ if (frag_len <= 0) {
+ ipv6_local_error(sk, EMSGSIZE, fl, mtu);
+ if (idev)
+ in6_dev_put(idev);
+ return -EMSGSIZE;
+ }
+
+ nfrags = last_len / frag_len;
+
+ /*
+ * We must send from end to start because of
+ * UDP/ICMP checksums. We do a funny trick:
+ * fill the last skb first with the fixed
+ * header (and its data) and then use it
+ * to create the following segments and send it
+ * in the end. If the peer is checking the M_flag
+ * to trigger the reassembly code then this
+ * might be a good idea.
+ */
+
+ frag_off = nfrags * frag_len;
+ last_len -= frag_off;
+
+ if (last_len == 0) {
+ last_len = frag_len;
+ frag_off -= frag_len;
+ nfrags--;
+ }
+ data_off = frag_off;
+
+ /* And it is implementation problem: for now we assume, that
+ all the exthdrs will fit to the first fragment.
+ */
+ if (opt) {
+ if (frag_len < opt->opt_flen) {
+ ipv6_local_error(sk, EMSGSIZE, fl, mtu);
+ if (idev)
+ in6_dev_put(idev);
+ return -EMSGSIZE;
+ }
+ data_off = frag_off - opt->opt_flen;
+ }
+
+ if (flags&MSG_PROBE){
+ if (idev)
+ in6_dev_put(idev);
+ return 0;
+ }
+
+ last_skb = sock_alloc_send_skb(sk, unfrag_len + frag_len +
+ dst->dev->hard_header_len + 15,
+ flags & MSG_DONTWAIT, &err);
+
+ if (last_skb == NULL){
+ if (idev)
+ in6_dev_put(idev);
+ return err;
+ }
+
+ last_skb->dst = dst_clone(dst);
+
+ skb_reserve(last_skb, (dst->dev->hard_header_len + 15) & ~15);
+
+ hdr = ip6_bld_1(sk, last_skb, fl, hlimit, tclass, frag_len+unfrag_len);
+ prev_hdr = &hdr->nexthdr;
+
+ if (opt && opt->opt_nflen)
+ prev_hdr = ipv6_build_nfrag_opts(last_skb, prev_hdr, opt, final_dst, 0);
+
+ prev_hdr = ipv6_build_fraghdr(last_skb, prev_hdr, frag_off);
+ fhdr_dist = prev_hdr - last_skb->data;
+
+ err = getfrag(data, &hdr->saddr, last_skb->tail, data_off, last_len);
+
+ if (!err) {
+ while (nfrags--) {
+ struct sk_buff *skb;
+
+ struct frag_hdr *fhdr2;
+
+ skb = skb_copy(last_skb, sk->allocation);
+
+ if (skb == NULL) {
+ IP6_INC_STATS(idev,Ip6FragFails);
+ if (idev)
+ in6_dev_put(idev);
+ kfree_skb(last_skb);
+ return -ENOMEM;
+ }
+
+ frag_off -= frag_len;
+ data_off -= frag_len;
+
+ fhdr2 = (struct frag_hdr *) (skb->data + fhdr_dist);
+
+ /* more flag on */
+ fhdr2->frag_off = htons(frag_off | 1);
+
+ /* Write fragmentable exthdrs to the first chunk */
+ if (nfrags == 0 && opt && opt->opt_flen) {
+ ipv6_build_frag_opts(skb, &fhdr2->nexthdr, opt);
+ frag_len -= opt->opt_flen;
+ data_off = 0;
+ }
+
+ err = getfrag(data, &hdr->saddr,skb_put(skb, frag_len),
+ data_off, frag_len);
+
+ if (err) {
+ kfree_skb(skb);
+ break;
+ }
+
+ IP6_INC_STATS(idev,Ip6FragCreates);
+ IP6_INC_STATS(idev,Ip6OutRequests);
+ err = NF_HOOK(PF_INET6,NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute);
+ if (err) {
+ if (idev)
+ in6_dev_put(idev);
+ kfree_skb(last_skb);
+ return err;
+ }
+ }
+ }
+
+ if (err) {
+ IP6_INC_STATS(idev,Ip6FragFails);
+ if (idev)
+ in6_dev_put(idev);
+ kfree_skb(last_skb);
+ return -EFAULT;
+ }
+
+ hdr->payload_len = htons(unfrag_len + last_len - sizeof(struct ipv6hdr));
+
+ /*
+ * update last_skb to reflect the getfrag we did
+ * on start.
+ */
+
+ skb_put(last_skb, last_len);
+
+ IP6_INC_STATS(idev,Ip6FragCreates);
+ IP6_INC_STATS(idev,Ip6FragOKs);
+ IP6_INC_STATS(idev,Ip6OutRequests);
+ if (idev)
+ in6_dev_put(idev);
+ return NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, last_skb, NULL,dst->dev, ip6_maybe_reroute);
+}
+
+int ip6_build_xmit(struct sock *sk, inet_getfrag_t getfrag, const void *data,
+ struct flowi *fl, unsigned length,
+ struct ipv6_txoptions *opt, int hlimit, int aux_tclass, int flags)
+{
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct in6_addr *final_dst = NULL;
+ struct dst_entry *dst = NULL;
+ struct rt6_info *rt;
+ struct inet6_dev *idev;
+#ifdef CONFIG_IPV6_SUBTREES
+ int reroute = 0;
+#endif
+ int err = 0;
+ unsigned int pktlength, jumbolen, mtu;
+ struct in6_addr saddr;
+ u8 tclass;
+
+ if (opt && opt->srcrt) {
+ struct rt0_hdr *rt0 = (struct rt0_hdr *) opt->srcrt;
+ final_dst = fl->fl6_dst;
+ fl->fl6_dst = rt0->addr;
+ }
+
+ if (!fl->oif && ipv6_addr_is_multicast(fl->nl_u.ip6_u.daddr))
+ fl->oif = np->mcast_oif;
+ if (dst == NULL)
+ dst = __sk_dst_check(sk, np->dst_cookie);
+ if (dst) {
+ rt = (struct rt6_info*)dst;
+
+ /* Yes, checking route validity in not connected
+ case is not very simple. Take into account,
+ that we do not support routing by source, TOS,
+ and MSG_DONTROUTE --ANK (980726)
+
+ 1. If route was host route, check that
+ cached destination is current.
+ If it is network route, we still may
+ check its validity using saved pointer
+ to the last used address: daddr_cache.
+ We do not want to save whole address now,
+ (because main consumer of this service
+ is tcp, which has not this problem),
+ so that the last trick works only on connected
+ sockets.
+ 2. oif also should be the same.
+ */
+
+ if (((rt->rt6i_dst.plen != 128 ||
+ ipv6_addr_cmp(fl->fl6_dst, &rt->rt6i_dst.addr))
+ && (np->daddr_cache == NULL ||
+ ipv6_addr_cmp(fl->fl6_dst, np->daddr_cache)))
+#ifdef CONFIG_IPV6_SUBTREES
+ || (fl->fl6_src
+ && (!ipv6_addr_any(fl->fl6_src)
+ && (rt->rt6i_src.plen != 128 ||
+ ipv6_addr_cmp(fl->fl6_src, &rt->rt6i_src.addr))
+ && (np->saddr_cache == NULL ||
+ ipv6_addr_cmp(fl->fl6_src, np->saddr_cache))))
+#endif
+ || (fl->oif && fl->oif != dst->dev->ifindex)) {
+ dst = NULL;
+ } else
+ dst_hold(dst);
+ }
+
+ if (dst == NULL)
+ dst = ip6_route_output(sk, fl);
+
+ idev = in6_dev_get(dst->dev);
+
+#ifdef CONFIG_IPV6_SUBTREES
+ reroute = (dst->error == -EAGAIN);
+
+ if (!reroute && dst->error) {
+#else
+ if (dst->error) {
+#endif
+ IP6_INC_STATS(idev,Ip6OutNoRoutes); /*XXX(?)*/
+ if (idev)
+ in6_dev_put(idev);
+ dst_release(dst);
+ return -ENETUNREACH;
+ }
+
+ if (fl->fl6_src == NULL) {
+ err = ipv6_get_saddr(dst, fl->fl6_dst, &saddr, np->use_tempaddr);
+
+ if (err) {
+#if IP6_DEBUG >= 2
+ printk(KERN_DEBUG "ip6_build_xmit: "
+ "no available source address\n");
+#endif
+ goto out;
+ }
+ fl->fl6_src = &saddr;
+#ifdef CONFIG_IPV6_SUBTREES
+ rt = (struct rt6_info*)dst;
+ if (ipv6_addr_cmp(fl->fl6_src, &np->saddr) &&
+ (rt->rt6i_src.plen != 128 ||
+ ipv6_addr_cmp(fl->fl6_src, &rt->rt6i_src.addr))) {
+ dst_release(dst);
+ dst = ip6_route_output(sk, fl);
+ if (dst->error) {
+ IP6_INC_STATS( idev, Ip6OutNoRoutes);
+ if (idev)
+ in6_dev_put(idev);
+ dst_release(dst);
+ return -ENETUNREACH;
+ }
+ }
+#endif
+ }
+
+ pktlength = length;
+
+ if (hlimit < 0) {
+ if (ipv6_addr_is_multicast(fl->fl6_dst))
+ hlimit = np->mcast_hops;
+ else
+ hlimit = np->hop_limit;
+ if (hlimit < 0)
+ hlimit = ((struct rt6_info*)dst)->rt6i_hoplimit;
+ }
+
+ jumbolen = 0;
+
+ if (!sk->protinfo.af_inet.hdrincl) {
+ pktlength += sizeof(struct ipv6hdr);
+ if (opt)
+ pktlength += opt->opt_flen + opt->opt_nflen;
+
+ if (pktlength > 0xFFFF + sizeof(struct ipv6hdr)) {
+ /* Jumbo datagram.
+ It is assumed, that in the case of hdrincl
+ jumbo option is supplied by user.
+ */
+ pktlength += 8;
+ jumbolen = pktlength - sizeof(struct ipv6hdr);
+ }
+ }
+
+ mtu = dst->pmtu;
+ if (np->frag_size < mtu) {
+ if (np->frag_size)
+ mtu = np->frag_size;
+ else if (np->pmtudisc == IPV6_PMTUDISC_DONT)
+ mtu = IPV6_MIN_MTU;
+ }
+
+ tclass = aux_tclass >= 0 ? aux_tclass : np->tclass;
+
+ /* Critical arithmetic overflow check.
+ FIXME: may gcc optimize it out? --ANK (980726)
+ */
+ if (pktlength < length) {
+ ipv6_local_error(sk, EMSGSIZE, fl, mtu);
+ err = -EMSGSIZE;
+ goto out;
+ }
+
+ if (flags&MSG_CONFIRM)
+ dst_confirm(dst);
+
+ if (pktlength <= mtu) {
+ struct sk_buff *skb;
+ struct ipv6hdr *hdr;
+ struct net_device *dev = dst->dev;
+
+ err = 0;
+ if (flags&MSG_PROBE)
+ goto out;
+
+ skb = sock_alloc_send_skb(sk, pktlength + 15 +
+ dev->hard_header_len,
+ flags & MSG_DONTWAIT, &err);
+
+ if (skb == NULL) {
+ IP6_INC_STATS(idev,Ip6OutDiscards);
+ goto out;
+ }
+
+ skb->dst = dst_clone(dst);
+
+ skb_reserve(skb, (dev->hard_header_len + 15) & ~15);
+
+ hdr = (struct ipv6hdr *) skb->tail;
+ skb->nh.ipv6h = hdr;
+
+
+ if (!sk->protinfo.af_inet.hdrincl) {
+ ip6_bld_1(sk, skb, fl, hlimit, tclass,
+ jumbolen ? sizeof(struct ipv6hdr) : pktlength);
+
+ if (opt || jumbolen) {
+ u8 *prev_hdr = &hdr->nexthdr;
+ prev_hdr = ipv6_build_nfrag_opts(skb, prev_hdr, opt, final_dst, jumbolen);
+ if (opt && opt->opt_flen)
+ ipv6_build_frag_opts(skb, prev_hdr, opt);
+ }
+ }
+
+ skb_put(skb, length);
+ err = getfrag(data, &hdr->saddr,
+ ((char *) hdr) + (pktlength - length),
+ 0, length);
+
+ if (!err) {
+ IP6_INC_STATS(idev,Ip6OutRequests);
+ err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, dst->dev, ip6_maybe_reroute);
+ } else {
+ err = -EFAULT;
+ kfree_skb(skb);
+ }
+ } else {
+ if (sk->protinfo.af_inet.hdrincl || jumbolen ||
+ np->pmtudisc == IPV6_PMTUDISC_DO) {
+ ipv6_local_error(sk, EMSGSIZE, fl, mtu);
+ err = -EMSGSIZE;
+ goto out;
+ }
+
+ err = ip6_frag_xmit(sk, getfrag, data, dst, fl, opt, final_dst, hlimit,
+ tclass,
+ flags, length, mtu);
+ }
+
+ /*
+ * cleanup
+ */
+out:
+ if (idev)
+ in6_dev_put(idev);
+ ip6_dst_store(sk, dst,
+ !ipv6_addr_cmp(fl->fl6_dst, &np->daddr) ?
+ &np->daddr : NULL,
+ !ipv6_addr_cmp(fl->fl6_src, &np->saddr) ?
+ &np->saddr : NULL);
+ if (err > 0)
+ err = np->recverr ? net_xmit_errno(err) : 0;
+
+ return err;
+}
+
+int ip6_call_ra_chain(struct sk_buff *skb, int sel)
+{
+ struct ip6_ra_chain *ra;
+ struct sock *last = NULL;
+
+ read_lock(&ip6_ra_lock);
+ for (ra = ip6_ra_chain; ra; ra = ra->next) {
+ struct sock *sk = ra->sk;
+ if (sk && ra->sel == sel) {
+ if (last) {
+ struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
+ if (skb2)
+ rawv6_rcv(last, skb2);
+ }
+ last = sk;
+ }
+ }
+
+ if (last) {
+ rawv6_rcv(last, skb);
+ read_unlock(&ip6_ra_lock);
+ return 1;
+ }
+ read_unlock(&ip6_ra_lock);
+ return 0;
+}
+
+static inline int ip6_forward_finish(struct sk_buff *skb)
+{
+ return skb->dst->output(skb);
+}
+
+int ip6_forward(struct sk_buff *skb)
+{
+ struct dst_entry *dst = skb->dst;
+ struct inet6_dev *idev = in6_dev_get(dst->dev);
+ struct ipv6hdr *hdr = skb->nh.ipv6h;
+ struct inet6_skb_parm *opt =(struct inet6_skb_parm*)skb->cb;
+
+ /* XXX: how about idev_of_input_device->cnf.forwarding? */
+ if (ipv6_devconf.forwarding == 0 && opt->srcrt == 0)
+ goto error;
+
+ skb->ip_summed = CHECKSUM_NONE;
+
+ /*
+ * We DO NOT make any processing on
+ * RA packets, pushing them to user level AS IS
+ * without ane WARRANTY that application will be able
+ * to interpret them. The reason is that we
+ * cannot make anything clever here.
+ *
+ * We are not end-node, so that if packet contains
+ * AH/ESP, we cannot make anything.
+ * Defragmentation also would be mistake, RA packets
+ * cannot be fragmented, because there is no warranty
+ * that different fragments will go along one path. --ANK
+ */
+ if (opt->ra) {
+ u8 *ptr = skb->nh.raw + opt->ra;
+ if (ip6_call_ra_chain(skb, (ptr[2]<<8) + ptr[3])) {
+ if (idev)
+ in6_dev_put(idev);
+ return 0;
+ }
+ }
+
+ /*
+ * check and decrement ttl
+ */
+ if (hdr->hop_limit <= 1) {
+ /* Force OUTPUT device used as source address */
+ skb->dev = dst->dev;
+ icmpv6_send(skb, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT,
+ 0, skb->dev);
+ if (idev)
+ in6_dev_put(idev);
+ kfree_skb(skb);
+ return -ETIMEDOUT;
+ }
+
+ /* IPv6 specs say nothing about it, but it is clear that we cannot
+ send redirects to source routed frames.
+ */
+ if (skb->dev == dst->dev && dst->neighbour && opt->srcrt == 0) {
+ struct in6_addr *target = NULL;
+ struct rt6_info *rt;
+ struct neighbour *n = dst->neighbour;
+
+ /*
+ * incoming and outgoing devices are the same
+ * send a redirect.
+ */
+
+ rt = (struct rt6_info *) dst;
+ if ((rt->rt6i_flags & RTF_GATEWAY))
+ target = (struct in6_addr*)&n->primary_key;
+ else
+ target = &hdr->daddr;
+
+ /* Limit redirects both by destination (here)
+ and by source (inside ndisc_send_redirect)
+ */
+ if (xrlim_allow(dst, 1*HZ))
+ ndisc_send_redirect(skb, n, target);
+ } else if (ipv6_addr_type(&hdr->saddr)&(IPV6_ADDR_MULTICAST|IPV6_ADDR_LOOPBACK
+ |IPV6_ADDR_LINKLOCAL)) {
+ /* This check is security critical. */
+ goto error;
+ }
+
+ if (skb->len > dst->pmtu) {
+ /* Again, force OUTPUT device used as source address */
+ skb->dev = dst->dev;
+ if (idev)
+ in6_dev_put(idev);
+ idev = in6_dev_get(skb->dev);
+ icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, dst->pmtu, skb->dev);
+ IP6_INC_STATS_BH(idev,Ip6InTooBigErrors);
+ if (idev)
+ in6_dev_put(idev);
+ kfree_skb(skb);
+ return -EMSGSIZE;
+ }
+
+ if (skb_cow(skb, dst->dev->hard_header_len))
+ goto drop;
+
+ hdr = skb->nh.ipv6h;
+
+ /* Mangling hops number delayed to point after skb COW */
+
+ hdr->hop_limit--;
+
+ IP6_INC_STATS_BH(idev,Ip6OutForwDatagrams);
+ if (idev)
+ in6_dev_put(idev);
+ return NF_HOOK(PF_INET6,NF_IP6_FORWARD, skb, skb->dev, dst->dev, ip6_forward_finish);
+
+error:
+ IP6_INC_STATS_BH(idev,Ip6InAddrErrors);
+drop:
+ if (idev)
+ in6_dev_put(idev);
+ kfree_skb(skb);
+ return -EINVAL;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv6/ipv6_sockglue.c b/uClinux-2.4.31-uc0/net/ipv6/ipv6_sockglue.c
new file mode 100644
index 0000000..530eda9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/ipv6_sockglue.c
@@ -0,0 +1,825 @@
+/* $USAGI: ipv6_sockglue.c,v 1.40 2003/08/08 13:46:38 yoshfuji Exp $ */
+
+/*
+ * IPv6 BSD socket options interface
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ *
+ * Based on linux/net/ipv4/ip_sockglue.c
+ *
+ * $Id: ipv6_sockglue.c,v 1.40 2001/09/18 22:29:10 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * FIXME: Make the setsockopt code POSIX compliant: That is
+ *
+ * o Return an optlen of the truncated length if need be
+ *
+ * Changes:
+ * David L Stevens <dlstevens@us.ibm.com>:
+ * - added multicast source filtering API for MLDv2
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/sched.h>
+#include <linux/net.h>
+#include <linux/in6.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/init.h>
+#include <linux/sysctl.h>
+#include <linux/netfilter.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+#include <net/ipv6.h>
+#include <net/ndisc.h>
+#include <net/protocol.h>
+#include <net/transp_v6.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+#include <net/inet_common.h>
+#include <net/tcp.h>
+#include <net/udp.h>
+
+#include <asm/uaccess.h>
+
+struct ipv6_mib ipv6_statistics[NR_CPUS*2];
+
+struct packet_type ipv6_packet_type =
+{
+ __constant_htons(ETH_P_IPV6),
+ NULL, /* All devices */
+ ipv6_rcv,
+ (void*)1,
+ NULL
+};
+
+/*
+ * addrconf module should be notifyed of a device going up
+ */
+static struct notifier_block ipv6_dev_notf = {
+ addrconf_notify,
+ NULL,
+ 0
+};
+
+struct ip6_ra_chain *ip6_ra_chain;
+rwlock_t ip6_ra_lock = RW_LOCK_UNLOCKED;
+
+int ip6_ra_control(struct sock *sk, int sel, void (*destructor)(struct sock *))
+{
+ struct ip6_ra_chain *ra, *new_ra, **rap;
+
+ /* RA packet may be delivered ONLY to IPPROTO_RAW socket */
+ if (sk->type != SOCK_RAW || sk->num != IPPROTO_RAW)
+ return -EINVAL;
+
+ new_ra = (sel>=0) ? kmalloc(sizeof(*new_ra), GFP_KERNEL) : NULL;
+
+ write_lock_bh(&ip6_ra_lock);
+ for (rap = &ip6_ra_chain; (ra=*rap) != NULL; rap = &ra->next) {
+ if (ra->sk == sk) {
+ if (sel>=0) {
+ write_unlock_bh(&ip6_ra_lock);
+ if (new_ra)
+ kfree(new_ra);
+ return -EADDRINUSE;
+ }
+
+ *rap = ra->next;
+ write_unlock_bh(&ip6_ra_lock);
+
+ if (ra->destructor)
+ ra->destructor(sk);
+ sock_put(sk);
+ kfree(ra);
+ return 0;
+ }
+ }
+ if (new_ra == NULL) {
+ write_unlock_bh(&ip6_ra_lock);
+ return -ENOBUFS;
+ }
+ new_ra->sk = sk;
+ new_ra->sel = sel;
+ new_ra->destructor = destructor;
+ new_ra->next = ra;
+ *rap = new_ra;
+ sock_hold(sk);
+ write_unlock_bh(&ip6_ra_lock);
+ return 0;
+}
+
+extern int ip6_mc_source(int add, int omode, struct sock *sk,
+ struct group_source_req *pgsr);
+extern int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf);
+extern int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf,
+ struct group_filter *optval, int *optlen);
+
+
+int ipv6_setsockopt(struct sock *sk, int level, int optname, char *optval,
+ int optlen)
+{
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ int val, valbool;
+ int retv = -ENOPROTOOPT;
+
+ if(level==SOL_IP && sk->type != SOCK_RAW)
+ return udp_prot.setsockopt(sk, level, optname, optval, optlen);
+
+ if(level!=SOL_IPV6)
+ goto out;
+
+ if (optlen < 0)
+ goto e_inval;
+
+ if (optval == NULL)
+ val=0;
+ else if (get_user(val, (int *) optval) && get_user(val, (u8 *) optval))
+ return -EFAULT;
+
+ valbool = (val!=0);
+
+ lock_sock(sk);
+
+ switch (optname) {
+
+ /* XXX: IPV6_ADDRFORM is deprecated */
+ case IPV6_ADDRFORM:
+ if (val == PF_INET) {
+ struct ipv6_txoptions *opt;
+ struct sk_buff *pktopt;
+
+ if (sk->protocol != IPPROTO_UDP &&
+ sk->protocol != IPPROTO_TCP)
+ break;
+
+ if (sk->state != TCP_ESTABLISHED) {
+ retv = -ENOTCONN;
+ break;
+ }
+
+ if (ipv6_only_sock(sk) ||
+ !(ipv6_addr_type(&np->daddr) & IPV6_ADDR_MAPPED)) {
+ retv = -EADDRNOTAVAIL;
+ break;
+ }
+
+ fl6_free_socklist(sk);
+ ipv6_sock_mc_close(sk);
+
+ if (sk->protocol == IPPROTO_TCP) {
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ local_bh_disable();
+ sock_prot_dec_use(sk->prot);
+ sock_prot_inc_use(&tcp_prot);
+ local_bh_enable();
+ sk->prot = &tcp_prot;
+ tp->af_specific = &ipv4_specific;
+ sk->socket->ops = &inet_stream_ops;
+ sk->family = PF_INET;
+ tcp_sync_mss(sk, tp->pmtu_cookie);
+ } else {
+ local_bh_disable();
+ sock_prot_dec_use(sk->prot);
+ sock_prot_inc_use(&udp_prot);
+ local_bh_enable();
+ sk->prot = &udp_prot;
+ sk->socket->ops = &inet_dgram_ops;
+ sk->family = PF_INET;
+ }
+ opt = xchg(&np->opt, NULL);
+ if (opt)
+ sock_kfree_s(sk, opt, opt->tot_len);
+ pktopt = xchg(&np->pktoptions, NULL);
+ if (pktopt)
+ kfree_skb(pktopt);
+
+ sk->destruct = inet_sock_destruct;
+#ifdef INET_REFCNT_DEBUG
+ atomic_dec(&inet6_sock_nr);
+#endif
+ MOD_DEC_USE_COUNT;
+ retv = 0;
+ break;
+ }
+ goto e_inval;
+
+ case IPV6_V6ONLY:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ if (sk->userlocks&SOCK_BINDADDR_LOCK)
+ goto e_inval;
+ if (sk->num)
+ goto e_inval;
+ np->ipv6only = valbool;
+ retv = 0;
+ break;
+
+ case IPV6_PKTINFO:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ np->rxopt.bits.rxinfo = valbool;
+ retv = 0;
+ break;
+
+ case IPV6_HOPLIMIT:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ np->rxopt.bits.rxhlim = valbool;
+ retv = 0;
+ break;
+
+ case IPV6_RTHDR:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ if (val < 0 || val > 2)
+ goto e_inval;
+ np->rxopt.bits.srcrt = val;
+ retv = 0;
+ break;
+
+ case IPV6_HOPOPTS:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ np->rxopt.bits.hopopts = valbool;
+ retv = 0;
+ break;
+
+ case IPV6_AUTHHDR:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ np->rxopt.bits.authhdr = valbool;
+ retv = 0;
+ break;
+
+ case IPV6_DSTOPTS:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ np->rxopt.bits.dstopts = valbool;
+ retv = 0;
+ break;
+
+ case IPV6_FLOWINFO:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ np->rxopt.bits.rxflow = valbool;
+ retv = 0;
+ break;
+
+ case IPV6_PKTOPTIONS:
+ {
+ struct ipv6_txoptions *opt = NULL;
+ struct msghdr msg;
+ struct flowi fl;
+ int junk;
+
+ fl.fl6_flowlabel = 0;
+ fl.oif = sk->bound_dev_if;
+
+ if (!optval || optlen == 0)
+ goto update;
+
+ /* 1K is probably excessive
+ * 1K is surely not enough, 2K per standard header is 16K.
+ */
+ retv = -EINVAL;
+ if (optlen > 64*1024)
+ break;
+
+ opt = sock_kmalloc(sk, sizeof(*opt) + optlen, GFP_KERNEL);
+ retv = -ENOBUFS;
+ if (opt == NULL)
+ break;
+
+ memset(opt, 0, sizeof(*opt));
+ opt->tot_len = sizeof(*opt) + optlen;
+ retv = -EFAULT;
+ if (copy_from_user(opt+1, optval, optlen))
+ goto done;
+
+ msg.msg_controllen = optlen;
+ msg.msg_control = (void*)(opt+1);
+
+ retv = datagram_send_ctl(&msg, &fl, opt, &junk, &junk);
+ if (retv)
+ goto done;
+update:
+ retv = 0;
+ if (sk->type == SOCK_STREAM) {
+ if (opt) {
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ if (!((1<<sk->state)&(TCPF_LISTEN|TCPF_CLOSE))
+ && sk->daddr != LOOPBACK4_IPV6) {
+ tp->ext_header_len = opt->opt_flen + opt->opt_nflen;
+ tcp_sync_mss(sk, tp->pmtu_cookie);
+ }
+ }
+ opt = xchg(&np->opt, opt);
+ sk_dst_reset(sk);
+ } else {
+ write_lock(&sk->dst_lock);
+ opt = xchg(&np->opt, opt);
+ write_unlock(&sk->dst_lock);
+ sk_dst_reset(sk);
+ }
+
+done:
+ if (opt)
+ sock_kfree_s(sk, opt, opt->tot_len);
+ break;
+ }
+ case IPV6_UNICAST_HOPS:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ if (val > 255 || val < -1)
+ goto e_inval;
+ np->hop_limit = val;
+ retv = 0;
+ break;
+
+ case IPV6_MULTICAST_HOPS:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ if (sk->type == SOCK_STREAM)
+ goto e_inval;
+ if (val > 255 || val < -1)
+ goto e_inval;
+ np->mcast_hops = val;
+ retv = 0;
+ break;
+
+ case IPV6_MULTICAST_LOOP:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ if (val != 0 && val != 1)
+ goto e_inval;
+ np->mc_loop = valbool;
+ retv = 0;
+ break;
+
+ case IPV6_MULTICAST_IF:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ if (sk->type == SOCK_STREAM)
+ goto e_inval;
+ if (sk->bound_dev_if && sk->bound_dev_if != val)
+ goto e_inval;
+
+ if (__dev_get_by_index(val) == NULL) {
+ retv = -ENODEV;
+ break;
+ }
+ np->mcast_oif = val;
+ retv = 0;
+ break;
+ case IPV6_JOIN_GROUP:
+ case IPV6_LEAVE_GROUP:
+ {
+ struct ipv6_mreq mreq;
+
+ if (optlen != sizeof(struct ipv6_mreq))
+ goto e_inval;
+
+ retv = -EFAULT;
+ if (copy_from_user(&mreq, optval, sizeof(struct ipv6_mreq)))
+ break;
+
+ if (optname == IPV6_JOIN_GROUP)
+ retv = ipv6_sock_mc_join(sk, mreq.ipv6mr_ifindex, &mreq.ipv6mr_multiaddr);
+ else
+ retv = ipv6_sock_mc_drop(sk, mreq.ipv6mr_ifindex, &mreq.ipv6mr_multiaddr);
+ break;
+ }
+ case IPV6_JOIN_ANYCAST:
+ case IPV6_LEAVE_ANYCAST:
+ {
+ struct ipv6_mreq mreq;
+
+ if (optlen != sizeof(struct ipv6_mreq))
+ goto e_inval;
+
+ retv = -EFAULT;
+ if (copy_from_user(&mreq, optval, sizeof(struct ipv6_mreq)))
+ break;
+
+ if (optname == IPV6_JOIN_ANYCAST)
+ retv = ipv6_sock_ac_join(sk, mreq.ipv6mr_ifindex, &mreq.ipv6mr_acaddr);
+ else
+ retv = ipv6_sock_ac_drop(sk, mreq.ipv6mr_ifindex, &mreq.ipv6mr_acaddr);
+ break;
+ }
+ case MCAST_JOIN_GROUP:
+ case MCAST_LEAVE_GROUP:
+ {
+ struct group_req greq;
+ struct sockaddr_in6 *psin6;
+
+ retv = -EFAULT;
+ if (copy_from_user(&greq, optval, sizeof(struct group_req)))
+ break;
+ if (greq.gr_group.ss_family != AF_INET6) {
+ retv = -EADDRNOTAVAIL;
+ break;
+ }
+ psin6 = (struct sockaddr_in6 *)&greq.gr_group;
+ if (optname == MCAST_JOIN_GROUP)
+ retv = ipv6_sock_mc_join(sk, greq.gr_interface,
+ &psin6->sin6_addr);
+ else
+ retv = ipv6_sock_mc_drop(sk, greq.gr_interface,
+ &psin6->sin6_addr);
+ break;
+ }
+ case MCAST_JOIN_SOURCE_GROUP:
+ case MCAST_LEAVE_SOURCE_GROUP:
+ case MCAST_BLOCK_SOURCE:
+ case MCAST_UNBLOCK_SOURCE:
+ {
+ struct group_source_req greqs;
+ int omode, add;
+
+ if (optlen != sizeof(struct group_source_req))
+ goto e_inval;
+ if (copy_from_user(&greqs, optval, sizeof(greqs))) {
+ retv = -EFAULT;
+ break;
+ }
+ if (greqs.gsr_group.ss_family != AF_INET6 ||
+ greqs.gsr_source.ss_family != AF_INET6) {
+ retv = -EADDRNOTAVAIL;
+ break;
+ }
+ if (optname == MCAST_BLOCK_SOURCE) {
+ omode = MCAST_EXCLUDE;
+ add = 1;
+ } else if (optname == MCAST_UNBLOCK_SOURCE) {
+ omode = MCAST_EXCLUDE;
+ add = 0;
+ } else if (optname == MCAST_JOIN_SOURCE_GROUP) {
+ struct sockaddr_in6 *psin6;
+
+ psin6 = (struct sockaddr_in6 *)&greqs.gsr_group;
+ retv = ipv6_sock_mc_join(sk, greqs.gsr_interface,
+ &psin6->sin6_addr);
+ if (retv)
+ break;
+ omode = MCAST_INCLUDE;
+ add = 1;
+ } else /*IP_DROP_SOURCE_MEMBERSHIP */ {
+ omode = MCAST_INCLUDE;
+ add = 0;
+ }
+ retv = ip6_mc_source(add, omode, sk, &greqs);
+ break;
+ }
+ case MCAST_MSFILTER:
+ {
+ extern int sysctl_optmem_max;
+ extern int sysctl_mld_max_msf;
+ struct group_filter *gsf;
+
+ if (optlen < GROUP_FILTER_SIZE(0))
+ goto e_inval;
+ if (optlen > sysctl_optmem_max) {
+ retv = -ENOBUFS;
+ break;
+ }
+ gsf = (struct group_filter *)kmalloc(optlen,GFP_KERNEL);
+ if (gsf == 0) {
+ retv = -ENOBUFS;
+ break;
+ }
+ retv = -EFAULT;
+ if (copy_from_user(gsf, optval, optlen)) {
+ kfree(gsf);
+ break;
+ }
+ /* numsrc >= (4G-140)/128 overflow in 32 bits */
+ if (gsf->gf_numsrc >= 0x1ffffffU ||
+ gsf->gf_numsrc > sysctl_mld_max_msf) {
+ kfree(gsf);
+ retv = -ENOBUFS;
+ break;
+ }
+ if (GROUP_FILTER_SIZE(gsf->gf_numsrc) > optlen) {
+ kfree(gsf);
+ retv = -EINVAL;
+ break;
+ }
+ retv = ip6_mc_msfilter(sk, gsf);
+ kfree(gsf);
+
+ break;
+ }
+ case IPV6_ROUTER_ALERT:
+ retv = ip6_ra_control(sk, val, NULL);
+ break;
+ case IPV6_MTU_DISCOVER:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ if (val<0 || val>2)
+ goto e_inval;
+ np->pmtudisc = val;
+ retv = 0;
+ break;
+ case IPV6_MTU:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ if (val && val < IPV6_MIN_MTU)
+ goto e_inval;
+ np->frag_size = val;
+ retv = 0;
+ break;
+ case IPV6_RECVERR:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ np->recverr = valbool;
+ if (!val)
+ skb_queue_purge(&sk->error_queue);
+ retv = 0;
+ break;
+ case IPV6_FLOWINFO_SEND:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ np->sndflow = valbool;
+ retv = 0;
+ break;
+ case IPV6_FLOWLABEL_MGR:
+ retv = ipv6_flowlabel_opt(sk, optval, optlen);
+ break;
+
+ case IPV6_RECVTCLASS:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ np->recvopt.tclass = valbool;
+ retv = 0;
+ break;
+
+ case IPV6_TCLASS:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ np->tclass = val;
+ retv = 0;
+ break;
+
+#ifdef CONFIG_IPV6_PRIVACY
+ case IPV6_PRIVACY:
+ if (optlen != sizeof(int))
+ goto e_inval;
+ if (val > 0)
+ np->use_tempaddr = 1;
+ else if (val < 0)
+ np->use_tempaddr = -1;
+ else
+ np->use_tempaddr = 0;
+ retv = 0;
+ break;
+#endif
+
+#ifdef CONFIG_NETFILTER
+ default:
+ retv = nf_setsockopt(sk, PF_INET6, optname, optval,
+ optlen);
+ break;
+#endif
+
+ }
+ release_sock(sk);
+
+out:
+ return retv;
+
+e_inval:
+ release_sock(sk);
+ return -EINVAL;
+}
+
+int ipv6_getsockopt(struct sock *sk, int level, int optname, char *optval,
+ int *optlen)
+{
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ int len;
+ int val;
+
+ if(level==SOL_IP && sk->type != SOCK_RAW)
+ return udp_prot.getsockopt(sk, level, optname, optval, optlen);
+ if(level!=SOL_IPV6)
+ return -ENOPROTOOPT;
+ if (get_user(len, optlen))
+ return -EFAULT;
+ if (optlen < 0)
+ return -EINVAL;
+ switch (optname) {
+ case IPV6_PKTOPTIONS:
+ {
+ struct msghdr msg;
+ struct sk_buff *skb;
+
+ if (sk->type != SOCK_STREAM)
+ return -ENOPROTOOPT;
+
+ msg.msg_control = optval;
+ msg.msg_controllen = len;
+ msg.msg_flags = 0;
+
+ lock_sock(sk);
+ skb = np->pktoptions;
+ if (skb)
+ atomic_inc(&skb->users);
+ release_sock(sk);
+
+ if (skb) {
+ int err = datagram_recv_ctl(sk, &msg, skb);
+ kfree_skb(skb);
+ if (err)
+ return err;
+ } else {
+ if (np->rxopt.bits.rxinfo) {
+ struct in6_pktinfo src_info;
+ src_info.ipi6_ifindex = np->mcast_oif;
+ ipv6_addr_copy(&src_info.ipi6_addr, &np->daddr);
+ put_cmsg(&msg, SOL_IPV6, IPV6_PKTINFO, sizeof(src_info), &src_info);
+ }
+ if (np->rxopt.bits.rxhlim) {
+ int hlim = np->mcast_hops;
+ put_cmsg(&msg, SOL_IPV6, IPV6_HOPLIMIT, sizeof(hlim), &hlim);
+ }
+ }
+ len -= msg.msg_controllen;
+ return put_user(len, optlen);
+ }
+
+ case IPV6_MTU:
+ {
+ struct dst_entry *dst;
+ val = 0;
+ lock_sock(sk);
+ dst = sk_dst_get(sk);
+ if (dst) {
+ val = dst->pmtu;
+ dst_release(dst);
+ }
+ release_sock(sk);
+ if (!val)
+ return -ENOTCONN;
+ break;
+ }
+
+ case IPV6_V6ONLY:
+ val = np->ipv6only;
+ break;
+
+ case IPV6_PKTINFO:
+ val = np->rxopt.bits.rxinfo;
+ break;
+ case MCAST_MSFILTER:
+ {
+ struct group_filter gsf;
+ int err;
+
+ if (len < GROUP_FILTER_SIZE(0))
+ return -EINVAL;
+ if (copy_from_user(&gsf, optval, GROUP_FILTER_SIZE(0)))
+ return -EFAULT;
+ lock_sock(sk);
+ err = ip6_mc_msfget(sk, &gsf,
+ (struct group_filter *)optval, optlen);
+ release_sock(sk);
+ return err;
+ }
+
+ case IPV6_HOPLIMIT:
+ val = np->rxopt.bits.rxhlim;
+ break;
+
+ case IPV6_RTHDR:
+ val = np->rxopt.bits.srcrt;
+ break;
+
+ case IPV6_HOPOPTS:
+ val = np->rxopt.bits.hopopts;
+ break;
+
+ case IPV6_AUTHHDR:
+ val = np->rxopt.bits.authhdr;
+ break;
+
+ case IPV6_DSTOPTS:
+ val = np->rxopt.bits.dstopts;
+ break;
+
+ case IPV6_FLOWINFO:
+ val = np->rxopt.bits.rxflow;
+ break;
+
+ case IPV6_UNICAST_HOPS:
+ val = np->hop_limit;
+ break;
+
+ case IPV6_MULTICAST_HOPS:
+ val = np->mcast_hops;
+ break;
+
+ case IPV6_MULTICAST_LOOP:
+ val = np->mc_loop;
+ break;
+
+ case IPV6_MULTICAST_IF:
+ val = np->mcast_oif;
+ break;
+
+ case IPV6_MTU_DISCOVER:
+ val = np->pmtudisc;
+ break;
+
+ case IPV6_RECVERR:
+ val = np->recverr;
+ break;
+
+ case IPV6_FLOWINFO_SEND:
+ val = np->sndflow;
+ break;
+
+ case IPV6_JOIN_GROUP:
+ case IPV6_LEAVE_GROUP:
+ return -EOPNOTSUPP;
+
+ case IPV6_RECVTCLASS:
+ val = np->recvopt.tclass;
+ break;
+
+ case IPV6_TCLASS:
+ val = np->tclass;
+ break;
+
+#ifdef CONFIG_IPV6_PRIVACY
+ case IPV6_PRIVACY:
+ val = np->use_tempaddr;
+ break;
+#endif
+
+ default:
+#ifdef CONFIG_NETFILTER
+ lock_sock(sk);
+ val = nf_getsockopt(sk, PF_INET6, optname, optval,
+ &len);
+ release_sock(sk);
+ if (val >= 0)
+ val = put_user(len, optlen);
+ return val;
+#else
+ return -EINVAL;
+#endif
+ }
+ len = min_t(unsigned int, sizeof(int), len);
+ if(put_user(len, optlen))
+ return -EFAULT;
+ if(copy_to_user(optval,&val,len))
+ return -EFAULT;
+ return 0;
+}
+
+#if defined(MODULE) && defined(CONFIG_SYSCTL)
+
+/*
+ * sysctl registration functions defined in sysctl_net_ipv6.c
+ */
+
+extern void ipv6_sysctl_register(void);
+extern void ipv6_sysctl_unregister(void);
+#endif
+
+void __init ipv6_packet_init(void)
+{
+ dev_add_pack(&ipv6_packet_type);
+}
+
+void __init ipv6_netdev_notif_init(void)
+{
+ register_netdevice_notifier(&ipv6_dev_notf);
+}
+
+#ifdef MODULE
+void ipv6_packet_cleanup(void)
+{
+ dev_remove_pack(&ipv6_packet_type);
+}
+
+void ipv6_netdev_notif_cleanup(void)
+{
+ unregister_netdevice_notifier(&ipv6_dev_notf);
+}
+#endif
diff --git a/uClinux-2.4.31-uc0/net/ipv6/ipv6_syms.c b/uClinux-2.4.31-uc0/net/ipv6/ipv6_syms.c
new file mode 100644
index 0000000..ebc31c4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/ipv6_syms.c
@@ -0,0 +1,48 @@
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <net/protocol.h>
+#include <net/ipv6.h>
+#include <net/addrconf.h>
+#include <net/ip6_route.h>
+#include <linux/inet.h>
+
+EXPORT_SYMBOL(__ipv6_addr_type);
+EXPORT_SYMBOL(icmpv6_send);
+EXPORT_SYMBOL(icmpv6_statistics);
+EXPORT_SYMBOL(icmpv6_err_convert);
+EXPORT_SYMBOL(ndisc_mc_map);
+EXPORT_SYMBOL(register_inet6addr_notifier);
+EXPORT_SYMBOL(unregister_inet6addr_notifier);
+EXPORT_SYMBOL(ip6_build_xmit);
+EXPORT_SYMBOL(ip6_route_output);
+#ifdef CONFIG_NETFILTER
+EXPORT_SYMBOL(ip6_route_me_harder);
+EXPORT_SYMBOL(ipv6_ext_hdr);
+#endif
+EXPORT_SYMBOL(addrconf_lock);
+EXPORT_SYMBOL(ipv6_setsockopt);
+EXPORT_SYMBOL(ipv6_getsockopt);
+EXPORT_SYMBOL(inet6_register_protosw);
+EXPORT_SYMBOL(inet6_unregister_protosw);
+EXPORT_SYMBOL(inet6_add_protocol);
+EXPORT_SYMBOL(inet6_del_protocol);
+EXPORT_SYMBOL(ip6_xmit);
+EXPORT_SYMBOL(inet6_release);
+EXPORT_SYMBOL(inet6_bind);
+EXPORT_SYMBOL(inet6_getname);
+EXPORT_SYMBOL(inet6_ioctl);
+EXPORT_SYMBOL(ipv6_get_saddr);
+EXPORT_SYMBOL(ipv6_chk_addr);
+EXPORT_SYMBOL(inet6_ifa_finish_destroy);
+EXPORT_SYMBOL(ipv6_get_ifaddr);
+EXPORT_SYMBOL(in6_dev_finish_destroy);
+EXPORT_SYMBOL(ipv6_skip_exthdr);
+EXPORT_SYMBOL(fl6_sock_lookup);
+EXPORT_SYMBOL(rt6_lookup);
+
+#ifdef CONFIG_IPV6
+EXPORT_SYMBOL(in6_ntop);
+#endif
+
diff --git a/uClinux-2.4.31-uc0/net/ipv6/ipv6_tunnel.c b/uClinux-2.4.31-uc0/net/ipv6/ipv6_tunnel.c
new file mode 100644
index 0000000..0b18634
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/ipv6_tunnel.c
@@ -0,0 +1,1898 @@
+/*
+ * IPv6 over IPv6 tunnel device
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Ville Nuorvala <vnuorval@tml.hut.fi>
+ *
+ * $Id: s.ipv6_tunnel.c 1.23 02/11/29 15:33:51+02:00 vnuorval@amber.hut.mediapoli.com $
+ *
+ * Based on:
+ * linux/net/ipv6/sit.c
+ *
+ * RFC 2473
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#define __NO_VERSION__
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/if.h>
+#include <linux/in.h>
+#include <linux/ip.h>
+#include <linux/if_tunnel.h>
+#include <linux/net.h>
+#include <linux/in6.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/icmpv6.h>
+#include <linux/init.h>
+#include <linux/route.h>
+#include <linux/rtnetlink.h>
+#include <linux/tqueue.h>
+
+#include <asm/uaccess.h>
+#include <asm/atomic.h>
+
+#include <net/sock.h>
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+#include <net/ipv6_tunnel.h>
+
+#ifdef MODULE
+MODULE_AUTHOR("Ville Nuorvala");
+MODULE_DESCRIPTION("IPv6 over IPv6 tunnel");
+MODULE_LICENSE("GPL");
+#endif
+
+#ifdef IPV6_TNL_DEBUG
+#define IPV6_TNL_TRACE(x...) printk(KERN_DEBUG __FUNCTION__ x "\n")
+#else
+#define IPV6_TNL_TRACE(x...) do {;} while(0)
+#endif
+
+/* socket used by ipv6_ipv6_tnl_xmit() for resending packets */
+static struct socket *ipv6_socket;
+
+/* current holder of socket */
+static int ipv6_xmit_holder = -1;
+
+/* socket locking functions copied from net/ipv6/icmp.c */
+
+static int
+ipv6_xmit_lock_bh(void)
+{
+ if (!spin_trylock(&ipv6_socket->sk->lock.slock)) {
+ if (ipv6_xmit_holder == smp_processor_id())
+ return -EAGAIN;
+ spin_lock(&ipv6_socket->sk->lock.slock);
+ }
+ ipv6_xmit_holder = smp_processor_id();
+ return 0;
+}
+
+static inline int
+ipv6_xmit_lock(void)
+{
+ int ret;
+ local_bh_disable();
+ ret = ipv6_xmit_lock_bh();
+ if (ret)
+ local_bh_enable();
+ return ret;
+}
+
+static void
+ipv6_xmit_unlock_bh(void)
+{
+ ipv6_xmit_holder = -1;
+ spin_unlock(&ipv6_socket->sk->lock.slock);
+}
+
+static inline void
+ipv6_xmit_unlock(void)
+{
+ ipv6_xmit_unlock_bh();
+ local_bh_enable();
+}
+
+#define HASH_SIZE 32
+
+#define HASH(addr) (((addr)->s6_addr16[0] ^ (addr)->s6_addr16[1] ^ \
+ (addr)->s6_addr16[2] ^ (addr)->s6_addr16[3] ^ \
+ (addr)->s6_addr16[4] ^ (addr)->s6_addr16[5] ^ \
+ (addr)->s6_addr16[6] ^ (addr)->s6_addr16[7]) & \
+ (HASH_SIZE - 1))
+
+static int ipv6_ipv6_fb_tnl_dev_init(struct net_device *dev);
+static int ipv6_ipv6_tnl_dev_init(struct net_device *dev);
+
+/* the IPv6 IPv6 tunnel fallback device */
+static struct net_device ipv6_ipv6_fb_tnl_dev = {
+ name:"ip6tnl0",
+ init:ipv6_ipv6_fb_tnl_dev_init
+};
+
+/* the IPv6 IPv6 fallback tunnel */
+static struct ipv6_tnl ipv6_ipv6_fb_tnl = {
+ dev:&ipv6_ipv6_fb_tnl_dev,
+ parms:{name: "ip6tnl0", proto:IPPROTO_IPV6}
+};
+
+/* lists for storing tunnels in use */
+static struct ipv6_tnl *tnls_r_l[HASH_SIZE];
+static struct ipv6_tnl *tnls_wc[1];
+static struct ipv6_tnl **tnls[2] = { tnls_wc, tnls_r_l };
+
+/* list for unused cached kernel tunnels */
+static struct ipv6_tnl *tnls_kernel[1];
+/* maximum number of cached kernel tunnels */
+static unsigned int max_kdev_count = 0;
+/* minimum number of cached kernel tunnels */
+static unsigned int min_kdev_count = 0;
+/* current number of cached kernel tunnels */
+static unsigned int kdev_count = 0;
+
+/* lists for tunnel hook functions */
+static struct list_head hooks[IPV6_TNL_MAXHOOKS];
+
+/* locks for the different lists */
+static rwlock_t ipv6_ipv6_lock = RW_LOCK_UNLOCKED;
+static rwlock_t ipv6_ipv6_kernel_lock = RW_LOCK_UNLOCKED;
+static rwlock_t ipv6_ipv6_hook_lock = RW_LOCK_UNLOCKED;
+
+/* flag indicating if the module is being removed */
+static int shutdown = 0;
+
+/**
+ * ipv6_ipv6_tnl_lookup - fetch tunnel matching the end-point addresses
+ * @remote: the address of the tunnel exit-point
+ * @local: the address of the tunnel entry-point
+ *
+ * Return:
+ * tunnel matching given end-points if found,
+ * else fallback tunnel if its device is up,
+ * else %NULL
+ **/
+
+struct ipv6_tnl *
+ipv6_ipv6_tnl_lookup(struct in6_addr *remote, struct in6_addr *local)
+{
+ unsigned h0 = HASH(remote);
+ unsigned h1 = HASH(local);
+ struct ipv6_tnl *t;
+
+ IPV6_TNL_TRACE();
+
+ for (t = tnls_r_l[h0 ^ h1]; t; t = t->next) {
+ if (!ipv6_addr_cmp(local, &t->parms.laddr) &&
+ !ipv6_addr_cmp(remote, &t->parms.raddr) &&
+ (t->dev->flags & IFF_UP))
+ return t;
+ }
+ if ((t = tnls_wc[0]) != NULL && (t->dev->flags & IFF_UP))
+ return t;
+
+ return NULL;
+}
+
+/**
+ * ipv6_ipv6_bucket - get head of list matching given tunnel parameters
+ * @p: parameters containing tunnel end-points
+ *
+ * Description:
+ * ipv6_ipv6_bucket() returns the head of the list matching the
+ * &struct in6_addr entries laddr and raddr in @p.
+ *
+ * Return: head of IPv6 tunnel list
+ **/
+
+static struct ipv6_tnl **
+ipv6_ipv6_bucket(struct ipv6_tnl_parm *p)
+{
+ struct in6_addr *remote = &p->raddr;
+ struct in6_addr *local = &p->laddr;
+ unsigned h = 0;
+ int prio = 0;
+
+ IPV6_TNL_TRACE();
+
+ if (!ipv6_addr_any(remote) || !ipv6_addr_any(local)) {
+ prio = 1;
+ h = HASH(remote) ^ HASH(local);
+ }
+ return &tnls[prio][h];
+}
+
+/**
+ * ipv6_ipv6_kernel_tnl_link - add new kernel tunnel to cache
+ * @t: kernel tunnel
+ *
+ * Note:
+ * %IPV6_TNL_F_KERNEL_DEV is assumed to be raised in t->parms.flags.
+ * See the comments on ipv6_ipv6_kernel_tnl_add() for more information.
+ **/
+
+static inline void
+ipv6_ipv6_kernel_tnl_link(struct ipv6_tnl *t)
+{
+ IPV6_TNL_TRACE();
+
+ write_lock_bh(&ipv6_ipv6_kernel_lock);
+ t->next = tnls_kernel[0];
+ tnls_kernel[0] = t;
+ kdev_count++;
+ write_unlock_bh(&ipv6_ipv6_kernel_lock);
+}
+
+/**
+ * ipv6_ipv6_kernel_tnl_unlink - remove first kernel tunnel from cache
+ *
+ * Return: first free kernel tunnel
+ *
+ * Note:
+ * See the comments on ipv6_ipv6_kernel_tnl_add() for more information.
+ **/
+
+static inline struct ipv6_tnl *
+ipv6_ipv6_kernel_tnl_unlink(void)
+{
+ struct ipv6_tnl *t;
+
+ IPV6_TNL_TRACE();
+
+ write_lock_bh(&ipv6_ipv6_kernel_lock);
+ if ((t = tnls_kernel[0]) != NULL) {
+ tnls_kernel[0] = t->next;
+ kdev_count--;
+ }
+ write_unlock_bh(&ipv6_ipv6_kernel_lock);
+ return t;
+}
+
+/**
+ * ipv6_ipv6_tnl_link - add tunnel to hash table
+ * @t: tunnel to be added
+ **/
+
+static void
+ipv6_ipv6_tnl_link(struct ipv6_tnl *t)
+{
+ struct ipv6_tnl **tp = ipv6_ipv6_bucket(&t->parms);
+
+ IPV6_TNL_TRACE();
+
+ write_lock_bh(&ipv6_ipv6_lock);
+ t->next = *tp;
+ write_unlock_bh(&ipv6_ipv6_lock);
+ *tp = t;
+}
+
+/**
+ * ipv6_ipv6_tnl_unlink - remove tunnel from hash table
+ * @t: tunnel to be removed
+ **/
+
+static void
+ipv6_ipv6_tnl_unlink(struct ipv6_tnl *t)
+{
+ struct ipv6_tnl **tp;
+
+ IPV6_TNL_TRACE();
+
+ for (tp = ipv6_ipv6_bucket(&t->parms); *tp; tp = &(*tp)->next) {
+ if (t == *tp) {
+ write_lock_bh(&ipv6_ipv6_lock);
+ *tp = t->next;
+ write_unlock_bh(&ipv6_ipv6_lock);
+ break;
+ }
+ }
+}
+
+/**
+ * ipv6_addr_local() - check if address is local
+ * @addr: address to be checked
+ *
+ * Return:
+ * 1 if @addr assigned to any local network device,
+ * 0 otherwise
+ **/
+
+static inline int
+ipv6_addr_local(struct in6_addr *addr)
+{
+ struct inet6_ifaddr *ifr;
+ int local = 0;
+
+ IPV6_TNL_TRACE();
+
+ if ((ifr = ipv6_get_ifaddr(addr, NULL)) != NULL) {
+ local = 1;
+ in6_ifa_put(ifr);
+ }
+ return local;
+}
+
+/**
+ * ipv6_tnl_addrs_sane() - check that the tunnel end points are sane
+ * @laddr: tunnel entry-point
+ * @raddr: tunnel exit-point
+ *
+ * Description:
+ * Sanity checks performed on tunnel end-points as suggested by
+ * RFCs 1853, 2003 and 2473.
+ *
+ * Return:
+ * 1 if sane,
+ * 0 otherwise
+ **/
+
+static inline int
+ipv6_tnl_addrs_sane(struct in6_addr *laddr, struct in6_addr *raddr)
+{
+ int laddr_type = ipv6_addr_type(laddr);
+ int raddr_type = ipv6_addr_type(raddr);
+
+ IPV6_TNL_TRACE();
+
+ if ((laddr_type &
+ (IPV6_ADDR_ANY | IPV6_ADDR_LOOPBACK | IPV6_ADDR_RESERVED)) ||
+ (raddr_type &
+ (IPV6_ADDR_ANY | IPV6_ADDR_LOOPBACK | IPV6_ADDR_RESERVED)) ||
+ ((laddr_type & IPV6_ADDR_UNICAST) && !ipv6_addr_local(laddr)) ||
+ ((raddr_type & IPV6_ADDR_UNICAST) && ipv6_addr_local(raddr))) {
+ return 0;
+ }
+ return 1;
+}
+
+/**
+ * ipv6_tnl_create() - create a new tunnel
+ * @p: tunnel parameters
+ * @pt: pointer to new tunnel
+ *
+ * Description:
+ * Create tunnel matching given parameters. New kernel managed devices are
+ * not put in the normal hash structure, but are instead cached for later
+ * use.
+ *
+ * Return:
+ * 0 on success
+ **/
+
+static int
+ipv6_tnl_create(struct ipv6_tnl_parm *p, struct ipv6_tnl **pt)
+{
+ struct net_device *dev;
+ int kernel_dev;
+ int err = -ENOBUFS;
+ struct ipv6_tnl *t;
+
+ IPV6_TNL_TRACE();
+
+ kernel_dev = (p->flags & IPV6_TNL_F_KERNEL_DEV);
+
+ MOD_INC_USE_COUNT;
+ dev = kmalloc(sizeof (*dev) + sizeof (*t), GFP_KERNEL);
+ if (!dev) {
+ MOD_DEC_USE_COUNT;
+ return err;
+ }
+ memset(dev, 0, sizeof (*dev) + sizeof (*t));
+ dev->priv = (void *) (dev + 1);
+ t = (struct ipv6_tnl *) dev->priv;
+ t->dev = dev;
+ dev->init = ipv6_ipv6_tnl_dev_init;
+ dev->features |= NETIF_F_DYNALLOC;
+ if (kernel_dev) {
+ memcpy(t->parms.name, p->name, IFNAMSIZ - 1);
+ t->parms.proto = IPPROTO_IPV6;
+ t->parms.flags = IPV6_TNL_F_KERNEL_DEV;
+ } else {
+ memcpy(&t->parms, p, sizeof (*p));
+ }
+ t->parms.name[IFNAMSIZ - 1] = '\0';
+ if (t->parms.hop_limit > 255)
+ t->parms.hop_limit = -1;
+ strcpy(dev->name, t->parms.name);
+ if (!dev->name[0]) {
+ int i = 0;
+ int exists = 0;
+
+ do {
+ sprintf(dev->name, "ip6tnl%d", ++i);
+ exists = (__dev_get_by_name(dev->name) != NULL);
+ } while (i < IPV6_TNL_MAX && exists);
+
+ if (i == IPV6_TNL_MAX) {
+ goto failed;
+ }
+ memcpy(t->parms.name, dev->name, IFNAMSIZ);
+ }
+ if ((err = register_netdevice(dev)) < 0) {
+ goto failed;
+ }
+ if (kernel_dev) {
+ ipv6_ipv6_kernel_tnl_link(t);
+ } else {
+ ipv6_ipv6_tnl_link(t);
+ }
+ *pt = t;
+ return 0;
+ failed:
+ kfree(dev);
+ MOD_DEC_USE_COUNT;
+ return err;
+}
+
+/**
+ * ipv6_tnl_destroy() - destroy old tunnel
+ * @t: tunnel to be destroyed
+ *
+ * Return:
+ * whatever unregister_netdevice() returns
+ **/
+
+static inline int
+ipv6_tnl_destroy(struct ipv6_tnl *t)
+{
+ IPV6_TNL_TRACE();
+ return unregister_netdevice(t->dev);
+}
+
+static void manage_kernel_tnls(void *foo);
+
+static struct tq_struct manager_task = {
+ routine:manage_kernel_tnls,
+ data:NULL
+};
+
+/**
+ * manage_kernel_tnls() - create and destroy kernel tunnels
+ *
+ * Description:
+ * manage_kernel_tnls() creates new kernel devices if there
+ * are less than $min_kdev_count of them and deletes old ones if
+ * there are less than $max_kdev_count of them in the cache
+ *
+ * Note:
+ * Schedules itself to be run later in process context if called from
+ * interrupt. Therefore only works synchronously when called from process
+ * context.
+ **/
+
+static void
+manage_kernel_tnls(void *foo)
+{
+ struct ipv6_tnl *t = NULL;
+ struct ipv6_tnl_parm parm;
+
+ IPV6_TNL_TRACE();
+
+ /* We can't do this processing in interrupt
+ context so schedule it for later */
+ if (in_interrupt()) {
+ read_lock(&ipv6_ipv6_kernel_lock);
+ if (!shutdown &&
+ (kdev_count < min_kdev_count ||
+ kdev_count > max_kdev_count)) {
+ schedule_task(&manager_task);
+ }
+ read_unlock(&ipv6_ipv6_kernel_lock);
+ return;
+ }
+
+ rtnl_lock();
+ read_lock_bh(&ipv6_ipv6_kernel_lock);
+ memset(&parm, 0, sizeof (parm));
+ parm.flags = IPV6_TNL_F_KERNEL_DEV;
+ /* Create tunnels until there are at least min_kdev_count */
+ while (kdev_count < min_kdev_count) {
+ read_unlock_bh(&ipv6_ipv6_kernel_lock);
+ if (!ipv6_tnl_create(&parm, &t)) {
+ dev_open(t->dev);
+ } else {
+ goto err;
+ }
+ read_lock_bh(&ipv6_ipv6_kernel_lock);
+ }
+
+ /* Destroy tunnels until there are at most max_kdev_count */
+ while (kdev_count > max_kdev_count) {
+ read_unlock_bh(&ipv6_ipv6_kernel_lock);
+ if ((t = ipv6_ipv6_kernel_tnl_unlink()) != NULL) {
+ ipv6_tnl_destroy(t);
+ } else {
+ goto err;
+ }
+ read_lock_bh(&ipv6_ipv6_kernel_lock);
+ }
+ read_unlock_bh(&ipv6_ipv6_kernel_lock);
+ err:
+ rtnl_unlock();
+}
+
+/**
+ * ipv6_ipv6_tnl_inc_max_kdev_count() - increase max kernel dev cache size
+ * @n: size increase
+ * Description:
+ * Increase the upper limit for the number of kernel devices allowed in the
+ * cache at any on time.
+ **/
+
+unsigned int
+ipv6_ipv6_tnl_inc_max_kdev_count(unsigned int n)
+{
+ IPV6_TNL_TRACE();
+
+ write_lock_bh(&ipv6_ipv6_kernel_lock);
+ max_kdev_count += n;
+ write_unlock_bh(&ipv6_ipv6_kernel_lock);
+ manage_kernel_tnls(NULL);
+ return max_kdev_count;
+}
+
+/**
+ * ipv6_ipv6_tnl_dec_max_kdev_count() - decrease max kernel dev cache size
+ * @n: size decrement
+ * Description:
+ * Decrease the upper limit for the number of kernel devices allowed in the
+ * cache at any on time.
+ **/
+
+unsigned int
+ipv6_ipv6_tnl_dec_max_kdev_count(unsigned int n)
+{
+ IPV6_TNL_TRACE();
+
+ write_lock_bh(&ipv6_ipv6_kernel_lock);
+ max_kdev_count -= min(max_kdev_count, n);
+ if (max_kdev_count < min_kdev_count)
+ min_kdev_count = max_kdev_count;
+ write_unlock_bh(&ipv6_ipv6_kernel_lock);
+ manage_kernel_tnls(NULL);
+ return max_kdev_count;
+}
+
+/**
+ * ipv6_ipv6_tnl_inc_min_kdev_count() - increase min kernel dev cache size
+ * @n: size increase
+ * Description:
+ * Increase the lower limit for the number of kernel devices allowed in the
+ * cache at any on time.
+ **/
+
+unsigned int
+ipv6_ipv6_tnl_inc_min_kdev_count(unsigned int n)
+{
+ IPV6_TNL_TRACE();
+
+ write_lock_bh(&ipv6_ipv6_kernel_lock);
+ min_kdev_count += n;
+ if (min_kdev_count > max_kdev_count)
+ max_kdev_count = min_kdev_count;
+ write_unlock_bh(&ipv6_ipv6_kernel_lock);
+ manage_kernel_tnls(NULL);
+ return min_kdev_count;
+}
+
+/**
+ * ipv6_ipv6_tnl_dec_min_kdev_count() - decrease min kernel dev cache size
+ * @n: size decrement
+ * Description:
+ * Decrease the lower limit for the number of kernel devices allowed in the
+ * cache at any on time.
+ **/
+
+unsigned int
+ipv6_ipv6_tnl_dec_min_kdev_count(unsigned int n)
+{
+ IPV6_TNL_TRACE();
+
+ write_lock_bh(&ipv6_ipv6_kernel_lock);
+ min_kdev_count -= min(min_kdev_count, n);
+ write_unlock_bh(&ipv6_ipv6_kernel_lock);
+ manage_kernel_tnls(NULL);
+ return min_kdev_count;
+}
+
+/**
+ * ipv6_ipv6_tnl_locate - find or create tunnel matching given parameters
+ * @p: tunnel parameters
+ * @create: != 0 if allowed to create new tunnel if no match found
+ *
+ * Description:
+ * ipv6_ipv6_tnl_locate() first tries to locate an existing tunnel
+ * based on @parms. If this is unsuccessful, but @create is set a new
+ * tunnel device is created and registered for use.
+ *
+ * Return:
+ * 0 if tunnel located or created,
+ * -EINVAL if parameters incorrect,
+ * -ENODEV if no matching tunnel available
+ **/
+
+static int
+ipv6_ipv6_tnl_locate(struct ipv6_tnl_parm *p, struct ipv6_tnl **pt, int create)
+{
+ struct in6_addr *remote = &p->raddr;
+ struct in6_addr *local = &p->laddr;
+ struct ipv6_tnl *t;
+
+ IPV6_TNL_TRACE();
+
+ if (p->proto != IPPROTO_IPV6 ||
+ (!(p->flags & IPV6_TNL_F_KERNEL_DEV) &&
+ !ipv6_tnl_addrs_sane(local, remote)))
+ return -EINVAL;
+
+ for (t = *ipv6_ipv6_bucket(p); t; t = t->next) {
+ if (!ipv6_addr_cmp(local, &t->parms.laddr) &&
+ !ipv6_addr_cmp(remote, &t->parms.raddr)) {
+ *pt = t;
+ return (create ? -EEXIST : 0);
+ }
+ }
+ /* Kernel devices are created on demand in
+ manage_kernel_tnls() */
+ if (!create || (p->flags & IPV6_TNL_F_KERNEL_DEV)) {
+ return -ENODEV;
+ }
+ return ipv6_tnl_create(p, pt);
+}
+
+/**
+ * ipv6_ipv6_tnl_dev_destructor - tunnel device destructor
+ * @dev: the device to be destroyed
+ **/
+
+static void
+ipv6_ipv6_tnl_dev_destructor(struct net_device *dev)
+{
+ IPV6_TNL_TRACE();
+
+ if (dev != &ipv6_ipv6_fb_tnl_dev) {
+ MOD_DEC_USE_COUNT;
+ }
+}
+
+/**
+ * ipv6_ipv6_tnl_dev_uninit - tunnel device uninitializer
+ * @dev: the device to be destroyed
+ *
+ * Description:
+ * ipv6_ipv6_tnl_dev_uninit() removes tunnel from its list
+ **/
+
+static void
+ipv6_ipv6_tnl_dev_uninit(struct net_device *dev)
+{
+ IPV6_TNL_TRACE();
+
+ if (dev == &ipv6_ipv6_fb_tnl_dev) {
+ write_lock_bh(&ipv6_ipv6_lock);
+ tnls_wc[0] = NULL;
+ write_unlock_bh(&ipv6_ipv6_lock);
+ } else {
+ struct ipv6_tnl *t = (struct ipv6_tnl *) dev->priv;
+ ipv6_ipv6_tnl_unlink(t);
+ }
+}
+
+/**
+ * parse_tvl_tnl_enc_lim - handle encapsulation limit option
+ * @skb: received socket buffer
+ *
+ * Return:
+ * 0 if none was found,
+ * else index to encapsulation limit
+ **/
+
+static __u16
+parse_tlv_tnl_enc_lim(struct sk_buff *skb, __u8 * raw)
+{
+ struct ipv6hdr *ipv6h = (struct ipv6hdr *) raw;
+ __u8 nexthdr = ipv6h->nexthdr;
+ __u16 off = sizeof (*ipv6h);
+
+ IPV6_TNL_TRACE();
+
+ while (ipv6_ext_hdr(nexthdr) && nexthdr != NEXTHDR_NONE) {
+ __u16 optlen = 0;
+ struct ipv6_opt_hdr *hdr;
+ if (raw + off + sizeof (*hdr) > skb->data &&
+ !pskb_may_pull(skb, raw - skb->data + off + sizeof (*hdr)))
+ break;
+
+ hdr = (struct ipv6_opt_hdr *) (raw + off);
+ if (nexthdr == NEXTHDR_FRAGMENT) {
+ struct frag_hdr *frag_hdr = (struct frag_hdr *) hdr;
+ if (frag_hdr->frag_off)
+ break;
+ optlen = 8;
+ } else if (nexthdr == NEXTHDR_AUTH) {
+ optlen = (hdr->hdrlen + 2) << 2;
+ } else {
+ optlen = ipv6_optlen(hdr);
+ }
+ if (nexthdr == NEXTHDR_DEST) {
+ __u16 i = off + 2;
+ while (1) {
+ struct ipv6_tlv_tnl_enc_lim *tel;
+
+ /* No more room for encapsulation limit */
+ if (i + sizeof (*tel) > off + optlen)
+ break;
+
+ tel = (struct ipv6_tlv_tnl_enc_lim *) &raw[i];
+ /* return index of option if found and valid */
+ if (tel->type == IPV6_TLV_TNL_ENCAP_LIMIT &&
+ tel->length == 1)
+ return i;
+ /* else jump to next option */
+ if (tel->type)
+ i += tel->length + 2;
+ else
+ i++;
+ }
+ }
+ nexthdr = hdr->nexthdr;
+ off += optlen;
+ }
+ return 0;
+}
+
+/**
+ * ipv6_ipv6_err - tunnel error handler
+ *
+ * Description:
+ * ipv6_ipv6_err() should handle errors in the tunnel according
+ * to the specifications in RFC 2473.
+ **/
+
+void
+ipv6_ipv6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
+ int type, int code, int offset, __u32 info)
+{
+ struct ipv6hdr *tipv6h = (struct ipv6hdr *) skb->data;
+ struct ipv6hdr *ipv6h = NULL;
+ struct ipv6_tnl *t;
+ int rel_msg = 0;
+ int rel_type = ICMPV6_DEST_UNREACH;
+ int rel_code = ICMPV6_ADDR_UNREACH;
+ __u32 rel_info = 0;
+ __u16 len;
+
+ IPV6_TNL_TRACE();
+
+ /* If the packet doesn't contain the original IPv6 header we are
+ in trouble since we might need the source address for furter
+ processing of the error. */
+
+ if (pskb_may_pull(skb, offset + sizeof (*ipv6h)))
+ ipv6h = (struct ipv6hdr *) &skb->data[offset];
+
+ read_lock(&ipv6_ipv6_lock);
+ if ((t = ipv6_ipv6_tnl_lookup(&tipv6h->daddr, &tipv6h->saddr)) == NULL)
+ goto out;
+
+ switch (type) {
+ __u32 teli;
+ struct ipv6_tlv_tnl_enc_lim *tel;
+ __u32 mtu;
+ case ICMPV6_DEST_UNREACH:
+ printk(KERN_ERR
+ "%s: Path to destination invalid or inactive!\n",
+ t->parms.name);
+ rel_msg = 1;
+ break;
+ case ICMPV6_TIME_EXCEED:
+ if (code == ICMPV6_EXC_HOPLIMIT) {
+ printk(KERN_ERR
+ "%s: Too small hop limit or "
+ "routing loop in tunnel!\n", t->parms.name);
+ rel_msg = 1;
+ }
+ break;
+ case ICMPV6_PARAMPROB:
+ /* ignore if parameter problem not caused by a tunnel
+ encapsulation limit sub-option */
+ if (code != ICMPV6_HDR_FIELD) {
+ break;
+ }
+ teli = parse_tlv_tnl_enc_lim(skb, skb->data);
+
+ if (teli && teli == info - 2) {
+ tel = (struct ipv6_tlv_tnl_enc_lim *) &skb->data[teli];
+ if (tel->encap_limit <= 1) {
+ printk(KERN_ERR
+ "%s: Too small encapsulation limit or "
+ "routing loop in tunnel!\n",
+ t->parms.name);
+ rel_msg = 1;
+ }
+ }
+ break;
+ case ICMPV6_PKT_TOOBIG:
+ mtu = info - offset;
+ if (mtu <= IPV6_MIN_MTU) {
+ mtu = IPV6_MIN_MTU;
+ }
+ t->dev->mtu = mtu;
+
+ if (ipv6h && (len = sizeof (*ipv6h) + ipv6h->payload_len) > mtu) {
+ rel_type = ICMPV6_PKT_TOOBIG;
+ rel_code = 0;
+ rel_info = mtu;
+ rel_msg = 1;
+ }
+ break;
+ }
+ if (rel_msg && ipv6h) {
+ struct rt6_info *rt6i;
+ struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
+
+ if (!skb2)
+ goto out;
+
+ dst_release(skb2->dst);
+ skb2->dst = NULL;
+ skb_pull(skb2, offset);
+ skb2->nh.raw = skb2->data;
+
+ /* Try to guess incoming interface */
+ rt6i = rt6_lookup(&ipv6h->saddr, NULL, 0, 0);
+ if (rt6i && rt6i->rt6i_dev)
+ skb2->dev = rt6i->rt6i_dev;
+
+ icmpv6_send(skb2, rel_type, rel_code, rel_info, skb2->dev);
+ kfree_skb(skb2);
+ }
+ out:
+ read_unlock(&ipv6_ipv6_lock);
+}
+
+/**
+ * call_hooks - call ipv6 tunnel hooks
+ * @hooknum: hook number, either %IPV6_TNL_PRE_ENCAP, or
+ * %IPV6_TNL_PRE_DECAP
+ * @t: the current tunnel
+ * @skb: the tunneled packet
+ *
+ * Description:
+ * Pass packet to all the hook functions until %IPV6_TNL_DROP or
+ * %IPV6_TNL_STOLEN returned by one of them.
+ *
+ * Return:
+ * %IPV6_TNL_ACCEPT, %IPV6_TNL_DROP or %IPV6_TNL_STOLEN
+ **/
+
+static inline int
+call_hooks(unsigned int hooknum, struct ipv6_tnl *t, struct sk_buff *skb)
+{
+ struct ipv6_tnl_hook_ops *h;
+ int accept = IPV6_TNL_ACCEPT;
+
+ IPV6_TNL_TRACE();
+
+ if (hooknum < IPV6_TNL_MAXHOOKS) {
+ struct list_head *i;
+ read_lock(&ipv6_ipv6_hook_lock);
+ for (i = hooks[hooknum].next; i != &hooks[hooknum]; i = i->next) {
+ h = (struct ipv6_tnl_hook_ops *) i;
+
+ if (h->hook) {
+ accept = h->hook(t, skb);
+
+ if (accept != IPV6_TNL_ACCEPT)
+ break;
+ }
+ }
+ read_unlock(&ipv6_ipv6_hook_lock);
+ }
+ return accept;
+}
+
+/**
+ * ipv6_ipv6_rcv - decapsulate IPv6 packet and retransmit it locally
+ * @skb: received socket buffer
+ *
+ * Return: 0
+ **/
+
+int
+ipv6_ipv6_rcv(struct sk_buff *skb)
+{
+ struct ipv6hdr *ipv6h;
+ struct ipv6_tnl *t;
+
+ IPV6_TNL_TRACE();
+
+ if (!pskb_may_pull(skb, sizeof (*ipv6h)))
+ goto out;
+
+ ipv6h = skb->nh.ipv6h;
+
+ read_lock(&ipv6_ipv6_lock);
+
+ if ((t = ipv6_ipv6_tnl_lookup(&ipv6h->saddr, &ipv6h->daddr)) != NULL) {
+ int hookval = call_hooks(IPV6_TNL_PRE_DECAP, t, skb);
+ switch (hookval) {
+ case IPV6_TNL_ACCEPT:
+ break;
+ case IPV6_TNL_STOLEN:
+ goto ignore_packet;
+ default:
+ if (hookval != IPV6_TNL_DROP) {
+ printk(KERN_ERR
+ "%s: Unknown return value for tunnel hook!\n",
+ t->parms.name);
+ }
+ t->stat.rx_dropped++;
+ goto drop_packet;
+ }
+ skb->mac.raw = skb->nh.raw;
+ skb->nh.raw = skb->data;
+ skb->protocol = htons(ETH_P_IPV6);
+ skb->pkt_type = PACKET_HOST;
+ skb->dev = t->dev;
+ dst_release(skb->dst);
+ skb->dst = NULL;
+ t->stat.rx_packets++;
+ t->stat.rx_bytes += skb->len;
+ netif_rx(skb);
+ read_unlock(&ipv6_ipv6_lock);
+ return 0;
+ }
+ icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH, 0, skb->dev);
+ drop_packet:
+ kfree_skb(skb);
+ ignore_packet:
+ read_unlock(&ipv6_ipv6_lock);
+ out:
+ return 0;
+}
+
+/**
+ * txopt_len - get necessary size for new &struct ipv6_txoptions
+ * @orig_opt: old options
+ *
+ * Return:
+ * Size of old one plus size of tunnel encapsulation limit option
+ **/
+
+static inline int
+txopt_len(struct ipv6_txoptions *orig_opt)
+{
+ int len = sizeof (*orig_opt) + 8;
+
+ IPV6_TNL_TRACE();
+
+ if (orig_opt && orig_opt->dst0opt)
+ len += ipv6_optlen(orig_opt->dst0opt);
+ return len;
+}
+
+/**
+ * merge_options - add encapsulation limit to original options
+ * @encap_limit: number of allowed encapsulation limits
+ * @orig_opt: original options
+ *
+ * Return:
+ * Pointer to new &struct ipv6_txoptions containing the tunnel
+ * encapsulation limit
+ **/
+
+static inline struct ipv6_txoptions *
+merge_options(struct sock *sk, __u8 encap_limit,
+ struct ipv6_txoptions *orig_opt)
+{
+ struct ipv6_tlv_tnl_enc_lim *tel;
+ struct ipv6_txoptions *opt;
+ __u8 *raw;
+ __u8 pad_to = 8;
+ int opt_len = txopt_len(orig_opt);
+
+ IPV6_TNL_TRACE();
+
+ if (!(opt = sock_kmalloc(sk, opt_len, GFP_ATOMIC))) {
+ return NULL;
+ }
+
+ memset(opt, 0, opt_len);
+ opt->tot_len = opt_len;
+ opt->dst0opt = (struct ipv6_opt_hdr *) (opt + 1);
+ opt->opt_nflen = 8;
+
+ raw = (__u8 *) opt->dst0opt;
+
+ tel = (struct ipv6_tlv_tnl_enc_lim *) (opt->dst0opt + 1);
+ tel->type = IPV6_TLV_TNL_ENCAP_LIMIT;
+ tel->length = 1;
+ tel->encap_limit = encap_limit;
+
+ if (orig_opt) {
+ __u8 *orig_raw;
+
+ opt->hopopt = orig_opt->hopopt;
+
+ /* Keep the original destination options properly
+ aligned and merge possible old paddings to the
+ new padding option */
+ if ((orig_raw = (__u8 *) orig_opt->dst0opt) != NULL) {
+ __u8 type;
+ int i = sizeof (struct ipv6_opt_hdr);
+ pad_to += sizeof (struct ipv6_opt_hdr);
+ while (i < ipv6_optlen(orig_opt->dst0opt)) {
+ type = orig_raw[i++];
+ if (type == IPV6_TLV_PAD0)
+ pad_to++;
+ else if (type == IPV6_TLV_PADN) {
+ int len = orig_raw[i++];
+ i += len;
+ pad_to += len + 2;
+ } else {
+ break;
+ }
+ }
+ opt->dst0opt->hdrlen = orig_opt->dst0opt->hdrlen + 1;
+ memcpy(raw + pad_to, orig_raw + pad_to - 8,
+ opt_len - sizeof (*opt) - pad_to);
+ }
+ opt->srcrt = orig_opt->srcrt;
+ opt->opt_nflen += orig_opt->opt_nflen;
+
+ opt->dst1opt = orig_opt->dst1opt;
+ opt->auth = orig_opt->auth;
+ opt->opt_flen = orig_opt->opt_flen;
+ }
+ raw[5] = IPV6_TLV_PADN;
+
+ /* subtract lengths of destination suboption header,
+ tunnel encapsulation limit and pad N header */
+ raw[6] = pad_to - 7;
+
+ return opt;
+}
+
+static int
+ipv6_ipv6_getfrag(const void *data, struct in6_addr *addr,
+ char *buff, unsigned int offset, unsigned int len)
+{
+ memcpy(buff, data + offset, len);
+ return 0;
+}
+
+/**
+ * ipv6_ipv6_tnl_addr_conflict - compare packet addresses to tunnel's own
+ * @t: the outgoing tunnel device
+ * @hdr: IPv6 header from the incoming packet
+ *
+ * Description:
+ * Avoid trivial tunneling loop by checking that tunnel exit-point
+ * doesn't match source of incoming packet.
+ *
+ * Return:
+ * 1 if conflict,
+ * 0 else
+ **/
+
+static inline int
+ipv6_ipv6_tnl_addr_conflict(struct ipv6_tnl *t, struct ipv6hdr *hdr)
+{
+ return !ipv6_addr_cmp(&t->parms.raddr, &hdr->saddr);
+}
+
+/**
+ * ipv6_ipv6_tnl_xmit - encapsulate packet and send
+ * @skb: the outgoing socket buffer
+ * @dev: the outgoing tunnel device
+ *
+ * Description:
+ * Build new header and do some sanity checks on the packet before sending
+ * it to ip6_build_xmit().
+ *
+ * Return:
+ * 0
+ **/
+
+static int
+ipv6_ipv6_tnl_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct ipv6_tnl *t = (struct ipv6_tnl *) dev->priv;
+ struct net_device_stats *stats = &t->stat;
+ struct ipv6hdr *ipv6h = skb->nh.ipv6h;
+ int hookval;
+ struct ipv6_txoptions *orig_opt = NULL;
+ struct ipv6_txoptions *opt = NULL;
+ __u8 encap_limit = 0;
+ __u16 offset;
+ struct flowi fl;
+ struct ip6_flowlabel *fl_lbl = NULL;
+ int err = 0;
+ struct dst_entry *dst;
+ int link_failure = 0;
+ struct sock *sk = ipv6_socket->sk;
+ struct net_device *tdev;
+ int mtu;
+
+ IPV6_TNL_TRACE();
+
+ if (t->recursion++) {
+ stats->collisions++;
+ goto tx_err;
+ }
+ if (skb->protocol != htons(ETH_P_IPV6) ||
+ (t->parms.flags & IPV6_TNL_F_RCV_ONLY) ||
+ ipv6_ipv6_tnl_addr_conflict(t, ipv6h) ||
+ (!(t->parms.flags & IPV6_TNL_F_ALLOW_LOCAL) &&
+ ipv6_addr_local(&ipv6h->saddr))) {
+ goto tx_err;
+ }
+ if ((offset = parse_tlv_tnl_enc_lim(skb, skb->nh.raw)) > 0) {
+ struct ipv6_tlv_tnl_enc_lim *tel;
+ tel = (struct ipv6_tlv_tnl_enc_lim *) &skb->nh.raw[offset];
+ if (tel->encap_limit <= 1) {
+ icmpv6_send(skb, ICMPV6_PARAMPROB,
+ ICMPV6_HDR_FIELD, offset + 2, skb->dev);
+ goto tx_err;
+ }
+ encap_limit = tel->encap_limit - 1;
+ } else if (!(t->parms.flags & IPV6_TNL_F_IGN_ENCAP_LIMIT)) {
+ encap_limit = t->parms.encap_limit;
+ }
+ hookval = call_hooks(IPV6_TNL_PRE_ENCAP, t, skb);
+ switch (hookval) {
+ case IPV6_TNL_ACCEPT:
+ break;
+ case IPV6_TNL_STOLEN:
+ goto ignore_packet;
+ default:
+ if (hookval != IPV6_TNL_DROP) {
+ printk(KERN_ERR
+ "%s: Unknown return value for tunnel hook!\n",
+ t->parms.name);
+ }
+ goto drop_packet;
+ }
+ if ((err = ipv6_xmit_lock()))
+ goto tx_err;
+
+ memcpy(&fl, &t->fl, sizeof (fl));
+
+ if (fl.fl6_flowlabel) {
+ fl_lbl = fl6_sock_lookup(sk, fl.fl6_flowlabel);
+ if (fl_lbl)
+ orig_opt = fl_lbl->opt;
+ }
+ if ((t->parms.flags & IPV6_TNL_F_USE_ORIG_TCLASS)) {
+ fl.fl6_flowlabel |= (*(__u32 *) ipv6h &
+ IPV6_FLOWINFO_MASK & ~IPV6_FLOWLABEL_MASK);
+ }
+ if (encap_limit > 0) {
+ if (!(opt = merge_options(sk, encap_limit, orig_opt))) {
+ goto tx_err_free_fl_lbl;
+ }
+ } else {
+ opt = orig_opt;
+ }
+ dst = ip6_route_output(sk, &fl);
+
+ if (dst->error) {
+ stats->tx_carrier_errors++;
+ link_failure = 1;
+ goto tx_err_dst_release;
+ }
+ tdev = dst->dev;
+
+ /* local routing loop */
+ if (tdev == dev) {
+ stats->collisions++;
+ printk(KERN_ERR "%s: Local routing loop detected!\n",
+ t->parms.name);
+ goto tx_err_dst_release;
+ }
+ mtu = dst->pmtu - sizeof (*ipv6h);
+ if (opt) {
+ mtu -= (opt->opt_nflen + opt->opt_flen);
+ }
+ if (mtu < IPV6_MIN_MTU)
+ mtu = IPV6_MIN_MTU;
+ if (skb->dst && mtu < skb->dst->pmtu) {
+ struct rt6_info *rt6 = (struct rt6_info *) skb->dst;
+ rt6->rt6i_flags |= RTF_MODIFIED;
+ rt6->u.dst.pmtu = mtu;
+ }
+ if (skb->len > mtu && skb->len > IPV6_MIN_MTU) {
+ icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0,
+ (mtu > IPV6_MIN_MTU ? mtu : IPV6_MIN_MTU), dev);
+ goto tx_err_dst_release;
+ }
+
+ err = ip6_build_xmit(sk, ipv6_ipv6_getfrag, (void *)skb->nh.raw,
+ &fl, skb->len, opt, t->parms.hop_limit, 0,
+ MSG_DONTWAIT);
+
+ if (err == NET_XMIT_SUCCESS || err == NET_XMIT_CN) {
+ stats->tx_bytes += skb->len;
+ stats->tx_packets++;
+ } else {
+ stats->tx_errors++;
+ stats->tx_aborted_errors++;
+ }
+ dst_release(dst);
+ if (opt && opt != orig_opt)
+ sock_kfree_s(sk, opt, opt->tot_len);
+ fl6_sock_release(fl_lbl);
+ ipv6_xmit_unlock();
+ kfree_skb(skb);
+ t->recursion--;
+ return 0;
+ tx_err_dst_release:
+ dst_release(dst);
+ if (opt && opt != orig_opt)
+ sock_kfree_s(sk, opt, opt->tot_len);
+ tx_err_free_fl_lbl:
+ fl6_sock_release(fl_lbl);
+ ipv6_xmit_unlock();
+ if (link_failure)
+ dst_link_failure(skb);
+ tx_err:
+ stats->tx_errors++;
+ drop_packet:
+ stats->tx_dropped++;
+ kfree_skb(skb);
+ ignore_packet:
+ t->recursion--;
+ return 0;
+}
+
+struct ipv6_tnl_link_parm {
+ int iflink;
+ unsigned short hard_header_len;
+ unsigned mtu;
+};
+
+static int
+ipv6_ipv6_tnl_link_parm_get(struct ipv6_tnl_parm *p,
+ int active, struct ipv6_tnl_link_parm *lp)
+{
+ int err = 0;
+
+ if (active) {
+ struct rt6_info *rt = rt6_lookup(&p->raddr, &p->laddr,
+ p->link, 0);
+ struct net_device *rtdev;
+ if (!rt) {
+ err = -ENOENT;
+ } else if ((rtdev = rt->rt6i_dev) == NULL) {
+ err = -ENODEV;
+ } else if (rtdev->type == ARPHRD_IPV6_IPV6_TUNNEL) {
+ /* as long as all tunnels use the same socket
+ (ipv6_socket) for transmission (locally)
+ nested tunnels won't work */
+ err = -ENOMEDIUM;
+ } else {
+ lp->iflink = rtdev->ifindex;
+ lp->hard_header_len = rtdev->hard_header_len +
+ sizeof (struct ipv6hdr);
+ lp->mtu = rtdev->mtu - sizeof (struct ipv6hdr);
+
+ if (lp->mtu < IPV6_MIN_MTU)
+ lp->mtu = IPV6_MIN_MTU;
+ }
+ if (rt) {
+ dst_release(&rt->u.dst);
+ }
+ } else {
+ lp->iflink = 0;
+ lp->hard_header_len = LL_MAX_HEADER + sizeof (struct ipv6hdr);
+ lp->mtu = ETH_DATA_LEN - sizeof (struct ipv6hdr);
+ }
+ return err;
+}
+
+static void
+ipv6_ipv6_tnl_dev_config(struct net_device *dev, struct ipv6_tnl_link_parm *lp)
+{
+ struct ipv6_tnl *t = (struct ipv6_tnl *) dev->priv;
+ struct flowi *fl;
+
+ IPV6_TNL_TRACE();
+
+ /* Set up flowi template */
+ fl = &t->fl;
+ fl->fl6_src = &t->parms.laddr;
+ fl->fl6_dst = &t->parms.raddr;
+ fl->oif = t->parms.link;
+ fl->fl6_flowlabel = IPV6_FLOWLABEL_MASK & htonl(t->parms.flow_lbl);
+
+ dev->iflink = lp->iflink;
+ dev->hard_header_len = lp->hard_header_len;
+ dev->mtu = lp->mtu;
+}
+
+/**
+ * ipv6_ipv6_tnl_change - update the tunnel parameters
+ * @t: tunnel to be changed
+ * @p: tunnel configuration parameters
+ * @active: != 0 if tunnel is ready for use
+ *
+ * Description:
+ * ipv6_ipv6_tnl_change() updates the tunnel parameters
+ **/
+
+static int
+ipv6_ipv6_tnl_change(struct ipv6_tnl *t, struct ipv6_tnl_parm *p, int active)
+{
+ struct net_device *dev = t->dev;
+ int laddr_uc = (ipv6_addr_type(&p->laddr) & IPV6_ADDR_UNICAST);
+ int raddr_uc = (ipv6_addr_type(&p->raddr) & IPV6_ADDR_UNICAST);
+ int err;
+ struct ipv6_tnl_link_parm lp;
+
+ IPV6_TNL_TRACE();
+
+ if ((err = ipv6_ipv6_tnl_link_parm_get(p, active, &lp)))
+ return err;
+
+ if (laddr_uc && raddr_uc) {
+ dev->flags |= IFF_POINTOPOINT;
+ } else {
+ dev->flags &= ~IFF_POINTOPOINT;
+ }
+ ipv6_addr_copy(&t->parms.laddr, &p->laddr);
+ ipv6_addr_copy(&t->parms.raddr, &p->raddr);
+ t->parms.flags = p->flags;
+
+ if (active) {
+ /* Only allow xmit if the tunnel is configured
+ with a valid unicast source address */
+ if (laddr_uc) {
+ t->parms.flags &= ~IPV6_TNL_F_RCV_ONLY;
+ } else {
+ t->parms.flags |= IPV6_TNL_F_RCV_ONLY;
+ }
+ }
+ t->parms.hop_limit = (p->hop_limit <= 255 ? p->hop_limit : -1);
+ t->parms.encap_limit = p->encap_limit;
+ t->parms.flow_lbl = p->flow_lbl;
+
+ ipv6_ipv6_tnl_dev_config(dev, &lp);
+ return 0;
+}
+
+static inline int
+ipv6_tnl_flag_cmp(__u32 f1, __u32 f2)
+{
+ return ((f1 & ~IPV6_TNL_F_RCV_ONLY) != (f2 & ~IPV6_TNL_F_RCV_ONLY));
+}
+
+/**
+ * ipv6_ipv6_kernel_tnl_add - configure and add kernel tunnel to hash
+ * @p: kernel tunnel configuration parameters
+ *
+ * Description:
+ * ipv6_ipv6_kernel_tnl_add() fetches an unused kernel tunnel configures
+ * it according to @p and places it among the active tunnels.
+ *
+ * Return:
+ * number of references to tunnel on success,
+ * %-EEXIST if there is already a device matching description
+ * %-EINVAL if p->flags doesn't have %IPV6_TNL_F_KERNEL_DEV raised,
+ * %-ENODEV if there are no unused kernel tunnels available
+ *
+ * Note:
+ * The code for creating, opening, closing and destroying network devices
+ * must be called from process context, while the Mobile IP code, which
+ * needs the tunnel devices, unfortunately runs in interrupt context.
+ *
+ * The devices must be created and opened in advance, then placed in a
+ * list where the kernel can fetch and ready them for use at a later time.
+ *
+ **/
+
+int
+ipv6_ipv6_kernel_tnl_add(struct ipv6_tnl_parm *p)
+{
+ struct ipv6_tnl *t;
+ int err;
+
+ IPV6_TNL_TRACE();
+
+ if (!(p->flags & IPV6_TNL_F_KERNEL_DEV) ||
+ !ipv6_tnl_addrs_sane(&p->laddr, &p->raddr))
+ return -EINVAL;
+ if ((t = ipv6_ipv6_tnl_lookup(&p->raddr, &p->laddr)) != NULL &&
+ t != &ipv6_ipv6_fb_tnl) {
+ if (ipv6_tnl_flag_cmp(p->flags, t->parms.flags)) {
+ /* Incompatible tunnel already exists for endpoints */
+ return -EEXIST;
+ } else {
+ /* Handle duplicate tunnels by incrementing
+ reference count */
+ atomic_inc(&t->refcnt);
+ goto out;
+ }
+ }
+ if ((t = ipv6_ipv6_kernel_tnl_unlink()) == NULL)
+ return -ENODEV;
+ if ((err = ipv6_ipv6_tnl_change(t, p, 1))) {
+ ipv6_ipv6_kernel_tnl_link(t);
+ return err;
+ }
+ atomic_inc(&t->refcnt);
+
+ ipv6_ipv6_tnl_link(t);
+
+ manage_kernel_tnls(NULL);
+ out:
+ return atomic_read(&t->refcnt);
+}
+
+/**
+ * ipv6_ipv6_kernel_tnl_del - delete no longer needed kernel tunnel
+ * @t: kernel tunnel to be removed from hash
+ *
+ * Description:
+ * ipv6_ipv6_kernel_tnl_del() removes and deconfigures the tunnel @t
+ * and places it among the unused kernel devices.
+ *
+ * Return:
+ * number of references on success,
+ * %-EINVAL if p->flags doesn't have %IPV6_TNL_F_KERNEL_DEV raised,
+ *
+ * Note:
+ * See the comments on ipv6_ipv6_kernel_tnl_add() for more information.
+ **/
+
+int
+ipv6_ipv6_kernel_tnl_del(struct ipv6_tnl *t)
+{
+ IPV6_TNL_TRACE();
+
+ if (!t)
+ return -ENODEV;
+
+ if (!(t->parms.flags & IPV6_TNL_F_KERNEL_DEV))
+ return -EINVAL;
+
+ if (atomic_dec_and_test(&t->refcnt)) {
+ struct ipv6_tnl_parm p;
+ ipv6_ipv6_tnl_unlink(t);
+ memset(&p, 0, sizeof (p));
+ p.flags = IPV6_TNL_F_KERNEL_DEV;
+
+ ipv6_ipv6_tnl_change(t, &p, 0);
+
+ ipv6_ipv6_kernel_tnl_link(t);
+
+ manage_kernel_tnls(NULL);
+ }
+ return atomic_read(&t->refcnt);
+}
+
+/**
+ * ipv6_ipv6_tnl_ioctl - configure ipv6 tunnels from userspace
+ * @dev: virtual device associated with tunnel
+ * @ifr: parameters passed from userspace
+ * @cmd: command to be performed
+ *
+ * Description:
+ * ipv6_ipv6_tnl_ioctl() is used for managing IPv6 tunnels
+ * from userspace.
+ *
+ * The possible commands are the following:
+ * %SIOCGETTUNNEL: get tunnel parameters for device
+ * %SIOCADDTUNNEL: add tunnel matching given tunnel parameters
+ * %SIOCCHGTUNNEL: change tunnel parameters to those given
+ * %SIOCDELTUNNEL: delete tunnel
+ *
+ * The fallback device "ip6tnl0", created during module
+ * initialization, can be used for creating other tunnel devices.
+ *
+ * Return:
+ * 0 on success,
+ * %-EFAULT if unable to copy data to or from userspace,
+ * %-EPERM if current process hasn't %CAP_NET_ADMIN set or attempting
+ * to configure kernel devices from userspace,
+ * %-EINVAL if passed tunnel parameters are invalid,
+ * %-EEXIST if changing a tunnel's parameters would cause a conflict
+ * %-ENODEV if attempting to change or delete a nonexisting device
+ *
+ * Note:
+ * See the comments on ipv6_ipv6_kernel_tnl_add() for more information
+ * about kernel tunnels.
+ * **/
+
+static int
+ipv6_ipv6_tnl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ int err = 0;
+ int create;
+ struct ipv6_tnl_parm p;
+ struct ipv6_tnl *t = NULL;
+
+ IPV6_TNL_TRACE();
+
+ MOD_INC_USE_COUNT;
+
+ switch (cmd) {
+ case SIOCGETTUNNEL:
+ if (dev == &ipv6_ipv6_fb_tnl_dev) {
+ if (copy_from_user(&p,
+ ifr->ifr_ifru.ifru_data,
+ sizeof (p))) {
+ err = -EFAULT;
+ break;
+ }
+ if ((err = ipv6_ipv6_tnl_locate(&p, &t, 0)) == -ENODEV)
+ t = (struct ipv6_tnl *) dev->priv;
+ else if (err)
+ break;
+ } else
+ t = (struct ipv6_tnl *) dev->priv;
+
+ memcpy(&p, &t->parms, sizeof (p));
+ if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof (p))) {
+ err = -EFAULT;
+ }
+ break;
+ case SIOCADDTUNNEL:
+ case SIOCCHGTUNNEL:
+ err = -EPERM;
+ create = (cmd == SIOCADDTUNNEL);
+ if (!capable(CAP_NET_ADMIN))
+ break;
+ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof (p))) {
+ err = -EFAULT;
+ break;
+ }
+ if (p.flags & IPV6_TNL_F_KERNEL_DEV) {
+ break;
+ }
+ if (!create && dev != &ipv6_ipv6_fb_tnl_dev) {
+ t = (struct ipv6_tnl *) dev->priv;
+ }
+ if (!t && (err = ipv6_ipv6_tnl_locate(&p, &t, create))) {
+ break;
+ }
+ if (cmd == SIOCCHGTUNNEL) {
+ if (t->dev != dev) {
+ err = -EEXIST;
+ break;
+ }
+ if (t->parms.flags & IPV6_TNL_F_KERNEL_DEV) {
+ err = -EPERM;
+ break;
+ }
+ ipv6_ipv6_tnl_unlink(t);
+ err = ipv6_ipv6_tnl_change(t, &p, 1);
+ ipv6_ipv6_tnl_link(t);
+ netdev_state_change(dev);
+ }
+ if (copy_to_user(ifr->ifr_ifru.ifru_data,
+ &t->parms, sizeof (p))) {
+ err = -EFAULT;
+ } else {
+ err = 0;
+ }
+ break;
+ case SIOCDELTUNNEL:
+ err = -EPERM;
+ if (!capable(CAP_NET_ADMIN))
+ break;
+
+ if (dev == &ipv6_ipv6_fb_tnl_dev) {
+ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data,
+ sizeof (p))) {
+ err = -EFAULT;
+ break;
+ }
+ err = ipv6_ipv6_tnl_locate(&p, &t, 0);
+ if (err)
+ break;
+ if (t == &ipv6_ipv6_fb_tnl) {
+ err = -EPERM;
+ break;
+ }
+ } else {
+ t = (struct ipv6_tnl *) dev->priv;
+ }
+ if (t->parms.flags & IPV6_TNL_F_KERNEL_DEV)
+ err = -EPERM;
+ else
+ err = ipv6_tnl_destroy(t);
+ break;
+ default:
+ err = -EINVAL;
+ }
+ MOD_DEC_USE_COUNT;
+ return err;
+}
+
+/**
+ * ipv6_ipv6_tnl_get_stats - return the stats for tunnel device
+ * @dev: virtual device associated with tunnel
+ *
+ * Return: stats for device
+ **/
+
+static struct net_device_stats *
+ipv6_ipv6_tnl_get_stats(struct net_device *dev)
+{
+ IPV6_TNL_TRACE();
+
+ return &(((struct ipv6_tnl *) dev->priv)->stat);
+}
+
+/**
+ * ipv6_ipv6_tnl_change_mtu - change mtu manually for tunnel device
+ * @dev: virtual device associated with tunnel
+ * @new_mtu: the new mtu
+ *
+ * Return:
+ * 0 on success,
+ * %-EINVAL if mtu too small
+ **/
+
+static int
+ipv6_ipv6_tnl_change_mtu(struct net_device *dev, int new_mtu)
+{
+ IPV6_TNL_TRACE();
+
+ if (new_mtu < IPV6_MIN_MTU) {
+ return -EINVAL;
+ }
+ dev->mtu = new_mtu;
+ return 0;
+}
+
+/**
+ * ipv6_ipv6_tnl_dev_init_gen - general initializer for all tunnel devices
+ * @dev: virtual device associated with tunnel
+ *
+ * Description:
+ * Set function pointers and initialize the &struct flowi template used
+ * by the tunnel.
+ **/
+
+static void
+ipv6_ipv6_tnl_dev_init_gen(struct net_device *dev)
+{
+ struct ipv6_tnl *t = (struct ipv6_tnl *) dev->priv;
+ struct flowi *fl = &t->fl;
+
+ IPV6_TNL_TRACE();
+
+ memset(fl, 0, sizeof (*fl));
+ fl->proto = IPPROTO_IPV6;
+
+ dev->destructor = ipv6_ipv6_tnl_dev_destructor;
+ dev->uninit = ipv6_ipv6_tnl_dev_uninit;
+ dev->hard_start_xmit = ipv6_ipv6_tnl_xmit;
+ dev->get_stats = ipv6_ipv6_tnl_get_stats;
+ dev->do_ioctl = ipv6_ipv6_tnl_ioctl;
+ dev->change_mtu = ipv6_ipv6_tnl_change_mtu;
+ dev->type = ARPHRD_IPV6_IPV6_TUNNEL;
+ dev->flags |= IFF_NOARP;
+ if (ipv6_addr_type(&t->parms.raddr) & IPV6_ADDR_UNICAST)
+ dev->flags |= IFF_POINTOPOINT;
+ dev->iflink = 0;
+ /* Hmm... MAX_ADDR_LEN is 8, so the ipv6 addresses can't be
+ copied to dev->dev_addr and dev->broadcast, like the ipv4
+ addresses were in ipip.c, ip_gre.c and sit.c. */
+ dev->addr_len = 0;
+}
+
+/**
+ * ipv6_ipv6_tnl_dev_init - initializer for all non fallback tunnel devices
+ * @dev: virtual device associated with tunnel
+ **/
+
+static int
+ipv6_ipv6_tnl_dev_init(struct net_device *dev)
+{
+ struct ipv6_tnl *t = (struct ipv6_tnl *) dev->priv;
+ int active = !(t->parms.flags & IPV6_TNL_F_KERNEL_DEV);
+ struct ipv6_tnl_link_parm lp;
+ int err;
+
+ IPV6_TNL_TRACE();
+
+ if ((err = ipv6_ipv6_tnl_link_parm_get(&t->parms, active, &lp))) {
+ return err;
+ }
+ ipv6_ipv6_tnl_dev_init_gen(dev);
+ ipv6_ipv6_tnl_dev_config(dev, &lp);
+ return 0;
+}
+
+#ifdef MODULE
+
+/**
+ * ipv6_ipv6_fb_tnl_open - function called when fallback device opened
+ * @dev: fallback device
+ *
+ * Return: 0
+ **/
+
+static int
+ipv6_ipv6_fb_tnl_open(struct net_device *dev)
+{
+ IPV6_TNL_TRACE();
+
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+/**
+ * ipv6_ipv6_fb_tnl_close - function called when fallback device closed
+ * @dev: fallback device
+ *
+ * Return: 0
+ **/
+
+static int
+ipv6_ipv6_fb_tnl_close(struct net_device *dev)
+{
+ IPV6_TNL_TRACE();
+
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+#endif
+
+/**
+ * ipv6_ipv6_fb_tnl_dev_init - initializer for fallback tunnel device
+ * @dev: fallback device
+ *
+ * Return: 0
+ **/
+
+int __init
+ipv6_ipv6_fb_tnl_dev_init(struct net_device *dev)
+{
+ IPV6_TNL_TRACE();
+
+ ipv6_ipv6_tnl_dev_init_gen(dev);
+#ifdef MODULE
+ dev->open = ipv6_ipv6_fb_tnl_open;
+ dev->stop = ipv6_ipv6_fb_tnl_close;
+#endif
+ tnls_wc[0] = &ipv6_ipv6_fb_tnl;
+ return 0;
+}
+
+/**
+ * ipv6_ipv6_tnl_register_hook - add hook for processing of tunneled packets
+ * @reg: hook function and its parameters
+ *
+ * Description:
+ * Add a netfilter like hook function for special handling of tunneled
+ * packets. The hook functions are called before encapsulation
+ * (%IPV6_TNL_PRE_ENCAP) and before decapsulation
+ * (%IPV6_TNL_PRE_DECAP). The possible return values by the hook
+ * functions are %IPV6_TNL_DROP, %IPV6_TNL_ACCEPT and
+ * %IPV6_TNL_STOLEN (in case the hook function took care of the packet
+ * and it doesn't have to be processed any further).
+ **/
+
+void
+ipv6_ipv6_tnl_register_hook(struct ipv6_tnl_hook_ops *reg)
+{
+ IPV6_TNL_TRACE();
+
+ if (reg->hooknum < IPV6_TNL_MAXHOOKS) {
+ struct list_head *i;
+
+ write_lock_bh(&ipv6_ipv6_hook_lock);
+ for (i = hooks[reg->hooknum].next;
+ i != &hooks[reg->hooknum]; i = i->next) {
+ if (reg->priority <
+ ((struct ipv6_tnl_hook_ops *) i)->priority) {
+ break;
+ }
+ }
+ list_add(&reg->list, i->prev);
+ write_unlock_bh(&ipv6_ipv6_hook_lock);
+ }
+}
+
+/**
+ * ipv6_ipv6_tnl_unregister_hook - remove tunnel hook
+ * @reg: hook function and its parameters
+ **/
+
+void
+ipv6_ipv6_tnl_unregister_hook(struct ipv6_tnl_hook_ops *reg)
+{
+ IPV6_TNL_TRACE();
+
+ if (reg->hooknum < IPV6_TNL_MAXHOOKS) {
+ write_lock_bh(&ipv6_ipv6_hook_lock);
+ list_del(&reg->list);
+ write_unlock_bh(&ipv6_ipv6_hook_lock);
+ }
+}
+
+/* the IPv6 over IPv6 protocol structure */
+static struct inet6_protocol ipv6_ipv6_protocol = {
+ ipv6_ipv6_rcv, /* IPv6 handler */
+ ipv6_ipv6_err, /* IPv6 error control */
+ NULL, /* next */
+ IPPROTO_IPV6, /* protocol ID */
+ 0, /* copy */
+ NULL, /* data */
+ "IPv6 over IPv6" /* name */
+};
+
+/**
+ * ipv6_ipv6_tnl_init - register protocol and reserve needed resources
+ *
+ * Return: 0 on success
+ **/
+
+int __init
+ipv6_ipv6_tnl_init(void)
+{
+ int i, err;
+ struct sock *sk;
+
+ IPV6_TNL_TRACE();
+
+ ipv6_ipv6_fb_tnl_dev.priv = (void *) &ipv6_ipv6_fb_tnl;
+ err = sock_create(PF_INET6, SOCK_RAW, IPPROTO_IPV6, &ipv6_socket);
+ if (err < 0) {
+ printk(KERN_ERR "Failed to create the IPv6 tunnel socket.\n");
+ return err;
+ }
+ sk = ipv6_socket->sk;
+ sk->allocation = GFP_ATOMIC;
+ sk->net_pinfo.af_inet6.hop_limit = 254;
+ sk->net_pinfo.af_inet6.mc_loop = 0;
+ sk->prot->unhash(sk);
+
+ for (i = 0; i < IPV6_TNL_MAXHOOKS; i++) {
+ INIT_LIST_HEAD(&hooks[i]);
+ }
+ register_netdev(&ipv6_ipv6_fb_tnl_dev);
+ inet6_add_protocol(&ipv6_ipv6_protocol);
+ return 0;
+}
+
+/**
+ * ipv6_ipv6_tnl_exit - free resources and unregister protocol
+ **/
+
+void __exit
+ipv6_ipv6_tnl_exit(void)
+{
+ IPV6_TNL_TRACE();
+
+ write_lock_bh(&ipv6_ipv6_kernel_lock);
+ shutdown = 1;
+ write_unlock_bh(&ipv6_ipv6_kernel_lock);
+ flush_scheduled_tasks();
+ manage_kernel_tnls(NULL);
+ inet6_del_protocol(&ipv6_ipv6_protocol);
+ unregister_netdev(&ipv6_ipv6_fb_tnl_dev);
+ sock_release(ipv6_socket);
+}
+
+#ifdef MODULE
+module_init(ipv6_ipv6_tnl_init);
+module_exit(ipv6_ipv6_tnl_exit);
+#endif
diff --git a/uClinux-2.4.31-uc0/net/ipv6/mcast.c b/uClinux-2.4.31-uc0/net/ipv6/mcast.c
new file mode 100644
index 0000000..4e40386
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/mcast.c
@@ -0,0 +1,2459 @@
+/* $USAGI: mcast.c,v 1.35 2003/11/12 05:12:01 yoshfuji Exp $ */
+
+/*
+ * Multicast support for IPv6
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ *
+ * $Id: mcast.c,v 1.38 2001/08/15 07:36:31 davem Exp $
+ *
+ * Based on linux/ipv4/igmp.c and linux/ipv4/ip_sockglue.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/* Changes:
+ *
+ * yoshfuji : fix format of router-alert option
+ * YOSHIFUJI Hideaki @USAGI:
+ * Fixed source address for MLD message based on
+ * <draft-ietf-magma-mld-source-02.txt>.
+ * YOSHIFUJI Hideaki @USAGI:
+ * - Ignore Queries for invalid addresses.
+ * - MLD for link-local addresses.
+ * David L Stevens <dlstevens@us.ibm.com>:
+ - MLDv2 support
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/random.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/sched.h>
+#include <linux/net.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/route.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+
+#include <linux/netfilter.h>
+#include <linux/netfilter_ipv6.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/if_inet6.h>
+#include <net/ndisc.h>
+#include <net/addrconf.h>
+#include <net/ip6_route.h>
+
+#include <net/checksum.h>
+
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+#include <linux/inet.h>
+#endif
+
+/* Set to 3 to get tracing... */
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+#define MCAST_DEBUG 3
+#else
+#define MCAST_DEBUG 1
+#endif
+
+#define MDBG(x) printk x
+#define NOMDBG(x) do { ; } while(0)
+
+#if MCAST_DEBUG >= 3
+#define MDBG3(x) MDBG(x)
+#else
+#define MDBG3(x) NOMDBG(x)
+#endif
+
+#if MCAST_DEBUB >= 2
+#define MDBG2(x) MDBG(x)
+#else
+#define MDBG2(x) NOMDBG(x)
+#endif
+
+#if MCAST_DEBUG >= 1
+#define MDBG1(x) MDBG(x)
+#else
+#define MDBG1(x) NOMDBG(x)
+#endif
+
+/*
+ * These header formats should be in a separate include file, but icmpv6.h
+ * doesn't have in6_addr defined in all cases, there is no __u128, and no
+ * other files reference these.
+ *
+ * +-DLS 4/14/03
+ */
+
+/* Multicast Listener Discovery version 2 headers */
+
+struct mld2_grec {
+ __u8 grec_type;
+ __u8 grec_auxwords;
+ __u16 grec_nsrcs;
+ struct in6_addr grec_mca;
+ struct in6_addr grec_src[0];
+};
+
+struct mld2_report {
+ __u8 type;
+ __u8 resv1;
+ __u16 csum;
+ __u16 resv2;
+ __u16 ngrec;
+ struct mld2_grec grec[0];
+};
+
+struct mld2_query {
+ __u8 type;
+ __u8 code;
+ __u16 csum;
+ __u16 mrc;
+ __u16 resv1;
+ struct in6_addr mca;
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ __u8 qrv:3,
+ suppress:1,
+ resv2:4;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ __u8 resv2:4,
+ suppress:1,
+ qrv:3;
+#else
+#error "Please fix <asm/byteorder.h>"
+#endif
+ __u8 qqic;
+ __u16 nsrcs;
+ struct in6_addr srcs[0];
+};
+
+struct in6_addr mld2_all_mcr = MLD2_ALL_MCR_INIT;
+struct in6_addr all_nodes_addr = {{{0xff,0x02,0,0,0,0,0,0,0,0,0,0,0,0,0,0x1}}};
+
+/* Big mc list lock for all the sockets */
+static rwlock_t ipv6_sk_mc_lock = RW_LOCK_UNLOCKED;
+
+static struct socket *igmp6_socket;
+
+static void igmp6_join_group(struct ifmcaddr6 *ma);
+static void igmp6_leave_group(struct ifmcaddr6 *ma);
+static void igmp6_timer_handler(unsigned long data);
+
+static void mld_gq_timer_expire(unsigned long data);
+static void mld_ifc_timer_expire(unsigned long data);
+static void mld_ifc_event(struct inet6_dev *idev);
+static void mld_add_delrec(struct inet6_dev *idev, struct ifmcaddr6 *pmc);
+static void mld_del_delrec(struct inet6_dev *idev, struct in6_addr *addr);
+static void mld_clear_delrec(struct inet6_dev *idev);
+static int sf_setstate(struct ifmcaddr6 *pmc);
+static void sf_markstate(struct ifmcaddr6 *pmc);
+static void ip6_mc_clear_src(struct ifmcaddr6 *pmc);
+int ip6_mc_del_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode,
+ int sfcount, struct in6_addr *psfsrc, int delta);
+int ip6_mc_add_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode,
+ int sfcount, struct in6_addr *psfsrc, int delta);
+int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml,
+ struct inet6_dev *idev);
+
+
+#define IGMP6_UNSOLICITED_IVAL (10*HZ)
+#define MLD_QRV_DEFAULT 2
+
+#define MLD_V1_SEEN(idev) ((idev)->mc_v1_seen && \
+ time_before(jiffies, (idev)->mc_v1_seen))
+
+#define MLDV2_MASK(value, nb) ((nb)>=32 ? (value) : ((1<<(nb))-1) & (value))
+#define MLDV2_EXP(thresh, nbmant, nbexp, value) \
+ ((value) < (thresh) ? (value) : \
+ ((MLDV2_MASK(value, nbmant) | (1<<(nbmant+nbexp))) << \
+ (MLDV2_MASK((value) >> (nbmant), nbexp) + (nbexp))))
+
+#define MLDV2_QQIC(value) MLDV2_EXP(0x80, 4, 3, value)
+#define MLDV2_MRC(value) MLDV2_EXP(0x8000, 12, 3, value)
+
+#define IPV6_MLD_MAX_MSF 10
+
+int sysctl_mld_max_msf = IPV6_MLD_MAX_MSF;
+
+/*
+ * socket join on multicast group
+ */
+
+int ipv6_sock_mc_join(struct sock *sk, int ifindex, struct in6_addr *addr)
+{
+ struct net_device *dev = NULL;
+ struct ipv6_mc_socklist *mc_lst;
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ int err;
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ char abuf[128];
+ in6_ntop(addr, abuf);
+
+ MDBG3((KERN_DEBUG
+ "ipv6_sock_mc_join(sk=%p, ifindex=%d, addr=%s)\n",
+ sk, ifindex, abuf));
+#endif
+
+ if (!(ipv6_addr_type(addr) & IPV6_ADDR_MULTICAST))
+ return -EINVAL;
+
+ mc_lst = sock_kmalloc(sk, sizeof(struct ipv6_mc_socklist), GFP_KERNEL);
+
+ if (mc_lst == NULL)
+ return -ENOMEM;
+
+ mc_lst->next = NULL;
+ ipv6_addr_copy(&mc_lst->addr, addr);
+
+ if (ifindex == 0) {
+ struct rt6_info *rt;
+ rt = rt6_lookup(addr, NULL, 0, 0);
+ if (rt) {
+ dev = rt->rt6i_dev;
+ dev_hold(dev);
+ dst_release(&rt->u.dst);
+ }
+ } else
+ dev = dev_get_by_index(ifindex);
+
+ if (dev == NULL) {
+ sock_kfree_s(sk, mc_lst, sizeof(*mc_lst));
+ return -ENODEV;
+ }
+
+ mc_lst->ifindex = dev->ifindex;
+ mc_lst->sfmode = MCAST_EXCLUDE;
+ mc_lst->sflist = 0;
+
+ /*
+ * now add/increase the group membership on the device
+ */
+
+ err = ipv6_dev_mc_inc(dev, addr);
+
+ if (err) {
+ sock_kfree_s(sk, mc_lst, sizeof(*mc_lst));
+ dev_put(dev);
+ return err;
+ }
+
+ write_lock_bh(&ipv6_sk_mc_lock);
+ mc_lst->next = np->ipv6_mc_list;
+ np->ipv6_mc_list = mc_lst;
+ write_unlock_bh(&ipv6_sk_mc_lock);
+
+ dev_put(dev);
+
+ return 0;
+}
+
+/*
+ * socket leave on multicast group
+ */
+int ipv6_sock_mc_drop(struct sock *sk, int ifindex, struct in6_addr *addr)
+{
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct ipv6_mc_socklist *mc_lst, **lnk;
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ char abuf[128];
+ in6_ntop(addr, abuf);
+
+ MDBG3((KERN_DEBUG
+ "ipv6_sock_mc_drop(sk=%p, ifindex=%d, addr=%s)\n",
+ sk, ifindex, abuf));
+#endif
+
+ write_lock_bh(&ipv6_sk_mc_lock);
+ for (lnk = &np->ipv6_mc_list; (mc_lst = *lnk) !=NULL ; lnk = &mc_lst->next) {
+ if ((ifindex == 0 || mc_lst->ifindex == ifindex) &&
+ ipv6_addr_cmp(&mc_lst->addr, addr) == 0) {
+ struct net_device *dev;
+
+ *lnk = mc_lst->next;
+ write_unlock_bh(&ipv6_sk_mc_lock);
+
+ /* Note: mc_lst->ifindex != 0 */
+ if ((dev = dev_get_by_index(mc_lst->ifindex)) != NULL) {
+ struct inet6_dev *idev = in6_dev_get(dev);
+
+ if (idev) {
+ (void) ip6_mc_leave_src(sk,mc_lst,idev);
+ in6_dev_put(idev);
+ }
+ ipv6_dev_mc_dec(dev, &mc_lst->addr);
+ dev_put(dev);
+ }
+ sock_kfree_s(sk, mc_lst, sizeof(*mc_lst));
+ return 0;
+ }
+ }
+ write_unlock_bh(&ipv6_sk_mc_lock);
+
+ return -ENOENT;
+}
+
+struct inet6_dev *ip6_mc_find_dev(struct in6_addr *group, int ifindex)
+{
+ struct net_device *dev = 0;
+ struct inet6_dev *idev = 0;
+
+ if (ifindex == 0) {
+ struct rt6_info *rt;
+
+ rt = rt6_lookup(group, NULL, 0, 0);
+ if (rt) {
+ dev = rt->rt6i_dev;
+ dev_hold(dev);
+ dst_release(&rt->u.dst);
+ }
+ } else
+ dev = dev_get_by_index(ifindex);
+
+ if (!dev)
+ return 0;
+ idev = in6_dev_get(dev);
+ if (!idev) {
+ dev_put(dev);
+ return 0;
+ }
+ read_lock_bh(&idev->lock);
+ if (idev->dead) {
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ dev_put(dev);
+ return 0;
+ }
+ return idev;
+}
+
+void ipv6_sock_mc_close(struct sock *sk)
+{
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct ipv6_mc_socklist *mc_lst;
+
+ MDBG3((KERN_DEBUG
+ "ipv6_sock_mc_close(sk=%p)\n", sk));
+
+ write_lock_bh(&ipv6_sk_mc_lock);
+ while ((mc_lst = np->ipv6_mc_list) != NULL) {
+ struct net_device *dev;
+
+ np->ipv6_mc_list = mc_lst->next;
+ write_unlock_bh(&ipv6_sk_mc_lock);
+
+ dev = dev_get_by_index(mc_lst->ifindex);
+ if (dev) {
+ struct inet6_dev *idev = in6_dev_get(dev);
+
+ if (idev) {
+ (void) ip6_mc_leave_src(sk, mc_lst, idev);
+ in6_dev_put(idev);
+ }
+ ipv6_dev_mc_dec(dev, &mc_lst->addr);
+ dev_put(dev);
+ }
+
+ sock_kfree_s(sk, mc_lst, sizeof(*mc_lst));
+
+ write_lock_bh(&ipv6_sk_mc_lock);
+ }
+ write_unlock_bh(&ipv6_sk_mc_lock);
+}
+
+int ip6_mc_source(int add, int omode, struct sock *sk,
+ struct group_source_req *pgsr)
+{
+ struct in6_addr *source, *group;
+ struct ipv6_mc_socklist *pmc;
+ struct net_device *dev;
+ struct inet6_dev *idev;
+ struct ipv6_pinfo *inet6 = &sk->net_pinfo.af_inet6;
+ struct ip6_sf_socklist *psl;
+ int i, j, rv;
+ int err;
+
+ if (pgsr->gsr_group.ss_family != AF_INET6 ||
+ pgsr->gsr_source.ss_family != AF_INET6)
+ return -EINVAL;
+
+ source = &((struct sockaddr_in6 *)&pgsr->gsr_source)->sin6_addr;
+ group = &((struct sockaddr_in6 *)&pgsr->gsr_group)->sin6_addr;
+
+ if (!(ipv6_addr_type(group) & IPV6_ADDR_MULTICAST))
+ return -EINVAL;
+
+ idev = ip6_mc_find_dev(group, pgsr->gsr_interface);
+ if (!idev)
+ return -ENODEV;
+ dev = idev->dev;
+
+ err = -EADDRNOTAVAIL;
+
+ for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) {
+ if (pgsr->gsr_interface && pmc->ifindex != pgsr->gsr_interface)
+ continue;
+ if (ipv6_addr_cmp(&pmc->addr, group) == 0)
+ break;
+ }
+ if (!pmc) /* must have a prior join */
+ goto done;
+ /* if a source filter was set, must be the same mode as before */
+ if (pmc->sflist) {
+ if (pmc->sfmode != omode)
+ goto done;
+ } else if (pmc->sfmode != omode) {
+ /* allow mode switches for empty-set filters */
+ ip6_mc_add_src(idev, group, omode, 0, 0, 0);
+ ip6_mc_del_src(idev, group, pmc->sfmode, 0, 0, 0);
+ pmc->sfmode = omode;
+ }
+
+ psl = pmc->sflist;
+ if (!add) {
+ if (!psl)
+ goto done;
+ rv = !0;
+ for (i=0; i<psl->sl_count; i++) {
+ rv = memcmp(&psl->sl_addr[i], source,
+ sizeof(struct in6_addr));
+ if (rv == 0)
+ break;
+ }
+ if (rv) /* source not found */
+ goto done;
+
+ /* update the interface filter */
+ ip6_mc_del_src(idev, group, omode, 1, source, 1);
+
+ for (j=i+1; j<psl->sl_count; j++)
+ psl->sl_addr[j-1] = psl->sl_addr[j];
+ psl->sl_count--;
+ err = 0;
+ goto done;
+ }
+ /* else, add a new source to the filter */
+
+ if (psl && psl->sl_count >= sysctl_mld_max_msf) {
+ err = -ENOBUFS;
+ goto done;
+ }
+ if (!psl || psl->sl_count == psl->sl_max) {
+ struct ip6_sf_socklist *newpsl;
+ int count = IP6_SFBLOCK;
+
+ if (psl)
+ count += psl->sl_max;
+ newpsl = (struct ip6_sf_socklist *)sock_kmalloc(sk,
+ IP6_SFLSIZE(count), GFP_ATOMIC);
+ if (!newpsl) {
+ err = -ENOBUFS;
+ goto done;
+ }
+ newpsl->sl_max = count;
+ newpsl->sl_count = count - IP6_SFBLOCK;
+ if (psl) {
+ for (i=0; i<psl->sl_count; i++)
+ newpsl->sl_addr[i] = psl->sl_addr[i];
+ sock_kfree_s(sk, psl, IP6_SFLSIZE(psl->sl_max));
+ }
+ pmc->sflist = psl = newpsl;
+ }
+ rv = 1; /* > 0 for insert logic below if sl_count is 0 */
+ for (i=0; i<psl->sl_count; i++) {
+ rv = memcmp(&psl->sl_addr[i], source, sizeof(struct in6_addr));
+ if (rv == 0)
+ break;
+ }
+ if (rv == 0) /* address already there is an error */
+ goto done;
+ for (j=psl->sl_count-1; j>=i; j--)
+ psl->sl_addr[j+1] = psl->sl_addr[j];
+ psl->sl_addr[i] = *source;
+ psl->sl_count++;
+ err = 0;
+ /* update the interface list */
+ ip6_mc_add_src(idev, group, omode, 1, source, 1);
+done:
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ dev_put(dev);
+ return err;
+}
+
+int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf)
+{
+ struct in6_addr *group;
+ struct ipv6_mc_socklist *pmc;
+ struct net_device *dev;
+ struct inet6_dev *idev;
+ struct ipv6_pinfo *inet6 = &sk->net_pinfo.af_inet6;
+ struct ip6_sf_socklist *newpsl, *psl;
+ int i, err;
+
+ group = &((struct sockaddr_in6 *)&gsf->gf_group)->sin6_addr;
+
+ if (!(ipv6_addr_type(group) & IPV6_ADDR_MULTICAST))
+ return -EINVAL;
+ if (gsf->gf_fmode != MCAST_INCLUDE &&
+ gsf->gf_fmode != MCAST_EXCLUDE)
+ return -EINVAL;
+
+ idev = ip6_mc_find_dev(group, gsf->gf_interface);
+
+ if (!idev)
+ return -ENODEV;
+ dev = idev->dev;
+ err = -EADDRNOTAVAIL;
+
+ for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) {
+ if (pmc->ifindex != gsf->gf_interface)
+ continue;
+ if (ipv6_addr_cmp(&pmc->addr, group) == 0)
+ break;
+ }
+ if (!pmc) /* must have a prior join */
+ goto done;
+ if (gsf->gf_numsrc) {
+ newpsl = (struct ip6_sf_socklist *)sock_kmalloc(sk,
+ IP6_SFLSIZE(gsf->gf_numsrc), GFP_ATOMIC);
+ if (!newpsl) {
+ err = -ENOBUFS;
+ goto done;
+ }
+ newpsl->sl_max = newpsl->sl_count = gsf->gf_numsrc;
+ for (i=0; i<newpsl->sl_count; ++i) {
+ struct sockaddr_in6 *psin6;
+
+ psin6 = (struct sockaddr_in6 *)&gsf->gf_slist[i];
+ newpsl->sl_addr[i] = psin6->sin6_addr;
+ }
+ err = ip6_mc_add_src(idev, group, gsf->gf_fmode,
+ newpsl->sl_count, newpsl->sl_addr, 0);
+ if (err) {
+ sock_kfree_s(sk, newpsl, IP6_SFLSIZE(newpsl->sl_max));
+ goto done;
+ }
+ } else
+ newpsl = 0;
+ psl = pmc->sflist;
+ if (psl) {
+ (void) ip6_mc_del_src(idev, group, pmc->sfmode,
+ psl->sl_count, psl->sl_addr, 0);
+ sock_kfree_s(sk, psl, IP6_SFLSIZE(psl->sl_max));
+ } else
+ (void) ip6_mc_del_src(idev, group, pmc->sfmode, 0, 0, 0);
+ pmc->sflist = newpsl;
+ pmc->sfmode = gsf->gf_fmode;
+done:
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ dev_put(dev);
+ return err;
+}
+
+int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf,
+ struct group_filter *optval, int *optlen)
+{
+ int err, i, count, copycount;
+ struct in6_addr *group;
+ struct ipv6_mc_socklist *pmc;
+ struct inet6_dev *idev;
+ struct net_device *dev;
+ struct ipv6_pinfo *inet6 = &sk->net_pinfo.af_inet6;
+ struct ip6_sf_socklist *psl;
+
+ group = &((struct sockaddr_in6 *)&gsf->gf_group)->sin6_addr;
+
+ if (!(ipv6_addr_type(group) & IPV6_ADDR_MULTICAST))
+ return -EINVAL;
+
+ idev = ip6_mc_find_dev(group, gsf->gf_interface);
+
+ if (!idev)
+ return -ENODEV;
+
+ dev = idev->dev;
+
+ err = -EADDRNOTAVAIL;
+
+ for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) {
+ if (pmc->ifindex != gsf->gf_interface)
+ continue;
+ if (ipv6_addr_cmp(group, &pmc->addr) == 0)
+ break;
+ }
+ if (!pmc) /* must have a prior join */
+ goto done;
+ gsf->gf_fmode = pmc->sfmode;
+ psl = pmc->sflist;
+ count = psl ? psl->sl_count : 0;
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ dev_put(dev);
+
+ copycount = count < gsf->gf_numsrc ? count : gsf->gf_numsrc;
+ gsf->gf_numsrc = count;
+ if (put_user(GROUP_FILTER_SIZE(copycount), optlen) ||
+ copy_to_user((void *)optval, gsf, GROUP_FILTER_SIZE(0))) {
+ return -EFAULT;
+ }
+ for (i=0; i<copycount; i++) {
+ struct sockaddr_in6 *psin6;
+ struct sockaddr_storage ss;
+
+ psin6 = (struct sockaddr_in6 *)&ss;
+ memset(&ss, 0, sizeof(ss));
+ psin6->sin6_family = AF_INET6;
+ psin6->sin6_addr = psl->sl_addr[i];
+ if (copy_to_user((void *)&optval->gf_slist[i], &ss, sizeof(ss)))
+ return -EFAULT;
+ }
+ return 0;
+done:
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ dev_put(dev);
+ return err;
+}
+
+int inet6_mc_check(struct sock *sk, struct in6_addr *mc_addr,
+ struct in6_addr *src_addr)
+{
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct ipv6_mc_socklist *mc;
+ struct ip6_sf_socklist *psl;
+ int rv = 1;
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ char abuf[128], sbuf[128];
+ in6_ntop(mc_addr, abuf);
+ in6_ntop(src_addr, sbuf);
+
+ MDBG3((KERN_DEBUG
+ "ipv6_sock_mc_check(sk=%p, mc_addr=%s, src_addr=%s)\n",
+ sk, abuf, sbuf));
+#endif
+
+ read_lock(&ipv6_sk_mc_lock);
+ for (mc = np->ipv6_mc_list; mc; mc = mc->next) {
+ if (ipv6_addr_cmp(&mc->addr, mc_addr) == 0)
+ break;
+ }
+ if (!mc) {
+ read_unlock(&ipv6_sk_mc_lock);
+ return 1;
+ }
+ psl = mc->sflist;
+ if (!psl) {
+ rv = mc->sfmode == MCAST_EXCLUDE;
+ } else {
+ int i;
+
+ for (i=0; i<psl->sl_count; i++) {
+ if (ipv6_addr_cmp(&psl->sl_addr[i], src_addr) == 0)
+ break;
+ }
+ if (mc->sfmode == MCAST_INCLUDE && i >= psl->sl_count)
+ rv = 0;
+ if (mc->sfmode == MCAST_EXCLUDE && i < psl->sl_count)
+ rv = 0;
+ }
+ read_unlock(&ipv6_sk_mc_lock);
+
+ return rv;
+}
+
+static void ma_put(struct ifmcaddr6 *mc)
+{
+ MDBG3((KERN_DEBUG
+ "ma_put(mc=%p): refcnt=%d\n",
+ mc, atomic_read(&mc->mca_refcnt)));
+
+ if (atomic_dec_and_test(&mc->mca_refcnt)) {
+ in6_dev_put(mc->idev);
+ kfree(mc);
+ }
+}
+
+static void igmp6_group_added(struct ifmcaddr6 *mc)
+{
+ struct net_device *dev = mc->idev->dev;
+ char buf[MAX_ADDR_LEN];
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ char abuf[128];
+#endif
+
+ spin_lock_bh(&mc->mca_lock);
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ in6_ntop(&mc->mca_addr, abuf);
+ MDBG3((KERN_DEBUG
+ "igmp6_group_added(mc=%p): mca_addr=%s, flag=%08x\n",
+ mc, abuf, mc->mca_flags));
+#endif
+ if (!(mc->mca_flags&MAF_LOADED)) {
+ mc->mca_flags |= MAF_LOADED;
+ if (ndisc_mc_map(&mc->mca_addr, buf, dev, 0) == 0)
+ dev_mc_add(dev, buf, dev->addr_len, 0);
+ }
+ spin_unlock_bh(&mc->mca_lock);
+
+ if (!(dev->flags & IFF_UP) || (mc->mca_flags & MAF_NOREPORT))
+ return;
+
+ if (MLD_V1_SEEN(mc->idev)) {
+ igmp6_join_group(mc);
+ return;
+ }
+ /* else v2 */
+
+ mc->mca_crcount = mc->idev->mc_qrv;
+ mld_ifc_event(mc->idev);
+}
+
+static void igmp6_group_dropped(struct ifmcaddr6 *mc)
+{
+ struct net_device *dev = mc->idev->dev;
+ char buf[MAX_ADDR_LEN];
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ char abuf[128];
+#endif
+
+ spin_lock_bh(&mc->mca_lock);
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ in6_ntop(&mc->mca_addr, abuf);
+ MDBG3((KERN_DEBUG
+ "igmp6_group_dropped(mc=%p): mca_addr=%s, flag=%08x\n",
+ mc, abuf, mc->mca_flags));
+#endif
+ if (mc->mca_flags&MAF_LOADED) {
+ mc->mca_flags &= ~MAF_LOADED;
+ if (ndisc_mc_map(&mc->mca_addr, buf, dev, 0) == 0)
+ dev_mc_delete(dev, buf, dev->addr_len, 0);
+ }
+
+ if (mc->mca_flags & MAF_NOREPORT)
+ goto done;
+ spin_unlock_bh(&mc->mca_lock);
+
+ if (!mc->idev->dead)
+ igmp6_leave_group(mc);
+
+ spin_lock_bh(&mc->mca_lock);
+ if (del_timer(&mc->mca_timer))
+ atomic_dec(&mc->mca_refcnt);
+done:
+ ip6_mc_clear_src(mc);
+ spin_unlock_bh(&mc->mca_lock);
+}
+
+/*
+ * deleted ifmcaddr6 manipulation
+ */
+static void mld_add_delrec(struct inet6_dev *idev, struct ifmcaddr6 *im)
+{
+ struct ifmcaddr6 *pmc;
+
+ /* this is an "ifmcaddr6" for convenience; only the fields below
+ * are actually used. In particular, the refcnt and users are not
+ * used for management of the delete list. Using the same structure
+ * for deleted items allows change reports to use common code with
+ * non-deleted or query-response MCA's.
+ */
+ pmc = (struct ifmcaddr6 *)kmalloc(sizeof(*pmc), GFP_ATOMIC);
+ if (!pmc)
+ return;
+ memset(pmc, 0, sizeof(*pmc));
+ spin_lock_bh(&im->mca_lock);
+ pmc->mca_lock = SPIN_LOCK_UNLOCKED;
+ pmc->idev = im->idev;
+ in6_dev_hold(idev);
+ pmc->mca_addr = im->mca_addr;
+ pmc->mca_crcount = idev->mc_qrv;
+ pmc->mca_sfmode = im->mca_sfmode;
+ if (pmc->mca_sfmode == MCAST_INCLUDE) {
+ struct ip6_sf_list *psf;
+
+ pmc->mca_tomb = im->mca_tomb;
+ pmc->mca_sources = im->mca_sources;
+ im->mca_tomb = im->mca_sources = 0;
+ for (psf=pmc->mca_sources; psf; psf=psf->sf_next)
+ psf->sf_crcount = pmc->mca_crcount;
+ }
+ spin_unlock_bh(&im->mca_lock);
+
+ write_lock_bh(&idev->mc_lock);
+ pmc->next = idev->mc_tomb;
+ idev->mc_tomb = pmc;
+ write_unlock_bh(&idev->mc_lock);
+}
+
+static void mld_del_delrec(struct inet6_dev *idev, struct in6_addr *pmca)
+{
+ struct ifmcaddr6 *pmc, *pmc_prev;
+ struct ip6_sf_list *psf, *psf_next;
+
+ write_lock_bh(&idev->mc_lock);
+ pmc_prev = 0;
+ for (pmc=idev->mc_tomb; pmc; pmc=pmc->next) {
+ if (ipv6_addr_cmp(&pmc->mca_addr, pmca) == 0)
+ break;
+ pmc_prev = pmc;
+ }
+ if (pmc) {
+ if (pmc_prev)
+ pmc_prev->next = pmc->next;
+ else
+ idev->mc_tomb = pmc->next;
+ }
+ write_unlock_bh(&idev->mc_lock);
+ if (pmc) {
+ for (psf=pmc->mca_tomb; psf; psf=psf_next) {
+ psf_next = psf->sf_next;
+ kfree(psf);
+ }
+ in6_dev_put(pmc->idev);
+ kfree(pmc);
+ }
+}
+
+static void mld_clear_delrec(struct inet6_dev *idev)
+{
+ struct ifmcaddr6 *pmc, *nextpmc;
+
+ write_lock_bh(&idev->mc_lock);
+ pmc = idev->mc_tomb;
+ idev->mc_tomb = 0;
+ write_unlock_bh(&idev->mc_lock);
+
+ for (; pmc; pmc = nextpmc) {
+ nextpmc = pmc->next;
+ ip6_mc_clear_src(pmc);
+ in6_dev_put(pmc->idev);
+ kfree(pmc);
+ }
+
+ /* clear dead sources, too */
+ read_lock_bh(&idev->lock);
+ for (pmc=idev->mc_list; pmc; pmc=pmc->next) {
+ struct ip6_sf_list *psf, *psf_next;
+
+ spin_lock_bh(&pmc->mca_lock);
+ psf = pmc->mca_tomb;
+ pmc->mca_tomb = 0;
+ spin_unlock_bh(&pmc->mca_lock);
+ for (; psf; psf=psf_next) {
+ psf_next = psf->sf_next;
+ kfree(psf);
+ }
+ }
+ read_unlock_bh(&idev->lock);
+}
+
+
+/*
+ * device multicast group inc (add if not found)
+ */
+int ipv6_dev_mc_inc(struct net_device *dev, struct in6_addr *addr)
+{
+ struct ifmcaddr6 *mc;
+ struct inet6_dev *idev;
+
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ char abuf[128];
+ in6_ntop(addr, abuf);
+
+ MDBG3((KERN_DEBUG
+ "ipv6_dev_mc_inc(dev=%p(%s), addr=%s)\n",
+ dev, dev->name ? dev->name :"<null>", abuf));
+#endif
+
+ idev = in6_dev_get(dev);
+
+ if (idev == NULL)
+ return -EINVAL;
+
+ write_lock_bh(&idev->lock);
+ if (idev->dead) {
+ write_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ return -ENODEV;
+ }
+
+ for (mc = idev->mc_list; mc; mc = mc->next) {
+ if (ipv6_addr_cmp(&mc->mca_addr, addr) == 0) {
+ mc->mca_users++;
+ write_unlock_bh(&idev->lock);
+ ip6_mc_add_src(idev, &mc->mca_addr, MCAST_EXCLUDE, 0,
+ 0, 0);
+ in6_dev_put(idev);
+ return 0;
+ }
+ }
+
+ /*
+ * not found: create a new one.
+ */
+
+ mc = kmalloc(sizeof(struct ifmcaddr6), GFP_ATOMIC);
+
+ if (mc == NULL) {
+ write_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ return -ENOMEM;
+ }
+
+ memset(mc, 0, sizeof(struct ifmcaddr6));
+ mc->mca_timer.function = igmp6_timer_handler;
+ mc->mca_timer.data = (unsigned long) mc;
+
+ ipv6_addr_copy(&mc->mca_addr, addr);
+ mc->idev = idev;
+ mc->mca_users = 1;
+ atomic_set(&mc->mca_refcnt, 2);
+ mc->mca_lock = SPIN_LOCK_UNLOCKED;
+
+ /* initial mode is (EX, empty) */
+ mc->mca_sfmode = MCAST_EXCLUDE;
+ mc->mca_sfcount[MCAST_EXCLUDE] = 1;
+
+ if (ipv6_addr_cmp(&mc->mca_addr, &all_nodes_addr) == 0 ||
+ IPV6_ADDR_MC_SCOPE(&mc->mca_addr) < IPV6_ADDR_SCOPE_LINKLOCAL)
+ mc->mca_flags |= MAF_NOREPORT;
+
+ mc->next = idev->mc_list;
+ idev->mc_list = mc;
+ write_unlock_bh(&idev->lock);
+
+ mld_del_delrec(idev, &mc->mca_addr);
+ igmp6_group_added(mc);
+ ma_put(mc);
+ return 0;
+}
+
+/*
+ * device multicast group del
+ */
+static int __ipv6_dev_mc_dec(struct net_device *dev, struct inet6_dev *idev, struct in6_addr *addr)
+{
+ struct ifmcaddr6 *ma, **map;
+
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ char abuf[128];
+ in6_ntop(addr, abuf);
+
+ MDBG3((KERN_DEBUG
+ "ipv6_dev_mc_dec(dev=%p(%s), idev=%p, addr=%s)\n",
+ dev, dev->name ? dev->name : "<null>", idev, abuf));
+#endif
+
+ write_lock_bh(&idev->lock);
+ for (map = &idev->mc_list; (ma=*map) != NULL; map = &ma->next) {
+ if (ipv6_addr_cmp(&ma->mca_addr, addr) == 0) {
+ if (--ma->mca_users == 0) {
+ *map = ma->next;
+ write_unlock_bh(&idev->lock);
+
+ igmp6_group_dropped(ma);
+
+ ma_put(ma);
+ return 0;
+ }
+ write_unlock_bh(&idev->lock);
+ return 0;
+ }
+ }
+ write_unlock_bh(&idev->lock);
+
+ return -ENOENT;
+}
+
+int ipv6_dev_mc_dec(struct net_device *dev, struct in6_addr *addr)
+{
+ struct inet6_dev *idev = in6_dev_get(dev);
+ int err;
+
+ if (!idev)
+ return -ENODEV;
+
+ err = __ipv6_dev_mc_dec(dev, idev, addr);
+
+ in6_dev_put(idev);
+
+ return err;
+}
+
+/*
+ * check if the interface/address pair is valid
+ */
+int ipv6_chk_mcast_addr(struct net_device *dev, struct in6_addr *group,
+ struct in6_addr *src_addr)
+{
+ struct inet6_dev *idev;
+ struct ifmcaddr6 *mc;
+ int rv = 0;
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ char abuf[128], sbuf[128];
+ in6_ntop(group, abuf);
+ in6_ntop(src_addr, sbuf);
+
+ MDBG3((KERN_DEBUG
+ "ipv6_chk_mcast_addr(dev=%p(%s), group=%s, src_addr=%s)\n",
+ dev, dev->name ? dev->name : "<null>", abuf, sbuf));
+#endif
+
+ idev = in6_dev_get(dev);
+ if (idev) {
+ read_lock_bh(&idev->lock);
+ for (mc = idev->mc_list; mc; mc=mc->next) {
+ if (ipv6_addr_cmp(&mc->mca_addr, group) == 0)
+ break;
+ }
+ if (mc) {
+ if (!ipv6_addr_any(src_addr)) {
+ struct ip6_sf_list *psf;
+
+ spin_lock_bh(&mc->mca_lock);
+ for (psf=mc->mca_sources;psf;psf=psf->sf_next) {
+ if (ipv6_addr_cmp(&psf->sf_addr,
+ src_addr) == 0)
+ break;
+ }
+ if (psf)
+ rv = psf->sf_count[MCAST_INCLUDE] ||
+ psf->sf_count[MCAST_EXCLUDE] !=
+ mc->mca_sfcount[MCAST_EXCLUDE];
+ else
+ rv = mc->mca_sfcount[MCAST_EXCLUDE] !=0;
+ spin_unlock_bh(&mc->mca_lock);
+ } else
+ rv = 1; /* don't filter unspecified source */
+ }
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ }
+ return rv;
+}
+
+static void mld_gq_start_timer(struct inet6_dev *idev)
+{
+ int tv = net_random() % idev->mc_maxdelay;
+
+ idev->mc_gq_running = 1;
+ if (!mod_timer(&idev->mc_gq_timer, jiffies+tv+2))
+ in6_dev_hold(idev);
+}
+
+static void mld_ifc_start_timer(struct inet6_dev *idev, int delay)
+{
+ int tv = net_random() % delay;
+
+ if (!mod_timer(&idev->mc_ifc_timer, jiffies+tv+2))
+ in6_dev_hold(idev);
+}
+
+/*
+ * IGMP handling (alias multicast ICMPv6 messages)
+ */
+
+static void igmp6_group_queried(struct ifmcaddr6 *ma, unsigned long resptime)
+{
+ unsigned long delay = resptime;
+
+ MDBG3((KERN_DEBUG
+ "igmp6_group_queried(ma=%p, resptime=%lu)\n",
+ ma, resptime));
+
+ /* Do not start timer for addresses with reserved/host scope */
+ if (IPV6_ADDR_MC_SCOPE(&ma->mca_addr) < IPV6_ADDR_SCOPE_LINKLOCAL ||
+ ipv6_addr_is_ll_all_nodes(&ma->mca_addr))
+ return;
+
+ if (del_timer(&ma->mca_timer)) {
+ atomic_dec(&ma->mca_refcnt);
+ delay = ma->mca_timer.expires - jiffies;
+ }
+
+ if (delay >= resptime) {
+ if (resptime) {
+ get_random_bytes(&delay, sizeof(delay));
+ delay %= resptime;
+ } else
+ delay = 1;
+ }
+
+ ma->mca_timer.expires = jiffies + delay;
+ if (!mod_timer(&ma->mca_timer, jiffies + delay))
+ atomic_inc(&ma->mca_refcnt);
+ ma->mca_flags |= MAF_TIMER_RUNNING;
+ spin_unlock(&ma->mca_lock);
+}
+
+static void mld_marksources(struct ifmcaddr6 *pmc, int nsrcs,
+ struct in6_addr *srcs)
+{
+ struct ip6_sf_list *psf;
+ int i, scount;
+
+ scount = 0;
+ for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {
+ if (scount == nsrcs)
+ break;
+ for (i=0; i<nsrcs; i++)
+ if (ipv6_addr_cmp(&srcs[i], &psf->sf_addr) == 0) {
+ psf->sf_gsresp = 1;
+ scount++;
+ break;
+ }
+ }
+}
+
+int igmp6_event_query(struct sk_buff *skb)
+{
+ struct mld2_query *mlh2 = (struct mld2_query *) skb->h.raw;
+ struct ifmcaddr6 *ma;
+ struct in6_addr *group;
+ unsigned long max_delay;
+ struct inet6_dev *idev;
+ struct icmp6hdr *hdr;
+ int group_type;
+ int mark = 0;
+ int len;
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ char abuf1[128], abuf2[128];
+
+ in6_ntop(&skb->nh.ipv6h->saddr, abuf1);
+ in6_ntop(&skb->nh.ipv6h->daddr, abuf2);
+ MDBG3((KERN_DEBUG
+ "igmp6_event_query(skb=%p): saddr=%s, daddr=%s\n",
+ skb, abuf1, abuf2));
+#endif
+
+ if (!pskb_may_pull(skb, sizeof(struct in6_addr)))
+ return -EINVAL;
+
+ len = ntohs(skb->nh.ipv6h->payload_len);
+
+ /* Drop queries with not link local source */
+ if (!(ipv6_addr_type(&skb->nh.ipv6h->saddr)&IPV6_ADDR_LINKLOCAL))
+ return -EINVAL;
+
+ idev = in6_dev_get(skb->dev);
+
+ if (idev == NULL)
+ return 0;
+
+ hdr = (struct icmp6hdr *) skb->h.raw;
+ group = (struct in6_addr *) (hdr + 1);
+ group_type = ipv6_addr_type(group);
+
+ if (group_type != IPV6_ADDR_ANY &&
+ !(group_type&IPV6_ADDR_MULTICAST)) {
+ in6_dev_put(idev);
+ return -EINVAL;
+ }
+
+ if (len == 24) {
+ int switchback;
+ /* MLDv1 router present */
+
+ /* Translate milliseconds to jiffies */
+ max_delay = (ntohs(hdr->icmp6_maxdelay)*HZ)/1000;
+
+ switchback = (idev->mc_qrv + 1) * max_delay;
+ idev->mc_v1_seen = jiffies + switchback;
+
+ /* cancel the interface change timer */
+ idev->mc_ifc_count = 0;
+ if (del_timer(&idev->mc_ifc_timer))
+ __in6_dev_put(idev);
+ /* clear deleted report items */
+ mld_clear_delrec(idev);
+ } else if (len >= 28) {
+ max_delay = (MLDV2_MRC(ntohs(mlh2->mrc))*HZ)/1000;
+ if (!max_delay)
+ max_delay = 1;
+ idev->mc_maxdelay = max_delay;
+ if (mlh2->qrv)
+ idev->mc_qrv = mlh2->qrv;
+ if (group_type == IPV6_ADDR_ANY) { /* general query */
+ if (mlh2->nsrcs) {
+ in6_dev_put(idev);
+ return -EINVAL; /* no sources allowed */
+ }
+ mld_gq_start_timer(idev);
+ in6_dev_put(idev);
+ return 0;
+ }
+ /* mark sources to include, if group & source-specific */
+ mark = mlh2->nsrcs != 0;
+ } else {
+ in6_dev_put(idev);
+ return -EINVAL;
+ }
+
+ read_lock_bh(&idev->lock);
+ if (group_type == IPV6_ADDR_ANY) {
+ for (ma = idev->mc_list; ma; ma=ma->next) {
+ spin_lock_bh(&ma->mca_lock);
+ igmp6_group_queried(ma, max_delay);
+ spin_unlock_bh(&ma->mca_lock);
+ }
+ } else {
+ for (ma = idev->mc_list; ma; ma=ma->next) {
+ if (group_type != IPV6_ADDR_ANY &&
+ ipv6_addr_cmp(group, &ma->mca_addr) != 0)
+ continue;
+ spin_lock_bh(&ma->mca_lock);
+ if (ma->mca_flags & MAF_TIMER_RUNNING) {
+ /* gsquery <- gsquery && mark */
+ if (!mark)
+ ma->mca_flags &= ~MAF_GSQUERY;
+ } else {
+ /* gsquery <- mark */
+ if (mark)
+ ma->mca_flags |= MAF_GSQUERY;
+ else
+ ma->mca_flags &= ~MAF_GSQUERY;
+ }
+ if (ma->mca_flags & MAF_GSQUERY)
+ mld_marksources(ma, ntohs(mlh2->nsrcs),
+ mlh2->srcs);
+ igmp6_group_queried(ma, max_delay);
+ spin_unlock_bh(&ma->mca_lock);
+ if (group_type != IPV6_ADDR_ANY)
+ break;
+ }
+ }
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ return 0;
+}
+
+
+int igmp6_event_report(struct sk_buff *skb)
+{
+ struct ifmcaddr6 *ma;
+ struct in6_addr *addrp;
+ struct inet6_dev *idev;
+ struct icmp6hdr *hdr;
+ int addr_type;
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ char abuf1[128], abuf2[128];
+ unsigned long resptime;
+
+ in6_ntop(&skb->nh.ipv6h->saddr, abuf1);
+ in6_ntop(&skb->nh.ipv6h->daddr, abuf2);
+ MDBG((KERN_DEBUG
+ "igmp6_event_report(skb=%p): saddr=%s, daddr=%s\n",
+ skb, abuf1, abuf2));
+#endif
+
+ /* Our own report looped back. Ignore it. */
+ if (skb->pkt_type == PACKET_LOOPBACK)
+ return 0;
+
+ if (!pskb_may_pull(skb, sizeof(struct in6_addr)))
+ return -EINVAL;
+
+ hdr = (struct icmp6hdr*) skb->h.raw;
+
+ /* Drop reports with not link local source */
+ addr_type = ipv6_addr_type(&skb->nh.ipv6h->saddr);
+ if (addr_type != IPV6_ADDR_ANY &&
+ !(addr_type&IPV6_ADDR_LINKLOCAL))
+ return -EINVAL;
+
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ resptime = ntohs(hdr->icmp6_maxdelay);
+#endif
+ addrp = (struct in6_addr *) (hdr + 1);
+
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ in6_ntop(addrp, abuf1);
+ MDBG3((KERN_DEBUG
+ "igmp6_event_report(): maxdelay=%lu, addr=%s\n",
+ resptime, abuf1));
+#endif
+
+ if (!ipv6_addr_is_multicast(addrp))
+ goto drop;
+
+ idev = in6_dev_get(skb->dev);
+ if (idev == NULL)
+ return -ENODEV;
+
+ /*
+ * Cancel the timer for this group
+ */
+
+ read_lock_bh(&idev->lock);
+ for (ma = idev->mc_list; ma; ma=ma->next) {
+ if (ipv6_addr_cmp(&ma->mca_addr, addrp) == 0) {
+ spin_lock(&ma->mca_lock);
+ if (del_timer(&ma->mca_timer))
+ atomic_dec(&ma->mca_refcnt);
+#ifndef CONFIG_IPV6_MLD6_ALL_DONE
+ ma->mca_flags &= ~(MAF_LAST_REPORTER|MAF_TIMER_RUNNING);
+#else
+ ma->mca_flags &= ~MAF_TIMER_RUNNING;
+#endif
+ spin_unlock(&ma->mca_lock);
+ break;
+ }
+ }
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ drop:
+ return 0;
+}
+
+static int is_in(struct ifmcaddr6 *pmc, struct ip6_sf_list *psf, int type,
+ int gdeleted, int sdeleted)
+{
+ switch (type) {
+ case MLD2_MODE_IS_INCLUDE:
+ case MLD2_MODE_IS_EXCLUDE:
+ if (gdeleted || sdeleted)
+ return 0;
+ return !((pmc->mca_flags & MAF_GSQUERY) && !psf->sf_gsresp);
+ case MLD2_CHANGE_TO_INCLUDE:
+ if (gdeleted || sdeleted)
+ return 0;
+ return psf->sf_count[MCAST_INCLUDE] != 0;
+ case MLD2_CHANGE_TO_EXCLUDE:
+ if (gdeleted || sdeleted)
+ return 0;
+ if (pmc->mca_sfcount[MCAST_EXCLUDE] == 0 ||
+ psf->sf_count[MCAST_INCLUDE])
+ return 0;
+ return pmc->mca_sfcount[MCAST_EXCLUDE] ==
+ psf->sf_count[MCAST_EXCLUDE];
+ case MLD2_ALLOW_NEW_SOURCES:
+ if (gdeleted || !psf->sf_crcount)
+ return 0;
+ return (pmc->mca_sfmode == MCAST_INCLUDE) ^ sdeleted;
+ case MLD2_BLOCK_OLD_SOURCES:
+ if (pmc->mca_sfmode == MCAST_INCLUDE)
+ return gdeleted || (psf->sf_crcount && sdeleted);
+ return psf->sf_crcount && !gdeleted && !sdeleted;
+ }
+ return 0;
+}
+
+static int
+mld_scount(struct ifmcaddr6 *pmc, int type, int gdeleted, int sdeleted)
+{
+ struct ip6_sf_list *psf;
+ int scount = 0;
+
+ for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {
+ if (!is_in(pmc, psf, type, gdeleted, sdeleted))
+ continue;
+ scount++;
+ }
+ return scount;
+}
+
+static struct sk_buff *mld_newpack(struct net_device *dev, int size)
+{
+ struct sock *sk = igmp6_socket->sk;
+ struct sk_buff *skb;
+ struct mld2_report *pmr;
+ struct in6_addr addr_buf;
+ int err;
+ u8 ra[8] = { IPPROTO_ICMPV6, 0,
+ IPV6_TLV_ROUTERALERT, 2, 0, 0,
+ IPV6_TLV_PADN, 0 };
+
+ skb = sock_alloc_send_skb(sk, size + dev->hard_header_len+15, 1, &err);
+
+ if (skb == 0)
+ return 0;
+
+ skb_reserve(skb, (dev->hard_header_len + 15) & ~15);
+ if (dev->hard_header) {
+ unsigned char ha[MAX_ADDR_LEN];
+
+ ndisc_mc_map(&mld2_all_mcr, ha, dev, 1);
+ if (dev->hard_header(skb, dev, ETH_P_IPV6,ha,NULL,size) < 0) {
+ kfree_skb(skb);
+ return 0;
+ }
+ }
+
+ if (ipv6_get_lladdr(dev, &addr_buf)) {
+ /* <draft-ietf-magma-mld-source-02.txt>:
+ * use unspecified address as the source address
+ * when a valid link-local address is not available.
+ */
+ memset(&addr_buf, 0, sizeof(addr_buf));
+ }
+
+ ip6_nd_hdr(sk, skb, dev, &addr_buf, &mld2_all_mcr, NEXTHDR_HOP, 0);
+
+ memcpy(skb_put(skb, sizeof(ra)), ra, sizeof(ra));
+
+ pmr =(struct mld2_report *)skb_put(skb, sizeof(*pmr));
+ skb->h.raw = (unsigned char *)pmr;
+ pmr->type = ICMPV6_MLD2_REPORT;
+ pmr->resv1 = 0;
+ pmr->csum = 0;
+ pmr->resv2 = 0;
+ pmr->ngrec = 0;
+ return skb;
+}
+
+static void mld_sendpack(struct sk_buff *skb)
+{
+ struct ipv6hdr *pip6 = skb->nh.ipv6h;
+ struct mld2_report *pmr = (struct mld2_report *)skb->h.raw;
+ struct inet6_dev *idev;
+ int payload_len, mldlen, err;
+
+ payload_len = skb->tail - (unsigned char *)skb->nh.ipv6h -
+ sizeof(struct ipv6hdr);
+ mldlen = skb->tail - skb->h.raw;
+ pip6->payload_len = htons(payload_len);
+
+ pmr->csum = csum_ipv6_magic(&pip6->saddr, &pip6->daddr, mldlen,
+ IPPROTO_ICMPV6, csum_partial(skb->h.raw, mldlen, 0));
+ idev = in6_dev_get(skb->dev);
+
+ err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, skb->dev,
+ dev_queue_xmit);
+ if (!err)
+ ICMP6_INC_STATS(idev, Icmp6OutMsgs);
+
+ if (idev)
+ in6_dev_put(idev);
+}
+
+static int grec_size(struct ifmcaddr6 *pmc, int type, int gdel, int sdel)
+{
+ return sizeof(struct mld2_grec) + 4*mld_scount(pmc,type,gdel,sdel);
+}
+
+static struct sk_buff *add_grhead(struct sk_buff *skb, struct ifmcaddr6 *pmc,
+ int type, struct mld2_grec **ppgr)
+{
+ struct net_device *dev = pmc->idev->dev;
+ struct mld2_report *pmr;
+ struct mld2_grec *pgr;
+
+ if (!skb)
+ skb = mld_newpack(dev, dev->mtu);
+ if (!skb)
+ return 0;
+ pgr = (struct mld2_grec *)skb_put(skb, sizeof(struct mld2_grec));
+ pgr->grec_type = type;
+ pgr->grec_auxwords = 0;
+ pgr->grec_nsrcs = 0;
+ pgr->grec_mca = pmc->mca_addr; /* structure copy */
+ pmr = (struct mld2_report *)skb->h.raw;
+ pmr->ngrec = htons(ntohs(pmr->ngrec)+1);
+ *ppgr = pgr;
+ return skb;
+}
+
+#define AVAILABLE(skb) ((skb) ? ((skb)->dev ? (skb)->dev->mtu - (skb)->len : \
+ skb_tailroom(skb)) : 0)
+
+static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc,
+ int type, int gdeleted, int sdeleted)
+{
+ struct net_device *dev = pmc->idev->dev;
+ struct mld2_report *pmr;
+ struct mld2_grec *pgr = 0;
+ struct ip6_sf_list *psf, *psf_next, *psf_prev, **psf_list;
+ int scount, first, isquery, truncate;
+
+ if (pmc->mca_flags & MAF_NOREPORT)
+ return skb;
+
+ isquery = type == MLD2_MODE_IS_INCLUDE ||
+ type == MLD2_MODE_IS_EXCLUDE;
+ truncate = type == MLD2_MODE_IS_EXCLUDE ||
+ type == MLD2_CHANGE_TO_EXCLUDE;
+
+ psf_list = sdeleted ? &pmc->mca_tomb : &pmc->mca_sources;
+
+ if (!*psf_list) {
+ if (type == MLD2_ALLOW_NEW_SOURCES ||
+ type == MLD2_BLOCK_OLD_SOURCES)
+ return skb;
+ if (pmc->mca_crcount || isquery) {
+ /* make sure we have room for group header and at
+ * least one source.
+ */
+ if (skb && AVAILABLE(skb) < sizeof(struct mld2_grec)+
+ sizeof(struct in6_addr)) {
+ mld_sendpack(skb);
+ skb = 0; /* add_grhead will get a new one */
+ }
+ skb = add_grhead(skb, pmc, type, &pgr);
+ }
+ return skb;
+ }
+ pmr = skb ? (struct mld2_report *)skb->h.raw : 0;
+
+ /* EX and TO_EX get a fresh packet, if needed */
+ if (truncate) {
+ if (pmr && pmr->ngrec &&
+ AVAILABLE(skb) < grec_size(pmc, type, gdeleted, sdeleted)) {
+ if (skb)
+ mld_sendpack(skb);
+ skb = mld_newpack(dev, dev->mtu);
+ }
+ }
+ first = 1;
+ scount = 0;
+ psf_prev = 0;
+ for (psf=*psf_list; psf; psf=psf_next) {
+ struct in6_addr *psrc;
+
+ psf_next = psf->sf_next;
+
+ if (!is_in(pmc, psf, type, gdeleted, sdeleted)) {
+ psf_prev = psf;
+ continue;
+ }
+
+ /* clear marks on query responses */
+ if (isquery)
+ psf->sf_gsresp = 0;
+
+ if (AVAILABLE(skb) < sizeof(*psrc) +
+ first*sizeof(struct mld2_grec)) {
+ if (truncate && !first)
+ break; /* truncate these */
+ if (pgr)
+ pgr->grec_nsrcs = htons(scount);
+ if (skb)
+ mld_sendpack(skb);
+ skb = mld_newpack(dev, dev->mtu);
+ first = 1;
+ scount = 0;
+ }
+ if (first) {
+ skb = add_grhead(skb, pmc, type, &pgr);
+ first = 0;
+ }
+ psrc = (struct in6_addr *)skb_put(skb, sizeof(*psrc));
+ *psrc = psf->sf_addr;
+ scount++;
+ if ((type == MLD2_ALLOW_NEW_SOURCES ||
+ type == MLD2_BLOCK_OLD_SOURCES) && psf->sf_crcount) {
+ psf->sf_crcount--;
+ if ((sdeleted || gdeleted) && psf->sf_crcount == 0) {
+ if (psf_prev)
+ psf_prev->sf_next = psf->sf_next;
+ else
+ *psf_list = psf->sf_next;
+ kfree(psf);
+ continue;
+ }
+ }
+ psf_prev = psf;
+ }
+ if (pgr)
+ pgr->grec_nsrcs = htons(scount);
+
+ if (isquery)
+ pmc->mca_flags &= ~MAF_GSQUERY; /* clear query state */
+ return skb;
+}
+
+static void mld_send_report(struct inet6_dev *idev, struct ifmcaddr6 *pmc)
+{
+ struct sk_buff *skb = 0;
+ int type;
+
+ if (!pmc) {
+ read_lock_bh(&idev->lock);
+ for (pmc=idev->mc_list; pmc; pmc=pmc->next) {
+ if (pmc->mca_flags & MAF_NOREPORT)
+ continue;
+ spin_lock_bh(&pmc->mca_lock);
+ if (pmc->mca_sfcount[MCAST_EXCLUDE])
+ type = MLD2_MODE_IS_EXCLUDE;
+ else
+ type = MLD2_MODE_IS_INCLUDE;
+ skb = add_grec(skb, pmc, type, 0, 0);
+ spin_unlock_bh(&pmc->mca_lock);
+ }
+ read_unlock_bh(&idev->lock);
+ } else {
+ spin_lock_bh(&pmc->mca_lock);
+ if (pmc->mca_sfcount[MCAST_EXCLUDE])
+ type = MLD2_MODE_IS_EXCLUDE;
+ else
+ type = MLD2_MODE_IS_INCLUDE;
+ skb = add_grec(skb, pmc, type, 0, 0);
+ spin_unlock_bh(&pmc->mca_lock);
+ }
+ if (skb)
+ mld_sendpack(skb);
+}
+
+/*
+ * remove zero-count source records from a source filter list
+ */
+static void mld_clear_zeros(struct ip6_sf_list **ppsf)
+{
+ struct ip6_sf_list *psf_prev, *psf_next, *psf;
+
+ psf_prev = 0;
+ for (psf=*ppsf; psf; psf = psf_next) {
+ psf_next = psf->sf_next;
+ if (psf->sf_crcount == 0) {
+ if (psf_prev)
+ psf_prev->sf_next = psf->sf_next;
+ else
+ *ppsf = psf->sf_next;
+ kfree(psf);
+ } else
+ psf_prev = psf;
+ }
+}
+
+static void mld_send_cr(struct inet6_dev *idev)
+{
+ struct ifmcaddr6 *pmc, *pmc_prev, *pmc_next;
+ struct sk_buff *skb = 0;
+ int type, dtype;
+
+ read_lock_bh(&idev->lock);
+ write_lock_bh(&idev->mc_lock);
+
+ /* deleted MCA's */
+ pmc_prev = 0;
+ for (pmc=idev->mc_tomb; pmc; pmc=pmc_next) {
+ pmc_next = pmc->next;
+ if (pmc->mca_sfmode == MCAST_INCLUDE) {
+ type = MLD2_BLOCK_OLD_SOURCES;
+ dtype = MLD2_BLOCK_OLD_SOURCES;
+ skb = add_grec(skb, pmc, type, 1, 0);
+ skb = add_grec(skb, pmc, dtype, 1, 1);
+ }
+ if (pmc->mca_crcount) {
+ pmc->mca_crcount--;
+ if (pmc->mca_sfmode == MCAST_EXCLUDE) {
+ type = MLD2_CHANGE_TO_INCLUDE;
+ skb = add_grec(skb, pmc, type, 1, 0);
+ }
+ if (pmc->mca_crcount == 0) {
+ mld_clear_zeros(&pmc->mca_tomb);
+ mld_clear_zeros(&pmc->mca_sources);
+ }
+ }
+ if (pmc->mca_crcount == 0 && !pmc->mca_tomb &&
+ !pmc->mca_sources) {
+ if (pmc_prev)
+ pmc_prev->next = pmc_next;
+ else
+ idev->mc_tomb = pmc_next;
+ in6_dev_put(pmc->idev);
+ kfree(pmc);
+ } else
+ pmc_prev = pmc;
+ }
+ write_unlock_bh(&idev->mc_lock);
+
+ /* change recs */
+ for (pmc=idev->mc_list; pmc; pmc=pmc->next) {
+ spin_lock_bh(&pmc->mca_lock);
+ if (pmc->mca_sfcount[MCAST_EXCLUDE]) {
+ type = MLD2_BLOCK_OLD_SOURCES;
+ dtype = MLD2_ALLOW_NEW_SOURCES;
+ } else {
+ type = MLD2_ALLOW_NEW_SOURCES;
+ dtype = MLD2_BLOCK_OLD_SOURCES;
+ }
+ skb = add_grec(skb, pmc, type, 0, 0);
+ skb = add_grec(skb, pmc, dtype, 0, 1); /* deleted sources */
+
+ /* filter mode changes */
+ if (pmc->mca_crcount) {
+ pmc->mca_crcount--;
+ if (pmc->mca_sfmode == MCAST_EXCLUDE)
+ type = MLD2_CHANGE_TO_EXCLUDE;
+ else
+ type = MLD2_CHANGE_TO_INCLUDE;
+ skb = add_grec(skb, pmc, type, 0, 0);
+ }
+ spin_unlock_bh(&pmc->mca_lock);
+ }
+ read_unlock_bh(&idev->lock);
+ if (!skb)
+ return;
+ (void) mld_sendpack(skb);
+}
+
+void igmp6_send(struct in6_addr *addr, struct net_device *dev, int type)
+{
+ struct sock *sk = igmp6_socket->sk;
+ struct sk_buff *skb;
+ struct inet6_dev *idev;
+ struct icmp6hdr *hdr;
+ struct in6_addr *snd_addr;
+ struct in6_addr *addrp;
+ struct in6_addr addr_buf;
+ struct in6_addr all_routers;
+ int err, len, payload_len, full_len;
+ u8 ra[8] = { IPPROTO_ICMPV6, 0,
+ IPV6_TLV_ROUTERALERT, 2, 0, 0,
+ IPV6_TLV_PADN, 0 };
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ char abuf[128];
+ in6_ntop(addr, abuf);
+
+ MDBG3((KERN_DEBUG
+ "igmp6_send(addr=%s, dev=%p(%s), type=%d)\n",
+ abuf, dev, dev->name ? dev->name :"<null>", type));
+#endif
+
+ snd_addr = addr;
+ if (type == ICMPV6_MGM_REDUCTION) {
+ snd_addr = &all_routers;
+ ipv6_addr_all_routers(&all_routers);
+ }
+
+ len = sizeof(struct icmp6hdr) + sizeof(struct in6_addr);
+ payload_len = len + sizeof(ra);
+ full_len = sizeof(struct ipv6hdr) + payload_len;
+
+ skb = sock_alloc_send_skb(sk, dev->hard_header_len + full_len + 15, 1, &err);
+
+ if (skb == NULL)
+ return;
+
+ skb_reserve(skb, (dev->hard_header_len + 15) & ~15);
+ if (dev->hard_header) {
+ unsigned char ha[MAX_ADDR_LEN];
+ ndisc_mc_map(snd_addr, ha, dev, 1);
+ if (dev->hard_header(skb, dev, ETH_P_IPV6, ha, NULL, full_len) < 0)
+ goto out;
+ }
+
+ if (ipv6_get_lladdr(dev, &addr_buf)) {
+ /* <draft-ietf-magma-mld-source-02.txt>:
+ * use unspecified address as the source address
+ * when a valid link-local address is not available.
+ */
+ MDBG2((KERN_WARNING "igmp6: %s no linklocal address; use unspecified address\n",
+ dev->name));
+ memset(&addr_buf, 0, sizeof(addr_buf));
+ }
+
+ ip6_nd_hdr(sk, skb, dev, &addr_buf, snd_addr, NEXTHDR_HOP, payload_len);
+
+ memcpy(skb_put(skb, sizeof(ra)), ra, sizeof(ra));
+
+ hdr = (struct icmp6hdr *) skb_put(skb, sizeof(struct icmp6hdr));
+ memset(hdr, 0, sizeof(struct icmp6hdr));
+ hdr->icmp6_type = type;
+
+ addrp = (struct in6_addr *) skb_put(skb, sizeof(struct in6_addr));
+ ipv6_addr_copy(addrp, addr);
+
+ hdr->icmp6_cksum = csum_ipv6_magic(&addr_buf, snd_addr, len,
+ IPPROTO_ICMPV6,
+ csum_partial((__u8 *) hdr, len, 0));
+
+ err = NF_HOOK(PF_INET6, NF_IP6_LOCAL_OUT, skb, NULL, skb->dev,
+ dev_queue_xmit);
+ idev = in6_dev_get(dev);
+ if (!err) {
+ if (type == ICMPV6_MGM_REDUCTION)
+ ICMP6_INC_STATS(idev, Icmp6OutGroupMembReductions);
+ else
+ ICMP6_INC_STATS(idev, Icmp6OutGroupMembResponses);
+ ICMP6_INC_STATS(idev, Icmp6OutMsgs);
+ }
+ if (idev)
+ in6_dev_put(idev);
+
+ return;
+
+out:
+ kfree_skb(skb);
+}
+
+static int ip6_mc_del1_src(struct ifmcaddr6 *pmc, int sfmode,
+ struct in6_addr *psfsrc)
+{
+ struct ip6_sf_list *psf, *psf_prev;
+ int rv = 0;
+
+ psf_prev = 0;
+ for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {
+ if (ipv6_addr_cmp(&psf->sf_addr, psfsrc) == 0)
+ break;
+ psf_prev = psf;
+ }
+ if (!psf || psf->sf_count[sfmode] == 0) {
+ /* source filter not found, or count wrong => bug */
+ return -ESRCH;
+ }
+ psf->sf_count[sfmode]--;
+ if (!psf->sf_count[MCAST_INCLUDE] && !psf->sf_count[MCAST_EXCLUDE]) {
+ struct inet6_dev *idev = pmc->idev;
+
+ /* no more filters for this source */
+ if (psf_prev)
+ psf_prev->sf_next = psf->sf_next;
+ else
+ pmc->mca_sources = psf->sf_next;
+ if (psf->sf_oldin && !(pmc->mca_flags & MAF_NOREPORT) &&
+ !MLD_V1_SEEN(idev)) {
+ psf->sf_crcount = idev->mc_qrv;
+ psf->sf_next = pmc->mca_tomb;
+ pmc->mca_tomb = psf;
+ rv = 1;
+ } else
+ kfree(psf);
+ }
+ return rv;
+}
+
+int ip6_mc_del_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode,
+ int sfcount, struct in6_addr *psfsrc, int delta)
+{
+ struct ifmcaddr6 *pmc;
+ int changerec = 0;
+ int i, err;
+
+ if (!idev)
+ return -ENODEV;
+ read_lock_bh(&idev->lock);
+ for (pmc=idev->mc_list; pmc; pmc=pmc->next) {
+ if (ipv6_addr_cmp(pmca, &pmc->mca_addr) == 0)
+ break;
+ }
+ if (!pmc) {
+ /* MCA not found?? bug */
+ read_unlock_bh(&idev->lock);
+ return -ESRCH;
+ }
+ spin_lock_bh(&pmc->mca_lock);
+ sf_markstate(pmc);
+ if (!delta) {
+ if (!pmc->mca_sfcount[sfmode]) {
+ spin_unlock_bh(&pmc->mca_lock);
+ read_unlock_bh(&idev->lock);
+ return -EINVAL;
+ }
+ pmc->mca_sfcount[sfmode]--;
+ }
+ err = 0;
+ for (i=0; i<sfcount; i++) {
+ int rv = ip6_mc_del1_src(pmc, sfmode, &psfsrc[i]);
+
+ changerec |= rv > 0;
+ if (!err && rv < 0)
+ err = rv;
+ }
+ if (pmc->mca_sfmode == MCAST_EXCLUDE &&
+ pmc->mca_sfcount[MCAST_EXCLUDE] == 0 &&
+ pmc->mca_sfcount[MCAST_INCLUDE]) {
+ struct ip6_sf_list *psf;
+
+ /* filter mode change */
+ pmc->mca_sfmode = MCAST_INCLUDE;
+ pmc->mca_crcount = idev->mc_qrv;
+ idev->mc_ifc_count = pmc->mca_crcount;
+ for (psf=pmc->mca_sources; psf; psf = psf->sf_next)
+ psf->sf_crcount = 0;
+ mld_ifc_event(pmc->idev);
+ } else if (sf_setstate(pmc) || changerec)
+ mld_ifc_event(pmc->idev);
+ spin_unlock_bh(&pmc->mca_lock);
+ read_unlock_bh(&idev->lock);
+ return err;
+}
+
+/*
+ * Add multicast single-source filter to the interface list
+ */
+static int ip6_mc_add1_src(struct ifmcaddr6 *pmc, int sfmode,
+ struct in6_addr *psfsrc, int delta)
+{
+ struct ip6_sf_list *psf, *psf_prev;
+
+ psf_prev = 0;
+ for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {
+ if (ipv6_addr_cmp(&psf->sf_addr, psfsrc) == 0)
+ break;
+ psf_prev = psf;
+ }
+ if (!psf) {
+ psf = (struct ip6_sf_list *)kmalloc(sizeof(*psf), GFP_ATOMIC);
+ if (!psf)
+ return -ENOBUFS;
+ memset(psf, 0, sizeof(*psf));
+ psf->sf_addr = *psfsrc;
+ if (psf_prev) {
+ psf_prev->sf_next = psf;
+ } else
+ pmc->mca_sources = psf;
+ }
+ psf->sf_count[sfmode]++;
+ return 0;
+}
+
+static void sf_markstate(struct ifmcaddr6 *pmc)
+{
+ struct ip6_sf_list *psf;
+ int mca_xcount = pmc->mca_sfcount[MCAST_EXCLUDE];
+
+ for (psf=pmc->mca_sources; psf; psf=psf->sf_next)
+ if (pmc->mca_sfcount[MCAST_EXCLUDE]) {
+ psf->sf_oldin = mca_xcount ==
+ psf->sf_count[MCAST_EXCLUDE] &&
+ !psf->sf_count[MCAST_INCLUDE];
+ } else
+ psf->sf_oldin = psf->sf_count[MCAST_INCLUDE] != 0;
+}
+
+static int sf_setstate(struct ifmcaddr6 *pmc)
+{
+ struct ip6_sf_list *psf;
+ int mca_xcount = pmc->mca_sfcount[MCAST_EXCLUDE];
+ int qrv = pmc->idev->mc_qrv;
+ int new_in, rv;
+
+ rv = 0;
+ for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {
+ if (pmc->mca_sfcount[MCAST_EXCLUDE]) {
+ new_in = mca_xcount == psf->sf_count[MCAST_EXCLUDE] &&
+ !psf->sf_count[MCAST_INCLUDE];
+ } else
+ new_in = psf->sf_count[MCAST_INCLUDE] != 0;
+ if (new_in != psf->sf_oldin) {
+ psf->sf_crcount = qrv;
+ rv++;
+ }
+ }
+ return rv;
+}
+
+/*
+ * Add multicast source filter list to the interface list
+ */
+int ip6_mc_add_src(struct inet6_dev *idev, struct in6_addr *pmca, int sfmode,
+ int sfcount, struct in6_addr *psfsrc, int delta)
+{
+ struct ifmcaddr6 *pmc;
+ int isexclude;
+ int i, err;
+
+ if (!idev)
+ return -ENODEV;
+ read_lock_bh(&idev->lock);
+ for (pmc=idev->mc_list; pmc; pmc=pmc->next) {
+ if (ipv6_addr_cmp(pmca, &pmc->mca_addr) == 0)
+ break;
+ }
+ if (!pmc) {
+ /* MCA not found?? bug */
+ read_unlock_bh(&idev->lock);
+ return -ESRCH;
+ }
+ spin_lock_bh(&pmc->mca_lock);
+
+ sf_markstate(pmc);
+ isexclude = pmc->mca_sfmode == MCAST_EXCLUDE;
+ if (!delta)
+ pmc->mca_sfcount[sfmode]++;
+ err = 0;
+ for (i=0; i<sfcount; i++) {
+ err = ip6_mc_add1_src(pmc, sfmode, &psfsrc[i], delta);
+ if (err)
+ break;
+ }
+ if (err) {
+ int j;
+
+ pmc->mca_sfcount[sfmode]--;
+ for (j=0; j<i; j++)
+ (void) ip6_mc_del1_src(pmc, sfmode, &psfsrc[i]);
+ } else if (isexclude != (pmc->mca_sfcount[MCAST_EXCLUDE] != 0)) {
+ struct inet6_dev *idev = pmc->idev;
+ struct ip6_sf_list *psf;
+
+ /* filter mode change */
+ if (pmc->mca_sfcount[MCAST_EXCLUDE])
+ pmc->mca_sfmode = MCAST_EXCLUDE;
+ else if (pmc->mca_sfcount[MCAST_INCLUDE])
+ pmc->mca_sfmode = MCAST_INCLUDE;
+ /* else no filters; keep old mode for reports */
+
+ pmc->mca_crcount = idev->mc_qrv;
+ idev->mc_ifc_count = pmc->mca_crcount;
+ for (psf=pmc->mca_sources; psf; psf = psf->sf_next)
+ psf->sf_crcount = 0;
+ mld_ifc_event(idev);
+ } else if (sf_setstate(pmc))
+ mld_ifc_event(idev);
+ spin_unlock_bh(&pmc->mca_lock);
+ read_unlock_bh(&idev->lock);
+ return err;
+}
+
+static void ip6_mc_clear_src(struct ifmcaddr6 *pmc)
+{
+ struct ip6_sf_list *psf, *nextpsf;
+
+ for (psf=pmc->mca_tomb; psf; psf=nextpsf) {
+ nextpsf = psf->sf_next;
+ kfree(psf);
+ }
+ pmc->mca_tomb = 0;
+ for (psf=pmc->mca_sources; psf; psf=nextpsf) {
+ nextpsf = psf->sf_next;
+ kfree(psf);
+ }
+ pmc->mca_sources = 0;
+ pmc->mca_sfmode = MCAST_EXCLUDE;
+ pmc->mca_sfcount[MCAST_EXCLUDE] = 0;
+ pmc->mca_sfcount[MCAST_EXCLUDE] = 1;
+}
+
+static void igmp6_join_group(struct ifmcaddr6 *ma)
+{
+ unsigned long delay;
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ char abuf[128];
+ in6_ntop(&ma->mca_addr, abuf);
+
+ MDBG3((KERN_DEBUG
+ "igmp6_join_group(ma=%p): mca_addr=%s\n",
+ ma, abuf));
+#endif
+
+ if (ma->mca_flags & MAF_NOREPORT)
+ return;
+
+ igmp6_send(&ma->mca_addr, ma->idev->dev, ICMPV6_MGM_REPORT);
+
+ get_random_bytes(&delay, sizeof(delay));
+ delay %= IGMP6_UNSOLICITED_IVAL;
+
+ spin_lock_bh(&ma->mca_lock);
+ if (del_timer(&ma->mca_timer)) {
+ atomic_dec(&ma->mca_refcnt);
+ delay = ma->mca_timer.expires - jiffies;
+ }
+
+ if (!mod_timer(&ma->mca_timer, jiffies + delay))
+ atomic_inc(&ma->mca_refcnt);
+ ma->mca_flags |= MAF_TIMER_RUNNING | MAF_LAST_REPORTER;
+ spin_unlock_bh(&ma->mca_lock);
+}
+
+int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml,
+ struct inet6_dev *idev)
+{
+ int err;
+
+ if (iml->sflist == 0) {
+ /* any-source empty exclude case */
+ return ip6_mc_del_src(idev, &iml->addr, iml->sfmode, 0, 0, 0);
+ }
+ err = ip6_mc_del_src(idev, &iml->addr, iml->sfmode,
+ iml->sflist->sl_count, iml->sflist->sl_addr, 0);
+ sock_kfree_s(sk, iml->sflist, IP6_SFLSIZE(iml->sflist->sl_max));
+ iml->sflist = 0;
+ return err;
+}
+
+static void igmp6_leave_group(struct ifmcaddr6 *ma)
+{
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ char abuf[128];
+ in6_ntop(&ma->mca_addr, abuf);
+
+ MDBG3((KERN_DEBUG
+ "igmp6_leave_group(ma=%p): mca_addr=%s\n",
+ ma, abuf));
+#endif
+
+ if (MLD_V1_SEEN(ma->idev)) {
+ if (ma->mca_flags & MAF_LAST_REPORTER)
+ igmp6_send(&ma->mca_addr, ma->idev->dev,
+ ICMPV6_MGM_REDUCTION);
+ } else {
+ mld_add_delrec(ma->idev, ma);
+ mld_ifc_event(ma->idev);
+ }
+}
+
+static void mld_gq_timer_expire(unsigned long data)
+{
+ struct inet6_dev *idev = (struct inet6_dev *)data;
+
+ idev->mc_gq_running = 0;
+ mld_send_report(idev, 0);
+ __in6_dev_put(idev);
+}
+
+static void mld_ifc_timer_expire(unsigned long data)
+{
+ struct inet6_dev *idev = (struct inet6_dev *)data;
+
+ mld_send_cr(idev);
+ if (idev->mc_ifc_count) {
+ idev->mc_ifc_count--;
+ if (idev->mc_ifc_count)
+ mld_ifc_start_timer(idev, idev->mc_maxdelay);
+ }
+ __in6_dev_put(idev);
+}
+
+static void mld_ifc_event(struct inet6_dev *idev)
+{
+ if (MLD_V1_SEEN(idev))
+ return;
+ idev->mc_ifc_count = idev->mc_qrv;
+ mld_ifc_start_timer(idev, 1);
+}
+
+
+static void igmp6_timer_handler(unsigned long data)
+{
+ struct ifmcaddr6 *ma = (struct ifmcaddr6 *) data;
+#ifdef CONFIG_IPV6_MLD6_DEBUG
+ char abuf[128];
+ in6_ntop(&ma->mca_addr, abuf);
+
+ MDBG3((KERN_DEBUG
+ "igmp6_timer_handler(ma=%p): mca_addr=%s\n",
+ ma, abuf));
+#endif
+
+ if (MLD_V1_SEEN(ma->idev))
+ igmp6_send(&ma->mca_addr, ma->idev->dev, ICMPV6_MGM_REPORT);
+ else
+ mld_send_report(ma->idev, ma);
+
+ spin_lock(&ma->mca_lock);
+ ma->mca_flags |= MAF_LAST_REPORTER;
+ ma->mca_flags &= ~MAF_TIMER_RUNNING;
+ spin_unlock(&ma->mca_lock);
+ ma_put(ma);
+}
+
+/* Device going down */
+
+void ipv6_mc_down(struct inet6_dev *idev)
+{
+ struct ifmcaddr6 *i;
+
+ MDBG3((KERN_DEBUG
+ "ipv6_mc_down(idev=%p)\n", idev));
+
+ /* Withdraw multicast list */
+
+ read_lock_bh(&idev->lock);
+ idev->mc_ifc_count = 0;
+ if (del_timer(&idev->mc_ifc_timer))
+ __in6_dev_put(idev);
+ idev->mc_gq_running = 0;
+ if (del_timer(&idev->mc_gq_timer))
+ __in6_dev_put(idev);
+
+ for (i = idev->mc_list; i; i=i->next)
+ igmp6_group_dropped(i);
+ read_unlock_bh(&idev->lock);
+
+ mld_clear_delrec(idev);
+}
+
+
+/* Device going up */
+
+void ipv6_mc_up(struct inet6_dev *idev)
+{
+ struct ifmcaddr6 *i;
+
+ /* Install multicast list, except for all-nodes (already installed) */
+
+ read_lock_bh(&idev->lock);
+ for (i = idev->mc_list; i; i=i->next)
+ igmp6_group_added(i);
+ read_unlock_bh(&idev->lock);
+}
+
+/* IPv6 device initialization. */
+
+void ipv6_mc_init_dev(struct inet6_dev *idev)
+{
+ struct in6_addr maddr;
+
+ write_lock_bh(&idev->lock);
+ idev->mc_lock = RW_LOCK_UNLOCKED;
+ idev->mc_gq_running = 0;
+ init_timer(&idev->mc_gq_timer);
+ idev->mc_gq_timer.data = (unsigned long) idev;
+ idev->mc_gq_timer.function = &mld_gq_timer_expire;
+ idev->mc_tomb = 0;
+ idev->mc_ifc_count = 0;
+ init_timer(&idev->mc_ifc_timer);
+ idev->mc_ifc_timer.data = (unsigned long) idev;
+ idev->mc_ifc_timer.function = &mld_ifc_timer_expire;
+ idev->mc_qrv = MLD_QRV_DEFAULT;
+ idev->mc_maxdelay = IGMP6_UNSOLICITED_IVAL;
+ idev->mc_v1_seen = 0;
+ write_unlock_bh(&idev->lock);
+
+ /* Add all-nodes address. */
+ ipv6_addr_all_nodes(&maddr);
+ ipv6_dev_mc_inc(idev->dev, &maddr);
+
+ /* Add all-routers address. */
+ if (idev->cnf.forwarding) {
+ ipv6_addr_all_routers(&maddr);
+ ipv6_dev_mc_inc(idev->dev, &maddr);
+ }
+}
+
+/*
+ * Device is about to be destroyed: clean up.
+ */
+
+void ipv6_mc_destroy_dev(struct inet6_dev *idev)
+{
+ struct ifmcaddr6 *i;
+ struct in6_addr maddr;
+
+ MDBG3((KERN_DEBUG
+ "ipv6_mc_destroy_dev(idev=%p)\n", idev));
+
+ /* Deactivate timers */
+ ipv6_mc_down(idev);
+
+ /* Delete all-nodes address. */
+ ipv6_addr_all_nodes(&maddr);
+
+ /* We cannot call ipv6_dev_mc_dec() directly, our caller in
+ * addrconf.c has NULL'd out dev->ip6_ptr so in6_dev_get() will
+ * fail.
+ */
+ __ipv6_dev_mc_dec(idev->dev, idev, &maddr);
+
+ /* Delete all-routers address. */
+ if (idev->cnf.forwarding) {
+ ipv6_addr_all_routers(&maddr);
+ ipv6_dev_mc_dec(idev->dev, &maddr);
+ }
+
+ write_lock_bh(&idev->lock);
+ while ((i = idev->mc_list) != NULL) {
+ idev->mc_list = i->next;
+ write_unlock_bh(&idev->lock);
+
+ igmp6_group_dropped(i);
+ ma_put(i);
+
+ write_lock_bh(&idev->lock);
+ }
+ write_unlock_bh(&idev->lock);
+}
+
+#ifdef CONFIG_PROC_FS
+static int igmp6_read_proc(char *buffer, char **start, off_t offset,
+ int length, int *eof, void *data)
+{
+ off_t pos=0, begin=0;
+ struct ifmcaddr6 *im;
+ int len=0;
+ struct net_device *dev;
+
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev; dev = dev->next) {
+ struct inet6_dev *idev;
+
+ if ((idev = in6_dev_get(dev)) == NULL)
+ continue;
+
+ read_lock_bh(&idev->lock);
+ for (im = idev->mc_list; im; im = im->next) {
+ int i;
+
+ len += sprintf(buffer+len,"%-4d %-15s ", dev->ifindex, dev->name);
+
+ for (i=0; i<16; i++)
+ len += sprintf(buffer+len, "%02x", im->mca_addr.s6_addr[i]);
+
+ len+=sprintf(buffer+len,
+ " %5d %08X %ld\n",
+ im->mca_users,
+ im->mca_flags,
+ (im->mca_flags&MAF_TIMER_RUNNING) ? im->mca_timer.expires-jiffies : 0);
+
+ pos=begin+len;
+ if (pos < offset) {
+ len=0;
+ begin=pos;
+ }
+ if (pos > offset+length) {
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ goto done;
+ }
+ }
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ }
+ *eof = 1;
+
+done:
+ read_unlock(&dev_base_lock);
+
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ if (len<0)
+ len=0;
+ return len;
+}
+
+static int ip6_mcf_read_proc(char *buffer, char **start, off_t offset,
+ int length, int *eof, void *data)
+{
+ off_t pos=0, begin=0;
+ int len=0;
+ int first=1;
+ struct net_device *dev;
+
+ read_lock(&dev_base_lock);
+ for (dev=dev_base; dev; dev=dev->next) {
+ struct inet6_dev *idev = in6_dev_get(dev);
+ struct ifmcaddr6 *imc;
+
+ if (idev == NULL)
+ continue;
+
+ read_lock_bh(&idev->lock);
+
+ for (imc=idev->mc_list; imc; imc=imc->next) {
+ struct ip6_sf_list *psf;
+ unsigned long i;
+
+ spin_lock_bh(&imc->mca_lock);
+ for (psf=imc->mca_sources; psf; psf=psf->sf_next) {
+ if (first) {
+ len += sprintf(buffer+len, "%3s %6s "
+ "%32s %32s %6s %6s\n", "Idx",
+ "Device", "Multicast Address",
+ "Source Address", "INC", "EXC");
+ first = 0;
+ }
+ len += sprintf(buffer+len,"%3d %6.6s ",
+ dev->ifindex, dev->name);
+
+ for (i=0; i<16; i++)
+ len += sprintf(buffer+len, "%02x",
+ imc->mca_addr.s6_addr[i]);
+ buffer[len++] = ' ';
+ for (i=0; i<16; i++)
+ len += sprintf(buffer+len, "%02x",
+ psf->sf_addr.s6_addr[i]);
+ len += sprintf(buffer+len, " %6lu %6lu\n",
+ psf->sf_count[MCAST_INCLUDE],
+ psf->sf_count[MCAST_EXCLUDE]);
+ pos = begin+len;
+ if (pos < offset) {
+ len=0;
+ begin=pos;
+ }
+ if (pos > offset+length) {
+ spin_unlock_bh(&imc->mca_lock);
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ goto done;
+ }
+ }
+ spin_unlock_bh(&imc->mca_lock);
+ }
+ read_unlock_bh(&idev->lock);
+ in6_dev_put(idev);
+ }
+ *eof = 1;
+
+done:
+ read_unlock(&dev_base_lock);
+
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ if (len<0)
+ len=0;
+ return len;
+}
+#endif
+
+int __init igmp6_init(struct net_proto_family *ops)
+{
+ struct sock *sk;
+ int err;
+
+ igmp6_socket = sock_alloc();
+ if (igmp6_socket == NULL) {
+ MDBG1((KERN_ERR
+ "Failed to create the IGMP6 control socket.\n"));
+ return -1;
+ }
+ igmp6_socket->inode->i_uid = 0;
+ igmp6_socket->inode->i_gid = 0;
+ igmp6_socket->type = SOCK_RAW;
+
+ if((err = ops->create(igmp6_socket, IPPROTO_ICMPV6)) < 0) {
+ MDBG1((KERN_ERR
+ "Failed to initialize the IGMP6 control socket (err %d).\n",
+ err));
+ sock_release(igmp6_socket);
+ igmp6_socket = NULL; /* For safety. */
+ return err;
+ }
+
+ sk = igmp6_socket->sk;
+ sk->allocation = GFP_ATOMIC;
+ sk->prot->unhash(sk);
+
+ sk->net_pinfo.af_inet6.hop_limit = 1;
+#ifdef CONFIG_PROC_FS
+ create_proc_read_entry("net/igmp6", 0, 0, igmp6_read_proc, NULL);
+ create_proc_read_entry("net/mcfilter6", 0, 0, ip6_mcf_read_proc, NULL);
+#endif
+
+ return 0;
+}
+
+void igmp6_cleanup(void)
+{
+ sock_release(igmp6_socket);
+ igmp6_socket = NULL; /* for safety */
+#ifdef CONFIG_PROC_FS
+ remove_proc_entry("net/igmp6", 0);
+#endif
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv6/ndisc.c b/uClinux-2.4.31-uc0/net/ipv6/ndisc.c
new file mode 100644
index 0000000..1b4e133
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/ndisc.c
@@ -0,0 +1,1917 @@
+/* $USAGI: ndisc.c,v 1.132 2004/02/01 10:05:55 yoshfuji Exp $ */
+
+/*
+ * Neighbour Discovery for IPv6
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ * Mike Shaver <shaver@ingenia.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/*
+ * Changes:
+ *
+ * Lars Fenneberg : fixed MTU setting on receipt
+ * of an RA.
+ *
+ * Janos Farkas : kmalloc failure checks
+ * Alexey Kuznetsov : state machine reworked
+ * and moved to net/core.
+ * Pekka Savola : RFC2461 validation
+ * YOSHIFUJI Hideaki @USAGI : Verify ND options properly
+ * Ville Nuorvala : proxy offers protection
+ * against DAD
+ */
+
+#define __NO_VERSION__
+#include <linux/config.h>
+
+/* Set to 3 to get tracing... */
+#ifdef CONFIG_IPV6_NDISC_DEBUG
+#define ND_DEBUG 3
+#else
+#define ND_DEBUG 1
+#endif
+
+#define ND_PRINTK(x...) printk(x)
+#define ND_NOPRINTK(x...) do { ; } while(0)
+#define ND_PRINTK0 ND_PRINTK
+#define ND_PRINTK1 ND_NOPRINTK
+#define ND_PRINTK2 ND_NOPRINTK
+#define ND_PRINTK3 ND_NOPRINTK
+#if ND_DEBUG >= 1
+#undef ND_PRINTK1
+#define ND_PRINTK1 ND_PRINTK
+#endif
+#if ND_DEBUG >= 2
+#undef ND_PRINTK2
+#define ND_PRINTK2 ND_PRINTK
+#endif
+#if ND_DEBUG >= 3
+#undef ND_PRINTK3
+#define ND_PRINTK3 ND_PRINTK
+#endif
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/sched.h>
+#include <linux/net.h>
+#include <linux/in6.h>
+#include <linux/route.h>
+#include <linux/init.h>
+#ifdef CONFIG_SYSCTL
+#include <linux/sysctl.h>
+#endif
+
+#include <linux/if_arp.h>
+#include <linux/ipv6.h>
+#include <linux/icmpv6.h>
+#include <linux/inet.h>
+
+#include <linux/if_tr.h>
+#include <linux/jhash.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/ndisc.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+#include <net/icmp.h>
+
+#include <net/checksum.h>
+#include <linux/proc_fs.h>
+
+static struct socket *ndisc_socket;
+
+static u32 ndisc_hash(const void *pkey, const struct net_device *dev);
+static int ndisc_constructor(struct neighbour *neigh);
+static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb);
+static void ndisc_error_report(struct neighbour *neigh, struct sk_buff *skb);
+static int pndisc_constructor(struct pneigh_entry *n);
+static void pndisc_destructor(struct pneigh_entry *n);
+static void pndisc_redo(struct sk_buff *skb);
+
+static struct neigh_ops ndisc_generic_ops =
+{
+ AF_INET6,
+ NULL,
+ ndisc_solicit,
+ ndisc_error_report,
+ neigh_resolve_output,
+ neigh_connected_output,
+ dev_queue_xmit,
+ dev_queue_xmit
+};
+
+static struct neigh_ops ndisc_hh_ops =
+{
+ AF_INET6,
+ NULL,
+ ndisc_solicit,
+ ndisc_error_report,
+ neigh_resolve_output,
+ neigh_resolve_output,
+ dev_queue_xmit,
+ dev_queue_xmit
+};
+
+
+static struct neigh_ops ndisc_direct_ops =
+{
+ AF_INET6,
+ NULL,
+ NULL,
+ NULL,
+ dev_queue_xmit,
+ dev_queue_xmit,
+ dev_queue_xmit,
+ dev_queue_xmit
+};
+
+struct neigh_table nd_tbl =
+{
+ NULL,
+ AF_INET6,
+ sizeof(struct neighbour) + sizeof(struct in6_addr),
+ sizeof(struct in6_addr),
+ ndisc_hash,
+ ndisc_constructor,
+ pndisc_constructor,
+ pndisc_destructor,
+ pndisc_redo,
+ "ndisc_cache",
+ { NULL, NULL, &nd_tbl, 0, NULL, NULL,
+ 30*HZ, 1*HZ, 86400*HZ, 30*HZ, 5*HZ, 3, 3, 0, 3, 1*HZ, (8*HZ)/10, 64, 0 },
+ 30*HZ, 128, 512, 1024,
+};
+
+#define NDISC_OPT_SPACE(len) (((len)+2+7)&~7)
+
+static u8 *ndisc_fill_option(u8 *opt, int type, void *data, int data_len)
+{
+ int space = NDISC_OPT_SPACE(data_len);
+
+ opt[0] = type;
+ opt[1] = space>>3;
+ memcpy(opt+2, data, data_len);
+ data_len += 2;
+ opt += data_len;
+ if ((space -= data_len) > 0)
+ memset(opt, 0, space);
+ return opt + space;
+}
+
+struct nd_opt_hdr *ndisc_next_option(struct nd_opt_hdr *cur,
+ struct nd_opt_hdr *end)
+{
+ int type;
+ if (!cur || !end || cur >= end)
+ return NULL;
+ type = cur->nd_opt_type;
+ do {
+ cur = ((void *)cur) + (cur->nd_opt_len << 3);
+ } while(cur < end && cur->nd_opt_type != type);
+ return (cur <= end && cur->nd_opt_type == type ? cur : NULL);
+}
+
+struct ndisc_options *ndisc_parse_options(u8 *opt, int opt_len,
+ struct ndisc_options *ndopts)
+{
+ struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)opt;
+
+ ND_PRINTK3(KERN_DEBUG
+ "ndisc_parse_options(opt=%p, opt_len=%d, ndopts=%p)\n",
+ opt, opt_len, ndopts);
+
+ if (!nd_opt || opt_len < 0 || !ndopts)
+ return NULL;
+ memset(ndopts, 0, sizeof(*ndopts));
+ while (opt_len) {
+ int l;
+ if (opt_len < sizeof(struct nd_opt_hdr)) {
+ ND_PRINTK3(KERN_WARNING
+ "ndisc_parse_options(opt=%p, opt_len=%d, ndopts=%p): truncated options found\n",
+ opt, opt_len, ndopts);
+ return NULL;
+ }
+ l = nd_opt->nd_opt_len << 3;
+ if (opt_len < l || l == 0) {
+ ND_PRINTK3(KERN_WARNING
+ "ndisc_parse_options(): %s found; type=%d, len=%d (remain: %d)\n",
+ l ? "truncated option" : "zero-length option",
+ nd_opt->nd_opt_type, l, opt_len);
+ return NULL;
+ }
+ switch (nd_opt->nd_opt_type) {
+ case ND_OPT_SOURCE_LL_ADDR:
+ case ND_OPT_TARGET_LL_ADDR:
+ case ND_OPT_MTU:
+ case ND_OPT_REDIRECT_HDR:
+ case ND_OPT_RTR_ADV_INTERVAL:
+ if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) {
+ ND_PRINTK3(KERN_WARNING
+ "ndisc_parse_options(): duplicated ND6 option found: type=%d\n",
+ nd_opt->nd_opt_type);
+ } else {
+ ndopts->nd_opt_array[nd_opt->nd_opt_type] = nd_opt;
+ }
+ break;
+ case ND_OPT_PREFIX_INFO:
+ ndopts->nd_opts_pi_end = nd_opt;
+ if (ndopts->nd_opt_array[nd_opt->nd_opt_type] == 0)
+ ndopts->nd_opt_array[nd_opt->nd_opt_type] = nd_opt;
+ break;
+ case ND_OPT_ROUTE_INFO:
+#if defined(CONFIG_IPV6_ROUTE_INFO)
+ ndopts->nd_opts_ri_end = nd_opt;
+ if (ndopts->nd_opt_array[nd_opt->nd_opt_type] == 0)
+ ndopts->nd_opt_array[nd_opt->nd_opt_type] = nd_opt;
+#endif
+ break;
+ case ND_OPT_HOME_AGENT_INFO:
+ /* These two are handled by the MIPv6 code */
+ break;
+ default:
+ /*
+ * Unknown options must be silently ignored,
+ * to accommodate future extension to the protocol.
+ */
+ ND_PRINTK2(KERN_WARNING
+ "ndisc_parse_options(): ignored unsupported option; type=%d, len=%d\n",
+ nd_opt->nd_opt_type, nd_opt->nd_opt_len);
+ }
+ opt_len -= l;
+ nd_opt = ((void *)nd_opt) + l;
+ }
+ return ndopts;
+}
+
+int ndisc_mc_map(struct in6_addr *addr, char *buf, struct net_device *dev, int dir)
+{
+ switch (dev->type) {
+ case ARPHRD_ETHER:
+ case ARPHRD_IEEE802: /* Not sure. Check it later. --ANK */
+ case ARPHRD_FDDI:
+ ipv6_eth_mc_map(addr, buf);
+ return 0;
+ case ARPHRD_IEEE802_TR:
+ ipv6_tr_mc_map(addr,buf);
+ return 0;
+ case ARPHRD_ARCNET:
+ ipv6_arcnet_mc_map(addr, buf);
+ return 0;
+ default:
+ if (dir) {
+ memcpy(buf, dev->broadcast, dev->addr_len);
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static u32 ndisc_hash(const void *pkey, const struct net_device *dev)
+{
+ const u32 *p32 = pkey;
+ u32 addr_hash, i;
+
+ addr_hash = 0;
+ for (i = 0; i < (sizeof(struct in6_addr) / sizeof(u32)); i++)
+ addr_hash ^= *p32++;
+
+ return jhash_2words(addr_hash, dev->ifindex, nd_tbl.hash_rnd);
+}
+
+static int ndisc_constructor(struct neighbour *neigh)
+{
+ struct in6_addr *addr = (struct in6_addr*)&neigh->primary_key;
+ struct net_device *dev = neigh->dev;
+ struct inet6_dev *in6_dev = in6_dev_get(dev);
+ int addr_type;
+
+ if (in6_dev == NULL)
+ return -EINVAL;
+
+ addr_type = ipv6_addr_type(addr);
+ if (in6_dev->nd_parms)
+ neigh->parms = in6_dev->nd_parms;
+
+ if (addr_type&IPV6_ADDR_MULTICAST)
+ neigh->type = RTN_MULTICAST;
+ else
+ neigh->type = RTN_UNICAST;
+ if (dev->hard_header == NULL) {
+ neigh->nud_state = NUD_NOARP;
+ neigh->ops = &ndisc_direct_ops;
+ neigh->output = neigh->ops->queue_xmit;
+ } else {
+ if (addr_type&IPV6_ADDR_MULTICAST) {
+ neigh->nud_state = NUD_NOARP;
+ ndisc_mc_map(addr, neigh->ha, dev, 1);
+ } else if (dev->flags&(IFF_NOARP|IFF_LOOPBACK)) {
+ neigh->nud_state = NUD_NOARP;
+ memcpy(neigh->ha, dev->dev_addr, dev->addr_len);
+ if (dev->flags&IFF_LOOPBACK)
+ neigh->type = RTN_LOCAL;
+ } else if (dev->flags&IFF_POINTOPOINT) {
+ neigh->nud_state = NUD_NOARP;
+ memcpy(neigh->ha, dev->broadcast, dev->addr_len);
+ }
+ if (dev->hard_header_cache)
+ neigh->ops = &ndisc_hh_ops;
+ else
+ neigh->ops = &ndisc_generic_ops;
+ if (neigh->nud_state&NUD_VALID)
+ neigh->output = neigh->ops->connected_output;
+ else
+ neigh->output = neigh->ops->output;
+ }
+ in6_dev_put(in6_dev);
+ return 0;
+}
+
+static int pndisc_constructor(struct pneigh_entry *n)
+{
+ struct in6_addr *addr = (struct in6_addr*)&n->key;
+ struct in6_addr maddr;
+ struct net_device *dev = n->dev;
+
+ if (dev == NULL || __in6_dev_get(dev) == NULL)
+ return -EINVAL;
+ addrconf_addr_solict_mult(addr, &maddr);
+ ipv6_dev_mc_inc(dev, &maddr);
+ return 0;
+}
+
+static void pndisc_destructor(struct pneigh_entry *n)
+{
+ struct in6_addr *addr = (struct in6_addr*)&n->key;
+ struct in6_addr maddr;
+ struct net_device *dev = n->dev;
+
+ if (dev == NULL || __in6_dev_get(dev) == NULL)
+ return;
+ addrconf_addr_solict_mult(addr, &maddr);
+ ipv6_dev_mc_dec(dev, &maddr);
+}
+
+
+
+static int
+ndisc_build_ll_hdr(struct sk_buff *skb, struct net_device *dev,
+ struct in6_addr *daddr, struct neighbour *neigh, int len)
+{
+ unsigned char ha[MAX_ADDR_LEN];
+ unsigned char *h_dest = NULL;
+
+ skb_reserve(skb, (dev->hard_header_len + 15) & ~15);
+
+ if (dev->hard_header) {
+ if (ipv6_addr_type(daddr) & IPV6_ADDR_MULTICAST) {
+ ndisc_mc_map(daddr, ha, dev, 1);
+ h_dest = ha;
+ } else if (neigh) {
+ read_lock_bh(&neigh->lock);
+ if (neigh->nud_state&NUD_VALID) {
+ memcpy(ha, neigh->ha, dev->addr_len);
+ h_dest = ha;
+ }
+ read_unlock_bh(&neigh->lock);
+ } else {
+ neigh = neigh_lookup(&nd_tbl, daddr, dev);
+ if (neigh) {
+ read_lock_bh(&neigh->lock);
+ if (neigh->nud_state&NUD_VALID) {
+ memcpy(ha, neigh->ha, dev->addr_len);
+ h_dest = ha;
+ }
+ read_unlock_bh(&neigh->lock);
+ neigh_release(neigh);
+ }
+ }
+
+ if (dev->hard_header(skb, dev, ETH_P_IPV6, h_dest, NULL, len) < 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+static void inline ndisc_update(struct neighbour *neigh,
+ u8 *lladdr, u32 flags)
+{
+ int notify;
+ write_lock_bh(&neigh->lock);
+ notify = __neigh_update(neigh, lladdr, NUD_STALE, flags);
+#ifdef CONFIG_ARPD
+ if (notify > 0 && neigh->parms->app_probes) {
+ write_unlock_bh(&neigh->lock);
+ neigh_app_notify(neigh);
+ } else
+#endif
+ write_unlock_bh(&neigh->lock);
+}
+
+/*
+ * Send a Neighbour Advertisement
+ */
+
+void ndisc_send_na(struct net_device *dev, struct neighbour *neigh,
+ struct in6_addr *daddr, struct in6_addr *solicited_addr,
+ int router, int solicited, int override, int inc_opt)
+{
+ struct in6_addr tmpaddr;
+ struct inet6_ifaddr *ifp;
+ struct sock *sk = ndisc_socket->sk;
+ struct in6_addr *src_addr;
+ struct nd_msg *msg;
+ int len;
+ struct sk_buff *skb;
+ struct inet6_dev *idev;
+ int err;
+
+ /* for proxy or anycast, solicited_addr != src_addr */
+ ifp = ipv6_get_ifaddr(solicited_addr, dev);
+ if (ifp) {
+ src_addr = solicited_addr;
+ in6_ifa_put(ifp);
+ } else {
+ if (ipv6_dev_get_saddr(dev, daddr, &tmpaddr, sk->net_pinfo.af_inet6.use_tempaddr)) {
+#ifdef CONFIG_IPV6_NDISC_DEBUG
+ char abuf[64];
+ in6_ntop(daddr, abuf);
+ ND_PRINTK2(KERN_WARNING
+ "ndisc_send_na(): could not get source address for %s on device %p(%s)\n",
+ abuf, dev, dev ? dev->name : "<null>");
+#endif
+ return;
+ }
+ src_addr = &tmpaddr;
+ }
+
+ len = sizeof(struct nd_msg);
+
+ if (inc_opt) {
+ if (dev->addr_len)
+ len += NDISC_OPT_SPACE(dev->addr_len);
+ else
+ inc_opt = 0;
+ }
+
+ skb = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15,
+ 1, &err);
+
+ if (skb == NULL) {
+ ND_PRINTK1(KERN_WARNING
+ "send_na: alloc skb failed\n");
+ return;
+ }
+
+ if (ndisc_build_ll_hdr(skb, dev, daddr, neigh, len) == 0) {
+ kfree_skb(skb);
+ return;
+ }
+
+ ip6_nd_hdr(sk, skb, dev, src_addr, daddr, IPPROTO_ICMPV6, len);
+
+ msg = (struct nd_msg *) skb_put(skb, len);
+ msg->icmph.icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT;
+ msg->icmph.icmp6_code = 0;
+ msg->icmph.icmp6_cksum = 0;
+
+ msg->icmph.icmp6_unused = 0;
+ msg->icmph.icmp6_router = router;
+ msg->icmph.icmp6_solicited = solicited;
+ msg->icmph.icmp6_override = !!override;
+
+ /* Set the target address. */
+ ipv6_addr_copy(&msg->target, solicited_addr);
+
+ if (inc_opt)
+ ndisc_fill_option(msg->opt, ND_OPT_TARGET_LL_ADDR, dev->dev_addr, dev->addr_len);
+
+ /* checksum */
+ msg->icmph.icmp6_cksum = csum_ipv6_magic(src_addr, daddr, len,
+ IPPROTO_ICMPV6,
+ csum_partial((__u8 *) msg,
+ len, 0));
+
+ dev_queue_xmit(skb);
+
+ idev = in6_dev_get(dev);
+ ICMP6_INC_STATS(idev,Icmp6OutNeighborAdvertisements);
+ ICMP6_INC_STATS(idev,Icmp6OutMsgs);
+ if (idev)
+ in6_dev_put(idev);
+}
+
+void ndisc_send_ns(struct net_device *dev, struct neighbour *neigh,
+ struct in6_addr *solicit,
+ struct in6_addr *daddr, struct in6_addr *saddr,
+ int dad)
+{
+ struct sock *sk = ndisc_socket->sk;
+ struct sk_buff *skb;
+ struct inet6_dev *idev;
+ struct nd_msg *msg;
+ struct in6_addr addr_buf;
+ int len;
+ int err;
+ int send_llinfo;
+
+#if CONFIG_IPV6_NDISC_DEBUG
+ char abuf1[128], abuf2[128], abuf3[128];
+
+ if (saddr)
+ in6_ntop(saddr, abuf1);
+ if (daddr)
+ in6_ntop(daddr, abuf2);
+ if (solicit)
+ in6_ntop(solicit, abuf3);
+ ND_PRINTK3(KERN_DEBUG
+ "ndisc_send_ns(): saddr=%s, daddr=%s, solicit=%s, dad=%d\n",
+ saddr ? abuf1 : "<null>",
+ daddr ? abuf2 : "<null>",
+ solicit ? abuf3 : "<null>",
+ dad);
+#endif
+
+ if (dad) {
+ memset(&addr_buf, 0, sizeof(addr_buf));
+ saddr = &addr_buf;
+ send_llinfo = 0;
+ } else {
+ if (saddr == NULL) {
+ if (ipv6_get_lladdr(dev, &addr_buf))
+ return;
+ saddr = &addr_buf;
+ }
+ send_llinfo = !ipv6_addr_any(saddr) && dev->addr_len;
+ }
+
+ len = sizeof(struct nd_msg);
+ if (send_llinfo)
+ len += NDISC_OPT_SPACE(dev->addr_len);
+
+ skb = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15,
+ 1, &err);
+
+ if (skb == NULL) {
+ ND_PRINTK1(KERN_WARNING
+ "send_ns: alloc skb failed\n");
+ return;
+ }
+
+ if (ndisc_build_ll_hdr(skb, dev, daddr, neigh, len) == 0) {
+ kfree_skb(skb);
+ return;
+ }
+
+ ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len);
+
+ msg = (struct nd_msg *)skb_put(skb, len);
+ msg->icmph.icmp6_type = NDISC_NEIGHBOUR_SOLICITATION;
+ msg->icmph.icmp6_code = 0;
+ msg->icmph.icmp6_cksum = 0;
+ msg->icmph.icmp6_unused = 0;
+
+ /* Set the target address. */
+ ipv6_addr_copy(&msg->target, solicit);
+
+ if (send_llinfo)
+ ndisc_fill_option(msg->opt, ND_OPT_SOURCE_LL_ADDR, dev->dev_addr, dev->addr_len);
+
+ /* checksum */
+ msg->icmph.icmp6_cksum = csum_ipv6_magic(&skb->nh.ipv6h->saddr,
+ daddr, len,
+ IPPROTO_ICMPV6,
+ csum_partial((__u8 *) msg,
+ len, 0));
+#if CONFIG_IPV6_NDISC_DEBUG
+ if (saddr)
+ in6_ntop(saddr, abuf1);
+ if (daddr)
+ in6_ntop(daddr, abuf2);
+ if (solicit)
+ in6_ntop(solicit, abuf3);
+ ND_PRINTK3(KERN_DEBUG
+ "ndisc_send_ns(): -> saddr=%s, daddr=%s, solicit=%s, option_ok=%d\n",
+ saddr ? abuf1 : "<null>",
+ daddr ? abuf2 : "<null>",
+ solicit ? abuf3 : "<null>",
+ send_llinfo);
+#endif
+
+ /* send it! */
+ dev_queue_xmit(skb);
+
+ idev = in6_dev_get(dev);
+ ICMP6_INC_STATS(idev,Icmp6OutNeighborSolicits);
+ ICMP6_INC_STATS(idev,Icmp6OutMsgs);
+ if (idev)
+ in6_dev_put(idev);
+}
+
+void ndisc_send_rs(struct net_device *dev, struct in6_addr *saddr,
+ struct in6_addr *daddr)
+{
+ struct sock *sk = ndisc_socket->sk;
+ struct sk_buff *skb;
+ struct inet6_dev *idev;
+ struct rs_msg *hdr;
+ int len;
+ int err;
+
+ len = sizeof(struct icmp6hdr);
+ if (dev->addr_len)
+ len += NDISC_OPT_SPACE(dev->addr_len);
+
+ skb = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15,
+ 1, &err);
+
+ if (skb == NULL) {
+ ND_PRINTK1(KERN_WARNING
+ "send_rs: alloc skb failed\n");
+ return;
+ }
+
+ if (ndisc_build_ll_hdr(skb, dev, daddr, NULL, len) == 0) {
+ kfree_skb(skb);
+ return;
+ }
+
+ ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len);
+
+ hdr = (struct rs_msg *)skb_put(skb, len);
+ hdr->icmph.icmp6_type = NDISC_ROUTER_SOLICITATION;
+ hdr->icmph.icmp6_code = 0;
+ hdr->icmph.icmp6_cksum = 0;
+ hdr->icmph.icmp6_unused = 0;
+
+ if (dev->addr_len)
+ ndisc_fill_option(hdr->opt, ND_OPT_SOURCE_LL_ADDR, dev->dev_addr, dev->addr_len);
+
+ /* checksum */
+ hdr->icmph.icmp6_cksum = csum_ipv6_magic(&skb->nh.ipv6h->saddr, daddr, len,
+ IPPROTO_ICMPV6,
+ csum_partial((__u8 *) hdr, len, 0));
+
+ /* send it! */
+ ND_PRINTK3(KERN_DEBUG
+ "ndisc_send_rs: send RS message\n");
+ dev_queue_xmit(skb);
+
+ idev = in6_dev_get(dev);
+ ICMP6_INC_STATS(idev,Icmp6OutRouterSolicits);
+ ICMP6_INC_STATS(idev,Icmp6OutMsgs);
+ if (idev)
+ in6_dev_put(idev);
+}
+
+static void ndisc_error_report(struct neighbour *neigh, struct sk_buff *skb)
+{
+ /*
+ * "The sender MUST return an ICMP
+ * destination unreachable"
+ */
+ dst_link_failure(skb);
+ kfree_skb(skb);
+}
+
+/* Called with locked neigh: either read or both */
+
+static void ndisc_solicit(struct neighbour *neigh, struct sk_buff *skb)
+{
+ struct in6_addr *saddr = NULL;
+ struct in6_addr mcaddr;
+ struct net_device *dev = neigh->dev;
+ struct in6_addr *target = (struct in6_addr *)&neigh->primary_key;
+ int probes = atomic_read(&neigh->probes);
+
+ if (skb && ipv6_chk_addr(&skb->nh.ipv6h->saddr, dev))
+ saddr = &skb->nh.ipv6h->saddr;
+
+ read_lock_bh(&neigh->lock);
+ if ((probes -= neigh->parms->ucast_probes) < 0) {
+ if (!(neigh->nud_state&NUD_VALID))
+ ND_PRINTK1(KERN_WARNING
+ "ndisc_solicit(): trying to ucast probe in NUD_INVALID\n");
+ read_unlock_bh(&neigh->lock);
+ ndisc_send_ns(dev, neigh, target, target, saddr, 0);
+ } else if ((probes -= neigh->parms->app_probes) < 0) {
+ read_unlock_bh(&neigh->lock);
+#ifdef CONFIG_ARPD
+ neigh_app_ns(neigh);
+#endif
+ } else {
+ read_unlock_bh(&neigh->lock);
+ addrconf_addr_solict_mult(target, &mcaddr);
+ ndisc_send_ns(dev, NULL, target, &mcaddr, saddr, 0);
+ }
+}
+
+#define IPV6READYLOGO
+#ifdef IPV6READYLOGO
+static int ndisc_getfrag( const void *data, struct in6_addr *addr, char *buff,
+ unsigned int offset, unsigned int len)
+{
+ ND_PRINTK1(KERN_DEBUG "ndisc_getfrag len=%d \n", len);
+ memcpy( buff, data+offset, len);
+ return 0;
+}
+
+static void no_neigh_send_na( struct net_device *dev, struct sk_buff *oldskb)
+ // copy&modify from icmp.c : static void icmpv6_echo_reply(struct sk_buff *skb)
+ // Need more work if define CONFIG_IPV6_IPSEC
+{
+ struct sock *sk = ndisc_socket->sk;
+ struct nd_msg *msg;
+ int len;
+ struct sk_buff *skb;
+ struct inet6_dev *idev;
+ int err;
+
+ int ipsec_hdrlen = 0;
+ int ipsec_authhdrlen = 0;
+
+ struct in6_addr *daddr ,*saddr;
+ struct flowi fl;
+
+ daddr = &oldskb->nh.ipv6h->saddr;
+ saddr = &oldskb->nh.ipv6h->daddr;
+
+ if ((ipv6_addr_type(saddr) & (IPV6_ADDR_MULTICAST|IPV6_ADDR_ANYCAST))
+#ifdef CONFIG_IPV6_ANYCAST
+ || ipv6_chk_acast_addr(0, saddr)
+#endif
+ )
+ saddr = NULL;
+ len = sizeof(struct nd_msg);
+//no opt len += NDISC_OPT_SPACE(dev->addr_len);
+ skb = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15 + ipsec_hdrlen,0, &err);
+
+ if (skb == NULL) {
+ ND_PRINTK1(KERN_DEBUG "no_neigh_send_ns: alloc skb failed\n");
+ return;
+ }
+#if 0 // Can not make ll here, because ns is w/o ll
+ if (ndisc_build_ll_hdr(skb, dev, daddr, NULL, len + ipsec_hdrlen) == 0
+ ) {
+ ND_PRINTK1(KERN_DEBUG "no_neigh_send_ns: build_ll_hdr failed\n");
+ kfree_skb(skb);
+ return;
+ }
+#endif
+ ip6_nd_hdr(sk, skb, dev, saddr, daddr, IPPROTO_ICMPV6, len + ipsec_authhdrlen);
+ msg = (struct nd_msg *) skb_put(skb, len);
+
+ msg->icmph.icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT;
+ msg->icmph.icmp6_code = 0;
+ msg->icmph.icmp6_cksum = 0;
+
+ msg->icmph.icmp6_unused = 0;
+ msg->icmph.icmp6_router = 1; //router;
+ msg->icmph.icmp6_solicited = 1; //solicited;
+ msg->icmph.icmp6_override = 0; //!!override;
+
+
+ /* Set the target address. */
+ ipv6_addr_copy(&msg->target, saddr);
+
+ // if (send_llinfo)
+ // no opt ndisc_fill_option((void*)(msg+1), ND_OPT_TARGET_LL_ADDR, dev->dev_addr, dev->addr_len);
+
+ /* checksum */
+ msg->icmph.icmp6_cksum = csum_ipv6_magic(&skb->nh.ipv6h->saddr,
+ daddr, len,
+ IPPROTO_ICMPV6,
+ csum_partial((__u8 *) msg,
+ len, 0));
+
+ fl.proto = IPPROTO_ICMPV6;
+ fl.nl_u.ip6_u.daddr = &oldskb->nh.ipv6h->saddr;
+ fl.nl_u.ip6_u.saddr = saddr;
+ fl.oif = skb->dev->ifindex;
+ fl.fl6_flowlabel = 0;
+ fl.uli_u.icmpt.type = NDISC_NEIGHBOUR_ADVERTISEMENT;
+ fl.uli_u.icmpt.code = 0;
+//may need more effort ? if (icmpv6_xmit_lock_bh())
+// return;
+
+ ip6_build_xmit(sk, ndisc_getfrag, msg, &fl, len, NULL, -1, -1, MSG_DONTWAIT);
+ idev = in6_dev_get(dev);
+ ICMP6_INC_STATS(idev,Icmp6OutNeighborSolicits);
+ ICMP6_INC_STATS(idev,Icmp6OutMsgs);
+ if (idev)
+ in6_dev_put(idev);
+
+ kfree_skb(skb);
+//may need more effort ? icmpv6_xmit_unlock_bh();
+}
+#endif
+static void ndisc_recv_ns(struct sk_buff *skb)
+{
+ struct nd_msg *msg = (struct nd_msg *) skb->h.raw;
+ struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
+ struct in6_addr *daddr = &skb->nh.ipv6h->daddr;
+ struct neighbour *neigh;
+ u8 *lladdr = NULL;
+ int lladdrlen = 0;
+ u32 ndoptlen = skb->len - sizeof(*msg);
+ struct ndisc_options ndopts;
+ struct net_device *dev = skb->dev;
+ struct inet6_ifaddr *ifp;
+ int addr_type = ipv6_addr_type(saddr);
+
+ if (skb->len < sizeof(*msg))
+ return;
+
+ if (addr_type == IPV6_ADDR_ANY) {
+ /* dst has to be solicited node multicast address */
+ if (!(daddr->s6_addr32[0] == htonl(0xff020000) &&
+ daddr->s6_addr32[1] == htonl(0x00000000) &&
+ daddr->s6_addr32[2] == htonl(0x00000001) &&
+ daddr->s6_addr [12] == 0xff )) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 NS: bad DAD packet (wrong ip6 dst)\n");
+ goto bad;
+ }
+ }
+
+ if (ipv6_addr_type(&msg->target)&IPV6_ADDR_MULTICAST) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 NS: bad ND target (multicast)\n");
+ goto bad;
+ }
+
+ if (!ndisc_parse_options(msg->opt, ndoptlen, &ndopts)) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 ND: invalid ND option, ignored\n");
+ goto bad;
+ }
+
+ if (ndopts.nd_opts_src_lladdr) {
+ lladdr = (u8*)(ndopts.nd_opts_src_lladdr + 1);
+ lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3;
+ if (lladdrlen != NDISC_OPT_SPACE(dev->addr_len))
+ goto bad;
+ }
+
+ /* XXX: RFC2461 7.1.1:
+ * If the IP source address is the unspecified address, there
+ * MUST NOT be source link-layer address option in the message.
+ *
+ * NOTE! Linux kernel < 2.4.4 broke this rule.
+ */
+ if (addr_type == IPV6_ADDR_ANY && lladdr) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 NS: bad DAD packet (link-layer address option)\n");
+ goto bad;
+ }
+
+ if ((ifp = ipv6_get_ifaddr(&msg->target, dev)) != NULL) {
+ if (ifp->flags & IFA_F_TENTATIVE) {
+ ND_PRINTK1(KERN_INFO
+ "ICMP6 NS : Received DAD NS but our address is TENTATIVE.\n");
+ /* Address is tentative. If the source
+ is unspecified address, it is someone
+ does DAD, otherwise we ignore solicitations
+ until DAD timer expires.
+ */
+ if (addr_type == IPV6_ADDR_ANY) {
+ if (dev->type == ARPHRD_IEEE802_TR) {
+ /*
+ * IMHO, this should be handled in
+ * network drivers.
+ * - yoshfuji (2001/01/07)
+ */
+ struct trh_hdr *thdr = (struct trh_hdr *)skb->mac.raw;
+ if (((thdr->saddr[0] ^ dev->dev_addr[0]) & 0x7f) ||
+ memcmp(&thdr->saddr[1], &dev->dev_addr[1], dev->addr_len - 1))
+ addrconf_dad_failure(ifp) ;
+ } else
+ addrconf_dad_failure(ifp);
+ } else
+ in6_ifa_put(ifp);
+ return;
+ }
+
+ if (addr_type == IPV6_ADDR_ANY) {
+ struct in6_addr maddr;
+
+ ipv6_addr_all_nodes(&maddr);
+ ndisc_send_na(dev, NULL, &maddr, &ifp->addr,
+ ifp->idev->cnf.forwarding, 0, 1, 1);
+ in6_ifa_put(ifp);
+ return;
+ }
+
+ if (addr_type & IPV6_ADDR_UNICAST) {
+ int inc = ipv6_addr_type(daddr)&IPV6_ADDR_MULTICAST;
+
+ if (inc)
+ NEIGH_CACHE_STAT_INC(&nd_tbl, rcv_probes_mcast);
+ else
+ NEIGH_CACHE_STAT_INC(&nd_tbl, rcv_probes_ucast);
+
+ /*
+ * update / create cache entry
+ * for the source address
+ */
+
+ neigh = __neigh_lookup(&nd_tbl, saddr, skb->dev, !inc || lladdr || !skb->dev->addr_len);
+ if (neigh) {
+ /* XXX: !skb->dev->addr_len case? */
+ ndisc_update(neigh, lladdr, NEIGH_UPDATE_F_IP6NS);
+ ndisc_send_na(dev, neigh, saddr, &ifp->addr,
+ ifp->idev->cnf.forwarding, 1, inc, inc);
+ neigh_release(neigh);
+ } else {
+#define IPV6READYLOGO
+#ifdef IPV6READYLOGO
+ no_neigh_send_na(dev, skb);
+#else
+ if (net_ratelimit())
+ ND_PRINTK3(KERN_DEBUG
+ "ICMP6 NS: Received Solicited NS but ignored.\n");
+#endif
+ }
+ }
+ in6_ifa_put(ifp);
+ } else if (ipv6_chk_acast_addr(dev, &msg->target)) {
+ struct inet6_dev *idev = in6_dev_get(dev);
+
+ /* anycast */
+
+ if (!idev) {
+ /* XXX: count this drop? */
+ return;
+ }
+
+ if (addr_type == IPV6_ADDR_ANY) {
+ struct in6_addr maddr;
+
+ ipv6_addr_all_nodes(&maddr);
+ ndisc_send_na(dev, NULL, &maddr, &msg->target,
+ idev->cnf.forwarding, 0, 0, 1);
+ in6_dev_put(idev);
+ return;
+ }
+
+ if (addr_type & IPV6_ADDR_UNICAST) {
+ int inc = ipv6_addr_type(daddr)&IPV6_ADDR_MULTICAST;
+ if (inc)
+ NEIGH_CACHE_STAT_INC(&nd_tbl, rcv_probes_mcast);
+ else
+ NEIGH_CACHE_STAT_INC(&nd_tbl, rcv_probes_ucast);
+
+ /*
+ * update / create cache entry
+ * for the source address
+ */
+
+ neigh = __neigh_lookup(&nd_tbl, saddr, skb->dev,
+ !inc || lladdr || !skb->dev->addr_len);
+
+ if (neigh) {
+ /* XXX: !skb->dev->addr_len case? */
+ ndisc_update(neigh, lladdr, NEIGH_UPDATE_F_IP6NS);
+ ndisc_send_na(dev, neigh, saddr, &msg->target,
+ idev->cnf.forwarding, 1, 0, inc);
+ neigh_release(neigh);
+ } else if (net_ratelimit())
+ ND_PRINTK3(KERN_DEBUG
+ "ICMP6 NS: Received Solicited NS but ignored.\n");
+ }
+ in6_dev_put(idev);
+ } else {
+ struct inet6_dev *in6_dev = in6_dev_get(dev);
+
+ if (in6_dev && in6_dev->cnf.forwarding &&
+ (addr_type & IPV6_ADDR_UNICAST) &&
+ pneigh_lookup(&nd_tbl, &msg->target, dev, 0)) {
+ int inc = ipv6_addr_type(daddr)&IPV6_ADDR_MULTICAST;
+
+ if (skb->stamp.tv_sec == 0 ||
+ skb->pkt_type == PACKET_HOST ||
+ inc == 0 ||
+ in6_dev->nd_parms->proxy_delay == 0) {
+ struct neighbour *neigh;
+ if (inc)
+ NEIGH_CACHE_STAT_INC(&nd_tbl,
+ rcv_probes_mcast);
+ else
+ neigh = neigh_event_ns(&nd_tbl, lladdr, saddr, dev);
+
+ if (neigh) {
+ /* XXX: !skb->dev->addr_len case? */
+ ndisc_update(neigh, lladdr,
+ NEIGH_UPDATE_F_IP6NS);
+ ndisc_send_na(dev, neigh, saddr, &msg->target,
+ 0, 1, 0, inc);
+ neigh_release(neigh);
+ }
+ } else {
+ struct sk_buff *n = skb_clone(skb, GFP_ATOMIC);
+ if (n)
+ pneigh_enqueue(&nd_tbl, in6_dev->nd_parms, n);
+ in6_dev_put(in6_dev);
+ return;
+ }
+ }
+ if (in6_dev)
+ in6_dev_put(in6_dev);
+
+ }
+bad:
+ return;
+}
+
+static void ndisc_recv_na(struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+ struct ipv6hdr *ip6 = skb->nh.ipv6h;
+ struct in6_addr *saddr = &ip6->saddr;
+ struct in6_addr *daddr = &ip6->daddr;
+ struct nd_msg *msg = (struct nd_msg *) skb->h.raw;
+ unsigned long ndoptlen = skb->len - sizeof(*msg);
+ u8 *lladdr = NULL;
+ int lladdrlen = 0;
+ struct inet6_ifaddr *ifp;
+ struct neighbour *neigh;
+ struct ndisc_options ndopts;
+
+ if (skb->len < sizeof(struct nd_msg))
+ return;
+
+ if (ipv6_addr_type(&msg->target) & IPV6_ADDR_MULTICAST) {
+ if (net_ratelimit()) {
+ char abuf[128];
+ in6_ntop(&msg->target, abuf);
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 NA: invalid target address %s\n",
+ abuf);
+ }
+ goto bad;
+ }
+
+ if ((ipv6_addr_type(daddr)&IPV6_ADDR_MULTICAST) &&
+ msg->icmph.icmp6_solicited) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 NA: solicited NA is multicasted\n");
+ return;
+ }
+ if (!ndisc_parse_options(msg->opt, ndoptlen, &ndopts)) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 NA: invalid ND option, ignored\n");
+ goto bad;
+ }
+ if (ndopts.nd_opts_tgt_lladdr) {
+ lladdr = (u8*)(ndopts.nd_opts_tgt_lladdr + 1);
+ lladdrlen = ndopts.nd_opts_tgt_lladdr->nd_opt_len << 3;
+ if (lladdrlen != NDISC_OPT_SPACE(skb->dev->addr_len))
+ goto bad;
+ }
+ if ((ifp = ipv6_get_ifaddr(&msg->target, dev))) {
+ if (ifp->flags & IFA_F_TENTATIVE) {
+ addrconf_dad_failure(ifp);
+ return;
+ }
+ /* What should we make now? The advertisement
+ is invalid, but ndisc specs say nothing
+ about it. It could be misconfiguration, or
+ an smart proxy agent tries to help us :-)
+ */
+ if (net_ratelimit())
+ ND_PRINTK1(KERN_WARNING
+ "ICMP6 NA: someone advertise our address on %s!\n",
+ ifp->idev->dev->name);
+ in6_ifa_put(ifp);
+ return;
+ }
+ neigh = neigh_lookup(&nd_tbl, &msg->target, skb->dev);
+
+ if (neigh) {
+ int notify = 0;
+ int was_router = 0;
+
+ write_lock_bh(&neigh->lock);
+ if (!(neigh->nud_state&~NUD_FAILED))
+ goto ignore;
+
+ was_router = neigh->flags&NTF_ROUTER;
+
+ notify = __neigh_update(neigh, lladdr,
+ msg->icmph.icmp6_solicited ? NUD_REACHABLE : NUD_STALE,
+ (NEIGH_UPDATE_F_IP6NA|
+ (msg->icmph.icmp6_override ? NEIGH_UPDATE_F_OVERRIDE : 0) |
+ (msg->icmph.icmp6_router ? NEIGH_UPDATE_F_ISROUTER : 0)));
+
+ if (was_router && !(neigh->flags&NTF_ROUTER)) {
+ /* XXX: race, ok? */
+ /*
+ * Change: router to host
+ */
+ struct rt6_info *rt;
+ rt = rt6_get_dflt_router(saddr, skb->dev);
+ if (rt) {
+ /* It is safe only because
+ we are in BH */
+ dst_release(&rt->u.dst);
+ ip6_del_rt(rt, NULL);
+ }
+ }
+ignore:
+#ifdef CONFIG_ARPD
+ if (notify > 0 && neigh->parms->app_probes) {
+ write_unlock_bh(&neigh->lock);
+ neigh_app_notify(neigh);
+ } else
+#endif
+ write_unlock_bh(&neigh->lock);
+ neigh_release(neigh);
+ }
+bad:
+ return;
+}
+
+static void ndisc_recv_rs(struct sk_buff *skb)
+{
+ struct rs_msg *rs_msg = (struct rs_msg *) skb->h.raw;
+ unsigned long ndoptlen = skb->len - sizeof(*rs_msg);
+ struct neighbour *neigh;
+ struct inet6_dev *idev;
+ struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
+ struct ndisc_options ndopts;
+ u8 *lladdr = NULL;
+ int lladdrlen = 0;
+
+ ND_PRINTK3(KERN_DEBUG "%s()\n", __FUNCTION__);
+
+ if (skb->len < sizeof(*rs_msg))
+ return;
+
+ idev = in6_dev_get(skb->dev);
+ if (!idev) {
+ ND_PRINTK1(KERN_WARNING
+ "%s: can't find in6 device\n",
+ __FUNCTION__);
+ return;
+ }
+
+ /* Ignore if I'm not a router */
+ if (!idev->cnf.forwarding || idev->cnf.accept_ra)
+ goto out;
+
+ /* sanity check */
+ if (!ndisc_parse_options(rs_msg->opt, ndoptlen, &ndopts)) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 NS: invalid ND option, ignored\n");
+ goto out;
+ }
+
+ /* Don't update NCE if src = :: */
+ if (ipv6_addr_any(saddr)) {
+ /*
+ * this is ok but ignore because this indicates that
+ * source has no ip address assigned yet.
+ */
+ goto out;
+ }
+
+ if (ndopts.nd_opts_src_lladdr) {
+ lladdr = (u8 *)(ndopts.nd_opts_src_lladdr + 1);
+ lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3;
+ if (lladdrlen != NDISC_OPT_SPACE(skb->dev->addr_len))
+ goto out;
+ }
+
+ neigh = __neigh_lookup(&nd_tbl, saddr, skb->dev, 1);
+ if (neigh) {
+ ndisc_update(neigh, lladdr, NEIGH_UPDATE_F_IP6RS);
+ neigh_release(neigh);
+ }
+out:
+ in6_dev_put(idev);
+}
+
+static void ndisc_recv_ra(struct sk_buff *skb)
+{
+ struct ra_msg *ra_msg = (struct ra_msg *) skb->h.raw;
+ unsigned long ndoptlen = skb->len - sizeof(*ra_msg);
+ struct neighbour *neigh;
+ struct inet6_dev *in6_dev;
+ struct rt6_info *rt = NULL;
+ int lifetime;
+ struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
+ struct ndisc_options ndopts;
+ int pref = 0;
+
+ ND_PRINTK3(KERN_DEBUG "%s()\n", __FUNCTION__);
+
+ if (skb->len < sizeof(*ra_msg))
+ return;
+
+ if ((ipv6_addr_type(saddr)&(IPV6_ADDR_UNICAST|IPV6_ADDR_LINKLOCAL)) != (IPV6_ADDR_UNICAST|IPV6_ADDR_LINKLOCAL)) {
+ if (net_ratelimit()) {
+ char abuf[128];
+ in6_ntop(saddr, abuf);
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 RA: src %s is not link-local\n", abuf);
+ }
+ return;
+ }
+
+ /*
+ * set the RA_RECV flag in the interface
+ */
+
+ in6_dev = in6_dev_get(skb->dev);
+ if (in6_dev == NULL) {
+ ND_PRINTK1(KERN_WARNING
+ "ICMP6 RA: can't find in6 device\n");
+ return;
+ }
+
+ if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_ra)
+ goto out;
+
+ if (!ndisc_parse_options(ra_msg->opt, ndoptlen, &ndopts)) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 RA: invalid ND option, ignored\n");
+ goto out;
+ }
+
+ if (in6_dev->if_flags & IF_RS_SENT) {
+ /*
+ * flag that an RA was received after an RS was sent
+ * out on this interface.
+ */
+ in6_dev->if_flags |= IF_RA_RCVD;
+ }
+
+ /*
+ * Remember the managed/otherconf flags from most recently
+ * received RA message (RFC 2462) -- yoshfuji
+ */
+ in6_dev->if_flags = (in6_dev->if_flags & ~(IF_RA_MANAGED |
+ IF_RA_OTHERCONF)) |
+ (ra_msg->icmph.icmp6_addrconf_managed ?
+ IF_RA_MANAGED : 0) |
+ (ra_msg->icmph.icmp6_addrconf_other ?
+ IF_RA_OTHERCONF : 0);
+
+ lifetime = ntohs(ra_msg->icmph.icmp6_rt_lifetime);
+ ND_PRINTK3(KERN_DEBUG
+ "ICMP6 RA: router lifetime %u\n", lifetime);
+
+#ifdef CONFIG_IPV6_ROUTER_PREF
+ pref = IPV6_SIGNEDPREF(ra_msg->icmph.icmp6_router_pref);
+ if (pref < -1) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 RA: invalid RA preference; zero lifetime\n");
+ lifetime = 0;
+ }
+#endif
+
+ rt = rt6_get_dflt_router(saddr, skb->dev);
+
+ if (rt && lifetime == 0) {
+ ip6_del_rt(rt, NULL);
+ rt = NULL;
+ }
+
+ if (rt == NULL && lifetime) {
+ ND_PRINTK2(KERN_DEBUG
+ "ndisc_rdisc: adding default router; lifetime=%u\n",
+ lifetime);
+
+ rt = rt6_add_dflt_router(saddr, skb->dev, pref);
+ if (rt == NULL) {
+ ND_PRINTK1(KERN_WARNING
+ "%s(): route_add failed\n",
+ __FUNCTION__);
+ in6_dev_put(in6_dev);
+ return;
+ }
+
+ neigh = rt->rt6i_nexthop;
+ if (neigh == NULL) {
+ ND_PRINTK1(KERN_WARNING
+ "%s(): add default router: null neighbour\n",
+ __FUNCTION__);
+ dst_release(&rt->u.dst);
+ in6_dev_put(in6_dev);
+ return;
+ }
+ neigh->flags |= NTF_ROUTER;
+
+ /*
+ * If we where using an "all destinations on link" route
+ * delete it
+ */
+
+ rt6_purge_dflt_routers(RTF_ALLONLINK);
+ }
+
+ if (rt) {
+ rt->rt6i_expires = jiffies + (HZ * lifetime);
+#ifdef CONFIG_IPV6_ROUTER_PREF
+ rt->rt6i_flags = (rt->rt6i_flags & ~RTF_PREF_MASK) | RTF_PREF(pref);
+#endif
+ }
+
+ if (ra_msg->icmph.icmp6_hop_limit)
+ in6_dev->cnf.hop_limit = ra_msg->icmph.icmp6_hop_limit;
+
+ /*
+ * Update Reachable Time and Retrans Timer
+ */
+
+ if (in6_dev->nd_parms) {
+ unsigned long rtime = ntohl(ra_msg->retrans_timer);
+
+ if (rtime && rtime/1000 < MAX_SCHEDULE_TIMEOUT/HZ) {
+ rtime = (rtime*HZ)/1000;
+ if (rtime < HZ/10)
+ rtime = HZ/10;
+ in6_dev->nd_parms->retrans_time = rtime;
+ }
+
+ rtime = ntohl(ra_msg->reachable_time);
+ if (rtime && rtime/1000 < MAX_SCHEDULE_TIMEOUT/(3*HZ)) {
+ rtime = (rtime*HZ)/1000;
+
+ if (rtime < HZ/10)
+ rtime = HZ/10;
+
+ if (rtime != in6_dev->nd_parms->base_reachable_time) {
+ in6_dev->nd_parms->base_reachable_time = rtime;
+ in6_dev->nd_parms->reachable_time = neigh_rand_reach_time(rtime);
+ }
+ }
+ }
+
+ /*
+ * Process options.
+ */
+
+#if defined(CONFIG_IPV6_ROUTE_INFO)
+ if (ndopts.nd_opts_ri) {
+ struct nd_opt_hdr *p;
+ for (p = ndopts.nd_opts_ri;
+ p;
+ p = ndisc_next_option(p, ndopts.nd_opts_ri_end)) {
+ rt6_route_rcv(skb->dev, (u8*)p, (p->nd_opt_len) << 3, saddr);
+ }
+ }
+#endif
+ if (ndopts.nd_opts_pi) {
+ struct nd_opt_hdr *p;
+ for (p = ndopts.nd_opts_pi;
+ p;
+ p = ndisc_next_option(p, ndopts.nd_opts_pi_end)) {
+ addrconf_prefix_rcv(skb->dev, (u8*)p, (p->nd_opt_len) << 3);
+ }
+ }
+ if (ndopts.nd_opts_mtu) {
+ u32 mtu;
+ memcpy(&mtu, ((u8*)(ndopts.nd_opts_mtu+1))+2, sizeof(mtu));
+ mtu = ntohl(mtu);
+ if (mtu < IPV6_MIN_MTU || mtu > skb->dev->mtu) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_DEBUG
+ "ICMP6 RA: router advertisement with invalid mtu = %d\n",
+ mtu);
+ } else if (in6_dev->cnf.mtu6 != mtu) {
+ in6_dev->cnf.mtu6 = mtu;
+ if (rt)
+ rt->u.dst.pmtu = mtu;
+ rt6_mtu_change(skb->dev, mtu);
+ }
+ }
+ if (rt && (neigh = rt->rt6i_nexthop) != NULL) {
+ u8 *lladdr = NULL;
+ int lladdrlen;
+ if (ndopts.nd_opts_src_lladdr) {
+ lladdr = (u8*)((ndopts.nd_opts_src_lladdr)+1);
+ lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3;
+ if (lladdrlen != NDISC_OPT_SPACE(skb->dev->addr_len))
+ goto out;
+ }
+ ndisc_update(neigh, lladdr, NEIGH_UPDATE_F_IP6RA);
+ }
+ if (ndopts.nd_opts_tgt_lladdr || ndopts.nd_opts_rh) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 RA: got illegal option with RA");
+ }
+ out:
+ if (rt)
+ dst_release(&rt->u.dst);
+ in6_dev_put(in6_dev);
+}
+
+static void ndisc_redirect_rcv(struct sk_buff *skb)
+{
+ struct inet6_dev *in6_dev;
+ struct rd_msg *rdmsg;
+ unsigned long ndoptlen = skb->len - sizeof(*rdmsg);
+ u8 *lladdr = NULL;
+ int lladdrlen = 0;
+ struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
+ struct in6_addr *dest;
+ struct in6_addr *target; /* new first hop to destination */
+ struct neighbour *neigh;
+ struct rt6_info *rt6;
+ int on_link = 0;
+ struct ndisc_options ndopts;
+
+ if (skb->len < sizeof(struct rd_msg))
+ return;
+
+ if (!(ipv6_addr_type(saddr) & IPV6_ADDR_LINKLOCAL)) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 Redirect: source address is not linklocal\n");
+ return;
+ }
+
+ rdmsg = (struct rd_msg *) skb->h.raw;
+ target = &rdmsg->rd_target;
+ dest = &rdmsg->rd_dst;
+
+ if (ipv6_addr_type(dest) & IPV6_ADDR_MULTICAST) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 Redirect: redirect for multicast addr\n");
+ return;
+ }
+
+ if (ipv6_addr_cmp(dest, target) == 0) {
+ on_link = 1;
+ } else if (!(ipv6_addr_type(target) & IPV6_ADDR_LINKLOCAL)) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 Redirect: target address is not linklocal\n");
+ return;
+ }
+
+#ifndef CONFIG_IPV6_NDISC_NEW
+ rt6 = rt6_lookup(dest, NULL, skb->dev->ifindex, RT6_LOOKUP_FLAG_STRICT|RT6_LOOKUP_FLAG_NOUSE);
+ if (!rt6) {
+ char sbuf[128], dbuf[128], tbuf[128];
+ in6_ntop(saddr, sbuf);
+ in6_ntop(dest, dbuf);
+ in6_ntop(target, tbuf);
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 Redirect: no route found for redirect dst:"
+ "src=%s, dst=%s, tgt=%s\n",
+ sbuf, dbuf, tbuf);
+ return;
+ } else if (ipv6_addr_cmp(saddr, &rt6->rt6i_gateway) != 0) {
+ char sbuf[128], dbuf[128], tbuf[128], gbuf[128];
+ in6_ntop(saddr, sbuf);
+ in6_ntop(dest, dbuf);
+ in6_ntop(target, tbuf);
+ in6_ntop(&rt6->rt6i_gateway, gbuf);
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 Redirect: not equal to gw-for-src=%s (must be same): "
+ "src=%s, dst=%s, tgt=%s\n",
+ gbuf, sbuf, dbuf, tbuf);
+ dst_release(&rt6->u.dst);
+ return;
+ }
+ dst_release(&rt6->u.dst);
+#endif
+
+ in6_dev = in6_dev_get(skb->dev);
+ if (!in6_dev)
+ return;
+ if (in6_dev->cnf.forwarding || !in6_dev->cnf.accept_redirects) {
+ in6_dev_put(in6_dev);
+ return;
+ }
+
+ /* XXX: RFC2461 8.1:
+ * The IP source address of the Redirect MUST be the same as the current
+ * first-hop router for the specified ICMP Destination Address.
+ */
+
+ /* passed validation tests */
+
+ if (!ndisc_parse_options(rdmsg->opt, ndoptlen, &ndopts)) {
+ char sbuf[128], dbuf[128], tbuf[128];
+ in6_ntop(saddr, sbuf);
+ in6_ntop(dest, dbuf);
+ in6_ntop(target, tbuf);
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 Redirect: invalid ND options, rejected: "
+ "src=%s, dst=%s, tgt=%s\n",
+ sbuf, dbuf, tbuf);
+ goto out;
+ }
+ if (ndopts.nd_opts_tgt_lladdr) {
+ lladdr = (u8*)(ndopts.nd_opts_tgt_lladdr + 1);
+ lladdrlen = ndopts.nd_opts_tgt_lladdr->nd_opt_len << 3;
+ if (lladdrlen != NDISC_OPT_SPACE(skb->dev->addr_len))
+ goto out;
+ }
+
+ /*
+ We install redirect only if nexthop state is valid.
+ */
+
+ neigh = __neigh_lookup(&nd_tbl, target, skb->dev, 1);
+ if (neigh) {
+#ifndef CONFIG_IPV6_NDISC_NEW
+ int notify;
+ write_lock_bh(&neigh->lock);
+ notify = __neigh_update(neigh, lladdr, NUD_STALE,
+ NEIGH_UPDATE_F_IP6REDIRECT|
+ (on_link ? 0 : (NEIGH_UPDATE_F_OVERRIDE_VALID_ISROUTER|
+ NEIGH_UPDATE_F_ISROUTER)));
+#ifdef CONFIG_ARPD
+ if (notify > 0 && !neigh->parms->app_probes)
+ notify = 0;
+#endif
+ if (neigh->nud_state&NUD_VALID) {
+ write_unlock_bh(&neigh->lock);
+ rt6_redirect(dest, &skb->nh.ipv6h->saddr, neigh, NULL, on_link);
+ } else {
+ __neigh_event_send(neigh, NULL);
+ write_unlock_bh(&neigh->lock);
+ }
+#ifdef CONFIG_ARPD
+ if (notify > 0)
+ neigh_app_notify(neigh);
+#endif
+#else
+ rt6_redirect(dest, &skb->nh.ipv6h->saddr, neigh, lladdr, on_link);
+#endif
+ neigh_release(neigh);
+ }
+out:
+ in6_dev_put(in6_dev);
+}
+
+void ndisc_send_redirect(struct sk_buff *skb, struct neighbour *neigh,
+ struct in6_addr *target)
+{
+ struct sock *sk = ndisc_socket->sk;
+ int len = sizeof(struct rd_msg);
+ struct sk_buff *buff;
+ struct rd_msg *rdmsg;
+ struct in6_addr saddr_buf;
+ struct net_device *dev;
+ struct inet6_dev *idev;
+ struct rt6_info *rt;
+ u8 *opt;
+ int rd_len;
+ int err;
+ int hlen;
+ u8 ha[MAX_ADDR_LEN];
+
+ dev = skb->dev;
+ rt = rt6_lookup(&skb->nh.ipv6h->saddr, NULL, dev->ifindex, RT6_LOOKUP_FLAG_STRICT);
+
+ if (rt == NULL)
+ return;
+
+ if (rt->rt6i_flags & RTF_GATEWAY) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "redirect: not a neighbour\n");
+ dst_release(&rt->u.dst);
+ return;
+ }
+ if (!xrlim_allow(&rt->u.dst, 1*HZ)) {
+ dst_release(&rt->u.dst);
+ return;
+ }
+ dst_release(&rt->u.dst);
+
+ if (dev->addr_len) {
+ read_lock_bh(&neigh->lock);
+ if (neigh->nud_state&NUD_VALID) {
+ len += NDISC_OPT_SPACE(dev->addr_len);
+ memcpy(ha, neigh->ha, dev->addr_len);
+ read_unlock_bh(&neigh->lock);
+ } else {
+ read_unlock_bh(&neigh->lock);
+ /* If nexthop is not valid, do not redirect!
+ We will make it later, when will be sure,
+ that it is alive.
+ */
+ return;
+ }
+ }
+
+ rd_len = min_t(unsigned int,
+ IPV6_MIN_MTU-sizeof(struct ipv6hdr)-len, skb->len + 8);
+ rd_len &= ~0x7;
+ len += rd_len;
+
+ if (ipv6_get_lladdr(dev, &saddr_buf)) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "redirect: no link_local addr for dev\n");
+ return;
+ }
+
+ buff = sock_alloc_send_skb(sk, MAX_HEADER + len + dev->hard_header_len + 15,
+ 1, &err);
+ if (buff == NULL) {
+ if (net_ratelimit())
+ ND_PRINTK1(KERN_WARNING
+ "redirect: alloc_skb failed\n");
+ return;
+ }
+
+ hlen = 0;
+
+ if (ndisc_build_ll_hdr(buff, dev, &skb->nh.ipv6h->saddr, NULL, len) == 0) {
+ kfree_skb(buff);
+ return;
+ }
+
+ ip6_nd_hdr(sk, buff, dev, &saddr_buf, &skb->nh.ipv6h->saddr,
+ IPPROTO_ICMPV6, len);
+
+ rdmsg = (struct rd_msg *) skb_put(buff, len);
+
+ memset(rdmsg, 0, sizeof(struct rd_msg));
+ rdmsg->icmph.icmp6_type = NDISC_REDIRECT;
+
+ /*
+ * copy target and destination addresses
+ */
+
+ ipv6_addr_copy(&rdmsg->rd_target, target);
+ ipv6_addr_copy(&rdmsg->rd_dst, &skb->nh.ipv6h->daddr);
+
+ opt = rdmsg->opt;
+
+ /*
+ * include target_address option
+ */
+
+ if (dev->addr_len)
+ opt = ndisc_fill_option(opt, ND_OPT_TARGET_LL_ADDR, ha, dev->addr_len);
+
+ /*
+ * build redirect option and copy skb over to the new packet.
+ */
+
+ memset(opt, 0, 8);
+ *(opt++) = ND_OPT_REDIRECT_HDR;
+ *(opt++) = (rd_len >> 3);
+ opt += 6;
+
+ memcpy(opt, skb->nh.ipv6h, rd_len - 8);
+
+ rdmsg->icmph.icmp6_cksum = csum_ipv6_magic(&saddr_buf,
+ &skb->nh.ipv6h->saddr,
+ len, IPPROTO_ICMPV6,
+ csum_partial((u8 *) rdmsg, len, 0));
+
+ dev_queue_xmit(buff);
+
+ idev = in6_dev_get(dev);
+ ICMP6_INC_STATS(idev,Icmp6OutRedirects);
+ ICMP6_INC_STATS(idev,Icmp6OutMsgs);
+ if (idev)
+ in6_dev_put(idev);
+}
+
+static void pndisc_redo(struct sk_buff *skb)
+{
+ ndisc_rcv(skb);
+ kfree_skb(skb);
+}
+
+int ndisc_rcv(struct sk_buff *skb)
+{
+ struct nd_msg *msg = (struct nd_msg *) skb->h.raw;
+
+ __skb_push(skb, skb->data-skb->h.raw);
+
+ if (skb->nh.ipv6h->hop_limit != 255) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 NDISC: fake message with non-255 Hop Limit received: %d\n",
+ skb->nh.ipv6h->hop_limit);
+ return 0;
+ }
+
+ if (msg->icmph.icmp6_code != 0) {
+ if (net_ratelimit())
+ ND_PRINTK2(KERN_WARNING
+ "ICMP6 NDISC: code is not zero\n");
+ return 0;
+ }
+
+ switch (msg->icmph.icmp6_type) {
+ case NDISC_NEIGHBOUR_SOLICITATION:
+ ndisc_recv_ns(skb);
+ break;
+
+ case NDISC_NEIGHBOUR_ADVERTISEMENT:
+ ndisc_recv_na(skb);
+ break;
+
+ case NDISC_ROUTER_ADVERTISEMENT:
+ ndisc_recv_ra(skb);
+ break;
+
+ case NDISC_ROUTER_SOLICITATION:
+ ndisc_recv_rs(skb);
+ break;
+
+ case NDISC_REDIRECT:
+ ndisc_redirect_rcv(skb);
+ break;
+ };
+
+ return 0;
+}
+
+#ifdef CONFIG_PROC_FS
+#ifdef CONFIG_IPV6_NDISC_DEBUG
+static int ndisc_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int len=0;
+ off_t pos=0;
+ int size;
+ unsigned long now = jiffies;
+ int i;
+
+ for (i = 0; i <= NEIGH_HASHMASK; i++) {
+ struct neighbour *neigh;
+
+ read_lock_bh(&nd_tbl.lock);
+ for (neigh = nd_tbl.hash_buckets[i]; neigh; neigh = neigh->next) {
+ int j;
+
+ size = 0;
+ for (j=0; j<16; j++) {
+ sprintf(buffer+len+size, "%02x", neigh->primary_key[j]);
+ size += 2;
+ }
+
+ read_lock(&neigh->lock);
+ size += sprintf(buffer+len+size,
+ " %02x %02x %02x %02x %08lx %08lx %08x %04x %04x %04x %8s ", i,
+ 128,
+ neigh->type,
+ neigh->nud_state,
+ now - neigh->used,
+ now - neigh->confirmed,
+ neigh->parms->reachable_time,
+ neigh->parms->gc_staletime,
+ atomic_read(&neigh->refcnt) - 1,
+ neigh->flags | (!neigh->hh ? 0 : (neigh->hh->hh_output==dev_queue_xmit ? 4 : 2)),
+ neigh->dev->name);
+
+ if ((neigh->nud_state&NUD_VALID) && neigh->dev->addr_len) {
+ for (j=0; j < neigh->dev->addr_len; j++) {
+ sprintf(buffer+len+size, "%02x", neigh->ha[j]);
+ size += 2;
+ }
+ } else {
+ size += sprintf(buffer+len+size, "000000000000");
+ }
+ read_unlock(&neigh->lock);
+ size += sprintf(buffer+len+size, "\n");
+ len += size;
+ pos += size;
+
+ if (pos <= offset)
+ len=0;
+ if (pos >= offset+length) {
+ read_unlock_bh(&nd_tbl.lock);
+ goto done;
+ }
+ }
+ read_unlock_bh(&nd_tbl.lock);
+ }
+
+done:
+
+ *start = buffer+len-(pos-offset); /* Start of wanted data */
+ len = pos-offset; /* Start slop */
+ if (len>length)
+ len = length; /* Ending slop */
+ if (len<0)
+ len = 0;
+ return len;
+}
+
+#endif
+#endif /* CONFIG_PROC_FS */
+
+
+static int ndisc_netdev_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct net_device *dev = ptr;
+
+ switch (event) {
+ case NETDEV_CHANGEADDR:
+ neigh_changeaddr(&nd_tbl, dev);
+ fib6_run_gc(0);
+ break;
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+struct notifier_block ndisc_netdev_notifier = {
+ .notifier_call = ndisc_netdev_event,
+};
+
+int __init ndisc_init(struct net_proto_family *ops)
+{
+ struct sock *sk;
+ int err;
+
+ ndisc_socket = sock_alloc();
+ if (ndisc_socket == NULL) {
+ ND_PRINTK1(KERN_ERR
+ "ndisc_init(): Failed to create the NDISC control socket.\n");
+ return -1;
+ }
+ ndisc_socket->inode->i_uid = 0;
+ ndisc_socket->inode->i_gid = 0;
+ ndisc_socket->type = SOCK_RAW;
+
+ if((err = ops->create(ndisc_socket, IPPROTO_ICMPV6)) < 0) {
+ ND_PRINTK1(KERN_ERR
+ "ndisc_init(): Failed to initialize the NDISC control socket (err %d).\n",
+ err);
+ sock_release(ndisc_socket);
+ ndisc_socket = NULL; /* For safety. */
+ return err;
+ }
+
+ sk = ndisc_socket->sk;
+ sk->allocation = GFP_ATOMIC;
+ sk->net_pinfo.af_inet6.hop_limit = 255;
+ /* Do not loopback ndisc messages */
+ sk->net_pinfo.af_inet6.mc_loop = 0;
+ sk->prot->unhash(sk);
+
+ /*
+ * Initialize the neighbour table
+ */
+
+ neigh_table_init(&nd_tbl);
+
+#ifdef CONFIG_PROC_FS
+#ifdef CONFIG_IPV6_NDISC_DEBUG
+ proc_net_create("ndisc", 0, ndisc_get_info);
+#endif
+#endif
+#ifdef CONFIG_SYSCTL
+ neigh_sysctl_register(NULL, &nd_tbl.parms, NET_IPV6, NET_IPV6_NEIGH, "ipv6");
+#endif
+
+ register_netdevice_notifier(&ndisc_netdev_notifier);
+ return 0;
+}
+
+void ndisc_cleanup(void)
+{
+#ifdef CONFIG_PROC_FS
+#ifdef CONFIG_IPV6_NDISC_DEBUG
+ proc_net_remove("ndisc");
+#endif
+#endif
+ neigh_table_clear(&nd_tbl);
+ sock_release(ndisc_socket);
+ ndisc_socket = NULL; /* For safety. */
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/Config.in b/uClinux-2.4.31-uc0/net/ipv6/netfilter/Config.in
new file mode 100644
index 0000000..531e3bf
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/Config.in
@@ -0,0 +1,82 @@
+#
+# IP netfilter configuration
+#
+mainmenu_option next_comment
+comment ' IPv6: Netfilter Configuration'
+
+#tristate 'Connection tracking (required for masq/NAT)' CONFIG_IP6_NF_CONNTRACK
+#if [ "$CONFIG_IP6_NF_CONNTRACK" != "n" ]; then
+# dep_tristate ' FTP protocol support' CONFIG_IP6_NF_FTP $CONFIG_IP6_NF_CONNTRACK
+#fi
+
+if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ tristate 'Userspace queueing via NETLINK (EXPERIMENTAL)' CONFIG_IP6_NF_QUEUE
+fi
+
+tristate 'IP6 tables support (required for filtering/masq/NAT)' CONFIG_IP6_NF_IPTABLES
+if [ "$CONFIG_IP6_NF_IPTABLES" != "n" ]; then
+# The simple matches.
+ dep_tristate ' limit match support' CONFIG_IP6_NF_MATCH_LIMIT $CONFIG_IP6_NF_IPTABLES
+ dep_tristate ' MAC address match support' CONFIG_IP6_NF_MATCH_MAC $CONFIG_IP6_NF_IPTABLES
+ if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ dep_tristate ' Routing header match support (EXPERIMENTAL)' CONFIG_IP6_NF_MATCH_RT $CONFIG_IP6_NF_IPTABLES
+ fi
+ if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ dep_tristate ' Hop-by-Hop and Dst opts header match (EXPERIMENTAL)' CONFIG_IP6_NF_MATCH_OPTS $CONFIG_IP6_NF_IPTABLES
+ fi
+ if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ dep_tristate ' Fragmentation header match support (EXPERIMENTAL)' CONFIG_IP6_NF_MATCH_FRAG $CONFIG_IP6_NF_IPTABLES
+ fi
+ dep_tristate ' HL match support' CONFIG_IP6_NF_MATCH_HL $CONFIG_IP6_NF_IPTABLES
+ dep_tristate ' Multiple port match support' CONFIG_IP6_NF_MATCH_MULTIPORT $CONFIG_IP6_NF_IPTABLES
+ if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ dep_tristate ' Owner match support (EXPERIMENTAL)' CONFIG_IP6_NF_MATCH_OWNER $CONFIG_IP6_NF_IPTABLES
+ fi
+# dep_tristate ' MAC address match support' CONFIG_IP6_NF_MATCH_MAC $CONFIG_IP6_NF_IPTABLES
+ dep_tristate ' netfilter MARK match support' CONFIG_IP6_NF_MATCH_MARK $CONFIG_IP6_NF_IPTABLES
+ if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ dep_tristate ' IPv6 Extension Headers Match (EXPERIMENTAL)' CONFIG_IP6_NF_MATCH_IPV6HEADER $CONFIG_IP6_NF_IPTABLES
+ fi
+ if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ dep_tristate ' AH/ESP match support (EXPERIMENTAL)' CONFIG_IP6_NF_MATCH_AHESP $CONFIG_IP6_NF_IPTABLES
+ fi
+ dep_tristate ' Packet Length match support' CONFIG_IP6_NF_MATCH_LENGTH $CONFIG_IP6_NF_IPTABLES
+ if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+ dep_tristate ' EUI64 address check (EXPERIMENTAL)' CONFIG_IP6_NF_MATCH_EUI64 $CONFIG_IP6_NF_IPTABLES
+ fi
+# dep_tristate ' Multiple port match support' CONFIG_IP6_NF_MATCH_MULTIPORT $CONFIG_IP6_NF_IPTABLES
+# dep_tristate ' TOS match support' CONFIG_IP6_NF_MATCH_TOS $CONFIG_IP6_NF_IPTABLES
+# if [ "$CONFIG_IP6_NF_CONNTRACK" != "n" ]; then
+# dep_tristate ' Connection state match support' CONFIG_IP6_NF_MATCH_STATE $CONFIG_IP6_NF_CONNTRACK $CONFIG_IP6_NF_IPTABLES
+# fi
+# if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+# dep_tristate ' Unclean match support (EXPERIMENTAL)' CONFIG_IP6_NF_MATCH_UNCLEAN $CONFIG_IP6_NF_IPTABLES
+# dep_tristate ' Owner match support (EXPERIMENTAL)' CONFIG_IP6_NF_MATCH_OWNER $CONFIG_IP6_NF_IPTABLES
+# fi
+
+# The targets
+ dep_tristate ' Packet filtering' CONFIG_IP6_NF_FILTER $CONFIG_IP6_NF_IPTABLES
+ if [ "$CONFIG_IP6_NF_FILTER" != "n" ]; then
+ dep_tristate ' LOG target support' CONFIG_IP6_NF_TARGET_LOG $CONFIG_IP6_NF_FILTER
+ fi
+ if [ "$CONFIG_IP6_NF_FILTER" != "n" ]; then
+ dep_tristate ' REJECT target support' CONFIG_IP6_NF_TARGET_REJECT $CONFIG_IP6_NF_FILTER
+ fi
+
+# if [ "$CONFIG_IP6_NF_FILTER" != "n" ]; then
+# dep_tristate ' REJECT target support' CONFIG_IP6_NF_TARGET_REJECT $CONFIG_IP6_NF_FILTER
+# if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+# dep_tristate ' MIRROR target support (EXPERIMENTAL)' CONFIG_IP6_NF_TARGET_MIRROR $CONFIG_IP6_NF_FILTER
+# fi
+# fi
+
+ dep_tristate ' Packet mangling' CONFIG_IP6_NF_MANGLE $CONFIG_IP6_NF_IPTABLES
+ if [ "$CONFIG_IP6_NF_MANGLE" != "n" ]; then
+# dep_tristate ' TOS target support' CONFIG_IP6_NF_TARGET_TOS $CONFIG_IP_NF_MANGLE
+ dep_tristate ' MARK target support' CONFIG_IP6_NF_TARGET_MARK $CONFIG_IP6_NF_MANGLE
+ dep_tristate ' IMQ target support' CONFIG_IP6_NF_TARGET_IMQ $CONFIG_IP6_NF_MANGLE
+ fi
+ #dep_tristate ' LOG target support' CONFIG_IP6_NF_TARGET_LOG $CONFIG_IP6_NF_IPTABLES
+fi
+
+endmenu
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/Makefile b/uClinux-2.4.31-uc0/net/ipv6/netfilter/Makefile
new file mode 100644
index 0000000..c04313f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/Makefile
@@ -0,0 +1,37 @@
+#
+# Makefile for the netfilter modules on top of IPv6.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := netfilter.o
+
+export-objs := ip6_tables.o
+
+# Link order matters here.
+obj-$(CONFIG_IP6_NF_IPTABLES) += ip6_tables.o
+obj-$(CONFIG_IP6_NF_MATCH_LIMIT) += ip6t_limit.o
+obj-$(CONFIG_IP6_NF_MATCH_MARK) += ip6t_mark.o
+obj-$(CONFIG_IP6_NF_MATCH_LENGTH) += ip6t_length.o
+obj-$(CONFIG_IP6_NF_MATCH_MAC) += ip6t_mac.o
+obj-$(CONFIG_IP6_NF_MATCH_RT) += ip6t_rt.o
+obj-$(CONFIG_IP6_NF_MATCH_OPTS) += ip6t_hbh.o ip6t_dst.o
+obj-$(CONFIG_IP6_NF_MATCH_IPV6HEADER) += ip6t_ipv6header.o
+obj-$(CONFIG_IP6_NF_MATCH_FRAG) += ip6t_frag.o
+obj-$(CONFIG_IP6_NF_MATCH_AHESP) += ip6t_esp.o ip6t_ah.o
+obj-$(CONFIG_IP6_NF_MATCH_EUI64) += ip6t_eui64.o
+obj-$(CONFIG_IP6_NF_MATCH_MULTIPORT) += ip6t_multiport.o
+obj-$(CONFIG_IP6_NF_MATCH_OWNER) += ip6t_owner.o
+obj-$(CONFIG_IP6_NF_FILTER) += ip6table_filter.o
+obj-$(CONFIG_IP6_NF_MANGLE) += ip6table_mangle.o
+obj-$(CONFIG_IP6_NF_TARGET_MARK) += ip6t_MARK.o
+obj-$(CONFIG_IP6_NF_TARGET_IMQ) += ip6t_IMQ.o
+obj-$(CONFIG_IP6_NF_QUEUE) += ip6_queue.o
+obj-$(CONFIG_IP6_NF_TARGET_LOG) += ip6t_LOG.o
+obj-$(CONFIG_IP6_NF_MATCH_HL) += ip6t_hl.o
+obj-$(CONFIG_IP6_NF_TARGET_REJECT) += ip6t_REJECT.o
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6_queue.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6_queue.c
new file mode 100644
index 0000000..56bdf34
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6_queue.c
@@ -0,0 +1,710 @@
+/*
+ * This is a module which is used for queueing IPv6 packets and
+ * communicating with userspace via netlink.
+ *
+ * (C) 2001 Fernando Anton, this code is GPL.
+ * IPv64 Project - Work based in IPv64 draft by Arturo Azcorra.
+ * Universidad Carlos III de Madrid - Leganes (Madrid) - Spain
+ * Universidad Politecnica de Alcala de Henares - Alcala de H. (Madrid) - Spain
+ * email: fanton@it.uc3m.es
+ *
+ * 2001-11-06: First try. Working with ip_queue.c for IPv4 and trying
+ * to adapt it to IPv6
+ * HEAVILY based in ipqueue.c by James Morris. It's just
+ * a little modified version of it, so he's nearly the
+ * real coder of this.
+ * Few changes needed, mainly the hard_routing code and
+ * the netlink socket protocol (we're NETLINK_IP6_FW).
+ * 2002-06-25: Code cleanup. [JM: ported cleanup over from ip_queue.c]
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+#include <linux/ipv6.h>
+#include <linux/notifier.h>
+#include <linux/netdevice.h>
+#include <linux/netfilter.h>
+#include <linux/netlink.h>
+#include <linux/spinlock.h>
+#include <linux/brlock.h>
+#include <linux/sysctl.h>
+#include <linux/proc_fs.h>
+#include <net/sock.h>
+#include <net/ipv6.h>
+#include <net/ip6_route.h>
+#include <linux/netfilter_ipv4/ip_queue.h>
+#include <linux/netfilter_ipv4/ip_tables.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+#define IPQ_QMAX_DEFAULT 1024
+#define IPQ_PROC_FS_NAME "ip6_queue"
+#define NET_IPQ_QMAX 2088
+#define NET_IPQ_QMAX_NAME "ip6_queue_maxlen"
+
+struct ipq_rt_info {
+ struct in6_addr daddr;
+ struct in6_addr saddr;
+};
+
+struct ipq_queue_entry {
+ struct list_head list;
+ struct nf_info *info;
+ struct sk_buff *skb;
+ struct ipq_rt_info rt_info;
+};
+
+typedef int (*ipq_cmpfn)(struct ipq_queue_entry *, unsigned long);
+
+static unsigned char copy_mode = IPQ_COPY_NONE;
+static unsigned int queue_maxlen = IPQ_QMAX_DEFAULT;
+static rwlock_t queue_lock = RW_LOCK_UNLOCKED;
+static int peer_pid;
+static unsigned int copy_range;
+static unsigned int queue_total;
+static struct sock *ipqnl;
+static LIST_HEAD(queue_list);
+static DECLARE_MUTEX(ipqnl_sem);
+
+static void
+ipq_issue_verdict(struct ipq_queue_entry *entry, int verdict)
+{
+ nf_reinject(entry->skb, entry->info, verdict);
+ kfree(entry);
+}
+
+static inline int
+__ipq_enqueue_entry(struct ipq_queue_entry *entry)
+{
+ if (queue_total >= queue_maxlen) {
+ if (net_ratelimit())
+ printk(KERN_WARNING "ip6_queue: full at %d entries, "
+ "dropping packet(s).\n", queue_total);
+ return -ENOSPC;
+ }
+ list_add(&entry->list, &queue_list);
+ queue_total++;
+ return 0;
+}
+
+/*
+ * Find and return a queued entry matched by cmpfn, or return the last
+ * entry if cmpfn is NULL.
+ */
+static inline struct ipq_queue_entry *
+__ipq_find_entry(ipq_cmpfn cmpfn, unsigned long data)
+{
+ struct list_head *p;
+
+ list_for_each_prev(p, &queue_list) {
+ struct ipq_queue_entry *entry = (struct ipq_queue_entry *)p;
+
+ if (!cmpfn || cmpfn(entry, data))
+ return entry;
+ }
+ return NULL;
+}
+
+static inline void
+__ipq_dequeue_entry(struct ipq_queue_entry *entry)
+{
+ list_del(&entry->list);
+ queue_total--;
+}
+
+static inline struct ipq_queue_entry *
+__ipq_find_dequeue_entry(ipq_cmpfn cmpfn, unsigned long data)
+{
+ struct ipq_queue_entry *entry;
+
+ entry = __ipq_find_entry(cmpfn, data);
+ if (entry == NULL)
+ return NULL;
+
+ __ipq_dequeue_entry(entry);
+ return entry;
+}
+
+
+static inline void
+__ipq_flush(int verdict)
+{
+ struct ipq_queue_entry *entry;
+
+ while ((entry = __ipq_find_dequeue_entry(NULL, 0)))
+ ipq_issue_verdict(entry, verdict);
+}
+
+static inline int
+__ipq_set_mode(unsigned char mode, unsigned int range)
+{
+ int status = 0;
+
+ switch(mode) {
+ case IPQ_COPY_NONE:
+ case IPQ_COPY_META:
+ copy_mode = mode;
+ copy_range = 0;
+ break;
+
+ case IPQ_COPY_PACKET:
+ copy_mode = mode;
+ copy_range = range;
+ if (copy_range > 0xFFFF)
+ copy_range = 0xFFFF;
+ break;
+
+ default:
+ status = -EINVAL;
+
+ }
+ return status;
+}
+
+static inline void
+__ipq_reset(void)
+{
+ peer_pid = 0;
+ __ipq_set_mode(IPQ_COPY_NONE, 0);
+ __ipq_flush(NF_DROP);
+}
+
+static struct ipq_queue_entry *
+ipq_find_dequeue_entry(ipq_cmpfn cmpfn, unsigned long data)
+{
+ struct ipq_queue_entry *entry;
+
+ write_lock_bh(&queue_lock);
+ entry = __ipq_find_dequeue_entry(cmpfn, data);
+ write_unlock_bh(&queue_lock);
+ return entry;
+}
+
+static void
+ipq_flush(int verdict)
+{
+ write_lock_bh(&queue_lock);
+ __ipq_flush(verdict);
+ write_unlock_bh(&queue_lock);
+}
+
+static struct sk_buff *
+ipq_build_packet_message(struct ipq_queue_entry *entry, int *errp)
+{
+ unsigned char *old_tail;
+ size_t size = 0;
+ size_t data_len = 0;
+ struct sk_buff *skb;
+ struct ipq_packet_msg *pmsg;
+ struct nlmsghdr *nlh;
+
+ read_lock_bh(&queue_lock);
+
+ switch (copy_mode) {
+ case IPQ_COPY_META:
+ case IPQ_COPY_NONE:
+ size = NLMSG_SPACE(sizeof(*pmsg));
+ data_len = 0;
+ break;
+
+ case IPQ_COPY_PACKET:
+ if (copy_range == 0 || copy_range > entry->skb->len)
+ data_len = entry->skb->len;
+ else
+ data_len = copy_range;
+
+ size = NLMSG_SPACE(sizeof(*pmsg) + data_len);
+ break;
+
+ default:
+ *errp = -EINVAL;
+ read_unlock_bh(&queue_lock);
+ return NULL;
+ }
+
+ read_unlock_bh(&queue_lock);
+
+ skb = alloc_skb(size, GFP_ATOMIC);
+ if (!skb)
+ goto nlmsg_failure;
+
+ old_tail= skb->tail;
+ nlh = NLMSG_PUT(skb, 0, 0, IPQM_PACKET, size - sizeof(*nlh));
+ pmsg = NLMSG_DATA(nlh);
+ memset(pmsg, 0, sizeof(*pmsg));
+
+ pmsg->packet_id = (unsigned long )entry;
+ pmsg->data_len = data_len;
+ pmsg->timestamp_sec = entry->skb->stamp.tv_sec;
+ pmsg->timestamp_usec = entry->skb->stamp.tv_usec;
+ pmsg->mark = entry->skb->nfmark;
+ pmsg->hook = entry->info->hook;
+ pmsg->hw_protocol = entry->skb->protocol;
+
+ if (entry->info->indev)
+ strcpy(pmsg->indev_name, entry->info->indev->name);
+ else
+ pmsg->indev_name[0] = '\0';
+
+ if (entry->info->outdev)
+ strcpy(pmsg->outdev_name, entry->info->outdev->name);
+ else
+ pmsg->outdev_name[0] = '\0';
+
+ if (entry->info->indev && entry->skb->dev) {
+ pmsg->hw_type = entry->skb->dev->type;
+ if (entry->skb->dev->hard_header_parse)
+ pmsg->hw_addrlen =
+ entry->skb->dev->hard_header_parse(entry->skb,
+ pmsg->hw_addr);
+ }
+
+ if (data_len)
+ memcpy(pmsg->payload, entry->skb->data, data_len);
+
+ nlh->nlmsg_len = skb->tail - old_tail;
+ return skb;
+
+nlmsg_failure:
+ if (skb)
+ kfree_skb(skb);
+ *errp = -EINVAL;
+ printk(KERN_ERR "ip6_queue: error creating packet message\n");
+ return NULL;
+}
+
+static int
+ipq_enqueue_packet(struct sk_buff *skb, struct nf_info *info, void *data)
+{
+ int status = -EINVAL;
+ struct sk_buff *nskb;
+ struct ipq_queue_entry *entry;
+
+ if (copy_mode == IPQ_COPY_NONE)
+ return -EAGAIN;
+
+ entry = kmalloc(sizeof(*entry), GFP_ATOMIC);
+ if (entry == NULL) {
+ printk(KERN_ERR "ip6_queue: OOM in ipq_enqueue_packet()\n");
+ return -ENOMEM;
+ }
+
+ entry->info = info;
+ entry->skb = skb;
+
+ if (entry->info->hook == NF_IP_LOCAL_OUT) {
+ struct ipv6hdr *iph = skb->nh.ipv6h;
+
+ entry->rt_info.daddr = iph->daddr;
+ entry->rt_info.saddr = iph->saddr;
+ }
+
+ nskb = ipq_build_packet_message(entry, &status);
+ if (nskb == NULL)
+ goto err_out_free;
+
+ write_lock_bh(&queue_lock);
+
+ if (!peer_pid)
+ goto err_out_free_nskb;
+
+ /* netlink_unicast will either free the nskb or attach it to a socket */
+ status = netlink_unicast(ipqnl, nskb, peer_pid, MSG_DONTWAIT);
+ if (status < 0)
+ goto err_out_unlock;
+
+ status = __ipq_enqueue_entry(entry);
+ if (status < 0)
+ goto err_out_unlock;
+
+ write_unlock_bh(&queue_lock);
+ return status;
+
+err_out_free_nskb:
+ kfree_skb(nskb);
+
+err_out_unlock:
+ write_unlock_bh(&queue_lock);
+
+err_out_free:
+ kfree(entry);
+ return status;
+}
+
+static int
+ipq_mangle_ipv6(ipq_verdict_msg_t *v, struct ipq_queue_entry *e)
+{
+ int diff;
+ struct ipv6hdr *user_iph = (struct ipv6hdr *)v->payload;
+
+ if (v->data_len < sizeof(*user_iph))
+ return 0;
+ diff = v->data_len - e->skb->len;
+ if (diff < 0)
+ skb_trim(e->skb, v->data_len);
+ else if (diff > 0) {
+ if (v->data_len > 0xFFFF)
+ return -EINVAL;
+ if (diff > skb_tailroom(e->skb)) {
+ struct sk_buff *newskb;
+
+ newskb = skb_copy_expand(e->skb,
+ skb_headroom(e->skb),
+ diff,
+ GFP_ATOMIC);
+ if (newskb == NULL) {
+ printk(KERN_WARNING "ip6_queue: OOM "
+ "in mangle, dropping packet\n");
+ return -ENOMEM;
+ }
+ if (e->skb->sk)
+ skb_set_owner_w(newskb, e->skb->sk);
+ kfree_skb(e->skb);
+ e->skb = newskb;
+ }
+ skb_put(e->skb, diff);
+ }
+ memcpy(e->skb->data, v->payload, v->data_len);
+ e->skb->nfcache |= NFC_ALTERED;
+
+ /*
+ * Extra routing may needed on local out, as the QUEUE target never
+ * returns control to the table.
+ * Not a nice way to cmp, but works
+ */
+ if (e->info->hook == NF_IP_LOCAL_OUT) {
+ struct ipv6hdr *iph = e->skb->nh.ipv6h;
+ if (ipv6_addr_cmp(&iph->daddr, &e->rt_info.daddr) ||
+ ipv6_addr_cmp(&iph->saddr, &e->rt_info.saddr))
+ return ip6_route_me_harder(e->skb);
+ }
+ return 0;
+}
+
+static inline int
+id_cmp(struct ipq_queue_entry *e, unsigned long id)
+{
+ return (id == (unsigned long )e);
+}
+
+static int
+ipq_set_verdict(struct ipq_verdict_msg *vmsg, unsigned int len)
+{
+ struct ipq_queue_entry *entry;
+
+ if (vmsg->value > NF_MAX_VERDICT)
+ return -EINVAL;
+
+ entry = ipq_find_dequeue_entry(id_cmp, vmsg->id);
+ if (entry == NULL)
+ return -ENOENT;
+ else {
+ int verdict = vmsg->value;
+
+ if (vmsg->data_len && vmsg->data_len == len)
+ if (ipq_mangle_ipv6(vmsg, entry) < 0)
+ verdict = NF_DROP;
+
+ ipq_issue_verdict(entry, verdict);
+ return 0;
+ }
+}
+
+static int
+ipq_set_mode(unsigned char mode, unsigned int range)
+{
+ int status;
+
+ write_lock_bh(&queue_lock);
+ status = __ipq_set_mode(mode, range);
+ write_unlock_bh(&queue_lock);
+ return status;
+}
+
+static int
+ipq_receive_peer(struct ipq_peer_msg *pmsg,
+ unsigned char type, unsigned int len)
+{
+ int status = 0;
+
+ if (len < sizeof(*pmsg))
+ return -EINVAL;
+
+ switch (type) {
+ case IPQM_MODE:
+ status = ipq_set_mode(pmsg->msg.mode.value,
+ pmsg->msg.mode.range);
+ break;
+
+ case IPQM_VERDICT:
+ if (pmsg->msg.verdict.value > NF_MAX_VERDICT)
+ status = -EINVAL;
+ else
+ status = ipq_set_verdict(&pmsg->msg.verdict,
+ len - sizeof(*pmsg));
+ break;
+ default:
+ status = -EINVAL;
+ }
+ return status;
+}
+
+static int
+dev_cmp(struct ipq_queue_entry *entry, unsigned long ifindex)
+{
+ if (entry->info->indev)
+ if (entry->info->indev->ifindex == ifindex)
+ return 1;
+
+ if (entry->info->outdev)
+ if (entry->info->outdev->ifindex == ifindex)
+ return 1;
+
+ return 0;
+}
+
+static void
+ipq_dev_drop(int ifindex)
+{
+ struct ipq_queue_entry *entry;
+
+ while ((entry = ipq_find_dequeue_entry(dev_cmp, ifindex)) != NULL)
+ ipq_issue_verdict(entry, NF_DROP);
+}
+
+#define RCV_SKB_FAIL(err) do { netlink_ack(skb, nlh, (err)); return; } while (0)
+
+static inline void
+ipq_rcv_skb(struct sk_buff *skb)
+{
+ int status, type, pid, flags, nlmsglen, skblen;
+ struct nlmsghdr *nlh;
+
+ skblen = skb->len;
+ if (skblen < sizeof(*nlh))
+ return;
+
+ nlh = (struct nlmsghdr *)skb->data;
+ nlmsglen = nlh->nlmsg_len;
+ if (nlmsglen < sizeof(*nlh) || skblen < nlmsglen)
+ return;
+
+ pid = nlh->nlmsg_pid;
+ flags = nlh->nlmsg_flags;
+
+ if(pid <= 0 || !(flags & NLM_F_REQUEST) || flags & NLM_F_MULTI)
+ RCV_SKB_FAIL(-EINVAL);
+
+ if (flags & MSG_TRUNC)
+ RCV_SKB_FAIL(-ECOMM);
+
+ type = nlh->nlmsg_type;
+ if (type < NLMSG_NOOP || type >= IPQM_MAX)
+ RCV_SKB_FAIL(-EINVAL);
+
+ if (type <= IPQM_BASE)
+ return;
+
+ if(!cap_raised(NETLINK_CB(skb).eff_cap, CAP_NET_ADMIN))
+ RCV_SKB_FAIL(-EPERM);
+
+ write_lock_bh(&queue_lock);
+
+ if (peer_pid) {
+ if (peer_pid != pid) {
+ write_unlock_bh(&queue_lock);
+ RCV_SKB_FAIL(-EBUSY);
+ }
+ }
+ else
+ peer_pid = pid;
+
+ write_unlock_bh(&queue_lock);
+
+ status = ipq_receive_peer(NLMSG_DATA(nlh), type,
+ skblen - NLMSG_LENGTH(0));
+ if (status < 0)
+ RCV_SKB_FAIL(status);
+
+ if (flags & NLM_F_ACK)
+ netlink_ack(skb, nlh, 0);
+ return;
+}
+
+static void
+ipq_rcv_sk(struct sock *sk, int len)
+{
+ do {
+ struct sk_buff *skb;
+
+ if (down_trylock(&ipqnl_sem))
+ return;
+
+ while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
+ ipq_rcv_skb(skb);
+ kfree_skb(skb);
+ }
+
+ up(&ipqnl_sem);
+
+ } while (ipqnl && ipqnl->receive_queue.qlen);
+}
+
+static int
+ipq_rcv_dev_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = ptr;
+
+ /* Drop any packets associated with the downed device */
+ if (event == NETDEV_DOWN)
+ ipq_dev_drop(dev->ifindex);
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block ipq_dev_notifier = {
+ ipq_rcv_dev_event,
+ NULL,
+ 0
+};
+
+static int
+ipq_rcv_nl_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ struct netlink_notify *n = ptr;
+
+ if (event == NETLINK_URELEASE &&
+ n->protocol == NETLINK_IP6_FW && n->pid) {
+ write_lock_bh(&queue_lock);
+ if (n->pid == peer_pid)
+ __ipq_reset();
+ write_unlock_bh(&queue_lock);
+ }
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block ipq_nl_notifier = {
+ ipq_rcv_nl_event,
+ NULL,
+ 0
+};
+
+static struct ctl_table_header *ipq_sysctl_header;
+
+static ctl_table ipq_table[] = {
+ { NET_IPQ_QMAX, NET_IPQ_QMAX_NAME, &queue_maxlen,
+ sizeof(queue_maxlen), 0644, NULL, proc_dointvec },
+ { 0 }
+};
+
+static ctl_table ipq_dir_table[] = {
+ {NET_IPV6, "ipv6", NULL, 0, 0555, ipq_table, 0, 0, 0, 0, 0},
+ { 0 }
+};
+
+static ctl_table ipq_root_table[] = {
+ {CTL_NET, "net", NULL, 0, 0555, ipq_dir_table, 0, 0, 0, 0, 0},
+ { 0 }
+};
+
+static int
+ipq_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int len;
+
+ read_lock_bh(&queue_lock);
+
+ len = sprintf(buffer,
+ "Peer PID : %d\n"
+ "Copy mode : %hu\n"
+ "Copy range : %u\n"
+ "Queue length : %u\n"
+ "Queue max. length : %u\n",
+ peer_pid,
+ copy_mode,
+ copy_range,
+ queue_total,
+ queue_maxlen);
+
+ read_unlock_bh(&queue_lock);
+
+ *start = buffer + offset;
+ len -= offset;
+ if (len > length)
+ len = length;
+ else if (len < 0)
+ len = 0;
+ return len;
+}
+
+static int
+init_or_cleanup(int init)
+{
+ int status = -ENOMEM;
+ struct proc_dir_entry *proc;
+
+ if (!init)
+ goto cleanup;
+
+ netlink_register_notifier(&ipq_nl_notifier);
+ ipqnl = netlink_kernel_create(NETLINK_IP6_FW, ipq_rcv_sk);
+ if (ipqnl == NULL) {
+ printk(KERN_ERR "ip6_queue: failed to create netlink socket\n");
+ goto cleanup_netlink_notifier;
+ }
+
+ proc = proc_net_create(IPQ_PROC_FS_NAME, 0, ipq_get_info);
+ if (proc)
+ proc->owner = THIS_MODULE;
+ else {
+ printk(KERN_ERR "ip6_queue: failed to create proc entry\n");
+ goto cleanup_ipqnl;
+ }
+
+ register_netdevice_notifier(&ipq_dev_notifier);
+ ipq_sysctl_header = register_sysctl_table(ipq_root_table, 0);
+
+ status = nf_register_queue_handler(PF_INET6, ipq_enqueue_packet, NULL);
+ if (status < 0) {
+ printk(KERN_ERR "ip6_queue: failed to register queue handler\n");
+ goto cleanup_sysctl;
+ }
+ return status;
+
+cleanup:
+ nf_unregister_queue_handler(PF_INET6);
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ ipq_flush(NF_DROP);
+
+cleanup_sysctl:
+ unregister_sysctl_table(ipq_sysctl_header);
+ unregister_netdevice_notifier(&ipq_dev_notifier);
+ proc_net_remove(IPQ_PROC_FS_NAME);
+
+cleanup_ipqnl:
+ sock_release(ipqnl->socket);
+ down(&ipqnl_sem);
+ up(&ipqnl_sem);
+
+cleanup_netlink_notifier:
+ netlink_unregister_notifier(&ipq_nl_notifier);
+ return status;
+}
+
+static int __init init(void)
+{
+
+ return init_or_cleanup(1);
+}
+
+static void __exit fini(void)
+{
+ init_or_cleanup(0);
+}
+
+MODULE_DESCRIPTION("IPv6 packet queue handler");
+MODULE_LICENSE("GPL");
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6_tables.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6_tables.c
new file mode 100644
index 0000000..31ead31
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6_tables.c
@@ -0,0 +1,1935 @@
+/*
+ * Packet matching code.
+ *
+ * Copyright (C) 1999 Paul `Rusty' Russell & Michael J. Neuling
+ * Copyright (C) 2000-2002 Netfilter core team <coreteam@netfilter.org>
+ *
+ * 19 Jan 2002 Harald Welte <laforge@gnumonks.org>
+ * - increase module usage count as soon as we have rules inside
+ * a table
+ * 06 Jun 2002 Andras Kis-Szabo <kisza@sch.bme.hu>
+ * - new extension header parser code
+ */
+#include <linux/config.h>
+#include <linux/skbuff.h>
+#include <linux/kmod.h>
+#include <linux/vmalloc.h>
+#include <linux/netdevice.h>
+#include <linux/module.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/icmpv6.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <asm/uaccess.h>
+#include <asm/semaphore.h>
+#include <linux/proc_fs.h>
+
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+#define IPV6_HDR_LEN (sizeof(struct ipv6hdr))
+#define IPV6_OPTHDR_LEN (sizeof(struct ipv6_opt_hdr))
+
+/*#define DEBUG_IP_FIREWALL*/
+/*#define DEBUG_ALLOW_ALL*/ /* Useful for remote debugging */
+/*#define DEBUG_IP_FIREWALL_USER*/
+
+#ifdef DEBUG_IP_FIREWALL
+#define dprintf(format, args...) printk(format , ## args)
+#else
+#define dprintf(format, args...)
+#endif
+
+#ifdef DEBUG_IP_FIREWALL_USER
+#define duprintf(format, args...) printk(format , ## args)
+#else
+#define duprintf(format, args...)
+#endif
+
+#ifdef CONFIG_NETFILTER_DEBUG
+#define IP_NF_ASSERT(x) \
+do { \
+ if (!(x)) \
+ printk("IP_NF_ASSERT: %s:%s:%u\n", \
+ __FUNCTION__, __FILE__, __LINE__); \
+} while(0)
+#else
+#define IP_NF_ASSERT(x)
+#endif
+#define SMP_ALIGN(x) (((x) + SMP_CACHE_BYTES-1) & ~(SMP_CACHE_BYTES-1))
+
+static DECLARE_MUTEX(ip6t_mutex);
+
+/* Must have mutex */
+#define ASSERT_READ_LOCK(x) IP_NF_ASSERT(down_trylock(&ip6t_mutex) != 0)
+#define ASSERT_WRITE_LOCK(x) IP_NF_ASSERT(down_trylock(&ip6t_mutex) != 0)
+#include <linux/netfilter_ipv4/lockhelp.h>
+#include <linux/netfilter_ipv4/listhelp.h>
+
+#if 0
+/* All the better to debug you with... */
+#define static
+#define inline
+#endif
+
+/* Locking is simple: we assume at worst case there will be one packet
+ in user context and one from bottom halves (or soft irq if Alexey's
+ softnet patch was applied).
+
+ We keep a set of rules for each CPU, so we can avoid write-locking
+ them; doing a readlock_bh() stops packets coming through if we're
+ in user context.
+
+ To be cache friendly on SMP, we arrange them like so:
+ [ n-entries ]
+ ... cache-align padding ...
+ [ n-entries ]
+
+ Hence the start of any table is given by get_table() below. */
+
+/* The table itself */
+struct ip6t_table_info
+{
+ /* Size per table */
+ unsigned int size;
+ /* Number of entries: FIXME. --RR */
+ unsigned int number;
+ /* Initial number of entries. Needed for module usage count */
+ unsigned int initial_entries;
+
+ /* Entry points and underflows */
+ unsigned int hook_entry[NF_IP6_NUMHOOKS];
+ unsigned int underflow[NF_IP6_NUMHOOKS];
+
+ /* ip6t_entry tables: one per CPU */
+ char entries[0] ____cacheline_aligned;
+};
+
+static LIST_HEAD(ip6t_target);
+static LIST_HEAD(ip6t_match);
+static LIST_HEAD(ip6t_tables);
+#define ADD_COUNTER(c,b,p) do { (c).bcnt += (b); (c).pcnt += (p); } while(0)
+
+#ifdef CONFIG_SMP
+#define TABLE_OFFSET(t,p) (SMP_ALIGN((t)->size)*(p))
+#else
+#define TABLE_OFFSET(t,p) 0
+#endif
+
+#if 0
+#define down(x) do { printk("DOWN:%u:" #x "\n", __LINE__); down(x); } while(0)
+#define down_interruptible(x) ({ int __r; printk("DOWNi:%u:" #x "\n", __LINE__); __r = down_interruptible(x); if (__r != 0) printk("ABORT-DOWNi:%u\n", __LINE__); __r; })
+#define up(x) do { printk("UP:%u:" #x "\n", __LINE__); up(x); } while(0)
+#endif
+
+static int ip6_masked_addrcmp(struct in6_addr addr1, struct in6_addr mask,
+ struct in6_addr addr2)
+{
+ int i;
+ for( i = 0; i < 16; i++){
+ if((addr1.s6_addr[i] & mask.s6_addr[i]) !=
+ (addr2.s6_addr[i] & mask.s6_addr[i]))
+ return 1;
+ }
+ return 0;
+}
+
+/* Check for an extension */
+int
+ip6t_ext_hdr(u8 nexthdr)
+{
+ return ( (nexthdr == IPPROTO_HOPOPTS) ||
+ (nexthdr == IPPROTO_ROUTING) ||
+ (nexthdr == IPPROTO_FRAGMENT) ||
+ (nexthdr == IPPROTO_ESP) ||
+ (nexthdr == IPPROTO_AH) ||
+ (nexthdr == IPPROTO_NONE) ||
+ (nexthdr == IPPROTO_DSTOPTS) );
+}
+
+/* Returns whether matches rule or not. */
+static inline int
+ip6_packet_match(const struct sk_buff *skb,
+ const char *indev,
+ const char *outdev,
+ const struct ip6t_ip6 *ip6info,
+ unsigned int *protoff,
+ int *fragoff)
+{
+ size_t i;
+ unsigned long ret;
+ const struct ipv6hdr *ipv6 = skb->nh.ipv6h;
+
+#define FWINV(bool,invflg) ((bool) ^ !!(ip6info->invflags & invflg))
+
+ if (FWINV(ip6_masked_addrcmp(ipv6->saddr,ip6info->smsk,ip6info->src),
+ IP6T_INV_SRCIP)
+ || FWINV(ip6_masked_addrcmp(ipv6->daddr,ip6info->dmsk,ip6info->dst),
+ IP6T_INV_DSTIP)) {
+ dprintf("Source or dest mismatch.\n");
+/*
+ dprintf("SRC: %u. Mask: %u. Target: %u.%s\n", ip->saddr,
+ ipinfo->smsk.s_addr, ipinfo->src.s_addr,
+ ipinfo->invflags & IP6T_INV_SRCIP ? " (INV)" : "");
+ dprintf("DST: %u. Mask: %u. Target: %u.%s\n", ip->daddr,
+ ipinfo->dmsk.s_addr, ipinfo->dst.s_addr,
+ ipinfo->invflags & IP6T_INV_DSTIP ? " (INV)" : "");*/
+ return 0;
+ }
+
+ /* Look for ifname matches; this should unroll nicely. */
+ for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) {
+ ret |= (((const unsigned long *)indev)[i]
+ ^ ((const unsigned long *)ip6info->iniface)[i])
+ & ((const unsigned long *)ip6info->iniface_mask)[i];
+ }
+
+ if (FWINV(ret != 0, IP6T_INV_VIA_IN)) {
+ dprintf("VIA in mismatch (%s vs %s).%s\n",
+ indev, ip6info->iniface,
+ ip6info->invflags&IP6T_INV_VIA_IN ?" (INV)":"");
+ return 0;
+ }
+
+ for (i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long); i++) {
+ ret |= (((const unsigned long *)outdev)[i]
+ ^ ((const unsigned long *)ip6info->outiface)[i])
+ & ((const unsigned long *)ip6info->outiface_mask)[i];
+ }
+
+ if (FWINV(ret != 0, IP6T_INV_VIA_OUT)) {
+ dprintf("VIA out mismatch (%s vs %s).%s\n",
+ outdev, ip6info->outiface,
+ ip6info->invflags&IP6T_INV_VIA_OUT ?" (INV)":"");
+ return 0;
+ }
+
+/* ... might want to do something with class and flowlabel here ... */
+
+ /* look for the desired protocol header */
+ if((ip6info->flags & IP6T_F_PROTO)) {
+ u_int8_t currenthdr = ipv6->nexthdr;
+ struct ipv6_opt_hdr *hdrptr;
+ u_int16_t ptr; /* Header offset in skb */
+ u_int16_t hdrlen; /* Header */
+ u_int16_t foff = 0;
+
+ ptr = IPV6_HDR_LEN;
+
+ while (ip6t_ext_hdr(currenthdr)) {
+ /* Is there enough space for the next ext header? */
+ if (skb->len - ptr < IPV6_OPTHDR_LEN)
+ return 0;
+
+ /* NONE or ESP: there isn't protocol part */
+ /* If we want to count these packets in '-p all',
+ * we will change the return 0 to 1*/
+ if ((currenthdr == IPPROTO_NONE) ||
+ (currenthdr == IPPROTO_ESP))
+ break;
+
+ hdrptr = (struct ipv6_opt_hdr *)(skb->data + ptr);
+
+ /* Size calculation */
+ if (currenthdr == IPPROTO_FRAGMENT) {
+ unsigned int foff_off;
+
+ foff_off = ptr + offsetof(struct frag_hdr,
+ frag_off);
+ if (skb->len - foff_off < sizeof(foff))
+ return 0;
+
+ foff = ntohs(*(u_int16_t *)(skb->data
+ + foff_off))
+ & ~0x7;
+ hdrlen = 8;
+ } else if (currenthdr == IPPROTO_AH)
+ hdrlen = (hdrptr->hdrlen+2)<<2;
+ else
+ hdrlen = ipv6_optlen(hdrptr);
+
+ currenthdr = hdrptr->nexthdr;
+ ptr += hdrlen;
+ /* ptr is too large */
+ if ( ptr > skb->len )
+ return 0;
+ if (foff) {
+ if (ip6t_ext_hdr(currenthdr))
+ return 0;
+ break;
+ }
+ }
+
+ *protoff = ptr;
+ *fragoff = foff;
+
+ /* currenthdr contains the protocol header */
+
+ dprintf("Packet protocol %hi ?= %s%hi.\n",
+ currenthdr,
+ ip6info->invflags & IP6T_INV_PROTO ? "!":"",
+ ip6info->proto);
+
+ if (ip6info->proto == currenthdr) {
+ if(ip6info->invflags & IP6T_INV_PROTO) {
+ return 0;
+ }
+ return 1;
+ }
+
+ /* We need match for the '-p all', too! */
+ if ((ip6info->proto != 0) &&
+ !(ip6info->invflags & IP6T_INV_PROTO))
+ return 0;
+ }
+ return 1;
+}
+
+/* should be ip6 safe */
+static inline int
+ip6_checkentry(const struct ip6t_ip6 *ipv6)
+{
+ if (ipv6->flags & ~IP6T_F_MASK) {
+ duprintf("Unknown flag bits set: %08X\n",
+ ipv6->flags & ~IP6T_F_MASK);
+ return 0;
+ }
+ if (ipv6->invflags & ~IP6T_INV_MASK) {
+ duprintf("Unknown invflag bits set: %08X\n",
+ ipv6->invflags & ~IP6T_INV_MASK);
+ return 0;
+ }
+ return 1;
+}
+
+static unsigned int
+ip6t_error(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ if (net_ratelimit())
+ printk("ip6_tables: error: `%s'\n", (char *)targinfo);
+
+ return NF_DROP;
+}
+
+static inline
+int do_match(struct ip6t_entry_match *m,
+ const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ /* Stop iteration if it doesn't match */
+ if (!m->u.kernel.match->match(skb, in, out, m->data,
+ offset, hdr, datalen, hotdrop))
+ return 1;
+ else
+ return 0;
+}
+
+static inline struct ip6t_entry *
+get_entry(void *base, unsigned int offset)
+{
+ return (struct ip6t_entry *)(base + offset);
+}
+
+/* Returns one of the generic firewall policies, like NF_ACCEPT. */
+unsigned int
+ip6t_do_table(struct sk_buff **pskb,
+ unsigned int hook,
+ const struct net_device *in,
+ const struct net_device *out,
+ struct ip6t_table *table,
+ void *userdata)
+{
+ static const char nulldevname[IFNAMSIZ] = { 0 };
+ int offset = 0;
+ unsigned int protoff = 0;
+ int hotdrop = 0;
+ /* Initializing verdict to NF_DROP keeps gcc happy. */
+ unsigned int verdict = NF_DROP;
+ const char *indev, *outdev;
+ void *table_base;
+ struct ip6t_entry *e, *back;
+
+ /* Initialization */
+ indev = in ? in->name : nulldevname;
+ outdev = out ? out->name : nulldevname;
+
+ /* We handle fragments by dealing with the first fragment as
+ * if it was a normal packet. All other fragments are treated
+ * normally, except that they will NEVER match rules that ask
+ * things we don't know, ie. tcp syn flag or ports). If the
+ * rule is also a fragment-specific rule, non-fragments won't
+ * match it. */
+
+ read_lock_bh(&table->lock);
+ IP_NF_ASSERT(table->valid_hooks & (1 << hook));
+ table_base = (void *)table->private->entries
+ + TABLE_OFFSET(table->private,
+ cpu_number_map(smp_processor_id()));
+ e = get_entry(table_base, table->private->hook_entry[hook]);
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ /* Check noone else using our table */
+ if (((struct ip6t_entry *)table_base)->comefrom != 0xdead57ac
+ && ((struct ip6t_entry *)table_base)->comefrom != 0xeeeeeeec) {
+ printk("ASSERT: CPU #%u, %s comefrom(%p) = %X\n",
+ smp_processor_id(),
+ table->name,
+ &((struct ip6t_entry *)table_base)->comefrom,
+ ((struct ip6t_entry *)table_base)->comefrom);
+ }
+ ((struct ip6t_entry *)table_base)->comefrom = 0x57acc001;
+#endif
+
+ /* For return from builtin chain */
+ back = get_entry(table_base, table->private->underflow[hook]);
+
+ do {
+ IP_NF_ASSERT(e);
+ IP_NF_ASSERT(back);
+ (*pskb)->nfcache |= e->nfcache;
+ if (ip6_packet_match(*pskb, indev, outdev, &e->ipv6,
+ &protoff, &offset)) {
+ struct ip6t_entry_target *t;
+
+ if (IP6T_MATCH_ITERATE(e, do_match,
+ *pskb, in, out,
+ offset,
+ (void *)((*pskb)->data
+ + protoff),
+ (*pskb)->len - protoff,
+ &hotdrop) != 0)
+ goto no_match;
+
+ ADD_COUNTER(e->counters, ntohs((*pskb)->nh.ipv6h->payload_len) + IPV6_HDR_LEN, 1);
+
+ t = ip6t_get_target(e);
+ IP_NF_ASSERT(t->u.kernel.target);
+ /* Standard target? */
+ if (!t->u.kernel.target->target) {
+ int v;
+
+ v = ((struct ip6t_standard_target *)t)->verdict;
+ if (v < 0) {
+ /* Pop from stack? */
+ if (v != IP6T_RETURN) {
+ verdict = (unsigned)(-v) - 1;
+ break;
+ }
+ e = back;
+ back = get_entry(table_base,
+ back->comefrom);
+ continue;
+ }
+ if (table_base + v
+ != (void *)e + e->next_offset) {
+ /* Save old back ptr in next entry */
+ struct ip6t_entry *next
+ = (void *)e + e->next_offset;
+ next->comefrom
+ = (void *)back - table_base;
+ /* set back pointer to next entry */
+ back = next;
+ }
+
+ e = get_entry(table_base, v);
+ } else {
+ /* Targets which reenter must return
+ abs. verdicts */
+#ifdef CONFIG_NETFILTER_DEBUG
+ ((struct ip6t_entry *)table_base)->comefrom
+ = 0xeeeeeeec;
+#endif
+ verdict = t->u.kernel.target->target(pskb,
+ hook,
+ in, out,
+ t->data,
+ userdata);
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ if (((struct ip6t_entry *)table_base)->comefrom
+ != 0xeeeeeeec
+ && verdict == IP6T_CONTINUE) {
+ printk("Target %s reentered!\n",
+ t->u.kernel.target->name);
+ verdict = NF_DROP;
+ }
+ ((struct ip6t_entry *)table_base)->comefrom
+ = 0x57acc001;
+#endif
+ if (verdict == IP6T_CONTINUE)
+ e = (void *)e + e->next_offset;
+ else
+ /* Verdict */
+ break;
+ }
+ } else {
+
+ no_match:
+ e = (void *)e + e->next_offset;
+ }
+ } while (!hotdrop);
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ ((struct ip6t_entry *)table_base)->comefrom = 0xdead57ac;
+#endif
+ read_unlock_bh(&table->lock);
+
+#ifdef DEBUG_ALLOW_ALL
+ return NF_ACCEPT;
+#else
+ if (hotdrop)
+ return NF_DROP;
+ else return verdict;
+#endif
+}
+
+/* If it succeeds, returns element and locks mutex */
+static inline void *
+find_inlist_lock_noload(struct list_head *head,
+ const char *name,
+ int *error,
+ struct semaphore *mutex)
+{
+ void *ret;
+
+#if 1
+ duprintf("find_inlist: searching for `%s' in %s.\n",
+ name, head == &ip6t_target ? "ip6t_target"
+ : head == &ip6t_match ? "ip6t_match"
+ : head == &ip6t_tables ? "ip6t_tables" : "UNKNOWN");
+#endif
+
+ *error = down_interruptible(mutex);
+ if (*error != 0)
+ return NULL;
+
+ ret = list_named_find(head, name);
+ if (!ret) {
+ *error = -ENOENT;
+ up(mutex);
+ }
+ return ret;
+}
+
+#ifndef CONFIG_KMOD
+#define find_inlist_lock(h,n,p,e,m) find_inlist_lock_noload((h),(n),(e),(m))
+#else
+static void *
+find_inlist_lock(struct list_head *head,
+ const char *name,
+ const char *prefix,
+ int *error,
+ struct semaphore *mutex)
+{
+ void *ret;
+
+ ret = find_inlist_lock_noload(head, name, error, mutex);
+ if (!ret) {
+ char modulename[IP6T_FUNCTION_MAXNAMELEN + strlen(prefix) + 1];
+ strcpy(modulename, prefix);
+ strcat(modulename, name);
+ duprintf("find_inlist: loading `%s'.\n", modulename);
+ request_module(modulename);
+ ret = find_inlist_lock_noload(head, name, error, mutex);
+ }
+
+ return ret;
+}
+#endif
+
+static inline struct ip6t_table *
+ip6t_find_table_lock(const char *name, int *error, struct semaphore *mutex)
+{
+ return find_inlist_lock(&ip6t_tables, name, "ip6table_", error, mutex);
+}
+
+static inline struct ip6t_match *
+find_match_lock(const char *name, int *error, struct semaphore *mutex)
+{
+ return find_inlist_lock(&ip6t_match, name, "ip6t_", error, mutex);
+}
+
+struct ip6t_target *
+ip6t_find_target_lock(const char *name, int *error, struct semaphore *mutex)
+{
+ return find_inlist_lock(&ip6t_target, name, "ip6t_", error, mutex);
+}
+
+/* All zeroes == unconditional rule. */
+static inline int
+unconditional(const struct ip6t_ip6 *ipv6)
+{
+ unsigned int i;
+
+ for (i = 0; i < sizeof(*ipv6); i++)
+ if (((char *)ipv6)[i])
+ break;
+
+ return (i == sizeof(*ipv6));
+}
+
+/* Figures out from what hook each rule can be called: returns 0 if
+ there are loops. Puts hook bitmask in comefrom. */
+static int
+mark_source_chains(struct ip6t_table_info *newinfo, unsigned int valid_hooks)
+{
+ unsigned int hook;
+
+ /* No recursion; use packet counter to save back ptrs (reset
+ to 0 as we leave), and comefrom to save source hook bitmask */
+ for (hook = 0; hook < NF_IP6_NUMHOOKS; hook++) {
+ unsigned int pos = newinfo->hook_entry[hook];
+ struct ip6t_entry *e
+ = (struct ip6t_entry *)(newinfo->entries + pos);
+
+ if (!(valid_hooks & (1 << hook)))
+ continue;
+
+ /* Set initial back pointer. */
+ e->counters.pcnt = pos;
+
+ for (;;) {
+ struct ip6t_standard_target *t
+ = (void *)ip6t_get_target(e);
+
+ if (e->comefrom & (1 << NF_IP6_NUMHOOKS)) {
+ printk("iptables: loop hook %u pos %u %08X.\n",
+ hook, pos, e->comefrom);
+ return 0;
+ }
+ e->comefrom
+ |= ((1 << hook) | (1 << NF_IP6_NUMHOOKS));
+
+ /* Unconditional return/END. */
+ if (e->target_offset == sizeof(struct ip6t_entry)
+ && (strcmp(t->target.u.user.name,
+ IP6T_STANDARD_TARGET) == 0)
+ && t->verdict < 0
+ && unconditional(&e->ipv6)) {
+ unsigned int oldpos, size;
+
+ /* Return: backtrack through the last
+ big jump. */
+ do {
+ e->comefrom ^= (1<<NF_IP6_NUMHOOKS);
+#ifdef DEBUG_IP_FIREWALL_USER
+ if (e->comefrom
+ & (1 << NF_IP6_NUMHOOKS)) {
+ duprintf("Back unset "
+ "on hook %u "
+ "rule %u\n",
+ hook, pos);
+ }
+#endif
+ oldpos = pos;
+ pos = e->counters.pcnt;
+ e->counters.pcnt = 0;
+
+ /* We're at the start. */
+ if (pos == oldpos)
+ goto next;
+
+ e = (struct ip6t_entry *)
+ (newinfo->entries + pos);
+ } while (oldpos == pos + e->next_offset);
+
+ /* Move along one */
+ size = e->next_offset;
+ e = (struct ip6t_entry *)
+ (newinfo->entries + pos + size);
+ e->counters.pcnt = pos;
+ pos += size;
+ } else {
+ int newpos = t->verdict;
+
+ if (strcmp(t->target.u.user.name,
+ IP6T_STANDARD_TARGET) == 0
+ && newpos >= 0) {
+ /* This a jump; chase it. */
+ duprintf("Jump rule %u -> %u\n",
+ pos, newpos);
+ } else {
+ /* ... this is a fallthru */
+ newpos = pos + e->next_offset;
+ }
+ e = (struct ip6t_entry *)
+ (newinfo->entries + newpos);
+ e->counters.pcnt = pos;
+ pos = newpos;
+ }
+ }
+ next:
+ duprintf("Finished chain %u\n", hook);
+ }
+ return 1;
+}
+
+static inline int
+cleanup_match(struct ip6t_entry_match *m, unsigned int *i)
+{
+ if (i && (*i)-- == 0)
+ return 1;
+
+ if (m->u.kernel.match->destroy)
+ m->u.kernel.match->destroy(m->data,
+ m->u.match_size - sizeof(*m));
+
+ if (m->u.kernel.match->me)
+ __MOD_DEC_USE_COUNT(m->u.kernel.match->me);
+
+ return 0;
+}
+
+static inline int
+standard_check(const struct ip6t_entry_target *t,
+ unsigned int max_offset)
+{
+ struct ip6t_standard_target *targ = (void *)t;
+
+ /* Check standard info. */
+ if (t->u.target_size
+ != IP6T_ALIGN(sizeof(struct ip6t_standard_target))) {
+ duprintf("standard_check: target size %u != %u\n",
+ t->u.target_size,
+ IP6T_ALIGN(sizeof(struct ip6t_standard_target)));
+ return 0;
+ }
+
+ if (targ->verdict >= 0
+ && targ->verdict > max_offset - sizeof(struct ip6t_entry)) {
+ duprintf("ip6t_standard_check: bad verdict (%i)\n",
+ targ->verdict);
+ return 0;
+ }
+
+ if (targ->verdict < -NF_MAX_VERDICT - 1) {
+ duprintf("ip6t_standard_check: bad negative verdict (%i)\n",
+ targ->verdict);
+ return 0;
+ }
+ return 1;
+}
+
+static inline int
+check_match(struct ip6t_entry_match *m,
+ const char *name,
+ const struct ip6t_ip6 *ipv6,
+ unsigned int hookmask,
+ unsigned int *i)
+{
+ int ret;
+ struct ip6t_match *match;
+
+ match = find_match_lock(m->u.user.name, &ret, &ip6t_mutex);
+ if (!match) {
+ // duprintf("check_match: `%s' not found\n", m->u.name);
+ return ret;
+ }
+ if (match->me)
+ __MOD_INC_USE_COUNT(match->me);
+ m->u.kernel.match = match;
+ up(&ip6t_mutex);
+
+ if (m->u.kernel.match->checkentry
+ && !m->u.kernel.match->checkentry(name, ipv6, m->data,
+ m->u.match_size - sizeof(*m),
+ hookmask)) {
+ if (m->u.kernel.match->me)
+ __MOD_DEC_USE_COUNT(m->u.kernel.match->me);
+ duprintf("ip_tables: check failed for `%s'.\n",
+ m->u.kernel.match->name);
+ return -EINVAL;
+ }
+
+ (*i)++;
+ return 0;
+}
+
+static struct ip6t_target ip6t_standard_target;
+
+static inline int
+check_entry(struct ip6t_entry *e, const char *name, unsigned int size,
+ unsigned int *i)
+{
+ struct ip6t_entry_target *t;
+ struct ip6t_target *target;
+ int ret;
+ unsigned int j;
+
+ if (!ip6_checkentry(&e->ipv6)) {
+ duprintf("ip_tables: ip check failed %p %s.\n", e, name);
+ return -EINVAL;
+ }
+
+ j = 0;
+ ret = IP6T_MATCH_ITERATE(e, check_match, name, &e->ipv6, e->comefrom, &j);
+ if (ret != 0)
+ goto cleanup_matches;
+
+ t = ip6t_get_target(e);
+ target = ip6t_find_target_lock(t->u.user.name, &ret, &ip6t_mutex);
+ if (!target) {
+ duprintf("check_entry: `%s' not found\n", t->u.user.name);
+ goto cleanup_matches;
+ }
+ if (target->me)
+ __MOD_INC_USE_COUNT(target->me);
+ t->u.kernel.target = target;
+ up(&ip6t_mutex);
+
+ if (t->u.kernel.target == &ip6t_standard_target) {
+ if (!standard_check(t, size)) {
+ ret = -EINVAL;
+ goto cleanup_matches;
+ }
+ } else if (t->u.kernel.target->checkentry
+ && !t->u.kernel.target->checkentry(name, e, t->data,
+ t->u.target_size
+ - sizeof(*t),
+ e->comefrom)) {
+ if (t->u.kernel.target->me)
+ __MOD_DEC_USE_COUNT(t->u.kernel.target->me);
+ duprintf("ip_tables: check failed for `%s'.\n",
+ t->u.kernel.target->name);
+ ret = -EINVAL;
+ goto cleanup_matches;
+ }
+
+ (*i)++;
+ return 0;
+
+ cleanup_matches:
+ IP6T_MATCH_ITERATE(e, cleanup_match, &j);
+ return ret;
+}
+
+static inline int
+check_entry_size_and_hooks(struct ip6t_entry *e,
+ struct ip6t_table_info *newinfo,
+ unsigned char *base,
+ unsigned char *limit,
+ const unsigned int *hook_entries,
+ const unsigned int *underflows,
+ unsigned int *i)
+{
+ unsigned int h;
+
+ if ((unsigned long)e % __alignof__(struct ip6t_entry) != 0
+ || (unsigned char *)e + sizeof(struct ip6t_entry) >= limit) {
+ duprintf("Bad offset %p\n", e);
+ return -EINVAL;
+ }
+
+ if (e->next_offset
+ < sizeof(struct ip6t_entry) + sizeof(struct ip6t_entry_target)) {
+ duprintf("checking: element %p size %u\n",
+ e, e->next_offset);
+ return -EINVAL;
+ }
+
+ /* Check hooks & underflows */
+ for (h = 0; h < NF_IP6_NUMHOOKS; h++) {
+ if ((unsigned char *)e - base == hook_entries[h])
+ newinfo->hook_entry[h] = hook_entries[h];
+ if ((unsigned char *)e - base == underflows[h])
+ newinfo->underflow[h] = underflows[h];
+ }
+
+ /* FIXME: underflows must be unconditional, standard verdicts
+ < 0 (not IP6T_RETURN). --RR */
+
+ /* Clear counters and comefrom */
+ e->counters = ((struct ip6t_counters) { 0, 0 });
+ e->comefrom = 0;
+
+ (*i)++;
+ return 0;
+}
+
+static inline int
+cleanup_entry(struct ip6t_entry *e, unsigned int *i)
+{
+ struct ip6t_entry_target *t;
+
+ if (i && (*i)-- == 0)
+ return 1;
+
+ /* Cleanup all matches */
+ IP6T_MATCH_ITERATE(e, cleanup_match, NULL);
+ t = ip6t_get_target(e);
+ if (t->u.kernel.target->destroy)
+ t->u.kernel.target->destroy(t->data,
+ t->u.target_size - sizeof(*t));
+ if (t->u.kernel.target->me)
+ __MOD_DEC_USE_COUNT(t->u.kernel.target->me);
+
+ return 0;
+}
+
+/* Checks and translates the user-supplied table segment (held in
+ newinfo) */
+static int
+translate_table(const char *name,
+ unsigned int valid_hooks,
+ struct ip6t_table_info *newinfo,
+ unsigned int size,
+ unsigned int number,
+ const unsigned int *hook_entries,
+ const unsigned int *underflows)
+{
+ unsigned int i;
+ int ret;
+
+ newinfo->size = size;
+ newinfo->number = number;
+
+ /* Init all hooks to impossible value. */
+ for (i = 0; i < NF_IP6_NUMHOOKS; i++) {
+ newinfo->hook_entry[i] = 0xFFFFFFFF;
+ newinfo->underflow[i] = 0xFFFFFFFF;
+ }
+
+ duprintf("translate_table: size %u\n", newinfo->size);
+ i = 0;
+ /* Walk through entries, checking offsets. */
+ ret = IP6T_ENTRY_ITERATE(newinfo->entries, newinfo->size,
+ check_entry_size_and_hooks,
+ newinfo,
+ newinfo->entries,
+ newinfo->entries + size,
+ hook_entries, underflows, &i);
+ if (ret != 0)
+ return ret;
+
+ if (i != number) {
+ duprintf("translate_table: %u not %u entries\n",
+ i, number);
+ return -EINVAL;
+ }
+
+ /* Check hooks all assigned */
+ for (i = 0; i < NF_IP6_NUMHOOKS; i++) {
+ /* Only hooks which are valid */
+ if (!(valid_hooks & (1 << i)))
+ continue;
+ if (newinfo->hook_entry[i] == 0xFFFFFFFF) {
+ duprintf("Invalid hook entry %u %u\n",
+ i, hook_entries[i]);
+ return -EINVAL;
+ }
+ if (newinfo->underflow[i] == 0xFFFFFFFF) {
+ duprintf("Invalid underflow %u %u\n",
+ i, underflows[i]);
+ return -EINVAL;
+ }
+ }
+
+ if (!mark_source_chains(newinfo, valid_hooks))
+ return -ELOOP;
+
+ /* Finally, each sanity check must pass */
+ i = 0;
+ ret = IP6T_ENTRY_ITERATE(newinfo->entries, newinfo->size,
+ check_entry, name, size, &i);
+
+ if (ret != 0) {
+ IP6T_ENTRY_ITERATE(newinfo->entries, newinfo->size,
+ cleanup_entry, &i);
+ return ret;
+ }
+
+ /* And one copy for every other CPU */
+ for (i = 1; i < smp_num_cpus; i++) {
+ memcpy(newinfo->entries + SMP_ALIGN(newinfo->size)*i,
+ newinfo->entries,
+ SMP_ALIGN(newinfo->size));
+ }
+
+ return ret;
+}
+
+static struct ip6t_table_info *
+replace_table(struct ip6t_table *table,
+ unsigned int num_counters,
+ struct ip6t_table_info *newinfo,
+ int *error)
+{
+ struct ip6t_table_info *oldinfo;
+
+#ifdef CONFIG_NETFILTER_DEBUG
+ {
+ struct ip6t_entry *table_base;
+ unsigned int i;
+
+ for (i = 0; i < smp_num_cpus; i++) {
+ table_base =
+ (void *)newinfo->entries
+ + TABLE_OFFSET(newinfo, i);
+
+ table_base->comefrom = 0xdead57ac;
+ }
+ }
+#endif
+
+ /* Do the substitution. */
+ write_lock_bh(&table->lock);
+ /* Check inside lock: is the old number correct? */
+ if (num_counters != table->private->number) {
+ duprintf("num_counters != table->private->number (%u/%u)\n",
+ num_counters, table->private->number);
+ write_unlock_bh(&table->lock);
+ *error = -EAGAIN;
+ return NULL;
+ }
+ oldinfo = table->private;
+ table->private = newinfo;
+ newinfo->initial_entries = oldinfo->initial_entries;
+ write_unlock_bh(&table->lock);
+
+ return oldinfo;
+}
+
+/* Gets counters. */
+static inline int
+add_entry_to_counter(const struct ip6t_entry *e,
+ struct ip6t_counters total[],
+ unsigned int *i)
+{
+ ADD_COUNTER(total[*i], e->counters.bcnt, e->counters.pcnt);
+
+ (*i)++;
+ return 0;
+}
+
+static void
+get_counters(const struct ip6t_table_info *t,
+ struct ip6t_counters counters[])
+{
+ unsigned int cpu;
+ unsigned int i;
+
+ for (cpu = 0; cpu < smp_num_cpus; cpu++) {
+ i = 0;
+ IP6T_ENTRY_ITERATE(t->entries + TABLE_OFFSET(t, cpu),
+ t->size,
+ add_entry_to_counter,
+ counters,
+ &i);
+ }
+}
+
+static int
+copy_entries_to_user(unsigned int total_size,
+ struct ip6t_table *table,
+ void *userptr)
+{
+ unsigned int off, num, countersize;
+ struct ip6t_entry *e;
+ struct ip6t_counters *counters;
+ int ret = 0;
+
+ /* We need atomic snapshot of counters: rest doesn't change
+ (other than comefrom, which userspace doesn't care
+ about). */
+ countersize = sizeof(struct ip6t_counters) * table->private->number;
+ counters = vmalloc(countersize);
+
+ if (counters == NULL)
+ return -ENOMEM;
+
+ /* First, sum counters... */
+ memset(counters, 0, countersize);
+ write_lock_bh(&table->lock);
+ get_counters(table->private, counters);
+ write_unlock_bh(&table->lock);
+
+ /* ... then copy entire thing from CPU 0... */
+ if (copy_to_user(userptr, table->private->entries, total_size) != 0) {
+ ret = -EFAULT;
+ goto free_counters;
+ }
+
+ /* FIXME: use iterator macros --RR */
+ /* ... then go back and fix counters and names */
+ for (off = 0, num = 0; off < total_size; off += e->next_offset, num++){
+ unsigned int i;
+ struct ip6t_entry_match *m;
+ struct ip6t_entry_target *t;
+
+ e = (struct ip6t_entry *)(table->private->entries + off);
+ if (copy_to_user(userptr + off
+ + offsetof(struct ip6t_entry, counters),
+ &counters[num],
+ sizeof(counters[num])) != 0) {
+ ret = -EFAULT;
+ goto free_counters;
+ }
+
+ for (i = sizeof(struct ip6t_entry);
+ i < e->target_offset;
+ i += m->u.match_size) {
+ m = (void *)e + i;
+
+ if (copy_to_user(userptr + off + i
+ + offsetof(struct ip6t_entry_match,
+ u.user.name),
+ m->u.kernel.match->name,
+ strlen(m->u.kernel.match->name)+1)
+ != 0) {
+ ret = -EFAULT;
+ goto free_counters;
+ }
+ }
+
+ t = ip6t_get_target(e);
+ if (copy_to_user(userptr + off + e->target_offset
+ + offsetof(struct ip6t_entry_target,
+ u.user.name),
+ t->u.kernel.target->name,
+ strlen(t->u.kernel.target->name)+1) != 0) {
+ ret = -EFAULT;
+ goto free_counters;
+ }
+ }
+
+ free_counters:
+ vfree(counters);
+ return ret;
+}
+
+static int
+get_entries(const struct ip6t_get_entries *entries,
+ struct ip6t_get_entries *uptr)
+{
+ int ret;
+ struct ip6t_table *t;
+
+ t = ip6t_find_table_lock(entries->name, &ret, &ip6t_mutex);
+ if (t) {
+ duprintf("t->private->number = %u\n",
+ t->private->number);
+ if (entries->size == t->private->size)
+ ret = copy_entries_to_user(t->private->size,
+ t, uptr->entrytable);
+ else {
+ duprintf("get_entries: I've got %u not %u!\n",
+ t->private->size,
+ entries->size);
+ ret = -EINVAL;
+ }
+ up(&ip6t_mutex);
+ } else
+ duprintf("get_entries: Can't find %s!\n",
+ entries->name);
+
+ return ret;
+}
+
+static int
+do_replace(void *user, unsigned int len)
+{
+ int ret;
+ struct ip6t_replace tmp;
+ struct ip6t_table *t;
+ struct ip6t_table_info *newinfo, *oldinfo;
+ struct ip6t_counters *counters;
+
+ if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
+ return -EFAULT;
+
+ /* Pedantry: prevent them from hitting BUG() in vmalloc.c --RR */
+ if ((SMP_ALIGN(tmp.size) >> PAGE_SHIFT) + 2 > num_physpages)
+ return -ENOMEM;
+
+ newinfo = vmalloc(sizeof(struct ip6t_table_info)
+ + SMP_ALIGN(tmp.size) * smp_num_cpus);
+ if (!newinfo)
+ return -ENOMEM;
+
+ if (copy_from_user(newinfo->entries, user + sizeof(tmp),
+ tmp.size) != 0) {
+ ret = -EFAULT;
+ goto free_newinfo;
+ }
+
+ counters = vmalloc(tmp.num_counters * sizeof(struct ip6t_counters));
+ if (!counters) {
+ ret = -ENOMEM;
+ goto free_newinfo;
+ }
+ memset(counters, 0, tmp.num_counters * sizeof(struct ip6t_counters));
+
+ ret = translate_table(tmp.name, tmp.valid_hooks,
+ newinfo, tmp.size, tmp.num_entries,
+ tmp.hook_entry, tmp.underflow);
+ if (ret != 0)
+ goto free_newinfo_counters;
+
+ duprintf("ip_tables: Translated table\n");
+
+ t = ip6t_find_table_lock(tmp.name, &ret, &ip6t_mutex);
+ if (!t)
+ goto free_newinfo_counters_untrans;
+
+ /* You lied! */
+ if (tmp.valid_hooks != t->valid_hooks) {
+ duprintf("Valid hook crap: %08X vs %08X\n",
+ tmp.valid_hooks, t->valid_hooks);
+ ret = -EINVAL;
+ goto free_newinfo_counters_untrans_unlock;
+ }
+
+ oldinfo = replace_table(t, tmp.num_counters, newinfo, &ret);
+ if (!oldinfo)
+ goto free_newinfo_counters_untrans_unlock;
+
+ /* Update module usage count based on number of rules */
+ duprintf("do_replace: oldnum=%u, initnum=%u, newnum=%u\n",
+ oldinfo->number, oldinfo->initial_entries, newinfo->number);
+ if (t->me && (oldinfo->number <= oldinfo->initial_entries) &&
+ (newinfo->number > oldinfo->initial_entries))
+ __MOD_INC_USE_COUNT(t->me);
+ else if (t->me && (oldinfo->number > oldinfo->initial_entries) &&
+ (newinfo->number <= oldinfo->initial_entries))
+ __MOD_DEC_USE_COUNT(t->me);
+
+ /* Get the old counters. */
+ get_counters(oldinfo, counters);
+ /* Decrease module usage counts and free resource */
+ IP6T_ENTRY_ITERATE(oldinfo->entries, oldinfo->size, cleanup_entry,NULL);
+ vfree(oldinfo);
+ /* Silent error: too late now. */
+ copy_to_user(tmp.counters, counters,
+ sizeof(struct ip6t_counters) * tmp.num_counters);
+ vfree(counters);
+ up(&ip6t_mutex);
+ return 0;
+
+ free_newinfo_counters_untrans_unlock:
+ up(&ip6t_mutex);
+ free_newinfo_counters_untrans:
+ IP6T_ENTRY_ITERATE(newinfo->entries, newinfo->size, cleanup_entry,NULL);
+ free_newinfo_counters:
+ vfree(counters);
+ free_newinfo:
+ vfree(newinfo);
+ return ret;
+}
+
+/* We're lazy, and add to the first CPU; overflow works its fey magic
+ * and everything is OK. */
+static inline int
+add_counter_to_entry(struct ip6t_entry *e,
+ const struct ip6t_counters addme[],
+ unsigned int *i)
+{
+#if 0
+ duprintf("add_counter: Entry %u %lu/%lu + %lu/%lu\n",
+ *i,
+ (long unsigned int)e->counters.pcnt,
+ (long unsigned int)e->counters.bcnt,
+ (long unsigned int)addme[*i].pcnt,
+ (long unsigned int)addme[*i].bcnt);
+#endif
+
+ ADD_COUNTER(e->counters, addme[*i].bcnt, addme[*i].pcnt);
+
+ (*i)++;
+ return 0;
+}
+
+static int
+do_add_counters(void *user, unsigned int len)
+{
+ unsigned int i;
+ struct ip6t_counters_info tmp, *paddc;
+ struct ip6t_table *t;
+ int ret;
+
+ if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)
+ return -EFAULT;
+
+ if (len != sizeof(tmp) + tmp.num_counters*sizeof(struct ip6t_counters))
+ return -EINVAL;
+
+ paddc = vmalloc(len);
+ if (!paddc)
+ return -ENOMEM;
+
+ if (copy_from_user(paddc, user, len) != 0) {
+ ret = -EFAULT;
+ goto free;
+ }
+
+ t = ip6t_find_table_lock(tmp.name, &ret, &ip6t_mutex);
+ if (!t)
+ goto free;
+
+ write_lock_bh(&t->lock);
+ if (t->private->number != paddc->num_counters) {
+ ret = -EINVAL;
+ goto unlock_up_free;
+ }
+
+ i = 0;
+ IP6T_ENTRY_ITERATE(t->private->entries,
+ t->private->size,
+ add_counter_to_entry,
+ paddc->counters,
+ &i);
+ unlock_up_free:
+ write_unlock_bh(&t->lock);
+ up(&ip6t_mutex);
+ free:
+ vfree(paddc);
+
+ return ret;
+}
+
+static int
+do_ip6t_set_ctl(struct sock *sk, int cmd, void *user, unsigned int len)
+{
+ int ret;
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ switch (cmd) {
+ case IP6T_SO_SET_REPLACE:
+ ret = do_replace(user, len);
+ break;
+
+ case IP6T_SO_SET_ADD_COUNTERS:
+ ret = do_add_counters(user, len);
+ break;
+
+ default:
+ duprintf("do_ip6t_set_ctl: unknown request %i\n", cmd);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int
+do_ip6t_get_ctl(struct sock *sk, int cmd, void *user, int *len)
+{
+ int ret;
+
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ switch (cmd) {
+ case IP6T_SO_GET_INFO: {
+ char name[IP6T_TABLE_MAXNAMELEN];
+ struct ip6t_table *t;
+
+ if (*len != sizeof(struct ip6t_getinfo)) {
+ duprintf("length %u != %u\n", *len,
+ sizeof(struct ip6t_getinfo));
+ ret = -EINVAL;
+ break;
+ }
+
+ if (copy_from_user(name, user, sizeof(name)) != 0) {
+ ret = -EFAULT;
+ break;
+ }
+ name[IP6T_TABLE_MAXNAMELEN-1] = '\0';
+ t = ip6t_find_table_lock(name, &ret, &ip6t_mutex);
+ if (t) {
+ struct ip6t_getinfo info;
+
+ info.valid_hooks = t->valid_hooks;
+ memcpy(info.hook_entry, t->private->hook_entry,
+ sizeof(info.hook_entry));
+ memcpy(info.underflow, t->private->underflow,
+ sizeof(info.underflow));
+ info.num_entries = t->private->number;
+ info.size = t->private->size;
+ memcpy(info.name, name, sizeof(info.name));
+
+ if (copy_to_user(user, &info, *len) != 0)
+ ret = -EFAULT;
+ else
+ ret = 0;
+
+ up(&ip6t_mutex);
+ }
+ }
+ break;
+
+ case IP6T_SO_GET_ENTRIES: {
+ struct ip6t_get_entries get;
+
+ if (*len < sizeof(get)) {
+ duprintf("get_entries: %u < %u\n", *len, sizeof(get));
+ ret = -EINVAL;
+ } else if (copy_from_user(&get, user, sizeof(get)) != 0) {
+ ret = -EFAULT;
+ } else if (*len != sizeof(struct ip6t_get_entries) + get.size) {
+ duprintf("get_entries: %u != %u\n", *len,
+ sizeof(struct ip6t_get_entries) + get.size);
+ ret = -EINVAL;
+ } else
+ ret = get_entries(&get, user);
+ break;
+ }
+
+ default:
+ duprintf("do_ip6t_get_ctl: unknown request %i\n", cmd);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/* Registration hooks for targets. */
+int
+ip6t_register_target(struct ip6t_target *target)
+{
+ int ret;
+
+ MOD_INC_USE_COUNT;
+ ret = down_interruptible(&ip6t_mutex);
+ if (ret != 0) {
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+ if (!list_named_insert(&ip6t_target, target)) {
+ duprintf("ip6t_register_target: `%s' already in list!\n",
+ target->name);
+ ret = -EINVAL;
+ MOD_DEC_USE_COUNT;
+ }
+ up(&ip6t_mutex);
+ return ret;
+}
+
+void
+ip6t_unregister_target(struct ip6t_target *target)
+{
+ down(&ip6t_mutex);
+ LIST_DELETE(&ip6t_target, target);
+ up(&ip6t_mutex);
+ MOD_DEC_USE_COUNT;
+}
+
+int
+ip6t_register_match(struct ip6t_match *match)
+{
+ int ret;
+
+ MOD_INC_USE_COUNT;
+ ret = down_interruptible(&ip6t_mutex);
+ if (ret != 0) {
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+ if (!list_named_insert(&ip6t_match, match)) {
+ duprintf("ip6t_register_match: `%s' already in list!\n",
+ match->name);
+ MOD_DEC_USE_COUNT;
+ ret = -EINVAL;
+ }
+ up(&ip6t_mutex);
+
+ return ret;
+}
+
+void
+ip6t_unregister_match(struct ip6t_match *match)
+{
+ down(&ip6t_mutex);
+ LIST_DELETE(&ip6t_match, match);
+ up(&ip6t_mutex);
+ MOD_DEC_USE_COUNT;
+}
+
+int ip6t_register_table(struct ip6t_table *table)
+{
+ int ret;
+ struct ip6t_table_info *newinfo;
+ static struct ip6t_table_info bootstrap
+ = { 0, 0, 0, { 0 }, { 0 }, { } };
+
+ MOD_INC_USE_COUNT;
+ newinfo = vmalloc(sizeof(struct ip6t_table_info)
+ + SMP_ALIGN(table->table->size) * smp_num_cpus);
+ if (!newinfo) {
+ ret = -ENOMEM;
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+ memcpy(newinfo->entries, table->table->entries, table->table->size);
+
+ ret = translate_table(table->name, table->valid_hooks,
+ newinfo, table->table->size,
+ table->table->num_entries,
+ table->table->hook_entry,
+ table->table->underflow);
+ if (ret != 0) {
+ vfree(newinfo);
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+
+ ret = down_interruptible(&ip6t_mutex);
+ if (ret != 0) {
+ vfree(newinfo);
+ MOD_DEC_USE_COUNT;
+ return ret;
+ }
+
+ /* Don't autoload: we'd eat our tail... */
+ if (list_named_find(&ip6t_tables, table->name)) {
+ ret = -EEXIST;
+ goto free_unlock;
+ }
+
+ /* Simplifies replace_table code. */
+ table->private = &bootstrap;
+ if (!replace_table(table, 0, newinfo, &ret))
+ goto free_unlock;
+
+ duprintf("table->private->number = %u\n",
+ table->private->number);
+
+ /* save number of initial entries */
+ table->private->initial_entries = table->private->number;
+
+ table->lock = RW_LOCK_UNLOCKED;
+ list_prepend(&ip6t_tables, table);
+
+ unlock:
+ up(&ip6t_mutex);
+ return ret;
+
+ free_unlock:
+ vfree(newinfo);
+ MOD_DEC_USE_COUNT;
+ goto unlock;
+}
+
+void ip6t_unregister_table(struct ip6t_table *table)
+{
+ down(&ip6t_mutex);
+ LIST_DELETE(&ip6t_tables, table);
+ up(&ip6t_mutex);
+
+ /* Decrease module usage counts and free resources */
+ IP6T_ENTRY_ITERATE(table->private->entries, table->private->size,
+ cleanup_entry, NULL);
+ vfree(table->private);
+ MOD_DEC_USE_COUNT;
+}
+
+/* Returns 1 if the port is matched by the range, 0 otherwise */
+static inline int
+port_match(u_int16_t min, u_int16_t max, u_int16_t port, int invert)
+{
+ int ret;
+
+ ret = (port >= min && port <= max) ^ invert;
+ return ret;
+}
+
+static int
+tcp_find_option(u_int8_t option,
+ const struct tcphdr *tcp,
+ u_int16_t datalen,
+ int invert,
+ int *hotdrop)
+{
+ unsigned int i = sizeof(struct tcphdr);
+ const u_int8_t *opt = (u_int8_t *)tcp;
+
+ duprintf("tcp_match: finding option\n");
+ /* If we don't have the whole header, drop packet. */
+ if (tcp->doff * 4 < sizeof(struct tcphdr) ||
+ tcp->doff * 4 > datalen) {
+ *hotdrop = 1;
+ return 0;
+ }
+
+ while (i < tcp->doff * 4) {
+ if (opt[i] == option) return !invert;
+ if (opt[i] < 2) i++;
+ else i += opt[i+1]?:1;
+ }
+
+ return invert;
+}
+
+static int
+tcp_match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct tcphdr *tcp = hdr;
+ const struct ip6t_tcp *tcpinfo = matchinfo;
+
+ /* To quote Alan:
+
+ Don't allow a fragment of TCP 8 bytes in. Nobody normal
+ causes this. Its a cracker trying to break in by doing a
+ flag overwrite to pass the direction checks.
+ */
+
+ if (offset == 1) {
+ duprintf("Dropping evil TCP offset=1 frag.\n");
+ *hotdrop = 1;
+ return 0;
+ } else if (offset == 0 && datalen < sizeof(struct tcphdr)) {
+ /* We've been asked to examine this packet, and we
+ can't. Hence, no choice but to drop. */
+ duprintf("Dropping evil TCP offset=0 tinygram.\n");
+ *hotdrop = 1;
+ return 0;
+ }
+
+ /* FIXME: Try tcp doff >> packet len against various stacks --RR */
+
+#define FWINVTCP(bool,invflg) ((bool) ^ !!(tcpinfo->invflags & invflg))
+
+ /* Must not be a fragment. */
+ return !offset
+ && port_match(tcpinfo->spts[0], tcpinfo->spts[1],
+ ntohs(tcp->source),
+ !!(tcpinfo->invflags & IP6T_TCP_INV_SRCPT))
+ && port_match(tcpinfo->dpts[0], tcpinfo->dpts[1],
+ ntohs(tcp->dest),
+ !!(tcpinfo->invflags & IP6T_TCP_INV_DSTPT))
+ && FWINVTCP((((unsigned char *)tcp)[13]
+ & tcpinfo->flg_mask)
+ == tcpinfo->flg_cmp,
+ IP6T_TCP_INV_FLAGS)
+ && (!tcpinfo->option
+ || tcp_find_option(tcpinfo->option, tcp, datalen,
+ tcpinfo->invflags
+ & IP6T_TCP_INV_OPTION,
+ hotdrop));
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+tcp_checkentry(const char *tablename,
+ const struct ip6t_ip6 *ipv6,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ const struct ip6t_tcp *tcpinfo = matchinfo;
+
+ /* Must specify proto == TCP, and no unknown invflags */
+ return ipv6->proto == IPPROTO_TCP
+ && !(ipv6->invflags & IP6T_INV_PROTO)
+ && matchsize == IP6T_ALIGN(sizeof(struct ip6t_tcp))
+ && !(tcpinfo->invflags & ~IP6T_TCP_INV_MASK);
+}
+
+static int
+udp_match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct udphdr *udp = hdr;
+ const struct ip6t_udp *udpinfo = matchinfo;
+
+ if (offset == 0 && datalen < sizeof(struct udphdr)) {
+ /* We've been asked to examine this packet, and we
+ can't. Hence, no choice but to drop. */
+ duprintf("Dropping evil UDP tinygram.\n");
+ *hotdrop = 1;
+ return 0;
+ }
+
+ /* Must not be a fragment. */
+ return !offset
+ && port_match(udpinfo->spts[0], udpinfo->spts[1],
+ ntohs(udp->source),
+ !!(udpinfo->invflags & IP6T_UDP_INV_SRCPT))
+ && port_match(udpinfo->dpts[0], udpinfo->dpts[1],
+ ntohs(udp->dest),
+ !!(udpinfo->invflags & IP6T_UDP_INV_DSTPT));
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+udp_checkentry(const char *tablename,
+ const struct ip6t_ip6 *ipv6,
+ void *matchinfo,
+ unsigned int matchinfosize,
+ unsigned int hook_mask)
+{
+ const struct ip6t_udp *udpinfo = matchinfo;
+
+ /* Must specify proto == UDP, and no unknown invflags */
+ if (ipv6->proto != IPPROTO_UDP || (ipv6->invflags & IP6T_INV_PROTO)) {
+ duprintf("ip6t_udp: Protocol %u != %u\n", ipv6->proto,
+ IPPROTO_UDP);
+ return 0;
+ }
+ if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_udp))) {
+ duprintf("ip6t_udp: matchsize %u != %u\n",
+ matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_udp)));
+ return 0;
+ }
+ if (udpinfo->invflags & ~IP6T_UDP_INV_MASK) {
+ duprintf("ip6t_udp: unknown flags %X\n",
+ udpinfo->invflags);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Returns 1 if the type and code is matched by the range, 0 otherwise */
+static inline int
+icmp6_type_code_match(u_int8_t test_type, u_int8_t min_code, u_int8_t max_code,
+ u_int8_t type, u_int8_t code,
+ int invert)
+{
+ return (type == test_type && code >= min_code && code <= max_code)
+ ^ invert;
+}
+
+static int
+icmp6_match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct icmp6hdr *icmp = hdr;
+ const struct ip6t_icmp *icmpinfo = matchinfo;
+
+ if (offset == 0 && datalen < 2) {
+ /* We've been asked to examine this packet, and we
+ can't. Hence, no choice but to drop. */
+ duprintf("Dropping evil ICMP tinygram.\n");
+ *hotdrop = 1;
+ return 0;
+ }
+
+ /* Must not be a fragment. */
+ return !offset
+ && icmp6_type_code_match(icmpinfo->type,
+ icmpinfo->code[0],
+ icmpinfo->code[1],
+ icmp->icmp6_type, icmp->icmp6_code,
+ !!(icmpinfo->invflags&IP6T_ICMP_INV));
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+icmp6_checkentry(const char *tablename,
+ const struct ip6t_ip6 *ipv6,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ const struct ip6t_icmp *icmpinfo = matchinfo;
+
+ /* Must specify proto == ICMP, and no unknown invflags */
+ return ipv6->proto == IPPROTO_ICMPV6
+ && !(ipv6->invflags & IP6T_INV_PROTO)
+ && matchsize == IP6T_ALIGN(sizeof(struct ip6t_icmp))
+ && !(icmpinfo->invflags & ~IP6T_ICMP_INV);
+}
+
+/* The built-in targets: standard (NULL) and error. */
+static struct ip6t_target ip6t_standard_target
+= { { NULL, NULL }, IP6T_STANDARD_TARGET, NULL, NULL, NULL };
+static struct ip6t_target ip6t_error_target
+= { { NULL, NULL }, IP6T_ERROR_TARGET, ip6t_error, NULL, NULL };
+
+static struct nf_sockopt_ops ip6t_sockopts
+= { { NULL, NULL }, PF_INET6, IP6T_BASE_CTL, IP6T_SO_SET_MAX+1, do_ip6t_set_ctl,
+ IP6T_BASE_CTL, IP6T_SO_GET_MAX+1, do_ip6t_get_ctl, 0, NULL };
+
+static struct ip6t_match tcp_matchstruct
+= { { NULL, NULL }, "tcp", &tcp_match, &tcp_checkentry, NULL };
+static struct ip6t_match udp_matchstruct
+= { { NULL, NULL }, "udp", &udp_match, &udp_checkentry, NULL };
+static struct ip6t_match icmp6_matchstruct
+= { { NULL, NULL }, "icmp6", &icmp6_match, &icmp6_checkentry, NULL };
+
+#ifdef CONFIG_PROC_FS
+static inline int print_name(const char *i,
+ off_t start_offset, char *buffer, int length,
+ off_t *pos, unsigned int *count)
+{
+ if ((*count)++ >= start_offset) {
+ unsigned int namelen;
+
+ namelen = sprintf(buffer + *pos, "%s\n",
+ i + sizeof(struct list_head));
+ if (*pos + namelen > length) {
+ /* Stop iterating */
+ return 1;
+ }
+ *pos += namelen;
+ }
+ return 0;
+}
+
+static inline int print_target(const struct ip6t_target *t,
+ off_t start_offset, char *buffer, int length,
+ off_t *pos, unsigned int *count)
+{
+ if (t == &ip6t_standard_target || t == &ip6t_error_target)
+ return 0;
+ return print_name((char *)t, start_offset, buffer, length, pos, count);
+}
+
+static int ip6t_get_tables(char *buffer, char **start, off_t offset, int length)
+{
+ off_t pos = 0;
+ unsigned int count = 0;
+
+ if (down_interruptible(&ip6t_mutex) != 0)
+ return 0;
+
+ LIST_FIND(&ip6t_tables, print_name, char *,
+ offset, buffer, length, &pos, &count);
+
+ up(&ip6t_mutex);
+
+ /* `start' hack - see fs/proc/generic.c line ~105 */
+ *start=(char *)((unsigned long)count-offset);
+ return pos;
+}
+
+static int ip6t_get_targets(char *buffer, char **start, off_t offset, int length)
+{
+ off_t pos = 0;
+ unsigned int count = 0;
+
+ if (down_interruptible(&ip6t_mutex) != 0)
+ return 0;
+
+ LIST_FIND(&ip6t_target, print_target, struct ip6t_target *,
+ offset, buffer, length, &pos, &count);
+
+ up(&ip6t_mutex);
+
+ *start = (char *)((unsigned long)count - offset);
+ return pos;
+}
+
+static int ip6t_get_matches(char *buffer, char **start, off_t offset, int length)
+{
+ off_t pos = 0;
+ unsigned int count = 0;
+
+ if (down_interruptible(&ip6t_mutex) != 0)
+ return 0;
+
+ LIST_FIND(&ip6t_match, print_name, char *,
+ offset, buffer, length, &pos, &count);
+
+ up(&ip6t_mutex);
+
+ *start = (char *)((unsigned long)count - offset);
+ return pos;
+}
+
+static struct { char *name; get_info_t *get_info; } ip6t_proc_entry[] =
+{ { "ip6_tables_names", ip6t_get_tables },
+ { "ip6_tables_targets", ip6t_get_targets },
+ { "ip6_tables_matches", ip6t_get_matches },
+ { NULL, NULL} };
+#endif /*CONFIG_PROC_FS*/
+
+static int __init init(void)
+{
+ int ret;
+
+ /* Noone else will be downing sem now, so we won't sleep */
+ down(&ip6t_mutex);
+ list_append(&ip6t_target, &ip6t_standard_target);
+ list_append(&ip6t_target, &ip6t_error_target);
+ list_append(&ip6t_match, &tcp_matchstruct);
+ list_append(&ip6t_match, &udp_matchstruct);
+ list_append(&ip6t_match, &icmp6_matchstruct);
+ up(&ip6t_mutex);
+
+ /* Register setsockopt */
+ ret = nf_register_sockopt(&ip6t_sockopts);
+ if (ret < 0) {
+ duprintf("Unable to register sockopts.\n");
+ return ret;
+ }
+
+#ifdef CONFIG_PROC_FS
+ {
+ struct proc_dir_entry *proc;
+ int i;
+
+ for (i = 0; ip6t_proc_entry[i].name; i++) {
+ proc = proc_net_create(ip6t_proc_entry[i].name, 0,
+ ip6t_proc_entry[i].get_info);
+ if (!proc) {
+ while (--i >= 0)
+ proc_net_remove(ip6t_proc_entry[i].name);
+ nf_unregister_sockopt(&ip6t_sockopts);
+ return -ENOMEM;
+ }
+ proc->owner = THIS_MODULE;
+ }
+ }
+#endif
+
+ printk("ip6_tables: (C) 2000-2002 Netfilter core team\n");
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ nf_unregister_sockopt(&ip6t_sockopts);
+#ifdef CONFIG_PROC_FS
+ {
+ int i;
+ for (i = 0; ip6t_proc_entry[i].name; i++)
+ proc_net_remove(ip6t_proc_entry[i].name);
+ }
+#endif
+}
+
+EXPORT_SYMBOL(ip6t_register_table);
+EXPORT_SYMBOL(ip6t_unregister_table);
+EXPORT_SYMBOL(ip6t_do_table);
+EXPORT_SYMBOL(ip6t_find_target_lock);
+EXPORT_SYMBOL(ip6t_register_match);
+EXPORT_SYMBOL(ip6t_unregister_match);
+EXPORT_SYMBOL(ip6t_register_target);
+EXPORT_SYMBOL(ip6t_unregister_target);
+EXPORT_SYMBOL(ip6t_ext_hdr);
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_IMQ.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_IMQ.c
new file mode 100644
index 0000000..760d744
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_IMQ.c
@@ -0,0 +1,78 @@
+/* This target marks packets to be enqueued to an imq device */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_IMQ.h>
+#include <linux/imq.h>
+
+static unsigned int imq_target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ struct ip6t_imq_info *mr = (struct ip6t_imq_info*)targinfo;
+
+ (*pskb)->imq_flags = mr->todev | IMQ_F_ENQUEUE;
+ (*pskb)->nfcache |= NFC_ALTERED;
+
+ return IP6T_CONTINUE;
+}
+
+static int imq_checkentry(const char *tablename,
+ const struct ip6t_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ struct ip6t_imq_info *mr;
+
+ if (targinfosize != IP6T_ALIGN(sizeof(struct ip6t_imq_info))) {
+ printk(KERN_WARNING "IMQ: invalid targinfosize\n");
+ return 0;
+ }
+ mr = (struct ip6t_imq_info*)targinfo;
+
+ if (strcmp(tablename, "mangle") != 0) {
+ printk(KERN_WARNING
+ "IMQ: IMQ can only be called from \"mangle\" table, not \"%s\"\n",
+ tablename);
+ return 0;
+ }
+
+ if (mr->todev > IMQ_MAX_DEVS) {
+ printk(KERN_WARNING
+ "IMQ: invalid device specified, highest is %u\n",
+ IMQ_MAX_DEVS);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ip6t_target ip6t_imq_reg = {
+ { NULL, NULL},
+ "IMQ",
+ imq_target,
+ imq_checkentry,
+ NULL,
+ THIS_MODULE
+};
+
+static int __init init(void)
+{
+ if (ip6t_register_target(&ip6t_imq_reg))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ip6t_unregister_target(&ip6t_imq_reg);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_LOG.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_LOG.c
new file mode 100644
index 0000000..86150b3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_LOG.c
@@ -0,0 +1,472 @@
+/*
+ * This is a module which is used for logging packets.
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <linux/spinlock.h>
+#include <linux/icmpv6.h>
+#include <net/udp.h>
+#include <net/tcp.h>
+#include <net/ipv6.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+MODULE_AUTHOR("Jan Rekorajski <baggins@pld.org.pl>");
+MODULE_DESCRIPTION("IP6 tables LOG target module");
+MODULE_LICENSE("GPL");
+
+struct in_device;
+#include <net/route.h>
+#include <linux/netfilter_ipv6/ip6t_LOG.h>
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+#define NIP6(addr) \
+ ntohs((addr).s6_addr16[0]), \
+ ntohs((addr).s6_addr16[1]), \
+ ntohs((addr).s6_addr16[2]), \
+ ntohs((addr).s6_addr16[3]), \
+ ntohs((addr).s6_addr16[4]), \
+ ntohs((addr).s6_addr16[5]), \
+ ntohs((addr).s6_addr16[6]), \
+ ntohs((addr).s6_addr16[7])
+
+/* FIXME evil kludge */
+
+struct ahhdr {
+ __u8 nexthdr;
+ __u8 hdrlen;
+ __u16 reserved;
+ __u32 spi;
+ __u32 seq_no;
+};
+
+struct esphdr {
+ __u32 spi;
+ __u32 seq_no;
+};
+
+/* Use lock to serialize, so printks don't overlap */
+static spinlock_t log_lock = SPIN_LOCK_UNLOCKED;
+
+/* One level of recursion won't kill us */
+static void dump_packet(const struct ip6t_log_info *info,
+ const struct sk_buff *skb, unsigned int ip6hoff,
+ int recurse)
+{
+ u_int8_t currenthdr;
+ int fragment;
+ struct ipv6hdr *ipv6h;
+ unsigned int ptr;
+ unsigned int hdrlen = 0;
+
+ if (skb->len - ip6hoff < sizeof(*ipv6h)) {
+ printk("TRUNCATED");
+ return;
+ }
+ ipv6h = (struct ipv6hdr *)(skb->data + ip6hoff);
+
+ /* Max length: 88 "SRC=0000.0000.0000.0000.0000.0000.0000.0000 DST=0000.0000.0000.0000.0000.0000.0000.0000" */
+ printk("SRC=%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x ", NIP6(ipv6h->saddr));
+ printk("DST=%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x ", NIP6(ipv6h->daddr));
+
+ /* Max length: 44 "LEN=65535 TC=255 HOPLIMIT=255 FLOWLBL=FFFFF " */
+ printk("LEN=%Zu TC=%u HOPLIMIT=%u FLOWLBL=%u ",
+ ntohs(ipv6h->payload_len) + sizeof(struct ipv6hdr),
+ (ntohl(*(u_int32_t *)ipv6h) & 0x0ff00000) >> 20,
+ ipv6h->hop_limit,
+ (ntohl(*(u_int32_t *)ipv6h) & 0x000fffff));
+
+ fragment = 0;
+ ptr = ip6hoff + sizeof(struct ipv6hdr);
+ currenthdr = ipv6h->nexthdr;
+ while (currenthdr != NEXTHDR_NONE && ip6t_ext_hdr(currenthdr)) {
+ struct ipv6_opt_hdr *hdr;
+
+ if (skb->len - ptr < sizeof(*hdr)) {
+ printk("TRUNCATED");
+ return;
+ }
+ hdr = (struct ipv6_opt_hdr *)(skb->data + ptr);
+
+ /* Max length: 48 "OPT (...) " */
+ if (info->logflags & IP6T_LOG_IPOPT)
+ printk("OPT ( ");
+
+ switch (currenthdr) {
+ case IPPROTO_FRAGMENT: {
+ struct frag_hdr *fhdr;
+
+ printk("FRAG:");
+ if (skb->len - ptr < sizeof(*fhdr)) {
+ printk("TRUNCATED ");
+ return;
+ }
+ fhdr = (struct frag_hdr *)(skb->data + ptr);
+
+ /* Max length: 6 "65535 " */
+ printk("%u ", ntohs(fhdr->frag_off) & 0xFFF8);
+
+ /* Max length: 11 "INCOMPLETE " */
+ if (fhdr->frag_off & htons(0x0001))
+ printk("INCOMPLETE ");
+
+ printk("ID:%08x ", ntohl(fhdr->identification));
+
+ if (ntohs(fhdr->frag_off) & 0xFFF8)
+ fragment = 1;
+
+ hdrlen = 8;
+
+ break;
+ }
+ case IPPROTO_DSTOPTS:
+ case IPPROTO_ROUTING:
+ case IPPROTO_HOPOPTS:
+ if (fragment) {
+ if (info->logflags & IP6T_LOG_IPOPT)
+ printk(")");
+ return;
+ }
+ hdrlen = ipv6_optlen(hdr);
+ break;
+ /* Max Length */
+ case IPPROTO_AH:
+ if (info->logflags & IP6T_LOG_IPOPT) {
+ struct ahhdr *ah;
+
+ /* Max length: 3 "AH " */
+ printk("AH ");
+
+ if (fragment) {
+ printk(")");
+ return;
+ }
+
+ if (skb->len - ptr < sizeof(*ah)) {
+ /*
+ * Max length: 26 "INCOMPLETE [65535
+ * bytes] )"
+ */
+ printk("INCOMPLETE [%u bytes] )",
+ skb->len - ptr);
+ return;
+ }
+ ah = (struct ahhdr *)(skb->data + ptr);
+
+ /* Length: 15 "SPI=0xF1234567 */
+ printk("SPI=0x%x ", ntohl(ah->spi));
+
+ }
+
+ hdrlen = (hdr->hdrlen+2)<<2;
+ break;
+ case IPPROTO_ESP:
+ if (info->logflags & IP6T_LOG_IPOPT) {
+ struct esphdr *esph;
+
+ /* Max length: 4 "ESP " */
+ printk("ESP ");
+
+ if (fragment) {
+ printk(")");
+ return;
+ }
+
+ /*
+ * Max length: 26 "INCOMPLETE [65535 bytes] )"
+ */
+ if (skb->len - ptr < sizeof(*esph)) {
+ printk("INCOMPLETE [%u bytes] )",
+ skb->len - ptr);
+ return;
+ }
+ esph = (struct esphdr *)(skb->data + ptr);
+
+ /* Length: 16 "SPI=0xF1234567 )" */
+ printk("SPI=0x%x )", ntohl(esph->spi) );
+
+ }
+ return;
+ default:
+ /* Max length: 20 "Unknown Ext Hdr 255" */
+ printk("Unknown Ext Hdr %u", currenthdr);
+ return;
+ }
+ if (info->logflags & IP6T_LOG_IPOPT)
+ printk(") ");
+
+ currenthdr = hdr->nexthdr;
+ ptr += hdrlen;
+ }
+
+ switch (currenthdr) {
+ case IPPROTO_TCP: {
+ struct tcphdr *tcph;
+
+ /* Max length: 10 "PROTO=TCP " */
+ printk("PROTO=TCP ");
+
+ if (fragment)
+ break;
+
+ /* Max length: 25 "INCOMPLETE [65535 bytes] " */
+ if (skb->len - ptr < sizeof(*tcph)) {
+ printk("INCOMPLETE [%u bytes] ", skb->len - ptr);
+ return;
+ }
+ tcph = (struct tcphdr *)(skb->data + ptr);
+
+ /* Max length: 20 "SPT=65535 DPT=65535 " */
+ printk("SPT=%u DPT=%u ",
+ ntohs(tcph->source), ntohs(tcph->dest));
+ /* Max length: 30 "SEQ=4294967295 ACK=4294967295 " */
+ if (info->logflags & IP6T_LOG_TCPSEQ)
+ printk("SEQ=%u ACK=%u ",
+ ntohl(tcph->seq), ntohl(tcph->ack_seq));
+ /* Max length: 13 "WINDOW=65535 " */
+ printk("WINDOW=%u ", ntohs(tcph->window));
+ /* Max length: 9 "RES=0x3C " */
+ printk("RES=0x%02x ", (u_int8_t)(ntohl(tcp_flag_word(tcph) & TCP_RESERVED_BITS) >> 22));
+ /* Max length: 32 "CWR ECE URG ACK PSH RST SYN FIN " */
+ if (tcph->cwr)
+ printk("CWR ");
+ if (tcph->ece)
+ printk("ECE ");
+ if (tcph->urg)
+ printk("URG ");
+ if (tcph->ack)
+ printk("ACK ");
+ if (tcph->psh)
+ printk("PSH ");
+ if (tcph->rst)
+ printk("RST ");
+ if (tcph->syn)
+ printk("SYN ");
+ if (tcph->fin)
+ printk("FIN ");
+ /* Max length: 11 "URGP=65535 " */
+ printk("URGP=%u ", ntohs(tcph->urg_ptr));
+
+ if ((info->logflags & IP6T_LOG_TCPOPT)
+ && tcph->doff * 4 > sizeof(struct tcphdr)) {
+ u_int8_t *op;
+ unsigned int i;
+ unsigned int optsize = tcph->doff * 4
+ - sizeof(struct tcphdr);
+
+ if (skb->len - ptr - sizeof(struct tcphdr) < optsize) {
+ printk("OPT (TRUNCATED)");
+ return;
+ }
+ op = (u_int8_t *)(skb->data + ptr
+ + sizeof(struct tcphdr));
+
+ /* Max length: 127 "OPT (" 15*4*2chars ") " */
+ printk("OPT (");
+ for (i =0; i < optsize; i++)
+ printk("%02X", op[i]);
+ printk(") ");
+ }
+ break;
+ }
+ case IPPROTO_UDP: {
+ struct udphdr *udph;
+
+ /* Max length: 10 "PROTO=UDP " */
+ printk("PROTO=UDP ");
+
+ if (fragment)
+ break;
+
+ /* Max length: 25 "INCOMPLETE [65535 bytes] " */
+ if (skb->len - ptr < sizeof(*udph)) {
+ printk("INCOMPLETE [%u bytes] ", skb->len - ptr);
+ return;
+ }
+ udph = (struct udphdr *)(skb->data + ptr);
+
+ /* Max length: 20 "SPT=65535 DPT=65535 " */
+ printk("SPT=%u DPT=%u LEN=%u ",
+ ntohs(udph->source), ntohs(udph->dest),
+ ntohs(udph->len));
+ break;
+ }
+ case IPPROTO_ICMPV6: {
+ struct icmp6hdr *icmp6h;
+
+ /* Max length: 13 "PROTO=ICMPv6 " */
+ printk("PROTO=ICMPv6 ");
+
+ if (fragment)
+ break;
+
+ /* Max length: 25 "INCOMPLETE [65535 bytes] " */
+ if (skb->len - ptr < sizeof(*icmp6h)) {
+ printk("INCOMPLETE [%u bytes] ", skb->len - ptr);
+ return;
+ }
+ icmp6h = (struct icmp6hdr *)(skb->data + ptr);
+
+ /* Max length: 18 "TYPE=255 CODE=255 " */
+ printk("TYPE=%u CODE=%u ", icmp6h->icmp6_type, icmp6h->icmp6_code);
+
+ switch (icmp6h->icmp6_type) {
+ case ICMPV6_ECHO_REQUEST:
+ case ICMPV6_ECHO_REPLY:
+ /* Max length: 19 "ID=65535 SEQ=65535 " */
+ printk("ID=%u SEQ=%u ",
+ ntohs(icmp6h->icmp6_identifier),
+ ntohs(icmp6h->icmp6_sequence));
+ break;
+ case ICMPV6_MGM_QUERY:
+ case ICMPV6_MGM_REPORT:
+ case ICMPV6_MGM_REDUCTION:
+ break;
+
+ case ICMPV6_PARAMPROB:
+ /* Max length: 17 "POINTER=ffffffff " */
+ printk("POINTER=%08x ", ntohl(icmp6h->icmp6_pointer));
+ /* Fall through */
+ case ICMPV6_DEST_UNREACH:
+ case ICMPV6_PKT_TOOBIG:
+ case ICMPV6_TIME_EXCEED:
+ /* Max length: 3+maxlen */
+ if (recurse) {
+ printk("[");
+ dump_packet(info, skb, ptr + sizeof(*icmp6h),
+ 0);
+ printk("] ");
+ }
+
+ /* Max length: 10 "MTU=65535 " */
+ if (icmp6h->icmp6_type == ICMPV6_PKT_TOOBIG)
+ printk("MTU=%u ", ntohl(icmp6h->icmp6_mtu));
+ }
+ break;
+ }
+ /* Max length: 10 "PROTO=255 " */
+ default:
+ printk("PROTO=%u ", currenthdr);
+ }
+}
+
+static unsigned int
+ip6t_log_target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ struct ipv6hdr *ipv6h = (*pskb)->nh.ipv6h;
+ const struct ip6t_log_info *loginfo = targinfo;
+ char level_string[4] = "< >";
+
+ level_string[1] = '0' + (loginfo->level % 8);
+ spin_lock_bh(&log_lock);
+ printk(level_string);
+ printk("%sIN=%s OUT=%s ",
+ loginfo->prefix,
+ in ? in->name : "",
+ out ? out->name : "");
+ if (in && !out) {
+ /* MAC logging for input chain only. */
+ printk("MAC=");
+ if ((*pskb)->dev && (*pskb)->dev->hard_header_len && (*pskb)->mac.raw != (void*)ipv6h) {
+ if ((*pskb)->dev->type != ARPHRD_SIT){
+ int i;
+ unsigned char *p = (*pskb)->mac.raw;
+ for (i = 0; i < (*pskb)->dev->hard_header_len; i++,p++)
+ printk("%02x%c", *p,
+ i==(*pskb)->dev->hard_header_len - 1
+ ? ' ':':');
+ } else {
+ int i;
+ unsigned char *p = (*pskb)->mac.raw;
+ if ( p - (ETH_ALEN*2+2) > (*pskb)->head ){
+ p -= (ETH_ALEN+2);
+ for (i = 0; i < (ETH_ALEN); i++,p++)
+ printk("%02x%s", *p,
+ i == ETH_ALEN-1 ? "->" : ":");
+ p -= (ETH_ALEN*2);
+ for (i = 0; i < (ETH_ALEN); i++,p++)
+ printk("%02x%c", *p,
+ i == ETH_ALEN-1 ? ' ' : ':');
+ }
+
+ if (((*pskb)->dev->addr_len == 4) &&
+ (*pskb)->dev->hard_header_len > 20){
+ printk("TUNNEL=");
+ p = (*pskb)->mac.raw + 12;
+ for (i = 0; i < 4; i++,p++)
+ printk("%3d%s", *p,
+ i == 3 ? "->" : ".");
+ for (i = 0; i < 4; i++,p++)
+ printk("%3d%c", *p,
+ i == 3 ? ' ' : '.');
+ }
+ }
+ } else
+ printk(" ");
+ }
+
+ dump_packet(loginfo, (*pskb), (u8*)(*pskb)->nh.ipv6h - (*pskb)->data,
+ 1);
+ printk("\n");
+ spin_unlock_bh(&log_lock);
+
+ return IP6T_CONTINUE;
+}
+
+static int ip6t_log_checkentry(const char *tablename,
+ const struct ip6t_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ const struct ip6t_log_info *loginfo = targinfo;
+
+ if (targinfosize != IP6T_ALIGN(sizeof(struct ip6t_log_info))) {
+ DEBUGP("LOG: targinfosize %u != %u\n",
+ targinfosize, IP6T_ALIGN(sizeof(struct ip6t_log_info)));
+ return 0;
+ }
+
+ if (loginfo->level >= 8) {
+ DEBUGP("LOG: level %u >= 8\n", loginfo->level);
+ return 0;
+ }
+
+ if (loginfo->prefix[sizeof(loginfo->prefix)-1] != '\0') {
+ DEBUGP("LOG: prefix term %i\n",
+ loginfo->prefix[sizeof(loginfo->prefix)-1]);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ip6t_target ip6t_log_reg
+= { { NULL, NULL }, "LOG", ip6t_log_target, ip6t_log_checkentry, NULL,
+ THIS_MODULE };
+
+static int __init init(void)
+{
+ if (ip6t_register_target(&ip6t_log_reg))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ip6t_unregister_target(&ip6t_log_reg);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_MARK.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_MARK.c
new file mode 100644
index 0000000..88c12a4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_MARK.c
@@ -0,0 +1,68 @@
+/* This is a module which is used for setting the NFMARK field of an skb. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ip.h>
+#include <net/checksum.h>
+
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_MARK.h>
+
+static unsigned int
+target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ const struct ip6t_mark_target_info *markinfo = targinfo;
+
+ if((*pskb)->nfmark != markinfo->mark) {
+ (*pskb)->nfmark = markinfo->mark;
+ (*pskb)->nfcache |= NFC_ALTERED;
+ }
+ return IP6T_CONTINUE;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ip6t_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ if (targinfosize != IP6T_ALIGN(sizeof(struct ip6t_mark_target_info))) {
+ printk(KERN_WARNING "MARK: targinfosize %u != %Zu\n",
+ targinfosize,
+ IP6T_ALIGN(sizeof(struct ip6t_mark_target_info)));
+ return 0;
+ }
+
+ if (strcmp(tablename, "mangle") != 0) {
+ printk(KERN_WARNING "MARK: can only be called from \"mangle\" table, not \"%s\"\n", tablename);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ip6t_target ip6t_mark_reg
+= { { NULL, NULL }, "MARK", target, checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ printk(KERN_DEBUG "registering ipv6 mark target\n");
+ if (ip6t_register_target(&ip6t_mark_reg))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ip6t_unregister_target(&ip6t_mark_reg);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_REJECT.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_REJECT.c
new file mode 100644
index 0000000..c1aca67
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_REJECT.c
@@ -0,0 +1,301 @@
+/*
+ * This is a module which is used for rejecting packets.
+ * Added support for customized reject packets (Jozsef Kadlecsik).
+ * Sun 12 Nov 2000
+ * Port to IPv6 / ip6tables (Harald Welte <laforge@gnumonks.org>)
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/icmpv6.h>
+#include <net/tcp.h>
+#include <net/ipv6.h>
+#include <net/ip6_fib.h>
+#include <net/ip6_route.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_REJECT.h>
+
+#if 1
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+#if 0
+/* Send RST reply */
+static void send_reset(struct sk_buff *oldskb)
+{
+ struct sk_buff *nskb;
+ struct tcphdr *otcph, *tcph;
+ struct rtable *rt;
+ unsigned int otcplen;
+ int needs_ack;
+
+ /* IP header checks: fragment, too short. */
+ if (oldskb->nh.iph->frag_off & htons(IP_OFFSET)
+ || oldskb->len < (oldskb->nh.iph->ihl<<2) + sizeof(struct tcphdr))
+ return;
+
+ otcph = (struct tcphdr *)((u_int32_t*)oldskb->nh.iph + oldskb->nh.iph->ihl);
+ otcplen = oldskb->len - oldskb->nh.iph->ihl*4;
+
+ /* No RST for RST. */
+ if (otcph->rst)
+ return;
+
+ /* Check checksum. */
+ if (tcp_v4_check(otcph, otcplen, oldskb->nh.iph->saddr,
+ oldskb->nh.iph->daddr,
+ csum_partial((char *)otcph, otcplen, 0)) != 0)
+ return;
+
+ /* Copy skb (even if skb is about to be dropped, we can't just
+ clone it because there may be other things, such as tcpdump,
+ interested in it) */
+ nskb = skb_copy(oldskb, GFP_ATOMIC);
+ if (!nskb)
+ return;
+
+ /* This packet will not be the same as the other: clear nf fields */
+ nf_conntrack_put(nskb->nfct);
+ nskb->nfct = NULL;
+ nskb->nfcache = 0;
+#ifdef CONFIG_NETFILTER_DEBUG
+ nskb->nf_debug = 0;
+#endif
+
+ tcph = (struct tcphdr *)((u_int32_t*)nskb->nh.iph + nskb->nh.iph->ihl);
+
+ nskb->nh.iph->daddr = xchg(&nskb->nh.iph->saddr, nskb->nh.iph->daddr);
+ tcph->source = xchg(&tcph->dest, tcph->source);
+
+ /* Truncate to length (no data) */
+ tcph->doff = sizeof(struct tcphdr)/4;
+ skb_trim(nskb, nskb->nh.iph->ihl*4 + sizeof(struct tcphdr));
+ nskb->nh.iph->tot_len = htons(nskb->len);
+
+ if (tcph->ack) {
+ needs_ack = 0;
+ tcph->seq = otcph->ack_seq;
+ tcph->ack_seq = 0;
+ } else {
+ needs_ack = 1;
+ tcph->ack_seq = htonl(ntohl(otcph->seq) + otcph->syn + otcph->fin
+ + otcplen - (otcph->doff<<2));
+ tcph->seq = 0;
+ }
+
+ /* Reset flags */
+ ((u_int8_t *)tcph)[13] = 0;
+ tcph->rst = 1;
+ tcph->ack = needs_ack;
+
+ tcph->window = 0;
+ tcph->urg_ptr = 0;
+
+ /* Adjust TCP checksum */
+ tcph->check = 0;
+ tcph->check = tcp_v4_check(tcph, sizeof(struct tcphdr),
+ nskb->nh.iph->saddr,
+ nskb->nh.iph->daddr,
+ csum_partial((char *)tcph,
+ sizeof(struct tcphdr), 0));
+
+ /* Adjust IP TTL, DF */
+ nskb->nh.iph->ttl = MAXTTL;
+ /* Set DF, id = 0 */
+ nskb->nh.iph->frag_off = htons(IP_DF);
+ nskb->nh.iph->id = 0;
+
+ /* Adjust IP checksum */
+ nskb->nh.iph->check = 0;
+ nskb->nh.iph->check = ip_fast_csum((unsigned char *)nskb->nh.iph,
+ nskb->nh.iph->ihl);
+
+ /* Routing */
+ if (ip_route_output(&rt, nskb->nh.iph->daddr, nskb->nh.iph->saddr,
+ RT_TOS(nskb->nh.iph->tos) | RTO_CONN,
+ 0) != 0)
+ goto free_nskb;
+
+ dst_release(nskb->dst);
+ nskb->dst = &rt->u.dst;
+
+ /* "Never happens" */
+ if (nskb->len > nskb->dst->pmtu)
+ goto free_nskb;
+
+ NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, nskb, NULL, nskb->dst->dev,
+ ip_finish_output);
+ return;
+
+ free_nskb:
+ kfree_skb(nskb);
+}
+#endif
+
+static unsigned int reject6_target(struct sk_buff **pskb,
+ unsigned int hooknum,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *targinfo,
+ void *userinfo)
+{
+ const struct ip6t_reject_info *reject = targinfo;
+ struct sk_buff *skb2 = NULL;
+ struct rt6_info *rt6i;
+ struct net_device odev;
+
+ if (!out) {
+ skb2 = skb_clone(*pskb, GFP_ATOMIC);
+ if (skb2 == NULL) {
+ return NF_DROP;
+ }
+ dst_release(skb2->dst);
+ skb2->dst = NULL;
+
+ rt6i = rt6_lookup(&skb2->nh.ipv6h->saddr, NULL, 0, 0);
+ if (rt6i && rt6i->rt6i_dev) {
+ skb2->dev = rt6i->rt6i_dev;
+ rt6i = rt6_lookup(&skb2->nh.ipv6h->daddr, &skb2->nh.ipv6h->saddr, 0, 0);
+ }
+ memcpy(&odev, skb2->dev, sizeof(odev)); /* XXX 'out' has 'const' qualifier... */
+ } else {
+ memcpy(&odev, out, sizeof(odev));
+ }
+
+ printk(KERN_DEBUG "%s: medium point\n", __FUNCTION__);
+ /* WARNING: This code causes reentry within ip6tables.
+ This means that the ip6tables jump stack is now crap. We
+ must return an absolute verdict. --RR */
+ DEBUGP("REJECTv6: calling icmpv6_send\n");
+ switch (reject->with) {
+ case IP6T_ICMP6_NO_ROUTE:
+ icmpv6_send(*pskb, ICMPV6_DEST_UNREACH, ICMPV6_NOROUTE, 0, &odev);
+ break;
+ case IP6T_ICMP6_ADM_PROHIBITED:
+ icmpv6_send(*pskb, ICMPV6_DEST_UNREACH, ICMPV6_ADM_PROHIBITED, 0, &odev);
+ break;
+ case IP6T_ICMP6_NOT_NEIGHBOUR:
+ icmpv6_send(*pskb, ICMPV6_DEST_UNREACH, ICMPV6_NOT_NEIGHBOUR, 0, &odev);
+ break;
+ case IP6T_ICMP6_ADDR_UNREACH:
+ icmpv6_send(*pskb, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH, 0, &odev);
+ break;
+ case IP6T_ICMP6_PORT_UNREACH:
+ icmpv6_send(*pskb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0, &odev);
+ break;
+#if 0
+ case IPT_ICMP_ECHOREPLY: {
+ struct icmp6hdr *icmph = (struct icmphdr *)
+ ((u_int32_t *)(*pskb)->nh.iph + (*pskb)->nh.iph->ihl);
+ unsigned int datalen = (*pskb)->len - (*pskb)->nh.iph->ihl * 4;
+
+ /* Not non-head frags, or truncated */
+ if (((ntohs((*pskb)->nh.iph->frag_off) & IP_OFFSET) == 0)
+ && datalen >= 4) {
+ /* Usually I don't like cut & pasting code,
+ but dammit, my party is starting in 45
+ mins! --RR */
+ struct icmp_bxm icmp_param;
+
+ icmp_param.icmph=*icmph;
+ icmp_param.icmph.type=ICMP_ECHOREPLY;
+ icmp_param.data_ptr=(icmph+1);
+ icmp_param.data_len=datalen;
+ icmp_reply(&icmp_param, *pskb);
+ }
+ }
+ break;
+ case IPT_TCP_RESET:
+ send_reset(*pskb);
+ break;
+#endif
+ default:
+ printk(KERN_WARNING "REJECTv6: case %u not handled yet\n", reject->with);
+ break;
+ }
+
+ if (skb2) kfree_skb(skb2);
+
+ return NF_DROP;
+}
+
+static inline int find_ping_match(const struct ip6t_entry_match *m)
+{
+ const struct ip6t_icmp *icmpinfo = (const struct ip6t_icmp *)m->data;
+
+ if (strcmp(m->u.kernel.match->name, "icmp6") == 0
+ && icmpinfo->type == ICMPV6_ECHO_REQUEST
+ && !(icmpinfo->invflags & IP6T_ICMP_INV))
+ return 1;
+
+ return 0;
+}
+
+static int check(const char *tablename,
+ const struct ip6t_entry *e,
+ void *targinfo,
+ unsigned int targinfosize,
+ unsigned int hook_mask)
+{
+ const struct ip6t_reject_info *rejinfo = targinfo;
+
+ if (targinfosize != IP6T_ALIGN(sizeof(struct ip6t_reject_info))) {
+ DEBUGP("REJECTv6: targinfosize %u != 0\n", targinfosize);
+ return 0;
+ }
+
+ /* Only allow these for packet filtering. */
+ if (strcmp(tablename, "filter") != 0) {
+ DEBUGP("REJECTv6: bad table `%s'.\n", tablename);
+ return 0;
+ }
+ if ((hook_mask & ~((1 << NF_IP6_LOCAL_IN)
+ | (1 << NF_IP6_FORWARD)
+ | (1 << NF_IP6_LOCAL_OUT))) != 0) {
+ DEBUGP("REJECTv6: bad hook mask %X\n", hook_mask);
+ return 0;
+ }
+
+ if (rejinfo->with == IP6T_ICMP6_ECHOREPLY) {
+ /* Must specify that it's an ICMP ping packet. */
+ if (e->ipv6.proto != IPPROTO_ICMPV6
+ || (e->ipv6.invflags & IP6T_INV_PROTO)) {
+ DEBUGP("REJECTv6: ECHOREPLY illegal for non-icmp\n");
+ return 0;
+ }
+ /* Must contain ICMP match. */
+ if (IP6T_MATCH_ITERATE(e, find_ping_match) == 0) {
+ DEBUGP("REJECTv6: ECHOREPLY illegal for non-ping\n");
+ return 0;
+ }
+ } else if (rejinfo->with == IP6T_TCP_RESET) {
+ /* Must specify that it's a TCP packet */
+ if (e->ipv6.proto != IPPROTO_TCP
+ || (e->ipv6.invflags & IP6T_INV_PROTO)) {
+ DEBUGP("REJECTv6: TCP_RESET illegal for non-tcp\n");
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static struct ip6t_target ip6t_reject_reg
+= { { NULL, NULL }, "REJECT", reject6_target, check, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ if (ip6t_register_target(&ip6t_reject_reg))
+ return -EINVAL;
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ip6t_unregister_target(&ip6t_reject_reg);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_agr.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_agr.c
new file mode 100644
index 0000000..05f5a38
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_agr.c
@@ -0,0 +1,91 @@
+/* Kernel module to match AGR address parameters. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ipv6.h>
+#include <linux/if_ether.h>
+
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+
+ unsigned char aggregated[8];
+ int i=0;
+
+ /*TODO size and pointer checking */
+ if ( !(skb->mac.raw >= skb->head
+ && (skb->mac.raw + ETH_HLEN) <= skb->data)
+ && offset != 0) {
+ *hotdrop = 1;
+ return 0;
+ }
+
+ memset(aggregated, 0, sizeof(aggregated));
+
+ if (skb->mac.ethernet->h_proto == ntohs(ETH_P_IPV6)) {
+ if (skb->nh.ipv6h->version == 0x6) {
+ memcpy(aggregated, skb->mac.ethernet->h_source, 3);
+ memcpy(aggregated + 5, skb->mac.ethernet->h_source + 3, 3);
+ aggregated[3]=0xff;
+ aggregated[4]=0xfe;
+ aggregated[0] |= 0x02;
+
+ i=0;
+ while ((skb->nh.ipv6h->saddr.in6_u.u6_addr8[8+i] ==
+ aggregated[i]) && (i<8)) i++;
+
+ if ( i == 8 )
+ return 1;
+ }
+ }
+
+ return 0;
+
+/* return (skb->mac.raw >= skb->head
+ && skb->mac.raw < skb->head + skb->len - ETH_HLEN */
+
+
+}
+
+static int
+ipt_agr_checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (hook_mask
+ & ~((1 << NF_IP6_PRE_ROUTING) | (1 << NF_IP6_LOCAL_IN))) {
+ printk("ipt_agr: only valid for PRE_ROUTING or LOCAL_IN.\n");
+ return 0;
+ }
+
+ if (matchsize != IP6T_ALIGN(sizeof(int)))
+ return 0;
+
+ return 1;
+}
+
+static struct ip6t_match agr_match
+= { { NULL, NULL }, "agr", &match, &ipt_agr_checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ip6t_register_match(&agr_match);
+}
+
+static void __exit fini(void)
+{
+ ip6t_unregister_match(&agr_match);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_ah.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_ah.c
new file mode 100644
index 0000000..3df7c04
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_ah.c
@@ -0,0 +1,207 @@
+/* Kernel module to match AH parameters. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ipv6.h>
+#include <linux/types.h>
+#include <net/checksum.h>
+#include <net/ipv6.h>
+
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_ah.h>
+
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("IPv6 AH match");
+MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+struct ahhdr {
+ __u8 nexthdr;
+ __u8 hdrlen;
+ __u16 reserved;
+ __u32 spi;
+};
+
+/* Returns 1 if the spi is matched by the range, 0 otherwise */
+static inline int
+spi_match(u_int32_t min, u_int32_t max, u_int32_t spi, int invert)
+{
+ int r=0;
+ DEBUGP("ah spi_match:%c 0x%x <= 0x%x <= 0x%x",invert? '!':' ',
+ min,spi,max);
+ r=(spi >= min && spi <= max) ^ invert;
+ DEBUGP(" result %s\n",r? "PASS\n" : "FAILED\n");
+ return r;
+}
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *protohdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ struct ahhdr *ah = NULL;
+ const struct ip6t_ah *ahinfo = matchinfo;
+ unsigned int temp;
+ int len;
+ u8 nexthdr;
+ unsigned int ptr;
+ unsigned int hdrlen = 0;
+
+ /*DEBUGP("IPv6 AH entered\n");*/
+ /* if (opt->auth == 0) return 0;
+ * It does not filled on output */
+
+ /* type of the 1st exthdr */
+ nexthdr = skb->nh.ipv6h->nexthdr;
+ /* pointer to the 1st exthdr */
+ ptr = sizeof(struct ipv6hdr);
+ /* available length */
+ len = skb->len - ptr;
+ temp = 0;
+
+ while (ipv6_ext_hdr(nexthdr)) {
+ struct ipv6_opt_hdr *hdr;
+
+ DEBUGP("ipv6_ah header iteration \n");
+
+ /* Is there enough space for the next ext header? */
+ if (len < (int)sizeof(struct ipv6_opt_hdr))
+ return 0;
+ /* No more exthdr -> evaluate */
+ if (nexthdr == NEXTHDR_NONE) {
+ break;
+ }
+ /* ESP -> evaluate */
+ if (nexthdr == NEXTHDR_ESP) {
+ break;
+ }
+
+ hdr=skb->data+ptr;
+
+ /* Calculate the header length */
+ if (nexthdr == NEXTHDR_FRAGMENT) {
+ hdrlen = 8;
+ } else if (nexthdr == NEXTHDR_AUTH)
+ hdrlen = (hdr->hdrlen+2)<<2;
+ else
+ hdrlen = ipv6_optlen(hdr);
+
+ /* AH -> evaluate */
+ if (nexthdr == NEXTHDR_AUTH) {
+ temp |= MASK_AH;
+ break;
+ }
+
+
+ /* set the flag */
+ switch (nexthdr){
+ case NEXTHDR_HOP:
+ case NEXTHDR_ROUTING:
+ case NEXTHDR_FRAGMENT:
+ case NEXTHDR_AUTH:
+ case NEXTHDR_DEST:
+ break;
+ default:
+ DEBUGP("ipv6_ah match: unknown nextheader %u\n",nexthdr);
+ return 0;
+ break;
+ }
+
+ nexthdr = hdr->nexthdr;
+ len -= hdrlen;
+ ptr += hdrlen;
+ if ( ptr > skb->len ) {
+ DEBUGP("ipv6_ah: new pointer too large! \n");
+ break;
+ }
+ }
+
+ /* AH header not found */
+ if ( temp != MASK_AH ) return 0;
+
+ if (len < (int)sizeof(struct ahhdr)){
+ *hotdrop = 1;
+ return 0;
+ }
+
+ ah = (struct ahhdr *) (skb->data + ptr);
+
+ DEBUGP("IPv6 AH LEN %u %u ", hdrlen, ah->hdrlen);
+ DEBUGP("RES %04X ", ah->reserved);
+ DEBUGP("SPI %u %08X\n", ntohl(ah->spi), ntohl(ah->spi));
+
+ DEBUGP("IPv6 AH spi %02X ",
+ (spi_match(ahinfo->spis[0], ahinfo->spis[1],
+ ntohl(ah->spi),
+ !!(ahinfo->invflags & IP6T_AH_INV_SPI))));
+ DEBUGP("len %02X %04X %02X ",
+ ahinfo->hdrlen, hdrlen,
+ (!ahinfo->hdrlen ||
+ (ahinfo->hdrlen == hdrlen) ^
+ !!(ahinfo->invflags & IP6T_AH_INV_LEN)));
+ DEBUGP("res %02X %04X %02X\n",
+ ahinfo->hdrres, ah->reserved,
+ !(ahinfo->hdrres && ah->reserved));
+
+ return (ah != NULL)
+ &&
+ (spi_match(ahinfo->spis[0], ahinfo->spis[1],
+ ntohl(ah->spi),
+ !!(ahinfo->invflags & IP6T_AH_INV_SPI)))
+ &&
+ (!ahinfo->hdrlen ||
+ (ahinfo->hdrlen == hdrlen) ^
+ !!(ahinfo->invflags & IP6T_AH_INV_LEN))
+ &&
+ !(ahinfo->hdrres && ah->reserved);
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchinfosize,
+ unsigned int hook_mask)
+{
+ const struct ip6t_ah *ahinfo = matchinfo;
+
+ if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_ah))) {
+ DEBUGP("ip6t_ah: matchsize %u != %u\n",
+ matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_ah)));
+ return 0;
+ }
+ if (ahinfo->invflags & ~IP6T_AH_INV_MASK) {
+ DEBUGP("ip6t_ah: unknown flags %X\n",
+ ahinfo->invflags);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ip6t_match ah_match
+= { { NULL, NULL }, "ah", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ip6t_register_match(&ah_match);
+}
+
+static void __exit cleanup(void)
+{
+ ip6t_unregister_match(&ah_match);
+}
+
+module_init(init);
+module_exit(cleanup);
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_dst.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_dst.c
new file mode 100644
index 0000000..66444c0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_dst.c
@@ -0,0 +1,278 @@
+/* Kernel module to match Hop-by-Hop and Destination parameters. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ipv6.h>
+#include <linux/types.h>
+#include <net/checksum.h>
+#include <net/ipv6.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_opts.h>
+
+#define HOPBYHOP 0
+
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
+#if HOPBYHOP
+MODULE_DESCRIPTION("IPv6 HbH match");
+#else
+MODULE_DESCRIPTION("IPv6 DST match");
+#endif
+MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+/*
+ * (Type & 0xC0) >> 6
+ * 0 -> ignorable
+ * 1 -> must drop the packet
+ * 2 -> send ICMP PARM PROB regardless and drop packet
+ * 3 -> Send ICMP if not a multicast address and drop packet
+ * (Type & 0x20) >> 5
+ * 0 -> invariant
+ * 1 -> can change the routing
+ * (Type & 0x1F) Type
+ * 0 -> Pad1 (only 1 byte!)
+ * 1 -> PadN LENGTH info (total length = length + 2)
+ * C0 | 2 -> JUMBO 4 x x x x ( xxxx > 64k )
+ * 5 -> RTALERT 2 x x
+ */
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *protohdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ struct ipv6_opt_hdr *optsh = NULL;
+ const struct ip6t_opts *optinfo = matchinfo;
+ unsigned int temp;
+ unsigned int len;
+ u8 nexthdr;
+ unsigned int ptr;
+ unsigned int hdrlen = 0;
+ unsigned int ret = 0;
+ u8 *opttype = NULL;
+ unsigned int optlen;
+
+ /* type of the 1st exthdr */
+ nexthdr = skb->nh.ipv6h->nexthdr;
+ /* pointer to the 1st exthdr */
+ ptr = sizeof(struct ipv6hdr);
+ /* available length */
+ len = skb->len - ptr;
+ temp = 0;
+
+ while (ipv6_ext_hdr(nexthdr)) {
+ struct ipv6_opt_hdr *hdr;
+
+ DEBUGP("ipv6_opts header iteration \n");
+
+ /* Is there enough space for the next ext header? */
+ if (len < (int)sizeof(struct ipv6_opt_hdr))
+ return 0;
+ /* No more exthdr -> evaluate */
+ if (nexthdr == NEXTHDR_NONE) {
+ break;
+ }
+ /* ESP -> evaluate */
+ if (nexthdr == NEXTHDR_ESP) {
+ break;
+ }
+
+ hdr = (void *)(skb->data + ptr);
+
+ /* Calculate the header length */
+ if (nexthdr == NEXTHDR_FRAGMENT) {
+ hdrlen = 8;
+ } else if (nexthdr == NEXTHDR_AUTH)
+ hdrlen = (hdr->hdrlen+2)<<2;
+ else
+ hdrlen = ipv6_optlen(hdr);
+
+ /* OPTS -> evaluate */
+#if HOPBYHOP
+ if (nexthdr == NEXTHDR_HOP) {
+ temp |= MASK_HOPOPTS;
+#else
+ if (nexthdr == NEXTHDR_DEST) {
+ temp |= MASK_DSTOPTS;
+#endif
+ break;
+ }
+
+
+ /* set the flag */
+ switch (nexthdr){
+ case NEXTHDR_HOP:
+ case NEXTHDR_ROUTING:
+ case NEXTHDR_FRAGMENT:
+ case NEXTHDR_AUTH:
+ case NEXTHDR_DEST:
+ break;
+ default:
+ DEBUGP("ipv6_opts match: unknown nextheader %u\n",nexthdr);
+ return 0;
+ break;
+ }
+
+ nexthdr = hdr->nexthdr;
+ len -= hdrlen;
+ ptr += hdrlen;
+ if ( ptr > skb->len ) {
+ DEBUGP("ipv6_opts: new pointer is too large! \n");
+ break;
+ }
+ }
+
+ /* OPTIONS header not found */
+#if HOPBYHOP
+ if ( temp != MASK_HOPOPTS ) return 0;
+#else
+ if ( temp != MASK_DSTOPTS ) return 0;
+#endif
+
+ if (len < (int)sizeof(struct ipv6_opt_hdr)){
+ *hotdrop = 1;
+ return 0;
+ }
+
+ if (len < hdrlen){
+ /* Packet smaller than it's length field */
+ return 0;
+ }
+
+ optsh = (void *)(skb->data + ptr);
+
+ DEBUGP("IPv6 OPTS LEN %u %u ", hdrlen, optsh->hdrlen);
+
+ DEBUGP("len %02X %04X %02X ",
+ optinfo->hdrlen, hdrlen,
+ (!(optinfo->flags & IP6T_OPTS_LEN) ||
+ ((optinfo->hdrlen == hdrlen) ^
+ !!(optinfo->invflags & IP6T_OPTS_INV_LEN))));
+
+ ret = (optsh != NULL)
+ &&
+ (!(optinfo->flags & IP6T_OPTS_LEN) ||
+ ((optinfo->hdrlen == hdrlen) ^
+ !!(optinfo->invflags & IP6T_OPTS_INV_LEN)));
+
+ ptr += 2;
+ hdrlen -= 2;
+ if ( !(optinfo->flags & IP6T_OPTS_OPTS) ){
+ return ret;
+ } else if (optinfo->flags & IP6T_OPTS_NSTRICT) {
+ DEBUGP("Not strict - not implemented");
+ } else {
+ DEBUGP("Strict ");
+ DEBUGP("#%d ",optinfo->optsnr);
+ for(temp=0; temp<optinfo->optsnr; temp++){
+ /* type field exists ? */
+ if (ptr > skb->len - 1 || hdrlen < 1)
+ break;
+ opttype = (void *)(skb->data + ptr);
+
+ /* Type check */
+ if (*opttype != (optinfo->opts[temp] & 0xFF00)>>8){
+ DEBUGP("Tbad %02X %02X\n",
+ *opttype,
+ (optinfo->opts[temp] & 0xFF00)>>8);
+ return 0;
+ } else {
+ DEBUGP("Tok ");
+ }
+ /* Length check */
+ if (*opttype) {
+ u16 spec_len;
+
+ /* length field exists ? */
+ if (ptr > skb->len - 2 || hdrlen < 2)
+ break;
+ optlen = *((u8 *)(skb->data + ptr + 1));
+ spec_len = optinfo->opts[temp] & 0x00FF;
+
+ if (spec_len != 0x00FF && spec_len != optlen) {
+ DEBUGP("Lbad %02X %04X\n", optlen,
+ spec_len);
+ return 0;
+ }
+ DEBUGP("Lok ");
+ optlen += 2;
+ } else {
+ DEBUGP("Pad1\n");
+ optlen = 1;
+ }
+
+ /* Step to the next */
+ DEBUGP("len%04X \n", optlen);
+
+ if ((ptr > skb->len - optlen || hdrlen < optlen) &&
+ (temp < optinfo->optsnr - 1)) {
+ DEBUGP("new pointer is too large! \n");
+ break;
+ }
+ ptr += optlen;
+ hdrlen -= optlen;
+ }
+ if (temp == optinfo->optsnr)
+ return ret;
+ else return 0;
+ }
+
+ return 0;
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchinfosize,
+ unsigned int hook_mask)
+{
+ const struct ip6t_opts *optsinfo = matchinfo;
+
+ if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_opts))) {
+ DEBUGP("ip6t_opts: matchsize %u != %u\n",
+ matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_opts)));
+ return 0;
+ }
+ if (optsinfo->invflags & ~IP6T_OPTS_INV_MASK) {
+ DEBUGP("ip6t_opts: unknown flags %X\n",
+ optsinfo->invflags);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ip6t_match opts_match
+#if HOPBYHOP
+= { { NULL, NULL }, "hbh", &match, &checkentry, NULL, THIS_MODULE };
+#else
+= { { NULL, NULL }, "dst", &match, &checkentry, NULL, THIS_MODULE };
+#endif
+
+static int __init init(void)
+{
+ return ip6t_register_match(&opts_match);
+}
+
+static void __exit cleanup(void)
+{
+ ip6t_unregister_match(&opts_match);
+}
+
+module_init(init);
+module_exit(cleanup);
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_esp.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_esp.c
new file mode 100644
index 0000000..8f3792b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_esp.c
@@ -0,0 +1,176 @@
+/* Kernel module to match ESP parameters. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ipv6.h>
+#include <linux/types.h>
+#include <net/checksum.h>
+#include <net/ipv6.h>
+
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_esp.h>
+
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("IPv6 ESP match");
+MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+struct esphdr {
+ __u32 spi;
+ __u32 seq_no;
+};
+
+/* Returns 1 if the spi is matched by the range, 0 otherwise */
+static inline int
+spi_match(u_int32_t min, u_int32_t max, u_int32_t spi, int invert)
+{
+ int r=0;
+ DEBUGP("esp spi_match:%c 0x%x <= 0x%x <= 0x%x",invert? '!':' ',
+ min,spi,max);
+ r=(spi >= min && spi <= max) ^ invert;
+ DEBUGP(" result %s\n",r? "PASS\n" : "FAILED\n");
+ return r;
+}
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *protohdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ struct esphdr *esp = NULL;
+ const struct ip6t_esp *espinfo = matchinfo;
+ unsigned int temp;
+ int len;
+ u8 nexthdr;
+ unsigned int ptr;
+
+ /* Make sure this isn't an evil packet */
+ /*DEBUGP("ipv6_esp entered \n");*/
+
+ /* type of the 1st exthdr */
+ nexthdr = skb->nh.ipv6h->nexthdr;
+ /* pointer to the 1st exthdr */
+ ptr = sizeof(struct ipv6hdr);
+ /* available length */
+ len = skb->len - ptr;
+ temp = 0;
+
+ while (ipv6_ext_hdr(nexthdr)) {
+ struct ipv6_opt_hdr *hdr;
+ int hdrlen;
+
+ DEBUGP("ipv6_esp header iteration \n");
+
+ /* Is there enough space for the next ext header? */
+ if (len < (int)sizeof(struct ipv6_opt_hdr))
+ return 0;
+ /* No more exthdr -> evaluate */
+ if (nexthdr == NEXTHDR_NONE) {
+ break;
+ }
+ /* ESP -> evaluate */
+ if (nexthdr == NEXTHDR_ESP) {
+ temp |= MASK_ESP;
+ break;
+ }
+
+ hdr=skb->data+ptr;
+
+ /* Calculate the header length */
+ if (nexthdr == NEXTHDR_FRAGMENT) {
+ hdrlen = 8;
+ } else if (nexthdr == NEXTHDR_AUTH)
+ hdrlen = (hdr->hdrlen+2)<<2;
+ else
+ hdrlen = ipv6_optlen(hdr);
+
+ /* set the flag */
+ switch (nexthdr){
+ case NEXTHDR_HOP:
+ case NEXTHDR_ROUTING:
+ case NEXTHDR_FRAGMENT:
+ case NEXTHDR_AUTH:
+ case NEXTHDR_DEST:
+ break;
+ default:
+ DEBUGP("ipv6_esp match: unknown nextheader %u\n",nexthdr);
+ return 0;
+ break;
+ }
+
+ nexthdr = hdr->nexthdr;
+ len -= hdrlen;
+ ptr += hdrlen;
+ if ( ptr > skb->len ) {
+ DEBUGP("ipv6_esp: new pointer too large! \n");
+ break;
+ }
+ }
+
+ /* ESP header not found */
+ if ( temp != MASK_ESP ) return 0;
+
+ if (len < (int)sizeof(struct esphdr)){
+ *hotdrop = 1;
+ return 0;
+ }
+
+ esp = (struct esphdr *) (skb->data + ptr);
+
+ DEBUGP("IPv6 ESP SPI %u %08X\n", ntohl(esp->spi), ntohl(esp->spi));
+
+ return (esp != NULL)
+ && spi_match(espinfo->spis[0], espinfo->spis[1],
+ ntohl(esp->spi),
+ !!(espinfo->invflags & IP6T_ESP_INV_SPI));
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchinfosize,
+ unsigned int hook_mask)
+{
+ const struct ip6t_esp *espinfo = matchinfo;
+
+ if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_esp))) {
+ DEBUGP("ip6t_esp: matchsize %u != %u\n",
+ matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_esp)));
+ return 0;
+ }
+ if (espinfo->invflags & ~IP6T_ESP_INV_MASK) {
+ DEBUGP("ip6t_esp: unknown flags %X\n",
+ espinfo->invflags);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ip6t_match esp_match
+= { { NULL, NULL }, "esp", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ip6t_register_match(&esp_match);
+}
+
+static void __exit cleanup(void)
+{
+ ip6t_unregister_match(&esp_match);
+}
+
+module_init(init);
+module_exit(cleanup);
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_eui64.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_eui64.c
new file mode 100644
index 0000000..5f91a5b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_eui64.c
@@ -0,0 +1,89 @@
+/* Kernel module to match EUI64 address parameters. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ipv6.h>
+#include <linux/if_ether.h>
+
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+
+ unsigned char eui64[8];
+ int i=0;
+
+ if ( !(skb->mac.raw >= skb->head
+ && (skb->mac.raw + ETH_HLEN) <= skb->data)
+ && offset != 0) {
+ *hotdrop = 1;
+ return 0;
+ }
+
+ memset(eui64, 0, sizeof(eui64));
+
+ if (skb->mac.ethernet->h_proto == ntohs(ETH_P_IPV6)) {
+ if (skb->nh.ipv6h->version == 0x6) {
+ memcpy(eui64, skb->mac.ethernet->h_source, 3);
+ memcpy(eui64 + 5, skb->mac.ethernet->h_source + 3, 3);
+ eui64[3]=0xff;
+ eui64[4]=0xfe;
+ eui64[0] |= 0x02;
+
+ i=0;
+ while ((skb->nh.ipv6h->saddr.in6_u.u6_addr8[8+i] ==
+ eui64[i]) && (i<8)) i++;
+
+ if ( i == 8 )
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int
+ip6t_eui64_checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (hook_mask
+ & ~((1 << NF_IP6_PRE_ROUTING) | (1 << NF_IP6_LOCAL_IN) |
+ (1 << NF_IP6_FORWARD))) {
+ printk("ip6t_eui64: only valid for PRE_ROUTING, LOCAL_IN or FORWARD.\n");
+ return 0;
+ }
+
+ if (matchsize != IP6T_ALIGN(sizeof(int)))
+ return 0;
+
+ return 1;
+}
+
+static struct ip6t_match eui64_match
+= { { NULL, NULL }, "eui64", &match, &ip6t_eui64_checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ip6t_register_match(&eui64_match);
+}
+
+static void __exit fini(void)
+{
+ ip6t_unregister_match(&eui64_match);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_DESCRIPTION("IPv6 EUI64 address checking match");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_frag.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_frag.c
new file mode 100644
index 0000000..dff4a2e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_frag.c
@@ -0,0 +1,219 @@
+/* Kernel module to match FRAG parameters. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ipv6.h>
+#include <linux/types.h>
+#include <net/checksum.h>
+#include <net/ipv6.h>
+
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_frag.h>
+
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("IPv6 FRAG match");
+MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+#define IP6_MF 0x0001
+
+/* Returns 1 if the id is matched by the range, 0 otherwise */
+static inline int
+id_match(u_int32_t min, u_int32_t max, u_int32_t id, int invert)
+{
+ int r=0;
+ DEBUGP("frag id_match:%c 0x%x <= 0x%x <= 0x%x",invert? '!':' ',
+ min,id,max);
+ r=(id >= min && id <= max) ^ invert;
+ DEBUGP(" result %s\n",r? "PASS" : "FAILED");
+ return r;
+}
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *protohdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ struct frag_hdr *frag = NULL;
+ const struct ip6t_frag *fraginfo = matchinfo;
+ unsigned int temp;
+ int len;
+ u8 nexthdr;
+ unsigned int ptr;
+ unsigned int hdrlen = 0;
+
+ /* type of the 1st exthdr */
+ nexthdr = skb->nh.ipv6h->nexthdr;
+ /* pointer to the 1st exthdr */
+ ptr = sizeof(struct ipv6hdr);
+ /* available length */
+ len = skb->len - ptr;
+ temp = 0;
+
+ while (ipv6_ext_hdr(nexthdr)) {
+ struct ipv6_opt_hdr *hdr;
+
+ DEBUGP("ipv6_frag header iteration \n");
+
+ /* Is there enough space for the next ext header? */
+ if (len < (int)sizeof(struct ipv6_opt_hdr))
+ return 0;
+ /* No more exthdr -> evaluate */
+ if (nexthdr == NEXTHDR_NONE) {
+ break;
+ }
+ /* ESP -> evaluate */
+ if (nexthdr == NEXTHDR_ESP) {
+ break;
+ }
+
+ hdr=(struct ipv6_opt_hdr *)(skb->data+ptr);
+
+ /* Calculate the header length */
+ if (nexthdr == NEXTHDR_FRAGMENT) {
+ hdrlen = 8;
+ } else if (nexthdr == NEXTHDR_AUTH)
+ hdrlen = (hdr->hdrlen+2)<<2;
+ else
+ hdrlen = ipv6_optlen(hdr);
+
+ /* FRAG -> evaluate */
+ if (nexthdr == NEXTHDR_FRAGMENT) {
+ temp |= MASK_FRAGMENT;
+ break;
+ }
+
+
+ /* set the flag */
+ switch (nexthdr){
+ case NEXTHDR_HOP:
+ case NEXTHDR_ROUTING:
+ case NEXTHDR_FRAGMENT:
+ case NEXTHDR_AUTH:
+ case NEXTHDR_DEST:
+ break;
+ default:
+ DEBUGP("ipv6_frag match: unknown nextheader %u\n",nexthdr);
+ return 0;
+ break;
+ }
+
+ nexthdr = hdr->nexthdr;
+ len -= hdrlen;
+ ptr += hdrlen;
+ if ( ptr > skb->len ) {
+ DEBUGP("ipv6_frag: new pointer too large! \n");
+ break;
+ }
+ }
+
+ /* FRAG header not found */
+ if ( temp != MASK_FRAGMENT ) return 0;
+
+ if (len < sizeof(struct frag_hdr)){
+ *hotdrop = 1;
+ return 0;
+ }
+
+ frag = (struct frag_hdr *) (skb->data + ptr);
+
+ DEBUGP("INFO %04X ", frag->frag_off);
+ DEBUGP("OFFSET %04X ", ntohs(frag->frag_off) & ~0x7);
+ DEBUGP("RES %02X %04X", frag->reserved, ntohs(frag->frag_off) & 0x6);
+ DEBUGP("MF %04X ", frag->frag_off & htons(IP6_MF));
+ DEBUGP("ID %u %08X\n", ntohl(frag->identification),
+ ntohl(frag->identification));
+
+ DEBUGP("IPv6 FRAG id %02X ",
+ (id_match(fraginfo->ids[0], fraginfo->ids[1],
+ ntohl(frag->identification),
+ !!(fraginfo->invflags & IP6T_FRAG_INV_IDS))));
+ DEBUGP("res %02X %02X%04X %02X ",
+ (fraginfo->flags & IP6T_FRAG_RES), frag->reserved,
+ ntohs(frag->frag_off) & 0x6,
+ !((fraginfo->flags & IP6T_FRAG_RES)
+ && (frag->reserved || (ntohs(frag->frag_off) & 0x6))));
+ DEBUGP("first %02X %02X %02X ",
+ (fraginfo->flags & IP6T_FRAG_FST),
+ ntohs(frag->frag_off) & ~0x7,
+ !((fraginfo->flags & IP6T_FRAG_FST)
+ && (ntohs(frag->frag_off) & ~0x7)));
+ DEBUGP("mf %02X %02X %02X ",
+ (fraginfo->flags & IP6T_FRAG_MF),
+ ntohs(frag->frag_off) & IP6_MF,
+ !((fraginfo->flags & IP6T_FRAG_MF)
+ && !((ntohs(frag->frag_off) & IP6_MF))));
+ DEBUGP("last %02X %02X %02X\n",
+ (fraginfo->flags & IP6T_FRAG_NMF),
+ ntohs(frag->frag_off) & IP6_MF,
+ !((fraginfo->flags & IP6T_FRAG_NMF)
+ && (ntohs(frag->frag_off) & IP6_MF)));
+
+ return (frag != NULL)
+ &&
+ (id_match(fraginfo->ids[0], fraginfo->ids[1],
+ ntohl(frag->identification),
+ !!(fraginfo->invflags & IP6T_FRAG_INV_IDS)))
+ &&
+ !((fraginfo->flags & IP6T_FRAG_RES)
+ && (frag->reserved || (ntohs(frag->frag_off) & 0x6)))
+ &&
+ !((fraginfo->flags & IP6T_FRAG_FST)
+ && (ntohs(frag->frag_off) & ~0x7))
+ &&
+ !((fraginfo->flags & IP6T_FRAG_MF)
+ && !(ntohs(frag->frag_off) & IP6_MF))
+ &&
+ !((fraginfo->flags & IP6T_FRAG_NMF)
+ && (ntohs(frag->frag_off) & IP6_MF));
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchinfosize,
+ unsigned int hook_mask)
+{
+ const struct ip6t_frag *fraginfo = matchinfo;
+
+ if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_frag))) {
+ DEBUGP("ip6t_frag: matchsize %u != %u\n",
+ matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_frag)));
+ return 0;
+ }
+ if (fraginfo->invflags & ~IP6T_FRAG_INV_MASK) {
+ DEBUGP("ip6t_frag: unknown flags %X\n",
+ fraginfo->invflags);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ip6t_match frag_match
+= { { NULL, NULL }, "frag", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ip6t_register_match(&frag_match);
+}
+
+static void __exit cleanup(void)
+{
+ ip6t_unregister_match(&frag_match);
+}
+
+module_init(init);
+module_exit(cleanup);
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_hbh.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_hbh.c
new file mode 100644
index 0000000..e41cdec
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_hbh.c
@@ -0,0 +1,278 @@
+/* Kernel module to match Hop-by-Hop and Destination parameters. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ipv6.h>
+#include <linux/types.h>
+#include <net/checksum.h>
+#include <net/ipv6.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_opts.h>
+
+#define HOPBYHOP 1
+
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
+#if HOPBYHOP
+MODULE_DESCRIPTION("IPv6 HbH match");
+#else
+MODULE_DESCRIPTION("IPv6 DST match");
+#endif
+MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+/*
+ * (Type & 0xC0) >> 6
+ * 0 -> ignorable
+ * 1 -> must drop the packet
+ * 2 -> send ICMP PARM PROB regardless and drop packet
+ * 3 -> Send ICMP if not a multicast address and drop packet
+ * (Type & 0x20) >> 5
+ * 0 -> invariant
+ * 1 -> can change the routing
+ * (Type & 0x1F) Type
+ * 0 -> Pad1 (only 1 byte!)
+ * 1 -> PadN LENGTH info (total length = length + 2)
+ * C0 | 2 -> JUMBO 4 x x x x ( xxxx > 64k )
+ * 5 -> RTALERT 2 x x
+ */
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *protohdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ struct ipv6_opt_hdr *optsh = NULL;
+ const struct ip6t_opts *optinfo = matchinfo;
+ unsigned int temp;
+ unsigned int len;
+ u8 nexthdr;
+ unsigned int ptr;
+ unsigned int hdrlen = 0;
+ unsigned int ret = 0;
+ u8 *opttype = NULL;
+ unsigned int optlen;
+
+ /* type of the 1st exthdr */
+ nexthdr = skb->nh.ipv6h->nexthdr;
+ /* pointer to the 1st exthdr */
+ ptr = sizeof(struct ipv6hdr);
+ /* available length */
+ len = skb->len - ptr;
+ temp = 0;
+
+ while (ipv6_ext_hdr(nexthdr)) {
+ struct ipv6_opt_hdr *hdr;
+
+ DEBUGP("ipv6_opts header iteration \n");
+
+ /* Is there enough space for the next ext header? */
+ if (len < (int)sizeof(struct ipv6_opt_hdr))
+ return 0;
+ /* No more exthdr -> evaluate */
+ if (nexthdr == NEXTHDR_NONE) {
+ break;
+ }
+ /* ESP -> evaluate */
+ if (nexthdr == NEXTHDR_ESP) {
+ break;
+ }
+
+ hdr = (void *)(skb->data + ptr);
+
+ /* Calculate the header length */
+ if (nexthdr == NEXTHDR_FRAGMENT) {
+ hdrlen = 8;
+ } else if (nexthdr == NEXTHDR_AUTH)
+ hdrlen = (hdr->hdrlen+2)<<2;
+ else
+ hdrlen = ipv6_optlen(hdr);
+
+ /* OPTS -> evaluate */
+#if HOPBYHOP
+ if (nexthdr == NEXTHDR_HOP) {
+ temp |= MASK_HOPOPTS;
+#else
+ if (nexthdr == NEXTHDR_DEST) {
+ temp |= MASK_DSTOPTS;
+#endif
+ break;
+ }
+
+
+ /* set the flag */
+ switch (nexthdr){
+ case NEXTHDR_HOP:
+ case NEXTHDR_ROUTING:
+ case NEXTHDR_FRAGMENT:
+ case NEXTHDR_AUTH:
+ case NEXTHDR_DEST:
+ break;
+ default:
+ DEBUGP("ipv6_opts match: unknown nextheader %u\n",nexthdr);
+ return 0;
+ break;
+ }
+
+ nexthdr = hdr->nexthdr;
+ len -= hdrlen;
+ ptr += hdrlen;
+ if ( ptr > skb->len ) {
+ DEBUGP("ipv6_opts: new pointer is too large! \n");
+ break;
+ }
+ }
+
+ /* OPTIONS header not found */
+#if HOPBYHOP
+ if ( temp != MASK_HOPOPTS ) return 0;
+#else
+ if ( temp != MASK_DSTOPTS ) return 0;
+#endif
+
+ if (len < (int)sizeof(struct ipv6_opt_hdr)){
+ *hotdrop = 1;
+ return 0;
+ }
+
+ if (len < hdrlen){
+ /* Packet smaller than it's length field */
+ return 0;
+ }
+
+ optsh = (void *)(skb->data + ptr);
+
+ DEBUGP("IPv6 OPTS LEN %u %u ", hdrlen, optsh->hdrlen);
+
+ DEBUGP("len %02X %04X %02X ",
+ optinfo->hdrlen, hdrlen,
+ (!(optinfo->flags & IP6T_OPTS_LEN) ||
+ ((optinfo->hdrlen == hdrlen) ^
+ !!(optinfo->invflags & IP6T_OPTS_INV_LEN))));
+
+ ret = (optsh != NULL)
+ &&
+ (!(optinfo->flags & IP6T_OPTS_LEN) ||
+ ((optinfo->hdrlen == hdrlen) ^
+ !!(optinfo->invflags & IP6T_OPTS_INV_LEN)));
+
+ ptr += 2;
+ hdrlen -= 2;
+ if ( !(optinfo->flags & IP6T_OPTS_OPTS) ){
+ return ret;
+ } else if (optinfo->flags & IP6T_OPTS_NSTRICT) {
+ DEBUGP("Not strict - not implemented");
+ } else {
+ DEBUGP("Strict ");
+ DEBUGP("#%d ",optinfo->optsnr);
+ for(temp=0; temp<optinfo->optsnr; temp++){
+ /* type field exists ? */
+ if (ptr > skb->len - 1 || hdrlen < 1)
+ break;
+ opttype = (void *)(skb->data + ptr);
+
+ /* Type check */
+ if (*opttype != (optinfo->opts[temp] & 0xFF00)>>8){
+ DEBUGP("Tbad %02X %02X\n",
+ *opttype,
+ (optinfo->opts[temp] & 0xFF00)>>8);
+ return 0;
+ } else {
+ DEBUGP("Tok ");
+ }
+ /* Length check */
+ if (*opttype) {
+ u16 spec_len;
+
+ /* length field exists ? */
+ if (ptr > skb->len - 2 || hdrlen < 2)
+ break;
+ optlen = *((u8 *)(skb->data + ptr + 1));
+ spec_len = optinfo->opts[temp] & 0x00FF;
+
+ if (spec_len != 0x00FF && spec_len != optlen) {
+ DEBUGP("Lbad %02X %04X\n", optlen,
+ spec_len);
+ return 0;
+ }
+ DEBUGP("Lok ");
+ optlen += 2;
+ } else {
+ DEBUGP("Pad1\n");
+ optlen = 1;
+ }
+
+ /* Step to the next */
+ DEBUGP("len%04X \n", optlen);
+
+ if ((ptr > skb->len - optlen || hdrlen < optlen) &&
+ (temp < optinfo->optsnr - 1)) {
+ DEBUGP("new pointer is too large! \n");
+ break;
+ }
+ ptr += optlen;
+ hdrlen -= optlen;
+ }
+ if (temp == optinfo->optsnr)
+ return ret;
+ else return 0;
+ }
+
+ return 0;
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchinfosize,
+ unsigned int hook_mask)
+{
+ const struct ip6t_opts *optsinfo = matchinfo;
+
+ if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_opts))) {
+ DEBUGP("ip6t_opts: matchsize %u != %u\n",
+ matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_opts)));
+ return 0;
+ }
+ if (optsinfo->invflags & ~IP6T_OPTS_INV_MASK) {
+ DEBUGP("ip6t_opts: unknown flags %X\n",
+ optsinfo->invflags);
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ip6t_match opts_match
+#if HOPBYHOP
+= { { NULL, NULL }, "hbh", &match, &checkentry, NULL, THIS_MODULE };
+#else
+= { { NULL, NULL }, "dst", &match, &checkentry, NULL, THIS_MODULE };
+#endif
+
+static int __init init(void)
+{
+ return ip6t_register_match(&opts_match);
+}
+
+static void __exit cleanup(void)
+{
+ ip6t_unregister_match(&opts_match);
+}
+
+module_init(init);
+module_exit(cleanup);
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_hl.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_hl.c
new file mode 100644
index 0000000..7a78025
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_hl.c
@@ -0,0 +1,74 @@
+/*
+ * Hop Limit matching module
+ * Maciej Soltysiak <solt@dns.toxicfilms.tv>
+ * Based on HW's ttl module
+ *
+ * This software is distributed under the terms GNU GPL
+ */
+
+#include <linux/module.h>
+#include <linux/skbuff.h>
+
+#include <linux/netfilter_ipv6/ip6t_hl.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+MODULE_AUTHOR("Maciej Soltysiak <solt@dns.toxicfilms.tv>");
+MODULE_DESCRIPTION("IP tables Hop Limit matching module");
+MODULE_LICENSE("GPL");
+
+static int match(const struct sk_buff *skb, const struct net_device *in,
+ const struct net_device *out, const void *matchinfo,
+ int offset, const void *hdr, u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ip6t_hl_info *info = matchinfo;
+ const struct ipv6hdr *ip6h = skb->nh.ipv6h;
+
+ switch (info->mode) {
+ case IP6T_HL_EQ:
+ return (ip6h->hop_limit == info->hop_limit);
+ break;
+ case IP6T_HL_NE:
+ return (!(ip6h->hop_limit == info->hop_limit));
+ break;
+ case IP6T_HL_LT:
+ return (ip6h->hop_limit < info->hop_limit);
+ break;
+ case IP6T_HL_GT:
+ return (ip6h->hop_limit > info->hop_limit);
+ break;
+ default:
+ printk(KERN_WARNING "ip6t_hl: unknown mode %d\n",
+ info->mode);
+ return 0;
+ }
+
+ return 0;
+}
+
+static int checkentry(const char *tablename, const struct ip6t_ip6 *ip,
+ void *matchinfo, unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_hl_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ip6t_match hl_match = { { NULL, NULL }, "hl", &match,
+ &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ip6t_register_match(&hl_match);
+}
+
+static void __exit fini(void)
+{
+ ip6t_unregister_match(&hl_match);
+
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_ipv6header.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_ipv6header.c
new file mode 100644
index 0000000..7a338c6
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_ipv6header.c
@@ -0,0 +1,164 @@
+/* ipv6header match - matches IPv6 packets based
+on whether they contain certain headers */
+
+/* Original idea: Brad Chapman
+ * Rewritten by: Andras Kis-Szabo <kisza@sch.bme.hu> */
+
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ipv6.h>
+#include <linux/types.h>
+#include <net/checksum.h>
+#include <net/ipv6.h>
+
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_ipv6header.h>
+
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("IPv6 headers match");
+MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
+
+static int
+ipv6header_match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *protohdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ip6t_ipv6header_info *info = matchinfo;
+ unsigned int temp;
+ int len;
+ u8 nexthdr;
+ unsigned int ptr;
+
+ /* Make sure this isn't an evil packet */
+
+ /* type of the 1st exthdr */
+ nexthdr = skb->nh.ipv6h->nexthdr;
+ /* pointer to the 1st exthdr */
+ ptr = sizeof(struct ipv6hdr);
+ /* available length */
+ len = skb->len - ptr;
+ temp = 0;
+
+ while (ipv6_ext_hdr(nexthdr)) {
+ struct ipv6_opt_hdr *hdr;
+ int hdrlen;
+
+ /* Is there enough space for the next ext header? */
+ if (len < (int)sizeof(struct ipv6_opt_hdr))
+ return 0;
+ /* No more exthdr -> evaluate */
+ if (nexthdr == NEXTHDR_NONE) {
+ temp |= MASK_NONE;
+ break;
+ }
+ /* ESP -> evaluate */
+ if (nexthdr == NEXTHDR_ESP) {
+ temp |= MASK_ESP;
+ break;
+ }
+
+ hdr=(struct ipv6_opt_hdr *)(skb->data+ptr);
+
+ /* Calculate the header length */
+ if (nexthdr == NEXTHDR_FRAGMENT) {
+ hdrlen = 8;
+ } else if (nexthdr == NEXTHDR_AUTH)
+ hdrlen = (hdr->hdrlen+2)<<2;
+ else
+ hdrlen = ipv6_optlen(hdr);
+
+ /* set the flag */
+ switch (nexthdr){
+ case NEXTHDR_HOP:
+ temp |= MASK_HOPOPTS;
+ break;
+ case NEXTHDR_ROUTING:
+ temp |= MASK_ROUTING;
+ break;
+ case NEXTHDR_FRAGMENT:
+ temp |= MASK_FRAGMENT;
+ break;
+ case NEXTHDR_AUTH:
+ temp |= MASK_AH;
+ break;
+ case NEXTHDR_DEST:
+ temp |= MASK_DSTOPTS;
+ break;
+ default:
+ return 0;
+ break;
+ }
+
+ nexthdr = hdr->nexthdr;
+ len -= hdrlen;
+ ptr += hdrlen;
+ if ( ptr > skb->len ) {
+ break;
+ }
+ }
+
+ if ( (nexthdr != NEXTHDR_NONE ) && (nexthdr != NEXTHDR_ESP) )
+ temp |= MASK_PROTO;
+
+ if (info->modeflag)
+ return !((temp ^ info->matchflags ^ info->invflags)
+ & info->matchflags);
+ else {
+ if (info->invflags)
+ return temp != info->matchflags;
+ else
+ return temp == info->matchflags;
+ }
+}
+
+static int
+ipv6header_checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ const struct ip6t_ipv6header_info *info = matchinfo;
+
+ /* Check for obvious errors */
+ /* This match is valid in all hooks! */
+ if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_ipv6header_info)))
+ return 0;
+
+ /* invflags is 0 or 0xff in hard mode */
+ if ((!info->modeflag) && info->invflags != 0x00
+ && info->invflags != 0xFF)
+ return 0;
+
+ return 1;
+}
+
+static struct ip6t_match
+ip6t_ipv6header_match = {
+ { NULL, NULL },
+ "ipv6header",
+ &ipv6header_match,
+ &ipv6header_checkentry,
+ NULL,
+ THIS_MODULE
+};
+
+static int __init ipv6header_init(void)
+{
+ return ip6t_register_match(&ip6t_ipv6header_match);
+}
+
+static void __exit ipv6header_exit(void)
+{
+ ip6t_unregister_match(&ip6t_ipv6header_match);
+}
+
+module_init(ipv6header_init);
+module_exit(ipv6header_exit);
+
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_length.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_length.c
new file mode 100644
index 0000000..3e6035d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_length.c
@@ -0,0 +1,51 @@
+/* Length Match - IPv6 Port */
+
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/netfilter_ipv6/ip6t_length.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ip6t_length_info *info = matchinfo;
+ u_int16_t pktlen = ntohs(skb->nh.ipv6h->payload_len) + sizeof(struct ipv6hdr);
+
+ return (pktlen >= info->min && pktlen <= info->max) ^ info->invert;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_length_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ip6t_match length_match
+= { { NULL, NULL }, "length", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ip6t_register_match(&length_match);
+}
+
+static void __exit fini(void)
+{
+ ip6t_unregister_match(&length_match);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_limit.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_limit.c
new file mode 100644
index 0000000..ab6aed1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_limit.c
@@ -0,0 +1,136 @@
+/* Kernel module to control the rate
+ *
+ * Jérôme de Vivie <devivie@info.enserb.u-bordeaux.fr>
+ * Hervé Eychenne <eychenne@info.enserb.u-bordeaux.fr>
+ *
+ * 2 September 1999: Changed from the target RATE to the match
+ * `limit', removed logging. Did I mention that
+ * Alexey is a fucking genius?
+ * Rusty Russell (rusty@rustcorp.com.au). */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_limit.h>
+
+/* The algorithm used is the Simple Token Bucket Filter (TBF)
+ * see net/sched/sch_tbf.c in the linux source tree
+ */
+
+static spinlock_t limit_lock = SPIN_LOCK_UNLOCKED;
+
+/* Rusty: This is my (non-mathematically-inclined) understanding of
+ this algorithm. The `average rate' in jiffies becomes your initial
+ amount of credit `credit' and the most credit you can ever have
+ `credit_cap'. The `peak rate' becomes the cost of passing the
+ test, `cost'.
+
+ `prev' tracks the last packet hit: you gain one credit per jiffy.
+ If you get credit balance more than this, the extra credit is
+ discarded. Every time the match passes, you lose `cost' credits;
+ if you don't have that many, the test fails.
+
+ See Alexey's formal explanation in net/sched/sch_tbf.c.
+
+ To avoid underflow, we multiply by 128 (ie. you get 128 credits per
+ jiffy). Hence a cost of 2^32-1, means one pass per 32768 seconds
+ at 1024HZ (or one every 9 hours). A cost of 1 means 12800 passes
+ per second at 100HZ. */
+
+#define CREDITS_PER_JIFFY 128
+
+static int
+ip6t_limit_match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ struct ip6t_rateinfo *r = ((struct ip6t_rateinfo *)matchinfo)->master;
+ unsigned long now = jiffies;
+
+ spin_lock_bh(&limit_lock);
+ r->credit += (now - xchg(&r->prev, now)) * CREDITS_PER_JIFFY;
+ if (r->credit > r->credit_cap)
+ r->credit = r->credit_cap;
+
+ if (r->credit >= r->cost) {
+ /* We're not limited. */
+ r->credit -= r->cost;
+ spin_unlock_bh(&limit_lock);
+ return 1;
+ }
+
+ spin_unlock_bh(&limit_lock);
+ return 0;
+}
+
+/* Precision saver. */
+static u_int32_t
+user2credits(u_int32_t user)
+{
+ /* If multiplying would overflow... */
+ if (user > 0xFFFFFFFF / (HZ*CREDITS_PER_JIFFY))
+ /* Divide first. */
+ return (user / IP6T_LIMIT_SCALE) * HZ * CREDITS_PER_JIFFY;
+
+ return (user * HZ * CREDITS_PER_JIFFY) / IP6T_LIMIT_SCALE;
+}
+
+static int
+ip6t_limit_checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ struct ip6t_rateinfo *r = matchinfo;
+
+ if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_rateinfo)))
+ return 0;
+
+ /* Check for overflow. */
+ if (r->burst == 0
+ || user2credits(r->avg * r->burst) < user2credits(r->avg)) {
+ printk("Call rusty: overflow in ip6t_limit: %u/%u\n",
+ r->avg, r->burst);
+ return 0;
+ }
+
+ /* User avg in seconds * IP6T_LIMIT_SCALE: convert to jiffies *
+ 128. */
+ r->prev = jiffies;
+ r->credit = user2credits(r->avg * r->burst); /* Credits full. */
+ r->credit_cap = user2credits(r->avg * r->burst); /* Credits full. */
+ r->cost = user2credits(r->avg);
+
+ /* For SMP, we only want to use one set of counters. */
+ r->master = r;
+
+ return 1;
+}
+
+static struct ip6t_match ip6t_limit_reg
+= { { NULL, NULL }, "limit", ip6t_limit_match, ip6t_limit_checkentry, NULL,
+ THIS_MODULE };
+
+static int __init init(void)
+{
+ if (ip6t_register_match(&ip6t_limit_reg))
+ return -EINVAL;
+ return 0;
+}
+
+static void __exit fini(void)
+{
+ ip6t_unregister_match(&ip6t_limit_reg);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_mac.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_mac.c
new file mode 100644
index 0000000..e4771d3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_mac.c
@@ -0,0 +1,66 @@
+/* Kernel module to match MAC address parameters. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/if_ether.h>
+
+#include <linux/netfilter_ipv6/ip6t_mac.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ip6t_mac_info *info = matchinfo;
+
+ /* Is mac pointer valid? */
+ return (skb->mac.raw >= skb->head
+ && (skb->mac.raw + ETH_HLEN) <= skb->data
+ /* If so, compare... */
+ && ((memcmp(skb->mac.ethernet->h_source, info->srcaddr, ETH_ALEN)
+ == 0) ^ info->invert));
+}
+
+static int
+ip6t_mac_checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (hook_mask
+ & ~((1 << NF_IP6_PRE_ROUTING) | (1 << NF_IP6_LOCAL_IN)
+ | (1 << NF_IP6_FORWARD))) {
+ printk("ip6t_mac: only valid for PRE_ROUTING, LOCAL_IN or"
+ " FORWARD\n");
+ return 0;
+ }
+
+ if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_mac_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ip6t_match mac_match
+= { { NULL, NULL }, "mac", &match, &ip6t_mac_checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ip6t_register_match(&mac_match);
+}
+
+static void __exit fini(void)
+{
+ ip6t_unregister_match(&mac_match);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MAC address matching module for IPv6");
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_mark.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_mark.c
new file mode 100644
index 0000000..6a7b61c
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_mark.c
@@ -0,0 +1,51 @@
+/* Kernel module to match NFMARK values. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+
+#include <linux/netfilter_ipv6/ip6t_mark.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ip6t_mark_info *info = matchinfo;
+
+ return ((skb->nfmark & info->mask) == info->mark) ^ info->invert;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_mark_info)))
+ return 0;
+
+ return 1;
+}
+
+static struct ip6t_match mark_match
+= { { NULL, NULL }, "mark", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ip6t_register_match(&mark_match);
+}
+
+static void __exit fini(void)
+{
+ ip6t_unregister_match(&mark_match);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_multiport.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_multiport.c
new file mode 100644
index 0000000..d6503ea
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_multiport.c
@@ -0,0 +1,104 @@
+/* Kernel module to match one of a list of TCP/UDP ports: ports are in
+ the same place so we can treat them as equal. */
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/udp.h>
+#include <linux/skbuff.h>
+#include <linux/in.h>
+
+#include <linux/netfilter_ipv6/ip6t_multiport.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+#if 0
+#define duprintf(format, args...) printk(format , ## args)
+#else
+#define duprintf(format, args...)
+#endif
+
+/* Returns 1 if the port is matched by the test, 0 otherwise. */
+static inline int
+ports_match(const u_int16_t *portlist, enum ip6t_multiport_flags flags,
+ u_int8_t count, u_int16_t src, u_int16_t dst)
+{
+ unsigned int i;
+ for (i=0; i<count; i++) {
+ if (flags != IP6T_MULTIPORT_DESTINATION
+ && portlist[i] == src)
+ return 1;
+
+ if (flags != IP6T_MULTIPORT_SOURCE
+ && portlist[i] == dst)
+ return 1;
+ }
+
+ return 0;
+}
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct udphdr *udp = hdr;
+ const struct ip6t_multiport *multiinfo = matchinfo;
+
+ /* Must be big enough to read ports. */
+ if (offset == 0 && datalen < sizeof(struct udphdr)) {
+ /* We've been asked to examine this packet, and we
+ can't. Hence, no choice but to drop. */
+ duprintf("ip6t_multiport:"
+ " Dropping evil offset=0 tinygram.\n");
+ *hotdrop = 1;
+ return 0;
+ }
+
+ /* Must not be a fragment. */
+ return !offset
+ && ports_match(multiinfo->ports,
+ multiinfo->flags, multiinfo->count,
+ ntohs(udp->source), ntohs(udp->dest));
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ const struct ip6t_multiport *multiinfo = matchinfo;
+
+ if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_multiport)))
+ return 0;
+
+ /* Must specify proto == TCP/UDP, no unknown flags or bad count */
+ return (ip->proto == IPPROTO_TCP || ip->proto == IPPROTO_UDP)
+ && !(ip->invflags & IP6T_INV_PROTO)
+ && matchsize == IP6T_ALIGN(sizeof(struct ip6t_multiport))
+ && (multiinfo->flags == IP6T_MULTIPORT_SOURCE
+ || multiinfo->flags == IP6T_MULTIPORT_DESTINATION
+ || multiinfo->flags == IP6T_MULTIPORT_EITHER)
+ && multiinfo->count <= IP6T_MULTI_PORTS;
+}
+
+static struct ip6t_match multiport_match
+= { { NULL, NULL }, "multiport", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ip6t_register_match(&multiport_match);
+}
+
+static void __exit fini(void)
+{
+ ip6t_unregister_match(&multiport_match);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_owner.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_owner.c
new file mode 100644
index 0000000..fc0186c
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_owner.c
@@ -0,0 +1,165 @@
+/* Kernel module to match various things tied to sockets associated with
+ locally generated outgoing packets.
+
+ Copyright (C) 2000,2001 Marc Boucher
+ */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/file.h>
+#include <net/sock.h>
+
+#include <linux/netfilter_ipv6/ip6t_owner.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+MODULE_AUTHOR("Marc Boucher <marc@mbsi.ca>");
+MODULE_DESCRIPTION("IP6 tables owner matching module");
+MODULE_LICENSE("GPL");
+
+static int
+match_pid(const struct sk_buff *skb, pid_t pid)
+{
+ struct task_struct *p;
+ struct files_struct *files;
+ int i;
+
+ read_lock(&tasklist_lock);
+ p = find_task_by_pid(pid);
+ if (!p)
+ goto out;
+ task_lock(p);
+ files = p->files;
+ if(files) {
+ read_lock(&files->file_lock);
+ for (i=0; i < files->max_fds; i++) {
+ if (fcheck_files(files, i) == skb->sk->socket->file) {
+ read_unlock(&files->file_lock);
+ task_unlock(p);
+ read_unlock(&tasklist_lock);
+ return 1;
+ }
+ }
+ read_unlock(&files->file_lock);
+ }
+ task_unlock(p);
+out:
+ read_unlock(&tasklist_lock);
+ return 0;
+}
+
+static int
+match_sid(const struct sk_buff *skb, pid_t sid)
+{
+ struct task_struct *p;
+ struct file *file = skb->sk->socket->file;
+ int i, found=0;
+
+ read_lock(&tasklist_lock);
+ for_each_task(p) {
+ struct files_struct *files;
+ if (p->session != sid)
+ continue;
+
+ task_lock(p);
+ files = p->files;
+ if (files) {
+ read_lock(&files->file_lock);
+ for (i=0; i < files->max_fds; i++) {
+ if (fcheck_files(files, i) == file) {
+ found = 1;
+ break;
+ }
+ }
+ read_unlock(&files->file_lock);
+ }
+ task_unlock(p);
+ if(found)
+ break;
+ }
+ read_unlock(&tasklist_lock);
+
+ return found;
+}
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *hdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ const struct ip6t_owner_info *info = matchinfo;
+
+ if (!skb->sk || !skb->sk->socket || !skb->sk->socket->file)
+ return 0;
+
+ if(info->match & IP6T_OWNER_UID) {
+ if((skb->sk->socket->file->f_uid != info->uid) ^
+ !!(info->invert & IP6T_OWNER_UID))
+ return 0;
+ }
+
+ if(info->match & IP6T_OWNER_GID) {
+ if((skb->sk->socket->file->f_gid != info->gid) ^
+ !!(info->invert & IP6T_OWNER_GID))
+ return 0;
+ }
+
+ if(info->match & IP6T_OWNER_PID) {
+ if (!match_pid(skb, info->pid) ^
+ !!(info->invert & IP6T_OWNER_PID))
+ return 0;
+ }
+
+ if(info->match & IP6T_OWNER_SID) {
+ if (!match_sid(skb, info->sid) ^
+ !!(info->invert & IP6T_OWNER_SID))
+ return 0;
+ }
+
+ return 1;
+}
+
+static int
+checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchsize,
+ unsigned int hook_mask)
+{
+ if (hook_mask
+ & ~((1 << NF_IP6_LOCAL_OUT) | (1 << NF_IP6_POST_ROUTING))) {
+ printk("ip6t_owner: only valid for LOCAL_OUT or POST_ROUTING.\n");
+ return 0;
+ }
+
+ if (matchsize != IP6T_ALIGN(sizeof(struct ip6t_owner_info)))
+ return 0;
+#ifdef CONFIG_SMP
+ /* files->file_lock can not be used in a BH */
+ if (((struct ip6t_owner_info *)matchinfo)->match
+ & (IP6T_OWNER_PID|IP6T_OWNER_SID)) {
+ printk("ip6t_owner: pid and sid matching is broken on SMP.\n");
+ return 0;
+ }
+#endif
+ return 1;
+}
+
+static struct ip6t_match owner_match
+= { { NULL, NULL }, "owner", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ip6t_register_match(&owner_match);
+}
+
+static void __exit fini(void)
+{
+ ip6t_unregister_match(&owner_match);
+}
+
+module_init(init);
+module_exit(fini);
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_rt.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_rt.c
new file mode 100644
index 0000000..542bc3d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6t_rt.c
@@ -0,0 +1,294 @@
+/* Kernel module to match ROUTING parameters. */
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/ipv6.h>
+#include <linux/types.h>
+#include <net/checksum.h>
+#include <net/ipv6.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <linux/netfilter_ipv6/ip6t_rt.h>
+
+EXPORT_NO_SYMBOLS;
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("IPv6 RT match");
+MODULE_AUTHOR("Andras Kis-Szabo <kisza@sch.bme.hu>");
+
+#if 0
+#define DEBUGP printk
+#else
+#define DEBUGP(format, args...)
+#endif
+
+/* Returns 1 if the id is matched by the range, 0 otherwise */
+static inline int
+segsleft_match(u_int32_t min, u_int32_t max, u_int32_t id, int invert)
+{
+ int r=0;
+ DEBUGP("rt segsleft_match:%c 0x%x <= 0x%x <= 0x%x",invert? '!':' ',
+ min,id,max);
+ r=(id >= min && id <= max) ^ invert;
+ DEBUGP(" result %s\n",r? "PASS" : "FAILED");
+ return r;
+}
+
+static int
+match(const struct sk_buff *skb,
+ const struct net_device *in,
+ const struct net_device *out,
+ const void *matchinfo,
+ int offset,
+ const void *protohdr,
+ u_int16_t datalen,
+ int *hotdrop)
+{
+ struct ipv6_rt_hdr *route = NULL;
+ const struct ip6t_rt *rtinfo = matchinfo;
+ unsigned int temp;
+ unsigned int len;
+ u8 nexthdr;
+ unsigned int ptr;
+ unsigned int hdrlen = 0;
+ unsigned int ret = 0;
+
+ /* type of the 1st exthdr */
+ nexthdr = skb->nh.ipv6h->nexthdr;
+ /* pointer to the 1st exthdr */
+ ptr = sizeof(struct ipv6hdr);
+ /* available length */
+ len = skb->len - ptr;
+ temp = 0;
+
+ while (ipv6_ext_hdr(nexthdr)) {
+ struct ipv6_opt_hdr *hdr;
+
+ DEBUGP("ipv6_rt header iteration \n");
+
+ /* Is there enough space for the next ext header? */
+ if (len < (int)sizeof(struct ipv6_opt_hdr))
+ return 0;
+ /* No more exthdr -> evaluate */
+ if (nexthdr == NEXTHDR_NONE) {
+ break;
+ }
+ /* ESP -> evaluate */
+ if (nexthdr == NEXTHDR_ESP) {
+ break;
+ }
+
+ hdr=(struct ipv6_opt_hdr *)(skb->data+ptr);
+
+ /* Calculate the header length */
+ if (nexthdr == NEXTHDR_FRAGMENT) {
+ hdrlen = 8;
+ } else if (nexthdr == NEXTHDR_AUTH)
+ hdrlen = (hdr->hdrlen+2)<<2;
+ else
+ hdrlen = ipv6_optlen(hdr);
+
+ /* ROUTING -> evaluate */
+ if (nexthdr == NEXTHDR_ROUTING) {
+ temp |= MASK_ROUTING;
+ break;
+ }
+
+
+ /* set the flag */
+ switch (nexthdr){
+ case NEXTHDR_HOP:
+ case NEXTHDR_ROUTING:
+ case NEXTHDR_FRAGMENT:
+ case NEXTHDR_AUTH:
+ case NEXTHDR_DEST:
+ break;
+ default:
+ DEBUGP("ipv6_rt match: unknown nextheader %u\n",nexthdr);
+ return 0;
+ break;
+ }
+
+ nexthdr = hdr->nexthdr;
+ len -= hdrlen;
+ ptr += hdrlen;
+ if ( ptr > skb->len ) {
+ DEBUGP("ipv6_rt: new pointer is too large! \n");
+ break;
+ }
+ }
+
+ /* ROUTING header not found */
+ if ( temp != MASK_ROUTING ) return 0;
+
+ if (len < (int)sizeof(struct ipv6_rt_hdr)){
+ *hotdrop = 1;
+ return 0;
+ }
+
+ if (len < hdrlen){
+ /* Pcket smaller than its length field */
+ return 0;
+ }
+
+ route = (struct ipv6_rt_hdr *) (skb->data + ptr);
+
+ DEBUGP("IPv6 RT LEN %u %u ", hdrlen, route->hdrlen);
+ DEBUGP("TYPE %04X ", route->type);
+ DEBUGP("SGS_LEFT %u %02X\n", route->segments_left, route->segments_left);
+
+ DEBUGP("IPv6 RT segsleft %02X ",
+ (segsleft_match(rtinfo->segsleft[0], rtinfo->segsleft[1],
+ route->segments_left,
+ !!(rtinfo->invflags & IP6T_RT_INV_SGS))));
+ DEBUGP("type %02X %02X %02X ",
+ rtinfo->rt_type, route->type,
+ (!(rtinfo->flags & IP6T_RT_TYP) ||
+ ((rtinfo->rt_type == route->type) ^
+ !!(rtinfo->invflags & IP6T_RT_INV_TYP))));
+ DEBUGP("len %02X %04X %02X ",
+ rtinfo->hdrlen, hdrlen,
+ (!(rtinfo->flags & IP6T_RT_LEN) ||
+ ((rtinfo->hdrlen == hdrlen) ^
+ !!(rtinfo->invflags & IP6T_RT_INV_LEN))));
+ DEBUGP("res %02X %02X %02X ",
+ (rtinfo->flags & IP6T_RT_RES), ((struct rt0_hdr *)route)->reserved,
+ !((rtinfo->flags & IP6T_RT_RES) && (((struct rt0_hdr *)route)->reserved)));
+
+ ret = (route != NULL)
+ &&
+ (segsleft_match(rtinfo->segsleft[0], rtinfo->segsleft[1],
+ route->segments_left,
+ !!(rtinfo->invflags & IP6T_RT_INV_SGS)))
+ &&
+ (!(rtinfo->flags & IP6T_RT_LEN) ||
+ ((rtinfo->hdrlen == hdrlen) ^
+ !!(rtinfo->invflags & IP6T_RT_INV_LEN)))
+ &&
+ (!(rtinfo->flags & IP6T_RT_TYP) ||
+ ((rtinfo->rt_type == route->type) ^
+ !!(rtinfo->invflags & IP6T_RT_INV_TYP)))
+ &&
+ !((rtinfo->flags & IP6T_RT_RES) && (((struct rt0_hdr *)route)->reserved));
+
+ DEBUGP("#%d ",rtinfo->addrnr);
+ temp = len = ptr = 0;
+ if ( !(rtinfo->flags & IP6T_RT_FST) ){
+ return ret;
+ } else if (rtinfo->flags & IP6T_RT_FST_NSTRICT) {
+ DEBUGP("Not strict ");
+ if ( rtinfo->addrnr > (unsigned int)((hdrlen-8)/16) ){
+ DEBUGP("There isn't enough space\n");
+ return 0;
+ } else {
+ DEBUGP("#%d ",rtinfo->addrnr);
+ ptr = 0;
+ for(temp=0; temp<(unsigned int)((hdrlen-8)/16); temp++){
+ len = 0;
+ while ((u8)(((struct rt0_hdr *)route)->
+ addr[temp].s6_addr[len]) ==
+ (u8)(rtinfo->addrs[ptr].s6_addr[len])){
+ DEBUGP("%02X?%02X ",
+ (u8)(((struct rt0_hdr *)route)->addr[temp].s6_addr[len]),
+ (u8)(rtinfo->addrs[ptr].s6_addr[len]));
+ len++;
+ if ( len == 16 ) break;
+ }
+ if (len==16) {
+ DEBUGP("ptr=%d temp=%d;\n",ptr,temp);
+ ptr++;
+ } else {
+ DEBUGP("%02X?%02X ",
+ (u8)(((struct rt0_hdr *)route)->addr[temp].s6_addr[len]),
+ (u8)(rtinfo->addrs[ptr].s6_addr[len]));
+ DEBUGP("!ptr=%d temp=%d;\n",ptr,temp);
+ }
+ if (ptr==rtinfo->addrnr) break;
+ }
+ DEBUGP("ptr=%d len=%d #%d\n",ptr,len, rtinfo->addrnr);
+ if ( (len == 16) && (ptr == rtinfo->addrnr))
+ return ret;
+ else return 0;
+ }
+ } else {
+ DEBUGP("Strict ");
+ if ( rtinfo->addrnr > (unsigned int)((hdrlen-8)/16) ){
+ DEBUGP("There isn't enough space\n");
+ return 0;
+ } else {
+ DEBUGP("#%d ",rtinfo->addrnr);
+ for(temp=0; temp<rtinfo->addrnr; temp++){
+ len = 0;
+ while ((u8)(((struct rt0_hdr *)route)->
+ addr[temp].s6_addr[len]) ==
+ (u8)(rtinfo->addrs[temp].s6_addr[len])){
+ DEBUGP("%02X?%02X ",
+ (u8)(((struct rt0_hdr *)route)->addr[temp].s6_addr[len]),
+ (u8)(rtinfo->addrs[temp].s6_addr[len]));
+ len++;
+ if ( len == 16 ) break;
+ }
+ if (len!=16) {
+ DEBUGP("%02X?%02X ",
+ (u8)(((struct rt0_hdr *)route)->addr[temp].s6_addr[len]),
+ (u8)(rtinfo->addrs[temp].s6_addr[len]));
+ DEBUGP("!len=%d temp=%d;\n",len,temp);
+ break;
+ }
+ }
+ DEBUGP("temp=%d len=%d #%d\n",temp,len,rtinfo->addrnr);
+ if ( (len == 16) && (temp == rtinfo->addrnr) && (temp == (unsigned int)((hdrlen-8)/16)))
+ return ret;
+ else return 0;
+ }
+ }
+
+ return 0;
+}
+
+/* Called when user tries to insert an entry of this type. */
+static int
+checkentry(const char *tablename,
+ const struct ip6t_ip6 *ip,
+ void *matchinfo,
+ unsigned int matchinfosize,
+ unsigned int hook_mask)
+{
+ const struct ip6t_rt *rtinfo = matchinfo;
+
+ if (matchinfosize != IP6T_ALIGN(sizeof(struct ip6t_rt))) {
+ DEBUGP("ip6t_rt: matchsize %u != %u\n",
+ matchinfosize, IP6T_ALIGN(sizeof(struct ip6t_rt)));
+ return 0;
+ }
+ if (rtinfo->invflags & ~IP6T_RT_INV_MASK) {
+ DEBUGP("ip6t_rt: unknown flags %X\n",
+ rtinfo->invflags);
+ return 0;
+ }
+ if ( (rtinfo->flags & (IP6T_RT_RES|IP6T_RT_FST_MASK)) &&
+ (!(rtinfo->flags & IP6T_RT_TYP) ||
+ (rtinfo->rt_type != 0) ||
+ (rtinfo->invflags & IP6T_RT_INV_TYP)) ) {
+ DEBUGP("`--rt-type 0' required before `--rt-0-*'");
+ return 0;
+ }
+
+ return 1;
+}
+
+static struct ip6t_match rt_match
+= { { NULL, NULL }, "rt", &match, &checkentry, NULL, THIS_MODULE };
+
+static int __init init(void)
+{
+ return ip6t_register_match(&rt_match);
+}
+
+static void __exit cleanup(void)
+{
+ ip6t_unregister_match(&rt_match);
+}
+
+module_init(init);
+module_exit(cleanup);
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6table_filter.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6table_filter.c
new file mode 100644
index 0000000..34913a8
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6table_filter.c
@@ -0,0 +1,184 @@
+/*
+ * This is the 1999 rewrite of IP Firewalling, aiming for kernel 2.3.x.
+ *
+ * Copyright (C) 1999 Paul `Rusty' Russell & Michael J. Neuling
+ */
+#include <linux/module.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+#define FILTER_VALID_HOOKS ((1 << NF_IP6_LOCAL_IN) | (1 << NF_IP6_FORWARD) | (1 << NF_IP6_LOCAL_OUT))
+
+/* Standard entry. */
+struct ip6t_standard
+{
+ struct ip6t_entry entry;
+ struct ip6t_standard_target target;
+};
+
+struct ip6t_error_target
+{
+ struct ip6t_entry_target target;
+ char errorname[IP6T_FUNCTION_MAXNAMELEN];
+};
+
+struct ip6t_error
+{
+ struct ip6t_entry entry;
+ struct ip6t_error_target target;
+};
+
+static struct
+{
+ struct ip6t_replace repl;
+ struct ip6t_standard entries[3];
+ struct ip6t_error term;
+} initial_table __initdata
+= { { "filter", FILTER_VALID_HOOKS, 4,
+ sizeof(struct ip6t_standard) * 3 + sizeof(struct ip6t_error),
+ { [NF_IP6_LOCAL_IN] 0,
+ [NF_IP6_FORWARD] sizeof(struct ip6t_standard),
+ [NF_IP6_LOCAL_OUT] sizeof(struct ip6t_standard) * 2 },
+ { [NF_IP6_LOCAL_IN] 0,
+ [NF_IP6_FORWARD] sizeof(struct ip6t_standard),
+ [NF_IP6_LOCAL_OUT] sizeof(struct ip6t_standard) * 2 },
+ 0, NULL, { } },
+ {
+ /* LOCAL_IN */
+ { { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ip6t_entry),
+ sizeof(struct ip6t_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ /* FORWARD */
+ { { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ip6t_entry),
+ sizeof(struct ip6t_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ /* LOCAL_OUT */
+ { { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ip6t_entry),
+ sizeof(struct ip6t_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } }
+ },
+ /* ERROR */
+ { { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ip6t_entry),
+ sizeof(struct ip6t_error),
+ 0, { 0, 0 }, { } },
+ { { { { IP6T_ALIGN(sizeof(struct ip6t_error_target)), IP6T_ERROR_TARGET } },
+ { } },
+ "ERROR"
+ }
+ }
+};
+
+static struct ip6t_table packet_filter
+= { { NULL, NULL }, "filter", &initial_table.repl,
+ FILTER_VALID_HOOKS, RW_LOCK_UNLOCKED, NULL, THIS_MODULE };
+
+/* The work comes in here from netfilter.c. */
+static unsigned int
+ip6t_hook(unsigned int hook,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ return ip6t_do_table(pskb, hook, in, out, &packet_filter, NULL);
+}
+
+static unsigned int
+ip6t_local_out_hook(unsigned int hook,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+#if 0
+ /* root is playing with raw sockets. */
+ if ((*pskb)->len < sizeof(struct iphdr)
+ || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr)) {
+ if (net_ratelimit())
+ printk("ip6t_hook: happy cracking.\n");
+ return NF_ACCEPT;
+ }
+#endif
+
+ return ip6t_do_table(pskb, hook, in, out, &packet_filter, NULL);
+}
+
+static struct nf_hook_ops ip6t_ops[]
+= { { { NULL, NULL }, ip6t_hook, PF_INET6, NF_IP6_LOCAL_IN, NF_IP6_PRI_FILTER },
+ { { NULL, NULL }, ip6t_hook, PF_INET6, NF_IP6_FORWARD, NF_IP6_PRI_FILTER },
+ { { NULL, NULL }, ip6t_local_out_hook, PF_INET6, NF_IP6_LOCAL_OUT,
+ NF_IP6_PRI_FILTER }
+};
+
+/* Default to forward because I got too much mail already. */
+static int forward = NF_ACCEPT;
+MODULE_PARM(forward, "i");
+
+static int __init init(void)
+{
+ int ret;
+
+ if (forward < 0 || forward > NF_MAX_VERDICT) {
+ printk("iptables forward must be 0 or 1\n");
+ return -EINVAL;
+ }
+
+ /* Entry 1 is the FORWARD hook */
+ initial_table.entries[1].target.verdict = -forward - 1;
+
+ /* Register table */
+ ret = ip6t_register_table(&packet_filter);
+ if (ret < 0)
+ return ret;
+
+ /* Register hooks */
+ ret = nf_register_hook(&ip6t_ops[0]);
+ if (ret < 0)
+ goto cleanup_table;
+
+ ret = nf_register_hook(&ip6t_ops[1]);
+ if (ret < 0)
+ goto cleanup_hook0;
+
+ ret = nf_register_hook(&ip6t_ops[2]);
+ if (ret < 0)
+ goto cleanup_hook1;
+
+ return ret;
+
+ cleanup_hook1:
+ nf_unregister_hook(&ip6t_ops[1]);
+ cleanup_hook0:
+ nf_unregister_hook(&ip6t_ops[0]);
+ cleanup_table:
+ ip6t_unregister_table(&packet_filter);
+
+ return ret;
+}
+
+static void __exit fini(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < sizeof(ip6t_ops)/sizeof(struct nf_hook_ops); i++)
+ nf_unregister_hook(&ip6t_ops[i]);
+
+ ip6t_unregister_table(&packet_filter);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6table_mangle.c b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6table_mangle.c
new file mode 100644
index 0000000..22db920
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/netfilter/ip6table_mangle.c
@@ -0,0 +1,244 @@
+/*
+ * IPv6 packet mangling table, a port of the IPv4 mangle table to IPv6
+ *
+ * Copyright (C) 2000-2001 by Harald Welte <laforge@gnumonks.org>
+ */
+#include <linux/module.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+
+#define MANGLE_VALID_HOOKS ((1 << NF_IP6_PRE_ROUTING) | \
+ (1 << NF_IP6_LOCAL_IN) | \
+ (1 << NF_IP6_FORWARD) | \
+ (1 << NF_IP6_LOCAL_OUT) | \
+ (1 << NF_IP6_POST_ROUTING))
+
+#if 0
+#define DEBUGP(x, args...) printk(KERN_DEBUG x, ## args)
+#else
+#define DEBUGP(x, args...)
+#endif
+
+/* Standard entry. */
+struct ip6t_standard
+{
+ struct ip6t_entry entry;
+ struct ip6t_standard_target target;
+};
+
+struct ip6t_error_target
+{
+ struct ip6t_entry_target target;
+ char errorname[IP6T_FUNCTION_MAXNAMELEN];
+};
+
+struct ip6t_error
+{
+ struct ip6t_entry entry;
+ struct ip6t_error_target target;
+};
+
+static struct
+{
+ struct ip6t_replace repl;
+ struct ip6t_standard entries[5];
+ struct ip6t_error term;
+} initial_table __initdata
+= { { "mangle", MANGLE_VALID_HOOKS, 6,
+ sizeof(struct ip6t_standard) * 5 + sizeof(struct ip6t_error),
+ { [NF_IP6_PRE_ROUTING] 0,
+ [NF_IP6_LOCAL_IN] sizeof(struct ip6t_standard),
+ [NF_IP6_FORWARD] sizeof(struct ip6t_standard) * 2,
+ [NF_IP6_LOCAL_OUT] sizeof(struct ip6t_standard) * 3,
+ [NF_IP6_POST_ROUTING] sizeof(struct ip6t_standard) * 4},
+ { [NF_IP6_PRE_ROUTING] 0,
+ [NF_IP6_LOCAL_IN] sizeof(struct ip6t_standard),
+ [NF_IP6_FORWARD] sizeof(struct ip6t_standard) * 2,
+ [NF_IP6_LOCAL_OUT] sizeof(struct ip6t_standard) * 3,
+ [NF_IP6_POST_ROUTING] sizeof(struct ip6t_standard) * 4},
+ 0, NULL, { } },
+ {
+ /* PRE_ROUTING */
+ { { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ip6t_entry),
+ sizeof(struct ip6t_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ /* LOCAL_IN */
+ { { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ip6t_entry),
+ sizeof(struct ip6t_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ /* FORWARD */
+ { { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ip6t_entry),
+ sizeof(struct ip6t_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ /* LOCAL_OUT */
+ { { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ip6t_entry),
+ sizeof(struct ip6t_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } },
+ /* POST_ROUTING */
+ { { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ip6t_entry),
+ sizeof(struct ip6t_standard),
+ 0, { 0, 0 }, { } },
+ { { { { IP6T_ALIGN(sizeof(struct ip6t_standard_target)), "" } }, { } },
+ -NF_ACCEPT - 1 } }
+ },
+ /* ERROR */
+ { { { { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, { { { 0 } } }, "", "", { 0 }, { 0 }, 0, 0, 0 },
+ 0,
+ sizeof(struct ip6t_entry),
+ sizeof(struct ip6t_error),
+ 0, { 0, 0 }, { } },
+ { { { { IP6T_ALIGN(sizeof(struct ip6t_error_target)), IP6T_ERROR_TARGET } },
+ { } },
+ "ERROR"
+ }
+ }
+};
+
+static struct ip6t_table packet_mangler
+= { { NULL, NULL }, "mangle", &initial_table.repl,
+ MANGLE_VALID_HOOKS, RW_LOCK_UNLOCKED, NULL, THIS_MODULE };
+
+/* The work comes in here from netfilter.c. */
+static unsigned int
+ip6t_route_hook(unsigned int hook,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+ return ip6t_do_table(pskb, hook, in, out, &packet_mangler, NULL);
+}
+
+static unsigned int
+ip6t_local_hook(unsigned int hook,
+ struct sk_buff **pskb,
+ const struct net_device *in,
+ const struct net_device *out,
+ int (*okfn)(struct sk_buff *))
+{
+
+ unsigned long nfmark;
+ unsigned int ret;
+ struct in6_addr saddr, daddr;
+ u_int8_t hop_limit;
+ u_int32_t flowlabel;
+
+#if 0
+ /* root is playing with raw sockets. */
+ if ((*pskb)->len < sizeof(struct iphdr)
+ || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr)) {
+ if (net_ratelimit())
+ printk("ip6t_hook: happy cracking.\n");
+ return NF_ACCEPT;
+ }
+#endif
+
+ /* save source/dest address, nfmark, hoplimit, flowlabel, priority, */
+ memcpy(&saddr, &(*pskb)->nh.ipv6h->saddr, sizeof(saddr));
+ memcpy(&daddr, &(*pskb)->nh.ipv6h->daddr, sizeof(daddr));
+ nfmark = (*pskb)->nfmark;
+ hop_limit = (*pskb)->nh.ipv6h->hop_limit;
+
+ /* flowlabel and prio (includes version, which shouldn't change either */
+ flowlabel = *((u_int32_t *) (*pskb)->nh.ipv6h);
+
+ ret = ip6t_do_table(pskb, hook, in, out, &packet_mangler, NULL);
+
+ if (ret != NF_DROP && ret != NF_STOLEN
+ && (memcmp(&(*pskb)->nh.ipv6h->saddr, &saddr, sizeof(saddr))
+ || memcmp(&(*pskb)->nh.ipv6h->daddr, &daddr, sizeof(daddr))
+ || (*pskb)->nfmark != nfmark
+ || (*pskb)->nh.ipv6h->hop_limit != hop_limit)) {
+
+ /* something which could affect routing has changed */
+
+ DEBUGP("ip6table_mangle: we'd need to re-route a packet\n");
+ }
+
+ return ret;
+}
+
+static struct nf_hook_ops ip6t_ops[]
+= { { { NULL, NULL }, ip6t_route_hook, PF_INET6, NF_IP6_PRE_ROUTING, NF_IP6_PRI_MANGLE },
+ { { NULL, NULL }, ip6t_local_hook, PF_INET6, NF_IP6_LOCAL_IN, NF_IP6_PRI_MANGLE },
+ { { NULL, NULL }, ip6t_route_hook, PF_INET6, NF_IP6_FORWARD, NF_IP6_PRI_MANGLE },
+ { { NULL, NULL }, ip6t_local_hook, PF_INET6, NF_IP6_LOCAL_OUT, NF_IP6_PRI_MANGLE },
+ { { NULL, NULL }, ip6t_route_hook, PF_INET6, NF_IP6_POST_ROUTING, NF_IP6_PRI_MANGLE }
+};
+
+static int __init init(void)
+{
+ int ret;
+
+ /* Register table */
+ ret = ip6t_register_table(&packet_mangler);
+ if (ret < 0)
+ return ret;
+
+ /* Register hooks */
+ ret = nf_register_hook(&ip6t_ops[0]);
+ if (ret < 0)
+ goto cleanup_table;
+
+ ret = nf_register_hook(&ip6t_ops[1]);
+ if (ret < 0)
+ goto cleanup_hook0;
+
+ ret = nf_register_hook(&ip6t_ops[2]);
+ if (ret < 0)
+ goto cleanup_hook1;
+
+ ret = nf_register_hook(&ip6t_ops[3]);
+ if (ret < 0)
+ goto cleanup_hook2;
+
+ ret = nf_register_hook(&ip6t_ops[4]);
+ if (ret < 0)
+ goto cleanup_hook3;
+
+ return ret;
+
+ cleanup_hook3:
+ nf_unregister_hook(&ip6t_ops[3]);
+ cleanup_hook2:
+ nf_unregister_hook(&ip6t_ops[2]);
+ cleanup_hook1:
+ nf_unregister_hook(&ip6t_ops[1]);
+ cleanup_hook0:
+ nf_unregister_hook(&ip6t_ops[0]);
+ cleanup_table:
+ ip6t_unregister_table(&packet_mangler);
+
+ return ret;
+}
+
+static void __exit fini(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < sizeof(ip6t_ops)/sizeof(struct nf_hook_ops); i++)
+ nf_unregister_hook(&ip6t_ops[i]);
+
+ ip6t_unregister_table(&packet_mangler);
+}
+
+module_init(init);
+module_exit(fini);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipv6/proc.c b/uClinux-2.4.31-uc0/net/ipv6/proc.c
new file mode 100644
index 0000000..0472753
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/proc.c
@@ -0,0 +1,228 @@
+/* $USAGI: proc.c,v 1.16 2003/06/26 13:54:30 yoshfuji Exp $ */
+
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * This file implements the various access functions for the
+ * PROC file system. This is very similar to the IPv4 version,
+ * except it reports the sockets in the INET6 address family.
+ *
+ * Version: $Id: proc.c,v 1.15.2.1 2002/01/24 15:46:07 davem Exp $
+ *
+ * Authors: David S. Miller (davem@caip.rutgers.edu)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+#include <linux/sched.h>
+#include <linux/socket.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/in6.h>
+#include <linux/stddef.h>
+#include <net/sock.h>
+#include <net/tcp.h>
+#include <net/transp_v6.h>
+#include <net/ipv6.h>
+#include <net/addrconf.h>
+#include <linux/usagi-version.h>
+
+static int fold_prot_inuse(struct proto *proto)
+{
+ int res = 0;
+ int cpu;
+
+ for (cpu=0; cpu<smp_num_cpus; cpu++)
+ res += proto->stats[cpu_logical_map(cpu)].inuse;
+
+ return res;
+}
+
+int afinet6_getversion(char *buffer, char **start, off_t offset, int length)
+{
+ int len = 0;
+ len += sprintf(buffer+len, "%s\n", USAGI_RELEASE);
+ *start = buffer + offset;
+ len -= offset;
+ if (len > length)
+ len = length;
+ return len;
+}
+
+int afinet6_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int len = 0;
+ len += sprintf(buffer+len, "TCP6: inuse %d\n",
+ fold_prot_inuse(&tcpv6_prot));
+ len += sprintf(buffer+len, "UDP6: inuse %d\n",
+ fold_prot_inuse(&udpv6_prot));
+ len += sprintf(buffer+len, "RAW6: inuse %d\n",
+ fold_prot_inuse(&rawv6_prot));
+ len += sprintf(buffer+len, "FRAG6: inuse %d memory %d\n",
+ ip6_frag_nqueues, atomic_read(&ip6_frag_mem));
+ *start = buffer + offset;
+ len -= offset;
+ if(len > length)
+ len = length;
+ return len;
+}
+
+#define OFFSETOF(s,m) ((size_t)(&((s *)NULL)->m))
+
+struct snmp6_item
+{
+ char *name;
+ size_t offset;
+} snmp6_list_ipv6[] = {
+/* ipv6 mib according to RFC2465 */
+#define SNMP6_GEN(x) { #x , OFFSETOF(struct ipv6_mib, x) }
+ SNMP6_GEN(Ip6LastChange),
+ SNMP6_GEN(Ip6InReceives),
+ SNMP6_GEN(Ip6InHdrErrors),
+ SNMP6_GEN(Ip6InTooBigErrors),
+ SNMP6_GEN(Ip6InNoRoutes),
+ SNMP6_GEN(Ip6InAddrErrors),
+ SNMP6_GEN(Ip6InUnknownProtos),
+ SNMP6_GEN(Ip6InTruncatedPkts),
+ SNMP6_GEN(Ip6InDiscards),
+ SNMP6_GEN(Ip6InDelivers),
+ SNMP6_GEN(Ip6OutForwDatagrams),
+ SNMP6_GEN(Ip6OutRequests),
+ SNMP6_GEN(Ip6OutDiscards),
+ SNMP6_GEN(Ip6OutNoRoutes),
+ SNMP6_GEN(Ip6ReasmTimeout),
+ SNMP6_GEN(Ip6ReasmReqds),
+ SNMP6_GEN(Ip6ReasmOKs),
+ SNMP6_GEN(Ip6ReasmFails),
+ SNMP6_GEN(Ip6FragOKs),
+ SNMP6_GEN(Ip6FragFails),
+ SNMP6_GEN(Ip6FragCreates),
+ SNMP6_GEN(Ip6InMcastPkts),
+ SNMP6_GEN(Ip6OutMcastPkts),
+#undef SNMP6_GEN
+};
+struct snmp6_item snmp6_list_icmpv6[] = {
+/* icmpv6 mib according to RFC2466 */
+#define SNMP6_GEN(x) { #x , OFFSETOF(struct icmpv6_mib, x) }
+ SNMP6_GEN(Icmp6InMsgs),
+ SNMP6_GEN(Icmp6InErrors),
+ SNMP6_GEN(Icmp6InDestUnreachs),
+ SNMP6_GEN(Icmp6InAdminProhibs),
+ SNMP6_GEN(Icmp6InTimeExcds),
+ SNMP6_GEN(Icmp6InParmProblems),
+ SNMP6_GEN(Icmp6InPktTooBigs),
+ SNMP6_GEN(Icmp6InEchos),
+ SNMP6_GEN(Icmp6InEchoReplies),
+ SNMP6_GEN(Icmp6InRouterSolicits),
+ SNMP6_GEN(Icmp6InRouterAdvertisements),
+ SNMP6_GEN(Icmp6InNeighborSolicits),
+ SNMP6_GEN(Icmp6InNeighborAdvertisements),
+ SNMP6_GEN(Icmp6InRedirects),
+ SNMP6_GEN(Icmp6InGroupMembQueries),
+ SNMP6_GEN(Icmp6InGroupMembResponses),
+ SNMP6_GEN(Icmp6InGroupMembReductions),
+ SNMP6_GEN(Icmp6OutMsgs),
+ SNMP6_GEN(Icmp6OutErrors),
+ SNMP6_GEN(Icmp6OutDestUnreachs),
+ SNMP6_GEN(Icmp6OutAdminProhibs),
+ SNMP6_GEN(Icmp6OutTimeExcds),
+ SNMP6_GEN(Icmp6OutParmProblems),
+ SNMP6_GEN(Icmp6OutPktTooBigs),
+ SNMP6_GEN(Icmp6OutEchos),
+ SNMP6_GEN(Icmp6OutEchoReplies),
+ SNMP6_GEN(Icmp6OutRouterSolicits),
+ SNMP6_GEN(Icmp6OutRouterAdvertisements),
+ SNMP6_GEN(Icmp6OutNeighborSolicits),
+ SNMP6_GEN(Icmp6OutNeighborAdvertisements),
+ SNMP6_GEN(Icmp6OutRedirects),
+ SNMP6_GEN(Icmp6OutGroupMembQueries),
+ SNMP6_GEN(Icmp6OutGroupMembResponses),
+ SNMP6_GEN(Icmp6OutGroupMembReductions),
+#undef SNMP6_GEN
+};
+struct snmp6_item snmp6_list_udp[] = {
+#define SNMP6_GEN(x) { "Udp6" #x , OFFSETOF(struct udp_mib, Udp##x) }
+ SNMP6_GEN(InDatagrams),
+ SNMP6_GEN(NoPorts),
+ SNMP6_GEN(InErrors),
+ SNMP6_GEN(OutDatagrams)
+#undef SNMP6_GEN
+};
+
+#define fold_field(base,offset) ({ \
+ unsigned long res = 0; \
+ int cpu; \
+ for (cpu=0 ; cpu<smp_num_cpus; cpu++) { \
+ res += *((unsigned long *)(((char *)&base[2*cpu_logical_map(cpu)]) + offset)) + \
+ *((unsigned long *)(((char *)&base[2*cpu_logical_map(cpu)+1]) + offset)); \
+ } \
+ res; \
+})
+
+#define snmp6_explore(_base,_list,_offset,_length,_done,_buffer,_start) ({ \
+ off_t _d = 0; \
+ int _i, _l; \
+ for (_i=0; _i<sizeof(_list)/sizeof(_list[0]); _i++){ \
+ if ((_done+_d) >= _offset + _length) \
+ break; \
+ _l = sprintf((_done+_d) <= _offset ? _buffer : *_start + (_done+_d) - _offset, \
+ "%-32s\t%ld\n", _list[_i].name, \
+ fold_field(_base,_list[_i].offset)); \
+ if ((_done+_d) <= _offset && (_done+_d)+_l >= _offset) \
+ *_start = _buffer + _offset - (_done+_d); \
+ _d += _l; \
+ } \
+ _d; \
+})
+
+int afinet6_get_snmp(char *buffer, char **start, off_t offset, int length)
+{
+ int len = 0;
+ off_t done = 0;
+
+ done += snmp6_explore(ipv6_statistics,snmp6_list_ipv6,offset,length,done,buffer,start);
+ done += snmp6_explore(icmpv6_statistics,snmp6_list_icmpv6,offset,length,done,buffer,start);
+ done += snmp6_explore(udp_stats_in6,snmp6_list_udp,offset,length,done,buffer,start);
+
+ len = done - offset;
+
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+
+ return len;
+}
+
+int afinet6_read_devsnmp(char *buffer, char **start, off_t offset, int length, int *eof, void *data)
+{
+ int len = 0;
+ struct inet6_dev *idev = data;
+ off_t done = 0;
+
+ if (idev){
+ done += sprintf(buffer, "%-32s\t%u\n", "ifIndex", idev->dev->ifindex);
+ if (done >= offset)
+ *start = buffer + offset;
+ done += snmp6_explore(idev->stats.ipv6,snmp6_list_ipv6,offset,length,done,buffer,start);
+ done += snmp6_explore(idev->stats.icmpv6,snmp6_list_icmpv6,offset,length,done,buffer,start);
+ }
+
+ if (done > offset){
+ len = done - offset;
+ if (len > length)
+ len = length;
+ else if (len < length)
+ *eof = 1;
+ }
+ else{
+ len = 0;
+ *eof = 1;
+ }
+
+ return len;
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv6/protocol.c b/uClinux-2.4.31-uc0/net/ipv6/protocol.c
new file mode 100644
index 0000000..85efc14
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/protocol.c
@@ -0,0 +1,118 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * PF_INET6 protocol dispatch tables.
+ *
+ * Version: $Id: protocol.c,v 1.10 2001/05/18 02:25:49 davem Exp $
+ *
+ * Authors: Pedro Roque <pedro_m@yahoo.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/*
+ * Changes:
+ *
+ * Vince Laviano (vince@cs.stanford.edu) 16 May 2001
+ * - Removed unused variable 'inet6_protocol_base'
+ * - Modified inet6_del_protocol() to correctly maintain copy bit.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/sched.h>
+#include <linux/net.h>
+#include <linux/in6.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/brlock.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/protocol.h>
+
+struct inet6_protocol *inet6_protos[MAX_INET_PROTOS];
+
+void inet6_add_protocol(struct inet6_protocol *prot)
+{
+ unsigned char hash;
+ struct inet6_protocol *p2;
+
+ hash = prot->protocol & (MAX_INET_PROTOS - 1);
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ prot->next = inet6_protos[hash];
+ inet6_protos[hash] = prot;
+ prot->copy = 0;
+
+ /*
+ * Set the copy bit if we need to.
+ */
+
+ p2 = (struct inet6_protocol *) prot->next;
+ while(p2 != NULL) {
+ if (p2->protocol == prot->protocol) {
+ prot->copy = 1;
+ break;
+ }
+ p2 = (struct inet6_protocol *) p2->next;
+ }
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+}
+
+/*
+ * Remove a protocol from the hash tables.
+ */
+
+int inet6_del_protocol(struct inet6_protocol *prot)
+{
+ struct inet6_protocol *p;
+ struct inet6_protocol *lp = NULL;
+ unsigned char hash;
+
+ hash = prot->protocol & (MAX_INET_PROTOS - 1);
+ br_write_lock_bh(BR_NETPROTO_LOCK);
+ if (prot == inet6_protos[hash]) {
+ inet6_protos[hash] = (struct inet6_protocol *) inet6_protos[hash]->next;
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ return(0);
+ }
+
+ p = (struct inet6_protocol *) inet6_protos[hash];
+
+ if (p != NULL && p->protocol == prot->protocol)
+ lp = p;
+
+ while(p != NULL) {
+ /*
+ * We have to worry if the protocol being deleted is
+ * the last one on the list, then we may need to reset
+ * someone's copied bit.
+ */
+ if (p->next != NULL && p->next == prot) {
+ /*
+ * if we are the last one with this protocol and
+ * there is a previous one, reset its copy bit.
+ */
+ if (prot->copy == 0 && lp != NULL)
+ lp->copy = 0;
+ p->next = prot->next;
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ return(0);
+ }
+ if (p->next != NULL && p->next->protocol == prot->protocol)
+ lp = p->next;
+
+ p = (struct inet6_protocol *) p->next;
+ }
+ br_write_unlock_bh(BR_NETPROTO_LOCK);
+ return(-1);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv6/raw.c b/uClinux-2.4.31-uc0/net/ipv6/raw.c
new file mode 100644
index 0000000..b2c005e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/raw.c
@@ -0,0 +1,966 @@
+/* $USAGI: raw.c,v 1.34 2003/08/11 16:54:00 yoshfuji Exp $ */
+
+/*
+ * RAW sockets for IPv6
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ *
+ * Adapted from linux/net/ipv4/raw.c
+ *
+ * $Id: raw.c,v 1.50.2.1 2002/03/05 12:47:34 davem Exp $
+ *
+ * Fixes:
+ * Hideaki YOSHIFUJI : sin6_scope_id support
+ * YOSHIFUJI,H.@USAGI : raw checksum (RFC2292(bis) compliance)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/sched.h>
+#include <linux/net.h>
+#include <linux/in6.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/icmpv6.h>
+#include <linux/inet.h>
+#include <asm/uaccess.h>
+#include <asm/ioctls.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/ndisc.h>
+#include <net/protocol.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+#include <net/transp_v6.h>
+#include <net/udp.h>
+#include <net/inet_common.h>
+
+#include <net/rawv6.h>
+
+struct sock *raw_v6_htable[RAWV6_HTABLE_SIZE];
+rwlock_t raw_v6_lock = RW_LOCK_UNLOCKED;
+
+static void raw_v6_hash(struct sock *sk)
+{
+ struct sock **skp = &raw_v6_htable[sk->num & (RAWV6_HTABLE_SIZE - 1)];
+
+ write_lock_bh(&raw_v6_lock);
+ if ((sk->next = *skp) != NULL)
+ (*skp)->pprev = &sk->next;
+ *skp = sk;
+ sk->pprev = skp;
+ sock_prot_inc_use(sk->prot);
+ sock_hold(sk);
+ write_unlock_bh(&raw_v6_lock);
+}
+
+static void raw_v6_unhash(struct sock *sk)
+{
+ write_lock_bh(&raw_v6_lock);
+ if (sk->pprev) {
+ if (sk->next)
+ sk->next->pprev = sk->pprev;
+ *sk->pprev = sk->next;
+ sk->pprev = NULL;
+ sock_prot_dec_use(sk->prot);
+ __sock_put(sk);
+ }
+ write_unlock_bh(&raw_v6_lock);
+}
+
+
+/* Grumble... icmp and ip_input want to get at this... */
+struct sock *__raw_v6_lookup(struct sock *sk, unsigned short num,
+ struct in6_addr *loc_addr, struct in6_addr *rmt_addr)
+{
+ struct sock *s = sk;
+ int addr_type = ipv6_addr_type(loc_addr);
+
+ for(s = sk; s; s = s->next) {
+ if(s->num == num) {
+ struct ipv6_pinfo *np = &s->net_pinfo.af_inet6;
+
+ if (!ipv6_addr_any(&np->daddr) &&
+ ipv6_addr_cmp(&np->daddr, rmt_addr))
+ continue;
+
+ if (!ipv6_addr_any(&np->rcv_saddr)) {
+ if (ipv6_addr_cmp(&np->rcv_saddr, loc_addr) == 0)
+ break;
+ if ((addr_type & IPV6_ADDR_MULTICAST) &&
+ inet6_mc_check(s, loc_addr, rmt_addr))
+ break;
+ continue;
+ }
+ break;
+ }
+ }
+ return s;
+}
+
+/*
+ * 0 - deliver
+ * 1 - block
+ */
+static __inline__ int icmpv6_filter(struct sock *sk, struct sk_buff *skb)
+{
+ struct icmp6hdr *icmph;
+ struct raw6_opt *opt;
+
+ opt = &sk->tp_pinfo.tp_raw;
+ if (pskb_may_pull(skb, sizeof(struct icmp6hdr))) {
+ __u32 *data = &opt->filter.data[0];
+ int bit_nr;
+
+ icmph = (struct icmp6hdr *) skb->data;
+ bit_nr = icmph->icmp6_type;
+
+ return (data[bit_nr >> 5] & (1 << (bit_nr & 31))) != 0;
+ }
+ return 0;
+}
+
+/*
+ * demultiplex raw sockets.
+ * (should consider queueing the skb in the sock receive_queue
+ * without calling rawv6.c)
+ */
+struct sock * ipv6_raw_deliver(struct sk_buff *skb, int nexthdr)
+{
+ struct in6_addr *saddr;
+ struct in6_addr *daddr;
+ struct sock *sk, *sk2;
+ __u8 hash;
+
+ saddr = &skb->nh.ipv6h->saddr;
+ daddr = saddr + 1;
+
+ hash = nexthdr & (MAX_INET_PROTOS - 1);
+
+ read_lock(&raw_v6_lock);
+ sk = raw_v6_htable[hash];
+
+ /*
+ * The first socket found will be delivered after
+ * delivery to transport protocols.
+ */
+
+ if (sk == NULL)
+ goto out;
+
+ sk = __raw_v6_lookup(sk, nexthdr, daddr, saddr);
+
+ if (sk) {
+ sk2 = sk;
+
+ while ((sk2 = __raw_v6_lookup(sk2->next, nexthdr, daddr, saddr))) {
+ struct sk_buff *buff;
+
+ if (nexthdr == IPPROTO_ICMPV6 &&
+ icmpv6_filter(sk2, skb))
+ continue;
+
+ buff = skb_clone(skb, GFP_ATOMIC);
+ if (buff)
+ rawv6_rcv(sk2, buff);
+ }
+ }
+
+ if (sk && nexthdr == IPPROTO_ICMPV6 && icmpv6_filter(sk, skb))
+ sk = NULL;
+
+out:
+ if (sk)
+ sock_hold(sk);
+ read_unlock(&raw_v6_lock);
+ return sk;
+}
+
+/* This cleans up af_inet6 a bit. -DaveM */
+static int rawv6_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
+{
+ struct sockaddr_in6 *addr = (struct sockaddr_in6 *) uaddr;
+ __u32 v4addr = 0;
+ int addr_type;
+ int err;
+
+ if (addr_len < SIN6_LEN_RFC2133)
+ return -EINVAL;
+ addr_type = ipv6_addr_type(&addr->sin6_addr);
+
+ /* Raw sockets are IPv6 only */
+ if (addr_type == IPV6_ADDR_MAPPED)
+ return(-EADDRNOTAVAIL);
+
+ lock_sock(sk);
+
+ err = -EINVAL;
+ if (sk->state != TCP_CLOSE)
+ goto out;
+
+ if (addr->sin6_port &&
+ ntohs(addr->sin6_port) != sk->num)
+ goto out;
+
+ if (addr_type & IPV6_ADDR_LINKLOCAL) {
+ if (addr_len >= sizeof(struct sockaddr_in6) &&
+ addr->sin6_scope_id) {
+ /* Override any existing binding, if another one
+ * is supplied by user.
+ */
+ sk->bound_dev_if = addr->sin6_scope_id;
+ }
+
+#ifndef CONFIG_IPV6_LOOSE_SCOPE_ID
+ /* Binding to link-local address requires an interface */
+ if (sk->bound_dev_if == 0)
+ goto out;
+#endif
+ }
+
+ /* Check if the address belongs to the host. */
+ if (addr_type != IPV6_ADDR_ANY) {
+ /* ipv4 addr of the socket is invalid. Only the
+ * unpecified and mapped address have a v4 equivalent.
+ */
+ v4addr = LOOPBACK4_IPV6;
+ if (!(addr_type & IPV6_ADDR_MULTICAST)) {
+ err = -EADDRNOTAVAIL;
+ if (!ipv6_chk_addr(&addr->sin6_addr, NULL))
+ goto out;
+ }
+ }
+
+ sk->rcv_saddr = v4addr;
+ sk->saddr = v4addr;
+ ipv6_addr_copy(&sk->net_pinfo.af_inet6.rcv_saddr, &addr->sin6_addr);
+ if (!(addr_type & IPV6_ADDR_MULTICAST))
+ ipv6_addr_copy(&sk->net_pinfo.af_inet6.saddr, &addr->sin6_addr);
+ err = 0;
+out:
+ release_sock(sk);
+ return err;
+}
+
+void rawv6_err(struct sock *sk, struct sk_buff *skb,
+ struct inet6_skb_parm *opt,
+ int type, int code, int offset, u32 info)
+{
+ int err;
+ int harderr;
+
+ /* Report error on raw socket, if:
+ 1. User requested recverr.
+ 2. Socket is connected (otherwise the error indication
+ is useless without recverr and error is hard.
+ */
+ if (!sk->net_pinfo.af_inet6.recverr && sk->state != TCP_ESTABLISHED)
+ return;
+
+ harderr = icmpv6_err_convert(type, code, &err);
+ if (type == ICMPV6_PKT_TOOBIG)
+ harderr = (sk->net_pinfo.af_inet6.pmtudisc == IPV6_PMTUDISC_DO);
+
+ if (sk->net_pinfo.af_inet6.recverr) {
+ u8 *payload = skb->data;
+ if (!sk->protinfo.af_inet.hdrincl)
+ payload += offset;
+ ipv6_icmp_error(sk, skb, err, 0, ntohl(info), payload);
+ }
+
+ if (sk->net_pinfo.af_inet6.recverr || harderr) {
+ sk->err = err;
+ sk->error_report(sk);
+ }
+}
+
+static inline int rawv6_rcv_skb(struct sock * sk, struct sk_buff * skb)
+{
+ struct inet6_dev *idev = in6_dev_get(skb->dev);
+ struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
+ struct in6_addr *daddr = &skb->nh.ipv6h->daddr;
+
+ if ((sk->tp_pinfo.tp_raw.checksum
+#if defined(CONFIG_FILTER)
+ || sk->filter
+#endif
+ ) && skb->ip_summed != CHECKSUM_UNNECESSARY) {
+ if ((unsigned short)csum_fold(skb_checksum(skb, 0, skb->len, skb->csum))) {
+ char sbuf[64], dbuf[64];
+ in6_ntop(saddr, sbuf);
+ in6_ntop(daddr, dbuf);
+ if (net_ratelimit())
+ printk(KERN_WARNING "RAWv6 checksum failed [%s > %s]\n",
+ sbuf, dbuf);
+ goto discard_it;
+ }
+ }
+ /* Charge it to the socket. */
+ if (sock_queue_rcv_skb(sk,skb)<0)
+ goto discard_it;
+
+ IP6_INC_STATS_BH(idev,Ip6InDelivers);
+ if (idev)
+ in6_dev_put(idev);
+ return 0;
+
+discard_it:
+ IP6_INC_STATS_BH(idev,Ip6InDiscards);
+ if (idev)
+ in6_dev_put(idev);
+ kfree_skb(skb);
+ return 0;
+}
+
+/*
+ * This is next to useless...
+ * if we demultiplex in network layer we don't need the extra call
+ * just to queue the skb...
+ * maybe we could have the network decide uppon a hint if it
+ * should call raw_rcv for demultiplexing
+ */
+int rawv6_rcv(struct sock *sk, struct sk_buff *skb)
+{
+ if (!sk->tp_pinfo.tp_raw.checksum)
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+
+ if (skb->ip_summed != CHECKSUM_UNNECESSARY) {
+ if (skb->ip_summed == CHECKSUM_HW) {
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ if (csum_ipv6_magic(&skb->nh.ipv6h->saddr,
+ &skb->nh.ipv6h->daddr,
+ skb->len, sk->num, skb->csum)) {
+ NETDEBUG(if (net_ratelimit()) printk(KERN_DEBUG "raw v6 hw csum failure.\n"));
+ skb->ip_summed = CHECKSUM_NONE;
+ }
+ }
+ if (skb->ip_summed == CHECKSUM_NONE)
+ skb->csum = ~csum_ipv6_magic(&skb->nh.ipv6h->saddr,
+ &skb->nh.ipv6h->daddr,
+ skb->len, sk->num, 0);
+ }
+
+ if (sk->protinfo.af_inet.hdrincl) {
+ if (skb->ip_summed != CHECKSUM_UNNECESSARY &&
+ (unsigned short)csum_fold(skb_checksum(skb, 0, skb->len, skb->csum))) {
+ struct inet6_dev *idev = in6_dev_get(skb->dev);
+ IP6_INC_STATS_BH(idev, Ip6InDiscards);
+ kfree_skb(skb);
+ if (idev)
+ in6_dev_put(idev);
+ return 0;
+ }
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ }
+
+ rawv6_rcv_skb(sk, skb);
+ return 0;
+}
+
+
+/*
+ * This should be easy, if there is something there
+ * we return it, otherwise we block.
+ */
+
+int rawv6_recvmsg(struct sock *sk, struct msghdr *msg, int len,
+ int noblock, int flags, int *addr_len)
+{
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)msg->msg_name;
+ struct sk_buff *skb;
+ struct net_device *dev = NULL;
+ struct inet6_dev *idev = NULL;
+ int copied, err;
+
+ if (flags & MSG_OOB)
+ return -EOPNOTSUPP;
+
+ if (addr_len)
+ *addr_len=sizeof(*sin6);
+
+ if (flags & MSG_ERRQUEUE)
+ return ipv6_recv_error(sk, msg, len);
+
+ skb = skb_recv_datagram(sk, flags, noblock, &err);
+ if (!skb)
+ goto out;
+
+ copied = skb->len;
+ if (copied > len) {
+ copied = len;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+
+ if (skb->ip_summed==CHECKSUM_UNNECESSARY) {
+ err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+ } else if (msg->msg_flags&MSG_TRUNC) {
+ if ((unsigned short)csum_fold(skb_checksum(skb, 0, skb->len, skb->csum)))
+ goto csum_copy_err;
+ err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+ } else {
+ err = skb_copy_and_csum_datagram_iovec(skb, 0, msg->msg_iov);
+ if (err == -EINVAL)
+ goto csum_copy_err;
+ }
+ if (err)
+ goto out_free;
+
+ /* Copy the address. */
+ if (sin6) {
+ sin6->sin6_family = AF_INET6;
+ ipv6_addr_copy(&sin6->sin6_addr, &skb->nh.ipv6h->saddr);
+ sin6->sin6_port = htons(sk->num);
+ sin6->sin6_flowinfo = 0;
+ sin6->sin6_scope_id = 0;
+ if (ipv6_addr_type(&sin6->sin6_addr) & IPV6_ADDR_LINKLOCAL) {
+ struct inet6_skb_parm *opt = (struct inet6_skb_parm *) skb->cb;
+ sin6->sin6_scope_id = opt->iif;
+ }
+ }
+
+ sock_recv_timestamp(msg, sk, skb);
+
+ if (sk->net_pinfo.af_inet6.rxopt.all)
+ datagram_recv_ctl(sk, msg, skb);
+
+ err = copied;
+ if (flags & MSG_TRUNC)
+ err = skb->len;
+
+out_free:
+ skb_free_datagram(sk, skb);
+out:
+ return err;
+
+csum_copy_err:
+ /* Clear queue. */
+ if (flags&MSG_PEEK) {
+ int clear = 0;
+ spin_lock_irq(&sk->receive_queue.lock);
+ if (skb == skb_peek(&sk->receive_queue)) {
+ __skb_unlink(skb, &sk->receive_queue);
+ clear = 1;
+ }
+ spin_unlock_irq(&sk->receive_queue.lock);
+ if (clear)
+ kfree_skb(skb);
+ }
+
+ /* Error for blocking case is chosen to masquerade
+ as some normal condition.
+ */
+ err = (flags&MSG_DONTWAIT) ? -EAGAIN : -EHOSTUNREACH;
+ dev = dev_get_by_index(((struct inet6_skb_parm *) skb->cb)->iif);
+ if (dev) {
+ idev = in6_dev_get(dev);
+ IP6_INC_STATS_USER(idev,Ip6InDiscards);
+ if (idev)
+ in6_dev_put(idev);
+ dev_put(dev);
+ }
+ goto out_free;
+}
+
+/*
+ * Sending...
+ */
+
+struct rawv6_fakehdr {
+ struct iovec *iov;
+ struct sock *sk;
+ __u32 len;
+ __u32 cksum;
+ __u32 proto;
+ struct in6_addr *daddr;
+};
+
+static int rawv6_getfrag(const void *data, struct in6_addr *saddr,
+ char *buff, unsigned int offset, unsigned int len)
+{
+ struct iovec *iov = (struct iovec *) data;
+
+ return memcpy_fromiovecend(buff, iov, offset, len);
+}
+
+static int rawv6_frag_cksum(const void *data, struct in6_addr *addr,
+ char *buff, unsigned int offset,
+ unsigned int len)
+{
+ struct rawv6_fakehdr *hdr = (struct rawv6_fakehdr *) data;
+
+ if (csum_partial_copy_fromiovecend(buff, hdr->iov, offset,
+ len, &hdr->cksum))
+ return -EFAULT;
+
+ if (offset == 0) {
+ struct sock *sk;
+ struct raw6_opt *opt;
+ struct in6_addr *daddr;
+
+ sk = hdr->sk;
+ opt = &sk->tp_pinfo.tp_raw;
+
+ if (hdr->daddr)
+ daddr = hdr->daddr;
+ else
+ daddr = addr + 1;
+
+ hdr->cksum = csum_ipv6_magic(addr, daddr, hdr->len,
+ hdr->proto, hdr->cksum);
+
+ if (opt->offset + 1 < len) {
+ __u16 *csum;
+
+ csum = (__u16 *) (buff + opt->offset);
+ if (*csum) {
+ /* in case cksum was not initialized */
+ __u32 sum = hdr->cksum;
+ sum += *csum;
+ *csum = hdr->cksum = (sum + (sum>>16));
+ } else {
+ *csum = hdr->cksum;
+ }
+ } else {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "icmp: cksum offset too big\n");
+ return -EINVAL;
+ }
+ hdr->cksum = 0;
+ }
+ return 0;
+}
+
+
+static int rawv6_sendmsg(struct sock *sk, struct msghdr *msg, int len)
+{
+ struct ipv6_txoptions opt_space;
+ struct sockaddr_in6 * sin6 = (struct sockaddr_in6 *) msg->msg_name;
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct ipv6_txoptions *opt = NULL;
+ struct ip6_flowlabel *flowlabel = NULL;
+ struct flowi fl;
+ int addr_len = msg->msg_namelen;
+ struct in6_addr *daddr;
+ struct raw6_opt *raw_opt;
+ int hlimit = -1;
+ int tclass = -1;
+ u16 proto;
+ int err;
+
+ /* Rough check on arithmetic overflow,
+ better check is made in ip6_build_xmit
+ */
+ if (len < 0)
+ return -EMSGSIZE;
+
+ /* Mirror BSD error message compatibility */
+ if (msg->msg_flags & MSG_OOB)
+ return -EOPNOTSUPP;
+
+ /*
+ * Get and verify the address.
+ */
+
+ fl.fl6_flowlabel = 0;
+ fl.oif = 0;
+
+ if (sin6) {
+ if (addr_len < SIN6_LEN_RFC2133)
+ return -EINVAL;
+
+ if (sin6->sin6_family && sin6->sin6_family != AF_INET6)
+ return(-EINVAL);
+
+ /* port is the proto value [0..255] carried in nexthdr */
+ proto = ntohs(sin6->sin6_port);
+
+ if (!proto)
+ proto = sk->num;
+ else if (proto != sk->num)
+ return(-EINVAL);
+
+ if (proto > 255)
+ return(-EINVAL);
+
+ daddr = &sin6->sin6_addr;
+ if (np->sndflow) {
+ fl.fl6_flowlabel = sin6->sin6_flowinfo&IPV6_FLOWINFO_MASK;
+ if (fl.fl6_flowlabel&IPV6_FLOWLABEL_MASK) {
+ flowlabel = fl6_sock_lookup(sk, fl.fl6_flowlabel);
+ if (flowlabel == NULL)
+ return -EINVAL;
+ daddr = &flowlabel->dst;
+ }
+ }
+
+ /* Otherwise it will be difficult to maintain sk->dst_cache. */
+ if (sk->state == TCP_ESTABLISHED &&
+ !ipv6_addr_cmp(daddr, &sk->net_pinfo.af_inet6.daddr))
+ daddr = &sk->net_pinfo.af_inet6.daddr;
+
+ if (addr_len >= sizeof(struct sockaddr_in6) &&
+ sin6->sin6_scope_id &&
+ ipv6_addr_type(daddr)&IPV6_ADDR_LINKLOCAL)
+ fl.oif = sin6->sin6_scope_id;
+ } else {
+ if (sk->state != TCP_ESTABLISHED)
+ return -EDESTADDRREQ;
+
+ proto = sk->num;
+ daddr = &(sk->net_pinfo.af_inet6.daddr);
+ fl.fl6_flowlabel = np->flow_label;
+ }
+
+ if (ipv6_addr_any(daddr)) {
+ /*
+ * unspecfied destination address
+ * treated as error... is this correct ?
+ */
+ return(-EINVAL);
+ }
+
+ if (fl.oif == 0)
+ fl.oif = sk->bound_dev_if;
+ fl.fl6_src = NULL;
+
+ if (msg->msg_controllen) {
+ opt = &opt_space;
+ memset(opt, 0, sizeof(struct ipv6_txoptions));
+
+ err = datagram_send_ctl(msg, &fl, opt, &hlimit, &tclass);
+ if (err < 0) {
+ fl6_sock_release(flowlabel);
+ return err;
+ }
+ if ((fl.fl6_flowlabel&IPV6_FLOWLABEL_MASK) && !flowlabel) {
+ flowlabel = fl6_sock_lookup(sk, fl.fl6_flowlabel);
+ if (flowlabel == NULL)
+ return -EINVAL;
+ }
+ if (!(opt->opt_nflen|opt->opt_flen))
+ opt = NULL;
+ }
+ if (opt == NULL)
+ opt = np->opt;
+ if (flowlabel)
+ opt = fl6_merge_options(&opt_space, flowlabel, opt);
+
+ raw_opt = &sk->tp_pinfo.tp_raw;
+
+ fl.proto = proto;
+ fl.fl6_dst = daddr;
+ if (fl.fl6_src == NULL && !ipv6_addr_any(&np->saddr))
+ fl.fl6_src = &np->saddr;
+ fl.uli_u.icmpt.type = 0;
+ fl.uli_u.icmpt.code = 0;
+
+ if (raw_opt->checksum) {
+ struct rawv6_fakehdr hdr;
+
+ hdr.iov = msg->msg_iov;
+ hdr.sk = sk;
+ hdr.len = len;
+ hdr.cksum = 0;
+ hdr.proto = proto;
+
+ if (opt && opt->srcrt)
+ hdr.daddr = daddr;
+ else
+ hdr.daddr = NULL;
+ err = ip6_build_xmit(sk, rawv6_frag_cksum, &hdr, &fl, len,
+ opt, hlimit, tclass, msg->msg_flags);
+ } else {
+ err = ip6_build_xmit(sk, rawv6_getfrag, msg->msg_iov, &fl, len,
+ opt, hlimit, tclass, msg->msg_flags);
+ }
+
+ fl6_sock_release(flowlabel);
+
+ return err<0?err:len;
+}
+
+static int rawv6_seticmpfilter(struct sock *sk, int level, int optname,
+ char *optval, int optlen)
+{
+ switch (optname) {
+ case ICMPV6_FILTER:
+ if (optlen > sizeof(struct icmp6_filter))
+ optlen = sizeof(struct icmp6_filter);
+ if (copy_from_user(&sk->tp_pinfo.tp_raw.filter, optval, optlen))
+ return -EFAULT;
+ return 0;
+ default:
+ return -ENOPROTOOPT;
+ };
+
+ return 0;
+}
+
+static int rawv6_geticmpfilter(struct sock *sk, int level, int optname,
+ char *optval, int *optlen)
+{
+ int len;
+
+ switch (optname) {
+ case ICMPV6_FILTER:
+ if (get_user(len, optlen))
+ return -EFAULT;
+ if (len < 0)
+ return -EINVAL;
+ if (len > sizeof(struct icmp6_filter))
+ len = sizeof(struct icmp6_filter);
+ if (put_user(len, optlen))
+ return -EFAULT;
+ if (copy_to_user(optval, &sk->tp_pinfo.tp_raw.filter, len))
+ return -EFAULT;
+ return 0;
+ default:
+ return -ENOPROTOOPT;
+ };
+
+ return 0;
+}
+
+
+static int rawv6_setsockopt(struct sock *sk, int level, int optname,
+ char *optval, int optlen)
+{
+ struct raw6_opt *opt = &sk->tp_pinfo.tp_raw;
+ int val;
+
+ switch(level) {
+ case SOL_RAW:
+ break;
+
+ case SOL_ICMPV6:
+ if (sk->num != IPPROTO_ICMPV6)
+ return -EOPNOTSUPP;
+ return rawv6_seticmpfilter(sk, level, optname, optval,
+ optlen);
+ case SOL_IPV6:
+ if (optname == IPV6_CHECKSUM)
+ break;
+ default:
+ return ipv6_setsockopt(sk, level, optname, optval,
+ optlen);
+ };
+
+ if (get_user(val, (int *)optval))
+ return -EFAULT;
+
+ switch (optname) {
+ case IPV6_CHECKSUM:
+ if (sk->num == IPPROTO_ICMPV6 && val != 2)
+ return(-EINVAL);
+ /* You may get strange result with a positive odd offset;
+ RFC2292bis agrees with me. */
+ if (val > 0 && (val&1))
+ return(-EINVAL);
+ if (val < 0) {
+ opt->checksum = 0;
+ } else {
+ opt->checksum = 1;
+ opt->offset = val;
+ }
+
+ return 0;
+ break;
+
+ default:
+ return(-ENOPROTOOPT);
+ }
+}
+
+static int rawv6_getsockopt(struct sock *sk, int level, int optname,
+ char *optval, int *optlen)
+{
+ struct raw6_opt *opt = &sk->tp_pinfo.tp_raw;
+ int val, len;
+
+ switch(level) {
+ case SOL_RAW:
+ break;
+
+ case SOL_ICMPV6:
+ if (sk->num != IPPROTO_ICMPV6)
+ return -EOPNOTSUPP;
+ return rawv6_geticmpfilter(sk, level, optname, optval,
+ optlen);
+ case SOL_IPV6:
+ if (optname == IPV6_CHECKSUM)
+ break;
+ default:
+ return ipv6_getsockopt(sk, level, optname, optval,
+ optlen);
+ };
+
+ if (get_user(len,optlen))
+ return -EFAULT;
+
+ switch (optname) {
+ case IPV6_CHECKSUM:
+ if (opt->checksum == 0)
+ val = -1;
+ else
+ val = opt->offset;
+ break;
+
+ default:
+ return -ENOPROTOOPT;
+ }
+
+ len = min_t(unsigned int, sizeof(int), len);
+
+ if (put_user(len, optlen))
+ return -EFAULT;
+ if (copy_to_user(optval,&val,len))
+ return -EFAULT;
+ return 0;
+}
+
+static int rawv6_ioctl(struct sock *sk, int cmd, unsigned long arg)
+{
+ switch(cmd) {
+ case SIOCOUTQ:
+ {
+ int amount = atomic_read(&sk->wmem_alloc);
+ return put_user(amount, (int *)arg);
+ }
+ case SIOCINQ:
+ {
+ struct sk_buff *skb;
+ int amount = 0;
+
+ spin_lock_irq(&sk->receive_queue.lock);
+ skb = skb_peek(&sk->receive_queue);
+ if (skb != NULL)
+ amount = skb->tail - skb->h.raw;
+ spin_unlock_irq(&sk->receive_queue.lock);
+ return put_user(amount, (int *)arg);
+ }
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+
+static void rawv6_close(struct sock *sk, long timeout)
+{
+ if (sk->num == IPPROTO_RAW)
+ ip6_ra_control(sk, -1, NULL);
+
+ inet_sock_release(sk);
+}
+
+static int rawv6_init_sk(struct sock *sk)
+{
+ if (sk->num == IPPROTO_ICMPV6){
+ struct raw6_opt *opt = &sk->tp_pinfo.tp_raw;
+ opt->checksum = 1;
+ opt->offset = 2;
+ }
+ return(0);
+}
+
+#define LINE_LEN 190
+#define LINE_FMT "%-190s\n"
+
+static void get_raw6_sock(struct sock *sp, char *tmpbuf, int i)
+{
+ struct in6_addr *dest, *src;
+ __u16 destp, srcp;
+
+ dest = &sp->net_pinfo.af_inet6.daddr;
+ src = &sp->net_pinfo.af_inet6.rcv_saddr;
+ destp = 0;
+ srcp = sp->num;
+ sprintf(tmpbuf,
+ "%4d: %08X%08X%08X%08X:%04X %08X%08X%08X%08X:%04X "
+ "%02X %08X:%08X %02X:%08lX %08X %5d %8d %lu %d %p",
+ i,
+ src->s6_addr32[0], src->s6_addr32[1],
+ src->s6_addr32[2], src->s6_addr32[3], srcp,
+ dest->s6_addr32[0], dest->s6_addr32[1],
+ dest->s6_addr32[2], dest->s6_addr32[3], destp,
+ sp->state,
+ atomic_read(&sp->wmem_alloc), atomic_read(&sp->rmem_alloc),
+ 0, 0L, 0,
+ sock_i_uid(sp), 0,
+ sock_i_ino(sp),
+ atomic_read(&sp->refcnt), sp);
+}
+
+int raw6_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int len = 0, num = 0, i;
+ off_t pos = 0;
+ off_t begin;
+ char tmpbuf[LINE_LEN+2];
+
+ if (offset < LINE_LEN+1)
+ len += sprintf(buffer, LINE_FMT,
+ " sl " /* 6 */
+ "local_address " /* 38 */
+ "remote_address " /* 38 */
+ "st tx_queue rx_queue tr tm->when retrnsmt" /* 41 */
+ " uid timeout inode"); /* 21 */
+ /*----*/
+ /*144 */
+ pos = LINE_LEN+1;
+ read_lock(&raw_v6_lock);
+ for (i = 0; i < RAWV6_HTABLE_SIZE; i++) {
+ struct sock *sk;
+
+ for (sk = raw_v6_htable[i]; sk; sk = sk->next, num++) {
+ if (sk->family != PF_INET6)
+ continue;
+ pos += LINE_LEN+1;
+ if (pos <= offset)
+ continue;
+ get_raw6_sock(sk, tmpbuf, i);
+ len += sprintf(buffer+len, LINE_FMT, tmpbuf);
+ if(len >= length)
+ goto out;
+ }
+ }
+out:
+ read_unlock(&raw_v6_lock);
+ begin = len - (pos - offset);
+ *start = buffer + begin;
+ len -= begin;
+ if(len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+struct proto rawv6_prot = {
+ name: "RAW",
+ close: rawv6_close,
+ connect: udpv6_connect,
+ disconnect: udp_disconnect,
+ ioctl: rawv6_ioctl,
+ init: rawv6_init_sk,
+ destroy: inet6_destroy_sock,
+ setsockopt: rawv6_setsockopt,
+ getsockopt: rawv6_getsockopt,
+ sendmsg: rawv6_sendmsg,
+ recvmsg: rawv6_recvmsg,
+ bind: rawv6_bind,
+ backlog_rcv: rawv6_rcv_skb,
+ hash: raw_v6_hash,
+ unhash: raw_v6_unhash,
+};
diff --git a/uClinux-2.4.31-uc0/net/ipv6/reassembly.c b/uClinux-2.4.31-uc0/net/ipv6/reassembly.c
new file mode 100644
index 0000000..bdc2715
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/reassembly.c
@@ -0,0 +1,792 @@
+/* $USAGI: reassembly.c,v 1.24 2003/08/08 13:46:38 yoshfuji Exp $ */
+
+/*
+ * IPv6 fragment reassembly
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ *
+ * $Id: reassembly.c,v 1.26 2001/03/07 22:00:57 davem Exp $
+ *
+ * Based on: net/ipv4/ip_fragment.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/*
+ * Fixes:
+ * Andi Kleen Make it work with multiple hosts.
+ * More RFC compliance.
+ *
+ * Horst von Brand Add missing #include <linux/string.h>
+ * Alexey Kuznetsov SMP races, threading, cleanup.
+ * Patrick McHardy LRU queue of frag heads for evictor.
+ */
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/in6.h>
+#include <linux/ipv6.h>
+#include <linux/icmpv6.h>
+#include <linux/random.h>
+#include <linux/jhash.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/transp_v6.h>
+#include <net/rawv6.h>
+#include <net/ndisc.h>
+#include <net/addrconf.h>
+
+int sysctl_ip6frag_high_thresh = 256*1024;
+int sysctl_ip6frag_low_thresh = 192*1024;
+
+int sysctl_ip6frag_time = IPV6_FRAG_TIMEOUT;
+
+struct ip6frag_skb_cb
+{
+ struct inet6_skb_parm h;
+ int offset;
+};
+
+#define FRAG6_CB(skb) ((struct ip6frag_skb_cb*)((skb)->cb))
+
+
+/*
+ * Equivalent of ipv4 struct ipq
+ */
+
+struct frag_queue
+{
+ struct frag_queue *next;
+ struct list_head lru_list; /* lru list member */
+
+ __u32 id; /* fragment id */
+ struct in6_addr saddr;
+ struct in6_addr daddr;
+
+ spinlock_t lock;
+ atomic_t refcnt;
+ struct timer_list timer; /* expire timer */
+ struct sk_buff *fragments;
+ int len;
+ int meat;
+ int iif;
+ struct timeval stamp;
+ unsigned int csum;
+ __u8 last_in; /* has first/last segment arrived? */
+#define COMPLETE 4
+#define FIRST_IN 2
+#define LAST_IN 1
+ __u16 nhoffset;
+ struct frag_queue **pprev;
+};
+
+/* Hash table. */
+
+#define IP6Q_HASHSZ 64
+
+static struct frag_queue *ip6_frag_hash[IP6Q_HASHSZ];
+static rwlock_t ip6_frag_lock = RW_LOCK_UNLOCKED;
+static u32 ip6_frag_hash_rnd;
+static LIST_HEAD(ip6_frag_lru_list);
+int ip6_frag_nqueues = 0;
+
+static __inline__ void __fq_unlink(struct frag_queue *fq)
+{
+ if(fq->next)
+ fq->next->pprev = fq->pprev;
+ *fq->pprev = fq->next;
+ list_del(&fq->lru_list);
+ ip6_frag_nqueues--;
+}
+
+static __inline__ void fq_unlink(struct frag_queue *fq)
+{
+ write_lock(&ip6_frag_lock);
+ __fq_unlink(fq);
+ write_unlock(&ip6_frag_lock);
+}
+
+static unsigned int ip6qhashfn(u32 id, struct in6_addr *saddr,
+ struct in6_addr *daddr)
+{
+ u32 a, b, c;
+
+ a = saddr->s6_addr32[0];
+ b = saddr->s6_addr32[1];
+ c = saddr->s6_addr32[2];
+
+ a += JHASH_GOLDEN_RATIO;
+ b += JHASH_GOLDEN_RATIO;
+ c += ip6_frag_hash_rnd;
+ __jhash_mix(a, b, c);
+
+ a += saddr->s6_addr32[3];
+ b += daddr->s6_addr32[0];
+ c += daddr->s6_addr32[1];
+ __jhash_mix(a, b, c);
+
+ a += daddr->s6_addr32[2];
+ b += daddr->s6_addr32[3];
+ c += id;
+ __jhash_mix(a, b, c);
+
+ return c & (IP6Q_HASHSZ - 1);
+}
+
+static struct timer_list ip6_frag_secret_timer;
+static int ip6_frag_secret_interval = 10 * 60 * HZ;
+
+static void ip6_frag_secret_rebuild(unsigned long dummy)
+{
+ unsigned long now = jiffies;
+ int i;
+
+ write_lock(&ip6_frag_lock);
+ get_random_bytes(&ip6_frag_hash_rnd, sizeof(u32));
+ for (i = 0; i < IP6Q_HASHSZ; i++) {
+ struct frag_queue *q;
+
+ q = ip6_frag_hash[i];
+ while (q) {
+ struct frag_queue *next = q->next;
+ unsigned int hval = ip6qhashfn(q->id,
+ &q->saddr,
+ &q->daddr);
+
+ if (hval != i) {
+ /* Unlink. */
+ if (q->next)
+ q->next->pprev = q->pprev;
+ *q->pprev = q->next;
+
+ /* Relink to new hash chain. */
+ if ((q->next = ip6_frag_hash[hval]) != NULL)
+ q->next->pprev = &q->next;
+ ip6_frag_hash[hval] = q;
+ q->pprev = &ip6_frag_hash[hval];
+ }
+
+ q = next;
+ }
+ }
+ write_unlock(&ip6_frag_lock);
+
+ mod_timer(&ip6_frag_secret_timer, now + ip6_frag_secret_interval);
+}
+
+atomic_t ip6_frag_mem = ATOMIC_INIT(0);
+
+/* Memory Tracking Functions. */
+static inline void frag_kfree_skb(struct sk_buff *skb, int *work)
+{
+ if (work)
+ *work -= skb->truesize;
+ atomic_sub(skb->truesize, &ip6_frag_mem);
+ kfree_skb(skb);
+}
+
+static inline void frag_free_queue(struct frag_queue *fq, int *work)
+{
+ if (work)
+ *work -= sizeof(struct frag_queue);
+ atomic_sub(sizeof(struct frag_queue), &ip6_frag_mem);
+ kfree(fq);
+}
+
+static inline struct frag_queue *frag_alloc_queue(void)
+{
+ struct frag_queue *fq = kmalloc(sizeof(struct frag_queue), GFP_ATOMIC);
+
+ if(!fq)
+ return NULL;
+ atomic_add(sizeof(struct frag_queue), &ip6_frag_mem);
+ return fq;
+}
+
+/* Destruction primitives. */
+
+/* Complete destruction of fq. */
+static void ip6_frag_destroy(struct frag_queue *fq, int *work)
+{
+ struct sk_buff *fp;
+
+ BUG_TRAP(fq->last_in&COMPLETE);
+ BUG_TRAP(del_timer(&fq->timer) == 0);
+
+ /* Release all fragment data. */
+ fp = fq->fragments;
+ while (fp) {
+ struct sk_buff *xp = fp->next;
+
+ frag_kfree_skb(fp, work);
+ fp = xp;
+ }
+
+ frag_free_queue(fq, work);
+}
+
+static __inline__ void fq_put(struct frag_queue *fq, int *work)
+{
+ if (atomic_dec_and_test(&fq->refcnt))
+ ip6_frag_destroy(fq, work);
+}
+
+/* Kill fq entry. It is not destroyed immediately,
+ * because caller (and someone more) holds reference count.
+ */
+static __inline__ void fq_kill(struct frag_queue *fq)
+{
+ if (del_timer(&fq->timer))
+ atomic_dec(&fq->refcnt);
+
+ if (!(fq->last_in & COMPLETE)) {
+ fq_unlink(fq);
+ atomic_dec(&fq->refcnt);
+ fq->last_in |= COMPLETE;
+ }
+}
+
+static void ip6_evictor(struct inet6_dev *idev)
+{
+ struct frag_queue *fq;
+ struct list_head *tmp;
+ int work;
+
+ work = atomic_read(&ip6_frag_mem) - sysctl_ip6frag_low_thresh;
+ if (work <= 0)
+ return;
+
+ while(work > 0) {
+ read_lock(&ip6_frag_lock);
+ if (list_empty(&ip6_frag_lru_list)) {
+ read_unlock(&ip6_frag_lock);
+ return;
+ }
+ tmp = ip6_frag_lru_list.next;
+ fq = list_entry(tmp, struct frag_queue, lru_list);
+ atomic_inc(&fq->refcnt);
+ read_unlock(&ip6_frag_lock);
+
+ spin_lock(&fq->lock);
+ if (!(fq->last_in&COMPLETE))
+ fq_kill(fq);
+ spin_unlock(&fq->lock);
+
+ fq_put(fq, &work);
+ IP6_INC_STATS_BH(idev,Ip6ReasmFails);
+ }
+}
+
+static void ip6_frag_expire(unsigned long data)
+{
+ struct frag_queue *fq = (struct frag_queue *) data;
+ struct net_device *dev;
+ struct inet6_dev *idev;
+
+ spin_lock(&fq->lock);
+
+ if (fq->last_in & COMPLETE)
+ goto out;
+
+ fq_kill(fq);
+
+ dev = dev_get_by_index(fq->iif);
+ idev = dev ? in6_dev_get(dev) : NULL;
+
+ IP6_INC_STATS_BH(idev,Ip6ReasmTimeout);
+ IP6_INC_STATS_BH(idev,Ip6ReasmFails);
+
+ /* Send error only if the first segment arrived. */
+ if (fq->last_in&FIRST_IN && fq->fragments) {
+ /*
+ But use as source device on which LAST ARRIVED
+ segment was received. And do not use fq->dev
+ pointer directly, device might already disappeared.
+ */
+ if (dev) {
+ fq->fragments->dev = dev;
+ icmpv6_send(fq->fragments, ICMPV6_TIME_EXCEED, ICMPV6_EXC_FRAGTIME, 0,
+ dev);
+ }
+ }
+ if (idev)
+ in6_dev_put(idev);
+ if (dev)
+ dev_put(dev);
+out:
+ spin_unlock(&fq->lock);
+ fq_put(fq, NULL);
+}
+
+/* Creation primitives. */
+
+
+static struct frag_queue *ip6_frag_intern(unsigned int hash,
+ struct frag_queue *fq_in)
+{
+ struct frag_queue *fq;
+
+ write_lock(&ip6_frag_lock);
+#ifdef CONFIG_SMP
+ for (fq = ip6_frag_hash[hash]; fq; fq = fq->next) {
+ if (fq->id == fq_in->id &&
+ !ipv6_addr_cmp(&fq_in->saddr, &fq->saddr) &&
+ !ipv6_addr_cmp(&fq_in->daddr, &fq->daddr)) {
+ atomic_inc(&fq->refcnt);
+ write_unlock(&ip6_frag_lock);
+ fq_in->last_in |= COMPLETE;
+ fq_put(fq_in, NULL);
+ return fq;
+ }
+ }
+#endif
+ fq = fq_in;
+
+ if (!mod_timer(&fq->timer, jiffies + sysctl_ip6frag_time))
+ atomic_inc(&fq->refcnt);
+
+ atomic_inc(&fq->refcnt);
+ if((fq->next = ip6_frag_hash[hash]) != NULL)
+ fq->next->pprev = &fq->next;
+ ip6_frag_hash[hash] = fq;
+ fq->pprev = &ip6_frag_hash[hash];
+ INIT_LIST_HEAD(&fq->lru_list);
+ list_add_tail(&fq->lru_list, &ip6_frag_lru_list);
+ ip6_frag_nqueues++;
+ write_unlock(&ip6_frag_lock);
+ return fq;
+}
+
+
+static struct frag_queue *
+ip6_frag_create(unsigned int hash, u32 id, struct in6_addr *src, struct in6_addr *dst,
+ struct inet6_dev *idev)
+{
+ struct frag_queue *fq;
+
+ if ((fq = frag_alloc_queue()) == NULL)
+ goto oom;
+
+ memset(fq, 0, sizeof(struct frag_queue));
+
+ fq->id = id;
+ ipv6_addr_copy(&fq->saddr, src);
+ ipv6_addr_copy(&fq->daddr, dst);
+
+ /* init_timer has been done by the memset */
+ fq->timer.function = ip6_frag_expire;
+ fq->timer.data = (long) fq;
+ fq->lock = SPIN_LOCK_UNLOCKED;
+ atomic_set(&fq->refcnt, 1);
+
+ return ip6_frag_intern(hash, fq);
+
+oom:
+ IP6_INC_STATS_BH(idev,Ip6ReasmFails);
+ return NULL;
+}
+
+static __inline__ struct frag_queue *
+fq_find(u32 id, struct in6_addr *src, struct in6_addr *dst, struct inet6_dev *idev)
+{
+ struct frag_queue *fq;
+ unsigned int hash = ip6qhashfn(id, src, dst);
+
+ read_lock(&ip6_frag_lock);
+ for(fq = ip6_frag_hash[hash]; fq; fq = fq->next) {
+ if (fq->id == id &&
+ !ipv6_addr_cmp(src, &fq->saddr) &&
+ !ipv6_addr_cmp(dst, &fq->daddr)) {
+ atomic_inc(&fq->refcnt);
+ read_unlock(&ip6_frag_lock);
+ return fq;
+ }
+ }
+ read_unlock(&ip6_frag_lock);
+
+ return ip6_frag_create(hash, id, src, dst, idev);
+}
+
+
+static void ip6_frag_queue(struct frag_queue *fq, struct sk_buff *skb,
+ struct frag_hdr *fhdr, int nhoff)
+{
+ struct sk_buff *prev, *next;
+ int offset, end;
+ unsigned int unfrag_len;
+
+ if (fq->last_in & COMPLETE)
+ goto err;
+
+ offset = ntohs(fhdr->frag_off) & ~0x7;
+ end = offset + (ntohs(skb->nh.ipv6h->payload_len) -
+ ((u8 *) (fhdr + 1) - (u8 *) (skb->nh.ipv6h + 1)));
+ if (offset) {
+ struct sk_buff *head = fq->fragments;
+ unfrag_len = (fq->last_in & FIRST_IN) ?
+ (head->h.raw - (u8 *) (head->nh.ipv6h + 1)) :
+ 0;
+ } else {
+ unfrag_len = ((u8 *) fhdr) - (u8 *)(skb->nh.ipv6h + 1);
+ }
+
+ if (unfrag_len + (unsigned int)end >= 65536) {
+ icmpv6_param_prob(skb,ICMPV6_HDR_FIELD, (u8*)&fhdr->frag_off - skb->nh.raw);
+ return;
+ }
+
+ if (skb->ip_summed == CHECKSUM_HW)
+ skb->csum = csum_sub(skb->csum,
+ csum_partial(skb->nh.raw, (u8*)(fhdr+1)-skb->nh.raw, 0));
+
+ /* Is this the final fragment? */
+ if (!(fhdr->frag_off & htons(0x0001))) {
+ /* If we already have some bits beyond end
+ * or have different end, the segment is corrupted.
+ */
+ if (end < fq->len ||
+ ((fq->last_in & LAST_IN) && end != fq->len))
+ goto err;
+ fq->last_in |= LAST_IN;
+ fq->len = end;
+ } else {
+ /* Check if the fragment is rounded to 8 bytes.
+ * Required by the RFC.
+ */
+ if (end & 0x7) {
+ /* RFC2460 says always send parameter problem in
+ * this case. -DaveM
+ */
+ icmpv6_param_prob(skb, ICMPV6_HDR_FIELD,
+ offsetof(struct ipv6hdr, payload_len));
+ return;
+ }
+ if (end > fq->len) {
+ /* Some bits beyond end -> corruption. */
+ if (fq->last_in & LAST_IN)
+ goto err;
+ fq->len = end;
+ }
+ }
+
+ if (end == offset)
+ goto err;
+
+ /* Point into the IP datagram 'data' part. */
+ if (!pskb_pull(skb, (u8 *) (fhdr + 1) - skb->data))
+ goto err;
+ if (end-offset < skb->len) {
+ if (pskb_trim(skb, end - offset))
+ goto err;
+ if (skb->ip_summed != CHECKSUM_UNNECESSARY)
+ skb->ip_summed = CHECKSUM_NONE;
+ }
+
+ /* Find out which fragments are in front and at the back of us
+ * in the chain of fragments so far. We must know where to put
+ * this fragment, right?
+ */
+ prev = NULL;
+ for(next = fq->fragments; next != NULL; next = next->next) {
+ if (FRAG6_CB(next)->offset >= offset)
+ break; /* bingo! */
+ prev = next;
+ }
+
+ /* We found where to put this one. Check for overlap with
+ * preceding fragment, and, if needed, align things so that
+ * any overlaps are eliminated.
+ */
+ if (prev) {
+ int i = (FRAG6_CB(prev)->offset + prev->len) - offset;
+
+ if (i > 0) {
+ offset += i;
+ if (end <= offset)
+ goto err;
+ if (!pskb_pull(skb, i))
+ goto err;
+ if (skb->ip_summed != CHECKSUM_UNNECESSARY)
+ skb->ip_summed = CHECKSUM_NONE;
+ }
+ }
+
+ /* Look for overlap with succeeding segments.
+ * If we can merge fragments, do it.
+ */
+ while (next && FRAG6_CB(next)->offset < end) {
+ int i = end - FRAG6_CB(next)->offset; /* overlap is 'i' bytes */
+
+ if (i < next->len) {
+ /* Eat head of the next overlapped fragment
+ * and leave the loop. The next ones cannot overlap.
+ */
+ if (!pskb_pull(next, i))
+ goto err;
+ FRAG6_CB(next)->offset += i; /* next fragment */
+ fq->meat -= i;
+ if (next->ip_summed != CHECKSUM_UNNECESSARY)
+ next->ip_summed = CHECKSUM_NONE;
+ break;
+ } else {
+ struct sk_buff *free_it = next;
+
+ /* Old fragmnet is completely overridden with
+ * new one drop it.
+ */
+ next = next->next;
+
+ if (prev)
+ prev->next = next;
+ else
+ fq->fragments = next;
+
+ fq->meat -= free_it->len;
+ frag_kfree_skb(free_it, NULL);
+ }
+ }
+
+ FRAG6_CB(skb)->offset = offset;
+
+ /* Insert this fragment in the chain of fragments. */
+ skb->next = next;
+ if (prev)
+ prev->next = skb;
+ else
+ fq->fragments = skb;
+
+ if (skb->dev)
+ fq->iif = skb->dev->ifindex;
+ skb->dev = NULL;
+ fq->stamp = skb->stamp;
+ fq->meat += skb->len;
+ atomic_add(skb->truesize, &ip6_frag_mem);
+
+ /* The first fragment.
+ * nhoffset is obtained from the first fragment, of course.
+ */
+ if (offset == 0) {
+ fq->nhoffset = nhoff;
+ fq->last_in |= FIRST_IN;
+ }
+ write_lock(&ip6_frag_lock);
+ list_move_tail(&fq->lru_list, &ip6_frag_lru_list);
+ write_unlock(&ip6_frag_lock);
+ return;
+
+err:
+ kfree_skb(skb);
+}
+
+/*
+ * Check if this packet is complete.
+ * Returns NULL on failure by any reason, and pointer
+ * to current nexthdr field in reassembled frame.
+ *
+ * It is called with locked fq, and caller must check that
+ * queue is eligible for reassembly i.e. it is not COMPLETE,
+ * the last and the first frames arrived and all the bits are here.
+ */
+static int ip6_frag_reasm(struct frag_queue *fq, struct sk_buff **skb_in,
+ struct net_device *dev)
+{
+ struct sk_buff *fp, *head = fq->fragments;
+ struct inet6_dev *idev;
+ int remove_fraghdr = 1;
+ int payload_len;
+ int nhoff;
+
+ fq_kill(fq);
+
+ BUG_TRAP(head != NULL);
+ BUG_TRAP(FRAG6_CB(head)->offset == 0);
+
+ idev = in6_dev_get(dev);
+
+ /* Unfragmented part is taken from the first segment. */
+ payload_len = (head->data - head->nh.raw) - sizeof(struct ipv6hdr) + fq->len;
+ nhoff = head->h.raw - head->nh.raw;
+
+ if (payload_len > 65535 + 8)
+ goto out_oversize;
+
+ /* Head of list must not be cloned. */
+ if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC))
+ goto out_oom;
+
+ /* If the first fragment is fragmented itself, we split
+ * it to two chunks: the first with data and paged part
+ * and the second, holding only fragments. */
+ if (skb_shinfo(head)->frag_list) {
+ struct sk_buff *clone;
+ int i, plen = 0;
+
+ if ((clone = alloc_skb(0, GFP_ATOMIC)) == NULL)
+ goto out_oom;
+ clone->next = head->next;
+ head->next = clone;
+ skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list;
+ skb_shinfo(head)->frag_list = NULL;
+ for (i=0; i<skb_shinfo(head)->nr_frags; i++)
+ plen += skb_shinfo(head)->frags[i].size;
+ clone->len = clone->data_len = head->data_len - plen;
+ head->data_len -= clone->len;
+ head->len -= clone->len;
+ clone->csum = 0;
+ clone->ip_summed = head->ip_summed;
+ atomic_add(clone->truesize, &ip6_frag_mem);
+ }
+
+ /* Normally we do not remove frag header from datagram, but
+ * we have to do this and to relocate header, when payload
+ * is > 65535-8. */
+ if (remove_fraghdr) {
+ nhoff = fq->nhoffset;
+ head->nh.raw[nhoff] = head->h.raw[0];
+ memmove(head->head+8, head->head, (head->data-head->head)-8);
+ head->mac.raw += 8;
+ head->nh.raw += 8;
+ payload_len -= 8;
+ } else {
+ ((struct frag_hdr*)head->h.raw)->frag_off = 0;
+ }
+
+ skb_shinfo(head)->frag_list = head->next;
+ head->h.raw = head->data;
+ skb_push(head, head->data - head->nh.raw);
+ atomic_sub(head->truesize, &ip6_frag_mem);
+
+ for (fp=head->next; fp; fp = fp->next) {
+ head->data_len += fp->len;
+ head->len += fp->len;
+ if (head->ip_summed != fp->ip_summed)
+ head->ip_summed = CHECKSUM_NONE;
+ else if (head->ip_summed == CHECKSUM_HW)
+ head->csum = csum_add(head->csum, fp->csum);
+ head->truesize += fp->truesize;
+ atomic_sub(fp->truesize, &ip6_frag_mem);
+ }
+
+ head->next = NULL;
+ head->dev = dev;
+ head->stamp = fq->stamp;
+ head->nh.ipv6h->payload_len = ntohs(payload_len);
+
+ *skb_in = head;
+
+ /* Yes, and fold redundant checksum back. 8) */
+ if (head->ip_summed == CHECKSUM_HW)
+ head->csum = csum_partial(head->nh.raw, head->h.raw-head->nh.raw, head->csum);
+
+ IP6_INC_STATS_BH(idev,Ip6ReasmOKs);
+ if (idev)
+ in6_dev_put(idev);
+ fq->fragments = NULL;
+ return nhoff;
+
+out_oversize:
+ if (net_ratelimit())
+ printk(KERN_DEBUG "ip6_frag_reasm: payload len = %d\n", payload_len);
+ goto out_fail;
+out_oom:
+ if (net_ratelimit())
+ printk(KERN_DEBUG "ip6_frag_reasm: no memory for reassembly\n");
+out_fail:
+ IP6_INC_STATS_BH(idev,Ip6ReasmFails);
+ if (idev)
+ in6_dev_put(idev);
+ return -1;
+}
+
+int ipv6_reassembly(struct sk_buff **skbp, int nhoff)
+{
+ struct sk_buff *skb = *skbp;
+ struct inet6_dev *idev = in6_dev_get(skb->dev);
+ struct net_device *dev = skb->dev;
+ struct frag_hdr *fhdr;
+ struct frag_queue *fq;
+ struct ipv6hdr *hdr;
+
+ hdr = skb->nh.ipv6h;
+
+ IP6_INC_STATS_BH(idev,Ip6ReasmReqds);
+
+ /* Jumbo payload inhibits frag. header */
+ if (hdr->payload_len==0) {
+ if (idev)
+ in6_dev_put(idev);
+ icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, skb->h.raw-skb->nh.raw);
+ return -1;
+ }
+ if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+sizeof(struct frag_hdr))) {
+ if (idev)
+ in6_dev_put(idev);
+ icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, skb->h.raw-skb->nh.raw);
+ return -1;
+ }
+
+ hdr = skb->nh.ipv6h;
+ fhdr = (struct frag_hdr *)skb->h.raw;
+
+ if (!(fhdr->frag_off & htons(0xFFF9))) {
+ /* It is not a fragmented frame */
+ skb->h.raw += sizeof(struct frag_hdr);
+ IP6_INC_STATS_BH(idev,Ip6ReasmOKs);
+
+ if (idev)
+ in6_dev_put(idev);
+ return (u8*)fhdr - skb->nh.raw;
+ }
+
+ if (atomic_read(&ip6_frag_mem) > sysctl_ip6frag_high_thresh)
+ ip6_evictor(idev);
+
+ if ((fq = fq_find(fhdr->identification, &hdr->saddr, &hdr->daddr, idev)) != NULL) {
+ int ret = -1;
+
+ spin_lock(&fq->lock);
+
+ ip6_frag_queue(fq, skb, fhdr, nhoff);
+
+ if (fq->last_in == (FIRST_IN|LAST_IN) &&
+ fq->meat == fq->len)
+ ret = ip6_frag_reasm(fq, skbp, dev);
+
+ spin_unlock(&fq->lock);
+ fq_put(fq, NULL);
+ return ret;
+ }
+
+ IP6_INC_STATS_BH(idev,Ip6ReasmFails);
+ if (idev)
+ in6_dev_put(idev);
+ kfree_skb(skb);
+ return -1;
+}
+
+void __init ipv6_frag_init(void)
+{
+ ip6_frag_hash_rnd = (u32) ((num_physpages ^ (num_physpages>>7)) ^
+ (jiffies ^ (jiffies >> 6)));
+
+ init_timer(&ip6_frag_secret_timer);
+ ip6_frag_secret_timer.function = ip6_frag_secret_rebuild;
+ ip6_frag_secret_timer.expires = jiffies + ip6_frag_secret_interval;
+ add_timer(&ip6_frag_secret_timer);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv6/route.c b/uClinux-2.4.31-uc0/net/ipv6/route.c
new file mode 100644
index 0000000..e2f948f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/route.c
@@ -0,0 +1,2606 @@
+/* $USAGI: route.c,v 1.70 2004/01/14 14:11:38 yoshfuji Exp $ */
+
+/*
+ * Linux INET6 implementation
+ * FIB front-end.
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ *
+ * $Id: route.c,v 1.56 2001/10/31 21:55:55 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/* Changes:
+ *
+ * YOSHIFUJI Hideaki @USAGI
+ * reworked default router selection.
+ * - respect outgoing interface
+ * - select from (probably) reachable routers (i.e.
+ * routers in REACHABLE, STALE, DELAY or PROBE states).
+ * - always select the same router if it is (probably)
+ * reachable. otherwise, round-robin the list.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/route.h>
+#include <linux/netdevice.h>
+#include <linux/in6.h>
+#include <linux/inet.h>
+#include <linux/init.h>
+#include <linux/netlink.h>
+#include <linux/if_arp.h>
+
+#ifdef CONFIG_PROC_FS
+#include <linux/proc_fs.h>
+#endif
+
+#include <net/snmp.h>
+#include <net/ipv6.h>
+#include <net/ip6_fib.h>
+#include <net/ip6_route.h>
+#include <net/ndisc.h>
+#include <net/addrconf.h>
+#include <net/tcp.h>
+#include <linux/rtnetlink.h>
+
+#include <asm/uaccess.h>
+
+#ifdef CONFIG_SYSCTL
+#include <linux/sysctl.h>
+#endif
+
+#undef CONFIG_RT6_POLICY
+
+/* Set to 3 to get tracing. */
+#ifdef CONFIG_IPV6_RT6_DEBUG
+#define RT6_DEBUG 3
+#else
+#define RT6_DEBUG 1
+#endif
+
+#define RDBG(x) printk x
+#define NORDBG(x) do { ; } while(0)
+
+#define RT6_TRACE(x...) RDBG((x))
+
+#if RT6_DEBUG >= 3
+#define RT6_TRACE3(x...) RDBG((x))
+#else
+#define RT6_TRACE3(x...) NORDBG((x))
+#endif
+
+#if RT6_DEBUG >= 2
+#define RT6_TRACE2(x...) RDBG((x))
+#else
+#define RT6_TRACE2(x...) NORDBG((x))
+#endif
+
+#if RT6_DEBUG >= 1
+#define RT6_TRACE1(x...) RDBG((x))
+#else
+#define RT6_TRACE1(x...) NORDBG((x))
+#endif
+
+
+int ip6_rt_max_size = 4096;
+int ip6_rt_gc_min_interval = HZ / 2;
+int ip6_rt_gc_timeout = 60*HZ;
+int ip6_rt_gc_interval = 30*HZ;
+int ip6_rt_gc_elasticity = 9;
+int ip6_rt_mtu_expires = 10*60*HZ;
+int ip6_rt_min_advmss = IPV6_MIN_MTU - 20 - 40;
+
+static struct rt6_info * ip6_rt_copy(struct rt6_info *ort);
+static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie);
+static struct dst_entry *ip6_dst_reroute(struct dst_entry *dst,
+ struct sk_buff *skb);
+static struct dst_entry *ip6_negative_advice(struct dst_entry *);
+static int ip6_dst_gc(void);
+
+static int ip6_pkt_discard(struct sk_buff *skb);
+static void ip6_link_failure(struct sk_buff *skb);
+
+#if defined(CONFIG_IPV6_ROUTE_INFO)
+static struct rt6_info *rt6_specific_route(struct in6_addr *prefix, int prefixlen,
+ struct in6_addr *saddr, int ifindex);
+#endif
+
+struct dst_ops ip6_dst_ops = {
+ AF_INET6,
+ __constant_htons(ETH_P_IPV6),
+ 1024,
+
+ ip6_dst_gc,
+ ip6_dst_check,
+ ip6_dst_reroute,
+ NULL,
+ ip6_negative_advice,
+ ip6_link_failure,
+ sizeof(struct rt6_info),
+};
+
+struct rt6_info ip6_null_entry = {
+ {{NULL, ATOMIC_INIT(1), 1, &loopback_dev,
+ -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ -ENETUNREACH, NULL, NULL,
+ ip6_pkt_discard, ip6_pkt_discard,
+#ifdef CONFIG_NET_CLS_ROUTE
+ 0,
+#endif
+ &ip6_dst_ops}},
+ NULL, {{{0}}}, RTF_REJECT|RTF_NONEXTHOP, ~0U,
+ 255, ATOMIC_INIT(1), {NULL}, {{{{0}}}, 0}, {{{{0}}}, 0}
+};
+
+struct fib6_node ip6_routing_table = {
+ NULL, NULL, NULL, NULL,
+ &ip6_null_entry,
+ 0, RTN_ROOT|RTN_TL_ROOT|RTN_RTINFO, 0
+};
+
+#ifdef CONFIG_RT6_POLICY
+int ip6_rt_policy = 0;
+
+struct pol_chain *rt6_pol_list = NULL;
+
+
+static int rt6_flow_match_in(struct rt6_info *rt, struct sk_buff *skb);
+static int rt6_flow_match_out(struct rt6_info *rt, struct sock *sk);
+
+static struct rt6_info *rt6_flow_lookup(struct rt6_info *rt,
+ struct in6_addr *daddr,
+ struct in6_addr *saddr,
+ struct fl_acc_args *args);
+
+#else
+#define ip6_rt_policy (0)
+#endif
+
+/* Protects all the ip6 fib */
+
+rwlock_t rt6_lock = RW_LOCK_UNLOCKED;
+
+
+/*
+ * Route lookup. Any rt6_lock is implied.
+ */
+
+static __inline__ struct rt6_info *rt6_device_match(struct rt6_info *rt,
+ int oif,
+ int strict,
+ int scope)
+{
+ struct rt6_info *local = NULL;
+ struct rt6_info *sprt;
+#ifdef CONFIG_IPV6_ZONE
+ struct net_device *odev = NULL;
+ struct inet6_dev *oidev = NULL;
+#endif
+
+ if (oif) {
+#ifdef CONFIG_IPV6_ZONE
+ if (scope < 0 || scope >= 16)
+ scope = 0; /*XXX*/
+
+ odev = dev_get_by_index(oif);
+ if (odev)
+ oidev = in6_dev_get(odev);
+#endif
+
+ for (sprt = rt; sprt; sprt = sprt->u.next) {
+ struct net_device *dev = sprt->rt6i_dev;
+#ifdef CONFIG_IPV6_ZONE
+ struct inet6_dev *idev;
+#endif
+ if (dev->ifindex == oif) {
+ rt = sprt;
+ goto out;
+ }
+#ifdef CONFIG_IPV6_ZONE
+ idev = in6_dev_get(dev);
+ if (idev) {
+ if (oidev) {
+ if (idev->zone.zoneid[scope] == 0 ||
+ oidev->zone.zoneid[scope] == idev->zone.zoneid[scope]) {
+ in6_dev_put(idev);
+ rt = sprt;
+ goto out;
+ }
+ }
+ in6_dev_put(idev);
+ }
+#endif
+ if (dev->flags&IFF_LOOPBACK)
+ local = sprt;
+ }
+
+ if (local) {
+ rt = local;
+ goto out;
+ }
+
+ if (strict) {
+ rt = &ip6_null_entry;
+ goto out;
+ }
+ }
+out:
+#ifdef CONFIG_IPV6_ZONE
+ if (odev) {
+ if (oidev)
+ in6_dev_put(oidev);
+ dev_put(odev);
+ }
+#endif
+ return rt;
+}
+
+/*
+ * pointer to the last default router chosen. BH is disabled locally.
+ */
+#if !defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+struct rt6_info *rt6_dflt_pointer = NULL;
+spinlock_t rt6_dflt_lock = SPIN_LOCK_UNLOCKED;
+#endif
+
+/* Default Router Selection (RFC 2461 6.3.6) */
+static int __rt6_score_dflt(struct rt6_info *sprt, struct rt6_info *dflt, int oif)
+{
+ struct neighbour *neigh = sprt->rt6i_nexthop;
+ int m = oif ? 0 : 8;
+
+ if (!neigh)
+ return -1;
+
+ if (oif && sprt->rt6i_dev &&
+ sprt->rt6i_dev->ifindex == oif)
+ m += 8;
+
+#if !defined(CONFIG_IPV6_ROUTER_PREF)
+ if (sprt == dflt)
+ m += 4;
+#endif
+
+ read_lock_bh(&neigh->lock);
+ switch (neigh->nud_state) {
+ case NUD_REACHABLE:
+ m += 3;
+ break;
+
+ case NUD_STALE:
+ case NUD_DELAY:
+ case NUD_PROBE:
+ m += 2;
+ break;
+
+ case NUD_NOARP:
+ case NUD_PERMANENT:
+ m += 1;
+ break;
+
+ case NUD_INCOMPLETE:
+ default:
+ m = -1;
+ }
+ read_unlock_bh(&neigh->lock);
+
+ return m;
+}
+
+static struct rt6_info *rt6_best_dflt(struct rt6_info *rt, struct rt6_info **head, int oif)
+{
+ struct rt6_info *match = NULL;
+ struct rt6_info *sprt;
+#if defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ struct rt6_info *last = NULL;
+#endif
+ int mpri = 0;
+#if defined(CONFIG_IPV6_ROUTER_PREF)
+ u32 metric = 0;
+ int pref = -3;
+#else
+ static const int okpri = 12; /* device match, prob. reachable */
+#endif
+
+ if (head != NULL && *head != rt)
+ head = NULL; /*XXX*/
+
+#if !defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ spin_lock(&rt6_dflt_lock);
+#endif
+#if !defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ if (rt6_dflt_pointer) {
+ for (sprt = rt; sprt; sprt = sprt->u.next) {
+ if (sprt == rt6_dflt_pointer)
+ break;
+ }
+ if (!sprt)
+ rt6_dflt_pointer = NULL; /* for sure */
+ }
+#endif
+
+#if defined(CONFIG_IPV6_ROUTER_PREF)
+#if !defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ if (rt6_dflt_pointer) {
+ for (sprt = rt6_dflt_pointer->u.next; sprt; sprt = sprt->u.next) {
+ int m, p;
+
+ if ((metric != 0 && sprt->rt6i_metric > metric) ||
+ sprt->u.dst.obsolete > 0 ||
+ sprt->u.dst.error != 0)
+ continue;
+
+#if defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ m = __rt6_score_dflt(sprt, rt, oif);
+#else
+ m = __rt6_score_dflt(sprt, rt6_dflt_pointer, oif);
+#endif
+ if (m < mpri)
+ continue;
+ p = IPV6_SIGNEDPREF(IPV6_UNSHIFT_PREF(sprt->rt6i_flags));
+ if (sprt->rt6i_metric < metric || m > mpri || p > pref) {
+ match = sprt;
+ metric = sprt->rt6i_metric;
+ mpri = m;
+ pref = p;
+ }
+ }
+ }
+#endif
+#if defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ metric = rt->rt6i_metric;
+#endif
+ for (sprt = rt; sprt; sprt = sprt->u.next) {
+ int m, p;
+#if defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ if (sprt->rt6i_metric > metric)
+ break;
+#else
+ if ((metric != 0 && sprt->rt6i_metric > metric) ||
+ sprt->u.dst.obsolete > 0 ||
+ sprt->u.dst.error != 0)
+ continue;
+
+#endif
+#if defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ m = __rt6_score_dflt(sprt, rt, oif);
+#else
+ m = __rt6_score_dflt(sprt, rt6_dflt_pointer, oif);
+#endif
+ if (m < mpri)
+ continue;
+ p = IPV6_SIGNEDPREF(IPV6_UNSHIFT_PREF(sprt->rt6i_flags));
+ if (
+#if !defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ sprt->rt6i_metric < metric ||
+#endif
+ m > mpri || p > pref) {
+ match = sprt;
+ metric = sprt->rt6i_metric;
+ mpri = m;
+ pref = p;
+ }
+#if defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ last = sprt;
+#else
+ if (sprt == rt6_dflt_pointer)
+ break;
+#endif
+ }
+#else /* CONFIG_IPV6_ROUTER_PREF / !CONFIG_IPV6_ROUTER_PREF */
+ for (sprt = rt; sprt; sprt = sprt->u.next) {
+ int m;
+#if defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ m = __rt6_score_dflt(sprt, rt, oif);
+#else
+ m = __rt6_score_dflt(sprt, rt6_dflt_pointer, oif);
+#endif
+
+ if (m > mpri || m >= okpri) {
+ match = sprt;
+ mpri = m;
+ if (m >= okpri) {
+ /* we choose the lastest default router if it
+ * is in (probably) reachable state.
+ * If route changed, we should do pmtu
+ * discovery. --yoshfuji
+ */
+ break;
+ }
+ }
+ }
+
+ if (!match) {
+ /*
+ * No default routers are known to be reachable.
+ * SHOULD round robin
+ */
+ if (rt6_dflt_pointer) {
+ for (sprt = rt6_dflt_pointer->u.next;
+ sprt; sprt = sprt->u.next) {
+ if (sprt->u.dst.obsolete <= 0 &&
+ sprt->u.dst.error == 0) {
+ match = sprt;
+ break;
+ }
+ }
+ for (sprt = rt;
+ !match && sprt;
+ sprt = sprt->u.next) {
+ if (sprt->u.dst.obsolete <= 0 &&
+ sprt->u.dst.error == 0) {
+ match = sprt;
+ break;
+ }
+ if (sprt == rt6_dflt_pointer)
+ break;
+ }
+ }
+ }
+#endif /* !CONFIG_IPV6_ROUTER_PREF */
+
+ if (match) {
+#if defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ if (rt != last && last) {
+ *head = rt->u.next;
+ rt->u.next = last->u.next;
+ last->u.next = rt;
+ }
+#else
+ if (rt6_dflt_pointer != match)
+ RT6_TRACE1(KERN_INFO
+ "changed default router: %p->%p\n",
+ rt6_dflt_pointer, match);
+ rt6_dflt_pointer = match;
+#endif
+ }
+#if !defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ spin_unlock(&rt6_dflt_lock);
+#endif
+
+ if (!match) {
+ /*
+ * Last Resort: if no default routers found,
+ * use addrconf default route.
+ * We don't record this route.
+ */
+ for (sprt = ip6_routing_table.leaf;
+ sprt; sprt = sprt->u.next) {
+ if ((sprt->rt6i_flags & RTF_DEFAULT) &&
+ (!oif ||
+ (sprt->rt6i_dev &&
+ sprt->rt6i_dev->ifindex == oif))) {
+ match = sprt;
+ break;
+ }
+ }
+ if (!match) {
+ /* no default route. give up. */
+ match = &ip6_null_entry;
+ }
+ }
+
+ return match;
+}
+
+#if defined(CONFIG_IPV6_ROUTER_PREF)
+#if defined(CONFIG_IPV6_ROUTE_INFO)
+int rt6_route_rcv(struct net_device *dev, u8 *opt, int len, struct in6_addr *gwaddr)
+{
+ struct route_info *rinfo = (struct route_info *) opt;
+ u32 lifetime;
+ int pref;
+ struct inet6_dev *in6_dev;
+ struct rt6_info *rt;
+ struct in6_addr prefix, *prefix_ptr;
+
+ if (len < sizeof(struct route_info)) {
+ if (net_ratelimit())
+ RT6_TRACE2(KERN_WARNING
+ "addrconf: too short route information option\n");
+ return -EINVAL;
+ }
+ if (rinfo->length > 3) {
+ if (net_ratelimit())
+ RT6_TRACE2(KERN_WARNING
+ "addrconf: too long route information option\n");
+ return -EINVAL;
+ }
+ if (rinfo->prefix_len > 128) {
+ if (net_ratelimit())
+ RT6_TRACE2(KERN_WARNING
+ "addrconf: invalid prefix length in route informatio\n");
+ return -EINVAL;
+ }
+ pref = IPV6_SIGNEDPREF(rinfo->route_pref);
+ if (pref < -1)
+ return -EINVAL;
+
+ lifetime = htonl(rinfo->lifetime);
+#if 0
+ if (lifetime == 0xffffffff) {
+ /* infinity */
+ } else
+#endif
+ if (lifetime > 0x7fffffff/HZ) {
+ /* Avoid arithemtic overflow */
+ if (net_ratelimit())
+ RT6_TRACE1(KERN_WARNING
+ "addrconf: lifetime %u is too long; adjusted to %u.\n",
+ lifetime, 0x7fffffff/HZ-1);
+ lifetime = 0x7fffffff/HZ-1;
+ }
+
+ in6_dev = in6_dev_get(dev);
+ if (in6_dev == NULL) {
+ if (net_ratelimit())
+ RT6_TRACE1(KERN_WARNING
+ "addrconf: device %s not configured\n", dev->name);
+ return -ENODEV;
+ }
+
+ if (rinfo->length < 3) {
+ memset(&prefix, 0, sizeof(prefix));
+ memcpy(&prefix, rinfo->prefix, (rinfo->length-1)<<3);
+ prefix_ptr = &prefix;
+ } else {
+ prefix_ptr = (struct in6_addr *)rinfo->prefix;
+ }
+
+ rt = rt6_specific_route(prefix_ptr, rinfo->prefix_len, gwaddr, dev->ifindex);
+ if (rt && lifetime == 0) {
+ ip6_del_rt(rt, NULL);
+ rt = NULL;
+ }
+
+ if (!rt && lifetime) {
+ /* new route */
+ struct in6_rtmsg rtmsg;
+
+ memset(&rtmsg, 0, sizeof(struct in6_rtmsg));
+ rtmsg.rtmsg_type = RTMSG_NEWROUTE;
+ ipv6_addr_copy(&rtmsg.rtmsg_dst, prefix_ptr); /*XXX*/
+ rtmsg.rtmsg_dst_len = rinfo->prefix_len;
+ ipv6_addr_copy(&rtmsg.rtmsg_gateway, gwaddr);
+ rtmsg.rtmsg_metric = 1024;
+ rtmsg.rtmsg_flags = RTF_GATEWAY | RTF_ADDRCONF | RTF_DEFAULT | RTF_UP | RTF_PREF(pref&3);
+
+ rtmsg.rtmsg_ifindex = dev->ifindex;
+
+ ip6_route_add(&rtmsg, NULL);
+ }
+ if (rt) {
+ rt->rt6i_flags = (rt->rt6i_flags & ~RTF_PREF_MASK) | RTF_PREF(pref&3);
+ rt->rt6i_expires = jiffies + HZ * lifetime;
+ dst_release(&rt->u.dst);
+ }
+ return 0;
+}
+
+static struct rt6_info *rt6_specific_route(struct in6_addr *prefix, int prefixlen,
+ struct in6_addr *saddr, int ifindex)
+{
+ struct fib6_node *fn;
+ struct rt6_info *rt = NULL;
+
+ write_lock_bh(&rt6_lock);
+ fn = fib6_locate(&ip6_routing_table,
+ prefix, prefixlen,
+ NULL, 0);
+ if (fn == NULL)
+ goto out;
+ for (rt = fn->leaf; rt; rt = rt->u.next) {
+ if (rt->rt6i_dev == NULL ||
+ rt->rt6i_dev->ifindex != ifindex)
+ continue;
+ if (!(rt->rt6i_flags & RTF_DEFAULT))
+ continue;
+ if (!(rt->rt6i_flags & RTF_GATEWAY))
+ continue;
+ if (ipv6_addr_cmp(&rt->rt6i_gateway, saddr))
+ continue;
+ dst_hold(&rt->u.dst);
+ break;
+ }
+out:
+ write_unlock_bh(&rt6_lock);
+ return rt;
+}
+#endif
+#endif
+
+struct rt6_info *rt6_lookup(struct in6_addr *daddr, struct in6_addr *saddr,
+ int oif, int flags)
+{
+ struct fib6_node *fn;
+ struct rt6_info *rt;
+ int scope = ipv6_addr_src_scope(daddr);
+
+ read_lock_bh(&rt6_lock);
+ fn = fib6_lookup(&ip6_routing_table, daddr, saddr);
+ rt = rt6_device_match(fn->leaf, oif, !!(flags & RT6_LOOKUP_FLAG_STRICT), scope);
+ dst_hold(&rt->u.dst);
+ rt->u.dst.__use++;
+ read_unlock_bh(&rt6_lock);
+
+ if (!(flags & RT6_LOOKUP_FLAG_NOUSE))
+ rt->u.dst.lastuse = jiffies;
+ if (rt->u.dst.error == 0)
+ return rt;
+ dst_release(&rt->u.dst);
+ return NULL;
+}
+
+/* rt6_ins is called with FREE rt6_lock.
+ It takes new route entry, the addition fails by any reason the
+ route is freed. In any case, if caller does not hold it, it may
+ be destroyed.
+ */
+
+static int rt6_ins(struct rt6_info *rt, struct nlmsghdr *nlh)
+{
+ int err;
+
+ write_lock_bh(&rt6_lock);
+ err = fib6_add(&ip6_routing_table, rt, nlh);
+ write_unlock_bh(&rt6_lock);
+
+ return err;
+}
+
+/* No rt6_lock! If COW failed, the function returns dead route entry
+ with dst->error set to errno value.
+ */
+
+static struct rt6_info *rt6_cow(struct rt6_info *ort, struct in6_addr *daddr,
+ struct in6_addr *saddr)
+{
+ int err;
+ struct rt6_info *rt;
+
+ /*
+ * Clone the route.
+ */
+
+ rt = ip6_rt_copy(ort);
+
+ if (rt) {
+ ipv6_addr_copy(&rt->rt6i_dst.addr, daddr);
+
+ if (!(rt->rt6i_flags&RTF_GATEWAY))
+ ipv6_addr_copy(&rt->rt6i_gateway, daddr);
+
+ rt->rt6i_dst.plen = 128;
+ rt->rt6i_flags |= RTF_CACHE;
+ rt->u.dst.flags |= DST_HOST;
+
+#ifdef CONFIG_IPV6_SUBTREES
+ rt->rt6i_src.plen = ort->rt6i_src.plen;
+#endif
+
+ rt->rt6i_nexthop = ndisc_get_neigh(rt->rt6i_dev, &rt->rt6i_gateway);
+
+ dst_hold(&rt->u.dst);
+
+ err = rt6_ins(rt, NULL);
+ if (err == 0)
+ return rt;
+
+ rt->u.dst.error = err;
+
+ return rt;
+ }
+ dst_hold(&ip6_null_entry.u.dst);
+ return &ip6_null_entry;
+}
+
+#ifdef CONFIG_RT6_POLICY
+static __inline__ struct rt6_info *rt6_flow_lookup_in(struct rt6_info *rt,
+ struct sk_buff *skb)
+{
+ struct in6_addr *daddr, *saddr;
+ struct fl_acc_args arg;
+
+ arg.type = FL_ARG_FORWARD;
+ arg.fl_u.skb = skb;
+
+ saddr = &skb->nh.ipv6h->saddr;
+ daddr = &skb->nh.ipv6h->daddr;
+
+ return rt6_flow_lookup(rt, daddr, saddr, &arg);
+}
+
+static __inline__ struct rt6_info *rt6_flow_lookup_out(struct rt6_info *rt,
+ struct sock *sk,
+ struct flowi *fl)
+{
+ struct fl_acc_args arg;
+
+ arg.type = FL_ARG_ORIGIN;
+ arg.fl_u.fl_o.sk = sk;
+ arg.fl_u.fl_o.flow = fl;
+
+ return rt6_flow_lookup(rt, fl->nl_u.ip6_u.daddr, fl->nl_u.ip6_u.saddr,
+ &arg);
+}
+
+#endif
+
+#define BACKTRACK() \
+if (rt == &ip6_null_entry && strict) { \
+ while ((fn = fn->parent) != NULL) { \
+ if (fn->fn_flags & RTN_ROOT) { \
+ dst_hold(&rt->u.dst); \
+ goto out; \
+ } \
+ if (fn->fn_flags & RTN_RTINFO) \
+ goto restart; \
+ } \
+}
+
+
+void ip6_route_input(struct sk_buff *skb)
+{
+ struct fib6_node *fn;
+ struct rt6_info *rt;
+ int strict, addrtype;
+ int attempts = 3;
+
+ addrtype = __ipv6_addr_type(&skb->nh.ipv6h->daddr);
+#ifdef CONFIG_IPV6_ZONE
+ strict = (addrtype & IPV6_ADDR_MULTICAST) ||
+ __ipv6_addr_src_scope(addrtype) < IPV6_ADDR_SCOPE_GLOBAL;
+#else
+ strict = (addrtype & IPV6_ADDR_MULTICAST) ||
+ __ipv6_addr_src_scope(addrtype) == IPV6_ADDR_SCOPE_LINKLOCAL;
+#endif
+
+relookup:
+ read_lock_bh(&rt6_lock);
+
+ fn = fib6_lookup(&ip6_routing_table, &skb->nh.ipv6h->daddr,
+ &skb->nh.ipv6h->saddr);
+
+restart:
+ rt = fn->leaf;
+
+ if ((rt->rt6i_flags & RTF_CACHE)) {
+ if (ip6_rt_policy == 0) {
+ rt = rt6_device_match(rt, skb->dev->ifindex, strict, __ipv6_addr_src_scope(addrtype));
+ BACKTRACK();
+ dst_hold(&rt->u.dst);
+ goto out;
+ }
+
+#ifdef CONFIG_RT6_POLICY
+ if ((rt->rt6i_flags & RTF_FLOW)) {
+ struct rt6_info *sprt;
+
+ for (sprt = rt; sprt; sprt = sprt->u.next) {
+ if (rt6_flow_match_in(sprt, skb)) {
+ rt = sprt;
+ dst_hold(&rt->u.dst);
+ goto out;
+ }
+ }
+ }
+#endif
+ }
+
+ rt = rt6_device_match(rt, skb->dev->ifindex, 0, __ipv6_addr_src_scope(addrtype));
+ BACKTRACK();
+
+ if (ip6_rt_policy == 0) {
+ if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP)) {
+ read_unlock_bh(&rt6_lock);
+
+ rt = rt6_cow(rt, &skb->nh.ipv6h->daddr,
+ &skb->nh.ipv6h->saddr);
+
+ if (rt->u.dst.error != -EEXIST || --attempts <= 0)
+ goto out2;
+ /* Race condition! In the gap, when rt6_lock was
+ released someone could insert this route. Relookup.
+ */
+ goto relookup;
+ }
+ dst_hold(&rt->u.dst);
+ } else {
+#ifdef CONFIG_RT6_POLICY
+ rt = rt6_flow_lookup_in(rt, skb);
+#else
+ /* NEVER REACHED */
+#endif
+ }
+
+out:
+ read_unlock_bh(&rt6_lock);
+out2:
+ rt->u.dst.lastuse = jiffies;
+ rt->u.dst.__use++;
+ skb->dst = (struct dst_entry *) rt;
+}
+
+struct dst_entry * ip6_route_output(struct sock *sk, struct flowi *fl)
+{
+ struct fib6_node *fn;
+ struct rt6_info *rt;
+ int strict, addrtype;
+ int attempts = 3;
+
+ addrtype = __ipv6_addr_type(fl->nl_u.ip6_u.daddr);
+#ifdef CONFIG_IPV6_ZONE
+ strict = (addrtype & IPV6_ADDR_MULTICAST) ||
+ __ipv6_addr_src_scope(addrtype) < IPV6_ADDR_SCOPE_GLOBAL;
+#else
+ strict = (addrtype & IPV6_ADDR_MULTICAST) ||
+ __ipv6_addr_src_scope(addrtype) == IPV6_ADDR_SCOPE_LINKLOCAL;
+#endif
+
+relookup:
+ read_lock_bh(&rt6_lock);
+
+ fn = fib6_lookup(&ip6_routing_table, fl->nl_u.ip6_u.daddr,
+ fl->nl_u.ip6_u.saddr);
+
+restart:
+ rt = fn->leaf;
+
+ if ((rt->rt6i_flags & RTF_CACHE)) {
+ if (ip6_rt_policy == 0) {
+ rt = rt6_device_match(rt, fl->oif, strict, __ipv6_addr_src_scope(addrtype));
+ BACKTRACK();
+ dst_hold(&rt->u.dst);
+ goto out;
+ }
+
+#ifdef CONFIG_RT6_POLICY
+ if ((rt->rt6i_flags & RTF_FLOW)) {
+ struct rt6_info *sprt;
+
+ for (sprt = rt; sprt; sprt = sprt->u.next) {
+ if (rt6_flow_match_out(sprt, sk)) {
+ rt = sprt;
+ dst_hold(&rt->u.dst);
+ goto out;
+ }
+ }
+ }
+#endif
+ }
+ if (rt->rt6i_flags & RTF_DEFAULT) {
+ rt = rt6_best_dflt(rt, &fn->leaf, fl->oif);
+ } else {
+ rt = rt6_device_match(rt, fl->oif, strict, __ipv6_addr_src_scope(addrtype));
+ BACKTRACK();
+ }
+
+ if (ip6_rt_policy == 0) {
+ if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP)) {
+ read_unlock_bh(&rt6_lock);
+
+ rt = rt6_cow(rt, fl->nl_u.ip6_u.daddr,
+ fl->nl_u.ip6_u.saddr);
+
+ if (rt->u.dst.error != -EEXIST || --attempts <= 0)
+ goto out2;
+
+ /* Race condition! In the gap, when rt6_lock was
+ released someone could insert this route. Relookup.
+ */
+ goto relookup;
+ }
+ dst_hold(&rt->u.dst);
+ } else {
+#ifdef CONFIG_RT6_POLICY
+ rt = rt6_flow_lookup_out(rt, sk, fl);
+#else
+ /* NEVER REACHED */
+#endif
+ }
+
+out:
+ read_unlock_bh(&rt6_lock);
+out2:
+ rt->u.dst.lastuse = jiffies;
+ rt->u.dst.__use++;
+ return &rt->u.dst;
+}
+
+
+/*
+ * Destination cache support functions
+ */
+
+static struct dst_entry *ip6_dst_check(struct dst_entry *dst, u32 cookie)
+{
+ struct rt6_info *rt;
+
+ rt = (struct rt6_info *) dst;
+
+ if (rt && rt->rt6i_node && (rt->rt6i_node->fn_sernum == cookie))
+ return dst;
+
+ dst_release(dst);
+ return NULL;
+}
+
+static struct dst_entry *ip6_dst_reroute(struct dst_entry *dst, struct sk_buff *skb)
+{
+ /*
+ * FIXME
+ */
+ RT6_TRACE3(KERN_DEBUG "ip6_dst_reroute(%p,%p)[%p] (AIEEE)\n", dst, skb,
+ __builtin_return_address(0));
+ return NULL;
+}
+
+static struct dst_entry *ip6_negative_advice(struct dst_entry *dst)
+{
+ struct rt6_info *rt = (struct rt6_info *) dst;
+
+ if (rt) {
+ if (rt->rt6i_flags & RTF_CACHE)
+ ip6_del_rt(rt, NULL);
+ else
+ dst_release(dst);
+ }
+ return NULL;
+}
+
+static void ip6_link_failure(struct sk_buff *skb)
+{
+ struct rt6_info *rt;
+
+ icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_ADDR_UNREACH, 0, skb->dev);
+
+ rt = (struct rt6_info *) skb->dst;
+ if (rt) {
+ if (rt->rt6i_flags&RTF_CACHE) {
+ dst_set_expires(&rt->u.dst, 0);
+ rt->rt6i_flags |= RTF_EXPIRES;
+ } else if (rt->rt6i_node && (rt->rt6i_flags & RTF_DEFAULT))
+ rt->rt6i_node->fn_sernum = -1;
+ }
+}
+
+static int ip6_dst_gc()
+{
+ static unsigned expire = 30*HZ;
+ static unsigned long last_gc;
+ unsigned long now = jiffies;
+
+ if (time_after(last_gc + ip6_rt_gc_min_interval, now) &&
+ atomic_read(&ip6_dst_ops.entries) <= ip6_rt_max_size)
+ goto out;
+
+ expire++;
+ fib6_run_gc(expire);
+ last_gc = now;
+ if (atomic_read(&ip6_dst_ops.entries) < ip6_dst_ops.gc_thresh)
+ expire = ip6_rt_gc_timeout>>1;
+
+out:
+ expire -= expire>>ip6_rt_gc_elasticity;
+ return (atomic_read(&ip6_dst_ops.entries) > ip6_rt_max_size);
+}
+
+static int ipv6_get_mtu(struct net_device *dev)
+{
+ int mtu = IPV6_MIN_MTU;
+ struct inet6_dev *idev;
+
+ idev = in6_dev_get(dev);
+ if (idev) {
+ mtu = idev->cnf.mtu6;
+ in6_dev_put(idev);
+ }
+ return mtu;
+}
+
+static int ipv6_get_hoplimit(struct net_device *dev)
+{
+ int hoplimit = ipv6_devconf.hop_limit;
+ struct inet6_dev *idev;
+
+ idev = in6_dev_get(dev);
+ if (idev) {
+ hoplimit = idev->cnf.hop_limit;
+ in6_dev_put(idev);
+ }
+ return hoplimit;
+}
+
+/*
+ *
+ */
+
+int ip6_route_add(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh)
+{
+ int err;
+ struct rtmsg *r;
+ struct rt6_info *rt;
+ struct net_device *dev = NULL;
+ int addr_type;
+
+ if (rtmsg->rtmsg_dst_len > 128 || rtmsg->rtmsg_src_len > 128)
+ return -EINVAL;
+#ifndef CONFIG_IPV6_SUBTREES
+ if (rtmsg->rtmsg_src_len)
+ return -EINVAL;
+#endif
+ if (rtmsg->rtmsg_metric == 0)
+ rtmsg->rtmsg_metric = IP6_RT_PRIO_USER;
+
+ rt = dst_alloc(&ip6_dst_ops);
+
+ if (rt == NULL)
+ return -ENOMEM;
+
+ rt->u.dst.obsolete = -1;
+ rt->rt6i_expires = rtmsg->rtmsg_info;
+ if (nlh && (r = NLMSG_DATA(nlh))) {
+ rt->rt6i_protocol = r->rtm_protocol;
+ } else {
+ rt->rt6i_protocol = RTPROT_BOOT;
+ }
+
+ addr_type = ipv6_addr_type(&rtmsg->rtmsg_dst);
+
+ if (addr_type & IPV6_ADDR_MULTICAST)
+ rt->u.dst.input = ip6_mc_input;
+ else
+ rt->u.dst.input = ip6_forward;
+
+ rt->u.dst.output = ip6_output;
+
+ if (rtmsg->rtmsg_ifindex) {
+ dev = dev_get_by_index(rtmsg->rtmsg_ifindex);
+ err = -ENODEV;
+ if (dev == NULL)
+ goto out;
+ }
+
+ ipv6_addr_prefix(&rt->rt6i_dst.addr,
+ &rtmsg->rtmsg_dst, rtmsg->rtmsg_dst_len);
+ rt->rt6i_dst.plen = rtmsg->rtmsg_dst_len;
+ if (rt->rt6i_dst.plen == 128)
+ rt->u.dst.flags = DST_HOST;
+
+#ifdef CONFIG_IPV6_SUBTREES
+ ipv6_addr_prefix(&rt->rt6i_src.addr,
+ &rtmsg->rtmsg_src, rtmsg->rtmsg_src_len);
+ rt->rt6i_src.plen = rtmsg->rtmsg_src_len;
+#endif
+
+ rt->rt6i_metric = rtmsg->rtmsg_metric;
+
+ /* We cannot add true routes via loopback here,
+ they would result in kernel looping; promote them to reject routes
+ */
+ if ((rtmsg->rtmsg_flags&RTF_REJECT) ||
+ (dev && (dev->flags&IFF_LOOPBACK) && !(addr_type&IPV6_ADDR_LOOPBACK))) {
+ if (dev)
+ dev_put(dev);
+ dev = &loopback_dev;
+ dev_hold(dev);
+ rt->u.dst.output = ip6_pkt_discard;
+ rt->u.dst.input = ip6_pkt_discard;
+ rt->u.dst.error = -ENETUNREACH;
+ rt->rt6i_flags = RTF_REJECT|RTF_NONEXTHOP;
+ goto install_route;
+ }
+
+ if (rtmsg->rtmsg_flags & RTF_GATEWAY) {
+ struct in6_addr *gw_addr;
+ int gwa_type;
+
+ gw_addr = &rtmsg->rtmsg_gateway;
+ ipv6_addr_copy(&rt->rt6i_gateway, &rtmsg->rtmsg_gateway);
+ gwa_type = ipv6_addr_type(gw_addr);
+
+ if (gwa_type != (IPV6_ADDR_LINKLOCAL|IPV6_ADDR_UNICAST)) {
+ struct rt6_info *grt;
+
+ /* IPv6 strictly inhibits using not link-local
+ addresses as nexthop address.
+ Otherwise, router will not able to send redirects.
+ It is very good, but in some (rare!) curcumstances
+ (SIT, PtP, NBMA NOARP links) it is handy to allow
+ some exceptions. --ANK
+ */
+ err = -EINVAL;
+ if (!(gwa_type&IPV6_ADDR_UNICAST))
+ goto out;
+
+ grt = rt6_lookup(gw_addr, NULL, rtmsg->rtmsg_ifindex, RT6_LOOKUP_FLAG_STRICT);
+
+ err = -EHOSTUNREACH;
+ if (grt == NULL)
+ goto out;
+ if (dev) {
+ if (dev != grt->rt6i_dev) {
+ dst_release(&grt->u.dst);
+ goto out;
+ }
+ } else {
+ dev = grt->rt6i_dev;
+ dev_hold(dev);
+ }
+ if (!(grt->rt6i_flags&RTF_GATEWAY))
+ err = 0;
+ dst_release(&grt->u.dst);
+
+ if (err)
+ goto out;
+ }
+ err = -EINVAL;
+ if (dev == NULL || (dev->flags&IFF_LOOPBACK))
+ goto out;
+ }
+
+ err = -ENODEV;
+ if (dev == NULL)
+ goto out;
+
+ if (rtmsg->rtmsg_flags & (RTF_GATEWAY|RTF_NONEXTHOP)) {
+ rt->rt6i_nexthop = __neigh_lookup_errno(&nd_tbl, &rt->rt6i_gateway, dev);
+ if (IS_ERR(rt->rt6i_nexthop)) {
+ err = PTR_ERR(rt->rt6i_nexthop);
+ rt->rt6i_nexthop = NULL;
+ goto out;
+ }
+ }
+
+ if (ipv6_addr_is_multicast(&rt->rt6i_dst.addr))
+ rt->rt6i_hoplimit = IPV6_DEFAULT_MCASTHOPS;
+ else
+ rt->rt6i_hoplimit = ipv6_get_hoplimit(dev);
+ rt->rt6i_flags = rtmsg->rtmsg_flags;
+
+install_route:
+ rt->u.dst.pmtu = ipv6_get_mtu(dev);
+ rt->u.dst.advmss = max_t(unsigned int, rt->u.dst.pmtu - 60, ip6_rt_min_advmss);
+ /* Maximal non-jumbo IPv6 payload is 65535 and corresponding
+ MSS is 65535 - tcp_header_size. 65535 is also valid and
+ means: "any MSS, rely only on pmtu discovery"
+ */
+ if (rt->u.dst.advmss > 65535-20)
+ rt->u.dst.advmss = 65535;
+ rt->u.dst.dev = dev;
+ return rt6_ins(rt, nlh);
+
+out:
+ if (dev)
+ dev_put(dev);
+ dst_free((struct dst_entry *) rt);
+ return err;
+}
+
+int ip6_del_rt(struct rt6_info *rt, struct nlmsghdr *nlh)
+{
+ int err;
+
+ write_lock_bh(&rt6_lock);
+
+#if !defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ spin_lock_bh(&rt6_dflt_lock);
+ rt6_dflt_pointer = NULL;
+ spin_unlock_bh(&rt6_dflt_lock);
+#endif
+
+ dst_release(&rt->u.dst);
+
+ err = fib6_del(rt, nlh);
+ write_unlock_bh(&rt6_lock);
+
+ return err;
+}
+
+int ip6_route_del(struct in6_rtmsg *rtmsg, struct nlmsghdr *nlh)
+{
+ struct fib6_node *fn;
+ struct rt6_info *rt;
+ int err = -ESRCH;
+
+ read_lock_bh(&rt6_lock);
+
+ fn = fib6_locate(&ip6_routing_table,
+ &rtmsg->rtmsg_dst, rtmsg->rtmsg_dst_len,
+ &rtmsg->rtmsg_src, rtmsg->rtmsg_src_len);
+
+ if (fn) {
+ for (rt = fn->leaf; rt; rt = rt->u.next) {
+ if (rtmsg->rtmsg_ifindex &&
+ (rt->rt6i_dev == NULL ||
+ rt->rt6i_dev->ifindex != rtmsg->rtmsg_ifindex))
+ continue;
+ if (rtmsg->rtmsg_flags&RTF_GATEWAY &&
+ ipv6_addr_cmp(&rtmsg->rtmsg_gateway, &rt->rt6i_gateway))
+ continue;
+ if (rtmsg->rtmsg_metric &&
+ rtmsg->rtmsg_metric != rt->rt6i_metric)
+ continue;
+ dst_hold(&rt->u.dst);
+ read_unlock_bh(&rt6_lock);
+
+ return ip6_del_rt(rt, nlh);
+ }
+ }
+ read_unlock_bh(&rt6_lock);
+
+ return err;
+}
+
+#ifdef CONFIG_IPV6_ZONE
+int ip6_get_zone(struct ip6_zoneid *zmsg)
+{
+ struct net_device *dev;
+ struct inet6_dev *idev;
+
+ if (!zmsg->zoneid[IPV6_ADDR_SCOPE_NODELOCAL])
+ return -EINVAL;
+ dev = dev_get_by_index(zmsg->zoneid[IPV6_ADDR_SCOPE_NODELOCAL]);
+ if (!dev)
+ return -ENODEV;
+ idev = in6_dev_get(dev);
+ if (!idev) {
+ dev_put(dev);
+ return -ENODEV;
+ }
+ read_lock(&idev->lock);
+ memcpy(zmsg, &idev->zone, sizeof(*zmsg));
+ read_unlock(&idev->lock);
+ in6_dev_put(idev);
+ dev_put(dev);
+ return 0;
+}
+
+int ip6_set_zone(struct ip6_zoneid *zmsg)
+{
+ struct net_device *dev, *odev;
+ struct inet6_dev *idev, *oidev;
+ int i;
+ int ret = 0;
+
+ if (!zmsg->zoneid[IPV6_ADDR_SCOPE_NODELOCAL])
+ return -EINVAL;
+
+ dev = dev_get_by_index(zmsg->zoneid[IPV6_ADDR_SCOPE_NODELOCAL]);
+ idev = dev ? in6_dev_get(dev) : NULL;
+ if (!idev) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ read_lock(&dev_base_lock);
+ write_lock(&idev->lock);
+
+ for (i=0; i<16; i++) {
+ if (zmsg->zoneid[i] & 0xf0000000) {
+ ret = -EINVAL;
+ goto unlock_out;
+ }
+ if (i <= IPV6_ADDR_SCOPE_NODELOCAL) {
+ if (!zmsg->zoneid[i]) {
+ zmsg->zoneid[i] = dev->ifindex;
+ } else if (zmsg->zoneid[i] != dev->ifindex) {
+ ret = -EINVAL;
+ goto unlock_out;
+ }
+ } else {
+ if (!zmsg->zoneid[i]) {
+ zmsg->zoneid[i] = idev->zone.zoneid[i];
+ }
+ }
+ }
+
+ for (odev = dev_base; odev; odev = odev->next) {
+ int match = 0;
+ if (odev == dev)
+ continue;
+ oidev = __in6_dev_get(odev);
+ if (!oidev)
+ continue;
+
+ for (i=0; i<16; i++) {
+ if (zmsg->zoneid[i] == oidev->zone.zoneid[i]) {
+ match++;
+ } else if (match) {
+ ret = -EINVAL;
+ goto unlock_out;
+ }
+ }
+ if (match)
+ break;
+ }
+ memcpy(&idev->zone, zmsg, sizeof(*zmsg));
+
+unlock_out:
+ write_unlock(&idev->lock);
+ read_unlock(&dev_base_lock);
+out:
+ if (idev)
+ in6_dev_put(idev);
+ dev_put(dev);
+ return ret;
+}
+#endif
+
+/*
+ * Handle redirects
+ */
+void rt6_redirect(struct in6_addr *dest, struct in6_addr *saddr,
+ struct neighbour *neigh, u8 *lladdr, int on_link)
+{
+ struct rt6_info *rt, *nrt;
+ int notify;
+
+ /* Locate old route to this destination. */
+ rt = rt6_lookup(dest, saddr, neigh->dev->ifindex, RT6_LOOKUP_FLAG_STRICT);
+
+ if (rt == NULL)
+ return;
+
+ if (neigh->dev != rt->rt6i_dev)
+ goto out;
+
+ /* Current route is on-link; redirect is always invalid.
+
+ Seems, previous statement is not true. It could
+ be node, which looks for us as on-link (f.e. proxy ndisc)
+ But then router serving it might decide, that we should
+ know truth 8)8) --ANK (980726).
+ */
+ if (!(rt->rt6i_flags&RTF_GATEWAY))
+ goto out;
+
+ /* Redirect received -> path was valid.
+ Look, redirects are sent only in response to data packets,
+ so that this nexthop apparently is reachable. --ANK
+ */
+ dst_confirm(&rt->u.dst);
+
+ /* Duplicate redirect: silently ignore. */
+ if (neigh == rt->u.dst.neighbour)
+ goto out;
+
+ /*
+ * RFC 1970 specifies that redirects should only be
+ * accepted if they come from the nexthop to the target.
+ * Due to the way default routers are chosen, this notion
+ * is a bit fuzzy and one might need to check all default
+ * routers.
+ */
+
+ if (ipv6_addr_cmp(saddr, &rt->rt6i_gateway)) {
+ if (rt->rt6i_flags & RTF_DEFAULT) {
+ struct rt6_info *rt1;
+
+ read_lock(&rt6_lock);
+ for (rt1 = ip6_routing_table.leaf; rt1; rt1 = rt1->u.next) {
+ if (!ipv6_addr_cmp(saddr, &rt1->rt6i_gateway)) {
+ dst_hold(&rt1->u.dst);
+ dst_release(&rt->u.dst);
+ read_unlock(&rt6_lock);
+ rt = rt1;
+ goto source_ok;
+ }
+ }
+ read_unlock(&rt6_lock);
+ }
+ if (net_ratelimit())
+ RT6_TRACE2(KERN_DEBUG "rt6_redirect: source isn't a valid nexthop "
+ "for redirect target\n");
+ goto out;
+ }
+
+source_ok:
+ /*
+ * We have finally decided to accept it.
+ */
+
+#ifdef CONFIG_IPV6_NDISC_NEW
+ write_lock_bh(&neigh->lock);
+ notify = __neigh_update(neigh, lladdr, NUD_STALE,
+ NEIGH_UPDATE_F_IP6REDIRECT|
+ (on_link ? 0 : (NEIGH_UPDATE_F_OVERRIDE_VALID_ISROUTER|
+ NEIGH_UPDATE_F_ISROUTER)));
+#ifdef CONFIG_ARPD
+ if (notify > 0 && !neigh->parms->app_probes) {
+ write_unlock_bh(&neigh->lock);
+ } else
+#endif
+ write_unlock_bh(&neigh->lock);
+#endif
+
+ /* Redirect received -> path was valid.
+ Look, redirects are sent only in response to data packets,
+ so that this nexthop apparently is reachable. --ANK
+ */
+ dst_confirm(&rt->u.dst);
+
+ /* Duplicate redirect: silently ignore. */
+ if (neigh == rt->u.dst.neighbour)
+ goto out;
+
+ nrt = ip6_rt_copy(rt);
+ if (nrt == NULL)
+ goto out;
+
+ nrt->rt6i_flags = RTF_GATEWAY|RTF_UP|RTF_DYNAMIC|RTF_CACHE;
+ if (on_link)
+ nrt->rt6i_flags &= ~RTF_GATEWAY;
+
+ ipv6_addr_copy(&nrt->rt6i_dst.addr, dest);
+ nrt->rt6i_dst.plen = 128;
+ nrt->u.dst.flags |= DST_HOST;
+
+ ipv6_addr_copy(&nrt->rt6i_gateway, (struct in6_addr*)neigh->primary_key);
+ nrt->rt6i_nexthop = neigh_clone(neigh);
+ /* Reset pmtu, it may be better */
+ nrt->u.dst.pmtu = ipv6_get_mtu(neigh->dev);
+ nrt->u.dst.advmss = max_t(unsigned int, nrt->u.dst.pmtu - 60, ip6_rt_min_advmss);
+ if (rt->u.dst.advmss > 65535-20)
+ rt->u.dst.advmss = 65535;
+ nrt->rt6i_hoplimit = ipv6_get_hoplimit(neigh->dev);
+
+ if (rt6_ins(nrt, NULL))
+ goto out;
+
+ if (rt->rt6i_flags&RTF_CACHE) {
+ ip6_del_rt(rt, NULL);
+ return;
+ }
+
+out:
+ dst_release(&rt->u.dst);
+ return;
+}
+
+/*
+ * Handle ICMP "packet too big" messages
+ * i.e. Path MTU discovery
+ */
+
+void rt6_pmtu_discovery(struct in6_addr *daddr, struct in6_addr *saddr,
+ struct net_device *dev, u32 pmtu)
+{
+ struct rt6_info *rt, *nrt;
+
+ if (pmtu < IPV6_MIN_MTU) {
+ if (net_ratelimit())
+ RT6_TRACE2(KERN_DEBUG "rt6_pmtu_discovery: invalid MTU value %d\n",
+ pmtu);
+ /* According to RFC1981, the PMTU is set to the IPv6 minimum
+ link MTU if the node receives a Packet Too Big message
+ reporting next-hop MTU that is less than the IPv6 minimum MTU.
+ */
+ pmtu = IPV6_MIN_MTU;
+ }
+
+ rt = rt6_lookup(daddr, saddr, dev->ifindex, 0);
+
+ if (rt == NULL)
+ return;
+
+ if (pmtu >= rt->u.dst.pmtu)
+ goto out;
+
+ /* New mtu received -> path was valid.
+ They are sent only in response to data packets,
+ so that this nexthop apparently is reachable. --ANK
+ */
+ dst_confirm(&rt->u.dst);
+
+ /* Host route. If it is static, it would be better
+ not to override it, but add new one, so that
+ when cache entry will expire old pmtu
+ would return automatically.
+ */
+ if (rt->rt6i_flags & RTF_CACHE) {
+ rt->u.dst.pmtu = pmtu;
+ dst_set_expires(&rt->u.dst, ip6_rt_mtu_expires);
+ rt->rt6i_flags |= RTF_MODIFIED|RTF_EXPIRES;
+ goto out;
+ }
+
+ /* Network route.
+ Two cases are possible:
+ 1. It is connected route. Action: COW
+ 2. It is gatewayed route or NONEXTHOP route. Action: clone it.
+ */
+ if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP)) {
+ nrt = rt6_cow(rt, daddr, saddr);
+ if (!nrt->u.dst.error) {
+ nrt->u.dst.pmtu = pmtu;
+ /* According to RFC 1981, detecting PMTU increase shouldn't be
+ happened within 5 mins, the recommended timer is 10 mins.
+ Here this route expiration time is set to ip6_rt_mtu_expires
+ which is 10 mins. After 10 mins the decreased pmtu is expired
+ and detecting PMTU increase will be automatically happened.
+ */
+ dst_set_expires(&nrt->u.dst, ip6_rt_mtu_expires);
+ nrt->rt6i_flags |= RTF_DYNAMIC|RTF_EXPIRES;
+ }
+ } else {
+ nrt = ip6_rt_copy(rt);
+ if (nrt == NULL)
+ goto out;
+#ifdef CONFIG_IPV6_SUBTREES
+ nrt->rt6i_src.plen = rt->rt6i_src.plen;
+#endif
+ ipv6_addr_copy(&nrt->rt6i_dst.addr, daddr);
+ nrt->rt6i_dst.plen = 128;
+#ifdef CONFIG_IPV6_SUBTREES
+ ipv6_addr_copy(&nrt->rt6i_src.addr, &rt->rt6i_src.addr);
+ nrt->rt6i_src.plen = rt->rt6i_src.plen;
+ ipv6_wash_prefix(&rt->rt6i_src.addr, rt->rt6i_src.plen);
+#endif
+ nrt->u.dst.flags |= DST_HOST;
+ nrt->rt6i_nexthop = neigh_clone(rt->rt6i_nexthop);
+ dst_set_expires(&nrt->u.dst, ip6_rt_mtu_expires);
+ nrt->rt6i_flags |= RTF_DYNAMIC|RTF_CACHE|RTF_EXPIRES;
+ nrt->u.dst.pmtu = pmtu;
+ dst_hold(&nrt->u.dst); /* hold this until it expires */
+ rt6_ins(nrt, NULL);
+ }
+
+out:
+ dst_release(&rt->u.dst);
+}
+
+/*
+ * Misc support functions
+ */
+
+static struct rt6_info * ip6_rt_copy(struct rt6_info *ort)
+{
+ struct rt6_info *rt;
+
+ rt = dst_alloc(&ip6_dst_ops);
+
+ if (rt) {
+ rt->u.dst.input = ort->u.dst.input;
+ rt->u.dst.output = ort->u.dst.output;
+
+ memcpy(&rt->u.dst.mxlock, &ort->u.dst.mxlock, RTAX_MAX*sizeof(unsigned));
+ rt->u.dst.dev = ort->u.dst.dev;
+ if (rt->u.dst.dev)
+ dev_hold(rt->u.dst.dev);
+ rt->u.dst.lastuse = jiffies;
+ rt->rt6i_hoplimit = ort->rt6i_hoplimit;
+ rt->rt6i_expires = 0;
+
+ ipv6_addr_copy(&rt->rt6i_gateway, &ort->rt6i_gateway);
+ rt->rt6i_flags = ort->rt6i_flags & ~RTF_EXPIRES;
+ rt->rt6i_metric = 0;
+
+ memcpy(&rt->rt6i_dst, &ort->rt6i_dst, sizeof(struct rt6key));
+#ifdef CONFIG_IPV6_SUBTREES
+ memcpy(&rt->rt6i_src, &ort->rt6i_src, sizeof(struct rt6key));
+#endif
+ }
+ return rt;
+}
+
+int rt6_get_dfltrt(char *buffer, char **start, off_t offset, int length, int dummy)
+{
+/*
+ * for /proc/net/rt6_default
+ */
+ int len = 0;
+ char buf1[128];
+ struct rt6_info *rt;
+
+#if !defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ if (rt6_dflt_pointer) {
+ in6_ntop(&rt6_dflt_pointer->rt6i_gateway, buf1);
+ goto out;
+ }
+#endif
+
+ read_lock_bh(&rt6_lock);
+ for (rt = ip6_routing_table.leaf; rt; rt = rt->u.next) {
+ if (rt->rt6i_flags & RTF_DEFAULT) {
+ in6_ntop(&rt->rt6i_gateway, buf1);
+ break;
+ }
+ if (rt->u.next == NULL) {
+ strcpy(buf1, "no default router");
+ break;
+ }
+ }
+ read_unlock_bh(&rt6_lock);
+
+#if !defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+out:
+#endif
+ len += sprintf(buffer+len, "%s\n", buf1);
+ *start = buffer + offset;
+ len -= offset;
+ if (len > length)
+ len = length;
+ return len;
+}
+
+
+
+
+struct rt6_info *rt6_get_dflt_router(struct in6_addr *addr, struct net_device *dev)
+{
+ struct rt6_info *rt;
+ struct fib6_node *fn;
+
+ fn = &ip6_routing_table;
+
+ write_lock_bh(&rt6_lock);
+ for (rt = fn->leaf; rt; rt=rt->u.next) {
+ if (dev == rt->rt6i_dev &&
+ ipv6_addr_cmp(&rt->rt6i_gateway, addr) == 0)
+ break;
+ }
+ if (rt)
+ dst_hold(&rt->u.dst);
+ write_unlock_bh(&rt6_lock);
+ return rt;
+}
+
+struct rt6_info *rt6_add_dflt_router(struct in6_addr *gwaddr,
+ struct net_device *dev,
+ int pref)
+{
+ struct in6_rtmsg rtmsg;
+
+ memset(&rtmsg, 0, sizeof(struct in6_rtmsg));
+ rtmsg.rtmsg_type = RTMSG_NEWROUTE;
+ ipv6_addr_copy(&rtmsg.rtmsg_gateway, gwaddr);
+ rtmsg.rtmsg_metric = 1024;
+ rtmsg.rtmsg_flags = RTF_GATEWAY | RTF_ADDRCONF | RTF_DEFAULT | RTF_UP | RTF_PREF(pref&3) | RTF_EXPIRES;
+
+ rtmsg.rtmsg_ifindex = dev->ifindex;
+
+ ip6_route_add(&rtmsg, NULL);
+ return rt6_get_dflt_router(gwaddr, dev);
+}
+
+void rt6_purge_dflt_routers(int last_resort)
+{
+ struct rt6_info *rt;
+ u32 flags;
+
+ if (last_resort)
+ flags = RTF_ALLONLINK;
+ else
+ flags = RTF_DEFAULT | RTF_ADDRCONF;
+
+restart:
+ read_lock_bh(&rt6_lock);
+ for (rt = ip6_routing_table.leaf; rt; rt = rt->u.next) {
+ if (rt->rt6i_flags & flags) {
+ dst_hold(&rt->u.dst);
+
+#if !defined(CONFIG_IPV6_NEW_ROUNDROBIN)
+ spin_lock_bh(&rt6_dflt_lock);
+ rt6_dflt_pointer = NULL;
+ spin_unlock_bh(&rt6_dflt_lock);
+#endif
+
+ read_unlock_bh(&rt6_lock);
+
+ ip6_del_rt(rt, NULL);
+
+ goto restart;
+ }
+ }
+ read_unlock_bh(&rt6_lock);
+}
+
+int ipv6_route_ioctl(unsigned int cmd, void *arg)
+{
+ struct in6_rtmsg rtmsg;
+ int err;
+
+ switch(cmd) {
+ case SIOCADDRT: /* Add a route */
+ case SIOCDELRT: /* Delete a route */
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ err = copy_from_user(&rtmsg, arg,
+ sizeof(struct in6_rtmsg));
+ if (err)
+ return -EFAULT;
+
+ rtnl_lock();
+ switch (cmd) {
+ case SIOCADDRT:
+ err = ip6_route_add(&rtmsg, NULL);
+ break;
+ case SIOCDELRT:
+ err = ip6_route_del(&rtmsg, NULL);
+ break;
+ default:
+ err = -EINVAL;
+ }
+ rtnl_unlock();
+
+ return err;
+ };
+
+ return -EINVAL;
+}
+
+#ifdef CONFIG_IPV6_ZONE
+int ipv6_zone_ioctl(unsigned int cmd, void *arg)
+{
+ struct ip6_zoneid zmsg __attribute__((unused));
+ int err = -EINVAL;
+
+ switch(cmd) {
+ case SIOCSIFZONE:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ err = copy_from_user(&zmsg, arg, sizeof(zmsg));
+ if (err)
+ return -EFAULT;
+ err = ip6_set_zone(&zmsg);
+ break;
+
+ case SIOCGIFZONE:
+ err = copy_from_user(&zmsg, arg, sizeof(zmsg));
+ if (err)
+ return -EFAULT;
+ err = ip6_get_zone(&zmsg);
+ if (err == 0)
+ err = copy_to_user(arg, &zmsg, sizeof(zmsg));
+ break;
+ }
+ return err;
+}
+#endif
+
+/*
+ * Drop the packet on the floor
+ */
+
+int ip6_pkt_discard(struct sk_buff *skb)
+{
+ struct inet6_dev *idev = in6_dev_get(skb->dev);
+ IP6_INC_STATS(idev,Ip6OutNoRoutes);
+ icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_NOROUTE, 0, skb->dev);
+ if (idev)
+ in6_dev_put(idev);
+ kfree_skb(skb);
+ return 0;
+}
+
+/*
+ * Add address
+ */
+
+int ip6_rt_addr_add(struct in6_addr *addr, struct net_device *dev)
+{
+ struct rt6_info *rt;
+
+ rt = dst_alloc(&ip6_dst_ops);
+ if (rt == NULL)
+ return -ENOMEM;
+
+ rt->u.dst.flags = DST_HOST;
+ rt->u.dst.input = ip6_input;
+ rt->u.dst.output = ip6_output;
+ rt->rt6i_dev = dev_get_by_name("lo");
+ if (rt->rt6i_dev == NULL) {
+ if (net_ratelimit())
+ printk(KERN_WARNING
+ "ip6_rt_addr_addr(): cannot find loopback device.\n");
+ dst_free((struct dst_entry *) rt);
+ return -ENODEV;
+ }
+ rt->u.dst.pmtu = ipv6_get_mtu(rt->rt6i_dev);
+ rt->u.dst.advmss = max_t(unsigned int, rt->u.dst.pmtu - 60, ip6_rt_min_advmss);
+ if (rt->u.dst.advmss > 65535-20)
+ rt->u.dst.advmss = 65535;
+ rt->rt6i_hoplimit = ipv6_get_hoplimit(rt->rt6i_dev);
+ rt->u.dst.obsolete = -1;
+
+ rt->rt6i_flags = RTF_UP | RTF_NONEXTHOP;
+ rt->rt6i_nexthop = ndisc_get_neigh(rt->rt6i_dev, &rt->rt6i_gateway);
+ if (rt->rt6i_nexthop == NULL) {
+ dst_free((struct dst_entry *) rt);
+ return -ENOMEM;
+ }
+
+ ipv6_addr_copy(&rt->rt6i_dst.addr, addr);
+ rt->rt6i_dst.plen = 128;
+ rt6_ins(rt, NULL);
+
+ return 0;
+}
+
+/* Delete address. Warning: you should check that this address
+ disappeared before calling this function.
+ */
+
+int ip6_rt_addr_del(struct in6_addr *addr, struct net_device *dev)
+{
+ struct rt6_info *rt;
+ int err = -ENOENT;
+
+ rt = rt6_lookup(addr, NULL, loopback_dev.ifindex, RT6_LOOKUP_FLAG_STRICT);
+ if (rt) {
+ if (rt->rt6i_dst.plen == 128)
+ err = ip6_del_rt(rt, NULL);
+ else
+ dst_release(&rt->u.dst);
+ }
+
+ return err;
+}
+
+#ifdef CONFIG_RT6_POLICY
+
+static int rt6_flow_match_in(struct rt6_info *rt, struct sk_buff *skb)
+{
+ struct flow_filter *frule;
+ struct pkt_filter *filter;
+ int res = 1;
+
+ if ((frule = rt->rt6i_filter) == NULL)
+ goto out;
+
+ if (frule->type != FLR_INPUT) {
+ res = 0;
+ goto out;
+ }
+
+ for (filter = frule->u.filter; filter; filter = filter->next) {
+ __u32 *word;
+
+ word = (__u32 *) skb->h.raw;
+ word += filter->offset;
+
+ if ((*word ^ filter->value) & filter->mask) {
+ res = 0;
+ break;
+ }
+ }
+
+out:
+ return res;
+}
+
+static int rt6_flow_match_out(struct rt6_info *rt, struct sock *sk)
+{
+ struct flow_filter *frule;
+ int res = 1;
+
+ if ((frule = rt->rt6i_filter) == NULL)
+ goto out;
+
+ if (frule->type != FLR_INPUT) {
+ res = 0;
+ goto out;
+ }
+
+ if (frule->u.sk != sk)
+ res = 0;
+out:
+ return res;
+}
+
+static struct rt6_info *rt6_flow_lookup(struct rt6_info *rt,
+ struct in6_addr *daddr,
+ struct in6_addr *saddr,
+ struct fl_acc_args *args)
+{
+ struct flow_rule *frule;
+ struct rt6_info *nrt = NULL;
+ struct pol_chain *pol;
+
+ for (pol = rt6_pol_list; pol; pol = pol->next) {
+ struct fib6_node *fn;
+ struct rt6_info *sprt;
+
+ fn = fib6_lookup(pol->rules, daddr, saddr);
+
+ do {
+ for (sprt = fn->leaf; sprt; sprt=sprt->u.next) {
+ int res;
+
+ frule = sprt->rt6i_flowr;
+#if RT6_DEBUG >= 2
+ if (frule == NULL) {
+ RDBG1(KERN_DEBUG "NULL flowr\n");
+ goto error;
+ }
+#endif
+ res = frule->ops->accept(rt, sprt, args, &nrt);
+
+ switch (res) {
+ case FLOWR_SELECT:
+ goto found;
+ case FLOWR_CLEAR:
+ goto next_policy;
+ case FLOWR_NODECISION:
+ break;
+ default:
+ goto error;
+ };
+ }
+
+ fn = fn->parent;
+
+ } while ((fn->fn_flags & RTN_TL_ROOT) == 0);
+
+ next_policy:
+ }
+
+error:
+ dst_hold(&ip6_null_entry.u.dst);
+ return &ip6_null_entry;
+
+found:
+ if (nrt == NULL)
+ goto error;
+
+ nrt->rt6i_flags |= RTF_CACHE;
+ dst_hold(&nrt->u.dst);
+ err = rt6_ins(nrt, NULL);
+ if (err)
+ nrt->u.dst.error = err;
+ return nrt;
+}
+#endif
+
+static int fib6_ifdown(struct rt6_info *rt, void *arg)
+{
+ if (((void*)rt->rt6i_dev == arg || arg == NULL) &&
+ rt != &ip6_null_entry) {
+ RT6_TRACE3(KERN_DEBUG "deleted by ifdown %p\n", rt);
+ return -1;
+ }
+ return 0;
+}
+
+void rt6_ifdown(struct net_device *dev)
+{
+ write_lock_bh(&rt6_lock);
+ fib6_clean_tree(&ip6_routing_table, fib6_ifdown, 0, dev);
+ write_unlock_bh(&rt6_lock);
+}
+
+struct rt6_mtu_change_arg
+{
+ struct net_device *dev;
+ unsigned mtu;
+};
+
+static int rt6_mtu_change_route(struct rt6_info *rt, void *p_arg)
+{
+ struct rt6_mtu_change_arg *arg = (struct rt6_mtu_change_arg *) p_arg;
+ struct inet6_dev *idev;
+ /* In IPv6 pmtu discovery is not optional,
+ so that RTAX_MTU lock cannot disable it.
+ We still use this lock to block changes
+ caused by addrconf/ndisc.
+ */
+ idev = __in6_dev_get(arg->dev);
+ if (idev == NULL)
+ return 0;
+
+ /* For administrative MTU increase, there is no way to discover
+ IPv6 PMTU increase, so PMTU increase should be updated here.
+ Since RFC 1981 doesn't include administrative MTU increase
+ update PMTU increase is a MUST. (i.e. jumbo frame)
+ */
+ /*
+ If new MTU is less than route PMTU, this new MTU will be the
+ lowest MTU in the path, update the route PMTU to refect PMTU
+ decreases; if new MTU is greater than route PMTU, and the
+ old MTU is the lowest MTU in the path, update the route PMTU
+ to refect the increase. In this case if the other nodes' MTU
+ also have the lowest MTU, TOO BIG MESSAGE will be lead to
+ PMTU discouvery.
+ */
+ if (rt->rt6i_dev == arg->dev &&
+ !(rt->u.dst.mxlock&(1<<RTAX_MTU)) &&
+ (rt->u.dst.pmtu > arg->mtu ||
+ (rt->u.dst.pmtu < arg->mtu &&
+ rt->u.dst.pmtu == idev->cnf.mtu6)))
+ rt->u.dst.pmtu = arg->mtu;
+ rt->u.dst.advmss = max_t(unsigned int, arg->mtu - 60, ip6_rt_min_advmss);
+ if (rt->u.dst.advmss > 65535-20)
+ rt->u.dst.advmss = 65535;
+ return 0;
+}
+
+void rt6_mtu_change(struct net_device *dev, unsigned mtu)
+{
+ struct rt6_mtu_change_arg arg;
+
+ arg.dev = dev;
+ arg.mtu = mtu;
+ read_lock_bh(&rt6_lock);
+ fib6_clean_tree(&ip6_routing_table, rt6_mtu_change_route, 0, &arg);
+ read_unlock_bh(&rt6_lock);
+}
+
+static int inet6_rtm_to_rtmsg(struct rtmsg *r, struct rtattr **rta,
+ struct in6_rtmsg *rtmsg)
+{
+ memset(rtmsg, 0, sizeof(*rtmsg));
+
+ rtmsg->rtmsg_dst_len = r->rtm_dst_len;
+ rtmsg->rtmsg_src_len = r->rtm_src_len;
+ rtmsg->rtmsg_flags = RTF_UP;
+ if (r->rtm_type == RTN_UNREACHABLE)
+ rtmsg->rtmsg_flags |= RTF_REJECT;
+
+ if (rta[RTA_GATEWAY-1]) {
+ if (rta[RTA_GATEWAY-1]->rta_len != RTA_LENGTH(16))
+ return -EINVAL;
+ memcpy(&rtmsg->rtmsg_gateway, RTA_DATA(rta[RTA_GATEWAY-1]), 16);
+ rtmsg->rtmsg_flags |= RTF_GATEWAY;
+ }
+ if (rta[RTA_DST-1]) {
+ if (RTA_PAYLOAD(rta[RTA_DST-1]) < ((r->rtm_dst_len+7)>>3))
+ return -EINVAL;
+ memcpy(&rtmsg->rtmsg_dst, RTA_DATA(rta[RTA_DST-1]), ((r->rtm_dst_len+7)>>3));
+ }
+ if (rta[RTA_SRC-1]) {
+ if (RTA_PAYLOAD(rta[RTA_SRC-1]) < ((r->rtm_src_len+7)>>3))
+ return -EINVAL;
+ memcpy(&rtmsg->rtmsg_src, RTA_DATA(rta[RTA_SRC-1]), ((r->rtm_src_len+7)>>3));
+ }
+ if (rta[RTA_OIF-1]) {
+ if (rta[RTA_OIF-1]->rta_len != RTA_LENGTH(sizeof(int)))
+ return -EINVAL;
+ memcpy(&rtmsg->rtmsg_ifindex, RTA_DATA(rta[RTA_OIF-1]), sizeof(int));
+ }
+ if (rta[RTA_PRIORITY-1]) {
+ if (rta[RTA_PRIORITY-1]->rta_len != RTA_LENGTH(4))
+ return -EINVAL;
+ memcpy(&rtmsg->rtmsg_metric, RTA_DATA(rta[RTA_PRIORITY-1]), 4);
+ }
+ return 0;
+}
+
+int inet6_rtm_delroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
+{
+ struct rtmsg *r = NLMSG_DATA(nlh);
+ struct in6_rtmsg rtmsg;
+
+ if (inet6_rtm_to_rtmsg(r, arg, &rtmsg))
+ return -EINVAL;
+ return ip6_route_del(&rtmsg, nlh);
+}
+
+int inet6_rtm_newroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
+{
+ struct rtmsg *r = NLMSG_DATA(nlh);
+ struct in6_rtmsg rtmsg;
+
+ if (inet6_rtm_to_rtmsg(r, arg, &rtmsg))
+ return -EINVAL;
+ return ip6_route_add(&rtmsg, nlh);
+}
+
+struct rt6_rtnl_dump_arg
+{
+ struct sk_buff *skb;
+ struct netlink_callback *cb;
+};
+
+static int rt6_fill_node(struct sk_buff *skb, struct rt6_info *rt,
+ struct in6_addr *dst,
+ struct in6_addr *src,
+ int iif,
+ int type, u32 pid, u32 seq,
+ struct nlmsghdr *in_nlh, int prefix)
+{
+ struct rtmsg *rtm;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+ struct rta_cacheinfo ci;
+
+ if (prefix) { /* user wants prefix routes only */
+ if (!(rt->rt6i_flags & RTF_PREFIX_RT)) {
+ /* success since this is not a prefix route */
+ return 1;
+ }
+ }
+ if (!pid && in_nlh) {
+ pid = in_nlh->nlmsg_pid;
+ }
+
+ nlh = NLMSG_PUT(skb, pid, seq, type, sizeof(*rtm));
+ rtm = NLMSG_DATA(nlh);
+ rtm->rtm_family = AF_INET6;
+ rtm->rtm_dst_len = rt->rt6i_dst.plen;
+ rtm->rtm_src_len = rt->rt6i_src.plen;
+ rtm->rtm_tos = 0;
+ rtm->rtm_table = RT_TABLE_MAIN;
+ if (rt->rt6i_flags&RTF_REJECT)
+ rtm->rtm_type = RTN_UNREACHABLE;
+ else if (rt->rt6i_dev && (rt->rt6i_dev->flags&IFF_LOOPBACK))
+ rtm->rtm_type = RTN_LOCAL;
+ else
+ rtm->rtm_type = RTN_UNICAST;
+ rtm->rtm_flags = 0;
+ rtm->rtm_scope = RT_SCOPE_UNIVERSE;
+ rtm->rtm_protocol = rt->rt6i_protocol;
+ if (rt->rt6i_flags&RTF_DYNAMIC)
+ rtm->rtm_protocol = RTPROT_REDIRECT;
+ else if (rt->rt6i_flags&(RTF_ADDRCONF|RTF_ALLONLINK))
+ rtm->rtm_protocol = RTPROT_KERNEL;
+ else if (rt->rt6i_flags&RTF_DEFAULT)
+ rtm->rtm_protocol = RTPROT_RA;
+
+ if (rt->rt6i_flags&RTF_CACHE)
+ rtm->rtm_flags |= RTM_F_CLONED;
+
+ if (dst) {
+ RTA_PUT(skb, RTA_DST, 16, dst);
+ rtm->rtm_dst_len = 128;
+ } else if (rtm->rtm_dst_len)
+ RTA_PUT(skb, RTA_DST, 16, &rt->rt6i_dst.addr);
+#ifdef CONFIG_IPV6_SUBTREES
+ if (src) {
+ RTA_PUT(skb, RTA_SRC, 16, src);
+ rtm->rtm_src_len = 128;
+ } else if (rtm->rtm_src_len)
+ RTA_PUT(skb, RTA_SRC, 16, &rt->rt6i_src.addr);
+#endif
+ if (iif)
+ RTA_PUT(skb, RTA_IIF, 4, &iif);
+ else if (dst) {
+ struct in6_addr saddr_buf;
+ if (ipv6_get_saddr(&rt->u.dst, dst, &saddr_buf, 0) == 0)
+ RTA_PUT(skb, RTA_PREFSRC, 16, &saddr_buf);
+ }
+ if (rtnetlink_put_metrics(skb, &rt->u.dst.mxlock) < 0)
+ goto rtattr_failure;
+ if (rt->u.dst.neighbour)
+ RTA_PUT(skb, RTA_GATEWAY, 16, &rt->u.dst.neighbour->primary_key);
+ if (rt->u.dst.dev)
+ RTA_PUT(skb, RTA_OIF, sizeof(int), &rt->rt6i_dev->ifindex);
+ RTA_PUT(skb, RTA_PRIORITY, 4, &rt->rt6i_metric);
+ ci.rta_lastuse = jiffies - rt->u.dst.lastuse;
+ if (rt->rt6i_expires)
+ ci.rta_expires = rt->rt6i_expires - jiffies;
+ else
+ ci.rta_expires = 0;
+ ci.rta_used = rt->u.dst.__use;
+ ci.rta_clntref = atomic_read(&rt->u.dst.__refcnt);
+ ci.rta_error = rt->u.dst.error;
+ ci.rta_id = 0;
+ ci.rta_ts = 0;
+ ci.rta_tsage = 0;
+ RTA_PUT(skb, RTA_CACHEINFO, sizeof(ci), &ci);
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int rt6_dump_route(struct rt6_info *rt, void *p_arg)
+{
+ struct rt6_rtnl_dump_arg *arg = (struct rt6_rtnl_dump_arg *) p_arg;
+ int prefix;
+
+ if (arg->cb->nlh->nlmsg_len >= NLMSG_LENGTH(sizeof(struct rtmsg))) {
+ struct rtmsg *rtm = NLMSG_DATA(arg->cb->nlh);
+ prefix = (rtm->rtm_flags & RTM_F_PREFIX) != 0;
+ } else
+ prefix = 0;
+
+ return rt6_fill_node(arg->skb, rt, NULL, NULL, 0, RTM_NEWROUTE,
+ NETLINK_CB(arg->cb->skb).pid, arg->cb->nlh->nlmsg_seq,
+ NULL, prefix);
+}
+
+static int fib6_dump_node(struct fib6_walker_t *w)
+{
+ int res;
+ struct rt6_info *rt;
+
+ for (rt = w->leaf; rt; rt = rt->u.next) {
+ res = rt6_dump_route(rt, w->args);
+ if (res < 0) {
+ /* Frame is full, suspend walking */
+ w->leaf = rt;
+ return 1;
+ }
+ BUG_TRAP(res!=0);
+ }
+ w->leaf = NULL;
+ return 0;
+}
+
+static void fib6_dump_end(struct netlink_callback *cb)
+{
+ struct fib6_walker_t *w = (void*)cb->args[0];
+
+ if (w) {
+ cb->args[0] = 0;
+ fib6_walker_unlink(w);
+ kfree(w);
+ }
+ if (cb->args[1]) {
+ cb->done = (void*)cb->args[1];
+ cb->args[1] = 0;
+ }
+}
+
+static int fib6_dump_done(struct netlink_callback *cb)
+{
+ fib6_dump_end(cb);
+ return cb->done(cb);
+}
+
+int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ struct rt6_rtnl_dump_arg arg;
+ struct fib6_walker_t *w;
+ int res;
+
+ arg.skb = skb;
+ arg.cb = cb;
+
+ w = (void*)cb->args[0];
+ if (w == NULL) {
+ /* New dump:
+ *
+ * 1. hook callback destructor.
+ */
+ cb->args[1] = (long)cb->done;
+ cb->done = fib6_dump_done;
+
+ /*
+ * 2. allocate and initialize walker.
+ */
+ w = kmalloc(sizeof(*w), GFP_ATOMIC);
+ if (w == NULL)
+ return -ENOMEM;
+ RT6_TRACE3(KERN_DEBUG "dump<%p", w);
+ memset(w, 0, sizeof(*w));
+ w->root = &ip6_routing_table;
+ w->func = fib6_dump_node;
+ w->args = &arg;
+ cb->args[0] = (long)w;
+ read_lock_bh(&rt6_lock);
+ res = fib6_walk(w);
+ read_unlock_bh(&rt6_lock);
+ } else {
+ w->args = &arg;
+ read_lock_bh(&rt6_lock);
+ res = fib6_walk_continue(w);
+ read_unlock_bh(&rt6_lock);
+ }
+#if RT6_DEBUG >= 3
+ if (res <= 0 && skb->len == 0)
+ RT6_TRACE3("%p>dump end\n", w);
+#endif
+ res = res < 0 ? res : skb->len;
+ /* res < 0 is an error. (really, impossible)
+ res == 0 means that dump is complete, but skb still can contain data.
+ res > 0 dump is not complete, but frame is full.
+ */
+ /* Destroy walker, if dump of this table is complete. */
+ if (res <= 0)
+ fib6_dump_end(cb);
+ return res;
+}
+
+int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr* nlh, void *arg)
+{
+ struct rtattr **rta = arg;
+ int iif = 0;
+ int err = -ENOBUFS;
+ struct sk_buff *skb;
+ struct flowi fl;
+ struct rt6_info *rt;
+
+ skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (skb == NULL)
+ goto out;
+
+ /* Reserve room for dummy headers, this skb can pass
+ through good chunk of routing engine.
+ */
+ skb->mac.raw = skb->data;
+ skb_reserve(skb, MAX_HEADER + sizeof(struct ipv6hdr));
+
+ fl.proto = 0;
+ fl.nl_u.ip6_u.daddr = NULL;
+ fl.nl_u.ip6_u.saddr = NULL;
+ fl.uli_u.icmpt.type = 0;
+ fl.uli_u.icmpt.code = 0;
+ if (rta[RTA_SRC-1])
+ fl.nl_u.ip6_u.saddr = (struct in6_addr*)RTA_DATA(rta[RTA_SRC-1]);
+ if (rta[RTA_DST-1])
+ fl.nl_u.ip6_u.daddr = (struct in6_addr*)RTA_DATA(rta[RTA_DST-1]);
+
+ if (rta[RTA_IIF-1])
+ memcpy(&iif, RTA_DATA(rta[RTA_IIF-1]), sizeof(int));
+
+ if (iif) {
+ struct net_device *dev;
+ dev = __dev_get_by_index(iif);
+ if (!dev) {
+ err = -ENODEV;
+ goto out_free;
+ }
+ }
+
+ fl.oif = 0;
+ if (rta[RTA_OIF-1])
+ memcpy(&fl.oif, RTA_DATA(rta[RTA_OIF-1]), sizeof(int));
+
+ rt = (struct rt6_info*)ip6_route_output(NULL, &fl);
+
+ skb->dst = &rt->u.dst;
+
+ NETLINK_CB(skb).dst_pid = NETLINK_CB(in_skb).pid;
+ err = rt6_fill_node(skb, rt,
+ fl.nl_u.ip6_u.daddr,
+ fl.nl_u.ip6_u.saddr,
+ iif,
+ RTM_NEWROUTE, NETLINK_CB(in_skb).pid,
+ nlh->nlmsg_seq, nlh, 0);
+ if (err < 0) {
+ err = -EMSGSIZE;
+ goto out_free;
+ }
+
+ err = netlink_unicast(rtnl, skb, NETLINK_CB(in_skb).pid, MSG_DONTWAIT);
+ if (err > 0)
+ err = 0;
+out:
+ return err;
+out_free:
+ kfree_skb(skb);
+ goto out;
+}
+
+void inet6_rt_notify(int event, struct rt6_info *rt, struct nlmsghdr *nlh)
+{
+ struct sk_buff *skb;
+ int size = NLMSG_SPACE(sizeof(struct rtmsg)+256);
+
+ skb = alloc_skb(size, gfp_any());
+ if (!skb) {
+ netlink_set_err(rtnl, 0, RTMGRP_IPV6_ROUTE, ENOBUFS);
+ return;
+ }
+ if (rt6_fill_node(skb, rt, NULL, NULL, 0, event, 0, 0, nlh, 0) < 0) {
+ kfree_skb(skb);
+ netlink_set_err(rtnl, 0, RTMGRP_IPV6_ROUTE, EINVAL);
+ return;
+ }
+ NETLINK_CB(skb).dst_groups = RTMGRP_IPV6_ROUTE;
+ netlink_broadcast(rtnl, skb, 0, RTMGRP_IPV6_ROUTE, gfp_any());
+}
+
+/*
+ * /proc
+ */
+
+#ifdef CONFIG_PROC_FS
+
+#define RT6_INFO_LEN (32 + 4 + 32 + 4 + 32 + 40 + 5 + 1)
+
+struct rt6_proc_arg
+{
+ char *buffer;
+ int offset;
+ int length;
+ int skip;
+ int len;
+};
+
+static int rt6_info_route(struct rt6_info *rt, void *p_arg)
+{
+ struct rt6_proc_arg *arg = (struct rt6_proc_arg *) p_arg;
+ int i;
+
+ if (arg->skip < arg->offset / RT6_INFO_LEN) {
+ arg->skip++;
+ return 0;
+ }
+
+ if (arg->len >= arg->length)
+ return 0;
+
+ for (i=0; i<16; i++) {
+ sprintf(arg->buffer + arg->len, "%02x",
+ rt->rt6i_dst.addr.s6_addr[i]);
+ arg->len += 2;
+ }
+ arg->len += sprintf(arg->buffer + arg->len, " %02x ",
+ rt->rt6i_dst.plen);
+
+#ifdef CONFIG_IPV6_SUBTREES
+ for (i=0; i<16; i++) {
+ sprintf(arg->buffer + arg->len, "%02x",
+ rt->rt6i_src.addr.s6_addr[i]);
+ arg->len += 2;
+ }
+ arg->len += sprintf(arg->buffer + arg->len, " %02x ",
+ rt->rt6i_src.plen);
+#else
+ sprintf(arg->buffer + arg->len,
+ "00000000000000000000000000000000 00 ");
+ arg->len += 36;
+#endif
+
+ if (rt->rt6i_nexthop) {
+ for (i=0; i<16; i++) {
+ sprintf(arg->buffer + arg->len, "%02x",
+ rt->rt6i_nexthop->primary_key[i]);
+ arg->len += 2;
+ }
+ } else {
+ sprintf(arg->buffer + arg->len,
+ "00000000000000000000000000000000");
+ arg->len += 32;
+ }
+ arg->len += sprintf(arg->buffer + arg->len,
+ " %08x %08x %08x %08x %8s\n",
+ rt->rt6i_metric, atomic_read(&rt->u.dst.__refcnt),
+ rt->u.dst.__use, rt->rt6i_flags,
+ rt->rt6i_dev ? rt->rt6i_dev->name : "");
+ return 0;
+}
+
+static int rt6_proc_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct rt6_proc_arg arg;
+ arg.buffer = buffer;
+ arg.offset = offset;
+ arg.length = length;
+ arg.skip = 0;
+ arg.len = 0;
+
+ read_lock_bh(&rt6_lock);
+ fib6_clean_tree(&ip6_routing_table, rt6_info_route, 0, &arg);
+ read_unlock_bh(&rt6_lock);
+
+ *start = buffer;
+ if (offset)
+ *start += offset % RT6_INFO_LEN;
+
+ arg.len -= offset % RT6_INFO_LEN;
+
+ if (arg.len > length)
+ arg.len = length;
+ if (arg.len < 0)
+ arg.len = 0;
+
+ return arg.len;
+}
+
+extern struct rt6_statistics rt6_stats;
+
+static int rt6_proc_stats(char *buffer, char **start, off_t offset, int length)
+{
+ int len;
+
+ len = sprintf(buffer, "%04x %04x %04x %04x %04x %04x\n",
+ rt6_stats.fib_nodes, rt6_stats.fib_route_nodes,
+ rt6_stats.fib_rt_alloc, rt6_stats.fib_rt_entries,
+ rt6_stats.fib_rt_cache,
+ atomic_read(&ip6_dst_ops.entries));
+
+ len -= offset;
+
+ if (len > length)
+ len = length;
+ if(len < 0)
+ len = 0;
+
+ *start = buffer + offset;
+
+ return len;
+}
+#endif /* CONFIG_PROC_FS */
+
+#ifdef CONFIG_SYSCTL
+
+static int flush_delay;
+
+static
+int ipv6_sysctl_rtcache_flush(ctl_table *ctl, int write, struct file * filp,
+ void *buffer, size_t *lenp)
+{
+ if (write) {
+ proc_dointvec(ctl, write, filp, buffer, lenp);
+ if (flush_delay < 0)
+ flush_delay = 0;
+ fib6_run_gc((unsigned long)flush_delay);
+ return 0;
+ } else
+ return -EINVAL;
+}
+
+ctl_table ipv6_route_table[] = {
+ {NET_IPV6_ROUTE_FLUSH, "flush",
+ &flush_delay, sizeof(int), 0644, NULL,
+ &ipv6_sysctl_rtcache_flush},
+ {NET_IPV6_ROUTE_GC_THRESH, "gc_thresh",
+ &ip6_dst_ops.gc_thresh, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV6_ROUTE_MAX_SIZE, "max_size",
+ &ip6_rt_max_size, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {NET_IPV6_ROUTE_GC_MIN_INTERVAL, "gc_min_interval",
+ &ip6_rt_gc_min_interval, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies, &sysctl_jiffies},
+ {NET_IPV6_ROUTE_GC_TIMEOUT, "gc_timeout",
+ &ip6_rt_gc_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies, &sysctl_jiffies},
+ {NET_IPV6_ROUTE_GC_INTERVAL, "gc_interval",
+ &ip6_rt_gc_interval, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies, &sysctl_jiffies},
+ {NET_IPV6_ROUTE_GC_ELASTICITY, "gc_elasticity",
+ &ip6_rt_gc_elasticity, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies, &sysctl_jiffies},
+ {NET_IPV6_ROUTE_MTU_EXPIRES, "mtu_expires",
+ &ip6_rt_mtu_expires, sizeof(int), 0644, NULL,
+ &proc_dointvec_jiffies, &sysctl_jiffies},
+ {NET_IPV6_ROUTE_MIN_ADVMSS, "min_adv_mss",
+ &ip6_rt_min_advmss, sizeof(int), 0644, NULL,
+ &proc_dointvec},
+ {0}
+};
+
+#endif
+
+
+void __init ip6_route_init(void)
+{
+ ip6_dst_ops.kmem_cachep = kmem_cache_create("ip6_dst_cache",
+ sizeof(struct rt6_info),
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+ fib6_init();
+#ifdef CONFIG_PROC_FS
+ proc_net_create("ipv6_route", 0, rt6_proc_info);
+ proc_net_create("rt6_stats", 0, rt6_proc_stats);
+#endif
+}
+
+#ifdef MODULE
+void ip6_route_cleanup(void)
+{
+#ifdef CONFIG_PROC_FS
+ proc_net_remove("ipv6_route");
+ proc_net_remove("rt6_stats");
+#endif
+
+ rt6_ifdown(NULL);
+ fib6_gc_cleanup();
+ if (kmem_cache_destroy(ip6_dst_ops.kmem_cachep) == 0) {
+ RT6_TRACE3(KERN_DEBUG "%s(): kmem_cache %p for ip6_dst_cache destroyed.\n",
+ __FUNCTION__, ip6_dst_ops.kmem_cachep);
+ ip6_dst_ops.kmem_cachep = NULL;
+ } else {
+ RT6_TRACE1(KERN_WARNING "%s(): failed to destroy kmem_cache %p for ip6_dst_cache.\n",
+ __FUNCTION__, ip6_dst_ops.kmem_cachep);
+ }
+}
+#endif /* MODULE */
diff --git a/uClinux-2.4.31-uc0/net/ipv6/sit.c b/uClinux-2.4.31-uc0/net/ipv6/sit.c
new file mode 100644
index 0000000..5db7045
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/sit.c
@@ -0,0 +1,881 @@
+/* $USAGI: sit.c,v 1.24 2003/11/21 20:13:13 yoshfuji Exp $ */
+
+/*
+ * IPv6 over IPv4 tunnel device - Simple Internet Transition (SIT)
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ * Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
+ *
+ * $Id: sit.c,v 1.53 2001/09/25 05:09:53 davem Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Changes:
+ * Roger Venning <r.venning@telstra.com>,
+ * Nate Thompson <nate@thebog.net>: 6to4 support
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/sched.h>
+#include <linux/net.h>
+#include <linux/in6.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/icmp.h>
+#include <asm/uaccess.h>
+#include <linux/init.h>
+#include <linux/netfilter_ipv4.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/transp_v6.h>
+#include <net/ip6_fib.h>
+#include <net/ip6_route.h>
+#include <net/ndisc.h>
+#include <net/addrconf.h>
+#include <net/ip.h>
+#include <net/udp.h>
+#include <net/icmp.h>
+#include <net/ipip.h>
+#include <net/inet_ecn.h>
+
+/*
+ This version of net/ipv6/sit.c is cloned of net/ipv4/ip_gre.c
+
+ For comments look at net/ipv4/ip_gre.c --ANK
+ */
+
+#ifdef CONFIG_NET_IPIP_IPV6
+extern rwlock_t ipip_lock;
+#else
+
+#define HASH_SIZE 16
+#define HASH(addr) ((addr^(addr>>4))&0xF)
+
+static int ipip6_fb_tunnel_init(struct net_device *dev);
+static int ipip6_tunnel_init(struct net_device *dev);
+
+static struct net_device ipip6_fb_tunnel_dev = {
+ name: "sit0",
+ init: ipip6_fb_tunnel_init,
+};
+
+static struct ip_tunnel ipip6_fb_tunnel = {
+ NULL, &ipip6_fb_tunnel_dev, {0, }, 0, 0, 0, 0, 0, 0, 0, {"sit0", }
+};
+
+static struct ip_tunnel *tunnels_r_l[HASH_SIZE];
+static struct ip_tunnel *tunnels_r[HASH_SIZE];
+static struct ip_tunnel *tunnels_l[HASH_SIZE];
+static struct ip_tunnel *tunnels_wc[1];
+static struct ip_tunnel **tunnels[4] = { tunnels_wc, tunnels_l, tunnels_r, tunnels_r_l };
+
+static rwlock_t ipip6_lock = RW_LOCK_UNLOCKED;
+
+static struct ip_tunnel * ipip6_tunnel_lookup(u32 remote, u32 local)
+{
+ unsigned h0 = HASH(remote);
+ unsigned h1 = HASH(local);
+ struct ip_tunnel *t;
+
+ for (t = tunnels_r_l[h0^h1]; t; t = t->next) {
+ if (local == t->parms.iph.saddr &&
+ remote == t->parms.iph.daddr && (t->dev->flags&IFF_UP))
+ return t;
+ }
+ for (t = tunnels_r[h0]; t; t = t->next) {
+ if (remote == t->parms.iph.daddr && (t->dev->flags&IFF_UP))
+ return t;
+ }
+ for (t = tunnels_l[h1]; t; t = t->next) {
+ if (local == t->parms.iph.saddr && (t->dev->flags&IFF_UP))
+ return t;
+ }
+ if ((t = tunnels_wc[0]) != NULL && (t->dev->flags&IFF_UP))
+ return t;
+ return NULL;
+}
+
+static __inline__ struct ip_tunnel ** __ipip6_bucket(struct ip_tunnel_parm *parms)
+{
+ u32 remote = parms->iph.daddr;
+ u32 local = parms->iph.saddr;
+ unsigned h = 0;
+ int prio = 0;
+
+ if (remote) {
+ prio |= 2;
+ h ^= HASH(remote);
+ }
+ if (local) {
+ prio |= 1;
+ h ^= HASH(local);
+ }
+ return &tunnels[prio][h];
+}
+
+static struct ip_tunnel ** ipip6_bucket(struct ip_tunnel *t)
+{
+ return __ipip6_bucket(&t->parms);
+}
+
+static void ipip6_tunnel_unlink(struct ip_tunnel *t)
+{
+ struct ip_tunnel **tp;
+
+ for (tp = ipip6_bucket(t); *tp; tp = &(*tp)->next) {
+ if (t == *tp) {
+ write_lock_bh(&ipip6_lock);
+ *tp = t->next;
+ write_unlock_bh(&ipip6_lock);
+ break;
+ }
+ }
+}
+
+static void ipip6_tunnel_link(struct ip_tunnel *t)
+{
+ struct ip_tunnel **tp = ipip6_bucket(t);
+
+ t->next = *tp;
+ write_lock_bh(&ipip6_lock);
+ *tp = t;
+ write_unlock_bh(&ipip6_lock);
+}
+
+struct ip_tunnel * ipip6_tunnel_locate(struct ip_tunnel_parm *parms, int create)
+{
+ u32 remote = parms->iph.daddr;
+ u32 local = parms->iph.saddr;
+ struct ip_tunnel *t, **tp, *nt;
+ struct net_device *dev;
+
+ for (tp = __ipip6_bucket(parms); (t = *tp) != NULL; tp = &t->next) {
+ if (local == t->parms.iph.saddr && remote == t->parms.iph.daddr)
+ return t;
+ }
+ if (!create)
+ return NULL;
+
+ MOD_INC_USE_COUNT;
+ dev = kmalloc(sizeof(*dev) + sizeof(*t), GFP_KERNEL);
+ if (dev == NULL) {
+ MOD_DEC_USE_COUNT;
+ return NULL;
+ }
+ memset(dev, 0, sizeof(*dev) + sizeof(*t));
+ dev->priv = (void*)(dev+1);
+ nt = (struct ip_tunnel*)dev->priv;
+ nt->dev = dev;
+ dev->init = ipip6_tunnel_init;
+ dev->features |= NETIF_F_DYNALLOC;
+ memcpy(&nt->parms, parms, sizeof(*parms));
+ nt->parms.name[IFNAMSIZ-1] = '\0';
+ strcpy(dev->name, nt->parms.name);
+ if (dev->name[0] == 0) {
+ int i;
+ for (i=1; i<100; i++) {
+ sprintf(dev->name, "sit%d", i);
+ if (__dev_get_by_name(dev->name) == NULL)
+ break;
+ }
+ if (i==100)
+ goto failed;
+ memcpy(nt->parms.name, dev->name, IFNAMSIZ);
+ }
+ if (register_netdevice(dev) < 0)
+ goto failed;
+
+ dev_hold(dev);
+ ipip6_tunnel_link(nt);
+ /* Do not decrement MOD_USE_COUNT here. */
+ return nt;
+
+failed:
+ kfree(dev);
+ MOD_DEC_USE_COUNT;
+ return NULL;
+}
+
+static void ipip6_tunnel_destructor(struct net_device *dev)
+{
+ if (dev != &ipip6_fb_tunnel_dev) {
+ MOD_DEC_USE_COUNT;
+ }
+}
+
+static void ipip6_tunnel_uninit(struct net_device *dev)
+{
+ if (dev == &ipip6_fb_tunnel_dev) {
+ write_lock_bh(&ipip6_lock);
+ tunnels_wc[0] = NULL;
+ write_unlock_bh(&ipip6_lock);
+ dev_put(dev);
+ } else {
+ ipip6_tunnel_unlink((struct ip_tunnel*)dev->priv);
+ dev_put(dev);
+ }
+}
+#endif /* !CONFIG_NET_IPIP_IPV6 */
+
+void ipip6_err(struct sk_buff *skb, u32 info)
+{
+#ifndef I_WISH_WORLD_WERE_PERFECT
+
+/* It is not :-( All the routers (except for Linux) return only
+ 8 bytes of packet payload. It means, that precise relaying of
+ ICMP in the real Internet is absolutely infeasible.
+ */
+ struct iphdr *iph = (struct iphdr*)skb->data;
+ int type = skb->h.icmph->type;
+ int code = skb->h.icmph->code;
+ struct ip_tunnel *t;
+
+ switch (type) {
+ default:
+ case ICMP_PARAMETERPROB:
+ return;
+
+ case ICMP_DEST_UNREACH:
+ switch (code) {
+ case ICMP_SR_FAILED:
+ case ICMP_PORT_UNREACH:
+ /* Impossible event. */
+ return;
+ case ICMP_FRAG_NEEDED:
+ /* Soft state for pmtu is maintained by IP core. */
+ return;
+ default:
+ /* All others are translated to HOST_UNREACH.
+ rfc2003 contains "deep thoughts" about NET_UNREACH,
+ I believe they are just ether pollution. --ANK
+ */
+ break;
+ }
+ break;
+ case ICMP_TIME_EXCEEDED:
+ if (code != ICMP_EXC_TTL)
+ return;
+ break;
+ }
+
+#ifdef CONFIG_NET_IPIP_IPV6
+ read_lock(&ipip_lock);
+ t = ipip_tunnel_lookup(iph->daddr, iph->saddr);
+#else
+ read_lock(&ipip6_lock);
+ t = ipip6_tunnel_lookup(iph->daddr, iph->saddr);
+#endif
+ if (t == NULL || t->parms.iph.daddr == 0)
+ goto out;
+ if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
+ goto out;
+
+ if (jiffies - t->err_time < IPTUNNEL_ERR_TIMEO)
+ t->err_count++;
+ else
+ t->err_count = 1;
+ t->err_time = jiffies;
+out:
+#ifdef CONFIG_NET_IPIP_IPV6
+ read_unlock(&ipip_lock);
+#else
+ read_unlock(&ipip6_lock);
+#endif
+ return;
+#else
+ struct iphdr *iph = (struct iphdr*)dp;
+ int hlen = iph->ihl<<2;
+ struct ipv6hdr *iph6;
+ int type = skb->h.icmph->type;
+ int code = skb->h.icmph->code;
+ int rel_type = 0;
+ int rel_code = 0;
+ int rel_info = 0;
+ struct sk_buff *skb2;
+ struct rt6_info *rt6i;
+
+ if (len < hlen + sizeof(struct ipv6hdr))
+ return;
+ iph6 = (struct ipv6hdr*)(dp + hlen);
+
+ switch (type) {
+ default:
+ return;
+ case ICMP_PARAMETERPROB:
+ if (skb->h.icmph->un.gateway < hlen)
+ return;
+
+ /* So... This guy found something strange INSIDE encapsulated
+ packet. Well, he is fool, but what can we do ?
+ */
+ rel_type = ICMPV6_PARAMPROB;
+ rel_info = skb->h.icmph->un.gateway - hlen;
+ break;
+
+ case ICMP_DEST_UNREACH:
+ switch (code) {
+ case ICMP_SR_FAILED:
+ case ICMP_PORT_UNREACH:
+ /* Impossible event. */
+ return;
+ case ICMP_FRAG_NEEDED:
+ /* Too complicated case ... */
+ return;
+ default:
+ /* All others are translated to HOST_UNREACH.
+ rfc2003 contains "deep thoughts" about NET_UNREACH,
+ I believe, it is just ether pollution. --ANK
+ */
+ rel_type = ICMPV6_DEST_UNREACH;
+ rel_code = ICMPV6_ADDR_UNREACH;
+ break;
+ }
+ break;
+ case ICMP_TIME_EXCEEDED:
+ if (code != ICMP_EXC_TTL)
+ return;
+ rel_type = ICMPV6_TIME_EXCEED;
+ rel_code = ICMPV6_EXC_HOPLIMIT;
+ break;
+ }
+
+ /* Prepare fake skb to feed it to icmpv6_send */
+ skb2 = skb_clone(skb, GFP_ATOMIC);
+ if (skb2 == NULL)
+ return;
+ dst_release(skb2->dst);
+ skb2->dst = NULL;
+ skb_pull(skb2, skb->data - (u8*)iph6);
+ skb2->nh.raw = skb2->data;
+
+ /* Try to guess incoming interface */
+ rt6i = rt6_lookup(&iph6->saddr, NULL, NULL, 0);
+ if (rt6i && rt6i->rt6i_dev) {
+ skb2->dev = rt6i->rt6i_dev;
+
+ rt6i = rt6_lookup(&iph6->daddr, &iph6->saddr, NULL, 0);
+
+ if (rt6i && rt6i->rt6i_dev && rt6i->rt6i_dev->type == ARPHRD_SIT) {
+ struct ip_tunnel * t = (struct ip_tunnel*)rt6i->rt6i_dev->priv;
+ if (rel_type == ICMPV6_TIME_EXCEED && t->parms.iph.ttl) {
+ rel_type = ICMPV6_DEST_UNREACH;
+ rel_code = ICMPV6_ADDR_UNREACH;
+ }
+ icmpv6_send(skb2, rel_type, rel_code, rel_info, skb2->dev);
+ }
+ }
+ kfree_skb(skb2);
+ return;
+#endif
+}
+
+static inline void ipip6_ecn_decapsulate(struct iphdr *iph, struct sk_buff *skb)
+{
+ if (INET_ECN_is_ce(iph->tos) &&
+ INET_ECN_is_not_ce(ip6_get_dsfield(skb->nh.ipv6h)))
+ IP6_ECN_set_ce(skb->nh.ipv6h);
+}
+
+int ipip6_rcv(struct sk_buff *skb)
+{
+ struct iphdr *iph;
+ struct ip_tunnel *tunnel;
+
+ if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
+ goto out;
+
+ iph = skb->nh.iph;
+
+#ifdef CONFIG_NET_IPIP_IPV6
+ read_lock(&ipip_lock);
+ tunnel = ipip_tunnel_lookup(iph->saddr, iph->daddr);
+#else
+ read_lock(&ipip6_lock);
+ tunnel = ipip6_tunnel_lookup(iph->saddr, iph->daddr);
+#endif
+ if (tunnel != NULL) {
+ skb->mac.raw = skb->nh.raw;
+ skb->nh.raw = skb->data;
+ memset(&(IPCB(skb)->opt), 0, sizeof(struct ip_options));
+ skb->protocol = htons(ETH_P_IPV6);
+ skb->pkt_type = PACKET_HOST;
+ tunnel->stat.rx_packets++;
+ tunnel->stat.rx_bytes += skb->len;
+ skb->dev = tunnel->dev;
+ dst_release(skb->dst);
+ skb->dst = NULL;
+ nf_reset(skb);
+ ipip6_ecn_decapsulate(iph, skb);
+ netif_rx(skb);
+#ifdef CONFIG_NET_IPIP_IPV6
+ read_unlock(&ipip_lock);
+#else
+ read_unlock(&ipip6_lock);
+#endif
+ return 0;
+ }
+
+ icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, 0);
+ kfree_skb(skb);
+#ifdef CONFIG_NET_IPIP_IPV6
+ read_unlock(&ipip_lock);
+#else
+ read_unlock(&ipip6_lock);
+#endif
+out:
+ return 0;
+}
+
+#ifndef CONFIG_NET_IPIP_IPV6
+/* Need this wrapper because NF_HOOK takes the function address */
+static inline int do_ip_send(struct sk_buff *skb)
+{
+ return ip_send(skb);
+}
+
+
+/* Returns the embedded IPv4 address if the IPv6 address
+ comes from 6to4 (draft-ietf-ngtrans-6to4-04) addr space */
+
+static inline u32 try_6to4(struct in6_addr *v6dst)
+{
+ u32 dst = 0;
+
+ if (v6dst->s6_addr16[0] == htons(0x2002)) {
+ /* 6to4 v6 addr has 16 bits prefix, 32 v4addr, 16 SLA, ... */
+ memcpy(&dst, &v6dst->s6_addr16[1], 4);
+ }
+ return dst;
+}
+
+/*
+ * This function assumes it is being called from dev_queue_xmit()
+ * and that skb is filled properly by that function.
+ */
+
+static int ipip6_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct ip_tunnel *tunnel = (struct ip_tunnel*)dev->priv;
+ struct net_device_stats *stats = &tunnel->stat;
+ struct iphdr *tiph = &tunnel->parms.iph;
+ struct ipv6hdr *iph6 = skb->nh.ipv6h;
+ u8 tos = tunnel->parms.iph.tos;
+ struct rtable *rt; /* Route to the other host */
+ struct net_device *tdev; /* Device to other host */
+ struct iphdr *iph; /* Our new IP header */
+ int max_headroom; /* The extra header space needed */
+ u32 dst = tiph->daddr;
+ int mtu;
+ struct in6_addr *addr6;
+ int addr_type;
+
+ if (tunnel->recursion++) {
+ tunnel->stat.collisions++;
+ goto tx_error;
+ }
+
+ if (skb->protocol != htons(ETH_P_IPV6))
+ goto tx_error;
+
+ if (!dst)
+ dst = try_6to4(&iph6->daddr);
+
+ if (!dst) {
+ struct neighbour *neigh = NULL;
+
+ if (skb->dst)
+ neigh = skb->dst->neighbour;
+
+ if (neigh == NULL) {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "sit: nexthop == NULL\n");
+ goto tx_error;
+ }
+
+ addr6 = (struct in6_addr*)&neigh->primary_key;
+ addr_type = ipv6_addr_type(addr6);
+
+ if (addr_type == IPV6_ADDR_ANY) {
+ addr6 = &skb->nh.ipv6h->daddr;
+ addr_type = ipv6_addr_type(addr6);
+ }
+
+ if (addr_type & IPV6_ADDR_COMPATv4)
+ dst = addr6->s6_addr32[3];
+ else
+#ifdef CONFIG_IPV6_6TO4_NEXTHOP
+ if (!(dst = try_6to4(addr6)))
+#endif
+ goto tx_error_icmp;
+ }
+
+ if (ip_route_output(&rt, dst, tiph->saddr, RT_TOS(tos), tunnel->parms.link)) {
+ tunnel->stat.tx_carrier_errors++;
+ goto tx_error_icmp;
+ }
+ if (rt->rt_type != RTN_UNICAST) {
+ ip_rt_put(rt);
+ tunnel->stat.tx_carrier_errors++;
+ goto tx_error_icmp;
+ }
+ tdev = rt->u.dst.dev;
+
+ if (tdev == dev) {
+ ip_rt_put(rt);
+ tunnel->stat.collisions++;
+ goto tx_error;
+ }
+
+ if (tiph->frag_off)
+ mtu = rt->u.dst.pmtu - sizeof(struct iphdr);
+ else
+ mtu = skb->dst ? skb->dst->pmtu : dev->mtu;
+
+ if (mtu < 68) {
+ tunnel->stat.collisions++;
+ ip_rt_put(rt);
+ goto tx_error;
+ }
+ if (mtu < IPV6_MIN_MTU)
+ mtu = IPV6_MIN_MTU;
+ if (skb->dst && mtu < skb->dst->pmtu) {
+ struct rt6_info *rt6 = (struct rt6_info*)skb->dst;
+ if (mtu < rt6->u.dst.pmtu) {
+ if (tunnel->parms.iph.daddr || rt6->rt6i_dst.plen == 128) {
+ rt6->rt6i_flags |= RTF_MODIFIED;
+ rt6->u.dst.pmtu = mtu;
+ }
+ }
+ }
+ if (skb->len > mtu) {
+ icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu, dev);
+ ip_rt_put(rt);
+ goto tx_error;
+ }
+
+ if (tunnel->err_count > 0) {
+ if (jiffies - tunnel->err_time < IPTUNNEL_ERR_TIMEO) {
+ tunnel->err_count--;
+ dst_link_failure(skb);
+ } else
+ tunnel->err_count = 0;
+ }
+
+ /*
+ * Okay, now see if we can stuff it in the buffer as-is.
+ */
+ max_headroom = (((tdev->hard_header_len+15)&~15)+sizeof(struct iphdr));
+
+ if (skb_headroom(skb) < max_headroom || skb_cloned(skb) || skb_shared(skb)) {
+ struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);
+ if (!new_skb) {
+ ip_rt_put(rt);
+ stats->tx_dropped++;
+ dev_kfree_skb(skb);
+ tunnel->recursion--;
+ return 0;
+ }
+ if (skb->sk)
+ skb_set_owner_w(new_skb, skb->sk);
+ dev_kfree_skb(skb);
+ skb = new_skb;
+ iph6 = skb->nh.ipv6h;
+ }
+
+ skb->h.raw = skb->nh.raw;
+ skb->nh.raw = skb_push(skb, sizeof(struct iphdr));
+ memset(&(IPCB(skb)->opt), 0, sizeof(IPCB(skb)->opt));
+ dst_release(skb->dst);
+ skb->dst = &rt->u.dst;
+
+ /*
+ * Push down and install the IPIP header.
+ */
+
+ iph = skb->nh.iph;
+ iph->version = 4;
+ iph->ihl = sizeof(struct iphdr)>>2;
+ if (mtu > IPV6_MIN_MTU)
+ iph->frag_off = htons(IP_DF);
+ else
+ iph->frag_off = 0;
+
+ iph->protocol = IPPROTO_IPV6;
+ iph->tos = INET_ECN_encapsulate(tos, ip6_get_dsfield(iph6));
+ iph->daddr = rt->rt_dst;
+ iph->saddr = rt->rt_src;
+
+ if ((iph->ttl = tiph->ttl) == 0)
+ iph->ttl = iph6->hop_limit;
+
+ nf_reset(skb);
+
+ IPTUNNEL_XMIT();
+ tunnel->recursion--;
+ return 0;
+
+tx_error_icmp:
+ dst_link_failure(skb);
+tx_error:
+ stats->tx_errors++;
+ dev_kfree_skb(skb);
+ tunnel->recursion--;
+ return 0;
+}
+
+static int
+ipip6_tunnel_ioctl (struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+ int err = 0;
+ struct ip_tunnel_parm p;
+ struct ip_tunnel *t;
+
+ MOD_INC_USE_COUNT;
+
+ switch (cmd) {
+ case SIOCGETTUNNEL:
+ t = NULL;
+ if (dev == &ipip6_fb_tunnel_dev) {
+ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p))) {
+ err = -EFAULT;
+ break;
+ }
+ t = ipip6_tunnel_locate(&p, 0);
+ }
+ if (t == NULL)
+ t = (struct ip_tunnel*)dev->priv;
+ memcpy(&p, &t->parms, sizeof(p));
+ if (copy_to_user(ifr->ifr_ifru.ifru_data, &p, sizeof(p)))
+ err = -EFAULT;
+ break;
+
+ case SIOCADDTUNNEL:
+ case SIOCCHGTUNNEL:
+ err = -EPERM;
+ if (!capable(CAP_NET_ADMIN))
+ goto done;
+
+ err = -EFAULT;
+ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
+ goto done;
+
+ err = -EINVAL;
+ if (p.iph.version != 4 || p.iph.protocol != IPPROTO_IPV6 ||
+ p.iph.ihl != 5 || (p.iph.frag_off&htons(~IP_DF)))
+ goto done;
+ if (p.iph.ttl)
+ p.iph.frag_off |= htons(IP_DF);
+
+ t = ipip6_tunnel_locate(&p, cmd == SIOCADDTUNNEL);
+
+ if (dev != &ipip6_fb_tunnel_dev && cmd == SIOCCHGTUNNEL &&
+ t != &ipip6_fb_tunnel) {
+ if (t != NULL) {
+ if (t->dev != dev) {
+ err = -EEXIST;
+ break;
+ }
+ } else {
+ if (((dev->flags&IFF_POINTOPOINT) && !p.iph.daddr) ||
+ (!(dev->flags&IFF_POINTOPOINT) && p.iph.daddr)) {
+ err = -EINVAL;
+ break;
+ }
+ t = (struct ip_tunnel*)dev->priv;
+ ipip6_tunnel_unlink(t);
+ t->parms.iph.saddr = p.iph.saddr;
+ t->parms.iph.daddr = p.iph.daddr;
+ memcpy(dev->dev_addr, &p.iph.saddr, 4);
+ memcpy(dev->broadcast, &p.iph.daddr, 4);
+ ipip6_tunnel_link(t);
+ netdev_state_change(dev);
+ }
+ }
+
+ if (t) {
+ err = 0;
+ if (cmd == SIOCCHGTUNNEL) {
+ t->parms.iph.ttl = p.iph.ttl;
+ t->parms.iph.tos = p.iph.tos;
+ }
+ if (copy_to_user(ifr->ifr_ifru.ifru_data, &t->parms, sizeof(p)))
+ err = -EFAULT;
+ } else
+ err = (cmd == SIOCADDTUNNEL ? -ENOBUFS : -ENOENT);
+ break;
+
+ case SIOCDELTUNNEL:
+ err = -EPERM;
+ if (!capable(CAP_NET_ADMIN))
+ goto done;
+
+ if (dev == &ipip6_fb_tunnel_dev) {
+ err = -EFAULT;
+ if (copy_from_user(&p, ifr->ifr_ifru.ifru_data, sizeof(p)))
+ goto done;
+ err = -ENOENT;
+ if ((t = ipip6_tunnel_locate(&p, 0)) == NULL)
+ goto done;
+ err = -EPERM;
+ if (t == &ipip6_fb_tunnel)
+ goto done;
+ dev = t->dev;
+ }
+ err = unregister_netdevice(dev);
+ break;
+
+ default:
+ err = -EINVAL;
+ }
+
+done:
+ MOD_DEC_USE_COUNT;
+ return err;
+}
+
+static struct net_device_stats *ipip6_tunnel_get_stats(struct net_device *dev)
+{
+ return &(((struct ip_tunnel*)dev->priv)->stat);
+}
+
+static int ipip6_tunnel_change_mtu(struct net_device *dev, int new_mtu)
+{
+ if (new_mtu < IPV6_MIN_MTU || new_mtu > 0xFFF8 - sizeof(struct iphdr))
+ return -EINVAL;
+ dev->mtu = new_mtu;
+ return 0;
+}
+
+static void ipip6_tunnel_init_gen(struct net_device *dev)
+{
+ struct ip_tunnel *t = (struct ip_tunnel*)dev->priv;
+
+ dev->destructor = ipip6_tunnel_destructor;
+ dev->uninit = ipip6_tunnel_uninit;
+ dev->hard_start_xmit = ipip6_tunnel_xmit;
+ dev->get_stats = ipip6_tunnel_get_stats;
+ dev->do_ioctl = ipip6_tunnel_ioctl;
+ dev->change_mtu = ipip6_tunnel_change_mtu;
+
+ dev->type = ARPHRD_SIT;
+ dev->hard_header_len = LL_MAX_HEADER + sizeof(struct iphdr);
+ dev->mtu = 1500 - sizeof(struct iphdr);
+ dev->flags = IFF_NOARP;
+ dev->iflink = 0;
+ dev->addr_len = 4;
+ memcpy(dev->dev_addr, &t->parms.iph.saddr, 4);
+ memcpy(dev->broadcast, &t->parms.iph.daddr, 4);
+}
+
+static int ipip6_tunnel_init(struct net_device *dev)
+{
+ struct net_device *tdev = NULL;
+ struct ip_tunnel *tunnel;
+ struct iphdr *iph;
+
+ tunnel = (struct ip_tunnel*)dev->priv;
+ iph = &tunnel->parms.iph;
+
+ ipip6_tunnel_init_gen(dev);
+
+ if (iph->daddr) {
+ struct rtable *rt;
+ if (!ip_route_output(&rt, iph->daddr, iph->saddr, RT_TOS(iph->tos), tunnel->parms.link)) {
+ tdev = rt->u.dst.dev;
+ ip_rt_put(rt);
+ }
+ dev->flags |= IFF_POINTOPOINT;
+ }
+
+ if (!tdev && tunnel->parms.link)
+ tdev = __dev_get_by_index(tunnel->parms.link);
+
+ if (tdev) {
+ dev->hard_header_len = tdev->hard_header_len + sizeof(struct iphdr);
+ dev->mtu = tdev->mtu - sizeof(struct iphdr);
+ if (dev->mtu < IPV6_MIN_MTU)
+ dev->mtu = IPV6_MIN_MTU;
+ }
+ dev->iflink = tunnel->parms.link;
+
+ return 0;
+}
+
+#ifdef MODULE
+static int ipip6_fb_tunnel_open(struct net_device *dev)
+{
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int ipip6_fb_tunnel_close(struct net_device *dev)
+{
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+#endif
+
+int __init ipip6_fb_tunnel_init(struct net_device *dev)
+{
+ struct iphdr *iph;
+
+ ipip6_tunnel_init_gen(dev);
+#ifdef MODULE
+ dev->open = ipip6_fb_tunnel_open;
+ dev->stop = ipip6_fb_tunnel_close;
+#endif
+
+ iph = &ipip6_fb_tunnel.parms.iph;
+ iph->version = 4;
+ iph->protocol = IPPROTO_IPV6;
+ iph->ihl = 5;
+ iph->ttl = 64;
+
+ dev_hold(dev);
+ tunnels_wc[0] = &ipip6_fb_tunnel;
+ return 0;
+}
+
+static struct inet_protocol sit_protocol = {
+ ipip6_rcv,
+ ipip6_err,
+ 0,
+ IPPROTO_IPV6,
+ 0,
+ NULL,
+ "IPv6"
+};
+
+#ifdef MODULE
+void sit_cleanup(void)
+{
+ inet_del_protocol(&sit_protocol);
+ unregister_netdev(&ipip6_fb_tunnel_dev);
+}
+#endif
+
+int __init sit_init(void)
+{
+ printk(KERN_INFO "IPv6 over IPv4 tunneling driver\n");
+
+ ipip6_fb_tunnel_dev.priv = (void*)&ipip6_fb_tunnel;
+ strcpy(ipip6_fb_tunnel_dev.name, ipip6_fb_tunnel.parms.name);
+ register_netdev(&ipip6_fb_tunnel_dev);
+ inet_add_protocol(&sit_protocol);
+ return 0;
+}
+#endif /* !CONFIG_NET_IPIP_IPV6 */
diff --git a/uClinux-2.4.31-uc0/net/ipv6/sysctl_net_ipv6.c b/uClinux-2.4.31-uc0/net/ipv6/sysctl_net_ipv6.c
new file mode 100644
index 0000000..b88d1f6
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/sysctl_net_ipv6.c
@@ -0,0 +1,73 @@
+/*
+ * sysctl_net_ipv6.c: sysctl interface to net IPV6 subsystem.
+ *
+ * Changes:
+ * YOSHIFUJI Hideaki @USAGI: added icmp sysctl table.
+ */
+
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+#include <linux/config.h>
+#include <linux/in6.h>
+#include <linux/ipv6.h>
+#include <net/ndisc.h>
+#include <net/ipv6.h>
+#include <net/addrconf.h>
+
+extern ctl_table ipv6_route_table[];
+extern ctl_table ipv6_icmp_table[];
+
+#ifdef CONFIG_SYSCTL
+
+ctl_table ipv6_table[] = {
+ {NET_IPV6_ROUTE, "route", NULL, 0, 0555, ipv6_route_table},
+ {NET_IPV6_ICMP, "icmp", NULL, 0, 0500, ipv6_icmp_table},
+ {NET_IPV6_BINDV6ONLY, "bindv6only",
+ &sysctl_ipv6_bindv6only, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_IPV6_IP6FRAG_HIGH_THRESH, "ip6frag_high_thresh",
+ &sysctl_ip6frag_high_thresh, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_IPV6_IP6FRAG_LOW_THRESH, "ip6frag_low_thresh",
+ &sysctl_ip6frag_low_thresh, sizeof(int), 0644, NULL, &proc_dointvec},
+ {NET_IPV6_IP6FRAG_TIME, "ip6frag_time",
+ &sysctl_ip6frag_time, sizeof(int), 0644, NULL, &proc_dointvec_jiffies,
+ &sysctl_jiffies},
+#ifdef CONFIG_IPV6_RESTRICTED_DOUBLE_BIND
+ {NET_IPV6_BINDV6ONLY_RESTRICTION, "bindv6only_restriction",
+ &sysctl_ipv6_bindv6only_restriction, sizeof(int), 0644, NULL, &proc_dointvec},
+#endif
+ {NET_IPV6_MLD_MAX_MSF, "mld_max_msf",
+ &sysctl_mld_max_msf, sizeof(int), 0644, NULL, &proc_dointvec},
+ {0}
+};
+
+#ifdef MODULE
+static struct ctl_table_header *ipv6_sysctl_header;
+static struct ctl_table ipv6_root_table[];
+static struct ctl_table ipv6_net_table[];
+
+
+ctl_table ipv6_root_table[] = {
+ {CTL_NET, "net", NULL, 0, 0555, ipv6_net_table},
+ {0}
+};
+
+ctl_table ipv6_net_table[] = {
+ {NET_IPV6, "ipv6", NULL, 0, 0555, ipv6_table},
+ {0}
+};
+
+void ipv6_sysctl_register(void)
+{
+ ipv6_sysctl_header = register_sysctl_table(ipv6_root_table, 0);
+}
+
+void ipv6_sysctl_unregister(void)
+{
+ unregister_sysctl_table(ipv6_sysctl_header);
+}
+#endif /* MODULE */
+
+#endif /* CONFIG_SYSCTL */
+
+
+
diff --git a/uClinux-2.4.31-uc0/net/ipv6/tcp_ipv6.c b/uClinux-2.4.31-uc0/net/ipv6/tcp_ipv6.c
new file mode 100644
index 0000000..38150f5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/tcp_ipv6.c
@@ -0,0 +1,2372 @@
+/* $USAGI: tcp_ipv6.c,v 1.86 2003/11/12 05:12:02 yoshfuji Exp $ */
+
+/*
+ * TCP over IPv6
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ *
+ * $Id: tcp_ipv6.c,v 1.142.2.1 2001/12/21 05:06:08 davem Exp $
+ *
+ * Based on:
+ * linux/net/ipv4/tcp.c
+ * linux/net/ipv4/tcp_input.c
+ * linux/net/ipv4/tcp_output.c
+ *
+ * Fixes:
+ * Hideaki YOSHIFUJI : sin6_scope_id support
+ * yoshfuji@USAGI : Reworked bind(2) behavior, including:
+ * - Allow ipv6 and ipv4 bind(2) to the
+ * same port.
+ * - Don't allow narrow binding unless
+ * later uid is the same as before:
+ * CONFIG_NET_RESTRICTED_REUSE
+ * - Don't allow binding to the same
+ * address unless it is one of multi-
+ * cast address even if SO_REUSEADDR
+ * is set.
+ *
+ * Changes:
+ * Kazunori MIYAZAWA <miyazawa@linux-ipv6.org> / USAGI : IPsec support
+ * Mitsuru KANDA <mk@linux-ipv6.org> / USAGI : IPsec support
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/sched.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/netdevice.h>
+#include <linux/init.h>
+#include <linux/jhash.h>
+#include <linux/ipsec.h>
+
+#include <linux/ipv6.h>
+#include <linux/icmpv6.h>
+#include <linux/random.h>
+
+#include <net/tcp.h>
+#include <net/ndisc.h>
+#include <net/ipv6.h>
+#include <net/transp_v6.h>
+#include <net/addrconf.h>
+#include <net/ip6_route.h>
+#include <net/inet_ecn.h>
+
+#include <asm/uaccess.h>
+
+static void tcp_v6_send_reset(struct sk_buff *skb);
+static void tcp_v6_or_send_ack(struct sk_buff *skb, struct open_request *req);
+static void tcp_v6_send_check(struct sock *sk, struct tcphdr *th, int len,
+ struct sk_buff *skb);
+
+static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb);
+static int tcp_v6_xmit(struct sk_buff *skb, int ipfragok);
+
+static struct tcp_func ipv6_mapped;
+static struct tcp_func ipv6_specific;
+
+/* I have no idea if this is a good hash for v6 or not. -DaveM */
+static __inline__ int tcp_v6_hashfn(struct in6_addr *laddr, u16 lport,
+ struct in6_addr *faddr, u16 fport)
+{
+ int hashent = (lport ^ fport);
+
+ hashent ^= (laddr->s6_addr32[3] ^ faddr->s6_addr32[3]);
+ hashent ^= hashent>>16;
+ hashent ^= hashent>>8;
+ return (hashent & (tcp_ehash_size - 1));
+}
+
+static __inline__ int tcp_v6_sk_hashfn(struct sock *sk)
+{
+ struct in6_addr *laddr = &sk->net_pinfo.af_inet6.rcv_saddr;
+ struct in6_addr *faddr = &sk->net_pinfo.af_inet6.daddr;
+ __u16 lport = sk->num;
+ __u16 fport = sk->dport;
+ return tcp_v6_hashfn(laddr, lport, faddr, fport);
+}
+
+/* Grrr, addr_type already calculated by caller, but I don't want
+ * to add some silly "cookie" argument to this method just for that.
+ * But it doesn't matter, the recalculation is in the rarest path
+ * this function ever takes.
+ */
+static int tcp_v6_get_port(struct sock *sk, unsigned short snum)
+{
+ struct tcp_bind_hashbucket *head;
+ struct tcp_bind_bucket *tb;
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ uid_t sk_uid = sk->state != TCP_TIME_WAIT ? sock_i_uid_t(sk) : ((struct tcp_tw_bucket *)sk)->uid;
+#endif
+ int ret;
+
+ local_bh_disable();
+ if (snum == 0) {
+ int low = sysctl_local_port_range[0];
+ int high = sysctl_local_port_range[1];
+ int remaining = (high - low) + 1;
+ int rover;
+
+ spin_lock(&tcp_portalloc_lock);
+ rover = tcp_port_rover;
+ do { rover++;
+ if ((rover < low) || (rover > high))
+ rover = low;
+ head = &tcp_bhash[tcp_bhashfn(rover)];
+ spin_lock(&head->lock);
+ for (tb = head->chain; tb; tb = tb->next)
+ if (tb->port == rover)
+ goto next;
+ break;
+ next:
+ spin_unlock(&head->lock);
+ } while (--remaining > 0);
+ tcp_port_rover = rover;
+ spin_unlock(&tcp_portalloc_lock);
+
+ /* Exhausted local port range during search? */
+ ret = 1;
+ if (remaining <= 0)
+ goto fail;
+
+ /* OK, here is the one we will use. */
+ snum = rover;
+ tb = NULL;
+ } else {
+ head = &tcp_bhash[tcp_bhashfn(snum)];
+ spin_lock(&head->lock);
+ for (tb = head->chain; tb != NULL; tb = tb->next)
+ if (tb->port == snum)
+ break;
+ }
+ if (tb != NULL && tb->owners != NULL) {
+ if (tb->fastreuse > 0 &&
+ (sk->reuse != 0
+#ifdef SO_REUSEPORT
+ || sk->reuseport != 0
+#endif
+ ) &&
+#if defined(CONFIG_NET_RESTRICTED_REUSE)
+ sk_uid == tb->uid &&
+#endif
+ sk->state != TCP_LISTEN) {
+ goto success;
+ } else {
+ struct sock *sk2 = tb->owners;
+ int sk_reuse, sk2_reuse;
+ struct in6_addr *sk_rcv_saddr6 = sk->state != TCP_TIME_WAIT ?
+ &sk->net_pinfo.af_inet6.rcv_saddr:
+ &((struct tcp_tw_bucket*)sk)->v6_rcv_saddr;
+ int addr_type = ipv6_addr_type(sk_rcv_saddr6),
+ addr_type2;
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ uid_t sk2_uid;
+#endif
+
+ sk_reuse = 0;
+ if (sk->reuse)
+ sk_reuse |= 1;
+#ifdef SO_REUSEPORT
+ if (sk->reuseport)
+ sk_reuse |= 2;
+#endif
+#if 0
+ if (sk_reuse &&
+ (addr_type != IPV6_ADDR_MAPPED ? (addr_type & IPV6_ADDR_MULTICAST) : MULTICAST(sk->rcv_saddr)))
+ sk_reuse |= 4;
+#endif
+
+ /* We must walk the whole port owner list in this case. -DaveM */
+ for( ; sk2 != NULL; sk2 = sk2->bind_next) {
+#if 1 /* XXX: convert like 2.4.21 */
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ int uid_ok;
+#endif
+ int both_specified = 0;
+ struct in6_addr *sk2_rcv_saddr6;
+ if (sk2 == sk ||
+ (sk2->bound_dev_if && sk->bound_dev_if &&
+ sk2->bound_dev_if != sk->bound_dev_if))
+ continue;
+#if 0
+ if (sk2->family != AF_INET6 && sk2->family != AF_INET)
+ continue;
+#endif
+ sk2_rcv_saddr6 = sk2->state != TCP_TIME_WAIT ?
+ &sk2->net_pinfo.af_inet6.rcv_saddr :
+ &((struct tcp_tw_bucket*)sk2)->v6_rcv_saddr;
+ addr_type2 = sk2->family == AF_INET6 ? ipv6_addr_type(sk2_rcv_saddr6) : IPV6_ADDR_MAPPED;
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ sk2_uid = sk2->state != TCP_TIME_WAIT ? sock_i_uid_t(sk2) : ((struct tcp_tw_bucket *)sk2)->uid;
+#endif
+
+ if ((addr_type2 != IPV6_ADDR_MAPPED ? addr_type2 != IPV6_ADDR_ANY : sk2->rcv_saddr) &&
+ (addr_type != IPV6_ADDR_MAPPED ? addr_type != IPV6_ADDR_ANY : sk->rcv_saddr)) {
+ if (addr_type2 == IPV6_ADDR_MAPPED || addr_type == IPV6_ADDR_MAPPED) {
+ if (addr_type2 != addr_type ||
+ sk2->rcv_saddr != sk->rcv_saddr)
+ continue;
+ } else {
+ if (ipv6_addr_cmp(sk2_rcv_saddr6, sk_rcv_saddr6))
+ continue;
+ }
+ both_specified = 1;
+ }
+
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ uid_ok = sk2_uid == (uid_t) -1 || sk_uid == sk2_uid;
+#endif
+
+ if ((addr_type2 == IPV6_ADDR_MAPPED &&
+ addr_type != IPV6_ADDR_MAPPED && __ipv6_only_sock(sk)) ||
+ (addr_type == IPV6_ADDR_MAPPED &&
+ addr_type2 != IPV6_ADDR_MAPPED && __ipv6_only_sock(sk2))) {
+#ifdef CONFIG_IPV6_RESTRICTED_DOUBLE_BIND
+ if (sysctl_ipv6_bindv6only_restriction == 0 || uid_ok)
+ continue;
+#else
+ continue;
+#endif
+ }
+
+ sk2_reuse = 0;
+ if (sk2->reuse)
+ sk2_reuse |= 1;
+#ifdef SO_REUSEPORT
+ if (sk2->reuseport)
+ sk2_reuse |= 2;
+#endif
+#if 0
+ if (sk2_reuse &&
+ (addr_type2 != IPV6_ADDR_MAPPED ? (addr_type2 & IPV6_ADDR_MULTICAST) : MULTICAST(sk2->rcv_saddr)))
+ sk2_reuse |= 4;
+#endif
+
+ if (sk2_reuse & sk_reuse & 3) { /* NOT && */
+ ret = 1;
+#if 0
+ if (sk2_reuse & sk_reuse & 4)
+ continue;
+#endif
+#ifdef CONFIG_NET_RESTRICTED_REUSE
+ if (!uid_ok)
+ goto fail_unlock;
+#endif
+#ifdef SO_REUSEPORT
+ if (sk2_reuse & sk_reuse & 2)
+ continue;
+#endif
+ if (both_specified) {
+ struct in6_addr *sk2_daddr6 = sk2->state != TCP_TIME_WAIT ?
+ &sk2->net_pinfo.af_inet6.daddr :
+ &((struct tcp_tw_bucket*)sk2)->v6_daddr;
+ int addr_type2d = sk2->family == AF_INET6 ? ipv6_addr_type(sk2_daddr6) : IPV6_ADDR_MAPPED;
+ if (addr_type2d != IPV6_ADDR_MAPPED ? addr_type2d != IPV6_ADDR_ANY : sk2->daddr)
+ continue;
+ } else {
+ if ((addr_type2 != IPV6_ADDR_MAPPED ? addr_type2 != IPV6_ADDR_ANY : sk2->rcv_saddr) ||
+ (addr_type != IPV6_ADDR_MAPPED ? addr_type != IPV6_ADDR_ANY : sk->rcv_saddr))
+ continue;
+ }
+ }
+ ret = 1;
+ goto fail_unlock;
+#else /* XXX: convert like 2.4.21 */
+ if (sk != sk2 &&
+ (!sk->bound_dev_if ||
+ !sk2->bound_dev_if ||
+ sk->bound_dev_if == sk2->bound_dev_if)) {
+ if (!sk_reuse ||
+ !sk2->reuse ||
+ sk2->state == TCP_LISTEN) {
+ /* NOTE: IPv6 tw bucket have different format */
+ if ((!sk2->rcv_saddr && !ipv6_only_sock(sk)) ||
+ (sk2->family == AF_INET6 &&
+ ipv6_addr_any(&sk2->net_pinfo.af_inet6.rcv_saddr) &&
+ !(ipv6_only_sock(sk2) && addr_type == IPV6_ADDR_MAPPED)) ||
+ (addr_type == IPV6_ADDR_ANY &&
+ (!ipv6_only_sock(sk) ||
+ !(sk2->family == AF_INET6 ? ipv6_addr_type(&sk2->net_pinfo.af_inet6.rcv_saddr) == IPV6_ADDR_MAPPED : 1))) ||
+ (sk2->family == AF_INET6 &&
+ !ipv6_addr_cmp(&sk->net_pinfo.af_inet6.rcv_saddr,
+ sk2->state != TCP_TIME_WAIT ?
+ &sk2->net_pinfo.af_inet6.rcv_saddr :
+ &((struct tcp_tw_bucket*)sk)->v6_rcv_saddr)) ||
+ (addr_type == IPV6_ADDR_MAPPED &&
+ !ipv6_only_sock(sk2) &&
+ (!sk2->rcv_saddr ||
+ !sk->rcv_saddr ||
+ sk->rcv_saddr == sk2->rcv_saddr)))
+ break;
+ }
+ }
+#endif /* XXX: convert like 2.4.21 */
+ }
+ /* If we found a conflict, fail. */
+ ret = 1;
+ if (sk2 != NULL)
+ goto fail_unlock;
+ }
+ }
+ ret = 1;
+ if (tb == NULL &&
+ (tb = tcp_bucket_create(head, snum)) == NULL)
+ goto fail_unlock;
+ if (tb->owners == NULL) {
+ if ((sk->reuse
+#ifdef SO_REUSEPORT
+ || sk->reuseport
+#endif
+ ) && sk->state != TCP_LISTEN) {
+ tb->fastreuse = 1;
+#if defined(CONFIG_NET_RESTRICTED_REUSE)
+ tb->uid = sk_uid;
+#endif
+ } else
+ tb->fastreuse = 0;
+ } else if (tb->fastreuse &&
+ ((sk->reuse == 0
+#ifdef SO_REUSEPORT
+ && sk_reuseport == 0
+#endif
+ ) ||
+#if defined(CONFIG_NET_RESTRICTED_REUSE)
+ (sk_uid != tb->uid) ||
+#endif
+ (sk->state == TCP_LISTEN)))
+ tb->fastreuse = 0;
+
+success:
+ sk->num = snum;
+ if (sk->prev == NULL) {
+ if ((sk->bind_next = tb->owners) != NULL)
+ tb->owners->bind_pprev = &sk->bind_next;
+ tb->owners = sk;
+ sk->bind_pprev = &tb->owners;
+ sk->prev = (struct sock *) tb;
+ } else {
+ BUG_TRAP(sk->prev == (struct sock *) tb);
+ }
+ ret = 0;
+
+fail_unlock:
+ spin_unlock(&head->lock);
+fail:
+ local_bh_enable();
+ return ret;
+}
+
+static __inline__ void __tcp_v6_hash(struct sock *sk)
+{
+ struct sock **skp;
+ rwlock_t *lock;
+
+ BUG_TRAP(sk->pprev==NULL);
+
+ if(sk->state == TCP_LISTEN) {
+ skp = &tcp_listening_hash[tcp_sk_listen_hashfn(sk)];
+ lock = &tcp_lhash_lock;
+ tcp_listen_wlock();
+ } else {
+ skp = &tcp_ehash[(sk->hashent = tcp_v6_sk_hashfn(sk))].chain;
+ lock = &tcp_ehash[sk->hashent].lock;
+ write_lock(lock);
+ }
+
+ if((sk->next = *skp) != NULL)
+ (*skp)->pprev = &sk->next;
+ *skp = sk;
+ sk->pprev = skp;
+ sock_prot_inc_use(sk->prot);
+ write_unlock(lock);
+}
+
+
+static void tcp_v6_hash(struct sock *sk)
+{
+ if(sk->state != TCP_CLOSE) {
+ if (sk->tp_pinfo.af_tcp.af_specific == &ipv6_mapped) {
+ tcp_prot.hash(sk);
+ return;
+ }
+ local_bh_disable();
+ __tcp_v6_hash(sk);
+ local_bh_enable();
+ }
+}
+
+static struct sock *tcp_v6_lookup_listener(struct in6_addr *daddr, unsigned short hnum, int dif)
+{
+ struct sock *sk;
+ struct sock *result = NULL;
+ int score, hiscore;
+
+ hiscore=0;
+ read_lock(&tcp_lhash_lock);
+ sk = tcp_listening_hash[tcp_lhashfn(hnum)];
+ for(; sk; sk = sk->next) {
+ if((sk->num == hnum) && (sk->family == PF_INET6)) {
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+
+ score = 1;
+ if(!ipv6_addr_any(&np->rcv_saddr)) {
+ if(ipv6_addr_cmp(&np->rcv_saddr, daddr))
+ continue;
+ score++;
+ }
+ if (sk->bound_dev_if) {
+ if (sk->bound_dev_if != dif)
+ continue;
+ score++;
+ }
+ if (score == 3) {
+ result = sk;
+ break;
+ }
+ if (score > hiscore) {
+ hiscore = score;
+ result = sk;
+ }
+ }
+ }
+ if (result)
+ sock_hold(result);
+ read_unlock(&tcp_lhash_lock);
+ return result;
+}
+
+/* Sockets in TCP_CLOSE state are _always_ taken out of the hash, so
+ * we need not check it for TCP lookups anymore, thanks Alexey. -DaveM
+ *
+ * The sockhash lock must be held as a reader here.
+ */
+
+static inline struct sock *__tcp_v6_lookup_established(struct in6_addr *saddr, u16 sport,
+ struct in6_addr *daddr, u16 hnum,
+ int dif)
+{
+ struct tcp_ehash_bucket *head;
+ struct sock *sk;
+ __u32 ports = TCP_COMBINED_PORTS(sport, hnum);
+ int hash;
+
+ /* Optimize here for direct hit, only listening connections can
+ * have wildcards anyways.
+ */
+ hash = tcp_v6_hashfn(daddr, hnum, saddr, sport);
+ head = &tcp_ehash[hash];
+ read_lock(&head->lock);
+ for(sk = head->chain; sk; sk = sk->next) {
+ /* For IPV6 do the cheaper port and family tests first. */
+ if(TCP_IPV6_MATCH(sk, saddr, daddr, ports, dif))
+ goto hit; /* You sunk my battleship! */
+ }
+ /* Must check for a TIME_WAIT'er before going to listener hash. */
+ for(sk = (head + tcp_ehash_size)->chain; sk; sk = sk->next) {
+ if(*((__u32 *)&(sk->dport)) == ports &&
+ sk->family == PF_INET6) {
+ struct tcp_tw_bucket *tw = (struct tcp_tw_bucket *)sk;
+ if(!ipv6_addr_cmp(&tw->v6_daddr, saddr) &&
+ !ipv6_addr_cmp(&tw->v6_rcv_saddr, daddr) &&
+ (!sk->bound_dev_if || sk->bound_dev_if == dif))
+ goto hit;
+ }
+ }
+ read_unlock(&head->lock);
+ return NULL;
+
+hit:
+ sock_hold(sk);
+ read_unlock(&head->lock);
+ return sk;
+}
+
+
+static inline struct sock *__tcp_v6_lookup(struct in6_addr *saddr, u16 sport,
+ struct in6_addr *daddr, u16 hnum,
+ int dif)
+{
+ struct sock *sk;
+
+ sk = __tcp_v6_lookup_established(saddr, sport, daddr, hnum, dif);
+
+ if (sk)
+ return sk;
+
+ return tcp_v6_lookup_listener(daddr, hnum, dif);
+}
+
+inline struct sock *tcp_v6_lookup(struct in6_addr *saddr, u16 sport,
+ struct in6_addr *daddr, u16 dport,
+ int dif)
+{
+ struct sock *sk;
+
+ local_bh_disable();
+ sk = __tcp_v6_lookup(saddr, sport, daddr, ntohs(dport), dif);
+ local_bh_enable();
+
+ return sk;
+}
+
+
+/*
+ * Open request hash tables.
+ */
+
+static u32 tcp_v6_synq_hash(struct in6_addr *raddr, u16 rport, u32 rnd)
+{
+ u32 a, b, c;
+
+ a = raddr->s6_addr32[0];
+ b = raddr->s6_addr32[1];
+ c = raddr->s6_addr32[2];
+
+ a += JHASH_GOLDEN_RATIO;
+ b += JHASH_GOLDEN_RATIO;
+ c += rnd;
+ __jhash_mix(a, b, c);
+
+ a += raddr->s6_addr32[3];
+ b += (u32) rport;
+ __jhash_mix(a, b, c);
+
+ return c & (TCP_SYNQ_HSIZE - 1);
+}
+
+static struct open_request *tcp_v6_search_req(struct tcp_opt *tp,
+ struct open_request ***prevp,
+ __u16 rport,
+ struct in6_addr *raddr,
+ struct in6_addr *laddr,
+ int iif)
+{
+ struct tcp_listen_opt *lopt = tp->listen_opt;
+ struct open_request *req, **prev;
+
+ for (prev = &lopt->syn_table[tcp_v6_synq_hash(raddr, rport, lopt->hash_rnd)];
+ (req = *prev) != NULL;
+ prev = &req->dl_next) {
+ if (req->rmt_port == rport &&
+ req->class->family == AF_INET6 &&
+ !ipv6_addr_cmp(&req->af.v6_req.rmt_addr, raddr) &&
+ !ipv6_addr_cmp(&req->af.v6_req.loc_addr, laddr) &&
+ (!req->af.v6_req.iif || req->af.v6_req.iif == iif)) {
+ BUG_TRAP(req->sk == NULL);
+ *prevp = prev;
+ return req;
+ }
+ }
+
+ return NULL;
+}
+
+static __inline__ u16 tcp_v6_check(struct tcphdr *th, int len,
+ struct in6_addr *saddr,
+ struct in6_addr *daddr,
+ unsigned long base)
+{
+ return csum_ipv6_magic(saddr, daddr, len, IPPROTO_TCP, base);
+}
+
+static __u32 tcp_v6_init_sequence(struct sock *sk, struct sk_buff *skb)
+{
+ if (skb->protocol == htons(ETH_P_IPV6)) {
+ return secure_tcpv6_sequence_number(skb->nh.ipv6h->daddr.s6_addr32,
+ skb->nh.ipv6h->saddr.s6_addr32,
+ skb->h.th->dest,
+ skb->h.th->source);
+ } else {
+ return secure_tcp_sequence_number(skb->nh.iph->daddr,
+ skb->nh.iph->saddr,
+ skb->h.th->dest,
+ skb->h.th->source);
+ }
+}
+
+static int tcp_v6_check_established(struct sock *sk)
+{
+ struct in6_addr *daddr = &sk->net_pinfo.af_inet6.rcv_saddr;
+ struct in6_addr *saddr = &sk->net_pinfo.af_inet6.daddr;
+ int dif = sk->bound_dev_if;
+ u32 ports = TCP_COMBINED_PORTS(sk->dport, sk->num);
+ int hash = tcp_v6_hashfn(daddr, sk->num, saddr, sk->dport);
+ struct tcp_ehash_bucket *head = &tcp_ehash[hash];
+ struct sock *sk2, **skp;
+ struct tcp_tw_bucket *tw;
+
+ write_lock_bh(&head->lock);
+
+ for(skp = &(head + tcp_ehash_size)->chain; (sk2=*skp)!=NULL; skp = &sk2->next) {
+ tw = (struct tcp_tw_bucket*)sk2;
+
+ if(*((__u32 *)&(sk2->dport)) == ports &&
+ sk2->family == PF_INET6 &&
+ !ipv6_addr_cmp(&tw->v6_daddr, saddr) &&
+ !ipv6_addr_cmp(&tw->v6_rcv_saddr, daddr) &&
+ sk2->bound_dev_if == sk->bound_dev_if) {
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ if (tw->ts_recent_stamp) {
+ /* See comment in tcp_ipv4.c */
+ if ((tp->write_seq = tw->snd_nxt+65535+2) == 0)
+ tp->write_seq = 1;
+ tp->ts_recent = tw->ts_recent;
+ tp->ts_recent_stamp = tw->ts_recent_stamp;
+ sock_hold(sk2);
+ skp = &head->chain;
+ goto unique;
+ } else
+ goto not_unique;
+ }
+ }
+ tw = NULL;
+
+ for(skp = &head->chain; (sk2=*skp)!=NULL; skp = &sk2->next) {
+ if(TCP_IPV6_MATCH(sk2, saddr, daddr, ports, dif))
+ goto not_unique;
+ }
+
+unique:
+ BUG_TRAP(sk->pprev==NULL);
+ if ((sk->next = *skp) != NULL)
+ (*skp)->pprev = &sk->next;
+
+ *skp = sk;
+ sk->pprev = skp;
+ sk->hashent = hash;
+ sock_prot_inc_use(sk->prot);
+ write_unlock_bh(&head->lock);
+
+ if (tw) {
+ /* Silly. Should hash-dance instead... */
+ local_bh_disable();
+ tcp_tw_deschedule(tw);
+ tcp_timewait_kill(tw);
+ NET_INC_STATS_BH(TimeWaitRecycled);
+ local_bh_enable();
+
+ tcp_tw_put(tw);
+ }
+ return 0;
+
+not_unique:
+ write_unlock_bh(&head->lock);
+ return -EADDRNOTAVAIL;
+}
+
+static int tcp_v6_hash_connect(struct sock *sk)
+{
+ struct tcp_bind_hashbucket *head;
+ struct tcp_bind_bucket *tb;
+
+ /* XXX */
+ if (sk->num == 0) {
+ int err = tcp_v6_get_port(sk, sk->num);
+ if (err)
+ return err;
+ sk->sport = htons(sk->num);
+ }
+
+ head = &tcp_bhash[tcp_bhashfn(sk->num)];
+ tb = head->chain;
+
+ spin_lock_bh(&head->lock);
+
+ if (tb->owners == sk && sk->bind_next == NULL) {
+ __tcp_v6_hash(sk);
+ spin_unlock_bh(&head->lock);
+ return 0;
+ } else {
+ spin_unlock_bh(&head->lock);
+ return tcp_v6_check_established(sk);
+ }
+}
+
+static __inline__ int tcp_v6_iif(struct sk_buff *skb)
+{
+ struct inet6_skb_parm *opt = (struct inet6_skb_parm *) skb->cb;
+ return opt->iif;
+}
+
+static int tcp_v6_connect(struct sock *sk, struct sockaddr *uaddr,
+ int addr_len)
+{
+ struct sockaddr_in6 *usin = (struct sockaddr_in6 *) uaddr;
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ struct in6_addr *saddr = NULL;
+ struct in6_addr saddr_buf;
+ struct flowi fl;
+ struct dst_entry *dst;
+ int addr_type;
+ int reroute = 0;
+ int err;
+
+ if (addr_len < SIN6_LEN_RFC2133)
+ return -EINVAL;
+
+ if (usin->sin6_family != AF_INET6)
+ return(-EAFNOSUPPORT);
+
+ memset(&fl, 0, sizeof(fl));
+
+ if (np->sndflow) {
+ fl.fl6_flowlabel = usin->sin6_flowinfo&IPV6_FLOWINFO_MASK;
+ IP6_ECN_flow_init(fl.fl6_flowlabel);
+ if (fl.fl6_flowlabel&IPV6_FLOWLABEL_MASK) {
+ struct ip6_flowlabel *flowlabel;
+ flowlabel = fl6_sock_lookup(sk, fl.fl6_flowlabel);
+ if (flowlabel == NULL)
+ return -EINVAL;
+ ipv6_addr_copy(&usin->sin6_addr, &flowlabel->dst);
+ fl6_sock_release(flowlabel);
+ }
+ }
+
+ /*
+ * connect() to INADDR_ANY means loopback (BSD'ism).
+ */
+
+ if(ipv6_addr_any(&usin->sin6_addr))
+ usin->sin6_addr.s6_addr[15] = 0x1;
+
+ addr_type = ipv6_addr_type(&usin->sin6_addr);
+
+ if(addr_type & IPV6_ADDR_MULTICAST)
+ return -ENETUNREACH;
+
+ if (addr_type&IPV6_ADDR_LINKLOCAL) {
+ if (addr_len >= sizeof(struct sockaddr_in6) &&
+ usin->sin6_scope_id) {
+ /* If interface is set while binding, indices
+ * must coincide.
+ */
+ if (sk->bound_dev_if &&
+ sk->bound_dev_if != usin->sin6_scope_id)
+ return -EINVAL;
+
+ sk->bound_dev_if = usin->sin6_scope_id;
+ }
+
+#ifndef CONFIG_IPV6_LOOSE_SCOPE_ID
+ /* Connect to link-local address requires an interface */
+ if (sk->bound_dev_if == 0)
+ return -EINVAL;
+#endif
+ }
+
+ if (tp->ts_recent_stamp && ipv6_addr_cmp(&np->daddr, &usin->sin6_addr)) {
+ tp->ts_recent = 0;
+ tp->ts_recent_stamp = 0;
+ tp->write_seq = 0;
+ }
+
+ ipv6_addr_copy(&np->daddr, &usin->sin6_addr);
+ np->flow_label = fl.fl6_flowlabel;
+
+ /*
+ * TCP over IPv4
+ */
+
+ if (addr_type == IPV6_ADDR_MAPPED) {
+ u32 exthdrlen = tp->ext_header_len;
+ struct sockaddr_in sin;
+
+ SOCK_DEBUG(sk, "connect: ipv4 mapped\n");
+
+ if (__ipv6_only_sock(sk))
+ return -ENETUNREACH;
+
+ sin.sin_family = AF_INET;
+ sin.sin_port = usin->sin6_port;
+ sin.sin_addr.s_addr = usin->sin6_addr.s6_addr32[3];
+
+ sk->tp_pinfo.af_tcp.af_specific = &ipv6_mapped;
+ sk->backlog_rcv = tcp_v4_do_rcv;
+
+ err = tcp_v4_connect(sk, (struct sockaddr *)&sin, sizeof(sin));
+
+ if (err) {
+ tp->ext_header_len = exthdrlen;
+ sk->tp_pinfo.af_tcp.af_specific = &ipv6_specific;
+ sk->backlog_rcv = tcp_v6_do_rcv;
+ goto failure;
+ } else {
+ ipv6_addr_set(&np->saddr, 0, 0, htonl(0x0000FFFF),
+ sk->saddr);
+ ipv6_addr_set(&np->rcv_saddr, 0, 0, htonl(0x0000FFFF),
+ sk->rcv_saddr);
+ }
+
+ return err;
+ }
+
+ if (!ipv6_addr_any(&np->rcv_saddr))
+ saddr = &np->rcv_saddr;
+
+ fl.proto = IPPROTO_TCP;
+ fl.fl6_dst = &np->daddr;
+ fl.fl6_src = saddr;
+ fl.oif = sk->bound_dev_if;
+ fl.uli_u.ports.dport = usin->sin6_port;
+ fl.uli_u.ports.sport = sk->sport;
+
+ if (np->opt && np->opt->srcrt) {
+ struct rt0_hdr *rt0 = (struct rt0_hdr *) np->opt->srcrt;
+ fl.nl_u.ip6_u.daddr = rt0->addr;
+ }
+
+ dst = ip6_route_output(sk, &fl);
+
+#ifdef CONFIG_IPV6_SUBTREES
+ reroute = (saddr == NULL);
+#endif
+ if ((err = dst->error) != 0) {
+ dst_release(dst);
+ goto failure;
+ }
+ if (!reroute) {
+ ip6_dst_store(sk, dst, NULL, NULL);
+ sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM;
+ }
+
+ if (saddr == NULL) {
+ err = ipv6_get_saddr(dst, &np->daddr, &saddr_buf, np->use_tempaddr);
+
+
+ if (reroute)
+ dst_release(dst);
+ if (err)
+ goto failure;
+
+ saddr = &saddr_buf;
+#ifdef CONFIG_IPV6_SUBTREES
+ if (saddr) fl.fl6_src = saddr;
+ dst = ip6_route_output(sk, &fl);
+
+ if ((err = dst->error) != 0) {
+ dst_release(dst);
+ goto failure;
+ }
+ ip6_dst_store(sk, dst, NULL, NULL);
+ sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM;
+#endif
+ ipv6_addr_copy(&np->rcv_saddr, saddr);
+ }
+
+ /* set the source address */
+ ipv6_addr_copy(&np->saddr, saddr);
+ sk->rcv_saddr= LOOPBACK4_IPV6;
+
+ tp->ext_header_len = 0;
+ {
+ if (np->opt)
+ tp->ext_header_len += np->opt->opt_flen +
+ np->opt->opt_nflen;
+ }
+ tp->mss_clamp = IPV6_MIN_MTU - sizeof(struct tcphdr) - sizeof(struct ipv6hdr);
+
+ sk->dport = usin->sin6_port;
+
+ tcp_set_state(sk, TCP_SYN_SENT);
+ err = tcp_v6_hash_connect(sk);
+ if (err)
+ goto late_failure;
+
+ if (!tp->write_seq)
+ tp->write_seq = secure_tcpv6_sequence_number(np->saddr.s6_addr32,
+ np->daddr.s6_addr32,
+ sk->sport, sk->dport);
+ err = tcp_connect(sk);
+ if (err)
+ goto late_failure;
+
+ return 0;
+
+late_failure:
+ tcp_set_state(sk, TCP_CLOSE);
+failure:
+ __sk_dst_reset(sk);
+ sk->dport = 0;
+ sk->route_caps = 0;
+ return err;
+}
+
+void tcp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
+ int type, int code, int offset, __u32 info)
+{
+ struct ipv6hdr *hdr = (struct ipv6hdr*)skb->data;
+ struct tcphdr *th = (struct tcphdr *)(skb->data+offset);
+ struct ipv6_pinfo *np;
+ struct sock *sk;
+ int err;
+ struct tcp_opt *tp;
+ __u32 seq;
+
+ sk = tcp_v6_lookup(&hdr->daddr, th->dest, &hdr->saddr, th->source, skb->dev->ifindex);
+
+ if (sk == NULL) {
+ struct inet6_dev *idev = in6_dev_get(skb->dev);
+ ICMP6_INC_STATS_BH(idev,Icmp6InErrors);
+ if (idev)
+ in6_dev_put(idev);
+ return;
+ }
+
+ if (sk->state == TCP_TIME_WAIT) {
+ tcp_tw_put((struct tcp_tw_bucket*)sk);
+ return;
+ }
+
+ bh_lock_sock(sk);
+ if (sk->lock.users)
+ NET_INC_STATS_BH(LockDroppedIcmps);
+
+ if (sk->state == TCP_CLOSE)
+ goto out;
+
+ tp = &sk->tp_pinfo.af_tcp;
+ seq = ntohl(th->seq);
+ if (sk->state != TCP_LISTEN && !between(seq, tp->snd_una, tp->snd_nxt)) {
+ NET_INC_STATS_BH(OutOfWindowIcmps);
+ goto out;
+ }
+
+ np = &sk->net_pinfo.af_inet6;
+
+ if (type == ICMPV6_PKT_TOOBIG) {
+ struct dst_entry *dst = NULL;
+
+ if (sk->lock.users)
+ goto out;
+ if ((1<<sk->state)&(TCPF_LISTEN|TCPF_CLOSE))
+ goto out;
+
+ /* icmp should have updated the destination cache entry */
+ dst = __sk_dst_check(sk, np->dst_cookie);
+
+ if (dst == NULL) {
+ struct flowi fl;
+
+ /* BUGGG_FUTURE: Again, it is not clear how
+ to handle rthdr case. Ignore this complexity
+ for now.
+ */
+ fl.proto = IPPROTO_TCP;
+ fl.nl_u.ip6_u.daddr = &np->daddr;
+ fl.nl_u.ip6_u.saddr = &np->saddr;
+ fl.oif = sk->bound_dev_if;
+ fl.uli_u.ports.dport = sk->dport;
+ fl.uli_u.ports.sport = sk->sport;
+
+ dst = ip6_route_output(sk, &fl);
+ } else
+ dst_hold(dst);
+
+ if (dst->error) {
+ sk->err_soft = -dst->error;
+ } else if (tp->pmtu_cookie > dst->pmtu) {
+ tcp_sync_mss(sk, dst->pmtu);
+ tcp_simple_retransmit(sk);
+ } /* else let the usual retransmit timer handle it */
+ dst_release(dst);
+ goto out;
+ }
+
+ icmpv6_err_convert(type, code, &err);
+
+ /* Might be for an open_request */
+ switch (sk->state) {
+ struct open_request *req, **prev;
+ case TCP_LISTEN:
+ if (sk->lock.users)
+ goto out;
+
+ req = tcp_v6_search_req(tp, &prev, th->dest, &hdr->daddr,
+ &hdr->saddr, tcp_v6_iif(skb));
+ if (!req)
+ goto out;
+
+ /* ICMPs are not backlogged, hence we cannot get
+ * an established socket here.
+ */
+ BUG_TRAP(req->sk == NULL);
+
+ if (seq != req->snt_isn) {
+ NET_INC_STATS_BH(OutOfWindowIcmps);
+ goto out;
+ }
+
+ tcp_synq_drop(sk, req, prev);
+ goto out;
+
+ case TCP_SYN_SENT:
+ case TCP_SYN_RECV: /* Cannot happen.
+ It can, it SYNs are crossed. --ANK */
+ if (sk->lock.users == 0) {
+ TCP_INC_STATS_BH(TcpAttemptFails);
+ sk->err = err;
+ sk->error_report(sk); /* Wake people up to see the error (see connect in sock.c) */
+
+ tcp_done(sk);
+ } else {
+ sk->err_soft = err;
+ }
+ goto out;
+ }
+
+ if (sk->lock.users == 0 && np->recverr) {
+ sk->err = err;
+ sk->error_report(sk);
+ } else {
+ sk->err_soft = err;
+ }
+
+out:
+ bh_unlock_sock(sk);
+ sock_put(sk);
+}
+
+
+static int tcp_v6_send_synack(struct sock *sk, struct open_request *req,
+ struct dst_entry *dst)
+{
+ struct sk_buff * skb;
+ struct ipv6_txoptions *opt = NULL;
+ struct flowi fl;
+ int err = -1;
+
+ fl.proto = IPPROTO_TCP;
+ fl.nl_u.ip6_u.daddr = &req->af.v6_req.rmt_addr;
+ fl.nl_u.ip6_u.saddr = &req->af.v6_req.loc_addr;
+ fl.fl6_flowlabel = 0;
+ fl.oif = req->af.v6_req.iif;
+ fl.uli_u.ports.dport = req->rmt_port;
+ fl.uli_u.ports.sport = sk->sport;
+
+ if (dst == NULL) {
+ opt = sk->net_pinfo.af_inet6.opt;
+ if (opt == NULL &&
+ sk->net_pinfo.af_inet6.rxopt.bits.srcrt == 2 &&
+ req->af.v6_req.pktopts) {
+ struct sk_buff *pktopts = req->af.v6_req.pktopts;
+ struct inet6_skb_parm *rxopt = (struct inet6_skb_parm *)pktopts->cb;
+ if (rxopt->srcrt)
+ opt = ipv6_invert_rthdr(sk, (struct ipv6_rt_hdr*)(pktopts->nh.raw + rxopt->srcrt));
+ }
+
+ if (opt && opt->srcrt) {
+ struct rt0_hdr *rt0 = (struct rt0_hdr *) opt->srcrt;
+ fl.nl_u.ip6_u.daddr = rt0->addr;
+ }
+
+ dst = ip6_route_output(sk, &fl);
+ if (dst->error)
+ goto done;
+ }
+
+ skb = tcp_make_synack(sk, dst, req);
+ if (skb) {
+ struct tcphdr *th = skb->h.th;
+
+ th->check = tcp_v6_check(th, skb->len,
+ &req->af.v6_req.loc_addr, &req->af.v6_req.rmt_addr,
+ csum_partial((char *)th, skb->len, skb->csum));
+
+ fl.nl_u.ip6_u.daddr = &req->af.v6_req.rmt_addr;
+ err = ip6_xmit(sk, skb, &fl, opt);
+ if (err == NET_XMIT_CN)
+ err = 0;
+ }
+
+done:
+ dst_release(dst);
+ if (opt && opt != sk->net_pinfo.af_inet6.opt)
+ sock_kfree_s(sk, opt, opt->tot_len);
+ return err;
+}
+
+static void tcp_v6_or_free(struct open_request *req)
+{
+ if (req->af.v6_req.pktopts)
+ kfree_skb(req->af.v6_req.pktopts);
+}
+
+static struct or_calltable or_ipv6 = {
+ AF_INET6,
+ tcp_v6_send_synack,
+ tcp_v6_or_send_ack,
+ tcp_v6_or_free,
+ tcp_v6_send_reset
+};
+
+static int ipv6_opt_accepted(struct sock *sk, struct sk_buff *skb)
+{
+ struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb;
+
+ if (sk->net_pinfo.af_inet6.rxopt.all) {
+ if ((opt->hop && sk->net_pinfo.af_inet6.rxopt.bits.hopopts) ||
+ ((IPV6_FLOWINFO_MASK&*(u32*)skb->nh.raw) &&
+ sk->net_pinfo.af_inet6.rxopt.bits.rxflow) ||
+ (opt->srcrt && sk->net_pinfo.af_inet6.rxopt.bits.srcrt) ||
+ ((opt->dst1 || opt->dst0) && sk->net_pinfo.af_inet6.rxopt.bits.dstopts))
+ return 1;
+ }
+ return 0;
+}
+
+
+static void tcp_v6_send_check(struct sock *sk, struct tcphdr *th, int len,
+ struct sk_buff *skb)
+{
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+
+ if (skb->ip_summed == CHECKSUM_HW) {
+ th->check = ~csum_ipv6_magic(&np->saddr, &np->daddr, len, IPPROTO_TCP, 0);
+ skb->csum = offsetof(struct tcphdr, check);
+ } else {
+ th->check = csum_ipv6_magic(&np->saddr, &np->daddr, len, IPPROTO_TCP,
+ csum_partial((char *)th, th->doff<<2,
+ skb->csum));
+ }
+}
+
+
+static void tcp_v6_send_reset(struct sk_buff *skb)
+{
+ struct tcphdr *th = skb->h.th, *t1;
+ struct sk_buff *buff;
+ struct flowi fl;
+
+ if (th->rst)
+ return;
+
+ if (ipv6_addr_is_multicast(&skb->nh.ipv6h->daddr))
+ return;
+
+ /*
+ * We need to grab some memory, and put together an RST,
+ * and then put it into the queue to be sent.
+ */
+
+ buff = alloc_skb(MAX_HEADER + sizeof(struct ipv6hdr) + sizeof(struct tcphdr), GFP_ATOMIC);
+ if (buff == NULL)
+ return;
+
+ skb_reserve(buff, MAX_HEADER + sizeof(struct ipv6hdr) + sizeof(struct tcphdr));
+
+ t1 = (struct tcphdr *) skb_push(buff,sizeof(struct tcphdr));
+
+ /* Swap the send and the receive. */
+ memset(t1, 0, sizeof(*t1));
+ t1->dest = th->source;
+ t1->source = th->dest;
+ t1->doff = sizeof(*t1)/4;
+ t1->rst = 1;
+
+ if(th->ack) {
+ t1->seq = th->ack_seq;
+ } else {
+ t1->ack = 1;
+ t1->ack_seq = htonl(ntohl(th->seq) + th->syn + th->fin
+ + skb->len - (th->doff<<2));
+ }
+
+ buff->csum = csum_partial((char *)t1, sizeof(*t1), 0);
+
+ fl.nl_u.ip6_u.daddr = &skb->nh.ipv6h->saddr;
+ fl.nl_u.ip6_u.saddr = &skb->nh.ipv6h->daddr;
+ fl.fl6_flowlabel = 0;
+
+ t1->check = csum_ipv6_magic(fl.nl_u.ip6_u.saddr,
+ fl.nl_u.ip6_u.daddr,
+ sizeof(*t1), IPPROTO_TCP,
+ buff->csum);
+
+ fl.proto = IPPROTO_TCP;
+ fl.oif = tcp_v6_iif(skb);
+ fl.uli_u.ports.dport = t1->dest;
+ fl.uli_u.ports.sport = t1->source;
+
+ /* sk = NULL, but it is safe for now. RST socket required. */
+ buff->dst = ip6_route_output(NULL, &fl);
+
+ if (buff->dst->error == 0) {
+ ip6_xmit(NULL, buff, &fl, NULL);
+ TCP_INC_STATS_BH(TcpOutSegs);
+ TCP_INC_STATS_BH(TcpOutRsts);
+ return;
+ }
+
+ kfree_skb(buff);
+}
+
+static void tcp_v6_send_ack(struct sk_buff *skb, u32 seq, u32 ack, u32 win, u32 ts)
+{
+ struct tcphdr *th = skb->h.th, *t1;
+ struct sk_buff *buff;
+ struct flowi fl;
+ int tot_len = sizeof(struct tcphdr);
+
+ if (ts)
+ tot_len += 3*4;
+
+ buff = alloc_skb(MAX_HEADER + sizeof(struct ipv6hdr) + tot_len, GFP_ATOMIC);
+ if (buff == NULL)
+ return;
+
+ skb_reserve(buff, MAX_HEADER + sizeof(struct ipv6hdr) + tot_len);
+
+ t1 = (struct tcphdr *) skb_push(buff,tot_len);
+
+ /* Swap the send and the receive. */
+ memset(t1, 0, sizeof(*t1));
+ t1->dest = th->source;
+ t1->source = th->dest;
+ t1->doff = tot_len/4;
+ t1->seq = htonl(seq);
+ t1->ack_seq = htonl(ack);
+ t1->ack = 1;
+ t1->window = htons(win);
+
+ if (ts) {
+ u32 *ptr = (u32*)(t1 + 1);
+ *ptr++ = htonl((TCPOPT_NOP << 24) |
+ (TCPOPT_NOP << 16) |
+ (TCPOPT_TIMESTAMP << 8) |
+ TCPOLEN_TIMESTAMP);
+ *ptr++ = htonl(tcp_time_stamp);
+ *ptr = htonl(ts);
+ }
+
+ buff->csum = csum_partial((char *)t1, tot_len, 0);
+
+ fl.nl_u.ip6_u.daddr = &skb->nh.ipv6h->saddr;
+ fl.nl_u.ip6_u.saddr = &skb->nh.ipv6h->daddr;
+ fl.fl6_flowlabel = 0;
+
+ t1->check = csum_ipv6_magic(fl.nl_u.ip6_u.saddr,
+ fl.nl_u.ip6_u.daddr,
+ tot_len, IPPROTO_TCP,
+ buff->csum);
+
+ fl.proto = IPPROTO_TCP;
+ fl.oif = tcp_v6_iif(skb);
+ fl.uli_u.ports.dport = t1->dest;
+ fl.uli_u.ports.sport = t1->source;
+
+ buff->dst = ip6_route_output(NULL, &fl);
+
+ if (buff->dst->error == 0) {
+ ip6_xmit(NULL, buff, &fl, NULL);
+ TCP_INC_STATS_BH(TcpOutSegs);
+ return;
+ }
+
+ kfree_skb(buff);
+}
+
+static void tcp_v6_timewait_ack(struct sock *sk, struct sk_buff *skb)
+{
+ struct tcp_tw_bucket *tw = (struct tcp_tw_bucket *)sk;
+
+ tcp_v6_send_ack(skb, tw->snd_nxt, tw->rcv_nxt,
+ tw->rcv_wnd>>tw->rcv_wscale, tw->ts_recent);
+
+ tcp_tw_put(tw);
+}
+
+static void tcp_v6_or_send_ack(struct sk_buff *skb, struct open_request *req)
+{
+ tcp_v6_send_ack(skb, req->snt_isn+1, req->rcv_isn+1, req->rcv_wnd, req->ts_recent);
+}
+
+
+static struct sock *tcp_v6_hnd_req(struct sock *sk,struct sk_buff *skb)
+{
+ struct open_request *req, **prev;
+ struct tcphdr *th = skb->h.th;
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ struct sock *nsk;
+
+ /* Find possible connection requests. */
+ req = tcp_v6_search_req(tp, &prev, th->source, &skb->nh.ipv6h->saddr,
+ &skb->nh.ipv6h->daddr, tcp_v6_iif(skb));
+ if (req)
+ return tcp_check_req(sk, skb, req, prev);
+
+ nsk = __tcp_v6_lookup_established(&skb->nh.ipv6h->saddr,
+ th->source,
+ &skb->nh.ipv6h->daddr,
+ ntohs(th->dest),
+ tcp_v6_iif(skb));
+
+ if (nsk) {
+ if (nsk->state != TCP_TIME_WAIT) {
+ bh_lock_sock(nsk);
+ return nsk;
+ }
+ tcp_tw_put((struct tcp_tw_bucket*)nsk);
+ return NULL;
+ }
+
+#if 0 /*def CONFIG_SYN_COOKIES*/
+ if (!th->rst && !th->syn && th->ack)
+ sk = cookie_v6_check(sk, skb, &(IPCB(skb)->opt));
+#endif
+ return sk;
+}
+
+static void tcp_v6_synq_add(struct sock *sk, struct open_request *req)
+{
+ struct tcp_opt *tp = &sk->tp_pinfo.af_tcp;
+ struct tcp_listen_opt *lopt = tp->listen_opt;
+ u32 h = tcp_v6_synq_hash(&req->af.v6_req.rmt_addr, req->rmt_port, lopt->hash_rnd);
+
+ req->sk = NULL;
+ req->expires = jiffies + TCP_TIMEOUT_INIT;
+ req->retrans = 0;
+ req->dl_next = lopt->syn_table[h];
+
+ write_lock(&tp->syn_wait_lock);
+ lopt->syn_table[h] = req;
+ write_unlock(&tp->syn_wait_lock);
+
+ tcp_synq_added(sk);
+}
+
+
+/* FIXME: this is substantially similar to the ipv4 code.
+ * Can some kind of merge be done? -- erics
+ */
+static int tcp_v6_conn_request(struct sock *sk, struct sk_buff *skb)
+{
+ struct tcp_opt tp;
+ struct open_request *req = NULL;
+ __u32 isn = TCP_SKB_CB(skb)->when;
+
+ if (skb->protocol == htons(ETH_P_IP))
+ return tcp_v4_conn_request(sk, skb);
+
+ /* FIXME: do the same check for anycast */
+ if (ipv6_addr_is_multicast(&skb->nh.ipv6h->daddr))
+ goto drop;
+
+ /*
+ * There are no SYN attacks on IPv6, yet...
+ */
+ if (tcp_synq_is_full(sk) && !isn) {
+ if (net_ratelimit())
+ printk(KERN_INFO "TCPv6: dropping request, synflood is possible\n");
+ goto drop;
+ }
+
+ if (tcp_acceptq_is_full(sk) && tcp_synq_young(sk) > 1)
+ goto drop;
+
+ req = tcp_openreq_alloc();
+ if (req == NULL)
+ goto drop;
+
+ tcp_clear_options(&tp);
+ tp.mss_clamp = IPV6_MIN_MTU - sizeof(struct tcphdr) - sizeof(struct ipv6hdr);
+ tp.user_mss = sk->tp_pinfo.af_tcp.user_mss;
+
+ tcp_parse_options(skb, &tp, 0);
+
+ tp.tstamp_ok = tp.saw_tstamp;
+ tcp_openreq_init(req, &tp, skb);
+
+ req->class = &or_ipv6;
+ ipv6_addr_copy(&req->af.v6_req.rmt_addr, &skb->nh.ipv6h->saddr);
+ ipv6_addr_copy(&req->af.v6_req.loc_addr, &skb->nh.ipv6h->daddr);
+ TCP_ECN_create_request(req, skb->h.th);
+ req->af.v6_req.pktopts = NULL;
+ if (ipv6_opt_accepted(sk, skb) ||
+ sk->net_pinfo.af_inet6.rxopt.bits.rxinfo ||
+ sk->net_pinfo.af_inet6.rxopt.bits.rxhlim) {
+ atomic_inc(&skb->users);
+ req->af.v6_req.pktopts = skb;
+ }
+ req->af.v6_req.iif = sk->bound_dev_if;
+
+ /* So that link locals have meaning */
+ if (!sk->bound_dev_if && ipv6_addr_type(&req->af.v6_req.rmt_addr)&IPV6_ADDR_LINKLOCAL)
+ req->af.v6_req.iif = tcp_v6_iif(skb);
+
+ if (isn == 0)
+ isn = tcp_v6_init_sequence(sk,skb);
+
+ req->snt_isn = isn;
+
+ if (tcp_v6_send_synack(sk, req, NULL))
+ goto drop;
+
+ tcp_v6_synq_add(sk, req);
+
+ return 0;
+
+drop:
+ if (req)
+ tcp_openreq_free(req);
+
+ TCP_INC_STATS_BH(TcpAttemptFails);
+ return 0; /* don't send reset */
+}
+
+static struct sock * tcp_v6_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
+ struct open_request *req,
+ struct dst_entry *dst)
+{
+ struct ipv6_pinfo *np;
+ struct flowi fl;
+ struct tcp_opt *newtp;
+ struct sock *newsk;
+ struct ipv6_txoptions *opt;
+
+ if (skb->protocol == htons(ETH_P_IP)) {
+ /*
+ * v6 mapped
+ */
+
+ newsk = tcp_v4_syn_recv_sock(sk, skb, req, dst);
+
+ if (newsk == NULL)
+ return NULL;
+
+ np = &newsk->net_pinfo.af_inet6;
+
+ ipv6_addr_set(&np->daddr, 0, 0, htonl(0x0000FFFF),
+ newsk->daddr);
+
+ ipv6_addr_set(&np->saddr, 0, 0, htonl(0x0000FFFF),
+ newsk->saddr);
+
+ ipv6_addr_copy(&np->rcv_saddr, &np->saddr);
+
+ newsk->tp_pinfo.af_tcp.af_specific = &ipv6_mapped;
+ newsk->backlog_rcv = tcp_v4_do_rcv;
+ newsk->net_pinfo.af_inet6.pktoptions = NULL;
+ newsk->net_pinfo.af_inet6.opt = NULL;
+ newsk->net_pinfo.af_inet6.mcast_oif = tcp_v6_iif(skb);
+ newsk->net_pinfo.af_inet6.mcast_hops = skb->nh.ipv6h->hop_limit;
+
+ /* Charge newly allocated IPv6 socket. Though it is mapped,
+ * it is IPv6 yet.
+ */
+#ifdef INET_REFCNT_DEBUG
+ atomic_inc(&inet6_sock_nr);
+#endif
+ MOD_INC_USE_COUNT;
+
+ /* It is tricky place. Until this moment IPv4 tcp
+ worked with IPv6 af_tcp.af_specific.
+ Sync it now.
+ */
+ tcp_sync_mss(newsk, newsk->tp_pinfo.af_tcp.pmtu_cookie);
+
+ return newsk;
+ }
+
+ opt = sk->net_pinfo.af_inet6.opt;
+
+ if (tcp_acceptq_is_full(sk))
+ goto out_overflow;
+
+ if (sk->net_pinfo.af_inet6.rxopt.bits.srcrt == 2 &&
+ opt == NULL && req->af.v6_req.pktopts) {
+ struct inet6_skb_parm *rxopt = (struct inet6_skb_parm *)req->af.v6_req.pktopts->cb;
+ if (rxopt->srcrt)
+ opt = ipv6_invert_rthdr(sk, (struct ipv6_rt_hdr*)(req->af.v6_req.pktopts->nh.raw+rxopt->srcrt));
+ }
+
+ if (dst == NULL) {
+ fl.proto = IPPROTO_TCP;
+ fl.nl_u.ip6_u.daddr = &req->af.v6_req.rmt_addr;
+ if (opt && opt->srcrt) {
+ struct rt0_hdr *rt0 = (struct rt0_hdr *) opt->srcrt;
+ fl.nl_u.ip6_u.daddr = rt0->addr;
+ }
+ fl.nl_u.ip6_u.saddr = &req->af.v6_req.loc_addr;
+ fl.fl6_flowlabel = 0;
+ fl.oif = sk->bound_dev_if;
+ fl.uli_u.ports.dport = req->rmt_port;
+ fl.uli_u.ports.sport = sk->sport;
+
+ dst = ip6_route_output(sk, &fl);
+ }
+
+ if (dst->error)
+ goto out;
+
+ newsk = tcp_create_openreq_child(sk, req, skb);
+ if (newsk == NULL)
+ goto out;
+
+ /* Charge newly allocated IPv6 socket */
+#ifdef INET_REFCNT_DEBUG
+ atomic_inc(&inet6_sock_nr);
+#endif
+ MOD_INC_USE_COUNT;
+
+ ip6_dst_store(newsk, dst, NULL, NULL);
+
+ sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM;
+
+ newtp = &(newsk->tp_pinfo.af_tcp);
+
+ np = &newsk->net_pinfo.af_inet6;
+ ipv6_addr_copy(&np->daddr, &req->af.v6_req.rmt_addr);
+ ipv6_addr_copy(&np->saddr, &req->af.v6_req.loc_addr);
+ ipv6_addr_copy(&np->rcv_saddr, &req->af.v6_req.loc_addr);
+ newsk->bound_dev_if = req->af.v6_req.iif;
+
+ /* Now IPv6 options...
+
+ First: no IPv4 options.
+ */
+ newsk->protinfo.af_inet.opt = NULL;
+
+ /* Clone RX bits */
+ np->rxopt.all = sk->net_pinfo.af_inet6.rxopt.all;
+
+ /* Clone pktoptions received with SYN */
+ np->pktoptions = NULL;
+ if (req->af.v6_req.pktopts) {
+ np->pktoptions = skb_clone(req->af.v6_req.pktopts, GFP_ATOMIC);
+ kfree_skb(req->af.v6_req.pktopts);
+ req->af.v6_req.pktopts = NULL;
+ if (np->pktoptions)
+ skb_set_owner_r(np->pktoptions, newsk);
+ }
+ np->opt = NULL;
+ np->mcast_oif = tcp_v6_iif(skb);
+ np->mcast_hops = skb->nh.ipv6h->hop_limit;
+
+ /* Clone native IPv6 options from listening socket (if any)
+
+ Yes, keeping reference count would be much more clever,
+ but we make one more one thing there: reattach optmem
+ to newsk.
+ */
+ if (opt) {
+ np->opt = ipv6_dup_options(newsk, opt);
+ if (opt != sk->net_pinfo.af_inet6.opt)
+ sock_kfree_s(sk, opt, opt->tot_len);
+ }
+
+ newtp->ext_header_len = 0;
+ {
+ if (np->opt)
+ newtp->ext_header_len += np->opt->opt_nflen +
+ np->opt->opt_flen;
+ }
+ tcp_sync_mss(newsk, dst->pmtu);
+ newtp->advmss = dst->advmss;
+ tcp_initialize_rcv_mss(newsk);
+
+ newsk->daddr = LOOPBACK4_IPV6;
+ newsk->saddr = LOOPBACK4_IPV6;
+ newsk->rcv_saddr= LOOPBACK4_IPV6;
+
+ __tcp_v6_hash(newsk);
+ tcp_inherit_port(sk, newsk);
+
+ return newsk;
+
+out_overflow:
+ NET_INC_STATS_BH(ListenOverflows);
+out:
+ NET_INC_STATS_BH(ListenDrops);
+ if (opt && opt != sk->net_pinfo.af_inet6.opt)
+ sock_kfree_s(sk, opt, opt->tot_len);
+ dst_release(dst);
+ return NULL;
+}
+
+static int tcp_v6_checksum_init(struct sk_buff *skb)
+{
+ if (skb->ip_summed == CHECKSUM_HW) {
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ if (!tcp_v6_check(skb->h.th,skb->len,&skb->nh.ipv6h->saddr,
+ &skb->nh.ipv6h->daddr,skb->csum))
+ return 0;
+ NETDEBUG(if (net_ratelimit()) printk(KERN_DEBUG "hw tcp v6 csum failed\n"));
+ }
+ if (skb->len <= 76) {
+ if (tcp_v6_check(skb->h.th,skb->len,&skb->nh.ipv6h->saddr,
+ &skb->nh.ipv6h->daddr,skb_checksum(skb, 0, skb->len, 0)))
+ return -1;
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ } else {
+ skb->csum = ~tcp_v6_check(skb->h.th,skb->len,&skb->nh.ipv6h->saddr,
+ &skb->nh.ipv6h->daddr,0);
+ }
+ return 0;
+}
+
+/* The socket must have it's spinlock held when we get
+ * here.
+ *
+ * We have a potential double-lock case here, so even when
+ * doing backlog processing we use the BH locking scheme.
+ * This is because we cannot sleep with the original spinlock
+ * held.
+ */
+static int tcp_v6_do_rcv(struct sock *sk, struct sk_buff *skb)
+{
+ struct sk_buff *opt_skb = NULL;
+ struct net_device *dev;
+ struct inet6_dev *idev;
+
+ /* Imagine: socket is IPv6. IPv4 packet arrives,
+ goes to IPv4 receive handler and backlogged.
+ From backlog it always goes here. Kerboom...
+ Fortunately, tcp_rcv_established and rcv_established
+ handle them correctly, but it is not case with
+ tcp_v6_hnd_req and tcp_v6_send_reset(). --ANK
+ */
+
+ if (skb->protocol == htons(ETH_P_IP))
+ return tcp_v4_do_rcv(sk, skb);
+
+ /*
+ * socket locking is here for SMP purposes as backlog rcv
+ * is currently called with bh processing disabled.
+ */
+
+ dev = dev_get_by_index(tcp_v6_iif(skb));
+ idev = dev ? in6_dev_get(dev) : NULL;
+ IP6_INC_STATS_BH(idev,Ip6InDelivers);
+ if (dev) {
+ if (idev)
+ in6_dev_put(idev);
+ dev_put(dev);
+ }
+
+ /* Do Stevens' IPV6_PKTOPTIONS.
+
+ Yes, guys, it is the only place in our code, where we
+ may make it not affecting IPv4.
+ The rest of code is protocol independent,
+ and I do not like idea to uglify IPv4.
+
+ Actually, all the idea behind IPV6_PKTOPTIONS
+ looks not very well thought. For now we latch
+ options, received in the last packet, enqueued
+ by tcp. Feel free to propose better solution.
+ --ANK (980728)
+ */
+ if (sk->net_pinfo.af_inet6.rxopt.all)
+ opt_skb = skb_clone(skb, GFP_ATOMIC);
+
+ if (sk->state == TCP_ESTABLISHED) { /* Fast path */
+ TCP_CHECK_TIMER(sk);
+ if (tcp_rcv_established(sk, skb, skb->h.th, skb->len))
+ goto reset;
+ TCP_CHECK_TIMER(sk);
+ if (opt_skb)
+ goto ipv6_pktoptions;
+ return 0;
+ }
+
+ if (skb->len < (skb->h.th->doff<<2) || tcp_checksum_complete(skb))
+ goto csum_err;
+
+ if (sk->state == TCP_LISTEN) {
+ struct sock *nsk = tcp_v6_hnd_req(sk, skb);
+ if (!nsk)
+ goto discard;
+
+ /*
+ * Queue it on the new socket if the new socket is active,
+ * otherwise we just shortcircuit this and continue with
+ * the new socket..
+ */
+ if(nsk != sk) {
+ if (tcp_child_process(sk, nsk, skb))
+ goto reset;
+ if (opt_skb)
+ __kfree_skb(opt_skb);
+ return 0;
+ }
+ }
+
+ TCP_CHECK_TIMER(sk);
+ if (tcp_rcv_state_process(sk, skb, skb->h.th, skb->len))
+ goto reset;
+ TCP_CHECK_TIMER(sk);
+ if (opt_skb)
+ goto ipv6_pktoptions;
+ return 0;
+
+reset:
+ tcp_v6_send_reset(skb);
+discard:
+ if (opt_skb)
+ __kfree_skb(opt_skb);
+ kfree_skb(skb);
+ return 0;
+csum_err:
+ TCP_INC_STATS_BH(TcpInErrs);
+ goto discard;
+
+
+ipv6_pktoptions:
+ /* Do you ask, what is it?
+
+ 1. skb was enqueued by tcp.
+ 2. skb is added to tail of read queue, rather than out of order.
+ 3. socket is not in passive state.
+ 4. Finally, it really contains options, which user wants to receive.
+ */
+ if (TCP_SKB_CB(opt_skb)->end_seq == sk->tp_pinfo.af_tcp.rcv_nxt &&
+ !((1<<sk->state)&(TCPF_CLOSE|TCPF_LISTEN))) {
+ if (sk->net_pinfo.af_inet6.rxopt.bits.rxinfo)
+ sk->net_pinfo.af_inet6.mcast_oif = tcp_v6_iif(opt_skb);
+ if (sk->net_pinfo.af_inet6.rxopt.bits.rxhlim)
+ sk->net_pinfo.af_inet6.mcast_hops = opt_skb->nh.ipv6h->hop_limit;
+ if (ipv6_opt_accepted(sk, opt_skb)) {
+ skb_set_owner_r(opt_skb, sk);
+ opt_skb = xchg(&sk->net_pinfo.af_inet6.pktoptions, opt_skb);
+ } else {
+ __kfree_skb(opt_skb);
+ opt_skb = xchg(&sk->net_pinfo.af_inet6.pktoptions, NULL);
+ }
+ }
+
+ if (opt_skb)
+ kfree_skb(opt_skb);
+ return 0;
+}
+
+int tcp_v6_rcv(struct sk_buff *skb)
+{
+ struct tcphdr *th;
+ struct sock *sk;
+ int ret;
+
+ if (skb->pkt_type != PACKET_HOST)
+ goto discard_it;
+
+ /*
+ * Count it even if it's bad.
+ */
+ TCP_INC_STATS_BH(TcpInSegs);
+
+ if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
+ goto discard_it;
+
+ th = skb->h.th;
+
+ if (th->doff < sizeof(struct tcphdr)/4)
+ goto bad_packet;
+ if (!pskb_may_pull(skb, th->doff*4))
+ goto discard_it;
+
+ if ((skb->ip_summed != CHECKSUM_UNNECESSARY &&
+ tcp_v6_checksum_init(skb) < 0))
+ goto bad_packet;
+
+ th = skb->h.th;
+ TCP_SKB_CB(skb)->seq = ntohl(th->seq);
+ TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
+ skb->len - th->doff*4);
+ TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
+ TCP_SKB_CB(skb)->when = 0;
+ TCP_SKB_CB(skb)->flags = ip6_get_dsfield(skb->nh.ipv6h);
+ TCP_SKB_CB(skb)->sacked = 0;
+
+ sk = __tcp_v6_lookup(&skb->nh.ipv6h->saddr, th->source,
+ &skb->nh.ipv6h->daddr, ntohs(th->dest), tcp_v6_iif(skb));
+
+ if (!sk)
+ goto no_tcp_socket;
+
+process:
+ if(!ipsec_sk_policy(sk,skb))
+ goto discard_and_relse;
+ if(sk->state == TCP_TIME_WAIT)
+ goto do_time_wait;
+
+ if (sk_filter(sk, skb, 0))
+ goto discard_and_relse;
+
+ skb->dev = NULL;
+
+ bh_lock_sock(sk);
+ ret = 0;
+ if (!sk->lock.users) {
+ if (!tcp_prequeue(sk, skb))
+ ret = tcp_v6_do_rcv(sk, skb);
+ } else
+ sk_add_backlog(sk, skb);
+ bh_unlock_sock(sk);
+
+ sock_put(sk);
+ return ret;
+
+no_tcp_socket:
+ if (skb->len < (th->doff<<2) || tcp_checksum_complete(skb)) {
+bad_packet:
+ TCP_INC_STATS_BH(TcpInErrs);
+ } else {
+ tcp_v6_send_reset(skb);
+ }
+
+discard_it:
+
+ /*
+ * Discard frame
+ */
+
+ kfree_skb(skb);
+ return 0;
+
+discard_and_relse:
+ sock_put(sk);
+ goto discard_it;
+
+do_time_wait:
+ if (skb->len < (th->doff<<2) || tcp_checksum_complete(skb)) {
+ TCP_INC_STATS_BH(TcpInErrs);
+ tcp_tw_put((struct tcp_tw_bucket *) sk);
+ goto discard_it;
+ }
+
+ switch(tcp_timewait_state_process((struct tcp_tw_bucket *)sk,
+ skb, th, skb->len)) {
+ case TCP_TW_SYN:
+ {
+ struct sock *sk2;
+
+ sk2 = tcp_v6_lookup_listener(&skb->nh.ipv6h->daddr, ntohs(th->dest), tcp_v6_iif(skb));
+ if (sk2 != NULL) {
+ tcp_tw_deschedule((struct tcp_tw_bucket *)sk);
+ tcp_timewait_kill((struct tcp_tw_bucket *)sk);
+ tcp_tw_put((struct tcp_tw_bucket *)sk);
+ sk = sk2;
+ goto process;
+ }
+ /* Fall through to ACK */
+ }
+ case TCP_TW_ACK:
+ tcp_v6_timewait_ack(sk, skb);
+ break;
+ case TCP_TW_RST:
+ goto no_tcp_socket;
+ case TCP_TW_SUCCESS:;
+ }
+ goto discard_it;
+}
+
+static int tcp_v6_rebuild_header(struct sock *sk)
+{
+ int err;
+ struct dst_entry *dst;
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+
+ dst = __sk_dst_check(sk, np->dst_cookie);
+
+ if (dst == NULL) {
+ struct flowi fl;
+
+ fl.proto = IPPROTO_TCP;
+ fl.nl_u.ip6_u.daddr = &np->daddr;
+ fl.nl_u.ip6_u.saddr = &np->saddr;
+ fl.fl6_flowlabel = np->flow_label;
+ fl.oif = sk->bound_dev_if;
+ fl.uli_u.ports.dport = sk->dport;
+ fl.uli_u.ports.sport = sk->sport;
+
+ if (np->opt && np->opt->srcrt) {
+ struct rt0_hdr *rt0 = (struct rt0_hdr *) np->opt->srcrt;
+ fl.nl_u.ip6_u.daddr = rt0->addr;
+ }
+
+ dst = ip6_route_output(sk, &fl);
+
+ if (dst->error) {
+ err = dst->error;
+ dst_release(dst);
+ sk->route_caps = 0;
+ return err;
+ }
+
+ ip6_dst_store(sk, dst, NULL, NULL);
+
+ sk->route_caps = dst->dev->features&~NETIF_F_IP_CSUM;
+ }
+
+ return 0;
+}
+
+static int tcp_v6_xmit(struct sk_buff *skb, int ipfragok)
+{
+ struct sock *sk = skb->sk;
+ struct ipv6_pinfo * np = &sk->net_pinfo.af_inet6;
+ struct flowi fl;
+ struct dst_entry *dst;
+
+ fl.proto = IPPROTO_TCP;
+ fl.fl6_dst = &np->daddr;
+ fl.fl6_src = &np->saddr;
+ fl.fl6_flowlabel = np->flow_label;
+ IP6_ECN_flow_xmit(sk, fl.fl6_flowlabel);
+ fl.oif = sk->bound_dev_if;
+ fl.uli_u.ports.sport = sk->sport;
+ fl.uli_u.ports.dport = sk->dport;
+
+ if (np->opt && np->opt->srcrt) {
+ struct rt0_hdr *rt0 = (struct rt0_hdr *) np->opt->srcrt;
+ fl.nl_u.ip6_u.daddr = rt0->addr;
+ }
+
+ dst = __sk_dst_check(sk, np->dst_cookie);
+
+ if (dst == NULL) {
+ dst = ip6_route_output(sk, &fl);
+
+ if (dst->error) {
+ sk->err_soft = -dst->error;
+ dst_release(dst);
+ return -sk->err_soft;
+ }
+
+ ip6_dst_store(sk, dst, NULL, NULL);
+
+ }
+
+ skb->dst = dst_clone(dst);
+
+ /* Restore final destination back after routing done */
+ fl.nl_u.ip6_u.daddr = &np->daddr;
+
+ return ip6_xmit(sk, skb, &fl, np->opt);
+}
+
+static void v6_addr2sockaddr(struct sock *sk, struct sockaddr * uaddr)
+{
+ struct ipv6_pinfo * np = &sk->net_pinfo.af_inet6;
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) uaddr;
+
+ sin6->sin6_family = AF_INET6;
+ ipv6_addr_copy(&sin6->sin6_addr, &np->daddr);
+ sin6->sin6_port = sk->dport;
+ /* We do not store received flowlabel for TCP */
+ sin6->sin6_flowinfo = 0;
+ sin6->sin6_scope_id = 0;
+ if (sk->bound_dev_if && ipv6_addr_type(&sin6->sin6_addr)&IPV6_ADDR_LINKLOCAL)
+ sin6->sin6_scope_id = sk->bound_dev_if;
+}
+
+static int tcp_v6_remember_stamp(struct sock *sk)
+{
+ /* Alas, not yet... */
+ return 0;
+}
+
+static struct tcp_func ipv6_specific = {
+ tcp_v6_xmit,
+ tcp_v6_send_check,
+ tcp_v6_rebuild_header,
+ tcp_v6_conn_request,
+ tcp_v6_syn_recv_sock,
+ tcp_v6_remember_stamp,
+ sizeof(struct ipv6hdr),
+
+ ipv6_setsockopt,
+ ipv6_getsockopt,
+ v6_addr2sockaddr,
+ sizeof(struct sockaddr_in6)
+};
+
+/*
+ * TCP over IPv4 via INET6 API
+ */
+
+static struct tcp_func ipv6_mapped = {
+ ip_queue_xmit,
+ tcp_v4_send_check,
+ tcp_v4_rebuild_header,
+ tcp_v6_conn_request,
+ tcp_v6_syn_recv_sock,
+ tcp_v4_remember_stamp,
+ sizeof(struct iphdr),
+
+ ipv6_setsockopt,
+ ipv6_getsockopt,
+ v6_addr2sockaddr,
+ sizeof(struct sockaddr_in6)
+};
+
+
+
+/* NOTE: A lot of things set to zero explicitly by call to
+ * sk_alloc() so need not be done here.
+ */
+static int tcp_v6_init_sock(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ skb_queue_head_init(&tp->out_of_order_queue);
+ tcp_init_xmit_timers(sk);
+ tcp_prequeue_init(tp);
+
+ tp->rto = TCP_TIMEOUT_INIT;
+ tp->mdev = TCP_TIMEOUT_INIT;
+
+ /* So many TCP implementations out there (incorrectly) count the
+ * initial SYN frame in their delayed-ACK and congestion control
+ * algorithms that we must have the following bandaid to talk
+ * efficiently to them. -DaveM
+ */
+ tp->snd_cwnd = 2;
+
+ /* See draft-stevens-tcpca-spec-01 for discussion of the
+ * initialization of these values.
+ */
+ tp->snd_ssthresh = 0x7fffffff;
+ tp->snd_cwnd_clamp = ~0;
+ tp->mss_cache = 536;
+
+ tp->reordering = sysctl_tcp_reordering;
+
+ sk->state = TCP_CLOSE;
+
+ sk->tp_pinfo.af_tcp.af_specific = &ipv6_specific;
+
+ sk->write_space = tcp_write_space;
+ sk->use_write_queue = 1;
+
+ sk->sndbuf = sysctl_tcp_wmem[1];
+ sk->rcvbuf = sysctl_tcp_rmem[1];
+
+ atomic_inc(&tcp_sockets_allocated);
+
+ return 0;
+}
+
+static int tcp_v6_destroy_sock(struct sock *sk)
+{
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ tcp_clear_xmit_timers(sk);
+
+ /* Cleanup up the write buffer. */
+ tcp_writequeue_purge(sk);
+
+ /* Cleans up our, hopefully empty, out_of_order_queue. */
+ __skb_queue_purge(&tp->out_of_order_queue);
+
+ /* Clean prequeue, it must be empty really */
+ __skb_queue_purge(&tp->ucopy.prequeue);
+
+ /* Clean up a referenced TCP bind bucket. */
+ if(sk->prev != NULL)
+ tcp_put_port(sk);
+
+ /* If sendmsg cached page exists, toss it. */
+ if (tp->sndmsg_page != NULL)
+ __free_page(tp->sndmsg_page);
+
+ atomic_dec(&tcp_sockets_allocated);
+
+ return inet6_destroy_sock(sk);
+}
+
+/* Proc filesystem TCPv6 sock list dumping. */
+static void get_openreq6(struct sock *sk, struct open_request *req, char *tmpbuf, int i, int uid)
+{
+ struct in6_addr *dest, *src;
+ int ttd = req->expires - jiffies;
+
+ if (ttd < 0)
+ ttd = 0;
+
+ src = &req->af.v6_req.loc_addr;
+ dest = &req->af.v6_req.rmt_addr;
+ sprintf(tmpbuf,
+ "%4d: %08X%08X%08X%08X:%04X %08X%08X%08X%08X:%04X "
+ "%02X %08X:%08X %02X:%08X %08X %5d %8d %d %d %p",
+ i,
+ src->s6_addr32[0], src->s6_addr32[1],
+ src->s6_addr32[2], src->s6_addr32[3],
+ ntohs(sk->sport),
+ dest->s6_addr32[0], dest->s6_addr32[1],
+ dest->s6_addr32[2], dest->s6_addr32[3],
+ ntohs(req->rmt_port),
+ TCP_SYN_RECV,
+ 0,0, /* could print option size, but that is af dependent. */
+ 1, /* timers active (only the expire timer) */
+ ttd,
+ req->retrans,
+ uid,
+ 0, /* non standard timer */
+ 0, /* open_requests have no inode */
+ 0, req);
+}
+
+static void get_tcp6_sock(struct sock *sp, char *tmpbuf, int i)
+{
+ struct in6_addr *dest, *src;
+ __u16 destp, srcp;
+ int timer_active;
+ unsigned long timer_expires;
+ struct tcp_opt *tp = &sp->tp_pinfo.af_tcp;
+
+ dest = &sp->net_pinfo.af_inet6.daddr;
+ src = &sp->net_pinfo.af_inet6.rcv_saddr;
+ destp = ntohs(sp->dport);
+ srcp = ntohs(sp->sport);
+ if (tp->pending == TCP_TIME_RETRANS) {
+ timer_active = 1;
+ timer_expires = tp->timeout;
+ } else if (tp->pending == TCP_TIME_PROBE0) {
+ timer_active = 4;
+ timer_expires = tp->timeout;
+ } else if (timer_pending(&sp->timer)) {
+ timer_active = 2;
+ timer_expires = sp->timer.expires;
+ } else {
+ timer_active = 0;
+ timer_expires = jiffies;
+ }
+
+ sprintf(tmpbuf,
+ "%4d: %08X%08X%08X%08X:%04X %08X%08X%08X%08X:%04X "
+ "%02X %08X:%08X %02X:%08lX %08X %5d %8d %lu %d %p %u %u %u %u %d",
+ i,
+ src->s6_addr32[0], src->s6_addr32[1],
+ src->s6_addr32[2], src->s6_addr32[3], srcp,
+ dest->s6_addr32[0], dest->s6_addr32[1],
+ dest->s6_addr32[2], dest->s6_addr32[3], destp,
+ sp->state,
+ tp->write_seq-tp->snd_una, tp->rcv_nxt-tp->copied_seq,
+ timer_active, timer_expires-jiffies,
+ tp->retransmits,
+ sock_i_uid(sp),
+ tp->probes_out,
+ sock_i_ino(sp),
+ atomic_read(&sp->refcnt), sp,
+ tp->rto, tp->ack.ato, (tp->ack.quick<<1)|tp->ack.pingpong,
+ tp->snd_cwnd, tp->snd_ssthresh>=0xFFFF?-1:tp->snd_ssthresh
+ );
+}
+
+static void get_timewait6_sock(struct tcp_tw_bucket *tw, char *tmpbuf, int i)
+{
+ struct in6_addr *dest, *src;
+ __u16 destp, srcp;
+ int ttd = tw->ttd - jiffies;
+
+ if (ttd < 0)
+ ttd = 0;
+
+ dest = &tw->v6_daddr;
+ src = &tw->v6_rcv_saddr;
+ destp = ntohs(tw->dport);
+ srcp = ntohs(tw->sport);
+
+ sprintf(tmpbuf,
+ "%4d: %08X%08X%08X%08X:%04X %08X%08X%08X%08X:%04X "
+ "%02X %08X:%08X %02X:%08X %08X %5d %8d %d %d %p",
+ i,
+ src->s6_addr32[0], src->s6_addr32[1],
+ src->s6_addr32[2], src->s6_addr32[3], srcp,
+ dest->s6_addr32[0], dest->s6_addr32[1],
+ dest->s6_addr32[2], dest->s6_addr32[3], destp,
+ tw->substate, 0, 0,
+ 3, ttd, 0, 0, 0, 0,
+ atomic_read(&tw->refcnt), tw);
+}
+
+#define LINE_LEN 190
+#define LINE_FMT "%-190s\n"
+
+int tcp6_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int len = 0, num = 0, i;
+ off_t begin, pos = 0;
+ char tmpbuf[LINE_LEN+2];
+
+ if (offset < LINE_LEN+1)
+ len += sprintf(buffer, LINE_FMT,
+ " sl " /* 6 */
+ "local_address " /* 38 */
+ "remote_address " /* 38 */
+ "st tx_queue rx_queue tr tm->when retrnsmt" /* 41 */
+ " uid timeout inode"); /* 21 */
+ /*----*/
+ /*144 */
+
+ pos = LINE_LEN+1;
+
+ /* First, walk listening socket table. */
+ tcp_listen_lock();
+ for(i = 0; i < TCP_LHTABLE_SIZE; i++) {
+ struct sock *sk = tcp_listening_hash[i];
+ struct tcp_listen_opt *lopt;
+ int k;
+
+ for (sk = tcp_listening_hash[i]; sk; sk = sk->next, num++) {
+ struct open_request *req;
+ int uid;
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ if (sk->family != PF_INET6)
+ continue;
+ pos += LINE_LEN+1;
+ if (pos >= offset) {
+ get_tcp6_sock(sk, tmpbuf, num);
+ len += sprintf(buffer+len, LINE_FMT, tmpbuf);
+ if (pos >= offset + length) {
+ tcp_listen_unlock();
+ goto out_no_bh;
+ }
+ }
+
+ uid = sock_i_uid(sk);
+ read_lock_bh(&tp->syn_wait_lock);
+ lopt = tp->listen_opt;
+ if (lopt && lopt->qlen != 0) {
+ for (k=0; k<TCP_SYNQ_HSIZE; k++) {
+ for (req = lopt->syn_table[k]; req; req = req->dl_next, num++) {
+ if (req->class->family != PF_INET6)
+ continue;
+ pos += LINE_LEN+1;
+ if (pos <= offset)
+ continue;
+ get_openreq6(sk, req, tmpbuf, num, uid);
+ len += sprintf(buffer+len, LINE_FMT, tmpbuf);
+ if (pos >= offset + length) {
+ read_unlock_bh(&tp->syn_wait_lock);
+ tcp_listen_unlock();
+ goto out_no_bh;
+ }
+ }
+ }
+ }
+ read_unlock_bh(&tp->syn_wait_lock);
+
+ /* Completed requests are in normal socket hash table */
+ }
+ }
+ tcp_listen_unlock();
+
+ local_bh_disable();
+
+ /* Next, walk established hash chain. */
+ for (i = 0; i < tcp_ehash_size; i++) {
+ struct tcp_ehash_bucket *head = &tcp_ehash[i];
+ struct sock *sk;
+ struct tcp_tw_bucket *tw;
+
+ read_lock(&head->lock);
+ for(sk = head->chain; sk; sk = sk->next, num++) {
+ if (sk->family != PF_INET6)
+ continue;
+ pos += LINE_LEN+1;
+ if (pos <= offset)
+ continue;
+ get_tcp6_sock(sk, tmpbuf, num);
+ len += sprintf(buffer+len, LINE_FMT, tmpbuf);
+ if (pos >= offset + length) {
+ read_unlock(&head->lock);
+ goto out;
+ }
+ }
+ for (tw = (struct tcp_tw_bucket *)tcp_ehash[i+tcp_ehash_size].chain;
+ tw != NULL;
+ tw = (struct tcp_tw_bucket *)tw->next, num++) {
+ if (tw->family != PF_INET6)
+ continue;
+ pos += LINE_LEN+1;
+ if (pos <= offset)
+ continue;
+ get_timewait6_sock(tw, tmpbuf, num);
+ len += sprintf(buffer+len, LINE_FMT, tmpbuf);
+ if (pos >= offset + length) {
+ read_unlock(&head->lock);
+ goto out;
+ }
+ }
+ read_unlock(&head->lock);
+ }
+
+out:
+ local_bh_enable();
+out_no_bh:
+
+ begin = len - (pos - offset);
+ *start = buffer + begin;
+ len -= begin;
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+struct proto tcpv6_prot = {
+ name: "TCPv6",
+ close: tcp_close,
+ connect: tcp_v6_connect,
+ disconnect: tcp_disconnect,
+ accept: tcp_accept,
+ ioctl: tcp_ioctl,
+ init: tcp_v6_init_sock,
+ destroy: tcp_v6_destroy_sock,
+ shutdown: tcp_shutdown,
+ setsockopt: tcp_setsockopt,
+ getsockopt: tcp_getsockopt,
+ sendmsg: tcp_sendmsg,
+ recvmsg: tcp_recvmsg,
+ backlog_rcv: tcp_v6_do_rcv,
+ hash: tcp_v6_hash,
+ unhash: tcp_unhash,
+ get_port: tcp_v6_get_port,
+};
+
+static struct inet6_protocol tcpv6_protocol =
+{
+ tcp_v6_rcv, /* TCP handler */
+ tcp_v6_err, /* TCP error control */
+ NULL, /* next */
+ IPPROTO_TCP, /* protocol ID */
+ 0, /* copy */
+ NULL, /* data */
+ "TCPv6" /* name */
+};
+
+extern struct proto_ops inet6_stream_ops;
+
+static struct inet_protosw tcpv6_protosw = {
+ type: SOCK_STREAM,
+ protocol: IPPROTO_TCP,
+ prot: &tcpv6_prot,
+ ops: &inet6_stream_ops,
+ capability: -1,
+ no_check: 0,
+ flags: INET_PROTOSW_PERMANENT,
+};
+
+void __init tcpv6_init(void)
+{
+ /* register inet6 protocol */
+ inet6_add_protocol(&tcpv6_protocol);
+ inet6_register_protosw(&tcpv6_protosw);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv6/udp.c b/uClinux-2.4.31-uc0/net/ipv6/udp.c
new file mode 100644
index 0000000..7b3cf27
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/udp.c
@@ -0,0 +1,1191 @@
+/* $USAGI: udp.c,v 1.63 2003/11/12 05:12:02 yoshfuji Exp $ */
+
+/*
+ * UDP over IPv6
+ * Linux INET6 implementation
+ *
+ * Authors:
+ * Pedro Roque <pedro_m@yahoo.com>
+ *
+ * Based on linux/ipv4/udp.c
+ *
+ * $Id: udp.c,v 1.64.2.1 2002/03/05 12:47:34 davem Exp $
+ *
+ * Fixes:
+ * Hideaki YOSHIFUJI : sin6_scope_id support
+ * yoshfuji@USAGI : Reworked bind(2) behavior, including:
+ * - Allow ipv6 and ipv4 bind(2) to the
+ * same port.
+ * - Don't allow narrow binding unless
+ * later uid is the same as before:
+ * CONFIG_NET_RESTRICTED_REUSE
+ * - Don't allow binding to the same
+ * address unless it is one of multi-
+ * cast address even if SO_REUSEADDR
+ * is set.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/sched.h>
+#include <linux/net.h>
+#include <linux/in6.h>
+#ifdef CONFIG_IPV6_DEBUG
+#include <linux/inet.h>
+#endif
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/ipv6.h>
+#include <linux/icmpv6.h>
+#include <linux/init.h>
+#include <asm/uaccess.h>
+
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/ndisc.h>
+#include <net/protocol.h>
+#include <net/transp_v6.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+#include <net/ip.h>
+#include <net/udp.h>
+#include <net/inet_common.h>
+
+#include <net/checksum.h>
+
+struct udp_mib udp_stats_in6[NR_CPUS*2];
+
+/* Grrr, addr_type already calculated by caller, but I don't want
+ * to add some silly "cookie" argument to this method just for that.
+ */
+static int udp_v6_get_port(struct sock *sk, unsigned short snum)
+{
+ write_lock_bh(&udp_hash_lock);
+ if (snum == 0) {
+ int best_size_so_far, best, result, i;
+
+ if (udp_port_rover > sysctl_local_port_range[1] ||
+ udp_port_rover < sysctl_local_port_range[0])
+ udp_port_rover = sysctl_local_port_range[0];
+ best_size_so_far = 32767;
+ best = result = udp_port_rover;
+ for (i = 0; i < UDP_HTABLE_SIZE; i++, result++) {
+ struct sock *sk;
+ int size;
+
+ sk = udp_hash[result & (UDP_HTABLE_SIZE - 1)];
+ if (!sk) {
+ if (result > sysctl_local_port_range[1])
+ result = sysctl_local_port_range[0] +
+ ((result - sysctl_local_port_range[0]) &
+ (UDP_HTABLE_SIZE - 1));
+ goto gotit;
+ }
+ size = 0;
+ do {
+ if (++size >= best_size_so_far)
+ goto next;
+ } while ((sk = sk->next) != NULL);
+ best_size_so_far = size;
+ best = result;
+ next:;
+ }
+ result = best;
+ for(;; result += UDP_HTABLE_SIZE) {
+ if (result > sysctl_local_port_range[1])
+ result = sysctl_local_port_range[0]
+ + ((result - sysctl_local_port_range[0]) &
+ (UDP_HTABLE_SIZE - 1));
+ if (!udp_lport_inuse(result))
+ break;
+ }
+gotit:
+ udp_port_rover = snum = result;
+ } else {
+ struct sock *sk2;
+ int sk_reuse, sk2_reuse;
+ int addr_type = ipv6_addr_type(&sk->net_pinfo.af_inet6.rcv_saddr),
+ addr_type2;
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ uid_t sk_uid = sock_i_uid_t(sk),
+ sk2_uid;
+#endif
+
+ sk_reuse = 0;
+ if (sk->reuse)
+ sk_reuse |= 1;
+#ifdef SO_REUSEPORT
+ if (sk->reuseport)
+ sk_reuse |= 2;
+#endif
+ if (sk_reuse &&
+ (addr_type != IPV6_ADDR_MAPPED ? (addr_type & IPV6_ADDR_MULTICAST) : MULTICAST(sk->rcv_saddr)))
+ sk_reuse |= 4;
+
+ for (sk2 = udp_hash[snum & (UDP_HTABLE_SIZE - 1)];
+ sk2 != NULL;
+ sk2 = sk2->next) {
+#if 1 /* XXX: should be recoded like 2.4.21 */
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ int uid_ok;
+#endif
+ int both_specified = 0;
+
+ if (sk2->num != snum ||
+ sk2 == sk ||
+ (sk2->bound_dev_if && sk->bound_dev_if &&
+ sk2->bound_dev_if != sk->bound_dev_if))
+ continue;
+#if 0
+ if (sk2->family != AF_INET6 && sk2->family != AF_INET)
+ continue;
+#endif
+
+ addr_type2 = sk2->family == AF_INET6 ? ipv6_addr_type(&sk2->net_pinfo.af_inet6.rcv_saddr) : IPV6_ADDR_MAPPED;
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ sk2_uid = sock_i_uid_t(sk2);
+#endif
+
+ if ((addr_type2 != IPV6_ADDR_MAPPED ? addr_type2 != IPV6_ADDR_ANY : sk2->rcv_saddr) &&
+ (addr_type != IPV6_ADDR_MAPPED ? addr_type != IPV6_ADDR_ANY : sk->rcv_saddr)) {
+ if (addr_type2 == IPV6_ADDR_MAPPED || addr_type == IPV6_ADDR_MAPPED) {
+ if (addr_type2 != addr_type ||
+ sk2->rcv_saddr != sk->rcv_saddr)
+ continue;
+ } else {
+ if (ipv6_addr_cmp(&sk2->net_pinfo.af_inet6.rcv_saddr,
+ &sk->net_pinfo.af_inet6.rcv_saddr))
+ continue;
+ }
+ both_specified = 1;
+ }
+
+#if defined(CONFIG_NET_RESTRICTED_REUSE) || defined(CONFIG_IPV6_RESTRICTED_DOUBLE_BIND)
+ uid_ok = sk2_uid == (uid_t) -1 || sk_uid == sk2_uid;
+#endif
+
+ if ((addr_type2 == IPV6_ADDR_MAPPED &&
+ addr_type != IPV6_ADDR_MAPPED && sk->net_pinfo.af_inet6.ipv6only) ||
+ (addr_type == IPV6_ADDR_MAPPED &&
+ addr_type2 != IPV6_ADDR_MAPPED && sk2->net_pinfo.af_inet6.ipv6only)) {
+#ifdef CONFIG_IPV6_RESTRICTED_DOUBLE_BIND
+ if (sysctl_ipv6_bindv6only_restriction == 0 || uid_ok)
+ continue;
+#else
+ continue;
+#endif
+ }
+
+ sk2_reuse = 0;
+ if (sk2->reuse)
+ sk2_reuse |= 1;
+#ifdef SO_REUSEPORT
+ if (sk2->reuseport)
+ sk2_reuse |= 2;
+#endif
+ if (sk2_reuse &&
+ (addr_type2 != IPV6_ADDR_MAPPED ? (addr_type2 & IPV6_ADDR_MULTICAST) : MULTICAST(sk2->rcv_saddr)))
+ sk2_reuse |= 4;
+
+ if (sk2_reuse & sk_reuse & 3) { /* NOT && */
+ if (sk2_reuse & sk_reuse & 4)
+ continue;
+#ifdef CONFIG_NET_RESTRICTED_REUSE
+ if (!uid_ok)
+ goto fail;
+#endif
+#ifdef SO_REUSEPORT
+ if (sk2_reuse & sk_reuse & 2)
+ continue;
+#endif
+ if (both_specified) {
+ int addr_type2d = sk2->family == AF_INET6 ? ipv6_addr_type(&sk2->net_pinfo.af_inet6.daddr) : IPV6_ADDR_MAPPED;
+ if (addr_type2d != IPV6_ADDR_MAPPED ? addr_type2d != IPV6_ADDR_ANY : sk2->daddr)
+ continue;
+ } else {
+ if ((addr_type2 != IPV6_ADDR_MAPPED ? addr_type2 != IPV6_ADDR_ANY : sk2->rcv_saddr) ||
+ (addr_type != IPV6_ADDR_MAPPED ? addr_type != IPV6_ADDR_ANY : sk->rcv_saddr))
+ continue;
+ }
+ }
+ goto fail;
+#else /* XXX: should be recoded like 2.4.21 */
+ if (sk2->num == snum &&
+ sk2 != sk &&
+ (!sk2->bound_dev_if ||
+ !sk->bound_dev_if ||
+ sk2->bound_dev_if == sk->bound_dev_if) &&
+ ((!sk2->rcv_saddr && !ipv6_only_sock(sk)) ||
+ (sk2->family == AF_INET6 &&
+ ipv6_addr_any(&sk2->net_pinfo.af_inet6.rcv_saddr) &&
+ !(ipv6_only_sock(sk2) && addr_type == IPV6_ADDR_MAPPED)) ||
+ (addr_type == IPV6_ADDR_ANY &&
+ (!ipv6_only_sock(sk) ||
+ !(sk2->family == AF_INET6 ? (ipv6_addr_type(&sk2->net_pinfo.af_inet6.rcv_saddr) == IPV6_ADDR_MAPPED) : 1))) ||
+ (sk2->family == AF_INET6 &&
+ !ipv6_addr_cmp(&sk->net_pinfo.af_inet6.rcv_saddr,
+ &sk2->net_pinfo.af_inet6.rcv_saddr)) ||
+ (addr_type == IPV6_ADDR_MAPPED &&
+ !ipv6_only_sock(sk2) &&
+ (!sk2->rcv_saddr ||
+ !sk->rcv_saddr ||
+ sk->rcv_saddr == sk2->rcv_saddr))) &&
+ (!sk2->reuse || !sk->reuse))
+ goto fail;
+#endif /* XXX: should be recoded like 2.4.21 */
+ }
+ }
+
+ sk->num = snum;
+ if (sk->pprev == NULL) {
+ struct sock **skp = &udp_hash[snum & (UDP_HTABLE_SIZE - 1)];
+ if ((sk->next = *skp) != NULL)
+ (*skp)->pprev = &sk->next;
+ *skp = sk;
+ sk->pprev = skp;
+ sock_prot_inc_use(sk->prot);
+ sock_hold(sk);
+ }
+ write_unlock_bh(&udp_hash_lock);
+ return 0;
+
+fail:
+ write_unlock_bh(&udp_hash_lock);
+ return 1;
+}
+
+static void udp_v6_hash(struct sock *sk)
+{
+ BUG();
+}
+
+static void udp_v6_unhash(struct sock *sk)
+{
+ write_lock_bh(&udp_hash_lock);
+ if (sk->pprev) {
+ if (sk->next)
+ sk->next->pprev = sk->pprev;
+ *sk->pprev = sk->next;
+ sk->pprev = NULL;
+ sk->num = 0;
+ sock_prot_dec_use(sk->prot);
+ __sock_put(sk);
+ }
+ write_unlock_bh(&udp_hash_lock);
+}
+
+static struct sock *udp_v6_lookup(struct in6_addr *saddr, u16 sport,
+ struct in6_addr *daddr, u16 dport, int dif)
+{
+ struct sock *sk, *result = NULL;
+ unsigned short hnum = ntohs(dport);
+ int badness = -1;
+
+ read_lock(&udp_hash_lock);
+ for(sk = udp_hash[hnum & (UDP_HTABLE_SIZE - 1)]; sk != NULL; sk = sk->next) {
+ if((sk->num == hnum) &&
+ (sk->family == PF_INET6)) {
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ int score = 0;
+ if(sk->dport) {
+ if(sk->dport != sport)
+ continue;
+ score++;
+ }
+ if(!ipv6_addr_any(&np->rcv_saddr)) {
+ if(ipv6_addr_cmp(&np->rcv_saddr, daddr))
+ continue;
+ score++;
+ }
+ if(!ipv6_addr_any(&np->daddr)) {
+ if(ipv6_addr_cmp(&np->daddr, saddr))
+ continue;
+ score++;
+ }
+ if(sk->bound_dev_if) {
+ if(sk->bound_dev_if != dif)
+ continue;
+ score++;
+ }
+ if(score == 4) {
+ result = sk;
+ break;
+ } else if(score > badness) {
+ result = sk;
+ badness = score;
+ }
+ }
+ }
+ if (result)
+ sock_hold(result);
+ read_unlock(&udp_hash_lock);
+ return result;
+}
+
+/*
+ *
+ */
+
+int udpv6_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
+{
+ struct sockaddr_in6 *usin = (struct sockaddr_in6 *) uaddr;
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct in6_addr *daddr;
+ struct in6_addr saddr;
+ struct dst_entry *dst;
+ struct flowi fl;
+ struct ip6_flowlabel *flowlabel = NULL;
+ int addr_type;
+ int err;
+ int reroute = 0;
+
+ if (usin->sin6_family == AF_INET) {
+ if (__ipv6_only_sock(sk))
+ return -EAFNOSUPPORT;
+ err = udp_connect(sk, uaddr, addr_len);
+ goto ipv4_connected;
+ }
+
+ if (addr_len < SIN6_LEN_RFC2133)
+ return -EINVAL;
+
+ if (usin->sin6_family != AF_INET6)
+ return -EAFNOSUPPORT;
+
+ fl.fl6_flowlabel = 0;
+ if (np->sndflow) {
+ fl.fl6_flowlabel = usin->sin6_flowinfo&IPV6_FLOWINFO_MASK;
+ if (fl.fl6_flowlabel&IPV6_FLOWLABEL_MASK) {
+ flowlabel = fl6_sock_lookup(sk, fl.fl6_flowlabel);
+ if (flowlabel == NULL)
+ return -EINVAL;
+ ipv6_addr_copy(&usin->sin6_addr, &flowlabel->dst);
+ }
+ }
+
+ addr_type = ipv6_addr_type(&usin->sin6_addr);
+
+ if (addr_type == IPV6_ADDR_ANY) {
+ /*
+ * connect to self
+ */
+ usin->sin6_addr.s6_addr[15] = 0x01;
+ }
+
+ daddr = &usin->sin6_addr;
+
+ if (addr_type == IPV6_ADDR_MAPPED) {
+ struct sockaddr_in sin;
+
+ if (__ipv6_only_sock(sk))
+ return -ENETUNREACH;
+
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = daddr->s6_addr32[3];
+ sin.sin_port = usin->sin6_port;
+
+ err = udp_connect(sk, (struct sockaddr*) &sin, sizeof(sin));
+
+ipv4_connected:
+ if (err < 0)
+ return err;
+
+ ipv6_addr_set(&np->daddr, 0, 0,
+ htonl(0x0000ffff),
+ sk->daddr);
+
+ if(ipv6_addr_any(&np->saddr)) {
+ ipv6_addr_set(&np->saddr, 0, 0,
+ htonl(0x0000ffff),
+ sk->saddr);
+ }
+
+ if(ipv6_addr_any(&np->rcv_saddr)) {
+ ipv6_addr_set(&np->rcv_saddr, 0, 0,
+ htonl(0x0000ffff),
+ sk->rcv_saddr);
+ }
+ return 0;
+ }
+
+ if (addr_type&IPV6_ADDR_LINKLOCAL) {
+ if (addr_len >= sizeof(struct sockaddr_in6) &&
+ usin->sin6_scope_id) {
+ if (sk->bound_dev_if && sk->bound_dev_if != usin->sin6_scope_id) {
+ fl6_sock_release(flowlabel);
+ return -EINVAL;
+ }
+ sk->bound_dev_if = usin->sin6_scope_id;
+ if (!sk->bound_dev_if && (addr_type&IPV6_ADDR_MULTICAST))
+ fl.oif = np->mcast_oif;
+ }
+
+#ifndef CONFIG_IPV6_LOOSE_SCOPE_ID
+ /* Connect to link-local address requires an interface */
+ if (sk->bound_dev_if == 0){
+ fl6_sock_release(flowlabel);
+ return -EINVAL;
+ }
+#endif
+ }
+
+ ipv6_addr_copy(&np->daddr, daddr);
+ np->flow_label = fl.fl6_flowlabel;
+
+ sk->dport = usin->sin6_port;
+
+ /*
+ * Check for a route to destination an obtain the
+ * destination cache for it.
+ */
+
+ fl.proto = IPPROTO_UDP;
+ fl.fl6_dst = &np->daddr;
+ fl.fl6_src = &saddr;
+ fl.oif = sk->bound_dev_if;
+ fl.uli_u.ports.dport = sk->dport;
+ fl.uli_u.ports.sport = sk->sport;
+
+ if (!fl.oif && (addr_type&IPV6_ADDR_MULTICAST))
+ fl.oif = np->mcast_oif;
+
+ if (flowlabel) {
+ if (flowlabel->opt && flowlabel->opt->srcrt) {
+ struct rt0_hdr *rt0 = (struct rt0_hdr *) flowlabel->opt->srcrt;
+ fl.fl6_dst = rt0->addr;
+ }
+ } else if (np->opt && np->opt->srcrt) {
+ struct rt0_hdr *rt0 = (struct rt0_hdr *) np->opt->srcrt;
+ fl.fl6_dst = rt0->addr;
+ }
+
+ dst = ip6_route_output(sk, &fl);
+
+ if ((err = dst->error) != 0) {
+ dst_release(dst);
+ fl6_sock_release(flowlabel);
+ return err;
+ }
+
+ /* get the source adddress used in the apropriate device */
+
+ err = ipv6_get_saddr(dst, daddr, &saddr, np->use_tempaddr);
+
+ if (reroute)
+ dst_release(dst);
+
+ if (err == 0) {
+#ifdef CONFIG_IPV6_SUBTREES
+ if (reroute) {
+ dst = ip6_route_output(sk, &fl);
+ if ((err = dst->error) != 0) {
+ dst_release(dst);
+ fl6_sock_release(flowlabel);
+ return err;
+ }
+ }
+#endif
+ if(ipv6_addr_any(&np->saddr))
+ ipv6_addr_copy(&np->saddr, &saddr);
+
+ if(ipv6_addr_any(&np->rcv_saddr)) {
+ ipv6_addr_copy(&np->rcv_saddr, &saddr);
+ sk->rcv_saddr = LOOPBACK4_IPV6;
+ }
+ ip6_dst_store(sk, dst, &np->daddr,
+ !ipv6_addr_cmp(fl.fl6_src, &np->saddr) ?
+ &np->saddr : NULL);
+ sk->state = TCP_ESTABLISHED;
+ }
+ fl6_sock_release(flowlabel);
+
+ return err;
+}
+
+static void udpv6_close(struct sock *sk, long timeout)
+{
+ inet_sock_release(sk);
+}
+
+/*
+ * This should be easy, if there is something there we
+ * return it, otherwise we block.
+ */
+
+int udpv6_recvmsg(struct sock *sk, struct msghdr *msg, int len,
+ int noblock, int flags, int *addr_len)
+{
+ struct sk_buff *skb;
+ int copied, err;
+
+ if (addr_len)
+ *addr_len=sizeof(struct sockaddr_in6);
+
+ if (flags & MSG_ERRQUEUE)
+ return ipv6_recv_error(sk, msg, len);
+
+try_again:
+ skb = skb_recv_datagram(sk, flags, noblock, &err);
+ if (!skb)
+ goto out;
+
+ copied = skb->len - sizeof(struct udphdr);
+ if (copied > len) {
+ copied = len;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+
+ if (skb->ip_summed==CHECKSUM_UNNECESSARY) {
+ err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov,
+ copied);
+ } else if (msg->msg_flags&MSG_TRUNC) {
+ if ((unsigned short)csum_fold(skb_checksum(skb, 0, skb->len, skb->csum)))
+ goto csum_copy_err;
+ err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov,
+ copied);
+ } else {
+ err = skb_copy_and_csum_datagram_iovec(skb, sizeof(struct udphdr), msg->msg_iov);
+ if (err == -EINVAL)
+ goto csum_copy_err;
+ }
+ if (err)
+ goto out_free;
+
+ sock_recv_timestamp(msg, sk, skb);
+
+ /* Copy the address. */
+ if (msg->msg_name) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *) msg->msg_name;
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = skb->h.uh->source;
+ sin6->sin6_flowinfo = 0;
+ sin6->sin6_scope_id = 0;
+
+ if (skb->protocol == htons(ETH_P_IP)) {
+ ipv6_addr_set(&sin6->sin6_addr, 0, 0,
+ htonl(0xffff), skb->nh.iph->saddr);
+ if (sk->protinfo.af_inet.cmsg_flags)
+ ip_cmsg_recv(msg, skb);
+ } else {
+ ipv6_addr_copy(&sin6->sin6_addr, &skb->nh.ipv6h->saddr);
+
+ if (sk->net_pinfo.af_inet6.rxopt.all)
+ datagram_recv_ctl(sk, msg, skb);
+ if (ipv6_addr_type(&sin6->sin6_addr) & IPV6_ADDR_LINKLOCAL) {
+ struct inet6_skb_parm *opt = (struct inet6_skb_parm *) skb->cb;
+ sin6->sin6_scope_id = opt->iif;
+ }
+ }
+ }
+
+ err = copied;
+ if (flags & MSG_TRUNC)
+ err = skb->len - sizeof(struct udphdr);
+
+out_free:
+ skb_free_datagram(sk, skb);
+out:
+ return err;
+
+csum_copy_err:
+ /* Clear queue. */
+ if (flags&MSG_PEEK) {
+ int clear = 0;
+ spin_lock_irq(&sk->receive_queue.lock);
+ if (skb == skb_peek(&sk->receive_queue)) {
+ __skb_unlink(skb, &sk->receive_queue);
+ clear = 1;
+ }
+ spin_unlock_irq(&sk->receive_queue.lock);
+ if (clear)
+ kfree_skb(skb);
+ }
+
+ skb_free_datagram(sk, skb);
+
+ if (flags & MSG_DONTWAIT) {
+ UDP6_INC_STATS_USER(UdpInErrors);
+ return -EAGAIN;
+ }
+ goto try_again;
+}
+
+void udpv6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
+ int type, int code, int offset, __u32 info)
+{
+ struct ipv6hdr *hdr = (struct ipv6hdr*)skb->data;
+ struct net_device *dev = skb->dev;
+ struct in6_addr *saddr = &hdr->saddr;
+ struct in6_addr *daddr = &hdr->daddr;
+ struct udphdr *uh = (struct udphdr*)(skb->data+offset);
+ struct sock *sk;
+ int err;
+
+ sk = udp_v6_lookup(daddr, uh->dest, saddr, uh->source, dev->ifindex);
+
+ if (sk == NULL)
+ return;
+
+ if (!icmpv6_err_convert(type, code, &err) &&
+ !sk->net_pinfo.af_inet6.recverr)
+ goto out;
+
+ if (sk->state!=TCP_ESTABLISHED &&
+ !sk->net_pinfo.af_inet6.recverr)
+ goto out;
+
+ if (sk->net_pinfo.af_inet6.recverr)
+ ipv6_icmp_error(sk, skb, err, uh->dest, ntohl(info), (u8 *)(uh+1));
+
+ sk->err = err;
+ sk->error_report(sk);
+out:
+ sock_put(sk);
+}
+
+static inline int udpv6_queue_rcv_skb(struct sock * sk, struct sk_buff *skb)
+{
+ struct inet6_dev *idev = in6_dev_get(skb->dev);
+ if (skb->ip_summed != CHECKSUM_UNNECESSARY) {
+ if ((unsigned short)csum_fold(skb_checksum(skb, 0, skb->len, skb->csum))) {
+ UDP6_INC_STATS_BH(UdpInErrors);
+ IP6_INC_STATS_BH(idev,Ip6InDiscards);
+ if (idev)
+ in6_dev_put(idev);
+ kfree_skb(skb);
+ return 0;
+ }
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ }
+ if (sock_queue_rcv_skb(sk,skb)<0) {
+ UDP6_INC_STATS_BH(UdpInErrors);
+ IP6_INC_STATS_BH(idev,Ip6InDiscards);
+ if (idev)
+ in6_dev_put(idev);
+ kfree_skb(skb);
+ return 0;
+ }
+ IP6_INC_STATS_BH(idev,Ip6InDelivers);
+ UDP6_INC_STATS_BH(UdpInDatagrams);
+
+ if (idev)
+ in6_dev_put(idev);
+ return 0;
+}
+
+static struct sock *udp_v6_mcast_next(struct sock *sk,
+ u16 loc_port, struct in6_addr *loc_addr,
+ u16 rmt_port, struct in6_addr *rmt_addr,
+ int dif)
+{
+ struct sock *s = sk;
+ unsigned short num = ntohs(loc_port);
+ for(; s; s = s->next) {
+ if(s->num == num) {
+ struct ipv6_pinfo *np = &s->net_pinfo.af_inet6;
+ if(s->dport) {
+ if(s->dport != rmt_port)
+ continue;
+ }
+ if(!ipv6_addr_any(&np->daddr) &&
+ ipv6_addr_cmp(&np->daddr, rmt_addr))
+ continue;
+
+ if (s->bound_dev_if && s->bound_dev_if != dif)
+ continue;
+
+ if(!ipv6_addr_any(&np->rcv_saddr)) {
+ if(ipv6_addr_cmp(&np->rcv_saddr, loc_addr) == 0)
+ return s;
+ continue;
+ }
+ if(!inet6_mc_check(s, loc_addr, rmt_addr))
+ continue;
+ return s;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Note: called only from the BH handler context,
+ * so we don't need to lock the hashes.
+ */
+static void udpv6_mcast_deliver(struct udphdr *uh,
+ struct in6_addr *saddr, struct in6_addr *daddr,
+ struct sk_buff *skb)
+{
+ struct sock *sk, *sk2;
+ int dif;
+
+ read_lock(&udp_hash_lock);
+ sk = udp_hash[ntohs(uh->dest) & (UDP_HTABLE_SIZE - 1)];
+ dif = skb->dev->ifindex;
+ sk = udp_v6_mcast_next(sk, uh->dest, daddr, uh->source, saddr, dif);
+ if (!sk) {
+ kfree_skb(skb);
+ goto out;
+ }
+
+ sk2 = sk;
+ while((sk2 = udp_v6_mcast_next(sk2->next, uh->dest, daddr,
+ uh->source, saddr, dif))) {
+ struct sk_buff *buff = skb_clone(skb, GFP_ATOMIC);
+ if (buff)
+ udpv6_queue_rcv_skb(sk2, buff);
+ }
+ udpv6_queue_rcv_skb(sk, skb);
+out:
+ read_unlock(&udp_hash_lock);
+}
+
+int udpv6_rcv(struct sk_buff *skb)
+{
+ struct sock *sk;
+ struct udphdr *uh;
+ struct net_device *dev = skb->dev;
+ struct in6_addr *saddr = &skb->nh.ipv6h->saddr;
+ struct in6_addr *daddr = &skb->nh.ipv6h->daddr;
+ u32 ulen = 0;
+
+ if (!pskb_may_pull(skb, sizeof(struct udphdr)))
+ goto short_packet;
+
+ uh = skb->h.uh;
+ ulen = ntohs(uh->len);
+
+ /* Check for jumbo payload */
+ if (ulen == 0)
+ ulen = skb->len;
+
+ if (ulen > skb->len || ulen < sizeof(*uh))
+ goto short_packet;
+
+ if (uh->check == 0) {
+ /* IPv6 draft-v2 section 8.1 says that we SHOULD log
+ this error. Well, it is reasonable.
+ */
+ if (net_ratelimit())
+ printk(KERN_INFO "IPv6: udp checksum is 0\n");
+ goto discard;
+ }
+
+ if (ulen < skb->len) {
+ if (__pskb_trim(skb, ulen))
+ goto discard;
+ saddr = &skb->nh.ipv6h->saddr;
+ daddr = &skb->nh.ipv6h->daddr;
+ uh = skb->h.uh;
+ }
+
+ if (skb->ip_summed==CHECKSUM_HW) {
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ if (csum_ipv6_magic(saddr, daddr, ulen, IPPROTO_UDP, skb->csum)) {
+ NETDEBUG(if (net_ratelimit()) printk(KERN_DEBUG "udp v6 hw csum failure.\n"));
+ skb->ip_summed = CHECKSUM_NONE;
+ }
+ }
+ if (skb->ip_summed != CHECKSUM_UNNECESSARY)
+ skb->csum = ~csum_ipv6_magic(saddr, daddr, ulen, IPPROTO_UDP, 0);
+
+ /*
+ * Multicast receive code
+ */
+ if (ipv6_addr_type(daddr) & IPV6_ADDR_MULTICAST) {
+ udpv6_mcast_deliver(uh, saddr, daddr, skb);
+ return 0;
+ }
+
+ /* Unicast */
+
+ /*
+ * check socket cache ... must talk to Alan about his plans
+ * for sock caches... i'll skip this for now.
+ */
+ sk = udp_v6_lookup(saddr, uh->source, daddr, uh->dest, dev->ifindex);
+
+ if (sk == NULL) {
+ if (skb->ip_summed != CHECKSUM_UNNECESSARY &&
+ (unsigned short)csum_fold(skb_checksum(skb, 0, skb->len, skb->csum)))
+ goto discard;
+ UDP6_INC_STATS_BH(UdpNoPorts);
+
+ icmpv6_send(skb, ICMPV6_DEST_UNREACH, ICMPV6_PORT_UNREACH, 0, dev);
+
+ kfree_skb(skb);
+ return(0);
+ }
+
+ /* deliver */
+
+ udpv6_queue_rcv_skb(sk, skb);
+ sock_put(sk);
+ return(0);
+
+short_packet:
+ if (net_ratelimit()) {
+#ifdef CONFIG_IPV6_DEBUG
+ char sbuf[128], dbuf[128];
+ in6_ntop(saddr, sbuf);
+ in6_ntop(daddr, dbuf);
+ printk(KERN_DEBUG "UDPv6: short packet: %u/%u (%s->%s)\n", ulen, skb->len, sbuf, dbuf);
+#else
+ printk(KERN_DEBUG "UDPv6: short packet: %u/%u\n", ulen, skb->len);
+#endif
+ }
+
+discard:
+ UDP6_INC_STATS_BH(UdpInErrors);
+ kfree_skb(skb);
+ return(0);
+}
+
+/*
+ * Sending
+ */
+
+struct udpv6fakehdr
+{
+ struct udphdr uh;
+ struct iovec *iov;
+ __u32 wcheck;
+ __u32 pl_len;
+ struct in6_addr *daddr;
+};
+
+/*
+ * with checksum
+ */
+
+static int udpv6_getfrag(const void *data, struct in6_addr *addr,
+ char *buff, unsigned int offset, unsigned int len)
+{
+ struct udpv6fakehdr *udh = (struct udpv6fakehdr *) data;
+ char *dst;
+ int final = 0;
+ int clen = len;
+
+ dst = buff;
+
+ if (udh->wcheck == 0)
+ udh->uh.check = 0;
+
+ if (offset) {
+ offset -= sizeof(struct udphdr);
+ } else {
+ dst += sizeof(struct udphdr);
+ final = 1;
+ clen -= sizeof(struct udphdr);
+ }
+
+ if (csum_partial_copy_fromiovecend(dst, udh->iov, offset,
+ clen, &udh->wcheck))
+ return -EFAULT;
+
+ if (final) {
+ struct in6_addr *daddr;
+
+ udh->wcheck = csum_partial((char *)udh, sizeof(struct udphdr),
+ udh->wcheck);
+
+ if (udh->daddr) {
+ daddr = udh->daddr;
+ } else {
+ /*
+ * use packet destination address
+ * this should improve cache locality
+ */
+ daddr = addr + 1;
+ }
+ udh->uh.check = csum_ipv6_magic(addr, daddr,
+ udh->pl_len, IPPROTO_UDP,
+ udh->wcheck);
+ if (udh->uh.check == 0)
+ udh->uh.check = -1;
+
+ memcpy(buff, udh, sizeof(struct udphdr));
+ udh->wcheck = 0;
+ }
+ return 0;
+}
+
+static int udpv6_sendmsg(struct sock *sk, struct msghdr *msg, int ulen)
+{
+ struct ipv6_txoptions opt_space;
+ struct udpv6fakehdr udh;
+ struct ipv6_pinfo *np = &sk->net_pinfo.af_inet6;
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) msg->msg_name;
+ struct ipv6_txoptions *opt = NULL;
+ struct ip6_flowlabel *flowlabel = NULL;
+ struct flowi fl;
+ int addr_len = msg->msg_namelen;
+ struct in6_addr *daddr;
+ int len = ulen + sizeof(struct udphdr);
+ int addr_type;
+ int hlimit = -1;
+ int tclass = -1;
+
+ int err;
+
+ /* Rough check on arithmetic overflow,
+ better check is made in ip6_build_xmit
+ */
+ if (ulen < 0 || ulen > INT_MAX - sizeof(struct udphdr))
+ return -EMSGSIZE;
+
+ fl.fl6_flowlabel = 0;
+ fl.oif = 0;
+
+ if (sin6) {
+ if (sin6->sin6_family == AF_INET) {
+ if (__ipv6_only_sock(sk))
+ return -ENETUNREACH;
+ return udp_sendmsg(sk, msg, ulen);
+ }
+
+ if (addr_len < SIN6_LEN_RFC2133)
+ return -EINVAL;
+
+ if (sin6->sin6_family && sin6->sin6_family != AF_INET6)
+ return -EINVAL;
+
+ if (sin6->sin6_port == 0)
+ return -EINVAL;
+
+ udh.uh.dest = sin6->sin6_port;
+ daddr = &sin6->sin6_addr;
+
+ if (np->sndflow) {
+ fl.fl6_flowlabel = sin6->sin6_flowinfo&IPV6_FLOWINFO_MASK;
+ if (fl.fl6_flowlabel&IPV6_FLOWLABEL_MASK) {
+ flowlabel = fl6_sock_lookup(sk, fl.fl6_flowlabel);
+ if (flowlabel == NULL)
+ return -EINVAL;
+ daddr = &flowlabel->dst;
+ }
+ }
+
+ /* Otherwise it will be difficult to maintain sk->dst_cache. */
+ if (sk->state == TCP_ESTABLISHED &&
+ !ipv6_addr_cmp(daddr, &sk->net_pinfo.af_inet6.daddr))
+ daddr = &sk->net_pinfo.af_inet6.daddr;
+
+ if (addr_len >= sizeof(struct sockaddr_in6) &&
+ sin6->sin6_scope_id &&
+ ipv6_addr_type(daddr)&IPV6_ADDR_LINKLOCAL)
+ fl.oif = sin6->sin6_scope_id;
+ } else {
+ if (sk->state != TCP_ESTABLISHED)
+ return -EDESTADDRREQ;
+
+ udh.uh.dest = sk->dport;
+ daddr = &sk->net_pinfo.af_inet6.daddr;
+ fl.fl6_flowlabel = np->flow_label;
+ }
+
+ addr_type = ipv6_addr_type(daddr);
+
+ if (addr_type == IPV6_ADDR_MAPPED) {
+ struct sockaddr_in sin;
+
+ if (__ipv6_only_sock(sk))
+ return -ENETUNREACH;
+
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = daddr->s6_addr32[3];
+ sin.sin_port = udh.uh.dest;
+ msg->msg_name = (struct sockaddr *)(&sin);
+ msg->msg_namelen = sizeof(sin);
+ fl6_sock_release(flowlabel);
+
+ return udp_sendmsg(sk, msg, ulen);
+ }
+
+ udh.daddr = NULL;
+ if (!fl.oif)
+ fl.oif = sk->bound_dev_if;
+ fl.fl6_src = NULL;
+
+ if (msg->msg_controllen) {
+ opt = &opt_space;
+ memset(opt, 0, sizeof(struct ipv6_txoptions));
+
+ err = datagram_send_ctl(msg, &fl, opt, &hlimit, &tclass);
+ if (err < 0) {
+ fl6_sock_release(flowlabel);
+ return err;
+ }
+ if ((fl.fl6_flowlabel&IPV6_FLOWLABEL_MASK) && !flowlabel) {
+ flowlabel = fl6_sock_lookup(sk, fl.fl6_flowlabel);
+ if (flowlabel == NULL)
+ return -EINVAL;
+ }
+ if (!(opt->opt_nflen|opt->opt_flen))
+ opt = NULL;
+ }
+ if (opt == NULL)
+ opt = np->opt;
+ if (flowlabel)
+ opt = fl6_merge_options(&opt_space, flowlabel, opt);
+ if (opt && opt->srcrt)
+ udh.daddr = daddr;
+ udh.daddr = daddr;
+
+ udh.uh.source = sk->sport;
+ udh.uh.len = len < 0x10000 ? htons(len) : 0;
+ udh.uh.check = 0;
+ udh.iov = msg->msg_iov;
+ udh.wcheck = 0;
+ udh.pl_len = len;
+
+ fl.proto = IPPROTO_UDP;
+ fl.fl6_dst = daddr;
+ if (fl.fl6_src == NULL && !ipv6_addr_any(&np->saddr))
+ fl.fl6_src = &np->saddr;
+ fl.uli_u.ports.dport = udh.uh.dest;
+ fl.uli_u.ports.sport = udh.uh.source;
+
+ err = ip6_build_xmit(sk, udpv6_getfrag, &udh, &fl, len, opt, hlimit,
+ tclass,
+ msg->msg_flags);
+
+ fl6_sock_release(flowlabel);
+
+ if (err < 0)
+ return err;
+
+ UDP6_INC_STATS_USER(UdpOutDatagrams);
+ return ulen;
+}
+
+static struct inet6_protocol udpv6_protocol =
+{
+ udpv6_rcv, /* UDP handler */
+ udpv6_err, /* UDP error control */
+ NULL, /* next */
+ IPPROTO_UDP, /* protocol ID */
+ 0, /* copy */
+ NULL, /* data */
+ "UDPv6" /* name */
+};
+
+#define LINE_LEN 190
+#define LINE_FMT "%-190s\n"
+
+static void get_udp6_sock(struct sock *sp, char *tmpbuf, int i)
+{
+ struct in6_addr *dest, *src;
+ __u16 destp, srcp;
+
+ dest = &sp->net_pinfo.af_inet6.daddr;
+ src = &sp->net_pinfo.af_inet6.rcv_saddr;
+ destp = ntohs(sp->dport);
+ srcp = ntohs(sp->sport);
+ sprintf(tmpbuf,
+ "%4d: %08X%08X%08X%08X:%04X %08X%08X%08X%08X:%04X "
+ "%02X %08X:%08X %02X:%08lX %08X %5d %8d %lu %d %p",
+ i,
+ src->s6_addr32[0], src->s6_addr32[1],
+ src->s6_addr32[2], src->s6_addr32[3], srcp,
+ dest->s6_addr32[0], dest->s6_addr32[1],
+ dest->s6_addr32[2], dest->s6_addr32[3], destp,
+ sp->state,
+ atomic_read(&sp->wmem_alloc), atomic_read(&sp->rmem_alloc),
+ 0, 0L, 0,
+ sock_i_uid(sp), 0,
+ sock_i_ino(sp),
+ atomic_read(&sp->refcnt), sp);
+}
+
+int udp6_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int len = 0, num = 0, i;
+ off_t pos = 0;
+ off_t begin;
+ char tmpbuf[LINE_LEN+2];
+
+ if (offset < LINE_LEN+1)
+ len += sprintf(buffer, LINE_FMT,
+ " sl " /* 6 */
+ "local_address " /* 38 */
+ "remote_address " /* 38 */
+ "st tx_queue rx_queue tr tm->when retrnsmt" /* 41 */
+ " uid timeout inode"); /* 21 */
+ /*----*/
+ /*144 */
+ pos = LINE_LEN+1;
+ read_lock(&udp_hash_lock);
+ for (i = 0; i < UDP_HTABLE_SIZE; i++) {
+ struct sock *sk;
+
+ for (sk = udp_hash[i]; sk; sk = sk->next, num++) {
+ if (sk->family != PF_INET6)
+ continue;
+ pos += LINE_LEN+1;
+ if (pos <= offset)
+ continue;
+ get_udp6_sock(sk, tmpbuf, i);
+ len += sprintf(buffer+len, LINE_FMT, tmpbuf);
+ if(len >= length)
+ goto out;
+ }
+ }
+out:
+ read_unlock(&udp_hash_lock);
+ begin = len - (pos - offset);
+ *start = buffer + begin;
+ len -= begin;
+ if(len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+struct proto udpv6_prot = {
+ name: "UDP",
+ close: udpv6_close,
+ connect: udpv6_connect,
+ disconnect: udp_disconnect,
+ ioctl: udp_ioctl,
+ destroy: inet6_destroy_sock,
+ setsockopt: ipv6_setsockopt,
+ getsockopt: ipv6_getsockopt,
+ sendmsg: udpv6_sendmsg,
+ recvmsg: udpv6_recvmsg,
+ backlog_rcv: udpv6_queue_rcv_skb,
+ hash: udp_v6_hash,
+ unhash: udp_v6_unhash,
+ get_port: udp_v6_get_port,
+};
+
+extern struct proto_ops inet6_dgram_ops;
+
+static struct inet_protosw udpv6_protosw = {
+ type: SOCK_DGRAM,
+ protocol: IPPROTO_UDP,
+ prot: &udpv6_prot,
+ ops: &inet6_dgram_ops,
+ capability: -1,
+ no_check: UDP_CSUM_DEFAULT,
+ flags: INET_PROTOSW_PERMANENT,
+};
+
+
+void __init udpv6_init(void)
+{
+ inet6_add_protocol(&udpv6_protocol);
+ inet6_register_protosw(&udpv6_protosw);
+}
diff --git a/uClinux-2.4.31-uc0/net/ipv6/utils.c b/uClinux-2.4.31-uc0/net/ipv6/utils.c
new file mode 100644
index 0000000..b613596
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipv6/utils.c
@@ -0,0 +1,50 @@
+/* $USAGI: utils.c,v 1.2 2001/05/26 12:18:53 yoshfuji Exp $ */
+
+/*
+ * Copyright (C) 2000 USAGI/WIDE Project.
+ * 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. Neither the name of the project nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ */
+
+#include <linux/config.h>
+#include <asm/byteorder.h>
+#include <linux/kernel.h>
+#include <linux/in6.h>
+
+#ifdef CONFIG_IPV6
+char* in6_ntop(const struct in6_addr *in6, char *buf){
+ if (!buf)
+ return NULL;
+ sprintf(buf,
+ "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x",
+ ntohs(in6->s6_addr16[0]), ntohs(in6->s6_addr16[1]),
+ ntohs(in6->s6_addr16[2]), ntohs(in6->s6_addr16[3]),
+ ntohs(in6->s6_addr16[4]), ntohs(in6->s6_addr16[5]),
+ ntohs(in6->s6_addr16[6]), ntohs(in6->s6_addr16[7]));
+ return buf;
+}
+#endif
+
diff --git a/uClinux-2.4.31-uc0/net/ipx/Config.in b/uClinux-2.4.31-uc0/net/ipx/Config.in
new file mode 100644
index 0000000..0c798e4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipx/Config.in
@@ -0,0 +1,8 @@
+#
+# IPX configuration
+#
+
+bool ' IPX: Full internal IPX network' CONFIG_IPX_INTERN
+#if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
+# dep_tristate ' IPX: SPX networking (EXPERIMENTAL)' CONFIG_SPX $CONFIG_IPX
+#fi
diff --git a/uClinux-2.4.31-uc0/net/ipx/Makefile b/uClinux-2.4.31-uc0/net/ipx/Makefile
new file mode 100644
index 0000000..b9a368b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipx/Makefile
@@ -0,0 +1,25 @@
+#
+# Makefile for the Linux IPX layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+# We only get in/to here if CONFIG_IPX = 'y' or 'm'
+
+O_TARGET := ipx.o
+
+export-objs = af_ipx.o af_spx.o
+
+obj-y := af_ipx.o
+
+ifeq ($(CONFIG_IPX),m)
+ obj-m += $(O_TARGET)
+endif
+
+obj-$(CONFIG_SYSCTL) += sysctl_net_ipx.o
+obj-$(CONFIG_SPX) += af_spx.o
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/ipx/af_ipx.c b/uClinux-2.4.31-uc0/net/ipx/af_ipx.c
new file mode 100644
index 0000000..a3f6eca
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipx/af_ipx.c
@@ -0,0 +1,2641 @@
+/*
+ * Implements an IPX socket layer.
+ *
+ * This code is derived from work by
+ * Ross Biro : Writing the original IP stack
+ * Fred Van Kempen : Tidying up the TCP/IP
+ *
+ * Many thanks go to Keith Baker, Institute For Industrial Information
+ * Technology Ltd, Swansea University for allowing me to work on this
+ * in my own time even though it was in some ways related to commercial
+ * work I am currently employed to do there.
+ *
+ * All the material in this file is subject to the Gnu license version 2.
+ * Neither Alan Cox nor the Swansea University Computer Society admit
+ * liability nor provide warranty for any of this software. This material
+ * is provided as is and at no charge.
+ *
+ * Revision 0.21: Uses the new generic socket option code.
+ * Revision 0.22: Gcc clean ups and drop out device registration. Use the
+ * new multi-protocol edition of hard_header
+ * Revision 0.23: IPX /proc by Mark Evans. Adding a route will
+ * will overwrite any existing route to the same network.
+ * Revision 0.24: Supports new /proc with no 4K limit
+ * Revision 0.25: Add ephemeral sockets, passive local network
+ * identification, support for local net 0 and
+ * multiple datalinks <Greg Page>
+ * Revision 0.26: Device drop kills IPX routes via it. (needed for module)
+ * Revision 0.27: Autobind <Mark Evans>
+ * Revision 0.28: Small fix for multiple local networks <Thomas Winder>
+ * Revision 0.29: Assorted major errors removed <Mark Evans>
+ * Small correction to promisc mode error fix <Alan Cox>
+ * Asynchronous I/O support. Changed to use notifiers
+ * and the newer packet_type stuff. Assorted major
+ * fixes <Alejandro Liu>
+ * Revision 0.30: Moved to net/ipx/... <Alan Cox>
+ * Don't set address length on recvfrom that errors.
+ * Incorrect verify_area.
+ * Revision 0.31: New sk_buffs. This still needs a lot of
+ * testing. <Alan Cox>
+ * Revision 0.32: Using sock_alloc_send_skb, firewall hooks. <Alan Cox>
+ * Supports sendmsg/recvmsg
+ * Revision 0.33: Internal network support, routing changes, uses a
+ * protocol private area for ipx data.
+ * Revision 0.34: Module support. <Jim Freeman>
+ * Revision 0.35: Checksum support. <Neil Turton>, hooked in by <Alan Cox>
+ * Handles WIN95 discovery packets <Volker Lendecke>
+ * Revision 0.36: Internal bump up for 2.1
+ * Revision 0.37: Began adding POSIXisms.
+ * Revision 0.38: Asynchronous socket stuff made current.
+ * Revision 0.39: SPX interfaces
+ * Revision 0.40: Tiny SIOCGSTAMP fix (chris@cybernet.co.nz)
+ * Revision 0.41: 802.2TR removed (p.norton@computer.org)
+ * Fixed connecting to primary net,
+ * Automatic binding on send & receive,
+ * Martijn van Oosterhout <kleptogimp@geocities.com>
+ * Revision 042: Multithreading - use spinlocks and refcounting to
+ * protect some structures: ipx_interface sock list, list
+ * of ipx interfaces, etc.
+ * Bugfixes - do refcounting on net_devices, check function
+ * results, etc. Thanks to davem and freitag for
+ * suggestions and guidance.
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br>,
+ * November, 2000
+ * Revision 043: Shared SKBs, don't mangle packets, some cleanups
+ * Arnaldo Carvalho de Melo <acme@conectiva.com.br>,
+ * December, 2000
+ * Revision 044: Call ipxitf_hold on NETDEV_UP (acme)
+ * Revision 045: fix PPROP routing bug (acme)
+ * Revision 046: Further fixes to PPROP, ipxitf_create_internal was
+ * doing an unneeded MOD_INC_USE_COUNT, implement
+ * sysctl for ipx_pprop_broacasting, fix the ipx sysctl
+ * handling, making it dynamic, some cleanups, thanks to
+ * Petr Vandrovec for review and good suggestions. (acme)
+ * Revision 047: Cleanups, CodingStyle changes, move the ncp connection
+ * hack out of line (acme)
+ *
+ * Protect the module by a MOD_INC_USE_COUNT/MOD_DEC_USE_COUNT
+ * pair. Also, now usage count is managed this way
+ * -Count one if the auto_interface mode is on
+ * -Count one per configured interface
+ *
+ * Jacques Gelinas (jacques@solucorp.qc.ca)
+ *
+ *
+ * Portions Copyright (c) 1995 Caldera, Inc. <greg@caldera.com>
+ * Neither Greg Page nor Caldera, Inc. admit liability nor provide
+ * warranty for any of this software. This material is provided
+ * "AS-IS" and at no charge.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <net/ipx.h>
+#include <linux/inet.h>
+#include <linux/route.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/termios.h> /* For TIOCOUTQ/INQ */
+#include <linux/interrupt.h>
+#include <net/p8022.h>
+#include <net/psnap.h>
+#include <linux/proc_fs.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <linux/if_arp.h>
+
+#ifdef CONFIG_SYSCTL
+extern void ipx_register_sysctl(void);
+extern void ipx_unregister_sysctl(void);
+#else
+#define ipx_register_sysctl()
+#define ipx_unregister_sysctl()
+#endif
+
+/* Configuration Variables */
+static unsigned char ipxcfg_max_hops = 16;
+static char ipxcfg_auto_select_primary;
+static char ipxcfg_auto_create_interfaces;
+int sysctl_ipx_pprop_broadcasting = 1;
+
+/* Global Variables */
+static struct datalink_proto *p8022_datalink;
+static struct datalink_proto *pEII_datalink;
+static struct datalink_proto *p8023_datalink;
+static struct datalink_proto *pSNAP_datalink;
+
+static struct proto_ops ipx_dgram_ops;
+
+static struct net_proto_family *spx_family_ops;
+
+static ipx_route *ipx_routes;
+static rwlock_t ipx_routes_lock = RW_LOCK_UNLOCKED;
+
+static ipx_interface *ipx_interfaces;
+static spinlock_t ipx_interfaces_lock = SPIN_LOCK_UNLOCKED;
+
+static ipx_interface *ipx_primary_net;
+static ipx_interface *ipx_internal_net;
+
+#define IPX_SKB_CB(__skb) ((struct ipx_cb *)&((__skb)->cb[0]))
+
+#undef IPX_REFCNT_DEBUG
+#ifdef IPX_REFCNT_DEBUG
+atomic_t ipx_sock_nr;
+#endif
+
+static void ipxcfg_set_auto_create(char val)
+{
+ if (ipxcfg_auto_create_interfaces != val) {
+ if (val)
+ MOD_INC_USE_COUNT;
+ else
+ MOD_DEC_USE_COUNT;
+
+ ipxcfg_auto_create_interfaces = val;
+ }
+}
+
+static void ipxcfg_set_auto_select(char val)
+{
+ ipxcfg_auto_select_primary = val;
+ if (val && !ipx_primary_net)
+ ipx_primary_net = ipx_interfaces;
+}
+
+static int ipxcfg_get_config_data(ipx_config_data *arg)
+{
+ ipx_config_data vals;
+
+ vals.ipxcfg_auto_create_interfaces = ipxcfg_auto_create_interfaces;
+ vals.ipxcfg_auto_select_primary = ipxcfg_auto_select_primary;
+
+ return copy_to_user(arg, &vals, sizeof(vals)) ? -EFAULT : 0;
+}
+
+/* Handlers for the socket list. */
+
+static inline void ipxitf_hold(ipx_interface *intrfc)
+{
+ atomic_inc(&intrfc->refcnt);
+}
+
+static void ipxitf_down(ipx_interface *intrfc);
+
+static inline void ipxitf_put(ipx_interface *intrfc)
+{
+ if (atomic_dec_and_test(&intrfc->refcnt))
+ ipxitf_down(intrfc);
+}
+
+static void __ipxitf_down(ipx_interface *intrfc);
+
+static inline void __ipxitf_put(ipx_interface *intrfc)
+{
+ if (atomic_dec_and_test(&intrfc->refcnt))
+ __ipxitf_down(intrfc);
+}
+/*
+ * Note: Sockets may not be removed _during_ an interrupt or inet_bh
+ * handler using this technique. They can be added although we do not
+ * use this facility.
+ */
+
+void ipx_remove_socket(struct sock *sk)
+{
+ struct sock *s;
+ ipx_interface *intrfc;
+
+ /* Determine interface with which socket is associated */
+ intrfc = sk->protinfo.af_ipx.intrfc;
+ if (!intrfc)
+ goto out;
+
+ ipxitf_hold(intrfc);
+ spin_lock_bh(&intrfc->if_sklist_lock);
+ s = intrfc->if_sklist;
+ if (s == sk) {
+ intrfc->if_sklist = s->next;
+ goto out_unlock;
+ }
+
+ while (s && s->next) {
+ if (s->next == sk) {
+ s->next = sk->next;
+ goto out_unlock;
+ }
+ s = s->next;
+ }
+out_unlock:
+ spin_unlock_bh(&intrfc->if_sklist_lock);
+ sock_put(sk);
+ ipxitf_put(intrfc);
+out: return;
+}
+
+static void ipx_destroy_socket(struct sock *sk)
+{
+ ipx_remove_socket(sk);
+ skb_queue_purge(&sk->receive_queue);
+#ifdef IPX_REFCNT_DEBUG
+ atomic_dec(&ipx_sock_nr);
+ printk(KERN_DEBUG "IPX socket %p released, %d are still alive\n", sk,
+ atomic_read(&ipx_sock_nr));
+ if (atomic_read(&sk->refcnt) != 1)
+ printk(KERN_DEBUG "Destruction sock ipx %p delayed, cnt=%d\n",
+ sk, atomic_read(&sk->refcnt));
+#endif
+ sock_put(sk);
+}
+
+/*
+ * The following code is used to support IPX Interfaces (IPXITF). An
+ * IPX interface is defined by a physical device and a frame type.
+ */
+static ipx_route * ipxrtr_lookup(__u32);
+
+/* ipxitf_clear_primary_net has to be called with ipx_interfaces_lock held */
+
+static void ipxitf_clear_primary_net(void)
+{
+ ipx_primary_net = ipxcfg_auto_select_primary ? ipx_interfaces : NULL;
+}
+
+static ipx_interface *__ipxitf_find_using_phys(struct net_device *dev,
+ unsigned short datalink)
+{
+ ipx_interface *i = ipx_interfaces;
+
+ while (i && (i->if_dev != dev || i->if_dlink_type != datalink))
+ i = i->if_next;
+
+ return i;
+}
+
+static ipx_interface *ipxitf_find_using_phys(struct net_device *dev,
+ unsigned short datalink)
+{
+ ipx_interface *i;
+
+ spin_lock_bh(&ipx_interfaces_lock);
+ i = __ipxitf_find_using_phys(dev, datalink);
+ if (i)
+ ipxitf_hold(i);
+ spin_unlock_bh(&ipx_interfaces_lock);
+ return i;
+}
+
+static ipx_interface *ipxitf_find_using_net(__u32 net)
+{
+ ipx_interface *i;
+
+ spin_lock_bh(&ipx_interfaces_lock);
+ if (net)
+ for (i = ipx_interfaces; i && i->if_netnum != net;
+ i = i->if_next)
+ ;
+ else
+ i = ipx_primary_net;
+ if (i)
+ ipxitf_hold(i);
+ spin_unlock_bh(&ipx_interfaces_lock);
+
+ return i;
+}
+
+/* Sockets are bound to a particular IPX interface. */
+static void ipxitf_insert_socket(ipx_interface *intrfc, struct sock *sk)
+{
+ ipxitf_hold(intrfc);
+ sock_hold(sk);
+ spin_lock_bh(&intrfc->if_sklist_lock);
+ sk->protinfo.af_ipx.intrfc = intrfc;
+ sk->next = NULL;
+ if (!intrfc->if_sklist)
+ intrfc->if_sklist = sk;
+ else {
+ struct sock *s = intrfc->if_sklist;
+ while (s->next)
+ s = s->next;
+ s->next = sk;
+ }
+ spin_unlock_bh(&intrfc->if_sklist_lock);
+ ipxitf_put(intrfc);
+}
+
+/* caller must hold intrfc->if_sklist_lock */
+static struct sock *__ipxitf_find_socket(ipx_interface *intrfc,
+ unsigned short port)
+{
+ struct sock *s = intrfc->if_sklist;
+
+ while (s && s->protinfo.af_ipx.port != port)
+ s = s->next;
+
+ return s;
+}
+
+/* caller must hold a reference to intrfc */
+static struct sock *ipxitf_find_socket(ipx_interface *intrfc,
+ unsigned short port)
+{
+ struct sock *s;
+
+ spin_lock_bh(&intrfc->if_sklist_lock);
+ s = __ipxitf_find_socket(intrfc, port);
+ if (s)
+ sock_hold(s);
+ spin_unlock_bh(&intrfc->if_sklist_lock);
+
+ return s;
+}
+
+#ifdef CONFIG_IPX_INTERN
+static struct sock *ipxitf_find_internal_socket(ipx_interface *intrfc,
+ unsigned char *node, unsigned short port)
+{
+ struct sock *s;
+
+ ipxitf_hold(intrfc);
+ spin_lock_bh(&intrfc->if_sklist_lock);
+ s = intrfc->if_sklist;
+
+ while (s) {
+ if (s->protinfo.af_ipx.port == port &&
+ !memcmp(node, s->protinfo.af_ipx.node, IPX_NODE_LEN))
+ break;
+ s = s->next;
+ }
+ spin_unlock_bh(&intrfc->if_sklist_lock);
+ ipxitf_put(intrfc);
+
+ return s;
+}
+#endif
+
+static void ipxrtr_del_routes(ipx_interface *);
+
+static void __ipxitf_down(ipx_interface *intrfc)
+{
+ struct sock *s, *t;
+
+ /* Delete all routes associated with this interface */
+ ipxrtr_del_routes(intrfc);
+
+ spin_lock_bh(&intrfc->if_sklist_lock);
+ /* error sockets */
+ for (s = intrfc->if_sklist; s;) {
+ s->err = ENOLINK;
+ s->error_report(s);
+ s->protinfo.af_ipx.intrfc = NULL;
+ s->protinfo.af_ipx.port = 0;
+ s->zapped = 1; /* Indicates it is no longer bound */
+ t = s;
+ s = s->next;
+ t->next = NULL;
+ }
+ intrfc->if_sklist = NULL;
+ spin_unlock_bh(&intrfc->if_sklist_lock);
+
+ /* remove this interface from list */
+ if (intrfc == ipx_interfaces)
+ ipx_interfaces = intrfc->if_next;
+ else {
+ ipx_interface *i = ipx_interfaces;
+ while (i && i->if_next != intrfc)
+ i = i->if_next;
+ if (i && i->if_next == intrfc)
+ i->if_next = intrfc->if_next;
+ }
+
+ /* remove this interface from *special* networks */
+ if (intrfc == ipx_primary_net)
+ ipxitf_clear_primary_net();
+ if (intrfc == ipx_internal_net)
+ ipx_internal_net = NULL;
+
+ if (intrfc->if_dev)
+ dev_put(intrfc->if_dev);
+ kfree(intrfc);
+ MOD_DEC_USE_COUNT;
+}
+
+static void ipxitf_down(ipx_interface *intrfc)
+{
+ spin_lock_bh(&ipx_interfaces_lock);
+ __ipxitf_down(intrfc);
+ spin_unlock_bh(&ipx_interfaces_lock);
+}
+
+static int ipxitf_device_event(struct notifier_block *notifier,
+ unsigned long event, void *ptr)
+{
+ struct net_device *dev = ptr;
+ ipx_interface *i, *tmp;
+
+ if (event != NETDEV_DOWN && event != NETDEV_UP)
+ goto out;
+
+ spin_lock_bh(&ipx_interfaces_lock);
+ for (i = ipx_interfaces; i;) {
+ tmp = i->if_next;
+ if (i->if_dev == dev) {
+ if (event == NETDEV_UP)
+ ipxitf_hold(i);
+ else
+ __ipxitf_put(i);
+ }
+ i = tmp;
+ }
+ spin_unlock_bh(&ipx_interfaces_lock);
+out: return NOTIFY_DONE;
+}
+
+static void ipxitf_def_skb_handler(struct sock *sock, struct sk_buff *skb)
+{
+ if (sock_queue_rcv_skb(sock, skb) < 0)
+ kfree_skb(skb);
+}
+
+/*
+ * On input skb->sk is NULL. Nobody is charged for the memory.
+ */
+
+/* caller must hold a reference to intrfc */
+
+#ifdef CONFIG_IPX_INTERN
+static int ipxitf_demux_socket(ipx_interface *intrfc, struct sk_buff *skb,
+ int copy)
+{
+ struct ipxhdr *ipx = skb->nh.ipxh;
+ int is_broadcast = !memcmp(ipx->ipx_dest.node, ipx_broadcast_node,
+ IPX_NODE_LEN);
+ struct sock *s;
+ int ret;
+
+ spin_lock_bh(&intrfc->if_sklist_lock);
+ s = intrfc->if_sklist;
+
+ while (s) {
+ if (s->protinfo.af_ipx.port == ipx->ipx_dest.sock &&
+ (is_broadcast || !memcmp(ipx->ipx_dest.node,
+ s->protinfo.af_ipx.node,
+ IPX_NODE_LEN))) {
+ /* We found a socket to which to send */
+ struct sk_buff *skb1;
+
+ if (copy) {
+ skb1 = skb_clone(skb, GFP_ATOMIC);
+ ret = -ENOMEM;
+ if (!skb1)
+ goto out;
+ } else {
+ skb1 = skb;
+ copy = 1; /* skb may only be used once */
+ }
+ ipxitf_def_skb_handler(s, skb1);
+
+ /* On an external interface, one socket can listen */
+ if (intrfc != ipx_internal_net)
+ break;
+ }
+ s = s->next;
+ }
+
+ /* skb was solely for us, and we did not make a copy, so free it. */
+ if (!copy)
+ kfree_skb(skb);
+
+ ret = 0;
+out: spin_unlock_bh(&intrfc->if_sklist_lock);
+ return ret;
+}
+#else
+static struct sock *ncp_connection_hack(ipx_interface *intrfc,
+ struct ipxhdr *ipx)
+{
+ /* The packet's target is a NCP connection handler. We want to hand it
+ * to the correct socket directly within the kernel, so that the
+ * mars_nwe packet distribution process does not have to do it. Here we
+ * only care about NCP and BURST packets.
+ *
+ * You might call this a hack, but believe me, you do not want a
+ * complete NCP layer in the kernel, and this is VERY fast as well. */
+ struct sock *sk = NULL;
+ int connection = 0;
+ u8 *ncphdr = (u8 *)(ipx + 1);
+
+ if (*ncphdr == 0x22 && *(ncphdr + 1) == 0x22) /* NCP request */
+ connection = (((int) *(ncphdr + 5)) << 8) | (int) *(ncphdr + 3);
+ else if (*ncphdr == 0x77 && *(ncphdr + 1) == 0x77) /* BURST packet */
+ connection = (((int) *(ncphdr + 9)) << 8) | (int) *(ncphdr + 8);
+
+ if (connection) {
+ /* Now we have to look for a special NCP connection handling
+ * socket. Only these sockets have ipx_ncp_conn != 0, set by
+ * SIOCIPXNCPCONN. */
+ spin_lock_bh(&intrfc->if_sklist_lock);
+ for (sk = intrfc->if_sklist;
+ sk && sk->protinfo.af_ipx.ipx_ncp_conn != connection;
+ sk = sk->next);
+ if (sk)
+ sock_hold(sk);
+ spin_unlock_bh(&intrfc->if_sklist_lock);
+ }
+ return sk;
+}
+
+static int ipxitf_demux_socket(ipx_interface *intrfc, struct sk_buff *skb,
+ int copy)
+{
+ struct ipxhdr *ipx = skb->nh.ipxh;
+ struct sock *sock1 = NULL, *sock2 = NULL;
+ struct sk_buff *skb1 = NULL, *skb2 = NULL;
+ int ret;
+
+ if (intrfc == ipx_primary_net && ntohs(ipx->ipx_dest.sock) == 0x451)
+ sock1 = ncp_connection_hack(intrfc, ipx);
+ if (!sock1)
+ /* No special socket found, forward the packet the normal way */
+ sock1 = ipxitf_find_socket(intrfc, ipx->ipx_dest.sock);
+
+ /*
+ * We need to check if there is a primary net and if
+ * this is addressed to one of the *SPECIAL* sockets because
+ * these need to be propagated to the primary net.
+ * The *SPECIAL* socket list contains: 0x452(SAP), 0x453(RIP) and
+ * 0x456(Diagnostic).
+ */
+
+ if (ipx_primary_net && intrfc != ipx_primary_net) {
+ const int dsock = ntohs(ipx->ipx_dest.sock);
+
+ if (dsock == 0x452 || dsock == 0x453 || dsock == 0x456)
+ /* The appropriate thing to do here is to dup the
+ * packet and route to the primary net interface via
+ * ipxitf_send; however, we'll cheat and just demux it
+ * here. */
+ sock2 = ipxitf_find_socket(ipx_primary_net,
+ ipx->ipx_dest.sock);
+ }
+
+ /*
+ * If there is nothing to do return. The kfree will cancel any charging.
+ */
+ if (!sock1 && !sock2) {
+ if (!copy)
+ kfree_skb(skb);
+ return 0;
+ }
+
+ /*
+ * This next segment of code is a little awkward, but it sets it up
+ * so that the appropriate number of copies of the SKB are made and
+ * that skb1 and skb2 point to it (them) so that it (they) can be
+ * demuxed to sock1 and/or sock2. If we are unable to make enough
+ * copies, we do as much as is possible.
+ */
+
+ if (copy)
+ skb1 = skb_clone(skb, GFP_ATOMIC);
+ else
+ skb1 = skb;
+
+ ret = -ENOMEM;
+ if (!skb1)
+ goto out;
+
+ /* Do we need 2 SKBs? */
+ if (sock1 && sock2)
+ skb2 = skb_clone(skb1, GFP_ATOMIC);
+ else
+ skb2 = skb1;
+
+ if (sock1)
+ ipxitf_def_skb_handler(sock1, skb1);
+
+ ret = -ENOMEM;
+ if (!skb2)
+ goto out;
+
+ if (sock2)
+ ipxitf_def_skb_handler(sock2, skb2);
+
+ ret = 0;
+out: if (sock1)
+ sock_put(sock1);
+ if (sock2)
+ sock_put(sock2);
+ return ret;
+}
+#endif /* CONFIG_IPX_INTERN */
+
+static struct sk_buff *ipxitf_adjust_skbuff(ipx_interface *intrfc,
+ struct sk_buff *skb)
+{
+ struct sk_buff *skb2;
+ int in_offset = skb->h.raw - skb->head;
+ int out_offset = intrfc->if_ipx_offset;
+ int len;
+
+ /* Hopefully, most cases */
+ if (in_offset >= out_offset)
+ return skb;
+
+ /* Need new SKB */
+ len = skb->len + out_offset;
+ skb2 = alloc_skb(len, GFP_ATOMIC);
+ if (skb2) {
+ skb_reserve(skb2, out_offset);
+ skb2->nh.raw = skb2->h.raw = skb_put(skb2, skb->len);
+ memcpy(skb2->h.raw, skb->h.raw, skb->len);
+ memcpy(skb2->cb, skb->cb, sizeof(skb->cb));
+ }
+ kfree_skb(skb);
+ return skb2;
+}
+
+/* caller must hold a reference to intrfc and the skb has to be unshared */
+
+static int ipxitf_send(ipx_interface *intrfc, struct sk_buff *skb, char *node)
+{
+ struct ipxhdr *ipx = skb->nh.ipxh;
+ struct net_device *dev = intrfc->if_dev;
+ struct datalink_proto *dl = intrfc->if_dlink;
+ char dest_node[IPX_NODE_LEN];
+ int send_to_wire = 1;
+ int addr_len;
+
+ ipx->ipx_tctrl = IPX_SKB_CB(skb)->ipx_tctrl;
+ ipx->ipx_dest.net = IPX_SKB_CB(skb)->ipx_dest_net;
+ ipx->ipx_source.net = IPX_SKB_CB(skb)->ipx_source_net;
+
+ /* see if we need to include the netnum in the route list */
+ if (IPX_SKB_CB(skb)->last_hop.index >= 0) {
+ u32 *last_hop = (u32 *)(((u8 *) skb->data) +
+ sizeof(struct ipxhdr) +
+ IPX_SKB_CB(skb)->last_hop.index *
+ sizeof(u32));
+ *last_hop = IPX_SKB_CB(skb)->last_hop.netnum;
+ IPX_SKB_CB(skb)->last_hop.index = -1;
+ }
+
+ /*
+ * We need to know how many skbuffs it will take to send out this
+ * packet to avoid unnecessary copies.
+ */
+
+ if (!dl || !dev || dev->flags & IFF_LOOPBACK)
+ send_to_wire = 0; /* No non looped */
+
+ /*
+ * See if this should be demuxed to sockets on this interface
+ *
+ * We want to ensure the original was eaten or that we only use
+ * up clones.
+ */
+
+ if (ipx->ipx_dest.net == intrfc->if_netnum) {
+ /*
+ * To our own node, loop and free the original.
+ * The internal net will receive on all node address.
+ */
+ if (intrfc == ipx_internal_net ||
+ !memcmp(intrfc->if_node, node, IPX_NODE_LEN)) {
+ /* Don't charge sender */
+ skb_orphan(skb);
+
+ /* Will charge receiver */
+ return ipxitf_demux_socket(intrfc, skb, 0);
+ }
+
+ /* Broadcast, loop and possibly keep to send on. */
+ if (!memcmp(ipx_broadcast_node, node, IPX_NODE_LEN)) {
+ if (!send_to_wire)
+ skb_orphan(skb);
+ ipxitf_demux_socket(intrfc, skb, send_to_wire);
+ if (!send_to_wire)
+ goto out;
+ }
+ }
+
+ /*
+ * If the originating net is not equal to our net; this is routed
+ * We are still charging the sender. Which is right - the driver
+ * free will handle this fairly.
+ */
+ if (ipx->ipx_source.net != intrfc->if_netnum) {
+ /*
+ * Unshare the buffer before modifying the count in
+ * case its a flood or tcpdump
+ */
+ skb = skb_unshare(skb, GFP_ATOMIC);
+ if (!skb)
+ goto out;
+ if (++ipx->ipx_tctrl > ipxcfg_max_hops)
+ send_to_wire = 0;
+ }
+
+ if (!send_to_wire) {
+ kfree_skb(skb);
+ goto out;
+ }
+
+ /* Determine the appropriate hardware address */
+ addr_len = dev->addr_len;
+ if (!memcmp(ipx_broadcast_node, node, IPX_NODE_LEN))
+ memcpy(dest_node, dev->broadcast, addr_len);
+ else
+ memcpy(dest_node, &(node[IPX_NODE_LEN-addr_len]), addr_len);
+
+ /* Make any compensation for differing physical/data link size */
+ skb = ipxitf_adjust_skbuff(intrfc, skb);
+ if (!skb)
+ goto out;
+
+ /* set up data link and physical headers */
+ skb->dev = dev;
+ skb->protocol = __constant_htons(ETH_P_IPX);
+ dl->datalink_header(dl, skb, dest_node);
+
+ /* Send it out */
+ dev_queue_xmit(skb);
+out: return 0;
+}
+
+static int ipxrtr_add_route(__u32, ipx_interface *, unsigned char *);
+
+static int ipxitf_add_local_route(ipx_interface *intrfc)
+{
+ return ipxrtr_add_route(intrfc->if_netnum, intrfc, NULL);
+}
+
+static const char * ipx_frame_name(unsigned short);
+static const char * ipx_device_name(ipx_interface *);
+static void ipxitf_discover_netnum(ipx_interface *intrfc, struct sk_buff *skb);
+static int ipxitf_pprop(ipx_interface *intrfc, struct sk_buff *skb);
+
+static int ipxitf_rcv(ipx_interface *intrfc, struct sk_buff *skb)
+{
+ struct ipxhdr *ipx = skb->nh.ipxh;
+ int ret = 0;
+
+ ipxitf_hold(intrfc);
+
+ /* See if we should update our network number */
+ if (!intrfc->if_netnum) /* net number of intrfc not known yet */
+ ipxitf_discover_netnum(intrfc, skb);
+
+ IPX_SKB_CB(skb)->last_hop.index = -1;
+ if (ipx->ipx_type == IPX_TYPE_PPROP) {
+ ret = ipxitf_pprop(intrfc, skb);
+ if (ret)
+ goto out_free_skb;
+ }
+
+ /* local processing follows */
+ if (!IPX_SKB_CB(skb)->ipx_dest_net)
+ IPX_SKB_CB(skb)->ipx_dest_net = intrfc->if_netnum;
+ if (!IPX_SKB_CB(skb)->ipx_source_net)
+ IPX_SKB_CB(skb)->ipx_source_net = intrfc->if_netnum;
+
+ /* it doesn't make sense to route a pprop packet, there's no meaning
+ * in the ipx_dest_net for such packets */
+ if (ipx->ipx_type != IPX_TYPE_PPROP &&
+ intrfc->if_netnum != IPX_SKB_CB(skb)->ipx_dest_net) {
+ /* We only route point-to-point packets. */
+ if (skb->pkt_type == PACKET_HOST) {
+ skb = skb_unshare(skb, GFP_ATOMIC);
+ if (skb)
+ ret = ipxrtr_route_skb(skb);
+ goto out_intrfc;
+ }
+
+ goto out_free_skb;
+ }
+
+ /* see if we should keep it */
+ if (!memcmp(ipx_broadcast_node, ipx->ipx_dest.node, IPX_NODE_LEN) ||
+ !memcmp(intrfc->if_node, ipx->ipx_dest.node, IPX_NODE_LEN)) {
+ ret = ipxitf_demux_socket(intrfc, skb, 0);
+ goto out_intrfc;
+ }
+
+ /* we couldn't pawn it off so unload it */
+out_free_skb:
+ kfree_skb(skb);
+out_intrfc:
+ ipxitf_put(intrfc);
+ return ret;
+}
+
+static void ipxitf_discover_netnum(ipx_interface *intrfc, struct sk_buff *skb)
+{
+ const struct ipx_cb *cb = IPX_SKB_CB(skb);
+
+ /* see if this is an intra packet: source_net == dest_net */
+ if (cb->ipx_source_net == cb->ipx_dest_net && cb->ipx_source_net) {
+ ipx_interface *i = ipxitf_find_using_net(cb->ipx_source_net);
+ /* NB: NetWare servers lie about their hop count so we
+ * dropped the test based on it. This is the best way
+ * to determine this is a 0 hop count packet. */
+ if (!i) {
+ intrfc->if_netnum = cb->ipx_source_net;
+ ipxitf_add_local_route(intrfc);
+ } else {
+ printk(KERN_WARNING "IPX: Network number collision "
+ "%lx\n %s %s and %s %s\n",
+ (unsigned long) htonl(cb->ipx_source_net),
+ ipx_device_name(i),
+ ipx_frame_name(i->if_dlink_type),
+ ipx_device_name(intrfc),
+ ipx_frame_name(intrfc->if_dlink_type));
+ ipxitf_put(i);
+ }
+ }
+}
+
+/**
+ * ipxitf_pprop - Process packet propagation IPX packet type 0x14, used for
+ * NetBIOS broadcasts
+ * @intrfc: IPX interface receiving this packet
+ * @skb: Received packet
+ *
+ * Checks if packet is valid: if its more than %IPX_MAX_PPROP_HOPS hops or if it
+ * is smaller than a IPX header + the room for %IPX_MAX_PPROP_HOPS hops we drop
+ * it, not even processing it locally, if it has exact %IPX_MAX_PPROP_HOPS we
+ * don't broadcast it, but process it locally. See chapter 5 of Novell's "IPX
+ * RIP and SAP Router Specification", Part Number 107-000029-001.
+ *
+ * If it is valid, check if we have pprop broadcasting enabled by the user,
+ * if not, just return zero for local processing.
+ *
+ * If it is enabled check the packet and don't broadcast it if we have already
+ * seen this packet.
+ *
+ * Broadcast: send it to the interfaces that aren't on the packet visited nets
+ * array, just after the IPX header.
+ *
+ * Returns -EINVAL for invalid packets, so that the calling function drops
+ * the packet without local processing. 0 if packet is to be locally processed.
+ */
+static int ipxitf_pprop(ipx_interface *intrfc, struct sk_buff *skb)
+{
+ struct ipxhdr *ipx = skb->nh.ipxh;
+ int i, ret = -EINVAL;
+ ipx_interface *ifcs;
+ char *c;
+ u32 *l;
+
+ /* Illegal packet - too many hops or too short */
+ /* We decide to throw it away: no broadcasting, no local processing.
+ * NetBIOS unaware implementations route them as normal packets -
+ * tctrl <= 15, any data payload... */
+ if (IPX_SKB_CB(skb)->ipx_tctrl > IPX_MAX_PPROP_HOPS ||
+ ntohs(ipx->ipx_pktsize) < sizeof(struct ipxhdr) +
+ IPX_MAX_PPROP_HOPS * sizeof(u32))
+ goto out;
+ /* are we broadcasting this damn thing? */
+ ret = 0;
+ if (!sysctl_ipx_pprop_broadcasting)
+ goto out;
+ /* We do broadcast packet on the IPX_MAX_PPROP_HOPS hop, but we
+ * process it locally. All previous hops broadcasted it, and process it
+ * locally. */
+ if (IPX_SKB_CB(skb)->ipx_tctrl == IPX_MAX_PPROP_HOPS)
+ goto out;
+
+ c = ((u8 *) ipx) + sizeof(struct ipxhdr);
+ l = (u32 *) c;
+
+ /* Don't broadcast packet if already seen this net */
+ for (i = 0; i < IPX_SKB_CB(skb)->ipx_tctrl; i++)
+ if (*l++ == intrfc->if_netnum)
+ goto out;
+
+ /* < IPX_MAX_PPROP_HOPS hops && input interface not in list. Save the
+ * position where we will insert recvd netnum into list, later on,
+ * in ipxitf_send */
+ IPX_SKB_CB(skb)->last_hop.index = i;
+ IPX_SKB_CB(skb)->last_hop.netnum = intrfc->if_netnum;
+ /* xmit on all other interfaces... */
+ spin_lock_bh(&ipx_interfaces_lock);
+ for (ifcs = ipx_interfaces; ifcs; ifcs = ifcs->if_next) {
+ /* Except unconfigured interfaces */
+ if (!ifcs->if_netnum)
+ continue;
+
+ /* That aren't in the list */
+ if (ifcs == intrfc)
+ continue;
+ l = (__u32 *) c;
+ /* don't consider the last entry in the packet list,
+ * it is our netnum, and it is not there yet */
+ for (i = 0; i < IPX_SKB_CB(skb)->ipx_tctrl; i++)
+ if (ifcs->if_netnum == *l++)
+ break;
+ if (i == IPX_SKB_CB(skb)->ipx_tctrl) {
+ struct sk_buff *s = skb_copy(skb, GFP_ATOMIC);
+
+ if (s) {
+ IPX_SKB_CB(s)->ipx_dest_net = ifcs->if_netnum;
+ ipxrtr_route_skb(s);
+ }
+ }
+ }
+ spin_unlock_bh(&ipx_interfaces_lock);
+out: return ret;
+}
+
+static void ipxitf_insert(ipx_interface *intrfc)
+{
+ intrfc->if_next = NULL;
+ spin_lock_bh(&ipx_interfaces_lock);
+ if (!ipx_interfaces)
+ ipx_interfaces = intrfc;
+ else {
+ ipx_interface *i = ipx_interfaces;
+ while (i->if_next)
+ i = i->if_next;
+ i->if_next = intrfc;
+ }
+ spin_unlock_bh(&ipx_interfaces_lock);
+
+ if (ipxcfg_auto_select_primary && !ipx_primary_net)
+ ipx_primary_net = intrfc;
+}
+
+static ipx_interface *ipxitf_alloc(struct net_device *dev, __u32 netnum,
+ unsigned short dlink_type,
+ struct datalink_proto *dlink,
+ unsigned char internal, int ipx_offset)
+{
+ ipx_interface *intrfc = kmalloc(sizeof(*intrfc), GFP_ATOMIC);
+
+ if (intrfc) {
+ intrfc->if_dev = dev;
+ intrfc->if_netnum = netnum;
+ intrfc->if_dlink_type = dlink_type;
+ intrfc->if_dlink = dlink;
+ intrfc->if_internal = internal;
+ intrfc->if_ipx_offset = ipx_offset;
+ intrfc->if_sknum = IPX_MIN_EPHEMERAL_SOCKET;
+ intrfc->if_sklist = NULL;
+ atomic_set(&intrfc->refcnt, 1);
+ spin_lock_init(&intrfc->if_sklist_lock);
+ MOD_INC_USE_COUNT;
+ }
+
+ return intrfc;
+}
+
+static int ipxitf_create_internal(ipx_interface_definition *idef)
+{
+ ipx_interface *intrfc;
+ int ret = -EEXIST;
+
+ /* Only one primary network allowed */
+ if (ipx_primary_net)
+ goto out;
+
+ /* Must have a valid network number */
+ ret = -EADDRNOTAVAIL;
+ if (!idef->ipx_network)
+ goto out;
+ intrfc = ipxitf_find_using_net(idef->ipx_network);
+ ret = -EADDRINUSE;
+ if (intrfc) {
+ ipxitf_put(intrfc);
+ goto out;
+ }
+ intrfc = ipxitf_alloc(NULL, idef->ipx_network, 0, NULL, 1, 0);
+ ret = -EAGAIN;
+ if (!intrfc)
+ goto out;
+ memcpy((char *)&(intrfc->if_node), idef->ipx_node, IPX_NODE_LEN);
+ ipx_internal_net = ipx_primary_net = intrfc;
+ ipxitf_hold(intrfc);
+ ipxitf_insert(intrfc);
+
+ ret = ipxitf_add_local_route(intrfc);
+ ipxitf_put(intrfc);
+out: return ret;
+}
+
+static int ipx_map_frame_type(unsigned char type)
+{
+ int ret = 0;
+
+ switch (type) {
+ case IPX_FRAME_ETHERII:
+ ret = __constant_htons(ETH_P_IPX);
+ break;
+ case IPX_FRAME_8022:
+ ret = __constant_htons(ETH_P_802_2);
+ break;
+ case IPX_FRAME_SNAP:
+ ret = __constant_htons(ETH_P_SNAP);
+ break;
+ case IPX_FRAME_8023:
+ ret = __constant_htons(ETH_P_802_3);
+ break;
+ }
+
+ return ret;
+}
+
+static int ipxitf_create(ipx_interface_definition *idef)
+{
+ struct net_device *dev;
+ unsigned short dlink_type = 0;
+ struct datalink_proto *datalink = NULL;
+ ipx_interface *intrfc;
+ int err;
+
+ if (idef->ipx_special == IPX_INTERNAL) {
+ err = ipxitf_create_internal(idef);
+ goto out;
+ }
+
+ err = -EEXIST;
+ if (idef->ipx_special == IPX_PRIMARY && ipx_primary_net)
+ goto out;
+
+ intrfc = ipxitf_find_using_net(idef->ipx_network);
+ err = -EADDRINUSE;
+ if (idef->ipx_network && intrfc) {
+ ipxitf_put(intrfc);
+ goto out;
+ }
+
+ if (intrfc)
+ ipxitf_put(intrfc);
+
+ dev = dev_get_by_name(idef->ipx_device);
+ err = -ENODEV;
+ if (!dev)
+ goto out;
+
+ switch (idef->ipx_dlink_type) {
+ case IPX_FRAME_TR_8022:
+ printk(KERN_WARNING "IPX frame type 802.2TR is "
+ "obsolete Use 802.2 instead.\n");
+ /* fall through */
+ case IPX_FRAME_8022:
+ dlink_type = __constant_htons(ETH_P_802_2);
+ datalink = p8022_datalink;
+ break;
+ case IPX_FRAME_ETHERII:
+ if (dev->type != ARPHRD_IEEE802) {
+ dlink_type = __constant_htons(ETH_P_IPX);
+ datalink = pEII_datalink;
+ break;
+ } else
+ printk(KERN_WARNING "IPX frame type EtherII "
+ "over token-ring is obsolete. Use SNAP "
+ "instead.\n");
+ /* fall through */
+ case IPX_FRAME_SNAP:
+ dlink_type = __constant_htons(ETH_P_SNAP);
+ datalink = pSNAP_datalink;
+ break;
+ case IPX_FRAME_8023:
+ dlink_type = __constant_htons(ETH_P_802_3);
+ datalink = p8023_datalink;
+ break;
+ case IPX_FRAME_NONE:
+ default:
+ err = -EPROTONOSUPPORT;
+ goto out_dev;
+ }
+
+ err = -ENETDOWN;
+ if (!(dev->flags & IFF_UP))
+ goto out_dev;
+
+ /* Check addresses are suitable */
+ err = -EINVAL;
+ if (dev->addr_len > IPX_NODE_LEN)
+ goto out_dev;
+
+ intrfc = ipxitf_find_using_phys(dev, dlink_type);
+ if (!intrfc) {
+ /* Ok now create */
+ intrfc = ipxitf_alloc(dev, idef->ipx_network, dlink_type,
+ datalink, 0, dev->hard_header_len +
+ datalink->header_length);
+ err = -EAGAIN;
+ if (!intrfc)
+ goto out_dev;
+ /* Setup primary if necessary */
+ if (idef->ipx_special == IPX_PRIMARY)
+ ipx_primary_net = intrfc;
+ if (!memcmp(idef->ipx_node, "\000\000\000\000\000\000",
+ IPX_NODE_LEN)) {
+ memset(intrfc->if_node, 0, IPX_NODE_LEN);
+ memcpy(intrfc->if_node + IPX_NODE_LEN - dev->addr_len,
+ dev->dev_addr, dev->addr_len);
+ } else
+ memcpy(intrfc->if_node, idef->ipx_node, IPX_NODE_LEN);
+ ipxitf_hold(intrfc);
+ ipxitf_insert(intrfc);
+ }
+
+
+ /* If the network number is known, add a route */
+ err = 0;
+ if (!intrfc->if_netnum)
+ goto out_intrfc;
+
+ err = ipxitf_add_local_route(intrfc);
+out_intrfc:
+ ipxitf_put(intrfc);
+ goto out;
+out_dev:
+ dev_put(dev);
+out: return err;
+}
+
+static int ipxitf_delete(ipx_interface_definition *idef)
+{
+ struct net_device *dev = NULL;
+ unsigned short dlink_type = 0;
+ ipx_interface *intrfc;
+ int ret = 0;
+
+ spin_lock_bh(&ipx_interfaces_lock);
+ if (idef->ipx_special == IPX_INTERNAL) {
+ if (ipx_internal_net) {
+ __ipxitf_put(ipx_internal_net);
+ goto out;
+ }
+ ret = -ENOENT;
+ goto out;
+ }
+
+ dlink_type = ipx_map_frame_type(idef->ipx_dlink_type);
+ ret = -EPROTONOSUPPORT;
+ if (!dlink_type)
+ goto out;
+
+ dev = __dev_get_by_name(idef->ipx_device);
+ ret = -ENODEV;
+ if (!dev)
+ goto out;
+
+ intrfc = __ipxitf_find_using_phys(dev, dlink_type);
+ ret = -EINVAL;
+ if (!intrfc)
+ goto out;
+ __ipxitf_put(intrfc);
+
+ ret = 0;
+out: spin_unlock_bh(&ipx_interfaces_lock);
+ return ret;
+}
+
+static ipx_interface *ipxitf_auto_create(struct net_device *dev,
+ unsigned short dlink_type)
+{
+ ipx_interface *intrfc = NULL;
+ struct datalink_proto *datalink;
+
+ if (!dev)
+ goto out;
+
+ /* Check addresses are suitable */
+ if (dev->addr_len > IPX_NODE_LEN)
+ goto out;
+
+ switch (htons(dlink_type)) {
+ case ETH_P_IPX:
+ datalink = pEII_datalink;
+ break;
+
+ case ETH_P_802_2:
+ datalink = p8022_datalink;
+ break;
+
+ case ETH_P_SNAP:
+ datalink = pSNAP_datalink;
+ break;
+
+ case ETH_P_802_3:
+ datalink = p8023_datalink;
+ break;
+
+ default:
+ goto out;
+ }
+
+ intrfc = ipxitf_alloc(dev, 0, dlink_type, datalink, 0,
+ dev->hard_header_len + datalink->header_length);
+
+ if (intrfc) {
+ memset(intrfc->if_node, 0, IPX_NODE_LEN);
+ memcpy((char *)&(intrfc->if_node[IPX_NODE_LEN-dev->addr_len]),
+ dev->dev_addr, dev->addr_len);
+ spin_lock_init(&intrfc->if_sklist_lock);
+ atomic_set(&intrfc->refcnt, 1);
+ ipxitf_insert(intrfc);
+ dev_hold(dev);
+ }
+
+out: return intrfc;
+}
+
+static int ipxitf_ioctl(unsigned int cmd, void *arg)
+{
+ struct ifreq ifr;
+ int val;
+
+ switch (cmd) {
+ case SIOCSIFADDR: {
+ struct sockaddr_ipx *sipx;
+ ipx_interface_definition f;
+
+ if (copy_from_user(&ifr, arg, sizeof(ifr)))
+ return -EFAULT;
+
+ sipx = (struct sockaddr_ipx *)&ifr.ifr_addr;
+ if (sipx->sipx_family != AF_IPX)
+ return -EINVAL;
+
+ f.ipx_network = sipx->sipx_network;
+ memcpy(f.ipx_device, ifr.ifr_name,
+ sizeof(f.ipx_device));
+ memcpy(f.ipx_node, sipx->sipx_node, IPX_NODE_LEN);
+ f.ipx_dlink_type = sipx->sipx_type;
+ f.ipx_special = sipx->sipx_special;
+
+ if (sipx->sipx_action == IPX_DLTITF)
+ return ipxitf_delete(&f);
+ else
+ return ipxitf_create(&f);
+ }
+
+ case SIOCGIFADDR: {
+ int err = 0;
+ struct sockaddr_ipx *sipx;
+ ipx_interface *ipxif;
+ struct net_device *dev;
+
+ if (copy_from_user(&ifr, arg, sizeof(ifr)))
+ return -EFAULT;
+
+ sipx = (struct sockaddr_ipx *)&ifr.ifr_addr;
+ dev = __dev_get_by_name(ifr.ifr_name);
+ if (!dev)
+ return -ENODEV;
+
+ ipxif = ipxitf_find_using_phys(dev, ipx_map_frame_type(sipx->sipx_type));
+ if (!ipxif)
+ return -EADDRNOTAVAIL;
+
+ sipx->sipx_family = AF_IPX;
+ sipx->sipx_network = ipxif->if_netnum;
+ memcpy(sipx->sipx_node, ipxif->if_node,
+ sizeof(sipx->sipx_node));
+ if (copy_to_user(arg, &ifr, sizeof(ifr)))
+ err = -EFAULT;
+
+ ipxitf_put(ipxif);
+ return err;
+ }
+
+ case SIOCAIPXITFCRT:
+ if (get_user(val, (unsigned char *) arg))
+ return -EFAULT;
+ ipxcfg_set_auto_create(val);
+ break;
+
+ case SIOCAIPXPRISLT:
+ if (get_user(val, (unsigned char *) arg))
+ return -EFAULT;
+ ipxcfg_set_auto_select(val);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Routing tables for the IPX socket layer. */
+
+static inline void ipxrtr_hold(ipx_route *rt)
+{
+ atomic_inc(&rt->refcnt);
+}
+
+static inline void ipxrtr_put(ipx_route *rt)
+{
+ if (atomic_dec_and_test(&rt->refcnt))
+ kfree(rt);
+}
+
+static ipx_route *ipxrtr_lookup(__u32 net)
+{
+ ipx_route *r;
+
+ read_lock_bh(&ipx_routes_lock);
+ for (r = ipx_routes; r && r->ir_net != net; r = r->ir_next)
+ ;
+ if (r)
+ ipxrtr_hold(r);
+ read_unlock_bh(&ipx_routes_lock);
+
+ return r;
+}
+
+/* caller must hold a reference to intrfc */
+
+static int ipxrtr_add_route(__u32 network, ipx_interface *intrfc,
+ unsigned char *node)
+{
+ ipx_route *rt;
+ int ret;
+
+ /* Get a route structure; either existing or create */
+ rt = ipxrtr_lookup(network);
+ if (!rt) {
+ rt = kmalloc(sizeof(ipx_route), GFP_ATOMIC);
+ ret = -EAGAIN;
+ if (!rt)
+ goto out;
+
+ atomic_set(&rt->refcnt, 1);
+ ipxrtr_hold(rt);
+ write_lock_bh(&ipx_routes_lock);
+ rt->ir_next = ipx_routes;
+ ipx_routes = rt;
+ write_unlock_bh(&ipx_routes_lock);
+ } else {
+ ret = -EEXIST;
+ if (intrfc == ipx_internal_net)
+ goto out_put;
+ }
+
+ rt->ir_net = network;
+ rt->ir_intrfc = intrfc;
+ if (!node) {
+ memset(rt->ir_router_node, '\0', IPX_NODE_LEN);
+ rt->ir_routed = 0;
+ } else {
+ memcpy(rt->ir_router_node, node, IPX_NODE_LEN);
+ rt->ir_routed = 1;
+ }
+
+ ret = 0;
+out_put:
+ ipxrtr_put(rt);
+out: return ret;
+}
+
+static void ipxrtr_del_routes(ipx_interface *intrfc)
+{
+ ipx_route **r, *tmp;
+
+ write_lock_bh(&ipx_routes_lock);
+ for (r = &ipx_routes; (tmp = *r) != NULL;) {
+ if (tmp->ir_intrfc == intrfc) {
+ *r = tmp->ir_next;
+ ipxrtr_put(tmp);
+ } else
+ r = &(tmp->ir_next);
+ }
+ write_unlock_bh(&ipx_routes_lock);
+}
+
+static int ipxrtr_create(ipx_route_definition *rd)
+{
+ ipx_interface *intrfc;
+ int ret = -ENETUNREACH;
+
+ /* Find the appropriate interface */
+ intrfc = ipxitf_find_using_net(rd->ipx_router_network);
+ if (!intrfc)
+ goto out;
+ ret = ipxrtr_add_route(rd->ipx_network, intrfc, rd->ipx_router_node);
+ ipxitf_put(intrfc);
+out: return ret;
+}
+
+static int ipxrtr_delete(long net)
+{
+ ipx_route **r;
+ ipx_route *tmp;
+ int err;
+
+ write_lock_bh(&ipx_routes_lock);
+ for (r = &ipx_routes; (tmp = *r) != NULL;) {
+ if (tmp->ir_net == net) {
+ /* Directly connected; can't lose route */
+ err = -EPERM;
+ if (!tmp->ir_routed)
+ goto out;
+
+ *r = tmp->ir_next;
+ ipxrtr_put(tmp);
+ err = 0;
+ goto out;
+ }
+
+ r = &(tmp->ir_next);
+ }
+ err = -ENOENT;
+out: write_unlock_bh(&ipx_routes_lock);
+ return err;
+}
+
+/*
+ * Checksum routine for IPX
+ */
+
+/* Note: We assume ipx_tctrl==0 and htons(length)==ipx_pktsize */
+/* This functions should *not* mess with packet contents */
+
+static __u16 ipx_cksum(struct ipxhdr *packet, int length)
+{
+ /*
+ * NOTE: sum is a net byte order quantity, which optimizes the
+ * loop. This only works on big and little endian machines. (I
+ * don't know of a machine that isn't.)
+ */
+ /* start at ipx_dest - We skip the checksum field and start with
+ * ipx_type before the loop, not considering ipx_tctrl in the calc */
+ __u16 *p = (__u16 *)&packet->ipx_dest;
+ __u32 i = (length >> 1) - 1; /* Number of complete words */
+ __u32 sum = packet->ipx_type << sizeof(packet->ipx_tctrl);
+
+ /* Loop through all complete words except the checksum field,
+ * ipx_type (accounted above) and ipx_tctrl (not used in the cksum) */
+ while (--i)
+ sum += *p++;
+
+ /* Add on the last part word if it exists */
+ if (packet->ipx_pktsize & __constant_htons(1))
+ sum += ntohs(0xff00) & *p;
+
+ /* Do final fixup */
+ sum = (sum & 0xffff) + (sum >> 16);
+
+ /* It's a pity there's no concept of carry in C */
+ if (sum >= 0x10000)
+ sum++;
+
+ return ~sum;
+}
+
+/*
+ * Route an outgoing frame from a socket.
+ */
+static int ipxrtr_route_packet(struct sock *sk, struct sockaddr_ipx *usipx,
+ struct iovec *iov, int len, int noblock)
+{
+ struct sk_buff *skb;
+ ipx_interface *intrfc;
+ struct ipxhdr *ipx;
+ int size;
+ int ipx_offset;
+ ipx_route *rt = NULL;
+ int err;
+
+ /* Find the appropriate interface on which to send packet */
+ if (!usipx->sipx_network && ipx_primary_net) {
+ usipx->sipx_network = ipx_primary_net->if_netnum;
+ intrfc = ipx_primary_net;
+ } else {
+ rt = ipxrtr_lookup(usipx->sipx_network);
+ err = -ENETUNREACH;
+ if (!rt)
+ goto out;
+ intrfc = rt->ir_intrfc;
+ }
+
+ ipxitf_hold(intrfc);
+ ipx_offset = intrfc->if_ipx_offset;
+ size = sizeof(struct ipxhdr) + len + ipx_offset;
+
+ skb = sock_alloc_send_skb(sk, size, noblock, &err);
+ if (!skb)
+ goto out_put;
+
+ skb_reserve(skb, ipx_offset);
+ skb->sk = sk;
+
+ /* Fill in IPX header */
+ ipx = (struct ipxhdr *)skb_put(skb, sizeof(struct ipxhdr));
+ ipx->ipx_pktsize = htons(len + sizeof(struct ipxhdr));
+ IPX_SKB_CB(skb)->ipx_tctrl = 0;
+ ipx->ipx_type = usipx->sipx_type;
+ skb->nh.ipxh = ipx;
+ skb->h.raw = (void *)skb->nh.ipxh;
+
+ IPX_SKB_CB(skb)->last_hop.index = -1;
+#ifdef CONFIG_IPX_INTERN
+ IPX_SKB_CB(skb)->ipx_source_net = sk->protinfo.af_ipx.intrfc->if_netnum;
+ memcpy(ipx->ipx_source.node, sk->protinfo.af_ipx.node, IPX_NODE_LEN);
+#else
+ err = ntohs(sk->protinfo.af_ipx.port);
+ if (err == 0x453 || err == 0x452) {
+ /* RIP/SAP special handling for mars_nwe */
+ IPX_SKB_CB(skb)->ipx_source_net = intrfc->if_netnum;
+ memcpy(ipx->ipx_source.node, intrfc->if_node, IPX_NODE_LEN);
+ } else {
+ IPX_SKB_CB(skb)->ipx_source_net =
+ sk->protinfo.af_ipx.intrfc->if_netnum;
+ memcpy(ipx->ipx_source.node, sk->protinfo.af_ipx.intrfc->if_node, IPX_NODE_LEN);
+ }
+#endif /* CONFIG_IPX_INTERN */
+ ipx->ipx_source.sock = sk->protinfo.af_ipx.port;
+ IPX_SKB_CB(skb)->ipx_dest_net = usipx->sipx_network;
+ memcpy(ipx->ipx_dest.node, usipx->sipx_node, IPX_NODE_LEN);
+ ipx->ipx_dest.sock = usipx->sipx_port;
+
+ err = memcpy_fromiovec(skb_put(skb, len), iov, len);
+ if (err) {
+ kfree_skb(skb);
+ goto out_put;
+ }
+
+ /* Apply checksum. Not allowed on 802.3 links. */
+ if (sk->no_check || intrfc->if_dlink_type == IPX_FRAME_8023)
+ ipx->ipx_checksum = 0xFFFF;
+ else
+ ipx->ipx_checksum = ipx_cksum(ipx, len + sizeof(struct ipxhdr));
+
+ err = ipxitf_send(intrfc, skb, (rt && rt->ir_routed) ?
+ rt->ir_router_node : ipx->ipx_dest.node);
+out_put:
+ ipxitf_put(intrfc);
+ if (rt)
+ ipxrtr_put(rt);
+out: return err;
+}
+
+/* the skb has to be unshared, we'll end up calling ipxitf_send, that'll
+ * modify the packet */
+int ipxrtr_route_skb(struct sk_buff *skb)
+{
+ struct ipxhdr *ipx = skb->nh.ipxh;
+ ipx_route *r = ipxrtr_lookup(IPX_SKB_CB(skb)->ipx_dest_net);
+
+ if (!r) { /* no known route */
+ kfree_skb(skb);
+ return 0;
+ }
+
+ ipxitf_hold(r->ir_intrfc);
+ ipxitf_send(r->ir_intrfc, skb, r->ir_routed ?
+ r->ir_router_node : ipx->ipx_dest.node);
+ ipxitf_put(r->ir_intrfc);
+ ipxrtr_put(r);
+
+ return 0;
+}
+
+/*
+ * We use a normal struct rtentry for route handling
+ */
+static int ipxrtr_ioctl(unsigned int cmd, void *arg)
+{
+ struct rtentry rt; /* Use these to behave like 'other' stacks */
+ struct sockaddr_ipx *sg, *st;
+ int ret = -EFAULT;
+
+ if (copy_from_user(&rt, arg, sizeof(rt)))
+ goto out;
+
+ sg = (struct sockaddr_ipx *)&rt.rt_gateway;
+ st = (struct sockaddr_ipx *)&rt.rt_dst;
+
+ ret = -EINVAL;
+ if (!(rt.rt_flags & RTF_GATEWAY) || /* Direct routes are fixed */
+ sg->sipx_family != AF_IPX ||
+ st->sipx_family != AF_IPX)
+ goto out;
+
+ switch (cmd) {
+ case SIOCDELRT:
+ ret = ipxrtr_delete(st->sipx_network);
+ break;
+ case SIOCADDRT: {
+ struct ipx_route_definition f;
+ f.ipx_network = st->sipx_network;
+ f.ipx_router_network = sg->sipx_network;
+ memcpy(f.ipx_router_node, sg->sipx_node, IPX_NODE_LEN);
+ ret = ipxrtr_create(&f);
+ break;
+ }
+ }
+
+out: return ret;
+}
+
+static const char *ipx_frame_name(unsigned short frame)
+{
+ char* ret = "None";
+
+ switch (ntohs(frame)) {
+ case ETH_P_IPX: ret = "EtherII"; break;
+ case ETH_P_802_2: ret = "802.2"; break;
+ case ETH_P_SNAP: ret = "SNAP"; break;
+ case ETH_P_802_3: ret = "802.3"; break;
+ case ETH_P_TR_802_2: ret = "802.2TR"; break;
+ }
+
+ return ret;
+}
+
+static const char *ipx_device_name(ipx_interface *intrfc)
+{
+ return intrfc->if_internal ? "Internal" :
+ intrfc->if_dev ? intrfc->if_dev->name : "Unknown";
+}
+
+/* Called from proc fs */
+static int ipx_interface_get_info(char *buffer, char **start, off_t offset,
+ int length)
+{
+ ipx_interface *i;
+ off_t begin = 0, pos = 0;
+ int len = 0;
+
+ /* Theory.. Keep printing in the same place until we pass offset */
+
+ len += sprintf(buffer, "%-11s%-15s%-9s%-11s%s", "Network",
+ "Node_Address", "Primary", "Device", "Frame_Type");
+#ifdef IPX_REFCNT_DEBUG
+ len += sprintf(buffer + len, " refcnt");
+#endif
+ strcat(buffer + len++, "\n");
+ spin_lock_bh(&ipx_interfaces_lock);
+ for (i = ipx_interfaces; i; i = i->if_next) {
+ len += sprintf(buffer + len, "%08lX ",
+ (long unsigned int) ntohl(i->if_netnum));
+ len += sprintf(buffer + len, "%02X%02X%02X%02X%02X%02X ",
+ i->if_node[0], i->if_node[1], i->if_node[2],
+ i->if_node[3], i->if_node[4], i->if_node[5]);
+ len += sprintf(buffer + len, "%-9s", i == ipx_primary_net ?
+ "Yes" : "No");
+ len += sprintf(buffer + len, "%-11s", ipx_device_name(i));
+ len += sprintf(buffer + len, "%-9s",
+ ipx_frame_name(i->if_dlink_type));
+#ifdef IPX_REFCNT_DEBUG
+ len += sprintf(buffer + len, "%6d", atomic_read(&i->refcnt));
+#endif
+ strcat(buffer + len++, "\n");
+ /* Are we still dumping unwanted data then discard the record */
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0; /* Keep dumping into the buffer start */
+ begin = pos;
+ }
+ if (pos > offset + length) /* We have dumped enough */
+ break;
+ }
+ spin_unlock_bh(&ipx_interfaces_lock);
+
+ /* The data in question runs from begin to begin+len */
+ *start = buffer + (offset - begin); /* Start of wanted data */
+ len -= (offset - begin); /* Remove unwanted header data from length */
+ if (len > length)
+ len = length; /* Remove unwanted tail data from length */
+
+ return len;
+}
+
+static int ipx_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct sock *s;
+ ipx_interface *i;
+ off_t begin = 0, pos = 0;
+ int len = 0;
+
+ /* Theory.. Keep printing in the same place until we pass offset */
+
+#ifdef CONFIG_IPX_INTERN
+ len += sprintf(buffer, "%-28s%-28s%-10s%-10s%-7s%s\n", "Local_Address",
+#else
+ len += sprintf(buffer, "%-15s%-28s%-10s%-10s%-7s%s\n", "Local_Address",
+#endif /* CONFIG_IPX_INTERN */
+ "Remote_Address", "Tx_Queue", "Rx_Queue",
+ "State", "Uid");
+
+ spin_lock_bh(&ipx_interfaces_lock);
+ for (i = ipx_interfaces; i; i = i->if_next) {
+ ipxitf_hold(i);
+ spin_lock_bh(&i->if_sklist_lock);
+ for (s = i->if_sklist; s; s = s->next) {
+#ifdef CONFIG_IPX_INTERN
+ len += sprintf(buffer + len,
+ "%08lX:%02X%02X%02X%02X%02X%02X:%04X ",
+ (unsigned long) htonl(s->protinfo.af_ipx.intrfc->if_netnum),
+ s->protinfo.af_ipx.node[0],
+ s->protinfo.af_ipx.node[1],
+ s->protinfo.af_ipx.node[2],
+ s->protinfo.af_ipx.node[3],
+ s->protinfo.af_ipx.node[4],
+ s->protinfo.af_ipx.node[5],
+ htons(s->protinfo.af_ipx.port));
+#else
+ len += sprintf(buffer + len, "%08lX:%04X ",
+ (unsigned long) htonl(i->if_netnum),
+ htons(s->protinfo.af_ipx.port));
+#endif /* CONFIG_IPX_INTERN */
+ if (s->state != TCP_ESTABLISHED)
+ len += sprintf(buffer + len, "%-28s",
+ "Not_Connected");
+ else {
+ len += sprintf(buffer + len,
+ "%08lX:%02X%02X%02X%02X%02X%02X:%04X ",
+ (unsigned long) htonl(s->protinfo.af_ipx.dest_addr.net),
+ s->protinfo.af_ipx.dest_addr.node[0],
+ s->protinfo.af_ipx.dest_addr.node[1],
+ s->protinfo.af_ipx.dest_addr.node[2],
+ s->protinfo.af_ipx.dest_addr.node[3],
+ s->protinfo.af_ipx.dest_addr.node[4],
+ s->protinfo.af_ipx.dest_addr.node[5],
+ htons(s->protinfo.af_ipx.dest_addr.sock));
+ }
+
+ len += sprintf(buffer + len, "%08X %08X ",
+ atomic_read(&s->wmem_alloc),
+ atomic_read(&s->rmem_alloc));
+ len += sprintf(buffer + len, "%02X %03d\n",
+ s->state, SOCK_INODE(s->socket)->i_uid);
+
+ pos = begin + len;
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length) /* We have dumped enough */
+ break;
+ }
+ spin_unlock_bh(&i->if_sklist_lock);
+ ipxitf_put(i);
+ }
+ spin_unlock_bh(&ipx_interfaces_lock);
+
+ /* The data in question runs from begin to begin+len */
+ *start = buffer + offset - begin;
+ len -= (offset - begin);
+ if (len > length)
+ len = length;
+
+ return len;
+}
+
+static int ipx_rt_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ ipx_route *rt;
+ off_t begin = 0, pos = 0;
+ int len = 0;
+
+ len += sprintf(buffer, "%-11s%-13s%s\n",
+ "Network", "Router_Net", "Router_Node");
+ read_lock_bh(&ipx_routes_lock);
+ for (rt = ipx_routes; rt; rt = rt->ir_next) {
+ len += sprintf(buffer + len, "%08lX ",
+ (long unsigned int) ntohl(rt->ir_net));
+ if (rt->ir_routed) {
+ len += sprintf(buffer + len,
+ "%08lX %02X%02X%02X%02X%02X%02X\n",
+ (long unsigned int) ntohl(rt->ir_intrfc->if_netnum),
+ rt->ir_router_node[0], rt->ir_router_node[1],
+ rt->ir_router_node[2], rt->ir_router_node[3],
+ rt->ir_router_node[4], rt->ir_router_node[5]);
+ } else {
+ len += sprintf(buffer + len, "%-13s%s\n",
+ "Directly", "Connected");
+ }
+
+ pos = begin + len;
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ break;
+ }
+ read_unlock_bh(&ipx_routes_lock);
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+ if (len > length)
+ len = length;
+
+ return len;
+}
+
+/* Handling for system calls applied via the various interfaces to an IPX
+ * socket object. */
+
+static int ipx_setsockopt(struct socket *sock, int level, int optname,
+ char *optval, int optlen)
+{
+ struct sock *sk = sock->sk;
+ int opt;
+ int ret = -EINVAL;
+
+ if (optlen != sizeof(int))
+ goto out;
+
+ ret = -EFAULT;
+ if (get_user(opt, (unsigned int *)optval))
+ goto out;
+
+ ret = -ENOPROTOOPT;
+ if (!(level == SOL_IPX && optname == IPX_TYPE))
+ goto out;
+
+ sk->protinfo.af_ipx.type = opt;
+ ret = 0;
+out: return ret;
+}
+
+static int ipx_getsockopt(struct socket *sock, int level, int optname,
+ char *optval, int *optlen)
+{
+ struct sock *sk = sock->sk;
+ int val = 0;
+ int len;
+ int ret = -ENOPROTOOPT;
+
+ if (!(level == SOL_IPX && optname == IPX_TYPE))
+ goto out;
+
+ val = sk->protinfo.af_ipx.type;
+
+ ret = -EFAULT;
+ if (get_user(len, optlen))
+ goto out;
+
+ len = min_t(unsigned int, len, sizeof(int));
+ ret = -EINVAL;
+ if(len < 0)
+ goto out;
+
+ ret = -EFAULT;
+ if (put_user(len, optlen) || copy_to_user(optval, &val, len))
+ goto out;
+
+ ret = 0;
+out: return ret;
+}
+
+static int ipx_create(struct socket *sock, int protocol)
+{
+ int ret = -ESOCKTNOSUPPORT;
+ struct sock *sk;
+
+ switch (sock->type) {
+ case SOCK_DGRAM:
+ sk = sk_alloc(PF_IPX, GFP_KERNEL, 1);
+ ret = -ENOMEM;
+ if (!sk)
+ goto out;
+ sock->ops = &ipx_dgram_ops;
+ break;
+
+ case SOCK_SEQPACKET:
+ /*
+ * From this point on SPX sockets are handled
+ * by af_spx.c and the methods replaced.
+ */
+ if (spx_family_ops) {
+ ret = spx_family_ops->create(sock, protocol);
+ goto out;
+ }
+ /* Fall through if SPX is not loaded */
+ case SOCK_STREAM: /* Allow higher levels to piggyback */
+ default:
+ goto out;
+ }
+#ifdef IPX_REFCNT_DEBUG
+ atomic_inc(&ipx_sock_nr);
+ printk(KERN_DEBUG "IPX socket %p created, now we have %d alive\n", sk,
+ atomic_read(&ipx_sock_nr));
+#endif
+ sock_init_data(sock, sk);
+ sk->destruct = NULL;
+ sk->no_check = 1; /* Checksum off by default */
+
+ MOD_INC_USE_COUNT;
+ ret = 0;
+out: return ret;
+}
+
+static int ipx_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ if (!sk)
+ goto out;
+
+ if (!sk->dead)
+ sk->state_change(sk);
+
+ sk->dead = 1;
+ sock->sk = NULL;
+ ipx_destroy_socket(sk);
+
+ if (sock->type == SOCK_DGRAM)
+ MOD_DEC_USE_COUNT;
+
+out: return 0;
+}
+
+/* caller must hold a reference to intrfc */
+
+static unsigned short ipx_first_free_socketnum(ipx_interface *intrfc)
+{
+ unsigned short socketNum = intrfc->if_sknum;
+
+ spin_lock_bh(&intrfc->if_sklist_lock);
+
+ if (socketNum < IPX_MIN_EPHEMERAL_SOCKET)
+ socketNum = IPX_MIN_EPHEMERAL_SOCKET;
+
+ while (__ipxitf_find_socket(intrfc, ntohs(socketNum)))
+ if (socketNum > IPX_MAX_EPHEMERAL_SOCKET)
+ socketNum = IPX_MIN_EPHEMERAL_SOCKET;
+ else
+ socketNum++;
+
+ spin_unlock_bh(&intrfc->if_sklist_lock);
+ intrfc->if_sknum = socketNum;
+
+ return ntohs(socketNum);
+}
+
+static int ipx_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sock *sk = sock->sk;
+ ipx_interface *intrfc;
+ struct sockaddr_ipx *addr = (struct sockaddr_ipx *)uaddr;
+ int ret = -EINVAL;
+
+ if (!sk->zapped || addr_len != sizeof(struct sockaddr_ipx))
+ goto out;
+
+ intrfc = ipxitf_find_using_net(addr->sipx_network);
+ ret = -EADDRNOTAVAIL;
+ if (!intrfc)
+ goto out;
+
+ if (!addr->sipx_port) {
+ addr->sipx_port = ipx_first_free_socketnum(intrfc);
+ ret = -EINVAL;
+ if (!addr->sipx_port)
+ goto out_put;
+ }
+
+ /* protect IPX system stuff like routing/sap */
+ ret = -EACCES;
+ if (ntohs(addr->sipx_port) < IPX_MIN_EPHEMERAL_SOCKET &&
+ !capable(CAP_NET_ADMIN))
+ goto out_put;
+
+ sk->protinfo.af_ipx.port = addr->sipx_port;
+
+#ifdef CONFIG_IPX_INTERN
+ if (intrfc == ipx_internal_net) {
+ /* The source address is to be set explicitly if the
+ * socket is to be bound on the internal network. If a
+ * node number 0 was specified, the default is used.
+ */
+
+ ret = -EINVAL;
+ if (!memcmp(addr->sipx_node, ipx_broadcast_node, IPX_NODE_LEN))
+ goto out_put;
+ if (!memcmp(addr->sipx_node, ipx_this_node, IPX_NODE_LEN))
+ memcpy(sk->protinfo.af_ipx.node, intrfc->if_node,
+ IPX_NODE_LEN);
+ else
+ memcpy(sk->protinfo.af_ipx.node, addr->sipx_node,
+ IPX_NODE_LEN);
+
+ ret = -EADDRINUSE;
+ if (ipxitf_find_internal_socket(intrfc,
+ sk->protinfo.af_ipx.node,
+ sk->protinfo.af_ipx.port)) {
+ SOCK_DEBUG(sk,
+ "IPX: bind failed because port %X in use.\n",
+ ntohs((int)addr->sipx_port));
+ goto out_put;
+ }
+ } else {
+ /* Source addresses are easy. It must be our
+ * network:node pair for an interface routed to IPX
+ * with the ipx routing ioctl()
+ */
+
+ memcpy(sk->protinfo.af_ipx.node, intrfc->if_node,
+ IPX_NODE_LEN);
+
+ ret = -EADDRINUSE;
+ if (ipxitf_find_socket(intrfc, addr->sipx_port)) {
+ SOCK_DEBUG(sk,
+ "IPX: bind failed because port %X in use.\n",
+ ntohs((int)addr->sipx_port));
+ goto out_put;
+ }
+ }
+
+#else /* !def CONFIG_IPX_INTERN */
+
+ /* Source addresses are easy. It must be our network:node pair for
+ an interface routed to IPX with the ipx routing ioctl() */
+
+ ret = -EADDRINUSE;
+ if (ipxitf_find_socket(intrfc, addr->sipx_port)) {
+ SOCK_DEBUG(sk, "IPX: bind failed because port %X in use.\n",
+ ntohs((int)addr->sipx_port));
+ goto out_put;
+ }
+
+#endif /* CONFIG_IPX_INTERN */
+
+ ipxitf_insert_socket(intrfc, sk);
+ sk->zapped = 0;
+ SOCK_DEBUG(sk, "IPX: bound socket 0x%04X.\n", ntohs(addr->sipx_port) );
+
+ ret = 0;
+out_put:
+ ipxitf_put(intrfc);
+out: return ret;
+}
+
+static int ipx_connect(struct socket *sock, struct sockaddr *uaddr,
+ int addr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_ipx *addr;
+ int ret = -EINVAL;
+ ipx_route *rt;
+
+ sk->state = TCP_CLOSE;
+ sock->state = SS_UNCONNECTED;
+
+ if (addr_len != sizeof(*addr))
+ goto out;
+ addr = (struct sockaddr_ipx *)uaddr;
+
+ /* put the autobinding in */
+ if (!sk->protinfo.af_ipx.port) {
+ struct sockaddr_ipx uaddr;
+
+ uaddr.sipx_port = 0;
+ uaddr.sipx_network = 0;
+
+#ifdef CONFIG_IPX_INTERN
+ ret = -ENETDOWN;
+ if (!sk->protinfo.af_ipx.intrfc)
+ goto out; /* Someone zonked the iface */
+ memcpy(uaddr.sipx_node, sk->protinfo.af_ipx.intrfc->if_node,
+ IPX_NODE_LEN);
+#endif /* CONFIG_IPX_INTERN */
+
+ ret = ipx_bind(sock, (struct sockaddr *)&uaddr,
+ sizeof(struct sockaddr_ipx));
+ if (ret)
+ goto out;
+ }
+
+ /* We can either connect to primary network or somewhere
+ * we can route to */
+ rt = ipxrtr_lookup(addr->sipx_network);
+ ret = -ENETUNREACH;
+ if (!rt && !(!addr->sipx_network && ipx_primary_net))
+ goto out;
+
+ sk->protinfo.af_ipx.dest_addr.net = addr->sipx_network;
+ sk->protinfo.af_ipx.dest_addr.sock = addr->sipx_port;
+ memcpy(sk->protinfo.af_ipx.dest_addr.node,
+ addr->sipx_node, IPX_NODE_LEN);
+ sk->protinfo.af_ipx.type = addr->sipx_type;
+
+ if (sock->type == SOCK_DGRAM) {
+ sock->state = SS_CONNECTED;
+ sk->state = TCP_ESTABLISHED;
+ }
+
+ if (rt)
+ ipxrtr_put(rt);
+ ret = 0;
+out: return ret;
+}
+
+
+static int ipx_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *uaddr_len, int peer)
+{
+ ipx_address *addr;
+ struct sockaddr_ipx sipx;
+ struct sock *sk = sock->sk;
+ int ret;
+
+ *uaddr_len = sizeof(struct sockaddr_ipx);
+
+ if (peer) {
+ ret = -ENOTCONN;
+ if (sk->state != TCP_ESTABLISHED)
+ goto out;
+
+ addr = &sk->protinfo.af_ipx.dest_addr;
+ sipx.sipx_network = addr->net;
+ sipx.sipx_port = addr->sock;
+ memcpy(sipx.sipx_node, addr->node, IPX_NODE_LEN);
+ } else {
+ if (sk->protinfo.af_ipx.intrfc) {
+ sipx.sipx_network =
+ sk->protinfo.af_ipx.intrfc->if_netnum;
+#ifdef CONFIG_IPX_INTERN
+ memcpy(sipx.sipx_node, sk->protinfo.af_ipx.node,
+ IPX_NODE_LEN);
+#else
+ memcpy(sipx.sipx_node,
+ sk->protinfo.af_ipx.intrfc->if_node,
+ IPX_NODE_LEN);
+#endif /* CONFIG_IPX_INTERN */
+
+ } else {
+ sipx.sipx_network = 0;
+ memset(sipx.sipx_node, '\0', IPX_NODE_LEN);
+ }
+
+ sipx.sipx_port = sk->protinfo.af_ipx.port;
+ }
+
+ sipx.sipx_family = AF_IPX;
+ sipx.sipx_type = sk->protinfo.af_ipx.type;
+ memcpy(uaddr, &sipx, sizeof(sipx));
+
+ ret = 0;
+out: return ret;
+}
+
+int ipx_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
+{
+ /* NULL here for pt means the packet was looped back */
+ ipx_interface *intrfc;
+ struct ipxhdr *ipx;
+ u16 ipx_pktsize;
+ int ret = 0;
+
+ /* Not ours */
+ if (skb->pkt_type == PACKET_OTHERHOST)
+ goto drop;
+
+ if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
+ goto out;
+
+ ipx = skb->nh.ipxh;
+ ipx_pktsize = ntohs(ipx->ipx_pktsize);
+
+ /* Too small or invalid header? */
+ if (ipx_pktsize < sizeof(struct ipxhdr) || ipx_pktsize > skb->len)
+ goto drop;
+
+ if (ipx->ipx_checksum != IPX_NO_CHECKSUM &&
+ ipx->ipx_checksum != ipx_cksum(ipx, ipx_pktsize))
+ goto drop;
+
+ IPX_SKB_CB(skb)->ipx_tctrl = ipx->ipx_tctrl;
+ IPX_SKB_CB(skb)->ipx_dest_net = ipx->ipx_dest.net;
+ IPX_SKB_CB(skb)->ipx_source_net = ipx->ipx_source.net;
+
+ /* Determine what local ipx endpoint this is */
+ intrfc = ipxitf_find_using_phys(dev, pt->type);
+ if (!intrfc) {
+ if (ipxcfg_auto_create_interfaces &&
+ ntohl(IPX_SKB_CB(skb)->ipx_dest_net)) {
+ intrfc = ipxitf_auto_create(dev, pt->type);
+ if (intrfc)
+ ipxitf_hold(intrfc);
+ }
+
+ if (!intrfc) /* Not one of ours */
+ /* or invalid packet for auto creation */
+ goto drop;
+ }
+
+ ret = ipxitf_rcv(intrfc, skb);
+ ipxitf_put(intrfc);
+ goto out;
+drop: kfree_skb(skb);
+out: return ret;
+}
+
+static int ipx_sendmsg(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_ipx *usipx = (struct sockaddr_ipx *)msg->msg_name;
+ struct sockaddr_ipx local_sipx;
+ int ret = -EINVAL;
+ int flags = msg->msg_flags;
+
+ /* Socket gets bound below anyway */
+/* if (sk->zapped)
+ return -EIO; */ /* Socket not bound */
+ if (flags & ~MSG_DONTWAIT)
+ goto out;
+
+ if (usipx) {
+ if (!sk->protinfo.af_ipx.port) {
+ struct sockaddr_ipx uaddr;
+
+ uaddr.sipx_port = 0;
+ uaddr.sipx_network = 0;
+#ifdef CONFIG_IPX_INTERN
+ ret = -ENETDOWN;
+ if (!sk->protinfo.af_ipx.intrfc)
+ goto out; /* Someone zonked the iface */
+ memcpy(uaddr.sipx_node,
+ sk->protinfo.af_ipx.intrfc->if_node,
+ IPX_NODE_LEN);
+#endif
+ ret = ipx_bind(sock, (struct sockaddr *)&uaddr,
+ sizeof(struct sockaddr_ipx));
+ if (ret)
+ goto out;
+ }
+
+ ret = -EINVAL;
+ if (msg->msg_namelen < sizeof(*usipx) ||
+ usipx->sipx_family != AF_IPX)
+ goto out;
+ } else {
+ ret = -ENOTCONN;
+ if (sk->state != TCP_ESTABLISHED)
+ goto out;
+
+ usipx = &local_sipx;
+ usipx->sipx_family = AF_IPX;
+ usipx->sipx_type = sk->protinfo.af_ipx.type;
+ usipx->sipx_port = sk->protinfo.af_ipx.dest_addr.sock;
+ usipx->sipx_network = sk->protinfo.af_ipx.dest_addr.net;
+ memcpy(usipx->sipx_node, sk->protinfo.af_ipx.dest_addr.node,
+ IPX_NODE_LEN);
+ }
+
+ ret = ipxrtr_route_packet(sk, usipx, msg->msg_iov, len,
+ flags & MSG_DONTWAIT);
+ if (ret >= 0)
+ ret = len;
+out: return ret;
+}
+
+
+static int ipx_recvmsg(struct socket *sock, struct msghdr *msg, int size,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_ipx *sipx = (struct sockaddr_ipx *)msg->msg_name;
+ struct ipxhdr *ipx = NULL;
+ struct sk_buff *skb;
+ int copied, err;
+
+ /* put the autobinding in */
+ if (!sk->protinfo.af_ipx.port) {
+ struct sockaddr_ipx uaddr;
+
+ uaddr.sipx_port = 0;
+ uaddr.sipx_network = 0;
+
+#ifdef CONFIG_IPX_INTERN
+ err = -ENETDOWN;
+ if (!sk->protinfo.af_ipx.intrfc)
+ goto out; /* Someone zonked the iface */
+ memcpy(uaddr.sipx_node,
+ sk->protinfo.af_ipx.intrfc->if_node, IPX_NODE_LEN);
+#endif /* CONFIG_IPX_INTERN */
+
+ err = ipx_bind(sock, (struct sockaddr *)&uaddr,
+ sizeof(struct sockaddr_ipx));
+ if (err)
+ goto out;
+ }
+
+ err = -ENOTCONN;
+ if (sk->zapped)
+ goto out;
+
+ skb = skb_recv_datagram(sk, flags & ~MSG_DONTWAIT,
+ flags & MSG_DONTWAIT, &err);
+ if (!skb)
+ goto out;
+
+ ipx = skb->nh.ipxh;
+ copied = ntohs(ipx->ipx_pktsize) - sizeof(struct ipxhdr);
+ if (copied > size) {
+ copied = size;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+
+ err = skb_copy_datagram_iovec(skb, sizeof(struct ipxhdr), msg->msg_iov,
+ copied);
+ if (err)
+ goto out_free;
+ sk->stamp = skb->stamp;
+
+ msg->msg_namelen = sizeof(*sipx);
+
+ if (sipx) {
+ sipx->sipx_family = AF_IPX;
+ sipx->sipx_port = ipx->ipx_source.sock;
+ memcpy(sipx->sipx_node, ipx->ipx_source.node, IPX_NODE_LEN);
+ sipx->sipx_network = IPX_SKB_CB(skb)->ipx_source_net;
+ sipx->sipx_type = ipx->ipx_type;
+ }
+ err = copied;
+
+out_free:
+ skb_free_datagram(sk, skb);
+out: return err;
+}
+
+
+static int ipx_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ long amount = 0;
+ struct sock *sk = sock->sk;
+
+ switch (cmd) {
+ case TIOCOUTQ:
+ amount = sk->sndbuf - atomic_read(&sk->wmem_alloc);
+ if (amount < 0)
+ amount = 0;
+ return put_user(amount, (int *)arg);
+
+ case TIOCINQ: {
+ struct sk_buff *skb = skb_peek(&sk->receive_queue);
+ /* These two are safe on a single CPU system as only
+ * user tasks fiddle here */
+ if (skb)
+ amount = skb->len - sizeof(struct ipxhdr);
+ return put_user(amount, (int *)arg);
+ }
+
+ case SIOCADDRT:
+ case SIOCDELRT:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ return ipxrtr_ioctl(cmd, (void *)arg);
+
+ case SIOCSIFADDR:
+ case SIOCAIPXITFCRT:
+ case SIOCAIPXPRISLT:
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+
+ case SIOCGIFADDR:
+ return ipxitf_ioctl(cmd, (void *)arg);
+
+ case SIOCIPXCFGDATA:
+ return ipxcfg_get_config_data((void *)arg);
+
+ case SIOCIPXNCPCONN:
+ /*
+ * This socket wants to take care of the NCP connection
+ * handed to us in arg.
+ */
+ if (!capable(CAP_NET_ADMIN))
+ return -EPERM;
+ return get_user(sk->protinfo.af_ipx.ipx_ncp_conn,
+ (const unsigned short *)(arg));
+
+ case SIOCGSTAMP: {
+ int ret = -EINVAL;
+ if (sk) {
+ if (!sk->stamp.tv_sec)
+ return -ENOENT;
+ ret = -EFAULT;
+ if (!copy_to_user((void *)arg, &sk->stamp,
+ sizeof(struct timeval)))
+ ret = 0;
+ }
+
+ return ret;
+ }
+
+ case SIOCGIFDSTADDR:
+ case SIOCSIFDSTADDR:
+ case SIOCGIFBRDADDR:
+ case SIOCSIFBRDADDR:
+ case SIOCGIFNETMASK:
+ case SIOCSIFNETMASK:
+ return -EINVAL;
+
+ default:
+ return dev_ioctl(cmd,(void *) arg);
+ }
+
+ /*NOT REACHED*/
+ return 0;
+}
+
+/*
+ * SPX interface support
+ */
+
+int ipx_register_spx(struct proto_ops **p, struct net_proto_family *spx)
+{
+ if (spx_family_ops)
+ return -EBUSY;
+ cli();
+ MOD_INC_USE_COUNT;
+ *p = &ipx_dgram_ops;
+ spx_family_ops = spx;
+ sti();
+ return 0;
+}
+
+int ipx_unregister_spx(void)
+{
+ spx_family_ops = NULL;
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+/*
+ * Socket family declarations
+ */
+
+static struct net_proto_family ipx_family_ops = {
+ family: PF_IPX,
+ create: ipx_create,
+};
+
+static struct proto_ops SOCKOPS_WRAPPED(ipx_dgram_ops) = {
+ family: PF_IPX,
+
+ release: ipx_release,
+ bind: ipx_bind,
+ connect: ipx_connect,
+ socketpair: sock_no_socketpair,
+ accept: sock_no_accept,
+ getname: ipx_getname,
+ poll: datagram_poll,
+ ioctl: ipx_ioctl,
+ listen: sock_no_listen,
+ shutdown: sock_no_shutdown, /* FIXME: have to support shutdown */
+ setsockopt: ipx_setsockopt,
+ getsockopt: ipx_getsockopt,
+ sendmsg: ipx_sendmsg,
+ recvmsg: ipx_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+#include <linux/smp_lock.h>
+SOCKOPS_WRAP(ipx_dgram, PF_IPX);
+
+static struct packet_type ipx_8023_packet_type = {
+ type: __constant_htons(ETH_P_802_3),
+ func: ipx_rcv,
+ data: (void *) 1, /* yap, I understand shared skbs :-) */
+};
+
+static struct packet_type ipx_dix_packet_type = {
+ type: __constant_htons(ETH_P_IPX),
+ func: ipx_rcv,
+ data: (void *) 1, /* yap, I understand shared skbs :-) */
+};
+
+static struct notifier_block ipx_dev_notifier = {
+ notifier_call: ipxitf_device_event,
+};
+
+
+extern struct datalink_proto *make_EII_client(void);
+extern struct datalink_proto *make_8023_client(void);
+extern void destroy_EII_client(struct datalink_proto *);
+extern void destroy_8023_client(struct datalink_proto *);
+
+static unsigned char ipx_8022_type = 0xE0;
+static unsigned char ipx_snap_id[5] = { 0x0, 0x0, 0x0, 0x81, 0x37 };
+static char banner[] __initdata =
+ KERN_INFO "NET4: Linux IPX 0.47 for NET4.0\n"
+ KERN_INFO "IPX Portions Copyright (c) 1995 Caldera, Inc.\n" \
+ KERN_INFO "IPX Portions Copyright (c) 2000, 2001 Conectiva, Inc.\n";
+
+static int __init ipx_init(void)
+{
+ sock_register(&ipx_family_ops);
+
+ pEII_datalink = make_EII_client();
+ dev_add_pack(&ipx_dix_packet_type);
+
+ p8023_datalink = make_8023_client();
+ dev_add_pack(&ipx_8023_packet_type);
+
+ p8022_datalink = register_8022_client(ipx_8022_type, ipx_rcv);
+ if (!p8022_datalink)
+ printk(KERN_CRIT "IPX: Unable to register with 802.2\n");
+
+ pSNAP_datalink = register_snap_client(ipx_snap_id, ipx_rcv);
+ if (!pSNAP_datalink)
+ printk(KERN_CRIT "IPX: Unable to register with SNAP\n");
+
+ register_netdevice_notifier(&ipx_dev_notifier);
+ ipx_register_sysctl();
+#ifdef CONFIG_PROC_FS
+ proc_net_create("ipx", 0, ipx_get_info);
+ proc_net_create("ipx_interface", 0, ipx_interface_get_info);
+ proc_net_create("ipx_route", 0, ipx_rt_get_info);
+#endif
+ printk(banner);
+ return 0;
+}
+
+module_init(ipx_init);
+
+/* Higher layers need this info to prep tx pkts */
+int ipx_if_offset(unsigned long ipx_net_number)
+{
+ ipx_route *rt = ipxrtr_lookup(ipx_net_number);
+ int ret = -ENETUNREACH;
+
+ if (!rt)
+ goto out;
+ ret = rt->ir_intrfc->if_ipx_offset;
+ ipxrtr_put(rt);
+out: return ret;
+}
+
+/* Export symbols for higher layers */
+EXPORT_SYMBOL(ipxrtr_route_skb);
+EXPORT_SYMBOL(ipx_if_offset);
+EXPORT_SYMBOL(ipx_remove_socket);
+EXPORT_SYMBOL(ipx_register_spx);
+EXPORT_SYMBOL(ipx_unregister_spx);
+
+/* Note on MOD_{INC,DEC}_USE_COUNT:
+ *
+ * Use counts are incremented/decremented when
+ * sockets are created/deleted.
+ *
+ * Routes are always associated with an interface, and
+ * allocs/frees will remain properly accounted for by
+ * their associated interfaces.
+ *
+ * Ergo, before the ipx module can be removed, all IPX
+ * sockets be closed from user space.
+ */
+
+static void __exit ipx_proto_finito(void)
+{
+ /* no need to worry about having anything on the ipx_interfaces
+ * list, when a interface is created we increment the module
+ * usage count, so the module will only be unloaded when there
+ * are no more interfaces */
+
+ proc_net_remove("ipx_route");
+ proc_net_remove("ipx_interface");
+ proc_net_remove("ipx");
+ ipx_unregister_sysctl();
+
+ unregister_netdevice_notifier(&ipx_dev_notifier);
+
+ unregister_snap_client(ipx_snap_id);
+ pSNAP_datalink = NULL;
+
+ unregister_8022_client(ipx_8022_type);
+ p8022_datalink = NULL;
+
+ dev_remove_pack(&ipx_8023_packet_type);
+ destroy_8023_client(p8023_datalink);
+ p8023_datalink = NULL;
+
+ dev_remove_pack(&ipx_dix_packet_type);
+ destroy_EII_client(pEII_datalink);
+ pEII_datalink = NULL;
+
+ sock_unregister(ipx_family_ops.family);
+}
+
+module_exit(ipx_proto_finito);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/ipx/af_spx.c b/uClinux-2.4.31-uc0/net/ipx/af_spx.c
new file mode 100644
index 0000000..3b5bf66
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipx/af_spx.c
@@ -0,0 +1,937 @@
+/*
+ * This module implements the (SPP-derived) Sequenced Packet eXchange
+ * (SPX) protocol for Linux 2.1.X as specified in
+ * NetWare SPX Services Specification, Semantics and API
+ * Revision: 1.00
+ * Revision Date: February 9, 1993
+ *
+ * Developers:
+ * Jay Schulist <jschlst@samba.org>
+ * Jim Freeman <jfree@caldera.com>
+ *
+ * Changes:
+ * Alan Cox : Fixed an skb_unshare check for NULL
+ * that crashed it under load. Renamed and
+ * made static the ipx ops. Removed the hack
+ * ipx methods interface. Dropped AF_SPX - its
+ * the wrong abstraction.
+ * Eduardo Trapani : Added a check for the return value of
+ * ipx_if_offset that crashed sock_alloc_send_skb.
+ * Added spx_datagram_poll() so that select()
+ * works now on SPX sockets. Added updating
+ * of the alloc count to follow rmt_seq.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * None of the authors or maintainers or their employers admit
+ * liability nor provide warranty for any of this software.
+ * This material is provided "as is" and at no charge.
+ */
+
+#include <linux/module.h>
+#include <net/ipx.h>
+#include <net/spx.h>
+#include <net/sock.h>
+#include <asm/byteorder.h>
+#include <asm/uaccess.h>
+#include <linux/uio.h>
+#include <linux/unistd.h>
+#include <linux/poll.h>
+
+static struct proto_ops *ipx_operations;
+static struct proto_ops spx_ops;
+static __u16 connids;
+
+/* Functions needed for SPX connection start up */
+static int spx_transmit(struct sock *sk,struct sk_buff *skb,int type,int len);
+static void spx_retransmit(unsigned long data);
+static void spx_watchdog(unsigned long data);
+void spx_rcv(struct sock *sk, int bytes);
+
+extern void ipx_remove_socket(struct sock *sk);
+
+/* Datagram poll: the same code as datagram_poll() in net/core
+ but the right spx buffers are looked at and
+ there is no question on the type of the socket
+ */
+static unsigned int spx_datagram_poll(struct file * file, struct socket *sock, poll_table *wait)
+{
+ struct sock *sk = sock->sk;
+ struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
+ unsigned int mask;
+
+ poll_wait(file, sk->sleep, wait);
+ mask = 0;
+
+ /* exceptional events? */
+ if (sk->err || !skb_queue_empty(&sk->error_queue))
+ mask |= POLLERR;
+ if (sk->shutdown & RCV_SHUTDOWN)
+ mask |= POLLHUP;
+
+ /* readable? */
+ if (!skb_queue_empty(&pdata->rcv_queue))
+ mask |= POLLIN | POLLRDNORM;
+
+ /* Need to check for termination and startup */
+ if (sk->state==TCP_CLOSE)
+ mask |= POLLHUP;
+ /* connection hasn't started yet? */
+ if (sk->state == TCP_SYN_SENT)
+ return mask;
+
+ /* writable? */
+ if (sock_writeable(sk))
+ mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
+ else
+ set_bit(SOCK_ASYNC_NOSPACE,&sk->socket->flags);
+
+ return mask;
+}
+
+/* Create the SPX specific data */
+static int spx_sock_init(struct sock *sk)
+{
+ struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
+
+ pdata->state = SPX_CLOSED;
+ pdata->sequence = 0;
+ pdata->acknowledge = 0;
+ pdata->source_connid = htons(connids);
+ pdata->rmt_seq = 0;
+ connids++;
+
+ pdata->owner = (void *)sk;
+ pdata->sndbuf = sk->sndbuf;
+
+ pdata->watchdog.function = spx_watchdog;
+ pdata->watchdog.data = (unsigned long)sk;
+ pdata->wd_interval = VERIFY_TIMEOUT;
+ pdata->retransmit.function = spx_retransmit;
+ pdata->retransmit.data = (unsigned long)sk;
+ pdata->retransmits = 0;
+ pdata->retries = 0;
+ pdata->max_retries = RETRY_COUNT;
+
+ skb_queue_head_init(&pdata->rcv_queue);
+ skb_queue_head_init(&pdata->transmit_queue);
+ skb_queue_head_init(&pdata->retransmit_queue);
+
+ return (0);
+}
+
+static int spx_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+
+ /*
+ * Called on connection receive so cannot be GFP_KERNEL
+ */
+
+ sk = sk_alloc(PF_IPX, GFP_ATOMIC, 1);
+ if(sk == NULL)
+ return (-ENOMEM);
+
+ switch(sock->type)
+ {
+ case SOCK_SEQPACKET:
+ sock->ops = &spx_ops;
+ break;
+ default:
+ sk_free(sk);
+ return (-ESOCKTNOSUPPORT);
+ }
+
+ sock_init_data(sock, sk);
+ spx_sock_init(sk);
+ sk->data_ready = spx_rcv;
+ sk->destruct = NULL;
+ sk->no_check = 1;
+
+ MOD_INC_USE_COUNT;
+
+ return (0);
+}
+
+
+void spx_close_socket(struct sock *sk)
+{
+ struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
+
+ pdata->state = SPX_CLOSED;
+ sk->state = TCP_CLOSE;
+ del_timer(&pdata->retransmit);
+ del_timer(&pdata->watchdog);
+}
+
+void spx_destroy_socket(struct sock *sk)
+{
+ struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
+ struct sk_buff *skb;
+
+ ipx_remove_socket(sk);
+ while((skb = skb_dequeue(&sk->receive_queue)) != NULL)
+ kfree_skb(skb);
+ while((skb = skb_dequeue(&pdata->transmit_queue)) != NULL)
+ kfree_skb(skb);
+ while((skb = skb_dequeue(&pdata->retransmit_queue)) != NULL)
+ kfree_skb(skb);
+ while((skb = skb_dequeue(&pdata->rcv_queue)) != NULL)
+ kfree_skb(skb);
+
+ sk_free(sk);
+ MOD_DEC_USE_COUNT;
+}
+
+/* Release an SPX socket */
+static int spx_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
+
+ if(sk == NULL)
+ return (0);
+ if(!sk->dead)
+ sk->state_change(sk);
+ sk->dead = 1;
+
+ if(pdata->state != SPX_CLOSED)
+ {
+ spx_transmit(sk, NULL, DISCON, 0);
+ spx_close_socket(sk);
+ }
+
+ sock->sk = NULL;
+ sk->socket = NULL;
+ spx_destroy_socket(sk);
+
+ return (0);
+}
+
+/* Move a socket into listening state. */
+static int spx_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+
+ if(sock->state != SS_UNCONNECTED)
+ return (-EINVAL);
+ if(sock->type != SOCK_SEQPACKET)
+ return (-EOPNOTSUPP);
+ if(sk->zapped != 0)
+ return (-EAGAIN);
+
+ sk->max_ack_backlog = backlog;
+ if(sk->state != TCP_LISTEN)
+ {
+ sk->ack_backlog = 0;
+ sk->state = TCP_LISTEN;
+ }
+ sk->socket->flags |= __SO_ACCEPTCON;
+
+ return (0);
+}
+
+/* Accept a pending SPX connection */
+static int spx_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+ struct sock *sk;
+ struct sock *newsk;
+ struct sk_buff *skb;
+ int err;
+
+ if(sock->sk == NULL)
+ return (-EINVAL);
+ sk = sock->sk;
+
+ if((sock->state != SS_UNCONNECTED) || !(sock->flags & __SO_ACCEPTCON))
+ return (-EINVAL);
+ if(sock->type != SOCK_SEQPACKET)
+ return (-EOPNOTSUPP);
+ if(sk->state != TCP_LISTEN)
+ return (-EINVAL);
+
+ cli();
+ do {
+ skb = skb_dequeue(&sk->receive_queue);
+ if(skb == NULL)
+ {
+ if(flags & O_NONBLOCK)
+ {
+ sti();
+ return (-EWOULDBLOCK);
+ }
+ interruptible_sleep_on(sk->sleep);
+ if(signal_pending(current))
+ {
+ sti();
+ return (-ERESTARTSYS);
+ }
+ }
+ } while (skb == NULL);
+
+ newsk = skb->sk;
+ newsk->pair = NULL;
+ sti();
+
+ err = spx_transmit(newsk, skb, CONACK, 0); /* Connection ACK */
+ if(err)
+ return (err);
+
+ /* Now attach up the new socket */
+ sock->sk = NULL;
+ sk->ack_backlog--;
+ newsock->sk = newsk;
+ newsk->state = TCP_ESTABLISHED;
+ newsk->protinfo.af_ipx.dest_addr = newsk->tp_pinfo.af_spx.dest_addr;
+
+ return (0);
+}
+
+/* Build a connection to an SPX socket */
+static int spx_connect(struct socket *sock, struct sockaddr *uaddr,
+ int addr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
+ struct sockaddr_ipx src;
+ struct sk_buff *skb;
+ int size, err;
+
+ size = sizeof(src);
+ err = ipx_operations->getname(sock, (struct sockaddr *)&src, &size, 0);
+ if(err)
+ return (err);
+
+ pdata->source_addr.net = src.sipx_network;
+ memcpy(pdata->source_addr.node, src.sipx_node, IPX_NODE_LEN);
+ pdata->source_addr.sock = (unsigned short)src.sipx_port;
+
+ err = ipx_operations->connect(sock, uaddr, addr_len, flags);
+ if(err)
+ return (err);
+
+ pdata->dest_addr = sk->protinfo.af_ipx.dest_addr;
+ pdata->state = SPX_CONNECTING;
+ sock->state = SS_CONNECTING;
+ sk->state = TCP_SYN_SENT;
+
+ /* Send Connection request */
+ err = spx_transmit(sk, NULL, CONREQ, 0);
+ if(err)
+ return (err);
+
+ cli();
+ do {
+ skb = skb_dequeue(&sk->receive_queue);
+ if(skb == NULL)
+ {
+ if(flags & O_NONBLOCK)
+ {
+ sti();
+ return (-EWOULDBLOCK);
+ }
+ interruptible_sleep_on(sk->sleep);
+ if(signal_pending(current))
+ {
+ sti();
+ return (-ERESTARTSYS);
+ }
+ }
+ } while (skb == NULL);
+
+ if(pdata->state == SPX_CLOSED)
+ {
+ sti();
+ del_timer(&pdata->watchdog);
+ return (-ETIMEDOUT);
+ }
+
+ sock->state = SS_CONNECTED;
+ sk->state = TCP_ESTABLISHED;
+ kfree_skb(skb);
+ sti();
+
+ return (0);
+}
+
+/*
+ * Calculate the timeout for a packet. Thankfully SPX has a large
+ * fudge factor (3/4 secs) and does not pay much attention to RTT.
+ * As we simply have a default retry time of 1*HZ and a max retry
+ * time of 5*HZ. Between those values we increase the timeout based
+ * on the number of retransmit tries.
+ *
+ * FixMe: This is quite fake, but will work for now. (JS)
+ */
+static inline unsigned long spx_calc_rtt(int tries)
+{
+ if(tries < 1)
+ return (RETRY_TIME);
+ if(tries > 5)
+ return (MAX_RETRY_DELAY);
+ return (tries * HZ);
+}
+
+static int spx_route_skb(struct spx_opt *pdata, struct sk_buff *skb, int type)
+{
+ struct sk_buff *skb2;
+ int err = 0;
+
+ skb = skb_unshare(skb, GFP_ATOMIC);
+ if(skb == NULL)
+ return (-ENOBUFS);
+
+ switch(type)
+ {
+ case (CONREQ):
+ case (DATA):
+ if(!skb_queue_empty(&pdata->retransmit_queue))
+ {
+ skb_queue_tail(&pdata->transmit_queue, skb);
+ return 0;
+ }
+
+ case (TQUEUE):
+ pdata->retransmit.expires = jiffies + spx_calc_rtt(0);
+ add_timer(&pdata->retransmit);
+
+ skb2 = skb_clone(skb, GFP_NOIO);
+ if(skb2 == NULL)
+ return -ENOBUFS;
+ skb_queue_tail(&pdata->retransmit_queue, skb2);
+
+ case (ACK):
+ case (CONACK):
+ case (WDREQ):
+ case (WDACK):
+ case (DISCON):
+ case (DISACK):
+ case (RETRAN):
+ default:
+ /* Send data */
+ err = ipxrtr_route_skb(skb);
+ if(err)
+ kfree_skb(skb);
+ }
+
+ return (err);
+}
+
+/* SPX packet transmit engine */
+static int spx_transmit(struct sock *sk, struct sk_buff *skb, int type, int len)
+{
+ struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
+ struct ipxspxhdr *ipxh;
+ unsigned long flags;
+ int err;
+
+ if(skb == NULL)
+ {
+ int offset = ipx_if_offset(pdata->dest_addr.net);
+ int size = offset + sizeof(struct ipxspxhdr);
+
+ if (offset < 0) /* ENETUNREACH */
+ return(-ENETUNREACH);
+
+ save_flags(flags);
+ cli();
+ skb = sock_alloc_send_skb(sk, size, 0, &err);
+ if(skb == NULL) {
+ restore_flags(flags);
+ return (-ENOMEM);
+ }
+ skb_reserve(skb, offset);
+ skb->h.raw = skb->nh.raw = skb_put(skb,sizeof(struct ipxspxhdr));
+ restore_flags(flags);
+ }
+
+ /* IPX header */
+ ipxh = (struct ipxspxhdr *)skb->nh.raw;
+ ipxh->ipx.ipx_checksum = 0xFFFF;
+ ipxh->ipx.ipx_pktsize = htons(SPX_SYS_PKT_LEN);
+ ipxh->ipx.ipx_tctrl = 0;
+ ipxh->ipx.ipx_type = IPX_TYPE_SPX;
+ ipxh->ipx.ipx_dest = pdata->dest_addr;
+ ipxh->ipx.ipx_source = pdata->source_addr;
+
+ /* SPX header */
+ ipxh->spx.dtype = 0;
+ ipxh->spx.sequence = htons(pdata->sequence);
+ ipxh->spx.ackseq = htons(pdata->rmt_seq);
+ ipxh->spx.sconn = pdata->source_connid;
+ ipxh->spx.dconn = pdata->dest_connid;
+ ipxh->spx.allocseq = htons(pdata->alloc);
+
+ /* Reset/Set WD timer */
+ mod_timer(&pdata->watchdog, jiffies+VERIFY_TIMEOUT);
+
+ switch(type)
+ {
+ case (DATA): /* Data */
+ ipxh->ipx.ipx_pktsize = htons(SPX_SYS_PKT_LEN + len);
+ ipxh->spx.cctl = (CCTL_ACK | CCTL_EOM);
+ pdata->sequence++;
+ break;
+
+ case (ACK): /* ACK */
+ pdata->rmt_seq++;
+ case (WDACK): /* WD ACK */
+ case (CONACK): /* Connection ACK */
+ ipxh->spx.cctl = CCTL_SYS;
+ ipxh->spx.ackseq = htons(pdata->rmt_seq);
+ break;
+
+ case (CONREQ): /* Connection Request */
+ del_timer(&pdata->watchdog);
+ case (WDREQ): /* WD Request */
+ pdata->source_connid = htons(connids++);
+ pdata->dest_connid = 0xFFFF;
+ pdata->alloc = 3 + pdata->rmt_seq;
+ ipxh->spx.cctl = (CCTL_ACK | CCTL_SYS);
+ ipxh->spx.sconn = pdata->source_connid;
+ ipxh->spx.dconn = pdata->dest_connid;
+ ipxh->spx.allocseq = htons(pdata->alloc);
+ break;
+
+ case (DISCON): /* Informed Disconnect */
+ ipxh->spx.cctl = CCTL_ACK;
+ ipxh->spx.dtype = SPX_DTYPE_ECONN;
+ break;
+
+ case (DISACK): /* Informed Disconnect ACK */
+ ipxh->spx.cctl = 0;
+ ipxh->spx.dtype = SPX_DTYPE_ECACK;
+ ipxh->spx.sequence = 0;
+ ipxh->spx.ackseq = htons(pdata->rmt_seq++);
+ break;
+
+ default:
+ return (-EOPNOTSUPP);
+ }
+
+ /* Send data */
+ return (spx_route_skb(pdata, skb, type));
+}
+
+/* Check the state of the connection and send a WD request if needed. */
+static void spx_watchdog(unsigned long data)
+{
+ struct sock *sk = (struct sock*)data;
+ struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
+
+ del_timer(&pdata->watchdog);
+ if(pdata->state == SPX_CLOSED)
+ return;
+ if(pdata->retries > pdata->max_retries)
+ {
+ spx_close_socket(sk); /* Unilateral Abort */
+ return;
+ }
+
+ /* Send WD request */
+ spx_transmit(sk, NULL, WDREQ, 0);
+ pdata->retries++;
+
+ return;
+}
+
+static void spx_retransmit(unsigned long data)
+{
+ struct sock *sk = (struct sock*)data;
+ struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
+ struct sk_buff *skb;
+ unsigned long flags;
+ int err;
+
+ del_timer(&pdata->retransmit);
+ if(pdata->state == SPX_CLOSED)
+ return;
+ if(pdata->retransmits > RETRY_COUNT)
+ {
+ spx_close_socket(sk); /* Unilateral Abort */
+ return;
+ }
+
+ /* Need to leave skb on the queue, aye the fear */
+ save_flags(flags);
+ cli();
+ skb = skb_peek(&pdata->retransmit_queue);
+ if(skb_cloned(skb))
+ skb = skb_copy(skb, GFP_ATOMIC);
+ else
+ skb = skb_clone(skb, GFP_ATOMIC);
+ restore_flags(flags);
+
+ pdata->retransmit.expires = jiffies + spx_calc_rtt(pdata->retransmits);
+ add_timer(&pdata->retransmit);
+
+ err = spx_route_skb(pdata, skb, RETRAN);
+ pdata->retransmits++;
+
+ return;
+}
+
+/* Check packet for retransmission, ConReqAck aware */
+static int spx_retransmit_chk(struct spx_opt *pdata, int ackseq, int type)
+{
+ struct ipxspxhdr *ipxh;
+ struct sk_buff *skb;
+
+ skb = skb_dequeue(&pdata->retransmit_queue);
+ if(!skb)
+ return (-ENOENT);
+
+ /* Check Data/ACK seq */
+ switch(type)
+ {
+ case ACK: /* Check Sequence, Should == 1 */
+ ipxh = (struct ipxspxhdr *)skb->nh.raw;
+ if(!(ntohs(ipxh->spx.sequence) - htons(ackseq)))
+ break;
+
+ case CONACK:
+ del_timer(&pdata->retransmit);
+ pdata->retransmits = 0;
+ kfree_skb(skb);
+ if(skb_queue_empty(&pdata->retransmit_queue))
+ {
+ skb = skb_dequeue(&pdata->transmit_queue);
+ if(skb != NULL)
+ spx_route_skb(pdata, skb, TQUEUE);
+ }
+ return (0);
+ }
+
+ skb_queue_head(&pdata->retransmit_queue, skb);
+ return (-1);
+}
+
+/* SPX packet receive engine */
+void spx_rcv(struct sock *sk, int bytes)
+{
+ struct sk_buff *skb;
+ struct ipxspxhdr *ipxh;
+ struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
+
+ skb = skb_dequeue(&sk->receive_queue);
+ if(skb == NULL)
+ return;
+ ipxh = (struct ipxspxhdr *)skb->nh.raw;
+
+ /* Can't receive on a closed connection */
+ if((pdata->state == SPX_CLOSED) && (ipxh->spx.sequence != 0))
+ goto toss_skb;
+ if(ntohs(ipxh->ipx.ipx_pktsize) < SPX_SYS_PKT_LEN)
+ goto toss_skb;
+ if(ipxh->ipx.ipx_type != IPX_TYPE_SPX)
+ goto toss_skb;
+ if(ntohs(ipxh->spx.ackseq) > pdata->sequence)
+ goto toss_skb;
+
+ /* Reset WD timer on any received packet */
+ del_timer(&pdata->watchdog);
+ pdata->retries = 0;
+ pdata->watchdog.expires = jiffies + ABORT_TIMEOUT;
+ add_timer(&pdata->watchdog);
+
+ switch(ipxh->spx.cctl)
+ {
+ case (CCTL_SYS | CCTL_ACK):
+ if((ipxh->spx.sequence == 0) /* ConReq */
+ && (ipxh->spx.ackseq == 0)
+ && (ipxh->spx.dconn == 0xFFFF))
+ {
+ pdata->state = SPX_CONNECTED;
+ pdata->dest_addr = ipxh->ipx.ipx_source;
+ pdata->source_addr = ipxh->ipx.ipx_dest;
+ pdata->dest_connid = ipxh->spx.sconn;
+ pdata->alloc = 3 + ntohs(ipxh->spx.sequence);
+
+ skb_queue_tail(&sk->receive_queue, skb);
+ wake_up_interruptible(sk->sleep);
+ }
+ else /* WD Request */
+ spx_transmit(sk, skb, WDACK, 0);
+ goto finish;
+
+ case CCTL_SYS: /* ACK */
+ if((ipxh->spx.dtype == 0) /* ConReq ACK */
+ && (ipxh->spx.sconn != 0xFFFF)
+ && (ipxh->spx.dconn != 0xFFFF)
+ && (ipxh->spx.sequence == 0)
+ && (ipxh->spx.ackseq == 0)
+ && (pdata->state != SPX_CONNECTED))
+ {
+ pdata->state = SPX_CONNECTED;
+ pdata->dest_connid = ipxh->spx.sconn;
+
+ if(spx_retransmit_chk(pdata, 0, CONACK) < 0)
+ goto toss_skb;
+
+ skb_queue_tail(&sk->receive_queue, skb);
+ wake_up_interruptible(sk->sleep);
+ goto finish;
+ }
+
+ spx_retransmit_chk(pdata, ipxh->spx.ackseq, ACK);
+ goto toss_skb;
+
+ case (CCTL_ACK):
+ /* Informed Disconnect */
+ if(ipxh->spx.dtype == SPX_DTYPE_ECONN)
+ {
+
+ spx_transmit(sk, skb, DISACK, 0);
+ spx_close_socket(sk);
+ goto finish;
+ }
+ /* Fall through */
+
+ default:
+ if(ntohs(ipxh->spx.sequence) == pdata->rmt_seq)
+ {
+ pdata->rmt_seq = ntohs(ipxh->spx.sequence);
+ pdata->rmt_ack = ntohs(ipxh->spx.ackseq);
+ pdata->alloc = pdata->rmt_seq + 3;
+ if(pdata->rmt_ack > 0 || pdata->rmt_ack == 0)
+ spx_retransmit_chk(pdata,pdata->rmt_ack, ACK);
+
+ skb_queue_tail(&pdata->rcv_queue, skb);
+ wake_up_interruptible(sk->sleep);
+ if(ipxh->spx.cctl&CCTL_ACK)
+ spx_transmit(sk, NULL, ACK, 0);
+ goto finish;
+ }
+
+ if(ipxh->spx.dtype == SPX_DTYPE_ECACK)
+ {
+ if(pdata->state != SPX_CLOSED)
+ spx_close_socket(sk);
+ goto toss_skb;
+ }
+ }
+
+toss_skb: /* Catch All */
+ kfree_skb(skb);
+finish:
+ return;
+}
+
+/* Get message/packet data from user-land */
+static int spx_sendmsg(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ int flags = msg->msg_flags;
+ struct sk_buff *skb;
+ int err, offset, size;
+
+ if(len > 534)
+ return (-EMSGSIZE);
+ if(sk->zapped)
+ return (-ENOTCONN); /* Socket not bound */
+ if(flags&~MSG_DONTWAIT)
+ return (-EINVAL);
+
+ offset = ipx_if_offset(sk->tp_pinfo.af_spx.dest_addr.net);
+ size = offset + sizeof(struct ipxspxhdr) + len;
+
+ cli();
+ skb = sock_alloc_send_skb(sk, size, flags&MSG_DONTWAIT, &err);
+ sti();
+ if(skb == NULL)
+ return (err);
+
+ skb->sk = sk;
+ skb_reserve(skb, offset);
+ skb->h.raw = skb->nh.raw = skb_put(skb, sizeof(struct ipxspxhdr));
+
+ err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
+ if(err)
+ {
+ kfree_skb(skb);
+ return (-EFAULT);
+ }
+
+ err = spx_transmit(sk, skb, DATA, len);
+ if(err)
+ return (-EAGAIN);
+
+ return (len);
+}
+
+/* Send message/packet data to user-land */
+static int spx_recvmsg(struct socket *sock, struct msghdr *msg, int size,
+ int flags, struct scm_cookie *scm)
+{
+ struct sk_buff *skb;
+ struct ipxspxhdr *ispxh;
+ struct sock *sk = sock->sk;
+ struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
+ struct sockaddr_ipx *sipx = (struct sockaddr_ipx *)msg->msg_name;
+ int copied, err;
+
+ if(sk->zapped)
+ return (-ENOTCONN); /* Socket not bound */
+
+ lock_sock(sk);
+restart:
+ while(skb_queue_empty(&pdata->rcv_queue)) /* No data */
+ {
+ /* Socket errors? */
+ err = sock_error(sk);
+ if(err)
+ return (err);
+
+ /* Socket shut down? */
+ if(sk->shutdown & RCV_SHUTDOWN)
+ return (-ESHUTDOWN);
+
+ /* handle signals */
+ if(signal_pending(current))
+ return (-ERESTARTSYS);
+
+ /* User doesn't want to wait */
+ if(flags&MSG_DONTWAIT)
+ return (-EAGAIN);
+
+ release_sock(sk);
+ save_flags(flags);
+ cli();
+ if(skb_peek(&pdata->rcv_queue) == NULL)
+ interruptible_sleep_on(sk->sleep);
+ restore_flags(flags);
+ lock_sock(sk);
+ }
+
+ skb = skb_dequeue(&pdata->rcv_queue);
+ if(skb == NULL)
+ goto restart;
+
+ ispxh = (struct ipxspxhdr *)skb->nh.raw;
+ copied = ntohs(ispxh->ipx.ipx_pktsize) - SPX_SYS_PKT_LEN;
+ if(copied > size)
+ {
+ copied = size;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+
+ err = memcpy_toiovec(msg->msg_iov, skb->nh.raw+SPX_SYS_PKT_LEN, copied);
+ if(err)
+ return (-EFAULT);
+
+ msg->msg_namelen = sizeof(*sipx);
+ if(sipx)
+ {
+ sipx->sipx_family = AF_IPX;
+ sipx->sipx_port = ispxh->ipx.ipx_source.sock;
+ memcpy(sipx->sipx_node,ispxh->ipx.ipx_source.node,IPX_NODE_LEN);
+ sipx->sipx_network = ispxh->ipx.ipx_source.net;
+ sipx->sipx_type = ispxh->ipx.ipx_type;
+ }
+ kfree_skb(skb);
+ release_sock(sk);
+
+ return (copied);
+}
+
+/*
+ * Functions which just wrap their IPX cousins
+ */
+
+static int spx_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ int err;
+ err = ipx_operations->bind(sock, uaddr, addr_len);
+ return (err);
+}
+
+static int spx_getname (struct socket *sock, struct sockaddr *uaddr,
+ int *usockaddr_len, int peer)
+{
+ int err;
+ err = ipx_operations->getname(sock, uaddr, usockaddr_len, peer);
+ return (err);
+}
+
+static int spx_ioctl (struct socket *sock, unsigned int cmd,
+ unsigned long arg)
+{
+ int err;
+ err = ipx_operations->ioctl(sock, cmd, arg);
+ return (err);
+}
+
+static int spx_setsockopt(struct socket *sock, int level, int optname,
+ char *optval, int optlen)
+{
+ int err;
+ err = ipx_operations->setsockopt(sock, level, optname, optval, optlen);
+ return (err);
+}
+
+static int spx_getsockopt(struct socket *sock, int level, int optname,
+ char *optval, int *optlen)
+{
+ int err;
+ err = ipx_operations->getsockopt(sock, level, optname, optval, optlen);
+ return (err);
+}
+
+static struct proto_ops SOCKOPS_WRAPPED(spx_ops) = {
+ family: PF_IPX,
+
+ release: spx_release,
+ bind: spx_bind,
+ connect: spx_connect,
+ socketpair: sock_no_socketpair,
+ accept: spx_accept,
+ getname: spx_getname,
+ poll: spx_datagram_poll,
+ ioctl: spx_ioctl,
+ listen: spx_listen,
+ shutdown: sock_no_shutdown,
+ setsockopt: spx_setsockopt,
+ getsockopt: spx_getsockopt,
+ sendmsg: spx_sendmsg,
+ recvmsg: spx_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+#include <linux/smp_lock.h>
+SOCKOPS_WRAP(spx, PF_IPX);
+
+static struct net_proto_family spx_family_ops = {
+ family: PF_IPX,
+ create: spx_create,
+};
+
+static char banner[] __initdata = KERN_INFO "NET4: Sequenced Packet eXchange (SPX) 0.02 for Linux NET4.0\n";
+
+static int __init spx_proto_init(void)
+{
+ int error;
+
+ connids = (__u16)jiffies; /* initalize random */
+
+ error = ipx_register_spx(&ipx_operations, &spx_family_ops);
+ if (error)
+ printk(KERN_ERR "SPX: unable to register with IPX.\n");
+
+ /* route socket(PF_IPX, SOCK_SEQPACKET) calls through spx_create() */
+
+ printk(banner);
+ return 0;
+}
+module_init(spx_proto_init);
+
+static void __exit spx_proto_finito(void)
+{
+ ipx_unregister_spx();
+ return;
+}
+module_exit(spx_proto_finito);
diff --git a/uClinux-2.4.31-uc0/net/ipx/sysctl_net_ipx.c b/uClinux-2.4.31-uc0/net/ipx/sysctl_net_ipx.c
new file mode 100644
index 0000000..2de4952
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/ipx/sysctl_net_ipx.c
@@ -0,0 +1,47 @@
+/* -*- linux-c -*-
+ * sysctl_net_ipx.c: sysctl interface to net IPX subsystem.
+ *
+ * Begun April 1, 1996, Mike Shaver.
+ * Added /proc/sys/net/ipx directory entry (empty =) ). [MS]
+ * Added /proc/sys/net/ipx/ipx_pprop_broadcasting - acme March 4, 2001
+ */
+
+#include <linux/config.h>
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+
+#ifndef CONFIG_SYSCTL
+#error This file should not be compiled without CONFIG_SYSCTL defined
+#endif
+
+/* From af_ipx.c */
+extern int sysctl_ipx_pprop_broadcasting;
+
+ctl_table ipx_table[] = {
+ { NET_IPX_PPROP_BROADCASTING, "ipx_pprop_broadcasting",
+ &sysctl_ipx_pprop_broadcasting, sizeof(int), 0644, NULL,
+ &proc_dointvec },
+ { 0 }
+};
+
+static ctl_table ipx_dir_table[] = {
+ { NET_IPX, "ipx", NULL, 0, 0555, ipx_table },
+ { 0 }
+};
+
+static ctl_table ipx_root_table[] = {
+ { CTL_NET, "net", NULL, 0, 0555, ipx_dir_table },
+ { 0 }
+};
+
+static struct ctl_table_header *ipx_table_header;
+
+void ipx_register_sysctl(void)
+{
+ ipx_table_header = register_sysctl_table(ipx_root_table, 1);
+}
+
+void ipx_unregister_sysctl(void)
+{
+ unregister_sysctl_table(ipx_table_header);
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/Config.in b/uClinux-2.4.31-uc0/net/irda/Config.in
new file mode 100644
index 0000000..dbb0fa3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/Config.in
@@ -0,0 +1,28 @@
+#
+# IrDA protocol configuration
+#
+
+if [ "$CONFIG_NET" != "n" ]; then
+
+ mainmenu_option next_comment
+ comment 'IrDA (infrared) support'
+ dep_tristate 'IrDA subsystem support' CONFIG_IRDA $CONFIG_NET
+
+ if [ "$CONFIG_IRDA" != "n" ]; then
+ comment 'IrDA protocols'
+ source net/irda/irlan/Config.in
+ source net/irda/irnet/Config.in
+ source net/irda/ircomm/Config.in
+ bool ' Ultra (connectionless) protocol' CONFIG_IRDA_ULTRA
+ comment 'IrDA options'
+ bool ' Cache last LSAP' CONFIG_IRDA_CACHE_LAST_LSAP
+ bool ' Fast RRs (low latency)' CONFIG_IRDA_FAST_RR
+ bool ' Debug information' CONFIG_IRDA_DEBUG
+ fi
+
+ if [ "$CONFIG_IRDA" != "n" ]; then
+ source drivers/net/irda/Config.in
+ fi
+ endmenu
+fi
+
diff --git a/uClinux-2.4.31-uc0/net/irda/Makefile b/uClinux-2.4.31-uc0/net/irda/Makefile
new file mode 100644
index 0000000..c7e3345
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/Makefile
@@ -0,0 +1,43 @@
+#
+# Makefile for the Linux IrDA protocol layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := irda.o
+
+export-objs := irsyms.o
+
+obj-y := iriap.o iriap_event.o irlmp.o irlmp_event.o irlmp_frame.o \
+ irlap.o irlap_event.o irlap_frame.o timer.o qos.o irqueue.o \
+ irttp.o irda_device.o irias_object.o crc.o wrapper.o af_irda.o \
+ discovery.o parameters.o irsyms.o
+
+ifeq ($(CONFIG_IRDA),m)
+obj-m := $(O_TARGET)
+endif
+
+obj-$(CONFIG_PROC_FS) += irproc.o
+obj-$(CONFIG_SYSCTL) += irsysctl.o
+obj-$(CONFIG_IRLAN) += irlan/irlan.o
+
+subdir-$(CONFIG_IRLAN) += irlan
+subdir-$(CONFIG_IRNET) += irnet
+subdir-$(CONFIG_IRCOMM) += ircomm
+
+ifeq ($(CONFIG_IRLAN),y)
+obj-y += irlan/irlan.o
+endif
+
+ifeq ($(CONFIG_IRNET),y)
+obj-y += irnet/irnet.o
+endif
+
+ifeq ($(CONFIG_IRCOMM),y)
+obj-y += ircomm/ircomm_and_tty.o
+endif
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/irda/af_irda.c b/uClinux-2.4.31-uc0/net/irda/af_irda.c
new file mode 100644
index 0000000..86f95d1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/af_irda.c
@@ -0,0 +1,2613 @@
+/*********************************************************************
+ *
+ * Filename: af_irda.c
+ * Version: 0.9
+ * Description: IrDA sockets implementation
+ * Status: Stable
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sun May 31 10:12:43 1998
+ * Modified at: Sat Dec 25 21:10:23 1999
+ * Modified by: Dag Brattli <dag@brattli.net>
+ * Sources: af_netroom.c, af_ax25.c, af_rose.c, af_x25.c etc.
+ *
+ * Copyright (c) 1999 Dag Brattli <dagb@cs.uit.no>
+ * Copyright (c) 1999-2001 Jean Tourrilhes <jt@hpl.hp.com>
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ * Linux-IrDA now supports four different types of IrDA sockets:
+ *
+ * o SOCK_STREAM: TinyTP connections with SAR disabled. The
+ * max SDU size is 0 for conn. of this type
+ * o SOCK_SEQPACKET: TinyTP connections with SAR enabled. TTP may
+ * fragment the messages, but will preserve
+ * the message boundaries
+ * o SOCK_DGRAM: IRDAPROTO_UNITDATA: TinyTP connections with Unitdata
+ * (unreliable) transfers
+ * IRDAPROTO_ULTRA: Connectionless and unreliable data
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/init.h>
+#include <linux/if_arp.h>
+#include <linux/net.h>
+#include <linux/irda.h>
+#include <linux/poll.h>
+
+#include <asm/uaccess.h>
+
+#include <net/sock.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/iriap.h>
+#include <net/irda/irias_object.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/irttp.h>
+#include <net/irda/discovery.h>
+
+extern int irda_init(void);
+extern void irda_cleanup(void);
+extern int irlap_driver_rcv(struct sk_buff *, struct net_device *,
+ struct packet_type *);
+
+static int irda_create(struct socket *sock, int protocol);
+
+static struct proto_ops irda_stream_ops;
+static struct proto_ops irda_seqpacket_ops;
+static struct proto_ops irda_dgram_ops;
+
+#ifdef CONFIG_IRDA_ULTRA
+static struct proto_ops irda_ultra_ops;
+#define ULTRA_MAX_DATA 382
+#endif /* CONFIG_IRDA_ULTRA */
+
+#define IRDA_MAX_HEADER (TTP_MAX_HEADER)
+
+#ifdef CONFIG_IRDA_DEBUG
+__u32 irda_debug = IRDA_DEBUG_LEVEL;
+#endif
+
+/*
+ * Function irda_data_indication (instance, sap, skb)
+ *
+ * Received some data from TinyTP. Just queue it on the receive queue
+ *
+ */
+static int irda_data_indication(void *instance, void *sap, struct sk_buff *skb)
+{
+ struct irda_sock *self;
+ struct sock *sk;
+ int err;
+
+ IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+ self = (struct irda_sock *) instance;
+ ASSERT(self != NULL, return -1;);
+
+ sk = self->sk;
+ ASSERT(sk != NULL, return -1;);
+
+ err = sock_queue_rcv_skb(sk, skb);
+ if (err) {
+ IRDA_DEBUG(1, "%s(), error: no more mem!\n", __FUNCTION__);
+ self->rx_flow = FLOW_STOP;
+
+ /* When we return error, TTP will need to requeue the skb */
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * Function irda_disconnect_indication (instance, sap, reason, skb)
+ *
+ * Connection has been closed. Check reason to find out why
+ *
+ */
+static void irda_disconnect_indication(void *instance, void *sap,
+ LM_REASON reason, struct sk_buff *skb)
+{
+ struct irda_sock *self;
+ struct sock *sk;
+
+ self = (struct irda_sock *) instance;
+
+ IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self);
+
+ /* Don't care about it, but let's not leak it */
+ if(skb)
+ dev_kfree_skb(skb);
+
+ sk = self->sk;
+ if (sk == NULL)
+ return;
+
+ /* Prevent race conditions with irda_release() and irda_shutdown() */
+ if ((!sk->dead) && (sk->state != TCP_CLOSE)) {
+ sk->state = TCP_CLOSE;
+ sk->err = ECONNRESET;
+ sk->shutdown |= SEND_SHUTDOWN;
+
+ sk->state_change(sk);
+ sk->dead = 1; /* Uh-oh... Should use sock_orphan ? */
+
+ /* Close our TSAP.
+ * If we leave it open, IrLMP put it back into the list of
+ * unconnected LSAPs. The problem is that any incoming request
+ * can then be matched to this socket (and it will be, because
+ * it is at the head of the list). This would prevent any
+ * listening socket waiting on the same TSAP to get those
+ * requests. Some apps forget to close sockets, or hang to it
+ * a bit too long, so we may stay in this dead state long
+ * enough to be noticed...
+ * Note : all socket function do check sk->state, so we are
+ * safe...
+ * Jean II
+ */
+ if (self->tsap) {
+ irttp_close_tsap(self->tsap);
+ self->tsap = NULL;
+ }
+ }
+
+ /* Note : once we are there, there is not much you want to do
+ * with the socket anymore, apart from closing it.
+ * For example, bind() and connect() won't reset sk->err,
+ * sk->shutdown and sk->dead to valid values...
+ * Jean II
+ */
+}
+
+/*
+ * Function irda_connect_confirm (instance, sap, qos, max_sdu_size, skb)
+ *
+ * Connections has been confirmed by the remote device
+ *
+ */
+static void irda_connect_confirm(void *instance, void *sap,
+ struct qos_info *qos,
+ __u32 max_sdu_size, __u8 max_header_size,
+ struct sk_buff *skb)
+{
+ struct irda_sock *self;
+ struct sock *sk;
+
+ self = (struct irda_sock *) instance;
+
+ IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self);
+
+ sk = self->sk;
+ if (sk == NULL)
+ return;
+
+ /* How much header space do we need to reserve */
+ self->max_header_size = max_header_size;
+
+ /* IrTTP max SDU size in transmit direction */
+ self->max_sdu_size_tx = max_sdu_size;
+
+ /* Find out what the largest chunk of data that we can transmit is */
+ switch (sk->type) {
+ case SOCK_STREAM:
+ if (max_sdu_size != 0) {
+ ERROR("%s(), max_sdu_size must be 0\n", __FUNCTION__);
+ return;
+ }
+ self->max_data_size = irttp_get_max_seg_size(self->tsap);
+ break;
+ case SOCK_SEQPACKET:
+ if (max_sdu_size == 0) {
+ ERROR("%s(), max_sdu_size cannot be 0\n", __FUNCTION__);
+ return;
+ }
+ self->max_data_size = max_sdu_size;
+ break;
+ default:
+ self->max_data_size = irttp_get_max_seg_size(self->tsap);
+ };
+
+ IRDA_DEBUG(2, "%s(), max_data_size=%d\n", __FUNCTION__,
+ self->max_data_size);
+
+ memcpy(&self->qos_tx, qos, sizeof(struct qos_info));
+ dev_kfree_skb(skb);
+ // Should be ??? skb_queue_tail(&sk->receive_queue, skb);
+
+ /* We are now connected! */
+ sk->state = TCP_ESTABLISHED;
+ sk->state_change(sk);
+}
+
+/*
+ * Function irda_connect_indication(instance, sap, qos, max_sdu_size, userdata)
+ *
+ * Incoming connection
+ *
+ */
+static void irda_connect_indication(void *instance, void *sap,
+ struct qos_info *qos, __u32 max_sdu_size,
+ __u8 max_header_size, struct sk_buff *skb)
+{
+ struct irda_sock *self;
+ struct sock *sk;
+
+ self = (struct irda_sock *) instance;
+
+ IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self);
+
+ sk = self->sk;
+ if (sk == NULL)
+ return;
+
+ /* How much header space do we need to reserve */
+ self->max_header_size = max_header_size;
+
+ /* IrTTP max SDU size in transmit direction */
+ self->max_sdu_size_tx = max_sdu_size;
+
+ /* Find out what the largest chunk of data that we can transmit is */
+ switch (sk->type) {
+ case SOCK_STREAM:
+ if (max_sdu_size != 0) {
+ ERROR("%s(), max_sdu_size must be 0\n", __FUNCTION__);
+ return;
+ }
+ self->max_data_size = irttp_get_max_seg_size(self->tsap);
+ break;
+ case SOCK_SEQPACKET:
+ if (max_sdu_size == 0) {
+ ERROR("%s(), max_sdu_size cannot be 0\n", __FUNCTION__);
+ return;
+ }
+ self->max_data_size = max_sdu_size;
+ break;
+ default:
+ self->max_data_size = irttp_get_max_seg_size(self->tsap);
+ };
+
+ IRDA_DEBUG(2, "%s(), max_data_size=%d\n", __FUNCTION__,
+ self->max_data_size);
+
+ memcpy(&self->qos_tx, qos, sizeof(struct qos_info));
+
+ skb_queue_tail(&sk->receive_queue, skb);
+ sk->state_change(sk);
+}
+
+/*
+ * Function irda_connect_response (handle)
+ *
+ * Accept incoming connection
+ *
+ */
+void irda_connect_response(struct irda_sock *self)
+{
+ struct sk_buff *skb;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+
+ skb = dev_alloc_skb(64);
+ if (skb == NULL) {
+ IRDA_DEBUG(0, "%s() Unable to allocate sk_buff!\n", __FUNCTION__);
+ return;
+ }
+
+ /* Reserve space for MUX_CONTROL and LAP header */
+ skb_reserve(skb, IRDA_MAX_HEADER);
+
+ irttp_connect_response(self->tsap, self->max_sdu_size_rx, skb);
+}
+
+/*
+ * Function irda_flow_indication (instance, sap, flow)
+ *
+ * Used by TinyTP to tell us if it can accept more data or not
+ *
+ */
+static void irda_flow_indication(void *instance, void *sap, LOCAL_FLOW flow)
+{
+ struct irda_sock *self;
+ struct sock *sk;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ self = (struct irda_sock *) instance;
+ ASSERT(self != NULL, return;);
+
+ sk = self->sk;
+ ASSERT(sk != NULL, return;);
+
+ switch (flow) {
+ case FLOW_STOP:
+ IRDA_DEBUG(1, "%s(), IrTTP wants us to slow down\n", __FUNCTION__);
+ self->tx_flow = flow;
+ break;
+ case FLOW_START:
+ self->tx_flow = flow;
+ IRDA_DEBUG(1, "%s(), IrTTP wants us to start again\n", __FUNCTION__);
+ wake_up_interruptible(sk->sleep);
+ break;
+ default:
+ IRDA_DEBUG( 0, "%s(), Unknown flow command!\n", __FUNCTION__);
+ /* Unknown flow command, better stop */
+ self->tx_flow = flow;
+ break;
+ }
+}
+
+/*
+ * Function irda_getvalue_confirm (obj_id, value, priv)
+ *
+ * Got answer from remote LM-IAS, just pass object to requester...
+ *
+ * Note : duplicate from above, but we need our own version that
+ * doesn't touch the dtsap_sel and save the full value structure...
+ */
+static void irda_getvalue_confirm(int result, __u16 obj_id,
+ struct ias_value *value, void *priv)
+{
+ struct irda_sock *self;
+
+ self = (struct irda_sock *) priv;
+ if (!self) {
+ WARNING("%s(), lost myself!\n", __FUNCTION__);
+ return;
+ }
+
+ IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self);
+
+ /* We probably don't need to make any more queries */
+ iriap_close(self->iriap);
+ self->iriap = NULL;
+
+ /* Check if request succeeded */
+ if (result != IAS_SUCCESS) {
+ IRDA_DEBUG(1, "%s(), IAS query failed! (%d)\n", __FUNCTION__,
+ result);
+
+ self->errno = result; /* We really need it later */
+
+ /* Wake up any processes waiting for result */
+ wake_up_interruptible(&self->query_wait);
+
+ return;
+ }
+
+ /* Pass the object to the caller (so the caller must delete it) */
+ self->ias_result = value;
+ self->errno = 0;
+
+ /* Wake up any processes waiting for result */
+ wake_up_interruptible(&self->query_wait);
+}
+
+/*
+ * Function irda_selective_discovery_indication (discovery)
+ *
+ * Got a selective discovery indication from IrLMP.
+ *
+ * IrLMP is telling us that this node is matching our hint bit
+ * filter. Check if it's a newly discovered node (or if node changed its
+ * hint bits), and then wake up any process waiting for answer...
+ */
+static void irda_selective_discovery_indication(discovery_t *discovery,
+ DISCOVERY_MODE mode,
+ void *priv)
+{
+ struct irda_sock *self;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ self = (struct irda_sock *) priv;
+ if (!self) {
+ WARNING("%s(), lost myself!\n", __FUNCTION__);
+ return;
+ }
+
+ /* Check if node is discovered is a new one or an old one.
+ * We check when how long ago this node was discovered, with a
+ * coarse timeout (we may miss some discovery events or be delayed).
+ * Note : by doing this test here, we avoid waking up a process ;-)
+ */
+ if((jiffies - discovery->first_timestamp) >
+ (sysctl_discovery_timeout * HZ)) {
+ return; /* Too old, not interesting -> goodbye */
+ }
+
+ /* Pass parameter to the caller */
+ self->cachediscovery = discovery;
+
+ /* Wake up process if its waiting for device to be discovered */
+ wake_up_interruptible(&self->query_wait);
+}
+
+/*
+ * Function irda_discovery_timeout (priv)
+ *
+ * Timeout in the selective discovery process
+ *
+ * We were waiting for a node to be discovered, but nothing has come up
+ * so far. Wake up the user and tell him that we failed...
+ */
+static void irda_discovery_timeout(u_long priv)
+{
+ struct irda_sock *self;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ self = (struct irda_sock *) priv;
+ ASSERT(self != NULL, return;);
+
+ /* Nothing for the caller */
+ self->cachelog = NULL;
+ self->cachediscovery = NULL;
+ self->errno = -ETIME;
+
+ /* Wake up process if its still waiting... */
+ wake_up_interruptible(&self->query_wait);
+}
+
+/*
+ * Function irda_open_tsap (self)
+ *
+ * Open local Transport Service Access Point (TSAP)
+ *
+ */
+static int irda_open_tsap(struct irda_sock *self, __u8 tsap_sel, char *name)
+{
+ notify_t notify;
+
+ if (self->tsap) {
+ WARNING("%s(), busy!\n", __FUNCTION__);
+ return -EBUSY;
+ }
+
+ /* Initialize callbacks to be used by the IrDA stack */
+ irda_notify_init(&notify);
+ notify.connect_confirm = irda_connect_confirm;
+ notify.connect_indication = irda_connect_indication;
+ notify.disconnect_indication = irda_disconnect_indication;
+ notify.data_indication = irda_data_indication;
+ notify.udata_indication = irda_data_indication;
+ notify.flow_indication = irda_flow_indication;
+ notify.instance = self;
+ strncpy(notify.name, name, NOTIFY_MAX_NAME);
+
+ self->tsap = irttp_open_tsap(tsap_sel, DEFAULT_INITIAL_CREDIT,
+ &notify);
+ if (self->tsap == NULL) {
+ IRDA_DEBUG( 0, "%s(), Unable to allocate TSAP!\n", __FUNCTION__);
+ return -ENOMEM;
+ }
+ /* Remember which TSAP selector we actually got */
+ self->stsap_sel = self->tsap->stsap_sel;
+
+ return 0;
+}
+
+/*
+ * Function irda_open_lsap (self)
+ *
+ * Open local Link Service Access Point (LSAP). Used for opening Ultra
+ * sockets
+ */
+#ifdef CONFIG_IRDA_ULTRA
+static int irda_open_lsap(struct irda_sock *self, int pid)
+{
+ notify_t notify;
+
+ if (self->lsap) {
+ WARNING("%s(), busy!\n", __FUNCTION__);
+ return -EBUSY;
+ }
+
+ /* Initialize callbacks to be used by the IrDA stack */
+ irda_notify_init(&notify);
+ notify.udata_indication = irda_data_indication;
+ notify.instance = self;
+ strncpy(notify.name, "Ultra", NOTIFY_MAX_NAME);
+
+ self->lsap = irlmp_open_lsap(LSAP_CONNLESS, &notify, pid);
+ if (self->lsap == NULL) {
+ IRDA_DEBUG( 0, "%s(), Unable to allocate LSAP!\n", __FUNCTION__);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+#endif /* CONFIG_IRDA_ULTRA */
+
+/*
+ * Function irda_find_lsap_sel (self, name)
+ *
+ * Try to lookup LSAP selector in remote LM-IAS
+ *
+ * Basically, we start a IAP query, and then go to sleep. When the query
+ * return, irda_getvalue_confirm will wake us up, and we can examine the
+ * result of the query...
+ * Note that in some case, the query fail even before we go to sleep,
+ * creating some races...
+ */
+static int irda_find_lsap_sel(struct irda_sock *self, char *name)
+{
+ IRDA_DEBUG(2, "%s(%p, %s)\n", __FUNCTION__, self, name);
+
+ ASSERT(self != NULL, return -1;);
+
+ if (self->iriap) {
+ WARNING("%s(), busy with a previous query\n", __FUNCTION__);
+ return -EBUSY;
+ }
+
+ self->iriap = iriap_open(LSAP_ANY, IAS_CLIENT, self,
+ irda_getvalue_confirm);
+ if(self->iriap == NULL)
+ return -ENOMEM;
+
+ /* Treat unexpected signals as disconnect */
+ self->errno = -EHOSTUNREACH;
+
+ /* Query remote LM-IAS */
+ iriap_getvaluebyclass_request(self->iriap, self->saddr, self->daddr,
+ name, "IrDA:TinyTP:LsapSel");
+ /* Wait for answer (if not already failed) */
+ if(self->iriap != NULL)
+ interruptible_sleep_on(&self->query_wait);
+
+ /* Check what happened */
+ if (self->errno)
+ {
+ /* Requested object/attribute doesn't exist */
+ if((self->errno == IAS_CLASS_UNKNOWN) ||
+ (self->errno == IAS_ATTRIB_UNKNOWN))
+ return (-EADDRNOTAVAIL);
+ else
+ return (-EHOSTUNREACH);
+ }
+
+ /* Get the remote TSAP selector */
+ switch (self->ias_result->type) {
+ case IAS_INTEGER:
+ IRDA_DEBUG(4, "%s() int=%d\n", __FUNCTION__,
+ self->ias_result->t.integer);
+
+ if (self->ias_result->t.integer != -1)
+ self->dtsap_sel = self->ias_result->t.integer;
+ else
+ self->dtsap_sel = 0;
+ break;
+ default:
+ self->dtsap_sel = 0;
+ IRDA_DEBUG(0, "%s(), bad type!\n", __FUNCTION__);
+ break;
+ }
+ if (self->ias_result)
+ irias_delete_value(self->ias_result);
+
+ if (self->dtsap_sel)
+ return 0;
+
+ return -EADDRNOTAVAIL;
+}
+
+/*
+ * Function irda_discover_daddr_and_lsap_sel (self, name)
+ *
+ * This try to find a device with the requested service.
+ *
+ * It basically look into the discovery log. For each address in the list,
+ * it queries the LM-IAS of the device to find if this device offer
+ * the requested service.
+ * If there is more than one node supporting the service, we complain
+ * to the user (it should move devices around).
+ * The, we set both the destination address and the lsap selector to point
+ * on the service on the unique device we have found.
+ *
+ * Note : this function fails if there is more than one device in range,
+ * because IrLMP doesn't disconnect the LAP when the last LSAP is closed.
+ * Moreover, we would need to wait the LAP disconnection...
+ */
+static int irda_discover_daddr_and_lsap_sel(struct irda_sock *self, char *name)
+{
+ struct irda_device_info *discoveries; /* Copy of the discovery log */
+ int number; /* Number of nodes in the log */
+ int i;
+ int err = -ENETUNREACH;
+ __u32 daddr = DEV_ADDR_ANY; /* Address we found the service on */
+ __u8 dtsap_sel = 0x0; /* TSAP associated with it */
+
+ IRDA_DEBUG(2, "%s(), name=%s\n", __FUNCTION__, name);
+
+ ASSERT(self != NULL, return -1;);
+
+ /* Ask lmp for the current discovery log
+ * Note : we have to use irlmp_get_discoveries(), as opposed
+ * to play with the cachelog directly, because while we are
+ * making our ias query, le log might change... */
+ discoveries = irlmp_get_discoveries(&number, self->mask, self->nslots);
+ /* Check if the we got some results */
+ if (discoveries == NULL)
+ return -ENETUNREACH; /* No nodes discovered */
+
+ /*
+ * Now, check all discovered devices (if any), and connect
+ * client only about the services that the client is
+ * interested in...
+ */
+ for(i = 0; i < number; i++) {
+ /* Try the address in the log */
+ self->daddr = discoveries[i].daddr;
+ self->saddr = 0x0;
+ IRDA_DEBUG(1, "%s(), trying daddr = %08x\n", __FUNCTION__,
+ self->daddr);
+
+ /* Query remote LM-IAS for this service */
+ err = irda_find_lsap_sel(self, name);
+ switch (err) {
+ case 0:
+ /* We found the requested service */
+ if(daddr != DEV_ADDR_ANY) {
+ IRDA_DEBUG(1, "%s(), discovered service ''%s'' in two different devices !!!\n",
+ __FUNCTION__, name);
+ self->daddr = DEV_ADDR_ANY;
+ kfree(discoveries);
+ return(-ENOTUNIQ);
+ }
+ /* First time we found that one, save it ! */
+ daddr = self->daddr;
+ dtsap_sel = self->dtsap_sel;
+ break;
+ case -EADDRNOTAVAIL:
+ /* Requested service simply doesn't exist on this node */
+ break;
+ default:
+ /* Something bad did happen :-( */
+ IRDA_DEBUG(0, "%s(), unexpected IAS query failure\n", __FUNCTION__);
+ self->daddr = DEV_ADDR_ANY;
+ kfree(discoveries);
+ return(-EHOSTUNREACH);
+ break;
+ }
+ }
+ /* Cleanup our copy of the discovery log */
+ kfree(discoveries);
+
+ /* Check out what we found */
+ if(daddr == DEV_ADDR_ANY) {
+ IRDA_DEBUG(1, "%s(), cannot discover service ''%s'' in any device !!!\n",
+ __FUNCTION__, name);
+ self->daddr = DEV_ADDR_ANY;
+ return(-EADDRNOTAVAIL);
+ }
+
+ /* Revert back to discovered device & service */
+ self->daddr = daddr;
+ self->saddr = 0x0;
+ self->dtsap_sel = dtsap_sel;
+
+ IRDA_DEBUG(1, "%s(), discovered requested service ''%s'' at address %08x\n",
+ __FUNCTION__, name, self->daddr);
+
+ return 0;
+}
+
+/*
+ * Function irda_getname (sock, uaddr, uaddr_len, peer)
+ *
+ * Return the our own, or peers socket address (sockaddr_irda)
+ *
+ */
+static int irda_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *uaddr_len, int peer)
+{
+ struct sockaddr_irda saddr;
+ struct sock *sk = sock->sk;
+ struct irda_sock *self = sk->protinfo.irda;
+
+ if (peer) {
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+
+ saddr.sir_family = AF_IRDA;
+ saddr.sir_lsap_sel = self->dtsap_sel;
+ saddr.sir_addr = self->daddr;
+ } else {
+ saddr.sir_family = AF_IRDA;
+ saddr.sir_lsap_sel = self->stsap_sel;
+ saddr.sir_addr = self->saddr;
+ }
+
+ IRDA_DEBUG(1, "%s(), tsap_sel = %#x\n", __FUNCTION__, saddr.sir_lsap_sel);
+ IRDA_DEBUG(1, "%s(), addr = %08x\n", __FUNCTION__, saddr.sir_addr);
+
+ /* uaddr_len come to us uninitialised */
+ *uaddr_len = sizeof (struct sockaddr_irda);
+ memcpy(uaddr, &saddr, *uaddr_len);
+
+ return 0;
+}
+
+/*
+ * Function irda_listen (sock, backlog)
+ *
+ * Just move to the listen state
+ *
+ */
+static int irda_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ if ((sk->type != SOCK_STREAM) && (sk->type != SOCK_SEQPACKET) &&
+ (sk->type != SOCK_DGRAM))
+ return -EOPNOTSUPP;
+
+ if (sk->state != TCP_LISTEN) {
+ sk->max_ack_backlog = backlog;
+ sk->state = TCP_LISTEN;
+
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+/*
+ * Function irda_bind (sock, uaddr, addr_len)
+ *
+ * Used by servers to register their well known TSAP
+ *
+ */
+static int irda_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_irda *addr = (struct sockaddr_irda *) uaddr;
+ struct irda_sock *self;
+ int err;
+
+ self = sk->protinfo.irda;
+ ASSERT(self != NULL, return -1;);
+
+ IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self);
+
+ if (addr_len != sizeof(struct sockaddr_irda))
+ return -EINVAL;
+
+#ifdef CONFIG_IRDA_ULTRA
+ /* Special care for Ultra sockets */
+ if ((sk->type == SOCK_DGRAM) && (sk->protocol == IRDAPROTO_ULTRA)) {
+ self->pid = addr->sir_lsap_sel;
+ if (self->pid & 0x80) {
+ IRDA_DEBUG(0, "%s(), extension in PID not supp!\n", __FUNCTION__);
+ return -EOPNOTSUPP;
+ }
+ err = irda_open_lsap(self, self->pid);
+ if (err < 0)
+ return err;
+
+ self->max_data_size = ULTRA_MAX_DATA - LMP_PID_HEADER;
+ self->max_header_size = IRDA_MAX_HEADER + LMP_PID_HEADER;
+
+ /* Pretend we are connected */
+ sock->state = SS_CONNECTED;
+ sk->state = TCP_ESTABLISHED;
+
+ return 0;
+ }
+#endif /* CONFIG_IRDA_ULTRA */
+
+ err = irda_open_tsap(self, addr->sir_lsap_sel, addr->sir_name);
+ if (err < 0)
+ return err;
+
+ /* Register with LM-IAS */
+ self->ias_obj = irias_new_object(addr->sir_name, jiffies);
+ irias_add_integer_attrib(self->ias_obj, "IrDA:TinyTP:LsapSel",
+ self->stsap_sel, IAS_KERNEL_ATTR);
+ irias_insert_object(self->ias_obj);
+
+ return 0;
+}
+
+/*
+ * Function irda_accept (sock, newsock, flags)
+ *
+ * Wait for incoming connection
+ *
+ */
+static int irda_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+ struct irda_sock *self, *new;
+ struct sock *sk = sock->sk;
+ struct sock *newsk;
+ struct sk_buff *skb;
+ int err;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ self = sk->protinfo.irda;
+ ASSERT(self != NULL, return -1;);
+
+ err = irda_create(newsock, sk->protocol);
+ if (err)
+ return err;
+
+ if (sock->state != SS_UNCONNECTED)
+ return -EINVAL;
+
+ if ((sk = sock->sk) == NULL)
+ return -EINVAL;
+
+ if ((sk->type != SOCK_STREAM) && (sk->type != SOCK_SEQPACKET) &&
+ (sk->type != SOCK_DGRAM))
+ return -EOPNOTSUPP;
+
+ if (sk->state != TCP_LISTEN)
+ return -EINVAL;
+
+ /*
+ * The read queue this time is holding sockets ready to use
+ * hooked into the SABM we saved
+ */
+ do {
+ if ((skb = skb_dequeue(&sk->receive_queue)) == NULL) {
+ if (flags & O_NONBLOCK)
+ return -EWOULDBLOCK;
+
+ interruptible_sleep_on(sk->sleep);
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ }
+ } while (skb == NULL);
+
+ newsk = newsock->sk;
+ newsk->state = TCP_ESTABLISHED;
+
+ new = newsk->protinfo.irda;
+ ASSERT(new != NULL, return -1;);
+
+ /* Now attach up the new socket */
+ new->tsap = irttp_dup(self->tsap, new);
+ if (!new->tsap) {
+ IRDA_DEBUG(0, "%s(), dup failed!\n", __FUNCTION__);
+ return -1;
+ }
+
+ new->stsap_sel = new->tsap->stsap_sel;
+ new->dtsap_sel = new->tsap->dtsap_sel;
+ new->saddr = irttp_get_saddr(new->tsap);
+ new->daddr = irttp_get_daddr(new->tsap);
+
+ new->max_sdu_size_tx = self->max_sdu_size_tx;
+ new->max_sdu_size_rx = self->max_sdu_size_rx;
+ new->max_data_size = self->max_data_size;
+ new->max_header_size = self->max_header_size;
+
+ memcpy(&new->qos_tx, &self->qos_tx, sizeof(struct qos_info));
+
+ /* Clean up the original one to keep it in listen state */
+ irttp_listen(self->tsap);
+
+ skb->sk = NULL;
+ skb->destructor = NULL;
+ kfree_skb(skb);
+ sk->ack_backlog--;
+
+ newsock->state = SS_CONNECTED;
+
+ irda_connect_response(new);
+
+ return 0;
+}
+
+/*
+ * Function irda_connect (sock, uaddr, addr_len, flags)
+ *
+ * Connect to a IrDA device
+ *
+ * The main difference with a "standard" connect is that with IrDA we need
+ * to resolve the service name into a TSAP selector (in TCP, port number
+ * doesn't have to be resolved).
+ * Because of this service name resoltion, we can offer "auto-connect",
+ * where we connect to a service without specifying a destination address.
+ *
+ * Note : by consulting "errno", the user space caller may learn the cause
+ * of the failure. Most of them are visible in the function, others may come
+ * from subroutines called and are listed here :
+ * o EBUSY : already processing a connect
+ * o EHOSTUNREACH : bad addr->sir_addr argument
+ * o EADDRNOTAVAIL : bad addr->sir_name argument
+ * o ENOTUNIQ : more than one node has addr->sir_name (auto-connect)
+ * o ENETUNREACH : no node found on the network (auto-connect)
+ */
+static int irda_connect(struct socket *sock, struct sockaddr *uaddr,
+ int addr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_irda *addr = (struct sockaddr_irda *) uaddr;
+ struct irda_sock *self;
+ int err;
+
+ self = sk->protinfo.irda;
+
+ IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self);
+
+ /* Don't allow connect for Ultra sockets */
+ if ((sk->type == SOCK_DGRAM) && (sk->protocol == IRDAPROTO_ULTRA))
+ return -ESOCKTNOSUPPORT;
+
+ if (sk->state == TCP_ESTABLISHED && sock->state == SS_CONNECTING) {
+ sock->state = SS_CONNECTED;
+ return 0; /* Connect completed during a ERESTARTSYS event */
+ }
+
+ if (sk->state == TCP_CLOSE && sock->state == SS_CONNECTING) {
+ sock->state = SS_UNCONNECTED;
+ return -ECONNREFUSED;
+ }
+
+ if (sk->state == TCP_ESTABLISHED)
+ return -EISCONN; /* No reconnect on a seqpacket socket */
+
+ sk->state = TCP_CLOSE;
+ sock->state = SS_UNCONNECTED;
+
+ if (addr_len != sizeof(struct sockaddr_irda))
+ return -EINVAL;
+
+ /* Check if user supplied any destination device address */
+ if ((!addr->sir_addr) || (addr->sir_addr == DEV_ADDR_ANY)) {
+ /* Try to find one suitable */
+ err = irda_discover_daddr_and_lsap_sel(self, addr->sir_name);
+ if (err) {
+ IRDA_DEBUG(0, "%s(), auto-connect failed!\n", __FUNCTION__);
+ return err;
+ }
+ } else {
+ /* Use the one provided by the user */
+ self->daddr = addr->sir_addr;
+ IRDA_DEBUG(1, "%s(), daddr = %08x\n", __FUNCTION__, self->daddr);
+
+ /* Query remote LM-IAS */
+ err = irda_find_lsap_sel(self, addr->sir_name);
+ if (err) {
+ IRDA_DEBUG(0, "%s(), connect failed!\n", __FUNCTION__);
+ return err;
+ }
+ }
+
+ /* Check if we have opened a local TSAP */
+ if (!self->tsap)
+ irda_open_tsap(self, LSAP_ANY, addr->sir_name);
+
+ /* Move to connecting socket, start sending Connect Requests */
+ sock->state = SS_CONNECTING;
+ sk->state = TCP_SYN_SENT;
+
+ /* Connect to remote device */
+ err = irttp_connect_request(self->tsap, self->dtsap_sel,
+ self->saddr, self->daddr, NULL,
+ self->max_sdu_size_rx, NULL);
+ if (err) {
+ IRDA_DEBUG(0, "%s(), connect failed!\n", __FUNCTION__);
+ return err;
+ }
+
+ /* Now the loop */
+ if (sk->state != TCP_ESTABLISHED && (flags & O_NONBLOCK))
+ return -EINPROGRESS;
+
+ /* Here, there is a race condition : the state may change between
+ * our test and the sleep, via irda_connect_confirm().
+ * The way to workaround that is to sleep with a timeout, so that
+ * we don't sleep forever and check the state when waking up.
+ * 50ms is plenty good enough, because the LAP is already connected.
+ * Jean II */
+ while (sk->state == TCP_SYN_SENT) {
+ interruptible_sleep_on_timeout(sk->sleep, HZ/20);
+ if (signal_pending(current)) {
+ return -ERESTARTSYS;
+ }
+ }
+
+ if (sk->state != TCP_ESTABLISHED) {
+ sock->state = SS_UNCONNECTED;
+ return sock_error(sk); /* Always set at this point */
+ }
+
+ sock->state = SS_CONNECTED;
+
+ /* At this point, IrLMP has assigned our source address */
+ self->saddr = irttp_get_saddr(self->tsap);
+
+ return 0;
+}
+
+/*
+ * Function irda_create (sock, protocol)
+ *
+ * Create IrDA socket
+ *
+ */
+static int irda_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+ struct irda_sock *self;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ /* Check for valid socket type */
+ switch (sock->type) {
+ case SOCK_STREAM: /* For TTP connections with SAR disabled */
+ case SOCK_SEQPACKET: /* For TTP connections with SAR enabled */
+ case SOCK_DGRAM: /* For TTP Unitdata or LMP Ultra transfers */
+ break;
+ default:
+ return -ESOCKTNOSUPPORT;
+ }
+
+ /* Allocate networking socket */
+ if ((sk = sk_alloc(PF_IRDA, GFP_ATOMIC, 1)) == NULL)
+ return -ENOMEM;
+
+ /* Allocate IrDA socket */
+ self = kmalloc(sizeof(struct irda_sock), GFP_ATOMIC);
+ if (self == NULL) {
+ sk_free(sk);
+ return -ENOMEM;
+ }
+ memset(self, 0, sizeof(struct irda_sock));
+
+ IRDA_DEBUG(2, "%s() : self is %p\n", __FUNCTION__, self);
+
+ init_waitqueue_head(&self->query_wait);
+
+ /* Initialise networking socket struct */
+ sock_init_data(sock, sk); /* Note : set sk->refcnt to 1 */
+ sk->family = PF_IRDA;
+ sk->protocol = protocol;
+ /* Link networking socket and IrDA socket structs together */
+ sk->protinfo.irda = self;
+ self->sk = sk;
+
+ switch (sock->type) {
+ case SOCK_STREAM:
+ sock->ops = &irda_stream_ops;
+ self->max_sdu_size_rx = TTP_SAR_DISABLE;
+ break;
+ case SOCK_SEQPACKET:
+ sock->ops = &irda_seqpacket_ops;
+ self->max_sdu_size_rx = TTP_SAR_UNBOUND;
+ break;
+ case SOCK_DGRAM:
+ switch (protocol) {
+#ifdef CONFIG_IRDA_ULTRA
+ case IRDAPROTO_ULTRA:
+ sock->ops = &irda_ultra_ops;
+ break;
+#endif /* CONFIG_IRDA_ULTRA */
+ case IRDAPROTO_UNITDATA:
+ sock->ops = &irda_dgram_ops;
+ /* We let Unitdata conn. be like seqpack conn. */
+ self->max_sdu_size_rx = TTP_SAR_UNBOUND;
+ break;
+ default:
+ ERROR("%s(), protocol not supported!\n", __FUNCTION__);
+ return -ESOCKTNOSUPPORT;
+ }
+ break;
+ default:
+ return -ESOCKTNOSUPPORT;
+ }
+
+ /* Register as a client with IrLMP */
+ self->ckey = irlmp_register_client(0, NULL, NULL, NULL);
+ self->mask = 0xffff;
+ self->rx_flow = self->tx_flow = FLOW_START;
+ self->nslots = DISCOVERY_DEFAULT_SLOTS;
+ self->daddr = DEV_ADDR_ANY; /* Until we get connected */
+ self->saddr = 0x0; /* so IrLMP assign us any link */
+
+ MOD_INC_USE_COUNT;
+
+ return 0;
+}
+
+/*
+ * Function irda_destroy_socket (self)
+ *
+ * Destroy socket
+ *
+ */
+void irda_destroy_socket(struct irda_sock *self)
+{
+ IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self);
+
+ ASSERT(self != NULL, return;);
+
+ /* Unregister with IrLMP */
+ irlmp_unregister_client(self->ckey);
+ irlmp_unregister_service(self->skey);
+
+ /* Unregister with LM-IAS */
+ if (self->ias_obj) {
+ irias_delete_object(self->ias_obj);
+ self->ias_obj = NULL;
+ }
+
+ if (self->iriap) {
+ iriap_close(self->iriap);
+ self->iriap = NULL;
+ }
+
+ if (self->tsap) {
+ irttp_disconnect_request(self->tsap, NULL, P_NORMAL);
+ irttp_close_tsap(self->tsap);
+ self->tsap = NULL;
+ }
+#ifdef CONFIG_IRDA_ULTRA
+ if (self->lsap) {
+ irlmp_close_lsap(self->lsap);
+ self->lsap = NULL;
+ }
+#endif /* CONFIG_IRDA_ULTRA */
+ kfree(self);
+ MOD_DEC_USE_COUNT;
+
+ return;
+}
+
+/*
+ * Function irda_release (sock)
+ *
+ *
+ *
+ */
+static int irda_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ if (sk == NULL)
+ return 0;
+
+ sk->state = TCP_CLOSE;
+ sk->shutdown |= SEND_SHUTDOWN;
+ sk->state_change(sk);
+
+ /* Destroy IrDA socket */
+ irda_destroy_socket(sk->protinfo.irda);
+ /* Prevent sock_def_destruct() to create havoc */
+ sk->protinfo.irda = NULL;
+
+ sock_orphan(sk);
+ sock->sk = NULL;
+
+ /* Purge queues (see sock_init_data()) */
+ skb_queue_purge(&sk->receive_queue);
+
+ /* Destroy networking socket if we are the last reference on it,
+ * i.e. if(sk->refcnt == 0) -> sk_free(sk) */
+ sock_put(sk);
+
+ /* Notes on socket locking and deallocation... - Jean II
+ * In theory we should put pairs of sock_hold() / sock_put() to
+ * prevent the socket to be destroyed whenever there is an
+ * outstanding request or outstanding incomming packet or event.
+ *
+ * 1) This may include IAS request, both in connect and getsockopt.
+ * Unfortunately, the situation is a bit more messy than it looks,
+ * because we close iriap and kfree(self) above.
+ *
+ * 2) This may include selective discovery in getsockopt.
+ * Same stuff as above, irlmp registration and self are gone.
+ *
+ * Probably 1 and 2 may not matter, because it's all triggered
+ * by a process and the socket layer already prevent the
+ * socket to go away while a process is holding it, through
+ * sockfd_put() and fput()...
+ *
+ * 3) This may include deferred TSAP closure. In particular,
+ * we may receive a late irda_disconnect_indication()
+ * Fortunately, (tsap_cb *)->close_pend should protect us
+ * from that.
+ *
+ * I did some testing on SMP, and it looks solid. And the socket
+ * memory leak is now gone... - Jean II
+ */
+
+ return 0;
+}
+
+/*
+ * Function irda_sendmsg (sock, msg, len, scm)
+ *
+ * Send message down to TinyTP. This function is used for both STREAM and
+ * SEQPACK services. This is possible since it forces the client to
+ * fragment the message if necessary
+ */
+static int irda_sendmsg(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct irda_sock *self;
+ struct sk_buff *skb;
+ unsigned char *asmptr;
+ int err;
+
+ IRDA_DEBUG(4, "%s(), len=%d\n", __FUNCTION__, len);
+
+ /* Note : socket.c set MSG_EOR on SEQPACKET sockets */
+ if (msg->msg_flags & ~(MSG_DONTWAIT | MSG_EOR))
+ return -EINVAL;
+
+ if (sk->shutdown & SEND_SHUTDOWN) {
+ send_sig(SIGPIPE, current, 0);
+ return -EPIPE;
+ }
+
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+
+ self = sk->protinfo.irda;
+ ASSERT(self != NULL, return -1;);
+
+ /* Check if IrTTP is wants us to slow down */
+ while (self->tx_flow == FLOW_STOP) {
+ IRDA_DEBUG(2, "%s(), IrTTP is busy, going to sleep!\n", __FUNCTION__);
+ interruptible_sleep_on(sk->sleep);
+
+ /* Check if we are still connected */
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+ /* Handle signals */
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ }
+
+ /* Check that we don't send out to big frames */
+ if (len > self->max_data_size) {
+ IRDA_DEBUG(2, "%s(), Chopping frame from %d to %d bytes!\n", __FUNCTION__, len,
+ self->max_data_size);
+ len = self->max_data_size;
+ }
+
+ skb = sock_alloc_send_skb(sk, len + self->max_header_size,
+ msg->msg_flags & MSG_DONTWAIT, &err);
+ if (!skb)
+ return -ENOBUFS;
+
+ skb_reserve(skb, self->max_header_size);
+
+ asmptr = skb->h.raw = skb_put(skb, len);
+ memcpy_fromiovec(asmptr, msg->msg_iov, len);
+
+ /*
+ * Just send the message to TinyTP, and let it deal with possible
+ * errors. No need to duplicate all that here
+ */
+ err = irttp_data_request(self->tsap, skb);
+ if (err) {
+ IRDA_DEBUG(0, "%s(), err=%d\n", __FUNCTION__, err);
+ return err;
+ }
+ /* Tell client how much data we actually sent */
+ return len;
+}
+
+/*
+ * Function irda_recvmsg_dgram (sock, msg, size, flags, scm)
+ *
+ * Try to receive message and copy it to user. The frame is discarded
+ * after being read, regardless of how much the user actually read
+ */
+static int irda_recvmsg_dgram(struct socket *sock, struct msghdr *msg,
+ int size, int flags, struct scm_cookie *scm)
+{
+ struct irda_sock *self;
+ struct sock *sk = sock->sk;
+ struct sk_buff *skb;
+ int copied, err;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ self = sk->protinfo.irda;
+ ASSERT(self != NULL, return -1;);
+
+ skb = skb_recv_datagram(sk, flags & ~MSG_DONTWAIT,
+ flags & MSG_DONTWAIT, &err);
+ if (!skb)
+ return err;
+
+ skb->h.raw = skb->data;
+ copied = skb->len;
+
+ if (copied > size) {
+ IRDA_DEBUG(2, "%s(), Received truncated frame (%d < %d)!\n", __FUNCTION__,
+ copied, size);
+ copied = size;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+ skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+
+ skb_free_datagram(sk, skb);
+
+ /*
+ * Check if we have previously stopped IrTTP and we know
+ * have more free space in our rx_queue. If so tell IrTTP
+ * to start delivering frames again before our rx_queue gets
+ * empty
+ */
+ if (self->rx_flow == FLOW_STOP) {
+ if ((atomic_read(&sk->rmem_alloc) << 2) <= sk->rcvbuf) {
+ IRDA_DEBUG(2, "%s(), Starting IrTTP\n", __FUNCTION__);
+ self->rx_flow = FLOW_START;
+ irttp_flow_request(self->tsap, FLOW_START);
+ }
+ }
+
+ return copied;
+}
+
+/*
+ * Function irda_data_wait (sk)
+ *
+ * Sleep until data has arrive. But check for races..
+ *
+ */
+static void irda_data_wait(struct sock *sk)
+{
+ if (!skb_peek(&sk->receive_queue)) {
+ set_bit(SOCK_ASYNC_WAITDATA, &sk->socket->flags);
+ interruptible_sleep_on(sk->sleep);
+ clear_bit(SOCK_ASYNC_WAITDATA, &sk->socket->flags);
+ }
+}
+
+/*
+ * Function irda_recvmsg_stream (sock, msg, size, flags, scm)
+ *
+ *
+ *
+ */
+static int irda_recvmsg_stream(struct socket *sock, struct msghdr *msg,
+ int size, int flags, struct scm_cookie *scm)
+{
+ struct irda_sock *self;
+ struct sock *sk = sock->sk;
+ int noblock = flags & MSG_DONTWAIT;
+ int copied = 0;
+ int target = 1;
+
+ IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+ self = sk->protinfo.irda;
+ ASSERT(self != NULL, return -1;);
+
+ if (sock->flags & __SO_ACCEPTCON)
+ return(-EINVAL);
+
+ if (flags & MSG_OOB)
+ return -EOPNOTSUPP;
+
+ if (flags & MSG_WAITALL)
+ target = size;
+
+ msg->msg_namelen = 0;
+
+ do {
+ int chunk;
+ struct sk_buff *skb;
+
+ skb=skb_dequeue(&sk->receive_queue);
+ if (skb==NULL) {
+ if (copied >= target)
+ break;
+
+ /*
+ * POSIX 1003.1g mandates this order.
+ */
+
+ if (sk->err) {
+ return sock_error(sk);
+ }
+
+ if (sk->shutdown & RCV_SHUTDOWN)
+ break;
+
+ if (noblock)
+ return -EAGAIN;
+ irda_data_wait(sk);
+ if (signal_pending(current))
+ return -ERESTARTSYS;
+ continue;
+ }
+
+ chunk = min_t(unsigned int, skb->len, size);
+ if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) {
+ skb_queue_head(&sk->receive_queue, skb);
+ if (copied == 0)
+ copied = -EFAULT;
+ break;
+ }
+ copied += chunk;
+ size -= chunk;
+
+ /* Mark read part of skb as used */
+ if (!(flags & MSG_PEEK)) {
+ skb_pull(skb, chunk);
+
+ /* put the skb back if we didn't use it up.. */
+ if (skb->len) {
+ IRDA_DEBUG(1, "%s(), back on q!\n", __FUNCTION__);
+ skb_queue_head(&sk->receive_queue, skb);
+ break;
+ }
+
+ kfree_skb(skb);
+ } else {
+ IRDA_DEBUG(0, "%s() questionable!?\n", __FUNCTION__);
+
+ /* put message back and return */
+ skb_queue_head(&sk->receive_queue, skb);
+ break;
+ }
+ } while (size);
+
+ /*
+ * Check if we have previously stopped IrTTP and we know
+ * have more free space in our rx_queue. If so tell IrTTP
+ * to start delivering frames again before our rx_queue gets
+ * empty
+ */
+ if (self->rx_flow == FLOW_STOP) {
+ if ((atomic_read(&sk->rmem_alloc) << 2) <= sk->rcvbuf) {
+ IRDA_DEBUG(2, "%s(), Starting IrTTP\n", __FUNCTION__);
+ self->rx_flow = FLOW_START;
+ irttp_flow_request(self->tsap, FLOW_START);
+ }
+ }
+
+ return copied;
+}
+
+/*
+ * Function irda_sendmsg_dgram (sock, msg, len, scm)
+ *
+ * Send message down to TinyTP for the unreliable sequenced
+ * packet service...
+ *
+ */
+static int irda_sendmsg_dgram(struct socket *sock, struct msghdr *msg,
+ int len, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct irda_sock *self;
+ struct sk_buff *skb;
+ unsigned char *asmptr;
+ int err;
+
+ IRDA_DEBUG(4, "%s(), len=%d\n", __FUNCTION__, len);
+
+ if (msg->msg_flags & ~MSG_DONTWAIT)
+ return -EINVAL;
+
+ if (sk->shutdown & SEND_SHUTDOWN) {
+ send_sig(SIGPIPE, current, 0);
+ return -EPIPE;
+ }
+
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+
+ self = sk->protinfo.irda;
+ ASSERT(self != NULL, return -1;);
+
+ /*
+ * Check that we don't send out to big frames. This is an unreliable
+ * service, so we have no fragmentation and no coalescence
+ */
+ if (len > self->max_data_size) {
+ IRDA_DEBUG(0, "%s(), Warning to much data! "
+ "Chopping frame from %d to %d bytes!\n", __FUNCTION__, len,
+ self->max_data_size);
+ len = self->max_data_size;
+ }
+
+ skb = sock_alloc_send_skb(sk, len + self->max_header_size,
+ msg->msg_flags & MSG_DONTWAIT, &err);
+ if (!skb)
+ return -ENOBUFS;
+
+ skb_reserve(skb, self->max_header_size);
+
+ IRDA_DEBUG(4, "%s(), appending user data\n", __FUNCTION__);
+ asmptr = skb->h.raw = skb_put(skb, len);
+ memcpy_fromiovec(asmptr, msg->msg_iov, len);
+
+ /*
+ * Just send the message to TinyTP, and let it deal with possible
+ * errors. No need to duplicate all that here
+ */
+ err = irttp_udata_request(self->tsap, skb);
+ if (err) {
+ IRDA_DEBUG(0, "%s(), err=%d\n", __FUNCTION__, err);
+ return err;
+ }
+ return len;
+}
+
+/*
+ * Function irda_sendmsg_ultra (sock, msg, len, scm)
+ *
+ * Send message down to IrLMP for the unreliable Ultra
+ * packet service...
+ */
+#ifdef CONFIG_IRDA_ULTRA
+static int irda_sendmsg_ultra(struct socket *sock, struct msghdr *msg,
+ int len, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct irda_sock *self;
+ struct sk_buff *skb;
+ unsigned char *asmptr;
+ int err;
+
+ IRDA_DEBUG(4, "%s(), len=%d\n", __FUNCTION__, len);
+
+ if (msg->msg_flags & ~MSG_DONTWAIT)
+ return -EINVAL;
+
+ if (sk->shutdown & SEND_SHUTDOWN) {
+ send_sig(SIGPIPE, current, 0);
+ return -EPIPE;
+ }
+
+ self = sk->protinfo.irda;
+ ASSERT(self != NULL, return -1;);
+
+ /*
+ * Check that we don't send out to big frames. This is an unreliable
+ * service, so we have no fragmentation and no coalescence
+ */
+ if (len > self->max_data_size) {
+ IRDA_DEBUG(0, "%s(), Warning to much data! "
+ "Chopping frame from %d to %d bytes!\n", __FUNCTION__, len,
+ self->max_data_size);
+ len = self->max_data_size;
+ }
+
+ skb = sock_alloc_send_skb(sk, len + self->max_header_size,
+ msg->msg_flags & MSG_DONTWAIT, &err);
+ if (!skb)
+ return -ENOBUFS;
+
+ skb_reserve(skb, self->max_header_size);
+
+ IRDA_DEBUG(4, "%s(), appending user data\n", __FUNCTION__);
+ asmptr = skb->h.raw = skb_put(skb, len);
+ memcpy_fromiovec(asmptr, msg->msg_iov, len);
+
+ err = irlmp_connless_data_request(self->lsap, skb);
+ if (err) {
+ IRDA_DEBUG(0, "%s(), err=%d\n", __FUNCTION__, err);
+ return err;
+ }
+ return len;
+}
+#endif /* CONFIG_IRDA_ULTRA */
+
+/*
+ * Function irda_shutdown (sk, how)
+ *
+ *
+ *
+ */
+static int irda_shutdown(struct socket *sock, int how)
+{
+ struct irda_sock *self;
+ struct sock *sk = sock->sk;
+
+ self = sk->protinfo.irda;
+ ASSERT(self != NULL, return -1;);
+
+ IRDA_DEBUG(1, "%s(%p)\n", __FUNCTION__, self);
+
+ sk->state = TCP_CLOSE;
+ sk->shutdown |= SEND_SHUTDOWN;
+ sk->state_change(sk);
+
+ if (self->iriap) {
+ iriap_close(self->iriap);
+ self->iriap = NULL;
+ }
+
+ if (self->tsap) {
+ irttp_disconnect_request(self->tsap, NULL, P_NORMAL);
+ irttp_close_tsap(self->tsap);
+ self->tsap = NULL;
+ }
+
+ /* A few cleanup so the socket look as good as new... */
+ self->rx_flow = self->tx_flow = FLOW_START; /* needed ??? */
+ self->daddr = DEV_ADDR_ANY; /* Until we get re-connected */
+ self->saddr = 0x0; /* so IrLMP assign us any link */
+
+ return 0;
+}
+
+/*
+ * Function irda_poll (file, sock, wait)
+ *
+ *
+ *
+ */
+static unsigned int irda_poll(struct file * file, struct socket *sock,
+ poll_table *wait)
+{
+ struct sock *sk = sock->sk;
+ unsigned int mask;
+ struct irda_sock *self;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ self = sk->protinfo.irda;
+ poll_wait(file, sk->sleep, wait);
+ mask = 0;
+
+ /* Exceptional events? */
+ if (sk->err)
+ mask |= POLLERR;
+ if (sk->shutdown & RCV_SHUTDOWN) {
+ IRDA_DEBUG(0, "%s(), POLLHUP\n", __FUNCTION__);
+ mask |= POLLHUP;
+ }
+
+ /* Readable? */
+ if (!skb_queue_empty(&sk->receive_queue)) {
+ IRDA_DEBUG(4, "Socket is readable\n");
+ mask |= POLLIN | POLLRDNORM;
+ }
+
+ /* Connection-based need to check for termination and startup */
+ switch (sk->type) {
+ case SOCK_STREAM:
+ if (sk->state == TCP_CLOSE) {
+ IRDA_DEBUG(0, "%s(), POLLHUP\n", __FUNCTION__);
+ mask |= POLLHUP;
+ }
+
+ if (sk->state == TCP_ESTABLISHED) {
+ if ((self->tx_flow == FLOW_START) &&
+ sock_writeable(sk))
+ {
+ mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
+ }
+ }
+ break;
+ case SOCK_SEQPACKET:
+ if ((self->tx_flow == FLOW_START) &&
+ sock_writeable(sk))
+ {
+ mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
+ }
+ break;
+ case SOCK_DGRAM:
+ if (sock_writeable(sk))
+ mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
+ break;
+ default:
+ break;
+ }
+ return mask;
+}
+
+/*
+ * Function irda_ioctl (sock, cmd, arg)
+ *
+ *
+ *
+ */
+static int irda_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+
+ IRDA_DEBUG(4, "%s(), cmd=%#x\n", __FUNCTION__, cmd);
+
+ switch (cmd) {
+ case TIOCOUTQ: {
+ long amount;
+ amount = sk->sndbuf - atomic_read(&sk->wmem_alloc);
+ if (amount < 0)
+ amount = 0;
+ if (put_user(amount, (unsigned int *)arg))
+ return -EFAULT;
+ return 0;
+ }
+
+ case TIOCINQ: {
+ struct sk_buff *skb;
+ long amount = 0L;
+ /* These two are safe on a single CPU system as only user tasks fiddle here */
+ if ((skb = skb_peek(&sk->receive_queue)) != NULL)
+ amount = skb->len;
+ if (put_user(amount, (unsigned int *)arg))
+ return -EFAULT;
+ return 0;
+ }
+
+ case SIOCGSTAMP:
+ if (sk != NULL) {
+ if (sk->stamp.tv_sec == 0)
+ return -ENOENT;
+ if (copy_to_user((void *)arg, &sk->stamp,
+ sizeof(struct timeval)))
+ return -EFAULT;
+ return 0;
+ }
+ return -EINVAL;
+
+ case SIOCGIFADDR:
+ case SIOCSIFADDR:
+ case SIOCGIFDSTADDR:
+ case SIOCSIFDSTADDR:
+ case SIOCGIFBRDADDR:
+ case SIOCSIFBRDADDR:
+ case SIOCGIFNETMASK:
+ case SIOCSIFNETMASK:
+ case SIOCGIFMETRIC:
+ case SIOCSIFMETRIC:
+ return -EINVAL;
+ default:
+ IRDA_DEBUG(1, "%s(), doing device ioctl!\n", __FUNCTION__);
+ return dev_ioctl(cmd, (void *) arg);
+ }
+
+ /*NOTREACHED*/
+ return 0;
+}
+
+/*
+ * Function irda_setsockopt (sock, level, optname, optval, optlen)
+ *
+ * Set some options for the socket
+ *
+ */
+static int irda_setsockopt(struct socket *sock, int level, int optname,
+ char *optval, int optlen)
+{
+ struct sock *sk = sock->sk;
+ struct irda_sock *self;
+ struct irda_ias_set *ias_opt;
+ struct ias_object *ias_obj;
+ struct ias_attrib * ias_attr; /* Attribute in IAS object */
+ int opt;
+
+ self = sk->protinfo.irda;
+ ASSERT(self != NULL, return -1;);
+
+ IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self);
+
+ if (level != SOL_IRLMP)
+ return -ENOPROTOOPT;
+
+ switch (optname) {
+ case IRLMP_IAS_SET:
+ /* The user want to add an attribute to an existing IAS object
+ * (in the IAS database) or to create a new object with this
+ * attribute.
+ * We first query IAS to know if the object exist, and then
+ * create the right attribute...
+ */
+
+ if (optlen != sizeof(struct irda_ias_set))
+ return -EINVAL;
+
+ ias_opt = kmalloc(sizeof(struct irda_ias_set), GFP_ATOMIC);
+ if (ias_opt == NULL)
+ return -ENOMEM;
+
+ /* Copy query to the driver. */
+ if (copy_from_user(ias_opt, (char *)optval, optlen)) {
+ kfree(ias_opt);
+ return -EFAULT;
+ }
+
+ /* Find the object we target.
+ * If the user gives us an empty string, we use the object
+ * associated with this socket. This will workaround
+ * duplicated class name - Jean II */
+ if(ias_opt->irda_class_name[0] == '\0') {
+ if(self->ias_obj == NULL) {
+ kfree(ias_opt);
+ return -EINVAL;
+ }
+ ias_obj = self->ias_obj;
+ } else
+ ias_obj = irias_find_object(ias_opt->irda_class_name);
+
+ /* Only ROOT can mess with the global IAS database.
+ * Users can only add attributes to the object associated
+ * with the socket they own - Jean II */
+ if((!capable(CAP_NET_ADMIN)) &&
+ ((ias_obj == NULL) || (ias_obj != self->ias_obj))) {
+ kfree(ias_opt);
+ return -EPERM;
+ }
+
+ /* If the object doesn't exist, create it */
+ if(ias_obj == (struct ias_object *) NULL) {
+ /* Create a new object */
+ ias_obj = irias_new_object(ias_opt->irda_class_name,
+ jiffies);
+ }
+
+ /* Do we have the attribute already ? */
+ if(irias_find_attrib(ias_obj, ias_opt->irda_attrib_name)) {
+ kfree(ias_opt);
+ return -EINVAL;
+ }
+
+ /* Look at the type */
+ switch(ias_opt->irda_attrib_type) {
+ case IAS_INTEGER:
+ /* Add an integer attribute */
+ irias_add_integer_attrib(
+ ias_obj,
+ ias_opt->irda_attrib_name,
+ ias_opt->attribute.irda_attrib_int,
+ IAS_USER_ATTR);
+ break;
+ case IAS_OCT_SEQ:
+ /* Check length */
+ if(ias_opt->attribute.irda_attrib_octet_seq.len >
+ IAS_MAX_OCTET_STRING) {
+ kfree(ias_opt);
+ return -EINVAL;
+ }
+ /* Add an octet sequence attribute */
+ irias_add_octseq_attrib(
+ ias_obj,
+ ias_opt->irda_attrib_name,
+ ias_opt->attribute.irda_attrib_octet_seq.octet_seq,
+ ias_opt->attribute.irda_attrib_octet_seq.len,
+ IAS_USER_ATTR);
+ break;
+ case IAS_STRING:
+ /* Should check charset & co */
+ /* Check length */
+ if(ias_opt->attribute.irda_attrib_string.len >
+ IAS_MAX_STRING) {
+ kfree(ias_opt);
+ return -EINVAL;
+ }
+ /* NULL terminate the string (avoid troubles) */
+ ias_opt->attribute.irda_attrib_string.string[ias_opt->attribute.irda_attrib_string.len] = '\0';
+ /* Add a string attribute */
+ irias_add_string_attrib(
+ ias_obj,
+ ias_opt->irda_attrib_name,
+ ias_opt->attribute.irda_attrib_string.string,
+ IAS_USER_ATTR);
+ break;
+ default :
+ kfree(ias_opt);
+ return -EINVAL;
+ }
+ irias_insert_object(ias_obj);
+ kfree(ias_opt);
+ break;
+ case IRLMP_IAS_DEL:
+ /* The user want to delete an object from our local IAS
+ * database. We just need to query the IAS, check is the
+ * object is not owned by the kernel and delete it.
+ */
+
+ if (optlen != sizeof(struct irda_ias_set))
+ return -EINVAL;
+
+ ias_opt = kmalloc(sizeof(struct irda_ias_set), GFP_ATOMIC);
+ if (ias_opt == NULL)
+ return -ENOMEM;
+
+ /* Copy query to the driver. */
+ if (copy_from_user(ias_opt, (char *)optval, optlen)) {
+ kfree(ias_opt);
+ return -EFAULT;
+ }
+
+ /* Find the object we target.
+ * If the user gives us an empty string, we use the object
+ * associated with this socket. This will workaround
+ * duplicated class name - Jean II */
+ if(ias_opt->irda_class_name[0] == '\0')
+ ias_obj = self->ias_obj;
+ else
+ ias_obj = irias_find_object(ias_opt->irda_class_name);
+ if(ias_obj == (struct ias_object *) NULL) {
+ kfree(ias_opt);
+ return -EINVAL;
+ }
+
+ /* Only ROOT can mess with the global IAS database.
+ * Users can only del attributes from the object associated
+ * with the socket they own - Jean II */
+ if((!capable(CAP_NET_ADMIN)) &&
+ ((ias_obj == NULL) || (ias_obj != self->ias_obj))) {
+ kfree(ias_opt);
+ return -EPERM;
+ }
+
+ /* Find the attribute (in the object) we target */
+ ias_attr = irias_find_attrib(ias_obj,
+ ias_opt->irda_attrib_name);
+ if(ias_attr == (struct ias_attrib *) NULL) {
+ kfree(ias_opt);
+ return -EINVAL;
+ }
+
+ /* Check is the user space own the object */
+ if(ias_attr->value->owner != IAS_USER_ATTR) {
+ IRDA_DEBUG(1, "%s(), attempting to delete a kernel attribute\n", __FUNCTION__);
+ kfree(ias_opt);
+ return -EPERM;
+ }
+
+ /* Remove the attribute (and maybe the object) */
+ irias_delete_attrib(ias_obj, ias_attr);
+ kfree(ias_opt);
+ break;
+ case IRLMP_MAX_SDU_SIZE:
+ if (optlen < sizeof(int))
+ return -EINVAL;
+
+ if (get_user(opt, (int *)optval))
+ return -EFAULT;
+
+ /* Only possible for a seqpacket service (TTP with SAR) */
+ if (sk->type != SOCK_SEQPACKET) {
+ IRDA_DEBUG(2, "%s(), setting max_sdu_size = %d\n", __FUNCTION__, opt);
+ self->max_sdu_size_rx = opt;
+ } else {
+ WARNING("%s(), not allowed to set MAXSDUSIZE for this "
+ "socket type!\n", __FUNCTION__);
+ return -ENOPROTOOPT;
+ }
+ break;
+ case IRLMP_HINTS_SET:
+ if (optlen < sizeof(int))
+ return -EINVAL;
+
+ if (get_user(opt, (int *)optval))
+ return -EFAULT;
+
+ /* Unregister any old registration */
+ if (self->skey)
+ irlmp_unregister_service(self->skey);
+
+ self->skey = irlmp_register_service((__u16) opt);
+ break;
+ case IRLMP_HINT_MASK_SET:
+ /* As opposed to the previous case which set the hint bits
+ * that we advertise, this one set the filter we use when
+ * making a discovery (nodes which don't match any hint
+ * bit in the mask are not reported).
+ */
+ if (optlen < sizeof(int))
+ return -EINVAL;
+
+ if (get_user(opt, (int *)optval))
+ return -EFAULT;
+
+ /* Set the new hint mask */
+ self->mask = (__u16) opt;
+ /* Mask out extension bits */
+ self->mask &= 0x7f7f;
+ /* Check if no bits */
+ if(!self->mask)
+ self->mask = 0xFFFF;
+
+ break;
+ default:
+ return -ENOPROTOOPT;
+ }
+ return 0;
+}
+
+/*
+ * Function irda_extract_ias_value(ias_opt, ias_value)
+ *
+ * Translate internal IAS value structure to the user space representation
+ *
+ * The external representation of IAS values, as we exchange them with
+ * user space program is quite different from the internal representation,
+ * as stored in the IAS database (because we need a flat structure for
+ * crossing kernel boundary).
+ * This function transform the former in the latter. We also check
+ * that the value type is valid.
+ */
+static int irda_extract_ias_value(struct irda_ias_set *ias_opt,
+ struct ias_value *ias_value)
+{
+ /* Look at the type */
+ switch (ias_value->type) {
+ case IAS_INTEGER:
+ /* Copy the integer */
+ ias_opt->attribute.irda_attrib_int = ias_value->t.integer;
+ break;
+ case IAS_OCT_SEQ:
+ /* Set length */
+ ias_opt->attribute.irda_attrib_octet_seq.len = ias_value->len;
+ /* Copy over */
+ memcpy(ias_opt->attribute.irda_attrib_octet_seq.octet_seq,
+ ias_value->t.oct_seq, ias_value->len);
+ break;
+ case IAS_STRING:
+ /* Set length */
+ ias_opt->attribute.irda_attrib_string.len = ias_value->len;
+ ias_opt->attribute.irda_attrib_string.charset = ias_value->charset;
+ /* Copy over */
+ memcpy(ias_opt->attribute.irda_attrib_string.string,
+ ias_value->t.string, ias_value->len);
+ /* NULL terminate the string (avoid troubles) */
+ ias_opt->attribute.irda_attrib_string.string[ias_value->len] = '\0';
+ break;
+ case IAS_MISSING:
+ default :
+ return -EINVAL;
+ }
+
+ /* Copy type over */
+ ias_opt->irda_attrib_type = ias_value->type;
+
+ return 0;
+}
+
+/*
+ * Function irda_getsockopt (sock, level, optname, optval, optlen)
+ *
+ *
+ *
+ */
+static int irda_getsockopt(struct socket *sock, int level, int optname,
+ char *optval, int *optlen)
+{
+ struct sock *sk = sock->sk;
+ struct irda_sock *self;
+ struct irda_device_list list;
+ struct irda_device_info *discoveries;
+ struct irda_ias_set * ias_opt; /* IAS get/query params */
+ struct ias_object * ias_obj; /* Object in IAS */
+ struct ias_attrib * ias_attr; /* Attribute in IAS object */
+ int daddr = DEV_ADDR_ANY; /* Dest address for IAS queries */
+ int val = 0;
+ int len = 0;
+ int err;
+ int offset, total;
+
+ self = sk->protinfo.irda;
+
+ IRDA_DEBUG(2, "%s(%p)\n", __FUNCTION__, self);
+
+ if (level != SOL_IRLMP)
+ return -ENOPROTOOPT;
+
+ if (get_user(len, optlen))
+ return -EFAULT;
+
+ if(len < 0)
+ return -EINVAL;
+
+ switch (optname) {
+ case IRLMP_ENUMDEVICES:
+ /* Ask lmp for the current discovery log */
+ discoveries = irlmp_get_discoveries(&list.len, self->mask,
+ self->nslots);
+ /* Check if the we got some results */
+ if (discoveries == NULL)
+ return -EAGAIN; /* Didn't find any devices */
+ err = 0;
+
+ /* Write total list length back to client */
+ if (copy_to_user(optval, &list,
+ sizeof(struct irda_device_list) -
+ sizeof(struct irda_device_info)))
+ err = -EFAULT;
+
+ /* Offset to first device entry */
+ offset = sizeof(struct irda_device_list) -
+ sizeof(struct irda_device_info);
+
+ /* Copy the list itself - watch for overflow */
+ if(list.len > 2048)
+ {
+ err = -EINVAL;
+ goto bed;
+ }
+ total = offset + (list.len * sizeof(struct irda_device_info));
+ if (total > len)
+ total = len;
+ if (copy_to_user(optval+offset, discoveries, total - offset))
+ err = -EFAULT;
+
+ /* Write total number of bytes used back to client */
+ if (put_user(total, optlen))
+ err = -EFAULT;
+bed:
+ /* Free up our buffer */
+ kfree(discoveries);
+ if (err)
+ return err;
+ break;
+ case IRLMP_MAX_SDU_SIZE:
+ val = self->max_data_size;
+ len = sizeof(int);
+ if (put_user(len, optlen))
+ return -EFAULT;
+
+ if (copy_to_user(optval, &val, len))
+ return -EFAULT;
+ break;
+ case IRLMP_IAS_GET:
+ /* The user want an object from our local IAS database.
+ * We just need to query the IAS and return the value
+ * that we found */
+
+ /* Check that the user has allocated the right space for us */
+ if (len != sizeof(struct irda_ias_set))
+ return -EINVAL;
+
+ ias_opt = kmalloc(sizeof(struct irda_ias_set), GFP_ATOMIC);
+ if (ias_opt == NULL)
+ return -ENOMEM;
+
+ /* Copy query to the driver. */
+ if (copy_from_user((char *) ias_opt, (char *)optval, len)) {
+ kfree(ias_opt);
+ return -EFAULT;
+ }
+
+ /* Find the object we target.
+ * If the user gives us an empty string, we use the object
+ * associated with this socket. This will workaround
+ * duplicated class name - Jean II */
+ if(ias_opt->irda_class_name[0] == '\0')
+ ias_obj = self->ias_obj;
+ else
+ ias_obj = irias_find_object(ias_opt->irda_class_name);
+ if(ias_obj == (struct ias_object *) NULL) {
+ kfree(ias_opt);
+ return -EINVAL;
+ }
+
+ /* Find the attribute (in the object) we target */
+ ias_attr = irias_find_attrib(ias_obj,
+ ias_opt->irda_attrib_name);
+ if(ias_attr == (struct ias_attrib *) NULL) {
+ kfree(ias_opt);
+ return -EINVAL;
+ }
+
+ /* Translate from internal to user structure */
+ err = irda_extract_ias_value(ias_opt, ias_attr->value);
+ if(err) {
+ kfree(ias_opt);
+ return err;
+ }
+
+ /* Copy reply to the user */
+ if (copy_to_user((char *)optval, (char *) ias_opt,
+ sizeof(struct irda_ias_set))) {
+ kfree(ias_opt);
+ return -EFAULT;
+ }
+ /* Note : don't need to put optlen, we checked it */
+ kfree(ias_opt);
+ break;
+ case IRLMP_IAS_QUERY:
+ /* The user want an object from a remote IAS database.
+ * We need to use IAP to query the remote database and
+ * then wait for the answer to come back. */
+
+ /* Check that the user has allocated the right space for us */
+ if (len != sizeof(struct irda_ias_set))
+ return -EINVAL;
+
+ ias_opt = kmalloc(sizeof(struct irda_ias_set), GFP_ATOMIC);
+ if (ias_opt == NULL)
+ return -ENOMEM;
+
+ /* Copy query to the driver. */
+ if (copy_from_user((char *) ias_opt, (char *)optval, len)) {
+ kfree(ias_opt);
+ return -EFAULT;
+ }
+
+ /* At this point, there are two cases...
+ * 1) the socket is connected - that's the easy case, we
+ * just query the device we are connected to...
+ * 2) the socket is not connected - the user doesn't want
+ * to connect and/or may not have a valid service name
+ * (so can't create a fake connection). In this case,
+ * we assume that the user pass us a valid destination
+ * address in the requesting structure...
+ */
+ if(self->daddr != DEV_ADDR_ANY) {
+ /* We are connected - reuse known daddr */
+ daddr = self->daddr;
+ } else {
+ /* We are not connected, we must specify a valid
+ * destination address */
+ daddr = ias_opt->daddr;
+ if((!daddr) || (daddr == DEV_ADDR_ANY)) {
+ kfree(ias_opt);
+ return -EINVAL;
+ }
+ }
+
+ /* Check that we can proceed with IAP */
+ if (self->iriap) {
+ WARNING("%s(), busy with a previous query\n", __FUNCTION__);
+ kfree(ias_opt);
+ return -EBUSY;
+ }
+
+ self->iriap = iriap_open(LSAP_ANY, IAS_CLIENT, self,
+ irda_getvalue_confirm);
+
+ /* Treat unexpected signals as disconnect */
+ self->errno = -EHOSTUNREACH;
+
+ /* Query remote LM-IAS */
+ iriap_getvaluebyclass_request(self->iriap,
+ self->saddr, daddr,
+ ias_opt->irda_class_name,
+ ias_opt->irda_attrib_name);
+ /* Wait for answer (if not already failed) */
+ if(self->iriap != NULL)
+ interruptible_sleep_on(&self->query_wait);
+ /* Check what happened */
+ if (self->errno)
+ {
+ kfree(ias_opt);
+ /* Requested object/attribute doesn't exist */
+ if((self->errno == IAS_CLASS_UNKNOWN) ||
+ (self->errno == IAS_ATTRIB_UNKNOWN))
+ return (-EADDRNOTAVAIL);
+ else
+ return (-EHOSTUNREACH);
+ }
+
+ /* Translate from internal to user structure */
+ err = irda_extract_ias_value(ias_opt, self->ias_result);
+ if (self->ias_result)
+ irias_delete_value(self->ias_result);
+ if (err) {
+ kfree(ias_opt);
+ return err;
+ }
+
+ /* Copy reply to the user */
+ if (copy_to_user((char *)optval, (char *) ias_opt,
+ sizeof(struct irda_ias_set))) {
+ kfree(ias_opt);
+ return -EFAULT;
+ }
+ /* Note : don't need to put optlen, we checked it */
+ kfree(ias_opt);
+ break;
+ case IRLMP_WAITDEVICE:
+ /* This function is just another way of seeing life ;-)
+ * IRLMP_ENUMDEVICES assumes that you have a static network,
+ * and that you just want to pick one of the devices present.
+ * On the other hand, in here we assume that no device is
+ * present and that at some point in the future a device will
+ * come into range. When this device arrive, we just wake
+ * up the caller, so that he has time to connect to it before
+ * the device goes away...
+ * Note : once the node has been discovered for more than a
+ * few second, it won't trigger this function, unless it
+ * goes away and come back changes its hint bits (so we
+ * might call it IRLMP_WAITNEWDEVICE).
+ */
+
+ /* Check that the user is passing us an int */
+ if (len != sizeof(int))
+ return -EINVAL;
+ /* Get timeout in ms (max time we block the caller) */
+ if (get_user(val, (int *)optval))
+ return -EFAULT;
+
+ /* Tell IrLMP we want to be notified */
+ irlmp_update_client(self->ckey, self->mask,
+ irda_selective_discovery_indication,
+ NULL, (void *) self);
+
+ /* Do some discovery (and also return cached results) */
+ irlmp_discovery_request(self->nslots);
+
+ /* Wait until a node is discovered */
+ if (!self->cachediscovery) {
+ IRDA_DEBUG(1, "%s(), nothing discovered yet, going to sleep...\n", __FUNCTION__);
+
+ /* Set watchdog timer to expire in <val> ms. */
+ self->watchdog.function = irda_discovery_timeout;
+ self->watchdog.data = (unsigned long) self;
+ self->watchdog.expires = jiffies + (val * HZ/1000);
+ add_timer(&(self->watchdog));
+
+ /* Wait for IR-LMP to call us back */
+ interruptible_sleep_on(&self->query_wait);
+
+ /* If watchdog is still activated, kill it! */
+ if(timer_pending(&(self->watchdog)))
+ del_timer(&(self->watchdog));
+
+ IRDA_DEBUG(1, "%s(), ...waking up !\n", __FUNCTION__);
+ }
+ else
+ IRDA_DEBUG(1, "%s(), found immediately !\n", __FUNCTION__);
+
+ /* Tell IrLMP that we have been notified */
+ irlmp_update_client(self->ckey, self->mask, NULL, NULL, NULL);
+
+ /* Check if the we got some results */
+ if (!self->cachediscovery)
+ return -EAGAIN; /* Didn't find any devices */
+ /* Cleanup */
+ self->cachediscovery = NULL;
+
+ /* Note : We don't return anything to the user.
+ * We could return the device that triggered the wake up,
+ * but it's probably better to force the user to query
+ * the whole discovery log and let him pick one device...
+ */
+ break;
+ default:
+ return -ENOPROTOOPT;
+ }
+
+ return 0;
+}
+
+static struct net_proto_family irda_family_ops =
+{
+ PF_IRDA,
+ irda_create
+};
+
+static struct proto_ops SOCKOPS_WRAPPED(irda_stream_ops) = {
+ family: PF_IRDA,
+
+ release: irda_release,
+ bind: irda_bind,
+ connect: irda_connect,
+ socketpair: sock_no_socketpair,
+ accept: irda_accept,
+ getname: irda_getname,
+ poll: irda_poll,
+ ioctl: irda_ioctl,
+ listen: irda_listen,
+ shutdown: irda_shutdown,
+ setsockopt: irda_setsockopt,
+ getsockopt: irda_getsockopt,
+ sendmsg: irda_sendmsg,
+ recvmsg: irda_recvmsg_stream,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+static struct proto_ops SOCKOPS_WRAPPED(irda_seqpacket_ops) = {
+ family: PF_IRDA,
+
+ release: irda_release,
+ bind: irda_bind,
+ connect: irda_connect,
+ socketpair: sock_no_socketpair,
+ accept: irda_accept,
+ getname: irda_getname,
+ poll: datagram_poll,
+ ioctl: irda_ioctl,
+ listen: irda_listen,
+ shutdown: irda_shutdown,
+ setsockopt: irda_setsockopt,
+ getsockopt: irda_getsockopt,
+ sendmsg: irda_sendmsg,
+ recvmsg: irda_recvmsg_dgram,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+static struct proto_ops SOCKOPS_WRAPPED(irda_dgram_ops) = {
+ family: PF_IRDA,
+
+ release: irda_release,
+ bind: irda_bind,
+ connect: irda_connect,
+ socketpair: sock_no_socketpair,
+ accept: irda_accept,
+ getname: irda_getname,
+ poll: datagram_poll,
+ ioctl: irda_ioctl,
+ listen: irda_listen,
+ shutdown: irda_shutdown,
+ setsockopt: irda_setsockopt,
+ getsockopt: irda_getsockopt,
+ sendmsg: irda_sendmsg_dgram,
+ recvmsg: irda_recvmsg_dgram,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+#ifdef CONFIG_IRDA_ULTRA
+static struct proto_ops SOCKOPS_WRAPPED(irda_ultra_ops) = {
+ family: PF_IRDA,
+
+ release: irda_release,
+ bind: irda_bind,
+ connect: sock_no_connect,
+ socketpair: sock_no_socketpair,
+ accept: sock_no_accept,
+ getname: irda_getname,
+ poll: datagram_poll,
+ ioctl: irda_ioctl,
+ listen: sock_no_listen,
+ shutdown: irda_shutdown,
+ setsockopt: irda_setsockopt,
+ getsockopt: irda_getsockopt,
+ sendmsg: irda_sendmsg_ultra,
+ recvmsg: irda_recvmsg_dgram,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+#endif /* CONFIG_IRDA_ULTRA */
+
+#include <linux/smp_lock.h>
+SOCKOPS_WRAP(irda_stream, PF_IRDA);
+SOCKOPS_WRAP(irda_seqpacket, PF_IRDA);
+SOCKOPS_WRAP(irda_dgram, PF_IRDA);
+#ifdef CONFIG_IRDA_ULTRA
+SOCKOPS_WRAP(irda_ultra, PF_IRDA);
+#endif /* CONFIG_IRDA_ULTRA */
+
+/*
+ * Function irda_device_event (this, event, ptr)
+ *
+ * Called when a device is taken up or down
+ *
+ */
+static int irda_device_event(struct notifier_block *this, unsigned long event,
+ void *ptr)
+{
+ struct net_device *dev = (struct net_device *) ptr;
+
+ /* Reject non IrDA devices */
+ if (dev->type != ARPHRD_IRDA)
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case NETDEV_UP:
+ IRDA_DEBUG(3, "%s(), NETDEV_UP\n", __FUNCTION__);
+ /* irda_dev_device_up(dev); */
+ break;
+ case NETDEV_DOWN:
+ IRDA_DEBUG(3, "%s(), NETDEV_DOWN\n", __FUNCTION__);
+ /* irda_kill_by_device(dev); */
+ /* irda_rt_device_down(dev); */
+ /* irda_dev_device_down(dev); */
+ break;
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct packet_type irda_packet_type =
+{
+ 0, /* MUTTER ntohs(ETH_P_IRDA),*/
+ NULL,
+ irlap_driver_rcv,
+ NULL,
+ NULL,
+};
+
+static struct notifier_block irda_dev_notifier = {
+ irda_device_event,
+ NULL,
+ 0
+};
+
+/*
+ * Function irda_proc_modcount (inode, fill)
+ *
+ * Use by the proc file system functions to prevent the irda module
+ * being removed while the use is standing in the net/irda directory
+ */
+void irda_proc_modcount(struct inode *inode, int fill)
+{
+#ifdef MODULE
+#ifdef CONFIG_PROC_FS
+ if (fill)
+ MOD_INC_USE_COUNT;
+ else
+ MOD_DEC_USE_COUNT;
+#endif /* CONFIG_PROC_FS */
+#endif /* MODULE */
+}
+
+/*
+ * Function irda_proto_init (pro)
+ *
+ * Initialize IrDA protocol layer
+ *
+ */
+int __init irda_proto_init(void)
+{
+ sock_register(&irda_family_ops);
+
+ irda_packet_type.type = htons(ETH_P_IRDA);
+ dev_add_pack(&irda_packet_type);
+
+ register_netdevice_notifier(&irda_dev_notifier);
+ irda_init();
+#ifdef MODULE
+ irda_device_init(); /* Called by init/main.c when non-modular */
+#endif
+ return 0;
+}
+#ifdef MODULE
+module_init(irda_proto_init); /* If non-module, called from init/main.c */
+#endif
+
+/*
+ * Function irda_proto_cleanup (void)
+ *
+ * Remove IrDA protocol layer
+ *
+ */
+#ifdef MODULE
+void __exit irda_proto_cleanup(void)
+{
+ irda_packet_type.type = htons(ETH_P_IRDA);
+ dev_remove_pack(&irda_packet_type);
+
+ unregister_netdevice_notifier(&irda_dev_notifier);
+
+ sock_unregister(PF_IRDA);
+
+ irda_cleanup();
+}
+module_exit(irda_proto_cleanup);
+
+MODULE_AUTHOR("Dag Brattli <dagb@cs.uit.no>");
+MODULE_DESCRIPTION("The Linux IrDA Protocol Subsystem");
+MODULE_LICENSE("GPL");
+#ifdef CONFIG_IRDA_DEBUG
+MODULE_PARM(irda_debug, "1l");
+#endif
+#endif /* MODULE */
diff --git a/uClinux-2.4.31-uc0/net/irda/crc.c b/uClinux-2.4.31-uc0/net/irda/crc.c
new file mode 100644
index 0000000..b9a46c9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/crc.c
@@ -0,0 +1,65 @@
+/*********************************************************************
+ *
+ * Filename: crc.c
+ * Version: 0.1
+ * Description: CRC calculation routines
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Mon Aug 4 20:40:53 1997
+ * Modified at: Sun May 2 20:28:08 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ * Sources: ppp.c by Michael Callahan <callahan@maths.ox.ac.uk>
+ * Al Longyear <longyear@netcom.com>
+ *
+ ********************************************************************/
+
+#include <net/irda/crc.h>
+
+/*
+ * This mysterious table is just the CRC of each possible byte. It can be
+ * computed using the standard bit-at-a-time methods. The polynomial can
+ * be seen in entry 128, 0x8408. This corresponds to x^0 + x^5 + x^12.
+ * Add the implicit x^16, and you have the standard CRC-CCITT.
+ */
+__u16 const irda_crc16_table[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
+};
+
+unsigned short irda_calc_crc16( __u16 fcs, __u8 const *buf, size_t len)
+{
+ while (len--)
+ fcs = irda_fcs(fcs, *buf++);
+ return fcs;
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/discovery.c b/uClinux-2.4.31-uc0/net/irda/discovery.c
new file mode 100644
index 0000000..9171ef0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/discovery.c
@@ -0,0 +1,368 @@
+/*********************************************************************
+ *
+ * Filename: discovery.c
+ * Version: 0.1
+ * Description: Routines for handling discoveries at the IrLMP layer
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Tue Apr 6 15:33:50 1999
+ * Modified at: Sat Oct 9 17:11:31 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ * Modified at: Fri May 28 3:11 CST 1999
+ * Modified by: Horst von Brand <vonbrand@sleipnir.valparaiso.cl>
+ *
+ * Copyright (c) 1999 Dag Brattli, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ ********************************************************************/
+
+#include <linux/string.h>
+#include <linux/socket.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irlmp.h>
+
+#include <net/irda/discovery.h>
+
+/*
+ * Function irlmp_add_discovery (cachelog, discovery)
+ *
+ * Add a new discovery to the cachelog, and remove any old discoveries
+ * from the same device
+ *
+ * Note : we try to preserve the time this device was *first* discovered
+ * (as opposed to the time of last discovery used for cleanup). This is
+ * used by clients waiting for discovery events to tell if the device
+ * discovered is "new" or just the same old one. They can't rely there
+ * on a binary flag (new/old), because not all discovery events are
+ * propagated to them, and they might not always listen, so they would
+ * miss some new devices popping up...
+ * Jean II
+ */
+void irlmp_add_discovery(hashbin_t *cachelog, discovery_t *new)
+{
+ discovery_t *discovery, *node;
+ unsigned long flags;
+
+ /* Set time of first discovery if node is new (see below) */
+ new->first_timestamp = new->timestamp;
+
+ spin_lock_irqsave(&irlmp->log_lock, flags);
+
+ /*
+ * Remove all discoveries of devices that has previously been
+ * discovered on the same link with the same name (info), or the
+ * same daddr. We do this since some devices (mostly PDAs) change
+ * their device address between every discovery.
+ */
+ discovery = (discovery_t *) hashbin_get_first(cachelog);
+ while (discovery != NULL ) {
+ node = discovery;
+
+ /* Be sure to stay one item ahead */
+ discovery = (discovery_t *) hashbin_get_next(cachelog);
+
+ if ((node->saddr == new->saddr) &&
+ ((node->daddr == new->daddr) ||
+ (strcmp(node->nickname, new->nickname) == 0)))
+ {
+ /* This discovery is a previous discovery
+ * from the same device, so just remove it
+ */
+ hashbin_remove_this(cachelog, (irda_queue_t *) node);
+ /* Check if hints bits have changed */
+ if(node->hints.word == new->hints.word)
+ /* Set time of first discovery for this node */
+ new->first_timestamp = node->first_timestamp;
+ kfree(node);
+ }
+ }
+
+ /* Insert the new and updated version */
+ hashbin_insert(cachelog, (irda_queue_t *) new, new->daddr, NULL);
+
+ spin_unlock_irqrestore(&irlmp->log_lock, flags);
+}
+
+/*
+ * Function irlmp_add_discovery_log (cachelog, log)
+ *
+ * Merge a disovery log into the cachlog.
+ *
+ */
+void irlmp_add_discovery_log(hashbin_t *cachelog, hashbin_t *log)
+{
+ discovery_t *discovery;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ /*
+ * If log is missing this means that IrLAP was unable to perform the
+ * discovery, so restart discovery again with just the half timeout
+ * of the normal one.
+ */
+ if (log == NULL) {
+ /* irlmp_start_discovery_timer(irlmp, 150); */
+ return;
+ }
+
+ discovery = (discovery_t *) hashbin_remove_first(log);
+ while (discovery != NULL) {
+ irlmp_add_discovery(cachelog, discovery);
+
+ discovery = (discovery_t *) hashbin_remove_first(log);
+ }
+
+ /* Delete the now empty log */
+ hashbin_delete(log, (FREE_FUNC) kfree);
+}
+
+/*
+ * Function irlmp_expire_discoveries (log, saddr, force)
+ *
+ * Go through all discoveries and expire all that has stayed to long
+ *
+ * Note : this assume that IrLAP won't change its saddr, which
+ * currently is a valid assumption...
+ */
+void irlmp_expire_discoveries(hashbin_t *log, __u32 saddr, int force)
+{
+ discovery_t *discovery, *curr;
+ unsigned long flags;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ spin_lock_irqsave(&irlmp->log_lock, flags);
+
+ discovery = (discovery_t *) hashbin_get_first(log);
+ while (discovery != NULL) {
+ curr = discovery;
+
+ /* Be sure to be one item ahead */
+ discovery = (discovery_t *) hashbin_get_next(log);
+
+ /* Test if it's time to expire this discovery */
+ if ((curr->saddr == saddr) &&
+ (force ||
+ ((jiffies - curr->timestamp) > DISCOVERY_EXPIRE_TIMEOUT)))
+ {
+ /* Tell IrLMP and registered clients about it */
+ irlmp_discovery_expiry(curr);
+ /* Remove it from the log */
+ curr = hashbin_remove_this(log, (irda_queue_t *) curr);
+ if (curr)
+ kfree(curr);
+ }
+ }
+
+ spin_unlock_irqrestore(&irlmp->log_lock, flags);
+}
+
+/*
+ * Function irlmp_dump_discoveries (log)
+ *
+ * Print out all discoveries in log
+ *
+ */
+void irlmp_dump_discoveries(hashbin_t *log)
+{
+ discovery_t *discovery;
+
+ ASSERT(log != NULL, return;);
+
+ discovery = (discovery_t *) hashbin_get_first(log);
+ while (discovery != NULL) {
+ IRDA_DEBUG(0, "Discovery:\n");
+ IRDA_DEBUG(0, " daddr=%08x\n", discovery->daddr);
+ IRDA_DEBUG(0, " saddr=%08x\n", discovery->saddr);
+ IRDA_DEBUG(0, " nickname=%s\n", discovery->nickname);
+
+ discovery = (discovery_t *) hashbin_get_next(log);
+ }
+}
+
+/*
+ * Function irlmp_copy_discoveries (log, pn, mask)
+ *
+ * Copy all discoveries in a buffer
+ *
+ * This function implement a safe way for lmp clients to access the
+ * discovery log. The basic problem is that we don't want the log
+ * to change (add/remove) while the client is reading it. If the
+ * lmp client manipulate directly the hashbin, he is sure to get
+ * into troubles...
+ * The idea is that we copy all the current discovery log in a buffer
+ * which is specific to the client and pass this copy to him. As we
+ * do this operation with the spinlock grabbed, we are safe...
+ * Note : we don't want those clients to grab the spinlock, because
+ * we have no control on how long they will hold it...
+ * Note : we choose to copy the log in "struct irda_device_info" to
+ * save space...
+ * Note : the client must kfree himself() the log...
+ * Jean II
+ */
+struct irda_device_info *irlmp_copy_discoveries(hashbin_t *log, int *pn, __u16 mask)
+{
+ discovery_t * discovery;
+ unsigned long flags;
+ struct irda_device_info * buffer;
+ int i = 0;
+ int n;
+
+ ASSERT(pn != NULL, return NULL;);
+
+ /* Check if log is empty */
+ if(log == NULL)
+ return NULL;
+
+ /* Save spin lock - spinlock should be discovery specific */
+ spin_lock_irqsave(&irlmp->log_lock, flags);
+
+ /* Create the client specific buffer */
+ n = HASHBIN_GET_SIZE(log);
+ buffer = kmalloc(n * sizeof(struct irda_device_info), GFP_ATOMIC);
+ if (buffer == NULL) {
+ spin_unlock_irqrestore(&irlmp->log_lock, flags);
+ return NULL;
+ }
+
+ discovery = (discovery_t *) hashbin_get_first(log);
+ while ((discovery != NULL) && (i < n)) {
+ /* Mask out the ones we don't want */
+ if (discovery->hints.word & mask) {
+ /* Copy discovery information */
+ buffer[i].saddr = discovery->saddr;
+ buffer[i].daddr = discovery->daddr;
+ buffer[i].charset = discovery->charset;
+ buffer[i].hints[0] = discovery->hints.byte[0];
+ buffer[i].hints[1] = discovery->hints.byte[1];
+ strncpy(buffer[i].info, discovery->nickname,
+ NICKNAME_MAX_LEN);
+ i++;
+ }
+ discovery = (discovery_t *) hashbin_get_next(log);
+ }
+
+ spin_unlock_irqrestore(&irlmp->log_lock, flags);
+
+ /* Get the actual number of device in the buffer and return */
+ *pn = i;
+ return(buffer);
+}
+
+/*
+ * Function irlmp_find_device (name, saddr)
+ *
+ * Look through the discovery log at each of the links and try to find
+ * the device with the given name. Return daddr and saddr. If saddr is
+ * specified, that look at that particular link only (not impl).
+ */
+__u32 irlmp_find_device(hashbin_t *cachelog, char *name, __u32 *saddr)
+{
+ unsigned long flags;
+ discovery_t *d;
+
+ spin_lock_irqsave(&irlmp->log_lock, flags);
+
+ /* Look at all discoveries for that link */
+ d = (discovery_t *) hashbin_get_first(cachelog);
+ while (d != NULL) {
+ IRDA_DEBUG(1, "Discovery:\n");
+ IRDA_DEBUG(1, " daddr=%08x\n", d->daddr);
+ IRDA_DEBUG(1, " nickname=%s\n", d->nickname);
+
+ if (strcmp(name, d->nickname) == 0) {
+ *saddr = d->saddr;
+
+ spin_unlock_irqrestore(&irlmp->log_lock, flags);
+ return d->daddr;
+ }
+ d = (discovery_t *) hashbin_get_next(cachelog);
+ }
+
+ spin_unlock_irqrestore(&irlmp->log_lock, flags);
+
+ return 0;
+}
+
+/*
+ * Function proc_discovery_read (buf, start, offset, len, unused)
+ *
+ * Print discovery information in /proc file system
+ *
+ */
+int discovery_proc_read(char *buf, char **start, off_t offset, int length,
+ int unused)
+{
+ discovery_t *discovery;
+ unsigned long flags;
+ hashbin_t *cachelog = irlmp_get_cachelog();
+ int len = 0;
+
+ if (!irlmp)
+ return len;
+
+ len = sprintf(buf, "IrLMP: Discovery log:\n\n");
+
+ spin_lock_irqsave(&irlmp->log_lock, flags);
+
+ discovery = (discovery_t *) hashbin_get_first(cachelog);
+ while (( discovery != NULL) && (len < length)) {
+ len += sprintf(buf+len, "nickname: %s,", discovery->nickname);
+
+ len += sprintf(buf+len, " hint: 0x%02x%02x",
+ discovery->hints.byte[0],
+ discovery->hints.byte[1]);
+#if 0
+ if ( discovery->hints.byte[0] & HINT_PNP)
+ len += sprintf( buf+len, "PnP Compatible ");
+ if ( discovery->hints.byte[0] & HINT_PDA)
+ len += sprintf( buf+len, "PDA/Palmtop ");
+ if ( discovery->hints.byte[0] & HINT_COMPUTER)
+ len += sprintf( buf+len, "Computer ");
+ if ( discovery->hints.byte[0] & HINT_PRINTER)
+ len += sprintf( buf+len, "Printer ");
+ if ( discovery->hints.byte[0] & HINT_MODEM)
+ len += sprintf( buf+len, "Modem ");
+ if ( discovery->hints.byte[0] & HINT_FAX)
+ len += sprintf( buf+len, "Fax ");
+ if ( discovery->hints.byte[0] & HINT_LAN)
+ len += sprintf( buf+len, "LAN Access ");
+
+ if ( discovery->hints.byte[1] & HINT_TELEPHONY)
+ len += sprintf( buf+len, "Telephony ");
+ if ( discovery->hints.byte[1] & HINT_FILE_SERVER)
+ len += sprintf( buf+len, "File Server ");
+ if ( discovery->hints.byte[1] & HINT_COMM)
+ len += sprintf( buf+len, "IrCOMM ");
+ if ( discovery->hints.byte[1] & HINT_OBEX)
+ len += sprintf( buf+len, "IrOBEX ");
+#endif
+ len += sprintf(buf+len, ", saddr: 0x%08x",
+ discovery->saddr);
+
+ len += sprintf(buf+len, ", daddr: 0x%08x\n",
+ discovery->daddr);
+
+ len += sprintf(buf+len, "\n");
+
+ discovery = (discovery_t *) hashbin_get_next(cachelog);
+ }
+ spin_unlock_irqrestore(&irlmp->log_lock, flags);
+
+ return len;
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/ircomm/Config.in b/uClinux-2.4.31-uc0/net/irda/ircomm/Config.in
new file mode 100644
index 0000000..67f967c
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/ircomm/Config.in
@@ -0,0 +1,3 @@
+
+dep_tristate ' IrCOMM protocol' CONFIG_IRCOMM $CONFIG_IRDA
+
diff --git a/uClinux-2.4.31-uc0/net/irda/ircomm/Makefile b/uClinux-2.4.31-uc0/net/irda/ircomm/Makefile
new file mode 100644
index 0000000..12c52cf
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/ircomm/Makefile
@@ -0,0 +1,25 @@
+#
+# Makefile for the Linux IrDA IrCOMM protocol layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := ircomm_and_tty.o
+
+list-multi := ircomm.o ircomm-tty.o
+ircomm-objs := ircomm_core.o ircomm_event.o ircomm_lmp.o ircomm_ttp.o
+ircomm-tty-objs := ircomm_tty.o ircomm_tty_attach.o ircomm_tty_ioctl.o ircomm_param.o
+
+obj-$(CONFIG_IRCOMM) += ircomm.o ircomm-tty.o
+
+include $(TOPDIR)/Rules.make
+
+ircomm.o: $(ircomm-objs)
+ $(LD) $(LD_RFLAG) -r -o $@ $(ircomm-objs)
+
+ircomm-tty.o: $(ircomm-tty-objs)
+ $(LD) $(LD_RFLAG) -r -o $@ $(ircomm-tty-objs)
+
diff --git a/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_core.c b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_core.c
new file mode 100644
index 0000000..63476a2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_core.c
@@ -0,0 +1,546 @@
+/*********************************************************************
+ *
+ * Filename: ircomm_core.c
+ * Version: 1.0
+ * Description: IrCOMM service interface
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sun Jun 6 20:37:34 1999
+ * Modified at: Tue Dec 21 13:26:41 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1999 Dag Brattli, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irmod.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/iriap.h>
+#include <net/irda/irttp.h>
+#include <net/irda/irias_object.h>
+
+#include <net/irda/ircomm_event.h>
+#include <net/irda/ircomm_lmp.h>
+#include <net/irda/ircomm_ttp.h>
+#include <net/irda/ircomm_param.h>
+#include <net/irda/ircomm_core.h>
+
+static int __ircomm_close(struct ircomm_cb *self);
+static void ircomm_control_indication(struct ircomm_cb *self,
+ struct sk_buff *skb, int clen);
+
+#ifdef CONFIG_PROC_FS
+static int ircomm_proc_read(char *buf, char **start, off_t offset, int len);
+
+extern struct proc_dir_entry *proc_irda;
+#endif /* CONFIG_PROC_FS */
+
+hashbin_t *ircomm = NULL;
+
+int __init ircomm_init(void)
+{
+ ircomm = hashbin_new(HB_LOCAL);
+ if (ircomm == NULL) {
+ ERROR("%s(), can't allocate hashbin!\n", __FUNCTION__);
+ return -ENOMEM;
+ }
+
+#ifdef CONFIG_PROC_FS
+ create_proc_info_entry("ircomm", 0, proc_irda, ircomm_proc_read);
+#endif /* CONFIG_PROC_FS */
+
+ MESSAGE("IrCOMM protocol (Dag Brattli)\n");
+
+ return 0;
+}
+
+#ifdef MODULE
+void ircomm_cleanup(void)
+{
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ hashbin_delete(ircomm, (FREE_FUNC) __ircomm_close);
+
+#ifdef CONFIG_PROC_FS
+ remove_proc_entry("ircomm", proc_irda);
+#endif /* CONFIG_PROC_FS */
+}
+#endif /* MODULE */
+
+/*
+ * Function ircomm_open (client_notify)
+ *
+ * Start a new IrCOMM instance
+ *
+ */
+struct ircomm_cb *ircomm_open(notify_t *notify, __u8 service_type, int line)
+{
+ struct ircomm_cb *self = NULL;
+ int ret;
+
+ IRDA_DEBUG(2, "%s(), service_type=0x%02x\n", __FUNCTION__,
+ service_type);
+
+ ASSERT(ircomm != NULL, return NULL;);
+
+ self = kmalloc(sizeof(struct ircomm_cb), GFP_ATOMIC);
+ if (self == NULL)
+ return NULL;
+
+ memset(self, 0, sizeof(struct ircomm_cb));
+
+ self->notify = *notify;
+ self->magic = IRCOMM_MAGIC;
+
+ /* Check if we should use IrLMP or IrTTP */
+ if (service_type & IRCOMM_3_WIRE_RAW) {
+ self->flow_status = FLOW_START;
+ ret = ircomm_open_lsap(self);
+ } else
+ ret = ircomm_open_tsap(self);
+
+ if (ret < 0) {
+ kfree(self);
+ return NULL;
+ }
+
+ self->service_type = service_type;
+ self->line = line;
+
+ hashbin_insert(ircomm, (irda_queue_t *) self, line, NULL);
+
+ ircomm_next_state(self, IRCOMM_IDLE);
+
+ return self;
+}
+
+/*
+ * Function ircomm_close_instance (self)
+ *
+ * Remove IrCOMM instance
+ *
+ */
+static int __ircomm_close(struct ircomm_cb *self)
+{
+ IRDA_DEBUG(2,"%s()\n", __FUNCTION__);
+
+ /* Disconnect link if any */
+ ircomm_do_event(self, IRCOMM_DISCONNECT_REQUEST, NULL, NULL);
+
+ /* Remove TSAP */
+ if (self->tsap) {
+ irttp_close_tsap(self->tsap);
+ self->tsap = NULL;
+ }
+
+ /* Remove LSAP */
+ if (self->lsap) {
+ irlmp_close_lsap(self->lsap);
+ self->lsap = NULL;
+ }
+ self->magic = 0;
+
+ kfree(self);
+
+ return 0;
+}
+
+/*
+ * Function ircomm_close (self)
+ *
+ * Closes and removes the specified IrCOMM instance
+ *
+ */
+int ircomm_close(struct ircomm_cb *self)
+{
+ struct ircomm_cb *entry;
+
+ ASSERT(self != NULL, return -EIO;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return -EIO;);
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ entry = hashbin_remove(ircomm, self->line, NULL);
+
+ ASSERT(entry == self, return -1;);
+
+ return __ircomm_close(self);
+}
+
+/*
+ * Function ircomm_connect_request (self, service_type)
+ *
+ * Impl. of this function is differ from one of the reference. This
+ * function does discovery as well as sending connect request
+ *
+ */
+int ircomm_connect_request(struct ircomm_cb *self, __u8 dlsap_sel,
+ __u32 saddr, __u32 daddr, struct sk_buff *skb,
+ __u8 service_type)
+{
+ struct ircomm_info info;
+ int ret;
+
+ IRDA_DEBUG(2 ,"%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return -1;);
+
+ self->service_type= service_type;
+
+ info.dlsap_sel = dlsap_sel;
+ info.saddr = saddr;
+ info.daddr = daddr;
+
+ ret = ircomm_do_event(self, IRCOMM_CONNECT_REQUEST, skb, &info);
+
+ return ret;
+}
+
+/*
+ * Function ircomm_connect_indication (self, qos, skb)
+ *
+ * Notify user layer about the incoming connection
+ *
+ */
+void ircomm_connect_indication(struct ircomm_cb *self, struct sk_buff *skb,
+ struct ircomm_info *info)
+{
+ int clen = 0;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ /* Check if the packet contains data on the control channel */
+ if (skb->len > 0)
+ clen = skb->data[0];
+
+ /*
+ * If there are any data hiding in the control channel, we must
+ * deliver it first. The side effect is that the control channel
+ * will be removed from the skb
+ */
+ if (self->notify.connect_indication)
+ self->notify.connect_indication(self->notify.instance, self,
+ info->qos, info->max_data_size,
+ info->max_header_size, skb);
+ else {
+ IRDA_DEBUG(0, "%s(), missing handler\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ }
+}
+
+/*
+ * Function ircomm_connect_response (self, userdata, max_sdu_size)
+ *
+ * User accepts connection
+ *
+ */
+int ircomm_connect_response(struct ircomm_cb *self, struct sk_buff *userdata)
+{
+ int ret;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return -1;);
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ret = ircomm_do_event(self, IRCOMM_CONNECT_RESPONSE, userdata, NULL);
+
+ return ret;
+}
+
+/*
+ * Function connect_confirm (self, skb)
+ *
+ * Notify user layer that the link is now connected
+ *
+ */
+void ircomm_connect_confirm(struct ircomm_cb *self, struct sk_buff *skb,
+ struct ircomm_info *info)
+{
+ IRDA_DEBUG(4,"%s()\n", __FUNCTION__);
+
+ if (self->notify.connect_confirm )
+ self->notify.connect_confirm(self->notify.instance,
+ self, info->qos,
+ info->max_data_size,
+ info->max_header_size, skb);
+ else {
+ IRDA_DEBUG(0, "%s(), missing handler\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ }
+}
+
+/*
+ * Function ircomm_data_request (self, userdata)
+ *
+ * Send IrCOMM data to peer device
+ *
+ */
+int ircomm_data_request(struct ircomm_cb *self, struct sk_buff *skb)
+{
+ int ret;
+
+ IRDA_DEBUG(4,"%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -EFAULT;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return -EFAULT;);
+ ASSERT(skb != NULL, return -EFAULT;);
+
+ ret = ircomm_do_event(self, IRCOMM_DATA_REQUEST, skb, NULL);
+
+ return ret;
+}
+
+/*
+ * Function ircomm_data_indication (self, skb)
+ *
+ * Data arrived, so deliver it to user
+ *
+ */
+void ircomm_data_indication(struct ircomm_cb *self, struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(skb->len > 0, return;);
+
+ if (self->notify.data_indication)
+ self->notify.data_indication(self->notify.instance, self, skb);
+ else {
+ IRDA_DEBUG(0, "%s(), missing handler\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ }
+}
+
+/*
+ * Function ircomm_process_data (self, skb)
+ *
+ * Data arrived which may contain control channel data
+ *
+ */
+void ircomm_process_data(struct ircomm_cb *self, struct sk_buff *skb)
+{
+ int clen;
+
+ ASSERT(skb->len > 0, return;);
+
+ clen = skb->data[0];
+
+ /*
+ * If there are any data hiding in the control channel, we must
+ * deliver it first. The side effect is that the control channel
+ * will be removed from the skb
+ */
+ if (clen > 0)
+ ircomm_control_indication(self, skb, clen);
+
+ /* Remove control channel from data channel */
+ skb_pull(skb, clen+1);
+
+ if (skb->len)
+ ircomm_data_indication(self, skb);
+ else {
+ IRDA_DEBUG(4, "%s(), data was control info only!\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ }
+}
+
+/*
+ * Function ircomm_control_request (self, params)
+ *
+ * Send control data to peer device
+ *
+ */
+int ircomm_control_request(struct ircomm_cb *self, struct sk_buff *skb)
+{
+ int ret;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -EFAULT;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return -EFAULT;);
+ ASSERT(skb != NULL, return -EFAULT;);
+
+ ret = ircomm_do_event(self, IRCOMM_CONTROL_REQUEST, skb, NULL);
+
+ return ret;
+}
+
+/*
+ * Function ircomm_control_indication (self, skb)
+ *
+ * Data has arrived on the control channel
+ *
+ */
+static void ircomm_control_indication(struct ircomm_cb *self,
+ struct sk_buff *skb, int clen)
+{
+ struct sk_buff *ctrl_skb;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ctrl_skb = skb_clone(skb, GFP_ATOMIC);
+ if (!ctrl_skb)
+ return;
+
+ /* Remove data channel from control channel */
+ skb_trim(ctrl_skb, clen+1);
+
+ /* Use udata for delivering data on the control channel */
+ if (self->notify.udata_indication)
+ self->notify.udata_indication(self->notify.instance, self,
+ ctrl_skb);
+ else {
+ IRDA_DEBUG(0, "%s(), missing handler\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ }
+}
+
+/*
+ * Function ircomm_disconnect_request (self, userdata, priority)
+ *
+ * User layer wants to disconnect the IrCOMM connection
+ *
+ */
+int ircomm_disconnect_request(struct ircomm_cb *self, struct sk_buff *userdata)
+{
+ struct ircomm_info info;
+ int ret;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return -1;);
+
+ ret = ircomm_do_event(self, IRCOMM_DISCONNECT_REQUEST, userdata,
+ &info);
+ return ret;
+}
+
+/*
+ * Function disconnect_indication (self, skb)
+ *
+ * Tell user that the link has been disconnected
+ *
+ */
+void ircomm_disconnect_indication(struct ircomm_cb *self, struct sk_buff *skb,
+ struct ircomm_info *info)
+{
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(info != NULL, return;);
+
+ if (self->notify.disconnect_indication) {
+ self->notify.disconnect_indication(self->notify.instance, self,
+ info->reason, skb);
+ } else {
+ IRDA_DEBUG(0, "%s(), missing handler\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ }
+}
+
+/*
+ * Function ircomm_flow_request (self, flow)
+ *
+ *
+ *
+ */
+void ircomm_flow_request(struct ircomm_cb *self, LOCAL_FLOW flow)
+{
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return;);
+
+ if (self->service_type == IRCOMM_3_WIRE_RAW)
+ return;
+
+ irttp_flow_request(self->tsap, flow);
+}
+
+#ifdef CONFIG_PROC_FS
+/*
+ * Function ircomm_proc_read (buf, start, offset, len, unused)
+ *
+ *
+ *
+ */
+int ircomm_proc_read(char *buf, char **start, off_t offset, int len)
+{
+ struct ircomm_cb *self;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ len = 0;
+
+ self = (struct ircomm_cb *) hashbin_get_first(ircomm);
+ while (self != NULL) {
+ ASSERT(self->magic == IRCOMM_MAGIC, break;);
+
+ if(self->line < 0x10)
+ len += sprintf(buf+len, "ircomm%d", self->line);
+ else
+ len += sprintf(buf+len, "irlpt%d", self->line - 0x10);
+ len += sprintf(buf+len, " state: %s, ",
+ ircomm_state[ self->state]);
+ len += sprintf(buf+len,
+ "slsap_sel: %#02x, dlsap_sel: %#02x, mode:",
+ self->slsap_sel, self->dlsap_sel);
+ if(self->service_type & IRCOMM_3_WIRE_RAW)
+ len += sprintf(buf+len, " 3-wire-raw");
+ if(self->service_type & IRCOMM_3_WIRE)
+ len += sprintf(buf+len, " 3-wire");
+ if(self->service_type & IRCOMM_9_WIRE)
+ len += sprintf(buf+len, " 9-wire");
+ if(self->service_type & IRCOMM_CENTRONICS)
+ len += sprintf(buf+len, " Centronics");
+ len += sprintf(buf+len, "\n");
+
+ self = (struct ircomm_cb *) hashbin_get_next(ircomm);
+ }
+ restore_flags(flags);
+
+ return len;
+}
+#endif /* CONFIG_PROC_FS */
+
+#ifdef MODULE
+MODULE_AUTHOR("Dag Brattli <dag@brattli.net>");
+MODULE_DESCRIPTION("IrCOMM protocol");
+MODULE_LICENSE("GPL");
+
+int init_module(void)
+{
+ return ircomm_init();
+}
+
+void cleanup_module(void)
+{
+ ircomm_cleanup();
+}
+#endif /* MODULE */
+
diff --git a/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_event.c b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_event.c
new file mode 100644
index 0000000..b75cf40
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_event.c
@@ -0,0 +1,257 @@
+/*********************************************************************
+ *
+ * Filename: ircomm_event.c
+ * Version: 1.0
+ * Description: IrCOMM layer state machine
+ * Status: Stable
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sun Jun 6 20:33:11 1999
+ * Modified at: Sun Dec 12 13:44:32 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1999 Dag Brattli, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ ********************************************************************/
+
+#include <linux/sched.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/iriap.h>
+#include <net/irda/irttp.h>
+#include <net/irda/irias_object.h>
+
+#include <net/irda/ircomm_core.h>
+#include <net/irda/ircomm_event.h>
+
+static int ircomm_state_idle(struct ircomm_cb *self, IRCOMM_EVENT event,
+ struct sk_buff *skb, struct ircomm_info *info);
+static int ircomm_state_waiti(struct ircomm_cb *self, IRCOMM_EVENT event,
+ struct sk_buff *skb, struct ircomm_info *info);
+static int ircomm_state_waitr(struct ircomm_cb *self, IRCOMM_EVENT event,
+ struct sk_buff *skb, struct ircomm_info *info);
+static int ircomm_state_conn(struct ircomm_cb *self, IRCOMM_EVENT event,
+ struct sk_buff *skb, struct ircomm_info *info);
+
+char *ircomm_state[] = {
+ "IRCOMM_IDLE",
+ "IRCOMM_WAITI",
+ "IRCOMM_WAITR",
+ "IRCOMM_CONN",
+};
+
+char *ircomm_event[] = {
+ "IRCOMM_CONNECT_REQUEST",
+ "IRCOMM_CONNECT_RESPONSE",
+ "IRCOMM_TTP_CONNECT_INDICATION",
+ "IRCOMM_LMP_CONNECT_INDICATION",
+ "IRCOMM_TTP_CONNECT_CONFIRM",
+ "IRCOMM_LMP_CONNECT_CONFIRM",
+
+ "IRCOMM_LMP_DISCONNECT_INDICATION",
+ "IRCOMM_TTP_DISCONNECT_INDICATION",
+ "IRCOMM_DISCONNECT_REQUEST",
+
+ "IRCOMM_TTP_DATA_INDICATION",
+ "IRCOMM_LMP_DATA_INDICATION",
+ "IRCOMM_DATA_REQUEST",
+ "IRCOMM_CONTROL_REQUEST",
+ "IRCOMM_CONTROL_INDICATION",
+};
+
+static int (*state[])(struct ircomm_cb *self, IRCOMM_EVENT event,
+ struct sk_buff *skb, struct ircomm_info *info) =
+{
+ ircomm_state_idle,
+ ircomm_state_waiti,
+ ircomm_state_waitr,
+ ircomm_state_conn,
+};
+
+/*
+ * Function ircomm_state_idle (self, event, skb)
+ *
+ * IrCOMM is currently idle
+ *
+ */
+static int ircomm_state_idle(struct ircomm_cb *self, IRCOMM_EVENT event,
+ struct sk_buff *skb, struct ircomm_info *info)
+{
+ int ret = 0;
+
+ switch (event) {
+ case IRCOMM_CONNECT_REQUEST:
+ ircomm_next_state(self, IRCOMM_WAITI);
+ ret = self->issue.connect_request(self, skb, info);
+ break;
+ case IRCOMM_TTP_CONNECT_INDICATION:
+ case IRCOMM_LMP_CONNECT_INDICATION:
+ ircomm_next_state(self, IRCOMM_WAITR);
+ ircomm_connect_indication(self, skb, info);
+ break;
+ default:
+ IRDA_DEBUG(4,"%s(), unknown event: %s\n", __FUNCTION__,
+ ircomm_event[event]);
+ if (skb)
+ dev_kfree_skb(skb);
+ return -EINVAL;
+ }
+ return ret;
+}
+
+/*
+ * Function ircomm_state_waiti (self, event, skb)
+ *
+ * The IrCOMM user has requested an IrCOMM connection to the remote
+ * device and is awaiting confirmation
+ */
+static int ircomm_state_waiti(struct ircomm_cb *self, IRCOMM_EVENT event,
+ struct sk_buff *skb, struct ircomm_info *info)
+{
+ int ret = 0;
+
+ switch (event) {
+ case IRCOMM_TTP_CONNECT_CONFIRM:
+ case IRCOMM_LMP_CONNECT_CONFIRM:
+ ircomm_next_state(self, IRCOMM_CONN);
+ ircomm_connect_confirm(self, skb, info);
+ break;
+ case IRCOMM_TTP_DISCONNECT_INDICATION:
+ case IRCOMM_LMP_DISCONNECT_INDICATION:
+ ircomm_next_state(self, IRCOMM_IDLE);
+ ircomm_disconnect_indication(self, skb, info);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), unknown event: %s\n", __FUNCTION__,
+ ircomm_event[event]);
+ if (skb)
+ dev_kfree_skb(skb);
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+/*
+ * Function ircomm_state_waitr (self, event, skb)
+ *
+ * IrCOMM has received an incoming connection request and is awaiting
+ * response from the user
+ */
+static int ircomm_state_waitr(struct ircomm_cb *self, IRCOMM_EVENT event,
+ struct sk_buff *skb, struct ircomm_info *info)
+{
+ int ret = 0;
+
+ switch (event) {
+ case IRCOMM_CONNECT_RESPONSE:
+ ircomm_next_state(self, IRCOMM_CONN);
+ ret = self->issue.connect_response(self, skb);
+ break;
+ case IRCOMM_DISCONNECT_REQUEST:
+ ircomm_next_state(self, IRCOMM_IDLE);
+ ret = self->issue.disconnect_request(self, skb, info);
+ break;
+ case IRCOMM_TTP_DISCONNECT_INDICATION:
+ case IRCOMM_LMP_DISCONNECT_INDICATION:
+ ircomm_next_state(self, IRCOMM_IDLE);
+ ircomm_disconnect_indication(self, skb, info);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), unknown event = %s\n", __FUNCTION__,
+ ircomm_event[event]);
+ if (skb)
+ dev_kfree_skb(skb);
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+/*
+ * Function ircomm_state_conn (self, event, skb)
+ *
+ * IrCOMM is connected to the peer IrCOMM device
+ *
+ */
+static int ircomm_state_conn(struct ircomm_cb *self, IRCOMM_EVENT event,
+ struct sk_buff *skb, struct ircomm_info *info)
+{
+ int ret = 0;
+
+ switch (event) {
+ case IRCOMM_DATA_REQUEST:
+ ret = self->issue.data_request(self, skb, 0);
+ break;
+ case IRCOMM_TTP_DATA_INDICATION:
+ ircomm_process_data(self, skb);
+ break;
+ case IRCOMM_LMP_DATA_INDICATION:
+ ircomm_data_indication(self, skb);
+ break;
+ case IRCOMM_CONTROL_REQUEST:
+ /* Just send a separate frame for now */
+ ret = self->issue.data_request(self, skb, skb->len);
+ break;
+ case IRCOMM_TTP_DISCONNECT_INDICATION:
+ case IRCOMM_LMP_DISCONNECT_INDICATION:
+ ircomm_next_state(self, IRCOMM_IDLE);
+ ircomm_disconnect_indication(self, skb, info);
+ break;
+ case IRCOMM_DISCONNECT_REQUEST:
+ ircomm_next_state(self, IRCOMM_IDLE);
+ ret = self->issue.disconnect_request(self, skb, info);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), unknown event = %s\n", __FUNCTION__,
+ ircomm_event[event]);
+ if (skb)
+ dev_kfree_skb(skb);
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+/*
+ * Function ircomm_do_event (self, event, skb)
+ *
+ * Process event
+ *
+ */
+int ircomm_do_event(struct ircomm_cb *self, IRCOMM_EVENT event,
+ struct sk_buff *skb, struct ircomm_info *info)
+{
+ IRDA_DEBUG(4, "%s: state=%s, event=%s\n", __FUNCTION__,
+ ircomm_state[self->state], ircomm_event[event]);
+
+ return (*state[self->state])(self, event, skb, info);
+}
+
+/*
+ * Function ircomm_next_state (self, state)
+ *
+ * Switch state
+ *
+ */
+void ircomm_next_state(struct ircomm_cb *self, IRCOMM_STATE state)
+{
+ self->state = state;
+
+ IRDA_DEBUG(4, "%s: next state=%s, service type=%d\n", __FUNCTION__,
+ ircomm_state[self->state], self->service_type);
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_lmp.c b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_lmp.c
new file mode 100644
index 0000000..3dfe78f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_lmp.c
@@ -0,0 +1,340 @@
+/*********************************************************************
+ *
+ * Filename: ircomm_lmp.c
+ * Version: 1.0
+ * Description: Interface between IrCOMM and IrLMP
+ * Status: Stable
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sun Jun 6 20:48:27 1999
+ * Modified at: Sun Dec 12 13:44:17 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ * Sources: Previous IrLPT work by Thomas Davis
+ *
+ * Copyright (c) 1999 Dag Brattli, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ ********************************************************************/
+
+#include <linux/sched.h>
+#include <linux/init.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/iriap.h>
+
+#include <net/irda/ircomm_event.h>
+#include <net/irda/ircomm_lmp.h>
+
+/*
+ * Function ircomm_open_lsap (self)
+ *
+ * Open LSAP. This function will only be used when using "raw" services
+ *
+ */
+int ircomm_open_lsap(struct ircomm_cb *self)
+{
+ notify_t notify;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ /* Register callbacks */
+ irda_notify_init(&notify);
+ notify.data_indication = ircomm_lmp_data_indication;
+ notify.connect_confirm = ircomm_lmp_connect_confirm;
+ notify.connect_indication = ircomm_lmp_connect_indication;
+ notify.disconnect_indication = ircomm_lmp_disconnect_indication;
+ notify.instance = self;
+ strncpy(notify.name, "IrCOMM", NOTIFY_MAX_NAME);
+
+ self->lsap = irlmp_open_lsap(LSAP_ANY, &notify, 0);
+ if (!self->lsap) {
+ IRDA_DEBUG(0, "%s failed to allocate tsap\n", __FUNCTION__);
+ return -1;
+ }
+ self->slsap_sel = self->lsap->slsap_sel;
+
+ /*
+ * Initialize the call-table for issuing commands
+ */
+ self->issue.data_request = ircomm_lmp_data_request;
+ self->issue.connect_request = ircomm_lmp_connect_request;
+ self->issue.connect_response = ircomm_lmp_connect_response;
+ self->issue.disconnect_request = ircomm_lmp_disconnect_request;
+
+ return 0;
+}
+
+/*
+ * Function ircomm_lmp_connect_request (self, userdata)
+ *
+ *
+ *
+ */
+int ircomm_lmp_connect_request(struct ircomm_cb *self,
+ struct sk_buff *userdata,
+ struct ircomm_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ ret = irlmp_connect_request(self->lsap, info->dlsap_sel,
+ info->saddr, info->daddr, NULL, userdata);
+ return ret;
+}
+
+/*
+ * Function ircomm_lmp_connect_response (self, skb)
+ *
+ *
+ *
+ */
+int ircomm_lmp_connect_response(struct ircomm_cb *self, struct sk_buff *userdata)
+{
+ struct sk_buff *skb;
+ int ret;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ /* Any userdata supplied? */
+ if (userdata == NULL) {
+ skb = dev_alloc_skb(64);
+ if (!skb)
+ return -ENOMEM;
+
+ /* Reserve space for MUX and LAP header */
+ skb_reserve(skb, LMP_MAX_HEADER);
+ } else {
+ skb = userdata;
+ /*
+ * Check that the client has reserved enough space for
+ * headers
+ */
+ ASSERT(skb_headroom(skb) >= LMP_MAX_HEADER, return -1;);
+ }
+
+ ret = irlmp_connect_response(self->lsap, skb);
+
+ return 0;
+}
+
+int ircomm_lmp_disconnect_request(struct ircomm_cb *self,
+ struct sk_buff *userdata,
+ struct ircomm_info *info)
+{
+ struct sk_buff *skb;
+ int ret;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ if (!userdata) {
+ skb = dev_alloc_skb(64);
+ if (!skb)
+ return -ENOMEM;
+
+ /* Reserve space for MUX and LAP header */
+ skb_reserve(skb, LMP_MAX_HEADER);
+ userdata = skb;
+ }
+ ret = irlmp_disconnect_request(self->lsap, userdata);
+
+ return ret;
+}
+
+/*
+ * Function ircomm_lmp_flow_control (skb)
+ *
+ * This function is called when a data frame we have sent to IrLAP has
+ * been deallocated. We do this to make sure we don't flood IrLAP with
+ * frames, since we are not using the IrTTP flow control mechanism
+ */
+void ircomm_lmp_flow_control(struct sk_buff *skb)
+{
+ struct irda_skb_cb *cb;
+ struct ircomm_cb *self;
+ int line;
+
+ ASSERT(skb != NULL, return;);
+
+ cb = (struct irda_skb_cb *) skb->cb;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ line = cb->line;
+
+ self = (struct ircomm_cb *) hashbin_find(ircomm, line, NULL);
+ if (!self) {
+ IRDA_DEBUG(2, "%s(), didn't find myself\n", __FUNCTION__);
+ return;
+ }
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return;);
+
+ self->pkt_count--;
+
+ if ((self->pkt_count < 2) && (self->flow_status == FLOW_STOP)) {
+ IRDA_DEBUG(2, "%s(), asking TTY to start again!\n", __FUNCTION__);
+ self->flow_status = FLOW_START;
+ if (self->notify.flow_indication)
+ self->notify.flow_indication(self->notify.instance,
+ self, FLOW_START);
+ }
+}
+
+/*
+ * Function ircomm_lmp_data_request (self, userdata)
+ *
+ * Send data frame to peer device
+ *
+ */
+int ircomm_lmp_data_request(struct ircomm_cb *self, struct sk_buff *skb,
+ int not_used)
+{
+ struct irda_skb_cb *cb;
+ int ret;
+
+ ASSERT(skb != NULL, return -1;);
+
+ cb = (struct irda_skb_cb *) skb->cb;
+
+ cb->line = self->line;
+
+ IRDA_DEBUG(4, "%s(), sending frame\n", __FUNCTION__);
+
+ skb->destructor = ircomm_lmp_flow_control;
+
+ if ((self->pkt_count++ > 7) && (self->flow_status == FLOW_START)) {
+ IRDA_DEBUG(2, "%s(), asking TTY to slow down!\n", __FUNCTION__);
+ self->flow_status = FLOW_STOP;
+ if (self->notify.flow_indication)
+ self->notify.flow_indication(self->notify.instance,
+ self, FLOW_STOP);
+ }
+ ret = irlmp_data_request(self->lsap, skb);
+ if (ret) {
+ ERROR("%s(), failed\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ }
+
+ return ret;
+}
+
+/*
+ * Function ircomm_lmp_data_indication (instance, sap, skb)
+ *
+ * Incoming data which we must deliver to the state machine, to check
+ * we are still connected.
+ */
+int ircomm_lmp_data_indication(void *instance, void *sap,
+ struct sk_buff *skb)
+{
+ struct ircomm_cb *self = (struct ircomm_cb *) instance;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return -1;);
+ ASSERT(skb != NULL, return -1;);
+
+ ircomm_do_event(self, IRCOMM_LMP_DATA_INDICATION, skb, NULL);
+
+ return 0;
+}
+
+/*
+ * Function ircomm_lmp_connect_confirm (instance, sap, qos, max_sdu_size,
+ * max_header_size, skb)
+ *
+ * Connection has been confirmed by peer device
+ *
+ */
+void ircomm_lmp_connect_confirm(void *instance, void *sap,
+ struct qos_info *qos,
+ __u32 max_seg_size,
+ __u8 max_header_size,
+ struct sk_buff *skb)
+{
+ struct ircomm_cb *self = (struct ircomm_cb *) instance;
+ struct ircomm_info info;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+ ASSERT(qos != NULL, return;);
+
+ info.max_data_size = max_seg_size;
+ info.max_header_size = max_header_size;
+ info.qos = qos;
+
+ ircomm_do_event(self, IRCOMM_LMP_CONNECT_CONFIRM, skb, &info);
+}
+
+/*
+ * Function ircomm_lmp_connect_indication (instance, sap, qos, max_sdu_size,
+ * max_header_size, skb)
+ *
+ * Peer device wants to make a connection with us
+ *
+ */
+void ircomm_lmp_connect_indication(void *instance, void *sap,
+ struct qos_info *qos,
+ __u32 max_seg_size,
+ __u8 max_header_size,
+ struct sk_buff *skb)
+{
+ struct ircomm_cb *self = (struct ircomm_cb *)instance;
+ struct ircomm_info info;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+ ASSERT(qos != NULL, return;);
+
+ info.max_data_size = max_seg_size;
+ info.max_header_size = max_header_size;
+ info.qos = qos;
+
+ ircomm_do_event(self, IRCOMM_LMP_CONNECT_INDICATION, skb, &info);
+}
+
+/*
+ * Function ircomm_lmp_disconnect_indication (instance, sap, reason, skb)
+ *
+ * Peer device has closed the connection, or the link went down for some
+ * other reason
+ */
+void ircomm_lmp_disconnect_indication(void *instance, void *sap,
+ LM_REASON reason,
+ struct sk_buff *skb)
+{
+ struct ircomm_cb *self = (struct ircomm_cb *) instance;
+ struct ircomm_info info;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return;);
+
+ info.reason = reason;
+
+ ircomm_do_event(self, IRCOMM_LMP_DISCONNECT_INDICATION, skb, &info);
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_param.c b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_param.c
new file mode 100644
index 0000000..0109075
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_param.c
@@ -0,0 +1,524 @@
+/*********************************************************************
+ *
+ * Filename: ircomm_param.c
+ * Version: 1.0
+ * Description: Parameter handling for the IrCOMM protocol
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Mon Jun 7 10:25:11 1999
+ * Modified at: Sun Jan 30 14:32:03 2000
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1999-2000 Dag Brattli, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ ********************************************************************/
+
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/parameters.h>
+
+#include <net/irda/ircomm_core.h>
+#include <net/irda/ircomm_tty_attach.h>
+#include <net/irda/ircomm_tty.h>
+
+#include <net/irda/ircomm_param.h>
+
+static int ircomm_param_service_type(void *instance, irda_param_t *param,
+ int get);
+static int ircomm_param_port_type(void *instance, irda_param_t *param,
+ int get);
+static int ircomm_param_port_name(void *instance, irda_param_t *param,
+ int get);
+static int ircomm_param_service_type(void *instance, irda_param_t *param,
+ int get);
+static int ircomm_param_data_rate(void *instance, irda_param_t *param,
+ int get);
+static int ircomm_param_data_format(void *instance, irda_param_t *param,
+ int get);
+static int ircomm_param_flow_control(void *instance, irda_param_t *param,
+ int get);
+static int ircomm_param_xon_xoff(void *instance, irda_param_t *param, int get);
+static int ircomm_param_enq_ack(void *instance, irda_param_t *param, int get);
+static int ircomm_param_line_status(void *instance, irda_param_t *param,
+ int get);
+static int ircomm_param_dte(void *instance, irda_param_t *param, int get);
+static int ircomm_param_dce(void *instance, irda_param_t *param, int get);
+static int ircomm_param_poll(void *instance, irda_param_t *param, int get);
+
+static pi_minor_info_t pi_minor_call_table_common[] = {
+ { ircomm_param_service_type, PV_INT_8_BITS },
+ { ircomm_param_port_type, PV_INT_8_BITS },
+ { ircomm_param_port_name, PV_STRING }
+};
+static pi_minor_info_t pi_minor_call_table_non_raw[] = {
+ { ircomm_param_data_rate, PV_INT_32_BITS | PV_BIG_ENDIAN },
+ { ircomm_param_data_format, PV_INT_8_BITS },
+ { ircomm_param_flow_control, PV_INT_8_BITS },
+ { ircomm_param_xon_xoff, PV_INT_16_BITS },
+ { ircomm_param_enq_ack, PV_INT_16_BITS },
+ { ircomm_param_line_status, PV_INT_8_BITS }
+};
+static pi_minor_info_t pi_minor_call_table_9_wire[] = {
+ { ircomm_param_dte, PV_INT_8_BITS },
+ { ircomm_param_dce, PV_INT_8_BITS },
+ { ircomm_param_poll, PV_NO_VALUE },
+};
+
+static pi_major_info_t pi_major_call_table[] = {
+ { pi_minor_call_table_common, 3 },
+ { pi_minor_call_table_non_raw, 6 },
+ { pi_minor_call_table_9_wire, 3 }
+/* { pi_minor_call_table_centronics } */
+};
+
+pi_param_info_t ircomm_param_info = { pi_major_call_table, 3, 0x0f, 4 };
+
+/*
+ * Function ircomm_param_flush (self)
+ *
+ * Flush (send) out all queued parameters
+ *
+ */
+int ircomm_param_flush(struct ircomm_tty_cb *self)
+{
+ if (self->ctrl_skb) {
+ ircomm_control_request(self->ircomm, self->ctrl_skb);
+ self->ctrl_skb = NULL;
+ }
+ return 0;
+}
+
+/*
+ * Function ircomm_param_request (self, pi, flush)
+ *
+ * Queue a parameter for the control channel
+ *
+ */
+int ircomm_param_request(struct ircomm_tty_cb *self, __u8 pi, int flush)
+{
+ struct tty_struct *tty;
+ unsigned long flags;
+ struct sk_buff *skb;
+ int count;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ tty = self->tty;
+ if (!tty)
+ return 0;
+
+ /* Make sure we don't send parameters for raw mode */
+ if (self->service_type == IRCOMM_3_WIRE_RAW)
+ return 0;
+
+ save_flags(flags);
+ cli();
+
+ skb = self->ctrl_skb;
+ if (!skb) {
+ skb = dev_alloc_skb(256);
+ if (!skb) {
+ restore_flags(flags);
+ return -ENOMEM;
+ }
+
+ skb_reserve(skb, self->max_header_size);
+ self->ctrl_skb = skb;
+ }
+ /*
+ * Inserting is a little bit tricky since we don't know how much
+ * room we will need. But this should hopefully work OK
+ */
+ count = irda_param_insert(self, pi, skb->tail, skb_tailroom(skb),
+ &ircomm_param_info);
+ if (count < 0) {
+ WARNING("%s(), no room for parameter!\n", __FUNCTION__);
+ restore_flags(flags);
+ return -1;
+ }
+ skb_put(skb, count);
+
+ restore_flags(flags);
+
+ IRDA_DEBUG(2, "%s(), skb->len=%d\n", __FUNCTION__, skb->len);
+
+ if (flush) {
+ /* ircomm_tty_do_softint will take care of the rest */
+ queue_task(&self->tqueue, &tq_immediate);
+ mark_bh(IMMEDIATE_BH);
+ }
+
+ return count;
+}
+
+/*
+ * Function ircomm_param_service_type (self, buf, len)
+ *
+ * Handle service type, this function will both be called after the LM-IAS
+ * query and then the remote device sends its initial paramters
+ *
+ */
+static int ircomm_param_service_type(void *instance, irda_param_t *param,
+ int get)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+ __u8 service_type = (__u8) param->pv.i;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ if (get) {
+ param->pv.i = self->settings.service_type;
+ return 0;
+ }
+
+ /* Find all common service types */
+ service_type &= self->service_type;
+ if (!service_type) {
+ IRDA_DEBUG(2, "%s(), No common service type to use!\n", __FUNCTION__);
+ return -1;
+ }
+ IRDA_DEBUG(0, "%s(), services in common=%02x\n", __FUNCTION__ ,
+ service_type);
+
+ /*
+ * Now choose a preferred service type of those available
+ */
+ if (service_type & IRCOMM_CENTRONICS)
+ self->settings.service_type = IRCOMM_CENTRONICS;
+ else if (service_type & IRCOMM_9_WIRE)
+ self->settings.service_type = IRCOMM_9_WIRE;
+ else if (service_type & IRCOMM_3_WIRE)
+ self->settings.service_type = IRCOMM_3_WIRE;
+ else if (service_type & IRCOMM_3_WIRE_RAW)
+ self->settings.service_type = IRCOMM_3_WIRE_RAW;
+
+ IRDA_DEBUG(0, "%s(), resulting service type=0x%02x\n", __FUNCTION__,
+ self->settings.service_type);
+
+ /*
+ * Now the line is ready for some communication. Check if we are a
+ * server, and send over some initial parameters.
+ * Client do it in ircomm_tty_state_setup().
+ * Note : we may get called from ircomm_tty_getvalue_confirm(),
+ * therefore before we even have open any socket. And self->client
+ * is initialised to TRUE only later. So, we check if the link is
+ * really initialised. - Jean II
+ */
+ if ((self->max_header_size != IRCOMM_TTY_HDR_UNINITIALISED) &&
+ (!self->client) &&
+ (self->settings.service_type != IRCOMM_3_WIRE_RAW))
+ {
+ /* Init connection */
+ ircomm_tty_send_initial_parameters(self);
+ ircomm_tty_link_established(self);
+ }
+
+ return 0;
+}
+
+/*
+ * Function ircomm_param_port_type (self, param)
+ *
+ * The port type parameter tells if the devices are serial or parallel.
+ * Since we only advertise serial service, this parameter should only
+ * be equal to IRCOMM_SERIAL.
+ */
+static int ircomm_param_port_type(void *instance, irda_param_t *param, int get)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ if (get)
+ param->pv.i = IRCOMM_SERIAL;
+ else {
+ self->settings.port_type = (__u8) param->pv.i;
+
+ IRDA_DEBUG(0, "%s(), port type=%d\n", __FUNCTION__,
+ self->settings.port_type);
+ }
+ return 0;
+}
+
+/*
+ * Function ircomm_param_port_name (self, param)
+ *
+ * Exchange port name
+ *
+ */
+static int ircomm_param_port_name(void *instance, irda_param_t *param, int get)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ if (get) {
+ IRDA_DEBUG(0, "%s(), not imp!\n", __FUNCTION__);
+ } else {
+ IRDA_DEBUG(0, "%s(), port-name=%s\n", __FUNCTION__, param->pv.c);
+ strncpy(self->settings.port_name, param->pv.c, 32);
+ }
+
+ return 0;
+}
+
+/*
+ * Function ircomm_param_data_rate (self, param)
+ *
+ * Exchange data rate to be used in this settings
+ *
+ */
+static int ircomm_param_data_rate(void *instance, irda_param_t *param, int get)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ if (get)
+ param->pv.i = self->settings.data_rate;
+ else
+ self->settings.data_rate = param->pv.i;
+
+ IRDA_DEBUG(2, "%s(), data rate = %d\n", __FUNCTION__, param->pv.i);
+
+ return 0;
+}
+
+/*
+ * Function ircomm_param_data_format (self, param)
+ *
+ * Exchange data format to be used in this settings
+ *
+ */
+static int ircomm_param_data_format(void *instance, irda_param_t *param,
+ int get)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ if (get)
+ param->pv.i = self->settings.data_format;
+ else
+ self->settings.data_format = (__u8) param->pv.i;
+
+ return 0;
+}
+
+/*
+ * Function ircomm_param_flow_control (self, param)
+ *
+ * Exchange flow control settings to be used in this settings
+ *
+ */
+static int ircomm_param_flow_control(void *instance, irda_param_t *param,
+ int get)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ if (get)
+ param->pv.i = self->settings.flow_control;
+ else
+ self->settings.flow_control = (__u8) param->pv.i;
+
+ IRDA_DEBUG(1, "%s(), flow control = 0x%02x\n", __FUNCTION__, (__u8) param->pv.i);
+
+ return 0;
+}
+
+/*
+ * Function ircomm_param_xon_xoff (self, param)
+ *
+ * Exchange XON/XOFF characters
+ *
+ */
+static int ircomm_param_xon_xoff(void *instance, irda_param_t *param, int get)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ if (get) {
+ param->pv.i = self->settings.xonxoff[0];
+ param->pv.i |= self->settings.xonxoff[1] << 8;
+ } else {
+ self->settings.xonxoff[0] = (__u16) param->pv.i & 0xff;
+ self->settings.xonxoff[1] = (__u16) param->pv.i >> 8;
+ }
+
+ IRDA_DEBUG(0, "%s(), XON/XOFF = 0x%02x,0x%02x\n", __FUNCTION__,
+ param->pv.i & 0xff, param->pv.i >> 8);
+
+ return 0;
+}
+
+/*
+ * Function ircomm_param_enq_ack (self, param)
+ *
+ * Exchange ENQ/ACK characters
+ *
+ */
+static int ircomm_param_enq_ack(void *instance, irda_param_t *param, int get)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ if (get) {
+ param->pv.i = self->settings.enqack[0];
+ param->pv.i |= self->settings.enqack[1] << 8;
+ } else {
+ self->settings.enqack[0] = (__u16) param->pv.i & 0xff;
+ self->settings.enqack[1] = (__u16) param->pv.i >> 8;
+ }
+
+ IRDA_DEBUG(0, "%s(), ENQ/ACK = 0x%02x,0x%02x\n", __FUNCTION__,
+ param->pv.i & 0xff, param->pv.i >> 8);
+
+ return 0;
+}
+
+/*
+ * Function ircomm_param_line_status (self, param)
+ *
+ *
+ *
+ */
+static int ircomm_param_line_status(void *instance, irda_param_t *param,
+ int get)
+{
+ IRDA_DEBUG(2, "%s(), not impl.\n", __FUNCTION__);
+
+ return 0;
+}
+
+/*
+ * Function ircomm_param_dte (instance, param)
+ *
+ * If we get here, there must be some sort of null-modem connection, and
+ * we are probably working in server mode as well.
+ */
+static int ircomm_param_dte(void *instance, irda_param_t *param, int get)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+ __u8 dte;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ if (get)
+ param->pv.i = self->settings.dte;
+ else {
+ dte = (__u8) param->pv.i;
+
+ if (dte & IRCOMM_DELTA_DTR)
+ self->settings.dce |= (IRCOMM_DELTA_DSR|
+ IRCOMM_DELTA_RI |
+ IRCOMM_DELTA_CD);
+ if (dte & IRCOMM_DTR)
+ self->settings.dce |= (IRCOMM_DSR|
+ IRCOMM_RI |
+ IRCOMM_CD);
+
+ if (dte & IRCOMM_DELTA_RTS)
+ self->settings.dce |= IRCOMM_DELTA_CTS;
+ if (dte & IRCOMM_RTS)
+ self->settings.dce |= IRCOMM_CTS;
+
+ /* Take appropriate actions */
+ ircomm_tty_check_modem_status(self);
+
+ /* Null modem cable emulator */
+ self->settings.null_modem = TRUE;
+ }
+
+ return 0;
+}
+
+/*
+ * Function ircomm_param_dce (instance, param)
+ *
+ *
+ *
+ */
+static int ircomm_param_dce(void *instance, irda_param_t *param, int get)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+ __u8 dce;
+
+ IRDA_DEBUG(1, "%s(), dce = 0x%02x\n", __FUNCTION__, (__u8) param->pv.i);
+
+ dce = (__u8) param->pv.i;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ self->settings.dce = dce;
+
+ /* Check if any of the settings have changed */
+ if (dce & 0x0f) {
+ if (dce & IRCOMM_DELTA_CTS) {
+ IRDA_DEBUG(2, "%s(), CTS \n", __FUNCTION__);
+ }
+ }
+
+ ircomm_tty_check_modem_status(self);
+
+ return 0;
+}
+
+/*
+ * Function ircomm_param_poll (instance, param)
+ *
+ * Called when the peer device is polling for the line settings
+ *
+ */
+static int ircomm_param_poll(void *instance, irda_param_t *param, int get)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ /* Poll parameters are always of lenght 0 (just a signal) */
+ if (!get) {
+ /* Respond with DTE line settings */
+ ircomm_param_request(self, IRCOMM_DTE, TRUE);
+ }
+ return 0;
+}
+
+
+
+
+
diff --git a/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_ttp.c b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_ttp.c
new file mode 100644
index 0000000..2cfd875
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_ttp.c
@@ -0,0 +1,304 @@
+/*********************************************************************
+ *
+ * Filename: ircomm_ttp.c
+ * Version: 1.0
+ * Description: Interface between IrCOMM and IrTTP
+ * Status: Stable
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sun Jun 6 20:48:27 1999
+ * Modified at: Mon Dec 13 11:35:13 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1999 Dag Brattli, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ ********************************************************************/
+
+#include <linux/sched.h>
+#include <linux/init.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/iriap.h>
+#include <net/irda/irttp.h>
+
+#include <net/irda/ircomm_event.h>
+#include <net/irda/ircomm_ttp.h>
+
+/*
+ * Function ircomm_open_tsap (self)
+ *
+ *
+ *
+ */
+int ircomm_open_tsap(struct ircomm_cb *self)
+{
+ notify_t notify;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ /* Register callbacks */
+ irda_notify_init(&notify);
+ notify.data_indication = ircomm_ttp_data_indication;
+ notify.connect_confirm = ircomm_ttp_connect_confirm;
+ notify.connect_indication = ircomm_ttp_connect_indication;
+ notify.flow_indication = ircomm_ttp_flow_indication;
+ notify.disconnect_indication = ircomm_ttp_disconnect_indication;
+ notify.instance = self;
+ strncpy(notify.name, "IrCOMM", NOTIFY_MAX_NAME);
+
+ self->tsap = irttp_open_tsap(LSAP_ANY, DEFAULT_INITIAL_CREDIT,
+ &notify);
+ if (!self->tsap) {
+ IRDA_DEBUG(0, "%s failed to allocate tsap\n", __FUNCTION__);
+ return -1;
+ }
+ self->slsap_sel = self->tsap->stsap_sel;
+
+ /*
+ * Initialize the call-table for issuing commands
+ */
+ self->issue.data_request = ircomm_ttp_data_request;
+ self->issue.connect_request = ircomm_ttp_connect_request;
+ self->issue.connect_response = ircomm_ttp_connect_response;
+ self->issue.disconnect_request = ircomm_ttp_disconnect_request;
+
+ return 0;
+}
+
+/*
+ * Function ircomm_ttp_connect_request (self, userdata)
+ *
+ *
+ *
+ */
+int ircomm_ttp_connect_request(struct ircomm_cb *self,
+ struct sk_buff *userdata,
+ struct ircomm_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ret = irttp_connect_request(self->tsap, info->dlsap_sel,
+ info->saddr, info->daddr, NULL,
+ TTP_SAR_DISABLE, userdata);
+ return ret;
+}
+
+/*
+ * Function ircomm_ttp_connect_response (self, skb)
+ *
+ *
+ *
+ */
+int ircomm_ttp_connect_response(struct ircomm_cb *self, struct sk_buff *skb)
+{
+ int ret;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ret = irttp_connect_response(self->tsap, TTP_SAR_DISABLE, skb);
+
+ return ret;
+}
+
+/*
+ * Function ircomm_ttp_data_request (self, userdata)
+ *
+ * Send IrCOMM data to IrTTP layer. Currently we do not try to combine
+ * control data with pure data, so they will be sent as separate frames.
+ * Should not be a big problem though, since control frames are rare. But
+ * some of them are sent after connection establishment, so this can
+ * increase the latency a bit.
+ */
+int ircomm_ttp_data_request(struct ircomm_cb *self, struct sk_buff *skb,
+ int clen)
+{
+ int ret;
+
+ ASSERT(skb != NULL, return -1;);
+
+ IRDA_DEBUG(2, "%s(), clen=%d\n", __FUNCTION__, clen);
+
+ /*
+ * Insert clen field, currently we either send data only, or control
+ * only frames, to make things easier and avoid queueing
+ */
+ ASSERT(skb_headroom(skb) >= IRCOMM_HEADER_SIZE, return -1;);
+ skb_push(skb, IRCOMM_HEADER_SIZE);
+
+ skb->data[0] = clen;
+
+ ret = irttp_data_request(self->tsap, skb);
+ if (ret) {
+ ERROR("%s(), failed\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ }
+
+ return ret;
+}
+
+/*
+ * Function ircomm_ttp_data_indication (instance, sap, skb)
+ *
+ * Incoming data
+ *
+ */
+int ircomm_ttp_data_indication(void *instance, void *sap,
+ struct sk_buff *skb)
+{
+ struct ircomm_cb *self = (struct ircomm_cb *) instance;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return -1;);
+ ASSERT(skb != NULL, return -1;);
+
+ ircomm_do_event(self, IRCOMM_TTP_DATA_INDICATION, skb, NULL);
+
+ return 0;
+}
+
+void ircomm_ttp_connect_confirm(void *instance, void *sap,
+ struct qos_info *qos,
+ __u32 max_sdu_size,
+ __u8 max_header_size,
+ struct sk_buff *skb)
+{
+ struct ircomm_cb *self = (struct ircomm_cb *) instance;
+ struct ircomm_info info;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+ ASSERT(qos != NULL, return;);
+
+ if (max_sdu_size != TTP_SAR_DISABLE) {
+ ERROR("%s(), SAR not allowed for IrCOMM!\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ return;
+ }
+
+ info.max_data_size = irttp_get_max_seg_size(self->tsap)
+ - IRCOMM_HEADER_SIZE;
+ info.max_header_size = max_header_size + IRCOMM_HEADER_SIZE;
+ info.qos = qos;
+
+ ircomm_do_event(self, IRCOMM_TTP_CONNECT_CONFIRM, skb, &info);
+}
+
+/*
+ * Function ircomm_ttp_connect_indication (instance, sap, qos, max_sdu_size,
+ * max_header_size, skb)
+ *
+ *
+ *
+ */
+void ircomm_ttp_connect_indication(void *instance, void *sap,
+ struct qos_info *qos,
+ __u32 max_sdu_size,
+ __u8 max_header_size,
+ struct sk_buff *skb)
+{
+ struct ircomm_cb *self = (struct ircomm_cb *)instance;
+ struct ircomm_info info;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+ ASSERT(qos != NULL, return;);
+
+ if (max_sdu_size != TTP_SAR_DISABLE) {
+ ERROR("%s(), SAR not allowed for IrCOMM!\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ return;
+ }
+
+ info.max_data_size = irttp_get_max_seg_size(self->tsap)
+ - IRCOMM_HEADER_SIZE;
+ info.max_header_size = max_header_size + IRCOMM_HEADER_SIZE;
+ info.qos = qos;
+
+ ircomm_do_event(self, IRCOMM_TTP_CONNECT_INDICATION, skb, &info);
+}
+
+/*
+ * Function ircomm_ttp_disconnect_request (self, userdata, info)
+ *
+ *
+ *
+ */
+int ircomm_ttp_disconnect_request(struct ircomm_cb *self,
+ struct sk_buff *userdata,
+ struct ircomm_info *info)
+{
+ int ret;
+
+ ret = irttp_disconnect_request(self->tsap, userdata, P_NORMAL);
+
+ return ret;
+}
+
+/*
+ * Function ircomm_ttp_disconnect_indication (instance, sap, reason, skb)
+ *
+ *
+ *
+ */
+void ircomm_ttp_disconnect_indication(void *instance, void *sap,
+ LM_REASON reason,
+ struct sk_buff *skb)
+{
+ struct ircomm_cb *self = (struct ircomm_cb *) instance;
+ struct ircomm_info info;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return;);
+
+ info.reason = reason;
+
+ ircomm_do_event(self, IRCOMM_TTP_DISCONNECT_INDICATION, skb, &info);
+}
+
+/*
+ * Function ircomm_ttp_flow_indication (instance, sap, cmd)
+ *
+ * Layer below is telling us to start or stop the flow of data
+ *
+ */
+void ircomm_ttp_flow_indication(void *instance, void *sap, LOCAL_FLOW cmd)
+{
+ struct ircomm_cb *self = (struct ircomm_cb *) instance;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_MAGIC, return;);
+
+ if (self->notify.flow_indication)
+ self->notify.flow_indication(self->notify.instance, self, cmd);
+}
+
+
diff --git a/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_tty.c b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_tty.c
new file mode 100644
index 0000000..8f1a39b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_tty.c
@@ -0,0 +1,1409 @@
+/*********************************************************************
+ *
+ * Filename: ircomm_tty.c
+ * Version: 1.0
+ * Description: IrCOMM serial TTY driver
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sun Jun 6 21:00:56 1999
+ * Modified at: Wed Feb 23 00:09:02 2000
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ * Sources: serial.c and previous IrCOMM work by Takahide Higuchi
+ *
+ * Copyright (c) 1999-2000 Dag Brattli, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/termios.h>
+#include <linux/tty.h>
+#include <linux/interrupt.h>
+
+#include <asm/segment.h>
+#include <asm/uaccess.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irmod.h>
+
+#include <net/irda/ircomm_core.h>
+#include <net/irda/ircomm_param.h>
+#include <net/irda/ircomm_tty_attach.h>
+#include <net/irda/ircomm_tty.h>
+
+static int ircomm_tty_open(struct tty_struct *tty, struct file *filp);
+static void ircomm_tty_close(struct tty_struct * tty, struct file *filp);
+static int ircomm_tty_write(struct tty_struct * tty, int from_user,
+ const unsigned char *buf, int count);
+static int ircomm_tty_write_room(struct tty_struct *tty);
+static void ircomm_tty_throttle(struct tty_struct *tty);
+static void ircomm_tty_unthrottle(struct tty_struct *tty);
+static int ircomm_tty_chars_in_buffer(struct tty_struct *tty);
+static void ircomm_tty_flush_buffer(struct tty_struct *tty);
+static void ircomm_tty_send_xchar(struct tty_struct *tty, char ch);
+static void ircomm_tty_wait_until_sent(struct tty_struct *tty, int timeout);
+static void ircomm_tty_hangup(struct tty_struct *tty);
+static void ircomm_tty_do_softint(void *private_);
+static void ircomm_tty_shutdown(struct ircomm_tty_cb *self);
+
+static int ircomm_tty_data_indication(void *instance, void *sap,
+ struct sk_buff *skb);
+static int ircomm_tty_control_indication(void *instance, void *sap,
+ struct sk_buff *skb);
+static void ircomm_tty_flow_indication(void *instance, void *sap,
+ LOCAL_FLOW cmd);
+#ifdef CONFIG_PROC_FS
+static int ircomm_tty_read_proc(char *buf, char **start, off_t offset, int len,
+ int *eof, void *unused);
+#endif /* CONFIG_PROC_FS */
+static struct tty_driver driver;
+static int ircomm_tty_refcount; /* If we manage several devices */
+
+static struct tty_struct *ircomm_tty_table[NR_PTYS];
+static struct termios *ircomm_tty_termios[NR_PTYS];
+static struct termios *ircomm_tty_termios_locked[NR_PTYS];
+
+hashbin_t *ircomm_tty = NULL;
+
+/*
+ * Function ircomm_tty_init()
+ *
+ * Init IrCOMM TTY layer/driver
+ *
+ */
+int __init ircomm_tty_init(void)
+{
+ ircomm_tty = hashbin_new(HB_LOCAL);
+ if (ircomm_tty == NULL) {
+ ERROR("%s(), can't allocate hashbin!\n", __FUNCTION__);
+ return -ENOMEM;
+ }
+
+ memset(&driver, 0, sizeof(struct tty_driver));
+ driver.magic = TTY_DRIVER_MAGIC;
+ driver.driver_name = "ircomm";
+#ifdef CONFIG_DEVFS_FS
+ driver.name = "ircomm%d";
+#else
+ driver.name = "ircomm";
+#endif
+ driver.major = IRCOMM_TTY_MAJOR;
+ driver.minor_start = IRCOMM_TTY_MINOR;
+ driver.num = IRCOMM_TTY_PORTS;
+ driver.type = TTY_DRIVER_TYPE_SERIAL;
+ driver.subtype = SERIAL_TYPE_NORMAL;
+ driver.init_termios = tty_std_termios;
+ driver.init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+ driver.flags = TTY_DRIVER_REAL_RAW;
+ driver.refcount = &ircomm_tty_refcount;
+ driver.table = ircomm_tty_table;
+ driver.termios = ircomm_tty_termios;
+ driver.termios_locked = ircomm_tty_termios_locked;
+ driver.open = ircomm_tty_open;
+ driver.close = ircomm_tty_close;
+ driver.write = ircomm_tty_write;
+ driver.write_room = ircomm_tty_write_room;
+ driver.chars_in_buffer = ircomm_tty_chars_in_buffer;
+ driver.flush_buffer = ircomm_tty_flush_buffer;
+ driver.ioctl = ircomm_tty_ioctl;
+ driver.throttle = ircomm_tty_throttle;
+ driver.unthrottle = ircomm_tty_unthrottle;
+ driver.send_xchar = ircomm_tty_send_xchar;
+ driver.set_termios = ircomm_tty_set_termios;
+ driver.stop = ircomm_tty_stop;
+ driver.start = ircomm_tty_start;
+ driver.hangup = ircomm_tty_hangup;
+ driver.wait_until_sent = ircomm_tty_wait_until_sent;
+#ifdef CONFIG_PROC_FS
+ driver.read_proc = ircomm_tty_read_proc;
+#endif /* CONFIG_PROC_FS */
+ if (tty_register_driver(&driver)) {
+ ERROR("%s: Couldn't register serial driver\n", __FUNCTION__);
+ return -1;
+ }
+ return 0;
+}
+
+#ifdef MODULE
+static void __ircomm_tty_cleanup(struct ircomm_tty_cb *self)
+{
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ ircomm_tty_shutdown(self);
+
+ self->magic = 0;
+ kfree(self);
+}
+
+/*
+ * Function ircomm_tty_cleanup ()
+ *
+ * Remove IrCOMM TTY layer/driver
+ *
+ */
+void ircomm_tty_cleanup(void)
+{
+ int ret;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ret = tty_unregister_driver(&driver);
+ if (ret) {
+ ERROR("%s, failed to unregister driver\n", __FUNCTION__);
+ return;
+ }
+
+ hashbin_delete(ircomm_tty, (FREE_FUNC) __ircomm_tty_cleanup);
+}
+#endif /* MODULE */
+
+/*
+ * Function ircomm_startup (self)
+ *
+ *
+ *
+ */
+static int ircomm_tty_startup(struct ircomm_tty_cb *self)
+{
+ notify_t notify;
+ int ret;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ /* Already open */
+ if (self->flags & ASYNC_INITIALIZED) {
+ IRDA_DEBUG(2, "%s(), already open so break out!\n", __FUNCTION__);
+ return 0;
+ }
+
+ /* Register with IrCOMM */
+ irda_notify_init(&notify);
+ /* These callbacks we must handle ourselves */
+ notify.data_indication = ircomm_tty_data_indication;
+ notify.udata_indication = ircomm_tty_control_indication;
+ notify.flow_indication = ircomm_tty_flow_indication;
+
+ /* Use the ircomm_tty interface for these ones */
+ notify.disconnect_indication = ircomm_tty_disconnect_indication;
+ notify.connect_confirm = ircomm_tty_connect_confirm;
+ notify.connect_indication = ircomm_tty_connect_indication;
+ strncpy(notify.name, "ircomm_tty", NOTIFY_MAX_NAME);
+ notify.instance = self;
+
+ if (!self->ircomm) {
+ self->ircomm = ircomm_open(&notify, self->service_type,
+ self->line);
+ }
+ if (!self->ircomm)
+ return -ENODEV;
+
+ self->slsap_sel = self->ircomm->slsap_sel;
+
+ /* Connect IrCOMM link with remote device */
+ ret = ircomm_tty_attach_cable(self);
+ if (ret < 0) {
+ ERROR("%s(), error attaching cable!\n", __FUNCTION__);
+ return ret;
+ }
+
+ self->flags |= ASYNC_INITIALIZED;
+
+ return 0;
+}
+
+/*
+ * Function ircomm_block_til_ready (self, filp)
+ *
+ *
+ *
+ */
+static int ircomm_tty_block_til_ready(struct ircomm_tty_cb *self,
+ struct file *filp)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ int retval;
+ int do_clocal = 0, extra_count = 0;
+ unsigned long flags;
+ struct tty_struct *tty;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ tty = self->tty;
+
+ if (tty->driver.subtype == SERIAL_TYPE_CALLOUT) {
+ /* this is a callout device */
+ /* just verify that normal device is not in use */
+ if (self->flags & ASYNC_NORMAL_ACTIVE)
+ return -EBUSY;
+ if ((self->flags & ASYNC_CALLOUT_ACTIVE) &&
+ (self->flags & ASYNC_SESSION_LOCKOUT) &&
+ (self->session != current->session))
+ return -EBUSY;
+ if ((self->flags & ASYNC_CALLOUT_ACTIVE) &&
+ (self->flags & ASYNC_PGRP_LOCKOUT) &&
+ (self->pgrp != current->pgrp))
+ return -EBUSY;
+ self->flags |= ASYNC_CALLOUT_ACTIVE;
+ return 0;
+ }
+
+ /*
+ * If non-blocking mode is set, or the port is not enabled,
+ * then make the check up front and then exit.
+ */
+ if (filp->f_flags & O_NONBLOCK || tty->flags & (1 << TTY_IO_ERROR)){
+ /* nonblock mode is set or port is not enabled */
+ /* just verify that callout device is not active */
+ if (self->flags & ASYNC_CALLOUT_ACTIVE)
+ return -EBUSY;
+ self->flags |= ASYNC_NORMAL_ACTIVE;
+
+ IRDA_DEBUG(1, "%s(), O_NONBLOCK requested!\n", __FUNCTION__);
+ return 0;
+ }
+
+ if (self->flags & ASYNC_CALLOUT_ACTIVE) {
+ if (self->normal_termios.c_cflag & CLOCAL) {
+ IRDA_DEBUG(1, "%s(), doing CLOCAL!\n", __FUNCTION__);
+ do_clocal = 1;
+ }
+ } else {
+ if (tty->termios->c_cflag & CLOCAL) {
+ IRDA_DEBUG(1, "%s(), doing CLOCAL!\n", __FUNCTION__);
+ do_clocal = 1;
+ }
+ }
+
+ /* Wait for carrier detect and the line to become
+ * free (i.e., not in use by the callout). While we are in
+ * this loop, self->open_count is dropped by one, so that
+ * mgsl_close() knows when to free things. We restore it upon
+ * exit, either normal or abnormal.
+ */
+
+ retval = 0;
+ add_wait_queue(&self->open_wait, &wait);
+
+ IRDA_DEBUG(2, "%s(%d):block_til_ready before block on %s open_count=%d\n",
+ __FILE__,__LINE__, tty->driver.name, self->open_count );
+
+ save_flags(flags); cli();
+ if (!tty_hung_up_p(filp)) {
+ extra_count = 1;
+ self->open_count--;
+ }
+ restore_flags(flags);
+ self->blocked_open++;
+
+ while (1) {
+ if (!(self->flags & ASYNC_CALLOUT_ACTIVE) &&
+ (tty->termios->c_cflag & CBAUD)) {
+ save_flags(flags); cli();
+ self->settings.dte |= IRCOMM_RTS + IRCOMM_DTR;
+
+ ircomm_param_request(self, IRCOMM_DTE, TRUE);
+ restore_flags(flags);
+ }
+
+ current->state = TASK_INTERRUPTIBLE;
+
+ if (tty_hung_up_p(filp) || !(self->flags & ASYNC_INITIALIZED)){
+ retval = (self->flags & ASYNC_HUP_NOTIFY) ?
+ -EAGAIN : -ERESTARTSYS;
+ break;
+ }
+
+ /*
+ * Check if link is ready now. Even if CLOCAL is
+ * specified, we cannot return before the IrCOMM link is
+ * ready
+ */
+ if (!(self->flags & ASYNC_CALLOUT_ACTIVE) &&
+ !(self->flags & ASYNC_CLOSING) &&
+ (do_clocal || (self->settings.dce & IRCOMM_CD)) &&
+ self->state == IRCOMM_TTY_READY)
+ {
+ break;
+ }
+
+ if (signal_pending(current)) {
+ retval = -ERESTARTSYS;
+ break;
+ }
+
+ IRDA_DEBUG(1, "%s(%d):block_til_ready blocking on %s open_count=%d\n",
+ __FILE__,__LINE__, tty->driver.name, self->open_count );
+
+ schedule();
+ }
+
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&self->open_wait, &wait);
+
+ if (extra_count)
+ self->open_count++;
+ self->blocked_open--;
+
+ IRDA_DEBUG(1, "%s(%d):block_til_ready after blocking on %s open_count=%d\n",
+ __FILE__,__LINE__, tty->driver.name, self->open_count);
+
+ if (!retval)
+ self->flags |= ASYNC_NORMAL_ACTIVE;
+
+ return retval;
+}
+
+/*
+ * Function ircomm_tty_open (tty, filp)
+ *
+ * This routine is called when a particular tty device is opened. This
+ * routine is mandatory; if this routine is not filled in, the attempted
+ * open will fail with ENODEV.
+ */
+static int ircomm_tty_open(struct tty_struct *tty, struct file *filp)
+{
+ struct ircomm_tty_cb *self;
+ int line;
+ int ret;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ MOD_INC_USE_COUNT;
+ line = MINOR(tty->device) - tty->driver.minor_start;
+ if ((line < 0) || (line >= IRCOMM_TTY_PORTS)) {
+ MOD_DEC_USE_COUNT;
+ return -ENODEV;
+ }
+
+ /* Check if instance already exists */
+ self = hashbin_find(ircomm_tty, line, NULL);
+ if (!self) {
+ /* No, so make new instance */
+ self = kmalloc(sizeof(struct ircomm_tty_cb), GFP_KERNEL);
+ if (self == NULL) {
+ ERROR("%s(), kmalloc failed!\n", __FUNCTION__);
+ MOD_DEC_USE_COUNT;
+ return -ENOMEM;
+ }
+ memset(self, 0, sizeof(struct ircomm_tty_cb));
+
+ self->magic = IRCOMM_TTY_MAGIC;
+ self->flow = FLOW_STOP;
+
+ self->line = line;
+ self->tqueue.routine = ircomm_tty_do_softint;
+ self->tqueue.data = self;
+ self->max_header_size = IRCOMM_TTY_HDR_UNINITIALISED;
+ self->max_data_size = IRCOMM_TTY_DATA_UNINITIALISED;
+ self->close_delay = 5*HZ/10;
+ self->closing_wait = 30*HZ;
+
+ /* Init some important stuff */
+ init_timer(&self->watchdog_timer);
+ init_waitqueue_head(&self->open_wait);
+ init_waitqueue_head(&self->close_wait);
+
+ /*
+ * Force TTY into raw mode by default which is usually what
+ * we want for IrCOMM and IrLPT. This way applications will
+ * not have to twiddle with printcap etc.
+ */
+ tty->termios->c_iflag = 0;
+ tty->termios->c_oflag = 0;
+
+ /* Insert into hash */
+ hashbin_insert(ircomm_tty, (irda_queue_t *) self, line, NULL);
+ }
+ self->open_count++;
+
+ tty->driver_data = self;
+ self->tty = tty;
+
+ IRDA_DEBUG(1, "%s(), %s%d, count = %d\n", __FUNCTION__,
+ tty->driver.name, self->line, self->open_count);
+
+ /* Not really used by us, but lets do it anyway */
+ self->tty->low_latency = (self->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+
+ /*
+ * If the port is the middle of closing, bail out now
+ */
+ if (tty_hung_up_p(filp) ||
+ (self->flags & ASYNC_CLOSING)) {
+ if (self->flags & ASYNC_CLOSING)
+ interruptible_sleep_on(&self->close_wait);
+ /* MOD_DEC_USE_COUNT; "info->tty" will cause this? */
+#ifdef SERIAL_DO_RESTART
+ return ((self->flags & ASYNC_HUP_NOTIFY) ?
+ -EAGAIN : -ERESTARTSYS);
+#else
+ return -EAGAIN;
+#endif
+ }
+
+ /* Check if this is a "normal" ircomm device, or an irlpt device */
+ if (line < 0x10) {
+ self->service_type = IRCOMM_3_WIRE | IRCOMM_9_WIRE;
+ self->settings.service_type = IRCOMM_9_WIRE; /* 9 wire as default */
+ self->settings.dce = IRCOMM_CTS | IRCOMM_CD; /* Default line settings */
+ IRDA_DEBUG(2, "%s(), IrCOMM device\n", __FUNCTION__);
+ } else {
+ IRDA_DEBUG(2, "%s(), IrLPT device\n", __FUNCTION__);
+ self->service_type = IRCOMM_3_WIRE_RAW;
+ self->settings.service_type = IRCOMM_3_WIRE_RAW; /* Default */
+ }
+
+ ret = ircomm_tty_startup(self);
+ if (ret)
+ return ret;
+
+ ret = ircomm_tty_block_til_ready(self, filp);
+ if (ret) {
+ /* MOD_DEC_USE_COUNT; "info->tty" will cause this? */
+ IRDA_DEBUG(2, "%s(), returning after block_til_ready with %d\n",
+ __FUNCTION__, ret);
+
+ return ret;
+ }
+
+ self->session = current->session;
+ self->pgrp = current->pgrp;
+
+ return 0;
+}
+
+/*
+ * Function ircomm_tty_close (tty, filp)
+ *
+ * This routine is called when a particular tty device is closed.
+ *
+ */
+static void ircomm_tty_close(struct tty_struct *tty, struct file *filp)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
+ unsigned long flags;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ if (!tty)
+ return;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ save_flags(flags);
+ cli();
+
+ if (tty_hung_up_p(filp)) {
+ MOD_DEC_USE_COUNT;
+ restore_flags(flags);
+
+ IRDA_DEBUG(0, "%s(), returning 1\n", __FUNCTION__);
+ return;
+ }
+
+ if ((tty->count == 1) && (self->open_count != 1)) {
+ /*
+ * Uh, oh. tty->count is 1, which means that the tty
+ * structure will be freed. state->count should always
+ * be one in these conditions. If it's greater than
+ * one, we've got real problems, since it means the
+ * serial port won't be shutdown.
+ */
+ IRDA_DEBUG(0, "%s(), bad serial port count; "
+ "tty->count is 1, state->count is %d\n",
+ __FUNCTION__, self->open_count);
+ self->open_count = 1;
+ }
+
+ if (--self->open_count < 0) {
+ ERROR("%s(), bad serial port count for ttys%d: %d\n",
+ __FUNCTION__, self->line, self->open_count);
+ self->open_count = 0;
+ }
+ if (self->open_count) {
+ MOD_DEC_USE_COUNT;
+ restore_flags(flags);
+
+ IRDA_DEBUG(0, "%s(), open count > 0\n", __FUNCTION__);
+ return;
+ }
+ self->flags |= ASYNC_CLOSING;
+
+ /*
+ * Now we wait for the transmit buffer to clear; and we notify
+ * the line discipline to only process XON/XOFF characters.
+ */
+ tty->closing = 1;
+ if (self->closing_wait != ASYNC_CLOSING_WAIT_NONE)
+ tty_wait_until_sent(tty, self->closing_wait);
+
+ ircomm_tty_shutdown(self);
+
+ if (tty->driver.flush_buffer)
+ tty->driver.flush_buffer(tty);
+ tty_ldisc_flush(tty);
+
+ tty->closing = 0;
+ self->tty = 0;
+
+ if (self->blocked_open) {
+ if (self->close_delay) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(self->close_delay);
+ }
+ wake_up_interruptible(&self->open_wait);
+ }
+
+ self->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE|
+ ASYNC_CLOSING);
+ wake_up_interruptible(&self->close_wait);
+
+ MOD_DEC_USE_COUNT;
+ restore_flags(flags);
+}
+
+/*
+ * Function ircomm_tty_flush_buffer (tty)
+ *
+ *
+ *
+ */
+static void ircomm_tty_flush_buffer(struct tty_struct *tty)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ /*
+ * Let do_softint() do this to avoid race condition with
+ * do_softint() ;-)
+ */
+ queue_task(&self->tqueue, &tq_immediate);
+ mark_bh(IMMEDIATE_BH);
+}
+
+/*
+ * Function ircomm_tty_do_softint (private_)
+ *
+ * We use this routine to give the write wakeup to the user at at a
+ * safe time (as fast as possible after write have completed). This
+ * can be compared to the Tx interrupt.
+ */
+static void ircomm_tty_do_softint(void *private_)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) private_;
+ struct tty_struct *tty;
+ unsigned long flags;
+ struct sk_buff *skb, *ctrl_skb;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ if (!self || self->magic != IRCOMM_TTY_MAGIC)
+ return;
+
+ tty = self->tty;
+ if (!tty)
+ return;
+
+ /* Unlink control buffer */
+ save_flags(flags);
+ cli();
+
+ ctrl_skb = self->ctrl_skb;
+ self->ctrl_skb = NULL;
+
+ restore_flags(flags);
+
+ /* Flush control buffer if any */
+ if (ctrl_skb && self->flow == FLOW_START)
+ ircomm_control_request(self->ircomm, ctrl_skb);
+
+ if (tty->hw_stopped)
+ return;
+
+ /* Unlink transmit buffer */
+ save_flags(flags);
+ cli();
+
+ skb = self->tx_skb;
+ self->tx_skb = NULL;
+
+ restore_flags(flags);
+
+ /* Flush transmit buffer if any */
+ if (skb)
+ ircomm_tty_do_event(self, IRCOMM_TTY_DATA_REQUEST, skb, NULL);
+
+ /* Check if user (still) wants to be waken up */
+ tty_wakeup(tty);
+}
+
+/*
+ * Function ircomm_tty_write (tty, from_user, buf, count)
+ *
+ * This routine is called by the kernel to write a series of characters
+ * to the tty device. The characters may come from user space or kernel
+ * space. This routine will return the number of characters actually
+ * accepted for writing. This routine is mandatory.
+ */
+static int ircomm_tty_write(struct tty_struct *tty, int from_user,
+ const unsigned char *buf, int count)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
+ unsigned long flags;
+ struct sk_buff *skb;
+ int tailroom = 0;
+ int len = 0;
+ int size;
+
+ IRDA_DEBUG(2, "%s(), count=%d, hw_stopped=%d\n",
+ __FUNCTION__, count, tty->hw_stopped);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ /* We may receive packets from the TTY even before we have finished
+ * our setup. Not cool.
+ * The problem is that we don't know the final header and data size
+ * to create the proper skb, so any skb we would create would have
+ * bogus header and data size, so need care.
+ * We use a bogus header size to safely detect this condition.
+ * Another problem is that hw_stopped was set to 0 way before it
+ * should be, so we would drop this skb. It should now be fixed.
+ * One option is to not accept data until we are properly setup.
+ * But, I suspect that when it happens, the ppp line discipline
+ * just "drops" the data, which might screw up connect scripts.
+ * The second option is to create a "safe skb", with large header
+ * and small size (see ircomm_tty_open() for values).
+ * We just need to make sure that when the real values get filled,
+ * we don't mess up the original "safe skb" (see tx_data_size).
+ * Jean II */
+ if (self->max_header_size == IRCOMM_TTY_HDR_UNINITIALISED) {
+ IRDA_DEBUG(1, "%s() : not initialised\n", __FUNCTION__);
+#ifdef IRCOMM_NO_TX_BEFORE_INIT
+ /* We didn't consume anything, TTY will retry */
+ return 0;
+#endif
+ }
+
+ save_flags(flags);
+ cli();
+
+ /* Fetch current transmit buffer */
+ skb = self->tx_skb;
+
+ /*
+ * Send out all the data we get, possibly as multiple fragmented
+ * frames, but this will only happen if the data is larger than the
+ * max data size. The normal case however is just the opposite, and
+ * this function may be called multiple times, and will then actually
+ * defragment the data and send it out as one packet as soon as
+ * possible, but at a safer point in time
+ */
+ while (count) {
+ size = count;
+
+ /* Adjust data size to the max data size */
+ if (size > self->max_data_size)
+ size = self->max_data_size;
+
+ /*
+ * Do we already have a buffer ready for transmit, or do
+ * we need to allocate a new frame
+ */
+ if (skb) {
+ /*
+ * Any room for more data at the end of the current
+ * transmit buffer? Cannot use skb_tailroom, since
+ * dev_alloc_skb gives us a larger skb than we
+ * requested
+ * Note : use tx_data_size, because max_data_size
+ * may have changed and we don't want to overwrite
+ * the skb. - Jean II
+ */
+ if ((tailroom = (self->tx_data_size - skb->len)) > 0) {
+ /* Adjust data to tailroom */
+ if (size > tailroom)
+ size = tailroom;
+ } else {
+ /*
+ * Current transmit frame is full, so break
+ * out, so we can send it as soon as possible
+ */
+ break;
+ }
+ } else {
+ /* Prepare a full sized frame */
+ skb = dev_alloc_skb(self->max_data_size+
+ self->max_header_size);
+ if (!skb) {
+ restore_flags(flags);
+ return -ENOBUFS;
+ }
+ skb_reserve(skb, self->max_header_size);
+ self->tx_skb = skb;
+ /* Remember skb size because max_data_size may
+ * change later on - Jean II */
+ self->tx_data_size = self->max_data_size;
+ }
+
+ /* Copy data */
+ if (from_user)
+ copy_from_user(skb_put(skb,size), buf+len, size);
+ else
+ memcpy(skb_put(skb,size), buf+len, size);
+
+ count -= size;
+ len += size;
+ }
+
+ restore_flags(flags);
+
+ /*
+ * Schedule a new thread which will transmit the frame as soon
+ * as possible, but at a safe point in time. We do this so the
+ * "user" can give us data multiple times, as PPP does (because of
+ * its 256 byte tx buffer). We will then defragment and send out
+ * all this data as one single packet.
+ */
+ queue_task(&self->tqueue, &tq_immediate);
+ mark_bh(IMMEDIATE_BH);
+
+ return len;
+}
+
+/*
+ * Function ircomm_tty_write_room (tty)
+ *
+ * This routine returns the numbers of characters the tty driver will
+ * accept for queuing to be written. This number is subject to change as
+ * output buffers get emptied, or if the output flow control is acted.
+ */
+static int ircomm_tty_write_room(struct tty_struct *tty)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
+ unsigned long flags;
+ int ret;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+#ifdef IRCOMM_NO_TX_BEFORE_INIT
+ /* max_header_size tells us if the channel is initialised or not. */
+ if (self->max_header_size == IRCOMM_TTY_HDR_UNINITIALISED)
+ /* Don't bother us yet */
+ return 0;
+#endif
+
+ /* Check if we are allowed to transmit any data.
+ * hw_stopped is the regular flow control.
+ * Jean II */
+ if (tty->hw_stopped)
+ ret = 0;
+ else {
+ save_flags(flags);
+ cli();
+ if (self->tx_skb)
+ ret = self->tx_data_size - self->tx_skb->len;
+ else
+ ret = self->max_data_size;
+ restore_flags(flags);
+ }
+ IRDA_DEBUG(2, "%s(), ret=%d\n", __FUNCTION__, ret);
+
+ return ret;
+}
+
+/*
+ * Function ircomm_tty_wait_until_sent (tty, timeout)
+ *
+ * This routine waits until the device has written out all of the
+ * characters in its transmitter FIFO.
+ */
+static void ircomm_tty_wait_until_sent(struct tty_struct *tty, int timeout)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
+ unsigned long orig_jiffies, poll_time;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ orig_jiffies = jiffies;
+
+ /* Set poll time to 200 ms */
+ poll_time = IRDA_MIN(timeout, MSECS_TO_JIFFIES(200));
+
+ while (self->tx_skb && self->tx_skb->len) {
+ current->state = TASK_INTERRUPTIBLE;
+ schedule_timeout(poll_time);
+ if (signal_pending(current))
+ break;
+ if (timeout && time_after(jiffies, orig_jiffies + timeout))
+ break;
+ }
+ current->state = TASK_RUNNING;
+}
+
+/*
+ * Function ircomm_tty_throttle (tty)
+ *
+ * This routine notifies the tty driver that input buffers for the line
+ * discipline are close to full, and it should somehow signal that no
+ * more characters should be sent to the tty.
+ */
+static void ircomm_tty_throttle(struct tty_struct *tty)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ /* Software flow control? */
+ if (I_IXOFF(tty))
+ ircomm_tty_send_xchar(tty, STOP_CHAR(tty));
+
+ /* Hardware flow control? */
+ if (tty->termios->c_cflag & CRTSCTS) {
+ self->settings.dte &= ~IRCOMM_RTS;
+ self->settings.dte |= IRCOMM_DELTA_RTS;
+
+ ircomm_param_request(self, IRCOMM_DTE, TRUE);
+ }
+
+ ircomm_flow_request(self->ircomm, FLOW_STOP);
+}
+
+/*
+ * Function ircomm_tty_unthrottle (tty)
+ *
+ * This routine notifies the tty drivers that it should signals that
+ * characters can now be sent to the tty without fear of overrunning the
+ * input buffers of the line disciplines.
+ */
+static void ircomm_tty_unthrottle(struct tty_struct *tty)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ /* Using software flow control? */
+ if (I_IXOFF(tty)) {
+ ircomm_tty_send_xchar(tty, START_CHAR(tty));
+ }
+
+ /* Using hardware flow control? */
+ if (tty->termios->c_cflag & CRTSCTS) {
+ self->settings.dte |= (IRCOMM_RTS|IRCOMM_DELTA_RTS);
+
+ ircomm_param_request(self, IRCOMM_DTE, TRUE);
+ IRDA_DEBUG(1, "%s(), FLOW_START\n", __FUNCTION__);
+ }
+ ircomm_flow_request(self->ircomm, FLOW_START);
+}
+
+/*
+ * Function ircomm_tty_chars_in_buffer (tty)
+ *
+ * Indicates if there are any data in the buffer
+ *
+ */
+static int ircomm_tty_chars_in_buffer(struct tty_struct *tty)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
+ unsigned long flags;
+ int len = 0;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ save_flags(flags);
+ cli();
+
+ if (self->tx_skb)
+ len = self->tx_skb->len;
+
+ restore_flags(flags);
+
+ return len;
+}
+
+static void ircomm_tty_shutdown(struct ircomm_tty_cb *self)
+{
+ unsigned long flags;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ if (!(self->flags & ASYNC_INITIALIZED))
+ return;
+
+ save_flags(flags);
+ cli();
+
+ del_timer(&self->watchdog_timer);
+
+ /* Free parameter buffer */
+ if (self->ctrl_skb) {
+ dev_kfree_skb(self->ctrl_skb);
+ self->ctrl_skb = NULL;
+ }
+
+ /* Free transmit buffer */
+ if (self->tx_skb) {
+ dev_kfree_skb(self->tx_skb);
+ self->tx_skb = NULL;
+ }
+
+ ircomm_tty_detach_cable(self);
+
+ if (self->ircomm) {
+ ircomm_close(self->ircomm);
+ self->ircomm = NULL;
+ }
+ self->flags &= ~ASYNC_INITIALIZED;
+
+ restore_flags(flags);
+}
+
+/*
+ * Function ircomm_tty_hangup (tty)
+ *
+ * This routine notifies the tty driver that it should hangup the tty
+ * device.
+ *
+ */
+static void ircomm_tty_hangup(struct tty_struct *tty)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ if (!tty)
+ return;
+
+ /* ircomm_tty_flush_buffer(tty); */
+ ircomm_tty_shutdown(self);
+
+ self->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE);
+ self->tty = 0;
+ self->open_count = 0;
+ wake_up_interruptible(&self->open_wait);
+}
+
+/*
+ * Function ircomm_tty_send_xchar (tty, ch)
+ *
+ * This routine is used to send a high-priority XON/XOFF character to
+ * the device.
+ */
+static void ircomm_tty_send_xchar(struct tty_struct *tty, char ch)
+{
+ IRDA_DEBUG(0, "%s(), not impl\n", __FUNCTION__);
+}
+
+/*
+ * Function ircomm_tty_start (tty)
+ *
+ * This routine notifies the tty driver that it resume sending
+ * characters to the tty device.
+ */
+void ircomm_tty_start(struct tty_struct *tty)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
+
+ ircomm_flow_request(self->ircomm, FLOW_START);
+}
+
+/*
+ * Function ircomm_tty_stop (tty)
+ *
+ * This routine notifies the tty driver that it should stop outputting
+ * characters to the tty device.
+ */
+void ircomm_tty_stop(struct tty_struct *tty)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ ircomm_flow_request(self->ircomm, FLOW_STOP);
+}
+
+/*
+ * Function ircomm_check_modem_status (self)
+ *
+ * Check for any changes in the DCE's line settings. This function should
+ * be called whenever the dce parameter settings changes, to update the
+ * flow control settings and other things
+ */
+void ircomm_tty_check_modem_status(struct ircomm_tty_cb *self)
+{
+ struct tty_struct *tty;
+ int status;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ tty = self->tty;
+
+ status = self->settings.dce;
+
+ if (status & IRCOMM_DCE_DELTA_ANY) {
+ /*wake_up_interruptible(&self->delta_msr_wait);*/
+ }
+ if ((self->flags & ASYNC_CHECK_CD) && (status & IRCOMM_DELTA_CD)) {
+ IRDA_DEBUG(2, "%s(), ircomm%d CD now %s...\n",
+ __FUNCTION__, self->line, (status & IRCOMM_CD) ? "on" : "off");
+
+ if (status & IRCOMM_CD) {
+ wake_up_interruptible(&self->open_wait);
+ } else if (!((self->flags & ASYNC_CALLOUT_ACTIVE) &&
+ (self->flags & ASYNC_CALLOUT_NOHUP)))
+ {
+ IRDA_DEBUG(2, "%s(), Doing serial hangup..\n", __FUNCTION__);
+ if (tty)
+ tty_hangup(tty);
+
+ /* Hangup will remote the tty, so better break out */
+ return;
+ }
+ }
+ if (self->flags & ASYNC_CTS_FLOW) {
+ if (tty->hw_stopped) {
+ if (status & IRCOMM_CTS) {
+ IRDA_DEBUG(2, "%s(), CTS tx start...\n", __FUNCTION__);
+ tty->hw_stopped = 0;
+
+ /* Wake up processes blocked on open */
+ wake_up_interruptible(&self->open_wait);
+
+ queue_task(&self->tqueue, &tq_immediate);
+ mark_bh(IMMEDIATE_BH);
+ return;
+ }
+ } else {
+ if (!(status & IRCOMM_CTS)) {
+ IRDA_DEBUG(2, "%s(), CTS tx stop...\n", __FUNCTION__);
+ tty->hw_stopped = 1;
+ }
+ }
+ }
+}
+
+/*
+ * Function ircomm_tty_data_indication (instance, sap, skb)
+ *
+ * Handle incoming data, and deliver it to the line discipline
+ *
+ */
+static int ircomm_tty_data_indication(void *instance, void *sap,
+ struct sk_buff *skb)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+ ASSERT(skb != NULL, return -1;);
+
+ if (!self->tty) {
+ IRDA_DEBUG(0, "%s(), no tty!\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ return 0;
+ }
+
+ /*
+ * If we receive data when hardware is stopped then something is wrong.
+ * We try to poll the peers line settings to check if we are up todate.
+ * Devices like WinCE can do this, and since they don't send any
+ * params, we can just as well declare the hardware for running.
+ */
+ if (self->tty->hw_stopped && (self->flow == FLOW_START)) {
+ IRDA_DEBUG(0, "%s(), polling for line settings!\n", __FUNCTION__);
+ ircomm_param_request(self, IRCOMM_POLL, TRUE);
+
+ /* We can just as well declare the hardware for running */
+ ircomm_tty_send_initial_parameters(self);
+ ircomm_tty_link_established(self);
+ }
+
+ /*
+ * Just give it over to the line discipline. There is no need to
+ * involve the flip buffers, since we are not running in an interrupt
+ * handler
+ */
+ self->tty->ldisc.receive_buf(self->tty, skb->data, NULL, skb->len);
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/*
+ * Function ircomm_tty_control_indication (instance, sap, skb)
+ *
+ * Parse all incoming parameters (easy!)
+ *
+ */
+static int ircomm_tty_control_indication(void *instance, void *sap,
+ struct sk_buff *skb)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+ int clen;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+ ASSERT(skb != NULL, return -1;);
+
+ clen = skb->data[0];
+
+ irda_param_extract_all(self, skb->data+1, IRDA_MIN(skb->len-1, clen),
+ &ircomm_param_info);
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/*
+ * Function ircomm_tty_flow_indication (instance, sap, cmd)
+ *
+ * This function is called by IrTTP when it wants us to slow down the
+ * transmission of data. We just mark the hardware as stopped, and wait
+ * for IrTTP to notify us that things are OK again.
+ */
+static void ircomm_tty_flow_indication(void *instance, void *sap,
+ LOCAL_FLOW cmd)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+ struct tty_struct *tty;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ tty = self->tty;
+
+ switch (cmd) {
+ case FLOW_START:
+ IRDA_DEBUG(2, "%s(), hw start!\n", __FUNCTION__);
+ tty->hw_stopped = 0;
+
+ /* ircomm_tty_do_softint will take care of the rest */
+ queue_task(&self->tqueue, &tq_immediate);
+ mark_bh(IMMEDIATE_BH);
+ break;
+ default: /* If we get here, something is very wrong, better stop */
+ case FLOW_STOP:
+ IRDA_DEBUG(2, "%s(), hw stopped!\n", __FUNCTION__);
+ tty->hw_stopped = 1;
+ break;
+ }
+ self->flow = cmd;
+}
+
+static int ircomm_tty_line_info(struct ircomm_tty_cb *self, char *buf)
+{
+ int ret=0;
+
+ ret += sprintf(buf+ret, "State: %s\n", ircomm_tty_state[self->state]);
+
+ ret += sprintf(buf+ret, "Service type: ");
+ if (self->service_type & IRCOMM_9_WIRE)
+ ret += sprintf(buf+ret, "9_WIRE");
+ else if (self->service_type & IRCOMM_3_WIRE)
+ ret += sprintf(buf+ret, "3_WIRE");
+ else if (self->service_type & IRCOMM_3_WIRE_RAW)
+ ret += sprintf(buf+ret, "3_WIRE_RAW");
+ else
+ ret += sprintf(buf+ret, "No common service type!\n");
+ ret += sprintf(buf+ret, "\n");
+
+ ret += sprintf(buf+ret, "Port name: %s\n", self->settings.port_name);
+
+ ret += sprintf(buf+ret, "DTE status: ");
+ if (self->settings.dte & IRCOMM_RTS)
+ ret += sprintf(buf+ret, "RTS|");
+ if (self->settings.dte & IRCOMM_DTR)
+ ret += sprintf(buf+ret, "DTR|");
+ if (self->settings.dte)
+ ret--; /* remove the last | */
+ ret += sprintf(buf+ret, "\n");
+
+ ret += sprintf(buf+ret, "DCE status: ");
+ if (self->settings.dce & IRCOMM_CTS)
+ ret += sprintf(buf+ret, "CTS|");
+ if (self->settings.dce & IRCOMM_DSR)
+ ret += sprintf(buf+ret, "DSR|");
+ if (self->settings.dce & IRCOMM_CD)
+ ret += sprintf(buf+ret, "CD|");
+ if (self->settings.dce & IRCOMM_RI)
+ ret += sprintf(buf+ret, "RI|");
+ if (self->settings.dce)
+ ret--; /* remove the last | */
+ ret += sprintf(buf+ret, "\n");
+
+ ret += sprintf(buf+ret, "Configuration: ");
+ if (!self->settings.null_modem)
+ ret += sprintf(buf+ret, "DTE <-> DCE\n");
+ else
+ ret += sprintf(buf+ret,
+ "DTE <-> DTE (null modem emulation)\n");
+
+ ret += sprintf(buf+ret, "Data rate: %d\n", self->settings.data_rate);
+
+ ret += sprintf(buf+ret, "Flow control: ");
+ if (self->settings.flow_control & IRCOMM_XON_XOFF_IN)
+ ret += sprintf(buf+ret, "XON_XOFF_IN|");
+ if (self->settings.flow_control & IRCOMM_XON_XOFF_OUT)
+ ret += sprintf(buf+ret, "XON_XOFF_OUT|");
+ if (self->settings.flow_control & IRCOMM_RTS_CTS_IN)
+ ret += sprintf(buf+ret, "RTS_CTS_IN|");
+ if (self->settings.flow_control & IRCOMM_RTS_CTS_OUT)
+ ret += sprintf(buf+ret, "RTS_CTS_OUT|");
+ if (self->settings.flow_control & IRCOMM_DSR_DTR_IN)
+ ret += sprintf(buf+ret, "DSR_DTR_IN|");
+ if (self->settings.flow_control & IRCOMM_DSR_DTR_OUT)
+ ret += sprintf(buf+ret, "DSR_DTR_OUT|");
+ if (self->settings.flow_control & IRCOMM_ENQ_ACK_IN)
+ ret += sprintf(buf+ret, "ENQ_ACK_IN|");
+ if (self->settings.flow_control & IRCOMM_ENQ_ACK_OUT)
+ ret += sprintf(buf+ret, "ENQ_ACK_OUT|");
+ if (self->settings.flow_control)
+ ret--; /* remove the last | */
+ ret += sprintf(buf+ret, "\n");
+
+ ret += sprintf(buf+ret, "Flags: ");
+ if (self->flags & ASYNC_CTS_FLOW)
+ ret += sprintf(buf+ret, "ASYNC_CTS_FLOW|");
+ if (self->flags & ASYNC_CHECK_CD)
+ ret += sprintf(buf+ret, "ASYNC_CHECK_CD|");
+ if (self->flags & ASYNC_INITIALIZED)
+ ret += sprintf(buf+ret, "ASYNC_INITIALIZED|");
+ if (self->flags & ASYNC_LOW_LATENCY)
+ ret += sprintf(buf+ret, "ASYNC_LOW_LATENCY|");
+ if (self->flags & ASYNC_CLOSING)
+ ret += sprintf(buf+ret, "ASYNC_CLOSING|");
+ if (self->flags & ASYNC_NORMAL_ACTIVE)
+ ret += sprintf(buf+ret, "ASYNC_NORMAL_ACTIVE|");
+ if (self->flags & ASYNC_CALLOUT_ACTIVE)
+ ret += sprintf(buf+ret, "ASYNC_CALLOUT_ACTIVE|");
+ if (self->flags)
+ ret--; /* remove the last | */
+ ret += sprintf(buf+ret, "\n");
+
+ ret += sprintf(buf+ret, "Role: %s\n", self->client ?
+ "client" : "server");
+ ret += sprintf(buf+ret, "Open count: %d\n", self->open_count);
+ ret += sprintf(buf+ret, "Max data size: %d\n", self->max_data_size);
+ ret += sprintf(buf+ret, "Max header size: %d\n", self->max_header_size);
+
+ if (self->tty)
+ ret += sprintf(buf+ret, "Hardware: %s\n",
+ self->tty->hw_stopped ? "Stopped" : "Running");
+
+ ret += sprintf(buf+ret, "\n");
+ return ret;
+}
+
+
+/*
+ * Function ircomm_tty_read_proc (buf, start, offset, len, eof, unused)
+ *
+ *
+ *
+ */
+#ifdef CONFIG_PROC_FS
+static int ircomm_tty_read_proc(char *buf, char **start, off_t offset, int len,
+ int *eof, void *unused)
+{
+ struct ircomm_tty_cb *self;
+ int count = 0, l;
+ off_t begin = 0;
+
+ self = (struct ircomm_tty_cb *) hashbin_get_first(ircomm_tty);
+ while ((self != NULL) && (count < 4000)) {
+ if (self->magic != IRCOMM_TTY_MAGIC)
+ return 0;
+
+ l = ircomm_tty_line_info(self, buf + count);
+ count += l;
+ if (count+begin > offset+len)
+ goto done;
+ if (count+begin < offset) {
+ begin += count;
+ count = 0;
+ }
+
+ self = (struct ircomm_tty_cb *) hashbin_get_next(ircomm_tty);
+ }
+ *eof = 1;
+done:
+ if (offset >= count+begin)
+ return 0;
+ *start = buf + (offset-begin);
+ return ((len < begin+count-offset) ? len : begin+count-offset);
+}
+#endif /* CONFIG_PROC_FS */
+
+#ifdef MODULE
+MODULE_AUTHOR("Dag Brattli <dagb@cs.uit.no>");
+MODULE_DESCRIPTION("IrCOMM serial TTY driver");
+MODULE_LICENSE("GPL");
+
+int init_module(void)
+{
+ return ircomm_tty_init();
+}
+
+void cleanup_module(void)
+{
+ ircomm_tty_cleanup();
+}
+
+#endif /* MODULE */
+
+
+
+
diff --git a/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_tty_attach.c b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_tty_attach.c
new file mode 100644
index 0000000..9777b86
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_tty_attach.c
@@ -0,0 +1,958 @@
+/*********************************************************************
+ *
+ * Filename: ircomm_tty_attach.c
+ * Version:
+ * Description: Code for attaching the serial driver to IrCOMM
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sat Jun 5 17:42:00 1999
+ * Modified at: Tue Jan 4 14:20:49 2000
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1999-2000 Dag Brattli, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ ********************************************************************/
+
+#include <linux/sched.h>
+#include <linux/init.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/iriap.h>
+#include <net/irda/irttp.h>
+#include <net/irda/irias_object.h>
+#include <net/irda/parameters.h>
+
+#include <net/irda/ircomm_core.h>
+#include <net/irda/ircomm_param.h>
+#include <net/irda/ircomm_event.h>
+
+#include <net/irda/ircomm_tty.h>
+#include <net/irda/ircomm_tty_attach.h>
+
+static void ircomm_tty_ias_register(struct ircomm_tty_cb *self);
+static void ircomm_tty_discovery_indication(discovery_t *discovery,
+ DISCOVERY_MODE mode,
+ void *priv);
+static void ircomm_tty_getvalue_confirm(int result, __u16 obj_id,
+ struct ias_value *value, void *priv);
+void ircomm_tty_start_watchdog_timer(struct ircomm_tty_cb *self, int timeout);
+void ircomm_tty_watchdog_timer_expired(void *data);
+
+static int ircomm_tty_state_idle(struct ircomm_tty_cb *self,
+ IRCOMM_TTY_EVENT event,
+ struct sk_buff *skb,
+ struct ircomm_tty_info *info);
+static int ircomm_tty_state_search(struct ircomm_tty_cb *self,
+ IRCOMM_TTY_EVENT event,
+ struct sk_buff *skb,
+ struct ircomm_tty_info *info);
+static int ircomm_tty_state_query_parameters(struct ircomm_tty_cb *self,
+ IRCOMM_TTY_EVENT event,
+ struct sk_buff *skb,
+ struct ircomm_tty_info *info);
+static int ircomm_tty_state_query_lsap_sel(struct ircomm_tty_cb *self,
+ IRCOMM_TTY_EVENT event,
+ struct sk_buff *skb,
+ struct ircomm_tty_info *info);
+static int ircomm_tty_state_setup(struct ircomm_tty_cb *self,
+ IRCOMM_TTY_EVENT event,
+ struct sk_buff *skb,
+ struct ircomm_tty_info *info);
+static int ircomm_tty_state_ready(struct ircomm_tty_cb *self,
+ IRCOMM_TTY_EVENT event,
+ struct sk_buff *skb,
+ struct ircomm_tty_info *info);
+
+char *ircomm_tty_state[] = {
+ "IRCOMM_TTY_IDLE",
+ "IRCOMM_TTY_SEARCH",
+ "IRCOMM_TTY_QUERY_PARAMETERS",
+ "IRCOMM_TTY_QUERY_LSAP_SEL",
+ "IRCOMM_TTY_SETUP",
+ "IRCOMM_TTY_READY",
+ "*** ERROR *** ",
+};
+
+char *ircomm_tty_event[] = {
+ "IRCOMM_TTY_ATTACH_CABLE",
+ "IRCOMM_TTY_DETACH_CABLE",
+ "IRCOMM_TTY_DATA_REQUEST",
+ "IRCOMM_TTY_DATA_INDICATION",
+ "IRCOMM_TTY_DISCOVERY_REQUEST",
+ "IRCOMM_TTY_DISCOVERY_INDICATION",
+ "IRCOMM_TTY_CONNECT_CONFIRM",
+ "IRCOMM_TTY_CONNECT_INDICATION",
+ "IRCOMM_TTY_DISCONNECT_REQUEST",
+ "IRCOMM_TTY_DISCONNECT_INDICATION",
+ "IRCOMM_TTY_WD_TIMER_EXPIRED",
+ "IRCOMM_TTY_GOT_PARAMETERS",
+ "IRCOMM_TTY_GOT_LSAPSEL",
+ "*** ERROR ****",
+};
+
+static int (*state[])(struct ircomm_tty_cb *self, IRCOMM_TTY_EVENT event,
+ struct sk_buff *skb, struct ircomm_tty_info *info) =
+{
+ ircomm_tty_state_idle,
+ ircomm_tty_state_search,
+ ircomm_tty_state_query_parameters,
+ ircomm_tty_state_query_lsap_sel,
+ ircomm_tty_state_setup,
+ ircomm_tty_state_ready,
+};
+
+/*
+ * Function ircomm_tty_attach_cable (driver)
+ *
+ * Try to attach cable (IrCOMM link). This function will only return
+ * when the link has been connected, or if an error condition occurs.
+ * If success, the return value is the resulting service type.
+ */
+int ircomm_tty_attach_cable(struct ircomm_tty_cb *self)
+{
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ /* Check if somebody has already connected to us */
+ if (ircomm_is_connected(self->ircomm)) {
+ IRDA_DEBUG(0, "%s(), already connected!\n", __FUNCTION__);
+ return 0;
+ }
+
+ /* Make sure nobody tries to write before the link is up */
+ self->tty->hw_stopped = 1;
+
+ ircomm_tty_ias_register(self);
+
+ /* Check if somebody has already connected to us */
+ if (ircomm_is_connected(self->ircomm)) {
+ IRDA_DEBUG(0, "%s(), already connected!\n", __FUNCTION__);
+ return 0;
+ }
+
+ ircomm_tty_do_event(self, IRCOMM_TTY_ATTACH_CABLE, NULL, NULL);
+
+ return 0;
+}
+
+/*
+ * Function ircomm_detach_cable (driver)
+ *
+ * Detach cable, or cable has been detached by peer
+ *
+ */
+void ircomm_tty_detach_cable(struct ircomm_tty_cb *self)
+{
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ del_timer(&self->watchdog_timer);
+
+ /* Remove IrCOMM hint bits */
+ irlmp_unregister_client(self->ckey);
+ irlmp_unregister_service(self->skey);
+
+ if (self->iriap) {
+ iriap_close(self->iriap);
+ self->iriap = NULL;
+ }
+
+ /* Remove LM-IAS object */
+ if (self->obj) {
+ irias_delete_object(self->obj);
+ self->obj = NULL;
+ }
+
+ ircomm_tty_do_event(self, IRCOMM_TTY_DETACH_CABLE, NULL, NULL);
+
+ /* Reset some values */
+ self->daddr = self->saddr = 0;
+ self->dlsap_sel = self->slsap_sel = 0;
+
+ memset(&self->settings, 0, sizeof(struct ircomm_params));
+}
+
+/*
+ * Function ircomm_tty_ias_register (self)
+ *
+ * Register with LM-IAS depending on which service type we are
+ *
+ */
+static void ircomm_tty_ias_register(struct ircomm_tty_cb *self)
+{
+ __u8 oct_seq[6];
+ __u16 hints;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ if (self->service_type & IRCOMM_3_WIRE_RAW) {
+ hints = irlmp_service_to_hint(S_PRINTER);
+ hints |= irlmp_service_to_hint(S_COMM);
+
+ /* Register IrLPT with LM-IAS */
+ self->obj = irias_new_object("IrLPT", IAS_IRLPT_ID);
+ irias_add_integer_attrib(self->obj, "IrDA:IrLMP:LsapSel",
+ self->slsap_sel, IAS_KERNEL_ATTR);
+ irias_insert_object(self->obj);
+ } else {
+ hints = irlmp_service_to_hint(S_COMM);
+
+ /* Register IrCOMM with LM-IAS */
+ self->obj = irias_new_object("IrDA:IrCOMM", IAS_IRCOMM_ID);
+ irias_add_integer_attrib(self->obj, "IrDA:TinyTP:LsapSel",
+ self->slsap_sel, IAS_KERNEL_ATTR);
+
+ /* Code the parameters into the buffer */
+ irda_param_pack(oct_seq, "bbbbbb",
+ IRCOMM_SERVICE_TYPE, 1, self->service_type,
+ IRCOMM_PORT_TYPE, 1, IRCOMM_SERIAL);
+
+ /* Register parameters with LM-IAS */
+ irias_add_octseq_attrib(self->obj, "Parameters", oct_seq, 6,
+ IAS_KERNEL_ATTR);
+ irias_insert_object(self->obj);
+ }
+ self->skey = irlmp_register_service(hints);
+ self->ckey = irlmp_register_client(
+ hints, ircomm_tty_discovery_indication, NULL, (void *) self);
+}
+
+/*
+ * Function ircomm_send_initial_parameters (self)
+ *
+ * Send initial parameters to the remote IrCOMM device. These parameters
+ * must be sent before any data.
+ */
+int ircomm_tty_send_initial_parameters(struct ircomm_tty_cb *self)
+{
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ if (self->service_type & IRCOMM_3_WIRE_RAW)
+ return 0;
+
+ /*
+ * Set default values, but only if the application for some reason
+ * haven't set them already
+ */
+ IRDA_DEBUG(2, "%s(), data-rate = %d\n", __FUNCTION__,
+ self->settings.data_rate);
+ if (!self->settings.data_rate)
+ self->settings.data_rate = 9600;
+ IRDA_DEBUG(2, "%s(), data-format = %d\n", __FUNCTION__,
+ self->settings.data_format);
+ if (!self->settings.data_format)
+ self->settings.data_format = IRCOMM_WSIZE_8; /* 8N1 */
+
+ IRDA_DEBUG(2, "%s(), flow-control = %d\n", __FUNCTION__,
+ self->settings.flow_control);
+ /*self->settings.flow_control = IRCOMM_RTS_CTS_IN|IRCOMM_RTS_CTS_OUT;*/
+
+ /* Do not set delta values for the initial parameters */
+ self->settings.dte = IRCOMM_DTR | IRCOMM_RTS;
+
+ /* Only send service type parameter when we are the client */
+ if (self->client)
+ ircomm_param_request(self, IRCOMM_SERVICE_TYPE, FALSE);
+ ircomm_param_request(self, IRCOMM_DATA_RATE, FALSE);
+ ircomm_param_request(self, IRCOMM_DATA_FORMAT, FALSE);
+
+ /* For a 3 wire service, we just flush the last parameter and return */
+ if (self->settings.service_type == IRCOMM_3_WIRE) {
+ ircomm_param_request(self, IRCOMM_FLOW_CONTROL, TRUE);
+ return 0;
+ }
+
+ /* Only 9-wire service types continue here */
+ ircomm_param_request(self, IRCOMM_FLOW_CONTROL, FALSE);
+#if 0
+ ircomm_param_request(self, IRCOMM_XON_XOFF, FALSE);
+ ircomm_param_request(self, IRCOMM_ENQ_ACK, FALSE);
+#endif
+ /* Notify peer that we are ready to receive data */
+ ircomm_param_request(self, IRCOMM_DTE, TRUE);
+
+ return 0;
+}
+
+/*
+ * Function ircomm_tty_discovery_indication (discovery)
+ *
+ * Remote device is discovered, try query the remote IAS to see which
+ * device it is, and which services it has.
+ *
+ */
+static void ircomm_tty_discovery_indication(discovery_t *discovery,
+ DISCOVERY_MODE mode,
+ void *priv)
+{
+ struct ircomm_tty_cb *self;
+ struct ircomm_tty_info info;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ /* Important note :
+ * We need to drop all passive discoveries.
+ * The LSAP management of IrComm is deficient and doesn't deal
+ * with the case of two instance connecting to each other
+ * simultaneously (it will deadlock in LMP).
+ * The proper fix would be to use the same technique as in IrNET,
+ * to have one server socket and separate instances for the
+ * connecting/connected socket.
+ * The workaround is to drop passive discovery, which drastically
+ * reduce the probability of this happening.
+ * Jean II */
+ if(mode == DISCOVERY_PASSIVE)
+ return;
+
+ info.daddr = discovery->daddr;
+ info.saddr = discovery->saddr;
+
+ self = (struct ircomm_tty_cb *) hashbin_get_first(ircomm_tty);
+ while (self != NULL) {
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ ircomm_tty_do_event(self, IRCOMM_TTY_DISCOVERY_INDICATION,
+ NULL, &info);
+
+ self = (struct ircomm_tty_cb *) hashbin_get_next(ircomm_tty);
+ }
+}
+
+/*
+ * Function ircomm_tty_disconnect_indication (instance, sap, reason, skb)
+ *
+ * Link disconnected
+ *
+ */
+void ircomm_tty_disconnect_indication(void *instance, void *sap,
+ LM_REASON reason,
+ struct sk_buff *skb)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ if (!self->tty)
+ return;
+
+ /* This will stop control data transfers */
+ self->flow = FLOW_STOP;
+
+ /* Stop data transfers */
+ self->tty->hw_stopped = 1;
+
+ ircomm_tty_do_event(self, IRCOMM_TTY_DISCONNECT_INDICATION, NULL,
+ NULL);
+}
+
+/*
+ * Function ircomm_tty_getvalue_confirm (result, obj_id, value, priv)
+ *
+ * Got result from the IAS query we make
+ *
+ */
+static void ircomm_tty_getvalue_confirm(int result, __u16 obj_id,
+ struct ias_value *value,
+ void *priv)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) priv;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ /* We probably don't need to make any more queries */
+ iriap_close(self->iriap);
+ self->iriap = NULL;
+
+ /* Check if request succeeded */
+ if (result != IAS_SUCCESS) {
+ IRDA_DEBUG(4, "%s(), got NULL value!\n", __FUNCTION__);
+ return;
+ }
+
+ switch (value->type) {
+ case IAS_OCT_SEQ:
+ IRDA_DEBUG(2, "%s(), got octet sequence\n", __FUNCTION__);
+
+ irda_param_extract_all(self, value->t.oct_seq, value->len,
+ &ircomm_param_info);
+
+ ircomm_tty_do_event(self, IRCOMM_TTY_GOT_PARAMETERS, NULL,
+ NULL);
+ break;
+ case IAS_INTEGER:
+ /* Got LSAP selector */
+ IRDA_DEBUG(2, "%s(), got lsapsel = %d\n", __FUNCTION__,
+ value->t.integer);
+
+ if (value->t.integer == -1) {
+ IRDA_DEBUG(0, "%s(), invalid value!\n", __FUNCTION__);
+ } else
+ self->dlsap_sel = value->t.integer;
+
+ ircomm_tty_do_event(self, IRCOMM_TTY_GOT_LSAPSEL, NULL, NULL);
+ break;
+ case IAS_MISSING:
+ IRDA_DEBUG(0, "%s(), got IAS_MISSING\n", __FUNCTION__);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), got unknown type!\n", __FUNCTION__);
+ break;
+ }
+ irias_delete_value(value);
+}
+
+/*
+ * Function ircomm_tty_connect_confirm (instance, sap, qos, max_sdu_size, skb)
+ *
+ * Connection confirmed
+ *
+ */
+void ircomm_tty_connect_confirm(void *instance, void *sap,
+ struct qos_info *qos,
+ __u32 max_data_size,
+ __u8 max_header_size,
+ struct sk_buff *skb)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ self->client = TRUE;
+ self->max_data_size = max_data_size;
+ self->max_header_size = max_header_size;
+ self->flow = FLOW_START;
+
+ ircomm_tty_do_event(self, IRCOMM_TTY_CONNECT_CONFIRM, NULL, NULL);
+
+ dev_kfree_skb(skb);
+}
+
+/*
+ * Function ircomm_tty_connect_indication (instance, sap, qos, max_sdu_size,
+ * skb)
+ *
+ * we are discovered and being requested to connect by remote device !
+ *
+ */
+void ircomm_tty_connect_indication(void *instance, void *sap,
+ struct qos_info *qos,
+ __u32 max_data_size,
+ __u8 max_header_size,
+ struct sk_buff *skb)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) instance;
+ int clen;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ self->client = FALSE;
+ self->max_data_size = max_data_size;
+ self->max_header_size = max_header_size;
+ self->flow = FLOW_START;
+
+ clen = skb->data[0];
+ if (clen)
+ irda_param_extract_all(self, skb->data+1,
+ IRDA_MIN(skb->len, clen),
+ &ircomm_param_info);
+
+ ircomm_tty_do_event(self, IRCOMM_TTY_CONNECT_INDICATION, NULL, NULL);
+
+ dev_kfree_skb(skb);
+}
+
+/*
+ * Function ircomm_tty_link_established (self)
+ *
+ * Called when the IrCOMM link is established
+ *
+ */
+void ircomm_tty_link_established(struct ircomm_tty_cb *self)
+{
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ if (!self->tty)
+ return;
+
+ del_timer(&self->watchdog_timer);
+
+ /* Remove LM-IAS object now so it is not reused.
+ * IrCOMM deals very poorly with multiple incomming connections.
+ * It should looks a lot more like IrNET, and "dup" a server TSAP
+ * to the application TSAP (based on various rules).
+ * This is a cheap workaround allowing multiple clients to
+ * connect to us. It will not always work.
+ * Each IrCOMM socket has an IAS entry. Incomming connection will
+ * pick the first one found. So, when we are fully connected,
+ * we remove our IAS entries so that the next IAS entry is used.
+ * We do that for *both* client and server, because a server
+ * can also create client instances.
+ * Jean II */
+ if (self->obj) {
+ irias_delete_object(self->obj);
+ self->obj = NULL;
+ }
+
+ /*
+ * IrCOMM link is now up, and if we are not using hardware
+ * flow-control, then declare the hardware as running. Otherwise we
+ * will have to wait for the peer device (DCE) to raise the CTS
+ * line.
+ */
+ if ((self->flags & ASYNC_CTS_FLOW) && ((self->settings.dce & IRCOMM_CTS) == 0)) {
+ IRDA_DEBUG(0, "%s(), waiting for CTS ...\n", __FUNCTION__);
+ return;
+ } else {
+ IRDA_DEBUG(1, "%s(), starting hardware!\n", __FUNCTION__);
+
+ self->tty->hw_stopped = 0;
+
+ /* Wake up processes blocked on open */
+ wake_up_interruptible(&self->open_wait);
+ }
+
+ queue_task(&self->tqueue, &tq_immediate);
+ mark_bh(IMMEDIATE_BH);
+}
+
+/*
+ * Function irlan_start_watchdog_timer (self, timeout)
+ *
+ * Start the watchdog timer. This timer is used to make sure that any
+ * connection attempt is successful, and if not, we will retry after
+ * the timeout
+ */
+void ircomm_tty_start_watchdog_timer(struct ircomm_tty_cb *self, int timeout)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ irda_start_timer(&self->watchdog_timer, timeout, (void *) self,
+ ircomm_tty_watchdog_timer_expired);
+}
+
+/*
+ * Function ircomm_tty_watchdog_timer_expired (data)
+ *
+ * Called when the connect procedure have taken to much time.
+ *
+ */
+void ircomm_tty_watchdog_timer_expired(void *data)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) data;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ ircomm_tty_do_event(self, IRCOMM_TTY_WD_TIMER_EXPIRED, NULL, NULL);
+}
+
+/*
+ * Function ircomm_tty_state_idle (self, event, skb, info)
+ *
+ * Just hanging around
+ *
+ */
+static int ircomm_tty_state_idle(struct ircomm_tty_cb *self,
+ IRCOMM_TTY_EVENT event,
+ struct sk_buff *skb,
+ struct ircomm_tty_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(2, "%s: state=%s, event=%s\n", __FUNCTION__,
+ ircomm_tty_state[self->state], ircomm_tty_event[event]);
+ switch (event) {
+ case IRCOMM_TTY_ATTACH_CABLE:
+ /* Try to discover any remote devices */
+ ircomm_tty_start_watchdog_timer(self, 3*HZ);
+ ircomm_tty_next_state(self, IRCOMM_TTY_SEARCH);
+
+ irlmp_discovery_request(DISCOVERY_DEFAULT_SLOTS);
+ break;
+ case IRCOMM_TTY_DISCOVERY_INDICATION:
+ self->daddr = info->daddr;
+ self->saddr = info->saddr;
+
+ if (self->iriap) {
+ WARNING("%s(), busy with a previous query\n", __FUNCTION__);
+ return -EBUSY;
+ }
+
+ self->iriap = iriap_open(LSAP_ANY, IAS_CLIENT, self,
+ ircomm_tty_getvalue_confirm);
+
+ iriap_getvaluebyclass_request(self->iriap,
+ self->saddr, self->daddr,
+ "IrDA:IrCOMM", "Parameters");
+
+ ircomm_tty_start_watchdog_timer(self, 3*HZ);
+ ircomm_tty_next_state(self, IRCOMM_TTY_QUERY_PARAMETERS);
+ break;
+ case IRCOMM_TTY_CONNECT_INDICATION:
+ del_timer(&self->watchdog_timer);
+
+ /* Accept connection */
+ ircomm_connect_response(self->ircomm, NULL);
+ ircomm_tty_next_state(self, IRCOMM_TTY_READY);
+ break;
+ case IRCOMM_TTY_WD_TIMER_EXPIRED:
+ /* Just stay idle */
+ break;
+ case IRCOMM_TTY_DETACH_CABLE:
+ ircomm_tty_next_state(self, IRCOMM_TTY_IDLE);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), unknown event: %s\n", __FUNCTION__,
+ ircomm_tty_event[event]);
+ return -EINVAL;
+ }
+ return ret;
+}
+
+/*
+ * Function ircomm_tty_state_search (self, event, skb, info)
+ *
+ * Trying to discover an IrCOMM device
+ *
+ */
+static int ircomm_tty_state_search(struct ircomm_tty_cb *self,
+ IRCOMM_TTY_EVENT event,
+ struct sk_buff *skb,
+ struct ircomm_tty_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(2, "%s: state=%s, event=%s\n", __FUNCTION__,
+ ircomm_tty_state[self->state], ircomm_tty_event[event]);
+
+ switch (event) {
+ case IRCOMM_TTY_DISCOVERY_INDICATION:
+ self->daddr = info->daddr;
+ self->saddr = info->saddr;
+
+ if (self->iriap) {
+ WARNING("%s(), busy with a previous query\n", __FUNCTION__);
+ return -EBUSY;
+ }
+
+ self->iriap = iriap_open(LSAP_ANY, IAS_CLIENT, self,
+ ircomm_tty_getvalue_confirm);
+
+ if (self->service_type == IRCOMM_3_WIRE_RAW) {
+ iriap_getvaluebyclass_request(self->iriap, self->saddr,
+ self->daddr, "IrLPT",
+ "IrDA:IrLMP:LsapSel");
+ ircomm_tty_next_state(self, IRCOMM_TTY_QUERY_LSAP_SEL);
+ } else {
+ iriap_getvaluebyclass_request(self->iriap, self->saddr,
+ self->daddr,
+ "IrDA:IrCOMM",
+ "Parameters");
+
+ ircomm_tty_next_state(self, IRCOMM_TTY_QUERY_PARAMETERS);
+ }
+ ircomm_tty_start_watchdog_timer(self, 3*HZ);
+ break;
+ case IRCOMM_TTY_CONNECT_INDICATION:
+ del_timer(&self->watchdog_timer);
+
+ /* Accept connection */
+ ircomm_connect_response(self->ircomm, NULL);
+ ircomm_tty_next_state(self, IRCOMM_TTY_READY);
+ break;
+ case IRCOMM_TTY_WD_TIMER_EXPIRED:
+#if 1
+ /* Give up */
+#else
+ /* Try to discover any remote devices */
+ ircomm_tty_start_watchdog_timer(self, 3*HZ);
+ irlmp_discovery_request(DISCOVERY_DEFAULT_SLOTS);
+#endif
+ break;
+ case IRCOMM_TTY_DETACH_CABLE:
+ ircomm_tty_next_state(self, IRCOMM_TTY_IDLE);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), unknown event: %s\n", __FUNCTION__,
+ ircomm_tty_event[event]);
+ return -EINVAL;
+ }
+ return ret;
+}
+
+/*
+ * Function ircomm_tty_state_query (self, event, skb, info)
+ *
+ * Querying the remote LM-IAS for IrCOMM parameters
+ *
+ */
+static int ircomm_tty_state_query_parameters(struct ircomm_tty_cb *self,
+ IRCOMM_TTY_EVENT event,
+ struct sk_buff *skb,
+ struct ircomm_tty_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(2, "%s: state=%s, event=%s\n", __FUNCTION__,
+ ircomm_tty_state[self->state], ircomm_tty_event[event]);
+
+ switch (event) {
+ case IRCOMM_TTY_GOT_PARAMETERS:
+ if (self->iriap) {
+ WARNING("%s(), busy with a previous query\n", __FUNCTION__);
+ return -EBUSY;
+ }
+
+ self->iriap = iriap_open(LSAP_ANY, IAS_CLIENT, self,
+ ircomm_tty_getvalue_confirm);
+
+ iriap_getvaluebyclass_request(self->iriap, self->saddr,
+ self->daddr, "IrDA:IrCOMM",
+ "IrDA:TinyTP:LsapSel");
+
+ ircomm_tty_start_watchdog_timer(self, 3*HZ);
+ ircomm_tty_next_state(self, IRCOMM_TTY_QUERY_LSAP_SEL);
+ break;
+ case IRCOMM_TTY_WD_TIMER_EXPIRED:
+ /* Go back to search mode */
+ ircomm_tty_next_state(self, IRCOMM_TTY_SEARCH);
+ ircomm_tty_start_watchdog_timer(self, 3*HZ);
+ break;
+ case IRCOMM_TTY_CONNECT_INDICATION:
+ del_timer(&self->watchdog_timer);
+
+ /* Accept connection */
+ ircomm_connect_response(self->ircomm, NULL);
+ ircomm_tty_next_state(self, IRCOMM_TTY_READY);
+ break;
+ case IRCOMM_TTY_DETACH_CABLE:
+ ircomm_tty_next_state(self, IRCOMM_TTY_IDLE);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), unknown event: %s\n", __FUNCTION__,
+ ircomm_tty_event[event]);
+ return -EINVAL;
+ }
+ return ret;
+}
+
+/*
+ * Function ircomm_tty_state_query_lsap_sel (self, event, skb, info)
+ *
+ * Query remote LM-IAS for the LSAP selector which we can connect to
+ *
+ */
+static int ircomm_tty_state_query_lsap_sel(struct ircomm_tty_cb *self,
+ IRCOMM_TTY_EVENT event,
+ struct sk_buff *skb,
+ struct ircomm_tty_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(2, "%s: state=%s, event=%s\n", __FUNCTION__,
+ ircomm_tty_state[self->state], ircomm_tty_event[event]);
+
+ switch (event) {
+ case IRCOMM_TTY_GOT_LSAPSEL:
+ /* Connect to remote device */
+ ret = ircomm_connect_request(self->ircomm, self->dlsap_sel,
+ self->saddr, self->daddr,
+ NULL, self->service_type);
+ ircomm_tty_start_watchdog_timer(self, 3*HZ);
+ ircomm_tty_next_state(self, IRCOMM_TTY_SETUP);
+ break;
+ case IRCOMM_TTY_WD_TIMER_EXPIRED:
+ /* Go back to search mode */
+ ircomm_tty_next_state(self, IRCOMM_TTY_SEARCH);
+ ircomm_tty_start_watchdog_timer(self, 3*HZ);
+ break;
+ case IRCOMM_TTY_CONNECT_INDICATION:
+ del_timer(&self->watchdog_timer);
+
+ /* Accept connection */
+ ircomm_connect_response(self->ircomm, NULL);
+ ircomm_tty_next_state(self, IRCOMM_TTY_READY);
+ break;
+ case IRCOMM_TTY_DETACH_CABLE:
+ ircomm_tty_next_state(self, IRCOMM_TTY_IDLE);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), unknown event: %s\n", __FUNCTION__,
+ ircomm_tty_event[event]);
+ return -EINVAL;
+ }
+ return ret;
+}
+
+/*
+ * Function ircomm_tty_state_setup (self, event, skb, info)
+ *
+ * Trying to connect
+ *
+ */
+static int ircomm_tty_state_setup(struct ircomm_tty_cb *self,
+ IRCOMM_TTY_EVENT event,
+ struct sk_buff *skb,
+ struct ircomm_tty_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(2, "%s: state=%s, event=%s\n", __FUNCTION__,
+ ircomm_tty_state[self->state], ircomm_tty_event[event]);
+
+ switch (event) {
+ case IRCOMM_TTY_CONNECT_CONFIRM:
+ del_timer(&self->watchdog_timer);
+ ircomm_tty_next_state(self, IRCOMM_TTY_READY);
+
+ /*
+ * Send initial parameters. This will also send out queued
+ * parameters waiting for the connection to come up
+ */
+ ircomm_tty_send_initial_parameters(self);
+ ircomm_tty_link_established(self);
+ break;
+ case IRCOMM_TTY_CONNECT_INDICATION:
+ del_timer(&self->watchdog_timer);
+
+ /* Accept connection */
+ ircomm_connect_response(self->ircomm, NULL);
+ ircomm_tty_next_state(self, IRCOMM_TTY_READY);
+ break;
+ case IRCOMM_TTY_WD_TIMER_EXPIRED:
+ /* Go back to search mode */
+ ircomm_tty_next_state(self, IRCOMM_TTY_SEARCH);
+ ircomm_tty_start_watchdog_timer(self, 3*HZ);
+ break;
+ case IRCOMM_TTY_DETACH_CABLE:
+ /* ircomm_disconnect_request(self->ircomm, NULL); */
+ ircomm_tty_next_state(self, IRCOMM_TTY_IDLE);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), unknown event: %s\n", __FUNCTION__,
+ ircomm_tty_event[event]);
+ return -EINVAL;
+ }
+ return ret;
+}
+
+/*
+ * Function ircomm_tty_state_ready (self, event, skb, info)
+ *
+ * IrCOMM is now connected
+ *
+ */
+static int ircomm_tty_state_ready(struct ircomm_tty_cb *self,
+ IRCOMM_TTY_EVENT event,
+ struct sk_buff *skb,
+ struct ircomm_tty_info *info)
+{
+ int ret = 0;
+
+ switch (event) {
+ case IRCOMM_TTY_DATA_REQUEST:
+ ret = ircomm_data_request(self->ircomm, skb);
+ break;
+ case IRCOMM_TTY_DETACH_CABLE:
+ ircomm_disconnect_request(self->ircomm, NULL);
+ ircomm_tty_next_state(self, IRCOMM_TTY_IDLE);
+ break;
+ case IRCOMM_TTY_DISCONNECT_INDICATION:
+ ircomm_tty_next_state(self, IRCOMM_TTY_SEARCH);
+ ircomm_tty_start_watchdog_timer(self, 3*HZ);
+
+ if (self->flags & ASYNC_CHECK_CD) {
+ /* Drop carrier */
+ self->settings.dce = IRCOMM_DELTA_CD;
+ ircomm_tty_check_modem_status(self);
+ } else {
+ IRDA_DEBUG(0, "%s(), hanging up!\n", __FUNCTION__);
+ if (self->tty)
+ tty_hangup(self->tty);
+ }
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), unknown event: %s\n", __FUNCTION__,
+ ircomm_tty_event[event]);
+ return -EINVAL;
+ }
+ return ret;
+}
+
+/*
+ * Function ircomm_tty_do_event (self, event, skb)
+ *
+ * Process event
+ *
+ */
+int ircomm_tty_do_event(struct ircomm_tty_cb *self, IRCOMM_TTY_EVENT event,
+ struct sk_buff *skb, struct ircomm_tty_info *info)
+{
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ IRDA_DEBUG(2, "%s: state=%s, event=%s\n", __FUNCTION__,
+ ircomm_tty_state[self->state], ircomm_tty_event[event]);
+
+ return (*state[self->state])(self, event, skb, info);
+}
+
+/*
+ * Function ircomm_tty_next_state (self, state)
+ *
+ * Switch state
+ *
+ */
+void ircomm_tty_next_state(struct ircomm_tty_cb *self, IRCOMM_TTY_STATE state)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return;);
+
+ self->state = state;
+
+ IRDA_DEBUG(2, "%s: next state=%s, service type=%d\n", __FUNCTION__,
+ ircomm_tty_state[self->state], self->service_type);
+}
+
diff --git a/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_tty_ioctl.c b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_tty_ioctl.c
new file mode 100644
index 0000000..64e12ed
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/ircomm/ircomm_tty_ioctl.c
@@ -0,0 +1,456 @@
+/*********************************************************************
+ *
+ * Filename: ircomm_tty_ioctl.c
+ * Version:
+ * Description:
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Thu Jun 10 14:39:09 1999
+ * Modified at: Wed Jan 5 14:45:43 2000
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1999-2000 Dag Brattli, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ ********************************************************************/
+
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/termios.h>
+#include <linux/tty.h>
+#include <linux/serial.h>
+
+#include <asm/segment.h>
+#include <asm/uaccess.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irmod.h>
+
+#include <net/irda/ircomm_core.h>
+#include <net/irda/ircomm_param.h>
+#include <net/irda/ircomm_tty_attach.h>
+#include <net/irda/ircomm_tty.h>
+
+#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))
+
+/*
+ * Function ircomm_tty_change_speed (driver)
+ *
+ * Change speed of the driver. If the remote device is a DCE, then this
+ * should make it change the speed of its serial port
+ */
+void ircomm_tty_change_speed(struct ircomm_tty_cb *self)
+{
+ unsigned cflag, cval;
+ int baud;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ if (!self->tty || !self->tty->termios || !self->ircomm)
+ return;
+
+ cflag = self->tty->termios->c_cflag;
+
+ /* byte size and parity */
+ switch (cflag & CSIZE) {
+ case CS5: cval = IRCOMM_WSIZE_5; break;
+ case CS6: cval = IRCOMM_WSIZE_6; break;
+ case CS7: cval = IRCOMM_WSIZE_7; break;
+ case CS8: cval = IRCOMM_WSIZE_8; break;
+ default: cval = IRCOMM_WSIZE_5; break;
+ }
+ if (cflag & CSTOPB)
+ cval |= IRCOMM_2_STOP_BIT;
+
+ if (cflag & PARENB)
+ cval |= IRCOMM_PARITY_ENABLE;
+ if (!(cflag & PARODD))
+ cval |= IRCOMM_PARITY_EVEN;
+
+ /* Determine divisor based on baud rate */
+ baud = tty_get_baud_rate(self->tty);
+ if (!baud)
+ baud = 9600; /* B0 transition handled in rs_set_termios */
+
+ self->settings.data_rate = baud;
+ ircomm_param_request(self, IRCOMM_DATA_RATE, FALSE);
+
+ /* CTS flow control flag and modem status interrupts */
+ if (cflag & CRTSCTS) {
+ self->flags |= ASYNC_CTS_FLOW;
+ self->settings.flow_control |= IRCOMM_RTS_CTS_IN;
+ /* This got me. Bummer. Jean II */
+ if (self->service_type == IRCOMM_3_WIRE_RAW)
+ WARNING("%s(), enabling RTS/CTS on link that doesn't support it (3-wire-raw)\n", __FUNCTION__);
+ } else {
+ self->flags &= ~ASYNC_CTS_FLOW;
+ self->settings.flow_control &= ~IRCOMM_RTS_CTS_IN;
+ }
+ if (cflag & CLOCAL)
+ self->flags &= ~ASYNC_CHECK_CD;
+ else
+ self->flags |= ASYNC_CHECK_CD;
+#if 0
+ /*
+ * Set up parity check flag
+ */
+
+ if (I_INPCK(self->tty))
+ driver->read_status_mask |= LSR_FE | LSR_PE;
+ if (I_BRKINT(driver->tty) || I_PARMRK(driver->tty))
+ driver->read_status_mask |= LSR_BI;
+
+ /*
+ * Characters to ignore
+ */
+ driver->ignore_status_mask = 0;
+ if (I_IGNPAR(driver->tty))
+ driver->ignore_status_mask |= LSR_PE | LSR_FE;
+
+ if (I_IGNBRK(self->tty)) {
+ self->ignore_status_mask |= LSR_BI;
+ /*
+ * If we're ignore parity and break indicators, ignore
+ * overruns too. (For real raw support).
+ */
+ if (I_IGNPAR(self->tty))
+ self->ignore_status_mask |= LSR_OE;
+ }
+#endif
+ self->settings.data_format = cval;
+
+ ircomm_param_request(self, IRCOMM_DATA_FORMAT, FALSE);
+ ircomm_param_request(self, IRCOMM_FLOW_CONTROL, TRUE);
+}
+
+/*
+ * Function ircomm_tty_set_termios (tty, old_termios)
+ *
+ * This routine allows the tty driver to be notified when device's
+ * termios settings have changed. Note that a well-designed tty driver
+ * should be prepared to accept the case where old == NULL, and try to
+ * do something rational.
+ */
+void ircomm_tty_set_termios(struct tty_struct *tty,
+ struct termios *old_termios)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
+ unsigned int cflag = tty->termios->c_cflag;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ if ((cflag == old_termios->c_cflag) &&
+ (RELEVANT_IFLAG(tty->termios->c_iflag) ==
+ RELEVANT_IFLAG(old_termios->c_iflag)))
+ {
+ return;
+ }
+
+ ircomm_tty_change_speed(self);
+
+ /* Handle transition to B0 status */
+ if ((old_termios->c_cflag & CBAUD) &&
+ !(cflag & CBAUD)) {
+ self->settings.dte &= ~(IRCOMM_DTR|IRCOMM_RTS);
+ ircomm_param_request(self, IRCOMM_DTE, TRUE);
+ }
+
+ /* Handle transition away from B0 status */
+ if (!(old_termios->c_cflag & CBAUD) &&
+ (cflag & CBAUD)) {
+ self->settings.dte |= IRCOMM_DTR;
+ if (!(tty->termios->c_cflag & CRTSCTS) ||
+ !test_bit(TTY_THROTTLED, &tty->flags)) {
+ self->settings.dte |= IRCOMM_RTS;
+ }
+ ircomm_param_request(self, IRCOMM_DTE, TRUE);
+ }
+
+ /* Handle turning off CRTSCTS */
+ if ((old_termios->c_cflag & CRTSCTS) &&
+ !(tty->termios->c_cflag & CRTSCTS))
+ {
+ tty->hw_stopped = 0;
+ ircomm_tty_start(tty);
+ }
+}
+
+/*
+ * Function ircomm_tty_get_modem_info (self, value)
+ *
+ *
+ *
+ */
+static int ircomm_tty_get_modem_info(struct ircomm_tty_cb *self,
+ unsigned int *value)
+{
+ unsigned int result;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ result = ((self->settings.dte & IRCOMM_RTS) ? TIOCM_RTS : 0)
+ | ((self->settings.dte & IRCOMM_DTR) ? TIOCM_DTR : 0)
+ | ((self->settings.dce & IRCOMM_CD) ? TIOCM_CAR : 0)
+ | ((self->settings.dce & IRCOMM_RI) ? TIOCM_RNG : 0)
+ | ((self->settings.dce & IRCOMM_DSR) ? TIOCM_DSR : 0)
+ | ((self->settings.dce & IRCOMM_CTS) ? TIOCM_CTS : 0);
+
+ return put_user(result, value);
+}
+
+/*
+ * Function set_modem_info (driver, cmd, value)
+ *
+ *
+ *
+ */
+static int ircomm_tty_set_modem_info(struct ircomm_tty_cb *self,
+ unsigned int cmd, unsigned int *value)
+{
+ unsigned int arg;
+ __u8 old_rts, old_dtr;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRCOMM_TTY_MAGIC, return -1;);
+
+ if (get_user(arg, value))
+ return -EFAULT;
+
+ old_rts = self->settings.dte & IRCOMM_RTS;
+ old_dtr = self->settings.dte & IRCOMM_DTR;
+
+ switch (cmd) {
+ case TIOCMBIS:
+ if (arg & TIOCM_RTS)
+ self->settings.dte |= IRCOMM_RTS;
+ if (arg & TIOCM_DTR)
+ self->settings.dte |= IRCOMM_DTR;
+ break;
+
+ case TIOCMBIC:
+ if (arg & TIOCM_RTS)
+ self->settings.dte &= ~IRCOMM_RTS;
+ if (arg & TIOCM_DTR)
+ self->settings.dte &= ~IRCOMM_DTR;
+ break;
+
+ case TIOCMSET:
+ self->settings.dte =
+ ((self->settings.dte & ~(IRCOMM_RTS | IRCOMM_DTR))
+ | ((arg & TIOCM_RTS) ? IRCOMM_RTS : 0)
+ | ((arg & TIOCM_DTR) ? IRCOMM_DTR : 0));
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ if ((self->settings.dte & IRCOMM_RTS) != old_rts)
+ self->settings.dte |= IRCOMM_DELTA_RTS;
+
+ if ((self->settings.dte & IRCOMM_DTR) != old_dtr)
+ self->settings.dte |= IRCOMM_DELTA_DTR;
+
+ ircomm_param_request(self, IRCOMM_DTE, TRUE);
+
+ return 0;
+}
+
+/*
+ * Function get_serial_info (driver, retinfo)
+ *
+ *
+ *
+ */
+static int ircomm_tty_get_serial_info(struct ircomm_tty_cb *self,
+ struct serial_struct *retinfo)
+{
+ struct serial_struct info;
+
+ if (!retinfo)
+ return -EFAULT;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ memset(&info, 0, sizeof(info));
+ info.line = self->line;
+ info.flags = self->flags;
+ info.baud_base = self->settings.data_rate;
+ info.close_delay = self->close_delay;
+ info.closing_wait = self->closing_wait;
+
+ /* For compatibility */
+ info.type = PORT_16550A;
+ info.port = 0;
+ info.irq = 0;
+ info.xmit_fifo_size = 0;
+ info.hub6 = 0;
+ info.custom_divisor = 0;
+
+ if (copy_to_user(retinfo, &info, sizeof(*retinfo)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * Function set_serial_info (driver, new_info)
+ *
+ *
+ *
+ */
+static int ircomm_tty_set_serial_info(struct ircomm_tty_cb *self,
+ struct serial_struct *new_info)
+{
+#if 0
+ struct serial_struct new_serial;
+ struct ircomm_tty_cb old_state, *state;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ if (copy_from_user(&new_serial,new_info,sizeof(new_serial)))
+ return -EFAULT;
+
+
+ state = self
+ old_state = *self;
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ if ((new_serial.baud_base != state->settings.data_rate) ||
+ (new_serial.close_delay != state->close_delay) ||
+ ((new_serial.flags & ~ASYNC_USR_MASK) !=
+ (self->flags & ~ASYNC_USR_MASK)))
+ return -EPERM;
+ state->flags = ((state->flags & ~ASYNC_USR_MASK) |
+ (new_serial.flags & ASYNC_USR_MASK));
+ self->flags = ((self->flags & ~ASYNC_USR_MASK) |
+ (new_serial.flags & ASYNC_USR_MASK));
+ /* self->custom_divisor = new_serial.custom_divisor; */
+ goto check_and_exit;
+ }
+
+ /*
+ * OK, past this point, all the error checking has been done.
+ * At this point, we start making changes.....
+ */
+
+ if (self->settings.data_rate != new_serial.baud_base) {
+ self->settings.data_rate = new_serial.baud_base;
+ ircomm_param_request(self, IRCOMM_DATA_RATE, TRUE);
+ }
+
+ self->close_delay = new_serial.close_delay * HZ/100;
+ self->closing_wait = new_serial.closing_wait * HZ/100;
+ /* self->custom_divisor = new_serial.custom_divisor; */
+
+ self->flags = ((self->flags & ~ASYNC_FLAGS) |
+ (new_serial.flags & ASYNC_FLAGS));
+ self->tty->low_latency = (self->flags & ASYNC_LOW_LATENCY) ? 1 : 0;
+
+ check_and_exit:
+
+ if (self->flags & ASYNC_INITIALIZED) {
+ if (((old_state.flags & ASYNC_SPD_MASK) !=
+ (self->flags & ASYNC_SPD_MASK)) ||
+ (old_driver.custom_divisor != driver->custom_divisor)) {
+ if ((driver->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI)
+ driver->tty->alt_speed = 57600;
+ if ((driver->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI)
+ driver->tty->alt_speed = 115200;
+ if ((driver->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI)
+ driver->tty->alt_speed = 230400;
+ if ((driver->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP)
+ driver->tty->alt_speed = 460800;
+ ircomm_tty_change_speed(driver);
+ }
+ }
+#endif
+ return 0;
+}
+
+/*
+ * Function ircomm_tty_ioctl (tty, file, cmd, arg)
+ *
+ *
+ *
+ */
+int ircomm_tty_ioctl(struct tty_struct *tty, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct ircomm_tty_cb *self = (struct ircomm_tty_cb *) tty->driver_data;
+ int ret = 0;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) &&
+ (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGSTRUCT) &&
+ (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) {
+ if (tty->flags & (1 << TTY_IO_ERROR))
+ return -EIO;
+ }
+
+ switch (cmd) {
+ case TIOCMGET:
+ ret = ircomm_tty_get_modem_info(self, (unsigned int *) arg);
+ break;
+ case TIOCMBIS:
+ case TIOCMBIC:
+ case TIOCMSET:
+ ret = ircomm_tty_set_modem_info(self, cmd, (unsigned int *) arg);
+ break;
+ case TIOCGSERIAL:
+ ret = ircomm_tty_get_serial_info(self, (struct serial_struct *) arg);
+ break;
+ case TIOCSSERIAL:
+ ret = ircomm_tty_set_serial_info(self, (struct serial_struct *) arg);
+ break;
+ case TIOCMIWAIT:
+ IRDA_DEBUG(0, "(), TIOCMIWAIT, not impl!\n");
+ break;
+
+ case TIOCGICOUNT:
+ IRDA_DEBUG(0, "%s(), TIOCGICOUNT not impl!\n", __FUNCTION__);
+#if 0
+ save_flags(flags); cli();
+ cnow = driver->icount;
+ restore_flags(flags);
+ p_cuser = (struct serial_icounter_struct *) arg;
+ if (put_user(cnow.cts, &p_cuser->cts) ||
+ put_user(cnow.dsr, &p_cuser->dsr) ||
+ put_user(cnow.rng, &p_cuser->rng) ||
+ put_user(cnow.dcd, &p_cuser->dcd) ||
+ put_user(cnow.rx, &p_cuser->rx) ||
+ put_user(cnow.tx, &p_cuser->tx) ||
+ put_user(cnow.frame, &p_cuser->frame) ||
+ put_user(cnow.overrun, &p_cuser->overrun) ||
+ put_user(cnow.parity, &p_cuser->parity) ||
+ put_user(cnow.brk, &p_cuser->brk) ||
+ put_user(cnow.buf_overrun, &p_cuser->buf_overrun))
+ return -EFAULT;
+#endif
+ return 0;
+ default:
+ ret = -ENOIOCTLCMD; /* ioctls which we must ignore */
+ }
+ return ret;
+}
+
+
+
diff --git a/uClinux-2.4.31-uc0/net/irda/irda_device.c b/uClinux-2.4.31-uc0/net/irda/irda_device.c
new file mode 100644
index 0000000..b62e965
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irda_device.c
@@ -0,0 +1,629 @@
+/*********************************************************************
+ *
+ * Filename: irda_device.c
+ * Version: 0.9
+ * Description: Utility functions used by the device drivers
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sat Oct 9 09:22:27 1999
+ * Modified at: Sun Jan 23 17:41:24 2000
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1999-2000 Dag Brattli, All Rights Reserved.
+ * Copyright (c) 2000-2001 Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/proc_fs.h>
+#include <linux/skbuff.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/if_arp.h>
+#include <linux/netdevice.h>
+#include <linux/init.h>
+#include <linux/tty.h>
+#include <linux/kmod.h>
+#include <linux/wireless.h>
+#include <linux/spinlock.h>
+
+#include <asm/ioctls.h>
+#include <asm/segment.h>
+#include <asm/uaccess.h>
+#include <asm/dma.h>
+#include <asm/io.h>
+
+#include <net/pkt_sched.h>
+
+#include <net/irda/irda_device.h>
+#include <net/irda/irlap.h>
+#include <net/irda/timer.h>
+#include <net/irda/wrapper.h>
+
+extern int irtty_init(void);
+extern int nsc_ircc_init(void);
+extern int ircc_init(void);
+extern int toshoboe_init(void);
+extern int litelink_init(void);
+extern int w83977af_init(void);
+extern int esi_init(void);
+extern int tekram_init(void);
+extern int actisys_init(void);
+extern int girbil_init(void);
+extern int sa1100_irda_init(void);
+extern int ep7211_ir_init(void);
+extern int mcp2120_init(void);
+
+static void __irda_task_delete(struct irda_task *task);
+
+static hashbin_t *dongles = NULL;
+static hashbin_t *tasks = NULL;
+
+const char *infrared_mode[] = {
+ "IRDA_IRLAP",
+ "IRDA_RAW",
+ "SHARP_ASK",
+ "TV_REMOTE",
+};
+
+#ifdef CONFIG_IRDA_DEBUG
+static const char *task_state[] = {
+ "IRDA_TASK_INIT",
+ "IRDA_TASK_DONE",
+ "IRDA_TASK_WAIT",
+ "IRDA_TASK_WAIT1",
+ "IRDA_TASK_WAIT2",
+ "IRDA_TASK_WAIT3",
+ "IRDA_TASK_CHILD_INIT",
+ "IRDA_TASK_CHILD_WAIT",
+ "IRDA_TASK_CHILD_DONE",
+};
+#endif /* CONFIG_IRDA_DEBUG */
+
+static void irda_task_timer_expired(void *data);
+
+#ifdef CONFIG_PROC_FS
+int irda_device_proc_read(char *buf, char **start, off_t offset, int len,
+ int unused);
+
+#endif /* CONFIG_PROC_FS */
+
+int __init irda_device_init( void)
+{
+ dongles = hashbin_new(HB_GLOBAL);
+ if (dongles == NULL) {
+ printk(KERN_WARNING
+ "IrDA: Can't allocate dongles hashbin!\n");
+ return -ENOMEM;
+ }
+
+ tasks = hashbin_new(HB_GLOBAL);
+ if (tasks == NULL) {
+ printk(KERN_WARNING
+ "IrDA: Can't allocate tasks hashbin!\n");
+ return -ENOMEM;
+ }
+
+ /*
+ * Call the init function of the device drivers that has not been
+ * compiled as a module
+ * Note : non-modular IrDA is not supported in 2.4.X, so don't
+ * waste too much time fixing this code. If you require it, please
+ * upgrade to the IrDA stack in 2.5.X. Jean II
+ */
+#ifdef CONFIG_IRTTY_SIR
+ irtty_init();
+#endif
+#ifdef CONFIG_WINBOND_FIR
+ w83977af_init();
+#endif
+#ifdef CONFIG_SA1100_FIR
+ sa1100_irda_init();
+#endif
+#ifdef CONFIG_NSC_FIR
+ nsc_ircc_init();
+#endif
+#ifdef CONFIG_TOSHIBA_OLD
+ toshoboe_init();
+#endif
+#ifdef CONFIG_SMC_IRCC_FIR
+ ircc_init();
+#endif
+#ifdef CONFIG_ESI_DONGLE
+ esi_init();
+#endif
+#ifdef CONFIG_TEKRAM_DONGLE
+ tekram_init();
+#endif
+#ifdef CONFIG_ACTISYS_DONGLE
+ actisys_init();
+#endif
+#ifdef CONFIG_GIRBIL_DONGLE
+ girbil_init();
+#endif
+#ifdef CONFIG_LITELINK_DONGLE
+ litelink_init();
+#endif
+#ifdef CONFIG_OLD_BELKIN
+ old_belkin_init();
+#endif
+#ifdef CONFIG_EP7211_IR
+ ep7211_ir_init();
+#endif
+#ifdef CONFIG_EP93XX_SIR
+ ep93xx_sir_init();
+#endif
+#ifdef CONFIG_EP93XX_IRDA
+ ep93xx_irda_init();
+#endif
+#ifdef CONFIG_MCP2120_DONGLE
+ mcp2120_init();
+#endif
+ return 0;
+}
+
+void irda_device_cleanup(void)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ hashbin_delete(tasks, (FREE_FUNC) __irda_task_delete);
+ hashbin_delete(dongles, NULL);
+}
+
+/*
+ * Function irda_device_set_media_busy (self, status)
+ *
+ * Called when we have detected that another station is transmiting
+ * in contention mode.
+ */
+void irda_device_set_media_busy(struct net_device *dev, int status)
+{
+ struct irlap_cb *self;
+
+ IRDA_DEBUG(4, "%s(%s)\n", __FUNCTION__, status ? "TRUE" : "FALSE");
+
+ self = (struct irlap_cb *) dev->atalk_ptr;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ if (status) {
+ self->media_busy = TRUE;
+ if (status == SMALL)
+ irlap_start_mbusy_timer(self, SMALLBUSY_TIMEOUT);
+ else
+ irlap_start_mbusy_timer(self, MEDIABUSY_TIMEOUT);
+ IRDA_DEBUG( 4, "Media busy!\n");
+ } else {
+ self->media_busy = FALSE;
+ irlap_stop_mbusy_timer(self);
+ }
+}
+
+int irda_device_set_dtr_rts(struct net_device *dev, int dtr, int rts)
+{
+ struct if_irda_req req;
+ int ret;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ if (!dev->do_ioctl) {
+ ERROR("%s(), do_ioctl not impl. by "
+ "device driver\n", __FUNCTION__);
+ return -1;
+ }
+
+ req.ifr_dtr = dtr;
+ req.ifr_rts = rts;
+
+ ret = dev->do_ioctl(dev, (struct ifreq *) &req, SIOCSDTRRTS);
+
+ return ret;
+}
+
+int irda_device_change_speed(struct net_device *dev, __u32 speed)
+{
+ struct if_irda_req req;
+ int ret;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ if (!dev->do_ioctl) {
+ ERROR("%s(), do_ioctl not impl. by "
+ "device driver\n", __FUNCTION__);
+ return -1;
+ }
+
+ req.ifr_baudrate = speed;
+
+ ret = dev->do_ioctl(dev, (struct ifreq *) &req, SIOCSBANDWIDTH);
+
+ return ret;
+}
+
+/*
+ * Function irda_device_is_receiving (dev)
+ *
+ * Check if the device driver is currently receiving data
+ *
+ */
+int irda_device_is_receiving(struct net_device *dev)
+{
+ struct if_irda_req req;
+ int ret;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ if (!dev->do_ioctl) {
+ ERROR("%s(), do_ioctl not impl. by "
+ "device driver\n", __FUNCTION__);
+ return -1;
+ }
+
+ ret = dev->do_ioctl(dev, (struct ifreq *) &req, SIOCGRECEIVING);
+ if (ret < 0)
+ return ret;
+
+ return req.ifr_receiving;
+}
+
+void irda_task_next_state(struct irda_task *task, IRDA_TASK_STATE state)
+{
+ IRDA_DEBUG(2, "%s(), state = %s\n", __FUNCTION__, task_state[state]);
+
+ task->state = state;
+}
+
+static void __irda_task_delete(struct irda_task *task)
+{
+ del_timer(&task->timer);
+
+ kfree(task);
+}
+
+void irda_task_delete(struct irda_task *task)
+{
+ /* Unregister task */
+ hashbin_remove(tasks, (int) task, NULL);
+
+ __irda_task_delete(task);
+}
+
+/*
+ * Function irda_task_kick (task)
+ *
+ * Tries to execute a task possible multiple times until the task is either
+ * finished, or askes for a timeout. When a task is finished, we do post
+ * processing, and notify the parent task, that is waiting for this task
+ * to complete.
+ */
+int irda_task_kick(struct irda_task *task)
+{
+ int finished = TRUE;
+ int count = 0;
+ int timeout;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(task != NULL, return -1;);
+ ASSERT(task->magic == IRDA_TASK_MAGIC, return -1;);
+
+ /* Execute task until it's finished, or askes for a timeout */
+ do {
+ timeout = task->function(task);
+ if (count++ > 100) {
+ ERROR("%s(), error in task handler!\n", __FUNCTION__);
+ irda_task_delete(task);
+ return TRUE;
+ }
+ } while ((timeout == 0) && (task->state != IRDA_TASK_DONE));
+
+ if (timeout < 0) {
+ ERROR("%s(), Error executing task!\n", __FUNCTION__);
+ irda_task_delete(task);
+ return TRUE;
+ }
+
+ /* Check if we are finished */
+ if (task->state == IRDA_TASK_DONE) {
+ del_timer(&task->timer);
+
+ /* Do post processing */
+ if (task->finished)
+ task->finished(task);
+
+ /* Notify parent */
+ if (task->parent) {
+ /* Check if parent is waiting for us to complete */
+ if (task->parent->state == IRDA_TASK_CHILD_WAIT) {
+ task->parent->state = IRDA_TASK_CHILD_DONE;
+
+ /* Stop timer now that we are here */
+ del_timer(&task->parent->timer);
+
+ /* Kick parent task */
+ irda_task_kick(task->parent);
+ }
+ }
+ irda_task_delete(task);
+ } else if (timeout > 0) {
+ irda_start_timer(&task->timer, timeout, (void *) task,
+ irda_task_timer_expired);
+ finished = FALSE;
+ } else {
+ IRDA_DEBUG(0, "%s(), not finished, and no timeout!\n", __FUNCTION__);
+ finished = FALSE;
+ }
+
+ return finished;
+}
+
+/*
+ * Function irda_task_execute (instance, function, finished)
+ *
+ * This function registers and tries to execute tasks that may take some
+ * time to complete. We do it this hairy way since we may have been
+ * called from interrupt context, so it's not possible to use
+ * schedule_timeout()
+ * Two important notes :
+ * o Make sure you irda_task_delete(task); in case you delete the
+ * calling instance.
+ * o No real need to lock when calling this function, but you may
+ * want to lock within the task handler.
+ * Jean II
+ */
+struct irda_task *irda_task_execute(void *instance,
+ IRDA_TASK_CALLBACK function,
+ IRDA_TASK_CALLBACK finished,
+ struct irda_task *parent, void *param)
+{
+ struct irda_task *task;
+ int ret;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ task = kmalloc(sizeof(struct irda_task), GFP_ATOMIC);
+ if (!task)
+ return NULL;
+
+ task->state = IRDA_TASK_INIT;
+ task->instance = instance;
+ task->function = function;
+ task->finished = finished;
+ task->parent = parent;
+ task->param = param;
+ task->magic = IRDA_TASK_MAGIC;
+
+ init_timer(&task->timer);
+
+ /* Register task */
+ hashbin_insert(tasks, (irda_queue_t *) task, (int) task, NULL);
+
+ /* No time to waste, so lets get going! */
+ ret = irda_task_kick(task);
+ if (ret)
+ return NULL;
+ else
+ return task;
+}
+
+/*
+ * Function irda_task_timer_expired (data)
+ *
+ * Task time has expired. We now try to execute task (again), and restart
+ * the timer if the task has not finished yet
+ */
+static void irda_task_timer_expired(void *data)
+{
+ struct irda_task *task;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ task = (struct irda_task *) data;
+
+ irda_task_kick(task);
+}
+
+/*
+ * Function irda_device_setup (dev)
+ *
+ * This function should be used by low level device drivers in a similar way
+ * as ether_setup() is used by normal network device drivers
+ */
+int irda_device_setup(struct net_device *dev)
+{
+ ASSERT(dev != NULL, return -1;);
+
+ dev->hard_header_len = 0;
+ dev->addr_len = 0;
+
+ dev->features |= NETIF_F_DYNALLOC;
+ /* dev->destructor = irda_device_destructor; */
+
+ dev->type = ARPHRD_IRDA;
+ dev->tx_queue_len = 8; /* Window size + 1 s-frame */
+
+ memset(dev->broadcast, 0xff, 4);
+
+ dev->mtu = 2048;
+ dev->flags = IFF_NOARP;
+ return 0;
+}
+
+/*
+ * Function irda_device_txqueue_empty (dev)
+ *
+ * Check if there is still some frames in the transmit queue for this
+ * device. Maybe we should use: q->q.qlen == 0.
+ *
+ */
+int irda_device_txqueue_empty(struct net_device *dev)
+{
+ if (skb_queue_len(&dev->qdisc->q))
+ return FALSE;
+
+ return TRUE;
+}
+
+/*
+ * Function irda_device_init_dongle (self, type, qos)
+ *
+ * Initialize attached dongle.
+ *
+ * Important : request_module require us to call this function with
+ * a process context and irq enabled. - Jean II
+ */
+dongle_t *irda_device_dongle_init(struct net_device *dev, int type)
+{
+ struct dongle_reg *reg;
+ dongle_t *dongle;
+
+ ASSERT(dev != NULL, return NULL;);
+
+#ifdef CONFIG_KMOD
+ {
+ char modname[32];
+ ASSERT(!in_interrupt(), return NULL;);
+ /* Try to load the module needed */
+ sprintf(modname, "irda-dongle-%d", type);
+ request_module(modname);
+ }
+#endif /* CONFIG_KMOD */
+
+ if (!(reg = hashbin_find(dongles, type, NULL))) {
+ ERROR("IrDA: Unable to find requested dongle\n");
+ return NULL;
+ }
+
+ /* Allocate dongle info for this instance */
+ dongle = kmalloc(sizeof(dongle_t), GFP_KERNEL);
+ if (!dongle)
+ return NULL;
+
+ memset(dongle, 0, sizeof(dongle_t));
+
+ /* Bind the registration info to this particular instance */
+ dongle->issue = reg;
+ dongle->dev = dev;
+
+ return dongle;
+}
+
+/*
+ * Function irda_device_dongle_cleanup (dongle)
+ *
+ *
+ *
+ */
+int irda_device_dongle_cleanup(dongle_t *dongle)
+{
+ ASSERT(dongle != NULL, return -1;);
+
+ dongle->issue->close(dongle);
+
+ kfree(dongle);
+
+ return 0;
+}
+
+/*
+ * Function irda_device_register_dongle (dongle)
+ *
+ *
+ *
+ */
+int irda_device_register_dongle(struct dongle_reg *new)
+{
+ /* Check if this dongle has been registred before */
+ if (hashbin_find(dongles, new->type, NULL)) {
+ MESSAGE("%s(), Dongle already registered\n", __FUNCTION__);
+ return 0;
+ }
+
+ /* Insert IrDA dongle into hashbin */
+ hashbin_insert(dongles, (irda_queue_t *) new, new->type, NULL);
+
+ return 0;
+}
+
+/*
+ * Function irda_device_unregister_dongle (dongle)
+ *
+ * Unregister dongle, and remove dongle from list of registred dongles
+ *
+ */
+void irda_device_unregister_dongle(struct dongle_reg *dongle)
+{
+ struct dongle *node;
+
+ node = hashbin_remove(dongles, dongle->type, NULL);
+ if (!node) {
+ ERROR("%s(), dongle not found!\n", __FUNCTION__);
+ return;
+ }
+}
+
+/*
+ * Function irda_device_set_mode (self, mode)
+ *
+ * Set the Infrared device driver into mode where it sends and receives
+ * data without using IrLAP framing. Check out the particular device
+ * driver to find out which modes it support.
+ */
+int irda_device_set_mode(struct net_device* dev, int mode)
+{
+ struct if_irda_req req;
+ int ret;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ if (!dev->do_ioctl) {
+ ERROR("%s(), set_raw_mode not impl. by "
+ "device driver\n", __FUNCTION__);
+ return -1;
+ }
+
+ req.ifr_mode = mode;
+
+ ret = dev->do_ioctl(dev, (struct ifreq *) &req, SIOCSMODE);
+
+ return ret;
+}
+
+/*
+ * Function setup_dma (idev, buffer, count, mode)
+ *
+ * Setup the DMA channel. Commonly used by ISA FIR drivers
+ *
+ */
+void setup_dma(int channel, char *buffer, int count, int mode)
+{
+ unsigned long flags;
+
+ flags = claim_dma_lock();
+
+ disable_dma(channel);
+ clear_dma_ff(channel);
+ set_dma_mode(channel, mode);
+ set_dma_addr(channel, virt_to_bus(buffer));
+ set_dma_count(channel, count);
+ enable_dma(channel);
+
+ release_dma_lock(flags);
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/iriap.c b/uClinux-2.4.31-uc0/net/irda/iriap.c
new file mode 100644
index 0000000..1fe2794
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/iriap.c
@@ -0,0 +1,1036 @@
+/*********************************************************************
+ *
+ * Filename: iriap.c
+ * Version: 0.8
+ * Description: Information Access Protocol (IAP)
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Thu Aug 21 00:02:07 1997
+ * Modified at: Sat Dec 25 16:42:42 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1998-1999 Dag Brattli <dagb@cs.uit.no>,
+ * All Rights Reserved.
+ * Copyright (c) 2000-2001 Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/skbuff.h>
+#include <linux/string.h>
+#include <linux/init.h>
+
+#include <asm/byteorder.h>
+#include <asm/unaligned.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irttp.h>
+#include <net/irda/irmod.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/irias_object.h>
+#include <net/irda/iriap_event.h>
+#include <net/irda/iriap.h>
+
+#ifdef CONFIG_IRDA_DEBUG
+/* FIXME: This one should go in irlmp.c */
+static const char *ias_charset_types[] = {
+ "CS_ASCII",
+ "CS_ISO_8859_1",
+ "CS_ISO_8859_2",
+ "CS_ISO_8859_3",
+ "CS_ISO_8859_4",
+ "CS_ISO_8859_5",
+ "CS_ISO_8859_6",
+ "CS_ISO_8859_7",
+ "CS_ISO_8859_8",
+ "CS_ISO_8859_9",
+ "CS_UNICODE"
+};
+#endif /* CONFIG_IRDA_DEBUG */
+
+static hashbin_t *iriap = NULL;
+static __u32 service_handle;
+
+extern char *lmp_reasons[];
+
+static void __iriap_close(struct iriap_cb *self);
+static int iriap_register_lsap(struct iriap_cb *self, __u8 slsap_sel, int mode);
+static void iriap_disconnect_indication(void *instance, void *sap,
+ LM_REASON reason, struct sk_buff *skb);
+static void iriap_connect_indication(void *instance, void *sap,
+ struct qos_info *qos, __u32 max_sdu_size,
+ __u8 max_header_size,
+ struct sk_buff *skb);
+static void iriap_connect_confirm(void *instance, void *sap,
+ struct qos_info *qos,
+ __u32 max_sdu_size, __u8 max_header_size,
+ struct sk_buff *skb);
+static int iriap_data_indication(void *instance, void *sap,
+ struct sk_buff *skb);
+
+/*
+ * Function iriap_init (void)
+ *
+ * Initializes the IrIAP layer, called by the module initialization code
+ * in irmod.c
+ */
+int __init iriap_init(void)
+{
+ struct ias_object *obj;
+ struct iriap_cb *server;
+ __u8 oct_seq[6];
+ __u16 hints;
+
+ /* Allocate master array */
+ iriap = hashbin_new(HB_LOCAL);
+ if (!iriap)
+ return -ENOMEM;
+
+ objects = hashbin_new(HB_LOCAL);
+ if (!objects) {
+ WARNING("%s(), Can't allocate objects hashbin!\n", __FUNCTION__);
+ return -ENOMEM;
+ }
+
+ /*
+ * Register some default services for IrLMP
+ */
+ hints = irlmp_service_to_hint(S_COMPUTER);
+ service_handle = irlmp_register_service(hints);
+
+ /* Register the Device object with LM-IAS */
+ obj = irias_new_object("Device", IAS_DEVICE_ID);
+ irias_add_string_attrib(obj, "DeviceName", "Linux", IAS_KERNEL_ATTR);
+
+ oct_seq[0] = 0x01; /* Version 1 */
+ oct_seq[1] = 0x00; /* IAS support bits */
+ oct_seq[2] = 0x00; /* LM-MUX support bits */
+#ifdef CONFIG_IRDA_ULTRA
+ oct_seq[2] |= 0x04; /* Connectionless Data support */
+#endif
+ irias_add_octseq_attrib(obj, "IrLMPSupport", oct_seq, 3,
+ IAS_KERNEL_ATTR);
+ irias_insert_object(obj);
+
+ /*
+ * Register server support with IrLMP so we can accept incoming
+ * connections
+ */
+ server = iriap_open(LSAP_IAS, IAS_SERVER, NULL, NULL);
+ if (!server) {
+ IRDA_DEBUG(0, "%s(), unable to open server\n", __FUNCTION__);
+ return -1;
+ }
+ iriap_register_lsap(server, LSAP_IAS, IAS_SERVER);
+
+ return 0;
+}
+
+/*
+ * Function iriap_cleanup (void)
+ *
+ * Initializes the IrIAP layer, called by the module cleanup code in
+ * irmod.c
+ */
+void iriap_cleanup(void)
+{
+ irlmp_unregister_service(service_handle);
+
+ hashbin_delete(iriap, (FREE_FUNC) __iriap_close);
+ hashbin_delete(objects, (FREE_FUNC) __irias_delete_object);
+}
+
+/*
+ * Function iriap_open (void)
+ *
+ * Opens an instance of the IrIAP layer, and registers with IrLMP
+ */
+struct iriap_cb *iriap_open(__u8 slsap_sel, int mode, void *priv,
+ CONFIRM_CALLBACK callback)
+{
+ struct iriap_cb *self;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ self = kmalloc(sizeof(struct iriap_cb), GFP_ATOMIC);
+ if (!self) {
+ WARNING("%s(), Unable to kmalloc!\n", __FUNCTION__);
+ return NULL;
+ }
+
+ /*
+ * Initialize instance
+ */
+ memset(self, 0, sizeof(struct iriap_cb));
+
+ self->magic = IAS_MAGIC;
+ self->mode = mode;
+ if (mode == IAS_CLIENT)
+ iriap_register_lsap(self, slsap_sel, mode);
+
+ self->confirm = callback;
+ self->priv = priv;
+
+ init_timer(&self->watchdog_timer);
+
+ hashbin_insert(iriap, (irda_queue_t *) self, (int) self, NULL);
+
+ /* Initialize state machines */
+ iriap_next_client_state(self, S_DISCONNECT);
+ iriap_next_call_state(self, S_MAKE_CALL);
+ iriap_next_server_state(self, R_DISCONNECT);
+ iriap_next_r_connect_state(self, R_WAITING);
+
+ return self;
+}
+
+/*
+ * Function __iriap_close (self)
+ *
+ * Removes (deallocates) the IrIAP instance
+ *
+ */
+static void __iriap_close(struct iriap_cb *self)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ del_timer(&self->watchdog_timer);
+
+ if (self->skb)
+ dev_kfree_skb(self->skb);
+
+ self->magic = 0;
+
+ kfree(self);
+}
+
+/*
+ * Function iriap_close (void)
+ *
+ * Closes IrIAP and deregisters with IrLMP
+ */
+void iriap_close(struct iriap_cb *self)
+{
+ struct iriap_cb *entry;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ if (self->lsap) {
+ irlmp_close_lsap(self->lsap);
+ self->lsap = NULL;
+ }
+
+ entry = (struct iriap_cb *) hashbin_remove(iriap, (int) self, NULL);
+ ASSERT(entry == self, return;);
+
+ __iriap_close(self);
+}
+
+static int iriap_register_lsap(struct iriap_cb *self, __u8 slsap_sel, int mode)
+{
+ notify_t notify;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ irda_notify_init(&notify);
+ notify.connect_confirm = iriap_connect_confirm;
+ notify.connect_indication = iriap_connect_indication;
+ notify.disconnect_indication = iriap_disconnect_indication;
+ notify.data_indication = iriap_data_indication;
+ notify.instance = self;
+ if (mode == IAS_CLIENT)
+ strcpy(notify.name, "IrIAS cli");
+ else
+ strcpy(notify.name, "IrIAS srv");
+
+ self->lsap = irlmp_open_lsap(slsap_sel, &notify, 0);
+ if (self->lsap == NULL) {
+ ERROR("%s(), Unable to allocated LSAP!\n", __FUNCTION__);
+ return -1;
+ }
+ self->slsap_sel = self->lsap->slsap_sel;
+
+ return 0;
+}
+
+/*
+ * Function iriap_disconnect_indication (handle, reason)
+ *
+ * Got disconnect, so clean up everything assosiated with this connection
+ *
+ */
+static void iriap_disconnect_indication(void *instance, void *sap,
+ LM_REASON reason,
+ struct sk_buff *userdata)
+{
+ struct iriap_cb *self;
+
+ IRDA_DEBUG(4, "%s(), reason=%s\n", __FUNCTION__, lmp_reasons[reason]);
+
+ self = (struct iriap_cb *) instance;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ ASSERT(iriap != NULL, return;);
+
+ del_timer(&self->watchdog_timer);
+
+ if (self->mode == IAS_CLIENT) {
+ IRDA_DEBUG(4, "%s(), disconnect as client\n", __FUNCTION__);
+
+
+ iriap_do_client_event(self, IAP_LM_DISCONNECT_INDICATION,
+ NULL);
+ /*
+ * Inform service user that the request failed by sending
+ * it a NULL value. Warning, the client might close us, so
+ * remember no to use self anymore after calling confirm
+ */
+ if (self->confirm)
+ self->confirm(IAS_DISCONNECT, 0, NULL, self->priv);
+ } else {
+ IRDA_DEBUG(4, "%s(), disconnect as server\n", __FUNCTION__);
+ iriap_do_server_event(self, IAP_LM_DISCONNECT_INDICATION,
+ NULL);
+ iriap_close(self);
+ }
+
+ if (userdata)
+ dev_kfree_skb(userdata);
+}
+
+/*
+ * Function iriap_disconnect_request (handle)
+ *
+ *
+ *
+ */
+void iriap_disconnect_request(struct iriap_cb *self)
+{
+ struct sk_buff *skb;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ skb = dev_alloc_skb(64);
+ if (skb == NULL) {
+ IRDA_DEBUG(0, "%s(), Could not allocate an sk_buff of length %d\n",
+ __FUNCTION__, 64);
+ return;
+ }
+
+ /*
+ * Reserve space for MUX control and LAP header
+ */
+ skb_reserve(skb, LMP_MAX_HEADER);
+
+ irlmp_disconnect_request(self->lsap, skb);
+}
+
+void iriap_getinfobasedetails_request(void)
+{
+ IRDA_DEBUG(0, "%s(), Not implemented!\n", __FUNCTION__);
+}
+
+void iriap_getinfobasedetails_confirm(void)
+{
+ IRDA_DEBUG(0, "%s(), Not implemented!\n", __FUNCTION__);
+}
+
+void iriap_getobjects_request(void)
+{
+ IRDA_DEBUG(0, "%s(), Not implemented!\n", __FUNCTION__);
+}
+
+void iriap_getobjects_confirm(void)
+{
+ IRDA_DEBUG(0, "%s(), Not implemented!\n", __FUNCTION__);
+}
+
+void iriap_getvalue(void)
+{
+ IRDA_DEBUG(0, "%s(), Not implemented!\n", __FUNCTION__);
+}
+
+/*
+ * Function iriap_getvaluebyclass (addr, name, attr)
+ *
+ * Retreive all values from attribute in all objects with given class
+ * name
+ */
+int iriap_getvaluebyclass_request(struct iriap_cb *self,
+ __u32 saddr, __u32 daddr,
+ char *name, char *attr)
+{
+ struct sk_buff *skb;
+ int name_len, attr_len, skb_len;
+ __u8 *frame;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IAS_MAGIC, return -1;);
+
+ /* Client must supply the destination device address */
+ if (!daddr)
+ return -1;
+
+ self->daddr = daddr;
+ self->saddr = saddr;
+
+ /*
+ * Save operation, so we know what the later indication is about
+ */
+ self->operation = GET_VALUE_BY_CLASS;
+
+ /* Give ourselves 10 secs to finish this operation */
+ iriap_start_watchdog_timer(self, 10*HZ);
+
+ name_len = strlen(name); /* Up to IAS_MAX_CLASSNAME = 60 */
+ attr_len = strlen(attr); /* Up to IAS_MAX_ATTRIBNAME = 60 */
+
+ skb_len = self->max_header_size+2+name_len+1+attr_len+4;
+ skb = dev_alloc_skb(skb_len);
+ if (!skb)
+ return -ENOMEM;
+
+ /* Reserve space for MUX and LAP header */
+ skb_reserve(skb, self->max_header_size);
+ skb_put(skb, 3+name_len+attr_len);
+ frame = skb->data;
+
+ /* Build frame */
+ frame[0] = IAP_LST | GET_VALUE_BY_CLASS;
+ frame[1] = name_len; /* Insert length of name */
+ memcpy(frame+2, name, name_len); /* Insert name */
+ frame[2+name_len] = attr_len; /* Insert length of attr */
+ memcpy(frame+3+name_len, attr, attr_len); /* Insert attr */
+
+ iriap_do_client_event(self, IAP_CALL_REQUEST_GVBC, skb);
+
+ return 0;
+}
+
+/*
+ * Function iriap_getvaluebyclass_confirm (self, skb)
+ *
+ * Got result from GetValueByClass command. Parse it and return result
+ * to service user.
+ *
+ */
+void iriap_getvaluebyclass_confirm(struct iriap_cb *self, struct sk_buff *skb)
+{
+ struct ias_value *value;
+ int charset;
+ __u32 value_len;
+ __u32 tmp_cpu32;
+ __u16 obj_id;
+ __u16 len;
+ __u8 type;
+ __u8 *fp;
+ int n;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+
+ /* Initialize variables */
+ fp = skb->data;
+ n = 2;
+
+ /* Get length, MSB first */
+ len = be16_to_cpu(get_unaligned((__u16 *)(fp+n))); n += 2;
+
+ IRDA_DEBUG(4, "%s(), len=%d\n", __FUNCTION__, len);
+
+ /* Get object ID, MSB first */
+ obj_id = be16_to_cpu(get_unaligned((__u16 *)(fp+n))); n += 2;
+
+ type = fp[n++];
+ IRDA_DEBUG(4, "%s(), Value type = %d\n", __FUNCTION__, type);
+
+ switch (type) {
+ case IAS_INTEGER:
+ memcpy(&tmp_cpu32, fp+n, 4); n += 4;
+ be32_to_cpus(&tmp_cpu32);
+ value = irias_new_integer_value(tmp_cpu32);
+
+ /* Legal values restricted to 0x01-0x6f, page 15 irttp */
+ IRDA_DEBUG(4, "%s(), lsap=%d\n", __FUNCTION__, value->t.integer);
+ break;
+ case IAS_STRING:
+ charset = fp[n++];
+
+ switch (charset) {
+ case CS_ASCII:
+ break;
+/* case CS_ISO_8859_1: */
+/* case CS_ISO_8859_2: */
+/* case CS_ISO_8859_3: */
+/* case CS_ISO_8859_4: */
+/* case CS_ISO_8859_5: */
+/* case CS_ISO_8859_6: */
+/* case CS_ISO_8859_7: */
+/* case CS_ISO_8859_8: */
+/* case CS_ISO_8859_9: */
+/* case CS_UNICODE: */
+ default:
+ IRDA_DEBUG(0, "%s(), charset %s, not supported\n",
+ __FUNCTION__, ias_charset_types[charset]);
+
+ /* Aborting, close connection! */
+ iriap_disconnect_request(self);
+ dev_kfree_skb(skb);
+ return;
+ /* break; */
+ }
+ value_len = fp[n++];
+ IRDA_DEBUG(4, "%s(), strlen=%d\n", __FUNCTION__, value_len);
+
+ /* Make sure the string is null-terminated */
+ fp[n+value_len] = 0x00;
+ IRDA_DEBUG(4, "Got string %s\n", fp+n);
+
+ /* Will truncate to IAS_MAX_STRING bytes */
+ value = irias_new_string_value(fp+n);
+ break;
+ case IAS_OCT_SEQ:
+ value_len = be16_to_cpu(get_unaligned((__u16 *)(fp+n)));
+ n += 2;
+
+ /* Will truncate to IAS_MAX_OCTET_STRING bytes */
+ value = irias_new_octseq_value(fp+n, value_len);
+ break;
+ default:
+ value = irias_new_missing_value();
+ break;
+ }
+
+ /* Finished, close connection! */
+ iriap_disconnect_request(self);
+
+ /* Warning, the client might close us, so remember no to use self
+ * anymore after calling confirm
+ */
+ if (self->confirm)
+ self->confirm(IAS_SUCCESS, obj_id, value, self->priv);
+ else {
+ IRDA_DEBUG(0, "%s(), missing handler!\n", __FUNCTION__);
+ irias_delete_value(value);
+ }
+ dev_kfree_skb(skb);
+}
+
+/*
+ * Function iriap_getvaluebyclass_response ()
+ *
+ * Send answer back to remote LM-IAS
+ *
+ */
+void iriap_getvaluebyclass_response(struct iriap_cb *self, __u16 obj_id,
+ __u8 ret_code, struct ias_value *value)
+{
+ struct sk_buff *skb;
+ int n;
+ __u32 tmp_be32, tmp_be16;
+ __u8 *fp;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+ ASSERT(value != NULL, return;);
+ ASSERT(value->len <= 1024, return;);
+
+ /* Initialize variables */
+ n = 0;
+
+ /*
+ * We must adjust the size of the response after the length of the
+ * value. We add 32 bytes because of the 6 bytes for the frame and
+ * max 5 bytes for the value coding.
+ */
+ skb = dev_alloc_skb(value->len + self->max_header_size + 32);
+ if (!skb)
+ return;
+
+ /* Reserve space for MUX and LAP header */
+ skb_reserve(skb, self->max_header_size);
+ skb_put(skb, 6);
+
+ fp = skb->data;
+
+ /* Build frame */
+ fp[n++] = GET_VALUE_BY_CLASS | IAP_LST;
+ fp[n++] = ret_code;
+
+ /* Insert list length (MSB first) */
+ tmp_be16 = __constant_htons(0x0001);
+ memcpy(fp+n, &tmp_be16, 2); n += 2;
+
+ /* Insert object identifier ( MSB first) */
+ tmp_be16 = cpu_to_be16(obj_id);
+ memcpy(fp+n, &tmp_be16, 2); n += 2;
+
+ switch (value->type) {
+ case IAS_STRING:
+ skb_put(skb, 3 + value->len);
+ fp[n++] = value->type;
+ fp[n++] = 0; /* ASCII */
+ fp[n++] = (__u8) value->len;
+ memcpy(fp+n, value->t.string, value->len); n+=value->len;
+ break;
+ case IAS_INTEGER:
+ skb_put(skb, 5);
+ fp[n++] = value->type;
+
+ tmp_be32 = cpu_to_be32(value->t.integer);
+ memcpy(fp+n, &tmp_be32, 4); n += 4;
+ break;
+ case IAS_OCT_SEQ:
+ skb_put(skb, 3 + value->len);
+ fp[n++] = value->type;
+
+ tmp_be16 = cpu_to_be16(value->len);
+ memcpy(fp+n, &tmp_be16, 2); n += 2;
+ memcpy(fp+n, value->t.oct_seq, value->len); n+=value->len;
+ break;
+ case IAS_MISSING:
+ IRDA_DEBUG( 3, "%s: sending IAS_MISSING\n", __FUNCTION__);
+ skb_put(skb, 1);
+ fp[n++] = value->type;
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), type not implemented!\n", __FUNCTION__);
+ break;
+ }
+ iriap_do_r_connect_event(self, IAP_CALL_RESPONSE, skb);
+}
+
+/*
+ * Function iriap_getvaluebyclass_indication (self, skb)
+ *
+ * getvaluebyclass is requested from peer LM-IAS
+ *
+ */
+void iriap_getvaluebyclass_indication(struct iriap_cb *self,
+ struct sk_buff *skb)
+{
+ struct ias_object *obj;
+ struct ias_attrib *attrib;
+ int name_len;
+ int attr_len;
+ char name[IAS_MAX_CLASSNAME + 1]; /* 60 bytes */
+ char attr[IAS_MAX_ATTRIBNAME + 1]; /* 60 bytes */
+ __u8 *fp;
+ int n;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+
+ fp = skb->data;
+ n = 1;
+
+ name_len = fp[n++];
+ memcpy(name, fp+n, name_len); n+=name_len;
+ name[name_len] = '\0';
+
+ attr_len = fp[n++];
+ memcpy(attr, fp+n, attr_len); n+=attr_len;
+ attr[attr_len] = '\0';
+
+ /* We do not need the buffer anymore */
+ dev_kfree_skb(skb);
+
+ IRDA_DEBUG(4, "LM-IAS: Looking up %s: %s\n", name, attr);
+ obj = irias_find_object(name);
+
+ if (obj == NULL) {
+ IRDA_DEBUG(2, "LM-IAS: Object %s not found\n", name);
+ iriap_getvaluebyclass_response(self, 0x1235, IAS_CLASS_UNKNOWN,
+ &missing);
+ return;
+ }
+ IRDA_DEBUG(4, "LM-IAS: found %s, id=%d\n", obj->name, obj->id);
+
+ attrib = irias_find_attrib(obj, attr);
+ if (attrib == NULL) {
+ IRDA_DEBUG(2, "LM-IAS: Attribute %s not found\n", attr);
+ iriap_getvaluebyclass_response(self, obj->id,
+ IAS_ATTRIB_UNKNOWN, &missing);
+ return;
+ }
+
+ /* We have a match; send the value. */
+ iriap_getvaluebyclass_response(self, obj->id, IAS_SUCCESS,
+ attrib->value);
+
+ return;
+}
+
+/*
+ * Function iriap_send_ack (void)
+ *
+ * Currently not used
+ *
+ */
+void iriap_send_ack(struct iriap_cb *self)
+{
+ struct sk_buff *skb;
+ __u8 *frame;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ skb = dev_alloc_skb(64);
+ if (!skb)
+ return;
+
+ /* Reserve space for MUX and LAP header */
+ skb_reserve(skb, self->max_header_size);
+ skb_put(skb, 1);
+ frame = skb->data;
+
+ /* Build frame */
+ frame[0] = IAP_LST | IAP_ACK | self->operation;
+
+ irlmp_data_request(self->lsap, skb);
+}
+
+void iriap_connect_request(struct iriap_cb *self)
+{
+ int ret;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ ret = irlmp_connect_request(self->lsap, LSAP_IAS,
+ self->saddr, self->daddr,
+ NULL, NULL);
+ if (ret < 0) {
+ IRDA_DEBUG(0, "%s(), connect failed!\n", __FUNCTION__);
+ self->confirm(IAS_DISCONNECT, 0, NULL, self->priv);
+ }
+}
+
+/*
+ * Function iriap_connect_confirm (handle, skb)
+ *
+ * LSAP connection confirmed!
+ *
+ */
+static void iriap_connect_confirm(void *instance, void *sap,
+ struct qos_info *qos, __u32 max_seg_size,
+ __u8 max_header_size,
+ struct sk_buff *userdata)
+{
+ struct iriap_cb *self;
+
+ self = (struct iriap_cb *) instance;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+ ASSERT(userdata != NULL, return;);
+
+ self->max_data_size = max_seg_size;
+ self->max_header_size = max_header_size;
+
+ del_timer(&self->watchdog_timer);
+
+ iriap_do_client_event(self, IAP_LM_CONNECT_CONFIRM, userdata);
+}
+
+/*
+ * Function iriap_connect_indication ( handle, skb)
+ *
+ * Remote LM-IAS is requesting connection
+ *
+ */
+static void iriap_connect_indication(void *instance, void *sap,
+ struct qos_info *qos, __u32 max_seg_size,
+ __u8 max_header_size,
+ struct sk_buff *userdata)
+{
+ struct iriap_cb *self, *new;
+
+ IRDA_DEBUG(1, "%s()\n", __FUNCTION__);
+
+ self = (struct iriap_cb *) instance;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ /* Start new server */
+ new = iriap_open(LSAP_IAS, IAS_SERVER, NULL, NULL);
+ if (!new) {
+ IRDA_DEBUG(0, "%s(), open failed\n", __FUNCTION__);
+ dev_kfree_skb(userdata);
+ return;
+ }
+
+ /* Now attach up the new "socket" */
+ new->lsap = irlmp_dup(self->lsap, new);
+ if (!new->lsap) {
+ IRDA_DEBUG(0, "%s(), dup failed!\n", __FUNCTION__);
+ return;
+ }
+
+ new->max_data_size = max_seg_size;
+ new->max_header_size = max_header_size;
+
+ /* Clean up the original one to keep it in listen state */
+ irlmp_listen(self->lsap);
+
+ iriap_do_server_event(new, IAP_LM_CONNECT_INDICATION, userdata);
+}
+
+/*
+ * Function iriap_data_indication (handle, skb)
+ *
+ * Receives data from connection identified by handle from IrLMP
+ *
+ */
+static int iriap_data_indication(void *instance, void *sap,
+ struct sk_buff *skb)
+{
+ struct iriap_cb *self;
+ __u8 *frame;
+ __u8 opcode;
+
+ IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+ self = (struct iriap_cb *) instance;
+
+ ASSERT(self != NULL, return 0;);
+ ASSERT(self->magic == IAS_MAGIC, return 0;);
+
+ ASSERT(skb != NULL, return 0;);
+
+ frame = skb->data;
+
+ if (self->mode == IAS_SERVER) {
+ /* Call server */
+ IRDA_DEBUG(4, "%s(), Calling server!\n", __FUNCTION__);
+ iriap_do_r_connect_event(self, IAP_RECV_F_LST, skb);
+
+ return 0;
+ }
+ opcode = frame[0];
+ if (~opcode & IAP_LST) {
+ WARNING("%s(), IrIAS multiframe commands or "
+ "results is not implemented yet!\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ return 0;
+ }
+
+ /* Check for ack frames since they don't contain any data */
+ if (opcode & IAP_ACK) {
+ IRDA_DEBUG(0, "%s() Got ack frame!\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ return 0;
+ }
+
+ opcode &= ~IAP_LST; /* Mask away LST bit */
+
+ switch (opcode) {
+ case GET_INFO_BASE:
+ IRDA_DEBUG(0, "IrLMP GetInfoBaseDetails not implemented!\n");
+ dev_kfree_skb(skb);
+ break;
+ case GET_VALUE_BY_CLASS:
+ iriap_do_call_event(self, IAP_RECV_F_LST, NULL);
+
+ switch (frame[1]) {
+ case IAS_SUCCESS:
+ iriap_getvaluebyclass_confirm(self, skb);
+ break;
+ case IAS_CLASS_UNKNOWN:
+ IRDA_DEBUG(1, "%s(), No such class!\n", __FUNCTION__);
+ /* Finished, close connection! */
+ iriap_disconnect_request(self);
+
+ /*
+ * Warning, the client might close us, so remember
+ * no to use self anymore after calling confirm
+ */
+ if (self->confirm)
+ self->confirm(IAS_CLASS_UNKNOWN, 0, NULL,
+ self->priv);
+ dev_kfree_skb(skb);
+ break;
+ case IAS_ATTRIB_UNKNOWN:
+ IRDA_DEBUG(1, "%s(), No such attribute!\n", __FUNCTION__);
+ /* Finished, close connection! */
+ iriap_disconnect_request(self);
+
+ /*
+ * Warning, the client might close us, so remember
+ * no to use self anymore after calling confirm
+ */
+ if (self->confirm)
+ self->confirm(IAS_ATTRIB_UNKNOWN, 0, NULL,
+ self->priv);
+ dev_kfree_skb(skb);
+ break;
+ }
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown op-code: %02x\n", __FUNCTION__, opcode);
+ dev_kfree_skb(skb);
+ break;
+ }
+ return 0;
+}
+
+/*
+ * Function iriap_call_indication (self, skb)
+ *
+ * Received call to server from peer LM-IAS
+ *
+ */
+void iriap_call_indication(struct iriap_cb *self, struct sk_buff *skb)
+{
+ __u8 *fp;
+ __u8 opcode;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+
+ fp = skb->data;
+
+ opcode = fp[0];
+ if (~opcode & 0x80) {
+ WARNING("%s(), IrIAS multiframe commands or results"
+ "is not implemented yet!\n", __FUNCTION__);
+ return;
+ }
+ opcode &= 0x7f; /* Mask away LST bit */
+
+ switch (opcode) {
+ case GET_INFO_BASE:
+ WARNING("%s(), GetInfoBaseDetails not implemented yet!\n",
+ __FUNCTION__);
+ break;
+ case GET_VALUE_BY_CLASS:
+ iriap_getvaluebyclass_indication(self, skb);
+ break;
+ }
+}
+
+/*
+ * Function iriap_watchdog_timer_expired (data)
+ *
+ * Query has taken to long time, so abort
+ *
+ */
+void iriap_watchdog_timer_expired(void *data)
+{
+ struct iriap_cb *self = (struct iriap_cb *) data;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ /* iriap_close(self); */
+}
+
+#ifdef CONFIG_PROC_FS
+
+static char *ias_value_types[] = {
+ "IAS_MISSING",
+ "IAS_INTEGER",
+ "IAS_OCT_SEQ",
+ "IAS_STRING"
+};
+
+int irias_proc_read(char *buf, char **start, off_t offset, int len)
+{
+ struct ias_object *obj;
+ struct ias_attrib *attrib;
+ unsigned long flags;
+
+ ASSERT( objects != NULL, return 0;);
+
+ save_flags( flags);
+ cli();
+
+ len = 0;
+
+ len += sprintf(buf+len, "LM-IAS Objects:\n");
+
+ /* List all objects */
+ obj = (struct ias_object *) hashbin_get_first(objects);
+ while ( obj != NULL) {
+ ASSERT(obj->magic == IAS_OBJECT_MAGIC, return 0;);
+
+ len += sprintf(buf+len, "name: %s, ", obj->name);
+ len += sprintf(buf+len, "id=%d", obj->id);
+ len += sprintf(buf+len, "\n");
+
+ /* List all attributes for this object */
+ attrib = (struct ias_attrib *)
+ hashbin_get_first(obj->attribs);
+ while (attrib != NULL) {
+ ASSERT(attrib->magic == IAS_ATTRIB_MAGIC, return 0;);
+
+ len += sprintf(buf+len, " - Attribute name: \"%s\", ",
+ attrib->name);
+ len += sprintf(buf+len, "value[%s]: ",
+ ias_value_types[attrib->value->type]);
+
+ switch (attrib->value->type) {
+ case IAS_INTEGER:
+ len += sprintf(buf+len, "%d\n",
+ attrib->value->t.integer);
+ break;
+ case IAS_STRING:
+ len += sprintf(buf+len, "\"%s\"\n",
+ attrib->value->t.string);
+ break;
+ case IAS_OCT_SEQ:
+ len += sprintf(buf+len, "octet sequence (%d bytes)\n", attrib->value->len);
+ break;
+ case IAS_MISSING:
+ len += sprintf(buf+len, "missing\n");
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown value type!\n", __FUNCTION__);
+ return -1;
+ }
+ len += sprintf(buf+len, "\n");
+
+ attrib = (struct ias_attrib *)
+ hashbin_get_next(obj->attribs);
+ }
+ obj = (struct ias_object *) hashbin_get_next(objects);
+ }
+ restore_flags(flags);
+
+ return len;
+}
+
+#endif /* PROC_FS */
diff --git a/uClinux-2.4.31-uc0/net/irda/iriap_event.c b/uClinux-2.4.31-uc0/net/irda/iriap_event.c
new file mode 100644
index 0000000..ec8cbb7
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/iriap_event.c
@@ -0,0 +1,514 @@
+/*********************************************************************
+ *
+ * Filename: iriap_event.c
+ * Version: 0.1
+ * Description: IAP Finite State Machine
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Thu Aug 21 00:02:07 1997
+ * Modified at: Wed Mar 1 11:28:34 2000
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1997, 1999-2000 Dag Brattli <dagb@cs.uit.no>,
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <net/irda/irda.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/iriap.h>
+#include <net/irda/iriap_event.h>
+
+static void state_s_disconnect (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+static void state_s_connecting (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+static void state_s_call (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+
+static void state_s_make_call (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+static void state_s_calling (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+static void state_s_outstanding (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+static void state_s_replying (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+static void state_s_wait_for_call(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+static void state_s_wait_active (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+
+static void state_r_disconnect (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+static void state_r_call (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+static void state_r_waiting (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+static void state_r_wait_active (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+static void state_r_receiving (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+static void state_r_execute (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+static void state_r_returning (struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb);
+
+static void (*iriap_state[])(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb) = {
+ /* Client FSM */
+ state_s_disconnect,
+ state_s_connecting,
+ state_s_call,
+
+ /* S-Call FSM */
+ state_s_make_call,
+ state_s_calling,
+ state_s_outstanding,
+ state_s_replying,
+ state_s_wait_for_call,
+ state_s_wait_active,
+
+ /* Server FSM */
+ state_r_disconnect,
+ state_r_call,
+
+ /* R-Connect FSM */
+ state_r_waiting,
+ state_r_wait_active,
+ state_r_receiving,
+ state_r_execute,
+ state_r_returning,
+};
+
+void iriap_next_client_state(struct iriap_cb *self, IRIAP_STATE state)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ self->client_state = state;
+}
+
+void iriap_next_call_state(struct iriap_cb *self, IRIAP_STATE state)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ self->call_state = state;
+}
+
+void iriap_next_server_state(struct iriap_cb *self, IRIAP_STATE state)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ self->server_state = state;
+}
+
+void iriap_next_r_connect_state(struct iriap_cb *self, IRIAP_STATE state)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ self->r_connect_state = state;
+}
+
+void iriap_do_client_event(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ (*iriap_state[ self->client_state]) (self, event, skb);
+}
+
+void iriap_do_call_event(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ (*iriap_state[ self->call_state]) (self, event, skb);
+}
+
+void iriap_do_server_event(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ (*iriap_state[ self->server_state]) (self, event, skb);
+}
+
+void iriap_do_r_connect_event(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ (*iriap_state[ self->r_connect_state]) (self, event, skb);
+}
+
+
+/*
+ * Function state_s_disconnect (event, skb)
+ *
+ * S-Disconnect, The device has no LSAP connection to a particular
+ * remote device.
+ */
+static void state_s_disconnect(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ switch (event) {
+ case IAP_CALL_REQUEST_GVBC:
+ iriap_next_client_state(self, S_CONNECTING);
+ ASSERT(self->skb == NULL, return;);
+ self->skb = skb;
+ iriap_connect_request(self);
+ break;
+ case IAP_LM_DISCONNECT_INDICATION:
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+}
+
+/*
+ * Function state_s_connecting (self, event, skb)
+ *
+ * S-Connecting
+ *
+ */
+static void state_s_connecting(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IAS_MAGIC, return;);
+
+ switch (event) {
+ case IAP_LM_CONNECT_CONFIRM:
+ /*
+ * Jump to S-Call FSM
+ */
+ iriap_do_call_event(self, IAP_CALL_REQUEST, skb);
+ /* iriap_call_request(self, 0,0,0); */
+ iriap_next_client_state(self, S_CALL);
+ break;
+ case IAP_LM_DISCONNECT_INDICATION:
+ /* Abort calls */
+ iriap_next_call_state(self, S_MAKE_CALL);
+ iriap_next_client_state(self, S_DISCONNECT);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+}
+
+/*
+ * Function state_s_call (self, event, skb)
+ *
+ * S-Call, The device can process calls to a specific remote
+ * device. Whenever the LSAP connection is disconnected, this state
+ * catches that event and clears up
+ */
+static void state_s_call(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return;);
+
+ switch (event) {
+ case IAP_LM_DISCONNECT_INDICATION:
+ /* Abort calls */
+ iriap_next_call_state(self, S_MAKE_CALL);
+ iriap_next_client_state(self, S_DISCONNECT);
+ break;
+ default:
+ IRDA_DEBUG(0, "state_s_call: Unknown event %d\n", event);
+ break;
+ }
+}
+
+/*
+ * Function state_s_make_call (event, skb)
+ *
+ * S-Make-Call
+ *
+ */
+static void state_s_make_call(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ struct sk_buff *tx_skb;
+
+ ASSERT(self != NULL, return;);
+
+ switch (event) {
+ case IAP_CALL_REQUEST:
+ tx_skb = self->skb;
+ self->skb = NULL;
+
+ irlmp_data_request(self->lsap, tx_skb);
+ iriap_next_call_state(self, S_OUTSTANDING);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+ /* Cleanup time ! */
+ if (skb)
+ dev_kfree_skb(skb);
+}
+
+/*
+ * Function state_s_calling (event, skb)
+ *
+ * S-Calling
+ *
+ */
+static void state_s_calling(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(0, "%s(), Not implemented\n", __FUNCTION__);
+}
+
+/*
+ * Function state_s_outstanding (event, skb)
+ *
+ * S-Outstanding, The device is waiting for a response to a command
+ *
+ */
+static void state_s_outstanding(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return;);
+
+ switch (event) {
+ case IAP_RECV_F_LST:
+ /*iriap_send_ack(self);*/
+ /*LM_Idle_request(idle); */
+
+ iriap_next_call_state(self, S_WAIT_FOR_CALL);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+}
+
+/*
+ * Function state_s_replying (event, skb)
+ *
+ * S-Replying, The device is collecting a multiple part response
+ */
+static void state_s_replying(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(0, "%s(), Not implemented\n", __FUNCTION__);
+}
+
+/*
+ * Function state_s_wait_for_call (event, skb)
+ *
+ * S-Wait-for-Call
+ *
+ */
+static void state_s_wait_for_call(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(0, "%s(), Not implemented\n", __FUNCTION__);
+}
+
+
+/*
+ * Function state_s_wait_active (event, skb)
+ *
+ * S-Wait-Active
+ *
+ */
+static void state_s_wait_active(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(0, "%s(), Not implemented\n", __FUNCTION__);
+}
+
+/**************************************************************************
+ *
+ * Server FSM
+ *
+ **************************************************************************/
+
+/*
+ * Function state_r_disconnect (self, event, skb)
+ *
+ * LM-IAS server is disconnected (not processing any requests!)
+ *
+ */
+static void state_r_disconnect(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ struct sk_buff *tx_skb;
+
+ switch (event) {
+ case IAP_LM_CONNECT_INDICATION:
+ tx_skb = dev_alloc_skb(64);
+ if (tx_skb == NULL) {
+ WARNING("%s(), unable to malloc!\n", __FUNCTION__);
+ return;
+ }
+
+ /* Reserve space for MUX_CONTROL and LAP header */
+ skb_reserve(tx_skb, LMP_MAX_HEADER);
+
+ irlmp_connect_response(self->lsap, tx_skb);
+ /*LM_Idle_request(idle); */
+
+ iriap_next_server_state(self, R_CALL);
+
+ /*
+ * Jump to R-Connect FSM, we skip R-Waiting since we do not
+ * care about LM_Idle_request()!
+ */
+ iriap_next_r_connect_state(self, R_RECEIVING);
+
+ if (skb)
+ dev_kfree_skb(skb);
+
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+}
+
+/*
+ * Function state_r_call (self, event, skb)
+ *
+ *
+ *
+ */
+static void state_r_call(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ switch (event) {
+ case IAP_LM_DISCONNECT_INDICATION:
+ /* Abort call */
+ iriap_next_server_state(self, R_DISCONNECT);
+ iriap_next_r_connect_state(self, R_WAITING);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), unknown event!\n", __FUNCTION__);
+ break;
+ }
+}
+
+/*
+ * R-Connect FSM
+*/
+
+/*
+ * Function state_r_waiting (self, event, skb)
+ *
+ *
+ *
+ */
+static void state_r_waiting(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(0, "%s(), Not implemented\n", __FUNCTION__);
+}
+
+static void state_r_wait_active(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(0, "%s(), Not implemented\n", __FUNCTION__);
+}
+
+/*
+ * Function state_r_receiving (self, event, skb)
+ *
+ * We are receiving a command
+ *
+ */
+static void state_r_receiving(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ switch (event) {
+ case IAP_RECV_F_LST:
+ iriap_next_r_connect_state(self, R_EXECUTE);
+
+ iriap_call_indication(self, skb);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), unknown event!\n", __FUNCTION__);
+ break;
+ }
+
+}
+
+/*
+ * Function state_r_execute (self, event, skb)
+ *
+ * The server is processing the request
+ *
+ */
+static void state_r_execute(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(skb != NULL, return;);
+
+ if (!self || self->magic != IAS_MAGIC) {
+ IRDA_DEBUG(0, "%s(), bad pointer self\n", __FUNCTION__);
+ return;
+ }
+
+ switch (event) {
+ case IAP_CALL_RESPONSE:
+ /*
+ * Since we don't implement the Waiting state, we return
+ * to state Receiving instead, DB.
+ */
+ iriap_next_r_connect_state(self, R_RECEIVING);
+
+ irlmp_data_request(self->lsap, skb);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), unknown event!\n", __FUNCTION__);
+ break;
+ }
+}
+
+static void state_r_returning(struct iriap_cb *self, IRIAP_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(0, "%s(), event=%d\n", __FUNCTION__, event);
+
+ switch (event) {
+ case IAP_RECV_F_LST:
+
+ break;
+ default:
+ break;
+ }
+}
+
+
+
diff --git a/uClinux-2.4.31-uc0/net/irda/irias_object.c b/uClinux-2.4.31-uc0/net/irda/irias_object.c
new file mode 100644
index 0000000..d448695
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irias_object.c
@@ -0,0 +1,539 @@
+/*********************************************************************
+ *
+ * Filename: irias_object.c
+ * Version: 0.3
+ * Description: IAS object database and functions
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Thu Oct 1 22:50:04 1998
+ * Modified at: Wed Dec 15 11:23:16 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1998-1999 Dag Brattli, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/string.h>
+#include <linux/socket.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irmod.h>
+#include <net/irda/irias_object.h>
+
+hashbin_t *objects = NULL;
+
+/*
+ * Used when a missing value needs to be returned
+ */
+struct ias_value missing = { IAS_MISSING, 0, 0, 0, {0}};
+
+/*
+ * Function strndup (str, max)
+ *
+ * My own kernel version of strndup!
+ *
+ * Faster, check boundary... Jean II
+ */
+char *strndup(char *str, int max)
+{
+ char *new_str;
+ int len;
+
+ /* Check string */
+ if (str == NULL)
+ return NULL;
+ /* Check length, truncate */
+ len = strlen(str);
+ if(len > max)
+ len = max;
+
+ /* Allocate new string */
+ new_str = kmalloc(len + 1, GFP_ATOMIC);
+ if (new_str == NULL) {
+ WARNING("%s(), Unable to kmalloc!\n", __FUNCTION__);
+ return NULL;
+ }
+
+ /* Copy and truncate */
+ memcpy(new_str, str, len);
+ new_str[len] = '\0';
+
+ return new_str;
+}
+
+/*
+ * Function ias_new_object (name, id)
+ *
+ * Create a new IAS object
+ *
+ */
+struct ias_object *irias_new_object( char *name, int id)
+{
+ struct ias_object *obj;
+
+ IRDA_DEBUG( 4, "%s()\n", __FUNCTION__);
+
+ obj = (struct ias_object *) kmalloc(sizeof(struct ias_object),
+ GFP_ATOMIC);
+ if (obj == NULL) {
+ IRDA_DEBUG(0, "%s(), Unable to allocate object!\n", __FUNCTION__);
+ return NULL;
+ }
+ memset(obj, 0, sizeof( struct ias_object));
+
+ obj->magic = IAS_OBJECT_MAGIC;
+ obj->name = strndup(name, IAS_MAX_CLASSNAME);
+ obj->id = id;
+
+ obj->attribs = hashbin_new(HB_LOCAL);
+
+ return obj;
+}
+
+/*
+ * Function irias_delete_attrib (attrib)
+ *
+ * Delete given attribute and deallocate all its memory
+ *
+ */
+void __irias_delete_attrib(struct ias_attrib *attrib)
+{
+ ASSERT(attrib != NULL, return;);
+ ASSERT(attrib->magic == IAS_ATTRIB_MAGIC, return;);
+
+ if (attrib->name)
+ kfree(attrib->name);
+
+ irias_delete_value(attrib->value);
+ attrib->magic = ~IAS_ATTRIB_MAGIC;
+
+ kfree(attrib);
+}
+
+void __irias_delete_object(struct ias_object *obj)
+{
+ ASSERT(obj != NULL, return;);
+ ASSERT(obj->magic == IAS_OBJECT_MAGIC, return;);
+
+ if (obj->name)
+ kfree(obj->name);
+
+ hashbin_delete(obj->attribs, (FREE_FUNC) __irias_delete_attrib);
+
+ obj->magic = ~IAS_OBJECT_MAGIC;
+
+ kfree(obj);
+}
+
+/*
+ * Function irias_delete_object (obj)
+ *
+ * Remove object from hashbin and deallocate all attributes assosiated with
+ * with this object and the object itself
+ *
+ */
+int irias_delete_object(struct ias_object *obj)
+{
+ struct ias_object *node;
+
+ ASSERT(obj != NULL, return -1;);
+ ASSERT(obj->magic == IAS_OBJECT_MAGIC, return -1;);
+
+ node = hashbin_remove(objects, 0, obj->name);
+ if (!node)
+ return 0; /* Already removed */
+
+ __irias_delete_object(node);
+
+ return 0;
+}
+
+/*
+ * Function irias_delete_attrib (obj)
+ *
+ * Remove attribute from hashbin and, if it was the last attribute of
+ * the object, remove the object as well.
+ *
+ */
+int irias_delete_attrib(struct ias_object *obj, struct ias_attrib *attrib)
+{
+ struct ias_attrib *node;
+
+ ASSERT(obj != NULL, return -1;);
+ ASSERT(obj->magic == IAS_OBJECT_MAGIC, return -1;);
+ ASSERT(attrib != NULL, return -1;);
+
+ /* Remove attribute from object */
+ node = hashbin_remove(obj->attribs, 0, attrib->name);
+ if (!node)
+ return 0; /* Already removed or non-existent */
+
+ /* Deallocate attribute */
+ __irias_delete_attrib(node);
+
+ /* Check if object has still some attributes */
+ node = (struct ias_attrib *) hashbin_get_first(obj->attribs);
+ if (!node)
+ irias_delete_object(obj);
+
+ return 0;
+}
+
+/*
+ * Function irias_insert_object (obj)
+ *
+ * Insert an object into the LM-IAS database
+ *
+ */
+void irias_insert_object(struct ias_object *obj)
+{
+ ASSERT(obj != NULL, return;);
+ ASSERT(obj->magic == IAS_OBJECT_MAGIC, return;);
+
+ hashbin_insert(objects, (irda_queue_t *) obj, 0, obj->name);
+}
+
+/*
+ * Function irias_find_object (name)
+ *
+ * Find object with given name
+ *
+ */
+struct ias_object *irias_find_object(char *name)
+{
+ ASSERT(name != NULL, return NULL;);
+
+ return hashbin_find(objects, 0, name);
+}
+
+/*
+ * Function irias_find_attrib (obj, name)
+ *
+ * Find named attribute in object
+ *
+ */
+struct ias_attrib *irias_find_attrib(struct ias_object *obj, char *name)
+{
+ struct ias_attrib *attrib;
+
+ ASSERT(obj != NULL, return NULL;);
+ ASSERT(obj->magic == IAS_OBJECT_MAGIC, return NULL;);
+ ASSERT(name != NULL, return NULL;);
+
+ attrib = hashbin_find(obj->attribs, 0, name);
+ if (attrib == NULL)
+ return NULL;
+
+ return attrib;
+}
+
+/*
+ * Function irias_add_attribute (obj, attrib)
+ *
+ * Add attribute to object
+ *
+ */
+void irias_add_attrib( struct ias_object *obj, struct ias_attrib *attrib,
+ int owner)
+{
+ ASSERT(obj != NULL, return;);
+ ASSERT(obj->magic == IAS_OBJECT_MAGIC, return;);
+
+ ASSERT(attrib != NULL, return;);
+ ASSERT(attrib->magic == IAS_ATTRIB_MAGIC, return;);
+
+ /* Set if attrib is owned by kernel or user space */
+ attrib->value->owner = owner;
+
+ hashbin_insert(obj->attribs, (irda_queue_t *) attrib, 0, attrib->name);
+}
+
+/*
+ * Function irias_object_change_attribute (obj_name, attrib_name, new_value)
+ *
+ * Change the value of an objects attribute.
+ *
+ */
+int irias_object_change_attribute(char *obj_name, char *attrib_name,
+ struct ias_value *new_value)
+{
+ struct ias_object *obj;
+ struct ias_attrib *attrib;
+
+ /* Find object */
+ obj = hashbin_find(objects, 0, obj_name);
+ if (obj == NULL) {
+ WARNING("%s(), Unable to find object: %s\n", __FUNCTION__,
+ obj_name);
+ return -1;
+ }
+
+ /* Find attribute */
+ attrib = hashbin_find(obj->attribs, 0, attrib_name);
+ if (attrib == NULL) {
+ WARNING("%s(), Unable to find attribute: %s\n", __FUNCTION__,
+ attrib_name);
+ return -1;
+ }
+
+ if ( attrib->value->type != new_value->type) {
+ IRDA_DEBUG( 0, "%s(), changing value type not allowed!\n", __FUNCTION__);
+ return -1;
+ }
+
+ /* Delete old value */
+ irias_delete_value(attrib->value);
+
+ /* Insert new value */
+ attrib->value = new_value;
+
+ /* Success */
+ return 0;
+}
+
+/*
+ * Function irias_object_add_integer_attrib (obj, name, value)
+ *
+ * Add an integer attribute to an LM-IAS object
+ *
+ */
+void irias_add_integer_attrib(struct ias_object *obj, char *name, int value,
+ int owner)
+{
+ struct ias_attrib *attrib;
+
+ ASSERT(obj != NULL, return;);
+ ASSERT(obj->magic == IAS_OBJECT_MAGIC, return;);
+ ASSERT(name != NULL, return;);
+
+ attrib = (struct ias_attrib *) kmalloc(sizeof(struct ias_attrib),
+ GFP_ATOMIC);
+ if (attrib == NULL) {
+ WARNING("%s(), Unable to allocate attribute!\n", __FUNCTION__);
+ return;
+ }
+ memset(attrib, 0, sizeof( struct ias_attrib));
+
+ attrib->magic = IAS_ATTRIB_MAGIC;
+ attrib->name = strndup(name, IAS_MAX_ATTRIBNAME);
+
+ /* Insert value */
+ attrib->value = irias_new_integer_value(value);
+
+ irias_add_attrib(obj, attrib, owner);
+}
+
+ /*
+ * Function irias_add_octseq_attrib (obj, name, octet_seq, len)
+ *
+ * Add a octet sequence attribute to an LM-IAS object
+ *
+ */
+
+void irias_add_octseq_attrib(struct ias_object *obj, char *name, __u8 *octets,
+ int len, int owner)
+{
+ struct ias_attrib *attrib;
+
+ ASSERT(obj != NULL, return;);
+ ASSERT(obj->magic == IAS_OBJECT_MAGIC, return;);
+
+ ASSERT(name != NULL, return;);
+ ASSERT(octets != NULL, return;);
+
+ attrib = (struct ias_attrib *) kmalloc(sizeof(struct ias_attrib),
+ GFP_ATOMIC);
+ if (attrib == NULL) {
+ WARNING("%s(), Unable to allocate attribute!\n", __FUNCTION__);
+ return;
+ }
+ memset(attrib, 0, sizeof( struct ias_attrib));
+
+ attrib->magic = IAS_ATTRIB_MAGIC;
+ attrib->name = strndup(name, IAS_MAX_ATTRIBNAME);
+
+ attrib->value = irias_new_octseq_value( octets, len);
+
+ irias_add_attrib(obj, attrib, owner);
+}
+
+/*
+ * Function irias_object_add_string_attrib (obj, string)
+ *
+ * Add a string attribute to an LM-IAS object
+ *
+ */
+void irias_add_string_attrib(struct ias_object *obj, char *name, char *value,
+ int owner)
+{
+ struct ias_attrib *attrib;
+
+ ASSERT(obj != NULL, return;);
+ ASSERT(obj->magic == IAS_OBJECT_MAGIC, return;);
+
+ ASSERT(name != NULL, return;);
+ ASSERT(value != NULL, return;);
+
+ attrib = (struct ias_attrib *) kmalloc(sizeof( struct ias_attrib),
+ GFP_ATOMIC);
+ if (attrib == NULL) {
+ WARNING("%s(), Unable to allocate attribute!\n", __FUNCTION__);
+ return;
+ }
+ memset(attrib, 0, sizeof( struct ias_attrib));
+
+ attrib->magic = IAS_ATTRIB_MAGIC;
+ attrib->name = strndup(name, IAS_MAX_ATTRIBNAME);
+
+ attrib->value = irias_new_string_value(value);
+
+ irias_add_attrib(obj, attrib, owner);
+}
+
+/*
+ * Function irias_new_integer_value (integer)
+ *
+ * Create new IAS integer value
+ *
+ */
+struct ias_value *irias_new_integer_value(int integer)
+{
+ struct ias_value *value;
+
+ value = kmalloc(sizeof(struct ias_value), GFP_ATOMIC);
+ if (value == NULL) {
+ WARNING("%s(), Unable to kmalloc!\n", __FUNCTION__);
+ return NULL;
+ }
+ memset(value, 0, sizeof(struct ias_value));
+
+ value->type = IAS_INTEGER;
+ value->len = 4;
+ value->t.integer = integer;
+
+ return value;
+}
+
+/*
+ * Function irias_new_string_value (string)
+ *
+ * Create new IAS string value
+ *
+ * Per IrLMP 1.1, 4.3.3.2, strings are up to 256 chars - Jean II
+ */
+struct ias_value *irias_new_string_value(char *string)
+{
+ struct ias_value *value;
+
+ value = kmalloc(sizeof(struct ias_value), GFP_ATOMIC);
+ if (value == NULL) {
+ WARNING("%s(), Unable to kmalloc!\n", __FUNCTION__);
+ return NULL;
+ }
+ memset( value, 0, sizeof( struct ias_value));
+
+ value->type = IAS_STRING;
+ value->charset = CS_ASCII;
+ value->t.string = strndup(string, IAS_MAX_STRING);
+ value->len = strlen(value->t.string);
+
+ return value;
+}
+
+
+/*
+ * Function irias_new_octseq_value (octets, len)
+ *
+ * Create new IAS octet-sequence value
+ *
+ * Per IrLMP 1.1, 4.3.3.2, octet-sequence are up to 1024 bytes - Jean II
+ */
+struct ias_value *irias_new_octseq_value(__u8 *octseq , int len)
+{
+ struct ias_value *value;
+
+ value = kmalloc(sizeof(struct ias_value), GFP_ATOMIC);
+ if (value == NULL) {
+ WARNING("%s(), Unable to kmalloc!\n", __FUNCTION__);
+ return NULL;
+ }
+ memset(value, 0, sizeof(struct ias_value));
+
+ value->type = IAS_OCT_SEQ;
+ /* Check length */
+ if(len > IAS_MAX_OCTET_STRING)
+ len = IAS_MAX_OCTET_STRING;
+ value->len = len;
+
+ value->t.oct_seq = kmalloc(len, GFP_ATOMIC);
+ if (value->t.oct_seq == NULL){
+ WARNING("%s(), Unable to kmalloc!\n", __FUNCTION__);
+ kfree(value);
+ return NULL;
+ }
+ memcpy(value->t.oct_seq, octseq , len);
+ return value;
+}
+
+struct ias_value *irias_new_missing_value(void)
+{
+ struct ias_value *value;
+
+ value = kmalloc(sizeof(struct ias_value), GFP_ATOMIC);
+ if (value == NULL) {
+ WARNING("%s(), Unable to kmalloc!\n", __FUNCTION__);
+ return NULL;
+ }
+ memset(value, 0, sizeof(struct ias_value));
+
+ value->type = IAS_MISSING;
+ value->len = 0;
+
+ return value;
+}
+
+/*
+ * Function irias_delete_value (value)
+ *
+ * Delete IAS value
+ *
+ */
+void irias_delete_value(struct ias_value *value)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(value != NULL, return;);
+
+ switch (value->type) {
+ case IAS_INTEGER: /* Fallthrough */
+ case IAS_MISSING:
+ /* No need to deallocate */
+ break;
+ case IAS_STRING:
+ /* If string, deallocate string */
+ if (value->t.string != NULL)
+ kfree(value->t.string);
+ break;
+ case IAS_OCT_SEQ:
+ /* If byte stream, deallocate byte stream */
+ if (value->t.oct_seq != NULL)
+ kfree(value->t.oct_seq);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown value type!\n", __FUNCTION__);
+ break;
+ }
+ kfree(value);
+}
+
+
+
diff --git a/uClinux-2.4.31-uc0/net/irda/irlan/Config.in b/uClinux-2.4.31-uc0/net/irda/irlan/Config.in
new file mode 100644
index 0000000..6b60616
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlan/Config.in
@@ -0,0 +1 @@
+dep_tristate ' IrLAN protocol' CONFIG_IRLAN $CONFIG_IRDA
diff --git a/uClinux-2.4.31-uc0/net/irda/irlan/Makefile b/uClinux-2.4.31-uc0/net/irda/irlan/Makefile
new file mode 100644
index 0000000..7ab6b6d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlan/Makefile
@@ -0,0 +1,15 @@
+#
+# Makefile for the Linux IrDA IrLAN protocol layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := irlan.o
+
+obj-y := irlan_common.o irlan_eth.o irlan_event.o irlan_client.o irlan_provider.o irlan_filter.o irlan_provider_event.o irlan_client_event.o
+obj-m := $(O_TARGET)
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/irda/irlan/irlan_client.c b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_client.c
new file mode 100644
index 0000000..73fd184
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_client.c
@@ -0,0 +1,564 @@
+/*********************************************************************
+ *
+ * Filename: irlan_client.c
+ * Version: 0.9
+ * Description: IrDA LAN Access Protocol (IrLAN) Client
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sun Aug 31 20:14:37 1997
+ * Modified at: Tue Dec 14 15:47:02 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ * Sources: skeleton.c by Donald Becker <becker@CESDIS.gsfc.nasa.gov>
+ * slip.c by Laurence Culhane, <loz@holmes.demon.co.uk>
+ * Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
+ *
+ * Copyright (c) 1998-1999 Dag Brattli <dagb@cs.uit.no>,
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <net/arp.h>
+
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <asm/byteorder.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irttp.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/irias_object.h>
+#include <net/irda/iriap.h>
+#include <net/irda/timer.h>
+
+#include <net/irda/irlan_common.h>
+#include <net/irda/irlan_event.h>
+#include <net/irda/irlan_eth.h>
+#include <net/irda/irlan_provider.h>
+#include <net/irda/irlan_client.h>
+
+#undef CONFIG_IRLAN_GRATUITOUS_ARP
+
+static void irlan_client_ctrl_disconnect_indication(void *instance, void *sap,
+ LM_REASON reason,
+ struct sk_buff *);
+static int irlan_client_ctrl_data_indication(void *instance, void *sap,
+ struct sk_buff *skb);
+static void irlan_client_ctrl_connect_confirm(void *instance, void *sap,
+ struct qos_info *qos,
+ __u32 max_sdu_size,
+ __u8 max_header_size,
+ struct sk_buff *);
+static void irlan_check_response_param(struct irlan_cb *self, char *param,
+ char *value, int val_len);
+
+static void irlan_client_kick_timer_expired(void *data)
+{
+ struct irlan_cb *self = (struct irlan_cb *) data;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ /*
+ * If we are in peer mode, the client may not have got the discovery
+ * indication it needs to make progress. If the client is still in
+ * IDLE state, we must kick it to, but only if the provider is not IDLE
+ */
+ if ((self->provider.access_type == ACCESS_PEER) &&
+ (self->client.state == IRLAN_IDLE) &&
+ (self->provider.state != IRLAN_IDLE)) {
+ irlan_client_wakeup(self, self->saddr, self->daddr);
+ }
+}
+
+void irlan_client_start_kick_timer(struct irlan_cb *self, int timeout)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ irda_start_timer(&self->client.kick_timer, timeout, (void *) self,
+ irlan_client_kick_timer_expired);
+}
+
+/*
+ * Function irlan_client_wakeup (self, saddr, daddr)
+ *
+ * Wake up client
+ *
+ */
+void irlan_client_wakeup(struct irlan_cb *self, __u32 saddr, __u32 daddr)
+{
+ IRDA_DEBUG(1, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ /*
+ * Check if we are already awake, or if we are a provider in direct
+ * mode (in that case we must leave the client idle
+ */
+ if ((self->client.state != IRLAN_IDLE) ||
+ (self->provider.access_type == ACCESS_DIRECT))
+ {
+ IRDA_DEBUG(0, "%s(), already awake!\n", __FUNCTION__);
+ return;
+ }
+
+ /* Addresses may have changed! */
+ self->saddr = saddr;
+ self->daddr = daddr;
+
+ if (self->disconnect_reason == LM_USER_REQUEST) {
+ IRDA_DEBUG(0, "%s(), still stopped by user\n", __FUNCTION__);
+ return;
+ }
+
+ /* Open TSAPs */
+ irlan_client_open_ctrl_tsap(self);
+ irlan_open_data_tsap(self);
+
+ irlan_do_client_event(self, IRLAN_DISCOVERY_INDICATION, NULL);
+
+ /* Start kick timer */
+ irlan_client_start_kick_timer(self, 2*HZ);
+}
+
+/*
+ * Function irlan_discovery_indication (daddr)
+ *
+ * Remote device with IrLAN server support discovered
+ *
+ */
+void irlan_client_discovery_indication(discovery_t *discovery,
+ DISCOVERY_MODE mode,
+ void *priv)
+{
+ struct irlan_cb *self;
+ __u32 saddr, daddr;
+
+ IRDA_DEBUG(1, "%s()\n", __FUNCTION__);
+
+ ASSERT(irlan != NULL, return;);
+ ASSERT(discovery != NULL, return;);
+
+ /*
+ * I didn't check it, but I bet that IrLAN suffer from the same
+ * deficiency as IrComm and doesn't handle two instances
+ * simultaneously connecting to each other.
+ * Same workaround, drop passive discoveries.
+ * Jean II */
+ if(mode == DISCOVERY_PASSIVE)
+ return;
+
+ saddr = discovery->saddr;
+ daddr = discovery->daddr;
+
+ /* Find instance */
+ self = (struct irlan_cb *) hashbin_get_first(irlan);
+ if (self) {
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ IRDA_DEBUG(1, "%s(), Found instance (%08x)!\n", __FUNCTION__,
+ daddr);
+
+ irlan_client_wakeup(self, saddr, daddr);
+ }
+}
+
+/*
+ * Function irlan_client_data_indication (handle, skb)
+ *
+ * This function gets the data that is received on the control channel
+ *
+ */
+static int irlan_client_ctrl_data_indication(void *instance, void *sap,
+ struct sk_buff *skb)
+{
+ struct irlan_cb *self;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ self = (struct irlan_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRLAN_MAGIC, return -1;);
+ ASSERT(skb != NULL, return -1;);
+
+ irlan_do_client_event(self, IRLAN_DATA_INDICATION, skb);
+
+ /* Ready for a new command */
+ IRDA_DEBUG(2, "%s(), clearing tx_busy\n", __FUNCTION__);
+ self->client.tx_busy = FALSE;
+
+ /* Check if we have some queued commands waiting to be sent */
+ irlan_run_ctrl_tx_queue(self);
+
+ return 0;
+}
+
+static void irlan_client_ctrl_disconnect_indication(void *instance, void *sap,
+ LM_REASON reason,
+ struct sk_buff *userdata)
+{
+ struct irlan_cb *self;
+ struct tsap_cb *tsap;
+ struct sk_buff *skb;
+
+ IRDA_DEBUG(4, "%s(), reason=%d\n", __FUNCTION__, reason);
+
+ self = (struct irlan_cb *) instance;
+ tsap = (struct tsap_cb *) sap;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+ ASSERT(tsap != NULL, return;);
+ ASSERT(tsap->magic == TTP_TSAP_MAGIC, return;);
+
+ ASSERT(tsap == self->client.tsap_ctrl, return;);
+
+ /* Remove frames queued on the control channel */
+ while ((skb = skb_dequeue(&self->client.txq))) {
+ dev_kfree_skb(skb);
+ }
+ self->client.tx_busy = FALSE;
+
+ irlan_do_client_event(self, IRLAN_LMP_DISCONNECT, NULL);
+}
+
+/*
+ * Function irlan_client_open_tsaps (self)
+ *
+ * Initialize callbacks and open IrTTP TSAPs
+ *
+ */
+void irlan_client_open_ctrl_tsap(struct irlan_cb *self)
+{
+ struct tsap_cb *tsap;
+ notify_t notify;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ /* Check if already open */
+ if (self->client.tsap_ctrl)
+ return;
+
+ irda_notify_init(&notify);
+
+ /* Set up callbacks */
+ notify.data_indication = irlan_client_ctrl_data_indication;
+ notify.connect_confirm = irlan_client_ctrl_connect_confirm;
+ notify.disconnect_indication = irlan_client_ctrl_disconnect_indication;
+ notify.instance = self;
+ strncpy(notify.name, "IrLAN ctrl (c)", NOTIFY_MAX_NAME);
+
+ tsap = irttp_open_tsap(LSAP_ANY, DEFAULT_INITIAL_CREDIT, &notify);
+ if (!tsap) {
+ IRDA_DEBUG(2, "%s(), Got no tsap!\n", __FUNCTION__);
+ return;
+ }
+ self->client.tsap_ctrl = tsap;
+}
+
+/*
+ * Function irlan_client_connect_confirm (handle, skb)
+ *
+ * Connection to peer IrLAN laye confirmed
+ *
+ */
+static void irlan_client_ctrl_connect_confirm(void *instance, void *sap,
+ struct qos_info *qos,
+ __u32 max_sdu_size,
+ __u8 max_header_size,
+ struct sk_buff *skb)
+{
+ struct irlan_cb *self;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ self = (struct irlan_cb *) instance;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ self->client.max_sdu_size = max_sdu_size;
+ self->client.max_header_size = max_header_size;
+
+ /* TODO: we could set the MTU depending on the max_sdu_size */
+
+ irlan_do_client_event(self, IRLAN_CONNECT_COMPLETE, NULL);
+}
+
+/*
+ * Function irlan_client_reconnect_data_channel (self)
+ *
+ * Try to reconnect data channel (currently not used)
+ *
+ */
+void irlan_client_reconnect_data_channel(struct irlan_cb *self)
+{
+ struct sk_buff *skb;
+ __u8 *frame;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ skb = dev_alloc_skb(128);
+ if (!skb)
+ return;
+
+ /* Reserve space for TTP, LMP, and LAP header */
+ skb_reserve(skb, self->max_header_size);
+ skb_put(skb, 2);
+
+ frame = skb->data;
+
+ frame[0] = CMD_RECONNECT_DATA_CHAN;
+ frame[1] = 0x01;
+ irlan_insert_array_param(skb, "RECONNECT_KEY",
+ self->client.reconnect_key,
+ self->client.key_len);
+
+ irttp_data_request(self->client.tsap_ctrl, skb);
+}
+
+/*
+ * Function irlan_client_parse_response (self, skb)
+ *
+ * Extract all parameters from received buffer, then feed them to
+ * check_params for parsing
+ */
+void irlan_client_parse_response(struct irlan_cb *self, struct sk_buff *skb)
+{
+ __u8 *frame;
+ __u8 *ptr;
+ int count;
+ int ret;
+ __u16 val_len;
+ int i;
+ char *name;
+ char *value;
+
+ ASSERT(skb != NULL, return;);
+
+ IRDA_DEBUG(4, "%s() skb->len=%d\n", __FUNCTION__, (int) skb->len);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ if (!skb) {
+ ERROR("%s(), Got NULL skb!\n", __FUNCTION__);
+ return;
+ }
+ frame = skb->data;
+
+ /*
+ * Check return code and print it if not success
+ */
+ if (frame[0]) {
+ print_ret_code(frame[0]);
+ return;
+ }
+
+ name = kmalloc(255, GFP_ATOMIC);
+ if (!name)
+ return;
+ value = kmalloc(1016, GFP_ATOMIC);
+ if (!value) {
+ kfree(name);
+ return;
+ }
+
+ /* How many parameters? */
+ count = frame[1];
+
+ IRDA_DEBUG(4, "%s(), got %d parameters\n", __FUNCTION__, count);
+
+ ptr = frame+2;
+
+ /* For all parameters */
+ for (i=0; i<count;i++) {
+ ret = irlan_extract_param(ptr, name, value, &val_len);
+ if (ret < 0) {
+ IRDA_DEBUG(2, "%s(), IrLAN, Error!\n", __FUNCTION__);
+ break;
+ }
+ ptr += ret;
+ irlan_check_response_param(self, name, value, val_len);
+ }
+ /* Cleanup */
+ kfree(name);
+ kfree(value);
+}
+
+/*
+ * Function irlan_check_response_param (self, param, value, val_len)
+ *
+ * Check which parameter is received and update local variables
+ *
+ */
+static void irlan_check_response_param(struct irlan_cb *self, char *param,
+ char *value, int val_len)
+{
+ __u16 tmp_cpu; /* Temporary value in host order */
+ __u8 *bytes;
+ int i;
+
+ IRDA_DEBUG(4, "%s(), parm=%s\n", __FUNCTION__, param);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ /* Media type */
+ if (strcmp(param, "MEDIA") == 0) {
+ if (strcmp(value, "802.3") == 0)
+ self->media = MEDIA_802_3;
+ else
+ self->media = MEDIA_802_5;
+ return;
+ }
+ if (strcmp(param, "FILTER_TYPE") == 0) {
+ if (strcmp(value, "DIRECTED") == 0)
+ self->client.filter_type |= IRLAN_DIRECTED;
+ else if (strcmp(value, "FUNCTIONAL") == 0)
+ self->client.filter_type |= IRLAN_FUNCTIONAL;
+ else if (strcmp(value, "GROUP") == 0)
+ self->client.filter_type |= IRLAN_GROUP;
+ else if (strcmp(value, "MAC_FRAME") == 0)
+ self->client.filter_type |= IRLAN_MAC_FRAME;
+ else if (strcmp(value, "MULTICAST") == 0)
+ self->client.filter_type |= IRLAN_MULTICAST;
+ else if (strcmp(value, "BROADCAST") == 0)
+ self->client.filter_type |= IRLAN_BROADCAST;
+ else if (strcmp(value, "IPX_SOCKET") == 0)
+ self->client.filter_type |= IRLAN_IPX_SOCKET;
+
+ }
+ if (strcmp(param, "ACCESS_TYPE") == 0) {
+ if (strcmp(value, "DIRECT") == 0)
+ self->client.access_type = ACCESS_DIRECT;
+ else if (strcmp(value, "PEER") == 0)
+ self->client.access_type = ACCESS_PEER;
+ else if (strcmp(value, "HOSTED") == 0)
+ self->client.access_type = ACCESS_HOSTED;
+ else {
+ IRDA_DEBUG(2, "%s(), unknown access type!\n", __FUNCTION__);
+ }
+ }
+ /* IRLAN version */
+ if (strcmp(param, "IRLAN_VER") == 0) {
+ IRDA_DEBUG(4, "IrLAN version %d.%d\n", (__u8) value[0],
+ (__u8) value[1]);
+
+ self->version[0] = value[0];
+ self->version[1] = value[1];
+ return;
+ }
+ /* Which remote TSAP to use for data channel */
+ if (strcmp(param, "DATA_CHAN") == 0) {
+ self->dtsap_sel_data = value[0];
+ IRDA_DEBUG(4, "Data TSAP = %02x\n", self->dtsap_sel_data);
+ return;
+ }
+ if (strcmp(param, "CON_ARB") == 0) {
+ memcpy(&tmp_cpu, value, 2); /* Align value */
+ le16_to_cpus(&tmp_cpu); /* Convert to host order */
+ self->client.recv_arb_val = tmp_cpu;
+ IRDA_DEBUG(2, "%s(), receive arb val=%d\n", __FUNCTION__,
+ self->client.recv_arb_val);
+ }
+ if (strcmp(param, "MAX_FRAME") == 0) {
+ memcpy(&tmp_cpu, value, 2); /* Align value */
+ le16_to_cpus(&tmp_cpu); /* Convert to host order */
+ self->client.max_frame = tmp_cpu;
+ IRDA_DEBUG(4, "%s(), max frame=%d\n", __FUNCTION__,
+ self->client.max_frame);
+ }
+
+ /* RECONNECT_KEY, in case the link goes down! */
+ if (strcmp(param, "RECONNECT_KEY") == 0) {
+ IRDA_DEBUG(4, "Got reconnect key: ");
+ /* for (i = 0; i < val_len; i++) */
+/* printk("%02x", value[i]); */
+ memcpy(self->client.reconnect_key, value, val_len);
+ self->client.key_len = val_len;
+ IRDA_DEBUG(4, "\n");
+ }
+ /* FILTER_ENTRY, have we got an ethernet address? */
+ if (strcmp(param, "FILTER_ENTRY") == 0) {
+ bytes = value;
+ IRDA_DEBUG(4, "Ethernet address = %02x:%02x:%02x:%02x:%02x:%02x\n",
+ bytes[0], bytes[1], bytes[2], bytes[3], bytes[4],
+ bytes[5]);
+ for (i = 0; i < 6; i++)
+ self->dev.dev_addr[i] = bytes[i];
+ }
+}
+
+/*
+ * Function irlan_client_get_value_confirm (obj_id, value)
+ *
+ * Got results from remote LM-IAS
+ *
+ */
+void irlan_client_get_value_confirm(int result, __u16 obj_id,
+ struct ias_value *value, void *priv)
+{
+ struct irlan_cb *self;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(priv != NULL, return;);
+
+ self = (struct irlan_cb *) priv;
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ /* We probably don't need to make any more queries */
+ iriap_close(self->client.iriap);
+ self->client.iriap = NULL;
+
+ /* Check if request succeeded */
+ if (result != IAS_SUCCESS) {
+ IRDA_DEBUG(2, "%s(), got NULL value!\n", __FUNCTION__);
+ irlan_do_client_event(self, IRLAN_IAS_PROVIDER_NOT_AVAIL,
+ NULL);
+ return;
+ }
+
+ switch (value->type) {
+ case IAS_INTEGER:
+ self->dtsap_sel_ctrl = value->t.integer;
+
+ if (value->t.integer != -1) {
+ irlan_do_client_event(self, IRLAN_IAS_PROVIDER_AVAIL,
+ NULL);
+ return;
+ }
+ irias_delete_value(value);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), unknown type!\n", __FUNCTION__);
+ break;
+ }
+ irlan_do_client_event(self, IRLAN_IAS_PROVIDER_NOT_AVAIL, NULL);
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/irlan/irlan_client_event.c b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_client_event.c
new file mode 100644
index 0000000..7cf3984
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_client_event.c
@@ -0,0 +1,532 @@
+/*********************************************************************
+ *
+ * Filename: irlan_client_event.c
+ * Version: 0.9
+ * Description: IrLAN client state machine
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sun Aug 31 20:14:37 1997
+ * Modified at: Sun Dec 26 21:52:24 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1998-1999 Dag Brattli <dagb@cs.uit.no>,
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/skbuff.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/timer.h>
+#include <net/irda/irmod.h>
+#include <net/irda/iriap.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/irttp.h>
+
+#include <net/irda/irlan_common.h>
+#include <net/irda/irlan_client.h>
+#include <net/irda/irlan_event.h>
+
+static int irlan_client_state_idle (struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+static int irlan_client_state_query(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+static int irlan_client_state_conn (struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+static int irlan_client_state_info (struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+static int irlan_client_state_media(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+static int irlan_client_state_open (struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+static int irlan_client_state_wait (struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+static int irlan_client_state_arb (struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+static int irlan_client_state_data (struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+static int irlan_client_state_close(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+static int irlan_client_state_sync (struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+
+static int (*state[])(struct irlan_cb *, IRLAN_EVENT event, struct sk_buff *) =
+{
+ irlan_client_state_idle,
+ irlan_client_state_query,
+ irlan_client_state_conn,
+ irlan_client_state_info,
+ irlan_client_state_media,
+ irlan_client_state_open,
+ irlan_client_state_wait,
+ irlan_client_state_arb,
+ irlan_client_state_data,
+ irlan_client_state_close,
+ irlan_client_state_sync
+};
+
+void irlan_do_client_event(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ (*state[ self->client.state]) (self, event, skb);
+}
+
+/*
+ * Function irlan_client_state_idle (event, skb, info)
+ *
+ * IDLE, We are waiting for an indication that there is a provider
+ * available.
+ */
+static int irlan_client_state_idle(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRLAN_MAGIC, return -1;);
+
+ switch (event) {
+ case IRLAN_DISCOVERY_INDICATION:
+ if (self->client.iriap) {
+ WARNING("%s(), busy with a previous query\n", __FUNCTION__);
+ return -EBUSY;
+ }
+
+ self->client.iriap = iriap_open(LSAP_ANY, IAS_CLIENT, self,
+ irlan_client_get_value_confirm);
+ /* Get some values from peer IAS */
+ irlan_next_client_state(self, IRLAN_QUERY);
+ iriap_getvaluebyclass_request(self->client.iriap,
+ self->saddr, self->daddr,
+ "IrLAN", "IrDA:TinyTP:LsapSel");
+ break;
+ case IRLAN_WATCHDOG_TIMEOUT:
+ IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__);
+ break;
+ default:
+ IRDA_DEBUG(4, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/*
+ * Function irlan_client_state_query (event, skb, info)
+ *
+ * QUERY, We have queryed the remote IAS and is ready to connect
+ * to provider, just waiting for the confirm.
+ *
+ */
+static int irlan_client_state_query(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRLAN_MAGIC, return -1;);
+
+ switch(event) {
+ case IRLAN_IAS_PROVIDER_AVAIL:
+ ASSERT(self->dtsap_sel_ctrl != 0, return -1;);
+
+ self->client.open_retries = 0;
+
+ irttp_connect_request(self->client.tsap_ctrl,
+ self->dtsap_sel_ctrl,
+ self->saddr, self->daddr, NULL,
+ IRLAN_MTU, NULL);
+ irlan_next_client_state(self, IRLAN_CONN);
+ break;
+ case IRLAN_IAS_PROVIDER_NOT_AVAIL:
+ IRDA_DEBUG(2, "%s(), IAS_PROVIDER_NOT_AVAIL\n", __FUNCTION__);
+ irlan_next_client_state(self, IRLAN_IDLE);
+
+ /* Give the client a kick! */
+ if ((self->provider.access_type == ACCESS_PEER) &&
+ (self->provider.state != IRLAN_IDLE))
+ irlan_client_wakeup(self, self->saddr, self->daddr);
+ break;
+ case IRLAN_LMP_DISCONNECT:
+ case IRLAN_LAP_DISCONNECT:
+ irlan_next_client_state(self, IRLAN_IDLE);
+ break;
+ case IRLAN_WATCHDOG_TIMEOUT:
+ IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/*
+ * Function irlan_client_state_conn (event, skb, info)
+ *
+ * CONN, We have connected to a provider but has not issued any
+ * commands yet.
+ *
+ */
+static int irlan_client_state_conn(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+
+ switch (event) {
+ case IRLAN_CONNECT_COMPLETE:
+ /* Send getinfo cmd */
+ irlan_get_provider_info(self);
+ irlan_next_client_state(self, IRLAN_INFO);
+ break;
+ case IRLAN_LMP_DISCONNECT:
+ case IRLAN_LAP_DISCONNECT:
+ irlan_next_client_state(self, IRLAN_IDLE);
+ break;
+ case IRLAN_WATCHDOG_TIMEOUT:
+ IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/*
+ * Function irlan_client_state_info (self, event, skb, info)
+ *
+ * INFO, We have issued a GetInfo command and is awaiting a reply.
+ */
+static int irlan_client_state_info(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+
+ switch (event) {
+ case IRLAN_DATA_INDICATION:
+ ASSERT(skb != NULL, return -1;);
+
+ irlan_client_parse_response(self, skb);
+
+ irlan_next_client_state(self, IRLAN_MEDIA);
+
+ irlan_get_media_char(self);
+ break;
+
+ case IRLAN_LMP_DISCONNECT:
+ case IRLAN_LAP_DISCONNECT:
+ irlan_next_client_state(self, IRLAN_IDLE);
+ break;
+ case IRLAN_WATCHDOG_TIMEOUT:
+ IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/*
+ * Function irlan_client_state_media (self, event, skb, info)
+ *
+ * MEDIA, The irlan_client has issued a GetMedia command and is awaiting a
+ * reply.
+ *
+ */
+static int irlan_client_state_media(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+
+ switch(event) {
+ case IRLAN_DATA_INDICATION:
+ irlan_client_parse_response(self, skb);
+ irlan_open_data_channel(self);
+ irlan_next_client_state(self, IRLAN_OPEN);
+ break;
+ case IRLAN_LMP_DISCONNECT:
+ case IRLAN_LAP_DISCONNECT:
+ irlan_next_client_state(self, IRLAN_IDLE);
+ break;
+ case IRLAN_WATCHDOG_TIMEOUT:
+ IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/*
+ * Function irlan_client_state_open (self, event, skb, info)
+ *
+ * OPEN, The irlan_client has issued a OpenData command and is awaiting a
+ * reply
+ *
+ */
+static int irlan_client_state_open(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ struct qos_info qos;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+
+ switch(event) {
+ case IRLAN_DATA_INDICATION:
+ irlan_client_parse_response(self, skb);
+
+ /*
+ * Check if we have got the remote TSAP for data
+ * communications
+ */
+ ASSERT(self->dtsap_sel_data != 0, return -1;);
+
+ /* Check which access type we are dealing with */
+ switch (self->client.access_type) {
+ case ACCESS_PEER:
+ if (self->provider.state == IRLAN_OPEN) {
+
+ irlan_next_client_state(self, IRLAN_ARB);
+ irlan_do_client_event(self, IRLAN_CHECK_CON_ARB,
+ NULL);
+ } else {
+
+ irlan_next_client_state(self, IRLAN_WAIT);
+ }
+ break;
+ case ACCESS_DIRECT:
+ case ACCESS_HOSTED:
+ qos.link_disc_time.bits = 0x01; /* 3 secs */
+
+ irttp_connect_request(self->tsap_data,
+ self->dtsap_sel_data,
+ self->saddr, self->daddr, &qos,
+ IRLAN_MTU, NULL);
+
+ irlan_next_client_state(self, IRLAN_DATA);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), unknown access type!\n", __FUNCTION__);
+ break;
+ }
+ break;
+ case IRLAN_LMP_DISCONNECT:
+ case IRLAN_LAP_DISCONNECT:
+ irlan_next_client_state(self, IRLAN_IDLE);
+ break;
+ case IRLAN_WATCHDOG_TIMEOUT:
+ IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/*
+ * Function irlan_client_state_wait (self, event, skb, info)
+ *
+ * WAIT, The irlan_client is waiting for the local provider to enter the
+ * provider OPEN state.
+ *
+ */
+static int irlan_client_state_wait(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+
+ switch(event) {
+ case IRLAN_PROVIDER_SIGNAL:
+ irlan_next_client_state(self, IRLAN_ARB);
+ irlan_do_client_event(self, IRLAN_CHECK_CON_ARB, NULL);
+ break;
+ case IRLAN_LMP_DISCONNECT:
+ case IRLAN_LAP_DISCONNECT:
+ irlan_next_client_state(self, IRLAN_IDLE);
+ break;
+ case IRLAN_WATCHDOG_TIMEOUT:
+ IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+static int irlan_client_state_arb(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ struct qos_info qos;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+
+ switch(event) {
+ case IRLAN_CHECK_CON_ARB:
+ if (self->client.recv_arb_val == self->provider.send_arb_val) {
+ irlan_next_client_state(self, IRLAN_CLOSE);
+ irlan_close_data_channel(self);
+ } else if (self->client.recv_arb_val <
+ self->provider.send_arb_val)
+ {
+ qos.link_disc_time.bits = 0x01; /* 3 secs */
+
+ irlan_next_client_state(self, IRLAN_DATA);
+ irttp_connect_request(self->tsap_data,
+ self->dtsap_sel_data,
+ self->saddr, self->daddr, &qos,
+ IRLAN_MTU, NULL);
+ } else if (self->client.recv_arb_val >
+ self->provider.send_arb_val)
+ {
+ IRDA_DEBUG(2, "%s(), lost the battle :-(\n", __FUNCTION__);
+ }
+ break;
+ case IRLAN_DATA_CONNECT_INDICATION:
+ irlan_next_client_state(self, IRLAN_DATA);
+ break;
+ case IRLAN_LMP_DISCONNECT:
+ case IRLAN_LAP_DISCONNECT:
+ irlan_next_client_state(self, IRLAN_IDLE);
+ break;
+ case IRLAN_WATCHDOG_TIMEOUT:
+ IRDA_DEBUG(2, "%s(), IRLAN_WATCHDOG_TIMEOUT\n", __FUNCTION__);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/*
+ * Function irlan_client_state_data (self, event, skb, info)
+ *
+ * DATA, The data channel is connected, allowing data transfers between
+ * the local and remote machines.
+ *
+ */
+static int irlan_client_state_data(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRLAN_MAGIC, return -1;);
+
+ switch(event) {
+ case IRLAN_DATA_INDICATION:
+ irlan_client_parse_response(self, skb);
+ break;
+ case IRLAN_LMP_DISCONNECT: /* FALLTHROUGH */
+ case IRLAN_LAP_DISCONNECT:
+ irlan_next_client_state(self, IRLAN_IDLE);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/*
+ * Function irlan_client_state_close (self, event, skb, info)
+ *
+ *
+ *
+ */
+static int irlan_client_state_close(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/*
+ * Function irlan_client_state_sync (self, event, skb, info)
+ *
+ *
+ *
+ */
+static int irlan_client_state_sync(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uClinux-2.4.31-uc0/net/irda/irlan/irlan_common.c b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_common.c
new file mode 100644
index 0000000..a2217e0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_common.c
@@ -0,0 +1,1232 @@
+/*********************************************************************
+ *
+ * Filename: irlan_common.c
+ * Version: 0.9
+ * Description: IrDA LAN Access Protocol Implementation
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sun Aug 31 20:14:37 1997
+ * Modified at: Sun Dec 26 21:53:10 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1997, 1999 Dag Brattli <dagb@cs.uit.no>,
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/proc_fs.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <asm/byteorder.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irttp.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/iriap.h>
+#include <net/irda/timer.h>
+
+#include <net/irda/irlan_common.h>
+#include <net/irda/irlan_client.h>
+#include <net/irda/irlan_provider.h>
+#include <net/irda/irlan_eth.h>
+#include <net/irda/irlan_filter.h>
+
+
+/*
+ * Send gratuitous ARP when connected to a new AP or not. May be a clever
+ * thing to do, but for some reason the machine crashes if you use DHCP. So
+ * lets not use it by default.
+ */
+#undef CONFIG_IRLAN_SEND_GRATUITOUS_ARP
+
+/* extern char sysctl_devname[]; */
+
+/*
+ * Master structure
+ */
+hashbin_t *irlan = NULL;
+static __u32 ckey, skey;
+
+/* Module parameters */
+static int eth = 0; /* Use "eth" or "irlan" name for devices */
+static int access = ACCESS_PEER; /* PEER, DIRECT or HOSTED */
+
+#ifdef CONFIG_PROC_FS
+static char *irlan_state[] = {
+ "IRLAN_IDLE",
+ "IRLAN_QUERY",
+ "IRLAN_CONN",
+ "IRLAN_INFO",
+ "IRLAN_MEDIA",
+ "IRLAN_OPEN",
+ "IRLAN_WAIT",
+ "IRLAN_ARB",
+ "IRLAN_DATA",
+ "IRLAN_CLOSE",
+ "IRLAN_SYNC"
+};
+
+static char *irlan_access[] = {
+ "UNKNOWN",
+ "DIRECT",
+ "PEER",
+ "HOSTED"
+};
+
+static char *irlan_media[] = {
+ "UNKNOWN",
+ "802.3",
+ "802.5"
+};
+#endif /* CONFIG_PROC_FS */
+
+static void __irlan_close(struct irlan_cb *self);
+static int __irlan_insert_param(struct sk_buff *skb, char *param, int type,
+ __u8 value_byte, __u16 value_short,
+ __u8 *value_array, __u16 value_len);
+void irlan_close_tsaps(struct irlan_cb *self);
+
+#ifdef CONFIG_PROC_FS
+static int irlan_proc_read(char *buf, char **start, off_t offset, int len);
+
+extern struct proc_dir_entry *proc_irda;
+#endif /* CONFIG_PROC_FS */
+
+/*
+ * Function irlan_init (void)
+ *
+ * Initialize IrLAN layer
+ *
+ */
+int __init irlan_init(void)
+{
+ struct irlan_cb *new;
+ __u16 hints;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+ /* Allocate master structure */
+ irlan = hashbin_new(HB_LOCAL);
+ if (irlan == NULL) {
+ printk(KERN_WARNING "IrLAN: Can't allocate hashbin!\n");
+ return -ENOMEM;
+ }
+#ifdef CONFIG_PROC_FS
+ create_proc_info_entry("irlan", 0, proc_irda, irlan_proc_read);
+#endif /* CONFIG_PROC_FS */
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+ hints = irlmp_service_to_hint(S_LAN);
+
+ /* Register with IrLMP as a client */
+ ckey = irlmp_register_client(hints, &irlan_client_discovery_indication,
+ NULL, NULL);
+
+ /* Register with IrLMP as a service */
+ skey = irlmp_register_service(hints);
+
+ /* Start the master IrLAN instance (the only one for now) */
+ new = irlan_open(DEV_ADDR_ANY, DEV_ADDR_ANY);
+
+ /* The master will only open its (listen) control TSAP */
+ irlan_provider_open_ctrl_tsap(new);
+
+ /* Do some fast discovery! */
+ irlmp_discovery_request(DISCOVERY_DEFAULT_SLOTS);
+
+ return 0;
+}
+
+void irlan_cleanup(void)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ irlmp_unregister_client(ckey);
+ irlmp_unregister_service(skey);
+
+#ifdef CONFIG_PROC_FS
+ remove_proc_entry("irlan", proc_irda);
+#endif /* CONFIG_PROC_FS */
+ /*
+ * Delete hashbin and close all irlan client instances in it
+ */
+ hashbin_delete(irlan, (FREE_FUNC) __irlan_close);
+}
+
+/*
+ * Function irlan_register_netdev (self)
+ *
+ * Registers the network device to be used. We should don't register until
+ * we have been binded to a particular provider or client.
+ */
+int irlan_register_netdev(struct irlan_cb *self)
+{
+ int i=0;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ /* Check if we should call the device eth<x> or irlan<x> */
+ if (!eth) {
+ /* Get the first free irlan<x> name */
+ do {
+ sprintf(self->dev.name, "%s%d", "irlan", i++);
+ } while (dev_get(self->dev.name));
+ }
+
+ if (register_netdev(&self->dev) != 0) {
+ IRDA_DEBUG(2, "%s(), register_netdev() failed!\n", __FUNCTION__);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Function irlan_open (void)
+ *
+ * Open new instance of a client/provider, we should only register the
+ * network device if this instance is ment for a particular client/provider
+ */
+struct irlan_cb *irlan_open(__u32 saddr, __u32 daddr)
+{
+ struct irlan_cb *self;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+ ASSERT(irlan != NULL, return NULL;);
+
+ /*
+ * Initialize the irlan structure.
+ */
+ self = kmalloc(sizeof(struct irlan_cb), GFP_ATOMIC);
+ if (self == NULL)
+ return NULL;
+
+ memset(self, 0, sizeof(struct irlan_cb));
+
+ /*
+ * Initialize local device structure
+ */
+ self->magic = IRLAN_MAGIC;
+
+ sprintf(self->dev.name, "%s", "unknown");
+
+ self->dev.priv = (void *) self;
+ self->dev.next = NULL;
+ self->dev.init = irlan_eth_init;
+
+ self->saddr = saddr;
+ self->daddr = daddr;
+
+ /* Provider access can only be PEER, DIRECT, or HOSTED */
+ self->provider.access_type = access;
+ self->media = MEDIA_802_3;
+ self->disconnect_reason = LM_USER_REQUEST;
+ init_timer(&self->watchdog_timer);
+ init_timer(&self->client.kick_timer);
+ init_waitqueue_head(&self->open_wait);
+
+ hashbin_insert(irlan, (irda_queue_t *) self, daddr, NULL);
+
+ skb_queue_head_init(&self->client.txq);
+
+ irlan_next_client_state(self, IRLAN_IDLE);
+ irlan_next_provider_state(self, IRLAN_IDLE);
+
+ irlan_register_netdev(self);
+
+ return self;
+}
+/*
+ * Function __irlan_close (self)
+ *
+ * This function closes and deallocates the IrLAN client instances. Be
+ * aware that other functions which calles client_close() must call
+ * hashbin_remove() first!!!
+ */
+static void __irlan_close(struct irlan_cb *self)
+{
+ struct sk_buff *skb;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ del_timer(&self->watchdog_timer);
+ del_timer(&self->client.kick_timer);
+
+ /* Close all open connections and remove TSAPs */
+ irlan_close_tsaps(self);
+
+ if (self->client.iriap)
+ iriap_close(self->client.iriap);
+
+ /* Remove frames queued on the control channel */
+ while ((skb = skb_dequeue(&self->client.txq)))
+ dev_kfree_skb(skb);
+
+ unregister_netdev(&self->dev);
+
+ self->magic = 0;
+ kfree(self);
+}
+
+/*
+ * Function irlan_connect_indication (instance, sap, qos, max_sdu_size, skb)
+ *
+ * Here we receive the connect indication for the data channel
+ *
+ */
+void irlan_connect_indication(void *instance, void *sap, struct qos_info *qos,
+ __u32 max_sdu_size, __u8 max_header_size,
+ struct sk_buff *skb)
+{
+ struct irlan_cb *self;
+ struct tsap_cb *tsap;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ self = (struct irlan_cb *) instance;
+ tsap = (struct tsap_cb *) sap;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+ ASSERT(tsap == self->tsap_data,return;);
+
+ self->max_sdu_size = max_sdu_size;
+ self->max_header_size = max_header_size;
+
+ IRDA_DEBUG(0, "IrLAN, We are now connected!\n");
+
+ del_timer(&self->watchdog_timer);
+
+ /* If you want to pass the skb to *both* state machines, you will
+ * need to skb_clone() it, so that you don't free it twice.
+ * As the state machines don't need it, git rid of it here...
+ * Jean II */
+ if (skb)
+ dev_kfree_skb(skb);
+
+ irlan_do_provider_event(self, IRLAN_DATA_CONNECT_INDICATION, NULL);
+ irlan_do_client_event(self, IRLAN_DATA_CONNECT_INDICATION, NULL);
+
+ if (self->provider.access_type == ACCESS_PEER) {
+ /*
+ * Data channel is open, so we are now allowed to
+ * configure the remote filter
+ */
+ irlan_get_unicast_addr(self);
+ irlan_open_unicast_addr(self);
+ }
+ /* Ready to transfer Ethernet frames (at last) */
+ netif_start_queue(&self->dev); /* Clear reason */
+}
+
+void irlan_connect_confirm(void *instance, void *sap, struct qos_info *qos,
+ __u32 max_sdu_size, __u8 max_header_size,
+ struct sk_buff *skb)
+{
+ struct irlan_cb *self;
+
+ self = (struct irlan_cb *) instance;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ self->max_sdu_size = max_sdu_size;
+ self->max_header_size = max_header_size;
+
+ /* TODO: we could set the MTU depending on the max_sdu_size */
+
+ IRDA_DEBUG(2, "IrLAN, We are now connected!\n");
+ del_timer(&self->watchdog_timer);
+
+ /*
+ * Data channel is open, so we are now allowed to configure the remote
+ * filter
+ */
+ irlan_get_unicast_addr(self);
+ irlan_open_unicast_addr(self);
+
+ /* Open broadcast and multicast filter by default */
+ irlan_set_broadcast_filter(self, TRUE);
+ irlan_set_multicast_filter(self, TRUE);
+
+ /* Ready to transfer Ethernet frames */
+ netif_start_queue(&self->dev);
+ self->disconnect_reason = 0; /* Clear reason */
+#ifdef CONFIG_IRLAN_SEND_GRATUITOUS_ARP
+ irlan_eth_send_gratuitous_arp(&self->dev);
+#endif
+ wake_up_interruptible(&self->open_wait);
+}
+
+/*
+ * Function irlan_client_disconnect_indication (handle)
+ *
+ * Callback function for the IrTTP layer. Indicates a disconnection of
+ * the specified connection (handle)
+ */
+void irlan_disconnect_indication(void *instance, void *sap, LM_REASON reason,
+ struct sk_buff *userdata)
+{
+ struct irlan_cb *self;
+ struct tsap_cb *tsap;
+
+ IRDA_DEBUG(0, "%s(), reason=%d\n", __FUNCTION__, reason);
+
+ self = (struct irlan_cb *) instance;
+ tsap = (struct tsap_cb *) sap;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+ ASSERT(tsap != NULL, return;);
+ ASSERT(tsap->magic == TTP_TSAP_MAGIC, return;);
+
+ ASSERT(tsap == self->tsap_data, return;);
+
+ IRDA_DEBUG(2, "IrLAN, data channel disconnected by peer!\n");
+
+ /* Save reason so we know if we should try to reconnect or not */
+ self->disconnect_reason = reason;
+
+ switch (reason) {
+ case LM_USER_REQUEST: /* User request */
+ IRDA_DEBUG(2, "%s(), User requested\n", __FUNCTION__);
+ break;
+ case LM_LAP_DISCONNECT: /* Unexpected IrLAP disconnect */
+ IRDA_DEBUG(2, "%s(), Unexpected IrLAP disconnect\n", __FUNCTION__);
+ break;
+ case LM_CONNECT_FAILURE: /* Failed to establish IrLAP connection */
+ IRDA_DEBUG(2, "%s(), IrLAP connect failed\n", __FUNCTION__);
+ break;
+ case LM_LAP_RESET: /* IrLAP reset */
+ IRDA_DEBUG(2, "%s(), IrLAP reset\n", __FUNCTION__);
+ break;
+ case LM_INIT_DISCONNECT:
+ IRDA_DEBUG(2, "%s(), IrLMP connect failed\n", __FUNCTION__);
+ break;
+ default:
+ ERROR("%s(), Unknown disconnect reason\n", __FUNCTION__);
+ break;
+ }
+
+ /* If you want to pass the skb to *both* state machines, you will
+ * need to skb_clone() it, so that you don't free it twice.
+ * As the state machines don't need it, git rid of it here...
+ * Jean II */
+ if (userdata)
+ dev_kfree_skb(userdata);
+
+ irlan_do_client_event(self, IRLAN_LMP_DISCONNECT, NULL);
+ irlan_do_provider_event(self, IRLAN_LMP_DISCONNECT, NULL);
+
+ wake_up_interruptible(&self->open_wait);
+}
+
+void irlan_open_data_tsap(struct irlan_cb *self)
+{
+ struct tsap_cb *tsap;
+ notify_t notify;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ /* Check if already open */
+ if (self->tsap_data)
+ return;
+
+ irda_notify_init(&notify);
+
+ notify.data_indication = irlan_eth_receive;
+ notify.udata_indication = irlan_eth_receive;
+ notify.connect_indication = irlan_connect_indication;
+ notify.connect_confirm = irlan_connect_confirm;
+ /*notify.flow_indication = irlan_eth_flow_indication;*/
+ notify.disconnect_indication = irlan_disconnect_indication;
+ notify.instance = self;
+ strncpy(notify.name, "IrLAN data", NOTIFY_MAX_NAME);
+
+ tsap = irttp_open_tsap(LSAP_ANY, DEFAULT_INITIAL_CREDIT, &notify);
+ if (!tsap) {
+ IRDA_DEBUG(2, "%s(), Got no tsap!\n", __FUNCTION__);
+ return;
+ }
+ self->tsap_data = tsap;
+
+ /*
+ * This is the data TSAP selector which we will pass to the client
+ * when the client ask for it.
+ */
+ self->stsap_sel_data = self->tsap_data->stsap_sel;
+}
+
+void irlan_close_tsaps(struct irlan_cb *self)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ /* Disconnect and close all open TSAP connections */
+ if (self->tsap_data) {
+ irttp_disconnect_request(self->tsap_data, NULL, P_NORMAL);
+ irttp_close_tsap(self->tsap_data);
+ self->tsap_data = NULL;
+ }
+ if (self->client.tsap_ctrl) {
+ irttp_disconnect_request(self->client.tsap_ctrl, NULL,
+ P_NORMAL);
+ irttp_close_tsap(self->client.tsap_ctrl);
+ self->client.tsap_ctrl = NULL;
+ }
+ if (self->provider.tsap_ctrl) {
+ irttp_disconnect_request(self->provider.tsap_ctrl, NULL,
+ P_NORMAL);
+ irttp_close_tsap(self->provider.tsap_ctrl);
+ self->provider.tsap_ctrl = NULL;
+ }
+ self->disconnect_reason = LM_USER_REQUEST;
+}
+
+/*
+ * Function irlan_ias_register (self, tsap_sel)
+ *
+ * Register with LM-IAS
+ *
+ */
+void irlan_ias_register(struct irlan_cb *self, __u8 tsap_sel)
+{
+ struct ias_object *obj;
+ struct ias_value *new_value;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ /*
+ * Check if object has already been registred by a previous provider.
+ * If that is the case, we just change the value of the attribute
+ */
+ if (!irias_find_object("IrLAN")) {
+ obj = irias_new_object("IrLAN", IAS_IRLAN_ID);
+ irias_add_integer_attrib(obj, "IrDA:TinyTP:LsapSel", tsap_sel,
+ IAS_KERNEL_ATTR);
+ irias_insert_object(obj);
+ } else {
+ new_value = irias_new_integer_value(tsap_sel);
+ irias_object_change_attribute("IrLAN", "IrDA:TinyTP:LsapSel",
+ new_value);
+ }
+
+ /* Register PnP object only if not registred before */
+ if (!irias_find_object("PnP")) {
+ obj = irias_new_object("PnP", IAS_PNP_ID);
+#if 0
+ irias_add_string_attrib(obj, "Name", sysctl_devname,
+ IAS_KERNEL_ATTR);
+#else
+ irias_add_string_attrib(obj, "Name", "Linux", IAS_KERNEL_ATTR);
+#endif
+ irias_add_string_attrib(obj, "DeviceID", "HWP19F0",
+ IAS_KERNEL_ATTR);
+ irias_add_integer_attrib(obj, "CompCnt", 1, IAS_KERNEL_ATTR);
+ if (self->provider.access_type == ACCESS_PEER)
+ irias_add_string_attrib(obj, "Comp#01", "PNP8389",
+ IAS_KERNEL_ATTR);
+ else
+ irias_add_string_attrib(obj, "Comp#01", "PNP8294",
+ IAS_KERNEL_ATTR);
+
+ irias_add_string_attrib(obj, "Manufacturer",
+ "Linux-IrDA Project", IAS_KERNEL_ATTR);
+ irias_insert_object(obj);
+ }
+}
+
+/*
+ * Function irlan_run_ctrl_tx_queue (self)
+ *
+ * Try to send the next command in the control transmit queue
+ *
+ */
+int irlan_run_ctrl_tx_queue(struct irlan_cb *self)
+{
+ struct sk_buff *skb;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ if (irda_lock(&self->client.tx_busy) == FALSE)
+ return -EBUSY;
+
+ skb = skb_dequeue(&self->client.txq);
+ if (!skb) {
+ self->client.tx_busy = FALSE;
+ return 0;
+ }
+
+ /* Check that it's really possible to send commands */
+ if ((self->client.tsap_ctrl == NULL) ||
+ (self->client.state == IRLAN_IDLE))
+ {
+ self->client.tx_busy = FALSE;
+ dev_kfree_skb(skb);
+ return -1;
+ }
+ IRDA_DEBUG(2, "%s(), sending ...\n", __FUNCTION__);
+
+ return irttp_data_request(self->client.tsap_ctrl, skb);
+}
+
+/*
+ * Function irlan_ctrl_data_request (self, skb)
+ *
+ * This function makes sure that commands on the control channel is being
+ * sent in a command/response fashion
+ */
+void irlan_ctrl_data_request(struct irlan_cb *self, struct sk_buff *skb)
+{
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ /* Queue command */
+ skb_queue_tail(&self->client.txq, skb);
+
+ /* Try to send command */
+ irlan_run_ctrl_tx_queue(self);
+}
+
+/*
+ * Function irlan_get_provider_info (self)
+ *
+ * Send Get Provider Information command to peer IrLAN layer
+ *
+ */
+void irlan_get_provider_info(struct irlan_cb *self)
+{
+ struct sk_buff *skb;
+ __u8 *frame;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ skb = dev_alloc_skb(64);
+ if (!skb)
+ return;
+
+ /* Reserve space for TTP, LMP, and LAP header */
+ skb_reserve(skb, self->client.max_header_size);
+ skb_put(skb, 2);
+
+ frame = skb->data;
+
+ frame[0] = CMD_GET_PROVIDER_INFO;
+ frame[1] = 0x00; /* Zero parameters */
+
+ irlan_ctrl_data_request(self, skb);
+}
+
+/*
+ * Function irlan_open_data_channel (self)
+ *
+ * Send an Open Data Command to provider
+ *
+ */
+void irlan_open_data_channel(struct irlan_cb *self)
+{
+ struct sk_buff *skb;
+ __u8 *frame;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ skb = dev_alloc_skb(64);
+ if (!skb)
+ return;
+
+ skb_reserve(skb, self->client.max_header_size);
+ skb_put(skb, 2);
+
+ frame = skb->data;
+
+ /* Build frame */
+ frame[0] = CMD_OPEN_DATA_CHANNEL;
+ frame[1] = 0x02; /* Two parameters */
+
+ irlan_insert_string_param(skb, "MEDIA", "802.3");
+ irlan_insert_string_param(skb, "ACCESS_TYPE", "DIRECT");
+ /* irlan_insert_string_param(skb, "MODE", "UNRELIABLE"); */
+
+/* self->use_udata = TRUE; */
+
+ irlan_ctrl_data_request(self, skb);
+}
+
+void irlan_close_data_channel(struct irlan_cb *self)
+{
+ struct sk_buff *skb;
+ __u8 *frame;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ /* Check if the TSAP is still there */
+ if (self->client.tsap_ctrl == NULL)
+ return;
+
+ skb = dev_alloc_skb(64);
+ if (!skb)
+ return;
+
+ skb_reserve(skb, self->client.max_header_size);
+ skb_put(skb, 2);
+
+ frame = skb->data;
+
+ /* Build frame */
+ frame[0] = CMD_CLOSE_DATA_CHAN;
+ frame[1] = 0x01; /* Two parameters */
+
+ irlan_insert_byte_param(skb, "DATA_CHAN", self->dtsap_sel_data);
+
+ irlan_ctrl_data_request(self, skb);
+}
+
+/*
+ * Function irlan_open_unicast_addr (self)
+ *
+ * Make IrLAN provider accept ethernet frames addressed to the unicast
+ * address.
+ *
+ */
+void irlan_open_unicast_addr(struct irlan_cb *self)
+{
+ struct sk_buff *skb;
+ __u8 *frame;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ skb = dev_alloc_skb(128);
+ if (!skb)
+ return;
+
+ /* Reserve space for TTP, LMP, and LAP header */
+ skb_reserve(skb, self->max_header_size);
+ skb_put(skb, 2);
+
+ frame = skb->data;
+
+ frame[0] = CMD_FILTER_OPERATION;
+ frame[1] = 0x03; /* Three parameters */
+ irlan_insert_byte_param(skb, "DATA_CHAN" , self->dtsap_sel_data);
+ irlan_insert_string_param(skb, "FILTER_TYPE", "DIRECTED");
+ irlan_insert_string_param(skb, "FILTER_MODE", "FILTER");
+
+ irlan_ctrl_data_request(self, skb);
+}
+
+/*
+ * Function irlan_set_broadcast_filter (self, status)
+ *
+ * Make IrLAN provider accept ethernet frames addressed to the broadcast
+ * address. Be careful with the use of this one, since there may be a lot
+ * of broadcast traffic out there. We can still function without this
+ * one but then _we_ have to initiate all communication with other
+ * hosts, since ARP request for this host will not be answered.
+ */
+void irlan_set_broadcast_filter(struct irlan_cb *self, int status)
+{
+ struct sk_buff *skb;
+ __u8 *frame;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ skb = dev_alloc_skb(128);
+ if (!skb)
+ return;
+
+ /* Reserve space for TTP, LMP, and LAP header */
+ skb_reserve(skb, self->client.max_header_size);
+ skb_put(skb, 2);
+
+ frame = skb->data;
+
+ frame[0] = CMD_FILTER_OPERATION;
+ frame[1] = 0x03; /* Three parameters */
+ irlan_insert_byte_param(skb, "DATA_CHAN", self->dtsap_sel_data);
+ irlan_insert_string_param(skb, "FILTER_TYPE", "BROADCAST");
+ if (status)
+ irlan_insert_string_param(skb, "FILTER_MODE", "FILTER");
+ else
+ irlan_insert_string_param(skb, "FILTER_MODE", "NONE");
+
+ irlan_ctrl_data_request(self, skb);
+}
+
+/*
+ * Function irlan_set_multicast_filter (self, status)
+ *
+ * Make IrLAN provider accept ethernet frames addressed to the multicast
+ * address.
+ *
+ */
+void irlan_set_multicast_filter(struct irlan_cb *self, int status)
+{
+ struct sk_buff *skb;
+ __u8 *frame;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ skb = dev_alloc_skb(128);
+ if (!skb)
+ return;
+
+ /* Reserve space for TTP, LMP, and LAP header */
+ skb_reserve(skb, self->client.max_header_size);
+ skb_put(skb, 2);
+
+ frame = skb->data;
+
+ frame[0] = CMD_FILTER_OPERATION;
+ frame[1] = 0x03; /* Three parameters */
+ irlan_insert_byte_param(skb, "DATA_CHAN", self->dtsap_sel_data);
+ irlan_insert_string_param(skb, "FILTER_TYPE", "MULTICAST");
+ if (status)
+ irlan_insert_string_param(skb, "FILTER_MODE", "ALL");
+ else
+ irlan_insert_string_param(skb, "FILTER_MODE", "NONE");
+
+ irlan_ctrl_data_request(self, skb);
+}
+
+/*
+ * Function irlan_get_unicast_addr (self)
+ *
+ * Retrives the unicast address from the IrLAN provider. This address
+ * will be inserted into the devices structure, so the ethernet layer
+ * can construct its packets.
+ *
+ */
+void irlan_get_unicast_addr(struct irlan_cb *self)
+{
+ struct sk_buff *skb;
+ __u8 *frame;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ skb = dev_alloc_skb(128);
+ if (!skb)
+ return;
+
+ /* Reserve space for TTP, LMP, and LAP header */
+ skb_reserve(skb, self->client.max_header_size);
+ skb_put(skb, 2);
+
+ frame = skb->data;
+
+ frame[0] = CMD_FILTER_OPERATION;
+ frame[1] = 0x03; /* Three parameters */
+ irlan_insert_byte_param(skb, "DATA_CHAN", self->dtsap_sel_data);
+ irlan_insert_string_param(skb, "FILTER_TYPE", "DIRECTED");
+ irlan_insert_string_param(skb, "FILTER_OPERATION", "DYNAMIC");
+
+ irlan_ctrl_data_request(self, skb);
+}
+
+/*
+ * Function irlan_get_media_char (self)
+ *
+ *
+ *
+ */
+void irlan_get_media_char(struct irlan_cb *self)
+{
+ struct sk_buff *skb;
+ __u8 *frame;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ skb = dev_alloc_skb(64);
+ if (!skb)
+ return;
+
+ /* Reserve space for TTP, LMP, and LAP header */
+ skb_reserve(skb, self->client.max_header_size);
+ skb_put(skb, 2);
+
+ frame = skb->data;
+
+ /* Build frame */
+ frame[0] = CMD_GET_MEDIA_CHAR;
+ frame[1] = 0x01; /* One parameter */
+
+ irlan_insert_string_param(skb, "MEDIA", "802.3");
+ irlan_ctrl_data_request(self, skb);
+}
+
+/*
+ * Function insert_byte_param (skb, param, value)
+ *
+ * Insert byte parameter into frame
+ *
+ */
+int irlan_insert_byte_param(struct sk_buff *skb, char *param, __u8 value)
+{
+ return __irlan_insert_param(skb, param, IRLAN_BYTE, value, 0, NULL, 0);
+}
+
+int irlan_insert_short_param(struct sk_buff *skb, char *param, __u16 value)
+{
+ return __irlan_insert_param(skb, param, IRLAN_SHORT, 0, value, NULL, 0);
+}
+
+/*
+ * Function insert_string (skb, param, value)
+ *
+ * Insert string parameter into frame
+ *
+ */
+int irlan_insert_string_param(struct sk_buff *skb, char *param, char *string)
+{
+ int string_len = strlen(string);
+
+ return __irlan_insert_param(skb, param, IRLAN_ARRAY, 0, 0, string,
+ string_len);
+}
+
+/*
+ * Function insert_array_param(skb, param, value, len_value)
+ *
+ * Insert array parameter into frame
+ *
+ */
+int irlan_insert_array_param(struct sk_buff *skb, char *name, __u8 *array,
+ __u16 array_len)
+{
+ return __irlan_insert_param(skb, name, IRLAN_ARRAY, 0, 0, array,
+ array_len);
+}
+
+/*
+ * Function insert_param (skb, param, value, byte)
+ *
+ * Insert parameter at end of buffer, structure of a parameter is:
+ *
+ * -----------------------------------------------------------------------
+ * | Name Length[1] | Param Name[1..255] | Val Length[2] | Value[0..1016]|
+ * -----------------------------------------------------------------------
+ */
+static int __irlan_insert_param(struct sk_buff *skb, char *param, int type,
+ __u8 value_byte, __u16 value_short,
+ __u8 *value_array, __u16 value_len)
+{
+ __u8 *frame;
+ __u8 param_len;
+ __u16 tmp_le; /* Temporary value in little endian format */
+ int n=0;
+
+ if (skb == NULL) {
+ IRDA_DEBUG(2, "%s(), Got NULL skb\n", __FUNCTION__);
+ return 0;
+ }
+
+ param_len = strlen(param);
+ switch (type) {
+ case IRLAN_BYTE:
+ value_len = 1;
+ break;
+ case IRLAN_SHORT:
+ value_len = 2;
+ break;
+ case IRLAN_ARRAY:
+ ASSERT(value_array != NULL, return 0;);
+ ASSERT(value_len > 0, return 0;);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown parameter type!\n", __FUNCTION__);
+ return 0;
+ break;
+ }
+
+ /* Insert at end of sk-buffer */
+ frame = skb->tail;
+
+ /* Make space for data */
+ if (skb_tailroom(skb) < (param_len+value_len+3)) {
+ IRDA_DEBUG(2, "%s(), No more space at end of skb\n", __FUNCTION__);
+ return 0;
+ }
+ skb_put(skb, param_len+value_len+3);
+
+ /* Insert parameter length */
+ frame[n++] = param_len;
+
+ /* Insert parameter */
+ memcpy(frame+n, param, param_len); n += param_len;
+
+ /* Insert value length (2 byte little endian format, LSB first) */
+ tmp_le = cpu_to_le16(value_len);
+ memcpy(frame+n, &tmp_le, 2); n += 2; /* To avoid alignment problems */
+
+ /* Insert value */
+ switch (type) {
+ case IRLAN_BYTE:
+ frame[n++] = value_byte;
+ break;
+ case IRLAN_SHORT:
+ tmp_le = cpu_to_le16(value_short);
+ memcpy(frame+n, &tmp_le, 2); n += 2;
+ break;
+ case IRLAN_ARRAY:
+ memcpy(frame+n, value_array, value_len); n+=value_len;
+ break;
+ default:
+ break;
+ }
+ ASSERT(n == (param_len+value_len+3), return 0;);
+
+ return param_len+value_len+3;
+}
+
+/*
+ * Function irlan_extract_param (buf, name, value, len)
+ *
+ * Extracts a single parameter name/value pair from buffer and updates
+ * the buffer pointer to point to the next name/value pair.
+ */
+int irlan_extract_param(__u8 *buf, char *name, char *value, __u16 *len)
+{
+ __u8 name_len;
+ __u16 val_len;
+ int n=0;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ /* get length of parameter name (1 byte) */
+ name_len = buf[n++];
+
+ if (name_len > 254) {
+ IRDA_DEBUG(2, "%s(), name_len > 254\n", __FUNCTION__);
+ return -RSP_INVALID_COMMAND_FORMAT;
+ }
+
+ /* get parameter name */
+ memcpy(name, buf+n, name_len);
+ name[name_len] = '\0';
+ n+=name_len;
+
+ /*
+ * Get length of parameter value (2 bytes in little endian
+ * format)
+ */
+ memcpy(&val_len, buf+n, 2); /* To avoid alignment problems */
+ le16_to_cpus(&val_len); n+=2;
+
+ if (val_len > 1016) {
+ IRDA_DEBUG(2, "%s(), parameter length to long\n", __FUNCTION__);
+ return -RSP_INVALID_COMMAND_FORMAT;
+ }
+ *len = val_len;
+
+ /* get parameter value */
+ memcpy(value, buf+n, val_len);
+ value[val_len] = '\0';
+ n+=val_len;
+
+ IRDA_DEBUG(4, "Parameter: %s ", name);
+ IRDA_DEBUG(4, "Value: %s\n", value);
+
+ return n;
+}
+
+#ifdef CONFIG_PROC_FS
+/*
+ * Function irlan_client_proc_read (buf, start, offset, len, unused)
+ *
+ * Give some info to the /proc file system
+ */
+static int irlan_proc_read(char *buf, char **start, off_t offset, int len)
+{
+ struct irlan_cb *self;
+ unsigned long flags;
+ ASSERT(irlan != NULL, return 0;);
+
+ save_flags(flags);
+ cli();
+
+ len = 0;
+
+ len += sprintf(buf+len, "IrLAN instances:\n");
+
+ self = (struct irlan_cb *) hashbin_get_first(irlan);
+ while (self != NULL) {
+ ASSERT(self->magic == IRLAN_MAGIC, break;);
+
+ len += sprintf(buf+len, "ifname: %s,\n",
+ self->dev.name);
+ len += sprintf(buf+len, "client state: %s, ",
+ irlan_state[ self->client.state]);
+ len += sprintf(buf+len, "provider state: %s,\n",
+ irlan_state[ self->provider.state]);
+ len += sprintf(buf+len, "saddr: %#08x, ",
+ self->saddr);
+ len += sprintf(buf+len, "daddr: %#08x\n",
+ self->daddr);
+ len += sprintf(buf+len, "version: %d.%d,\n",
+ self->version[1], self->version[0]);
+ len += sprintf(buf+len, "access type: %s\n",
+ irlan_access[self->client.access_type]);
+ len += sprintf(buf+len, "media: %s\n",
+ irlan_media[self->media]);
+
+ len += sprintf(buf+len, "local filter:\n");
+ len += sprintf(buf+len, "remote filter: ");
+ len += irlan_print_filter(self->client.filter_type,
+ buf+len);
+
+ len += sprintf(buf+len, "tx busy: %s\n",
+ netif_queue_stopped(&self->dev) ? "TRUE" : "FALSE");
+
+ len += sprintf(buf+len, "\n");
+
+ self = (struct irlan_cb *) hashbin_get_next(irlan);
+ }
+ restore_flags(flags);
+
+ return len;
+}
+#endif
+
+/*
+ * Function print_ret_code (code)
+ *
+ * Print return code of request to peer IrLAN layer.
+ *
+ */
+void print_ret_code(__u8 code)
+{
+ switch(code) {
+ case 0:
+ printk(KERN_INFO "Success\n");
+ break;
+ case 1:
+ WARNING("IrLAN: Insufficient resources\n");
+ break;
+ case 2:
+ WARNING("IrLAN: Invalid command format\n");
+ break;
+ case 3:
+ WARNING("IrLAN: Command not supported\n");
+ break;
+ case 4:
+ WARNING("IrLAN: Parameter not supported\n");
+ break;
+ case 5:
+ WARNING("IrLAN: Value not supported\n");
+ break;
+ case 6:
+ WARNING("IrLAN: Not open\n");
+ break;
+ case 7:
+ WARNING("IrLAN: Authentication required\n");
+ break;
+ case 8:
+ WARNING("IrLAN: Invalid password\n");
+ break;
+ case 9:
+ WARNING("IrLAN: Protocol error\n");
+ break;
+ case 255:
+ WARNING("IrLAN: Asynchronous status\n");
+ break;
+ }
+}
+
+void irlan_mod_inc_use_count(void)
+{
+#ifdef MODULE
+ MOD_INC_USE_COUNT;
+#endif
+}
+
+void irlan_mod_dec_use_count(void)
+{
+#ifdef MODULE
+ MOD_DEC_USE_COUNT;
+#endif
+}
+
+#ifdef MODULE
+
+MODULE_AUTHOR("Dag Brattli <dagb@cs.uit.no>");
+MODULE_DESCRIPTION("The Linux IrDA LAN protocol");
+MODULE_LICENSE("GPL");
+
+MODULE_PARM(eth, "i");
+MODULE_PARM_DESC(eth, "Name devices ethX (0) or irlanX (1)");
+MODULE_PARM(access, "i");
+MODULE_PARM_DESC(access, "Access type DIRECT=1, PEER=2, HOSTED=3");
+
+/*
+ * Function init_module (void)
+ *
+ * Initialize the IrLAN module, this function is called by the
+ * modprobe(1) program.
+ */
+int init_module(void)
+{
+ return irlan_init();
+}
+
+/*
+ * Function cleanup_module (void)
+ *
+ * Remove the IrLAN module, this function is called by the rmmod(1)
+ * program
+ */
+void cleanup_module(void)
+{
+ /* Free some memory */
+ irlan_cleanup();
+}
+
+#endif /* MODULE */
+
diff --git a/uClinux-2.4.31-uc0/net/irda/irlan/irlan_eth.c b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_eth.c
new file mode 100644
index 0000000..28441ce
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_eth.c
@@ -0,0 +1,411 @@
+/*********************************************************************
+ *
+ * Filename: irlan_eth.c
+ * Version:
+ * Description:
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Thu Oct 15 08:37:58 1998
+ * Modified at: Tue Mar 21 09:06:41 2000
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ * Sources: skeleton.c by Donald Becker <becker@CESDIS.gsfc.nasa.gov>
+ * slip.c by Laurence Culhane, <loz@holmes.demon.co.uk>
+ * Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
+ *
+ * Copyright (c) 1998-2000 Dag Brattli, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/if_arp.h>
+#include <linux/random.h>
+#include <linux/module.h>
+#include <net/arp.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irmod.h>
+#include <net/irda/irlan_common.h>
+#include <net/irda/irlan_client.h>
+#include <net/irda/irlan_event.h>
+#include <net/irda/irlan_eth.h>
+
+/*
+ * Function irlan_eth_init (dev)
+ *
+ * The network device initialization function.
+ *
+ */
+int irlan_eth_init(struct net_device *dev)
+{
+ struct irlan_cb *self;
+
+ IRDA_DEBUG(2,"%s()\n", __FUNCTION__);
+
+ ASSERT(dev != NULL, return -1;);
+
+ self = (struct irlan_cb *) dev->priv;
+
+ dev->open = irlan_eth_open;
+ dev->stop = irlan_eth_close;
+ dev->hard_start_xmit = irlan_eth_xmit;
+ dev->get_stats = irlan_eth_get_stats;
+ dev->set_multicast_list = irlan_eth_set_multicast_list;
+ SET_MODULE_OWNER(dev);
+
+ /* NETIF_F_DYNALLOC feature was set by irlan_eth_init() and would
+ * cause the unregister_netdev() to do asynch completion _and_
+ * kfree self->dev afterwards. Which is really bad because the
+ * netdevice was not allocated separately but is embedded in
+ * our control block and therefore gets freed with *self.
+ * The only reason why this would have been enabled is to hide
+ * some netdev refcount issues. If unregister_netdev() blocks
+ * forever, tell us about it... */
+ //dev->features |= NETIF_F_DYNALLOC;
+
+ ether_setup(dev);
+
+ /*
+ * Lets do all queueing in IrTTP instead of this device driver.
+ * Queueing here as well can introduce some strange latency
+ * problems, which we will avoid by setting the queue size to 0.
+ */
+ dev->tx_queue_len = 0;
+
+ if (self->provider.access_type == ACCESS_DIRECT) {
+ /*
+ * Since we are emulating an IrLAN sever we will have to
+ * give ourself an ethernet address!
+ */
+ dev->dev_addr[0] = 0x40;
+ dev->dev_addr[1] = 0x00;
+ dev->dev_addr[2] = 0x00;
+ dev->dev_addr[3] = 0x00;
+ get_random_bytes(dev->dev_addr+4, 1);
+ get_random_bytes(dev->dev_addr+5, 1);
+ }
+
+ return 0;
+}
+
+/*
+ * Function irlan_eth_open (dev)
+ *
+ * Network device has been opened by user
+ *
+ */
+int irlan_eth_open(struct net_device *dev)
+{
+ struct irlan_cb *self;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(dev != NULL, return -1;);
+
+ self = (struct irlan_cb *) dev->priv;
+
+ ASSERT(self != NULL, return -1;);
+
+ /* Ready to play! */
+ netif_stop_queue(dev); /* Wait until data link is ready */
+
+ /* We are now open, so time to do some work */
+ self->disconnect_reason = 0;
+ irlan_client_wakeup(self, self->saddr, self->daddr);
+
+ /* Make sure we have a hardware address before we return, so DHCP clients gets happy */
+ interruptible_sleep_on(&self->open_wait);
+
+ return 0;
+}
+
+/*
+ * Function irlan_eth_close (dev)
+ *
+ * Stop the ether network device, his function will usually be called by
+ * ifconfig down. We should now disconnect the link, We start the
+ * close timer, so that the instance will be removed if we are unable
+ * to discover the remote device after the disconnect.
+ */
+int irlan_eth_close(struct net_device *dev)
+{
+ struct irlan_cb *self = (struct irlan_cb *) dev->priv;
+ struct sk_buff *skb;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ /* Stop device */
+ netif_stop_queue(dev);
+
+ irlan_close_data_channel(self);
+ irlan_close_tsaps(self);
+
+ irlan_do_client_event(self, IRLAN_LMP_DISCONNECT, NULL);
+ irlan_do_provider_event(self, IRLAN_LMP_DISCONNECT, NULL);
+
+ /* Remove frames queued on the control channel */
+ while ((skb = skb_dequeue(&self->client.txq)))
+ dev_kfree_skb(skb);
+
+ self->client.tx_busy = 0;
+
+ return 0;
+}
+
+/*
+ * Function irlan_eth_tx (skb)
+ *
+ * Transmits ethernet frames over IrDA link.
+ *
+ */
+int irlan_eth_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct irlan_cb *self;
+ int ret;
+
+ self = (struct irlan_cb *) dev->priv;
+
+ ASSERT(self != NULL, return 0;);
+ ASSERT(self->magic == IRLAN_MAGIC, return 0;);
+
+ /* skb headroom large enough to contain all IrDA-headers? */
+ if ((skb_headroom(skb) < self->max_header_size) || (skb_shared(skb))) {
+ struct sk_buff *new_skb =
+ skb_realloc_headroom(skb, self->max_header_size);
+
+ /* We have to free the original skb anyway */
+ dev_kfree_skb(skb);
+
+ /* Did the realloc succeed? */
+ if (new_skb == NULL)
+ return 0;
+
+ /* Use the new skb instead */
+ skb = new_skb;
+ }
+
+ dev->trans_start = jiffies;
+
+ /* Now queue the packet in the transport layer */
+ if (self->use_udata)
+ ret = irttp_udata_request(self->tsap_data, skb);
+ else
+ ret = irttp_data_request(self->tsap_data, skb);
+
+ if (ret < 0) {
+ /*
+ * IrTTPs tx queue is full, so we just have to
+ * drop the frame! You might think that we should
+ * just return -1 and don't deallocate the frame,
+ * but that is dangerous since it's possible that
+ * we have replaced the original skb with a new
+ * one with larger headroom, and that would really
+ * confuse do_dev_queue_xmit() in dev.c! I have
+ * tried :-) DB
+ */
+ dev_kfree_skb(skb);
+ self->stats.tx_dropped++;
+ } else {
+ self->stats.tx_packets++;
+ self->stats.tx_bytes += skb->len;
+ }
+
+ return 0;
+}
+
+/*
+ * Function irlan_eth_receive (handle, skb)
+ *
+ * This function gets the data that is received on the data channel
+ *
+ */
+int irlan_eth_receive(void *instance, void *sap, struct sk_buff *skb)
+{
+ struct irlan_cb *self;
+
+ self = (struct irlan_cb *) instance;
+
+ if (skb == NULL) {
+ ++self->stats.rx_dropped;
+ return 0;
+ }
+ ASSERT(skb->len > 1, return 0;);
+
+ /*
+ * Adopt this frame! Important to set all these fields since they
+ * might have been previously set by the low level IrDA network
+ * device driver
+ */
+ skb->dev = &self->dev;
+ skb->protocol=eth_type_trans(skb, skb->dev); /* Remove eth header */
+
+ self->stats.rx_packets++;
+ self->stats.rx_bytes += skb->len;
+
+ netif_rx(skb); /* Eat it! */
+
+ return 0;
+}
+
+/*
+ * Function irlan_eth_flow (status)
+ *
+ * Do flow control between IP/Ethernet and IrLAN/IrTTP. This is done by
+ * controlling the queue stop/start.
+ */
+void irlan_eth_flow_indication(void *instance, void *sap, LOCAL_FLOW flow)
+{
+ struct irlan_cb *self;
+ struct net_device *dev;
+
+ self = (struct irlan_cb *) instance;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ dev = &self->dev;
+
+ ASSERT(dev != NULL, return;);
+
+ switch (flow) {
+ case FLOW_STOP:
+ netif_stop_queue(dev);
+ break;
+ case FLOW_START:
+ default:
+ /* Tell upper layers that its time to transmit frames again */
+ /* Schedule network layer */
+ netif_start_queue(dev);
+ break;
+ }
+}
+
+/*
+ * Function irlan_eth_rebuild_header (buff, dev, dest, skb)
+ *
+ * If we don't want to use ARP. Currently not used!!
+ *
+ */
+void irlan_eth_rebuild_header(void *buff, struct net_device *dev,
+ unsigned long dest, struct sk_buff *skb)
+{
+ struct ethhdr *eth = (struct ethhdr *) buff;
+
+ memcpy(eth->h_source, dev->dev_addr, dev->addr_len);
+ memcpy(eth->h_dest, dev->dev_addr, dev->addr_len);
+
+ /* return 0; */
+}
+
+/*
+ * Function irlan_etc_send_gratuitous_arp (dev)
+ *
+ * Send gratuitous ARP to announce that we have changed
+ * hardware address, so that all peers updates their ARP tables
+ */
+void irlan_eth_send_gratuitous_arp(struct net_device *dev)
+{
+ struct in_device *in_dev;
+
+ /*
+ * When we get a new MAC address do a gratuitous ARP. This
+ * is useful if we have changed access points on the same
+ * subnet.
+ */
+#ifdef CONFIG_INET
+ IRDA_DEBUG(4, "IrLAN: Sending gratuitous ARP\n");
+ in_dev = in_dev_get(dev);
+ if (in_dev == NULL)
+ return;
+ read_lock(&in_dev->lock);
+ if (in_dev->ifa_list)
+
+ arp_send(ARPOP_REQUEST, ETH_P_ARP,
+ in_dev->ifa_list->ifa_address,
+ dev,
+ in_dev->ifa_list->ifa_address,
+ NULL, dev->dev_addr, NULL);
+ read_unlock(&in_dev->lock);
+ in_dev_put(in_dev);
+#endif /* CONFIG_INET */
+}
+
+/*
+ * Function set_multicast_list (dev)
+ *
+ * Configure the filtering of the device
+ *
+ */
+#define HW_MAX_ADDRS 4 /* Must query to get it! */
+void irlan_eth_set_multicast_list(struct net_device *dev)
+{
+ struct irlan_cb *self;
+
+ self = dev->priv;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ /* Check if data channel has been connected yet */
+ if (self->client.state != IRLAN_DATA) {
+ IRDA_DEBUG(1, "%s(), delaying!\n", __FUNCTION__);
+ return;
+ }
+
+ if (dev->flags & IFF_PROMISC) {
+ /* Enable promiscuous mode */
+ WARNING("Promiscous mode not implemented by IrLAN!\n");
+ }
+ else if ((dev->flags & IFF_ALLMULTI) || dev->mc_count > HW_MAX_ADDRS) {
+ /* Disable promiscuous mode, use normal mode. */
+ IRDA_DEBUG(4, "%s(), Setting multicast filter\n", __FUNCTION__);
+ /* hardware_set_filter(NULL); */
+
+ irlan_set_multicast_filter(self, TRUE);
+ }
+ else if (dev->mc_count) {
+ IRDA_DEBUG(4, "%s(), Setting multicast filter\n", __FUNCTION__);
+ /* Walk the address list, and load the filter */
+ /* hardware_set_filter(dev->mc_list); */
+
+ irlan_set_multicast_filter(self, TRUE);
+ }
+ else {
+ IRDA_DEBUG(4, "%s(), Clearing multicast filter\n", __FUNCTION__);
+ irlan_set_multicast_filter(self, FALSE);
+ }
+
+ if (dev->flags & IFF_BROADCAST)
+ irlan_set_broadcast_filter(self, TRUE);
+ else
+ irlan_set_broadcast_filter(self, FALSE);
+}
+
+/*
+ * Function irlan_get_stats (dev)
+ *
+ * Get the current statistics for this device
+ *
+ */
+struct net_device_stats *irlan_eth_get_stats(struct net_device *dev)
+{
+ struct irlan_cb *self = (struct irlan_cb *) dev->priv;
+
+ ASSERT(self != NULL, return NULL;);
+ ASSERT(self->magic == IRLAN_MAGIC, return NULL;);
+
+ return &self->stats;
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/irlan/irlan_event.c b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_event.c
new file mode 100644
index 0000000..7865542
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_event.c
@@ -0,0 +1,60 @@
+/*********************************************************************
+ *
+ * Filename: irlan_event.c
+ * Version:
+ * Description:
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Tue Oct 20 09:10:16 1998
+ * Modified at: Sat Oct 30 12:59:01 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1998-1999 Dag Brattli, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <net/irda/irlan_event.h>
+
+char *irlan_state[] = {
+ "IRLAN_IDLE",
+ "IRLAN_QUERY",
+ "IRLAN_CONN",
+ "IRLAN_INFO",
+ "IRLAN_MEDIA",
+ "IRLAN_OPEN",
+ "IRLAN_WAIT",
+ "IRLAN_ARB",
+ "IRLAN_DATA",
+ "IRLAN_CLOSE",
+ "IRLAN_SYNC",
+};
+
+void irlan_next_client_state(struct irlan_cb *self, IRLAN_STATE state)
+{
+ IRDA_DEBUG(2, "%s(), %s\n", __FUNCTION__, irlan_state[state]);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ self->client.state = state;
+}
+
+void irlan_next_provider_state(struct irlan_cb *self, IRLAN_STATE state)
+{
+ IRDA_DEBUG(2, "%s(), %s\n", __FUNCTION__, irlan_state[state]);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ self->provider.state = state;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/irda/irlan/irlan_filter.c b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_filter.c
new file mode 100644
index 0000000..4f16f07
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_filter.c
@@ -0,0 +1,241 @@
+/*********************************************************************
+ *
+ * Filename: irlan_filter.c
+ * Version:
+ * Description:
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Fri Jan 29 11:16:38 1999
+ * Modified at: Sat Oct 30 12:58:45 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1998-1999 Dag Brattli, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/skbuff.h>
+#include <linux/random.h>
+
+#include <net/irda/irlan_common.h>
+
+/*
+ * Function handle_filter_request (self, skb)
+ *
+ * Handle filter request from client peer device
+ *
+ */
+void handle_filter_request(struct irlan_cb *self, struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ if ((self->provider.filter_type == IRLAN_DIRECTED) &&
+ (self->provider.filter_operation == DYNAMIC))
+ {
+ IRDA_DEBUG(0, "Giving peer a dynamic Ethernet address\n");
+ self->provider.mac_address[0] = 0x40;
+ self->provider.mac_address[1] = 0x00;
+ self->provider.mac_address[2] = 0x00;
+ self->provider.mac_address[3] = 0x00;
+
+ /* Use arbitration value to generate MAC address */
+ if (self->provider.access_type == ACCESS_PEER) {
+ self->provider.mac_address[4] =
+ self->provider.send_arb_val & 0xff;
+ self->provider.mac_address[5] =
+ (self->provider.send_arb_val >> 8) & 0xff;;
+ } else {
+ /* Just generate something for now */
+ get_random_bytes(self->provider.mac_address+4, 1);
+ get_random_bytes(self->provider.mac_address+5, 1);
+ }
+
+ skb->data[0] = 0x00; /* Success */
+ skb->data[1] = 0x03;
+ irlan_insert_string_param(skb, "FILTER_MODE", "NONE");
+ irlan_insert_short_param(skb, "MAX_ENTRY", 0x0001);
+ irlan_insert_array_param(skb, "FILTER_ENTRY",
+ self->provider.mac_address, 6);
+ return;
+ }
+
+ if ((self->provider.filter_type == IRLAN_DIRECTED) &&
+ (self->provider.filter_mode == FILTER))
+ {
+ IRDA_DEBUG(0, "Directed filter on\n");
+ skb->data[0] = 0x00; /* Success */
+ skb->data[1] = 0x00;
+ return;
+ }
+ if ((self->provider.filter_type == IRLAN_DIRECTED) &&
+ (self->provider.filter_mode == NONE))
+ {
+ IRDA_DEBUG(0, "Directed filter off\n");
+ skb->data[0] = 0x00; /* Success */
+ skb->data[1] = 0x00;
+ return;
+ }
+
+ if ((self->provider.filter_type == IRLAN_BROADCAST) &&
+ (self->provider.filter_mode == FILTER))
+ {
+ IRDA_DEBUG(0, "Broadcast filter on\n");
+ skb->data[0] = 0x00; /* Success */
+ skb->data[1] = 0x00;
+ return;
+ }
+ if ((self->provider.filter_type == IRLAN_BROADCAST) &&
+ (self->provider.filter_mode == NONE))
+ {
+ IRDA_DEBUG(0, "Broadcast filter off\n");
+ skb->data[0] = 0x00; /* Success */
+ skb->data[1] = 0x00;
+ return;
+ }
+ if ((self->provider.filter_type == IRLAN_MULTICAST) &&
+ (self->provider.filter_mode == FILTER))
+ {
+ IRDA_DEBUG(0, "Multicast filter on\n");
+ skb->data[0] = 0x00; /* Success */
+ skb->data[1] = 0x00;
+ return;
+ }
+ if ((self->provider.filter_type == IRLAN_MULTICAST) &&
+ (self->provider.filter_mode == NONE))
+ {
+ IRDA_DEBUG(0, "Multicast filter off\n");
+ skb->data[0] = 0x00; /* Success */
+ skb->data[1] = 0x00;
+ return;
+ }
+ if ((self->provider.filter_type == IRLAN_MULTICAST) &&
+ (self->provider.filter_operation == GET))
+ {
+ IRDA_DEBUG(0, "Multicast filter get\n");
+ skb->data[0] = 0x00; /* Success? */
+ skb->data[1] = 0x02;
+ irlan_insert_string_param(skb, "FILTER_MODE", "NONE");
+ irlan_insert_short_param(skb, "MAX_ENTRY", 16);
+ return;
+ }
+ skb->data[0] = 0x00; /* Command not supported */
+ skb->data[1] = 0x00;
+
+ IRDA_DEBUG(0, "Not implemented!\n");
+}
+
+/*
+ * Function check_request_param (self, param, value)
+ *
+ * Check parameters in request from peer device
+ *
+ */
+void irlan_check_command_param(struct irlan_cb *self, char *param, char *value)
+{
+ __u8 *bytes;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ bytes = value;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ IRDA_DEBUG(4, "%s, %s\n", param, value);
+
+ /*
+ * This is experimental!! DB.
+ */
+ if (strcmp(param, "MODE") == 0) {
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+ self->use_udata = TRUE;
+ return;
+ }
+
+ /*
+ * FILTER_TYPE
+ */
+ if (strcmp(param, "FILTER_TYPE") == 0) {
+ if (strcmp(value, "DIRECTED") == 0) {
+ self->provider.filter_type = IRLAN_DIRECTED;
+ return;
+ }
+ if (strcmp(value, "MULTICAST") == 0) {
+ self->provider.filter_type = IRLAN_MULTICAST;
+ return;
+ }
+ if (strcmp(value, "BROADCAST") == 0) {
+ self->provider.filter_type = IRLAN_BROADCAST;
+ return;
+ }
+ }
+ /*
+ * FILTER_MODE
+ */
+ if (strcmp(param, "FILTER_MODE") == 0) {
+ if (strcmp(value, "ALL") == 0) {
+ self->provider.filter_mode = ALL;
+ return;
+ }
+ if (strcmp(value, "FILTER") == 0) {
+ self->provider.filter_mode = FILTER;
+ return;
+ }
+ if (strcmp(value, "NONE") == 0) {
+ self->provider.filter_mode = FILTER;
+ return;
+ }
+ }
+ /*
+ * FILTER_OPERATION
+ */
+ if (strcmp(param, "FILTER_OPERATION") == 0) {
+ if (strcmp(value, "DYNAMIC") == 0) {
+ self->provider.filter_operation = DYNAMIC;
+ return;
+ }
+ if (strcmp(value, "GET") == 0) {
+ self->provider.filter_operation = GET;
+ return;
+ }
+ }
+}
+
+/*
+ * Function irlan_print_filter (filter_type, buf)
+ *
+ * Print status of filter. Used by /proc file system
+ *
+ */
+int irlan_print_filter(int filter_type, char *buf)
+{
+ int len = 0;
+
+ if (filter_type & IRLAN_DIRECTED)
+ len += sprintf(buf+len, "%s", "DIRECTED ");
+ if (filter_type & IRLAN_FUNCTIONAL)
+ len += sprintf(buf+len, "%s", "FUNCTIONAL ");
+ if (filter_type & IRLAN_GROUP)
+ len += sprintf(buf+len, "%s", "GROUP ");
+ if (filter_type & IRLAN_MAC_FRAME)
+ len += sprintf(buf+len, "%s", "MAC_FRAME ");
+ if (filter_type & IRLAN_MULTICAST)
+ len += sprintf(buf+len, "%s", "MULTICAST ");
+ if (filter_type & IRLAN_BROADCAST)
+ len += sprintf(buf+len, "%s", "BROADCAST ");
+ if (filter_type & IRLAN_IPX_SOCKET)
+ len += sprintf(buf+len, "%s", "IPX_SOCKET");
+
+ len += sprintf(buf+len, "\n");
+
+ return len;
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/irlan/irlan_provider.c b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_provider.c
new file mode 100644
index 0000000..2cbcd6a
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_provider.c
@@ -0,0 +1,413 @@
+/*********************************************************************
+ *
+ * Filename: irlan_provider.c
+ * Version: 0.9
+ * Description: IrDA LAN Access Protocol Implementation
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sun Aug 31 20:14:37 1997
+ * Modified at: Sat Oct 30 12:52:10 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ * Sources: skeleton.c by Donald Becker <becker@CESDIS.gsfc.nasa.gov>
+ * slip.c by Laurence Culhane, <loz@holmes.demon.co.uk>
+ * Fred N. van Kempen, <waltje@uwalt.nl.mugnet.org>
+ *
+ * Copyright (c) 1998-1999 Dag Brattli <dagb@cs.uit.no>,
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/init.h>
+#include <linux/random.h>
+
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <asm/byteorder.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irttp.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/irias_object.h>
+#include <net/irda/iriap.h>
+#include <net/irda/timer.h>
+
+#include <net/irda/irlan_common.h>
+#include <net/irda/irlan_eth.h>
+#include <net/irda/irlan_event.h>
+#include <net/irda/irlan_provider.h>
+#include <net/irda/irlan_filter.h>
+#include <net/irda/irlan_client.h>
+
+static void irlan_provider_connect_indication(void *instance, void *sap,
+ struct qos_info *qos,
+ __u32 max_sdu_size,
+ __u8 max_header_size,
+ struct sk_buff *skb);
+
+/*
+ * Function irlan_provider_control_data_indication (handle, skb)
+ *
+ * This function gets the data that is received on the control channel
+ *
+ */
+static int irlan_provider_data_indication(void *instance, void *sap,
+ struct sk_buff *skb)
+{
+ struct irlan_cb *self;
+ __u8 code;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ self = (struct irlan_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRLAN_MAGIC, return -1;);
+
+ ASSERT(skb != NULL, return -1;);
+
+ code = skb->data[0];
+ switch(code) {
+ case CMD_GET_PROVIDER_INFO:
+ IRDA_DEBUG(4, "Got GET_PROVIDER_INFO command!\n");
+ irlan_do_provider_event(self, IRLAN_GET_INFO_CMD, skb);
+ break;
+
+ case CMD_GET_MEDIA_CHAR:
+ IRDA_DEBUG(4, "Got GET_MEDIA_CHAR command!\n");
+ irlan_do_provider_event(self, IRLAN_GET_MEDIA_CMD, skb);
+ break;
+ case CMD_OPEN_DATA_CHANNEL:
+ IRDA_DEBUG(4, "Got OPEN_DATA_CHANNEL command!\n");
+ irlan_do_provider_event(self, IRLAN_OPEN_DATA_CMD, skb);
+ break;
+ case CMD_FILTER_OPERATION:
+ IRDA_DEBUG(4, "Got FILTER_OPERATION command!\n");
+ irlan_do_provider_event(self, IRLAN_FILTER_CONFIG_CMD, skb);
+ break;
+ case CMD_RECONNECT_DATA_CHAN:
+ IRDA_DEBUG(2, "%s(), Got RECONNECT_DATA_CHAN command\n", __FUNCTION__);
+ IRDA_DEBUG(2, "%s(), NOT IMPLEMENTED\n", __FUNCTION__);
+ break;
+ case CMD_CLOSE_DATA_CHAN:
+ IRDA_DEBUG(2, "Got CLOSE_DATA_CHAN command!\n");
+ IRDA_DEBUG(2, "%s(), NOT IMPLEMENTED\n", __FUNCTION__);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown command!\n", __FUNCTION__);
+ break;
+ }
+ return 0;
+}
+
+/*
+ * Function irlan_provider_connect_indication (handle, skb, priv)
+ *
+ * Got connection from peer IrLAN client
+ *
+ */
+static void irlan_provider_connect_indication(void *instance, void *sap,
+ struct qos_info *qos,
+ __u32 max_sdu_size,
+ __u8 max_header_size,
+ struct sk_buff *skb)
+{
+ struct irlan_cb *self;
+ struct tsap_cb *tsap;
+ __u32 saddr, daddr;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ self = (struct irlan_cb *) instance;
+ tsap = (struct tsap_cb *) sap;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ ASSERT(tsap == self->provider.tsap_ctrl,return;);
+ ASSERT(self->provider.state == IRLAN_IDLE, return;);
+
+ daddr = irttp_get_daddr(tsap);
+ saddr = irttp_get_saddr(tsap);
+ self->provider.max_sdu_size = max_sdu_size;
+ self->provider.max_header_size = max_header_size;
+
+ irlan_do_provider_event(self, IRLAN_CONNECT_INDICATION, NULL);
+
+ /*
+ * If we are in peer mode, the client may not have got the discovery
+ * indication it needs to make progress. If the client is still in
+ * IDLE state, we must kick it.
+ */
+ if ((self->provider.access_type == ACCESS_PEER) &&
+ (self->client.state == IRLAN_IDLE))
+ {
+ irlan_client_wakeup(self, self->saddr, self->daddr);
+ }
+}
+
+/*
+ * Function irlan_provider_connect_response (handle)
+ *
+ * Accept incoming connection
+ *
+ */
+void irlan_provider_connect_response(struct irlan_cb *self,
+ struct tsap_cb *tsap)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ /* Just accept */
+ irttp_connect_response(tsap, IRLAN_MTU, NULL);
+}
+
+void irlan_provider_disconnect_indication(void *instance, void *sap,
+ LM_REASON reason,
+ struct sk_buff *userdata)
+{
+ struct irlan_cb *self;
+ struct tsap_cb *tsap;
+
+ IRDA_DEBUG(4, "%s(), reason=%d\n", __FUNCTION__, reason);
+
+ self = (struct irlan_cb *) instance;
+ tsap = (struct tsap_cb *) sap;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+ ASSERT(tsap != NULL, return;);
+ ASSERT(tsap->magic == TTP_TSAP_MAGIC, return;);
+
+ ASSERT(tsap == self->provider.tsap_ctrl, return;);
+
+ irlan_do_provider_event(self, IRLAN_LMP_DISCONNECT, NULL);
+}
+
+/*
+ * Function irlan_parse_open_data_cmd (self, skb)
+ *
+ *
+ *
+ */
+int irlan_parse_open_data_cmd(struct irlan_cb *self, struct sk_buff *skb)
+{
+ int ret;
+
+ ret = irlan_provider_parse_command(self, CMD_OPEN_DATA_CHANNEL, skb);
+
+ /* Open data channel */
+ irlan_open_data_tsap(self);
+
+ return ret;
+}
+
+/*
+ * Function parse_command (skb)
+ *
+ * Extract all parameters from received buffer, then feed them to
+ * check_params for parsing
+ *
+ */
+int irlan_provider_parse_command(struct irlan_cb *self, int cmd,
+ struct sk_buff *skb)
+{
+ __u8 *frame;
+ __u8 *ptr;
+ int count;
+ __u16 val_len;
+ int i;
+ char *name;
+ char *value;
+ int ret = RSP_SUCCESS;
+
+ ASSERT(skb != NULL, return -RSP_PROTOCOL_ERROR;);
+
+ IRDA_DEBUG(4, "%s(), skb->len=%d\n", __FUNCTION__, (int)skb->len);
+
+ ASSERT(self != NULL, return -RSP_PROTOCOL_ERROR;);
+ ASSERT(self->magic == IRLAN_MAGIC, return -RSP_PROTOCOL_ERROR;);
+
+ if (!skb)
+ return -RSP_PROTOCOL_ERROR;
+
+ frame = skb->data;
+
+ name = kmalloc(255, GFP_ATOMIC);
+ if (!name)
+ return -RSP_INSUFFICIENT_RESOURCES;
+ value = kmalloc(1016, GFP_ATOMIC);
+ if (!value) {
+ kfree(name);
+ return -RSP_INSUFFICIENT_RESOURCES;
+ }
+
+ /* How many parameters? */
+ count = frame[1];
+
+ IRDA_DEBUG(4, "Got %d parameters\n", count);
+
+ ptr = frame+2;
+
+ /* For all parameters */
+ for (i=0; i<count;i++) {
+ ret = irlan_extract_param(ptr, name, value, &val_len);
+ if (ret < 0) {
+ IRDA_DEBUG(2, "%s(), IrLAN, Error!\n", __FUNCTION__);
+ break;
+ }
+ ptr+=ret;
+ ret = RSP_SUCCESS;
+ irlan_check_command_param(self, name, value);
+ }
+ /* Cleanup */
+ kfree(name);
+ kfree(value);
+
+ return ret;
+}
+
+/*
+ * Function irlan_provider_send_reply (self, info)
+ *
+ * Send reply to query to peer IrLAN layer
+ *
+ */
+void irlan_provider_send_reply(struct irlan_cb *self, int command,
+ int ret_code)
+{
+ struct sk_buff *skb;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == IRLAN_MAGIC, return;);
+
+ skb = dev_alloc_skb(128);
+ if (!skb)
+ return;
+
+ /* Reserve space for TTP, LMP, and LAP header */
+ skb_reserve(skb, self->provider.max_header_size);
+ skb_put(skb, 2);
+
+ switch (command) {
+ case CMD_GET_PROVIDER_INFO:
+ skb->data[0] = 0x00; /* Success */
+ skb->data[1] = 0x02; /* 2 parameters */
+ switch (self->media) {
+ case MEDIA_802_3:
+ irlan_insert_string_param(skb, "MEDIA", "802.3");
+ break;
+ case MEDIA_802_5:
+ irlan_insert_string_param(skb, "MEDIA", "802.5");
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), unknown media type!\n", __FUNCTION__);
+ break;
+ }
+ irlan_insert_short_param(skb, "IRLAN_VER", 0x0101);
+ break;
+
+ case CMD_GET_MEDIA_CHAR:
+ skb->data[0] = 0x00; /* Success */
+ skb->data[1] = 0x05; /* 5 parameters */
+ irlan_insert_string_param(skb, "FILTER_TYPE", "DIRECTED");
+ irlan_insert_string_param(skb, "FILTER_TYPE", "BROADCAST");
+ irlan_insert_string_param(skb, "FILTER_TYPE", "MULTICAST");
+
+ switch (self->provider.access_type) {
+ case ACCESS_DIRECT:
+ irlan_insert_string_param(skb, "ACCESS_TYPE", "DIRECT");
+ break;
+ case ACCESS_PEER:
+ irlan_insert_string_param(skb, "ACCESS_TYPE", "PEER");
+ break;
+ case ACCESS_HOSTED:
+ irlan_insert_string_param(skb, "ACCESS_TYPE", "HOSTED");
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown access type\n", __FUNCTION__);
+ break;
+ }
+ irlan_insert_short_param(skb, "MAX_FRAME", 0x05ee);
+ break;
+ case CMD_OPEN_DATA_CHANNEL:
+ skb->data[0] = 0x00; /* Success */
+ if (self->provider.send_arb_val) {
+ skb->data[1] = 0x03; /* 3 parameters */
+ irlan_insert_short_param(skb, "CON_ARB",
+ self->provider.send_arb_val);
+ } else
+ skb->data[1] = 0x02; /* 2 parameters */
+ irlan_insert_byte_param(skb, "DATA_CHAN", self->stsap_sel_data);
+ irlan_insert_array_param(skb, "RECONNECT_KEY", "LINUX RULES!",
+ 12);
+ break;
+ case CMD_FILTER_OPERATION:
+ handle_filter_request(self, skb);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown command!\n", __FUNCTION__);
+ break;
+ }
+
+ irttp_data_request(self->provider.tsap_ctrl, skb);
+}
+
+/*
+ * Function irlan_provider_register(void)
+ *
+ * Register provider support so we can accept incoming connections.
+ *
+ */
+int irlan_provider_open_ctrl_tsap(struct irlan_cb *self)
+{
+ struct tsap_cb *tsap;
+ notify_t notify;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRLAN_MAGIC, return -1;);
+
+ /* Check if already open */
+ if (self->provider.tsap_ctrl)
+ return -1;
+
+ /*
+ * First register well known control TSAP
+ */
+ irda_notify_init(&notify);
+ notify.data_indication = irlan_provider_data_indication;
+ notify.connect_indication = irlan_provider_connect_indication;
+ notify.disconnect_indication = irlan_provider_disconnect_indication;
+ notify.instance = self;
+ strncpy(notify.name, "IrLAN ctrl (p)", 16);
+
+ tsap = irttp_open_tsap(LSAP_ANY, 1, &notify);
+ if (!tsap) {
+ IRDA_DEBUG(2, "%s(), Got no tsap!\n", __FUNCTION__);
+ return -1;
+ }
+ self->provider.tsap_ctrl = tsap;
+
+ /* Register with LM-IAS */
+ irlan_ias_register(self, tsap->stsap_sel);
+
+ return 0;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/irda/irlan/irlan_provider_event.c b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_provider_event.c
new file mode 100644
index 0000000..57ef738
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlan/irlan_provider_event.c
@@ -0,0 +1,241 @@
+/*********************************************************************
+ *
+ * Filename: irlan_provider_event.c
+ * Version: 0.9
+ * Description: IrLAN provider state machine)
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sun Aug 31 20:14:37 1997
+ * Modified at: Sat Oct 30 12:52:41 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1998-1999 Dag Brattli <dagb@cs.uit.no>, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <net/irda/irda.h>
+#include <net/irda/iriap.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/irttp.h>
+
+#include <net/irda/irlan_provider.h>
+#include <net/irda/irlan_event.h>
+
+static int irlan_provider_state_idle(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+static int irlan_provider_state_info(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+static int irlan_provider_state_open(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+static int irlan_provider_state_data(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb);
+
+static int (*state[])(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb) =
+{
+ irlan_provider_state_idle,
+ NULL, /* Query */
+ NULL, /* Info */
+ irlan_provider_state_info,
+ NULL, /* Media */
+ irlan_provider_state_open,
+ NULL, /* Wait */
+ NULL, /* Arb */
+ irlan_provider_state_data,
+ NULL, /* Close */
+ NULL, /* Sync */
+};
+
+void irlan_do_provider_event(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ ASSERT(*state[ self->provider.state] != NULL, return;);
+
+ (*state[self->provider.state]) (self, event, skb);
+}
+
+/*
+ * Function irlan_provider_state_idle (event, skb, info)
+ *
+ * IDLE, We are waiting for an indication that there is a provider
+ * available.
+ */
+static int irlan_provider_state_idle(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+
+ switch(event) {
+ case IRLAN_CONNECT_INDICATION:
+ irlan_provider_connect_response( self, self->provider.tsap_ctrl);
+ irlan_next_provider_state( self, IRLAN_INFO);
+ break;
+ default:
+ IRDA_DEBUG(4, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/*
+ * Function irlan_provider_state_info (self, event, skb, info)
+ *
+ * INFO, We have issued a GetInfo command and is awaiting a reply.
+ */
+static int irlan_provider_state_info(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ int ret;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+
+ switch(event) {
+ case IRLAN_GET_INFO_CMD:
+ /* Be sure to use 802.3 in case of peer mode */
+ if (self->provider.access_type == ACCESS_PEER) {
+ self->media = MEDIA_802_3;
+
+ /* Check if client has started yet */
+ if (self->client.state == IRLAN_IDLE) {
+ /* This should get the client going */
+ irlmp_discovery_request(8);
+ }
+ }
+
+ irlan_provider_send_reply(self, CMD_GET_PROVIDER_INFO,
+ RSP_SUCCESS);
+ /* Keep state */
+ break;
+ case IRLAN_GET_MEDIA_CMD:
+ irlan_provider_send_reply(self, CMD_GET_MEDIA_CHAR,
+ RSP_SUCCESS);
+ /* Keep state */
+ break;
+ case IRLAN_OPEN_DATA_CMD:
+ ret = irlan_parse_open_data_cmd(self, skb);
+ if (self->provider.access_type == ACCESS_PEER) {
+ /* FIXME: make use of random functions! */
+ self->provider.send_arb_val = (jiffies & 0xffff);
+ }
+ irlan_provider_send_reply(self, CMD_OPEN_DATA_CHANNEL, ret);
+
+ if (ret == RSP_SUCCESS) {
+ irlan_next_provider_state(self, IRLAN_OPEN);
+
+ /* Signal client that we are now open */
+ irlan_do_client_event(self, IRLAN_PROVIDER_SIGNAL, NULL);
+ }
+ break;
+ case IRLAN_LMP_DISCONNECT: /* FALLTHROUGH */
+ case IRLAN_LAP_DISCONNECT:
+ irlan_next_provider_state(self, IRLAN_IDLE);
+ break;
+ default:
+ IRDA_DEBUG( 0, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/*
+ * Function irlan_provider_state_open (self, event, skb, info)
+ *
+ * OPEN, The client has issued a OpenData command and is awaiting a
+ * reply
+ *
+ */
+static int irlan_provider_state_open(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+
+ switch(event) {
+ case IRLAN_FILTER_CONFIG_CMD:
+ irlan_provider_parse_command(self, CMD_FILTER_OPERATION, skb);
+ irlan_provider_send_reply(self, CMD_FILTER_OPERATION,
+ RSP_SUCCESS);
+ /* Keep state */
+ break;
+ case IRLAN_DATA_CONNECT_INDICATION:
+ irlan_next_provider_state(self, IRLAN_DATA);
+ irlan_provider_connect_response(self, self->tsap_data);
+ break;
+ case IRLAN_LMP_DISCONNECT: /* FALLTHROUGH */
+ case IRLAN_LAP_DISCONNECT:
+ irlan_next_provider_state(self, IRLAN_IDLE);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+/*
+ * Function irlan_provider_state_data (self, event, skb, info)
+ *
+ * DATA, The data channel is connected, allowing data transfers between
+ * the local and remote machines.
+ *
+ */
+static int irlan_provider_state_data(struct irlan_cb *self, IRLAN_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == IRLAN_MAGIC, return -1;);
+
+ switch(event) {
+ case IRLAN_FILTER_CONFIG_CMD:
+ irlan_provider_parse_command(self, CMD_FILTER_OPERATION, skb);
+ irlan_provider_send_reply(self, CMD_FILTER_OPERATION,
+ RSP_SUCCESS);
+ break;
+ case IRLAN_LMP_DISCONNECT: /* FALLTHROUGH */
+ case IRLAN_LAP_DISCONNECT:
+ irlan_next_provider_state(self, IRLAN_IDLE);
+ break;
+ default:
+ IRDA_DEBUG( 0, "%s(), Unknown event %d\n", __FUNCTION__, event);
+ break;
+ }
+ if (skb)
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/uClinux-2.4.31-uc0/net/irda/irlap.c b/uClinux-2.4.31-uc0/net/irda/irlap.c
new file mode 100644
index 0000000..0afbed9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlap.c
@@ -0,0 +1,1183 @@
+/*********************************************************************
+ *
+ * Filename: irlap.c
+ * Version: 1.0
+ * Description: IrLAP implementation for Linux
+ * Status: Stable
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Mon Aug 4 20:40:53 1997
+ * Modified at: Tue Dec 14 09:26:44 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1998-1999 Dag Brattli, All Rights Reserved.
+ * Copyright (c) 2000-2001 Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/skbuff.h>
+#include <linux/delay.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <linux/random.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irda_device.h>
+#include <net/irda/irqueue.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/irlmp_frame.h>
+#include <net/irda/irlap_frame.h>
+#include <net/irda/irlap.h>
+#include <net/irda/timer.h>
+#include <net/irda/qos.h>
+
+hashbin_t *irlap = NULL;
+int sysctl_slot_timeout = SLOT_TIMEOUT * 1000 / HZ;
+
+/* This is the delay of missed pf period before generating an event
+ * to the application. The spec mandate 3 seconds, but in some cases
+ * it's way too long. - Jean II */
+int sysctl_warn_noreply_time = 3;
+
+extern void irlap_queue_xmit(struct irlap_cb *self, struct sk_buff *skb);
+static void __irlap_close(struct irlap_cb *self);
+
+#ifdef CONFIG_IRDA_DEBUG
+static char *lap_reasons[] = {
+ "ERROR, NOT USED",
+ "LAP_DISC_INDICATION",
+ "LAP_NO_RESPONSE",
+ "LAP_RESET_INDICATION",
+ "LAP_FOUND_NONE",
+ "LAP_MEDIA_BUSY",
+ "LAP_PRIMARY_CONFLICT",
+ "ERROR, NOT USED",
+};
+#endif /* CONFIG_IRDA_DEBUG */
+
+#ifdef CONFIG_PROC_FS
+int irlap_proc_read(char *, char **, off_t, int);
+
+#endif /* CONFIG_PROC_FS */
+
+int __init irlap_init(void)
+{
+ /* Allocate master array */
+ irlap = hashbin_new(HB_LOCAL);
+ if (irlap == NULL) {
+ ERROR("%s(), can't allocate irlap hashbin!\n", __FUNCTION__);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+void irlap_cleanup(void)
+{
+ ASSERT(irlap != NULL, return;);
+
+ hashbin_delete(irlap, (FREE_FUNC) __irlap_close);
+}
+
+/*
+ * Function irlap_open (driver)
+ *
+ * Initialize IrLAP layer
+ *
+ */
+struct irlap_cb *irlap_open(struct net_device *dev, struct qos_info *qos,
+ char * hw_name)
+{
+ struct irlap_cb *self;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ /* Initialize the irlap structure. */
+ self = kmalloc(sizeof(struct irlap_cb), GFP_KERNEL);
+ if (self == NULL)
+ return NULL;
+
+ memset(self, 0, sizeof(struct irlap_cb));
+ self->magic = LAP_MAGIC;
+
+ /* Make a binding between the layers */
+ self->netdev = dev;
+ self->qos_dev = qos;
+ /* Copy hardware name */
+ if(hw_name != NULL) {
+ strncpy(self->hw_name, hw_name, 2*IFNAMSIZ);
+ self->hw_name[2*IFNAMSIZ] = '\0';
+ } else {
+ self->hw_name[0] = '\0';
+ }
+
+ /* FIXME: should we get our own field? */
+ dev->atalk_ptr = self;
+
+ self->state = LAP_OFFLINE;
+
+ /* Initialize transmit queue */
+ skb_queue_head_init(&self->txq);
+ skb_queue_head_init(&self->txq_ultra);
+ skb_queue_head_init(&self->wx_list);
+
+ /* My unique IrLAP device address! */
+ get_random_bytes(&self->saddr, sizeof(self->saddr));
+ memcpy(dev->dev_addr, &self->saddr, 4);
+
+ init_timer(&self->slot_timer);
+ init_timer(&self->query_timer);
+ init_timer(&self->discovery_timer);
+ init_timer(&self->final_timer);
+ init_timer(&self->poll_timer);
+ init_timer(&self->wd_timer);
+ init_timer(&self->backoff_timer);
+ init_timer(&self->media_busy_timer);
+
+ irlap_apply_default_connection_parameters(self);
+
+ self->N3 = 3; /* # connections attemts to try before giving up */
+
+ self->state = LAP_NDM;
+
+ hashbin_insert(irlap, (irda_queue_t *) self, self->saddr, NULL);
+
+ irlmp_register_link(self, self->saddr, &self->notify);
+
+ return self;
+}
+
+/*
+ * Function __irlap_close (self)
+ *
+ * Remove IrLAP and all allocated memory. Stop any pending timers.
+ *
+ */
+static void __irlap_close(struct irlap_cb *self)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ /* Stop timers */
+ del_timer(&self->slot_timer);
+ del_timer(&self->query_timer);
+ del_timer(&self->discovery_timer);
+ del_timer(&self->final_timer);
+ del_timer(&self->poll_timer);
+ del_timer(&self->wd_timer);
+ del_timer(&self->backoff_timer);
+ del_timer(&self->media_busy_timer);
+
+ irlap_flush_all_queues(self);
+
+ self->magic = 0;
+
+ kfree(self);
+}
+
+/*
+ * Function irlap_close (self)
+ *
+ * Remove IrLAP instance
+ *
+ */
+void irlap_close(struct irlap_cb *self)
+{
+ struct irlap_cb *lap;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ irlap_disconnect_indication(self, LAP_DISC_INDICATION);
+
+ irlmp_unregister_link(self->saddr);
+ self->notify.instance = NULL;
+
+ /* Be sure that we manage to remove ourself from the hash */
+ lap = hashbin_remove(irlap, self->saddr, NULL);
+ if (!lap) {
+ IRDA_DEBUG(1, "%s(), Didn't find myself!\n", __FUNCTION__);
+ return;
+ }
+ __irlap_close(lap);
+}
+
+/*
+ * Function irlap_connect_indication (self, skb)
+ *
+ * Another device is attempting to make a connection
+ *
+ */
+void irlap_connect_indication(struct irlap_cb *self, struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ irlap_init_qos_capabilities(self, NULL); /* No user QoS! */
+
+ skb_get(skb); /*LEVEL4*/
+ irlmp_link_connect_indication(self->notify.instance, self->saddr,
+ self->daddr, &self->qos_tx, skb);
+}
+
+/*
+ * Function irlap_connect_response (self, skb)
+ *
+ * Service user has accepted incoming connection
+ *
+ */
+void irlap_connect_response(struct irlap_cb *self, struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ irlap_do_event(self, CONNECT_RESPONSE, skb, NULL);
+ kfree_skb(skb);
+}
+
+/*
+ * Function irlap_connect_request (self, daddr, qos_user, sniff)
+ *
+ * Request connection with another device, sniffing is not implemented
+ * yet.
+ *
+ */
+void irlap_connect_request(struct irlap_cb *self, __u32 daddr,
+ struct qos_info *qos_user, int sniff)
+{
+ IRDA_DEBUG(3, "%s(), daddr=0x%08x\n", __FUNCTION__, daddr);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ self->daddr = daddr;
+
+ /*
+ * If the service user specifies QoS values for this connection,
+ * then use them
+ */
+ irlap_init_qos_capabilities(self, qos_user);
+
+ if ((self->state == LAP_NDM) && !self->media_busy)
+ irlap_do_event(self, CONNECT_REQUEST, NULL, NULL);
+ else
+ self->connect_pending = TRUE;
+}
+
+/*
+ * Function irlap_connect_confirm (self, skb)
+ *
+ * Connection request has been accepted
+ *
+ */
+void irlap_connect_confirm(struct irlap_cb *self, struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ skb_get(skb); /*LEVEL4*/
+ irlmp_link_connect_confirm(self->notify.instance, &self->qos_tx, skb);
+}
+
+/*
+ * Function irlap_data_indication (self, skb)
+ *
+ * Received data frames from IR-port, so we just pass them up to
+ * IrLMP for further processing
+ *
+ */
+void irlap_data_indication(struct irlap_cb *self, struct sk_buff *skb,
+ int unreliable)
+{
+ /* Hide LAP header from IrLMP layer */
+ skb_pull(skb, LAP_ADDR_HEADER+LAP_CTRL_HEADER);
+
+ skb_get(skb); /*LEVEL4*/
+ irlmp_link_data_indication(self->notify.instance, skb, unreliable);
+}
+
+
+/*
+ * Function irlap_data_request (self, skb)
+ *
+ * Queue data for transmission, must wait until XMIT state
+ *
+ */
+void irlap_data_request(struct irlap_cb *self, struct sk_buff *skb,
+ int unreliable)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+ ASSERT(skb_headroom(skb) >= (LAP_ADDR_HEADER+LAP_CTRL_HEADER),
+ return;);
+ skb_push(skb, LAP_ADDR_HEADER+LAP_CTRL_HEADER);
+
+ /*
+ * Must set frame format now so that the rest of the code knows
+ * if its dealing with an I or an UI frame
+ */
+ if (unreliable)
+ skb->data[1] = UI_FRAME;
+ else
+ skb->data[1] = I_FRAME;
+
+ /* Add at the end of the queue (keep ordering) - Jean II */
+ skb_queue_tail(&self->txq, skb);
+
+ /*
+ * Send event if this frame only if we are in the right state
+ * FIXME: udata should be sent first! (skb_queue_head?)
+ */
+ if ((self->state == LAP_XMIT_P) || (self->state == LAP_XMIT_S)) {
+ /* If we are not already processing the Tx queue, trigger
+ * transmission immediately - Jean II */
+ if((skb_queue_len(&self->txq) <= 1) && (!self->local_busy))
+ irlap_do_event(self, DATA_REQUEST, skb, NULL);
+ /* Otherwise, the packets will be sent normally at the
+ * next pf-poll - Jean II */
+ }
+}
+
+/*
+ * Function irlap_unitdata_request (self, skb)
+ *
+ * Send Ultra data. This is data that must be sent outside any connection
+ *
+ */
+#ifdef CONFIG_IRDA_ULTRA
+void irlap_unitdata_request(struct irlap_cb *self, struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+ ASSERT(skb_headroom(skb) >= (LAP_ADDR_HEADER+LAP_CTRL_HEADER),
+ return;);
+ skb_push(skb, LAP_ADDR_HEADER+LAP_CTRL_HEADER);
+
+ skb->data[0] = CBROADCAST;
+ skb->data[1] = UI_FRAME;
+
+ skb_queue_tail(&self->txq_ultra, skb);
+
+ irlap_do_event(self, SEND_UI_FRAME, NULL, NULL);
+}
+#endif /*CONFIG_IRDA_ULTRA */
+
+/*
+ * Function irlap_udata_indication (self, skb)
+ *
+ * Receive Ultra data. This is data that is received outside any connection
+ *
+ */
+#ifdef CONFIG_IRDA_ULTRA
+void irlap_unitdata_indication(struct irlap_cb *self, struct sk_buff *skb)
+{
+ IRDA_DEBUG(1, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+
+ /* Hide LAP header from IrLMP layer */
+ skb_pull(skb, LAP_ADDR_HEADER+LAP_CTRL_HEADER);
+
+ skb_get(skb); /*LEVEL4*/
+ irlmp_link_unitdata_indication(self->notify.instance, skb);
+}
+#endif /* CONFIG_IRDA_ULTRA */
+
+/*
+ * Function irlap_disconnect_request (void)
+ *
+ * Request to disconnect connection by service user
+ */
+void irlap_disconnect_request(struct irlap_cb *self)
+{
+ IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ /* Don't disconnect until all data frames are successfully sent */
+ if (skb_queue_len(&self->txq) > 0) {
+ self->disconnect_pending = TRUE;
+
+ return;
+ }
+
+ /* Check if we are in the right state for disconnecting */
+ switch (self->state) {
+ case LAP_XMIT_P: /* FALLTROUGH */
+ case LAP_XMIT_S: /* FALLTROUGH */
+ case LAP_CONN: /* FALLTROUGH */
+ case LAP_RESET_WAIT: /* FALLTROUGH */
+ case LAP_RESET_CHECK:
+ irlap_do_event(self, DISCONNECT_REQUEST, NULL, NULL);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), disconnect pending!\n", __FUNCTION__);
+ self->disconnect_pending = TRUE;
+ break;
+ }
+}
+
+/*
+ * Function irlap_disconnect_indication (void)
+ *
+ * Disconnect request from other device
+ *
+ */
+void irlap_disconnect_indication(struct irlap_cb *self, LAP_REASON reason)
+{
+ IRDA_DEBUG(1, "%s(), reason=%s\n", __FUNCTION__, lap_reasons[reason]);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ /* Flush queues */
+ irlap_flush_all_queues(self);
+
+ switch (reason) {
+ case LAP_RESET_INDICATION:
+ IRDA_DEBUG(1, "%s(), Sending reset request!\n", __FUNCTION__);
+ irlap_do_event(self, RESET_REQUEST, NULL, NULL);
+ break;
+ case LAP_NO_RESPONSE: /* FALLTROUGH */
+ case LAP_DISC_INDICATION: /* FALLTROUGH */
+ case LAP_FOUND_NONE: /* FALLTROUGH */
+ case LAP_MEDIA_BUSY:
+ irlmp_link_disconnect_indication(self->notify.instance, self,
+ reason, NULL);
+ break;
+ default:
+ ERROR("%s(), Unknown reason %d\n", __FUNCTION__, reason);
+ }
+}
+
+/*
+ * Function irlap_discovery_request (gen_addr_bit)
+ *
+ * Start one single discovery operation.
+ *
+ */
+void irlap_discovery_request(struct irlap_cb *self, discovery_t *discovery)
+{
+ struct irlap_info info;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+ ASSERT(discovery != NULL, return;);
+
+ IRDA_DEBUG(4, "%s(), nslots = %d\n", __FUNCTION__, discovery->nslots);
+
+ ASSERT((discovery->nslots == 1) || (discovery->nslots == 6) ||
+ (discovery->nslots == 8) || (discovery->nslots == 16),
+ return;);
+
+ /* Discovery is only possible in NDM mode */
+ if (self->state != LAP_NDM) {
+ IRDA_DEBUG(4, "%s(), discovery only possible in NDM mode\n",
+ __FUNCTION__);
+ irlap_discovery_confirm(self, NULL);
+ /* Note : in theory, if we are not in NDM, we could postpone
+ * the discovery like we do for connection request.
+ * In practice, it's not worth it. If the media was busy,
+ * it's likely next time around it won't be busy. If we are
+ * in REPLY state, we will get passive discovery info & event.
+ * Jean II */
+ return;
+ }
+
+ /* Check if last discovery request finished in time, or if
+ * it was aborted due to the media busy flag. */
+ if (self->discovery_log != NULL) {
+ hashbin_delete(self->discovery_log, (FREE_FUNC) kfree);
+ self->discovery_log = NULL;
+ }
+
+ self->discovery_log= hashbin_new(HB_LOCAL);
+
+ info.S = discovery->nslots; /* Number of slots */
+ info.s = 0; /* Current slot */
+
+ self->discovery_cmd = discovery;
+ info.discovery = discovery;
+
+ /* sysctl_slot_timeout bounds are checked in irsysctl.c - Jean II */
+ self->slot_timeout = sysctl_slot_timeout * HZ / 1000;
+
+ irlap_do_event(self, DISCOVERY_REQUEST, NULL, &info);
+}
+
+/*
+ * Function irlap_discovery_confirm (log)
+ *
+ * A device has been discovered in front of this station, we
+ * report directly to LMP.
+ */
+void irlap_discovery_confirm(struct irlap_cb *self, hashbin_t *discovery_log)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ ASSERT(self->notify.instance != NULL, return;);
+
+ /*
+ * Check for successful discovery, since we are then allowed to clear
+ * the media busy condition (IrLAP 6.13.4 - p.94). This should allow
+ * us to make connection attempts much faster and easier (i.e. no
+ * collisions).
+ * Setting media busy to false will also generate an event allowing
+ * to process pending events in NDM state machine.
+ * Note : the spec doesn't define what's a successful discovery is.
+ * If we want Ultra to work, it's successful even if there is
+ * nobody discovered - Jean II
+ */
+ if (discovery_log)
+ irda_device_set_media_busy(self->netdev, FALSE);
+
+ /* Inform IrLMP */
+ irlmp_link_discovery_confirm(self->notify.instance, discovery_log);
+}
+
+/*
+ * Function irlap_discovery_indication (log)
+ *
+ * Somebody is trying to discover us!
+ *
+ */
+void irlap_discovery_indication(struct irlap_cb *self, discovery_t *discovery)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+ ASSERT(discovery != NULL, return;);
+
+ ASSERT(self->notify.instance != NULL, return;);
+
+ /* A device is very likely to connect immediately after it performs
+ * a successful discovery. This means that in our case, we are much
+ * more likely to receive a connection request over the medium.
+ * So, we backoff to avoid collisions.
+ * IrLAP spec 6.13.4 suggest 100ms...
+ * Note : this little trick actually make a *BIG* difference. If I set
+ * my Linux box with discovery enabled and one Ultra frame sent every
+ * second, my Palm has no trouble connecting to it every time !
+ * Jean II */
+ irda_device_set_media_busy(self->netdev, SMALL);
+
+ irlmp_link_discovery_indication(self->notify.instance, discovery);
+}
+
+/*
+ * Function irlap_status_indication (quality_of_link)
+ *
+ *
+ *
+ */
+void irlap_status_indication(struct irlap_cb *self, int quality_of_link)
+{
+ switch (quality_of_link) {
+ case STATUS_NO_ACTIVITY:
+ MESSAGE("IrLAP, no activity on link!\n");
+ break;
+ case STATUS_NOISY:
+ MESSAGE("IrLAP, noisy link!\n");
+ break;
+ default:
+ break;
+ }
+ irlmp_status_indication(self->notify.instance,
+ quality_of_link, LOCK_NO_CHANGE);
+}
+
+/*
+ * Function irlap_reset_indication (void)
+ *
+ *
+ *
+ */
+void irlap_reset_indication(struct irlap_cb *self)
+{
+ IRDA_DEBUG(1, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ if (self->state == LAP_RESET_WAIT)
+ irlap_do_event(self, RESET_REQUEST, NULL, NULL);
+ else
+ irlap_do_event(self, RESET_RESPONSE, NULL, NULL);
+}
+
+/*
+ * Function irlap_reset_confirm (void)
+ *
+ *
+ *
+ */
+void irlap_reset_confirm(void)
+{
+ IRDA_DEBUG(1, "%s()\n", __FUNCTION__);
+}
+
+/*
+ * Function irlap_generate_rand_time_slot (S, s)
+ *
+ * Generate a random time slot between s and S-1 where
+ * S = Number of slots (0 -> S-1)
+ * s = Current slot
+ */
+int irlap_generate_rand_time_slot(int S, int s)
+{
+ static int rand;
+ int slot;
+
+ ASSERT((S - s) > 0, return 0;);
+
+ rand += jiffies;
+ rand ^= (rand << 12);
+ rand ^= (rand >> 20);
+
+ slot = s + rand % (S-s);
+
+ ASSERT((slot >= s) || (slot < S), return 0;);
+
+ return slot;
+}
+
+/*
+ * Function irlap_update_nr_received (nr)
+ *
+ * Remove all acknowledged frames in current window queue. This code is
+ * not intuitive and you should not try to change it. If you think it
+ * contains bugs, please mail a patch to the author instead.
+ */
+void irlap_update_nr_received(struct irlap_cb *self, int nr)
+{
+ struct sk_buff *skb = NULL;
+ int count = 0;
+
+ /*
+ * Remove all the ack-ed frames from the window queue.
+ */
+
+ /*
+ * Optimize for the common case. It is most likely that the receiver
+ * will acknowledge all the frames we have sent! So in that case we
+ * delete all frames stored in window.
+ */
+ if (nr == self->vs) {
+ while ((skb = skb_dequeue(&self->wx_list)) != NULL) {
+ dev_kfree_skb(skb);
+ }
+ /* The last acked frame is the next to send minus one */
+ self->va = nr - 1;
+ } else {
+ /* Remove all acknowledged frames in current window */
+ while ((skb_peek(&self->wx_list) != NULL) &&
+ (((self->va+1) % 8) != nr))
+ {
+ skb = skb_dequeue(&self->wx_list);
+ dev_kfree_skb(skb);
+
+ self->va = (self->va + 1) % 8;
+ count++;
+ }
+ }
+
+ /* Advance window */
+ self->window = self->window_size - skb_queue_len(&self->wx_list);
+}
+
+/*
+ * Function irlap_validate_ns_received (ns)
+ *
+ * Validate the next to send (ns) field from received frame.
+ */
+int irlap_validate_ns_received(struct irlap_cb *self, int ns)
+{
+ /* ns as expected? */
+ if (ns == self->vr)
+ return NS_EXPECTED;
+ /*
+ * Stations are allowed to treat invalid NS as unexpected NS
+ * IrLAP, Recv ... with-invalid-Ns. p. 84
+ */
+ return NS_UNEXPECTED;
+
+ /* return NR_INVALID; */
+}
+/*
+ * Function irlap_validate_nr_received (nr)
+ *
+ * Validate the next to receive (nr) field from received frame.
+ *
+ */
+int irlap_validate_nr_received(struct irlap_cb *self, int nr)
+{
+ /* nr as expected? */
+ if (nr == self->vs) {
+ IRDA_DEBUG(4, "%s(), expected!\n", __FUNCTION__);
+ return NR_EXPECTED;
+ }
+
+ /*
+ * unexpected nr? (but within current window), first we check if the
+ * ns numbers of the frames in the current window wrap.
+ */
+ if (self->va < self->vs) {
+ if ((nr >= self->va) && (nr <= self->vs))
+ return NR_UNEXPECTED;
+ } else {
+ if ((nr >= self->va) || (nr <= self->vs))
+ return NR_UNEXPECTED;
+ }
+
+ /* Invalid nr! */
+ return NR_INVALID;
+}
+
+/*
+ * Function irlap_initiate_connection_state ()
+ *
+ * Initialize the connection state parameters
+ *
+ */
+void irlap_initiate_connection_state(struct irlap_cb *self)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ /* Next to send and next to receive */
+ self->vs = self->vr = 0;
+
+ /* Last frame which got acked (0 - 1) % 8 */
+ self->va = 7;
+
+ self->window = 1;
+
+ self->remote_busy = FALSE;
+ self->retry_count = 0;
+}
+
+/*
+ * Function irlap_wait_min_turn_around (self, qos)
+ *
+ * Wait negotiated minimum turn around time, this function actually sets
+ * the number of BOS's that must be sent before the next transmitted
+ * frame in order to delay for the specified amount of time. This is
+ * done to avoid using timers, and the forbidden udelay!
+ */
+void irlap_wait_min_turn_around(struct irlap_cb *self, struct qos_info *qos)
+{
+ __u32 min_turn_time;
+ __u32 speed;
+
+ /* Get QoS values. */
+ speed = qos->baud_rate.value;
+ min_turn_time = qos->min_turn_time.value;
+
+ /* No need to calculate XBOFs for speeds over 115200 bps */
+ if (speed > 115200) {
+ self->mtt_required = min_turn_time;
+ return;
+ }
+
+ /*
+ * Send additional BOF's for the next frame for the requested
+ * min turn time, so now we must calculate how many chars (XBOF's) we
+ * must send for the requested time period (min turn time)
+ */
+ self->xbofs_delay = irlap_min_turn_time_in_bytes(speed, min_turn_time);
+}
+
+/*
+ * Function irlap_flush_all_queues (void)
+ *
+ * Flush all queues
+ *
+ */
+void irlap_flush_all_queues(struct irlap_cb *self)
+{
+ struct sk_buff* skb;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ /* Free transmission queue */
+ while ((skb = skb_dequeue(&self->txq)) != NULL)
+ dev_kfree_skb(skb);
+
+ while ((skb = skb_dequeue(&self->txq_ultra)) != NULL)
+ dev_kfree_skb(skb);
+
+ /* Free sliding window buffered packets */
+ while ((skb = skb_dequeue(&self->wx_list)) != NULL)
+ dev_kfree_skb(skb);
+}
+
+/*
+ * Function irlap_setspeed (self, speed)
+ *
+ * Change the speed of the IrDA port
+ *
+ */
+void irlap_change_speed(struct irlap_cb *self, __u32 speed, int now)
+{
+ struct sk_buff *skb;
+
+ IRDA_DEBUG(0, "%s(), setting speed to %d\n", __FUNCTION__, speed);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ self->speed = speed;
+
+ /* Change speed now, or just piggyback speed on frames */
+ if (now) {
+ /* Send down empty frame to trigger speed change */
+ skb = dev_alloc_skb(0);
+ irlap_queue_xmit(self, skb);
+ }
+}
+
+/*
+ * Function irlap_init_qos_capabilities (self, qos)
+ *
+ * Initialize QoS for this IrLAP session, What we do is to compute the
+ * intersection of the QoS capabilities for the user, driver and for
+ * IrLAP itself. Normally, IrLAP will not specify any values, but it can
+ * be used to restrict certain values.
+ */
+void irlap_init_qos_capabilities(struct irlap_cb *self,
+ struct qos_info *qos_user)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+ ASSERT(self->netdev != NULL, return;);
+
+ /* Start out with the maximum QoS support possible */
+ irda_init_max_qos_capabilies(&self->qos_rx);
+
+ /* Apply drivers QoS capabilities */
+ irda_qos_compute_intersection(&self->qos_rx, self->qos_dev);
+
+ /*
+ * Check for user supplied QoS parameters. The service user is only
+ * allowed to supply these values. We check each parameter since the
+ * user may not have set all of them.
+ */
+ if (qos_user) {
+ IRDA_DEBUG(1, "%s(), Found user specified QoS!\n", __FUNCTION__);
+
+ if (qos_user->baud_rate.bits)
+ self->qos_rx.baud_rate.bits &= qos_user->baud_rate.bits;
+
+ if (qos_user->max_turn_time.bits)
+ self->qos_rx.max_turn_time.bits &= qos_user->max_turn_time.bits;
+ if (qos_user->data_size.bits)
+ self->qos_rx.data_size.bits &= qos_user->data_size.bits;
+
+ if (qos_user->link_disc_time.bits)
+ self->qos_rx.link_disc_time.bits &= qos_user->link_disc_time.bits;
+ }
+
+ /* Use 500ms in IrLAP for now */
+ self->qos_rx.max_turn_time.bits &= 0x01;
+
+ /* Set data size */
+ /*self->qos_rx.data_size.bits &= 0x03;*/
+
+ irda_qos_bits_to_value(&self->qos_rx);
+}
+
+/*
+ * Function irlap_apply_default_connection_parameters (void, now)
+ *
+ * Use the default connection and transmission parameters
+ *
+ */
+void irlap_apply_default_connection_parameters(struct irlap_cb *self)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ /* xbofs : Default value in NDM */
+ self->next_bofs = 12;
+ self->bofs_count = 12;
+
+ /* NDM Speed is 9600 */
+ irlap_change_speed(self, 9600, TRUE);
+
+ /* Set mbusy when going to NDM state */
+ irda_device_set_media_busy(self->netdev, TRUE);
+
+ /*
+ * Generate random connection address for this session, which must
+ * be 7 bits wide and different from 0x00 and 0xfe
+ */
+ while ((self->caddr == 0x00) || (self->caddr == 0xfe)) {
+ get_random_bytes(&self->caddr, sizeof(self->caddr));
+ self->caddr &= 0xfe;
+ }
+
+ /* Use default values until connection has been negitiated */
+ self->slot_timeout = sysctl_slot_timeout;
+ self->final_timeout = FINAL_TIMEOUT;
+ self->poll_timeout = POLL_TIMEOUT;
+ self->wd_timeout = WD_TIMEOUT;
+
+ /* Set some default values */
+ self->qos_tx.baud_rate.value = 9600;
+ self->qos_rx.baud_rate.value = 9600;
+ self->qos_tx.max_turn_time.value = 0;
+ self->qos_rx.max_turn_time.value = 0;
+ self->qos_tx.min_turn_time.value = 0;
+ self->qos_rx.min_turn_time.value = 0;
+ self->qos_tx.data_size.value = 64;
+ self->qos_rx.data_size.value = 64;
+ self->qos_tx.window_size.value = 1;
+ self->qos_rx.window_size.value = 1;
+ self->qos_tx.additional_bofs.value = 12;
+ self->qos_rx.additional_bofs.value = 12;
+ self->qos_tx.link_disc_time.value = 0;
+ self->qos_rx.link_disc_time.value = 0;
+
+ irlap_flush_all_queues(self);
+
+ self->disconnect_pending = FALSE;
+ self->connect_pending = FALSE;
+}
+
+/*
+ * Function irlap_apply_connection_parameters (qos, now)
+ *
+ * Initialize IrLAP with the negotiated QoS values
+ *
+ * If 'now' is false, the speed and xbofs will be changed after the next
+ * frame is sent.
+ * If 'now' is true, the speed and xbofs is changed immediately
+ */
+void irlap_apply_connection_parameters(struct irlap_cb *self, int now)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ /* Set the negociated xbofs value */
+ self->next_bofs = self->qos_tx.additional_bofs.value;
+ if (now)
+ self->bofs_count = self->next_bofs;
+
+ /* Set the negociated link speed (may need the new xbofs value) */
+ irlap_change_speed(self, self->qos_tx.baud_rate.value, now);
+
+ self->window_size = self->qos_tx.window_size.value;
+ self->window = self->qos_tx.window_size.value;
+
+#ifdef CONFIG_IRDA_DYNAMIC_WINDOW
+ /*
+ * Calculate how many bytes it is possible to transmit before the
+ * link must be turned around
+ */
+ self->line_capacity =
+ irlap_max_line_capacity(self->qos_tx.baud_rate.value,
+ self->qos_tx.max_turn_time.value);
+ self->bytes_left = self->line_capacity;
+#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */
+
+
+ /*
+ * Initialize timeout values, some of the rules are listed on
+ * page 92 in IrLAP.
+ */
+ ASSERT(self->qos_tx.max_turn_time.value != 0, return;);
+ ASSERT(self->qos_rx.max_turn_time.value != 0, return;);
+ /* The poll timeout applies only to the primary station.
+ * It defines the maximum time the primary stay in XMIT mode
+ * before timeout and turning the link around (sending a RR).
+ * Or, this is how much we can keep the pf bit in primary mode.
+ * Therefore, it must be lower or equal than our *OWN* max turn around.
+ * Jean II */
+ self->poll_timeout = self->qos_tx.max_turn_time.value * HZ / 1000;
+ /* The Final timeout applies only to the primary station.
+ * It defines the maximum time the primary wait (mostly in RECV mode)
+ * for an answer from the secondary station before polling it again.
+ * Therefore, it must be greater or equal than our *PARTNER*
+ * max turn around time - Jean II */
+ self->final_timeout = self->qos_rx.max_turn_time.value * HZ / 1000;
+ /* The Watchdog Bit timeout applies only to the secondary station.
+ * It defines the maximum time the secondary wait (mostly in RECV mode)
+ * for poll from the primary station before getting annoyed.
+ * Therefore, it must be greater or equal than our *PARTNER*
+ * max turn around time - Jean II */
+ self->wd_timeout = self->final_timeout * 2;
+
+ /*
+ * N1 and N2 are maximum retry count for *both* the final timer
+ * and the wd timer (with a factor 2) as defined above.
+ * After N1 retry of a timer, we give a warning to the user.
+ * After N2 retry, we consider the link dead and disconnect it.
+ * Jean II
+ */
+
+ /*
+ * Set N1 to 0 if Link Disconnect/Threshold Time = 3 and set it to
+ * 3 seconds otherwise. See page 71 in IrLAP for more details.
+ * Actually, it's not always 3 seconds, as we allow to set
+ * it via sysctl... Max maxtt is 500ms, and N1 need to be multiple
+ * of 2, so 1 second is minimum we can allow. - Jean II
+ */
+ if (self->qos_tx.link_disc_time.value == sysctl_warn_noreply_time)
+ /*
+ * If we set N1 to 0, it will trigger immediately, which is
+ * not what we want. What we really want is to disable it,
+ * Jean II
+ */
+ self->N1 = -2; /* Disable - Need to be multiple of 2*/
+ else
+ self->N1 = sysctl_warn_noreply_time * 1000 /
+ self->qos_rx.max_turn_time.value;
+
+ IRDA_DEBUG(4, "Setting N1 = %d\n", self->N1);
+
+ /* Set N2 to match our own disconnect time */
+ self->N2 = self->qos_tx.link_disc_time.value * 1000 /
+ self->qos_rx.max_turn_time.value;
+ IRDA_DEBUG(4, "Setting N2 = %d\n", self->N2);
+}
+
+#ifdef CONFIG_PROC_FS
+/*
+ * Function irlap_proc_read (buf, start, offset, len, unused)
+ *
+ * Give some info to the /proc file system
+ *
+ */
+int irlap_proc_read(char *buf, char **start, off_t offset, int len)
+{
+ struct irlap_cb *self;
+ unsigned long flags;
+ int i = 0;
+
+ save_flags(flags);
+ cli();
+
+ len = 0;
+
+ self = (struct irlap_cb *) hashbin_get_first(irlap);
+ while (self != NULL) {
+ ASSERT(self != NULL, break;);
+ ASSERT(self->magic == LAP_MAGIC, break;);
+
+ len += sprintf(buf+len, "irlap%d ", i++);
+ len += sprintf(buf+len, "state: %s\n",
+ irlap_state[self->state]);
+
+ len += sprintf(buf+len, " device name: %s, ",
+ (self->netdev) ? self->netdev->name : "bug");
+ len += sprintf(buf+len, "hardware name: %s\n", self->hw_name);
+
+ len += sprintf(buf+len, " caddr: %#02x, ", self->caddr);
+ len += sprintf(buf+len, "saddr: %#08x, ", self->saddr);
+ len += sprintf(buf+len, "daddr: %#08x\n", self->daddr);
+
+ len += sprintf(buf+len, " win size: %d, ",
+ self->window_size);
+ len += sprintf(buf+len, "win: %d, ", self->window);
+#if CONFIG_IRDA_DYNAMIC_WINDOW
+ len += sprintf(buf+len, "line capacity: %d, ",
+ self->line_capacity);
+ len += sprintf(buf+len, "bytes left: %d\n", self->bytes_left);
+#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */
+ len += sprintf(buf+len, " tx queue len: %d ",
+ skb_queue_len(&self->txq));
+ len += sprintf(buf+len, "win queue len: %d ",
+ skb_queue_len(&self->wx_list));
+ len += sprintf(buf+len, "rbusy: %s", self->remote_busy ?
+ "TRUE" : "FALSE");
+ len += sprintf(buf+len, " mbusy: %s\n", self->media_busy ?
+ "TRUE" : "FALSE");
+
+ len += sprintf(buf+len, " retrans: %d ", self->retry_count);
+ len += sprintf(buf+len, "vs: %d ", self->vs);
+ len += sprintf(buf+len, "vr: %d ", self->vr);
+ len += sprintf(buf+len, "va: %d\n", self->va);
+
+ len += sprintf(buf+len, " qos\tbps\tmaxtt\tdsize\twinsize\taddbofs\tmintt\tldisc\tcomp\n");
+
+ len += sprintf(buf+len, " tx\t%d\t",
+ self->qos_tx.baud_rate.value);
+ len += sprintf(buf+len, "%d\t",
+ self->qos_tx.max_turn_time.value);
+ len += sprintf(buf+len, "%d\t",
+ self->qos_tx.data_size.value);
+ len += sprintf(buf+len, "%d\t",
+ self->qos_tx.window_size.value);
+ len += sprintf(buf+len, "%d\t",
+ self->qos_tx.additional_bofs.value);
+ len += sprintf(buf+len, "%d\t",
+ self->qos_tx.min_turn_time.value);
+ len += sprintf(buf+len, "%d\t",
+ self->qos_tx.link_disc_time.value);
+ len += sprintf(buf+len, "\n");
+
+ len += sprintf(buf+len, " rx\t%d\t",
+ self->qos_rx.baud_rate.value);
+ len += sprintf(buf+len, "%d\t",
+ self->qos_rx.max_turn_time.value);
+ len += sprintf(buf+len, "%d\t",
+ self->qos_rx.data_size.value);
+ len += sprintf(buf+len, "%d\t",
+ self->qos_rx.window_size.value);
+ len += sprintf(buf+len, "%d\t",
+ self->qos_rx.additional_bofs.value);
+ len += sprintf(buf+len, "%d\t",
+ self->qos_rx.min_turn_time.value);
+ len += sprintf(buf+len, "%d\t",
+ self->qos_rx.link_disc_time.value);
+ len += sprintf(buf+len, "\n");
+
+ self = (struct irlap_cb *) hashbin_get_next(irlap);
+ }
+ restore_flags(flags);
+
+ return len;
+}
+
+#endif /* CONFIG_PROC_FS */
+
diff --git a/uClinux-2.4.31-uc0/net/irda/irlap_event.c b/uClinux-2.4.31-uc0/net/irda/irlap_event.c
new file mode 100644
index 0000000..c6ab878
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlap_event.c
@@ -0,0 +1,2203 @@
+/*********************************************************************
+ *
+ * Filename: irlap_event.c
+ * Version: 0.9
+ * Description: IrLAP state machine implementation
+ * Status: Experimental.
+ * Author: Dag Brattli <dag@brattli.net>
+ * Created at: Sat Aug 16 00:59:29 1997
+ * Modified at: Sat Dec 25 21:07:57 1999
+ * Modified by: Dag Brattli <dag@brattli.net>
+ *
+ * Copyright (c) 1998-2000 Dag Brattli <dag@brattli.net>,
+ * Copyright (c) 1998 Thomas Davis <ratbert@radiks.net>
+ * All Rights Reserved.
+ * Copyright (c) 2000-2001 Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/skbuff.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irlap_event.h>
+
+#include <net/irda/timer.h>
+#include <net/irda/irlap.h>
+#include <net/irda/irlap_frame.h>
+#include <net/irda/qos.h>
+#include <net/irda/parameters.h>
+
+#include <net/irda/irda_device.h>
+
+#if CONFIG_IRDA_FAST_RR
+int sysctl_fast_poll_increase = 50;
+#endif
+
+static int irlap_state_ndm (struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info);
+static int irlap_state_query (struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info);
+static int irlap_state_reply (struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info);
+static int irlap_state_conn (struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info);
+static int irlap_state_setup (struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info);
+static int irlap_state_offline(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info);
+static int irlap_state_xmit_p (struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info);
+static int irlap_state_pclose (struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info);
+static int irlap_state_nrm_p (struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info);
+static int irlap_state_reset_wait(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info);
+static int irlap_state_reset (struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info);
+static int irlap_state_nrm_s (struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info);
+static int irlap_state_xmit_s (struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info);
+static int irlap_state_sclose (struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info);
+static int irlap_state_reset_check(struct irlap_cb *, IRLAP_EVENT event,
+ struct sk_buff *, struct irlap_info *);
+
+#ifdef CONFIG_IRDA_DEBUG
+static const char *irlap_event[] = {
+ "DISCOVERY_REQUEST",
+ "CONNECT_REQUEST",
+ "CONNECT_RESPONSE",
+ "DISCONNECT_REQUEST",
+ "DATA_REQUEST",
+ "RESET_REQUEST",
+ "RESET_RESPONSE",
+ "SEND_I_CMD",
+ "SEND_UI_FRAME",
+ "RECV_DISCOVERY_XID_CMD",
+ "RECV_DISCOVERY_XID_RSP",
+ "RECV_SNRM_CMD",
+ "RECV_TEST_CMD",
+ "RECV_TEST_RSP",
+ "RECV_UA_RSP",
+ "RECV_DM_RSP",
+ "RECV_RD_RSP",
+ "RECV_I_CMD",
+ "RECV_I_RSP",
+ "RECV_UI_FRAME",
+ "RECV_FRMR_RSP",
+ "RECV_RR_CMD",
+ "RECV_RR_RSP",
+ "RECV_RNR_CMD",
+ "RECV_RNR_RSP",
+ "RECV_REJ_CMD",
+ "RECV_REJ_RSP",
+ "RECV_SREJ_CMD",
+ "RECV_SREJ_RSP",
+ "RECV_DISC_CMD",
+ "SLOT_TIMER_EXPIRED",
+ "QUERY_TIMER_EXPIRED",
+ "FINAL_TIMER_EXPIRED",
+ "POLL_TIMER_EXPIRED",
+ "DISCOVERY_TIMER_EXPIRED",
+ "WD_TIMER_EXPIRED",
+ "BACKOFF_TIMER_EXPIRED",
+ "MEDIA_BUSY_TIMER_EXPIRED",
+};
+#endif /* CONFIG_IRDA_DEBUG */
+
+const char *irlap_state[] = {
+ "LAP_NDM",
+ "LAP_QUERY",
+ "LAP_REPLY",
+ "LAP_CONN",
+ "LAP_SETUP",
+ "LAP_OFFLINE",
+ "LAP_XMIT_P",
+ "LAP_PCLOSE",
+ "LAP_NRM_P",
+ "LAP_RESET_WAIT",
+ "LAP_RESET",
+ "LAP_NRM_S",
+ "LAP_XMIT_S",
+ "LAP_SCLOSE",
+ "LAP_RESET_CHECK",
+};
+
+static int (*state[])(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info) =
+{
+ irlap_state_ndm,
+ irlap_state_query,
+ irlap_state_reply,
+ irlap_state_conn,
+ irlap_state_setup,
+ irlap_state_offline,
+ irlap_state_xmit_p,
+ irlap_state_pclose,
+ irlap_state_nrm_p,
+ irlap_state_reset_wait,
+ irlap_state_reset,
+ irlap_state_nrm_s,
+ irlap_state_xmit_s,
+ irlap_state_sclose,
+ irlap_state_reset_check,
+};
+
+/*
+ * Function irda_poll_timer_expired (data)
+ *
+ * Poll timer has expired. Normally we must now send a RR frame to the
+ * remote device
+ */
+static void irlap_poll_timer_expired(void *data)
+{
+ struct irlap_cb *self = (struct irlap_cb *) data;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ irlap_do_event(self, POLL_TIMER_EXPIRED, NULL, NULL);
+}
+
+/*
+ * Calculate and set time before we will have to send back the pf bit
+ * to the peer. Use in primary.
+ * Make sure that state is XMIT_P/XMIT_S when calling this function
+ * (and that nobody messed up with the state). - Jean II
+ */
+void irlap_start_poll_timer(struct irlap_cb *self, int timeout)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+#ifdef CONFIG_IRDA_FAST_RR
+ /*
+ * Send out the RR frames faster if our own transmit queue is empty, or
+ * if the peer is busy. The effect is a much faster conversation
+ */
+ if ((skb_queue_len(&self->txq) == 0) || (self->remote_busy)) {
+ if (self->fast_RR == TRUE) {
+ /*
+ * Assert that the fast poll timer has not reached the
+ * normal poll timer yet
+ */
+ if (self->fast_RR_timeout < timeout) {
+ /*
+ * FIXME: this should be a more configurable
+ * function
+ */
+ self->fast_RR_timeout +=
+ (sysctl_fast_poll_increase * HZ/1000);
+
+ /* Use this fast(er) timeout instead */
+ timeout = self->fast_RR_timeout;
+ }
+ } else {
+ self->fast_RR = TRUE;
+
+ /* Start with just 0 ms */
+ self->fast_RR_timeout = 0;
+ timeout = 0;
+ }
+ } else
+ self->fast_RR = FALSE;
+
+ IRDA_DEBUG(3, "%s(), timeout=%d (%ld)\n", __FUNCTION__, timeout, jiffies);
+#endif /* CONFIG_IRDA_FAST_RR */
+
+ if (timeout == 0)
+ irlap_do_event(self, POLL_TIMER_EXPIRED, NULL, NULL);
+ else
+ irda_start_timer(&self->poll_timer, timeout, self,
+ irlap_poll_timer_expired);
+}
+
+/*
+ * Function irlap_do_event (event, skb, info)
+ *
+ * Rushes through the state machine without any delay. If state == XMIT
+ * then send queued data frames.
+ */
+void irlap_do_event(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ int ret;
+
+ if (!self || self->magic != LAP_MAGIC)
+ return;
+
+ IRDA_DEBUG(3, "%s(), event = %s, state = %s\n", __FUNCTION__,
+ irlap_event[event], irlap_state[self->state]);
+
+ ret = (*state[self->state])(self, event, skb, info);
+
+ /*
+ * Check if there are any pending events that needs to be executed
+ */
+ switch (self->state) {
+ case LAP_XMIT_P: /* FALLTHROUGH */
+ case LAP_XMIT_S:
+ /*
+ * We just received the pf bit and are at the beginning
+ * of a new LAP transmit window.
+ * Check if there are any queued data frames, and do not
+ * try to disconnect link if we send any data frames, since
+ * that will change the state away form XMIT
+ */
+ IRDA_DEBUG(2, "%s() : queue len = %d\n", __FUNCTION__,
+ skb_queue_len(&self->txq));
+
+ if (skb_queue_len(&self->txq)) {
+ /* Prevent race conditions with irlap_data_request() */
+ self->local_busy = TRUE;
+
+ /* Theory of operation.
+ * We send frames up to when we fill the window or
+ * reach line capacity. Those frames will queue up
+ * in the device queue, and the driver will slowly
+ * send them.
+ * After each frame that we send, we poll the higher
+ * layer for more data. It's the right time to do
+ * that because the link layer need to perform the mtt
+ * and then send the first frame, so we can afford
+ * to send a bit of time in kernel space.
+ * The explicit flow indication allow to minimise
+ * buffers (== lower latency), to avoid higher layer
+ * polling via timers (== less context switches) and
+ * to implement a crude scheduler - Jean II */
+
+ /* Try to send away all queued data frames */
+ while ((skb = skb_dequeue(&self->txq)) != NULL) {
+ /* Send one frame */
+ ret = (*state[self->state])(self, SEND_I_CMD,
+ skb, NULL);
+ kfree_skb(skb);
+
+ /* Poll the higher layers for one more frame */
+ irlmp_flow_indication(self->notify.instance,
+ FLOW_START);
+
+ if (ret == -EPROTO)
+ break; /* Try again later! */
+ }
+ /* Finished transmitting */
+ self->local_busy = FALSE;
+ } else if (self->disconnect_pending) {
+ self->disconnect_pending = FALSE;
+
+ ret = (*state[self->state])(self, DISCONNECT_REQUEST,
+ NULL, NULL);
+ }
+ break;
+/* case LAP_NDM: */
+/* case LAP_CONN: */
+/* case LAP_RESET_WAIT: */
+/* case LAP_RESET_CHECK: */
+ default:
+ break;
+ }
+}
+
+/*
+ * Function irlap_next_state (self, state)
+ *
+ * Switches state and provides debug information
+ *
+ */
+static inline void irlap_next_state(struct irlap_cb *self, IRLAP_STATE state)
+{
+ /*
+ if (!self || self->magic != LAP_MAGIC)
+ return;
+
+ IRDA_DEBUG(4, "next LAP state = %s\n", irlap_state[state]);
+ */
+ self->state = state;
+}
+
+/*
+ * Function irlap_state_ndm (event, skb, frame)
+ *
+ * NDM (Normal Disconnected Mode) state
+ *
+ */
+static int irlap_state_ndm(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ discovery_t *discovery_rsp;
+ int ret = 0;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ switch (event) {
+ case CONNECT_REQUEST:
+ ASSERT(self->netdev != NULL, return -1;);
+
+ if (self->media_busy) {
+ /* Note : this will never happen, because we test
+ * media busy in irlap_connect_request() and
+ * postpone the event... - Jean II */
+ IRDA_DEBUG(0, "%s(), CONNECT_REQUEST: media busy!\n", __FUNCTION__);
+
+ /* Always switch state before calling upper layers */
+ irlap_next_state(self, LAP_NDM);
+
+ irlap_disconnect_indication(self, LAP_MEDIA_BUSY);
+ } else {
+ irlap_send_snrm_frame(self, &self->qos_rx);
+
+ /* Start Final-bit timer */
+ irlap_start_final_timer(self, self->final_timeout);
+
+ self->retry_count = 0;
+ irlap_next_state(self, LAP_SETUP);
+ }
+ break;
+ case RECV_SNRM_CMD:
+ /* Check if the frame contains and I field */
+ if (info) {
+ self->daddr = info->daddr;
+ self->caddr = info->caddr;
+
+ irlap_next_state(self, LAP_CONN);
+
+ irlap_connect_indication(self, skb);
+ } else {
+ IRDA_DEBUG(0, "%s(), SNRM frame does not "
+ "contain an I field!\n", __FUNCTION__);
+ }
+ break;
+ case DISCOVERY_REQUEST:
+ ASSERT(info != NULL, return -1;);
+
+ if (self->media_busy) {
+ IRDA_DEBUG(0, "%s(), media busy!\n", __FUNCTION__);
+ /* irlap->log.condition = MEDIA_BUSY; */
+
+ /* This will make IrLMP try again */
+ irlap_discovery_confirm(self, NULL);
+ /* Note : the discovery log is not cleaned up here,
+ * it will be done in irlap_discovery_request()
+ * Jean II */
+ return 0;
+ }
+
+ self->S = info->S;
+ self->s = info->s;
+ irlap_send_discovery_xid_frame(self, info->S, info->s, TRUE,
+ info->discovery);
+ self->frame_sent = FALSE;
+ self->s++;
+
+ irlap_start_slot_timer(self, self->slot_timeout);
+ irlap_next_state(self, LAP_QUERY);
+ break;
+ case RECV_DISCOVERY_XID_CMD:
+ ASSERT(info != NULL, return -1;);
+
+ /* Assert that this is not the final slot */
+ if (info->s <= info->S) {
+ self->slot = irlap_generate_rand_time_slot(info->S,
+ info->s);
+ if (self->slot == info->s) {
+ discovery_rsp = irlmp_get_discovery_response();
+ discovery_rsp->daddr = info->daddr;
+
+ irlap_send_discovery_xid_frame(self, info->S,
+ self->slot,
+ FALSE,
+ discovery_rsp);
+ self->frame_sent = TRUE;
+ } else
+ self->frame_sent = FALSE;
+
+ /*
+ * Remember to multiply the query timeout value with
+ * the number of slots used
+ */
+ irlap_start_query_timer(self, QUERY_TIMEOUT*info->S);
+ irlap_next_state(self, LAP_REPLY);
+ } else {
+ /* This is the final slot. How is it possible ?
+ * This would happen is both discoveries are just slightly
+ * offset (if they are in sync, all packets are lost).
+ * Most often, all the discovery requests will be received
+ * in QUERY state (see my comment there), except for the
+ * last frame that will come here.
+ * The big trouble when it happen is that active discovery
+ * doesn't happen, because nobody answer the discoveries
+ * frame of the other guy, so the log shows up empty.
+ * What should we do ?
+ * Not much. It's too late to answer those discovery frames,
+ * so we just pass the info to IrLMP who will put it in the
+ * log (and post an event).
+ * Jean II
+ */
+ IRDA_DEBUG(1, "%s(), Receiving final discovery request, missed the discovery slots :-(\n", __FUNCTION__);
+
+ /* Last discovery request -> in the log */
+ irlap_discovery_indication(self, info->discovery);
+ }
+ break;
+ case MEDIA_BUSY_TIMER_EXPIRED:
+ /* A bunch of events may be postponed because the media is
+ * busy (usually immediately after we close a connection),
+ * or while we are doing discovery (state query/reply).
+ * In all those cases, the media busy flag will be cleared
+ * when it's OK for us to process those postponed events.
+ * This event is not mentioned in the state machines in the
+ * IrLAP spec. It's because they didn't consider Ultra and
+ * postponing connection request is optional.
+ * Jean II */
+#ifdef CONFIG_IRDA_ULTRA
+ /* Send any pending Ultra frames if any */
+ if (!skb_queue_empty(&self->txq_ultra)) {
+ /* We don't send the frame, just post an event.
+ * Also, previously this code was in timer.c...
+ * Jean II */
+ ret = (*state[self->state])(self, SEND_UI_FRAME,
+ NULL, NULL);
+ }
+#endif /* CONFIG_IRDA_ULTRA */
+ /* Check if we should try to connect.
+ * This code was previously in irlap_do_event() */
+ if (self->connect_pending) {
+ self->connect_pending = FALSE;
+
+ /* This one *should* not pend in this state, except
+ * if a socket try to connect and immediately
+ * disconnect. - clear - Jean II */
+ if (self->disconnect_pending)
+ irlap_disconnect_indication(self, LAP_DISC_INDICATION);
+ else
+ ret = (*state[self->state])(self,
+ CONNECT_REQUEST,
+ NULL, NULL);
+ self->disconnect_pending = FALSE;
+ }
+ /* Note : one way to test if this code works well (including
+ * media busy and small busy) is to create a user space
+ * application generating an Ultra packet every 3.05 sec (or
+ * 2.95 sec) and to see how it interact with discovery.
+ * It's fairly easy to check that no packet is lost, that the
+ * packets are postponed during discovery and that after
+ * discovery indication you have a 100ms "gap".
+ * As connection request and Ultra are now processed the same
+ * way, this avoid the tedious job of trying IrLAP connection
+ * in all those cases...
+ * Jean II */
+ break;
+#ifdef CONFIG_IRDA_ULTRA
+ case SEND_UI_FRAME:
+ {
+ int i;
+ /* Only allowed to repeat an operation twice */
+ for (i=0; ((i<2) && (self->media_busy == FALSE)); i++) {
+ skb = skb_dequeue(&self->txq_ultra);
+ if (skb)
+ irlap_send_ui_frame(self, skb, CBROADCAST,
+ CMD_FRAME);
+ else
+ break;
+ }
+ if (i == 2) {
+ /* Force us to listen 500 ms again */
+ irda_device_set_media_busy(self->netdev, TRUE);
+ }
+ break;
+ }
+ case RECV_UI_FRAME:
+ /* Only accept broadcast frames in NDM mode */
+ if (info->caddr != CBROADCAST) {
+ IRDA_DEBUG(0, "%s(), not a broadcast frame!\n", __FUNCTION__);
+ } else
+ irlap_unitdata_indication(self, skb);
+ break;
+#endif /* CONFIG_IRDA_ULTRA */
+ case RECV_TEST_CMD:
+ /* Remove test frame header */
+ skb_pull(skb, 10 /*sizeof(struct test_frame)*/);
+
+ /*
+ * Send response. This skb will not be sent out again, and
+ * will only be used to send out the same info as the cmd
+ */
+ irlap_send_test_frame(self, CBROADCAST, info->daddr, skb);
+ break;
+ case RECV_TEST_RSP:
+ IRDA_DEBUG(0, "%s() not implemented!\n", __FUNCTION__);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown event %s\n", __FUNCTION__,
+ irlap_event[event]);
+
+ ret = -1;
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlap_state_query (event, skb, info)
+ *
+ * QUERY state
+ *
+ */
+static int irlap_state_query(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ int ret = 0;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ switch (event) {
+ case RECV_DISCOVERY_XID_RSP:
+ ASSERT(info != NULL, return -1;);
+ ASSERT(info->discovery != NULL, return -1;);
+
+ IRDA_DEBUG(4, "%s(), daddr=%08x\n", __FUNCTION__,
+ info->discovery->daddr);
+
+ if (!self->discovery_log) {
+ WARNING("%s(), discovery log is gone! "
+ "maybe the discovery timeout has been set to "
+ "short?\n", __FUNCTION__);
+ break;
+ }
+ hashbin_insert(self->discovery_log,
+ (irda_queue_t *) info->discovery,
+ info->discovery->daddr, NULL);
+
+ /* Keep state */
+ /* irlap_next_state(self, LAP_QUERY); */
+
+ break;
+ case RECV_DISCOVERY_XID_CMD:
+ /* Yes, it is possible to receive those frames in this mode.
+ * Note that most often the last discovery request won't
+ * occur here but in NDM state (see my comment there).
+ * What should we do ?
+ * Not much. We are currently performing our own discovery,
+ * therefore we can't answer those frames. We don't want
+ * to change state either. We just pass the info to
+ * IrLMP who will put it in the log (and post an event).
+ * Jean II
+ */
+
+ ASSERT(info != NULL, return -1;);
+
+ IRDA_DEBUG(1, "%s(), Receiving discovery request (s = %d) while performing discovery :-(\n", __FUNCTION__, info->s);
+
+ /* Last discovery request ? */
+ if (info->s == 0xff)
+ irlap_discovery_indication(self, info->discovery);
+ break;
+ case SLOT_TIMER_EXPIRED:
+ /*
+ * Wait a little longer if we detect an incoming frame. This
+ * is not mentioned in the spec, but is a good thing to do,
+ * since we want to work even with devices that violate the
+ * timing requirements.
+ */
+ if (irda_device_is_receiving(self->netdev) && !self->add_wait) {
+ IRDA_DEBUG(2, "%s(), device is slow to answer, "
+ "waiting some more!\n", __FUNCTION__);
+ irlap_start_slot_timer(self, MSECS_TO_JIFFIES(10));
+ self->add_wait = TRUE;
+ return ret;
+ }
+ self->add_wait = FALSE;
+
+ if (self->s < self->S) {
+ irlap_send_discovery_xid_frame(self, self->S,
+ self->s, TRUE,
+ self->discovery_cmd);
+ self->s++;
+ irlap_start_slot_timer(self, self->slot_timeout);
+
+ /* Keep state */
+ irlap_next_state(self, LAP_QUERY);
+ } else {
+ /* This is the final slot! */
+ irlap_send_discovery_xid_frame(self, self->S, 0xff,
+ TRUE,
+ self->discovery_cmd);
+
+ /* Always switch state before calling upper layers */
+ irlap_next_state(self, LAP_NDM);
+
+ /*
+ * We are now finished with the discovery procedure,
+ * so now we must return the results
+ */
+ irlap_discovery_confirm(self, self->discovery_log);
+
+ /* IrLMP should now have taken care of the log */
+ self->discovery_log = NULL;
+ }
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown event %s\n", __FUNCTION__,
+ irlap_event[event]);
+
+ ret = -1;
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlap_state_reply (self, event, skb, info)
+ *
+ * REPLY, we have received a XID discovery frame from a device and we
+ * are waiting for the right time slot to send a response XID frame
+ *
+ */
+static int irlap_state_reply(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ discovery_t *discovery_rsp;
+ int ret=0;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ switch (event) {
+ case QUERY_TIMER_EXPIRED:
+ IRDA_DEBUG(2, "%s(), QUERY_TIMER_EXPIRED <%ld>\n", __FUNCTION__,
+ jiffies);
+ irlap_next_state(self, LAP_NDM);
+ break;
+ case RECV_DISCOVERY_XID_CMD:
+ ASSERT(info != NULL, return -1;);
+ /* Last frame? */
+ if (info->s == 0xff) {
+ del_timer(&self->query_timer);
+
+ /* info->log.condition = REMOTE; */
+
+ /* Always switch state before calling upper layers */
+ irlap_next_state(self, LAP_NDM);
+
+ irlap_discovery_indication(self, info->discovery);
+ } else if ((info->s >= self->slot) && (!self->frame_sent)) {
+ discovery_rsp = irlmp_get_discovery_response();
+ discovery_rsp->daddr = info->daddr;
+
+ irlap_send_discovery_xid_frame(self, info->S,
+ self->slot, FALSE,
+ discovery_rsp);
+
+ self->frame_sent = TRUE;
+ irlap_next_state(self, LAP_REPLY);
+ }
+ break;
+ default:
+ IRDA_DEBUG(1, "%s(), Unknown event %d, %s\n", __FUNCTION__, event,
+ irlap_event[event]);
+
+ ret = -1;
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlap_state_conn (event, skb, info)
+ *
+ * CONN, we have received a SNRM command and is waiting for the upper
+ * layer to accept or refuse connection
+ *
+ */
+static int irlap_state_conn(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(4, "%s(), event=%s\n", __FUNCTION__, irlap_event[ event]);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ switch (event) {
+ case CONNECT_RESPONSE:
+ skb_pull(skb, 11 /*sizeof(struct snrm_frame)*/);
+
+ ASSERT(self->netdev != NULL, return -1;);
+
+ irlap_qos_negotiate(self, skb);
+
+ irlap_initiate_connection_state(self);
+
+ /*
+ * Applying the parameters now will make sure we change speed
+ * *after* we have sent the next frame
+ */
+ irlap_apply_connection_parameters(self, FALSE);
+
+ /*
+ * Sending this frame will force a speed change after it has
+ * been sent (i.e. the frame will be sent at 9600).
+ */
+ irlap_send_ua_response_frame(self, &self->qos_rx);
+
+#if 0
+ /*
+ * We are allowed to send two frames, but this may increase
+ * the connect latency, so lets not do it for now.
+ */
+ /* This is full of good intentions, but doesn't work in
+ * practice.
+ * After sending the first UA response, we switch the
+ * dongle to the negociated speed, which is usually
+ * different than 9600 kb/s.
+ * From there, there is two solutions :
+ * 1) The other end has received the first UA response :
+ * it will set up the connection, move to state LAP_NRM_P,
+ * and will ignore and drop the second UA response.
+ * Actually, it's even worse : the other side will almost
+ * immediately send a RR that will likely collide with the
+ * UA response (depending on negociated turnaround).
+ * 2) The other end has not received the first UA response,
+ * will stay at 9600 and will never see the second UA response.
+ * Jean II */
+ irlap_send_ua_response_frame(self, &self->qos_rx);
+#endif
+
+ /*
+ * The WD-timer could be set to the duration of the P-timer
+ * for this case, but it is recommended to use twice the
+ * value (note 3 IrLAP p. 60).
+ */
+ irlap_start_wd_timer(self, self->wd_timeout);
+ irlap_next_state(self, LAP_NRM_S);
+
+ break;
+ case RECV_DISCOVERY_XID_CMD:
+ IRDA_DEBUG(3, "%s(), event RECV_DISCOVER_XID_CMD!\n", __FUNCTION__);
+ irlap_next_state(self, LAP_NDM);
+
+ break;
+ case DISCONNECT_REQUEST:
+ IRDA_DEBUG(0, "%s(), Disconnect request!\n", __FUNCTION__);
+ irlap_send_dm_frame(self);
+ irlap_next_state( self, LAP_NDM);
+ irlap_disconnect_indication(self, LAP_DISC_INDICATION);
+ break;
+ default:
+ IRDA_DEBUG(1, "%s(), Unknown event %d, %s\n", __FUNCTION__, event,
+ irlap_event[event]);
+
+ ret = -1;
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * Function irlap_state_setup (event, skb, frame)
+ *
+ * SETUP state, The local layer has transmitted a SNRM command frame to
+ * a remote peer layer and is awaiting a reply .
+ *
+ */
+static int irlap_state_setup(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ switch (event) {
+ case FINAL_TIMER_EXPIRED:
+ if (self->retry_count < self->N3) {
+/*
+ * Perform random backoff, Wait a random number of time units, minimum
+ * duration half the time taken to transmitt a SNRM frame, maximum duration
+ * 1.5 times the time taken to transmit a SNRM frame. So this time should
+ * between 15 msecs and 45 msecs.
+ */
+ irlap_start_backoff_timer(self, MSECS_TO_JIFFIES(20 +
+ (jiffies % 30)));
+ } else {
+ /* Always switch state before calling upper layers */
+ irlap_next_state(self, LAP_NDM);
+
+ irlap_disconnect_indication(self, LAP_FOUND_NONE);
+ }
+ break;
+ case BACKOFF_TIMER_EXPIRED:
+ irlap_send_snrm_frame(self, &self->qos_rx);
+ irlap_start_final_timer(self, self->final_timeout);
+ self->retry_count++;
+ break;
+ case RECV_SNRM_CMD:
+ IRDA_DEBUG(4, "%s(), SNRM battle!\n", __FUNCTION__);
+
+ ASSERT(skb != NULL, return 0;);
+ ASSERT(info != NULL, return 0;);
+
+ /*
+ * The device with the largest device address wins the battle
+ * (both have sent a SNRM command!)
+ */
+ if (info &&(info->daddr > self->saddr)) {
+ del_timer(&self->final_timer);
+ irlap_initiate_connection_state(self);
+
+ ASSERT(self->netdev != NULL, return -1;);
+
+ skb_pull(skb, 11 /*sizeof(struct snrm_frame)*/);
+
+ irlap_qos_negotiate(self, skb);
+
+ /* Send UA frame and then change link settings */
+ irlap_apply_connection_parameters(self, FALSE);
+ irlap_send_ua_response_frame(self, &self->qos_rx);
+
+ irlap_next_state(self, LAP_NRM_S);
+ irlap_connect_confirm(self, skb);
+
+ /*
+ * The WD-timer could be set to the duration of the
+ * P-timer for this case, but it is recommended
+ * to use twice the value (note 3 IrLAP p. 60).
+ */
+ irlap_start_wd_timer(self, self->wd_timeout);
+ } else {
+ /* We just ignore the other device! */
+ irlap_next_state(self, LAP_SETUP);
+ }
+ break;
+ case RECV_UA_RSP:
+ /* Stop F-timer */
+ del_timer(&self->final_timer);
+
+ /* Initiate connection state */
+ irlap_initiate_connection_state(self);
+
+ /* Negotiate connection parameters */
+ ASSERT(skb->len > 10, return -1;);
+
+ skb_pull(skb, 10 /*sizeof(struct ua_frame)*/);
+
+ ASSERT(self->netdev != NULL, return -1;);
+
+ irlap_qos_negotiate(self, skb);
+
+ /* Set the new link setting *now* (before the rr frame) */
+ irlap_apply_connection_parameters(self, TRUE);
+ self->retry_count = 0;
+
+ /* Wait for turnaround time to give a chance to the other
+ * device to be ready to receive us.
+ * Note : the time to switch speed is typically larger
+ * than the turnaround time, but as we don't have the other
+ * side speed switch time, that's our best guess...
+ * Jean II */
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+
+ /* This frame will actually be sent at the new speed */
+ irlap_send_rr_frame(self, CMD_FRAME);
+
+ irlap_start_final_timer(self, self->final_timeout/2);
+ irlap_next_state(self, LAP_NRM_P);
+
+ irlap_connect_confirm(self, skb);
+ break;
+ case RECV_DM_RSP: /* FALLTHROUGH */
+ case RECV_DISC_CMD:
+ del_timer(&self->final_timer);
+ irlap_next_state(self, LAP_NDM);
+
+ irlap_disconnect_indication(self, LAP_DISC_INDICATION);
+ break;
+ default:
+ IRDA_DEBUG(1, "%s(), Unknown event %d, %s\n", __FUNCTION__, event,
+ irlap_event[event]);
+
+ ret = -1;
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlap_state_offline (self, event, skb, info)
+ *
+ * OFFLINE state, not used for now!
+ *
+ */
+static int irlap_state_offline(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ IRDA_DEBUG( 0, "%s(), Unknown event\n", __FUNCTION__);
+
+ return -1;
+}
+
+/*
+ * Function irlap_state_xmit_p (self, event, skb, info)
+ *
+ * XMIT, Only the primary station has right to transmit, and we
+ * therefore do not expect to receive any transmissions from other
+ * stations.
+ *
+ */
+static int irlap_state_xmit_p(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ int ret = 0;
+
+ switch (event) {
+ case SEND_I_CMD:
+ /*
+ * Only send frame if send-window > 0.
+ */
+ if ((self->window > 0) && (!self->remote_busy)) {
+#ifdef CONFIG_IRDA_DYNAMIC_WINDOW
+ /*
+ * Test if we have transmitted more bytes over the
+ * link than its possible to do with the current
+ * speed and turn-around-time.
+ */
+ if (skb->len > self->bytes_left) {
+ IRDA_DEBUG(4, "%s(), Not allowed to transmit more "
+ "bytes!\n", __FUNCTION__);
+ skb_queue_head(&self->txq, skb_get(skb));
+ /*
+ * We should switch state to LAP_NRM_P, but
+ * that is not possible since we must be sure
+ * that we poll the other side. Since we have
+ * used up our time, the poll timer should
+ * trigger anyway now, so we just wait for it
+ * DB
+ */
+ return -EPROTO;
+ }
+ self->bytes_left -= skb->len;
+#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */
+ /*
+ * Send data with poll bit cleared only if window > 1
+ * and there is more frames after this one to be sent
+ */
+ if ((self->window > 1) &&
+ skb_queue_len( &self->txq) > 0)
+ {
+ irlap_send_data_primary(self, skb);
+ irlap_next_state(self, LAP_XMIT_P);
+ } else {
+ irlap_send_data_primary_poll(self, skb);
+ irlap_next_state(self, LAP_NRM_P);
+
+ /*
+ * Make sure state machine does not try to send
+ * any more frames
+ */
+ ret = -EPROTO;
+ }
+#ifdef CONFIG_IRDA_FAST_RR
+ /* Peer may want to reply immediately */
+ self->fast_RR = FALSE;
+#endif /* CONFIG_IRDA_FAST_RR */
+ } else {
+ IRDA_DEBUG(4, "%s(), Unable to send! remote busy?\n", __FUNCTION__);
+ skb_queue_head(&self->txq, skb_get(skb));
+
+ /*
+ * The next ret is important, because it tells
+ * irlap_next_state _not_ to deliver more frames
+ */
+ ret = -EPROTO;
+ }
+ break;
+ case POLL_TIMER_EXPIRED:
+ IRDA_DEBUG(3, "%s(), POLL_TIMER_EXPIRED (%ld)\n", __FUNCTION__,
+ jiffies);
+ irlap_send_rr_frame(self, CMD_FRAME);
+ /* Return to NRM properly - Jean II */
+ self->window = self->window_size;
+#ifdef CONFIG_IRDA_DYNAMIC_WINDOW
+ /* Allowed to transmit a maximum number of bytes again. */
+ self->bytes_left = self->line_capacity;
+#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */
+ irlap_start_final_timer(self, self->final_timeout);
+ irlap_next_state(self, LAP_NRM_P);
+ break;
+ case DISCONNECT_REQUEST:
+ del_timer(&self->poll_timer);
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_disc_frame(self);
+ irlap_flush_all_queues(self);
+ irlap_start_final_timer(self, self->final_timeout);
+ self->retry_count = 0;
+ irlap_next_state(self, LAP_PCLOSE);
+ break;
+ case DATA_REQUEST:
+ /* Nothing to do, irlap_do_event() will send the packet
+ * when we return... - Jean II */
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown event %s\n", __FUNCTION__,
+ irlap_event[event]);
+
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlap_state_pclose (event, skb, info)
+ *
+ * PCLOSE state
+ */
+static int irlap_state_pclose(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(1, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ switch (event) {
+ case RECV_UA_RSP: /* FALLTHROUGH */
+ case RECV_DM_RSP:
+ del_timer(&self->final_timer);
+
+ /* Set new link parameters */
+ irlap_apply_default_connection_parameters(self);
+
+ /* Always switch state before calling upper layers */
+ irlap_next_state(self, LAP_NDM);
+
+ irlap_disconnect_indication(self, LAP_DISC_INDICATION);
+ break;
+ case FINAL_TIMER_EXPIRED:
+ if (self->retry_count < self->N3) {
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_disc_frame(self);
+ irlap_start_final_timer(self, self->final_timeout);
+ self->retry_count++;
+ /* Keep state */
+ } else {
+ irlap_apply_default_connection_parameters(self);
+
+ /* Always switch state before calling upper layers */
+ irlap_next_state(self, LAP_NDM);
+
+ irlap_disconnect_indication(self, LAP_NO_RESPONSE);
+ }
+ break;
+ default:
+ IRDA_DEBUG(1, "%s(), Unknown event %d\n", __FUNCTION__, event);
+
+ ret = -1;
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlap_state_nrm_p (self, event, skb, info)
+ *
+ * NRM_P (Normal Response Mode as Primary), The primary station has given
+ * permissions to a secondary station to transmit IrLAP resonse frames
+ * (by sending a frame with the P bit set). The primary station will not
+ * transmit any frames and is expecting to receive frames only from the
+ * secondary to which transmission permissions has been given.
+ */
+static int irlap_state_nrm_p(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ int ret = 0;
+ int ns_status;
+ int nr_status;
+
+ switch (event) {
+ case RECV_I_RSP: /* Optimize for the common case */
+ /* FIXME: must check for remote_busy below */
+#ifdef CONFIG_IRDA_FAST_RR
+ /*
+ * Reset the fast_RR so we can use the fast RR code with
+ * full speed the next time since peer may have more frames
+ * to transmitt
+ */
+ self->fast_RR = FALSE;
+#endif /* CONFIG_IRDA_FAST_RR */
+ ASSERT( info != NULL, return -1;);
+
+ ns_status = irlap_validate_ns_received(self, info->ns);
+ nr_status = irlap_validate_nr_received(self, info->nr);
+
+ /*
+ * Check for expected I(nformation) frame
+ */
+ if ((ns_status == NS_EXPECTED) && (nr_status == NR_EXPECTED)) {
+ /* poll bit cleared? */
+ if (!info->pf) {
+ self->vr = (self->vr + 1) % 8;
+
+ /* Update Nr received */
+ irlap_update_nr_received( self, info->nr);
+
+ self->ack_required = TRUE;
+
+ /* Keep state, do not move this line */
+ irlap_next_state(self, LAP_NRM_P);
+
+ irlap_data_indication(self, skb, FALSE);
+ } else {
+ del_timer(&self->final_timer);
+
+ self->vr = (self->vr + 1) % 8;
+
+ /* Update Nr received */
+ irlap_update_nr_received(self, info->nr);
+
+ /*
+ * Got expected NR, so reset the
+ * retry_count. This is not done by IrLAP,
+ * which is strange!
+ */
+ self->retry_count = 0;
+ self->ack_required = TRUE;
+
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+
+ /* Call higher layer *before* changing state
+ * to give them a chance to send data in the
+ * next LAP frame.
+ * Jean II */
+ irlap_data_indication(self, skb, FALSE);
+
+ /* XMIT states are the most dangerous state
+ * to be in, because user requests are
+ * processed directly and may change state.
+ * On the other hand, in NDM_P, those
+ * requests are queued and we will process
+ * them when we return to irlap_do_event().
+ * Jean II
+ */
+ irlap_next_state(self, LAP_XMIT_P);
+
+ /* This is the last frame.
+ * Make sure it's always called in XMIT state.
+ * - Jean II */
+ irlap_start_poll_timer(self, self->poll_timeout);
+ }
+ break;
+
+ }
+ /* Unexpected next to send (Ns) */
+ if ((ns_status == NS_UNEXPECTED) && (nr_status == NR_EXPECTED))
+ {
+ if (!info->pf) {
+ irlap_update_nr_received(self, info->nr);
+
+ /*
+ * Wait until the last frame before doing
+ * anything
+ */
+
+ /* Keep state */
+ irlap_next_state(self, LAP_NRM_P);
+ } else {
+ IRDA_DEBUG(4, "%s(), missing or duplicate frame!\n", __FUNCTION__);
+
+ /* Update Nr received */
+ irlap_update_nr_received(self, info->nr);
+
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_rr_frame(self, CMD_FRAME);
+
+ self->ack_required = FALSE;
+
+ irlap_start_final_timer(self, self->final_timeout);
+ irlap_next_state(self, LAP_NRM_P);
+ }
+ break;
+ }
+ /*
+ * Unexpected next to receive (Nr)
+ */
+ if ((ns_status == NS_EXPECTED) && (nr_status == NR_UNEXPECTED))
+ {
+ if (info->pf) {
+ self->vr = (self->vr + 1) % 8;
+
+ /* Update Nr received */
+ irlap_update_nr_received(self, info->nr);
+
+ /* Resend rejected frames */
+ irlap_resend_rejected_frames(self, CMD_FRAME);
+
+ self->ack_required = FALSE;
+ irlap_start_final_timer(self, self->final_timeout);
+
+ /* Keep state, do not move this line */
+ irlap_next_state(self, LAP_NRM_P);
+
+ irlap_data_indication(self, skb, FALSE);
+ } else {
+ /*
+ * Do not resend frames until the last
+ * frame has arrived from the other
+ * device. This is not documented in
+ * IrLAP!!
+ */
+ self->vr = (self->vr + 1) % 8;
+
+ /* Update Nr received */
+ irlap_update_nr_received(self, info->nr);
+
+ self->ack_required = FALSE;
+
+ /* Keep state, do not move this line!*/
+ irlap_next_state(self, LAP_NRM_P);
+
+ irlap_data_indication(self, skb, FALSE);
+ }
+ break;
+ }
+ /*
+ * Unexpected next to send (Ns) and next to receive (Nr)
+ * Not documented by IrLAP!
+ */
+ if ((ns_status == NS_UNEXPECTED) &&
+ (nr_status == NR_UNEXPECTED))
+ {
+ IRDA_DEBUG(4, "%s(), unexpected nr and ns!\n", __FUNCTION__);
+ if (info->pf) {
+ /* Resend rejected frames */
+ irlap_resend_rejected_frames(self, CMD_FRAME);
+
+ /* Give peer some time to retransmit! */
+ irlap_start_final_timer(self, self->final_timeout);
+
+ /* Keep state, do not move this line */
+ irlap_next_state(self, LAP_NRM_P);
+ } else {
+ /* Update Nr received */
+ /* irlap_update_nr_received( info->nr); */
+
+ self->ack_required = FALSE;
+ }
+ break;
+ }
+
+ /*
+ * Invalid NR or NS
+ */
+ if ((nr_status == NR_INVALID) || (ns_status == NS_INVALID)) {
+ if (info->pf) {
+ del_timer(&self->final_timer);
+
+ irlap_next_state(self, LAP_RESET_WAIT);
+
+ irlap_disconnect_indication(self, LAP_RESET_INDICATION);
+ self->xmitflag = TRUE;
+ } else {
+ del_timer(&self->final_timer);
+
+ irlap_disconnect_indication(self, LAP_RESET_INDICATION);
+
+ self->xmitflag = FALSE;
+ }
+ break;
+ }
+ IRDA_DEBUG(1, "%s(), Not implemented!\n", __FUNCTION__);
+ IRDA_DEBUG(1, "%s(), event=%s, ns_status=%d, nr_status=%d\n", __FUNCTION__,
+ irlap_event[ event], ns_status, nr_status);
+ break;
+ case RECV_UI_FRAME:
+ /* Poll bit cleared? */
+ if (!info->pf) {
+ irlap_data_indication(self, skb, TRUE);
+ irlap_next_state(self, LAP_NRM_P);
+ } else {
+ del_timer(&self->final_timer);
+ irlap_data_indication(self, skb, TRUE);
+ irlap_next_state(self, LAP_XMIT_P);
+ printk("%s(): RECV_UI_FRAME: next state %s\n", __FUNCTION__, irlap_state[self->state]);
+ irlap_start_poll_timer(self, self->poll_timeout);
+ }
+ break;
+ case RECV_RR_RSP:
+ /*
+ * If you get a RR, the remote isn't busy anymore,
+ * no matter what the NR
+ */
+ self->remote_busy = FALSE;
+
+ /*
+ * Nr as expected?
+ */
+ ret = irlap_validate_nr_received(self, info->nr);
+ if (ret == NR_EXPECTED) {
+ /* Stop final timer */
+ del_timer(&self->final_timer);
+
+ /* Update Nr received */
+ irlap_update_nr_received(self, info->nr);
+
+ /*
+ * Got expected NR, so reset the retry_count. This
+ * is not done by the IrLAP standard , which is
+ * strange! DB.
+ */
+ self->retry_count = 0;
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+
+ irlap_next_state(self, LAP_XMIT_P);
+
+ /* Start poll timer */
+ irlap_start_poll_timer(self, self->poll_timeout);
+ } else if (ret == NR_UNEXPECTED) {
+ ASSERT(info != NULL, return -1;);
+ /*
+ * Unexpected nr!
+ */
+
+ /* Update Nr received */
+ irlap_update_nr_received(self, info->nr);
+
+ IRDA_DEBUG(4, "RECV_RR_FRAME: Retrans:%d, nr=%d, va=%d, "
+ "vs=%d, vr=%d\n",
+ self->retry_count, info->nr, self->va,
+ self->vs, self->vr);
+
+ /* Resend rejected frames */
+ irlap_resend_rejected_frames(self, CMD_FRAME);
+
+ irlap_next_state(self, LAP_NRM_P);
+ } else if (ret == NR_INVALID) {
+ IRDA_DEBUG(1, "%s(), Received RR with "
+ "invalid nr !\n", __FUNCTION__);
+ del_timer(&self->final_timer);
+
+ irlap_next_state(self, LAP_RESET_WAIT);
+
+ irlap_disconnect_indication(self, LAP_RESET_INDICATION);
+ self->xmitflag = TRUE;
+ }
+ break;
+ case RECV_RNR_RSP:
+ ASSERT(info != NULL, return -1;);
+
+ /* Stop final timer */
+ del_timer(&self->final_timer);
+ self->remote_busy = TRUE;
+
+ /* Update Nr received */
+ irlap_update_nr_received(self, info->nr);
+ irlap_next_state(self, LAP_XMIT_P);
+
+ /* Start poll timer */
+ irlap_start_poll_timer(self, self->poll_timeout);
+ break;
+ case RECV_FRMR_RSP:
+ del_timer(&self->final_timer);
+ self->xmitflag = TRUE;
+ irlap_next_state(self, LAP_RESET_WAIT);
+ irlap_reset_indication(self);
+ break;
+ case FINAL_TIMER_EXPIRED:
+ /*
+ * We are allowed to wait for additional 300 ms if
+ * final timer expires when we are in the middle
+ * of receiving a frame (page 45, IrLAP). Check that
+ * we only do this once for each frame.
+ */
+ if (irda_device_is_receiving(self->netdev) && !self->add_wait) {
+ IRDA_DEBUG(1, "FINAL_TIMER_EXPIRED when receiving a "
+ "frame! Waiting a little bit more!\n");
+ irlap_start_final_timer(self, MSECS_TO_JIFFIES(300));
+
+ /*
+ * Don't allow this to happen one more time in a row,
+ * or else we can get a pretty tight loop here if
+ * if we only receive half a frame. DB.
+ */
+ self->add_wait = TRUE;
+ break;
+ }
+ self->add_wait = FALSE;
+
+ /* N2 is the disconnect timer. Until we reach it, we retry */
+ if (self->retry_count < self->N2) {
+ /* Retry sending the pf bit to the secondary */
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_rr_frame(self, CMD_FRAME);
+
+ irlap_start_final_timer(self, self->final_timeout);
+ self->retry_count++;
+ IRDA_DEBUG(4, "irlap_state_nrm_p: FINAL_TIMER_EXPIRED:"
+ " retry_count=%d\n", self->retry_count);
+
+ /* Early warning event. I'm using a pretty liberal
+ * interpretation of the spec and generate an event
+ * every time the timer is multiple of N1 (and not
+ * only the first time). This allow application
+ * to know precisely if connectivity restart...
+ * Jean II */
+ if((self->retry_count % self->N1) == 0)
+ irlap_status_indication(self,
+ STATUS_NO_ACTIVITY);
+
+ /* Keep state */
+ } else {
+ irlap_apply_default_connection_parameters(self);
+
+ /* Always switch state before calling upper layers */
+ irlap_next_state(self, LAP_NDM);
+ irlap_disconnect_indication(self, LAP_NO_RESPONSE);
+ }
+ break;
+ case RECV_REJ_RSP:
+ irlap_update_nr_received(self, info->nr);
+ if (self->remote_busy) {
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_rr_frame(self, CMD_FRAME);
+ } else
+ irlap_resend_rejected_frames(self, CMD_FRAME);
+ irlap_start_final_timer(self, self->final_timeout);
+ break;
+ case RECV_SREJ_RSP:
+ irlap_update_nr_received(self, info->nr);
+ if (self->remote_busy) {
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_rr_frame(self, CMD_FRAME);
+ } else
+ irlap_resend_rejected_frame(self, CMD_FRAME);
+ irlap_start_final_timer(self, self->final_timeout);
+ break;
+ case RECV_RD_RSP:
+ IRDA_DEBUG(1, "%s(), RECV_RD_RSP\n", __FUNCTION__);
+
+ irlap_flush_all_queues(self);
+ irlap_next_state(self, LAP_XMIT_P);
+ /* Call back the LAP state machine to do a proper disconnect */
+ irlap_disconnect_request(self);
+ break;
+ default:
+ IRDA_DEBUG(1, "%s(), Unknown event %s\n", __FUNCTION__,
+ irlap_event[event]);
+
+ ret = -1;
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlap_state_reset_wait (event, skb, info)
+ *
+ * We have informed the service user of a reset condition, and is
+ * awaiting reset of disconnect request.
+ *
+ */
+static int irlap_state_reset_wait(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(3, "%s(), event = %s\n", __FUNCTION__, irlap_event[event]);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ switch (event) {
+ case RESET_REQUEST:
+ if (self->xmitflag) {
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_snrm_frame(self, NULL);
+ irlap_start_final_timer(self, self->final_timeout);
+ irlap_next_state(self, LAP_RESET);
+ } else {
+ irlap_start_final_timer(self, self->final_timeout);
+ irlap_next_state(self, LAP_RESET);
+ }
+ break;
+ case DISCONNECT_REQUEST:
+ irlap_wait_min_turn_around( self, &self->qos_tx);
+ irlap_send_disc_frame( self);
+ irlap_flush_all_queues( self);
+ irlap_start_final_timer( self, self->final_timeout);
+ self->retry_count = 0;
+ irlap_next_state( self, LAP_PCLOSE);
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown event %s\n", __FUNCTION__,
+ irlap_event[event]);
+
+ ret = -1;
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlap_state_reset (self, event, skb, info)
+ *
+ * We have sent a SNRM reset command to the peer layer, and is awaiting
+ * reply.
+ *
+ */
+static int irlap_state_reset(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(3, "%s(), event = %s\n", __FUNCTION__, irlap_event[event]);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ switch (event) {
+ case RECV_DISC_CMD:
+ del_timer(&self->final_timer);
+
+ irlap_apply_default_connection_parameters(self);
+
+ /* Always switch state before calling upper layers */
+ irlap_next_state(self, LAP_NDM);
+
+ irlap_disconnect_indication(self, LAP_NO_RESPONSE);
+
+ break;
+ case RECV_UA_RSP:
+ del_timer(&self->final_timer);
+
+ /* Initiate connection state */
+ irlap_initiate_connection_state(self);
+
+ irlap_reset_confirm();
+
+ self->remote_busy = FALSE;
+
+ irlap_next_state(self, LAP_XMIT_P);
+
+ irlap_start_poll_timer(self, self->poll_timeout);
+
+ break;
+ case FINAL_TIMER_EXPIRED:
+ if (self->retry_count < 3) {
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+
+ ASSERT(self->netdev != NULL, return -1;);
+ irlap_send_snrm_frame(self, self->qos_dev);
+
+ self->retry_count++; /* Experimental!! */
+
+ irlap_start_final_timer(self, self->final_timeout);
+ irlap_next_state(self, LAP_RESET);
+ } else if (self->retry_count >= self->N3) {
+ irlap_apply_default_connection_parameters(self);
+
+ /* Always switch state before calling upper layers */
+ irlap_next_state(self, LAP_NDM);
+
+ irlap_disconnect_indication(self, LAP_NO_RESPONSE);
+ }
+ break;
+ case RECV_SNRM_CMD:
+ /*
+ * SNRM frame is not allowed to contain an I-field in this
+ * state
+ */
+ if (!info) {
+ IRDA_DEBUG(3, "%s(), RECV_SNRM_CMD\n", __FUNCTION__);
+ irlap_initiate_connection_state(self);
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_ua_response_frame(self, &self->qos_rx);
+ irlap_reset_confirm();
+ irlap_start_wd_timer(self, self->wd_timeout);
+ irlap_next_state(self, LAP_NDM);
+ } else {
+ IRDA_DEBUG(0, "%s(), SNRM frame contained an I field!\n", __FUNCTION__);
+ }
+ break;
+ default:
+ IRDA_DEBUG(1, "%s(), Unknown event %s\n", __FUNCTION__,
+ irlap_event[event]);
+
+ ret = -1;
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlap_state_xmit_s (event, skb, info)
+ *
+ * XMIT_S, The secondary station has been given the right to transmit,
+ * and we therefor do not expect to receive any transmissions from other
+ * stations.
+ */
+static int irlap_state_xmit_s(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(4, "%s(), event=%s\n", __FUNCTION__, irlap_event[event]);
+
+ ASSERT(self != NULL, return -ENODEV;);
+ ASSERT(self->magic == LAP_MAGIC, return -EBADR;);
+
+ switch (event) {
+ case SEND_I_CMD:
+ /*
+ * Send frame only if send window > 1
+ */
+ if ((self->window > 0) && (!self->remote_busy)) {
+#ifdef CONFIG_IRDA_DYNAMIC_WINDOW
+ /*
+ * Test if we have transmitted more bytes over the
+ * link than its possible to do with the current
+ * speed and turn-around-time.
+ */
+ if (skb->len > self->bytes_left) {
+ skb_queue_head(&self->txq, skb_get(skb));
+
+ /*
+ * Switch to NRM_S, this is only possible
+ * when we are in secondary mode, since we
+ * must be sure that we don't miss any RR
+ * frames
+ */
+ self->window = self->window_size;
+ self->bytes_left = self->line_capacity;
+ irlap_start_wd_timer(self, self->wd_timeout);
+
+ irlap_next_state(self, LAP_NRM_S);
+
+ return -EPROTO; /* Try again later */
+ }
+ self->bytes_left -= skb->len;
+#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */
+ /*
+ * Send data with final bit cleared only if window > 1
+ * and there is more frames to be sent
+ */
+ if ((self->window > 1) &&
+ skb_queue_len(&self->txq) > 0)
+ {
+ irlap_send_data_secondary(self, skb);
+ irlap_next_state(self, LAP_XMIT_S);
+ } else {
+ irlap_send_data_secondary_final(self, skb);
+ irlap_next_state(self, LAP_NRM_S);
+
+ /*
+ * Make sure state machine does not try to send
+ * any more frames
+ */
+ ret = -EPROTO;
+ }
+ } else {
+ IRDA_DEBUG(2, "%s(), Unable to send!\n", __FUNCTION__);
+ skb_queue_head(&self->txq, skb_get(skb));
+ ret = -EPROTO;
+ }
+ break;
+ case DISCONNECT_REQUEST:
+ irlap_send_rd_frame(self);
+ irlap_flush_all_queues(self);
+ irlap_start_wd_timer(self, self->wd_timeout);
+ irlap_next_state(self, LAP_SCLOSE);
+ break;
+ case DATA_REQUEST:
+ /* Nothing to do, irlap_do_event() will send the packet
+ * when we return... - Jean II */
+ break;
+ default:
+ IRDA_DEBUG(2, "%s(), Unknown event %s\n", __FUNCTION__,
+ irlap_event[event]);
+
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlap_state_nrm_s (event, skb, info)
+ *
+ * NRM_S (Normal Response Mode as Secondary) state, in this state we are
+ * expecting to receive frames from the primary station
+ *
+ */
+static int irlap_state_nrm_s(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ int ns_status;
+ int nr_status;
+ int ret = 0;
+
+ IRDA_DEBUG(4, "%s(), event=%s\n", __FUNCTION__, irlap_event[ event]);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ switch (event) {
+ case RECV_I_CMD: /* Optimize for the common case */
+ /* FIXME: must check for remote_busy below */
+ IRDA_DEBUG(4, "%s(), event=%s nr=%d, vs=%d, ns=%d, "
+ "vr=%d, pf=%d\n", __FUNCTION__, irlap_event[event], info->nr,
+ self->vs, info->ns, self->vr, info->pf);
+
+ self->retry_count = 0;
+
+ ns_status = irlap_validate_ns_received(self, info->ns);
+ nr_status = irlap_validate_nr_received(self, info->nr);
+ /*
+ * Check for expected I(nformation) frame
+ */
+ if ((ns_status == NS_EXPECTED) && (nr_status == NR_EXPECTED)) {
+ /*
+ * poll bit cleared?
+ */
+ if (!info->pf) {
+ self->vr = (self->vr + 1) % 8;
+
+ /* Update Nr received */
+ irlap_update_nr_received(self, info->nr);
+
+ self->ack_required = TRUE;
+
+ /*
+ * Starting WD-timer here is optional, but
+ * not recommended. Note 6 IrLAP p. 83
+ */
+#if 0
+ irda_start_timer(WD_TIMER, self->wd_timeout);
+#endif
+ /* Keep state, do not move this line */
+ irlap_next_state(self, LAP_NRM_S);
+
+ irlap_data_indication(self, skb, FALSE);
+ break;
+ } else {
+ self->vr = (self->vr + 1) % 8;
+
+ /* Update Nr received */
+ irlap_update_nr_received(self, info->nr);
+
+ /*
+ * We should wait before sending RR, and
+ * also before changing to XMIT_S
+ * state. (note 1, IrLAP p. 82)
+ */
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+
+ /*
+ * Give higher layers a chance to
+ * immediately reply with some data before
+ * we decide if we should send a RR frame
+ * or not
+ */
+ irlap_data_indication(self, skb, FALSE);
+
+ /* Any pending data requests? */
+ if ((skb_queue_len(&self->txq) > 0) &&
+ (self->window > 0))
+ {
+ self->ack_required = TRUE;
+
+ del_timer(&self->wd_timer);
+
+ irlap_next_state(self, LAP_XMIT_S);
+ } else {
+ irlap_send_rr_frame(self, RSP_FRAME);
+ irlap_start_wd_timer(self,
+ self->wd_timeout);
+
+ /* Keep the state */
+ irlap_next_state(self, LAP_NRM_S);
+ }
+ break;
+ }
+ }
+ /*
+ * Check for Unexpected next to send (Ns)
+ */
+ if ((ns_status == NS_UNEXPECTED) && (nr_status == NR_EXPECTED))
+ {
+ /* Unexpected next to send, with final bit cleared */
+ if (!info->pf) {
+ irlap_update_nr_received(self, info->nr);
+
+ irlap_start_wd_timer(self, self->wd_timeout);
+ } else {
+ /* Update Nr received */
+ irlap_update_nr_received(self, info->nr);
+
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_rr_frame(self, RSP_FRAME);
+
+ irlap_start_wd_timer(self, self->wd_timeout);
+ }
+ break;
+ }
+
+ /*
+ * Unexpected Next to Receive(NR) ?
+ */
+ if ((ns_status == NS_EXPECTED) && (nr_status == NR_UNEXPECTED))
+ {
+ if (info->pf) {
+ IRDA_DEBUG(4, "RECV_I_RSP: frame(s) lost\n");
+
+ self->vr = (self->vr + 1) % 8;
+
+ /* Update Nr received */
+ irlap_update_nr_received(self, info->nr);
+
+ /* Resend rejected frames */
+ irlap_resend_rejected_frames(self, RSP_FRAME);
+
+ /* Keep state, do not move this line */
+ irlap_next_state(self, LAP_NRM_S);
+
+ irlap_data_indication(self, skb, FALSE);
+ irlap_start_wd_timer(self, self->wd_timeout);
+ break;
+ }
+ /*
+ * This is not documented in IrLAP!! Unexpected NR
+ * with poll bit cleared
+ */
+ if (!info->pf) {
+ self->vr = (self->vr + 1) % 8;
+
+ /* Update Nr received */
+ irlap_update_nr_received(self, info->nr);
+
+ /* Keep state, do not move this line */
+ irlap_next_state(self, LAP_NRM_S);
+
+ irlap_data_indication(self, skb, FALSE);
+ irlap_start_wd_timer(self, self->wd_timeout);
+ }
+ break;
+ }
+
+ if (ret == NR_INVALID) {
+ IRDA_DEBUG(0, "NRM_S, NR_INVALID not implemented!\n");
+ }
+ if (ret == NS_INVALID) {
+ IRDA_DEBUG(0, "NRM_S, NS_INVALID not implemented!\n");
+ }
+ break;
+ case RECV_UI_FRAME:
+ /*
+ * poll bit cleared?
+ */
+ if (!info->pf) {
+ irlap_data_indication(self, skb, TRUE);
+ irlap_next_state(self, LAP_NRM_S); /* Keep state */
+ } else {
+ /*
+ * Any pending data requests?
+ */
+ if ((skb_queue_len(&self->txq) > 0) &&
+ (self->window > 0) && !self->remote_busy)
+ {
+ irlap_data_indication(self, skb, TRUE);
+
+ del_timer(&self->wd_timer);
+
+ irlap_next_state(self, LAP_XMIT_S);
+ } else {
+ irlap_data_indication(self, skb, TRUE);
+
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+
+ irlap_send_rr_frame(self, RSP_FRAME);
+ self->ack_required = FALSE;
+
+ irlap_start_wd_timer(self, self->wd_timeout);
+
+ /* Keep the state */
+ irlap_next_state(self, LAP_NRM_S);
+ }
+ }
+ break;
+ case RECV_RR_CMD:
+ self->retry_count = 0;
+
+ /*
+ * Nr as expected?
+ */
+ nr_status = irlap_validate_nr_received(self, info->nr);
+ if (nr_status == NR_EXPECTED) {
+ if ((skb_queue_len( &self->txq) > 0) &&
+ (self->window > 0)) {
+ self->remote_busy = FALSE;
+
+ /* Update Nr received */
+ irlap_update_nr_received(self, info->nr);
+ del_timer(&self->wd_timer);
+
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_next_state(self, LAP_XMIT_S);
+ } else {
+ self->remote_busy = FALSE;
+ /* Update Nr received */
+ irlap_update_nr_received(self, info->nr);
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_start_wd_timer(self, self->wd_timeout);
+
+ /* Note : if the link is idle (this case),
+ * we never go in XMIT_S, so we never get a
+ * chance to process any DISCONNECT_REQUEST.
+ * Do it now ! - Jean II */
+ if (self->disconnect_pending) {
+ /* Disconnect */
+ irlap_send_rd_frame(self);
+ irlap_flush_all_queues(self);
+
+ irlap_next_state(self, LAP_SCLOSE);
+ } else {
+ /* Just send back pf bit */
+ irlap_send_rr_frame(self, RSP_FRAME);
+
+ irlap_next_state(self, LAP_NRM_S);
+ }
+ }
+ } else if (nr_status == NR_UNEXPECTED) {
+ self->remote_busy = FALSE;
+ irlap_update_nr_received(self, info->nr);
+ irlap_resend_rejected_frames(self, RSP_FRAME);
+
+ irlap_start_wd_timer(self, self->wd_timeout);
+
+ /* Keep state */
+ irlap_next_state(self, LAP_NRM_S);
+ } else {
+ IRDA_DEBUG(1, "%s(), invalid nr not implemented!\n", __FUNCTION__);
+ }
+ break;
+ case RECV_SNRM_CMD:
+ /* SNRM frame is not allowed to contain an I-field */
+ if (!info) {
+ del_timer(&self->wd_timer);
+ IRDA_DEBUG(1, "%s(), received SNRM cmd\n", __FUNCTION__);
+ irlap_next_state(self, LAP_RESET_CHECK);
+
+ irlap_reset_indication(self);
+ } else {
+ IRDA_DEBUG(0, "%s(), SNRM frame contained an I-field!\n", __FUNCTION__);
+
+ }
+ break;
+ case RECV_REJ_CMD:
+ irlap_update_nr_received(self, info->nr);
+ if (self->remote_busy) {
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_rr_frame(self, RSP_FRAME);
+ } else
+ irlap_resend_rejected_frames(self, RSP_FRAME);
+ irlap_start_wd_timer(self, self->wd_timeout);
+ break;
+ case RECV_SREJ_CMD:
+ irlap_update_nr_received(self, info->nr);
+ if (self->remote_busy) {
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_rr_frame(self, RSP_FRAME);
+ } else
+ irlap_resend_rejected_frame(self, RSP_FRAME);
+ irlap_start_wd_timer(self, self->wd_timeout);
+ break;
+ case WD_TIMER_EXPIRED:
+ /*
+ * Wait until retry_count * n matches negotiated threshold/
+ * disconnect time (note 2 in IrLAP p. 82)
+ *
+ * Similar to irlap_state_nrm_p() -> FINAL_TIMER_EXPIRED
+ * Note : self->wd_timeout = (self->final_timeout * 2),
+ * which explain why we use (self->N2 / 2) here !!!
+ * Jean II
+ */
+ IRDA_DEBUG(1, "%s(), retry_count = %d\n", __FUNCTION__,
+ self->retry_count);
+
+ if (self->retry_count < (self->N2 / 2)) {
+ /* No retry, just wait for primary */
+ irlap_start_wd_timer(self, self->wd_timeout);
+ self->retry_count++;
+
+ if((self->retry_count % (self->N1 / 2)) == 0)
+ irlap_status_indication(self,
+ STATUS_NO_ACTIVITY);
+ } else {
+ irlap_apply_default_connection_parameters(self);
+
+ /* Always switch state before calling upper layers */
+ irlap_next_state(self, LAP_NDM);
+ irlap_disconnect_indication(self, LAP_NO_RESPONSE);
+ }
+ break;
+ case RECV_DISC_CMD:
+ /* Always switch state before calling upper layers */
+ irlap_next_state(self, LAP_NDM);
+
+ /* Send disconnect response */
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_ua_response_frame(self, NULL);
+
+ del_timer(&self->wd_timer);
+ irlap_flush_all_queues(self);
+ /* Set default link parameters */
+ irlap_apply_default_connection_parameters(self);
+
+ irlap_disconnect_indication(self, LAP_DISC_INDICATION);
+ break;
+ case RECV_DISCOVERY_XID_CMD:
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_rr_frame(self, RSP_FRAME);
+ self->ack_required = TRUE;
+ irlap_start_wd_timer(self, self->wd_timeout);
+ irlap_next_state(self, LAP_NRM_S);
+
+ break;
+ case RECV_TEST_CMD:
+ /* Remove test frame header (only LAP header in NRM) */
+ skb_pull(skb, LAP_ADDR_HEADER + LAP_CTRL_HEADER);
+
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_start_wd_timer(self, self->wd_timeout);
+
+ /* Send response (info will be copied) */
+ irlap_send_test_frame(self, self->caddr, info->daddr, skb);
+ break;
+ default:
+ IRDA_DEBUG(1, "%s(), Unknown event %d, (%s)\n", __FUNCTION__,
+ event, irlap_event[event]);
+
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlap_state_sclose (self, event, skb, info)
+ *
+ *
+ *
+ */
+static int irlap_state_sclose(struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb, struct irlap_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(1, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -ENODEV;);
+ ASSERT(self->magic == LAP_MAGIC, return -EBADR;);
+
+ switch (event) {
+ case RECV_DISC_CMD:
+ /* Always switch state before calling upper layers */
+ irlap_next_state(self, LAP_NDM);
+
+ /* Send disconnect response */
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_ua_response_frame(self, NULL);
+
+ del_timer(&self->wd_timer);
+ /* Set default link parameters */
+ irlap_apply_default_connection_parameters(self);
+
+ irlap_disconnect_indication(self, LAP_DISC_INDICATION);
+ break;
+ case RECV_DM_RSP:
+ /* Always switch state before calling upper layers */
+ irlap_next_state(self, LAP_NDM);
+
+ del_timer(&self->wd_timer);
+ irlap_apply_default_connection_parameters(self);
+
+ irlap_disconnect_indication(self, LAP_DISC_INDICATION);
+ break;
+ case WD_TIMER_EXPIRED:
+ /* Always switch state before calling upper layers */
+ irlap_next_state(self, LAP_NDM);
+
+ irlap_apply_default_connection_parameters(self);
+
+ irlap_disconnect_indication(self, LAP_DISC_INDICATION);
+ break;
+ default:
+ IRDA_DEBUG(1, "%s(), Unknown event %d, (%s)\n", __FUNCTION__,
+ event, irlap_event[event]);
+
+ ret = -EINVAL;
+ break;
+ }
+
+ return -1;
+}
+
+static int irlap_state_reset_check( struct irlap_cb *self, IRLAP_EVENT event,
+ struct sk_buff *skb,
+ struct irlap_info *info)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(1, "%s(), event=%s\n", __FUNCTION__, irlap_event[event]);
+
+ ASSERT(self != NULL, return -ENODEV;);
+ ASSERT(self->magic == LAP_MAGIC, return -EBADR;);
+
+ switch (event) {
+ case RESET_RESPONSE:
+ irlap_send_ua_response_frame(self, &self->qos_rx);
+ irlap_initiate_connection_state(self);
+ irlap_start_wd_timer(self, WD_TIMEOUT);
+ irlap_flush_all_queues(self);
+
+ irlap_next_state(self, LAP_NRM_S);
+ break;
+ case DISCONNECT_REQUEST:
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_send_rd_frame(self);
+ irlap_start_wd_timer(self, WD_TIMEOUT);
+ irlap_next_state(self, LAP_SCLOSE);
+ break;
+ default:
+ IRDA_DEBUG(1, "%s(), Unknown event %d, (%s)\n", __FUNCTION__,
+ event, irlap_event[event]);
+
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/irlap_frame.c b/uClinux-2.4.31-uc0/net/irda/irlap_frame.c
new file mode 100644
index 0000000..527f7ef
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlap_frame.c
@@ -0,0 +1,1374 @@
+/*********************************************************************
+ *
+ * Filename: irlap_frame.c
+ * Version: 1.0
+ * Description: Build and transmit IrLAP frames
+ * Status: Stable
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Tue Aug 19 10:27:26 1997
+ * Modified at: Wed Jan 5 08:59:04 2000
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1998-2000 Dag Brattli <dagb@cs.uit.no>,
+ * All Rights Reserved.
+ * Copyright (c) 2000-2001 Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/skbuff.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/netdevice.h>
+#include <linux/irda.h>
+
+#include <net/pkt_sched.h>
+#include <net/sock.h>
+
+#include <asm/byteorder.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irda_device.h>
+#include <net/irda/irlap.h>
+#include <net/irda/wrapper.h>
+#include <net/irda/timer.h>
+#include <net/irda/irlap_frame.h>
+#include <net/irda/qos.h>
+
+/*
+ * Function irlap_insert_info (self, skb)
+ *
+ * Insert minimum turnaround time and speed information into the skb. We
+ * need to do this since it's per packet relevant information. Safe to
+ * have this function inlined since it's only called from one place
+ */
+static inline void irlap_insert_info(struct irlap_cb *self,
+ struct sk_buff *skb)
+{
+ struct irda_skb_cb *cb = (struct irda_skb_cb *) skb->cb;
+
+ /*
+ * Insert MTT (min. turn time) and speed into skb, so that the
+ * device driver knows which settings to use
+ */
+ cb->magic = LAP_MAGIC;
+ cb->mtt = self->mtt_required;
+ cb->next_speed = self->speed;
+
+ /* Reset */
+ self->mtt_required = 0;
+
+ /*
+ * Delay equals negotiated BOFs count, plus the number of BOFs to
+ * force the negotiated minimum turnaround time
+ */
+ cb->xbofs = self->bofs_count;
+ cb->next_xbofs = self->next_bofs;
+ cb->xbofs_delay = self->xbofs_delay;
+
+ /* Reset XBOF's delay (used only for getting min turn time) */
+ self->xbofs_delay = 0;
+ /* Put the correct xbofs value for the next packet */
+ self->bofs_count = self->next_bofs;
+}
+
+/*
+ * Function irlap_queue_xmit (self, skb)
+ *
+ * A little wrapper for dev_queue_xmit, so we can insert some common
+ * code into it.
+ */
+void irlap_queue_xmit(struct irlap_cb *self, struct sk_buff *skb)
+{
+ /* Some common init stuff */
+ skb->dev = self->netdev;
+ skb->h.raw = skb->nh.raw = skb->mac.raw = skb->data;
+ skb->protocol = htons(ETH_P_IRDA);
+ skb->priority = TC_PRIO_BESTEFFORT;
+
+ irlap_insert_info(self, skb);
+
+ dev_queue_xmit(skb);
+}
+
+/*
+ * Function irlap_send_snrm_cmd (void)
+ *
+ * Transmits a connect SNRM command frame
+ */
+void irlap_send_snrm_frame(struct irlap_cb *self, struct qos_info *qos)
+{
+ struct sk_buff *skb;
+ struct snrm_frame *frame;
+ int ret;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ /* Allocate frame */
+ skb = dev_alloc_skb(64);
+ if (!skb)
+ return;
+
+ frame = (struct snrm_frame *) skb_put(skb, 2);
+
+ /* Insert connection address field */
+ if (qos)
+ frame->caddr = CMD_FRAME | CBROADCAST;
+ else
+ frame->caddr = CMD_FRAME | self->caddr;
+
+ /* Insert control field */
+ frame->control = SNRM_CMD | PF_BIT;
+
+ /*
+ * If we are establishing a connection then insert QoS paramerters
+ */
+ if (qos) {
+ skb_put(skb, 9); /* 21 left */
+ frame->saddr = cpu_to_le32(self->saddr);
+ frame->daddr = cpu_to_le32(self->daddr);
+
+ frame->ncaddr = self->caddr;
+
+ ret = irlap_insert_qos_negotiation_params(self, skb);
+ if (ret < 0) {
+ dev_kfree_skb(skb);
+ return;
+ }
+ }
+ irlap_queue_xmit(self, skb);
+}
+
+/*
+ * Function irlap_recv_snrm_cmd (skb, info)
+ *
+ * Received SNRM (Set Normal Response Mode) command frame
+ *
+ */
+static void irlap_recv_snrm_cmd(struct irlap_cb *self, struct sk_buff *skb,
+ struct irlap_info *info)
+{
+ struct snrm_frame *frame;
+
+ frame = (struct snrm_frame *) skb->data;
+
+ if (skb->len >= sizeof(struct snrm_frame)) {
+ /* Copy the new connection address ignoring the C/R bit */
+ info->caddr = frame->ncaddr & 0xFE;
+
+ /* Check if the new connection address is valid */
+ if ((info->caddr == 0x00) || (info->caddr == 0xfe)) {
+ IRDA_DEBUG(3, "%s(), invalid connection address!\n", __FUNCTION__);
+ return;
+ }
+
+ /* Copy peer device address */
+ info->daddr = le32_to_cpu(frame->saddr);
+ info->saddr = le32_to_cpu(frame->daddr);
+
+ /* Only accept if addressed directly to us */
+ if (info->saddr != self->saddr) {
+ IRDA_DEBUG(2, "%s(), not addressed to us!\n", __FUNCTION__);
+ return;
+ }
+ irlap_do_event(self, RECV_SNRM_CMD, skb, info);
+ } else {
+ /* Signal that this SNRM frame does not contain and I-field */
+ irlap_do_event(self, RECV_SNRM_CMD, skb, NULL);
+ }
+}
+
+/*
+ * Function irlap_send_ua_response_frame (qos)
+ *
+ * Send UA (Unnumbered Acknowledgement) frame
+ *
+ */
+void irlap_send_ua_response_frame(struct irlap_cb *self, struct qos_info *qos)
+{
+ struct sk_buff *skb;
+ struct ua_frame *frame;
+ int ret;
+
+ IRDA_DEBUG(2, "%s() <%ld>\n", __FUNCTION__, jiffies);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ skb = NULL;
+
+ /* Allocate frame */
+ skb = dev_alloc_skb(64);
+ if (!skb)
+ return;
+
+ frame = (struct ua_frame *) skb_put(skb, 10);
+
+ /* Build UA response */
+ frame->caddr = self->caddr;
+ frame->control = UA_RSP | PF_BIT;
+
+ frame->saddr = cpu_to_le32(self->saddr);
+ frame->daddr = cpu_to_le32(self->daddr);
+
+ /* Should we send QoS negotiation parameters? */
+ if (qos) {
+ ret = irlap_insert_qos_negotiation_params(self, skb);
+ if (ret < 0) {
+ dev_kfree_skb(skb);
+ return;
+ }
+ }
+
+ irlap_queue_xmit(self, skb);
+}
+
+
+/*
+ * Function irlap_send_dm_frame (void)
+ *
+ * Send disconnected mode (DM) frame
+ *
+ */
+void irlap_send_dm_frame( struct irlap_cb *self)
+{
+ struct sk_buff *skb = NULL;
+ __u8 *frame;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ skb = dev_alloc_skb(32);
+ if (!skb)
+ return;
+
+ frame = skb_put( skb, 2);
+
+ if (self->state == LAP_NDM)
+ frame[0] = CBROADCAST;
+ else
+ frame[0] = self->caddr;
+
+ frame[1] = DM_RSP | PF_BIT;
+
+ irlap_queue_xmit(self, skb);
+}
+
+/*
+ * Function irlap_send_disc_frame (void)
+ *
+ * Send disconnect (DISC) frame
+ *
+ */
+void irlap_send_disc_frame(struct irlap_cb *self)
+{
+ struct sk_buff *skb = NULL;
+ __u8 *frame;
+
+ IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ skb = dev_alloc_skb(16);
+ if (!skb)
+ return;
+
+ frame = skb_put(skb, 2);
+
+ frame[0] = self->caddr | CMD_FRAME;
+ frame[1] = DISC_CMD | PF_BIT;
+
+ irlap_queue_xmit(self, skb);
+}
+
+/*
+ * Function irlap_send_discovery_xid_frame (S, s, command)
+ *
+ * Build and transmit a XID (eXchange station IDentifier) discovery
+ * frame.
+ */
+void irlap_send_discovery_xid_frame(struct irlap_cb *self, int S, __u8 s,
+ __u8 command, discovery_t *discovery)
+{
+ struct sk_buff *skb = NULL;
+ struct xid_frame *frame;
+ __u32 bcast = BROADCAST;
+ __u8 *info;
+
+ IRDA_DEBUG(4, "%s(), s=%d, S=%d, command=%d\n", __FUNCTION__, s, S,
+ command);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+ ASSERT(discovery != NULL, return;);
+
+ skb = dev_alloc_skb(64);
+ if (!skb)
+ return;
+
+ skb_put(skb, 14);
+ frame = (struct xid_frame *) skb->data;
+
+ if (command) {
+ frame->caddr = CBROADCAST | CMD_FRAME;
+ frame->control = XID_CMD | PF_BIT;
+ } else {
+ frame->caddr = CBROADCAST;
+ frame->control = XID_RSP | PF_BIT;
+ }
+ frame->ident = XID_FORMAT;
+
+ frame->saddr = cpu_to_le32(self->saddr);
+
+ if (command)
+ frame->daddr = cpu_to_le32(bcast);
+ else
+ frame->daddr = cpu_to_le32(discovery->daddr);
+
+ switch (S) {
+ case 1:
+ frame->flags = 0x00;
+ break;
+ case 6:
+ frame->flags = 0x01;
+ break;
+ case 8:
+ frame->flags = 0x02;
+ break;
+ case 16:
+ frame->flags = 0x03;
+ break;
+ default:
+ frame->flags = 0x02;
+ break;
+ }
+
+ frame->slotnr = s;
+ frame->version = 0x00;
+
+ /*
+ * Provide info for final slot only in commands, and for all
+ * responses. Send the second byte of the hint only if the
+ * EXTENSION bit is set in the first byte.
+ */
+ if (!command || (frame->slotnr == 0xff)) {
+ int len;
+
+ if (discovery->hints.byte[0] & HINT_EXTENSION) {
+ info = skb_put(skb, 2);
+ info[0] = discovery->hints.byte[0];
+ info[1] = discovery->hints.byte[1];
+ } else {
+ info = skb_put(skb, 1);
+ info[0] = discovery->hints.byte[0];
+ }
+ info = skb_put(skb, 1);
+ info[0] = discovery->charset;
+
+ len = IRDA_MIN(discovery->name_len, skb_tailroom(skb));
+ info = skb_put(skb, len);
+ memcpy(info, discovery->nickname, len);
+ }
+ irlap_queue_xmit(self, skb);
+}
+
+/*
+ * Function irlap_recv_discovery_xid_rsp (skb, info)
+ *
+ * Received a XID discovery response
+ *
+ */
+static void irlap_recv_discovery_xid_rsp(struct irlap_cb *self,
+ struct sk_buff *skb,
+ struct irlap_info *info)
+{
+ struct xid_frame *xid;
+ discovery_t *discovery = NULL;
+ __u8 *discovery_info;
+ char *text;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ xid = (struct xid_frame *) skb->data;
+
+ info->daddr = le32_to_cpu(xid->saddr);
+ info->saddr = le32_to_cpu(xid->daddr);
+
+ /* Make sure frame is addressed to us */
+ if ((info->saddr != self->saddr) && (info->saddr != BROADCAST)) {
+ IRDA_DEBUG(0, "%s(), frame is not addressed to us!\n", __FUNCTION__);
+ return;
+ }
+
+ if ((discovery = kmalloc(sizeof(discovery_t), GFP_ATOMIC)) == NULL) {
+ WARNING("%s(), kmalloc failed!\n", __FUNCTION__);
+ return;
+ }
+ memset(discovery, 0, sizeof(discovery_t));
+
+ discovery->daddr = info->daddr;
+ discovery->saddr = self->saddr;
+ discovery->timestamp = jiffies;
+
+ IRDA_DEBUG(4, "%s(), daddr=%08x\n", __FUNCTION__, discovery->daddr);
+
+ discovery_info = skb_pull(skb, sizeof(struct xid_frame));
+
+ /* Get info returned from peer */
+ discovery->hints.byte[0] = discovery_info[0];
+ if (discovery_info[0] & HINT_EXTENSION) {
+ IRDA_DEBUG(4, "EXTENSION\n");
+ discovery->hints.byte[1] = discovery_info[1];
+ discovery->charset = discovery_info[2];
+ text = (char *) &discovery_info[3];
+ } else {
+ discovery->hints.byte[1] = 0;
+ discovery->charset = discovery_info[1];
+ text = (char *) &discovery_info[2];
+ }
+ /*
+ * Terminate info string, should be safe since this is where the
+ * FCS bytes resides.
+ */
+ skb->data[skb->len] = '\0';
+ strncpy(discovery->nickname, text, NICKNAME_MAX_LEN);
+ discovery->name_len = strlen(discovery->nickname);
+
+ info->discovery = discovery;
+
+ irlap_do_event(self, RECV_DISCOVERY_XID_RSP, skb, info);
+}
+
+/*
+ * Function irlap_recv_discovery_xid_cmd (skb, info)
+ *
+ * Received a XID discovery command
+ *
+ */
+static void irlap_recv_discovery_xid_cmd(struct irlap_cb *self,
+ struct sk_buff *skb,
+ struct irlap_info *info)
+{
+ struct xid_frame *xid;
+ discovery_t *discovery = NULL;
+ __u8 *discovery_info;
+ char *text;
+
+ xid = (struct xid_frame *) skb->data;
+
+ info->daddr = le32_to_cpu(xid->saddr);
+ info->saddr = le32_to_cpu(xid->daddr);
+
+ /* Make sure frame is addressed to us */
+ if ((info->saddr != self->saddr) && (info->saddr != BROADCAST)) {
+ IRDA_DEBUG(0, "%s(), frame is not addressed to us!\n", __FUNCTION__);
+ return;
+ }
+
+ switch (xid->flags & 0x03) {
+ case 0x00:
+ info->S = 1;
+ break;
+ case 0x01:
+ info->S = 6;
+ break;
+ case 0x02:
+ info->S = 8;
+ break;
+ case 0x03:
+ info->S = 16;
+ break;
+ default:
+ /* Error!! */
+ dev_kfree_skb(skb);
+ return;
+ }
+ info->s = xid->slotnr;
+
+ discovery_info = skb_pull(skb, sizeof(struct xid_frame));
+
+ /*
+ * Check if last frame
+ */
+ if (info->s == 0xff) {
+ /* Check if things are sane at this point... */
+ if((discovery_info == NULL) || (skb->len < 3)) {
+ ERROR("%s(), discovery frame to short!\n", __FUNCTION__);
+ return;
+ }
+
+ /*
+ * We now have some discovery info to deliver!
+ */
+ discovery = kmalloc(sizeof(discovery_t), GFP_ATOMIC);
+ if (!discovery) {
+ WARNING("%s(), unable to malloc!\n", __FUNCTION__);
+ return;
+ }
+
+ discovery->daddr = info->daddr;
+ discovery->saddr = self->saddr;
+ discovery->timestamp = jiffies;
+
+ discovery->hints.byte[0] = discovery_info[0];
+ if (discovery_info[0] & HINT_EXTENSION) {
+ discovery->hints.byte[1] = discovery_info[1];
+ discovery->charset = discovery_info[2];
+ text = (char *) &discovery_info[3];
+ } else {
+ discovery->hints.byte[1] = 0;
+ discovery->charset = discovery_info[1];
+ text = (char *) &discovery_info[2];
+ }
+ /*
+ * Terminate string, should be safe since this is where the
+ * FCS bytes resides.
+ */
+ skb->data[skb->len] = '\0';
+ strncpy(discovery->nickname, text, NICKNAME_MAX_LEN);
+ discovery->name_len = strlen(discovery->nickname);
+
+ info->discovery = discovery;
+ } else
+ info->discovery = NULL;
+
+ irlap_do_event(self, RECV_DISCOVERY_XID_CMD, skb, info);
+}
+
+/*
+ * Function irlap_send_rr_frame (self, command)
+ *
+ * Build and transmit RR (Receive Ready) frame. Notice that it is currently
+ * only possible to send RR frames with the poll bit set.
+ */
+void irlap_send_rr_frame(struct irlap_cb *self, int command)
+{
+ struct sk_buff *skb;
+ __u8 *frame;
+
+ skb = dev_alloc_skb(16);
+ if (!skb)
+ return;
+
+ frame = skb_put(skb, 2);
+
+ frame[0] = self->caddr;
+ frame[0] |= (command) ? CMD_FRAME : 0;
+
+ frame[1] = RR | PF_BIT | (self->vr << 5);
+
+ irlap_queue_xmit(self, skb);
+}
+
+/*
+ * Function irlap_send_rd_frame (self)
+ *
+ * Request disconnect. Used by a secondary station to request the
+ * disconnection of the link.
+ */
+void irlap_send_rd_frame(struct irlap_cb *self)
+{
+ struct sk_buff *skb;
+ __u8 *frame;
+
+ skb = dev_alloc_skb(16);
+ if (!skb)
+ return;
+
+ frame = skb_put(skb, 2);
+
+ frame[0] = self->caddr;
+ frame[1] = RD_RSP | PF_BIT;
+
+ irlap_queue_xmit(self, skb);
+}
+
+/*
+ * Function irlap_recv_rr_frame (skb, info)
+ *
+ * Received RR (Receive Ready) frame from peer station, no harm in
+ * making it inline since its called only from one single place
+ * (irlap_driver_rcv).
+ */
+static inline void irlap_recv_rr_frame(struct irlap_cb *self,
+ struct sk_buff *skb,
+ struct irlap_info *info, int command)
+{
+ info->nr = skb->data[1] >> 5;
+
+ /* Check if this is a command or a response frame */
+ if (command)
+ irlap_do_event(self, RECV_RR_CMD, skb, info);
+ else
+ irlap_do_event(self, RECV_RR_RSP, skb, info);
+}
+
+void irlap_send_frmr_frame( struct irlap_cb *self, int command)
+{
+ struct sk_buff *skb = NULL;
+ __u8 *frame;
+
+ ASSERT( self != NULL, return;);
+ ASSERT( self->magic == LAP_MAGIC, return;);
+
+ skb = dev_alloc_skb( 32);
+ if (!skb)
+ return;
+
+ frame = skb_put( skb, 2);
+
+ frame[0] = self->caddr;
+ frame[0] |= (command) ? CMD_FRAME : 0;
+
+ frame[1] = (self->vs << 1);
+ frame[1] |= PF_BIT;
+ frame[1] |= (self->vr << 5);
+
+ frame[2] = 0;
+
+ IRDA_DEBUG(4, "%s(), vr=%d, %ld\n", __FUNCTION__, self->vr, jiffies);
+
+ irlap_queue_xmit(self, skb);
+}
+
+/*
+ * Function irlap_recv_rnr_frame (self, skb, info)
+ *
+ * Received RNR (Receive Not Ready) frame from peer station
+ *
+ */
+static void irlap_recv_rnr_frame(struct irlap_cb *self, struct sk_buff *skb,
+ struct irlap_info *info, int command)
+{
+ info->nr = skb->data[1] >> 5;
+
+ IRDA_DEBUG(4, "%s(), nr=%d, %ld\n", __FUNCTION__, info->nr, jiffies);
+
+ if (command)
+ irlap_do_event(self, RECV_RNR_CMD, skb, info);
+ else
+ irlap_do_event(self, RECV_RNR_RSP, skb, info);
+}
+
+static void irlap_recv_rej_frame(struct irlap_cb *self, struct sk_buff *skb,
+ struct irlap_info *info, int command)
+{
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ info->nr = skb->data[1] >> 5;
+
+ /* Check if this is a command or a response frame */
+ if (command)
+ irlap_do_event(self, RECV_REJ_CMD, skb, info);
+ else
+ irlap_do_event(self, RECV_REJ_RSP, skb, info);
+}
+
+static void irlap_recv_srej_frame(struct irlap_cb *self, struct sk_buff *skb,
+ struct irlap_info *info, int command)
+{
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ info->nr = skb->data[1] >> 5;
+
+ /* Check if this is a command or a response frame */
+ if (command)
+ irlap_do_event(self, RECV_SREJ_CMD, skb, info);
+ else
+ irlap_do_event(self, RECV_SREJ_RSP, skb, info);
+}
+
+static void irlap_recv_disc_frame(struct irlap_cb *self, struct sk_buff *skb,
+ struct irlap_info *info, int command)
+{
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ /* Check if this is a command or a response frame */
+ if (command)
+ irlap_do_event(self, RECV_DISC_CMD, skb, info);
+ else
+ irlap_do_event(self, RECV_RD_RSP, skb, info);
+}
+
+/*
+ * Function irlap_recv_ua_frame (skb, frame)
+ *
+ * Received UA (Unnumbered Acknowledgement) frame
+ *
+ */
+static inline void irlap_recv_ua_frame(struct irlap_cb *self,
+ struct sk_buff *skb,
+ struct irlap_info *info)
+{
+ irlap_do_event(self, RECV_UA_RSP, skb, info);
+}
+
+/*
+ * Function irlap_send_data_primary(self, skb)
+ *
+ * Send I-frames as the primary station but without the poll bit set
+ *
+ */
+void irlap_send_data_primary(struct irlap_cb *self, struct sk_buff *skb)
+{
+ struct sk_buff *tx_skb;
+
+ if (skb->data[1] == I_FRAME) {
+
+ /*
+ * Insert frame sequence number (Vs) in control field before
+ * inserting into transmit window queue.
+ */
+ skb->data[1] = I_FRAME | (self->vs << 1);
+
+ /* Copy buffer */
+ tx_skb = skb_clone(skb, GFP_ATOMIC);
+ if (tx_skb == NULL) {
+ return;
+ }
+
+ /*
+ * Insert frame in store, in case of retransmissions
+ */
+ skb_queue_tail(&self->wx_list, skb_get(skb));
+
+ self->vs = (self->vs + 1) % 8;
+ self->ack_required = FALSE;
+ self->window -= 1;
+
+ irlap_send_i_frame( self, tx_skb, CMD_FRAME);
+ } else {
+ IRDA_DEBUG(4, "%s(), sending unreliable frame\n", __FUNCTION__);
+ irlap_send_ui_frame(self, skb_get(skb), self->caddr, CMD_FRAME);
+ self->window -= 1;
+ }
+}
+/*
+ * Function irlap_send_data_primary_poll (self, skb)
+ *
+ * Send I(nformation) frame as primary with poll bit set
+ */
+void irlap_send_data_primary_poll(struct irlap_cb *self, struct sk_buff *skb)
+{
+ struct sk_buff *tx_skb;
+
+ /* Stop P timer */
+ del_timer(&self->poll_timer);
+
+ /* Is this reliable or unreliable data? */
+ if (skb->data[1] == I_FRAME) {
+
+ /*
+ * Insert frame sequence number (Vs) in control field before
+ * inserting into transmit window queue.
+ */
+ skb->data[1] = I_FRAME | (self->vs << 1);
+
+ /* Copy buffer */
+ tx_skb = skb_clone(skb, GFP_ATOMIC);
+ if (tx_skb == NULL) {
+ return;
+ }
+
+ /*
+ * Insert frame in store, in case of retransmissions
+ */
+ skb_queue_tail(&self->wx_list, skb_get(skb));
+
+ /*
+ * Set poll bit if necessary. We do this to the copied
+ * skb, since retransmitted need to set or clear the poll
+ * bit depending on when they are sent.
+ */
+ tx_skb->data[1] |= PF_BIT;
+
+ self->vs = (self->vs + 1) % 8;
+ self->ack_required = FALSE;
+
+ irlap_send_i_frame(self, tx_skb, CMD_FRAME);
+ } else {
+ IRDA_DEBUG(4, "%s(), sending unreliable frame\n", __FUNCTION__);
+
+ if (self->ack_required) {
+ irlap_send_ui_frame(self, skb_get(skb), self->caddr, CMD_FRAME);
+ irlap_send_rr_frame(self, CMD_FRAME);
+ self->ack_required = FALSE;
+ } else {
+ skb->data[1] |= PF_BIT;
+ irlap_send_ui_frame(self, skb_get(skb), self->caddr, CMD_FRAME);
+ }
+ }
+
+ self->window = self->window_size;
+#ifdef CONFIG_IRDA_DYNAMIC_WINDOW
+ /* We are allowed to transmit a maximum number of bytes again. */
+ self->bytes_left = self->line_capacity;
+#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */
+
+ irlap_start_final_timer(self, self->final_timeout);
+}
+
+/*
+ * Function irlap_send_data_secondary_final (self, skb)
+ *
+ * Send I(nformation) frame as secondary with final bit set
+ *
+ */
+void irlap_send_data_secondary_final(struct irlap_cb *self,
+ struct sk_buff *skb)
+{
+ struct sk_buff *tx_skb = NULL;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+
+ /* Is this reliable or unreliable data? */
+ if (skb->data[1] == I_FRAME) {
+
+ /*
+ * Insert frame sequence number (Vs) in control field before
+ * inserting into transmit window queue.
+ */
+ skb->data[1] = I_FRAME | (self->vs << 1);
+
+ tx_skb = skb_clone(skb, GFP_ATOMIC);
+ if (tx_skb == NULL) {
+ return;
+ }
+
+ /* Insert frame in store */
+ skb_queue_tail(&self->wx_list, skb_get(skb));
+
+ tx_skb->data[1] |= PF_BIT;
+
+ self->vs = (self->vs + 1) % 8;
+ self->ack_required = FALSE;
+
+ irlap_send_i_frame(self, tx_skb, RSP_FRAME);
+ } else {
+ if (self->ack_required) {
+ irlap_send_ui_frame(self, skb_get(skb), self->caddr, RSP_FRAME);
+ irlap_send_rr_frame(self, RSP_FRAME);
+ self->ack_required = FALSE;
+ } else {
+ skb->data[1] |= PF_BIT;
+ irlap_send_ui_frame(self, skb_get(skb), self->caddr, RSP_FRAME);
+ }
+ }
+
+ self->window = self->window_size;
+#ifdef CONFIG_IRDA_DYNAMIC_WINDOW
+ /* We are allowed to transmit a maximum number of bytes again. */
+ self->bytes_left = self->line_capacity;
+#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */
+
+ irlap_start_wd_timer(self, self->wd_timeout);
+}
+
+/*
+ * Function irlap_send_data_secondary (self, skb)
+ *
+ * Send I(nformation) frame as secondary without final bit set
+ *
+ */
+void irlap_send_data_secondary(struct irlap_cb *self, struct sk_buff *skb)
+{
+ struct sk_buff *tx_skb = NULL;
+
+ /* Is this reliable or unreliable data? */
+ if (skb->data[1] == I_FRAME) {
+
+ /*
+ * Insert frame sequence number (Vs) in control field before
+ * inserting into transmit window queue.
+ */
+ skb->data[1] = I_FRAME | (self->vs << 1);
+
+ tx_skb = skb_clone(skb, GFP_ATOMIC);
+ if (tx_skb == NULL) {
+ return;
+ }
+
+ /* Insert frame in store */
+ skb_queue_tail(&self->wx_list, skb_get(skb));
+
+ self->vs = (self->vs + 1) % 8;
+ self->ack_required = FALSE;
+ self->window -= 1;
+
+ irlap_send_i_frame(self, tx_skb, RSP_FRAME);
+ } else {
+ irlap_send_ui_frame(self, skb_get(skb), self->caddr, RSP_FRAME);
+ self->window -= 1;
+ }
+}
+
+/*
+ * Function irlap_resend_rejected_frames (nr)
+ *
+ * Resend frames which has not been acknowledged. Should be safe to
+ * traverse the list without locking it since this function will only be
+ * called from interrupt context (BH)
+ */
+void irlap_resend_rejected_frames(struct irlap_cb *self, int command)
+{
+ struct sk_buff *tx_skb;
+ struct sk_buff *skb;
+ int count;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ /* Initialize variables */
+ skb = tx_skb = NULL;
+
+ count = skb_queue_len(&self->wx_list);
+
+ /* Resend unacknowledged frame(s) */
+ skb = skb_peek(&self->wx_list);
+ while (skb != NULL) {
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+
+ /* We copy the skb to be retransmitted since we will have to
+ * modify it. Cloning will confuse packet sniffers
+ */
+ /* tx_skb = skb_clone( skb, GFP_ATOMIC); */
+ tx_skb = skb_copy(skb, GFP_ATOMIC);
+ if (!tx_skb) {
+ IRDA_DEBUG(0, "%s(), unable to copy\n", __FUNCTION__);
+ return;
+ }
+ /* Unlink tx_skb from list */
+ tx_skb->next = tx_skb->prev = NULL;
+ tx_skb->list = NULL;
+
+ /* Clear old Nr field + poll bit */
+ tx_skb->data[1] &= 0x0f;
+
+ /*
+ * Set poll bit on the last frame retransmitted
+ */
+ if (count-- == 1)
+ tx_skb->data[1] |= PF_BIT; /* Set p/f bit */
+ else
+ tx_skb->data[1] &= ~PF_BIT; /* Clear p/f bit */
+
+ irlap_send_i_frame(self, tx_skb, command);
+
+ /*
+ * If our skb is the last buffer in the list, then
+ * we are finished, if not, move to the next sk-buffer
+ */
+ if (skb == skb_peek_tail(&self->wx_list))
+ skb = NULL;
+ else
+ skb = skb->next;
+ }
+#if 0 /* Not yet */
+ /*
+ * We can now fill the window with additinal data frames
+ */
+ while (skb_queue_len( &self->txq) > 0) {
+
+ IRDA_DEBUG(0, "%s(), sending additional frames!\n", __FUNCTION__);
+ if ((skb_queue_len( &self->txq) > 0) &&
+ (self->window > 0)) {
+ skb = skb_dequeue( &self->txq);
+ ASSERT(skb != NULL, return;);
+
+ /*
+ * If send window > 1 then send frame with pf
+ * bit cleared
+ */
+ if ((self->window > 1) &&
+ skb_queue_len(&self->txq) > 0)
+ {
+ irlap_send_data_primary(self, skb);
+ } else {
+ irlap_send_data_primary_poll(self, skb);
+ }
+ kfree_skb(skb);
+ }
+ }
+#endif
+}
+
+void irlap_resend_rejected_frame(struct irlap_cb *self, int command)
+{
+ struct sk_buff *tx_skb;
+ struct sk_buff *skb;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ /* Initialize variables */
+ skb = tx_skb = NULL;
+
+ /* Resend unacknowledged frame(s) */
+ skb = skb_peek(&self->wx_list);
+ if (skb != NULL) {
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+
+ /* We copy the skb to be retransmitted since we will have to
+ * modify it. Cloning will confuse packet sniffers
+ */
+ /* tx_skb = skb_clone( skb, GFP_ATOMIC); */
+ tx_skb = skb_copy(skb, GFP_ATOMIC);
+ if (!tx_skb) {
+ IRDA_DEBUG(0, "%s(), unable to copy\n", __FUNCTION__);
+ return;
+ }
+ /* Unlink tx_skb from list */
+ tx_skb->next = tx_skb->prev = NULL;
+ tx_skb->list = NULL;
+
+ /* Clear old Nr field + poll bit */
+ tx_skb->data[1] &= 0x0f;
+
+ /* Set poll/final bit */
+ tx_skb->data[1] |= PF_BIT; /* Set p/f bit */
+
+ irlap_send_i_frame(self, tx_skb, command);
+ }
+}
+
+/*
+ * Function irlap_send_ui_frame (self, skb, command)
+ *
+ * Contruct and transmit an Unnumbered Information (UI) frame
+ *
+ */
+void irlap_send_ui_frame(struct irlap_cb *self, struct sk_buff *skb,
+ __u8 caddr, int command)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+
+ /* Insert connection address */
+ skb->data[0] = caddr | ((command) ? CMD_FRAME : 0);
+
+ irlap_queue_xmit(self, skb);
+}
+
+/*
+ * Function irlap_send_i_frame (skb)
+ *
+ * Contruct and transmit Information (I) frame
+ */
+void irlap_send_i_frame(struct irlap_cb *self, struct sk_buff *skb,
+ int command)
+{
+ /* Insert connection address */
+ skb->data[0] = self->caddr;
+ skb->data[0] |= (command) ? CMD_FRAME : 0;
+
+ /* Insert next to receive (Vr) */
+ skb->data[1] |= (self->vr << 5); /* insert nr */
+
+ irlap_queue_xmit(self, skb);
+}
+
+/*
+ * Function irlap_recv_i_frame (skb, frame)
+ *
+ * Receive and parse an I (Information) frame, no harm in making it inline
+ * since it's called only from one single place (irlap_driver_rcv).
+ */
+static inline void irlap_recv_i_frame(struct irlap_cb *self,
+ struct sk_buff *skb,
+ struct irlap_info *info, int command)
+{
+ info->nr = skb->data[1] >> 5; /* Next to receive */
+ info->pf = skb->data[1] & PF_BIT; /* Final bit */
+ info->ns = (skb->data[1] >> 1) & 0x07; /* Next to send */
+
+ /* Check if this is a command or a response frame */
+ if (command)
+ irlap_do_event(self, RECV_I_CMD, skb, info);
+ else
+ irlap_do_event(self, RECV_I_RSP, skb, info);
+}
+
+/*
+ * Function irlap_recv_ui_frame (self, skb, info)
+ *
+ * Receive and parse an Unnumbered Information (UI) frame
+ *
+ */
+static void irlap_recv_ui_frame(struct irlap_cb *self, struct sk_buff *skb,
+ struct irlap_info *info)
+{
+ IRDA_DEBUG( 4, "%s()\n", __FUNCTION__);
+
+ info->pf = skb->data[1] & PF_BIT; /* Final bit */
+
+ irlap_do_event(self, RECV_UI_FRAME, skb, info);
+}
+
+/*
+ * Function irlap_recv_frmr_frame (skb, frame)
+ *
+ * Received Frame Reject response.
+ *
+ */
+static void irlap_recv_frmr_frame(struct irlap_cb *self, struct sk_buff *skb,
+ struct irlap_info *info)
+{
+ __u8 *frame;
+ int w, x, y, z;
+
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+ ASSERT(info != NULL, return;);
+
+ frame = skb->data;
+
+ info->nr = frame[2] >> 5; /* Next to receive */
+ info->pf = frame[2] & PF_BIT; /* Final bit */
+ info->ns = (frame[2] >> 1) & 0x07; /* Next to send */
+
+ w = frame[3] & 0x01;
+ x = frame[3] & 0x02;
+ y = frame[3] & 0x04;
+ z = frame[3] & 0x08;
+
+ if (w) {
+ IRDA_DEBUG(0, "Rejected control field is undefined or not "
+ "implemented.\n");
+ }
+ if (x) {
+ IRDA_DEBUG(0, "Rejected control field was invalid because it "
+ "contained a non permitted I field.\n");
+ }
+ if (y) {
+ IRDA_DEBUG(0, "Received I field exceeded the maximum negotiated "
+ "for the existing connection or exceeded the maximum "
+ "this station supports if no connection exists.\n");
+ }
+ if (z) {
+ IRDA_DEBUG(0, "Rejected control field control field contained an "
+ "invalid Nr count.\n");
+ }
+ irlap_do_event(self, RECV_FRMR_RSP, skb, info);
+}
+
+/*
+ * Function irlap_send_test_frame (self, daddr)
+ *
+ * Send a test frame response
+ *
+ */
+void irlap_send_test_frame(struct irlap_cb *self, __u8 caddr, __u32 daddr,
+ struct sk_buff *cmd)
+{
+ struct sk_buff *skb;
+ struct test_frame *frame;
+ __u8 *info;
+
+ skb = dev_alloc_skb(cmd->len+sizeof(struct test_frame));
+ if (!skb)
+ return;
+
+ /* Broadcast frames must include saddr and daddr fields */
+ if (caddr == CBROADCAST) {
+ frame = (struct test_frame *)
+ skb_put(skb, sizeof(struct test_frame));
+
+ /* Insert the swapped addresses */
+ frame->saddr = cpu_to_le32(self->saddr);
+ frame->daddr = cpu_to_le32(daddr);
+ } else
+ frame = (struct test_frame *) skb_put(skb, LAP_ADDR_HEADER + LAP_CTRL_HEADER);
+
+ frame->caddr = caddr;
+ frame->control = TEST_RSP | PF_BIT;
+
+ /* Copy info */
+ info = skb_put(skb, cmd->len);
+ memcpy(info, cmd->data, cmd->len);
+
+ /* Return to sender */
+ irlap_wait_min_turn_around(self, &self->qos_tx);
+ irlap_queue_xmit(self, skb);
+}
+
+/*
+ * Function irlap_recv_test_frame (self, skb)
+ *
+ * Receive a test frame
+ *
+ */
+static void irlap_recv_test_frame(struct irlap_cb *self, struct sk_buff *skb,
+ struct irlap_info *info, int command)
+{
+ struct test_frame *frame;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ frame = (struct test_frame *) skb->data;
+
+ /* Broadcast frames must carry saddr and daddr fields */
+ if (info->caddr == CBROADCAST) {
+ if (skb->len < sizeof(struct test_frame)) {
+ IRDA_DEBUG(0, "%s() test frame to short!\n", __FUNCTION__);
+ return;
+ }
+
+ /* Read and swap addresses */
+ info->daddr = le32_to_cpu(frame->saddr);
+ info->saddr = le32_to_cpu(frame->daddr);
+
+ /* Make sure frame is addressed to us */
+ if ((info->saddr != self->saddr) &&
+ (info->saddr != BROADCAST)) {
+ return;
+ }
+ }
+
+ if (command)
+ irlap_do_event(self, RECV_TEST_CMD, skb, info);
+ else
+ irlap_do_event(self, RECV_TEST_RSP, skb, info);
+}
+
+/*
+ * Function irlap_driver_rcv (skb, netdev, ptype)
+ *
+ * Called when a frame is received. Dispatches the right receive function
+ * for processing of the frame.
+ *
+ */
+int irlap_driver_rcv(struct sk_buff *skb, struct net_device *dev,
+ struct packet_type *ptype)
+{
+ struct irlap_info info;
+ struct irlap_cb *self;
+ int command;
+ __u8 control;
+
+ /* FIXME: should we get our own field? */
+ self = (struct irlap_cb *) dev->atalk_ptr;
+
+ /* If the net device is down, then IrLAP is gone! */
+ if (!self || self->magic != LAP_MAGIC) {
+ dev_kfree_skb(skb);
+ return -1;
+ }
+
+ /* Check if frame is large enough for parsing */
+ if (skb->len < 2) {
+ ERROR("%s(), frame to short!\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ return -1;
+ }
+
+ command = skb->data[0] & CMD_FRAME;
+ info.caddr = skb->data[0] & CBROADCAST;
+
+ info.pf = skb->data[1] & PF_BIT;
+ info.control = skb->data[1] & ~PF_BIT; /* Mask away poll/final bit */
+
+ control = info.control;
+
+ /* First we check if this frame has a valid connection address */
+ if ((info.caddr != self->caddr) && (info.caddr != CBROADCAST)) {
+ IRDA_DEBUG(0, "%s(), wrong connection address!\n", __FUNCTION__);
+ goto out;
+ }
+ /*
+ * Optimize for the common case and check if the frame is an
+ * I(nformation) frame. Only I-frames have bit 0 set to 0
+ */
+ if (~control & 0x01) {
+ irlap_recv_i_frame(self, skb, &info, command);
+ goto out;
+ }
+ /*
+ * We now check is the frame is an S(upervisory) frame. Only
+ * S-frames have bit 0 set to 1 and bit 1 set to 0
+ */
+ if (~control & 0x02) {
+ /*
+ * Received S(upervisory) frame, check which frame type it is
+ * only the first nibble is of interest
+ */
+ switch (control & 0x0f) {
+ case RR:
+ irlap_recv_rr_frame(self, skb, &info, command);
+ break;
+ case RNR:
+ irlap_recv_rnr_frame(self, skb, &info, command);
+ break;
+ case REJ:
+ irlap_recv_rej_frame(self, skb, &info, command);
+ break;
+ case SREJ:
+ irlap_recv_srej_frame(self, skb, &info, command);
+ break;
+ default:
+ WARNING("%s() Unknown S-frame %02x received!\n", __FUNCTION__,
+ info.control);
+ break;
+ }
+ goto out;
+ }
+ /*
+ * This must be a C(ontrol) frame
+ */
+ switch (control) {
+ case XID_RSP:
+ irlap_recv_discovery_xid_rsp(self, skb, &info);
+ break;
+ case XID_CMD:
+ irlap_recv_discovery_xid_cmd(self, skb, &info);
+ break;
+ case SNRM_CMD:
+ irlap_recv_snrm_cmd(self, skb, &info);
+ break;
+ case DM_RSP:
+ irlap_do_event(self, RECV_DM_RSP, skb, &info);
+ break;
+ case DISC_CMD: /* And RD_RSP since they have the same value */
+ irlap_recv_disc_frame(self, skb, &info, command);
+ break;
+ case TEST_CMD:
+ irlap_recv_test_frame(self, skb, &info, command);
+ break;
+ case UA_RSP:
+ irlap_recv_ua_frame(self, skb, &info);
+ break;
+ case FRMR_RSP:
+ irlap_recv_frmr_frame(self, skb, &info);
+ break;
+ case UI_FRAME:
+ irlap_recv_ui_frame(self, skb, &info);
+ break;
+ default:
+ WARNING("%s(), Unknown frame %02x received!\n", __FUNCTION__,
+ info.control);
+ break;
+ }
+out:
+ dev_kfree_skb(skb);
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/irlmp.c b/uClinux-2.4.31-uc0/net/irda/irlmp.c
new file mode 100644
index 0000000..10b8ddb
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlmp.c
@@ -0,0 +1,1806 @@
+/*********************************************************************
+ *
+ * Filename: irlmp.c
+ * Version: 1.0
+ * Description: IrDA Link Management Protocol (LMP) layer
+ * Status: Stable.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sun Aug 17 20:54:32 1997
+ * Modified at: Wed Jan 5 11:26:03 2000
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1998-2000 Dag Brattli <dagb@cs.uit.no>,
+ * All Rights Reserved.
+ * Copyright (c) 2000-2001 Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/skbuff.h>
+#include <linux/types.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <linux/kmod.h>
+#include <linux/random.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irmod.h>
+#include <net/irda/timer.h>
+#include <net/irda/qos.h>
+#include <net/irda/irlap.h>
+#include <net/irda/iriap.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/irlmp_frame.h>
+
+/* Master structure */
+struct irlmp_cb *irlmp = NULL;
+
+/* These can be altered by the sysctl interface */
+int sysctl_discovery = 0;
+int sysctl_discovery_timeout = 3; /* 3 seconds by default */
+int sysctl_discovery_slots = 6; /* 6 slots by default */
+int sysctl_lap_keepalive_time = LM_IDLE_TIMEOUT * 1000 / HZ;
+char sysctl_devname[65];
+
+char *lmp_reasons[] = {
+ "ERROR, NOT USED",
+ "LM_USER_REQUEST",
+ "LM_LAP_DISCONNECT",
+ "LM_CONNECT_FAILURE",
+ "LM_LAP_RESET",
+ "LM_INIT_DISCONNECT",
+ "ERROR, NOT USED",
+};
+
+__u8 *irlmp_hint_to_service(__u8 *hint);
+#ifdef CONFIG_PROC_FS
+int irlmp_proc_read(char *buf, char **start, off_t offst, int len);
+#endif
+
+/*
+ * Function irlmp_init (void)
+ *
+ * Create (allocate) the main IrLMP structure
+ *
+ */
+int __init irlmp_init(void)
+{
+ IRDA_DEBUG(1, "%s()\n", __FUNCTION__);
+ /* Initialize the irlmp structure. */
+ irlmp = kmalloc( sizeof(struct irlmp_cb), GFP_KERNEL);
+ if (irlmp == NULL)
+ return -ENOMEM;
+ memset(irlmp, 0, sizeof(struct irlmp_cb));
+
+ irlmp->magic = LMP_MAGIC;
+ spin_lock_init(&irlmp->log_lock);
+
+ irlmp->clients = hashbin_new(HB_GLOBAL);
+ irlmp->services = hashbin_new(HB_GLOBAL);
+ irlmp->links = hashbin_new(HB_GLOBAL);
+ irlmp->unconnected_lsaps = hashbin_new(HB_GLOBAL);
+ irlmp->cachelog = hashbin_new(HB_GLOBAL);
+
+ irlmp->free_lsap_sel = 0x10; /* Reserved 0x00-0x0f */
+#ifdef CONFIG_IRDA_CACHE_LAST_LSAP
+ irlmp->cache.valid = FALSE;
+#endif
+ strcpy(sysctl_devname, "Linux");
+
+ /* Do discovery every 3 seconds */
+ init_timer(&irlmp->discovery_timer);
+ irlmp_start_discovery_timer(irlmp, sysctl_discovery_timeout*HZ);
+
+ return 0;
+}
+
+/*
+ * Function irlmp_cleanup (void)
+ *
+ * Remove IrLMP layer
+ *
+ */
+void irlmp_cleanup(void)
+{
+ /* Check for main structure */
+ ASSERT(irlmp != NULL, return;);
+ ASSERT(irlmp->magic == LMP_MAGIC, return;);
+
+ del_timer(&irlmp->discovery_timer);
+
+ hashbin_delete(irlmp->links, (FREE_FUNC) kfree);
+ hashbin_delete(irlmp->unconnected_lsaps, (FREE_FUNC) kfree);
+ hashbin_delete(irlmp->clients, (FREE_FUNC) kfree);
+ hashbin_delete(irlmp->services, (FREE_FUNC) kfree);
+ hashbin_delete(irlmp->cachelog, (FREE_FUNC) kfree);
+
+ /* De-allocate main structure */
+ kfree(irlmp);
+ irlmp = NULL;
+}
+
+/*
+ * Function irlmp_open_lsap (slsap, notify)
+ *
+ * Register with IrLMP and create a local LSAP,
+ * returns handle to LSAP.
+ */
+struct lsap_cb *irlmp_open_lsap(__u8 slsap_sel, notify_t *notify, __u8 pid)
+{
+ struct lsap_cb *self;
+
+ ASSERT(notify != NULL, return NULL;);
+ ASSERT(irlmp != NULL, return NULL;);
+ ASSERT(irlmp->magic == LMP_MAGIC, return NULL;);
+
+ /* Does the client care which Source LSAP selector it gets? */
+ if (slsap_sel == LSAP_ANY) {
+ slsap_sel = irlmp_find_free_slsap();
+ if (!slsap_sel)
+ return NULL;
+ } else if (irlmp_slsap_inuse(slsap_sel))
+ return NULL;
+
+ /* Allocate new instance of a LSAP connection */
+ self = kmalloc(sizeof(struct lsap_cb), GFP_ATOMIC);
+ if (self == NULL) {
+ ERROR("%s(), can't allocate memory", __FUNCTION__);
+ return NULL;
+ }
+ memset(self, 0, sizeof(struct lsap_cb));
+
+ self->magic = LMP_LSAP_MAGIC;
+ self->slsap_sel = slsap_sel;
+
+ /* Fix connectionless LSAP's */
+ if (slsap_sel == LSAP_CONNLESS) {
+#ifdef CONFIG_IRDA_ULTRA
+ self->dlsap_sel = LSAP_CONNLESS;
+ self->pid = pid;
+#endif /* CONFIG_IRDA_ULTRA */
+ } else
+ self->dlsap_sel = LSAP_ANY;
+ /* self->connected = FALSE; -> already NULL via memset() */
+
+ init_timer(&self->watchdog_timer);
+
+ ASSERT(notify->instance != NULL, return NULL;);
+ self->notify = *notify;
+
+ self->lsap_state = LSAP_DISCONNECTED;
+
+ /* Insert into queue of unconnected LSAPs */
+ hashbin_insert(irlmp->unconnected_lsaps, (irda_queue_t *) self, (int) self,
+ NULL);
+
+ return self;
+}
+
+/*
+ * Function __irlmp_close_lsap (self)
+ *
+ * Remove an instance of LSAP
+ */
+static void __irlmp_close_lsap(struct lsap_cb *self)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return;);
+
+ /*
+ * Set some of the variables to preset values
+ */
+ self->magic = 0;
+ del_timer(&self->watchdog_timer); /* Important! */
+
+ if (self->conn_skb)
+ dev_kfree_skb(self->conn_skb);
+
+#ifdef CONFIG_IRDA_CACHE_LAST_LSAP
+ ASSERT(irlmp != NULL, return;);
+ irlmp->cache.valid = FALSE;
+#endif
+ kfree(self);
+}
+
+/*
+ * Function irlmp_close_lsap (self)
+ *
+ * Close and remove LSAP
+ *
+ */
+void irlmp_close_lsap(struct lsap_cb *self)
+{
+ struct lap_cb *lap;
+ struct lsap_cb *lsap = NULL;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return;);
+
+ /*
+ * Find out if we should remove this LSAP from a link or from the
+ * list of unconnected lsaps (not associated with a link)
+ */
+ lap = self->lap;
+ if (lap) {
+ ASSERT(lap->magic == LMP_LAP_MAGIC, return;);
+ /* We might close a LSAP before it has completed the
+ * connection setup. In those case, higher layers won't
+ * send a proper disconnect request. Harmless, except
+ * that we will forget to close LAP... - Jean II */
+ if(self->lsap_state != LSAP_DISCONNECTED) {
+ self->lsap_state = LSAP_DISCONNECTED;
+ irlmp_do_lap_event(self->lap,
+ LM_LAP_DISCONNECT_REQUEST, NULL);
+ }
+ /* Now, remove from the link */
+ lsap = hashbin_remove(lap->lsaps, (int) self, NULL);
+ }
+ self->lap = NULL;
+ /* Check if we found the LSAP! If not then try the unconnected lsaps */
+ if (!lsap) {
+ lsap = hashbin_remove(irlmp->unconnected_lsaps, (int) self,
+ NULL);
+ }
+ if (!lsap) {
+ IRDA_DEBUG(0, "%s(), Looks like somebody has removed me already!\n", __FUNCTION__);
+ return;
+ }
+ __irlmp_close_lsap(self);
+}
+
+/*
+ * Function irlmp_register_irlap (saddr, notify)
+ *
+ * Register IrLAP layer with IrLMP. There is possible to have multiple
+ * instances of the IrLAP layer, each connected to different IrDA ports
+ *
+ */
+void irlmp_register_link(struct irlap_cb *irlap, __u32 saddr, notify_t *notify)
+{
+ struct lap_cb *lap;
+
+ ASSERT(irlmp != NULL, return;);
+ ASSERT(irlmp->magic == LMP_MAGIC, return;);
+ ASSERT(notify != NULL, return;);
+
+ /*
+ * Allocate new instance of a LSAP connection
+ */
+ lap = kmalloc(sizeof(struct lap_cb), GFP_KERNEL);
+ if (lap == NULL) {
+ ERROR("%s(), unable to kmalloc\n", __FUNCTION__);
+ return;
+ }
+ memset(lap, 0, sizeof(struct lap_cb));
+
+ lap->irlap = irlap;
+ lap->magic = LMP_LAP_MAGIC;
+ lap->saddr = saddr;
+ lap->daddr = DEV_ADDR_ANY;
+ lap->lsaps = hashbin_new(HB_GLOBAL);
+
+ lap->lap_state = LAP_STANDBY;
+
+ init_timer(&lap->idle_timer);
+
+ /*
+ * Insert into queue of LMP links
+ */
+ hashbin_insert(irlmp->links, (irda_queue_t *) lap, lap->saddr, NULL);
+
+ /*
+ * We set only this variable so IrLAP can tell us on which link the
+ * different events happened on
+ */
+ irda_notify_init(notify);
+ notify->instance = lap;
+}
+
+/*
+ * Function irlmp_unregister_irlap (saddr)
+ *
+ * IrLAP layer has been removed!
+ *
+ */
+void irlmp_unregister_link(__u32 saddr)
+{
+ struct lap_cb *link;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ link = hashbin_remove(irlmp->links, saddr, NULL);
+ if (link) {
+ ASSERT(link->magic == LMP_LAP_MAGIC, return;);
+
+ /* Remove all discoveries discovered at this link */
+ irlmp_expire_discoveries(irlmp->cachelog, link->saddr, TRUE);
+
+ del_timer(&link->idle_timer);
+
+ link->magic = 0;
+ kfree(link);
+ }
+}
+
+/*
+ * Function irlmp_connect_request (handle, dlsap, userdata)
+ *
+ * Connect with a peer LSAP
+ *
+ */
+int irlmp_connect_request(struct lsap_cb *self, __u8 dlsap_sel,
+ __u32 saddr, __u32 daddr,
+ struct qos_info *qos, struct sk_buff *userdata)
+{
+ struct sk_buff *skb = NULL;
+ struct lap_cb *lap;
+ struct lsap_cb *lsap;
+ discovery_t *discovery;
+
+ ASSERT(self != NULL, return -EBADR;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return -EBADR;);
+
+ IRDA_DEBUG(2, "%s(), slsap_sel=%02x, dlsap_sel=%02x, saddr=%08x, daddr=%08x\n",
+ __FUNCTION__, self->slsap_sel, dlsap_sel, saddr, daddr);
+
+ if (test_bit(0, &self->connected))
+ return -EISCONN;
+
+ /* Client must supply destination device address */
+ if (!daddr)
+ return -EINVAL;
+
+ /* Any userdata? */
+ if (userdata == NULL) {
+ skb = dev_alloc_skb(64);
+ if (!skb)
+ return -ENOMEM;
+
+ skb_reserve(skb, LMP_MAX_HEADER);
+ } else
+ skb = userdata;
+
+ /* Make room for MUX control header (3 bytes) */
+ ASSERT(skb_headroom(skb) >= LMP_CONTROL_HEADER, return -1;);
+ skb_push(skb, LMP_CONTROL_HEADER);
+
+ self->dlsap_sel = dlsap_sel;
+
+ /*
+ * Find the link to where we should try to connect since there may
+ * be more than one IrDA port on this machine. If the client has
+ * passed us the saddr (and already knows which link to use), then
+ * we use that to find the link, if not then we have to look in the
+ * discovery log and check if any of the links has discovered a
+ * device with the given daddr
+ */
+ if ((!saddr) || (saddr == DEV_ADDR_ANY)) {
+ if (daddr != DEV_ADDR_ANY)
+ discovery = hashbin_find(irlmp->cachelog, daddr, NULL);
+ else {
+ IRDA_DEBUG(2, "%s(), no daddr\n", __FUNCTION__);
+ discovery = (discovery_t *)
+ hashbin_get_first(irlmp->cachelog);
+ }
+
+ if (discovery) {
+ saddr = discovery->saddr;
+ daddr = discovery->daddr;
+ }
+ }
+ lap = hashbin_find(irlmp->links, saddr, NULL);
+ if (lap == NULL) {
+ IRDA_DEBUG(1, "%s(), Unable to find a usable link!\n", __FUNCTION__);
+ return -EHOSTUNREACH;
+ }
+
+ /* Check if LAP is disconnected or already connected */
+ if (lap->daddr == DEV_ADDR_ANY)
+ lap->daddr = daddr;
+ else if (lap->daddr != daddr) {
+ struct lsap_cb *any_lsap;
+
+ /* Check if some LSAPs are active on this LAP */
+ any_lsap = (struct lsap_cb *) hashbin_get_first(lap->lsaps);
+ if (any_lsap == NULL) {
+ /* No active connection, but LAP hasn't been
+ * disconnected yet (waiting for timeout in LAP).
+ * Maybe we could give LAP a bit of help in this case.
+ */
+ IRDA_DEBUG(0, "%s(), sorry, but I'm waiting for LAP to timeout!\n", __FUNCTION__);
+ return -EAGAIN;
+ }
+
+ /* LAP is already connected to a different node, and LAP
+ * can only talk to one node at a time */
+ IRDA_DEBUG(0, "%s(), sorry, but link is busy!\n", __FUNCTION__);
+ return -EBUSY;
+ }
+
+ self->lap = lap;
+
+ /*
+ * Remove LSAP from list of unconnected LSAPs and insert it into the
+ * list of connected LSAPs for the particular link
+ */
+ lsap = hashbin_remove(irlmp->unconnected_lsaps, (int) self, NULL);
+
+ ASSERT(lsap != NULL, return -1;);
+ ASSERT(lsap->magic == LMP_LSAP_MAGIC, return -1;);
+ ASSERT(lsap->lap != NULL, return -1;);
+ ASSERT(lsap->lap->magic == LMP_LAP_MAGIC, return -1;);
+
+ hashbin_insert(self->lap->lsaps, (irda_queue_t *) self, (int) self, NULL);
+
+ set_bit(0, &self->connected); /* TRUE */
+
+ /*
+ * User supplied qos specifications?
+ */
+ if (qos)
+ self->qos = *qos;
+
+ irlmp_do_lsap_event(self, LM_CONNECT_REQUEST, skb);
+
+ return 0;
+}
+
+/*
+ * Function irlmp_connect_indication (self)
+ *
+ * Incoming connection
+ *
+ */
+void irlmp_connect_indication(struct lsap_cb *self, struct sk_buff *skb)
+{
+ int max_seg_size;
+ int lap_header_size;
+ int max_header_size;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+ ASSERT(self->lap != NULL, return;);
+
+ IRDA_DEBUG(2, "%s(), slsap_sel=%02x, dlsap_sel=%02x\n",
+ __FUNCTION__, self->slsap_sel, self->dlsap_sel);
+
+ /* Note : self->lap is set in irlmp_link_data_indication(),
+ * (case CONNECT_CMD:) because we have no way to set it here.
+ * Similarly, self->dlsap_sel is usually set in irlmp_find_lsap().
+ * Jean II */
+
+ self->qos = *self->lap->qos;
+
+ max_seg_size = self->lap->qos->data_size.value-LMP_HEADER;
+ lap_header_size = IRLAP_GET_HEADER_SIZE(self->lap->irlap);
+ max_header_size = LMP_HEADER + lap_header_size;
+
+ /* Hide LMP_CONTROL_HEADER header from layer above */
+ skb_pull(skb, LMP_CONTROL_HEADER);
+
+ if (self->notify.connect_indication)
+ self->notify.connect_indication(self->notify.instance, self,
+ &self->qos, max_seg_size,
+ max_header_size, skb);
+ else
+ dev_kfree_skb(skb);
+}
+
+/*
+ * Function irlmp_connect_response (handle, userdata)
+ *
+ * Service user is accepting connection
+ *
+ */
+int irlmp_connect_response(struct lsap_cb *self, struct sk_buff *userdata)
+{
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return -1;);
+ ASSERT(userdata != NULL, return -1;);
+
+ set_bit(0, &self->connected); /* TRUE */
+
+ IRDA_DEBUG(2, "%s(), slsap_sel=%02x, dlsap_sel=%02x\n",
+ __FUNCTION__, self->slsap_sel, self->dlsap_sel);
+
+ /* Make room for MUX control header (3 bytes) */
+ ASSERT(skb_headroom(userdata) >= LMP_CONTROL_HEADER, return -1;);
+ skb_push(userdata, LMP_CONTROL_HEADER);
+
+ irlmp_do_lsap_event(self, LM_CONNECT_RESPONSE, userdata);
+
+ return 0;
+}
+
+/*
+ * Function irlmp_connect_confirm (handle, skb)
+ *
+ * LSAP connection confirmed peer device!
+ */
+void irlmp_connect_confirm(struct lsap_cb *self, struct sk_buff *skb)
+{
+ int max_header_size;
+ int lap_header_size;
+ int max_seg_size;
+
+ IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+ ASSERT(skb != NULL, return;);
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return;);
+ ASSERT(self->lap != NULL, return;);
+
+ self->qos = *self->lap->qos;
+
+ max_seg_size = self->lap->qos->data_size.value-LMP_HEADER;
+ lap_header_size = IRLAP_GET_HEADER_SIZE(self->lap->irlap);
+ max_header_size = LMP_HEADER + lap_header_size;
+
+ IRDA_DEBUG(2, "%s(), max_header_size=%d\n",
+ __FUNCTION__, max_header_size);
+
+ /* Hide LMP_CONTROL_HEADER header from layer above */
+ skb_pull(skb, LMP_CONTROL_HEADER);
+
+ if (self->notify.connect_confirm) {
+ self->notify.connect_confirm(self->notify.instance, self,
+ &self->qos, max_seg_size,
+ max_header_size, skb);
+ } else
+ dev_kfree_skb(skb);
+}
+
+/*
+ * Function irlmp_dup (orig, instance)
+ *
+ * Duplicate LSAP, can be used by servers to confirm a connection on a
+ * new LSAP so it can keep listening on the old one.
+ *
+ */
+struct lsap_cb *irlmp_dup(struct lsap_cb *orig, void *instance)
+{
+ struct lsap_cb *new;
+
+ IRDA_DEBUG(1, "%s()\n", __FUNCTION__);
+
+ /* Only allowed to duplicate unconnected LSAP's */
+ if (!hashbin_find(irlmp->unconnected_lsaps, (int) orig, NULL)) {
+ IRDA_DEBUG(0, "%s(), unable to find LSAP\n", __FUNCTION__);
+ return NULL;
+ }
+ new = kmalloc(sizeof(struct lsap_cb), GFP_ATOMIC);
+ if (!new) {
+ IRDA_DEBUG(0, "%s(), unable to kmalloc\n", __FUNCTION__);
+ return NULL;
+ }
+ /* Dup */
+ memcpy(new, orig, sizeof(struct lsap_cb));
+ new->notify.instance = instance;
+ /* new->lap = orig->lap; => done in the memcpy() */
+ /* new->slsap_sel = orig->slsap_sel; => done in the memcpy() */
+
+ init_timer(&new->watchdog_timer);
+
+ hashbin_insert(irlmp->unconnected_lsaps, (irda_queue_t *) new, (int) new,
+ NULL);
+
+ /* Make sure that we invalidate the cache */
+#ifdef CONFIG_IRDA_CACHE_LAST_LSAP
+ irlmp->cache.valid = FALSE;
+#endif /* CONFIG_IRDA_CACHE_LAST_LSAP */
+
+ return new;
+}
+
+/*
+ * Function irlmp_disconnect_request (handle, userdata)
+ *
+ * The service user is requesting disconnection, this will not remove the
+ * LSAP, but only mark it as disconnected
+ */
+int irlmp_disconnect_request(struct lsap_cb *self, struct sk_buff *userdata)
+{
+ struct lsap_cb *lsap;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return -1;);
+ ASSERT(userdata != NULL, return -1;);
+
+ /* Already disconnected ?
+ * There is a race condition between irlmp_disconnect_indication()
+ * and us that might mess up the hashbins below. This fixes it.
+ * Jean II */
+ if (! test_and_clear_bit(0, &self->connected)) {
+ IRDA_DEBUG(0, "%s(), already disconnected!\n", __FUNCTION__);
+ dev_kfree_skb(userdata);
+ return -1;
+ }
+
+ skb_push(userdata, LMP_CONTROL_HEADER);
+
+ /*
+ * Do the event before the other stuff since we must know
+ * which lap layer that the frame should be transmitted on
+ */
+ irlmp_do_lsap_event(self, LM_DISCONNECT_REQUEST, userdata);
+
+ /*
+ * Remove LSAP from list of connected LSAPs for the particular link
+ * and insert it into the list of unconnected LSAPs
+ */
+ ASSERT(self->lap != NULL, return -1;);
+ ASSERT(self->lap->magic == LMP_LAP_MAGIC, return -1;);
+ ASSERT(self->lap->lsaps != NULL, return -1;);
+
+ lsap = hashbin_remove(self->lap->lsaps, (int) self, NULL);
+
+ ASSERT(lsap != NULL, return -1;);
+ ASSERT(lsap->magic == LMP_LSAP_MAGIC, return -1;);
+ ASSERT(lsap == self, return -1;);
+
+ hashbin_insert(irlmp->unconnected_lsaps, (irda_queue_t *) self, (int) self,
+ NULL);
+
+ /* Reset some values */
+ self->dlsap_sel = LSAP_ANY;
+ self->lap = NULL;
+
+ return 0;
+}
+
+/*
+ * Function irlmp_disconnect_indication (reason, userdata)
+ *
+ * LSAP is being closed!
+ */
+void irlmp_disconnect_indication(struct lsap_cb *self, LM_REASON reason,
+ struct sk_buff *userdata)
+{
+ struct lsap_cb *lsap;
+
+ IRDA_DEBUG(1, "%s(), reason=%s\n", __FUNCTION__, lmp_reasons[reason]);
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return;);
+
+ IRDA_DEBUG(3, "%s(), slsap_sel=%02x, dlsap_sel=%02x\n",
+ __FUNCTION__, self->slsap_sel, self->dlsap_sel);
+
+ /* Already disconnected ?
+ * There is a race condition between irlmp_disconnect_request()
+ * and us that might mess up the hashbins below. This fixes it.
+ * Jean II */
+ if (! test_and_clear_bit(0, &self->connected)) {
+ IRDA_DEBUG(0, "%s(), already disconnected!\n", __FUNCTION__);
+ if (userdata)
+ dev_kfree_skb(userdata);
+ return;
+ }
+
+#ifdef CONFIG_IRDA_CACHE_LAST_LSAP
+ irlmp->cache.valid = FALSE;
+#endif
+
+ /*
+ * Remove association between this LSAP and the link it used
+ */
+ ASSERT(self->lap != NULL, return;);
+ ASSERT(self->lap->lsaps != NULL, return;);
+
+ lsap = hashbin_remove(self->lap->lsaps, (int) self, NULL);
+
+ ASSERT(lsap != NULL, return;);
+ ASSERT(lsap == self, return;);
+ hashbin_insert(irlmp->unconnected_lsaps, (irda_queue_t *) lsap, (int) lsap,
+ NULL);
+
+ self->dlsap_sel = LSAP_ANY;
+ self->lap = NULL;
+
+ /*
+ * Inform service user
+ */
+ if (self->notify.disconnect_indication)
+ self->notify.disconnect_indication(self->notify.instance,
+ self, reason, userdata);
+ else {
+ IRDA_DEBUG(0, "%s(), no handler\n", __FUNCTION__);
+ if (userdata)
+ dev_kfree_skb(userdata);
+ }
+}
+
+/*
+ * Function irlmp_do_expiry (void)
+ *
+ * Do a cleanup of the discovery log (remove old entries)
+ *
+ * Note : separate from irlmp_do_discovery() so that we can handle
+ * passive discovery properly.
+ */
+void irlmp_do_expiry()
+{
+ struct lap_cb *lap;
+
+ /*
+ * Expire discovery on all links which are *not* connected.
+ * On links which are connected, we can't do discovery
+ * anymore and can't refresh the log, so we freeze the
+ * discovery log to keep info about the device we are
+ * connected to.
+ * This info is mandatory if we want irlmp_connect_request()
+ * to work properly. - Jean II
+ */
+ lap = (struct lap_cb *) hashbin_get_first(irlmp->links);
+ while (lap != NULL) {
+ ASSERT(lap->magic == LMP_LAP_MAGIC, return;);
+
+ if (lap->lap_state == LAP_STANDBY) {
+ /* Expire discoveries discovered on this link */
+ irlmp_expire_discoveries(irlmp->cachelog, lap->saddr,
+ FALSE);
+ }
+ lap = (struct lap_cb *) hashbin_get_next(irlmp->links);
+ }
+}
+
+/*
+ * Function irlmp_do_discovery (nslots)
+ *
+ * Do some discovery on all links
+ *
+ * Note : log expiry is done above.
+ */
+void irlmp_do_discovery(int nslots)
+{
+ struct lap_cb *lap;
+
+ /* Make sure the value is sane */
+ if ((nslots != 1) && (nslots != 6) && (nslots != 8) && (nslots != 16)){
+ WARNING("%s(), invalid value for number of slots!\n", __FUNCTION__);
+ nslots = sysctl_discovery_slots = 8;
+ }
+
+ /* Construct new discovery info to be used by IrLAP, */
+ irlmp->discovery_cmd.hints.word = irlmp->hints.word;
+
+ /*
+ * Set character set for device name (we use ASCII), and
+ * copy device name. Remember to make room for a \0 at the
+ * end
+ */
+ irlmp->discovery_cmd.charset = CS_ASCII;
+ strncpy(irlmp->discovery_cmd.nickname, sysctl_devname,
+ NICKNAME_MAX_LEN);
+ irlmp->discovery_cmd.name_len = strlen(irlmp->discovery_cmd.nickname);
+ irlmp->discovery_cmd.nslots = nslots;
+
+ /*
+ * Try to send discovery packets on all links
+ */
+ lap = (struct lap_cb *) hashbin_get_first(irlmp->links);
+ while (lap != NULL) {
+ ASSERT(lap->magic == LMP_LAP_MAGIC, return;);
+
+ if (lap->lap_state == LAP_STANDBY) {
+ /* Try to discover */
+ irlmp_do_lap_event(lap, LM_LAP_DISCOVERY_REQUEST,
+ NULL);
+ }
+ lap = (struct lap_cb *) hashbin_get_next(irlmp->links);
+ }
+}
+
+/*
+ * Function irlmp_discovery_request (nslots)
+ *
+ * Do a discovery of devices in front of the computer
+ *
+ */
+void irlmp_discovery_request(int nslots)
+{
+ /* Return current cached discovery log */
+ irlmp_discovery_confirm(irlmp->cachelog, DISCOVERY_LOG);
+
+ /*
+ * Start a single discovery operation if discovery is not already
+ * running
+ */
+ if (!sysctl_discovery) {
+ /* Check if user wants to override the default */
+ if (nslots == DISCOVERY_DEFAULT_SLOTS)
+ nslots = sysctl_discovery_slots;
+
+ irlmp_do_discovery(nslots);
+ /* Note : we never do expiry here. Expiry will run on the
+ * discovery timer regardless of the state of sysctl_discovery
+ * Jean II */
+ }
+}
+
+/*
+ * Function irlmp_get_discoveries (pn, mask, slots)
+ *
+ * Return the current discovery log
+ *
+ */
+struct irda_device_info *irlmp_get_discoveries(int *pn, __u16 mask, int nslots)
+{
+ /* If discovery is not enabled, it's likely that the discovery log
+ * will be empty. So, we trigger a single discovery, so that next
+ * time the user call us there might be some results in the log.
+ * Jean II
+ */
+ if (!sysctl_discovery) {
+ /* Check if user wants to override the default */
+ if (nslots == DISCOVERY_DEFAULT_SLOTS)
+ nslots = sysctl_discovery_slots;
+
+ /* Start discovery - will complete sometime later */
+ irlmp_do_discovery(nslots);
+ /* Note : we never do expiry here. Expiry will run on the
+ * discovery timer regardless of the state of sysctl_discovery
+ * Jean II */
+ }
+
+ /* Return current cached discovery log */
+ return(irlmp_copy_discoveries(irlmp->cachelog, pn, mask));
+}
+
+#if 0
+/*
+ * Function irlmp_check_services (discovery)
+ *
+ *
+ *
+ */
+void irlmp_check_services(discovery_t *discovery)
+{
+ struct irlmp_client *client;
+ __u8 *service_log;
+ __u8 service;
+ int i = 0;
+
+ IRDA_DEBUG(1, "IrDA Discovered: %s\n", discovery->info);
+ IRDA_DEBUG(1, " Services: ");
+
+ service_log = irlmp_hint_to_service(discovery->hints.byte);
+ if (!service_log)
+ return;
+
+ /*
+ * Check all services on the device
+ */
+ while ((service = service_log[i++]) != S_END) {
+ IRDA_DEBUG( 4, "service=%02x\n", service);
+ client = hashbin_find(irlmp->registry, service, NULL);
+ if (entry && entry->discovery_callback) {
+ IRDA_DEBUG( 4, "discovery_callback!\n");
+
+ entry->discovery_callback(discovery);
+ } else {
+ /* Don't notify about the ANY service */
+ if (service == S_ANY)
+ continue;
+ /*
+ * Found no clients for dealing with this service,
+ */
+ }
+ }
+ kfree(service_log);
+}
+#endif
+/*
+ * Function irlmp_notify_client (log)
+ *
+ * Notify all about discovered devices
+ *
+ * Clients registered with IrLMP are :
+ * o IrComm
+ * o IrLAN
+ * o Any socket (in any state - ouch, that may be a lot !)
+ * The client may have defined a callback to be notified in case of
+ * partial/selective discovery based on the hints that it passed to IrLMP.
+ */
+static inline void
+irlmp_notify_client(irlmp_client_t *client,
+ hashbin_t *log, DISCOVERY_MODE mode)
+{
+ discovery_t *discovery;
+
+ IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+ /* Check if client wants or not partial/selective log (optimisation) */
+ if (!client->disco_callback)
+ return;
+
+ /*
+ * Now, check all discovered devices (if any), and notify client
+ * only about the services that the client is interested in
+ */
+ discovery = (discovery_t *) hashbin_get_first(log);
+ while (discovery != NULL) {
+ IRDA_DEBUG(3, "discovery->daddr = 0x%08x\n", discovery->daddr);
+
+ /*
+ * Any common hint bits? Remember to mask away the extension
+ * bits ;-)
+ */
+ if (client->hint_mask & discovery->hints.word & 0x7f7f)
+ client->disco_callback(discovery, mode, client->priv);
+
+ discovery = (discovery_t *) hashbin_get_next(log);
+ }
+}
+
+/*
+ * Function irlmp_discovery_confirm ( self, log)
+ *
+ * Some device(s) answered to our discovery request! Check to see which
+ * device it is, and give indication to the client(s)
+ *
+ */
+void irlmp_discovery_confirm(hashbin_t *log, DISCOVERY_MODE mode)
+{
+ irlmp_client_t *client;
+
+ IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+ ASSERT(log != NULL, return;);
+
+ if (!(HASHBIN_GET_SIZE(log)))
+ return;
+
+ client = (irlmp_client_t *) hashbin_get_first(irlmp->clients);
+ while (client != NULL) {
+ /* Check if we should notify client */
+ irlmp_notify_client(client, log, mode);
+
+ client = (irlmp_client_t *) hashbin_get_next(irlmp->clients);
+ }
+}
+
+/*
+ * Function irlmp_discovery_expiry (expiry)
+ *
+ * This device is no longer been discovered, and therefore it is beeing
+ * purged from the discovery log. Inform all clients who have
+ * registered for this event...
+ *
+ * Note : called exclusively from discovery.c
+ * Note : as we are currently processing the log, the clients callback
+ * should *NOT* attempt to touch the log now.
+ */
+void irlmp_discovery_expiry(discovery_t *expiry)
+{
+ irlmp_client_t *client;
+
+ IRDA_DEBUG(3, "%s()\n", __FUNCTION__);
+
+ ASSERT(expiry != NULL, return;);
+
+ client = (irlmp_client_t *) hashbin_get_first(irlmp->clients);
+ while (client != NULL) {
+ /* Check if we should notify client */
+ if ((client->expir_callback) &&
+ (client->hint_mask & expiry->hints.word & 0x7f7f))
+ client->expir_callback(expiry, EXPIRY_TIMEOUT,
+ client->priv);
+
+ /* Next client */
+ client = (irlmp_client_t *) hashbin_get_next(irlmp->clients);
+ }
+}
+
+/*
+ * Function irlmp_get_discovery_response ()
+ *
+ * Used by IrLAP to get the discovery info it needs when answering
+ * discovery requests by other devices.
+ */
+discovery_t *irlmp_get_discovery_response()
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(irlmp != NULL, return NULL;);
+
+ irlmp->discovery_rsp.hints.word = irlmp->hints.word;
+
+ /*
+ * Set character set for device name (we use ASCII), and
+ * copy device name. Remember to make room for a \0 at the
+ * end
+ */
+ irlmp->discovery_rsp.charset = CS_ASCII;
+
+ strncpy(irlmp->discovery_rsp.nickname, sysctl_devname,
+ NICKNAME_MAX_LEN);
+ irlmp->discovery_rsp.name_len = strlen(irlmp->discovery_rsp.nickname);
+
+ return &irlmp->discovery_rsp;
+}
+
+/*
+ * Function irlmp_data_request (self, skb)
+ *
+ * Send some data to peer device
+ *
+ */
+int irlmp_data_request(struct lsap_cb *self, struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return -1;);
+
+ /* Make room for MUX header */
+ ASSERT(skb_headroom(skb) >= LMP_HEADER, return -1;);
+ skb_push(skb, LMP_HEADER);
+
+ return irlmp_do_lsap_event(self, LM_DATA_REQUEST, skb);
+}
+
+/*
+ * Function irlmp_data_indication (handle, skb)
+ *
+ * Got data from LAP layer so pass it up to upper layer
+ *
+ */
+void irlmp_data_indication(struct lsap_cb *self, struct sk_buff *skb)
+{
+ /* Hide LMP header from layer above */
+ skb_pull(skb, LMP_HEADER);
+
+ if (self->notify.data_indication)
+ self->notify.data_indication(self->notify.instance, self, skb);
+ else
+ dev_kfree_skb(skb);
+}
+
+/*
+ * Function irlmp_udata_request (self, skb)
+ *
+ *
+ *
+ */
+int irlmp_udata_request(struct lsap_cb *self, struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(skb != NULL, return -1;);
+
+ /* Make room for MUX header */
+ ASSERT(skb_headroom(skb) >= LMP_HEADER, return -1;);
+ skb_push(skb, LMP_HEADER);
+
+ return irlmp_do_lsap_event(self, LM_UDATA_REQUEST, skb);
+}
+
+/*
+ * Function irlmp_udata_indication (self, skb)
+ *
+ * Send unreliable data (but still within the connection)
+ *
+ */
+void irlmp_udata_indication(struct lsap_cb *self, struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+
+ /* Hide LMP header from layer above */
+ skb_pull(skb, LMP_HEADER);
+
+ if (self->notify.udata_indication)
+ self->notify.udata_indication(self->notify.instance, self,
+ skb);
+ else
+ dev_kfree_skb(skb);
+}
+
+/*
+ * Function irlmp_connless_data_request (self, skb)
+ *
+ *
+ *
+ */
+#ifdef CONFIG_IRDA_ULTRA
+int irlmp_connless_data_request(struct lsap_cb *self, struct sk_buff *skb)
+{
+ struct sk_buff *clone_skb;
+ struct lap_cb *lap;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(skb != NULL, return -1;);
+
+ /* Make room for MUX and PID header */
+ ASSERT(skb_headroom(skb) >= LMP_HEADER+LMP_PID_HEADER, return -1;);
+
+ /* Insert protocol identifier */
+ skb_push(skb, LMP_PID_HEADER);
+ skb->data[0] = self->pid;
+
+ /* Connectionless sockets must use 0x70 */
+ skb_push(skb, LMP_HEADER);
+ skb->data[0] = skb->data[1] = LSAP_CONNLESS;
+
+ /* Try to send Connectionless packets out on all links */
+ lap = (struct lap_cb *) hashbin_get_first(irlmp->links);
+ while (lap != NULL) {
+ ASSERT(lap->magic == LMP_LAP_MAGIC, return -1;);
+
+ clone_skb = skb_clone(skb, GFP_ATOMIC);
+ if (!clone_skb)
+ return -ENOMEM;
+
+ irlap_unitdata_request(lap->irlap, clone_skb);
+
+ lap = (struct lap_cb *) hashbin_get_next(irlmp->links);
+ }
+ dev_kfree_skb(skb);
+
+ return 0;
+}
+#endif /* CONFIG_IRDA_ULTRA */
+
+/*
+ * Function irlmp_connless_data_indication (self, skb)
+ *
+ * Receive unreliable data outside any connection. Mostly used by Ultra
+ *
+ */
+#ifdef CONFIG_IRDA_ULTRA
+void irlmp_connless_data_indication(struct lsap_cb *self, struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+
+ /* Hide LMP and PID header from layer above */
+ skb_pull(skb, LMP_HEADER+LMP_PID_HEADER);
+
+ if (self->notify.udata_indication)
+ self->notify.udata_indication(self->notify.instance, self,
+ skb);
+ else
+ dev_kfree_skb(skb);
+}
+#endif /* CONFIG_IRDA_ULTRA */
+
+void irlmp_status_request(void)
+{
+ IRDA_DEBUG(0, "%s(), Not implemented\n", __FUNCTION__);
+}
+
+/*
+ * Propagate status indication from LAP to LSAPs (via LMP)
+ * This don't trigger any change of state in lap_cb, lmp_cb or lsap_cb,
+ * and the event is stateless, therefore we can bypass both state machines
+ * and send the event direct to the LSAP user.
+ * Jean II
+ */
+void irlmp_status_indication(struct lap_cb *self,
+ LINK_STATUS link, LOCK_STATUS lock)
+{
+ struct lsap_cb *next;
+ struct lsap_cb *curr;
+
+ /* Send status_indication to all LSAPs using this link */
+ next = (struct lsap_cb *) hashbin_get_first( self->lsaps);
+ while (next != NULL ) {
+ curr = next;
+ next = (struct lsap_cb *) hashbin_get_next(self->lsaps);
+
+ ASSERT(curr->magic == LMP_LSAP_MAGIC, return;);
+ /*
+ * Inform service user if he has requested it
+ */
+ if (curr->notify.status_indication != NULL)
+ curr->notify.status_indication(curr->notify.instance,
+ link, lock);
+ else
+ IRDA_DEBUG(2, "%s(), no handler\n", __FUNCTION__);
+ }
+}
+
+/*
+ * Receive flow control indication from LAP.
+ * LAP want us to send it one more frame. We implement a simple round
+ * robin scheduler between the active sockets so that we get a bit of
+ * fairness. Note that the round robin is far from perfect, but it's
+ * better than nothing.
+ * We then poll the selected socket so that we can do synchronous
+ * refilling of IrLAP (which allow to minimise the number of buffers).
+ * Jean II
+ */
+void irlmp_flow_indication(struct lap_cb *self, LOCAL_FLOW flow)
+{
+ struct lsap_cb *next;
+ struct lsap_cb *curr;
+ int lsap_todo;
+
+ ASSERT(self->magic == LMP_LAP_MAGIC, return;);
+ ASSERT(flow == FLOW_START, return;);
+
+ /* Get the number of lsap. That's the only safe way to know
+ * that we have looped around... - Jean II */
+ lsap_todo = HASHBIN_GET_SIZE(self->lsaps);
+ IRDA_DEBUG(4, "%s() : %d lsaps to scan\n", __FUNCTION__ , lsap_todo);
+
+ /* Poll lsap in order until the queue is full or until we
+ * tried them all.
+ * Most often, the current LSAP will have something to send,
+ * so we will go through this loop only once. - Jean II */
+ while((lsap_todo--) &&
+ (IRLAP_GET_TX_QUEUE_LEN(self->irlap) < LAP_HIGH_THRESHOLD)) {
+ /* Try to find the next lsap we should poll. */
+ next = self->flow_next;
+ if(next != NULL) {
+ /* Note that if there is only one LSAP on the LAP
+ * (most common case), self->flow_next is always NULL,
+ * so we always avoid this loop. - Jean II */
+ IRDA_DEBUG(4, "%s() : searching my LSAP\n", __FUNCTION__ );
+
+ /* We look again in hashbins, because the lsap
+ * might have gone away... - Jean II */
+ curr = (struct lsap_cb *) hashbin_get_first(self->lsaps);
+ while((curr != NULL ) && (curr != next))
+ curr = (struct lsap_cb *) hashbin_get_next(self->lsaps);
+ } else
+ curr = NULL;
+
+ /* If we have no lsap, restart from first one */
+ if(curr == NULL)
+ curr = (struct lsap_cb *) hashbin_get_first(self->lsaps);
+ /* Uh-oh... Paranoia */
+ if(curr == NULL)
+ break;
+
+ /* Next time, we will get the next one (or the first one) */
+ self->flow_next = (struct lsap_cb *) hashbin_get_next(self->lsaps);
+ IRDA_DEBUG(4, "%s() : curr is %p, next was %p and is now %p, still %d to go - queue len = %d\n", __FUNCTION__ , curr, next, self->flow_next, lsap_todo, IRLAP_GET_TX_QUEUE_LEN(self->irlap));
+
+ /* Inform lsap user that it can send one more packet. */
+ if (curr->notify.flow_indication != NULL)
+ curr->notify.flow_indication(curr->notify.instance,
+ curr, flow);
+ else
+ IRDA_DEBUG(1, "%s(), no handler\n", __FUNCTION__ );
+ }
+}
+
+/*
+ * Function irlmp_hint_to_service (hint)
+ *
+ * Returns a list of all servics contained in the given hint bits. This
+ * funtion assumes that the hint bits have the size of two bytes only
+ */
+__u8 *irlmp_hint_to_service(__u8 *hint)
+{
+ __u8 *service;
+ int i = 0;
+
+ /*
+ * Allocate array to store services in. 16 entries should be safe
+ * since we currently only support 2 hint bytes
+ */
+ service = kmalloc(16, GFP_ATOMIC);
+ if (!service) {
+ IRDA_DEBUG(1, "%s(), Unable to kmalloc!\n", __FUNCTION__);
+ return NULL;
+ }
+
+ if (!hint[0]) {
+ IRDA_DEBUG(1, "<None>\n");
+ kfree(service);
+ return NULL;
+ }
+ if (hint[0] & HINT_PNP)
+ IRDA_DEBUG(1, "PnP Compatible ");
+ if (hint[0] & HINT_PDA)
+ IRDA_DEBUG(1, "PDA/Palmtop ");
+ if (hint[0] & HINT_COMPUTER)
+ IRDA_DEBUG(1, "Computer ");
+ if (hint[0] & HINT_PRINTER) {
+ IRDA_DEBUG(1, "Printer ");
+ service[i++] = S_PRINTER;
+ }
+ if (hint[0] & HINT_MODEM)
+ IRDA_DEBUG(1, "Modem ");
+ if (hint[0] & HINT_FAX)
+ IRDA_DEBUG(1, "Fax ");
+ if (hint[0] & HINT_LAN) {
+ IRDA_DEBUG(1, "LAN Access ");
+ service[i++] = S_LAN;
+ }
+ /*
+ * Test if extension byte exists. This byte will usually be
+ * there, but this is not really required by the standard.
+ * (IrLMP p. 29)
+ */
+ if (hint[0] & HINT_EXTENSION) {
+ if (hint[1] & HINT_TELEPHONY) {
+ IRDA_DEBUG(1, "Telephony ");
+ service[i++] = S_TELEPHONY;
+ } if (hint[1] & HINT_FILE_SERVER)
+ IRDA_DEBUG(1, "File Server ");
+
+ if (hint[1] & HINT_COMM) {
+ IRDA_DEBUG(1, "IrCOMM ");
+ service[i++] = S_COMM;
+ }
+ if (hint[1] & HINT_OBEX) {
+ IRDA_DEBUG(1, "IrOBEX ");
+ service[i++] = S_OBEX;
+ }
+ }
+ IRDA_DEBUG(1, "\n");
+
+ /* So that client can be notified about any discovery */
+ service[i++] = S_ANY;
+
+ service[i] = S_END;
+
+ return service;
+}
+
+/*
+ * Function irlmp_service_to_hint (service)
+ *
+ * Converts a service type, to a hint bit
+ *
+ * Returns: a 16 bit hint value, with the service bit set
+ */
+__u16 irlmp_service_to_hint(int service)
+{
+ __u16_host_order hint;
+
+ hint.word = 0;
+
+ switch (service) {
+ case S_PNP:
+ hint.byte[0] |= HINT_PNP;
+ break;
+ case S_PDA:
+ hint.byte[0] |= HINT_PDA;
+ break;
+ case S_COMPUTER:
+ hint.byte[0] |= HINT_COMPUTER;
+ break;
+ case S_PRINTER:
+ hint.byte[0] |= HINT_PRINTER;
+ break;
+ case S_MODEM:
+ hint.byte[0] |= HINT_PRINTER;
+ break;
+ case S_LAN:
+ hint.byte[0] |= HINT_LAN;
+ break;
+ case S_COMM:
+ hint.byte[0] |= HINT_EXTENSION;
+ hint.byte[1] |= HINT_COMM;
+ break;
+ case S_OBEX:
+ hint.byte[0] |= HINT_EXTENSION;
+ hint.byte[1] |= HINT_OBEX;
+ break;
+ case S_TELEPHONY:
+ hint.byte[0] |= HINT_EXTENSION;
+ hint.byte[1] |= HINT_TELEPHONY;
+ break;
+ case S_ANY:
+ hint.word = 0xffff;
+ break;
+ default:
+ IRDA_DEBUG( 1, "%s(), Unknown service!\n", __FUNCTION__);
+ break;
+ }
+ return hint.word;
+}
+
+/*
+ * Function irlmp_register_service (service)
+ *
+ * Register local service with IrLMP
+ *
+ */
+__u32 irlmp_register_service(__u16 hints)
+{
+ irlmp_service_t *service;
+ __u32 handle;
+
+ IRDA_DEBUG(4, "%s(), hints = %04x\n", __FUNCTION__, hints);
+
+ /* Get a unique handle for this service */
+ get_random_bytes(&handle, sizeof(handle));
+ while (hashbin_find(irlmp->services, handle, NULL) || !handle)
+ get_random_bytes(&handle, sizeof(handle));
+
+ irlmp->hints.word |= hints;
+
+ /* Make a new registration */
+ service = kmalloc(sizeof(irlmp_service_t), GFP_ATOMIC);
+ if (!service) {
+ IRDA_DEBUG(1, "%s(), Unable to kmalloc!\n", __FUNCTION__);
+ return 0;
+ }
+ service->hints = hints;
+ hashbin_insert(irlmp->services, (irda_queue_t *) service, handle, NULL);
+
+ return handle;
+}
+
+/*
+ * Function irlmp_unregister_service (handle)
+ *
+ * Unregister service with IrLMP.
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int irlmp_unregister_service(__u32 handle)
+{
+ irlmp_service_t *service;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ if (!handle)
+ return -1;
+
+ service = hashbin_find(irlmp->services, handle, NULL);
+ if (!service) {
+ IRDA_DEBUG(1, "%s(), Unknown service!\n", __FUNCTION__);
+ return -1;
+ }
+
+ service = hashbin_remove(irlmp->services, handle, NULL);
+ if (service)
+ kfree(service);
+
+ /* Remove old hint bits */
+ irlmp->hints.word = 0;
+
+ /* Refresh current hint bits */
+ service = (irlmp_service_t *) hashbin_get_first(irlmp->services);
+ while (service) {
+ irlmp->hints.word |= service->hints;
+
+ service = (irlmp_service_t *)hashbin_get_next(irlmp->services);
+ }
+ return 0;
+}
+
+/*
+ * Function irlmp_register_client (hint_mask, callback1, callback2)
+ *
+ * Register a local client with IrLMP
+ * First callback is selective discovery (based on hints)
+ * Second callback is for selective discovery expiries
+ *
+ * Returns: handle > 0 on success, 0 on error
+ */
+__u32 irlmp_register_client(__u16 hint_mask, DISCOVERY_CALLBACK1 disco_clb,
+ DISCOVERY_CALLBACK1 expir_clb, void *priv)
+{
+ irlmp_client_t *client;
+ __u32 handle;
+
+ IRDA_DEBUG(1, "%s()\n", __FUNCTION__);
+ ASSERT(irlmp != NULL, return 0;);
+
+ /* Get a unique handle for this client */
+ get_random_bytes(&handle, sizeof(handle));
+ while (hashbin_find(irlmp->clients, handle, NULL) || !handle)
+ get_random_bytes(&handle, sizeof(handle));
+
+ /* Make a new registration */
+ client = kmalloc(sizeof(irlmp_client_t), GFP_ATOMIC);
+ if (!client) {
+ IRDA_DEBUG( 1, "%s(), Unable to kmalloc!\n", __FUNCTION__);
+ return 0;
+ }
+
+ /* Register the details */
+ client->hint_mask = hint_mask;
+ client->disco_callback = disco_clb;
+ client->expir_callback = expir_clb;
+ client->priv = priv;
+
+ hashbin_insert(irlmp->clients, (irda_queue_t *) client, handle, NULL);
+
+ return handle;
+}
+
+/*
+ * Function irlmp_update_client (handle, hint_mask, callback1, callback2)
+ *
+ * Updates specified client (handle) with possibly new hint_mask and
+ * callback
+ *
+ * Returns: 0 on success, -1 on error
+ */
+int irlmp_update_client(__u32 handle, __u16 hint_mask,
+ DISCOVERY_CALLBACK1 disco_clb,
+ DISCOVERY_CALLBACK1 expir_clb, void *priv)
+{
+ irlmp_client_t *client;
+
+ if (!handle)
+ return -1;
+
+ client = hashbin_find(irlmp->clients, handle, NULL);
+ if (!client) {
+ IRDA_DEBUG(1, "%s(), Unknown client!\n", __FUNCTION__);
+ return -1;
+ }
+
+ client->hint_mask = hint_mask;
+ client->disco_callback = disco_clb;
+ client->expir_callback = expir_clb;
+ client->priv = priv;
+
+ return 0;
+}
+
+/*
+ * Function irlmp_unregister_client (handle)
+ *
+ * Returns: 0 on success, -1 on error
+ *
+ */
+int irlmp_unregister_client(__u32 handle)
+{
+ struct irlmp_client *client;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ if (!handle)
+ return -1;
+
+ client = hashbin_find(irlmp->clients, handle, NULL);
+ if (!client) {
+ IRDA_DEBUG(1, "%s(), Unknown client!\n", __FUNCTION__);
+ return -1;
+ }
+
+ IRDA_DEBUG( 4, "%s(), removing client!\n", __FUNCTION__);
+ client = hashbin_remove( irlmp->clients, handle, NULL);
+ if (client)
+ kfree(client);
+
+ return 0;
+}
+
+/*
+ * Function irlmp_slsap_inuse (slsap)
+ *
+ * Check if the given source LSAP selector is in use
+ */
+int irlmp_slsap_inuse(__u8 slsap_sel)
+{
+ struct lsap_cb *self;
+ struct lap_cb *lap;
+
+ ASSERT(irlmp != NULL, return TRUE;);
+ ASSERT(irlmp->magic == LMP_MAGIC, return TRUE;);
+ ASSERT(slsap_sel != LSAP_ANY, return TRUE;);
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+#ifdef CONFIG_IRDA_ULTRA
+ /* Accept all bindings to the connectionless LSAP */
+ if (slsap_sel == LSAP_CONNLESS)
+ return FALSE;
+#endif /* CONFIG_IRDA_ULTRA */
+
+ /* Valid values are between 0 and 127 */
+ if (slsap_sel > LSAP_MAX)
+ return TRUE;
+
+ /*
+ * Check if slsap is already in use. To do this we have to loop over
+ * every IrLAP connection and check every LSAP assosiated with each
+ * the connection.
+ */
+ lap = (struct lap_cb *) hashbin_get_first(irlmp->links);
+ while (lap != NULL) {
+ ASSERT(lap->magic == LMP_LAP_MAGIC, return TRUE;);
+
+ self = (struct lsap_cb *) hashbin_get_first(lap->lsaps);
+ while (self != NULL) {
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return TRUE;);
+
+ if ((self->slsap_sel == slsap_sel)) {
+ IRDA_DEBUG(4, "Source LSAP selector=%02x in use\n",
+ self->slsap_sel);
+ return TRUE;
+ }
+ self = (struct lsap_cb*) hashbin_get_next(lap->lsaps);
+ }
+ lap = (struct lap_cb *) hashbin_get_next(irlmp->links);
+ }
+ return FALSE;
+}
+
+/*
+ * Function irlmp_find_free_slsap ()
+ *
+ * Find a free source LSAP to use. This function is called if the service
+ * user has requested a source LSAP equal to LM_ANY
+ */
+__u8 irlmp_find_free_slsap(void)
+{
+ __u8 lsap_sel;
+ int wrapped = 0;
+
+ ASSERT(irlmp != NULL, return -1;);
+ ASSERT(irlmp->magic == LMP_MAGIC, return -1;);
+
+ lsap_sel = irlmp->free_lsap_sel++;
+
+ /* Check if the new free lsap is really free */
+ while (irlmp_slsap_inuse(irlmp->free_lsap_sel)) {
+ irlmp->free_lsap_sel++;
+
+ /* Check if we need to wraparound (0x70-0x7f are reserved) */
+ if (irlmp->free_lsap_sel > LSAP_MAX) {
+ irlmp->free_lsap_sel = 10;
+
+ /* Make sure we terminate the loop */
+ if (wrapped++)
+ return 0;
+ }
+ }
+ IRDA_DEBUG(4, "%s(), next free lsap_sel=%02x\n", __FUNCTION__, lsap_sel);
+
+ return lsap_sel;
+}
+
+/*
+ * Function irlmp_convert_lap_reason (lap_reason)
+ *
+ * Converts IrLAP disconnect reason codes to IrLMP disconnect reason
+ * codes
+ *
+ */
+LM_REASON irlmp_convert_lap_reason( LAP_REASON lap_reason)
+{
+ int reason = LM_LAP_DISCONNECT;
+
+ switch (lap_reason) {
+ case LAP_DISC_INDICATION: /* Received a disconnect request from peer */
+ IRDA_DEBUG( 1, "%s(), LAP_DISC_INDICATION\n", __FUNCTION__);
+ reason = LM_USER_REQUEST;
+ break;
+ case LAP_NO_RESPONSE: /* To many retransmits without response */
+ IRDA_DEBUG( 1, "%s(), LAP_NO_RESPONSE\n", __FUNCTION__);
+ reason = LM_LAP_DISCONNECT;
+ break;
+ case LAP_RESET_INDICATION:
+ IRDA_DEBUG( 1, "%s(), LAP_RESET_INDICATION\n", __FUNCTION__);
+ reason = LM_LAP_RESET;
+ break;
+ case LAP_FOUND_NONE:
+ case LAP_MEDIA_BUSY:
+ case LAP_PRIMARY_CONFLICT:
+ IRDA_DEBUG(1, "%s(), LAP_FOUND_NONE, LAP_MEDIA_BUSY or LAP_PRIMARY_CONFLICT\n", __FUNCTION__);
+ reason = LM_CONNECT_FAILURE;
+ break;
+ default:
+ IRDA_DEBUG(1, "%s(), Unknow IrLAP disconnect reason %d!\n",
+ __FUNCTION__, lap_reason);
+ reason = LM_LAP_DISCONNECT;
+ break;
+ }
+
+ return reason;
+}
+
+__u32 irlmp_get_saddr(struct lsap_cb *self)
+{
+ ASSERT(self != NULL, return 0;);
+ ASSERT(self->lap != NULL, return 0;);
+
+ return self->lap->saddr;
+}
+
+__u32 irlmp_get_daddr(struct lsap_cb *self)
+{
+ ASSERT(self != NULL, return 0;);
+ ASSERT(self->lap != NULL, return 0;);
+
+ return self->lap->daddr;
+}
+
+#ifdef CONFIG_PROC_FS
+/*
+ * Function irlmp_proc_read (buf, start, offset, len, unused)
+ *
+ * Give some info to the /proc file system
+ *
+ */
+int irlmp_proc_read(char *buf, char **start, off_t offset, int len)
+{
+ struct lsap_cb *self;
+ struct lap_cb *lap;
+ unsigned long flags;
+
+ ASSERT(irlmp != NULL, return 0;);
+
+ save_flags( flags);
+ cli();
+
+ len = 0;
+
+ len += sprintf( buf+len, "Unconnected LSAPs:\n");
+ self = (struct lsap_cb *) hashbin_get_first( irlmp->unconnected_lsaps);
+ while (self != NULL) {
+ ASSERT(self->magic == LMP_LSAP_MAGIC, break;);
+ len += sprintf(buf+len, "lsap state: %s, ",
+ irlsap_state[ self->lsap_state]);
+ len += sprintf(buf+len,
+ "slsap_sel: %#02x, dlsap_sel: %#02x, ",
+ self->slsap_sel, self->dlsap_sel);
+ len += sprintf(buf+len, "(%s)", self->notify.name);
+ len += sprintf(buf+len, "\n");
+
+ self = (struct lsap_cb *) hashbin_get_next(
+ irlmp->unconnected_lsaps);
+ }
+
+ len += sprintf(buf+len, "\nRegistred Link Layers:\n");
+
+ lap = (struct lap_cb *) hashbin_get_first(irlmp->links);
+ while (lap != NULL) {
+ len += sprintf(buf+len, "lap state: %s, ",
+ irlmp_state[lap->lap_state]);
+
+ len += sprintf(buf+len, "saddr: %#08x, daddr: %#08x, ",
+ lap->saddr, lap->daddr);
+ len += sprintf(buf+len, "num lsaps: %d",
+ HASHBIN_GET_SIZE(lap->lsaps));
+ len += sprintf(buf+len, "\n");
+
+ len += sprintf(buf+len, "\n Connected LSAPs:\n");
+ self = (struct lsap_cb *) hashbin_get_first(lap->lsaps);
+ while (self != NULL) {
+ ASSERT(self->magic == LMP_LSAP_MAGIC, break;);
+ len += sprintf(buf+len, " lsap state: %s, ",
+ irlsap_state[ self->lsap_state]);
+ len += sprintf(buf+len,
+ "slsap_sel: %#02x, dlsap_sel: %#02x, ",
+ self->slsap_sel, self->dlsap_sel);
+ len += sprintf(buf+len, "(%s)", self->notify.name);
+ len += sprintf(buf+len, "\n");
+
+ self = (struct lsap_cb *) hashbin_get_next(
+ lap->lsaps);
+ }
+ len += sprintf(buf+len, "\n");
+
+ lap = (struct lap_cb *) hashbin_get_next(irlmp->links);
+ }
+ restore_flags(flags);
+
+ return len;
+}
+
+#endif /* PROC_FS */
+
+
+
diff --git a/uClinux-2.4.31-uc0/net/irda/irlmp_event.c b/uClinux-2.4.31-uc0/net/irda/irlmp_event.c
new file mode 100644
index 0000000..9decb79
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlmp_event.c
@@ -0,0 +1,898 @@
+/*********************************************************************
+ *
+ * Filename: irlmp_event.c
+ * Version: 0.8
+ * Description: An IrDA LMP event driver for Linux
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Mon Aug 4 20:40:53 1997
+ * Modified at: Tue Dec 14 23:04:16 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1998-1999 Dag Brattli <dagb@cs.uit.no>,
+ * All Rights Reserved.
+ * Copyright (c) 2000-2001 Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/timer.h>
+#include <net/irda/irlap.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/irlmp_frame.h>
+#include <net/irda/irlmp_event.h>
+
+const char *irlmp_state[] = {
+ "LAP_STANDBY",
+ "LAP_U_CONNECT",
+ "LAP_ACTIVE",
+};
+
+const char *irlsap_state[] = {
+ "LSAP_DISCONNECTED",
+ "LSAP_CONNECT",
+ "LSAP_CONNECT_PEND",
+ "LSAP_DATA_TRANSFER_READY",
+ "LSAP_SETUP",
+ "LSAP_SETUP_PEND",
+};
+
+#ifdef CONFIG_IRDA_DEBUG
+static const char *irlmp_event[] = {
+ "LM_CONNECT_REQUEST",
+ "LM_CONNECT_CONFIRM",
+ "LM_CONNECT_RESPONSE",
+ "LM_CONNECT_INDICATION",
+
+ "LM_DISCONNECT_INDICATION",
+ "LM_DISCONNECT_REQUEST",
+
+ "LM_DATA_REQUEST",
+ "LM_UDATA_REQUEST",
+ "LM_DATA_INDICATION",
+ "LM_UDATA_INDICATION",
+
+ "LM_WATCHDOG_TIMEOUT",
+
+ /* IrLAP events */
+ "LM_LAP_CONNECT_REQUEST",
+ "LM_LAP_CONNECT_INDICATION",
+ "LM_LAP_CONNECT_CONFIRM",
+ "LM_LAP_DISCONNECT_INDICATION",
+ "LM_LAP_DISCONNECT_REQUEST",
+ "LM_LAP_DISCOVERY_REQUEST",
+ "LM_LAP_DISCOVERY_CONFIRM",
+ "LM_LAP_IDLE_TIMEOUT",
+};
+#endif /* CONFIG_IRDA_DEBUG */
+
+/* LAP Connection control proto declarations */
+static void irlmp_state_standby (struct lap_cb *, IRLMP_EVENT,
+ struct sk_buff *);
+static void irlmp_state_u_connect(struct lap_cb *, IRLMP_EVENT,
+ struct sk_buff *);
+static void irlmp_state_active (struct lap_cb *, IRLMP_EVENT,
+ struct sk_buff *);
+
+/* LSAP Connection control proto declarations */
+static int irlmp_state_disconnected(struct lsap_cb *, IRLMP_EVENT,
+ struct sk_buff *);
+static int irlmp_state_connect (struct lsap_cb *, IRLMP_EVENT,
+ struct sk_buff *);
+static int irlmp_state_connect_pend(struct lsap_cb *, IRLMP_EVENT,
+ struct sk_buff *);
+static int irlmp_state_dtr (struct lsap_cb *, IRLMP_EVENT,
+ struct sk_buff *);
+static int irlmp_state_setup (struct lsap_cb *, IRLMP_EVENT,
+ struct sk_buff *);
+static int irlmp_state_setup_pend (struct lsap_cb *, IRLMP_EVENT,
+ struct sk_buff *);
+
+static void (*lap_state[]) (struct lap_cb *, IRLMP_EVENT, struct sk_buff *) =
+{
+ irlmp_state_standby,
+ irlmp_state_u_connect,
+ irlmp_state_active,
+};
+
+static int (*lsap_state[])( struct lsap_cb *, IRLMP_EVENT, struct sk_buff *) =
+{
+ irlmp_state_disconnected,
+ irlmp_state_connect,
+ irlmp_state_connect_pend,
+ irlmp_state_dtr,
+ irlmp_state_setup,
+ irlmp_state_setup_pend
+};
+
+static inline void irlmp_next_lap_state(struct lap_cb *self,
+ IRLMP_STATE state)
+{
+ /*
+ IRDA_DEBUG(4, "%s(), LMP LAP = %s\n", __FUNCTION__, irlmp_state[state]);
+ */
+ self->lap_state = state;
+}
+
+static inline void irlmp_next_lsap_state(struct lsap_cb *self,
+ LSAP_STATE state)
+{
+ /*
+ ASSERT(self != NULL, return;);
+ IRDA_DEBUG(4, "%s(), LMP LSAP = %s\n", __FUNCTION__, irlsap_state[state]);
+ */
+ self->lsap_state = state;
+}
+
+/* Do connection control events */
+int irlmp_do_lsap_event(struct lsap_cb *self, IRLMP_EVENT event,
+ struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return -1;);
+
+ IRDA_DEBUG(4, "%s(), EVENT = %s, STATE = %s\n",
+ __FUNCTION__, irlmp_event[event], irlsap_state[ self->lsap_state]);
+
+ return (*lsap_state[self->lsap_state]) (self, event, skb);
+}
+
+/*
+ * Function do_lap_event (event, skb, info)
+ *
+ * Do IrLAP control events
+ *
+ */
+void irlmp_do_lap_event(struct lap_cb *self, IRLMP_EVENT event,
+ struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LAP_MAGIC, return;);
+
+ IRDA_DEBUG(4, "%s(), EVENT = %s, STATE = %s\n",
+ __FUNCTION__, irlmp_event[event],
+ irlmp_state[self->lap_state]);
+
+ (*lap_state[self->lap_state]) (self, event, skb);
+}
+
+void irlmp_discovery_timer_expired(void *data)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ /* We always cleanup the log (active & passive discovery) */
+ irlmp_do_expiry();
+
+ /* Active discovery is conditional */
+ if (sysctl_discovery)
+ irlmp_do_discovery(sysctl_discovery_slots);
+
+ /* Restart timer */
+ irlmp_start_discovery_timer(irlmp, sysctl_discovery_timeout * HZ);
+}
+
+void irlmp_watchdog_timer_expired(void *data)
+{
+ struct lsap_cb *self = (struct lsap_cb *) data;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return;);
+
+ irlmp_do_lsap_event(self, LM_WATCHDOG_TIMEOUT, NULL);
+}
+
+void irlmp_idle_timer_expired(void *data)
+{
+ struct lap_cb *self = (struct lap_cb *) data;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LAP_MAGIC, return;);
+
+ irlmp_do_lap_event(self, LM_LAP_IDLE_TIMEOUT, NULL);
+}
+
+/*********************************************************************
+ *
+ * LAP connection control states
+ *
+ ********************************************************************/
+
+/*
+ * Function irlmp_state_standby (event, skb, info)
+ *
+ * STANDBY, The IrLAP connection does not exist.
+ *
+ */
+static void irlmp_state_standby(struct lap_cb *self, IRLMP_EVENT event,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+ ASSERT(self->irlap != NULL, return;);
+
+ switch (event) {
+ case LM_LAP_DISCOVERY_REQUEST:
+ /* irlmp_next_station_state( LMP_DISCOVER); */
+
+ irlap_discovery_request(self->irlap, &irlmp->discovery_cmd);
+ break;
+ case LM_LAP_CONNECT_INDICATION:
+ /* It's important to switch state first, to avoid IrLMP to
+ * think that the link is free since IrLMP may then start
+ * discovery before the connection is properly set up. DB.
+ */
+ irlmp_next_lap_state(self, LAP_ACTIVE);
+
+ /* Just accept connection TODO, this should be fixed */
+ irlap_connect_response(self->irlap, skb);
+ break;
+ case LM_LAP_CONNECT_REQUEST:
+ IRDA_DEBUG(4, "%s() LS_CONNECT_REQUEST\n", __FUNCTION__);
+
+ irlmp_next_lap_state(self, LAP_U_CONNECT);
+
+ /* FIXME: need to set users requested QoS */
+ irlap_connect_request(self->irlap, self->daddr, NULL, 0);
+ break;
+ case LM_LAP_DISCONNECT_INDICATION:
+ IRDA_DEBUG(4, "%s(), Error LM_LAP_DISCONNECT_INDICATION\n", __FUNCTION__);
+
+ irlmp_next_lap_state(self, LAP_STANDBY);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown event %s\n", __FUNCTION__, irlmp_event[event]);
+ if (skb)
+ dev_kfree_skb(skb);
+ break;
+ }
+}
+
+/*
+ * Function irlmp_state_u_connect (event, skb, info)
+ *
+ * U_CONNECT, The layer above has tried to open an LSAP connection but
+ * since the IrLAP connection does not exist, we must first start an
+ * IrLAP connection. We are now waiting response from IrLAP.
+ * */
+static void irlmp_state_u_connect(struct lap_cb *self, IRLMP_EVENT event,
+ struct sk_buff *skb)
+{
+ struct lsap_cb *lsap;
+ struct lsap_cb *lsap_current;
+
+ IRDA_DEBUG(2, "%s(), event=%s\n", __FUNCTION__, irlmp_event[event]);
+
+ switch (event) {
+ case LM_LAP_CONNECT_INDICATION:
+ /* It's important to switch state first, to avoid IrLMP to
+ * think that the link is free since IrLMP may then start
+ * discovery before the connection is properly set up. DB.
+ */
+ irlmp_next_lap_state(self, LAP_ACTIVE);
+
+ /* Just accept connection TODO, this should be fixed */
+ irlap_connect_response(self->irlap, skb);
+
+ lsap = (struct lsap_cb *) hashbin_get_first(self->lsaps);
+ while (lsap != NULL) {
+ irlmp_do_lsap_event(lsap, LM_LAP_CONNECT_CONFIRM, NULL);
+ lsap = (struct lsap_cb*) hashbin_get_next(self->lsaps);
+ }
+ /* Note : by the time we get there (LAP retries and co),
+ * the lsaps may already have gone. This avoid getting stuck
+ * forever in LAP_ACTIVE state - Jean II */
+ if (HASHBIN_GET_SIZE(self->lsaps) == 0) {
+ IRDA_DEBUG(0, "%s() NO LSAPs !\n", __FUNCTION__);
+ irlmp_start_idle_timer(self, LM_IDLE_TIMEOUT);
+ }
+ break;
+ case LM_LAP_CONNECT_REQUEST:
+ /* Already trying to connect */
+ break;
+ case LM_LAP_CONNECT_CONFIRM:
+ /* For all lsap_ce E Associated do LS_Connect_confirm */
+ irlmp_next_lap_state(self, LAP_ACTIVE);
+
+ lsap = (struct lsap_cb *) hashbin_get_first(self->lsaps);
+ while (lsap != NULL) {
+ irlmp_do_lsap_event(lsap, LM_LAP_CONNECT_CONFIRM, NULL);
+ lsap = (struct lsap_cb*) hashbin_get_next(self->lsaps);
+ }
+ /* Note : by the time we get there (LAP retries and co),
+ * the lsaps may already have gone. This avoid getting stuck
+ * forever in LAP_ACTIVE state - Jean II */
+ if (HASHBIN_GET_SIZE(self->lsaps) == 0) {
+ IRDA_DEBUG(0, "%s() NO LSAPs !\n", __FUNCTION__);
+ irlmp_start_idle_timer(self, LM_IDLE_TIMEOUT);
+ }
+ break;
+ case LM_LAP_DISCONNECT_INDICATION:
+ IRDA_DEBUG(4, "%s(), LM_LAP_DISCONNECT_INDICATION\n", __FUNCTION__);
+ irlmp_next_lap_state(self, LAP_STANDBY);
+
+ /* Send disconnect event to all LSAPs using this link */
+ lsap = (struct lsap_cb *) hashbin_get_first( self->lsaps);
+ while (lsap != NULL ) {
+ ASSERT(lsap->magic == LMP_LSAP_MAGIC, return;);
+
+ lsap_current = lsap;
+
+ /* Be sure to stay one item ahead */
+ lsap = (struct lsap_cb *) hashbin_get_next(self->lsaps);
+ irlmp_do_lsap_event(lsap_current,
+ LM_LAP_DISCONNECT_INDICATION,
+ NULL);
+ }
+ break;
+ case LM_LAP_DISCONNECT_REQUEST:
+ IRDA_DEBUG(4, "%s(), LM_LAP_DISCONNECT_REQUEST\n", __FUNCTION__);
+
+ /* One of the LSAP did timeout or was closed, if it was
+ * the last one, try to get out of here - Jean II */
+ if (HASHBIN_GET_SIZE(self->lsaps) <= 1) {
+ irlap_disconnect_request(self->irlap);
+ }
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown event %s\n",
+ __FUNCTION__, irlmp_event[event]);
+ if (skb)
+ dev_kfree_skb(skb);
+ break;
+ }
+}
+
+/*
+ * Function irlmp_state_active (event, skb, info)
+ *
+ * ACTIVE, IrLAP connection is active
+ *
+ */
+static void irlmp_state_active(struct lap_cb *self, IRLMP_EVENT event,
+ struct sk_buff *skb)
+{
+ struct lsap_cb *lsap;
+ struct lsap_cb *lsap_current;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ switch (event) {
+ case LM_LAP_CONNECT_REQUEST:
+ IRDA_DEBUG(4, "%s(), LS_CONNECT_REQUEST\n", __FUNCTION__);
+
+ /*
+ * LAP connection allready active, just bounce back! Since we
+ * don't know which LSAP that tried to do this, we have to
+ * notify all LSAPs using this LAP, but that should be safe to
+ * do anyway.
+ */
+ lsap = (struct lsap_cb *) hashbin_get_first(self->lsaps);
+ while (lsap != NULL) {
+ irlmp_do_lsap_event(lsap, LM_LAP_CONNECT_CONFIRM, NULL);
+ lsap = (struct lsap_cb*) hashbin_get_next(self->lsaps);
+ }
+
+ /* Needed by connect indication */
+ lsap = (struct lsap_cb *) hashbin_get_first(irlmp->unconnected_lsaps);
+ while (lsap != NULL) {
+ lsap_current = lsap;
+
+ /* Be sure to stay one item ahead */
+ lsap = (struct lsap_cb*) hashbin_get_next(irlmp->unconnected_lsaps);
+ irlmp_do_lsap_event(lsap_current,
+ LM_LAP_CONNECT_CONFIRM, NULL);
+ }
+ /* Keep state */
+ break;
+ case LM_LAP_DISCONNECT_REQUEST:
+ /*
+ * Need to find out if we should close IrLAP or not. If there
+ * is only one LSAP connection left on this link, that LSAP
+ * must be the one that tries to close IrLAP. It will be
+ * removed later and moved to the list of unconnected LSAPs
+ */
+ if (HASHBIN_GET_SIZE(self->lsaps) > 0) {
+ /* Timer value is checked in irsysctl - Jean II */
+ irlmp_start_idle_timer(self, sysctl_lap_keepalive_time * HZ / 1000);
+ } else {
+ /* No more connections, so close IrLAP */
+
+ /* We don't want to change state just yet, because
+ * we want to reflect accurately the real state of
+ * the LAP, not the state we wish it was in,
+ * so that we don't loose LM_LAP_CONNECT_REQUEST.
+ * In some cases, IrLAP won't close the LAP
+ * immediately. For example, it might still be
+ * retrying packets or waiting for the pf bit.
+ * As the LAP always send a DISCONNECT_INDICATION
+ * in PCLOSE or SCLOSE, just change state on that.
+ * Jean II */
+ irlap_disconnect_request(self->irlap);
+ }
+ break;
+ case LM_LAP_IDLE_TIMEOUT:
+ if (HASHBIN_GET_SIZE(self->lsaps) == 0) {
+ /* Same reasoning as above - keep state */
+ irlap_disconnect_request(self->irlap);
+ }
+ break;
+ case LM_LAP_DISCONNECT_INDICATION:
+ irlmp_next_lap_state(self, LAP_STANDBY);
+
+ /* In some case, at this point our side has already closed
+ * all lsaps, and we are waiting for the idle_timer to
+ * expire. If another device reconnect immediately, the
+ * idle timer will expire in the midle of the connection
+ * initialisation, screwing up things a lot...
+ * Therefore, we must stop the timer... */
+ irlmp_stop_idle_timer(self);
+
+ /*
+ * Inform all connected LSAP's using this link
+ */
+ lsap = (struct lsap_cb *) hashbin_get_first(self->lsaps);
+ while (lsap != NULL ) {
+ ASSERT(lsap->magic == LMP_LSAP_MAGIC, return;);
+
+ lsap_current = lsap;
+
+ /* Be sure to stay one item ahead */
+ lsap = (struct lsap_cb *) hashbin_get_next(self->lsaps);
+ irlmp_do_lsap_event(lsap_current,
+ LM_LAP_DISCONNECT_INDICATION,
+ NULL);
+ }
+
+ /* Force an expiry of the discovery log.
+ * Now that the LAP is free, the system may attempt to
+ * connect to another device. Unfortunately, our entries
+ * are stale. There is a small window (<3s) before the
+ * normal discovery will run and where irlmp_connect_request()
+ * can get the wrong info, so make sure things get
+ * cleaned *NOW* ;-) - Jean II */
+ irlmp_do_expiry();
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown event %s\n",
+ __FUNCTION__, irlmp_event[event]);
+ if (skb)
+ dev_kfree_skb(skb);
+ break;
+ }
+}
+
+/*********************************************************************
+ *
+ * LSAP connection control states
+ *
+ ********************************************************************/
+
+/*
+ * Function irlmp_state_disconnected (event, skb, info)
+ *
+ * DISCONNECTED
+ *
+ */
+static int irlmp_state_disconnected(struct lsap_cb *self, IRLMP_EVENT event,
+ struct sk_buff *skb)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return -1;);
+
+ switch (event) {
+#ifdef CONFIG_IRDA_ULTRA
+ case LM_UDATA_INDICATION:
+ irlmp_connless_data_indication(self, skb);
+ break;
+#endif /* CONFIG_IRDA_ULTRA */
+ case LM_CONNECT_REQUEST:
+ IRDA_DEBUG(4, "%s(), LM_CONNECT_REQUEST\n", __FUNCTION__);
+
+ if (self->conn_skb) {
+ WARNING("%s(), busy with another request!\n", __FUNCTION__);
+ return -EBUSY;
+ }
+ self->conn_skb = skb;
+
+ irlmp_next_lsap_state(self, LSAP_SETUP_PEND);
+
+ /* Start watchdog timer (5 secs for now) */
+ irlmp_start_watchdog_timer(self, 5*HZ);
+
+ irlmp_do_lap_event(self->lap, LM_LAP_CONNECT_REQUEST, NULL);
+ break;
+ case LM_CONNECT_INDICATION:
+ if (self->conn_skb) {
+ WARNING("%s(), busy with another request!\n", __FUNCTION__);
+ return -EBUSY;
+ }
+ self->conn_skb = skb;
+
+ irlmp_next_lsap_state(self, LSAP_CONNECT_PEND);
+
+ /* Start watchdog timer
+ * This is not mentionned in the spec, but there is a rare
+ * race condition that can get the socket stuck.
+ * If we receive this event while our LAP is closing down,
+ * the LM_LAP_CONNECT_REQUEST get lost and we get stuck in
+ * CONNECT_PEND state forever.
+ * The other cause of getting stuck down there is if the
+ * higher layer never reply to the CONNECT_INDICATION.
+ * Anyway, it make sense to make sure that we always have
+ * a backup plan. 1 second is plenty (should be immediate).
+ * Jean II */
+ irlmp_start_watchdog_timer(self, 1*HZ);
+
+ irlmp_do_lap_event(self->lap, LM_LAP_CONNECT_REQUEST, NULL);
+ break;
+ default:
+ IRDA_DEBUG(1, "%s(), Unknown event %s on LSAP %#02x\n",
+ __FUNCTION__, irlmp_event[event], self->slsap_sel);
+ if (skb)
+ dev_kfree_skb(skb);
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlmp_state_connect (self, event, skb)
+ *
+ * CONNECT
+ *
+ */
+static int irlmp_state_connect(struct lsap_cb *self, IRLMP_EVENT event,
+ struct sk_buff *skb)
+{
+ struct lsap_cb *lsap;
+ int ret = 0;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return -1;);
+
+ switch (event) {
+ case LM_CONNECT_RESPONSE:
+ /*
+ * Bind this LSAP to the IrLAP link where the connect was
+ * received
+ */
+ lsap = hashbin_remove(irlmp->unconnected_lsaps, (int) self,
+ NULL);
+
+ ASSERT(lsap == self, return -1;);
+ ASSERT(self->lap != NULL, return -1;);
+ ASSERT(self->lap->lsaps != NULL, return -1;);
+
+ hashbin_insert(self->lap->lsaps, (irda_queue_t *) self, (int) self,
+ NULL);
+
+ irlmp_send_lcf_pdu(self->lap, self->dlsap_sel,
+ self->slsap_sel, CONNECT_CNF, skb);
+
+ del_timer(&self->watchdog_timer);
+
+ irlmp_next_lsap_state(self, LSAP_DATA_TRANSFER_READY);
+ break;
+ case LM_WATCHDOG_TIMEOUT:
+ /* May happen, who knows...
+ * Jean II */
+ IRDA_DEBUG(0, "%s() WATCHDOG_TIMEOUT!\n", __FUNCTION__);
+
+ /* Disconnect, get out... - Jean II */
+ self->dlsap_sel = LSAP_ANY;
+ irlmp_next_lsap_state(self, LSAP_DISCONNECTED);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown event %s on LSAP %#02x\n",
+ __FUNCTION__, irlmp_event[event], self->slsap_sel);
+ if (skb)
+ dev_kfree_skb(skb);
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlmp_state_connect_pend (event, skb, info)
+ *
+ * CONNECT_PEND
+ *
+ */
+static int irlmp_state_connect_pend(struct lsap_cb *self, IRLMP_EVENT event,
+ struct sk_buff *skb)
+{
+ int ret = 0;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return -1;);
+
+ switch (event) {
+ case LM_CONNECT_REQUEST:
+ /* Keep state */
+ break;
+ case LM_CONNECT_RESPONSE:
+ IRDA_DEBUG(0, "%s(), LM_CONNECT_RESPONSE, "
+ "no indication issued yet\n", __FUNCTION__);
+ /* Keep state */
+ break;
+ case LM_DISCONNECT_REQUEST:
+ IRDA_DEBUG(0, "%s(), LM_DISCONNECT_REQUEST, "
+ "not yet bound to IrLAP connection\n", __FUNCTION__);
+ /* Keep state */
+ break;
+ case LM_LAP_CONNECT_CONFIRM:
+ IRDA_DEBUG(4, "%s(), LS_CONNECT_CONFIRM\n", __FUNCTION__);
+ irlmp_next_lsap_state(self, LSAP_CONNECT);
+
+ skb = self->conn_skb;
+ self->conn_skb = NULL;
+
+ irlmp_connect_indication(self, skb);
+ break;
+ case LM_WATCHDOG_TIMEOUT:
+ /* Will happen in some rare cases because of a race condition.
+ * Just make sure we don't stay there forever...
+ * Jean II */
+ IRDA_DEBUG(0, "%s() WATCHDOG_TIMEOUT!\n", __FUNCTION__);
+
+ /* Go back to disconnected mode, keep the socket waiting */
+ self->dlsap_sel = LSAP_ANY;
+ if(self->conn_skb)
+ dev_kfree_skb(self->conn_skb);
+ self->conn_skb = NULL;
+ irlmp_next_lsap_state(self, LSAP_DISCONNECTED);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown event %s on LSAP %#02x\n",
+ __FUNCTION__, irlmp_event[event], self->slsap_sel);
+ if (skb)
+ dev_kfree_skb(skb);
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlmp_state_dtr (self, event, skb)
+ *
+ * DATA_TRANSFER_READY
+ *
+ */
+static int irlmp_state_dtr(struct lsap_cb *self, IRLMP_EVENT event,
+ struct sk_buff *skb)
+{
+ LM_REASON reason;
+ int ret = 0;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return -1;);
+ ASSERT(self->lap != NULL, return -1;);
+
+ switch (event) {
+ case LM_DATA_REQUEST: /* Optimize for the common case */
+ irlmp_send_data_pdu(self->lap, self->dlsap_sel,
+ self->slsap_sel, FALSE, skb);
+ break;
+ case LM_DATA_INDICATION: /* Optimize for the common case */
+ irlmp_data_indication(self, skb);
+ break;
+ case LM_UDATA_REQUEST:
+ ASSERT(skb != NULL, return -1;);
+ irlmp_send_data_pdu(self->lap, self->dlsap_sel,
+ self->slsap_sel, TRUE, skb);
+ break;
+ case LM_UDATA_INDICATION:
+ irlmp_udata_indication(self, skb);
+ break;
+ case LM_CONNECT_REQUEST:
+ IRDA_DEBUG(0, "%s(), LM_CONNECT_REQUEST, "
+ "error, LSAP already connected\n", __FUNCTION__);
+ /* Keep state */
+ break;
+ case LM_CONNECT_RESPONSE:
+ IRDA_DEBUG(0, "%s(), LM_CONNECT_RESPONSE, "
+ "error, LSAP allready connected\n", __FUNCTION__);
+ /* Keep state */
+ break;
+ case LM_DISCONNECT_REQUEST:
+ irlmp_send_lcf_pdu(self->lap, self->dlsap_sel, self->slsap_sel,
+ DISCONNECT, skb);
+ irlmp_next_lsap_state(self, LSAP_DISCONNECTED);
+
+ /* Try to close the LAP connection if its still there */
+ if (self->lap) {
+ IRDA_DEBUG(4, "%s(), trying to close IrLAP\n", __FUNCTION__);
+ irlmp_do_lap_event(self->lap,
+ LM_LAP_DISCONNECT_REQUEST,
+ NULL);
+ }
+ break;
+ case LM_LAP_DISCONNECT_INDICATION:
+ irlmp_next_lsap_state(self, LSAP_DISCONNECTED);
+
+ reason = irlmp_convert_lap_reason(self->lap->reason);
+
+ irlmp_disconnect_indication(self, reason, NULL);
+ break;
+ case LM_DISCONNECT_INDICATION:
+ irlmp_next_lsap_state(self, LSAP_DISCONNECTED);
+
+ ASSERT(self->lap != NULL, return -1;);
+ ASSERT(self->lap->magic == LMP_LAP_MAGIC, return -1;);
+
+ ASSERT(skb != NULL, return -1;);
+ ASSERT(skb->len > 3, return -1;);
+ reason = skb->data[3];
+
+ /* Try to close the LAP connection */
+ IRDA_DEBUG(4, "%ss(), trying to close IrLAP\n", __FUNCTION__);
+ irlmp_do_lap_event(self->lap, LM_LAP_DISCONNECT_REQUEST, NULL);
+
+ irlmp_disconnect_indication(self, reason, skb);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown event %s on LSAP %#02x\n",
+ __FUNCTION__, irlmp_event[event], self->slsap_sel);
+ if (skb)
+ dev_kfree_skb(skb);
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlmp_state_setup (event, skb, info)
+ *
+ * SETUP, Station Control has set up the underlying IrLAP connection.
+ * An LSAP connection request has been transmitted to the peer
+ * LSAP-Connection Control FSM and we are awaiting reply.
+ */
+static int irlmp_state_setup(struct lsap_cb *self, IRLMP_EVENT event,
+ struct sk_buff *skb)
+{
+ LM_REASON reason;
+ int ret = 0;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LMP_LSAP_MAGIC, return -1;);
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ switch (event) {
+ case LM_CONNECT_CONFIRM:
+ irlmp_next_lsap_state(self, LSAP_DATA_TRANSFER_READY);
+
+ del_timer(&self->watchdog_timer);
+
+ irlmp_connect_confirm(self, skb);
+ break;
+ case LM_DISCONNECT_INDICATION:
+ irlmp_next_lsap_state(self, LSAP_DISCONNECTED);
+
+ ASSERT(self->lap != NULL, return -1;);
+ ASSERT(self->lap->magic == LMP_LAP_MAGIC, return -1;);
+
+ ASSERT(skb != NULL, return -1;);
+ ASSERT(skb->len > 3, return -1;);
+ reason = skb->data[3];
+
+ /* Try to close the LAP connection */
+ IRDA_DEBUG(4, "%s(), trying to close IrLAP\n", __FUNCTION__);
+ irlmp_do_lap_event(self->lap, LM_LAP_DISCONNECT_REQUEST, NULL);
+
+ irlmp_disconnect_indication(self, reason, skb);
+ break;
+ case LM_LAP_DISCONNECT_INDICATION:
+ irlmp_next_lsap_state(self, LSAP_DISCONNECTED);
+
+ del_timer(&self->watchdog_timer);
+
+ ASSERT(self->lap != NULL, return -1;);
+ ASSERT(self->lap->magic == LMP_LAP_MAGIC, return -1;);
+
+ reason = irlmp_convert_lap_reason(self->lap->reason);
+
+ irlmp_disconnect_indication(self, reason, skb);
+ break;
+ case LM_WATCHDOG_TIMEOUT:
+ IRDA_DEBUG(0, "%s() WATCHDOG_TIMEOUT!\n", __FUNCTION__);
+
+ ASSERT(self->lap != NULL, return -1;);
+ irlmp_do_lap_event(self->lap, LM_LAP_DISCONNECT_REQUEST, NULL);
+ irlmp_next_lsap_state(self, LSAP_DISCONNECTED);
+
+ irlmp_disconnect_indication(self, LM_CONNECT_FAILURE, NULL);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown event %s on LSAP %#02x\n",
+ __FUNCTION__, irlmp_event[event], self->slsap_sel);
+ if (skb)
+ dev_kfree_skb(skb);
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Function irlmp_state_setup_pend (event, skb, info)
+ *
+ * SETUP_PEND, An LM_CONNECT_REQUEST has been received from the service
+ * user to set up an LSAP connection. A request has been sent to the
+ * LAP FSM to set up the underlying IrLAP connection, and we
+ * are awaiting confirm.
+ */
+static int irlmp_state_setup_pend(struct lsap_cb *self, IRLMP_EVENT event,
+ struct sk_buff *skb)
+{
+ LM_REASON reason;
+ int ret = 0;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(irlmp != NULL, return -1;);
+
+ switch (event) {
+ case LM_LAP_CONNECT_CONFIRM:
+ ASSERT(self->conn_skb != NULL, return -1;);
+
+ skb = self->conn_skb;
+ self->conn_skb = NULL;
+
+ irlmp_send_lcf_pdu(self->lap, self->dlsap_sel,
+ self->slsap_sel, CONNECT_CMD, skb);
+
+ irlmp_next_lsap_state(self, LSAP_SETUP);
+ break;
+ case LM_WATCHDOG_TIMEOUT:
+ IRDA_DEBUG(0, "%s() : WATCHDOG_TIMEOUT !\n", __FUNCTION__);
+
+ ASSERT(self->lap != NULL, return -1;);
+ irlmp_do_lap_event(self->lap, LM_LAP_DISCONNECT_REQUEST, NULL);
+ irlmp_next_lsap_state(self, LSAP_DISCONNECTED);
+
+ irlmp_disconnect_indication(self, LM_CONNECT_FAILURE, NULL);
+ break;
+ case LM_LAP_DISCONNECT_INDICATION: /* LS_Disconnect.indication */
+ del_timer( &self->watchdog_timer);
+
+ irlmp_next_lsap_state(self, LSAP_DISCONNECTED);
+
+ reason = irlmp_convert_lap_reason(self->lap->reason);
+
+ irlmp_disconnect_indication(self, reason, NULL);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown event %s on LSAP %#02x\n",
+ __FUNCTION__, irlmp_event[event], self->slsap_sel);
+ if (skb)
+ dev_kfree_skb(skb);
+ break;
+ }
+ return ret;
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/irlmp_frame.c b/uClinux-2.4.31-uc0/net/irda/irlmp_frame.c
new file mode 100644
index 0000000..ea56017
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irlmp_frame.c
@@ -0,0 +1,477 @@
+/*********************************************************************
+ *
+ * Filename: irlmp_frame.c
+ * Version: 0.9
+ * Description: IrLMP frame implementation
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Tue Aug 19 02:09:59 1997
+ * Modified at: Mon Dec 13 13:41:12 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1998-1999 Dag Brattli <dagb@cs.uit.no>
+ * All Rights Reserved.
+ * Copyright (c) 2000-2001 Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/skbuff.h>
+#include <linux/kernel.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irlap.h>
+#include <net/irda/timer.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/irlmp_frame.h>
+#include <net/irda/discovery.h>
+
+static struct lsap_cb *irlmp_find_lsap(struct lap_cb *self, __u8 dlsap,
+ __u8 slsap, int status, hashbin_t *);
+
+inline void irlmp_send_data_pdu(struct lap_cb *self, __u8 dlsap, __u8 slsap,
+ int expedited, struct sk_buff *skb)
+{
+ skb->data[0] = dlsap;
+ skb->data[1] = slsap;
+
+ if (expedited) {
+ IRDA_DEBUG(4, "%s(), sending expedited data\n", __FUNCTION__);
+ irlap_data_request(self->irlap, skb, TRUE);
+ } else
+ irlap_data_request(self->irlap, skb, FALSE);
+}
+
+/*
+ * Function irlmp_send_lcf_pdu (dlsap, slsap, opcode,skb)
+ *
+ * Send Link Control Frame to IrLAP
+ */
+void irlmp_send_lcf_pdu(struct lap_cb *self, __u8 dlsap, __u8 slsap,
+ __u8 opcode, struct sk_buff *skb)
+{
+ __u8 *frame;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LAP_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+
+ frame = skb->data;
+
+ frame[0] = dlsap | CONTROL_BIT;
+ frame[1] = slsap;
+
+ frame[2] = opcode;
+
+ if (opcode == DISCONNECT)
+ frame[3] = 0x01; /* Service user request */
+ else
+ frame[3] = 0x00; /* rsvd */
+
+ irlap_data_request(self->irlap, skb, FALSE);
+}
+
+/*
+ * Function irlmp_input (skb)
+ *
+ * Used by IrLAP to pass received data frames to IrLMP layer
+ *
+ */
+void irlmp_link_data_indication(struct lap_cb *self, struct sk_buff *skb,
+ int unreliable)
+{
+ struct lsap_cb *lsap;
+ __u8 slsap_sel; /* Source (this) LSAP address */
+ __u8 dlsap_sel; /* Destination LSAP address */
+ __u8 *fp;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LAP_MAGIC, return;);
+ ASSERT(skb->len > 2, return;);
+
+ fp = skb->data;
+
+ /*
+ * The next statements may be confusing, but we do this so that
+ * destination LSAP of received frame is source LSAP in our view
+ */
+ slsap_sel = fp[0] & LSAP_MASK;
+ dlsap_sel = fp[1];
+
+ /*
+ * Check if this is an incoming connection, since we must deal with
+ * it in a different way than other established connections.
+ */
+ if ((fp[0] & CONTROL_BIT) && (fp[2] == CONNECT_CMD)) {
+ IRDA_DEBUG(3, "%s(), incoming connection, "
+ "source LSAP=%d, dest LSAP=%d\n",
+ __FUNCTION__, slsap_sel, dlsap_sel);
+
+ /* Try to find LSAP among the unconnected LSAPs */
+ lsap = irlmp_find_lsap(self, dlsap_sel, slsap_sel, CONNECT_CMD,
+ irlmp->unconnected_lsaps);
+
+ /* Maybe LSAP was already connected, so try one more time */
+ if (!lsap) {
+ IRDA_DEBUG(1, "%s(), incoming connection for LSAP already connected\n", __FUNCTION__);
+ lsap = irlmp_find_lsap(self, dlsap_sel, slsap_sel, 0,
+ self->lsaps);
+ }
+ } else
+ lsap = irlmp_find_lsap(self, dlsap_sel, slsap_sel, 0,
+ self->lsaps);
+
+ if (lsap == NULL) {
+ IRDA_DEBUG(2, "IrLMP, Sorry, no LSAP for received frame!\n");
+ IRDA_DEBUG(2, "%s(), slsap_sel = %02x, dlsap_sel = %02x\n", __FUNCTION__, slsap_sel,
+ dlsap_sel);
+ if (fp[0] & CONTROL_BIT) {
+ IRDA_DEBUG(2, "%s(), received control frame %02x\n", __FUNCTION__, fp[2]);
+ } else {
+ IRDA_DEBUG(2, "%s(), received data frame\n", __FUNCTION__);
+ }
+ dev_kfree_skb(skb);
+ return;
+ }
+
+ /*
+ * Check if we received a control frame?
+ */
+ if (fp[0] & CONTROL_BIT) {
+ switch (fp[2]) {
+ case CONNECT_CMD:
+ lsap->lap = self;
+ irlmp_do_lsap_event(lsap, LM_CONNECT_INDICATION, skb);
+ break;
+ case CONNECT_CNF:
+ irlmp_do_lsap_event(lsap, LM_CONNECT_CONFIRM, skb);
+ break;
+ case DISCONNECT:
+ IRDA_DEBUG(4, "%s(), Disconnect indication!\n", __FUNCTION__);
+ irlmp_do_lsap_event(lsap, LM_DISCONNECT_INDICATION,
+ skb);
+ break;
+ case ACCESSMODE_CMD:
+ IRDA_DEBUG(0, "Access mode cmd not implemented!\n");
+ dev_kfree_skb(skb);
+ break;
+ case ACCESSMODE_CNF:
+ IRDA_DEBUG(0, "Access mode cnf not implemented!\n");
+ dev_kfree_skb(skb);
+ break;
+ default:
+ IRDA_DEBUG(0, "%s(), Unknown control frame %02x\n", __FUNCTION__, fp[2]);
+ dev_kfree_skb(skb);
+ break;
+ }
+ } else if (unreliable) {
+ /* Optimize and bypass the state machine if possible */
+ if (lsap->lsap_state == LSAP_DATA_TRANSFER_READY)
+ irlmp_udata_indication(lsap, skb);
+ else
+ irlmp_do_lsap_event(lsap, LM_UDATA_INDICATION, skb);
+ } else {
+ /* Optimize and bypass the state machine if possible */
+ if (lsap->lsap_state == LSAP_DATA_TRANSFER_READY)
+ irlmp_data_indication(lsap, skb);
+ else
+ irlmp_do_lsap_event(lsap, LM_DATA_INDICATION, skb);
+ }
+}
+
+/*
+ * Function irlmp_link_unitdata_indication (self, skb)
+ *
+ *
+ *
+ */
+#ifdef CONFIG_IRDA_ULTRA
+void irlmp_link_unitdata_indication(struct lap_cb *self, struct sk_buff *skb)
+{
+ struct lsap_cb *lsap;
+ __u8 slsap_sel; /* Source (this) LSAP address */
+ __u8 dlsap_sel; /* Destination LSAP address */
+ __u8 pid; /* Protocol identifier */
+ __u8 *fp;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LAP_MAGIC, return;);
+ ASSERT(skb->len > 2, return;);
+
+ fp = skb->data;
+
+ /*
+ * The next statements may be confusing, but we do this so that
+ * destination LSAP of received frame is source LSAP in our view
+ */
+ slsap_sel = fp[0] & LSAP_MASK;
+ dlsap_sel = fp[1];
+ pid = fp[2];
+
+ if (pid & 0x80) {
+ IRDA_DEBUG(0, "%s(), extension in PID not supp!\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+
+ return;
+ }
+
+ /* Check if frame is addressed to the connectionless LSAP */
+ if ((slsap_sel != LSAP_CONNLESS) || (dlsap_sel != LSAP_CONNLESS)) {
+ IRDA_DEBUG(0, "%s(), dropping frame!\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+
+ return;
+ }
+
+ lsap = (struct lsap_cb *) hashbin_get_first(irlmp->unconnected_lsaps);
+ while (lsap != NULL) {
+ /*
+ * Check if source LSAP and dest LSAP selectors and PID match.
+ */
+ if ((lsap->slsap_sel == slsap_sel) &&
+ (lsap->dlsap_sel == dlsap_sel) &&
+ (lsap->pid == pid))
+ {
+ break;
+ }
+ lsap = (struct lsap_cb *) hashbin_get_next(irlmp->unconnected_lsaps);
+ }
+ if (lsap)
+ irlmp_connless_data_indication(lsap, skb);
+ else {
+ IRDA_DEBUG(0, "%s(), found no matching LSAP!\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+ }
+}
+#endif /* CONFIG_IRDA_ULTRA */
+
+/*
+ * Function irlmp_link_disconnect_indication (reason, userdata)
+ *
+ * IrLAP has disconnected
+ *
+ */
+void irlmp_link_disconnect_indication(struct lap_cb *lap,
+ struct irlap_cb *irlap,
+ LAP_REASON reason,
+ struct sk_buff *userdata)
+{
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(lap != NULL, return;);
+ ASSERT(lap->magic == LMP_LAP_MAGIC, return;);
+
+ lap->reason = reason;
+ lap->daddr = DEV_ADDR_ANY;
+
+ /* FIXME: must do something with the userdata if any */
+ if (userdata)
+ dev_kfree_skb(userdata);
+
+ /*
+ * Inform station state machine
+ */
+ irlmp_do_lap_event(lap, LM_LAP_DISCONNECT_INDICATION, NULL);
+}
+
+/*
+ * Function irlmp_link_connect_indication (qos)
+ *
+ * Incoming LAP connection!
+ *
+ */
+void irlmp_link_connect_indication(struct lap_cb *self, __u32 saddr,
+ __u32 daddr, struct qos_info *qos,
+ struct sk_buff *skb)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ /* Copy QoS settings for this session */
+ self->qos = qos;
+
+ /* Update destination device address */
+ self->daddr = daddr;
+ ASSERT(self->saddr == saddr, return;);
+
+ irlmp_do_lap_event(self, LM_LAP_CONNECT_INDICATION, skb);
+}
+
+/*
+ * Function irlmp_link_connect_confirm (qos)
+ *
+ * LAP connection confirmed!
+ *
+ */
+void irlmp_link_connect_confirm(struct lap_cb *self, struct qos_info *qos,
+ struct sk_buff *userdata)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LAP_MAGIC, return;);
+ ASSERT(qos != NULL, return;);
+
+ /* Don't need use the userdata for now */
+ if (userdata)
+ dev_kfree_skb(userdata);
+
+ /* Copy QoS settings for this session */
+ self->qos = qos;
+
+ irlmp_do_lap_event(self, LM_LAP_CONNECT_CONFIRM, NULL);
+}
+
+/*
+ * Function irlmp_link_discovery_indication (self, log)
+ *
+ * Device is discovering us
+ *
+ * It's not an answer to our own discoveries, just another device trying
+ * to perform discovery, but we don't want to miss the opportunity
+ * to exploit this information, because :
+ * o We may not actively perform discovery (just passive discovery)
+ * o This type of discovery is much more reliable. In some cases, it
+ * seem that less than 50% of our discoveries get an answer, while
+ * we always get ~100% of these.
+ * o Make faster discovery, statistically divide time of discovery
+ * events by 2 (important for the latency aspect and user feel)
+ * o Even is we do active discovery, the other node might not
+ * answer our discoveries (ex: Palm). The Palm will just perform
+ * one active discovery and connect directly to us.
+ *
+ * However, when both devices discover each other, they might attempt to
+ * connect to each other following the discovery event, and it would create
+ * collisions on the medium (SNRM battle).
+ * The "fix" for that is to disable all connection requests in IrLAP
+ * for 100ms after a discovery indication by setting the media_busy flag.
+ * Previously, we used to postpone the event which was quite ugly. Now
+ * that IrLAP takes care of this problem, just pass the event up...
+ *
+ * Jean II
+ */
+void irlmp_link_discovery_indication(struct lap_cb *self,
+ discovery_t *discovery)
+{
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LAP_MAGIC, return;);
+
+ irlmp_add_discovery(irlmp->cachelog, discovery);
+
+ /* Just handle it the same way as a discovery confirm,
+ * bypass the LM_LAP state machine (see below) */
+ irlmp_discovery_confirm(irlmp->cachelog, DISCOVERY_PASSIVE);
+}
+
+/*
+ * Function irlmp_link_discovery_confirm (self, log)
+ *
+ * Called by IrLAP with a list of discoveries after the discovery
+ * request has been carried out. A NULL log is received if IrLAP
+ * was unable to carry out the discovery request
+ *
+ */
+void irlmp_link_discovery_confirm(struct lap_cb *self, hashbin_t *log)
+{
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LMP_LAP_MAGIC, return;);
+
+ irlmp_add_discovery_log(irlmp->cachelog, log);
+
+ /* Propagate event to various LSAPs registered for it.
+ * We bypass the LM_LAP state machine because
+ * 1) We do it regardless of the LM_LAP state
+ * 2) It doesn't affect the LM_LAP state
+ * 3) Faster, slimer, simpler, ...
+ * Jean II */
+ irlmp_discovery_confirm(irlmp->cachelog, DISCOVERY_ACTIVE);
+}
+
+#ifdef CONFIG_IRDA_CACHE_LAST_LSAP
+inline void irlmp_update_cache(struct lsap_cb *self)
+{
+ /* Update cache entry */
+ irlmp->cache.dlsap_sel = self->dlsap_sel;
+ irlmp->cache.slsap_sel = self->slsap_sel;
+ irlmp->cache.lsap = self;
+ irlmp->cache.valid = TRUE;
+}
+#endif
+
+/*
+ * Function irlmp_find_handle (self, dlsap_sel, slsap_sel, status, queue)
+ *
+ * Find handle assosiated with destination and source LSAP
+ *
+ */
+static struct lsap_cb *irlmp_find_lsap(struct lap_cb *self, __u8 dlsap_sel,
+ __u8 slsap_sel, int status,
+ hashbin_t *queue)
+{
+ struct lsap_cb *lsap;
+
+ /*
+ * Optimize for the common case. We assume that the last frame
+ * received is in the same connection as the last one, so check in
+ * cache first to avoid the linear search
+ */
+#ifdef CONFIG_IRDA_CACHE_LAST_LSAP
+ if ((irlmp->cache.valid) &&
+ (irlmp->cache.slsap_sel == slsap_sel) &&
+ (irlmp->cache.dlsap_sel == dlsap_sel))
+ {
+ return (irlmp->cache.lsap);
+ }
+#endif
+ lsap = (struct lsap_cb *) hashbin_get_first(queue);
+ while (lsap != NULL) {
+ /*
+ * If this is an incoming connection, then the destination
+ * LSAP selector may have been specified as LM_ANY so that
+ * any client can connect. In that case we only need to check
+ * if the source LSAP (in our view!) match!
+ */
+ if ((status == CONNECT_CMD) &&
+ (lsap->slsap_sel == slsap_sel) &&
+ (lsap->dlsap_sel == LSAP_ANY))
+ {
+ lsap->dlsap_sel = dlsap_sel;
+
+#ifdef CONFIG_IRDA_CACHE_LAST_LSAP
+ irlmp_update_cache(lsap);
+#endif
+ return lsap;
+ }
+ /*
+ * Check if source LSAP and dest LSAP selectors match.
+ */
+ if ((lsap->slsap_sel == slsap_sel) &&
+ (lsap->dlsap_sel == dlsap_sel))
+ {
+#ifdef CONFIG_IRDA_CACHE_LAST_LSAP
+ irlmp_update_cache(lsap);
+#endif
+ return lsap;
+ }
+ lsap = (struct lsap_cb *) hashbin_get_next(queue);
+ }
+
+ /* Sorry not found! */
+ return NULL;
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/irnet/Config.in b/uClinux-2.4.31-uc0/net/irda/irnet/Config.in
new file mode 100644
index 0000000..ac6f9c4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irnet/Config.in
@@ -0,0 +1,4 @@
+if [ "$CONFIG_NETDEVICES" != "n" ]; then
+ dep_tristate ' IrNET protocol' CONFIG_IRNET $CONFIG_IRDA $CONFIG_PPP
+fi
+
diff --git a/uClinux-2.4.31-uc0/net/irda/irnet/Makefile b/uClinux-2.4.31-uc0/net/irda/irnet/Makefile
new file mode 100644
index 0000000..ea4c3d3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irnet/Makefile
@@ -0,0 +1,16 @@
+#
+# Makefile for the Linux IrDA IrNET protocol layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+MOD_LIST_NAME := IRDA_MODULES
+O_TARGET := irnet.o
+
+obj-y := irnet_ppp.o irnet_irda.o
+obj-m := $(O_TARGET)
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/irda/irnet/irnet.h b/uClinux-2.4.31-uc0/net/irda/irnet/irnet.h
new file mode 100644
index 0000000..0130e79
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irnet/irnet.h
@@ -0,0 +1,521 @@
+/*
+ * IrNET protocol module : Synchronous PPP over an IrDA socket.
+ *
+ * Jean II - HPL `00 - <jt@hpl.hp.com>
+ *
+ * This file contains definitions and declarations global to the IrNET module,
+ * all grouped in one place...
+ * This file is a *private* header, so other modules don't want to know
+ * what's in there...
+ *
+ * Note : as most part of the Linux kernel, this module is available
+ * under the GNU General Public License (GPL).
+ */
+
+#ifndef IRNET_H
+#define IRNET_H
+
+/************************** DOCUMENTATION ***************************/
+/*
+ * What is IrNET
+ * -------------
+ * IrNET is a protocol allowing to carry TCP/IP traffic between two
+ * IrDA peers in an efficient fashion. It is a thin layer, passing PPP
+ * packets to IrTTP and vice versa. It uses PPP in synchronous mode,
+ * because IrTTP offer a reliable sequenced packet service (as opposed
+ * to a byte stream). In fact, you could see IrNET as carrying TCP/IP
+ * in a IrDA socket, using PPP to provide the glue.
+ *
+ * The main difference with traditional PPP over IrCOMM is that we
+ * avoid the framing and serial emulation which are a performance
+ * bottleneck. It also allows multipoint communications in a sensible
+ * fashion.
+ *
+ * The main difference with IrLAN is that we use PPP for the link
+ * management, which is more standard, interoperable and flexible than
+ * the IrLAN protocol. For example, PPP adds authentication,
+ * encryption, compression, header compression and automated routing
+ * setup. And, as IrNET let PPP do the hard work, the implementation
+ * is much simpler than IrLAN.
+ *
+ * The Linux implementation
+ * ------------------------
+ * IrNET is written on top of the Linux-IrDA stack, and interface with
+ * the generic Linux PPP driver. Because IrNET depend on recent
+ * changes of the PPP driver interface, IrNET will work only with very
+ * recent kernel (2.3.99-pre6 and up).
+ *
+ * The present implementation offer the following features :
+ * o simple user interface using pppd
+ * o efficient implementation (interface directly to PPP and IrTTP)
+ * o addressing (you can specify the name of the IrNET recipient)
+ * o multipoint operation (limited by IrLAP specification)
+ * o information in /proc/net/irda/irnet
+ * o IrNET events on /dev/irnet (for user space daemon)
+ * o IrNET daemon (irnetd) to automatically handle incoming requests
+ * o Windows 2000 compatibility (tested, but need more work)
+ * Currently missing :
+ * o Lot's of testing (that's your job)
+ * o Connection retries (may be too hard to do)
+ * o Check pppd persist mode
+ * o User space daemon (to automatically handle incoming requests)
+ *
+ * The setup is not currently the most easy, but this should get much
+ * better when everything will get integrated...
+ *
+ * Acknowledgements
+ * ----------------
+ * This module is based on :
+ * o The PPP driver (ppp_synctty/ppp_generic) by Paul Mackerras
+ * o The IrLAN protocol (irlan_common/XXX) by Dag Brattli
+ * o The IrSock interface (af_irda) by Dag Brattli
+ * o Some other bits from the kernel and my drivers...
+ * Infinite thanks to those brave souls for providing the infrastructure
+ * upon which IrNET is built.
+ *
+ * Thanks to all my collegues in HP for helping me. In particular,
+ * thanks to Salil Pradhan and Bill Serra for W2k testing...
+ * Thanks to Luiz Magalhaes for irnetd and much testing...
+ *
+ * Thanks to Alan Cox for answering lot's of my stupid questions, and
+ * to Paul Mackerras answering my questions on how to best integrate
+ * IrNET and pppd.
+ *
+ * Jean II
+ *
+ * Note on some implementations choices...
+ * ------------------------------------
+ * 1) Direct interface vs tty/socket
+ * I could have used a tty interface to hook to ppp and use the full
+ * socket API to connect to IrDA. The code would have been easier to
+ * maintain, and maybe the code would have been smaller...
+ * Instead, we hook directly to ppp_generic and to IrTTP, which make
+ * things more complicated...
+ *
+ * The first reason is flexibility : this allow us to create IrNET
+ * instances on demand (no /dev/ircommX crap) and to allow linkname
+ * specification on pppd command line...
+ *
+ * Second reason is speed optimisation. If you look closely at the
+ * transmit and receive paths, you will notice that they are "super lean"
+ * (that's why they look ugly), with no function calls and as little data
+ * copy and modification as I could...
+ *
+ * 2) irnetd in user space
+ * irnetd is implemented in user space, which is necessary to call pppd.
+ * This also give maximum benefits in term of flexibility and customability,
+ * and allow to offer the event channel, useful for other stuff like debug.
+ *
+ * On the other hand, this require a loose coordination between the
+ * present module and irnetd. One critical area is how incoming request
+ * are handled.
+ * When irnet receive an incoming request, it send an event to irnetd and
+ * drop the incoming IrNET socket.
+ * irnetd start a pppd instance, which create a new IrNET socket. This new
+ * socket is then connected in the originating node to the pppd instance.
+ * At this point, in the originating node, the first socket is closed.
+ *
+ * I admit, this is a bit messy and waste some ressources. The alternative
+ * is caching incoming socket, and that's also quite messy and waste
+ * ressources.
+ * We also make connection time slower. For example, on a 115 kb/s link it
+ * adds 60ms to the connection time (770 ms). However, this is slower than
+ * the time it takes to fire up pppd on my P133...
+ *
+ *
+ * History :
+ * -------
+ *
+ * v1 - 15.5.00 - Jean II
+ * o Basic IrNET (hook to ppp_generic & IrTTP - incl. multipoint)
+ * o control channel on /dev/irnet (set name/address)
+ * o event channel on /dev/irnet (for user space daemon)
+ *
+ * v2 - 5.6.00 - Jean II
+ * o Enable DROP_NOT_READY to avoid PPP timeouts & other weirdness...
+ * o Add DISCONNECT_TO event and rename DISCONNECT_FROM.
+ * o Set official device number alloaction on /dev/irnet
+ *
+ * v3 - 30.8.00 - Jean II
+ * o Update to latest Linux-IrDA changes :
+ * - queue_t => irda_queue_t
+ * o Update to ppp-2.4.0 :
+ * - move irda_irnet_connect from PPPIOCATTACH to TIOCSETD
+ * o Add EXPIRE event (depend on new IrDA-Linux patch)
+ * o Switch from `hashbin_remove' to `hashbin_remove_this' to fix
+ * a multilink bug... (depend on new IrDA-Linux patch)
+ * o fix a self->daddr to self->raddr in irda_irnet_connect to fix
+ * another multilink bug (darn !)
+ * o Remove LINKNAME_IOCTL cruft
+ *
+ * v3b - 31.8.00 - Jean II
+ * o Dump discovery log at event channel startup
+ *
+ * v4 - 28.9.00 - Jean II
+ * o Fix interaction between poll/select and dump discovery log
+ * o Add IRNET_BLOCKED_LINK event (depend on new IrDA-Linux patch)
+ * o Add IRNET_NOANSWER_FROM event (mostly to help support)
+ * o Release flow control in disconnect_indication
+ * o Block packets while connecting (speed up connections)
+ *
+ * v5 - 11.01.01 - Jean II
+ * o Init self->max_header_size, just in case...
+ * o Set up ap->chan.hdrlen, to get zero copy on tx side working.
+ * o avoid tx->ttp->flow->ppp->tx->... loop, by checking flow state
+ * Thanks to Christian Gennerat for finding this bug !
+ * ---
+ * o Declare the proper MTU/MRU that we can support
+ * (but PPP doesn't read the MTU value :-()
+ * o Declare hashbin HB_NOLOCK instead of HB_LOCAL to avoid
+ * disabling and enabling irq twice
+ *
+ * v6 - 31.05.01 - Jean II
+ * o Print source address in Found, Discovery, Expiry & Request events
+ * o Print requested source address in /proc/net/irnet
+ * o Change control channel input. Allow multiple commands in one line.
+ * o Add saddr command to change ap->rsaddr (and use that in IrDA)
+ * ---
+ * o Make the IrDA connection procedure totally asynchronous.
+ * Heavy rewrite of the IAS query code and the whole connection
+ * procedure. Now, irnet_connect() no longer need to be called from
+ * a process context...
+ * o Enable IrDA connect retries in ppp_irnet_send(). The good thing
+ * is that IrDA connect retries are directly driven by PPP LCP
+ * retries (we retry for each LCP packet), so that everything
+ * is transparently controlled from pppd lcp-max-configure.
+ * o Add ttp_connect flag to prevent rentry on the connect procedure
+ * o Test and fixups to eliminate side effects of retries
+ *
+ * v7 - 22.08.01 - Jean II
+ * o Cleanup : Change "saddr = 0x0" to "saddr = DEV_ADDR_ANY"
+ * o Fix bug in BLOCK_WHEN_CONNECT introduced in v6 : due to the
+ * asynchronous IAS query, self->tsap is NULL when PPP send the
+ * first packet. This was preventing "connect-delay 0" to work.
+ * Change the test in ppp_irnet_send() to self->ttp_connect.
+ *
+ * v8 - 1.11.01 - Jean II
+ * o Tighten the use of self->ttp_connect and self->ttp_open to
+ * prevent various race conditions.
+ * o Avoid leaking discovery log and skb
+ * o Replace "self" with "server" in irnet_connect_indication() to
+ * better detect cut'n'paste error ;-)
+ *
+ * v9 - 29.11.01 - Jean II
+ * o Fix event generation in disconnect indication that I broke in v8
+ * It was always generation "No-Answer" because I was testing ttp_open
+ * just after clearing it. *blush*.
+ * o Use newly created irttp_listen() to fix potential crash when LAP
+ * destroyed before irnet module removed.
+ *
+ * v10 - 4.3.2 - Jean II
+ * o When receiving a disconnect indication, don't reenable the
+ * PPP Tx queue, this will trigger a reconnect. Instead, close
+ * the channel, which will kill pppd...
+ *
+ * v11 - 20.3.02 - Jean II
+ * o Oops ! v10 fix disabled IrNET retries and passive behaviour.
+ * Better fix in irnet_disconnect_indication() :
+ * - if connected, kill pppd via hangup.
+ * - if not connected, reenable ppp Tx, which trigger IrNET retry.
+ *
+ * v12 - 10.4.02 - Jean II
+ * o Fix race condition in irnet_connect_indication().
+ * If the socket was already trying to connect, drop old connection
+ * and use new one only if acting as primary. See comments.
+ */
+
+/***************************** INCLUDES *****************************/
+
+#include <linux/module.h>
+
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+#include <linux/tty.h>
+#include <linux/proc_fs.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/netdevice.h>
+#include <linux/miscdevice.h>
+#include <linux/poll.h>
+#include <linux/config.h>
+#include <linux/ctype.h> /* isspace() */
+#include <asm/uaccess.h>
+
+#include <linux/ppp_defs.h>
+#include <linux/if_ppp.h>
+#include <linux/ppp_channel.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/iriap.h>
+#include <net/irda/irias_object.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/irttp.h>
+#include <net/irda/discovery.h>
+
+/***************************** OPTIONS *****************************/
+/*
+ * Define or undefine to compile or not some optional part of the
+ * IrNET driver...
+ * Note : the present defaults make sense, play with that at your
+ * own risk...
+ */
+/* IrDA side of the business... */
+#define DISCOVERY_NOMASK /* To enable W2k compatibility... */
+#define ADVERTISE_HINT /* Advertise IrLAN hint bit */
+#define ALLOW_SIMULT_CONNECT /* This seem to work, cross fingers... */
+#define DISCOVERY_EVENTS /* Query the discovery log to post events */
+#define INITIAL_DISCOVERY /* Dump current discovery log as events */
+#undef STREAM_COMPAT /* Not needed - potentially messy */
+#undef CONNECT_INDIC_KICK /* Might mess IrDA, not needed */
+#undef FAIL_SEND_DISCONNECT /* Might mess IrDA, not needed */
+#undef PASS_CONNECT_PACKETS /* Not needed ? Safe */
+
+/* PPP side of the business */
+#define BLOCK_WHEN_CONNECT /* Block packets when connecting */
+#define CONNECT_IN_SEND /* Retry IrDA connection procedure */
+#undef FLUSH_TO_PPP /* Not sure about this one, let's play safe */
+#undef SECURE_DEVIRNET /* Bah... */
+
+/****************************** DEBUG ******************************/
+
+/*
+ * This set of flags enable and disable all the various warning,
+ * error and debug message of this driver.
+ * Each section can be enabled and disabled independantly
+ */
+/* In the PPP part */
+#define DEBUG_CTRL_TRACE 0 /* Control channel */
+#define DEBUG_CTRL_INFO 0 /* various info */
+#define DEBUG_CTRL_ERROR 1 /* problems */
+#define DEBUG_FS_TRACE 0 /* filesystem callbacks */
+#define DEBUG_FS_INFO 0 /* various info */
+#define DEBUG_FS_ERROR 1 /* problems */
+#define DEBUG_PPP_TRACE 0 /* PPP related functions */
+#define DEBUG_PPP_INFO 0 /* various info */
+#define DEBUG_PPP_ERROR 1 /* problems */
+#define DEBUG_MODULE_TRACE 0 /* module insertion/removal */
+#define DEBUG_MODULE_ERROR 1 /* problems */
+
+/* In the IrDA part */
+#define DEBUG_IRDA_SR_TRACE 0 /* IRDA subroutines */
+#define DEBUG_IRDA_SR_INFO 0 /* various info */
+#define DEBUG_IRDA_SR_ERROR 1 /* problems */
+#define DEBUG_IRDA_SOCK_TRACE 0 /* IRDA main socket functions */
+#define DEBUG_IRDA_SOCK_INFO 0 /* various info */
+#define DEBUG_IRDA_SOCK_ERROR 1 /* problems */
+#define DEBUG_IRDA_SERV_TRACE 0 /* The IrNET server */
+#define DEBUG_IRDA_SERV_INFO 0 /* various info */
+#define DEBUG_IRDA_SERV_ERROR 1 /* problems */
+#define DEBUG_IRDA_TCB_TRACE 0 /* IRDA IrTTP callbacks */
+#define DEBUG_IRDA_CB_INFO 0 /* various info */
+#define DEBUG_IRDA_CB_ERROR 1 /* problems */
+#define DEBUG_IRDA_OCB_TRACE 0 /* IRDA other callbacks */
+#define DEBUG_IRDA_OCB_INFO 0 /* various info */
+#define DEBUG_IRDA_OCB_ERROR 1 /* problems */
+
+#define DEBUG_ASSERT 0 /* Verify all assertions */
+
+/*
+ * These are the macros we are using to actually print the debug
+ * statements. Don't look at it, it's ugly...
+ *
+ * One of the trick is that, as the DEBUG_XXX are constant, the
+ * compiler will optimise away the if() in all cases.
+ */
+/* All error messages (will show up in the normal logs) */
+#define DERROR(dbg, format, args...) \
+ {if(DEBUG_##dbg) \
+ printk(KERN_INFO "irnet: %s(): " format, __FUNCTION__ , ##args);}
+
+/* Normal debug message (will show up in /var/log/debug) */
+#define DEBUG(dbg, format, args...) \
+ {if(DEBUG_##dbg) \
+ printk(KERN_DEBUG "irnet: %s(): " format, __FUNCTION__ , ##args);}
+
+/* Entering a function (trace) */
+#define DENTER(dbg, format, args...) \
+ {if(DEBUG_##dbg) \
+ printk(KERN_DEBUG "irnet: -> %s" format, __FUNCTION__ , ##args);}
+
+/* Entering and exiting a function in one go (trace) */
+#define DPASS(dbg, format, args...) \
+ {if(DEBUG_##dbg) \
+ printk(KERN_DEBUG "irnet: <>%s" format, __FUNCTION__ , ##args);}
+
+/* Exiting a function (trace) */
+#define DEXIT(dbg, format, args...) \
+ {if(DEBUG_##dbg) \
+ printk(KERN_DEBUG "irnet: <-%s()" format, __FUNCTION__ , ##args);}
+
+/* Exit a function with debug */
+#define DRETURN(ret, dbg, args...) \
+ {DEXIT(dbg, ": " args);\
+ return(ret); }
+
+/* Exit a function on failed condition */
+#define DABORT(cond, ret, dbg, args...) \
+ {if(cond) {\
+ DERROR(dbg, args);\
+ return(ret); }}
+
+/* Invalid assertion, print out an error and exit... */
+#define DASSERT(cond, ret, dbg, args...) \
+ {if((DEBUG_ASSERT) && !(cond)) {\
+ DERROR(dbg, "Invalid assertion: " args);\
+ return ret; }}
+
+/************************ CONSTANTS & MACROS ************************/
+
+/* Paranoia */
+#define IRNET_MAGIC 0xB00754
+
+/* Number of control events in the control channel buffer... */
+#define IRNET_MAX_EVENTS 8 /* Should be more than enough... */
+
+/****************************** TYPES ******************************/
+
+/*
+ * This is the main structure where we store all the data pertaining to
+ * one instance of irnet.
+ * Note : in irnet functions, a pointer this structure is usually called
+ * "ap" or "self". If the code is borrowed from the IrDA stack, it tend
+ * to be called "self", and if it is borrowed from the PPP driver it is
+ * "ap". Apart from that, it's exactly the same structure ;-)
+ */
+typedef struct irnet_socket
+{
+ /* ------------------- Instance management ------------------- */
+ /* We manage a linked list of IrNET socket instances */
+ irda_queue_t q; /* Must be first - for hasbin */
+ int magic; /* Paranoia */
+
+ /* --------------------- FileSystem part --------------------- */
+ /* "pppd" interact directly with us on a /dev/ file */
+ struct file * file; /* File descriptor of this instance */
+ /* TTY stuff - to keep "pppd" happy */
+ struct termios termios; /* Various tty flags */
+ /* Stuff for the control channel */
+ int event_index; /* Last read in the event log */
+
+ /* ------------------------- PPP part ------------------------- */
+ /* We interface directly to the ppp_generic driver in the kernel */
+ int ppp_open; /* registered with ppp_generic */
+ struct ppp_channel chan; /* Interface to generic ppp layer */
+
+ int mru; /* Max size of PPP payload */
+ u32 xaccm[8]; /* Asynchronous character map (just */
+ u32 raccm; /* to please pppd - dummy) */
+ unsigned int flags; /* PPP flags (compression, ...) */
+ unsigned int rbits; /* Unused receive flags ??? */
+
+ /* ------------------------ IrTTP part ------------------------ */
+ /* We create a pseudo "socket" over the IrDA tranport */
+ int ttp_open; /* Set when IrTTP is ready */
+ int ttp_connect; /* Set when IrTTP is connecting */
+ struct tsap_cb * tsap; /* IrTTP instance (the connection) */
+
+ char rname[NICKNAME_MAX_LEN + 1];
+ /* IrDA nickname of destination */
+ __u32 rdaddr; /* Requested peer IrDA address */
+ __u32 rsaddr; /* Requested local IrDA address */
+ __u32 daddr; /* actual peer IrDA address */
+ __u32 saddr; /* my local IrDA address */
+ __u8 dtsap_sel; /* Remote TSAP selector */
+ __u8 stsap_sel; /* Local TSAP selector */
+
+ __u32 max_sdu_size_rx;/* Socket parameters used for IrTTP */
+ __u32 max_sdu_size_tx;
+ __u32 max_data_size;
+ __u8 max_header_size;
+ LOCAL_FLOW tx_flow; /* State of the Tx path in IrTTP */
+
+ /* ------------------- IrLMP and IrIAS part ------------------- */
+ /* Used for IrDA Discovery and socket name resolution */
+ __u32 ckey; /* IrLMP client handle */
+ __u16 mask; /* Hint bits mask (filter discov.)*/
+ int nslots; /* Number of slots for discovery */
+
+ struct iriap_cb * iriap; /* Used to query remote IAS */
+ int errno; /* status of the IAS query */
+
+ /* -------------------- Discovery log part -------------------- */
+ /* Used by initial discovery on the control channel
+ * and by irnet_discover_daddr_and_lsap_sel() */
+ struct irda_device_info *discoveries; /* Copy of the discovery log */
+ int disco_index; /* Last read in the discovery log */
+ int disco_number; /* Size of the discovery log */
+
+} irnet_socket;
+
+/*
+ * This is the various event that we will generate on the control channel
+ */
+typedef enum irnet_event
+{
+ IRNET_DISCOVER, /* New IrNET node discovered */
+ IRNET_EXPIRE, /* IrNET node expired */
+ IRNET_CONNECT_TO, /* IrNET socket has connected to other node */
+ IRNET_CONNECT_FROM, /* Other node has connected to IrNET socket */
+ IRNET_REQUEST_FROM, /* Non satisfied connection request */
+ IRNET_NOANSWER_FROM, /* Failed connection request */
+ IRNET_BLOCKED_LINK, /* Link (IrLAP) is blocked for > 3s */
+ IRNET_DISCONNECT_FROM, /* IrNET socket has disconnected */
+ IRNET_DISCONNECT_TO /* Closing IrNET socket */
+} irnet_event;
+
+/*
+ * This is the storage for an event and its arguments
+ */
+typedef struct irnet_log
+{
+ irnet_event event;
+ int unit;
+ __u32 saddr;
+ __u32 daddr;
+ char name[NICKNAME_MAX_LEN + 1]; /* 21 + 1 */
+} irnet_log;
+
+/*
+ * This is the storage for all events and related stuff...
+ */
+typedef struct irnet_ctrl_channel
+{
+ irnet_log log[IRNET_MAX_EVENTS]; /* Event log */
+ int index; /* Current index in log */
+ spinlock_t spinlock; /* Serialize access to the event log */
+ wait_queue_head_t rwait; /* processes blocked on read (or poll) */
+} irnet_ctrl_channel;
+
+/**************************** PROTOTYPES ****************************/
+/*
+ * Global functions of the IrNET module
+ * Note : we list here also functions called from one file to the other.
+ */
+
+/* -------------------------- IRDA PART -------------------------- */
+extern int
+ irda_irnet_create(irnet_socket *); /* Initialise a IrNET socket */
+extern int
+ irda_irnet_connect(irnet_socket *); /* Try to connect over IrDA */
+extern void
+ irda_irnet_destroy(irnet_socket *); /* Teardown a IrNET socket */
+extern int
+ irda_irnet_init(void); /* Initialise IrDA part of IrNET */
+extern void
+ irda_irnet_cleanup(void); /* Teardown IrDA part of IrNET */
+/* --------------------------- PPP PART --------------------------- */
+extern int
+ ppp_irnet_init(void); /* Initialise PPP part of IrNET */
+extern void
+ ppp_irnet_cleanup(void); /* Teardown PPP part of IrNET */
+/* ---------------------------- MODULE ---------------------------- */
+extern int
+ init_module(void); /* Initialise IrNET module */
+extern void
+ cleanup_module(void); /* Teardown IrNET module */
+
+/**************************** VARIABLES ****************************/
+
+/* Control channel stuff - allocated in irnet_irda.h */
+extern struct irnet_ctrl_channel irnet_events;
+
+#endif /* IRNET_H */
diff --git a/uClinux-2.4.31-uc0/net/irda/irnet/irnet_irda.c b/uClinux-2.4.31-uc0/net/irda/irnet/irnet_irda.c
new file mode 100644
index 0000000..8e629b9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irnet/irnet_irda.c
@@ -0,0 +1,1867 @@
+/*
+ * IrNET protocol module : Synchronous PPP over an IrDA socket.
+ *
+ * Jean II - HPL `00 - <jt@hpl.hp.com>
+ *
+ * This file implement the IRDA interface of IrNET.
+ * Basically, we sit on top of IrTTP. We set up IrTTP, IrIAS properly,
+ * and exchange frames with IrTTP.
+ */
+
+#include "irnet_irda.h" /* Private header */
+
+/************************* CONTROL CHANNEL *************************/
+/*
+ * When ppp is not active, /dev/irnet act as a control channel.
+ * Writting allow to set up the IrDA destination of the IrNET channel,
+ * and any application may be read events happening on IrNET...
+ */
+
+/*------------------------------------------------------------------*/
+/*
+ * Post an event to the control channel...
+ * Put the event in the log, and then wait all process blocked on read
+ * so they can read the log...
+ */
+static void
+irnet_post_event(irnet_socket * ap,
+ irnet_event event,
+ __u32 saddr,
+ __u32 daddr,
+ char * name)
+{
+ unsigned long flags; /* For spinlock */
+ int index; /* In the log */
+
+ DENTER(CTRL_TRACE, "(ap=0x%X, event=%d, daddr=%08x, name=``%s'')\n",
+ (unsigned int) ap, event, daddr, name);
+
+ /* Protect this section via spinlock.
+ * Note : as we are the only event producer, we only need to exclude
+ * ourself when touching the log, which is nice and easy.
+ */
+ spin_lock_irqsave(&irnet_events.spinlock, flags);
+
+ /* Copy the event in the log */
+ index = irnet_events.index;
+ irnet_events.log[index].event = event;
+ irnet_events.log[index].daddr = daddr;
+ irnet_events.log[index].saddr = saddr;
+ /* Try to copy IrDA nickname */
+ if(name)
+ strcpy(irnet_events.log[index].name, name);
+ else
+ irnet_events.log[index].name[0] = '\0';
+ /* Try to get ppp unit number */
+ if((ap != (irnet_socket *) NULL) && (ap->ppp_open))
+ irnet_events.log[index].unit = ppp_unit_number(&ap->chan);
+ else
+ irnet_events.log[index].unit = -1;
+
+ /* Increment the index
+ * Note that we increment the index only after the event is written,
+ * to make sure that the readers don't get garbage... */
+ irnet_events.index = (index + 1) % IRNET_MAX_EVENTS;
+
+ DEBUG(CTRL_INFO, "New event index is %d\n", irnet_events.index);
+
+ /* Spin lock end */
+ spin_unlock_irqrestore(&irnet_events.spinlock, flags);
+
+ /* Now : wake up everybody waiting for events... */
+ wake_up_interruptible_all(&irnet_events.rwait);
+
+ DEXIT(CTRL_TRACE, "\n");
+}
+
+/************************* IRDA SUBROUTINES *************************/
+/*
+ * These are a bunch of subroutines called from other functions
+ * down there, mostly common code or to improve readability...
+ *
+ * Note : we duplicate quite heavily some routines of af_irda.c,
+ * because our input structure (self) is quite different
+ * (struct irnet instead of struct irda_sock), which make sharing
+ * the same code impossible (at least, without templates).
+ */
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irda_open_tsap (self)
+ *
+ * Open local Transport Service Access Point (TSAP)
+ *
+ * Create a IrTTP instance for us and set all the IrTTP callbacks.
+ */
+static inline int
+irnet_open_tsap(irnet_socket * self)
+{
+ notify_t notify; /* Callback structure */
+
+ DENTER(IRDA_SR_TRACE, "(self=0x%X)\n", (unsigned int) self);
+
+ DABORT(self->tsap != NULL, -EBUSY, IRDA_SR_ERROR, "Already busy !\n");
+
+ /* Initialize IrTTP callbacks to be used by the IrDA stack */
+ irda_notify_init(&notify);
+ notify.connect_confirm = irnet_connect_confirm;
+ notify.connect_indication = irnet_connect_indication;
+ notify.disconnect_indication = irnet_disconnect_indication;
+ notify.data_indication = irnet_data_indication;
+ /*notify.udata_indication = NULL;*/
+ notify.flow_indication = irnet_flow_indication;
+ notify.status_indication = irnet_status_indication;
+ notify.instance = self;
+ strncpy(notify.name, IRNET_NOTIFY_NAME, NOTIFY_MAX_NAME);
+
+ /* Open an IrTTP instance */
+ self->tsap = irttp_open_tsap(LSAP_ANY, DEFAULT_INITIAL_CREDIT,
+ &notify);
+ DABORT(self->tsap == NULL, -ENOMEM,
+ IRDA_SR_ERROR, "Unable to allocate TSAP !\n");
+
+ /* Remember which TSAP selector we actually got */
+ self->stsap_sel = self->tsap->stsap_sel;
+
+ DEXIT(IRDA_SR_TRACE, " - tsap=0x%X, sel=0x%X\n",
+ (unsigned int) self->tsap, self->stsap_sel);
+ return 0;
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_ias_to_tsap (self, result, value)
+ *
+ * Examine an IAS object and extract TSAP
+ *
+ * We do an IAP query to find the TSAP associated with the IrNET service.
+ * When IrIAP pass us the result of the query, this function look at
+ * the return values to check for failures and extract the TSAP if
+ * possible.
+ * Also deallocate value
+ * The failure is in self->errno
+ * Return TSAP or -1
+ */
+static inline __u8
+irnet_ias_to_tsap(irnet_socket * self,
+ int result,
+ struct ias_value * value)
+{
+ __u8 dtsap_sel = 0; /* TSAP we are looking for */
+
+ DENTER(IRDA_SR_TRACE, "(self=0x%X)\n", (unsigned int) self);
+
+ /* By default, no error */
+ self->errno = 0;
+
+ /* Check if request succeeded */
+ switch(result)
+ {
+ /* Standard errors : service not available */
+ case IAS_CLASS_UNKNOWN:
+ case IAS_ATTRIB_UNKNOWN:
+ DEBUG(IRDA_SR_INFO, "IAS object doesn't exist ! (%d)\n", result);
+ self->errno = -EADDRNOTAVAIL;
+ break;
+
+ /* Other errors, most likely IrDA stack failure */
+ default :
+ DEBUG(IRDA_SR_INFO, "IAS query failed ! (%d)\n", result);
+ self->errno = -EHOSTUNREACH;
+ break;
+
+ /* Success : we got what we wanted */
+ case IAS_SUCCESS:
+ break;
+ }
+
+ /* Check what was returned to us */
+ if(value != NULL)
+ {
+ /* What type of argument have we got ? */
+ switch(value->type)
+ {
+ case IAS_INTEGER:
+ DEBUG(IRDA_SR_INFO, "result=%d\n", value->t.integer);
+ if(value->t.integer != -1)
+ /* Get the remote TSAP selector */
+ dtsap_sel = value->t.integer;
+ else
+ self->errno = -EADDRNOTAVAIL;
+ break;
+ default:
+ self->errno = -EADDRNOTAVAIL;
+ DERROR(IRDA_SR_ERROR, "bad type ! (0x%X)\n", value->type);
+ break;
+ }
+
+ /* Cleanup */
+ irias_delete_value(value);
+ }
+ else /* value == NULL */
+ {
+ /* Nothing returned to us - usually result != SUCCESS */
+ if(!(self->errno))
+ {
+ DERROR(IRDA_SR_ERROR,
+ "IrDA bug : result == SUCCESS && value == NULL\n");
+ self->errno = -EHOSTUNREACH;
+ }
+ }
+ DEXIT(IRDA_SR_TRACE, "\n");
+
+ /* Return the TSAP */
+ return(dtsap_sel);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_find_lsap_sel (self)
+ *
+ * Try to lookup LSAP selector in remote LM-IAS
+ *
+ * Basically, we start a IAP query, and then go to sleep. When the query
+ * return, irnet_getvalue_confirm will wake us up, and we can examine the
+ * result of the query...
+ * Note that in some case, the query fail even before we go to sleep,
+ * creating some races...
+ */
+static inline int
+irnet_find_lsap_sel(irnet_socket * self)
+{
+ DENTER(IRDA_SR_TRACE, "(self=0x%X)\n", (unsigned int) self);
+
+ /* This should not happen */
+ DABORT(self->iriap, -EBUSY, IRDA_SR_ERROR, "busy with a previous query.\n");
+
+ /* Create an IAP instance, will be closed in irnet_getvalue_confirm() */
+ self->iriap = iriap_open(LSAP_ANY, IAS_CLIENT, self,
+ irnet_getvalue_confirm);
+
+ /* Treat unexpected signals as disconnect */
+ self->errno = -EHOSTUNREACH;
+
+ /* Query remote LM-IAS */
+ iriap_getvaluebyclass_request(self->iriap, self->rsaddr, self->daddr,
+ IRNET_SERVICE_NAME, IRNET_IAS_VALUE);
+
+ /* The above request is non-blocking.
+ * After a while, IrDA will call us back in irnet_getvalue_confirm()
+ * We will then call irnet_ias_to_tsap() and finish the
+ * connection procedure */
+
+ DEXIT(IRDA_SR_TRACE, "\n");
+ return 0;
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_connect_tsap (self)
+ *
+ * Initialise the TTP socket and initiate TTP connection
+ *
+ */
+static inline int
+irnet_connect_tsap(irnet_socket * self)
+{
+ int err;
+
+ DENTER(IRDA_SR_TRACE, "(self=0x%X)\n", (unsigned int) self);
+
+ /* Open a local TSAP (an IrTTP instance) */
+ err = irnet_open_tsap(self);
+ if(err != 0)
+ {
+ clear_bit(0, &self->ttp_connect);
+ DERROR(IRDA_SR_ERROR, "connect aborted!\n");
+ return(err);
+ }
+
+ /* Connect to remote device */
+ err = irttp_connect_request(self->tsap, self->dtsap_sel,
+ self->rsaddr, self->daddr, NULL,
+ self->max_sdu_size_rx, NULL);
+ if(err != 0)
+ {
+ clear_bit(0, &self->ttp_connect);
+ DERROR(IRDA_SR_ERROR, "connect aborted!\n");
+ return(err);
+ }
+
+ /* The above call is non-blocking.
+ * After a while, the IrDA stack will either call us back in
+ * irnet_connect_confirm() or irnet_disconnect_indication()
+ * See you there ;-) */
+
+ DEXIT(IRDA_SR_TRACE, "\n");
+ return(err);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_discover_next_daddr (self)
+ *
+ * Query the IrNET TSAP of the next device in the log.
+ *
+ * Used in the TSAP discovery procedure.
+ */
+static inline int
+irnet_discover_next_daddr(irnet_socket * self)
+{
+ /* Close the last instance of IrIAP, and open a new one.
+ * We can't reuse the IrIAP instance in the IrIAP callback */
+ if(self->iriap)
+ {
+ iriap_close(self->iriap);
+ self->iriap = NULL;
+ }
+ /* Create a new IAP instance */
+ self->iriap = iriap_open(LSAP_ANY, IAS_CLIENT, self,
+ irnet_discovervalue_confirm);
+
+ /* Next discovery - before the call to avoid races */
+ self->disco_index++;
+
+ /* Check if we have one more address to try */
+ if(self->disco_index < self->disco_number)
+ {
+ /* Query remote LM-IAS */
+ iriap_getvaluebyclass_request(self->iriap,
+ self->discoveries[self->disco_index].saddr,
+ self->discoveries[self->disco_index].daddr,
+ IRNET_SERVICE_NAME, IRNET_IAS_VALUE);
+ /* The above request is non-blocking.
+ * After a while, IrDA will call us back in irnet_discovervalue_confirm()
+ * We will then call irnet_ias_to_tsap() and come back here again... */
+ return(0);
+ }
+ else
+ return(1);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_discover_daddr_and_lsap_sel (self)
+ *
+ * This try to find a device with the requested service.
+ *
+ * Initiate a TSAP discovery procedure.
+ * It basically look into the discovery log. For each address in the list,
+ * it queries the LM-IAS of the device to find if this device offer
+ * the requested service.
+ * If there is more than one node supporting the service, we complain
+ * to the user (it should move devices around).
+ * If we find one node which have the requested TSAP, we connect to it.
+ *
+ * This function just start the whole procedure. It request the discovery
+ * log and submit the first IAS query.
+ * The bulk of the job is handled in irnet_discovervalue_confirm()
+ *
+ * Note : this procedure fails if there is more than one device in range
+ * on the same dongle, because IrLMP doesn't disconnect the LAP when the
+ * last LSAP is closed. Moreover, we would need to wait the LAP
+ * disconnection...
+ */
+static inline int
+irnet_discover_daddr_and_lsap_sel(irnet_socket * self)
+{
+ int ret;
+
+ DENTER(IRDA_SR_TRACE, "(self=0x%X)\n", (unsigned int) self);
+
+ /* Ask lmp for the current discovery log */
+ self->discoveries = irlmp_get_discoveries(&self->disco_number, self->mask,
+ DISCOVERY_DEFAULT_SLOTS);
+
+ /* Check if the we got some results */
+ if(self->discoveries == NULL)
+ {
+ self->disco_number = -1;
+ clear_bit(0, &self->ttp_connect);
+ DRETURN(-ENETUNREACH, IRDA_SR_INFO, "No Cachelog...\n");
+ }
+ DEBUG(IRDA_SR_INFO, "Got the log (0x%X), size is %d\n",
+ (unsigned int) self->discoveries, self->disco_number);
+
+ /* Start with the first discovery */
+ self->disco_index = -1;
+ self->daddr = DEV_ADDR_ANY;
+
+ /* This will fail if the log is empty - this is non-blocking */
+ ret = irnet_discover_next_daddr(self);
+ if(ret)
+ {
+ /* Close IAP */
+ iriap_close(self->iriap);
+ self->iriap = NULL;
+
+ /* Cleanup our copy of the discovery log */
+ kfree(self->discoveries);
+ self->discoveries = NULL;
+
+ clear_bit(0, &self->ttp_connect);
+ DRETURN(-ENETUNREACH, IRDA_SR_INFO, "Cachelog empty...\n");
+ }
+
+ /* Follow me in irnet_discovervalue_confirm() */
+
+ DEXIT(IRDA_SR_TRACE, "\n");
+ return(0);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_dname_to_daddr (self)
+ *
+ * Convert an IrDA nickname to a valid IrDA address
+ *
+ * It basically look into the discovery log until there is a match.
+ */
+static inline int
+irnet_dname_to_daddr(irnet_socket * self)
+{
+ struct irda_device_info *discoveries; /* Copy of the discovery log */
+ int number; /* Number of nodes in the log */
+ int i;
+
+ DENTER(IRDA_SR_TRACE, "(self=0x%X)\n", (unsigned int) self);
+
+ /* Ask lmp for the current discovery log */
+ discoveries = irlmp_get_discoveries(&number, 0xffff,
+ DISCOVERY_DEFAULT_SLOTS);
+ /* Check if the we got some results */
+ if(discoveries == NULL)
+ DRETURN(-ENETUNREACH, IRDA_SR_INFO, "Cachelog empty...\n");
+
+ /*
+ * Now, check all discovered devices (if any), and connect
+ * client only about the services that the client is
+ * interested in...
+ */
+ for(i = 0; i < number; i++)
+ {
+ /* Does the name match ? */
+ if(!strncmp(discoveries[i].info, self->rname, NICKNAME_MAX_LEN))
+ {
+ /* Yes !!! Get it.. */
+ self->daddr = discoveries[i].daddr;
+ DEBUG(IRDA_SR_INFO, "discovered device ``%s'' at address 0x%08x.\n",
+ self->rname, self->daddr);
+ kfree(discoveries);
+ DEXIT(IRDA_SR_TRACE, "\n");
+ return 0;
+ }
+ }
+ /* No luck ! */
+ DEBUG(IRDA_SR_INFO, "cannot discover device ``%s'' !!!\n", self->rname);
+ kfree(discoveries);
+ return(-EADDRNOTAVAIL);
+}
+
+
+/************************* SOCKET ROUTINES *************************/
+/*
+ * This are the main operations on IrNET sockets, basically to create
+ * and destroy IrNET sockets. These are called from the PPP part...
+ */
+
+/*------------------------------------------------------------------*/
+/*
+ * Create a IrNET instance : just initialise some parameters...
+ */
+int
+irda_irnet_create(irnet_socket * self)
+{
+ DENTER(IRDA_SOCK_TRACE, "(self=0x%X)\n", (unsigned int) self);
+
+ self->magic = IRNET_MAGIC; /* Paranoia */
+
+ self->ttp_open = 0; /* Prevent higher layer from accessing IrTTP */
+ self->ttp_connect = 0; /* Not connecting yet */
+ self->rname[0] = '\0'; /* May be set via control channel */
+ self->rdaddr = DEV_ADDR_ANY; /* May be set via control channel */
+ self->rsaddr = DEV_ADDR_ANY; /* May be set via control channel */
+ self->daddr = DEV_ADDR_ANY; /* Until we get connected */
+ self->saddr = DEV_ADDR_ANY; /* Until we get connected */
+ self->max_sdu_size_rx = TTP_SAR_UNBOUND;
+
+ /* Register as a client with IrLMP */
+ self->ckey = irlmp_register_client(0, NULL, NULL, NULL);
+#ifdef DISCOVERY_NOMASK
+ self->mask = 0xffff; /* For W2k compatibility */
+#else /* DISCOVERY_NOMASK */
+ self->mask = irlmp_service_to_hint(S_LAN);
+#endif /* DISCOVERY_NOMASK */
+ self->tx_flow = FLOW_START; /* Flow control from IrTTP */
+
+ DEXIT(IRDA_SOCK_TRACE, "\n");
+ return(0);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Connect to the other side :
+ * o convert device name to an address
+ * o find the socket number (dlsap)
+ * o Establish the connection
+ *
+ * Note : We no longer mimic af_irda. The IAS query for finding the TSAP
+ * is done asynchronously, like the TTP connection. This allow us to
+ * call this function from any context (not only process).
+ * The downside is that following what's happening in there is tricky
+ * because it involve various functions all over the place...
+ */
+int
+irda_irnet_connect(irnet_socket * self)
+{
+ int err;
+
+ DENTER(IRDA_SOCK_TRACE, "(self=0x%X)\n", (unsigned int) self);
+
+ /* Check if we are already trying to connect.
+ * Because irda_irnet_connect() can be called directly by pppd plus
+ * packet retries in ppp_generic and connect may take time, plus we may
+ * race with irnet_connect_indication(), we need to be careful there... */
+ if(test_and_set_bit(0, &self->ttp_connect))
+ DRETURN(-EBUSY, IRDA_SOCK_INFO, "Already connecting...\n");
+ if((self->iriap != NULL) || (self->tsap != NULL))
+ DERROR(IRDA_SOCK_ERROR, "Socket not cleaned up...\n");
+
+ /* Insert ourselves in the hashbin so that the IrNET server can find us.
+ * Notes : 4th arg is string of 32 char max and must be null terminated
+ * When 4th arg is used (string), 3rd arg isn't (int)
+ * Can't re-insert (MUST remove first) so check for that... */
+ if((irnet_server.running) && (self->q.q_next == NULL))
+ {
+ unsigned long flags;
+ spin_lock_irqsave(&irnet_server.spinlock, flags);
+ hashbin_insert(irnet_server.list, (irda_queue_t *) self, 0, self->rname);
+ spin_unlock_irqrestore(&irnet_server.spinlock, flags);
+ DEBUG(IRDA_SOCK_INFO, "Inserted ``%s'' in hashbin...\n", self->rname);
+ }
+
+ /* If we don't have anything (no address, no name) */
+ if((self->rdaddr == DEV_ADDR_ANY) && (self->rname[0] == '\0'))
+ {
+ /* Try to find a suitable address */
+ if((err = irnet_discover_daddr_and_lsap_sel(self)) != 0)
+ DRETURN(err, IRDA_SOCK_INFO, "auto-connect failed!\n");
+ /* In most cases, the call above is non-blocking */
+ }
+ else
+ {
+ /* If we have only the name (no address), try to get an address */
+ if(self->rdaddr == DEV_ADDR_ANY)
+ {
+ if((err = irnet_dname_to_daddr(self)) != 0)
+ DRETURN(err, IRDA_SOCK_INFO, "name connect failed!\n");
+ }
+ else
+ /* Use the requested destination address */
+ self->daddr = self->rdaddr;
+
+ /* Query remote LM-IAS to find LSAP selector */
+ irnet_find_lsap_sel(self);
+ /* The above call is non blocking */
+ }
+
+ /* At this point, we are waiting for the IrDA stack to call us back,
+ * or we have already failed.
+ * We will finish the connection procedure in irnet_connect_tsap().
+ */
+ DEXIT(IRDA_SOCK_TRACE, "\n");
+ return(0);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irda_irnet_destroy(self)
+ *
+ * Destroy irnet instance
+ *
+ * Note : this need to be called from a process context.
+ */
+void
+irda_irnet_destroy(irnet_socket * self)
+{
+ DENTER(IRDA_SOCK_TRACE, "(self=0x%X)\n", (unsigned int) self);
+ if(self == NULL)
+ return;
+
+ /* Remove ourselves from hashbin (if we are queued in hashbin)
+ * Note : `irnet_server.running' protect us from calls in hashbin_delete() */
+ if((irnet_server.running) && (self->q.q_next != NULL))
+ {
+ struct irnet_socket * entry;
+ unsigned long flags;
+ DEBUG(IRDA_SOCK_INFO, "Removing from hash..\n");
+ spin_lock_irqsave(&irnet_server.spinlock, flags);
+ entry = hashbin_remove_this(irnet_server.list, (irda_queue_t *) self);
+ self->q.q_next = NULL;
+ spin_unlock_irqrestore(&irnet_server.spinlock, flags);
+ DASSERT(entry == self, , IRDA_SOCK_ERROR, "Can't remove from hash.\n");
+ }
+
+ /* If we were connected, post a message */
+ if(test_bit(0, &self->ttp_open))
+ {
+ /* Note : as the disconnect comes from ppp_generic, the unit number
+ * doesn't exist anymore when we post the event, so we need to pass
+ * NULL as the first arg... */
+ irnet_post_event(NULL, IRNET_DISCONNECT_TO,
+ self->saddr, self->daddr, self->rname);
+ }
+
+ /* Prevent various IrDA callbacks from messing up things
+ * Need to be first */
+ clear_bit(0, &self->ttp_connect);
+
+ /* Prevent higher layer from accessing IrTTP */
+ clear_bit(0, &self->ttp_open);
+
+ /* Unregister with IrLMP */
+ irlmp_unregister_client(self->ckey);
+
+ /* Unregister with LM-IAS */
+ if(self->iriap)
+ {
+ iriap_close(self->iriap);
+ self->iriap = NULL;
+ }
+
+ /* Cleanup eventual discoveries from connection attempt */
+ if(self->discoveries != NULL)
+ {
+ /* Cleanup our copy of the discovery log */
+ kfree(self->discoveries);
+ self->discoveries = NULL;
+ }
+
+ /* Close our IrTTP connection */
+ if(self->tsap)
+ {
+ DEBUG(IRDA_SOCK_INFO, "Closing our TTP connection.\n");
+ irttp_disconnect_request(self->tsap, NULL, P_NORMAL);
+ irttp_close_tsap(self->tsap);
+ self->tsap = NULL;
+ }
+ self->stsap_sel = 0;
+
+ DEXIT(IRDA_SOCK_TRACE, "\n");
+ return;
+}
+
+
+/************************** SERVER SOCKET **************************/
+/*
+ * The IrNET service is composed of one server socket and a variable
+ * number of regular IrNET sockets. The server socket is supposed to
+ * handle incoming connections and redirect them to one IrNET sockets.
+ * It's a superset of the regular IrNET socket, but has a very distinct
+ * behaviour...
+ */
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_daddr_to_dname (self)
+ *
+ * Convert an IrDA address to a IrDA nickname
+ *
+ * It basically look into the discovery log until there is a match.
+ */
+static inline int
+irnet_daddr_to_dname(irnet_socket * self)
+{
+ struct irda_device_info *discoveries; /* Copy of the discovery log */
+ int number; /* Number of nodes in the log */
+ int i;
+
+ DENTER(IRDA_SERV_TRACE, "(self=0x%X)\n", (unsigned int) self);
+
+ /* Ask lmp for the current discovery log */
+ discoveries = irlmp_get_discoveries(&number, 0xffff,
+ DISCOVERY_DEFAULT_SLOTS);
+ /* Check if the we got some results */
+ if (discoveries == NULL)
+ DRETURN(-ENETUNREACH, IRDA_SERV_INFO, "Cachelog empty...\n");
+
+ /* Now, check all discovered devices (if any) */
+ for(i = 0; i < number; i++)
+ {
+ /* Does the name match ? */
+ if(discoveries[i].daddr == self->daddr)
+ {
+ /* Yes !!! Get it.. */
+ strncpy(self->rname, discoveries[i].info, NICKNAME_MAX_LEN);
+ self->rname[NICKNAME_MAX_LEN + 1] = '\0';
+ DEBUG(IRDA_SERV_INFO, "Device 0x%08x is in fact ``%s''.\n",
+ self->daddr, self->rname);
+ kfree(discoveries);
+ DEXIT(IRDA_SERV_TRACE, "\n");
+ return 0;
+ }
+ }
+ /* No luck ! */
+ DEXIT(IRDA_SERV_INFO, ": cannot discover device 0x%08x !!!\n", self->daddr);
+ kfree(discoveries);
+ return(-EADDRNOTAVAIL);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irda_find_socket (self)
+ *
+ * Find the correct IrNET socket
+ *
+ * Look into the list of IrNET sockets and finds one with the right
+ * properties...
+ */
+static inline irnet_socket *
+irnet_find_socket(irnet_socket * self)
+{
+ irnet_socket * new = (irnet_socket *) NULL;
+ unsigned long flags;
+ int err;
+
+ DENTER(IRDA_SERV_TRACE, "(self=0x%X)\n", (unsigned int) self);
+
+ /* Get the addresses of the requester */
+ self->daddr = irttp_get_daddr(self->tsap);
+ self->saddr = irttp_get_saddr(self->tsap);
+
+ /* Try to get the IrDA nickname of the requester */
+ err = irnet_daddr_to_dname(self);
+
+ /* Protect access to the instance list */
+ spin_lock_irqsave(&irnet_server.spinlock, flags);
+
+ /* So now, try to get an socket having specifically
+ * requested that nickname */
+ if(err == 0)
+ {
+ new = (irnet_socket *) hashbin_find(irnet_server.list,
+ 0, self->rname);
+ if(new)
+ DEBUG(IRDA_SERV_INFO, "Socket 0x%X matches rname ``%s''.\n",
+ (unsigned int) new, new->rname);
+ }
+
+ /* If no name matches, try to find an socket by the destination address */
+ /* It can be either the requested destination address (set via the
+ * control channel), or the current destination address if the
+ * socket is in the middle of a connection request */
+ if(new == (irnet_socket *) NULL)
+ {
+ new = (irnet_socket *) hashbin_get_first(irnet_server.list);
+ while(new !=(irnet_socket *) NULL)
+ {
+ /* Does it have the same address ? */
+ if((new->rdaddr == self->daddr) || (new->daddr == self->daddr))
+ {
+ /* Yes !!! Get it.. */
+ DEBUG(IRDA_SERV_INFO, "Socket 0x%X matches daddr %#08x.\n",
+ (unsigned int) new, self->daddr);
+ break;
+ }
+ new = (irnet_socket *) hashbin_get_next(irnet_server.list);
+ }
+ }
+
+ /* If we don't have any socket, get the first unconnected socket */
+ if(new == (irnet_socket *) NULL)
+ {
+ new = (irnet_socket *) hashbin_get_first(irnet_server.list);
+ while(new !=(irnet_socket *) NULL)
+ {
+ /* Is it available ? */
+ if(!(test_bit(0, &new->ttp_open)) && (new->rdaddr == DEV_ADDR_ANY) &&
+ (new->rname[0] == '\0') && (new->ppp_open))
+ {
+ /* Yes !!! Get it.. */
+ DEBUG(IRDA_SERV_INFO, "Socket 0x%X is free.\n",
+ (unsigned int) new);
+ break;
+ }
+ new = (irnet_socket *) hashbin_get_next(irnet_server.list);
+ }
+ }
+
+ /* Spin lock end */
+ spin_unlock_irqrestore(&irnet_server.spinlock, flags);
+
+ DEXIT(IRDA_SERV_TRACE, " - new = 0x%X\n", (unsigned int) new);
+ return new;
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irda_connect_socket (self)
+ *
+ * Connect an incoming connection to the socket
+ *
+ */
+static inline int
+irnet_connect_socket(irnet_socket * server,
+ irnet_socket * new,
+ struct qos_info * qos,
+ __u32 max_sdu_size,
+ __u8 max_header_size)
+{
+ DENTER(IRDA_SERV_TRACE, "(server=0x%X, new=0x%X)\n",
+ (unsigned int) server, (unsigned int) new);
+
+ /* Now attach up the new socket */
+ new->tsap = irttp_dup(server->tsap, new);
+ DABORT(new->tsap == NULL, -1, IRDA_SERV_ERROR, "dup failed!\n");
+
+ /* Set up all the relevant parameters on the new socket */
+ new->stsap_sel = new->tsap->stsap_sel;
+ new->dtsap_sel = new->tsap->dtsap_sel;
+ new->saddr = irttp_get_saddr(new->tsap);
+ new->daddr = irttp_get_daddr(new->tsap);
+
+ new->max_header_size = max_header_size;
+ new->max_sdu_size_tx = max_sdu_size;
+ new->max_data_size = max_sdu_size;
+#ifdef STREAM_COMPAT
+ /* If we want to receive "stream sockets" */
+ if(max_sdu_size == 0)
+ new->max_data_size = irttp_get_max_seg_size(new->tsap);
+#endif /* STREAM_COMPAT */
+
+ /* Clean up the original one to keep it in listen state */
+ irttp_listen(server->tsap);
+
+ /* Send a connection response on the new socket */
+ irttp_connect_response(new->tsap, new->max_sdu_size_rx, NULL);
+
+ /* Allow PPP to send its junk over the new socket... */
+ set_bit(0, &new->ttp_open);
+
+ /* Not connecting anymore, and clean up last possible remains
+ * of connection attempts on the socket */
+ clear_bit(0, &new->ttp_connect);
+ if(new->iriap)
+ {
+ iriap_close(new->iriap);
+ new->iriap = NULL;
+ }
+ if(new->discoveries != NULL)
+ {
+ kfree(new->discoveries);
+ new->discoveries = NULL;
+ }
+
+#ifdef CONNECT_INDIC_KICK
+ /* As currently we don't block packets in ppp_irnet_send() while passive,
+ * this is not really needed...
+ * Also, not doing it give IrDA a chance to finish the setup properly
+ * before beeing swamped with packets... */
+ ppp_output_wakeup(&new->chan);
+#endif /* CONNECT_INDIC_KICK */
+
+ /* Notify the control channel */
+ irnet_post_event(new, IRNET_CONNECT_FROM,
+ new->saddr, new->daddr, server->rname);
+
+ DEXIT(IRDA_SERV_TRACE, "\n");
+ return 0;
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irda_disconnect_server (self)
+ *
+ * Cleanup the server socket when the incoming connection abort
+ *
+ */
+static inline void
+irnet_disconnect_server(irnet_socket * self,
+ struct sk_buff *skb)
+{
+ DENTER(IRDA_SERV_TRACE, "(self=0x%X)\n", (unsigned int) self);
+
+ /* Put the received packet in the black hole */
+ kfree_skb(skb);
+
+#ifdef FAIL_SEND_DISCONNECT
+ /* Tell the other party we don't want to be connected */
+ /* Hum... Is it the right thing to do ? And do we need to send
+ * a connect response before ? It looks ok without this... */
+ irttp_disconnect_request(self->tsap, NULL, P_NORMAL);
+#endif /* FAIL_SEND_DISCONNECT */
+
+ /* Notify the control channel (see irnet_find_socket()) */
+ irnet_post_event(NULL, IRNET_REQUEST_FROM,
+ self->saddr, self->daddr, self->rname);
+
+ /* Clean up the server to keep it in listen state */
+ irttp_listen(self->tsap);
+
+ DEXIT(IRDA_SERV_TRACE, "\n");
+ return;
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irda_setup_server (self)
+ *
+ * Create a IrTTP server and set it up...
+ *
+ * Register the IrLAN hint bit, create a IrTTP instance for us,
+ * set all the IrTTP callbacks and create an IrIAS entry...
+ */
+static inline int
+irnet_setup_server(void)
+{
+ __u16 hints;
+
+ DENTER(IRDA_SERV_TRACE, "()\n");
+
+ /* Initialise the regular socket part of the server */
+ irda_irnet_create(&irnet_server.s);
+
+ /* Open a local TSAP (an IrTTP instance) for the server */
+ irnet_open_tsap(&irnet_server.s);
+
+ /* PPP part setup */
+ irnet_server.s.ppp_open = 0;
+ irnet_server.s.chan.private = NULL;
+ irnet_server.s.file = NULL;
+
+ /* Get the hint bit corresponding to IrLAN */
+ /* Note : we overload the IrLAN hint bit. As it is only a "hint", and as
+ * we provide roughly the same functionality as IrLAN, this is ok.
+ * In fact, the situation is similar as JetSend overloading the Obex hint
+ */
+ hints = irlmp_service_to_hint(S_LAN);
+
+#ifdef ADVERTISE_HINT
+ /* Register with IrLMP as a service (advertise our hint bit) */
+ irnet_server.skey = irlmp_register_service(hints);
+#endif /* ADVERTISE_HINT */
+
+ /* Register with LM-IAS (so that people can connect to us) */
+ irnet_server.ias_obj = irias_new_object(IRNET_SERVICE_NAME, jiffies);
+ irias_add_integer_attrib(irnet_server.ias_obj, IRNET_IAS_VALUE,
+ irnet_server.s.stsap_sel, IAS_KERNEL_ATTR);
+ irias_insert_object(irnet_server.ias_obj);
+
+#ifdef DISCOVERY_EVENTS
+ /* Tell IrLMP we want to be notified of newly discovered nodes */
+ irlmp_update_client(irnet_server.s.ckey, hints,
+ irnet_discovery_indication, irnet_expiry_indication,
+ (void *) &irnet_server.s);
+#endif
+
+ DEXIT(IRDA_SERV_TRACE, " - self=0x%X\n", (unsigned int) &irnet_server.s);
+ return 0;
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irda_destroy_server (self)
+ *
+ * Destroy the IrTTP server...
+ *
+ * Reverse of the previous function...
+ */
+static inline void
+irnet_destroy_server(void)
+{
+ DENTER(IRDA_SERV_TRACE, "()\n");
+
+#ifdef ADVERTISE_HINT
+ /* Unregister with IrLMP */
+ irlmp_unregister_service(irnet_server.skey);
+#endif /* ADVERTISE_HINT */
+
+ /* Unregister with LM-IAS */
+ if(irnet_server.ias_obj)
+ irias_delete_object(irnet_server.ias_obj);
+
+ /* Cleanup the socket part */
+ irda_irnet_destroy(&irnet_server.s);
+
+ DEXIT(IRDA_SERV_TRACE, "\n");
+ return;
+}
+
+
+/************************ IRDA-TTP CALLBACKS ************************/
+/*
+ * When we create a IrTTP instance, we pass to it a set of callbacks
+ * that IrTTP will call in case of various events.
+ * We take care of those events here.
+ */
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_data_indication (instance, sap, skb)
+ *
+ * Received some data from TinyTP. Just queue it on the receive queue
+ *
+ */
+static int
+irnet_data_indication(void * instance,
+ void * sap,
+ struct sk_buff *skb)
+{
+ irnet_socket * ap = (irnet_socket *) instance;
+ unsigned char * p;
+ int code = 0;
+
+ DENTER(IRDA_TCB_TRACE, "(self/ap=0x%X, skb=0x%X)\n",
+ (unsigned int) ap,(unsigned int) skb);
+ DASSERT(skb != NULL, 0, IRDA_CB_ERROR, "skb is NULL !!!\n");
+
+ /* Check is ppp is ready to receive our packet */
+ if(!ap->ppp_open)
+ {
+ DERROR(IRDA_CB_ERROR, "PPP not ready, dropping packet...\n");
+ /* When we return error, TTP will need to requeue the skb and
+ * will stop the sender. IrTTP will stall until we send it a
+ * flow control request... */
+ return -ENOMEM;
+ }
+
+ /* strip address/control field if present */
+ p = skb->data;
+ if((p[0] == PPP_ALLSTATIONS) && (p[1] == PPP_UI))
+ {
+ /* chop off address/control */
+ if(skb->len < 3)
+ goto err_exit;
+ p = skb_pull(skb, 2);
+ }
+
+ /* decompress protocol field if compressed */
+ if(p[0] & 1)
+ {
+ /* protocol is compressed */
+ skb_push(skb, 1)[0] = 0;
+ }
+ else
+ if(skb->len < 2)
+ goto err_exit;
+
+ /* pass to generic ppp layer */
+ /* Note : how do I know if ppp can accept or not the packet ? This is
+ * essential if I want to manage flow control smoothly... */
+ ppp_input(&ap->chan, skb);
+
+ DEXIT(IRDA_TCB_TRACE, "\n");
+ return 0;
+
+ err_exit:
+ DERROR(IRDA_CB_ERROR, "Packet too small, dropping...\n");
+ kfree_skb(skb);
+ ppp_input_error(&ap->chan, code);
+ return 0; /* Don't return an error code, only for flow control... */
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_disconnect_indication (instance, sap, reason, skb)
+ *
+ * Connection has been closed. Chech reason to find out why
+ *
+ * Note : there are many cases where we come here :
+ * o attempted to connect, timeout
+ * o connected, link is broken, LAP has timeout
+ * o connected, other side close the link
+ * o connection request on the server not handled
+ */
+static void
+irnet_disconnect_indication(void * instance,
+ void * sap,
+ LM_REASON reason,
+ struct sk_buff *skb)
+{
+ irnet_socket * self = (irnet_socket *) instance;
+ int test_open;
+ int test_connect;
+
+ DENTER(IRDA_TCB_TRACE, "(self=0x%X)\n", (unsigned int) self);
+ DASSERT(self != NULL, , IRDA_CB_ERROR, "Self is NULL !!!\n");
+
+ /* Don't care about it, but let's not leak it */
+ if(skb)
+ dev_kfree_skb(skb);
+
+ /* Prevent higher layer from accessing IrTTP */
+ test_open = test_and_clear_bit(0, &self->ttp_open);
+ /* Not connecting anymore...
+ * (note : TSAP is open, so IAP callbacks are no longer pending...) */
+ test_connect = test_and_clear_bit(0, &self->ttp_connect);
+
+ /* If both self->ttp_open and self->ttp_connect are NULL, it mean that we
+ * have a race condition with irda_irnet_destroy() or
+ * irnet_connect_indication(), so don't mess up tsap...
+ */
+ if(!(test_open || test_connect))
+ {
+ DERROR(IRDA_CB_ERROR, "Race condition detected...\n");
+ return;
+ }
+
+ /* If we were active, notify the control channel */
+ if(test_open)
+ irnet_post_event(self, IRNET_DISCONNECT_FROM,
+ self->saddr, self->daddr, self->rname);
+ else
+ /* If we were trying to connect, notify the control channel */
+ if((self->tsap) && (self != &irnet_server.s))
+ irnet_post_event(self, IRNET_NOANSWER_FROM,
+ self->saddr, self->daddr, self->rname);
+
+ /* Close our IrTTP connection, cleanup tsap */
+ if((self->tsap) && (self != &irnet_server.s))
+ {
+ DEBUG(IRDA_CB_INFO, "Closing our TTP connection.\n");
+ irttp_close_tsap(self->tsap);
+ self->tsap = NULL;
+ }
+ /* Cleanup the socket in case we want to reconnect in ppp_output_wakeup() */
+ self->stsap_sel = 0;
+ self->daddr = DEV_ADDR_ANY;
+ self->tx_flow = FLOW_START;
+
+ /* Deal with the ppp instance if it's still alive */
+ if(self->ppp_open)
+ {
+ if(test_open)
+ {
+ /* If we were connected, cleanup & close the PPP channel,
+ * which will kill pppd (hangup) and the rest */
+ ppp_unregister_channel(&self->chan);
+ self->ppp_open = 0;
+ }
+ else
+ {
+ /* If we were trying to connect, flush (drain) ppp_generic
+ * Tx queue (most often we have blocked it), which will
+ * trigger an other attempt to connect. If we are passive,
+ * this will empty the Tx queue after last try. */
+ ppp_output_wakeup(&self->chan);
+ }
+ }
+
+ DEXIT(IRDA_TCB_TRACE, "\n");
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_connect_confirm (instance, sap, qos, max_sdu_size, skb)
+ *
+ * Connections has been confirmed by the remote device
+ *
+ */
+static void
+irnet_connect_confirm(void * instance,
+ void * sap,
+ struct qos_info *qos,
+ __u32 max_sdu_size,
+ __u8 max_header_size,
+ struct sk_buff *skb)
+{
+ irnet_socket * self = (irnet_socket *) instance;
+
+ DENTER(IRDA_TCB_TRACE, "(self=0x%X)\n", (unsigned int) self);
+
+ /* Check if socket is closing down (via irda_irnet_destroy()) */
+ if(! test_bit(0, &self->ttp_connect))
+ {
+ DERROR(IRDA_CB_ERROR, "Socket no longer connecting. Ouch !\n");
+ return;
+ }
+
+ /* How much header space do we need to reserve */
+ self->max_header_size = max_header_size;
+
+ /* IrTTP max SDU size in transmit direction */
+ self->max_sdu_size_tx = max_sdu_size;
+ self->max_data_size = max_sdu_size;
+#ifdef STREAM_COMPAT
+ if(max_sdu_size == 0)
+ self->max_data_size = irttp_get_max_seg_size(self->tsap);
+#endif /* STREAM_COMPAT */
+
+ /* At this point, IrLMP has assigned our source address */
+ self->saddr = irttp_get_saddr(self->tsap);
+
+ /* Allow higher layer to access IrTTP */
+ set_bit(0, &self->ttp_open);
+ clear_bit(0, &self->ttp_connect); /* Not racy, IrDA traffic is serial */
+ /* Give a kick in the ass of ppp_generic so that he sends us some data */
+ ppp_output_wakeup(&self->chan);
+
+ /* Check size of received packet */
+ if(skb->len > 0)
+ {
+#ifdef PASS_CONNECT_PACKETS
+ DEBUG(IRDA_CB_INFO, "Passing connect packet to PPP.\n");
+ /* Try to pass it to PPP */
+ irnet_data_indication(instance, sap, skb);
+#else /* PASS_CONNECT_PACKETS */
+ DERROR(IRDA_CB_ERROR, "Dropping non empty packet.\n");
+ kfree_skb(skb); /* Note : will be optimised with other kfree... */
+#endif /* PASS_CONNECT_PACKETS */
+ }
+ else
+ kfree_skb(skb);
+
+ /* Notify the control channel */
+ irnet_post_event(self, IRNET_CONNECT_TO,
+ self->saddr, self->daddr, self->rname);
+
+ DEXIT(IRDA_TCB_TRACE, "\n");
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_flow_indication (instance, sap, flow)
+ *
+ * Used by TinyTP to tell us if it can accept more data or not
+ *
+ */
+static void
+irnet_flow_indication(void * instance,
+ void * sap,
+ LOCAL_FLOW flow)
+{
+ irnet_socket * self = (irnet_socket *) instance;
+ LOCAL_FLOW oldflow = self->tx_flow;
+
+ DENTER(IRDA_TCB_TRACE, "(self=0x%X, flow=%d)\n", (unsigned int) self, flow);
+
+ /* Update our state */
+ self->tx_flow = flow;
+
+ /* Check what IrTTP want us to do... */
+ switch(flow)
+ {
+ case FLOW_START:
+ DEBUG(IRDA_CB_INFO, "IrTTP wants us to start again\n");
+ /* Check if we really need to wake up PPP */
+ if(oldflow == FLOW_STOP)
+ ppp_output_wakeup(&self->chan);
+ else
+ DEBUG(IRDA_CB_INFO, "But we were already transmitting !!!\n");
+ break;
+ case FLOW_STOP:
+ DEBUG(IRDA_CB_INFO, "IrTTP wants us to slow down\n");
+ break;
+ default:
+ DEBUG(IRDA_CB_INFO, "Unknown flow command!\n");
+ break;
+ }
+
+ DEXIT(IRDA_TCB_TRACE, "\n");
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_status_indication (instance, sap, reason, skb)
+ *
+ * Link (IrLAP) status report.
+ *
+ */
+static void
+irnet_status_indication(void * instance,
+ LINK_STATUS link,
+ LOCK_STATUS lock)
+{
+ irnet_socket * self = (irnet_socket *) instance;
+
+ DENTER(IRDA_TCB_TRACE, "(self=0x%X)\n", (unsigned int) self);
+ DASSERT(self != NULL, , IRDA_CB_ERROR, "Self is NULL !!!\n");
+
+ /* We can only get this event if we are connected */
+ switch(link)
+ {
+ case STATUS_NO_ACTIVITY:
+ irnet_post_event(self, IRNET_BLOCKED_LINK,
+ self->saddr, self->daddr, self->rname);
+ break;
+ default:
+ DEBUG(IRDA_CB_INFO, "Unknown status...\n");
+ }
+
+ DEXIT(IRDA_TCB_TRACE, "\n");
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_connect_indication(instance, sap, qos, max_sdu_size, userdata)
+ *
+ * Incoming connection
+ *
+ * In theory, this function is called only on the server socket.
+ * Some other node is attempting to connect to the IrNET service, and has
+ * sent a connection request on our server socket.
+ * We just redirect the connection to the relevant IrNET socket.
+ *
+ * Note : we also make sure that between 2 irnet nodes, there can
+ * exist only one irnet connection.
+ */
+static void
+irnet_connect_indication(void * instance,
+ void * sap,
+ struct qos_info *qos,
+ __u32 max_sdu_size,
+ __u8 max_header_size,
+ struct sk_buff *skb)
+{
+ irnet_socket * server = &irnet_server.s;
+ irnet_socket * new = (irnet_socket *) NULL;
+
+ DENTER(IRDA_TCB_TRACE, "(server=0x%X)\n", (unsigned int) server);
+ DASSERT(instance == &irnet_server, , IRDA_CB_ERROR,
+ "Invalid instance (0x%X) !!!\n", (unsigned int) instance);
+ DASSERT(sap == irnet_server.s.tsap, , IRDA_CB_ERROR, "Invalid sap !!!\n");
+
+ /* Try to find the most appropriate IrNET socket */
+ new = irnet_find_socket(server);
+
+ /* After all this hard work, do we have an socket ? */
+ if(new == (irnet_socket *) NULL)
+ {
+ DEXIT(IRDA_CB_INFO, ": No socket waiting for this connection.\n");
+ irnet_disconnect_server(server, skb);
+ return;
+ }
+
+ /* Is the socket already busy ? */
+ if(test_bit(0, &new->ttp_open))
+ {
+ DEXIT(IRDA_CB_INFO, ": Socket already connected.\n");
+ irnet_disconnect_server(server, skb);
+ return;
+ }
+
+ /* The following code is a bit tricky, so need comments ;-)
+ */
+ /* If ttp_connect is set, the socket is trying to connect to the other
+ * end and may have sent a IrTTP connection request and is waiting for
+ * a connection response (that may never come).
+ * Now, the pain is that the socket may have opened a tsap and is
+ * waiting on it, while the other end is trying to connect to it on
+ * another tsap.
+ * Because IrNET can be peer to peer, we need to workaround this.
+ * Furthermore, the way the irnetd script is implemented, the
+ * target will create a second IrNET connection back to the
+ * originator and expect the originator to bind this new connection
+ * to the original PPPD instance.
+ * And of course, if we don't use irnetd, we can have a race when
+ * both side try to connect simultaneously, which could leave both
+ * connections half closed (yuck).
+ * Conclusions :
+ * 1) The "originator" must accept the new connection and get rid
+ * of the old one so that irnetd works
+ * 2) One side must deny the new connection to avoid races,
+ * but both side must agree on which side it is...
+ * Most often, the originator is primary at the LAP layer.
+ * Jean II
+ */
+ /* Now, let's look at the way I wrote the test...
+ * We need to clear up the ttp_connect flag atomically to prevent
+ * irnet_disconnect_indication() to mess up the tsap we are going to close.
+ * We want to clear the ttp_connect flag only if we close the tsap,
+ * otherwise we will never close it, so we need to check for primary
+ * *before* doing the test on the flag.
+ * And of course, ALLOW_SIMULT_CONNECT can disable this entirely...
+ * Jean II
+ */
+
+ /* Socket already connecting ? On primary ? */
+ if(0
+#ifdef ALLOW_SIMULT_CONNECT
+ || ((irttp_is_primary(server->tsap) == 1) /* primary */
+ && (test_and_clear_bit(0, &new->ttp_connect)))
+#endif /* ALLOW_SIMULT_CONNECT */
+ )
+ {
+ DERROR(IRDA_CB_ERROR, "Socket already connecting, but going to reuse it !\n");
+
+ /* Cleanup the old TSAP if necessary - IrIAP will be cleaned up later */
+ if(new->tsap != NULL)
+ {
+ /* Close the old connection the new socket was attempting,
+ * so that we can hook it up to the new connection.
+ * It's now safe to do it... */
+ irttp_close_tsap(new->tsap);
+ new->tsap = NULL;
+ }
+ }
+ else
+ {
+ /* Three options :
+ * 1) socket was not connecting or connected : ttp_connect should be 0.
+ * 2) we don't want to connect the socket because we are secondary or
+ * ALLOW_SIMULT_CONNECT is undefined. ttp_connect should be 1.
+ * 3) we are half way in irnet_disconnect_indication(), and it's a
+ * nice race condition... Fortunately, we can detect that by checking
+ * if tsap is still alive. On the other hand, we can't be in
+ * irda_irnet_destroy() otherwise we would not have found this
+ * socket in the hashbin.
+ * Jean II */
+ if((test_bit(0, &new->ttp_connect)) || (new->tsap != NULL))
+ {
+ /* Don't mess this socket, somebody else in in charge... */
+ DERROR(IRDA_CB_ERROR, "Race condition detected, socket in use, abort connect...\n");
+ irnet_disconnect_server(server, skb);
+ return;
+ }
+ }
+
+ /* So : at this point, we have a socket, and it is idle. Good ! */
+ irnet_connect_socket(server, new, qos, max_sdu_size, max_header_size);
+
+ /* Check size of received packet */
+ if(skb->len > 0)
+ {
+#ifdef PASS_CONNECT_PACKETS
+ DEBUG(IRDA_CB_INFO, "Passing connect packet to PPP.\n");
+ /* Try to pass it to PPP */
+ irnet_data_indication(new, new->tsap, skb);
+#else /* PASS_CONNECT_PACKETS */
+ DERROR(IRDA_CB_ERROR, "Dropping non empty packet.\n");
+ kfree_skb(skb); /* Note : will be optimised with other kfree... */
+#endif /* PASS_CONNECT_PACKETS */
+ }
+ else
+ kfree_skb(skb);
+
+ DEXIT(IRDA_TCB_TRACE, "\n");
+}
+
+
+/********************** IRDA-IAS/LMP CALLBACKS **********************/
+/*
+ * These are the callbacks called by other layers of the IrDA stack,
+ * mainly LMP for discovery and IAS for name queries.
+ */
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_getvalue_confirm (result, obj_id, value, priv)
+ *
+ * Got answer from remote LM-IAS, just connect
+ *
+ * This is the reply to a IAS query we were doing to find the TSAP of
+ * the device we want to connect to.
+ * If we have found a valid TSAP, just initiate the TTP connection
+ * on this TSAP.
+ */
+static void
+irnet_getvalue_confirm(int result,
+ __u16 obj_id,
+ struct ias_value *value,
+ void * priv)
+{
+ irnet_socket * self = (irnet_socket *) priv;
+
+ DENTER(IRDA_OCB_TRACE, "(self=0x%X)\n", (unsigned int) self);
+ DASSERT(self != NULL, , IRDA_OCB_ERROR, "Self is NULL !!!\n");
+
+ /* Check if already connected (via irnet_connect_socket())
+ * or socket is closing down (via irda_irnet_destroy()) */
+ if(! test_bit(0, &self->ttp_connect))
+ {
+ DERROR(IRDA_OCB_ERROR, "Socket no longer connecting. Ouch !\n");
+ return;
+ }
+
+ /* We probably don't need to make any more queries */
+ iriap_close(self->iriap);
+ self->iriap = NULL;
+
+ /* Post process the IAS reply */
+ self->dtsap_sel = irnet_ias_to_tsap(self, result, value);
+
+ /* If error, just go out */
+ if(self->errno)
+ {
+ clear_bit(0, &self->ttp_connect);
+ DERROR(IRDA_OCB_ERROR, "IAS connect failed ! (0x%X)\n", self->errno);
+ return;
+ }
+
+ DEBUG(IRDA_OCB_INFO, "daddr = %08x, lsap = %d, starting IrTTP connection\n",
+ self->daddr, self->dtsap_sel);
+
+ /* Start up TTP - non blocking */
+ irnet_connect_tsap(self);
+
+ DEXIT(IRDA_OCB_TRACE, "\n");
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_discovervalue_confirm (result, obj_id, value, priv)
+ *
+ * Handle the TSAP discovery procedure state machine.
+ * Got answer from remote LM-IAS, try next device
+ *
+ * We are doing a TSAP discovery procedure, and we got an answer to
+ * a IAS query we were doing to find the TSAP on one of the address
+ * in the discovery log.
+ *
+ * If we have found a valid TSAP for the first time, save it. If it's
+ * not the first time we found one, complain.
+ *
+ * If we have more addresses in the log, just initiate a new query.
+ * Note that those query may fail (see irnet_discover_daddr_and_lsap_sel())
+ *
+ * Otherwise, wrap up the procedure (cleanup), check if we have found
+ * any device and connect to it.
+ */
+static void
+irnet_discovervalue_confirm(int result,
+ __u16 obj_id,
+ struct ias_value *value,
+ void * priv)
+{
+ irnet_socket * self = (irnet_socket *) priv;
+ __u8 dtsap_sel; /* TSAP we are looking for */
+
+ DENTER(IRDA_OCB_TRACE, "(self=0x%X)\n", (unsigned int) self);
+ DASSERT(self != NULL, , IRDA_OCB_ERROR, "Self is NULL !!!\n");
+
+ /* Check if already connected (via irnet_connect_socket())
+ * or socket is closing down (via irda_irnet_destroy()) */
+ if(! test_bit(0, &self->ttp_connect))
+ {
+ DERROR(IRDA_OCB_ERROR, "Socket no longer connecting. Ouch !\n");
+ return;
+ }
+
+ /* Post process the IAS reply */
+ dtsap_sel = irnet_ias_to_tsap(self, result, value);
+
+ /* Have we got something ? */
+ if(self->errno == 0)
+ {
+ /* We found the requested service */
+ if(self->daddr != DEV_ADDR_ANY)
+ {
+ DERROR(IRDA_OCB_ERROR, "More than one device in range supports IrNET...\n");
+ }
+ else
+ {
+ /* First time we found that one, save it ! */
+ self->daddr = self->discoveries[self->disco_index].daddr;
+ self->dtsap_sel = dtsap_sel;
+ }
+ }
+
+ /* If no failure */
+ if((self->errno == -EADDRNOTAVAIL) || (self->errno == 0))
+ {
+ int ret;
+
+ /* Search the next node */
+ ret = irnet_discover_next_daddr(self);
+ if(!ret)
+ {
+ /* In this case, the above request was non-blocking.
+ * We will return here after a while... */
+ return;
+ }
+ /* In this case, we have processed the last discovery item */
+ }
+
+ /* No more queries to be done (failure or last one) */
+
+ /* We probably don't need to make any more queries */
+ iriap_close(self->iriap);
+ self->iriap = NULL;
+
+ /* No more items : remove the log and signal termination */
+ DEBUG(IRDA_OCB_INFO, "Cleaning up log (0x%X)\n",
+ (unsigned int) self->discoveries);
+ if(self->discoveries != NULL)
+ {
+ /* Cleanup our copy of the discovery log */
+ kfree(self->discoveries);
+ self->discoveries = NULL;
+ }
+ self->disco_number = -1;
+
+ /* Check out what we found */
+ if(self->daddr == DEV_ADDR_ANY)
+ {
+ self->daddr = DEV_ADDR_ANY;
+ clear_bit(0, &self->ttp_connect);
+ DEXIT(IRDA_OCB_TRACE, ": cannot discover IrNET in any device !!!\n");
+ return;
+ }
+
+ /* We have a valid address - just connect */
+
+ DEBUG(IRDA_OCB_INFO, "daddr = %08x, lsap = %d, starting IrTTP connection\n",
+ self->daddr, self->dtsap_sel);
+
+ /* Start up TTP - non blocking */
+ irnet_connect_tsap(self);
+
+ DEXIT(IRDA_OCB_TRACE, "\n");
+}
+
+#ifdef DISCOVERY_EVENTS
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_discovery_indication (discovery)
+ *
+ * Got a discovery indication from IrLMP, post an event
+ *
+ * Note : IrLMP take care of matching the hint mask for us, we only
+ * check if it is a "new" node...
+ *
+ * As IrLMP filter on the IrLAN hint bit, we get both IrLAN and IrNET
+ * nodes, so it's only at connection time that we will know if the
+ * node support IrNET, IrLAN or both. The other solution is to check
+ * in IAS the PNP ids and service name.
+ * Note : even if a node support IrNET (or IrLAN), it's no guarantee
+ * that we will be able to connect to it, the node might already be
+ * busy...
+ *
+ * One last thing : in some case, this function will trigger duplicate
+ * discovery events. On the other hand, we should catch all
+ * discoveries properly (i.e. not miss one). Filtering duplicate here
+ * is to messy, so we leave that to user space...
+ */
+static void
+irnet_discovery_indication(discovery_t * discovery,
+ DISCOVERY_MODE mode,
+ void * priv)
+{
+ irnet_socket * self = &irnet_server.s;
+
+ DENTER(IRDA_OCB_TRACE, "(self=0x%X)\n", (unsigned int) self);
+ DASSERT(priv == &irnet_server, , IRDA_OCB_ERROR,
+ "Invalid instance (0x%X) !!!\n", (unsigned int) priv);
+
+ /* Check if node is discovered is a new one or an old one.
+ * We check when how long ago this node was discovered, with a
+ * coarse timeout (we may miss some discovery events or be delayed).
+ */
+ if((jiffies - discovery->first_timestamp) >= (sysctl_discovery_timeout * HZ))
+ {
+ return; /* Too old, not interesting -> goodbye */
+ }
+
+ DEBUG(IRDA_OCB_INFO, "Discovered new IrNET/IrLAN node %s...\n",
+ discovery->nickname);
+
+ /* Notify the control channel */
+ irnet_post_event(NULL, IRNET_DISCOVER,
+ discovery->saddr, discovery->daddr, discovery->nickname);
+
+ DEXIT(IRDA_OCB_TRACE, "\n");
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_expiry_indication (expiry)
+ *
+ * Got a expiry indication from IrLMP, post an event
+ *
+ * Note : IrLMP take care of matching the hint mask for us, we only
+ * check if it is a "new" node...
+ */
+static void
+irnet_expiry_indication(discovery_t * expiry,
+ DISCOVERY_MODE mode,
+ void * priv)
+{
+ irnet_socket * self = &irnet_server.s;
+
+ DENTER(IRDA_OCB_TRACE, "(self=0x%X)\n", (unsigned int) self);
+ DASSERT(priv == &irnet_server, , IRDA_OCB_ERROR,
+ "Invalid instance (0x%X) !!!\n", (unsigned int) priv);
+
+ DEBUG(IRDA_OCB_INFO, "IrNET/IrLAN node %s expired...\n",
+ expiry->nickname);
+
+ /* Notify the control channel */
+ irnet_post_event(NULL, IRNET_EXPIRE,
+ expiry->saddr, expiry->daddr, expiry->nickname);
+
+ DEXIT(IRDA_OCB_TRACE, "\n");
+}
+#endif /* DISCOVERY_EVENTS */
+
+
+/*********************** PROC ENTRY CALLBACKS ***********************/
+/*
+ * We create a instance in the /proc filesystem, and here we take care
+ * of that...
+ */
+
+#ifdef CONFIG_PROC_FS
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_proc_read (buf, start, offset, len, unused)
+ *
+ * Give some info to the /proc file system
+ */
+static int
+irnet_proc_read(char * buf,
+ char ** start,
+ off_t offset,
+ int len)
+{
+ irnet_socket * self;
+ char * state;
+ unsigned long flags;
+ int i = 0;
+
+ len = 0;
+
+ /* Get the IrNET server information... */
+ len += sprintf(buf+len, "IrNET server - ");
+ len += sprintf(buf+len, "IrDA state: %s, ",
+ (irnet_server.running ? "running" : "dead"));
+ len += sprintf(buf+len, "stsap_sel: %02x, ", irnet_server.s.stsap_sel);
+ len += sprintf(buf+len, "dtsap_sel: %02x\n", irnet_server.s.dtsap_sel);
+
+ /* Do we need to continue ? */
+ if(!irnet_server.running)
+ return len;
+
+ /* Protect access to the instance list */
+ spin_lock_irqsave(&irnet_server.spinlock, flags);
+
+ /* Get the sockets one by one... */
+ self = (irnet_socket *) hashbin_get_first(irnet_server.list);
+ while(self != NULL)
+ {
+ /* Start printing info about the socket. */
+ len += sprintf(buf+len, "\nIrNET socket %d - ", i++);
+
+ /* First, get the requested configuration */
+ len += sprintf(buf+len, "Requested IrDA name: \"%s\", ", self->rname);
+ len += sprintf(buf+len, "daddr: %08x, ", self->rdaddr);
+ len += sprintf(buf+len, "saddr: %08x\n", self->rsaddr);
+
+ /* Second, get all the PPP info */
+ len += sprintf(buf+len, " PPP state: %s",
+ (self->ppp_open ? "registered" : "unregistered"));
+ if(self->ppp_open)
+ {
+ len += sprintf(buf+len, ", unit: ppp%d",
+ ppp_unit_number(&self->chan));
+ len += sprintf(buf+len, ", channel: %d",
+ ppp_channel_index(&self->chan));
+ len += sprintf(buf+len, ", mru: %d",
+ self->mru);
+ /* Maybe add self->flags ? Later... */
+ }
+
+ /* Then, get all the IrDA specific info... */
+ if(self->ttp_open)
+ state = "connected";
+ else
+ if(self->tsap != NULL)
+ state = "connecting";
+ else
+ if(self->iriap != NULL)
+ state = "searching";
+ else
+ if(self->ttp_connect)
+ state = "weird";
+ else
+ state = "idle";
+ len += sprintf(buf+len, "\n IrDA state: %s, ", state);
+ len += sprintf(buf+len, "daddr: %08x, ", self->daddr);
+ len += sprintf(buf+len, "stsap_sel: %02x, ", self->stsap_sel);
+ len += sprintf(buf+len, "dtsap_sel: %02x\n", self->dtsap_sel);
+
+ /* Next socket, please... */
+ self = (irnet_socket *) hashbin_get_next(irnet_server.list);
+ }
+
+ /* Spin lock end */
+ spin_unlock_irqrestore(&irnet_server.spinlock, flags);
+
+ return len;
+}
+#endif /* PROC_FS */
+
+
+/********************** CONFIGURATION/CLEANUP **********************/
+/*
+ * Initialisation and teardown of the IrDA part, called at module
+ * insertion and removal...
+ */
+
+/*------------------------------------------------------------------*/
+/*
+ * Prepare the IrNET layer for operation...
+ */
+int
+irda_irnet_init(void)
+{
+ int err = 0;
+
+ DENTER(MODULE_TRACE, "()\n");
+
+ /* Pure paranoia - should be redundant */
+ memset(&irnet_server, 0, sizeof(struct irnet_root));
+
+ /* Setup start of irnet instance list */
+ irnet_server.list = hashbin_new(HB_NOLOCK);
+ DABORT(irnet_server.list == NULL, -ENOMEM,
+ MODULE_ERROR, "Can't allocate hashbin!\n");
+ /* Init spinlock for instance list */
+ spin_lock_init(&irnet_server.spinlock);
+
+ /* Initialise control channel */
+ init_waitqueue_head(&irnet_events.rwait);
+ irnet_events.index = 0;
+ /* Init spinlock for event logging */
+ spin_lock_init(&irnet_events.spinlock);
+
+#ifdef CONFIG_PROC_FS
+ /* Add a /proc file for irnet infos */
+ create_proc_info_entry("irnet", 0, proc_irda, irnet_proc_read);
+#endif /* CONFIG_PROC_FS */
+
+ /* Setup the IrNET server */
+ err = irnet_setup_server();
+
+ if(!err)
+ /* We are no longer functional... */
+ irnet_server.running = 1;
+
+ DEXIT(MODULE_TRACE, "\n");
+ return err;
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Cleanup at exit...
+ */
+void
+irda_irnet_cleanup(void)
+{
+ DENTER(MODULE_TRACE, "()\n");
+
+ /* We are no longer there... */
+ irnet_server.running = 0;
+
+#ifdef CONFIG_PROC_FS
+ /* Remove our /proc file */
+ remove_proc_entry("irnet", proc_irda);
+#endif /* CONFIG_PROC_FS */
+
+ /* Remove our IrNET server from existence */
+ irnet_destroy_server();
+
+ /* Remove all instances of IrNET socket still present */
+ hashbin_delete(irnet_server.list, (FREE_FUNC) irda_irnet_destroy);
+
+ DEXIT(MODULE_TRACE, "\n");
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/irnet/irnet_irda.h b/uClinux-2.4.31-uc0/net/irda/irnet/irnet_irda.h
new file mode 100644
index 0000000..2ad5cc0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irnet/irnet_irda.h
@@ -0,0 +1,185 @@
+/*
+ * IrNET protocol module : Synchronous PPP over an IrDA socket.
+ *
+ * Jean II - HPL `00 - <jt@hpl.hp.com>
+ *
+ * This file contains all definitions and declarations necessary for the
+ * IRDA part of the IrNET module (dealing with IrTTP, IrIAS and co).
+ * This file is a private header, so other modules don't want to know
+ * what's in there...
+ */
+
+#ifndef IRNET_IRDA_H
+#define IRNET_IRDA_H
+
+/***************************** INCLUDES *****************************/
+/* Please add other headers in irnet.h */
+
+#include "irnet.h" /* Module global include */
+
+/************************ CONSTANTS & MACROS ************************/
+
+/*
+ * Name of the service (socket name) used by IrNET
+ */
+/* IAS object name (or part of it) */
+#define IRNET_SERVICE_NAME "IrNetv1"
+/* IAS attribute */
+#define IRNET_IAS_VALUE "IrDA:TinyTP:LsapSel"
+/* LMP notify name for client (only for /proc/net/irda/irlmp) */
+#define IRNET_NOTIFY_NAME "IrNET socket"
+/* LMP notify name for server (only for /proc/net/irda/irlmp) */
+#define IRNET_NOTIFY_NAME_SERV "IrNET server"
+
+/****************************** TYPES ******************************/
+
+/*
+ * This is the main structure where we store all the data pertaining to
+ * the IrNET server (listen for connection requests) and the root
+ * of the IrNET socket list
+ */
+typedef struct irnet_root
+{
+ irnet_socket s; /* To pretend we are a client... */
+
+ /* Generic stuff */
+ int magic; /* Paranoia */
+ int running; /* Are we operational ? */
+
+ /* Link list of all IrNET instances opened */
+ hashbin_t * list;
+ spinlock_t spinlock; /* Serialize access to the list */
+ /* Note : the way hashbin has been designed is absolutely not
+ * reentrant, beware... So, we blindly protect all with spinlock */
+
+ /* Handle for the hint bit advertised in IrLMP */
+ __u32 skey;
+
+ /* Server socket part */
+ struct ias_object * ias_obj; /* Our service name + lsap in IAS */
+
+} irnet_root;
+
+
+/**************************** PROTOTYPES ****************************/
+
+/* ----------------------- CONTROL CHANNEL ----------------------- */
+static void
+ irnet_post_event(irnet_socket *,
+ irnet_event,
+ __u32,
+ __u32,
+ char *);
+/* ----------------------- IRDA SUBROUTINES ----------------------- */
+static inline int
+ irnet_open_tsap(irnet_socket *);
+static inline __u8
+ irnet_ias_to_tsap(irnet_socket *,
+ int,
+ struct ias_value *);
+static inline int
+ irnet_find_lsap_sel(irnet_socket *);
+static inline int
+ irnet_connect_tsap(irnet_socket *);
+static inline int
+ irnet_discover_next_daddr(irnet_socket *);
+static inline int
+ irnet_discover_daddr_and_lsap_sel(irnet_socket *);
+static inline int
+ irnet_dname_to_daddr(irnet_socket *);
+/* ------------------------ SERVER SOCKET ------------------------ */
+static inline int
+ irnet_daddr_to_dname(irnet_socket *);
+static inline irnet_socket *
+ irnet_find_socket(irnet_socket *);
+static inline int
+ irnet_connect_socket(irnet_socket *,
+ irnet_socket *,
+ struct qos_info *,
+ __u32,
+ __u8);
+static inline void
+ irnet_disconnect_server(irnet_socket *,
+ struct sk_buff *);
+static inline int
+ irnet_setup_server(void);
+static inline void
+ irnet_destroy_server(void);
+/* ---------------------- IRDA-TTP CALLBACKS ---------------------- */
+static int
+ irnet_data_indication(void *, /* instance */
+ void *, /* sap */
+ struct sk_buff *);
+static void
+ irnet_disconnect_indication(void *,
+ void *,
+ LM_REASON,
+ struct sk_buff *);
+static void
+ irnet_connect_confirm(void *,
+ void *,
+ struct qos_info *,
+ __u32,
+ __u8,
+ struct sk_buff *);
+static void
+ irnet_flow_indication(void *,
+ void *,
+ LOCAL_FLOW);
+static void
+ irnet_status_indication(void *,
+ LINK_STATUS,
+ LOCK_STATUS);
+static void
+ irnet_connect_indication(void *,
+ void *,
+ struct qos_info *,
+ __u32,
+ __u8,
+ struct sk_buff *);
+/* -------------------- IRDA-IAS/LMP CALLBACKS -------------------- */
+static void
+ irnet_getvalue_confirm(int,
+ __u16,
+ struct ias_value *,
+ void *);
+static void
+ irnet_discovervalue_confirm(int,
+ __u16,
+ struct ias_value *,
+ void *);
+#ifdef DISCOVERY_EVENTS
+static void
+ irnet_discovery_indication(discovery_t *,
+ DISCOVERY_MODE,
+ void *);
+static void
+ irnet_expiry_indication(discovery_t *,
+ DISCOVERY_MODE,
+ void *);
+#endif
+/* -------------------------- PROC ENTRY -------------------------- */
+#ifdef CONFIG_PROC_FS
+static int
+ irnet_proc_read(char *,
+ char **,
+ off_t,
+ int);
+#endif /* CONFIG_PROC_FS */
+
+/**************************** VARIABLES ****************************/
+
+/*
+ * The IrNET server. Listen to connection requests and co...
+ */
+static struct irnet_root irnet_server;
+
+/* Control channel stuff (note : extern) */
+struct irnet_ctrl_channel irnet_events;
+
+/* The /proc/net/irda directory, defined elsewhere... */
+#ifdef CONFIG_PROC_FS
+extern struct proc_dir_entry *proc_irda;
+#endif /* CONFIG_PROC_FS */
+
+#endif /* IRNET_IRDA_H */
diff --git a/uClinux-2.4.31-uc0/net/irda/irnet/irnet_ppp.c b/uClinux-2.4.31-uc0/net/irda/irnet/irnet_ppp.c
new file mode 100644
index 0000000..c310000
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irnet/irnet_ppp.c
@@ -0,0 +1,1100 @@
+/*
+ * IrNET protocol module : Synchronous PPP over an IrDA socket.
+ *
+ * Jean II - HPL `00 - <jt@hpl.hp.com>
+ *
+ * This file implement the PPP interface and /dev/irnet character device.
+ * The PPP interface hook to the ppp_generic module, handle all our
+ * relationship to the PPP code in the kernel (and by extension to pppd),
+ * and exchange PPP frames with this module (send/receive).
+ * The /dev/irnet device is used primarily for 2 functions :
+ * 1) as a stub for pppd (the ppp daemon), so that we can appropriately
+ * generate PPP sessions (we pretend we are a tty).
+ * 2) as a control channel (write commands, read events)
+ */
+
+#include "irnet_ppp.h" /* Private header */
+/* Please put other headers in irnet.h - Thanks */
+
+/************************* CONTROL CHANNEL *************************/
+/*
+ * When a pppd instance is not active on /dev/irnet, it acts as a control
+ * channel.
+ * Writting allow to set up the IrDA destination of the IrNET channel,
+ * and any application may be read events happening in IrNET...
+ */
+
+/*------------------------------------------------------------------*/
+/*
+ * Write is used to send a command to configure a IrNET channel
+ * before it is open by pppd. The syntax is : "command argument"
+ * Currently there is only two defined commands :
+ * o name : set the requested IrDA nickname of the IrNET peer.
+ * o addr : set the requested IrDA address of the IrNET peer.
+ * Note : the code is crude, but effective...
+ */
+static inline ssize_t
+irnet_ctrl_write(irnet_socket * ap,
+ const char * buf,
+ size_t count)
+{
+ char command[IRNET_MAX_COMMAND];
+ char * start; /* Current command beeing processed */
+ char * next; /* Next command to process */
+ int length; /* Length of current command */
+
+ DENTER(CTRL_TRACE, "(ap=0x%X, count=%d)\n", (unsigned int) ap, count);
+
+ /* Check for overflow... */
+ DABORT(count >= IRNET_MAX_COMMAND, -ENOMEM,
+ CTRL_ERROR, "Too much data !!!\n");
+
+ /* Get the data in the driver */
+ if(copy_from_user(command, buf, count))
+ {
+ DERROR(CTRL_ERROR, "Invalid user space pointer.\n");
+ return -EFAULT;
+ }
+
+ /* Safe terminate the string */
+ command[count] = '\0';
+ DEBUG(CTRL_INFO, "Command line received is ``%s'' (%d).\n",
+ command, count);
+
+ /* Check every commands in the command line */
+ next = command;
+ while(next != NULL)
+ {
+ /* Look at the next command */
+ start = next;
+
+ /* Scrap whitespaces before the command */
+ while(isspace(*start))
+ start++;
+
+ /* ',' is our command separator */
+ next = strchr(start, ',');
+ if(next)
+ {
+ *next = '\0'; /* Terminate command */
+ length = next - start; /* Length */
+ next++; /* Skip the '\0' */
+ }
+ else
+ length = strlen(start);
+
+ DEBUG(CTRL_INFO, "Found command ``%s'' (%d).\n", start, length);
+
+ /* Check if we recognised one of the known command
+ * We can't use "switch" with strings, so hack with "continue" */
+
+ /* First command : name -> Requested IrDA nickname */
+ if(!strncmp(start, "name", 4))
+ {
+ /* Copy the name only if is included and not "any" */
+ if((length > 5) && (strcmp(start + 5, "any")))
+ {
+ /* Strip out trailing whitespaces */
+ while(isspace(start[length - 1]))
+ length--;
+
+ /* Copy the name for later reuse */
+ memcpy(ap->rname, start + 5, length - 5);
+ ap->rname[length - 5] = '\0';
+ }
+ else
+ ap->rname[0] = '\0';
+ DEBUG(CTRL_INFO, "Got rname = ``%s''\n", ap->rname);
+
+ /* Restart the loop */
+ continue;
+ }
+
+ /* Second command : addr, daddr -> Requested IrDA destination address
+ * Also process : saddr -> Requested IrDA source address */
+ if((!strncmp(start, "addr", 4)) ||
+ (!strncmp(start, "daddr", 5)) ||
+ (!strncmp(start, "saddr", 5)))
+ {
+ __u32 addr = DEV_ADDR_ANY;
+
+ /* Copy the address only if is included and not "any" */
+ if((length > 5) && (strcmp(start + 5, "any")))
+ {
+ char * begp = start + 5;
+ char * endp;
+
+ /* Scrap whitespaces before the command */
+ while(isspace(*begp))
+ begp++;
+
+ /* Convert argument to a number (last arg is the base) */
+ addr = simple_strtoul(begp, &endp, 16);
+ /* Has it worked ? (endp should be start + length) */
+ DABORT(endp <= (start + 5), -EINVAL,
+ CTRL_ERROR, "Invalid address.\n");
+ }
+ /* Which type of address ? */
+ if(start[0] == 's')
+ {
+ /* Save it */
+ ap->rsaddr = addr;
+ DEBUG(CTRL_INFO, "Got rsaddr = %08x\n", ap->rsaddr);
+ }
+ else
+ {
+ /* Save it */
+ ap->rdaddr = addr;
+ DEBUG(CTRL_INFO, "Got rdaddr = %08x\n", ap->rdaddr);
+ }
+
+ /* Restart the loop */
+ continue;
+ }
+
+ /* Other possible command : connect N (number of retries) */
+
+ /* No command matched -> Failed... */
+ DABORT(1, -EINVAL, CTRL_ERROR, "Not a recognised IrNET command.\n");
+ }
+
+ /* Success : we have parsed all commands successfully */
+ return(count);
+}
+
+#ifdef INITIAL_DISCOVERY
+/*------------------------------------------------------------------*/
+/*
+ * Function irnet_read_discovery_log (self)
+ *
+ * Read the content on the discovery log
+ *
+ * This function dump the current content of the discovery log
+ * at the startup of the event channel.
+ * Return 1 if written on the control channel...
+ *
+ * State of the ap->disco_XXX variables :
+ * at socket creation : disco_index = 0 ; disco_number = 0
+ * while reading : disco_index = X ; disco_number = Y
+ * After reading : disco_index = Y ; disco_number = -1
+ */
+static inline int
+irnet_read_discovery_log(irnet_socket * ap,
+ char * event)
+{
+ int done_event = 0;
+
+ DENTER(CTRL_TRACE, "(ap=0x%X, event=0x%X)\n",
+ (unsigned int) ap, (unsigned int) event);
+
+ /* Test if we have some work to do or we have already finished */
+ if(ap->disco_number == -1)
+ {
+ DEBUG(CTRL_INFO, "Already done\n");
+ return 0;
+ }
+
+ /* Test if it's the first time and therefore we need to get the log */
+ if(ap->disco_index == 0)
+ {
+ __u16 mask = irlmp_service_to_hint(S_LAN);
+
+ /* Ask IrLMP for the current discovery log */
+ ap->discoveries = irlmp_get_discoveries(&ap->disco_number, mask,
+ DISCOVERY_DEFAULT_SLOTS);
+ /* Check if the we got some results */
+ if(ap->discoveries == NULL)
+ ap->disco_number = -1;
+ DEBUG(CTRL_INFO, "Got the log (0x%X), size is %d\n",
+ (unsigned int) ap->discoveries, ap->disco_number);
+ }
+
+ /* Check if we have more item to dump */
+ if(ap->disco_index < ap->disco_number)
+ {
+ /* Write an event */
+ sprintf(event, "Found %08x (%s) behind %08x\n",
+ ap->discoveries[ap->disco_index].daddr,
+ ap->discoveries[ap->disco_index].info,
+ ap->discoveries[ap->disco_index].saddr);
+ DEBUG(CTRL_INFO, "Writing discovery %d : %s\n",
+ ap->disco_index, ap->discoveries[ap->disco_index].info);
+
+ /* We have an event */
+ done_event = 1;
+ /* Next discovery */
+ ap->disco_index++;
+ }
+
+ /* Check if we have done the last item */
+ if(ap->disco_index >= ap->disco_number)
+ {
+ /* No more items : remove the log and signal termination */
+ DEBUG(CTRL_INFO, "Cleaning up log (0x%X)\n",
+ (unsigned int) ap->discoveries);
+ if(ap->discoveries != NULL)
+ {
+ /* Cleanup our copy of the discovery log */
+ kfree(ap->discoveries);
+ ap->discoveries = NULL;
+ }
+ ap->disco_number = -1;
+ }
+
+ return done_event;
+}
+#endif /* INITIAL_DISCOVERY */
+
+/*------------------------------------------------------------------*/
+/*
+ * Read is used to get IrNET events
+ */
+static inline ssize_t
+irnet_ctrl_read(irnet_socket * ap,
+ struct file * file,
+ char * buf,
+ size_t count)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ char event[64]; /* Max event is 61 char */
+ ssize_t ret = 0;
+
+ DENTER(CTRL_TRACE, "(ap=0x%X, count=%d)\n", (unsigned int) ap, count);
+
+ /* Check if we can write an event out in one go */
+ DABORT(count < sizeof(event), -EOVERFLOW, CTRL_ERROR, "Buffer to small.\n");
+
+#ifdef INITIAL_DISCOVERY
+ /* Check if we have read the log */
+ if(irnet_read_discovery_log(ap, event))
+ {
+ /* We have an event !!! Copy it to the user */
+ if(copy_to_user(buf, event, strlen(event)))
+ {
+ DERROR(CTRL_ERROR, "Invalid user space pointer.\n");
+ return -EFAULT;
+ }
+
+ DEXIT(CTRL_TRACE, "\n");
+ return(strlen(event));
+ }
+#endif /* INITIAL_DISCOVERY */
+
+ /* Put ourselves on the wait queue to be woken up */
+ add_wait_queue(&irnet_events.rwait, &wait);
+ current->state = TASK_INTERRUPTIBLE;
+ for(;;)
+ {
+ /* If there is unread events */
+ ret = 0;
+ if(ap->event_index != irnet_events.index)
+ break;
+ ret = -EAGAIN;
+ if(file->f_flags & O_NONBLOCK)
+ break;
+ ret = -ERESTARTSYS;
+ if(signal_pending(current))
+ break;
+ /* Yield and wait to be woken up */
+ schedule();
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(&irnet_events.rwait, &wait);
+
+ /* Did we got it ? */
+ if(ret != 0)
+ {
+ /* No, return the error code */
+ DEXIT(CTRL_TRACE, " - ret %d\n", ret);
+ return ret;
+ }
+
+ /* Which event is it ? */
+ switch(irnet_events.log[ap->event_index].event)
+ {
+ case IRNET_DISCOVER:
+ sprintf(event, "Discovered %08x (%s) behind %08x\n",
+ irnet_events.log[ap->event_index].daddr,
+ irnet_events.log[ap->event_index].name,
+ irnet_events.log[ap->event_index].saddr);
+ break;
+ case IRNET_EXPIRE:
+ sprintf(event, "Expired %08x (%s) behind %08x\n",
+ irnet_events.log[ap->event_index].daddr,
+ irnet_events.log[ap->event_index].name,
+ irnet_events.log[ap->event_index].saddr);
+ break;
+ case IRNET_CONNECT_TO:
+ sprintf(event, "Connected to %08x (%s) on ppp%d\n",
+ irnet_events.log[ap->event_index].daddr,
+ irnet_events.log[ap->event_index].name,
+ irnet_events.log[ap->event_index].unit);
+ break;
+ case IRNET_CONNECT_FROM:
+ sprintf(event, "Connection from %08x (%s) on ppp%d\n",
+ irnet_events.log[ap->event_index].daddr,
+ irnet_events.log[ap->event_index].name,
+ irnet_events.log[ap->event_index].unit);
+ break;
+ case IRNET_REQUEST_FROM:
+ sprintf(event, "Request from %08x (%s) behind %08x\n",
+ irnet_events.log[ap->event_index].daddr,
+ irnet_events.log[ap->event_index].name,
+ irnet_events.log[ap->event_index].saddr);
+ break;
+ case IRNET_NOANSWER_FROM:
+ sprintf(event, "No-answer from %08x (%s) on ppp%d\n",
+ irnet_events.log[ap->event_index].daddr,
+ irnet_events.log[ap->event_index].name,
+ irnet_events.log[ap->event_index].unit);
+ break;
+ case IRNET_BLOCKED_LINK:
+ sprintf(event, "Blocked link with %08x (%s) on ppp%d\n",
+ irnet_events.log[ap->event_index].daddr,
+ irnet_events.log[ap->event_index].name,
+ irnet_events.log[ap->event_index].unit);
+ break;
+ case IRNET_DISCONNECT_FROM:
+ sprintf(event, "Disconnection from %08x (%s) on ppp%d\n",
+ irnet_events.log[ap->event_index].daddr,
+ irnet_events.log[ap->event_index].name,
+ irnet_events.log[ap->event_index].unit);
+ break;
+ case IRNET_DISCONNECT_TO:
+ sprintf(event, "Disconnected to %08x (%s)\n",
+ irnet_events.log[ap->event_index].daddr,
+ irnet_events.log[ap->event_index].name);
+ break;
+ default:
+ sprintf(event, "Bug\n");
+ }
+ /* Increment our event index */
+ ap->event_index = (ap->event_index + 1) % IRNET_MAX_EVENTS;
+
+ DEBUG(CTRL_INFO, "Event is :%s", event);
+
+ /* Copy it to the user */
+ if(copy_to_user(buf, event, strlen(event)))
+ {
+ DERROR(CTRL_ERROR, "Invalid user space pointer.\n");
+ return -EFAULT;
+ }
+
+ DEXIT(CTRL_TRACE, "\n");
+ return(strlen(event));
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Poll : called when someone do a select on /dev/irnet.
+ * Just check if there are new events...
+ */
+static inline unsigned int
+irnet_ctrl_poll(irnet_socket * ap,
+ struct file * file,
+ poll_table * wait)
+{
+ unsigned int mask;
+
+ DENTER(CTRL_TRACE, "(ap=0x%X)\n", (unsigned int) ap);
+
+ poll_wait(file, &irnet_events.rwait, wait);
+ mask = POLLOUT | POLLWRNORM;
+ /* If there is unread events */
+ if(ap->event_index != irnet_events.index)
+ mask |= POLLIN | POLLRDNORM;
+#ifdef INITIAL_DISCOVERY
+ if(ap->disco_number != -1)
+ mask |= POLLIN | POLLRDNORM;
+#endif /* INITIAL_DISCOVERY */
+
+ DEXIT(CTRL_TRACE, " - mask=0x%X\n", mask);
+ return mask;
+}
+
+
+/*********************** FILESYSTEM CALLBACKS ***********************/
+/*
+ * Implement the usual open, read, write functions that will be called
+ * by the file system when some action is performed on /dev/irnet.
+ * Most of those actions will in fact be performed by "pppd" or
+ * the control channel, we just act as a redirector...
+ */
+
+/*------------------------------------------------------------------*/
+/*
+ * Open : when somebody open /dev/irnet
+ * We basically create a new instance of irnet and initialise it.
+ */
+static int
+dev_irnet_open(struct inode * inode,
+ struct file * file)
+{
+ struct irnet_socket * ap;
+ int err;
+
+ DENTER(FS_TRACE, "(file=0x%X)\n", (unsigned int) file);
+
+#ifdef SECURE_DEVIRNET
+ /* This could (should?) be enforced by the permissions on /dev/irnet. */
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+#endif /* SECURE_DEVIRNET */
+
+ /* Allocate a private structure for this IrNET instance */
+ ap = kmalloc(sizeof(*ap), GFP_KERNEL);
+ DABORT(ap == NULL, -ENOMEM, FS_ERROR, "Can't allocate struct irnet...\n");
+
+ MOD_INC_USE_COUNT;
+
+ /* initialize the irnet structure */
+ memset(ap, 0, sizeof(*ap));
+ ap->file = file;
+
+ /* PPP channel setup */
+ ap->ppp_open = 0;
+ ap->chan.private = ap;
+ ap->chan.ops = &irnet_ppp_ops;
+ ap->chan.mtu = (2048 - TTP_MAX_HEADER - 2 - PPP_HDRLEN);
+ ap->chan.hdrlen = 2 + TTP_MAX_HEADER; /* for A/C + Max IrDA hdr */
+ /* PPP parameters */
+ ap->mru = (2048 - TTP_MAX_HEADER - 2 - PPP_HDRLEN);
+ ap->xaccm[0] = ~0U;
+ ap->xaccm[3] = 0x60000000U;
+ ap->raccm = ~0U;
+
+ /* Setup the IrDA part... */
+ err = irda_irnet_create(ap);
+ if(err)
+ {
+ DERROR(FS_ERROR, "Can't setup IrDA link...\n");
+ kfree(ap);
+ MOD_DEC_USE_COUNT;
+ return err;
+ }
+
+ /* For the control channel */
+ ap->event_index = irnet_events.index; /* Cancel all past events */
+
+ /* Put our stuff where we will be able to find it later */
+ file->private_data = ap;
+
+ DEXIT(FS_TRACE, " - ap=0x%X\n", (unsigned int) ap);
+ return 0;
+}
+
+
+/*------------------------------------------------------------------*/
+/*
+ * Close : when somebody close /dev/irnet
+ * Destroy the instance of /dev/irnet
+ */
+static int
+dev_irnet_close(struct inode * inode,
+ struct file * file)
+{
+ irnet_socket * ap = (struct irnet_socket *) file->private_data;
+
+ DENTER(FS_TRACE, "(file=0x%X, ap=0x%X)\n",
+ (unsigned int) file, (unsigned int) ap);
+ DABORT(ap == NULL, 0, FS_ERROR, "ap is NULL !!!\n");
+
+ /* Detach ourselves */
+ file->private_data = NULL;
+
+ /* Close IrDA stuff */
+ irda_irnet_destroy(ap);
+
+ /* Disconnect from the generic PPP layer if not already done */
+ if(ap->ppp_open)
+ {
+ DERROR(FS_ERROR, "Channel still registered - deregistering !\n");
+ ppp_unregister_channel(&ap->chan);
+ ap->ppp_open = 0;
+ }
+
+ kfree(ap);
+ MOD_DEC_USE_COUNT;
+
+ DEXIT(FS_TRACE, "\n");
+ return 0;
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Write does nothing.
+ * (we receive packet from ppp_generic through ppp_irnet_send())
+ */
+static ssize_t
+dev_irnet_write(struct file * file,
+ const char * buf,
+ size_t count,
+ loff_t * ppos)
+{
+ irnet_socket * ap = (struct irnet_socket *) file->private_data;
+
+ DPASS(FS_TRACE, "(file=0x%X, ap=0x%X, count=%d)\n",
+ (unsigned int) file, (unsigned int) ap, count);
+ DABORT(ap == NULL, -ENXIO, FS_ERROR, "ap is NULL !!!\n");
+
+ /* If we are connected to ppp_generic, let it handle the job */
+ if(ap->ppp_open)
+ return -EAGAIN;
+ else
+ return irnet_ctrl_write(ap, buf, count);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Read doesn't do much either.
+ * (pppd poll us, but ultimately reads through /dev/ppp)
+ */
+static ssize_t
+dev_irnet_read(struct file * file,
+ char * buf,
+ size_t count,
+ loff_t * ppos)
+{
+ irnet_socket * ap = (struct irnet_socket *) file->private_data;
+
+ DPASS(FS_TRACE, "(file=0x%X, ap=0x%X, count=%d)\n",
+ (unsigned int) file, (unsigned int) ap, count);
+ DABORT(ap == NULL, -ENXIO, FS_ERROR, "ap is NULL !!!\n");
+
+ /* If we are connected to ppp_generic, let it handle the job */
+ if(ap->ppp_open)
+ return -EAGAIN;
+ else
+ return irnet_ctrl_read(ap, file, buf, count);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Poll : called when someone do a select on /dev/irnet
+ */
+static unsigned int
+dev_irnet_poll(struct file * file,
+ poll_table * wait)
+{
+ irnet_socket * ap = (struct irnet_socket *) file->private_data;
+ unsigned int mask;
+
+ DENTER(FS_TRACE, "(file=0x%X, ap=0x%X)\n",
+ (unsigned int) file, (unsigned int) ap);
+
+ mask = POLLOUT | POLLWRNORM;
+ DABORT(ap == NULL, mask, FS_ERROR, "ap is NULL !!!\n");
+
+ /* If we are connected to ppp_generic, let it handle the job */
+ if(!ap->ppp_open)
+ mask |= irnet_ctrl_poll(ap, file, wait);
+
+ DEXIT(FS_TRACE, " - mask=0x%X\n", mask);
+ return(mask);
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * IOCtl : Called when someone does some ioctls on /dev/irnet
+ * This is the way pppd configure us and control us while the PPP
+ * instance is active.
+ */
+static int
+dev_irnet_ioctl(struct inode * inode,
+ struct file * file,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ irnet_socket * ap = (struct irnet_socket *) file->private_data;
+ int err;
+ int val;
+
+ DENTER(FS_TRACE, "(file=0x%X, ap=0x%X, cmd=0x%X)\n",
+ (unsigned int) file, (unsigned int) ap, cmd);
+
+ /* Basic checks... */
+ DASSERT(ap != NULL, -ENXIO, PPP_ERROR, "ap is NULL...\n");
+#ifdef SECURE_DEVIRNET
+ if(!capable(CAP_NET_ADMIN))
+ return -EPERM;
+#endif /* SECURE_DEVIRNET */
+
+ err = -EFAULT;
+ switch(cmd)
+ {
+ /* Set discipline (should be N_SYNC_PPP or N_TTY) */
+ case TIOCSETD:
+ if(get_user(val, (int *) arg))
+ break;
+ if((val == N_SYNC_PPP) || (val == N_PPP))
+ {
+ DEBUG(FS_INFO, "Entering PPP discipline.\n");
+ /* PPP channel setup (ap->chan in configued in dev_irnet_open())*/
+ err = ppp_register_channel(&ap->chan);
+ if(err == 0)
+ {
+ /* Our ppp side is active */
+ ap->ppp_open = 1;
+
+ DEBUG(FS_INFO, "Trying to establish a connection.\n");
+ /* Setup the IrDA link now - may fail... */
+ irda_irnet_connect(ap);
+ }
+ else
+ DERROR(FS_ERROR, "Can't setup PPP channel...\n");
+ }
+ else
+ {
+ /* In theory, should be N_TTY */
+ DEBUG(FS_INFO, "Exiting PPP discipline.\n");
+ /* Disconnect from the generic PPP layer */
+ if(ap->ppp_open)
+ ppp_unregister_channel(&ap->chan);
+ else
+ DERROR(FS_ERROR, "Channel not registered !\n");
+ ap->ppp_open = 0;
+ err = 0;
+ }
+ break;
+
+ /* Query PPP channel and unit number */
+ case PPPIOCGCHAN:
+ if(!ap->ppp_open)
+ break;
+ if(put_user(ppp_channel_index(&ap->chan), (int *) arg))
+ break;
+ DEBUG(FS_INFO, "Query channel.\n");
+ err = 0;
+ break;
+ case PPPIOCGUNIT:
+ if(!ap->ppp_open)
+ break;
+ if(put_user(ppp_unit_number(&ap->chan), (int *) arg))
+ break;
+ DEBUG(FS_INFO, "Query unit number.\n");
+ err = 0;
+ break;
+
+ /* All these ioctls can be passed both directly and from ppp_generic,
+ * so we just deal with them in one place...
+ */
+ case PPPIOCGFLAGS:
+ case PPPIOCSFLAGS:
+ case PPPIOCGASYNCMAP:
+ case PPPIOCSASYNCMAP:
+ case PPPIOCGRASYNCMAP:
+ case PPPIOCSRASYNCMAP:
+ case PPPIOCGXASYNCMAP:
+ case PPPIOCSXASYNCMAP:
+ case PPPIOCGMRU:
+ case PPPIOCSMRU:
+ DEBUG(FS_INFO, "Standard PPP ioctl.\n");
+ if(!capable(CAP_NET_ADMIN))
+ err = -EPERM;
+ else
+ err = ppp_irnet_ioctl(&ap->chan, cmd, arg);
+ break;
+
+ /* TTY IOCTLs : Pretend that we are a tty, to keep pppd happy */
+ /* Get termios */
+ case TCGETS:
+ DEBUG(FS_INFO, "Get termios.\n");
+ if(kernel_termios_to_user_termios((struct termios *)arg, &ap->termios))
+ break;
+ err = 0;
+ break;
+ /* Set termios */
+ case TCSETSF:
+ DEBUG(FS_INFO, "Set termios.\n");
+ if(user_termios_to_kernel_termios(&ap->termios, (struct termios *) arg))
+ break;
+ err = 0;
+ break;
+
+ /* Set DTR/RTS */
+ case TIOCMBIS:
+ case TIOCMBIC:
+ /* Set exclusive/non-exclusive mode */
+ case TIOCEXCL:
+ case TIOCNXCL:
+ DEBUG(FS_INFO, "TTY compatibility.\n");
+ err = 0;
+ break;
+
+ case TCGETA:
+ DEBUG(FS_INFO, "TCGETA\n");
+ break;
+
+ case TCFLSH:
+ DEBUG(FS_INFO, "TCFLSH\n");
+ /* Note : this will flush buffers in PPP, so it *must* be done
+ * We should also worry that we don't accept junk here and that
+ * we get rid of our own buffers */
+#ifdef FLUSH_TO_PPP
+ ppp_output_wakeup(&ap->chan);
+#endif /* FLUSH_TO_PPP */
+ err = 0;
+ break;
+
+ case FIONREAD:
+ DEBUG(FS_INFO, "FIONREAD\n");
+ val = 0;
+ if(put_user(val, (int *) arg))
+ break;
+ err = 0;
+ break;
+
+ default:
+ DERROR(FS_ERROR, "Unsupported ioctl (0x%X)\n", cmd);
+ err = -ENOIOCTLCMD;
+ }
+
+ DEXIT(FS_TRACE, " - err = 0x%X\n", err);
+ return err;
+}
+
+/************************** PPP CALLBACKS **************************/
+/*
+ * This are the functions that the generic PPP driver in the kernel
+ * will call to communicate to us.
+ */
+
+/*------------------------------------------------------------------*/
+/*
+ * Prepare the ppp frame for transmission over the IrDA socket.
+ * We make sure that the header space is enough, and we change ppp header
+ * according to flags passed by pppd.
+ * This is not a callback, but just a helper function used in ppp_irnet_send()
+ */
+static inline struct sk_buff *
+irnet_prepare_skb(irnet_socket * ap,
+ struct sk_buff * skb)
+{
+ unsigned char * data;
+ int proto; /* PPP protocol */
+ int islcp; /* Protocol == LCP */
+ int needaddr; /* Need PPP address */
+
+ DENTER(PPP_TRACE, "(ap=0x%X, skb=0x%X)\n",
+ (unsigned int) ap, (unsigned int) skb);
+
+ /* Extract PPP protocol from the frame */
+ data = skb->data;
+ proto = (data[0] << 8) + data[1];
+
+ /* LCP packets with codes between 1 (configure-request)
+ * and 7 (code-reject) must be sent as though no options
+ * have been negotiated. */
+ islcp = (proto == PPP_LCP) && (1 <= data[2]) && (data[2] <= 7);
+
+ /* compress protocol field if option enabled */
+ if((data[0] == 0) && (ap->flags & SC_COMP_PROT) && (!islcp))
+ skb_pull(skb,1);
+
+ /* Check if we need address/control fields */
+ needaddr = 2*((ap->flags & SC_COMP_AC) == 0 || islcp);
+
+ /* Is the skb headroom large enough to contain all IrDA-headers? */
+ if((skb_headroom(skb) < (ap->max_header_size + needaddr)) ||
+ (skb_shared(skb)))
+ {
+ struct sk_buff * new_skb;
+
+ DEBUG(PPP_INFO, "Reallocating skb\n");
+
+ /* Create a new skb */
+ new_skb = skb_realloc_headroom(skb, ap->max_header_size + needaddr);
+
+ /* We have to free the original skb anyway */
+ dev_kfree_skb(skb);
+
+ /* Did the realloc succeed ? */
+ DABORT(new_skb == NULL, NULL, PPP_ERROR, "Could not realloc skb\n");
+
+ /* Use the new skb instead */
+ skb = new_skb;
+ }
+
+ /* prepend address/control fields if necessary */
+ if(needaddr)
+ {
+ skb_push(skb, 2);
+ skb->data[0] = PPP_ALLSTATIONS;
+ skb->data[1] = PPP_UI;
+ }
+
+ DEXIT(PPP_TRACE, "\n");
+
+ return skb;
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Send a packet to the peer over the IrTTP connection.
+ * Returns 1 iff the packet was accepted.
+ * Returns 0 iff packet was not consumed.
+ * If the packet was not accepted, we will call ppp_output_wakeup
+ * at some later time to reactivate flow control in ppp_generic.
+ */
+static int
+ppp_irnet_send(struct ppp_channel * chan,
+ struct sk_buff * skb)
+{
+ irnet_socket * self = (struct irnet_socket *) chan->private;
+ int ret;
+
+ DENTER(PPP_TRACE, "(channel=0x%X, ap/self=0x%X)\n",
+ (unsigned int) chan, (unsigned int) self);
+
+ /* Check if things are somewhat valid... */
+ DASSERT(self != NULL, 0, PPP_ERROR, "Self is NULL !!!\n");
+
+ /* Check if we are connected */
+ if(!(test_bit(0, &self->ttp_open)))
+ {
+#ifdef CONNECT_IN_SEND
+ /* Let's try to connect one more time... */
+ /* Note : we won't be connected after this call, but we should be
+ * ready for next packet... */
+ /* If we are already connecting, this will fail */
+ irda_irnet_connect(self);
+#endif /* CONNECT_IN_SEND */
+
+ DEBUG(PPP_INFO, "IrTTP not ready ! (%d-%d)\n",
+ self->ttp_open, self->ttp_connect);
+
+ /* Note : we can either drop the packet or block the packet.
+ *
+ * Blocking the packet allow us a better connection time,
+ * because by calling ppp_output_wakeup() we can have
+ * ppp_generic resending the LCP request immediately to us,
+ * rather than waiting for one of pppd periodic transmission of
+ * LCP request.
+ *
+ * On the other hand, if we block all packet, all those periodic
+ * transmissions of pppd accumulate in ppp_generic, creating a
+ * backlog of LCP request. When we eventually connect later on,
+ * we have to transmit all this backlog before we can connect
+ * proper (if we don't timeout before).
+ *
+ * The current strategy is as follow :
+ * While we are attempting to connect, we block packets to get
+ * a better connection time.
+ * If we fail to connect, we drain the queue and start dropping packets
+ */
+#ifdef BLOCK_WHEN_CONNECT
+ /* If we are attempting to connect */
+ if(test_bit(0, &self->ttp_connect))
+ {
+ /* Blocking packet, ppp_generic will retry later */
+ return 0;
+ }
+#endif /* BLOCK_WHEN_CONNECT */
+
+ /* Dropping packet, pppd will retry later */
+ dev_kfree_skb(skb);
+ return 1;
+ }
+
+ /* Check if the queue can accept any packet, otherwise block */
+ if(self->tx_flow != FLOW_START)
+ DRETURN(0, PPP_INFO, "IrTTP queue full (%d skbs)...\n",
+ skb_queue_len(&self->tsap->tx_queue));
+
+ /* Prepare ppp frame for transmission */
+ skb = irnet_prepare_skb(self, skb);
+ DABORT(skb == NULL, 1, PPP_ERROR, "Prepare skb for Tx failed.\n");
+
+ /* Send the packet to IrTTP */
+ ret = irttp_data_request(self->tsap, skb);
+ if(ret < 0)
+ {
+ /*
+ * > IrTTPs tx queue is full, so we just have to
+ * > drop the frame! You might think that we should
+ * > just return -1 and don't deallocate the frame,
+ * > but that is dangerous since it's possible that
+ * > we have replaced the original skb with a new
+ * > one with larger headroom, and that would really
+ * > confuse do_dev_queue_xmit() in dev.c! I have
+ * > tried :-) DB
+ * Correction : we verify the flow control above (self->tx_flow),
+ * so we come here only if IrTTP doesn't like the packet (empty,
+ * too large, IrTTP not connected). In those rare cases, it's ok
+ * to drop it, we don't want to see it here again...
+ * Jean II
+ */
+ DERROR(PPP_ERROR, "IrTTP doesn't like this packet !!! (0x%X)\n", ret);
+ dev_kfree_skb(skb);
+ }
+
+ DEXIT(PPP_TRACE, "\n");
+ return 1; /* Packet has been consumed */
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Take care of the ioctls that ppp_generic doesn't want to deal with...
+ * Note : we are also called from dev_irnet_ioctl().
+ */
+static int
+ppp_irnet_ioctl(struct ppp_channel * chan,
+ unsigned int cmd,
+ unsigned long arg)
+{
+ irnet_socket * ap = (struct irnet_socket *) chan->private;
+ int err;
+ int val;
+ u32 accm[8];
+
+ DENTER(PPP_TRACE, "(channel=0x%X, ap=0x%X, cmd=0x%X)\n",
+ (unsigned int) chan, (unsigned int) ap, cmd);
+
+ /* Basic checks... */
+ DASSERT(ap != NULL, -ENXIO, PPP_ERROR, "ap is NULL...\n");
+
+ err = -EFAULT;
+ switch(cmd)
+ {
+ /* PPP flags */
+ case PPPIOCGFLAGS:
+ val = ap->flags | ap->rbits;
+ if(put_user(val, (int *) arg))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSFLAGS:
+ if(get_user(val, (int *) arg))
+ break;
+ ap->flags = val & ~SC_RCV_BITS;
+ ap->rbits = val & SC_RCV_BITS;
+ err = 0;
+ break;
+
+ /* Async map stuff - all dummy to please pppd */
+ case PPPIOCGASYNCMAP:
+ if(put_user(ap->xaccm[0], (u32 *) arg))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSASYNCMAP:
+ if(get_user(ap->xaccm[0], (u32 *) arg))
+ break;
+ err = 0;
+ break;
+ case PPPIOCGRASYNCMAP:
+ if(put_user(ap->raccm, (u32 *) arg))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSRASYNCMAP:
+ if(get_user(ap->raccm, (u32 *) arg))
+ break;
+ err = 0;
+ break;
+ case PPPIOCGXASYNCMAP:
+ if(copy_to_user((void *) arg, ap->xaccm, sizeof(ap->xaccm)))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSXASYNCMAP:
+ if(copy_from_user(accm, (void *) arg, sizeof(accm)))
+ break;
+ accm[2] &= ~0x40000000U; /* can't escape 0x5e */
+ accm[3] |= 0x60000000U; /* must escape 0x7d, 0x7e */
+ memcpy(ap->xaccm, accm, sizeof(ap->xaccm));
+ err = 0;
+ break;
+
+ /* Max PPP frame size */
+ case PPPIOCGMRU:
+ if(put_user(ap->mru, (int *) arg))
+ break;
+ err = 0;
+ break;
+ case PPPIOCSMRU:
+ if(get_user(val, (int *) arg))
+ break;
+ if(val < PPP_MRU)
+ val = PPP_MRU;
+ ap->mru = val;
+ err = 0;
+ break;
+
+ default:
+ DEBUG(PPP_INFO, "Unsupported ioctl (0x%X)\n", cmd);
+ err = -ENOIOCTLCMD;
+ }
+
+ DEXIT(PPP_TRACE, " - err = 0x%X\n", err);
+ return err;
+}
+
+/************************** INITIALISATION **************************/
+/*
+ * Module initialisation and all that jazz...
+ */
+
+/*------------------------------------------------------------------*/
+/*
+ * Hook our device callbacks in the filesystem, to connect our code
+ * to /dev/irnet
+ */
+int
+ppp_irnet_init(void)
+{
+ int err = 0;
+
+ DENTER(MODULE_TRACE, "()\n");
+
+ /* Allocate ourselves as a minor in the misc range */
+ err = misc_register(&irnet_misc_device);
+
+ DEXIT(MODULE_TRACE, "\n");
+ return err;
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Cleanup at exit...
+ */
+void
+ppp_irnet_cleanup(void)
+{
+ DENTER(MODULE_TRACE, "()\n");
+
+ /* De-allocate /dev/irnet minor in misc range */
+ misc_deregister(&irnet_misc_device);
+
+ DEXIT(MODULE_TRACE, "\n");
+}
+
+#ifdef MODULE
+/*------------------------------------------------------------------*/
+/*
+ * Module main entry point
+ */
+int
+init_module(void)
+{
+ int err;
+
+ /* Initialise both parts... */
+ err = irda_irnet_init();
+ if(!err)
+ err = ppp_irnet_init();
+ return err;
+}
+
+/*------------------------------------------------------------------*/
+/*
+ * Module exit
+ */
+void
+cleanup_module(void)
+{
+ irda_irnet_cleanup();
+ return ppp_irnet_cleanup();
+}
+#endif /* MODULE */
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/irda/irnet/irnet_ppp.h b/uClinux-2.4.31-uc0/net/irda/irnet/irnet_ppp.h
new file mode 100644
index 0000000..3a81e1c
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irnet/irnet_ppp.h
@@ -0,0 +1,125 @@
+/*
+ * IrNET protocol module : Synchronous PPP over an IrDA socket.
+ *
+ * Jean II - HPL `00 - <jt@hpl.hp.com>
+ *
+ * This file contains all definitions and declarations necessary for the
+ * PPP part of the IrNET module.
+ * This file is a private header, so other modules don't want to know
+ * what's in there...
+ */
+
+#ifndef IRNET_PPP_H
+#define IRNET_PPP_H
+
+/***************************** INCLUDES *****************************/
+
+#include "irnet.h" /* Module global include */
+
+/************************ CONSTANTS & MACROS ************************/
+
+/* /dev/irnet file constants */
+#define IRNET_MAJOR 10 /* Misc range */
+#define IRNET_MINOR 187 /* Official allocation */
+
+/* IrNET control channel stuff */
+#define IRNET_MAX_COMMAND 256 /* Max length of a command line */
+
+/* PPP hardcore stuff */
+
+/* Bits in rbits (PPP flags in irnet struct) */
+#define SC_RCV_BITS (SC_RCV_B7_1|SC_RCV_B7_0|SC_RCV_ODDP|SC_RCV_EVNP)
+
+/* Bit numbers in busy */
+#define XMIT_BUSY 0
+#define RECV_BUSY 1
+#define XMIT_WAKEUP 2
+#define XMIT_FULL 3
+
+/* Queue management */
+#define PPPSYNC_MAX_RQLEN 32 /* arbitrary */
+
+/****************************** TYPES ******************************/
+
+
+/**************************** PROTOTYPES ****************************/
+
+/* ----------------------- CONTROL CHANNEL ----------------------- */
+static inline ssize_t
+ irnet_ctrl_write(irnet_socket *,
+ const char *,
+ size_t);
+static inline ssize_t
+ irnet_ctrl_read(irnet_socket *,
+ struct file *,
+ char *,
+ size_t);
+static inline unsigned int
+ irnet_ctrl_poll(irnet_socket *,
+ struct file *,
+ poll_table *);
+/* ----------------------- CHARACTER DEVICE ----------------------- */
+static int
+ dev_irnet_open(struct inode *, /* fs callback : open */
+ struct file *),
+ dev_irnet_close(struct inode *,
+ struct file *);
+static ssize_t
+ dev_irnet_write(struct file *,
+ const char *,
+ size_t,
+ loff_t *),
+ dev_irnet_read(struct file *,
+ char *,
+ size_t,
+ loff_t *);
+static unsigned int
+ dev_irnet_poll(struct file *,
+ poll_table *);
+static int
+ dev_irnet_ioctl(struct inode *,
+ struct file *,
+ unsigned int,
+ unsigned long);
+/* ------------------------ PPP INTERFACE ------------------------ */
+static inline struct sk_buff *
+ irnet_prepare_skb(irnet_socket *,
+ struct sk_buff *);
+static int
+ ppp_irnet_send(struct ppp_channel *,
+ struct sk_buff *);
+static int
+ ppp_irnet_ioctl(struct ppp_channel *,
+ unsigned int,
+ unsigned long);
+
+/**************************** VARIABLES ****************************/
+
+/* Filesystem callbacks (to call us) */
+static struct file_operations irnet_device_fops =
+{
+ read: dev_irnet_read,
+ write: dev_irnet_write,
+ poll: dev_irnet_poll,
+ ioctl: dev_irnet_ioctl,
+ open: dev_irnet_open,
+ release: dev_irnet_close
+ /* Also : llseek, readdir, mmap, flush, fsync, fasync, lock, readv, writev */
+};
+
+/* Structure so that the misc major (drivers/char/misc.c) take care of us... */
+static struct miscdevice irnet_misc_device =
+{
+ IRNET_MINOR,
+ "irnet",
+ &irnet_device_fops
+};
+
+/* Generic PPP callbacks (to call us) */
+struct ppp_channel_ops irnet_ppp_ops =
+{
+ ppp_irnet_send,
+ ppp_irnet_ioctl
+};
+
+#endif /* IRNET_PPP_H */
diff --git a/uClinux-2.4.31-uc0/net/irda/irproc.c b/uClinux-2.4.31-uc0/net/irda/irproc.c
new file mode 100644
index 0000000..e820e19
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irproc.c
@@ -0,0 +1,97 @@
+/*********************************************************************
+ *
+ * Filename: irproc.c
+ * Version: 1.0
+ * Description: Various entries in the /proc file system
+ * Status: Experimental.
+ * Author: Thomas Davis, <ratbert@radiks.net>
+ * Created at: Sat Feb 21 21:33:24 1998
+ * Modified at: Sun Nov 14 08:54:54 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1998-1999, Dag Brattli <dagb@cs.uit.no>
+ * Copyright (c) 1998, Thomas Davis, <ratbert@radiks.net>,
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * I, Thomas Davis, provide no warranty for any of this software.
+ * This material is provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/miscdevice.h>
+#include <linux/proc_fs.h>
+#define __NO_VERSION__
+#include <linux/module.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irmod.h>
+#include <net/irda/irlap.h>
+#include <net/irda/irlmp.h>
+
+extern int irlap_proc_read(char *buf, char **start, off_t offset, int len);
+extern int irlmp_proc_read(char *buf, char **start, off_t offset, int len);
+extern int irttp_proc_read(char *buf, char **start, off_t offset, int len);
+extern int irias_proc_read(char *buf, char **start, off_t offset, int len);
+extern int discovery_proc_read(char *buf, char **start, off_t offset, int len);
+
+struct irda_entry {
+ char *name;
+ int (*fn)(char*, char**, off_t, int);
+};
+
+struct proc_dir_entry *proc_irda;
+
+static struct irda_entry dir[] = {
+ {"discovery", discovery_proc_read},
+ {"irttp", irttp_proc_read},
+ {"irlmp", irlmp_proc_read},
+ {"irlap", irlap_proc_read},
+ {"irias", irias_proc_read},
+};
+
+#define IRDA_ENTRIES_NUM (sizeof(dir)/sizeof(dir[0]))
+
+/*
+ * Function irda_proc_register (void)
+ *
+ * Register irda entry in /proc file system
+ *
+ */
+void irda_proc_register(void)
+{
+ int i;
+
+ proc_irda = proc_mkdir("net/irda", NULL);
+ if (proc_irda == NULL)
+ return;
+ proc_irda->owner = THIS_MODULE;
+
+ for (i=0;i<IRDA_ENTRIES_NUM;i++)
+ create_proc_info_entry(dir[i].name,0,proc_irda,dir[i].fn);
+}
+
+/*
+ * Function irda_proc_unregister (void)
+ *
+ * Unregister irda entry in /proc file system
+ *
+ */
+void irda_proc_unregister(void)
+{
+ int i;
+
+ if (proc_irda) {
+ for (i=0;i<IRDA_ENTRIES_NUM;i++)
+ remove_proc_entry(dir[i].name, proc_irda);
+
+ remove_proc_entry("net/irda", NULL);
+ proc_irda = NULL;
+ }
+}
+
+
diff --git a/uClinux-2.4.31-uc0/net/irda/irqueue.c b/uClinux-2.4.31-uc0/net/irda/irqueue.c
new file mode 100644
index 0000000..042e833
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irqueue.c
@@ -0,0 +1,783 @@
+/*********************************************************************
+ *
+ * Filename: irqueue.c
+ * Version: 0.3
+ * Description: General queue implementation
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Tue Jun 9 13:29:31 1998
+ * Modified at: Sun Dec 12 13:48:22 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ * Modified at: Thu Jan 4 14:29:10 CET 2001
+ * Modified by: Marc Zyngier <mzyngier@freesurf.fr>
+ *
+ * Copyright (C) 1998-1999, Aage Kvalnes <aage@cs.uit.no>
+ * Copyright (C) 1998, Dag Brattli,
+ * All Rights Reserved.
+ *
+ * This code is taken from the Vortex Operating System written by Aage
+ * Kvalnes. Aage has agreed that this code can use the GPL licence,
+ * although he does not use that licence in his own code.
+ *
+ * This copyright does however _not_ include the ELF hash() function
+ * which I currently don't know which licence or copyright it
+ * has. Please inform me if you know.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <net/irda/irda.h>
+#include <net/irda/irqueue.h>
+#include <net/irda/irmod.h>
+
+static irda_queue_t *dequeue_general( irda_queue_t **queue, irda_queue_t* element);
+static __u32 hash( char* name);
+
+/*
+ * Function hashbin_create ( type, name )
+ *
+ * Create hashbin!
+ *
+ */
+hashbin_t *hashbin_new(int type)
+{
+ hashbin_t* hashbin;
+ int i;
+
+ /*
+ * Allocate new hashbin
+ */
+ hashbin = kmalloc( sizeof(hashbin_t), GFP_ATOMIC);
+ if (!hashbin)
+ return NULL;
+
+ /*
+ * Initialize structure
+ */
+ memset(hashbin, 0, sizeof(hashbin_t));
+ hashbin->hb_type = type;
+ hashbin->magic = HB_MAGIC;
+
+ /* Make sure all spinlock's are unlocked */
+ for (i=0;i<HASHBIN_SIZE;i++)
+ hashbin->hb_mutex[i] = SPIN_LOCK_UNLOCKED;
+
+ return hashbin;
+}
+
+/*
+ * Function hashbin_clear (hashbin, free_func)
+ *
+ * Remove all entries from the hashbin, see also the comments in
+ * hashbin_delete() below
+ */
+int hashbin_clear( hashbin_t* hashbin, FREE_FUNC free_func)
+{
+ irda_queue_t* queue;
+ int i;
+
+ ASSERT(hashbin != NULL, return -1;);
+ ASSERT(hashbin->magic == HB_MAGIC, return -1;);
+
+ /*
+ * Free the entries in the hashbin
+ */
+ for (i = 0; i < HASHBIN_SIZE; i ++ ) {
+ queue = dequeue_first( (irda_queue_t**) &hashbin->hb_queue[i]);
+ while (queue) {
+ if (free_func)
+ (*free_func)(queue);
+ queue = dequeue_first(
+ (irda_queue_t**) &hashbin->hb_queue[i]);
+ }
+ }
+ hashbin->hb_size = 0;
+
+ return 0;
+}
+
+
+/*
+ * Function hashbin_delete (hashbin, free_func)
+ *
+ * Destroy hashbin, the free_func can be a user supplied special routine
+ * for deallocating this structure if it's complex. If not the user can
+ * just supply kfree, which should take care of the job.
+ */
+int hashbin_delete( hashbin_t* hashbin, FREE_FUNC free_func)
+{
+ irda_queue_t* queue;
+ int i;
+
+ ASSERT(hashbin != NULL, return -1;);
+ ASSERT(hashbin->magic == HB_MAGIC, return -1;);
+
+ /*
+ * Free the entries in the hashbin, TODO: use hashbin_clear when
+ * it has been shown to work
+ */
+ for (i = 0; i < HASHBIN_SIZE; i ++ ) {
+ queue = dequeue_first((irda_queue_t**) &hashbin->hb_queue[i]);
+ while (queue ) {
+ if (free_func)
+ (*free_func)(queue);
+ queue = dequeue_first(
+ (irda_queue_t**) &hashbin->hb_queue[i]);
+ }
+ }
+
+ /*
+ * Free the hashbin structure
+ */
+ hashbin->magic = ~HB_MAGIC;
+ kfree(hashbin);
+
+ return 0;
+}
+
+/*
+ * Function hashbin_insert (hashbin, entry, name)
+ *
+ * Insert an entry into the hashbin
+ *
+ */
+void hashbin_insert(hashbin_t* hashbin, irda_queue_t* entry, __u32 hashv, char* name)
+{
+ unsigned long flags = 0;
+ int bin;
+
+ IRDA_DEBUG( 4,"%s()\n", __FUNCTION__);
+
+ ASSERT( hashbin != NULL, return;);
+ ASSERT( hashbin->magic == HB_MAGIC, return;);
+
+ /*
+ * Locate hashbin
+ */
+ if ( name )
+ hashv = hash( name );
+ bin = GET_HASHBIN( hashv );
+
+ /* Synchronize */
+ if ( hashbin->hb_type & HB_GLOBAL ) {
+ spin_lock_irqsave( &hashbin->hb_mutex[ bin ], flags);
+
+ } else if ( hashbin->hb_type & HB_LOCAL ) {
+ save_flags(flags);
+ cli();
+ } /* Default is no-lock */
+
+ /*
+ * Store name and key
+ */
+ entry->q_hash = hashv;
+ if ( name )
+ strncpy( entry->q_name, name, 32);
+
+ /*
+ * Insert new entry first
+ * TODO: Perhaps allow sorted lists?
+ * -> Merge sort if a sorted list should be created
+ */
+ if ( hashbin->hb_type & HB_SORTED) {
+ } else {
+ enqueue_first( (irda_queue_t**) &hashbin->hb_queue[ bin ],
+ entry);
+ }
+ hashbin->hb_size++;
+
+ /* Release lock */
+ if ( hashbin->hb_type & HB_GLOBAL) {
+
+ spin_unlock_irqrestore( &hashbin->hb_mutex[ bin], flags);
+
+ } else if ( hashbin->hb_type & HB_LOCAL) {
+ restore_flags( flags);
+ }
+}
+
+/*
+ * Function hashbin_find (hashbin, hashv, name)
+ *
+ * Find item with the given hashv or name
+ *
+ */
+void* hashbin_find( hashbin_t* hashbin, __u32 hashv, char* name )
+{
+ int bin, found = FALSE;
+ unsigned long flags = 0;
+ irda_queue_t* entry;
+
+ IRDA_DEBUG( 4, "hashbin_find()\n");
+
+ ASSERT( hashbin != NULL, return NULL;);
+ ASSERT( hashbin->magic == HB_MAGIC, return NULL;);
+
+ /*
+ * Locate hashbin
+ */
+ if ( name )
+ hashv = hash( name );
+ bin = GET_HASHBIN( hashv );
+
+ /* Synchronize */
+ if ( hashbin->hb_type & HB_GLOBAL ) {
+ spin_lock_irqsave( &hashbin->hb_mutex[ bin ], flags);
+
+ } else if ( hashbin->hb_type & HB_LOCAL ) {
+ save_flags(flags);
+ cli();
+ } /* Default is no-lock */
+
+ /*
+ * Search for entry
+ */
+ entry = hashbin->hb_queue[ bin];
+ if ( entry ) {
+ do {
+ /*
+ * Check for key
+ */
+ if ( entry->q_hash == hashv ) {
+ /*
+ * Name compare too?
+ */
+ if ( name ) {
+ if ( strcmp( entry->q_name, name ) == 0 ) {
+ found = TRUE;
+ break;
+ }
+ } else {
+ found = TRUE;
+ break;
+ }
+ }
+ entry = entry->q_next;
+ } while ( entry != hashbin->hb_queue[ bin ] );
+ }
+
+ /* Release lock */
+ if ( hashbin->hb_type & HB_GLOBAL) {
+ spin_unlock_irqrestore( &hashbin->hb_mutex[ bin], flags);
+
+ } else if ( hashbin->hb_type & HB_LOCAL) {
+ restore_flags( flags);
+ }
+
+ if ( found )
+ return entry;
+ else
+ return NULL;
+}
+
+void *hashbin_remove_first( hashbin_t *hashbin)
+{
+ unsigned long flags;
+ irda_queue_t *entry = NULL;
+
+ save_flags(flags);
+ cli();
+
+ entry = hashbin_get_first( hashbin);
+ if ( entry != NULL)
+ hashbin_remove( hashbin, entry->q_hash, NULL);
+
+ restore_flags( flags);
+
+ return entry;
+}
+
+
+/*
+ * Function hashbin_remove (hashbin, hashv, name)
+ *
+ * Remove entry with the given name
+ *
+ */
+void* hashbin_remove( hashbin_t* hashbin, __u32 hashv, char* name)
+{
+ int bin, found = FALSE;
+ unsigned long flags = 0;
+ irda_queue_t* entry;
+
+ IRDA_DEBUG( 4, "%s()\n", __FUNCTION__);
+
+ ASSERT( hashbin != NULL, return NULL;);
+ ASSERT( hashbin->magic == HB_MAGIC, return NULL;);
+
+ /*
+ * Locate hashbin
+ */
+ if ( name )
+ hashv = hash( name );
+ bin = GET_HASHBIN( hashv );
+
+ /* Synchronize */
+ if ( hashbin->hb_type & HB_GLOBAL ) {
+ spin_lock_irqsave( &hashbin->hb_mutex[ bin ], flags);
+
+ } else if ( hashbin->hb_type & HB_LOCAL ) {
+ save_flags(flags);
+ cli();
+ } /* Default is no-lock */
+
+ /*
+ * Search for entry
+ */
+ entry = hashbin->hb_queue[ bin ];
+ if ( entry ) {
+ do {
+ /*
+ * Check for key
+ */
+ if ( entry->q_hash == hashv ) {
+ /*
+ * Name compare too?
+ */
+ if ( name ) {
+ if ( strcmp( entry->q_name, name) == 0)
+ {
+ found = TRUE;
+ break;
+ }
+ } else {
+ found = TRUE;
+ break;
+ }
+ }
+ entry = entry->q_next;
+ } while ( entry != hashbin->hb_queue[ bin ] );
+ }
+
+ /*
+ * If entry was found, dequeue it
+ */
+ if ( found ) {
+ dequeue_general( (irda_queue_t**) &hashbin->hb_queue[ bin ],
+ (irda_queue_t*) entry );
+ hashbin->hb_size--;
+
+ /*
+ * Check if this item is the currently selected item, and in
+ * that case we must reset hb_current
+ */
+ if ( entry == hashbin->hb_current)
+ hashbin->hb_current = NULL;
+ }
+
+ /* Release lock */
+ if ( hashbin->hb_type & HB_GLOBAL) {
+ spin_unlock_irqrestore( &hashbin->hb_mutex[ bin], flags);
+
+ } else if ( hashbin->hb_type & HB_LOCAL) {
+ restore_flags( flags);
+ }
+
+
+ /* Return */
+ if ( found )
+ return entry;
+ else
+ return NULL;
+
+}
+
+/*
+ * Function hashbin_remove (hashbin, hashv, name)
+ *
+ * Remove entry with the given name
+ *
+ * In some cases, the user of hashbin can't guarantee the unicity
+ * of either the hashv or name.
+ * In those cases, using the above function is guaranteed to cause troubles,
+ * so we use this one instead...
+ * And by the way, it's also faster, because we skip the search phase ;-)
+ */
+void* hashbin_remove_this( hashbin_t* hashbin, irda_queue_t* entry)
+{
+ unsigned long flags = 0;
+ int bin;
+ __u32 hashv;
+
+ IRDA_DEBUG( 4, "%s()\n", __FUNCTION__);
+
+ ASSERT( hashbin != NULL, return NULL;);
+ ASSERT( hashbin->magic == HB_MAGIC, return NULL;);
+ ASSERT( entry != NULL, return NULL;);
+
+ /* Check if valid and not already removed... */
+ if((entry->q_next == NULL) || (entry->q_prev == NULL))
+ return NULL;
+
+ /*
+ * Locate hashbin
+ */
+ hashv = entry->q_hash;
+ bin = GET_HASHBIN( hashv );
+
+ /* Synchronize */
+ if ( hashbin->hb_type & HB_GLOBAL ) {
+ spin_lock_irqsave( &hashbin->hb_mutex[ bin ], flags);
+
+ } else if ( hashbin->hb_type & HB_LOCAL ) {
+ save_flags(flags);
+ cli();
+ } /* Default is no-lock */
+
+ /*
+ * Dequeue the entry...
+ */
+ dequeue_general( (irda_queue_t**) &hashbin->hb_queue[ bin ],
+ (irda_queue_t*) entry );
+ hashbin->hb_size--;
+ entry->q_next = NULL;
+ entry->q_prev = NULL;
+
+ /*
+ * Check if this item is the currently selected item, and in
+ * that case we must reset hb_current
+ */
+ if ( entry == hashbin->hb_current)
+ hashbin->hb_current = NULL;
+
+ /* Release lock */
+ if ( hashbin->hb_type & HB_GLOBAL) {
+ spin_unlock_irqrestore( &hashbin->hb_mutex[ bin], flags);
+
+ } else if ( hashbin->hb_type & HB_LOCAL) {
+ restore_flags( flags);
+ }
+
+ return entry;
+}
+
+/*
+ * Function hashbin_get_first (hashbin)
+ *
+ * Get a pointer to first element in hashbin, this function must be
+ * called before any calls to hashbin_get_next()!
+ *
+ */
+irda_queue_t *hashbin_get_first( hashbin_t* hashbin)
+{
+ irda_queue_t *entry;
+ int i;
+
+ ASSERT( hashbin != NULL, return NULL;);
+ ASSERT( hashbin->magic == HB_MAGIC, return NULL;);
+
+ if ( hashbin == NULL)
+ return NULL;
+
+ for ( i = 0; i < HASHBIN_SIZE; i ++ ) {
+ entry = hashbin->hb_queue[ i];
+ if ( entry) {
+ hashbin->hb_current = entry;
+ return entry;
+ }
+ }
+ /*
+ * Did not find any item in hashbin
+ */
+ return NULL;
+}
+
+/*
+ * Function hashbin_get_next (hashbin)
+ *
+ * Get next item in hashbin. A series of hashbin_get_next() calls must
+ * be started by a call to hashbin_get_first(). The function returns
+ * NULL when all items have been traversed
+ *
+ */
+irda_queue_t *hashbin_get_next( hashbin_t *hashbin)
+{
+ irda_queue_t* entry;
+ int bin;
+ int i;
+
+ ASSERT( hashbin != NULL, return NULL;);
+ ASSERT( hashbin->magic == HB_MAGIC, return NULL;);
+
+ if ( hashbin->hb_current == NULL) {
+ ASSERT( hashbin->hb_current != NULL, return NULL;);
+ return NULL;
+ }
+ entry = hashbin->hb_current->q_next;
+ bin = GET_HASHBIN( entry->q_hash);
+
+ /*
+ * Make sure that we are not back at the beginning of the queue
+ * again
+ */
+ if ( entry != hashbin->hb_queue[ bin ]) {
+ hashbin->hb_current = entry;
+
+ return entry;
+ }
+
+ /*
+ * Check that this is not the last queue in hashbin
+ */
+ if ( bin >= HASHBIN_SIZE)
+ return NULL;
+
+ /*
+ * Move to next queue in hashbin
+ */
+ bin++;
+ for ( i = bin; i < HASHBIN_SIZE; i++ ) {
+ entry = hashbin->hb_queue[ i];
+ if ( entry) {
+ hashbin->hb_current = entry;
+
+ return entry;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * Function enqueue_last (queue, proc)
+ *
+ * Insert item into end of queue.
+ *
+ */
+static void __enqueue_last( irda_queue_t **queue, irda_queue_t* element)
+{
+ IRDA_DEBUG( 4, "%s()\n", __FUNCTION__);
+
+ /*
+ * Check if queue is empty.
+ */
+ if ( *queue == NULL ) {
+ /*
+ * Queue is empty. Insert one element into the queue.
+ */
+ element->q_next = element->q_prev = *queue = element;
+
+ } else {
+ /*
+ * Queue is not empty. Insert element into end of queue.
+ */
+ element->q_prev = (*queue)->q_prev;
+ element->q_prev->q_next = element;
+ (*queue)->q_prev = element;
+ element->q_next = *queue;
+ }
+}
+
+inline void enqueue_last( irda_queue_t **queue, irda_queue_t* element)
+{
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ __enqueue_last( queue, element);
+
+ restore_flags(flags);
+}
+
+/*
+ * Function enqueue_first (queue, proc)
+ *
+ * Insert item first in queue.
+ *
+ */
+void enqueue_first(irda_queue_t **queue, irda_queue_t* element)
+{
+
+ IRDA_DEBUG( 4, "%s()\n", __FUNCTION__);
+
+ /*
+ * Check if queue is empty.
+ */
+ if ( *queue == NULL ) {
+ /*
+ * Queue is empty. Insert one element into the queue.
+ */
+ element->q_next = element->q_prev = *queue = element;
+
+ } else {
+ /*
+ * Queue is not empty. Insert element into front of queue.
+ */
+ element->q_next = (*queue);
+ (*queue)->q_prev->q_next = element;
+ element->q_prev = (*queue)->q_prev;
+ (*queue)->q_prev = element;
+ (*queue) = element;
+ }
+}
+
+/*
+ * Function enqueue_queue (queue, list)
+ *
+ * Insert a queue (list) into the start of the first queue
+ *
+ */
+void enqueue_queue( irda_queue_t** queue, irda_queue_t** list )
+{
+ irda_queue_t* tmp;
+
+ /*
+ * Check if queue is empty
+ */
+ if ( *queue ) {
+ (*list)->q_prev->q_next = (*queue);
+ (*queue)->q_prev->q_next = (*list);
+ tmp = (*list)->q_prev;
+ (*list)->q_prev = (*queue)->q_prev;
+ (*queue)->q_prev = tmp;
+ } else {
+ *queue = (*list);
+ }
+
+ (*list) = NULL;
+}
+
+/*
+ * Function enqueue_second (queue, proc)
+ *
+ * Insert item behind head of queue.
+ *
+ */
+#if 0
+static void enqueue_second(irda_queue_t **queue, irda_queue_t* element)
+{
+ IRDA_DEBUG( 0, "enqueue_second()\n");
+
+ /*
+ * Check if queue is empty.
+ */
+ if ( *queue == NULL ) {
+ /*
+ * Queue is empty. Insert one element into the queue.
+ */
+ element->q_next = element->q_prev = *queue = element;
+
+ } else {
+ /*
+ * Queue is not empty. Insert element into ..
+ */
+ element->q_prev = (*queue);
+ (*queue)->q_next->q_prev = element;
+ element->q_next = (*queue)->q_next;
+ (*queue)->q_next = element;
+ }
+}
+#endif
+
+/*
+ * Function dequeue (queue)
+ *
+ * Remove first entry in queue
+ *
+ */
+irda_queue_t *dequeue_first(irda_queue_t **queue)
+{
+ irda_queue_t *ret;
+
+ IRDA_DEBUG( 4, "dequeue_first()\n");
+
+ /*
+ * Set return value
+ */
+ ret = *queue;
+
+ if ( *queue == NULL ) {
+ /*
+ * Queue was empty.
+ */
+ } else if ( (*queue)->q_next == *queue ) {
+ /*
+ * Queue only contained a single element. It will now be
+ * empty.
+ */
+ *queue = NULL;
+ } else {
+ /*
+ * Queue contained several element. Remove the first one.
+ */
+ (*queue)->q_prev->q_next = (*queue)->q_next;
+ (*queue)->q_next->q_prev = (*queue)->q_prev;
+ *queue = (*queue)->q_next;
+ }
+
+ /*
+ * Return the removed entry (or NULL of queue was empty).
+ */
+ return ret;
+}
+
+/*
+ * Function dequeue_general (queue, element)
+ *
+ *
+ */
+static irda_queue_t *dequeue_general(irda_queue_t **queue, irda_queue_t* element)
+{
+ irda_queue_t *ret;
+
+ IRDA_DEBUG( 4, "dequeue_general()\n");
+
+ /*
+ * Set return value
+ */
+ ret = *queue;
+
+ if ( *queue == NULL ) {
+ /*
+ * Queue was empty.
+ */
+ } else if ( (*queue)->q_next == *queue ) {
+ /*
+ * Queue only contained a single element. It will now be
+ * empty.
+ */
+ *queue = NULL;
+
+ } else {
+ /*
+ * Remove specific element.
+ */
+ element->q_prev->q_next = element->q_next;
+ element->q_next->q_prev = element->q_prev;
+ if ( (*queue) == element)
+ (*queue) = element->q_next;
+ }
+
+ /*
+ * Return the removed entry (or NULL of queue was empty).
+ */
+ return ret;
+}
+
+/*
+ * Function hash (name)
+ *
+ * This function hash the input string 'name' using the ELF hash
+ * function for strings.
+ */
+static __u32 hash( char* name)
+{
+ __u32 h = 0;
+ __u32 g;
+
+ while(*name) {
+ h = (h<<4) + *name++;
+ if ((g = (h & 0xf0000000)))
+ h ^=g>>24;
+ h &=~g;
+ }
+ return h;
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/irsyms.c b/uClinux-2.4.31-uc0/net/irda/irsyms.c
new file mode 100644
index 0000000..bb91e47
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irsyms.c
@@ -0,0 +1,243 @@
+/*********************************************************************
+ *
+ * Filename: irsyms.c
+ * Version: 0.9
+ * Description: IrDA module symbols
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Mon Dec 15 13:55:39 1997
+ * Modified at: Wed Jan 5 15:12:41 2000
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1997, 1999-2000 Dag Brattli, All Rights Reserved.
+ * Copyright (c) 2000-2001 Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/proc_fs.h>
+#include <linux/smp_lock.h>
+
+#include <asm/segment.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irmod.h>
+#include <net/irda/irlap.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/iriap.h>
+#include <net/irda/irias_object.h>
+#include <net/irda/irttp.h>
+#include <net/irda/irda_device.h>
+#include <net/irda/wrapper.h>
+#include <net/irda/timer.h>
+#include <net/irda/parameters.h>
+#include <net/irda/crc.h>
+
+extern struct proc_dir_entry *proc_irda;
+
+extern void irda_proc_register(void);
+extern void irda_proc_unregister(void);
+extern int irda_sysctl_register(void);
+extern void irda_sysctl_unregister(void);
+
+extern int irda_proto_init(void);
+extern void irda_proto_cleanup(void);
+
+extern int irda_device_init(void);
+extern int irlan_init(void);
+extern int irlan_client_init(void);
+extern int irlan_server_init(void);
+extern int ircomm_init(void);
+extern int ircomm_tty_init(void);
+extern int irlpt_client_init(void);
+extern int irlpt_server_init(void);
+
+/* IrTTP */
+EXPORT_SYMBOL(irttp_open_tsap);
+EXPORT_SYMBOL(irttp_close_tsap);
+EXPORT_SYMBOL(irttp_connect_response);
+EXPORT_SYMBOL(irttp_data_request);
+EXPORT_SYMBOL(irttp_disconnect_request);
+EXPORT_SYMBOL(irttp_flow_request);
+EXPORT_SYMBOL(irttp_connect_request);
+EXPORT_SYMBOL(irttp_udata_request);
+EXPORT_SYMBOL(irttp_dup);
+
+/* Main IrDA module */
+#ifdef CONFIG_IRDA_DEBUG
+EXPORT_SYMBOL(irda_debug);
+#endif
+EXPORT_SYMBOL(irda_notify_init);
+#ifdef CONFIG_PROC_FS
+EXPORT_SYMBOL(proc_irda);
+#endif
+EXPORT_SYMBOL(irda_param_insert);
+EXPORT_SYMBOL(irda_param_extract);
+EXPORT_SYMBOL(irda_param_extract_all);
+EXPORT_SYMBOL(irda_param_pack);
+EXPORT_SYMBOL(irda_param_unpack);
+
+/* IrIAP/IrIAS */
+EXPORT_SYMBOL(iriap_open);
+EXPORT_SYMBOL(iriap_close);
+EXPORT_SYMBOL(iriap_getvaluebyclass_request);
+EXPORT_SYMBOL(irias_object_change_attribute);
+EXPORT_SYMBOL(irias_add_integer_attrib);
+EXPORT_SYMBOL(irias_add_octseq_attrib);
+EXPORT_SYMBOL(irias_add_string_attrib);
+EXPORT_SYMBOL(irias_insert_object);
+EXPORT_SYMBOL(irias_new_object);
+EXPORT_SYMBOL(irias_delete_object);
+EXPORT_SYMBOL(irias_delete_value);
+EXPORT_SYMBOL(irias_find_object);
+EXPORT_SYMBOL(irias_find_attrib);
+EXPORT_SYMBOL(irias_new_integer_value);
+EXPORT_SYMBOL(irias_new_string_value);
+EXPORT_SYMBOL(irias_new_octseq_value);
+
+/* IrLMP */
+EXPORT_SYMBOL(irlmp_discovery_request);
+EXPORT_SYMBOL(irlmp_get_discoveries);
+EXPORT_SYMBOL(sysctl_discovery_timeout);
+EXPORT_SYMBOL(irlmp_register_client);
+EXPORT_SYMBOL(irlmp_unregister_client);
+EXPORT_SYMBOL(irlmp_update_client);
+EXPORT_SYMBOL(irlmp_register_service);
+EXPORT_SYMBOL(irlmp_unregister_service);
+EXPORT_SYMBOL(irlmp_service_to_hint);
+EXPORT_SYMBOL(irlmp_data_request);
+EXPORT_SYMBOL(irlmp_open_lsap);
+EXPORT_SYMBOL(irlmp_close_lsap);
+EXPORT_SYMBOL(irlmp_connect_request);
+EXPORT_SYMBOL(irlmp_connect_response);
+EXPORT_SYMBOL(irlmp_disconnect_request);
+EXPORT_SYMBOL(irlmp_get_daddr);
+EXPORT_SYMBOL(irlmp_get_saddr);
+EXPORT_SYMBOL(irlmp_dup);
+EXPORT_SYMBOL(lmp_reasons);
+
+/* Queue */
+EXPORT_SYMBOL(hashbin_find);
+EXPORT_SYMBOL(hashbin_new);
+EXPORT_SYMBOL(hashbin_insert);
+EXPORT_SYMBOL(hashbin_delete);
+EXPORT_SYMBOL(hashbin_remove);
+EXPORT_SYMBOL(hashbin_remove_this);
+EXPORT_SYMBOL(hashbin_get_next);
+EXPORT_SYMBOL(hashbin_get_first);
+
+/* IrLAP */
+EXPORT_SYMBOL(irlap_open);
+EXPORT_SYMBOL(irlap_close);
+EXPORT_SYMBOL(irda_init_max_qos_capabilies);
+EXPORT_SYMBOL(irda_qos_bits_to_value);
+EXPORT_SYMBOL(irda_device_setup);
+EXPORT_SYMBOL(irda_device_set_media_busy);
+EXPORT_SYMBOL(irda_device_txqueue_empty);
+
+EXPORT_SYMBOL(irda_device_dongle_init);
+EXPORT_SYMBOL(irda_device_dongle_cleanup);
+EXPORT_SYMBOL(irda_device_register_dongle);
+EXPORT_SYMBOL(irda_device_unregister_dongle);
+EXPORT_SYMBOL(irda_task_execute);
+EXPORT_SYMBOL(irda_task_kick);
+EXPORT_SYMBOL(irda_task_next_state);
+EXPORT_SYMBOL(irda_task_delete);
+
+EXPORT_SYMBOL(async_wrap_skb);
+EXPORT_SYMBOL(async_unwrap_char);
+EXPORT_SYMBOL(irda_calc_crc16);
+EXPORT_SYMBOL(irda_crc16_table);
+EXPORT_SYMBOL(irda_start_timer);
+EXPORT_SYMBOL(setup_dma);
+EXPORT_SYMBOL(infrared_mode);
+
+#ifdef CONFIG_IRTTY
+EXPORT_SYMBOL(irtty_set_dtr_rts);
+EXPORT_SYMBOL(irtty_register_dongle);
+EXPORT_SYMBOL(irtty_unregister_dongle);
+EXPORT_SYMBOL(irtty_set_packet_mode);
+#endif
+
+int __init irda_init(void)
+{
+ IRDA_DEBUG(0, "%s()\n", __FUNCTION__);
+
+ irlmp_init();
+ irlap_init();
+
+ iriap_init();
+ irttp_init();
+
+#ifdef CONFIG_PROC_FS
+ irda_proc_register();
+#endif
+#ifdef CONFIG_SYSCTL
+ irda_sysctl_register();
+#endif
+ /*
+ * Initialize modules that got compiled into the kernel
+ */
+#ifdef CONFIG_IRLAN
+ irlan_init();
+#endif
+#ifdef CONFIG_IRCOMM
+ ircomm_init();
+ ircomm_tty_init();
+#endif
+ return 0;
+}
+
+void __exit irda_cleanup(void)
+{
+#ifdef CONFIG_SYSCTL
+ irda_sysctl_unregister();
+#endif
+
+#ifdef CONFIG_PROC_FS
+ irda_proc_unregister();
+#endif
+ /* Remove higher layers */
+ irttp_cleanup();
+ iriap_cleanup();
+
+ /* Remove lower layers */
+ irda_device_cleanup();
+ irlap_cleanup(); /* Must be done before irlmp_cleanup()! DB */
+
+ /* Remove middle layer */
+ irlmp_cleanup();
+}
+
+/*
+ * Function irda_notify_init (notify)
+ *
+ * Used for initializing the notify structure
+ *
+ */
+void irda_notify_init(notify_t *notify)
+{
+ notify->data_indication = NULL;
+ notify->udata_indication = NULL;
+ notify->connect_confirm = NULL;
+ notify->connect_indication = NULL;
+ notify->disconnect_indication = NULL;
+ notify->flow_indication = NULL;
+ notify->status_indication = NULL;
+ notify->instance = NULL;
+ strncpy(notify->name, "Unknown", NOTIFY_MAX_NAME);
+}
+
diff --git a/uClinux-2.4.31-uc0/net/irda/irsysctl.c b/uClinux-2.4.31-uc0/net/irda/irsysctl.c
new file mode 100644
index 0000000..e08556f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irsysctl.c
@@ -0,0 +1,187 @@
+/*********************************************************************
+ *
+ * Filename: irsysctl.c
+ * Version: 1.0
+ * Description: Sysctl interface for IrDA
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sun May 24 22:12:06 1998
+ * Modified at: Fri Jun 4 02:50:15 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1997, 1999 Dag Brattli, All Rights Reserved.
+ * Copyright (c) 2000-2001 Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/mm.h>
+#include <linux/ctype.h>
+#include <linux/sysctl.h>
+#include <asm/segment.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irias_object.h>
+
+#define NET_IRDA 412 /* Random number */
+enum { DISCOVERY=1, DEVNAME, DEBUG, FAST_POLL, DISCOVERY_SLOTS,
+ DISCOVERY_TIMEOUT, SLOT_TIMEOUT, MAX_BAUD_RATE, MIN_TX_TURN_TIME,
+ MAX_TX_DATA_SIZE, MAX_TX_WINDOW, MAX_NOREPLY_TIME, WARN_NOREPLY_TIME,
+ LAP_KEEPALIVE_TIME };
+
+extern int sysctl_discovery;
+extern int sysctl_discovery_slots;
+extern int sysctl_discovery_timeout;
+extern int sysctl_slot_timeout;
+extern int sysctl_fast_poll_increase;
+int sysctl_compression = 0;
+extern char sysctl_devname[];
+extern int sysctl_max_baud_rate;
+extern int sysctl_min_tx_turn_time;
+extern int sysctl_max_tx_data_size;
+extern int sysctl_max_tx_window;
+extern int sysctl_max_noreply_time;
+extern int sysctl_warn_noreply_time;
+extern int sysctl_lap_keepalive_time;
+
+#ifdef CONFIG_IRDA_DEBUG
+extern unsigned int irda_debug;
+#endif
+
+/* this is needed for the proc_dointvec_minmax - Jean II */
+static int max_discovery_slots = 16; /* ??? */
+static int min_discovery_slots = 1;
+/* IrLAP 6.13.2 says 25ms to 10+70ms - allow higher since some devices
+ * seems to require it. (from Dag's comment) */
+static int max_slot_timeout = 160;
+static int min_slot_timeout = 20;
+static int max_max_baud_rate = 16000000; /* See qos.c - IrLAP spec */
+static int min_max_baud_rate = 2400;
+static int max_min_tx_turn_time = 10000; /* See qos.c - IrLAP spec */
+static int min_min_tx_turn_time = 0;
+static int max_max_tx_data_size = 2048; /* See qos.c - IrLAP spec */
+static int min_max_tx_data_size = 64;
+static int max_max_tx_window = 7; /* See qos.c - IrLAP spec */
+static int min_max_tx_window = 1;
+static int max_max_noreply_time = 40; /* See qos.c - IrLAP spec */
+static int min_max_noreply_time = 3;
+static int max_warn_noreply_time = 3; /* 3s == standard */
+static int min_warn_noreply_time = 1; /* 1s == min WD_TIMER */
+static int max_lap_keepalive_time = 10000; /* 10s */
+static int min_lap_keepalive_time = 100; /* 100us */
+/* For other sysctl, I've no idea of the range. Maybe Dag could help
+ * us on that - Jean II */
+
+static int do_devname(ctl_table *table, int write, struct file *filp,
+ void *buffer, size_t *lenp)
+{
+ int ret;
+
+ ret = proc_dostring(table, write, filp, buffer, lenp);
+ if (ret == 0 && write) {
+ struct ias_value *val;
+
+ val = irias_new_string_value(sysctl_devname);
+ if (val)
+ irias_object_change_attribute("Device", "DeviceName", val);
+ }
+ return ret;
+}
+
+/* One file */
+static ctl_table irda_table[] = {
+ { DISCOVERY, "discovery", &sysctl_discovery,
+ sizeof(int), 0644, NULL, &proc_dointvec },
+ { DEVNAME, "devname", sysctl_devname,
+ 65, 0644, NULL, &do_devname, &sysctl_string},
+#ifdef CONFIG_IRDA_DEBUG
+ { DEBUG, "debug", &irda_debug,
+ sizeof(int), 0644, NULL, &proc_dointvec },
+#endif
+#ifdef CONFIG_IRDA_FAST_RR
+ { FAST_POLL, "fast_poll_increase", &sysctl_fast_poll_increase,
+ sizeof(int), 0644, NULL, &proc_dointvec },
+#endif
+ { DISCOVERY_SLOTS, "discovery_slots", &sysctl_discovery_slots,
+ sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec,
+ NULL, &min_discovery_slots, &max_discovery_slots },
+ { DISCOVERY_TIMEOUT, "discovery_timeout", &sysctl_discovery_timeout,
+ sizeof(int), 0644, NULL, &proc_dointvec },
+ { SLOT_TIMEOUT, "slot_timeout", &sysctl_slot_timeout,
+ sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec,
+ NULL, &min_slot_timeout, &max_slot_timeout },
+ { MAX_BAUD_RATE, "max_baud_rate", &sysctl_max_baud_rate,
+ sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec,
+ NULL, &min_max_baud_rate, &max_max_baud_rate },
+ { MIN_TX_TURN_TIME, "min_tx_turn_time", &sysctl_min_tx_turn_time,
+ sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec,
+ NULL, &min_min_tx_turn_time, &max_min_tx_turn_time },
+ { MAX_TX_DATA_SIZE, "max_tx_data_size", &sysctl_max_tx_data_size,
+ sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec,
+ NULL, &min_max_tx_data_size, &max_max_tx_data_size },
+ { MAX_TX_WINDOW, "max_tx_window", &sysctl_max_tx_window,
+ sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec,
+ NULL, &min_max_tx_window, &max_max_tx_window },
+ { MAX_NOREPLY_TIME, "max_noreply_time", &sysctl_max_noreply_time,
+ sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec,
+ NULL, &min_max_noreply_time, &max_max_noreply_time },
+ { WARN_NOREPLY_TIME, "warn_noreply_time", &sysctl_warn_noreply_time,
+ sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec,
+ NULL, &min_warn_noreply_time, &max_warn_noreply_time },
+ { LAP_KEEPALIVE_TIME, "lap_keepalive_time", &sysctl_lap_keepalive_time,
+ sizeof(int), 0644, NULL, &proc_dointvec_minmax, &sysctl_intvec,
+ NULL, &min_lap_keepalive_time, &max_lap_keepalive_time },
+ { 0 }
+};
+
+/* One directory */
+static ctl_table irda_net_table[] = {
+ { NET_IRDA, "irda", NULL, 0, 0555, irda_table },
+ { 0 }
+};
+
+/* The parent directory */
+static ctl_table irda_root_table[] = {
+ { CTL_NET, "net", NULL, 0, 0555, irda_net_table },
+ { 0 }
+};
+
+static struct ctl_table_header *irda_table_header;
+
+/*
+ * Function irda_sysctl_register (void)
+ *
+ * Register our sysctl interface
+ *
+ */
+int irda_sysctl_register(void)
+{
+ irda_table_header = register_sysctl_table(irda_root_table, 0);
+ if (!irda_table_header)
+ return -ENOMEM;
+
+ return 0;
+}
+
+/*
+ * Function irda_sysctl_unregister (void)
+ *
+ * Unregister our sysctl interface
+ *
+ */
+void irda_sysctl_unregister(void)
+{
+ unregister_sysctl_table(irda_table_header);
+}
+
+
+
diff --git a/uClinux-2.4.31-uc0/net/irda/irttp.c b/uClinux-2.4.31-uc0/net/irda/irttp.c
new file mode 100644
index 0000000..117f66b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/irttp.c
@@ -0,0 +1,1778 @@
+/*********************************************************************
+ *
+ * Filename: irttp.c
+ * Version: 1.2
+ * Description: Tiny Transport Protocol (TTP) implementation
+ * Status: Stable
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sun Aug 31 20:14:31 1997
+ * Modified at: Wed Jan 5 11:31:27 2000
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1998-2000 Dag Brattli <dagb@cs.uit.no>,
+ * All Rights Reserved.
+ * Copyright (c) 2000-2001 Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <linux/skbuff.h>
+#include <linux/init.h>
+
+#include <asm/byteorder.h>
+#include <asm/unaligned.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irmod.h>
+#include <net/irda/irlap.h>
+#include <net/irda/irlmp.h>
+#include <net/irda/parameters.h>
+#include <net/irda/irttp.h>
+
+static struct irttp_cb *irttp = NULL;
+
+static void __irttp_close_tsap(struct tsap_cb *self);
+
+static int irttp_data_indication(void *instance, void *sap,
+ struct sk_buff *skb);
+static int irttp_udata_indication(void *instance, void *sap,
+ struct sk_buff *skb);
+static void irttp_disconnect_indication(void *instance, void *sap,
+ LM_REASON reason, struct sk_buff *);
+static void irttp_connect_indication(void *instance, void *sap,
+ struct qos_info *qos, __u32 max_sdu_size,
+ __u8 header_size, struct sk_buff *skb);
+static void irttp_connect_confirm(void *instance, void *sap,
+ struct qos_info *qos, __u32 max_sdu_size,
+ __u8 header_size, struct sk_buff *skb);
+static void irttp_run_tx_queue(struct tsap_cb *self);
+static void irttp_run_rx_queue(struct tsap_cb *self);
+
+static void irttp_flush_queues(struct tsap_cb *self);
+static void irttp_fragment_skb(struct tsap_cb *self, struct sk_buff *skb);
+static struct sk_buff *irttp_reassemble_skb(struct tsap_cb *self);
+static void irttp_todo_expired(unsigned long data);
+static int irttp_param_max_sdu_size(void *instance, irda_param_t *param,
+ int get);
+
+/* Information for parsing parameters in IrTTP */
+static pi_minor_info_t pi_minor_call_table[] = {
+ { NULL, 0 }, /* 0x00 */
+ { irttp_param_max_sdu_size, PV_INTEGER | PV_BIG_ENDIAN } /* 0x01 */
+};
+static pi_major_info_t pi_major_call_table[] = {{ pi_minor_call_table, 2 }};
+static pi_param_info_t param_info = { pi_major_call_table, 1, 0x0f, 4 };
+
+/************************ GLOBAL PROCEDURES ************************/
+
+/*
+ * Function irttp_init (void)
+ *
+ * Initialize the IrTTP layer. Called by module initialization code
+ *
+ */
+int __init irttp_init(void)
+{
+ /* Initialize the irttp structure. */
+ if (irttp == NULL) {
+ irttp = kmalloc(sizeof(struct irttp_cb), GFP_KERNEL);
+ if (irttp == NULL)
+ return -ENOMEM;
+ }
+ memset(irttp, 0, sizeof(struct irttp_cb));
+
+ irttp->magic = TTP_MAGIC;
+
+ irttp->tsaps = hashbin_new(HB_LOCAL);
+ if (!irttp->tsaps) {
+ ERROR("%s(), can't allocate IrTTP hashbin!\n", __FUNCTION__);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/*
+ * Function irttp_cleanup (void)
+ *
+ * Called by module destruction/cleanup code
+ *
+ */
+#ifdef MODULE
+void irttp_cleanup(void)
+{
+ /* Check for main structure */
+ ASSERT(irttp != NULL, return;);
+ ASSERT(irttp->magic == TTP_MAGIC, return;);
+
+ /*
+ * Delete hashbin and close all TSAP instances in it
+ */
+ hashbin_delete(irttp->tsaps, (FREE_FUNC) __irttp_close_tsap);
+
+ irttp->magic = 0;
+
+ /* De-allocate main structure */
+ kfree(irttp);
+
+ irttp = NULL;
+}
+#endif
+
+/*************************** SUBROUTINES ***************************/
+
+/*
+ * Function irttp_start_todo_timer (self, timeout)
+ *
+ * Start todo timer.
+ *
+ * Made it more effient and unsensitive to race conditions - Jean II
+ */
+static inline void irttp_start_todo_timer(struct tsap_cb *self, int timeout)
+{
+ /* Set new value for timer */
+ mod_timer(&self->todo_timer, jiffies + timeout);
+}
+
+/*
+ * Function irttp_todo_expired (data)
+ *
+ * Todo timer has expired!
+ *
+ * One of the restriction of the timer is that it is run only on the timer
+ * interrupt which run every 10ms. This mean that even if you set the timer
+ * with a delay of 0, it may take up to 10ms before it's run.
+ * So, to minimise latency and keep cache fresh, we try to avoid using
+ * it as much as possible.
+ * Note : we can't use tasklets, because they can't be asynchronously
+ * killed (need user context), and we can't guarantee that here...
+ * Jean II
+ */
+static void irttp_todo_expired(unsigned long data)
+{
+ struct tsap_cb *self = (struct tsap_cb *) data;
+
+ /* Check that we still exist */
+ if (!self || self->magic != TTP_TSAP_MAGIC)
+ return;
+
+ IRDA_DEBUG(4, "%s(instance=%p)\n", __FUNCTION__, self);
+
+ /* Try to make some progress, especially on Tx side - Jean II */
+ irttp_run_rx_queue(self);
+ irttp_run_tx_queue(self);
+
+ /* Check if time for disconnect */
+ if (test_bit(0, &self->disconnect_pend)) {
+ /* Check if it's possible to disconnect yet */
+ if (skb_queue_empty(&self->tx_queue)) {
+ /* Make sure disconnect is not pending anymore */
+ clear_bit(0, &self->disconnect_pend); /* FALSE */
+
+ /* Note : self->disconnect_skb may be NULL */
+ irttp_disconnect_request(self, self->disconnect_skb,
+ P_NORMAL);
+ self->disconnect_skb = NULL;
+ } else {
+ /* Try again later */
+ irttp_start_todo_timer(self, HZ/10);
+
+ /* No reason to try and close now */
+ return;
+ }
+ }
+
+ /* Check if it's closing time */
+ if (self->close_pend)
+ /* Finish cleanup */
+ irttp_close_tsap(self);
+}
+
+/*
+ * Function irttp_flush_queues (self)
+ *
+ * Flushes (removes all frames) in transitt-buffer (tx_list)
+ */
+void irttp_flush_queues(struct tsap_cb *self)
+{
+ struct sk_buff* skb;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return;);
+
+ /* Deallocate frames waiting to be sent */
+ while ((skb = skb_dequeue(&self->tx_queue)) != NULL)
+ dev_kfree_skb(skb);
+
+ /* Deallocate received frames */
+ while ((skb = skb_dequeue(&self->rx_queue)) != NULL)
+ dev_kfree_skb(skb);
+
+ /* Deallocate received fragments */
+ while ((skb = skb_dequeue(&self->rx_fragments)) != NULL)
+ dev_kfree_skb(skb);
+}
+
+/*
+ * Function irttp_reassemble (self)
+ *
+ * Makes a new (continuous) skb of all the fragments in the fragment
+ * queue
+ *
+ */
+static struct sk_buff *irttp_reassemble_skb(struct tsap_cb *self)
+{
+ struct sk_buff *skb, *frag;
+ int n = 0; /* Fragment index */
+
+ ASSERT(self != NULL, return NULL;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return NULL;);
+
+ IRDA_DEBUG(2, "%s(), self->rx_sdu_size=%d\n", __FUNCTION__,
+ self->rx_sdu_size);
+
+ skb = dev_alloc_skb(TTP_HEADER + self->rx_sdu_size);
+ if (!skb)
+ return NULL;
+
+ /*
+ * Need to reserve space for TTP header in case this skb needs to
+ * be requeued in case delivery failes
+ */
+ skb_reserve(skb, TTP_HEADER);
+ skb_put(skb, self->rx_sdu_size);
+
+ /*
+ * Copy all fragments to a new buffer
+ */
+ while ((frag = skb_dequeue(&self->rx_fragments)) != NULL) {
+ memcpy(skb->data+n, frag->data, frag->len);
+ n += frag->len;
+
+ dev_kfree_skb(frag);
+ }
+ IRDA_DEBUG(2, "%s(), frame len=%d\n", __FUNCTION__, n);
+
+ IRDA_DEBUG(2, "%s(), rx_sdu_size=%d\n", __FUNCTION__, self->rx_sdu_size);
+ ASSERT(n <= self->rx_sdu_size, return NULL;);
+
+ /* Set the new length */
+ skb_trim(skb, n);
+
+ self->rx_sdu_size = 0;
+
+ return skb;
+}
+
+/*
+ * Function irttp_fragment_skb (skb)
+ *
+ * Fragments a frame and queues all the fragments for transmission
+ *
+ */
+static inline void irttp_fragment_skb(struct tsap_cb *self,
+ struct sk_buff *skb)
+{
+ struct sk_buff *frag;
+ __u8 *frame;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+
+ /*
+ * Split frame into a number of segments
+ */
+ while (skb->len > self->max_seg_size) {
+ IRDA_DEBUG(2, "%s(), fragmenting ...\n", __FUNCTION__);
+
+ /* Make new segment */
+ frag = dev_alloc_skb(self->max_seg_size+self->max_header_size);
+ if (!frag)
+ return;
+
+ skb_reserve(frag, self->max_header_size);
+
+ /* Copy data from the original skb into this fragment. */
+ memcpy(skb_put(frag, self->max_seg_size), skb->data,
+ self->max_seg_size);
+
+ /* Insert TTP header, with the more bit set */
+ frame = skb_push(frag, TTP_HEADER);
+ frame[0] = TTP_MORE;
+
+ /* Hide the copied data from the original skb */
+ skb_pull(skb, self->max_seg_size);
+
+ /* Queue fragment */
+ skb_queue_tail(&self->tx_queue, frag);
+ }
+ /* Queue what is left of the original skb */
+ IRDA_DEBUG(2, "%s(), queuing last segment\n", __FUNCTION__);
+
+ frame = skb_push(skb, TTP_HEADER);
+ frame[0] = 0x00; /* Clear more bit */
+
+ /* Queue fragment */
+ skb_queue_tail(&self->tx_queue, skb);
+}
+
+/*
+ * Function irttp_param_max_sdu_size (self, param)
+ *
+ * Handle the MaxSduSize parameter in the connect frames, this function
+ * will be called both when this parameter needs to be inserted into, and
+ * extracted from the connect frames
+ */
+static int irttp_param_max_sdu_size(void *instance, irda_param_t *param,
+ int get)
+{
+ struct tsap_cb *self;
+
+ self = (struct tsap_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return -1;);
+
+ if (get)
+ param->pv.i = self->tx_max_sdu_size;
+ else
+ self->tx_max_sdu_size = param->pv.i;
+
+ IRDA_DEBUG(1, "%s(), MaxSduSize=%d\n", __FUNCTION__, param->pv.i);
+
+ return 0;
+}
+
+/*************************** CLIENT CALLS ***************************/
+/************************** LMP CALLBACKS **************************/
+/* Everything is happily mixed up. Waiting for next clean up - Jean II */
+
+/*
+ * Function irttp_open_tsap (stsap, notify)
+ *
+ * Create TSAP connection endpoint,
+ */
+struct tsap_cb *irttp_open_tsap(__u8 stsap_sel, int credit, notify_t *notify)
+{
+ struct tsap_cb *self;
+ struct lsap_cb *lsap;
+ notify_t ttp_notify;
+
+ ASSERT(irttp != NULL, return NULL;);
+ ASSERT(irttp->magic == TTP_MAGIC, return NULL;);
+
+ /* The IrLMP spec (IrLMP 1.1 p10) says that we have the right to
+ * use only 0x01-0x6F. Of course, we can use LSAP_ANY as well.
+ * JeanII */
+ if((stsap_sel != LSAP_ANY) &&
+ ((stsap_sel < 0x01) || (stsap_sel >= 0x70))) {
+ IRDA_DEBUG(0, "%s(), invalid tsap!\n", __FUNCTION__);
+ return NULL;
+ }
+
+ self = kmalloc(sizeof(struct tsap_cb), GFP_ATOMIC);
+ if (self == NULL) {
+ IRDA_DEBUG(0, "%s(), unable to kmalloc!\n", __FUNCTION__);
+ return NULL;
+ }
+ memset(self, 0, sizeof(struct tsap_cb));
+ spin_lock_init(&self->lock);
+
+ /* Initialise todo timer */
+ init_timer(&self->todo_timer);
+ self->todo_timer.data = (unsigned long) self;
+ self->todo_timer.function = &irttp_todo_expired;
+
+ /* Initialize callbacks for IrLMP to use */
+ irda_notify_init(&ttp_notify);
+ ttp_notify.connect_confirm = irttp_connect_confirm;
+ ttp_notify.connect_indication = irttp_connect_indication;
+ ttp_notify.disconnect_indication = irttp_disconnect_indication;
+ ttp_notify.data_indication = irttp_data_indication;
+ ttp_notify.udata_indication = irttp_udata_indication;
+ ttp_notify.flow_indication = irttp_flow_indication;
+ if(notify->status_indication != NULL)
+ ttp_notify.status_indication = irttp_status_indication;
+ ttp_notify.instance = self;
+ strncpy(ttp_notify.name, notify->name, NOTIFY_MAX_NAME);
+
+ self->magic = TTP_TSAP_MAGIC;
+ self->connected = FALSE;
+
+ skb_queue_head_init(&self->rx_queue);
+ skb_queue_head_init(&self->tx_queue);
+ skb_queue_head_init(&self->rx_fragments);
+ /*
+ * Create LSAP at IrLMP layer
+ */
+ lsap = irlmp_open_lsap(stsap_sel, &ttp_notify, 0);
+ if (lsap == NULL) {
+ WARNING("%s(), unable to allocate LSAP!!\n", __FUNCTION__);
+ return NULL;
+ }
+
+ /*
+ * If user specified LSAP_ANY as source TSAP selector, then IrLMP
+ * will replace it with whatever source selector which is free, so
+ * the stsap_sel we have might not be valid anymore
+ */
+ self->stsap_sel = lsap->slsap_sel;
+ IRDA_DEBUG(4, "%s(), stsap_sel=%02x\n", __FUNCTION__, self->stsap_sel);
+
+ self->notify = *notify;
+ self->lsap = lsap;
+
+ hashbin_insert(irttp->tsaps, (irda_queue_t *) self, (int) self, NULL);
+
+ if (credit > TTP_RX_MAX_CREDIT)
+ self->initial_credit = TTP_RX_MAX_CREDIT;
+ else
+ self->initial_credit = credit;
+
+ return self;
+}
+
+/*
+ * Function irttp_close (handle)
+ *
+ * Remove an instance of a TSAP. This function should only deal with the
+ * deallocation of the TSAP, and resetting of the TSAPs values;
+ *
+ */
+static void __irttp_close_tsap(struct tsap_cb *self)
+{
+ /* First make sure we're connected. */
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return;);
+
+ irttp_flush_queues(self);
+
+ del_timer(&self->todo_timer);
+
+ /* This one won't be cleaned up if we are disconnect_pend + close_pend
+ * and we receive a disconnect_indication */
+ if (self->disconnect_skb)
+ dev_kfree_skb(self->disconnect_skb);
+
+ self->connected = FALSE;
+ self->magic = ~TTP_TSAP_MAGIC;
+
+ kfree(self);
+}
+
+/*
+ * Function irttp_close (self)
+ *
+ * Remove TSAP from list of all TSAPs and then deallocate all resources
+ * associated with this TSAP
+ *
+ * Note : because we *free* the tsap structure, it is the responsability
+ * of the caller to make sure we are called only once and to deal with
+ * possible race conditions. - Jean II
+ */
+int irttp_close_tsap(struct tsap_cb *self)
+{
+ struct tsap_cb *tsap;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return -1;);
+
+ /* Make sure tsap has been disconnected */
+ if (self->connected) {
+ /* Check if disconnect is not pending */
+ if (!test_bit(0, &self->disconnect_pend)) {
+ WARNING("%s(), TSAP still connected!\n", __FUNCTION__);
+ irttp_disconnect_request(self, NULL, P_NORMAL);
+ }
+ self->close_pend = TRUE;
+ irttp_start_todo_timer(self, HZ/10);
+
+ return 0; /* Will be back! */
+ }
+
+ tsap = hashbin_remove(irttp->tsaps, (int) self, NULL);
+
+ ASSERT(tsap == self, return -1;);
+
+ /* Close corresponding LSAP */
+ if (self->lsap) {
+ irlmp_close_lsap(self->lsap);
+ self->lsap = NULL;
+ }
+
+ __irttp_close_tsap(self);
+
+ return 0;
+}
+
+/*
+ * Function irttp_udata_request (self, skb)
+ *
+ * Send unreliable data on this TSAP
+ *
+ */
+int irttp_udata_request(struct tsap_cb *self, struct sk_buff *skb)
+{
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return -1;);
+ ASSERT(skb != NULL, return -1;);
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ /* Check that nothing bad happens */
+ if ((skb->len == 0) || (!self->connected)) {
+ IRDA_DEBUG(1, "%s(), No data, or not connected\n", __FUNCTION__);
+ return -1;
+ }
+
+ if (skb->len > self->max_seg_size) {
+ IRDA_DEBUG(1, "%s(), UData is to large for IrLAP!\n", __FUNCTION__);
+ return -1;
+ }
+
+ irlmp_udata_request(self->lsap, skb);
+ self->stats.tx_packets++;
+
+ return 0;
+}
+
+/*
+ * Function irttp_data_request (handle, skb)
+ *
+ * Queue frame for transmission. If SAR is enabled, fragement the frame
+ * and queue the fragments for transmission
+ */
+int irttp_data_request(struct tsap_cb *self, struct sk_buff *skb)
+{
+ __u8 *frame;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return -1;);
+ ASSERT(skb != NULL, return -1;);
+
+ IRDA_DEBUG(2, "%s : queue len = %d\n", __FUNCTION__,
+ skb_queue_len(&self->tx_queue));
+
+ /* Check that nothing bad happens */
+ if ((skb->len == 0) || (!self->connected)) {
+ WARNING("%s(), No data, or not connected\n", __FUNCTION__);
+ return -ENOTCONN;
+ }
+
+ /*
+ * Check if SAR is disabled, and the frame is larger than what fits
+ * inside an IrLAP frame
+ */
+ if ((self->tx_max_sdu_size == 0) && (skb->len > self->max_seg_size)) {
+ ERROR("%s(), SAR disabled, and data is to large for IrLAP!\n", __FUNCTION__);
+ return -EMSGSIZE;
+ }
+
+ /*
+ * Check if SAR is enabled, and the frame is larger than the
+ * TxMaxSduSize
+ */
+ if ((self->tx_max_sdu_size != 0) &&
+ (self->tx_max_sdu_size != TTP_SAR_UNBOUND) &&
+ (skb->len > self->tx_max_sdu_size))
+ {
+ ERROR("%s(), SAR enabled, "
+ "but data is larger than TxMaxSduSize!\n", __FUNCTION__);
+ return -EMSGSIZE;
+ }
+ /*
+ * Check if transmit queue is full
+ */
+ if (skb_queue_len(&self->tx_queue) >= TTP_TX_MAX_QUEUE) {
+ /*
+ * Give it a chance to empty itself
+ */
+ irttp_run_tx_queue(self);
+
+ /* Drop packet. This error code should trigger the caller
+ * to requeue the packet in the client code - Jean II */
+ return -ENOBUFS;
+ }
+
+ /* Queue frame, or queue frame segments */
+ if ((self->tx_max_sdu_size == 0) || (skb->len < self->max_seg_size)) {
+ /* Queue frame */
+ ASSERT(skb_headroom(skb) >= TTP_HEADER, return -1;);
+ frame = skb_push(skb, TTP_HEADER);
+ frame[0] = 0x00; /* Clear more bit */
+
+ skb_queue_tail(&self->tx_queue, skb);
+ } else {
+ /*
+ * Fragment the frame, this function will also queue the
+ * fragments, we don't care about the fact the transmit
+ * queue may be overfilled by all the segments for a little
+ * while
+ */
+ irttp_fragment_skb(self, skb);
+ }
+
+ /* Check if we can accept more data from client */
+ if ((!self->tx_sdu_busy) &&
+ (skb_queue_len(&self->tx_queue) > TTP_TX_HIGH_THRESHOLD)) {
+ /* Tx queue filling up, so stop client. */
+ if (self->notify.flow_indication) {
+ self->notify.flow_indication(self->notify.instance,
+ self, FLOW_STOP);
+ }
+ /* self->tx_sdu_busy is the state of the client.
+ * Update state after notifying client to avoid
+ * race condition with irttp_flow_indication().
+ * If the queue empty itself after our test but before
+ * we set the flag, we will fix ourselves below in
+ * irttp_run_tx_queue().
+ * Jean II */
+ self->tx_sdu_busy = TRUE;
+ }
+
+ /* Try to make some progress */
+ irttp_run_tx_queue(self);
+
+ return 0;
+}
+
+/*
+ * Function irttp_run_tx_queue (self)
+ *
+ * Transmit packets queued for transmission (if possible)
+ *
+ */
+static void irttp_run_tx_queue(struct tsap_cb *self)
+{
+ struct sk_buff *skb;
+ unsigned long flags;
+ int n;
+
+ IRDA_DEBUG(2, "%s() : send_credit = %d, queue_len = %d\n", __FUNCTION__,
+ self->send_credit, skb_queue_len(&self->tx_queue));
+
+ /* Get exclusive access to the tx queue, otherwise don't touch it */
+ if (irda_lock(&self->tx_queue_lock) == FALSE)
+ return;
+
+ /* Try to send out frames as long as we have credits
+ * and as long as LAP is not full. If LAP is full, it will
+ * poll us through irttp_flow_indication() - Jean II */
+ while ((self->send_credit > 0) &&
+ (!irlmp_lap_tx_queue_full(self->lsap)) &&
+ (skb = skb_dequeue(&self->tx_queue)))
+ {
+ /*
+ * Since we can transmit and receive frames concurrently,
+ * the code below is a critical region and we must assure that
+ * nobody messes with the credits while we update them.
+ */
+ spin_lock_irqsave(&self->lock, flags);
+
+ n = self->avail_credit;
+ self->avail_credit = 0;
+
+ /* Only room for 127 credits in frame */
+ if (n > 127) {
+ self->avail_credit = n-127;
+ n = 127;
+ }
+ self->remote_credit += n;
+ self->send_credit--;
+
+ spin_unlock_irqrestore(&self->lock, flags);
+
+ /*
+ * More bit must be set by the data_request() or fragment()
+ * functions
+ */
+ skb->data[0] |= (n & 0x7f);
+
+ /* Detach from socket.
+ * The current skb has a reference to the socket that sent
+ * it (skb->sk). When we pass it to IrLMP, the skb will be
+ * stored in in IrLAP (self->wx_list). When we are within
+ * IrLAP, we loose the notion of socket, so we should not
+ * have a reference to a socket. So, we drop it here.
+ *
+ * Why does it matter ?
+ * When the skb is freed (kfree_skb), if it is associated
+ * with a socket, it release buffer space on the socket
+ * (through sock_wfree() and sock_def_write_space()).
+ * If the socket no longer exist, we may crash. Hard.
+ * When we close a socket, we make sure that associated packets
+ * in IrTTP are freed. However, we have no way to cancel
+ * the packet that we have passed to IrLAP. So, if a packet
+ * remains in IrLAP (retry on the link or else) after we
+ * close the socket, we are dead !
+ * Jean II */
+ if (skb->sk != NULL) {
+ /* IrSOCK application, IrOBEX, ... */
+ skb_orphan(skb);
+ }
+ /* IrCOMM over IrTTP, IrLAN, ... */
+
+ /* Pass the skb to IrLMP - done */
+ irlmp_data_request(self->lsap, skb);
+ self->stats.tx_packets++;
+ }
+
+ /* Check if we can accept more frames from client.
+ * We don't want to wait until the todo timer to do that, and we
+ * can't use tasklets (grr...), so we are obliged to give control
+ * to client. That's ok, this test will be true not too often
+ * (max once per LAP window) and we are called from places
+ * where we can spend a bit of time doing stuff. - Jean II */
+ if ((self->tx_sdu_busy) &&
+ (skb_queue_len(&self->tx_queue) < TTP_TX_LOW_THRESHOLD) &&
+ (!self->close_pend))
+ {
+ if (self->notify.flow_indication)
+ self->notify.flow_indication(self->notify.instance,
+ self, FLOW_START);
+
+ /* self->tx_sdu_busy is the state of the client.
+ * We don't really have a race here, but it's always safer
+ * to update our state after the client - Jean II */
+ self->tx_sdu_busy = FALSE;
+ }
+
+ /* Reset lock */
+ self->tx_queue_lock = 0;
+}
+
+/*
+ * Function irttp_give_credit (self)
+ *
+ * Send a dataless flowdata TTP-PDU and give available credit to peer
+ * TSAP
+ */
+static inline void irttp_give_credit(struct tsap_cb *self)
+{
+ struct sk_buff *tx_skb = NULL;
+ unsigned long flags;
+ int n;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return;);
+
+ IRDA_DEBUG(4, "%s() send=%d,avail=%d,remote=%d\n", __FUNCTION__,
+ self->send_credit, self->avail_credit, self->remote_credit);
+
+ /* Give credit to peer */
+ tx_skb = dev_alloc_skb(64);
+ if (!tx_skb)
+ return;
+
+ /* Reserve space for LMP, and LAP header */
+ skb_reserve(tx_skb, self->max_header_size);
+
+ /*
+ * Since we can transmit and receive frames concurrently,
+ * the code below is a critical region and we must assure that
+ * nobody messes with the credits while we update them.
+ */
+ spin_lock_irqsave(&self->lock, flags);
+
+ n = self->avail_credit;
+ self->avail_credit = 0;
+
+ /* Only space for 127 credits in frame */
+ if (n > 127) {
+ self->avail_credit = n - 127;
+ n = 127;
+ }
+ self->remote_credit += n;
+
+ spin_unlock_irqrestore(&self->lock, flags);
+
+ skb_put(tx_skb, 1);
+ tx_skb->data[0] = (__u8) (n & 0x7f);
+
+ irlmp_data_request(self->lsap, tx_skb);
+ self->stats.tx_packets++;
+}
+
+/*
+ * Function irttp_udata_indication (instance, sap, skb)
+ *
+ * Received some unit-data (unreliable)
+ *
+ */
+static int irttp_udata_indication(void *instance, void *sap,
+ struct sk_buff *skb)
+{
+ struct tsap_cb *self;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ self = (struct tsap_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return -1;);
+ ASSERT(skb != NULL, return -1;);
+
+ /* Just pass data to layer above */
+ if (self->notify.udata_indication)
+ self->notify.udata_indication(self->notify.instance, self,skb);
+ else
+ dev_kfree_skb(skb);
+
+ self->stats.rx_packets++;
+
+ return 0;
+}
+
+/*
+ * Function irttp_data_indication (instance, sap, skb)
+ *
+ * Receive segment from IrLMP.
+ *
+ */
+static int irttp_data_indication(void *instance, void *sap,
+ struct sk_buff *skb)
+{
+ struct tsap_cb *self;
+ unsigned long flags;
+ int n;
+
+ self = (struct tsap_cb *) instance;
+
+ n = skb->data[0] & 0x7f; /* Extract the credits */
+
+ self->stats.rx_packets++;
+
+ /* Deal with inbound credit
+ * Since we can transmit and receive frames concurrently,
+ * the code below is a critical region and we must assure that
+ * nobody messes with the credits while we update them.
+ */
+ spin_lock_irqsave(&self->lock, flags);
+ self->send_credit += n;
+ if (skb->len > 1)
+ self->remote_credit--;
+ spin_unlock_irqrestore(&self->lock, flags);
+
+ /*
+ * Data or dataless packet? Dataless frames contains only the
+ * TTP_HEADER.
+ */
+ if (skb->len > 1) {
+ /*
+ * We don't remove the TTP header, since we must preserve the
+ * more bit, so the defragment routing knows what to do
+ */
+ skb_queue_tail(&self->rx_queue, skb);
+ } else {
+ /* Dataless flowdata TTP-PDU */
+ dev_kfree_skb(skb);
+ }
+
+
+ /* Push data to the higher layer.
+ * We do it synchronously because running the todo timer for each
+ * receive packet would be too much overhead and latency.
+ * By passing control to the higher layer, we run the risk that
+ * it may take time or grab a lock. Most often, the higher layer
+ * will only put packet in a queue.
+ * Anyway, packets are only dripping through the IrDA, so we can
+ * have time before the next packet.
+ * Further, we are run from NET_BH, so the worse that can happen is
+ * us missing the optimal time to send back the PF bit in LAP.
+ * Jean II */
+ irttp_run_rx_queue(self);
+
+ /* We now give credits to peer in irttp_run_rx_queue().
+ * We need to send credit *NOW*, otherwise we are going
+ * to miss the next Tx window. The todo timer may take
+ * a while before it's run... - Jean II */
+
+ /*
+ * If the peer device has given us some credits and we didn't have
+ * anyone from before, then we need to shedule the tx queue.
+ * We need to do that because our Tx have stopped (so we may not
+ * get any LAP flow indication) and the user may be stopped as
+ * well. - Jean II
+ */
+ if (self->send_credit == n) {
+ /* Restart pushing stuff to LAP */
+ irttp_run_tx_queue(self);
+ /* Note : we don't want to schedule the todo timer
+ * because it has horrible latency. No tasklets
+ * because the tasklet API is broken. - Jean II */
+ }
+
+ return 0;
+}
+
+/*
+ * Function irttp_status_indication (self, reason)
+ *
+ * Status_indication, just pass to the higher layer...
+ *
+ */
+void irttp_status_indication(void *instance,
+ LINK_STATUS link, LOCK_STATUS lock)
+{
+ struct tsap_cb *self;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ self = (struct tsap_cb *) instance;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return;);
+
+ /*
+ * Inform service user if he has requested it
+ */
+ if (self->notify.status_indication != NULL)
+ self->notify.status_indication(self->notify.instance,
+ link, lock);
+ else
+ IRDA_DEBUG(2, "%s(), no handler\n", __FUNCTION__);
+}
+
+/*
+ * Function irttp_flow_indication (self, reason)
+ *
+ * Flow_indication : IrLAP tells us to send more data.
+ *
+ */
+void irttp_flow_indication(void *instance, void *sap, LOCAL_FLOW flow)
+{
+ struct tsap_cb *self;
+
+ self = (struct tsap_cb *) instance;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return;);
+
+ IRDA_DEBUG(4, "%s(instance=%p)\n", __FUNCTION__, self);
+
+ /* We are "polled" directly from LAP, and the LAP want to fill
+ * its Tx window. We want to do our best to send it data, so that
+ * we maximise the window. On the other hand, we want to limit the
+ * amount of work here so that LAP doesn't hang forever waiting
+ * for packets. - Jean II */
+
+ /* Try to send some packets. Currently, LAP calls us every time
+ * there is one free slot, so we will send only one packet.
+ * This allow the scheduler to do its round robin - Jean II */
+ irttp_run_tx_queue(self);
+
+ /* Note regarding the interraction with higher layer.
+ * irttp_run_tx_queue() may call the client when its queue
+ * start to empty, via notify.flow_indication(). Initially.
+ * I wanted this to happen in a tasklet, to avoid client
+ * grabbing the CPU, but we can't use tasklets safely. And timer
+ * is definitely too slow.
+ * This will happen only once per LAP window, and usually at
+ * the third packet (unless window is smaller). LAP is still
+ * doing mtt and sending first packet so it's sort of OK
+ * to do that. Jean II */
+
+ /* If we need to send disconnect. try to do it now */
+ if(self->disconnect_pend)
+ irttp_start_todo_timer(self, 0);
+}
+
+/*
+ * Function irttp_flow_request (self, command)
+ *
+ * This funtion could be used by the upper layers to tell IrTTP to stop
+ * delivering frames if the receive queues are starting to get full, or
+ * to tell IrTTP to start delivering frames again.
+ */
+void irttp_flow_request(struct tsap_cb *self, LOCAL_FLOW flow)
+{
+ IRDA_DEBUG(1, "%s()\n", __FUNCTION__);
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return;);
+
+ switch (flow) {
+ case FLOW_STOP:
+ IRDA_DEBUG(1, "%s(), flow stop\n", __FUNCTION__);
+ self->rx_sdu_busy = TRUE;
+ break;
+ case FLOW_START:
+ IRDA_DEBUG(1, "%s(), flow start\n", __FUNCTION__);
+ self->rx_sdu_busy = FALSE;
+
+ /* Client say he can accept more data, try to free our
+ * queues ASAP - Jean II */
+ irttp_run_rx_queue(self);
+
+ break;
+ default:
+ IRDA_DEBUG(1, "%s(), Unknown flow command!\n", __FUNCTION__);
+ }
+}
+
+/*
+ * Function irttp_connect_request (self, dtsap_sel, daddr, qos)
+ *
+ * Try to connect to remote destination TSAP selector
+ *
+ */
+int irttp_connect_request(struct tsap_cb *self, __u8 dtsap_sel,
+ __u32 saddr, __u32 daddr,
+ struct qos_info *qos, __u32 max_sdu_size,
+ struct sk_buff *userdata)
+{
+ struct sk_buff *skb;
+ __u8 *frame;
+ __u8 n;
+
+ IRDA_DEBUG(4, "%s(), max_sdu_size=%d\n", __FUNCTION__, max_sdu_size);
+
+ ASSERT(self != NULL, return -EBADR;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return -EBADR;);
+
+ if (self->connected)
+ return -EISCONN;
+
+ /* Any userdata supplied? */
+ if (userdata == NULL) {
+ skb = dev_alloc_skb(64);
+ if (!skb)
+ return -ENOMEM;
+
+ /* Reserve space for MUX_CONTROL and LAP header */
+ skb_reserve(skb, TTP_MAX_HEADER);
+ } else {
+ skb = userdata;
+ /*
+ * Check that the client has reserved enough space for
+ * headers
+ */
+ ASSERT(skb_headroom(userdata) >= TTP_MAX_HEADER, return -1;);
+ }
+
+ /* Initialize connection parameters */
+ self->connected = FALSE;
+ self->avail_credit = 0;
+ self->rx_max_sdu_size = max_sdu_size;
+ self->rx_sdu_size = 0;
+ self->rx_sdu_busy = FALSE;
+ self->dtsap_sel = dtsap_sel;
+
+ n = self->initial_credit;
+
+ self->remote_credit = 0;
+ self->send_credit = 0;
+
+ /*
+ * Give away max 127 credits for now
+ */
+ if (n > 127) {
+ self->avail_credit=n-127;
+ n = 127;
+ }
+
+ self->remote_credit = n;
+
+ /* SAR enabled? */
+ if (max_sdu_size > 0) {
+ ASSERT(skb_headroom(skb) >= (TTP_MAX_HEADER + TTP_SAR_HEADER),
+ return -1;);
+
+ /* Insert SAR parameters */
+ frame = skb_push(skb, TTP_HEADER+TTP_SAR_HEADER);
+
+ frame[0] = TTP_PARAMETERS | n;
+ frame[1] = 0x04; /* Length */
+ frame[2] = 0x01; /* MaxSduSize */
+ frame[3] = 0x02; /* Value length */
+
+ put_unaligned(cpu_to_be16((__u16) max_sdu_size),
+ (__u16 *)(frame+4));
+ } else {
+ /* Insert plain TTP header */
+ frame = skb_push(skb, TTP_HEADER);
+
+ /* Insert initial credit in frame */
+ frame[0] = n & 0x7f;
+ }
+
+ /* Connect with IrLMP. No QoS parameters for now */
+ return irlmp_connect_request(self->lsap, dtsap_sel, saddr, daddr, qos,
+ skb);
+}
+
+/*
+ * Function irttp_connect_confirm (handle, qos, skb)
+ *
+ * Sevice user confirms TSAP connection with peer.
+ *
+ */
+static void irttp_connect_confirm(void *instance, void *sap,
+ struct qos_info *qos, __u32 max_seg_size,
+ __u8 max_header_size, struct sk_buff *skb)
+{
+ struct tsap_cb *self;
+ int parameters;
+ int ret;
+ __u8 plen;
+ __u8 n;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ self = (struct tsap_cb *) instance;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+
+ self->max_seg_size = max_seg_size - TTP_HEADER;
+ self->max_header_size = max_header_size + TTP_HEADER;
+
+ /*
+ * Check if we have got some QoS parameters back! This should be the
+ * negotiated QoS for the link.
+ */
+ if (qos) {
+ IRDA_DEBUG(4, "IrTTP, Negotiated BAUD_RATE: %02x\n",
+ qos->baud_rate.bits);
+ IRDA_DEBUG(4, "IrTTP, Negotiated BAUD_RATE: %d bps.\n",
+ qos->baud_rate.value);
+ }
+
+ n = skb->data[0] & 0x7f;
+
+ IRDA_DEBUG(4, "%s(), Initial send_credit=%d\n", __FUNCTION__, n);
+
+ self->send_credit = n;
+ self->tx_max_sdu_size = 0;
+ self->connected = TRUE;
+
+ parameters = skb->data[0] & 0x80;
+
+ ASSERT(skb->len >= TTP_HEADER, return;);
+ skb_pull(skb, TTP_HEADER);
+
+ if (parameters) {
+ plen = skb->data[0];
+
+ ret = irda_param_extract_all(self, skb->data+1,
+ IRDA_MIN(skb->len-1, plen),
+ &param_info);
+
+ /* Any errors in the parameter list? */
+ if (ret < 0) {
+ WARNING("%s(), error extracting parameters\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+
+ /* Do not accept this connection attempt */
+ return;
+ }
+ /* Remove parameters */
+ skb_pull(skb, IRDA_MIN(skb->len, plen+1));
+ }
+
+ IRDA_DEBUG(4, "%s() send=%d,avail=%d,remote=%d\n", __FUNCTION__,
+ self->send_credit, self->avail_credit, self->remote_credit);
+
+ IRDA_DEBUG(2, "%s(), MaxSduSize=%d\n", __FUNCTION__, self->tx_max_sdu_size);
+
+ if (self->notify.connect_confirm) {
+ self->notify.connect_confirm(self->notify.instance, self, qos,
+ self->tx_max_sdu_size,
+ self->max_header_size, skb);
+ }
+}
+
+/*
+ * Function irttp_connect_indication (handle, skb)
+ *
+ * Some other device is connecting to this TSAP
+ *
+ */
+void irttp_connect_indication(void *instance, void *sap, struct qos_info *qos,
+ __u32 max_seg_size, __u8 max_header_size,
+ struct sk_buff *skb)
+{
+ struct tsap_cb *self;
+ struct lsap_cb *lsap;
+ int parameters;
+ int ret;
+ __u8 plen;
+ __u8 n;
+
+ self = (struct tsap_cb *) instance;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return;);
+ ASSERT(skb != NULL, return;);
+
+ lsap = (struct lsap_cb *) sap;
+
+ self->max_seg_size = max_seg_size - TTP_HEADER;;
+ self->max_header_size = max_header_size+TTP_HEADER;
+
+ IRDA_DEBUG(4, "%s(), TSAP sel=%02x\n", __FUNCTION__, self->stsap_sel);
+
+ /* Need to update dtsap_sel if its equal to LSAP_ANY */
+ self->dtsap_sel = lsap->dlsap_sel;
+
+ n = skb->data[0] & 0x7f;
+
+ self->send_credit = n;
+ self->tx_max_sdu_size = 0;
+
+ parameters = skb->data[0] & 0x80;
+
+ ASSERT(skb->len >= TTP_HEADER, return;);
+ skb_pull(skb, TTP_HEADER);
+
+ if (parameters) {
+ plen = skb->data[0];
+
+ ret = irda_param_extract_all(self, skb->data+1,
+ IRDA_MIN(skb->len-1, plen),
+ &param_info);
+
+ /* Any errors in the parameter list? */
+ if (ret < 0) {
+ WARNING("%s(), error extracting parameters\n", __FUNCTION__);
+ dev_kfree_skb(skb);
+
+ /* Do not accept this connection attempt */
+ return;
+ }
+
+ /* Remove parameters */
+ skb_pull(skb, IRDA_MIN(skb->len, plen+1));
+ }
+
+ if (self->notify.connect_indication) {
+ self->notify.connect_indication(self->notify.instance, self,
+ qos, self->tx_max_sdu_size,
+ self->max_header_size, skb);
+ } else
+ dev_kfree_skb(skb);
+}
+
+/*
+ * Function irttp_connect_response (handle, userdata)
+ *
+ * Service user is accepting the connection, just pass it down to
+ * IrLMP!
+ *
+ */
+int irttp_connect_response(struct tsap_cb *self, __u32 max_sdu_size,
+ struct sk_buff *userdata)
+{
+ struct sk_buff *skb;
+ __u8 *frame;
+ int ret;
+ __u8 n;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return -1;);
+
+ IRDA_DEBUG(4, "%s(), Source TSAP selector=%02x\n", __FUNCTION__,
+ self->stsap_sel);
+
+ /* Any userdata supplied? */
+ if (userdata == NULL) {
+ skb = dev_alloc_skb(64);
+ if (!skb)
+ return -ENOMEM;
+
+ /* Reserve space for MUX_CONTROL and LAP header */
+ skb_reserve(skb, TTP_MAX_HEADER);
+ } else {
+ skb = userdata;
+ /*
+ * Check that the client has reserved enough space for
+ * headers
+ */
+ ASSERT(skb_headroom(skb) >= TTP_MAX_HEADER, return -1;);
+ }
+
+ self->avail_credit = 0;
+ self->remote_credit = 0;
+ self->rx_max_sdu_size = max_sdu_size;
+ self->rx_sdu_size = 0;
+ self->rx_sdu_busy = FALSE;
+
+ n = self->initial_credit;
+
+ /* Frame has only space for max 127 credits (7 bits) */
+ if (n > 127) {
+ self->avail_credit = n - 127;
+ n = 127;
+ }
+
+ self->remote_credit = n;
+ self->connected = TRUE;
+
+ /* SAR enabled? */
+ if (max_sdu_size > 0) {
+ ASSERT(skb_headroom(skb) >= (TTP_MAX_HEADER+TTP_SAR_HEADER),
+ return -1;);
+
+ /* Insert TTP header with SAR parameters */
+ frame = skb_push(skb, TTP_HEADER+TTP_SAR_HEADER);
+
+ frame[0] = TTP_PARAMETERS | n;
+ frame[1] = 0x04; /* Length */
+
+ /* irda_param_insert(self, IRTTP_MAX_SDU_SIZE, frame+1, */
+/* TTP_SAR_HEADER, &param_info) */
+
+ frame[2] = 0x01; /* MaxSduSize */
+ frame[3] = 0x02; /* Value length */
+
+ put_unaligned(cpu_to_be16((__u16) max_sdu_size),
+ (__u16 *)(frame+4));
+ } else {
+ /* Insert TTP header */
+ frame = skb_push(skb, TTP_HEADER);
+
+ frame[0] = n & 0x7f;
+ }
+
+ ret = irlmp_connect_response(self->lsap, skb);
+
+ return ret;
+}
+
+/*
+ * Function irttp_dup (self, instance)
+ *
+ * Duplicate TSAP, can be used by servers to confirm a connection on a
+ * new TSAP so it can keep listening on the old one.
+ */
+struct tsap_cb *irttp_dup(struct tsap_cb *orig, void *instance)
+{
+ struct tsap_cb *new;
+
+ IRDA_DEBUG(1, "%s()\n", __FUNCTION__);
+
+ if (!hashbin_find(irttp->tsaps, (int) orig, NULL)) {
+ IRDA_DEBUG(0, "%s(), unable to find TSAP\n", __FUNCTION__);
+ return NULL;
+ }
+ new = kmalloc(sizeof(struct tsap_cb), GFP_ATOMIC);
+ if (!new) {
+ IRDA_DEBUG(0, "%s(), unable to kmalloc\n", __FUNCTION__);
+ return NULL;
+ }
+ /* Dup */
+ memcpy(new, orig, sizeof(struct tsap_cb));
+ new->notify.instance = instance;
+ new->lsap = irlmp_dup(orig->lsap, new);
+
+ /* Not everything should be copied */
+ init_timer(&new->todo_timer);
+
+ skb_queue_head_init(&new->rx_queue);
+ skb_queue_head_init(&new->tx_queue);
+ skb_queue_head_init(&new->rx_fragments);
+
+ hashbin_insert(irttp->tsaps, (irda_queue_t *) new, (int) new, NULL);
+
+ return new;
+}
+
+/*
+ * Function irttp_disconnect_request (self)
+ *
+ * Close this connection please! If priority is high, the queued data
+ * segments, if any, will be deallocated first
+ *
+ */
+int irttp_disconnect_request(struct tsap_cb *self, struct sk_buff *userdata,
+ int priority)
+{
+ struct sk_buff *skb;
+ int ret;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return -1;);
+
+ /* Already disconnected? */
+ if (!self->connected) {
+ IRDA_DEBUG(4, "%s(), already disconnected!\n", __FUNCTION__);
+ if (userdata)
+ dev_kfree_skb(userdata);
+ return -1;
+ }
+
+ /* Disconnect already pending ?
+ * We need to use an atomic operation to prevent reentry. This
+ * function may be called from various context, like user, timer
+ * for following a disconnect_indication() (i.e. net_bh).
+ * Jean II */
+ if(test_and_set_bit(0, &self->disconnect_pend)) {
+ IRDA_DEBUG(0, "%s(), disconnect already pending\n", __FUNCTION__);
+ if (userdata)
+ dev_kfree_skb(userdata);
+
+ /* Try to make some progress */
+ irttp_run_tx_queue(self);
+ return -1;
+ }
+
+ /*
+ * Check if there is still data segments in the transmit queue
+ */
+ if (skb_queue_len(&self->tx_queue) > 0) {
+ if (priority == P_HIGH) {
+ /*
+ * No need to send the queued data, if we are
+ * disconnecting right now since the data will
+ * not have any usable connection to be sent on
+ */
+ IRDA_DEBUG(1, "%s High priority!!()\n", __FUNCTION__);
+ irttp_flush_queues(self);
+ } else if (priority == P_NORMAL) {
+ /*
+ * Must delay disconnect until after all data segments
+ * have been sent and the tx_queue is empty
+ */
+ /* We'll reuse this one later for the disconnect */
+ self->disconnect_skb = userdata; /* May be NULL */
+
+ irttp_run_tx_queue(self);
+
+ irttp_start_todo_timer(self, HZ/10);
+ return -1;
+ }
+ }
+ /* Note : we don't need to check if self->rx_queue is full and the
+ * state of self->rx_sdu_busy because the disconnect response will
+ * be sent at the LMP level (so even if the peer has its Tx queue
+ * full of data). - Jean II */
+
+ IRDA_DEBUG(1, "%s(), Disconnecting ...\n", __FUNCTION__);
+ self->connected = FALSE;
+
+ if (!userdata) {
+ skb = dev_alloc_skb(64);
+ if (!skb)
+ return -ENOMEM;
+
+ /*
+ * Reserve space for MUX and LAP header
+ */
+ skb_reserve(skb, TTP_MAX_HEADER);
+
+ userdata = skb;
+ }
+ ret = irlmp_disconnect_request(self->lsap, userdata);
+
+ /* The disconnect is no longer pending */
+ clear_bit(0, &self->disconnect_pend); /* FALSE */
+
+ return ret;
+}
+
+/*
+ * Function irttp_disconnect_indication (self, reason)
+ *
+ * Disconnect indication, TSAP disconnected by peer?
+ *
+ */
+void irttp_disconnect_indication(void *instance, void *sap, LM_REASON reason,
+ struct sk_buff *skb)
+{
+ struct tsap_cb *self;
+
+ IRDA_DEBUG(4, "%s()\n", __FUNCTION__);
+
+ self = (struct tsap_cb *) instance;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == TTP_TSAP_MAGIC, return;);
+
+ /* Prevent higher layer to send more data */
+ self->connected = FALSE;
+
+ /* Check if client has already tried to close the TSAP */
+ if (self->close_pend) {
+ /* In this case, the higher layer is probably gone. Don't
+ * bother it and clean up the remains - Jean II */
+ if (skb)
+ dev_kfree_skb(skb);
+ irttp_close_tsap(self);
+ return;
+ }
+
+ /* If we are here, we assume that is the higher layer is still
+ * waiting for the disconnect notification and able to process it,
+ * even if he tried to disconnect. Otherwise, it would have already
+ * attempted to close the tsap and self->close_pend would be TRUE.
+ * Jean II */
+
+ /* No need to notify the client if has already tried to disconnect */
+ if(self->notify.disconnect_indication)
+ self->notify.disconnect_indication(self->notify.instance, self,
+ reason, skb);
+ else
+ if (skb)
+ dev_kfree_skb(skb);
+}
+
+/*
+ * Function irttp_do_data_indication (self, skb)
+ *
+ * Try to deliver reassebled skb to layer above, and requeue it if that
+ * for some reason should fail. We mark rx sdu as busy to apply back
+ * pressure is necessary.
+ */
+void irttp_do_data_indication(struct tsap_cb *self, struct sk_buff *skb)
+{
+ int err;
+
+ /* Check if client has already tried to close the TSAP */
+ if (self->close_pend) {
+ dev_kfree_skb(skb);
+ return;
+ }
+
+ err = self->notify.data_indication(self->notify.instance, self, skb);
+
+ /* Usually the layer above will notify that it's input queue is
+ * starting to get filled by using the flow request, but this may
+ * be difficult, so it can instead just refuse to eat it and just
+ * give an error back
+ */
+ if (err == -ENOMEM) {
+ IRDA_DEBUG(0, "%s() requeueing skb!\n", __FUNCTION__);
+
+ /* Make sure we take a break */
+ self->rx_sdu_busy = TRUE;
+
+ /* Need to push the header in again */
+ skb_push(skb, TTP_HEADER);
+ skb->data[0] = 0x00; /* Make sure MORE bit is cleared */
+
+ /* Put skb back on queue */
+ skb_queue_head(&self->rx_queue, skb);
+ }
+}
+
+/*
+ * Function irttp_run_rx_queue (self)
+ *
+ * Check if we have any frames to be transmitted, or if we have any
+ * available credit to give away.
+ */
+void irttp_run_rx_queue(struct tsap_cb *self)
+{
+ struct sk_buff *skb;
+ int more = 0;
+
+ IRDA_DEBUG(2, "%s() send=%d,avail=%d,remote=%d\n", __FUNCTION__,
+ self->send_credit, self->avail_credit, self->remote_credit);
+
+ /* Get exclusive access to the rx queue, otherwise don't touch it */
+ if (irda_lock(&self->rx_queue_lock) == FALSE)
+ return;
+
+ /*
+ * Reassemble all frames in receive queue and deliver them
+ */
+ while (!self->rx_sdu_busy && (skb = skb_dequeue(&self->rx_queue))) {
+ /* This bit will tell us if it's the last fragment or not */
+ more = skb->data[0] & 0x80;
+
+ /* Remove TTP header */
+ skb_pull(skb, TTP_HEADER);
+
+ /* Add the length of the remaining data */
+ self->rx_sdu_size += skb->len;
+
+ /*
+ * If SAR is disabled, or user has requested no reassembly
+ * of received fragments then we just deliver them
+ * immediately. This can be requested by clients that
+ * implements byte streams without any message boundaries
+ */
+ if (self->rx_max_sdu_size == TTP_SAR_DISABLE) {
+ irttp_do_data_indication(self, skb);
+ self->rx_sdu_size = 0;
+
+ continue;
+ }
+
+ /* Check if this is a fragment, and not the last fragment */
+ if (more) {
+ /*
+ * Queue the fragment if we still are within the
+ * limits of the maximum size of the rx_sdu
+ */
+ if (self->rx_sdu_size <= self->rx_max_sdu_size) {
+ IRDA_DEBUG(4, "%s(), queueing frag\n", __FUNCTION__);
+ skb_queue_tail(&self->rx_fragments, skb);
+ } else {
+ /* Free the part of the SDU that is too big */
+ dev_kfree_skb(skb);
+ }
+ continue;
+ }
+ /*
+ * This is the last fragment, so time to reassemble!
+ */
+ if ((self->rx_sdu_size <= self->rx_max_sdu_size) ||
+ (self->rx_max_sdu_size == TTP_SAR_UNBOUND))
+ {
+ /*
+ * A little optimizing. Only queue the fragment if
+ * there are other fragments. Since if this is the
+ * last and only fragment, there is no need to
+ * reassemble :-)
+ */
+ if (!skb_queue_empty(&self->rx_fragments)) {
+ skb_queue_tail(&self->rx_fragments,
+ skb);
+
+ skb = irttp_reassemble_skb(self);
+ }
+
+ /* Now we can deliver the reassembled skb */
+ irttp_do_data_indication(self, skb);
+ } else {
+ IRDA_DEBUG(1, "%s(), Truncated frame\n", __FUNCTION__);
+
+ /* Free the part of the SDU that is too big */
+ dev_kfree_skb(skb);
+
+ /* Deliver only the valid but truncated part of SDU */
+ skb = irttp_reassemble_skb(self);
+
+ irttp_do_data_indication(self, skb);
+ }
+ self->rx_sdu_size = 0;
+ }
+
+ /*
+ * It's not trivial to keep track of how many credits are available
+ * by incrementing at each packet, because delivery may fail
+ * (irttp_do_data_indication() may requeue the frame) and because
+ * we need to take care of fragmentation.
+ * We want the other side to send up to initial_credit packets.
+ * We have some frames in our queues, and we have already allowed it
+ * to send remote_credit.
+ * No need to spinlock, write is atomic and self correcting...
+ * Jean II
+ */
+ self->avail_credit = (self->initial_credit -
+ (self->remote_credit +
+ skb_queue_len(&self->rx_queue) +
+ skb_queue_len(&self->rx_fragments)));
+
+ /* Do we have too much credits to send to peer ? */
+ if ((self->remote_credit <= TTP_RX_MIN_CREDIT) &&
+ (self->avail_credit > 0)) {
+ /* Send explicit credit frame */
+ irttp_give_credit(self);
+ /* Note : do *NOT* check if tx_queue is non-empty, that
+ * will produce deadlocks. I repeat : send a credit frame
+ * even if we have something to send in our Tx queue.
+ * If we have credits, it means that our Tx queue is blocked.
+ *
+ * Let's suppose the peer can't keep up with our Tx. He will
+ * flow control us by not sending us any credits, and we
+ * will stop Tx and start accumulating credits here.
+ * Up to the point where the peer will stop its Tx queue,
+ * for lack of credits.
+ * Let's assume the peer application is single threaded.
+ * It will block on Tx and never consume any Rx buffer.
+ * Deadlock. Guaranteed. - Jean II
+ */
+ }
+
+ /* Reset lock */
+ self->rx_queue_lock = 0;
+}
+
+#ifdef CONFIG_PROC_FS
+/*
+ * Function irttp_proc_read (buf, start, offset, len, unused)
+ *
+ * Give some info to the /proc file system
+ */
+int irttp_proc_read(char *buf, char **start, off_t offset, int len)
+{
+ struct tsap_cb *self;
+ unsigned long flags;
+ int i = 0;
+
+ ASSERT(irttp != NULL, return 0;);
+
+ len = 0;
+
+ save_flags(flags);
+ cli();
+
+ self = (struct tsap_cb *) hashbin_get_first(irttp->tsaps);
+ while (self != NULL) {
+ if (!self || self->magic != TTP_TSAP_MAGIC)
+ break;
+
+ len += sprintf(buf+len, "TSAP %d, ", i++);
+ len += sprintf(buf+len, "stsap_sel: %02x, ",
+ self->stsap_sel);
+ len += sprintf(buf+len, "dtsap_sel: %02x\n",
+ self->dtsap_sel);
+ len += sprintf(buf+len, " connected: %s, ",
+ self->connected? "TRUE":"FALSE");
+ len += sprintf(buf+len, "avail credit: %d, ",
+ self->avail_credit);
+ len += sprintf(buf+len, "remote credit: %d, ",
+ self->remote_credit);
+ len += sprintf(buf+len, "send credit: %d\n",
+ self->send_credit);
+ len += sprintf(buf+len, " tx packets: %ld, ",
+ self->stats.tx_packets);
+ len += sprintf(buf+len, "rx packets: %ld, ",
+ self->stats.rx_packets);
+ len += sprintf(buf+len, "tx_queue len: %d ",
+ skb_queue_len(&self->tx_queue));
+ len += sprintf(buf+len, "rx_queue len: %d\n",
+ skb_queue_len(&self->rx_queue));
+ len += sprintf(buf+len, " tx_sdu_busy: %s, ",
+ self->tx_sdu_busy? "TRUE":"FALSE");
+ len += sprintf(buf+len, "rx_sdu_busy: %s\n",
+ self->rx_sdu_busy? "TRUE":"FALSE");
+ len += sprintf(buf+len, " max_seg_size: %d, ",
+ self->max_seg_size);
+ len += sprintf(buf+len, "tx_max_sdu_size: %d, ",
+ self->tx_max_sdu_size);
+ len += sprintf(buf+len, "rx_max_sdu_size: %d\n",
+ self->rx_max_sdu_size);
+
+ len += sprintf(buf+len, " Used by (%s)\n",
+ self->notify.name);
+
+ len += sprintf(buf+len, "\n");
+
+ self = (struct tsap_cb *) hashbin_get_next(irttp->tsaps);
+ }
+ restore_flags(flags);
+
+ return len;
+}
+
+#endif /* PROC_FS */
diff --git a/uClinux-2.4.31-uc0/net/irda/parameters.c b/uClinux-2.4.31-uc0/net/irda/parameters.c
new file mode 100644
index 0000000..76cdf27
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/parameters.c
@@ -0,0 +1,587 @@
+/*********************************************************************
+ *
+ * Filename: parameters.c
+ * Version: 1.0
+ * Description: A more general way to handle (pi,pl,pv) parameters
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Mon Jun 7 10:25:11 1999
+ * Modified at: Sun Jan 30 14:08:39 2000
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1999-2000 Dag Brattli, All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ ********************************************************************/
+
+#include <linux/types.h>
+#include <asm/unaligned.h>
+#include <asm/byteorder.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/parameters.h>
+
+static int irda_extract_integer(void *self, __u8 *buf, int len, __u8 pi,
+ PV_TYPE type, PI_HANDLER func);
+static int irda_extract_string(void *self, __u8 *buf, int len, __u8 pi,
+ PV_TYPE type, PI_HANDLER func);
+static int irda_extract_octseq(void *self, __u8 *buf, int len, __u8 pi,
+ PV_TYPE type, PI_HANDLER func);
+static int irda_extract_no_value(void *self, __u8 *buf, int len, __u8 pi,
+ PV_TYPE type, PI_HANDLER func);
+
+static int irda_insert_integer(void *self, __u8 *buf, int len, __u8 pi,
+ PV_TYPE type, PI_HANDLER func);
+static int irda_insert_no_value(void *self, __u8 *buf, int len, __u8 pi,
+ PV_TYPE type, PI_HANDLER func);
+
+/* Parameter value call table. Must match PV_TYPE */
+static PV_HANDLER pv_extract_table[] = {
+ irda_extract_integer, /* Handler for any length integers */
+ irda_extract_integer, /* Handler for 8 bits integers */
+ irda_extract_integer, /* Handler for 16 bits integers */
+ irda_extract_string, /* Handler for strings */
+ irda_extract_integer, /* Handler for 32 bits integers */
+ irda_extract_octseq, /* Handler for octet sequences */
+ irda_extract_no_value /* Handler for no value parameters */
+};
+
+static PV_HANDLER pv_insert_table[] = {
+ irda_insert_integer, /* Handler for any length integers */
+ irda_insert_integer, /* Handler for 8 bits integers */
+ irda_insert_integer, /* Handler for 16 bits integers */
+ NULL, /* Handler for strings */
+ irda_insert_integer, /* Handler for 32 bits integers */
+ NULL, /* Handler for octet sequences */
+ irda_insert_no_value /* Handler for no value parameters */
+};
+
+/*
+ * Function irda_insert_no_value (self, buf, len, pi, type, func)
+ *
+ *
+ *
+ */
+static int irda_insert_no_value(void *self, __u8 *buf, int len, __u8 pi,
+ PV_TYPE type, PI_HANDLER func)
+{
+ irda_param_t p;
+ int ret;
+
+ p.pi = pi;
+ p.pl = 0;
+
+ /* Call handler for this parameter */
+ ret = (*func)(self, &p, PV_GET);
+
+ /* Extract values anyway, since handler may need them */
+ irda_param_pack(buf, "bb", p.pi, p.pl);
+
+ if (ret < 0)
+ return ret;
+
+ return 2; /* Inserted pl+2 bytes */
+}
+
+/*
+ * Function irda_extract_no_value (self, buf, len, type, func)
+ *
+ * Extracts a parameter without a pv field (pl=0)
+ *
+ */
+static int irda_extract_no_value(void *self, __u8 *buf, int len, __u8 pi,
+ PV_TYPE type, PI_HANDLER func)
+{
+ irda_param_t p;
+ int ret;
+
+ /* Extract values anyway, since handler may need them */
+ irda_param_unpack(buf, "bb", &p.pi, &p.pl);
+
+ /* Call handler for this parameter */
+ ret = (*func)(self, &p, PV_PUT);
+
+ if (ret < 0)
+ return ret;
+
+ return 2; /* Extracted pl+2 bytes */
+}
+
+/*
+ * Function irda_insert_integer (self, buf, len, pi, type, func)
+ *
+ *
+ *
+ */
+static int irda_insert_integer(void *self, __u8 *buf, int len, __u8 pi,
+ PV_TYPE type, PI_HANDLER func)
+{
+ irda_param_t p;
+ int n = 0;
+ int err;
+
+ p.pi = pi; /* In case handler needs to know */
+ p.pl = type & PV_MASK; /* The integer type codes the lenght as well */
+ p.pv.i = 0; /* Clear value */
+
+ /* Call handler for this parameter */
+ err = (*func)(self, &p, PV_GET);
+ if (err < 0)
+ return err;
+
+ /*
+ * If parameter lenght is still 0, then (1) this is an any length
+ * integer, and (2) the handler function does not care which length
+ * we choose to use, so we pick the one the gives the fewest bytes.
+ */
+ if (p.pl == 0) {
+ if (p.pv.i < 0xff) {
+ IRDA_DEBUG(2, "%s(), using 1 byte\n", __FUNCTION__);
+ p.pl = 1;
+ } else if (p.pv.i < 0xffff) {
+ IRDA_DEBUG(2, "%s(), using 2 bytes\n", __FUNCTION__);
+ p.pl = 2;
+ } else {
+ IRDA_DEBUG(2, "%s(), using 4 bytes\n", __FUNCTION__);
+ p.pl = 4; /* Default length */
+ }
+ }
+ /* Check if buffer is long enough for insertion */
+ if (len < (2+p.pl)) {
+ WARNING("%s(), buffer to short for insertion!\n", __FUNCTION__);
+ return -1;
+ }
+ IRDA_DEBUG(2, "%s(), pi=%#x, pl=%d, pi=%d\n", __FUNCTION__, p.pi, p.pl, p.pv.i);
+ switch (p.pl) {
+ case 1:
+ n += irda_param_pack(buf, "bbb", p.pi, p.pl, (__u8) p.pv.i);
+ break;
+ case 2:
+ if (type & PV_BIG_ENDIAN)
+ p.pv.i = cpu_to_be16((__u16) p.pv.i);
+ else
+ p.pv.i = cpu_to_le16((__u16) p.pv.i);
+ n += irda_param_pack(buf, "bbs", p.pi, p.pl, (__u16) p.pv.i);
+ break;
+ case 4:
+ if (type & PV_BIG_ENDIAN)
+ cpu_to_be32s(&p.pv.i);
+ else
+ cpu_to_le32s(&p.pv.i);
+ n += irda_param_pack(buf, "bbi", p.pi, p.pl, p.pv.i);
+
+ break;
+ default:
+ WARNING("%s() length %d not supported\n", __FUNCTION__, p.pl);
+ /* Skip parameter */
+ return -1;
+ }
+
+ return p.pl+2; /* Inserted pl+2 bytes */
+}
+
+/*
+ * Function irda_extract integer (self, buf, len, pi, type, func)
+ *
+ * Extract a possibly variable length integer from buffer, and call
+ * handler for processing of the parameter
+ */
+static int irda_extract_integer(void *self, __u8 *buf, int len, __u8 pi,
+ PV_TYPE type, PI_HANDLER func)
+{
+ irda_param_t p;
+ int n = 0;
+ int extract_len; /* Real lenght we extract */
+ int err;
+
+ p.pi = pi; /* In case handler needs to know */
+ p.pl = buf[1]; /* Extract lenght of value */
+ p.pv.i = 0; /* Clear value */
+ extract_len = p.pl; /* Default : extract all */
+
+ /* Check if buffer is long enough for parsing */
+ if (len < (2+p.pl)) {
+ WARNING("%s(), buffer to short for parsing! "
+ "Need %d bytes, but len is only %d\n", __FUNCTION__, p.pl, len);
+ return -1;
+ }
+
+ /*
+ * Check that the integer length is what we expect it to be. If the
+ * handler want a 16 bits integer then a 32 bits is not good enough
+ * PV_INTEGER means that the handler is flexible.
+ */
+ if (((type & PV_MASK) != PV_INTEGER) && ((type & PV_MASK) != p.pl)) {
+ ERROR("%s(), invalid parameter length! "
+ "Expected %d bytes, but value had %d bytes!\n",
+ __FUNCTION__, type & PV_MASK, p.pl);
+
+ /* Most parameters are bit/byte fields or little endian,
+ * so it's ok to only extract a subset of it (the subset
+ * that the handler expect). This is necessary, as some
+ * broken implementations seems to add extra undefined bits.
+ * If the parameter is shorter than we expect or is big
+ * endian, we can't play those tricks. Jean II */
+ if((p.pl < (type & PV_MASK)) || (type & PV_BIG_ENDIAN)) {
+ /* Skip parameter */
+ return p.pl+2;
+ } else {
+ /* Extract subset of it, fallthrough */
+ extract_len = type & PV_MASK;
+ }
+ }
+
+
+ switch (extract_len) {
+ case 1:
+ n += irda_param_unpack(buf+2, "b", &p.pv.i);
+ break;
+ case 2:
+ n += irda_param_unpack(buf+2, "s", &p.pv.i);
+ if (type & PV_BIG_ENDIAN)
+ p.pv.i = be16_to_cpu((__u16) p.pv.i);
+ else
+ p.pv.i = le16_to_cpu((__u16) p.pv.i);
+ break;
+ case 4:
+ n += irda_param_unpack(buf+2, "i", &p.pv.i);
+ if (type & PV_BIG_ENDIAN)
+ be32_to_cpus(&p.pv.i);
+ else
+ le32_to_cpus(&p.pv.i);
+ break;
+ default:
+ WARNING("%s() length %d not supported\n", __FUNCTION__, p.pl);
+
+ /* Skip parameter */
+ return p.pl+2;
+ }
+
+ IRDA_DEBUG(2, "%s(), pi=%#x, pl=%d, pi=%d\n", __FUNCTION__, p.pi, p.pl, p.pv.i);
+ /* Call handler for this parameter */
+ err = (*func)(self, &p, PV_PUT);
+ if (err < 0)
+ return err;
+
+ return p.pl+2; /* Extracted pl+2 bytes */
+}
+
+/*
+ * Function irda_extract_string (self, buf, len, type, func)
+ *
+ *
+ *
+ */
+static int irda_extract_string(void *self, __u8 *buf, int len, __u8 pi,
+ PV_TYPE type, PI_HANDLER func)
+{
+ char str[33];
+ irda_param_t p;
+ int err;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ p.pi = pi; /* In case handler needs to know */
+ p.pl = buf[1]; /* Extract lenght of value */
+
+ IRDA_DEBUG(2, "%s(), pi=%#x, pl=%d\n", __FUNCTION__, p.pi, p.pl);
+
+ /* Check if buffer is long enough for parsing */
+ if (len < (2+p.pl)) {
+ WARNING("%s(), buffer to short for parsing! "
+ "Need %d bytes, but len is only %d\n", __FUNCTION__, p.pl, len);
+ return -1;
+ }
+
+ /* Should be safe to copy string like this since we have already
+ * checked that the buffer is long enough */
+ strncpy(str, buf+2, p.pl);
+
+ IRDA_DEBUG(2, "%s(), str=0x%02x 0x%02x\n", __FUNCTION__, (__u8) str[0],
+ (__u8) str[1]);
+
+ /* Null terminate string */
+ str[p.pl+1] = '\0';
+
+ p.pv.c = str; /* Handler will need to take a copy */
+
+ /* Call handler for this parameter */
+ err = (*func)(self, &p, PV_PUT);
+ if (err < 0)
+ return err;
+
+ return p.pl+2; /* Extracted pl+2 bytes */
+}
+
+/*
+ * Function irda_extract_octseq (self, buf, len, type, func)
+ *
+ *
+ *
+ */
+static int irda_extract_octseq(void *self, __u8 *buf, int len, __u8 pi,
+ PV_TYPE type, PI_HANDLER func)
+{
+ irda_param_t p;
+
+ p.pi = pi; /* In case handler needs to know */
+ p.pl = buf[1]; /* Extract lenght of value */
+
+ /* Check if buffer is long enough for parsing */
+ if (len < (2+p.pl)) {
+ WARNING("%s(), buffer to short for parsing! "
+ "Need %d bytes, but len is only %d\n", __FUNCTION__, p.pl, len);
+ return -1;
+ }
+
+ IRDA_DEBUG(0, "%s(), not impl\n", __FUNCTION__);
+
+ return p.pl+2; /* Extracted pl+2 bytes */
+}
+
+/*
+ * Function irda_param_pack (skb, fmt, ...)
+ *
+ * Format:
+ * 'i' = 32 bits integer
+ * 's' = string
+ *
+ */
+int irda_param_pack(__u8 *buf, char *fmt, ...)
+{
+ irda_pv_t arg;
+ va_list args;
+ char *p;
+ int n = 0;
+
+ va_start(args, fmt);
+
+ for (p = fmt; *p != '\0'; p++) {
+ switch (*p) {
+ case 'b': /* 8 bits unsigned byte */
+ buf[n++] = (__u8)va_arg(args, int);
+ break;
+ case 's': /* 16 bits unsigned short */
+ arg.i = (__u16)va_arg(args, int);
+ put_unaligned((__u16)arg.i, (__u16 *)(buf+n)); n+=2;
+ break;
+ case 'i': /* 32 bits unsigned integer */
+ arg.i = va_arg(args, __u32);
+ put_unaligned(arg.i, (__u32 *)(buf+n)); n+=4;
+ break;
+#if 0
+ case 'c': /* \0 terminated string */
+ arg.c = va_arg(args, char *);
+ strcpy(buf+n, arg.c);
+ n += strlen(arg.c) + 1;
+ break;
+#endif
+ default:
+ va_end(args);
+ return -1;
+ }
+
+ }
+ va_end(args);
+
+ return 0;
+}
+
+/*
+ * Function irda_param_unpack (skb, fmt, ...)
+ *
+ *
+ *
+ */
+int irda_param_unpack(__u8 *buf, char *fmt, ...)
+{
+ irda_pv_t arg;
+ va_list args;
+ char *p;
+ int n = 0;
+
+ va_start(args, fmt);
+
+ for (p = fmt; *p != '\0'; p++) {
+ switch (*p) {
+ case 'b': /* 8 bits byte */
+ arg.ip = va_arg(args, __u32 *);
+ *arg.ip = buf[n++];
+ break;
+ case 's': /* 16 bits short */
+ arg.ip = va_arg(args, __u32 *);
+ *arg.ip = get_unaligned((__u16 *)(buf+n)); n+=2;
+ break;
+ case 'i': /* 32 bits unsigned integer */
+ arg.ip = va_arg(args, __u32 *);
+ *arg.ip = get_unaligned((__u32 *)(buf+n)); n+=4;
+ break;
+#if 0
+ case 'c': /* \0 terminated string */
+ arg.c = va_arg(args, char *);
+ strcpy(arg.c, buf+n);
+ n += strlen(arg.c) + 1;
+ break;
+#endif
+ default:
+ va_end(args);
+ return -1;
+ }
+
+ }
+ va_end(args);
+
+ return 0;
+}
+
+/*
+ * Function irda_param_insert (self, pi, buf, len, info)
+ *
+ * Insert the specified parameter (pi) into buffer. Returns number of
+ * bytes inserted
+ */
+int irda_param_insert(void *self, __u8 pi, __u8 *buf, int len,
+ pi_param_info_t *info)
+{
+ pi_minor_info_t *pi_minor_info;
+ __u8 pi_minor;
+ __u8 pi_major;
+ int type;
+ int ret = -1;
+ int n = 0;
+
+ ASSERT(buf != NULL, return ret;);
+ ASSERT(info != 0, return ret;);
+
+ pi_minor = pi & info->pi_mask;
+ pi_major = pi >> info->pi_major_offset;
+
+ /* Check if the identifier value (pi) is valid */
+ if ((pi_major > info->len-1) ||
+ (pi_minor > info->tables[pi_major].len-1))
+ {
+ IRDA_DEBUG(0, "%s(), no handler for parameter=0x%02x\n", __FUNCTION__, pi);
+
+ /* Skip this parameter */
+ return -1;
+ }
+
+ /* Lookup the info on how to parse this parameter */
+ pi_minor_info = &info->tables[pi_major].pi_minor_call_table[pi_minor];
+
+ /* Find expected data type for this parameter identifier (pi)*/
+ type = pi_minor_info->type;
+
+ /* Check if handler has been implemented */
+ if (!pi_minor_info->func) {
+ MESSAGE("%s(), no handler for pi=%#x\n", __FUNCTION__, pi);
+ /* Skip this parameter */
+ return -1;
+ }
+
+ /* Insert parameter value */
+ ret = (*pv_insert_table[type & PV_MASK])(self, buf+n, len, pi, type,
+ pi_minor_info->func);
+ return ret;
+}
+
+/*
+ * Function irda_param_extract_all (self, buf, len, info)
+ *
+ * Parse all parameters. If len is correct, then everything should be
+ * safe. Returns the number of bytes that was parsed
+ *
+ */
+int irda_param_extract(void *self, __u8 *buf, int len, pi_param_info_t *info)
+{
+ pi_minor_info_t *pi_minor_info;
+ __u8 pi_minor;
+ __u8 pi_major;
+ int type;
+ int ret = -1;
+ int n = 0;
+
+ ASSERT(buf != NULL, return ret;);
+ ASSERT(info != 0, return ret;);
+
+ pi_minor = buf[n] & info->pi_mask;
+ pi_major = buf[n] >> info->pi_major_offset;
+
+ /* Check if the identifier value (pi) is valid */
+ if ((pi_major > info->len-1) ||
+ (pi_minor > info->tables[pi_major].len-1))
+ {
+ IRDA_DEBUG(0, "%s(), no handler for parameter=0x%02x\n", __FUNCTION__,
+ buf[0]);
+
+ /* Skip this parameter */
+ return 2 + buf[n + 1]; /* Continue */
+ }
+
+ /* Lookup the info on how to parse this parameter */
+ pi_minor_info = &info->tables[pi_major].pi_minor_call_table[pi_minor];
+
+ /* Find expected data type for this parameter identifier (pi)*/
+ type = pi_minor_info->type;
+
+ IRDA_DEBUG(3, "%s(), pi=[%d,%d], type=%d\n", __FUNCTION__,
+ pi_major, pi_minor, type);
+
+ /* Check if handler has been implemented */
+ if (!pi_minor_info->func) {
+ MESSAGE("%s(), no handler for pi=%#x\n", __FUNCTION__, buf[n]);
+ /* Skip this parameter */
+ return 2 + buf[n + 1]; /* Continue */
+ }
+
+ /* Parse parameter value */
+ ret = (*pv_extract_table[type & PV_MASK])(self, buf+n, len, buf[n],
+ type, pi_minor_info->func);
+ return ret;
+}
+
+/*
+ * Function irda_param_extract_all (self, buf, len, info)
+ *
+ * Parse all parameters. If len is correct, then everything should be
+ * safe. Returns the number of bytes that was parsed
+ *
+ */
+int irda_param_extract_all(void *self, __u8 *buf, int len,
+ pi_param_info_t *info)
+{
+ int ret = -1;
+ int n = 0;
+
+ ASSERT(buf != NULL, return ret;);
+ ASSERT(info != 0, return ret;);
+
+ /*
+ * Parse all parameters. Each parameter must be at least two bytes
+ * long or else there is no point in trying to parse it
+ */
+ while (len > 2) {
+ ret = irda_param_extract(self, buf+n, len, info);
+ if (ret < 0)
+ return ret;
+
+ n += ret;
+ len -= ret;
+ }
+ return n;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/irda/qos.c b/uClinux-2.4.31-uc0/net/irda/qos.c
new file mode 100644
index 0000000..97b8041
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/qos.c
@@ -0,0 +1,774 @@
+/*********************************************************************
+ *
+ * Filename: qos.c
+ * Version: 1.0
+ * Description: IrLAP QoS parameter negotiation
+ * Status: Stable
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Tue Sep 9 00:00:26 1997
+ * Modified at: Sun Jan 30 14:29:16 2000
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1998-2000 Dag Brattli <dagb@cs.uit.no>,
+ * All Rights Reserved.
+ * Copyright (c) 2000-2001 Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ ********************************************************************/
+
+#include <linux/config.h>
+#include <asm/byteorder.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/parameters.h>
+#include <net/irda/qos.h>
+#include <net/irda/irlap.h>
+
+/*
+ * Maximum values of the baud rate we negociate with the other end.
+ * Most often, you don't have to change that, because Linux-IrDA will
+ * use the maximum offered by the link layer, which usually works fine.
+ * In some very rare cases, you may want to limit it to lower speeds...
+ */
+int sysctl_max_baud_rate = 16000000;
+/*
+ * Maximum value of the lap disconnect timer we negociate with the other end.
+ * Most often, the value below represent the best compromise, but some user
+ * may want to keep the LAP alive longuer or shorter in case of link failure.
+ * Remember that the threshold time (early warning) is fixed to 3s...
+ */
+int sysctl_max_noreply_time = 12;
+/*
+ * Minimum turn time to be applied before transmitting to the peer.
+ * Nonzero values (usec) are used as lower limit to the per-connection
+ * mtt value which was announced by the other end during negotiation.
+ * Might be helpful if the peer device provides too short mtt.
+ * Default is 10us which means using the unmodified value given by the
+ * peer except if it's 0 (0 is likely a bug in the other stack).
+ */
+unsigned sysctl_min_tx_turn_time = 10;
+/*
+ * Maximum data size to be used in transmission in payload of LAP frame.
+ * There is a bit of confusion in the IrDA spec :
+ * The LAP spec defines the payload of a LAP frame (I field) to be
+ * 2048 bytes max (IrLAP 1.1, chapt 6.6.5, p40).
+ * On the other hand, the PHY mention frames of 2048 bytes max (IrPHY
+ * 1.2, chapt 5.3.2.1, p41). But, this number includes the LAP header
+ * (2 bytes), and CRC (32 bits at 4 Mb/s). So, for the I field (LAP
+ * payload), that's only 2042 bytes. Oups !
+ * My nsc-ircc hardware has troubles receiving 2048 bytes frames at 4 Mb/s,
+ * so adjust to 2042... I don't know if this bug applies only for 2048
+ * bytes frames or all negociated frame sizes, but you can use the sysctl
+ * to play with this value anyway.
+ * Jean II */
+unsigned sysctl_max_tx_data_size = 2042;
+/*
+ * Maximum transmit window, i.e. number of LAP frames between turn-around.
+ * This allow to override what the peer told us. Some peers are buggy and
+ * don't always support what they tell us.
+ * Jean II */
+unsigned sysctl_max_tx_window = 7;
+
+static int irlap_param_baud_rate(void *instance, irda_param_t *param, int get);
+static int irlap_param_link_disconnect(void *instance, irda_param_t *parm,
+ int get);
+static int irlap_param_max_turn_time(void *instance, irda_param_t *param,
+ int get);
+static int irlap_param_data_size(void *instance, irda_param_t *param, int get);
+static int irlap_param_window_size(void *instance, irda_param_t *param,
+ int get);
+static int irlap_param_additional_bofs(void *instance, irda_param_t *parm,
+ int get);
+static int irlap_param_min_turn_time(void *instance, irda_param_t *param,
+ int get);
+
+__u32 min_turn_times[] = { 10000, 5000, 1000, 500, 100, 50, 10, 0 }; /* us */
+__u32 baud_rates[] = { 2400, 9600, 19200, 38400, 57600, 115200, 576000,
+ 1152000, 4000000, 16000000 }; /* bps */
+__u32 data_sizes[] = { 64, 128, 256, 512, 1024, 2048 }; /* bytes */
+__u32 add_bofs[] = { 48, 24, 12, 5, 3, 2, 1, 0 }; /* bytes */
+__u32 max_turn_times[] = { 500, 250, 100, 50 }; /* ms */
+__u32 link_disc_times[] = { 3, 8, 12, 16, 20, 25, 30, 40 }; /* secs */
+
+__u32 max_line_capacities[10][4] = {
+ /* 500 ms 250 ms 100 ms 50 ms (max turn time) */
+ { 100, 0, 0, 0 }, /* 2400 bps */
+ { 400, 0, 0, 0 }, /* 9600 bps */
+ { 800, 0, 0, 0 }, /* 19200 bps */
+ { 1600, 0, 0, 0 }, /* 38400 bps */
+ { 2360, 0, 0, 0 }, /* 57600 bps */
+ { 4800, 2400, 960, 480 }, /* 115200 bps */
+ { 28800, 11520, 5760, 2880 }, /* 576000 bps */
+ { 57600, 28800, 11520, 5760 }, /* 1152000 bps */
+ { 200000, 100000, 40000, 20000 }, /* 4000000 bps */
+ { 800000, 400000, 160000, 80000 }, /* 16000000 bps */
+};
+
+static pi_minor_info_t pi_minor_call_table_type_0[] = {
+ { NULL, 0 },
+/* 01 */{ irlap_param_baud_rate, PV_INTEGER | PV_LITTLE_ENDIAN },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+ { NULL, 0 },
+/* 08 */{ irlap_param_link_disconnect, PV_INT_8_BITS }
+};
+
+static pi_minor_info_t pi_minor_call_table_type_1[] = {
+ { NULL, 0 },
+ { NULL, 0 },
+/* 82 */{ irlap_param_max_turn_time, PV_INT_8_BITS },
+/* 83 */{ irlap_param_data_size, PV_INT_8_BITS },
+/* 84 */{ irlap_param_window_size, PV_INT_8_BITS },
+/* 85 */{ irlap_param_additional_bofs, PV_INT_8_BITS },
+/* 86 */{ irlap_param_min_turn_time, PV_INT_8_BITS },
+};
+
+static pi_major_info_t pi_major_call_table[] = {
+ { pi_minor_call_table_type_0, 9 },
+ { pi_minor_call_table_type_1, 7 },
+};
+
+static pi_param_info_t irlap_param_info = { pi_major_call_table, 2, 0x7f, 7 };
+
+/* ---------------------- LOCAL SUBROUTINES ---------------------- */
+/* Note : we start with a bunch of local subroutines.
+ * As the compiler is "one pass", this is the only way to get them to
+ * inline properly...
+ * Jean II
+ */
+/*
+ * Function value_index (value, array, size)
+ *
+ * Returns the index to the value in the specified array
+ */
+static inline int value_index(__u32 value, __u32 *array, int size)
+{
+ int i;
+
+ for (i=0; i < size; i++)
+ if (array[i] == value)
+ break;
+ return i;
+}
+
+/*
+ * Function index_value (index, array)
+ *
+ * Returns value to index in array, easy!
+ *
+ */
+static inline __u32 index_value(int index, __u32 *array)
+{
+ return array[index];
+}
+
+/*
+ * Function msb_index (word)
+ *
+ * Returns index to most significant bit (MSB) in word
+ *
+ */
+int msb_index (__u16 word)
+{
+ __u16 msb = 0x8000;
+ int index = 15; /* Current MSB */
+
+ /* Check for buggy peers.
+ * Note : there is a small probability that it could be us, but I
+ * would expect driver authors to catch that pretty early and be
+ * able to check precisely what's going on. If a end user sees this,
+ * it's very likely the peer. - Jean II */
+ if (word == 0) {
+ WARNING("%s(), Detected buggy peer, adjust null PV to 0x1!\n",
+ __FUNCTION__);
+ /* The only safe choice (we don't know the array size) */
+ word = 0x1;
+ }
+
+ while (msb) {
+ if (word & msb)
+ break; /* Found it! */
+ msb >>=1;
+ index--;
+ }
+ return index;
+}
+
+static inline __u32 byte_value(__u8 byte, __u32 *array)
+{
+ int index;
+
+ ASSERT(array != NULL, return -1;);
+
+ index = msb_index(byte);
+
+ return index_value(index, array);
+}
+
+/*
+ * Function value_lower_bits (value, array)
+ *
+ * Returns a bit field marking all possibility lower than value.
+ */
+static inline int value_lower_bits(__u32 value, __u32 *array, int size, __u16 *field)
+{
+ int i;
+ __u16 mask = 0x1;
+ __u16 result = 0x0;
+
+ for (i=0; i < size; i++) {
+ /* Add the current value to the bit field, shift mask */
+ result |= mask;
+ mask <<= 1;
+ /* Finished ? */
+ if (array[i] >= value)
+ break;
+ }
+ /* Send back a valid index */
+ if(i >= size)
+ i = size - 1; /* Last item */
+ *field = result;
+ return i;
+}
+
+/*
+ * Function value_highest_bit (value, array)
+ *
+ * Returns a bit field marking the highest possibility lower than value.
+ */
+static inline int value_highest_bit(__u32 value, __u32 *array, int size, __u16 *field)
+{
+ int i;
+ __u16 mask = 0x1;
+ __u16 result = 0x0;
+
+ for (i=0; i < size; i++) {
+ /* Finished ? */
+ if (array[i] <= value)
+ break;
+ /* Shift mask */
+ mask <<= 1;
+ }
+ /* Set the current value to the bit field */
+ result |= mask;
+ /* Send back a valid index */
+ if(i >= size)
+ i = size - 1; /* Last item */
+ *field = result;
+ return i;
+}
+
+/* -------------------------- MAIN CALLS -------------------------- */
+
+/*
+ * Function irda_qos_compute_intersection (qos, new)
+ *
+ * Compute the intersection of the old QoS capabilites with new ones
+ *
+ */
+void irda_qos_compute_intersection(struct qos_info *qos, struct qos_info *new)
+{
+ ASSERT(qos != NULL, return;);
+ ASSERT(new != NULL, return;);
+
+ /* Apply */
+ qos->baud_rate.bits &= new->baud_rate.bits;
+ qos->window_size.bits &= new->window_size.bits;
+ qos->min_turn_time.bits &= new->min_turn_time.bits;
+ qos->max_turn_time.bits &= new->max_turn_time.bits;
+ qos->data_size.bits &= new->data_size.bits;
+ qos->link_disc_time.bits &= new->link_disc_time.bits;
+ qos->additional_bofs.bits &= new->additional_bofs.bits;
+
+ irda_qos_bits_to_value(qos);
+}
+
+/*
+ * Function irda_init_max_qos_capabilies (qos)
+ *
+ * The purpose of this function is for layers and drivers to be able to
+ * set the maximum QoS possible and then "and in" their own limitations
+ *
+ */
+void irda_init_max_qos_capabilies(struct qos_info *qos)
+{
+ int i;
+ /*
+ * These are the maximum supported values as specified on pages
+ * 39-43 in IrLAP
+ */
+
+ /* Use sysctl to set some configurable values... */
+ /* Set configured max speed */
+ i = value_lower_bits(sysctl_max_baud_rate, baud_rates, 10,
+ &qos->baud_rate.bits);
+ sysctl_max_baud_rate = index_value(i, baud_rates);
+
+ /* Set configured max disc time */
+ i = value_lower_bits(sysctl_max_noreply_time, link_disc_times, 8,
+ &qos->link_disc_time.bits);
+ sysctl_max_noreply_time = index_value(i, link_disc_times);
+
+ /* LSB is first byte, MSB is second byte */
+ qos->baud_rate.bits &= 0x03ff;
+
+ qos->window_size.bits = 0x7f;
+ qos->min_turn_time.bits = 0xff;
+ qos->max_turn_time.bits = 0x0f;
+ qos->data_size.bits = 0x3f;
+ qos->link_disc_time.bits &= 0xff;
+ qos->additional_bofs.bits = 0xff;
+}
+
+/*
+ * Function irlap_adjust_qos_settings (qos)
+ *
+ * Adjust QoS settings in case some values are not possible to use because
+ * of other settings
+ */
+void irlap_adjust_qos_settings(struct qos_info *qos)
+{
+ __u32 line_capacity;
+ int index;
+
+ IRDA_DEBUG(2, "%s()\n", __FUNCTION__);
+
+ /*
+ * Make sure the mintt is sensible.
+ * Main culprit : Ericsson T39. - Jean II
+ */
+ if (sysctl_min_tx_turn_time > qos->min_turn_time.value) {
+ int i;
+
+ WARNING("%s(), Detected buggy peer, adjust mtt to %dus!\n",
+ __FUNCTION__, sysctl_min_tx_turn_time);
+
+ /* We don't really need bits, but easier this way */
+ i = value_highest_bit(sysctl_min_tx_turn_time, min_turn_times,
+ 8, &qos->min_turn_time.bits);
+ sysctl_min_tx_turn_time = index_value(i, min_turn_times);
+ qos->min_turn_time.value = sysctl_min_tx_turn_time;
+ }
+
+ /*
+ * Not allowed to use a max turn time less than 500 ms if the baudrate
+ * is less than 115200
+ */
+ if ((qos->baud_rate.value < 115200) &&
+ (qos->max_turn_time.value < 500))
+ {
+ IRDA_DEBUG(0, "%s(), adjusting max turn time from %d to 500 ms\n", __FUNCTION__,
+ qos->max_turn_time.value);
+ qos->max_turn_time.value = 500;
+ }
+
+ /*
+ * The data size must be adjusted according to the baud rate and max
+ * turn time
+ */
+ index = value_index(qos->data_size.value, data_sizes, 6);
+ line_capacity = irlap_max_line_capacity(qos->baud_rate.value,
+ qos->max_turn_time.value);
+
+#ifdef CONFIG_IRDA_DYNAMIC_WINDOW
+ while ((qos->data_size.value > line_capacity) && (index > 0)) {
+ qos->data_size.value = data_sizes[index--];
+ IRDA_DEBUG(2, "%s(), reducing data size to %d\n", __FUNCTION__,
+ qos->data_size.value);
+ }
+#else /* Use method described in section 6.6.11 of IrLAP */
+ while (irlap_requested_line_capacity(qos) > line_capacity) {
+ ASSERT(index != 0, return;);
+
+ /* Must be able to send at least one frame */
+ if (qos->window_size.value > 1) {
+ qos->window_size.value--;
+ IRDA_DEBUG(2, "%s(), reducing window size to %d\n", __FUNCTION__,
+ qos->window_size.value);
+ } else if (index > 1) {
+ qos->data_size.value = data_sizes[index--];
+ IRDA_DEBUG(2, "%s(), reducing data size to %d\n", __FUNCTION__,
+ qos->data_size.value);
+ } else {
+ WARNING("%s(), nothing more we can do!\n", __FUNCTION__);
+ }
+ }
+#endif /* CONFIG_IRDA_DYNAMIC_WINDOW */
+ /*
+ * Fix tx data size according to user limits - Jean II
+ */
+ if (qos->data_size.value > sysctl_max_tx_data_size)
+ /* Allow non discrete adjustement to avoid loosing capacity */
+ qos->data_size.value = sysctl_max_tx_data_size;
+ /*
+ * Override Tx window if user request it. - Jean II
+ */
+ if (qos->window_size.value > sysctl_max_tx_window)
+ qos->window_size.value = sysctl_max_tx_window;
+}
+
+/*
+ * Function irlap_negotiate (qos_device, qos_session, skb)
+ *
+ * Negotiate QoS values, not really that much negotiation :-)
+ * We just set the QoS capabilities for the peer station
+ *
+ */
+int irlap_qos_negotiate(struct irlap_cb *self, struct sk_buff *skb)
+{
+ int ret;
+
+ ret = irda_param_extract_all(self, skb->data, skb->len,
+ &irlap_param_info);
+
+ /* Convert the negotiated bits to values */
+ irda_qos_bits_to_value(&self->qos_tx);
+ irda_qos_bits_to_value(&self->qos_rx);
+
+ irlap_adjust_qos_settings(&self->qos_tx);
+
+ IRDA_DEBUG(2, "Setting BAUD_RATE to %d bps.\n",
+ self->qos_tx.baud_rate.value);
+ IRDA_DEBUG(2, "Setting DATA_SIZE to %d bytes\n",
+ self->qos_tx.data_size.value);
+ IRDA_DEBUG(2, "Setting WINDOW_SIZE to %d\n",
+ self->qos_tx.window_size.value);
+ IRDA_DEBUG(2, "Setting XBOFS to %d\n",
+ self->qos_tx.additional_bofs.value);
+ IRDA_DEBUG(2, "Setting MAX_TURN_TIME to %d ms.\n",
+ self->qos_tx.max_turn_time.value);
+ IRDA_DEBUG(2, "Setting MIN_TURN_TIME to %d usecs.\n",
+ self->qos_tx.min_turn_time.value);
+ IRDA_DEBUG(2, "Setting LINK_DISC to %d secs.\n",
+ self->qos_tx.link_disc_time.value);
+ return ret;
+}
+
+/*
+ * Function irlap_insert_negotiation_params (qos, fp)
+ *
+ * Insert QoS negotiaion pararameters into frame
+ *
+ */
+int irlap_insert_qos_negotiation_params(struct irlap_cb *self,
+ struct sk_buff *skb)
+{
+ int ret;
+
+ /* Insert data rate */
+ ret = irda_param_insert(self, PI_BAUD_RATE, skb->tail,
+ skb_tailroom(skb), &irlap_param_info);
+ if (ret < 0)
+ return ret;
+ skb_put(skb, ret);
+
+ /* Insert max turnaround time */
+ ret = irda_param_insert(self, PI_MAX_TURN_TIME, skb->tail,
+ skb_tailroom(skb), &irlap_param_info);
+ if (ret < 0)
+ return ret;
+ skb_put(skb, ret);
+
+ /* Insert data size */
+ ret = irda_param_insert(self, PI_DATA_SIZE, skb->tail,
+ skb_tailroom(skb), &irlap_param_info);
+ if (ret < 0)
+ return ret;
+ skb_put(skb, ret);
+
+ /* Insert window size */
+ ret = irda_param_insert(self, PI_WINDOW_SIZE, skb->tail,
+ skb_tailroom(skb), &irlap_param_info);
+ if (ret < 0)
+ return ret;
+ skb_put(skb, ret);
+
+ /* Insert additional BOFs */
+ ret = irda_param_insert(self, PI_ADD_BOFS, skb->tail,
+ skb_tailroom(skb), &irlap_param_info);
+ if (ret < 0)
+ return ret;
+ skb_put(skb, ret);
+
+ /* Insert minimum turnaround time */
+ ret = irda_param_insert(self, PI_MIN_TURN_TIME, skb->tail,
+ skb_tailroom(skb), &irlap_param_info);
+ if (ret < 0)
+ return ret;
+ skb_put(skb, ret);
+
+ /* Insert link disconnect/threshold time */
+ ret = irda_param_insert(self, PI_LINK_DISC, skb->tail,
+ skb_tailroom(skb), &irlap_param_info);
+ if (ret < 0)
+ return ret;
+ skb_put(skb, ret);
+
+ return 0;
+}
+
+/*
+ * Function irlap_param_baud_rate (instance, param, get)
+ *
+ * Negotiate data-rate
+ *
+ */
+static int irlap_param_baud_rate(void *instance, irda_param_t *param, int get)
+{
+ __u16 final;
+
+ struct irlap_cb *self = (struct irlap_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ if (get) {
+ param->pv.i = self->qos_rx.baud_rate.bits;
+ IRDA_DEBUG(2, "%s(), baud rate = 0x%02x\n", __FUNCTION__,
+ param->pv.i);
+ } else {
+ /*
+ * Stations must agree on baud rate, so calculate
+ * intersection
+ */
+ IRDA_DEBUG(2, "Requested BAUD_RATE: 0x%04x\n", (__u16) param->pv.i);
+ final = (__u16) param->pv.i & self->qos_rx.baud_rate.bits;
+
+ IRDA_DEBUG(2, "Final BAUD_RATE: 0x%04x\n", final);
+ self->qos_tx.baud_rate.bits = final;
+ self->qos_rx.baud_rate.bits = final;
+ }
+
+ return 0;
+}
+
+/*
+ * Function irlap_param_link_disconnect (instance, param, get)
+ *
+ * Negotiate link disconnect/threshold time.
+ *
+ */
+static int irlap_param_link_disconnect(void *instance, irda_param_t *param,
+ int get)
+{
+ __u16 final;
+
+ struct irlap_cb *self = (struct irlap_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ if (get)
+ param->pv.i = self->qos_rx.link_disc_time.bits;
+ else {
+ /*
+ * Stations must agree on link disconnect/threshold
+ * time.
+ */
+ IRDA_DEBUG(2, "LINK_DISC: %02x\n", (__u8) param->pv.i);
+ final = (__u8) param->pv.i & self->qos_rx.link_disc_time.bits;
+
+ IRDA_DEBUG(2, "Final LINK_DISC: %02x\n", final);
+ self->qos_tx.link_disc_time.bits = final;
+ self->qos_rx.link_disc_time.bits = final;
+ }
+ return 0;
+}
+
+/*
+ * Function irlap_param_max_turn_time (instance, param, get)
+ *
+ * Negotiate the maximum turnaround time. This is a type 1 parameter and
+ * will be negotiated independently for each station
+ *
+ */
+static int irlap_param_max_turn_time(void *instance, irda_param_t *param,
+ int get)
+{
+ struct irlap_cb *self = (struct irlap_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ if (get)
+ param->pv.i = self->qos_rx.max_turn_time.bits;
+ else
+ self->qos_tx.max_turn_time.bits = (__u8) param->pv.i;
+
+ return 0;
+}
+
+/*
+ * Function irlap_param_data_size (instance, param, get)
+ *
+ * Negotiate the data size. This is a type 1 parameter and
+ * will be negotiated independently for each station
+ *
+ */
+static int irlap_param_data_size(void *instance, irda_param_t *param, int get)
+{
+ struct irlap_cb *self = (struct irlap_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ if (get)
+ param->pv.i = self->qos_rx.data_size.bits;
+ else
+ self->qos_tx.data_size.bits = (__u8) param->pv.i;
+
+ return 0;
+}
+
+/*
+ * Function irlap_param_window_size (instance, param, get)
+ *
+ * Negotiate the window size. This is a type 1 parameter and
+ * will be negotiated independently for each station
+ *
+ */
+static int irlap_param_window_size(void *instance, irda_param_t *param,
+ int get)
+{
+ struct irlap_cb *self = (struct irlap_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ if (get)
+ param->pv.i = self->qos_rx.window_size.bits;
+ else
+ self->qos_tx.window_size.bits = (__u8) param->pv.i;
+
+ return 0;
+}
+
+/*
+ * Function irlap_param_additional_bofs (instance, param, get)
+ *
+ * Negotiate additional BOF characters. This is a type 1 parameter and
+ * will be negotiated independently for each station.
+ */
+static int irlap_param_additional_bofs(void *instance, irda_param_t *param, int get)
+{
+ struct irlap_cb *self = (struct irlap_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ if (get)
+ param->pv.i = self->qos_rx.additional_bofs.bits;
+ else
+ self->qos_tx.additional_bofs.bits = (__u8) param->pv.i;
+
+ return 0;
+}
+
+/*
+ * Function irlap_param_min_turn_time (instance, param, get)
+ *
+ * Negotiate the minimum turn around time. This is a type 1 parameter and
+ * will be negotiated independently for each station
+ */
+static int irlap_param_min_turn_time(void *instance, irda_param_t *param,
+ int get)
+{
+ struct irlap_cb *self = (struct irlap_cb *) instance;
+
+ ASSERT(self != NULL, return -1;);
+ ASSERT(self->magic == LAP_MAGIC, return -1;);
+
+ if (get)
+ param->pv.i = self->qos_rx.min_turn_time.bits;
+ else
+ self->qos_tx.min_turn_time.bits = (__u8) param->pv.i;
+
+ return 0;
+}
+
+/*
+ * Function irlap_max_line_capacity (speed, max_turn_time, min_turn_time)
+ *
+ * Calculate the maximum line capacity
+ *
+ */
+__u32 irlap_max_line_capacity(__u32 speed, __u32 max_turn_time)
+{
+ __u32 line_capacity;
+ int i,j;
+
+ IRDA_DEBUG(2, "%s(), speed=%d, max_turn_time=%d\n", __FUNCTION__,
+ speed, max_turn_time);
+
+ i = value_index(speed, baud_rates, 10);
+ j = value_index(max_turn_time, max_turn_times, 4);
+
+ ASSERT(((i >=0) && (i <=10)), return 0;);
+ ASSERT(((j >=0) && (j <=4)), return 0;);
+
+ line_capacity = max_line_capacities[i][j];
+
+ IRDA_DEBUG(2, "%s(), line capacity=%d bytes\n", __FUNCTION__,
+ line_capacity);
+
+ return line_capacity;
+}
+
+__u32 irlap_requested_line_capacity(struct qos_info *qos)
+{ __u32 line_capacity;
+
+ line_capacity = qos->window_size.value *
+ (qos->data_size.value + 6 + qos->additional_bofs.value) +
+ irlap_min_turn_time_in_bytes(qos->baud_rate.value,
+ qos->min_turn_time.value);
+
+ IRDA_DEBUG(2, "%s(), requested line capacity=%d\n", __FUNCTION__,
+ line_capacity);
+
+ return line_capacity;
+}
+
+void irda_qos_bits_to_value(struct qos_info *qos)
+{
+ int index;
+
+ ASSERT(qos != NULL, return;);
+
+ index = msb_index(qos->baud_rate.bits);
+ qos->baud_rate.value = baud_rates[index];
+
+ index = msb_index(qos->data_size.bits);
+ qos->data_size.value = data_sizes[index];
+
+ index = msb_index(qos->window_size.bits);
+ qos->window_size.value = index+1;
+
+ index = msb_index(qos->min_turn_time.bits);
+ qos->min_turn_time.value = min_turn_times[index];
+
+ index = msb_index(qos->max_turn_time.bits);
+ qos->max_turn_time.value = max_turn_times[index];
+
+ index = msb_index(qos->link_disc_time.bits);
+ qos->link_disc_time.value = link_disc_times[index];
+
+ index = msb_index(qos->additional_bofs.bits);
+ qos->additional_bofs.value = add_bofs[index];
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/timer.c b/uClinux-2.4.31-uc0/net/irda/timer.c
new file mode 100644
index 0000000..16ee2fb
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/timer.c
@@ -0,0 +1,239 @@
+/*********************************************************************
+ *
+ * Filename: timer.c
+ * Version:
+ * Description:
+ * Status: Experimental.
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Sat Aug 16 00:59:29 1997
+ * Modified at: Wed Dec 8 12:50:34 1999
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ *
+ * Copyright (c) 1997, 1999 Dag Brattli <dagb@cs.uit.no>,
+ * All Rights Reserved.
+ * Copyright (c) 2000-2001 Jean Tourrilhes <jt@hpl.hp.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <asm/system.h>
+#include <linux/config.h>
+#include <linux/delay.h>
+
+#include <net/irda/timer.h>
+#include <net/irda/irda.h>
+#include <net/irda/irtty.h>
+#include <net/irda/irlap.h>
+#include <net/irda/irlmp_event.h>
+
+static void irlap_slot_timer_expired(void* data);
+static void irlap_query_timer_expired(void* data);
+static void irlap_final_timer_expired(void* data);
+static void irlap_wd_timer_expired(void* data);
+static void irlap_backoff_timer_expired(void* data);
+
+static void irlap_media_busy_expired(void* data);
+/*
+ * Function irda_start_timer (timer, timeout)
+ *
+ * Start an IrDA timer
+ *
+ */
+void irda_start_timer(struct timer_list *ptimer, int timeout, void *data,
+ TIMER_CALLBACK callback)
+{
+ del_timer(ptimer);
+
+ ptimer->data = (unsigned long) data;
+
+ /*
+ * For most architectures void * is the same as unsigned long, but
+ * at least we try to use void * as long as possible. Since the
+ * timer functions use unsigned long, we cast the function here
+ */
+ ptimer->function = (void (*)(unsigned long)) callback;
+ ptimer->expires = jiffies + timeout;
+
+ add_timer(ptimer);
+}
+
+void irlap_start_slot_timer(struct irlap_cb *self, int timeout)
+{
+ irda_start_timer(&self->slot_timer, timeout, (void *) self,
+ irlap_slot_timer_expired);
+}
+
+void irlap_start_query_timer(struct irlap_cb *self, int timeout)
+{
+ irda_start_timer( &self->query_timer, timeout, (void *) self,
+ irlap_query_timer_expired);
+}
+
+void irlap_start_final_timer(struct irlap_cb *self, int timeout)
+{
+ irda_start_timer(&self->final_timer, timeout, (void *) self,
+ irlap_final_timer_expired);
+}
+
+void irlap_start_wd_timer(struct irlap_cb *self, int timeout)
+{
+ irda_start_timer(&self->wd_timer, timeout, (void *) self,
+ irlap_wd_timer_expired);
+}
+
+void irlap_start_backoff_timer(struct irlap_cb *self, int timeout)
+{
+ irda_start_timer(&self->backoff_timer, timeout, (void *) self,
+ irlap_backoff_timer_expired);
+}
+
+void irlap_start_mbusy_timer(struct irlap_cb *self, int timeout)
+{
+ irda_start_timer(&self->media_busy_timer, timeout,
+ (void *) self, irlap_media_busy_expired);
+}
+
+void irlap_stop_mbusy_timer(struct irlap_cb *self)
+{
+ /* If timer is activated, kill it! */
+ del_timer(&self->media_busy_timer);
+
+ /* If we are in NDM, there is a bunch of events in LAP that
+ * that be pending due to the media_busy condition, such as
+ * CONNECT_REQUEST and SEND_UI_FRAME. If we don't generate
+ * an event, they will wait forever...
+ * Jean II */
+ if (self->state == LAP_NDM)
+ irlap_do_event(self, MEDIA_BUSY_TIMER_EXPIRED, NULL, NULL);
+}
+
+void irlmp_start_watchdog_timer(struct lsap_cb *self, int timeout)
+{
+ irda_start_timer(&self->watchdog_timer, timeout, (void *) self,
+ irlmp_watchdog_timer_expired);
+}
+
+void irlmp_start_discovery_timer(struct irlmp_cb *self, int timeout)
+{
+ irda_start_timer(&self->discovery_timer, timeout, (void *) self,
+ irlmp_discovery_timer_expired);
+}
+
+void irlmp_start_idle_timer(struct lap_cb *self, int timeout)
+{
+ irda_start_timer(&self->idle_timer, timeout, (void *) self,
+ irlmp_idle_timer_expired);
+}
+
+void irlmp_stop_idle_timer(struct lap_cb *self)
+{
+ /* If timer is activated, kill it! */
+ if(timer_pending(&self->idle_timer))
+ del_timer(&self->idle_timer);
+}
+
+/*
+ * Function irlap_slot_timer_expired (data)
+ *
+ * IrLAP slot timer has expired
+ *
+ */
+static void irlap_slot_timer_expired(void *data)
+{
+ struct irlap_cb *self = (struct irlap_cb *) data;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ irlap_do_event(self, SLOT_TIMER_EXPIRED, NULL, NULL);
+}
+
+/*
+ * Function irlap_query_timer_expired (data)
+ *
+ * IrLAP query timer has expired
+ *
+ */
+static void irlap_query_timer_expired(void *data)
+{
+ struct irlap_cb *self = (struct irlap_cb *) data;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ irlap_do_event(self, QUERY_TIMER_EXPIRED, NULL, NULL);
+}
+
+/*
+ * Function irda_final_timer_expired (data)
+ *
+ *
+ *
+ */
+static void irlap_final_timer_expired(void *data)
+{
+ struct irlap_cb *self = (struct irlap_cb *) data;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ irlap_do_event(self, FINAL_TIMER_EXPIRED, NULL, NULL);
+}
+
+/*
+ * Function irda_wd_timer_expired (data)
+ *
+ *
+ *
+ */
+static void irlap_wd_timer_expired(void *data)
+{
+ struct irlap_cb *self = (struct irlap_cb *) data;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ irlap_do_event(self, WD_TIMER_EXPIRED, NULL, NULL);
+}
+
+/*
+ * Function irda_backoff_timer_expired (data)
+ *
+ *
+ *
+ */
+static void irlap_backoff_timer_expired(void *data)
+{
+ struct irlap_cb *self = (struct irlap_cb *) data;
+
+ ASSERT(self != NULL, return;);
+ ASSERT(self->magic == LAP_MAGIC, return;);
+
+ irlap_do_event(self, BACKOFF_TIMER_EXPIRED, NULL, NULL);
+}
+
+
+/*
+ * Function irtty_media_busy_expired (data)
+ *
+ *
+ */
+void irlap_media_busy_expired(void* data)
+{
+ struct irlap_cb *self = (struct irlap_cb *) data;
+
+ ASSERT(self != NULL, return;);
+
+ irda_device_set_media_busy(self->netdev, FALSE);
+ /* Note : the LAP event will be send in irlap_stop_mbusy_timer(),
+ * to catch other cases where the flag is cleared (for example
+ * after a discovery) - Jean II */
+}
diff --git a/uClinux-2.4.31-uc0/net/irda/wrapper.c b/uClinux-2.4.31-uc0/net/irda/wrapper.c
new file mode 100644
index 0000000..679b059
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/irda/wrapper.c
@@ -0,0 +1,370 @@
+/*********************************************************************
+ *
+ * Filename: wrapper.c
+ * Version: 1.2
+ * Description: IrDA SIR async wrapper layer
+ * Status: Stable
+ * Author: Dag Brattli <dagb@cs.uit.no>
+ * Created at: Mon Aug 4 20:40:53 1997
+ * Modified at: Fri Jan 28 13:21:09 2000
+ * Modified by: Dag Brattli <dagb@cs.uit.no>
+ * Modified at: Fri May 28 3:11 CST 1999
+ * Modified by: Horst von Brand <vonbrand@sleipnir.valparaiso.cl>
+ *
+ * Copyright (c) 1998-2000 Dag Brattli <dagb@cs.uit.no>,
+ * All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * Neither Dag Brattli nor University of Tromsø admit liability nor
+ * provide warranty for any of this software. This material is
+ * provided "AS-IS" and at no charge.
+ *
+ ********************************************************************/
+
+#include <linux/skbuff.h>
+#include <linux/string.h>
+#include <asm/byteorder.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/wrapper.h>
+#include <net/irda/irtty.h>
+#include <net/irda/crc.h>
+#include <net/irda/irlap.h>
+#include <net/irda/irlap_frame.h>
+#include <net/irda/irda_device.h>
+
+static void state_outside_frame(struct net_device *dev,
+ struct net_device_stats *stats,
+ iobuff_t *rx_buff, __u8 byte);
+static void state_begin_frame(struct net_device *dev,
+ struct net_device_stats *stats,
+ iobuff_t *rx_buff, __u8 byte);
+static void state_link_escape(struct net_device *dev,
+ struct net_device_stats *stats,
+ iobuff_t *rx_buff, __u8 byte);
+static void state_inside_frame(struct net_device *dev,
+ struct net_device_stats *stats,
+ iobuff_t *rx_buff, __u8 byte);
+
+static void (*state[])(struct net_device *dev, struct net_device_stats *stats,
+ iobuff_t *rx_buff, __u8 byte) =
+{
+ state_outside_frame,
+ state_begin_frame,
+ state_link_escape,
+ state_inside_frame,
+};
+
+/*
+ * Function stuff_byte (byte, buf)
+ *
+ * Byte stuff one single byte and put the result in buffer pointed to by
+ * buf. The buffer must at all times be able to have two bytes inserted.
+ *
+ */
+static inline int stuff_byte(__u8 byte, __u8 *buf)
+{
+ switch (byte) {
+ case BOF: /* FALLTHROUGH */
+ case EOF: /* FALLTHROUGH */
+ case CE:
+ /* Insert transparently coded */
+ buf[0] = CE; /* Send link escape */
+ buf[1] = byte^IRDA_TRANS; /* Complement bit 5 */
+ return 2;
+ /* break; */
+ default:
+ /* Non-special value, no transparency required */
+ buf[0] = byte;
+ return 1;
+ /* break; */
+ }
+}
+
+/*
+ * Function async_wrap (skb, *tx_buff, buffsize)
+ *
+ * Makes a new buffer with wrapping and stuffing, should check that
+ * we don't get tx buffer overflow.
+ */
+int async_wrap_skb(struct sk_buff *skb, __u8 *tx_buff, int buffsize)
+{
+ struct irda_skb_cb *cb = (struct irda_skb_cb *) skb->cb;
+ int xbofs;
+ int i;
+ int n;
+ union {
+ __u16 value;
+ __u8 bytes[2];
+ } fcs;
+
+ /* Initialize variables */
+ fcs.value = INIT_FCS;
+ n = 0;
+
+ /*
+ * Send XBOF's for required min. turn time and for the negotiated
+ * additional XBOFS
+ */
+
+ if (cb->magic != LAP_MAGIC) {
+ /*
+ * This will happen for all frames sent from user-space.
+ * Nothing to worry about, but we set the default number of
+ * BOF's
+ */
+ IRDA_DEBUG(1, "%s(), wrong magic in skb!\n", __FUNCTION__);
+ xbofs = 10;
+ } else
+ xbofs = cb->xbofs + cb->xbofs_delay;
+
+ IRDA_DEBUG(4, "%s(), xbofs=%d\n", __FUNCTION__, xbofs);
+
+ /* Check that we never use more than 115 + 48 xbofs */
+ if (xbofs > 163) {
+ IRDA_DEBUG(0, "%s(), too many xbofs (%d)\n", __FUNCTION__, xbofs);
+ xbofs = 163;
+ }
+
+ memset(tx_buff+n, XBOF, xbofs);
+ n += xbofs;
+
+ /* Start of packet character BOF */
+ tx_buff[n++] = BOF;
+
+ /* Insert frame and calc CRC */
+ for (i=0; i < skb->len; i++) {
+ /*
+ * Check for the possibility of tx buffer overflow. We use
+ * bufsize-5 since the maximum number of bytes that can be
+ * transmitted after this point is 5.
+ */
+ ASSERT(n < (buffsize-5), return n;);
+
+ n += stuff_byte(skb->data[i], tx_buff+n);
+ fcs.value = irda_fcs(fcs.value, skb->data[i]);
+ }
+
+ /* Insert CRC in little endian format (LSB first) */
+ fcs.value = ~fcs.value;
+#ifdef __LITTLE_ENDIAN
+ n += stuff_byte(fcs.bytes[0], tx_buff+n);
+ n += stuff_byte(fcs.bytes[1], tx_buff+n);
+#else /* ifdef __BIG_ENDIAN */
+ n += stuff_byte(fcs.bytes[1], tx_buff+n);
+ n += stuff_byte(fcs.bytes[0], tx_buff+n);
+#endif
+ tx_buff[n++] = EOF;
+
+ return n;
+}
+
+/*
+ * Function async_bump (buf, len, stats)
+ *
+ * Got a frame, make a copy of it, and pass it up the stack! We can try
+ * to inline it since it's only called from state_inside_frame
+ */
+inline void async_bump(struct net_device *dev, struct net_device_stats *stats,
+ __u8 *buf, int len)
+{
+ struct sk_buff *skb;
+
+ skb = dev_alloc_skb(len+1);
+ if (!skb) {
+ stats->rx_dropped++;
+ return;
+ }
+
+ /* Align IP header to 20 bytes */
+ skb_reserve(skb, 1);
+
+ /* Copy data without CRC */
+ memcpy(skb_put(skb, len-2), buf, len-2);
+
+ /* Feed it to IrLAP layer */
+ skb->dev = dev;
+ skb->mac.raw = skb->data;
+ skb->protocol = htons(ETH_P_IRDA);
+
+ netif_rx(skb);
+
+ stats->rx_packets++;
+ stats->rx_bytes += len;
+}
+
+/*
+ * Function async_unwrap_char (dev, rx_buff, byte)
+ *
+ * Parse and de-stuff frame received from the IrDA-port
+ *
+ */
+inline void async_unwrap_char(struct net_device *dev,
+ struct net_device_stats *stats,
+ iobuff_t *rx_buff, __u8 byte)
+{
+ (*state[rx_buff->state])(dev, stats, rx_buff, byte);
+}
+
+/*
+ * Function state_outside_frame (dev, rx_buff, byte)
+ *
+ * Not receiving any frame (or just bogus data)
+ *
+ */
+static void state_outside_frame(struct net_device *dev,
+ struct net_device_stats *stats,
+ iobuff_t *rx_buff, __u8 byte)
+{
+ switch (byte) {
+ case BOF:
+ rx_buff->state = BEGIN_FRAME;
+ rx_buff->in_frame = TRUE;
+ break;
+ case XBOF:
+ /* idev->xbofs++; */
+ break;
+ case EOF:
+ irda_device_set_media_busy(dev, TRUE);
+ break;
+ default:
+ irda_device_set_media_busy(dev, TRUE);
+ break;
+ }
+}
+
+/*
+ * Function state_begin_frame (idev, byte)
+ *
+ * Begin of frame detected
+ *
+ */
+static void state_begin_frame(struct net_device *dev,
+ struct net_device_stats *stats,
+ iobuff_t *rx_buff, __u8 byte)
+{
+ /* Time to initialize receive buffer */
+ rx_buff->data = rx_buff->head;
+ rx_buff->len = 0;
+ rx_buff->fcs = INIT_FCS;
+
+ switch (byte) {
+ case BOF:
+ /* Continue */
+ break;
+ case CE:
+ /* Stuffed byte */
+ rx_buff->state = LINK_ESCAPE;
+ break;
+ case EOF:
+ /* Abort frame */
+ rx_buff->state = OUTSIDE_FRAME;
+ IRDA_DEBUG(1, "%s(), abort frame\n", __FUNCTION__);
+ stats->rx_errors++;
+ stats->rx_frame_errors++;
+ break;
+ default:
+ rx_buff->data[rx_buff->len++] = byte;
+ rx_buff->fcs = irda_fcs(rx_buff->fcs, byte);
+ rx_buff->state = INSIDE_FRAME;
+ break;
+ }
+}
+
+/*
+ * Function state_link_escape (dev, byte)
+ *
+ * Found link escape character
+ *
+ */
+static void state_link_escape(struct net_device *dev,
+ struct net_device_stats *stats,
+ iobuff_t *rx_buff, __u8 byte)
+{
+ switch (byte) {
+ case BOF: /* New frame? */
+ IRDA_DEBUG(1, "%s(), Discarding incomplete frame\n", __FUNCTION__);
+ rx_buff->state = BEGIN_FRAME;
+ irda_device_set_media_busy(dev, TRUE);
+ break;
+ case CE:
+ WARNING("%s(), state not defined\n", __FUNCTION__);
+ break;
+ case EOF: /* Abort frame */
+ rx_buff->state = OUTSIDE_FRAME;
+ break;
+ default:
+ /*
+ * Stuffed char, complement bit 5 of byte
+ * following CE, IrLAP p.114
+ */
+ byte ^= IRDA_TRANS;
+ if (rx_buff->len < rx_buff->truesize) {
+ rx_buff->data[rx_buff->len++] = byte;
+ rx_buff->fcs = irda_fcs(rx_buff->fcs, byte);
+ rx_buff->state = INSIDE_FRAME;
+ } else {
+ IRDA_DEBUG(1, "%s(), rx buffer overflow\n", __FUNCTION__);
+ rx_buff->state = OUTSIDE_FRAME;
+ }
+ break;
+ }
+}
+
+/*
+ * Function state_inside_frame (dev, byte)
+ *
+ * Handle bytes received within a frame
+ *
+ */
+static void state_inside_frame(struct net_device *dev,
+ struct net_device_stats *stats,
+ iobuff_t *rx_buff, __u8 byte)
+{
+ int ret = 0;
+
+ switch (byte) {
+ case BOF: /* New frame? */
+ IRDA_DEBUG(1, "%s(), Discarding incomplete frame\n", __FUNCTION__);
+ rx_buff->state = BEGIN_FRAME;
+ irda_device_set_media_busy(dev, TRUE);
+ break;
+ case CE: /* Stuffed char */
+ rx_buff->state = LINK_ESCAPE;
+ break;
+ case EOF: /* End of frame */
+ rx_buff->state = OUTSIDE_FRAME;
+ rx_buff->in_frame = FALSE;
+
+ /* Test FCS and signal success if the frame is good */
+ if (rx_buff->fcs == GOOD_FCS) {
+ /* Deliver frame */
+ async_bump(dev, stats, rx_buff->data, rx_buff->len);
+ ret = TRUE;
+ break;
+ } else {
+ /* Wrong CRC, discard frame! */
+ irda_device_set_media_busy(dev, TRUE);
+
+ IRDA_DEBUG(1, "%s(), crc error\n", __FUNCTION__);
+ stats->rx_errors++;
+ stats->rx_crc_errors++;
+ }
+ break;
+ default: /* Must be the next byte of the frame */
+ if (rx_buff->len < rx_buff->truesize) {
+ rx_buff->data[rx_buff->len++] = byte;
+ rx_buff->fcs = irda_fcs(rx_buff->fcs, byte);
+ } else {
+ IRDA_DEBUG(1, "%s(), Rx buffer overflow, aborting\n", __FUNCTION__);
+ rx_buff->state = OUTSIDE_FRAME;
+ }
+ break;
+ }
+}
+
+
diff --git a/uClinux-2.4.31-uc0/net/khttpd/Config.in b/uClinux-2.4.31-uc0/net/khttpd/Config.in
new file mode 100644
index 0000000..32c7b70
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/Config.in
@@ -0,0 +1,10 @@
+# $USAGI: Config.in,v 1.4 2000/11/01 12:57:43 yoshfuji Exp $
+#
+# kHTTPd
+#
+tristate ' Kernel httpd acceleration (EXPERIMENTAL)' CONFIG_KHTTPD
+if [ "$CONFIG_KHTTPD" != "n" ]; then
+ if [ "$CONFIG_IPV6" != "n" ]; then
+ bool ' Use IPv6 socket for khttpd' CONFIG_KHTTPD_IPV6
+ fi
+fi
diff --git a/uClinux-2.4.31-uc0/net/khttpd/Makefile b/uClinux-2.4.31-uc0/net/khttpd/Makefile
new file mode 100644
index 0000000..a7b2e02
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/Makefile
@@ -0,0 +1,25 @@
+#
+# Makefile for kHTTPd
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := khttpd.o
+
+obj-m := $(O_TARGET)
+obj-y := main.o accept.o datasending.o logging.o misc.o rfc.o rfc_time.o security.o \
+ sockets.o sysctl.o userspace.o waitheaders.o
+
+
+include $(TOPDIR)/Rules.make
+
+rfc_time.o: times.h
+
+make_times_h: make_times_h.c
+ $(HOSTCC) $(HOSTCFLAGS) -o make_times_h make_times_h.c
+
+times.h: make_times_h
+ ./make_times_h
diff --git a/uClinux-2.4.31-uc0/net/khttpd/README b/uClinux-2.4.31-uc0/net/khttpd/README
new file mode 100644
index 0000000..33a8b07
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/README
@@ -0,0 +1,247 @@
+=====
+
+kHTTPd - Kernel httpd accelerator
+
+(C) 1999 by Arjan van de Ven
+Licensed under the terms of the GNU General Public License
+
+=====
+
+
+1. Introduction
+---------------
+ kHTTPd is a http-daemon (webserver) for Linux. kHTTPd is different from
+ other webservers in that it runs from within the Linux-kernel as a module
+ (device-driver).
+
+ kHTTPd handles only static (file based) web-pages, and passes all requests
+ for non-static information to a regular userspace-webserver such as Apache
+ or Zeus. The userspace-daemon doesn't have to be altered in any way.
+
+ Static web-pages are not a very complex thing to serve, but these are very
+ important nevertheless, since virtually all images are static, and a large
+ portion of the html-pages are static also. A "regular" webserver has little
+ added value for static pages, it is simply a "copy file to network"
+ operation.
+ This can be done very efficiently from within the Linux-kernel, for example
+ the nfs (network file system) daemon performs a similar task and also runs
+ in the kernel.
+
+ By "accelerating" the simple case within the kernel, userspace daemons can
+ do what they are very good at: Generating user-specific, dynamic content.
+
+ Note: This document sometimes uses "Apache" instead of "any webserver you
+ ever might want to use", just for reasons of readability.
+
+
+2. Quick Start
+--------------
+
+ 1) compile and load the module
+ 2) configure the module in /proc/sys/net/khttpd if needed
+ 3) echo 1 > /proc/sys/net/khttpd/start
+
+ unloading:
+
+ echo 1 > /proc/sys/net/khttpd/stop
+ echo 1 > /proc/sys/net/khttpd/unload
+ sleep 2
+ rmmod khttpd
+
+
+
+3. Configuration
+----------------
+
+ Modes of operation
+ ==================
+
+
+ There are two recommended modes of operation:
+
+ 1) "Apache" is main webserver, kHTTPd is assistant
+ clientport -> 80
+ serverport -> 8080 (or whatever)
+
+ 2) kHTTPd is main webserver, "Apache" is assistant
+ clientport -> 8080 (or whatever)
+ serverport -> 80
+
+
+ Configuring kHTTPd
+ ==================
+
+ Before you can start using kHTTPd, you have to configure it. This
+ is done through the /proc filesystem, and can thus be done from inside
+ a script. Most parameters can only be set when kHTTPd is stopped.
+
+ The following things need configuration:
+
+ 1) The port where kHTTPd should listen for requests
+ 2) The port (on "localhost") where "Apache" is listening
+ 3) The location of the documents (documentroot)
+ 4) The strings that indicate dynamic content (optional)
+ [ "cgi-bin" is added by default ]
+
+ It is very important that the documentroot for kHTTPd matches the
+ documentroot for the userspace-daemon, as kHTTPd might "redirect"
+ any request to this userspace-daemon.
+
+ A typical script (for the first mode of operation) to do this would
+ look like:
+
+#!/bin/sh
+modprobe khttpd
+echo 80 > /proc/sys/net/khttpd/clientport
+echo 8080 > /proc/sys/net/khttpd/serverport
+echo /var/www > /proc/sys/net/khttpd/documentroot
+echo php3 > /proc/sys/net/khttpd/dynamic
+echo shtml > /proc/sys/net/khttpd/dynamic
+echo 1 > /proc/sys/net/khttpd/start
+
+ For the second mode of operation, this would be:
+
+#!/bin/sh
+modprobe khttpd
+echo 8080 > /proc/sys/net/khttpd/clientport
+echo 80 > /proc/sys/net/khttpd/serverport
+echo /var/www > /proc/sys/net/khttpd/documentroot
+echo php3 > /proc/sys/net/khttpd/dynamic
+echo shtml > /proc/sys/net/khttpd/dynamic
+echo 1 > /proc/sys/net/khttpd/start
+
+ In this case, you also have to change the configuration of the
+ userspace-daemon. For Apache, you do this by changing
+
+ Port 80
+
+ to
+
+ Port 8080
+
+ Starting kHTTPd
+ ===============
+ Once you have set up the configuration, start kHTTPD by running
+ echo 1 > /proc/sys/net/khttpd/start
+ It may take a jiffie or two to start.
+
+ Stopping kHTTPd
+ ===============
+ To stop kHTTPd, do
+ echo 1 > /proc/sys/net/khttpd/stop
+ It should stop in a jiffy or two.
+
+ Unloading kHTTPd
+ ===============
+ To unload the module, do
+ echo 1 > /proc/sys/net/khttpd/stop
+ echo 1 > /proc/sys/net/khttpd/unload
+ #killall -HUP khttpd
+ sleep 2
+ rmmod khttpd
+
+ If this doesn't work fast enough for you (unloading can wait for
+ a remote connection to close down), you can send the daemons a "HUP"
+ signal after you told them to stop. This will cause the daemon-threads to
+ stop immediately.
+
+
+4. Permissions
+--------------
+ The security model of kHTTPd is very strict. It can be, since there is a
+ userspace daemon that can handle the complex exceptions.
+
+ kHTTPd only serves a file if
+
+ 1) There is no "?" in the URL
+ 2) The URL starts with a "/"
+ 3) The file indicated by the URL exists
+ 4) The file is world-readable (*)
+ 5) The file is not a directory, executable or has the Sticky-bit
+ set (*)
+ 6) The URL doesn't contain any "forbidden" substrings such as ".."
+ and "cgi-bin" (*)
+ 7) The mime-type is known (*)
+
+ The items marked with a (*) are configurable through the
+ sysctl-parameters in /proc/sys/net/khttpd.
+
+
+ In all cases where any of the above conditions isn't met, the
+ userspace-daemon is handed the request.
+
+
+
+5. Parameters
+-------------
+ The following parameters are settable through /proc/sys/net/khttpd:
+
+ Name Default Description
+
+ serverport 8080 The port where kHTTPd listens on
+
+ clientport 80 The port of the userspace
+ http-daemon
+
+ threads 2 The number of server-threads. Should
+ be 1 per CPU for small websites, 2
+ per CPU for big (the active files
+ do not fit in the RAM) websites.
+
+ documentroot /var/www the directory where the
+ document-files are
+
+ start 0 Set to 1 to start kHTTPd
+ (this also resets "stop" to 0)
+
+ stop 0 Set to 1 to stop kHTTPd
+ (this also resets "start" to 0)
+
+ unload 0 Set to 1 to prepare kHTTPd for
+ unloading of the module
+
+ sloppymime 0 If set to 1, unknown mime-types are
+ set to text/html. If set to 0,
+ files with unknown mime-types are
+ handled by the userspace daemon
+
+ perm_required S_IROTH Minimum permissions required
+ (for values see "man 2 stat")
+
+ perm_forbid dir+sticky+ Permission-mask with "forbidden"
+ execute permissions.
+ (for values see "man 2 stat")
+
+ dynamic cgi-bin .. Strings that, if they are a subset
+ of the URL, indicate "dynamic
+ content"
+
+ maxconnect 1000 Maximum number of concurrent
+ connections
+
+6. Known Issues
+ kHTTPd is *not* currently compatible with tmpfs. Trying to serve
+ files stored on a tmpfs partition is known to cause kernel oopses
+ as of 2.4.18. This is due to the same problem that prevents sendfile()
+ from being usable with tmpfs. A tmpfs patch is floating around that seems
+ to fix this, but has not been released as of 27 May 2002.
+ kHTTPD does work fine with ramfs, though.
+
+ There is debate about whether to remove kHTTPd from the main
+ kernel sources. This will probably happen in the 2.5 kernel series,
+ after which khttpd will still be available as a patch.
+
+ The kHTTPd source code could use a good spring cleaning.
+
+7. More information
+-------------------
+ More information about the architecture of kHTTPd, the mailinglist and
+ configuration-examples can be found at the kHTTPd homepage:
+
+ http://www.fenrus.demon.nl
+
+ Bugreports, patches, etc can be send to the mailinglist
+ (khttpd-users@zgp.org) or to khttpd@fenrus.demon.nl
+ Mailing list archives are at
+ http://lists.alt.org/mailman/listinfo/khttpd-users
+
diff --git a/uClinux-2.4.31-uc0/net/khttpd/accept.c b/uClinux-2.4.31-uc0/net/khttpd/accept.c
new file mode 100644
index 0000000..97dd217
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/accept.c
@@ -0,0 +1,127 @@
+/*
+
+kHTTPd -- the next generation
+
+Accept connections
+
+*/
+/****************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ****************************************************************/
+
+#include "structure.h"
+#include "prototypes.h"
+#include "sysctl.h"
+
+#include <linux/smp_lock.h>
+
+/*
+
+Purpose:
+
+AcceptConnections puts all "accepted" connections in the
+"WaitForHeader" queue.
+
+Return value:
+ The number of accepted connections
+*/
+
+
+int AcceptConnections(const int CPUNR, struct socket *Socket)
+{
+ struct http_request *NewRequest;
+ struct socket *NewSock;
+ int count = 0;
+ int error;
+
+ EnterFunction("AcceptConnections");
+
+ if (atomic_read(&ConnectCount)>sysctl_khttpd_maxconnect)
+ {
+ LeaveFunction("AcceptConnections - to many active connections");
+ return 0;
+ }
+
+ if (Socket==NULL) return 0;
+
+ /*
+ Quick test to see if there are connections on the queue.
+ This is cheaper than accept() itself because this saves us
+ the allocation of a new socket. (Which doesn't seem to be
+ used anyway)
+ */
+ if (Socket->sk->tp_pinfo.af_tcp.accept_queue==NULL)
+ {
+ return 0;
+ }
+
+ error = 0;
+ while (error>=0)
+ {
+ NewSock = sock_alloc();
+ if (NewSock==NULL)
+ break;
+
+
+ NewSock->type = Socket->type;
+ NewSock->ops = Socket->ops;
+
+
+ error = Socket->ops->accept(Socket,NewSock,O_NONBLOCK);
+
+
+ if (error<0)
+ {
+ sock_release(NewSock);
+ break;
+ }
+
+ if (NewSock->sk->state==TCP_CLOSE)
+ {
+ sock_release(NewSock);
+ continue;
+ }
+
+ /* Allocate a request-entry for the connection */
+ NewRequest = kmalloc(sizeof(struct http_request),(int)GFP_KERNEL);
+
+ if (NewRequest == NULL)
+ {
+ Send50x(NewSock); /* Service not available. Try again later */
+ sock_release(NewSock);
+ break;
+ }
+ memset(NewRequest,0,sizeof(struct http_request));
+
+ NewRequest->sock = NewSock;
+
+ NewRequest->Next = threadinfo[CPUNR].WaitForHeaderQueue;
+
+ init_waitqueue_entry(&NewRequest->sleep,current);
+
+ add_wait_queue(NewSock->sk->sleep,&(NewRequest->sleep));
+
+ threadinfo[CPUNR].WaitForHeaderQueue = NewRequest;
+
+ atomic_inc(&ConnectCount);
+
+
+ count++;
+ }
+
+ LeaveFunction("AcceptConnections");
+ return count;
+}
diff --git a/uClinux-2.4.31-uc0/net/khttpd/datasending.c b/uClinux-2.4.31-uc0/net/khttpd/datasending.c
new file mode 100644
index 0000000..91d57f9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/datasending.c
@@ -0,0 +1,241 @@
+/*
+
+kHTTPd -- the next generation
+
+Send actual file-data to the connections
+
+*/
+/****************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ****************************************************************/
+
+/*
+
+Purpose:
+
+DataSending does the actual sending of file-data to the socket.
+
+Note: Since asynchronous reads do not -yet- exists, this might block!
+
+Return value:
+ The number of requests that changed status (ie: made some progress)
+*/
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/locks.h>
+#include <linux/skbuff.h>
+
+#include <net/tcp.h>
+
+#include <asm/uaccess.h>
+#include <linux/smp_lock.h>
+
+#include "structure.h"
+#include "prototypes.h"
+
+static char *Block[CONFIG_KHTTPD_NUMCPU];
+
+/*
+
+This send_actor is for use with do_generic_file_read (ie sendfile())
+It sends the data to the socket indicated by desc->buf.
+
+*/
+static int sock_send_actor(read_descriptor_t * desc, struct page *page, unsigned long offset, unsigned long size)
+{
+ int written;
+ char *kaddr;
+ unsigned long count = desc->count;
+ struct socket *sock = (struct socket *) desc->buf;
+ mm_segment_t old_fs;
+
+ if (size > count)
+ size = count;
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+
+ kaddr = kmap(page);
+ written = SendBuffer_async(sock, kaddr + offset, size);
+ kunmap(page);
+ set_fs(old_fs);
+ if (written < 0) {
+ desc->error = written;
+ written = 0;
+ }
+ desc->count = count - written;
+ desc->written += written;
+ return written;
+}
+
+
+
+
+int DataSending(const int CPUNR)
+{
+ struct http_request *CurrentRequest,**Prev;
+ int count = 0;
+
+ EnterFunction("DataSending");
+
+ Prev = &(threadinfo[CPUNR].DataSendingQueue);
+ CurrentRequest = threadinfo[CPUNR].DataSendingQueue;
+ while (CurrentRequest!=NULL)
+ {
+ int ReadSize,Space;
+ int retval;
+
+
+ /* First, test if the socket has any buffer-space left.
+ If not, no need to actually try to send something. */
+
+
+ Space = sock_wspace(CurrentRequest->sock->sk);
+
+ ReadSize = min_t(int, 4 * 4096, CurrentRequest->FileLength - CurrentRequest->BytesSent);
+ ReadSize = min_t(int, ReadSize, Space);
+
+ if (ReadSize>0)
+ {
+ struct inode *inode;
+
+ inode = CurrentRequest->filp->f_dentry->d_inode;
+
+ if (inode->i_mapping->a_ops->readpage) {
+ /* This does the actual transfer using sendfile */
+ read_descriptor_t desc;
+ loff_t *ppos;
+
+ CurrentRequest->filp->f_pos = CurrentRequest->BytesSent;
+
+ ppos = &CurrentRequest->filp->f_pos;
+
+ desc.written = 0;
+ desc.count = ReadSize;
+ desc.buf = (char *) CurrentRequest->sock;
+ desc.error = 0;
+ do_generic_file_read(CurrentRequest->filp, ppos, &desc, sock_send_actor);
+ if (desc.written>0)
+ {
+ CurrentRequest->BytesSent += desc.written;
+ count++;
+ }
+ }
+ else /* FS doesn't support sendfile() */
+ {
+ mm_segment_t oldfs;
+ CurrentRequest->filp->f_pos = CurrentRequest->BytesSent;
+
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ retval = CurrentRequest->filp->f_op->read(CurrentRequest->filp, Block[CPUNR], ReadSize, &CurrentRequest->filp->f_pos);
+ set_fs(oldfs);
+
+ if (retval>0)
+ {
+ retval = SendBuffer_async(CurrentRequest->sock,Block[CPUNR],(size_t)retval);
+ if (retval>0)
+ {
+ CurrentRequest->BytesSent += retval;
+ count++;
+ }
+ }
+ }
+
+ }
+
+ /*
+ If end-of-file or closed connection: Finish this request
+ by moving it to the "logging" queue.
+ */
+ if ((CurrentRequest->BytesSent>=CurrentRequest->FileLength)||
+ (CurrentRequest->sock->sk->state!=TCP_ESTABLISHED
+ && CurrentRequest->sock->sk->state!=TCP_CLOSE_WAIT))
+ {
+ struct http_request *Next;
+ Next = CurrentRequest->Next;
+
+ lock_sock(CurrentRequest->sock->sk);
+ if (CurrentRequest->sock->sk->state == TCP_ESTABLISHED ||
+ CurrentRequest->sock->sk->state == TCP_CLOSE_WAIT)
+ {
+ CurrentRequest->sock->sk->tp_pinfo.af_tcp.nonagle = 0;
+ tcp_push_pending_frames(CurrentRequest->sock->sk,&(CurrentRequest->sock->sk->tp_pinfo.af_tcp));
+ }
+ release_sock(CurrentRequest->sock->sk);
+
+ (*Prev) = CurrentRequest->Next;
+
+ CurrentRequest->Next = threadinfo[CPUNR].LoggingQueue;
+ threadinfo[CPUNR].LoggingQueue = CurrentRequest;
+
+ CurrentRequest = Next;
+ continue;
+
+ }
+
+
+ Prev = &(CurrentRequest->Next);
+ CurrentRequest = CurrentRequest->Next;
+ }
+
+ LeaveFunction("DataSending");
+ return count;
+}
+
+int InitDataSending(int ThreadCount)
+{
+ int I,I2;
+
+ EnterFunction("InitDataSending");
+ I=0;
+ while (I<ThreadCount)
+ {
+ Block[I] = (char*)get_free_page((int)GFP_KERNEL);
+ if (Block[I] == NULL)
+ {
+ I2=0;
+ while (I2<I-1)
+ {
+ free_page((unsigned long)Block[I2++]);
+ }
+ LeaveFunction("InitDataSending - abort");
+ return -1;
+ }
+ I++;
+ }
+ LeaveFunction("InitDataSending");
+ return 0;
+}
+
+void StopDataSending(const int CPUNR)
+{
+ struct http_request *CurrentRequest,*Next;
+
+ EnterFunction("StopDataSending");
+ CurrentRequest = threadinfo[CPUNR].DataSendingQueue;
+
+ while (CurrentRequest!=NULL)
+ {
+ Next = CurrentRequest->Next;
+ CleanUpRequest(CurrentRequest);
+ CurrentRequest=Next;
+ }
+
+ threadinfo[CPUNR].DataSendingQueue = NULL;
+
+ free_page( (unsigned long)Block[CPUNR]);
+ LeaveFunction("StopDataSending");
+}
diff --git a/uClinux-2.4.31-uc0/net/khttpd/logging.c b/uClinux-2.4.31-uc0/net/khttpd/logging.c
new file mode 100644
index 0000000..2291167
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/logging.c
@@ -0,0 +1,95 @@
+/*
+
+kHTTPd -- the next generation
+
+logging.c takes care of shutting down a connection.
+
+*/
+/****************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ****************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+#include <linux/smp_lock.h>
+#include <net/tcp.h>
+#include <asm/uaccess.h>
+#include "structure.h"
+#include "prototypes.h"
+
+/*
+
+Purpose:
+
+Logging() terminates "finished" connections and will eventually log them to a
+userspace daemon.
+
+Return value:
+ The number of requests that changed status, thus the number of connections
+ that shut down.
+*/
+
+
+int Logging(const int CPUNR)
+{
+ struct http_request *CurrentRequest,*Req;
+ int count = 0;
+
+ EnterFunction("Logging");
+
+ CurrentRequest = threadinfo[CPUNR].LoggingQueue;
+
+ /* For now, all requests are removed immediatly, but this changes
+ when userspace-logging is added. */
+
+ while (CurrentRequest!=NULL)
+ {
+
+ Req = CurrentRequest->Next;
+
+ CleanUpRequest(CurrentRequest);
+
+ threadinfo[CPUNR].LoggingQueue = Req;
+
+ CurrentRequest = Req;
+
+ count++;
+
+ }
+
+ LeaveFunction("Logging");
+ return count;
+}
+
+
+
+void StopLogging(const int CPUNR)
+{
+ struct http_request *CurrentRequest,*Next;
+
+ EnterFunction("StopLogging");
+ CurrentRequest = threadinfo[CPUNR].LoggingQueue;
+
+ while (CurrentRequest!=NULL)
+ {
+ Next=CurrentRequest->Next;
+ CleanUpRequest(CurrentRequest);
+ CurrentRequest=Next;
+ }
+
+ threadinfo[CPUNR].LoggingQueue = NULL;
+ LeaveFunction("StopLogging");
+}
diff --git a/uClinux-2.4.31-uc0/net/khttpd/main.c b/uClinux-2.4.31-uc0/net/khttpd/main.c
new file mode 100644
index 0000000..2fe2c36
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/main.c
@@ -0,0 +1,362 @@
+/*
+
+kHTTPd -- the next generation
+
+Main program
+
+
+kHTTPd TNG consists of 1 thread, this main-thread handles ALL connections
+simultanious. It does this by keeping queues with the requests in different
+stages.
+
+The stages are
+
+<not accepted> - TCP/IP connection is not accepted yet
+WaitForHeaders - Connection is accepted, waiting for headers
+DataSending - Headers decoded, sending file-data
+Userspace - Requires userspace daemon
+Logging - The request is finished, cleanup and logging
+
+A typical flow for a request would be:
+
+<not accepted>
+WaitForHeaders
+DataSending
+Logging
+
+or
+
+<not accepted>
+WaitForHeaders
+Userspace
+
+
+
+*/
+/****************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ****************************************************************/
+
+
+static int errno;
+#define __KERNEL_SYSCALLS__
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/signal.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/smp_lock.h>
+#include <asm/unistd.h>
+
+#include "structure.h"
+#include "prototypes.h"
+#include "sysctl.h"
+
+struct khttpd_threadinfo threadinfo[CONFIG_KHTTPD_NUMCPU]; /* The actual work-queues */
+
+
+atomic_t ConnectCount;
+atomic_t DaemonCount;
+
+static int ActualThreads; /* The number of actual, active threads */
+
+
+static int ConnectionsPending(int CPUNR)
+{
+ if (threadinfo[CPUNR].DataSendingQueue!=NULL) return O_NONBLOCK;
+ if (threadinfo[CPUNR].WaitForHeaderQueue!=NULL) return O_NONBLOCK;
+ if (threadinfo[CPUNR].LoggingQueue!=NULL) return O_NONBLOCK;
+ if (threadinfo[CPUNR].UserspaceQueue!=NULL) return O_NONBLOCK;
+ return 0;
+}
+
+
+
+static wait_queue_head_t DummyWQ[CONFIG_KHTTPD_NUMCPU];
+static atomic_t Running[CONFIG_KHTTPD_NUMCPU];
+
+static int MainDaemon(void *cpu_pointer)
+{
+ int CPUNR;
+ sigset_t tmpsig;
+ int old_stop_count;
+
+ DECLARE_WAITQUEUE(main_wait,current);
+
+ MOD_INC_USE_COUNT;
+
+ /* Remember value of stop count. If it changes, user must have
+ * asked us to stop. Sensing this is much less racy than
+ * directly sensing sysctl_khttpd_stop. - dank
+ */
+ old_stop_count = atomic_read(&khttpd_stopCount);
+
+ CPUNR=0;
+ if (cpu_pointer!=NULL)
+ CPUNR=(int)*(int*)cpu_pointer;
+
+ sprintf(current->comm,"khttpd - %i",CPUNR);
+ daemonize();
+
+ init_waitqueue_head(&(DummyWQ[CPUNR]));
+
+
+ /* Block all signals except SIGKILL, SIGSTOP and SIGHUP */
+ spin_lock_irq(&current->sigmask_lock);
+ tmpsig = current->blocked;
+ siginitsetinv(&current->blocked, sigmask(SIGKILL) | sigmask(SIGSTOP)| sigmask(SIGHUP));
+ recalc_sigpending(current);
+ spin_unlock_irq(&current->sigmask_lock);
+
+
+ if (MainSocket->sk==NULL)
+ return 0;
+ add_wait_queue_exclusive(MainSocket->sk->sleep,&(main_wait));
+ atomic_inc(&DaemonCount);
+ atomic_set(&Running[CPUNR],1);
+
+ while (old_stop_count == atomic_read(&khttpd_stopCount))
+ {
+ int changes = 0;
+
+ changes +=AcceptConnections(CPUNR,MainSocket);
+ if (ConnectionsPending(CPUNR))
+ {
+ changes +=WaitForHeaders(CPUNR);
+ changes +=DataSending(CPUNR);
+ changes +=Userspace(CPUNR);
+ changes +=Logging(CPUNR);
+ /* Test for incoming connections _again_, because it is possible
+ one came in during the other steps, and the wakeup doesn't happen
+ then.
+ */
+ changes +=AcceptConnections(CPUNR,MainSocket);
+ }
+
+ if (changes==0)
+ {
+ (void)interruptible_sleep_on_timeout(&(DummyWQ[CPUNR]),1);
+ if (CPUNR==0)
+ UpdateCurrentDate();
+ }
+
+ if (signal_pending(current)!=0)
+ {
+ (void)printk(KERN_NOTICE "kHTTPd: Ring Ring - signal received\n");
+ break;
+ }
+
+ }
+
+ remove_wait_queue(MainSocket->sk->sleep,&(main_wait));
+
+ StopWaitingForHeaders(CPUNR);
+ StopDataSending(CPUNR);
+ StopUserspace(CPUNR);
+ StopLogging(CPUNR);
+
+ atomic_set(&Running[CPUNR],0);
+ atomic_dec(&DaemonCount);
+ (void)printk(KERN_NOTICE "kHTTPd: Daemon %i has ended\n",CPUNR);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int CountBuf[CONFIG_KHTTPD_NUMCPU];
+
+
+
+/*
+
+The ManagementDaemon has a very simple task: Start the real daemons when the user wants us
+to, and cleanup when the users wants to unload the module.
+
+Initially, kHTTPd didn't have this thread, but it is the only way to have "delayed activation",
+a feature required to prevent accidental activations resulting in unexpected backdoors.
+
+*/
+static int ManagementDaemon(void *unused)
+{
+ sigset_t tmpsig;
+ int waitpid_result;
+
+ DECLARE_WAIT_QUEUE_HEAD(WQ);
+
+ sprintf(current->comm,"khttpd manager");
+ daemonize();
+
+ /* Block all signals except SIGKILL and SIGSTOP */
+ spin_lock_irq(&current->sigmask_lock);
+ tmpsig = current->blocked;
+ siginitsetinv(&current->blocked, sigmask(SIGKILL) | sigmask(SIGSTOP) );
+ recalc_sigpending(current);
+ spin_unlock_irq(&current->sigmask_lock);
+
+ /* main loop */
+ while (sysctl_khttpd_unload==0)
+ {
+ int I;
+ int old_stop_count;
+
+ /* First : wait for activation */
+ while ( (sysctl_khttpd_start==0) && (!signal_pending(current)) && (sysctl_khttpd_unload==0) )
+ {
+ current->state = TASK_INTERRUPTIBLE;
+ interruptible_sleep_on_timeout(&WQ,HZ);
+ }
+ if ( (signal_pending(current)) || (sysctl_khttpd_unload!=0) )
+ break;
+ sysctl_khttpd_stop = 0;
+
+ /* Then start listening and spawn the daemons */
+ if (StartListening(sysctl_khttpd_serverport)==0)
+ {
+ sysctl_khttpd_start = 0;
+ continue;
+ }
+
+ ActualThreads = sysctl_khttpd_threads;
+ if (ActualThreads<1)
+ ActualThreads = 1;
+ if (ActualThreads>CONFIG_KHTTPD_NUMCPU)
+ ActualThreads = CONFIG_KHTTPD_NUMCPU;
+ /* Write back the actual value */
+ sysctl_khttpd_threads = ActualThreads;
+
+ InitUserspace(ActualThreads);
+
+ if (InitDataSending(ActualThreads)!=0)
+ {
+ StopListening();
+ sysctl_khttpd_start = 0;
+ continue;
+ }
+ if (InitWaitHeaders(ActualThreads)!=0)
+ {
+ for (I=0; I<ActualThreads; I++) {
+ StopDataSending(I);
+ }
+ StopListening();
+ sysctl_khttpd_start = 0;
+ continue;
+ }
+
+ /* Clean all queues */
+ memset(threadinfo, 0, sizeof(struct khttpd_threadinfo));
+
+ for (I=0; I<ActualThreads; I++) {
+ atomic_set(&Running[I],1);
+ (void)kernel_thread(MainDaemon,&(CountBuf[I]), CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
+ }
+
+ /* Then wait for deactivation */
+ /* Remember value of stop count. If it changes, user must
+ * have asked us to stop. Sensing this is much less racy
+ * than directly sensing sysctl_khttpd_stop. - dank
+ */
+ old_stop_count = atomic_read(&khttpd_stopCount);
+ while ( ( old_stop_count == atomic_read(&khttpd_stopCount))
+ && (!signal_pending(current))
+ && (sysctl_khttpd_unload==0) )
+ {
+ /* Used to restart dead threads here, but it was buggy*/
+ interruptible_sleep_on_timeout(&WQ,HZ);
+ }
+
+ /* Wait for the daemons to stop, one second per iteration */
+ while (atomic_read(&DaemonCount)>0)
+ interruptible_sleep_on_timeout(&WQ,HZ);
+ StopListening();
+ sysctl_khttpd_start = 0;
+ /* reap the zombie-daemons */
+ do
+ waitpid_result = waitpid(-1,NULL,__WCLONE|WNOHANG);
+ while (waitpid_result>0);
+ }
+ sysctl_khttpd_start = 0;
+ sysctl_khttpd_stop = 1;
+ atomic_inc(&khttpd_stopCount);
+
+ /* Wait for the daemons to stop, one second per iteration */
+ while (atomic_read(&DaemonCount)>0)
+ interruptible_sleep_on_timeout(&WQ,HZ);
+ StopListening();
+ /* reap the zombie-daemons */
+ do
+ waitpid_result = waitpid(-1,NULL,__WCLONE|WNOHANG);
+ while (waitpid_result>0);
+
+ (void)printk(KERN_NOTICE "kHTTPd: Management daemon stopped. \n You can unload the module now.\n");
+
+ MOD_DEC_USE_COUNT;
+
+ return 0;
+}
+
+int __init khttpd_init(void)
+{
+ int I;
+
+ MOD_INC_USE_COUNT;
+
+ for (I=0; I<CONFIG_KHTTPD_NUMCPU; I++) {
+ CountBuf[I]=I;
+ }
+
+ atomic_set(&ConnectCount,0);
+ atomic_set(&DaemonCount,0);
+ atomic_set(&khttpd_stopCount,0);
+
+
+ /* Maybe the mime-types will be set-able through sysctl in the future */
+
+ AddMimeType(".htm","text/html");
+ AddMimeType("html","text/html");
+ AddMimeType(".gif","image/gif");
+ AddMimeType(".jpg","image/jpeg");
+ AddMimeType(".png","image/png");
+ AddMimeType("tiff","image/tiff");
+ AddMimeType(".zip","application/zip");
+ AddMimeType(".pdf","application/pdf");
+ AddMimeType("r.gz","application/x-gtar");
+ AddMimeType(".tgz","application/x-gtar");
+ AddMimeType(".deb","application/x-debian-package");
+ AddMimeType("lass","application/x-java");
+ AddMimeType(".mp3","audio/mpeg");
+ AddMimeType(".txt","text/plain");
+
+ AddDynamicString("..");
+ AddDynamicString("cgi-bin");
+
+ StartSysctl();
+
+ (void)kernel_thread(ManagementDaemon,NULL, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
+
+ return 0;
+}
+
+void khttpd_cleanup(void)
+{
+ EndSysctl();
+}
+
+ module_init(khttpd_init)
+ module_exit(khttpd_cleanup)
+
+ MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/khttpd/make_times_h.c b/uClinux-2.4.31-uc0/net/khttpd/make_times_h.c
new file mode 100644
index 0000000..84a9d3c
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/make_times_h.c
@@ -0,0 +1,122 @@
+/*
+
+This program generates the "times.h" file with the zulu-times of the first of
+every month of a decade.
+
+*/
+/****************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ****************************************************************/
+
+#include <time.h>
+#include <stdio.h>
+
+static time_t GetDay(int D,int M,int Y)
+{
+ struct tm TM;
+
+ TM.tm_sec = 0;
+ TM.tm_min = 0;
+ TM.tm_hour = 0;
+ TM.tm_mday = D;
+ TM.tm_mon = M;
+ TM.tm_wday = 0;
+ TM.tm_yday = 0;
+ TM.tm_year = Y-1900;
+ TM.tm_isdst = 0;
+
+ return mktime(&TM);
+
+}
+static int WeekGetDay(int D,int M,int Y)
+{
+ struct tm TM;
+
+ TM.tm_sec = 0;
+ TM.tm_min = 0;
+ TM.tm_hour = 0;
+ TM.tm_mday = D;
+ TM.tm_mon = M;
+ TM.tm_year = Y-1900;
+ TM.tm_isdst = 0;
+ TM.tm_wday = 0;
+ TM.tm_yday = 0;
+
+ (void)mktime(&TM);
+
+ return TM.tm_wday;
+
+}
+
+int main(void)
+{
+ int M,Y;
+ FILE *file;
+
+ file=fopen("times.h","w");
+
+ if (file==NULL)
+ return 0;
+
+ fprintf(file,"static time_t TimeDays[10][13] = { \n");
+
+ Y=1997;
+ while (Y<2007)
+ {
+ M=0;
+ fprintf(file," { ");
+ while (M<12)
+ {
+ fprintf(file,"%i",(int)GetDay(1,M,Y));
+ fprintf(file,",\t");
+
+ M++;
+ }
+
+ fprintf(file,"%i } ",(int)GetDay(1,0,Y+1));
+ if (Y!=2006) fprintf(file,",");
+ fprintf(file,"\n");
+ Y++;
+ }
+ fprintf(file,"};\n");
+
+ fprintf(file,"static int WeekDays[10][13] = { \n");
+
+ Y=1997;
+ while (Y<2007)
+ {
+ M=0;
+ fprintf(file," { ");
+ while (M<12)
+ {
+ fprintf(file,"%i",(int)WeekGetDay(1,M,Y));
+ fprintf(file,",\t");
+
+ M++;
+ }
+
+ fprintf(file,"%i } ",(int)WeekGetDay(1,0,Y+1));
+ if (Y!=2006) fprintf(file,",");
+ fprintf(file,"\n");
+ Y++;
+ }
+ fprintf(file,"};\n");
+ fprintf(file,"#define KHTTPD_YEAROFFSET 1997\n");
+ fprintf(file,"#define KHTTPD_NUMYEARS 10\n");
+ (void)fclose(file);
+
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/khttpd/misc.c b/uClinux-2.4.31-uc0/net/khttpd/misc.c
new file mode 100644
index 0000000..b5a2f3f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/misc.c
@@ -0,0 +1,242 @@
+/*
+
+kHTTPd -- the next generation
+
+General functions
+
+*/
+/****************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ****************************************************************/
+
+#include <linux/kernel.h>
+
+#include <linux/ctype.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/net.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/unistd.h>
+#include <linux/file.h>
+#include <linux/smp_lock.h>
+
+#include <net/ip.h>
+#include <net/sock.h>
+
+#include <asm/atomic.h>
+#include <asm/errno.h>
+#include <asm/semaphore.h>
+#include <asm/processor.h>
+#include <asm/uaccess.h>
+
+#include "structure.h"
+#include "prototypes.h"
+
+#ifndef ECONNRESET
+#define ECONNRESET 102
+#endif
+
+
+/*
+
+Readrest reads and discards all pending input on a socket. This is required
+before closing the socket.
+
+*/
+static void ReadRest(struct socket *sock)
+{
+ struct msghdr msg;
+ struct iovec iov;
+ int len;
+
+ mm_segment_t oldfs;
+
+
+ EnterFunction("ReadRest");
+
+
+ if (sock->sk==NULL)
+ return;
+
+ len = 1;
+
+ while (len>0)
+ {
+ static char Buffer[1024]; /* Never read, so doesn't need to
+ be SMP safe */
+
+ msg.msg_name = 0;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = MSG_DONTWAIT;
+
+ msg.msg_iov->iov_base = &Buffer[0];
+ msg.msg_iov->iov_len = (__kernel_size_t)1024;
+
+ len = 0;
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ len = sock_recvmsg(sock,&msg,1024,MSG_DONTWAIT);
+ set_fs(oldfs);
+ }
+ LeaveFunction("ReadRest");
+}
+
+
+/*
+
+CleanUpRequest takes care of shutting down the connection, closing the file-pointer
+and releasing the memory of the request-structure. Do not try to access it afterwards!
+
+*/
+void CleanUpRequest(struct http_request *Req)
+{
+ EnterFunction("CleanUpRequest");
+
+ /* Close the socket ....*/
+ if ((Req->sock!=NULL)&&(Req->sock->sk!=NULL))
+ {
+ ReadRest(Req->sock);
+ remove_wait_queue(Req->sock->sk->sleep,&(Req->sleep));
+ sock_release(Req->sock);
+ }
+
+ /* ... and the file-pointer ... */
+ if (Req->filp!=NULL)
+ {
+ fput(Req->filp);
+ Req->filp = NULL;
+ }
+
+
+ /* ... and release the memory for the structure. */
+ kfree(Req);
+
+ atomic_dec(&ConnectCount);
+ LeaveFunction("CleanUpRequest");
+}
+
+
+/*
+
+SendBuffer and Sendbuffer_async send "Length" bytes from "Buffer" to the "sock"et.
+The _async-version is non-blocking.
+
+A positive return-value indicates the number of bytes sent, a negative value indicates
+an error-condition.
+
+*/
+int SendBuffer(struct socket *sock, const char *Buffer,const size_t Length)
+{
+ struct msghdr msg;
+ mm_segment_t oldfs;
+ struct iovec iov;
+ int len;
+
+ EnterFunction("SendBuffer");
+
+ msg.msg_name = 0;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = MSG_NOSIGNAL;
+ msg.msg_iov->iov_len = (__kernel_size_t)Length;
+ msg.msg_iov->iov_base = (char*) Buffer;
+
+
+ len = 0;
+
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ len = sock_sendmsg(sock,&msg,(size_t)(Length-len));
+ set_fs(oldfs);
+ LeaveFunction("SendBuffer");
+ return len;
+}
+
+int SendBuffer_async(struct socket *sock, const char *Buffer,const size_t Length)
+{
+ struct msghdr msg;
+ mm_segment_t oldfs;
+ struct iovec iov;
+ int len;
+
+ EnterFunction("SendBuffer_async");
+ msg.msg_name = 0;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = MSG_DONTWAIT|MSG_NOSIGNAL;
+ msg.msg_iov->iov_base = (char*) Buffer;
+ msg.msg_iov->iov_len = (__kernel_size_t)Length;
+
+
+ if (sock->sk)
+ {
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ len = sock_sendmsg(sock,&msg,(size_t)(Length));
+ set_fs(oldfs);
+ } else
+ {
+ return -ECONNRESET;
+ }
+
+ LeaveFunction("SendBuffer_async");
+ return len;
+}
+
+
+
+
+/*
+
+HTTP header shortcuts. Hardcoded since these might be called in a low-memory
+situation, and they don't change anyhow.
+
+*/
+
+static char NoPerm[] = "HTTP/1.0 403 Forbidden\r\nServer: kHTTPd 0.1.6\r\n\r\n";
+static char TryLater[] = "HTTP/1.0 503 Service Unavailable\r\nServer: kHTTPd 0.1.6\r\nContent-Length: 15\r\n\r\nTry again later";
+static char NotModified[] = "HTTP/1.0 304 Not Modified\r\nServer: kHTTPd 0.1.6\r\n\r\n";
+
+
+void Send403(struct socket *sock)
+{
+ EnterFunction("Send403");
+ (void)SendBuffer(sock,NoPerm,strlen(NoPerm));
+ LeaveFunction("Send403");
+}
+
+void Send304(struct socket *sock)
+{
+ EnterFunction("Send304");
+ (void)SendBuffer(sock,NotModified,strlen(NotModified));
+ LeaveFunction("Send304");
+}
+
+void Send50x(struct socket *sock)
+{
+ EnterFunction("Send50x");
+ (void)SendBuffer(sock,TryLater,strlen(TryLater));
+ LeaveFunction("Send50x");
+}
+
diff --git a/uClinux-2.4.31-uc0/net/khttpd/prototypes.h b/uClinux-2.4.31-uc0/net/khttpd/prototypes.h
new file mode 100644
index 0000000..c478308
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/prototypes.h
@@ -0,0 +1,120 @@
+#ifndef _INCLUDE_GUARD_PROTOTYPES_H
+#define _INCLUDE_GUARD_PROTOTYPES_H
+
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/net.h>
+#include <linux/time.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+
+#include "structure.h"
+
+
+/* General defines and stuff */
+
+
+#define CONFIG_KHTTPD_NUMCPU 16 /* Maximum number of threads */
+
+#ifdef OOPSTRACE
+#define EnterFunction(x) printk("Enter: %s, %s line %i\n",x,__FILE__,__LINE__)
+#define LeaveFunction(x) printk("Leave: %s, %s line %i\n",x,__FILE__,__LINE__)
+#else
+#define EnterFunction(x) do {} while (0)
+#define LeaveFunction(x) do {} while (0)
+#endif
+
+
+
+/* sockets.c */
+int StartListening(const int Port);
+void StopListening(void);
+
+extern struct socket *MainSocket;
+
+
+/* sysctl.c */
+void StartSysctl(void);
+void EndSysctl(void);
+
+extern int sysctl_khttpd_stop;
+
+
+/* main.c */
+
+
+extern struct khttpd_threadinfo threadinfo[CONFIG_KHTTPD_NUMCPU];
+extern char CurrentTime[];
+extern atomic_t ConnectCount;
+extern struct wait_queue main_wait[CONFIG_KHTTPD_NUMCPU];
+
+/* misc.c */
+
+void CleanUpRequest(struct http_request *Req);
+int SendBuffer(struct socket *sock, const char *Buffer,const size_t Length);
+int SendBuffer_async(struct socket *sock, const char *Buffer,const size_t Length);
+void Send403(struct socket *sock);
+void Send304(struct socket *sock);
+void Send50x(struct socket *sock);
+
+/* accept.c */
+
+int AcceptConnections(const int CPUNR,struct socket *Socket);
+
+/* waitheaders.c */
+
+int WaitForHeaders(const int CPUNR);
+void StopWaitingForHeaders(const int CPUNR);
+int InitWaitHeaders(int ThreadCount);
+
+/* datasending.c */
+
+int DataSending(const int CPUNR);
+void StopDataSending(const int CPUNR);
+int InitDataSending(int ThreadCount);
+
+
+/* userspace.c */
+
+int Userspace(const int CPUNR);
+void StopUserspace(const int CPUNR);
+void InitUserspace(const int CPUNR);
+
+
+/* rfc_time.c */
+
+void time_Unix2RFC(const time_t Zulu,char *Buffer);
+void UpdateCurrentDate(void);
+time_t mimeTime_to_UnixTime(char *Q);
+extern int CurrentTime_i;
+
+/* rfc.c */
+
+void ParseHeader(char *Buffer,const int length, struct http_request *Head);
+char *ResolveMimeType(const char *File,__kernel_size_t *Len);
+void AddMimeType(const char *Ident,const char *Type);
+void SendHTTPHeader(struct http_request *Request);
+
+
+
+/* security.c */
+
+struct file *OpenFileForSecurity(char *Filename);
+void AddDynamicString(const char *String);
+void GetSecureString(char *String);
+
+
+/* logging.c */
+
+int Logging(const int CPUNR);
+void StopLogging(const int CPUNR);
+
+
+/* Other prototypes */
+
+
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/khttpd/rfc.c b/uClinux-2.4.31-uc0/net/khttpd/rfc.c
new file mode 100644
index 0000000..9c570b3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/rfc.c
@@ -0,0 +1,374 @@
+/*
+
+kHTTPd -- the next generation
+
+RFC related functions (headers and stuff)
+
+*/
+
+/****************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ****************************************************************/
+
+
+#include <linux/kernel.h>
+
+#include <linux/ctype.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/net.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/unistd.h>
+#include <linux/file.h>
+#include <linux/smp_lock.h>
+
+#include <net/ip.h>
+#include <net/sock.h>
+
+#include <asm/atomic.h>
+#include <asm/semaphore.h>
+#include <asm/processor.h>
+#include <asm/uaccess.h>
+
+
+#include "prototypes.h"
+#include "structure.h"
+#include "sysctl.h"
+
+
+#define KHTTPD_NUMMIMETYPES 40
+
+static atomic_t MimeCount;
+
+struct MimeType
+{
+ __u32 identifier;
+ char type[64-sizeof(__u32)-sizeof(__kernel_size_t)];
+ __kernel_size_t len;
+};
+
+static struct MimeType MimeTypes[KHTTPD_NUMMIMETYPES];
+
+
+void AddMimeType(const char *Ident,const char *Type)
+{
+ __u32 *I;
+
+ EnterFunction("AddMimeType");
+
+ if (strlen(Ident)!=4)
+ {
+ (void)printk(KERN_ERR "httpd: Only 4-byte mime-identifiers are accepted\n");
+ return;
+ }
+
+ if (strlen(Type)>(64-sizeof(__u32)-sizeof(__kernel_size_t) ) )
+ {
+ (void)printk(KERN_ERR "httpd: Mime-string too long.\n");
+ return;
+ }
+
+ I=(__u32*)Ident;
+
+ /* FIXME: Need to lock-down all access to the mime-structure here */
+ /* For now, just don't add mime-types after initialisation */
+
+
+ MimeTypes[atomic_read(&MimeCount)].identifier=*I;
+ strncpy(MimeTypes[atomic_read(&MimeCount)].type,Type,(64-sizeof(__u32)-sizeof(__kernel_size_t)));
+ MimeTypes[atomic_read(&MimeCount)].len = strlen(Type);
+
+ atomic_inc(&MimeCount);
+ LeaveFunction("AddMimeType");
+}
+
+
+char *ResolveMimeType(const char *File,__kernel_size_t *Len)
+/*
+
+ The returned string is for READ ONLY, ownership of the memory is NOT
+ transferred.
+
+*/
+{
+ __u32 *I;
+ int pos,lc,filelen;
+
+ EnterFunction("ResolveMimeType");
+
+ *Len = 0;
+
+ if (File==NULL)
+ return NULL;
+
+ filelen = (int)strlen(File);
+
+ if (filelen<4)
+ {
+ return NULL;
+ }
+
+ /* The Merced-people are NOT going to like this! So this has to be fixed
+ in a later stage. */
+
+ pos = filelen-4;
+ I=(__u32*)(File+pos);
+
+ lc=0;
+
+ while (lc<atomic_read(&MimeCount))
+ {
+ if (MimeTypes[lc].identifier == *I)
+ {
+ *Len = MimeTypes[lc].len;
+ LeaveFunction("ResolveMimeType - success");
+ return MimeTypes[lc].type;
+ }
+ lc++;
+ }
+
+ if (sysctl_khttpd_sloppymime)
+ {
+ *Len = MimeTypes[0].len;
+ LeaveFunction("ResolveMimeType - unknown");
+ return MimeTypes[0].type;
+ }
+ else
+ {
+ LeaveFunction("ResolveMimeType - failure");
+ return NULL;
+ }
+}
+
+
+static char HeaderPart1[] = "HTTP/1.0 200 OK\r\nServer: kHTTPd/0.1.6\r\nDate: ";
+#ifdef BENCHMARK
+static char HeaderPart1b[] ="HTTP/1.0 200 OK";
+#endif
+static char HeaderPart3[] = "\r\nContent-type: ";
+static char HeaderPart5[] = "\r\nLast-modified: ";
+static char HeaderPart7[] = "\r\nContent-length: ";
+static char HeaderPart9[] = "\r\n\r\n";
+
+#ifdef BENCHMARK
+/* In BENCHMARK-mode, just send the bare essentials */
+void SendHTTPHeader(struct http_request *Request)
+{
+ struct msghdr msg;
+ mm_segment_t oldfs;
+ struct iovec iov[9];
+ int len,len2;
+
+
+ EnterFunction("SendHTTPHeader");
+
+ msg.msg_name = 0;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov[0];
+ msg.msg_iovlen = 6;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0; /* Synchronous for now */
+
+ iov[0].iov_base = HeaderPart1b;
+ iov[0].iov_len = 15;
+ iov[1].iov_base = HeaderPart3;
+ iov[1].iov_len = 16;
+ iov[2].iov_base = Request->MimeType;
+ iov[2].iov_len = Request->MimeLength;
+
+ iov[3].iov_base = HeaderPart7;
+ iov[3].iov_len = 18;
+
+
+ sprintf(Request->LengthS,"%i",Request->FileLength);
+ iov[4].iov_base = Request->LengthS;
+ iov[4].iov_len = strlen(Request->LengthS);
+ iov[5].iov_base = HeaderPart9;
+ iov[5].iov_len = 4;
+
+ len2=15+16+18+iov[2].iov_len+iov[4].iov_len+4;
+
+
+ len = 0;
+
+
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ len = sock_sendmsg(Request->sock,&msg,len2);
+ set_fs(oldfs);
+
+
+ return;
+}
+#else
+void SendHTTPHeader(struct http_request *Request)
+{
+ struct msghdr msg;
+ mm_segment_t oldfs;
+ struct iovec iov[9];
+ int len,len2;
+ __kernel_size_t slen;
+
+ EnterFunction("SendHTTPHeader");
+
+ msg.msg_name = 0;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &(iov[0]);
+ msg.msg_iovlen = 9;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0; /* Synchronous for now */
+
+ iov[0].iov_base = HeaderPart1;
+ iov[0].iov_len = 45;
+ iov[1].iov_base = CurrentTime;
+ iov[1].iov_len = 29;
+ iov[2].iov_base = HeaderPart3;
+ iov[2].iov_len = 16;
+
+ iov[3].iov_base = Request->MimeType;
+ iov[3].iov_len = Request->MimeLength;
+
+ iov[4].iov_base = HeaderPart5;
+ iov[4].iov_len = 17;
+ iov[5].iov_base = &(Request->TimeS[0]);
+ iov[5].iov_len = 29;
+ iov[6].iov_base = HeaderPart7;
+ iov[6].iov_len = 18;
+ iov[7].iov_base = &(Request->LengthS[0]);
+ slen = strlen(Request->LengthS);
+ iov[7].iov_len = slen;
+ iov[8].iov_base = HeaderPart9;
+ iov[8].iov_len = 4;
+
+ len2=45+2*29+16+17+18+slen+4+iov[3].iov_len;
+
+ len = 0;
+
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ len = sock_sendmsg(Request->sock,&msg,len2);
+ set_fs(oldfs);
+ LeaveFunction("SendHTTPHeader");
+
+
+ return;
+}
+#endif
+
+
+
+/*
+
+Parse a HTTP-header. Be careful for buffer-overflows here, this is the most important
+place for this, since the remote-user controls the data.
+
+*/
+void ParseHeader(char *Buffer,const int length, struct http_request *Head)
+{
+ char *Endval,*EOL,*tmp;
+
+ EnterFunction("ParseHeader");
+ Endval = Buffer + length;
+
+ /* We want to parse only the first header if multiple headers are present */
+ tmp = strstr(Buffer,"\r\n\r\n");
+ if (tmp!=NULL)
+ Endval = tmp;
+
+
+ while (Buffer<Endval)
+ {
+ if (isspace(Buffer[0]))
+ {
+ Buffer++;
+ continue;
+ }
+
+
+ EOL=strchr(Buffer,'\n');
+
+ if (EOL==NULL) EOL=Endval;
+
+ if (EOL-Buffer<4)
+ {
+ Buffer++;
+ continue;
+ }
+
+ if (strncmp("GET ",Buffer,4)==0)
+ {
+ int PrefixLen;
+ Buffer+=4;
+
+ tmp=strchr(Buffer,' ');
+ if (tmp==0)
+ {
+ tmp=EOL-1;
+ Head->HTTPVER = 9;
+ } else
+ Head->HTTPVER = 10;
+
+ if (tmp>Endval) continue;
+
+ strncpy(Head->FileName,sysctl_khttpd_docroot,sizeof(Head->FileName));
+ PrefixLen = strlen(sysctl_khttpd_docroot);
+ Head->FileNameLength = min_t(unsigned int, 255, tmp - Buffer + PrefixLen);
+
+ strncat(Head->FileName,Buffer,min_t(unsigned int, 255 - PrefixLen, tmp - Buffer));
+
+ Buffer=EOL+1;
+#ifdef BENCHMARK
+ break;
+#endif
+ continue;
+ }
+#ifndef BENCHMARK
+ if (strncmp("If-Modified-Since: ",Buffer,19)==0)
+ {
+ Buffer+=19;
+
+ strncpy(Head->IMS,Buffer,min_t(unsigned int, 127,EOL-Buffer-1));
+
+ Buffer=EOL+1;
+ continue;
+ }
+
+ if (strncmp("User-Agent: ",Buffer,12)==0)
+ {
+ Buffer+=12;
+
+ strncpy(Head->Agent,Buffer,min_t(unsigned int, 127,EOL-Buffer-1));
+
+ Buffer=EOL+1;
+ continue;
+ }
+
+
+ if (strncmp("Host: ",Buffer,6)==0)
+ {
+ Buffer+=6;
+
+ strncpy(Head->Host,Buffer,min_t(unsigned int, 127,EOL-Buffer-1));
+
+ Buffer=EOL+1;
+ continue;
+ }
+#endif
+ Buffer = EOL+1; /* Skip line */
+ }
+ LeaveFunction("ParseHeader");
+}
diff --git a/uClinux-2.4.31-uc0/net/khttpd/rfc_time.c b/uClinux-2.4.31-uc0/net/khttpd/rfc_time.c
new file mode 100644
index 0000000..4fd2678
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/rfc_time.c
@@ -0,0 +1,227 @@
+/*
+
+Functions related to time:
+
+1) rfc (string) time to unix-time
+2) unix-time to rfc (string) time
+3) current time to rfc (string) time for the "Date:" header
+
+*/
+
+/****************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ****************************************************************/
+
+#include <linux/time.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/ctype.h>
+
+
+#include "times.h"
+#include "prototypes.h"
+static char *dayName[7] = {
+ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
+};
+
+static char *monthName[12] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+
+char CurrentTime[64];
+int CurrentTime_i;
+
+
+static char itoa_h[60]={'0','0','0','0','0','0','0','0','0','0',
+ '1','1','1','1','1','1','1','1','1','1',
+ '2','2','2','2','2','2','2','2','2','2',
+ '3','3','3','3','3','3','3','3','3','3',
+ '4','4','4','4','4','4','4','4','4','4',
+ '5','5','5','5','5','5','5','5','5','5'};
+
+static char itoa_l[60]={'0','1','2','3','4','5','6','7','8','9',
+ '0','1','2','3','4','5','6','7','8','9',
+ '0','1','2','3','4','5','6','7','8','9',
+ '0','1','2','3','4','5','6','7','8','9',
+ '0','1','2','3','4','5','6','7','8','9',
+ '0','1','2','3','4','5','6','7','8','9'};
+void time_Unix2RFC(const time_t Zulu,char *Buffer)
+{
+ int Y=0,M=0,D=0;
+ int H=0,Min=0,S=0,WD=0;
+ int I,I2;
+ time_t rest;
+
+
+
+ I=0;
+ while (I<KHTTPD_NUMYEARS)
+ {
+ if (TimeDays[I][0]>Zulu)
+ break;
+ I++;
+ }
+
+ Y=--I;
+ if (I<0)
+ {
+ Y=0;
+ goto BuildYear;
+ }
+ I2=0;
+ while (I2<=12)
+ {
+ if (TimeDays[I][I2]>Zulu)
+ break;
+ I2++;
+ }
+
+ M=I2-1;
+
+ rest=Zulu - TimeDays[Y][M];
+ WD=WeekDays[Y][M];
+ D=rest/86400;
+ rest=rest%86400;
+ WD+=D;
+ WD=WD%7;
+ H=rest/3600;
+ rest=rest%3600;
+ Min=rest/60;
+ rest=rest%60;
+ S=rest;
+
+BuildYear:
+ Y+=KHTTPD_YEAROFFSET;
+
+
+ /* Format: Day, 01 Mon 1999 01:01:01 GMT */
+
+/*
+ We want to do
+
+ sprintf( Buffer, "%s, %02i %s %04i %02i:%02i:%02i GMT",
+ dayName[ WD ], D+1, monthName[ M ], Y,
+ H, Min, S
+ );
+
+ but this is very expensive. Since the string is fixed length,
+ it is filled manually.
+*/
+ Buffer[0]=dayName[WD][0];
+ Buffer[1]=dayName[WD][1];
+ Buffer[2]=dayName[WD][2];
+ Buffer[3]=',';
+ Buffer[4]=' ';
+ Buffer[5]=itoa_h[D+1];
+ Buffer[6]=itoa_l[D+1];
+ Buffer[7]=' ';
+ Buffer[8]=monthName[M][0];
+ Buffer[9]=monthName[M][1];
+ Buffer[10]=monthName[M][2];
+ Buffer[11]=' ';
+ Buffer[12]=itoa_l[Y/1000];
+ Buffer[13]=itoa_l[(Y/100)%10];
+ Buffer[14]=itoa_l[(Y/10)%10];
+ Buffer[15]=itoa_l[Y%10];
+ Buffer[16]=' ';
+ Buffer[17]=itoa_h[H];
+ Buffer[18]=itoa_l[H];
+ Buffer[19]=':';
+ Buffer[20]=itoa_h[Min];
+ Buffer[21]=itoa_l[Min];
+ Buffer[22]=':';
+ Buffer[23]=itoa_h[S];
+ Buffer[24]=itoa_l[S];
+ Buffer[25]=' ';
+ Buffer[26]='G';
+ Buffer[27]='M';
+ Buffer[28]='T';
+ Buffer[29]=0;
+
+
+
+
+}
+
+void UpdateCurrentDate(void)
+{
+ struct timeval tv;
+
+ do_gettimeofday(&tv);
+ if (CurrentTime_i!=tv.tv_sec)
+ time_Unix2RFC(tv.tv_sec,CurrentTime);
+
+ CurrentTime_i = tv.tv_sec;
+}
+
+static int MonthHash[32] = {0,0,7,0,0,0,0,0,0,0,0,3,0,0,0,2,6,0,5,0,9,8,4,0,0,11,1,10,0,0,0,0};
+
+#define is_digit(c) ((c) >= '0' && (c) <= '9')
+
+__inline static int skip_atoi(char **s)
+{
+ int i=0;
+
+ while (is_digit(**s))
+ i = i*10 + *((*s)++) - '0';
+ return i;
+}
+
+time_t mimeTime_to_UnixTime(char *Q)
+{
+ int Y,M,D,H,Min,S;
+ unsigned int Hash;
+ time_t Temp;
+ char *s,**s2;
+
+ s=Q;
+ s2=&s;
+
+ if (strlen(s)<30) return 0;
+ if (s[3]!=',') return 0;
+ if (s[19]!=':') return 0;
+
+ s+=5; /* Skip day of week */
+ D = skip_atoi(s2); /* Day of month */
+ s++;
+ Hash = (unsigned char)s[0]+(unsigned char)s[2];
+ Hash = (Hash<<1) + (unsigned char)s[1];
+ Hash = (Hash&63)>>1;
+ M = MonthHash[Hash];
+ s+=4;
+ Y = skip_atoi(s2); /* Year */
+ s++;
+ H = skip_atoi(s2); /* Hour */
+ s++;
+ Min = skip_atoi(s2); /* Minutes */
+ s++;
+ S = skip_atoi(s2); /* Seconds */
+ s++;
+ if ((s[0]!='G')||(s[1]!='M')||(s[2]!='T'))
+ {
+ return 0; /* No GMT */
+ }
+
+ if (Y<KHTTPD_YEAROFFSET) Y = KHTTPD_YEAROFFSET;
+ if (Y>KHTTPD_YEAROFFSET+9) Y = KHTTPD_YEAROFFSET+9;
+
+ Temp = TimeDays[Y-KHTTPD_YEAROFFSET][M];
+ Temp += D*86400+H*3600+Min*60+S;
+
+ return Temp;
+}
diff --git a/uClinux-2.4.31-uc0/net/khttpd/security.c b/uClinux-2.4.31-uc0/net/khttpd/security.c
new file mode 100644
index 0000000..9bda959
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/security.c
@@ -0,0 +1,254 @@
+/*
+
+kHTTPd -- the next generation
+
+Permissions/Security functions
+
+*/
+
+/****************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ****************************************************************/
+
+
+#include <linux/kernel.h>
+
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/net.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/smp_lock.h>
+#include <linux/un.h>
+#include <linux/unistd.h>
+
+#include <net/ip.h>
+#include <net/sock.h>
+#include <net/tcp.h>
+
+#include <asm/atomic.h>
+#include <asm/semaphore.h>
+#include <asm/processor.h>
+#include <asm/uaccess.h>
+
+#include <linux/file.h>
+
+#include "sysctl.h"
+#include "security.h"
+#include "prototypes.h"
+
+/*
+
+The basic security function answers "Userspace" when any one of the following
+conditions is met:
+
+1) The filename contains a "?" (this is before % decoding, all others are
+ after % decoding)
+2) The filename doesn't start with a "/"
+3) The file does not exist
+4) The file does not have enough permissions
+ (sysctl-configurable, default = worldreadble)
+5) The file has any of the "forbidden" permissions
+ (sysctl-configurable, default = execute, directory and sticky)
+6) The filename contains a string as defined in the "Dynamic" list.
+
+*/
+
+
+/* Prototypes */
+
+static void DecodeHexChars(char *URL);
+static struct DynamicString *DynamicList=NULL;
+
+
+
+/*
+
+The function "OpenFileForSecurity" returns either the "struct file" pointer
+of the file, or NULL. NULL means "let userspace handle it".
+
+*/
+struct file *OpenFileForSecurity(char *Filename)
+{
+ struct file *filp = NULL;
+ struct DynamicString *List;
+ umode_t permission;
+
+ EnterFunction("OpenFileForSecurity");
+ if (Filename==NULL)
+ goto out_error;
+
+ if (strlen(Filename)>=256 )
+ goto out_error; /* Sanity check */
+
+ /* Rule no. 1 -- No "?" characters */
+#ifndef BENCHMARK
+ if (strchr(Filename,'?')!=NULL)
+ goto out_error;
+
+ /* Intermediate step: decode all %hex sequences */
+
+ DecodeHexChars(Filename);
+
+ /* Rule no. 2 -- Must start with a "/" */
+
+ if (Filename[0]!='/')
+ goto out_error;
+
+#endif
+ /* Rule no. 3 -- Does the file exist ? */
+
+ filp = filp_open(Filename, O_RDONLY, 0);
+
+ if (IS_ERR(filp))
+ goto out_error;
+
+#ifndef BENCHMARK
+ permission = filp->f_dentry->d_inode->i_mode;
+
+ /* Rule no. 4 : must have enough permissions */
+
+ if ((permission & sysctl_khttpd_permreq)==0)
+ goto out_error_put;
+
+ /* Rule no. 5 : cannot have "forbidden" permission */
+
+ if ((permission & sysctl_khttpd_permforbid)!=0)
+ goto out_error_put;
+
+ /* Rule no. 6 : No string in DynamicList can be a
+ substring of the filename */
+
+ List = DynamicList;
+ while (List!=NULL)
+ {
+ if (strstr(Filename,List->value)!=NULL)
+ goto out_error_put;
+
+ List = List->Next;
+ }
+
+#endif
+ LeaveFunction("OpenFileForSecurity - success");
+out:
+ return filp;
+
+out_error_put:
+ fput(filp);
+out_error:
+ filp=NULL;
+ LeaveFunction("OpenFileForSecurity - fail");
+ goto out;
+}
+
+/*
+
+DecodeHexChars does the actual %HEX decoding, in place.
+In place is possible because strings only get shorter by this.
+
+*/
+static void DecodeHexChars(char *URL)
+{
+ char *Source,*Dest;
+ int val,val2;
+
+ EnterFunction("DecodeHexChars");
+
+ Source = strchr(URL,'%');
+
+ if (Source==NULL)
+ return;
+
+ Dest = Source;
+
+ while (*Source!=0)
+ {
+ if (*Source=='%')
+ {
+ Source++;
+ val = *Source;
+
+ if (val>'Z') val-=0x20;
+ val = val - '0';
+ if (val<0) val=0;
+ if (val>9) val-=7;
+ if (val>15) val=15;
+
+ Source++;
+
+ val2 = *Source;
+
+ if (val2>'Z') val2-=0x20;
+ val2 = val2 - '0';
+ if (val2<0) val2=0;
+ if (val2>9) val2-=7;
+ if (val2>15) val2=15;
+
+ *Dest=val*16+val2;
+ } else *Dest = *Source;
+ Dest++;
+ Source++;
+ }
+ *Dest=0;
+
+ LeaveFunction("DecodeHexChars");
+}
+
+
+void AddDynamicString(const char *String)
+{
+ struct DynamicString *Temp;
+
+ EnterFunction("AddDynamicString");
+
+ Temp = (struct DynamicString*)kmalloc(sizeof(struct DynamicString),(int)GFP_KERNEL);
+
+ if (Temp==NULL)
+ return;
+
+ memset(Temp->value,0,sizeof(Temp->value));
+ strncpy(Temp->value,String,sizeof(Temp->value)-1);
+
+ Temp->Next = DynamicList;
+ DynamicList = Temp;
+
+ LeaveFunction("AddDynamicString");
+}
+
+void GetSecureString(char *String)
+{
+ struct DynamicString *Temp;
+ int max;
+
+ EnterFunction("GetSecureString");
+
+ *String = 0;
+
+ memset(String,0,255);
+
+ strncpy(String,"Dynamic strings are : -",255);
+ Temp = DynamicList;
+ while (Temp!=NULL)
+ {
+ max=253 - strlen(String) - strlen(Temp->value);
+ strncat(String,Temp->value,max);
+ max=253 - strlen(String) - 3;
+ strncat(String,"- -",max);
+ Temp = Temp->Next;
+ }
+
+ LeaveFunction("GetSecureString");
+}
diff --git a/uClinux-2.4.31-uc0/net/khttpd/security.h b/uClinux-2.4.31-uc0/net/khttpd/security.h
new file mode 100644
index 0000000..0876da2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/security.h
@@ -0,0 +1,12 @@
+#ifndef _INCLUDE_GUARD_SECURITY_H
+#define _INCLUDE_GUARD_SECURITY_H
+
+struct DynamicString;
+
+struct DynamicString
+{
+ struct DynamicString* Next;
+ char value[32-sizeof(void*)]; /* fill 1 cache-line */
+};
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/khttpd/sockets.c b/uClinux-2.4.31-uc0/net/khttpd/sockets.c
new file mode 100644
index 0000000..3c2b612
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/sockets.c
@@ -0,0 +1,117 @@
+/* $USAGI: sockets.c,v 1.5 2002/08/04 02:57:46 yoshfuji Exp $ */
+
+/*
+
+kHTTPd -- the next generation
+
+Basic socket functions
+
+*/
+/****************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ****************************************************************/
+
+#include "prototypes.h"
+#include <linux/kernel.h>
+#include <linux/net.h>
+#include <linux/version.h>
+#include <linux/smp_lock.h>
+#include <net/sock.h>
+
+
+/*
+
+MainSocket is shared by all threads, therefore it has to be
+a global variable.
+
+*/
+struct socket *MainSocket=NULL;
+
+
+int StartListening(const int Port)
+{
+ struct socket *sock;
+#ifdef CONFIG_KHTTPD_IPV6
+ struct sockaddr_in6 sin;
+#else
+ struct sockaddr_in sin;
+#endif
+ int error;
+
+ EnterFunction("StartListening");
+
+ /* First create a socket */
+
+#ifdef CONFIG_KHTTPD_IPV6
+ error = sock_create(PF_INET6,SOCK_STREAM,IPPROTO_TCP,&sock);
+#else
+ error = sock_create(PF_INET,SOCK_STREAM,IPPROTO_TCP,&sock);
+#endif
+ if (error<0)
+ (void)printk(KERN_ERR "Error during creation of socket; terminating\n");
+
+
+
+ /* Now bind the socket */
+
+#ifdef CONFIG_KHTTPD_IPV6
+ memset(&sin, 0, sizeof(sin));
+ sin.sin6_family = AF_INET6;
+ sin.sin6_port = htons((unsigned short)Port);
+#else
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = INADDR_ANY;
+ sin.sin_port = htons((unsigned short)Port);
+#endif
+
+ error = sock->ops->bind(sock,(struct sockaddr*)&sin,sizeof(sin));
+ if (error<0)
+ {
+ (void)printk(KERN_ERR "kHTTPd: Error binding socket. This means that some other \n");
+ (void)printk(KERN_ERR " daemon is (or was a short time ago) using port %i.\n",Port);
+ return 0;
+ }
+
+ /* Grrr... setsockopt() does this. */
+ sock->sk->reuse = 1;
+
+ /* Now, start listening on the socket */
+
+ /* I have no idea what a sane backlog-value is. 48 works so far. */
+
+ error=sock->ops->listen(sock,48);
+ if (error!=0)
+ (void)printk(KERN_ERR "kHTTPd: Error listening on socket \n");
+
+ MainSocket = sock;
+
+ LeaveFunction("StartListening");
+ return 1;
+}
+
+void StopListening(void)
+{
+ struct socket *sock;
+
+ EnterFunction("StopListening");
+ if (MainSocket==NULL) return;
+
+ sock=MainSocket;
+ MainSocket = NULL;
+ sock_release(sock);
+
+ LeaveFunction("StopListening");
+}
diff --git a/uClinux-2.4.31-uc0/net/khttpd/structure.h b/uClinux-2.4.31-uc0/net/khttpd/structure.h
new file mode 100644
index 0000000..5f6f2a6
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/structure.h
@@ -0,0 +1,68 @@
+#ifndef _INCLUDE_GUARD_STRUCTURE_H_
+#define _INCLUDE_GUARD_STRUCTURE_H_
+
+#include <linux/time.h>
+#include <linux/wait.h>
+
+
+struct http_request;
+
+struct http_request
+{
+ /* Linked list */
+ struct http_request *Next;
+
+ /* Network and File data */
+ struct socket *sock;
+ struct file *filp;
+
+ /* Raw data about the file */
+
+ int FileLength; /* File length in bytes */
+ int Time; /* mtime of the file, unix format */
+ int BytesSent; /* The number of bytes already sent */
+ int IsForUserspace; /* 1 means let Userspace handle this one */
+
+ /* Wait queue */
+
+ wait_queue_t sleep; /* For putting in the socket's waitqueue */
+
+ /* HTTP request information */
+ char FileName[256]; /* The requested filename */
+ int FileNameLength; /* The length of the string representing the filename */
+ char Agent[128]; /* The agent-string of the remote browser */
+ char IMS[128]; /* If-modified-since time, rfc string format */
+ char Host[128]; /* Value given by the Host: header */
+ int HTTPVER; /* HTTP-version; 9 for 0.9, 10 for 1.0 and above */
+
+
+ /* Derived date from the above fields */
+ int IMS_Time; /* if-modified-since time, unix format */
+ char TimeS[64]; /* File mtime, rfc string representation */
+ char LengthS[14]; /* File length, string representation */
+ char *MimeType; /* Pointer to a string with the mime-type
+ based on the filename */
+ __kernel_size_t MimeLength; /* The length of this string */
+
+};
+
+
+
+/*
+
+struct khttpd_threadinfo represents the four queues that 1 thread has to deal with.
+It is padded to occupy 1 (Intel) cache-line, to avoid "cacheline-pingpong".
+
+*/
+struct khttpd_threadinfo
+{
+ struct http_request* WaitForHeaderQueue;
+ struct http_request* DataSendingQueue;
+ struct http_request* LoggingQueue;
+ struct http_request* UserspaceQueue;
+ char dummy[16]; /* Padding for cache-lines */
+};
+
+
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/khttpd/sysctl.c b/uClinux-2.4.31-uc0/net/khttpd/sysctl.c
new file mode 100644
index 0000000..c1f1cc3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/sysctl.c
@@ -0,0 +1,341 @@
+/*
+
+kHTTPd -- the next generation
+
+Sysctl interface
+
+*/
+/****************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ****************************************************************/
+
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/net.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/smp_lock.h>
+#include <linux/sysctl.h>
+#include <linux/un.h>
+#include <linux/unistd.h>
+
+#include <net/ip.h>
+#include <net/sock.h>
+#include <net/tcp.h>
+
+#include <asm/atomic.h>
+#include <asm/semaphore.h>
+#include <asm/processor.h>
+#include <asm/uaccess.h>
+
+#include <linux/file.h>
+#include "prototypes.h"
+
+
+
+char sysctl_khttpd_docroot[200] = "/var/www";
+int sysctl_khttpd_stop = 0;
+int sysctl_khttpd_start = 0;
+int sysctl_khttpd_unload = 0;
+int sysctl_khttpd_clientport = 80;
+int sysctl_khttpd_permreq = S_IROTH; /* "other" read-access is required by default*/
+int sysctl_khttpd_permforbid = S_IFDIR | S_ISVTX | S_IXOTH | S_IXGRP | S_IXUSR;
+ /* forbidden is execute, directory and sticky*/
+int sysctl_khttpd_logging = 0;
+int sysctl_khttpd_serverport= 8080;
+
+char sysctl_khttpd_dynamicstring[200];
+int sysctl_khttpd_sloppymime= 0;
+int sysctl_khttpd_threads = 2;
+int sysctl_khttpd_maxconnect = 1000;
+
+atomic_t khttpd_stopCount;
+
+static struct ctl_table_header *khttpd_table_header;
+
+static int sysctl_SecureString(ctl_table *table, int *name, int nlen,
+ void *oldval, size_t *oldlenp,
+ void *newval, size_t newlen, void **context);
+static int proc_dosecurestring(ctl_table *table, int write, struct file *filp,
+ void *buffer, size_t *lenp);
+static int khttpd_stop_wrap_proc_dointvec(ctl_table *table, int write, struct file *filp,
+ void *buffer, size_t *lenp);
+
+
+static ctl_table khttpd_table[] = {
+ { NET_KHTTPD_DOCROOT,
+ "documentroot",
+ &sysctl_khttpd_docroot,
+ sizeof(sysctl_khttpd_docroot),
+ 0644,
+ NULL,
+ proc_dostring,
+ &sysctl_string,
+ NULL,
+ NULL,
+ NULL
+ },
+ { NET_KHTTPD_STOP,
+ "stop",
+ &sysctl_khttpd_stop,
+ sizeof(int),
+ 0644,
+ NULL,
+ khttpd_stop_wrap_proc_dointvec,
+ &sysctl_intvec,
+ NULL,
+ NULL,
+ NULL
+ },
+ { NET_KHTTPD_START,
+ "start",
+ &sysctl_khttpd_start,
+ sizeof(int),
+ 0644,
+ NULL,
+ proc_dointvec,
+ &sysctl_intvec,
+ NULL,
+ NULL,
+ NULL
+ },
+ { NET_KHTTPD_UNLOAD,
+ "unload",
+ &sysctl_khttpd_unload,
+ sizeof(int),
+ 0644,
+ NULL,
+ proc_dointvec,
+ &sysctl_intvec,
+ NULL,
+ NULL,
+ NULL
+ },
+ { NET_KHTTPD_THREADS,
+ "threads",
+ &sysctl_khttpd_threads,
+ sizeof(int),
+ 0644,
+ NULL,
+ proc_dointvec,
+ &sysctl_intvec,
+ NULL,
+ NULL,
+ NULL
+ },
+ { NET_KHTTPD_MAXCONNECT,
+ "maxconnect",
+ &sysctl_khttpd_maxconnect,
+ sizeof(int),
+ 0644,
+ NULL,
+ proc_dointvec,
+ &sysctl_intvec,
+ NULL,
+ NULL,
+ NULL
+ },
+ { NET_KHTTPD_SLOPPYMIME,
+ "sloppymime",
+ &sysctl_khttpd_sloppymime,
+ sizeof(int),
+ 0644,
+ NULL,
+ proc_dointvec,
+ &sysctl_intvec,
+ NULL,
+ NULL,
+ NULL
+ },
+ { NET_KHTTPD_CLIENTPORT,
+ "clientport",
+ &sysctl_khttpd_clientport,
+ sizeof(int),
+ 0644,
+ NULL,
+ proc_dointvec,
+ &sysctl_intvec,
+ NULL,
+ NULL,
+ NULL
+ },
+ { NET_KHTTPD_PERMREQ,
+ "perm_required",
+ &sysctl_khttpd_permreq,
+ sizeof(int),
+ 0644,
+ NULL,
+ proc_dointvec,
+ &sysctl_intvec,
+ NULL,
+ NULL,
+ NULL
+ },
+ { NET_KHTTPD_PERMFORBID,
+ "perm_forbid",
+ &sysctl_khttpd_permforbid,
+ sizeof(int),
+ 0644,
+ NULL,
+ proc_dointvec,
+ &sysctl_intvec,
+ NULL,
+ NULL,
+ NULL
+ },
+ { NET_KHTTPD_LOGGING,
+ "logging",
+ &sysctl_khttpd_logging,
+ sizeof(int),
+ 0644,
+ NULL,
+ proc_dointvec,
+ &sysctl_intvec,
+ NULL,
+ NULL,
+ NULL
+ },
+ { NET_KHTTPD_SERVERPORT,
+ "serverport",
+ &sysctl_khttpd_serverport,
+ sizeof(int),
+ 0644,
+ NULL,
+ proc_dointvec,
+ &sysctl_intvec,
+ NULL,
+ NULL,
+ NULL
+ },
+ { NET_KHTTPD_DYNAMICSTRING,
+ "dynamic",
+ &sysctl_khttpd_dynamicstring,
+ sizeof(sysctl_khttpd_dynamicstring),
+ 0644,
+ NULL,
+ proc_dosecurestring,
+ &sysctl_SecureString,
+ NULL,
+ NULL,
+ NULL
+ },
+ {0,0,0,0,0,0,0,0,0,0,0} };
+
+
+static ctl_table khttpd_dir_table[] = {
+ {NET_KHTTPD, "khttpd", NULL, 0, 0555, khttpd_table,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0}
+};
+
+static ctl_table khttpd_root_table[] = {
+ {CTL_NET, "net", NULL, 0, 0555, khttpd_dir_table,0,0,0,0,0},
+ {0,0,0,0,0,0,0,0,0,0,0}
+};
+
+
+void StartSysctl(void)
+{
+ khttpd_table_header = register_sysctl_table(khttpd_root_table,1);
+}
+
+
+void EndSysctl(void)
+{
+ unregister_sysctl_table(khttpd_table_header);
+}
+
+static int proc_dosecurestring(ctl_table *table, int write, struct file *filp,
+ void *buffer, size_t *lenp)
+{
+ size_t len;
+ char *p, c=0;
+ char String[256];
+
+ if ((table->data==0) || (table->maxlen==0) || (*lenp==0) ||
+ ((filp->f_pos!=0) && (write==0))) {
+ *lenp = 0;
+ return 0;
+ }
+
+ if (write!=0) {
+ len = 0;
+ p = buffer;
+ while (len < *lenp) {
+ if(get_user(c, p++))
+ return -EFAULT;
+ if (c == 0 || c == '\n')
+ break;
+ len++;
+ }
+ if (len >= table->maxlen)
+ len = table->maxlen-1;
+ if(copy_from_user(String, buffer,(unsigned long)len))
+ return -EFAULT;
+ ((char *) String)[len] = 0;
+ filp->f_pos += *lenp;
+ AddDynamicString(String);
+ } else {
+ GetSecureString(String);
+ len = strlen(String);
+ if (len > table->maxlen)
+ len = table->maxlen;
+ if (len > *lenp)
+ len = *lenp;
+ if (len!=0)
+ if(copy_to_user(buffer, String,(unsigned long)len))
+ return -EFAULT;
+ if (len < *lenp) {
+ if(put_user('\n', ((char *) buffer) + len))
+ return -EFAULT;
+ len++;
+ }
+ *lenp = len;
+ filp->f_pos += len;
+ }
+ return 0;
+}
+
+/* A wrapper around proc_dointvec that computes
+ * khttpd_stopCount = # of times sysctl_khttpd_stop has gone true
+ * Sensing sysctl_khttpd_stop in other threads is racy;
+ * sensing khttpd_stopCount in other threads is not.
+ */
+static int khttpd_stop_wrap_proc_dointvec(ctl_table *table, int write, struct file *filp,
+ void *buffer, size_t *lenp)
+{
+ int rv;
+ int oldstop = sysctl_khttpd_stop;
+ rv = proc_dointvec(table, write, filp, buffer, lenp);
+ if (sysctl_khttpd_stop && !oldstop)
+ atomic_inc(&khttpd_stopCount);
+
+ return rv;
+}
+
+
+static int sysctl_SecureString (/*@unused@*/ctl_table *table,
+ /*@unused@*/int *name,
+ /*@unused@*/int nlen,
+ /*@unused@*/void *oldval,
+ /*@unused@*/size_t *oldlenp,
+ /*@unused@*/void *newval,
+ /*@unused@*/size_t newlen,
+ /*@unused@*/void **context)
+{
+ return -ENOSYS;
+}
diff --git a/uClinux-2.4.31-uc0/net/khttpd/sysctl.h b/uClinux-2.4.31-uc0/net/khttpd/sysctl.h
new file mode 100644
index 0000000..eb2ec1e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/sysctl.h
@@ -0,0 +1,20 @@
+#ifndef _KHTTPD_INCLUDE_GUARD_SYSCTL_H
+#define _KHTTPD_INCLUDE_GUARD_SYSCTL_H
+
+extern char sysctl_khttpd_docroot[200];
+extern int sysctl_khttpd_stop;
+extern int sysctl_khttpd_start;
+extern int sysctl_khttpd_unload;
+extern int sysctl_khttpd_clientport;
+extern int sysctl_khttpd_permreq;
+extern int sysctl_khttpd_permforbid;
+extern int sysctl_khttpd_logging;
+extern int sysctl_khttpd_serverport;
+extern int sysctl_khttpd_sloppymime;
+extern int sysctl_khttpd_threads;
+extern int sysctl_khttpd_maxconnect;
+
+/* incremented each time sysctl_khttpd_stop goes nonzero */
+extern atomic_t khttpd_stopCount;
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/khttpd/userspace.c b/uClinux-2.4.31-uc0/net/khttpd/userspace.c
new file mode 100644
index 0000000..9c78439
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/userspace.c
@@ -0,0 +1,243 @@
+/*
+
+kHTTPd -- the next generation
+
+Pass connections to userspace-daemons
+
+*/
+/****************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ****************************************************************/
+
+/*
+
+Purpose:
+
+Userspace() hands all requests in the queue to the userspace-daemon, if
+such beast exists.
+
+Return value:
+ The number of requests that changed status
+*/
+#include <linux/kernel.h>
+
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/net.h>
+#include <linux/sched.h>
+#include <linux/skbuff.h>
+#include <linux/smp_lock.h>
+#include <linux/un.h>
+#include <linux/unistd.h>
+#include <linux/wait.h>
+
+#include <net/ip.h>
+#include <net/sock.h>
+#include <net/tcp.h>
+
+#include <asm/atomic.h>
+#include <asm/semaphore.h>
+#include <asm/processor.h>
+#include <asm/uaccess.h>
+
+#include <linux/file.h>
+
+
+#include "structure.h"
+#include "prototypes.h"
+#include "sysctl.h"
+
+/* prototypes of local, static functions */
+static int AddSocketToAcceptQueue(struct socket *sock,const int Port);
+
+
+int Userspace(const int CPUNR)
+{
+ struct http_request *CurrentRequest,**Prev,*Next;
+
+ EnterFunction("Userspace");
+
+
+
+
+ CurrentRequest = threadinfo[CPUNR].UserspaceQueue;
+ Prev = &(threadinfo[CPUNR].UserspaceQueue);
+
+ while (CurrentRequest!=NULL)
+ {
+
+ /* Clean-up the waitqueue of the socket.. Bad things happen if
+ this is forgotten. */
+ if (CurrentRequest->sock!=NULL)
+ {
+ if ((CurrentRequest->sock!=NULL)&&(CurrentRequest->sock->sk!=NULL))
+ {
+ remove_wait_queue(CurrentRequest->sock->sk->sleep,&(CurrentRequest->sleep));
+ }
+ }
+
+
+ if (AddSocketToAcceptQueue(CurrentRequest->sock,sysctl_khttpd_clientport)>=0)
+ {
+
+ (*Prev) = CurrentRequest->Next;
+ Next = CurrentRequest->Next;
+
+
+ sock_release(CurrentRequest->sock);
+ CurrentRequest->sock = NULL; /* We no longer own it */
+
+ CleanUpRequest(CurrentRequest);
+
+ CurrentRequest = Next;
+ continue;
+
+ }
+ else /* No userspace-daemon present, or other problems with it */
+ {
+ (*Prev) = CurrentRequest->Next;
+ Next = CurrentRequest->Next;
+
+ Send403(CurrentRequest->sock); /* Sorry, no go... */
+
+ CleanUpRequest(CurrentRequest);
+
+ CurrentRequest = Next;
+ continue;
+
+ }
+
+
+ Prev = &(CurrentRequest->Next);
+ CurrentRequest = CurrentRequest->Next;
+ }
+
+ LeaveFunction("Userspace");
+ return 0;
+}
+
+void StopUserspace(const int CPUNR)
+{
+ struct http_request *CurrentRequest,*Next;
+
+ EnterFunction("StopUserspace");
+ CurrentRequest = threadinfo[CPUNR].UserspaceQueue;
+
+ while (CurrentRequest!=NULL)
+ {
+ Next= CurrentRequest->Next;
+ CleanUpRequest(CurrentRequest);
+ CurrentRequest=Next;
+ }
+ threadinfo[CPUNR].UserspaceQueue = NULL;
+
+ LeaveFunction("StopUserspace");
+}
+
+
+/*
+ "FindUserspace" returns the struct sock of the userspace-daemon, so that we can
+ "drop" our request in the accept-queue
+*/
+
+static struct sock *FindUserspace(const unsigned short Port)
+{
+ struct sock *sk;
+
+ EnterFunction("FindUserspace");
+
+ local_bh_disable();
+ sk = tcp_v4_lookup_listener(INADDR_ANY,Port,0);
+ local_bh_enable();
+ return sk;
+}
+
+static void dummy_destructor(struct open_request *req)
+{
+}
+
+static struct or_calltable Dummy =
+{
+ 0,
+ NULL,
+ NULL,
+ &dummy_destructor,
+ NULL
+};
+
+static int AddSocketToAcceptQueue(struct socket *sock,const int Port)
+{
+ struct open_request *req;
+ struct sock *sk, *nsk;
+
+ EnterFunction("AddSocketToAcceptQueue");
+
+
+ sk = FindUserspace((unsigned short)Port);
+
+ if (sk==NULL) /* No userspace-daemon found */
+ {
+ return -1;
+ }
+
+ lock_sock(sk);
+
+ if (sk->state != TCP_LISTEN || tcp_acceptq_is_full(sk))
+ {
+ release_sock(sk);
+ sock_put(sk);
+ return -1;
+ }
+
+ req = tcp_openreq_alloc();
+
+ if (req==NULL)
+ {
+ release_sock(sk);
+ sock_put(sk);
+ return -1;
+ }
+
+ nsk = sock->sk;
+ sock->sk = NULL;
+ sock->state = SS_UNCONNECTED;
+
+ req->class = &Dummy;
+ write_lock_bh(&nsk->callback_lock);
+ nsk->socket = NULL;
+ nsk->sleep = NULL;
+ write_unlock_bh(&nsk->callback_lock);
+
+ tcp_acceptq_queue(sk, req, nsk);
+
+ sk->data_ready(sk, 0);
+
+ release_sock(sk);
+ sock_put(sk);
+
+ LeaveFunction("AddSocketToAcceptQueue");
+
+ return +1;
+
+
+
+}
+
+void InitUserspace(const int CPUNR)
+{
+}
+
+
diff --git a/uClinux-2.4.31-uc0/net/khttpd/waitheaders.c b/uClinux-2.4.31-uc0/net/khttpd/waitheaders.c
new file mode 100644
index 0000000..9bef9f2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/khttpd/waitheaders.c
@@ -0,0 +1,302 @@
+/*
+
+kHTTPd -- the next generation
+
+Wait for headers on the accepted connections
+
+*/
+/****************************************************************
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ ****************************************************************/
+
+/*
+
+Purpose:
+
+WaitForHeaders polls all connections in "WaitForHeaderQueue" to see if
+headers have arived. If so, the headers are decoded and the request is
+moved to either the "SendingDataQueue" or the "UserspaceQueue".
+
+Return value:
+ The number of requests that changed status
+*/
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+#include <linux/smp_lock.h>
+#include <linux/file.h>
+
+#include <asm/uaccess.h>
+
+#include "structure.h"
+#include "prototypes.h"
+
+static char *Buffer[CONFIG_KHTTPD_NUMCPU];
+
+
+static int DecodeHeader(const int CPUNR, struct http_request *Request);
+
+
+int WaitForHeaders(const int CPUNR)
+{
+ struct http_request *CurrentRequest,**Prev;
+ struct sock *sk;
+ int count = 0;
+
+ EnterFunction("WaitForHeaders");
+
+ CurrentRequest = threadinfo[CPUNR].WaitForHeaderQueue;
+
+ Prev = &(threadinfo[CPUNR].WaitForHeaderQueue);
+
+ while (CurrentRequest!=NULL)
+ {
+
+ /* If the connection is lost, remove from queue */
+
+ if (CurrentRequest->sock->sk->state != TCP_ESTABLISHED
+ && CurrentRequest->sock->sk->state != TCP_CLOSE_WAIT)
+ {
+ struct http_request *Next;
+
+ Next = CurrentRequest->Next;
+
+ *Prev = CurrentRequest->Next;
+ CurrentRequest->Next = NULL;
+
+
+ CleanUpRequest(CurrentRequest);
+ CurrentRequest = Next;
+ continue;
+ }
+
+
+
+ /* If data pending, take action */
+
+ sk = CurrentRequest->sock->sk;
+
+ if (!skb_queue_empty(&(sk->receive_queue))) /* Do we have data ? */
+ {
+ struct http_request *Next;
+
+
+
+ /* Decode header */
+
+ if (DecodeHeader(CPUNR,CurrentRequest)<0)
+ {
+ CurrentRequest = CurrentRequest->Next;
+ continue;
+ }
+
+
+ /* Remove from WaitForHeaderQueue */
+
+ Next= CurrentRequest->Next;
+
+ *Prev = Next;
+ count++;
+
+ /* Add to either the UserspaceQueue or the DataSendingQueue */
+
+ if (CurrentRequest->IsForUserspace!=0)
+ {
+ CurrentRequest->Next = threadinfo[CPUNR].UserspaceQueue;
+ threadinfo[CPUNR].UserspaceQueue = CurrentRequest;
+ } else
+ {
+ CurrentRequest->Next = threadinfo[CPUNR].DataSendingQueue;
+ threadinfo[CPUNR].DataSendingQueue = CurrentRequest;
+ }
+
+ CurrentRequest = Next;
+ continue;
+
+ }
+
+
+ Prev = &(CurrentRequest->Next);
+ CurrentRequest = CurrentRequest->Next;
+ }
+
+ LeaveFunction("WaitForHeaders");
+ return count;
+}
+
+void StopWaitingForHeaders(const int CPUNR)
+{
+ struct http_request *CurrentRequest,*Next;
+
+ EnterFunction("StopWaitingForHeaders");
+ CurrentRequest = threadinfo[CPUNR].WaitForHeaderQueue;
+
+ while (CurrentRequest!=NULL)
+ {
+ Next = CurrentRequest->Next;
+ CleanUpRequest(CurrentRequest);
+ CurrentRequest=Next;
+ }
+
+ threadinfo[CPUNR].WaitForHeaderQueue = NULL; /* The queue is empty now */
+
+ free_page((unsigned long)Buffer[CPUNR]);
+ Buffer[CPUNR]=NULL;
+
+ EnterFunction("StopWaitingForHeaders");
+}
+
+
+/*
+
+DecodeHeader peeks at the TCP/IP data, determines what the request is,
+fills the request-structure and sends the HTTP-header when apropriate.
+
+*/
+
+static int DecodeHeader(const int CPUNR, struct http_request *Request)
+{
+ struct msghdr msg;
+ struct iovec iov;
+ int len;
+
+ mm_segment_t oldfs;
+
+ EnterFunction("DecodeHeader");
+
+ if (Buffer[CPUNR] == NULL) {
+ /* see comments in main.c regarding buffer managemnet - dank */
+ printk(KERN_CRIT "khttpd: lost my buffer");
+ BUG();
+ }
+
+ /* First, read the data */
+
+ msg.msg_name = 0;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+
+ msg.msg_iov->iov_base = &Buffer[CPUNR][0];
+ msg.msg_iov->iov_len = (size_t)4095;
+
+ len = 0;
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ /* 4095 leaves a "0" to terminate the string */
+
+ len = sock_recvmsg(Request->sock,&msg,4095,MSG_PEEK);
+ set_fs(oldfs);
+
+ if (len<0) {
+ /* WONDERFUL. NO COMMENTS. --ANK */
+ Request->IsForUserspace = 1;
+ return 0;
+ }
+
+ if (len>=4094) /* BIG header, we cannot decode it so leave it to userspace */
+ {
+ Request->IsForUserspace = 1;
+ return 0;
+ }
+
+ /* Then, decode the header */
+
+
+ ParseHeader(Buffer[CPUNR],len,Request);
+
+ Request->filp = OpenFileForSecurity(Request->FileName);
+
+
+ Request->MimeType = ResolveMimeType(Request->FileName,&Request->MimeLength);
+
+
+ if (Request->MimeType==NULL) /* Unknown mime-type */
+ {
+ if (Request->filp!=NULL)
+ {
+ fput(Request->filp);
+ Request->filp = NULL;
+ }
+ Request->IsForUserspace = 1;
+
+ return 0;
+ }
+
+ if (Request->filp==NULL)
+ {
+ Request->IsForUserspace = 1;
+ return 0;
+ }
+ else
+ {
+ Request->FileLength = (int)Request->filp->f_dentry->d_inode->i_size;
+ Request->Time = Request->filp->f_dentry->d_inode->i_mtime;
+ Request->IMS_Time = mimeTime_to_UnixTime(Request->IMS);
+ sprintf(Request->LengthS,"%i",Request->FileLength);
+ time_Unix2RFC(min_t(unsigned int, Request->Time,CurrentTime_i),Request->TimeS);
+ /* The min() is required by rfc1945, section 10.10:
+ It is not allowed to send a filetime in the future */
+
+ if (Request->IMS_Time>Request->Time)
+ { /* Not modified since last time */
+ Send304(Request->sock);
+ Request->FileLength=0;
+ }
+ else /* Normal Case */
+ {
+ Request->sock->sk->tp_pinfo.af_tcp.nonagle = 2; /* this is TCP_CORK */
+ if (Request->HTTPVER!=9) /* HTTP/0.9 doesn't allow a header */
+ SendHTTPHeader(Request);
+ }
+
+
+ }
+
+ LeaveFunction("DecodeHeader");
+ return 0;
+}
+
+
+int InitWaitHeaders(int ThreadCount)
+{
+ int I,I2;
+
+ EnterFunction("InitWaitHeaders");
+ I=0;
+ while (I<ThreadCount)
+ {
+ Buffer[I] = (char*)get_free_page((int)GFP_KERNEL);
+ if (Buffer[I] == NULL)
+ {
+ printk(KERN_CRIT "kHTTPd: Not enough memory for basic needs\n");
+ I2=0;
+ while (I2<I-1)
+ {
+ free_page( (unsigned long)Buffer[I2++]);
+ }
+ return -1;
+ }
+ I++;
+ }
+
+ LeaveFunction("InitWaitHeaders");
+ return 0;
+
+}
diff --git a/uClinux-2.4.31-uc0/net/lapb/Makefile b/uClinux-2.4.31-uc0/net/lapb/Makefile
new file mode 100644
index 0000000..85b6991
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/lapb/Makefile
@@ -0,0 +1,19 @@
+#
+# Makefile for the Linux LAPB layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+
+O_TARGET := lapb.o
+
+export-objs := lapb_iface.o
+
+obj-y := lapb_in.o lapb_out.o lapb_subr.o lapb_timer.o lapb_iface.o
+obj-m := $(O_TARGET)
+
+include $(TOPDIR)/Rules.make
+
diff --git a/uClinux-2.4.31-uc0/net/lapb/lapb_iface.c b/uClinux-2.4.31-uc0/net/lapb/lapb_iface.c
new file mode 100644
index 0000000..5142e74
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/lapb/lapb_iface.c
@@ -0,0 +1,412 @@
+/*
+ * LAPB release 002
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * LAPB 001 Jonathan Naylor Started Coding
+ * LAPB 002 Jonathan Naylor New timer architecture.
+ * 2000-10-29 Henner Eisen lapb_data_indication() return status.
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/stat.h>
+#include <linux/init.h>
+#include <net/lapb.h>
+
+static lapb_cb *volatile lapb_list /* = NULL initially */;
+
+/*
+ * Free an allocated lapb control block. This is done to centralise
+ * the MOD count code.
+ */
+static void lapb_free_cb(lapb_cb *lapb)
+{
+ kfree(lapb);
+
+ MOD_DEC_USE_COUNT;
+}
+
+/*
+ * Socket removal during an interrupt is now safe.
+ */
+static void lapb_remove_cb(lapb_cb *lapb)
+{
+ lapb_cb *s;
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+ if ((s = lapb_list) == lapb) {
+ lapb_list = s->next;
+ restore_flags(flags);
+ return;
+ }
+
+ while (s != NULL && s->next != NULL) {
+ if (s->next == lapb) {
+ s->next = lapb->next;
+ restore_flags(flags);
+ return;
+ }
+
+ s = s->next;
+ }
+
+ restore_flags(flags);
+}
+
+/*
+ * Add a socket to the bound sockets list.
+ */
+static void lapb_insert_cb(lapb_cb *lapb)
+{
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+ lapb->next = lapb_list;
+ lapb_list = lapb;
+
+ restore_flags(flags);
+}
+
+/*
+ * Convert the integer token used by the device driver into a pointer
+ * to a LAPB control structure.
+ */
+static lapb_cb *lapb_tokentostruct(void *token)
+{
+ lapb_cb *lapb;
+
+ for (lapb = lapb_list; lapb != NULL; lapb = lapb->next)
+ if (lapb->token == token)
+ return lapb;
+
+ return NULL;
+}
+
+/*
+ * Create an empty LAPB control block.
+ */
+static lapb_cb *lapb_create_cb(void)
+{
+ lapb_cb *lapb;
+
+ if ((lapb = kmalloc(sizeof(*lapb), GFP_ATOMIC)) == NULL)
+ return NULL;
+
+ MOD_INC_USE_COUNT;
+
+ memset(lapb, 0x00, sizeof(*lapb));
+
+ skb_queue_head_init(&lapb->write_queue);
+ skb_queue_head_init(&lapb->ack_queue);
+
+ init_timer(&lapb->t1timer);
+ init_timer(&lapb->t2timer);
+
+ lapb->t1 = LAPB_DEFAULT_T1;
+ lapb->t2 = LAPB_DEFAULT_T2;
+ lapb->n2 = LAPB_DEFAULT_N2;
+ lapb->mode = LAPB_DEFAULT_MODE;
+ lapb->window = LAPB_DEFAULT_WINDOW;
+ lapb->state = LAPB_STATE_0;
+
+ return lapb;
+}
+
+int lapb_register(void *token, struct lapb_register_struct *callbacks)
+{
+ lapb_cb *lapb;
+
+ if (lapb_tokentostruct(token) != NULL)
+ return LAPB_BADTOKEN;
+
+ if ((lapb = lapb_create_cb()) == NULL)
+ return LAPB_NOMEM;
+
+ lapb->token = token;
+ lapb->callbacks = *callbacks;
+
+ lapb_insert_cb(lapb);
+
+ lapb_start_t1timer(lapb);
+
+ return LAPB_OK;
+}
+
+int lapb_unregister(void *token)
+{
+ lapb_cb *lapb;
+
+ if ((lapb = lapb_tokentostruct(token)) == NULL)
+ return LAPB_BADTOKEN;
+
+ lapb_stop_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+
+ lapb_clear_queues(lapb);
+
+ lapb_remove_cb(lapb);
+
+ lapb_free_cb(lapb);
+
+ return LAPB_OK;
+}
+
+int lapb_getparms(void *token, struct lapb_parms_struct *parms)
+{
+ lapb_cb *lapb;
+
+ if ((lapb = lapb_tokentostruct(token)) == NULL)
+ return LAPB_BADTOKEN;
+
+ parms->t1 = lapb->t1 / HZ;
+ parms->t2 = lapb->t2 / HZ;
+ parms->n2 = lapb->n2;
+ parms->n2count = lapb->n2count;
+ parms->state = lapb->state;
+ parms->window = lapb->window;
+ parms->mode = lapb->mode;
+
+ if (!timer_pending(&lapb->t1timer))
+ parms->t1timer = 0;
+ else
+ parms->t1timer = (lapb->t1timer.expires - jiffies) / HZ;
+
+ if (!timer_pending(&lapb->t2timer))
+ parms->t2timer = 0;
+ else
+ parms->t2timer = (lapb->t2timer.expires - jiffies) / HZ;
+
+ return LAPB_OK;
+}
+
+int lapb_setparms(void *token, struct lapb_parms_struct *parms)
+{
+ lapb_cb *lapb;
+
+ if ((lapb = lapb_tokentostruct(token)) == NULL)
+ return LAPB_BADTOKEN;
+
+ if (parms->t1 < 1)
+ return LAPB_INVALUE;
+
+ if (parms->t2 < 1)
+ return LAPB_INVALUE;
+
+ if (parms->n2 < 1)
+ return LAPB_INVALUE;
+
+ if (lapb->state == LAPB_STATE_0) {
+ if (parms->mode & LAPB_EXTENDED) {
+ if (parms->window < 1 || parms->window > 127)
+ return LAPB_INVALUE;
+ } else {
+ if (parms->window < 1 || parms->window > 7)
+ return LAPB_INVALUE;
+ }
+
+ lapb->mode = parms->mode;
+ lapb->window = parms->window;
+ }
+
+ lapb->t1 = parms->t1 * HZ;
+ lapb->t2 = parms->t2 * HZ;
+ lapb->n2 = parms->n2;
+
+ return LAPB_OK;
+}
+
+int lapb_connect_request(void *token)
+{
+ lapb_cb *lapb;
+
+ if ((lapb = lapb_tokentostruct(token)) == NULL)
+ return LAPB_BADTOKEN;
+
+ switch (lapb->state) {
+ case LAPB_STATE_1:
+ return LAPB_OK;
+ case LAPB_STATE_3:
+ case LAPB_STATE_4:
+ return LAPB_CONNECTED;
+ }
+
+ lapb_establish_data_link(lapb);
+
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S0 -> S1\n", lapb->token);
+#endif
+
+ lapb->state = LAPB_STATE_1;
+
+ return LAPB_OK;
+}
+
+int lapb_disconnect_request(void *token)
+{
+ lapb_cb *lapb;
+
+ if ((lapb = lapb_tokentostruct(token)) == NULL)
+ return LAPB_BADTOKEN;
+
+ switch (lapb->state) {
+ case LAPB_STATE_0:
+ return LAPB_NOTCONNECTED;
+
+ case LAPB_STATE_1:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S1 TX DISC(1)\n", lapb->token);
+#endif
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S1 -> S0\n", lapb->token);
+#endif
+ lapb_send_control(lapb, LAPB_DISC, LAPB_POLLON, LAPB_COMMAND);
+ lapb->state = LAPB_STATE_0;
+ lapb_start_t1timer(lapb);
+ return LAPB_NOTCONNECTED;
+
+ case LAPB_STATE_2:
+ return LAPB_OK;
+ }
+
+ lapb_clear_queues(lapb);
+ lapb->n2count = 0;
+ lapb_send_control(lapb, LAPB_DISC, LAPB_POLLON, LAPB_COMMAND);
+ lapb_start_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb->state = LAPB_STATE_2;
+
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 DISC(1)\n", lapb->token);
+#endif
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S3 -> S2\n", lapb->token);
+#endif
+
+ return LAPB_OK;
+}
+
+int lapb_data_request(void *token, struct sk_buff *skb)
+{
+ lapb_cb *lapb;
+
+ if ((lapb = lapb_tokentostruct(token)) == NULL)
+ return LAPB_BADTOKEN;
+
+ if (lapb->state != LAPB_STATE_3 && lapb->state != LAPB_STATE_4)
+ return LAPB_NOTCONNECTED;
+
+ skb_queue_tail(&lapb->write_queue, skb);
+
+ lapb_kick(lapb);
+
+ return LAPB_OK;
+}
+
+int lapb_data_received(void *token, struct sk_buff *skb)
+{
+ lapb_cb *lapb;
+
+ if ((lapb = lapb_tokentostruct(token)) == NULL)
+ return LAPB_BADTOKEN;
+
+ lapb_data_input(lapb, skb);
+
+ return LAPB_OK;
+}
+
+void lapb_connect_confirmation(lapb_cb *lapb, int reason)
+{
+ if (lapb->callbacks.connect_confirmation != NULL)
+ (lapb->callbacks.connect_confirmation)(lapb->token, reason);
+}
+
+void lapb_connect_indication(lapb_cb *lapb, int reason)
+{
+ if (lapb->callbacks.connect_indication != NULL)
+ (lapb->callbacks.connect_indication)(lapb->token, reason);
+}
+
+void lapb_disconnect_confirmation(lapb_cb *lapb, int reason)
+{
+ if (lapb->callbacks.disconnect_confirmation != NULL)
+ (lapb->callbacks.disconnect_confirmation)(lapb->token, reason);
+}
+
+void lapb_disconnect_indication(lapb_cb *lapb, int reason)
+{
+ if (lapb->callbacks.disconnect_indication != NULL)
+ (lapb->callbacks.disconnect_indication)(lapb->token, reason);
+}
+
+int lapb_data_indication(lapb_cb *lapb, struct sk_buff *skb)
+{
+ if (lapb->callbacks.data_indication != NULL) {
+ return (lapb->callbacks.data_indication)(lapb->token, skb);
+ }
+ kfree_skb(skb);
+ return NET_RX_CN_HIGH; /* For now; must be != NET_RX_DROP */
+}
+
+int lapb_data_transmit(lapb_cb *lapb, struct sk_buff *skb)
+{
+ int used = 0;
+
+ if (lapb->callbacks.data_transmit != NULL) {
+ (lapb->callbacks.data_transmit)(lapb->token, skb);
+ used = 1;
+ }
+
+ return used;
+}
+
+EXPORT_SYMBOL(lapb_register);
+EXPORT_SYMBOL(lapb_unregister);
+EXPORT_SYMBOL(lapb_getparms);
+EXPORT_SYMBOL(lapb_setparms);
+EXPORT_SYMBOL(lapb_connect_request);
+EXPORT_SYMBOL(lapb_disconnect_request);
+EXPORT_SYMBOL(lapb_data_request);
+EXPORT_SYMBOL(lapb_data_received);
+
+static char banner[] __initdata = KERN_INFO "NET4: LAPB for Linux. Version 0.01 for NET4.0\n";
+
+static int __init lapb_init(void)
+{
+ printk(banner);
+ return 0;
+}
+
+MODULE_AUTHOR("Jonathan Naylor <g4klx@g4klx.demon.co.uk>");
+MODULE_DESCRIPTION("The X.25 Link Access Procedure B link layer protocol");
+MODULE_LICENSE("GPL");
+
+module_init(lapb_init);
diff --git a/uClinux-2.4.31-uc0/net/lapb/lapb_in.c b/uClinux-2.4.31-uc0/net/lapb/lapb_in.c
new file mode 100644
index 0000000..b44e7e1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/lapb/lapb_in.c
@@ -0,0 +1,641 @@
+/*
+ * LAPB release 002
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * LAPB 001 Jonathan Naulor Started Coding
+ * LAPB 002 Jonathan Naylor New timer architecture.
+ * 2000-10-29 Henner Eisen lapb_data_indication() return status.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/lapb.h>
+
+/*
+ * State machine for state 0, Disconnected State.
+ * The handling of the timer(s) is in file lapb_timer.c.
+ */
+static void lapb_state0_machine(lapb_cb *lapb, struct sk_buff *skb, struct lapb_frame *frame)
+{
+ switch (frame->type) {
+ case LAPB_SABM:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S0 RX SABM(%d)\n", lapb->token, frame->pf);
+#endif
+ if (lapb->mode & LAPB_EXTENDED) {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S0 TX DM(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_DM, frame->pf, LAPB_RESPONSE);
+ } else {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S0 TX UA(%d)\n", lapb->token, frame->pf);
+#endif
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S0 -> S3\n", lapb->token);
+#endif
+ lapb_send_control(lapb, LAPB_UA, frame->pf, LAPB_RESPONSE);
+ lapb_stop_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb->state = LAPB_STATE_3;
+ lapb->condition = 0x00;
+ lapb->n2count = 0;
+ lapb->vs = 0;
+ lapb->vr = 0;
+ lapb->va = 0;
+ lapb_connect_indication(lapb, LAPB_OK);
+ }
+ break;
+
+ case LAPB_SABME:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S0 RX SABME(%d)\n", lapb->token, frame->pf);
+#endif
+ if (lapb->mode & LAPB_EXTENDED) {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S0 TX UA(%d)\n", lapb->token, frame->pf);
+#endif
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S0 -> S3\n", lapb->token);
+#endif
+ lapb_send_control(lapb, LAPB_UA, frame->pf, LAPB_RESPONSE);
+ lapb_stop_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb->state = LAPB_STATE_3;
+ lapb->condition = 0x00;
+ lapb->n2count = 0;
+ lapb->vs = 0;
+ lapb->vr = 0;
+ lapb->va = 0;
+ lapb_connect_indication(lapb, LAPB_OK);
+ } else {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S0 TX DM(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_DM, frame->pf, LAPB_RESPONSE);
+ }
+ break;
+
+ case LAPB_DISC:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S0 RX DISC(%d)\n", lapb->token, frame->pf);
+ printk(KERN_DEBUG "lapb: (%p) S0 TX UA(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_UA, frame->pf, LAPB_RESPONSE);
+ break;
+
+ default:
+ break;
+ }
+
+ kfree_skb(skb);
+}
+
+/*
+ * State machine for state 1, Awaiting Connection State.
+ * The handling of the timer(s) is in file lapb_timer.c.
+ */
+static void lapb_state1_machine(lapb_cb *lapb, struct sk_buff *skb, struct lapb_frame *frame)
+{
+ switch (frame->type) {
+ case LAPB_SABM:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S1 RX SABM(%d)\n", lapb->token, frame->pf);
+#endif
+ if (lapb->mode & LAPB_EXTENDED) {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S1 TX DM(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_DM, frame->pf, LAPB_RESPONSE);
+ } else {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S1 TX UA(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_UA, frame->pf, LAPB_RESPONSE);
+ }
+ break;
+
+ case LAPB_SABME:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S1 RX SABME(%d)\n", lapb->token, frame->pf);
+#endif
+ if (lapb->mode & LAPB_EXTENDED) {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S1 TX UA(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_UA, frame->pf, LAPB_RESPONSE);
+ } else {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S1 TX DM(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_DM, frame->pf, LAPB_RESPONSE);
+ }
+ break;
+
+ case LAPB_DISC:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S1 RX DISC(%d)\n", lapb->token, frame->pf);
+ printk(KERN_DEBUG "lapb: (%p) S1 TX DM(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_DM, frame->pf, LAPB_RESPONSE);
+ break;
+
+ case LAPB_UA:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S1 RX UA(%d)\n", lapb->token, frame->pf);
+#endif
+ if (frame->pf) {
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S1 -> S3\n", lapb->token);
+#endif
+ lapb_stop_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb->state = LAPB_STATE_3;
+ lapb->condition = 0x00;
+ lapb->n2count = 0;
+ lapb->vs = 0;
+ lapb->vr = 0;
+ lapb->va = 0;
+ lapb_connect_confirmation(lapb, LAPB_OK);
+ }
+ break;
+
+ case LAPB_DM:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S1 RX DM(%d)\n", lapb->token, frame->pf);
+#endif
+ if (frame->pf) {
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S1 -> S0\n", lapb->token);
+#endif
+ lapb_clear_queues(lapb);
+ lapb->state = LAPB_STATE_0;
+ lapb_start_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb_disconnect_indication(lapb, LAPB_REFUSED);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ kfree_skb(skb);
+}
+
+/*
+ * State machine for state 2, Awaiting Release State.
+ * The handling of the timer(s) is in file lapb_timer.c
+ */
+static void lapb_state2_machine(lapb_cb *lapb, struct sk_buff *skb, struct lapb_frame *frame)
+{
+ switch (frame->type) {
+ case LAPB_SABM:
+ case LAPB_SABME:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S2 RX {SABM,SABME}(%d)\n", lapb->token, frame->pf);
+ printk(KERN_DEBUG "lapb: (%p) S2 TX DM(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_DM, frame->pf, LAPB_RESPONSE);
+ break;
+
+ case LAPB_DISC:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S2 RX DISC(%d)\n", lapb->token, frame->pf);
+ printk(KERN_DEBUG "lapb: (%p) S2 TX UA(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_UA, frame->pf, LAPB_RESPONSE);
+ break;
+
+ case LAPB_UA:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S2 RX UA(%d)\n", lapb->token, frame->pf);
+#endif
+ if (frame->pf) {
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S2 -> S0\n", lapb->token);
+#endif
+ lapb->state = LAPB_STATE_0;
+ lapb_start_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb_disconnect_confirmation(lapb, LAPB_OK);
+ }
+ break;
+
+ case LAPB_DM:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S2 RX DM(%d)\n", lapb->token, frame->pf);
+#endif
+ if (frame->pf) {
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S2 -> S0\n", lapb->token);
+#endif
+ lapb->state = LAPB_STATE_0;
+ lapb_start_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb_disconnect_confirmation(lapb, LAPB_NOTCONNECTED);
+ }
+ break;
+
+ case LAPB_I:
+ case LAPB_REJ:
+ case LAPB_RNR:
+ case LAPB_RR:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S2 RX {I,REJ,RNR,RR}(%d)\n", lapb->token, frame->pf);
+ printk(KERN_DEBUG "lapb: (%p) S2 RX DM(%d)\n", lapb->token, frame->pf);
+#endif
+ if (frame->pf) lapb_send_control(lapb, LAPB_DM, frame->pf, LAPB_RESPONSE);
+ break;
+
+ default:
+ break;
+ }
+
+ kfree_skb(skb);
+}
+
+/*
+ * State machine for state 3, Connected State.
+ * The handling of the timer(s) is in file lapb_timer.c
+ */
+static void lapb_state3_machine(lapb_cb *lapb, struct sk_buff *skb, struct lapb_frame *frame)
+{
+ int queued = 0;
+ int modulus;
+
+ modulus = (lapb->mode & LAPB_EXTENDED) ? LAPB_EMODULUS : LAPB_SMODULUS;
+
+ switch (frame->type) {
+ case LAPB_SABM:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 RX SABM(%d)\n", lapb->token, frame->pf);
+#endif
+ if (lapb->mode & LAPB_EXTENDED) {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 TX DM(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_DM, frame->pf, LAPB_RESPONSE);
+ } else {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 TX UA(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_UA, frame->pf, LAPB_RESPONSE);
+ lapb_stop_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb->condition = 0x00;
+ lapb->n2count = 0;
+ lapb->vs = 0;
+ lapb->vr = 0;
+ lapb->va = 0;
+ lapb_requeue_frames(lapb);
+ }
+ break;
+
+ case LAPB_SABME:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 RX SABME(%d)\n", lapb->token, frame->pf);
+#endif
+ if (lapb->mode & LAPB_EXTENDED) {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 TX UA(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_UA, frame->pf, LAPB_RESPONSE);
+ lapb_stop_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb->condition = 0x00;
+ lapb->n2count = 0;
+ lapb->vs = 0;
+ lapb->vr = 0;
+ lapb->va = 0;
+ lapb_requeue_frames(lapb);
+ } else {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 TX DM(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_DM, frame->pf, LAPB_RESPONSE);
+ }
+ break;
+
+ case LAPB_DISC:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 RX DISC(%d)\n", lapb->token, frame->pf);
+#endif
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S3 -> S0\n", lapb->token);
+#endif
+ lapb_clear_queues(lapb);
+ lapb_send_control(lapb, LAPB_UA, frame->pf, LAPB_RESPONSE);
+ lapb_start_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb->state = LAPB_STATE_0;
+ lapb_disconnect_indication(lapb, LAPB_OK);
+ break;
+
+ case LAPB_DM:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 RX DM(%d)\n", lapb->token, frame->pf);
+#endif
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S3 -> S0\n", lapb->token);
+#endif
+ lapb_clear_queues(lapb);
+ lapb->state = LAPB_STATE_0;
+ lapb_start_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb_disconnect_indication(lapb, LAPB_NOTCONNECTED);
+ break;
+
+ case LAPB_RNR:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 RX RNR(%d) R%d\n", lapb->token, frame->pf, frame->nr);
+#endif
+ lapb->condition |= LAPB_PEER_RX_BUSY_CONDITION;
+ lapb_check_need_response(lapb, frame->cr, frame->pf);
+ if (lapb_validate_nr(lapb, frame->nr)) {
+ lapb_check_iframes_acked(lapb, frame->nr);
+ } else {
+ lapb->frmr_data = *frame;
+ lapb->frmr_type = LAPB_FRMR_Z;
+ lapb_transmit_frmr(lapb);
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S3 -> S4\n", lapb->token);
+#endif
+ lapb_start_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb->state = LAPB_STATE_4;
+ lapb->n2count = 0;
+ }
+ break;
+
+ case LAPB_RR:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 RX RR(%d) R%d\n", lapb->token, frame->pf, frame->nr);
+#endif
+ lapb->condition &= ~LAPB_PEER_RX_BUSY_CONDITION;
+ lapb_check_need_response(lapb, frame->cr, frame->pf);
+ if (lapb_validate_nr(lapb, frame->nr)) {
+ lapb_check_iframes_acked(lapb, frame->nr);
+ } else {
+ lapb->frmr_data = *frame;
+ lapb->frmr_type = LAPB_FRMR_Z;
+ lapb_transmit_frmr(lapb);
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S3 -> S4\n", lapb->token);
+#endif
+ lapb_start_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb->state = LAPB_STATE_4;
+ lapb->n2count = 0;
+ }
+ break;
+
+ case LAPB_REJ:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 RX REJ(%d) R%d\n", lapb->token, frame->pf, frame->nr);
+#endif
+ lapb->condition &= ~LAPB_PEER_RX_BUSY_CONDITION;
+ lapb_check_need_response(lapb, frame->cr, frame->pf);
+ if (lapb_validate_nr(lapb, frame->nr)) {
+ lapb_frames_acked(lapb, frame->nr);
+ lapb_stop_t1timer(lapb);
+ lapb->n2count = 0;
+ lapb_requeue_frames(lapb);
+ } else {
+ lapb->frmr_data = *frame;
+ lapb->frmr_type = LAPB_FRMR_Z;
+ lapb_transmit_frmr(lapb);
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S3 -> S4\n", lapb->token);
+#endif
+ lapb_start_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb->state = LAPB_STATE_4;
+ lapb->n2count = 0;
+ }
+ break;
+
+ case LAPB_I:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 RX I(%d) S%d R%d\n", lapb->token, frame->pf, frame->ns, frame->nr);
+#endif
+ if (!lapb_validate_nr(lapb, frame->nr)) {
+ lapb->frmr_data = *frame;
+ lapb->frmr_type = LAPB_FRMR_Z;
+ lapb_transmit_frmr(lapb);
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S3 -> S4\n", lapb->token);
+#endif
+ lapb_start_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb->state = LAPB_STATE_4;
+ lapb->n2count = 0;
+ break;
+ }
+ if (lapb->condition & LAPB_PEER_RX_BUSY_CONDITION) {
+ lapb_frames_acked(lapb, frame->nr);
+ } else {
+ lapb_check_iframes_acked(lapb, frame->nr);
+ }
+ if (frame->ns == lapb->vr) {
+ int cn;
+ cn = lapb_data_indication(lapb, skb);
+ queued = 1;
+ /*
+ * If upper layer has dropped the frame, we
+ * basically ignore any further protocol
+ * processing. This will cause the peer
+ * to re-transmit the frame later like
+ * a frame lost on the wire.
+ */
+ if(cn == NET_RX_DROP){
+ printk(KERN_DEBUG "LAPB: rx congestion\n");
+ break;
+ }
+ lapb->vr = (lapb->vr + 1) % modulus;
+ lapb->condition &= ~LAPB_REJECT_CONDITION;
+ if (frame->pf) {
+ lapb_enquiry_response(lapb);
+ } else {
+ if (!(lapb->condition & LAPB_ACK_PENDING_CONDITION)) {
+ lapb->condition |= LAPB_ACK_PENDING_CONDITION;
+ lapb_start_t2timer(lapb);
+ }
+ }
+ } else {
+ if (lapb->condition & LAPB_REJECT_CONDITION) {
+ if (frame->pf)
+ lapb_enquiry_response(lapb);
+ } else {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 TX REJ(%d) R%d\n", lapb->token, frame->pf, lapb->vr);
+#endif
+ lapb->condition |= LAPB_REJECT_CONDITION;
+ lapb_send_control(lapb, LAPB_REJ, frame->pf, LAPB_RESPONSE);
+ lapb->condition &= ~LAPB_ACK_PENDING_CONDITION;
+ }
+ }
+ break;
+
+ case LAPB_FRMR:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 RX FRMR(%d) %02X %02X %02X %02X %02X\n", lapb->token, frame->pf, skb->data[0], skb->data[1], skb->data[2], skb->data[3], skb->data[4]);
+#endif
+ lapb_establish_data_link(lapb);
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S3 -> S1\n", lapb->token);
+#endif
+ lapb_requeue_frames(lapb);
+ lapb->state = LAPB_STATE_1;
+ break;
+
+ case LAPB_ILLEGAL:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S3 RX ILLEGAL(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb->frmr_data = *frame;
+ lapb->frmr_type = LAPB_FRMR_W;
+ lapb_transmit_frmr(lapb);
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S3 -> S4\n", lapb->token);
+#endif
+ lapb_start_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb->state = LAPB_STATE_4;
+ lapb->n2count = 0;
+ break;
+
+ default:
+ break;
+ }
+
+ if (!queued)
+ kfree_skb(skb);
+}
+
+/*
+ * State machine for state 4, Frame Reject State.
+ * The handling of the timer(s) is in file lapb_timer.c.
+ */
+static void lapb_state4_machine(lapb_cb *lapb, struct sk_buff *skb, struct lapb_frame *frame)
+{
+ switch (frame->type) {
+ case LAPB_SABM:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S4 RX SABM(%d)\n", lapb->token, frame->pf);
+#endif
+ if (lapb->mode & LAPB_EXTENDED) {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S4 TX DM(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_DM, frame->pf, LAPB_RESPONSE);
+ } else {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S4 TX UA(%d)\n", lapb->token, frame->pf);
+#endif
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S4 -> S3\n", lapb->token);
+#endif
+ lapb_send_control(lapb, LAPB_UA, frame->pf, LAPB_RESPONSE);
+ lapb_stop_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb->state = LAPB_STATE_3;
+ lapb->condition = 0x00;
+ lapb->n2count = 0;
+ lapb->vs = 0;
+ lapb->vr = 0;
+ lapb->va = 0;
+ lapb_connect_indication(lapb, LAPB_OK);
+ }
+ break;
+
+ case LAPB_SABME:
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S4 RX SABME(%d)\n", lapb->token, frame->pf);
+#endif
+ if (lapb->mode & LAPB_EXTENDED) {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S4 TX UA(%d)\n", lapb->token, frame->pf);
+#endif
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S4 -> S3\n", lapb->token);
+#endif
+ lapb_send_control(lapb, LAPB_UA, frame->pf, LAPB_RESPONSE);
+ lapb_stop_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+ lapb->state = LAPB_STATE_3;
+ lapb->condition = 0x00;
+ lapb->n2count = 0;
+ lapb->vs = 0;
+ lapb->vr = 0;
+ lapb->va = 0;
+ lapb_connect_indication(lapb, LAPB_OK);
+ } else {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S4 TX DM(%d)\n", lapb->token, frame->pf);
+#endif
+ lapb_send_control(lapb, LAPB_DM, frame->pf, LAPB_RESPONSE);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ kfree_skb(skb);
+}
+
+/*
+ * Process an incoming LAPB frame
+ */
+void lapb_data_input(lapb_cb *lapb, struct sk_buff *skb)
+{
+ struct lapb_frame frame;
+
+ lapb_decode(lapb, skb, &frame);
+
+ switch (lapb->state) {
+ case LAPB_STATE_0:
+ lapb_state0_machine(lapb, skb, &frame);
+ break;
+ case LAPB_STATE_1:
+ lapb_state1_machine(lapb, skb, &frame);
+ break;
+ case LAPB_STATE_2:
+ lapb_state2_machine(lapb, skb, &frame);
+ break;
+ case LAPB_STATE_3:
+ lapb_state3_machine(lapb, skb, &frame);
+ break;
+ case LAPB_STATE_4:
+ lapb_state4_machine(lapb, skb, &frame);
+ break;
+ }
+
+ lapb_kick(lapb);
+}
diff --git a/uClinux-2.4.31-uc0/net/lapb/lapb_out.c b/uClinux-2.4.31-uc0/net/lapb/lapb_out.c
new file mode 100644
index 0000000..c3fcc2f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/lapb/lapb_out.c
@@ -0,0 +1,223 @@
+/*
+ * LAPB release 002
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * LAPB 001 Jonathan Naylor Started Coding
+ * LAPB 002 Jonathan Naylor New timer architecture.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/lapb.h>
+
+/*
+ * This procedure is passed a buffer descriptor for an iframe. It builds
+ * the rest of the control part of the frame and then writes it out.
+ */
+static void lapb_send_iframe(lapb_cb *lapb, struct sk_buff *skb, int poll_bit)
+{
+ unsigned char *frame;
+
+ if (skb == NULL)
+ return;
+
+ if (lapb->mode & LAPB_EXTENDED) {
+ frame = skb_push(skb, 2);
+
+ frame[0] = LAPB_I;
+ frame[0] |= (lapb->vs << 1);
+ frame[1] = (poll_bit) ? LAPB_EPF : 0;
+ frame[1] |= (lapb->vr << 1);
+ } else {
+ frame = skb_push(skb, 1);
+
+ *frame = LAPB_I;
+ *frame |= (poll_bit) ? LAPB_SPF : 0;
+ *frame |= (lapb->vr << 5);
+ *frame |= (lapb->vs << 1);
+ }
+
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S%d TX I(%d) S%d R%d\n", lapb->token, lapb->state, poll_bit, lapb->vs, lapb->vr);
+#endif
+
+ lapb_transmit_buffer(lapb, skb, LAPB_COMMAND);
+}
+
+void lapb_kick(lapb_cb *lapb)
+{
+ struct sk_buff *skb, *skbn;
+ unsigned short modulus, start, end;
+
+ modulus = (lapb->mode & LAPB_EXTENDED) ? LAPB_EMODULUS : LAPB_SMODULUS;
+
+ start = (skb_peek(&lapb->ack_queue) == NULL) ? lapb->va : lapb->vs;
+ end = (lapb->va + lapb->window) % modulus;
+
+ if (!(lapb->condition & LAPB_PEER_RX_BUSY_CONDITION) &&
+ start != end &&
+ skb_peek(&lapb->write_queue) != NULL) {
+
+ lapb->vs = start;
+
+ /*
+ * Dequeue the frame and copy it.
+ */
+ skb = skb_dequeue(&lapb->write_queue);
+
+ do {
+ if ((skbn = skb_clone(skb, GFP_ATOMIC)) == NULL) {
+ skb_queue_head(&lapb->write_queue, skb);
+ break;
+ }
+
+ if (skb->sk != NULL)
+ skb_set_owner_w(skbn, skb->sk);
+
+ /*
+ * Transmit the frame copy.
+ */
+ lapb_send_iframe(lapb, skbn, LAPB_POLLOFF);
+
+ lapb->vs = (lapb->vs + 1) % modulus;
+
+ /*
+ * Requeue the original data frame.
+ */
+ skb_queue_tail(&lapb->ack_queue, skb);
+
+ } while (lapb->vs != end && (skb = skb_dequeue(&lapb->write_queue)) != NULL);
+
+ lapb->condition &= ~LAPB_ACK_PENDING_CONDITION;
+
+ if (!lapb_t1timer_running(lapb))
+ lapb_start_t1timer(lapb);
+ }
+}
+
+void lapb_transmit_buffer(lapb_cb *lapb, struct sk_buff *skb, int type)
+{
+ unsigned char *ptr;
+
+ ptr = skb_push(skb, 1);
+
+ if (lapb->mode & LAPB_MLP) {
+ if (lapb->mode & LAPB_DCE) {
+ if (type == LAPB_COMMAND)
+ *ptr = LAPB_ADDR_C;
+ if (type == LAPB_RESPONSE)
+ *ptr = LAPB_ADDR_D;
+ } else {
+ if (type == LAPB_COMMAND)
+ *ptr = LAPB_ADDR_D;
+ if (type == LAPB_RESPONSE)
+ *ptr = LAPB_ADDR_C;
+ }
+ } else {
+ if (lapb->mode & LAPB_DCE) {
+ if (type == LAPB_COMMAND)
+ *ptr = LAPB_ADDR_A;
+ if (type == LAPB_RESPONSE)
+ *ptr = LAPB_ADDR_B;
+ } else {
+ if (type == LAPB_COMMAND)
+ *ptr = LAPB_ADDR_B;
+ if (type == LAPB_RESPONSE)
+ *ptr = LAPB_ADDR_A;
+ }
+ }
+
+#if LAPB_DEBUG > 2
+ printk(KERN_DEBUG "lapb: (%p) S%d TX %02X %02X %02X\n", lapb->token, lapb->state, skb->data[0], skb->data[1], skb->data[2]);
+#endif
+
+ if (!lapb_data_transmit(lapb, skb))
+ kfree_skb(skb);
+}
+
+void lapb_establish_data_link(lapb_cb *lapb)
+{
+ lapb->condition = 0x00;
+ lapb->n2count = 0;
+
+ if (lapb->mode & LAPB_EXTENDED) {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S%d TX SABME(1)\n", lapb->token, lapb->state);
+#endif
+ lapb_send_control(lapb, LAPB_SABME, LAPB_POLLON, LAPB_COMMAND);
+ } else {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S%d TX SABM(1)\n", lapb->token, lapb->state);
+#endif
+ lapb_send_control(lapb, LAPB_SABM, LAPB_POLLON, LAPB_COMMAND);
+ }
+
+ lapb_start_t1timer(lapb);
+ lapb_stop_t2timer(lapb);
+}
+
+void lapb_enquiry_response(lapb_cb *lapb)
+{
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S%d TX RR(1) R%d\n", lapb->token, lapb->state, lapb->vr);
+#endif
+
+ lapb_send_control(lapb, LAPB_RR, LAPB_POLLON, LAPB_RESPONSE);
+
+ lapb->condition &= ~LAPB_ACK_PENDING_CONDITION;
+}
+
+void lapb_timeout_response(lapb_cb *lapb)
+{
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S%d TX RR(0) R%d\n", lapb->token, lapb->state, lapb->vr);
+#endif
+
+ lapb_send_control(lapb, LAPB_RR, LAPB_POLLOFF, LAPB_RESPONSE);
+
+ lapb->condition &= ~LAPB_ACK_PENDING_CONDITION;
+}
+
+void lapb_check_iframes_acked(lapb_cb *lapb, unsigned short nr)
+{
+ if (lapb->vs == nr) {
+ lapb_frames_acked(lapb, nr);
+ lapb_stop_t1timer(lapb);
+ lapb->n2count = 0;
+ } else {
+ if (lapb->va != nr) {
+ lapb_frames_acked(lapb, nr);
+ lapb_start_t1timer(lapb);
+ }
+ }
+}
+
+void lapb_check_need_response(lapb_cb *lapb, int type, int pf)
+{
+ if (type == LAPB_COMMAND && pf)
+ lapb_enquiry_response(lapb);
+}
diff --git a/uClinux-2.4.31-uc0/net/lapb/lapb_subr.c b/uClinux-2.4.31-uc0/net/lapb/lapb_subr.c
new file mode 100644
index 0000000..439a6dd
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/lapb/lapb_subr.c
@@ -0,0 +1,276 @@
+/*
+ * LAPB release 002
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * LAPB 001 Jonathan Naylor Started Coding
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/lapb.h>
+
+/*
+ * This routine purges all the queues of frames.
+ */
+void lapb_clear_queues(lapb_cb *lapb)
+{
+ skb_queue_purge(&lapb->write_queue);
+ skb_queue_purge(&lapb->ack_queue);
+}
+
+/*
+ * This routine purges the input queue of those frames that have been
+ * acknowledged. This replaces the boxes labelled "V(a) <- N(r)" on the
+ * SDL diagram.
+ */
+void lapb_frames_acked(lapb_cb *lapb, unsigned short nr)
+{
+ struct sk_buff *skb;
+ int modulus;
+
+ modulus = (lapb->mode & LAPB_EXTENDED) ? LAPB_EMODULUS : LAPB_SMODULUS;
+
+ /*
+ * Remove all the ack-ed frames from the ack queue.
+ */
+ if (lapb->va != nr) {
+ while (skb_peek(&lapb->ack_queue) != NULL && lapb->va != nr) {
+ skb = skb_dequeue(&lapb->ack_queue);
+ kfree_skb(skb);
+ lapb->va = (lapb->va + 1) % modulus;
+ }
+ }
+}
+
+void lapb_requeue_frames(lapb_cb *lapb)
+{
+ struct sk_buff *skb, *skb_prev = NULL;
+
+ /*
+ * Requeue all the un-ack-ed frames on the output queue to be picked
+ * up by lapb_kick called from the timer. This arrangement handles the
+ * possibility of an empty output queue.
+ */
+ while ((skb = skb_dequeue(&lapb->ack_queue)) != NULL) {
+ if (skb_prev == NULL)
+ skb_queue_head(&lapb->write_queue, skb);
+ else
+ skb_append(skb_prev, skb);
+ skb_prev = skb;
+ }
+}
+
+/*
+ * Validate that the value of nr is between va and vs. Return true or
+ * false for testing.
+ */
+int lapb_validate_nr(lapb_cb *lapb, unsigned short nr)
+{
+ unsigned short vc = lapb->va;
+ int modulus;
+
+ modulus = (lapb->mode & LAPB_EXTENDED) ? LAPB_EMODULUS : LAPB_SMODULUS;
+
+ while (vc != lapb->vs) {
+ if (nr == vc) return 1;
+ vc = (vc + 1) % modulus;
+ }
+
+ if (nr == lapb->vs) return 1;
+
+ return 0;
+}
+
+/*
+ * This routine is the centralised routine for parsing the control
+ * information for the different frame formats.
+ */
+void lapb_decode(lapb_cb *lapb, struct sk_buff *skb, struct lapb_frame *frame)
+{
+ frame->type = LAPB_ILLEGAL;
+
+#if LAPB_DEBUG > 2
+ printk(KERN_DEBUG "lapb: (%p) S%d RX %02X %02X %02X\n", lapb->token, lapb->state, skb->data[0], skb->data[1], skb->data[2]);
+#endif
+
+ if (lapb->mode & LAPB_MLP) {
+ if (lapb->mode & LAPB_DCE) {
+ if (skb->data[0] == LAPB_ADDR_D)
+ frame->cr = LAPB_COMMAND;
+ if (skb->data[0] == LAPB_ADDR_C)
+ frame->cr = LAPB_RESPONSE;
+ } else {
+ if (skb->data[0] == LAPB_ADDR_C)
+ frame->cr = LAPB_COMMAND;
+ if (skb->data[0] == LAPB_ADDR_D)
+ frame->cr = LAPB_RESPONSE;
+ }
+ } else {
+ if (lapb->mode & LAPB_DCE) {
+ if (skb->data[0] == LAPB_ADDR_B)
+ frame->cr = LAPB_COMMAND;
+ if (skb->data[0] == LAPB_ADDR_A)
+ frame->cr = LAPB_RESPONSE;
+ } else {
+ if (skb->data[0] == LAPB_ADDR_A)
+ frame->cr = LAPB_COMMAND;
+ if (skb->data[0] == LAPB_ADDR_B)
+ frame->cr = LAPB_RESPONSE;
+ }
+ }
+
+ skb_pull(skb, 1);
+
+ if (lapb->mode & LAPB_EXTENDED) {
+ if ((skb->data[0] & LAPB_S) == 0) {
+ frame->type = LAPB_I; /* I frame - carries NR/NS/PF */
+ frame->ns = (skb->data[0] >> 1) & 0x7F;
+ frame->nr = (skb->data[1] >> 1) & 0x7F;
+ frame->pf = skb->data[1] & LAPB_EPF;
+ frame->control[0] = skb->data[0];
+ frame->control[1] = skb->data[1];
+ skb_pull(skb, 2);
+ } else if ((skb->data[0] & LAPB_U) == 1) { /* S frame - take out PF/NR */
+ frame->type = skb->data[0] & 0x0F;
+ frame->nr = (skb->data[1] >> 1) & 0x7F;
+ frame->pf = skb->data[1] & LAPB_EPF;
+ frame->control[0] = skb->data[0];
+ frame->control[1] = skb->data[1];
+ skb_pull(skb, 2);
+ } else if ((skb->data[0] & LAPB_U) == 3) { /* U frame - take out PF */
+ frame->type = skb->data[0] & ~LAPB_SPF;
+ frame->pf = skb->data[0] & LAPB_SPF;
+ frame->control[0] = skb->data[0];
+ frame->control[1] = 0x00;
+ skb_pull(skb, 1);
+ }
+ } else {
+ if ((skb->data[0] & LAPB_S) == 0) {
+ frame->type = LAPB_I; /* I frame - carries NR/NS/PF */
+ frame->ns = (skb->data[0] >> 1) & 0x07;
+ frame->nr = (skb->data[0] >> 5) & 0x07;
+ frame->pf = skb->data[0] & LAPB_SPF;
+ } else if ((skb->data[0] & LAPB_U) == 1) { /* S frame - take out PF/NR */
+ frame->type = skb->data[0] & 0x0F;
+ frame->nr = (skb->data[0] >> 5) & 0x07;
+ frame->pf = skb->data[0] & LAPB_SPF;
+ } else if ((skb->data[0] & LAPB_U) == 3) { /* U frame - take out PF */
+ frame->type = skb->data[0] & ~LAPB_SPF;
+ frame->pf = skb->data[0] & LAPB_SPF;
+ }
+
+ frame->control[0] = skb->data[0];
+
+ skb_pull(skb, 1);
+ }
+}
+
+/*
+ * This routine is called when the HDLC layer internally generates a
+ * command or response for the remote machine ( eg. RR, UA etc. ).
+ * Only supervisory or unnumbered frames are processed, FRMRs are handled
+ * by lapb_transmit_frmr below.
+ */
+void lapb_send_control(lapb_cb *lapb, int frametype, int poll_bit, int type)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+
+ if ((skb = alloc_skb(LAPB_HEADER_LEN + 3, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skb, LAPB_HEADER_LEN + 1);
+
+ if (lapb->mode & LAPB_EXTENDED) {
+ if ((frametype & LAPB_U) == LAPB_U) {
+ dptr = skb_put(skb, 1);
+ *dptr = frametype;
+ *dptr |= (poll_bit) ? LAPB_SPF : 0;
+ } else {
+ dptr = skb_put(skb, 2);
+ dptr[0] = frametype;
+ dptr[1] = (lapb->vr << 1);
+ dptr[1] |= (poll_bit) ? LAPB_EPF : 0;
+ }
+ } else {
+ dptr = skb_put(skb, 1);
+ *dptr = frametype;
+ *dptr |= (poll_bit) ? LAPB_SPF : 0;
+ if ((frametype & LAPB_U) == LAPB_S) /* S frames carry NR */
+ *dptr |= (lapb->vr << 5);
+ }
+
+ lapb_transmit_buffer(lapb, skb, type);
+}
+
+/*
+ * This routine generates FRMRs based on information previously stored in
+ * the LAPB control block.
+ */
+void lapb_transmit_frmr(lapb_cb *lapb)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+
+ if ((skb = alloc_skb(LAPB_HEADER_LEN + 7, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skb, LAPB_HEADER_LEN + 1);
+
+ if (lapb->mode & LAPB_EXTENDED) {
+ dptr = skb_put(skb, 6);
+ *dptr++ = LAPB_FRMR;
+ *dptr++ = lapb->frmr_data.control[0];
+ *dptr++ = lapb->frmr_data.control[1];
+ *dptr++ = (lapb->vs << 1) & 0xFE;
+ *dptr = (lapb->vr << 1) & 0xFE;
+ if (lapb->frmr_data.cr == LAPB_RESPONSE)
+ *dptr |= 0x01;
+ dptr++;
+ *dptr++ = lapb->frmr_type;
+
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S%d TX FRMR %02X %02X %02X %02X %02X\n", lapb->token, lapb->state, skb->data[1], skb->data[2], skb->data[3], skb->data[4], skb->data[5]);
+#endif
+ } else {
+ dptr = skb_put(skb, 4);
+ *dptr++ = LAPB_FRMR;
+ *dptr++ = lapb->frmr_data.control[0];
+ *dptr = (lapb->vs << 1) & 0x0E;
+ *dptr |= (lapb->vr << 5) & 0xE0;
+ if (lapb->frmr_data.cr == LAPB_RESPONSE)
+ *dptr |= 0x10;
+ dptr++;
+ *dptr++ = lapb->frmr_type;
+
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S%d TX FRMR %02X %02X %02X\n", lapb->token, lapb->state, skb->data[1], skb->data[2], skb->data[3]);
+#endif
+ }
+
+ lapb_transmit_buffer(lapb, skb, LAPB_RESPONSE);
+}
diff --git a/uClinux-2.4.31-uc0/net/lapb/lapb_timer.c b/uClinux-2.4.31-uc0/net/lapb/lapb_timer.c
new file mode 100644
index 0000000..d65e32e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/lapb/lapb_timer.c
@@ -0,0 +1,189 @@
+/*
+ * LAPB release 002
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * LAPB 001 Jonathan Naylor Started Coding
+ * LAPB 002 Jonathan Naylor New timer architecture.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/lapb.h>
+
+static void lapb_t1timer_expiry(unsigned long);
+static void lapb_t2timer_expiry(unsigned long);
+
+void lapb_start_t1timer(lapb_cb *lapb)
+{
+ del_timer(&lapb->t1timer);
+
+ lapb->t1timer.data = (unsigned long)lapb;
+ lapb->t1timer.function = &lapb_t1timer_expiry;
+ lapb->t1timer.expires = jiffies + lapb->t1;
+
+ add_timer(&lapb->t1timer);
+}
+
+void lapb_start_t2timer(lapb_cb *lapb)
+{
+ del_timer(&lapb->t2timer);
+
+ lapb->t2timer.data = (unsigned long)lapb;
+ lapb->t2timer.function = &lapb_t2timer_expiry;
+ lapb->t2timer.expires = jiffies + lapb->t2;
+
+ add_timer(&lapb->t2timer);
+}
+
+void lapb_stop_t1timer(lapb_cb *lapb)
+{
+ del_timer(&lapb->t1timer);
+}
+
+void lapb_stop_t2timer(lapb_cb *lapb)
+{
+ del_timer(&lapb->t2timer);
+}
+
+int lapb_t1timer_running(lapb_cb *lapb)
+{
+ return timer_pending(&lapb->t1timer);
+}
+
+static void lapb_t2timer_expiry(unsigned long param)
+{
+ lapb_cb *lapb = (lapb_cb *)param;
+
+ if (lapb->condition & LAPB_ACK_PENDING_CONDITION) {
+ lapb->condition &= ~LAPB_ACK_PENDING_CONDITION;
+ lapb_timeout_response(lapb);
+ }
+}
+
+static void lapb_t1timer_expiry(unsigned long param)
+{
+ lapb_cb *lapb = (lapb_cb *)param;
+
+ switch (lapb->state) {
+
+ /*
+ * If we are a DCE, keep going DM .. DM .. DM
+ */
+ case LAPB_STATE_0:
+ if (lapb->mode & LAPB_DCE)
+ lapb_send_control(lapb, LAPB_DM, LAPB_POLLOFF, LAPB_RESPONSE);
+ break;
+
+ /*
+ * Awaiting connection state, send SABM(E), up to N2 times.
+ */
+ case LAPB_STATE_1:
+ if (lapb->n2count == lapb->n2) {
+ lapb_clear_queues(lapb);
+ lapb->state = LAPB_STATE_0;
+ lapb_disconnect_indication(lapb, LAPB_TIMEDOUT);
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S1 -> S0\n", lapb->token);
+#endif
+ return;
+ } else {
+ lapb->n2count++;
+ if (lapb->mode & LAPB_EXTENDED) {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S1 TX SABME(1)\n", lapb->token);
+#endif
+ lapb_send_control(lapb, LAPB_SABME, LAPB_POLLON, LAPB_COMMAND);
+ } else {
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S1 TX SABM(1)\n", lapb->token);
+#endif
+ lapb_send_control(lapb, LAPB_SABM, LAPB_POLLON, LAPB_COMMAND);
+ }
+ }
+ break;
+
+ /*
+ * Awaiting disconnection state, send DISC, up to N2 times.
+ */
+ case LAPB_STATE_2:
+ if (lapb->n2count == lapb->n2) {
+ lapb_clear_queues(lapb);
+ lapb->state = LAPB_STATE_0;
+ lapb_disconnect_confirmation(lapb, LAPB_TIMEDOUT);
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S2 -> S0\n", lapb->token);
+#endif
+ return;
+ } else {
+ lapb->n2count++;
+#if LAPB_DEBUG > 1
+ printk(KERN_DEBUG "lapb: (%p) S2 TX DISC(1)\n", lapb->token);
+#endif
+ lapb_send_control(lapb, LAPB_DISC, LAPB_POLLON, LAPB_COMMAND);
+ }
+ break;
+
+ /*
+ * Data transfer state, restransmit I frames, up to N2 times.
+ */
+ case LAPB_STATE_3:
+ if (lapb->n2count == lapb->n2) {
+ lapb_clear_queues(lapb);
+ lapb->state = LAPB_STATE_0;
+ lapb_stop_t2timer(lapb);
+ lapb_disconnect_indication(lapb, LAPB_TIMEDOUT);
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S3 -> S0\n", lapb->token);
+#endif
+ return;
+ } else {
+ lapb->n2count++;
+ lapb_requeue_frames(lapb);
+ }
+ break;
+
+ /*
+ * Frame reject state, restransmit FRMR frames, up to N2 times.
+ */
+ case LAPB_STATE_4:
+ if (lapb->n2count == lapb->n2) {
+ lapb_clear_queues(lapb);
+ lapb->state = LAPB_STATE_0;
+ lapb_disconnect_indication(lapb, LAPB_TIMEDOUT);
+#if LAPB_DEBUG > 0
+ printk(KERN_DEBUG "lapb: (%p) S4 -> S0\n", lapb->token);
+#endif
+ return;
+ } else {
+ lapb->n2count++;
+ lapb_transmit_frmr(lapb);
+ }
+ break;
+ }
+
+ lapb_start_t1timer(lapb);
+}
diff --git a/uClinux-2.4.31-uc0/net/netlink/Makefile b/uClinux-2.4.31-uc0/net/netlink/Makefile
new file mode 100644
index 0000000..15edc53
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/netlink/Makefile
@@ -0,0 +1,20 @@
+#
+# Makefile for the netlink driver.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := netlink.o
+
+export-objs := af_netlink.o
+
+obj-y := af_netlink.o
+
+obj-m :=
+
+obj-$(CONFIG_NETLINK_DEV) += netlink_dev.o
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/netlink/af_netlink.c b/uClinux-2.4.31-uc0/net/netlink/af_netlink.c
new file mode 100644
index 0000000..00e5405
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/netlink/af_netlink.c
@@ -0,0 +1,1306 @@
+/*
+ * NETLINK Kernel-user communication protocol.
+ *
+ * Authors: Alan Cox <alan@redhat.com>
+ * Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Tue Jun 26 14:36:48 MEST 2001 Herbert "herp" Rosmanith
+ * added netlink_proto_exit
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/major.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/stat.h>
+#include <linux/socket.h>
+#include <linux/un.h>
+#include <linux/fcntl.h>
+#include <linux/termios.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <asm/uaccess.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/proc_fs.h>
+#include <linux/smp_lock.h>
+#include <linux/notifier.h>
+#include <linux/jhash.h>
+#include <linux/random.h>
+#include <linux/bitops.h>
+#include <linux/mm.h>
+#include <linux/types.h>
+#include <net/sock.h>
+#include <net/scm.h>
+
+#define Nprintk(a...)
+
+#if defined(CONFIG_NETLINK_DEV) || defined(CONFIG_NETLINK_DEV_MODULE)
+#define NL_EMULATE_DEV
+#endif
+
+struct netlink_opt
+{
+ u32 pid;
+ unsigned int groups;
+ u32 dst_pid;
+ unsigned int dst_groups;
+ unsigned long state;
+ int (*handler)(int unit, struct sk_buff *skb);
+ wait_queue_head_t wait;
+ struct netlink_callback *cb;
+ spinlock_t cb_lock;
+ void (*data_ready)(struct sock *sk, int bytes);
+};
+
+struct nl_pid_hash {
+ struct sock **table;
+ unsigned long rehash_time;
+
+ unsigned int mask;
+ unsigned int shift;
+
+ unsigned int entries;
+ unsigned int max_shift;
+
+ u32 rnd;
+};
+
+struct netlink_table {
+ struct nl_pid_hash hash;
+ struct sock *mc_list;
+};
+
+#define nlk_sk(__sk) ((__sk)->protinfo.af_netlink)
+
+static struct netlink_table *nl_table;
+
+static DECLARE_WAIT_QUEUE_HEAD(nl_table_wait);
+static unsigned int nl_nonroot[MAX_LINKS];
+
+#ifdef NL_EMULATE_DEV
+static struct socket *netlink_kernel[MAX_LINKS];
+#endif
+
+static int netlink_dump(struct sock *sk);
+static void netlink_destroy_callback(struct netlink_callback *cb);
+
+atomic_t netlink_sock_nr;
+
+static rwlock_t nl_table_lock = RW_LOCK_UNLOCKED;
+static atomic_t nl_table_users = ATOMIC_INIT(0);
+
+static struct notifier_block *netlink_chain;
+
+static struct sock **nl_pid_hashfn(struct nl_pid_hash *hash, u32 pid)
+{
+ return &hash->table[jhash_1word(pid, hash->rnd) & hash->mask];
+}
+
+static void netlink_sock_destruct(struct sock *sk)
+{
+ skb_queue_purge(&sk->receive_queue);
+
+ if (!sk->dead) {
+ printk("Freeing alive netlink socket %p\n", sk);
+ return;
+ }
+ BUG_TRAP(atomic_read(&sk->rmem_alloc)==0);
+ BUG_TRAP(atomic_read(&sk->wmem_alloc)==0);
+ BUG_TRAP(sk->protinfo.af_netlink->cb==NULL);
+
+ kfree(sk->protinfo.af_netlink);
+
+ atomic_dec(&netlink_sock_nr);
+#ifdef NETLINK_REFCNT_DEBUG
+ printk(KERN_DEBUG "NETLINK %p released, %d are still alive\n", sk, atomic_read(&netlink_sock_nr));
+#endif
+}
+
+/* This lock without WQ_FLAG_EXCLUSIVE is good on UP and it is _very_ bad on SMP.
+ * Look, when several writers sleep and reader wakes them up, all but one
+ * immediately hit write lock and grab all the cpus. Exclusive sleep solves
+ * this, _but_ remember, it adds useless work on UP machines.
+ */
+
+static void netlink_table_grab(void)
+{
+ write_lock_bh(&nl_table_lock);
+
+ if (atomic_read(&nl_table_users)) {
+ DECLARE_WAITQUEUE(wait, current);
+
+ add_wait_queue_exclusive(&nl_table_wait, &wait);
+ for(;;) {
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ if (atomic_read(&nl_table_users) == 0)
+ break;
+ write_unlock_bh(&nl_table_lock);
+ schedule();
+ write_lock_bh(&nl_table_lock);
+ }
+
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&nl_table_wait, &wait);
+ }
+}
+
+static __inline__ void netlink_table_ungrab(void)
+{
+ write_unlock_bh(&nl_table_lock);
+ wake_up(&nl_table_wait);
+}
+
+static __inline__ void
+netlink_lock_table(void)
+{
+ /* read_lock() synchronizes us to netlink_table_grab */
+
+ read_lock(&nl_table_lock);
+ atomic_inc(&nl_table_users);
+ read_unlock(&nl_table_lock);
+}
+
+static __inline__ void
+netlink_unlock_table(void)
+{
+ if (atomic_dec_and_test(&nl_table_users))
+ wake_up(&nl_table_wait);
+}
+
+static __inline__ struct sock *netlink_lookup(int protocol, u32 pid)
+{
+ struct nl_pid_hash *hash = &nl_table[protocol].hash;
+ struct sock *sk;
+
+ read_lock(&nl_table_lock);
+ for (sk = *nl_pid_hashfn(hash, pid); sk; sk = sk->next) {
+ if (sk->protinfo.af_netlink->pid == pid) {
+ sock_hold(sk);
+ read_unlock(&nl_table_lock);
+ return sk;
+ }
+ }
+
+ read_unlock(&nl_table_lock);
+ return NULL;
+}
+
+static inline struct sock **nl_pid_hash_alloc(size_t size)
+{
+ if (size <= PAGE_SIZE)
+ return kmalloc(size, GFP_ATOMIC);
+ else
+ return (struct sock **)
+ __get_free_pages(GFP_ATOMIC, get_order(size));
+}
+
+static inline void nl_pid_hash_free(struct sock **table, size_t size)
+{
+ if (size <= PAGE_SIZE)
+ kfree(table);
+ else
+ free_pages((unsigned long)table, get_order(size));
+}
+
+static int nl_pid_hash_rehash(struct nl_pid_hash *hash, int grow)
+{
+ unsigned int omask, mask, shift;
+ size_t osize, size;
+ struct sock **otable, **table;
+ int i;
+
+ omask = mask = hash->mask;
+ osize = size = (mask + 1) * sizeof(*table);
+ shift = hash->shift;
+
+ if (grow) {
+ if (++shift > hash->max_shift)
+ return 0;
+ mask = mask * 2 + 1;
+ size *= 2;
+ }
+
+ table = nl_pid_hash_alloc(size);
+ if (!table)
+ return 0;
+
+ memset(table, 0, size);
+ otable = hash->table;
+ hash->table = table;
+ hash->mask = mask;
+ hash->shift = shift;
+ get_random_bytes(&hash->rnd, sizeof(hash->rnd));
+
+ for (i = 0; i <= omask; i++) {
+ struct sock *sk;
+ struct sock *tmp, **head;
+
+ for (sk = otable[i]; sk; sk = tmp) {
+ tmp = sk->next;
+ head = nl_pid_hashfn(hash, nlk_sk(sk)->pid);
+ sk->next = *head;
+ *head = sk;
+ }
+ }
+
+ nl_pid_hash_free(otable, osize);
+ hash->rehash_time = jiffies + 10 * 60 * HZ;
+ return 1;
+}
+
+static inline int nl_pid_hash_dilute(struct nl_pid_hash *hash, int len)
+{
+ int avg = hash->entries >> hash->shift;
+
+ if (unlikely(avg > 1) && nl_pid_hash_rehash(hash, 1))
+ return 1;
+
+ if (unlikely(len > avg) && time_after(jiffies, hash->rehash_time)) {
+ nl_pid_hash_rehash(hash, 0);
+ return 1;
+ }
+
+ return 0;
+}
+
+extern struct proto_ops netlink_ops;
+
+static int netlink_insert(struct sock *sk, u32 pid)
+{
+ struct nl_pid_hash *hash = &nl_table[sk->protocol].hash;
+ struct sock **head;
+ int err = -EADDRINUSE;
+ struct sock *osk;
+ int len;
+
+ netlink_table_grab();
+ head = nl_pid_hashfn(hash, pid);
+ len = 0;
+ for (osk = *head; osk; osk = osk->next) {
+ if (osk->protinfo.af_netlink->pid == pid)
+ break;
+ len++;
+ }
+ if (osk)
+ goto err;
+
+ err = -EBUSY;
+ if (nlk_sk(sk)->pid)
+ goto err;
+
+ err = -ENOMEM;
+ if (BITS_PER_LONG > 32 && unlikely(hash->entries >= UINT_MAX))
+ goto err;
+
+ if (len && nl_pid_hash_dilute(hash, len))
+ head = nl_pid_hashfn(hash, pid);
+ hash->entries++;
+ nlk_sk(sk)->pid = pid;
+ sk->next = *head;
+ *head = sk;
+ sock_hold(sk);
+ err = 0;
+
+err:
+ netlink_table_ungrab();
+ return err;
+}
+
+static void netlink_remove(struct sock *sk)
+{
+ struct sock **skp;
+ struct netlink_table *table = &nl_table[sk->protocol];
+ struct nl_pid_hash *hash = &table->hash;
+ u32 pid = nlk_sk(sk)->pid;
+
+ netlink_table_grab();
+ hash->entries--;
+ for (skp = nl_pid_hashfn(hash, pid); *skp; skp = &((*skp)->next)) {
+ if (*skp == sk) {
+ *skp = sk->next;
+ __sock_put(sk);
+ break;
+ }
+ }
+ if (!nlk_sk(sk)->groups)
+ goto out;
+ for (skp = &table->mc_list; *skp; skp = &((*skp)->bind_next)) {
+ if (*skp == sk) {
+ *skp = sk->bind_next;
+ break;
+ }
+ }
+out:
+ netlink_table_ungrab();
+}
+
+static int netlink_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+
+ sock->state = SS_UNCONNECTED;
+
+ if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)
+ return -ESOCKTNOSUPPORT;
+
+ if (protocol<0 || protocol >= MAX_LINKS)
+ return -EPROTONOSUPPORT;
+
+ sock->ops = &netlink_ops;
+
+ sk = sk_alloc(PF_NETLINK, GFP_KERNEL, 1);
+ if (!sk)
+ return -ENOMEM;
+
+ sock_init_data(sock,sk);
+
+ sk->protinfo.af_netlink = kmalloc(sizeof(struct netlink_opt), GFP_KERNEL);
+ if (sk->protinfo.af_netlink == NULL) {
+ sk_free(sk);
+ return -ENOMEM;
+ }
+ memset(sk->protinfo.af_netlink, 0, sizeof(struct netlink_opt));
+
+ spin_lock_init(&sk->protinfo.af_netlink->cb_lock);
+ init_waitqueue_head(&sk->protinfo.af_netlink->wait);
+ sk->destruct = netlink_sock_destruct;
+ atomic_inc(&netlink_sock_nr);
+
+ sk->protocol=protocol;
+ return 0;
+}
+
+static int netlink_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ if (!sk)
+ return 0;
+
+ netlink_remove(sk);
+
+ spin_lock(&sk->protinfo.af_netlink->cb_lock);
+ if (sk->protinfo.af_netlink->cb) {
+ sk->protinfo.af_netlink->cb->done(sk->protinfo.af_netlink->cb);
+ netlink_destroy_callback(sk->protinfo.af_netlink->cb);
+ sk->protinfo.af_netlink->cb = NULL;
+ __sock_put(sk);
+ }
+ spin_unlock(&sk->protinfo.af_netlink->cb_lock);
+
+ /* OK. Socket is unlinked, and, therefore,
+ no new packets will arrive */
+
+ sock_orphan(sk);
+ sock->sk = NULL;
+ wake_up_interruptible_all(&sk->protinfo.af_netlink->wait);
+
+ skb_queue_purge(&sk->write_queue);
+
+ if (sk->protinfo.af_netlink->pid && !sk->protinfo.af_netlink->groups) {
+ struct netlink_notify n = { protocol:sk->protocol,
+ pid:sk->protinfo.af_netlink->pid };
+ notifier_call_chain(&netlink_chain, NETLINK_URELEASE, &n);
+ }
+
+ sock_put(sk);
+ return 0;
+}
+
+static int netlink_autobind(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct nl_pid_hash *hash = &nl_table[sk->protocol].hash;
+ struct sock *osk;
+ s32 pid = current->pid;
+ int err;
+ static s32 rover = -4097;
+
+retry:
+ cond_resched();
+ netlink_table_grab();
+ for (osk = *nl_pid_hashfn(hash, pid); osk; osk = osk->next) {
+ if (osk->protinfo.af_netlink->pid == pid) {
+ /* Bind collision, search negative pid values. */
+ pid = rover--;
+ if (rover > -4097)
+ rover = -4097;
+ netlink_table_ungrab();
+ goto retry;
+ }
+ }
+ netlink_table_ungrab();
+
+ err = netlink_insert(sk, pid);
+ if (err == -EADDRINUSE)
+ goto retry;
+ return 0;
+}
+
+static inline int netlink_capable(struct socket *sock, unsigned int flag)
+{
+ return (nl_nonroot[sock->sk->protocol] & flag) || capable(CAP_NET_ADMIN);
+}
+
+static int netlink_bind(struct socket *sock, struct sockaddr *addr, int addr_len)
+{
+ struct sock *sk = sock->sk;
+ struct sock **skp;
+ int err;
+ struct netlink_opt *nlk = nlk_sk(sk);
+ struct sockaddr_nl *nladdr=(struct sockaddr_nl *)addr;
+
+ if (nladdr->nl_family != AF_NETLINK)
+ return -EINVAL;
+
+ /* Only superuser is allowed to listen multicasts */
+ if (nladdr->nl_groups && !netlink_capable(sock, NL_NONROOT_RECV))
+ return -EPERM;
+
+ if (sk->protinfo.af_netlink->pid) {
+ if (nladdr->nl_pid != sk->protinfo.af_netlink->pid)
+ return -EINVAL;
+ } else {
+ err = nladdr->nl_pid ?
+ netlink_insert(sk, nladdr->nl_pid) :
+ netlink_autobind(sock);
+ if (err)
+ return err;
+ }
+
+ if (!nladdr->nl_groups && !nlk->groups)
+ return 0;
+
+ netlink_table_grab();
+ skp = &nl_table[sk->protocol].mc_list;
+ if (nlk->groups && !nladdr->nl_groups) {
+ for (; *skp; skp = &((*skp)->bind_next)) {
+ if (*skp == sk) {
+ *skp = sk->bind_next;
+ break;
+ }
+ }
+ } else if (!nlk->groups && nladdr->nl_groups) {
+ sk->bind_next = *skp;
+ *skp = sk;
+ }
+ nlk->groups = nladdr->nl_groups;
+ netlink_table_ungrab();
+
+ return 0;
+}
+
+static int netlink_connect(struct socket *sock, struct sockaddr *addr,
+ int alen, int flags)
+{
+ int err = 0;
+ struct sock *sk = sock->sk;
+ struct sockaddr_nl *nladdr=(struct sockaddr_nl*)addr;
+
+ if (addr->sa_family == AF_UNSPEC) {
+ sk->protinfo.af_netlink->dst_pid = 0;
+ sk->protinfo.af_netlink->dst_groups = 0;
+ return 0;
+ }
+ if (addr->sa_family != AF_NETLINK)
+ return -EINVAL;
+
+ /* Only superuser is allowed to send multicasts */
+ if (nladdr->nl_groups && !netlink_capable(sock, NL_NONROOT_SEND))
+ return -EPERM;
+
+ if (!sk->protinfo.af_netlink->pid)
+ err = netlink_autobind(sock);
+
+ if (err == 0) {
+ sk->protinfo.af_netlink->dst_pid = nladdr->nl_pid;
+ sk->protinfo.af_netlink->dst_groups = nladdr->nl_groups;
+ }
+
+ return 0;
+}
+
+static int netlink_getname(struct socket *sock, struct sockaddr *addr, int *addr_len, int peer)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_nl *nladdr=(struct sockaddr_nl *)addr;
+
+ nladdr->nl_family = AF_NETLINK;
+ nladdr->nl_pad = 0;
+ *addr_len = sizeof(*nladdr);
+
+ if (peer) {
+ nladdr->nl_pid = sk->protinfo.af_netlink->dst_pid;
+ nladdr->nl_groups = sk->protinfo.af_netlink->dst_groups;
+ } else {
+ nladdr->nl_pid = sk->protinfo.af_netlink->pid;
+ nladdr->nl_groups = sk->protinfo.af_netlink->groups;
+ }
+ return 0;
+}
+
+static void netlink_overrun(struct sock *sk)
+{
+ if (!test_and_set_bit(0, &sk->protinfo.af_netlink->state)) {
+ sk->err = ENOBUFS;
+ sk->error_report(sk);
+ }
+}
+
+int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)
+{
+ struct sock *sk;
+ int len = skb->len;
+ int protocol = ssk->protocol;
+ long timeo;
+ DECLARE_WAITQUEUE(wait, current);
+
+ timeo = sock_sndtimeo(ssk, nonblock);
+
+retry:
+ sk = netlink_lookup(protocol, pid);
+ if (sk == NULL)
+ goto no_dst;
+
+ /* Don't bother queuing skb if kernel socket has no input function */
+ if (sk->protinfo.af_netlink->pid == 0 &&
+ !sk->protinfo.af_netlink->data_ready)
+ goto no_dst;
+
+#ifdef NL_EMULATE_DEV
+ if (sk->protinfo.af_netlink->handler) {
+ skb_orphan(skb);
+ len = sk->protinfo.af_netlink->handler(protocol, skb);
+ sock_put(sk);
+ return len;
+ }
+#endif
+
+ if (atomic_read(&sk->rmem_alloc) > sk->rcvbuf ||
+ test_bit(0, &sk->protinfo.af_netlink->state)) {
+ if (!timeo) {
+ if (ssk->protinfo.af_netlink->pid == 0)
+ netlink_overrun(sk);
+ sock_put(sk);
+ kfree_skb(skb);
+ return -EAGAIN;
+ }
+
+ __set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&sk->protinfo.af_netlink->wait, &wait);
+
+ if ((atomic_read(&sk->rmem_alloc) > sk->rcvbuf ||
+ test_bit(0, &sk->protinfo.af_netlink->state)) &&
+ !sk->dead)
+ timeo = schedule_timeout(timeo);
+
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&sk->protinfo.af_netlink->wait, &wait);
+ sock_put(sk);
+
+ if (signal_pending(current)) {
+ kfree_skb(skb);
+ return sock_intr_errno(timeo);
+ }
+ goto retry;
+ }
+
+ skb_orphan(skb);
+ skb_set_owner_r(skb, sk);
+ skb_queue_tail(&sk->receive_queue, skb);
+ sk->data_ready(sk, len);
+ sock_put(sk);
+ return len;
+
+no_dst:
+ kfree_skb(skb);
+ return -ECONNREFUSED;
+}
+
+static __inline__ int netlink_broadcast_deliver(struct sock *sk, struct sk_buff *skb)
+{
+#ifdef NL_EMULATE_DEV
+ if (sk->protinfo.af_netlink->handler) {
+ skb_orphan(skb);
+ sk->protinfo.af_netlink->handler(sk->protocol, skb);
+ return 0;
+ } else
+#endif
+ if (atomic_read(&sk->rmem_alloc) <= sk->rcvbuf &&
+ !test_bit(0, &sk->protinfo.af_netlink->state)) {
+ skb_orphan(skb);
+ skb_set_owner_r(skb, sk);
+ skb_queue_tail(&sk->receive_queue, skb);
+ sk->data_ready(sk, skb->len);
+ return 0;
+ }
+ return -1;
+}
+
+struct netlink_broadcast_data {
+ struct sock *exclude_sk;
+ u32 pid;
+ u32 group;
+ int failure;
+ int allocation;
+ struct sk_buff *skb, *skb2;
+};
+
+static inline int do_one_broadcast(struct sock *sk,
+ struct netlink_broadcast_data *p)
+{
+ struct netlink_opt *nlk = nlk_sk(sk);
+ int val;
+
+ if (p->exclude_sk == sk)
+ goto out;
+
+ if (nlk->pid == p->pid || !(nlk->groups & p->group))
+ goto out;
+
+ if (p->failure) {
+ netlink_overrun(sk);
+ goto out;
+ }
+
+ sock_hold(sk);
+ if (p->skb2 == NULL) {
+ if (atomic_read(&p->skb->users) != 1) {
+ p->skb2 = skb_clone(p->skb, p->allocation);
+ } else {
+ p->skb2 = p->skb;
+ atomic_inc(&p->skb->users);
+ }
+ }
+ if (p->skb2 == NULL) {
+ netlink_overrun(sk);
+ /* Clone failed. Notify ALL listeners. */
+ p->failure = 1;
+ } else if ((val = netlink_broadcast_deliver(sk, p->skb2)) < 0) {
+ netlink_overrun(sk);
+ } else
+ p->skb2 = NULL;
+ sock_put(sk);
+
+out:
+ return 0;
+}
+
+void netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid,
+ u32 group, int allocation)
+{
+ struct netlink_broadcast_data info;
+ struct sock *sk;
+
+ info.exclude_sk = ssk;
+ info.pid = pid;
+ info.group = group;
+ info.failure = 0;
+ info.allocation = allocation;
+ info.skb = skb;
+ info.skb2 = NULL;
+
+ /* While we sleep in clone, do not allow to change socket list */
+
+ netlink_lock_table();
+
+ for (sk = nl_table[ssk->protocol].mc_list; sk; sk = sk->bind_next)
+ do_one_broadcast(sk, &info);
+
+ netlink_unlock_table();
+
+ if (info.skb2)
+ kfree_skb(info.skb2);
+ kfree_skb(skb);
+}
+
+struct netlink_set_err_data {
+ struct sock *exclude_sk;
+ u32 pid;
+ u32 group;
+ int code;
+};
+
+static inline int do_one_set_err(struct sock *sk,
+ struct netlink_set_err_data *p)
+{
+ struct netlink_opt *nlk = nlk_sk(sk);
+
+ if (sk == p->exclude_sk)
+ goto out;
+
+ if (nlk->pid == p->pid || !(nlk->groups & p->group))
+ goto out;
+
+ sk->err = p->code;
+ sk->error_report(sk);
+out:
+ return 0;
+}
+
+void netlink_set_err(struct sock *ssk, u32 pid, u32 group, int code)
+{
+ struct netlink_set_err_data info;
+ struct sock *sk;
+
+ info.exclude_sk = ssk;
+ info.pid = pid;
+ info.group = group;
+ info.code = code;
+
+ read_lock(&nl_table_lock);
+ for (sk = nl_table[ssk->protocol].mc_list; sk; sk = sk->bind_next)
+ do_one_set_err(sk, &info);
+ read_unlock(&nl_table_lock);
+}
+
+static inline void netlink_rcv_wake(struct sock *sk)
+{
+ if (skb_queue_len(&sk->receive_queue) == 0)
+ clear_bit(0, &sk->protinfo.af_netlink->state);
+ if (!test_bit(0, &sk->protinfo.af_netlink->state))
+ wake_up_interruptible(&sk->protinfo.af_netlink->wait);
+}
+
+static int netlink_sendmsg(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_nl *addr=msg->msg_name;
+ u32 dst_pid;
+ u32 dst_groups;
+ struct sk_buff *skb;
+ int err;
+
+ if (msg->msg_flags&MSG_OOB)
+ return -EOPNOTSUPP;
+
+ if (msg->msg_namelen) {
+ if (addr->nl_family != AF_NETLINK)
+ return -EINVAL;
+ dst_pid = addr->nl_pid;
+ dst_groups = addr->nl_groups;
+ if (dst_groups && !netlink_capable(sock, NL_NONROOT_SEND))
+ return -EPERM;
+ } else {
+ dst_pid = sk->protinfo.af_netlink->dst_pid;
+ dst_groups = sk->protinfo.af_netlink->dst_groups;
+ }
+
+ if (!sk->protinfo.af_netlink->pid) {
+ err = netlink_autobind(sock);
+ if (err)
+ goto out;
+ }
+
+ err = -EMSGSIZE;
+ if ((unsigned)len > sk->sndbuf-32)
+ goto out;
+ err = -ENOBUFS;
+ skb = alloc_skb(len, GFP_KERNEL);
+ if (skb==NULL)
+ goto out;
+
+ NETLINK_CB(skb).pid = sk->protinfo.af_netlink->pid;
+ NETLINK_CB(skb).groups = sk->protinfo.af_netlink->groups;
+ NETLINK_CB(skb).dst_pid = dst_pid;
+ NETLINK_CB(skb).dst_groups = dst_groups;
+ memcpy(NETLINK_CREDS(skb), &scm->creds, sizeof(struct ucred));
+
+ /* What can I do? Netlink is asynchronous, so that
+ we will have to save current capabilities to
+ check them, when this message will be delivered
+ to corresponding kernel module. --ANK (980802)
+ */
+ NETLINK_CB(skb).eff_cap = current->cap_effective;
+
+ err = -EFAULT;
+ if (memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len)) {
+ kfree_skb(skb);
+ goto out;
+ }
+
+ if (dst_groups) {
+ atomic_inc(&skb->users);
+ netlink_broadcast(sk, skb, dst_pid, dst_groups, GFP_KERNEL);
+ }
+ err = netlink_unicast(sk, skb, dst_pid, msg->msg_flags&MSG_DONTWAIT);
+
+out:
+ return err;
+}
+
+static int netlink_recvmsg(struct socket *sock, struct msghdr *msg, int len,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ int noblock = flags&MSG_DONTWAIT;
+ int copied;
+ struct sk_buff *skb;
+ int err;
+
+ if (flags&MSG_OOB)
+ return -EOPNOTSUPP;
+
+ copied = 0;
+
+ skb = skb_recv_datagram(sk,flags,noblock,&err);
+ if (skb==NULL)
+ goto out;
+
+ msg->msg_namelen = 0;
+
+ copied = skb->len;
+ if (len < copied) {
+ msg->msg_flags |= MSG_TRUNC;
+ copied = len;
+ }
+
+ skb->h.raw = skb->data;
+ err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+
+ if (msg->msg_name) {
+ struct sockaddr_nl *addr = (struct sockaddr_nl*)msg->msg_name;
+ addr->nl_family = AF_NETLINK;
+ addr->nl_pad = 0;
+ addr->nl_pid = NETLINK_CB(skb).pid;
+ addr->nl_groups = NETLINK_CB(skb).dst_groups;
+ msg->msg_namelen = sizeof(*addr);
+ }
+
+ scm->creds = *NETLINK_CREDS(skb);
+ skb_free_datagram(sk, skb);
+
+ if (sk->protinfo.af_netlink->cb
+ && atomic_read(&sk->rmem_alloc) <= sk->rcvbuf/2)
+ netlink_dump(sk);
+
+out:
+ netlink_rcv_wake(sk);
+ return err ? : copied;
+}
+
+void netlink_data_ready(struct sock *sk, int len)
+{
+ if (sk->protinfo.af_netlink->data_ready)
+ sk->protinfo.af_netlink->data_ready(sk, len);
+ netlink_rcv_wake(sk);
+}
+
+/*
+ * We export these functions to other modules. They provide a
+ * complete set of kernel non-blocking support for message
+ * queueing.
+ */
+
+struct sock *
+netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len))
+{
+ struct socket *sock;
+ struct sock *sk;
+
+ if (!nl_table)
+ return NULL;
+
+ if (unit<0 || unit>=MAX_LINKS)
+ return NULL;
+
+ if (!(sock = sock_alloc()))
+ return NULL;
+
+ sock->type = SOCK_RAW;
+
+ if (netlink_create(sock, unit) < 0) {
+ sock_release(sock);
+ return NULL;
+ }
+ sk = sock->sk;
+ sk->data_ready = netlink_data_ready;
+ if (input)
+ sk->protinfo.af_netlink->data_ready = input;
+
+ netlink_insert(sk, 0);
+ return sk;
+}
+
+void netlink_set_nonroot(int protocol, unsigned int flags)
+{
+ if ((unsigned int)protocol < MAX_LINKS)
+ nl_nonroot[protocol] = flags;
+}
+
+static void netlink_destroy_callback(struct netlink_callback *cb)
+{
+ if (cb->skb)
+ kfree_skb(cb->skb);
+ kfree(cb);
+}
+
+/*
+ * It looks a bit ugly.
+ * It would be better to create kernel thread.
+ */
+
+static int netlink_dump(struct sock *sk)
+{
+ struct netlink_callback *cb;
+ struct sk_buff *skb;
+ struct nlmsghdr *nlh;
+ int len;
+
+ skb = sock_rmalloc(sk, NLMSG_GOODSIZE, 0, GFP_KERNEL);
+ if (!skb)
+ return -ENOBUFS;
+
+ spin_lock(&sk->protinfo.af_netlink->cb_lock);
+
+ cb = sk->protinfo.af_netlink->cb;
+ if (cb == NULL) {
+ spin_unlock(&sk->protinfo.af_netlink->cb_lock);
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ len = cb->dump(skb, cb);
+
+ if (len > 0) {
+ sock_hold(sk);
+ spin_unlock(&sk->protinfo.af_netlink->cb_lock);
+ skb_queue_tail(&sk->receive_queue, skb);
+ sk->data_ready(sk, len);
+ sock_put(sk);
+ return 0;
+ }
+
+ nlh = __nlmsg_put(skb, NETLINK_CB(cb->skb).pid, cb->nlh->nlmsg_seq, NLMSG_DONE, sizeof(int));
+ nlh->nlmsg_flags |= NLM_F_MULTI;
+ memcpy(NLMSG_DATA(nlh), &len, sizeof(len));
+ skb_queue_tail(&sk->receive_queue, skb);
+ sk->data_ready(sk, skb->len);
+
+ cb->done(cb);
+ sk->protinfo.af_netlink->cb = NULL;
+ spin_unlock(&sk->protinfo.af_netlink->cb_lock);
+
+ netlink_destroy_callback(cb);
+ sock_put(sk);
+ return 0;
+}
+
+int netlink_dump_start(struct sock *ssk, struct sk_buff *skb,
+ struct nlmsghdr *nlh,
+ int (*dump)(struct sk_buff *skb, struct netlink_callback*),
+ int (*done)(struct netlink_callback*))
+{
+ struct netlink_callback *cb;
+ struct sock *sk;
+
+ cb = kmalloc(sizeof(*cb), GFP_KERNEL);
+ if (cb == NULL)
+ return -ENOBUFS;
+
+ memset(cb, 0, sizeof(*cb));
+ cb->dump = dump;
+ cb->done = done;
+ cb->nlh = nlh;
+ atomic_inc(&skb->users);
+ cb->skb = skb;
+
+ sk = netlink_lookup(ssk->protocol, NETLINK_CB(skb).pid);
+ if (sk == NULL) {
+ netlink_destroy_callback(cb);
+ return -ECONNREFUSED;
+ }
+ /* A dump is in progress... */
+ spin_lock(&sk->protinfo.af_netlink->cb_lock);
+ if (sk->protinfo.af_netlink->cb) {
+ spin_unlock(&sk->protinfo.af_netlink->cb_lock);
+ netlink_destroy_callback(cb);
+ sock_put(sk);
+ return -EBUSY;
+ }
+ sk->protinfo.af_netlink->cb = cb;
+ spin_unlock(&sk->protinfo.af_netlink->cb_lock);
+
+ netlink_dump(sk);
+ return 0;
+}
+
+void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err)
+{
+ struct sk_buff *skb;
+ struct nlmsghdr *rep;
+ struct nlmsgerr *errmsg;
+ int size;
+
+ if (err == 0)
+ size = NLMSG_SPACE(sizeof(struct nlmsgerr));
+ else
+ size = NLMSG_SPACE(4 + NLMSG_ALIGN(nlh->nlmsg_len));
+
+ skb = alloc_skb(size, GFP_KERNEL);
+ if (!skb) {
+ struct sock *sk;
+
+ sk = netlink_lookup(in_skb->sk->protocol,
+ NETLINK_CB(in_skb).pid);
+ if (sk) {
+ sk->err = ENOBUFS;
+ sk->error_report(sk);
+ sock_put(sk);
+ }
+ }
+
+ rep = __nlmsg_put(skb, NETLINK_CB(in_skb).pid, nlh->nlmsg_seq,
+ NLMSG_ERROR, sizeof(struct nlmsgerr));
+ errmsg = NLMSG_DATA(rep);
+ errmsg->error = err;
+ memcpy(&errmsg->msg, nlh, err ? nlh->nlmsg_len : sizeof(struct nlmsghdr));
+ netlink_unicast(in_skb->sk, skb, NETLINK_CB(in_skb).pid, MSG_DONTWAIT);
+}
+
+
+#ifdef NL_EMULATE_DEV
+
+static rwlock_t nl_emu_lock = RW_LOCK_UNLOCKED;
+
+/*
+ * Backward compatibility.
+ */
+
+int netlink_attach(int unit, int (*function)(int, struct sk_buff *skb))
+{
+ struct sock *sk = netlink_kernel_create(unit, NULL);
+ if (sk == NULL)
+ return -ENOBUFS;
+ sk->protinfo.af_netlink->handler = function;
+ write_lock_bh(&nl_emu_lock);
+ netlink_kernel[unit] = sk->socket;
+ write_unlock_bh(&nl_emu_lock);
+ return 0;
+}
+
+void netlink_detach(int unit)
+{
+ struct socket *sock;
+
+ write_lock_bh(&nl_emu_lock);
+ sock = netlink_kernel[unit];
+ netlink_kernel[unit] = NULL;
+ write_unlock_bh(&nl_emu_lock);
+
+ sock_release(sock);
+}
+
+int netlink_post(int unit, struct sk_buff *skb)
+{
+ struct socket *sock;
+
+ read_lock(&nl_emu_lock);
+ sock = netlink_kernel[unit];
+ if (sock) {
+ struct sock *sk = sock->sk;
+ memset(skb->cb, 0, sizeof(skb->cb));
+ sock_hold(sk);
+ read_unlock(&nl_emu_lock);
+
+ netlink_broadcast(sk, skb, 0, ~0, GFP_ATOMIC);
+
+ sock_put(sk);
+ return 0;
+ }
+ read_unlock(&nl_emu_lock);
+ return -EUNATCH;
+}
+
+#endif
+
+
+#ifdef CONFIG_PROC_FS
+struct nl_seq_iter {
+ int link;
+ int hash_idx;
+};
+
+static int netlink_read_proc(char *buffer, char **start, off_t offset,
+ int length, int *eof, void *data)
+{
+ off_t pos=0;
+ off_t begin=0;
+ int len=0;
+ int i, j;
+ struct sock *s;
+
+ len+= sprintf(buffer,"sk Eth Pid Groups "
+ "Rmem Wmem Dump Locks\n");
+
+ for (i=0; i<MAX_LINKS; i++) {
+ struct nl_pid_hash *hash = &nl_table[i].hash;
+
+ read_lock(&nl_table_lock);
+ for (j = 0; j <= hash->mask; j++) {
+ for (s = hash->table[j]; s; s = s->next) {
+ len += sprintf(buffer + len,
+ "%p %-3d %-6d %08x %-8d %-8d %p %d",
+ s,
+ s->protocol,
+ s->protinfo.af_netlink->pid,
+ s->protinfo.af_netlink->groups,
+ atomic_read(&s->rmem_alloc),
+ atomic_read(&s->wmem_alloc),
+ s->protinfo.af_netlink->cb,
+ atomic_read(&s->refcnt));
+
+ buffer[len++]='\n';
+
+ pos = begin + len;
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+ if (pos > offset + length) {
+ read_unlock(&nl_table_lock);
+ goto done;
+ }
+ }
+ }
+ read_unlock(&nl_table_lock);
+ }
+ *eof = 1;
+
+done:
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ if(len<0)
+ len=0;
+ return len;
+}
+#endif
+
+int netlink_register_notifier(struct notifier_block *nb)
+{
+ return notifier_chain_register(&netlink_chain, nb);
+}
+
+int netlink_unregister_notifier(struct notifier_block *nb)
+{
+ return notifier_chain_unregister(&netlink_chain, nb);
+}
+
+struct proto_ops netlink_ops = {
+ family: PF_NETLINK,
+
+ release: netlink_release,
+ bind: netlink_bind,
+ connect: netlink_connect,
+ socketpair: sock_no_socketpair,
+ accept: sock_no_accept,
+ getname: netlink_getname,
+ poll: datagram_poll,
+ ioctl: sock_no_ioctl,
+ listen: sock_no_listen,
+ shutdown: sock_no_shutdown,
+ setsockopt: sock_no_setsockopt,
+ getsockopt: sock_no_getsockopt,
+ sendmsg: netlink_sendmsg,
+ recvmsg: netlink_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+struct net_proto_family netlink_family_ops = {
+ PF_NETLINK,
+ netlink_create
+};
+
+extern void netlink_skb_parms_too_large(void);
+
+int __init netlink_proto_init(void)
+{
+ struct sk_buff *dummy_skb;
+ int i;
+ unsigned long max;
+ unsigned int order;
+
+ if (sizeof(struct netlink_skb_parms) > sizeof(dummy_skb->cb))
+ netlink_skb_parms_too_large();
+
+ nl_table = kmalloc(sizeof(*nl_table) * MAX_LINKS, GFP_KERNEL);
+ if (!nl_table) {
+enomem:
+ printk(KERN_CRIT "netlink_init: Cannot allocate nl_table\n");
+ return -ENOMEM;
+ }
+
+ memset(nl_table, 0, sizeof(*nl_table) * MAX_LINKS);
+
+ if (num_physpages >= (128 * 1024))
+ max = num_physpages >> (21 - PAGE_SHIFT);
+ else
+ max = num_physpages >> (23 - PAGE_SHIFT);
+
+ for (order = 0; (1UL << order) < max + 1; order++)
+ ;
+ order += PAGE_SHIFT - 1;
+ max = (1UL << order) / sizeof(struct sock *);
+ if (max > UINT_MAX)
+ max = UINT_MAX;
+ for (order = 0; (1UL << order) < max + 1; order++)
+ ;
+ order--;
+
+ for (i = 0; i < MAX_LINKS; i++) {
+ struct nl_pid_hash *hash = &nl_table[i].hash;
+
+ hash->table = nl_pid_hash_alloc(1 * sizeof(*hash->table));
+ if (!hash->table) {
+ while (i-- > 0)
+ nl_pid_hash_free(nl_table[i].hash.table,
+ 1 * sizeof(*hash->table));
+ kfree(nl_table);
+ goto enomem;
+ }
+ memset(hash->table, 0, 1 * sizeof(*hash->table));
+ hash->max_shift = order;
+ hash->shift = 0;
+ hash->mask = 0;
+ hash->rehash_time = jiffies;
+ }
+
+ sock_register(&netlink_family_ops);
+#ifdef CONFIG_PROC_FS
+ create_proc_read_entry("net/netlink", 0, 0, netlink_read_proc, NULL);
+#endif
+ return 0;
+}
+
+static void __exit netlink_proto_exit(void)
+{
+ sock_unregister(PF_NETLINK);
+ remove_proc_entry("net/netlink", NULL);
+ kfree(nl_table);
+ nl_table = NULL;
+}
+
+#ifdef MODULE
+module_init(netlink_proto_init);
+#endif
+module_exit(netlink_proto_exit);
diff --git a/uClinux-2.4.31-uc0/net/netlink/netlink_dev.c b/uClinux-2.4.31-uc0/net/netlink/netlink_dev.c
new file mode 100644
index 0000000..a1af93c
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/netlink/netlink_dev.c
@@ -0,0 +1,223 @@
+/*
+ * NETLINK An implementation of a loadable kernel mode driver providing
+ * multiple kernel/user space bidirectional communications links.
+ *
+ * Author: Alan Cox <alan@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Now netlink devices are emulated on the top of netlink sockets
+ * by compatibility reasons. Remove this file after a period. --ANK
+ *
+ */
+
+#include <linux/module.h>
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+#include <linux/netlink.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/smp_lock.h>
+
+#include <asm/bitops.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+
+static long open_map;
+static struct socket *netlink_user[MAX_LINKS];
+
+/*
+ * Device operations
+ */
+
+static unsigned int netlink_poll(struct file *file, poll_table * wait)
+{
+ struct socket *sock = netlink_user[MINOR(file->f_dentry->d_inode->i_rdev)];
+
+ if (sock->ops->poll==NULL)
+ return 0;
+ return sock->ops->poll(file, sock, wait);
+}
+
+/*
+ * Write a message to the kernel side of a communication link
+ */
+
+static ssize_t netlink_write(struct file * file, const char * buf,
+ size_t count, loff_t *pos)
+{
+ struct inode *inode = file->f_dentry->d_inode;
+ struct socket *sock = netlink_user[MINOR(inode->i_rdev)];
+ struct msghdr msg;
+ struct iovec iov;
+
+ iov.iov_base = (void*)buf;
+ iov.iov_len = count;
+ msg.msg_name=NULL;
+ msg.msg_namelen=0;
+ msg.msg_controllen=0;
+ msg.msg_flags=0;
+ msg.msg_iov=&iov;
+ msg.msg_iovlen=1;
+
+ return sock_sendmsg(sock, &msg, count);
+}
+
+/*
+ * Read a message from the kernel side of the communication link
+ */
+
+static ssize_t netlink_read(struct file * file, char * buf,
+ size_t count, loff_t *pos)
+{
+ struct inode *inode = file->f_dentry->d_inode;
+ struct socket *sock = netlink_user[MINOR(inode->i_rdev)];
+ struct msghdr msg;
+ struct iovec iov;
+
+ iov.iov_base = buf;
+ iov.iov_len = count;
+ msg.msg_name=NULL;
+ msg.msg_namelen=0;
+ msg.msg_controllen=0;
+ msg.msg_flags=0;
+ msg.msg_iov=&iov;
+ msg.msg_iovlen=1;
+ if (file->f_flags&O_NONBLOCK)
+ msg.msg_flags=MSG_DONTWAIT;
+
+ return sock_recvmsg(sock, &msg, count, msg.msg_flags);
+}
+
+static int netlink_open(struct inode * inode, struct file * file)
+{
+ unsigned int minor = MINOR(inode->i_rdev);
+ struct socket *sock;
+ struct sockaddr_nl nladdr;
+ int err;
+
+ if (minor>=MAX_LINKS)
+ return -ENODEV;
+ if (test_and_set_bit(minor, &open_map))
+ return -EBUSY;
+
+ err = sock_create(PF_NETLINK, SOCK_RAW, minor, &sock);
+ if (err < 0)
+ goto out;
+
+ memset(&nladdr, 0, sizeof(nladdr));
+ nladdr.nl_family = AF_NETLINK;
+ nladdr.nl_groups = ~0;
+ if ((err = sock->ops->bind(sock, (struct sockaddr*)&nladdr, sizeof(nladdr))) < 0) {
+ sock_release(sock);
+ goto out;
+ }
+
+ netlink_user[minor] = sock;
+ return 0;
+
+out:
+ clear_bit(minor, &open_map);
+ return err;
+}
+
+static int netlink_release(struct inode * inode, struct file * file)
+{
+ unsigned int minor = MINOR(inode->i_rdev);
+ struct socket *sock;
+
+ sock = netlink_user[minor];
+ netlink_user[minor] = NULL;
+ clear_bit(minor, &open_map);
+ sock_release(sock);
+ return 0;
+}
+
+
+static int netlink_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ unsigned int minor = MINOR(inode->i_rdev);
+ int retval = 0;
+
+ if (minor >= MAX_LINKS)
+ return -ENODEV;
+ switch ( cmd ) {
+ default:
+ retval = -EINVAL;
+ }
+ return retval;
+}
+
+
+static struct file_operations netlink_fops = {
+ owner: THIS_MODULE,
+ llseek: no_llseek,
+ read: netlink_read,
+ write: netlink_write,
+ poll: netlink_poll,
+ ioctl: netlink_ioctl,
+ open: netlink_open,
+ release: netlink_release,
+};
+
+static devfs_handle_t devfs_handle;
+
+static void __init make_devfs_entries (const char *name, int minor)
+{
+ devfs_register (devfs_handle, name, DEVFS_FL_DEFAULT,
+ NETLINK_MAJOR, minor,
+ S_IFCHR | S_IRUSR | S_IWUSR,
+ &netlink_fops, NULL);
+}
+
+int __init init_netlink(void)
+{
+ if (devfs_register_chrdev(NETLINK_MAJOR,"netlink", &netlink_fops)) {
+ printk(KERN_ERR "netlink: unable to get major %d\n", NETLINK_MAJOR);
+ return -EIO;
+ }
+ devfs_handle = devfs_mk_dir (NULL, "netlink", NULL);
+ /* Someone tell me the official names for the uppercase ones */
+ make_devfs_entries ("route", 0);
+ make_devfs_entries ("skip", 1);
+ make_devfs_entries ("usersock", 2);
+ make_devfs_entries ("fwmonitor", 3);
+ make_devfs_entries ("tcpdiag", 4);
+ make_devfs_entries ("arpd", 8);
+ make_devfs_entries ("route6", 11);
+ make_devfs_entries ("ip6_fw", 13);
+ make_devfs_entries ("dnrtmsg", 13);
+ devfs_register_series (devfs_handle, "tap%u", 16, DEVFS_FL_DEFAULT,
+ NETLINK_MAJOR, 16,
+ S_IFCHR | S_IRUSR | S_IWUSR,
+ &netlink_fops, NULL);
+ return 0;
+}
+
+#ifdef MODULE
+
+MODULE_LICENSE("GPL");
+
+int init_module(void)
+{
+ printk(KERN_INFO "Network Kernel/User communications module 0.04\n");
+ return init_netlink();
+}
+
+void cleanup_module(void)
+{
+ devfs_unregister (devfs_handle);
+ devfs_unregister_chrdev(NETLINK_MAJOR, "netlink");
+}
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/netrom/Makefile b/uClinux-2.4.31-uc0/net/netrom/Makefile
new file mode 100644
index 0000000..cf3a8a4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/netrom/Makefile
@@ -0,0 +1,19 @@
+#
+# Makefile for the Linux NET/ROM layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := netrom.o
+
+obj-y := af_netrom.o nr_dev.o nr_in.o nr_loopback.o nr_out.o nr_route.o \
+ nr_subr.o nr_timer.o
+obj-m := $(O_TARGET)
+
+obj-$(CONFIG_SYSCTL) += sysctl_net_netrom.o
+
+include $(TOPDIR)/Rules.make
+
diff --git a/uClinux-2.4.31-uc0/net/netrom/af_netrom.c b/uClinux-2.4.31-uc0/net/netrom/af_netrom.c
new file mode 100644
index 0000000..b0779d3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/netrom/af_netrom.c
@@ -0,0 +1,1384 @@
+/*
+ * NET/ROM release 007
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * NET/ROM 001 Jonathan(G4KLX) Cloned from the AX25 code.
+ * NET/ROM 002 Darryl(G7LED) Fixes and address enhancement.
+ * Jonathan(G4KLX) Complete bind re-think.
+ * Alan(GW4PTS) Trivial tweaks into new format.
+ * NET/ROM 003 Jonathan(G4KLX) Added G8BPQ extensions.
+ * Added NET/ROM routing ioctl.
+ * Darryl(G7LED) Fix autobinding (on connect).
+ * Fixed nr_release(), set TCP_CLOSE, wakeup app
+ * context, THEN make the sock dead.
+ * Circuit ID check before allocating it on
+ * a connection.
+ * Alan(GW4PTS) sendmsg/recvmsg only. Fixed connect clear bug
+ * inherited from AX.25
+ * NET/ROM 004 Jonathan(G4KLX) Converted to module.
+ * NET/ROM 005 Jonathan(G4KLX) Linux 2.1
+ * Alan(GW4PTS) Started POSIXisms
+ * NET/ROM 006 Alan(GW4PTS) Brought in line with the ANK changes
+ * Jonathan(G4KLX) Removed hdrincl.
+ * NET/ROM 007 Jonathan(G4KLX) New timer architecture.
+ * Impmented Idle timer.
+ * Arnaldo C. Melo s/suser/capable/, micro cleanups
+ * Jeroen(PE1RXQ) Use sock_orphan() on release.
+ * Tomi(OH2BNS) Better frame type checking.
+ * Device refcnt fixes.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/stat.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/termios.h> /* For TIOCINQ/OUTQ */
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <net/netrom.h>
+#include <linux/proc_fs.h>
+#include <net/ip.h>
+#include <net/arp.h>
+#include <linux/init.h>
+
+int nr_ndevs = 4;
+
+int sysctl_netrom_default_path_quality = NR_DEFAULT_QUAL;
+int sysctl_netrom_obsolescence_count_initialiser = NR_DEFAULT_OBS;
+int sysctl_netrom_network_ttl_initialiser = NR_DEFAULT_TTL;
+int sysctl_netrom_transport_timeout = NR_DEFAULT_T1;
+int sysctl_netrom_transport_maximum_tries = NR_DEFAULT_N2;
+int sysctl_netrom_transport_acknowledge_delay = NR_DEFAULT_T2;
+int sysctl_netrom_transport_busy_delay = NR_DEFAULT_T4;
+int sysctl_netrom_transport_requested_window_size = NR_DEFAULT_WINDOW;
+int sysctl_netrom_transport_no_activity_timeout = NR_DEFAULT_IDLE;
+int sysctl_netrom_routing_control = NR_DEFAULT_ROUTING;
+int sysctl_netrom_link_fails_count = NR_DEFAULT_FAILS;
+
+static unsigned short circuit = 0x101;
+
+static struct sock *volatile nr_list;
+
+static struct proto_ops nr_proto_ops;
+
+static void nr_free_sock(struct sock *sk)
+{
+ sk_free(sk);
+
+ MOD_DEC_USE_COUNT;
+}
+
+static struct sock *nr_alloc_sock(void)
+{
+ struct sock *sk;
+ nr_cb *nr;
+
+ if ((sk = sk_alloc(PF_NETROM, GFP_ATOMIC, 1)) == NULL)
+ return NULL;
+
+ if ((nr = kmalloc(sizeof(*nr), GFP_ATOMIC)) == NULL) {
+ sk_free(sk);
+ return NULL;
+ }
+
+ MOD_INC_USE_COUNT;
+
+ memset(nr, 0x00, sizeof(*nr));
+
+ sk->protinfo.nr = nr;
+ nr->sk = sk;
+
+ return sk;
+}
+
+/*
+ * Socket removal during an interrupt is now safe.
+ */
+static void nr_remove_socket(struct sock *sk)
+{
+ struct sock *s;
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+ if ((s = nr_list) == sk) {
+ nr_list = s->next;
+ dev_put(sk->protinfo.nr->device);
+ restore_flags(flags);
+ return;
+ }
+
+ while (s != NULL && s->next != NULL) {
+ if (s->next == sk) {
+ s->next = sk->next;
+ dev_put(sk->protinfo.nr->device);
+ restore_flags(flags);
+ return;
+ }
+
+ s = s->next;
+ }
+
+ restore_flags(flags);
+}
+
+/*
+ * Kill all bound sockets on a dropped device.
+ */
+static void nr_kill_by_device(struct net_device *dev)
+{
+ struct sock *s;
+
+ for (s = nr_list; s != NULL; s = s->next) {
+ if (s->protinfo.nr->device == dev)
+ nr_disconnect(s, ENETUNREACH);
+ }
+}
+
+/*
+ * Handle device status changes.
+ */
+static int nr_device_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct net_device *dev = (struct net_device *)ptr;
+
+ if (event != NETDEV_DOWN)
+ return NOTIFY_DONE;
+
+ nr_kill_by_device(dev);
+ nr_rt_device_down(dev);
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Add a socket to the bound sockets list.
+ */
+static void nr_insert_socket(struct sock *sk)
+{
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+ sk->next = nr_list;
+ nr_list = sk;
+
+ restore_flags(flags);
+}
+
+/*
+ * Find a socket that wants to accept the Connect Request we just
+ * received.
+ */
+static struct sock *nr_find_listener(ax25_address *addr)
+{
+ unsigned long flags;
+ struct sock *s;
+
+ save_flags(flags);
+ cli();
+
+ for (s = nr_list; s != NULL; s = s->next) {
+ if (ax25cmp(&s->protinfo.nr->source_addr, addr) == 0 && s->state == TCP_LISTEN) {
+ restore_flags(flags);
+ return s;
+ }
+ }
+
+ restore_flags(flags);
+ return NULL;
+}
+
+/*
+ * Find a connected NET/ROM socket given my circuit IDs.
+ */
+static struct sock *nr_find_socket(unsigned char index, unsigned char id)
+{
+ struct sock *s;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ for (s = nr_list; s != NULL; s = s->next) {
+ if (s->protinfo.nr->my_index == index && s->protinfo.nr->my_id == id) {
+ restore_flags(flags);
+ return s;
+ }
+ }
+
+ restore_flags(flags);
+
+ return NULL;
+}
+
+/*
+ * Find a connected NET/ROM socket given their circuit IDs.
+ */
+static struct sock *nr_find_peer(unsigned char index, unsigned char id, ax25_address *dest)
+{
+ struct sock *s;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ for (s = nr_list; s != NULL; s = s->next) {
+ if (s->protinfo.nr->your_index == index && s->protinfo.nr->your_id == id && ax25cmp(&s->protinfo.nr->dest_addr, dest) == 0) {
+ restore_flags(flags);
+ return s;
+ }
+ }
+
+ restore_flags(flags);
+
+ return NULL;
+}
+
+/*
+ * Find next free circuit ID.
+ */
+static unsigned short nr_find_next_circuit(void)
+{
+ unsigned short id = circuit;
+ unsigned char i, j;
+
+ for (;;) {
+ i = id / 256;
+ j = id % 256;
+
+ if (i != 0 && j != 0)
+ if (nr_find_socket(i, j) == NULL)
+ break;
+
+ id++;
+ }
+
+ return id;
+}
+
+/*
+ * Deferred destroy.
+ */
+void nr_destroy_socket(struct sock *);
+
+/*
+ * Handler for deferred kills.
+ */
+static void nr_destroy_timer(unsigned long data)
+{
+ nr_destroy_socket((struct sock *)data);
+}
+
+/*
+ * This is called from user mode and the timers. Thus it protects itself against
+ * interrupt users but doesn't worry about being called during work.
+ * Once it is removed from the queue no interrupt or bottom half will
+ * touch it and we are (fairly 8-) ) safe.
+ */
+void nr_destroy_socket(struct sock *sk) /* Not static as it's used by the timer */
+{
+ struct sk_buff *skb;
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+ nr_stop_heartbeat(sk);
+ nr_stop_t1timer(sk);
+ nr_stop_t2timer(sk);
+ nr_stop_t4timer(sk);
+ nr_stop_idletimer(sk);
+
+ nr_remove_socket(sk);
+ nr_clear_queues(sk); /* Flush the queues */
+
+ while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
+ if (skb->sk != sk) { /* A pending connection */
+ skb->sk->dead = 1; /* Queue the unaccepted socket for death */
+ nr_start_heartbeat(skb->sk);
+ skb->sk->protinfo.nr->state = NR_STATE_0;
+ }
+
+ kfree_skb(skb);
+ }
+
+ if (atomic_read(&sk->wmem_alloc) != 0 || atomic_read(&sk->rmem_alloc) != 0) {
+ /* Defer: outstanding buffers */
+ init_timer(&sk->timer);
+ sk->timer.expires = jiffies + 10 * HZ;
+ sk->timer.function = nr_destroy_timer;
+ sk->timer.data = (unsigned long)sk;
+ add_timer(&sk->timer);
+ } else {
+ nr_free_sock(sk);
+ }
+
+ restore_flags(flags);
+}
+
+/*
+ * Handling for system calls applied via the various interfaces to a
+ * NET/ROM socket object.
+ */
+
+static int nr_setsockopt(struct socket *sock, int level, int optname,
+ char *optval, int optlen)
+{
+ struct sock *sk = sock->sk;
+ int opt;
+
+ if (level != SOL_NETROM)
+ return -ENOPROTOOPT;
+
+ if (optlen < sizeof(int))
+ return -EINVAL;
+
+ if (get_user(opt, (int *)optval))
+ return -EFAULT;
+
+ switch (optname) {
+ case NETROM_T1:
+ if (opt < 1)
+ return -EINVAL;
+ sk->protinfo.nr->t1 = opt * HZ;
+ return 0;
+
+ case NETROM_T2:
+ if (opt < 1)
+ return -EINVAL;
+ sk->protinfo.nr->t2 = opt * HZ;
+ return 0;
+
+ case NETROM_N2:
+ if (opt < 1 || opt > 31)
+ return -EINVAL;
+ sk->protinfo.nr->n2 = opt;
+ return 0;
+
+ case NETROM_T4:
+ if (opt < 1)
+ return -EINVAL;
+ sk->protinfo.nr->t4 = opt * HZ;
+ return 0;
+
+ case NETROM_IDLE:
+ if (opt < 0)
+ return -EINVAL;
+ sk->protinfo.nr->idle = opt * 60 * HZ;
+ return 0;
+
+ default:
+ return -ENOPROTOOPT;
+ }
+}
+
+static int nr_getsockopt(struct socket *sock, int level, int optname,
+ char *optval, int *optlen)
+{
+ struct sock *sk = sock->sk;
+ int val = 0;
+ int len;
+
+ if (level != SOL_NETROM)
+ return -ENOPROTOOPT;
+
+ if (get_user(len, optlen))
+ return -EFAULT;
+
+ if (len < 0)
+ return -EINVAL;
+
+ switch (optname) {
+ case NETROM_T1:
+ val = sk->protinfo.nr->t1 / HZ;
+ break;
+
+ case NETROM_T2:
+ val = sk->protinfo.nr->t2 / HZ;
+ break;
+
+ case NETROM_N2:
+ val = sk->protinfo.nr->n2;
+ break;
+
+ case NETROM_T4:
+ val = sk->protinfo.nr->t4 / HZ;
+ break;
+
+ case NETROM_IDLE:
+ val = sk->protinfo.nr->idle / (60 * HZ);
+ break;
+
+ default:
+ return -ENOPROTOOPT;
+ }
+
+ len = min_t(unsigned int, len, sizeof(int));
+
+ if (put_user(len, optlen))
+ return -EFAULT;
+
+ return copy_to_user(optval, &val, len) ? -EFAULT : 0;
+}
+
+static int nr_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk->state != TCP_LISTEN) {
+ memset(&sk->protinfo.nr->user_addr, '\0', AX25_ADDR_LEN);
+ sk->max_ack_backlog = backlog;
+ sk->state = TCP_LISTEN;
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static int nr_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+ nr_cb *nr;
+
+ if (sock->type != SOCK_SEQPACKET || protocol != 0)
+ return -ESOCKTNOSUPPORT;
+
+ if ((sk = nr_alloc_sock()) == NULL)
+ return -ENOMEM;
+
+ nr = sk->protinfo.nr;
+
+ sock_init_data(sock, sk);
+
+ sock->ops = &nr_proto_ops;
+ sk->protocol = protocol;
+
+ skb_queue_head_init(&nr->ack_queue);
+ skb_queue_head_init(&nr->reseq_queue);
+ skb_queue_head_init(&nr->frag_queue);
+
+ init_timer(&nr->t1timer);
+ init_timer(&nr->t2timer);
+ init_timer(&nr->t4timer);
+ init_timer(&nr->idletimer);
+
+ nr->t1 = sysctl_netrom_transport_timeout;
+ nr->t2 = sysctl_netrom_transport_acknowledge_delay;
+ nr->n2 = sysctl_netrom_transport_maximum_tries;
+ nr->t4 = sysctl_netrom_transport_busy_delay;
+ nr->idle = sysctl_netrom_transport_no_activity_timeout;
+ nr->window = sysctl_netrom_transport_requested_window_size;
+
+ nr->bpqext = 1;
+ nr->state = NR_STATE_0;
+
+ return 0;
+}
+
+static struct sock *nr_make_new(struct sock *osk)
+{
+ struct sock *sk;
+ nr_cb *nr;
+
+ if (osk->type != SOCK_SEQPACKET)
+ return NULL;
+
+ if ((sk = nr_alloc_sock()) == NULL)
+ return NULL;
+
+ nr = sk->protinfo.nr;
+
+ sock_init_data(NULL, sk);
+
+ sk->type = osk->type;
+ sk->socket = osk->socket;
+ sk->priority = osk->priority;
+ sk->protocol = osk->protocol;
+ sk->rcvbuf = osk->rcvbuf;
+ sk->sndbuf = osk->sndbuf;
+ sk->debug = osk->debug;
+ sk->state = TCP_ESTABLISHED;
+ sk->sleep = osk->sleep;
+ sk->zapped = osk->zapped;
+
+ skb_queue_head_init(&nr->ack_queue);
+ skb_queue_head_init(&nr->reseq_queue);
+ skb_queue_head_init(&nr->frag_queue);
+
+ init_timer(&nr->t1timer);
+ init_timer(&nr->t2timer);
+ init_timer(&nr->t4timer);
+ init_timer(&nr->idletimer);
+
+ nr->t1 = osk->protinfo.nr->t1;
+ nr->t2 = osk->protinfo.nr->t2;
+ nr->n2 = osk->protinfo.nr->n2;
+ nr->t4 = osk->protinfo.nr->t4;
+ nr->idle = osk->protinfo.nr->idle;
+ nr->window = osk->protinfo.nr->window;
+
+ nr->device = osk->protinfo.nr->device;
+ nr->bpqext = osk->protinfo.nr->bpqext;
+
+ return sk;
+}
+
+static int nr_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk == NULL) return 0;
+
+ switch (sk->protinfo.nr->state) {
+
+ case NR_STATE_0:
+ case NR_STATE_1:
+ case NR_STATE_2:
+ nr_disconnect(sk, 0);
+ nr_destroy_socket(sk);
+ break;
+
+ case NR_STATE_3:
+ nr_clear_queues(sk);
+ sk->protinfo.nr->n2count = 0;
+ nr_write_internal(sk, NR_DISCREQ);
+ nr_start_t1timer(sk);
+ nr_stop_t2timer(sk);
+ nr_stop_t4timer(sk);
+ nr_stop_idletimer(sk);
+ sk->protinfo.nr->state = NR_STATE_2;
+ sk->state = TCP_CLOSE;
+ sk->shutdown |= SEND_SHUTDOWN;
+ sk->state_change(sk);
+ sock_orphan(sk);
+ sk->destroy = 1;
+ break;
+
+ default:
+ sk->socket = NULL;
+ break;
+ }
+
+ sock->sk = NULL;
+
+ return 0;
+}
+
+static int nr_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sock *sk = sock->sk;
+ struct full_sockaddr_ax25 *addr = (struct full_sockaddr_ax25 *)uaddr;
+ struct net_device *dev;
+ ax25_address *user, *source;
+
+ if (sk->zapped == 0)
+ return -EINVAL;
+
+ if (addr_len < sizeof(struct sockaddr_ax25) || addr_len > sizeof(struct
+full_sockaddr_ax25))
+ return -EINVAL;
+
+ if (addr_len < (addr->fsa_ax25.sax25_ndigis * sizeof(ax25_address) + sizeof(struct sockaddr_ax25)))
+ return -EINVAL;
+
+ if (addr->fsa_ax25.sax25_family != AF_NETROM)
+ return -EINVAL;
+
+ if ((dev = nr_dev_get(&addr->fsa_ax25.sax25_call)) == NULL) {
+ SOCK_DEBUG(sk, "NET/ROM: bind failed: invalid node callsign\n");
+ return -EADDRNOTAVAIL;
+ }
+
+ /*
+ * Only the super user can set an arbitrary user callsign.
+ */
+ if (addr->fsa_ax25.sax25_ndigis == 1) {
+ if (!capable(CAP_NET_BIND_SERVICE)) {
+ dev_put(dev);
+ return -EACCES;
+ }
+ sk->protinfo.nr->user_addr = addr->fsa_digipeater[0];
+ sk->protinfo.nr->source_addr = addr->fsa_ax25.sax25_call;
+ } else {
+ source = &addr->fsa_ax25.sax25_call;
+
+ if ((user = ax25_findbyuid(current->euid)) == NULL) {
+ if (ax25_uid_policy && !capable(CAP_NET_BIND_SERVICE)) {
+ dev_put(dev);
+ return -EPERM;
+ }
+ user = source;
+ }
+
+ sk->protinfo.nr->user_addr = *user;
+ sk->protinfo.nr->source_addr = *source;
+ }
+
+ sk->protinfo.nr->device = dev;
+ nr_insert_socket(sk);
+
+ sk->zapped = 0;
+ SOCK_DEBUG(sk, "NET/ROM: socket is bound\n");
+ return 0;
+}
+
+static int nr_connect(struct socket *sock, struct sockaddr *uaddr,
+ int addr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_ax25 *addr = (struct sockaddr_ax25 *)uaddr;
+ ax25_address *user, *source = NULL;
+ struct net_device *dev;
+
+ if (sk->state == TCP_ESTABLISHED && sock->state == SS_CONNECTING) {
+ sock->state = SS_CONNECTED;
+ return 0; /* Connect completed during a ERESTARTSYS event */
+ }
+
+ if (sk->state == TCP_CLOSE && sock->state == SS_CONNECTING) {
+ sock->state = SS_UNCONNECTED;
+ return -ECONNREFUSED;
+ }
+
+ if (sk->state == TCP_ESTABLISHED)
+ return -EISCONN; /* No reconnect on a seqpacket socket */
+
+ sk->state = TCP_CLOSE;
+ sock->state = SS_UNCONNECTED;
+
+ if (addr_len != sizeof(struct sockaddr_ax25) && addr_len != sizeof(struct full_sockaddr_ax25))
+ return -EINVAL;
+
+ if (addr->sax25_family != AF_NETROM)
+ return -EINVAL;
+
+ if (sk->zapped) { /* Must bind first - autobinding in this may or may not work */
+ sk->zapped = 0;
+
+ if ((dev = nr_dev_first()) == NULL)
+ return -ENETUNREACH;
+
+ source = (ax25_address *)dev->dev_addr;
+
+ if ((user = ax25_findbyuid(current->euid)) == NULL) {
+ if (ax25_uid_policy && !capable(CAP_NET_ADMIN)) {
+ dev_put(dev);
+ return -EPERM;
+ }
+ user = source;
+ }
+
+ sk->protinfo.nr->user_addr = *user;
+ sk->protinfo.nr->source_addr = *source;
+ sk->protinfo.nr->device = dev;
+
+ nr_insert_socket(sk); /* Finish the bind */
+ }
+
+ sk->protinfo.nr->dest_addr = addr->sax25_call;
+
+ circuit = nr_find_next_circuit();
+
+ sk->protinfo.nr->my_index = circuit / 256;
+ sk->protinfo.nr->my_id = circuit % 256;
+
+ circuit++;
+
+ /* Move to connecting socket, start sending Connect Requests */
+ sock->state = SS_CONNECTING;
+ sk->state = TCP_SYN_SENT;
+
+ nr_establish_data_link(sk);
+
+ sk->protinfo.nr->state = NR_STATE_1;
+
+ nr_start_heartbeat(sk);
+
+ /* Now the loop */
+ if (sk->state != TCP_ESTABLISHED && (flags & O_NONBLOCK))
+ return -EINPROGRESS;
+
+ cli(); /* To avoid races on the sleep */
+
+ /*
+ * A Connect Ack with Choke or timeout or failed routing will go to closed.
+ */
+ while (sk->state == TCP_SYN_SENT) {
+ interruptible_sleep_on(sk->sleep);
+ if (signal_pending(current)) {
+ sti();
+ return -ERESTARTSYS;
+ }
+ }
+
+ if (sk->state != TCP_ESTABLISHED) {
+ sti();
+ sock->state = SS_UNCONNECTED;
+ return sock_error(sk); /* Always set at this point */
+ }
+
+ sock->state = SS_CONNECTED;
+
+ sti();
+
+ return 0;
+}
+
+static int nr_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+ struct sock *sk;
+ struct sock *newsk;
+ struct sk_buff *skb;
+
+ if ((sk = sock->sk) == NULL)
+ return -EINVAL;
+
+ if (sk->type != SOCK_SEQPACKET)
+ return -EOPNOTSUPP;
+
+ if (sk->state != TCP_LISTEN)
+ return -EINVAL;
+
+ /*
+ * The write queue this time is holding sockets ready to use
+ * hooked into the SABM we saved
+ */
+ do {
+ cli();
+ if ((skb = skb_dequeue(&sk->receive_queue)) == NULL) {
+ if (flags & O_NONBLOCK) {
+ sti();
+ return -EWOULDBLOCK;
+ }
+ interruptible_sleep_on(sk->sleep);
+ if (signal_pending(current)) {
+ sti();
+ return -ERESTARTSYS;
+ }
+ }
+ } while (skb == NULL);
+
+ newsk = skb->sk;
+ newsk->pair = NULL;
+ newsk->socket = newsock;
+ newsk->sleep = &newsock->wait;
+ sti();
+
+ /* Now attach up the new socket */
+ kfree_skb(skb);
+ sk->ack_backlog--;
+ newsock->sk = newsk;
+
+ return 0;
+}
+
+static int nr_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *uaddr_len, int peer)
+{
+ struct full_sockaddr_ax25 *sax = (struct full_sockaddr_ax25 *)uaddr;
+ struct sock *sk = sock->sk;
+
+ if (peer != 0) {
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+ sax->fsa_ax25.sax25_family = AF_NETROM;
+ sax->fsa_ax25.sax25_ndigis = 1;
+ sax->fsa_ax25.sax25_call = sk->protinfo.nr->user_addr;
+ sax->fsa_digipeater[0] = sk->protinfo.nr->dest_addr;
+ *uaddr_len = sizeof(struct full_sockaddr_ax25);
+ } else {
+ sax->fsa_ax25.sax25_family = AF_NETROM;
+ sax->fsa_ax25.sax25_ndigis = 0;
+ sax->fsa_ax25.sax25_call = sk->protinfo.nr->source_addr;
+ *uaddr_len = sizeof(struct sockaddr_ax25);
+ }
+
+ return 0;
+}
+
+int nr_rx_frame(struct sk_buff *skb, struct net_device *dev)
+{
+ struct sock *sk;
+ struct sock *make;
+ ax25_address *src, *dest, *user;
+ unsigned short circuit_index, circuit_id;
+ unsigned short peer_circuit_index, peer_circuit_id;
+ unsigned short frametype, flags, window, timeout;
+
+ skb->sk = NULL; /* Initially we don't know who it's for */
+
+ /*
+ * skb->data points to the netrom frame start
+ */
+
+ src = (ax25_address *)(skb->data + 0);
+ dest = (ax25_address *)(skb->data + 7);
+
+ circuit_index = skb->data[15];
+ circuit_id = skb->data[16];
+ peer_circuit_index = skb->data[17];
+ peer_circuit_id = skb->data[18];
+ frametype = skb->data[19] & 0x0F;
+ flags = skb->data[19] & 0xF0;
+
+ switch (frametype) {
+ case NR_PROTOEXT:
+#ifdef CONFIG_INET
+ /*
+ * Check for an incoming IP over NET/ROM frame.
+ */
+ if (circuit_index == NR_PROTO_IP && circuit_id == NR_PROTO_IP) {
+ skb_pull(skb, NR_NETWORK_LEN + NR_TRANSPORT_LEN);
+ skb->h.raw = skb->data;
+
+ return nr_rx_ip(skb, dev);
+ }
+#endif
+ return 0;
+
+ case NR_CONNREQ:
+ case NR_CONNACK:
+ case NR_DISCREQ:
+ case NR_DISCACK:
+ case NR_INFO:
+ case NR_INFOACK:
+ /*
+ * These frame types we understand.
+ */
+ break;
+
+ default:
+ /*
+ * Everything else is ignored.
+ */
+ return 0;
+ }
+
+ /*
+ * Find an existing socket connection, based on circuit ID, if it's
+ * a Connect Request base it on their circuit ID.
+ *
+ * Circuit ID 0/0 is not valid but it could still be a "reset" for a
+ * circuit that no longer exists at the other end ...
+ */
+
+ sk = NULL;
+
+ if (circuit_index == 0 && circuit_id == 0) {
+ if (frametype == NR_CONNACK && flags == NR_CHOKE_FLAG)
+ sk = nr_find_peer(peer_circuit_index, peer_circuit_id, src);
+ } else {
+ if (frametype == NR_CONNREQ)
+ sk = nr_find_peer(circuit_index, circuit_id, src);
+ else
+ sk = nr_find_socket(circuit_index, circuit_id);
+ }
+
+ if (sk != NULL) {
+ skb->h.raw = skb->data;
+
+ if (frametype == NR_CONNACK && skb->len == 22)
+ sk->protinfo.nr->bpqext = 1;
+ else
+ sk->protinfo.nr->bpqext = 0;
+
+ return nr_process_rx_frame(sk, skb);
+ }
+
+ /*
+ * Now it should be a CONNREQ.
+ */
+ if (frametype != NR_CONNREQ) {
+ /*
+ * Here it would be nice to be able to send a reset but
+ * NET/ROM doesn't have one. The following hack would
+ * have been a way to extend the protocol but apparently
+ * it kills BPQ boxes... :-(
+ */
+#if 0
+ /*
+ * Never reply to a CONNACK/CHOKE.
+ */
+ if (frametype != NR_CONNACK || flags != NR_CHOKE_FLAG)
+ nr_transmit_refusal(skb, 1);
+#endif
+ return 0;
+ }
+
+ sk = nr_find_listener(dest);
+
+ user = (ax25_address *)(skb->data + 21);
+
+ if (sk == NULL || sk->ack_backlog == sk->max_ack_backlog || (make = nr_make_new(sk)) == NULL) {
+ nr_transmit_refusal(skb, 0);
+ return 0;
+ }
+
+ window = skb->data[20];
+
+ skb->sk = make;
+ make->state = TCP_ESTABLISHED;
+
+ /* Fill in his circuit details */
+ make->protinfo.nr->source_addr = *dest;
+ make->protinfo.nr->dest_addr = *src;
+ make->protinfo.nr->user_addr = *user;
+
+ make->protinfo.nr->your_index = circuit_index;
+ make->protinfo.nr->your_id = circuit_id;
+
+ circuit = nr_find_next_circuit();
+
+ make->protinfo.nr->my_index = circuit / 256;
+ make->protinfo.nr->my_id = circuit % 256;
+
+ circuit++;
+
+ /* Window negotiation */
+ if (window < make->protinfo.nr->window)
+ make->protinfo.nr->window = window;
+
+ /* L4 timeout negotiation */
+ if (skb->len == 37) {
+ timeout = skb->data[36] * 256 + skb->data[35];
+ if (timeout * HZ < make->protinfo.nr->t1)
+ make->protinfo.nr->t1 = timeout * HZ;
+ make->protinfo.nr->bpqext = 1;
+ } else {
+ make->protinfo.nr->bpqext = 0;
+ }
+
+ nr_write_internal(make, NR_CONNACK);
+
+ make->protinfo.nr->condition = 0x00;
+ make->protinfo.nr->vs = 0;
+ make->protinfo.nr->va = 0;
+ make->protinfo.nr->vr = 0;
+ make->protinfo.nr->vl = 0;
+ make->protinfo.nr->state = NR_STATE_3;
+ sk->ack_backlog++;
+ make->pair = sk;
+
+ dev_hold(make->protinfo.nr->device);
+
+ nr_insert_socket(make);
+
+ skb_queue_head(&sk->receive_queue, skb);
+
+ nr_start_heartbeat(make);
+ nr_start_idletimer(make);
+
+ if (!sk->dead)
+ sk->data_ready(sk, skb->len);
+
+ return 1;
+}
+
+static int nr_sendmsg(struct socket *sock, struct msghdr *msg, int len, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_ax25 *usax = (struct sockaddr_ax25 *)msg->msg_name;
+ int err;
+ struct sockaddr_ax25 sax;
+ struct sk_buff *skb;
+ unsigned char *asmptr;
+ int size;
+
+ if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_EOR))
+ return -EINVAL;
+
+ if (sk->zapped)
+ return -EADDRNOTAVAIL;
+
+ if (sk->shutdown & SEND_SHUTDOWN) {
+ send_sig(SIGPIPE, current, 0);
+ return -EPIPE;
+ }
+
+ if (sk->protinfo.nr->device == NULL)
+ return -ENETUNREACH;
+
+ if (usax) {
+ if (msg->msg_namelen < sizeof(sax))
+ return -EINVAL;
+ sax = *usax;
+ if (ax25cmp(&sk->protinfo.nr->dest_addr, &sax.sax25_call) != 0)
+ return -EISCONN;
+ if (sax.sax25_family != AF_NETROM)
+ return -EINVAL;
+ } else {
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+ sax.sax25_family = AF_NETROM;
+ sax.sax25_call = sk->protinfo.nr->dest_addr;
+ }
+
+ SOCK_DEBUG(sk, "NET/ROM: sendto: Addresses built.\n");
+
+ /* Build a packet */
+ SOCK_DEBUG(sk, "NET/ROM: sendto: building packet.\n");
+ size = len + AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + NR_NETWORK_LEN + NR_TRANSPORT_LEN;
+
+ if ((skb = sock_alloc_send_skb(sk, size, msg->msg_flags & MSG_DONTWAIT, &err)) == NULL)
+ return err;
+
+ skb_reserve(skb, size - len);
+
+ /*
+ * Push down the NET/ROM header
+ */
+
+ asmptr = skb_push(skb, NR_TRANSPORT_LEN);
+ SOCK_DEBUG(sk, "Building NET/ROM Header.\n");
+
+ /* Build a NET/ROM Transport header */
+
+ *asmptr++ = sk->protinfo.nr->your_index;
+ *asmptr++ = sk->protinfo.nr->your_id;
+ *asmptr++ = 0; /* To be filled in later */
+ *asmptr++ = 0; /* Ditto */
+ *asmptr++ = NR_INFO;
+ SOCK_DEBUG(sk, "Built header.\n");
+
+ /*
+ * Put the data on the end
+ */
+
+ skb->h.raw = skb_put(skb, len);
+
+ asmptr = skb->h.raw;
+ SOCK_DEBUG(sk, "NET/ROM: Appending user data\n");
+
+ /* User data follows immediately after the NET/ROM transport header */
+ memcpy_fromiovec(asmptr, msg->msg_iov, len);
+ SOCK_DEBUG(sk, "NET/ROM: Transmitting buffer\n");
+
+ if (sk->state != TCP_ESTABLISHED) {
+ kfree_skb(skb);
+ return -ENOTCONN;
+ }
+
+ nr_output(sk, skb); /* Shove it onto the queue */
+
+ return len;
+}
+
+static int nr_recvmsg(struct socket *sock, struct msghdr *msg, int size,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_ax25 *sax = (struct sockaddr_ax25 *)msg->msg_name;
+ int copied;
+ struct sk_buff *skb;
+ int er;
+
+ /*
+ * This works for seqpacket too. The receiver has ordered the queue for
+ * us! We do one quick check first though
+ */
+
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+
+ /* Now we can treat all alike */
+ if ((skb = skb_recv_datagram(sk, flags & ~MSG_DONTWAIT, flags & MSG_DONTWAIT, &er)) == NULL)
+ return er;
+
+ skb->h.raw = skb->data;
+ copied = skb->len;
+
+ if (copied > size) {
+ copied = size;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+
+ skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+
+ if (sax != NULL) {
+ sax->sax25_family = AF_NETROM;
+ memcpy(sax->sax25_call.ax25_call, skb->data + 7, AX25_ADDR_LEN);
+ }
+
+ msg->msg_namelen = sizeof(*sax);
+
+ skb_free_datagram(sk, skb);
+
+ return copied;
+}
+
+
+static int nr_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+
+ switch (cmd) {
+ case TIOCOUTQ: {
+ long amount;
+ amount = sk->sndbuf - atomic_read(&sk->wmem_alloc);
+ if (amount < 0)
+ amount = 0;
+ return put_user(amount, (int *)arg);
+ }
+
+ case TIOCINQ: {
+ struct sk_buff *skb;
+ long amount = 0L;
+ /* These two are safe on a single CPU system as only user tasks fiddle here */
+ if ((skb = skb_peek(&sk->receive_queue)) != NULL)
+ amount = skb->len;
+ return put_user(amount, (int *)arg);
+ }
+
+ case SIOCGSTAMP:
+ if (sk != NULL) {
+ if (sk->stamp.tv_sec == 0)
+ return -ENOENT;
+ return copy_to_user((void *)arg, &sk->stamp, sizeof(struct timeval)) ? -EFAULT : 0;
+ }
+ return -EINVAL;
+
+ case SIOCGIFADDR:
+ case SIOCSIFADDR:
+ case SIOCGIFDSTADDR:
+ case SIOCSIFDSTADDR:
+ case SIOCGIFBRDADDR:
+ case SIOCSIFBRDADDR:
+ case SIOCGIFNETMASK:
+ case SIOCSIFNETMASK:
+ case SIOCGIFMETRIC:
+ case SIOCSIFMETRIC:
+ return -EINVAL;
+
+ case SIOCADDRT:
+ case SIOCDELRT:
+ case SIOCNRDECOBS:
+ if (!capable(CAP_NET_ADMIN)) return -EPERM;
+ return nr_rt_ioctl(cmd, (void *)arg);
+
+ default:
+ return dev_ioctl(cmd, (void *)arg);
+ }
+
+ /*NOTREACHED*/
+ return 0;
+}
+
+static int nr_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct sock *s;
+ struct net_device *dev;
+ const char *devname;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+
+ cli();
+
+ len += sprintf(buffer, "user_addr dest_node src_node dev my your st vs vr va t1 t2 t4 idle n2 wnd Snd-Q Rcv-Q inode\n");
+
+ for (s = nr_list; s != NULL; s = s->next) {
+ if ((dev = s->protinfo.nr->device) == NULL)
+ devname = "???";
+ else
+ devname = dev->name;
+
+ len += sprintf(buffer + len, "%-9s ",
+ ax2asc(&s->protinfo.nr->user_addr));
+ len += sprintf(buffer + len, "%-9s ",
+ ax2asc(&s->protinfo.nr->dest_addr));
+ len += sprintf(buffer + len, "%-9s %-3s %02X/%02X %02X/%02X %2d %3d %3d %3d %3lu/%03lu %2lu/%02lu %3lu/%03lu %3lu/%03lu %2d/%02d %3d %5d %5d %ld\n",
+ ax2asc(&s->protinfo.nr->source_addr),
+ devname,
+ s->protinfo.nr->my_index,
+ s->protinfo.nr->my_id,
+ s->protinfo.nr->your_index,
+ s->protinfo.nr->your_id,
+ s->protinfo.nr->state,
+ s->protinfo.nr->vs,
+ s->protinfo.nr->vr,
+ s->protinfo.nr->va,
+ ax25_display_timer(&s->protinfo.nr->t1timer) / HZ,
+ s->protinfo.nr->t1 / HZ,
+ ax25_display_timer(&s->protinfo.nr->t2timer) / HZ,
+ s->protinfo.nr->t2 / HZ,
+ ax25_display_timer(&s->protinfo.nr->t4timer) / HZ,
+ s->protinfo.nr->t4 / HZ,
+ ax25_display_timer(&s->protinfo.nr->idletimer) / (60 * HZ),
+ s->protinfo.nr->idle / (60 * HZ),
+ s->protinfo.nr->n2count,
+ s->protinfo.nr->n2,
+ s->protinfo.nr->window,
+ atomic_read(&s->wmem_alloc),
+ atomic_read(&s->rmem_alloc),
+ s->socket != NULL ? s->socket->inode->i_ino : 0L);
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ break;
+ }
+
+ sti();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return(len);
+}
+
+static struct net_proto_family nr_family_ops = {
+ family: PF_NETROM,
+ create: nr_create,
+};
+
+static struct proto_ops SOCKOPS_WRAPPED(nr_proto_ops) = {
+ family: PF_NETROM,
+
+ release: nr_release,
+ bind: nr_bind,
+ connect: nr_connect,
+ socketpair: sock_no_socketpair,
+ accept: nr_accept,
+ getname: nr_getname,
+ poll: datagram_poll,
+ ioctl: nr_ioctl,
+ listen: nr_listen,
+ shutdown: sock_no_shutdown,
+ setsockopt: nr_setsockopt,
+ getsockopt: nr_getsockopt,
+ sendmsg: nr_sendmsg,
+ recvmsg: nr_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+#include <linux/smp_lock.h>
+SOCKOPS_WRAP(nr_proto, PF_NETROM);
+
+static struct notifier_block nr_dev_notifier = {
+ notifier_call: nr_device_event,
+};
+
+static struct net_device *dev_nr;
+
+static char banner[] __initdata = KERN_INFO "G4KLX NET/ROM for Linux. Version 0.7 for AX25.037 Linux 2.4\n";
+
+static int __init nr_proto_init(void)
+{
+ int i;
+
+ if (nr_ndevs > 0x7fffffff/sizeof(struct net_device)) {
+ printk(KERN_ERR "NET/ROM: nr_proto_init - nr_ndevs parameter to large\n");
+ return -1;
+ }
+
+ if ((dev_nr = kmalloc(nr_ndevs * sizeof(struct net_device), GFP_KERNEL)) == NULL) {
+ printk(KERN_ERR "NET/ROM: nr_proto_init - unable to allocate device structure\n");
+ return -1;
+ }
+
+ memset(dev_nr, 0x00, nr_ndevs * sizeof(struct net_device));
+
+ for (i = 0; i < nr_ndevs; i++) {
+ sprintf(dev_nr[i].name, "nr%d", i);
+ dev_nr[i].init = nr_init;
+ register_netdev(&dev_nr[i]);
+ }
+
+ sock_register(&nr_family_ops);
+ register_netdevice_notifier(&nr_dev_notifier);
+ printk(banner);
+
+ ax25_protocol_register(AX25_P_NETROM, nr_route_frame);
+ ax25_linkfail_register(nr_link_failed);
+
+#ifdef CONFIG_SYSCTL
+ nr_register_sysctl();
+#endif
+
+ nr_loopback_init();
+
+ proc_net_create("nr", 0, nr_get_info);
+ proc_net_create("nr_neigh", 0, nr_neigh_get_info);
+ proc_net_create("nr_nodes", 0, nr_nodes_get_info);
+ return 0;
+}
+
+module_init(nr_proto_init);
+
+
+EXPORT_NO_SYMBOLS;
+
+MODULE_PARM(nr_ndevs, "i");
+MODULE_PARM_DESC(nr_ndevs, "number of NET/ROM devices");
+
+MODULE_AUTHOR("Jonathan Naylor G4KLX <g4klx@g4klx.demon.co.uk>");
+MODULE_DESCRIPTION("The amateur radio NET/ROM network and transport layer protocol");
+MODULE_LICENSE("GPL");
+
+static void __exit nr_exit(void)
+{
+ int i;
+
+ proc_net_remove("nr");
+ proc_net_remove("nr_neigh");
+ proc_net_remove("nr_nodes");
+ nr_loopback_clear();
+
+ nr_rt_free();
+
+ ax25_protocol_release(AX25_P_NETROM);
+ ax25_linkfail_release(nr_link_failed);
+
+ unregister_netdevice_notifier(&nr_dev_notifier);
+
+#ifdef CONFIG_SYSCTL
+ nr_unregister_sysctl();
+#endif
+ sock_unregister(PF_NETROM);
+
+ for (i = 0; i < nr_ndevs; i++) {
+ if (dev_nr[i].priv != NULL) {
+ kfree(dev_nr[i].priv);
+ dev_nr[i].priv = NULL;
+ unregister_netdev(&dev_nr[i]);
+ }
+ }
+
+ kfree(dev_nr);
+}
+module_exit(nr_exit);
diff --git a/uClinux-2.4.31-uc0/net/netrom/nr_dev.c b/uClinux-2.4.31-uc0/net/netrom/nr_dev.c
new file mode 100644
index 0000000..fea453a
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/netrom/nr_dev.c
@@ -0,0 +1,237 @@
+/*
+ * NET/ROM release 007
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * NET/ROM 001 Jonathan(G4KLX) Cloned from loopback.c
+ * NET/ROM 002 Steve Whitehouse(GW7RRM) fixed the set_mac_address
+ * NET/ROM 003 Jonathan(G4KLX) Put nr_rebuild_header into line with
+ * ax25_rebuild_header
+ * NET/ROM 004 Jonathan(G4KLX) Callsign registration with AX.25.
+ * NET/ROM 006 Hans(PE1AYX) Fixed interface to IP layer.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/fs.h>
+#include <linux/types.h>
+#include <linux/sysctl.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/errno.h>
+#include <linux/fcntl.h>
+#include <linux/in.h>
+#include <linux/if_ether.h> /* For the statistics structure. */
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+
+#include <net/ip.h>
+#include <net/arp.h>
+
+#include <net/ax25.h>
+#include <net/netrom.h>
+
+#ifdef CONFIG_INET
+
+/*
+ * Only allow IP over NET/ROM frames through if the netrom device is up.
+ */
+
+int nr_rx_ip(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_device_stats *stats = (struct net_device_stats *)dev->priv;
+
+ if (!netif_running(dev)) {
+ stats->rx_errors++;
+ return 0;
+ }
+
+ stats->rx_packets++;
+ stats->rx_bytes += skb->len;
+
+ skb->protocol = htons(ETH_P_IP);
+
+ /* Spoof incoming device */
+ skb->dev = dev;
+ skb->h.raw = skb->data;
+ skb->nh.raw = skb->data;
+ skb->pkt_type = PACKET_HOST;
+
+ ip_rcv(skb, skb->dev, NULL);
+
+ return 1;
+}
+
+
+static int nr_rebuild_header(struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+ struct net_device_stats *stats = (struct net_device_stats *)dev->priv;
+ struct sk_buff *skbn;
+ unsigned char *bp = skb->data;
+ int len;
+
+ if (arp_find(bp + 7, skb)) {
+ return 1;
+ }
+
+ bp[6] &= ~AX25_CBIT;
+ bp[6] &= ~AX25_EBIT;
+ bp[6] |= AX25_SSSID_SPARE;
+ bp += AX25_ADDR_LEN;
+
+ bp[6] &= ~AX25_CBIT;
+ bp[6] |= AX25_EBIT;
+ bp[6] |= AX25_SSSID_SPARE;
+
+ if ((skbn = skb_clone(skb, GFP_ATOMIC)) == NULL) {
+ kfree_skb(skb);
+ return 1;
+ }
+
+ if (skb->sk != NULL)
+ skb_set_owner_w(skbn, skb->sk);
+
+ kfree_skb(skb);
+
+ len = skbn->len;
+
+ if (!nr_route_frame(skbn, NULL)) {
+ kfree_skb(skbn);
+ stats->tx_errors++;
+ }
+
+ stats->tx_packets++;
+ stats->tx_bytes += len;
+
+ return 1;
+}
+
+#else
+
+static int nr_rebuild_header(struct sk_buff *skb)
+{
+ return 1;
+}
+
+#endif
+
+static int nr_header(struct sk_buff *skb, struct net_device *dev, unsigned short type,
+ void *daddr, void *saddr, unsigned len)
+{
+ unsigned char *buff = skb_push(skb, NR_NETWORK_LEN + NR_TRANSPORT_LEN);
+
+ memcpy(buff, (saddr != NULL) ? saddr : dev->dev_addr, dev->addr_len);
+ buff[6] &= ~AX25_CBIT;
+ buff[6] &= ~AX25_EBIT;
+ buff[6] |= AX25_SSSID_SPARE;
+ buff += AX25_ADDR_LEN;
+
+ if (daddr != NULL)
+ memcpy(buff, daddr, dev->addr_len);
+ buff[6] &= ~AX25_CBIT;
+ buff[6] |= AX25_EBIT;
+ buff[6] |= AX25_SSSID_SPARE;
+ buff += AX25_ADDR_LEN;
+
+ *buff++ = sysctl_netrom_network_ttl_initialiser;
+
+ *buff++ = NR_PROTO_IP;
+ *buff++ = NR_PROTO_IP;
+ *buff++ = 0;
+ *buff++ = 0;
+ *buff++ = NR_PROTOEXT;
+
+ if (daddr != NULL)
+ return 37;
+
+ return -37;
+}
+
+static int nr_set_mac_address(struct net_device *dev, void *addr)
+{
+ struct sockaddr *sa = addr;
+
+ ax25_listen_release((ax25_address *)dev->dev_addr, NULL);
+
+ memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
+
+ ax25_listen_register((ax25_address *)dev->dev_addr, NULL);
+
+ return 0;
+}
+
+static int nr_open(struct net_device *dev)
+{
+ MOD_INC_USE_COUNT;
+ netif_start_queue(dev);
+ ax25_listen_register((ax25_address *)dev->dev_addr, NULL);
+ return 0;
+}
+
+static int nr_close(struct net_device *dev)
+{
+ netif_stop_queue(dev);
+ ax25_listen_release((ax25_address *)dev->dev_addr, NULL);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int nr_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_device_stats *stats = (struct net_device_stats *)dev->priv;
+ dev_kfree_skb(skb);
+ stats->tx_errors++;
+ return 0;
+}
+
+static struct net_device_stats *nr_get_stats(struct net_device *dev)
+{
+ return (struct net_device_stats *)dev->priv;
+}
+
+int nr_init(struct net_device *dev)
+{
+ dev->mtu = NR_MAX_PACKET_SIZE;
+ dev->hard_start_xmit = nr_xmit;
+ dev->open = nr_open;
+ dev->stop = nr_close;
+
+ dev->hard_header = nr_header;
+ dev->hard_header_len = AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + NR_NETWORK_LEN + NR_TRANSPORT_LEN;
+ dev->addr_len = AX25_ADDR_LEN;
+ dev->type = ARPHRD_NETROM;
+ dev->rebuild_header = nr_rebuild_header;
+ dev->set_mac_address = nr_set_mac_address;
+
+ /* New-style flags. */
+ dev->flags = 0;
+
+ if ((dev->priv = kmalloc(sizeof(struct net_device_stats), GFP_KERNEL)) == NULL)
+ return -ENOMEM;
+
+ memset(dev->priv, 0, sizeof(struct net_device_stats));
+
+ dev->get_stats = nr_get_stats;
+
+ return 0;
+};
diff --git a/uClinux-2.4.31-uc0/net/netrom/nr_in.c b/uClinux-2.4.31-uc0/net/netrom/nr_in.c
new file mode 100644
index 0000000..29762e9
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/netrom/nr_in.c
@@ -0,0 +1,304 @@
+/*
+ * NET/ROM release 007
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Most of this code is based on the SDL diagrams published in the 7th
+ * ARRL Computer Networking Conference papers. The diagrams have mistakes
+ * in them, but are mostly correct. Before you modify the code could you
+ * read the SDL diagrams as the code is not obvious and probably very
+ * easy to break;
+ *
+ * History
+ * NET/ROM 001 Jonathan(G4KLX) Cloned from ax25_in.c
+ * NET/ROM 003 Jonathan(G4KLX) Added NET/ROM fragment reception.
+ * Darryl(G7LED) Added missing INFO with NAK case, optimized
+ * INFOACK handling, removed reconnect on error.
+ * NET/ROM 006 Jonathan(G4KLX) Hdrincl removal changes.
+ * NET/ROM 007 Jonathan(G4KLX) New timer architecture.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/ip.h> /* For ip_rcv */
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/netrom.h>
+
+static int nr_queue_rx_frame(struct sock *sk, struct sk_buff *skb, int more)
+{
+ struct sk_buff *skbo, *skbn = skb;
+
+ skb_pull(skb, NR_NETWORK_LEN + NR_TRANSPORT_LEN);
+
+ nr_start_idletimer(sk);
+
+ if (more) {
+ sk->protinfo.nr->fraglen += skb->len;
+ skb_queue_tail(&sk->protinfo.nr->frag_queue, skb);
+ return 0;
+ }
+
+ if (!more && sk->protinfo.nr->fraglen > 0) { /* End of fragment */
+ sk->protinfo.nr->fraglen += skb->len;
+ skb_queue_tail(&sk->protinfo.nr->frag_queue, skb);
+
+ if ((skbn = alloc_skb(sk->protinfo.nr->fraglen, GFP_ATOMIC)) == NULL)
+ return 1;
+
+ skbn->h.raw = skbn->data;
+
+ while ((skbo = skb_dequeue(&sk->protinfo.nr->frag_queue)) != NULL) {
+ memcpy(skb_put(skbn, skbo->len), skbo->data, skbo->len);
+ kfree_skb(skbo);
+ }
+
+ sk->protinfo.nr->fraglen = 0;
+ }
+
+ return sock_queue_rcv_skb(sk, skbn);
+}
+
+/*
+ * State machine for state 1, Awaiting Connection State.
+ * The handling of the timer(s) is in file nr_timer.c.
+ * Handling of state 0 and connection release is in netrom.c.
+ */
+static int nr_state1_machine(struct sock *sk, struct sk_buff *skb, int frametype)
+{
+ switch (frametype) {
+
+ case NR_CONNACK:
+ nr_stop_t1timer(sk);
+ nr_start_idletimer(sk);
+ sk->protinfo.nr->your_index = skb->data[17];
+ sk->protinfo.nr->your_id = skb->data[18];
+ sk->protinfo.nr->vs = 0;
+ sk->protinfo.nr->va = 0;
+ sk->protinfo.nr->vr = 0;
+ sk->protinfo.nr->vl = 0;
+ sk->protinfo.nr->state = NR_STATE_3;
+ sk->protinfo.nr->n2count = 0;
+ sk->protinfo.nr->window = skb->data[20];
+ sk->state = TCP_ESTABLISHED;
+ if (!sk->dead)
+ sk->state_change(sk);
+ break;
+
+ case NR_CONNACK | NR_CHOKE_FLAG:
+ nr_disconnect(sk, ECONNREFUSED);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * State machine for state 2, Awaiting Release State.
+ * The handling of the timer(s) is in file nr_timer.c
+ * Handling of state 0 and connection release is in netrom.c.
+ */
+static int nr_state2_machine(struct sock *sk, struct sk_buff *skb, int frametype)
+{
+ switch (frametype) {
+
+ case NR_CONNACK | NR_CHOKE_FLAG:
+ nr_disconnect(sk, ECONNRESET);
+ break;
+
+ case NR_DISCREQ:
+ nr_write_internal(sk, NR_DISCACK);
+
+ case NR_DISCACK:
+ nr_disconnect(sk, 0);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * State machine for state 3, Connected State.
+ * The handling of the timer(s) is in file nr_timer.c
+ * Handling of state 0 and connection release is in netrom.c.
+ */
+static int nr_state3_machine(struct sock *sk, struct sk_buff *skb, int frametype)
+{
+ struct sk_buff_head temp_queue;
+ struct sk_buff *skbn;
+ unsigned short save_vr;
+ unsigned short nr, ns;
+ int queued = 0;
+
+ nr = skb->data[18];
+ ns = skb->data[17];
+
+ switch (frametype) {
+
+ case NR_CONNREQ:
+ nr_write_internal(sk, NR_CONNACK);
+ break;
+
+ case NR_DISCREQ:
+ nr_write_internal(sk, NR_DISCACK);
+ nr_disconnect(sk, 0);
+ break;
+
+ case NR_CONNACK | NR_CHOKE_FLAG:
+ case NR_DISCACK:
+ nr_disconnect(sk, ECONNRESET);
+ break;
+
+ case NR_INFOACK:
+ case NR_INFOACK | NR_CHOKE_FLAG:
+ case NR_INFOACK | NR_NAK_FLAG:
+ case NR_INFOACK | NR_NAK_FLAG | NR_CHOKE_FLAG:
+ if (frametype & NR_CHOKE_FLAG) {
+ sk->protinfo.nr->condition |= NR_COND_PEER_RX_BUSY;
+ nr_start_t4timer(sk);
+ } else {
+ sk->protinfo.nr->condition &= ~NR_COND_PEER_RX_BUSY;
+ nr_stop_t4timer(sk);
+ }
+ if (!nr_validate_nr(sk, nr)) {
+ break;
+ }
+ if (frametype & NR_NAK_FLAG) {
+ nr_frames_acked(sk, nr);
+ nr_send_nak_frame(sk);
+ } else {
+ if (sk->protinfo.nr->condition & NR_COND_PEER_RX_BUSY) {
+ nr_frames_acked(sk, nr);
+ } else {
+ nr_check_iframes_acked(sk, nr);
+ }
+ }
+ break;
+
+ case NR_INFO:
+ case NR_INFO | NR_NAK_FLAG:
+ case NR_INFO | NR_CHOKE_FLAG:
+ case NR_INFO | NR_MORE_FLAG:
+ case NR_INFO | NR_NAK_FLAG | NR_CHOKE_FLAG:
+ case NR_INFO | NR_CHOKE_FLAG | NR_MORE_FLAG:
+ case NR_INFO | NR_NAK_FLAG | NR_MORE_FLAG:
+ case NR_INFO | NR_NAK_FLAG | NR_CHOKE_FLAG | NR_MORE_FLAG:
+ if (frametype & NR_CHOKE_FLAG) {
+ sk->protinfo.nr->condition |= NR_COND_PEER_RX_BUSY;
+ nr_start_t4timer(sk);
+ } else {
+ sk->protinfo.nr->condition &= ~NR_COND_PEER_RX_BUSY;
+ nr_stop_t4timer(sk);
+ }
+ if (nr_validate_nr(sk, nr)) {
+ if (frametype & NR_NAK_FLAG) {
+ nr_frames_acked(sk, nr);
+ nr_send_nak_frame(sk);
+ } else {
+ if (sk->protinfo.nr->condition & NR_COND_PEER_RX_BUSY) {
+ nr_frames_acked(sk, nr);
+ } else {
+ nr_check_iframes_acked(sk, nr);
+ }
+ }
+ }
+ queued = 1;
+ skb_queue_head(&sk->protinfo.nr->reseq_queue, skb);
+ if (sk->protinfo.nr->condition & NR_COND_OWN_RX_BUSY)
+ break;
+ skb_queue_head_init(&temp_queue);
+ do {
+ save_vr = sk->protinfo.nr->vr;
+ while ((skbn = skb_dequeue(&sk->protinfo.nr->reseq_queue)) != NULL) {
+ ns = skbn->data[17];
+ if (ns == sk->protinfo.nr->vr) {
+ if (nr_queue_rx_frame(sk, skbn, frametype & NR_MORE_FLAG) == 0) {
+ sk->protinfo.nr->vr = (sk->protinfo.nr->vr + 1) % NR_MODULUS;
+ } else {
+ sk->protinfo.nr->condition |= NR_COND_OWN_RX_BUSY;
+ skb_queue_tail(&temp_queue, skbn);
+ }
+ } else if (nr_in_rx_window(sk, ns)) {
+ skb_queue_tail(&temp_queue, skbn);
+ } else {
+ kfree_skb(skbn);
+ }
+ }
+ while ((skbn = skb_dequeue(&temp_queue)) != NULL) {
+ skb_queue_tail(&sk->protinfo.nr->reseq_queue, skbn);
+ }
+ } while (save_vr != sk->protinfo.nr->vr);
+ /*
+ * Window is full, ack it immediately.
+ */
+ if (((sk->protinfo.nr->vl + sk->protinfo.nr->window) % NR_MODULUS) == sk->protinfo.nr->vr) {
+ nr_enquiry_response(sk);
+ } else {
+ if (!(sk->protinfo.nr->condition & NR_COND_ACK_PENDING)) {
+ sk->protinfo.nr->condition |= NR_COND_ACK_PENDING;
+ nr_start_t2timer(sk);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return queued;
+}
+
+/* Higher level upcall for a LAPB frame */
+int nr_process_rx_frame(struct sock *sk, struct sk_buff *skb)
+{
+ int queued = 0, frametype;
+
+ if (sk->protinfo.nr->state == NR_STATE_0)
+ return 0;
+
+ frametype = skb->data[19];
+
+ switch (sk->protinfo.nr->state) {
+ case NR_STATE_1:
+ queued = nr_state1_machine(sk, skb, frametype);
+ break;
+ case NR_STATE_2:
+ queued = nr_state2_machine(sk, skb, frametype);
+ break;
+ case NR_STATE_3:
+ queued = nr_state3_machine(sk, skb, frametype);
+ break;
+ }
+
+ nr_kick(sk);
+
+ return queued;
+}
diff --git a/uClinux-2.4.31-uc0/net/netrom/nr_loopback.c b/uClinux-2.4.31-uc0/net/netrom/nr_loopback.c
new file mode 100644
index 0000000..d0402b4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/netrom/nr_loopback.c
@@ -0,0 +1,100 @@
+/*
+ * NET/ROM release 007
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * NET/ROM 007 Tomi(OH2BNS) Created this file.
+ * Small change in nr_loopback_queue().
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/timer.h>
+#include <net/ax25.h>
+#include <linux/skbuff.h>
+#include <net/netrom.h>
+#include <linux/init.h>
+
+static struct sk_buff_head loopback_queue;
+static struct timer_list loopback_timer;
+
+static void nr_set_loopback_timer(void);
+
+void nr_loopback_init(void)
+{
+ skb_queue_head_init(&loopback_queue);
+
+ init_timer(&loopback_timer);
+}
+
+static int nr_loopback_running(void)
+{
+ return timer_pending(&loopback_timer);
+}
+
+int nr_loopback_queue(struct sk_buff *skb)
+{
+ struct sk_buff *skbn;
+
+ if ((skbn = alloc_skb(skb->len, GFP_ATOMIC)) != NULL) {
+ memcpy(skb_put(skbn, skb->len), skb->data, skb->len);
+ skbn->h.raw = skbn->data;
+
+ skb_queue_tail(&loopback_queue, skbn);
+
+ if (!nr_loopback_running())
+ nr_set_loopback_timer();
+ }
+
+ kfree_skb(skb);
+ return 1;
+}
+
+static void nr_loopback_timer(unsigned long);
+
+static void nr_set_loopback_timer(void)
+{
+ del_timer(&loopback_timer);
+
+ loopback_timer.data = 0;
+ loopback_timer.function = &nr_loopback_timer;
+ loopback_timer.expires = jiffies + 10;
+
+ add_timer(&loopback_timer);
+}
+
+static void nr_loopback_timer(unsigned long param)
+{
+ struct sk_buff *skb;
+ ax25_address *nr_dest;
+ struct net_device *dev;
+
+ if ((skb = skb_dequeue(&loopback_queue)) != NULL) {
+ nr_dest = (ax25_address *)(skb->data + 7);
+
+ dev = nr_dev_get(nr_dest);
+
+ if (dev == NULL || nr_rx_frame(skb, dev) == 0)
+ kfree_skb(skb);
+
+ if (dev != NULL)
+ dev_put(dev);
+
+ if (!skb_queue_empty(&loopback_queue) && !nr_loopback_running())
+ nr_set_loopback_timer();
+ }
+}
+
+void __exit nr_loopback_clear(void)
+{
+ del_timer(&loopback_timer);
+ skb_queue_purge(&loopback_queue);
+}
diff --git a/uClinux-2.4.31-uc0/net/netrom/nr_out.c b/uClinux-2.4.31-uc0/net/netrom/nr_out.c
new file mode 100644
index 0000000..d602377
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/netrom/nr_out.c
@@ -0,0 +1,272 @@
+/*
+ * NET/ROM release 007
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * NET/ROM 001 Jonathan(G4KLX) Cloned from ax25_out.c
+ * NET/ROM 003 Jonathan(G4KLX) Added NET/ROM fragmentation.
+ * Darryl(G7LED) Fixed NAK, to give out correct reponse.
+ * NET/ROM 007 Jonathan(G4KLX) New timer architecture.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/netrom.h>
+
+/*
+ * This is where all NET/ROM frames pass, except for IP-over-NET/ROM which
+ * cannot be fragmented in this manner.
+ */
+void nr_output(struct sock *sk, struct sk_buff *skb)
+{
+ struct sk_buff *skbn;
+ unsigned char transport[NR_TRANSPORT_LEN];
+ int err, frontlen, len;
+
+ if (skb->len - NR_TRANSPORT_LEN > NR_MAX_PACKET_SIZE) {
+ /* Save a copy of the Transport Header */
+ memcpy(transport, skb->data, NR_TRANSPORT_LEN);
+ skb_pull(skb, NR_TRANSPORT_LEN);
+
+ frontlen = skb_headroom(skb);
+
+ while (skb->len > 0) {
+ if ((skbn = sock_alloc_send_skb(sk, frontlen + NR_MAX_PACKET_SIZE, 0, &err)) == NULL)
+ return;
+
+ skb_reserve(skbn, frontlen);
+
+ len = (NR_MAX_PACKET_SIZE > skb->len) ? skb->len : NR_MAX_PACKET_SIZE;
+
+ /* Copy the user data */
+ memcpy(skb_put(skbn, len), skb->data, len);
+ skb_pull(skb, len);
+
+ /* Duplicate the Transport Header */
+ skb_push(skbn, NR_TRANSPORT_LEN);
+ memcpy(skbn->data, transport, NR_TRANSPORT_LEN);
+
+ if (skb->len > 0)
+ skbn->data[4] |= NR_MORE_FLAG;
+
+ skb_queue_tail(&sk->write_queue, skbn); /* Throw it on the queue */
+ }
+
+ kfree_skb(skb);
+ } else {
+ skb_queue_tail(&sk->write_queue, skb); /* Throw it on the queue */
+ }
+
+ nr_kick(sk);
+}
+
+/*
+ * This procedure is passed a buffer descriptor for an iframe. It builds
+ * the rest of the control part of the frame and then writes it out.
+ */
+static void nr_send_iframe(struct sock *sk, struct sk_buff *skb)
+{
+ if (skb == NULL)
+ return;
+
+ skb->data[2] = sk->protinfo.nr->vs;
+ skb->data[3] = sk->protinfo.nr->vr;
+
+ if (sk->protinfo.nr->condition & NR_COND_OWN_RX_BUSY)
+ skb->data[4] |= NR_CHOKE_FLAG;
+
+ nr_start_idletimer(sk);
+
+ nr_transmit_buffer(sk, skb);
+}
+
+void nr_send_nak_frame(struct sock *sk)
+{
+ struct sk_buff *skb, *skbn;
+
+ if ((skb = skb_peek(&sk->protinfo.nr->ack_queue)) == NULL)
+ return;
+
+ if ((skbn = skb_clone(skb, GFP_ATOMIC)) == NULL)
+ return;
+
+ skbn->data[2] = sk->protinfo.nr->va;
+ skbn->data[3] = sk->protinfo.nr->vr;
+
+ if (sk->protinfo.nr->condition & NR_COND_OWN_RX_BUSY)
+ skbn->data[4] |= NR_CHOKE_FLAG;
+
+ nr_transmit_buffer(sk, skbn);
+
+ sk->protinfo.nr->condition &= ~NR_COND_ACK_PENDING;
+ sk->protinfo.nr->vl = sk->protinfo.nr->vr;
+
+ nr_stop_t1timer(sk);
+}
+
+void nr_kick(struct sock *sk)
+{
+ struct sk_buff *skb, *skbn;
+ unsigned short start, end;
+
+ if (sk->protinfo.nr->state != NR_STATE_3)
+ return;
+
+ if (sk->protinfo.nr->condition & NR_COND_PEER_RX_BUSY)
+ return;
+
+ if (skb_peek(&sk->write_queue) == NULL)
+ return;
+
+ start = (skb_peek(&sk->protinfo.nr->ack_queue) == NULL) ? sk->protinfo.nr->va : sk->protinfo.nr->vs;
+ end = (sk->protinfo.nr->va + sk->protinfo.nr->window) % NR_MODULUS;
+
+ if (start == end)
+ return;
+
+ sk->protinfo.nr->vs = start;
+
+ /*
+ * Transmit data until either we're out of data to send or
+ * the window is full.
+ */
+
+ /*
+ * Dequeue the frame and copy it.
+ */
+ skb = skb_dequeue(&sk->write_queue);
+
+ do {
+ if ((skbn = skb_clone(skb, GFP_ATOMIC)) == NULL) {
+ skb_queue_head(&sk->write_queue, skb);
+ break;
+ }
+
+ skb_set_owner_w(skbn, sk);
+
+ /*
+ * Transmit the frame copy.
+ */
+ nr_send_iframe(sk, skbn);
+
+ sk->protinfo.nr->vs = (sk->protinfo.nr->vs + 1) % NR_MODULUS;
+
+ /*
+ * Requeue the original data frame.
+ */
+ skb_queue_tail(&sk->protinfo.nr->ack_queue, skb);
+
+ } while (sk->protinfo.nr->vs != end && (skb = skb_dequeue(&sk->write_queue)) != NULL);
+
+ sk->protinfo.nr->vl = sk->protinfo.nr->vr;
+ sk->protinfo.nr->condition &= ~NR_COND_ACK_PENDING;
+
+ if (!nr_t1timer_running(sk))
+ nr_start_t1timer(sk);
+}
+
+void nr_transmit_buffer(struct sock *sk, struct sk_buff *skb)
+{
+ unsigned char *dptr;
+
+ /*
+ * Add the protocol byte and network header.
+ */
+ dptr = skb_push(skb, NR_NETWORK_LEN);
+
+ memcpy(dptr, &sk->protinfo.nr->source_addr, AX25_ADDR_LEN);
+ dptr[6] &= ~AX25_CBIT;
+ dptr[6] &= ~AX25_EBIT;
+ dptr[6] |= AX25_SSSID_SPARE;
+ dptr += AX25_ADDR_LEN;
+
+ memcpy(dptr, &sk->protinfo.nr->dest_addr, AX25_ADDR_LEN);
+ dptr[6] &= ~AX25_CBIT;
+ dptr[6] |= AX25_EBIT;
+ dptr[6] |= AX25_SSSID_SPARE;
+ dptr += AX25_ADDR_LEN;
+
+ *dptr++ = sysctl_netrom_network_ttl_initialiser;
+
+ if (!nr_route_frame(skb, NULL)) {
+ kfree_skb(skb);
+ nr_disconnect(sk, ENETUNREACH);
+ }
+}
+
+/*
+ * The following routines are taken from page 170 of the 7th ARRL Computer
+ * Networking Conference paper, as is the whole state machine.
+ */
+
+void nr_establish_data_link(struct sock *sk)
+{
+ sk->protinfo.nr->condition = 0x00;
+ sk->protinfo.nr->n2count = 0;
+
+ nr_write_internal(sk, NR_CONNREQ);
+
+ nr_stop_t2timer(sk);
+ nr_stop_t4timer(sk);
+ nr_stop_idletimer(sk);
+ nr_start_t1timer(sk);
+}
+
+/*
+ * Never send a NAK when we are CHOKEd.
+ */
+void nr_enquiry_response(struct sock *sk)
+{
+ int frametype = NR_INFOACK;
+
+ if (sk->protinfo.nr->condition & NR_COND_OWN_RX_BUSY) {
+ frametype |= NR_CHOKE_FLAG;
+ } else {
+ if (skb_peek(&sk->protinfo.nr->reseq_queue) != NULL)
+ frametype |= NR_NAK_FLAG;
+ }
+
+ nr_write_internal(sk, frametype);
+
+ sk->protinfo.nr->vl = sk->protinfo.nr->vr;
+ sk->protinfo.nr->condition &= ~NR_COND_ACK_PENDING;
+}
+
+void nr_check_iframes_acked(struct sock *sk, unsigned short nr)
+{
+ if (sk->protinfo.nr->vs == nr) {
+ nr_frames_acked(sk, nr);
+ nr_stop_t1timer(sk);
+ sk->protinfo.nr->n2count = 0;
+ } else {
+ if (sk->protinfo.nr->va != nr) {
+ nr_frames_acked(sk, nr);
+ nr_start_t1timer(sk);
+ }
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/netrom/nr_route.c b/uClinux-2.4.31-uc0/net/netrom/nr_route.c
new file mode 100644
index 0000000..573a21e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/netrom/nr_route.c
@@ -0,0 +1,908 @@
+/*
+ * NET/ROM release 007
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * NET/ROM 001 Jonathan(G4KLX) First attempt.
+ * NET/ROM 003 Jonathan(G4KLX) Use SIOCADDRT/SIOCDELRT ioctl values
+ * for NET/ROM routes.
+ * Use '*' for a blank mnemonic in /proc/net/nr_nodes.
+ * Change default quality for new neighbour when same
+ * as node callsign.
+ * Alan Cox(GW4PTS) Added the firewall hooks.
+ * NET/ROM 006 Jonathan(G4KLX) Added the setting of digipeated neighbours.
+ * Tomi(OH2BNS) Routing quality and link failure changes.
+ * Device refcnt fixes.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <net/arp.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/termios.h> /* For TIOCINQ/OUTQ */
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/netfilter.h>
+#include <linux/init.h>
+#include <net/netrom.h>
+
+static unsigned int nr_neigh_no = 1;
+
+static struct nr_node *nr_node_list;
+static struct nr_neigh *nr_neigh_list;
+
+static void nr_remove_neigh(struct nr_neigh *);
+
+/*
+ * Add a new route to a node, and in the process add the node and the
+ * neighbour if it is new.
+ */
+static int nr_add_node(ax25_address *nr, const char *mnemonic, ax25_address *ax25,
+ ax25_digi *ax25_digi, struct net_device *dev, int quality, int obs_count)
+{
+ struct nr_node *nr_node;
+ struct nr_neigh *nr_neigh;
+ struct nr_route nr_route;
+ struct net_device *tdev;
+ unsigned long flags;
+ int i, found;
+
+ /* Can't add routes to ourself */
+ if ((tdev = nr_dev_get(nr)) != NULL) {
+ dev_put(tdev);
+ return -EINVAL;
+ }
+
+ for (nr_node = nr_node_list; nr_node != NULL; nr_node = nr_node->next)
+ if (ax25cmp(nr, &nr_node->callsign) == 0)
+ break;
+
+ for (nr_neigh = nr_neigh_list; nr_neigh != NULL; nr_neigh = nr_neigh->next)
+ if (ax25cmp(ax25, &nr_neigh->callsign) == 0 && nr_neigh->dev == dev)
+ break;
+
+ /*
+ * The L2 link to a neighbour has failed in the past
+ * and now a frame comes from this neighbour. We assume
+ * it was a temporary trouble with the link and reset the
+ * routes now (and not wait for a node broadcast).
+ */
+ if (nr_neigh != NULL && nr_neigh->failed != 0 && quality == 0) {
+ struct nr_node *node;
+
+ for (node = nr_node_list; node != NULL; node = node->next)
+ for (i = 0; i < node->count; i++)
+ if (node->routes[i].neighbour == nr_neigh)
+ if (i < node->which)
+ node->which = i;
+ }
+
+ if (nr_neigh != NULL)
+ nr_neigh->failed = 0;
+
+ if (quality == 0 && nr_neigh != NULL && nr_node != NULL)
+ return 0;
+
+ if (nr_neigh == NULL) {
+ if ((nr_neigh = kmalloc(sizeof(*nr_neigh), GFP_ATOMIC)) == NULL)
+ return -ENOMEM;
+
+ nr_neigh->callsign = *ax25;
+ nr_neigh->digipeat = NULL;
+ nr_neigh->ax25 = NULL;
+ nr_neigh->dev = dev;
+ nr_neigh->quality = sysctl_netrom_default_path_quality;
+ nr_neigh->locked = 0;
+ nr_neigh->count = 0;
+ nr_neigh->number = nr_neigh_no++;
+ nr_neigh->failed = 0;
+
+ if (ax25_digi != NULL && ax25_digi->ndigi > 0) {
+ if ((nr_neigh->digipeat = kmalloc(sizeof(*ax25_digi), GFP_KERNEL)) == NULL) {
+ kfree(nr_neigh);
+ return -ENOMEM;
+ }
+ memcpy(nr_neigh->digipeat, ax25_digi, sizeof(ax25_digi));
+ }
+
+ dev_hold(nr_neigh->dev);
+
+ save_flags(flags);
+ cli();
+
+ nr_neigh->next = nr_neigh_list;
+ nr_neigh_list = nr_neigh;
+
+ restore_flags(flags);
+ }
+
+ if (quality != 0 && ax25cmp(nr, ax25) == 0 && !nr_neigh->locked)
+ nr_neigh->quality = quality;
+
+ if (nr_node == NULL) {
+ if ((nr_node = kmalloc(sizeof(*nr_node), GFP_ATOMIC)) == NULL)
+ return -ENOMEM;
+
+ nr_node->callsign = *nr;
+ strcpy(nr_node->mnemonic, mnemonic);
+
+ nr_node->which = 0;
+ nr_node->count = 1;
+
+ nr_node->routes[0].quality = quality;
+ nr_node->routes[0].obs_count = obs_count;
+ nr_node->routes[0].neighbour = nr_neigh;
+
+ save_flags(flags);
+ cli();
+
+ nr_node->next = nr_node_list;
+ nr_node_list = nr_node;
+
+ restore_flags(flags);
+
+ nr_neigh->count++;
+
+ return 0;
+ }
+
+ if (quality != 0)
+ strcpy(nr_node->mnemonic, mnemonic);
+
+ for (found = 0, i = 0; i < nr_node->count; i++) {
+ if (nr_node->routes[i].neighbour == nr_neigh) {
+ nr_node->routes[i].quality = quality;
+ nr_node->routes[i].obs_count = obs_count;
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ /* We have space at the bottom, slot it in */
+ if (nr_node->count < 3) {
+ nr_node->routes[2] = nr_node->routes[1];
+ nr_node->routes[1] = nr_node->routes[0];
+
+ nr_node->routes[0].quality = quality;
+ nr_node->routes[0].obs_count = obs_count;
+ nr_node->routes[0].neighbour = nr_neigh;
+
+ nr_node->which++;
+ nr_node->count++;
+ nr_neigh->count++;
+ } else {
+ /* It must be better than the worst */
+ if (quality > nr_node->routes[2].quality) {
+ nr_node->routes[2].neighbour->count--;
+
+ if (nr_node->routes[2].neighbour->count == 0 && !nr_node->routes[2].neighbour->locked)
+ nr_remove_neigh(nr_node->routes[2].neighbour);
+
+ nr_node->routes[2].quality = quality;
+ nr_node->routes[2].obs_count = obs_count;
+ nr_node->routes[2].neighbour = nr_neigh;
+
+ nr_neigh->count++;
+ }
+ }
+ }
+
+ /* Now re-sort the routes in quality order */
+ switch (nr_node->count) {
+ case 3:
+ if (nr_node->routes[1].quality > nr_node->routes[0].quality) {
+ switch (nr_node->which) {
+ case 0: nr_node->which = 1; break;
+ case 1: nr_node->which = 0; break;
+ default: break;
+ }
+ nr_route = nr_node->routes[0];
+ nr_node->routes[0] = nr_node->routes[1];
+ nr_node->routes[1] = nr_route;
+ }
+ if (nr_node->routes[2].quality > nr_node->routes[1].quality) {
+ switch (nr_node->which) {
+ case 1: nr_node->which = 2; break;
+ case 2: nr_node->which = 1; break;
+ default: break;
+ }
+ nr_route = nr_node->routes[1];
+ nr_node->routes[1] = nr_node->routes[2];
+ nr_node->routes[2] = nr_route;
+ }
+ case 2:
+ if (nr_node->routes[1].quality > nr_node->routes[0].quality) {
+ switch (nr_node->which) {
+ case 0: nr_node->which = 1; break;
+ case 1: nr_node->which = 0; break;
+ default: break;
+ }
+ nr_route = nr_node->routes[0];
+ nr_node->routes[0] = nr_node->routes[1];
+ nr_node->routes[1] = nr_route;
+ }
+ case 1:
+ break;
+ }
+
+ for (i = 0; i < nr_node->count; i++) {
+ if (nr_node->routes[i].neighbour == nr_neigh) {
+ if (i < nr_node->which)
+ nr_node->which = i;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static void nr_remove_node(struct nr_node *nr_node)
+{
+ struct nr_node *s;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ if ((s = nr_node_list) == nr_node) {
+ nr_node_list = nr_node->next;
+ restore_flags(flags);
+ kfree(nr_node);
+ return;
+ }
+
+ while (s != NULL && s->next != NULL) {
+ if (s->next == nr_node) {
+ s->next = nr_node->next;
+ restore_flags(flags);
+ kfree(nr_node);
+ return;
+ }
+
+ s = s->next;
+ }
+
+ restore_flags(flags);
+}
+
+static void nr_remove_neigh(struct nr_neigh *nr_neigh)
+{
+ struct nr_neigh *s;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ if ((s = nr_neigh_list) == nr_neigh) {
+ nr_neigh_list = nr_neigh->next;
+ restore_flags(flags);
+ dev_put(nr_neigh->dev);
+ if (nr_neigh->digipeat != NULL)
+ kfree(nr_neigh->digipeat);
+ kfree(nr_neigh);
+ return;
+ }
+
+ while (s != NULL && s->next != NULL) {
+ if (s->next == nr_neigh) {
+ s->next = nr_neigh->next;
+ restore_flags(flags);
+ dev_put(nr_neigh->dev);
+ if (nr_neigh->digipeat != NULL)
+ kfree(nr_neigh->digipeat);
+ kfree(nr_neigh);
+ return;
+ }
+
+ s = s->next;
+ }
+
+ restore_flags(flags);
+}
+
+/*
+ * "Delete" a node. Strictly speaking remove a route to a node. The node
+ * is only deleted if no routes are left to it.
+ */
+static int nr_del_node(ax25_address *callsign, ax25_address *neighbour, struct net_device *dev)
+{
+ struct nr_node *nr_node;
+ struct nr_neigh *nr_neigh;
+ int i;
+
+ for (nr_node = nr_node_list; nr_node != NULL; nr_node = nr_node->next)
+ if (ax25cmp(callsign, &nr_node->callsign) == 0)
+ break;
+
+ if (nr_node == NULL) return -EINVAL;
+
+ for (nr_neigh = nr_neigh_list; nr_neigh != NULL; nr_neigh = nr_neigh->next)
+ if (ax25cmp(neighbour, &nr_neigh->callsign) == 0 && nr_neigh->dev == dev)
+ break;
+
+ if (nr_neigh == NULL) return -EINVAL;
+
+ for (i = 0; i < nr_node->count; i++) {
+ if (nr_node->routes[i].neighbour == nr_neigh) {
+ nr_neigh->count--;
+
+ if (nr_neigh->count == 0 && !nr_neigh->locked)
+ nr_remove_neigh(nr_neigh);
+
+ nr_node->count--;
+
+ if (nr_node->count == 0) {
+ nr_remove_node(nr_node);
+ } else {
+ switch (i) {
+ case 0:
+ nr_node->routes[0] = nr_node->routes[1];
+ case 1:
+ nr_node->routes[1] = nr_node->routes[2];
+ case 2:
+ break;
+ }
+ }
+
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/*
+ * Lock a neighbour with a quality.
+ */
+static int nr_add_neigh(ax25_address *callsign, ax25_digi *ax25_digi, struct net_device *dev, unsigned int quality)
+{
+ struct nr_neigh *nr_neigh;
+ unsigned long flags;
+
+ for (nr_neigh = nr_neigh_list; nr_neigh != NULL; nr_neigh = nr_neigh->next) {
+ if (ax25cmp(callsign, &nr_neigh->callsign) == 0 && nr_neigh->dev == dev) {
+ nr_neigh->quality = quality;
+ nr_neigh->locked = 1;
+ return 0;
+ }
+ }
+
+ if ((nr_neigh = kmalloc(sizeof(*nr_neigh), GFP_ATOMIC)) == NULL)
+ return -ENOMEM;
+
+ nr_neigh->callsign = *callsign;
+ nr_neigh->digipeat = NULL;
+ nr_neigh->ax25 = NULL;
+ nr_neigh->dev = dev;
+ nr_neigh->quality = quality;
+ nr_neigh->locked = 1;
+ nr_neigh->count = 0;
+ nr_neigh->number = nr_neigh_no++;
+ nr_neigh->failed = 0;
+
+ if (ax25_digi != NULL && ax25_digi->ndigi > 0) {
+ if ((nr_neigh->digipeat = kmalloc(sizeof(*ax25_digi), GFP_KERNEL)) == NULL) {
+ kfree(nr_neigh);
+ return -ENOMEM;
+ }
+ memcpy(nr_neigh->digipeat, ax25_digi, sizeof(ax25_digi));
+ }
+
+ dev_hold(nr_neigh->dev);
+
+ save_flags(flags);
+ cli();
+
+ nr_neigh->next = nr_neigh_list;
+ nr_neigh_list = nr_neigh;
+
+ restore_flags(flags);
+
+ return 0;
+}
+
+/*
+ * "Delete" a neighbour. The neighbour is only removed if the number
+ * of nodes that may use it is zero.
+ */
+static int nr_del_neigh(ax25_address *callsign, struct net_device *dev, unsigned int quality)
+{
+ struct nr_neigh *nr_neigh;
+
+ for (nr_neigh = nr_neigh_list; nr_neigh != NULL; nr_neigh = nr_neigh->next)
+ if (ax25cmp(callsign, &nr_neigh->callsign) == 0 && nr_neigh->dev == dev)
+ break;
+
+ if (nr_neigh == NULL) return -EINVAL;
+
+ nr_neigh->quality = quality;
+ nr_neigh->locked = 0;
+
+ if (nr_neigh->count == 0)
+ nr_remove_neigh(nr_neigh);
+
+ return 0;
+}
+
+/*
+ * Decrement the obsolescence count by one. If a route is reduced to a
+ * count of zero, remove it. Also remove any unlocked neighbours with
+ * zero nodes routing via it.
+ */
+static int nr_dec_obs(void)
+{
+ struct nr_neigh *nr_neigh;
+ struct nr_node *s, *nr_node;
+ int i;
+
+ nr_node = nr_node_list;
+
+ while (nr_node != NULL) {
+ s = nr_node;
+ nr_node = nr_node->next;
+
+ for (i = 0; i < s->count; i++) {
+ switch (s->routes[i].obs_count) {
+
+ case 0: /* A locked entry */
+ break;
+
+ case 1: /* From 1 -> 0 */
+ nr_neigh = s->routes[i].neighbour;
+
+ nr_neigh->count--;
+
+ if (nr_neigh->count == 0 && !nr_neigh->locked)
+ nr_remove_neigh(nr_neigh);
+
+ s->count--;
+
+ switch (i) {
+ case 0:
+ s->routes[0] = s->routes[1];
+ case 1:
+ s->routes[1] = s->routes[2];
+ case 2:
+ break;
+ }
+ break;
+
+ default:
+ s->routes[i].obs_count--;
+ break;
+
+ }
+ }
+
+ if (s->count <= 0)
+ nr_remove_node(s);
+ }
+
+ return 0;
+}
+
+/*
+ * A device has been removed. Remove its routes and neighbours.
+ */
+void nr_rt_device_down(struct net_device *dev)
+{
+ struct nr_neigh *s, *nr_neigh = nr_neigh_list;
+ struct nr_node *t, *nr_node;
+ int i;
+
+ while (nr_neigh != NULL) {
+ s = nr_neigh;
+ nr_neigh = nr_neigh->next;
+
+ if (s->dev == dev) {
+ nr_node = nr_node_list;
+
+ while (nr_node != NULL) {
+ t = nr_node;
+ nr_node = nr_node->next;
+
+ for (i = 0; i < t->count; i++) {
+ if (t->routes[i].neighbour == s) {
+ t->count--;
+
+ switch (i) {
+ case 0:
+ t->routes[0] = t->routes[1];
+ case 1:
+ t->routes[1] = t->routes[2];
+ case 2:
+ break;
+ }
+ }
+ }
+
+ if (t->count <= 0)
+ nr_remove_node(t);
+ }
+
+ nr_remove_neigh(s);
+ }
+ }
+}
+
+/*
+ * Check that the device given is a valid AX.25 interface that is "up".
+ * Or a valid ethernet interface with an AX.25 callsign binding.
+ */
+static struct net_device *nr_ax25_dev_get(char *devname)
+{
+ struct net_device *dev;
+
+ if ((dev = dev_get_by_name(devname)) == NULL)
+ return NULL;
+
+ if ((dev->flags & IFF_UP) && dev->type == ARPHRD_AX25)
+ return dev;
+
+ dev_put(dev);
+ return NULL;
+}
+
+/*
+ * Find the first active NET/ROM device, usually "nr0".
+ */
+struct net_device *nr_dev_first(void)
+{
+ struct net_device *dev, *first = NULL;
+
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev != NULL; dev = dev->next) {
+ if ((dev->flags & IFF_UP) && dev->type == ARPHRD_NETROM)
+ if (first == NULL || strncmp(dev->name, first->name, 3) < 0)
+ first = dev;
+ }
+
+ if (first != NULL)
+ dev_hold(first);
+
+ read_unlock(&dev_base_lock);
+
+ return first;
+}
+
+/*
+ * Find the NET/ROM device for the given callsign.
+ */
+struct net_device *nr_dev_get(ax25_address *addr)
+{
+ struct net_device *dev;
+
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev != NULL; dev = dev->next) {
+ if ((dev->flags & IFF_UP) && dev->type == ARPHRD_NETROM && ax25cmp(addr, (ax25_address *)dev->dev_addr) == 0) {
+ dev_hold(dev);
+ goto out;
+ }
+ }
+out:
+ read_unlock(&dev_base_lock);
+ return dev;
+}
+
+static ax25_digi *nr_call_to_digi(int ndigis, ax25_address *digipeaters)
+{
+ static ax25_digi ax25_digi;
+ int i;
+
+ if (ndigis == 0)
+ return NULL;
+
+ for (i = 0; i < ndigis; i++) {
+ ax25_digi.calls[i] = digipeaters[i];
+ ax25_digi.repeated[i] = 0;
+ }
+
+ ax25_digi.ndigi = ndigis;
+ ax25_digi.lastrepeat = -1;
+
+ return &ax25_digi;
+}
+
+/*
+ * Handle the ioctls that control the routing functions.
+ */
+int nr_rt_ioctl(unsigned int cmd, void *arg)
+{
+ struct nr_route_struct nr_route;
+ struct net_device *dev;
+ int ret;
+
+ switch (cmd) {
+
+ case SIOCADDRT:
+ if (copy_from_user(&nr_route, arg, sizeof(struct nr_route_struct)))
+ return -EFAULT;
+ if ((dev = nr_ax25_dev_get(nr_route.device)) == NULL)
+ return -EINVAL;
+ if (nr_route.ndigis < 0 || nr_route.ndigis > AX25_MAX_DIGIS) {
+ dev_put(dev);
+ return -EINVAL;
+ }
+ switch (nr_route.type) {
+ case NETROM_NODE:
+ ret = nr_add_node(&nr_route.callsign,
+ nr_route.mnemonic,
+ &nr_route.neighbour,
+ nr_call_to_digi(nr_route.ndigis, nr_route.digipeaters),
+ dev, nr_route.quality,
+ nr_route.obs_count);
+ break;
+ case NETROM_NEIGH:
+ ret = nr_add_neigh(&nr_route.callsign,
+ nr_call_to_digi(nr_route.ndigis, nr_route.digipeaters),
+ dev, nr_route.quality);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ dev_put(dev);
+ return ret;
+
+ case SIOCDELRT:
+ if (copy_from_user(&nr_route, arg, sizeof(struct nr_route_struct)))
+ return -EFAULT;
+ if ((dev = nr_ax25_dev_get(nr_route.device)) == NULL)
+ return -EINVAL;
+ switch (nr_route.type) {
+ case NETROM_NODE:
+ ret = nr_del_node(&nr_route.callsign,
+ &nr_route.neighbour, dev);
+ break;
+ case NETROM_NEIGH:
+ ret = nr_del_neigh(&nr_route.callsign,
+ dev, nr_route.quality);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ dev_put(dev);
+ return ret;
+
+ case SIOCNRDECOBS:
+ return nr_dec_obs();
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * A level 2 link has timed out, therefore it appears to be a poor link,
+ * then don't use that neighbour until it is reset.
+ */
+void nr_link_failed(ax25_cb *ax25, int reason)
+{
+ struct nr_neigh *nr_neigh;
+ struct nr_node *nr_node;
+
+ for (nr_neigh = nr_neigh_list; nr_neigh != NULL; nr_neigh = nr_neigh->next)
+ if (nr_neigh->ax25 == ax25)
+ break;
+
+ if (nr_neigh == NULL) return;
+
+ nr_neigh->ax25 = NULL;
+
+ if (++nr_neigh->failed < sysctl_netrom_link_fails_count) return;
+
+ for (nr_node = nr_node_list; nr_node != NULL; nr_node = nr_node->next)
+ if (nr_node->which < nr_node->count && nr_node->routes[nr_node->which].neighbour == nr_neigh)
+ nr_node->which++;
+}
+
+/*
+ * Route a frame to an appropriate AX.25 connection. A NULL ax25_cb
+ * indicates an internally generated frame.
+ */
+int nr_route_frame(struct sk_buff *skb, ax25_cb *ax25)
+{
+ ax25_address *nr_src, *nr_dest;
+ struct nr_neigh *nr_neigh;
+ struct nr_node *nr_node;
+ struct net_device *dev;
+ unsigned char *dptr;
+
+
+ nr_src = (ax25_address *)(skb->data + 0);
+ nr_dest = (ax25_address *)(skb->data + 7);
+
+ if (ax25 != NULL)
+ nr_add_node(nr_src, "", &ax25->dest_addr, ax25->digipeat,
+ ax25->ax25_dev->dev, 0, sysctl_netrom_obsolescence_count_initialiser);
+
+ if ((dev = nr_dev_get(nr_dest)) != NULL) { /* Its for me */
+ int ret;
+
+ if (ax25 == NULL) /* Its from me */
+ ret = nr_loopback_queue(skb);
+ else
+ ret = nr_rx_frame(skb, dev);
+
+ dev_put(dev);
+ return ret;
+ }
+
+ if (!sysctl_netrom_routing_control && ax25 != NULL)
+ return 0;
+
+ /* Its Time-To-Live has expired */
+ if (--skb->data[14] == 0)
+ return 0;
+
+ for (nr_node = nr_node_list; nr_node != NULL; nr_node = nr_node->next)
+ if (ax25cmp(nr_dest, &nr_node->callsign) == 0)
+ break;
+
+ if (nr_node == NULL || nr_node->which >= nr_node->count)
+ return 0;
+
+ nr_neigh = nr_node->routes[nr_node->which].neighbour;
+
+ if ((dev = nr_dev_first()) == NULL)
+ return 0;
+
+ dptr = skb_push(skb, 1);
+ *dptr = AX25_P_NETROM;
+
+ nr_neigh->ax25 = ax25_send_frame(skb, 256, (ax25_address *)dev->dev_addr, &nr_neigh->callsign, nr_neigh->digipeat, nr_neigh->dev);
+
+ dev_put(dev);
+
+ return (nr_neigh->ax25 != NULL);
+}
+
+int nr_nodes_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct nr_node *nr_node;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+ int i;
+
+ cli();
+
+ len += sprintf(buffer, "callsign mnemonic w n qual obs neigh qual obs neigh qual obs neigh\n");
+
+ for (nr_node = nr_node_list; nr_node != NULL; nr_node = nr_node->next) {
+ len += sprintf(buffer + len, "%-9s %-7s %d %d",
+ ax2asc(&nr_node->callsign),
+ (nr_node->mnemonic[0] == '\0') ? "*" : nr_node->mnemonic,
+ nr_node->which + 1,
+ nr_node->count);
+
+ for (i = 0; i < nr_node->count; i++) {
+ len += sprintf(buffer + len, " %3d %d %05d",
+ nr_node->routes[i].quality,
+ nr_node->routes[i].obs_count,
+ nr_node->routes[i].neighbour->number);
+ }
+
+ len += sprintf(buffer + len, "\n");
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ break;
+ }
+
+ sti();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return len;
+}
+
+int nr_neigh_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct nr_neigh *nr_neigh;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+ int i;
+
+ cli();
+
+ len += sprintf(buffer, "addr callsign dev qual lock count failed digipeaters\n");
+
+ for (nr_neigh = nr_neigh_list; nr_neigh != NULL; nr_neigh = nr_neigh->next) {
+ len += sprintf(buffer + len, "%05d %-9s %-4s %3d %d %3d %3d",
+ nr_neigh->number,
+ ax2asc(&nr_neigh->callsign),
+ nr_neigh->dev ? nr_neigh->dev->name : "???",
+ nr_neigh->quality,
+ nr_neigh->locked,
+ nr_neigh->count,
+ nr_neigh->failed);
+
+ if (nr_neigh->digipeat != NULL) {
+ for (i = 0; i < nr_neigh->digipeat->ndigi; i++)
+ len += sprintf(buffer + len, " %s", ax2asc(&nr_neigh->digipeat->calls[i]));
+ }
+
+ len += sprintf(buffer + len, "\n");
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ break;
+ }
+
+ sti();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return len;
+}
+
+/*
+ * Free all memory associated with the nodes and routes lists.
+ */
+void __exit nr_rt_free(void)
+{
+ struct nr_neigh *s, *nr_neigh = nr_neigh_list;
+ struct nr_node *t, *nr_node = nr_node_list;
+
+ while (nr_node != NULL) {
+ t = nr_node;
+ nr_node = nr_node->next;
+
+ nr_remove_node(t);
+ }
+
+ while (nr_neigh != NULL) {
+ s = nr_neigh;
+ nr_neigh = nr_neigh->next;
+
+ nr_remove_neigh(s);
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/netrom/nr_subr.c b/uClinux-2.4.31-uc0/net/netrom/nr_subr.c
new file mode 100644
index 0000000..bd30fa7
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/netrom/nr_subr.c
@@ -0,0 +1,288 @@
+/*
+ * NET/ROM release 007
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * NET/ROM 001 Jonathan(G4KLX) Cloned from ax25_subr.c
+ * NET/ROM 003 Jonathan(G4KLX) Added G8BPQ NET/ROM extensions.
+ * NET/ROM 007 Jonathan(G4KLX) New timer architecture.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/netrom.h>
+
+/*
+ * This routine purges all of the queues of frames.
+ */
+void nr_clear_queues(struct sock *sk)
+{
+ skb_queue_purge(&sk->write_queue);
+ skb_queue_purge(&sk->protinfo.nr->ack_queue);
+ skb_queue_purge(&sk->protinfo.nr->reseq_queue);
+ skb_queue_purge(&sk->protinfo.nr->frag_queue);
+}
+
+/*
+ * This routine purges the input queue of those frames that have been
+ * acknowledged. This replaces the boxes labelled "V(a) <- N(r)" on the
+ * SDL diagram.
+ */
+void nr_frames_acked(struct sock *sk, unsigned short nr)
+{
+ struct sk_buff *skb;
+
+ /*
+ * Remove all the ack-ed frames from the ack queue.
+ */
+ if (sk->protinfo.nr->va != nr) {
+ while (skb_peek(&sk->protinfo.nr->ack_queue) != NULL && sk->protinfo.nr->va != nr) {
+ skb = skb_dequeue(&sk->protinfo.nr->ack_queue);
+ kfree_skb(skb);
+ sk->protinfo.nr->va = (sk->protinfo.nr->va + 1) % NR_MODULUS;
+ }
+ }
+}
+
+/*
+ * Requeue all the un-ack-ed frames on the output queue to be picked
+ * up by nr_kick called from the timer. This arrangement handles the
+ * possibility of an empty output queue.
+ */
+void nr_requeue_frames(struct sock *sk)
+{
+ struct sk_buff *skb, *skb_prev = NULL;
+
+ while ((skb = skb_dequeue(&sk->protinfo.nr->ack_queue)) != NULL) {
+ if (skb_prev == NULL)
+ skb_queue_head(&sk->write_queue, skb);
+ else
+ skb_append(skb_prev, skb);
+ skb_prev = skb;
+ }
+}
+
+/*
+ * Validate that the value of nr is between va and vs. Return true or
+ * false for testing.
+ */
+int nr_validate_nr(struct sock *sk, unsigned short nr)
+{
+ unsigned short vc = sk->protinfo.nr->va;
+
+ while (vc != sk->protinfo.nr->vs) {
+ if (nr == vc) return 1;
+ vc = (vc + 1) % NR_MODULUS;
+ }
+
+ if (nr == sk->protinfo.nr->vs) return 1;
+
+ return 0;
+}
+
+/*
+ * Check that ns is within the receive window.
+ */
+int nr_in_rx_window(struct sock *sk, unsigned short ns)
+{
+ unsigned short vc = sk->protinfo.nr->vr;
+ unsigned short vt = (sk->protinfo.nr->vl + sk->protinfo.nr->window) % NR_MODULUS;
+
+ while (vc != vt) {
+ if (ns == vc) return 1;
+ vc = (vc + 1) % NR_MODULUS;
+ }
+
+ return 0;
+}
+
+/*
+ * This routine is called when the HDLC layer internally generates a
+ * control frame.
+ */
+void nr_write_internal(struct sock *sk, int frametype)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+ int len, timeout;
+
+ len = AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + NR_NETWORK_LEN + NR_TRANSPORT_LEN;
+
+ switch (frametype & 0x0F) {
+ case NR_CONNREQ:
+ len += 17;
+ break;
+ case NR_CONNACK:
+ len += (sk->protinfo.nr->bpqext) ? 2 : 1;
+ break;
+ case NR_DISCREQ:
+ case NR_DISCACK:
+ case NR_INFOACK:
+ break;
+ default:
+ printk(KERN_ERR "NET/ROM: nr_write_internal - invalid frame type %d\n", frametype);
+ return;
+ }
+
+ if ((skb = alloc_skb(len, GFP_ATOMIC)) == NULL)
+ return;
+
+ /*
+ * Space for AX.25 and NET/ROM network header
+ */
+ skb_reserve(skb, AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + NR_NETWORK_LEN);
+
+ dptr = skb_put(skb, skb_tailroom(skb));
+
+ switch (frametype & 0x0F) {
+
+ case NR_CONNREQ:
+ timeout = sk->protinfo.nr->t1 / HZ;
+ *dptr++ = sk->protinfo.nr->my_index;
+ *dptr++ = sk->protinfo.nr->my_id;
+ *dptr++ = 0;
+ *dptr++ = 0;
+ *dptr++ = frametype;
+ *dptr++ = sk->protinfo.nr->window;
+ memcpy(dptr, &sk->protinfo.nr->user_addr, AX25_ADDR_LEN);
+ dptr[6] &= ~AX25_CBIT;
+ dptr[6] &= ~AX25_EBIT;
+ dptr[6] |= AX25_SSSID_SPARE;
+ dptr += AX25_ADDR_LEN;
+ memcpy(dptr, &sk->protinfo.nr->source_addr, AX25_ADDR_LEN);
+ dptr[6] &= ~AX25_CBIT;
+ dptr[6] &= ~AX25_EBIT;
+ dptr[6] |= AX25_SSSID_SPARE;
+ dptr += AX25_ADDR_LEN;
+ *dptr++ = timeout % 256;
+ *dptr++ = timeout / 256;
+ break;
+
+ case NR_CONNACK:
+ *dptr++ = sk->protinfo.nr->your_index;
+ *dptr++ = sk->protinfo.nr->your_id;
+ *dptr++ = sk->protinfo.nr->my_index;
+ *dptr++ = sk->protinfo.nr->my_id;
+ *dptr++ = frametype;
+ *dptr++ = sk->protinfo.nr->window;
+ if (sk->protinfo.nr->bpqext) *dptr++ = sysctl_netrom_network_ttl_initialiser;
+ break;
+
+ case NR_DISCREQ:
+ case NR_DISCACK:
+ *dptr++ = sk->protinfo.nr->your_index;
+ *dptr++ = sk->protinfo.nr->your_id;
+ *dptr++ = 0;
+ *dptr++ = 0;
+ *dptr++ = frametype;
+ break;
+
+ case NR_INFOACK:
+ *dptr++ = sk->protinfo.nr->your_index;
+ *dptr++ = sk->protinfo.nr->your_id;
+ *dptr++ = 0;
+ *dptr++ = sk->protinfo.nr->vr;
+ *dptr++ = frametype;
+ break;
+ }
+
+ nr_transmit_buffer(sk, skb);
+}
+
+/*
+ * This routine is called when a Connect Acknowledge with the Choke Flag
+ * set is needed to refuse a connection.
+ */
+void nr_transmit_refusal(struct sk_buff *skb, int mine)
+{
+ struct sk_buff *skbn;
+ unsigned char *dptr;
+ int len;
+
+ len = AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + NR_NETWORK_LEN + NR_TRANSPORT_LEN + 1;
+
+ if ((skbn = alloc_skb(len, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skbn, AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN);
+
+ dptr = skb_put(skbn, NR_NETWORK_LEN + NR_TRANSPORT_LEN);
+
+ memcpy(dptr, skb->data + 7, AX25_ADDR_LEN);
+ dptr[6] &= ~AX25_CBIT;
+ dptr[6] &= ~AX25_EBIT;
+ dptr[6] |= AX25_SSSID_SPARE;
+ dptr += AX25_ADDR_LEN;
+
+ memcpy(dptr, skb->data + 0, AX25_ADDR_LEN);
+ dptr[6] &= ~AX25_CBIT;
+ dptr[6] |= AX25_EBIT;
+ dptr[6] |= AX25_SSSID_SPARE;
+ dptr += AX25_ADDR_LEN;
+
+ *dptr++ = sysctl_netrom_network_ttl_initialiser;
+
+ if (mine) {
+ *dptr++ = 0;
+ *dptr++ = 0;
+ *dptr++ = skb->data[15];
+ *dptr++ = skb->data[16];
+ } else {
+ *dptr++ = skb->data[15];
+ *dptr++ = skb->data[16];
+ *dptr++ = 0;
+ *dptr++ = 0;
+ }
+
+ *dptr++ = NR_CONNACK | NR_CHOKE_FLAG;
+ *dptr++ = 0;
+
+ if (!nr_route_frame(skbn, NULL))
+ kfree_skb(skbn);
+}
+
+void nr_disconnect(struct sock *sk, int reason)
+{
+ nr_stop_t1timer(sk);
+ nr_stop_t2timer(sk);
+ nr_stop_t4timer(sk);
+ nr_stop_idletimer(sk);
+
+ nr_clear_queues(sk);
+
+ sk->protinfo.nr->state = NR_STATE_0;
+
+ sk->state = TCP_CLOSE;
+ sk->err = reason;
+ sk->shutdown |= SEND_SHUTDOWN;
+
+ if (!sk->dead)
+ sk->state_change(sk);
+
+ sk->dead = 1;
+}
diff --git a/uClinux-2.4.31-uc0/net/netrom/nr_timer.c b/uClinux-2.4.31-uc0/net/netrom/nr_timer.c
new file mode 100644
index 0000000..1167704
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/netrom/nr_timer.c
@@ -0,0 +1,245 @@
+/*
+ * NET/ROM release 007
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * NET/ROM 001 Jonathan(G4KLX) Cloned from ax25_timer.c
+ * NET/ROM 007 Jonathan(G4KLX) New timer architecture.
+ * Implemented idle timer.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/netrom.h>
+
+static void nr_heartbeat_expiry(unsigned long);
+static void nr_t1timer_expiry(unsigned long);
+static void nr_t2timer_expiry(unsigned long);
+static void nr_t4timer_expiry(unsigned long);
+static void nr_idletimer_expiry(unsigned long);
+
+void nr_start_t1timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.nr->t1timer);
+
+ sk->protinfo.nr->t1timer.data = (unsigned long)sk;
+ sk->protinfo.nr->t1timer.function = &nr_t1timer_expiry;
+ sk->protinfo.nr->t1timer.expires = jiffies + sk->protinfo.nr->t1;
+
+ add_timer(&sk->protinfo.nr->t1timer);
+}
+
+void nr_start_t2timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.nr->t2timer);
+
+ sk->protinfo.nr->t2timer.data = (unsigned long)sk;
+ sk->protinfo.nr->t2timer.function = &nr_t2timer_expiry;
+ sk->protinfo.nr->t2timer.expires = jiffies + sk->protinfo.nr->t2;
+
+ add_timer(&sk->protinfo.nr->t2timer);
+}
+
+void nr_start_t4timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.nr->t4timer);
+
+ sk->protinfo.nr->t4timer.data = (unsigned long)sk;
+ sk->protinfo.nr->t4timer.function = &nr_t4timer_expiry;
+ sk->protinfo.nr->t4timer.expires = jiffies + sk->protinfo.nr->t4;
+
+ add_timer(&sk->protinfo.nr->t4timer);
+}
+
+void nr_start_idletimer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.nr->idletimer);
+
+ if (sk->protinfo.nr->idle > 0) {
+ sk->protinfo.nr->idletimer.data = (unsigned long)sk;
+ sk->protinfo.nr->idletimer.function = &nr_idletimer_expiry;
+ sk->protinfo.nr->idletimer.expires = jiffies + sk->protinfo.nr->idle;
+
+ add_timer(&sk->protinfo.nr->idletimer);
+ }
+}
+
+void nr_start_heartbeat(struct sock *sk)
+{
+ del_timer(&sk->timer);
+
+ sk->timer.data = (unsigned long)sk;
+ sk->timer.function = &nr_heartbeat_expiry;
+ sk->timer.expires = jiffies + 5 * HZ;
+
+ add_timer(&sk->timer);
+}
+
+void nr_stop_t1timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.nr->t1timer);
+}
+
+void nr_stop_t2timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.nr->t2timer);
+}
+
+void nr_stop_t4timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.nr->t4timer);
+}
+
+void nr_stop_idletimer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.nr->idletimer);
+}
+
+void nr_stop_heartbeat(struct sock *sk)
+{
+ del_timer(&sk->timer);
+}
+
+int nr_t1timer_running(struct sock *sk)
+{
+ return timer_pending(&sk->protinfo.nr->t1timer);
+}
+
+static void nr_heartbeat_expiry(unsigned long param)
+{
+ struct sock *sk = (struct sock *)param;
+
+ switch (sk->protinfo.nr->state) {
+
+ case NR_STATE_0:
+ /* Magic here: If we listen() and a new link dies before it
+ is accepted() it isn't 'dead' so doesn't get removed. */
+ if (sk->destroy || (sk->state == TCP_LISTEN && sk->dead)) {
+ nr_destroy_socket(sk);
+ return;
+ }
+ break;
+
+ case NR_STATE_3:
+ /*
+ * Check for the state of the receive buffer.
+ */
+ if (atomic_read(&sk->rmem_alloc) < (sk->rcvbuf / 2) &&
+ (sk->protinfo.nr->condition & NR_COND_OWN_RX_BUSY)) {
+ sk->protinfo.nr->condition &= ~NR_COND_OWN_RX_BUSY;
+ sk->protinfo.nr->condition &= ~NR_COND_ACK_PENDING;
+ sk->protinfo.nr->vl = sk->protinfo.nr->vr;
+ nr_write_internal(sk, NR_INFOACK);
+ break;
+ }
+ break;
+ }
+
+ nr_start_heartbeat(sk);
+}
+
+static void nr_t2timer_expiry(unsigned long param)
+{
+ struct sock *sk = (struct sock *)param;
+
+ if (sk->protinfo.nr->condition & NR_COND_ACK_PENDING) {
+ sk->protinfo.nr->condition &= ~NR_COND_ACK_PENDING;
+ nr_enquiry_response(sk);
+ }
+}
+
+static void nr_t4timer_expiry(unsigned long param)
+{
+ struct sock *sk = (struct sock *)param;
+
+ sk->protinfo.nr->condition &= ~NR_COND_PEER_RX_BUSY;
+}
+
+static void nr_idletimer_expiry(unsigned long param)
+{
+ struct sock *sk = (struct sock *)param;
+
+ nr_clear_queues(sk);
+
+ sk->protinfo.nr->n2count = 0;
+ nr_write_internal(sk, NR_DISCREQ);
+ sk->protinfo.nr->state = NR_STATE_2;
+
+ nr_start_t1timer(sk);
+ nr_stop_t2timer(sk);
+ nr_stop_t4timer(sk);
+
+ sk->state = TCP_CLOSE;
+ sk->err = 0;
+ sk->shutdown |= SEND_SHUTDOWN;
+
+ if (!sk->dead)
+ sk->state_change(sk);
+
+ sk->dead = 1;
+}
+
+static void nr_t1timer_expiry(unsigned long param)
+{
+ struct sock *sk = (struct sock *)param;
+
+ switch (sk->protinfo.nr->state) {
+
+ case NR_STATE_1:
+ if (sk->protinfo.nr->n2count == sk->protinfo.nr->n2) {
+ nr_disconnect(sk, ETIMEDOUT);
+ return;
+ } else {
+ sk->protinfo.nr->n2count++;
+ nr_write_internal(sk, NR_CONNREQ);
+ }
+ break;
+
+ case NR_STATE_2:
+ if (sk->protinfo.nr->n2count == sk->protinfo.nr->n2) {
+ nr_disconnect(sk, ETIMEDOUT);
+ return;
+ } else {
+ sk->protinfo.nr->n2count++;
+ nr_write_internal(sk, NR_DISCREQ);
+ }
+ break;
+
+ case NR_STATE_3:
+ if (sk->protinfo.nr->n2count == sk->protinfo.nr->n2) {
+ nr_disconnect(sk, ETIMEDOUT);
+ return;
+ } else {
+ sk->protinfo.nr->n2count++;
+ nr_requeue_frames(sk);
+ }
+ break;
+ }
+
+ nr_start_t1timer(sk);
+}
diff --git a/uClinux-2.4.31-uc0/net/netrom/sysctl_net_netrom.c b/uClinux-2.4.31-uc0/net/netrom/sysctl_net_netrom.c
new file mode 100644
index 0000000..bf80062
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/netrom/sysctl_net_netrom.c
@@ -0,0 +1,90 @@
+/* -*- linux-c -*-
+ * sysctl_net_netrom.c: sysctl interface to net NET/ROM subsystem.
+ *
+ * Begun April 1, 1996, Mike Shaver.
+ * Added /proc/sys/net/netrom directory entry (empty =) ). [MS]
+ */
+
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+#include <linux/init.h>
+#include <net/ax25.h>
+#include <net/netrom.h>
+
+/*
+ * Values taken from NET/ROM documentation.
+ */
+static int min_quality[] = {0}, max_quality[] = {255};
+static int min_obs[] = {0}, max_obs[] = {255};
+static int min_ttl[] = {0}, max_ttl[] = {255};
+static int min_t1[] = {5 * HZ};
+static int max_t1[] = {600 * HZ};
+static int min_n2[] = {2}, max_n2[] = {127};
+static int min_t2[] = {1 * HZ};
+static int max_t2[] = {60 * HZ};
+static int min_t4[] = {1 * HZ};
+static int max_t4[] = {1000 * HZ};
+static int min_window[] = {1}, max_window[] = {127};
+static int min_idle[] = {0 * HZ};
+static int max_idle[] = {65535 * HZ};
+static int min_route[] = {0}, max_route[] = {1};
+static int min_fails[] = {1}, max_fails[] = {10};
+
+static struct ctl_table_header *nr_table_header;
+
+static ctl_table nr_table[] = {
+ {NET_NETROM_DEFAULT_PATH_QUALITY, "default_path_quality",
+ &sysctl_netrom_default_path_quality, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_quality, &max_quality},
+ {NET_NETROM_OBSOLESCENCE_COUNT_INITIALISER, "obsolescence_count_initialiser",
+ &sysctl_netrom_obsolescence_count_initialiser, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_obs, &max_obs},
+ {NET_NETROM_NETWORK_TTL_INITIALISER, "network_ttl_initialiser",
+ &sysctl_netrom_network_ttl_initialiser, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_ttl, &max_ttl},
+ {NET_NETROM_TRANSPORT_TIMEOUT, "transport_timeout",
+ &sysctl_netrom_transport_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_t1, &max_t1},
+ {NET_NETROM_TRANSPORT_MAXIMUM_TRIES, "transport_maximum_tries",
+ &sysctl_netrom_transport_maximum_tries, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_n2, &max_n2},
+ {NET_NETROM_TRANSPORT_ACKNOWLEDGE_DELAY, "transport_acknowledge_delay",
+ &sysctl_netrom_transport_acknowledge_delay, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_t2, &max_t2},
+ {NET_NETROM_TRANSPORT_BUSY_DELAY, "transport_busy_delay",
+ &sysctl_netrom_transport_busy_delay, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_t4, &max_t4},
+ {NET_NETROM_TRANSPORT_REQUESTED_WINDOW_SIZE, "transport_requested_window_size",
+ &sysctl_netrom_transport_requested_window_size, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_window, &max_window},
+ {NET_NETROM_TRANSPORT_NO_ACTIVITY_TIMEOUT, "transport_no_activity_timeout",
+ &sysctl_netrom_transport_no_activity_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_idle, &max_idle},
+ {NET_NETROM_ROUTING_CONTROL, "routing_control",
+ &sysctl_netrom_routing_control, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_route, &max_route},
+ {NET_NETROM_LINK_FAILS_COUNT, "link_fails_count",
+ &sysctl_netrom_link_fails_count, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_fails, &max_fails},
+ {0}
+};
+
+static ctl_table nr_dir_table[] = {
+ {NET_NETROM, "netrom", NULL, 0, 0555, nr_table},
+ {0}
+};
+
+static ctl_table nr_root_table[] = {
+ {CTL_NET, "net", NULL, 0, 0555, nr_dir_table},
+ {0}
+};
+
+void __init nr_register_sysctl(void)
+{
+ nr_table_header = register_sysctl_table(nr_root_table, 1);
+}
+
+void nr_unregister_sysctl(void)
+{
+ unregister_sysctl_table(nr_table_header);
+}
diff --git a/uClinux-2.4.31-uc0/net/netsyms.c b/uClinux-2.4.31-uc0/net/netsyms.c
new file mode 100644
index 0000000..6b05c36
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/netsyms.c
@@ -0,0 +1,635 @@
+/* $USAGI: netsyms.c,v 1.61 2003/12/22 04:36:30 yoshfuji Exp $ */
+
+/*
+ * linux/net/netsyms.c
+ *
+ * Symbol table for the linux networking subsystem. Moved here to
+ * make life simpler in ksyms.c.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/net.h>
+#include <linux/in.h>
+#include <linux/netdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/fddidevice.h>
+#include <linux/trdevice.h>
+#include <linux/fcdevice.h>
+#include <linux/ioport.h>
+#include <linux/tty.h>
+#include <net/neighbour.h>
+#include <net/snmp.h>
+#include <net/dst.h>
+#include <net/checksum.h>
+#include <linux/etherdevice.h>
+#include <net/route.h>
+#ifdef CONFIG_HIPPI
+#include <linux/hippidevice.h>
+#endif
+#include <net/pkt_sched.h>
+#include <net/scm.h>
+#include <linux/if_bridge.h>
+#include <linux/if_vlan.h>
+#include <linux/random.h>
+#ifdef CONFIG_NET_DIVERT
+#include <linux/divert.h>
+#endif /* CONFIG_NET_DIVERT */
+
+#ifdef CONFIG_NET
+extern __u32 sysctl_wmem_max;
+extern __u32 sysctl_rmem_max;
+extern int sysctl_optmem_max;
+#endif
+
+#ifdef CONFIG_INET
+#include <linux/ip.h>
+#include <net/protocol.h>
+#include <net/arp.h>
+#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
+#include <net/atmclip.h>
+#endif
+#include <net/ip.h>
+#include <net/udp.h>
+#include <net/tcp.h>
+#include <net/icmp.h>
+#include <net/inet_common.h>
+#include <linux/inet.h>
+#include <linux/mroute.h>
+#include <linux/igmp.h>
+
+extern struct net_proto_family inet_family_ops;
+
+#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE) || defined (CONFIG_KHTTPD) || defined (CONFIG_KHTTPD_MODULE) || defined (CONFIG_IP_SCTP_MODULE)
+#include <linux/in6.h>
+#include <linux/icmpv6.h>
+#include <net/ipv6.h>
+#include <net/ndisc.h>
+#include <net/transp_v6.h>
+#include <net/addrconf.h>
+#include <net/ip6_route.h>
+
+extern int sysctl_local_port_range[2];
+extern int tcp_port_rover;
+extern int udp_port_rover;
+#endif
+
+#endif
+
+extern int netdev_finish_unregister(struct net_device *dev);
+
+#include <linux/rtnetlink.h>
+
+#ifdef CONFIG_IPX_MODULE
+extern struct datalink_proto *make_EII_client(void);
+extern struct datalink_proto *make_8023_client(void);
+extern void destroy_EII_client(struct datalink_proto *);
+extern void destroy_8023_client(struct datalink_proto *);
+#endif
+
+#ifdef CONFIG_ATALK_MODULE
+#include <net/sock.h>
+#endif
+
+#ifdef CONFIG_SYSCTL
+extern int sysctl_max_syn_backlog;
+#endif
+
+/* Skbuff symbols. */
+EXPORT_SYMBOL(skb_over_panic);
+EXPORT_SYMBOL(skb_under_panic);
+EXPORT_SYMBOL(skb_pad);
+
+/* Socket layer registration */
+EXPORT_SYMBOL(sock_register);
+EXPORT_SYMBOL(sock_unregister);
+
+/* Socket locking */
+EXPORT_SYMBOL(__lock_sock);
+EXPORT_SYMBOL(__release_sock);
+
+/* Socket layer support routines */
+EXPORT_SYMBOL(memcpy_fromiovec);
+EXPORT_SYMBOL(memcpy_tokerneliovec);
+EXPORT_SYMBOL(sock_create);
+EXPORT_SYMBOL(sock_alloc);
+EXPORT_SYMBOL(sock_release);
+EXPORT_SYMBOL(sock_setsockopt);
+EXPORT_SYMBOL(sock_getsockopt);
+EXPORT_SYMBOL(sock_sendmsg);
+EXPORT_SYMBOL(sock_recvmsg);
+EXPORT_SYMBOL(sk_alloc);
+EXPORT_SYMBOL(sk_free);
+EXPORT_SYMBOL(sock_wake_async);
+EXPORT_SYMBOL(sock_alloc_send_skb);
+EXPORT_SYMBOL(sock_alloc_send_pskb);
+EXPORT_SYMBOL(sock_init_data);
+EXPORT_SYMBOL(sock_no_release);
+EXPORT_SYMBOL(sock_no_bind);
+EXPORT_SYMBOL(sock_no_connect);
+EXPORT_SYMBOL(sock_no_socketpair);
+EXPORT_SYMBOL(sock_no_accept);
+EXPORT_SYMBOL(sock_no_getname);
+EXPORT_SYMBOL(sock_no_poll);
+EXPORT_SYMBOL(sock_no_ioctl);
+EXPORT_SYMBOL(sock_no_listen);
+EXPORT_SYMBOL(sock_no_shutdown);
+EXPORT_SYMBOL(sock_no_getsockopt);
+EXPORT_SYMBOL(sock_no_setsockopt);
+EXPORT_SYMBOL(sock_no_sendmsg);
+EXPORT_SYMBOL(sock_no_recvmsg);
+EXPORT_SYMBOL(sock_no_mmap);
+EXPORT_SYMBOL(sock_no_sendpage);
+EXPORT_SYMBOL(sock_rfree);
+EXPORT_SYMBOL(sock_wfree);
+EXPORT_SYMBOL(sock_wmalloc);
+EXPORT_SYMBOL(sock_rmalloc);
+EXPORT_SYMBOL(skb_linearize);
+EXPORT_SYMBOL(skb_checksum);
+EXPORT_SYMBOL(skb_checksum_help);
+EXPORT_SYMBOL(skb_recv_datagram);
+EXPORT_SYMBOL(skb_free_datagram);
+EXPORT_SYMBOL(skb_copy_datagram);
+EXPORT_SYMBOL(skb_copy_datagram_iovec);
+EXPORT_SYMBOL(skb_copy_and_csum_datagram_iovec);
+EXPORT_SYMBOL(skb_copy_bits);
+EXPORT_SYMBOL(skb_copy_and_csum_bits);
+EXPORT_SYMBOL(skb_copy_and_csum_dev);
+EXPORT_SYMBOL(skb_copy_expand);
+EXPORT_SYMBOL(___pskb_trim);
+EXPORT_SYMBOL(__pskb_pull_tail);
+EXPORT_SYMBOL(pskb_expand_head);
+EXPORT_SYMBOL(pskb_copy);
+EXPORT_SYMBOL(skb_realloc_headroom);
+EXPORT_SYMBOL(datagram_poll);
+EXPORT_SYMBOL(put_cmsg);
+EXPORT_SYMBOL(sock_kmalloc);
+EXPORT_SYMBOL(sock_kfree_s);
+EXPORT_SYMBOL(sock_map_fd);
+EXPORT_SYMBOL(sockfd_lookup);
+
+#ifdef CONFIG_FILTER
+EXPORT_SYMBOL(sk_run_filter);
+EXPORT_SYMBOL(sk_chk_filter);
+#endif
+
+EXPORT_SYMBOL(neigh_table_init);
+EXPORT_SYMBOL(neigh_table_clear);
+EXPORT_SYMBOL(neigh_resolve_output);
+EXPORT_SYMBOL(neigh_connected_output);
+EXPORT_SYMBOL(__neigh_update);
+EXPORT_SYMBOL(neigh_update);
+EXPORT_SYMBOL(neigh_create);
+EXPORT_SYMBOL(neigh_lookup);
+EXPORT_SYMBOL(neigh_lookup_nodev);
+EXPORT_SYMBOL(__neigh_event_send);
+EXPORT_SYMBOL(neigh_event_ns);
+EXPORT_SYMBOL(neigh_ifdown);
+EXPORT_SYMBOL(neigh_seq_start);
+EXPORT_SYMBOL(neigh_seq_next);
+EXPORT_SYMBOL(neigh_seq_stop);
+#ifdef CONFIG_ARPD
+EXPORT_SYMBOL(neigh_app_ns);
+EXPORT_SYMBOL(neigh_app_notify);
+#endif
+#ifdef CONFIG_SYSCTL
+EXPORT_SYMBOL(neigh_sysctl_register);
+#endif
+EXPORT_SYMBOL(pneigh_lookup);
+EXPORT_SYMBOL(pneigh_enqueue);
+EXPORT_SYMBOL(pneigh_delete);
+EXPORT_SYMBOL(neigh_destroy);
+EXPORT_SYMBOL(neigh_parms_alloc);
+EXPORT_SYMBOL(neigh_parms_release);
+EXPORT_SYMBOL(neigh_rand_reach_time);
+EXPORT_SYMBOL(neigh_compat_output);
+EXPORT_SYMBOL(neigh_changeaddr);
+
+/* dst_entry */
+EXPORT_SYMBOL(dst_alloc);
+EXPORT_SYMBOL(__dst_free);
+EXPORT_SYMBOL(dst_destroy);
+
+/* misc. support routines */
+EXPORT_SYMBOL(net_ratelimit);
+EXPORT_SYMBOL(net_random);
+EXPORT_SYMBOL(net_srandom);
+
+/* Needed by smbfs.o */
+EXPORT_SYMBOL(__scm_destroy);
+EXPORT_SYMBOL(__scm_send);
+
+/* Needed by unix.o */
+EXPORT_SYMBOL(scm_fp_dup);
+EXPORT_SYMBOL(files_stat);
+EXPORT_SYMBOL(memcpy_toiovec);
+
+#ifdef CONFIG_IPX_MODULE
+EXPORT_SYMBOL(make_8023_client);
+EXPORT_SYMBOL(destroy_8023_client);
+EXPORT_SYMBOL(make_EII_client);
+EXPORT_SYMBOL(destroy_EII_client);
+#endif
+
+/* for 801q VLAN support */
+#if defined(CONFIG_VLAN_8021Q) || defined(CONFIG_VLAN_8021Q_MODULE)
+EXPORT_SYMBOL(dev_change_flags);
+EXPORT_SYMBOL(vlan_ioctl_hook);
+#endif
+
+EXPORT_SYMBOL(sklist_destroy_socket);
+EXPORT_SYMBOL(sklist_insert_socket);
+
+EXPORT_SYMBOL(scm_detach_fds);
+
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+EXPORT_SYMBOL(br_handle_frame_hook);
+#ifdef CONFIG_INET
+EXPORT_SYMBOL(br_ioctl_hook);
+#endif
+#endif
+
+#ifdef CONFIG_NET_DIVERT
+EXPORT_SYMBOL(alloc_divert_blk);
+EXPORT_SYMBOL(free_divert_blk);
+EXPORT_SYMBOL(divert_ioctl);
+#endif /* CONFIG_NET_DIVERT */
+
+#ifdef CONFIG_INET
+/* Internet layer registration */
+EXPORT_SYMBOL(inetdev_lock);
+EXPORT_SYMBOL(inet_add_protocol);
+EXPORT_SYMBOL(inet_del_protocol);
+EXPORT_SYMBOL(inet_register_protosw);
+EXPORT_SYMBOL(inet_unregister_protosw);
+EXPORT_SYMBOL(ip_route_output_key);
+EXPORT_SYMBOL(ip_route_input);
+EXPORT_SYMBOL(icmp_send);
+EXPORT_SYMBOL(icmp_statistics);
+EXPORT_SYMBOL(icmp_err_convert);
+EXPORT_SYMBOL(ip_options_compile);
+EXPORT_SYMBOL(ip_options_undo);
+EXPORT_SYMBOL(arp_create);
+EXPORT_SYMBOL(arp_xmit);
+EXPORT_SYMBOL(arp_send);
+EXPORT_SYMBOL(arp_broken_ops);
+EXPORT_SYMBOL(__ip_select_ident);
+EXPORT_SYMBOL(ip_send_check);
+EXPORT_SYMBOL(ip_fragment);
+EXPORT_SYMBOL(inet_family_ops);
+EXPORT_SYMBOL(in_aton);
+EXPORT_SYMBOL(ip_mc_inc_group);
+EXPORT_SYMBOL(ip_mc_dec_group);
+EXPORT_SYMBOL(ip_mc_join_group);
+EXPORT_SYMBOL(ip_finish_output);
+EXPORT_SYMBOL(inet_stream_ops);
+EXPORT_SYMBOL(inet_dgram_ops);
+EXPORT_SYMBOL(ip_cmsg_recv);
+EXPORT_SYMBOL(inet_addr_type);
+EXPORT_SYMBOL(inet_select_addr);
+EXPORT_SYMBOL(ip_dev_find);
+EXPORT_SYMBOL(inetdev_by_index);
+EXPORT_SYMBOL(in_dev_finish_destroy);
+EXPORT_SYMBOL(ip_defrag);
+
+/* Route manipulation */
+EXPORT_SYMBOL(ip_rt_ioctl);
+EXPORT_SYMBOL(devinet_ioctl);
+EXPORT_SYMBOL(register_inetaddr_notifier);
+EXPORT_SYMBOL(unregister_inetaddr_notifier);
+
+/* needed for ip_gre -cw */
+EXPORT_SYMBOL(ip_statistics);
+
+#ifdef CONFIG_DLCI_MODULE
+extern int (*dlci_ioctl_hook)(unsigned int, void *);
+EXPORT_SYMBOL(dlci_ioctl_hook);
+#endif
+
+#if defined (CONFIG_IPV6_MODULE) || defined (CONFIG_KHTTPD) || defined (CONFIG_KHTTPD_MODULE) || defined (CONFIG_IP_SCTP_MODULE)
+/* inet functions common to v4 and v6 */
+EXPORT_SYMBOL(inet_release);
+EXPORT_SYMBOL(inet_stream_connect);
+EXPORT_SYMBOL(inet_dgram_connect);
+EXPORT_SYMBOL(inet_accept);
+EXPORT_SYMBOL(inet_listen);
+EXPORT_SYMBOL(inet_shutdown);
+EXPORT_SYMBOL(inet_setsockopt);
+EXPORT_SYMBOL(inet_getsockopt);
+EXPORT_SYMBOL(inet_sendmsg);
+EXPORT_SYMBOL(inet_recvmsg);
+#ifdef INET_REFCNT_DEBUG
+EXPORT_SYMBOL(inet_sock_nr);
+#endif
+EXPORT_SYMBOL(inet_sock_destruct);
+EXPORT_SYMBOL(inet_sock_release);
+
+/* Socket demultiplexing. */
+EXPORT_SYMBOL(tcp_hashinfo);
+EXPORT_SYMBOL(tcp_listen_wlock);
+EXPORT_SYMBOL(udp_hash);
+EXPORT_SYMBOL(udp_hash_lock);
+EXPORT_SYMBOL(udp_poll);
+
+EXPORT_SYMBOL(tcp_destroy_sock);
+EXPORT_SYMBOL(ip_queue_xmit);
+EXPORT_SYMBOL(memcpy_fromiovecend);
+EXPORT_SYMBOL(csum_partial_copy_fromiovecend);
+EXPORT_SYMBOL(tcp_v4_lookup_listener);
+/* UDP/TCP exported functions for TCPv6 */
+EXPORT_SYMBOL(udp_ioctl);
+EXPORT_SYMBOL(udp_connect);
+EXPORT_SYMBOL(udp_disconnect);
+EXPORT_SYMBOL(udp_sendmsg);
+EXPORT_SYMBOL(tcp_close);
+EXPORT_SYMBOL(tcp_disconnect);
+EXPORT_SYMBOL(tcp_accept);
+EXPORT_SYMBOL(tcp_write_wakeup);
+EXPORT_SYMBOL(tcp_write_space);
+EXPORT_SYMBOL(tcp_poll);
+EXPORT_SYMBOL(tcp_ioctl);
+EXPORT_SYMBOL(tcp_shutdown);
+EXPORT_SYMBOL(tcp_setsockopt);
+EXPORT_SYMBOL(tcp_getsockopt);
+EXPORT_SYMBOL(tcp_recvmsg);
+EXPORT_SYMBOL(tcp_send_synack);
+EXPORT_SYMBOL(tcp_check_req);
+EXPORT_SYMBOL(tcp_child_process);
+EXPORT_SYMBOL(tcp_parse_options);
+EXPORT_SYMBOL(tcp_rcv_established);
+EXPORT_SYMBOL(tcp_init_xmit_timers);
+EXPORT_SYMBOL(tcp_clear_xmit_timers);
+EXPORT_SYMBOL(tcp_statistics);
+EXPORT_SYMBOL(tcp_rcv_state_process);
+EXPORT_SYMBOL(tcp_timewait_state_process);
+EXPORT_SYMBOL(tcp_timewait_cachep);
+EXPORT_SYMBOL(tcp_timewait_kill);
+EXPORT_SYMBOL(tcp_sendmsg);
+EXPORT_SYMBOL(tcp_v4_rebuild_header);
+EXPORT_SYMBOL(tcp_v4_send_check);
+EXPORT_SYMBOL(tcp_v4_conn_request);
+EXPORT_SYMBOL(tcp_create_openreq_child);
+EXPORT_SYMBOL(tcp_bucket_create);
+EXPORT_SYMBOL(__tcp_put_port);
+EXPORT_SYMBOL(tcp_put_port);
+EXPORT_SYMBOL(tcp_inherit_port);
+EXPORT_SYMBOL(tcp_v4_syn_recv_sock);
+EXPORT_SYMBOL(tcp_v4_do_rcv);
+EXPORT_SYMBOL(tcp_v4_connect);
+EXPORT_SYMBOL(tcp_unhash);
+EXPORT_SYMBOL(udp_prot);
+EXPORT_SYMBOL(tcp_prot);
+EXPORT_SYMBOL(tcp_openreq_cachep);
+EXPORT_SYMBOL(ipv4_specific);
+EXPORT_SYMBOL(tcp_simple_retransmit);
+EXPORT_SYMBOL(tcp_transmit_skb);
+EXPORT_SYMBOL(tcp_connect);
+EXPORT_SYMBOL(tcp_make_synack);
+EXPORT_SYMBOL(tcp_tw_deschedule);
+EXPORT_SYMBOL(tcp_delete_keepalive_timer);
+EXPORT_SYMBOL(tcp_reset_keepalive_timer);
+EXPORT_SYMBOL(sysctl_local_port_range);
+EXPORT_SYMBOL(tcp_port_rover);
+EXPORT_SYMBOL(udp_port_rover);
+EXPORT_SYMBOL(tcp_sync_mss);
+EXPORT_SYMBOL(net_statistics);
+EXPORT_SYMBOL(__tcp_mem_reclaim);
+EXPORT_SYMBOL(tcp_sockets_allocated);
+EXPORT_SYMBOL(sysctl_tcp_reordering);
+EXPORT_SYMBOL(sysctl_tcp_rmem);
+EXPORT_SYMBOL(sysctl_tcp_wmem);
+EXPORT_SYMBOL(sysctl_tcp_ecn);
+EXPORT_SYMBOL(tcp_cwnd_application_limited);
+EXPORT_SYMBOL(tcp_sendpage);
+EXPORT_SYMBOL(sysctl_tcp_low_latency);
+
+EXPORT_SYMBOL(tcp_write_xmit);
+
+EXPORT_SYMBOL(tcp_v4_remember_stamp);
+
+extern int sysctl_tcp_tw_recycle;
+
+#ifdef CONFIG_SYSCTL
+EXPORT_SYMBOL(sysctl_tcp_tw_recycle);
+EXPORT_SYMBOL(sysctl_max_syn_backlog);
+#endif
+
+#if defined (CONFIG_IPV6_MODULE)
+EXPORT_SYMBOL(secure_tcpv6_sequence_number);
+EXPORT_SYMBOL(secure_ipv6_id);
+#endif
+
+#endif
+
+EXPORT_SYMBOL(tcp_read_sock);
+
+#ifdef CONFIG_IP_SCTP_MODULE
+EXPORT_SYMBOL(ip_setsockopt);
+EXPORT_SYMBOL(ip_getsockopt);
+EXPORT_SYMBOL(inet_ioctl);
+EXPORT_SYMBOL(inet_bind);
+EXPORT_SYMBOL(inet_getname);
+#endif /* CONFIG_IP_SCTP_MODULE */
+
+EXPORT_SYMBOL(netlink_set_err);
+EXPORT_SYMBOL(netlink_broadcast);
+EXPORT_SYMBOL(netlink_unicast);
+EXPORT_SYMBOL(netlink_kernel_create);
+EXPORT_SYMBOL(netlink_dump_start);
+EXPORT_SYMBOL(netlink_ack);
+EXPORT_SYMBOL(netlink_set_nonroot);
+EXPORT_SYMBOL(netlink_register_notifier);
+EXPORT_SYMBOL(netlink_unregister_notifier);
+#if defined(CONFIG_NETLINK_DEV) || defined(CONFIG_NETLINK_DEV_MODULE)
+EXPORT_SYMBOL(netlink_attach);
+EXPORT_SYMBOL(netlink_detach);
+EXPORT_SYMBOL(netlink_post);
+#endif
+
+EXPORT_SYMBOL(rtattr_parse);
+EXPORT_SYMBOL(rtnetlink_links);
+EXPORT_SYMBOL(__rta_fill);
+EXPORT_SYMBOL(rtnetlink_dump_ifinfo);
+EXPORT_SYMBOL(rtnetlink_put_metrics);
+EXPORT_SYMBOL(rtnl);
+EXPORT_SYMBOL(neigh_delete);
+EXPORT_SYMBOL(neigh_add);
+EXPORT_SYMBOL(neigh_dump_info);
+
+EXPORT_SYMBOL(dev_set_allmulti);
+EXPORT_SYMBOL(dev_set_promiscuity);
+EXPORT_SYMBOL(sklist_remove_socket);
+EXPORT_SYMBOL(rtnl_sem);
+EXPORT_SYMBOL(rtnl_lock);
+EXPORT_SYMBOL(rtnl_unlock);
+
+/* ABI emulation layers need this */
+EXPORT_SYMBOL(move_addr_to_kernel);
+EXPORT_SYMBOL(move_addr_to_user);
+
+/* Used by at least ipip.c. */
+EXPORT_SYMBOL(ipv4_config);
+EXPORT_SYMBOL(dev_open);
+
+/* Used by other modules */
+EXPORT_SYMBOL(xrlim_allow);
+
+EXPORT_SYMBOL(ip_rcv);
+EXPORT_SYMBOL(arp_rcv);
+EXPORT_SYMBOL(arp_tbl);
+#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
+EXPORT_SYMBOL(clip_tbl_hook);
+#endif
+EXPORT_SYMBOL(arp_find);
+
+#endif /* CONFIG_INET */
+
+#ifdef CONFIG_TR
+EXPORT_SYMBOL(tr_type_trans);
+#endif
+
+/* Device callback registration */
+EXPORT_SYMBOL(register_netdevice_notifier);
+EXPORT_SYMBOL(unregister_netdevice_notifier);
+
+/* support for loadable net drivers */
+#ifdef CONFIG_NET
+EXPORT_SYMBOL(loopback_dev);
+EXPORT_SYMBOL(register_netdevice);
+EXPORT_SYMBOL(unregister_netdevice);
+EXPORT_SYMBOL(netdev_state_change);
+EXPORT_SYMBOL(dev_new_index);
+EXPORT_SYMBOL(dev_get_by_flags);
+EXPORT_SYMBOL(__dev_get_by_flags);
+EXPORT_SYMBOL(dev_get_by_index);
+EXPORT_SYMBOL(__dev_get_by_index);
+EXPORT_SYMBOL(dev_get_by_name);
+EXPORT_SYMBOL(__dev_get_by_name);
+EXPORT_SYMBOL(netdev_finish_unregister);
+EXPORT_SYMBOL(netdev_set_master);
+EXPORT_SYMBOL(eth_type_trans);
+#ifdef CONFIG_FDDI
+EXPORT_SYMBOL(fddi_type_trans);
+#endif /* CONFIG_FDDI */
+#if 0
+EXPORT_SYMBOL(eth_copy_and_sum);
+#endif
+EXPORT_SYMBOL(alloc_skb);
+EXPORT_SYMBOL(__kfree_skb);
+EXPORT_SYMBOL(skb_clone);
+EXPORT_SYMBOL(skb_copy);
+EXPORT_SYMBOL(netif_rx);
+EXPORT_SYMBOL(netif_receive_skb);
+EXPORT_SYMBOL(dev_add_pack);
+EXPORT_SYMBOL(dev_remove_pack);
+EXPORT_SYMBOL(dev_get);
+EXPORT_SYMBOL(dev_alloc);
+EXPORT_SYMBOL(dev_alloc_name);
+EXPORT_SYMBOL(__netdev_watchdog_up);
+#ifdef CONFIG_KMOD
+EXPORT_SYMBOL(dev_load);
+#endif
+EXPORT_SYMBOL(dev_ioctl);
+EXPORT_SYMBOL(dev_queue_xmit);
+#ifdef CONFIG_NET_HW_FLOWCONTROL
+EXPORT_SYMBOL(netdev_dropping);
+EXPORT_SYMBOL(netdev_register_fc);
+EXPORT_SYMBOL(netdev_unregister_fc);
+EXPORT_SYMBOL(netdev_fc_xoff);
+#endif
+EXPORT_SYMBOL(dev_base);
+EXPORT_SYMBOL(dev_base_lock);
+EXPORT_SYMBOL(dev_close);
+EXPORT_SYMBOL(dev_mc_add);
+EXPORT_SYMBOL(dev_mc_delete);
+EXPORT_SYMBOL(dev_mc_upload);
+EXPORT_SYMBOL(__kill_fasync);
+
+EXPORT_SYMBOL(if_port_text);
+
+#ifdef CONFIG_HIPPI
+EXPORT_SYMBOL(hippi_type_trans);
+#endif
+
+#ifdef CONFIG_NET_FASTROUTE
+EXPORT_SYMBOL(netdev_fastroute);
+#endif
+
+#ifdef CONFIG_SYSCTL
+EXPORT_SYMBOL(sysctl_wmem_max);
+EXPORT_SYMBOL(sysctl_rmem_max);
+EXPORT_SYMBOL(sysctl_optmem_max);
+#ifdef CONFIG_INET
+EXPORT_SYMBOL(sysctl_ip_default_ttl);
+#endif
+#endif
+
+/* Packet scheduler modules want these. */
+EXPORT_SYMBOL(qdisc_destroy);
+EXPORT_SYMBOL(qdisc_reset);
+EXPORT_SYMBOL(qdisc_restart);
+EXPORT_SYMBOL(qdisc_create_dflt);
+EXPORT_SYMBOL(noop_qdisc);
+EXPORT_SYMBOL(qdisc_tree_lock);
+#ifdef CONFIG_NET_SCHED
+PSCHED_EXPORTLIST;
+EXPORT_SYMBOL(pfifo_qdisc_ops);
+EXPORT_SYMBOL(bfifo_qdisc_ops);
+EXPORT_SYMBOL(register_qdisc);
+EXPORT_SYMBOL(unregister_qdisc);
+EXPORT_SYMBOL(qdisc_get_rtab);
+EXPORT_SYMBOL(qdisc_put_rtab);
+EXPORT_SYMBOL(qdisc_copy_stats);
+#ifdef CONFIG_NET_ESTIMATOR
+EXPORT_SYMBOL(qdisc_new_estimator);
+EXPORT_SYMBOL(qdisc_kill_estimator);
+#endif
+#ifdef CONFIG_NET_CLS_POLICE
+EXPORT_SYMBOL(tcf_police);
+EXPORT_SYMBOL(tcf_police_locate);
+EXPORT_SYMBOL(tcf_police_destroy);
+EXPORT_SYMBOL(tcf_police_dump);
+#endif
+#endif
+#ifdef CONFIG_NET_CLS
+EXPORT_SYMBOL(register_tcf_proto_ops);
+EXPORT_SYMBOL(unregister_tcf_proto_ops);
+#endif
+#ifdef CONFIG_NETFILTER
+#include <linux/netfilter.h>
+EXPORT_SYMBOL(nf_register_hook);
+EXPORT_SYMBOL(nf_unregister_hook);
+EXPORT_SYMBOL(nf_register_sockopt);
+EXPORT_SYMBOL(nf_unregister_sockopt);
+EXPORT_SYMBOL(nf_reinject);
+EXPORT_SYMBOL(nf_register_queue_handler);
+EXPORT_SYMBOL(nf_unregister_queue_handler);
+EXPORT_SYMBOL(nf_hook_slow);
+EXPORT_SYMBOL(nf_hooks);
+EXPORT_SYMBOL(nf_setsockopt);
+EXPORT_SYMBOL(nf_getsockopt);
+EXPORT_SYMBOL(ip_ct_attach);
+EXPORT_SYMBOL(nf_ct_attach);
+#ifdef CONFIG_INET
+#include <linux/netfilter_ipv4.h>
+EXPORT_SYMBOL(ip_route_me_harder);
+#endif
+#endif
+
+EXPORT_SYMBOL(register_gifconf);
+
+EXPORT_SYMBOL(softnet_data);
+
+#if defined(CONFIG_NET_RADIO) || defined(CONFIG_NET_PCMCIA_RADIO)
+#include <net/iw_handler.h>
+EXPORT_SYMBOL(wireless_send_event);
+EXPORT_SYMBOL(iw_handler_set_spy);
+EXPORT_SYMBOL(iw_handler_get_spy);
+EXPORT_SYMBOL(iw_handler_set_thrspy);
+EXPORT_SYMBOL(iw_handler_get_thrspy);
+EXPORT_SYMBOL(wireless_spy_update);
+#endif /* CONFIG_NET_RADIO || CONFIG_NET_PCMCIA_RADIO */
+
+#endif /* CONFIG_NET */
diff --git a/uClinux-2.4.31-uc0/net/packet/Makefile b/uClinux-2.4.31-uc0/net/packet/Makefile
new file mode 100644
index 0000000..f17e887
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/packet/Makefile
@@ -0,0 +1,14 @@
+#
+# Makefile for the packet AF.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := packet.o
+
+obj-$(CONFIG_PACKET) += af_packet.o
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/packet/af_packet.c b/uClinux-2.4.31-uc0/net/packet/af_packet.c
new file mode 100644
index 0000000..9543efd
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/packet/af_packet.c
@@ -0,0 +1,1922 @@
+/*
+ * INET An implementation of the TCP/IP protocol suite for the LINUX
+ * operating system. INET is implemented using the BSD Socket
+ * interface as the means of communication with the user level.
+ *
+ * PACKET - implements raw packet sockets.
+ *
+ * Version: $Id: af_packet.c,v 1.58 2001/11/28 21:02:10 davem Exp $
+ *
+ * Authors: Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ * Alan Cox, <gw4pts@gw4pts.ampr.org>
+ *
+ * Fixes:
+ * Alan Cox : verify_area() now used correctly
+ * Alan Cox : new skbuff lists, look ma no backlogs!
+ * Alan Cox : tidied skbuff lists.
+ * Alan Cox : Now uses generic datagram routines I
+ * added. Also fixed the peek/read crash
+ * from all old Linux datagram code.
+ * Alan Cox : Uses the improved datagram code.
+ * Alan Cox : Added NULL's for socket options.
+ * Alan Cox : Re-commented the code.
+ * Alan Cox : Use new kernel side addressing
+ * Rob Janssen : Correct MTU usage.
+ * Dave Platt : Counter leaks caused by incorrect
+ * interrupt locking and some slightly
+ * dubious gcc output. Can you read
+ * compiler: it said _VOLATILE_
+ * Richard Kooijman : Timestamp fixes.
+ * Alan Cox : New buffers. Use sk->mac.raw.
+ * Alan Cox : sendmsg/recvmsg support.
+ * Alan Cox : Protocol setting support
+ * Alexey Kuznetsov : Untied from IPv4 stack.
+ * Cyrus Durgin : Fixed kerneld for kmod.
+ * Michal Ostrowski : Module initialization cleanup.
+ * Ulises Alonso : Frame number limit removal and
+ * packet_set_ring memory leak.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/fcntl.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_packet.h>
+#include <linux/wireless.h>
+#include <linux/kmod.h>
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <linux/errno.h>
+#include <linux/timer.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/ioctls.h>
+#include <linux/proc_fs.h>
+#include <linux/poll.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/if_bridge.h>
+
+#ifdef CONFIG_NET_DIVERT
+#include <linux/divert.h>
+#endif /* CONFIG_NET_DIVERT */
+
+#ifdef CONFIG_INET
+#include <net/inet_common.h>
+#endif
+
+#ifdef CONFIG_DLCI
+extern int dlci_ioctl(unsigned int, void*);
+#endif
+
+#define CONFIG_SOCK_PACKET 1
+
+/*
+ Proposed replacement for SIOC{ADD,DEL}MULTI and
+ IFF_PROMISC, IFF_ALLMULTI flags.
+
+ It is more expensive, but I believe,
+ it is really correct solution: reentereble, safe and fault tolerant.
+
+ IFF_PROMISC/IFF_ALLMULTI/SIOC{ADD/DEL}MULTI are faked by keeping
+ reference count and global flag, so that real status is
+ (gflag|(count != 0)), so that we can use obsolete faulty interface
+ not harming clever users.
+ */
+#define CONFIG_PACKET_MULTICAST 1
+
+/*
+ Assumptions:
+ - if device has no dev->hard_header routine, it adds and removes ll header
+ inside itself. In this case ll header is invisible outside of device,
+ but higher levels still should reserve dev->hard_header_len.
+ Some devices are enough clever to reallocate skb, when header
+ will not fit to reserved space (tunnel), another ones are silly
+ (PPP).
+ - packet socket receives packets with pulled ll header,
+ so that SOCK_RAW should push it back.
+
+On receive:
+-----------
+
+Incoming, dev->hard_header!=NULL
+ mac.raw -> ll header
+ data -> data
+
+Outgoing, dev->hard_header!=NULL
+ mac.raw -> ll header
+ data -> ll header
+
+Incoming, dev->hard_header==NULL
+ mac.raw -> UNKNOWN position. It is very likely, that it points to ll header.
+ PPP makes it, that is wrong, because introduce assymetry
+ between rx and tx paths.
+ data -> data
+
+Outgoing, dev->hard_header==NULL
+ mac.raw -> data. ll header is still not built!
+ data -> data
+
+Resume
+ If dev->hard_header==NULL we are unlikely to restore sensible ll header.
+
+
+On transmit:
+------------
+
+dev->hard_header != NULL
+ mac.raw -> ll header
+ data -> ll header
+
+dev->hard_header == NULL (ll header is added by device, we cannot control it)
+ mac.raw -> data
+ data -> data
+
+ We should set nh.raw on output to correct posistion,
+ packet classifier depends on it.
+ */
+
+/* List of all packet sockets. */
+static struct sock * packet_sklist;
+static rwlock_t packet_sklist_lock = RW_LOCK_UNLOCKED;
+
+atomic_t packet_socks_nr;
+
+
+/* Private packet socket structures. */
+
+#ifdef CONFIG_PACKET_MULTICAST
+struct packet_mclist
+{
+ struct packet_mclist *next;
+ int ifindex;
+ int count;
+ unsigned short type;
+ unsigned short alen;
+ unsigned char addr[8];
+};
+#endif
+#ifdef CONFIG_PACKET_MMAP
+static int packet_set_ring(struct sock *sk, struct tpacket_req *req, int closing);
+#endif
+
+static void packet_flush_mclist(struct sock *sk);
+
+struct packet_opt
+{
+ struct tpacket_stats stats;
+#ifdef CONFIG_PACKET_MMAP
+ unsigned long *pg_vec;
+ unsigned int head;
+ unsigned int frames_per_block;
+ unsigned int frame_size;
+ unsigned int frame_max;
+ int copy_thresh;
+#endif
+ struct packet_type prot_hook;
+ spinlock_t bind_lock;
+ char running; /* prot_hook is attached*/
+ int ifindex; /* bound device */
+#ifdef CONFIG_PACKET_MULTICAST
+ struct packet_mclist *mclist;
+#endif
+#ifdef CONFIG_PACKET_MMAP
+ atomic_t mapped;
+ unsigned int pg_vec_order;
+ unsigned int pg_vec_pages;
+ unsigned int pg_vec_len;
+#endif
+};
+
+#ifdef CONFIG_PACKET_MMAP
+
+static inline unsigned long packet_lookup_frame(struct packet_opt *po, unsigned int position)
+{
+ unsigned int pg_vec_pos, frame_offset;
+ unsigned long frame;
+
+ pg_vec_pos = position / po->frames_per_block;
+ frame_offset = position % po->frames_per_block;
+
+ frame = (unsigned long) (po->pg_vec[pg_vec_pos] + (frame_offset * po->frame_size));
+
+ return frame;
+}
+#endif
+
+void packet_sock_destruct(struct sock *sk)
+{
+ BUG_TRAP(atomic_read(&sk->rmem_alloc)==0);
+ BUG_TRAP(atomic_read(&sk->wmem_alloc)==0);
+
+ if (!sk->dead) {
+ printk("Attempt to release alive packet socket: %p\n", sk);
+ return;
+ }
+
+ if (sk->protinfo.destruct_hook)
+ kfree(sk->protinfo.destruct_hook);
+ atomic_dec(&packet_socks_nr);
+#ifdef PACKET_REFCNT_DEBUG
+ printk(KERN_DEBUG "PACKET socket %p is free, %d are alive\n", sk, atomic_read(&packet_socks_nr));
+#endif
+ MOD_DEC_USE_COUNT;
+}
+
+
+extern struct proto_ops packet_ops;
+
+#ifdef CONFIG_SOCK_PACKET
+extern struct proto_ops packet_ops_spkt;
+
+static int packet_rcv_spkt(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
+{
+ struct sock *sk;
+ struct sockaddr_pkt *spkt;
+
+ /*
+ * When we registered the protocol we saved the socket in the data
+ * field for just this event.
+ */
+
+ sk = (struct sock *) pt->data;
+
+ /*
+ * Yank back the headers [hope the device set this
+ * right or kerboom...]
+ *
+ * Incoming packets have ll header pulled,
+ * push it back.
+ *
+ * For outgoing ones skb->data == skb->mac.raw
+ * so that this procedure is noop.
+ */
+
+ if (skb->pkt_type == PACKET_LOOPBACK)
+ goto out;
+
+ if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL)
+ goto oom;
+
+ spkt = (struct sockaddr_pkt*)skb->cb;
+
+ skb_push(skb, skb->data-skb->mac.raw);
+
+ /*
+ * The SOCK_PACKET socket receives _all_ frames.
+ */
+
+ spkt->spkt_family = dev->type;
+ strncpy(spkt->spkt_device, dev->name, sizeof(spkt->spkt_device));
+ spkt->spkt_protocol = skb->protocol;
+
+ /*
+ * Charge the memory to the socket. This is done specifically
+ * to prevent sockets using all the memory up.
+ */
+
+ if (sock_queue_rcv_skb(sk,skb) == 0)
+ return 0;
+
+out:
+ kfree_skb(skb);
+oom:
+ return 0;
+}
+
+
+/*
+ * Output a raw packet to a device layer. This bypasses all the other
+ * protocol layers and you must therefore supply it with a complete frame
+ */
+
+static int packet_sendmsg_spkt(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_pkt *saddr=(struct sockaddr_pkt *)msg->msg_name;
+ struct sk_buff *skb;
+ struct net_device *dev;
+ unsigned short proto=0;
+ int err;
+
+ /*
+ * Get and verify the address.
+ */
+
+ if (saddr)
+ {
+ if (msg->msg_namelen < sizeof(struct sockaddr))
+ return(-EINVAL);
+ if (msg->msg_namelen==sizeof(struct sockaddr_pkt))
+ proto=saddr->spkt_protocol;
+ }
+ else
+ return(-ENOTCONN); /* SOCK_PACKET must be sent giving an address */
+
+ /*
+ * Find the device first to size check it
+ */
+
+ saddr->spkt_device[13] = 0;
+ dev = dev_get_by_name(saddr->spkt_device);
+ err = -ENODEV;
+ if (dev == NULL)
+ goto out_unlock;
+
+ /*
+ * You may not queue a frame bigger than the mtu. This is the lowest level
+ * raw protocol and you must do your own fragmentation at this level.
+ */
+
+ err = -EMSGSIZE;
+ if(len>dev->mtu+dev->hard_header_len)
+ goto out_unlock;
+
+ err = -ENOBUFS;
+ skb = sock_wmalloc(sk, len+dev->hard_header_len+15, 0, GFP_KERNEL);
+
+ /*
+ * If the write buffer is full, then tough. At this level the user gets to
+ * deal with the problem - do your own algorithmic backoffs. That's far
+ * more flexible.
+ */
+
+ if (skb == NULL)
+ goto out_unlock;
+
+ /*
+ * Fill it in
+ */
+
+ /* FIXME: Save some space for broken drivers that write a
+ * hard header at transmission time by themselves. PPP is the
+ * notable one here. This should really be fixed at the driver level.
+ */
+ skb_reserve(skb,(dev->hard_header_len+15)&~15);
+ skb->nh.raw = skb->data;
+
+ /* Try to align data part correctly */
+ if (dev->hard_header) {
+ skb->data -= dev->hard_header_len;
+ skb->tail -= dev->hard_header_len;
+ if (len < dev->hard_header_len)
+ skb->nh.raw = skb->data;
+ }
+
+ /* Returns -EFAULT on error */
+ err = memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len);
+ skb->protocol = proto;
+ skb->dev = dev;
+ skb->priority = sk->priority;
+ if (err)
+ goto out_free;
+
+ err = -ENETDOWN;
+ if (!(dev->flags & IFF_UP))
+ goto out_free;
+
+ /*
+ * Now send it
+ */
+
+ dev_queue_xmit(skb);
+ dev_put(dev);
+ return(len);
+
+out_free:
+ kfree_skb(skb);
+out_unlock:
+ if (dev)
+ dev_put(dev);
+ return err;
+}
+#endif
+
+/*
+ This function makes lazy skb cloning in hope that most of packets
+ are discarded by BPF.
+
+ Note tricky part: we DO mangle shared skb! skb->data, skb->len
+ and skb->cb are mangled. It works because (and until) packets
+ falling here are owned by current CPU. Output packets are cloned
+ by dev_queue_xmit_nit(), input packets are processed by net_bh
+ sequencially, so that if we return skb to original state on exit,
+ we will not harm anyone.
+ */
+
+static int packet_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
+{
+ struct sock *sk;
+ struct sockaddr_ll *sll;
+ struct packet_opt *po;
+ u8 * skb_head = skb->data;
+ int skb_len = skb->len;
+#ifdef CONFIG_FILTER
+ unsigned snaplen;
+#endif
+
+ if (skb->pkt_type == PACKET_LOOPBACK)
+ goto drop;
+
+ sk = (struct sock *) pt->data;
+ po = sk->protinfo.af_packet;
+
+ skb->dev = dev;
+
+ if (dev->hard_header) {
+ /* The device has an explicit notion of ll header,
+ exported to higher levels.
+
+ Otherwise, the device hides datails of it frame
+ structure, so that corresponding packet head
+ never delivered to user.
+ */
+ if (sk->type != SOCK_DGRAM)
+ skb_push(skb, skb->data - skb->mac.raw);
+ else if (skb->pkt_type == PACKET_OUTGOING) {
+ /* Special case: outgoing packets have ll header at head */
+ skb_pull(skb, skb->nh.raw - skb->data);
+ }
+ }
+
+#ifdef CONFIG_FILTER
+ snaplen = skb->len;
+
+ if (sk->filter) {
+ unsigned res = snaplen;
+ struct sk_filter *filter;
+
+ bh_lock_sock(sk);
+ if ((filter = sk->filter) != NULL)
+ res = sk_run_filter(skb, sk->filter->insns, sk->filter->len);
+ bh_unlock_sock(sk);
+
+ if (res == 0)
+ goto drop_n_restore;
+ if (snaplen > res)
+ snaplen = res;
+ }
+#endif /* CONFIG_FILTER */
+
+ if (atomic_read(&sk->rmem_alloc) + skb->truesize >= (unsigned)sk->rcvbuf)
+ goto drop_n_acct;
+
+ if (skb_shared(skb)) {
+ struct sk_buff *nskb = skb_clone(skb, GFP_ATOMIC);
+ if (nskb == NULL)
+ goto drop_n_acct;
+
+ if (skb_head != skb->data) {
+ skb->data = skb_head;
+ skb->len = skb_len;
+ }
+ kfree_skb(skb);
+ skb = nskb;
+ }
+
+ sll = (struct sockaddr_ll*)skb->cb;
+ sll->sll_family = AF_PACKET;
+ sll->sll_hatype = dev->type;
+ sll->sll_protocol = skb->protocol;
+ sll->sll_pkttype = skb->pkt_type;
+ sll->sll_ifindex = dev->ifindex;
+ sll->sll_halen = 0;
+
+ if (dev->hard_header_parse)
+ sll->sll_halen = dev->hard_header_parse(skb, sll->sll_addr);
+
+#ifdef CONFIG_FILTER
+ if (pskb_trim(skb, snaplen))
+ goto drop_n_acct;
+#endif
+
+ skb_set_owner_r(skb, sk);
+ skb->dev = NULL;
+ spin_lock(&sk->receive_queue.lock);
+ po->stats.tp_packets++;
+ __skb_queue_tail(&sk->receive_queue, skb);
+ spin_unlock(&sk->receive_queue.lock);
+ sk->data_ready(sk,skb->len);
+ return 0;
+
+drop_n_acct:
+ spin_lock(&sk->receive_queue.lock);
+ po->stats.tp_drops++;
+ spin_unlock(&sk->receive_queue.lock);
+
+#ifdef CONFIG_FILTER
+drop_n_restore:
+#endif
+ if (skb_head != skb->data && skb_shared(skb)) {
+ skb->data = skb_head;
+ skb->len = skb_len;
+ }
+drop:
+ kfree_skb(skb);
+ return 0;
+}
+
+#ifdef CONFIG_PACKET_MMAP
+static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt)
+{
+ struct sock *sk;
+ struct packet_opt *po;
+ struct sockaddr_ll *sll;
+ struct tpacket_hdr *h;
+ u8 * skb_head = skb->data;
+ int skb_len = skb->len;
+ unsigned snaplen;
+ unsigned long status = TP_STATUS_LOSING|TP_STATUS_USER;
+ unsigned short macoff, netoff;
+ struct sk_buff *copy_skb = NULL;
+
+ if (skb->pkt_type == PACKET_LOOPBACK)
+ goto drop;
+
+ sk = (struct sock *) pt->data;
+ po = sk->protinfo.af_packet;
+
+ if (dev->hard_header) {
+ if (sk->type != SOCK_DGRAM)
+ skb_push(skb, skb->data - skb->mac.raw);
+ else if (skb->pkt_type == PACKET_OUTGOING) {
+ /* Special case: outgoing packets have ll header at head */
+ skb_pull(skb, skb->nh.raw - skb->data);
+ if (skb->ip_summed == CHECKSUM_HW)
+ status |= TP_STATUS_CSUMNOTREADY;
+ }
+ }
+
+ snaplen = skb->len;
+
+#ifdef CONFIG_FILTER
+ if (sk->filter) {
+ unsigned res = snaplen;
+ struct sk_filter *filter;
+
+ bh_lock_sock(sk);
+ if ((filter = sk->filter) != NULL)
+ res = sk_run_filter(skb, sk->filter->insns, sk->filter->len);
+ bh_unlock_sock(sk);
+
+ if (res == 0)
+ goto drop_n_restore;
+ if (snaplen > res)
+ snaplen = res;
+ }
+#endif
+
+ if (sk->type == SOCK_DGRAM) {
+ macoff = netoff = TPACKET_ALIGN(TPACKET_HDRLEN) + 16;
+ } else {
+ unsigned maclen = skb->nh.raw - skb->data;
+ netoff = TPACKET_ALIGN(TPACKET_HDRLEN + (maclen < 16 ? 16 : maclen));
+ macoff = netoff - maclen;
+ }
+
+ if (macoff + snaplen > po->frame_size) {
+ if (po->copy_thresh &&
+ atomic_read(&sk->rmem_alloc) + skb->truesize < (unsigned)sk->rcvbuf) {
+ if (skb_shared(skb)) {
+ copy_skb = skb_clone(skb, GFP_ATOMIC);
+ } else {
+ copy_skb = skb_get(skb);
+ skb_head = skb->data;
+ }
+ if (copy_skb)
+ skb_set_owner_r(copy_skb, sk);
+ }
+ snaplen = po->frame_size - macoff;
+ if ((int)snaplen < 0)
+ snaplen = 0;
+ }
+ if (snaplen > skb->len-skb->data_len)
+ snaplen = skb->len-skb->data_len;
+
+ spin_lock(&sk->receive_queue.lock);
+ h = (struct tpacket_hdr *)packet_lookup_frame(po, po->head);
+
+ if (h->tp_status)
+ goto ring_is_full;
+ po->head = po->head != po->frame_max ? po->head+1 : 0;
+ po->stats.tp_packets++;
+ if (copy_skb) {
+ status |= TP_STATUS_COPY;
+ __skb_queue_tail(&sk->receive_queue, copy_skb);
+ }
+ if (!po->stats.tp_drops)
+ status &= ~TP_STATUS_LOSING;
+ spin_unlock(&sk->receive_queue.lock);
+
+ memcpy((u8*)h + macoff, skb->data, snaplen);
+
+ h->tp_len = skb->len;
+ h->tp_snaplen = snaplen;
+ h->tp_mac = macoff;
+ h->tp_net = netoff;
+ h->tp_sec = skb->stamp.tv_sec;
+ h->tp_usec = skb->stamp.tv_usec;
+
+ sll = (struct sockaddr_ll*)((u8*)h + TPACKET_ALIGN(sizeof(*h)));
+ sll->sll_halen = 0;
+ if (dev->hard_header_parse)
+ sll->sll_halen = dev->hard_header_parse(skb, sll->sll_addr);
+ sll->sll_family = AF_PACKET;
+ sll->sll_hatype = dev->type;
+ sll->sll_protocol = skb->protocol;
+ sll->sll_pkttype = skb->pkt_type;
+ sll->sll_ifindex = dev->ifindex;
+
+ h->tp_status = status;
+ mb();
+
+ {
+ struct page *p_start, *p_end;
+ u8 *h_end = (u8 *)h + macoff + snaplen - 1;
+
+ p_start = virt_to_page(h);
+ p_end = virt_to_page(h_end);
+ while (p_start <= p_end) {
+ flush_dcache_page(p_start);
+ p_start++;
+ }
+ }
+
+ sk->data_ready(sk, 0);
+
+drop_n_restore:
+ if (skb_head != skb->data && skb_shared(skb)) {
+ skb->data = skb_head;
+ skb->len = skb_len;
+ }
+drop:
+ kfree_skb(skb);
+ return 0;
+
+ring_is_full:
+ po->stats.tp_drops++;
+ spin_unlock(&sk->receive_queue.lock);
+
+ sk->data_ready(sk, 0);
+ if (copy_skb)
+ kfree_skb(copy_skb);
+ goto drop_n_restore;
+}
+
+#endif
+
+
+static int packet_sendmsg(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_ll *saddr=(struct sockaddr_ll *)msg->msg_name;
+ struct sk_buff *skb;
+ struct net_device *dev;
+ unsigned short proto;
+ unsigned char *addr;
+ int ifindex, err, reserve = 0;
+
+ /*
+ * Get and verify the address.
+ */
+
+ if (saddr == NULL) {
+ ifindex = sk->protinfo.af_packet->ifindex;
+ proto = sk->num;
+ addr = NULL;
+ } else {
+ err = -EINVAL;
+ if (msg->msg_namelen < sizeof(struct sockaddr_ll))
+ goto out;
+ ifindex = saddr->sll_ifindex;
+ proto = saddr->sll_protocol;
+ addr = saddr->sll_addr;
+ }
+
+
+ dev = dev_get_by_index(ifindex);
+ err = -ENXIO;
+ if (dev == NULL)
+ goto out_unlock;
+ if (sock->type == SOCK_RAW)
+ reserve = dev->hard_header_len;
+
+ err = -EMSGSIZE;
+ if (len > dev->mtu+reserve)
+ goto out_unlock;
+
+ skb = sock_alloc_send_skb(sk, len+dev->hard_header_len+15,
+ msg->msg_flags & MSG_DONTWAIT, &err);
+ if (skb==NULL)
+ goto out_unlock;
+
+ skb_reserve(skb, (dev->hard_header_len+15)&~15);
+ skb->nh.raw = skb->data;
+
+ if (dev->hard_header) {
+ int res;
+ err = -EINVAL;
+ res = dev->hard_header(skb, dev, ntohs(proto), addr, NULL, len);
+ if (sock->type != SOCK_DGRAM) {
+ skb->tail = skb->data;
+ skb->len = 0;
+ } else if (res < 0)
+ goto out_free;
+ }
+
+ /* Returns -EFAULT on error */
+ err = memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len);
+ if (err)
+ goto out_free;
+
+ skb->protocol = proto;
+ skb->dev = dev;
+ skb->priority = sk->priority;
+
+ err = -ENETDOWN;
+ if (!(dev->flags & IFF_UP))
+ goto out_free;
+
+ /*
+ * Now send it
+ */
+
+ err = dev_queue_xmit(skb);
+ if (err > 0 && (err = net_xmit_errno(err)) != 0)
+ goto out_unlock;
+
+ dev_put(dev);
+
+ return(len);
+
+out_free:
+ kfree_skb(skb);
+out_unlock:
+ if (dev)
+ dev_put(dev);
+out:
+ return err;
+}
+
+/*
+ * Close a PACKET socket. This is fairly simple. We immediately go
+ * to 'closed' state and remove our protocol entry in the device list.
+ */
+
+static int packet_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ struct sock **skp;
+
+ if (!sk)
+ return 0;
+
+ write_lock_bh(&packet_sklist_lock);
+ for (skp = &packet_sklist; *skp; skp = &(*skp)->next) {
+ if (*skp == sk) {
+ *skp = sk->next;
+ __sock_put(sk);
+ break;
+ }
+ }
+ write_unlock_bh(&packet_sklist_lock);
+
+ /*
+ * Unhook packet receive handler.
+ */
+
+ if (sk->protinfo.af_packet->running) {
+ /*
+ * Remove the protocol hook
+ */
+ dev_remove_pack(&sk->protinfo.af_packet->prot_hook);
+ sk->protinfo.af_packet->running = 0;
+ __sock_put(sk);
+ }
+
+#ifdef CONFIG_PACKET_MULTICAST
+ packet_flush_mclist(sk);
+#endif
+
+#ifdef CONFIG_PACKET_MMAP
+ if (sk->protinfo.af_packet->pg_vec) {
+ struct tpacket_req req;
+ memset(&req, 0, sizeof(req));
+ packet_set_ring(sk, &req, 1);
+ }
+#endif
+
+ /*
+ * Now the socket is dead. No more input will appear.
+ */
+
+ sock_orphan(sk);
+ sock->sk = NULL;
+
+ /* Purge queues */
+
+ skb_queue_purge(&sk->receive_queue);
+
+ sock_put(sk);
+ return 0;
+}
+
+/*
+ * Attach a packet hook.
+ */
+
+static int packet_do_bind(struct sock *sk, struct net_device *dev, int protocol)
+{
+ /*
+ * Detach an existing hook if present.
+ */
+
+ lock_sock(sk);
+
+ spin_lock(&sk->protinfo.af_packet->bind_lock);
+ if (sk->protinfo.af_packet->running) {
+ dev_remove_pack(&sk->protinfo.af_packet->prot_hook);
+ __sock_put(sk);
+ sk->protinfo.af_packet->running = 0;
+ }
+
+ sk->num = protocol;
+ sk->protinfo.af_packet->prot_hook.type = protocol;
+ sk->protinfo.af_packet->prot_hook.dev = dev;
+
+ sk->protinfo.af_packet->ifindex = dev ? dev->ifindex : 0;
+
+ if (protocol == 0)
+ goto out_unlock;
+
+ if (dev) {
+ if (dev->flags&IFF_UP) {
+ dev_add_pack(&sk->protinfo.af_packet->prot_hook);
+ sock_hold(sk);
+ sk->protinfo.af_packet->running = 1;
+ } else {
+ sk->err = ENETDOWN;
+ if (!sk->dead)
+ sk->error_report(sk);
+ }
+ } else {
+ dev_add_pack(&sk->protinfo.af_packet->prot_hook);
+ sock_hold(sk);
+ sk->protinfo.af_packet->running = 1;
+ }
+
+out_unlock:
+ spin_unlock(&sk->protinfo.af_packet->bind_lock);
+ release_sock(sk);
+ return 0;
+}
+
+/*
+ * Bind a packet socket to a device
+ */
+
+#ifdef CONFIG_SOCK_PACKET
+
+static int packet_bind_spkt(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sock *sk=sock->sk;
+ char name[15];
+ struct net_device *dev;
+ int err = -ENODEV;
+
+ /*
+ * Check legality
+ */
+
+ if(addr_len!=sizeof(struct sockaddr))
+ return -EINVAL;
+ strncpy(name,uaddr->sa_data,14);
+ name[14]=0;
+
+ dev = dev_get_by_name(name);
+ if (dev) {
+ err = packet_do_bind(sk, dev, sk->num);
+ dev_put(dev);
+ }
+ return err;
+}
+#endif
+
+static int packet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sockaddr_ll *sll = (struct sockaddr_ll*)uaddr;
+ struct sock *sk=sock->sk;
+ struct net_device *dev = NULL;
+ int err;
+
+
+ /*
+ * Check legality
+ */
+
+ if (addr_len < sizeof(struct sockaddr_ll))
+ return -EINVAL;
+ if (sll->sll_family != AF_PACKET)
+ return -EINVAL;
+
+ if (sll->sll_ifindex) {
+ err = -ENODEV;
+ dev = dev_get_by_index(sll->sll_ifindex);
+ if (dev == NULL)
+ goto out;
+ }
+ err = packet_do_bind(sk, dev, sll->sll_protocol ? : sk->num);
+ if (dev)
+ dev_put(dev);
+
+out:
+ return err;
+}
+
+
+/*
+ * Create a packet of type SOCK_PACKET.
+ */
+
+static int packet_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+ int err;
+
+ if (!capable(CAP_NET_RAW))
+ return -EPERM;
+ if (sock->type != SOCK_DGRAM && sock->type != SOCK_RAW
+#ifdef CONFIG_SOCK_PACKET
+ && sock->type != SOCK_PACKET
+#endif
+ )
+ return -ESOCKTNOSUPPORT;
+
+ sock->state = SS_UNCONNECTED;
+ MOD_INC_USE_COUNT;
+
+ err = -ENOBUFS;
+ sk = sk_alloc(PF_PACKET, GFP_KERNEL, 1);
+ if (sk == NULL)
+ goto out;
+
+ sock->ops = &packet_ops;
+#ifdef CONFIG_SOCK_PACKET
+ if (sock->type == SOCK_PACKET)
+ sock->ops = &packet_ops_spkt;
+#endif
+ sock_init_data(sock,sk);
+
+ sk->protinfo.af_packet = kmalloc(sizeof(struct packet_opt), GFP_KERNEL);
+ if (sk->protinfo.af_packet == NULL)
+ goto out_free;
+ memset(sk->protinfo.af_packet, 0, sizeof(struct packet_opt));
+ sk->family = PF_PACKET;
+ sk->num = protocol;
+
+ sk->destruct = packet_sock_destruct;
+ atomic_inc(&packet_socks_nr);
+
+ /*
+ * Attach a protocol block
+ */
+
+ spin_lock_init(&sk->protinfo.af_packet->bind_lock);
+ sk->protinfo.af_packet->prot_hook.func = packet_rcv;
+#ifdef CONFIG_SOCK_PACKET
+ if (sock->type == SOCK_PACKET)
+ sk->protinfo.af_packet->prot_hook.func = packet_rcv_spkt;
+#endif
+ sk->protinfo.af_packet->prot_hook.data = (void *)sk;
+
+ if (protocol) {
+ sk->protinfo.af_packet->prot_hook.type = protocol;
+ dev_add_pack(&sk->protinfo.af_packet->prot_hook);
+ sock_hold(sk);
+ sk->protinfo.af_packet->running = 1;
+ }
+
+ write_lock_bh(&packet_sklist_lock);
+ sk->next = packet_sklist;
+ packet_sklist = sk;
+ sock_hold(sk);
+ write_unlock_bh(&packet_sklist_lock);
+ return(0);
+
+out_free:
+ sk_free(sk);
+out:
+ MOD_DEC_USE_COUNT;
+ return err;
+}
+
+/*
+ * Pull a packet from our receive queue and hand it to the user.
+ * If necessary we block.
+ */
+
+static int packet_recvmsg(struct socket *sock, struct msghdr *msg, int len,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sk_buff *skb;
+ int copied, err;
+
+ err = -EINVAL;
+ if (flags & ~(MSG_PEEK|MSG_DONTWAIT|MSG_TRUNC))
+ goto out;
+
+#if 0
+ /* What error should we return now? EUNATTACH? */
+ if (sk->protinfo.af_packet->ifindex < 0)
+ return -ENODEV;
+#endif
+
+ /*
+ * If the address length field is there to be filled in, we fill
+ * it in now.
+ */
+
+ if (sock->type == SOCK_PACKET)
+ msg->msg_namelen = sizeof(struct sockaddr_pkt);
+ else
+ msg->msg_namelen = sizeof(struct sockaddr_ll);
+
+ /*
+ * Call the generic datagram receiver. This handles all sorts
+ * of horrible races and re-entrancy so we can forget about it
+ * in the protocol layers.
+ *
+ * Now it will return ENETDOWN, if device have just gone down,
+ * but then it will block.
+ */
+
+ skb=skb_recv_datagram(sk,flags,flags&MSG_DONTWAIT,&err);
+
+ /*
+ * An error occurred so return it. Because skb_recv_datagram()
+ * handles the blocking we don't see and worry about blocking
+ * retries.
+ */
+
+ if(skb==NULL)
+ goto out;
+
+ /*
+ * You lose any data beyond the buffer you gave. If it worries a
+ * user program they can ask the device for its MTU anyway.
+ */
+
+ copied = skb->len;
+ if (copied > len)
+ {
+ copied=len;
+ msg->msg_flags|=MSG_TRUNC;
+ }
+
+ err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+ if (err)
+ goto out_free;
+
+ sock_recv_timestamp(msg, sk, skb);
+
+ if (msg->msg_name)
+ memcpy(msg->msg_name, skb->cb, msg->msg_namelen);
+
+ /*
+ * Free or return the buffer as appropriate. Again this
+ * hides all the races and re-entrancy issues from us.
+ */
+ err = (flags&MSG_TRUNC) ? skb->len : copied;
+
+out_free:
+ skb_free_datagram(sk, skb);
+out:
+ return err;
+}
+
+#ifdef CONFIG_SOCK_PACKET
+static int packet_getname_spkt(struct socket *sock, struct sockaddr *uaddr,
+ int *uaddr_len, int peer)
+{
+ struct net_device *dev;
+ struct sock *sk = sock->sk;
+
+ if (peer)
+ return -EOPNOTSUPP;
+
+ uaddr->sa_family = AF_PACKET;
+ dev = dev_get_by_index(sk->protinfo.af_packet->ifindex);
+ if (dev) {
+ strncpy(uaddr->sa_data, dev->name, 15);
+ dev_put(dev);
+ } else
+ memset(uaddr->sa_data, 0, 14);
+ *uaddr_len = sizeof(*uaddr);
+
+ return 0;
+}
+#endif
+
+static int packet_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *uaddr_len, int peer)
+{
+ struct net_device *dev;
+ struct sock *sk = sock->sk;
+ struct sockaddr_ll *sll = (struct sockaddr_ll*)uaddr;
+
+ if (peer)
+ return -EOPNOTSUPP;
+
+ sll->sll_family = AF_PACKET;
+ sll->sll_ifindex = sk->protinfo.af_packet->ifindex;
+ sll->sll_protocol = sk->num;
+ dev = dev_get_by_index(sk->protinfo.af_packet->ifindex);
+ if (dev) {
+ sll->sll_hatype = dev->type;
+ sll->sll_halen = dev->addr_len;
+ memcpy(sll->sll_addr, dev->dev_addr, dev->addr_len);
+ dev_put(dev);
+ } else {
+ sll->sll_hatype = 0; /* Bad: we have no ARPHRD_UNSPEC */
+ sll->sll_halen = 0;
+ }
+ *uaddr_len = sizeof(*sll);
+
+ return 0;
+}
+
+#ifdef CONFIG_PACKET_MULTICAST
+static void packet_dev_mc(struct net_device *dev, struct packet_mclist *i, int what)
+{
+ switch (i->type) {
+ case PACKET_MR_MULTICAST:
+ if (what > 0)
+ dev_mc_add(dev, i->addr, i->alen, 0);
+ else
+ dev_mc_delete(dev, i->addr, i->alen, 0);
+ break;
+ case PACKET_MR_PROMISC:
+ dev_set_promiscuity(dev, what);
+ break;
+ case PACKET_MR_ALLMULTI:
+ dev_set_allmulti(dev, what);
+ break;
+ default:;
+ }
+}
+
+static void packet_dev_mclist(struct net_device *dev, struct packet_mclist *i, int what)
+{
+ for ( ; i; i=i->next) {
+ if (i->ifindex == dev->ifindex)
+ packet_dev_mc(dev, i, what);
+ }
+}
+
+static int packet_mc_add(struct sock *sk, struct packet_mreq *mreq)
+{
+ struct packet_mclist *ml, *i;
+ struct net_device *dev;
+ int err;
+
+ rtnl_lock();
+
+ err = -ENODEV;
+ dev = __dev_get_by_index(mreq->mr_ifindex);
+ if (!dev)
+ goto done;
+
+ err = -EINVAL;
+ if (mreq->mr_alen > dev->addr_len)
+ goto done;
+
+ err = -ENOBUFS;
+ i = (struct packet_mclist *)kmalloc(sizeof(*i), GFP_KERNEL);
+ if (i == NULL)
+ goto done;
+
+ err = 0;
+ for (ml=sk->protinfo.af_packet->mclist; ml; ml=ml->next) {
+ if (ml->ifindex == mreq->mr_ifindex &&
+ ml->type == mreq->mr_type &&
+ ml->alen == mreq->mr_alen &&
+ memcmp(ml->addr, mreq->mr_address, ml->alen) == 0) {
+ ml->count++;
+ /* Free the new element ... */
+ kfree(i);
+ goto done;
+ }
+ }
+
+ i->type = mreq->mr_type;
+ i->ifindex = mreq->mr_ifindex;
+ i->alen = mreq->mr_alen;
+ memcpy(i->addr, mreq->mr_address, i->alen);
+ i->count = 1;
+ i->next = sk->protinfo.af_packet->mclist;
+ sk->protinfo.af_packet->mclist = i;
+ packet_dev_mc(dev, i, +1);
+
+done:
+ rtnl_unlock();
+ return err;
+}
+
+static int packet_mc_drop(struct sock *sk, struct packet_mreq *mreq)
+{
+ struct packet_mclist *ml, **mlp;
+
+ rtnl_lock();
+
+ for (mlp=&sk->protinfo.af_packet->mclist; (ml=*mlp)!=NULL; mlp=&ml->next) {
+ if (ml->ifindex == mreq->mr_ifindex &&
+ ml->type == mreq->mr_type &&
+ ml->alen == mreq->mr_alen &&
+ memcmp(ml->addr, mreq->mr_address, ml->alen) == 0) {
+ if (--ml->count == 0) {
+ struct net_device *dev;
+ *mlp = ml->next;
+ dev = dev_get_by_index(ml->ifindex);
+ if (dev) {
+ packet_dev_mc(dev, ml, -1);
+ dev_put(dev);
+ }
+ kfree(ml);
+ }
+ rtnl_unlock();
+ return 0;
+ }
+ }
+ rtnl_unlock();
+ return -EADDRNOTAVAIL;
+}
+
+static void packet_flush_mclist(struct sock *sk)
+{
+ struct packet_mclist *ml;
+
+ if (sk->protinfo.af_packet->mclist == NULL)
+ return;
+
+ rtnl_lock();
+ while ((ml=sk->protinfo.af_packet->mclist) != NULL) {
+ struct net_device *dev;
+ sk->protinfo.af_packet->mclist = ml->next;
+ if ((dev = dev_get_by_index(ml->ifindex)) != NULL) {
+ packet_dev_mc(dev, ml, -1);
+ dev_put(dev);
+ }
+ kfree(ml);
+ }
+ rtnl_unlock();
+}
+#endif
+
+static int
+packet_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen)
+{
+ struct sock *sk = sock->sk;
+ int ret;
+
+ if (level != SOL_PACKET)
+ return -ENOPROTOOPT;
+
+ switch(optname) {
+#ifdef CONFIG_PACKET_MULTICAST
+ case PACKET_ADD_MEMBERSHIP:
+ case PACKET_DROP_MEMBERSHIP:
+ {
+ struct packet_mreq mreq;
+ if (optlen<sizeof(mreq))
+ return -EINVAL;
+ if (copy_from_user(&mreq,optval,sizeof(mreq)))
+ return -EFAULT;
+ if (optname == PACKET_ADD_MEMBERSHIP)
+ ret = packet_mc_add(sk, &mreq);
+ else
+ ret = packet_mc_drop(sk, &mreq);
+ return ret;
+ }
+#endif
+#ifdef CONFIG_PACKET_MMAP
+ case PACKET_RX_RING:
+ {
+ struct tpacket_req req;
+
+ if (optlen<sizeof(req))
+ return -EINVAL;
+ if (copy_from_user(&req,optval,sizeof(req)))
+ return -EFAULT;
+ return packet_set_ring(sk, &req, 0);
+ }
+ case PACKET_COPY_THRESH:
+ {
+ int val;
+
+ if (optlen!=sizeof(val))
+ return -EINVAL;
+ if (copy_from_user(&val,optval,sizeof(val)))
+ return -EFAULT;
+
+ sk->protinfo.af_packet->copy_thresh = val;
+ return 0;
+ }
+#endif
+ default:
+ return -ENOPROTOOPT;
+ }
+}
+
+int packet_getsockopt(struct socket *sock, int level, int optname,
+ char *optval, int *optlen)
+{
+ int len;
+ struct sock *sk = sock->sk;
+
+ if (level != SOL_PACKET)
+ return -ENOPROTOOPT;
+
+ if (get_user(len,optlen))
+ return -EFAULT;
+
+ if (len < 0)
+ return -EINVAL;
+
+ switch(optname) {
+ case PACKET_STATISTICS:
+ {
+ struct tpacket_stats st;
+
+ if (len > sizeof(struct tpacket_stats))
+ len = sizeof(struct tpacket_stats);
+ spin_lock_bh(&sk->receive_queue.lock);
+ st = sk->protinfo.af_packet->stats;
+ memset(&sk->protinfo.af_packet->stats, 0, sizeof(st));
+ spin_unlock_bh(&sk->receive_queue.lock);
+ st.tp_packets += st.tp_drops;
+
+ if (copy_to_user(optval, &st, len))
+ return -EFAULT;
+ break;
+ }
+ default:
+ return -ENOPROTOOPT;
+ }
+
+ if (put_user(len, optlen))
+ return -EFAULT;
+ return 0;
+}
+
+
+static int packet_notifier(struct notifier_block *this, unsigned long msg, void *data)
+{
+ struct sock *sk;
+ struct packet_opt *po;
+ struct net_device *dev = (struct net_device*)data;
+
+ read_lock(&packet_sklist_lock);
+ for (sk = packet_sklist; sk; sk = sk->next) {
+ po = sk->protinfo.af_packet;
+
+ switch (msg) {
+ case NETDEV_UNREGISTER:
+#ifdef CONFIG_PACKET_MULTICAST
+ if (po->mclist)
+ packet_dev_mclist(dev, po->mclist, -1);
+ // fallthrough
+#endif
+ case NETDEV_DOWN:
+ if (dev->ifindex == po->ifindex) {
+ spin_lock(&po->bind_lock);
+ if (po->running) {
+ dev_remove_pack(&po->prot_hook);
+ __sock_put(sk);
+ po->running = 0;
+ sk->err = ENETDOWN;
+ if (!sk->dead)
+ sk->error_report(sk);
+ }
+ if (msg == NETDEV_UNREGISTER) {
+ po->ifindex = -1;
+ po->prot_hook.dev = NULL;
+ }
+ spin_unlock(&po->bind_lock);
+ }
+ break;
+ case NETDEV_UP:
+ spin_lock(&po->bind_lock);
+ if (dev->ifindex == po->ifindex && sk->num && po->running==0) {
+ dev_add_pack(&po->prot_hook);
+ sock_hold(sk);
+ po->running = 1;
+ }
+ spin_unlock(&po->bind_lock);
+ break;
+ }
+ }
+ read_unlock(&packet_sklist_lock);
+ return NOTIFY_DONE;
+}
+
+
+static int packet_ioctl(struct socket *sock, unsigned int cmd,
+ unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+
+ switch(cmd)
+ {
+ case SIOCOUTQ:
+ {
+ int amount = atomic_read(&sk->wmem_alloc);
+ return put_user(amount, (int *)arg);
+ }
+ case SIOCINQ:
+ {
+ struct sk_buff *skb;
+ int amount = 0;
+
+ spin_lock_bh(&sk->receive_queue.lock);
+ skb = skb_peek(&sk->receive_queue);
+ if (skb)
+ amount = skb->len;
+ spin_unlock_bh(&sk->receive_queue.lock);
+ return put_user(amount, (int *)arg);
+ }
+ case FIOSETOWN:
+ case SIOCSPGRP: {
+ int pid;
+ if (get_user(pid, (int *) arg))
+ return -EFAULT;
+ if (current->pid != pid && current->pgrp != -pid &&
+ !capable(CAP_NET_ADMIN))
+ return -EPERM;
+ sk->proc = pid;
+ break;
+ }
+ case FIOGETOWN:
+ case SIOCGPGRP:
+ return put_user(sk->proc, (int *)arg);
+ case SIOCGSTAMP:
+ if(sk->stamp.tv_sec==0)
+ return -ENOENT;
+ if (copy_to_user((void *)arg, &sk->stamp,
+ sizeof(struct timeval)))
+ return -EFAULT;
+ break;
+ case SIOCGIFFLAGS:
+#ifndef CONFIG_INET
+ case SIOCSIFFLAGS:
+#endif
+ case SIOCGIFCONF:
+ case SIOCGIFMETRIC:
+ case SIOCSIFMETRIC:
+ case SIOCGIFMEM:
+ case SIOCSIFMEM:
+ case SIOCGIFMTU:
+ case SIOCSIFMTU:
+ case SIOCSIFLINK:
+ case SIOCGIFHWADDR:
+ case SIOCSIFHWADDR:
+ case SIOCSIFMAP:
+ case SIOCGIFMAP:
+ case SIOCSIFSLAVE:
+ case SIOCGIFSLAVE:
+ case SIOCGIFINDEX:
+ case SIOCGIFNAME:
+ case SIOCGIFCOUNT:
+ case SIOCSIFHWBROADCAST:
+ return(dev_ioctl(cmd,(void *) arg));
+
+ case SIOCGIFBR:
+ case SIOCSIFBR:
+#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
+#ifdef CONFIG_INET
+#ifdef CONFIG_KMOD
+ if (br_ioctl_hook == NULL)
+ request_module("bridge");
+#endif
+ if (br_ioctl_hook != NULL)
+ return br_ioctl_hook(arg);
+#endif
+#endif
+ return -ENOPKG;
+
+ case SIOCGIFDIVERT:
+ case SIOCSIFDIVERT:
+#ifdef CONFIG_NET_DIVERT
+ return divert_ioctl(cmd, (struct divert_cf *) arg);
+#else
+ return -ENOPKG;
+#endif /* CONFIG_NET_DIVERT */
+
+#ifdef CONFIG_INET
+ case SIOCADDRT:
+ case SIOCDELRT:
+ case SIOCDARP:
+ case SIOCGARP:
+ case SIOCSARP:
+ case SIOCGIFADDR:
+ case SIOCSIFADDR:
+ case SIOCGIFBRDADDR:
+ case SIOCSIFBRDADDR:
+ case SIOCGIFNETMASK:
+ case SIOCSIFNETMASK:
+ case SIOCGIFDSTADDR:
+ case SIOCSIFDSTADDR:
+ case SIOCSIFFLAGS:
+ case SIOCADDDLCI:
+ case SIOCDELDLCI:
+ return inet_dgram_ops.ioctl(sock, cmd, arg);
+#endif
+
+ default:
+ if ((cmd >= SIOCDEVPRIVATE) &&
+ (cmd <= (SIOCDEVPRIVATE + 15)))
+ return(dev_ioctl(cmd,(void *) arg));
+
+#ifdef CONFIG_NET_RADIO
+ if((cmd >= SIOCIWFIRST) && (cmd <= SIOCIWLAST))
+ return(dev_ioctl(cmd,(void *) arg));
+#endif
+ return -EOPNOTSUPP;
+ }
+ return 0;
+}
+
+#ifndef CONFIG_PACKET_MMAP
+#define packet_mmap sock_no_mmap
+#define packet_poll datagram_poll
+#else
+
+unsigned int packet_poll(struct file * file, struct socket *sock, poll_table *wait)
+{
+ struct sock *sk = sock->sk;
+ struct packet_opt *po = sk->protinfo.af_packet;
+ unsigned int mask = datagram_poll(file, sock, wait);
+
+ spin_lock_bh(&sk->receive_queue.lock);
+ if (po->pg_vec) {
+ unsigned last = po->head ? po->head-1 : po->frame_max;
+ struct tpacket_hdr *h;
+
+ h = (struct tpacket_hdr *)packet_lookup_frame(po, last);
+
+ if (h->tp_status)
+ mask |= POLLIN | POLLRDNORM;
+ }
+ spin_unlock_bh(&sk->receive_queue.lock);
+ return mask;
+}
+
+
+/* Dirty? Well, I still did not learn better way to account
+ * for user mmaps.
+ */
+
+static void packet_mm_open(struct vm_area_struct *vma)
+{
+ struct file *file = vma->vm_file;
+ struct inode *inode = file->f_dentry->d_inode;
+ struct socket * sock = &inode->u.socket_i;
+ struct sock *sk = sock->sk;
+
+ if (sk)
+ atomic_inc(&sk->protinfo.af_packet->mapped);
+}
+
+static void packet_mm_close(struct vm_area_struct *vma)
+{
+ struct file *file = vma->vm_file;
+ struct inode *inode = file->f_dentry->d_inode;
+ struct socket * sock = &inode->u.socket_i;
+ struct sock *sk = sock->sk;
+
+ if (sk)
+ atomic_dec(&sk->protinfo.af_packet->mapped);
+}
+
+static struct vm_operations_struct packet_mmap_ops = {
+ open: packet_mm_open,
+ close: packet_mm_close,
+};
+
+static void free_pg_vec(unsigned long *pg_vec, unsigned order, unsigned len)
+{
+ int i;
+
+ for (i=0; i<len; i++) {
+ if (pg_vec[i]) {
+ struct page *page, *pend;
+
+ pend = virt_to_page(pg_vec[i] + (PAGE_SIZE << order) - 1);
+ for (page = virt_to_page(pg_vec[i]); page <= pend; page++)
+ ClearPageReserved(page);
+ free_pages(pg_vec[i], order);
+ }
+ }
+ kfree(pg_vec);
+}
+
+
+static int packet_set_ring(struct sock *sk, struct tpacket_req *req, int closing)
+{
+ unsigned long *pg_vec = NULL;
+ struct packet_opt *po = sk->protinfo.af_packet;
+ int order = 0;
+ int err = 0;
+
+ if (req->tp_block_nr) {
+ int i, l;
+
+ /* Sanity tests and some calculations */
+
+ if (po->pg_vec)
+ return -EBUSY;
+
+ if ((int)req->tp_block_size <= 0)
+ return -EINVAL;
+ if (req->tp_block_size&(PAGE_SIZE-1))
+ return -EINVAL;
+ if (req->tp_frame_size < TPACKET_HDRLEN)
+ return -EINVAL;
+ if (req->tp_frame_size&(TPACKET_ALIGNMENT-1))
+ return -EINVAL;
+
+ po->frames_per_block = req->tp_block_size/req->tp_frame_size;
+ if (po->frames_per_block <= 0)
+ return -EINVAL;
+ if (po->frames_per_block*req->tp_block_nr != req->tp_frame_nr)
+ return -EINVAL;
+ /* OK! */
+
+ /* Allocate page vector */
+ while ((PAGE_SIZE<<order) < req->tp_block_size)
+ order++;
+
+ err = -ENOMEM;
+
+ pg_vec = kmalloc(req->tp_block_nr*sizeof(unsigned long*), GFP_KERNEL);
+ if (pg_vec == NULL)
+ goto out;
+ memset(pg_vec, 0, req->tp_block_nr*sizeof(unsigned long*));
+
+ for (i=0; i<req->tp_block_nr; i++) {
+ struct page *page, *pend;
+ pg_vec[i] = __get_free_pages(GFP_KERNEL, order);
+ if (!pg_vec[i])
+ goto out_free_pgvec;
+ memset((void *)(pg_vec[i]), 0, PAGE_SIZE << order);
+ pend = virt_to_page(pg_vec[i] + (PAGE_SIZE << order) - 1);
+ for (page = virt_to_page(pg_vec[i]); page <= pend; page++)
+ SetPageReserved(page);
+ }
+ /* Page vector is allocated */
+
+ l = 0;
+ for (i=0; i<req->tp_block_nr; i++) {
+ unsigned long ptr = pg_vec[i];
+ struct tpacket_hdr *header;
+ int k;
+
+ for (k=0; k<po->frames_per_block; k++) {
+
+ header = (struct tpacket_hdr*)ptr;
+ header->tp_status = TP_STATUS_KERNEL;
+ ptr += req->tp_frame_size;
+ }
+ }
+ /* Done */
+ } else {
+ if (req->tp_frame_nr)
+ return -EINVAL;
+ }
+
+ lock_sock(sk);
+
+ /* Detach socket from network */
+ spin_lock(&po->bind_lock);
+ if (po->running)
+ dev_remove_pack(&po->prot_hook);
+ spin_unlock(&po->bind_lock);
+
+ err = -EBUSY;
+ if (closing || atomic_read(&po->mapped) == 0) {
+ err = 0;
+#define XC(a, b) ({ __typeof__ ((a)) __t; __t = (a); (a) = (b); __t; })
+
+ spin_lock_bh(&sk->receive_queue.lock);
+ pg_vec = XC(po->pg_vec, pg_vec);
+ po->frame_max = req->tp_frame_nr-1;
+ po->head = 0;
+ po->frame_size = req->tp_frame_size;
+ spin_unlock_bh(&sk->receive_queue.lock);
+
+ order = XC(po->pg_vec_order, order);
+ req->tp_block_nr = XC(po->pg_vec_len, req->tp_block_nr);
+
+ po->pg_vec_pages = req->tp_block_size/PAGE_SIZE;
+ po->prot_hook.func = po->pg_vec ? tpacket_rcv : packet_rcv;
+ skb_queue_purge(&sk->receive_queue);
+#undef XC
+ if (atomic_read(&po->mapped))
+ printk(KERN_DEBUG "packet_mmap: vma is busy: %d\n", atomic_read(&po->mapped));
+ }
+
+ spin_lock(&po->bind_lock);
+ if (po->running)
+ dev_add_pack(&po->prot_hook);
+ spin_unlock(&po->bind_lock);
+
+ release_sock(sk);
+
+out_free_pgvec:
+ if (pg_vec)
+ free_pg_vec(pg_vec, order, req->tp_block_nr);
+out:
+ return err;
+}
+
+static int packet_mmap(struct file *file, struct socket *sock, struct vm_area_struct *vma)
+{
+ struct sock *sk = sock->sk;
+ struct packet_opt *po = sk->protinfo.af_packet;
+ unsigned long size;
+ unsigned long start;
+ int err = -EINVAL;
+ int i;
+
+ if (vma->vm_pgoff)
+ return -EINVAL;
+
+ size = vma->vm_end - vma->vm_start;
+
+ lock_sock(sk);
+ if (po->pg_vec == NULL)
+ goto out;
+ if (size != po->pg_vec_len*po->pg_vec_pages*PAGE_SIZE)
+ goto out;
+
+ atomic_inc(&po->mapped);
+ start = vma->vm_start;
+ vma->vm_flags |= VM_IO;
+ err = -EAGAIN;
+ for (i=0; i<po->pg_vec_len; i++) {
+ if (remap_page_range(start, __pa(po->pg_vec[i]),
+ po->pg_vec_pages*PAGE_SIZE,
+ vma->vm_page_prot))
+ goto out;
+ start += po->pg_vec_pages*PAGE_SIZE;
+ }
+ vma->vm_ops = &packet_mmap_ops;
+ err = 0;
+
+out:
+ release_sock(sk);
+ return err;
+}
+#endif
+
+
+#ifdef CONFIG_SOCK_PACKET
+struct proto_ops packet_ops_spkt = {
+ family: PF_PACKET,
+
+ release: packet_release,
+ bind: packet_bind_spkt,
+ connect: sock_no_connect,
+ socketpair: sock_no_socketpair,
+ accept: sock_no_accept,
+ getname: packet_getname_spkt,
+ poll: datagram_poll,
+ ioctl: packet_ioctl,
+ listen: sock_no_listen,
+ shutdown: sock_no_shutdown,
+ setsockopt: sock_no_setsockopt,
+ getsockopt: sock_no_getsockopt,
+ sendmsg: packet_sendmsg_spkt,
+ recvmsg: packet_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+#endif
+
+struct proto_ops packet_ops = {
+ family: PF_PACKET,
+
+ release: packet_release,
+ bind: packet_bind,
+ connect: sock_no_connect,
+ socketpair: sock_no_socketpair,
+ accept: sock_no_accept,
+ getname: packet_getname,
+ poll: packet_poll,
+ ioctl: packet_ioctl,
+ listen: sock_no_listen,
+ shutdown: sock_no_shutdown,
+ setsockopt: packet_setsockopt,
+ getsockopt: packet_getsockopt,
+ sendmsg: packet_sendmsg,
+ recvmsg: packet_recvmsg,
+ mmap: packet_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+static struct net_proto_family packet_family_ops = {
+ family: PF_PACKET,
+ create: packet_create,
+};
+
+static struct notifier_block packet_netdev_notifier = {
+ notifier_call: packet_notifier,
+};
+
+#ifdef CONFIG_PROC_FS
+static int packet_read_proc(char *buffer, char **start, off_t offset,
+ int length, int *eof, void *data)
+{
+ off_t pos=0;
+ off_t begin=0;
+ int len=0;
+ struct sock *s;
+
+ len+= sprintf(buffer,"sk RefCnt Type Proto Iface R Rmem User Inode\n");
+
+ read_lock(&packet_sklist_lock);
+
+ for (s = packet_sklist; s; s = s->next) {
+ len+=sprintf(buffer+len,"%p %-6d %-4d %04x %-5d %1d %-6u %-6u %-6lu",
+ s,
+ atomic_read(&s->refcnt),
+ s->type,
+ ntohs(s->num),
+ s->protinfo.af_packet->ifindex,
+ s->protinfo.af_packet->running,
+ atomic_read(&s->rmem_alloc),
+ sock_i_uid(s),
+ sock_i_ino(s)
+ );
+
+ buffer[len++]='\n';
+
+ pos=begin+len;
+ if(pos<offset) {
+ len=0;
+ begin=pos;
+ }
+ if(pos>offset+length)
+ goto done;
+ }
+ *eof = 1;
+
+done:
+ read_unlock(&packet_sklist_lock);
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ if(len<0)
+ len=0;
+ return len;
+}
+#endif
+
+static void __exit packet_exit(void)
+{
+ remove_proc_entry("net/packet", 0);
+ unregister_netdevice_notifier(&packet_netdev_notifier);
+ sock_unregister(PF_PACKET);
+ return;
+}
+
+static int __init packet_init(void)
+{
+ sock_register(&packet_family_ops);
+ register_netdevice_notifier(&packet_netdev_notifier);
+#ifdef CONFIG_PROC_FS
+ create_proc_read_entry("net/packet", 0, 0, packet_read_proc, NULL);
+#endif
+ return 0;
+}
+
+module_init(packet_init);
+module_exit(packet_exit);
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/rose/Makefile b/uClinux-2.4.31-uc0/net/rose/Makefile
new file mode 100644
index 0000000..08da73a
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/rose/Makefile
@@ -0,0 +1,19 @@
+#
+# Makefile for the Linux Rose (X.25 PLP) layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := rose.o
+
+obj-y := af_rose.o rose_dev.o rose_in.o rose_link.o rose_loopback.o \
+ rose_out.o rose_route.o rose_subr.o rose_timer.o
+obj-m := $(O_TARGET)
+
+obj-$(CONFIG_SYSCTL) += sysctl_net_rose.o
+
+include $(TOPDIR)/Rules.make
+
diff --git a/uClinux-2.4.31-uc0/net/rose/af_rose.c b/uClinux-2.4.31-uc0/net/rose/af_rose.c
new file mode 100644
index 0000000..26c4910
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/rose/af_rose.c
@@ -0,0 +1,1531 @@
+/*
+ * ROSE release 003
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * ROSE 001 Jonathan(G4KLX) Cloned from af_netrom.c.
+ * Alan(GW4PTS) Hacked up for newer API stuff
+ * Terry (VK2KTJ) Added support for variable length
+ * address masks.
+ * ROSE 002 Jonathan(G4KLX) Changed hdrincl to qbitincl.
+ * Added random number facilities entry.
+ * Variable number of ROSE devices.
+ * ROSE 003 Jonathan(G4KLX) New timer architecture.
+ * Implemented idle timer.
+ * Added use count to neighbour.
+ * Tomi(OH2BNS) Fixed rose_getname().
+ * Arnaldo C. Melo s/suser/capable/ + micro cleanups
+ * Joroen (PE1RXQ) Use sock_orphan() on release.
+ *
+ * ROSE 0.63 Jean-Paul(F6FBB) Fixed wrong length of L3 packets
+ * Added CLEAR_REQUEST facilities
+ * ROSE 0.64 Jean-Paul(F6FBB) Fixed null pointer in rose_kill_by_device
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/stat.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <linux/fcntl.h>
+#include <linux/termios.h> /* For TIOCINQ/OUTQ */
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <net/rose.h>
+#include <linux/proc_fs.h>
+#include <net/ip.h>
+#include <net/arp.h>
+
+int rose_ndevs = 10;
+
+int sysctl_rose_restart_request_timeout = ROSE_DEFAULT_T0;
+int sysctl_rose_call_request_timeout = ROSE_DEFAULT_T1;
+int sysctl_rose_reset_request_timeout = ROSE_DEFAULT_T2;
+int sysctl_rose_clear_request_timeout = ROSE_DEFAULT_T3;
+int sysctl_rose_no_activity_timeout = ROSE_DEFAULT_IDLE;
+int sysctl_rose_ack_hold_back_timeout = ROSE_DEFAULT_HB;
+int sysctl_rose_routing_control = ROSE_DEFAULT_ROUTING;
+int sysctl_rose_link_fail_timeout = ROSE_DEFAULT_FAIL_TIMEOUT;
+int sysctl_rose_maximum_vcs = ROSE_DEFAULT_MAXVC;
+int sysctl_rose_window_size = ROSE_DEFAULT_WINDOW_SIZE;
+
+static struct sock *rose_list;
+
+static struct proto_ops rose_proto_ops;
+
+ax25_address rose_callsign;
+
+/*
+ * Convert a ROSE address into text.
+ */
+char *rose2asc(rose_address *addr)
+{
+ static char buffer[11];
+
+ if (addr->rose_addr[0] == 0x00 && addr->rose_addr[1] == 0x00 &&
+ addr->rose_addr[2] == 0x00 && addr->rose_addr[3] == 0x00 &&
+ addr->rose_addr[4] == 0x00) {
+ strcpy(buffer, "*");
+ } else {
+ sprintf(buffer, "%02X%02X%02X%02X%02X", addr->rose_addr[0] & 0xFF,
+ addr->rose_addr[1] & 0xFF,
+ addr->rose_addr[2] & 0xFF,
+ addr->rose_addr[3] & 0xFF,
+ addr->rose_addr[4] & 0xFF);
+ }
+
+ return buffer;
+}
+
+/*
+ * Compare two ROSE addresses, 0 == equal.
+ */
+int rosecmp(rose_address *addr1, rose_address *addr2)
+{
+ int i;
+
+ for (i = 0; i < 5; i++)
+ if (addr1->rose_addr[i] != addr2->rose_addr[i])
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Compare two ROSE addresses for only mask digits, 0 == equal.
+ */
+int rosecmpm(rose_address *addr1, rose_address *addr2, unsigned short mask)
+{
+ int i, j;
+
+ if (mask > 10)
+ return 1;
+
+ for (i = 0; i < mask; i++) {
+ j = i / 2;
+
+ if ((i % 2) != 0) {
+ if ((addr1->rose_addr[j] & 0x0F) != (addr2->rose_addr[j] & 0x0F))
+ return 1;
+ } else {
+ if ((addr1->rose_addr[j] & 0xF0) != (addr2->rose_addr[j] & 0xF0))
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void rose_free_sock(struct sock *sk)
+{
+ sk_free(sk);
+
+ MOD_DEC_USE_COUNT;
+}
+
+static struct sock *rose_alloc_sock(void)
+{
+ struct sock *sk;
+ rose_cb *rose;
+
+ if ((sk = sk_alloc(PF_ROSE, GFP_ATOMIC, 1)) == NULL)
+ return NULL;
+
+ if ((rose = kmalloc(sizeof(*rose), GFP_ATOMIC)) == NULL) {
+ sk_free(sk);
+ return NULL;
+ }
+
+ MOD_INC_USE_COUNT;
+
+ memset(rose, 0x00, sizeof(*rose));
+
+ sk->protinfo.rose = rose;
+ rose->sk = sk;
+
+ return sk;
+}
+
+/*
+ * Socket removal during an interrupt is now safe.
+ */
+static void rose_remove_socket(struct sock *sk)
+{
+ struct sock *s;
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+ if ((s = rose_list) == sk) {
+ rose_list = s->next;
+ restore_flags(flags);
+ return;
+ }
+
+ while (s != NULL && s->next != NULL) {
+ if (s->next == sk) {
+ s->next = sk->next;
+ restore_flags(flags);
+ return;
+ }
+
+ s = s->next;
+ }
+
+ restore_flags(flags);
+}
+
+/*
+ * Kill all bound sockets on a broken link layer connection to a
+ * particular neighbour.
+ */
+void rose_kill_by_neigh(struct rose_neigh *neigh)
+{
+ struct sock *s;
+
+ for (s = rose_list; s != NULL; s = s->next) {
+ if (s->protinfo.rose->neighbour == neigh) {
+ rose_disconnect(s, ENETUNREACH, ROSE_OUT_OF_ORDER, 0);
+ s->protinfo.rose->neighbour->use--;
+ s->protinfo.rose->neighbour = NULL;
+ }
+ }
+}
+
+/*
+ * Kill all bound sockets on a dropped device.
+ */
+static void rose_kill_by_device(struct net_device *dev)
+{
+ struct sock *s;
+
+ for (s = rose_list; s != NULL; s = s->next) {
+ if (s->protinfo.rose->device == dev) {
+ rose_disconnect(s, ENETUNREACH, ROSE_OUT_OF_ORDER, 0);
+ if (s->protinfo.rose->neighbour)
+ s->protinfo.rose->neighbour->use--;
+ s->protinfo.rose->device = NULL;
+ }
+ }
+}
+
+/*
+ * Handle device status changes.
+ */
+static int rose_device_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct net_device *dev = (struct net_device *)ptr;
+
+ if (event != NETDEV_DOWN)
+ return NOTIFY_DONE;
+
+ switch (dev->type) {
+ case ARPHRD_ROSE:
+ rose_kill_by_device(dev);
+ break;
+ case ARPHRD_AX25:
+ rose_link_device_down(dev);
+ rose_rt_device_down(dev);
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Add a socket to the bound sockets list.
+ */
+static void rose_insert_socket(struct sock *sk)
+{
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+ sk->next = rose_list;
+ rose_list = sk;
+
+ restore_flags(flags);
+}
+
+/*
+ * Find a socket that wants to accept the Call Request we just
+ * received.
+ */
+static struct sock *rose_find_listener(rose_address *addr, ax25_address *call)
+{
+ unsigned long flags;
+ struct sock *s;
+
+ save_flags(flags); cli();
+
+ for (s = rose_list; s != NULL; s = s->next) {
+ if (rosecmp(&s->protinfo.rose->source_addr, addr) == 0 && ax25cmp(&s->protinfo.rose->source_call, call) == 0 && s->protinfo.rose->source_ndigis == 0 && s->state == TCP_LISTEN) {
+ restore_flags(flags);
+ return s;
+ }
+ }
+
+ for (s = rose_list; s != NULL; s = s->next) {
+ if (rosecmp(&s->protinfo.rose->source_addr, addr) == 0 && ax25cmp(&s->protinfo.rose->source_call, &null_ax25_address) == 0 && s->state == TCP_LISTEN) {
+ restore_flags(flags);
+ return s;
+ }
+ }
+
+ restore_flags(flags);
+ return NULL;
+}
+
+/*
+ * Find a connected ROSE socket given my LCI and device.
+ */
+struct sock *rose_find_socket(unsigned int lci, struct rose_neigh *neigh)
+{
+ struct sock *s;
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+ for (s = rose_list; s != NULL; s = s->next) {
+ if (s->protinfo.rose->lci == lci && s->protinfo.rose->neighbour == neigh) {
+ restore_flags(flags);
+ return s;
+ }
+ }
+
+ restore_flags(flags);
+
+ return NULL;
+}
+
+/*
+ * Find a unique LCI for a given device.
+ */
+unsigned int rose_new_lci(struct rose_neigh *neigh)
+{
+ int lci;
+
+ if (neigh->dce_mode) {
+ for (lci = 1; lci <= sysctl_rose_maximum_vcs; lci++)
+ if (rose_find_socket(lci, neigh) == NULL && rose_route_free_lci(lci, neigh) == NULL)
+ return lci;
+ } else {
+ for (lci = sysctl_rose_maximum_vcs; lci > 0; lci--)
+ if (rose_find_socket(lci, neigh) == NULL && rose_route_free_lci(lci, neigh) == NULL)
+ return lci;
+ }
+
+ return 0;
+}
+
+/*
+ * Deferred destroy.
+ */
+void rose_destroy_socket(struct sock *);
+
+/*
+ * Handler for deferred kills.
+ */
+static void rose_destroy_timer(unsigned long data)
+{
+ rose_destroy_socket((struct sock *)data);
+}
+
+/*
+ * This is called from user mode and the timers. Thus it protects itself against
+ * interrupt users but doesn't worry about being called during work.
+ * Once it is removed from the queue no interrupt or bottom half will
+ * touch it and we are (fairly 8-) ) safe.
+ */
+void rose_destroy_socket(struct sock *sk) /* Not static as it's used by the timer */
+{
+ struct sk_buff *skb;
+ unsigned long flags;
+
+ save_flags(flags); cli();
+
+ rose_stop_heartbeat(sk);
+ rose_stop_idletimer(sk);
+ rose_stop_timer(sk);
+
+ rose_remove_socket(sk);
+ rose_clear_queues(sk); /* Flush the queues */
+
+ while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
+ if (skb->sk != sk) { /* A pending connection */
+ skb->sk->dead = 1; /* Queue the unaccepted socket for death */
+ rose_start_heartbeat(skb->sk);
+ skb->sk->protinfo.rose->state = ROSE_STATE_0;
+ }
+
+ kfree_skb(skb);
+ }
+
+ if (atomic_read(&sk->wmem_alloc) != 0 || atomic_read(&sk->rmem_alloc) != 0) {
+ /* Defer: outstanding buffers */
+ init_timer(&sk->timer);
+ sk->timer.expires = jiffies + 10 * HZ;
+ sk->timer.function = rose_destroy_timer;
+ sk->timer.data = (unsigned long)sk;
+ add_timer(&sk->timer);
+ } else {
+ rose_free_sock(sk);
+ }
+
+ restore_flags(flags);
+}
+
+/*
+ * Handling for system calls applied via the various interfaces to a
+ * ROSE socket object.
+ */
+
+static int rose_setsockopt(struct socket *sock, int level, int optname,
+ char *optval, int optlen)
+{
+ struct sock *sk = sock->sk;
+ int opt;
+
+ if (level != SOL_ROSE)
+ return -ENOPROTOOPT;
+
+ if (optlen < sizeof(int))
+ return -EINVAL;
+
+ if (get_user(opt, (int *)optval))
+ return -EFAULT;
+
+ switch (optname) {
+ case ROSE_DEFER:
+ sk->protinfo.rose->defer = opt ? 1 : 0;
+ return 0;
+
+ case ROSE_T1:
+ if (opt < 1)
+ return -EINVAL;
+ sk->protinfo.rose->t1 = opt * HZ;
+ return 0;
+
+ case ROSE_T2:
+ if (opt < 1)
+ return -EINVAL;
+ sk->protinfo.rose->t2 = opt * HZ;
+ return 0;
+
+ case ROSE_T3:
+ if (opt < 1)
+ return -EINVAL;
+ sk->protinfo.rose->t3 = opt * HZ;
+ return 0;
+
+ case ROSE_HOLDBACK:
+ if (opt < 1)
+ return -EINVAL;
+ sk->protinfo.rose->hb = opt * HZ;
+ return 0;
+
+ case ROSE_IDLE:
+ if (opt < 0)
+ return -EINVAL;
+ sk->protinfo.rose->idle = opt * 60 * HZ;
+ return 0;
+
+ case ROSE_QBITINCL:
+ sk->protinfo.rose->qbitincl = opt ? 1 : 0;
+ return 0;
+
+ default:
+ return -ENOPROTOOPT;
+ }
+}
+
+static int rose_getsockopt(struct socket *sock, int level, int optname,
+ char *optval, int *optlen)
+{
+ struct sock *sk = sock->sk;
+ int val = 0;
+ int len;
+
+ if (level != SOL_ROSE)
+ return -ENOPROTOOPT;
+
+ if (get_user(len, optlen))
+ return -EFAULT;
+
+ if (len < 0)
+ return -EINVAL;
+
+ switch (optname) {
+ case ROSE_DEFER:
+ val = sk->protinfo.rose->defer;
+ break;
+
+ case ROSE_T1:
+ val = sk->protinfo.rose->t1 / HZ;
+ break;
+
+ case ROSE_T2:
+ val = sk->protinfo.rose->t2 / HZ;
+ break;
+
+ case ROSE_T3:
+ val = sk->protinfo.rose->t3 / HZ;
+ break;
+
+ case ROSE_HOLDBACK:
+ val = sk->protinfo.rose->hb / HZ;
+ break;
+
+ case ROSE_IDLE:
+ val = sk->protinfo.rose->idle / (60 * HZ);
+ break;
+
+ case ROSE_QBITINCL:
+ val = sk->protinfo.rose->qbitincl;
+ break;
+
+ default:
+ return -ENOPROTOOPT;
+ }
+
+ len = min_t(unsigned int, len, sizeof(int));
+
+ if (put_user(len, optlen))
+ return -EFAULT;
+
+ return copy_to_user(optval, &val, len) ? -EFAULT : 0;
+}
+
+static int rose_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk->state != TCP_LISTEN) {
+ sk->protinfo.rose->dest_ndigis = 0;
+ memset(&sk->protinfo.rose->dest_addr, '\0', ROSE_ADDR_LEN);
+ memset(&sk->protinfo.rose->dest_call, '\0', AX25_ADDR_LEN);
+ memset(sk->protinfo.rose->dest_digis, '\0', AX25_ADDR_LEN*ROSE_MAX_DIGIS);
+ sk->max_ack_backlog = backlog;
+ sk->state = TCP_LISTEN;
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static int rose_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+ rose_cb *rose;
+
+ if (sock->type != SOCK_SEQPACKET || protocol != 0)
+ return -ESOCKTNOSUPPORT;
+
+ if ((sk = rose_alloc_sock()) == NULL)
+ return -ENOMEM;
+
+ rose = sk->protinfo.rose;
+
+ sock_init_data(sock, sk);
+
+ skb_queue_head_init(&rose->ack_queue);
+#ifdef M_BIT
+ skb_queue_head_init(&rose->frag_queue);
+ rose->fraglen = 0;
+#endif
+
+ sock->ops = &rose_proto_ops;
+ sk->protocol = protocol;
+
+ init_timer(&rose->timer);
+ init_timer(&rose->idletimer);
+
+ rose->t1 = sysctl_rose_call_request_timeout;
+ rose->t2 = sysctl_rose_reset_request_timeout;
+ rose->t3 = sysctl_rose_clear_request_timeout;
+ rose->hb = sysctl_rose_ack_hold_back_timeout;
+ rose->idle = sysctl_rose_no_activity_timeout;
+
+ rose->state = ROSE_STATE_0;
+
+ return 0;
+}
+
+static struct sock *rose_make_new(struct sock *osk)
+{
+ struct sock *sk;
+ rose_cb *rose;
+
+ if (osk->type != SOCK_SEQPACKET)
+ return NULL;
+
+ if ((sk = rose_alloc_sock()) == NULL)
+ return NULL;
+
+ rose = sk->protinfo.rose;
+
+ sock_init_data(NULL, sk);
+
+ skb_queue_head_init(&rose->ack_queue);
+#ifdef M_BIT
+ skb_queue_head_init(&rose->frag_queue);
+ rose->fraglen = 0;
+#endif
+
+ sk->type = osk->type;
+ sk->socket = osk->socket;
+ sk->priority = osk->priority;
+ sk->protocol = osk->protocol;
+ sk->rcvbuf = osk->rcvbuf;
+ sk->sndbuf = osk->sndbuf;
+ sk->debug = osk->debug;
+ sk->state = TCP_ESTABLISHED;
+ sk->sleep = osk->sleep;
+ sk->zapped = osk->zapped;
+
+ init_timer(&rose->timer);
+ init_timer(&rose->idletimer);
+
+ rose->t1 = osk->protinfo.rose->t1;
+ rose->t2 = osk->protinfo.rose->t2;
+ rose->t3 = osk->protinfo.rose->t3;
+ rose->hb = osk->protinfo.rose->hb;
+ rose->idle = osk->protinfo.rose->idle;
+
+ rose->defer = osk->protinfo.rose->defer;
+ rose->device = osk->protinfo.rose->device;
+ rose->qbitincl = osk->protinfo.rose->qbitincl;
+
+ return sk;
+}
+
+static int rose_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk == NULL) return 0;
+
+ switch (sk->protinfo.rose->state) {
+
+ case ROSE_STATE_0:
+ rose_disconnect(sk, 0, -1, -1);
+ rose_destroy_socket(sk);
+ break;
+
+ case ROSE_STATE_2:
+ sk->protinfo.rose->neighbour->use--;
+ rose_disconnect(sk, 0, -1, -1);
+ rose_destroy_socket(sk);
+ break;
+
+ case ROSE_STATE_1:
+ case ROSE_STATE_3:
+ case ROSE_STATE_4:
+ case ROSE_STATE_5:
+ rose_clear_queues(sk);
+ rose_stop_idletimer(sk);
+ rose_write_internal(sk, ROSE_CLEAR_REQUEST);
+ rose_start_t3timer(sk);
+ sk->protinfo.rose->state = ROSE_STATE_2;
+ sk->state = TCP_CLOSE;
+ sk->shutdown |= SEND_SHUTDOWN;
+ sk->state_change(sk);
+ sock_orphan(sk);
+ sk->destroy = 1;
+ break;
+
+ default:
+ sk->socket = NULL;
+ break;
+ }
+
+ sock->sk = NULL;
+
+ return 0;
+}
+
+static int rose_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_rose *addr = (struct sockaddr_rose *)uaddr;
+ struct net_device *dev;
+ ax25_address *user, *source;
+ int n;
+
+ if (sk->zapped == 0)
+ return -EINVAL;
+
+ if (addr_len != sizeof(struct sockaddr_rose) && addr_len != sizeof(struct full_sockaddr_rose))
+ return -EINVAL;
+
+ if (addr->srose_family != AF_ROSE)
+ return -EINVAL;
+
+ if (addr_len == sizeof(struct sockaddr_rose) && addr->srose_ndigis > 1)
+ return -EINVAL;
+
+ if (addr->srose_ndigis > ROSE_MAX_DIGIS)
+ return -EINVAL;
+
+ if ((dev = rose_dev_get(&addr->srose_addr)) == NULL) {
+ SOCK_DEBUG(sk, "ROSE: bind failed: invalid address\n");
+ return -EADDRNOTAVAIL;
+ }
+
+ source = &addr->srose_call;
+
+ if ((user = ax25_findbyuid(current->euid)) == NULL) {
+ if (ax25_uid_policy && !capable(CAP_NET_BIND_SERVICE))
+ return -EACCES;
+ user = source;
+ }
+
+ sk->protinfo.rose->source_addr = addr->srose_addr;
+ sk->protinfo.rose->source_call = *user;
+ sk->protinfo.rose->device = dev;
+ sk->protinfo.rose->source_ndigis = addr->srose_ndigis;
+
+ if (addr_len == sizeof(struct full_sockaddr_rose)) {
+ struct full_sockaddr_rose *full_addr = (struct full_sockaddr_rose *)uaddr;
+ for (n = 0 ; n < addr->srose_ndigis ; n++)
+ sk->protinfo.rose->source_digis[n] = full_addr->srose_digis[n];
+ } else {
+ if (sk->protinfo.rose->source_ndigis == 1) {
+ sk->protinfo.rose->source_digis[0] = addr->srose_digi;
+ }
+ }
+
+ rose_insert_socket(sk);
+
+ sk->zapped = 0;
+ SOCK_DEBUG(sk, "ROSE: socket is bound\n");
+ return 0;
+}
+
+static int rose_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_rose *addr = (struct sockaddr_rose *)uaddr;
+ unsigned char cause, diagnostic;
+ ax25_address *user;
+ struct net_device *dev;
+ int n;
+
+ if (sk->state == TCP_ESTABLISHED && sock->state == SS_CONNECTING) {
+ sock->state = SS_CONNECTED;
+ return 0; /* Connect completed during a ERESTARTSYS event */
+ }
+
+ if (sk->state == TCP_CLOSE && sock->state == SS_CONNECTING) {
+ sock->state = SS_UNCONNECTED;
+ return -ECONNREFUSED;
+ }
+
+ if (sk->state == TCP_ESTABLISHED)
+ return -EISCONN; /* No reconnect on a seqpacket socket */
+
+ sk->state = TCP_CLOSE;
+ sock->state = SS_UNCONNECTED;
+
+ if (addr_len != sizeof(struct sockaddr_rose) && addr_len != sizeof(struct full_sockaddr_rose))
+ return -EINVAL;
+
+ if (addr->srose_family != AF_ROSE)
+ return -EINVAL;
+
+ if (addr_len == sizeof(struct sockaddr_rose) && addr->srose_ndigis > 1)
+ return -EINVAL;
+
+ if (addr->srose_ndigis > ROSE_MAX_DIGIS)
+ return -EINVAL;
+
+ /* Source + Destination digis should not exceed ROSE_MAX_DIGIS */
+ if ((sk->protinfo.rose->source_ndigis + addr->srose_ndigis) > ROSE_MAX_DIGIS)
+ return -EINVAL;
+
+ if ((sk->protinfo.rose->neighbour = rose_get_neigh(&addr->srose_addr, &cause, &diagnostic)) == NULL)
+ return -ENETUNREACH;
+
+ if ((sk->protinfo.rose->lci = rose_new_lci(sk->protinfo.rose->neighbour)) == 0)
+ return -ENETUNREACH;
+
+ if (sk->zapped) { /* Must bind first - autobinding in this may or may not work */
+ sk->zapped = 0;
+
+ if ((dev = rose_dev_first()) == NULL)
+ return -ENETUNREACH;
+
+ if ((user = ax25_findbyuid(current->euid)) == NULL)
+ return -EINVAL;
+
+ memcpy(&sk->protinfo.rose->source_addr, dev->dev_addr, ROSE_ADDR_LEN);
+ sk->protinfo.rose->source_call = *user;
+ sk->protinfo.rose->device = dev;
+
+ rose_insert_socket(sk); /* Finish the bind */
+ }
+
+ sk->protinfo.rose->dest_addr = addr->srose_addr;
+ sk->protinfo.rose->dest_call = addr->srose_call;
+ sk->protinfo.rose->rand = ((int)sk->protinfo.rose & 0xFFFF) + sk->protinfo.rose->lci;
+ sk->protinfo.rose->dest_ndigis = addr->srose_ndigis;
+
+ if (addr_len == sizeof(struct full_sockaddr_rose)) {
+ struct full_sockaddr_rose *full_addr = (struct full_sockaddr_rose *)uaddr;
+ for (n = 0 ; n < addr->srose_ndigis ; n++)
+ sk->protinfo.rose->dest_digis[n] = full_addr->srose_digis[n];
+ } else {
+ if (sk->protinfo.rose->dest_ndigis == 1) {
+ sk->protinfo.rose->dest_digis[0] = addr->srose_digi;
+ }
+ }
+
+ /* Move to connecting socket, start sending Connect Requests */
+ sock->state = SS_CONNECTING;
+ sk->state = TCP_SYN_SENT;
+
+ sk->protinfo.rose->state = ROSE_STATE_1;
+
+ sk->protinfo.rose->neighbour->use++;
+
+ rose_write_internal(sk, ROSE_CALL_REQUEST);
+ rose_start_heartbeat(sk);
+ rose_start_t1timer(sk);
+
+ /* Now the loop */
+ if (sk->state != TCP_ESTABLISHED && (flags & O_NONBLOCK))
+ return -EINPROGRESS;
+
+ cli(); /* To avoid races on the sleep */
+
+ /*
+ * A Connect Ack with Choke or timeout or failed routing will go to closed.
+ */
+ while (sk->state == TCP_SYN_SENT) {
+ interruptible_sleep_on(sk->sleep);
+ if (signal_pending(current)) {
+ sti();
+ return -ERESTARTSYS;
+ }
+ }
+
+ if (sk->state != TCP_ESTABLISHED) {
+ sti();
+ sock->state = SS_UNCONNECTED;
+ return sock_error(sk); /* Always set at this point */
+ }
+
+ sock->state = SS_CONNECTED;
+
+ sti();
+
+ return 0;
+}
+
+static int rose_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+ struct sock *sk;
+ struct sock *newsk;
+ struct sk_buff *skb;
+
+ if ((sk = sock->sk) == NULL)
+ return -EINVAL;
+
+ if (sk->type != SOCK_SEQPACKET)
+ return -EOPNOTSUPP;
+
+ if (sk->state != TCP_LISTEN)
+ return -EINVAL;
+
+ /*
+ * The write queue this time is holding sockets ready to use
+ * hooked into the SABM we saved
+ */
+ do {
+ cli();
+ if ((skb = skb_dequeue(&sk->receive_queue)) == NULL) {
+ if (flags & O_NONBLOCK) {
+ sti();
+ return -EWOULDBLOCK;
+ }
+ interruptible_sleep_on(sk->sleep);
+ if (signal_pending(current)) {
+ sti();
+ return -ERESTARTSYS;
+ }
+ }
+ } while (skb == NULL);
+
+ newsk = skb->sk;
+ newsk->pair = NULL;
+ newsk->socket = newsock;
+ newsk->sleep = &newsock->wait;
+ sti();
+
+ /* Now attach up the new socket */
+ skb->sk = NULL;
+ kfree_skb(skb);
+ sk->ack_backlog--;
+ newsock->sk = newsk;
+
+ return 0;
+}
+
+static int rose_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *uaddr_len, int peer)
+{
+ struct full_sockaddr_rose *srose = (struct full_sockaddr_rose *)uaddr;
+ struct sock *sk = sock->sk;
+ int n;
+
+ if (peer != 0) {
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+ srose->srose_family = AF_ROSE;
+ srose->srose_addr = sk->protinfo.rose->dest_addr;
+ srose->srose_call = sk->protinfo.rose->dest_call;
+ srose->srose_ndigis = sk->protinfo.rose->dest_ndigis;
+ for (n = 0 ; n < sk->protinfo.rose->dest_ndigis ; n++)
+ srose->srose_digis[n] = sk->protinfo.rose->dest_digis[n];
+ } else {
+ srose->srose_family = AF_ROSE;
+ srose->srose_addr = sk->protinfo.rose->source_addr;
+ srose->srose_call = sk->protinfo.rose->source_call;
+ srose->srose_ndigis = sk->protinfo.rose->source_ndigis;
+ for (n = 0 ; n < sk->protinfo.rose->source_ndigis ; n++)
+ srose->srose_digis[n] = sk->protinfo.rose->source_digis[n];
+ }
+
+ *uaddr_len = sizeof(struct full_sockaddr_rose);
+ return 0;
+}
+
+int rose_rx_call_request(struct sk_buff *skb, struct net_device *dev, struct rose_neigh *neigh, unsigned int lci)
+{
+ struct sock *sk;
+ struct sock *make;
+ struct rose_facilities_struct facilities;
+ int n, len;
+
+ skb->sk = NULL; /* Initially we don't know who it's for */
+
+ /*
+ * skb->data points to the rose frame start
+ */
+ memset(&facilities, 0x00, sizeof(struct rose_facilities_struct));
+
+ len = (((skb->data[3] >> 4) & 0x0F) + 1) / 2;
+ len += (((skb->data[3] >> 0) & 0x0F) + 1) / 2;
+ if (!rose_parse_facilities(skb->data + len + 4, &facilities)) {
+ rose_transmit_clear_request(neigh, lci, ROSE_INVALID_FACILITY, 76);
+ return 0;
+ }
+
+ sk = rose_find_listener(&facilities.source_addr, &facilities.source_call);
+
+ /*
+ * We can't accept the Call Request.
+ */
+ if (sk == NULL || sk->ack_backlog == sk->max_ack_backlog || (make = rose_make_new(sk)) == NULL) {
+ rose_transmit_clear_request(neigh, lci, ROSE_NETWORK_CONGESTION, 120);
+ return 0;
+ }
+
+ skb->sk = make;
+ make->state = TCP_ESTABLISHED;
+
+ make->protinfo.rose->lci = lci;
+ make->protinfo.rose->dest_addr = facilities.dest_addr;
+ make->protinfo.rose->dest_call = facilities.dest_call;
+ make->protinfo.rose->dest_ndigis = facilities.dest_ndigis;
+ for (n = 0 ; n < facilities.dest_ndigis ; n++)
+ make->protinfo.rose->dest_digis[n] = facilities.dest_digis[n];
+ make->protinfo.rose->source_addr = facilities.source_addr;
+ make->protinfo.rose->source_call = facilities.source_call;
+ make->protinfo.rose->source_ndigis = facilities.source_ndigis;
+ for (n = 0 ; n < facilities.source_ndigis ; n++)
+ make->protinfo.rose->source_digis[n]= facilities.source_digis[n];
+ make->protinfo.rose->neighbour = neigh;
+ make->protinfo.rose->device = dev;
+ make->protinfo.rose->facilities = facilities;
+
+ make->protinfo.rose->neighbour->use++;
+
+ if (sk->protinfo.rose->defer) {
+ make->protinfo.rose->state = ROSE_STATE_5;
+ } else {
+ rose_write_internal(make, ROSE_CALL_ACCEPTED);
+ make->protinfo.rose->state = ROSE_STATE_3;
+ rose_start_idletimer(make);
+ }
+
+ make->protinfo.rose->condition = 0x00;
+ make->protinfo.rose->vs = 0;
+ make->protinfo.rose->va = 0;
+ make->protinfo.rose->vr = 0;
+ make->protinfo.rose->vl = 0;
+ sk->ack_backlog++;
+ make->pair = sk;
+
+ rose_insert_socket(make);
+
+ skb_queue_head(&sk->receive_queue, skb);
+
+ rose_start_heartbeat(make);
+
+ if (!sk->dead)
+ sk->data_ready(sk, skb->len);
+
+ return 1;
+}
+
+static int rose_sendmsg(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_rose *usrose = (struct sockaddr_rose *)msg->msg_name;
+ int err;
+ struct full_sockaddr_rose srose;
+ struct sk_buff *skb;
+ unsigned char *asmptr;
+ int n, size, qbit = 0;
+
+ if (msg->msg_flags & ~(MSG_DONTWAIT|MSG_EOR))
+ return -EINVAL;
+
+ if (sk->zapped)
+ return -EADDRNOTAVAIL;
+
+ if (sk->shutdown & SEND_SHUTDOWN) {
+ send_sig(SIGPIPE, current, 0);
+ return -EPIPE;
+ }
+
+ if (sk->protinfo.rose->neighbour == NULL || sk->protinfo.rose->device == NULL)
+ return -ENETUNREACH;
+
+ if (usrose != NULL) {
+ if (msg->msg_namelen != sizeof(struct sockaddr_rose) && msg->msg_namelen != sizeof(struct full_sockaddr_rose))
+ return -EINVAL;
+ memset(&srose, 0, sizeof(struct full_sockaddr_rose));
+ memcpy(&srose, usrose, msg->msg_namelen);
+ if (rosecmp(&sk->protinfo.rose->dest_addr, &srose.srose_addr) != 0 ||
+ ax25cmp(&sk->protinfo.rose->dest_call, &srose.srose_call) != 0)
+ return -EISCONN;
+ if (srose.srose_ndigis != sk->protinfo.rose->dest_ndigis)
+ return -EISCONN;
+ if (srose.srose_ndigis == sk->protinfo.rose->dest_ndigis) {
+ for (n = 0 ; n < srose.srose_ndigis ; n++)
+ if (ax25cmp(&sk->protinfo.rose->dest_digis[n], &srose.srose_digis[n]) != 0)
+ return -EISCONN;
+ }
+ if (srose.srose_family != AF_ROSE)
+ return -EINVAL;
+ } else {
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+
+ srose.srose_family = AF_ROSE;
+ srose.srose_addr = sk->protinfo.rose->dest_addr;
+ srose.srose_call = sk->protinfo.rose->dest_call;
+ srose.srose_ndigis = sk->protinfo.rose->dest_ndigis;
+ for (n = 0 ; n < sk->protinfo.rose->dest_ndigis ; n++)
+ srose.srose_digis[n] = sk->protinfo.rose->dest_digis[n];
+ }
+
+ SOCK_DEBUG(sk, "ROSE: sendto: Addresses built.\n");
+
+ /* Build a packet */
+ SOCK_DEBUG(sk, "ROSE: sendto: building packet.\n");
+ size = len + AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + ROSE_MIN_LEN;
+
+ if ((skb = sock_alloc_send_skb(sk, size, msg->msg_flags & MSG_DONTWAIT, &err)) == NULL)
+ return err;
+
+ skb_reserve(skb, AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + ROSE_MIN_LEN);
+
+ /*
+ * Put the data on the end
+ */
+ SOCK_DEBUG(sk, "ROSE: Appending user data\n");
+
+ asmptr = skb->h.raw = skb_put(skb, len);
+
+ memcpy_fromiovec(asmptr, msg->msg_iov, len);
+
+ /*
+ * If the Q BIT Include socket option is in force, the first
+ * byte of the user data is the logical value of the Q Bit.
+ */
+ if (sk->protinfo.rose->qbitincl) {
+ qbit = skb->data[0];
+ skb_pull(skb, 1);
+ }
+
+ /*
+ * Push down the ROSE header
+ */
+ asmptr = skb_push(skb, ROSE_MIN_LEN);
+
+ SOCK_DEBUG(sk, "ROSE: Building Network Header.\n");
+
+ /* Build a ROSE Network header */
+ asmptr[0] = ((sk->protinfo.rose->lci >> 8) & 0x0F) | ROSE_GFI;
+ asmptr[1] = (sk->protinfo.rose->lci >> 0) & 0xFF;
+ asmptr[2] = ROSE_DATA;
+
+ if (qbit)
+ asmptr[0] |= ROSE_Q_BIT;
+
+ SOCK_DEBUG(sk, "ROSE: Built header.\n");
+
+ SOCK_DEBUG(sk, "ROSE: Transmitting buffer\n");
+
+ if (sk->state != TCP_ESTABLISHED) {
+ kfree_skb(skb);
+ return -ENOTCONN;
+ }
+
+#ifdef M_BIT
+#define ROSE_PACLEN (256-ROSE_MIN_LEN)
+ if (skb->len - ROSE_MIN_LEN > ROSE_PACLEN) {
+ unsigned char header[ROSE_MIN_LEN];
+ struct sk_buff *skbn;
+ int frontlen;
+ int lg;
+
+ /* Save a copy of the Header */
+ memcpy(header, skb->data, ROSE_MIN_LEN);
+ skb_pull(skb, ROSE_MIN_LEN);
+
+ frontlen = skb_headroom(skb);
+
+ while (skb->len > 0) {
+ if ((skbn = sock_alloc_send_skb(sk, frontlen + ROSE_PACLEN, 0, &err)) == NULL)
+ return err;
+
+ skbn->sk = sk;
+ skbn->free = 1;
+ skbn->arp = 1;
+
+ skb_reserve(skbn, frontlen);
+
+ lg = (ROSE_PACLEN > skb->len) ? skb->len : ROSE_PACLEN;
+
+ /* Copy the user data */
+ memcpy(skb_put(skbn, lg), skb->data, lg);
+ skb_pull(skb, lg);
+
+ /* Duplicate the Header */
+ skb_push(skbn, ROSE_MIN_LEN);
+ memcpy(skbn->data, header, ROSE_MIN_LEN);
+
+ if (skb->len > 0)
+ skbn->data[2] |= M_BIT;
+
+ skb_queue_tail(&sk->write_queue, skbn); /* Throw it on the queue */
+ }
+
+ skb->free = 1;
+ kfree_skb(skb, FREE_WRITE);
+ } else {
+ skb_queue_tail(&sk->write_queue, skb); /* Throw it on the queue */
+ }
+#else
+ skb_queue_tail(&sk->write_queue, skb); /* Shove it onto the queue */
+#endif
+
+ rose_kick(sk);
+
+ return len;
+}
+
+
+static int rose_recvmsg(struct socket *sock, struct msghdr *msg, int size,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_rose *srose = (struct sockaddr_rose *)msg->msg_name;
+ int copied, qbit;
+ unsigned char *asmptr;
+ struct sk_buff *skb;
+ int n, er;
+
+ /*
+ * This works for seqpacket too. The receiver has ordered the queue for
+ * us! We do one quick check first though
+ */
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+
+ /* Now we can treat all alike */
+ if ((skb = skb_recv_datagram(sk, flags & ~MSG_DONTWAIT, flags & MSG_DONTWAIT, &er)) == NULL)
+ return er;
+
+ qbit = (skb->data[0] & ROSE_Q_BIT) == ROSE_Q_BIT;
+
+ skb_pull(skb, ROSE_MIN_LEN);
+
+ if (sk->protinfo.rose->qbitincl) {
+ asmptr = skb_push(skb, 1);
+ *asmptr = qbit;
+ }
+
+ skb->h.raw = skb->data;
+ copied = skb->len;
+
+ if (copied > size) {
+ copied = size;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+
+ skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+
+ if (srose != NULL) {
+ srose->srose_family = AF_ROSE;
+ srose->srose_addr = sk->protinfo.rose->dest_addr;
+ srose->srose_call = sk->protinfo.rose->dest_call;
+ srose->srose_ndigis = sk->protinfo.rose->dest_ndigis;
+ if (msg->msg_namelen >= sizeof(struct full_sockaddr_rose)) {
+ struct full_sockaddr_rose *full_srose = (struct full_sockaddr_rose *)msg->msg_name;
+ for (n = 0 ; n < sk->protinfo.rose->dest_ndigis ; n++)
+ full_srose->srose_digis[n] = sk->protinfo.rose->dest_digis[n];
+ msg->msg_namelen = sizeof(struct full_sockaddr_rose);
+ } else {
+ if (sk->protinfo.rose->dest_ndigis >= 1) {
+ srose->srose_ndigis = 1;
+ srose->srose_digi = sk->protinfo.rose->dest_digis[0];
+ }
+ msg->msg_namelen = sizeof(struct sockaddr_rose);
+ }
+ }
+
+ skb_free_datagram(sk, skb);
+
+ return copied;
+}
+
+
+static int rose_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+
+ switch (cmd) {
+ case TIOCOUTQ: {
+ long amount;
+ amount = sk->sndbuf - atomic_read(&sk->wmem_alloc);
+ if (amount < 0)
+ amount = 0;
+ return put_user(amount, (unsigned int *)arg);
+ }
+
+ case TIOCINQ: {
+ struct sk_buff *skb;
+ long amount = 0L;
+ /* These two are safe on a single CPU system as only user tasks fiddle here */
+ if ((skb = skb_peek(&sk->receive_queue)) != NULL)
+ amount = skb->len;
+ return put_user(amount, (unsigned int *)arg);
+ }
+
+ case SIOCGSTAMP:
+ if (sk != NULL) {
+ if (sk->stamp.tv_sec == 0)
+ return -ENOENT;
+ return copy_to_user((void *)arg, &sk->stamp, sizeof(struct timeval)) ? -EFAULT : 0;
+ }
+ return -EINVAL;
+
+ case SIOCGIFADDR:
+ case SIOCSIFADDR:
+ case SIOCGIFDSTADDR:
+ case SIOCSIFDSTADDR:
+ case SIOCGIFBRDADDR:
+ case SIOCSIFBRDADDR:
+ case SIOCGIFNETMASK:
+ case SIOCSIFNETMASK:
+ case SIOCGIFMETRIC:
+ case SIOCSIFMETRIC:
+ return -EINVAL;
+
+ case SIOCADDRT:
+ case SIOCDELRT:
+ case SIOCRSCLRRT:
+ if (!capable(CAP_NET_ADMIN)) return -EPERM;
+ return rose_rt_ioctl(cmd, (void *)arg);
+
+ case SIOCRSGCAUSE: {
+ struct rose_cause_struct rose_cause;
+ rose_cause.cause = sk->protinfo.rose->cause;
+ rose_cause.diagnostic = sk->protinfo.rose->diagnostic;
+ return copy_to_user((void *)arg, &rose_cause, sizeof(struct rose_cause_struct)) ? -EFAULT : 0;
+ }
+
+ case SIOCRSSCAUSE: {
+ struct rose_cause_struct rose_cause;
+ if (copy_from_user(&rose_cause, (void *)arg, sizeof(struct rose_cause_struct)))
+ return -EFAULT;
+ sk->protinfo.rose->cause = rose_cause.cause;
+ sk->protinfo.rose->diagnostic = rose_cause.diagnostic;
+ return 0;
+ }
+
+ case SIOCRSSL2CALL:
+ if (!capable(CAP_NET_ADMIN)) return -EPERM;
+ if (ax25cmp(&rose_callsign, &null_ax25_address) != 0)
+ ax25_listen_release(&rose_callsign, NULL);
+ if (copy_from_user(&rose_callsign, (void *)arg, sizeof(ax25_address)))
+ return -EFAULT;
+ if (ax25cmp(&rose_callsign, &null_ax25_address) != 0)
+ ax25_listen_register(&rose_callsign, NULL);
+ return 0;
+
+ case SIOCRSGL2CALL:
+ return copy_to_user((void *)arg, &rose_callsign, sizeof(ax25_address)) ? -EFAULT : 0;
+
+ case SIOCRSACCEPT:
+ if (sk->protinfo.rose->state == ROSE_STATE_5) {
+ rose_write_internal(sk, ROSE_CALL_ACCEPTED);
+ rose_start_idletimer(sk);
+ sk->protinfo.rose->condition = 0x00;
+ sk->protinfo.rose->vs = 0;
+ sk->protinfo.rose->va = 0;
+ sk->protinfo.rose->vr = 0;
+ sk->protinfo.rose->vl = 0;
+ sk->protinfo.rose->state = ROSE_STATE_3;
+ }
+ return 0;
+
+ default:
+ return dev_ioctl(cmd, (void *)arg);
+ }
+
+ /*NOTREACHED*/
+ return 0;
+}
+
+static int rose_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct sock *s;
+ struct net_device *dev;
+ const char *devname, *callsign;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+
+ cli();
+
+ len += sprintf(buffer, "dest_addr dest_call src_addr src_call dev lci neigh st vs vr va t t1 t2 t3 hb idle Snd-Q Rcv-Q inode\n");
+
+ for (s = rose_list; s != NULL; s = s->next) {
+ if ((dev = s->protinfo.rose->device) == NULL)
+ devname = "???";
+ else
+ devname = dev->name;
+
+ len += sprintf(buffer + len, "%-10s %-9s ",
+ rose2asc(&s->protinfo.rose->dest_addr),
+ ax2asc(&s->protinfo.rose->dest_call));
+
+ if (ax25cmp(&s->protinfo.rose->source_call, &null_ax25_address) == 0)
+ callsign = "??????-?";
+ else
+ callsign = ax2asc(&s->protinfo.rose->source_call);
+
+ len += sprintf(buffer + len, "%-10s %-9s %-5s %3.3X %05d %d %d %d %d %3lu %3lu %3lu %3lu %3lu %3lu/%03lu %5d %5d %ld\n",
+ rose2asc(&s->protinfo.rose->source_addr),
+ callsign,
+ devname,
+ s->protinfo.rose->lci & 0x0FFF,
+ (s->protinfo.rose->neighbour) ? s->protinfo.rose->neighbour->number : 0,
+ s->protinfo.rose->state,
+ s->protinfo.rose->vs,
+ s->protinfo.rose->vr,
+ s->protinfo.rose->va,
+ ax25_display_timer(&s->protinfo.rose->timer) / HZ,
+ s->protinfo.rose->t1 / HZ,
+ s->protinfo.rose->t2 / HZ,
+ s->protinfo.rose->t3 / HZ,
+ s->protinfo.rose->hb / HZ,
+ ax25_display_timer(&s->protinfo.rose->idletimer) / (60 * HZ),
+ s->protinfo.rose->idle / (60 * HZ),
+ atomic_read(&s->wmem_alloc),
+ atomic_read(&s->rmem_alloc),
+ s->socket != NULL ? s->socket->inode->i_ino : 0L);
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ break;
+ }
+
+ sti();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return(len);
+}
+
+static struct net_proto_family rose_family_ops = {
+ family: PF_ROSE,
+ create: rose_create,
+};
+
+static struct proto_ops SOCKOPS_WRAPPED(rose_proto_ops) = {
+ family: PF_ROSE,
+
+ release: rose_release,
+ bind: rose_bind,
+ connect: rose_connect,
+ socketpair: sock_no_socketpair,
+ accept: rose_accept,
+ getname: rose_getname,
+ poll: datagram_poll,
+ ioctl: rose_ioctl,
+ listen: rose_listen,
+ shutdown: sock_no_shutdown,
+ setsockopt: rose_setsockopt,
+ getsockopt: rose_getsockopt,
+ sendmsg: rose_sendmsg,
+ recvmsg: rose_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+#include <linux/smp_lock.h>
+SOCKOPS_WRAP(rose_proto, PF_ROSE);
+
+static struct notifier_block rose_dev_notifier = {
+ notifier_call: rose_device_event,
+};
+
+static struct net_device *dev_rose;
+
+static const char banner[] = KERN_INFO "F6FBB/G4KLX ROSE for Linux. Version 0.64 for AX25.037 Linux 2.4\n";
+
+static int __init rose_proto_init(void)
+{
+ int i;
+
+ rose_callsign = null_ax25_address;
+
+ if (rose_ndevs > 0x7FFFFFFF/sizeof(struct net_device)) {
+ printk(KERN_ERR "ROSE: rose_proto_init - rose_ndevs parameter to large\n");
+ return -1;
+ }
+
+ if ((dev_rose = kmalloc(rose_ndevs * sizeof(struct net_device), GFP_KERNEL)) == NULL) {
+ printk(KERN_ERR "ROSE: rose_proto_init - unable to allocate device structure\n");
+ return -1;
+ }
+
+ memset(dev_rose, 0x00, rose_ndevs * sizeof(struct net_device));
+
+ for (i = 0; i < rose_ndevs; i++) {
+ sprintf(dev_rose[i].name, "rose%d", i);
+ dev_rose[i].init = rose_init;
+ register_netdev(&dev_rose[i]);
+ }
+
+ sock_register(&rose_family_ops);
+ register_netdevice_notifier(&rose_dev_notifier);
+ printk(banner);
+
+ ax25_protocol_register(AX25_P_ROSE, rose_route_frame);
+ ax25_linkfail_register(rose_link_failed);
+
+#ifdef CONFIG_SYSCTL
+ rose_register_sysctl();
+#endif
+ rose_loopback_init();
+
+ rose_add_loopback_neigh();
+
+ proc_net_create("rose", 0, rose_get_info);
+ proc_net_create("rose_neigh", 0, rose_neigh_get_info);
+ proc_net_create("rose_nodes", 0, rose_nodes_get_info);
+ proc_net_create("rose_routes", 0, rose_routes_get_info);
+ return 0;
+}
+module_init(rose_proto_init);
+
+EXPORT_NO_SYMBOLS;
+
+MODULE_PARM(rose_ndevs, "i");
+MODULE_PARM_DESC(rose_ndevs, "number of ROSE devices");
+
+MODULE_AUTHOR("Jonathan Naylor G4KLX <g4klx@g4klx.demon.co.uk>");
+MODULE_DESCRIPTION("The amateur radio ROSE network layer protocol");
+MODULE_LICENSE("GPL");
+
+static void __exit rose_exit(void)
+{
+ int i;
+
+ proc_net_remove("rose");
+ proc_net_remove("rose_neigh");
+ proc_net_remove("rose_nodes");
+ proc_net_remove("rose_routes");
+ rose_loopback_clear();
+
+ rose_rt_free();
+
+ ax25_protocol_release(AX25_P_ROSE);
+ ax25_linkfail_release(rose_link_failed);
+
+ if (ax25cmp(&rose_callsign, &null_ax25_address) != 0)
+ ax25_listen_release(&rose_callsign, NULL);
+
+#ifdef CONFIG_SYSCTL
+ rose_unregister_sysctl();
+#endif
+ unregister_netdevice_notifier(&rose_dev_notifier);
+
+ sock_unregister(PF_ROSE);
+
+ for (i = 0; i < rose_ndevs; i++) {
+ if (dev_rose[i].priv != NULL) {
+ kfree(dev_rose[i].priv);
+ dev_rose[i].priv = NULL;
+ unregister_netdev(&dev_rose[i]);
+ }
+ }
+
+ kfree(dev_rose);
+}
+module_exit(rose_exit);
+
diff --git a/uClinux-2.4.31-uc0/net/rose/rose_dev.c b/uClinux-2.4.31-uc0/net/rose/rose_dev.c
new file mode 100644
index 0000000..16619c7
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/rose/rose_dev.c
@@ -0,0 +1,204 @@
+/*
+ * ROSE release 003
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * ROSE 001 Jonathan(G4KLX) Cloned from nr_dev.c.
+ * Hans(PE1AYX) Fixed interface to IP layer.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/fs.h>
+#include <linux/types.h>
+#include <linux/sysctl.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/errno.h>
+#include <linux/fcntl.h>
+#include <linux/in.h>
+#include <linux/if_ether.h> /* For the statistics structure. */
+
+#include <asm/system.h>
+#include <asm/segment.h>
+#include <asm/io.h>
+
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+
+#include <net/ip.h>
+#include <net/arp.h>
+
+#include <net/ax25.h>
+#include <net/rose.h>
+
+/*
+ * Only allow IP over ROSE frames through if the netrom device is up.
+ */
+
+int rose_rx_ip(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_device_stats *stats = (struct net_device_stats *)dev->priv;
+
+#ifdef CONFIG_INET
+ if (!netif_running(dev)) {
+ stats->rx_errors++;
+ return 0;
+ }
+
+ stats->rx_packets++;
+ stats->rx_bytes += skb->len;
+
+ skb->protocol = htons(ETH_P_IP);
+
+ /* Spoof incoming device */
+ skb->dev = dev;
+ skb->h.raw = skb->data;
+ skb->nh.raw = skb->data;
+ skb->pkt_type = PACKET_HOST;
+
+ ip_rcv(skb, skb->dev, NULL);
+#else
+ kfree_skb(skb);
+#endif
+ return 1;
+}
+
+static int rose_header(struct sk_buff *skb, struct net_device *dev, unsigned short type,
+ void *daddr, void *saddr, unsigned len)
+{
+ unsigned char *buff = skb_push(skb, ROSE_MIN_LEN + 2);
+
+ *buff++ = ROSE_GFI | ROSE_Q_BIT;
+ *buff++ = 0x00;
+ *buff++ = ROSE_DATA;
+ *buff++ = 0x7F;
+ *buff++ = AX25_P_IP;
+
+ if (daddr != NULL)
+ return 37;
+
+ return -37;
+}
+
+static int rose_rebuild_header(struct sk_buff *skb)
+{
+ struct net_device *dev = skb->dev;
+ struct net_device_stats *stats = (struct net_device_stats *)dev->priv;
+ unsigned char *bp = (unsigned char *)skb->data;
+ struct sk_buff *skbn;
+
+#ifdef CONFIG_INET
+ if (arp_find(bp + 7, skb)) {
+ return 1;
+ }
+
+ if ((skbn = skb_clone(skb, GFP_ATOMIC)) == NULL) {
+ kfree_skb(skb);
+ return 1;
+ }
+
+ if (skb->sk != NULL)
+ skb_set_owner_w(skbn, skb->sk);
+
+ kfree_skb(skb);
+
+ if (!rose_route_frame(skbn, NULL)) {
+ kfree_skb(skbn);
+ stats->tx_errors++;
+ return 1;
+ }
+
+ stats->tx_packets++;
+ stats->tx_bytes += skbn->len;
+#endif
+ return 1;
+}
+
+static int rose_set_mac_address(struct net_device *dev, void *addr)
+{
+ struct sockaddr *sa = addr;
+
+ rose_del_loopback_node((rose_address *)dev->dev_addr);
+
+ memcpy(dev->dev_addr, sa->sa_data, dev->addr_len);
+
+ rose_add_loopback_node((rose_address *)dev->dev_addr);
+
+ return 0;
+}
+
+static int rose_open(struct net_device *dev)
+{
+ MOD_INC_USE_COUNT;
+ netif_start_queue(dev);
+ rose_add_loopback_node((rose_address *)dev->dev_addr);
+ return 0;
+}
+
+static int rose_close(struct net_device *dev)
+{
+ netif_stop_queue(dev);
+ rose_del_loopback_node((rose_address *)dev->dev_addr);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static int rose_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct net_device_stats *stats = (struct net_device_stats *)dev->priv;
+
+ if (!netif_running(dev)) {
+ printk(KERN_ERR "ROSE: rose_xmit - called when iface is down\n");
+ return 1;
+ }
+ dev_kfree_skb(skb);
+ stats->tx_errors++;
+ return 0;
+}
+
+static struct net_device_stats *rose_get_stats(struct net_device *dev)
+{
+ return (struct net_device_stats *)dev->priv;
+}
+
+int rose_init(struct net_device *dev)
+{
+ dev->mtu = ROSE_MAX_PACKET_SIZE - 2;
+ dev->hard_start_xmit = rose_xmit;
+ dev->open = rose_open;
+ dev->stop = rose_close;
+
+ dev->hard_header = rose_header;
+ dev->hard_header_len = AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + ROSE_MIN_LEN;
+ dev->addr_len = ROSE_ADDR_LEN;
+ dev->type = ARPHRD_ROSE;
+ dev->rebuild_header = rose_rebuild_header;
+ dev->set_mac_address = rose_set_mac_address;
+
+ /* New-style flags. */
+ dev->flags = 0;
+
+ if ((dev->priv = kmalloc(sizeof(struct net_device_stats), GFP_KERNEL)) == NULL)
+ return -ENOMEM;
+
+ memset(dev->priv, 0, sizeof(struct net_device_stats));
+
+ dev->get_stats = rose_get_stats;
+
+ return 0;
+};
diff --git a/uClinux-2.4.31-uc0/net/rose/rose_in.c b/uClinux-2.4.31-uc0/net/rose/rose_in.c
new file mode 100644
index 0000000..38fb76c
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/rose/rose_in.c
@@ -0,0 +1,303 @@
+/*
+ * ROSE release 003
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Most of this code is based on the SDL diagrams published in the 7th
+ * ARRL Computer Networking Conference papers. The diagrams have mistakes
+ * in them, but are mostly correct. Before you modify the code could you
+ * read the SDL diagrams as the code is not obvious and probably very
+ * easy to break;
+ *
+ * History
+ * ROSE 001 Jonathan(G4KLX) Cloned from nr_in.c
+ * ROSE 002 Jonathan(G4KLX) Return cause and diagnostic codes from Clear Requests.
+ * ROSE 003 Jonathan(G4KLX) New timer architecture.
+ * Removed M bit processing.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/ip.h> /* For ip_rcv */
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/rose.h>
+
+/*
+ * State machine for state 1, Awaiting Call Accepted State.
+ * The handling of the timer(s) is in file rose_timer.c.
+ * Handling of state 0 and connection release is in af_rose.c.
+ */
+static int rose_state1_machine(struct sock *sk, struct sk_buff *skb, int frametype)
+{
+ switch (frametype) {
+
+ case ROSE_CALL_ACCEPTED:
+ rose_stop_timer(sk);
+ rose_start_idletimer(sk);
+ sk->protinfo.rose->condition = 0x00;
+ sk->protinfo.rose->vs = 0;
+ sk->protinfo.rose->va = 0;
+ sk->protinfo.rose->vr = 0;
+ sk->protinfo.rose->vl = 0;
+ sk->protinfo.rose->state = ROSE_STATE_3;
+ sk->state = TCP_ESTABLISHED;
+ if (!sk->dead)
+ sk->state_change(sk);
+ break;
+
+ case ROSE_CLEAR_REQUEST:
+ rose_write_internal(sk, ROSE_CLEAR_CONFIRMATION);
+ rose_disconnect(sk, ECONNREFUSED, skb->data[3], skb->data[4]);
+ sk->protinfo.rose->neighbour->use--;
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * State machine for state 2, Awaiting Clear Confirmation State.
+ * The handling of the timer(s) is in file rose_timer.c
+ * Handling of state 0 and connection release is in af_rose.c.
+ */
+static int rose_state2_machine(struct sock *sk, struct sk_buff *skb, int frametype)
+{
+ switch (frametype) {
+
+ case ROSE_CLEAR_REQUEST:
+ rose_write_internal(sk, ROSE_CLEAR_CONFIRMATION);
+ rose_disconnect(sk, 0, skb->data[3], skb->data[4]);
+ sk->protinfo.rose->neighbour->use--;
+ break;
+
+ case ROSE_CLEAR_CONFIRMATION:
+ rose_disconnect(sk, 0, -1, -1);
+ sk->protinfo.rose->neighbour->use--;
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * State machine for state 3, Connected State.
+ * The handling of the timer(s) is in file rose_timer.c
+ * Handling of state 0 and connection release is in af_rose.c.
+ */
+static int rose_state3_machine(struct sock *sk, struct sk_buff *skb, int frametype, int ns, int nr, int q, int d, int m)
+{
+ int queued = 0;
+
+ switch (frametype) {
+
+ case ROSE_RESET_REQUEST:
+ rose_stop_timer(sk);
+ rose_start_idletimer(sk);
+ rose_write_internal(sk, ROSE_RESET_CONFIRMATION);
+ sk->protinfo.rose->condition = 0x00;
+ sk->protinfo.rose->vs = 0;
+ sk->protinfo.rose->vr = 0;
+ sk->protinfo.rose->va = 0;
+ sk->protinfo.rose->vl = 0;
+ rose_requeue_frames(sk);
+ break;
+
+ case ROSE_CLEAR_REQUEST:
+ rose_write_internal(sk, ROSE_CLEAR_CONFIRMATION);
+ rose_disconnect(sk, 0, skb->data[3], skb->data[4]);
+ sk->protinfo.rose->neighbour->use--;
+ break;
+
+ case ROSE_RR:
+ case ROSE_RNR:
+ if (!rose_validate_nr(sk, nr)) {
+ rose_write_internal(sk, ROSE_RESET_REQUEST);
+ sk->protinfo.rose->condition = 0x00;
+ sk->protinfo.rose->vs = 0;
+ sk->protinfo.rose->vr = 0;
+ sk->protinfo.rose->va = 0;
+ sk->protinfo.rose->vl = 0;
+ sk->protinfo.rose->state = ROSE_STATE_4;
+ rose_start_t2timer(sk);
+ rose_stop_idletimer(sk);
+ } else {
+ rose_frames_acked(sk, nr);
+ if (frametype == ROSE_RNR) {
+ sk->protinfo.rose->condition |= ROSE_COND_PEER_RX_BUSY;
+ } else {
+ sk->protinfo.rose->condition &= ~ROSE_COND_PEER_RX_BUSY;
+ }
+ }
+ break;
+
+ case ROSE_DATA: /* XXX */
+ sk->protinfo.rose->condition &= ~ROSE_COND_PEER_RX_BUSY;
+ if (!rose_validate_nr(sk, nr)) {
+ rose_write_internal(sk, ROSE_RESET_REQUEST);
+ sk->protinfo.rose->condition = 0x00;
+ sk->protinfo.rose->vs = 0;
+ sk->protinfo.rose->vr = 0;
+ sk->protinfo.rose->va = 0;
+ sk->protinfo.rose->vl = 0;
+ sk->protinfo.rose->state = ROSE_STATE_4;
+ rose_start_t2timer(sk);
+ rose_stop_idletimer(sk);
+ break;
+ }
+ rose_frames_acked(sk, nr);
+ if (ns == sk->protinfo.rose->vr) {
+ rose_start_idletimer(sk);
+ if (sock_queue_rcv_skb(sk, skb) == 0) {
+ sk->protinfo.rose->vr = (sk->protinfo.rose->vr + 1) % ROSE_MODULUS;
+ queued = 1;
+ } else {
+ /* Should never happen ! */
+ rose_write_internal(sk, ROSE_RESET_REQUEST);
+ sk->protinfo.rose->condition = 0x00;
+ sk->protinfo.rose->vs = 0;
+ sk->protinfo.rose->vr = 0;
+ sk->protinfo.rose->va = 0;
+ sk->protinfo.rose->vl = 0;
+ sk->protinfo.rose->state = ROSE_STATE_4;
+ rose_start_t2timer(sk);
+ rose_stop_idletimer(sk);
+ break;
+ }
+ if (atomic_read(&sk->rmem_alloc) > (sk->rcvbuf / 2))
+ sk->protinfo.rose->condition |= ROSE_COND_OWN_RX_BUSY;
+ }
+ /*
+ * If the window is full, ack the frame, else start the
+ * acknowledge hold back timer.
+ */
+ if (((sk->protinfo.rose->vl + sysctl_rose_window_size) % ROSE_MODULUS) == sk->protinfo.rose->vr) {
+ sk->protinfo.rose->condition &= ~ROSE_COND_ACK_PENDING;
+ rose_stop_timer(sk);
+ rose_enquiry_response(sk);
+ } else {
+ sk->protinfo.rose->condition |= ROSE_COND_ACK_PENDING;
+ rose_start_hbtimer(sk);
+ }
+ break;
+
+ default:
+ printk(KERN_WARNING "ROSE: unknown %02X in state 3\n", frametype);
+ break;
+ }
+
+ return queued;
+}
+
+/*
+ * State machine for state 4, Awaiting Reset Confirmation State.
+ * The handling of the timer(s) is in file rose_timer.c
+ * Handling of state 0 and connection release is in af_rose.c.
+ */
+static int rose_state4_machine(struct sock *sk, struct sk_buff *skb, int frametype)
+{
+ switch (frametype) {
+
+ case ROSE_RESET_REQUEST:
+ rose_write_internal(sk, ROSE_RESET_CONFIRMATION);
+ case ROSE_RESET_CONFIRMATION:
+ rose_stop_timer(sk);
+ rose_start_idletimer(sk);
+ sk->protinfo.rose->condition = 0x00;
+ sk->protinfo.rose->va = 0;
+ sk->protinfo.rose->vr = 0;
+ sk->protinfo.rose->vs = 0;
+ sk->protinfo.rose->vl = 0;
+ sk->protinfo.rose->state = ROSE_STATE_3;
+ rose_requeue_frames(sk);
+ break;
+
+ case ROSE_CLEAR_REQUEST:
+ rose_write_internal(sk, ROSE_CLEAR_CONFIRMATION);
+ rose_disconnect(sk, 0, skb->data[3], skb->data[4]);
+ sk->protinfo.rose->neighbour->use--;
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * State machine for state 5, Awaiting Call Acceptance State.
+ * The handling of the timer(s) is in file rose_timer.c
+ * Handling of state 0 and connection release is in af_rose.c.
+ */
+static int rose_state5_machine(struct sock *sk, struct sk_buff *skb, int frametype)
+{
+ if (frametype == ROSE_CLEAR_REQUEST) {
+ rose_write_internal(sk, ROSE_CLEAR_CONFIRMATION);
+ rose_disconnect(sk, 0, skb->data[3], skb->data[4]);
+ sk->protinfo.rose->neighbour->use--;
+ }
+
+ return 0;
+}
+
+/* Higher level upcall for a LAPB frame */
+int rose_process_rx_frame(struct sock *sk, struct sk_buff *skb)
+{
+ int queued = 0, frametype, ns, nr, q, d, m;
+
+ if (sk->protinfo.rose->state == ROSE_STATE_0)
+ return 0;
+
+ frametype = rose_decode(skb, &ns, &nr, &q, &d, &m);
+
+ switch (sk->protinfo.rose->state) {
+ case ROSE_STATE_1:
+ queued = rose_state1_machine(sk, skb, frametype);
+ break;
+ case ROSE_STATE_2:
+ queued = rose_state2_machine(sk, skb, frametype);
+ break;
+ case ROSE_STATE_3:
+ queued = rose_state3_machine(sk, skb, frametype, ns, nr, q, d, m);
+ break;
+ case ROSE_STATE_4:
+ queued = rose_state4_machine(sk, skb, frametype);
+ break;
+ case ROSE_STATE_5:
+ queued = rose_state5_machine(sk, skb, frametype);
+ break;
+ }
+
+ rose_kick(sk);
+
+ return queued;
+}
diff --git a/uClinux-2.4.31-uc0/net/rose/rose_link.c b/uClinux-2.4.31-uc0/net/rose/rose_link.c
new file mode 100644
index 0000000..1be087d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/rose/rose_link.c
@@ -0,0 +1,343 @@
+/*
+ * ROSE release 003
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * ROSE 001 Jonathan(G4KLX) Cloned from rose_timer.c
+ * ROSE 003 Jonathan(G4KLX) New timer architecture.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/netfilter.h>
+#include <net/rose.h>
+
+static void rose_ftimer_expiry(unsigned long);
+static void rose_t0timer_expiry(unsigned long);
+
+void rose_start_ftimer(struct rose_neigh *neigh)
+{
+ del_timer(&neigh->ftimer);
+
+ neigh->ftimer.data = (unsigned long)neigh;
+ neigh->ftimer.function = &rose_ftimer_expiry;
+ neigh->ftimer.expires = jiffies + sysctl_rose_link_fail_timeout;
+
+ add_timer(&neigh->ftimer);
+}
+
+void rose_start_t0timer(struct rose_neigh *neigh)
+{
+ del_timer(&neigh->t0timer);
+
+ neigh->t0timer.data = (unsigned long)neigh;
+ neigh->t0timer.function = &rose_t0timer_expiry;
+ neigh->t0timer.expires = jiffies + sysctl_rose_restart_request_timeout;
+
+ add_timer(&neigh->t0timer);
+}
+
+void rose_stop_ftimer(struct rose_neigh *neigh)
+{
+ del_timer(&neigh->ftimer);
+}
+
+void rose_stop_t0timer(struct rose_neigh *neigh)
+{
+ del_timer(&neigh->t0timer);
+}
+
+int rose_ftimer_running(struct rose_neigh *neigh)
+{
+ return timer_pending(&neigh->ftimer);
+}
+
+int rose_t0timer_running(struct rose_neigh *neigh)
+{
+ return timer_pending(&neigh->t0timer);
+}
+
+static void rose_ftimer_expiry(unsigned long param)
+{
+}
+
+static void rose_t0timer_expiry(unsigned long param)
+{
+ struct rose_neigh *neigh = (struct rose_neigh *)param;
+
+ rose_transmit_restart_request(neigh);
+
+ neigh->dce_mode = 0;
+
+ rose_start_t0timer(neigh);
+}
+
+/*
+ * Interface to ax25_send_frame. Changes my level 2 callsign depending
+ * on whether we have a global ROSE callsign or use the default port
+ * callsign.
+ */
+static int rose_send_frame(struct sk_buff *skb, struct rose_neigh *neigh)
+{
+ ax25_address *rose_call;
+
+ if (ax25cmp(&rose_callsign, &null_ax25_address) == 0)
+ rose_call = (ax25_address *)neigh->dev->dev_addr;
+ else
+ rose_call = &rose_callsign;
+
+ neigh->ax25 = ax25_send_frame(skb, 260, rose_call, &neigh->callsign, neigh->digipeat, neigh->dev);
+
+ return (neigh->ax25 != NULL);
+}
+
+/*
+ * Interface to ax25_link_up. Changes my level 2 callsign depending
+ * on whether we have a global ROSE callsign or use the default port
+ * callsign.
+ */
+static int rose_link_up(struct rose_neigh *neigh)
+{
+ ax25_address *rose_call;
+
+ if (ax25cmp(&rose_callsign, &null_ax25_address) == 0)
+ rose_call = (ax25_address *)neigh->dev->dev_addr;
+ else
+ rose_call = &rose_callsign;
+
+ neigh->ax25 = ax25_find_cb(rose_call, &neigh->callsign, neigh->digipeat, neigh->dev);
+
+ return (neigh->ax25 != NULL);
+}
+
+/*
+ * This handles all restart and diagnostic frames.
+ */
+void rose_link_rx_restart(struct sk_buff *skb, struct rose_neigh *neigh, unsigned short frametype)
+{
+ struct sk_buff *skbn;
+
+ switch (frametype) {
+ case ROSE_RESTART_REQUEST:
+ rose_stop_t0timer(neigh);
+ neigh->restarted = 1;
+ neigh->dce_mode = (skb->data[3] == ROSE_DTE_ORIGINATED);
+ rose_transmit_restart_confirmation(neigh);
+ break;
+
+ case ROSE_RESTART_CONFIRMATION:
+ rose_stop_t0timer(neigh);
+ neigh->restarted = 1;
+ break;
+
+ case ROSE_DIAGNOSTIC:
+ printk(KERN_WARNING "ROSE: received diagnostic #%d - %02X %02X %02X\n", skb->data[3], skb->data[4], skb->data[5], skb->data[6]);
+ break;
+
+ default:
+ printk(KERN_WARNING "ROSE: received unknown %02X with LCI 000\n", frametype);
+ break;
+ }
+
+ if (neigh->restarted) {
+ while ((skbn = skb_dequeue(&neigh->queue)) != NULL)
+ if (!rose_send_frame(skbn, neigh))
+ kfree_skb(skbn);
+ }
+}
+
+/*
+ * This routine is called when a Restart Request is needed
+ */
+void rose_transmit_restart_request(struct rose_neigh *neigh)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+ int len;
+
+ len = AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + ROSE_MIN_LEN + 3;
+
+ if ((skb = alloc_skb(len, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skb, AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN);
+
+ dptr = skb_put(skb, ROSE_MIN_LEN + 3);
+
+ *dptr++ = AX25_P_ROSE;
+ *dptr++ = ROSE_GFI;
+ *dptr++ = 0x00;
+ *dptr++ = ROSE_RESTART_REQUEST;
+ *dptr++ = ROSE_DTE_ORIGINATED;
+ *dptr++ = 0;
+
+ if (!rose_send_frame(skb, neigh))
+ kfree_skb(skb);
+}
+
+/*
+ * This routine is called when a Restart Confirmation is needed
+ */
+void rose_transmit_restart_confirmation(struct rose_neigh *neigh)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+ int len;
+
+ len = AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + ROSE_MIN_LEN + 1;
+
+ if ((skb = alloc_skb(len, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skb, AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN);
+
+ dptr = skb_put(skb, ROSE_MIN_LEN + 1);
+
+ *dptr++ = AX25_P_ROSE;
+ *dptr++ = ROSE_GFI;
+ *dptr++ = 0x00;
+ *dptr++ = ROSE_RESTART_CONFIRMATION;
+
+ if (!rose_send_frame(skb, neigh))
+ kfree_skb(skb);
+}
+
+/*
+ * This routine is called when a Diagnostic is required.
+ */
+void rose_transmit_diagnostic(struct rose_neigh *neigh, unsigned char diag)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+ int len;
+
+ len = AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + ROSE_MIN_LEN + 2;
+
+ if ((skb = alloc_skb(len, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skb, AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN);
+
+ dptr = skb_put(skb, ROSE_MIN_LEN + 2);
+
+ *dptr++ = AX25_P_ROSE;
+ *dptr++ = ROSE_GFI;
+ *dptr++ = 0x00;
+ *dptr++ = ROSE_DIAGNOSTIC;
+ *dptr++ = diag;
+
+ if (!rose_send_frame(skb, neigh))
+ kfree_skb(skb);
+}
+
+/*
+ * This routine is called when a Clear Request is needed outside of the context
+ * of a connected socket.
+ */
+void rose_transmit_clear_request(struct rose_neigh *neigh, unsigned int lci, unsigned char cause, unsigned char diagnostic)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+ int len;
+ struct net_device *first;
+ int faclen = 0;
+
+ len = AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + ROSE_MIN_LEN + 3;
+
+ first = rose_dev_first();
+ if (first)
+ faclen = 6 + AX25_ADDR_LEN + 3 + ROSE_ADDR_LEN;
+
+ if ((skb = alloc_skb(len + faclen, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skb, AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN);
+
+ dptr = skb_put(skb, ROSE_MIN_LEN + 3 + faclen);
+
+ *dptr++ = AX25_P_ROSE;
+ *dptr++ = ((lci >> 8) & 0x0F) | ROSE_GFI;
+ *dptr++ = ((lci >> 0) & 0xFF);
+ *dptr++ = ROSE_CLEAR_REQUEST;
+ *dptr++ = cause;
+ *dptr++ = diagnostic;
+
+ if (first) {
+ *dptr++ = 0x00; /* Address length */
+ *dptr++ = 4 + AX25_ADDR_LEN + 3 + ROSE_ADDR_LEN; /* Facilities length */
+ *dptr++ = 0;
+ *dptr++ = FAC_NATIONAL;
+ *dptr++ = FAC_NATIONAL_FAIL_CALL;
+ *dptr++ = AX25_ADDR_LEN;
+ memcpy(dptr, &rose_callsign, AX25_ADDR_LEN);
+ dptr += AX25_ADDR_LEN;
+ *dptr++ = FAC_NATIONAL_FAIL_ADD;
+ *dptr++ = ROSE_ADDR_LEN + 1;
+ *dptr++ = ROSE_ADDR_LEN * 2;
+ memcpy(dptr, first->dev_addr, ROSE_ADDR_LEN);
+ }
+
+ if (!rose_send_frame(skb, neigh))
+ kfree_skb(skb);
+}
+
+void rose_transmit_link(struct sk_buff *skb, struct rose_neigh *neigh)
+{
+ unsigned char *dptr;
+
+#if 0
+ if (call_fw_firewall(PF_ROSE, skb->dev, skb->data, NULL, &skb) != FW_ACCEPT) {
+ kfree_skb(skb);
+ return;
+ }
+#endif
+
+ if (neigh->loopback) {
+ rose_loopback_queue(skb, neigh);
+ return;
+ }
+
+ if (!rose_link_up(neigh))
+ neigh->restarted = 0;
+
+ dptr = skb_push(skb, 1);
+ *dptr++ = AX25_P_ROSE;
+
+ if (neigh->restarted) {
+ if (!rose_send_frame(skb, neigh))
+ kfree_skb(skb);
+ } else {
+ skb_queue_tail(&neigh->queue, skb);
+
+ if (!rose_t0timer_running(neigh)) {
+ rose_transmit_restart_request(neigh);
+ neigh->dce_mode = 0;
+ rose_start_t0timer(neigh);
+ }
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/rose/rose_loopback.c b/uClinux-2.4.31-uc0/net/rose/rose_loopback.c
new file mode 100644
index 0000000..ad00608
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/rose/rose_loopback.c
@@ -0,0 +1,119 @@
+/*
+ * ROSE release 003
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * ROSE 003 Jonathan(G4KLX) Created this file from nr_loopback.c.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/timer.h>
+#include <net/ax25.h>
+#include <linux/skbuff.h>
+#include <net/rose.h>
+#include <linux/init.h>
+
+static struct sk_buff_head loopback_queue;
+static struct timer_list loopback_timer;
+
+static void rose_set_loopback_timer(void);
+
+void rose_loopback_init(void)
+{
+ skb_queue_head_init(&loopback_queue);
+
+ init_timer(&loopback_timer);
+}
+
+static int rose_loopback_running(void)
+{
+ return timer_pending(&loopback_timer);
+}
+
+int rose_loopback_queue(struct sk_buff *skb, struct rose_neigh *neigh)
+{
+ struct sk_buff *skbn;
+
+ skbn = skb_clone(skb, GFP_ATOMIC);
+
+ kfree_skb(skb);
+
+ if (skbn != NULL) {
+ skb_queue_tail(&loopback_queue, skbn);
+
+ if (!rose_loopback_running())
+ rose_set_loopback_timer();
+ }
+
+ return 1;
+}
+
+static void rose_loopback_timer(unsigned long);
+
+static void rose_set_loopback_timer(void)
+{
+ del_timer(&loopback_timer);
+
+ loopback_timer.data = 0;
+ loopback_timer.function = &rose_loopback_timer;
+ loopback_timer.expires = jiffies + 10;
+
+ add_timer(&loopback_timer);
+}
+
+static void rose_loopback_timer(unsigned long param)
+{
+ struct sk_buff *skb;
+ struct net_device *dev;
+ rose_address *dest;
+ struct sock *sk;
+ unsigned short frametype;
+ unsigned int lci_i, lci_o;
+
+ while ((skb = skb_dequeue(&loopback_queue)) != NULL) {
+ lci_i = ((skb->data[0] << 8) & 0xF00) + ((skb->data[1] << 0) & 0x0FF);
+ frametype = skb->data[2];
+ dest = (rose_address *)(skb->data + 4);
+ lci_o = 0xFFF - lci_i;
+
+ skb->h.raw = skb->data;
+
+ if ((sk = rose_find_socket(lci_o, rose_loopback_neigh)) != NULL) {
+ if (rose_process_rx_frame(sk, skb) == 0)
+ kfree_skb(skb);
+ continue;
+ }
+
+ if (frametype == ROSE_CALL_REQUEST) {
+ if ((dev = rose_dev_get(dest)) != NULL) {
+ if (rose_rx_call_request(skb, dev, rose_loopback_neigh, lci_o) == 0)
+ kfree_skb(skb);
+ } else {
+ kfree_skb(skb);
+ }
+ } else {
+ kfree_skb(skb);
+ }
+ }
+}
+
+void __exit rose_loopback_clear(void)
+{
+ struct sk_buff *skb;
+
+ del_timer(&loopback_timer);
+
+ while ((skb = skb_dequeue(&loopback_queue)) != NULL) {
+ skb->sk = NULL;
+ kfree_skb(skb);
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/rose/rose_out.c b/uClinux-2.4.31-uc0/net/rose/rose_out.c
new file mode 100644
index 0000000..8f9855d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/rose/rose_out.c
@@ -0,0 +1,130 @@
+/*
+ * ROSE release 003
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * ROSE 001 Jonathan(G4KLX) Cloned from nr_out.c
+ * ROSE 003 Jonathan(G4KLX) New timer architecture.
+ * Removed M bit processing.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/rose.h>
+
+/*
+ * This procedure is passed a buffer descriptor for an iframe. It builds
+ * the rest of the control part of the frame and then writes it out.
+ */
+static void rose_send_iframe(struct sock *sk, struct sk_buff *skb)
+{
+ if (skb == NULL)
+ return;
+
+ skb->data[2] |= (sk->protinfo.rose->vr << 5) & 0xE0;
+ skb->data[2] |= (sk->protinfo.rose->vs << 1) & 0x0E;
+
+ rose_start_idletimer(sk);
+
+ rose_transmit_link(skb, sk->protinfo.rose->neighbour);
+}
+
+void rose_kick(struct sock *sk)
+{
+ struct sk_buff *skb, *skbn;
+ unsigned short start, end;
+
+ if (sk->protinfo.rose->state != ROSE_STATE_3)
+ return;
+
+ if (sk->protinfo.rose->condition & ROSE_COND_PEER_RX_BUSY)
+ return;
+
+ if (skb_peek(&sk->write_queue) == NULL)
+ return;
+
+ start = (skb_peek(&sk->protinfo.rose->ack_queue) == NULL) ? sk->protinfo.rose->va : sk->protinfo.rose->vs;
+ end = (sk->protinfo.rose->va + sysctl_rose_window_size) % ROSE_MODULUS;
+
+ if (start == end)
+ return;
+
+ sk->protinfo.rose->vs = start;
+
+ /*
+ * Transmit data until either we're out of data to send or
+ * the window is full.
+ */
+
+ skb = skb_dequeue(&sk->write_queue);
+
+ do {
+ if ((skbn = skb_clone(skb, GFP_ATOMIC)) == NULL) {
+ skb_queue_head(&sk->write_queue, skb);
+ break;
+ }
+
+ skb_set_owner_w(skbn, sk);
+
+ /*
+ * Transmit the frame copy.
+ */
+ rose_send_iframe(sk, skbn);
+
+ sk->protinfo.rose->vs = (sk->protinfo.rose->vs + 1) % ROSE_MODULUS;
+
+ /*
+ * Requeue the original data frame.
+ */
+ skb_queue_tail(&sk->protinfo.rose->ack_queue, skb);
+
+ } while (sk->protinfo.rose->vs != end && (skb = skb_dequeue(&sk->write_queue)) != NULL);
+
+ sk->protinfo.rose->vl = sk->protinfo.rose->vr;
+ sk->protinfo.rose->condition &= ~ROSE_COND_ACK_PENDING;
+
+ rose_stop_timer(sk);
+}
+
+/*
+ * The following routines are taken from page 170 of the 7th ARRL Computer
+ * Networking Conference paper, as is the whole state machine.
+ */
+
+void rose_enquiry_response(struct sock *sk)
+{
+ if (sk->protinfo.rose->condition & ROSE_COND_OWN_RX_BUSY)
+ rose_write_internal(sk, ROSE_RNR);
+ else
+ rose_write_internal(sk, ROSE_RR);
+
+ sk->protinfo.rose->vl = sk->protinfo.rose->vr;
+ sk->protinfo.rose->condition &= ~ROSE_COND_ACK_PENDING;
+
+ rose_stop_timer(sk);
+}
diff --git a/uClinux-2.4.31-uc0/net/rose/rose_route.c b/uClinux-2.4.31-uc0/net/rose/rose_route.c
new file mode 100644
index 0000000..dbc3e84
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/rose/rose_route.c
@@ -0,0 +1,1156 @@
+/*
+ * ROSE release 003
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * ROSE 001 Jonathan(G4KLX) Cloned from nr_route.c.
+ * Terry(VK2KTJ) Added support for variable length
+ * address masks.
+ * ROSE 002 Jonathan(G4KLX) Uprated through routing of packets.
+ * Routing loop detection.
+ * ROSE 003 Jonathan(G4KLX) New timer architecture.
+ * Added use count to neighbours.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <net/arp.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <linux/fcntl.h>
+#include <linux/termios.h> /* For TIOCINQ/OUTQ */
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/netfilter.h>
+#include <linux/init.h>
+#include <net/rose.h>
+
+static unsigned int rose_neigh_no = 1;
+
+static struct rose_node *rose_node_list;
+static struct rose_neigh *rose_neigh_list;
+static struct rose_route *rose_route_list;
+
+struct rose_neigh *rose_loopback_neigh;
+
+static void rose_remove_neigh(struct rose_neigh *);
+
+/*
+ * Add a new route to a node, and in the process add the node and the
+ * neighbour if it is new.
+ */
+static int rose_add_node(struct rose_route_struct *rose_route, struct net_device *dev)
+{
+ struct rose_node *rose_node, *rose_tmpn, *rose_tmpp;
+ struct rose_neigh *rose_neigh;
+ unsigned long flags;
+ int i;
+
+ for (rose_node = rose_node_list; rose_node != NULL; rose_node = rose_node->next)
+ if ((rose_node->mask == rose_route->mask) && (rosecmpm(&rose_route->address, &rose_node->address, rose_route->mask) == 0))
+ break;
+
+ if (rose_node != NULL && rose_node->loopback)
+ return -EINVAL;
+
+ for (rose_neigh = rose_neigh_list; rose_neigh != NULL; rose_neigh = rose_neigh->next)
+ if (ax25cmp(&rose_route->neighbour, &rose_neigh->callsign) == 0 && rose_neigh->dev == dev)
+ break;
+
+ if (rose_neigh == NULL) {
+ if ((rose_neigh = kmalloc(sizeof(*rose_neigh), GFP_ATOMIC)) == NULL)
+ return -ENOMEM;
+
+ rose_neigh->callsign = rose_route->neighbour;
+ rose_neigh->digipeat = NULL;
+ rose_neigh->ax25 = NULL;
+ rose_neigh->dev = dev;
+ rose_neigh->count = 0;
+ rose_neigh->use = 0;
+ rose_neigh->dce_mode = 0;
+ rose_neigh->loopback = 0;
+ rose_neigh->number = rose_neigh_no++;
+ rose_neigh->restarted = 0;
+
+ skb_queue_head_init(&rose_neigh->queue);
+
+ init_timer(&rose_neigh->ftimer);
+ init_timer(&rose_neigh->t0timer);
+
+ if (rose_route->ndigis != 0) {
+ if ((rose_neigh->digipeat = kmalloc(sizeof(ax25_digi), GFP_KERNEL)) == NULL) {
+ kfree(rose_neigh);
+ return -ENOMEM;
+ }
+
+ rose_neigh->digipeat->ndigi = rose_route->ndigis;
+ rose_neigh->digipeat->lastrepeat = -1;
+
+ for (i = 0; i < rose_route->ndigis; i++) {
+ rose_neigh->digipeat->calls[i] = rose_route->digipeaters[i];
+ rose_neigh->digipeat->repeated[i] = 0;
+ }
+ }
+
+ save_flags(flags); cli();
+ rose_neigh->next = rose_neigh_list;
+ rose_neigh_list = rose_neigh;
+ restore_flags(flags);
+ }
+
+ /*
+ * This is a new node to be inserted into the list. Find where it needs
+ * to be inserted into the list, and insert it. We want to be sure
+ * to order the list in descending order of mask size to ensure that
+ * later when we are searching this list the first match will be the
+ * best match.
+ */
+ if (rose_node == NULL) {
+ rose_tmpn = rose_node_list;
+ rose_tmpp = NULL;
+
+ while (rose_tmpn != NULL) {
+ if (rose_tmpn->mask > rose_route->mask) {
+ rose_tmpp = rose_tmpn;
+ rose_tmpn = rose_tmpn->next;
+ } else {
+ break;
+ }
+ }
+
+ /* create new node */
+ if ((rose_node = kmalloc(sizeof(*rose_node), GFP_ATOMIC)) == NULL)
+ return -ENOMEM;
+
+ rose_node->address = rose_route->address;
+ rose_node->mask = rose_route->mask;
+ rose_node->count = 1;
+ rose_node->loopback = 0;
+ rose_node->neighbour[0] = rose_neigh;
+
+ save_flags(flags); cli();
+
+ if (rose_tmpn == NULL) {
+ if (rose_tmpp == NULL) { /* Empty list */
+ rose_node_list = rose_node;
+ rose_node->next = NULL;
+ } else {
+ rose_tmpp->next = rose_node;
+ rose_node->next = NULL;
+ }
+ } else {
+ if (rose_tmpp == NULL) { /* 1st node */
+ rose_node->next = rose_node_list;
+ rose_node_list = rose_node;
+ } else {
+ rose_tmpp->next = rose_node;
+ rose_node->next = rose_tmpn;
+ }
+ }
+
+ restore_flags(flags);
+
+ rose_neigh->count++;
+
+ return 0;
+ }
+
+ /* We have space, slot it in */
+ if (rose_node->count < 3) {
+ rose_node->neighbour[rose_node->count] = rose_neigh;
+ rose_node->count++;
+ rose_neigh->count++;
+ }
+
+ return 0;
+}
+
+static void rose_remove_node(struct rose_node *rose_node)
+{
+ struct rose_node *s;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ if ((s = rose_node_list) == rose_node) {
+ rose_node_list = rose_node->next;
+ restore_flags(flags);
+ kfree(rose_node);
+ return;
+ }
+
+ while (s != NULL && s->next != NULL) {
+ if (s->next == rose_node) {
+ s->next = rose_node->next;
+ restore_flags(flags);
+ kfree(rose_node);
+ return;
+ }
+
+ s = s->next;
+ }
+
+ restore_flags(flags);
+}
+
+static void rose_remove_neigh(struct rose_neigh *rose_neigh)
+{
+ struct rose_neigh *s;
+ unsigned long flags;
+
+ rose_stop_ftimer(rose_neigh);
+ rose_stop_t0timer(rose_neigh);
+
+ skb_queue_purge(&rose_neigh->queue);
+
+ save_flags(flags); cli();
+
+ if ((s = rose_neigh_list) == rose_neigh) {
+ rose_neigh_list = rose_neigh->next;
+ restore_flags(flags);
+ if (rose_neigh->digipeat != NULL)
+ kfree(rose_neigh->digipeat);
+ kfree(rose_neigh);
+ return;
+ }
+
+ while (s != NULL && s->next != NULL) {
+ if (s->next == rose_neigh) {
+ s->next = rose_neigh->next;
+ restore_flags(flags);
+ if (rose_neigh->digipeat != NULL)
+ kfree(rose_neigh->digipeat);
+ kfree(rose_neigh);
+ return;
+ }
+
+ s = s->next;
+ }
+
+ restore_flags(flags);
+}
+
+static void rose_remove_route(struct rose_route *rose_route)
+{
+ struct rose_route *s;
+ unsigned long flags;
+
+ if (rose_route->neigh1 != NULL)
+ rose_route->neigh1->use--;
+
+ if (rose_route->neigh2 != NULL)
+ rose_route->neigh2->use--;
+
+ save_flags(flags); cli();
+
+ if ((s = rose_route_list) == rose_route) {
+ rose_route_list = rose_route->next;
+ restore_flags(flags);
+ kfree(rose_route);
+ return;
+ }
+
+ while (s != NULL && s->next != NULL) {
+ if (s->next == rose_route) {
+ s->next = rose_route->next;
+ restore_flags(flags);
+ kfree(rose_route);
+ return;
+ }
+
+ s = s->next;
+ }
+
+ restore_flags(flags);
+}
+
+/*
+ * "Delete" a node. Strictly speaking remove a route to a node. The node
+ * is only deleted if no routes are left to it.
+ */
+static int rose_del_node(struct rose_route_struct *rose_route, struct net_device *dev)
+{
+ struct rose_node *rose_node;
+ struct rose_neigh *rose_neigh;
+ int i;
+
+ for (rose_node = rose_node_list; rose_node != NULL; rose_node = rose_node->next)
+ if ((rose_node->mask == rose_route->mask) && (rosecmpm(&rose_route->address, &rose_node->address, rose_route->mask) == 0))
+ break;
+
+ if (rose_node == NULL) return -EINVAL;
+
+ if (rose_node->loopback) return -EINVAL;
+
+ for (rose_neigh = rose_neigh_list; rose_neigh != NULL; rose_neigh = rose_neigh->next)
+ if (ax25cmp(&rose_route->neighbour, &rose_neigh->callsign) == 0 && rose_neigh->dev == dev)
+ break;
+
+ if (rose_neigh == NULL) return -EINVAL;
+
+ for (i = 0; i < rose_node->count; i++) {
+ if (rose_node->neighbour[i] == rose_neigh) {
+ rose_neigh->count--;
+
+ if (rose_neigh->count == 0 && rose_neigh->use == 0)
+ rose_remove_neigh(rose_neigh);
+
+ rose_node->count--;
+
+ if (rose_node->count == 0) {
+ rose_remove_node(rose_node);
+ } else {
+ switch (i) {
+ case 0:
+ rose_node->neighbour[0] = rose_node->neighbour[1];
+ case 1:
+ rose_node->neighbour[1] = rose_node->neighbour[2];
+ case 2:
+ break;
+ }
+ }
+
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/*
+ * Add the loopback neighbour.
+ */
+int rose_add_loopback_neigh(void)
+{
+ unsigned long flags;
+
+ if ((rose_loopback_neigh = kmalloc(sizeof(struct rose_neigh), GFP_ATOMIC)) == NULL)
+ return -ENOMEM;
+
+ rose_loopback_neigh->callsign = null_ax25_address;
+ rose_loopback_neigh->digipeat = NULL;
+ rose_loopback_neigh->ax25 = NULL;
+ rose_loopback_neigh->dev = NULL;
+ rose_loopback_neigh->count = 0;
+ rose_loopback_neigh->use = 0;
+ rose_loopback_neigh->dce_mode = 1;
+ rose_loopback_neigh->loopback = 1;
+ rose_loopback_neigh->number = rose_neigh_no++;
+ rose_loopback_neigh->restarted = 1;
+
+ skb_queue_head_init(&rose_loopback_neigh->queue);
+
+ init_timer(&rose_loopback_neigh->ftimer);
+ init_timer(&rose_loopback_neigh->t0timer);
+
+ save_flags(flags); cli();
+ rose_loopback_neigh->next = rose_neigh_list;
+ rose_neigh_list = rose_loopback_neigh;
+ restore_flags(flags);
+
+ return 0;
+}
+
+/*
+ * Add a loopback node.
+ */
+int rose_add_loopback_node(rose_address *address)
+{
+ struct rose_node *rose_node;
+ unsigned long flags;
+
+ for (rose_node = rose_node_list; rose_node != NULL; rose_node = rose_node->next)
+ if ((rose_node->mask == 10) && (rosecmpm(address, &rose_node->address, 10) == 0) && rose_node->loopback)
+ break;
+
+ if (rose_node != NULL) return 0;
+
+ if ((rose_node = kmalloc(sizeof(*rose_node), GFP_ATOMIC)) == NULL)
+ return -ENOMEM;
+
+ rose_node->address = *address;
+ rose_node->mask = 10;
+ rose_node->count = 1;
+ rose_node->loopback = 1;
+ rose_node->neighbour[0] = rose_loopback_neigh;
+
+ /* Insert at the head of list. Address is always mask=10 */
+ save_flags(flags); cli();
+ rose_node->next = rose_node_list;
+ rose_node_list = rose_node;
+ restore_flags(flags);
+
+ rose_loopback_neigh->count++;
+
+ return 0;
+}
+
+/*
+ * Delete a loopback node.
+ */
+void rose_del_loopback_node(rose_address *address)
+{
+ struct rose_node *rose_node;
+
+ for (rose_node = rose_node_list; rose_node != NULL; rose_node = rose_node->next)
+ if ((rose_node->mask == 10) && (rosecmpm(address, &rose_node->address, 10) == 0) && rose_node->loopback)
+ break;
+
+ if (rose_node == NULL) return;
+
+ rose_remove_node(rose_node);
+
+ rose_loopback_neigh->count--;
+}
+
+/*
+ * A device has been removed. Remove its routes and neighbours.
+ */
+void rose_rt_device_down(struct net_device *dev)
+{
+ struct rose_neigh *s, *rose_neigh = rose_neigh_list;
+ struct rose_node *t, *rose_node;
+ int i;
+
+ while (rose_neigh != NULL) {
+ s = rose_neigh;
+ rose_neigh = rose_neigh->next;
+
+ if (s->dev == dev) {
+ rose_node = rose_node_list;
+
+ while (rose_node != NULL) {
+ t = rose_node;
+ rose_node = rose_node->next;
+
+ for (i = 0; i < t->count; i++) {
+ if (t->neighbour[i] == s) {
+ t->count--;
+
+ switch (i) {
+ case 0:
+ t->neighbour[0] = t->neighbour[1];
+ case 1:
+ t->neighbour[1] = t->neighbour[2];
+ case 2:
+ break;
+ }
+ }
+ }
+
+ if (t->count <= 0)
+ rose_remove_node(t);
+ }
+
+ rose_remove_neigh(s);
+ }
+ }
+}
+
+/*
+ * A device has been removed. Remove its links.
+ */
+void rose_route_device_down(struct net_device *dev)
+{
+ struct rose_route *s, *rose_route = rose_route_list;
+
+ while (rose_route != NULL) {
+ s = rose_route;
+ rose_route = rose_route->next;
+
+ if (s->neigh1->dev == dev || s->neigh2->dev == dev)
+ rose_remove_route(s);
+ }
+}
+
+/*
+ * Clear all nodes and neighbours out, except for neighbours with
+ * active connections going through them.
+ * Do not clear loopback neighbour and nodes.
+ */
+static int rose_clear_routes(void)
+{
+ struct rose_neigh *s, *rose_neigh = rose_neigh_list;
+ struct rose_node *t, *rose_node = rose_node_list;
+
+ while (rose_node != NULL) {
+ t = rose_node;
+ rose_node = rose_node->next;
+ if (!t->loopback)
+ rose_remove_node(t);
+ }
+
+ while (rose_neigh != NULL) {
+ s = rose_neigh;
+ rose_neigh = rose_neigh->next;
+
+ if (s->use == 0 && !s->loopback) {
+ s->count = 0;
+ rose_remove_neigh(s);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Check that the device given is a valid AX.25 interface that is "up".
+ */
+struct net_device *rose_ax25_dev_get(char *devname)
+{
+ struct net_device *dev;
+
+ if ((dev = dev_get_by_name(devname)) == NULL)
+ return NULL;
+
+ if ((dev->flags & IFF_UP) && dev->type == ARPHRD_AX25)
+ return dev;
+
+ dev_put(dev);
+ return NULL;
+}
+
+/*
+ * Find the first active ROSE device, usually "rose0".
+ */
+struct net_device *rose_dev_first(void)
+{
+ struct net_device *dev, *first = NULL;
+
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev != NULL; dev = dev->next) {
+ if ((dev->flags & IFF_UP) && dev->type == ARPHRD_ROSE)
+ if (first == NULL || strncmp(dev->name, first->name, 3) < 0)
+ first = dev;
+ }
+ read_unlock(&dev_base_lock);
+
+ return first;
+}
+
+/*
+ * Find the ROSE device for the given address.
+ */
+struct net_device *rose_dev_get(rose_address *addr)
+{
+ struct net_device *dev;
+
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev != NULL; dev = dev->next) {
+ if ((dev->flags & IFF_UP) && dev->type == ARPHRD_ROSE && rosecmp(addr, (rose_address *)dev->dev_addr) == 0) {
+ dev_hold(dev);
+ goto out;
+ }
+ }
+out:
+ read_unlock(&dev_base_lock);
+ return dev;
+}
+
+static int rose_dev_exists(rose_address *addr)
+{
+ struct net_device *dev;
+
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev != NULL; dev = dev->next) {
+ if ((dev->flags & IFF_UP) && dev->type == ARPHRD_ROSE && rosecmp(addr, (rose_address *)dev->dev_addr) == 0)
+ goto out;
+ }
+out:
+ read_unlock(&dev_base_lock);
+ return dev != NULL;
+}
+
+
+
+
+struct rose_route *rose_route_free_lci(unsigned int lci, struct rose_neigh *neigh)
+{
+ struct rose_route *rose_route;
+
+ for (rose_route = rose_route_list; rose_route != NULL; rose_route = rose_route->next)
+ if ((rose_route->neigh1 == neigh && rose_route->lci1 == lci) ||
+ (rose_route->neigh2 == neigh && rose_route->lci2 == lci))
+ return rose_route;
+
+ return NULL;
+}
+
+/*
+ * Find a neighbour given a ROSE address.
+ */
+struct rose_neigh *rose_get_neigh(rose_address *addr, unsigned char *cause, unsigned char *diagnostic)
+{
+ struct rose_node *node;
+ int failed = 0;
+ int i;
+
+ for (node = rose_node_list; node != NULL; node = node->next) {
+ if (rosecmpm(addr, &node->address, node->mask) == 0) {
+ for (i = 0; i < node->count; i++) {
+ if (!rose_ftimer_running(node->neighbour[i])) {
+ return node->neighbour[i]; }
+ else
+ failed = 1;
+ }
+ break;
+ }
+ }
+
+ if (failed) {
+ *cause = ROSE_OUT_OF_ORDER;
+ *diagnostic = 0;
+ } else {
+ *cause = ROSE_NOT_OBTAINABLE;
+ *diagnostic = 0;
+ }
+
+ return NULL;
+}
+
+/*
+ * Handle the ioctls that control the routing functions.
+ */
+int rose_rt_ioctl(unsigned int cmd, void *arg)
+{
+ struct rose_route_struct rose_route;
+ struct net_device *dev;
+ int err;
+
+ switch (cmd) {
+
+ case SIOCADDRT:
+ if (copy_from_user(&rose_route, arg, sizeof(struct rose_route_struct)))
+ return -EFAULT;
+ if ((dev = rose_ax25_dev_get(rose_route.device)) == NULL)
+ return -EINVAL;
+ if (rose_dev_exists(&rose_route.address)) { /* Can't add routes to ourself */
+ dev_put(dev);
+ return -EINVAL;
+ }
+ if (rose_route.mask > 10) /* Mask can't be more than 10 digits */
+ return -EINVAL;
+
+ if(rose_route.ndigis > 8) /* No more than 8 digipeats */
+ return -EINVAL;
+
+ err = rose_add_node(&rose_route, dev);
+ dev_put(dev);
+ return err;
+
+ case SIOCDELRT:
+ if (copy_from_user(&rose_route, arg, sizeof(struct rose_route_struct)))
+ return -EFAULT;
+ if ((dev = rose_ax25_dev_get(rose_route.device)) == NULL)
+ return -EINVAL;
+ err = rose_del_node(&rose_route, dev);
+ dev_put(dev);
+ return err;
+
+
+ case SIOCRSCLRRT:
+ return rose_clear_routes();
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void rose_del_route_by_neigh(struct rose_neigh *rose_neigh)
+{
+ struct rose_route *rose_route, *s;
+
+ rose_neigh->restarted = 0;
+
+ rose_stop_t0timer(rose_neigh);
+ rose_start_ftimer(rose_neigh);
+
+ skb_queue_purge(&rose_neigh->queue);
+
+ rose_route = rose_route_list;
+
+ while (rose_route != NULL) {
+ if ((rose_route->neigh1 == rose_neigh && rose_route->neigh2 == rose_neigh) ||
+ (rose_route->neigh1 == rose_neigh && rose_route->neigh2 == NULL) ||
+ (rose_route->neigh2 == rose_neigh && rose_route->neigh1 == NULL)) {
+ s = rose_route->next;
+ rose_remove_route(rose_route);
+ rose_route = s;
+ continue;
+ }
+
+ if (rose_route->neigh1 == rose_neigh) {
+ rose_route->neigh1->use--;
+ rose_route->neigh1 = NULL;
+ rose_transmit_clear_request(rose_route->neigh2, rose_route->lci2, ROSE_OUT_OF_ORDER, 0);
+ }
+
+ if (rose_route->neigh2 == rose_neigh) {
+ rose_route->neigh2->use--;
+ rose_route->neigh2 = NULL;
+ rose_transmit_clear_request(rose_route->neigh1, rose_route->lci1, ROSE_OUT_OF_ORDER, 0);
+ }
+
+ rose_route = rose_route->next;
+ }
+}
+
+/*
+ * A level 2 link has timed out, therefore it appears to be a poor link,
+ * then don't use that neighbour until it is reset. Blow away all through
+ * routes and connections using this route.
+ */
+void rose_link_failed(ax25_cb *ax25, int reason)
+{
+ struct rose_neigh *rose_neigh;
+
+ for (rose_neigh = rose_neigh_list; rose_neigh != NULL; rose_neigh = rose_neigh->next)
+ if (rose_neigh->ax25 == ax25)
+ break;
+
+ if (rose_neigh == NULL) return;
+
+ rose_neigh->ax25 = NULL;
+
+ rose_del_route_by_neigh(rose_neigh);
+ rose_kill_by_neigh(rose_neigh);
+}
+
+/*
+ * A device has been "downed" remove its link status. Blow away all
+ * through routes and connections that use this device.
+ */
+void rose_link_device_down(struct net_device *dev)
+{
+ struct rose_neigh *rose_neigh;
+
+ for (rose_neigh = rose_neigh_list; rose_neigh != NULL; rose_neigh = rose_neigh->next) {
+ if (rose_neigh->dev == dev) {
+ rose_del_route_by_neigh(rose_neigh);
+ rose_kill_by_neigh(rose_neigh);
+ }
+ }
+}
+
+/*
+ * Route a frame to an appropriate AX.25 connection.
+ */
+int rose_route_frame(struct sk_buff *skb, ax25_cb *ax25)
+{
+ struct rose_neigh *rose_neigh, *new_neigh;
+ struct rose_route *rose_route;
+ struct rose_facilities_struct facilities;
+ rose_address *src_addr, *dest_addr;
+ struct sock *sk;
+ unsigned short frametype;
+ unsigned int lci, new_lci;
+ unsigned char cause, diagnostic;
+ struct net_device *dev;
+ unsigned long flags;
+ int len;
+
+#if 0
+ if (call_in_firewall(PF_ROSE, skb->dev, skb->data, NULL, &skb) != FW_ACCEPT)
+ return 0;
+#endif
+
+ frametype = skb->data[2];
+ lci = ((skb->data[0] << 8) & 0xF00) + ((skb->data[1] << 0) & 0x0FF);
+ src_addr = (rose_address *)(skb->data + 9);
+ dest_addr = (rose_address *)(skb->data + 4);
+
+ for (rose_neigh = rose_neigh_list; rose_neigh != NULL; rose_neigh = rose_neigh->next)
+ if (ax25cmp(&ax25->dest_addr, &rose_neigh->callsign) == 0 && ax25->ax25_dev->dev == rose_neigh->dev)
+ break;
+
+ if (rose_neigh == NULL) {
+ printk("rose_route : unknown neighbour or device %s\n", ax2asc(&ax25->dest_addr));
+ return 0;
+ }
+
+ /*
+ * Obviously the link is working, halt the ftimer.
+ */
+ rose_stop_ftimer(rose_neigh);
+
+ /*
+ * LCI of zero is always for us, and its always a restart
+ * frame.
+ */
+ if (lci == 0) {
+ rose_link_rx_restart(skb, rose_neigh, frametype);
+ return 0;
+ }
+
+ /*
+ * Find an existing socket.
+ */
+ if ((sk = rose_find_socket(lci, rose_neigh)) != NULL) {
+ if (frametype == ROSE_CALL_REQUEST) {
+ /* Remove an existing unused socket */
+ rose_clear_queues(sk);
+ sk->protinfo.rose->cause = ROSE_NETWORK_CONGESTION;
+ sk->protinfo.rose->diagnostic = 0;
+ sk->protinfo.rose->neighbour->use--;
+ sk->protinfo.rose->neighbour = NULL;
+ sk->protinfo.rose->lci = 0;
+ sk->protinfo.rose->state = ROSE_STATE_0;
+ sk->state = TCP_CLOSE;
+ sk->err = 0;
+ sk->shutdown |= SEND_SHUTDOWN;
+ if (!sk->dead)
+ sk->state_change(sk);
+ sk->dead = 1;
+ }
+ else {
+ skb->h.raw = skb->data;
+ return rose_process_rx_frame(sk, skb);
+ }
+ }
+
+ /*
+ * Is is a Call Request and is it for us ?
+ */
+ if (frametype == ROSE_CALL_REQUEST)
+ if ((dev = rose_dev_get(dest_addr)) != NULL) {
+ int err = rose_rx_call_request(skb, dev, rose_neigh, lci);
+ dev_put(dev);
+ return err;
+ }
+
+ if (!sysctl_rose_routing_control) {
+ rose_transmit_clear_request(rose_neigh, lci, ROSE_NOT_OBTAINABLE, 0);
+ return 0;
+ }
+
+ /*
+ * Route it to the next in line if we have an entry for it.
+ */
+ for (rose_route = rose_route_list; rose_route != NULL; rose_route = rose_route->next) {
+ if (rose_route->lci1 == lci && rose_route->neigh1 == rose_neigh) {
+ if (frametype == ROSE_CALL_REQUEST) {
+ /* F6FBB - Remove an existing unused route */
+ rose_remove_route(rose_route);
+ break;
+ } else if (rose_route->neigh2 != NULL) {
+ skb->data[0] &= 0xF0;
+ skb->data[0] |= (rose_route->lci2 >> 8) & 0x0F;
+ skb->data[1] = (rose_route->lci2 >> 0) & 0xFF;
+ rose_transmit_link(skb, rose_route->neigh2);
+ if (frametype == ROSE_CLEAR_CONFIRMATION)
+ rose_remove_route(rose_route);
+ return 1;
+ } else {
+ if (frametype == ROSE_CLEAR_CONFIRMATION)
+ rose_remove_route(rose_route);
+ return 0;
+ }
+ }
+ if (rose_route->lci2 == lci && rose_route->neigh2 == rose_neigh) {
+ if (frametype == ROSE_CALL_REQUEST) {
+ /* F6FBB - Remove an existing unused route */
+ rose_remove_route(rose_route);
+ break;
+ } else if (rose_route->neigh1 != NULL) {
+ skb->data[0] &= 0xF0;
+ skb->data[0] |= (rose_route->lci1 >> 8) & 0x0F;
+ skb->data[1] = (rose_route->lci1 >> 0) & 0xFF;
+ rose_transmit_link(skb, rose_route->neigh1);
+ if (frametype == ROSE_CLEAR_CONFIRMATION)
+ rose_remove_route(rose_route);
+ return 1;
+ } else {
+ if (frametype == ROSE_CLEAR_CONFIRMATION)
+ rose_remove_route(rose_route);
+ return 0;
+ }
+ }
+ }
+
+ /*
+ * We know that:
+ * 1. The frame isn't for us,
+ * 2. It isn't "owned" by any existing route.
+ */
+ if (frametype != ROSE_CALL_REQUEST) /* XXX */
+ return 0;
+
+ len = (((skb->data[3] >> 4) & 0x0F) + 1) / 2;
+ len += (((skb->data[3] >> 0) & 0x0F) + 1) / 2;
+
+ memset(&facilities, 0x00, sizeof(struct rose_facilities_struct));
+
+ if (!rose_parse_facilities(skb->data + len + 4, &facilities)) {
+ rose_transmit_clear_request(rose_neigh, lci, ROSE_INVALID_FACILITY, 76);
+ return 0;
+ }
+
+ /*
+ * Check for routing loops.
+ */
+ for (rose_route = rose_route_list; rose_route != NULL; rose_route = rose_route->next) {
+ if (rose_route->rand == facilities.rand &&
+ rosecmp(src_addr, &rose_route->src_addr) == 0 &&
+ ax25cmp(&facilities.dest_call, &rose_route->src_call) == 0 &&
+ ax25cmp(&facilities.source_call, &rose_route->dest_call) == 0) {
+ rose_transmit_clear_request(rose_neigh, lci, ROSE_NOT_OBTAINABLE, 120);
+ return 0;
+ }
+ }
+
+ if ((new_neigh = rose_get_neigh(dest_addr, &cause, &diagnostic)) == NULL) {
+ rose_transmit_clear_request(rose_neigh, lci, cause, diagnostic);
+ return 0;
+ }
+
+ if ((new_lci = rose_new_lci(new_neigh)) == 0) {
+ rose_transmit_clear_request(rose_neigh, lci, ROSE_NETWORK_CONGESTION, 71);
+ return 0;
+ }
+
+ if ((rose_route = kmalloc(sizeof(*rose_route), GFP_ATOMIC)) == NULL) {
+ rose_transmit_clear_request(rose_neigh, lci, ROSE_NETWORK_CONGESTION, 120);
+ return 0;
+ }
+
+ rose_route->lci1 = lci;
+ rose_route->src_addr = *src_addr;
+ rose_route->dest_addr = *dest_addr;
+ rose_route->src_call = facilities.dest_call;
+ rose_route->dest_call = facilities.source_call;
+ rose_route->rand = facilities.rand;
+ rose_route->neigh1 = rose_neigh;
+ rose_route->lci2 = new_lci;
+ rose_route->neigh2 = new_neigh;
+
+ rose_route->neigh1->use++;
+ rose_route->neigh2->use++;
+
+ save_flags(flags); cli();
+ rose_route->next = rose_route_list;
+ rose_route_list = rose_route;
+ restore_flags(flags);
+
+ skb->data[0] &= 0xF0;
+ skb->data[0] |= (rose_route->lci2 >> 8) & 0x0F;
+ skb->data[1] = (rose_route->lci2 >> 0) & 0xFF;
+
+ rose_transmit_link(skb, rose_route->neigh2);
+
+ return 1;
+}
+
+int rose_nodes_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct rose_node *rose_node;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+ int i;
+
+ cli();
+
+ len += sprintf(buffer, "address mask n neigh neigh neigh\n");
+
+ for (rose_node = rose_node_list; rose_node != NULL; rose_node = rose_node->next) {
+ /* if (rose_node->loopback) {
+ len += sprintf(buffer + len, "%-10s %04d 1 loopback\n",
+ rose2asc(&rose_node->address),
+ rose_node->mask);
+ } else { */
+ len += sprintf(buffer + len, "%-10s %04d %d",
+ rose2asc(&rose_node->address),
+ rose_node->mask,
+ rose_node->count);
+
+ for (i = 0; i < rose_node->count; i++)
+ len += sprintf(buffer + len, " %05d",
+ rose_node->neighbour[i]->number);
+
+ len += sprintf(buffer + len, "\n");
+ /* } */
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ break;
+ }
+
+ sti();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return len;
+}
+
+int rose_neigh_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct rose_neigh *rose_neigh;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+ int i;
+
+ cli();
+
+ len += sprintf(buffer, "addr callsign dev count use mode restart t0 tf digipeaters\n");
+
+ for (rose_neigh = rose_neigh_list; rose_neigh != NULL; rose_neigh = rose_neigh->next) {
+ /* if (!rose_neigh->loopback) { */
+ len += sprintf(buffer + len, "%05d %-9s %-4s %3d %3d %3s %3s %3lu %3lu",
+ rose_neigh->number,
+ (rose_neigh->loopback) ? "RSLOOP-0" : ax2asc(&rose_neigh->callsign),
+ rose_neigh->dev ? rose_neigh->dev->name : "???",
+ rose_neigh->count,
+ rose_neigh->use,
+ (rose_neigh->dce_mode) ? "DCE" : "DTE",
+ (rose_neigh->restarted) ? "yes" : "no",
+ ax25_display_timer(&rose_neigh->t0timer) / HZ,
+ ax25_display_timer(&rose_neigh->ftimer) / HZ);
+
+ if (rose_neigh->digipeat != NULL) {
+ for (i = 0; i < rose_neigh->digipeat->ndigi; i++)
+ len += sprintf(buffer + len, " %s", ax2asc(&rose_neigh->digipeat->calls[i]));
+ }
+
+ len += sprintf(buffer + len, "\n");
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ break;
+ /* } */
+ }
+
+ sti();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return len;
+}
+
+int rose_routes_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct rose_route *rose_route;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+
+ cli();
+
+ len += sprintf(buffer, "lci address callsign neigh <-> lci address callsign neigh\n");
+
+ for (rose_route = rose_route_list; rose_route != NULL; rose_route = rose_route->next) {
+ if (rose_route->neigh1 != NULL) {
+ len += sprintf(buffer + len, "%3.3X %-10s %-9s %05d ",
+ rose_route->lci1,
+ rose2asc(&rose_route->src_addr),
+ ax2asc(&rose_route->src_call),
+ rose_route->neigh1->number);
+ } else {
+ len += sprintf(buffer + len, "000 * * 00000 ");
+ }
+
+ if (rose_route->neigh2 != NULL) {
+ len += sprintf(buffer + len, "%3.3X %-10s %-9s %05d\n",
+ rose_route->lci2,
+ rose2asc(&rose_route->dest_addr),
+ ax2asc(&rose_route->dest_call),
+ rose_route->neigh2->number);
+ } else {
+ len += sprintf(buffer + len, "000 * * 00000\n");
+ }
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ break;
+ }
+
+ sti();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return len;
+}
+
+/*
+ * Release all memory associated with ROSE routing structures.
+ */
+void __exit rose_rt_free(void)
+{
+ struct rose_neigh *s, *rose_neigh = rose_neigh_list;
+ struct rose_node *t, *rose_node = rose_node_list;
+ struct rose_route *u, *rose_route = rose_route_list;
+
+ while (rose_neigh != NULL) {
+ s = rose_neigh;
+ rose_neigh = rose_neigh->next;
+
+ rose_remove_neigh(s);
+ }
+
+ while (rose_node != NULL) {
+ t = rose_node;
+ rose_node = rose_node->next;
+
+ rose_remove_node(t);
+ }
+
+ while (rose_route != NULL) {
+ u = rose_route;
+ rose_route = rose_route->next;
+
+ rose_remove_route(u);
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/rose/rose_subr.c b/uClinux-2.4.31-uc0/net/rose/rose_subr.c
new file mode 100644
index 0000000..36f5a8c
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/rose/rose_subr.c
@@ -0,0 +1,544 @@
+/*
+ * ROSE release 003
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * ROSE 001 Jonathan(G4KLX) Cloned from nr_subr.c
+ * ROSE 002 Jonathan(G4KLX) Centralised disconnect processing.
+ * ROSE 003 Jonathan(G4KLX) Added use count to neighbours.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/rose.h>
+
+/*
+ * This routine purges all of the queues of frames.
+ */
+void rose_clear_queues(struct sock *sk)
+{
+ skb_queue_purge(&sk->write_queue);
+ skb_queue_purge(&sk->protinfo.rose->ack_queue);
+}
+
+/*
+ * This routine purges the input queue of those frames that have been
+ * acknowledged. This replaces the boxes labelled "V(a) <- N(r)" on the
+ * SDL diagram.
+ */
+void rose_frames_acked(struct sock *sk, unsigned short nr)
+{
+ struct sk_buff *skb;
+
+ /*
+ * Remove all the ack-ed frames from the ack queue.
+ */
+ if (sk->protinfo.rose->va != nr) {
+ while (skb_peek(&sk->protinfo.rose->ack_queue) != NULL && sk->protinfo.rose->va != nr) {
+ skb = skb_dequeue(&sk->protinfo.rose->ack_queue);
+ kfree_skb(skb);
+ sk->protinfo.rose->va = (sk->protinfo.rose->va + 1) % ROSE_MODULUS;
+ }
+ }
+}
+
+void rose_requeue_frames(struct sock *sk)
+{
+ struct sk_buff *skb, *skb_prev = NULL;
+
+ /*
+ * Requeue all the un-ack-ed frames on the output queue to be picked
+ * up by rose_kick. This arrangement handles the possibility of an
+ * empty output queue.
+ */
+ while ((skb = skb_dequeue(&sk->protinfo.rose->ack_queue)) != NULL) {
+ if (skb_prev == NULL)
+ skb_queue_head(&sk->write_queue, skb);
+ else
+ skb_append(skb_prev, skb);
+ skb_prev = skb;
+ }
+}
+
+/*
+ * Validate that the value of nr is between va and vs. Return true or
+ * false for testing.
+ */
+int rose_validate_nr(struct sock *sk, unsigned short nr)
+{
+ unsigned short vc = sk->protinfo.rose->va;
+
+ while (vc != sk->protinfo.rose->vs) {
+ if (nr == vc) return 1;
+ vc = (vc + 1) % ROSE_MODULUS;
+ }
+
+ if (nr == sk->protinfo.rose->vs) return 1;
+
+ return 0;
+}
+
+/*
+ * This routine is called when the packet layer internally generates a
+ * control frame.
+ */
+void rose_write_internal(struct sock *sk, int frametype)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+ unsigned char lci1, lci2;
+ char buffer[100];
+ int len, faclen = 0;
+ int ax25_header_len = AX25_BPQ_HEADER_LEN + AX25_MAX_HEADER_LEN + 1;
+
+ len = ax25_header_len + ROSE_MIN_LEN;
+
+ switch (frametype) {
+ case ROSE_CALL_REQUEST:
+ len += 1 + ROSE_ADDR_LEN + ROSE_ADDR_LEN;
+ faclen = rose_create_facilities(buffer, sk->protinfo.rose);
+ len += faclen;
+ break;
+ case ROSE_CALL_ACCEPTED:
+ case ROSE_RESET_REQUEST:
+ len += 2;
+ break;
+ case ROSE_CLEAR_REQUEST:
+ len += 3;
+ /* facilities */
+ faclen = 3 + 2 + AX25_ADDR_LEN + 3 + ROSE_ADDR_LEN;
+ dptr = buffer;
+ *dptr++ = faclen-1; /* Facilities length */
+ *dptr++ = 0;
+ *dptr++ = FAC_NATIONAL;
+ *dptr++ = FAC_NATIONAL_FAIL_CALL;
+ *dptr++ = AX25_ADDR_LEN;
+ memcpy(dptr, &rose_callsign, AX25_ADDR_LEN);
+ dptr += AX25_ADDR_LEN;
+ *dptr++ = FAC_NATIONAL_FAIL_ADD;
+ *dptr++ = ROSE_ADDR_LEN + 1;
+ *dptr++ = ROSE_ADDR_LEN * 2;
+ memcpy(dptr, &sk->protinfo.rose->source_addr, ROSE_ADDR_LEN);
+ len += faclen;
+ break;
+ }
+
+ if ((skb = alloc_skb(len, GFP_ATOMIC)) == NULL)
+ return;
+
+ /*
+ * Space for AX.25 header and PID.
+ */
+ skb_reserve(skb, ax25_header_len);
+
+ dptr = skb_put(skb, len - ax25_header_len);
+
+ lci1 = (sk->protinfo.rose->lci >> 8) & 0x0F;
+ lci2 = (sk->protinfo.rose->lci >> 0) & 0xFF;
+
+ switch (frametype) {
+
+ case ROSE_CALL_REQUEST:
+ *dptr++ = ROSE_GFI | lci1;
+ *dptr++ = lci2;
+ *dptr++ = frametype;
+ *dptr++ = 0xAA;
+ memcpy(dptr, &sk->protinfo.rose->dest_addr, ROSE_ADDR_LEN);
+ dptr += ROSE_ADDR_LEN;
+ memcpy(dptr, &sk->protinfo.rose->source_addr, ROSE_ADDR_LEN);
+ dptr += ROSE_ADDR_LEN;
+ memcpy(dptr, buffer, faclen);
+ dptr += faclen;
+ break;
+
+ case ROSE_CALL_ACCEPTED:
+ *dptr++ = ROSE_GFI | lci1;
+ *dptr++ = lci2;
+ *dptr++ = frametype;
+ *dptr++ = 0x00; /* Address length */
+ *dptr++ = 0; /* Facilities length */
+ break;
+
+ case ROSE_CLEAR_REQUEST:
+ *dptr++ = ROSE_GFI | lci1;
+ *dptr++ = lci2;
+ *dptr++ = frametype;
+ *dptr++ = sk->protinfo.rose->cause;
+ *dptr++ = sk->protinfo.rose->diagnostic;
+ *dptr++ = 0x00; /* Address length */
+ memcpy(dptr, buffer, faclen);
+ dptr += faclen;
+ break;
+
+ case ROSE_RESET_REQUEST:
+ *dptr++ = ROSE_GFI | lci1;
+ *dptr++ = lci2;
+ *dptr++ = frametype;
+ *dptr++ = ROSE_DTE_ORIGINATED;
+ *dptr++ = 0;
+ break;
+
+ case ROSE_RR:
+ case ROSE_RNR:
+ *dptr++ = ROSE_GFI | lci1;
+ *dptr++ = lci2;
+ *dptr = frametype;
+ *dptr++ |= (sk->protinfo.rose->vr << 5) & 0xE0;
+ break;
+
+ case ROSE_CLEAR_CONFIRMATION:
+ case ROSE_RESET_CONFIRMATION:
+ *dptr++ = ROSE_GFI | lci1;
+ *dptr++ = lci2;
+ *dptr++ = frametype;
+ break;
+
+ default:
+ printk(KERN_ERR "ROSE: rose_write_internal - invalid frametype %02X\n", frametype);
+ kfree_skb(skb);
+ return;
+ }
+
+ rose_transmit_link(skb, sk->protinfo.rose->neighbour);
+}
+
+int rose_decode(struct sk_buff *skb, int *ns, int *nr, int *q, int *d, int *m)
+{
+ unsigned char *frame;
+
+ frame = skb->data;
+
+ *ns = *nr = *q = *d = *m = 0;
+
+ switch (frame[2]) {
+ case ROSE_CALL_REQUEST:
+ case ROSE_CALL_ACCEPTED:
+ case ROSE_CLEAR_REQUEST:
+ case ROSE_CLEAR_CONFIRMATION:
+ case ROSE_RESET_REQUEST:
+ case ROSE_RESET_CONFIRMATION:
+ return frame[2];
+ default:
+ break;
+ }
+
+ if ((frame[2] & 0x1F) == ROSE_RR ||
+ (frame[2] & 0x1F) == ROSE_RNR) {
+ *nr = (frame[2] >> 5) & 0x07;
+ return frame[2] & 0x1F;
+ }
+
+ if ((frame[2] & 0x01) == ROSE_DATA) {
+ *q = (frame[0] & ROSE_Q_BIT) == ROSE_Q_BIT;
+ *d = (frame[0] & ROSE_D_BIT) == ROSE_D_BIT;
+ *m = (frame[2] & ROSE_M_BIT) == ROSE_M_BIT;
+ *nr = (frame[2] >> 5) & 0x07;
+ *ns = (frame[2] >> 1) & 0x07;
+ return ROSE_DATA;
+ }
+
+ return ROSE_ILLEGAL;
+}
+
+static int rose_parse_national(unsigned char *p, struct rose_facilities_struct *facilities, int len)
+{
+ unsigned char *pt;
+ unsigned char l, lg, n = 0;
+ int fac_national_digis_received = 0;
+
+ do {
+ switch (*p & 0xC0) {
+ case 0x00:
+ p += 2;
+ n += 2;
+ len -= 2;
+ break;
+
+ case 0x40:
+ if (*p == FAC_NATIONAL_RAND)
+ facilities->rand = ((p[1] << 8) & 0xFF00) + ((p[2] << 0) & 0x00FF);
+ p += 3;
+ n += 3;
+ len -= 3;
+ break;
+
+ case 0x80:
+ p += 4;
+ n += 4;
+ len -= 4;
+ break;
+
+ case 0xC0:
+ l = p[1];
+ if (*p == FAC_NATIONAL_DEST_DIGI) {
+ if (!fac_national_digis_received) {
+ memcpy(&facilities->source_digis[0], p + 2, AX25_ADDR_LEN);
+ facilities->source_ndigis = 1;
+ }
+ }
+ else if (*p == FAC_NATIONAL_SRC_DIGI) {
+ if (!fac_national_digis_received) {
+ memcpy(&facilities->dest_digis[0], p + 2, AX25_ADDR_LEN);
+ facilities->dest_ndigis = 1;
+ }
+ }
+ else if (*p == FAC_NATIONAL_FAIL_CALL) {
+ memcpy(&facilities->fail_call, p + 2, AX25_ADDR_LEN);
+ }
+ else if (*p == FAC_NATIONAL_FAIL_ADD) {
+ memcpy(&facilities->fail_addr, p + 3, ROSE_ADDR_LEN);
+ }
+ else if (*p == FAC_NATIONAL_DIGIS) {
+ fac_national_digis_received = 1;
+ facilities->source_ndigis = 0;
+ facilities->dest_ndigis = 0;
+ for (pt = p + 2, lg = 0 ; lg < l ; pt += AX25_ADDR_LEN, lg += AX25_ADDR_LEN) {
+ if (pt[6] & AX25_HBIT)
+ memcpy(&facilities->dest_digis[facilities->dest_ndigis++], pt, AX25_ADDR_LEN);
+ else
+ memcpy(&facilities->source_digis[facilities->source_ndigis++], pt, AX25_ADDR_LEN);
+ }
+ }
+ p += l + 2;
+ n += l + 2;
+ len -= l + 2;
+ break;
+ }
+ } while (*p != 0x00 && len > 0);
+
+ return n;
+}
+
+static int rose_parse_ccitt(unsigned char *p, struct rose_facilities_struct *facilities, int len)
+{
+ unsigned char l, n = 0;
+ char callsign[11];
+
+ do {
+ switch (*p & 0xC0) {
+ case 0x00:
+ p += 2;
+ n += 2;
+ len -= 2;
+ break;
+
+ case 0x40:
+ p += 3;
+ n += 3;
+ len -= 3;
+ break;
+
+ case 0x80:
+ p += 4;
+ n += 4;
+ len -= 4;
+ break;
+
+ case 0xC0:
+ l = p[1];
+ if (*p == FAC_CCITT_DEST_NSAP) {
+ memcpy(&facilities->source_addr, p + 7, ROSE_ADDR_LEN);
+ memcpy(callsign, p + 12, l - 10);
+ callsign[l - 10] = '\0';
+ facilities->source_call = *asc2ax(callsign);
+ }
+ if (*p == FAC_CCITT_SRC_NSAP) {
+ memcpy(&facilities->dest_addr, p + 7, ROSE_ADDR_LEN);
+ memcpy(callsign, p + 12, l - 10);
+ callsign[l - 10] = '\0';
+ facilities->dest_call = *asc2ax(callsign);
+ }
+ p += l + 2;
+ n += l + 2;
+ len -= l + 2;
+ break;
+ }
+ } while (*p != 0x00 && len > 0);
+
+ return n;
+}
+
+int rose_parse_facilities(unsigned char *p, struct rose_facilities_struct *facilities)
+{
+ int facilities_len, len;
+
+ facilities_len = *p++;
+
+ if (facilities_len == 0)
+ return 0;
+
+ while (facilities_len > 0) {
+ if (*p == 0x00) {
+ facilities_len--;
+ p++;
+
+ switch (*p) {
+ case FAC_NATIONAL: /* National */
+ len = rose_parse_national(p + 1, facilities, facilities_len - 1);
+ facilities_len -= len + 1;
+ p += len + 1;
+ break;
+
+ case FAC_CCITT: /* CCITT */
+ len = rose_parse_ccitt(p + 1, facilities, facilities_len - 1);
+ facilities_len -= len + 1;
+ p += len + 1;
+ break;
+
+ default:
+ printk(KERN_DEBUG "ROSE: rose_parse_facilities - unknown facilities family %02X\n", *p);
+ facilities_len--;
+ p++;
+ break;
+ }
+ }
+ else break; /* Error in facilities format */
+ }
+
+ return 1;
+}
+
+int rose_create_facilities(unsigned char *buffer, rose_cb *rose)
+{
+ unsigned char *p = buffer + 1;
+ char *callsign;
+ int len, nb;
+
+ /* National Facilities */
+ if (rose->rand != 0 || rose->source_ndigis == 1 || rose->dest_ndigis == 1) {
+ *p++ = 0x00;
+ *p++ = FAC_NATIONAL;
+
+ if (rose->rand != 0) {
+ *p++ = FAC_NATIONAL_RAND;
+ *p++ = (rose->rand >> 8) & 0xFF;
+ *p++ = (rose->rand >> 0) & 0xFF;
+ }
+
+ /* Sent before older facilities */
+ if ((rose->source_ndigis > 0) || (rose->dest_ndigis > 0)) {
+ int maxdigi = 0;
+ *p++ = FAC_NATIONAL_DIGIS;
+ *p++ = AX25_ADDR_LEN * (rose->source_ndigis + rose->dest_ndigis);
+ for (nb = 0 ; nb < rose->source_ndigis ; nb++) {
+ if (++maxdigi >= ROSE_MAX_DIGIS)
+ break;
+ memcpy(p, &rose->source_digis[nb], AX25_ADDR_LEN);
+ p[6] |= AX25_HBIT;
+ p += AX25_ADDR_LEN;
+ }
+ for (nb = 0 ; nb < rose->dest_ndigis ; nb++) {
+ if (++maxdigi >= ROSE_MAX_DIGIS)
+ break;
+ memcpy(p, &rose->dest_digis[nb], AX25_ADDR_LEN);
+ p[6] &= ~AX25_HBIT;
+ p += AX25_ADDR_LEN;
+ }
+ }
+
+ /* For compatibility */
+ if (rose->source_ndigis > 0) {
+ *p++ = FAC_NATIONAL_SRC_DIGI;
+ *p++ = AX25_ADDR_LEN;
+ memcpy(p, &rose->source_digis[0], AX25_ADDR_LEN);
+ p += AX25_ADDR_LEN;
+ }
+
+ /* For compatibility */
+ if (rose->dest_ndigis > 0) {
+ *p++ = FAC_NATIONAL_DEST_DIGI;
+ *p++ = AX25_ADDR_LEN;
+ memcpy(p, &rose->dest_digis[0], AX25_ADDR_LEN);
+ p += AX25_ADDR_LEN;
+ }
+ }
+
+ *p++ = 0x00;
+ *p++ = FAC_CCITT;
+
+ *p++ = FAC_CCITT_DEST_NSAP;
+
+ callsign = ax2asc(&rose->dest_call);
+
+ *p++ = strlen(callsign) + 10;
+ *p++ = (strlen(callsign) + 9) * 2; /* ??? */
+
+ *p++ = 0x47; *p++ = 0x00; *p++ = 0x11;
+ *p++ = ROSE_ADDR_LEN * 2;
+ memcpy(p, &rose->dest_addr, ROSE_ADDR_LEN);
+ p += ROSE_ADDR_LEN;
+
+ memcpy(p, callsign, strlen(callsign));
+ p += strlen(callsign);
+
+ *p++ = FAC_CCITT_SRC_NSAP;
+
+ callsign = ax2asc(&rose->source_call);
+
+ *p++ = strlen(callsign) + 10;
+ *p++ = (strlen(callsign) + 9) * 2; /* ??? */
+
+ *p++ = 0x47; *p++ = 0x00; *p++ = 0x11;
+ *p++ = ROSE_ADDR_LEN * 2;
+ memcpy(p, &rose->source_addr, ROSE_ADDR_LEN);
+ p += ROSE_ADDR_LEN;
+
+ memcpy(p, callsign, strlen(callsign));
+ p += strlen(callsign);
+
+ len = p - buffer;
+ buffer[0] = len - 1;
+
+ return len;
+}
+
+void rose_disconnect(struct sock *sk, int reason, int cause, int diagnostic)
+{
+ rose_stop_timer(sk);
+ rose_stop_idletimer(sk);
+
+ rose_clear_queues(sk);
+
+ sk->protinfo.rose->lci = 0;
+ sk->protinfo.rose->state = ROSE_STATE_0;
+
+ if (cause != -1)
+ sk->protinfo.rose->cause = cause;
+
+ if (diagnostic != -1)
+ sk->protinfo.rose->diagnostic = diagnostic;
+
+ sk->state = TCP_CLOSE;
+ sk->err = reason;
+ sk->shutdown |= SEND_SHUTDOWN;
+
+ if (!sk->dead)
+ sk->state_change(sk);
+
+ sk->dead = 1;
+}
diff --git a/uClinux-2.4.31-uc0/net/rose/rose_timer.c b/uClinux-2.4.31-uc0/net/rose/rose_timer.c
new file mode 100644
index 0000000..8bd3042
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/rose/rose_timer.c
@@ -0,0 +1,207 @@
+/*
+ * ROSE release 003
+ *
+ * This code REQUIRES 2.1.15 or higher/ NET3.038
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * ROSE 001 Jonathan(G4KLX) Cloned from nr_timer.c
+ * ROSE 003 Jonathan(G4KLX) New timer architecture.
+ * Implemented idle timer.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <net/ax25.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/rose.h>
+
+static void rose_heartbeat_expiry(unsigned long);
+static void rose_timer_expiry(unsigned long);
+static void rose_idletimer_expiry(unsigned long);
+
+void rose_start_heartbeat(struct sock *sk)
+{
+ del_timer(&sk->timer);
+
+ sk->timer.data = (unsigned long)sk;
+ sk->timer.function = &rose_heartbeat_expiry;
+ sk->timer.expires = jiffies + 5 * HZ;
+
+ add_timer(&sk->timer);
+}
+
+void rose_start_t1timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.rose->timer);
+
+ sk->protinfo.rose->timer.data = (unsigned long)sk;
+ sk->protinfo.rose->timer.function = &rose_timer_expiry;
+ sk->protinfo.rose->timer.expires = jiffies + sk->protinfo.rose->t1;
+
+ add_timer(&sk->protinfo.rose->timer);
+}
+
+void rose_start_t2timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.rose->timer);
+
+ sk->protinfo.rose->timer.data = (unsigned long)sk;
+ sk->protinfo.rose->timer.function = &rose_timer_expiry;
+ sk->protinfo.rose->timer.expires = jiffies + sk->protinfo.rose->t2;
+
+ add_timer(&sk->protinfo.rose->timer);
+}
+
+void rose_start_t3timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.rose->timer);
+
+ sk->protinfo.rose->timer.data = (unsigned long)sk;
+ sk->protinfo.rose->timer.function = &rose_timer_expiry;
+ sk->protinfo.rose->timer.expires = jiffies + sk->protinfo.rose->t3;
+
+ add_timer(&sk->protinfo.rose->timer);
+}
+
+void rose_start_hbtimer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.rose->timer);
+
+ sk->protinfo.rose->timer.data = (unsigned long)sk;
+ sk->protinfo.rose->timer.function = &rose_timer_expiry;
+ sk->protinfo.rose->timer.expires = jiffies + sk->protinfo.rose->hb;
+
+ add_timer(&sk->protinfo.rose->timer);
+}
+
+void rose_start_idletimer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.rose->idletimer);
+
+ if (sk->protinfo.rose->idle > 0) {
+ sk->protinfo.rose->idletimer.data = (unsigned long)sk;
+ sk->protinfo.rose->idletimer.function = &rose_idletimer_expiry;
+ sk->protinfo.rose->idletimer.expires = jiffies + sk->protinfo.rose->idle;
+
+ add_timer(&sk->protinfo.rose->idletimer);
+ }
+}
+
+void rose_stop_heartbeat(struct sock *sk)
+{
+ del_timer(&sk->timer);
+}
+
+void rose_stop_timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.rose->timer);
+}
+
+void rose_stop_idletimer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.rose->idletimer);
+}
+
+static void rose_heartbeat_expiry(unsigned long param)
+{
+ struct sock *sk = (struct sock *)param;
+
+ switch (sk->protinfo.rose->state) {
+
+ case ROSE_STATE_0:
+ /* Magic here: If we listen() and a new link dies before it
+ is accepted() it isn't 'dead' so doesn't get removed. */
+ if (sk->destroy || (sk->state == TCP_LISTEN && sk->dead)) {
+ rose_destroy_socket(sk);
+ return;
+ }
+ break;
+
+ case ROSE_STATE_3:
+ /*
+ * Check for the state of the receive buffer.
+ */
+ if (atomic_read(&sk->rmem_alloc) < (sk->rcvbuf / 2) &&
+ (sk->protinfo.rose->condition & ROSE_COND_OWN_RX_BUSY)) {
+ sk->protinfo.rose->condition &= ~ROSE_COND_OWN_RX_BUSY;
+ sk->protinfo.rose->condition &= ~ROSE_COND_ACK_PENDING;
+ sk->protinfo.rose->vl = sk->protinfo.rose->vr;
+ rose_write_internal(sk, ROSE_RR);
+ rose_stop_timer(sk); /* HB */
+ break;
+ }
+ break;
+ }
+
+ rose_start_heartbeat(sk);
+}
+
+static void rose_timer_expiry(unsigned long param)
+{
+ struct sock *sk = (struct sock *)param;
+
+ switch (sk->protinfo.rose->state) {
+
+ case ROSE_STATE_1: /* T1 */
+ case ROSE_STATE_4: /* T2 */
+ rose_write_internal(sk, ROSE_CLEAR_REQUEST);
+ sk->protinfo.rose->state = ROSE_STATE_2;
+ rose_start_t3timer(sk);
+ break;
+
+ case ROSE_STATE_2: /* T3 */
+ sk->protinfo.rose->neighbour->use--;
+ rose_disconnect(sk, ETIMEDOUT, -1, -1);
+ break;
+
+ case ROSE_STATE_3: /* HB */
+ if (sk->protinfo.rose->condition & ROSE_COND_ACK_PENDING) {
+ sk->protinfo.rose->condition &= ~ROSE_COND_ACK_PENDING;
+ rose_enquiry_response(sk);
+ }
+ break;
+ }
+}
+
+static void rose_idletimer_expiry(unsigned long param)
+{
+ struct sock *sk = (struct sock *)param;
+
+ rose_clear_queues(sk);
+
+ rose_write_internal(sk, ROSE_CLEAR_REQUEST);
+ sk->protinfo.rose->state = ROSE_STATE_2;
+
+ rose_start_t3timer(sk);
+
+ sk->state = TCP_CLOSE;
+ sk->err = 0;
+ sk->shutdown |= SEND_SHUTDOWN;
+
+ if (!sk->dead)
+ sk->state_change(sk);
+
+ sk->dead = 1;
+}
diff --git a/uClinux-2.4.31-uc0/net/rose/sysctl_net_rose.c b/uClinux-2.4.31-uc0/net/rose/sysctl_net_rose.c
new file mode 100644
index 0000000..8b5c89f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/rose/sysctl_net_rose.c
@@ -0,0 +1,78 @@
+/* -*- linux-c -*-
+ * sysctl_net_rose.c: sysctl interface to net ROSE subsystem.
+ *
+ * Begun April 1, 1996, Mike Shaver.
+ * Added /proc/sys/net/rose directory entry (empty =) ). [MS]
+ */
+
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+#include <linux/init.h>
+#include <net/ax25.h>
+#include <net/rose.h>
+
+static int min_timer[] = {1 * HZ};
+static int max_timer[] = {300 * HZ};
+static int min_idle[] = {0 * HZ};
+static int max_idle[] = {65535 * HZ};
+static int min_route[] = {0}, max_route[] = {1};
+static int min_ftimer[] = {60 * HZ};
+static int max_ftimer[] = {600 * HZ};
+static int min_maxvcs[] = {1}, max_maxvcs[] = {254};
+static int min_window[] = {1}, max_window[] = {7};
+
+static struct ctl_table_header *rose_table_header;
+
+static ctl_table rose_table[] = {
+ {NET_ROSE_RESTART_REQUEST_TIMEOUT, "restart_request_timeout",
+ &sysctl_rose_restart_request_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_timer, &max_timer},
+ {NET_ROSE_CALL_REQUEST_TIMEOUT, "call_request_timeout",
+ &sysctl_rose_call_request_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_timer, &max_timer},
+ {NET_ROSE_RESET_REQUEST_TIMEOUT, "reset_request_timeout",
+ &sysctl_rose_reset_request_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_timer, &max_timer},
+ {NET_ROSE_CLEAR_REQUEST_TIMEOUT, "clear_request_timeout",
+ &sysctl_rose_clear_request_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_timer, &max_timer},
+ {NET_ROSE_NO_ACTIVITY_TIMEOUT, "no_activity_timeout",
+ &sysctl_rose_no_activity_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_idle, &max_idle},
+ {NET_ROSE_ACK_HOLD_BACK_TIMEOUT, "acknowledge_hold_back_timeout",
+ &sysctl_rose_ack_hold_back_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_timer, &max_timer},
+ {NET_ROSE_ROUTING_CONTROL, "routing_control",
+ &sysctl_rose_routing_control, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_route, &max_route},
+ {NET_ROSE_LINK_FAIL_TIMEOUT, "link_fail_timeout",
+ &sysctl_rose_link_fail_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_ftimer, &max_ftimer},
+ {NET_ROSE_MAX_VCS, "maximum_virtual_circuits",
+ &sysctl_rose_maximum_vcs, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_maxvcs, &max_maxvcs},
+ {NET_ROSE_WINDOW_SIZE, "window_size",
+ &sysctl_rose_window_size, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_window, &max_window},
+ {0}
+};
+
+static ctl_table rose_dir_table[] = {
+ {NET_ROSE, "rose", NULL, 0, 0555, rose_table},
+ {0}
+};
+
+static ctl_table rose_root_table[] = {
+ {CTL_NET, "net", NULL, 0, 0555, rose_dir_table},
+ {0}
+};
+
+void __init rose_register_sysctl(void)
+{
+ rose_table_header = register_sysctl_table(rose_root_table, 1);
+}
+
+void rose_unregister_sysctl(void)
+{
+ unregister_sysctl_table(rose_table_header);
+}
diff --git a/uClinux-2.4.31-uc0/net/sched/Config.in b/uClinux-2.4.31-uc0/net/sched/Config.in
new file mode 100644
index 0000000..00383d1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/Config.in
@@ -0,0 +1,43 @@
+#
+# Traffic control configuration.
+#
+tristate ' CBQ packet scheduler' CONFIG_NET_SCH_CBQ
+tristate ' HTB packet scheduler' CONFIG_NET_SCH_HTB
+tristate ' CSZ packet scheduler' CONFIG_NET_SCH_CSZ
+#tristate ' H-PFQ packet scheduler' CONFIG_NET_SCH_HPFQ
+tristate ' H-FSC packet scheduler' CONFIG_NET_SCH_HFSC
+if [ "$CONFIG_ATM" = "y" -o "$CONFIG_ATM" = "m" ]; then
+ dep_tristate ' ATM pseudo-scheduler' CONFIG_NET_SCH_ATM $CONFIG_ATM
+fi
+tristate ' The simplest PRIO pseudoscheduler' CONFIG_NET_SCH_PRIO
+tristate ' WRR packet scheduler' CONFIG_NET_SCH_WRR
+tristate ' RED queue' CONFIG_NET_SCH_RED
+tristate ' SFQ queue' CONFIG_NET_SCH_SFQ
+tristate ' TEQL queue' CONFIG_NET_SCH_TEQL
+tristate ' TBF queue' CONFIG_NET_SCH_TBF
+tristate ' GRED queue' CONFIG_NET_SCH_GRED
+tristate ' Network emulator' CONFIG_NET_SCH_NETEM
+tristate ' Diffserv field marker' CONFIG_NET_SCH_DSMARK
+if [ "$CONFIG_NETFILTER" = "y" ]; then
+ tristate ' Ingress Qdisc' CONFIG_NET_SCH_INGRESS
+fi
+bool ' QoS support' CONFIG_NET_QOS
+if [ "$CONFIG_NET_QOS" = "y" ]; then
+ bool ' Rate estimator' CONFIG_NET_ESTIMATOR
+fi
+bool ' Packet classifier API' CONFIG_NET_CLS
+if [ "$CONFIG_NET_CLS" = "y" ]; then
+ tristate ' TC index classifier' CONFIG_NET_CLS_TCINDEX
+ tristate ' Routing table based classifier' CONFIG_NET_CLS_ROUTE4
+ if [ "$CONFIG_NET_CLS_ROUTE4" != "n" ]; then
+ define_bool CONFIG_NET_CLS_ROUTE y
+ fi
+ tristate ' Firewall based classifier' CONFIG_NET_CLS_FW
+ tristate ' U32 classifier' CONFIG_NET_CLS_U32
+ if [ "$CONFIG_NET_QOS" = "y" ]; then
+ tristate ' Special RSVP classifier' CONFIG_NET_CLS_RSVP
+ tristate ' Special RSVP classifier for IPv6' CONFIG_NET_CLS_RSVP6
+ bool ' Traffic policing (needed for in/egress)' CONFIG_NET_CLS_POLICE
+ fi
+fi
+
diff --git a/uClinux-2.4.31-uc0/net/sched/Makefile b/uClinux-2.4.31-uc0/net/sched/Makefile
new file mode 100644
index 0000000..31508ab
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/Makefile
@@ -0,0 +1,37 @@
+#
+# Makefile for the Linux Traffic Control Unit.
+#
+
+O_TARGET := sched.o
+
+obj-y := sch_generic.o
+
+
+obj-$(CONFIG_NET_SCHED) += sch_api.o sch_fifo.o
+obj-$(CONFIG_NET_ESTIMATOR) += estimator.o
+obj-$(CONFIG_NET_CLS) += cls_api.o
+obj-$(CONFIG_NET_CLS_POLICE) += police.o
+obj-$(CONFIG_NET_SCH_INGRESS) += sch_ingress.o
+obj-$(CONFIG_NET_SCH_CBQ) += sch_cbq.o
+obj-$(CONFIG_NET_SCH_WRR) += sch_wrr.o
+obj-$(CONFIG_NET_SCH_CSZ) += sch_csz.o
+obj-$(CONFIG_NET_SCH_NETEM) += sch_netem.o
+obj-$(CONFIG_NET_SCH_HPFQ) += sch_hpfq.o
+obj-$(CONFIG_NET_SCH_HFSC) += sch_hfsc.o
+obj-$(CONFIG_NET_SCH_HTB) += sch_htb.o
+obj-$(CONFIG_NET_SCH_SFQ) += sch_sfq.o
+obj-$(CONFIG_NET_SCH_RED) += sch_red.o
+obj-$(CONFIG_NET_SCH_TBF) += sch_tbf.o
+obj-$(CONFIG_NET_SCH_PRIO) += sch_prio.o
+obj-$(CONFIG_NET_SCH_TEQL) += sch_teql.o
+obj-$(CONFIG_NET_SCH_GRED) += sch_gred.o
+obj-$(CONFIG_NET_SCH_DSMARK) += sch_dsmark.o
+obj-$(CONFIG_NET_CLS_TCINDEX) += cls_tcindex.o
+obj-$(CONFIG_NET_SCH_ATM) += sch_atm.o
+obj-$(CONFIG_NET_CLS_U32) += cls_u32.o
+obj-$(CONFIG_NET_CLS_RSVP) += cls_rsvp.o
+obj-$(CONFIG_NET_CLS_RSVP6) += cls_rsvp6.o
+obj-$(CONFIG_NET_CLS_ROUTE4) += cls_route.o
+obj-$(CONFIG_NET_CLS_FW) += cls_fw.o
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/sched/cls_api.c b/uClinux-2.4.31-uc0/net/sched/cls_api.c
new file mode 100644
index 0000000..a7ce5a7
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/cls_api.c
@@ -0,0 +1,468 @@
+/*
+ * net/sched/cls_api.c Packet classifier API.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Changes:
+ *
+ * Eduardo J. Blanco <ejbs@netlabs.com.uy> :990222: kmod support
+ */
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/init.h>
+#include <linux/kmod.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+/* The list of all installed classifier types */
+
+static struct tcf_proto_ops *tcf_proto_base;
+
+/* Protects list of registered TC modules. It is pure SMP lock. */
+static rwlock_t cls_mod_lock = RW_LOCK_UNLOCKED;
+
+/* Find classifier type by string name */
+
+struct tcf_proto_ops * tcf_proto_lookup_ops(struct rtattr *kind)
+{
+ struct tcf_proto_ops *t = NULL;
+
+ if (kind) {
+ read_lock(&cls_mod_lock);
+ for (t = tcf_proto_base; t; t = t->next) {
+ if (rtattr_strcmp(kind, t->kind) == 0)
+ break;
+ }
+ read_unlock(&cls_mod_lock);
+ }
+ return t;
+}
+
+/* Register(unregister) new classifier type */
+
+int register_tcf_proto_ops(struct tcf_proto_ops *ops)
+{
+ struct tcf_proto_ops *t, **tp;
+
+ write_lock(&cls_mod_lock);
+ for (tp = &tcf_proto_base; (t=*tp) != NULL; tp = &t->next) {
+ if (strcmp(ops->kind, t->kind) == 0) {
+ write_unlock(&cls_mod_lock);
+ return -EEXIST;
+ }
+ }
+
+ ops->next = NULL;
+ *tp = ops;
+ write_unlock(&cls_mod_lock);
+ return 0;
+}
+
+int unregister_tcf_proto_ops(struct tcf_proto_ops *ops)
+{
+ struct tcf_proto_ops *t, **tp;
+
+ write_lock(&cls_mod_lock);
+ for (tp = &tcf_proto_base; (t=*tp) != NULL; tp = &t->next)
+ if (t == ops)
+ break;
+
+ if (!t) {
+ write_unlock(&cls_mod_lock);
+ return -ENOENT;
+ }
+ *tp = t->next;
+ write_unlock(&cls_mod_lock);
+ return 0;
+}
+
+static int tfilter_notify(struct sk_buff *oskb, struct nlmsghdr *n,
+ struct tcf_proto *tp, unsigned long fh, int event);
+
+
+/* Select new prio value from the range, managed by kernel. */
+
+static __inline__ u32 tcf_auto_prio(struct tcf_proto *tp)
+{
+ u32 first = TC_H_MAKE(0xC0000000U,0U);
+
+ if (tp)
+ first = tp->prio-1;
+
+ return first;
+}
+
+/* Add/change/delete/get a filter node */
+
+static int tc_ctl_tfilter(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
+{
+ struct rtattr **tca = arg;
+ struct tcmsg *t = NLMSG_DATA(n);
+ u32 protocol = TC_H_MIN(t->tcm_info);
+ u32 prio = TC_H_MAJ(t->tcm_info);
+ u32 nprio = prio;
+ u32 parent = t->tcm_parent;
+ struct net_device *dev;
+ struct Qdisc *q;
+ struct tcf_proto **back, **chain;
+ struct tcf_proto *tp = NULL;
+ struct tcf_proto_ops *tp_ops;
+ struct Qdisc_class_ops *cops;
+ unsigned long cl = 0;
+ unsigned long fh;
+ int err;
+
+ if (prio == 0) {
+ /* If no priority is given, user wants we allocated it. */
+ if (n->nlmsg_type != RTM_NEWTFILTER || !(n->nlmsg_flags&NLM_F_CREATE))
+ return -ENOENT;
+ prio = TC_H_MAKE(0x80000000U,0U);
+ }
+
+ /* Find head of filter chain. */
+
+ /* Find link */
+ if ((dev = __dev_get_by_index(t->tcm_ifindex)) == NULL)
+ return -ENODEV;
+
+ /* Find qdisc */
+ if (!parent) {
+ q = dev->qdisc_sleeping;
+ parent = q->handle;
+ } else if ((q = qdisc_lookup(dev, TC_H_MAJ(t->tcm_parent))) == NULL)
+ return -EINVAL;
+
+ /* Is it classful? */
+ if ((cops = q->ops->cl_ops) == NULL)
+ return -EINVAL;
+
+ /* Do we search for filter, attached to class? */
+ if (TC_H_MIN(parent)) {
+ cl = cops->get(q, parent);
+ if (cl == 0)
+ return -ENOENT;
+ }
+
+ /* And the last stroke */
+ chain = cops->tcf_chain(q, cl);
+ err = -EINVAL;
+ if (chain == NULL)
+ goto errout;
+
+ /* Check the chain for existence of proto-tcf with this priority */
+ for (back = chain; (tp=*back) != NULL; back = &tp->next) {
+ if (tp->prio >= prio) {
+ if (tp->prio == prio) {
+ if (!nprio || (tp->protocol != protocol && protocol))
+ goto errout;
+ } else
+ tp = NULL;
+ break;
+ }
+ }
+
+ if (tp == NULL) {
+ /* Proto-tcf does not exist, create new one */
+
+ if (tca[TCA_KIND-1] == NULL || !protocol)
+ goto errout;
+
+ err = -ENOENT;
+ if (n->nlmsg_type != RTM_NEWTFILTER || !(n->nlmsg_flags&NLM_F_CREATE))
+ goto errout;
+
+
+ /* Create new proto tcf */
+
+ err = -ENOBUFS;
+ if ((tp = kmalloc(sizeof(*tp), GFP_KERNEL)) == NULL)
+ goto errout;
+ tp_ops = tcf_proto_lookup_ops(tca[TCA_KIND-1]);
+#ifdef CONFIG_KMOD
+ if (tp_ops==NULL && tca[TCA_KIND-1] != NULL) {
+ struct rtattr *kind = tca[TCA_KIND-1];
+ char module_name[4 + IFNAMSIZ + 1];
+
+ if (RTA_PAYLOAD(kind) <= IFNAMSIZ) {
+ sprintf(module_name, "cls_%s", (char*)RTA_DATA(kind));
+ request_module (module_name);
+ tp_ops = tcf_proto_lookup_ops(kind);
+ }
+ }
+#endif
+ if (tp_ops == NULL) {
+ err = -EINVAL;
+ kfree(tp);
+ goto errout;
+ }
+ memset(tp, 0, sizeof(*tp));
+ tp->ops = tp_ops;
+ tp->protocol = protocol;
+ tp->prio = nprio ? : tcf_auto_prio(*back);
+ tp->q = q;
+ tp->classify = tp_ops->classify;
+ tp->classid = parent;
+ err = tp_ops->init(tp);
+ if (err) {
+ kfree(tp);
+ goto errout;
+ }
+ write_lock(&qdisc_tree_lock);
+ spin_lock_bh(&dev->queue_lock);
+ tp->next = *back;
+ *back = tp;
+ spin_unlock_bh(&dev->queue_lock);
+ write_unlock(&qdisc_tree_lock);
+ } else if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], tp->ops->kind))
+ goto errout;
+
+ fh = tp->ops->get(tp, t->tcm_handle);
+
+ if (fh == 0) {
+ if (n->nlmsg_type == RTM_DELTFILTER && t->tcm_handle == 0) {
+ write_lock(&qdisc_tree_lock);
+ spin_lock_bh(&dev->queue_lock);
+ *back = tp->next;
+ spin_unlock_bh(&dev->queue_lock);
+ write_unlock(&qdisc_tree_lock);
+ tcf_destroy(tp);
+ err = 0;
+ goto errout;
+ }
+
+ err = -ENOENT;
+ if (n->nlmsg_type != RTM_NEWTFILTER || !(n->nlmsg_flags&NLM_F_CREATE))
+ goto errout;
+ } else {
+ switch (n->nlmsg_type) {
+ case RTM_NEWTFILTER:
+ err = -EEXIST;
+ if (n->nlmsg_flags&NLM_F_EXCL)
+ goto errout;
+ break;
+ case RTM_DELTFILTER:
+ err = tp->ops->delete(tp, fh);
+ goto errout;
+ case RTM_GETTFILTER:
+ err = tfilter_notify(skb, n, tp, fh, RTM_NEWTFILTER);
+ goto errout;
+ default:
+ err = -EINVAL;
+ goto errout;
+ }
+ }
+
+ err = tp->ops->change(tp, cl, t->tcm_handle, tca, &fh);
+ if (err == 0)
+ tfilter_notify(skb, n, tp, fh, RTM_NEWTFILTER);
+
+errout:
+ if (cl)
+ cops->put(q, cl);
+ return err;
+}
+
+static int
+tcf_fill_node(struct sk_buff *skb, struct tcf_proto *tp, unsigned long fh,
+ u32 pid, u32 seq, unsigned flags, int event)
+{
+ struct tcmsg *tcm;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+ nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*tcm));
+ nlh->nlmsg_flags = flags;
+ tcm = NLMSG_DATA(nlh);
+ tcm->tcm_family = AF_UNSPEC;
+ tcm->tcm_ifindex = tp->q->dev->ifindex;
+ tcm->tcm_parent = tp->classid;
+ tcm->tcm_handle = 0;
+ tcm->tcm_info = TC_H_MAKE(tp->prio, tp->protocol);
+ RTA_PUT(skb, TCA_KIND, IFNAMSIZ, tp->ops->kind);
+ if (tp->ops->dump && tp->ops->dump(tp, fh, skb, tcm) < 0)
+ goto rtattr_failure;
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int tfilter_notify(struct sk_buff *oskb, struct nlmsghdr *n,
+ struct tcf_proto *tp, unsigned long fh, int event)
+{
+ struct sk_buff *skb;
+ u32 pid = oskb ? NETLINK_CB(oskb).pid : 0;
+
+ skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!skb)
+ return -ENOBUFS;
+
+ if (tcf_fill_node(skb, tp, fh, pid, n->nlmsg_seq, 0, event) <= 0) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ return rtnetlink_send(skb, pid, RTMGRP_TC, n->nlmsg_flags&NLM_F_ECHO);
+}
+
+struct tcf_dump_args
+{
+ struct tcf_walker w;
+ struct sk_buff *skb;
+ struct netlink_callback *cb;
+};
+
+static int tcf_node_dump(struct tcf_proto *tp, unsigned long n, struct tcf_walker *arg)
+{
+ struct tcf_dump_args *a = (void*)arg;
+
+ return tcf_fill_node(a->skb, tp, n, NETLINK_CB(a->cb->skb).pid,
+ a->cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWTFILTER);
+}
+
+static int tc_dump_tfilter(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int t;
+ int s_t;
+ struct net_device *dev;
+ struct Qdisc *q;
+ struct tcf_proto *tp, **chain;
+ struct tcmsg *tcm = (struct tcmsg*)NLMSG_DATA(cb->nlh);
+ unsigned long cl = 0;
+ struct Qdisc_class_ops *cops;
+ struct tcf_dump_args arg;
+
+ if (cb->nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*tcm)))
+ return skb->len;
+ if ((dev = dev_get_by_index(tcm->tcm_ifindex)) == NULL)
+ return skb->len;
+
+ read_lock(&qdisc_tree_lock);
+ if (!tcm->tcm_parent)
+ q = dev->qdisc_sleeping;
+ else
+ q = qdisc_lookup(dev, TC_H_MAJ(tcm->tcm_parent));
+ if (q == NULL) {
+ read_unlock(&qdisc_tree_lock);
+ dev_put(dev);
+ return skb->len;
+ }
+ if ((cops = q->ops->cl_ops) == NULL)
+ goto errout;
+ if (TC_H_MIN(tcm->tcm_parent)) {
+ cl = cops->get(q, tcm->tcm_parent);
+ if (cl == 0)
+ goto errout;
+ }
+ chain = cops->tcf_chain(q, cl);
+ if (chain == NULL)
+ goto errout;
+
+ s_t = cb->args[0];
+
+ for (tp=*chain, t=0; tp; tp = tp->next, t++) {
+ if (t < s_t) continue;
+ if (TC_H_MAJ(tcm->tcm_info) &&
+ TC_H_MAJ(tcm->tcm_info) != tp->prio)
+ continue;
+ if (TC_H_MIN(tcm->tcm_info) &&
+ TC_H_MIN(tcm->tcm_info) != tp->protocol)
+ continue;
+ if (t > s_t)
+ memset(&cb->args[1], 0, sizeof(cb->args)-sizeof(cb->args[0]));
+ if (cb->args[1] == 0) {
+ if (tcf_fill_node(skb, tp, 0, NETLINK_CB(cb->skb).pid,
+ cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWTFILTER) <= 0) {
+ break;
+ }
+ cb->args[1] = 1;
+ }
+ if (tp->ops->walk == NULL)
+ continue;
+ arg.w.fn = tcf_node_dump;
+ arg.skb = skb;
+ arg.cb = cb;
+ arg.w.stop = 0;
+ arg.w.skip = cb->args[1]-1;
+ arg.w.count = 0;
+ tp->ops->walk(tp, &arg.w);
+ cb->args[1] = arg.w.count+1;
+ if (arg.w.stop)
+ break;
+ }
+
+ cb->args[0] = t;
+
+errout:
+ if (cl)
+ cops->put(q, cl);
+
+ read_unlock(&qdisc_tree_lock);
+ dev_put(dev);
+ return skb->len;
+}
+
+
+int __init tc_filter_init(void)
+{
+ struct rtnetlink_link *link_p = rtnetlink_links[PF_UNSPEC];
+
+ /* Setup rtnetlink links. It is made here to avoid
+ exporting large number of public symbols.
+ */
+
+ if (link_p) {
+ link_p[RTM_NEWTFILTER-RTM_BASE].doit = tc_ctl_tfilter;
+ link_p[RTM_DELTFILTER-RTM_BASE].doit = tc_ctl_tfilter;
+ link_p[RTM_GETTFILTER-RTM_BASE].doit = tc_ctl_tfilter;
+ link_p[RTM_GETTFILTER-RTM_BASE].dumpit = tc_dump_tfilter;
+ }
+#define INIT_TC_FILTER(name) { \
+ extern struct tcf_proto_ops cls_##name##_ops; \
+ register_tcf_proto_ops(&cls_##name##_ops); \
+ }
+
+#ifdef CONFIG_NET_CLS_U32
+ INIT_TC_FILTER(u32);
+#endif
+#ifdef CONFIG_NET_CLS_ROUTE4
+ INIT_TC_FILTER(route4);
+#endif
+#ifdef CONFIG_NET_CLS_FW
+ INIT_TC_FILTER(fw);
+#endif
+#ifdef CONFIG_NET_CLS_RSVP
+ INIT_TC_FILTER(rsvp);
+#endif
+#ifdef CONFIG_NET_CLS_TCINDEX
+ INIT_TC_FILTER(tcindex);
+#endif
+#ifdef CONFIG_NET_CLS_RSVP6
+ INIT_TC_FILTER(rsvp6);
+#endif
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/sched/cls_fw.c b/uClinux-2.4.31-uc0/net/sched/cls_fw.c
new file mode 100644
index 0000000..58fc991
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/cls_fw.c
@@ -0,0 +1,379 @@
+/*
+ * net/sched/cls_fw.c Classifier mapping ipchains' fwmark to traffic class.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Changes:
+ * Karlis Peisenieks <karlis@mt.lv> : 990415 : fw_walk off by one
+ * Karlis Peisenieks <karlis@mt.lv> : 990415 : fw_delete killed all the filter (and kernel).
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <linux/netfilter.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+struct fw_head
+{
+ struct fw_filter *ht[256];
+};
+
+struct fw_filter
+{
+ struct fw_filter *next;
+ u32 id;
+ struct tcf_result res;
+#ifdef CONFIG_NET_CLS_POLICE
+ struct tcf_police *police;
+#endif
+};
+
+static __inline__ int fw_hash(u32 handle)
+{
+ return handle&0xFF;
+}
+
+static int fw_classify(struct sk_buff *skb, struct tcf_proto *tp,
+ struct tcf_result *res)
+{
+ struct fw_head *head = (struct fw_head*)tp->root;
+ struct fw_filter *f;
+#ifdef CONFIG_NETFILTER
+ u32 id = skb->nfmark;
+#else
+ u32 id = 0;
+#endif
+
+ if (head == NULL)
+ goto old_method;
+
+ for (f=head->ht[fw_hash(id)]; f; f=f->next) {
+ if (f->id == id) {
+ *res = f->res;
+#ifdef CONFIG_NET_CLS_POLICE
+ if (f->police)
+ return tcf_police(skb, f->police);
+#endif
+ return 0;
+ }
+ }
+ return -1;
+
+old_method:
+ if (id && (TC_H_MAJ(id) == 0 ||
+ !(TC_H_MAJ(id^tp->q->handle)))) {
+ res->classid = id;
+ res->class = 0;
+ return 0;
+ }
+ return -1;
+}
+
+static unsigned long fw_get(struct tcf_proto *tp, u32 handle)
+{
+ struct fw_head *head = (struct fw_head*)tp->root;
+ struct fw_filter *f;
+
+ if (head == NULL)
+ return 0;
+
+ for (f=head->ht[fw_hash(handle)]; f; f=f->next) {
+ if (f->id == handle)
+ return (unsigned long)f;
+ }
+ return 0;
+}
+
+static void fw_put(struct tcf_proto *tp, unsigned long f)
+{
+}
+
+static int fw_init(struct tcf_proto *tp)
+{
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void fw_destroy(struct tcf_proto *tp)
+{
+ struct fw_head *head = (struct fw_head*)xchg(&tp->root, NULL);
+ struct fw_filter *f;
+ int h;
+
+ if (head == NULL) {
+ MOD_DEC_USE_COUNT;
+ return;
+ }
+
+ for (h=0; h<256; h++) {
+ while ((f=head->ht[h]) != NULL) {
+ unsigned long cl;
+ head->ht[h] = f->next;
+
+ if ((cl = __cls_set_class(&f->res.class, 0)) != 0)
+ tp->q->ops->cl_ops->unbind_tcf(tp->q, cl);
+#ifdef CONFIG_NET_CLS_POLICE
+ tcf_police_release(f->police);
+#endif
+ kfree(f);
+ }
+ }
+ kfree(head);
+ MOD_DEC_USE_COUNT;
+}
+
+static int fw_delete(struct tcf_proto *tp, unsigned long arg)
+{
+ struct fw_head *head = (struct fw_head*)tp->root;
+ struct fw_filter *f = (struct fw_filter*)arg;
+ struct fw_filter **fp;
+
+ if (head == NULL || f == NULL)
+ return -EINVAL;
+
+ for (fp=&head->ht[fw_hash(f->id)]; *fp; fp = &(*fp)->next) {
+ if (*fp == f) {
+ unsigned long cl;
+
+ tcf_tree_lock(tp);
+ *fp = f->next;
+ tcf_tree_unlock(tp);
+
+ if ((cl = cls_set_class(tp, &f->res.class, 0)) != 0)
+ tp->q->ops->cl_ops->unbind_tcf(tp->q, cl);
+#ifdef CONFIG_NET_CLS_POLICE
+ tcf_police_release(f->police);
+#endif
+ kfree(f);
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+static int fw_change(struct tcf_proto *tp, unsigned long base,
+ u32 handle,
+ struct rtattr **tca,
+ unsigned long *arg)
+{
+ struct fw_head *head = (struct fw_head*)tp->root;
+ struct fw_filter *f;
+ struct rtattr *opt = tca[TCA_OPTIONS-1];
+ struct rtattr *tb[TCA_FW_MAX];
+ int err;
+
+ if (!opt)
+ return handle ? -EINVAL : 0;
+
+ if (rtattr_parse(tb, TCA_FW_MAX, RTA_DATA(opt), RTA_PAYLOAD(opt)) < 0)
+ return -EINVAL;
+
+ if ((f = (struct fw_filter*)*arg) != NULL) {
+ /* Node exists: adjust only classid */
+
+ if (f->id != handle && handle)
+ return -EINVAL;
+ if (tb[TCA_FW_CLASSID-1]) {
+ unsigned long cl;
+
+ f->res.classid = *(u32*)RTA_DATA(tb[TCA_FW_CLASSID-1]);
+ cl = tp->q->ops->cl_ops->bind_tcf(tp->q, base, f->res.classid);
+ cl = cls_set_class(tp, &f->res.class, cl);
+ if (cl)
+ tp->q->ops->cl_ops->unbind_tcf(tp->q, cl);
+ }
+#ifdef CONFIG_NET_CLS_POLICE
+ if (tb[TCA_FW_POLICE-1]) {
+ struct tcf_police *police = tcf_police_locate(tb[TCA_FW_POLICE-1], tca[TCA_RATE-1]);
+
+ tcf_tree_lock(tp);
+ police = xchg(&f->police, police);
+ tcf_tree_unlock(tp);
+
+ tcf_police_release(police);
+ }
+#endif
+ return 0;
+ }
+
+ if (!handle)
+ return -EINVAL;
+
+ if (head == NULL) {
+ head = kmalloc(sizeof(struct fw_head), GFP_KERNEL);
+ if (head == NULL)
+ return -ENOBUFS;
+ memset(head, 0, sizeof(*head));
+
+ tcf_tree_lock(tp);
+ tp->root = head;
+ tcf_tree_unlock(tp);
+ }
+
+ f = kmalloc(sizeof(struct fw_filter), GFP_KERNEL);
+ if (f == NULL)
+ return -ENOBUFS;
+ memset(f, 0, sizeof(*f));
+
+ f->id = handle;
+
+ if (tb[TCA_FW_CLASSID-1]) {
+ err = -EINVAL;
+ if (RTA_PAYLOAD(tb[TCA_FW_CLASSID-1]) != 4)
+ goto errout;
+ f->res.classid = *(u32*)RTA_DATA(tb[TCA_FW_CLASSID-1]);
+ cls_set_class(tp, &f->res.class, tp->q->ops->cl_ops->bind_tcf(tp->q, base, f->res.classid));
+ }
+
+#ifdef CONFIG_NET_CLS_POLICE
+ if (tb[TCA_FW_POLICE-1])
+ f->police = tcf_police_locate(tb[TCA_FW_POLICE-1], tca[TCA_RATE-1]);
+#endif
+
+ f->next = head->ht[fw_hash(handle)];
+ tcf_tree_lock(tp);
+ head->ht[fw_hash(handle)] = f;
+ tcf_tree_unlock(tp);
+
+ *arg = (unsigned long)f;
+ return 0;
+
+errout:
+ if (f)
+ kfree(f);
+ return err;
+}
+
+static void fw_walk(struct tcf_proto *tp, struct tcf_walker *arg)
+{
+ struct fw_head *head = (struct fw_head*)tp->root;
+ int h;
+
+ if (head == NULL)
+ arg->stop = 1;
+
+ if (arg->stop)
+ return;
+
+ for (h = 0; h < 256; h++) {
+ struct fw_filter *f;
+
+ for (f = head->ht[h]; f; f = f->next) {
+ if (arg->count < arg->skip) {
+ arg->count++;
+ continue;
+ }
+ if (arg->fn(tp, (unsigned long)f, arg) < 0) {
+ arg->stop = 1;
+ return;
+ }
+ arg->count++;
+ }
+ }
+}
+
+static int fw_dump(struct tcf_proto *tp, unsigned long fh,
+ struct sk_buff *skb, struct tcmsg *t)
+{
+ struct fw_filter *f = (struct fw_filter*)fh;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+
+ if (f == NULL)
+ return skb->len;
+
+ t->tcm_handle = f->id;
+
+ if (!f->res.classid
+#ifdef CONFIG_NET_CLS_POLICE
+ && !f->police
+#endif
+ )
+ return skb->len;
+
+ rta = (struct rtattr*)b;
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+
+ if (f->res.classid)
+ RTA_PUT(skb, TCA_FW_CLASSID, 4, &f->res.classid);
+#ifdef CONFIG_NET_CLS_POLICE
+ if (f->police) {
+ struct rtattr * p_rta = (struct rtattr*)skb->tail;
+
+ RTA_PUT(skb, TCA_FW_POLICE, 0, NULL);
+
+ if (tcf_police_dump(skb, f->police) < 0)
+ goto rtattr_failure;
+
+ p_rta->rta_len = skb->tail - (u8*)p_rta;
+ }
+#endif
+
+ rta->rta_len = skb->tail - b;
+#ifdef CONFIG_NET_CLS_POLICE
+ if (f->police) {
+ if (qdisc_copy_stats(skb, &f->police->stats))
+ goto rtattr_failure;
+ }
+#endif
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+struct tcf_proto_ops cls_fw_ops = {
+ NULL,
+ "fw",
+ fw_classify,
+ fw_init,
+ fw_destroy,
+
+ fw_get,
+ fw_put,
+ fw_change,
+ fw_delete,
+ fw_walk,
+ fw_dump
+};
+
+#ifdef MODULE
+int init_module(void)
+{
+ return register_tcf_proto_ops(&cls_fw_ops);
+}
+
+void cleanup_module(void)
+{
+ unregister_tcf_proto_ops(&cls_fw_ops);
+}
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/cls_route.c b/uClinux-2.4.31-uc0/net/sched/cls_route.c
new file mode 100644
index 0000000..86ecbff
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/cls_route.c
@@ -0,0 +1,635 @@
+/*
+ * net/sched/cls_route.c ROUTE4 classifier.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+/*
+ 1. For now we assume that route tags < 256.
+ It allows to use direct table lookups, instead of hash tables.
+ 2. For now we assume that "from TAG" and "fromdev DEV" statements
+ are mutually exclusive.
+ 3. "to TAG from ANY" has higher priority, than "to ANY from XXX"
+ */
+
+struct route4_fastmap
+{
+ struct route4_filter *filter;
+ u32 id;
+ int iif;
+};
+
+struct route4_head
+{
+ struct route4_fastmap fastmap[16];
+ struct route4_bucket *table[256+1];
+};
+
+struct route4_bucket
+{
+ struct route4_filter *ht[16+16+1];
+};
+
+struct route4_filter
+{
+ struct route4_filter *next;
+ u32 id;
+ int iif;
+
+ struct tcf_result res;
+#ifdef CONFIG_NET_CLS_POLICE
+ struct tcf_police *police;
+#endif
+
+ u32 handle;
+ struct route4_bucket *bkt;
+};
+
+#define ROUTE4_FAILURE ((struct route4_filter*)(-1L))
+
+static __inline__ int route4_fastmap_hash(u32 id, int iif)
+{
+ return id&0xF;
+}
+
+static void route4_reset_fastmap(struct net_device *dev, struct route4_head *head, u32 id)
+{
+ spin_lock_bh(&dev->queue_lock);
+ memset(head->fastmap, 0, sizeof(head->fastmap));
+ spin_unlock_bh(&dev->queue_lock);
+}
+
+static void __inline__
+route4_set_fastmap(struct route4_head *head, u32 id, int iif,
+ struct route4_filter *f)
+{
+ int h = route4_fastmap_hash(id, iif);
+ head->fastmap[h].id = id;
+ head->fastmap[h].iif = iif;
+ head->fastmap[h].filter = f;
+}
+
+static __inline__ int route4_hash_to(u32 id)
+{
+ return id&0xFF;
+}
+
+static __inline__ int route4_hash_from(u32 id)
+{
+ return (id>>16)&0xF;
+}
+
+static __inline__ int route4_hash_iif(int iif)
+{
+ return 16 + ((iif>>16)&0xF);
+}
+
+static __inline__ int route4_hash_wild(void)
+{
+ return 32;
+}
+
+#ifdef CONFIG_NET_CLS_POLICE
+#define IF_ROUTE_POLICE \
+if (f->police) { \
+ int pol_res = tcf_police(skb, f->police); \
+ if (pol_res >= 0) return pol_res; \
+ dont_cache = 1; \
+ continue; \
+} \
+if (!dont_cache)
+#else
+#define IF_ROUTE_POLICE
+#endif
+
+
+static int route4_classify(struct sk_buff *skb, struct tcf_proto *tp,
+ struct tcf_result *res)
+{
+ struct route4_head *head = (struct route4_head*)tp->root;
+ struct dst_entry *dst;
+ struct route4_bucket *b;
+ struct route4_filter *f;
+#ifdef CONFIG_NET_CLS_POLICE
+ int dont_cache = 0;
+#endif
+ u32 id, h;
+ int iif;
+
+ if ((dst = skb->dst) == NULL)
+ goto failure;
+
+ id = dst->tclassid;
+ if (head == NULL)
+ goto old_method;
+
+ iif = ((struct rtable*)dst)->key.iif;
+
+ h = route4_fastmap_hash(id, iif);
+ if (id == head->fastmap[h].id &&
+ iif == head->fastmap[h].iif &&
+ (f = head->fastmap[h].filter) != NULL) {
+ if (f == ROUTE4_FAILURE)
+ goto failure;
+
+ *res = f->res;
+ return 0;
+ }
+
+ h = route4_hash_to(id);
+
+restart:
+ if ((b = head->table[h]) != NULL) {
+ f = b->ht[route4_hash_from(id)];
+
+ for ( ; f; f = f->next) {
+ if (f->id == id) {
+ *res = f->res;
+ IF_ROUTE_POLICE route4_set_fastmap(head, id, iif, f);
+ return 0;
+ }
+ }
+
+ for (f = b->ht[route4_hash_iif(iif)]; f; f = f->next) {
+ if (f->iif == iif) {
+ *res = f->res;
+ IF_ROUTE_POLICE route4_set_fastmap(head, id, iif, f);
+ return 0;
+ }
+ }
+
+ for (f = b->ht[route4_hash_wild()]; f; f = f->next) {
+ *res = f->res;
+ IF_ROUTE_POLICE route4_set_fastmap(head, id, iif, f);
+ return 0;
+ }
+
+ }
+ if (h < 256) {
+ h = 256;
+ id &= ~0xFFFF;
+ goto restart;
+ }
+
+#ifdef CONFIG_NET_CLS_POLICE
+ if (!dont_cache)
+#endif
+ route4_set_fastmap(head, id, iif, ROUTE4_FAILURE);
+failure:
+ return -1;
+
+old_method:
+ if (id && (TC_H_MAJ(id) == 0 ||
+ !(TC_H_MAJ(id^tp->q->handle)))) {
+ res->classid = id;
+ res->class = 0;
+ return 0;
+ }
+ return -1;
+}
+
+static u32 to_hash(u32 id)
+{
+ u32 h = id&0xFF;
+ if (id&0x8000)
+ h += 256;
+ return h;
+}
+
+static u32 from_hash(u32 id)
+{
+ id &= 0xFFFF;
+ if (id == 0xFFFF)
+ return 32;
+ if (!(id & 0x8000)) {
+ if (id > 255)
+ return 256;
+ return id&0xF;
+ }
+ return 16 + (id&0xF);
+}
+
+static unsigned long route4_get(struct tcf_proto *tp, u32 handle)
+{
+ struct route4_head *head = (struct route4_head*)tp->root;
+ struct route4_bucket *b;
+ struct route4_filter *f;
+ unsigned h1, h2;
+
+ if (!head)
+ return 0;
+
+ h1 = to_hash(handle);
+ if (h1 > 256)
+ return 0;
+
+ h2 = from_hash(handle>>16);
+ if (h2 > 32)
+ return 0;
+
+ if ((b = head->table[h1]) != NULL) {
+ for (f = b->ht[h2]; f; f = f->next)
+ if (f->handle == handle)
+ return (unsigned long)f;
+ }
+ return 0;
+}
+
+static void route4_put(struct tcf_proto *tp, unsigned long f)
+{
+}
+
+static int route4_init(struct tcf_proto *tp)
+{
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void route4_destroy(struct tcf_proto *tp)
+{
+ struct route4_head *head = xchg(&tp->root, NULL);
+ int h1, h2;
+
+ if (head == NULL) {
+ MOD_DEC_USE_COUNT;
+ return;
+ }
+
+ for (h1=0; h1<=256; h1++) {
+ struct route4_bucket *b;
+
+ if ((b = head->table[h1]) != NULL) {
+ for (h2=0; h2<=32; h2++) {
+ struct route4_filter *f;
+
+ while ((f = b->ht[h2]) != NULL) {
+ unsigned long cl;
+
+ b->ht[h2] = f->next;
+ if ((cl = __cls_set_class(&f->res.class, 0)) != 0)
+ tp->q->ops->cl_ops->unbind_tcf(tp->q, cl);
+#ifdef CONFIG_NET_CLS_POLICE
+ tcf_police_release(f->police);
+#endif
+ kfree(f);
+ }
+ }
+ kfree(b);
+ }
+ }
+ kfree(head);
+ MOD_DEC_USE_COUNT;
+}
+
+static int route4_delete(struct tcf_proto *tp, unsigned long arg)
+{
+ struct route4_head *head = (struct route4_head*)tp->root;
+ struct route4_filter **fp, *f = (struct route4_filter*)arg;
+ unsigned h = 0;
+ struct route4_bucket *b;
+ int i;
+
+ if (!head || !f)
+ return -EINVAL;
+
+ h = f->handle;
+ b = f->bkt;
+
+ for (fp = &b->ht[from_hash(h>>16)]; *fp; fp = &(*fp)->next) {
+ if (*fp == f) {
+ unsigned long cl;
+
+ tcf_tree_lock(tp);
+ *fp = f->next;
+ tcf_tree_unlock(tp);
+
+ route4_reset_fastmap(tp->q->dev, head, f->id);
+
+ if ((cl = cls_set_class(tp, &f->res.class, 0)) != 0)
+ tp->q->ops->cl_ops->unbind_tcf(tp->q, cl);
+
+#ifdef CONFIG_NET_CLS_POLICE
+ tcf_police_release(f->police);
+#endif
+ kfree(f);
+
+ /* Strip tree */
+
+ for (i=0; i<=32; i++)
+ if (b->ht[i])
+ return 0;
+
+ /* OK, session has no flows */
+ tcf_tree_lock(tp);
+ head->table[to_hash(h)] = NULL;
+ tcf_tree_unlock(tp);
+
+ kfree(b);
+ return 0;
+ }
+ }
+ return 0;
+}
+
+static int route4_change(struct tcf_proto *tp, unsigned long base,
+ u32 handle,
+ struct rtattr **tca,
+ unsigned long *arg)
+{
+ struct route4_head *head = tp->root;
+ struct route4_filter *f, *f1, **ins_f;
+ struct route4_bucket *b;
+ struct rtattr *opt = tca[TCA_OPTIONS-1];
+ struct rtattr *tb[TCA_ROUTE4_MAX];
+ unsigned h1, h2;
+ int err;
+
+ if (opt == NULL)
+ return handle ? -EINVAL : 0;
+
+ if (rtattr_parse(tb, TCA_ROUTE4_MAX, RTA_DATA(opt), RTA_PAYLOAD(opt)) < 0)
+ return -EINVAL;
+
+ if ((f = (struct route4_filter*)*arg) != NULL) {
+ /* Node exists: adjust only classid */
+
+ if (f->handle != handle && handle)
+ return -EINVAL;
+ if (tb[TCA_ROUTE4_CLASSID-1]) {
+ unsigned long cl;
+
+ f->res.classid = *(u32*)RTA_DATA(tb[TCA_ROUTE4_CLASSID-1]);
+ cl = cls_set_class(tp, &f->res.class, tp->q->ops->cl_ops->bind_tcf(tp->q, base, f->res.classid));
+ if (cl)
+ tp->q->ops->cl_ops->unbind_tcf(tp->q, cl);
+ }
+#ifdef CONFIG_NET_CLS_POLICE
+ if (tb[TCA_ROUTE4_POLICE-1]) {
+ struct tcf_police *police = tcf_police_locate(tb[TCA_ROUTE4_POLICE-1], tca[TCA_RATE-1]);
+
+ tcf_tree_lock(tp);
+ police = xchg(&f->police, police);
+ tcf_tree_unlock(tp);
+
+ tcf_police_release(police);
+ }
+#endif
+ return 0;
+ }
+
+ /* Now more serious part... */
+
+ if (head == NULL) {
+ head = kmalloc(sizeof(struct route4_head), GFP_KERNEL);
+ if (head == NULL)
+ return -ENOBUFS;
+ memset(head, 0, sizeof(struct route4_head));
+
+ tcf_tree_lock(tp);
+ tp->root = head;
+ tcf_tree_unlock(tp);
+ }
+
+ f = kmalloc(sizeof(struct route4_filter), GFP_KERNEL);
+ if (f == NULL)
+ return -ENOBUFS;
+
+ memset(f, 0, sizeof(*f));
+
+ err = -EINVAL;
+ f->handle = 0x8000;
+ if (tb[TCA_ROUTE4_TO-1]) {
+ if (handle&0x8000)
+ goto errout;
+ if (RTA_PAYLOAD(tb[TCA_ROUTE4_TO-1]) < 4)
+ goto errout;
+ f->id = *(u32*)RTA_DATA(tb[TCA_ROUTE4_TO-1]);
+ if (f->id > 0xFF)
+ goto errout;
+ f->handle = f->id;
+ }
+ if (tb[TCA_ROUTE4_FROM-1]) {
+ u32 sid;
+ if (tb[TCA_ROUTE4_IIF-1])
+ goto errout;
+ if (RTA_PAYLOAD(tb[TCA_ROUTE4_FROM-1]) < 4)
+ goto errout;
+ sid = (*(u32*)RTA_DATA(tb[TCA_ROUTE4_FROM-1]));
+ if (sid > 0xFF)
+ goto errout;
+ f->handle |= sid<<16;
+ f->id |= sid<<16;
+ } else if (tb[TCA_ROUTE4_IIF-1]) {
+ if (RTA_PAYLOAD(tb[TCA_ROUTE4_IIF-1]) < 4)
+ goto errout;
+ f->iif = *(u32*)RTA_DATA(tb[TCA_ROUTE4_IIF-1]);
+ if (f->iif > 0x7FFF)
+ goto errout;
+ f->handle |= (f->iif|0x8000)<<16;
+ } else
+ f->handle |= 0xFFFF<<16;
+
+ if (handle) {
+ f->handle |= handle&0x7F00;
+ if (f->handle != handle)
+ goto errout;
+ }
+
+ if (tb[TCA_ROUTE4_CLASSID-1]) {
+ if (RTA_PAYLOAD(tb[TCA_ROUTE4_CLASSID-1]) < 4)
+ goto errout;
+ f->res.classid = *(u32*)RTA_DATA(tb[TCA_ROUTE4_CLASSID-1]);
+ }
+
+ h1 = to_hash(f->handle);
+ if ((b = head->table[h1]) == NULL) {
+ err = -ENOBUFS;
+ b = kmalloc(sizeof(struct route4_bucket), GFP_KERNEL);
+ if (b == NULL)
+ goto errout;
+ memset(b, 0, sizeof(*b));
+
+ tcf_tree_lock(tp);
+ head->table[h1] = b;
+ tcf_tree_unlock(tp);
+ }
+ f->bkt = b;
+
+ err = -EEXIST;
+ h2 = from_hash(f->handle>>16);
+ for (ins_f = &b->ht[h2]; (f1=*ins_f) != NULL; ins_f = &f1->next) {
+ if (f->handle < f1->handle)
+ break;
+ if (f1->handle == f->handle)
+ goto errout;
+ }
+
+ cls_set_class(tp, &f->res.class, tp->q->ops->cl_ops->bind_tcf(tp->q, base, f->res.classid));
+#ifdef CONFIG_NET_CLS_POLICE
+ if (tb[TCA_ROUTE4_POLICE-1])
+ f->police = tcf_police_locate(tb[TCA_ROUTE4_POLICE-1], tca[TCA_RATE-1]);
+#endif
+
+ f->next = f1;
+ tcf_tree_lock(tp);
+ *ins_f = f;
+ tcf_tree_unlock(tp);
+
+ route4_reset_fastmap(tp->q->dev, head, f->id);
+ *arg = (unsigned long)f;
+ return 0;
+
+errout:
+ if (f)
+ kfree(f);
+ return err;
+}
+
+static void route4_walk(struct tcf_proto *tp, struct tcf_walker *arg)
+{
+ struct route4_head *head = tp->root;
+ unsigned h, h1;
+
+ if (head == NULL)
+ arg->stop = 1;
+
+ if (arg->stop)
+ return;
+
+ for (h = 0; h <= 256; h++) {
+ struct route4_bucket *b = head->table[h];
+
+ if (b) {
+ for (h1 = 0; h1 <= 32; h1++) {
+ struct route4_filter *f;
+
+ for (f = b->ht[h1]; f; f = f->next) {
+ if (arg->count < arg->skip) {
+ arg->count++;
+ continue;
+ }
+ if (arg->fn(tp, (unsigned long)f, arg) < 0) {
+ arg->stop = 1;
+ return;
+ }
+ arg->count++;
+ }
+ }
+ }
+ }
+}
+
+static int route4_dump(struct tcf_proto *tp, unsigned long fh,
+ struct sk_buff *skb, struct tcmsg *t)
+{
+ struct route4_filter *f = (struct route4_filter*)fh;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+ u32 id;
+
+ if (f == NULL)
+ return skb->len;
+
+ t->tcm_handle = f->handle;
+
+ rta = (struct rtattr*)b;
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+
+ if (!(f->handle&0x8000)) {
+ id = f->id&0xFF;
+ RTA_PUT(skb, TCA_ROUTE4_TO, sizeof(id), &id);
+ }
+ if (f->handle&0x80000000) {
+ if ((f->handle>>16) != 0xFFFF)
+ RTA_PUT(skb, TCA_ROUTE4_IIF, sizeof(f->iif), &f->iif);
+ } else {
+ id = f->id>>16;
+ RTA_PUT(skb, TCA_ROUTE4_FROM, sizeof(id), &id);
+ }
+ if (f->res.classid)
+ RTA_PUT(skb, TCA_ROUTE4_CLASSID, 4, &f->res.classid);
+#ifdef CONFIG_NET_CLS_POLICE
+ if (f->police) {
+ struct rtattr * p_rta = (struct rtattr*)skb->tail;
+
+ RTA_PUT(skb, TCA_ROUTE4_POLICE, 0, NULL);
+
+ if (tcf_police_dump(skb, f->police) < 0)
+ goto rtattr_failure;
+
+ p_rta->rta_len = skb->tail - (u8*)p_rta;
+ }
+#endif
+
+ rta->rta_len = skb->tail - b;
+#ifdef CONFIG_NET_CLS_POLICE
+ if (f->police) {
+ if (qdisc_copy_stats(skb, &f->police->stats))
+ goto rtattr_failure;
+ }
+#endif
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+struct tcf_proto_ops cls_route4_ops = {
+ NULL,
+ "route",
+ route4_classify,
+ route4_init,
+ route4_destroy,
+
+ route4_get,
+ route4_put,
+ route4_change,
+ route4_delete,
+ route4_walk,
+ route4_dump
+};
+
+#ifdef MODULE
+int init_module(void)
+{
+ return register_tcf_proto_ops(&cls_route4_ops);
+}
+
+void cleanup_module(void)
+{
+ unregister_tcf_proto_ops(&cls_route4_ops);
+}
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/cls_rsvp.c b/uClinux-2.4.31-uc0/net/sched/cls_rsvp.c
new file mode 100644
index 0000000..05a937b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/cls_rsvp.c
@@ -0,0 +1,42 @@
+/*
+ * net/sched/cls_rsvp.c Special RSVP packet classifier for IPv4.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+#define RSVP_DST_LEN 1
+#define RSVP_ID "rsvp"
+#define RSVP_OPS cls_rsvp_ops
+
+#include "cls_rsvp.h"
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/cls_rsvp.h b/uClinux-2.4.31-uc0/net/sched/cls_rsvp.h
new file mode 100644
index 0000000..0c46d76
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/cls_rsvp.h
@@ -0,0 +1,698 @@
+/*
+ * net/sched/cls_rsvp.h Template file for RSVPv[46] classifiers.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+/*
+ Comparing to general packet classification problem,
+ RSVP needs only sevaral relatively simple rules:
+
+ * (dst, protocol) are always specified,
+ so that we are able to hash them.
+ * src may be exact, or may be wildcard, so that
+ we can keep a hash table plus one wildcard entry.
+ * source port (or flow label) is important only if src is given.
+
+ IMPLEMENTATION.
+
+ We use a two level hash table: The top level is keyed by
+ destination address and protocol ID, every bucket contains a list
+ of "rsvp sessions", identified by destination address, protocol and
+ DPI(="Destination Port ID"): triple (key, mask, offset).
+
+ Every bucket has a smaller hash table keyed by source address
+ (cf. RSVP flowspec) and one wildcard entry for wildcard reservations.
+ Every bucket is again a list of "RSVP flows", selected by
+ source address and SPI(="Source Port ID" here rather than
+ "security parameter index"): triple (key, mask, offset).
+
+
+ NOTE 1. All the packets with IPv6 extension headers (but AH and ESP)
+ and all fragmented packets go to the best-effort traffic class.
+
+
+ NOTE 2. Two "port id"'s seems to be redundant, rfc2207 requires
+ only one "Generalized Port Identifier". So that for classic
+ ah, esp (and udp,tcp) both *pi should coincide or one of them
+ should be wildcard.
+
+ At first sight, this redundancy is just a waste of CPU
+ resources. But DPI and SPI add the possibility to assign different
+ priorities to GPIs. Look also at note 4 about tunnels below.
+
+
+ NOTE 3. One complication is the case of tunneled packets.
+ We implement it as following: if the first lookup
+ matches a special session with "tunnelhdr" value not zero,
+ flowid doesn't contain the true flow ID, but the tunnel ID (1...255).
+ In this case, we pull tunnelhdr bytes and restart lookup
+ with tunnel ID added to the list of keys. Simple and stupid 8)8)
+ It's enough for PIMREG and IPIP.
+
+
+ NOTE 4. Two GPIs make it possible to parse even GRE packets.
+ F.e. DPI can select ETH_P_IP (and necessary flags to make
+ tunnelhdr correct) in GRE protocol field and SPI matches
+ GRE key. Is it not nice? 8)8)
+
+
+ Well, as result, despite its simplicity, we get a pretty
+ powerful classification engine. */
+
+#include <linux/config.h>
+
+struct rsvp_head
+{
+ u32 tmap[256/32];
+ u32 hgenerator;
+ u8 tgenerator;
+ struct rsvp_session *ht[256];
+};
+
+struct rsvp_session
+{
+ struct rsvp_session *next;
+ u32 dst[RSVP_DST_LEN];
+ struct tc_rsvp_gpi dpi;
+ u8 protocol;
+ u8 tunnelid;
+ /* 16 (src,sport) hash slots, and one wildcard source slot */
+ struct rsvp_filter *ht[16+1];
+};
+
+
+struct rsvp_filter
+{
+ struct rsvp_filter *next;
+ u32 src[RSVP_DST_LEN];
+ struct tc_rsvp_gpi spi;
+ u8 tunnelhdr;
+
+ struct tcf_result res;
+#ifdef CONFIG_NET_CLS_POLICE
+ struct tcf_police *police;
+#endif
+
+ u32 handle;
+ struct rsvp_session *sess;
+};
+
+static __inline__ unsigned hash_dst(u32 *dst, u8 protocol, u8 tunnelid)
+{
+ unsigned h = dst[RSVP_DST_LEN-1];
+ h ^= h>>16;
+ h ^= h>>8;
+ return (h ^ protocol ^ tunnelid) & 0xFF;
+}
+
+static __inline__ unsigned hash_src(u32 *src)
+{
+ unsigned h = src[RSVP_DST_LEN-1];
+ h ^= h>>16;
+ h ^= h>>8;
+ h ^= h>>4;
+ return h & 0xF;
+}
+
+#ifdef CONFIG_NET_CLS_POLICE
+#define RSVP_POLICE() \
+if (f->police) { \
+ int pol_res = tcf_police(skb, f->police); \
+ if (pol_res < 0) continue; \
+ if (pol_res) return pol_res; \
+}
+#else
+#define RSVP_POLICE()
+#endif
+
+
+static int rsvp_classify(struct sk_buff *skb, struct tcf_proto *tp,
+ struct tcf_result *res)
+{
+ struct rsvp_session **sht = ((struct rsvp_head*)tp->root)->ht;
+ struct rsvp_session *s;
+ struct rsvp_filter *f;
+ unsigned h1, h2;
+ u32 *dst, *src;
+ u8 protocol;
+ u8 tunnelid = 0;
+ u8 *xprt;
+#if RSVP_DST_LEN == 4
+ struct ipv6hdr *nhptr = skb->nh.ipv6h;
+#else
+ struct iphdr *nhptr = skb->nh.iph;
+#endif
+
+restart:
+
+#if RSVP_DST_LEN == 4
+ src = &nhptr->saddr.s6_addr32[0];
+ dst = &nhptr->daddr.s6_addr32[0];
+ protocol = nhptr->nexthdr;
+ xprt = ((u8*)nhptr) + sizeof(struct ipv6hdr);
+#else
+ src = &nhptr->saddr;
+ dst = &nhptr->daddr;
+ protocol = nhptr->protocol;
+ xprt = ((u8*)nhptr) + (nhptr->ihl<<2);
+ if (nhptr->frag_off&__constant_htons(IP_MF|IP_OFFSET))
+ return -1;
+#endif
+
+ h1 = hash_dst(dst, protocol, tunnelid);
+ h2 = hash_src(src);
+
+ for (s = sht[h1]; s; s = s->next) {
+ if (dst[RSVP_DST_LEN-1] == s->dst[RSVP_DST_LEN-1] &&
+ protocol == s->protocol &&
+ !(s->dpi.mask & (*(u32*)(xprt+s->dpi.offset)^s->dpi.key))
+#if RSVP_DST_LEN == 4
+ && dst[0] == s->dst[0]
+ && dst[1] == s->dst[1]
+ && dst[2] == s->dst[2]
+#endif
+ && tunnelid == s->tunnelid) {
+
+ for (f = s->ht[h2]; f; f = f->next) {
+ if (src[RSVP_DST_LEN-1] == f->src[RSVP_DST_LEN-1] &&
+ !(f->spi.mask & (*(u32*)(xprt+f->spi.offset)^f->spi.key))
+#if RSVP_DST_LEN == 4
+ && src[0] == f->src[0]
+ && src[1] == f->src[1]
+ && src[2] == f->src[2]
+#endif
+ ) {
+ *res = f->res;
+
+ RSVP_POLICE();
+
+matched:
+ if (f->tunnelhdr == 0)
+ return 0;
+
+ tunnelid = f->res.classid;
+ nhptr = (void*)(xprt + f->tunnelhdr - sizeof(*nhptr));
+ goto restart;
+ }
+ }
+
+ /* And wildcard bucket... */
+ for (f = s->ht[16]; f; f = f->next) {
+ *res = f->res;
+ RSVP_POLICE();
+ goto matched;
+ }
+ return -1;
+ }
+ }
+ return -1;
+}
+
+static unsigned long rsvp_get(struct tcf_proto *tp, u32 handle)
+{
+ struct rsvp_session **sht = ((struct rsvp_head*)tp->root)->ht;
+ struct rsvp_session *s;
+ struct rsvp_filter *f;
+ unsigned h1 = handle&0xFF;
+ unsigned h2 = (handle>>8)&0xFF;
+
+ if (h2 > 16)
+ return 0;
+
+ for (s = sht[h1]; s; s = s->next) {
+ for (f = s->ht[h2]; f; f = f->next) {
+ if (f->handle == handle)
+ return (unsigned long)f;
+ }
+ }
+ return 0;
+}
+
+static void rsvp_put(struct tcf_proto *tp, unsigned long f)
+{
+}
+
+static int rsvp_init(struct tcf_proto *tp)
+{
+ struct rsvp_head *data;
+
+ MOD_INC_USE_COUNT;
+ data = kmalloc(sizeof(struct rsvp_head), GFP_KERNEL);
+ if (data) {
+ memset(data, 0, sizeof(struct rsvp_head));
+ tp->root = data;
+ return 0;
+ }
+ MOD_DEC_USE_COUNT;
+ return -ENOBUFS;
+}
+
+static void rsvp_destroy(struct tcf_proto *tp)
+{
+ struct rsvp_head *data = xchg(&tp->root, NULL);
+ struct rsvp_session **sht;
+ int h1, h2;
+
+ if (data == NULL)
+ return;
+
+ sht = data->ht;
+
+ for (h1=0; h1<256; h1++) {
+ struct rsvp_session *s;
+
+ while ((s = sht[h1]) != NULL) {
+ sht[h1] = s->next;
+
+ for (h2=0; h2<=16; h2++) {
+ struct rsvp_filter *f;
+
+ while ((f = s->ht[h2]) != NULL) {
+ unsigned long cl;
+
+ s->ht[h2] = f->next;
+ if ((cl = __cls_set_class(&f->res.class, 0)) != 0)
+ tp->q->ops->cl_ops->unbind_tcf(tp->q, cl);
+#ifdef CONFIG_NET_CLS_POLICE
+ tcf_police_release(f->police);
+#endif
+ kfree(f);
+ }
+ }
+ kfree(s);
+ }
+ }
+ kfree(data);
+ MOD_DEC_USE_COUNT;
+}
+
+static int rsvp_delete(struct tcf_proto *tp, unsigned long arg)
+{
+ struct rsvp_filter **fp, *f = (struct rsvp_filter*)arg;
+ unsigned h = f->handle;
+ struct rsvp_session **sp;
+ struct rsvp_session *s = f->sess;
+ int i;
+
+ for (fp = &s->ht[(h>>8)&0xFF]; *fp; fp = &(*fp)->next) {
+ if (*fp == f) {
+ unsigned long cl;
+
+
+ tcf_tree_lock(tp);
+ *fp = f->next;
+ tcf_tree_unlock(tp);
+
+ if ((cl = cls_set_class(tp, &f->res.class, 0)) != 0)
+ tp->q->ops->cl_ops->unbind_tcf(tp->q, cl);
+
+#ifdef CONFIG_NET_CLS_POLICE
+ tcf_police_release(f->police);
+#endif
+
+ kfree(f);
+
+ /* Strip tree */
+
+ for (i=0; i<=16; i++)
+ if (s->ht[i])
+ return 0;
+
+ /* OK, session has no flows */
+ for (sp = &((struct rsvp_head*)tp->root)->ht[h&0xFF];
+ *sp; sp = &(*sp)->next) {
+ if (*sp == s) {
+ tcf_tree_lock(tp);
+ *sp = s->next;
+ tcf_tree_unlock(tp);
+
+ kfree(s);
+ return 0;
+ }
+ }
+
+ return 0;
+ }
+ }
+ return 0;
+}
+
+static unsigned gen_handle(struct tcf_proto *tp, unsigned salt)
+{
+ struct rsvp_head *data = tp->root;
+ int i = 0xFFFF;
+
+ while (i-- > 0) {
+ u32 h;
+ if ((data->hgenerator += 0x10000) == 0)
+ data->hgenerator = 0x10000;
+ h = data->hgenerator|salt;
+ if (rsvp_get(tp, h) == 0)
+ return h;
+ }
+ return 0;
+}
+
+static int tunnel_bts(struct rsvp_head *data)
+{
+ int n = data->tgenerator>>5;
+ u32 b = 1<<(data->tgenerator&0x1F);
+
+ if (data->tmap[n]&b)
+ return 0;
+ data->tmap[n] |= b;
+ return 1;
+}
+
+static void tunnel_recycle(struct rsvp_head *data)
+{
+ struct rsvp_session **sht = data->ht;
+ u32 tmap[256/32];
+ int h1, h2;
+
+ memset(tmap, 0, sizeof(tmap));
+
+ for (h1=0; h1<256; h1++) {
+ struct rsvp_session *s;
+ for (s = sht[h1]; s; s = s->next) {
+ for (h2=0; h2<=16; h2++) {
+ struct rsvp_filter *f;
+
+ for (f = s->ht[h2]; f; f = f->next) {
+ if (f->tunnelhdr == 0)
+ continue;
+ data->tgenerator = f->res.classid;
+ tunnel_bts(data);
+ }
+ }
+ }
+ }
+
+ memcpy(data->tmap, tmap, sizeof(tmap));
+}
+
+static u32 gen_tunnel(struct rsvp_head *data)
+{
+ int i, k;
+
+ for (k=0; k<2; k++) {
+ for (i=255; i>0; i--) {
+ if (++data->tgenerator == 0)
+ data->tgenerator = 1;
+ if (tunnel_bts(data))
+ return data->tgenerator;
+ }
+ tunnel_recycle(data);
+ }
+ return 0;
+}
+
+static int rsvp_change(struct tcf_proto *tp, unsigned long base,
+ u32 handle,
+ struct rtattr **tca,
+ unsigned long *arg)
+{
+ struct rsvp_head *data = tp->root;
+ struct rsvp_filter *f, **fp;
+ struct rsvp_session *s, **sp;
+ struct tc_rsvp_pinfo *pinfo = NULL;
+ struct rtattr *opt = tca[TCA_OPTIONS-1];
+ struct rtattr *tb[TCA_RSVP_MAX];
+ unsigned h1, h2;
+ u32 *dst;
+ int err;
+
+ if (opt == NULL)
+ return handle ? -EINVAL : 0;
+
+ if (rtattr_parse(tb, TCA_RSVP_MAX, RTA_DATA(opt), RTA_PAYLOAD(opt)) < 0)
+ return -EINVAL;
+
+ if ((f = (struct rsvp_filter*)*arg) != NULL) {
+ /* Node exists: adjust only classid */
+
+ if (f->handle != handle && handle)
+ return -EINVAL;
+ if (tb[TCA_RSVP_CLASSID-1]) {
+ unsigned long cl;
+
+ f->res.classid = *(u32*)RTA_DATA(tb[TCA_RSVP_CLASSID-1]);
+ cl = cls_set_class(tp, &f->res.class, tp->q->ops->cl_ops->bind_tcf(tp->q, base, f->res.classid));
+ if (cl)
+ tp->q->ops->cl_ops->unbind_tcf(tp->q, cl);
+ }
+#ifdef CONFIG_NET_CLS_POLICE
+ if (tb[TCA_RSVP_POLICE-1]) {
+ struct tcf_police *police = tcf_police_locate(tb[TCA_RSVP_POLICE-1], tca[TCA_RATE-1]);
+
+ tcf_tree_lock(tp);
+ police = xchg(&f->police, police);
+ tcf_tree_unlock(tp);
+
+ tcf_police_release(police);
+ }
+#endif
+ return 0;
+ }
+
+ /* Now more serious part... */
+ if (handle)
+ return -EINVAL;
+ if (tb[TCA_RSVP_DST-1] == NULL)
+ return -EINVAL;
+
+ f = kmalloc(sizeof(struct rsvp_filter), GFP_KERNEL);
+ if (f == NULL)
+ return -ENOBUFS;
+
+ memset(f, 0, sizeof(*f));
+ h2 = 16;
+ if (tb[TCA_RSVP_SRC-1]) {
+ err = -EINVAL;
+ if (RTA_PAYLOAD(tb[TCA_RSVP_SRC-1]) != sizeof(f->src))
+ goto errout;
+ memcpy(f->src, RTA_DATA(tb[TCA_RSVP_SRC-1]), sizeof(f->src));
+ h2 = hash_src(f->src);
+ }
+ if (tb[TCA_RSVP_PINFO-1]) {
+ err = -EINVAL;
+ if (RTA_PAYLOAD(tb[TCA_RSVP_PINFO-1]) < sizeof(struct tc_rsvp_pinfo))
+ goto errout;
+ pinfo = RTA_DATA(tb[TCA_RSVP_PINFO-1]);
+ f->spi = pinfo->spi;
+ f->tunnelhdr = pinfo->tunnelhdr;
+ }
+ if (tb[TCA_RSVP_CLASSID-1]) {
+ err = -EINVAL;
+ if (RTA_PAYLOAD(tb[TCA_RSVP_CLASSID-1]) != 4)
+ goto errout;
+ f->res.classid = *(u32*)RTA_DATA(tb[TCA_RSVP_CLASSID-1]);
+ }
+
+ err = -EINVAL;
+ if (RTA_PAYLOAD(tb[TCA_RSVP_DST-1]) != sizeof(f->src))
+ goto errout;
+ dst = RTA_DATA(tb[TCA_RSVP_DST-1]);
+ h1 = hash_dst(dst, pinfo ? pinfo->protocol : 0, pinfo ? pinfo->tunnelid : 0);
+
+ err = -ENOMEM;
+ if ((f->handle = gen_handle(tp, h1 | (h2<<8))) == 0)
+ goto errout;
+
+ if (f->tunnelhdr) {
+ err = -EINVAL;
+ if (f->res.classid > 255)
+ goto errout;
+
+ err = -ENOMEM;
+ if (f->res.classid == 0 &&
+ (f->res.classid = gen_tunnel(data)) == 0)
+ goto errout;
+ }
+
+ for (sp = &data->ht[h1]; (s=*sp) != NULL; sp = &s->next) {
+ if (dst[RSVP_DST_LEN-1] == s->dst[RSVP_DST_LEN-1] &&
+ pinfo && pinfo->protocol == s->protocol &&
+ memcmp(&pinfo->dpi, &s->dpi, sizeof(s->dpi)) == 0
+#if RSVP_DST_LEN == 4
+ && dst[0] == s->dst[0]
+ && dst[1] == s->dst[1]
+ && dst[2] == s->dst[2]
+#endif
+ && pinfo->tunnelid == s->tunnelid) {
+
+insert:
+ /* OK, we found appropriate session */
+
+ fp = &s->ht[h2];
+
+ f->sess = s;
+ if (f->tunnelhdr == 0)
+ cls_set_class(tp, &f->res.class, tp->q->ops->cl_ops->bind_tcf(tp->q, base, f->res.classid));
+#ifdef CONFIG_NET_CLS_POLICE
+ if (tb[TCA_RSVP_POLICE-1])
+ f->police = tcf_police_locate(tb[TCA_RSVP_POLICE-1], tca[TCA_RATE-1]);
+#endif
+
+ for (fp = &s->ht[h2]; *fp; fp = &(*fp)->next)
+ if (((*fp)->spi.mask&f->spi.mask) != f->spi.mask)
+ break;
+ f->next = *fp;
+ wmb();
+ *fp = f;
+
+ *arg = (unsigned long)f;
+ return 0;
+ }
+ }
+
+ /* No session found. Create new one. */
+
+ err = -ENOBUFS;
+ s = kmalloc(sizeof(struct rsvp_session), GFP_KERNEL);
+ if (s == NULL)
+ goto errout;
+ memset(s, 0, sizeof(*s));
+ memcpy(s->dst, dst, sizeof(s->dst));
+
+ if (pinfo) {
+ s->dpi = pinfo->dpi;
+ s->protocol = pinfo->protocol;
+ s->tunnelid = pinfo->tunnelid;
+ }
+ for (sp = &data->ht[h1]; *sp; sp = &(*sp)->next) {
+ if (((*sp)->dpi.mask&s->dpi.mask) != s->dpi.mask)
+ break;
+ }
+ s->next = *sp;
+ wmb();
+ *sp = s;
+
+ goto insert;
+
+errout:
+ if (f)
+ kfree(f);
+ return err;
+}
+
+static void rsvp_walk(struct tcf_proto *tp, struct tcf_walker *arg)
+{
+ struct rsvp_head *head = tp->root;
+ unsigned h, h1;
+
+ if (arg->stop)
+ return;
+
+ for (h = 0; h < 256; h++) {
+ struct rsvp_session *s;
+
+ for (s = head->ht[h]; s; s = s->next) {
+ for (h1 = 0; h1 <= 16; h1++) {
+ struct rsvp_filter *f;
+
+ for (f = s->ht[h1]; f; f = f->next) {
+ if (arg->count < arg->skip) {
+ arg->count++;
+ continue;
+ }
+ if (arg->fn(tp, (unsigned long)f, arg) < 0) {
+ arg->stop = 1;
+ return;
+ }
+ arg->count++;
+ }
+ }
+ }
+ }
+}
+
+static int rsvp_dump(struct tcf_proto *tp, unsigned long fh,
+ struct sk_buff *skb, struct tcmsg *t)
+{
+ struct rsvp_filter *f = (struct rsvp_filter*)fh;
+ struct rsvp_session *s;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+ struct tc_rsvp_pinfo pinfo;
+
+ if (f == NULL)
+ return skb->len;
+ s = f->sess;
+
+ t->tcm_handle = f->handle;
+
+
+ rta = (struct rtattr*)b;
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+
+ RTA_PUT(skb, TCA_RSVP_DST, sizeof(s->dst), &s->dst);
+ pinfo.dpi = s->dpi;
+ pinfo.spi = f->spi;
+ pinfo.protocol = s->protocol;
+ pinfo.tunnelid = s->tunnelid;
+ pinfo.tunnelhdr = f->tunnelhdr;
+ RTA_PUT(skb, TCA_RSVP_PINFO, sizeof(pinfo), &pinfo);
+ if (f->res.classid)
+ RTA_PUT(skb, TCA_RSVP_CLASSID, 4, &f->res.classid);
+ if (((f->handle>>8)&0xFF) != 16)
+ RTA_PUT(skb, TCA_RSVP_SRC, sizeof(f->src), f->src);
+#ifdef CONFIG_NET_CLS_POLICE
+ if (f->police) {
+ struct rtattr * p_rta = (struct rtattr*)skb->tail;
+
+ RTA_PUT(skb, TCA_RSVP_POLICE, 0, NULL);
+
+ if (tcf_police_dump(skb, f->police) < 0)
+ goto rtattr_failure;
+
+ p_rta->rta_len = skb->tail - (u8*)p_rta;
+ }
+#endif
+
+ rta->rta_len = skb->tail - b;
+#ifdef CONFIG_NET_CLS_POLICE
+ if (f->police) {
+ if (qdisc_copy_stats(skb, &f->police->stats))
+ goto rtattr_failure;
+ }
+#endif
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+struct tcf_proto_ops RSVP_OPS = {
+ NULL,
+ RSVP_ID,
+ rsvp_classify,
+ rsvp_init,
+ rsvp_destroy,
+
+ rsvp_get,
+ rsvp_put,
+ rsvp_change,
+ rsvp_delete,
+ rsvp_walk,
+ rsvp_dump
+};
+
+#ifdef MODULE
+int init_module(void)
+{
+ return register_tcf_proto_ops(&RSVP_OPS);
+}
+
+void cleanup_module(void)
+{
+ unregister_tcf_proto_ops(&RSVP_OPS);
+}
+#endif
diff --git a/uClinux-2.4.31-uc0/net/sched/cls_rsvp6.c b/uClinux-2.4.31-uc0/net/sched/cls_rsvp6.c
new file mode 100644
index 0000000..85ed7b4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/cls_rsvp6.c
@@ -0,0 +1,43 @@
+/*
+ * net/sched/cls_rsvp6.c Special RSVP packet classifier for IPv6.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <net/ip.h>
+#include <linux/ipv6.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+#define RSVP_DST_LEN 4
+#define RSVP_ID "rsvp6"
+#define RSVP_OPS cls_rsvp6_ops
+
+#include "cls_rsvp.h"
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/cls_tcindex.c b/uClinux-2.4.31-uc0/net/sched/cls_tcindex.c
new file mode 100644
index 0000000..e77ca20
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/cls_tcindex.c
@@ -0,0 +1,516 @@
+/*
+ * net/sched/cls_tcindex.c Packet classifier for skb->tc_index
+ *
+ * Written 1998,1999 by Werner Almesberger, EPFL ICA
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <net/ip.h>
+#include <net/pkt_sched.h>
+#include <net/route.h>
+
+
+/*
+ * Not quite sure if we need all the xchgs Alexey uses when accessing things.
+ * Can always add them later ... :)
+ */
+
+/*
+ * Passing parameters to the root seems to be done more awkwardly than really
+ * necessary. At least, u32 doesn't seem to use such dirty hacks. To be
+ * verified. FIXME.
+ */
+
+#define PERFECT_HASH_THRESHOLD 64 /* use perfect hash if not bigger */
+#define DEFAULT_HASH_SIZE 64 /* optimized for diffserv */
+
+
+#if 1 /* control */
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+#if 0 /* data */
+#define D2PRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define D2PRINTK(format,args...)
+#endif
+
+
+#define PRIV(tp) ((struct tcindex_data *) (tp)->root)
+
+
+struct tcindex_filter_result {
+ struct tcf_police *police;
+ struct tcf_result res;
+};
+
+struct tcindex_filter {
+ __u16 key;
+ struct tcindex_filter_result result;
+ struct tcindex_filter *next;
+};
+
+
+struct tcindex_data {
+ struct tcindex_filter_result *perfect; /* perfect hash; NULL if none */
+ struct tcindex_filter **h; /* imperfect hash; only used if !perfect;
+ NULL if unused */
+ __u16 mask; /* AND key with mask */
+ int shift; /* shift ANDed key to the right */
+ int hash; /* hash table size; 0 if undefined */
+ int alloc_hash; /* allocated size */
+ int fall_through; /* 0: only classify if explicit match */
+};
+
+
+static struct tcindex_filter_result *lookup(struct tcindex_data *p,__u16 key)
+{
+ struct tcindex_filter *f;
+
+ if (p->perfect)
+ return p->perfect[key].res.class ? p->perfect+key : NULL;
+ if (!p->h)
+ return NULL;
+ for (f = p->h[key % p->hash]; f; f = f->next) {
+ if (f->key == key)
+ return &f->result;
+ }
+ return NULL;
+}
+
+
+static int tcindex_classify(struct sk_buff *skb, struct tcf_proto *tp,
+ struct tcf_result *res)
+{
+ struct tcindex_data *p = PRIV(tp);
+ struct tcindex_filter_result *f;
+
+ D2PRINTK("tcindex_classify(skb %p,tp %p,res %p),p %p\n",skb,tp,res,p);
+
+ f = lookup(p,(skb->tc_index & p->mask) >> p->shift);
+ if (!f) {
+ if (!p->fall_through)
+ return -1;
+ res->classid = TC_H_MAKE(TC_H_MAJ(tp->q->handle),
+ (skb->tc_index& p->mask) >> p->shift);
+ res->class = 0;
+ D2PRINTK("alg 0x%x\n",res->classid);
+ return 0;
+ }
+ *res = f->res;
+ D2PRINTK("map 0x%x\n",res->classid);
+#ifdef CONFIG_NET_CLS_POLICE
+ if (f->police) {
+ int result;
+
+ result = tcf_police(skb,f->police);
+ D2PRINTK("police %d\n",res);
+ return result;
+ }
+#endif
+ return 0;
+}
+
+
+static unsigned long tcindex_get(struct tcf_proto *tp, u32 handle)
+{
+ struct tcindex_data *p = PRIV(tp);
+ struct tcindex_filter_result *r;
+
+ DPRINTK("tcindex_get(tp %p,handle 0x%08x)\n",tp,handle);
+ if (p->perfect && handle >= p->alloc_hash)
+ return 0;
+ r = lookup(PRIV(tp),handle);
+ return r && r->res.class ? (unsigned long) r : 0;
+}
+
+
+static void tcindex_put(struct tcf_proto *tp, unsigned long f)
+{
+ DPRINTK("tcindex_put(tp %p,f 0x%lx)\n",tp,f);
+}
+
+
+static int tcindex_init(struct tcf_proto *tp)
+{
+ struct tcindex_data *p;
+
+ DPRINTK("tcindex_init(tp %p)\n",tp);
+ MOD_INC_USE_COUNT;
+ p = kmalloc(sizeof(struct tcindex_data),GFP_KERNEL);
+ if (!p) {
+ MOD_DEC_USE_COUNT;
+ return -ENOMEM;
+ }
+ tp->root = p;
+ p->perfect = NULL;
+ p->h = NULL;
+ p->hash = 0;
+ p->mask = 0xffff;
+ p->shift = 0;
+ p->fall_through = 1;
+ return 0;
+}
+
+
+static int
+__tcindex_delete(struct tcf_proto *tp, unsigned long arg, int lock)
+{
+ struct tcindex_data *p = PRIV(tp);
+ struct tcindex_filter_result *r = (struct tcindex_filter_result *) arg;
+ struct tcindex_filter *f = NULL;
+ unsigned long cl;
+
+ DPRINTK("tcindex_delete(tp %p,arg 0x%lx),p %p,f %p\n",tp,arg,p,f);
+ if (p->perfect) {
+ if (!r->res.class)
+ return -ENOENT;
+ } else {
+ int i;
+ struct tcindex_filter **walk = NULL;
+
+ for (i = 0; i < p->hash; i++)
+ for (walk = p->h+i; *walk; walk = &(*walk)->next)
+ if (&(*walk)->result == r)
+ goto found;
+ return -ENOENT;
+
+found:
+ f = *walk;
+ if (lock)
+ tcf_tree_lock(tp);
+ *walk = f->next;
+ if (lock)
+ tcf_tree_unlock(tp);
+ }
+ cl = __cls_set_class(&r->res.class,0);
+ if (cl)
+ tp->q->ops->cl_ops->unbind_tcf(tp->q,cl);
+#ifdef CONFIG_NET_CLS_POLICE
+ tcf_police_release(r->police);
+#endif
+ if (f)
+ kfree(f);
+ return 0;
+}
+
+static int tcindex_delete(struct tcf_proto *tp, unsigned long arg)
+{
+ return __tcindex_delete(tp, arg, 1);
+}
+
+/*
+ * There are no parameters for tcindex_init, so we overload tcindex_change
+ */
+
+
+static int tcindex_change(struct tcf_proto *tp,unsigned long base,u32 handle,
+ struct rtattr **tca,unsigned long *arg)
+{
+ struct tcindex_filter_result new_filter_result = {
+ NULL, /* no policing */
+ { 0,0 }, /* no classification */
+ };
+ struct rtattr *opt = tca[TCA_OPTIONS-1];
+ struct rtattr *tb[TCA_TCINDEX_MAX];
+ struct tcindex_data *p = PRIV(tp);
+ struct tcindex_filter *f;
+ struct tcindex_filter_result *r = (struct tcindex_filter_result *) *arg;
+ struct tcindex_filter **walk;
+ int hash,shift;
+ __u16 mask;
+
+ DPRINTK("tcindex_change(tp %p,handle 0x%08x,tca %p,arg %p),opt %p,"
+ "p %p,r %p\n",tp,handle,tca,arg,opt,p,r);
+ if (arg)
+ DPRINTK("*arg = 0x%lx\n",*arg);
+ if (!opt)
+ return 0;
+ if (rtattr_parse(tb,TCA_TCINDEX_MAX,RTA_DATA(opt),RTA_PAYLOAD(opt)) < 0)
+ return -EINVAL;
+ if (!tb[TCA_TCINDEX_HASH-1]) {
+ hash = p->hash;
+ } else {
+ if (RTA_PAYLOAD(tb[TCA_TCINDEX_HASH-1]) < sizeof(int))
+ return -EINVAL;
+ hash = *(int *) RTA_DATA(tb[TCA_TCINDEX_HASH-1]);
+ }
+ if (!tb[TCA_TCINDEX_MASK-1]) {
+ mask = p->mask;
+ } else {
+ if (RTA_PAYLOAD(tb[TCA_TCINDEX_MASK-1]) < sizeof(__u16))
+ return -EINVAL;
+ mask = *(__u16 *) RTA_DATA(tb[TCA_TCINDEX_MASK-1]);
+ }
+ if (!tb[TCA_TCINDEX_SHIFT-1])
+ shift = p->shift;
+ else {
+ if (RTA_PAYLOAD(tb[TCA_TCINDEX_SHIFT-1]) < sizeof(__u16))
+ return -EINVAL;
+ shift = *(int *) RTA_DATA(tb[TCA_TCINDEX_SHIFT-1]);
+ }
+ if (p->perfect && hash <= (mask >> shift))
+ return -EBUSY;
+ if (p->perfect && hash > p->alloc_hash)
+ return -EBUSY;
+ if (p->h && hash != p->alloc_hash)
+ return -EBUSY;
+ p->hash = hash;
+ p->mask = mask;
+ p->shift = shift;
+ if (tb[TCA_TCINDEX_FALL_THROUGH-1]) {
+ if (RTA_PAYLOAD(tb[TCA_TCINDEX_FALL_THROUGH-1]) < sizeof(int))
+ return -EINVAL;
+ p->fall_through =
+ *(int *) RTA_DATA(tb[TCA_TCINDEX_FALL_THROUGH-1]);
+ }
+ DPRINTK("classid/police %p/%p\n",tb[TCA_TCINDEX_CLASSID-1],
+ tb[TCA_TCINDEX_POLICE-1]);
+ if (!tb[TCA_TCINDEX_CLASSID-1] && !tb[TCA_TCINDEX_POLICE-1])
+ return 0;
+ if (!hash) {
+ if ((mask >> shift) < PERFECT_HASH_THRESHOLD) {
+ p->hash = (mask >> shift)+1;
+ } else {
+ p->hash = DEFAULT_HASH_SIZE;
+ }
+ }
+ if (!p->perfect && !p->h) {
+ p->alloc_hash = p->hash;
+ DPRINTK("hash %d mask %d\n",p->hash,p->mask);
+ if (p->hash > (mask >> shift)) {
+ p->perfect = kmalloc(p->hash*
+ sizeof(struct tcindex_filter_result),GFP_KERNEL);
+ if (!p->perfect)
+ return -ENOMEM;
+ memset(p->perfect, 0,
+ p->hash * sizeof(struct tcindex_filter_result));
+ } else {
+ p->h = kmalloc(p->hash*sizeof(struct tcindex_filter *),
+ GFP_KERNEL);
+ if (!p->h)
+ return -ENOMEM;
+ memset(p->h, 0, p->hash*sizeof(struct tcindex_filter *));
+ }
+ }
+ /*
+ * Note: this could be as restrictive as
+ * if (handle & ~(mask >> shift))
+ * but then, we'd fail handles that may become valid after some
+ * future mask change. While this is extremely unlikely to ever
+ * matter, the check below is safer (and also more
+ * backwards-compatible).
+ */
+ if (p->perfect && handle >= p->alloc_hash)
+ return -EINVAL;
+ if (p->perfect) {
+ r = p->perfect+handle;
+ } else {
+ r = lookup(p,handle);
+ DPRINTK("r=%p\n",r);
+ if (!r)
+ r = &new_filter_result;
+ }
+ DPRINTK("r=%p\n",r);
+ if (tb[TCA_TCINDEX_CLASSID-1]) {
+ unsigned long cl = cls_set_class(tp,&r->res.class,0);
+
+ if (cl)
+ tp->q->ops->cl_ops->unbind_tcf(tp->q,cl);
+ r->res.classid = *(__u32 *) RTA_DATA(tb[TCA_TCINDEX_CLASSID-1]);
+ r->res.class = tp->q->ops->cl_ops->bind_tcf(tp->q,base,
+ r->res.classid);
+ if (!r->res.class) {
+ r->res.classid = 0;
+ return -ENOENT;
+ }
+ }
+#ifdef CONFIG_NET_CLS_POLICE
+ {
+ struct tcf_police *police;
+
+ police = tb[TCA_TCINDEX_POLICE-1] ?
+ tcf_police_locate(tb[TCA_TCINDEX_POLICE-1],NULL) : NULL;
+ tcf_tree_lock(tp);
+ police = xchg(&r->police,police);
+ tcf_tree_unlock(tp);
+ tcf_police_release(police);
+ }
+#endif
+ if (r != &new_filter_result)
+ return 0;
+ f = kmalloc(sizeof(struct tcindex_filter),GFP_KERNEL);
+ if (!f)
+ return -ENOMEM;
+ f->key = handle;
+ f->result = new_filter_result;
+ f->next = NULL;
+ for (walk = p->h+(handle % p->hash); *walk; walk = &(*walk)->next)
+ /* nothing */;
+ wmb();
+ *walk = f;
+ return 0;
+}
+
+
+static void tcindex_walk(struct tcf_proto *tp, struct tcf_walker *walker)
+{
+ struct tcindex_data *p = PRIV(tp);
+ struct tcindex_filter *f,*next;
+ int i;
+
+ DPRINTK("tcindex_walk(tp %p,walker %p),p %p\n",tp,walker,p);
+ if (p->perfect) {
+ for (i = 0; i < p->hash; i++) {
+ if (!p->perfect[i].res.class)
+ continue;
+ if (walker->count >= walker->skip) {
+ if (walker->fn(tp,
+ (unsigned long) (p->perfect+i), walker)
+ < 0) {
+ walker->stop = 1;
+ return;
+ }
+ }
+ walker->count++;
+ }
+ }
+ if (!p->h)
+ return;
+ for (i = 0; i < p->hash; i++) {
+ for (f = p->h[i]; f; f = next) {
+ next = f->next;
+ if (walker->count >= walker->skip) {
+ if (walker->fn(tp,(unsigned long) &f->result,
+ walker) < 0) {
+ walker->stop = 1;
+ return;
+ }
+ }
+ walker->count++;
+ }
+ }
+}
+
+
+static int tcindex_destroy_element(struct tcf_proto *tp,
+ unsigned long arg, struct tcf_walker *walker)
+{
+ return __tcindex_delete(tp, arg, 0);
+}
+
+
+static void tcindex_destroy(struct tcf_proto *tp)
+{
+ struct tcindex_data *p = PRIV(tp);
+ struct tcf_walker walker;
+
+ DPRINTK("tcindex_destroy(tp %p),p %p\n",tp,p);
+ walker.count = 0;
+ walker.skip = 0;
+ walker.fn = &tcindex_destroy_element;
+ tcindex_walk(tp,&walker);
+ if (p->perfect)
+ kfree(p->perfect);
+ if (p->h)
+ kfree(p->h);
+ kfree(p);
+ tp->root = NULL;
+ MOD_DEC_USE_COUNT;
+}
+
+
+static int tcindex_dump(struct tcf_proto *tp, unsigned long fh,
+ struct sk_buff *skb, struct tcmsg *t)
+{
+ struct tcindex_data *p = PRIV(tp);
+ struct tcindex_filter_result *r = (struct tcindex_filter_result *) fh;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+
+ DPRINTK("tcindex_dump(tp %p,fh 0x%lx,skb %p,t %p),p %p,r %p,b %p\n",
+ tp,fh,skb,t,p,r,b);
+ DPRINTK("p->perfect %p p->h %p\n",p->perfect,p->h);
+ rta = (struct rtattr *) b;
+ RTA_PUT(skb,TCA_OPTIONS,0,NULL);
+ if (!fh) {
+ t->tcm_handle = ~0; /* whatever ... */
+ RTA_PUT(skb,TCA_TCINDEX_HASH,sizeof(p->hash),&p->hash);
+ RTA_PUT(skb,TCA_TCINDEX_MASK,sizeof(p->mask),&p->mask);
+ RTA_PUT(skb,TCA_TCINDEX_SHIFT,sizeof(p->shift),&p->shift);
+ RTA_PUT(skb,TCA_TCINDEX_FALL_THROUGH,sizeof(p->fall_through),
+ &p->fall_through);
+ } else {
+ if (p->perfect) {
+ t->tcm_handle = r-p->perfect;
+ } else {
+ struct tcindex_filter *f;
+ int i;
+
+ t->tcm_handle = 0;
+ for (i = 0; !t->tcm_handle && i < p->hash; i++) {
+ for (f = p->h[i]; !t->tcm_handle && f;
+ f = f->next) {
+ if (&f->result == r)
+ t->tcm_handle = f->key;
+ }
+ }
+ }
+ DPRINTK("handle = %d\n",t->tcm_handle);
+ if (r->res.class)
+ RTA_PUT(skb, TCA_TCINDEX_CLASSID, 4, &r->res.classid);
+#ifdef CONFIG_NET_CLS_POLICE
+ if (r->police) {
+ struct rtattr *p_rta = (struct rtattr *) skb->tail;
+
+ RTA_PUT(skb,TCA_TCINDEX_POLICE,0,NULL);
+ if (tcf_police_dump(skb,r->police) < 0)
+ goto rtattr_failure;
+ p_rta->rta_len = skb->tail-(u8 *) p_rta;
+ }
+#endif
+ }
+ rta->rta_len = skb->tail-b;
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+struct tcf_proto_ops cls_tcindex_ops = {
+ NULL,
+ "tcindex",
+ tcindex_classify,
+ tcindex_init,
+ tcindex_destroy,
+
+ tcindex_get,
+ tcindex_put,
+ tcindex_change,
+ tcindex_delete,
+ tcindex_walk,
+ tcindex_dump
+};
+
+
+#ifdef MODULE
+int init_module(void)
+{
+ return register_tcf_proto_ops(&cls_tcindex_ops);
+}
+
+void cleanup_module(void)
+{
+ unregister_tcf_proto_ops(&cls_tcindex_ops);
+}
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/cls_u32.c b/uClinux-2.4.31-uc0/net/sched/cls_u32.c
new file mode 100644
index 0000000..4d627c1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/cls_u32.c
@@ -0,0 +1,722 @@
+/*
+ * net/sched/cls_u32.c Ugly (or Universal) 32bit key Packet Classifier.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * The filters are packed to hash tables of key nodes
+ * with a set of 32bit key/mask pairs at every node.
+ * Nodes reference next level hash tables etc.
+ *
+ * This scheme is the best universal classifier I managed to
+ * invent; it is not super-fast, but it is not slow (provided you
+ * program it correctly), and general enough. And its relative
+ * speed grows as the number of rules becomes larger.
+ *
+ * It seems that it represents the best middle point between
+ * speed and manageability both by human and by machine.
+ *
+ * It is especially useful for link sharing combined with QoS;
+ * pure RSVP doesn't need such a general approach and can use
+ * much simpler (and faster) schemes, sort of cls_rsvp.c.
+ */
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <linux/rtnetlink.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+
+struct tc_u_knode
+{
+ struct tc_u_knode *next;
+ u32 handle;
+ struct tc_u_hnode *ht_up;
+#ifdef CONFIG_NET_CLS_POLICE
+ struct tcf_police *police;
+#endif
+ struct tcf_result res;
+ struct tc_u_hnode *ht_down;
+ struct tc_u32_sel sel;
+};
+
+struct tc_u_hnode
+{
+ struct tc_u_hnode *next;
+ u32 handle;
+ u32 prio;
+ struct tc_u_common *tp_c;
+ int refcnt;
+ unsigned divisor;
+ struct tc_u_knode *ht[1];
+};
+
+struct tc_u_common
+{
+ struct tc_u_common *next;
+ struct tc_u_hnode *hlist;
+ struct Qdisc *q;
+ int refcnt;
+ u32 hgenerator;
+};
+
+static struct tc_u_common *u32_list;
+
+static __inline__ unsigned u32_hash_fold(u32 key, struct tc_u32_sel *sel)
+{
+ unsigned h = key & sel->hmask;
+
+ h ^= h>>16;
+ h ^= h>>8;
+ return h;
+}
+
+static int u32_classify(struct sk_buff *skb, struct tcf_proto *tp, struct tcf_result *res)
+{
+ struct {
+ struct tc_u_knode *knode;
+ u8 *ptr;
+ } stack[TC_U32_MAXDEPTH];
+
+ struct tc_u_hnode *ht = (struct tc_u_hnode*)tp->root;
+ u8 *ptr = skb->nh.raw;
+ struct tc_u_knode *n;
+ int sdepth = 0;
+ int off2 = 0;
+ int sel = 0;
+ int i;
+
+next_ht:
+ n = ht->ht[sel];
+
+next_knode:
+ if (n) {
+ struct tc_u32_key *key = n->sel.keys;
+
+ for (i = n->sel.nkeys; i>0; i--, key++) {
+ if ((*(u32*)(ptr+key->off+(off2&key->offmask))^key->val)&key->mask) {
+ n = n->next;
+ goto next_knode;
+ }
+ }
+ if (n->ht_down == NULL) {
+check_terminal:
+ if (n->sel.flags&TC_U32_TERMINAL) {
+ *res = n->res;
+#ifdef CONFIG_NET_CLS_POLICE
+ if (n->police) {
+ int pol_res = tcf_police(skb, n->police);
+ if (pol_res >= 0)
+ return pol_res;
+ } else
+#endif
+ return 0;
+ }
+ n = n->next;
+ goto next_knode;
+ }
+
+ /* PUSH */
+ if (sdepth >= TC_U32_MAXDEPTH)
+ goto deadloop;
+ stack[sdepth].knode = n;
+ stack[sdepth].ptr = ptr;
+ sdepth++;
+
+ ht = n->ht_down;
+ sel = 0;
+ if (ht->divisor)
+ sel = ht->divisor&u32_hash_fold(*(u32*)(ptr+n->sel.hoff), &n->sel);
+
+ if (!(n->sel.flags&(TC_U32_VAROFFSET|TC_U32_OFFSET|TC_U32_EAT)))
+ goto next_ht;
+
+ if (n->sel.flags&(TC_U32_OFFSET|TC_U32_VAROFFSET)) {
+ off2 = n->sel.off + 3;
+ if (n->sel.flags&TC_U32_VAROFFSET)
+ off2 += ntohs(n->sel.offmask & *(u16*)(ptr+n->sel.offoff)) >>n->sel.offshift;
+ off2 &= ~3;
+ }
+ if (n->sel.flags&TC_U32_EAT) {
+ ptr += off2;
+ off2 = 0;
+ }
+
+ if (ptr < skb->tail)
+ goto next_ht;
+ }
+
+ /* POP */
+ if (sdepth--) {
+ n = stack[sdepth].knode;
+ ht = n->ht_up;
+ ptr = stack[sdepth].ptr;
+ goto check_terminal;
+ }
+ return -1;
+
+deadloop:
+ if (net_ratelimit())
+ printk("cls_u32: dead loop\n");
+ return -1;
+}
+
+static __inline__ struct tc_u_hnode *
+u32_lookup_ht(struct tc_u_common *tp_c, u32 handle)
+{
+ struct tc_u_hnode *ht;
+
+ for (ht = tp_c->hlist; ht; ht = ht->next)
+ if (ht->handle == handle)
+ break;
+
+ return ht;
+}
+
+static __inline__ struct tc_u_knode *
+u32_lookup_key(struct tc_u_hnode *ht, u32 handle)
+{
+ unsigned sel;
+ struct tc_u_knode *n;
+
+ sel = TC_U32_HASH(handle);
+ if (sel > ht->divisor)
+ return 0;
+
+ for (n = ht->ht[sel]; n; n = n->next)
+ if (n->handle == handle)
+ return n;
+
+ return NULL;
+}
+
+
+static unsigned long u32_get(struct tcf_proto *tp, u32 handle)
+{
+ struct tc_u_hnode *ht;
+ struct tc_u_common *tp_c = tp->data;
+
+ if (TC_U32_HTID(handle) == TC_U32_ROOT)
+ ht = tp->root;
+ else
+ ht = u32_lookup_ht(tp_c, TC_U32_HTID(handle));
+
+ if (!ht)
+ return 0;
+
+ if (TC_U32_KEY(handle) == 0)
+ return (unsigned long)ht;
+
+ return (unsigned long)u32_lookup_key(ht, handle);
+}
+
+static void u32_put(struct tcf_proto *tp, unsigned long f)
+{
+}
+
+static u32 gen_new_htid(struct tc_u_common *tp_c)
+{
+ int i = 0x800;
+
+ do {
+ if (++tp_c->hgenerator == 0x7FF)
+ tp_c->hgenerator = 1;
+ } while (--i>0 && u32_lookup_ht(tp_c, (tp_c->hgenerator|0x800)<<20));
+
+ return i > 0 ? (tp_c->hgenerator|0x800)<<20 : 0;
+}
+
+static int u32_init(struct tcf_proto *tp)
+{
+ struct tc_u_hnode *root_ht;
+ struct tc_u_common *tp_c;
+
+ MOD_INC_USE_COUNT;
+
+ for (tp_c = u32_list; tp_c; tp_c = tp_c->next)
+ if (tp_c->q == tp->q)
+ break;
+
+ root_ht = kmalloc(sizeof(*root_ht), GFP_KERNEL);
+ if (root_ht == NULL) {
+ MOD_DEC_USE_COUNT;
+ return -ENOBUFS;
+ }
+ memset(root_ht, 0, sizeof(*root_ht));
+ root_ht->divisor = 0;
+ root_ht->refcnt++;
+ root_ht->handle = tp_c ? gen_new_htid(tp_c) : 0x80000000;
+ root_ht->prio = tp->prio;
+
+ if (tp_c == NULL) {
+ tp_c = kmalloc(sizeof(*tp_c), GFP_KERNEL);
+ if (tp_c == NULL) {
+ kfree(root_ht);
+ MOD_DEC_USE_COUNT;
+ return -ENOBUFS;
+ }
+ memset(tp_c, 0, sizeof(*tp_c));
+ tp_c->q = tp->q;
+ tp_c->next = u32_list;
+ u32_list = tp_c;
+ }
+
+ tp_c->refcnt++;
+ root_ht->next = tp_c->hlist;
+ tp_c->hlist = root_ht;
+ root_ht->tp_c = tp_c;
+
+ tp->root = root_ht;
+ tp->data = tp_c;
+ return 0;
+}
+
+static int u32_destroy_key(struct tcf_proto *tp, struct tc_u_knode *n)
+{
+ unsigned long cl;
+
+ if ((cl = __cls_set_class(&n->res.class, 0)) != 0)
+ tp->q->ops->cl_ops->unbind_tcf(tp->q, cl);
+#ifdef CONFIG_NET_CLS_POLICE
+ tcf_police_release(n->police);
+#endif
+ if (n->ht_down)
+ n->ht_down->refcnt--;
+ kfree(n);
+ return 0;
+}
+
+static int u32_delete_key(struct tcf_proto *tp, struct tc_u_knode* key)
+{
+ struct tc_u_knode **kp;
+ struct tc_u_hnode *ht = key->ht_up;
+
+ if (ht) {
+ for (kp = &ht->ht[TC_U32_HASH(key->handle)]; *kp; kp = &(*kp)->next) {
+ if (*kp == key) {
+ tcf_tree_lock(tp);
+ *kp = key->next;
+ tcf_tree_unlock(tp);
+
+ u32_destroy_key(tp, key);
+ return 0;
+ }
+ }
+ }
+ BUG_TRAP(0);
+ return 0;
+}
+
+static void u32_clear_hnode(struct tcf_proto *tp, struct tc_u_hnode *ht)
+{
+ struct tc_u_knode *n;
+ unsigned h;
+
+ for (h=0; h<=ht->divisor; h++) {
+ while ((n = ht->ht[h]) != NULL) {
+ ht->ht[h] = n->next;
+
+ u32_destroy_key(tp, n);
+ }
+ }
+}
+
+static int u32_destroy_hnode(struct tcf_proto *tp, struct tc_u_hnode *ht)
+{
+ struct tc_u_common *tp_c = tp->data;
+ struct tc_u_hnode **hn;
+
+ BUG_TRAP(!ht->refcnt);
+
+ u32_clear_hnode(tp, ht);
+
+ for (hn = &tp_c->hlist; *hn; hn = &(*hn)->next) {
+ if (*hn == ht) {
+ *hn = ht->next;
+ kfree(ht);
+ return 0;
+ }
+ }
+
+ BUG_TRAP(0);
+ return -ENOENT;
+}
+
+static void u32_destroy(struct tcf_proto *tp)
+{
+ struct tc_u_common *tp_c = tp->data;
+ struct tc_u_hnode *root_ht = xchg(&tp->root, NULL);
+
+ BUG_TRAP(root_ht != NULL);
+
+ if (root_ht && --root_ht->refcnt == 0)
+ u32_destroy_hnode(tp, root_ht);
+
+ if (--tp_c->refcnt == 0) {
+ struct tc_u_hnode *ht;
+ struct tc_u_common **tp_cp;
+
+ for (tp_cp = &u32_list; *tp_cp; tp_cp = &(*tp_cp)->next) {
+ if (*tp_cp == tp_c) {
+ *tp_cp = tp_c->next;
+ break;
+ }
+ }
+
+ for (ht=tp_c->hlist; ht; ht = ht->next)
+ u32_clear_hnode(tp, ht);
+
+ while ((ht = tp_c->hlist) != NULL) {
+ tp_c->hlist = ht->next;
+
+ BUG_TRAP(ht->refcnt == 0);
+
+ kfree(ht);
+ };
+
+ kfree(tp_c);
+ }
+
+ MOD_DEC_USE_COUNT;
+ tp->data = NULL;
+}
+
+static int u32_delete(struct tcf_proto *tp, unsigned long arg)
+{
+ struct tc_u_hnode *ht = (struct tc_u_hnode*)arg;
+
+ if (ht == NULL)
+ return 0;
+
+ if (TC_U32_KEY(ht->handle))
+ return u32_delete_key(tp, (struct tc_u_knode*)ht);
+
+ if (tp->root == ht)
+ return -EINVAL;
+
+ if (--ht->refcnt == 0)
+ u32_destroy_hnode(tp, ht);
+
+ return 0;
+}
+
+static u32 gen_new_kid(struct tc_u_hnode *ht, u32 handle)
+{
+ struct tc_u_knode *n;
+ unsigned i = 0x7FF;
+
+ for (n=ht->ht[TC_U32_HASH(handle)]; n; n = n->next)
+ if (i < TC_U32_NODE(n->handle))
+ i = TC_U32_NODE(n->handle);
+ i++;
+
+ return handle|(i>0xFFF ? 0xFFF : i);
+}
+
+static int u32_set_parms(struct Qdisc *q, unsigned long base,
+ struct tc_u_hnode *ht,
+ struct tc_u_knode *n, struct rtattr **tb,
+ struct rtattr *est)
+{
+ if (tb[TCA_U32_LINK-1]) {
+ u32 handle = *(u32*)RTA_DATA(tb[TCA_U32_LINK-1]);
+ struct tc_u_hnode *ht_down = NULL;
+
+ if (TC_U32_KEY(handle))
+ return -EINVAL;
+
+ if (handle) {
+ ht_down = u32_lookup_ht(ht->tp_c, handle);
+
+ if (ht_down == NULL)
+ return -EINVAL;
+ ht_down->refcnt++;
+ }
+
+ sch_tree_lock(q);
+ ht_down = xchg(&n->ht_down, ht_down);
+ sch_tree_unlock(q);
+
+ if (ht_down)
+ ht_down->refcnt--;
+ }
+ if (tb[TCA_U32_CLASSID-1]) {
+ unsigned long cl;
+
+ n->res.classid = *(u32*)RTA_DATA(tb[TCA_U32_CLASSID-1]);
+ sch_tree_lock(q);
+ cl = __cls_set_class(&n->res.class, q->ops->cl_ops->bind_tcf(q, base, n->res.classid));
+ sch_tree_unlock(q);
+ if (cl)
+ q->ops->cl_ops->unbind_tcf(q, cl);
+ }
+#ifdef CONFIG_NET_CLS_POLICE
+ if (tb[TCA_U32_POLICE-1]) {
+ struct tcf_police *police = tcf_police_locate(tb[TCA_U32_POLICE-1], est);
+
+ sch_tree_lock(q);
+ police = xchg(&n->police, police);
+ sch_tree_unlock(q);
+
+ tcf_police_release(police);
+ }
+#endif
+ return 0;
+}
+
+static int u32_change(struct tcf_proto *tp, unsigned long base, u32 handle,
+ struct rtattr **tca,
+ unsigned long *arg)
+{
+ struct tc_u_common *tp_c = tp->data;
+ struct tc_u_hnode *ht;
+ struct tc_u_knode *n;
+ struct tc_u32_sel *s;
+ struct rtattr *opt = tca[TCA_OPTIONS-1];
+ struct rtattr *tb[TCA_U32_MAX];
+ u32 htid;
+ int err;
+
+ if (opt == NULL)
+ return handle ? -EINVAL : 0;
+
+ if (rtattr_parse(tb, TCA_U32_MAX, RTA_DATA(opt), RTA_PAYLOAD(opt)) < 0)
+ return -EINVAL;
+
+ if ((n = (struct tc_u_knode*)*arg) != NULL) {
+ if (TC_U32_KEY(n->handle) == 0)
+ return -EINVAL;
+
+ return u32_set_parms(tp->q, base, n->ht_up, n, tb, tca[TCA_RATE-1]);
+ }
+
+ if (tb[TCA_U32_DIVISOR-1]) {
+ unsigned divisor = *(unsigned*)RTA_DATA(tb[TCA_U32_DIVISOR-1]);
+
+ if (--divisor > 0x100)
+ return -EINVAL;
+ if (TC_U32_KEY(handle))
+ return -EINVAL;
+ if (handle == 0) {
+ handle = gen_new_htid(tp->data);
+ if (handle == 0)
+ return -ENOMEM;
+ }
+ ht = kmalloc(sizeof(*ht) + divisor*sizeof(void*), GFP_KERNEL);
+ if (ht == NULL)
+ return -ENOBUFS;
+ memset(ht, 0, sizeof(*ht) + divisor*sizeof(void*));
+ ht->tp_c = tp_c;
+ ht->refcnt = 0;
+ ht->divisor = divisor;
+ ht->handle = handle;
+ ht->prio = tp->prio;
+ ht->next = tp_c->hlist;
+ tp_c->hlist = ht;
+ *arg = (unsigned long)ht;
+ return 0;
+ }
+
+ if (tb[TCA_U32_HASH-1]) {
+ htid = *(unsigned*)RTA_DATA(tb[TCA_U32_HASH-1]);
+ if (TC_U32_HTID(htid) == TC_U32_ROOT) {
+ ht = tp->root;
+ htid = ht->handle;
+ } else {
+ ht = u32_lookup_ht(tp->data, TC_U32_HTID(htid));
+ if (ht == NULL)
+ return -EINVAL;
+ }
+ } else {
+ ht = tp->root;
+ htid = ht->handle;
+ }
+
+ if (ht->divisor < TC_U32_HASH(htid))
+ return -EINVAL;
+
+ if (handle) {
+ if (TC_U32_HTID(handle) && TC_U32_HTID(handle^htid))
+ return -EINVAL;
+ handle = htid | TC_U32_NODE(handle);
+ } else
+ handle = gen_new_kid(ht, htid);
+
+ if (tb[TCA_U32_SEL-1] == 0 ||
+ RTA_PAYLOAD(tb[TCA_U32_SEL-1]) < sizeof(struct tc_u32_sel))
+ return -EINVAL;
+
+ s = RTA_DATA(tb[TCA_U32_SEL-1]);
+ n = kmalloc(sizeof(*n) + s->nkeys*sizeof(struct tc_u32_key), GFP_KERNEL);
+ if (n == NULL)
+ return -ENOBUFS;
+ memset(n, 0, sizeof(*n) + s->nkeys*sizeof(struct tc_u32_key));
+ memcpy(&n->sel, s, sizeof(*s) + s->nkeys*sizeof(struct tc_u32_key));
+ n->ht_up = ht;
+ n->handle = handle;
+ err = u32_set_parms(tp->q, base, ht, n, tb, tca[TCA_RATE-1]);
+ if (err == 0) {
+ struct tc_u_knode **ins;
+ for (ins = &ht->ht[TC_U32_HASH(handle)]; *ins; ins = &(*ins)->next)
+ if (TC_U32_NODE(handle) < TC_U32_NODE((*ins)->handle))
+ break;
+
+ n->next = *ins;
+ wmb();
+ *ins = n;
+
+ *arg = (unsigned long)n;
+ return 0;
+ }
+ kfree(n);
+ return err;
+}
+
+static void u32_walk(struct tcf_proto *tp, struct tcf_walker *arg)
+{
+ struct tc_u_common *tp_c = tp->data;
+ struct tc_u_hnode *ht;
+ struct tc_u_knode *n;
+ unsigned h;
+
+ if (arg->stop)
+ return;
+
+ for (ht = tp_c->hlist; ht; ht = ht->next) {
+ if (ht->prio != tp->prio)
+ continue;
+ if (arg->count >= arg->skip) {
+ if (arg->fn(tp, (unsigned long)ht, arg) < 0) {
+ arg->stop = 1;
+ return;
+ }
+ }
+ arg->count++;
+ for (h = 0; h <= ht->divisor; h++) {
+ for (n = ht->ht[h]; n; n = n->next) {
+ if (arg->count < arg->skip) {
+ arg->count++;
+ continue;
+ }
+ if (arg->fn(tp, (unsigned long)n, arg) < 0) {
+ arg->stop = 1;
+ return;
+ }
+ arg->count++;
+ }
+ }
+ }
+}
+
+static int u32_dump(struct tcf_proto *tp, unsigned long fh,
+ struct sk_buff *skb, struct tcmsg *t)
+{
+ struct tc_u_knode *n = (struct tc_u_knode*)fh;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+
+ if (n == NULL)
+ return skb->len;
+
+ t->tcm_handle = n->handle;
+
+ rta = (struct rtattr*)b;
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+
+ if (TC_U32_KEY(n->handle) == 0) {
+ struct tc_u_hnode *ht = (struct tc_u_hnode*)fh;
+ u32 divisor = ht->divisor+1;
+ RTA_PUT(skb, TCA_U32_DIVISOR, 4, &divisor);
+ } else {
+ RTA_PUT(skb, TCA_U32_SEL,
+ sizeof(n->sel) + n->sel.nkeys*sizeof(struct tc_u32_key),
+ &n->sel);
+ if (n->ht_up) {
+ u32 htid = n->handle & 0xFFFFF000;
+ RTA_PUT(skb, TCA_U32_HASH, 4, &htid);
+ }
+ if (n->res.classid)
+ RTA_PUT(skb, TCA_U32_CLASSID, 4, &n->res.classid);
+ if (n->ht_down)
+ RTA_PUT(skb, TCA_U32_LINK, 4, &n->ht_down->handle);
+#ifdef CONFIG_NET_CLS_POLICE
+ if (n->police) {
+ struct rtattr * p_rta = (struct rtattr*)skb->tail;
+
+ RTA_PUT(skb, TCA_U32_POLICE, 0, NULL);
+
+ if (tcf_police_dump(skb, n->police) < 0)
+ goto rtattr_failure;
+
+ p_rta->rta_len = skb->tail - (u8*)p_rta;
+ }
+#endif
+ }
+
+ rta->rta_len = skb->tail - b;
+#ifdef CONFIG_NET_CLS_POLICE
+ if (TC_U32_KEY(n->handle) && n->police) {
+ if (qdisc_copy_stats(skb, &n->police->stats))
+ goto rtattr_failure;
+ }
+#endif
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+struct tcf_proto_ops cls_u32_ops = {
+ NULL,
+ "u32",
+ u32_classify,
+ u32_init,
+ u32_destroy,
+
+ u32_get,
+ u32_put,
+ u32_change,
+ u32_delete,
+ u32_walk,
+ u32_dump
+};
+
+#ifdef MODULE
+int init_module(void)
+{
+ return register_tcf_proto_ops(&cls_u32_ops);
+}
+
+void cleanup_module(void)
+{
+ unregister_tcf_proto_ops(&cls_u32_ops);
+}
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/estimator.c b/uClinux-2.4.31-uc0/net/sched/estimator.c
new file mode 100644
index 0000000..53fe5ad
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/estimator.c
@@ -0,0 +1,193 @@
+/*
+ * net/sched/estimator.c Simple rate estimator.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+/*
+ This code is NOT intended to be used for statistics collection,
+ its purpose is to provide a base for statistical multiplexing
+ for controlled load service.
+ If you need only statistics, run a user level daemon which
+ periodically reads byte counters.
+
+ Unfortunately, rate estimation is not a very easy task.
+ F.e. I did not find a simple way to estimate the current peak rate
+ and even failed to formulate the problem 8)8)
+
+ So I preferred not to built an estimator into the scheduler,
+ but run this task separately.
+ Ideally, it should be kernel thread(s), but for now it runs
+ from timers, which puts apparent top bounds on the number of rated
+ flows, has minimal overhead on small, but is enough
+ to handle controlled load service, sets of aggregates.
+
+ We measure rate over A=(1<<interval) seconds and evaluate EWMA:
+
+ avrate = avrate*(1-W) + rate*W
+
+ where W is chosen as negative power of 2: W = 2^(-ewma_log)
+
+ The resulting time constant is:
+
+ T = A/(-ln(1-W))
+
+
+ NOTES.
+
+ * The stored value for avbps is scaled by 2^5, so that maximal
+ rate is ~1Gbit, avpps is scaled by 2^10.
+
+ * Minimal interval is HZ/4=250msec (it is the greatest common divisor
+ for HZ=100 and HZ=1024 8)), maximal interval
+ is (HZ*2^EST_MAX_INTERVAL)/4 = 8sec. Shorter intervals
+ are too expensive, longer ones can be implemented
+ at user level painlessly.
+ */
+
+#define EST_MAX_INTERVAL 5
+
+struct qdisc_estimator
+{
+ struct qdisc_estimator *next;
+ struct tc_stats *stats;
+ unsigned interval;
+ int ewma_log;
+ u64 last_bytes;
+ u32 last_packets;
+ u32 avpps;
+ u32 avbps;
+};
+
+struct qdisc_estimator_head
+{
+ struct timer_list timer;
+ struct qdisc_estimator *list;
+};
+
+static struct qdisc_estimator_head elist[EST_MAX_INTERVAL+1];
+
+/* Estimator array lock */
+static rwlock_t est_lock = RW_LOCK_UNLOCKED;
+
+static void est_timer(unsigned long arg)
+{
+ int idx = (int)arg;
+ struct qdisc_estimator *e;
+
+ read_lock(&est_lock);
+ for (e = elist[idx].list; e; e = e->next) {
+ struct tc_stats *st = e->stats;
+ u64 nbytes;
+ u32 npackets;
+ u32 rate;
+
+ spin_lock(st->lock);
+ nbytes = st->bytes;
+ npackets = st->packets;
+ rate = (nbytes - e->last_bytes)<<(7 - idx);
+ e->last_bytes = nbytes;
+ e->avbps += ((long)rate - (long)e->avbps) >> e->ewma_log;
+ st->bps = (e->avbps+0xF)>>5;
+
+ rate = (npackets - e->last_packets)<<(12 - idx);
+ e->last_packets = npackets;
+ e->avpps += ((long)rate - (long)e->avpps) >> e->ewma_log;
+ e->stats->pps = (e->avpps+0x1FF)>>10;
+ spin_unlock(st->lock);
+ }
+
+ mod_timer(&elist[idx].timer, jiffies + ((HZ<<idx)/4));
+ read_unlock(&est_lock);
+}
+
+int qdisc_new_estimator(struct tc_stats *stats, struct rtattr *opt)
+{
+ struct qdisc_estimator *est;
+ struct tc_estimator *parm = RTA_DATA(opt);
+
+ if (RTA_PAYLOAD(opt) < sizeof(*parm))
+ return -EINVAL;
+
+ if (parm->interval < -2 || parm->interval > 3)
+ return -EINVAL;
+
+ est = kmalloc(sizeof(*est), GFP_KERNEL);
+ if (est == NULL)
+ return -ENOBUFS;
+
+ memset(est, 0, sizeof(*est));
+ est->interval = parm->interval + 2;
+ est->stats = stats;
+ est->ewma_log = parm->ewma_log;
+ est->last_bytes = stats->bytes;
+ est->avbps = stats->bps<<5;
+ est->last_packets = stats->packets;
+ est->avpps = stats->pps<<10;
+
+ est->next = elist[est->interval].list;
+ if (est->next == NULL) {
+ init_timer(&elist[est->interval].timer);
+ elist[est->interval].timer.data = est->interval;
+ elist[est->interval].timer.expires = jiffies + ((HZ<<est->interval)/4);
+ elist[est->interval].timer.function = est_timer;
+ add_timer(&elist[est->interval].timer);
+ }
+ write_lock_bh(&est_lock);
+ elist[est->interval].list = est;
+ write_unlock_bh(&est_lock);
+ return 0;
+}
+
+void qdisc_kill_estimator(struct tc_stats *stats)
+{
+ int idx;
+ struct qdisc_estimator *est, **pest;
+
+ for (idx=0; idx <= EST_MAX_INTERVAL; idx++) {
+ int killed = 0;
+ pest = &elist[idx].list;
+ while ((est=*pest) != NULL) {
+ if (est->stats != stats) {
+ pest = &est->next;
+ continue;
+ }
+
+ write_lock_bh(&est_lock);
+ *pest = est->next;
+ write_unlock_bh(&est_lock);
+
+ kfree(est);
+ killed++;
+ }
+ if (killed && elist[idx].list == NULL)
+ del_timer(&elist[idx].timer);
+ }
+}
+
diff --git a/uClinux-2.4.31-uc0/net/sched/police.c b/uClinux-2.4.31-uc0/net/sched/police.c
new file mode 100644
index 0000000..78fb8c5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/police.c
@@ -0,0 +1,251 @@
+/*
+ * net/sched/police.c Input police filter.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+#define L2T(p,L) ((p)->R_tab->data[(L)>>(p)->R_tab->rate.cell_log])
+#define L2T_P(p,L) ((p)->P_tab->data[(L)>>(p)->P_tab->rate.cell_log])
+
+static u32 idx_gen;
+static struct tcf_police *tcf_police_ht[16];
+/* Policer hash table lock */
+static rwlock_t police_lock = RW_LOCK_UNLOCKED;
+
+/* Each policer is serialized by its individual spinlock */
+
+static __inline__ unsigned tcf_police_hash(u32 index)
+{
+ return index&0xF;
+}
+
+static __inline__ struct tcf_police * tcf_police_lookup(u32 index)
+{
+ struct tcf_police *p;
+
+ read_lock(&police_lock);
+ for (p = tcf_police_ht[tcf_police_hash(index)]; p; p = p->next) {
+ if (p->index == index)
+ break;
+ }
+ read_unlock(&police_lock);
+ return p;
+}
+
+static __inline__ u32 tcf_police_new_index(void)
+{
+ do {
+ if (++idx_gen == 0)
+ idx_gen = 1;
+ } while (tcf_police_lookup(idx_gen));
+
+ return idx_gen;
+}
+
+
+void tcf_police_destroy(struct tcf_police *p)
+{
+ unsigned h = tcf_police_hash(p->index);
+ struct tcf_police **p1p;
+
+ for (p1p = &tcf_police_ht[h]; *p1p; p1p = &(*p1p)->next) {
+ if (*p1p == p) {
+ write_lock_bh(&police_lock);
+ *p1p = p->next;
+ write_unlock_bh(&police_lock);
+#ifdef CONFIG_NET_ESTIMATOR
+ qdisc_kill_estimator(&p->stats);
+#endif
+ if (p->R_tab)
+ qdisc_put_rtab(p->R_tab);
+ if (p->P_tab)
+ qdisc_put_rtab(p->P_tab);
+ kfree(p);
+ return;
+ }
+ }
+ BUG_TRAP(0);
+}
+
+struct tcf_police * tcf_police_locate(struct rtattr *rta, struct rtattr *est)
+{
+ unsigned h;
+ struct tcf_police *p;
+ struct rtattr *tb[TCA_POLICE_MAX];
+ struct tc_police *parm;
+
+ if (rtattr_parse(tb, TCA_POLICE_MAX, RTA_DATA(rta), RTA_PAYLOAD(rta)) < 0)
+ return NULL;
+
+ if (tb[TCA_POLICE_TBF-1] == NULL)
+ return NULL;
+
+ parm = RTA_DATA(tb[TCA_POLICE_TBF-1]);
+
+ if (parm->index && (p = tcf_police_lookup(parm->index)) != NULL) {
+ p->refcnt++;
+ return p;
+ }
+
+ p = kmalloc(sizeof(*p), GFP_KERNEL);
+ if (p == NULL)
+ return NULL;
+
+ memset(p, 0, sizeof(*p));
+ p->refcnt = 1;
+ spin_lock_init(&p->lock);
+ p->stats.lock = &p->lock;
+ if (parm->rate.rate) {
+ if ((p->R_tab = qdisc_get_rtab(&parm->rate, tb[TCA_POLICE_RATE-1])) == NULL)
+ goto failure;
+ if (parm->peakrate.rate &&
+ (p->P_tab = qdisc_get_rtab(&parm->peakrate, tb[TCA_POLICE_PEAKRATE-1])) == NULL)
+ goto failure;
+ }
+ if (tb[TCA_POLICE_RESULT-1])
+ p->result = *(int*)RTA_DATA(tb[TCA_POLICE_RESULT-1]);
+#ifdef CONFIG_NET_ESTIMATOR
+ if (tb[TCA_POLICE_AVRATE-1])
+ p->ewma_rate = *(u32*)RTA_DATA(tb[TCA_POLICE_AVRATE-1]);
+#endif
+ p->toks = p->burst = parm->burst;
+ p->mtu = parm->mtu;
+ if (p->mtu == 0) {
+ p->mtu = ~0;
+ if (p->R_tab)
+ p->mtu = 255<<p->R_tab->rate.cell_log;
+ }
+ if (p->P_tab)
+ p->ptoks = L2T_P(p, p->mtu);
+ PSCHED_GET_TIME(p->t_c);
+ p->index = parm->index ? : tcf_police_new_index();
+ p->action = parm->action;
+#ifdef CONFIG_NET_ESTIMATOR
+ if (est)
+ qdisc_new_estimator(&p->stats, est);
+#endif
+ h = tcf_police_hash(p->index);
+ write_lock_bh(&police_lock);
+ p->next = tcf_police_ht[h];
+ tcf_police_ht[h] = p;
+ write_unlock_bh(&police_lock);
+ return p;
+
+failure:
+ if (p->R_tab)
+ qdisc_put_rtab(p->R_tab);
+ kfree(p);
+ return NULL;
+}
+
+int tcf_police(struct sk_buff *skb, struct tcf_police *p)
+{
+ psched_time_t now;
+ long toks;
+ long ptoks = 0;
+
+ spin_lock(&p->lock);
+
+ p->stats.bytes += skb->len;
+ p->stats.packets++;
+
+#ifdef CONFIG_NET_ESTIMATOR
+ if (p->ewma_rate && p->stats.bps >= p->ewma_rate) {
+ p->stats.overlimits++;
+ spin_unlock(&p->lock);
+ return p->action;
+ }
+#endif
+
+ if (skb->len <= p->mtu) {
+ if (p->R_tab == NULL) {
+ spin_unlock(&p->lock);
+ return p->result;
+ }
+
+ PSCHED_GET_TIME(now);
+
+ toks = PSCHED_TDIFF_SAFE(now, p->t_c, p->burst, 0);
+
+ if (p->P_tab) {
+ ptoks = toks + p->ptoks;
+ if (ptoks > (long)L2T_P(p, p->mtu))
+ ptoks = (long)L2T_P(p, p->mtu);
+ ptoks -= L2T_P(p, skb->len);
+ }
+ toks += p->toks;
+ if (toks > (long)p->burst)
+ toks = p->burst;
+ toks -= L2T(p, skb->len);
+
+ if ((toks|ptoks) >= 0) {
+ p->t_c = now;
+ p->toks = toks;
+ p->ptoks = ptoks;
+ spin_unlock(&p->lock);
+ return p->result;
+ }
+ }
+
+ p->stats.overlimits++;
+ spin_unlock(&p->lock);
+ return p->action;
+}
+
+int tcf_police_dump(struct sk_buff *skb, struct tcf_police *p)
+{
+ unsigned char *b = skb->tail;
+ struct tc_police opt;
+
+ opt.index = p->index;
+ opt.action = p->action;
+ opt.mtu = p->mtu;
+ opt.burst = p->burst;
+ if (p->R_tab)
+ opt.rate = p->R_tab->rate;
+ else
+ memset(&opt.rate, 0, sizeof(opt.rate));
+ if (p->P_tab)
+ opt.peakrate = p->P_tab->rate;
+ else
+ memset(&opt.peakrate, 0, sizeof(opt.peakrate));
+ RTA_PUT(skb, TCA_POLICE_TBF, sizeof(opt), &opt);
+ if (p->result)
+ RTA_PUT(skb, TCA_POLICE_RESULT, sizeof(int), &p->result);
+#ifdef CONFIG_NET_ESTIMATOR
+ if (p->ewma_rate)
+ RTA_PUT(skb, TCA_POLICE_AVRATE, 4, &p->ewma_rate);
+#endif
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
diff --git a/uClinux-2.4.31-uc0/net/sched/proxydict.c b/uClinux-2.4.31-uc0/net/sched/proxydict.c
new file mode 100644
index 0000000..efa3671
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/proxydict.c
@@ -0,0 +1,153 @@
+#ifndef __KERNEL__
+#include <string.h>
+#include <netinet/in.h>
+#endif
+
+#include "proxyremap.h"
+#include "proxydict.h"
+
+
+/*--------------------------------------------------------------------------
+Implementation.
+*/
+
+// Hash function
+#define hash_fnc(m,server,port,proto) \
+ (((proto)*7+(server)*13+(port)*5)%m->hash_size)
+
+// Size of hash table given maximal number of connections:
+#define hash_size_max_con(max_con) (2*(max_con))
+
+// The memory area we maintain:
+typedef struct {
+ int hash_size;
+ int max_con;
+ int cur_con;
+
+ int free_first;
+
+ // Then we have:
+ // int hash_table[hash_size];
+ // int next[max_con];
+ // ProxyRemapBlock info[max_con];
+ //
+ // The idea is the following:
+ // Given a connection we map it by hash_fnc into hash_table. This gives an
+ // index in next which contains a -1 terminated linked list of connections
+ // mapping to that hash value.
+ //
+ // The entries in next not allocated is also in linked list where
+ // the first free index is free_first.
+} memory;
+
+#define Memory(m) ((memory*)m)
+#define Hash_table(m) ((int*)(((char*)m)+sizeof(memory)))
+#define Next(m) ((int*)(((char*)m)+sizeof(memory)+ \
+ sizeof(int)*((memory*)m)->hash_size))
+#define Info(m) ((ProxyRemapBlock*)(((char*)m)+ \
+ sizeof(memory)+ \
+ sizeof(int)*((memory*)m)->hash_size+\
+ sizeof(int)*((memory*)m)->max_con \
+ ))
+
+int proxyGetMemSize(int max_con) {
+ return sizeof(memory)+
+ sizeof(int)*hash_size_max_con(max_con)+
+ sizeof(int)*max_con+
+ sizeof(ProxyRemapBlock)*max_con;
+}
+
+void proxyInitMem(void* data, int max_con) {
+ // Init m:
+ memory* m=Memory(data);
+ m->max_con=max_con;
+ m->cur_con=0;
+ m->hash_size=hash_size_max_con(max_con);
+
+ {
+ // Get pointers:
+ int* hash_table=Hash_table(data);
+ int* next=Next(data);
+ int i;
+
+ // Init the hash table:
+ for(i=0; i<m->hash_size; i++) hash_table[i]=-1;
+
+ // Init the free-list
+ for(i=0; i<m->max_con; i++) next[i]=i+1;
+ m->free_first=0;
+ }
+}
+
+int proxyGetCurConn(void* data) {
+ return Memory(data)->cur_con;
+}
+
+int proxyGetMaxConn(void* data) {
+ return Memory(data)->max_con;
+}
+
+ProxyRemapBlock* proxyLookup(void* data, unsigned ipaddr, unsigned short port, char proto) {
+ memory* m=Memory(data);
+ int* hash_table=Hash_table(m);
+ int* next=Next(m);
+ ProxyRemapBlock* info=Info(m);
+ int i;
+
+ for(i=hash_table[hash_fnc(m,ipaddr,port,proto)]; i!=-1; i=next[i]) {
+ if(info[i].proto==proto &&
+ info[i].sport==port &&
+ info[i].saddr==ipaddr) return &info[i];
+ }
+
+ return 0;
+}
+
+int proxyConsumeBlock(void* data, ProxyRemapBlock* blk) {
+ memory* m=Memory(data);
+ int* hash_table=Hash_table(m);
+ int* next=Next(m);
+ ProxyRemapBlock* info=Info(m);
+ int hash=hash_fnc(m,blk->saddr,blk->sport,blk->proto);
+ int foo;
+
+ if(blk->open) {
+ if(m->cur_con == m->max_con) return -1;
+
+ // Insert the block at a free entry:
+ info[m->free_first]=*blk;
+ m->cur_con++;
+
+ foo=next[m->free_first];
+
+ // And insert it in the hash tabel:
+ next[m->free_first]=hash_table[hash];
+ hash_table[hash]=m->free_first;
+ m->free_first=foo;
+ } else {
+ int* toupdate;
+
+ // Find the block
+ for(toupdate=&hash_table[hash];
+ *toupdate!=-1;
+ toupdate=&next[*toupdate]) {
+ if(info[*toupdate].proto==blk->proto &&
+ info[*toupdate].sport==blk->sport &&
+ info[*toupdate].saddr==blk->saddr) break;
+ }
+ if(*toupdate==-1) return -1;
+
+ foo=*toupdate;
+
+ // Delete it from the hashing list:
+ *toupdate=next[*toupdate];
+
+ // And put it on the free list:
+ next[foo]=m->free_first;
+ m->free_first=foo;
+
+ m->cur_con--;
+ }
+
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/sched/proxydict.h b/uClinux-2.4.31-uc0/net/sched/proxydict.h
new file mode 100644
index 0000000..a2e7289
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/proxydict.h
@@ -0,0 +1,32 @@
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*--------------------------------------------------------------------------
+This is common code for for handling the tabels containing information about
+which proxyserver connections are associated with which machines..
+*/
+
+// Returns the number of bytes that should be available in the area
+// maintained by this module given the maximal number of concurrent
+// connections.
+int proxyGetMemSize(int max_connections);
+
+// Initializes a memory area to use. There must be as many bytes
+// available as returned by getMemSize.
+void proxyInitMem(void* data, int max_connections);
+
+// Queries:
+int proxyGetCurConn(void* data); // Returns current number of connections
+int proxyMaxCurConn(void* data); // Returns maximal number of connections
+
+// This is called to open and close conenctions. Returns -1 if
+// a protocol error occores (i.e.: If it is discovered)
+int proxyConsumeBlock(void* data, ProxyRemapBlock*);
+
+// Returns the RemapBlock associated with this connection or 0:
+ProxyRemapBlock* proxyLookup(void* data, unsigned ipaddr, unsigned short port, char proto);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/uClinux-2.4.31-uc0/net/sched/proxyremap.h b/uClinux-2.4.31-uc0/net/sched/proxyremap.h
new file mode 100644
index 0000000..41f8a10
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/proxyremap.h
@@ -0,0 +1,33 @@
+#ifndef PROXYREMAP_H
+#define PROXYREMAP_H
+
+// This describes the information that is written in proxyremap.log and which
+// are used in the communication between proxyremapserver and proxyremapclient.
+// Everything is in network order.
+
+// First this header is send:
+#define PROXY_WELCOME_LINE "ProxyRemap 1.02. This is a binary protocol.\r\n"
+
+// Then this block is send every time a connection is opened or closed.
+// Note how it is alligned to use small space usage - arrays of this
+// structure are saved in many places.
+typedef struct {
+ // Server endpoint of connection:
+ unsigned saddr;
+ unsigned short sport;
+
+ // IP protocol for this connection (typically udp or tcp):
+ unsigned char proto;
+
+ // Is the connection opened or closed?
+ unsigned char open;
+
+ // Client the packets should be accounted to:
+ unsigned caddr;
+ unsigned char macaddr[6]; // Might be 0.
+
+ // An informal two-charecter code from the proxyserver. Used for debugging.
+ char proxyinfo[2];
+} ProxyRemapBlock;
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_api.c b/uClinux-2.4.31-uc0/net/sched/sch_api.c
new file mode 100644
index 0000000..b485e57
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_api.c
@@ -0,0 +1,1269 @@
+/*
+ * net/sched/sch_api.c Packet scheduler API.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Fixes:
+ *
+ * Rani Assaf <rani@magic.metawire.com> :980802: JIFFIES and CPU clock sources are repaired.
+ * Eduardo J. Blanco <ejbs@netlabs.com.uy> :990222: kmod support
+ * Jamal Hadi Salim <hadi@nortelnetworks.com>: 990601: ingress support
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/kmod.h>
+#include <linux/list.h>
+
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+#include <asm/processor.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+
+static int qdisc_notify(struct sk_buff *oskb, struct nlmsghdr *n, u32 clid,
+ struct Qdisc *old, struct Qdisc *new);
+static int tclass_notify(struct sk_buff *oskb, struct nlmsghdr *n,
+ struct Qdisc *q, unsigned long cl, int event);
+
+/*
+
+ Short review.
+ -------------
+
+ This file consists of two interrelated parts:
+
+ 1. queueing disciplines manager frontend.
+ 2. traffic classes manager frontend.
+
+ Generally, queueing discipline ("qdisc") is a black box,
+ which is able to enqueue packets and to dequeue them (when
+ device is ready to send something) in order and at times
+ determined by algorithm hidden in it.
+
+ qdisc's are divided to two categories:
+ - "queues", which have no internal structure visible from outside.
+ - "schedulers", which split all the packets to "traffic classes",
+ using "packet classifiers" (look at cls_api.c)
+
+ In turn, classes may have child qdiscs (as rule, queues)
+ attached to them etc. etc. etc.
+
+ The goal of the routines in this file is to translate
+ information supplied by user in the form of handles
+ to more intelligible for kernel form, to make some sanity
+ checks and part of work, which is common to all qdiscs
+ and to provide rtnetlink notifications.
+
+ All real intelligent work is done inside qdisc modules.
+
+
+
+ Every discipline has two major routines: enqueue and dequeue.
+
+ ---dequeue
+
+ dequeue usually returns a skb to send. It is allowed to return NULL,
+ but it does not mean that queue is empty, it just means that
+ discipline does not want to send anything this time.
+ Queue is really empty if q->q.qlen == 0.
+ For complicated disciplines with multiple queues q->q is not
+ real packet queue, but however q->q.qlen must be valid.
+
+ ---enqueue
+
+ enqueue returns 0, if packet was enqueued successfully.
+ If packet (this one or another one) was dropped, it returns
+ not zero error code.
+ NET_XMIT_DROP - this packet dropped
+ Expected action: do not backoff, but wait until queue will clear.
+ NET_XMIT_CN - probably this packet enqueued, but another one dropped.
+ Expected action: backoff or ignore
+ NET_XMIT_POLICED - dropped by police.
+ Expected action: backoff or error to real-time apps.
+
+ Auxiliary routines:
+
+ ---requeue
+
+ requeues once dequeued packet. It is used for non-standard or
+ just buggy devices, which can defer output even if dev->tbusy=0.
+
+ ---reset
+
+ returns qdisc to initial state: purge all buffers, clear all
+ timers, counters (except for statistics) etc.
+
+ ---init
+
+ initializes newly created qdisc.
+
+ ---destroy
+
+ destroys resources allocated by init and during lifetime of qdisc.
+
+ ---change
+
+ changes qdisc parameters.
+ */
+
+/* Protects list of registered TC modules. It is pure SMP lock. */
+static rwlock_t qdisc_mod_lock = RW_LOCK_UNLOCKED;
+
+
+/************************************************
+ * Queueing disciplines manipulation. *
+ ************************************************/
+
+
+/* The list of all installed queueing disciplines. */
+
+static struct Qdisc_ops *qdisc_base = NULL;
+
+/* Register/uregister queueing discipline */
+
+int register_qdisc(struct Qdisc_ops *qops)
+{
+ struct Qdisc_ops *q, **qp;
+
+ write_lock(&qdisc_mod_lock);
+ for (qp = &qdisc_base; (q=*qp)!=NULL; qp = &q->next) {
+ if (strcmp(qops->id, q->id) == 0) {
+ write_unlock(&qdisc_mod_lock);
+ return -EEXIST;
+ }
+ }
+
+ if (qops->enqueue == NULL)
+ qops->enqueue = noop_qdisc_ops.enqueue;
+ if (qops->requeue == NULL)
+ qops->requeue = noop_qdisc_ops.requeue;
+ if (qops->dequeue == NULL)
+ qops->dequeue = noop_qdisc_ops.dequeue;
+
+ qops->next = NULL;
+ *qp = qops;
+ write_unlock(&qdisc_mod_lock);
+ return 0;
+}
+
+int unregister_qdisc(struct Qdisc_ops *qops)
+{
+ struct Qdisc_ops *q, **qp;
+ int err = -ENOENT;
+
+ write_lock(&qdisc_mod_lock);
+ for (qp = &qdisc_base; (q=*qp)!=NULL; qp = &q->next)
+ if (q == qops)
+ break;
+ if (q) {
+ *qp = q->next;
+ q->next = NULL;
+ err = 0;
+ }
+ write_unlock(&qdisc_mod_lock);
+ return err;
+}
+
+/* We know handle. Find qdisc among all qdisc's attached to device
+ (root qdisc, all its children, children of children etc.)
+ */
+
+struct Qdisc *qdisc_lookup(struct net_device *dev, u32 handle)
+{
+ struct Qdisc *q;
+
+ list_for_each_entry(q, &dev->qdisc_list, list) {
+ if (q->handle == handle)
+ return q;
+ }
+ return NULL;
+}
+
+struct Qdisc *qdisc_leaf(struct Qdisc *p, u32 classid)
+{
+ unsigned long cl;
+ struct Qdisc *leaf;
+ struct Qdisc_class_ops *cops = p->ops->cl_ops;
+
+ if (cops == NULL)
+ return NULL;
+ cl = cops->get(p, classid);
+
+ if (cl == 0)
+ return NULL;
+ leaf = cops->leaf(p, cl);
+ cops->put(p, cl);
+ return leaf;
+}
+
+/* Find queueing discipline by name */
+
+struct Qdisc_ops *qdisc_lookup_ops(struct rtattr *kind)
+{
+ struct Qdisc_ops *q = NULL;
+
+ if (kind) {
+ read_lock(&qdisc_mod_lock);
+ for (q = qdisc_base; q; q = q->next) {
+ if (rtattr_strcmp(kind, q->id) == 0)
+ break;
+ }
+ read_unlock(&qdisc_mod_lock);
+ }
+ return q;
+}
+
+static struct qdisc_rate_table *qdisc_rtab_list;
+
+struct qdisc_rate_table *qdisc_get_rtab(struct tc_ratespec *r, struct rtattr *tab)
+{
+ struct qdisc_rate_table *rtab;
+
+ for (rtab = qdisc_rtab_list; rtab; rtab = rtab->next) {
+ if (memcmp(&rtab->rate, r, sizeof(struct tc_ratespec)) == 0) {
+ rtab->refcnt++;
+ return rtab;
+ }
+ }
+
+ if (tab == NULL || r->rate == 0 || r->cell_log == 0 || RTA_PAYLOAD(tab) != 1024)
+ return NULL;
+
+ rtab = kmalloc(sizeof(*rtab), GFP_KERNEL);
+ if (rtab) {
+ rtab->rate = *r;
+ rtab->refcnt = 1;
+ memcpy(rtab->data, RTA_DATA(tab), 1024);
+ rtab->next = qdisc_rtab_list;
+ qdisc_rtab_list = rtab;
+ }
+ return rtab;
+}
+
+void qdisc_put_rtab(struct qdisc_rate_table *tab)
+{
+ struct qdisc_rate_table *rtab, **rtabp;
+
+ if (!tab || --tab->refcnt)
+ return;
+
+ for (rtabp = &qdisc_rtab_list; (rtab=*rtabp) != NULL; rtabp = &rtab->next) {
+ if (rtab == tab) {
+ *rtabp = rtab->next;
+ kfree(rtab);
+ return;
+ }
+ }
+}
+
+
+/* Allocate an unique handle from space managed by kernel */
+
+u32 qdisc_alloc_handle(struct net_device *dev)
+{
+ int i = 0x10000;
+ static u32 autohandle = TC_H_MAKE(0x80000000U, 0);
+
+ do {
+ autohandle += TC_H_MAKE(0x10000U, 0);
+ if (autohandle == TC_H_MAKE(TC_H_ROOT, 0))
+ autohandle = TC_H_MAKE(0x80000000U, 0);
+ } while (qdisc_lookup(dev, autohandle) && --i > 0);
+
+ return i>0 ? autohandle : 0;
+}
+
+/* Attach toplevel qdisc to device dev */
+
+static struct Qdisc *
+dev_graft_qdisc(struct net_device *dev, struct Qdisc *qdisc)
+{
+ struct Qdisc *oqdisc;
+
+ if (dev->flags & IFF_UP)
+ dev_deactivate(dev);
+
+ write_lock(&qdisc_tree_lock);
+ spin_lock_bh(&dev->queue_lock);
+ if (qdisc && qdisc->flags&TCQ_F_INGRESS) {
+ oqdisc = dev->qdisc_ingress;
+ /* Prune old scheduler */
+ if (oqdisc && atomic_read(&oqdisc->refcnt) <= 1) {
+ /* delete */
+ qdisc_reset(oqdisc);
+ dev->qdisc_ingress = NULL;
+ } else { /* new */
+ dev->qdisc_ingress = qdisc;
+ }
+
+ } else {
+
+ oqdisc = dev->qdisc_sleeping;
+
+ /* Prune old scheduler */
+ if (oqdisc && atomic_read(&oqdisc->refcnt) <= 1)
+ qdisc_reset(oqdisc);
+
+ /* ... and graft new one */
+ if (qdisc == NULL)
+ qdisc = &noop_qdisc;
+ dev->qdisc_sleeping = qdisc;
+ dev->qdisc = &noop_qdisc;
+ }
+
+ spin_unlock_bh(&dev->queue_lock);
+ write_unlock(&qdisc_tree_lock);
+
+ if (dev->flags & IFF_UP)
+ dev_activate(dev);
+
+ return oqdisc;
+}
+
+
+/* Graft qdisc "new" to class "classid" of qdisc "parent" or
+ to device "dev".
+
+ Old qdisc is not destroyed but returned in *old.
+ */
+
+int qdisc_graft(struct net_device *dev, struct Qdisc *parent, u32 classid,
+ struct Qdisc *new, struct Qdisc **old)
+{
+ int err = 0;
+ struct Qdisc *q = *old;
+
+
+ if (parent == NULL) {
+ if (q && q->flags&TCQ_F_INGRESS) {
+ *old = dev_graft_qdisc(dev, q);
+ } else {
+ *old = dev_graft_qdisc(dev, new);
+ }
+ } else {
+ struct Qdisc_class_ops *cops = parent->ops->cl_ops;
+
+ err = -EINVAL;
+
+ if (cops) {
+ unsigned long cl = cops->get(parent, classid);
+ if (cl) {
+ err = cops->graft(parent, cl, new, old);
+ if (new)
+ new->parent = classid;
+ cops->put(parent, cl);
+ }
+ }
+ }
+ return err;
+}
+
+/*
+ Allocate and initialize new qdisc.
+
+ Parameters are passed via opt.
+ */
+
+static struct Qdisc *
+qdisc_create(struct net_device *dev, u32 handle, struct rtattr **tca, int *errp)
+{
+ int err;
+ struct rtattr *kind = tca[TCA_KIND-1];
+ struct Qdisc *sch = NULL;
+ struct Qdisc_ops *ops;
+ int size;
+
+ ops = qdisc_lookup_ops(kind);
+#ifdef CONFIG_KMOD
+ if (ops==NULL && tca[TCA_KIND-1] != NULL) {
+ char module_name[4 + IFNAMSIZ + 1];
+
+ if (RTA_PAYLOAD(kind) <= IFNAMSIZ) {
+ sprintf(module_name, "sch_%s", (char*)RTA_DATA(kind));
+ request_module (module_name);
+ ops = qdisc_lookup_ops(kind);
+ }
+ }
+#endif
+
+ err = -EINVAL;
+ if (ops == NULL)
+ goto err_out;
+
+ size = sizeof(*sch) + ops->priv_size;
+
+ sch = kmalloc(size, GFP_KERNEL);
+ err = -ENOBUFS;
+ if (!sch)
+ goto err_out;
+
+ /* Grrr... Resolve race condition with module unload */
+
+ err = -EINVAL;
+ if (ops != qdisc_lookup_ops(kind))
+ goto err_out;
+
+ memset(sch, 0, size);
+
+ INIT_LIST_HEAD(&sch->list);
+ skb_queue_head_init(&sch->q);
+
+ if (handle == TC_H_INGRESS)
+ sch->flags |= TCQ_F_INGRESS;
+
+ sch->ops = ops;
+ sch->enqueue = ops->enqueue;
+ sch->dequeue = ops->dequeue;
+ sch->dev = dev;
+ atomic_set(&sch->refcnt, 1);
+ sch->stats.lock = &dev->queue_lock;
+ if (handle == 0) {
+ handle = qdisc_alloc_handle(dev);
+ err = -ENOMEM;
+ if (handle == 0)
+ goto err_out;
+ }
+
+ if (handle == TC_H_INGRESS)
+ sch->handle =TC_H_MAKE(TC_H_INGRESS, 0);
+ else
+ sch->handle = handle;
+
+ if (!ops->init || (err = ops->init(sch, tca[TCA_OPTIONS-1])) == 0) {
+ write_lock(&qdisc_tree_lock);
+ list_add_tail(&sch->list, &dev->qdisc_list);
+ write_unlock(&qdisc_tree_lock);
+#ifdef CONFIG_NET_ESTIMATOR
+ if (tca[TCA_RATE-1])
+ qdisc_new_estimator(&sch->stats, tca[TCA_RATE-1]);
+#endif
+ return sch;
+ }
+
+err_out:
+ *errp = err;
+ if (sch)
+ kfree(sch);
+ return NULL;
+}
+
+static int qdisc_change(struct Qdisc *sch, struct rtattr **tca)
+{
+ if (tca[TCA_OPTIONS-1]) {
+ int err;
+
+ if (sch->ops->change == NULL)
+ return -EINVAL;
+ err = sch->ops->change(sch, tca[TCA_OPTIONS-1]);
+ if (err)
+ return err;
+ }
+#ifdef CONFIG_NET_ESTIMATOR
+ if (tca[TCA_RATE-1]) {
+ qdisc_kill_estimator(&sch->stats);
+ qdisc_new_estimator(&sch->stats, tca[TCA_RATE-1]);
+ }
+#endif
+ return 0;
+}
+
+struct check_loop_arg
+{
+ struct qdisc_walker w;
+ struct Qdisc *p;
+ int depth;
+};
+
+static int check_loop_fn(struct Qdisc *q, unsigned long cl, struct qdisc_walker *w);
+
+static int check_loop(struct Qdisc *q, struct Qdisc *p, int depth)
+{
+ struct check_loop_arg arg;
+
+ if (q->ops->cl_ops == NULL)
+ return 0;
+
+ arg.w.stop = arg.w.skip = arg.w.count = 0;
+ arg.w.fn = check_loop_fn;
+ arg.depth = depth;
+ arg.p = p;
+ q->ops->cl_ops->walk(q, &arg.w);
+ return arg.w.stop ? -ELOOP : 0;
+}
+
+static int
+check_loop_fn(struct Qdisc *q, unsigned long cl, struct qdisc_walker *w)
+{
+ struct Qdisc *leaf;
+ struct Qdisc_class_ops *cops = q->ops->cl_ops;
+ struct check_loop_arg *arg = (struct check_loop_arg *)w;
+
+ leaf = cops->leaf(q, cl);
+ if (leaf) {
+ if (leaf == arg->p || arg->depth > 7)
+ return -ELOOP;
+ return check_loop(leaf, arg->p, arg->depth + 1);
+ }
+ return 0;
+}
+
+/*
+ * Delete/get qdisc.
+ */
+
+static int tc_get_qdisc(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
+{
+ struct tcmsg *tcm = NLMSG_DATA(n);
+ struct rtattr **tca = arg;
+ struct net_device *dev;
+ u32 clid = tcm->tcm_parent;
+ struct Qdisc *q = NULL;
+ struct Qdisc *p = NULL;
+ int err;
+
+ if ((dev = __dev_get_by_index(tcm->tcm_ifindex)) == NULL)
+ return -ENODEV;
+
+ if (clid) {
+ if (clid != TC_H_ROOT) {
+ if (TC_H_MAJ(clid) != TC_H_MAJ(TC_H_INGRESS)) {
+ if ((p = qdisc_lookup(dev, TC_H_MAJ(clid))) == NULL)
+ return -ENOENT;
+ q = qdisc_leaf(p, clid);
+ } else { /* ingress */
+ q = dev->qdisc_ingress;
+ }
+ } else {
+ q = dev->qdisc_sleeping;
+ }
+ if (!q)
+ return -ENOENT;
+
+ if (tcm->tcm_handle && q->handle != tcm->tcm_handle)
+ return -EINVAL;
+ } else {
+ if ((q = qdisc_lookup(dev, tcm->tcm_handle)) == NULL)
+ return -ENOENT;
+ }
+
+ if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], q->ops->id))
+ return -EINVAL;
+
+ if (n->nlmsg_type == RTM_DELQDISC) {
+ if (!clid)
+ return -EINVAL;
+ if (q->handle == 0)
+ return -ENOENT;
+ if ((err = qdisc_graft(dev, p, clid, NULL, &q)) != 0)
+ return err;
+ if (q) {
+ qdisc_notify(skb, n, clid, q, NULL);
+ spin_lock_bh(&dev->queue_lock);
+ qdisc_destroy(q);
+ spin_unlock_bh(&dev->queue_lock);
+ }
+ } else {
+ qdisc_notify(skb, n, clid, NULL, q);
+ }
+ return 0;
+}
+
+/*
+ Create/change qdisc.
+ */
+
+static int tc_modify_qdisc(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
+{
+ struct tcmsg *tcm = NLMSG_DATA(n);
+ struct rtattr **tca = arg;
+ struct net_device *dev;
+ u32 clid = tcm->tcm_parent;
+ struct Qdisc *q = NULL;
+ struct Qdisc *p = NULL;
+ int err;
+
+ if ((dev = __dev_get_by_index(tcm->tcm_ifindex)) == NULL)
+ return -ENODEV;
+
+ if (clid) {
+ if (clid != TC_H_ROOT) {
+ if (clid != TC_H_INGRESS) {
+ if ((p = qdisc_lookup(dev, TC_H_MAJ(clid))) == NULL)
+ return -ENOENT;
+ q = qdisc_leaf(p, clid);
+ } else { /*ingress */
+ q = dev->qdisc_ingress;
+ }
+ } else {
+ q = dev->qdisc_sleeping;
+ }
+
+ /* It may be default qdisc, ignore it */
+ if (q && q->handle == 0)
+ q = NULL;
+
+ if (!q || !tcm->tcm_handle || q->handle != tcm->tcm_handle) {
+ if (tcm->tcm_handle) {
+ if (q && !(n->nlmsg_flags&NLM_F_REPLACE))
+ return -EEXIST;
+ if (TC_H_MIN(tcm->tcm_handle))
+ return -EINVAL;
+ if ((q = qdisc_lookup(dev, tcm->tcm_handle)) == NULL)
+ goto create_n_graft;
+ if (n->nlmsg_flags&NLM_F_EXCL)
+ return -EEXIST;
+ if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], q->ops->id))
+ return -EINVAL;
+ if (q == p ||
+ (p && check_loop(q, p, 0)))
+ return -ELOOP;
+ atomic_inc(&q->refcnt);
+ goto graft;
+ } else {
+ if (q == NULL)
+ goto create_n_graft;
+
+ /* This magic test requires explanation.
+ *
+ * We know, that some child q is already
+ * attached to this parent and have choice:
+ * either to change it or to create/graft new one.
+ *
+ * 1. We are allowed to create/graft only
+ * if CREATE and REPLACE flags are set.
+ *
+ * 2. If EXCL is set, requestor wanted to say,
+ * that qdisc tcm_handle is not expected
+ * to exist, so that we choose create/graft too.
+ *
+ * 3. The last case is when no flags are set.
+ * Alas, it is sort of hole in API, we
+ * cannot decide what to do unambiguously.
+ * For now we select create/graft, if
+ * user gave KIND, which does not match existing.
+ */
+ if ((n->nlmsg_flags&NLM_F_CREATE) &&
+ (n->nlmsg_flags&NLM_F_REPLACE) &&
+ ((n->nlmsg_flags&NLM_F_EXCL) ||
+ (tca[TCA_KIND-1] &&
+ rtattr_strcmp(tca[TCA_KIND-1], q->ops->id))))
+ goto create_n_graft;
+ }
+ }
+ } else {
+ if (!tcm->tcm_handle)
+ return -EINVAL;
+ q = qdisc_lookup(dev, tcm->tcm_handle);
+ }
+
+ /* Change qdisc parameters */
+ if (q == NULL)
+ return -ENOENT;
+ if (n->nlmsg_flags&NLM_F_EXCL)
+ return -EEXIST;
+ if (tca[TCA_KIND-1] && rtattr_strcmp(tca[TCA_KIND-1], q->ops->id))
+ return -EINVAL;
+ err = qdisc_change(q, tca);
+ if (err == 0)
+ qdisc_notify(skb, n, clid, NULL, q);
+ return err;
+
+create_n_graft:
+ if (!(n->nlmsg_flags&NLM_F_CREATE))
+ return -ENOENT;
+ if (clid == TC_H_INGRESS)
+ q = qdisc_create(dev, tcm->tcm_parent, tca, &err);
+ else
+ q = qdisc_create(dev, tcm->tcm_handle, tca, &err);
+ if (q == NULL)
+ return err;
+
+graft:
+ if (1) {
+ struct Qdisc *old_q = NULL;
+ err = qdisc_graft(dev, p, clid, q, &old_q);
+ if (err) {
+ if (q) {
+ spin_lock_bh(&dev->queue_lock);
+ qdisc_destroy(q);
+ spin_unlock_bh(&dev->queue_lock);
+ }
+ return err;
+ }
+ qdisc_notify(skb, n, clid, old_q, q);
+ if (old_q) {
+ spin_lock_bh(&dev->queue_lock);
+ qdisc_destroy(old_q);
+ spin_unlock_bh(&dev->queue_lock);
+ }
+ }
+ return 0;
+}
+
+int qdisc_copy_stats(struct sk_buff *skb, struct tc_stats *st)
+{
+ spin_lock_bh(st->lock);
+ RTA_PUT(skb, TCA_STATS, (char*)&st->lock - (char*)st, st);
+ spin_unlock_bh(st->lock);
+ return 0;
+
+rtattr_failure:
+ spin_unlock_bh(st->lock);
+ return -1;
+}
+
+
+static int tc_fill_qdisc(struct sk_buff *skb, struct Qdisc *q, u32 clid,
+ u32 pid, u32 seq, unsigned flags, int event)
+{
+ struct tcmsg *tcm;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+ nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*tcm));
+ nlh->nlmsg_flags = flags;
+ tcm = NLMSG_DATA(nlh);
+ tcm->tcm_family = AF_UNSPEC;
+ tcm->tcm_ifindex = q->dev ? q->dev->ifindex : 0;
+ tcm->tcm_parent = clid;
+ tcm->tcm_handle = q->handle;
+ tcm->tcm_info = atomic_read(&q->refcnt);
+ RTA_PUT(skb, TCA_KIND, IFNAMSIZ, q->ops->id);
+ if (q->ops->dump && q->ops->dump(q, skb) < 0)
+ goto rtattr_failure;
+ q->stats.qlen = q->q.qlen;
+ if (qdisc_copy_stats(skb, &q->stats))
+ goto rtattr_failure;
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int qdisc_notify(struct sk_buff *oskb, struct nlmsghdr *n,
+ u32 clid, struct Qdisc *old, struct Qdisc *new)
+{
+ struct sk_buff *skb;
+ u32 pid = oskb ? NETLINK_CB(oskb).pid : 0;
+
+ skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!skb)
+ return -ENOBUFS;
+
+ if (old && old->handle) {
+ if (tc_fill_qdisc(skb, old, clid, pid, n->nlmsg_seq, 0, RTM_DELQDISC) < 0)
+ goto err_out;
+ }
+ if (new) {
+ if (tc_fill_qdisc(skb, new, clid, pid, n->nlmsg_seq, old ? NLM_F_REPLACE : 0, RTM_NEWQDISC) < 0)
+ goto err_out;
+ }
+
+ if (skb->len)
+ return rtnetlink_send(skb, pid, RTMGRP_TC, n->nlmsg_flags&NLM_F_ECHO);
+
+err_out:
+ kfree_skb(skb);
+ return -EINVAL;
+}
+
+static int tc_dump_qdisc(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int idx, q_idx;
+ int s_idx, s_q_idx;
+ struct net_device *dev;
+ struct Qdisc *q;
+
+ s_idx = cb->args[0];
+ s_q_idx = q_idx = cb->args[1];
+ read_lock(&dev_base_lock);
+ for (dev=dev_base, idx=0; dev; dev = dev->next, idx++) {
+ if (idx < s_idx)
+ continue;
+ if (idx > s_idx)
+ s_q_idx = 0;
+ read_lock(&qdisc_tree_lock);
+ q_idx = 0;
+ list_for_each_entry(q, &dev->qdisc_list, list) {
+ if (q_idx < s_q_idx) {
+ q_idx++;
+ continue;
+ }
+ if (tc_fill_qdisc(skb, q, q->parent, NETLINK_CB(cb->skb).pid,
+ cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWQDISC) <= 0) {
+ read_unlock(&qdisc_tree_lock);
+ goto done;
+ }
+ q_idx++;
+ }
+ read_unlock(&qdisc_tree_lock);
+ }
+
+done:
+ read_unlock(&dev_base_lock);
+
+ cb->args[0] = idx;
+ cb->args[1] = q_idx;
+
+ return skb->len;
+}
+
+
+
+/************************************************
+ * Traffic classes manipulation. *
+ ************************************************/
+
+
+
+static int tc_ctl_tclass(struct sk_buff *skb, struct nlmsghdr *n, void *arg)
+{
+ struct tcmsg *tcm = NLMSG_DATA(n);
+ struct rtattr **tca = arg;
+ struct net_device *dev;
+ struct Qdisc *q = NULL;
+ struct Qdisc_class_ops *cops;
+ unsigned long cl = 0;
+ unsigned long new_cl;
+ u32 pid = tcm->tcm_parent;
+ u32 clid = tcm->tcm_handle;
+ u32 qid = TC_H_MAJ(clid);
+ int err;
+
+ if ((dev = __dev_get_by_index(tcm->tcm_ifindex)) == NULL)
+ return -ENODEV;
+
+ /*
+ parent == TC_H_UNSPEC - unspecified parent.
+ parent == TC_H_ROOT - class is root, which has no parent.
+ parent == X:0 - parent is root class.
+ parent == X:Y - parent is a node in hierarchy.
+ parent == 0:Y - parent is X:Y, where X:0 is qdisc.
+
+ handle == 0:0 - generate handle from kernel pool.
+ handle == 0:Y - class is X:Y, where X:0 is qdisc.
+ handle == X:Y - clear.
+ handle == X:0 - root class.
+ */
+
+ /* Step 1. Determine qdisc handle X:0 */
+
+ if (pid != TC_H_ROOT) {
+ u32 qid1 = TC_H_MAJ(pid);
+
+ if (qid && qid1) {
+ /* If both majors are known, they must be identical. */
+ if (qid != qid1)
+ return -EINVAL;
+ } else if (qid1) {
+ qid = qid1;
+ } else if (qid == 0)
+ qid = dev->qdisc_sleeping->handle;
+
+ /* Now qid is genuine qdisc handle consistent
+ both with parent and child.
+
+ TC_H_MAJ(pid) still may be unspecified, complete it now.
+ */
+ if (pid)
+ pid = TC_H_MAKE(qid, pid);
+ } else {
+ if (qid == 0)
+ qid = dev->qdisc_sleeping->handle;
+ }
+
+ /* OK. Locate qdisc */
+ if ((q = qdisc_lookup(dev, qid)) == NULL)
+ return -ENOENT;
+
+ /* An check that it supports classes */
+ cops = q->ops->cl_ops;
+ if (cops == NULL)
+ return -EINVAL;
+
+ /* Now try to get class */
+ if (clid == 0) {
+ if (pid == TC_H_ROOT)
+ clid = qid;
+ } else
+ clid = TC_H_MAKE(qid, clid);
+
+ if (clid)
+ cl = cops->get(q, clid);
+
+ if (cl == 0) {
+ err = -ENOENT;
+ if (n->nlmsg_type != RTM_NEWTCLASS || !(n->nlmsg_flags&NLM_F_CREATE))
+ goto out;
+ } else {
+ switch (n->nlmsg_type) {
+ case RTM_NEWTCLASS:
+ err = -EEXIST;
+ if (n->nlmsg_flags&NLM_F_EXCL)
+ goto out;
+ break;
+ case RTM_DELTCLASS:
+ err = cops->delete(q, cl);
+ if (err == 0)
+ tclass_notify(skb, n, q, cl, RTM_DELTCLASS);
+ goto out;
+ case RTM_GETTCLASS:
+ err = tclass_notify(skb, n, q, cl, RTM_NEWTCLASS);
+ goto out;
+ default:
+ err = -EINVAL;
+ goto out;
+ }
+ }
+
+ new_cl = cl;
+ err = cops->change(q, clid, pid, tca, &new_cl);
+ if (err == 0)
+ tclass_notify(skb, n, q, new_cl, RTM_NEWTCLASS);
+
+out:
+ if (cl)
+ cops->put(q, cl);
+
+ return err;
+}
+
+
+static int tc_fill_tclass(struct sk_buff *skb, struct Qdisc *q,
+ unsigned long cl,
+ u32 pid, u32 seq, unsigned flags, int event)
+{
+ struct tcmsg *tcm;
+ struct nlmsghdr *nlh;
+ unsigned char *b = skb->tail;
+
+ nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*tcm));
+ nlh->nlmsg_flags = flags;
+ tcm = NLMSG_DATA(nlh);
+ tcm->tcm_family = AF_UNSPEC;
+ tcm->tcm_ifindex = q->dev ? q->dev->ifindex : 0;
+ tcm->tcm_parent = q->handle;
+ tcm->tcm_handle = q->handle;
+ tcm->tcm_info = 0;
+ RTA_PUT(skb, TCA_KIND, IFNAMSIZ, q->ops->id);
+ if (q->ops->cl_ops->dump && q->ops->cl_ops->dump(q, cl, skb, tcm) < 0)
+ goto rtattr_failure;
+ nlh->nlmsg_len = skb->tail - b;
+ return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int tclass_notify(struct sk_buff *oskb, struct nlmsghdr *n,
+ struct Qdisc *q, unsigned long cl, int event)
+{
+ struct sk_buff *skb;
+ u32 pid = oskb ? NETLINK_CB(oskb).pid : 0;
+
+ skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
+ if (!skb)
+ return -ENOBUFS;
+
+ if (tc_fill_tclass(skb, q, cl, pid, n->nlmsg_seq, 0, event) < 0) {
+ kfree_skb(skb);
+ return -EINVAL;
+ }
+
+ return rtnetlink_send(skb, pid, RTMGRP_TC, n->nlmsg_flags&NLM_F_ECHO);
+}
+
+struct qdisc_dump_args
+{
+ struct qdisc_walker w;
+ struct sk_buff *skb;
+ struct netlink_callback *cb;
+};
+
+static int qdisc_class_dump(struct Qdisc *q, unsigned long cl, struct qdisc_walker *arg)
+{
+ struct qdisc_dump_args *a = (struct qdisc_dump_args *)arg;
+
+ return tc_fill_tclass(a->skb, q, cl, NETLINK_CB(a->cb->skb).pid,
+ a->cb->nlh->nlmsg_seq, NLM_F_MULTI, RTM_NEWTCLASS);
+}
+
+static int tc_dump_tclass(struct sk_buff *skb, struct netlink_callback *cb)
+{
+ int t;
+ int s_t;
+ struct net_device *dev;
+ struct Qdisc *q;
+ struct tcmsg *tcm = (struct tcmsg*)NLMSG_DATA(cb->nlh);
+ struct qdisc_dump_args arg;
+
+ if (cb->nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*tcm)))
+ return 0;
+ if ((dev = dev_get_by_index(tcm->tcm_ifindex)) == NULL)
+ return 0;
+
+ s_t = cb->args[0];
+ t = 0;
+
+ read_lock(&qdisc_tree_lock);
+ list_for_each_entry(q, &dev->qdisc_list, list) {
+ if (t < s_t || !q->ops->cl_ops ||
+ (tcm->tcm_parent &&
+ TC_H_MAJ(tcm->tcm_parent) != q->handle)) {
+ t++;
+ continue;
+ }
+ if (t > s_t)
+ memset(&cb->args[1], 0, sizeof(cb->args)-sizeof(cb->args[0]));
+ arg.w.fn = qdisc_class_dump;
+ arg.skb = skb;
+ arg.cb = cb;
+ arg.w.stop = 0;
+ arg.w.skip = cb->args[1];
+ arg.w.count = 0;
+ q->ops->cl_ops->walk(q, &arg.w);
+ cb->args[1] = arg.w.count;
+ if (arg.w.stop)
+ break;
+ t++;
+ }
+ read_unlock(&qdisc_tree_lock);
+
+ cb->args[0] = t;
+
+ dev_put(dev);
+ return skb->len;
+}
+
+int psched_us_per_tick = 1;
+int psched_tick_per_us = 1;
+
+#ifdef CONFIG_PROC_FS
+static int psched_read_proc(char *buffer, char **start, off_t offset,
+ int length, int *eof, void *data)
+{
+ int len;
+
+ len = sprintf(buffer, "%08x %08x %08x %08x\n",
+ psched_tick_per_us, psched_us_per_tick,
+ 1000000, HZ);
+
+ len -= offset;
+
+ if (len > length)
+ len = length;
+ if(len < 0)
+ len = 0;
+
+ *start = buffer + offset;
+ *eof = 1;
+
+ return len;
+}
+#endif
+
+#if PSCHED_CLOCK_SOURCE == PSCHED_GETTIMEOFDAY
+int psched_tod_diff(int delta_sec, int bound)
+{
+ int delta;
+
+ if (bound <= 1000000 || delta_sec > (0x7FFFFFFF/1000000)-1)
+ return bound;
+ delta = delta_sec * 1000000;
+ if (delta > bound)
+ delta = bound;
+ return delta;
+}
+#endif
+
+psched_time_t psched_time_base;
+
+#if PSCHED_CLOCK_SOURCE == PSCHED_CPU
+psched_tdiff_t psched_clock_per_hz;
+int psched_clock_scale;
+#endif
+
+#ifdef PSCHED_WATCHER
+PSCHED_WATCHER psched_time_mark;
+
+static void psched_tick(unsigned long);
+
+static struct timer_list psched_timer =
+ { function: psched_tick };
+
+static void psched_tick(unsigned long dummy)
+{
+#if PSCHED_CLOCK_SOURCE == PSCHED_CPU
+ psched_time_t dummy_stamp;
+ PSCHED_GET_TIME(dummy_stamp);
+ /* It is OK up to 4GHz cpu */
+ psched_timer.expires = jiffies + 1*HZ;
+#else
+ unsigned long now = jiffies;
+ psched_time_base += ((u64)(now-psched_time_mark))<<PSCHED_JSCALE;
+ psched_time_mark = now;
+ psched_timer.expires = now + 60*60*HZ;
+#endif
+ add_timer(&psched_timer);
+}
+#endif
+
+#if PSCHED_CLOCK_SOURCE == PSCHED_CPU
+int __init psched_calibrate_clock(void)
+{
+ psched_time_t stamp, stamp1;
+ struct timeval tv, tv1;
+ psched_tdiff_t delay;
+ long rdelay;
+ unsigned long stop;
+
+#ifdef PSCHED_WATCHER
+ psched_tick(0);
+#endif
+ stop = jiffies + HZ/10;
+ PSCHED_GET_TIME(stamp);
+ do_gettimeofday(&tv);
+ while (time_before(jiffies, stop)) {
+ barrier();
+ cpu_relax();
+ }
+ PSCHED_GET_TIME(stamp1);
+ do_gettimeofday(&tv1);
+
+ delay = PSCHED_TDIFF(stamp1, stamp);
+ rdelay = tv1.tv_usec - tv.tv_usec;
+ rdelay += (tv1.tv_sec - tv.tv_sec)*1000000;
+ if (rdelay > delay)
+ return -1;
+ delay /= rdelay;
+ psched_tick_per_us = delay;
+ while ((delay>>=1) != 0)
+ psched_clock_scale++;
+ psched_us_per_tick = 1<<psched_clock_scale;
+ psched_clock_per_hz = (psched_tick_per_us*(1000000/HZ))>>psched_clock_scale;
+ return 0;
+}
+#endif
+
+int __init pktsched_init(void)
+{
+ struct rtnetlink_link *link_p;
+
+#if PSCHED_CLOCK_SOURCE == PSCHED_CPU
+ if (psched_calibrate_clock() < 0)
+ return -1;
+#elif PSCHED_CLOCK_SOURCE == PSCHED_JIFFIES
+ psched_tick_per_us = HZ<<PSCHED_JSCALE;
+ psched_us_per_tick = 1000000;
+#ifdef PSCHED_WATCHER
+ psched_tick(0);
+#endif
+#endif
+
+ link_p = rtnetlink_links[PF_UNSPEC];
+
+ /* Setup rtnetlink links. It is made here to avoid
+ exporting large number of public symbols.
+ */
+
+ if (link_p) {
+ link_p[RTM_NEWQDISC-RTM_BASE].doit = tc_modify_qdisc;
+ link_p[RTM_DELQDISC-RTM_BASE].doit = tc_get_qdisc;
+ link_p[RTM_GETQDISC-RTM_BASE].doit = tc_get_qdisc;
+ link_p[RTM_GETQDISC-RTM_BASE].dumpit = tc_dump_qdisc;
+ link_p[RTM_NEWTCLASS-RTM_BASE].doit = tc_ctl_tclass;
+ link_p[RTM_DELTCLASS-RTM_BASE].doit = tc_ctl_tclass;
+ link_p[RTM_GETTCLASS-RTM_BASE].doit = tc_ctl_tclass;
+ link_p[RTM_GETTCLASS-RTM_BASE].dumpit = tc_dump_tclass;
+ }
+
+#define INIT_QDISC(name) { \
+ extern struct Qdisc_ops name##_qdisc_ops; \
+ register_qdisc(& name##_qdisc_ops); \
+ }
+
+ INIT_QDISC(pfifo);
+ INIT_QDISC(bfifo);
+
+#ifdef CONFIG_NET_SCH_CBQ
+ INIT_QDISC(cbq);
+#endif
+#ifdef CONFIG_NET_SCH_HTB
+ INIT_QDISC(htb);
+#endif
+#ifdef CONFIG_NET_SCH_CSZ
+ INIT_QDISC(csz);
+#endif
+#ifdef CONFIG_NET_SCH_HPFQ
+ INIT_QDISC(hpfq);
+#endif
+#ifdef CONFIG_NET_SCH_HFSC
+ INIT_QDISC(hfsc);
+#endif
+#ifdef CONFIG_NET_SCH_RED
+ INIT_QDISC(red);
+#endif
+#ifdef CONFIG_NET_SCH_WRR
+ INIT_QDISC(wrr);
+#endif
+#ifdef CONFIG_NET_SCH_GRED
+ INIT_QDISC(gred);
+#endif
+#ifdef CONFIG_NET_SCH_INGRESS
+ INIT_QDISC(ingress);
+#endif
+#ifdef CONFIG_NET_SCH_DSMARK
+ INIT_QDISC(dsmark);
+#endif
+#ifdef CONFIG_NET_SCH_SFQ
+ INIT_QDISC(sfq);
+#endif
+#ifdef CONFIG_NET_SCH_TBF
+ INIT_QDISC(tbf);
+#endif
+#ifdef CONFIG_NET_SCH_TEQL
+ teql_init();
+#endif
+#ifdef CONFIG_NET_SCH_PRIO
+ INIT_QDISC(prio);
+#endif
+#ifdef CONFIG_NET_SCH_ATM
+ INIT_QDISC(atm);
+#endif
+#ifdef CONFIG_NET_CLS
+ tc_filter_init();
+#endif
+
+#ifdef CONFIG_PROC_FS
+ create_proc_read_entry("net/psched", 0, 0, psched_read_proc, NULL);
+#endif
+
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_atm.c b/uClinux-2.4.31-uc0/net/sched/sch_atm.c
new file mode 100644
index 0000000..a926404
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_atm.c
@@ -0,0 +1,717 @@
+/* net/sched/sch_atm.c - ATM VC selection "queueing discipline" */
+
+/* Written 1998-2000 by Werner Almesberger, EPFL ICA */
+
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/skbuff.h>
+#include <linux/interrupt.h>
+#include <linux/atmdev.h>
+#include <linux/atmclip.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/file.h> /* for fput */
+#include <net/pkt_sched.h>
+#include <net/sock.h>
+
+
+extern struct socket *sockfd_lookup(int fd, int *err); /* @@@ fix this */
+#define sockfd_put(sock) fput((sock)->file) /* @@@ copied because it's
+ __inline__ in socket.c */
+
+
+#if 0 /* control */
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+#if 0 /* data */
+#define D2PRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define D2PRINTK(format,args...)
+#endif
+
+
+/*
+ * The ATM queuing discipline provides a framework for invoking classifiers
+ * (aka "filters"), which in turn select classes of this queuing discipline.
+ * Each class maps the flow(s) it is handling to a given VC. Multiple classes
+ * may share the same VC.
+ *
+ * When creating a class, VCs are specified by passing the number of the open
+ * socket descriptor by which the calling process references the VC. The kernel
+ * keeps the VC open at least until all classes using it are removed.
+ *
+ * In this file, most functions are named atm_tc_* to avoid confusion with all
+ * the atm_* in net/atm. This naming convention differs from what's used in the
+ * rest of net/sched.
+ *
+ * Known bugs:
+ * - sometimes messes up the IP stack
+ * - any manipulations besides the few operations described in the README, are
+ * untested and likely to crash the system
+ * - should lock the flow while there is data in the queue (?)
+ */
+
+
+#define PRIV(sch) ((struct atm_qdisc_data *) (sch)->data)
+#define VCC2FLOW(vcc) ((struct atm_flow_data *) ((vcc)->user_back))
+
+
+struct atm_flow_data {
+ struct Qdisc *q; /* FIFO, TBF, etc. */
+ struct tcf_proto *filter_list;
+ struct atm_vcc *vcc; /* VCC; NULL if VCC is closed */
+ void (*old_pop)(struct atm_vcc *vcc,struct sk_buff *skb); /* chaining */
+ struct atm_qdisc_data *parent; /* parent qdisc */
+ struct socket *sock; /* for closing */
+ u32 classid; /* x:y type ID */
+ int ref; /* reference count */
+ struct tc_stats stats;
+ struct atm_flow_data *next;
+ struct atm_flow_data *excess; /* flow for excess traffic;
+ NULL to set CLP instead */
+ int hdr_len;
+ unsigned char hdr[0]; /* header data; MUST BE LAST */
+};
+
+struct atm_qdisc_data {
+ struct atm_flow_data link; /* unclassified skbs go here */
+ struct atm_flow_data *flows; /* NB: "link" is also on this
+ list */
+ struct tasklet_struct task; /* requeue tasklet */
+};
+
+
+/* ------------------------- Class/flow operations ------------------------- */
+
+
+static int find_flow(struct atm_qdisc_data *qdisc,struct atm_flow_data *flow)
+{
+ struct atm_flow_data *walk;
+
+ DPRINTK("find_flow(qdisc %p,flow %p)\n",qdisc,flow);
+ for (walk = qdisc->flows; walk; walk = walk->next)
+ if (walk == flow) return 1;
+ DPRINTK("find_flow: not found\n");
+ return 0;
+}
+
+
+static __inline__ struct atm_flow_data *lookup_flow(struct Qdisc *sch,
+ u32 classid)
+{
+ struct atm_flow_data *flow;
+
+ for (flow = PRIV(sch)->flows; flow; flow = flow->next)
+ if (flow->classid == classid) break;
+ return flow;
+}
+
+
+static int atm_tc_graft(struct Qdisc *sch,unsigned long arg,
+ struct Qdisc *new,struct Qdisc **old)
+{
+ struct atm_qdisc_data *p = PRIV(sch);
+ struct atm_flow_data *flow = (struct atm_flow_data *) arg;
+
+ DPRINTK("atm_tc_graft(sch %p,[qdisc %p],flow %p,new %p,old %p)\n",sch,
+ p,flow,new,old);
+ if (!find_flow(p,flow)) return -EINVAL;
+ if (!new) new = &noop_qdisc;
+ *old = xchg(&flow->q,new);
+ if (*old) qdisc_reset(*old);
+ return 0;
+}
+
+
+static struct Qdisc *atm_tc_leaf(struct Qdisc *sch,unsigned long cl)
+{
+ struct atm_flow_data *flow = (struct atm_flow_data *) cl;
+
+ DPRINTK("atm_tc_leaf(sch %p,flow %p)\n",sch,flow);
+ return flow ? flow->q : NULL;
+}
+
+
+static unsigned long atm_tc_get(struct Qdisc *sch,u32 classid)
+{
+ struct atm_qdisc_data *p __attribute__((unused)) = PRIV(sch);
+ struct atm_flow_data *flow;
+
+ DPRINTK("atm_tc_get(sch %p,[qdisc %p],classid %x)\n",sch,p,classid);
+ flow = lookup_flow(sch,classid);
+ if (flow) flow->ref++;
+ DPRINTK("atm_tc_get: flow %p\n",flow);
+ return (unsigned long) flow;
+}
+
+
+static unsigned long atm_tc_bind_filter(struct Qdisc *sch,
+ unsigned long parent, u32 classid)
+{
+ return atm_tc_get(sch,classid);
+}
+
+
+static void destroy_filters(struct atm_flow_data *flow)
+{
+ struct tcf_proto *filter;
+
+ while ((filter = flow->filter_list)) {
+ DPRINTK("destroy_filters: destroying filter %p\n",filter);
+ flow->filter_list = filter->next;
+ tcf_destroy(filter);
+ }
+}
+
+
+/*
+ * atm_tc_put handles all destructions, including the ones that are explicitly
+ * requested (atm_tc_destroy, etc.). The assumption here is that we never drop
+ * anything that still seems to be in use.
+ */
+
+static void atm_tc_put(struct Qdisc *sch, unsigned long cl)
+{
+ struct atm_qdisc_data *p = PRIV(sch);
+ struct atm_flow_data *flow = (struct atm_flow_data *) cl;
+ struct atm_flow_data **prev;
+
+ DPRINTK("atm_tc_put(sch %p,[qdisc %p],flow %p)\n",sch,p,flow);
+ if (--flow->ref) return;
+ DPRINTK("atm_tc_put: destroying\n");
+ for (prev = &p->flows; *prev; prev = &(*prev)->next)
+ if (*prev == flow) break;
+ if (!*prev) {
+ printk(KERN_CRIT "atm_tc_put: class %p not found\n",flow);
+ return;
+ }
+ *prev = flow->next;
+ DPRINTK("atm_tc_put: qdisc %p\n",flow->q);
+ qdisc_destroy(flow->q);
+ destroy_filters(flow);
+ if (flow->sock) {
+ DPRINTK("atm_tc_put: f_count %d\n",
+ file_count(flow->sock->file));
+ flow->vcc->pop = flow->old_pop;
+ sockfd_put(flow->sock);
+ }
+ if (flow->excess) atm_tc_put(sch,(unsigned long) flow->excess);
+ if (flow != &p->link) kfree(flow);
+ /*
+ * If flow == &p->link, the qdisc no longer works at this point and
+ * needs to be removed. (By the caller of atm_tc_put.)
+ */
+}
+
+
+static void sch_atm_pop(struct atm_vcc *vcc,struct sk_buff *skb)
+{
+ struct atm_qdisc_data *p = VCC2FLOW(vcc)->parent;
+
+ D2PRINTK("sch_atm_pop(vcc %p,skb %p,[qdisc %p])\n",vcc,skb,p);
+ VCC2FLOW(vcc)->old_pop(vcc,skb);
+ tasklet_schedule(&p->task);
+}
+
+
+static int atm_tc_change(struct Qdisc *sch, u32 classid, u32 parent,
+ struct rtattr **tca, unsigned long *arg)
+{
+ struct atm_qdisc_data *p = PRIV(sch);
+ struct atm_flow_data *flow = (struct atm_flow_data *) *arg;
+ struct atm_flow_data *excess = NULL;
+ struct rtattr *opt = tca[TCA_OPTIONS-1];
+ struct rtattr *tb[TCA_ATM_MAX];
+ struct socket *sock;
+ int fd,error,hdr_len;
+ void *hdr;
+
+ DPRINTK("atm_tc_change(sch %p,[qdisc %p],classid %x,parent %x,"
+ "flow %p,opt %p)\n",sch,p,classid,parent,flow,opt);
+ /*
+ * The concept of parents doesn't apply for this qdisc.
+ */
+ if (parent && parent != TC_H_ROOT && parent != sch->handle)
+ return -EINVAL;
+ /*
+ * ATM classes cannot be changed. In order to change properties of the
+ * ATM connection, that socket needs to be modified directly (via the
+ * native ATM API. In order to send a flow to a different VC, the old
+ * class needs to be removed and a new one added. (This may be changed
+ * later.)
+ */
+ if (flow) return -EBUSY;
+ if (opt == NULL || rtattr_parse(tb,TCA_ATM_MAX,RTA_DATA(opt),
+ RTA_PAYLOAD(opt))) return -EINVAL;
+ if (!tb[TCA_ATM_FD-1] || RTA_PAYLOAD(tb[TCA_ATM_FD-1]) < sizeof(fd))
+ return -EINVAL;
+ fd = *(int *) RTA_DATA(tb[TCA_ATM_FD-1]);
+ DPRINTK("atm_tc_change: fd %d\n",fd);
+ if (tb[TCA_ATM_HDR-1]) {
+ hdr_len = RTA_PAYLOAD(tb[TCA_ATM_HDR-1]);
+ hdr = RTA_DATA(tb[TCA_ATM_HDR-1]);
+ }
+ else {
+ hdr_len = RFC1483LLC_LEN;
+ hdr = NULL; /* default LLC/SNAP for IP */
+ }
+ if (!tb[TCA_ATM_EXCESS-1]) excess = NULL;
+ else {
+ if (RTA_PAYLOAD(tb[TCA_ATM_EXCESS-1]) != sizeof(u32))
+ return -EINVAL;
+ excess = (struct atm_flow_data *) atm_tc_get(sch,
+ *(u32 *) RTA_DATA(tb[TCA_ATM_EXCESS-1]));
+ if (!excess) return -ENOENT;
+ }
+ DPRINTK("atm_tc_change: type %d, payload %d, hdr_len %d\n",
+ opt->rta_type,RTA_PAYLOAD(opt),hdr_len);
+ if (!(sock = sockfd_lookup(fd,&error))) return error; /* f_count++ */
+ DPRINTK("atm_tc_change: f_count %d\n",file_count(sock->file));
+ if (sock->ops->family != PF_ATMSVC && sock->ops->family != PF_ATMPVC) {
+ error = -EPROTOTYPE;
+ goto err_out;
+ }
+ /* @@@ should check if the socket is really operational or we'll crash
+ on vcc->send */
+ if (classid) {
+ if (TC_H_MAJ(classid ^ sch->handle)) {
+ DPRINTK("atm_tc_change: classid mismatch\n");
+ error = -EINVAL;
+ goto err_out;
+ }
+ if (find_flow(p,flow)) {
+ error = -EEXIST;
+ goto err_out;
+ }
+ }
+ else {
+ int i;
+ unsigned long cl;
+
+ for (i = 1; i < 0x8000; i++) {
+ classid = TC_H_MAKE(sch->handle,0x8000 | i);
+ if (!(cl = atm_tc_get(sch,classid))) break;
+ atm_tc_put(sch,cl);
+ }
+ }
+ DPRINTK("atm_tc_change: new id %x\n",classid);
+ flow = kmalloc(sizeof(struct atm_flow_data)+hdr_len,GFP_KERNEL);
+ DPRINTK("atm_tc_change: flow %p\n",flow);
+ if (!flow) {
+ error = -ENOBUFS;
+ goto err_out;
+ }
+ memset(flow,0,sizeof(*flow));
+ flow->filter_list = NULL;
+ if (!(flow->q = qdisc_create_dflt(sch->dev,&pfifo_qdisc_ops)))
+ flow->q = &noop_qdisc;
+ DPRINTK("atm_tc_change: qdisc %p\n",flow->q);
+ flow->sock = sock;
+ flow->vcc = ATM_SD(sock); /* speedup */
+ flow->vcc->user_back = flow;
+ DPRINTK("atm_tc_change: vcc %p\n",flow->vcc);
+ flow->old_pop = flow->vcc->pop;
+ flow->parent = p;
+ flow->vcc->pop = sch_atm_pop;
+ flow->classid = classid;
+ flow->ref = 1;
+ flow->excess = excess;
+ flow->next = p->link.next;
+ p->link.next = flow;
+ flow->hdr_len = hdr_len;
+ if (hdr) memcpy(flow->hdr,hdr,hdr_len);
+ else {
+ memcpy(flow->hdr,llc_oui,sizeof(llc_oui));
+ ((u16 *) flow->hdr)[3] = htons(ETH_P_IP);
+ }
+ *arg = (unsigned long) flow;
+ return 0;
+err_out:
+ if (excess) atm_tc_put(sch,(unsigned long) excess);
+ sockfd_put(sock);
+ return error;
+}
+
+
+static int atm_tc_delete(struct Qdisc *sch,unsigned long arg)
+{
+ struct atm_qdisc_data *p = PRIV(sch);
+ struct atm_flow_data *flow = (struct atm_flow_data *) arg;
+
+ DPRINTK("atm_tc_delete(sch %p,[qdisc %p],flow %p)\n",sch,p,flow);
+ if (!find_flow(PRIV(sch),flow)) return -EINVAL;
+ if (flow->filter_list || flow == &p->link) return -EBUSY;
+ /*
+ * Reference count must be 2: one for "keepalive" (set at class
+ * creation), and one for the reference held when calling delete.
+ */
+ if (flow->ref < 2) {
+ printk(KERN_ERR "atm_tc_delete: flow->ref == %d\n",flow->ref);
+ return -EINVAL;
+ }
+ if (flow->ref > 2) return -EBUSY; /* catch references via excess, etc.*/
+ atm_tc_put(sch,arg);
+ return 0;
+}
+
+
+static void atm_tc_walk(struct Qdisc *sch,struct qdisc_walker *walker)
+{
+ struct atm_qdisc_data *p = PRIV(sch);
+ struct atm_flow_data *flow;
+
+ DPRINTK("atm_tc_walk(sch %p,[qdisc %p],walker %p)\n",sch,p,walker);
+ if (walker->stop) return;
+ for (flow = p->flows; flow; flow = flow->next) {
+ if (walker->count >= walker->skip)
+ if (walker->fn(sch,(unsigned long) flow,walker) < 0) {
+ walker->stop = 1;
+ break;
+ }
+ walker->count++;
+ }
+}
+
+
+static struct tcf_proto **atm_tc_find_tcf(struct Qdisc *sch,unsigned long cl)
+{
+ struct atm_qdisc_data *p = PRIV(sch);
+ struct atm_flow_data *flow = (struct atm_flow_data *) cl;
+
+ DPRINTK("atm_tc_find_tcf(sch %p,[qdisc %p],flow %p)\n",sch,p,flow);
+ return flow ? &flow->filter_list : &p->link.filter_list;
+}
+
+
+/* --------------------------- Qdisc operations ---------------------------- */
+
+
+static int atm_tc_enqueue(struct sk_buff *skb,struct Qdisc *sch)
+{
+ struct atm_qdisc_data *p = PRIV(sch);
+ struct atm_flow_data *flow = NULL ; /* @@@ */
+ struct tcf_result res;
+ int result;
+ int ret = NET_XMIT_POLICED;
+
+ D2PRINTK("atm_tc_enqueue(skb %p,sch %p,[qdisc %p])\n",skb,sch,p);
+ result = TC_POLICE_OK; /* be nice to gcc */
+ if (TC_H_MAJ(skb->priority) != sch->handle ||
+ !(flow = (struct atm_flow_data *) atm_tc_get(sch,skb->priority)))
+ for (flow = p->flows; flow; flow = flow->next)
+ if (flow->filter_list) {
+ result = tc_classify(skb,flow->filter_list,
+ &res);
+ if (result < 0) continue;
+ flow = (struct atm_flow_data *) res.class;
+ if (!flow) flow = lookup_flow(sch,res.classid);
+ break;
+ }
+ if (!flow) flow = &p->link;
+ else {
+ if (flow->vcc)
+ ATM_SKB(skb)->atm_options = flow->vcc->atm_options;
+ /*@@@ looks good ... but it's not supposed to work :-)*/
+#ifdef CONFIG_NET_CLS_POLICE
+ switch (result) {
+ case TC_POLICE_SHOT:
+ kfree_skb(skb);
+ break;
+ case TC_POLICE_RECLASSIFY:
+ if (flow->excess) flow = flow->excess;
+ else {
+ ATM_SKB(skb)->atm_options |=
+ ATM_ATMOPT_CLP;
+ break;
+ }
+ /* fall through */
+ case TC_POLICE_OK:
+ /* fall through */
+ default:
+ break;
+ }
+#endif
+ }
+ if (
+#ifdef CONFIG_NET_CLS_POLICE
+ result == TC_POLICE_SHOT ||
+#endif
+ (ret = flow->q->enqueue(skb,flow->q)) != 0) {
+ sch->stats.drops++;
+ if (flow) flow->stats.drops++;
+ return ret;
+ }
+ sch->stats.bytes += skb->len;
+ sch->stats.packets++;
+ flow->stats.bytes += skb->len;
+ flow->stats.packets++;
+ /*
+ * Okay, this may seem weird. We pretend we've dropped the packet if
+ * it goes via ATM. The reason for this is that the outer qdisc
+ * expects to be able to q->dequeue the packet later on if we return
+ * success at this place. Also, sch->q.qdisc needs to reflect whether
+ * there is a packet egligible for dequeuing or not. Note that the
+ * statistics of the outer qdisc are necessarily wrong because of all
+ * this. There's currently no correct solution for this.
+ */
+ if (flow == &p->link) {
+ sch->q.qlen++;
+ return 0;
+ }
+ tasklet_schedule(&p->task);
+ return NET_XMIT_BYPASS;
+}
+
+
+/*
+ * Dequeue packets and send them over ATM. Note that we quite deliberately
+ * avoid checking net_device's flow control here, simply because sch_atm
+ * uses its own channels, which have nothing to do with any CLIP/LANE/or
+ * non-ATM interfaces.
+ */
+
+
+static void sch_atm_dequeue(unsigned long data)
+{
+ struct Qdisc *sch = (struct Qdisc *) data;
+ struct atm_qdisc_data *p = PRIV(sch);
+ struct atm_flow_data *flow;
+ struct sk_buff *skb;
+
+ D2PRINTK("sch_atm_dequeue(sch %p,[qdisc %p])\n",sch,p);
+ for (flow = p->link.next; flow; flow = flow->next)
+ /*
+ * If traffic is properly shaped, this won't generate nasty
+ * little bursts. Otherwise, it may ... (but that's okay)
+ */
+ while ((skb = flow->q->dequeue(flow->q))) {
+ if (!atm_may_send(flow->vcc,skb->truesize)) {
+ (void) flow->q->ops->requeue(skb,flow->q);
+ break;
+ }
+ D2PRINTK("atm_tc_deqeueue: sending on class %p\n",flow);
+ /* remove any LL header somebody else has attached */
+ skb_pull(skb,(char *) skb->nh.iph-(char *) skb->data);
+ if (skb_headroom(skb) < flow->hdr_len) {
+ struct sk_buff *new;
+
+ new = skb_realloc_headroom(skb,flow->hdr_len);
+ dev_kfree_skb(skb);
+ if (!new) continue;
+ skb = new;
+ }
+ D2PRINTK("sch_atm_dequeue: ip %p, data %p\n",
+ skb->nh.iph,skb->data);
+ ATM_SKB(skb)->vcc = flow->vcc;
+ memcpy(skb_push(skb,flow->hdr_len),flow->hdr,
+ flow->hdr_len);
+ atomic_add(skb->truesize,&flow->vcc->sk->wmem_alloc);
+ /* atm.atm_options are already set by atm_tc_enqueue */
+ (void) flow->vcc->send(flow->vcc,skb);
+ }
+}
+
+
+static struct sk_buff *atm_tc_dequeue(struct Qdisc *sch)
+{
+ struct atm_qdisc_data *p = PRIV(sch);
+ struct sk_buff *skb;
+
+ D2PRINTK("atm_tc_dequeue(sch %p,[qdisc %p])\n",sch,p);
+ tasklet_schedule(&p->task);
+ skb = p->link.q->dequeue(p->link.q);
+ if (skb) sch->q.qlen--;
+ return skb;
+}
+
+
+static int atm_tc_requeue(struct sk_buff *skb,struct Qdisc *sch)
+{
+ struct atm_qdisc_data *p = PRIV(sch);
+ int ret;
+
+ D2PRINTK("atm_tc_requeue(skb %p,sch %p,[qdisc %p])\n",skb,sch,p);
+ ret = p->link.q->ops->requeue(skb,p->link.q);
+ if (!ret) sch->q.qlen++;
+ else {
+ sch->stats.drops++;
+ p->link.stats.drops++;
+ }
+ return ret;
+}
+
+
+static unsigned int atm_tc_drop(struct Qdisc *sch)
+{
+ struct atm_qdisc_data *p = PRIV(sch);
+ struct atm_flow_data *flow;
+ unsigned int len;
+
+ DPRINTK("atm_tc_drop(sch %p,[qdisc %p])\n",sch,p);
+ for (flow = p->flows; flow; flow = flow->next)
+ if (flow->q->ops->drop && (len = flow->q->ops->drop(flow->q)))
+ return len;
+ return 0;
+}
+
+
+static int atm_tc_init(struct Qdisc *sch,struct rtattr *opt)
+{
+ struct atm_qdisc_data *p = PRIV(sch);
+
+ DPRINTK("atm_tc_init(sch %p,[qdisc %p],opt %p)\n",sch,p,opt);
+ p->flows = &p->link;
+ if(!(p->link.q = qdisc_create_dflt(sch->dev,&pfifo_qdisc_ops)))
+ p->link.q = &noop_qdisc;
+ DPRINTK("atm_tc_init: link (%p) qdisc %p\n",&p->link,p->link.q);
+ p->link.filter_list = NULL;
+ p->link.vcc = NULL;
+ p->link.sock = NULL;
+ p->link.classid = sch->handle;
+ p->link.ref = 1;
+ p->link.next = NULL;
+ tasklet_init(&p->task,sch_atm_dequeue,(unsigned long) sch);
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+
+static void atm_tc_reset(struct Qdisc *sch)
+{
+ struct atm_qdisc_data *p = PRIV(sch);
+ struct atm_flow_data *flow;
+
+ DPRINTK("atm_tc_reset(sch %p,[qdisc %p])\n",sch,p);
+ for (flow = p->flows; flow; flow = flow->next) qdisc_reset(flow->q);
+ sch->q.qlen = 0;
+}
+
+
+static void atm_tc_destroy(struct Qdisc *sch)
+{
+ struct atm_qdisc_data *p = PRIV(sch);
+ struct atm_flow_data *flow;
+
+ DPRINTK("atm_tc_destroy(sch %p,[qdisc %p])\n",sch,p);
+ /* races ? */
+ while ((flow = p->flows)) {
+ destroy_filters(flow);
+ if (flow->ref > 1)
+ printk(KERN_ERR "atm_destroy: %p->ref = %d\n",flow,
+ flow->ref);
+ atm_tc_put(sch,(unsigned long) flow);
+ if (p->flows == flow) {
+ printk(KERN_ERR "atm_destroy: putting flow %p didn't "
+ "kill it\n",flow);
+ p->flows = flow->next; /* brute force */
+ break;
+ }
+ }
+ tasklet_kill(&p->task);
+ MOD_DEC_USE_COUNT;
+}
+
+
+static int atm_tc_dump_class(struct Qdisc *sch, unsigned long cl,
+ struct sk_buff *skb, struct tcmsg *tcm)
+{
+ struct atm_qdisc_data *p = PRIV(sch);
+ struct atm_flow_data *flow = (struct atm_flow_data *) cl;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+
+ DPRINTK("atm_tc_dump_class(sch %p,[qdisc %p],flow %p,skb %p,tcm %p)\n",
+ sch,p,flow,skb,tcm);
+ if (!find_flow(p,flow)) return -EINVAL;
+ tcm->tcm_handle = flow->classid;
+ rta = (struct rtattr *) b;
+ RTA_PUT(skb,TCA_OPTIONS,0,NULL);
+ RTA_PUT(skb,TCA_ATM_HDR,flow->hdr_len,flow->hdr);
+ if (flow->vcc) {
+ struct sockaddr_atmpvc pvc;
+ int state;
+
+ pvc.sap_family = AF_ATMPVC;
+ pvc.sap_addr.itf = flow->vcc->dev ? flow->vcc->dev->number : -1;
+ pvc.sap_addr.vpi = flow->vcc->vpi;
+ pvc.sap_addr.vci = flow->vcc->vci;
+ RTA_PUT(skb,TCA_ATM_ADDR,sizeof(pvc),&pvc);
+ state = ATM_VF2VS(flow->vcc->flags);
+ RTA_PUT(skb,TCA_ATM_STATE,sizeof(state),&state);
+ }
+ if (flow->excess)
+ RTA_PUT(skb,TCA_ATM_EXCESS,sizeof(u32),&flow->classid);
+ else {
+ static u32 zero = 0;
+
+ RTA_PUT(skb,TCA_ATM_EXCESS,sizeof(zero),&zero);
+ }
+ rta->rta_len = skb->tail-b;
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb,b-skb->data);
+ return -1;
+}
+
+static int atm_tc_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+ return 0;
+}
+
+static struct Qdisc_class_ops atm_class_ops =
+{
+ atm_tc_graft, /* graft */
+ atm_tc_leaf, /* leaf */
+ atm_tc_get, /* get */
+ atm_tc_put, /* put */
+ atm_tc_change, /* change */
+ atm_tc_delete, /* delete */
+ atm_tc_walk, /* walk */
+
+ atm_tc_find_tcf, /* tcf_chain */
+ atm_tc_bind_filter, /* bind_tcf */
+ atm_tc_put, /* unbind_tcf */
+
+ atm_tc_dump_class, /* dump */
+};
+
+struct Qdisc_ops atm_qdisc_ops =
+{
+ NULL, /* next */
+ &atm_class_ops, /* cl_ops */
+ "atm",
+ sizeof(struct atm_qdisc_data),
+
+ atm_tc_enqueue, /* enqueue */
+ atm_tc_dequeue, /* dequeue */
+ atm_tc_requeue, /* requeue */
+ atm_tc_drop, /* drop */
+
+ atm_tc_init, /* init */
+ atm_tc_reset, /* reset */
+ atm_tc_destroy, /* destroy */
+ NULL, /* change */
+
+ atm_tc_dump /* dump */
+};
+
+
+#ifdef MODULE
+int init_module(void)
+{
+ return register_qdisc(&atm_qdisc_ops);
+}
+
+
+void cleanup_module(void)
+{
+ unregister_qdisc(&atm_qdisc_ops);
+}
+#endif
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_cbq.c b/uClinux-2.4.31-uc0/net/sched/sch_cbq.c
new file mode 100644
index 0000000..4b0da1b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_cbq.c
@@ -0,0 +1,2121 @@
+/*
+ * net/sched/sch_cbq.c Class-Based Queueing discipline.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+
+/* Class-Based Queueing (CBQ) algorithm.
+ =======================================
+
+ Sources: [1] Sally Floyd and Van Jacobson, "Link-sharing and Resource
+ Management Models for Packet Networks",
+ IEEE/ACM Transactions on Networking, Vol.3, No.4, 1995
+
+ [2] Sally Floyd, "Notes on CBQ and Guaranted Service", 1995
+
+ [3] Sally Floyd, "Notes on Class-Based Queueing: Setting
+ Parameters", 1996
+
+ [4] Sally Floyd and Michael Speer, "Experimental Results
+ for Class-Based Queueing", 1998, not published.
+
+ -----------------------------------------------------------------------
+
+ Algorithm skeleton was taken from NS simulator cbq.cc.
+ If someone wants to check this code against the LBL version,
+ he should take into account that ONLY the skeleton was borrowed,
+ the implementation is different. Particularly:
+
+ --- The WRR algorithm is different. Our version looks more
+ reasonable (I hope) and works when quanta are allowed to be
+ less than MTU, which is always the case when real time classes
+ have small rates. Note, that the statement of [3] is
+ incomplete, delay may actually be estimated even if class
+ per-round allotment is less than MTU. Namely, if per-round
+ allotment is W*r_i, and r_1+...+r_k = r < 1
+
+ delay_i <= ([MTU/(W*r_i)]*W*r + W*r + k*MTU)/B
+
+ In the worst case we have IntServ estimate with D = W*r+k*MTU
+ and C = MTU*r. The proof (if correct at all) is trivial.
+
+
+ --- It seems that cbq-2.0 is not very accurate. At least, I cannot
+ interpret some places, which look like wrong translations
+ from NS. Anyone is advised to find these differences
+ and explain to me, why I am wrong 8).
+
+ --- Linux has no EOI event, so that we cannot estimate true class
+ idle time. Workaround is to consider the next dequeue event
+ as sign that previous packet is finished. This is wrong because of
+ internal device queueing, but on a permanently loaded link it is true.
+ Moreover, combined with clock integrator, this scheme looks
+ very close to an ideal solution. */
+
+struct cbq_sched_data;
+
+
+struct cbq_class
+{
+ struct cbq_class *next; /* hash table link */
+ struct cbq_class *next_alive; /* next class with backlog in this priority band */
+
+/* Parameters */
+ u32 classid;
+ unsigned char priority; /* class priority */
+ unsigned char priority2; /* priority to be used after overlimit */
+ unsigned char ewma_log; /* time constant for idle time calculation */
+ unsigned char ovl_strategy;
+#ifdef CONFIG_NET_CLS_POLICE
+ unsigned char police;
+#endif
+
+ u32 defmap;
+
+ /* Link-sharing scheduler parameters */
+ long maxidle; /* Class paramters: see below. */
+ long offtime;
+ long minidle;
+ u32 avpkt;
+ struct qdisc_rate_table *R_tab;
+
+ /* Overlimit strategy parameters */
+ void (*overlimit)(struct cbq_class *cl);
+ long penalty;
+
+ /* General scheduler (WRR) parameters */
+ long allot;
+ long quantum; /* Allotment per WRR round */
+ long weight; /* Relative allotment: see below */
+
+ struct Qdisc *qdisc; /* Ptr to CBQ discipline */
+ struct cbq_class *split; /* Ptr to split node */
+ struct cbq_class *share; /* Ptr to LS parent in the class tree */
+ struct cbq_class *tparent; /* Ptr to tree parent in the class tree */
+ struct cbq_class *borrow; /* NULL if class is bandwidth limited;
+ parent otherwise */
+ struct cbq_class *sibling; /* Sibling chain */
+ struct cbq_class *children; /* Pointer to children chain */
+
+ struct Qdisc *q; /* Elementary queueing discipline */
+
+
+/* Variables */
+ unsigned char cpriority; /* Effective priority */
+ unsigned char delayed;
+ unsigned char level; /* level of the class in hierarchy:
+ 0 for leaf classes, and maximal
+ level of children + 1 for nodes.
+ */
+
+ psched_time_t last; /* Last end of service */
+ psched_time_t undertime;
+ long avgidle;
+ long deficit; /* Saved deficit for WRR */
+ unsigned long penalized;
+ struct tc_stats stats;
+ struct tc_cbq_xstats xstats;
+
+ struct tcf_proto *filter_list;
+
+ int refcnt;
+ int filters;
+
+ struct cbq_class *defaults[TC_PRIO_MAX+1];
+};
+
+struct cbq_sched_data
+{
+ struct cbq_class *classes[16]; /* Hash table of all classes */
+ int nclasses[TC_CBQ_MAXPRIO+1];
+ unsigned quanta[TC_CBQ_MAXPRIO+1];
+
+ struct cbq_class link;
+
+ unsigned activemask;
+ struct cbq_class *active[TC_CBQ_MAXPRIO+1]; /* List of all classes
+ with backlog */
+
+#ifdef CONFIG_NET_CLS_POLICE
+ struct cbq_class *rx_class;
+#endif
+ struct cbq_class *tx_class;
+ struct cbq_class *tx_borrowed;
+ int tx_len;
+ psched_time_t now; /* Cached timestamp */
+ psched_time_t now_rt; /* Cached real time */
+ unsigned pmask;
+
+ struct timer_list delay_timer;
+ struct timer_list wd_timer; /* Watchdog timer,
+ started when CBQ has
+ backlog, but cannot
+ transmit just now */
+ long wd_expires;
+ int toplevel;
+ u32 hgenerator;
+};
+
+
+#define L2T(cl,len) ((cl)->R_tab->data[(len)>>(cl)->R_tab->rate.cell_log])
+
+
+static __inline__ unsigned cbq_hash(u32 h)
+{
+ h ^= h>>8;
+ h ^= h>>4;
+ return h&0xF;
+}
+
+static __inline__ struct cbq_class *
+cbq_class_lookup(struct cbq_sched_data *q, u32 classid)
+{
+ struct cbq_class *cl;
+
+ for (cl = q->classes[cbq_hash(classid)]; cl; cl = cl->next)
+ if (cl->classid == classid)
+ return cl;
+ return NULL;
+}
+
+#ifdef CONFIG_NET_CLS_POLICE
+
+static struct cbq_class *
+cbq_reclassify(struct sk_buff *skb, struct cbq_class *this)
+{
+ struct cbq_class *cl, *new;
+
+ for (cl = this->tparent; cl; cl = cl->tparent)
+ if ((new = cl->defaults[TC_PRIO_BESTEFFORT]) != NULL && new != this)
+ return new;
+
+ return NULL;
+}
+
+#endif
+
+/* Classify packet. The procedure is pretty complicated, but
+ it allows us to combine link sharing and priority scheduling
+ transparently.
+
+ Namely, you can put link sharing rules (f.e. route based) at root of CBQ,
+ so that it resolves to split nodes. Then packets are classified
+ by logical priority, or a more specific classifier may be attached
+ to the split node.
+ */
+
+static struct cbq_class *
+cbq_classify(struct sk_buff *skb, struct Qdisc *sch)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data*)sch->data;
+ struct cbq_class *head = &q->link;
+ struct cbq_class **defmap;
+ struct cbq_class *cl = NULL;
+ u32 prio = skb->priority;
+ struct tcf_result res;
+
+ /*
+ * Step 1. If skb->priority points to one of our classes, use it.
+ */
+ if (TC_H_MAJ(prio^sch->handle) == 0 &&
+ (cl = cbq_class_lookup(q, prio)) != NULL)
+ return cl;
+
+ for (;;) {
+ int result = 0;
+
+ defmap = head->defaults;
+
+ /*
+ * Step 2+n. Apply classifier.
+ */
+ if (!head->filter_list || (result = tc_classify(skb, head->filter_list, &res)) < 0)
+ goto fallback;
+
+ if ((cl = (void*)res.class) == NULL) {
+ if (TC_H_MAJ(res.classid))
+ cl = cbq_class_lookup(q, res.classid);
+ else if ((cl = defmap[res.classid&TC_PRIO_MAX]) == NULL)
+ cl = defmap[TC_PRIO_BESTEFFORT];
+
+ if (cl == NULL || cl->level >= head->level)
+ goto fallback;
+ }
+
+#ifdef CONFIG_NET_CLS_POLICE
+ switch (result) {
+ case TC_POLICE_RECLASSIFY:
+ return cbq_reclassify(skb, cl);
+ case TC_POLICE_SHOT:
+ return NULL;
+ default:
+ break;
+ }
+#endif
+ if (cl->level == 0)
+ return cl;
+
+ /*
+ * Step 3+n. If classifier selected a link sharing class,
+ * apply agency specific classifier.
+ * Repeat this procdure until we hit a leaf node.
+ */
+ head = cl;
+ }
+
+fallback:
+ cl = head;
+
+ /*
+ * Step 4. No success...
+ */
+ if (TC_H_MAJ(prio) == 0 &&
+ !(cl = head->defaults[prio&TC_PRIO_MAX]) &&
+ !(cl = head->defaults[TC_PRIO_BESTEFFORT]))
+ return head;
+
+ return cl;
+}
+
+/*
+ A packet has just been enqueued on the empty class.
+ cbq_activate_class adds it to the tail of active class list
+ of its priority band.
+ */
+
+static __inline__ void cbq_activate_class(struct cbq_class *cl)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data*)cl->qdisc->data;
+ int prio = cl->cpriority;
+ struct cbq_class *cl_tail;
+
+ cl_tail = q->active[prio];
+ q->active[prio] = cl;
+
+ if (cl_tail != NULL) {
+ cl->next_alive = cl_tail->next_alive;
+ cl_tail->next_alive = cl;
+ } else {
+ cl->next_alive = cl;
+ q->activemask |= (1<<prio);
+ }
+}
+
+/*
+ Unlink class from active chain.
+ Note that this same procedure is done directly in cbq_dequeue*
+ during round-robin procedure.
+ */
+
+static void cbq_deactivate_class(struct cbq_class *this)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data*)this->qdisc->data;
+ int prio = this->cpriority;
+ struct cbq_class *cl;
+ struct cbq_class *cl_prev = q->active[prio];
+
+ do {
+ cl = cl_prev->next_alive;
+ if (cl == this) {
+ cl_prev->next_alive = cl->next_alive;
+ cl->next_alive = NULL;
+
+ if (cl == q->active[prio]) {
+ q->active[prio] = cl_prev;
+ if (cl == q->active[prio]) {
+ q->active[prio] = NULL;
+ q->activemask &= ~(1<<prio);
+ return;
+ }
+ }
+
+ cl = cl_prev->next_alive;
+ return;
+ }
+ } while ((cl_prev = cl) != q->active[prio]);
+}
+
+static void
+cbq_mark_toplevel(struct cbq_sched_data *q, struct cbq_class *cl)
+{
+ int toplevel = q->toplevel;
+
+ if (toplevel > cl->level && !(cl->q->flags&TCQ_F_THROTTLED)) {
+ psched_time_t now;
+ psched_tdiff_t incr;
+
+ PSCHED_GET_TIME(now);
+ incr = PSCHED_TDIFF(now, q->now_rt);
+ PSCHED_TADD2(q->now, incr, now);
+
+ do {
+ if (PSCHED_TLESS(cl->undertime, now)) {
+ q->toplevel = cl->level;
+ return;
+ }
+ } while ((cl=cl->borrow) != NULL && toplevel > cl->level);
+ }
+}
+
+static int
+cbq_enqueue(struct sk_buff *skb, struct Qdisc *sch)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ struct cbq_class *cl = cbq_classify(skb, sch);
+ int len = skb->len;
+ int ret = NET_XMIT_POLICED;
+
+#ifdef CONFIG_NET_CLS_POLICE
+ q->rx_class = cl;
+#endif
+ if (cl) {
+#ifdef CONFIG_NET_CLS_POLICE
+ cl->q->__parent = sch;
+#endif
+ if ((ret = cl->q->enqueue(skb, cl->q)) == 0) {
+ sch->q.qlen++;
+ sch->stats.packets++;
+ sch->stats.bytes+=len;
+ cbq_mark_toplevel(q, cl);
+ if (!cl->next_alive)
+ cbq_activate_class(cl);
+ return 0;
+ }
+ }
+
+ sch->stats.drops++;
+ if (cl == NULL)
+ kfree_skb(skb);
+ else {
+ cbq_mark_toplevel(q, cl);
+ cl->stats.drops++;
+ }
+ return ret;
+}
+
+static int
+cbq_requeue(struct sk_buff *skb, struct Qdisc *sch)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ struct cbq_class *cl;
+ int ret;
+
+ if ((cl = q->tx_class) == NULL) {
+ kfree_skb(skb);
+ sch->stats.drops++;
+ return NET_XMIT_CN;
+ }
+ q->tx_class = NULL;
+
+ cbq_mark_toplevel(q, cl);
+
+#ifdef CONFIG_NET_CLS_POLICE
+ q->rx_class = cl;
+ cl->q->__parent = sch;
+#endif
+ if ((ret = cl->q->ops->requeue(skb, cl->q)) == 0) {
+ sch->q.qlen++;
+ if (!cl->next_alive)
+ cbq_activate_class(cl);
+ return 0;
+ }
+ sch->stats.drops++;
+ cl->stats.drops++;
+ return ret;
+}
+
+/* Overlimit actions */
+
+/* TC_CBQ_OVL_CLASSIC: (default) penalize leaf class by adding offtime */
+
+static void cbq_ovl_classic(struct cbq_class *cl)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)cl->qdisc->data;
+ psched_tdiff_t delay = PSCHED_TDIFF(cl->undertime, q->now);
+
+ if (!cl->delayed) {
+ delay += cl->offtime;
+
+ /*
+ Class goes to sleep, so that it will have no
+ chance to work avgidle. Let's forgive it 8)
+
+ BTW cbq-2.0 has a crap in this
+ place, apparently they forgot to shift it by cl->ewma_log.
+ */
+ if (cl->avgidle < 0)
+ delay -= (-cl->avgidle) - ((-cl->avgidle) >> cl->ewma_log);
+ if (cl->avgidle < cl->minidle)
+ cl->avgidle = cl->minidle;
+ if (delay <= 0)
+ delay = 1;
+ PSCHED_TADD2(q->now, delay, cl->undertime);
+
+ cl->xstats.overactions++;
+ cl->delayed = 1;
+ }
+ if (q->wd_expires == 0 || q->wd_expires > delay)
+ q->wd_expires = delay;
+
+ /* Dirty work! We must schedule wakeups based on
+ real available rate, rather than leaf rate,
+ which may be tiny (even zero).
+ */
+ if (q->toplevel == TC_CBQ_MAXLEVEL) {
+ struct cbq_class *b;
+ psched_tdiff_t base_delay = q->wd_expires;
+
+ for (b = cl->borrow; b; b = b->borrow) {
+ delay = PSCHED_TDIFF(b->undertime, q->now);
+ if (delay < base_delay) {
+ if (delay <= 0)
+ delay = 1;
+ base_delay = delay;
+ }
+ }
+
+ q->wd_expires = base_delay;
+ }
+}
+
+/* TC_CBQ_OVL_RCLASSIC: penalize by offtime classes in hierarchy, when
+ they go overlimit
+ */
+
+static void cbq_ovl_rclassic(struct cbq_class *cl)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)cl->qdisc->data;
+ struct cbq_class *this = cl;
+
+ do {
+ if (cl->level > q->toplevel) {
+ cl = NULL;
+ break;
+ }
+ } while ((cl = cl->borrow) != NULL);
+
+ if (cl == NULL)
+ cl = this;
+ cbq_ovl_classic(cl);
+}
+
+/* TC_CBQ_OVL_DELAY: delay until it will go to underlimit */
+
+static void cbq_ovl_delay(struct cbq_class *cl)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)cl->qdisc->data;
+ psched_tdiff_t delay = PSCHED_TDIFF(cl->undertime, q->now);
+
+ if (!cl->delayed) {
+ unsigned long sched = jiffies;
+
+ delay += cl->offtime;
+ if (cl->avgidle < 0)
+ delay -= (-cl->avgidle) - ((-cl->avgidle) >> cl->ewma_log);
+ if (cl->avgidle < cl->minidle)
+ cl->avgidle = cl->minidle;
+ PSCHED_TADD2(q->now, delay, cl->undertime);
+
+ if (delay > 0) {
+ sched += PSCHED_US2JIFFIE(delay) + cl->penalty;
+ cl->penalized = sched;
+ cl->cpriority = TC_CBQ_MAXPRIO;
+ q->pmask |= (1<<TC_CBQ_MAXPRIO);
+ if (del_timer(&q->delay_timer) &&
+ (long)(q->delay_timer.expires - sched) > 0)
+ q->delay_timer.expires = sched;
+ add_timer(&q->delay_timer);
+ cl->delayed = 1;
+ cl->xstats.overactions++;
+ return;
+ }
+ delay = 1;
+ }
+ if (q->wd_expires == 0 || q->wd_expires > delay)
+ q->wd_expires = delay;
+}
+
+/* TC_CBQ_OVL_LOWPRIO: penalize class by lowering its priority band */
+
+static void cbq_ovl_lowprio(struct cbq_class *cl)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data*)cl->qdisc->data;
+
+ cl->penalized = jiffies + cl->penalty;
+
+ if (cl->cpriority != cl->priority2) {
+ cl->cpriority = cl->priority2;
+ q->pmask |= (1<<cl->cpriority);
+ cl->xstats.overactions++;
+ }
+ cbq_ovl_classic(cl);
+}
+
+/* TC_CBQ_OVL_DROP: penalize class by dropping */
+
+static void cbq_ovl_drop(struct cbq_class *cl)
+{
+ if (cl->q->ops->drop)
+ if (cl->q->ops->drop(cl->q))
+ cl->qdisc->q.qlen--;
+ cl->xstats.overactions++;
+ cbq_ovl_classic(cl);
+}
+
+static void cbq_watchdog(unsigned long arg)
+{
+ struct Qdisc *sch = (struct Qdisc*)arg;
+
+ sch->flags &= ~TCQ_F_THROTTLED;
+ netif_schedule(sch->dev);
+}
+
+static unsigned long cbq_undelay_prio(struct cbq_sched_data *q, int prio)
+{
+ struct cbq_class *cl;
+ struct cbq_class *cl_prev = q->active[prio];
+ unsigned long now = jiffies;
+ unsigned long sched = now;
+
+ if (cl_prev == NULL)
+ return now;
+
+ do {
+ cl = cl_prev->next_alive;
+ if ((long)(now - cl->penalized) > 0) {
+ cl_prev->next_alive = cl->next_alive;
+ cl->next_alive = NULL;
+ cl->cpriority = cl->priority;
+ cl->delayed = 0;
+ cbq_activate_class(cl);
+
+ if (cl == q->active[prio]) {
+ q->active[prio] = cl_prev;
+ if (cl == q->active[prio]) {
+ q->active[prio] = NULL;
+ return 0;
+ }
+ }
+
+ cl = cl_prev->next_alive;
+ } else if ((long)(sched - cl->penalized) > 0)
+ sched = cl->penalized;
+ } while ((cl_prev = cl) != q->active[prio]);
+
+ return (long)(sched - now);
+}
+
+static void cbq_undelay(unsigned long arg)
+{
+ struct Qdisc *sch = (struct Qdisc*)arg;
+ struct cbq_sched_data *q = (struct cbq_sched_data*)sch->data;
+ long delay = 0;
+ unsigned pmask;
+
+ pmask = q->pmask;
+ q->pmask = 0;
+
+ while (pmask) {
+ int prio = ffz(~pmask);
+ long tmp;
+
+ pmask &= ~(1<<prio);
+
+ tmp = cbq_undelay_prio(q, prio);
+ if (tmp > 0) {
+ q->pmask |= 1<<prio;
+ if (tmp < delay || delay == 0)
+ delay = tmp;
+ }
+ }
+
+ if (delay) {
+ q->delay_timer.expires = jiffies + delay;
+ add_timer(&q->delay_timer);
+ }
+
+ sch->flags &= ~TCQ_F_THROTTLED;
+ netif_schedule(sch->dev);
+}
+
+
+#ifdef CONFIG_NET_CLS_POLICE
+
+static int cbq_reshape_fail(struct sk_buff *skb, struct Qdisc *child)
+{
+ int len = skb->len;
+ struct Qdisc *sch = child->__parent;
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ struct cbq_class *cl = q->rx_class;
+
+ q->rx_class = NULL;
+
+ if (cl && (cl = cbq_reclassify(skb, cl)) != NULL) {
+
+ cbq_mark_toplevel(q, cl);
+
+ q->rx_class = cl;
+ cl->q->__parent = sch;
+
+ if (cl->q->enqueue(skb, cl->q) == 0) {
+ sch->q.qlen++;
+ sch->stats.packets++;
+ sch->stats.bytes+=len;
+ if (!cl->next_alive)
+ cbq_activate_class(cl);
+ return 0;
+ }
+ sch->stats.drops++;
+ return 0;
+ }
+
+ sch->stats.drops++;
+ return -1;
+}
+#endif
+
+/*
+ It is mission critical procedure.
+
+ We "regenerate" toplevel cutoff, if transmitting class
+ has backlog and it is not regulated. It is not part of
+ original CBQ description, but looks more reasonable.
+ Probably, it is wrong. This question needs further investigation.
+*/
+
+static __inline__ void
+cbq_update_toplevel(struct cbq_sched_data *q, struct cbq_class *cl,
+ struct cbq_class *borrowed)
+{
+ if (cl && q->toplevel >= borrowed->level) {
+ if (cl->q->q.qlen > 1) {
+ do {
+ if (PSCHED_IS_PASTPERFECT(borrowed->undertime)) {
+ q->toplevel = borrowed->level;
+ return;
+ }
+ } while ((borrowed=borrowed->borrow) != NULL);
+ }
+#if 0
+ /* It is not necessary now. Uncommenting it
+ will save CPU cycles, but decrease fairness.
+ */
+ q->toplevel = TC_CBQ_MAXLEVEL;
+#endif
+ }
+}
+
+static void
+cbq_update(struct cbq_sched_data *q)
+{
+ struct cbq_class *this = q->tx_class;
+ struct cbq_class *cl = this;
+ int len = q->tx_len;
+
+ q->tx_class = NULL;
+
+ for ( ; cl; cl = cl->share) {
+ long avgidle = cl->avgidle;
+ long idle;
+
+ cl->stats.packets++;
+ cl->stats.bytes += len;
+
+ /*
+ (now - last) is total time between packet right edges.
+ (last_pktlen/rate) is "virtual" busy time, so that
+
+ idle = (now - last) - last_pktlen/rate
+ */
+
+ idle = PSCHED_TDIFF(q->now, cl->last);
+ if ((unsigned long)idle > 128*1024*1024) {
+ avgidle = cl->maxidle;
+ } else {
+ idle -= L2T(cl, len);
+
+ /* true_avgidle := (1-W)*true_avgidle + W*idle,
+ where W=2^{-ewma_log}. But cl->avgidle is scaled:
+ cl->avgidle == true_avgidle/W,
+ hence:
+ */
+ avgidle += idle - (avgidle>>cl->ewma_log);
+ }
+
+ if (avgidle <= 0) {
+ /* Overlimit or at-limit */
+
+ if (avgidle < cl->minidle)
+ avgidle = cl->minidle;
+
+ cl->avgidle = avgidle;
+
+ /* Calculate expected time, when this class
+ will be allowed to send.
+ It will occur, when:
+ (1-W)*true_avgidle + W*delay = 0, i.e.
+ idle = (1/W - 1)*(-true_avgidle)
+ or
+ idle = (1 - W)*(-cl->avgidle);
+ */
+ idle = (-avgidle) - ((-avgidle) >> cl->ewma_log);
+
+ /*
+ That is not all.
+ To maintain the rate allocated to the class,
+ we add to undertime virtual clock,
+ necesary to complete transmitted packet.
+ (len/phys_bandwidth has been already passed
+ to the moment of cbq_update)
+ */
+
+ idle -= L2T(&q->link, len);
+ idle += L2T(cl, len);
+
+ PSCHED_AUDIT_TDIFF(idle);
+
+ PSCHED_TADD2(q->now, idle, cl->undertime);
+ } else {
+ /* Underlimit */
+
+ PSCHED_SET_PASTPERFECT(cl->undertime);
+ if (avgidle > cl->maxidle)
+ cl->avgidle = cl->maxidle;
+ else
+ cl->avgidle = avgidle;
+ }
+ cl->last = q->now;
+ }
+
+ cbq_update_toplevel(q, this, q->tx_borrowed);
+}
+
+static __inline__ struct cbq_class *
+cbq_under_limit(struct cbq_class *cl)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data*)cl->qdisc->data;
+ struct cbq_class *this_cl = cl;
+
+ if (cl->tparent == NULL)
+ return cl;
+
+ if (PSCHED_IS_PASTPERFECT(cl->undertime) ||
+ !PSCHED_TLESS(q->now, cl->undertime)) {
+ cl->delayed = 0;
+ return cl;
+ }
+
+ do {
+ /* It is very suspicious place. Now overlimit
+ action is generated for not bounded classes
+ only if link is completely congested.
+ Though it is in agree with ancestor-only paradigm,
+ it looks very stupid. Particularly,
+ it means that this chunk of code will either
+ never be called or result in strong amplification
+ of burstiness. Dangerous, silly, and, however,
+ no another solution exists.
+ */
+ if ((cl = cl->borrow) == NULL) {
+ this_cl->stats.overlimits++;
+ this_cl->overlimit(this_cl);
+ return NULL;
+ }
+ if (cl->level > q->toplevel)
+ return NULL;
+ } while (!PSCHED_IS_PASTPERFECT(cl->undertime) &&
+ PSCHED_TLESS(q->now, cl->undertime));
+
+ cl->delayed = 0;
+ return cl;
+}
+
+static __inline__ struct sk_buff *
+cbq_dequeue_prio(struct Qdisc *sch, int prio)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ struct cbq_class *cl_tail, *cl_prev, *cl;
+ struct sk_buff *skb;
+ int deficit;
+
+ cl_tail = cl_prev = q->active[prio];
+ cl = cl_prev->next_alive;
+
+ do {
+ deficit = 0;
+
+ /* Start round */
+ do {
+ struct cbq_class *borrow = cl;
+
+ if (cl->q->q.qlen &&
+ (borrow = cbq_under_limit(cl)) == NULL)
+ goto skip_class;
+
+ if (cl->deficit <= 0) {
+ /* Class exhausted its allotment per
+ this round. Switch to the next one.
+ */
+ deficit = 1;
+ cl->deficit += cl->quantum;
+ goto next_class;
+ }
+
+ skb = cl->q->dequeue(cl->q);
+
+ /* Class did not give us any skb :-(
+ It could occur even if cl->q->q.qlen != 0
+ f.e. if cl->q == "tbf"
+ */
+ if (skb == NULL)
+ goto skip_class;
+
+ cl->deficit -= skb->len;
+ q->tx_class = cl;
+ q->tx_borrowed = borrow;
+ if (borrow != cl) {
+#ifndef CBQ_XSTATS_BORROWS_BYTES
+ borrow->xstats.borrows++;
+ cl->xstats.borrows++;
+#else
+ borrow->xstats.borrows += skb->len;
+ cl->xstats.borrows += skb->len;
+#endif
+ }
+ q->tx_len = skb->len;
+
+ if (cl->deficit <= 0) {
+ q->active[prio] = cl;
+ cl = cl->next_alive;
+ cl->deficit += cl->quantum;
+ }
+ return skb;
+
+skip_class:
+ if (cl->q->q.qlen == 0 || prio != cl->cpriority) {
+ /* Class is empty or penalized.
+ Unlink it from active chain.
+ */
+ cl_prev->next_alive = cl->next_alive;
+ cl->next_alive = NULL;
+
+ /* Did cl_tail point to it? */
+ if (cl == cl_tail) {
+ /* Repair it! */
+ cl_tail = cl_prev;
+
+ /* Was it the last class in this band? */
+ if (cl == cl_tail) {
+ /* Kill the band! */
+ q->active[prio] = NULL;
+ q->activemask &= ~(1<<prio);
+ if (cl->q->q.qlen)
+ cbq_activate_class(cl);
+ return NULL;
+ }
+
+ q->active[prio] = cl_tail;
+ }
+ if (cl->q->q.qlen)
+ cbq_activate_class(cl);
+
+ cl = cl_prev;
+ }
+
+next_class:
+ cl_prev = cl;
+ cl = cl->next_alive;
+ } while (cl_prev != cl_tail);
+ } while (deficit);
+
+ q->active[prio] = cl_prev;
+
+ return NULL;
+}
+
+static __inline__ struct sk_buff *
+cbq_dequeue_1(struct Qdisc *sch)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ struct sk_buff *skb;
+ unsigned activemask;
+
+ activemask = q->activemask&0xFF;
+ while (activemask) {
+ int prio = ffz(~activemask);
+ activemask &= ~(1<<prio);
+ skb = cbq_dequeue_prio(sch, prio);
+ if (skb)
+ return skb;
+ }
+ return NULL;
+}
+
+static struct sk_buff *
+cbq_dequeue(struct Qdisc *sch)
+{
+ struct sk_buff *skb;
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ psched_time_t now;
+ psched_tdiff_t incr;
+
+ PSCHED_GET_TIME(now);
+ incr = PSCHED_TDIFF(now, q->now_rt);
+
+ if (q->tx_class) {
+ psched_tdiff_t incr2;
+ /* Time integrator. We calculate EOS time
+ by adding expected packet transmittion time.
+ If real time is greater, we warp artificial clock,
+ so that:
+
+ cbq_time = max(real_time, work);
+ */
+ incr2 = L2T(&q->link, q->tx_len);
+ PSCHED_TADD(q->now, incr2);
+ cbq_update(q);
+ if ((incr -= incr2) < 0)
+ incr = 0;
+ }
+ PSCHED_TADD(q->now, incr);
+ q->now_rt = now;
+
+ for (;;) {
+ q->wd_expires = 0;
+
+ skb = cbq_dequeue_1(sch);
+ if (skb) {
+ sch->q.qlen--;
+ sch->flags &= ~TCQ_F_THROTTLED;
+ return skb;
+ }
+
+ /* All the classes are overlimit.
+
+ It is possible, if:
+
+ 1. Scheduler is empty.
+ 2. Toplevel cutoff inhibited borrowing.
+ 3. Root class is overlimit.
+
+ Reset 2d and 3d conditions and retry.
+
+ Note, that NS and cbq-2.0 are buggy, peeking
+ an arbitrary class is appropriate for ancestor-only
+ sharing, but not for toplevel algorithm.
+
+ Our version is better, but slower, because it requires
+ two passes, but it is unavoidable with top-level sharing.
+ */
+
+ if (q->toplevel == TC_CBQ_MAXLEVEL &&
+ PSCHED_IS_PASTPERFECT(q->link.undertime))
+ break;
+
+ q->toplevel = TC_CBQ_MAXLEVEL;
+ PSCHED_SET_PASTPERFECT(q->link.undertime);
+ }
+
+ /* No packets in scheduler or nobody wants to give them to us :-(
+ Sigh... start watchdog timer in the last case. */
+
+ if (sch->q.qlen) {
+ sch->stats.overlimits++;
+ if (q->wd_expires) {
+ long delay = PSCHED_US2JIFFIE(q->wd_expires);
+ if (delay <= 0)
+ delay = 1;
+ mod_timer(&q->wd_timer, jiffies + delay);
+ sch->flags |= TCQ_F_THROTTLED;
+ }
+ }
+ return NULL;
+}
+
+/* CBQ class maintanance routines */
+
+static void cbq_adjust_levels(struct cbq_class *this)
+{
+ if (this == NULL)
+ return;
+
+ do {
+ int level = 0;
+ struct cbq_class *cl;
+
+ if ((cl = this->children) != NULL) {
+ do {
+ if (cl->level > level)
+ level = cl->level;
+ } while ((cl = cl->sibling) != this->children);
+ }
+ this->level = level+1;
+ } while ((this = this->tparent) != NULL);
+}
+
+static void cbq_normalize_quanta(struct cbq_sched_data *q, int prio)
+{
+ struct cbq_class *cl;
+ unsigned h;
+
+ if (q->quanta[prio] == 0)
+ return;
+
+ for (h=0; h<16; h++) {
+ for (cl = q->classes[h]; cl; cl = cl->next) {
+ /* BUGGGG... Beware! This expression suffer of
+ arithmetic overflows!
+ */
+ if (cl->priority == prio) {
+ cl->quantum = (cl->weight*cl->allot*q->nclasses[prio])/
+ q->quanta[prio];
+ }
+ if (cl->quantum <= 0 || cl->quantum>32*cl->qdisc->dev->mtu) {
+ printk(KERN_WARNING "CBQ: class %08x has bad quantum==%ld, repaired.\n", cl->classid, cl->quantum);
+ cl->quantum = cl->qdisc->dev->mtu/2 + 1;
+ }
+ }
+ }
+}
+
+static void cbq_sync_defmap(struct cbq_class *cl)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data*)cl->qdisc->data;
+ struct cbq_class *split = cl->split;
+ unsigned h;
+ int i;
+
+ if (split == NULL)
+ return;
+
+ for (i=0; i<=TC_PRIO_MAX; i++) {
+ if (split->defaults[i] == cl && !(cl->defmap&(1<<i)))
+ split->defaults[i] = NULL;
+ }
+
+ for (i=0; i<=TC_PRIO_MAX; i++) {
+ int level = split->level;
+
+ if (split->defaults[i])
+ continue;
+
+ for (h=0; h<16; h++) {
+ struct cbq_class *c;
+
+ for (c = q->classes[h]; c; c = c->next) {
+ if (c->split == split && c->level < level &&
+ c->defmap&(1<<i)) {
+ split->defaults[i] = c;
+ level = c->level;
+ }
+ }
+ }
+ }
+}
+
+static void cbq_change_defmap(struct cbq_class *cl, u32 splitid, u32 def, u32 mask)
+{
+ struct cbq_class *split = NULL;
+
+ if (splitid == 0) {
+ if ((split = cl->split) == NULL)
+ return;
+ splitid = split->classid;
+ }
+
+ if (split == NULL || split->classid != splitid) {
+ for (split = cl->tparent; split; split = split->tparent)
+ if (split->classid == splitid)
+ break;
+ }
+
+ if (split == NULL)
+ return;
+
+ if (cl->split != split) {
+ cl->defmap = 0;
+ cbq_sync_defmap(cl);
+ cl->split = split;
+ cl->defmap = def&mask;
+ } else
+ cl->defmap = (cl->defmap&~mask)|(def&mask);
+
+ cbq_sync_defmap(cl);
+}
+
+static void cbq_unlink_class(struct cbq_class *this)
+{
+ struct cbq_class *cl, **clp;
+ struct cbq_sched_data *q = (struct cbq_sched_data*)this->qdisc->data;
+
+ for (clp = &q->classes[cbq_hash(this->classid)]; (cl = *clp) != NULL; clp = &cl->next) {
+ if (cl == this) {
+ *clp = cl->next;
+ cl->next = NULL;
+ break;
+ }
+ }
+
+ if (this->tparent) {
+ clp=&this->sibling;
+ cl = *clp;
+ do {
+ if (cl == this) {
+ *clp = cl->sibling;
+ break;
+ }
+ clp = &cl->sibling;
+ } while ((cl = *clp) != this->sibling);
+
+ if (this->tparent->children == this) {
+ this->tparent->children = this->sibling;
+ if (this->sibling == this)
+ this->tparent->children = NULL;
+ }
+ } else {
+ BUG_TRAP(this->sibling == this);
+ }
+}
+
+static void cbq_link_class(struct cbq_class *this)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data*)this->qdisc->data;
+ unsigned h = cbq_hash(this->classid);
+ struct cbq_class *parent = this->tparent;
+
+ this->sibling = this;
+ this->next = q->classes[h];
+ q->classes[h] = this;
+
+ if (parent == NULL)
+ return;
+
+ if (parent->children == NULL) {
+ parent->children = this;
+ } else {
+ this->sibling = parent->children->sibling;
+ parent->children->sibling = this;
+ }
+}
+
+static unsigned int cbq_drop(struct Qdisc* sch)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ struct cbq_class *cl, *cl_head;
+ int prio;
+ unsigned int len;
+
+ for (prio = TC_CBQ_MAXPRIO; prio >= 0; prio--) {
+ if ((cl_head = q->active[prio]) == NULL)
+ continue;
+
+ cl = cl_head;
+ do {
+ if (cl->q->ops->drop && (len = cl->q->ops->drop(cl->q))) {
+ sch->q.qlen--;
+ return len;
+ }
+ } while ((cl = cl->next_alive) != cl_head);
+ }
+ return 0;
+}
+
+static void
+cbq_reset(struct Qdisc* sch)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ struct cbq_class *cl;
+ int prio;
+ unsigned h;
+
+ q->activemask = 0;
+ q->pmask = 0;
+ q->tx_class = NULL;
+ q->tx_borrowed = NULL;
+ del_timer(&q->wd_timer);
+ del_timer(&q->delay_timer);
+ q->toplevel = TC_CBQ_MAXLEVEL;
+ PSCHED_GET_TIME(q->now);
+ q->now_rt = q->now;
+
+ for (prio = 0; prio <= TC_CBQ_MAXPRIO; prio++)
+ q->active[prio] = NULL;
+
+ for (h = 0; h < 16; h++) {
+ for (cl = q->classes[h]; cl; cl = cl->next) {
+ qdisc_reset(cl->q);
+
+ cl->next_alive = NULL;
+ PSCHED_SET_PASTPERFECT(cl->undertime);
+ cl->avgidle = cl->maxidle;
+ cl->deficit = cl->quantum;
+ cl->cpriority = cl->priority;
+ }
+ }
+ sch->q.qlen = 0;
+}
+
+
+static int cbq_set_lss(struct cbq_class *cl, struct tc_cbq_lssopt *lss)
+{
+ if (lss->change&TCF_CBQ_LSS_FLAGS) {
+ cl->share = (lss->flags&TCF_CBQ_LSS_ISOLATED) ? NULL : cl->tparent;
+ cl->borrow = (lss->flags&TCF_CBQ_LSS_BOUNDED) ? NULL : cl->tparent;
+ }
+ if (lss->change&TCF_CBQ_LSS_EWMA)
+ cl->ewma_log = lss->ewma_log;
+ if (lss->change&TCF_CBQ_LSS_AVPKT)
+ cl->avpkt = lss->avpkt;
+ if (lss->change&TCF_CBQ_LSS_MINIDLE)
+ cl->minidle = -(long)lss->minidle;
+ if (lss->change&TCF_CBQ_LSS_MAXIDLE) {
+ cl->maxidle = lss->maxidle;
+ cl->avgidle = lss->maxidle;
+ }
+ if (lss->change&TCF_CBQ_LSS_OFFTIME)
+ cl->offtime = lss->offtime;
+ return 0;
+}
+
+static void cbq_rmprio(struct cbq_sched_data *q, struct cbq_class *cl)
+{
+ q->nclasses[cl->priority]--;
+ q->quanta[cl->priority] -= cl->weight;
+ cbq_normalize_quanta(q, cl->priority);
+}
+
+static void cbq_addprio(struct cbq_sched_data *q, struct cbq_class *cl)
+{
+ q->nclasses[cl->priority]++;
+ q->quanta[cl->priority] += cl->weight;
+ cbq_normalize_quanta(q, cl->priority);
+}
+
+static int cbq_set_wrr(struct cbq_class *cl, struct tc_cbq_wrropt *wrr)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)cl->qdisc->data;
+
+ if (wrr->allot)
+ cl->allot = wrr->allot;
+ if (wrr->weight)
+ cl->weight = wrr->weight;
+ if (wrr->priority) {
+ cl->priority = wrr->priority-1;
+ cl->cpriority = cl->priority;
+ if (cl->priority >= cl->priority2)
+ cl->priority2 = TC_CBQ_MAXPRIO-1;
+ }
+
+ cbq_addprio(q, cl);
+ return 0;
+}
+
+static int cbq_set_overlimit(struct cbq_class *cl, struct tc_cbq_ovl *ovl)
+{
+ switch (ovl->strategy) {
+ case TC_CBQ_OVL_CLASSIC:
+ cl->overlimit = cbq_ovl_classic;
+ break;
+ case TC_CBQ_OVL_DELAY:
+ cl->overlimit = cbq_ovl_delay;
+ break;
+ case TC_CBQ_OVL_LOWPRIO:
+ if (ovl->priority2-1 >= TC_CBQ_MAXPRIO ||
+ ovl->priority2-1 <= cl->priority)
+ return -EINVAL;
+ cl->priority2 = ovl->priority2-1;
+ cl->overlimit = cbq_ovl_lowprio;
+ break;
+ case TC_CBQ_OVL_DROP:
+ cl->overlimit = cbq_ovl_drop;
+ break;
+ case TC_CBQ_OVL_RCLASSIC:
+ cl->overlimit = cbq_ovl_rclassic;
+ break;
+ default:
+ return -EINVAL;
+ }
+ cl->penalty = (ovl->penalty*HZ)/1000;
+ return 0;
+}
+
+#ifdef CONFIG_NET_CLS_POLICE
+static int cbq_set_police(struct cbq_class *cl, struct tc_cbq_police *p)
+{
+ cl->police = p->police;
+
+ if (cl->q->handle) {
+ if (p->police == TC_POLICE_RECLASSIFY)
+ cl->q->reshape_fail = cbq_reshape_fail;
+ else
+ cl->q->reshape_fail = NULL;
+ }
+ return 0;
+}
+#endif
+
+static int cbq_set_fopt(struct cbq_class *cl, struct tc_cbq_fopt *fopt)
+{
+ cbq_change_defmap(cl, fopt->split, fopt->defmap, fopt->defchange);
+ return 0;
+}
+
+static int cbq_init(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data*)sch->data;
+ struct rtattr *tb[TCA_CBQ_MAX];
+ struct tc_ratespec *r;
+
+ if (rtattr_parse(tb, TCA_CBQ_MAX, RTA_DATA(opt), RTA_PAYLOAD(opt)) < 0 ||
+ tb[TCA_CBQ_RTAB-1] == NULL || tb[TCA_CBQ_RATE-1] == NULL ||
+ RTA_PAYLOAD(tb[TCA_CBQ_RATE-1]) < sizeof(struct tc_ratespec))
+ return -EINVAL;
+
+ if (tb[TCA_CBQ_LSSOPT-1] &&
+ RTA_PAYLOAD(tb[TCA_CBQ_LSSOPT-1]) < sizeof(struct tc_cbq_lssopt))
+ return -EINVAL;
+
+ r = RTA_DATA(tb[TCA_CBQ_RATE-1]);
+
+ MOD_INC_USE_COUNT;
+ if ((q->link.R_tab = qdisc_get_rtab(r, tb[TCA_CBQ_RTAB-1])) == NULL) {
+ MOD_DEC_USE_COUNT;
+ return -EINVAL;
+ }
+
+ q->link.refcnt = 1;
+ q->link.sibling = &q->link;
+ q->link.classid = sch->handle;
+ q->link.qdisc = sch;
+ if (!(q->link.q = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops)))
+ q->link.q = &noop_qdisc;
+
+ q->link.priority = TC_CBQ_MAXPRIO-1;
+ q->link.priority2 = TC_CBQ_MAXPRIO-1;
+ q->link.cpriority = TC_CBQ_MAXPRIO-1;
+ q->link.ovl_strategy = TC_CBQ_OVL_CLASSIC;
+ q->link.overlimit = cbq_ovl_classic;
+ q->link.allot = psched_mtu(sch->dev);
+ q->link.quantum = q->link.allot;
+ q->link.weight = q->link.R_tab->rate.rate;
+
+ q->link.ewma_log = TC_CBQ_DEF_EWMA;
+ q->link.avpkt = q->link.allot/2;
+ q->link.minidle = -0x7FFFFFFF;
+ q->link.stats.lock = &sch->dev->queue_lock;
+
+ init_timer(&q->wd_timer);
+ q->wd_timer.data = (unsigned long)sch;
+ q->wd_timer.function = cbq_watchdog;
+ init_timer(&q->delay_timer);
+ q->delay_timer.data = (unsigned long)sch;
+ q->delay_timer.function = cbq_undelay;
+ q->toplevel = TC_CBQ_MAXLEVEL;
+ PSCHED_GET_TIME(q->now);
+ q->now_rt = q->now;
+
+ cbq_link_class(&q->link);
+
+ if (tb[TCA_CBQ_LSSOPT-1])
+ cbq_set_lss(&q->link, RTA_DATA(tb[TCA_CBQ_LSSOPT-1]));
+
+ cbq_addprio(q, &q->link);
+ return 0;
+}
+
+static __inline__ int cbq_dump_rate(struct sk_buff *skb, struct cbq_class *cl)
+{
+ unsigned char *b = skb->tail;
+
+ RTA_PUT(skb, TCA_CBQ_RATE, sizeof(cl->R_tab->rate), &cl->R_tab->rate);
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static __inline__ int cbq_dump_lss(struct sk_buff *skb, struct cbq_class *cl)
+{
+ unsigned char *b = skb->tail;
+ struct tc_cbq_lssopt opt;
+
+ opt.flags = 0;
+ if (cl->borrow == NULL)
+ opt.flags |= TCF_CBQ_LSS_BOUNDED;
+ if (cl->share == NULL)
+ opt.flags |= TCF_CBQ_LSS_ISOLATED;
+ opt.ewma_log = cl->ewma_log;
+ opt.level = cl->level;
+ opt.avpkt = cl->avpkt;
+ opt.maxidle = cl->maxidle;
+ opt.minidle = (u32)(-cl->minidle);
+ opt.offtime = cl->offtime;
+ opt.change = ~0;
+ RTA_PUT(skb, TCA_CBQ_LSSOPT, sizeof(opt), &opt);
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static __inline__ int cbq_dump_wrr(struct sk_buff *skb, struct cbq_class *cl)
+{
+ unsigned char *b = skb->tail;
+ struct tc_cbq_wrropt opt;
+
+ opt.flags = 0;
+ opt.allot = cl->allot;
+ opt.priority = cl->priority+1;
+ opt.cpriority = cl->cpriority+1;
+ opt.weight = cl->weight;
+ RTA_PUT(skb, TCA_CBQ_WRROPT, sizeof(opt), &opt);
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static __inline__ int cbq_dump_ovl(struct sk_buff *skb, struct cbq_class *cl)
+{
+ unsigned char *b = skb->tail;
+ struct tc_cbq_ovl opt;
+
+ opt.strategy = cl->ovl_strategy;
+ opt.priority2 = cl->priority2+1;
+ opt.penalty = (cl->penalty*1000)/HZ;
+ RTA_PUT(skb, TCA_CBQ_OVL_STRATEGY, sizeof(opt), &opt);
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static __inline__ int cbq_dump_fopt(struct sk_buff *skb, struct cbq_class *cl)
+{
+ unsigned char *b = skb->tail;
+ struct tc_cbq_fopt opt;
+
+ if (cl->split || cl->defmap) {
+ opt.split = cl->split ? cl->split->classid : 0;
+ opt.defmap = cl->defmap;
+ opt.defchange = ~0;
+ RTA_PUT(skb, TCA_CBQ_FOPT, sizeof(opt), &opt);
+ }
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+#ifdef CONFIG_NET_CLS_POLICE
+static __inline__ int cbq_dump_police(struct sk_buff *skb, struct cbq_class *cl)
+{
+ unsigned char *b = skb->tail;
+ struct tc_cbq_police opt;
+
+ if (cl->police) {
+ opt.police = cl->police;
+ RTA_PUT(skb, TCA_CBQ_POLICE, sizeof(opt), &opt);
+ }
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+#endif
+
+static int cbq_dump_attr(struct sk_buff *skb, struct cbq_class *cl)
+{
+ if (cbq_dump_lss(skb, cl) < 0 ||
+ cbq_dump_rate(skb, cl) < 0 ||
+ cbq_dump_wrr(skb, cl) < 0 ||
+ cbq_dump_ovl(skb, cl) < 0 ||
+#ifdef CONFIG_NET_CLS_POLICE
+ cbq_dump_police(skb, cl) < 0 ||
+#endif
+ cbq_dump_fopt(skb, cl) < 0)
+ return -1;
+ return 0;
+}
+
+int cbq_copy_xstats(struct sk_buff *skb, struct tc_cbq_xstats *st)
+{
+ RTA_PUT(skb, TCA_XSTATS, sizeof(*st), st);
+ return 0;
+
+rtattr_failure:
+ return -1;
+}
+
+
+static int cbq_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data*)sch->data;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+
+ rta = (struct rtattr*)b;
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+ if (cbq_dump_attr(skb, &q->link) < 0)
+ goto rtattr_failure;
+ rta->rta_len = skb->tail - b;
+ spin_lock_bh(&sch->dev->queue_lock);
+ q->link.xstats.avgidle = q->link.avgidle;
+ if (cbq_copy_xstats(skb, &q->link.xstats)) {
+ spin_unlock_bh(&sch->dev->queue_lock);
+ goto rtattr_failure;
+ }
+ spin_unlock_bh(&sch->dev->queue_lock);
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int
+cbq_dump_class(struct Qdisc *sch, unsigned long arg,
+ struct sk_buff *skb, struct tcmsg *tcm)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data*)sch->data;
+ struct cbq_class *cl = (struct cbq_class*)arg;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+
+ if (cl->tparent)
+ tcm->tcm_parent = cl->tparent->classid;
+ else
+ tcm->tcm_parent = TC_H_ROOT;
+ tcm->tcm_handle = cl->classid;
+ tcm->tcm_info = cl->q->handle;
+
+ rta = (struct rtattr*)b;
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+ if (cbq_dump_attr(skb, cl) < 0)
+ goto rtattr_failure;
+ rta->rta_len = skb->tail - b;
+ cl->stats.qlen = cl->q->q.qlen;
+ if (qdisc_copy_stats(skb, &cl->stats))
+ goto rtattr_failure;
+ spin_lock_bh(&sch->dev->queue_lock);
+ cl->xstats.avgidle = cl->avgidle;
+ cl->xstats.undertime = 0;
+ if (!PSCHED_IS_PASTPERFECT(cl->undertime))
+ cl->xstats.undertime = PSCHED_TDIFF(cl->undertime, q->now);
+ if (cbq_copy_xstats(skb, &cl->xstats)) {
+ spin_unlock_bh(&sch->dev->queue_lock);
+ goto rtattr_failure;
+ }
+ spin_unlock_bh(&sch->dev->queue_lock);
+
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int cbq_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
+ struct Qdisc **old)
+{
+ struct cbq_class *cl = (struct cbq_class*)arg;
+
+ if (cl) {
+ if (new == NULL) {
+ if ((new = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops)) == NULL)
+ return -ENOBUFS;
+ } else {
+#ifdef CONFIG_NET_CLS_POLICE
+ if (cl->police == TC_POLICE_RECLASSIFY)
+ new->reshape_fail = cbq_reshape_fail;
+#endif
+ }
+ sch_tree_lock(sch);
+ *old = cl->q;
+ cl->q = new;
+ sch->q.qlen -= (*old)->q.qlen;
+ qdisc_reset(*old);
+ sch_tree_unlock(sch);
+
+ return 0;
+ }
+ return -ENOENT;
+}
+
+static struct Qdisc *
+cbq_leaf(struct Qdisc *sch, unsigned long arg)
+{
+ struct cbq_class *cl = (struct cbq_class*)arg;
+
+ return cl ? cl->q : NULL;
+}
+
+static unsigned long cbq_get(struct Qdisc *sch, u32 classid)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ struct cbq_class *cl = cbq_class_lookup(q, classid);
+
+ if (cl) {
+ cl->refcnt++;
+ return (unsigned long)cl;
+ }
+ return 0;
+}
+
+static void cbq_destroy_filters(struct cbq_class *cl)
+{
+ struct tcf_proto *tp;
+
+ while ((tp = cl->filter_list) != NULL) {
+ cl->filter_list = tp->next;
+ tcf_destroy(tp);
+ }
+}
+
+static void cbq_destroy_class(struct Qdisc *sch, struct cbq_class *cl)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+
+ BUG_TRAP(!cl->filters);
+
+ cbq_destroy_filters(cl);
+ qdisc_destroy(cl->q);
+ qdisc_put_rtab(cl->R_tab);
+#ifdef CONFIG_NET_ESTIMATOR
+ qdisc_kill_estimator(&cl->stats);
+#endif
+ if (cl != &q->link)
+ kfree(cl);
+}
+
+static void
+cbq_destroy(struct Qdisc* sch)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ struct cbq_class *cl;
+ unsigned h;
+
+#ifdef CONFIG_NET_CLS_POLICE
+ q->rx_class = NULL;
+#endif
+ /*
+ * Filters must be destroyed first because we don't destroy the
+ * classes from root to leafs which means that filters can still
+ * be bound to classes which have been destroyed already. --TGR '04
+ */
+ for (h = 0; h < 16; h++)
+ for (cl = q->classes[h]; cl; cl = cl->next)
+ cbq_destroy_filters(cl);
+
+ for (h = 0; h < 16; h++) {
+ struct cbq_class *next;
+
+ for (cl = q->classes[h]; cl; cl = next) {
+ next = cl->next;
+ cbq_destroy_class(sch, cl);
+ }
+ }
+
+ MOD_DEC_USE_COUNT;
+}
+
+static void cbq_put(struct Qdisc *sch, unsigned long arg)
+{
+ struct cbq_class *cl = (struct cbq_class*)arg;
+
+ if (--cl->refcnt == 0) {
+#ifdef CONFIG_NET_CLS_POLICE
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+
+ spin_lock_bh(&sch->dev->queue_lock);
+ if (q->rx_class == cl)
+ q->rx_class = NULL;
+ spin_unlock_bh(&sch->dev->queue_lock);
+#endif
+
+ cbq_destroy_class(sch, cl);
+ }
+}
+
+static int
+cbq_change_class(struct Qdisc *sch, u32 classid, u32 parentid, struct rtattr **tca,
+ unsigned long *arg)
+{
+ int err;
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ struct cbq_class *cl = (struct cbq_class*)*arg;
+ struct rtattr *opt = tca[TCA_OPTIONS-1];
+ struct rtattr *tb[TCA_CBQ_MAX];
+ struct cbq_class *parent;
+ struct qdisc_rate_table *rtab = NULL;
+
+ if (opt==NULL ||
+ rtattr_parse(tb, TCA_CBQ_MAX, RTA_DATA(opt), RTA_PAYLOAD(opt)))
+ return -EINVAL;
+
+ if (tb[TCA_CBQ_OVL_STRATEGY-1] &&
+ RTA_PAYLOAD(tb[TCA_CBQ_OVL_STRATEGY-1]) < sizeof(struct tc_cbq_ovl))
+ return -EINVAL;
+
+ if (tb[TCA_CBQ_FOPT-1] &&
+ RTA_PAYLOAD(tb[TCA_CBQ_FOPT-1]) < sizeof(struct tc_cbq_fopt))
+ return -EINVAL;
+
+ if (tb[TCA_CBQ_RATE-1] &&
+ RTA_PAYLOAD(tb[TCA_CBQ_RATE-1]) < sizeof(struct tc_ratespec))
+ return -EINVAL;
+
+ if (tb[TCA_CBQ_LSSOPT-1] &&
+ RTA_PAYLOAD(tb[TCA_CBQ_LSSOPT-1]) < sizeof(struct tc_cbq_lssopt))
+ return -EINVAL;
+
+ if (tb[TCA_CBQ_WRROPT-1] &&
+ RTA_PAYLOAD(tb[TCA_CBQ_WRROPT-1]) < sizeof(struct tc_cbq_wrropt))
+ return -EINVAL;
+
+#ifdef CONFIG_NET_CLS_POLICE
+ if (tb[TCA_CBQ_POLICE-1] &&
+ RTA_PAYLOAD(tb[TCA_CBQ_POLICE-1]) < sizeof(struct tc_cbq_police))
+ return -EINVAL;
+#endif
+
+ if (cl) {
+ /* Check parent */
+ if (parentid) {
+ if (cl->tparent && cl->tparent->classid != parentid)
+ return -EINVAL;
+ if (!cl->tparent && parentid != TC_H_ROOT)
+ return -EINVAL;
+ }
+
+ if (tb[TCA_CBQ_RATE-1]) {
+ rtab = qdisc_get_rtab(RTA_DATA(tb[TCA_CBQ_RATE-1]), tb[TCA_CBQ_RTAB-1]);
+ if (rtab == NULL)
+ return -EINVAL;
+ }
+
+ /* Change class parameters */
+ sch_tree_lock(sch);
+
+ if (cl->next_alive != NULL)
+ cbq_deactivate_class(cl);
+
+ if (rtab) {
+ rtab = xchg(&cl->R_tab, rtab);
+ qdisc_put_rtab(rtab);
+ }
+
+ if (tb[TCA_CBQ_LSSOPT-1])
+ cbq_set_lss(cl, RTA_DATA(tb[TCA_CBQ_LSSOPT-1]));
+
+ if (tb[TCA_CBQ_WRROPT-1]) {
+ cbq_rmprio(q, cl);
+ cbq_set_wrr(cl, RTA_DATA(tb[TCA_CBQ_WRROPT-1]));
+ }
+
+ if (tb[TCA_CBQ_OVL_STRATEGY-1])
+ cbq_set_overlimit(cl, RTA_DATA(tb[TCA_CBQ_OVL_STRATEGY-1]));
+
+#ifdef CONFIG_NET_CLS_POLICE
+ if (tb[TCA_CBQ_POLICE-1])
+ cbq_set_police(cl, RTA_DATA(tb[TCA_CBQ_POLICE-1]));
+#endif
+
+ if (tb[TCA_CBQ_FOPT-1])
+ cbq_set_fopt(cl, RTA_DATA(tb[TCA_CBQ_FOPT-1]));
+
+ if (cl->q->q.qlen)
+ cbq_activate_class(cl);
+
+ sch_tree_unlock(sch);
+
+#ifdef CONFIG_NET_ESTIMATOR
+ if (tca[TCA_RATE-1]) {
+ qdisc_kill_estimator(&cl->stats);
+ qdisc_new_estimator(&cl->stats, tca[TCA_RATE-1]);
+ }
+#endif
+ return 0;
+ }
+
+ if (parentid == TC_H_ROOT)
+ return -EINVAL;
+
+ if (tb[TCA_CBQ_WRROPT-1] == NULL || tb[TCA_CBQ_RATE-1] == NULL ||
+ tb[TCA_CBQ_LSSOPT-1] == NULL)
+ return -EINVAL;
+
+ rtab = qdisc_get_rtab(RTA_DATA(tb[TCA_CBQ_RATE-1]), tb[TCA_CBQ_RTAB-1]);
+ if (rtab == NULL)
+ return -EINVAL;
+
+ if (classid) {
+ err = -EINVAL;
+ if (TC_H_MAJ(classid^sch->handle) || cbq_class_lookup(q, classid))
+ goto failure;
+ } else {
+ int i;
+ classid = TC_H_MAKE(sch->handle,0x8000);
+
+ for (i=0; i<0x8000; i++) {
+ if (++q->hgenerator >= 0x8000)
+ q->hgenerator = 1;
+ if (cbq_class_lookup(q, classid|q->hgenerator) == NULL)
+ break;
+ }
+ err = -ENOSR;
+ if (i >= 0x8000)
+ goto failure;
+ classid = classid|q->hgenerator;
+ }
+
+ parent = &q->link;
+ if (parentid) {
+ parent = cbq_class_lookup(q, parentid);
+ err = -EINVAL;
+ if (parent == NULL)
+ goto failure;
+ }
+
+ err = -ENOBUFS;
+ cl = kmalloc(sizeof(*cl), GFP_KERNEL);
+ if (cl == NULL)
+ goto failure;
+ memset(cl, 0, sizeof(*cl));
+ cl->R_tab = rtab;
+ rtab = NULL;
+ cl->refcnt = 1;
+ if (!(cl->q = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops)))
+ cl->q = &noop_qdisc;
+ cl->classid = classid;
+ cl->tparent = parent;
+ cl->qdisc = sch;
+ cl->allot = parent->allot;
+ cl->quantum = cl->allot;
+ cl->weight = cl->R_tab->rate.rate;
+ cl->stats.lock = &sch->dev->queue_lock;
+
+ sch_tree_lock(sch);
+ cbq_link_class(cl);
+ cl->borrow = cl->tparent;
+ if (cl->tparent != &q->link)
+ cl->share = cl->tparent;
+ cbq_adjust_levels(parent);
+ cl->minidle = -0x7FFFFFFF;
+ cbq_set_lss(cl, RTA_DATA(tb[TCA_CBQ_LSSOPT-1]));
+ cbq_set_wrr(cl, RTA_DATA(tb[TCA_CBQ_WRROPT-1]));
+ if (cl->ewma_log==0)
+ cl->ewma_log = q->link.ewma_log;
+ if (cl->maxidle==0)
+ cl->maxidle = q->link.maxidle;
+ if (cl->avpkt==0)
+ cl->avpkt = q->link.avpkt;
+ cl->overlimit = cbq_ovl_classic;
+ if (tb[TCA_CBQ_OVL_STRATEGY-1])
+ cbq_set_overlimit(cl, RTA_DATA(tb[TCA_CBQ_OVL_STRATEGY-1]));
+#ifdef CONFIG_NET_CLS_POLICE
+ if (tb[TCA_CBQ_POLICE-1])
+ cbq_set_police(cl, RTA_DATA(tb[TCA_CBQ_POLICE-1]));
+#endif
+ if (tb[TCA_CBQ_FOPT-1])
+ cbq_set_fopt(cl, RTA_DATA(tb[TCA_CBQ_FOPT-1]));
+ sch_tree_unlock(sch);
+
+#ifdef CONFIG_NET_ESTIMATOR
+ if (tca[TCA_RATE-1])
+ qdisc_new_estimator(&cl->stats, tca[TCA_RATE-1]);
+#endif
+
+ *arg = (unsigned long)cl;
+ return 0;
+
+failure:
+ qdisc_put_rtab(rtab);
+ return err;
+}
+
+static int cbq_delete(struct Qdisc *sch, unsigned long arg)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ struct cbq_class *cl = (struct cbq_class*)arg;
+
+ if (cl->filters || cl->children || cl == &q->link)
+ return -EBUSY;
+
+ sch_tree_lock(sch);
+
+ if (cl->next_alive)
+ cbq_deactivate_class(cl);
+
+ if (q->tx_borrowed == cl)
+ q->tx_borrowed = q->tx_class;
+ if (q->tx_class == cl) {
+ q->tx_class = NULL;
+ q->tx_borrowed = NULL;
+ }
+#ifdef CONFIG_NET_CLS_POLICE
+ if (q->rx_class == cl)
+ q->rx_class = NULL;
+#endif
+
+ cbq_unlink_class(cl);
+ cbq_adjust_levels(cl->tparent);
+ cl->defmap = 0;
+ cbq_sync_defmap(cl);
+
+ cbq_rmprio(q, cl);
+ sch_tree_unlock(sch);
+
+ if (--cl->refcnt == 0)
+ cbq_destroy_class(sch, cl);
+
+ return 0;
+}
+
+static struct tcf_proto **cbq_find_tcf(struct Qdisc *sch, unsigned long arg)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ struct cbq_class *cl = (struct cbq_class *)arg;
+
+ if (cl == NULL)
+ cl = &q->link;
+
+ return &cl->filter_list;
+}
+
+static unsigned long cbq_bind_filter(struct Qdisc *sch, unsigned long parent,
+ u32 classid)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ struct cbq_class *p = (struct cbq_class*)parent;
+ struct cbq_class *cl = cbq_class_lookup(q, classid);
+
+ if (cl) {
+ if (p && p->level <= cl->level)
+ return 0;
+ cl->filters++;
+ return (unsigned long)cl;
+ }
+ return 0;
+}
+
+static void cbq_unbind_filter(struct Qdisc *sch, unsigned long arg)
+{
+ struct cbq_class *cl = (struct cbq_class*)arg;
+
+ cl->filters--;
+}
+
+static void cbq_walk(struct Qdisc *sch, struct qdisc_walker *arg)
+{
+ struct cbq_sched_data *q = (struct cbq_sched_data *)sch->data;
+ unsigned h;
+
+ if (arg->stop)
+ return;
+
+ for (h = 0; h < 16; h++) {
+ struct cbq_class *cl;
+
+ for (cl = q->classes[h]; cl; cl = cl->next) {
+ if (arg->count < arg->skip) {
+ arg->count++;
+ continue;
+ }
+ if (arg->fn(sch, (unsigned long)cl, arg) < 0) {
+ arg->stop = 1;
+ return;
+ }
+ arg->count++;
+ }
+ }
+}
+
+static struct Qdisc_class_ops cbq_class_ops =
+{
+ cbq_graft,
+ cbq_leaf,
+ cbq_get,
+ cbq_put,
+ cbq_change_class,
+ cbq_delete,
+ cbq_walk,
+
+ cbq_find_tcf,
+ cbq_bind_filter,
+ cbq_unbind_filter,
+
+ cbq_dump_class,
+};
+
+struct Qdisc_ops cbq_qdisc_ops =
+{
+ NULL,
+ &cbq_class_ops,
+ "cbq",
+ sizeof(struct cbq_sched_data),
+
+ cbq_enqueue,
+ cbq_dequeue,
+ cbq_requeue,
+ cbq_drop,
+
+ cbq_init,
+ cbq_reset,
+ cbq_destroy,
+ NULL /* cbq_change */,
+
+ cbq_dump,
+};
+
+#ifdef MODULE
+int init_module(void)
+{
+ return register_qdisc(&cbq_qdisc_ops);
+}
+
+void cleanup_module(void)
+{
+ unregister_qdisc(&cbq_qdisc_ops);
+}
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_csz.c b/uClinux-2.4.31-uc0/net/sched/sch_csz.c
new file mode 100644
index 0000000..a9469ff
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_csz.c
@@ -0,0 +1,1069 @@
+/*
+ * net/sched/sch_csz.c Clark-Shenker-Zhang scheduler.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+
+/* Clark-Shenker-Zhang algorithm.
+ =======================================
+
+ SOURCE.
+
+ David D. Clark, Scott Shenker and Lixia Zhang
+ "Supporting Real-Time Applications in an Integrated Services Packet
+ Network: Architecture and Mechanism".
+
+ CBQ presents a flexible universal algorithm for packet scheduling,
+ but it has pretty poor delay characteristics.
+ Round-robin scheduling and link-sharing goals
+ apparently contradict minimization of network delay and jitter.
+ Moreover, correct handling of predictive flows seems to be
+ impossible in CBQ.
+
+ CSZ presents a more precise but less flexible and less efficient
+ approach. As I understand it, the main idea is to create
+ WFQ flows for each guaranteed service and to allocate
+ the rest of bandwidth to dummy flow-0. Flow-0 comprises
+ the predictive services and the best effort traffic;
+ it is handled by a priority scheduler with the highest
+ priority band allocated for predictive services, and the rest ---
+ to the best effort packets.
+
+ Note that in CSZ flows are NOT limited to their bandwidth. It
+ is supposed that the flow passed admission control at the edge
+ of the QoS network and it doesn't need further shaping. Any
+ attempt to improve the flow or to shape it to a token bucket
+ at intermediate hops will introduce undesired delays and raise
+ jitter.
+
+ At the moment CSZ is the only scheduler that provides
+ true guaranteed service. Another schemes (including CBQ)
+ do not provide guaranteed delay and randomize jitter.
+ There is a proof (Sally Floyd), that delay
+ can be estimated by a IntServ compliant formula.
+ This result is true formally, but it is wrong in principle.
+ It takes into account only round-robin delays,
+ ignoring delays introduced by link sharing i.e. overlimiting.
+ Note that temporary overlimits are inevitable because
+ real links are not ideal, and the real algorithm must take this
+ into account.
+
+ ALGORITHM.
+
+ --- Notations.
+
+ $B$ is link bandwidth (bits/sec).
+
+ $I$ is set of all flows, including flow $0$.
+ Every flow $a \in I$ has associated bandwidth slice $r_a < 1$ and
+ $\sum_{a \in I} r_a = 1$.
+
+ --- Flow model.
+
+ Let $m_a$ is the number of backlogged bits in flow $a$.
+ The flow is {\em active}, if $m_a > 0$.
+ This number is a discontinuous function of time;
+ when a packet $i$ arrives:
+ \[
+ m_a(t_i+0) - m_a(t_i-0) = L^i,
+ \]
+ where $L^i$ is the length of the arrived packet.
+ The flow queue is drained continuously until $m_a == 0$:
+ \[
+ {d m_a \over dt} = - { B r_a \over \sum_{b \in A} r_b}.
+ \]
+ I.e. flow rates are their allocated rates proportionally
+ scaled to take all available link bandwidth. Apparently,
+ it is not the only possible policy. F.e. CBQ classes
+ without borrowing would be modelled by:
+ \[
+ {d m_a \over dt} = - B r_a .
+ \]
+ More complicated hierarchical bandwidth allocation
+ policies are possible, but unfortunately, the basic
+ flow equations have a simple solution only for proportional
+ scaling.
+
+ --- Departure times.
+
+ We calculate the time until the last bit of packet is sent:
+ \[
+ E_a^i(t) = { m_a(t_i) - \delta_a(t) \over r_a },
+ \]
+ where $\delta_a(t)$ is number of bits drained since $t_i$.
+ We have to evaluate $E_a^i$ for all queued packets,
+ then find the packet with minimal $E_a^i$ and send it.
+
+ This sounds good, but direct implementation of the algorithm
+ is absolutely infeasible. Luckily, if flow rates
+ are scaled proportionally, the equations have a simple solution.
+
+ The differential equation for $E_a^i$ is
+ \[
+ {d E_a^i (t) \over dt } = - { d \delta_a(t) \over dt} { 1 \over r_a} =
+ { B \over \sum_{b \in A} r_b}
+ \]
+ with initial condition
+ \[
+ E_a^i (t_i) = { m_a(t_i) \over r_a } .
+ \]
+
+ Let's introduce an auxiliary function $R(t)$:
+
+ --- Round number.
+
+ Consider the following model: we rotate over active flows,
+ sending $r_a B$ bits from every flow, so that we send
+ $B \sum_{a \in A} r_a$ bits per round, that takes
+ $\sum_{a \in A} r_a$ seconds.
+
+ Hence, $R(t)$ (round number) is a monotonically increasing
+ linear function of time when $A$ is not changed
+ \[
+ { d R(t) \over dt } = { 1 \over \sum_{a \in A} r_a }
+ \]
+ and it is continuous when $A$ changes.
+
+ The central observation is that the quantity
+ $F_a^i = R(t) + E_a^i(t)/B$ does not depend on time at all!
+ $R(t)$ does not depend on flow, so that $F_a^i$ can be
+ calculated only once on packet arrival, and we need not
+ recalculate $E$ numbers and resorting queues.
+ The number $F_a^i$ is called finish number of the packet.
+ It is just the value of $R(t)$ when the last bit of packet
+ is sent out.
+
+ Maximal finish number on flow is called finish number of flow
+ and minimal one is "start number of flow".
+ Apparently, flow is active if and only if $F_a \leq R$.
+
+ When a packet of length $L_i$ bit arrives to flow $a$ at time $t_i$,
+ we calculate $F_a^i$ as:
+
+ If flow was inactive ($F_a < R$):
+ $F_a^i = R(t) + {L_i \over B r_a}$
+ otherwise
+ $F_a^i = F_a + {L_i \over B r_a}$
+
+ These equations complete the algorithm specification.
+
+ It looks pretty hairy, but there is a simple
+ procedure for solving these equations.
+ See procedure csz_update(), that is a generalization of
+ the algorithm from S. Keshav's thesis Chapter 3
+ "Efficient Implementation of Fair Queeing".
+
+ NOTES.
+
+ * We implement only the simplest variant of CSZ,
+ when flow-0 is a explicit 4band priority fifo.
+ This is bad, but we need a "peek" operation in addition
+ to "dequeue" to implement complete CSZ.
+ I do not want to do that, unless it is absolutely
+ necessary.
+
+ * A primitive support for token bucket filtering
+ presents itself too. It directly contradicts CSZ, but
+ even though the Internet is on the globe ... :-)
+ "the edges of the network" really exist.
+
+ BUGS.
+
+ * Fixed point arithmetic is overcomplicated, suboptimal and even
+ wrong. Check it later. */
+
+
+/* This number is arbitrary */
+
+#define CSZ_GUARANTEED 16
+#define CSZ_FLOWS (CSZ_GUARANTEED+4)
+
+struct csz_head
+{
+ struct csz_head *snext;
+ struct csz_head *sprev;
+ struct csz_head *fnext;
+ struct csz_head *fprev;
+};
+
+struct csz_flow
+{
+ struct csz_head *snext;
+ struct csz_head *sprev;
+ struct csz_head *fnext;
+ struct csz_head *fprev;
+
+/* Parameters */
+ struct tc_ratespec rate;
+ struct tc_ratespec slice;
+ u32 *L_tab; /* Lookup table for L/(B*r_a) values */
+ unsigned long limit; /* Maximal length of queue */
+#ifdef CSZ_PLUS_TBF
+ struct tc_ratespec peakrate;
+ __u32 buffer; /* Depth of token bucket, normalized
+ as L/(B*r_a) */
+ __u32 mtu;
+#endif
+
+/* Variables */
+#ifdef CSZ_PLUS_TBF
+ unsigned long tokens; /* Tokens number: usecs */
+ psched_time_t t_tbf;
+ unsigned long R_tbf;
+ int throttled;
+#endif
+ unsigned peeked;
+ unsigned long start; /* Finish number of the first skb */
+ unsigned long finish; /* Finish number of the flow */
+
+ struct sk_buff_head q; /* FIFO queue */
+};
+
+#define L2R(f,L) ((f)->L_tab[(L)>>(f)->slice.cell_log])
+
+struct csz_sched_data
+{
+/* Parameters */
+ unsigned char rate_log; /* fixed point position for rate;
+ * really we need not it */
+ unsigned char R_log; /* fixed point position for round number */
+ unsigned char delta_log; /* 1<<delta_log is maximal timeout in usecs;
+ * 21 <-> 2.1sec is MAXIMAL value */
+
+/* Variables */
+ struct tcf_proto *filter_list;
+ u8 prio2band[TC_PRIO_MAX+1];
+#ifdef CSZ_PLUS_TBF
+ struct timer_list wd_timer;
+ long wd_expires;
+#endif
+ psched_time_t t_c; /* Time check-point */
+ unsigned long R_c; /* R-number check-point */
+ unsigned long rate; /* Current sum of rates of active flows */
+ struct csz_head s; /* Flows sorted by "start" */
+ struct csz_head f; /* Flows sorted by "finish" */
+
+ struct sk_buff_head other[4];/* Predicted (0) and the best efforts
+ classes (1,2,3) */
+ struct csz_flow flow[CSZ_GUARANTEED]; /* Array of flows */
+};
+
+/* These routines (csz_insert_finish and csz_insert_start) are
+ the most time consuming part of all the algorithm.
+
+ We insert to sorted list, so that time
+ is linear with respect to number of active flows in the worst case.
+ Note that we have not very large number of guaranteed flows,
+ so that logarithmic algorithms (heap etc.) are useless,
+ they are slower than linear one when length of list <= 32.
+
+ Heap would take sence if we used WFQ for best efforts
+ flows, but SFQ is better choice in this case.
+ */
+
+
+/* Insert flow "this" to the list "b" before
+ flow with greater finish number.
+ */
+
+#if 0
+/* Scan forward */
+extern __inline__ void csz_insert_finish(struct csz_head *b,
+ struct csz_flow *this)
+{
+ struct csz_head *f = b->fnext;
+ unsigned long finish = this->finish;
+
+ while (f != b) {
+ if (((struct csz_flow*)f)->finish - finish > 0)
+ break;
+ f = f->fnext;
+ }
+ this->fnext = f;
+ this->fprev = f->fprev;
+ this->fnext->fprev = this->fprev->fnext = (struct csz_head*)this;
+}
+#else
+/* Scan backward */
+extern __inline__ void csz_insert_finish(struct csz_head *b,
+ struct csz_flow *this)
+{
+ struct csz_head *f = b->fprev;
+ unsigned long finish = this->finish;
+
+ while (f != b) {
+ if (((struct csz_flow*)f)->finish - finish <= 0)
+ break;
+ f = f->fprev;
+ }
+ this->fnext = f->fnext;
+ this->fprev = f;
+ this->fnext->fprev = this->fprev->fnext = (struct csz_head*)this;
+}
+#endif
+
+/* Insert flow "this" to the list "b" before
+ flow with greater start number.
+ */
+
+extern __inline__ void csz_insert_start(struct csz_head *b,
+ struct csz_flow *this)
+{
+ struct csz_head *f = b->snext;
+ unsigned long start = this->start;
+
+ while (f != b) {
+ if (((struct csz_flow*)f)->start - start > 0)
+ break;
+ f = f->snext;
+ }
+ this->snext = f;
+ this->sprev = f->sprev;
+ this->snext->sprev = this->sprev->snext = (struct csz_head*)this;
+}
+
+
+/* Calculate and return current round number.
+ It is another time consuming part, but
+ it is impossible to avoid it.
+
+ It costs O(N) that make all the algorithm useful only
+ to play with closest to ideal fluid model.
+
+ There exist less academic, but more practical modifications,
+ which might have even better characteristics (WF2Q+, HPFQ, HFSC)
+ */
+
+static unsigned long csz_update(struct Qdisc *sch)
+{
+ struct csz_sched_data *q = (struct csz_sched_data*)sch->data;
+ struct csz_flow *a;
+ unsigned long F;
+ unsigned long tmp;
+ psched_time_t now;
+ unsigned long delay;
+ unsigned long R_c;
+
+ PSCHED_GET_TIME(now);
+ delay = PSCHED_TDIFF_SAFE(now, q->t_c, 0, goto do_reset);
+
+ if (delay>>q->delta_log) {
+do_reset:
+ /* Delta is too large.
+ It is possible if MTU/BW > 1<<q->delta_log
+ (i.e. configuration error) or because of hardware
+ fault. We have no choice...
+ */
+ qdisc_reset(sch);
+ return 0;
+ }
+
+ q->t_c = now;
+
+ for (;;) {
+ a = (struct csz_flow*)q->f.fnext;
+
+ /* No more active flows. Reset R and exit. */
+ if (a == (struct csz_flow*)&q->f) {
+#ifdef CSZ_DEBUG
+ if (q->rate) {
+ printk("csz_update: rate!=0 on inactive csz\n");
+ q->rate = 0;
+ }
+#endif
+ q->R_c = 0;
+ return 0;
+ }
+
+ F = a->finish;
+
+#ifdef CSZ_DEBUG
+ if (q->rate == 0) {
+ printk("csz_update: rate=0 on active csz\n");
+ goto do_reset;
+ }
+#endif
+
+ /*
+ * tmp = (t - q->t_c)/q->rate;
+ */
+
+ tmp = ((delay<<(31-q->delta_log))/q->rate)>>(31-q->delta_log+q->R_log);
+
+ tmp += q->R_c;
+
+ /* OK, this flow (and all flows with greater
+ finish numbers) is still active */
+ if (F - tmp > 0)
+ break;
+
+ /* It is more not active */
+
+ a->fprev->fnext = a->fnext;
+ a->fnext->fprev = a->fprev;
+
+ /*
+ * q->t_c += (F - q->R_c)*q->rate
+ */
+
+ tmp = ((F-q->R_c)*q->rate)<<q->R_log;
+ R_c = F;
+ q->rate -= a->slice.rate;
+
+ if ((long)(delay - tmp) >= 0) {
+ delay -= tmp;
+ continue;
+ }
+ delay = 0;
+ }
+
+ q->R_c = tmp;
+ return tmp;
+}
+
+unsigned csz_classify(struct sk_buff *skb, struct csz_sched_data *q)
+{
+ return CSZ_GUARANTEED;
+}
+
+static int
+csz_enqueue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct csz_sched_data *q = (struct csz_sched_data *)sch->data;
+ unsigned flow_id = csz_classify(skb, q);
+ unsigned long R;
+ int prio = 0;
+ struct csz_flow *this;
+
+ if (flow_id >= CSZ_GUARANTEED) {
+ prio = flow_id - CSZ_GUARANTEED;
+ flow_id = 0;
+ }
+
+ this = &q->flow[flow_id];
+ if (this->q.qlen >= this->limit || this->L_tab == NULL) {
+ sch->stats.drops++;
+ kfree_skb(skb);
+ return NET_XMIT_DROP;
+ }
+
+ R = csz_update(sch);
+
+ if ((long)(this->finish - R) >= 0) {
+ /* It was active */
+ this->finish += L2R(this,skb->len);
+ } else {
+ /* It is inactive; activate it */
+ this->finish = R + L2R(this,skb->len);
+ q->rate += this->slice.rate;
+ csz_insert_finish(&q->f, this);
+ }
+
+ /* If this flow was empty, remember start number
+ and insert it into start queue */
+ if (this->q.qlen == 0) {
+ this->start = this->finish;
+ csz_insert_start(&q->s, this);
+ }
+ if (flow_id)
+ skb_queue_tail(&this->q, skb);
+ else
+ skb_queue_tail(&q->other[prio], skb);
+ sch->q.qlen++;
+ sch->stats.bytes += skb->len;
+ sch->stats.packets++;
+ return 0;
+}
+
+static __inline__ struct sk_buff *
+skb_dequeue_best(struct csz_sched_data * q)
+{
+ int i;
+ struct sk_buff *skb;
+
+ for (i=0; i<4; i++) {
+ skb = skb_dequeue(&q->other[i]);
+ if (skb) {
+ q->flow[0].q.qlen--;
+ return skb;
+ }
+ }
+ return NULL;
+}
+
+static __inline__ struct sk_buff *
+skb_peek_best(struct csz_sched_data * q)
+{
+ int i;
+ struct sk_buff *skb;
+
+ for (i=0; i<4; i++) {
+ skb = skb_peek(&q->other[i]);
+ if (skb)
+ return skb;
+ }
+ return NULL;
+}
+
+#ifdef CSZ_PLUS_TBF
+
+static void csz_watchdog(unsigned long arg)
+{
+ struct Qdisc *sch = (struct Qdisc*)arg;
+
+ qdisc_wakeup(sch->dev);
+}
+
+static __inline__ void
+csz_move_queue(struct csz_flow *this, long delta)
+{
+ this->fprev->fnext = this->fnext;
+ this->fnext->fprev = this->fprev;
+
+ this->start += delta;
+ this->finish += delta;
+
+ csz_insert_finish(this);
+}
+
+static __inline__ int csz_enough_tokens(struct csz_sched_data *q,
+ struct csz_flow *this,
+ struct sk_buff *skb)
+{
+ long toks;
+ long shift;
+ psched_time_t now;
+
+ PSCHED_GET_TIME(now);
+
+ toks = PSCHED_TDIFF(now, t_tbf) + this->tokens - L2R(q,this,skb->len);
+
+ shift = 0;
+ if (this->throttled) {
+ /* Remember aposteriory delay */
+
+ unsigned long R = csz_update(q);
+ shift = R - this->R_tbf;
+ this->R_tbf = R;
+ }
+
+ if (toks >= 0) {
+ /* Now we have enough tokens to proceed */
+
+ this->tokens = toks <= this->depth ? toks : this->depth;
+ this->t_tbf = now;
+
+ if (!this->throttled)
+ return 1;
+
+ /* Flow was throttled. Update its start&finish numbers
+ with delay calculated aposteriori.
+ */
+
+ this->throttled = 0;
+ if (shift > 0)
+ csz_move_queue(this, shift);
+ return 1;
+ }
+
+ if (!this->throttled) {
+ /* Flow has just been throttled; remember
+ current round number to calculate aposteriori delay
+ */
+ this->throttled = 1;
+ this->R_tbf = csz_update(q);
+ }
+
+ /* Move all the queue to the time when it will be allowed to send.
+ We should translate time to round number, but it is impossible,
+ so that we made the most conservative estimate i.e. we suppose
+ that only this flow is active and, hence, R = t.
+ Really toks <= R <= toks/r_a.
+
+ This apriory shift in R will be adjusted later to reflect
+ real delay. We cannot avoid it because of:
+ - throttled flow continues to be active from the viewpoint
+ of CSZ, so that it would acquire the highest priority,
+ if you not adjusted start numbers.
+ - Eventually, finish number would become less than round
+ number and flow were declared inactive.
+ */
+
+ toks = -toks;
+
+ /* Remeber, that we should start watchdog */
+ if (toks < q->wd_expires)
+ q->wd_expires = toks;
+
+ toks >>= q->R_log;
+ shift += toks;
+ if (shift > 0) {
+ this->R_tbf += toks;
+ csz_move_queue(this, shift);
+ }
+ csz_insert_start(this);
+ return 0;
+}
+#endif
+
+
+static struct sk_buff *
+csz_dequeue(struct Qdisc* sch)
+{
+ struct csz_sched_data *q = (struct csz_sched_data *)sch->data;
+ struct sk_buff *skb;
+ struct csz_flow *this;
+
+#ifdef CSZ_PLUS_TBF
+ q->wd_expires = 0;
+#endif
+ this = (struct csz_flow*)q->s.snext;
+
+ while (this != (struct csz_flow*)&q->s) {
+
+ /* First of all: unlink from start list */
+ this->sprev->snext = this->snext;
+ this->snext->sprev = this->sprev;
+
+ if (this != &q->flow[0]) { /* Guaranteed flow */
+ skb = __skb_dequeue(&this->q);
+ if (skb) {
+#ifdef CSZ_PLUS_TBF
+ if (this->depth) {
+ if (!csz_enough_tokens(q, this, skb))
+ continue;
+ }
+#endif
+ if (this->q.qlen) {
+ struct sk_buff *nskb = skb_peek(&this->q);
+ this->start += L2R(this,nskb->len);
+ csz_insert_start(&q->s, this);
+ }
+ sch->q.qlen--;
+ return skb;
+ }
+ } else { /* Predicted or best effort flow */
+ skb = skb_dequeue_best(q);
+ if (skb) {
+ unsigned peeked = this->peeked;
+ this->peeked = 0;
+
+ if (--this->q.qlen) {
+ struct sk_buff *nskb;
+ unsigned dequeued = L2R(this,skb->len);
+
+ /* We got not the same thing that
+ peeked earlier; adjust start number
+ */
+ if (peeked != dequeued && peeked)
+ this->start += dequeued - peeked;
+
+ nskb = skb_peek_best(q);
+ peeked = L2R(this,nskb->len);
+ this->start += peeked;
+ this->peeked = peeked;
+ csz_insert_start(&q->s, this);
+ }
+ sch->q.qlen--;
+ return skb;
+ }
+ }
+ }
+#ifdef CSZ_PLUS_TBF
+ /* We are about to return no skb.
+ Schedule watchdog timer, if it occurred because of shaping.
+ */
+ if (q->wd_expires) {
+ unsigned long delay = PSCHED_US2JIFFIE(q->wd_expires);
+ if (delay == 0)
+ delay = 1;
+ mod_timer(&q->wd_timer, jiffies + delay);
+ sch->stats.overlimits++;
+ }
+#endif
+ return NULL;
+}
+
+static void
+csz_reset(struct Qdisc* sch)
+{
+ struct csz_sched_data *q = (struct csz_sched_data *)sch->data;
+ int i;
+
+ for (i=0; i<4; i++)
+ skb_queue_purge(&q->other[i]);
+
+ for (i=0; i<CSZ_GUARANTEED; i++) {
+ struct csz_flow *this = q->flow + i;
+ skb_queue_purge(&this->q);
+ this->snext = this->sprev =
+ this->fnext = this->fprev = (struct csz_head*)this;
+ this->start = this->finish = 0;
+ }
+ q->s.snext = q->s.sprev = &q->s;
+ q->f.fnext = q->f.fprev = &q->f;
+ q->R_c = 0;
+#ifdef CSZ_PLUS_TBF
+ PSCHED_GET_TIME(&q->t_tbf);
+ q->tokens = q->depth;
+ del_timer(&q->wd_timer);
+#endif
+ sch->q.qlen = 0;
+}
+
+static void
+csz_destroy(struct Qdisc* sch)
+{
+ struct csz_sched_data *q = (struct csz_sched_data *)sch->data;
+ struct tcf_proto *tp;
+
+ while ((tp = q->filter_list) != NULL) {
+ q->filter_list = tp->next;
+ tcf_destroy(tp);
+ }
+
+ MOD_DEC_USE_COUNT;
+}
+
+static int csz_init(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct csz_sched_data *q = (struct csz_sched_data *)sch->data;
+ struct rtattr *tb[TCA_CSZ_PTAB];
+ struct tc_csz_qopt *qopt;
+ int i;
+
+ rtattr_parse(tb, TCA_CSZ_PTAB, RTA_DATA(opt), RTA_PAYLOAD(opt));
+ if (tb[TCA_CSZ_PARMS-1] == NULL ||
+ RTA_PAYLOAD(tb[TCA_CSZ_PARMS-1]) < sizeof(*qopt))
+ return -EINVAL;
+ qopt = RTA_DATA(tb[TCA_CSZ_PARMS-1]);
+
+ q->R_log = qopt->R_log;
+ q->delta_log = qopt->delta_log;
+ for (i=0; i<=TC_PRIO_MAX; i++) {
+ if (qopt->priomap[i] >= CSZ_FLOWS)
+ return -EINVAL;
+ q->prio2band[i] = qopt->priomap[i];
+ }
+
+ for (i=0; i<4; i++)
+ skb_queue_head_init(&q->other[i]);
+
+ for (i=0; i<CSZ_GUARANTEED; i++) {
+ struct csz_flow *this = q->flow + i;
+ skb_queue_head_init(&this->q);
+ this->snext = this->sprev =
+ this->fnext = this->fprev = (struct csz_head*)this;
+ this->start = this->finish = 0;
+ }
+ q->s.snext = q->s.sprev = &q->s;
+ q->f.fnext = q->f.fprev = &q->f;
+ q->R_c = 0;
+#ifdef CSZ_PLUS_TBF
+ init_timer(&q->wd_timer);
+ q->wd_timer.data = (unsigned long)sch;
+ q->wd_timer.function = csz_watchdog;
+#endif
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int csz_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+ struct csz_sched_data *q = (struct csz_sched_data *)sch->data;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+ struct tc_csz_qopt opt;
+
+ rta = (struct rtattr*)b;
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+
+ opt.flows = CSZ_FLOWS;
+ memcpy(&opt.priomap, q->prio2band, TC_PRIO_MAX+1);
+ RTA_PUT(skb, TCA_CSZ_PARMS, sizeof(opt), &opt);
+ rta->rta_len = skb->tail - b;
+
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int csz_graft(struct Qdisc *sch, unsigned long cl, struct Qdisc *new,
+ struct Qdisc **old)
+{
+ return -EINVAL;
+}
+
+static struct Qdisc * csz_leaf(struct Qdisc *sch, unsigned long cl)
+{
+ return NULL;
+}
+
+
+static unsigned long csz_get(struct Qdisc *sch, u32 classid)
+{
+ struct csz_sched_data *q = (struct csz_sched_data *)sch->data;
+ unsigned long band = TC_H_MIN(classid) - 1;
+
+ if (band >= CSZ_FLOWS)
+ return 0;
+
+ if (band < CSZ_GUARANTEED && q->flow[band].L_tab == NULL)
+ return 0;
+
+ return band+1;
+}
+
+static unsigned long csz_bind(struct Qdisc *sch, unsigned long parent, u32 classid)
+{
+ return csz_get(sch, classid);
+}
+
+
+static void csz_put(struct Qdisc *sch, unsigned long cl)
+{
+ return;
+}
+
+static int csz_change(struct Qdisc *sch, u32 handle, u32 parent, struct rtattr **tca, unsigned long *arg)
+{
+ unsigned long cl = *arg;
+ struct csz_sched_data *q = (struct csz_sched_data *)sch->data;
+ struct rtattr *opt = tca[TCA_OPTIONS-1];
+ struct rtattr *tb[TCA_CSZ_PTAB];
+ struct tc_csz_copt *copt;
+
+ rtattr_parse(tb, TCA_CSZ_PTAB, RTA_DATA(opt), RTA_PAYLOAD(opt));
+ if (tb[TCA_CSZ_PARMS-1] == NULL ||
+ RTA_PAYLOAD(tb[TCA_CSZ_PARMS-1]) < sizeof(*copt))
+ return -EINVAL;
+ copt = RTA_DATA(tb[TCA_CSZ_PARMS-1]);
+
+ if (tb[TCA_CSZ_RTAB-1] &&
+ RTA_PAYLOAD(tb[TCA_CSZ_RTAB-1]) < 1024)
+ return -EINVAL;
+
+ if (cl) {
+ struct csz_flow *a;
+ cl--;
+ if (cl >= CSZ_FLOWS)
+ return -ENOENT;
+ if (cl >= CSZ_GUARANTEED || q->flow[cl].L_tab == NULL)
+ return -EINVAL;
+
+ a = &q->flow[cl];
+
+ spin_lock_bh(&sch->dev->queue_lock);
+#if 0
+ a->rate_log = copt->rate_log;
+#endif
+#ifdef CSZ_PLUS_TBF
+ a->limit = copt->limit;
+ a->rate = copt->rate;
+ a->buffer = copt->buffer;
+ a->mtu = copt->mtu;
+#endif
+
+ if (tb[TCA_CSZ_RTAB-1])
+ memcpy(a->L_tab, RTA_DATA(tb[TCA_CSZ_RTAB-1]), 1024);
+
+ spin_unlock_bh(&sch->dev->queue_lock);
+ return 0;
+ }
+ /* NI */
+ return 0;
+}
+
+static int csz_delete(struct Qdisc *sch, unsigned long cl)
+{
+ struct csz_sched_data *q = (struct csz_sched_data *)sch->data;
+ struct csz_flow *a;
+
+ cl--;
+
+ if (cl >= CSZ_FLOWS)
+ return -ENOENT;
+ if (cl >= CSZ_GUARANTEED || q->flow[cl].L_tab == NULL)
+ return -EINVAL;
+
+ a = &q->flow[cl];
+
+ spin_lock_bh(&sch->dev->queue_lock);
+ a->fprev->fnext = a->fnext;
+ a->fnext->fprev = a->fprev;
+ a->sprev->snext = a->snext;
+ a->snext->sprev = a->sprev;
+ a->start = a->finish = 0;
+ kfree(xchg(&q->flow[cl].L_tab, NULL));
+ spin_unlock_bh(&sch->dev->queue_lock);
+
+ return 0;
+}
+
+static int csz_dump_class(struct Qdisc *sch, unsigned long cl, struct sk_buff *skb, struct tcmsg *tcm)
+{
+ struct csz_sched_data *q = (struct csz_sched_data *)sch->data;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+ struct tc_csz_copt opt;
+
+ tcm->tcm_handle = sch->handle|cl;
+
+ cl--;
+
+ if (cl > CSZ_FLOWS)
+ goto rtattr_failure;
+
+ if (cl < CSZ_GUARANTEED) {
+ struct csz_flow *f = &q->flow[cl];
+
+ if (f->L_tab == NULL)
+ goto rtattr_failure;
+
+ rta = (struct rtattr*)b;
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+
+ opt.limit = f->limit;
+ opt.rate = f->rate;
+ opt.slice = f->slice;
+ memset(&opt.peakrate, 0, sizeof(opt.peakrate));
+#ifdef CSZ_PLUS_TBF
+ opt.buffer = f->buffer;
+ opt.mtu = f->mtu;
+#else
+ opt.buffer = 0;
+ opt.mtu = 0;
+#endif
+
+ RTA_PUT(skb, TCA_CSZ_PARMS, sizeof(opt), &opt);
+ rta->rta_len = skb->tail - b;
+ }
+
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static void csz_walk(struct Qdisc *sch, struct qdisc_walker *arg)
+{
+ struct csz_sched_data *q = (struct csz_sched_data *)sch->data;
+ int prio = 0;
+
+ if (arg->stop)
+ return;
+
+ for (prio = 0; prio < CSZ_FLOWS; prio++) {
+ if (arg->count < arg->skip) {
+ arg->count++;
+ continue;
+ }
+ if (prio < CSZ_GUARANTEED && q->flow[prio].L_tab == NULL) {
+ arg->count++;
+ continue;
+ }
+ if (arg->fn(sch, prio+1, arg) < 0) {
+ arg->stop = 1;
+ break;
+ }
+ arg->count++;
+ }
+}
+
+static struct tcf_proto ** csz_find_tcf(struct Qdisc *sch, unsigned long cl)
+{
+ struct csz_sched_data *q = (struct csz_sched_data *)sch->data;
+
+ if (cl)
+ return NULL;
+
+ return &q->filter_list;
+}
+
+struct Qdisc_class_ops csz_class_ops =
+{
+ csz_graft,
+ csz_leaf,
+
+ csz_get,
+ csz_put,
+ csz_change,
+ csz_delete,
+ csz_walk,
+
+ csz_find_tcf,
+ csz_bind,
+ csz_put,
+
+ csz_dump_class,
+};
+
+struct Qdisc_ops csz_qdisc_ops =
+{
+ NULL,
+ &csz_class_ops,
+ "csz",
+ sizeof(struct csz_sched_data),
+
+ csz_enqueue,
+ csz_dequeue,
+ NULL,
+ NULL,
+
+ csz_init,
+ csz_reset,
+ csz_destroy,
+ NULL /* csz_change */,
+
+ csz_dump,
+};
+
+
+#ifdef MODULE
+int init_module(void)
+{
+ return register_qdisc(&csz_qdisc_ops);
+}
+
+void cleanup_module(void)
+{
+ unregister_qdisc(&csz_qdisc_ops);
+}
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_dsmark.c b/uClinux-2.4.31-uc0/net/sched/sch_dsmark.c
new file mode 100644
index 0000000..b7def63
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_dsmark.c
@@ -0,0 +1,488 @@
+/* net/sched/sch_dsmark.c - Differentiated Services field marker */
+
+/* Written 1998-2000 by Werner Almesberger, EPFL ICA */
+
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/string.h>
+#include <linux/errno.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h> /* for pkt_sched */
+#include <linux/rtnetlink.h>
+#include <net/pkt_sched.h>
+#include <net/dsfield.h>
+#include <net/inet_ecn.h>
+#include <asm/byteorder.h>
+
+
+#if 1 /* control */
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+#if 0 /* data */
+#define D2PRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define D2PRINTK(format,args...)
+#endif
+
+
+#define PRIV(sch) ((struct dsmark_qdisc_data *) (sch)->data)
+
+
+/*
+ * classid class marking
+ * ------- ----- -------
+ * n/a 0 n/a
+ * x:0 1 use entry [0]
+ * ... ... ...
+ * x:y y>0 y+1 use entry [y]
+ * ... ... ...
+ * x:indices-1 indices use entry [indices-1]
+ * ... ... ...
+ * x:y y+1 use entry [y & (indices-1)]
+ * ... ... ...
+ * 0xffff 0x10000 use entry [indices-1]
+ */
+
+
+#define NO_DEFAULT_INDEX (1 << 16)
+
+struct dsmark_qdisc_data {
+ struct Qdisc *q;
+ struct tcf_proto *filter_list;
+ __u8 *mask; /* "owns" the array */
+ __u8 *value;
+ __u16 indices;
+ __u32 default_index; /* index range is 0...0xffff */
+ int set_tc_index;
+};
+
+
+/* ------------------------- Class/flow operations ------------------------- */
+
+
+static int dsmark_graft(struct Qdisc *sch,unsigned long arg,
+ struct Qdisc *new,struct Qdisc **old)
+{
+ struct dsmark_qdisc_data *p = PRIV(sch);
+
+ DPRINTK("dsmark_graft(sch %p,[qdisc %p],new %p,old %p)\n",sch,p,new,
+ old);
+ if (!new)
+ new = &noop_qdisc;
+ sch_tree_lock(sch);
+ *old = xchg(&p->q,new);
+ if (*old)
+ qdisc_reset(*old);
+ sch->q.qlen = 0;
+ sch_tree_unlock(sch); /* @@@ move up ? */
+ return 0;
+}
+
+
+static struct Qdisc *dsmark_leaf(struct Qdisc *sch, unsigned long arg)
+{
+ struct dsmark_qdisc_data *p = PRIV(sch);
+
+ return p->q;
+}
+
+
+static unsigned long dsmark_get(struct Qdisc *sch,u32 classid)
+{
+ struct dsmark_qdisc_data *p __attribute__((unused)) = PRIV(sch);
+
+ DPRINTK("dsmark_get(sch %p,[qdisc %p],classid %x)\n",sch,p,classid);
+ return TC_H_MIN(classid)+1;
+}
+
+
+static unsigned long dsmark_bind_filter(struct Qdisc *sch,
+ unsigned long parent, u32 classid)
+{
+ return dsmark_get(sch,classid);
+}
+
+
+static void dsmark_put(struct Qdisc *sch, unsigned long cl)
+{
+}
+
+
+static int dsmark_change(struct Qdisc *sch, u32 classid, u32 parent,
+ struct rtattr **tca, unsigned long *arg)
+{
+ struct dsmark_qdisc_data *p = PRIV(sch);
+ struct rtattr *opt = tca[TCA_OPTIONS-1];
+ struct rtattr *tb[TCA_DSMARK_MAX];
+
+ DPRINTK("dsmark_change(sch %p,[qdisc %p],classid %x,parent %x),"
+ "arg 0x%lx\n",sch,p,classid,parent,*arg);
+ if (*arg > p->indices)
+ return -ENOENT;
+ if (!opt || rtattr_parse(tb, TCA_DSMARK_MAX, RTA_DATA(opt),
+ RTA_PAYLOAD(opt)))
+ return -EINVAL;
+ if (tb[TCA_DSMARK_MASK-1]) {
+ if (!RTA_PAYLOAD(tb[TCA_DSMARK_MASK-1]))
+ return -EINVAL;
+ p->mask[*arg-1] = *(__u8 *) RTA_DATA(tb[TCA_DSMARK_MASK-1]);
+ }
+ if (tb[TCA_DSMARK_VALUE-1]) {
+ if (!RTA_PAYLOAD(tb[TCA_DSMARK_VALUE-1]))
+ return -EINVAL;
+ p->value[*arg-1] = *(__u8 *) RTA_DATA(tb[TCA_DSMARK_VALUE-1]);
+ }
+ return 0;
+}
+
+
+static int dsmark_delete(struct Qdisc *sch,unsigned long arg)
+{
+ struct dsmark_qdisc_data *p = PRIV(sch);
+
+ if (!arg || arg > p->indices)
+ return -EINVAL;
+ p->mask[arg-1] = 0xff;
+ p->value[arg-1] = 0;
+ return 0;
+}
+
+
+static void dsmark_walk(struct Qdisc *sch,struct qdisc_walker *walker)
+{
+ struct dsmark_qdisc_data *p = PRIV(sch);
+ int i;
+
+ DPRINTK("dsmark_walk(sch %p,[qdisc %p],walker %p)\n",sch,p,walker);
+ if (walker->stop)
+ return;
+ for (i = 0; i < p->indices; i++) {
+ if (p->mask[i] == 0xff && !p->value[i])
+ continue;
+ if (walker->count >= walker->skip) {
+ if (walker->fn(sch, i+1, walker) < 0) {
+ walker->stop = 1;
+ break;
+ }
+ }
+ walker->count++;
+ }
+}
+
+
+static struct tcf_proto **dsmark_find_tcf(struct Qdisc *sch,unsigned long cl)
+{
+ struct dsmark_qdisc_data *p = PRIV(sch);
+
+ return &p->filter_list;
+}
+
+
+/* --------------------------- Qdisc operations ---------------------------- */
+
+
+static int dsmark_enqueue(struct sk_buff *skb,struct Qdisc *sch)
+{
+ struct dsmark_qdisc_data *p = PRIV(sch);
+ struct tcf_result res;
+ int result;
+ int ret = NET_XMIT_POLICED;
+
+ D2PRINTK("dsmark_enqueue(skb %p,sch %p,[qdisc %p])\n",skb,sch,p);
+ if (p->set_tc_index) {
+ switch (skb->protocol) {
+ case __constant_htons(ETH_P_IP):
+ skb->tc_index = ipv4_get_dsfield(skb->nh.iph)
+ & ~INET_ECN_MASK;
+ break;
+ case __constant_htons(ETH_P_IPV6):
+ skb->tc_index = ipv6_get_dsfield(skb->nh.ipv6h)
+ & ~INET_ECN_MASK;
+ break;
+ default:
+ skb->tc_index = 0;
+ break;
+ };
+ }
+ result = TC_POLICE_OK; /* be nice to gcc */
+ if (TC_H_MAJ(skb->priority) == sch->handle) {
+ skb->tc_index = TC_H_MIN(skb->priority);
+ } else {
+ result = tc_classify(skb,p->filter_list,&res);
+ D2PRINTK("result %d class 0x%04x\n",result,res.classid);
+ switch (result) {
+#ifdef CONFIG_NET_CLS_POLICE
+ case TC_POLICE_SHOT:
+ kfree_skb(skb);
+ break;
+#if 0
+ case TC_POLICE_RECLASSIFY:
+ /* FIXME: what to do here ??? */
+#endif
+#endif
+ case TC_POLICE_OK:
+ skb->tc_index = TC_H_MIN(res.classid);
+ break;
+ case TC_POLICE_UNSPEC:
+ /* fall through */
+ default:
+ if (p->default_index != NO_DEFAULT_INDEX)
+ skb->tc_index = p->default_index;
+ break;
+ };
+ }
+ if (
+#ifdef CONFIG_NET_CLS_POLICE
+ result == TC_POLICE_SHOT ||
+#endif
+
+ ((ret = p->q->enqueue(skb,p->q)) != 0)) {
+ sch->stats.drops++;
+ return ret;
+ }
+ sch->stats.bytes += skb->len;
+ sch->stats.packets++;
+ sch->q.qlen++;
+ return ret;
+}
+
+
+static struct sk_buff *dsmark_dequeue(struct Qdisc *sch)
+{
+ struct dsmark_qdisc_data *p = PRIV(sch);
+ struct sk_buff *skb;
+ int index;
+
+ D2PRINTK("dsmark_dequeue(sch %p,[qdisc %p])\n",sch,p);
+ skb = p->q->ops->dequeue(p->q);
+ if (!skb)
+ return NULL;
+ sch->q.qlen--;
+ index = skb->tc_index & (p->indices-1);
+ D2PRINTK("index %d->%d\n",skb->tc_index,index);
+ switch (skb->protocol) {
+ case __constant_htons(ETH_P_IP):
+ ipv4_change_dsfield(skb->nh.iph,
+ p->mask[index],p->value[index]);
+ break;
+ case __constant_htons(ETH_P_IPV6):
+ ipv6_change_dsfield(skb->nh.ipv6h,
+ p->mask[index],p->value[index]);
+ break;
+ default:
+ /*
+ * Only complain if a change was actually attempted.
+ * This way, we can send non-IP traffic through dsmark
+ * and don't need yet another qdisc as a bypass.
+ */
+ if (p->mask[index] != 0xff || p->value[index])
+ printk(KERN_WARNING "dsmark_dequeue: "
+ "unsupported protocol %d\n",
+ htons(skb->protocol));
+ break;
+ };
+ return skb;
+}
+
+
+static int dsmark_requeue(struct sk_buff *skb,struct Qdisc *sch)
+{
+ int ret;
+ struct dsmark_qdisc_data *p = PRIV(sch);
+
+ D2PRINTK("dsmark_requeue(skb %p,sch %p,[qdisc %p])\n",skb,sch,p);
+ if ((ret = p->q->ops->requeue(skb, p->q)) == 0) {
+ sch->q.qlen++;
+ return 0;
+ }
+ sch->stats.drops++;
+ return ret;
+}
+
+
+static unsigned int dsmark_drop(struct Qdisc *sch)
+{
+ struct dsmark_qdisc_data *p = PRIV(sch);
+ unsigned int len;
+
+ DPRINTK("dsmark_reset(sch %p,[qdisc %p])\n",sch,p);
+ if (!p->q->ops->drop)
+ return 0;
+ if (!(len = p->q->ops->drop(p->q)))
+ return 0;
+ sch->q.qlen--;
+ return len;
+}
+
+
+int dsmark_init(struct Qdisc *sch,struct rtattr *opt)
+{
+ struct dsmark_qdisc_data *p = PRIV(sch);
+ struct rtattr *tb[TCA_DSMARK_MAX];
+ __u16 tmp;
+
+ DPRINTK("dsmark_init(sch %p,[qdisc %p],opt %p)\n",sch,p,opt);
+ if (!opt ||
+ rtattr_parse(tb,TCA_DSMARK_MAX,RTA_DATA(opt),RTA_PAYLOAD(opt)) < 0 ||
+ !tb[TCA_DSMARK_INDICES-1] ||
+ RTA_PAYLOAD(tb[TCA_DSMARK_INDICES-1]) < sizeof(__u16))
+ return -EINVAL;
+ p->indices = *(__u16 *) RTA_DATA(tb[TCA_DSMARK_INDICES-1]);
+ if (!p->indices)
+ return -EINVAL;
+ for (tmp = p->indices; tmp != 1; tmp >>= 1) {
+ if (tmp & 1)
+ return -EINVAL;
+ }
+ p->default_index = NO_DEFAULT_INDEX;
+ if (tb[TCA_DSMARK_DEFAULT_INDEX-1]) {
+ if (RTA_PAYLOAD(tb[TCA_DSMARK_DEFAULT_INDEX-1]) < sizeof(__u16))
+ return -EINVAL;
+ p->default_index =
+ *(__u16 *) RTA_DATA(tb[TCA_DSMARK_DEFAULT_INDEX-1]);
+ }
+ p->set_tc_index = !!tb[TCA_DSMARK_SET_TC_INDEX-1];
+ p->mask = kmalloc(p->indices*2,GFP_KERNEL);
+ if (!p->mask)
+ return -ENOMEM;
+ p->value = p->mask+p->indices;
+ memset(p->mask,0xff,p->indices);
+ memset(p->value,0,p->indices);
+ if (!(p->q = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops)))
+ p->q = &noop_qdisc;
+ DPRINTK("dsmark_init: qdisc %p\n",&p->q);
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+
+static void dsmark_reset(struct Qdisc *sch)
+{
+ struct dsmark_qdisc_data *p = PRIV(sch);
+
+ DPRINTK("dsmark_reset(sch %p,[qdisc %p])\n",sch,p);
+ qdisc_reset(p->q);
+ sch->q.qlen = 0;
+}
+
+
+static void dsmark_destroy(struct Qdisc *sch)
+{
+ struct dsmark_qdisc_data *p = PRIV(sch);
+ struct tcf_proto *tp;
+
+ DPRINTK("dsmark_destroy(sch %p,[qdisc %p])\n",sch,p);
+ while (p->filter_list) {
+ tp = p->filter_list;
+ p->filter_list = tp->next;
+ tcf_destroy(tp);
+ }
+ qdisc_destroy(p->q);
+ p->q = &noop_qdisc;
+ kfree(p->mask);
+ MOD_DEC_USE_COUNT;
+}
+
+
+static int dsmark_dump_class(struct Qdisc *sch, unsigned long cl,
+ struct sk_buff *skb, struct tcmsg *tcm)
+{
+ struct dsmark_qdisc_data *p = PRIV(sch);
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+
+ DPRINTK("dsmark_dump_class(sch %p,[qdisc %p],class %ld\n",sch,p,cl);
+ if (!cl || cl > p->indices)
+ return -EINVAL;
+ tcm->tcm_handle = TC_H_MAKE(TC_H_MAJ(sch->handle),cl-1);
+ rta = (struct rtattr *) b;
+ RTA_PUT(skb,TCA_OPTIONS,0,NULL);
+ RTA_PUT(skb,TCA_DSMARK_MASK,1,&p->mask[cl-1]);
+ RTA_PUT(skb,TCA_DSMARK_VALUE,1,&p->value[cl-1]);
+ rta->rta_len = skb->tail-b;
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb,b-skb->data);
+ return -1;
+}
+
+static int dsmark_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+ struct dsmark_qdisc_data *p = PRIV(sch);
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+
+ rta = (struct rtattr *) b;
+ RTA_PUT(skb,TCA_OPTIONS,0,NULL);
+ RTA_PUT(skb,TCA_DSMARK_INDICES,sizeof(__u16),&p->indices);
+ if (p->default_index != NO_DEFAULT_INDEX) {
+ __u16 tmp = p->default_index;
+
+ RTA_PUT(skb,TCA_DSMARK_DEFAULT_INDEX, sizeof(__u16), &tmp);
+ }
+ if (p->set_tc_index)
+ RTA_PUT(skb, TCA_DSMARK_SET_TC_INDEX, 0, NULL);
+ rta->rta_len = skb->tail-b;
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb,b-skb->data);
+ return -1;
+}
+
+static struct Qdisc_class_ops dsmark_class_ops =
+{
+ dsmark_graft, /* graft */
+ dsmark_leaf, /* leaf */
+ dsmark_get, /* get */
+ dsmark_put, /* put */
+ dsmark_change, /* change */
+ dsmark_delete, /* delete */
+ dsmark_walk, /* walk */
+
+ dsmark_find_tcf, /* tcf_chain */
+ dsmark_bind_filter, /* bind_tcf */
+ dsmark_put, /* unbind_tcf */
+
+ dsmark_dump_class, /* dump */
+};
+
+struct Qdisc_ops dsmark_qdisc_ops =
+{
+ NULL, /* next */
+ &dsmark_class_ops, /* cl_ops */
+ "dsmark",
+ sizeof(struct dsmark_qdisc_data),
+
+ dsmark_enqueue, /* enqueue */
+ dsmark_dequeue, /* dequeue */
+ dsmark_requeue, /* requeue */
+ dsmark_drop, /* drop */
+
+ dsmark_init, /* init */
+ dsmark_reset, /* reset */
+ dsmark_destroy, /* destroy */
+ NULL, /* change */
+
+ dsmark_dump /* dump */
+};
+
+#ifdef MODULE
+int init_module(void)
+{
+ return register_qdisc(&dsmark_qdisc_ops);
+}
+
+
+void cleanup_module(void)
+{
+ unregister_qdisc(&dsmark_qdisc_ops);
+}
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_fifo.c b/uClinux-2.4.31-uc0/net/sched/sch_fifo.c
new file mode 100644
index 0000000..e5b903d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_fifo.c
@@ -0,0 +1,211 @@
+/*
+ * net/sched/sch_fifo.c The simplest FIFO queue.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <linux/config.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+/* 1 band FIFO pseudo-"scheduler" */
+
+struct fifo_sched_data
+{
+ unsigned limit;
+};
+
+static int
+bfifo_enqueue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct fifo_sched_data *q = (struct fifo_sched_data *)sch->data;
+
+ if (sch->stats.backlog + skb->len <= q->limit) {
+ __skb_queue_tail(&sch->q, skb);
+ sch->stats.backlog += skb->len;
+ sch->stats.bytes += skb->len;
+ sch->stats.packets++;
+ return 0;
+ }
+ sch->stats.drops++;
+#ifdef CONFIG_NET_CLS_POLICE
+ if (sch->reshape_fail==NULL || sch->reshape_fail(skb, sch))
+#endif
+ kfree_skb(skb);
+ return NET_XMIT_DROP;
+}
+
+static int
+bfifo_requeue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ __skb_queue_head(&sch->q, skb);
+ sch->stats.backlog += skb->len;
+ return 0;
+}
+
+static struct sk_buff *
+bfifo_dequeue(struct Qdisc* sch)
+{
+ struct sk_buff *skb;
+
+ skb = __skb_dequeue(&sch->q);
+ if (skb)
+ sch->stats.backlog -= skb->len;
+ return skb;
+}
+
+static unsigned int
+fifo_drop(struct Qdisc* sch)
+{
+ struct sk_buff *skb;
+
+ skb = __skb_dequeue_tail(&sch->q);
+ if (skb) {
+ unsigned int len = skb->len;
+ sch->stats.backlog -= len;
+ kfree_skb(skb);
+ return len;
+ }
+ return 0;
+}
+
+static void
+fifo_reset(struct Qdisc* sch)
+{
+ skb_queue_purge(&sch->q);
+ sch->stats.backlog = 0;
+}
+
+static int
+pfifo_enqueue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct fifo_sched_data *q = (struct fifo_sched_data *)sch->data;
+
+ if (sch->q.qlen < q->limit) {
+ __skb_queue_tail(&sch->q, skb);
+ sch->stats.bytes += skb->len;
+ sch->stats.packets++;
+ return 0;
+ }
+ sch->stats.drops++;
+#ifdef CONFIG_NET_CLS_POLICE
+ if (sch->reshape_fail==NULL || sch->reshape_fail(skb, sch))
+#endif
+ kfree_skb(skb);
+ return NET_XMIT_DROP;
+}
+
+static int
+pfifo_requeue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ __skb_queue_head(&sch->q, skb);
+ return 0;
+}
+
+
+static struct sk_buff *
+pfifo_dequeue(struct Qdisc* sch)
+{
+ return __skb_dequeue(&sch->q);
+}
+
+static int fifo_init(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct fifo_sched_data *q = (void*)sch->data;
+
+ if (opt == NULL) {
+ unsigned int limit = sch->dev->tx_queue_len ? : 1;
+
+ if (sch->ops == &bfifo_qdisc_ops)
+ q->limit = limit*sch->dev->mtu;
+ else
+ q->limit = limit;
+ } else {
+ struct tc_fifo_qopt *ctl = RTA_DATA(opt);
+ if (opt->rta_len < RTA_LENGTH(sizeof(*ctl)))
+ return -EINVAL;
+ q->limit = ctl->limit;
+ }
+ return 0;
+}
+
+static int fifo_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+ struct fifo_sched_data *q = (void*)sch->data;
+ unsigned char *b = skb->tail;
+ struct tc_fifo_qopt opt;
+
+ opt.limit = q->limit;
+ RTA_PUT(skb, TCA_OPTIONS, sizeof(opt), &opt);
+
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+struct Qdisc_ops pfifo_qdisc_ops =
+{
+ NULL,
+ NULL,
+ "pfifo",
+ sizeof(struct fifo_sched_data),
+
+ pfifo_enqueue,
+ pfifo_dequeue,
+ pfifo_requeue,
+ fifo_drop,
+
+ fifo_init,
+ fifo_reset,
+ NULL,
+ fifo_init,
+
+ fifo_dump,
+};
+
+struct Qdisc_ops bfifo_qdisc_ops =
+{
+ NULL,
+ NULL,
+ "bfifo",
+ sizeof(struct fifo_sched_data),
+
+ bfifo_enqueue,
+ bfifo_dequeue,
+ bfifo_requeue,
+ fifo_drop,
+
+ fifo_init,
+ fifo_reset,
+ NULL,
+ fifo_init,
+ fifo_dump,
+};
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_generic.c b/uClinux-2.4.31-uc0/net/sched/sch_generic.c
new file mode 100644
index 0000000..ac74cdc
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_generic.c
@@ -0,0 +1,537 @@
+/*
+ * net/sched/sch_generic.c Generic packet scheduler routines.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ * Jamal Hadi Salim, <hadi@cyberus.ca> 990601
+ * - Ingress support
+ */
+
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/init.h>
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
+#include <linux/imq.h>
+#endif
+#include <linux/list.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+/* Main transmission queue. */
+
+/* Main qdisc structure lock.
+
+ However, modifications
+ to data, participating in scheduling must be additionally
+ protected with dev->queue_lock spinlock.
+
+ The idea is the following:
+ - enqueue, dequeue are serialized via top level device
+ spinlock dev->queue_lock.
+ - tree walking is protected by read_lock(qdisc_tree_lock)
+ and this lock is used only in process context.
+ - updates to tree are made only under rtnl semaphore,
+ hence this lock may be made without local bh disabling.
+
+ qdisc_tree_lock must be grabbed BEFORE dev->queue_lock!
+ */
+rwlock_t qdisc_tree_lock = RW_LOCK_UNLOCKED;
+
+/*
+ dev->queue_lock serializes queue accesses for this device
+ AND dev->qdisc pointer itself.
+
+ dev->xmit_lock serializes accesses to device driver.
+
+ dev->queue_lock and dev->xmit_lock are mutually exclusive,
+ if one is grabbed, another must be free.
+ */
+
+
+/* Kick device.
+ Note, that this procedure can be called by a watchdog timer, so that
+ we do not check dev->tbusy flag here.
+
+ Returns: 0 - queue is empty.
+ >0 - queue is not empty, but throttled.
+ <0 - queue is not empty. Device is throttled, if dev->tbusy != 0.
+
+ NOTE: Called under dev->queue_lock with locally disabled BH.
+*/
+
+int qdisc_restart(struct net_device *dev)
+{
+ struct Qdisc *q = dev->qdisc;
+ struct sk_buff *skb;
+
+ /* Dequeue packet */
+ if ((skb = q->dequeue(q)) != NULL) {
+ if (spin_trylock(&dev->xmit_lock)) {
+ /* Remember that the driver is grabbed by us. */
+ dev->xmit_lock_owner = smp_processor_id();
+
+ /* And release queue */
+ spin_unlock(&dev->queue_lock);
+
+ if (!netif_queue_stopped(dev)) {
+ if (netdev_nit
+#if defined(CONFIG_IMQ) || defined(CONFIG_IMQ_MODULE)
+ && !(skb->imq_flags & IMQ_F_ENQUEUE)
+#endif
+ )
+ dev_queue_xmit_nit(skb, dev);
+
+ if (dev->hard_start_xmit(skb, dev) == 0) {
+ dev->xmit_lock_owner = -1;
+ spin_unlock(&dev->xmit_lock);
+
+ spin_lock(&dev->queue_lock);
+ return -1;
+ }
+ }
+
+ /* Release the driver */
+ dev->xmit_lock_owner = -1;
+ spin_unlock(&dev->xmit_lock);
+ spin_lock(&dev->queue_lock);
+ q = dev->qdisc;
+ } else {
+ /* So, someone grabbed the driver. */
+
+ /* It may be transient configuration error,
+ when hard_start_xmit() recurses. We detect
+ it by checking xmit owner and drop the
+ packet when deadloop is detected.
+ */
+ if (dev->xmit_lock_owner == smp_processor_id()) {
+ kfree_skb(skb);
+ if (net_ratelimit())
+ printk(KERN_DEBUG "Dead loop on netdevice %s, fix it urgently!\n", dev->name);
+ return -1;
+ }
+ netdev_rx_stat[smp_processor_id()].cpu_collision++;
+ }
+
+ /* Device kicked us out :(
+ This is possible in three cases:
+
+ 0. driver is locked
+ 1. fastroute is enabled
+ 2. device cannot determine busy state
+ before start of transmission (f.e. dialout)
+ 3. device is buggy (ppp)
+ */
+
+ q->ops->requeue(skb, q);
+ netif_schedule(dev);
+ return 1;
+ }
+ return q->q.qlen;
+}
+
+static void dev_watchdog(unsigned long arg)
+{
+ struct net_device *dev = (struct net_device *)arg;
+
+ spin_lock(&dev->xmit_lock);
+ if (dev->qdisc != &noop_qdisc) {
+ if (netif_device_present(dev) &&
+ netif_running(dev) &&
+ netif_carrier_ok(dev)) {
+ if (netif_queue_stopped(dev) &&
+ (jiffies - dev->trans_start) > dev->watchdog_timeo) {
+ printk(KERN_INFO "NETDEV WATCHDOG: %s: transmit timed out\n", dev->name);
+ dev->tx_timeout(dev);
+ }
+ if (!mod_timer(&dev->watchdog_timer, jiffies + dev->watchdog_timeo))
+ dev_hold(dev);
+ }
+ }
+ spin_unlock(&dev->xmit_lock);
+
+ dev_put(dev);
+}
+
+static void dev_watchdog_init(struct net_device *dev)
+{
+ init_timer(&dev->watchdog_timer);
+ dev->watchdog_timer.data = (unsigned long)dev;
+ dev->watchdog_timer.function = dev_watchdog;
+}
+
+void __netdev_watchdog_up(struct net_device *dev)
+{
+ if (dev->tx_timeout) {
+ if (dev->watchdog_timeo <= 0)
+ dev->watchdog_timeo = 5*HZ;
+ if (!mod_timer(&dev->watchdog_timer, jiffies + dev->watchdog_timeo))
+ dev_hold(dev);
+ }
+}
+
+static void dev_watchdog_up(struct net_device *dev)
+{
+ spin_lock_bh(&dev->xmit_lock);
+ __netdev_watchdog_up(dev);
+ spin_unlock_bh(&dev->xmit_lock);
+}
+
+static void dev_watchdog_down(struct net_device *dev)
+{
+ spin_lock_bh(&dev->xmit_lock);
+ if (del_timer(&dev->watchdog_timer))
+ __dev_put(dev);
+ spin_unlock_bh(&dev->xmit_lock);
+}
+
+/* "NOOP" scheduler: the best scheduler, recommended for all interfaces
+ under all circumstances. It is difficult to invent anything faster or
+ cheaper.
+ */
+
+static int
+noop_enqueue(struct sk_buff *skb, struct Qdisc * qdisc)
+{
+ kfree_skb(skb);
+ return NET_XMIT_CN;
+}
+
+static struct sk_buff *
+noop_dequeue(struct Qdisc * qdisc)
+{
+ return NULL;
+}
+
+static int
+noop_requeue(struct sk_buff *skb, struct Qdisc* qdisc)
+{
+ if (net_ratelimit())
+ printk(KERN_DEBUG "%s deferred output. It is buggy.\n", skb->dev->name);
+ kfree_skb(skb);
+ return NET_XMIT_CN;
+}
+
+struct Qdisc_ops noop_qdisc_ops =
+{
+ NULL,
+ NULL,
+ "noop",
+ 0,
+
+ noop_enqueue,
+ noop_dequeue,
+ noop_requeue,
+};
+
+struct Qdisc noop_qdisc =
+{
+ noop_enqueue,
+ noop_dequeue,
+ TCQ_F_BUILTIN,
+ &noop_qdisc_ops,
+};
+
+
+struct Qdisc_ops noqueue_qdisc_ops =
+{
+ NULL,
+ NULL,
+ "noqueue",
+ 0,
+
+ noop_enqueue,
+ noop_dequeue,
+ noop_requeue,
+
+};
+
+struct Qdisc noqueue_qdisc =
+{
+ NULL,
+ noop_dequeue,
+ TCQ_F_BUILTIN,
+ &noqueue_qdisc_ops,
+};
+
+
+static const u8 prio2band[TC_PRIO_MAX+1] =
+{ 1, 2, 2, 2, 1, 2, 0, 0 , 1, 1, 1, 1, 1, 1, 1, 1 };
+
+/* 3-band FIFO queue: old style, but should be a bit faster than
+ generic prio+fifo combination.
+ */
+
+static int
+pfifo_fast_enqueue(struct sk_buff *skb, struct Qdisc* qdisc)
+{
+ struct sk_buff_head *list;
+
+ list = ((struct sk_buff_head*)qdisc->data) +
+ prio2band[skb->priority&TC_PRIO_MAX];
+
+ if (list->qlen < qdisc->dev->tx_queue_len) {
+ __skb_queue_tail(list, skb);
+ qdisc->q.qlen++;
+ qdisc->stats.bytes += skb->len;
+ qdisc->stats.packets++;
+ return 0;
+ }
+ qdisc->stats.drops++;
+ kfree_skb(skb);
+ return NET_XMIT_DROP;
+}
+
+static struct sk_buff *
+pfifo_fast_dequeue(struct Qdisc* qdisc)
+{
+ int prio;
+ struct sk_buff_head *list = ((struct sk_buff_head*)qdisc->data);
+ struct sk_buff *skb;
+
+ for (prio = 0; prio < 3; prio++, list++) {
+ skb = __skb_dequeue(list);
+ if (skb) {
+ qdisc->q.qlen--;
+ return skb;
+ }
+ }
+ return NULL;
+}
+
+static int
+pfifo_fast_requeue(struct sk_buff *skb, struct Qdisc* qdisc)
+{
+ struct sk_buff_head *list;
+
+ list = ((struct sk_buff_head*)qdisc->data) +
+ prio2band[skb->priority&TC_PRIO_MAX];
+
+ __skb_queue_head(list, skb);
+ qdisc->q.qlen++;
+ return 0;
+}
+
+static void
+pfifo_fast_reset(struct Qdisc* qdisc)
+{
+ int prio;
+ struct sk_buff_head *list = ((struct sk_buff_head*)qdisc->data);
+
+ for (prio=0; prio < 3; prio++)
+ skb_queue_purge(list+prio);
+ qdisc->q.qlen = 0;
+}
+
+static int pfifo_fast_dump(struct Qdisc *qdisc, struct sk_buff *skb)
+{
+ unsigned char *b = skb->tail;
+ struct tc_prio_qopt opt;
+
+ opt.bands = 3;
+ memcpy(&opt.priomap, prio2band, TC_PRIO_MAX+1);
+ RTA_PUT(skb, TCA_OPTIONS, sizeof(opt), &opt);
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int pfifo_fast_init(struct Qdisc *qdisc, struct rtattr *opt)
+{
+ int i;
+ struct sk_buff_head *list;
+
+ list = ((struct sk_buff_head*)qdisc->data);
+
+ for (i=0; i<3; i++)
+ skb_queue_head_init(list+i);
+
+ return 0;
+}
+
+static struct Qdisc_ops pfifo_fast_ops =
+{
+ NULL,
+ NULL,
+ "pfifo_fast",
+ 3 * sizeof(struct sk_buff_head),
+
+ pfifo_fast_enqueue,
+ pfifo_fast_dequeue,
+ pfifo_fast_requeue,
+ NULL,
+
+ pfifo_fast_init,
+ pfifo_fast_reset,
+ NULL,
+ NULL,
+ pfifo_fast_dump,
+
+};
+
+struct Qdisc * qdisc_create_dflt(struct net_device *dev, struct Qdisc_ops *ops)
+{
+ struct Qdisc *sch;
+ int size = sizeof(*sch) + ops->priv_size;
+
+ sch = kmalloc(size, GFP_KERNEL);
+ if (!sch)
+ return NULL;
+ memset(sch, 0, size);
+
+ INIT_LIST_HEAD(&sch->list);
+ skb_queue_head_init(&sch->q);
+ sch->ops = ops;
+ sch->enqueue = ops->enqueue;
+ sch->dequeue = ops->dequeue;
+ sch->dev = dev;
+ sch->stats.lock = &dev->queue_lock;
+ atomic_set(&sch->refcnt, 1);
+ if (!ops->init || ops->init(sch, NULL) == 0)
+ return sch;
+
+ kfree(sch);
+ return NULL;
+}
+
+/* Under dev->queue_lock and BH! */
+
+void qdisc_reset(struct Qdisc *qdisc)
+{
+ struct Qdisc_ops *ops = qdisc->ops;
+
+ if (ops->reset)
+ ops->reset(qdisc);
+}
+
+/* Under dev->queue_lock and BH! */
+
+void qdisc_destroy(struct Qdisc *qdisc)
+{
+ struct Qdisc_ops *ops = qdisc->ops;
+
+ if (qdisc->flags&TCQ_F_BUILTIN ||
+ !atomic_dec_and_test(&qdisc->refcnt))
+ return;
+ list_del(&qdisc->list);
+#ifdef CONFIG_NET_ESTIMATOR
+ qdisc_kill_estimator(&qdisc->stats);
+#endif
+ if (ops->reset)
+ ops->reset(qdisc);
+ if (ops->destroy)
+ ops->destroy(qdisc);
+ kfree(qdisc);
+}
+
+
+void dev_activate(struct net_device *dev)
+{
+ /* No queueing discipline is attached to device;
+ create default one i.e. pfifo_fast for devices,
+ which need queueing and noqueue_qdisc for
+ virtual interfaces
+ */
+
+ if (dev->qdisc_sleeping == &noop_qdisc) {
+ struct Qdisc *qdisc;
+ if (dev->tx_queue_len) {
+ qdisc = qdisc_create_dflt(dev, &pfifo_fast_ops);
+ if (qdisc == NULL) {
+ printk(KERN_INFO "%s: activation failed\n", dev->name);
+ return;
+ }
+ write_lock(&qdisc_tree_lock);
+ list_add_tail(&qdisc->list, &dev->qdisc_list);
+ write_unlock(&qdisc_tree_lock);
+
+ } else {
+ qdisc = &noqueue_qdisc;
+ }
+ write_lock(&qdisc_tree_lock);
+ dev->qdisc_sleeping = qdisc;
+ write_unlock(&qdisc_tree_lock);
+ }
+
+ spin_lock_bh(&dev->queue_lock);
+ if ((dev->qdisc = dev->qdisc_sleeping) != &noqueue_qdisc) {
+ dev->trans_start = jiffies;
+ dev_watchdog_up(dev);
+ }
+ spin_unlock_bh(&dev->queue_lock);
+}
+
+void dev_deactivate(struct net_device *dev)
+{
+ struct Qdisc *qdisc;
+
+ spin_lock_bh(&dev->queue_lock);
+ qdisc = dev->qdisc;
+ dev->qdisc = &noop_qdisc;
+
+ qdisc_reset(qdisc);
+
+ spin_unlock_bh(&dev->queue_lock);
+
+ dev_watchdog_down(dev);
+
+ while (test_bit(__LINK_STATE_SCHED, &dev->state))
+ yield();
+
+ spin_unlock_wait(&dev->xmit_lock);
+}
+
+void dev_init_scheduler(struct net_device *dev)
+{
+ write_lock(&qdisc_tree_lock);
+ spin_lock_bh(&dev->queue_lock);
+ dev->qdisc = &noop_qdisc;
+ spin_unlock_bh(&dev->queue_lock);
+ dev->qdisc_sleeping = &noop_qdisc;
+ INIT_LIST_HEAD(&dev->qdisc_list);
+ write_unlock(&qdisc_tree_lock);
+
+ dev_watchdog_init(dev);
+}
+
+void dev_shutdown(struct net_device *dev)
+{
+ struct Qdisc *qdisc;
+
+ write_lock(&qdisc_tree_lock);
+ spin_lock_bh(&dev->queue_lock);
+ qdisc = dev->qdisc_sleeping;
+ dev->qdisc = &noop_qdisc;
+ dev->qdisc_sleeping = &noop_qdisc;
+ qdisc_destroy(qdisc);
+#if defined(CONFIG_NET_SCH_INGRESS) || defined(CONFIG_NET_SCH_INGRESS_MODULE)
+ if ((qdisc = dev->qdisc_ingress) != NULL) {
+ dev->qdisc_ingress = NULL;
+ qdisc_destroy(qdisc);
+ }
+#endif
+ BUG_TRAP(list_empty(&dev->qdisc_list));
+ BUG_TRAP(!timer_pending(&dev->watchdog_timer));
+ spin_unlock_bh(&dev->queue_lock);
+ write_unlock(&qdisc_tree_lock);
+}
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_gred.c b/uClinux-2.4.31-uc0/net/sched/sch_gred.c
new file mode 100644
index 0000000..f89f6c8
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_gred.c
@@ -0,0 +1,637 @@
+/*
+ * net/sched/sch_gred.c Generic Random Early Detection queue.
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: J Hadi Salim (hadi@cyberus.ca) 1998-2002
+ *
+ * 991129: - Bug fix with grio mode
+ * - a better sing. AvgQ mode with Grio(WRED)
+ * - A finer grained VQ dequeue based on sugestion
+ * from Ren Liu
+ * - More error checks
+ *
+ *
+ *
+ * For all the glorious comments look at Alexey's sch_red.c
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+#if 1 /* control */
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+#if 0 /* data */
+#define D2PRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define D2PRINTK(format,args...)
+#endif
+
+struct gred_sched_data;
+struct gred_sched;
+
+struct gred_sched_data
+{
+/* Parameters */
+ u32 limit; /* HARD maximal queue length */
+ u32 qth_min; /* Min average length threshold: A scaled */
+ u32 qth_max; /* Max average length threshold: A scaled */
+ u32 DP; /* the drop pramaters */
+ char Wlog; /* log(W) */
+ char Plog; /* random number bits */
+ u32 Scell_max;
+ u32 Rmask;
+ u32 bytesin; /* bytes seen on virtualQ so far*/
+ u32 packetsin; /* packets seen on virtualQ so far*/
+ u32 backlog; /* bytes on the virtualQ */
+ u32 forced; /* packets dropped for exceeding limits */
+ u32 early; /* packets dropped as a warning */
+ u32 other; /* packets dropped by invoking drop() */
+ u32 pdrop; /* packets dropped because we exceeded physical queue limits */
+ char Scell_log;
+ u8 Stab[256];
+ u8 prio; /* the prio of this vq */
+
+/* Variables */
+ unsigned long qave; /* Average queue length: A scaled */
+ int qcount; /* Packets since last random number generation */
+ u32 qR; /* Cached random number */
+
+ psched_time_t qidlestart; /* Start of idle period */
+};
+
+struct gred_sched
+{
+ struct gred_sched_data *tab[MAX_DPs];
+ u32 DPs;
+ u32 def;
+ u8 initd;
+ u8 grio;
+ u8 eqp;
+};
+
+static int
+gred_enqueue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ psched_time_t now;
+ struct gred_sched_data *q=NULL;
+ struct gred_sched *t= (struct gred_sched *)sch->data;
+ unsigned long qave=0;
+ int i=0;
+
+ if (!t->initd && skb_queue_len(&sch->q) < (sch->dev->tx_queue_len ? : 1)) {
+ D2PRINTK("NO GRED Queues setup yet! Enqueued anyway\n");
+ goto do_enqueue;
+ }
+
+
+ if ( ((skb->tc_index&0xf) > (t->DPs -1)) || !(q=t->tab[skb->tc_index&0xf])) {
+ printk("GRED: setting to default (%d)\n ",t->def);
+ if (!(q=t->tab[t->def])) {
+ DPRINTK("GRED: setting to default FAILED! dropping!! "
+ "(%d)\n ", t->def);
+ goto drop;
+ }
+ /* fix tc_index? --could be controvesial but needed for
+ requeueing */
+ skb->tc_index=(skb->tc_index&0xfffffff0) | t->def;
+ }
+
+ D2PRINTK("gred_enqueue virtualQ 0x%x classid %x backlog %d "
+ "general backlog %d\n",skb->tc_index&0xf,sch->handle,q->backlog,
+ sch->stats.backlog);
+ /* sum up all the qaves of prios <= to ours to get the new qave*/
+ if (!t->eqp && t->grio) {
+ for (i=0;i<t->DPs;i++) {
+ if ((!t->tab[i]) || (i==q->DP))
+ continue;
+
+ if ((t->tab[i]->prio < q->prio) && (PSCHED_IS_PASTPERFECT(t->tab[i]->qidlestart)))
+ qave +=t->tab[i]->qave;
+ }
+
+ }
+
+ q->packetsin++;
+ q->bytesin+=skb->len;
+
+ if (t->eqp && t->grio) {
+ qave=0;
+ q->qave=t->tab[t->def]->qave;
+ q->qidlestart=t->tab[t->def]->qidlestart;
+ }
+
+ if (!PSCHED_IS_PASTPERFECT(q->qidlestart)) {
+ long us_idle;
+ PSCHED_GET_TIME(now);
+ us_idle = PSCHED_TDIFF_SAFE(now, q->qidlestart, q->Scell_max, 0);
+ PSCHED_SET_PASTPERFECT(q->qidlestart);
+
+ q->qave >>= q->Stab[(us_idle>>q->Scell_log)&0xFF];
+ } else {
+ if (t->eqp) {
+ q->qave += sch->stats.backlog - (q->qave >> q->Wlog);
+ } else {
+ q->qave += q->backlog - (q->qave >> q->Wlog);
+ }
+
+ }
+
+
+ if (t->eqp && t->grio)
+ t->tab[t->def]->qave=q->qave;
+
+ if ((q->qave+qave) < q->qth_min) {
+ q->qcount = -1;
+enqueue:
+ if (q->backlog + skb->len <= q->limit) {
+ q->backlog += skb->len;
+do_enqueue:
+ __skb_queue_tail(&sch->q, skb);
+ sch->stats.backlog += skb->len;
+ sch->stats.bytes += skb->len;
+ sch->stats.packets++;
+ return 0;
+ } else {
+ q->pdrop++;
+ }
+
+drop:
+ kfree_skb(skb);
+ sch->stats.drops++;
+ return NET_XMIT_DROP;
+ }
+ if ((q->qave+qave) >= q->qth_max) {
+ q->qcount = -1;
+ sch->stats.overlimits++;
+ q->forced++;
+ goto drop;
+ }
+ if (++q->qcount) {
+ if ((((qave+q->qave) - q->qth_min)>>q->Wlog)*q->qcount < q->qR)
+ goto enqueue;
+ q->qcount = 0;
+ q->qR = net_random()&q->Rmask;
+ sch->stats.overlimits++;
+ q->early++;
+ goto drop;
+ }
+ q->qR = net_random()&q->Rmask;
+ goto enqueue;
+}
+
+static int
+gred_requeue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct gred_sched_data *q;
+ struct gred_sched *t= (struct gred_sched *)sch->data;
+ q= t->tab[(skb->tc_index&0xf)];
+/* error checking here -- probably unnecessary */
+ PSCHED_SET_PASTPERFECT(q->qidlestart);
+
+ __skb_queue_head(&sch->q, skb);
+ sch->stats.backlog += skb->len;
+ q->backlog += skb->len;
+ return 0;
+}
+
+static struct sk_buff *
+gred_dequeue(struct Qdisc* sch)
+{
+ struct sk_buff *skb;
+ struct gred_sched_data *q;
+ struct gred_sched *t= (struct gred_sched *)sch->data;
+
+ skb = __skb_dequeue(&sch->q);
+ if (skb) {
+ sch->stats.backlog -= skb->len;
+ q= t->tab[(skb->tc_index&0xf)];
+ if (q) {
+ q->backlog -= skb->len;
+ if (!q->backlog && !t->eqp)
+ PSCHED_GET_TIME(q->qidlestart);
+ } else {
+ D2PRINTK("gred_dequeue: skb has bad tcindex %x\n",skb->tc_index&0xf);
+ }
+ return skb;
+ }
+
+ if (t->eqp) {
+ q= t->tab[t->def];
+ if (!q)
+ D2PRINTK("no default VQ set: Results will be "
+ "screwed up\n");
+ else
+ PSCHED_GET_TIME(q->qidlestart);
+ }
+
+ return NULL;
+}
+
+static unsigned int gred_drop(struct Qdisc* sch)
+{
+ struct sk_buff *skb;
+
+ struct gred_sched_data *q;
+ struct gred_sched *t= (struct gred_sched *)sch->data;
+
+ skb = __skb_dequeue_tail(&sch->q);
+ if (skb) {
+ unsigned int len = skb->len;
+ sch->stats.backlog -= len;
+ sch->stats.drops++;
+ q= t->tab[(skb->tc_index&0xf)];
+ if (q) {
+ q->backlog -= len;
+ q->other++;
+ if (!q->backlog && !t->eqp)
+ PSCHED_GET_TIME(q->qidlestart);
+ } else {
+ D2PRINTK("gred_dequeue: skb has bad tcindex %x\n",skb->tc_index&0xf);
+ }
+
+ kfree_skb(skb);
+ return len;
+ }
+
+ q=t->tab[t->def];
+ if (!q) {
+ D2PRINTK("no default VQ set: Results might be screwed up\n");
+ return 0;
+ }
+
+ PSCHED_GET_TIME(q->qidlestart);
+ return 0;
+
+}
+
+static void gred_reset(struct Qdisc* sch)
+{
+ int i;
+ struct gred_sched_data *q;
+ struct gred_sched *t= (struct gred_sched *)sch->data;
+
+ __skb_queue_purge(&sch->q);
+
+ sch->stats.backlog = 0;
+
+ for (i=0;i<t->DPs;i++) {
+ q= t->tab[i];
+ if (!q)
+ continue;
+ PSCHED_SET_PASTPERFECT(q->qidlestart);
+ q->qave = 0;
+ q->qcount = -1;
+ q->backlog = 0;
+ q->other=0;
+ q->forced=0;
+ q->pdrop=0;
+ q->early=0;
+ }
+}
+
+static int gred_change(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct gred_sched *table = (struct gred_sched *)sch->data;
+ struct gred_sched_data *q;
+ struct tc_gred_qopt *ctl;
+ struct tc_gred_sopt *sopt;
+ struct rtattr *tb[TCA_GRED_STAB];
+ struct rtattr *tb2[TCA_GRED_DPS];
+ int i;
+
+ if (opt == NULL ||
+ rtattr_parse(tb, TCA_GRED_STAB, RTA_DATA(opt), RTA_PAYLOAD(opt)) )
+ return -EINVAL;
+
+ if (tb[TCA_GRED_PARMS-1] == 0 && tb[TCA_GRED_STAB-1] == 0) {
+ rtattr_parse(tb2, TCA_GRED_DPS, RTA_DATA(opt),
+ RTA_PAYLOAD(opt));
+
+ if (tb2[TCA_GRED_DPS-1] == 0)
+ return -EINVAL;
+
+ sopt = RTA_DATA(tb2[TCA_GRED_DPS-1]);
+ table->DPs=sopt->DPs;
+ table->def=sopt->def_DP;
+ table->grio=sopt->grio;
+ table->initd=0;
+ /* probably need to clear all the table DP entries as well */
+ MOD_INC_USE_COUNT;
+ return 0;
+ }
+
+
+ if (!table->DPs || tb[TCA_GRED_PARMS-1] == 0 || tb[TCA_GRED_STAB-1] == 0 ||
+ RTA_PAYLOAD(tb[TCA_GRED_PARMS-1]) < sizeof(*ctl) ||
+ RTA_PAYLOAD(tb[TCA_GRED_STAB-1]) < 256)
+ return -EINVAL;
+
+ ctl = RTA_DATA(tb[TCA_GRED_PARMS-1]);
+ if (ctl->DP > MAX_DPs-1 ) {
+ /* misbehaving is punished! Put in the default drop probability */
+ DPRINTK("\nGRED: DP %u not in the proper range fixed. New DP "
+ "set to default at %d\n",ctl->DP,table->def);
+ ctl->DP=table->def;
+ }
+
+ if (table->tab[ctl->DP] == NULL) {
+ table->tab[ctl->DP]=kmalloc(sizeof(struct gred_sched_data),
+ GFP_KERNEL);
+ if (NULL == table->tab[ctl->DP])
+ return -ENOMEM;
+ memset(table->tab[ctl->DP], 0, (sizeof(struct gred_sched_data)));
+ }
+ q= table->tab[ctl->DP];
+
+ if (table->grio) {
+ if (ctl->prio <=0) {
+ if (table->def && table->tab[table->def]) {
+ DPRINTK("\nGRED: DP %u does not have a prio"
+ "setting default to %d\n",ctl->DP,
+ table->tab[table->def]->prio);
+ q->prio=table->tab[table->def]->prio;
+ } else {
+ DPRINTK("\nGRED: DP %u does not have a prio"
+ " setting default to 8\n",ctl->DP);
+ q->prio=8;
+ }
+ } else {
+ q->prio=ctl->prio;
+ }
+ } else {
+ q->prio=8;
+ }
+
+
+ q->DP=ctl->DP;
+ q->Wlog = ctl->Wlog;
+ q->Plog = ctl->Plog;
+ q->limit = ctl->limit;
+ q->Scell_log = ctl->Scell_log;
+ q->Rmask = ctl->Plog < 32 ? ((1<<ctl->Plog) - 1) : ~0UL;
+ q->Scell_max = (255<<q->Scell_log);
+ q->qth_min = ctl->qth_min<<ctl->Wlog;
+ q->qth_max = ctl->qth_max<<ctl->Wlog;
+ q->qave=0;
+ q->backlog=0;
+ q->qcount = -1;
+ q->other=0;
+ q->forced=0;
+ q->pdrop=0;
+ q->early=0;
+
+ PSCHED_SET_PASTPERFECT(q->qidlestart);
+ memcpy(q->Stab, RTA_DATA(tb[TCA_GRED_STAB-1]), 256);
+
+ if ( table->initd && table->grio) {
+ /* this looks ugly but its not in the fast path */
+ for (i=0;i<table->DPs;i++) {
+ if ((!table->tab[i]) || (i==q->DP) )
+ continue;
+ if (table->tab[i]->prio == q->prio ){
+ /* WRED mode detected */
+ table->eqp=1;
+ break;
+ }
+ }
+ }
+
+ if (!table->initd) {
+ table->initd=1;
+ /*
+ the first entry also goes into the default until
+ over-written
+ */
+
+ if (table->tab[table->def] == NULL) {
+ table->tab[table->def]=
+ kmalloc(sizeof(struct gred_sched_data), GFP_KERNEL);
+ if (NULL == table->tab[table->def])
+ return -ENOMEM;
+
+ memset(table->tab[table->def], 0,
+ (sizeof(struct gred_sched_data)));
+ }
+ q= table->tab[table->def];
+ q->DP=table->def;
+ q->Wlog = ctl->Wlog;
+ q->Plog = ctl->Plog;
+ q->limit = ctl->limit;
+ q->Scell_log = ctl->Scell_log;
+ q->Rmask = ctl->Plog < 32 ? ((1<<ctl->Plog) - 1) : ~0UL;
+ q->Scell_max = (255<<q->Scell_log);
+ q->qth_min = ctl->qth_min<<ctl->Wlog;
+ q->qth_max = ctl->qth_max<<ctl->Wlog;
+
+ if (table->grio)
+ q->prio=table->tab[ctl->DP]->prio;
+ else
+ q->prio=8;
+
+ q->qcount = -1;
+ PSCHED_SET_PASTPERFECT(q->qidlestart);
+ memcpy(q->Stab, RTA_DATA(tb[TCA_GRED_STAB-1]), 256);
+ }
+ return 0;
+
+}
+
+static int gred_init(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct gred_sched *table = (struct gred_sched *)sch->data;
+ struct tc_gred_sopt *sopt;
+ struct rtattr *tb[TCA_GRED_STAB];
+ struct rtattr *tb2[TCA_GRED_DPS];
+
+ if (opt == NULL ||
+ rtattr_parse(tb, TCA_GRED_STAB, RTA_DATA(opt), RTA_PAYLOAD(opt)) )
+ return -EINVAL;
+
+ if (tb[TCA_GRED_PARMS-1] == 0 && tb[TCA_GRED_STAB-1] == 0 ) {
+ rtattr_parse(tb2, TCA_GRED_DPS, RTA_DATA(opt),RTA_PAYLOAD(opt));
+
+ if (tb2[TCA_GRED_DPS-1] == 0)
+ return -EINVAL;
+
+ sopt = RTA_DATA(tb2[TCA_GRED_DPS-1]);
+ table->DPs=sopt->DPs;
+ table->def=sopt->def_DP;
+ table->grio=sopt->grio;
+ table->initd=0;
+ MOD_INC_USE_COUNT;
+ return 0;
+ }
+
+ DPRINTK("\n GRED_INIT error!\n");
+ return -EINVAL;
+}
+
+static int gred_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+ unsigned long qave;
+ struct rtattr *rta;
+ struct tc_gred_qopt *opt = NULL ;
+ struct tc_gred_qopt *dst;
+ struct gred_sched *table = (struct gred_sched *)sch->data;
+ struct gred_sched_data *q;
+ int i;
+ unsigned char *b = skb->tail;
+
+ rta = (struct rtattr*)b;
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+
+ opt=kmalloc(sizeof(struct tc_gred_qopt)*MAX_DPs, GFP_KERNEL);
+
+ if (opt == NULL) {
+ DPRINTK("gred_dump:failed to malloc for %Zd\n",
+ sizeof(struct tc_gred_qopt)*MAX_DPs);
+ goto rtattr_failure;
+ }
+
+ memset(opt, 0, (sizeof(struct tc_gred_qopt))*table->DPs);
+
+ if (!table->initd) {
+ DPRINTK("NO GRED Queues setup!\n");
+ }
+
+ for (i=0;i<MAX_DPs;i++) {
+ dst= &opt[i];
+ q= table->tab[i];
+
+ if (!q) {
+ /* hack -- fix at some point with proper message
+ This is how we indicate to tc that there is no VQ
+ at this DP */
+
+ dst->DP=MAX_DPs+i;
+ continue;
+ }
+
+ dst->limit=q->limit;
+ dst->qth_min=q->qth_min>>q->Wlog;
+ dst->qth_max=q->qth_max>>q->Wlog;
+ dst->DP=q->DP;
+ dst->backlog=q->backlog;
+ if (q->qave) {
+ if (table->eqp && table->grio) {
+ q->qidlestart=table->tab[table->def]->qidlestart;
+ q->qave=table->tab[table->def]->qave;
+ }
+ if (!PSCHED_IS_PASTPERFECT(q->qidlestart)) {
+ long idle;
+ psched_time_t now;
+ PSCHED_GET_TIME(now);
+ idle = PSCHED_TDIFF_SAFE(now, q->qidlestart, q->Scell_max, 0);
+ qave = q->qave >> q->Stab[(idle>>q->Scell_log)&0xFF];
+ dst->qave = qave >> q->Wlog;
+
+ } else {
+ dst->qave = q->qave >> q->Wlog;
+ }
+ } else {
+ dst->qave = 0;
+ }
+
+
+ dst->Wlog = q->Wlog;
+ dst->Plog = q->Plog;
+ dst->Scell_log = q->Scell_log;
+ dst->other = q->other;
+ dst->forced = q->forced;
+ dst->early = q->early;
+ dst->pdrop = q->pdrop;
+ dst->prio = q->prio;
+ dst->packets=q->packetsin;
+ dst->bytesin=q->bytesin;
+ }
+
+ RTA_PUT(skb, TCA_GRED_PARMS, sizeof(struct tc_gred_qopt)*MAX_DPs, opt);
+ rta->rta_len = skb->tail - b;
+
+ kfree(opt);
+ return skb->len;
+
+rtattr_failure:
+ if (opt)
+ kfree(opt);
+ DPRINTK("gred_dump: FAILURE!!!!\n");
+
+/* also free the opt struct here */
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static void gred_destroy(struct Qdisc *sch)
+{
+ struct gred_sched *table = (struct gred_sched *)sch->data;
+ int i;
+
+ for (i = 0;i < table->DPs; i++) {
+ if (table->tab[i])
+ kfree(table->tab[i]);
+ }
+ MOD_DEC_USE_COUNT;
+}
+
+struct Qdisc_ops gred_qdisc_ops =
+{
+ NULL,
+ NULL,
+ "gred",
+ sizeof(struct gred_sched),
+ gred_enqueue,
+ gred_dequeue,
+ gred_requeue,
+ gred_drop,
+ gred_init,
+ gred_reset,
+ gred_destroy,
+ gred_change, /* change */
+ gred_dump,
+};
+
+
+#ifdef MODULE
+int init_module(void)
+{
+ return register_qdisc(&gred_qdisc_ops);
+}
+
+void cleanup_module(void)
+{
+ unregister_qdisc(&gred_qdisc_ops);
+}
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_hfsc.c b/uClinux-2.4.31-uc0/net/sched/sch_hfsc.c
new file mode 100644
index 0000000..a163582
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_hfsc.c
@@ -0,0 +1,1817 @@
+/*
+ * Copyright (c) 2003 Patrick McHardy, <kaber@trash.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * 2003-10-17 - Ported from altq
+ */
+/*
+ * Copyright (c) 1997-1999 Carnegie Mellon University. All Rights Reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software and
+ * its documentation is hereby granted (including for commercial or
+ * for-profit use), provided that both the copyright notice and this
+ * permission notice appear in all copies of the software, derivative
+ * works, or modified versions, and any portions thereof.
+ *
+ * THIS SOFTWARE IS EXPERIMENTAL AND IS KNOWN TO HAVE BUGS, SOME OF
+ * WHICH MAY HAVE SERIOUS CONSEQUENCES. CARNEGIE MELLON PROVIDES THIS
+ * SOFTWARE IN ITS ``AS IS'' CONDITION, 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 CARNEGIE MELLON UNIVERSITY 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.
+ *
+ * Carnegie Mellon encourages (but does not require) users of this
+ * software to return any improvements or extensions that they make,
+ * and to grant Carnegie Mellon the rights to redistribute these
+ * changes without encumbrance.
+ */
+/*
+ * H-FSC is described in Proceedings of SIGCOMM'97,
+ * "A Hierarchical Fair Service Curve Algorithm for Link-Sharing,
+ * Real-Time and Priority Service"
+ * by Ion Stoica, Hui Zhang, and T. S. Eugene Ng.
+ *
+ * Oleg Cherevko <olwi@aq.ml.com.ua> added the upperlimit for link-sharing.
+ * when a class has an upperlimit, the fit-time is computed from the
+ * upperlimit service curve. the link-sharing scheduler does not schedule
+ * a class whose fit-time exceeds the current time.
+ */
+
+#include <linux/kernel.h>
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/compiler.h>
+#include <linux/spinlock.h>
+#include <linux/skbuff.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/pkt_sched.h>
+#include <net/pkt_sched.h>
+#include <net/pkt_cls.h>
+#include <asm/system.h>
+#include <asm/div64.h>
+
+#define HFSC_DEBUG 1
+
+/*
+ * kernel internal service curve representation:
+ * coordinates are given by 64 bit unsigned integers.
+ * x-axis: unit is clock count.
+ * y-axis: unit is byte.
+ *
+ * The service curve parameters are converted to the internal
+ * representation. The slope values are scaled to avoid overflow.
+ * the inverse slope values as well as the y-projection of the 1st
+ * segment are kept in order to to avoid 64-bit divide operations
+ * that are expensive on 32-bit architectures.
+ */
+
+struct internal_sc
+{
+ u64 sm1; /* scaled slope of the 1st segment */
+ u64 ism1; /* scaled inverse-slope of the 1st segment */
+ u64 dx; /* the x-projection of the 1st segment */
+ u64 dy; /* the y-projection of the 1st segment */
+ u64 sm2; /* scaled slope of the 2nd segment */
+ u64 ism2; /* scaled inverse-slope of the 2nd segment */
+};
+
+/* runtime service curve */
+struct runtime_sc
+{
+ u64 x; /* current starting position on x-axis */
+ u64 y; /* current starting position on y-axis */
+ u64 sm1; /* scaled slope of the 1st segment */
+ u64 ism1; /* scaled inverse-slope of the 1st segment */
+ u64 dx; /* the x-projection of the 1st segment */
+ u64 dy; /* the y-projection of the 1st segment */
+ u64 sm2; /* scaled slope of the 2nd segment */
+ u64 ism2; /* scaled inverse-slope of the 2nd segment */
+};
+
+enum hfsc_class_flags
+{
+ HFSC_RSC = 0x1,
+ HFSC_FSC = 0x2,
+ HFSC_USC = 0x4
+};
+
+struct hfsc_class
+{
+ u32 classid; /* class id */
+ unsigned int refcnt; /* usage count */
+
+ struct tc_stats stats; /* generic statistics */
+ unsigned int level; /* class level in hierarchy */
+ struct tcf_proto *filter_list; /* filter list */
+ unsigned int filter_cnt; /* filter count */
+
+ struct hfsc_sched *sched; /* scheduler data */
+ struct hfsc_class *cl_parent; /* parent class */
+ struct list_head siblings; /* sibling classes */
+ struct list_head children; /* child classes */
+ struct Qdisc *qdisc; /* leaf qdisc */
+
+ rb_node_t el_node; /* qdisc's eligible tree member */
+ rb_root_t vt_tree; /* active children sorted by cl_vt */
+ rb_node_t vt_node; /* parent's vt_tree member */
+ rb_root_t cf_tree; /* active children sorted by cl_f */
+ rb_node_t cf_node; /* parent's cf_heap member */
+ struct list_head hlist; /* hash list member */
+ struct list_head dlist; /* drop list member */
+
+ u64 cl_total; /* total work in bytes */
+ u64 cl_cumul; /* cumulative work in bytes done by
+ real-time criteria */
+
+ u64 cl_d; /* deadline*/
+ u64 cl_e; /* eligible time */
+ u64 cl_vt; /* virtual time */
+ u64 cl_f; /* time when this class will fit for
+ link-sharing, max(myf, cfmin) */
+ u64 cl_myf; /* my fit-time (calculated from this
+ class's own upperlimit curve) */
+ u64 cl_myfadj; /* my fit-time adjustment (to cancel
+ history dependence) */
+ u64 cl_cfmin; /* earliest children's fit-time (used
+ with cl_myf to obtain cl_f) */
+ u64 cl_cvtmin; /* minimal virtual time among the
+ children fit for link-sharing
+ (monotonic within a period) */
+ u64 cl_vtadj; /* intra-period cumulative vt
+ adjustment */
+ u64 cl_vtoff; /* inter-period cumulative vt offset */
+ u64 cl_cvtmax; /* max child's vt in the last period */
+ u64 cl_cvtoff; /* cumulative cvtmax of all periods */
+ u64 cl_pcvtoff; /* parent's cvtoff at initalization
+ time */
+
+ struct internal_sc cl_rsc; /* internal real-time service curve */
+ struct internal_sc cl_fsc; /* internal fair service curve */
+ struct internal_sc cl_usc; /* internal upperlimit service curve */
+ struct runtime_sc cl_deadline; /* deadline curve */
+ struct runtime_sc cl_eligible; /* eligible curve */
+ struct runtime_sc cl_virtual; /* virtual curve */
+ struct runtime_sc cl_ulimit; /* upperlimit curve */
+
+ unsigned long cl_flags; /* which curves are valid */
+ unsigned long cl_vtperiod; /* vt period sequence number */
+ unsigned long cl_parentperiod;/* parent's vt period sequence number*/
+ unsigned long cl_nactive; /* number of active children */
+};
+
+#define HFSC_HSIZE 16
+
+struct hfsc_sched
+{
+ u16 defcls; /* default class id */
+ struct hfsc_class root; /* root class */
+ struct list_head clhash[HFSC_HSIZE]; /* class hash */
+ rb_root_t eligible; /* eligible tree */
+ struct list_head droplist; /* active leaf class list (for
+ dropping) */
+ struct sk_buff_head requeue; /* requeued packet */
+ struct timer_list wd_timer; /* watchdog timer */
+};
+
+/*
+ * macros
+ */
+#if PSCHED_CLOCK_SOURCE == PSCHED_GETTIMEOFDAY
+#include <linux/time.h>
+#undef PSCHED_GET_TIME
+#define PSCHED_GET_TIME(stamp) \
+do { \
+ struct timeval tv; \
+ do_gettimeofday(&tv); \
+ (stamp) = 1000000ULL * tv.tv_sec + tv.tv_usec; \
+} while (0)
+#endif
+
+#if HFSC_DEBUG
+#define ASSERT(cond) \
+do { \
+ if (unlikely(!(cond))) \
+ printk("assertion %s failed at %s:%i (%s)\n", \
+ #cond, __FILE__, __LINE__, __FUNCTION__); \
+} while (0)
+#else
+#define ASSERT(cond)
+#endif /* HFSC_DEBUG */
+
+#define HT_INFINITY 0xffffffffffffffffULL /* infinite time value */
+
+
+/*
+ * eligible tree holds backlogged classes being sorted by their eligible times.
+ * there is one eligible tree per hfsc instance.
+ */
+
+static void
+eltree_insert(struct hfsc_class *cl)
+{
+ rb_node_t **p = &cl->sched->eligible.rb_node;
+ rb_node_t *parent = NULL;
+ struct hfsc_class *cl1;
+
+ while (*p != NULL) {
+ parent = *p;
+ cl1 = rb_entry(parent, struct hfsc_class, el_node);
+ if (cl->cl_e >= cl1->cl_e)
+ p = &parent->rb_right;
+ else
+ p = &parent->rb_left;
+ }
+ rb_link_node(&cl->el_node, parent, p);
+ rb_insert_color(&cl->el_node, &cl->sched->eligible);
+}
+
+static inline void
+eltree_remove(struct hfsc_class *cl)
+{
+ rb_erase(&cl->el_node, &cl->sched->eligible);
+}
+
+static inline void
+eltree_update(struct hfsc_class *cl)
+{
+ eltree_remove(cl);
+ eltree_insert(cl);
+}
+
+/* find the class with the minimum deadline among the eligible classes */
+static inline struct hfsc_class *
+eltree_get_mindl(struct hfsc_sched *q, u64 cur_time)
+{
+ struct hfsc_class *p, *cl = NULL;
+ rb_node_t *n;
+
+ for (n = rb_first(&q->eligible); n != NULL; n = rb_next(n)) {
+ p = rb_entry(n, struct hfsc_class, el_node);
+ if (p->cl_e > cur_time)
+ break;
+ if (cl == NULL || p->cl_d < cl->cl_d)
+ cl = p;
+ }
+ return cl;
+}
+
+/* find the class with minimum eligible time among the eligible classes */
+static inline struct hfsc_class *
+eltree_get_minel(struct hfsc_sched *q)
+{
+ rb_node_t *n;
+
+ n = rb_first(&q->eligible);
+ if (n == NULL)
+ return NULL;
+ return rb_entry(n, struct hfsc_class, el_node);
+}
+
+/*
+ * vttree holds holds backlogged child classes being sorted by their virtual
+ * time. each intermediate class has one vttree.
+ */
+static void
+vttree_insert(struct hfsc_class *cl)
+{
+ rb_node_t **p = &cl->cl_parent->vt_tree.rb_node;
+ rb_node_t *parent = NULL;
+ struct hfsc_class *cl1;
+
+ while (*p != NULL) {
+ parent = *p;
+ cl1 = rb_entry(parent, struct hfsc_class, vt_node);
+ if (cl->cl_vt >= cl1->cl_vt)
+ p = &parent->rb_right;
+ else
+ p = &parent->rb_left;
+ }
+ rb_link_node(&cl->vt_node, parent, p);
+ rb_insert_color(&cl->vt_node, &cl->cl_parent->vt_tree);
+}
+
+static inline void
+vttree_remove(struct hfsc_class *cl)
+{
+ rb_erase(&cl->vt_node, &cl->cl_parent->vt_tree);
+}
+
+static inline void
+vttree_update(struct hfsc_class *cl)
+{
+ vttree_remove(cl);
+ vttree_insert(cl);
+}
+
+static inline struct hfsc_class *
+vttree_firstfit(struct hfsc_class *cl, u64 cur_time)
+{
+ struct hfsc_class *p;
+ rb_node_t *n;
+
+ for (n = rb_first(&cl->vt_tree); n != NULL; n = rb_next(n)) {
+ p = rb_entry(n, struct hfsc_class, vt_node);
+ if (p->cl_f <= cur_time)
+ return p;
+ }
+ return NULL;
+}
+
+/*
+ * get the leaf class with the minimum vt in the hierarchy
+ */
+static struct hfsc_class *
+vttree_get_minvt(struct hfsc_class *cl, u64 cur_time)
+{
+ /* if root-class's cfmin is bigger than cur_time nothing to do */
+ if (cl->cl_cfmin > cur_time)
+ return NULL;
+
+ while (cl->level > 0) {
+ cl = vttree_firstfit(cl, cur_time);
+ if (cl == NULL)
+ return NULL;
+ /*
+ * update parent's cl_cvtmin.
+ */
+ if (cl->cl_parent->cl_cvtmin < cl->cl_vt)
+ cl->cl_parent->cl_cvtmin = cl->cl_vt;
+ }
+ return cl;
+}
+
+static void
+cftree_insert(struct hfsc_class *cl)
+{
+ rb_node_t **p = &cl->cl_parent->cf_tree.rb_node;
+ rb_node_t *parent = NULL;
+ struct hfsc_class *cl1;
+
+ while (*p != NULL) {
+ parent = *p;
+ cl1 = rb_entry(parent, struct hfsc_class, cf_node);
+ if (cl->cl_f >= cl1->cl_f)
+ p = &parent->rb_right;
+ else
+ p = &parent->rb_left;
+ }
+ rb_link_node(&cl->cf_node, parent, p);
+ rb_insert_color(&cl->cf_node, &cl->cl_parent->cf_tree);
+}
+
+static inline void
+cftree_remove(struct hfsc_class *cl)
+{
+ rb_erase(&cl->cf_node, &cl->cl_parent->cf_tree);
+}
+
+static inline void
+cftree_update(struct hfsc_class *cl)
+{
+ cftree_remove(cl);
+ cftree_insert(cl);
+}
+
+/*
+ * service curve support functions
+ *
+ * external service curve parameters
+ * m: bps
+ * d: us
+ * internal service curve parameters
+ * sm: (bytes/psched_us) << SM_SHIFT
+ * ism: (psched_us/byte) << ISM_SHIFT
+ * dx: psched_us
+ *
+ * Time source resolution
+ * PSCHED_JIFFIES: for 48<=HZ<=1534 resolution is between 0.63us and 1.27us.
+ * PSCHED_CPU: resolution is between 0.5us and 1us.
+ * PSCHED_GETTIMEOFDAY: resolution is exactly 1us.
+ *
+ * sm and ism are scaled in order to keep effective digits.
+ * SM_SHIFT and ISM_SHIFT are selected to keep at least 4 effective
+ * digits in decimal using the following table.
+ *
+ * Note: We can afford the additional accuracy (altq hfsc keeps at most
+ * 3 effective digits) thanks to the fact that linux clock is bounded
+ * much more tightly.
+ *
+ * bits/sec 100Kbps 1Mbps 10Mbps 100Mbps 1Gbps
+ * ------------+-------------------------------------------------------
+ * bytes/0.5us 6.25e-3 62.5e-3 625e-3 6250e-e 62500e-3
+ * bytes/us 12.5e-3 125e-3 1250e-3 12500e-3 125000e-3
+ * bytes/1.27us 15.875e-3 158.75e-3 1587.5e-3 15875e-3 158750e-3
+ *
+ * 0.5us/byte 160 16 1.6 0.16 0.016
+ * us/byte 80 8 0.8 0.08 0.008
+ * 1.27us/byte 63 6.3 0.63 0.063 0.0063
+ */
+#define SM_SHIFT 20
+#define ISM_SHIFT 18
+
+#define SM_MASK ((1ULL << SM_SHIFT) - 1)
+#define ISM_MASK ((1ULL << ISM_SHIFT) - 1)
+
+static inline u64
+seg_x2y(u64 x, u64 sm)
+{
+ u64 y;
+
+ /*
+ * compute
+ * y = x * sm >> SM_SHIFT
+ * but divide it for the upper and lower bits to avoid overflow
+ */
+ y = (x >> SM_SHIFT) * sm + (((x & SM_MASK) * sm) >> SM_SHIFT);
+ return y;
+}
+
+static inline u64
+seg_y2x(u64 y, u64 ism)
+{
+ u64 x;
+
+ if (y == 0)
+ x = 0;
+ else if (ism == HT_INFINITY)
+ x = HT_INFINITY;
+ else {
+ x = (y >> ISM_SHIFT) * ism
+ + (((y & ISM_MASK) * ism) >> ISM_SHIFT);
+ }
+ return x;
+}
+
+/* Convert m (bps) into sm (bytes/psched us) */
+static u64
+m2sm(u32 m)
+{
+ u64 sm;
+
+ sm = ((u64)m << SM_SHIFT);
+ sm += PSCHED_JIFFIE2US(HZ) - 1;
+ do_div(sm, PSCHED_JIFFIE2US(HZ));
+ return sm;
+}
+
+/* convert m (bps) into ism (psched us/byte) */
+static u64
+m2ism(u32 m)
+{
+ u64 ism;
+
+ if (m == 0)
+ ism = HT_INFINITY;
+ else {
+ ism = ((u64)PSCHED_JIFFIE2US(HZ) << ISM_SHIFT);
+ ism += m - 1;
+ do_div(ism, m);
+ }
+ return ism;
+}
+
+/* convert d (us) into dx (psched us) */
+static u64
+d2dx(u32 d)
+{
+ u64 dx;
+
+ dx = ((u64)d * PSCHED_JIFFIE2US(HZ));
+ dx += 1000000 - 1;
+ do_div(dx, 1000000);
+ return dx;
+}
+
+/* convert sm (bytes/psched us) into m (bps) */
+static u32
+sm2m(u64 sm)
+{
+ u64 m;
+
+ m = (sm * PSCHED_JIFFIE2US(HZ)) >> SM_SHIFT;
+ return (u32)m;
+}
+
+/* convert dx (psched us) into d (us) */
+static u32
+dx2d(u64 dx)
+{
+ u64 d;
+
+ d = dx * 1000000;
+ do_div(d, PSCHED_JIFFIE2US(HZ));
+ return (u32)d;
+}
+
+static void
+sc2isc(struct tc_service_curve *sc, struct internal_sc *isc)
+{
+ isc->sm1 = m2sm(sc->m1);
+ isc->ism1 = m2ism(sc->m1);
+ isc->dx = d2dx(sc->d);
+ isc->dy = seg_x2y(isc->dx, isc->sm1);
+ isc->sm2 = m2sm(sc->m2);
+ isc->ism2 = m2ism(sc->m2);
+}
+
+/*
+ * initialize the runtime service curve with the given internal
+ * service curve starting at (x, y).
+ */
+static void
+rtsc_init(struct runtime_sc *rtsc, struct internal_sc *isc, u64 x, u64 y)
+{
+ rtsc->x = x;
+ rtsc->y = y;
+ rtsc->sm1 = isc->sm1;
+ rtsc->ism1 = isc->ism1;
+ rtsc->dx = isc->dx;
+ rtsc->dy = isc->dy;
+ rtsc->sm2 = isc->sm2;
+ rtsc->ism2 = isc->ism2;
+}
+
+/*
+ * calculate the y-projection of the runtime service curve by the
+ * given x-projection value
+ */
+static u64
+rtsc_y2x(struct runtime_sc *rtsc, u64 y)
+{
+ u64 x;
+
+ if (y < rtsc->y)
+ x = rtsc->x;
+ else if (y <= rtsc->y + rtsc->dy) {
+ /* x belongs to the 1st segment */
+ if (rtsc->dy == 0)
+ x = rtsc->x + rtsc->dx;
+ else
+ x = rtsc->x + seg_y2x(y - rtsc->y, rtsc->ism1);
+ } else {
+ /* x belongs to the 2nd segment */
+ x = rtsc->x + rtsc->dx
+ + seg_y2x(y - rtsc->y - rtsc->dy, rtsc->ism2);
+ }
+ return x;
+}
+
+static u64
+rtsc_x2y(struct runtime_sc *rtsc, u64 x)
+{
+ u64 y;
+
+ if (x <= rtsc->x)
+ y = rtsc->y;
+ else if (x <= rtsc->x + rtsc->dx)
+ /* y belongs to the 1st segment */
+ y = rtsc->y + seg_x2y(x - rtsc->x, rtsc->sm1);
+ else
+ /* y belongs to the 2nd segment */
+ y = rtsc->y + rtsc->dy
+ + seg_x2y(x - rtsc->x - rtsc->dx, rtsc->sm2);
+ return y;
+}
+
+/*
+ * update the runtime service curve by taking the minimum of the current
+ * runtime service curve and the service curve starting at (x, y).
+ */
+static void
+rtsc_min(struct runtime_sc *rtsc, struct internal_sc *isc, u64 x, u64 y)
+{
+ u64 y1, y2, dx, dy;
+ u32 dsm;
+
+ if (isc->sm1 <= isc->sm2) {
+ /* service curve is convex */
+ y1 = rtsc_x2y(rtsc, x);
+ if (y1 < y)
+ /* the current rtsc is smaller */
+ return;
+ rtsc->x = x;
+ rtsc->y = y;
+ return;
+ }
+
+ /*
+ * service curve is concave
+ * compute the two y values of the current rtsc
+ * y1: at x
+ * y2: at (x + dx)
+ */
+ y1 = rtsc_x2y(rtsc, x);
+ if (y1 <= y) {
+ /* rtsc is below isc, no change to rtsc */
+ return;
+ }
+
+ y2 = rtsc_x2y(rtsc, x + isc->dx);
+ if (y2 >= y + isc->dy) {
+ /* rtsc is above isc, replace rtsc by isc */
+ rtsc->x = x;
+ rtsc->y = y;
+ rtsc->dx = isc->dx;
+ rtsc->dy = isc->dy;
+ return;
+ }
+
+ /*
+ * the two curves intersect
+ * compute the offsets (dx, dy) using the reverse
+ * function of seg_x2y()
+ * seg_x2y(dx, sm1) == seg_x2y(dx, sm2) + (y1 - y)
+ */
+ dx = (y1 - y) << SM_SHIFT;
+ dsm = isc->sm1 - isc->sm2;
+ do_div(dx, dsm);
+ /*
+ * check if (x, y1) belongs to the 1st segment of rtsc.
+ * if so, add the offset.
+ */
+ if (rtsc->x + rtsc->dx > x)
+ dx += rtsc->x + rtsc->dx - x;
+ dy = seg_x2y(dx, isc->sm1);
+
+ rtsc->x = x;
+ rtsc->y = y;
+ rtsc->dx = dx;
+ rtsc->dy = dy;
+ return;
+}
+
+static void
+init_ed(struct hfsc_class *cl, unsigned int next_len)
+{
+ u64 cur_time;
+
+ PSCHED_GET_TIME(cur_time);
+
+ /* update the deadline curve */
+ rtsc_min(&cl->cl_deadline, &cl->cl_rsc, cur_time, cl->cl_cumul);
+
+ /*
+ * update the eligible curve.
+ * for concave, it is equal to the deadline curve.
+ * for convex, it is a linear curve with slope m2.
+ */
+ cl->cl_eligible = cl->cl_deadline;
+ if (cl->cl_rsc.sm1 <= cl->cl_rsc.sm2) {
+ cl->cl_eligible.dx = 0;
+ cl->cl_eligible.dy = 0;
+ }
+
+ /* compute e and d */
+ cl->cl_e = rtsc_y2x(&cl->cl_eligible, cl->cl_cumul);
+ cl->cl_d = rtsc_y2x(&cl->cl_deadline, cl->cl_cumul + next_len);
+
+ eltree_insert(cl);
+}
+
+static void
+update_ed(struct hfsc_class *cl, unsigned int next_len)
+{
+ cl->cl_e = rtsc_y2x(&cl->cl_eligible, cl->cl_cumul);
+ cl->cl_d = rtsc_y2x(&cl->cl_deadline, cl->cl_cumul + next_len);
+
+ eltree_update(cl);
+}
+
+static inline void
+update_d(struct hfsc_class *cl, unsigned int next_len)
+{
+ cl->cl_d = rtsc_y2x(&cl->cl_deadline, cl->cl_cumul + next_len);
+}
+
+static inline void
+update_cfmin(struct hfsc_class *cl)
+{
+ rb_node_t *n = rb_first(&cl->cf_tree);
+ struct hfsc_class *p;
+
+ if (n == NULL) {
+ cl->cl_cfmin = 0;
+ return;
+ }
+ p = rb_entry(n, struct hfsc_class, cf_node);
+ cl->cl_cfmin = p->cl_f;
+}
+
+static void
+init_vf(struct hfsc_class *cl, unsigned int len)
+{
+ struct hfsc_class *max_cl;
+ rb_node_t *n;
+ u64 vt, f, cur_time;
+ int go_active;
+
+ cur_time = 0;
+ go_active = 1;
+ for (; cl->cl_parent != NULL; cl = cl->cl_parent) {
+ if (go_active && cl->cl_nactive++ == 0)
+ go_active = 1;
+ else
+ go_active = 0;
+
+ if (go_active) {
+ n = rb_last(&cl->cl_parent->vt_tree);
+ if (n != NULL) {
+ max_cl = rb_entry(n, struct hfsc_class,vt_node);
+ /*
+ * set vt to the average of the min and max
+ * classes. if the parent's period didn't
+ * change, don't decrease vt of the class.
+ */
+ vt = max_cl->cl_vt;
+ if (cl->cl_parent->cl_cvtmin != 0)
+ vt = (cl->cl_parent->cl_cvtmin + vt)/2;
+
+ if (cl->cl_parent->cl_vtperiod !=
+ cl->cl_parentperiod || vt > cl->cl_vt)
+ cl->cl_vt = vt;
+ } else {
+ /*
+ * first child for a new parent backlog period.
+ * add parent's cvtmax to cvtoff to make a new
+ * vt (vtoff + vt) larger than the vt in the
+ * last period for all children.
+ */
+ vt = cl->cl_parent->cl_cvtmax;
+ cl->cl_parent->cl_cvtoff += vt;
+ cl->cl_parent->cl_cvtmax = 0;
+ cl->cl_parent->cl_cvtmin = 0;
+ cl->cl_vt = 0;
+ }
+
+ cl->cl_vtoff = cl->cl_parent->cl_cvtoff -
+ cl->cl_pcvtoff;
+
+ /* update the virtual curve */
+ vt = cl->cl_vt + cl->cl_vtoff;
+ rtsc_min(&cl->cl_virtual, &cl->cl_fsc, vt,
+ cl->cl_total);
+ if (cl->cl_virtual.x == vt) {
+ cl->cl_virtual.x -= cl->cl_vtoff;
+ cl->cl_vtoff = 0;
+ }
+ cl->cl_vtadj = 0;
+
+ cl->cl_vtperiod++; /* increment vt period */
+ cl->cl_parentperiod = cl->cl_parent->cl_vtperiod;
+ if (cl->cl_parent->cl_nactive == 0)
+ cl->cl_parentperiod++;
+ cl->cl_f = 0;
+
+ vttree_insert(cl);
+ cftree_insert(cl);
+
+ if (cl->cl_flags & HFSC_USC) {
+ /* class has upper limit curve */
+ if (cur_time == 0)
+ PSCHED_GET_TIME(cur_time);
+
+ /* update the ulimit curve */
+ rtsc_min(&cl->cl_ulimit, &cl->cl_usc, cur_time,
+ cl->cl_total);
+ /* compute myf */
+ cl->cl_myf = rtsc_y2x(&cl->cl_ulimit,
+ cl->cl_total);
+ cl->cl_myfadj = 0;
+ }
+ }
+
+ f = max(cl->cl_myf, cl->cl_cfmin);
+ if (f != cl->cl_f) {
+ cl->cl_f = f;
+ cftree_update(cl);
+ update_cfmin(cl->cl_parent);
+ }
+ }
+}
+
+static void
+update_vf(struct hfsc_class *cl, unsigned int len, u64 cur_time)
+{
+ u64 f; /* , myf_bound, delta; */
+ int go_passive = 0;
+
+ if (cl->qdisc->q.qlen == 0 && cl->cl_flags & HFSC_FSC)
+ go_passive = 1;
+
+ for (; cl->cl_parent != NULL; cl = cl->cl_parent) {
+ cl->cl_total += len;
+
+ if (!(cl->cl_flags & HFSC_FSC) || cl->cl_nactive == 0)
+ continue;
+
+ if (go_passive && --cl->cl_nactive == 0)
+ go_passive = 1;
+ else
+ go_passive = 0;
+
+ if (go_passive) {
+ /* no more active child, going passive */
+
+ /* update cvtmax of the parent class */
+ if (cl->cl_vt > cl->cl_parent->cl_cvtmax)
+ cl->cl_parent->cl_cvtmax = cl->cl_vt;
+
+ /* remove this class from the vt tree */
+ vttree_remove(cl);
+
+ cftree_remove(cl);
+ update_cfmin(cl->cl_parent);
+
+ continue;
+ }
+
+ /*
+ * update vt and f
+ */
+ cl->cl_vt = rtsc_y2x(&cl->cl_virtual, cl->cl_total)
+ - cl->cl_vtoff + cl->cl_vtadj;
+
+ /*
+ * if vt of the class is smaller than cvtmin,
+ * the class was skipped in the past due to non-fit.
+ * if so, we need to adjust vtadj.
+ */
+ if (cl->cl_vt < cl->cl_parent->cl_cvtmin) {
+ cl->cl_vtadj += cl->cl_parent->cl_cvtmin - cl->cl_vt;
+ cl->cl_vt = cl->cl_parent->cl_cvtmin;
+ }
+
+ /* update the vt tree */
+ vttree_update(cl);
+
+ if (cl->cl_flags & HFSC_USC) {
+ cl->cl_myf = cl->cl_myfadj + rtsc_y2x(&cl->cl_ulimit,
+ cl->cl_total);
+#if 0
+ /*
+ * This code causes classes to stay way under their
+ * limit when multiple classes are used at gigabit
+ * speed. needs investigation. -kaber
+ */
+ /*
+ * if myf lags behind by more than one clock tick
+ * from the current time, adjust myfadj to prevent
+ * a rate-limited class from going greedy.
+ * in a steady state under rate-limiting, myf
+ * fluctuates within one clock tick.
+ */
+ myf_bound = cur_time - PSCHED_JIFFIE2US(1);
+ if (cl->cl_myf < myf_bound) {
+ delta = cur_time - cl->cl_myf;
+ cl->cl_myfadj += delta;
+ cl->cl_myf += delta;
+ }
+#endif
+ }
+
+ f = max(cl->cl_myf, cl->cl_cfmin);
+ if (f != cl->cl_f) {
+ cl->cl_f = f;
+ cftree_update(cl);
+ update_cfmin(cl->cl_parent);
+ }
+ }
+}
+
+static void
+set_active(struct hfsc_class *cl, unsigned int len)
+{
+ if (cl->cl_flags & HFSC_RSC)
+ init_ed(cl, len);
+ if (cl->cl_flags & HFSC_FSC)
+ init_vf(cl, len);
+
+ list_add_tail(&cl->dlist, &cl->sched->droplist);
+}
+
+static void
+set_passive(struct hfsc_class *cl)
+{
+ if (cl->cl_flags & HFSC_RSC)
+ eltree_remove(cl);
+
+ list_del(&cl->dlist);
+
+ /*
+ * vttree is now handled in update_vf() so that update_vf(cl, 0, 0)
+ * needs to be called explicitly to remove a class from vttree.
+ */
+}
+
+/*
+ * hack to get length of first packet in queue.
+ */
+static unsigned int
+qdisc_peek_len(struct Qdisc *sch)
+{
+ struct sk_buff *skb;
+ unsigned int len;
+
+ skb = sch->dequeue(sch);
+ if (skb == NULL) {
+ if (net_ratelimit())
+ printk("qdisc_peek_len: non work-conserving qdisc ?\n");
+ return 0;
+ }
+ len = skb->len;
+ if (unlikely(sch->ops->requeue(skb, sch) != NET_XMIT_SUCCESS)) {
+ if (net_ratelimit())
+ printk("qdisc_peek_len: failed to requeue\n");
+ return 0;
+ }
+ return len;
+}
+
+static void
+hfsc_purge_queue(struct Qdisc *sch, struct hfsc_class *cl)
+{
+ unsigned int len = cl->qdisc->q.qlen;
+
+ qdisc_reset(cl->qdisc);
+ if (len > 0) {
+ update_vf(cl, 0, 0);
+ set_passive(cl);
+ sch->q.qlen -= len;
+ }
+}
+
+static void
+hfsc_adjust_levels(struct hfsc_class *cl)
+{
+ struct hfsc_class *p;
+ unsigned int level;
+
+ do {
+ level = 0;
+ list_for_each_entry(p, &cl->children, siblings) {
+ if (p->level > level)
+ level = p->level;
+ }
+ cl->level = level + 1;
+ } while ((cl = cl->cl_parent) != NULL);
+}
+
+static inline unsigned int
+hfsc_hash(u32 h)
+{
+ h ^= h >> 8;
+ h ^= h >> 4;
+
+ return h & (HFSC_HSIZE - 1);
+}
+
+static inline struct hfsc_class *
+hfsc_find_class(u32 classid, struct Qdisc *sch)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+ struct hfsc_class *cl;
+
+ list_for_each_entry(cl, &q->clhash[hfsc_hash(classid)], hlist) {
+ if (cl->classid == classid)
+ return cl;
+ }
+ return NULL;
+}
+
+static void
+hfsc_change_rsc(struct hfsc_class *cl, struct tc_service_curve *rsc,
+ u64 cur_time)
+{
+ sc2isc(rsc, &cl->cl_rsc);
+ rtsc_init(&cl->cl_deadline, &cl->cl_rsc, cur_time, cl->cl_cumul);
+ cl->cl_eligible = cl->cl_deadline;
+ if (cl->cl_rsc.sm1 <= cl->cl_rsc.sm2) {
+ cl->cl_eligible.dx = 0;
+ cl->cl_eligible.dy = 0;
+ }
+ cl->cl_flags |= HFSC_RSC;
+}
+
+static void
+hfsc_change_fsc(struct hfsc_class *cl, struct tc_service_curve *fsc)
+{
+ sc2isc(fsc, &cl->cl_fsc);
+ rtsc_init(&cl->cl_virtual, &cl->cl_fsc, cl->cl_vt, cl->cl_total);
+ cl->cl_flags |= HFSC_FSC;
+}
+
+static void
+hfsc_change_usc(struct hfsc_class *cl, struct tc_service_curve *usc,
+ u64 cur_time)
+{
+ sc2isc(usc, &cl->cl_usc);
+ rtsc_init(&cl->cl_ulimit, &cl->cl_usc, cur_time, cl->cl_total);
+ cl->cl_flags |= HFSC_USC;
+}
+
+static int
+hfsc_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
+ struct rtattr **tca, unsigned long *arg)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+ struct hfsc_class *cl = (struct hfsc_class *)*arg;
+ struct hfsc_class *parent = NULL;
+ struct rtattr *opt = tca[TCA_OPTIONS-1];
+ struct rtattr *tb[TCA_HFSC_MAX];
+ struct tc_service_curve *rsc = NULL, *fsc = NULL, *usc = NULL;
+ u64 cur_time;
+
+ if (opt == NULL ||
+ rtattr_parse(tb, TCA_HFSC_MAX, RTA_DATA(opt), RTA_PAYLOAD(opt)))
+ return -EINVAL;
+
+ if (tb[TCA_HFSC_RSC-1]) {
+ if (RTA_PAYLOAD(tb[TCA_HFSC_RSC-1]) < sizeof(*rsc))
+ return -EINVAL;
+ rsc = RTA_DATA(tb[TCA_HFSC_RSC-1]);
+ if (rsc->m1 == 0 && rsc->m2 == 0)
+ rsc = NULL;
+ }
+
+ if (tb[TCA_HFSC_FSC-1]) {
+ if (RTA_PAYLOAD(tb[TCA_HFSC_FSC-1]) < sizeof(*fsc))
+ return -EINVAL;
+ fsc = RTA_DATA(tb[TCA_HFSC_FSC-1]);
+ if (fsc->m1 == 0 && fsc->m2 == 0)
+ fsc = NULL;
+ }
+
+ if (tb[TCA_HFSC_USC-1]) {
+ if (RTA_PAYLOAD(tb[TCA_HFSC_USC-1]) < sizeof(*usc))
+ return -EINVAL;
+ usc = RTA_DATA(tb[TCA_HFSC_USC-1]);
+ if (usc->m1 == 0 && usc->m2 == 0)
+ usc = NULL;
+ }
+
+ if (cl != NULL) {
+ if (parentid) {
+ if (cl->cl_parent && cl->cl_parent->classid != parentid)
+ return -EINVAL;
+ if (cl->cl_parent == NULL && parentid != TC_H_ROOT)
+ return -EINVAL;
+ }
+ PSCHED_GET_TIME(cur_time);
+
+ sch_tree_lock(sch);
+ if (rsc != NULL)
+ hfsc_change_rsc(cl, rsc, cur_time);
+ if (fsc != NULL)
+ hfsc_change_fsc(cl, fsc);
+ if (usc != NULL)
+ hfsc_change_usc(cl, usc, cur_time);
+
+ if (cl->qdisc->q.qlen != 0) {
+ if (cl->cl_flags & HFSC_RSC)
+ update_ed(cl, qdisc_peek_len(cl->qdisc));
+ if (cl->cl_flags & HFSC_FSC)
+ update_vf(cl, 0, cur_time);
+ }
+ sch_tree_unlock(sch);
+
+#ifdef CONFIG_NET_ESTIMATOR
+ if (tca[TCA_RATE-1]) {
+ qdisc_kill_estimator(&cl->stats);
+ qdisc_new_estimator(&cl->stats, tca[TCA_RATE-1]);
+ }
+#endif
+ return 0;
+ }
+
+ if (parentid == TC_H_ROOT)
+ return -EEXIST;
+
+ parent = &q->root;
+ if (parentid) {
+ parent = hfsc_find_class(parentid, sch);
+ if (parent == NULL)
+ return -ENOENT;
+ }
+
+ if (classid == 0 || TC_H_MAJ(classid ^ sch->handle) != 0)
+ return -EINVAL;
+ if (hfsc_find_class(classid, sch))
+ return -EEXIST;
+
+ if (rsc == NULL && fsc == NULL)
+ return -EINVAL;
+
+ cl = kmalloc(sizeof(struct hfsc_class), GFP_KERNEL);
+ if (cl == NULL)
+ return -ENOBUFS;
+ memset(cl, 0, sizeof(struct hfsc_class));
+
+ if (rsc != NULL)
+ hfsc_change_rsc(cl, rsc, 0);
+ if (fsc != NULL)
+ hfsc_change_fsc(cl, fsc);
+ if (usc != NULL)
+ hfsc_change_usc(cl, usc, 0);
+
+ cl->refcnt = 1;
+ cl->classid = classid;
+ cl->sched = q;
+ cl->cl_parent = parent;
+ cl->qdisc = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops);
+ if (cl->qdisc == NULL)
+ cl->qdisc = &noop_qdisc;
+ cl->stats.lock = &sch->dev->queue_lock;
+ INIT_LIST_HEAD(&cl->children);
+ cl->vt_tree = RB_ROOT;
+ cl->cf_tree = RB_ROOT;
+
+ sch_tree_lock(sch);
+ list_add_tail(&cl->hlist, &q->clhash[hfsc_hash(classid)]);
+ list_add_tail(&cl->siblings, &parent->children);
+ if (parent->level == 0)
+ hfsc_purge_queue(sch, parent);
+ hfsc_adjust_levels(parent);
+ cl->cl_pcvtoff = parent->cl_cvtoff;
+ sch_tree_unlock(sch);
+
+#ifdef CONFIG_NET_ESTIMATOR
+ if (tca[TCA_RATE-1])
+ qdisc_new_estimator(&cl->stats, tca[TCA_RATE-1]);
+#endif
+ *arg = (unsigned long)cl;
+ return 0;
+}
+
+static void
+hfsc_destroy_filters(struct tcf_proto **fl)
+{
+ struct tcf_proto *tp;
+
+ while ((tp = *fl) != NULL) {
+ *fl = tp->next;
+ tcf_destroy(tp);
+ }
+}
+
+static void
+hfsc_destroy_class(struct Qdisc *sch, struct hfsc_class *cl)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+
+ hfsc_destroy_filters(&cl->filter_list);
+ qdisc_destroy(cl->qdisc);
+#ifdef CONFIG_NET_ESTIMATOR
+ qdisc_kill_estimator(&cl->stats);
+#endif
+ if (cl != &q->root)
+ kfree(cl);
+}
+
+static int
+hfsc_delete_class(struct Qdisc *sch, unsigned long arg)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+ struct hfsc_class *cl = (struct hfsc_class *)arg;
+
+ if (cl->level > 0 || cl->filter_cnt > 0 || cl == &q->root)
+ return -EBUSY;
+
+ sch_tree_lock(sch);
+
+ list_del(&cl->hlist);
+ list_del(&cl->siblings);
+ hfsc_adjust_levels(cl->cl_parent);
+ hfsc_purge_queue(sch, cl);
+ if (--cl->refcnt == 0)
+ hfsc_destroy_class(sch, cl);
+
+ sch_tree_unlock(sch);
+ return 0;
+}
+
+static struct hfsc_class *
+hfsc_classify(struct sk_buff *skb, struct Qdisc *sch)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+ struct hfsc_class *cl;
+ struct tcf_result res;
+ struct tcf_proto *tcf;
+ int result;
+
+ if (TC_H_MAJ(skb->priority ^ sch->handle) == 0 &&
+ (cl = hfsc_find_class(skb->priority, sch)) != NULL)
+ if (cl->level == 0)
+ return cl;
+
+ tcf = q->root.filter_list;
+ while (tcf && (result = tc_classify(skb, tcf, &res)) >= 0) {
+#ifdef CONFIG_NET_CLS_POLICE
+ if (result == TC_POLICE_SHOT)
+ return NULL;
+#endif
+ if ((cl = (struct hfsc_class *)res.class) == NULL) {
+ if ((cl = hfsc_find_class(res.classid, sch)) == NULL)
+ break; /* filter selected invalid classid */
+ }
+
+ if (cl->level == 0)
+ return cl; /* hit leaf class */
+
+ /* apply inner filter chain */
+ tcf = cl->filter_list;
+ }
+
+ /* classification failed, try default class */
+ cl = hfsc_find_class(TC_H_MAKE(TC_H_MAJ(sch->handle), q->defcls), sch);
+ if (cl == NULL || cl->level > 0)
+ return NULL;
+
+ return cl;
+}
+
+static int
+hfsc_graft_class(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
+ struct Qdisc **old)
+{
+ struct hfsc_class *cl = (struct hfsc_class *)arg;
+
+ if (cl == NULL)
+ return -ENOENT;
+ if (cl->level > 0)
+ return -EINVAL;
+ if (new == NULL) {
+ new = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops);
+ if (new == NULL)
+ new = &noop_qdisc;
+ }
+
+ sch_tree_lock(sch);
+ hfsc_purge_queue(sch, cl);
+ *old = xchg(&cl->qdisc, new);
+ sch_tree_unlock(sch);
+ return 0;
+}
+
+static struct Qdisc *
+hfsc_class_leaf(struct Qdisc *sch, unsigned long arg)
+{
+ struct hfsc_class *cl = (struct hfsc_class *)arg;
+
+ if (cl != NULL && cl->level == 0)
+ return cl->qdisc;
+
+ return NULL;
+}
+
+static unsigned long
+hfsc_get_class(struct Qdisc *sch, u32 classid)
+{
+ struct hfsc_class *cl = hfsc_find_class(classid, sch);
+
+ if (cl != NULL)
+ cl->refcnt++;
+
+ return (unsigned long)cl;
+}
+
+static void
+hfsc_put_class(struct Qdisc *sch, unsigned long arg)
+{
+ struct hfsc_class *cl = (struct hfsc_class *)arg;
+
+ if (--cl->refcnt == 0)
+ hfsc_destroy_class(sch, cl);
+}
+
+static unsigned long
+hfsc_bind_tcf(struct Qdisc *sch, unsigned long parent, u32 classid)
+{
+ struct hfsc_class *p = (struct hfsc_class *)parent;
+ struct hfsc_class *cl = hfsc_find_class(classid, sch);
+
+ if (cl != NULL) {
+ if (p != NULL && p->level <= cl->level)
+ return 0;
+ cl->filter_cnt++;
+ }
+
+ return (unsigned long)cl;
+}
+
+static void
+hfsc_unbind_tcf(struct Qdisc *sch, unsigned long arg)
+{
+ struct hfsc_class *cl = (struct hfsc_class *)arg;
+
+ cl->filter_cnt--;
+}
+
+static struct tcf_proto **
+hfsc_tcf_chain(struct Qdisc *sch, unsigned long arg)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+ struct hfsc_class *cl = (struct hfsc_class *)arg;
+
+ if (cl == NULL)
+ cl = &q->root;
+
+ return &cl->filter_list;
+}
+
+static int
+hfsc_dump_sc(struct sk_buff *skb, int attr, struct internal_sc *sc)
+{
+ struct tc_service_curve tsc;
+
+ tsc.m1 = sm2m(sc->sm1);
+ tsc.d = dx2d(sc->dx);
+ tsc.m2 = sm2m(sc->sm2);
+ RTA_PUT(skb, attr, sizeof(tsc), &tsc);
+
+ return skb->len;
+
+ rtattr_failure:
+ return -1;
+}
+
+static inline int
+hfsc_dump_curves(struct sk_buff *skb, struct hfsc_class *cl)
+{
+ if ((cl->cl_flags & HFSC_RSC) &&
+ (hfsc_dump_sc(skb, TCA_HFSC_RSC, &cl->cl_rsc) < 0))
+ goto rtattr_failure;
+
+ if ((cl->cl_flags & HFSC_FSC) &&
+ (hfsc_dump_sc(skb, TCA_HFSC_FSC, &cl->cl_fsc) < 0))
+ goto rtattr_failure;
+
+ if ((cl->cl_flags & HFSC_USC) &&
+ (hfsc_dump_sc(skb, TCA_HFSC_USC, &cl->cl_usc) < 0))
+ goto rtattr_failure;
+
+ return skb->len;
+
+ rtattr_failure:
+ return -1;
+}
+
+static inline int
+hfsc_dump_stats(struct sk_buff *skb, struct hfsc_class *cl)
+{
+ cl->stats.qlen = cl->qdisc->q.qlen;
+ if (qdisc_copy_stats(skb, &cl->stats) < 0)
+ goto rtattr_failure;
+
+ return skb->len;
+
+ rtattr_failure:
+ return -1;
+}
+
+static inline int
+hfsc_dump_xstats(struct sk_buff *skb, struct hfsc_class *cl)
+{
+ struct tc_hfsc_stats xstats;
+
+ xstats.level = cl->level;
+ xstats.period = cl->cl_vtperiod;
+ xstats.work = cl->cl_total;
+ xstats.rtwork = cl->cl_cumul;
+ RTA_PUT(skb, TCA_XSTATS, sizeof(xstats), &xstats);
+
+ return skb->len;
+
+ rtattr_failure:
+ return -1;
+}
+
+static int
+hfsc_dump_class(struct Qdisc *sch, unsigned long arg, struct sk_buff *skb,
+ struct tcmsg *tcm)
+{
+ struct hfsc_class *cl = (struct hfsc_class *)arg;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta = (struct rtattr *)b;
+
+ tcm->tcm_parent = cl->cl_parent ? cl->cl_parent->classid : TC_H_ROOT;
+ tcm->tcm_handle = cl->classid;
+ if (cl->level == 0)
+ tcm->tcm_info = cl->qdisc->handle;
+
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+ if (hfsc_dump_curves(skb, cl) < 0)
+ goto rtattr_failure;
+ rta->rta_len = skb->tail - b;
+
+ if ((hfsc_dump_stats(skb, cl) < 0) ||
+ (hfsc_dump_xstats(skb, cl) < 0))
+ goto rtattr_failure;
+
+ return skb->len;
+
+ rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static void
+hfsc_walk(struct Qdisc *sch, struct qdisc_walker *arg)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+ struct hfsc_class *cl;
+ unsigned int i;
+
+ if (arg->stop)
+ return;
+
+ for (i = 0; i < HFSC_HSIZE; i++) {
+ list_for_each_entry(cl, &q->clhash[i], hlist) {
+ if (arg->count < arg->skip) {
+ arg->count++;
+ continue;
+ }
+ if (arg->fn(sch, (unsigned long)cl, arg) < 0) {
+ arg->stop = 1;
+ return;
+ }
+ arg->count++;
+ }
+ }
+}
+
+static void
+hfsc_watchdog(unsigned long arg)
+{
+ struct Qdisc *sch = (struct Qdisc *)arg;
+
+ sch->flags &= ~TCQ_F_THROTTLED;
+ netif_schedule(sch->dev);
+}
+
+static void
+hfsc_schedule_watchdog(struct Qdisc *sch, u64 cur_time)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+ struct hfsc_class *cl;
+ u64 next_time = 0;
+ long delay;
+
+ if ((cl = eltree_get_minel(q)) != NULL)
+ next_time = cl->cl_e;
+ if (q->root.cl_cfmin != 0) {
+ if (next_time == 0 || next_time > q->root.cl_cfmin)
+ next_time = q->root.cl_cfmin;
+ }
+ ASSERT(next_time != 0);
+ delay = next_time - cur_time;
+ delay = PSCHED_US2JIFFIE(delay);
+
+ sch->flags |= TCQ_F_THROTTLED;
+ mod_timer(&q->wd_timer, jiffies + delay);
+}
+
+static int
+hfsc_init_qdisc(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+ struct tc_hfsc_qopt *qopt;
+ unsigned int i;
+
+ if (opt == NULL || RTA_PAYLOAD(opt) < sizeof(*qopt))
+ return -EINVAL;
+ qopt = RTA_DATA(opt);
+
+ sch->stats.lock = &sch->dev->queue_lock;
+
+ q->defcls = qopt->defcls;
+ for (i = 0; i < HFSC_HSIZE; i++)
+ INIT_LIST_HEAD(&q->clhash[i]);
+ q->eligible = RB_ROOT;
+ INIT_LIST_HEAD(&q->droplist);
+ skb_queue_head_init(&q->requeue);
+
+ q->root.refcnt = 1;
+ q->root.classid = sch->handle;
+ q->root.sched = q;
+ q->root.qdisc = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops);
+ if (q->root.qdisc == NULL)
+ q->root.qdisc = &noop_qdisc;
+ q->root.stats.lock = &sch->dev->queue_lock;
+ INIT_LIST_HEAD(&q->root.children);
+ q->root.vt_tree = RB_ROOT;
+ q->root.cf_tree = RB_ROOT;
+
+ list_add(&q->root.hlist, &q->clhash[hfsc_hash(q->root.classid)]);
+
+ init_timer(&q->wd_timer);
+ q->wd_timer.function = hfsc_watchdog;
+ q->wd_timer.data = (unsigned long)sch;
+
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int
+hfsc_change_qdisc(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+ struct tc_hfsc_qopt *qopt;
+
+ if (opt == NULL || RTA_PAYLOAD(opt) < sizeof(*qopt))
+ return -EINVAL;;
+ qopt = RTA_DATA(opt);
+
+ sch_tree_lock(sch);
+ q->defcls = qopt->defcls;
+ sch_tree_unlock(sch);
+
+ return 0;
+}
+
+static void
+hfsc_reset_class(struct hfsc_class *cl)
+{
+ cl->cl_total = 0;
+ cl->cl_cumul = 0;
+ cl->cl_d = 0;
+ cl->cl_e = 0;
+ cl->cl_vt = 0;
+ cl->cl_vtadj = 0;
+ cl->cl_vtoff = 0;
+ cl->cl_cvtmin = 0;
+ cl->cl_cvtmax = 0;
+ cl->cl_cvtoff = 0;
+ cl->cl_pcvtoff = 0;
+ cl->cl_vtperiod = 0;
+ cl->cl_parentperiod = 0;
+ cl->cl_f = 0;
+ cl->cl_myf = 0;
+ cl->cl_myfadj = 0;
+ cl->cl_cfmin = 0;
+ cl->cl_nactive = 0;
+
+ cl->vt_tree = RB_ROOT;
+ cl->cf_tree = RB_ROOT;
+ qdisc_reset(cl->qdisc);
+
+ if (cl->cl_flags & HFSC_RSC)
+ rtsc_init(&cl->cl_deadline, &cl->cl_rsc, 0, 0);
+ if (cl->cl_flags & HFSC_FSC)
+ rtsc_init(&cl->cl_virtual, &cl->cl_fsc, 0, 0);
+ if (cl->cl_flags & HFSC_USC)
+ rtsc_init(&cl->cl_ulimit, &cl->cl_usc, 0, 0);
+}
+
+static void
+hfsc_reset_qdisc(struct Qdisc *sch)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+ struct hfsc_class *cl;
+ unsigned int i;
+
+ for (i = 0; i < HFSC_HSIZE; i++) {
+ list_for_each_entry(cl, &q->clhash[i], hlist)
+ hfsc_reset_class(cl);
+ }
+ __skb_queue_purge(&q->requeue);
+ q->eligible = RB_ROOT;
+ INIT_LIST_HEAD(&q->droplist);
+ del_timer(&q->wd_timer);
+ sch->flags &= ~TCQ_F_THROTTLED;
+ sch->q.qlen = 0;
+}
+
+static void
+hfsc_destroy_qdisc(struct Qdisc *sch)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+ struct hfsc_class *cl, *next;
+ unsigned int i;
+
+ for (i = 0; i < HFSC_HSIZE; i++) {
+ list_for_each_entry_safe(cl, next, &q->clhash[i], hlist)
+ hfsc_destroy_class(sch, cl);
+ }
+ __skb_queue_purge(&q->requeue);
+ del_timer(&q->wd_timer);
+ MOD_DEC_USE_COUNT;
+}
+
+static int
+hfsc_dump_qdisc(struct Qdisc *sch, struct sk_buff *skb)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+ unsigned char *b = skb->tail;
+ struct tc_hfsc_qopt qopt;
+
+ qopt.defcls = q->defcls;
+ RTA_PUT(skb, TCA_OPTIONS, sizeof(qopt), &qopt);
+
+ return skb->len;
+
+ rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int
+hfsc_enqueue(struct sk_buff *skb, struct Qdisc *sch)
+{
+ struct hfsc_class *cl = hfsc_classify(skb, sch);
+ unsigned int len = skb->len;
+ int err;
+
+ if (cl == NULL) {
+ kfree_skb(skb);
+ sch->stats.drops++;
+ return NET_XMIT_DROP;
+ }
+
+ err = cl->qdisc->enqueue(skb, cl->qdisc);
+ if (unlikely(err != NET_XMIT_SUCCESS)) {
+ cl->stats.drops++;
+ sch->stats.drops++;
+ return err;
+ }
+
+ if (cl->qdisc->q.qlen == 1)
+ set_active(cl, len);
+
+ cl->stats.packets++;
+ cl->stats.bytes += len;
+ sch->stats.packets++;
+ sch->stats.bytes += len;
+ sch->q.qlen++;
+
+ return NET_XMIT_SUCCESS;
+}
+
+static struct sk_buff *
+hfsc_dequeue(struct Qdisc *sch)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+ struct hfsc_class *cl;
+ struct sk_buff *skb;
+ u64 cur_time;
+ unsigned int next_len;
+ int realtime = 0;
+
+ if (sch->q.qlen == 0)
+ return NULL;
+ if ((skb = __skb_dequeue(&q->requeue)))
+ goto out;
+
+ PSCHED_GET_TIME(cur_time);
+
+ /*
+ * if there are eligible classes, use real-time criteria.
+ * find the class with the minimum deadline among
+ * the eligible classes.
+ */
+ if ((cl = eltree_get_mindl(q, cur_time)) != NULL) {
+ realtime = 1;
+ } else {
+ /*
+ * use link-sharing criteria
+ * get the class with the minimum vt in the hierarchy
+ */
+ cl = vttree_get_minvt(&q->root, cur_time);
+ if (cl == NULL) {
+ sch->stats.overlimits++;
+ hfsc_schedule_watchdog(sch, cur_time);
+ return NULL;
+ }
+ }
+
+ skb = cl->qdisc->dequeue(cl->qdisc);
+ if (skb == NULL) {
+ if (net_ratelimit())
+ printk("HFSC: Non-work-conserving qdisc ?\n");
+ return NULL;
+ }
+
+ update_vf(cl, skb->len, cur_time);
+ if (realtime)
+ cl->cl_cumul += skb->len;
+
+ if (cl->qdisc->q.qlen != 0) {
+ if (cl->cl_flags & HFSC_RSC) {
+ /* update ed */
+ next_len = qdisc_peek_len(cl->qdisc);
+ if (realtime)
+ update_ed(cl, next_len);
+ else
+ update_d(cl, next_len);
+ }
+ } else {
+ /* the class becomes passive */
+ set_passive(cl);
+ }
+
+ out:
+ sch->flags &= ~TCQ_F_THROTTLED;
+ sch->q.qlen--;
+
+ return skb;
+}
+
+static int
+hfsc_requeue(struct sk_buff *skb, struct Qdisc *sch)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+
+ __skb_queue_head(&q->requeue, skb);
+ sch->q.qlen++;
+ return NET_XMIT_SUCCESS;
+}
+
+static unsigned int
+hfsc_drop(struct Qdisc *sch)
+{
+ struct hfsc_sched *q = (struct hfsc_sched *)sch->data;
+ struct hfsc_class *cl;
+ unsigned int len;
+
+ list_for_each_entry(cl, &q->droplist, dlist) {
+ if (cl->qdisc->ops->drop != NULL &&
+ (len = cl->qdisc->ops->drop(cl->qdisc)) > 0) {
+ if (cl->qdisc->q.qlen == 0) {
+ update_vf(cl, 0, 0);
+ set_passive(cl);
+ } else {
+ list_move_tail(&cl->dlist, &q->droplist);
+ }
+ cl->stats.drops++;
+ sch->stats.drops++;
+ sch->q.qlen--;
+ return len;
+ }
+ }
+ return 0;
+}
+
+static struct Qdisc_class_ops hfsc_class_ops = {
+ .change = hfsc_change_class,
+ .delete = hfsc_delete_class,
+ .graft = hfsc_graft_class,
+ .leaf = hfsc_class_leaf,
+ .get = hfsc_get_class,
+ .put = hfsc_put_class,
+ .bind_tcf = hfsc_bind_tcf,
+ .unbind_tcf = hfsc_unbind_tcf,
+ .tcf_chain = hfsc_tcf_chain,
+ .dump = hfsc_dump_class,
+ .walk = hfsc_walk
+};
+
+struct Qdisc_ops hfsc_qdisc_ops = {
+ .id = "hfsc",
+ .init = hfsc_init_qdisc,
+ .change = hfsc_change_qdisc,
+ .reset = hfsc_reset_qdisc,
+ .destroy = hfsc_destroy_qdisc,
+ .dump = hfsc_dump_qdisc,
+ .enqueue = hfsc_enqueue,
+ .dequeue = hfsc_dequeue,
+ .requeue = hfsc_requeue,
+ .drop = hfsc_drop,
+ .cl_ops = &hfsc_class_ops,
+ .priv_size = sizeof(struct hfsc_sched)
+};
+
+static int __init
+hfsc_init(void)
+{
+ return register_qdisc(&hfsc_qdisc_ops);
+}
+
+static void __exit
+hfsc_cleanup(void)
+{
+ unregister_qdisc(&hfsc_qdisc_ops);
+}
+
+MODULE_LICENSE("GPL");
+module_init(hfsc_init);
+module_exit(hfsc_cleanup);
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_htb.c b/uClinux-2.4.31-uc0/net/sched/sch_htb.c
new file mode 100644
index 0000000..5b70656
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_htb.c
@@ -0,0 +1,1739 @@
+/* vim: ts=8 sw=8
+ * net/sched/sch_htb.c Hierarchical token bucket, feed tree version
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Martin Devera, <devik@cdi.cz>
+ *
+ * Credits (in time order) for older HTB versions:
+ * Stef Coene <stef.coene@docum.org>
+ * HTB support at LARTC mailing list
+ * Ondrej Kraus, <krauso@barr.cz>
+ * found missing INIT_QDISC(htb)
+ * Vladimir Smelhaus, Aamer Akhter, Bert Hubert
+ * helped a lot to locate nasty class stall bug
+ * Andi Kleen, Jamal Hadi, Bert Hubert
+ * code review and helpful comments on shaping
+ * Tomasz Wrona, <tw@eter.tym.pl>
+ * created test case so that I was able to fix nasty bug
+ * Wilfried Weissmann
+ * spotted bug in dequeue code and helped with fix
+ * Jiri Fojtasek
+ * fixed requeue routine
+ * and many others. thanks.
+ *
+ * $Id: sch_htb.c,v 1.25 2003/12/07 11:08:25 devik Exp devik $
+ */
+#include <linux/config.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/version.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <linux/list.h>
+#include <linux/compiler.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+#include <linux/rbtree.h>
+
+/* HTB algorithm.
+ Author: devik@cdi.cz
+ ========================================================================
+ HTB is like TBF with multiple classes. It is also similar to CBQ because
+ it allows to assign priority to each class in hierarchy.
+ In fact it is another implementation of Floyd's formal sharing.
+
+ Levels:
+ Each class is assigned level. Leaf has ALWAYS level 0 and root
+ classes have level TC_HTB_MAXDEPTH-1. Interior nodes has level
+ one less than their parent.
+*/
+
+#define HTB_HSIZE 16 /* classid hash size */
+#define HTB_EWMAC 2 /* rate average over HTB_EWMAC*HTB_HSIZE sec */
+/*#define HTB_DEBUG 1 * compile debugging support (activated by tc tool) */
+#define HTB_RATECM 1 /* whether to use rate computer */
+#define HTB_HYSTERESIS 1/* whether to use mode hysteresis for speedup */
+#define HTB_QLOCK(S) spin_lock_bh(&(S)->dev->queue_lock)
+#define HTB_QUNLOCK(S) spin_unlock_bh(&(S)->dev->queue_lock)
+#define HTB_VER 0x30011 /* major must be matched with number suplied by TC as version */
+
+#if HTB_VER >> 16 != TC_HTB_PROTOVER
+#error "Mismatched sch_htb.c and pkt_sch.h"
+#endif
+
+/* debugging support; S is subsystem, these are defined:
+ 0 - netlink messages
+ 1 - enqueue
+ 2 - drop & requeue
+ 3 - dequeue main
+ 4 - dequeue one prio DRR part
+ 5 - dequeue class accounting
+ 6 - class overlimit status computation
+ 7 - hint tree
+ 8 - event queue
+ 10 - rate estimator
+ 11 - classifier
+ 12 - fast dequeue cache
+
+ L is level; 0 = none, 1 = basic info, 2 = detailed, 3 = full
+ q->debug uint32 contains 16 2-bit fields one for subsystem starting
+ from LSB
+ */
+#ifdef HTB_DEBUG
+#define HTB_DBG_COND(S,L) (((q->debug>>(2*S))&3) >= L)
+#define HTB_DBG(S,L,FMT,ARG...) if (HTB_DBG_COND(S,L)) \
+ printk(KERN_DEBUG FMT,##ARG)
+#define HTB_CHCL(cl) BUG_TRAP((cl)->magic == HTB_CMAGIC)
+#define HTB_PASSQ q,
+#define HTB_ARGQ struct htb_sched *q,
+#define static
+#undef __inline__
+#define __inline__
+#undef inline
+#define inline
+#define HTB_CMAGIC 0xFEFAFEF1
+#define htb_safe_rb_erase(N,R) do { BUG_TRAP((N)->rb_color != -1); \
+ if ((N)->rb_color == -1) break; \
+ rb_erase(N,R); \
+ (N)->rb_color = -1; } while (0)
+#else
+#define HTB_DBG_COND(S,L) (0)
+#define HTB_DBG(S,L,FMT,ARG...)
+#define HTB_PASSQ
+#define HTB_ARGQ
+#define HTB_CHCL(cl)
+#define htb_safe_rb_erase(N,R) rb_erase(N,R)
+#endif
+
+
+/* used internaly to keep status of single class */
+enum htb_cmode {
+ HTB_CANT_SEND, /* class can't send and can't borrow */
+ HTB_MAY_BORROW, /* class can't send but may borrow */
+ HTB_CAN_SEND /* class can send */
+};
+
+/* interior & leaf nodes; props specific to leaves are marked L: */
+struct htb_class
+{
+#ifdef HTB_DEBUG
+ unsigned magic;
+#endif
+ /* general class parameters */
+ u32 classid;
+ struct tc_stats stats; /* generic stats */
+ struct tc_htb_xstats xstats;/* our special stats */
+ int refcnt; /* usage count of this class */
+
+#ifdef HTB_RATECM
+ /* rate measurement counters */
+ unsigned long rate_bytes,sum_bytes;
+ unsigned long rate_packets,sum_packets;
+#endif
+
+ /* topology */
+ int level; /* our level (see above) */
+ struct htb_class *parent; /* parent class */
+ struct list_head hlist; /* classid hash list item */
+ struct list_head sibling; /* sibling list item */
+ struct list_head children; /* children list */
+
+ union {
+ struct htb_class_leaf {
+ struct Qdisc *q;
+ int prio;
+ int aprio;
+ int quantum;
+ int deficit[TC_HTB_MAXDEPTH];
+ struct list_head drop_list;
+ } leaf;
+ struct htb_class_inner {
+ rb_root_t feed[TC_HTB_NUMPRIO]; /* feed trees */
+ rb_node_t *ptr[TC_HTB_NUMPRIO]; /* current class ptr */
+ /* When class changes from state 1->2 and disconnects from
+ parent's feed then we lost ptr value and start from the
+ first child again. Here we store classid of the
+ last valid ptr (used when ptr is NULL). */
+ u32 last_ptr_id[TC_HTB_NUMPRIO];
+ } inner;
+ } un;
+ rb_node_t node[TC_HTB_NUMPRIO]; /* node for self or feed tree */
+ rb_node_t pq_node; /* node for event queue */
+ unsigned long pq_key; /* the same type as jiffies global */
+
+ int prio_activity; /* for which prios are we active */
+ enum htb_cmode cmode; /* current mode of the class */
+
+ /* class attached filters */
+ struct tcf_proto *filter_list;
+ int filter_cnt;
+
+ int warned; /* only one warning about non work conserving .. */
+
+ /* token bucket parameters */
+ struct qdisc_rate_table *rate; /* rate table of the class itself */
+ struct qdisc_rate_table *ceil; /* ceiling rate (limits borrows too) */
+ long buffer,cbuffer; /* token bucket depth/rate */
+ long mbuffer; /* max wait time */
+ long tokens,ctokens; /* current number of tokens */
+ psched_time_t t_c; /* checkpoint time */
+};
+
+/* TODO: maybe compute rate when size is too large .. or drop ? */
+static __inline__ long L2T(struct htb_class *cl,struct qdisc_rate_table *rate,
+ int size)
+{
+ int slot = size >> rate->rate.cell_log;
+ if (slot > 255) {
+ cl->xstats.giants++;
+ slot = 255;
+ }
+ return rate->data[slot];
+}
+
+struct htb_sched
+{
+ struct list_head root; /* root classes list */
+ struct list_head hash[HTB_HSIZE]; /* hashed by classid */
+ struct list_head drops[TC_HTB_NUMPRIO]; /* active leaves (for drops) */
+
+ /* self list - roots of self generating tree */
+ rb_root_t row[TC_HTB_MAXDEPTH][TC_HTB_NUMPRIO];
+ int row_mask[TC_HTB_MAXDEPTH];
+ rb_node_t *ptr[TC_HTB_MAXDEPTH][TC_HTB_NUMPRIO];
+ u32 last_ptr_id[TC_HTB_MAXDEPTH][TC_HTB_NUMPRIO];
+
+ /* self wait list - roots of wait PQs per row */
+ rb_root_t wait_pq[TC_HTB_MAXDEPTH];
+
+ /* time of nearest event per level (row) */
+ unsigned long near_ev_cache[TC_HTB_MAXDEPTH];
+
+ /* cached value of jiffies in dequeue */
+ unsigned long jiffies;
+
+ /* whether we hit non-work conserving class during this dequeue; we use */
+ int nwc_hit; /* this to disable mindelay complaint in dequeue */
+
+ int defcls; /* class where unclassified flows go to */
+ u32 debug; /* subsystem debug levels */
+
+ /* filters for qdisc itself */
+ struct tcf_proto *filter_list;
+ int filter_cnt;
+
+ int rate2quantum; /* quant = rate / rate2quantum */
+ psched_time_t now; /* cached dequeue time */
+ struct timer_list timer; /* send delay timer */
+#ifdef HTB_RATECM
+ struct timer_list rttim; /* rate computer timer */
+ int recmp_bucket; /* which hash bucket to recompute next */
+#endif
+
+ /* non shaped skbs; let them go directly thru */
+ struct sk_buff_head direct_queue;
+ int direct_qlen; /* max qlen of above */
+
+ long direct_pkts;
+};
+
+/* compute hash of size HTB_HSIZE for given handle */
+static __inline__ int htb_hash(u32 h)
+{
+#if HTB_HSIZE != 16
+ #error "Declare new hash for your HTB_HSIZE"
+#endif
+ h ^= h>>8; /* stolen from cbq_hash */
+ h ^= h>>4;
+ return h & 0xf;
+}
+
+/* find class in global hash table using given handle */
+static __inline__ struct htb_class *htb_find(u32 handle, struct Qdisc *sch)
+{
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ struct list_head *p;
+ if (TC_H_MAJ(handle) != sch->handle)
+ return NULL;
+
+ list_for_each (p,q->hash+htb_hash(handle)) {
+ struct htb_class *cl = list_entry(p,struct htb_class,hlist);
+ if (cl->classid == handle)
+ return cl;
+ }
+ return NULL;
+}
+
+/**
+ * htb_classify - classify a packet into class
+ *
+ * It returns NULL if the packet should be dropped or -1 if the packet
+ * should be passed directly thru. In all other cases leaf class is returned.
+ * We allow direct class selection by classid in priority. The we examine
+ * filters in qdisc and in inner nodes (if higher filter points to the inner
+ * node). If we end up with classid MAJOR:0 we enqueue the skb into special
+ * internal fifo (direct). These packets then go directly thru. If we still
+ * have no valid leaf we try to use MAJOR:default leaf. It still unsuccessfull
+ * then finish and return direct queue.
+ */
+#define HTB_DIRECT (struct htb_class*)-1
+static struct htb_class *htb_classify(struct sk_buff *skb, struct Qdisc *sch)
+{
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ struct htb_class *cl;
+ struct tcf_result res;
+ struct tcf_proto *tcf;
+ int result;
+
+ /* allow to select class by setting skb->priority to valid classid;
+ note that nfmark can be used too by attaching filter fw with no
+ rules in it */
+ if (skb->priority == sch->handle)
+ return HTB_DIRECT; /* X:0 (direct flow) selected */
+ if ((cl = htb_find(skb->priority,sch)) != NULL && cl->level == 0)
+ return cl;
+
+ tcf = q->filter_list;
+ while (tcf && (result = tc_classify(skb, tcf, &res)) >= 0) {
+#ifdef CONFIG_NET_CLS_POLICE
+ if (result == TC_POLICE_SHOT)
+ return NULL;
+#endif
+ if ((cl = (void*)res.class) == NULL) {
+ if (res.classid == sch->handle)
+ return HTB_DIRECT; /* X:0 (direct flow) */
+ if ((cl = htb_find(res.classid,sch)) == NULL)
+ break; /* filter selected invalid classid */
+ }
+ if (!cl->level)
+ return cl; /* we hit leaf; return it */
+
+ /* we have got inner class; apply inner filter chain */
+ tcf = cl->filter_list;
+ }
+ /* classification failed; try to use default class */
+ cl = htb_find(TC_H_MAKE(TC_H_MAJ(sch->handle),q->defcls),sch);
+ if (!cl || cl->level)
+ return HTB_DIRECT; /* bad default .. this is safe bet */
+ return cl;
+}
+
+#ifdef HTB_DEBUG
+static void htb_next_rb_node(rb_node_t **n);
+#define HTB_DUMTREE(root,memb) if(root) { \
+ rb_node_t *n = (root)->rb_node; \
+ while (n->rb_left) n = n->rb_left; \
+ while (n) { \
+ struct htb_class *cl = rb_entry(n, struct htb_class, memb); \
+ printk(" %x",cl->classid); htb_next_rb_node (&n); \
+ } }
+
+static void htb_debug_dump (struct htb_sched *q)
+{
+ int i,p;
+ printk(KERN_DEBUG "htb*g j=%lu lj=%lu\n",jiffies,q->jiffies);
+ /* rows */
+ for (i=TC_HTB_MAXDEPTH-1;i>=0;i--) {
+ printk(KERN_DEBUG "htb*r%d m=%x",i,q->row_mask[i]);
+ for (p=0;p<TC_HTB_NUMPRIO;p++) {
+ if (!q->row[i][p].rb_node) continue;
+ printk(" p%d:",p);
+ HTB_DUMTREE(q->row[i]+p,node[p]);
+ }
+ printk("\n");
+ }
+ /* classes */
+ for (i = 0; i < HTB_HSIZE; i++) {
+ struct list_head *l;
+ list_for_each (l,q->hash+i) {
+ struct htb_class *cl = list_entry(l,struct htb_class,hlist);
+ long diff = PSCHED_TDIFF_SAFE(q->now, cl->t_c, (u32)cl->mbuffer, 0);
+ printk(KERN_DEBUG "htb*c%x m=%d t=%ld c=%ld pq=%lu df=%ld ql=%d "
+ "pa=%x f:",
+ cl->classid,cl->cmode,cl->tokens,cl->ctokens,
+ cl->pq_node.rb_color==-1?0:cl->pq_key,diff,
+ cl->level?0:cl->un.leaf.q->q.qlen,cl->prio_activity);
+ if (cl->level)
+ for (p=0;p<TC_HTB_NUMPRIO;p++) {
+ if (!cl->un.inner.feed[p].rb_node) continue;
+ printk(" p%d a=%x:",p,cl->un.inner.ptr[p]?rb_entry(cl->un.inner.ptr[p], struct htb_class,node[p])->classid:0);
+ HTB_DUMTREE(cl->un.inner.feed+p,node[p]);
+ }
+ printk("\n");
+ }
+ }
+}
+#endif
+/**
+ * htb_add_to_id_tree - adds class to the round robin list
+ *
+ * Routine adds class to the list (actually tree) sorted by classid.
+ * Make sure that class is not already on such list for given prio.
+ */
+static void htb_add_to_id_tree (HTB_ARGQ rb_root_t *root,
+ struct htb_class *cl,int prio)
+{
+ rb_node_t **p = &root->rb_node, *parent = NULL;
+ HTB_DBG(7,3,"htb_add_id_tree cl=%X prio=%d\n",cl->classid,prio);
+#ifdef HTB_DEBUG
+ if (cl->node[prio].rb_color != -1) { BUG_TRAP(0); return; }
+ HTB_CHCL(cl);
+ if (*p) {
+ struct htb_class *x = rb_entry(*p,struct htb_class,node[prio]);
+ HTB_CHCL(x);
+ }
+#endif
+ while (*p) {
+ struct htb_class *c; parent = *p;
+ c = rb_entry(parent, struct htb_class, node[prio]);
+ HTB_CHCL(c);
+ if (cl->classid > c->classid)
+ p = &parent->rb_right;
+ else
+ p = &parent->rb_left;
+ }
+ rb_link_node(&cl->node[prio], parent, p);
+ rb_insert_color(&cl->node[prio], root);
+}
+
+/**
+ * htb_add_to_wait_tree - adds class to the event queue with delay
+ *
+ * The class is added to priority event queue to indicate that class will
+ * change its mode in cl->pq_key microseconds. Make sure that class is not
+ * already in the queue.
+ */
+static void htb_add_to_wait_tree (struct htb_sched *q,
+ struct htb_class *cl,long delay,int debug_hint)
+{
+ rb_node_t **p = &q->wait_pq[cl->level].rb_node, *parent = NULL;
+ HTB_DBG(7,3,"htb_add_wt cl=%X key=%lu\n",cl->classid,cl->pq_key);
+#ifdef HTB_DEBUG
+ if (cl->pq_node.rb_color != -1) { BUG_TRAP(0); return; }
+ HTB_CHCL(cl);
+ if ((delay <= 0 || delay > cl->mbuffer) && net_ratelimit())
+ printk(KERN_ERR "HTB: suspicious delay in wait_tree d=%ld cl=%X h=%d\n",delay,cl->classid,debug_hint);
+#endif
+ cl->pq_key = q->jiffies + PSCHED_US2JIFFIE(delay);
+ if (cl->pq_key == q->jiffies)
+ cl->pq_key++;
+
+ /* update the nearest event cache */
+ if (time_after(q->near_ev_cache[cl->level], cl->pq_key))
+ q->near_ev_cache[cl->level] = cl->pq_key;
+
+ while (*p) {
+ struct htb_class *c; parent = *p;
+ c = rb_entry(parent, struct htb_class, pq_node);
+ if (time_after_eq(cl->pq_key, c->pq_key))
+ p = &parent->rb_right;
+ else
+ p = &parent->rb_left;
+ }
+ rb_link_node(&cl->pq_node, parent, p);
+ rb_insert_color(&cl->pq_node, &q->wait_pq[cl->level]);
+}
+
+/**
+ * htb_next_rb_node - finds next node in binary tree
+ *
+ * When we are past last key we return NULL.
+ * Average complexity is 2 steps per call.
+ */
+static void htb_next_rb_node(rb_node_t **n)
+{
+ rb_node_t *p;
+ if ((*n)->rb_right) {
+ /* child at right. use it or its leftmost ancestor */
+ *n = (*n)->rb_right;
+ while ((*n)->rb_left)
+ *n = (*n)->rb_left;
+ return;
+ }
+ while ((p = (*n)->rb_parent) != NULL) {
+ /* if we've arrived from left child then we have next node */
+ if (p->rb_left == *n) break;
+ *n = p;
+ }
+ *n = p;
+}
+
+/**
+ * htb_add_class_to_row - add class to its row
+ *
+ * The class is added to row at priorities marked in mask.
+ * It does nothing if mask == 0.
+ */
+static inline void htb_add_class_to_row(struct htb_sched *q,
+ struct htb_class *cl,int mask)
+{
+ HTB_DBG(7,2,"htb_addrow cl=%X mask=%X rmask=%X\n",
+ cl->classid,mask,q->row_mask[cl->level]);
+ HTB_CHCL(cl);
+ q->row_mask[cl->level] |= mask;
+ while (mask) {
+ int prio = ffz(~mask);
+ mask &= ~(1 << prio);
+ htb_add_to_id_tree(HTB_PASSQ q->row[cl->level]+prio,cl,prio);
+ }
+}
+
+/**
+ * htb_remove_class_from_row - removes class from its row
+ *
+ * The class is removed from row at priorities marked in mask.
+ * It does nothing if mask == 0.
+ */
+static __inline__ void htb_remove_class_from_row(struct htb_sched *q,
+ struct htb_class *cl,int mask)
+{
+ int m = 0;
+ HTB_CHCL(cl);
+ while (mask) {
+ int prio = ffz(~mask);
+ mask &= ~(1 << prio);
+ if (q->ptr[cl->level][prio] == cl->node+prio)
+ htb_next_rb_node(q->ptr[cl->level]+prio);
+ htb_safe_rb_erase(cl->node + prio,q->row[cl->level]+prio);
+ if (!q->row[cl->level][prio].rb_node)
+ m |= 1 << prio;
+ }
+ HTB_DBG(7,2,"htb_delrow cl=%X mask=%X rmask=%X maskdel=%X\n",
+ cl->classid,mask,q->row_mask[cl->level],m);
+ q->row_mask[cl->level] &= ~m;
+}
+
+/**
+ * htb_activate_prios - creates active classe's feed chain
+ *
+ * The class is connected to ancestors and/or appropriate rows
+ * for priorities it is participating on. cl->cmode must be new
+ * (activated) mode. It does nothing if cl->prio_activity == 0.
+ */
+static void htb_activate_prios(struct htb_sched *q,struct htb_class *cl)
+{
+ struct htb_class *p = cl->parent;
+ long m,mask = cl->prio_activity;
+ HTB_DBG(7,2,"htb_act_prios cl=%X mask=%lX cmode=%d\n",cl->classid,mask,cl->cmode);
+ HTB_CHCL(cl);
+
+ while (cl->cmode == HTB_MAY_BORROW && p && mask) {
+ HTB_CHCL(p);
+ m = mask; while (m) {
+ int prio = ffz(~m);
+ m &= ~(1 << prio);
+
+ if (p->un.inner.feed[prio].rb_node)
+ /* parent already has its feed in use so that
+ reset bit in mask as parent is already ok */
+ mask &= ~(1 << prio);
+
+ htb_add_to_id_tree(HTB_PASSQ p->un.inner.feed+prio,cl,prio);
+ }
+ HTB_DBG(7,3,"htb_act_pr_aft p=%X pact=%X mask=%lX pmode=%d\n",
+ p->classid,p->prio_activity,mask,p->cmode);
+ p->prio_activity |= mask;
+ cl = p; p = cl->parent;
+ HTB_CHCL(cl);
+ }
+ if (cl->cmode == HTB_CAN_SEND && mask)
+ htb_add_class_to_row(q,cl,mask);
+}
+
+/**
+ * htb_deactivate_prios - remove class from feed chain
+ *
+ * cl->cmode must represent old mode (before deactivation). It does
+ * nothing if cl->prio_activity == 0. Class is removed from all feed
+ * chains and rows.
+ */
+static void htb_deactivate_prios(struct htb_sched *q, struct htb_class *cl)
+{
+ struct htb_class *p = cl->parent;
+ long m,mask = cl->prio_activity;
+ HTB_DBG(7,2,"htb_deact_prios cl=%X mask=%lX cmode=%d\n",cl->classid,mask,cl->cmode);
+ HTB_CHCL(cl);
+
+ while (cl->cmode == HTB_MAY_BORROW && p && mask) {
+ m = mask; mask = 0;
+ while (m) {
+ int prio = ffz(~m);
+ m &= ~(1 << prio);
+
+ if (p->un.inner.ptr[prio] == cl->node+prio) {
+ /* we are removing child which is pointed to from
+ parent feed - forget the pointer but remember
+ classid */
+ p->un.inner.last_ptr_id[prio] = cl->classid;
+ p->un.inner.ptr[prio] = NULL;
+ }
+
+ htb_safe_rb_erase(cl->node + prio,p->un.inner.feed + prio);
+
+ if (!p->un.inner.feed[prio].rb_node)
+ mask |= 1 << prio;
+ }
+ HTB_DBG(7,3,"htb_deact_pr_aft p=%X pact=%X mask=%lX pmode=%d\n",
+ p->classid,p->prio_activity,mask,p->cmode);
+ p->prio_activity &= ~mask;
+ cl = p; p = cl->parent;
+ HTB_CHCL(cl);
+ }
+ if (cl->cmode == HTB_CAN_SEND && mask)
+ htb_remove_class_from_row(q,cl,mask);
+}
+
+/**
+ * htb_class_mode - computes and returns current class mode
+ *
+ * It computes cl's mode at time cl->t_c+diff and returns it. If mode
+ * is not HTB_CAN_SEND then cl->pq_key is updated to time difference
+ * from now to time when cl will change its state.
+ * Also it is worth to note that class mode doesn't change simply
+ * at cl->{c,}tokens == 0 but there can rather be hysteresis of
+ * 0 .. -cl->{c,}buffer range. It is meant to limit number of
+ * mode transitions per time unit. The speed gain is about 1/6.
+ */
+static __inline__ enum htb_cmode
+htb_class_mode(struct htb_class *cl,long *diff)
+{
+ long toks;
+
+ if ((toks = (cl->ctokens + *diff)) < (
+#if HTB_HYSTERESIS
+ cl->cmode != HTB_CANT_SEND ? -cl->cbuffer :
+#endif
+ 0)) {
+ *diff = -toks;
+ return HTB_CANT_SEND;
+ }
+ if ((toks = (cl->tokens + *diff)) >= (
+#if HTB_HYSTERESIS
+ cl->cmode == HTB_CAN_SEND ? -cl->buffer :
+#endif
+ 0))
+ return HTB_CAN_SEND;
+
+ *diff = -toks;
+ return HTB_MAY_BORROW;
+}
+
+/**
+ * htb_change_class_mode - changes classe's mode
+ *
+ * This should be the only way how to change classe's mode under normal
+ * cirsumstances. Routine will update feed lists linkage, change mode
+ * and add class to the wait event queue if appropriate. New mode should
+ * be different from old one and cl->pq_key has to be valid if changing
+ * to mode other than HTB_CAN_SEND (see htb_add_to_wait_tree).
+ */
+static void
+htb_change_class_mode(struct htb_sched *q, struct htb_class *cl, long *diff)
+{
+ enum htb_cmode new_mode = htb_class_mode(cl,diff);
+
+ HTB_CHCL(cl);
+ HTB_DBG(7,1,"htb_chging_clmode %d->%d cl=%X\n",cl->cmode,new_mode,cl->classid);
+
+ if (new_mode == cl->cmode)
+ return;
+
+ if (cl->prio_activity) { /* not neccessary: speed optimization */
+ if (cl->cmode != HTB_CANT_SEND)
+ htb_deactivate_prios(q,cl);
+ cl->cmode = new_mode;
+ if (new_mode != HTB_CANT_SEND)
+ htb_activate_prios(q,cl);
+ } else
+ cl->cmode = new_mode;
+}
+
+/**
+ * htb_activate - inserts leaf cl into appropriate active feeds
+ *
+ * Routine learns (new) priority of leaf and activates feed chain
+ * for the prio. It can be called on already active leaf safely.
+ * It also adds leaf into droplist.
+ */
+static __inline__ void htb_activate(struct htb_sched *q,struct htb_class *cl)
+{
+ BUG_TRAP(!cl->level && cl->un.leaf.q && cl->un.leaf.q->q.qlen);
+ HTB_CHCL(cl);
+ if (!cl->prio_activity) {
+ cl->prio_activity = 1 << (cl->un.leaf.aprio = cl->un.leaf.prio);
+ htb_activate_prios(q,cl);
+ list_add_tail(&cl->un.leaf.drop_list,q->drops+cl->un.leaf.aprio);
+ }
+}
+
+/**
+ * htb_deactivate - remove leaf cl from active feeds
+ *
+ * Make sure that leaf is active. In the other words it can't be called
+ * with non-active leaf. It also removes class from the drop list.
+ */
+static __inline__ void
+htb_deactivate(struct htb_sched *q,struct htb_class *cl)
+{
+ BUG_TRAP(cl->prio_activity);
+ HTB_CHCL(cl);
+ htb_deactivate_prios(q,cl);
+ cl->prio_activity = 0;
+ list_del_init(&cl->un.leaf.drop_list);
+}
+
+static int htb_enqueue(struct sk_buff *skb, struct Qdisc *sch)
+{
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ struct htb_class *cl = htb_classify(skb,sch);
+
+ if (cl == HTB_DIRECT || !cl) {
+ /* enqueue to helper queue */
+ if (q->direct_queue.qlen < q->direct_qlen && cl) {
+ __skb_queue_tail(&q->direct_queue, skb);
+ q->direct_pkts++;
+ } else {
+ kfree_skb (skb);
+ sch->stats.drops++;
+ return NET_XMIT_DROP;
+ }
+ } else if (cl->un.leaf.q->enqueue(skb, cl->un.leaf.q) != NET_XMIT_SUCCESS) {
+ sch->stats.drops++;
+ cl->stats.drops++;
+ return NET_XMIT_DROP;
+ } else {
+ cl->stats.packets++; cl->stats.bytes += skb->len;
+ htb_activate (q,cl);
+ }
+
+ sch->q.qlen++;
+ sch->stats.packets++; sch->stats.bytes += skb->len;
+ HTB_DBG(1,1,"htb_enq_ok cl=%X skb=%p\n",(cl && cl != HTB_DIRECT)?cl->classid:0,skb);
+ return NET_XMIT_SUCCESS;
+}
+
+/* TODO: requeuing packet charges it to policers again !! */
+static int htb_requeue(struct sk_buff *skb, struct Qdisc *sch)
+{
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ struct htb_class *cl = htb_classify(skb,sch);
+ struct sk_buff *tskb;
+
+ if (cl == HTB_DIRECT || !cl) {
+ /* enqueue to helper queue */
+ if (q->direct_queue.qlen < q->direct_qlen && cl) {
+ __skb_queue_head(&q->direct_queue, skb);
+ } else {
+ __skb_queue_head(&q->direct_queue, skb);
+ tskb = __skb_dequeue_tail(&q->direct_queue);
+ kfree_skb (tskb);
+ sch->stats.drops++;
+ return NET_XMIT_CN;
+ }
+ } else if (cl->un.leaf.q->ops->requeue(skb, cl->un.leaf.q) != NET_XMIT_SUCCESS) {
+ sch->stats.drops++;
+ cl->stats.drops++;
+ return NET_XMIT_DROP;
+ } else
+ htb_activate (q,cl);
+
+ sch->q.qlen++;
+ HTB_DBG(1,1,"htb_req_ok cl=%X skb=%p\n",(cl && cl != HTB_DIRECT)?cl->classid:0,skb);
+ return NET_XMIT_SUCCESS;
+}
+
+static void htb_timer(unsigned long arg)
+{
+ struct Qdisc *sch = (struct Qdisc*)arg;
+ sch->flags &= ~TCQ_F_THROTTLED;
+ wmb();
+ netif_schedule(sch->dev);
+}
+
+#ifdef HTB_RATECM
+#define RT_GEN(D,R) R+=D-(R/HTB_EWMAC);D=0
+static void htb_rate_timer(unsigned long arg)
+{
+ struct Qdisc *sch = (struct Qdisc*)arg;
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ struct list_head *p;
+
+ /* lock queue so that we can muck with it */
+ HTB_QLOCK(sch);
+ HTB_DBG(10,1,"htb_rttmr j=%ld\n",jiffies);
+
+ q->rttim.expires = jiffies + HZ;
+ add_timer(&q->rttim);
+
+ /* scan and recompute one bucket at time */
+ if (++q->recmp_bucket >= HTB_HSIZE)
+ q->recmp_bucket = 0;
+ list_for_each (p,q->hash+q->recmp_bucket) {
+ struct htb_class *cl = list_entry(p,struct htb_class,hlist);
+ HTB_DBG(10,2,"htb_rttmr_cl cl=%X sbyte=%lu spkt=%lu\n",
+ cl->classid,cl->sum_bytes,cl->sum_packets);
+ RT_GEN (cl->sum_bytes,cl->rate_bytes);
+ RT_GEN (cl->sum_packets,cl->rate_packets);
+ }
+ HTB_QUNLOCK(sch);
+}
+#endif
+
+/**
+ * htb_charge_class - charges ammount "bytes" to leaf and ancestors
+ *
+ * Routine assumes that packet "bytes" long was dequeued from leaf cl
+ * borrowing from "level". It accounts bytes to ceil leaky bucket for
+ * leaf and all ancestors and to rate bucket for ancestors at levels
+ * "level" and higher. It also handles possible change of mode resulting
+ * from the update. Note that mode can also increase here (MAY_BORROW to
+ * CAN_SEND) because we can use more precise clock that event queue here.
+ * In such case we remove class from event queue first.
+ */
+static void htb_charge_class(struct htb_sched *q,struct htb_class *cl,
+ int level,int bytes)
+{
+ long toks,diff;
+ enum htb_cmode old_mode;
+ HTB_DBG(5,1,"htb_chrg_cl cl=%X lev=%d len=%d\n",cl->classid,level,bytes);
+
+#define HTB_ACCNT(T,B,R) toks = diff + cl->T; \
+ if (toks > cl->B) toks = cl->B; \
+ toks -= L2T(cl, cl->R, bytes); \
+ if (toks <= -cl->mbuffer) toks = 1-cl->mbuffer; \
+ cl->T = toks
+
+ while (cl) {
+ HTB_CHCL(cl);
+ diff = PSCHED_TDIFF_SAFE(q->now, cl->t_c, (u32)cl->mbuffer, 0);
+#ifdef HTB_DEBUG
+ if (diff > cl->mbuffer || diff < 0 || PSCHED_TLESS(q->now, cl->t_c)) {
+ if (net_ratelimit())
+ printk(KERN_ERR "HTB: bad diff in charge, cl=%X diff=%lX now=%Lu then=%Lu j=%lu\n",
+ cl->classid, diff,
+ (unsigned long long) q->now,
+ (unsigned long long) cl->t_c,
+ q->jiffies);
+ diff = 1000;
+ }
+#endif
+ if (cl->level >= level) {
+ if (cl->level == level) cl->xstats.lends++;
+ HTB_ACCNT (tokens,buffer,rate);
+ } else {
+ cl->xstats.borrows++;
+ cl->tokens += diff; /* we moved t_c; update tokens */
+ }
+ HTB_ACCNT (ctokens,cbuffer,ceil);
+ cl->t_c = q->now;
+ HTB_DBG(5,2,"htb_chrg_clp cl=%X diff=%ld tok=%ld ctok=%ld\n",cl->classid,diff,cl->tokens,cl->ctokens);
+
+ old_mode = cl->cmode; diff = 0;
+ htb_change_class_mode(q,cl,&diff);
+ if (old_mode != cl->cmode) {
+ if (old_mode != HTB_CAN_SEND)
+ htb_safe_rb_erase(&cl->pq_node,q->wait_pq+cl->level);
+ if (cl->cmode != HTB_CAN_SEND)
+ htb_add_to_wait_tree (q,cl,diff,1);
+ }
+
+#ifdef HTB_RATECM
+ /* update rate counters */
+ cl->sum_bytes += bytes; cl->sum_packets++;
+#endif
+
+ /* update byte stats except for leaves which are already updated */
+ if (cl->level) {
+ cl->stats.bytes += bytes;
+ cl->stats.packets++;
+ }
+ cl = cl->parent;
+ }
+}
+
+/**
+ * htb_do_events - make mode changes to classes at the level
+ *
+ * Scans event queue for pending events and applies them. Returns jiffies to
+ * next pending event (0 for no event in pq).
+ * Note: Aplied are events whose have cl->pq_key <= jiffies.
+ */
+static long htb_do_events(struct htb_sched *q,int level)
+{
+ int i;
+ HTB_DBG(8,1,"htb_do_events l=%d root=%p rmask=%X\n",
+ level,q->wait_pq[level].rb_node,q->row_mask[level]);
+ for (i = 0; i < 500; i++) {
+ struct htb_class *cl;
+ long diff;
+ rb_node_t *p = q->wait_pq[level].rb_node;
+ if (!p) return 0;
+ while (p->rb_left) p = p->rb_left;
+
+ cl = rb_entry(p, struct htb_class, pq_node);
+ if (time_after(cl->pq_key, q->jiffies)) {
+ HTB_DBG(8,3,"htb_do_ev_ret delay=%ld\n",cl->pq_key - q->jiffies);
+ return cl->pq_key - q->jiffies;
+ }
+ htb_safe_rb_erase(p,q->wait_pq+level);
+ diff = PSCHED_TDIFF_SAFE(q->now, cl->t_c, (u32)cl->mbuffer, 0);
+#ifdef HTB_DEBUG
+ if (diff > cl->mbuffer || diff < 0 || PSCHED_TLESS(q->now, cl->t_c)) {
+ if (net_ratelimit())
+ printk(KERN_ERR "HTB: bad diff in events, cl=%X diff=%lX now=%Lu then=%Lu j=%lu\n",
+ cl->classid, diff,
+ (unsigned long long) q->now,
+ (unsigned long long) cl->t_c,
+ q->jiffies);
+ diff = 1000;
+ }
+#endif
+ htb_change_class_mode(q,cl,&diff);
+ if (cl->cmode != HTB_CAN_SEND)
+ htb_add_to_wait_tree (q,cl,diff,2);
+ }
+ if (net_ratelimit())
+ printk(KERN_WARNING "htb: too many events !\n");
+ return HZ/10;
+}
+
+/* Returns class->node+prio from id-tree where classe's id is >= id. NULL
+ is no such one exists. */
+static rb_node_t *
+htb_id_find_next_upper(int prio,rb_node_t *n,u32 id)
+{
+ rb_node_t *r = NULL;
+ while (n) {
+ struct htb_class *cl = rb_entry(n,struct htb_class,node[prio]);
+ if (id == cl->classid) return n;
+
+ if (id > cl->classid) {
+ n = n->rb_right;
+ } else {
+ r = n;
+ n = n->rb_left;
+ }
+ }
+ return r;
+}
+
+/**
+ * htb_lookup_leaf - returns next leaf class in DRR order
+ *
+ * Find leaf where current feed pointers points to.
+ */
+static struct htb_class *
+htb_lookup_leaf(HTB_ARGQ rb_root_t *tree,int prio,rb_node_t **pptr,u32 *pid)
+{
+ int i;
+ struct {
+ rb_node_t *root;
+ rb_node_t **pptr;
+ u32 *pid;
+ } stk[TC_HTB_MAXDEPTH],*sp = stk;
+
+ BUG_TRAP(tree->rb_node);
+ sp->root = tree->rb_node;
+ sp->pptr = pptr;
+ sp->pid = pid;
+
+ for (i = 0; i < 65535; i++) {
+ HTB_DBG(4,2,"htb_lleaf ptr=%p pid=%X\n",*sp->pptr,*sp->pid);
+
+ if (!*sp->pptr && *sp->pid) {
+ /* ptr was invalidated but id is valid - try to recover
+ the original or next ptr */
+ *sp->pptr = htb_id_find_next_upper(prio,sp->root,*sp->pid);
+ }
+ *sp->pid = 0; /* ptr is valid now so that remove this hint as it
+ can become out of date quickly */
+ if (!*sp->pptr) { /* we are at right end; rewind & go up */
+ *sp->pptr = sp->root;
+ while ((*sp->pptr)->rb_left)
+ *sp->pptr = (*sp->pptr)->rb_left;
+ if (sp > stk) {
+ sp--;
+ BUG_TRAP(*sp->pptr); if(!*sp->pptr) return NULL;
+ htb_next_rb_node (sp->pptr);
+ }
+ } else {
+ struct htb_class *cl;
+ cl = rb_entry(*sp->pptr,struct htb_class,node[prio]);
+ HTB_CHCL(cl);
+ if (!cl->level)
+ return cl;
+ (++sp)->root = cl->un.inner.feed[prio].rb_node;
+ sp->pptr = cl->un.inner.ptr+prio;
+ sp->pid = cl->un.inner.last_ptr_id+prio;
+ }
+ }
+ BUG_TRAP(0);
+ return NULL;
+}
+
+/* dequeues packet at given priority and level; call only if
+ you are sure that there is active class at prio/level */
+static struct sk_buff *
+htb_dequeue_tree(struct htb_sched *q,int prio,int level)
+{
+ struct sk_buff *skb = NULL;
+ struct htb_class *cl,*start;
+ /* look initial class up in the row */
+ start = cl = htb_lookup_leaf (HTB_PASSQ q->row[level]+prio,prio,
+ q->ptr[level]+prio,q->last_ptr_id[level]+prio);
+
+ do {
+next:
+ BUG_TRAP(cl);
+ if (!cl) return NULL;
+ HTB_DBG(4,1,"htb_deq_tr prio=%d lev=%d cl=%X defic=%d\n",
+ prio,level,cl->classid,cl->un.leaf.deficit[level]);
+
+ /* class can be empty - it is unlikely but can be true if leaf
+ qdisc drops packets in enqueue routine or if someone used
+ graft operation on the leaf since last dequeue;
+ simply deactivate and skip such class */
+ if (unlikely(cl->un.leaf.q->q.qlen == 0)) {
+ struct htb_class *next;
+ htb_deactivate(q,cl);
+
+ /* row/level might become empty */
+ if ((q->row_mask[level] & (1 << prio)) == 0)
+ return NULL;
+
+ next = htb_lookup_leaf (HTB_PASSQ q->row[level]+prio,
+ prio,q->ptr[level]+prio,q->last_ptr_id[level]+prio);
+ if (cl == start) /* fix start if we just deleted it */
+ start = next;
+ cl = next;
+ goto next;
+ }
+
+ if (likely((skb = cl->un.leaf.q->dequeue(cl->un.leaf.q)) != NULL))
+ break;
+ if (!cl->warned) {
+ printk(KERN_WARNING "htb: class %X isn't work conserving ?!\n",cl->classid);
+ cl->warned = 1;
+ }
+ q->nwc_hit++;
+ htb_next_rb_node((level?cl->parent->un.inner.ptr:q->ptr[0])+prio);
+ cl = htb_lookup_leaf (HTB_PASSQ q->row[level]+prio,prio,q->ptr[level]+prio,
+ q->last_ptr_id[level]+prio);
+ } while (cl != start);
+
+ if (likely(skb != NULL)) {
+ if ((cl->un.leaf.deficit[level] -= skb->len) < 0) {
+ HTB_DBG(4,2,"htb_next_cl oldptr=%p quant_add=%d\n",
+ level?cl->parent->un.inner.ptr[prio]:q->ptr[0][prio],cl->un.leaf.quantum);
+ cl->un.leaf.deficit[level] += cl->un.leaf.quantum;
+ htb_next_rb_node((level?cl->parent->un.inner.ptr:q->ptr[0])+prio);
+ }
+ /* this used to be after charge_class but this constelation
+ gives us slightly better performance */
+ if (!cl->un.leaf.q->q.qlen)
+ htb_deactivate (q,cl);
+ htb_charge_class (q,cl,level,skb->len);
+ }
+ return skb;
+}
+
+static void htb_delay_by(struct Qdisc *sch,long delay)
+{
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ if (delay <= 0) delay = 1;
+ if (unlikely(delay > 5*HZ)) {
+ if (net_ratelimit())
+ printk(KERN_INFO "HTB delay %ld > 5sec\n", delay);
+ delay = 5*HZ;
+ }
+ /* why don't use jiffies here ? because expires can be in past */
+ mod_timer(&q->timer, q->jiffies + delay);
+ sch->flags |= TCQ_F_THROTTLED;
+ sch->stats.overlimits++;
+ HTB_DBG(3,1,"htb_deq t_delay=%ld\n",delay);
+}
+
+static struct sk_buff *htb_dequeue(struct Qdisc *sch)
+{
+ struct sk_buff *skb = NULL;
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ int level;
+ long min_delay;
+#ifdef HTB_DEBUG
+ int evs_used = 0;
+#endif
+
+ q->jiffies = jiffies;
+ HTB_DBG(3,1,"htb_deq dircnt=%d qlen=%d\n",skb_queue_len(&q->direct_queue),
+ sch->q.qlen);
+
+ /* try to dequeue direct packets as high prio (!) to minimize cpu work */
+ if ((skb = __skb_dequeue(&q->direct_queue)) != NULL) {
+ sch->flags &= ~TCQ_F_THROTTLED;
+ sch->q.qlen--;
+ return skb;
+ }
+
+ if (!sch->q.qlen) goto fin;
+ PSCHED_GET_TIME(q->now);
+
+ min_delay = LONG_MAX;
+ q->nwc_hit = 0;
+ for (level = 0; level < TC_HTB_MAXDEPTH; level++) {
+ /* common case optimization - skip event handler quickly */
+ int m;
+ long delay;
+ if (time_after_eq(q->jiffies, q->near_ev_cache[level])) {
+ delay = htb_do_events(q,level);
+ q->near_ev_cache[level] = q->jiffies + (delay ? delay : HZ);
+#ifdef HTB_DEBUG
+ evs_used++;
+#endif
+ } else
+ delay = q->near_ev_cache[level] - q->jiffies;
+
+ if (delay && min_delay > delay)
+ min_delay = delay;
+ m = ~q->row_mask[level];
+ while (m != (int)(-1)) {
+ int prio = ffz (m);
+ m |= 1 << prio;
+ skb = htb_dequeue_tree(q,prio,level);
+ if (likely(skb != NULL)) {
+ sch->q.qlen--;
+ sch->flags &= ~TCQ_F_THROTTLED;
+ goto fin;
+ }
+ }
+ }
+#ifdef HTB_DEBUG
+ if (!q->nwc_hit && min_delay >= 10*HZ && net_ratelimit()) {
+ if (min_delay == LONG_MAX) {
+ printk(KERN_ERR "HTB: dequeue bug (%d,%lu,%lu), report it please !\n",
+ evs_used,q->jiffies,jiffies);
+ htb_debug_dump(q);
+ } else
+ printk(KERN_WARNING "HTB: mindelay=%ld, some class has "
+ "too small rate\n",min_delay);
+ }
+#endif
+ htb_delay_by (sch,min_delay > 5*HZ ? 5*HZ : min_delay);
+fin:
+ HTB_DBG(3,1,"htb_deq_end %s j=%lu skb=%p\n",sch->dev->name,q->jiffies,skb);
+ return skb;
+}
+
+/* try to drop from each class (by prio) until one succeed */
+static unsigned int htb_drop(struct Qdisc* sch)
+{
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ int prio;
+
+ for (prio = TC_HTB_NUMPRIO - 1; prio >= 0; prio--) {
+ struct list_head *p;
+ list_for_each (p,q->drops+prio) {
+ struct htb_class *cl = list_entry(p, struct htb_class,
+ un.leaf.drop_list);
+ unsigned int len;
+ if (cl->un.leaf.q->ops->drop &&
+ (len = cl->un.leaf.q->ops->drop(cl->un.leaf.q))) {
+ sch->q.qlen--;
+ if (!cl->un.leaf.q->q.qlen)
+ htb_deactivate (q,cl);
+ return len;
+ }
+ }
+ }
+ return 0;
+}
+
+/* reset all classes */
+/* always caled under BH & queue lock */
+static void htb_reset(struct Qdisc* sch)
+{
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ int i;
+ HTB_DBG(0,1,"htb_reset sch=%p, handle=%X\n",sch,sch->handle);
+
+ for (i = 0; i < HTB_HSIZE; i++) {
+ struct list_head *p;
+ list_for_each (p,q->hash+i) {
+ struct htb_class *cl = list_entry(p,struct htb_class,hlist);
+ if (cl->level)
+ memset(&cl->un.inner,0,sizeof(cl->un.inner));
+ else {
+ if (cl->un.leaf.q)
+ qdisc_reset(cl->un.leaf.q);
+ INIT_LIST_HEAD(&cl->un.leaf.drop_list);
+ }
+ cl->prio_activity = 0;
+ cl->cmode = HTB_CAN_SEND;
+#ifdef HTB_DEBUG
+ cl->pq_node.rb_color = -1;
+ memset(cl->node,255,sizeof(cl->node));
+#endif
+
+ }
+ }
+ sch->flags &= ~TCQ_F_THROTTLED;
+ del_timer(&q->timer);
+ __skb_queue_purge(&q->direct_queue);
+ sch->q.qlen = 0;
+ memset(q->row,0,sizeof(q->row));
+ memset(q->row_mask,0,sizeof(q->row_mask));
+ memset(q->wait_pq,0,sizeof(q->wait_pq));
+ memset(q->ptr,0,sizeof(q->ptr));
+ for (i = 0; i < TC_HTB_NUMPRIO; i++)
+ INIT_LIST_HEAD(q->drops+i);
+}
+
+static int htb_init(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct htb_sched *q = (struct htb_sched*)sch->data;
+ struct rtattr *tb[TCA_HTB_INIT];
+ struct tc_htb_glob *gopt;
+ int i;
+#ifdef HTB_DEBUG
+ printk(KERN_INFO "HTB init, kernel part version %d.%d\n",
+ HTB_VER >> 16,HTB_VER & 0xffff);
+#endif
+ if (!opt || rtattr_parse(tb, TCA_HTB_INIT, RTA_DATA(opt), RTA_PAYLOAD(opt)) ||
+ tb[TCA_HTB_INIT-1] == NULL ||
+ RTA_PAYLOAD(tb[TCA_HTB_INIT-1]) < sizeof(*gopt)) {
+ printk(KERN_ERR "HTB: hey probably you have bad tc tool ?\n");
+ return -EINVAL;
+ }
+ gopt = RTA_DATA(tb[TCA_HTB_INIT-1]);
+ if (gopt->version != HTB_VER >> 16) {
+ printk(KERN_ERR "HTB: need tc/htb version %d (minor is %d), you have %d\n",
+ HTB_VER >> 16,HTB_VER & 0xffff,gopt->version);
+ return -EINVAL;
+ }
+ q->debug = gopt->debug;
+ HTB_DBG(0,1,"htb_init sch=%p handle=%X r2q=%d\n",sch,sch->handle,gopt->rate2quantum);
+
+ INIT_LIST_HEAD(&q->root);
+ for (i = 0; i < HTB_HSIZE; i++)
+ INIT_LIST_HEAD(q->hash+i);
+ for (i = 0; i < TC_HTB_NUMPRIO; i++)
+ INIT_LIST_HEAD(q->drops+i);
+
+ init_timer(&q->timer);
+ skb_queue_head_init(&q->direct_queue);
+
+ q->direct_qlen = sch->dev->tx_queue_len;
+ if (q->direct_qlen < 2) /* some devices have zero tx_queue_len */
+ q->direct_qlen = 2;
+ q->timer.function = htb_timer;
+ q->timer.data = (unsigned long)sch;
+
+#ifdef HTB_RATECM
+ init_timer(&q->rttim);
+ q->rttim.function = htb_rate_timer;
+ q->rttim.data = (unsigned long)sch;
+ q->rttim.expires = jiffies + HZ;
+ add_timer(&q->rttim);
+#endif
+ if ((q->rate2quantum = gopt->rate2quantum) < 1)
+ q->rate2quantum = 1;
+ q->defcls = gopt->defcls;
+
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int htb_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+ struct htb_sched *q = (struct htb_sched*)sch->data;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+ struct tc_htb_glob gopt;
+ HTB_DBG(0,1,"htb_dump sch=%p, handle=%X\n",sch,sch->handle);
+ /* stats */
+ HTB_QLOCK(sch);
+ gopt.direct_pkts = q->direct_pkts;
+
+#ifdef HTB_DEBUG
+ if (HTB_DBG_COND(0,2))
+ htb_debug_dump(q);
+#endif
+ gopt.version = HTB_VER;
+ gopt.rate2quantum = q->rate2quantum;
+ gopt.defcls = q->defcls;
+ gopt.debug = q->debug;
+ rta = (struct rtattr*)b;
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+ RTA_PUT(skb, TCA_HTB_INIT, sizeof(gopt), &gopt);
+ rta->rta_len = skb->tail - b;
+ HTB_QUNLOCK(sch);
+ return skb->len;
+rtattr_failure:
+ HTB_QUNLOCK(sch);
+ skb_trim(skb, skb->tail - skb->data);
+ return -1;
+}
+
+static int htb_dump_class(struct Qdisc *sch, unsigned long arg,
+ struct sk_buff *skb, struct tcmsg *tcm)
+{
+#ifdef HTB_DEBUG
+ struct htb_sched *q = (struct htb_sched*)sch->data;
+#endif
+ struct htb_class *cl = (struct htb_class*)arg;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+ struct tc_htb_opt opt;
+
+ HTB_DBG(0,1,"htb_dump_class handle=%X clid=%X\n",sch->handle,cl->classid);
+
+ HTB_QLOCK(sch);
+ tcm->tcm_parent = cl->parent ? cl->parent->classid : TC_H_ROOT;
+ tcm->tcm_handle = cl->classid;
+ if (!cl->level && cl->un.leaf.q) {
+ tcm->tcm_info = cl->un.leaf.q->handle;
+ cl->stats.qlen = cl->un.leaf.q->q.qlen;
+ }
+
+ rta = (struct rtattr*)b;
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+
+ memset (&opt,0,sizeof(opt));
+
+ opt.rate = cl->rate->rate; opt.buffer = cl->buffer;
+ opt.ceil = cl->ceil->rate; opt.cbuffer = cl->cbuffer;
+ opt.quantum = cl->un.leaf.quantum; opt.prio = cl->un.leaf.prio;
+ opt.level = cl->level;
+ RTA_PUT(skb, TCA_HTB_PARMS, sizeof(opt), &opt);
+ rta->rta_len = skb->tail - b;
+
+#ifdef HTB_RATECM
+ cl->stats.bps = cl->rate_bytes/(HTB_EWMAC*HTB_HSIZE);
+ cl->stats.pps = cl->rate_packets/(HTB_EWMAC*HTB_HSIZE);
+#endif
+
+ cl->xstats.tokens = cl->tokens;
+ cl->xstats.ctokens = cl->ctokens;
+ RTA_PUT(skb, TCA_STATS, sizeof(cl->stats), &cl->stats);
+ RTA_PUT(skb, TCA_XSTATS, sizeof(cl->xstats), &cl->xstats);
+ HTB_QUNLOCK(sch);
+ return skb->len;
+rtattr_failure:
+ HTB_QUNLOCK(sch);
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int htb_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
+ struct Qdisc **old)
+{
+ struct htb_class *cl = (struct htb_class*)arg;
+
+ if (cl && !cl->level) {
+ if (new == NULL && (new = qdisc_create_dflt(sch->dev,
+ &pfifo_qdisc_ops)) == NULL)
+ return -ENOBUFS;
+ sch_tree_lock(sch);
+ if ((*old = xchg(&cl->un.leaf.q, new)) != NULL) {
+ if (cl->prio_activity)
+ htb_deactivate ((struct htb_sched*)sch->data,cl);
+
+ /* TODO: is it correct ? Why CBQ doesn't do it ? */
+ sch->q.qlen -= (*old)->q.qlen;
+ qdisc_reset(*old);
+ }
+ sch_tree_unlock(sch);
+ return 0;
+ }
+ return -ENOENT;
+}
+
+static struct Qdisc * htb_leaf(struct Qdisc *sch, unsigned long arg)
+{
+ struct htb_class *cl = (struct htb_class*)arg;
+ return (cl && !cl->level) ? cl->un.leaf.q : NULL;
+}
+
+static unsigned long htb_get(struct Qdisc *sch, u32 classid)
+{
+#ifdef HTB_DEBUG
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+#endif
+ struct htb_class *cl = htb_find(classid,sch);
+ HTB_DBG(0,1,"htb_get clid=%X q=%p cl=%p ref=%d\n",classid,q,cl,cl?cl->refcnt:0);
+ if (cl)
+ cl->refcnt++;
+ return (unsigned long)cl;
+}
+
+static void htb_destroy_filters(struct tcf_proto **fl)
+{
+ struct tcf_proto *tp;
+
+ while ((tp = *fl) != NULL) {
+ *fl = tp->next;
+ tcf_destroy(tp);
+ }
+}
+
+static void htb_destroy_class(struct Qdisc* sch,struct htb_class *cl)
+{
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ HTB_DBG(0,1,"htb_destrycls clid=%X ref=%d\n", cl?cl->classid:0,cl?cl->refcnt:0);
+ if (!cl->level) {
+ BUG_TRAP(cl->un.leaf.q);
+ sch->q.qlen -= cl->un.leaf.q->q.qlen;
+ qdisc_destroy(cl->un.leaf.q);
+ }
+ qdisc_put_rtab(cl->rate);
+ qdisc_put_rtab(cl->ceil);
+
+#ifdef CONFIG_NET_ESTIMATOR
+ qdisc_kill_estimator(&cl->stats);
+#endif
+ htb_destroy_filters (&cl->filter_list);
+
+ while (!list_empty(&cl->children))
+ htb_destroy_class (sch,list_entry(cl->children.next,
+ struct htb_class,sibling));
+
+ /* note: this delete may happen twice (see htb_delete) */
+ list_del(&cl->hlist);
+ list_del(&cl->sibling);
+
+ if (cl->prio_activity)
+ htb_deactivate (q,cl);
+
+ if (cl->cmode != HTB_CAN_SEND)
+ htb_safe_rb_erase(&cl->pq_node,q->wait_pq+cl->level);
+
+ kfree(cl);
+}
+
+/* always caled under BH & queue lock */
+static void htb_destroy(struct Qdisc* sch)
+{
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ HTB_DBG(0,1,"htb_destroy q=%p\n",q);
+
+ del_timer_sync (&q->timer);
+#ifdef HTB_RATECM
+ del_timer_sync (&q->rttim);
+#endif
+ /* This line used to be after htb_destroy_class call below
+ and surprisingly it worked in 2.4. But it must precede it
+ because filter need its target class alive to be able to call
+ unbind_filter on it (without Oops). */
+ htb_destroy_filters(&q->filter_list);
+
+ while (!list_empty(&q->root))
+ htb_destroy_class (sch,list_entry(q->root.next,
+ struct htb_class,sibling));
+
+ __skb_queue_purge(&q->direct_queue);
+ MOD_DEC_USE_COUNT;
+}
+
+static int htb_delete(struct Qdisc *sch, unsigned long arg)
+{
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ struct htb_class *cl = (struct htb_class*)arg;
+ HTB_DBG(0,1,"htb_delete q=%p cl=%X ref=%d\n",q,cl?cl->classid:0,cl?cl->refcnt:0);
+
+ // TODO: why don't allow to delete subtree ? references ? does
+ // tc subsys quarantee us that in htb_destroy it holds no class
+ // refs so that we can remove children safely there ?
+ if (!list_empty(&cl->children) || cl->filter_cnt)
+ return -EBUSY;
+
+ sch_tree_lock(sch);
+
+ /* delete from hash and active; remainder in destroy_class */
+ list_del_init(&cl->hlist);
+ if (cl->prio_activity)
+ htb_deactivate (q,cl);
+
+ if (--cl->refcnt == 0)
+ htb_destroy_class(sch,cl);
+
+ sch_tree_unlock(sch);
+ return 0;
+}
+
+static void htb_put(struct Qdisc *sch, unsigned long arg)
+{
+#ifdef HTB_DEBUG
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+#endif
+ struct htb_class *cl = (struct htb_class*)arg;
+ HTB_DBG(0,1,"htb_put q=%p cl=%X ref=%d\n",q,cl?cl->classid:0,cl?cl->refcnt:0);
+
+ if (--cl->refcnt == 0)
+ htb_destroy_class(sch,cl);
+}
+
+static int htb_change_class(struct Qdisc *sch, u32 classid,
+ u32 parentid, struct rtattr **tca, unsigned long *arg)
+{
+ int err = -EINVAL;
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ struct htb_class *cl = (struct htb_class*)*arg,*parent;
+ struct rtattr *opt = tca[TCA_OPTIONS-1];
+ struct qdisc_rate_table *rtab = NULL, *ctab = NULL;
+ struct rtattr *tb[TCA_HTB_RTAB];
+ struct tc_htb_opt *hopt;
+
+ /* extract all subattrs from opt attr */
+ if (!opt || rtattr_parse(tb, TCA_HTB_RTAB, RTA_DATA(opt), RTA_PAYLOAD(opt)) ||
+ tb[TCA_HTB_PARMS-1] == NULL ||
+ RTA_PAYLOAD(tb[TCA_HTB_PARMS-1]) < sizeof(*hopt))
+ goto failure;
+
+ parent = parentid == TC_H_ROOT ? NULL : htb_find (parentid,sch);
+
+ hopt = RTA_DATA(tb[TCA_HTB_PARMS-1]);
+ HTB_DBG(0,1,"htb_chg cl=%p(%X), clid=%X, parid=%X, opt/prio=%d, rate=%u, buff=%d, quant=%d\n", cl,cl?cl->classid:0,classid,parentid,(int)hopt->prio,hopt->rate.rate,hopt->buffer,hopt->quantum);
+ rtab = qdisc_get_rtab(&hopt->rate, tb[TCA_HTB_RTAB-1]);
+ ctab = qdisc_get_rtab(&hopt->ceil, tb[TCA_HTB_CTAB-1]);
+ if (!rtab || !ctab) goto failure;
+
+ if (!cl) { /* new class */
+ struct Qdisc *new_q;
+ /* check for valid classid */
+ if (!classid || TC_H_MAJ(classid^sch->handle) || htb_find(classid,sch))
+ goto failure;
+
+ /* check maximal depth */
+ if (parent && parent->parent && parent->parent->level < 2) {
+ printk(KERN_ERR "htb: tree is too deep\n");
+ goto failure;
+ }
+ err = -ENOBUFS;
+ if ((cl = kmalloc(sizeof(*cl), GFP_KERNEL)) == NULL)
+ goto failure;
+
+ memset(cl, 0, sizeof(*cl));
+ cl->refcnt = 1;
+ INIT_LIST_HEAD(&cl->sibling);
+ INIT_LIST_HEAD(&cl->hlist);
+ INIT_LIST_HEAD(&cl->children);
+ INIT_LIST_HEAD(&cl->un.leaf.drop_list);
+#ifdef HTB_DEBUG
+ cl->magic = HTB_CMAGIC;
+#endif
+
+ /* create leaf qdisc early because it uses kmalloc(GFP_KERNEL)
+ so that can't be used inside of sch_tree_lock
+ -- thanks to Karlis Peisenieks */
+ new_q = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops);
+ sch_tree_lock(sch);
+ if (parent && !parent->level) {
+ /* turn parent into inner node */
+ sch->q.qlen -= parent->un.leaf.q->q.qlen;
+ qdisc_destroy (parent->un.leaf.q);
+ if (parent->prio_activity)
+ htb_deactivate (q,parent);
+
+ /* remove from evt list because of level change */
+ if (parent->cmode != HTB_CAN_SEND) {
+ htb_safe_rb_erase(&parent->pq_node,q->wait_pq /*+0*/);
+ parent->cmode = HTB_CAN_SEND;
+ }
+ parent->level = (parent->parent ? parent->parent->level
+ : TC_HTB_MAXDEPTH) - 1;
+ memset (&parent->un.inner,0,sizeof(parent->un.inner));
+ }
+ /* leaf (we) needs elementary qdisc */
+ cl->un.leaf.q = new_q ? new_q : &noop_qdisc;
+
+ cl->classid = classid; cl->parent = parent;
+
+ /* set class to be in HTB_CAN_SEND state */
+ cl->tokens = hopt->buffer;
+ cl->ctokens = hopt->cbuffer;
+ cl->mbuffer = 60000000; /* 1min */
+ PSCHED_GET_TIME(cl->t_c);
+ cl->cmode = HTB_CAN_SEND;
+
+ /* attach to the hash list and parent's family */
+ list_add_tail(&cl->hlist, q->hash+htb_hash(classid));
+ list_add_tail(&cl->sibling, parent ? &parent->children : &q->root);
+#ifdef HTB_DEBUG
+ {
+ int i;
+ for (i = 0; i < TC_HTB_NUMPRIO; i++) cl->node[i].rb_color = -1;
+ cl->pq_node.rb_color = -1;
+ }
+#endif
+ } else sch_tree_lock(sch);
+
+ /* it used to be a nasty bug here, we have to check that node
+ is really leaf before changing cl->un.leaf ! */
+ if (!cl->level) {
+ cl->un.leaf.quantum = rtab->rate.rate / q->rate2quantum;
+ if (!hopt->quantum && cl->un.leaf.quantum < 1000) {
+ printk(KERN_WARNING "HTB: quantum of class %X is small. Consider r2q change.\n", cl->classid);
+ cl->un.leaf.quantum = 1000;
+ }
+ if (!hopt->quantum && cl->un.leaf.quantum > 200000) {
+ printk(KERN_WARNING "HTB: quantum of class %X is big. Consider r2q change.\n", cl->classid);
+ cl->un.leaf.quantum = 200000;
+ }
+ if (hopt->quantum)
+ cl->un.leaf.quantum = hopt->quantum;
+ if ((cl->un.leaf.prio = hopt->prio) >= TC_HTB_NUMPRIO)
+ cl->un.leaf.prio = TC_HTB_NUMPRIO - 1;
+ }
+
+ cl->buffer = hopt->buffer;
+ cl->cbuffer = hopt->cbuffer;
+ if (cl->rate) qdisc_put_rtab(cl->rate); cl->rate = rtab;
+ if (cl->ceil) qdisc_put_rtab(cl->ceil); cl->ceil = ctab;
+ sch_tree_unlock(sch);
+
+ *arg = (unsigned long)cl;
+ return 0;
+
+failure:
+ if (rtab) qdisc_put_rtab(rtab);
+ if (ctab) qdisc_put_rtab(ctab);
+ return err;
+}
+
+static struct tcf_proto **htb_find_tcf(struct Qdisc *sch, unsigned long arg)
+{
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ struct htb_class *cl = (struct htb_class *)arg;
+ struct tcf_proto **fl = cl ? &cl->filter_list : &q->filter_list;
+ HTB_DBG(0,2,"htb_tcf q=%p clid=%X fref=%d fl=%p\n",q,cl?cl->classid:0,cl?cl->filter_cnt:q->filter_cnt,*fl);
+ return fl;
+}
+
+static unsigned long htb_bind_filter(struct Qdisc *sch, unsigned long parent,
+ u32 classid)
+{
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ struct htb_class *cl = htb_find (classid,sch);
+ HTB_DBG(0,2,"htb_bind q=%p clid=%X cl=%p fref=%d\n",q,classid,cl,cl?cl->filter_cnt:q->filter_cnt);
+ /*if (cl && !cl->level) return 0;
+ The line above used to be there to prevent attaching filters to
+ leaves. But at least tc_index filter uses this just to get class
+ for other reasons so that we have to allow for it.
+ ----
+ 19.6.2002 As Werner explained it is ok - bind filter is just
+ another way to "lock" the class - unlike "get" this lock can
+ be broken by class during destroy IIUC.
+ */
+ if (cl)
+ cl->filter_cnt++;
+ else
+ q->filter_cnt++;
+ return (unsigned long)cl;
+}
+
+static void htb_unbind_filter(struct Qdisc *sch, unsigned long arg)
+{
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ struct htb_class *cl = (struct htb_class *)arg;
+ HTB_DBG(0,2,"htb_unbind q=%p cl=%p fref=%d\n",q,cl,cl?cl->filter_cnt:q->filter_cnt);
+ if (cl)
+ cl->filter_cnt--;
+ else
+ q->filter_cnt--;
+}
+
+static void htb_walk(struct Qdisc *sch, struct qdisc_walker *arg)
+{
+ struct htb_sched *q = (struct htb_sched *)sch->data;
+ int i;
+
+ if (arg->stop)
+ return;
+
+ for (i = 0; i < HTB_HSIZE; i++) {
+ struct list_head *p;
+ list_for_each (p,q->hash+i) {
+ struct htb_class *cl = list_entry(p,struct htb_class,hlist);
+ if (arg->count < arg->skip) {
+ arg->count++;
+ continue;
+ }
+ if (arg->fn(sch, (unsigned long)cl, arg) < 0) {
+ arg->stop = 1;
+ return;
+ }
+ arg->count++;
+ }
+ }
+}
+
+static struct Qdisc_class_ops htb_class_ops =
+{
+ htb_graft,
+ htb_leaf,
+ htb_get,
+ htb_put,
+ htb_change_class,
+ htb_delete,
+ htb_walk,
+
+ htb_find_tcf,
+ htb_bind_filter,
+ htb_unbind_filter,
+
+ htb_dump_class,
+};
+
+struct Qdisc_ops htb_qdisc_ops =
+{
+ NULL,
+ &htb_class_ops,
+ "htb",
+ sizeof(struct htb_sched),
+
+ htb_enqueue,
+ htb_dequeue,
+ htb_requeue,
+ htb_drop,
+
+ htb_init,
+ htb_reset,
+ htb_destroy,
+ NULL /* htb_change */,
+
+ htb_dump,
+};
+
+#ifdef MODULE
+int init_module(void)
+{
+ return register_qdisc(&htb_qdisc_ops);
+}
+
+void cleanup_module(void)
+{
+ unregister_qdisc(&htb_qdisc_ops);
+}
+MODULE_LICENSE("GPL");
+#endif
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_ingress.c b/uClinux-2.4.31-uc0/net/sched/sch_ingress.c
new file mode 100644
index 0000000..49e3662
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_ingress.c
@@ -0,0 +1,399 @@
+/* net/sched/sch_ingress.c - Ingress qdisc
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Jamal Hadi Salim 1999
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/netfilter_ipv6.h>
+#include <linux/netfilter.h>
+#include <linux/smp.h>
+#include <net/pkt_sched.h>
+#include <asm/byteorder.h>
+#include <asm/uaccess.h>
+#include <linux/kmod.h>
+#include <linux/stat.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+
+
+#undef DEBUG_INGRESS
+
+#ifdef DEBUG_INGRESS /* control */
+#define DPRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define DPRINTK(format,args...)
+#endif
+
+#if 0 /* data */
+#define D2PRINTK(format,args...) printk(KERN_DEBUG format,##args)
+#else
+#define D2PRINTK(format,args...)
+#endif
+
+
+#define PRIV(sch) ((struct ingress_qdisc_data *) (sch)->data)
+
+
+/* Thanks to Doron Oz for this hack
+*/
+static int nf_registered = 0;
+
+struct ingress_qdisc_data {
+ struct Qdisc *q;
+ struct tcf_proto *filter_list;
+};
+
+
+/* ------------------------- Class/flow operations ------------------------- */
+
+
+static int ingress_graft(struct Qdisc *sch,unsigned long arg,
+ struct Qdisc *new,struct Qdisc **old)
+{
+#ifdef DEBUG_INGRESS
+ struct ingress_qdisc_data *p = PRIV(sch);
+#endif
+
+ DPRINTK("ingress_graft(sch %p,[qdisc %p],new %p,old %p)\n",
+ sch, p, new, old);
+ DPRINTK("\n ingress_graft: You cannot add qdiscs to classes");
+ return 1;
+}
+
+
+static struct Qdisc *ingress_leaf(struct Qdisc *sch, unsigned long arg)
+{
+ return NULL;
+}
+
+
+static unsigned long ingress_get(struct Qdisc *sch,u32 classid)
+{
+#ifdef DEBUG_INGRESS
+ struct ingress_qdisc_data *p = PRIV(sch);
+#endif
+ DPRINTK("ingress_get(sch %p,[qdisc %p],classid %x)\n", sch, p, classid);
+ return TC_H_MIN(classid) + 1;
+}
+
+
+static unsigned long ingress_bind_filter(struct Qdisc *sch,
+ unsigned long parent, u32 classid)
+{
+ return ingress_get(sch, classid);
+}
+
+
+static void ingress_put(struct Qdisc *sch, unsigned long cl)
+{
+}
+
+
+static int ingress_change(struct Qdisc *sch, u32 classid, u32 parent,
+ struct rtattr **tca, unsigned long *arg)
+{
+#ifdef DEBUG_INGRESS
+ struct ingress_qdisc_data *p = PRIV(sch);
+#endif
+ DPRINTK("ingress_change(sch %p,[qdisc %p],classid %x,parent %x),"
+ "arg 0x%lx\n", sch, p, classid, parent, *arg);
+ DPRINTK("No effect. sch_ingress doesn't maintain classes at the moment");
+ return 0;
+}
+
+
+
+static void ingress_walk(struct Qdisc *sch,struct qdisc_walker *walker)
+{
+#ifdef DEBUG_INGRESS
+ struct ingress_qdisc_data *p = PRIV(sch);
+#endif
+ DPRINTK("ingress_walk(sch %p,[qdisc %p],walker %p)\n", sch, p, walker);
+ DPRINTK("No effect. sch_ingress doesn't maintain classes at the moment");
+}
+
+
+static struct tcf_proto **ingress_find_tcf(struct Qdisc *sch,unsigned long cl)
+{
+ struct ingress_qdisc_data *p = PRIV(sch);
+
+ return &p->filter_list;
+}
+
+
+/* --------------------------- Qdisc operations ---------------------------- */
+
+
+static int ingress_enqueue(struct sk_buff *skb,struct Qdisc *sch)
+{
+ struct ingress_qdisc_data *p = PRIV(sch);
+ struct tcf_result res;
+ int result;
+
+ D2PRINTK("ingress_enqueue(skb %p,sch %p,[qdisc %p])\n", skb, sch, p);
+ result = tc_classify(skb, p->filter_list, &res);
+ D2PRINTK("result %d class 0x%04x\n", result, res.classid);
+ /*
+ * Unlike normal "enqueue" functions, ingress_enqueue returns a
+ * firewall FW_* code.
+ */
+#ifdef CONFIG_NET_CLS_POLICE
+ switch (result) {
+ case TC_POLICE_SHOT:
+ result = NF_DROP;
+ sch->stats.drops++;
+ break;
+ case TC_POLICE_RECLASSIFY: /* DSCP remarking here ? */
+ case TC_POLICE_OK:
+ case TC_POLICE_UNSPEC:
+ default:
+ sch->stats.packets++;
+ sch->stats.bytes += skb->len;
+ result = NF_ACCEPT;
+ break;
+ };
+#else
+ sch->stats.packets++;
+ sch->stats.bytes += skb->len;
+#endif
+
+ skb->tc_index = TC_H_MIN(res.classid);
+ return result;
+}
+
+
+static struct sk_buff *ingress_dequeue(struct Qdisc *sch)
+{
+/*
+ struct ingress_qdisc_data *p = PRIV(sch);
+ D2PRINTK("ingress_dequeue(sch %p,[qdisc %p])\n",sch,PRIV(p));
+*/
+ return NULL;
+}
+
+
+static int ingress_requeue(struct sk_buff *skb,struct Qdisc *sch)
+{
+/*
+ struct ingress_qdisc_data *p = PRIV(sch);
+ D2PRINTK("ingress_requeue(skb %p,sch %p,[qdisc %p])\n",skb,sch,PRIV(p));
+*/
+ return 0;
+}
+
+static unsigned int ingress_drop(struct Qdisc *sch)
+{
+#ifdef DEBUG_INGRESS
+ struct ingress_qdisc_data *p = PRIV(sch);
+#endif
+ DPRINTK("ingress_drop(sch %p,[qdisc %p])\n", sch, p);
+ return 0;
+}
+
+static unsigned int
+ing_hook(unsigned int hook, struct sk_buff **pskb,
+ const struct net_device *indev,
+ const struct net_device *outdev,
+ int (*okfn)(struct sk_buff *))
+{
+
+ struct Qdisc *q;
+ struct sk_buff *skb = *pskb;
+ struct net_device *dev = skb->dev;
+ int fwres=NF_ACCEPT;
+
+ DPRINTK("ing_hook: skb %s dev=%s len=%u\n",
+ skb->sk ? "(owned)" : "(unowned)",
+ skb->dev ? (*pskb)->dev->name : "(no dev)",
+ skb->len);
+
+/*
+revisit later: Use a private since lock dev->queue_lock is also
+used on the egress (might slow things for an iota)
+*/
+
+ if (dev->qdisc_ingress) {
+ spin_lock(&dev->queue_lock);
+ if ((q = dev->qdisc_ingress) != NULL)
+ fwres = q->enqueue(skb, q);
+ spin_unlock(&dev->queue_lock);
+ }
+
+ return fwres;
+}
+
+/* after ipt_filter */
+static struct nf_hook_ops ing_ops =
+{
+ { NULL, NULL},
+ ing_hook,
+ PF_INET,
+ NF_IP_PRE_ROUTING,
+ NF_IP_PRI_FILTER + 1
+};
+
+static struct nf_hook_ops ing6_ops =
+{
+ { NULL, NULL},
+ ing_hook,
+ PF_INET6,
+ NF_IP6_PRE_ROUTING,
+ NF_IP6_PRI_FILTER + 1
+};
+
+int ingress_init(struct Qdisc *sch,struct rtattr *opt)
+{
+ struct ingress_qdisc_data *p = PRIV(sch);
+
+ if (!nf_registered) {
+ if (nf_register_hook(&ing_ops) < 0) {
+ printk("ingress qdisc registration error \n");
+ goto error;
+ }
+ nf_registered++;
+ if (nf_register_hook(&ing6_ops) < 0) {
+ printk("IPv6 ingress qdisc registration error, " \
+ "disabling IPv6 support.\n");
+ } else
+ nf_registered++;
+ }
+
+ DPRINTK("ingress_init(sch %p,[qdisc %p],opt %p)\n",sch,p,opt);
+ p->q = &noop_qdisc;
+ MOD_INC_USE_COUNT;
+ return 0;
+error:
+ return -EINVAL;
+}
+
+
+static void ingress_reset(struct Qdisc *sch)
+{
+ struct ingress_qdisc_data *p = PRIV(sch);
+
+ DPRINTK("ingress_reset(sch %p,[qdisc %p])\n", sch, p);
+
+/*
+#if 0
+*/
+/* for future use */
+ qdisc_reset(p->q);
+/*
+#endif
+*/
+}
+
+/* ------------------------------------------------------------- */
+
+
+/* ------------------------------------------------------------- */
+
+static void ingress_destroy(struct Qdisc *sch)
+{
+ struct ingress_qdisc_data *p = PRIV(sch);
+ struct tcf_proto *tp;
+
+ DPRINTK("ingress_destroy(sch %p,[qdisc %p])\n", sch, p);
+ while (p->filter_list) {
+ tp = p->filter_list;
+ p->filter_list = tp->next;
+ tcf_destroy(tp);
+ }
+#if 0
+/* for future use */
+ qdisc_destroy(p->q);
+#endif
+
+ MOD_DEC_USE_COUNT;
+
+}
+
+
+static int ingress_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+
+ rta = (struct rtattr *) b;
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+ rta->rta_len = skb->tail - b;
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static struct Qdisc_class_ops ingress_class_ops =
+{
+ ingress_graft, /* graft */
+ ingress_leaf, /* leaf */
+ ingress_get, /* get */
+ ingress_put, /* put */
+ ingress_change, /* change */
+ NULL, /* delete */
+ ingress_walk, /* walk */
+
+ ingress_find_tcf, /* tcf_chain */
+ ingress_bind_filter, /* bind_tcf */
+ ingress_put, /* unbind_tcf */
+
+ NULL, /* dump */
+};
+
+struct Qdisc_ops ingress_qdisc_ops =
+{
+ NULL, /* next */
+ &ingress_class_ops, /* cl_ops */
+ "ingress",
+ sizeof(struct ingress_qdisc_data),
+
+ ingress_enqueue, /* enqueue */
+ ingress_dequeue, /* dequeue */
+ ingress_requeue, /* requeue */
+ ingress_drop, /* drop */
+
+ ingress_init, /* init */
+ ingress_reset, /* reset */
+ ingress_destroy, /* destroy */
+ NULL, /* change */
+
+ ingress_dump, /* dump */
+};
+
+
+#ifdef MODULE
+int init_module(void)
+{
+ int ret = 0;
+
+ if ((ret = register_qdisc(&ingress_qdisc_ops)) < 0) {
+ printk("Unable to register Ingress qdisc\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+
+void cleanup_module(void)
+{
+ unregister_qdisc(&ingress_qdisc_ops);
+ if (nf_registered) {
+ nf_unregister_hook(&ing_ops);
+ if (nf_registered > 1)
+ nf_unregister_hook(&ing6_ops);
+ }
+}
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_netem.c b/uClinux-2.4.31-uc0/net/sched/sch_netem.c
new file mode 100644
index 0000000..c4c8f56
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_netem.c
@@ -0,0 +1,600 @@
+/*
+ * net/sched/sch_netem.c Network emulator
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Many of the algorithms and ideas for this came from
+ * NIST Net which is not copyrighted.
+ *
+ * Authors: Stephen Hemminger <shemminger@osdl.org>
+ * Catalin(ux aka Dino) BOIE <catab at umbrella dot ro>
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <linux/rtnetlink.h>
+#include <linux/init.h>
+
+#include <net/pkt_sched.h>
+
+#define qdisc_priv(q) ((void *)(q->data))
+
+/* Network Emulation Queuing algorithm.
+ ====================================
+
+ Sources: [1] Mark Carson, Darrin Santay, "NIST Net - A Linux-based
+ Network Emulation Tool
+ [2] Luigi Rizzo, DummyNet for FreeBSD
+
+ ----------------------------------------------------------------
+
+ This started out as a simple way to delay outgoing packets to
+ test TCP but has grown to include most of the functionality
+ of a full blown network emulator like NISTnet. It can delay
+ packets and add random jitter (and correlation). The random
+ distribution can be loaded from a table as well to provide
+ normal, Pareto, or experimental curves. Packet loss,
+ duplication, and reordering can also be emulated.
+
+ This qdisc does not do classification that can be handled in
+ layering other disciplines. It does not need to do bandwidth
+ control either since that can be handled by using token
+ bucket or other rate control.
+
+ The simulator is limited by the Linux timer resolution
+ and will create packet bursts on the HZ boundary (1ms).
+*/
+
+struct netem_sched_data {
+ struct Qdisc *qdisc;
+ struct sk_buff_head delayed;
+ struct timer_list timer;
+
+ u32 latency;
+ u32 loss;
+ u32 limit;
+ u32 counter;
+ u32 gap;
+ u32 jitter;
+ u32 duplicate;
+
+ struct crndstate {
+ unsigned long last;
+ unsigned long rho;
+ } delay_cor, loss_cor, dup_cor;
+
+ struct disttable {
+ u32 size;
+ s16 table[0];
+ } *delay_dist;
+};
+
+/* Time stamp put into socket buffer control block */
+struct netem_skb_cb {
+ psched_time_t time_to_send;
+};
+
+/* init_crandom - initialize correlated random number generator
+ * Use entropy source for initial seed.
+ */
+static void init_crandom(struct crndstate *state, unsigned long rho)
+{
+ state->rho = rho;
+ state->last = net_random();
+}
+
+/* get_crandom - correlated random number generator
+ * Next number depends on last value.
+ * rho is scaled to avoid floating point.
+ */
+static unsigned long get_crandom(struct crndstate *state)
+{
+ u64 value, rho;
+ unsigned long answer;
+
+ if (state->rho == 0) /* no correllation */
+ return net_random();
+
+ value = net_random();
+ rho = (u64)state->rho + 1;
+ answer = (value * ((1ull<<32) - rho) + state->last * rho) >> 32;
+ state->last = answer;
+ return answer;
+}
+
+/* tabledist - return a pseudo-randomly distributed value with mean mu and
+ * std deviation sigma. Uses table lookup to approximate the desired
+ * distribution, and a uniformly-distributed pseudo-random source.
+ */
+static long tabledist(unsigned long mu, long sigma,
+ struct crndstate *state, const struct disttable *dist)
+{
+ long t, x;
+ unsigned long rnd;
+
+ if (sigma == 0)
+ return mu;
+
+ rnd = get_crandom(state);
+
+ /* default uniform distribution */
+ if (dist == NULL)
+ return (rnd % (2*sigma)) - sigma + mu;
+
+ t = dist->table[rnd % dist->size];
+ x = (sigma % NETEM_DIST_SCALE) * t;
+ if (x >= 0)
+ x += NETEM_DIST_SCALE/2;
+ else
+ x -= NETEM_DIST_SCALE/2;
+
+ return x / NETEM_DIST_SCALE + (sigma / NETEM_DIST_SCALE) * t + mu;
+}
+
+/* Put skb in the private delayed queue. */
+static int delay_skb(struct Qdisc *sch, struct sk_buff *skb)
+{
+ struct netem_sched_data *q = qdisc_priv(sch);
+ struct netem_skb_cb *cb = (struct netem_skb_cb *)skb->cb;
+ psched_tdiff_t td;
+ psched_time_t now;
+
+ PSCHED_GET_TIME(now);
+ td = tabledist(q->latency, q->jitter, &q->delay_cor, q->delay_dist);
+ PSCHED_TADD2(now, td, cb->time_to_send);
+
+ /* Always queue at tail to keep packets in order */
+ if (likely(q->delayed.qlen < q->limit)) {
+ __skb_queue_tail(&q->delayed, skb);
+ sch->stats.bytes += skb->len;
+ sch->stats.packets++;
+
+ if (!timer_pending(&q->timer)) {
+ q->timer.expires = jiffies + PSCHED_US2JIFFIE(td);
+ add_timer(&q->timer);
+ }
+ return NET_XMIT_SUCCESS;
+ }
+
+ sch->stats.drops++;
+ kfree_skb(skb);
+ return NET_XMIT_DROP;
+}
+
+static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch)
+{
+ struct netem_sched_data *q = qdisc_priv(sch);
+
+ pr_debug("netem_enqueue skb=%p @%lu\n", skb, jiffies);
+
+ /* Random packet drop 0 => none, ~0 => all */
+ if (q->loss && q->loss >= get_crandom(&q->loss_cor)) {
+ pr_debug("netem_enqueue: random loss\n");
+ sch->stats.drops++;
+ kfree_skb(skb);
+ return 0; /* lie about loss so TCP doesn't know */
+ }
+
+ /* Random duplication */
+ if (q->duplicate && q->duplicate >= get_crandom(&q->dup_cor)) {
+ struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
+
+ pr_debug("netem_enqueue: dup %p\n", skb2);
+ if (skb2)
+ delay_skb(sch, skb2);
+ }
+
+ /* If doing simple delay then gap == 0 so all packets
+ * go into the delayed holding queue
+ * otherwise if doing out of order only "1 out of gap"
+ * packets will be delayed.
+ */
+ if (q->counter < q->gap) {
+ int ret;
+
+ ++q->counter;
+ ret = q->qdisc->enqueue(skb, q->qdisc);
+ if (likely(ret == NET_XMIT_SUCCESS)) {
+ sch->q.qlen++;
+ sch->stats.bytes += skb->len;
+ sch->stats.packets++;
+ } else
+ sch->stats.drops++;
+ return ret;
+ }
+
+ q->counter = 0;
+
+ return delay_skb(sch, skb);
+}
+
+/* Requeue packets but don't change time stamp */
+static int netem_requeue(struct sk_buff *skb, struct Qdisc *sch)
+{
+ struct netem_sched_data *q = qdisc_priv(sch);
+ int ret;
+
+ if ((ret = q->qdisc->ops->requeue(skb, q->qdisc)) == 0)
+ sch->q.qlen++;
+
+ return ret;
+}
+
+static unsigned int netem_drop(struct Qdisc* sch)
+{
+ struct netem_sched_data *q = qdisc_priv(sch);
+ unsigned int len;
+
+ if ((len = q->qdisc->ops->drop(q->qdisc)) != 0) {
+ sch->q.qlen--;
+ sch->stats.drops++;
+ }
+ return len;
+}
+
+/* Dequeue packet.
+ * Move all packets that are ready to send from the delay holding
+ * list to the underlying qdisc, then just call dequeue
+ */
+static struct sk_buff *netem_dequeue(struct Qdisc *sch)
+{
+ struct netem_sched_data *q = qdisc_priv(sch);
+ struct sk_buff *skb;
+
+ skb = q->qdisc->dequeue(q->qdisc);
+ if (skb)
+ sch->q.qlen--;
+ return skb;
+}
+
+static void netem_watchdog(unsigned long arg)
+{
+ struct Qdisc *sch = (struct Qdisc *)arg;
+ struct netem_sched_data *q = qdisc_priv(sch);
+ struct net_device *dev = sch->dev;
+ struct sk_buff *skb;
+ psched_time_t now;
+
+ pr_debug("netem_watchdog: fired @%lu\n", jiffies);
+
+ spin_lock_bh(&dev->queue_lock);
+ PSCHED_GET_TIME(now);
+
+ while ((skb = skb_peek(&q->delayed)) != NULL) {
+ const struct netem_skb_cb *cb
+ = (const struct netem_skb_cb *)skb->cb;
+ long delay
+ = PSCHED_US2JIFFIE(PSCHED_TDIFF(cb->time_to_send, now));
+ pr_debug("netem_watchdog: skb %p@%lu %ld\n",
+ skb, jiffies, delay);
+
+ /* if more time remaining? */
+ if (delay > 0) {
+ mod_timer(&q->timer, jiffies + delay);
+ break;
+ }
+ __skb_unlink(skb, &q->delayed);
+
+ if (q->qdisc->enqueue(skb, q->qdisc))
+ sch->stats.drops++;
+ else
+ sch->q.qlen++;
+ }
+ qdisc_run(dev);
+ spin_unlock_bh(&dev->queue_lock);
+}
+
+static void netem_reset(struct Qdisc *sch)
+{
+ struct netem_sched_data *q = qdisc_priv(sch);
+
+ qdisc_reset(q->qdisc);
+ skb_queue_purge(&q->delayed);
+
+ sch->q.qlen = 0;
+ del_timer_sync(&q->timer);
+}
+
+static int set_fifo_limit(struct Qdisc *q, int limit)
+{
+ struct rtattr *rta;
+ int ret = -ENOMEM;
+
+ rta = kmalloc(RTA_LENGTH(sizeof(struct tc_fifo_qopt)), GFP_KERNEL);
+ if (rta) {
+ rta->rta_type = RTM_NEWQDISC;
+ rta->rta_len = RTA_LENGTH(sizeof(struct tc_fifo_qopt));
+ ((struct tc_fifo_qopt *)RTA_DATA(rta))->limit = limit;
+
+ ret = q->ops->change(q, rta);
+ kfree(rta);
+ }
+ return ret;
+}
+
+/*
+ * Distribution data is a variable size payload containing
+ * signed 16 bit values.
+ */
+static int get_dist_table(struct Qdisc *sch, const struct rtattr *attr)
+{
+ struct netem_sched_data *q = qdisc_priv(sch);
+ unsigned long n = RTA_PAYLOAD(attr)/sizeof(__s16);
+ const __s16 *data = RTA_DATA(attr);
+ struct disttable *d;
+ int i;
+
+ if (n > 65536)
+ return -EINVAL;
+
+ d = kmalloc(sizeof(*d) + n*sizeof(d->table[0]), GFP_KERNEL);
+ if (!d)
+ return -ENOMEM;
+
+ d->size = n;
+ for (i = 0; i < n; i++)
+ d->table[i] = data[i];
+
+ spin_lock_bh(&sch->dev->queue_lock);
+ d = xchg(&q->delay_dist, d);
+ spin_unlock_bh(&sch->dev->queue_lock);
+
+ kfree(d);
+ return 0;
+}
+
+static int get_correlation(struct Qdisc *sch, const struct rtattr *attr)
+{
+ struct netem_sched_data *q = qdisc_priv(sch);
+ const struct tc_netem_corr *c = RTA_DATA(attr);
+
+ if (RTA_PAYLOAD(attr) != sizeof(*c))
+ return -EINVAL;
+
+ init_crandom(&q->delay_cor, c->delay_corr);
+ init_crandom(&q->loss_cor, c->loss_corr);
+ init_crandom(&q->dup_cor, c->dup_corr);
+ return 0;
+}
+
+static int netem_change(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct netem_sched_data *q = qdisc_priv(sch);
+ struct tc_netem_qopt *qopt;
+ int ret;
+
+ if (opt == NULL || RTA_PAYLOAD(opt) < sizeof(*qopt))
+ return -EINVAL;
+
+ qopt = RTA_DATA(opt);
+ ret = set_fifo_limit(q->qdisc, qopt->limit);
+ if (ret) {
+ pr_debug("netem: can't set fifo limit\n");
+ return ret;
+ }
+
+ q->latency = qopt->latency;
+ q->jitter = qopt->jitter;
+ q->limit = qopt->limit;
+ q->gap = qopt->gap;
+ q->loss = qopt->loss;
+ q->duplicate = qopt->duplicate;
+
+ /* Handle nested options after initial queue options.
+ * Should have put all options in nested format but too late now.
+ */
+ if (RTA_PAYLOAD(opt) > sizeof(*qopt)) {
+ struct rtattr *tb[TCA_NETEM_MAX];
+ if (rtattr_parse(tb, TCA_NETEM_MAX,
+ RTA_DATA(opt) + sizeof(*qopt),
+ RTA_PAYLOAD(opt) - sizeof(*qopt)))
+ return -EINVAL;
+
+ if (tb[TCA_NETEM_CORR-1]) {
+ ret = get_correlation(sch, tb[TCA_NETEM_CORR-1]);
+ if (ret)
+ return ret;
+ }
+
+ if (tb[TCA_NETEM_DELAY_DIST-1]) {
+ ret = get_dist_table(sch, tb[TCA_NETEM_DELAY_DIST-1]);
+ if (ret)
+ return ret;
+ }
+ }
+
+
+ return 0;
+}
+
+static int netem_init(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct netem_sched_data *q = qdisc_priv(sch);
+ int ret;
+
+ if (!opt)
+ return -EINVAL;
+
+ MOD_INC_USE_COUNT;
+ skb_queue_head_init(&q->delayed);
+ init_timer(&q->timer);
+ q->timer.function = netem_watchdog;
+ q->timer.data = (unsigned long) sch;
+ q->counter = 0;
+
+ q->qdisc = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops);
+ if (!q->qdisc) {
+ pr_debug("netem: qdisc create failed\n");
+ return -ENOMEM;
+ }
+
+ ret = netem_change(sch, opt);
+ if (ret) {
+ pr_debug("netem: change failed\n");
+ qdisc_destroy(q->qdisc);
+ MOD_DEC_USE_COUNT;
+ }
+ return ret;
+}
+
+static void netem_destroy(struct Qdisc *sch)
+{
+ struct netem_sched_data *q = qdisc_priv(sch);
+
+ del_timer_sync(&q->timer);
+ qdisc_destroy(q->qdisc);
+ kfree(q->delay_dist);
+ MOD_DEC_USE_COUNT;
+}
+
+static int netem_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+ const struct netem_sched_data *q = qdisc_priv(sch);
+ unsigned char *b = skb->tail;
+ struct rtattr *rta = (struct rtattr *) b;
+ struct tc_netem_qopt qopt;
+ struct tc_netem_corr cor;
+
+ qopt.latency = q->latency;
+ qopt.jitter = q->jitter;
+ qopt.limit = q->limit;
+ qopt.loss = q->loss;
+ qopt.gap = q->gap;
+ qopt.duplicate = q->duplicate;
+ RTA_PUT(skb, TCA_OPTIONS, sizeof(qopt), &qopt);
+
+ cor.delay_corr = q->delay_cor.rho;
+ cor.loss_corr = q->loss_cor.rho;
+ cor.dup_corr = q->dup_cor.rho;
+ RTA_PUT(skb, TCA_NETEM_CORR, sizeof(cor), &cor);
+ rta->rta_len = skb->tail - b;
+
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int netem_dump_class(struct Qdisc *sch, unsigned long cl,
+ struct sk_buff *skb, struct tcmsg *tcm)
+{
+ struct netem_sched_data *q = qdisc_priv(sch);
+
+ if (cl != 1) /* only one class */
+ return -ENOENT;
+
+ tcm->tcm_handle |= TC_H_MIN(1);
+ tcm->tcm_info = q->qdisc->handle;
+
+ return 0;
+}
+
+static int netem_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
+ struct Qdisc **old)
+{
+ struct netem_sched_data *q = qdisc_priv(sch);
+
+ if (new == NULL)
+ new = &noop_qdisc;
+
+ sch_tree_lock(sch);
+ *old = xchg(&q->qdisc, new);
+ qdisc_reset(*old);
+ sch->q.qlen = 0;
+ sch_tree_unlock(sch);
+
+ return 0;
+}
+
+static struct Qdisc *netem_leaf(struct Qdisc *sch, unsigned long arg)
+{
+ struct netem_sched_data *q = qdisc_priv(sch);
+ return q->qdisc;
+}
+
+static unsigned long netem_get(struct Qdisc *sch, u32 classid)
+{
+ return 1;
+}
+
+static void netem_put(struct Qdisc *sch, unsigned long arg)
+{
+}
+
+static int netem_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
+ struct rtattr **tca, unsigned long *arg)
+{
+ return -ENOSYS;
+}
+
+static int netem_delete(struct Qdisc *sch, unsigned long arg)
+{
+ return -ENOSYS;
+}
+
+static void netem_walk(struct Qdisc *sch, struct qdisc_walker *walker)
+{
+ if (!walker->stop) {
+ if (walker->count >= walker->skip)
+ if (walker->fn(sch, 1, walker) < 0) {
+ walker->stop = 1;
+ return;
+ }
+ walker->count++;
+ }
+}
+
+static struct tcf_proto **netem_find_tcf(struct Qdisc *sch, unsigned long cl)
+{
+ return NULL;
+}
+
+static struct Qdisc_class_ops netem_class_ops = {
+ .graft = netem_graft,
+ .leaf = netem_leaf,
+ .get = netem_get,
+ .put = netem_put,
+ .change = netem_change_class,
+ .delete = netem_delete,
+ .walk = netem_walk,
+ .tcf_chain = netem_find_tcf,
+ .dump = netem_dump_class,
+};
+
+static struct Qdisc_ops netem_qdisc_ops = {
+ .id = "netem",
+ .cl_ops = &netem_class_ops,
+ .priv_size = sizeof(struct netem_sched_data),
+ .enqueue = netem_enqueue,
+ .dequeue = netem_dequeue,
+ .requeue = netem_requeue,
+ .drop = netem_drop,
+ .init = netem_init,
+ .reset = netem_reset,
+ .destroy = netem_destroy,
+ .change = netem_change,
+ .dump = netem_dump,
+};
+
+
+static int __init netem_module_init(void)
+{
+ return register_qdisc(&netem_qdisc_ops);
+}
+static void __exit netem_module_exit(void)
+{
+ unregister_qdisc(&netem_qdisc_ops);
+}
+module_init(netem_module_init)
+module_exit(netem_module_exit)
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_prio.c b/uClinux-2.4.31-uc0/net/sched/sch_prio.c
new file mode 100644
index 0000000..2e97a82
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_prio.c
@@ -0,0 +1,424 @@
+/*
+ * net/sched/sch_prio.c Simple 3-band priority "scheduler".
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ * Fixes: 19990609: J Hadi Salim <hadi@nortelnetworks.com>:
+ * Init -- EINVAL when opt undefined
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+
+struct prio_sched_data
+{
+ int bands;
+ struct tcf_proto *filter_list;
+ u8 prio2band[TC_PRIO_MAX+1];
+ struct Qdisc *queues[TCQ_PRIO_BANDS];
+};
+
+
+static __inline__ unsigned prio_classify(struct sk_buff *skb, struct Qdisc *sch)
+{
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+ struct tcf_result res;
+ u32 band;
+
+ band = skb->priority;
+ if (TC_H_MAJ(skb->priority) != sch->handle) {
+ if (!q->filter_list || tc_classify(skb, q->filter_list, &res)) {
+ if (TC_H_MAJ(band))
+ band = 0;
+ return q->prio2band[band&TC_PRIO_MAX];
+ }
+ band = res.classid;
+ }
+ band = TC_H_MIN(band) - 1;
+ return band < q->bands ? band : q->prio2band[0];
+}
+
+static int
+prio_enqueue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+ struct Qdisc *qdisc;
+ int ret;
+
+ qdisc = q->queues[prio_classify(skb, sch)];
+
+ if ((ret = qdisc->enqueue(skb, qdisc)) == 0) {
+ sch->stats.bytes += skb->len;
+ sch->stats.packets++;
+ sch->q.qlen++;
+ return 0;
+ }
+ sch->stats.drops++;
+ return ret;
+}
+
+
+static int
+prio_requeue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+ struct Qdisc *qdisc;
+ int ret;
+
+ qdisc = q->queues[prio_classify(skb, sch)];
+
+ if ((ret = qdisc->ops->requeue(skb, qdisc)) == 0) {
+ sch->q.qlen++;
+ return 0;
+ }
+ sch->stats.drops++;
+ return ret;
+}
+
+
+static struct sk_buff *
+prio_dequeue(struct Qdisc* sch)
+{
+ struct sk_buff *skb;
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+ int prio;
+ struct Qdisc *qdisc;
+
+ for (prio = 0; prio < q->bands; prio++) {
+ qdisc = q->queues[prio];
+ skb = qdisc->dequeue(qdisc);
+ if (skb) {
+ sch->q.qlen--;
+ return skb;
+ }
+ }
+ return NULL;
+
+}
+
+static unsigned int prio_drop(struct Qdisc* sch)
+{
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+ int prio;
+ unsigned int len;
+ struct Qdisc *qdisc;
+
+ for (prio = q->bands-1; prio >= 0; prio--) {
+ qdisc = q->queues[prio];
+ if ((len = qdisc->ops->drop(qdisc)) != 0) {
+ sch->q.qlen--;
+ return len;
+ }
+ }
+ return 0;
+}
+
+
+static void
+prio_reset(struct Qdisc* sch)
+{
+ int prio;
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+
+ for (prio=0; prio<q->bands; prio++)
+ qdisc_reset(q->queues[prio]);
+ sch->q.qlen = 0;
+}
+
+static void
+prio_destroy(struct Qdisc* sch)
+{
+ int prio;
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+ struct tcf_proto *tp;
+
+ while ((tp = q->filter_list) != NULL) {
+ q->filter_list = tp->next;
+ tcf_destroy(tp);
+ }
+
+ for (prio=0; prio<q->bands; prio++) {
+ qdisc_destroy(q->queues[prio]);
+ q->queues[prio] = &noop_qdisc;
+ }
+ MOD_DEC_USE_COUNT;
+}
+
+static int prio_tune(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+ struct tc_prio_qopt *qopt = RTA_DATA(opt);
+ int i;
+
+ if (opt->rta_len < RTA_LENGTH(sizeof(*qopt)))
+ return -EINVAL;
+ if (qopt->bands > TCQ_PRIO_BANDS || qopt->bands < 2)
+ return -EINVAL;
+
+ for (i=0; i<=TC_PRIO_MAX; i++) {
+ if (qopt->priomap[i] >= qopt->bands)
+ return -EINVAL;
+ }
+
+ sch_tree_lock(sch);
+ q->bands = qopt->bands;
+ memcpy(q->prio2band, qopt->priomap, TC_PRIO_MAX+1);
+
+ for (i=q->bands; i<TCQ_PRIO_BANDS; i++) {
+ struct Qdisc *child = xchg(&q->queues[i], &noop_qdisc);
+ if (child != &noop_qdisc)
+ qdisc_destroy(child);
+ }
+ sch_tree_unlock(sch);
+
+ for (i=0; i<=TC_PRIO_MAX; i++) {
+ int band = q->prio2band[i];
+ if (q->queues[band] == &noop_qdisc) {
+ struct Qdisc *child;
+ child = qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops);
+ if (child) {
+ sch_tree_lock(sch);
+ child = xchg(&q->queues[band], child);
+
+ if (child != &noop_qdisc)
+ qdisc_destroy(child);
+ sch_tree_unlock(sch);
+ }
+ }
+ }
+ return 0;
+}
+
+static int prio_init(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+ int i;
+
+ for (i=0; i<TCQ_PRIO_BANDS; i++)
+ q->queues[i] = &noop_qdisc;
+
+ if (opt == NULL) {
+ return -EINVAL;
+ } else {
+ int err;
+
+ if ((err= prio_tune(sch, opt)) != 0)
+ return err;
+ }
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int prio_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+ unsigned char *b = skb->tail;
+ struct tc_prio_qopt opt;
+
+ opt.bands = q->bands;
+ memcpy(&opt.priomap, q->prio2band, TC_PRIO_MAX+1);
+ RTA_PUT(skb, TCA_OPTIONS, sizeof(opt), &opt);
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int prio_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
+ struct Qdisc **old)
+{
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+ unsigned long band = arg - 1;
+
+ if (band >= q->bands)
+ return -EINVAL;
+
+ if (new == NULL)
+ new = &noop_qdisc;
+
+ sch_tree_lock(sch);
+ *old = q->queues[band];
+ q->queues[band] = new;
+ sch->q.qlen -= (*old)->q.qlen;
+ qdisc_reset(*old);
+ sch_tree_unlock(sch);
+
+ return 0;
+}
+
+static struct Qdisc *
+prio_leaf(struct Qdisc *sch, unsigned long arg)
+{
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+ unsigned long band = arg - 1;
+
+ if (band >= q->bands)
+ return NULL;
+
+ return q->queues[band];
+}
+
+static unsigned long prio_get(struct Qdisc *sch, u32 classid)
+{
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+ unsigned long band = TC_H_MIN(classid);
+
+ if (band - 1 >= q->bands)
+ return 0;
+ return band;
+}
+
+static unsigned long prio_bind(struct Qdisc *sch, unsigned long parent, u32 classid)
+{
+ return prio_get(sch, classid);
+}
+
+
+static void prio_put(struct Qdisc *q, unsigned long cl)
+{
+ return;
+}
+
+static int prio_change(struct Qdisc *sch, u32 handle, u32 parent, struct rtattr **tca, unsigned long *arg)
+{
+ unsigned long cl = *arg;
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+
+ if (cl - 1 > q->bands)
+ return -ENOENT;
+ return 0;
+}
+
+static int prio_delete(struct Qdisc *sch, unsigned long cl)
+{
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+ if (cl - 1 > q->bands)
+ return -ENOENT;
+ return 0;
+}
+
+
+static int prio_dump_class(struct Qdisc *sch, unsigned long cl, struct sk_buff *skb,
+ struct tcmsg *tcm)
+{
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+
+ if (cl - 1 > q->bands)
+ return -ENOENT;
+ tcm->tcm_handle |= TC_H_MIN(cl);
+ if (q->queues[cl-1])
+ tcm->tcm_info = q->queues[cl-1]->handle;
+ return 0;
+}
+
+static void prio_walk(struct Qdisc *sch, struct qdisc_walker *arg)
+{
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+ int prio;
+
+ if (arg->stop)
+ return;
+
+ for (prio = 0; prio < q->bands; prio++) {
+ if (arg->count < arg->skip) {
+ arg->count++;
+ continue;
+ }
+ if (arg->fn(sch, prio+1, arg) < 0) {
+ arg->stop = 1;
+ break;
+ }
+ arg->count++;
+ }
+}
+
+static struct tcf_proto ** prio_find_tcf(struct Qdisc *sch, unsigned long cl)
+{
+ struct prio_sched_data *q = (struct prio_sched_data *)sch->data;
+
+ if (cl)
+ return NULL;
+ return &q->filter_list;
+}
+
+static struct Qdisc_class_ops prio_class_ops =
+{
+ prio_graft,
+ prio_leaf,
+
+ prio_get,
+ prio_put,
+ prio_change,
+ prio_delete,
+ prio_walk,
+
+ prio_find_tcf,
+ prio_bind,
+ prio_put,
+
+ prio_dump_class,
+};
+
+struct Qdisc_ops prio_qdisc_ops =
+{
+ NULL,
+ &prio_class_ops,
+ "prio",
+ sizeof(struct prio_sched_data),
+
+ prio_enqueue,
+ prio_dequeue,
+ prio_requeue,
+ prio_drop,
+
+ prio_init,
+ prio_reset,
+ prio_destroy,
+ prio_tune,
+
+ prio_dump,
+};
+
+#ifdef MODULE
+
+int init_module(void)
+{
+ return register_qdisc(&prio_qdisc_ops);
+}
+
+void cleanup_module(void)
+{
+ unregister_qdisc(&prio_qdisc_ops);
+}
+
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_red.c b/uClinux-2.4.31-uc0/net/sched/sch_red.c
new file mode 100644
index 0000000..4a0cf53
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_red.c
@@ -0,0 +1,481 @@
+/*
+ * net/sched/sch_red.c Random Early Detection queue.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ *
+ * Changes:
+ * J Hadi Salim <hadi@nortel.com> 980914: computation fixes
+ * Alexey Makarenko <makar@phoenix.kharkov.ua> 990814: qave on idle link was calculated incorrectly.
+ * J Hadi Salim <hadi@nortelnetworks.com> 980816: ECN support
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+#include <net/inet_ecn.h>
+
+
+/* Random Early Detection (RED) algorithm.
+ =======================================
+
+ Source: Sally Floyd and Van Jacobson, "Random Early Detection Gateways
+ for Congestion Avoidance", 1993, IEEE/ACM Transactions on Networking.
+
+ This file codes a "divisionless" version of RED algorithm
+ as written down in Fig.17 of the paper.
+
+Short description.
+------------------
+
+ When a new packet arrives we calculate the average queue length:
+
+ avg = (1-W)*avg + W*current_queue_len,
+
+ W is the filter time constant (choosen as 2^(-Wlog)), it controls
+ the inertia of the algorithm. To allow larger bursts, W should be
+ decreased.
+
+ if (avg > th_max) -> packet marked (dropped).
+ if (avg < th_min) -> packet passes.
+ if (th_min < avg < th_max) we calculate probability:
+
+ Pb = max_P * (avg - th_min)/(th_max-th_min)
+
+ and mark (drop) packet with this probability.
+ Pb changes from 0 (at avg==th_min) to max_P (avg==th_max).
+ max_P should be small (not 1), usually 0.01..0.02 is good value.
+
+ max_P is chosen as a number, so that max_P/(th_max-th_min)
+ is a negative power of two in order arithmetics to contain
+ only shifts.
+
+
+ Parameters, settable by user:
+ -----------------------------
+
+ limit - bytes (must be > qth_max + burst)
+
+ Hard limit on queue length, should be chosen >qth_max
+ to allow packet bursts. This parameter does not
+ affect the algorithms behaviour and can be chosen
+ arbitrarily high (well, less than ram size)
+ Really, this limit will never be reached
+ if RED works correctly.
+
+ qth_min - bytes (should be < qth_max/2)
+ qth_max - bytes (should be at least 2*qth_min and less limit)
+ Wlog - bits (<32) log(1/W).
+ Plog - bits (<32)
+
+ Plog is related to max_P by formula:
+
+ max_P = (qth_max-qth_min)/2^Plog;
+
+ F.e. if qth_max=128K and qth_min=32K, then Plog=22
+ corresponds to max_P=0.02
+
+ Scell_log
+ Stab
+
+ Lookup table for log((1-W)^(t/t_ave).
+
+
+NOTES:
+
+Upper bound on W.
+-----------------
+
+ If you want to allow bursts of L packets of size S,
+ you should choose W:
+
+ L + 1 - th_min/S < (1-(1-W)^L)/W
+
+ th_min/S = 32 th_min/S = 4
+
+ log(W) L
+ -1 33
+ -2 35
+ -3 39
+ -4 46
+ -5 57
+ -6 75
+ -7 101
+ -8 135
+ -9 190
+ etc.
+ */
+
+struct red_sched_data
+{
+/* Parameters */
+ u32 limit; /* HARD maximal queue length */
+ u32 qth_min; /* Min average length threshold: A scaled */
+ u32 qth_max; /* Max average length threshold: A scaled */
+ u32 Rmask;
+ u32 Scell_max;
+ unsigned char flags;
+ char Wlog; /* log(W) */
+ char Plog; /* random number bits */
+ char Scell_log;
+ u8 Stab[256];
+
+/* Variables */
+ unsigned long qave; /* Average queue length: A scaled */
+ int qcount; /* Packets since last random number generation */
+ u32 qR; /* Cached random number */
+
+ psched_time_t qidlestart; /* Start of idle period */
+ struct tc_red_xstats st;
+};
+
+static int red_ecn_mark(struct sk_buff *skb)
+{
+ if (skb->nh.raw + 20 > skb->tail)
+ return 0;
+
+ switch (skb->protocol) {
+ case __constant_htons(ETH_P_IP):
+ if (!INET_ECN_is_capable(skb->nh.iph->tos))
+ return 0;
+ if (INET_ECN_is_not_ce(skb->nh.iph->tos))
+ IP_ECN_set_ce(skb->nh.iph);
+ return 1;
+ case __constant_htons(ETH_P_IPV6):
+ if (!INET_ECN_is_capable(ip6_get_dsfield(skb->nh.ipv6h)))
+ return 0;
+ IP6_ECN_set_ce(skb->nh.ipv6h);
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int
+red_enqueue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct red_sched_data *q = (struct red_sched_data *)sch->data;
+
+ psched_time_t now;
+
+ if (!PSCHED_IS_PASTPERFECT(q->qidlestart)) {
+ long us_idle;
+ int shift;
+
+ PSCHED_GET_TIME(now);
+ us_idle = PSCHED_TDIFF_SAFE(now, q->qidlestart, q->Scell_max, 0);
+ PSCHED_SET_PASTPERFECT(q->qidlestart);
+
+/*
+ The problem: ideally, average length queue recalcultion should
+ be done over constant clock intervals. This is too expensive, so that
+ the calculation is driven by outgoing packets.
+ When the queue is idle we have to model this clock by hand.
+
+ SF+VJ proposed to "generate" m = idletime/(average_pkt_size/bandwidth)
+ dummy packets as a burst after idle time, i.e.
+
+ q->qave *= (1-W)^m
+
+ This is an apparently overcomplicated solution (f.e. we have to precompute
+ a table to make this calculation in reasonable time)
+ I believe that a simpler model may be used here,
+ but it is field for experiments.
+*/
+ shift = q->Stab[us_idle>>q->Scell_log];
+
+ if (shift) {
+ q->qave >>= shift;
+ } else {
+ /* Approximate initial part of exponent
+ with linear function:
+ (1-W)^m ~= 1-mW + ...
+
+ Seems, it is the best solution to
+ problem of too coarce exponent tabulation.
+ */
+
+ us_idle = (q->qave * us_idle)>>q->Scell_log;
+ if (us_idle < q->qave/2)
+ q->qave -= us_idle;
+ else
+ q->qave >>= 1;
+ }
+ } else {
+ q->qave += sch->stats.backlog - (q->qave >> q->Wlog);
+ /* NOTE:
+ q->qave is fixed point number with point at Wlog.
+ The formulae above is equvalent to floating point
+ version:
+
+ qave = qave*(1-W) + sch->stats.backlog*W;
+ --ANK (980924)
+ */
+ }
+
+ if (q->qave < q->qth_min) {
+ q->qcount = -1;
+enqueue:
+ if (sch->stats.backlog + skb->len <= q->limit) {
+ __skb_queue_tail(&sch->q, skb);
+ sch->stats.backlog += skb->len;
+ sch->stats.bytes += skb->len;
+ sch->stats.packets++;
+ return NET_XMIT_SUCCESS;
+ } else {
+ q->st.pdrop++;
+ }
+ kfree_skb(skb);
+ sch->stats.drops++;
+ return NET_XMIT_DROP;
+ }
+ if (q->qave >= q->qth_max) {
+ q->qcount = -1;
+ sch->stats.overlimits++;
+mark:
+ if (!(q->flags&TC_RED_ECN) || !red_ecn_mark(skb)) {
+ q->st.early++;
+ goto drop;
+ }
+ q->st.marked++;
+ goto enqueue;
+ }
+
+ if (++q->qcount) {
+ /* The formula used below causes questions.
+
+ OK. qR is random number in the interval 0..Rmask
+ i.e. 0..(2^Plog). If we used floating point
+ arithmetics, it would be: (2^Plog)*rnd_num,
+ where rnd_num is less 1.
+
+ Taking into account, that qave have fixed
+ point at Wlog, and Plog is related to max_P by
+ max_P = (qth_max-qth_min)/2^Plog; two lines
+ below have the following floating point equivalent:
+
+ max_P*(qave - qth_min)/(qth_max-qth_min) < rnd/qcount
+
+ Any questions? --ANK (980924)
+ */
+ if (((q->qave - q->qth_min)>>q->Wlog)*q->qcount < q->qR)
+ goto enqueue;
+ q->qcount = 0;
+ q->qR = net_random()&q->Rmask;
+ sch->stats.overlimits++;
+ goto mark;
+ }
+ q->qR = net_random()&q->Rmask;
+ goto enqueue;
+
+drop:
+ kfree_skb(skb);
+ sch->stats.drops++;
+ return NET_XMIT_CN;
+}
+
+static int
+red_requeue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct red_sched_data *q = (struct red_sched_data *)sch->data;
+
+ PSCHED_SET_PASTPERFECT(q->qidlestart);
+
+ __skb_queue_head(&sch->q, skb);
+ sch->stats.backlog += skb->len;
+ return 0;
+}
+
+static struct sk_buff *
+red_dequeue(struct Qdisc* sch)
+{
+ struct sk_buff *skb;
+ struct red_sched_data *q = (struct red_sched_data *)sch->data;
+
+ skb = __skb_dequeue(&sch->q);
+ if (skb) {
+ sch->stats.backlog -= skb->len;
+ return skb;
+ }
+ PSCHED_GET_TIME(q->qidlestart);
+ return NULL;
+}
+
+static unsigned int red_drop(struct Qdisc* sch)
+{
+ struct sk_buff *skb;
+ struct red_sched_data *q = (struct red_sched_data *)sch->data;
+
+ skb = __skb_dequeue_tail(&sch->q);
+ if (skb) {
+ unsigned int len = skb->len;
+ sch->stats.backlog -= len;
+ sch->stats.drops++;
+ q->st.other++;
+ kfree_skb(skb);
+ return len;
+ }
+ PSCHED_GET_TIME(q->qidlestart);
+ return 0;
+}
+
+static void red_reset(struct Qdisc* sch)
+{
+ struct red_sched_data *q = (struct red_sched_data *)sch->data;
+
+ __skb_queue_purge(&sch->q);
+ sch->stats.backlog = 0;
+ PSCHED_SET_PASTPERFECT(q->qidlestart);
+ q->qave = 0;
+ q->qcount = -1;
+}
+
+static int red_change(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct red_sched_data *q = (struct red_sched_data *)sch->data;
+ struct rtattr *tb[TCA_RED_STAB];
+ struct tc_red_qopt *ctl;
+
+ if (opt == NULL ||
+ rtattr_parse(tb, TCA_RED_STAB, RTA_DATA(opt), RTA_PAYLOAD(opt)) ||
+ tb[TCA_RED_PARMS-1] == 0 || tb[TCA_RED_STAB-1] == 0 ||
+ RTA_PAYLOAD(tb[TCA_RED_PARMS-1]) < sizeof(*ctl) ||
+ RTA_PAYLOAD(tb[TCA_RED_STAB-1]) < 256)
+ return -EINVAL;
+
+ ctl = RTA_DATA(tb[TCA_RED_PARMS-1]);
+
+ sch_tree_lock(sch);
+ q->flags = ctl->flags;
+ q->Wlog = ctl->Wlog;
+ q->Plog = ctl->Plog;
+ q->Rmask = ctl->Plog < 32 ? ((1<<ctl->Plog) - 1) : ~0UL;
+ q->Scell_log = ctl->Scell_log;
+ q->Scell_max = (255<<q->Scell_log);
+ q->qth_min = ctl->qth_min<<ctl->Wlog;
+ q->qth_max = ctl->qth_max<<ctl->Wlog;
+ q->limit = ctl->limit;
+ memcpy(q->Stab, RTA_DATA(tb[TCA_RED_STAB-1]), 256);
+
+ q->qcount = -1;
+ if (skb_queue_len(&sch->q) == 0)
+ PSCHED_SET_PASTPERFECT(q->qidlestart);
+ sch_tree_unlock(sch);
+ return 0;
+}
+
+static int red_init(struct Qdisc* sch, struct rtattr *opt)
+{
+ int err;
+
+ MOD_INC_USE_COUNT;
+
+ if ((err = red_change(sch, opt)) != 0) {
+ MOD_DEC_USE_COUNT;
+ }
+ return err;
+}
+
+
+int red_copy_xstats(struct sk_buff *skb, struct tc_red_xstats *st)
+{
+ RTA_PUT(skb, TCA_XSTATS, sizeof(*st), st);
+ return 0;
+
+rtattr_failure:
+ return 1;
+}
+
+static int red_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+ struct red_sched_data *q = (struct red_sched_data *)sch->data;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+ struct tc_red_qopt opt;
+
+ rta = (struct rtattr*)b;
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+ opt.limit = q->limit;
+ opt.qth_min = q->qth_min>>q->Wlog;
+ opt.qth_max = q->qth_max>>q->Wlog;
+ opt.Wlog = q->Wlog;
+ opt.Plog = q->Plog;
+ opt.Scell_log = q->Scell_log;
+ opt.flags = q->flags;
+ RTA_PUT(skb, TCA_RED_PARMS, sizeof(opt), &opt);
+ rta->rta_len = skb->tail - b;
+
+ if (red_copy_xstats(skb, &q->st))
+ goto rtattr_failure;
+
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static void red_destroy(struct Qdisc *sch)
+{
+ MOD_DEC_USE_COUNT;
+}
+
+struct Qdisc_ops red_qdisc_ops =
+{
+ NULL,
+ NULL,
+ "red",
+ sizeof(struct red_sched_data),
+
+ red_enqueue,
+ red_dequeue,
+ red_requeue,
+ red_drop,
+
+ red_init,
+ red_reset,
+ red_destroy,
+ red_change,
+
+ red_dump,
+};
+
+
+#ifdef MODULE
+int init_module(void)
+{
+ return register_qdisc(&red_qdisc_ops);
+}
+
+void cleanup_module(void)
+{
+ unregister_qdisc(&red_qdisc_ops);
+}
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_sfq.c b/uClinux-2.4.31-uc0/net/sched/sch_sfq.c
new file mode 100644
index 0000000..0294588
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_sfq.c
@@ -0,0 +1,502 @@
+/*
+ * net/sched/sch_sfq.c Stochastic Fairness Queueing discipline.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <linux/init.h>
+#include <net/ip.h>
+#include <linux/ipv6.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+
+/* Stochastic Fairness Queuing algorithm.
+ =======================================
+
+ Source:
+ Paul E. McKenney "Stochastic Fairness Queuing",
+ IEEE INFOCOMM'90 Proceedings, San Francisco, 1990.
+
+ Paul E. McKenney "Stochastic Fairness Queuing",
+ "Interworking: Research and Experience", v.2, 1991, p.113-131.
+
+
+ See also:
+ M. Shreedhar and George Varghese "Efficient Fair
+ Queuing using Deficit Round Robin", Proc. SIGCOMM 95.
+
+
+ This is not the thing that is usually called (W)FQ nowadays.
+ It does not use any timestamp mechanism, but instead
+ processes queues in round-robin order.
+
+ ADVANTAGE:
+
+ - It is very cheap. Both CPU and memory requirements are minimal.
+
+ DRAWBACKS:
+
+ - "Stochastic" -> It is not 100% fair.
+ When hash collisions occur, several flows are considered as one.
+
+ - "Round-robin" -> It introduces larger delays than virtual clock
+ based schemes, and should not be used for isolating interactive
+ traffic from non-interactive. It means, that this scheduler
+ should be used as leaf of CBQ or P3, which put interactive traffic
+ to higher priority band.
+
+ We still need true WFQ for top level CSZ, but using WFQ
+ for the best effort traffic is absolutely pointless:
+ SFQ is superior for this purpose.
+
+ IMPLEMENTATION:
+ This implementation limits maximal queue length to 128;
+ maximal mtu to 2^15-1; number of hash buckets to 1024.
+ The only goal of this restrictions was that all data
+ fit into one 4K page :-). Struct sfq_sched_data is
+ organized in anti-cache manner: all the data for a bucket
+ are scattered over different locations. This is not good,
+ but it allowed me to put it into 4K.
+
+ It is easy to increase these values, but not in flight. */
+
+#define SFQ_DEPTH 128
+#define SFQ_HASH_DIVISOR 1024
+
+/* This type should contain at least SFQ_DEPTH*2 values */
+typedef unsigned char sfq_index;
+
+struct sfq_head
+{
+ sfq_index next;
+ sfq_index prev;
+};
+
+struct sfq_sched_data
+{
+/* Parameters */
+ int perturb_period;
+ unsigned quantum; /* Allotment per round: MUST BE >= MTU */
+ int limit;
+
+/* Variables */
+ struct timer_list perturb_timer;
+ int perturbation;
+ sfq_index tail; /* Index of current slot in round */
+ sfq_index max_depth; /* Maximal depth */
+
+ sfq_index ht[SFQ_HASH_DIVISOR]; /* Hash table */
+ sfq_index next[SFQ_DEPTH]; /* Active slots link */
+ short allot[SFQ_DEPTH]; /* Current allotment per slot */
+ unsigned short hash[SFQ_DEPTH]; /* Hash value indexed by slots */
+ struct sk_buff_head qs[SFQ_DEPTH]; /* Slot queue */
+ struct sfq_head dep[SFQ_DEPTH*2]; /* Linked list of slots, indexed by depth */
+};
+
+static __inline__ unsigned sfq_fold_hash(struct sfq_sched_data *q, u32 h, u32 h1)
+{
+ int pert = q->perturbation;
+
+ /* Have we any rotation primitives? If not, WHY? */
+ h ^= (h1<<pert) ^ (h1>>(0x1F - pert));
+ h ^= h>>10;
+ return h & 0x3FF;
+}
+
+static unsigned sfq_hash(struct sfq_sched_data *q, struct sk_buff *skb)
+{
+ u32 h, h2;
+
+ switch (skb->protocol) {
+ case __constant_htons(ETH_P_IP):
+ {
+ struct iphdr *iph = skb->nh.iph;
+ h = iph->daddr;
+ h2 = iph->saddr^iph->protocol;
+ if (!(iph->frag_off&htons(IP_MF|IP_OFFSET)) &&
+ (iph->protocol == IPPROTO_TCP ||
+ iph->protocol == IPPROTO_UDP ||
+ iph->protocol == IPPROTO_ESP))
+ h2 ^= *(((u32*)iph) + iph->ihl);
+ break;
+ }
+ case __constant_htons(ETH_P_IPV6):
+ {
+ struct ipv6hdr *iph = skb->nh.ipv6h;
+ h = iph->daddr.s6_addr32[3];
+ h2 = iph->saddr.s6_addr32[3]^iph->nexthdr;
+ if (iph->nexthdr == IPPROTO_TCP ||
+ iph->nexthdr == IPPROTO_UDP ||
+ iph->nexthdr == IPPROTO_ESP)
+ h2 ^= *(u32*)&iph[1];
+ break;
+ }
+ default:
+ h = (u32)(unsigned long)skb->dst^skb->protocol;
+ h2 = (u32)(unsigned long)skb->sk;
+ }
+ return sfq_fold_hash(q, h, h2);
+}
+
+extern __inline__ void sfq_link(struct sfq_sched_data *q, sfq_index x)
+{
+ sfq_index p, n;
+ int d = q->qs[x].qlen + SFQ_DEPTH;
+
+ p = d;
+ n = q->dep[d].next;
+ q->dep[x].next = n;
+ q->dep[x].prev = p;
+ q->dep[p].next = q->dep[n].prev = x;
+}
+
+extern __inline__ void sfq_dec(struct sfq_sched_data *q, sfq_index x)
+{
+ sfq_index p, n;
+
+ n = q->dep[x].next;
+ p = q->dep[x].prev;
+ q->dep[p].next = n;
+ q->dep[n].prev = p;
+
+ if (n == p && q->max_depth == q->qs[x].qlen + 1)
+ q->max_depth--;
+
+ sfq_link(q, x);
+}
+
+extern __inline__ void sfq_inc(struct sfq_sched_data *q, sfq_index x)
+{
+ sfq_index p, n;
+ int d;
+
+ n = q->dep[x].next;
+ p = q->dep[x].prev;
+ q->dep[p].next = n;
+ q->dep[n].prev = p;
+ d = q->qs[x].qlen;
+ if (q->max_depth < d)
+ q->max_depth = d;
+
+ sfq_link(q, x);
+}
+
+static unsigned int sfq_drop(struct Qdisc *sch)
+{
+ struct sfq_sched_data *q = (struct sfq_sched_data *)sch->data;
+ sfq_index d = q->max_depth;
+ struct sk_buff *skb;
+ unsigned int len;
+
+ /* Queue is full! Find the longest slot and
+ drop a packet from it */
+
+ if (d > 1) {
+ sfq_index x = q->dep[d+SFQ_DEPTH].next;
+ skb = q->qs[x].prev;
+ len = skb->len;
+ __skb_unlink(skb, &q->qs[x]);
+ kfree_skb(skb);
+ sfq_dec(q, x);
+ sch->q.qlen--;
+ sch->stats.drops++;
+ return len;
+ }
+
+ if (d == 1) {
+ /* It is difficult to believe, but ALL THE SLOTS HAVE LENGTH 1. */
+ d = q->next[q->tail];
+ q->next[q->tail] = q->next[d];
+ q->allot[q->next[d]] += q->quantum;
+ skb = q->qs[d].prev;
+ len = skb->len;
+ __skb_unlink(skb, &q->qs[d]);
+ kfree_skb(skb);
+ sfq_dec(q, d);
+ sch->q.qlen--;
+ q->ht[q->hash[d]] = SFQ_DEPTH;
+ sch->stats.drops++;
+ return len;
+ }
+
+ return 0;
+}
+
+static int
+sfq_enqueue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct sfq_sched_data *q = (struct sfq_sched_data *)sch->data;
+ unsigned hash = sfq_hash(q, skb);
+ sfq_index x;
+
+ x = q->ht[hash];
+ if (x == SFQ_DEPTH) {
+ q->ht[hash] = x = q->dep[SFQ_DEPTH].next;
+ q->hash[x] = hash;
+ }
+ __skb_queue_tail(&q->qs[x], skb);
+ sfq_inc(q, x);
+ if (q->qs[x].qlen == 1) { /* The flow is new */
+ if (q->tail == SFQ_DEPTH) { /* It is the first flow */
+ q->tail = x;
+ q->next[x] = x;
+ q->allot[x] = q->quantum;
+ } else {
+ q->next[x] = q->next[q->tail];
+ q->next[q->tail] = x;
+ q->tail = x;
+ }
+ }
+ if (++sch->q.qlen < q->limit-1) {
+ sch->stats.bytes += skb->len;
+ sch->stats.packets++;
+ return 0;
+ }
+
+ sfq_drop(sch);
+ return NET_XMIT_CN;
+}
+
+static int
+sfq_requeue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct sfq_sched_data *q = (struct sfq_sched_data *)sch->data;
+ unsigned hash = sfq_hash(q, skb);
+ sfq_index x;
+
+ x = q->ht[hash];
+ if (x == SFQ_DEPTH) {
+ q->ht[hash] = x = q->dep[SFQ_DEPTH].next;
+ q->hash[x] = hash;
+ }
+ __skb_queue_head(&q->qs[x], skb);
+ sfq_inc(q, x);
+ if (q->qs[x].qlen == 1) { /* The flow is new */
+ if (q->tail == SFQ_DEPTH) { /* It is the first flow */
+ q->tail = x;
+ q->next[x] = x;
+ q->allot[x] = q->quantum;
+ } else {
+ q->next[x] = q->next[q->tail];
+ q->next[q->tail] = x;
+ q->tail = x;
+ }
+ }
+ if (++sch->q.qlen < q->limit - 1)
+ return 0;
+
+ sch->stats.drops++;
+ sfq_drop(sch);
+ return NET_XMIT_CN;
+}
+
+
+
+
+static struct sk_buff *
+sfq_dequeue(struct Qdisc* sch)
+{
+ struct sfq_sched_data *q = (struct sfq_sched_data *)sch->data;
+ struct sk_buff *skb;
+ sfq_index a, old_a;
+
+ /* No active slots */
+ if (q->tail == SFQ_DEPTH)
+ return NULL;
+
+ a = old_a = q->next[q->tail];
+
+ /* Grab packet */
+ skb = __skb_dequeue(&q->qs[a]);
+ sfq_dec(q, a);
+ sch->q.qlen--;
+
+ /* Is the slot empty? */
+ if (q->qs[a].qlen == 0) {
+ q->ht[q->hash[a]] = SFQ_DEPTH;
+ a = q->next[a];
+ if (a == old_a) {
+ q->tail = SFQ_DEPTH;
+ return skb;
+ }
+ q->next[q->tail] = a;
+ q->allot[a] += q->quantum;
+ } else if ((q->allot[a] -= skb->len) <= 0) {
+ q->tail = a;
+ a = q->next[a];
+ q->allot[a] += q->quantum;
+ }
+ return skb;
+}
+
+static void
+sfq_reset(struct Qdisc* sch)
+{
+ struct sk_buff *skb;
+
+ while ((skb = sfq_dequeue(sch)) != NULL)
+ kfree_skb(skb);
+}
+
+static void sfq_perturbation(unsigned long arg)
+{
+ struct Qdisc *sch = (struct Qdisc*)arg;
+ struct sfq_sched_data *q = (struct sfq_sched_data *)sch->data;
+
+ q->perturbation = net_random()&0x1F;
+ q->perturb_timer.expires = jiffies + q->perturb_period;
+
+ if (q->perturb_period) {
+ q->perturb_timer.expires = jiffies + q->perturb_period;
+ add_timer(&q->perturb_timer);
+ }
+}
+
+static int sfq_change(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct sfq_sched_data *q = (struct sfq_sched_data *)sch->data;
+ struct tc_sfq_qopt *ctl = RTA_DATA(opt);
+
+ if (opt->rta_len < RTA_LENGTH(sizeof(*ctl)))
+ return -EINVAL;
+
+ sch_tree_lock(sch);
+ q->quantum = ctl->quantum ? : psched_mtu(sch->dev);
+ q->perturb_period = ctl->perturb_period*HZ;
+ if (ctl->limit)
+ q->limit = min_t(u32, ctl->limit, SFQ_DEPTH);
+
+ while (sch->q.qlen >= q->limit-1)
+ sfq_drop(sch);
+
+ del_timer(&q->perturb_timer);
+ if (q->perturb_period) {
+ q->perturb_timer.expires = jiffies + q->perturb_period;
+ add_timer(&q->perturb_timer);
+ }
+ sch_tree_unlock(sch);
+ return 0;
+}
+
+static int sfq_init(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct sfq_sched_data *q = (struct sfq_sched_data *)sch->data;
+ int i;
+
+ q->perturb_timer.data = (unsigned long)sch;
+ q->perturb_timer.function = sfq_perturbation;
+ init_timer(&q->perturb_timer);
+
+ for (i=0; i<SFQ_HASH_DIVISOR; i++)
+ q->ht[i] = SFQ_DEPTH;
+ for (i=0; i<SFQ_DEPTH; i++) {
+ skb_queue_head_init(&q->qs[i]);
+ q->dep[i+SFQ_DEPTH].next = i+SFQ_DEPTH;
+ q->dep[i+SFQ_DEPTH].prev = i+SFQ_DEPTH;
+ }
+ q->limit = SFQ_DEPTH;
+ q->max_depth = 0;
+ q->tail = SFQ_DEPTH;
+ if (opt == NULL) {
+ q->quantum = psched_mtu(sch->dev);
+ q->perturb_period = 0;
+ } else {
+ int err = sfq_change(sch, opt);
+ if (err)
+ return err;
+ }
+ for (i=0; i<SFQ_DEPTH; i++)
+ sfq_link(q, i);
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static void sfq_destroy(struct Qdisc *sch)
+{
+ struct sfq_sched_data *q = (struct sfq_sched_data *)sch->data;
+ del_timer(&q->perturb_timer);
+ MOD_DEC_USE_COUNT;
+}
+
+static int sfq_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+ struct sfq_sched_data *q = (struct sfq_sched_data *)sch->data;
+ unsigned char *b = skb->tail;
+ struct tc_sfq_qopt opt;
+
+ opt.quantum = q->quantum;
+ opt.perturb_period = q->perturb_period/HZ;
+
+ opt.limit = q->limit;
+ opt.divisor = SFQ_HASH_DIVISOR;
+ opt.flows = q->limit;
+
+ RTA_PUT(skb, TCA_OPTIONS, sizeof(opt), &opt);
+
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+struct Qdisc_ops sfq_qdisc_ops =
+{
+ NULL,
+ NULL,
+ "sfq",
+ sizeof(struct sfq_sched_data),
+
+ sfq_enqueue,
+ sfq_dequeue,
+ sfq_requeue,
+ sfq_drop,
+
+ sfq_init,
+ sfq_reset,
+ sfq_destroy,
+ NULL, /* sfq_change */
+
+ sfq_dump,
+};
+
+#ifdef MODULE
+int init_module(void)
+{
+ return register_qdisc(&sfq_qdisc_ops);
+}
+
+void cleanup_module(void)
+{
+ unregister_qdisc(&sfq_qdisc_ops);
+}
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_tbf.c b/uClinux-2.4.31-uc0/net/sched/sch_tbf.c
new file mode 100644
index 0000000..ef3df91
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_tbf.c
@@ -0,0 +1,554 @@
+/*
+ * net/sched/sch_tbf.c Token Bucket Filter queue.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ * Dmitry Torokhov <dtor@mail.ru> - allow attaching inner qdiscs -
+ * original idea by Martin Devera
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+
+/* Simple Token Bucket Filter.
+ =======================================
+
+ SOURCE.
+ -------
+
+ None.
+
+ Description.
+ ------------
+
+ A data flow obeys TBF with rate R and depth B, if for any
+ time interval t_i...t_f the number of transmitted bits
+ does not exceed B + R*(t_f-t_i).
+
+ Packetized version of this definition:
+ The sequence of packets of sizes s_i served at moments t_i
+ obeys TBF, if for any i<=k:
+
+ s_i+....+s_k <= B + R*(t_k - t_i)
+
+ Algorithm.
+ ----------
+
+ Let N(t_i) be B/R initially and N(t) grow continuously with time as:
+
+ N(t+delta) = min{B/R, N(t) + delta}
+
+ If the first packet in queue has length S, it may be
+ transmitted only at the time t_* when S/R <= N(t_*),
+ and in this case N(t) jumps:
+
+ N(t_* + 0) = N(t_* - 0) - S/R.
+
+
+
+ Actually, QoS requires two TBF to be applied to a data stream.
+ One of them controls steady state burst size, another
+ one with rate P (peak rate) and depth M (equal to link MTU)
+ limits bursts at a smaller time scale.
+
+ It is easy to see that P>R, and B>M. If P is infinity, this double
+ TBF is equivalent to a single one.
+
+ When TBF works in reshaping mode, latency is estimated as:
+
+ lat = max ((L-B)/R, (L-M)/P)
+
+
+ NOTES.
+ ------
+
+ If TBF throttles, it starts a watchdog timer, which will wake it up
+ when it is ready to transmit.
+ Note that the minimal timer resolution is 1/HZ.
+ If no new packets arrive during this period,
+ or if the device is not awaken by EOI for some previous packet,
+ TBF can stop its activity for 1/HZ.
+
+
+ This means, that with depth B, the maximal rate is
+
+ R_crit = B*HZ
+
+ F.e. for 10Mbit ethernet and HZ=100 the minimal allowed B is ~10Kbytes.
+
+ Note that the peak rate TBF is much more tough: with MTU 1500
+ P_crit = 150Kbytes/sec. So, if you need greater peak
+ rates, use alpha with HZ=1000 :-)
+
+ With classful TBF, limit is just kept for backwards compatibility.
+ It is passed to the default bfifo qdisc - if the inner qdisc is
+ changed the limit is not effective anymore.
+*/
+
+struct tbf_sched_data
+{
+/* Parameters */
+ u32 limit; /* Maximal length of backlog: bytes */
+ u32 buffer; /* Token bucket depth/rate: MUST BE >= MTU/B */
+ u32 mtu;
+ u32 max_size;
+ struct qdisc_rate_table *R_tab;
+ struct qdisc_rate_table *P_tab;
+
+/* Variables */
+ long tokens; /* Current number of B tokens */
+ long ptokens; /* Current number of P tokens */
+ psched_time_t t_c; /* Time check-point */
+ struct timer_list wd_timer; /* Watchdog timer */
+ struct Qdisc *qdisc; /* Inner qdisc, default - bfifo queue */
+};
+
+#define L2T(q,L) ((q)->R_tab->data[(L)>>(q)->R_tab->rate.cell_log])
+#define L2T_P(q,L) ((q)->P_tab->data[(L)>>(q)->P_tab->rate.cell_log])
+
+static int tbf_enqueue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct tbf_sched_data *q = (struct tbf_sched_data *)sch->data;
+ int ret;
+
+ if (skb->len > q->max_size) {
+ sch->stats.drops++;
+#ifdef CONFIG_NET_CLS_POLICE
+ if (sch->reshape_fail == NULL || sch->reshape_fail(skb, sch))
+#endif
+ kfree_skb(skb);
+
+ return NET_XMIT_DROP;
+ }
+
+ if ((ret = q->qdisc->enqueue(skb, q->qdisc)) != 0) {
+ sch->stats.drops++;
+ return ret;
+ }
+
+ sch->q.qlen++;
+ sch->stats.bytes += skb->len;
+ sch->stats.packets++;
+ return 0;
+}
+
+static int tbf_requeue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct tbf_sched_data *q = (struct tbf_sched_data *)sch->data;
+ int ret;
+
+ if ((ret = q->qdisc->ops->requeue(skb, q->qdisc)) == 0)
+ sch->q.qlen++;
+
+ return ret;
+}
+
+static unsigned int tbf_drop(struct Qdisc* sch)
+{
+ struct tbf_sched_data *q = (struct tbf_sched_data *)sch->data;
+ unsigned int len;
+
+ if ((len = q->qdisc->ops->drop(q->qdisc)) != 0) {
+ sch->q.qlen--;
+ sch->stats.drops++;
+ }
+ return len;
+}
+
+static void tbf_watchdog(unsigned long arg)
+{
+ struct Qdisc *sch = (struct Qdisc*)arg;
+
+ sch->flags &= ~TCQ_F_THROTTLED;
+ netif_schedule(sch->dev);
+}
+
+static struct sk_buff *tbf_dequeue(struct Qdisc* sch)
+{
+ struct tbf_sched_data *q = (struct tbf_sched_data *)sch->data;
+ struct sk_buff *skb;
+
+ skb = q->qdisc->dequeue(q->qdisc);
+
+ if (skb) {
+ psched_time_t now;
+ long toks, delay;
+ long ptoks = 0;
+ unsigned int len = skb->len;
+
+ PSCHED_GET_TIME(now);
+
+ toks = PSCHED_TDIFF_SAFE(now, q->t_c, q->buffer, 0);
+
+ if (q->P_tab) {
+ ptoks = toks + q->ptokens;
+ if (ptoks > (long)q->mtu)
+ ptoks = q->mtu;
+ ptoks -= L2T_P(q, len);
+ }
+ toks += q->tokens;
+ if (toks > (long)q->buffer)
+ toks = q->buffer;
+ toks -= L2T(q, len);
+
+ if ((toks|ptoks) >= 0) {
+ q->t_c = now;
+ q->tokens = toks;
+ q->ptokens = ptoks;
+ sch->q.qlen--;
+ sch->flags &= ~TCQ_F_THROTTLED;
+ return skb;
+ }
+
+ delay = PSCHED_US2JIFFIE(max_t(long, -toks, -ptoks));
+
+ if (delay == 0)
+ delay = 1;
+
+ mod_timer(&q->wd_timer, jiffies+delay);
+
+ /* Maybe we have a shorter packet in the queue,
+ which can be sent now. It sounds cool,
+ but, however, this is wrong in principle.
+ We MUST NOT reorder packets under these circumstances.
+
+ Really, if we split the flow into independent
+ subflows, it would be a very good solution.
+ This is the main idea of all FQ algorithms
+ (cf. CSZ, HPFQ, HFSC)
+ */
+
+ if (q->qdisc->ops->requeue(skb, q->qdisc) != NET_XMIT_SUCCESS) {
+ /* When requeue fails skb is dropped */
+ sch->q.qlen--;
+ sch->stats.drops++;
+ }
+
+ sch->flags |= TCQ_F_THROTTLED;
+ sch->stats.overlimits++;
+ }
+ return NULL;
+}
+
+static void tbf_reset(struct Qdisc* sch)
+{
+ struct tbf_sched_data *q = (struct tbf_sched_data *)sch->data;
+
+ qdisc_reset(q->qdisc);
+ sch->q.qlen = 0;
+ PSCHED_GET_TIME(q->t_c);
+ q->tokens = q->buffer;
+ q->ptokens = q->mtu;
+ sch->flags &= ~TCQ_F_THROTTLED;
+ del_timer(&q->wd_timer);
+}
+
+static struct Qdisc *tbf_create_dflt_qdisc(struct net_device *dev, u32 limit)
+{
+ struct Qdisc *q = qdisc_create_dflt(dev, &bfifo_qdisc_ops);
+ struct rtattr *rta;
+ int ret;
+
+ if (q) {
+ rta = kmalloc(RTA_LENGTH(sizeof(struct tc_fifo_qopt)), GFP_KERNEL);
+ if (rta) {
+ rta->rta_type = RTM_NEWQDISC;
+ rta->rta_len = RTA_LENGTH(sizeof(struct tc_fifo_qopt));
+ ((struct tc_fifo_qopt *)RTA_DATA(rta))->limit = limit;
+
+ ret = q->ops->change(q, rta);
+ kfree(rta);
+
+ if (ret == 0)
+ return q;
+ }
+ qdisc_destroy(q);
+ }
+
+ return NULL;
+}
+
+static int tbf_change(struct Qdisc* sch, struct rtattr *opt)
+{
+ int err = -EINVAL;
+ struct tbf_sched_data *q = (struct tbf_sched_data *)sch->data;
+ struct rtattr *tb[TCA_TBF_PTAB];
+ struct tc_tbf_qopt *qopt;
+ struct qdisc_rate_table *rtab = NULL;
+ struct qdisc_rate_table *ptab = NULL;
+ struct Qdisc *child = NULL;
+ int max_size,n;
+
+ if (rtattr_parse(tb, TCA_TBF_PTAB, RTA_DATA(opt), RTA_PAYLOAD(opt)) ||
+ tb[TCA_TBF_PARMS-1] == NULL ||
+ RTA_PAYLOAD(tb[TCA_TBF_PARMS-1]) < sizeof(*qopt))
+ goto done;
+
+ qopt = RTA_DATA(tb[TCA_TBF_PARMS-1]);
+ rtab = qdisc_get_rtab(&qopt->rate, tb[TCA_TBF_RTAB-1]);
+ if (rtab == NULL)
+ goto done;
+
+ if (qopt->peakrate.rate) {
+ if (qopt->peakrate.rate > qopt->rate.rate)
+ ptab = qdisc_get_rtab(&qopt->peakrate, tb[TCA_TBF_PTAB-1]);
+ if (ptab == NULL)
+ goto done;
+ }
+
+ for (n = 0; n < 256; n++)
+ if (rtab->data[n] > qopt->buffer) break;
+ max_size = (n << qopt->rate.cell_log)-1;
+ if (ptab) {
+ int size;
+
+ for (n = 0; n < 256; n++)
+ if (ptab->data[n] > qopt->mtu) break;
+ size = (n << qopt->peakrate.cell_log)-1;
+ if (size < max_size) max_size = size;
+ }
+ if (max_size < 0)
+ goto done;
+
+ if (q->qdisc == &noop_qdisc) {
+ if ((child = tbf_create_dflt_qdisc(sch->dev, qopt->limit)) == NULL)
+ goto done;
+ }
+
+ sch_tree_lock(sch);
+ if (child) q->qdisc = child;
+ q->limit = qopt->limit;
+ q->mtu = qopt->mtu;
+ q->max_size = max_size;
+ q->buffer = qopt->buffer;
+ q->tokens = q->buffer;
+ q->ptokens = q->mtu;
+ rtab = xchg(&q->R_tab, rtab);
+ ptab = xchg(&q->P_tab, ptab);
+ sch_tree_unlock(sch);
+ err = 0;
+done:
+ if (rtab)
+ qdisc_put_rtab(rtab);
+ if (ptab)
+ qdisc_put_rtab(ptab);
+ return err;
+}
+
+static int tbf_init(struct Qdisc* sch, struct rtattr *opt)
+{
+ int err;
+ struct tbf_sched_data *q = (struct tbf_sched_data *)sch->data;
+
+ if (opt == NULL)
+ return -EINVAL;
+
+ MOD_INC_USE_COUNT;
+
+ PSCHED_GET_TIME(q->t_c);
+ init_timer(&q->wd_timer);
+ q->wd_timer.function = tbf_watchdog;
+ q->wd_timer.data = (unsigned long)sch;
+
+ q->qdisc = &noop_qdisc;
+
+ if ((err = tbf_change(sch, opt)) != 0) {
+ MOD_DEC_USE_COUNT;
+ }
+ return err;
+}
+
+static void tbf_destroy(struct Qdisc *sch)
+{
+ struct tbf_sched_data *q = (struct tbf_sched_data *)sch->data;
+
+ del_timer(&q->wd_timer);
+
+ if (q->P_tab)
+ qdisc_put_rtab(q->P_tab);
+ if (q->R_tab)
+ qdisc_put_rtab(q->R_tab);
+
+ qdisc_destroy(q->qdisc);
+ q->qdisc = &noop_qdisc;
+
+ MOD_DEC_USE_COUNT;
+}
+
+static int tbf_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+ struct tbf_sched_data *q = (struct tbf_sched_data *)sch->data;
+ unsigned char *b = skb->tail;
+ struct rtattr *rta;
+ struct tc_tbf_qopt opt;
+
+ rta = (struct rtattr*)b;
+ RTA_PUT(skb, TCA_OPTIONS, 0, NULL);
+
+ opt.limit = q->limit;
+ opt.rate = q->R_tab->rate;
+ if (q->P_tab)
+ opt.peakrate = q->P_tab->rate;
+ else
+ memset(&opt.peakrate, 0, sizeof(opt.peakrate));
+ opt.mtu = q->mtu;
+ opt.buffer = q->buffer;
+ RTA_PUT(skb, TCA_TBF_PARMS, sizeof(opt), &opt);
+ rta->rta_len = skb->tail - b;
+
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int tbf_dump_class(struct Qdisc *sch, unsigned long cl,
+ struct sk_buff *skb, struct tcmsg *tcm)
+{
+ struct tbf_sched_data *q = (struct tbf_sched_data*)sch->data;
+
+ if (cl != 1) /* only one class */
+ return -ENOENT;
+
+ tcm->tcm_handle |= TC_H_MIN(1);
+ tcm->tcm_info = q->qdisc->handle;
+
+ return 0;
+}
+
+static int tbf_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
+ struct Qdisc **old)
+{
+ struct tbf_sched_data *q = (struct tbf_sched_data *)sch->data;
+
+ if (new == NULL)
+ new = &noop_qdisc;
+
+ sch_tree_lock(sch);
+ *old = xchg(&q->qdisc, new);
+ qdisc_reset(*old);
+ sch->q.qlen = 0;
+ sch_tree_unlock(sch);
+
+ return 0;
+}
+
+static struct Qdisc *tbf_leaf(struct Qdisc *sch, unsigned long arg)
+{
+ struct tbf_sched_data *q = (struct tbf_sched_data *)sch->data;
+ return q->qdisc;
+}
+
+static unsigned long tbf_get(struct Qdisc *sch, u32 classid)
+{
+ return 1;
+}
+
+static void tbf_put(struct Qdisc *sch, unsigned long arg)
+{
+}
+
+static int tbf_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
+ struct rtattr **tca, unsigned long *arg)
+{
+ return -ENOSYS;
+}
+
+static int tbf_delete(struct Qdisc *sch, unsigned long arg)
+{
+ return -ENOSYS;
+}
+
+static void tbf_walk(struct Qdisc *sch, struct qdisc_walker *walker)
+{
+ if (!walker->stop) {
+ if (walker->count >= walker->skip)
+ if (walker->fn(sch, 1, walker) < 0) {
+ walker->stop = 1;
+ return;
+ }
+ walker->count++;
+ }
+}
+
+static struct tcf_proto **tbf_find_tcf(struct Qdisc *sch, unsigned long cl)
+{
+ return NULL;
+}
+
+static struct Qdisc_class_ops tbf_class_ops =
+{
+ .graft = tbf_graft,
+ .leaf = tbf_leaf,
+ .get = tbf_get,
+ .put = tbf_put,
+ .change = tbf_change_class,
+ .delete = tbf_delete,
+ .walk = tbf_walk,
+ .tcf_chain = tbf_find_tcf,
+ .dump = tbf_dump_class,
+};
+
+struct Qdisc_ops tbf_qdisc_ops =
+{
+ NULL,
+ &tbf_class_ops,
+ "tbf",
+ sizeof(struct tbf_sched_data),
+
+ tbf_enqueue,
+ tbf_dequeue,
+ tbf_requeue,
+ tbf_drop,
+
+ tbf_init,
+ tbf_reset,
+ tbf_destroy,
+ tbf_change,
+
+ tbf_dump,
+};
+
+
+#ifdef MODULE
+int init_module(void)
+{
+ return register_qdisc(&tbf_qdisc_ops);
+}
+
+void cleanup_module(void)
+{
+ unregister_qdisc(&tbf_qdisc_ops);
+}
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_teql.c b/uClinux-2.4.31-uc0/net/sched/sch_teql.c
new file mode 100644
index 0000000..7bc13e3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_teql.c
@@ -0,0 +1,496 @@
+/* net/sched/sch_teql.c "True" (or "trivial") link equalizer.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
+ */
+
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <linux/init.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+/*
+ How to setup it.
+ ----------------
+
+ After loading this module you will find a new device teqlN
+ and new qdisc with the same name. To join a slave to the equalizer
+ you should just set this qdisc on a device f.e.
+
+ # tc qdisc add dev eth0 root teql0
+ # tc qdisc add dev eth1 root teql0
+
+ That's all. Full PnP 8)
+
+ Applicability.
+ --------------
+
+ 1. Slave devices MUST be active devices, i.e., they must raise the tbusy
+ signal and generate EOI events. If you want to equalize virtual devices
+ like tunnels, use a normal eql device.
+ 2. This device puts no limitations on physical slave characteristics
+ f.e. it will equalize 9600baud line and 100Mb ethernet perfectly :-)
+ Certainly, large difference in link speeds will make the resulting
+ eqalized link unusable, because of huge packet reordering.
+ I estimate an upper useful difference as ~10 times.
+ 3. If the slave requires address resolution, only protocols using
+ neighbour cache (IPv4/IPv6) will work over the equalized link.
+ Other protocols are still allowed to use the slave device directly,
+ which will not break load balancing, though native slave
+ traffic will have the highest priority. */
+
+struct teql_master
+{
+ struct Qdisc_ops qops;
+ struct net_device dev;
+ struct Qdisc *slaves;
+ struct net_device_stats stats;
+};
+
+struct teql_sched_data
+{
+ struct Qdisc *next;
+ struct teql_master *m;
+ struct neighbour *ncache;
+ struct sk_buff_head q;
+};
+
+#define NEXT_SLAVE(q) (((struct teql_sched_data*)((q)->data))->next)
+
+#define FMASK (IFF_BROADCAST|IFF_POINTOPOINT|IFF_BROADCAST)
+
+/* "teql*" qdisc routines */
+
+static int
+teql_enqueue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct net_device *dev = sch->dev;
+ struct teql_sched_data *q = (struct teql_sched_data *)sch->data;
+
+ __skb_queue_tail(&q->q, skb);
+ if (q->q.qlen <= dev->tx_queue_len) {
+ sch->stats.bytes += skb->len;
+ sch->stats.packets++;
+ return 0;
+ }
+
+ __skb_unlink(skb, &q->q);
+ kfree_skb(skb);
+ sch->stats.drops++;
+ return NET_XMIT_DROP;
+}
+
+static int
+teql_requeue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct teql_sched_data *q = (struct teql_sched_data *)sch->data;
+
+ __skb_queue_head(&q->q, skb);
+ return 0;
+}
+
+static struct sk_buff *
+teql_dequeue(struct Qdisc* sch)
+{
+ struct teql_sched_data *dat = (struct teql_sched_data *)sch->data;
+ struct sk_buff *skb;
+
+ skb = __skb_dequeue(&dat->q);
+ if (skb == NULL) {
+ struct net_device *m = dat->m->dev.qdisc->dev;
+ if (m) {
+ dat->m->slaves = sch;
+ netif_wake_queue(m);
+ }
+ }
+ sch->q.qlen = dat->q.qlen + dat->m->dev.qdisc->q.qlen;
+ return skb;
+}
+
+static __inline__ void
+teql_neigh_release(struct neighbour *n)
+{
+ if (n)
+ neigh_release(n);
+}
+
+static void
+teql_reset(struct Qdisc* sch)
+{
+ struct teql_sched_data *dat = (struct teql_sched_data *)sch->data;
+
+ skb_queue_purge(&dat->q);
+ sch->q.qlen = 0;
+ teql_neigh_release(xchg(&dat->ncache, NULL));
+}
+
+static void
+teql_destroy(struct Qdisc* sch)
+{
+ struct Qdisc *q, *prev;
+ struct teql_sched_data *dat = (struct teql_sched_data *)sch->data;
+ struct teql_master *master = dat->m;
+
+ if ((prev = master->slaves) != NULL) {
+ do {
+ q = NEXT_SLAVE(prev);
+ if (q == sch) {
+ NEXT_SLAVE(prev) = NEXT_SLAVE(q);
+ if (q == master->slaves) {
+ master->slaves = NEXT_SLAVE(q);
+ if (q == master->slaves) {
+ master->slaves = NULL;
+ spin_lock_bh(&master->dev.queue_lock);
+ qdisc_reset(master->dev.qdisc);
+ spin_unlock_bh(&master->dev.queue_lock);
+ }
+ }
+ skb_queue_purge(&dat->q);
+ teql_neigh_release(xchg(&dat->ncache, NULL));
+ break;
+ }
+
+ } while ((prev = q) != master->slaves);
+ }
+
+ MOD_DEC_USE_COUNT;
+}
+
+static int teql_qdisc_init(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct net_device *dev = sch->dev;
+ struct teql_master *m = (struct teql_master*)sch->ops;
+ struct teql_sched_data *q = (struct teql_sched_data *)sch->data;
+
+ if (dev->hard_header_len > m->dev.hard_header_len)
+ return -EINVAL;
+
+ if (&m->dev == dev)
+ return -ELOOP;
+
+ q->m = m;
+
+ skb_queue_head_init(&q->q);
+
+ if (m->slaves) {
+ if (m->dev.flags & IFF_UP) {
+ if ((m->dev.flags&IFF_POINTOPOINT && !(dev->flags&IFF_POINTOPOINT))
+ || (m->dev.flags&IFF_BROADCAST && !(dev->flags&IFF_BROADCAST))
+ || (m->dev.flags&IFF_MULTICAST && !(dev->flags&IFF_MULTICAST))
+ || dev->mtu < m->dev.mtu)
+ return -EINVAL;
+ } else {
+ if (!(dev->flags&IFF_POINTOPOINT))
+ m->dev.flags &= ~IFF_POINTOPOINT;
+ if (!(dev->flags&IFF_BROADCAST))
+ m->dev.flags &= ~IFF_BROADCAST;
+ if (!(dev->flags&IFF_MULTICAST))
+ m->dev.flags &= ~IFF_MULTICAST;
+ if (dev->mtu < m->dev.mtu)
+ m->dev.mtu = dev->mtu;
+ }
+ q->next = NEXT_SLAVE(m->slaves);
+ NEXT_SLAVE(m->slaves) = sch;
+ } else {
+ q->next = sch;
+ m->slaves = sch;
+ m->dev.mtu = dev->mtu;
+ m->dev.flags = (m->dev.flags&~FMASK)|(dev->flags&FMASK);
+ }
+
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+/* "teql*" netdevice routines */
+
+static int
+__teql_resolve(struct sk_buff *skb, struct sk_buff *skb_res, struct net_device *dev)
+{
+ struct teql_sched_data *q = (void*)dev->qdisc->data;
+ struct neighbour *mn = skb->dst->neighbour;
+ struct neighbour *n = q->ncache;
+
+ if (mn->tbl == NULL)
+ return -EINVAL;
+ if (n && n->tbl == mn->tbl &&
+ memcmp(n->primary_key, mn->primary_key, mn->tbl->key_len) == 0) {
+ atomic_inc(&n->refcnt);
+ } else {
+ n = __neigh_lookup_errno(mn->tbl, mn->primary_key, dev);
+ if (IS_ERR(n))
+ return PTR_ERR(n);
+ }
+ if (neigh_event_send(n, skb_res) == 0) {
+ int err;
+ read_lock(&n->lock);
+ err = dev->hard_header(skb, dev, ntohs(skb->protocol), n->ha, NULL, skb->len);
+ read_unlock(&n->lock);
+ if (err < 0) {
+ neigh_release(n);
+ return -EINVAL;
+ }
+ teql_neigh_release(xchg(&q->ncache, n));
+ return 0;
+ }
+ neigh_release(n);
+ return (skb_res == NULL) ? -EAGAIN : 1;
+}
+
+static __inline__ int
+teql_resolve(struct sk_buff *skb, struct sk_buff *skb_res, struct net_device *dev)
+{
+ if (dev->hard_header == NULL ||
+ skb->dst == NULL ||
+ skb->dst->neighbour == NULL)
+ return 0;
+ return __teql_resolve(skb, skb_res, dev);
+}
+
+static int teql_master_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+ struct teql_master *master = (void*)dev->priv;
+ struct Qdisc *start, *q;
+ int busy;
+ int nores;
+ int len = skb->len;
+ struct sk_buff *skb_res = NULL;
+
+ start = master->slaves;
+
+restart:
+ nores = 0;
+ busy = 0;
+
+ if ((q = start) == NULL)
+ goto drop;
+
+ do {
+ struct net_device *slave = q->dev;
+
+ if (slave->qdisc_sleeping != q)
+ continue;
+ if (netif_queue_stopped(slave) || ! netif_running(slave)) {
+ busy = 1;
+ continue;
+ }
+
+ switch (teql_resolve(skb, skb_res, slave)) {
+ case 0:
+ if (spin_trylock(&slave->xmit_lock)) {
+ slave->xmit_lock_owner = smp_processor_id();
+ if (!netif_queue_stopped(slave) &&
+ slave->hard_start_xmit(skb, slave) == 0) {
+ slave->xmit_lock_owner = -1;
+ spin_unlock(&slave->xmit_lock);
+ master->slaves = NEXT_SLAVE(q);
+ netif_wake_queue(dev);
+ master->stats.tx_packets++;
+ master->stats.tx_bytes += len;
+ return 0;
+ }
+ slave->xmit_lock_owner = -1;
+ spin_unlock(&slave->xmit_lock);
+ }
+ if (netif_queue_stopped(dev))
+ busy = 1;
+ break;
+ case 1:
+ master->slaves = NEXT_SLAVE(q);
+ return 0;
+ default:
+ nores = 1;
+ break;
+ }
+ __skb_pull(skb, skb->nh.raw - skb->data);
+ } while ((q = NEXT_SLAVE(q)) != start);
+
+ if (nores && skb_res == NULL) {
+ skb_res = skb;
+ goto restart;
+ }
+
+ if (busy) {
+ netif_stop_queue(dev);
+ return 1;
+ }
+ master->stats.tx_errors++;
+
+drop:
+ master->stats.tx_dropped++;
+ dev_kfree_skb(skb);
+ return 0;
+}
+
+static int teql_master_open(struct net_device *dev)
+{
+ struct Qdisc * q;
+ struct teql_master *m = (void*)dev->priv;
+ int mtu = 0xFFFE;
+ unsigned flags = IFF_NOARP|IFF_MULTICAST;
+
+ if (m->slaves == NULL)
+ return -EUNATCH;
+
+ flags = FMASK;
+
+ q = m->slaves;
+ do {
+ struct net_device *slave = q->dev;
+
+ if (slave == NULL)
+ return -EUNATCH;
+
+ if (slave->mtu < mtu)
+ mtu = slave->mtu;
+ if (slave->hard_header_len > LL_MAX_HEADER)
+ return -EINVAL;
+
+ /* If all the slaves are BROADCAST, master is BROADCAST
+ If all the slaves are PtP, master is PtP
+ Otherwise, master is NBMA.
+ */
+ if (!(slave->flags&IFF_POINTOPOINT))
+ flags &= ~IFF_POINTOPOINT;
+ if (!(slave->flags&IFF_BROADCAST))
+ flags &= ~IFF_BROADCAST;
+ if (!(slave->flags&IFF_MULTICAST))
+ flags &= ~IFF_MULTICAST;
+ } while ((q = NEXT_SLAVE(q)) != m->slaves);
+
+ m->dev.mtu = mtu;
+ m->dev.flags = (m->dev.flags&~FMASK) | flags;
+ netif_start_queue(&m->dev);
+ MOD_INC_USE_COUNT;
+ return 0;
+}
+
+static int teql_master_close(struct net_device *dev)
+{
+ netif_stop_queue(dev);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static struct net_device_stats *teql_master_stats(struct net_device *dev)
+{
+ struct teql_master *m = (void*)dev->priv;
+ return &m->stats;
+}
+
+static int teql_master_mtu(struct net_device *dev, int new_mtu)
+{
+ struct teql_master *m = (void*)dev->priv;
+ struct Qdisc *q;
+
+ if (new_mtu < 68)
+ return -EINVAL;
+
+ q = m->slaves;
+ if (q) {
+ do {
+ if (new_mtu > q->dev->mtu)
+ return -EINVAL;
+ } while ((q=NEXT_SLAVE(q)) != m->slaves);
+ }
+
+ dev->mtu = new_mtu;
+ return 0;
+}
+
+static int teql_master_init(struct net_device *dev)
+{
+ dev->open = teql_master_open;
+ dev->hard_start_xmit = teql_master_xmit;
+ dev->stop = teql_master_close;
+ dev->get_stats = teql_master_stats;
+ dev->change_mtu = teql_master_mtu;
+ dev->type = ARPHRD_VOID;
+ dev->mtu = 1500;
+ dev->tx_queue_len = 100;
+ dev->flags = IFF_NOARP;
+ dev->hard_header_len = LL_MAX_HEADER;
+ return 0;
+}
+
+static struct teql_master the_master = {
+{
+ NULL,
+ NULL,
+ "",
+ sizeof(struct teql_sched_data),
+
+ teql_enqueue,
+ teql_dequeue,
+ teql_requeue,
+ NULL,
+
+ teql_qdisc_init,
+ teql_reset,
+ teql_destroy,
+ NULL,
+},};
+
+
+#ifdef MODULE
+int init_module(void)
+#else
+int __init teql_init(void)
+#endif
+{
+ int err;
+
+ rtnl_lock();
+
+ the_master.dev.priv = (void*)&the_master;
+ err = dev_alloc_name(&the_master.dev, "teql%d");
+ if (err < 0)
+ return err;
+ memcpy(the_master.qops.id, the_master.dev.name, IFNAMSIZ);
+ the_master.dev.init = teql_master_init;
+
+ err = register_netdevice(&the_master.dev);
+ if (err == 0) {
+ err = register_qdisc(&the_master.qops);
+ if (err)
+ unregister_netdevice(&the_master.dev);
+ }
+ rtnl_unlock();
+ return err;
+}
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+ rtnl_lock();
+ unregister_qdisc(&the_master.qops);
+ unregister_netdevice(&the_master.dev);
+ rtnl_unlock();
+}
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sched/sch_wrr.c b/uClinux-2.4.31-uc0/net/sched/sch_wrr.c
new file mode 100644
index 0000000..72eafbe
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sched/sch_wrr.c
@@ -0,0 +1,1364 @@
+/*-----------------------------------------------------------------------------
+Weighted Round Robin scheduler.
+
+Written by Christian Worm Mortensen, cworm@it-c.dk.
+
+Introduction
+============
+This module implements a weighted round robin queue with build-in classifier.
+The classifier currently map each MAC or IP address (configurable either MAC
+or IP and either source or destination) to different classes. Each such class
+is called a band. Whan using MAC addresses only bridged packets can be
+classified other packets go to a default MAC address.
+
+Each band has a weight value, where 0<weight<=1. The bandwidth each band
+get is proportional to the weight as can be deduced from the next section.
+
+
+The queue
+=========
+Each band has a penalty value. Bands having something to sent are kept in
+a heap according to this value. The band with the lowest penalty value
+is in the root of the heap. The penalty value is a 128 bit number. Initially
+no bands are in the heap.
+
+Two global 64 bit values counter_low_penal and couter_high_penal are initialized
+to 0 and to 2^63 respectively.
+
+Enqueing:
+ The packet is inserted in the queue for the band it belongs to. If the band
+ is not in the heap it is inserted into it. In this case, the upper 64 bits
+ of its penalty value is set to the same as for the root-band of the heap.
+ If the heap is empty 0 is used. The lower 64 bit is set to couter_low_penal
+ and couter_low_penal is incremented by 1.
+
+Dequing:
+ If the heap is empty we have nothing to send.
+
+ If the root band has a non-empty queue a packet is dequeued from that.
+ The upper 64 bit of the penalty value of the band is incremented by the
+ packet size divided with the weight of the band. The lower 64 bit is set to
+ couter_high_penal and couter_high_penal is incremented by 1.
+
+ If the root element for some reason has an empty queue it is removed from
+ the heap and we try to dequeue again.
+
+The effect of the heap and the upper 64 bit of the penalty values is to
+implement a weighted round robin queue. The effect of counter_low_penal,
+counter_high_penal and the lower 64 bit of the penalty value is primarily to
+stabilize the queue and to give better quality of service to machines only
+sending a packet now and then. For example machines which have a single
+interactive connection such as telnet or simple text chatting.
+
+
+Setting weight
+==============
+The weight value can be changed dynamically by the queue itself. The weight
+value and how it is changed is described by the two members weight1 and
+weight2 which has type tc_wrr_class_weight and which are in each class. And
+by the two integer value members of the qdisc called penalfact1 and penalfact2.
+The structure is defined as:
+
+ struct tc_wrr_class_weight {
+ // All are represented as parts of (2^64-1).
+ __u64 val; // Current value (0 is not valid)
+ __u64 decr; // Value pr bytes (2^64-1 is not valid)
+ __u64 incr; // Value pr seconds (2^64-1 is not valid)
+ __u64 min; // Minimal value (0 is not valid)
+ __u64 max; // Minimal value (0 is not valid)
+
+ // The time where the above information was correct:
+ time_t tim;
+ };
+
+The weight value used by the dequeue operations is calculated as
+weight1.val*weight2.val. weight1 and weight2 and handled independently and in the
+same way as will be described now.
+
+Every second, the val parameter is incremented by incr.
+
+Every time a packet is transmitted the value is increment by decr times
+the packet size. Depending on the value of the weight_mode parameter it
+is also mulitplied with other numbers. This makes it possible to give
+penalty to machines transferring much data.
+
+-----------------------------------------------------------------------------*/
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <asm/uaccess.h>
+#include <asm/system.h>
+#include <asm/bitops.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/vmalloc.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/in.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/if_ether.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/notifier.h>
+#include <net/ip.h>
+#include <net/route.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/pkt_sched.h>
+
+#include <linux/if_arp.h>
+#include <linux/version.h>
+
+// There seems to be problems when calling functions from userspace when
+// using vmalloc and vfree.
+//#define my_malloc(size) vmalloc(size)
+//#define my_free(ptr) vfree(ptr)
+#define my_malloc(size) kmalloc(size,GFP_KERNEL)
+#define my_free(ptr) kfree(ptr)
+
+// Kernel depend stuff:
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,0)
+ #define KERNEL22
+#endif
+
+#ifdef KERNEL22
+ #define LOCK_START start_bh_atomic();
+ #define LOCK_END end_bh_atomic();
+ #define ENQUEUE_SUCCESS 1
+ #define ENQUEUE_FAIL 0
+ #ifdef CONFIG_IP_MASQUERADE
+ #include <net/ip_masq.h>
+ #define MASQ_SUPPORT
+ #endif
+#else
+ #define LOCK_START sch_tree_lock(sch);
+ #define LOCK_END sch_tree_unlock(sch);
+ #define ENQUEUE_SUCCESS 0
+ #define ENQUEUE_FAIL NET_XMIT_DROP
+ #ifdef CONFIG_NETFILTER
+ #include <linux/netfilter_ipv4/ip_conntrack.h>
+ #define MASQ_SUPPORT
+ #endif
+#endif
+
+#include "proxydict.c"
+
+// The penalty (priority) type:
+typedef u64 penalty_base_t;
+#define penalty_base_t_max ((penalty_base_t)-1)
+typedef struct penalty_t {
+ penalty_base_t ms;
+ penalty_base_t ls;
+} penalty_t;
+#define penalty_leq(a,b) (a.ms<b.ms || (a.ms==b.ms && a.ls<=b.ls))
+#define penalty_le(a,b) (a.ms<b.ms || (a.ms==b.ms && a.ls<b.ls))
+static penalty_t penalty_max={penalty_base_t_max,penalty_base_t_max};
+
+//-----------------------------------------------------------------------------
+// A generel heap.
+
+struct heap;
+struct heap_element;
+
+// Initializes an empty heap:
+// he: A pointer to an unintialized heap structure identifying the heap
+// size: Maximal number of elements the heap can contain
+// poll: An array of size "size" used by the heap.
+static void heap_init(struct heap* he,int size, struct heap_element* poll);
+
+// Each element in the heap is identified by a user-assigned id which
+// should be a non negative integer less than the size argument
+// given to heap_init.
+static void heap_insert(struct heap*, int id, penalty_t);
+static void heap_remove(struct heap*, int id);
+static void heap_set_penalty(struct heap*, int id, penalty_t);
+
+// Retreviewing information:
+static char heap_empty(struct heap*); // Heap empty?
+static char heap_contains(struct heap*, int id); // Does heap contain
+ // the given id?
+static int heap_root(struct heap*); // Returns the id of the root
+static penalty_t heap_get_penalty(struct heap*, int id); // Returns penaly
+ // of root node
+
+//--------------------
+// Heap implementation
+
+struct heap_element {
+ penalty_t penalty;
+ int id; // The user-assigned id of this element
+ int id2idx; // Maps from user-assigned ids to indices in root_1
+};
+
+struct heap {
+ struct heap_element* root_1;
+ int elements;
+};
+
+// Heap implementation:
+static void heap_init(struct heap* h, int size, struct heap_element* poll) {
+ int i;
+
+ h->elements=0;
+ h->root_1=poll-1;
+
+ for(i=0; i<size; i++) poll[i].id2idx=0;
+};
+
+static char heap_empty(struct heap* h) {
+ return h->elements==0;
+}
+
+static char heap_contains(struct heap* h, int id) {
+ return h->root_1[id+1].id2idx!=0;
+}
+
+static int heap_root(struct heap* h) {
+ return h->root_1[1].id;
+}
+
+static penalty_t heap_get_penalty(struct heap* h, int id) {
+ return h->root_1[ h->root_1[id+1].id2idx ].penalty;
+}
+
+static void heap_penalty_changed_internal(struct heap* h,int idx);
+
+static void heap_set_penalty(struct heap* h, int id, penalty_t p) {
+ int idx=h->root_1[id+1].id2idx;
+ h->root_1[idx].penalty=p;
+ heap_penalty_changed_internal(h,idx);
+}
+
+static void heap_insert(struct heap* h, int id, penalty_t p) {
+ // Insert at the end of the heap:
+ h->elements++;
+ h->root_1[h->elements].id=id;
+ h->root_1[h->elements].penalty=p;
+ h->root_1[id+1].id2idx=h->elements;
+
+ // And put it in the right position:
+ heap_penalty_changed_internal(h,h->elements);
+}
+
+static void heap_remove(struct heap* h, int id) {
+ int idx=h->root_1[id+1].id2idx;
+ int mvid;
+ h->root_1[id+1].id2idx=0;
+
+ if(h->elements==idx) { h->elements--; return; }
+
+ mvid=h->root_1[h->elements].id;
+ h->root_1[idx].id=mvid;
+ h->root_1[idx].penalty=h->root_1[h->elements].penalty;
+ h->root_1[mvid+1].id2idx=idx;
+
+ h->elements--;
+ heap_penalty_changed_internal(h,idx);
+}
+
+static void heap_swap(struct heap* h, int idx0, int idx1) {
+ penalty_t tmp_p;
+ int tmp_id;
+ int id0,id1;
+
+ // Simple content:
+ tmp_p=h->root_1[idx0].penalty;
+ tmp_id=h->root_1[idx0].id;
+ h->root_1[idx0].penalty=h->root_1[idx1].penalty;
+ h->root_1[idx0].id=h->root_1[idx1].id;
+ h->root_1[idx1].penalty=tmp_p;
+ h->root_1[idx1].id=tmp_id;
+
+ // Update reverse pointers:
+ id0=h->root_1[idx0].id;
+ id1=h->root_1[idx1].id;
+ h->root_1[id0+1].id2idx=idx0;
+ h->root_1[id1+1].id2idx=idx1;
+}
+
+static void heap_penalty_changed_internal(struct heap* h,int cur) {
+ if(cur==1 || penalty_leq(h->root_1[cur>>1].penalty,h->root_1[cur].penalty)) {
+ // We are in heap order upwards - so we should move the element down
+ for(;;) {
+ int nxt0=cur<<1;
+ int nxt1=nxt0+1;
+ penalty_t pen_c=h->root_1[cur].penalty;
+ penalty_t pen_0=nxt0<=h->elements ? h->root_1[nxt0].penalty : penalty_max;
+ penalty_t pen_1=nxt1<=h->elements ? h->root_1[nxt1].penalty : penalty_max;
+
+ if(penalty_le(pen_0,pen_c) && penalty_leq(pen_0,pen_1)) {
+ // Swap with child 0:
+ heap_swap(h,cur,nxt0);
+ cur=nxt0;
+ } else if(penalty_le(pen_1,pen_c)) {
+ // Swap with child 1:
+ heap_swap(h,cur,nxt1);
+ cur=nxt1;
+ } else {
+ // Heap in heap order:
+ return;
+ }
+ }
+ } else {
+ // We are not in heap order upwards (and thus we must be it downwards).
+ // We move up:
+ while(cur!=1) { // While not root
+ int nxt=cur>>1;
+ if(penalty_leq(h->root_1[nxt].penalty,h->root_1[cur].penalty)) return;
+ heap_swap(h,cur,nxt);
+ cur=nxt;
+ }
+ }
+};
+
+//-----------------------------------------------------------------------------
+// Classification based on MAC or IP adresses. Note that of historical reason
+// these are prefixed with mac_ since originally only MAC bases classification
+// was supported.
+//
+// This code should be in a separate filter module - but it isn't.
+
+// Interface:
+
+struct mac_head;
+
+// Initialices/destroys the structure we maintain.
+// Returns -1 on error
+static int mac_init(struct mac_head*, int max_macs, char srcaddr,
+ char usemac, char usemasq, void* proxyremap);
+static void mac_done(struct mac_head*);
+static void mac_reset(struct mac_head*);
+
+// Classify a packet. Returns a number n where 0<=n<max_macs. Or -1 if
+// the packet should be dropped.
+static int mac_classify(struct mac_head*, struct sk_buff *skb);
+
+//-------------
+// Implementation:
+
+struct mac_addr {
+ unsigned char addr[ETH_ALEN]; // Address of this band (last two are 0 on IP)
+ unsigned long lastused; // Last time a packet was encountered
+ int class; // Classid of this band (0<=classid<max_macs)
+};
+
+static int mac_compare(const void* a, const void* b) {
+ return memcmp(a,b,ETH_ALEN);
+}
+
+struct mac_head {
+ int mac_max; // Maximal number of MAC addresses/classes allowed
+ int mac_cur; // Current number of MAC addresses/classes
+ int mac_reused; // Number of times we have reused a class with a new
+ // address.
+ u64 incr_time;
+ char srcaddr; // True if we classify on the source address of packets,
+ // else we use destination address.
+ char usemac; // If true we use mac, else we use IP
+ char usemasq; // If true we try to demasqgrade
+ struct mac_addr* macs; // Allocated mac_max elements, used max_cur
+ char* cls2mac; // Mapping from classnumbers to addresses -
+ // there is 6 bytes in each entry
+
+ void* proxyremap; // Information on proxy remapping of data or 0
+};
+
+// This is as the standard C library function with the same name:
+static const void* bsearch(const void* key, const void* base, int nmemb,
+ size_t size,
+ int (*compare)(const void*, const void*)) {
+ int m_idx;
+ const void* m_ptr;
+ int i;
+
+ if(nmemb<=0) return 0;
+
+ m_idx=nmemb>>1;
+ m_ptr=((const char*)base)+m_idx*size;
+
+ i=compare(key,m_ptr);
+ if(i<0) // key is less
+ return bsearch(key,base,m_idx,size,compare);
+ else if(i>0)
+ return bsearch(key,((const char*)m_ptr)+size,nmemb-m_idx-1,size,compare);
+
+ return m_ptr;
+}
+
+static int mac_init(struct mac_head* h, int max_macs, char srcaddr,
+ char usemac, char usemasq,void* proxyremap) {
+ h->mac_cur=0;
+ h->mac_reused=0;
+ h->incr_time=0;
+ h->srcaddr=srcaddr;
+ h->usemac=usemac;
+ h->usemasq=usemasq;
+ h->mac_max=max_macs;
+ h->proxyremap=proxyremap;
+
+ h->macs=(struct mac_addr*)
+ my_malloc( sizeof(struct mac_addr)*max_macs);
+ h->cls2mac=(char*)my_malloc( 6*max_macs);
+ if(!h->macs || !h->cls2mac) {
+ if(h->macs) my_free(h->macs);
+ if(h->cls2mac) my_free(h->cls2mac);
+ return -1;
+ }
+ return 0;
+}
+
+static void mac_done(struct mac_head* h) {
+ my_free(h->macs);
+ my_free(h->cls2mac);
+}
+
+static void mac_reset(struct mac_head* h) {
+ h->mac_cur=0;
+ h->mac_reused=0;
+ h->incr_time=0;
+}
+
+static int lookup_mac(struct mac_head* h, unsigned char* addr) {
+ int i;
+ int class;
+
+ // First try to find the address in the table:
+ struct mac_addr* m=(struct mac_addr*)
+ bsearch(addr,h->macs,h->mac_cur,sizeof(struct mac_addr),mac_compare);
+ if(m) {
+ // Found:
+ m->lastused=h->incr_time++;
+ return m->class;
+ }
+
+ // Okay - the MAC adress was not in table
+ if(h->mac_cur==h->mac_max) {
+ // And the table is full - delete the oldest entry:
+
+ // Find the oldest entry:
+ int lowidx=0;
+ int i;
+ for(i=1; i<h->mac_cur; i++)
+ if(h->macs[i].lastused < h->macs[lowidx].lastused) lowidx=i;
+
+ class=h->macs[lowidx].class;
+
+ // And delete it:
+ memmove(&h->macs[lowidx],&h->macs[lowidx+1],
+ (h->mac_cur-lowidx-1)*sizeof(struct mac_addr));
+ h->mac_reused++;
+ h->mac_cur--;
+ } else {
+ class=h->mac_cur;
+ }
+
+ // The table is now not full - find the position we should put the address in:
+ for(i=0; i<h->mac_cur; i++) if(mac_compare(addr,&h->macs[i])<0) break;
+
+ // We should insert at position i:
+ memmove(&h->macs[i+1],&h->macs[i],(h->mac_cur-i)*sizeof(struct mac_addr));
+ m=&h->macs[i];
+ memcpy(m->addr,addr,ETH_ALEN);
+ m->lastused=h->incr_time++;
+ m->class=class;
+ h->mac_cur++;
+
+ // Finally update the cls2mac variabel:
+ memcpy(h->cls2mac+ETH_ALEN*class,addr,ETH_ALEN);
+
+ return m->class;
+}
+
+int valid_ip_checksum(struct iphdr* ip, int size) {
+ __u16 header_len=ip->ihl<<2;
+ __u16 c=0;
+ __u16* ipu=(u16*)ip;
+ int a;
+
+ // We require 4 bytes in the packet since we access the port numbers:
+ if((size<header_len) || size<sizeof(struct iphdr)+4) return 0;
+
+ for(a=0; a<(header_len>>1); a++, ipu++) {
+ if(a!=5) { // If not the checksum field
+ __u16 oldc=c;
+ c+=(*ipu);
+ if(c<oldc) c++;
+ }
+ }
+
+ return ip->check==(__u16)~c;
+}
+
+static int mac_classify(struct mac_head* head, struct sk_buff *skb)
+{
+ // We set this to the address we map to. In case we map to an IP
+ // address the last two entries are set to 0.
+ unsigned char addr[ETH_ALEN];
+
+
+ // This is the size of the network part of the packet, I think:
+ int size=((char*)skb->data+skb->len)-((char*)skb->nh.iph);
+
+ // Set a default value for the address:
+ memset(addr,0,ETH_ALEN);
+
+ // Accept IP-ARP traffic with big-enough packets:
+ if(ntohs(skb->protocol)==ETH_P_ARP &&
+ ntohs(skb->nh.arph->ar_pro)==ETH_P_IP) {
+ // Map all ARP trafic to a default adress to make sure
+ // it goes through
+ } else if ((ntohs(skb->protocol)==ETH_P_IP) &&
+ valid_ip_checksum(skb->nh.iph,size)) {
+ // Accept IP packets which have correct checksum.
+
+ // This is the IP header:
+ struct iphdr* iph=skb->nh.iph;
+
+ // And this is the port numbers:
+ const __u16 *portp = (__u16 *)&(((char *)iph)[iph->ihl*4]);
+ __u16 sport=portp[0];
+ __u16 dport=portp[1];
+
+ // We will set this to the IP address of the packet that should be
+ // accounted to:
+ unsigned ipaddr;
+
+ // Used below:
+ ProxyRemapBlock* prm;
+
+ // Set ipaddr:
+ if(head->srcaddr)
+ ipaddr=iph->saddr;
+ else
+ ipaddr=iph->daddr;
+
+#ifdef MASQ_SUPPORT
+ // Update ipaddr if packet is masqgraded:
+ if(head->usemasq) {
+ #ifdef KERNEL22
+ struct ip_masq* src;
+
+ // HACK!:
+ // ip_masq_in_get must be called for packets comming from the outside
+ // to the firewall. We have a a packet which is comming from the
+ // firewall to the outside - so we switch the parameters:
+ if((src=ip_masq_in_get(
+ iph->protocol,
+ iph->daddr,dport,
+ iph->saddr,sport))) {
+ // Use masqgraded address:
+ ipaddr=src->saddr;
+
+ // It seems like we must put it back:
+ ip_masq_put(src);
+ }
+ #else
+ // Thanks to Rusty Russell for help with the following code:
+ enum ip_conntrack_info ctinfo;
+ struct ip_conntrack *ct;
+ ct = ip_conntrack_get(skb, &ctinfo);
+ if (ct) {
+ if(head->srcaddr)
+ ipaddr=ct->tuplehash[CTINFO2DIR(ctinfo)].tuple.src.ip;
+ else
+ ipaddr=ct->tuplehash[CTINFO2DIR(ctinfo)].tuple.dst.ip;
+ }
+ #endif
+ }
+#endif
+
+ // Set prm based on ipaddr:
+ prm=0;
+ if(head->proxyremap) {
+ if(head->srcaddr) {
+ prm=proxyLookup(head->proxyremap,ipaddr,sport,skb->nh.iph->protocol);
+ } else {
+ prm=proxyLookup(head->proxyremap,ipaddr,dport,skb->nh.iph->protocol);
+ }
+ }
+
+ // And finally set addr to the address:
+ memset(addr,0,ETH_ALEN);
+ if(prm) {
+ // This package should be remapped:
+ if(head->usemac)
+ memcpy(addr,prm->macaddr,ETH_ALEN);
+ else {
+ memcpy(addr,&prm->caddr,sizeof(unsigned));
+ }
+ } else {
+ // This packet should not be remapped:
+ if(head->usemac) {
+ // We should find MAC address of packet.
+ // Unfortunatly, this is not always available.
+ // On bridged packets it always is, however..
+ #ifdef KERNEL22
+ if(skb->pkt_bridged) {
+ if(head->srcaddr) {
+ memcpy(addr,skb->mac.ethernet->h_source,ETH_ALEN);
+ } else {
+ memcpy(addr,skb->mac.ethernet->h_dest,ETH_ALEN);
+ }
+ }
+ #endif
+ } else {
+ memcpy(addr,&ipaddr,4);
+ }
+ }
+ } else {
+ // All other traffic is dropped - this ensures that packets
+ // we consider probably have valid addresses so we don't
+ // get to many strange addresses into our table. And that we
+ // don't use bandwidth on strange packets..
+ return -1;
+ }
+
+ return lookup_mac(head,addr);
+}
+
+//-----------------------------------------------------------------------------
+// The qdisc itself
+
+// Pr-class information.
+struct wrrc_sched_data {
+ struct Qdisc* que; // The queue for this class
+ struct tc_wrr_class_modf class_modf; // Information about the class.
+
+ // For classes in the heap this is the priority value priosum
+ // was updated with for this class:
+ u64 priosum_val;
+};
+
+// Pr-qdisc information:
+struct wrr_sched_data
+{
+ // A heap containing all the bands that will send something
+ struct heap h;
+ struct heap_element* poll; // bandc elements
+
+ // The sum of the prioities of the elements in the heap where
+ // a priority of 1 is saved as 2^32
+ u64 priosum;
+
+ // A class for each band
+ struct wrrc_sched_data* bands; // bandc elements
+
+ // Information maintained by the proxydict module of 0 if we
+ // have no proxy remapping
+ void* proxydict;
+
+ // Always incrementning counters, we always have that any value of
+ // counter_low_penal < any value of counter_high_penal.
+ penalty_base_t counter_low_penal;
+ penalty_base_t counter_high_penal;
+
+ // Penalty updating:
+ struct tc_wrr_qdisc_modf qdisc_modf;
+
+ // Statistics:
+ int packets_requed;
+
+ // The filter:
+ struct mac_head filter;
+ int bandc; // Number of bands
+};
+
+// Priority handling.
+// weight is in interval [0..2^32]
+// priosum has whole numbers in the upper and fragments in the lower 32 bits.
+static void weight_transmit(struct tc_wrr_class_weight* p,
+ struct tc_wrr_qdisc_weight q,
+ unsigned heapsize,
+ u64 priosum, u64 weight,
+ unsigned size) {
+
+ unsigned long now=jiffies/HZ;
+
+ // Penalty for transmitting:
+ u64 change,old;
+ u32 divisor;
+
+ change=0;
+ switch(q.weight_mode) {
+ case 1: change=p->decr*size; break;
+ case 2: change=p->decr*size*heapsize; break;
+ case 3: // Note: 64 bit division is not always available..
+ divisor=(u32)(weight>>16);
+ if(divisor<=0) divisor=1;
+ change=p->decr*size*(((u32)(priosum>>16))/divisor); break;
+ }
+ old=p->val;
+ p->val-=change;
+ if(p->val>old || p->val<p->min) p->val=p->min;
+
+ // Credit for time went:
+ change=(now-p->tim)*p->incr;
+ p->tim=now;
+ old=p->val;
+ p->val+=change;
+ if(p->val<old || p->val>p->max) p->val=p->max;
+}
+
+static void weight_setdefault(struct tc_wrr_class_weight* p) {
+ p->val=(u64)-1;
+ p->decr=0;
+ p->incr=0;
+ p->min=(u64)-1;
+ p->max=(u64)-1;
+ p->tim=jiffies/HZ;
+}
+
+static void weight_setvalue(struct tc_wrr_class_weight* dst,
+ struct tc_wrr_class_weight* src) {
+ if(src->val!=0) {
+ dst->val=src->val;
+ dst->tim=jiffies/HZ;
+ }
+ if(src->min!=0) dst->min=src->min;
+ if(src->max!=0) dst->max=src->max;
+ if(src->decr!=((u64)-1)) dst->decr=src->decr;
+ if(src->incr!=((u64)-1)) dst->incr=src->incr;
+ if(dst->val<dst->min) dst->val=dst->min;
+ if(dst->val>dst->max) dst->val=dst->max;
+}
+
+static void wrr_destroy(struct Qdisc *sch)
+{
+ struct wrr_sched_data *q=(struct wrr_sched_data *)sch->data;
+ int i;
+
+ // Destroy our filter:
+ mac_done(&q->filter);
+
+ // Destroy all our childre ques:
+ for(i=0; i<q->bandc; i++)
+ qdisc_destroy(q->bands[i].que);
+
+ // And free memory:
+ my_free(q->bands);
+ my_free(q->poll);
+ if(q->proxydict) my_free(q->proxydict);
+
+ MOD_DEC_USE_COUNT;
+}
+
+static int wrr_init(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ int i,maciniterr;
+ char crterr;
+ struct tc_wrr_qdisc_crt *qopt;
+
+ // Parse options:
+ if (!opt) return -EINVAL; // Options must be specified
+ if (opt->rta_len < RTA_LENGTH(sizeof(*qopt))) return -EINVAL;
+ qopt = RTA_DATA(opt);
+
+ if(qopt->bands_max>8192 || qopt->bands_max<2) {
+ // More than 8192 queues or less than 2? That cannot be true - it must be
+ // an error...
+ return -EINVAL;
+ }
+
+ if(qopt->proxy_maxconn<0 || qopt->proxy_maxconn>20000) {
+ // More than this number of maximal concurrent connections is unrealistic
+ return -EINVAL;
+ }
+
+#ifndef MASQ_SUPPORT
+ if(qopt->usemasq) {
+ return -ENOSYS;
+ }
+#endif
+
+#ifndef KERNEL22
+ if(qopt->usemac) { // Not supported - please fix this!
+ return -ENOSYS;
+ }
+#endif
+
+ q->bandc=qopt->bands_max;
+ q->qdisc_modf=qopt->qdisc_modf;
+
+ // Create structures:
+ q->poll=(struct heap_element*)
+ my_malloc( sizeof(struct heap_element)*q->bandc);
+ q->bands=(struct wrrc_sched_data*)
+ my_malloc( sizeof(struct wrrc_sched_data)*q->bandc);
+
+ if(qopt->proxy_maxconn>0) {
+ q->proxydict=my_malloc(proxyGetMemSize(qopt->proxy_maxconn));
+ } else {
+ q->proxydict=0;
+ }
+
+ // Init mac module:
+ maciniterr=mac_init(&q->filter,qopt->bands_max,qopt->srcaddr,
+ qopt->usemac,qopt->usemasq,q->proxydict);
+
+ // See if we got the memory we wanted:
+ if(!q->poll || !q->bands ||
+ (qopt->proxy_maxconn>0 && !q->proxydict) || maciniterr<0) {
+ if(q->poll) my_free(q->poll);
+ if(q->bands) my_free(q->bands);
+ if(q->proxydict) my_free(q->proxydict);
+ if(maciniterr>=0) mac_done(&q->filter);
+ return -ENOMEM;
+ }
+
+ // Initialize proxy:
+ if(q->proxydict) {
+ proxyInitMem(q->proxydict,qopt->proxy_maxconn);
+ }
+
+ // Initialize values:
+ q->counter_low_penal=0;
+ q->counter_high_penal=penalty_base_t_max>>1;
+ q->packets_requed=0;
+
+ // Initialize empty heap:
+ heap_init(&q->h,q->bandc,q->poll);
+ q->priosum=0;
+
+ // Initialize each band:
+ crterr=0;
+ for (i=0; i<q->bandc; i++) {
+ weight_setdefault(&q->bands[i].class_modf.weight1);
+ weight_setdefault(&q->bands[i].class_modf.weight2);
+ if(!crterr) {
+ struct Qdisc *child=qdisc_create_dflt(sch->dev, &pfifo_qdisc_ops);
+ if(child)
+ q->bands[i].que = child;
+ else {
+ // Queue couldn't be created :-(
+ crterr=1;
+ }
+ }
+ if(crterr) q->bands[i].que = &noop_qdisc;
+ }
+
+ MOD_INC_USE_COUNT;
+
+ if(crterr) {
+ // Destroy again:
+ wrr_destroy(sch);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void wrr_reset(struct Qdisc* sch)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ int i;
+
+ // Reset own values:
+ q->counter_low_penal=0;
+ q->counter_high_penal=penalty_base_t_max>>1;
+ q->packets_requed=0;
+
+ // Reset filter:
+ mac_reset(&q->filter);
+
+ // Reinitialize heap:
+ heap_init(&q->h,q->bandc,q->poll);
+ q->priosum=0;
+
+ // Reset all bands:
+ for (i=0; i<q->bandc; i++) {
+ weight_setdefault(&q->bands[i].class_modf.weight1);
+ weight_setdefault(&q->bands[i].class_modf.weight2);
+ qdisc_reset(q->bands[i].que);
+ }
+
+ // Reset proxy remapping information:
+ if(q->proxydict)
+ proxyInitMem(q->proxydict,proxyGetMaxConn(q->proxydict));
+}
+
+static int wrr_enqueue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ int retvalue=ENQUEUE_FAIL;
+
+ // The packet is in skb.
+ int band=mac_classify(&q->filter,skb);
+
+ if(band>=0) {
+ // Enque packet for this band:
+ struct Qdisc* qdisc = q->bands[band].que;
+
+ if ((retvalue=qdisc->enqueue(skb, qdisc)) == ENQUEUE_SUCCESS) {
+ // Successfull
+ sch->stats.bytes += skb->len;
+ sch->stats.packets++;
+ sch->q.qlen++;
+
+ // Insert band into heap if not already there:
+ if(!heap_contains(&q->h,band)) {
+ penalty_t p;
+ if(!heap_empty(&q->h))
+ p.ms=heap_get_penalty(&q->h,heap_root(&q->h)).ms;
+ else
+ p.ms=0;
+ p.ls=q->counter_low_penal++;
+ heap_insert(&q->h,band,p);
+ q->bands[band].priosum_val=
+ ((q->bands[band].class_modf.weight1.val>>48)+1)*
+ ((q->bands[band].class_modf.weight2.val>>48)+1);
+ q->priosum+=q->bands[band].priosum_val;
+ }
+ }
+ } else {
+ // If we decide not to enque it seems like we also need to free the packet:
+ kfree_skb(skb);
+ }
+
+ if(retvalue!=ENQUEUE_SUCCESS) {
+ // Packet not enqued:
+ sch->stats.drops++;
+ }
+
+ return retvalue;
+}
+
+static struct sk_buff *wrr_dequeue(struct Qdisc* sch)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ struct sk_buff* skb;
+ int band;
+ u64 weight,priosum;
+ struct wrrc_sched_data* b;
+
+ // Return if heap is empty:
+ if(heap_empty(&q->h)) return 0;
+
+ // Find root element:
+ band=heap_root(&q->h);
+
+ // Find priority of this element in interval [1;2^32]
+ b=&q->bands[band];
+ weight=((b->class_modf.weight1.val>>48)+1)*
+ ((b->class_modf.weight2.val>>48)+1); //weight is in interval [1;2^32]
+ priosum=q->priosum;
+ q->priosum-=q->bands[band].priosum_val;
+
+ // Deque the packet from the root:
+ skb=q->bands[band].que->dequeue(q->bands[band].que);
+
+ if(skb) {
+ // There was a packet in this que.
+ unsigned adjlen;
+ penalty_t p;
+
+ // Find length of packet adjusted with priority:
+ adjlen=(u32)(weight>>(32-16));
+ if(adjlen==0) adjlen=1;
+ adjlen=(skb->len<<16)/adjlen;
+
+ // Update penalty information for this class:
+ weight_transmit(&b->class_modf.weight1,q->qdisc_modf.weight1,q->h.elements,priosum,weight,skb->len);
+ weight_transmit(&b->class_modf.weight2,q->qdisc_modf.weight2,q->h.elements,priosum,weight,skb->len);
+ q->bands[band].priosum_val=((b->class_modf.weight1.val>>48)+1)*
+ ((b->class_modf.weight2.val>>48)+1);
+ q->priosum+=q->bands[band].priosum_val;
+
+ // And update the class in the heap
+ p=heap_get_penalty(&q->h,band);
+ p.ms+=adjlen;
+ p.ls=q->counter_high_penal++;
+ heap_set_penalty(&q->h,band,p);
+
+ // Return packet:
+ sch->q.qlen--;
+ return skb;
+ }
+
+ // No packet - so machine should be removed from heap:
+ heap_remove(&q->h,band);
+
+ // And try again:
+ return wrr_dequeue(sch);
+}
+
+static int wrr_requeue(struct sk_buff *skb, struct Qdisc* sch)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ struct Qdisc* qdisc;
+ int ret;
+
+ // Find band we took it from:
+ int band=mac_classify(&q->filter,skb);
+ if(band<0) {
+ // Who should now free the pakcet?
+ printk(KERN_DEBUG "sch_wrr: Oops - packet requed could never have been queued.\n");
+ sch->stats.drops++;
+ return ENQUEUE_FAIL;
+ }
+
+ q->packets_requed++;
+
+ // Try to requeue it on that machine:
+ qdisc=q->bands[band].que;
+
+ if((ret=qdisc->ops->requeue(skb,qdisc))==ENQUEUE_SUCCESS) {
+ // On success:
+ sch->q.qlen++;
+
+ // We should restore priority information - but we don't
+ //
+ // p=heap_get_penalty(&q->h,band);
+ // ...
+ // heap_set_penalty(&q->h,band,p);
+
+ return ENQUEUE_SUCCESS;
+ } else {
+ sch->stats.drops++;
+ return ret;
+ }
+}
+
+static int wrr_drop(struct Qdisc* sch)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+
+ // Ugly... Drop button up in heap:
+ int i;
+
+ for(i=q->h.elements; i>=1; i--) {
+ int band=q->h.root_1[i].id;
+ if(q->bands[band].que->ops->drop(q->bands[band].que)) {
+ // On success
+ sch->q.qlen--;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int wrr_dump(struct Qdisc *sch, struct sk_buff *skb)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ unsigned char *b = skb->tail;
+ struct tc_wrr_qdisc_stats opt;
+
+ opt.qdisc_crt.qdisc_modf=q->qdisc_modf;
+ opt.qdisc_crt.srcaddr=q->filter.srcaddr;
+ opt.qdisc_crt.usemac=q->filter.usemac;
+ opt.qdisc_crt.usemasq=q->filter.usemasq;
+ opt.qdisc_crt.bands_max=q->filter.mac_max;
+ opt.nodes_in_heap=q->h.elements;
+ opt.bands_cur=q->filter.mac_cur;
+ opt.bands_reused=q->filter.mac_reused;
+ opt.packets_requed=q->packets_requed;
+ opt.priosum=q->priosum;
+
+ if(q->proxydict) {
+ opt.qdisc_crt.proxy_maxconn=proxyGetMaxConn(q->proxydict);
+ opt.proxy_curconn=proxyGetCurConn(q->proxydict);
+ } else {
+ opt.qdisc_crt.proxy_maxconn=0;
+ opt.proxy_curconn=0;
+ }
+
+ RTA_PUT(skb, TCA_OPTIONS, sizeof(opt), &opt);
+ return skb->len;
+
+rtattr_failure: // seems like RTA_PUT jump to this label..
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int wrr_tune_std(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ struct tc_wrr_qdisc_modf_std *qopt = RTA_DATA(opt);
+
+ if(opt->rta_len < RTA_LENGTH(sizeof(*qopt))) return -EINVAL;
+
+ LOCK_START
+
+ if(qopt->change_class) {
+ int idx=lookup_mac(&q->filter,qopt->addr);
+ weight_setvalue
+ (&q->bands[idx].class_modf.weight1,&qopt->class_modf.weight1);
+ weight_setvalue
+ (&q->bands[idx].class_modf.weight2,&qopt->class_modf.weight2);
+ } else {
+ if(qopt->qdisc_modf.weight1.weight_mode!=-1)
+ q->qdisc_modf.weight1.weight_mode=qopt->qdisc_modf.weight1.weight_mode;
+ if(qopt->qdisc_modf.weight2.weight_mode!=-1)
+ q->qdisc_modf.weight2.weight_mode=qopt->qdisc_modf.weight2.weight_mode;
+ }
+
+ LOCK_END
+
+ return 0;
+}
+
+static int wrr_tune_proxy(struct Qdisc *sch, struct rtattr *opt)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ struct tc_wrr_qdisc_modf_proxy *qopt = RTA_DATA(opt);
+ int i;
+
+ // Return if we are not configured with proxy support:
+ if(!q->proxydict) return -ENOSYS;
+
+ // Return if not enough data given:
+ if(opt->rta_len<RTA_LENGTH(sizeof(*qopt)) ||
+ opt->rta_len<
+ RTA_LENGTH(sizeof(*qopt)+sizeof(ProxyRemapBlock)*qopt->changec))
+ return -EINVAL;
+
+ LOCK_START;
+
+ if(qopt->reset) {
+ proxyInitMem(q->proxydict,proxyGetMaxConn(q->proxydict));
+ }
+
+ // Do all the changes:
+ for(i=0; i<qopt->changec; i++) {
+ proxyConsumeBlock(q->proxydict,&((ProxyRemapBlock*)&qopt->changes)[i]);
+ }
+
+ LOCK_END;
+
+ return 0;
+}
+
+static int wrr_tune(struct Qdisc *sch, struct rtattr *opt) {
+ if(((struct tc_wrr_qdisc_modf_std*)RTA_DATA(opt))->proxy) {
+ return wrr_tune_proxy(sch,opt);
+ } else {
+ return wrr_tune_std(sch,opt);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Classes.
+// External and internal IDs are equal. They are the band number plus 1.
+
+// Replace a class with another:
+static int wrr_graft(struct Qdisc *sch, unsigned long arg, struct Qdisc *new,
+ struct Qdisc **old)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ if(arg>q->bandc || arg==0) return -EINVAL;
+ arg--;
+
+ if (new == NULL)
+ new = &noop_qdisc;
+
+#ifdef KERNEL22
+ *old = xchg(&q->bands[arg].que, new);
+#else
+ LOCK_START
+ *old = q->bands[arg].que;
+ q->bands[arg].que = new;
+ qdisc_reset(*old);
+ LOCK_END
+#endif
+
+ return 0;
+}
+
+// Returns the qdisc for a class:
+static struct Qdisc * wrr_leaf(struct Qdisc *sch, unsigned long arg)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ if(arg>q->bandc || arg==0) return NULL;
+ arg--;
+ return q->bands[arg].que;
+}
+
+static unsigned long wrr_get(struct Qdisc *sch, u32 classid)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ unsigned long band = TC_H_MIN(classid);
+ if(band>q->bandc || band==0) return 0;
+ return band;
+}
+
+static void wrr_put(struct Qdisc *q, unsigned long cl)
+{
+ return;
+}
+
+static int wrr_delete(struct Qdisc *sch, unsigned long cl)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ if(cl==0 || cl>q->bandc) return -ENOENT;
+ cl--;
+ return 0;
+}
+
+static int wrr_dump_class(struct Qdisc *sch, unsigned long cl,
+ struct sk_buff *skb, struct tcmsg *tcm)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ unsigned char *b = skb->tail;
+ struct tc_wrr_class_stats opt;
+
+ // Handle of this class:
+ tcm->tcm_handle = sch->handle|cl;
+
+ if(cl==0 || cl>q->bandc)
+ goto rtattr_failure;
+ cl--;
+
+ if(cl>=q->filter.mac_cur) {
+ // Band is unused:
+ memset(&opt,0,sizeof(opt));
+ opt.used=0;
+ } else {
+ opt.used=1;
+ opt.class_modf.weight1=q->bands[cl].class_modf.weight1;
+ opt.class_modf.weight2=q->bands[cl].class_modf.weight2;
+ weight_transmit(&opt.class_modf.weight1,q->qdisc_modf.weight1,0,0,0,0);
+ weight_transmit(&opt.class_modf.weight2,q->qdisc_modf.weight2,0,0,0,0);
+ memcpy(opt.addr,q->filter.cls2mac+cl*ETH_ALEN,ETH_ALEN);
+ opt.usemac=q->filter.usemac;
+ opt.heappos=q->h.root_1[cl+1].id2idx;
+ if(opt.heappos!=0) { // Is in heap
+ opt.penal_ls=heap_get_penalty(&q->h,cl).ls;
+ opt.penal_ms=heap_get_penalty(&q->h,cl).ms;
+ } else {
+ opt.penal_ls=0;
+ opt.penal_ms=0;
+ }
+ }
+
+ // Put quing information:
+ RTA_PUT(skb, TCA_OPTIONS, sizeof(opt), &opt);
+ return skb->len;
+
+rtattr_failure:
+ skb_trim(skb, b - skb->data);
+ return -1;
+}
+
+static int wrr_change(struct Qdisc *sch, u32 handle, u32 parent,
+ struct rtattr **tca, unsigned long *arg)
+{
+ unsigned long cl = *arg;
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ struct rtattr *opt = tca[TCA_OPTIONS-1];
+ struct tc_wrr_class_modf *copt = RTA_DATA(opt);
+
+ if(cl==0 || cl>q->bandc) return -EINVAL;
+ cl--;
+
+ if (opt->rta_len < RTA_LENGTH(sizeof(*copt))) return -EINVAL;
+
+ LOCK_START;
+
+ weight_setvalue(&q->bands[cl].class_modf.weight1,&copt->weight1);
+ weight_setvalue(&q->bands[cl].class_modf.weight2,&copt->weight2);
+
+ LOCK_END;
+
+ return 0;
+}
+
+static void wrr_walk(struct Qdisc *sch, struct qdisc_walker *arg)
+{
+ struct wrr_sched_data *q = (struct wrr_sched_data *)sch->data;
+ int prio;
+
+ if (arg->stop) return;
+
+ for (prio = 1; prio <= q->bandc; prio++) {
+ if (arg->count < arg->skip) {
+ arg->count++;
+ continue;
+ }
+ if (arg->fn(sch, prio, arg) < 0) {
+ arg->stop = 1;
+ break;
+ }
+ arg->count++;
+ }
+}
+
+static struct tcf_proto ** wrr_find_tcf(struct Qdisc *sch, unsigned long cl)
+{
+ return NULL;
+}
+
+static unsigned long wrr_bind(struct Qdisc *sch,
+ unsigned long parent, u32 classid)
+{
+ return wrr_get(sch, classid);
+}
+
+//-----------------------------------------------------------------------------
+// Generel
+
+static struct Qdisc_class_ops wrr_class_ops =
+{
+ wrr_graft,
+ wrr_leaf,
+
+ wrr_get,
+ wrr_put,
+ wrr_change,
+ wrr_delete,
+ wrr_walk,
+
+ wrr_find_tcf,
+ wrr_bind,
+ wrr_put,
+
+#if !defined(KERNEL22) || defined(CONFIG_RTNETLINK)
+ wrr_dump_class,
+#endif
+};
+
+struct Qdisc_ops wrr_qdisc_ops =
+{
+ NULL,
+ &wrr_class_ops,
+ "wrr",
+ sizeof(struct wrr_sched_data),
+
+ wrr_enqueue,
+ wrr_dequeue,
+ wrr_requeue,
+ wrr_drop,
+
+ wrr_init,
+ wrr_reset,
+ wrr_destroy,
+ wrr_tune,
+
+#if !defined(KERNEL22) || defined(CONFIG_RTNETLINK)
+ wrr_dump,
+#endif
+};
+
+#ifdef MODULE
+
+int init_module(void)
+{
+ return register_qdisc(&wrr_qdisc_ops);
+}
+
+void cleanup_module(void)
+{
+ unregister_qdisc(&wrr_qdisc_ops);
+}
+
+#ifndef KERNEL22
+ MODULE_LICENSE("GPL");
+#endif
+
+#endif
+
diff --git a/uClinux-2.4.31-uc0/net/sctp/Config.in b/uClinux-2.4.31-uc0/net/sctp/Config.in
new file mode 100644
index 0000000..8c8582f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/Config.in
@@ -0,0 +1,43 @@
+#
+# SCTP configuration
+#
+mainmenu_option next_comment
+comment ' SCTP Configuration (EXPERIMENTAL)'
+
+tristate ' The SCTP Protocol (EXPERIMENTAL)' CONFIG_IP_SCTP
+
+if [ "$CONFIG_IP_SCTP" = "y" -a "$CONFIG_IPV6" = "m" ]; then
+ define_tristate CONFIG_IP_SCTP m
+fi
+
+if [ "$CONFIG_IP_SCTP" != "n" ]; then
+ bool ' SCTP: Debug messages' CONFIG_SCTP_DBG_MSG
+ bool ' SCTP: Debug object counts' CONFIG_SCTP_DBG_OBJCNT
+ if [ "$CONFIG_CRYPTO_HMAC" = "n" ]; then
+ choice ' SCTP: Cookie HMAC Algorithm' \
+ "HMAC-NONE CONFIG_SCTP_HMAC_NONE" HMAC-NONE
+ else
+ if [ "$CONFIG_CRYPTO_MD5" = "n" -a "$CONFIG_CRYPTO_SHA1" = "n" ]; then
+ choice ' SCTP: Cookie HMAC Algorithm' \
+ "HMAC-NONE CONFIG_SCTP_HMAC_NONE" HMAC-NONE
+ else
+ if [ "$CONFIG_CRYPTO_MD5" != "n" -a "$CONFIG_CRYPTO_SHA1" != "n" ]; then
+ choice ' SCTP: Cookie HMAC Algorithm' \
+ "HMAC-NONE CONFIG_SCTP_HMAC_NONE \
+ HMAC-SHA1 CONFIG_SCTP_HMAC_SHA1 \
+ HMAC-MD5 CONFIG_SCTP_HMAC_MD5" HMAC-MD5
+ else
+ if [ "$CONFIG_CRYPTO_MD5" != "n" ]; then
+ choice 'SCTP: Cookie HMAC Algorithm' \
+ "HMAC-NONE CONFIG_SCTP_HMAC_NONE \
+ HMAC-MD5 CONFIG_SCTP_HMAC_MD5" HMAC-MD5
+ else
+ choice 'SCTP: Cookie HMAC Algorithm' \
+ "HMAC-NONE CONFIG_SCTP_HMAC_NONE \
+ HMAC-SHA1 CONFIG_SCTP_HMAC_SHA1" HMAC-SHA1
+ fi
+ fi
+ fi
+ fi
+fi
+endmenu
diff --git a/uClinux-2.4.31-uc0/net/sctp/Makefile b/uClinux-2.4.31-uc0/net/sctp/Makefile
new file mode 100644
index 0000000..b80ef95
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/Makefile
@@ -0,0 +1,22 @@
+#
+# Makefile for SCTP support code.
+#
+
+O_TARGET := sctp.o
+
+obj-$(CONFIG_IP_SCTP) += sctp.o
+
+obj-y := endpointola.o output.o sm_make_chunk.o associola.o \
+ outqueue.o sm_sideeffect.o transport.o bind_addr.o input.o primitive.o \
+ sm_statefuns.o tsnmap.o command.o inqueue.o proc.o sm_statetable.o \
+ ulpevent.o protocol.o socket.o ulpqueue.o debug.o ssnmap.o \
+ crc32c.o chunk.o
+
+obj-$(CONFIG_SCTP_DBG_OBJCNT) += objcnt.o
+obj-$(CONFIG_SYSCTL) += sysctl.o
+
+obj-$(subst m,y,$(CONFIG_IPV6)) += ipv6.o
+
+sctp-objs := $(obj-y)
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/sctp/adler32.c b/uClinux-2.4.31-uc0/net/sctp/adler32.c
new file mode 100644
index 0000000..63058b5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/adler32.c
@@ -0,0 +1,171 @@
+/* SCTP kernel reference Implementation
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2003 International Business Machines, Corp.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * This file has direct heritage from the SCTP user-level reference
+ * implementation by R. Stewart, et al. These functions implement the
+ * Adler-32 algorithm as specified by RFC 2960.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * Randall Stewart <rstewar1@email.mot.com>
+ * Ken Morneau <kmorneau@cisco.com>
+ * Qiaobing Xie <qxie1@email.mot.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+/* This is an entry point for external calls
+ * Define this function in the header file. This is
+ * direct from rfc1950, ...
+ *
+ * The following C code computes the Adler-32 checksum of a data buffer.
+ * It is written for clarity, not for speed. The sample code is in the
+ * ANSI C programming language. Non C users may find it easier to read
+ * with these hints:
+ *
+ * & Bitwise AND operator.
+ * >> Bitwise right shift operator. When applied to an
+ * unsigned quantity, as here, right shift inserts zero bit(s)
+ * at the left.
+ * << Bitwise left shift operator. Left shift inserts zero
+ * bit(s) at the right.
+ * ++ "n++" increments the variable n.
+ * % modulo operator: a % b is the remainder of a divided by b.
+ *
+ * Well, the above is a bit of a lie, I have optimized this a small
+ * tad, but I have commented the original lines below
+ */
+
+#include <linux/types.h>
+#include <net/sctp/sctp.h>
+
+#define BASE 65521 /* largest prime smaller than 65536 */
+
+
+/* Performance work as shown this pig to be the
+ * worst CPU wise guy. I have done what I could think
+ * of on my flight to Australia but I am sure some
+ * clever assembly could speed this up, but of
+ * course this would require the dreaded #ifdef's for
+ * architecture. If you can speed this up more, pass
+ * it back and we will incorporate it :-)
+ */
+
+unsigned long update_adler32(unsigned long adler,
+ unsigned char *buf, int len)
+{
+ __u32 s1 = adler & 0xffff;
+ __u32 s2 = (adler >> 16) & 0xffff;
+ int n;
+
+ for (n = 0; n < len; n++,buf++) {
+ /* s1 = (s1 + buf[n]) % BASE */
+ /* first we add */
+ s1 = (s1 + *buf);
+
+ /* Now if we need to, we do a mod by
+ * subtracting. It seems a bit faster
+ * since I really will only ever do
+ * one subtract at the MOST, since buf[n]
+ * is a max of 255.
+ */
+ if (s1 >= BASE)
+ s1 -= BASE;
+
+ /* s2 = (s2 + s1) % BASE */
+ /* first we add */
+ s2 = (s2 + s1);
+
+ /* again, it is more efficient (it seems) to
+ * subtract since the most s2 will ever be
+ * is (BASE-1 + BASE-1) in the worse case.
+ * This would then be (2 * BASE) - 2, which
+ * will still only do one subtract. On Intel
+ * this is much better to do this way and
+ * avoid the divide. Have not -pg'd on
+ * sparc.
+ */
+ if (s2 >= BASE) {
+ /* s2 %= BASE;*/
+ s2 -= BASE;
+ }
+ }
+
+ /* Return the adler32 of the bytes buf[0..len-1] */
+ return (s2 << 16) + s1;
+}
+
+__u32 sctp_start_cksum(__u8 *ptr, __u16 count)
+{
+ /*
+ * Update a running Adler-32 checksum with the bytes
+ * buf[0..len-1] and return the updated checksum. The Adler-32
+ * checksum should be initialized to 1.
+ */
+ __u32 adler = 1L;
+ __u32 zero = 0L;
+
+ /* Calculate the CRC up to the checksum field. */
+ adler = update_adler32(adler, ptr,
+ sizeof(struct sctphdr) - sizeof(__u32));
+ /* Skip over the checksum field. */
+ adler = update_adler32(adler, (unsigned char *) &zero,
+ sizeof(__u32));
+ ptr += sizeof(struct sctphdr);
+ count -= sizeof(struct sctphdr);
+
+ /* Calculate the rest of the Adler-32. */
+ adler = update_adler32(adler, ptr, count);
+
+ return adler;
+}
+
+__u32 sctp_update_cksum(__u8 *ptr, __u16 count, __u32 adler)
+{
+ adler = update_adler32(adler, ptr, count);
+
+ return adler;
+}
+
+__u32 sctp_update_copy_cksum(__u8 *to, __u8 *from, __u16 count, __u32 adler)
+{
+ /* Its not worth it to try harder. Adler32 is obsolescent. */
+ adler = update_adler32(adler, from, count);
+ memcpy(to, from, count);
+
+ return adler;
+}
+
+__u32 sctp_end_cksum(__u32 adler)
+{
+ return adler;
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/associola.c b/uClinux-2.4.31-uc0/net/sctp/associola.c
new file mode 100644
index 0000000..a563606
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/associola.c
@@ -0,0 +1,1220 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2001, 2004
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001 Intel Corp.
+ * Copyright (c) 2001 La Monte H.P. Yarroll
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * This module provides the abstraction for an SCTP association.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Xingang Guo <xingang.guo@intel.com>
+ * Hui Huang <hui.huang@nokia.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ * Daisy Chang <daisyc@us.ibm.com>
+ * Ryan Layer <rmlayer@us.ibm.com>
+ * Kevin Gao <kevin.gao@intel.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <linux/fcntl.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+
+#include <linux/slab.h>
+#include <linux/in.h>
+#include <net/ipv6.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+/* Forward declarations for internal functions. */
+static void sctp_assoc_bh_rcv(struct sctp_association *asoc);
+
+
+/* 1st Level Abstractions. */
+
+/* Initialize a new association from provided memory. */
+static struct sctp_association *sctp_association_init(struct sctp_association *asoc,
+ const struct sctp_endpoint *ep,
+ const struct sock *sk,
+ sctp_scope_t scope,
+ int gfp)
+{
+ struct sctp_opt *sp;
+ int i;
+
+ /* Retrieve the SCTP per socket area. */
+ sp = sctp_sk((struct sock *)sk);
+
+ /* Init all variables to a known value. */
+ memset(asoc, 0, sizeof(struct sctp_association));
+
+ /* Discarding const is appropriate here. */
+ asoc->ep = (struct sctp_endpoint *)ep;
+ sctp_endpoint_hold(asoc->ep);
+
+ /* Hold the sock. */
+ asoc->base.sk = (struct sock *)sk;
+ sock_hold(asoc->base.sk);
+
+ /* Initialize the common base substructure. */
+ asoc->base.type = SCTP_EP_TYPE_ASSOCIATION;
+
+ /* Initialize the object handling fields. */
+ atomic_set(&asoc->base.refcnt, 1);
+ asoc->base.dead = 0;
+ asoc->base.malloced = 0;
+
+ /* Initialize the bind addr area. */
+ sctp_bind_addr_init(&asoc->base.bind_addr, ep->base.bind_addr.port);
+ asoc->base.addr_lock = RW_LOCK_UNLOCKED;
+
+ asoc->state = SCTP_STATE_CLOSED;
+
+ /* Set these values from the socket values, a conversion between
+ * millsecons to seconds/microseconds must also be done.
+ */
+ asoc->cookie_life.tv_sec = sp->assocparams.sasoc_cookie_life / 1000;
+ asoc->cookie_life.tv_usec = (sp->assocparams.sasoc_cookie_life % 1000)
+ * 1000;
+ asoc->pmtu = 0;
+ asoc->frag_point = 0;
+
+ /* Set the association max_retrans and RTO values from the
+ * socket values.
+ */
+ asoc->max_retrans = sp->assocparams.sasoc_asocmaxrxt;
+ asoc->rto_initial = SCTP_MSECS_TO_JIFFIES(sp->rtoinfo.srto_initial);
+ asoc->rto_max = SCTP_MSECS_TO_JIFFIES(sp->rtoinfo.srto_max);
+ asoc->rto_min = SCTP_MSECS_TO_JIFFIES(sp->rtoinfo.srto_min);
+
+ asoc->overall_error_count = 0;
+
+ /* Initialize the maximum mumber of new data packets that can be sent
+ * in a burst.
+ */
+ asoc->max_burst = sctp_max_burst;
+
+ /* Copy things from the endpoint. */
+ for (i = SCTP_EVENT_TIMEOUT_NONE; i < SCTP_NUM_TIMEOUT_TYPES; ++i) {
+ asoc->timeouts[i] = ep->timeouts[i];
+ init_timer(&asoc->timers[i]);
+ asoc->timers[i].function = sctp_timer_events[i];
+ asoc->timers[i].data = (unsigned long) asoc;
+ }
+
+ /* Pull default initialization values from the sock options.
+ * Note: This assumes that the values have already been
+ * validated in the sock.
+ */
+ asoc->c.sinit_max_instreams = sp->initmsg.sinit_max_instreams;
+ asoc->c.sinit_num_ostreams = sp->initmsg.sinit_num_ostreams;
+ asoc->max_init_attempts = sp->initmsg.sinit_max_attempts;
+
+ asoc->max_init_timeo =
+ SCTP_MSECS_TO_JIFFIES(sp->initmsg.sinit_max_init_timeo);
+
+ /* Allocate storage for the ssnmap after the inbound and outbound
+ * streams have been negotiated during Init.
+ */
+ asoc->ssnmap = NULL;
+
+ /* Set the local window size for receive.
+ * This is also the rcvbuf space per association.
+ * RFC 6 - A SCTP receiver MUST be able to receive a minimum of
+ * 1500 bytes in one SCTP packet.
+ */
+ if (sk->rcvbuf < SCTP_DEFAULT_MINWINDOW)
+ asoc->rwnd = SCTP_DEFAULT_MINWINDOW;
+ else
+ asoc->rwnd = sk->rcvbuf;
+
+ asoc->a_rwnd = asoc->rwnd;
+
+ asoc->rwnd_over = 0;
+
+ /* Use my own max window until I learn something better. */
+ asoc->peer.rwnd = SCTP_DEFAULT_MAXWINDOW;
+
+ /* Set the sndbuf size for transmit. */
+ asoc->sndbuf_used = 0;
+
+ init_waitqueue_head(&asoc->wait);
+
+ asoc->c.my_vtag = sctp_generate_tag(ep);
+ asoc->peer.i.init_tag = 0; /* INIT needs a vtag of 0. */
+ asoc->c.peer_vtag = 0;
+ asoc->c.my_ttag = 0;
+ asoc->c.peer_ttag = 0;
+ asoc->c.my_port = ep->base.bind_addr.port;
+
+ asoc->c.initial_tsn = sctp_generate_tsn(ep);
+
+ asoc->next_tsn = asoc->c.initial_tsn;
+
+ asoc->ctsn_ack_point = asoc->next_tsn - 1;
+ asoc->adv_peer_ack_point = asoc->ctsn_ack_point;
+ asoc->highest_sacked = asoc->ctsn_ack_point;
+ asoc->last_cwr_tsn = asoc->ctsn_ack_point;
+ asoc->unack_data = 0;
+
+ SCTP_DEBUG_PRINTK("myctsnap for %s INIT as 0x%x.\n",
+ asoc->ep->debug_name,
+ asoc->ctsn_ack_point);
+
+ /* ADDIP Section 4.1 Asconf Chunk Procedures
+ *
+ * When an endpoint has an ASCONF signaled change to be sent to the
+ * remote endpoint it should do the following:
+ * ...
+ * A2) a serial number should be assigned to the chunk. The serial
+ * number SHOULD be a monotonically increasing number. The serial
+ * numbers SHOULD be initialized at the start of the
+ * association to the same value as the initial TSN.
+ */
+ asoc->addip_serial = asoc->c.initial_tsn;
+
+ skb_queue_head_init(&asoc->addip_chunks);
+
+ /* Make an empty list of remote transport addresses. */
+ INIT_LIST_HEAD(&asoc->peer.transport_addr_list);
+
+ /* RFC 2960 5.1 Normal Establishment of an Association
+ *
+ * After the reception of the first data chunk in an
+ * association the endpoint must immediately respond with a
+ * sack to acknowledge the data chunk. Subsequent
+ * acknowledgements should be done as described in Section
+ * 6.2.
+ *
+ * [We implement this by telling a new association that it
+ * already received one packet.]
+ */
+ asoc->peer.sack_needed = 1;
+
+ /* Assume that the peer recongizes ASCONF until reported otherwise
+ * via an ERROR chunk.
+ */
+ asoc->peer.asconf_capable = 1;
+
+ /* Create an input queue. */
+ sctp_inq_init(&asoc->base.inqueue);
+ sctp_inq_set_th_handler(&asoc->base.inqueue,
+ (void (*)(void *))sctp_assoc_bh_rcv,
+ asoc);
+
+ /* Create an output queue. */
+ sctp_outq_init(asoc, &asoc->outqueue);
+
+ if (!sctp_ulpq_init(&asoc->ulpq, asoc))
+ goto fail_init;
+
+ /* Set up the tsn tracking. */
+ sctp_tsnmap_init(&asoc->peer.tsn_map, SCTP_TSN_MAP_SIZE, 0);
+
+ asoc->need_ecne = 0;
+
+ asoc->eyecatcher = SCTP_ASSOC_EYECATCHER;
+
+ /* Assume that peer would support both address types unless we are
+ * told otherwise.
+ */
+ asoc->peer.ipv4_address = 1;
+ asoc->peer.ipv6_address = 1;
+ INIT_LIST_HEAD(&asoc->asocs);
+
+ asoc->autoclose = sp->autoclose;
+
+ asoc->default_stream = sp->default_stream;
+ asoc->default_ppid = sp->default_ppid;
+ asoc->default_flags = sp->default_flags;
+ asoc->default_context = sp->default_context;
+ asoc->default_timetolive = sp->default_timetolive;
+
+ return asoc;
+
+fail_init:
+ sctp_endpoint_put(asoc->ep);
+ sock_put(asoc->base.sk);
+ return NULL;
+}
+
+/* Allocate and initialize a new association */
+struct sctp_association *sctp_association_new(const struct sctp_endpoint *ep,
+ const struct sock *sk,
+ sctp_scope_t scope, int gfp)
+{
+ struct sctp_association *asoc;
+
+ asoc = t_new(struct sctp_association, gfp);
+ if (!asoc)
+ goto fail;
+
+ if (!sctp_association_init(asoc, ep, sk, scope, gfp))
+ goto fail_init;
+
+ asoc->base.malloced = 1;
+ SCTP_DBG_OBJCNT_INC(assoc);
+
+ return asoc;
+
+fail_init:
+ kfree(asoc);
+fail:
+ return NULL;
+}
+
+/* Free this association if possible. There may still be users, so
+ * the actual deallocation may be delayed.
+ */
+void sctp_association_free(struct sctp_association *asoc)
+{
+ struct sock *sk = asoc->base.sk;
+ struct sctp_transport *transport;
+ struct list_head *pos, *temp;
+ int i;
+
+ list_del(&asoc->asocs);
+
+ /* Decrement the backlog value for a TCP-style listening socket. */
+ if (sctp_style(sk, TCP) && sctp_sstate(sk, LISTENING))
+ sk->ack_backlog--;
+
+ /* Mark as dead, so other users can know this structure is
+ * going away.
+ */
+ asoc->base.dead = 1;
+
+ /* Dispose of any data lying around in the outqueue. */
+ sctp_outq_free(&asoc->outqueue);
+
+ /* Dispose of any pending messages for the upper layer. */
+ sctp_ulpq_free(&asoc->ulpq);
+
+ /* Dispose of any pending chunks on the inqueue. */
+ sctp_inq_free(&asoc->base.inqueue);
+
+ /* Free ssnmap storage. */
+ sctp_ssnmap_free(asoc->ssnmap);
+
+ /* Clean up the bound address list. */
+ sctp_bind_addr_free(&asoc->base.bind_addr);
+
+ /* Do we need to go through all of our timers and
+ * delete them? To be safe we will try to delete all, but we
+ * should be able to go through and make a guess based
+ * on our state.
+ */
+ for (i = SCTP_EVENT_TIMEOUT_NONE; i < SCTP_NUM_TIMEOUT_TYPES; ++i) {
+ if (timer_pending(&asoc->timers[i]) &&
+ del_timer(&asoc->timers[i]))
+ sctp_association_put(asoc);
+ }
+
+ /* Free peer's cached cookie. */
+ if (asoc->peer.cookie) {
+ kfree(asoc->peer.cookie);
+ }
+
+ /* Release the transport structures. */
+ list_for_each_safe(pos, temp, &asoc->peer.transport_addr_list) {
+ transport = list_entry(pos, struct sctp_transport, transports);
+ list_del(pos);
+ sctp_transport_free(transport);
+ }
+
+ asoc->eyecatcher = 0;
+
+ /* Free any cached ASCONF_ACK chunk. */
+ if (asoc->addip_last_asconf_ack)
+ sctp_chunk_free(asoc->addip_last_asconf_ack);
+
+ /* Free any cached ASCONF chunk. */
+ if (asoc->addip_last_asconf)
+ sctp_chunk_free(asoc->addip_last_asconf);
+
+ sctp_association_put(asoc);
+}
+
+/* Cleanup and free up an association. */
+static void sctp_association_destroy(struct sctp_association *asoc)
+{
+ SCTP_ASSERT(asoc->base.dead, "Assoc is not dead", return);
+
+ sctp_endpoint_put(asoc->ep);
+ sock_put(asoc->base.sk);
+
+ if (asoc->base.malloced) {
+ kfree(asoc);
+ SCTP_DBG_OBJCNT_DEC(assoc);
+ }
+}
+
+/* Change the primary destination address for the peer. */
+void sctp_assoc_set_primary(struct sctp_association *asoc,
+ struct sctp_transport *transport)
+{
+ asoc->peer.primary_path = transport;
+
+ /* Set a default msg_name for events. */
+ memcpy(&asoc->peer.primary_addr, &transport->ipaddr,
+ sizeof(union sctp_addr));
+
+ /* If the primary path is changing, assume that the
+ * user wants to use this new path.
+ */
+ if (transport->active)
+ asoc->peer.active_path = transport;
+
+ /*
+ * SFR-CACC algorithm:
+ * Upon the receipt of a request to change the primary
+ * destination address, on the data structure for the new
+ * primary destination, the sender MUST do the following:
+ *
+ * 1) If CHANGEOVER_ACTIVE is set, then there was a switch
+ * to this destination address earlier. The sender MUST set
+ * CYCLING_CHANGEOVER to indicate that this switch is a
+ * double switch to the same destination address.
+ */
+ if (transport->cacc.changeover_active)
+ transport->cacc.cycling_changeover = 1;
+
+ /* 2) The sender MUST set CHANGEOVER_ACTIVE to indicate that
+ * a changeover has occurred.
+ */
+ transport->cacc.changeover_active = 1;
+
+ /* 3) The sender MUST store the next TSN to be sent in
+ * next_tsn_at_change.
+ */
+ transport->cacc.next_tsn_at_change = asoc->next_tsn;
+}
+
+/* Add a transport address to an association. */
+struct sctp_transport *sctp_assoc_add_peer(struct sctp_association *asoc,
+ const union sctp_addr *addr,
+ int gfp)
+{
+ struct sctp_transport *peer;
+ struct sctp_opt *sp;
+ unsigned short port;
+
+ sp = sctp_sk(asoc->base.sk);
+
+ /* AF_INET and AF_INET6 share common port field. */
+ port = addr->v4.sin_port;
+
+ /* Set the port if it has not been set yet. */
+ if (0 == asoc->peer.port)
+ asoc->peer.port = port;
+
+ /* Check to see if this is a duplicate. */
+ peer = sctp_assoc_lookup_paddr(asoc, addr);
+ if (peer)
+ return peer;
+
+ peer = sctp_transport_new(addr, gfp);
+ if (!peer)
+ return NULL;
+
+ sctp_transport_set_owner(peer, asoc);
+
+ /* Initialize the pmtu of the transport. */
+ sctp_transport_pmtu(peer);
+
+ /* If this is the first transport addr on this association,
+ * initialize the association PMTU to the peer's PMTU.
+ * If not and the current association PMTU is higher than the new
+ * peer's PMTU, reset the association PMTU to the new peer's PMTU.
+ */
+ if (asoc->pmtu)
+ asoc->pmtu = min_t(int, peer->pmtu, asoc->pmtu);
+ else
+ asoc->pmtu = peer->pmtu;
+
+ SCTP_DEBUG_PRINTK("sctp_assoc_add_peer:association %p PMTU set to "
+ "%d\n", asoc, asoc->pmtu);
+
+ asoc->frag_point = sctp_frag_point(sp, asoc->pmtu);
+
+ /* The asoc->peer.port might not be meaningful yet, but
+ * initialize the packet structure anyway.
+ */
+ sctp_packet_init(&peer->packet, peer, asoc->base.bind_addr.port,
+ asoc->peer.port);
+
+ /* 7.2.1 Slow-Start
+ *
+ * o The initial cwnd before DATA transmission or after a sufficiently
+ * long idle period MUST be set to
+ * min(4*MTU, max(2*MTU, 4380 bytes))
+ *
+ * o The initial value of ssthresh MAY be arbitrarily high
+ * (for example, implementations MAY use the size of the
+ * receiver advertised window).
+ */
+ peer->cwnd = min(4*asoc->pmtu, max_t(__u32, 2*asoc->pmtu, 4380));
+
+ /* At this point, we may not have the receiver's advertised window,
+ * so initialize ssthresh to the default value and it will be set
+ * later when we process the INIT.
+ */
+ peer->ssthresh = SCTP_DEFAULT_MAXWINDOW;
+
+ peer->partial_bytes_acked = 0;
+ peer->flight_size = 0;
+
+ /* By default, enable heartbeat for peer address. */
+ peer->hb_allowed = 1;
+
+ /* Initialize the peer's heartbeat interval based on the
+ * sock configured value.
+ */
+ peer->hb_interval = SCTP_MSECS_TO_JIFFIES(sp->paddrparam.spp_hbinterval);
+
+ /* Set the path max_retrans. */
+ peer->max_retrans = sp->paddrparam.spp_pathmaxrxt;
+
+ /* Set the transport's RTO.initial value */
+ peer->rto = asoc->rto_initial;
+
+ /* Attach the remote transport to our asoc. */
+ list_add_tail(&peer->transports, &asoc->peer.transport_addr_list);
+
+ /* If we do not yet have a primary path, set one. */
+ if (!asoc->peer.primary_path) {
+ sctp_assoc_set_primary(asoc, peer);
+ asoc->peer.retran_path = peer;
+ }
+
+ if (asoc->peer.active_path == asoc->peer.retran_path)
+ asoc->peer.retran_path = peer;
+
+ return peer;
+}
+
+/* Delete a transport address from an association. */
+void sctp_assoc_del_peer(struct sctp_association *asoc,
+ const union sctp_addr *addr)
+{
+ struct list_head *pos;
+ struct list_head *temp;
+ struct sctp_transport *peer = NULL;
+ struct sctp_transport *transport;
+
+ list_for_each_safe(pos, temp, &asoc->peer.transport_addr_list) {
+ transport = list_entry(pos, struct sctp_transport, transports);
+ if (sctp_cmp_addr_exact(addr, &transport->ipaddr)) {
+ peer = transport;
+ list_del(pos);
+ break;
+ }
+ }
+
+ /* The address we want delete is not in the association. */
+ if (!peer)
+ return;
+
+ /* Get the first transport of asoc. */
+ pos = asoc->peer.transport_addr_list.next;
+ transport = list_entry(pos, struct sctp_transport, transports);
+
+ /* Update any entries that match the peer to be deleted. */
+ if (asoc->peer.primary_path == peer)
+ sctp_assoc_set_primary(asoc, transport);
+ if (asoc->peer.active_path == peer)
+ asoc->peer.active_path = transport;
+ if (asoc->peer.retran_path == peer)
+ asoc->peer.retran_path = transport;
+ if (asoc->peer.last_data_from == peer)
+ asoc->peer.last_data_from = transport;
+
+ sctp_transport_free(peer);
+}
+
+/* Lookup a transport by address. */
+struct sctp_transport *sctp_assoc_lookup_paddr(
+ const struct sctp_association *asoc,
+ const union sctp_addr *address)
+{
+ struct sctp_transport *t;
+ struct list_head *pos;
+
+ /* Cycle through all transports searching for a peer address. */
+
+ list_for_each(pos, &asoc->peer.transport_addr_list) {
+ t = list_entry(pos, struct sctp_transport, transports);
+ if (sctp_cmp_addr_exact(address, &t->ipaddr))
+ return t;
+ }
+
+ return NULL;
+}
+
+/* Engage in transport control operations.
+ * Mark the transport up or down and send a notification to the user.
+ * Select and update the new active and retran paths.
+ */
+void sctp_assoc_control_transport(struct sctp_association *asoc,
+ struct sctp_transport *transport,
+ sctp_transport_cmd_t command,
+ sctp_sn_error_t error)
+{
+ struct sctp_transport *t = NULL;
+ struct sctp_transport *first;
+ struct sctp_transport *second;
+ struct sctp_ulpevent *event;
+ struct list_head *pos;
+ int spc_state = 0;
+
+ /* Record the transition on the transport. */
+ switch (command) {
+ case SCTP_TRANSPORT_UP:
+ transport->active = SCTP_ACTIVE;
+ spc_state = SCTP_ADDR_AVAILABLE;
+ break;
+
+ case SCTP_TRANSPORT_DOWN:
+ transport->active = SCTP_INACTIVE;
+ spc_state = SCTP_ADDR_UNREACHABLE;
+ break;
+
+ default:
+ return;
+ };
+
+ /* Generate and send a SCTP_PEER_ADDR_CHANGE notification to the
+ * user.
+ */
+ event = sctp_ulpevent_make_peer_addr_change(asoc,
+ (struct sockaddr_storage *) &transport->ipaddr,
+ 0, spc_state, error, GFP_ATOMIC);
+ if (event)
+ sctp_ulpq_tail_event(&asoc->ulpq, event);
+
+ /* Select new active and retran paths. */
+
+ /* Look for the two most recently used active transports.
+ *
+ * This code produces the wrong ordering whenever jiffies
+ * rolls over, but we still get usable transports, so we don't
+ * worry about it.
+ */
+ first = NULL; second = NULL;
+
+ list_for_each(pos, &asoc->peer.transport_addr_list) {
+ t = list_entry(pos, struct sctp_transport, transports);
+
+ if (!t->active)
+ continue;
+ if (!first || t->last_time_heard > first->last_time_heard) {
+ second = first;
+ first = t;
+ }
+ if (!second || t->last_time_heard > second->last_time_heard)
+ second = t;
+ }
+
+ /* RFC 2960 6.4 Multi-Homed SCTP Endpoints
+ *
+ * By default, an endpoint should always transmit to the
+ * primary path, unless the SCTP user explicitly specifies the
+ * destination transport address (and possibly source
+ * transport address) to use.
+ *
+ * [If the primary is active but not most recent, bump the most
+ * recently used transport.]
+ */
+ if (asoc->peer.primary_path->active &&
+ first != asoc->peer.primary_path) {
+ second = first;
+ first = asoc->peer.primary_path;
+ }
+
+ /* If we failed to find a usable transport, just camp on the
+ * primary, even if it is inactive.
+ */
+ if (!first) {
+ first = asoc->peer.primary_path;
+ second = asoc->peer.primary_path;
+ }
+
+ /* Set the active and retran transports. */
+ asoc->peer.active_path = first;
+ asoc->peer.retran_path = second;
+}
+
+/* Hold a reference to an association. */
+void sctp_association_hold(struct sctp_association *asoc)
+{
+ atomic_inc(&asoc->base.refcnt);
+}
+
+/* Release a reference to an association and cleanup
+ * if there are no more references.
+ */
+void sctp_association_put(struct sctp_association *asoc)
+{
+ if (atomic_dec_and_test(&asoc->base.refcnt))
+ sctp_association_destroy(asoc);
+}
+
+/* Allocate the next TSN, Transmission Sequence Number, for the given
+ * association.
+ */
+__u32 sctp_association_get_next_tsn(struct sctp_association *asoc)
+{
+ /* From Section 1.6 Serial Number Arithmetic:
+ * Transmission Sequence Numbers wrap around when they reach
+ * 2**32 - 1. That is, the next TSN a DATA chunk MUST use
+ * after transmitting TSN = 2*32 - 1 is TSN = 0.
+ */
+ __u32 retval = asoc->next_tsn;
+ asoc->next_tsn++;
+ asoc->unack_data++;
+
+ return retval;
+}
+
+/* Compare two addresses to see if they match. Wildcard addresses
+ * only match themselves.
+ */
+int sctp_cmp_addr_exact(const union sctp_addr *ss1,
+ const union sctp_addr *ss2)
+{
+ struct sctp_af *af;
+
+ af = sctp_get_af_specific(ss1->sa.sa_family);
+ if (unlikely(!af))
+ return 0;
+
+ return af->cmp_addr(ss1, ss2);
+}
+
+/* Return an ecne chunk to get prepended to a packet.
+ * Note: We are sly and return a shared, prealloced chunk. FIXME:
+ * No we don't, but we could/should.
+ */
+struct sctp_chunk *sctp_get_ecne_prepend(struct sctp_association *asoc)
+{
+ struct sctp_chunk *chunk;
+
+ /* Send ECNE if needed.
+ * Not being able to allocate a chunk here is not deadly.
+ */
+ if (asoc->need_ecne)
+ chunk = sctp_make_ecne(asoc, asoc->last_ecne_tsn);
+ else
+ chunk = NULL;
+
+ return chunk;
+}
+
+/*
+ * Find which transport this TSN was sent on.
+ */
+struct sctp_transport *sctp_assoc_lookup_tsn(struct sctp_association *asoc,
+ __u32 tsn)
+{
+ struct sctp_transport *active;
+ struct sctp_transport *match;
+ struct list_head *entry, *pos;
+ struct sctp_transport *transport;
+ struct sctp_chunk *chunk;
+ __u32 key = htonl(tsn);
+
+ match = NULL;
+
+ /*
+ * FIXME: In general, find a more efficient data structure for
+ * searching.
+ */
+
+ /*
+ * The general strategy is to search each transport's transmitted
+ * list. Return which transport this TSN lives on.
+ *
+ * Let's be hopeful and check the active_path first.
+ * Another optimization would be to know if there is only one
+ * outbound path and not have to look for the TSN at all.
+ *
+ */
+
+ active = asoc->peer.active_path;
+
+ list_for_each(entry, &active->transmitted) {
+ chunk = list_entry(entry, struct sctp_chunk, transmitted_list);
+
+ if (key == chunk->subh.data_hdr->tsn) {
+ match = active;
+ goto out;
+ }
+ }
+
+ /* If not found, go search all the other transports. */
+ list_for_each(pos, &asoc->peer.transport_addr_list) {
+ transport = list_entry(pos, struct sctp_transport, transports);
+
+ if (transport == active)
+ break;
+ list_for_each(entry, &transport->transmitted) {
+ chunk = list_entry(entry, struct sctp_chunk,
+ transmitted_list);
+ if (key == chunk->subh.data_hdr->tsn) {
+ match = transport;
+ goto out;
+ }
+ }
+ }
+out:
+ return match;
+}
+
+/* Is this the association we are looking for? */
+struct sctp_transport *sctp_assoc_is_match(struct sctp_association *asoc,
+ const union sctp_addr *laddr,
+ const union sctp_addr *paddr)
+{
+ struct sctp_transport *transport;
+
+ sctp_read_lock(&asoc->base.addr_lock);
+
+ if ((asoc->base.bind_addr.port == laddr->v4.sin_port) &&
+ (asoc->peer.port == paddr->v4.sin_port)) {
+ transport = sctp_assoc_lookup_paddr(asoc, paddr);
+ if (!transport)
+ goto out;
+
+ if (sctp_bind_addr_match(&asoc->base.bind_addr, laddr,
+ sctp_sk(asoc->base.sk)))
+ goto out;
+ }
+ transport = NULL;
+
+out:
+ sctp_read_unlock(&asoc->base.addr_lock);
+ return transport;
+}
+
+/* Is this a live association structure. */
+int sctp_assoc_valid(struct sock *sk, struct sctp_association *asoc)
+{
+
+ /* First, verify that this is a kernel address. */
+ if (!sctp_is_valid_kaddr((unsigned long) asoc))
+ return 0;
+
+ /* Verify that this _is_ an sctp_association
+ * data structure and if so, that the socket matches.
+ */
+ if (SCTP_ASSOC_EYECATCHER != asoc->eyecatcher)
+ return 0;
+ if (asoc->base.sk != sk)
+ return 0;
+
+ /* The association is valid. */
+ return 1;
+}
+
+/* Do delayed input processing. This is scheduled by sctp_rcv(). */
+static void sctp_assoc_bh_rcv(struct sctp_association *asoc)
+{
+ struct sctp_endpoint *ep;
+ struct sctp_chunk *chunk;
+ struct sock *sk;
+ struct sctp_inq *inqueue;
+ int state;
+ sctp_subtype_t subtype;
+ int error = 0;
+
+ /* The association should be held so we should be safe. */
+ ep = asoc->ep;
+ sk = asoc->base.sk;
+
+ inqueue = &asoc->base.inqueue;
+ while (NULL != (chunk = sctp_inq_pop(inqueue))) {
+ state = asoc->state;
+ subtype = SCTP_ST_CHUNK(chunk->chunk_hdr->type);
+
+ /* Remember where the last DATA chunk came from so we
+ * know where to send the SACK.
+ */
+ if (sctp_chunk_is_data(chunk))
+ asoc->peer.last_data_from = chunk->transport;
+ else
+ SCTP_INC_STATS(SctpInCtrlChunks);
+
+ if (chunk->transport)
+ chunk->transport->last_time_heard = jiffies;
+
+ /* Run through the state machine. */
+ error = sctp_do_sm(SCTP_EVENT_T_CHUNK, subtype,
+ state, ep, asoc, chunk, GFP_ATOMIC);
+
+ /* Check to see if the association is freed in response to
+ * the incoming chunk. If so, get out of the while loop.
+ */
+ if (!sctp_assoc_valid(sk, asoc))
+ break;
+
+ /* If there is an error on chunk, discard this packet. */
+ if (error && chunk)
+ chunk->pdiscard = 1;
+ }
+
+}
+
+/* This routine moves an association from its old sk to a new sk. */
+void sctp_assoc_migrate(struct sctp_association *assoc, struct sock *newsk)
+{
+ struct sctp_opt *newsp = sctp_sk(newsk);
+ struct sock *oldsk = assoc->base.sk;
+
+ /* Delete the association from the old endpoint's list of
+ * associations.
+ */
+ list_del_init(&assoc->asocs);
+
+ /* Decrement the backlog value for a TCP-style socket. */
+ if (sctp_style(oldsk, TCP))
+ oldsk->ack_backlog--;
+
+ /* Release references to the old endpoint and the sock. */
+ sctp_endpoint_put(assoc->ep);
+ sock_put(assoc->base.sk);
+
+ /* Get a reference to the new endpoint. */
+ assoc->ep = newsp->ep;
+ sctp_endpoint_hold(assoc->ep);
+
+ /* Get a reference to the new sock. */
+ assoc->base.sk = newsk;
+ sock_hold(assoc->base.sk);
+
+ /* Add the association to the new endpoint's list of associations. */
+ sctp_endpoint_add_asoc(newsp->ep, assoc);
+}
+
+/* Update an association (possibly from unexpected COOKIE-ECHO processing). */
+void sctp_assoc_update(struct sctp_association *asoc,
+ struct sctp_association *new)
+{
+ struct sctp_transport *trans;
+ struct list_head *pos, *temp;
+
+ /* Copy in new parameters of peer. */
+ asoc->c = new->c;
+ asoc->peer.rwnd = new->peer.rwnd;
+ asoc->peer.sack_needed = new->peer.sack_needed;
+ asoc->peer.i = new->peer.i;
+ sctp_tsnmap_init(&asoc->peer.tsn_map, SCTP_TSN_MAP_SIZE,
+ asoc->peer.i.initial_tsn);
+
+ /* Remove any peer addresses not present in the new association. */
+ list_for_each_safe(pos, temp, &asoc->peer.transport_addr_list) {
+ trans = list_entry(pos, struct sctp_transport, transports);
+ if (!sctp_assoc_lookup_paddr(new, &trans->ipaddr))
+ sctp_assoc_del_peer(asoc, &trans->ipaddr);
+ }
+
+ /* If the case is A (association restart), use
+ * initial_tsn as next_tsn. If the case is B, use
+ * current next_tsn in case data sent to peer
+ * has been discarded and needs retransmission.
+ */
+ if (asoc->state >= SCTP_STATE_ESTABLISHED) {
+ asoc->next_tsn = new->next_tsn;
+ asoc->ctsn_ack_point = new->ctsn_ack_point;
+ asoc->adv_peer_ack_point = new->adv_peer_ack_point;
+
+ /* Reinitialize SSN for both local streams
+ * and peer's streams.
+ */
+ sctp_ssnmap_clear(asoc->ssnmap);
+
+ } else {
+ /* Add any peer addresses from the new association. */
+ list_for_each(pos, &new->peer.transport_addr_list) {
+ trans = list_entry(pos, struct sctp_transport,
+ transports);
+ if (!sctp_assoc_lookup_paddr(asoc, &trans->ipaddr))
+ sctp_assoc_add_peer(asoc, &trans->ipaddr,
+ GFP_ATOMIC);
+ }
+
+ asoc->ctsn_ack_point = asoc->next_tsn - 1;
+ asoc->adv_peer_ack_point = asoc->ctsn_ack_point;
+ if (!asoc->ssnmap) {
+ /* Move the ssnmap. */
+ asoc->ssnmap = new->ssnmap;
+ new->ssnmap = NULL;
+ }
+ }
+}
+
+/* Update the retran path for sending a retransmitted packet.
+ * Round-robin through the active transports, else round-robin
+ * through the inactive transports as this is the next best thing
+ * we can try.
+ */
+void sctp_assoc_update_retran_path(struct sctp_association *asoc)
+{
+ struct sctp_transport *t, *next;
+ struct list_head *head = &asoc->peer.transport_addr_list;
+ struct list_head *pos;
+
+ /* Find the next transport in a round-robin fashion. */
+ t = asoc->peer.retran_path;
+ pos = &t->transports;
+ next = NULL;
+
+ while (1) {
+ /* Skip the head. */
+ if (pos->next == head)
+ pos = head->next;
+ else
+ pos = pos->next;
+
+ t = list_entry(pos, struct sctp_transport, transports);
+
+ /* Try to find an active transport. */
+
+ if (t->active) {
+ break;
+ } else {
+ /* Keep track of the next transport in case
+ * we don't find any active transport.
+ */
+ if (!next)
+ next = t;
+ }
+
+ /* We have exhausted the list, but didn't find any
+ * other active transports. If so, use the next
+ * transport.
+ */
+ if (t == asoc->peer.retran_path) {
+ t = next;
+ break;
+ }
+ }
+
+ asoc->peer.retran_path = t;
+}
+
+/* Choose the transport for sending a SHUTDOWN packet. */
+struct sctp_transport *sctp_assoc_choose_shutdown_transport(
+ struct sctp_association *asoc)
+{
+ /* If this is the first time SHUTDOWN is sent, use the active path,
+ * else use the retran path. If the last SHUTDOWN was sent over the
+ * retran path, update the retran path and use it.
+ */
+ if (!asoc->shutdown_last_sent_to)
+ return asoc->peer.active_path;
+ else {
+ if (asoc->shutdown_last_sent_to == asoc->peer.retran_path)
+ sctp_assoc_update_retran_path(asoc);
+ return asoc->peer.retran_path;
+ }
+
+}
+
+/* Update the association's pmtu and frag_point by going through all the
+ * transports. This routine is called when a transport's PMTU has changed.
+ */
+void sctp_assoc_sync_pmtu(struct sctp_association *asoc)
+{
+ struct sctp_transport *t;
+ struct list_head *pos;
+ __u32 pmtu = 0;
+
+ if (!asoc)
+ return;
+
+ /* Get the lowest pmtu of all the transports. */
+ list_for_each(pos, &asoc->peer.transport_addr_list) {
+ t = list_entry(pos, struct sctp_transport, transports);
+ if (!pmtu || (t->pmtu < pmtu))
+ pmtu = t->pmtu;
+ }
+
+ if (pmtu) {
+ struct sctp_opt *sp = sctp_sk(asoc->base.sk);
+ asoc->pmtu = pmtu;
+ asoc->frag_point = sctp_frag_point(sp, pmtu);
+ }
+
+ SCTP_DEBUG_PRINTK("%s: asoc:%p, pmtu:%d, frag_point:%d\n",
+ __FUNCTION__, asoc, asoc->pmtu, asoc->frag_point);
+}
+
+/* Should we send a SACK to update our peer? */
+static inline int sctp_peer_needs_update(struct sctp_association *asoc)
+{
+ switch (asoc->state) {
+ case SCTP_STATE_ESTABLISHED:
+ case SCTP_STATE_SHUTDOWN_PENDING:
+ case SCTP_STATE_SHUTDOWN_RECEIVED:
+ case SCTP_STATE_SHUTDOWN_SENT:
+ if ((asoc->rwnd > asoc->a_rwnd) &&
+ ((asoc->rwnd - asoc->a_rwnd) >=
+ min_t(__u32, (asoc->base.sk->rcvbuf >> 1), asoc->pmtu)))
+ return 1;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/* Increase asoc's rwnd by len and send any window update SACK if needed. */
+void sctp_assoc_rwnd_increase(struct sctp_association *asoc, unsigned len)
+{
+ struct sctp_chunk *sack;
+ struct timer_list *timer;
+
+ if (asoc->rwnd_over) {
+ if (asoc->rwnd_over >= len) {
+ asoc->rwnd_over -= len;
+ } else {
+ asoc->rwnd += (len - asoc->rwnd_over);
+ asoc->rwnd_over = 0;
+ }
+ } else {
+ asoc->rwnd += len;
+ }
+
+ SCTP_DEBUG_PRINTK("%s: asoc %p rwnd increased by %d to (%u, %u) "
+ "- %u\n", __FUNCTION__, asoc, len, asoc->rwnd,
+ asoc->rwnd_over, asoc->a_rwnd);
+
+ /* Send a window update SACK if the rwnd has increased by at least the
+ * minimum of the association's PMTU and half of the receive buffer.
+ * The algorithm used is similar to the one described in
+ * Section 4.2.3.3 of RFC 1122.
+ */
+ if (sctp_peer_needs_update(asoc)) {
+ asoc->a_rwnd = asoc->rwnd;
+ SCTP_DEBUG_PRINTK("%s: Sending window update SACK- asoc: %p "
+ "rwnd: %u a_rwnd: %u\n", __FUNCTION__,
+ asoc, asoc->rwnd, asoc->a_rwnd);
+ sack = sctp_make_sack(asoc);
+ if (!sack)
+ return;
+
+ asoc->peer.sack_needed = 0;
+
+ sctp_outq_tail(&asoc->outqueue, sack);
+
+ /* Stop the SACK timer. */
+ timer = &asoc->timers[SCTP_EVENT_TIMEOUT_SACK];
+ if (timer_pending(timer) && del_timer(timer))
+ sctp_association_put(asoc);
+ }
+}
+
+/* Decrease asoc's rwnd by len. */
+void sctp_assoc_rwnd_decrease(struct sctp_association *asoc, unsigned len)
+{
+ SCTP_ASSERT(asoc->rwnd, "rwnd zero", return);
+ SCTP_ASSERT(!asoc->rwnd_over, "rwnd_over not zero", return);
+ if (asoc->rwnd >= len) {
+ asoc->rwnd -= len;
+ } else {
+ asoc->rwnd_over = len - asoc->rwnd;
+ asoc->rwnd = 0;
+ }
+ SCTP_DEBUG_PRINTK("%s: asoc %p rwnd decreased by %d to (%u, %u)\n",
+ __FUNCTION__, asoc, len, asoc->rwnd,
+ asoc->rwnd_over);
+}
+
+/* Build the bind address list for the association based on info from the
+ * local endpoint and the remote peer.
+ */
+int sctp_assoc_set_bind_addr_from_ep(struct sctp_association *asoc, int gfp)
+{
+ sctp_scope_t scope;
+ int flags;
+
+ /* Use scoping rules to determine the subset of addresses from
+ * the endpoint.
+ */
+ scope = sctp_scope(&asoc->peer.active_path->ipaddr);
+ flags = (PF_INET6 == asoc->base.sk->family) ? SCTP_ADDR6_ALLOWED : 0;
+ if (asoc->peer.ipv4_address)
+ flags |= SCTP_ADDR4_PEERSUPP;
+ if (asoc->peer.ipv6_address)
+ flags |= SCTP_ADDR6_PEERSUPP;
+
+ return sctp_bind_addr_copy(&asoc->base.bind_addr,
+ &asoc->ep->base.bind_addr,
+ scope, gfp, flags);
+}
+
+/* Build the association's bind address list from the cookie. */
+int sctp_assoc_set_bind_addr_from_cookie(struct sctp_association *asoc,
+ struct sctp_cookie *cookie, int gfp)
+{
+ int var_size2 = ntohs(cookie->peer_init->chunk_hdr.length);
+ int var_size3 = cookie->raw_addr_list_len;
+ __u8 *raw = (__u8 *)cookie->peer_init + var_size2;
+
+ return sctp_raw_to_bind_addrs(&asoc->base.bind_addr, raw, var_size3,
+ asoc->ep->base.bind_addr.port, gfp);
+}
+
+/* Lookup laddr in the bind address list of an association. */
+int sctp_assoc_lookup_laddr(struct sctp_association *asoc,
+ const union sctp_addr *laddr)
+{
+ int found;
+
+ sctp_read_lock(&asoc->base.addr_lock);
+ if ((asoc->base.bind_addr.port == ntohs(laddr->v4.sin_port)) &&
+ sctp_bind_addr_match(&asoc->base.bind_addr, laddr,
+ sctp_sk(asoc->base.sk))) {
+ found = 1;
+ goto out;
+ }
+
+ found = 0;
+out:
+ sctp_read_unlock(&asoc->base.addr_lock);
+ return found;
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/bind_addr.c b/uClinux-2.4.31-uc0/net/sctp/bind_addr.c
new file mode 100644
index 0000000..61787e5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/bind_addr.c
@@ -0,0 +1,417 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2001, 2003
+ * Copyright (c) Cisco 1999,2000
+ * Copyright (c) Motorola 1999,2000,2001
+ * Copyright (c) La Monte H.P. Yarroll 2001
+ *
+ * This file is part of the SCTP kernel reference implementation.
+ *
+ * A collection class to handle the storage of transport addresses.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Daisy Chang <daisyc@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/in.h>
+#include <net/sock.h>
+#include <net/ipv6.h>
+#include <net/if_inet6.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+/* Forward declarations for internal helpers. */
+static int sctp_copy_one_addr(struct sctp_bind_addr *, union sctp_addr *,
+ sctp_scope_t scope, int gfp, int flags);
+static void sctp_bind_addr_clean(struct sctp_bind_addr *);
+
+/* First Level Abstractions. */
+
+/* Copy 'src' to 'dest' taking 'scope' into account. Omit addresses
+ * in 'src' which have a broader scope than 'scope'.
+ */
+int sctp_bind_addr_copy(struct sctp_bind_addr *dest,
+ const struct sctp_bind_addr *src,
+ sctp_scope_t scope, int gfp, int flags)
+{
+ struct sctp_sockaddr_entry *addr;
+ struct list_head *pos;
+ int error = 0;
+
+ /* All addresses share the same port. */
+ dest->port = src->port;
+
+ /* Extract the addresses which are relevant for this scope. */
+ list_for_each(pos, &src->address_list) {
+ addr = list_entry(pos, struct sctp_sockaddr_entry, list);
+ error = sctp_copy_one_addr(dest, &addr->a, scope,
+ gfp, flags);
+ if (error < 0)
+ goto out;
+ }
+
+ /* If there are no addresses matching the scope and
+ * this is global scope, try to get a link scope address, with
+ * the assumption that we must be sitting behind a NAT.
+ */
+ if (list_empty(&dest->address_list) && (SCTP_SCOPE_GLOBAL == scope)) {
+ list_for_each(pos, &src->address_list) {
+ addr = list_entry(pos, struct sctp_sockaddr_entry,
+ list);
+ error = sctp_copy_one_addr(dest, &addr->a,
+ SCTP_SCOPE_LINK, gfp,
+ flags);
+ if (error < 0)
+ goto out;
+ }
+ }
+
+out:
+ if (error)
+ sctp_bind_addr_clean(dest);
+
+ return error;
+}
+
+/* Initialize the SCTP_bind_addr structure for either an endpoint or
+ * an association.
+ */
+void sctp_bind_addr_init(struct sctp_bind_addr *bp, __u16 port)
+{
+ bp->malloced = 0;
+
+ INIT_LIST_HEAD(&bp->address_list);
+ bp->port = port;
+}
+
+/* Dispose of the address list. */
+static void sctp_bind_addr_clean(struct sctp_bind_addr *bp)
+{
+ struct sctp_sockaddr_entry *addr;
+ struct list_head *pos, *temp;
+
+ /* Empty the bind address list. */
+ list_for_each_safe(pos, temp, &bp->address_list) {
+ addr = list_entry(pos, struct sctp_sockaddr_entry, list);
+ list_del(pos);
+ kfree(addr);
+ SCTP_DBG_OBJCNT_DEC(addr);
+ }
+}
+
+/* Dispose of an SCTP_bind_addr structure */
+void sctp_bind_addr_free(struct sctp_bind_addr *bp)
+{
+ /* Empty the bind address list. */
+ sctp_bind_addr_clean(bp);
+
+ if (bp->malloced) {
+ kfree(bp);
+ SCTP_DBG_OBJCNT_DEC(bind_addr);
+ }
+}
+
+/* Add an address to the bind address list in the SCTP_bind_addr structure. */
+int sctp_add_bind_addr(struct sctp_bind_addr *bp, union sctp_addr *new,
+ int gfp)
+{
+ struct sctp_sockaddr_entry *addr;
+
+ /* Add the address to the bind address list. */
+ addr = t_new(struct sctp_sockaddr_entry, gfp);
+ if (!addr)
+ return -ENOMEM;
+
+ memcpy(&addr->a, new, sizeof(*new));
+
+ /* Fix up the port if it has not yet been set.
+ * Both v4 and v6 have the port at the same offset.
+ */
+ if (!addr->a.v4.sin_port)
+ addr->a.v4.sin_port = bp->port;
+
+ INIT_LIST_HEAD(&addr->list);
+ list_add_tail(&addr->list, &bp->address_list);
+ SCTP_DBG_OBJCNT_INC(addr);
+
+ return 0;
+}
+
+/* Delete an address from the bind address list in the SCTP_bind_addr
+ * structure.
+ */
+int sctp_del_bind_addr(struct sctp_bind_addr *bp, union sctp_addr *del_addr)
+{
+ struct list_head *pos, *temp;
+ struct sctp_sockaddr_entry *addr;
+
+ list_for_each_safe(pos, temp, &bp->address_list) {
+ addr = list_entry(pos, struct sctp_sockaddr_entry, list);
+ if (sctp_cmp_addr_exact(&addr->a, del_addr)) {
+ /* Found the exact match. */
+ list_del(pos);
+ kfree(addr);
+ SCTP_DBG_OBJCNT_DEC(addr);
+
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/* Create a network byte-order representation of all the addresses
+ * formated as SCTP parameters.
+ *
+ * The second argument is the return value for the length.
+ */
+union sctp_params sctp_bind_addrs_to_raw(const struct sctp_bind_addr *bp,
+ int *addrs_len, int gfp)
+{
+ union sctp_params addrparms;
+ union sctp_params retval;
+ int addrparms_len;
+ union sctp_addr_param rawaddr;
+ int len;
+ struct sctp_sockaddr_entry *addr;
+ struct list_head *pos;
+ struct sctp_af *af;
+
+ addrparms_len = 0;
+ len = 0;
+
+ /* Allocate enough memory at once. */
+ list_for_each(pos, &bp->address_list) {
+ len += sizeof(union sctp_addr_param);
+ }
+
+ /* Don't even bother embedding an address if there
+ * is only one.
+ */
+ if (len == sizeof(union sctp_addr_param)) {
+ retval.v = NULL;
+ goto end_raw;
+ }
+
+ retval.v = kmalloc(len, gfp);
+ if (!retval.v)
+ goto end_raw;
+
+ addrparms = retval;
+
+ list_for_each(pos, &bp->address_list) {
+ addr = list_entry(pos, struct sctp_sockaddr_entry, list);
+ af = sctp_get_af_specific(addr->a.v4.sin_family);
+ len = af->to_addr_param(&addr->a, &rawaddr);
+ memcpy(addrparms.v, &rawaddr, len);
+ addrparms.v += len;
+ addrparms_len += len;
+ }
+
+end_raw:
+ *addrs_len = addrparms_len;
+ return retval;
+}
+
+/*
+ * Create an address list out of the raw address list format (IPv4 and IPv6
+ * address parameters).
+ */
+int sctp_raw_to_bind_addrs(struct sctp_bind_addr *bp, __u8 *raw_addr_list,
+ int addrs_len, __u16 port, int gfp)
+{
+ union sctp_addr_param *rawaddr;
+ struct sctp_paramhdr *param;
+ union sctp_addr addr;
+ int retval = 0;
+ int len;
+ struct sctp_af *af;
+
+ /* Convert the raw address to standard address format */
+ while (addrs_len) {
+ param = (struct sctp_paramhdr *)raw_addr_list;
+ rawaddr = (union sctp_addr_param *)raw_addr_list;
+
+ af = sctp_get_af_specific(param_type2af(param->type));
+ if (unlikely(!af)) {
+ retval = -EINVAL;
+ sctp_bind_addr_clean(bp);
+ break;
+ }
+
+ af->from_addr_param(&addr, rawaddr, port, 0);
+ retval = sctp_add_bind_addr(bp, &addr, gfp);
+ if (retval) {
+ /* Can't finish building the list, clean up. */
+ sctp_bind_addr_clean(bp);
+ break;;
+ }
+
+ len = ntohs(param->length);
+ addrs_len -= len;
+ raw_addr_list += len;
+ }
+
+ return retval;
+}
+
+/********************************************************************
+ * 2nd Level Abstractions
+ ********************************************************************/
+
+/* Does this contain a specified address? Allow wildcarding. */
+int sctp_bind_addr_match(struct sctp_bind_addr *bp,
+ const union sctp_addr *addr,
+ struct sctp_opt *opt)
+{
+ struct sctp_sockaddr_entry *laddr;
+ struct list_head *pos;
+
+ list_for_each(pos, &bp->address_list) {
+ laddr = list_entry(pos, struct sctp_sockaddr_entry, list);
+ if (opt->pf->cmp_addr(&laddr->a, addr, opt))
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Find the first address in the bind address list that is not present in
+ * the addrs packed array.
+ */
+union sctp_addr *sctp_find_unmatch_addr(struct sctp_bind_addr *bp,
+ const union sctp_addr *addrs,
+ int addrcnt,
+ struct sctp_opt *opt)
+{
+ struct sctp_sockaddr_entry *laddr;
+ union sctp_addr *addr;
+ void *addr_buf;
+ struct sctp_af *af;
+ struct list_head *pos;
+ int i;
+
+ list_for_each(pos, &bp->address_list) {
+ laddr = list_entry(pos, struct sctp_sockaddr_entry, list);
+
+ addr_buf = (union sctp_addr *)addrs;
+ for (i = 0; i < addrcnt; i++) {
+ addr = (union sctp_addr *)addr_buf;
+ af = sctp_get_af_specific(addr->v4.sin_family);
+ if (!af)
+ return NULL;
+
+ if (opt->pf->cmp_addr(&laddr->a, addr, opt))
+ break;
+
+ addr_buf += af->sockaddr_len;
+ }
+ if (i == addrcnt)
+ return &laddr->a;
+ }
+
+ return NULL;
+}
+
+/* Copy out addresses from the global local address list. */
+static int sctp_copy_one_addr(struct sctp_bind_addr *dest,
+ union sctp_addr *addr,
+ sctp_scope_t scope, int gfp, int flags)
+{
+ int error = 0;
+
+ if (sctp_is_any(addr)) {
+ error = sctp_copy_local_addr_list(dest, scope, gfp, flags);
+ } else if (sctp_in_scope(addr, scope)) {
+ /* Now that the address is in scope, check to see if
+ * the address type is supported by local sock as
+ * well as the remote peer.
+ */
+ if ((((AF_INET == addr->sa.sa_family) &&
+ (flags & SCTP_ADDR4_PEERSUPP))) ||
+ (((AF_INET6 == addr->sa.sa_family) &&
+ (flags & SCTP_ADDR6_ALLOWED) &&
+ (flags & SCTP_ADDR6_PEERSUPP))))
+ error = sctp_add_bind_addr(dest, addr, gfp);
+ }
+
+ return error;
+}
+
+/* Is this a wildcard address? */
+int sctp_is_any(const union sctp_addr *addr)
+{
+ struct sctp_af *af = sctp_get_af_specific(addr->sa.sa_family);
+ if (!af)
+ return 0;
+ return af->is_any(addr);
+}
+
+/* Is 'addr' valid for 'scope'? */
+int sctp_in_scope(const union sctp_addr *addr, sctp_scope_t scope)
+{
+ sctp_scope_t addr_scope = sctp_scope(addr);
+
+ /* The unusable SCTP addresses will not be considered with
+ * any defined scopes.
+ */
+ if (SCTP_SCOPE_UNUSABLE == addr_scope)
+ return 0;
+ /*
+ * For INIT and INIT-ACK address list, let L be the level of
+ * of requested destination address, sender and receiver
+ * SHOULD include all of its addresses with level greater
+ * than or equal to L.
+ */
+ if (addr_scope <= scope)
+ return 1;
+
+ return 0;
+}
+
+/********************************************************************
+ * 3rd Level Abstractions
+ ********************************************************************/
+
+/* What is the scope of 'addr'? */
+sctp_scope_t sctp_scope(const union sctp_addr *addr)
+{
+ struct sctp_af *af;
+
+ af = sctp_get_af_specific(addr->sa.sa_family);
+ if (!af)
+ return SCTP_SCOPE_UNUSABLE;
+
+ return af->scope((union sctp_addr *)addr);
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/chunk.c b/uClinux-2.4.31-uc0/net/sctp/chunk.c
new file mode 100644
index 0000000..080e317
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/chunk.c
@@ -0,0 +1,309 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2003, 2004
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * This file contains the code relating the the chunk abstraction.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+/* This file is mostly in anticipation of future work, but initially
+ * populate with fragment tracking for an outbound message.
+ */
+
+/* Initialize datamsg from memory. */
+static void sctp_datamsg_init(struct sctp_datamsg *msg)
+{
+ atomic_set(&msg->refcnt, 1);
+ msg->send_failed = 0;
+ msg->send_error = 0;
+ msg->can_abandon = 0;
+ msg->expires_at = 0;
+ INIT_LIST_HEAD(&msg->chunks);
+}
+
+/* Allocate and initialize datamsg. */
+SCTP_STATIC struct sctp_datamsg *sctp_datamsg_new(int gfp)
+{
+ struct sctp_datamsg *msg;
+ msg = kmalloc(sizeof(struct sctp_datamsg), gfp);
+ if (msg)
+ sctp_datamsg_init(msg);
+ SCTP_DBG_OBJCNT_INC(datamsg);
+ return msg;
+}
+
+/* Final destructruction of datamsg memory. */
+static void sctp_datamsg_destroy(struct sctp_datamsg *msg)
+{
+ struct list_head *pos, *temp;
+ struct sctp_chunk *chunk;
+ struct sctp_opt *sp;
+ struct sctp_ulpevent *ev;
+ struct sctp_association *asoc = NULL;
+ int error = 0, notify;
+
+ /* If we failed, we may need to notify. */
+ notify = msg->send_failed ? -1 : 0;
+
+ /* Release all references. */
+ list_for_each_safe(pos, temp, &msg->chunks) {
+ list_del_init(pos);
+ chunk = list_entry(pos, struct sctp_chunk, frag_list);
+ /* Check whether we _really_ need to notify. */
+ if (notify < 0) {
+ asoc = chunk->asoc;
+ if (msg->send_error)
+ error = msg->send_error;
+ else
+ error = asoc->outqueue.error;
+
+ sp = sctp_sk(asoc->base.sk);
+ notify = sctp_ulpevent_type_enabled(SCTP_SEND_FAILED,
+ &sp->subscribe);
+ }
+
+ /* Generate a SEND FAILED event only if enabled. */
+ if (notify > 0) {
+ int sent;
+ if (chunk->has_tsn)
+ sent = SCTP_DATA_SENT;
+ else
+ sent = SCTP_DATA_UNSENT;
+
+ ev = sctp_ulpevent_make_send_failed(asoc, chunk, sent,
+ error, GFP_ATOMIC);
+ if (ev)
+ sctp_ulpq_tail_event(&asoc->ulpq, ev);
+ }
+
+ sctp_chunk_put(chunk);
+ }
+
+ SCTP_DBG_OBJCNT_DEC(datamsg);
+ kfree(msg);
+}
+
+/* Hold a reference. */
+static void sctp_datamsg_hold(struct sctp_datamsg *msg)
+{
+ atomic_inc(&msg->refcnt);
+}
+
+/* Release a reference. */
+void sctp_datamsg_put(struct sctp_datamsg *msg)
+{
+ if (atomic_dec_and_test(&msg->refcnt))
+ sctp_datamsg_destroy(msg);
+}
+
+/* Free a message. Really just give up a reference, the
+ * really free happens in sctp_datamsg_destroy().
+ */
+void sctp_datamsg_free(struct sctp_datamsg *msg)
+{
+ sctp_datamsg_put(msg);
+}
+
+/* Hold on to all the fragments until all chunks have been sent. */
+void sctp_datamsg_track(struct sctp_chunk *chunk)
+{
+ sctp_chunk_hold(chunk);
+}
+
+/* Assign a chunk to this datamsg. */
+static void sctp_datamsg_assign(struct sctp_datamsg *msg, struct sctp_chunk *chunk)
+{
+ sctp_datamsg_hold(msg);
+ chunk->msg = msg;
+}
+
+
+/* A data chunk can have a maximum payload of (2^16 - 20). Break
+ * down any such message into smaller chunks. Opportunistically, fragment
+ * the chunks down to the current MTU constraints. We may get refragmented
+ * later if the PMTU changes, but it is _much better_ to fragment immediately
+ * with a reasonable guess than always doing our fragmentation on the
+ * soft-interrupt.
+ */
+struct sctp_datamsg *sctp_datamsg_from_user(struct sctp_association *asoc,
+ struct sctp_sndrcvinfo *sinfo,
+ struct msghdr *msgh, int msg_len)
+{
+ int max, whole, i, offset, over, err;
+ int len, first_len;
+ struct sctp_chunk *chunk;
+ struct sctp_datamsg *msg;
+ struct list_head *pos, *temp;
+ __u8 frag;
+
+ msg = sctp_datamsg_new(GFP_KERNEL);
+ if (!msg)
+ return NULL;
+
+ /* Note: Calculate this outside of the loop, so that all fragments
+ * have the same expiration.
+ */
+ if (sinfo->sinfo_timetolive) {
+ /* sinfo_timetolive is in milliseconds */
+ msg->expires_at = jiffies +
+ SCTP_MSECS_TO_JIFFIES(sinfo->sinfo_timetolive);
+ msg->can_abandon = 1;
+ SCTP_DEBUG_PRINTK("%s: msg:%p expires_at: %ld jiffies:%ld\n",
+ __FUNCTION__, msg, msg->expires_at, jiffies);
+ }
+
+ max = asoc->frag_point;
+
+ whole = 0;
+ first_len = max;
+
+ /* Encourage Cookie-ECHO bundling. */
+ if (asoc->state < SCTP_STATE_COOKIE_ECHOED) {
+ whole = msg_len / (max - SCTP_ARBITRARY_COOKIE_ECHO_LEN);
+
+ /* Account for the DATA to be bundled with the COOKIE-ECHO. */
+ if (whole) {
+ first_len = max - SCTP_ARBITRARY_COOKIE_ECHO_LEN;
+ msg_len -= first_len;
+ whole = 1;
+ }
+ }
+
+ /* How many full sized? How many bytes leftover? */
+ whole += msg_len / max;
+ over = msg_len % max;
+ offset = 0;
+
+ if ((whole > 1) || (whole && over))
+ SCTP_INC_STATS_USER(SctpFragUsrMsgs);
+
+ /* Create chunks for all the full sized DATA chunks. */
+ for (i=0, len=first_len; i < whole; i++) {
+ frag = SCTP_DATA_MIDDLE_FRAG;
+
+ if (0 == i)
+ frag |= SCTP_DATA_FIRST_FRAG;
+
+ if ((i == (whole - 1)) && !over)
+ frag |= SCTP_DATA_LAST_FRAG;
+
+ chunk = sctp_make_datafrag_empty(asoc, sinfo, len, frag, 0);
+
+ if (!chunk)
+ goto errout;
+ err = sctp_user_addto_chunk(chunk, offset, len, msgh->msg_iov);
+ if (err < 0)
+ goto errout;
+
+ offset += len;
+
+ /* Put the chunk->skb back into the form expected by send. */
+ __skb_pull(chunk->skb, (__u8 *)chunk->chunk_hdr
+ - (__u8 *)chunk->skb->data);
+
+ sctp_datamsg_assign(msg, chunk);
+ list_add_tail(&chunk->frag_list, &msg->chunks);
+
+ /* The first chunk, the first chunk was likely short
+ * to allow bundling, so reset to full size.
+ */
+ if (0 == i)
+ len = max;
+ }
+
+ /* .. now the leftover bytes. */
+ if (over) {
+ if (!whole)
+ frag = SCTP_DATA_NOT_FRAG;
+ else
+ frag = SCTP_DATA_LAST_FRAG;
+
+ chunk = sctp_make_datafrag_empty(asoc, sinfo, over, frag, 0);
+
+ if (!chunk)
+ goto errout;
+
+ err = sctp_user_addto_chunk(chunk, offset, over,msgh->msg_iov);
+
+ /* Put the chunk->skb back into the form expected by send. */
+ __skb_pull(chunk->skb, (__u8 *)chunk->chunk_hdr
+ - (__u8 *)chunk->skb->data);
+ if (err < 0)
+ goto errout;
+
+ sctp_datamsg_assign(msg, chunk);
+ list_add_tail(&chunk->frag_list, &msg->chunks);
+ }
+
+ return msg;
+
+errout:
+ list_for_each_safe(pos, temp, &msg->chunks) {
+ list_del_init(pos);
+ chunk = list_entry(pos, struct sctp_chunk, frag_list);
+ sctp_chunk_free(chunk);
+ }
+ sctp_datamsg_free(msg);
+ return NULL;
+}
+
+/* Check whether this message has expired. */
+int sctp_chunk_abandoned(struct sctp_chunk *chunk)
+{
+ struct sctp_datamsg *msg = chunk->msg;
+
+ if (!msg->can_abandon)
+ return 0;
+
+ if (time_after(jiffies, msg->expires_at))
+ return 1;
+
+ return 0;
+}
+
+/* This chunk (and consequently entire message) has failed in its sending. */
+void sctp_chunk_fail(struct sctp_chunk *chunk, int error)
+{
+ chunk->msg->send_failed = 1;
+ chunk->msg->send_error = error;
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/command.c b/uClinux-2.4.31-uc0/net/sctp/command.c
new file mode 100644
index 0000000..3ff8047
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/command.c
@@ -0,0 +1,81 @@
+/* SCTP kernel reference Implementation Copyright (C) 1999-2001
+ * Cisco, Motorola, and IBM
+ * Copyright 2001 La Monte H.P. Yarroll
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * These functions manipulate sctp command sequences.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+/* Initialize a block of memory as a command sequence. */
+int sctp_init_cmd_seq(sctp_cmd_seq_t *seq)
+{
+ memset(seq, 0, sizeof(sctp_cmd_seq_t));
+ return 1; /* We always succeed. */
+}
+
+/* Add a command to a sctp_cmd_seq_t.
+ * Return 0 if the command sequence is full.
+ */
+int sctp_add_cmd(sctp_cmd_seq_t *seq, sctp_verb_t verb, sctp_arg_t obj)
+{
+ if (seq->next_free_slot >= SCTP_MAX_NUM_COMMANDS)
+ goto fail;
+
+ seq->cmds[seq->next_free_slot].verb = verb;
+ seq->cmds[seq->next_free_slot++].obj = obj;
+
+ return 1;
+
+fail:
+ return 0;
+}
+
+/* Return the next command structure in a sctp_cmd_seq.
+ * Returns NULL at the end of the sequence.
+ */
+sctp_cmd_t *sctp_next_cmd(sctp_cmd_seq_t *seq)
+{
+ sctp_cmd_t *retval = NULL;
+
+ if (seq->next_cmd < seq->next_free_slot)
+ retval = &seq->cmds[seq->next_cmd++];
+
+ return retval;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/sctp/crc32c.c b/uClinux-2.4.31-uc0/net/sctp/crc32c.c
new file mode 100644
index 0000000..31f05ec
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/crc32c.c
@@ -0,0 +1,220 @@
+/* SCTP kernel reference Implementation
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001-2003 International Business Machines, Corp.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * SCTP Checksum functions
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * Dinakaran Joseph
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+/* The following code has been taken directly from
+ * draft-ietf-tsvwg-sctpcsum-03.txt
+ *
+ * The code has now been modified specifically for SCTP knowledge.
+ */
+
+#include <linux/types.h>
+#include <net/sctp/sctp.h>
+
+#define CRC32C_POLY 0x1EDC6F41
+#define CRC32C(c,d) (c=(c>>8)^crc_c[(c^(d))&0xFF])
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+/* Copyright 2001, D. Otis. Use this program, code or tables */
+/* extracted from it, as desired without restriction. */
+/* */
+/* 32 Bit Reflected CRC table generation for SCTP. */
+/* To accommodate serial byte data being shifted out least */
+/* significant bit first, the table's 32 bit words are reflected */
+/* which flips both byte and bit MS and LS positions. The CRC */
+/* is calculated MS bits first from the perspective of the serial*/
+/* stream. The x^32 term is implied and the x^0 term may also */
+/* be shown as +1. The polynomial code used is 0x1EDC6F41. */
+/* Castagnoli93 */
+/* x^32+x^28+x^27+x^26+x^25+x^23+x^22+x^20+x^19+x^18+x^14+x^13+ */
+/* x^11+x^10+x^9+x^8+x^6+x^0 */
+/* Guy Castagnoli Stefan Braeuer and Martin Herrman */
+/* "Optimization of Cyclic Redundancy-Check Codes */
+/* with 24 and 32 Parity Bits", */
+/* IEEE Transactions on Communications, Vol.41, No.6, June 1993 */
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+static const __u32 crc_c[256] = {
+ 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4,
+ 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB,
+ 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B,
+ 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24,
+ 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B,
+ 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,
+ 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54,
+ 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B,
+ 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A,
+ 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,
+ 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5,
+ 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,
+ 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45,
+ 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A,
+ 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A,
+ 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595,
+ 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48,
+ 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
+ 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687,
+ 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,
+ 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927,
+ 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38,
+ 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8,
+ 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,
+ 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096,
+ 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789,
+ 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859,
+ 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46,
+ 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9,
+ 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
+ 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36,
+ 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829,
+ 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C,
+ 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93,
+ 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043,
+ 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
+ 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3,
+ 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC,
+ 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C,
+ 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,
+ 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652,
+ 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,
+ 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D,
+ 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982,
+ 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D,
+ 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622,
+ 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2,
+ 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,
+ 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530,
+ 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,
+ 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF,
+ 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0,
+ 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F,
+ 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
+ 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90,
+ 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F,
+ 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE,
+ 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1,
+ 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321,
+ 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
+ 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81,
+ 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E,
+ 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E,
+ 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351,
+};
+
+__u32 sctp_start_cksum(__u8 *buffer, __u16 length)
+{
+ __u32 crc32 = ~(__u32) 0;
+ __u32 i;
+
+ /* Optimize this routine to be SCTP specific, knowing how
+ * to skip the checksum field of the SCTP header.
+ */
+
+ /* Calculate CRC up to the checksum. */
+ for (i = 0; i < (sizeof(struct sctphdr) - sizeof(__u32)); i++)
+ CRC32C(crc32, buffer[i]);
+
+ /* Skip checksum field of the header. */
+ for (i = 0; i < sizeof(__u32); i++)
+ CRC32C(crc32, 0);
+
+ /* Calculate the rest of the CRC. */
+ for (i = sizeof(struct sctphdr); i < length ; i++)
+ CRC32C(crc32, buffer[i]);
+
+ return crc32;
+}
+
+__u32 sctp_update_cksum(__u8 *buffer, __u16 length, __u32 crc32)
+{
+ __u32 i;
+
+ for (i = 0; i < length ; i++)
+ CRC32C(crc32, buffer[i]);
+
+ return crc32;
+}
+
+__u32 sctp_update_copy_cksum(__u8 *to, __u8 *from, __u16 length, __u32 crc32)
+{
+ __u32 i;
+ __u32 *_to = (__u32 *)to;
+ __u32 *_from = (__u32 *)from;
+
+ for (i = 0; i < (length/4); i++) {
+ _to[i] = _from[i];
+ CRC32C(crc32, from[i*4]);
+ CRC32C(crc32, from[i*4+1]);
+ CRC32C(crc32, from[i*4+2]);
+ CRC32C(crc32, from[i*4+3]);
+ }
+
+ return crc32;
+}
+
+__u32 sctp_end_cksum(__u32 crc32)
+{
+ __u32 result;
+ __u8 byte0, byte1, byte2, byte3;
+
+ result = ~crc32;
+
+ /* result now holds the negated polynomial remainder;
+ * since the table and algorithm is "reflected" [williams95].
+ * That is, result has the same value as if we mapped the message
+ * to a polyomial, computed the host-bit-order polynomial
+ * remainder, performed final negation, then did an end-for-end
+ * bit-reversal.
+ * Note that a 32-bit bit-reversal is identical to four inplace
+ * 8-bit reversals followed by an end-for-end byteswap.
+ * In other words, the bytes of each bit are in the right order,
+ * but the bytes have been byteswapped. So we now do an explicit
+ * byteswap. On a little-endian machine, this byteswap and
+ * the final ntohl cancel out and could be elided.
+ */
+ byte0 = result & 0xff;
+ byte1 = (result>>8) & 0xff;
+ byte2 = (result>>16) & 0xff;
+ byte3 = (result>>24) & 0xff;
+
+ crc32 = ((byte0 << 24) |
+ (byte1 << 16) |
+ (byte2 << 8) |
+ byte3);
+ return crc32;
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/debug.c b/uClinux-2.4.31-uc0/net/sctp/debug.c
new file mode 100644
index 0000000..aa83403
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/debug.c
@@ -0,0 +1,191 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2001, 2004
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001 Intel Corp.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * This file is part of the implementation of the add-IP extension,
+ * based on <draft-ietf-tsvwg-addip-sctp-02.txt> June 29, 2001,
+ * for the SCTP kernel reference Implementation.
+ *
+ * This file converts numerical ID value to alphabetical names for SCTP
+ * terms such as chunk type, parameter time, event type, etc.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Xingang Guo <xingang.guo@intel.com>
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Daisy Chang <daisyc@us.ibm.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <net/sctp/sctp.h>
+
+#if SCTP_DEBUG
+int sctp_debug_flag = 1; /* Initially enable DEBUG */
+#endif /* SCTP_DEBUG */
+
+/* These are printable forms of Chunk ID's from section 3.1. */
+static const char *sctp_cid_tbl[SCTP_NUM_BASE_CHUNK_TYPES] = {
+ "DATA",
+ "INIT",
+ "INIT_ACK",
+ "SACK",
+ "HEARTBEAT",
+ "HEARTBEAT_ACK",
+ "ABORT",
+ "SHUTDOWN",
+ "SHUTDOWN_ACK",
+ "ERROR",
+ "COOKIE_ECHO",
+ "COOKIE_ACK",
+ "ECN_ECNE",
+ "ECN_CWR",
+ "SHUTDOWN_COMPLETE",
+};
+
+/* Lookup "chunk type" debug name. */
+const char *sctp_cname(const sctp_subtype_t cid)
+{
+ if (cid.chunk < 0)
+ return "illegal chunk id";
+ if (cid.chunk <= SCTP_CID_BASE_MAX)
+ return sctp_cid_tbl[cid.chunk];
+
+ switch (cid.chunk) {
+ case SCTP_CID_ASCONF:
+ return "ASCONF";
+
+ case SCTP_CID_ASCONF_ACK:
+ return "ASCONF_ACK";
+
+ case SCTP_CID_FWD_TSN:
+ return "FWD_TSN";
+
+ default:
+ return "unknown chunk";
+ };
+ return "unknown chunk";
+}
+
+/* These are printable forms of the states. */
+const char *sctp_state_tbl[SCTP_STATE_NUM_STATES] = {
+ "STATE_EMPTY",
+ "STATE_CLOSED",
+ "STATE_COOKIE_WAIT",
+ "STATE_COOKIE_ECHOED",
+ "STATE_ESTABLISHED",
+ "STATE_SHUTDOWN_PENDING",
+ "STATE_SHUTDOWN_SENT",
+ "STATE_SHUTDOWN_RECEIVED",
+ "STATE_SHUTDOWN_ACK_SENT",
+};
+
+/* Events that could change the state of an association. */
+const char *sctp_evttype_tbl[] = {
+ "EVENT_T_unknown",
+ "EVENT_T_CHUNK",
+ "EVENT_T_TIMEOUT",
+ "EVENT_T_OTHER",
+ "EVENT_T_PRIMITIVE"
+};
+
+/* Return value of a state function */
+const char *sctp_status_tbl[] = {
+ "DISPOSITION_DISCARD",
+ "DISPOSITION_CONSUME",
+ "DISPOSITION_NOMEM",
+ "DISPOSITION_DELETE_TCB",
+ "DISPOSITION_ABORT",
+ "DISPOSITION_VIOLATION",
+ "DISPOSITION_NOT_IMPL",
+ "DISPOSITION_ERROR",
+ "DISPOSITION_BUG"
+};
+
+/* Printable forms of primitives */
+static const char *sctp_primitive_tbl[SCTP_NUM_PRIMITIVE_TYPES] = {
+ "PRIMITIVE_ASSOCIATE",
+ "PRIMITIVE_SHUTDOWN",
+ "PRIMITIVE_ABORT",
+ "PRIMITIVE_SEND",
+ "PRIMITIVE_REQUESTHEARTBEAT",
+};
+
+/* Lookup primitive debug name. */
+const char *sctp_pname(const sctp_subtype_t id)
+{
+ if (id.primitive < 0)
+ return "illegal primitive";
+ if (id.primitive <= SCTP_EVENT_PRIMITIVE_MAX)
+ return sctp_primitive_tbl[id.primitive];
+ return "unknown_primitive";
+}
+
+static const char *sctp_other_tbl[] = {
+ "NO_PENDING_TSN",
+ "ICMP_PROTO_UNREACH",
+};
+
+/* Lookup "other" debug name. */
+const char *sctp_oname(const sctp_subtype_t id)
+{
+ if (id.other < 0)
+ return "illegal 'other' event";
+ if (id.other <= SCTP_EVENT_OTHER_MAX)
+ return sctp_other_tbl[id.other];
+ return "unknown 'other' event";
+}
+
+static const char *sctp_timer_tbl[] = {
+ "TIMEOUT_NONE",
+ "TIMEOUT_T1_COOKIE",
+ "TIMEOUT_T1_INIT",
+ "TIMEOUT_T2_SHUTDOWN",
+ "TIMEOUT_T3_RTX",
+ "TIMEOUT_T4_RTO",
+ "TIMEOUT_T5_SHUTDOWN_GUARD",
+ "TIMEOUT_HEARTBEAT",
+ "TIMEOUT_SACK",
+ "TIMEOUT_AUTOCLOSE",
+};
+
+/* Lookup timer debug name. */
+const char *sctp_tname(const sctp_subtype_t id)
+{
+ if (id.timeout < 0)
+ return "illegal 'timer' event";
+ if (id.timeout <= SCTP_EVENT_TIMEOUT_MAX)
+ return sctp_timer_tbl[id.timeout];
+ return "unknown_timer";
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/endpointola.c b/uClinux-2.4.31-uc0/net/sctp/endpointola.c
new file mode 100644
index 0000000..72b2dd4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/endpointola.c
@@ -0,0 +1,389 @@
+/* SCTP kernel reference Implementation
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001-2002 International Business Machines, Corp.
+ * Copyright (c) 2001 Intel Corp.
+ * Copyright (c) 2001 Nokia, Inc.
+ * Copyright (c) 2001 La Monte H.P. Yarroll
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * This abstraction represents an SCTP endpoint.
+ *
+ * This file is part of the implementation of the add-IP extension,
+ * based on <draft-ietf-tsvwg-addip-sctp-02.txt> June 29, 2001,
+ * for the SCTP kernel reference Implementation.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Jon Grimm <jgrimm@austin.ibm.com>
+ * Daisy Chang <daisyc@us.ibm.com>
+ * Dajiang Zhang <dajiang.zhang@nokia.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/in.h>
+#include <linux/random.h> /* get_random_bytes() */
+#include <linux/crypto.h>
+#include <net/sock.h>
+#include <net/ipv6.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+/* Forward declarations for internal helpers. */
+static void sctp_endpoint_bh_rcv(struct sctp_endpoint *ep);
+
+/*
+ * Initialize the base fields of the endpoint structure.
+ */
+static struct sctp_endpoint *sctp_endpoint_init(struct sctp_endpoint *ep,
+ struct sock *sk, int gfp)
+{
+ struct sctp_opt *sp = sctp_sk(sk);
+ memset(ep, 0, sizeof(struct sctp_endpoint));
+
+ /* Initialize the base structure. */
+ /* What type of endpoint are we? */
+ ep->base.type = SCTP_EP_TYPE_SOCKET;
+
+ /* Initialize the basic object fields. */
+ atomic_set(&ep->base.refcnt, 1);
+ ep->base.dead = 0;
+ ep->base.malloced = 1;
+
+ /* Create an input queue. */
+ sctp_inq_init(&ep->base.inqueue);
+
+ /* Set its top-half handler */
+ sctp_inq_set_th_handler(&ep->base.inqueue,
+ (void (*)(void *))sctp_endpoint_bh_rcv, ep);
+
+ /* Initialize the bind addr area */
+ sctp_bind_addr_init(&ep->base.bind_addr, 0);
+ ep->base.addr_lock = RW_LOCK_UNLOCKED;
+
+ /* Remember who we are attached to. */
+ ep->base.sk = sk;
+ sock_hold(ep->base.sk);
+
+ /* Create the lists of associations. */
+ INIT_LIST_HEAD(&ep->asocs);
+
+ /* Set up the base timeout information. */
+ ep->timeouts[SCTP_EVENT_TIMEOUT_NONE] = 0;
+ ep->timeouts[SCTP_EVENT_TIMEOUT_T1_COOKIE] =
+ SCTP_DEFAULT_TIMEOUT_T1_COOKIE;
+ ep->timeouts[SCTP_EVENT_TIMEOUT_T1_INIT] =
+ SCTP_DEFAULT_TIMEOUT_T1_INIT;
+ ep->timeouts[SCTP_EVENT_TIMEOUT_T2_SHUTDOWN] =
+ SCTP_MSECS_TO_JIFFIES(sp->rtoinfo.srto_initial);
+ ep->timeouts[SCTP_EVENT_TIMEOUT_T3_RTX] = 0;
+ ep->timeouts[SCTP_EVENT_TIMEOUT_T4_RTO] = 0;
+
+ /* sctpimpguide-05 Section 2.12.2
+ * If the 'T5-shutdown-guard' timer is used, it SHOULD be set to the
+ * recommended value of 5 times 'RTO.Max'.
+ */
+ ep->timeouts[SCTP_EVENT_TIMEOUT_T5_SHUTDOWN_GUARD]
+ = 5 * SCTP_MSECS_TO_JIFFIES(sp->rtoinfo.srto_max);
+
+ ep->timeouts[SCTP_EVENT_TIMEOUT_HEARTBEAT] =
+ SCTP_DEFAULT_TIMEOUT_HEARTBEAT;
+ ep->timeouts[SCTP_EVENT_TIMEOUT_SACK] =
+ SCTP_DEFAULT_TIMEOUT_SACK;
+ ep->timeouts[SCTP_EVENT_TIMEOUT_AUTOCLOSE] =
+ sp->autoclose * HZ;
+
+ /* Use SCTP specific send buffer space queues. */
+ sk->write_space = sctp_write_space;
+ sk->use_write_queue = 1;
+
+ /* Initialize the secret key used with cookie. */
+ get_random_bytes(&ep->secret_key[0], SCTP_SECRET_SIZE);
+ ep->last_key = ep->current_key = 0;
+ ep->key_changed_at = jiffies;
+
+ ep->debug_name = "unnamedEndpoint";
+ return ep;
+}
+
+/* Create a sctp_endpoint with all that boring stuff initialized.
+ * Returns NULL if there isn't enough memory.
+ */
+struct sctp_endpoint *sctp_endpoint_new(struct sock *sk, int gfp)
+{
+ struct sctp_endpoint *ep;
+
+ /* Build a local endpoint. */
+ ep = t_new(struct sctp_endpoint, gfp);
+ if (!ep)
+ goto fail;
+ if (!sctp_endpoint_init(ep, sk, gfp))
+ goto fail_init;
+ ep->base.malloced = 1;
+ SCTP_DBG_OBJCNT_INC(ep);
+ return ep;
+
+fail_init:
+ kfree(ep);
+fail:
+ return NULL;
+}
+
+/* Add an association to an endpoint. */
+void sctp_endpoint_add_asoc(struct sctp_endpoint *ep,
+ struct sctp_association *asoc)
+{
+ struct sock *sk = ep->base.sk;
+
+ /* Now just add it to our list of asocs */
+ list_add_tail(&asoc->asocs, &ep->asocs);
+
+ /* Increment the backlog value for a TCP-style listening socket. */
+ if (sctp_style(sk, TCP) && sctp_sstate(sk, LISTENING))
+ sk->ack_backlog++;
+}
+
+/* Free the endpoint structure. Delay cleanup until
+ * all users have released their reference count on this structure.
+ */
+void sctp_endpoint_free(struct sctp_endpoint *ep)
+{
+ ep->base.dead = 1;
+ sctp_endpoint_put(ep);
+}
+
+/* Final destructor for endpoint. */
+static void sctp_endpoint_destroy(struct sctp_endpoint *ep)
+{
+ SCTP_ASSERT(ep->base.dead, "Endpoint is not dead", return);
+
+ ep->base.sk->state = SCTP_SS_CLOSED;
+
+ /* Unlink this endpoint, so we can't find it again! */
+ sctp_unhash_endpoint(ep);
+
+ /* Free up the HMAC transform. */
+ if (sctp_sk(ep->base.sk)->hmac)
+ sctp_crypto_free_tfm(sctp_sk(ep->base.sk)->hmac);
+
+ /* Cleanup. */
+ sctp_inq_free(&ep->base.inqueue);
+ sctp_bind_addr_free(&ep->base.bind_addr);
+
+ /* Remove and free the port */
+ if (ep->base.sk->prev != NULL)
+ sctp_put_port(ep->base.sk);
+
+ /* Give up our hold on the sock. */
+ if (ep->base.sk)
+ sock_put(ep->base.sk);
+
+ /* Finally, free up our memory. */
+ if (ep->base.malloced) {
+ kfree(ep);
+ SCTP_DBG_OBJCNT_DEC(ep);
+ }
+}
+
+/* Hold a reference to an endpoint. */
+void sctp_endpoint_hold(struct sctp_endpoint *ep)
+{
+ atomic_inc(&ep->base.refcnt);
+}
+
+/* Release a reference to an endpoint and clean up if there are
+ * no more references.
+ */
+void sctp_endpoint_put(struct sctp_endpoint *ep)
+{
+ if (atomic_dec_and_test(&ep->base.refcnt))
+ sctp_endpoint_destroy(ep);
+}
+
+/* Is this the endpoint we are looking for? */
+struct sctp_endpoint *sctp_endpoint_is_match(struct sctp_endpoint *ep,
+ const union sctp_addr *laddr)
+{
+ struct sctp_endpoint *retval;
+
+ sctp_read_lock(&ep->base.addr_lock);
+ if (ep->base.bind_addr.port == laddr->v4.sin_port) {
+ if (sctp_bind_addr_match(&ep->base.bind_addr, laddr,
+ sctp_sk(ep->base.sk))) {
+ retval = ep;
+ goto out;
+ }
+ }
+
+ retval = NULL;
+
+out:
+ sctp_read_unlock(&ep->base.addr_lock);
+ return retval;
+}
+
+/* Find the association that goes with this chunk.
+ * We do a linear search of the associations for this endpoint.
+ * We return the matching transport address too.
+ */
+static struct sctp_association *__sctp_endpoint_lookup_assoc(
+ const struct sctp_endpoint *ep,
+ const union sctp_addr *paddr,
+ struct sctp_transport **transport)
+{
+ int rport;
+ struct sctp_association *asoc;
+ struct list_head *pos;
+
+ rport = paddr->v4.sin_port;
+
+ list_for_each(pos, &ep->asocs) {
+ asoc = list_entry(pos, struct sctp_association, asocs);
+ if (rport == asoc->peer.port) {
+ sctp_read_lock(&asoc->base.addr_lock);
+ *transport = sctp_assoc_lookup_paddr(asoc, paddr);
+ sctp_read_unlock(&asoc->base.addr_lock);
+
+ if (*transport)
+ return asoc;
+ }
+ }
+
+ *transport = NULL;
+ return NULL;
+}
+
+/* Lookup association on an endpoint based on a peer address. BH-safe. */
+struct sctp_association *sctp_endpoint_lookup_assoc(
+ const struct sctp_endpoint *ep,
+ const union sctp_addr *paddr,
+ struct sctp_transport **transport)
+{
+ struct sctp_association *asoc;
+
+ sctp_local_bh_disable();
+ asoc = __sctp_endpoint_lookup_assoc(ep, paddr, transport);
+ sctp_local_bh_enable();
+
+ return asoc;
+}
+
+/* Look for any peeled off association from the endpoint that matches the
+ * given peer address.
+ */
+int sctp_endpoint_is_peeled_off(struct sctp_endpoint *ep,
+ const union sctp_addr *paddr)
+{
+ struct list_head *pos;
+ struct sctp_sockaddr_entry *addr;
+ struct sctp_bind_addr *bp;
+
+ sctp_read_lock(&ep->base.addr_lock);
+ bp = &ep->base.bind_addr;
+ list_for_each(pos, &bp->address_list) {
+ addr = list_entry(pos, struct sctp_sockaddr_entry, list);
+ if (sctp_has_association(&addr->a, paddr)) {
+ sctp_read_unlock(&ep->base.addr_lock);
+ return 1;
+ }
+ }
+ sctp_read_unlock(&ep->base.addr_lock);
+
+ return 0;
+}
+
+/* Do delayed input processing. This is scheduled by sctp_rcv().
+ * This may be called on BH or task time.
+ */
+static void sctp_endpoint_bh_rcv(struct sctp_endpoint *ep)
+{
+ struct sctp_association *asoc;
+ struct sock *sk;
+ struct sctp_transport *transport;
+ struct sctp_chunk *chunk;
+ struct sctp_inq *inqueue;
+ sctp_subtype_t subtype;
+ sctp_state_t state;
+ int error = 0;
+
+ if (ep->base.dead)
+ return;
+
+ asoc = NULL;
+ inqueue = &ep->base.inqueue;
+ sk = ep->base.sk;
+
+ while (NULL != (chunk = sctp_inq_pop(inqueue))) {
+ subtype = SCTP_ST_CHUNK(chunk->chunk_hdr->type);
+
+ /* We might have grown an association since last we
+ * looked, so try again.
+ *
+ * This happens when we've just processed our
+ * COOKIE-ECHO chunk.
+ */
+ if (NULL == chunk->asoc) {
+ asoc = sctp_endpoint_lookup_assoc(ep,
+ sctp_source(chunk),
+ &transport);
+ chunk->asoc = asoc;
+ chunk->transport = transport;
+ }
+
+ state = asoc ? asoc->state : SCTP_STATE_CLOSED;
+
+ /* Remember where the last DATA chunk came from so we
+ * know where to send the SACK.
+ */
+ if (asoc && sctp_chunk_is_data(chunk))
+ asoc->peer.last_data_from = chunk->transport;
+ else
+ SCTP_INC_STATS(SctpInCtrlChunks);
+
+ if (chunk->transport)
+ chunk->transport->last_time_heard = jiffies;
+
+ error = sctp_do_sm(SCTP_EVENT_T_CHUNK, subtype, state,
+ ep, asoc, chunk, GFP_ATOMIC);
+
+ if (error && chunk)
+ chunk->pdiscard = 1;
+
+ /* Check to see if the endpoint is freed in response to
+ * the incoming chunk. If so, get out of the while loop.
+ */
+ if (!sctp_sk(sk)->ep)
+ break;
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/hashdriver.c b/uClinux-2.4.31-uc0/net/sctp/hashdriver.c
new file mode 100644
index 0000000..bb21267
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/hashdriver.c
@@ -0,0 +1,128 @@
+/* SCTP reference Implementation Copyright (C) 1999 Cisco And Motorola
+ *
+ * This file origiantes from Randy Stewart's SCTP reference Implementation.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * Randy Stewart rstewar1@email.mot.com
+ * Ken Morneau kmorneau@cisco.com
+ * Qiaobing Xie qxie1@email.mot.com
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorperated into the next SCTP release.
+ *
+ * There are still LOTS of bugs in this code... I always run on the motto
+ * "it is a wonder any code ever works :)"
+ */
+
+#include <linux/types.h>
+#include <asm/string.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sla1.h>
+
+/* SCTP Main driver.
+ * passing a two pointers and two lengths,
+ * returning a digest pointer filled. The md5 code
+ * was taken directly from the RFC (2104) so to understand it
+ * you may want to go look at the RFC referenced in the
+ * SCTP spec. We did modify this code to either user OUR
+ * implementation of SLA1 or the MD5 that comes from its
+ * RFC. SLA1 may have IPR issues so you need to check in
+ * to this if you wish to use it... Or at least that is
+ * what the FIP-180.1 web page says.
+ */
+
+void sctp_hash_digest(const char *key, const int in_key_len,
+ const char *text, const int text_len,
+ __u8 *digest)
+{
+ int key_len = in_key_len;
+ struct SLA_1_Context context;
+
+ __u8 k_ipad[65]; /* inner padding -
+ * key XORd with ipad
+ */
+ __u8 k_opad[65]; /* outer padding -
+ * key XORd with opad
+ */
+ __u8 tk[20];
+ int i;
+
+ /* if key is longer than 64 bytes reset it to key=MD5(key) */
+ if (key_len > 64) {
+ struct SLA_1_Context tctx;
+
+ SLA1_Init(&tctx);
+ SLA1_Process(&tctx, key, key_len);
+ SLA1_Final(&tctx,tk);
+ key = tk;
+ key_len = 20;
+ }
+
+ /*
+ * the HMAC_MD5 transform looks like:
+ *
+ * MD5(K XOR opad, MD5(K XOR ipad, text))
+ *
+ * where K is an n byte key
+ * ipad is the byte 0x36 repeated 64 times
+ * opad is the byte 0x5c repeated 64 times
+ * and text is the data being protected
+ */
+
+ /* start out by storing key in pads */
+ memset(k_ipad, 0, sizeof k_ipad);
+ memset(k_opad, 0, sizeof k_opad);
+ memcpy(k_ipad, key, key_len);
+ memcpy(k_opad, key, key_len);
+
+ /* XOR key with ipad and opad values */
+ for (i = 0; i < 64; i++) {
+ k_ipad[i] ^= 0x36;
+ k_opad[i] ^= 0x5c;
+ }
+
+ /* perform inner hash */
+ SLA1_Init(&context); /* init context for 1st
+ * pass
+ */
+ SLA1_Process(&context, k_ipad, 64); /* start with inner pad */
+ SLA1_Process(&context, text, text_len); /* then text of datagram */
+ SLA1_Final(&context,digest); /* finish up 1st pass */
+
+ /*
+ * perform outer hash
+ */
+ SLA1_Init(&context); /* init context for 2nd
+ * pass
+ */
+ SLA1_Process(&context, k_opad, 64); /* start with outer pad */
+ SLA1_Process(&context, digest, 20); /* then results of 1st
+ * hash
+ */
+ SLA1_Final(&context, digest); /* finish up 2nd pass */
+}
+
diff --git a/uClinux-2.4.31-uc0/net/sctp/input.c b/uClinux-2.4.31-uc0/net/sctp/input.c
new file mode 100644
index 0000000..9ae7175
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/input.c
@@ -0,0 +1,885 @@
+/* SCTP kernel reference Implementation
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001-2003 International Business Machines, Corp.
+ * Copyright (c) 2001 Intel Corp.
+ * Copyright (c) 2001 Nokia, Inc.
+ * Copyright (c) 2001 La Monte H.P. Yarroll
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * These functions handle all input from the IP layer into SCTP.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Xingang Guo <xingang.guo@intel.com>
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Hui Huang <hui.huang@nokia.com>
+ * Daisy Chang <daisyc@us.ibm.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ * Ardelle Fan <ardelle.fan@intel.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <linux/list.h> /* For struct list_head */
+#include <linux/socket.h>
+#include <linux/ip.h>
+#include <linux/time.h> /* For struct timeval */
+#include <net/ip.h>
+#include <net/icmp.h>
+#include <net/snmp.h>
+#include <net/sock.h>
+#include <linux/ipsec.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+/* Forward declarations for internal helpers. */
+static int sctp_rcv_ootb(struct sk_buff *);
+static struct sctp_association *__sctp_rcv_lookup(struct sk_buff *skb,
+ const union sctp_addr *laddr,
+ const union sctp_addr *paddr,
+ struct sctp_transport **transportp);
+static struct sctp_endpoint *__sctp_rcv_lookup_endpoint(const union sctp_addr *laddr);
+static struct sctp_association *__sctp_lookup_association(
+ const union sctp_addr *local,
+ const union sctp_addr *peer,
+ struct sctp_transport **pt);
+
+
+/* Calculate the SCTP checksum of an SCTP packet. */
+static inline int sctp_rcv_checksum(struct sk_buff *skb)
+{
+ struct sctphdr *sh;
+ __u32 cmp, val;
+ struct sk_buff *list = skb_shinfo(skb)->frag_list;
+
+ sh = (struct sctphdr *) skb->h.raw;
+ cmp = ntohl(sh->checksum);
+
+ val = sctp_start_cksum((__u8 *)sh, skb_headlen(skb));
+
+ for (; list; list = list->next)
+ val = sctp_update_cksum((__u8 *)list->data, skb_headlen(list),
+ val);
+
+ val = sctp_end_cksum(val);
+
+ if (val != cmp) {
+ /* CRC failure, dump it. */
+ SCTP_INC_STATS_BH(SctpChecksumErrors);
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * This is the routine which IP calls when receiving an SCTP packet.
+ */
+int sctp_rcv(struct sk_buff *skb)
+{
+ struct sock *sk;
+ struct sctp_association *asoc;
+ struct sctp_endpoint *ep = NULL;
+ struct sctp_ep_common *rcvr;
+ struct sctp_transport *transport = NULL;
+ struct sctp_chunk *chunk;
+ struct sctphdr *sh;
+ union sctp_addr src;
+ union sctp_addr dest;
+ int family;
+ struct sctp_af *af;
+ int ret = 0;
+
+ if (skb->pkt_type!=PACKET_HOST)
+ goto discard_it;
+
+ SCTP_INC_STATS_BH(SctpInSCTPPacks);
+
+ sh = (struct sctphdr *) skb->h.raw;
+
+ /* Pull up the IP and SCTP headers. */
+ __skb_pull(skb, skb->h.raw - skb->data);
+ if (skb->len < sizeof(struct sctphdr))
+ goto discard_it;
+ if (sctp_rcv_checksum(skb) < 0)
+ goto discard_it;
+
+ skb_pull(skb, sizeof(struct sctphdr));
+
+ /* Make sure we at least have chunk headers worth of data left. */
+ if (skb->len < sizeof(struct sctp_chunkhdr))
+ goto discard_it;
+
+ family = ipver2af(skb->nh.iph->version);
+ af = sctp_get_af_specific(family);
+ if (unlikely(!af))
+ goto discard_it;
+
+ /* Initialize local addresses for lookups. */
+ af->from_skb(&src, skb, 1);
+ af->from_skb(&dest, skb, 0);
+
+ /* If the packet is to or from a non-unicast address,
+ * silently discard the packet.
+ *
+ * This is not clearly defined in the RFC except in section
+ * 8.4 - OOTB handling. However, based on the book "Stream Control
+ * Transmission Protocol" 2.1, "It is important to note that the
+ * IP address of an SCTP transport address must be a routable
+ * unicast address. In other words, IP multicast addresses and
+ * IP broadcast addresses cannot be used in an SCTP transport
+ * address."
+ */
+ if (!af->addr_valid(&src, NULL) || !af->addr_valid(&dest, NULL))
+ goto discard_it;
+
+ asoc = __sctp_rcv_lookup(skb, &src, &dest, &transport);
+
+ /*
+ * RFC 2960, 8.4 - Handle "Out of the blue" Packets.
+ * An SCTP packet is called an "out of the blue" (OOTB)
+ * packet if it is correctly formed, i.e., passed the
+ * receiver's checksum check, but the receiver is not
+ * able to identify the association to which this
+ * packet belongs.
+ */
+ if (!asoc) {
+ ep = __sctp_rcv_lookup_endpoint(&dest);
+ if (sctp_rcv_ootb(skb)) {
+ SCTP_INC_STATS_BH(SctpOutOfBlues);
+ goto discard_release;
+ }
+ }
+
+ /* Retrieve the common input handling substructure. */
+ rcvr = asoc ? &asoc->base : &ep->base;
+ sk = rcvr->sk;
+
+ if (!ipsec_sk_policy(sk, skb))
+ goto discard_release;
+
+ ret = sk_filter(sk, skb, 1);
+ if (ret)
+ goto discard_release;
+
+ /* Create an SCTP packet structure. */
+ chunk = sctp_chunkify(skb, asoc, sk);
+ if (!chunk) {
+ ret = -ENOMEM;
+ goto discard_release;
+ }
+
+ /* Remember what endpoint is to handle this packet. */
+ chunk->rcvr = rcvr;
+
+ /* Remember the SCTP header. */
+ chunk->sctp_hdr = sh;
+
+ /* Set the source and destination addresses of the incoming chunk. */
+ sctp_init_addrs(chunk, &src, &dest);
+
+ /* Remember where we came from. */
+ chunk->transport = transport;
+
+ /* Acquire access to the sock lock. Note: We are safe from other
+ * bottom halves on this lock, but a user may be in the lock too,
+ * so check if it is busy.
+ */
+ sctp_bh_lock_sock(sk);
+
+ if (sock_owned_by_user(sk))
+ sk_add_backlog(sk, (struct sk_buff *) chunk);
+ else
+ sctp_backlog_rcv(sk, (struct sk_buff *) chunk);
+
+ /* Release the sock and any reference counts we took in the
+ * lookup calls.
+ */
+ sctp_bh_unlock_sock(sk);
+ if (asoc)
+ sctp_association_put(asoc);
+ else
+ sctp_endpoint_put(ep);
+ sock_put(sk);
+ return ret;
+
+discard_it:
+ kfree_skb(skb);
+ return ret;
+
+discard_release:
+ /* Release any structures we may be holding. */
+ if (asoc) {
+ sock_put(asoc->base.sk);
+ sctp_association_put(asoc);
+ } else {
+ sock_put(ep->base.sk);
+ sctp_endpoint_put(ep);
+ }
+
+ goto discard_it;
+}
+
+/* Handle second half of inbound skb processing. If the sock was busy,
+ * we may have need to delay processing until later when the sock is
+ * released (on the backlog). If not busy, we call this routine
+ * directly from the bottom half.
+ */
+int sctp_backlog_rcv(struct sock *sk, struct sk_buff *skb)
+{
+ struct sctp_chunk *chunk;
+ struct sctp_inq *inqueue;
+
+ /* One day chunk will live inside the skb, but for
+ * now this works.
+ */
+ chunk = (struct sctp_chunk *) skb;
+ inqueue = &chunk->rcvr->inqueue;
+
+ sctp_inq_push(inqueue, chunk);
+ return 0;
+}
+
+/* Handle icmp frag needed error. */
+void sctp_icmp_frag_needed(struct sock *sk, struct sctp_association *asoc,
+ struct sctp_transport *t, __u32 pmtu)
+{
+ if (unlikely(pmtu < SCTP_DEFAULT_MINSEGMENT)) {
+ printk(KERN_WARNING "%s: Reported pmtu %d too low, "
+ "using default minimum of %d\n", __FUNCTION__, pmtu,
+ SCTP_DEFAULT_MINSEGMENT);
+ pmtu = SCTP_DEFAULT_MINSEGMENT;
+ }
+
+ if (!sock_owned_by_user(sk) && t && (t->pmtu != pmtu)) {
+ t->pmtu = pmtu;
+ sctp_assoc_sync_pmtu(asoc);
+ sctp_retransmit(&asoc->outqueue, t, SCTP_RTXR_PMTUD);
+ }
+}
+
+/*
+ * SCTP Implementer's Guide, 2.37 ICMP handling procedures
+ *
+ * ICMP8) If the ICMP code is a "Unrecognized next header type encountered"
+ * or a "Protocol Unreachable" treat this message as an abort
+ * with the T bit set.
+ *
+ * This function sends an event to the state machine, which will abort the
+ * association.
+ *
+ */
+void sctp_icmp_proto_unreachable(struct sock *sk,
+ struct sctp_endpoint *ep,
+ struct sctp_association *asoc,
+ struct sctp_transport *t)
+{
+ SCTP_DEBUG_PRINTK("%s\n", __FUNCTION__);
+
+ sctp_do_sm(SCTP_EVENT_T_OTHER,
+ SCTP_ST_OTHER(SCTP_EVENT_ICMP_PROTO_UNREACH),
+ asoc->state, asoc->ep, asoc, NULL,
+ GFP_ATOMIC);
+
+}
+
+/* Common lookup code for icmp/icmpv6 error handler. */
+struct sock *sctp_err_lookup(int family, struct sk_buff *skb,
+ struct sctphdr *sctphdr,
+ struct sctp_endpoint **epp,
+ struct sctp_association **app,
+ struct sctp_transport **tpp)
+{
+ union sctp_addr saddr;
+ union sctp_addr daddr;
+ struct sctp_af *af;
+ struct sock *sk = NULL;
+ struct sctp_endpoint *ep = NULL;
+ struct sctp_association *asoc = NULL;
+ struct sctp_transport *transport = NULL;
+
+ *app = NULL; *epp = NULL; *tpp = NULL;
+
+ af = sctp_get_af_specific(family);
+ if (unlikely(!af)) {
+ return NULL;
+ }
+
+ /* Initialize local addresses for lookups. */
+ af->from_skb(&saddr, skb, 1);
+ af->from_skb(&daddr, skb, 0);
+
+ /* Look for an association that matches the incoming ICMP error
+ * packet.
+ */
+ asoc = __sctp_lookup_association(&saddr, &daddr, &transport);
+ if (!asoc) {
+ /* If there is no matching association, see if it matches any
+ * endpoint. This may happen for an ICMP error generated in
+ * response to an INIT_ACK.
+ */
+ ep = __sctp_rcv_lookup_endpoint(&daddr);
+ if (!ep) {
+ return NULL;
+ }
+ }
+
+ if (asoc) {
+ sk = asoc->base.sk;
+
+ if (ntohl(sctphdr->vtag) != asoc->c.peer_vtag) {
+ ICMP_INC_STATS_BH(IcmpInErrors);
+ goto out;
+ }
+ } else
+ sk = ep->base.sk;
+
+ sctp_bh_lock_sock(sk);
+
+ /* If too many ICMPs get dropped on busy
+ * servers this needs to be solved differently.
+ */
+ if (sock_owned_by_user(sk))
+ NET_INC_STATS_BH(LockDroppedIcmps);
+
+ *epp = ep;
+ *app = asoc;
+ *tpp = transport;
+ return sk;
+
+out:
+ sock_put(sk);
+ if (asoc)
+ sctp_association_put(asoc);
+ if (ep)
+ sctp_endpoint_put(ep);
+ return NULL;
+}
+
+/* Common cleanup code for icmp/icmpv6 error handler. */
+void sctp_err_finish(struct sock *sk, struct sctp_endpoint *ep,
+ struct sctp_association *asoc)
+{
+ sctp_bh_unlock_sock(sk);
+ sock_put(sk);
+ if (asoc)
+ sctp_association_put(asoc);
+ if (ep)
+ sctp_endpoint_put(ep);
+}
+
+/*
+ * This routine is called by the ICMP module when it gets some
+ * sort of error condition. If err < 0 then the socket should
+ * be closed and the error returned to the user. If err > 0
+ * it's just the icmp type << 8 | icmp code. After adjustment
+ * header points to the first 8 bytes of the sctp header. We need
+ * to find the appropriate port.
+ *
+ * The locking strategy used here is very "optimistic". When
+ * someone else accesses the socket the ICMP is just dropped
+ * and for some paths there is no check at all.
+ * A more general error queue to queue errors for later handling
+ * is probably better.
+ *
+ */
+void sctp_v4_err(struct sk_buff *skb, __u32 info)
+{
+ struct iphdr *iph = (struct iphdr *)skb->data;
+ struct sctphdr *sh = (struct sctphdr *)(skb->data + (iph->ihl <<2));
+ int type = skb->h.icmph->type;
+ int code = skb->h.icmph->code;
+ struct sock *sk;
+ struct sctp_endpoint *ep;
+ struct sctp_association *asoc;
+ struct sctp_transport *transport;
+ struct inet_opt *inet;
+ char *saveip, *savesctp;
+ int err;
+
+ if (skb->len < ((iph->ihl << 2) + 8)) {
+ ICMP_INC_STATS_BH(IcmpInErrors);
+ return;
+ }
+
+ /* Fix up skb to look at the embedded net header. */
+ saveip = skb->nh.raw;
+ savesctp = skb->h.raw;
+ skb->nh.iph = iph;
+ skb->h.raw = (char *)sh;
+ sk = sctp_err_lookup(AF_INET, skb, sh, &ep, &asoc, &transport);
+ /* Put back, the original pointers. */
+ skb->nh.raw = saveip;
+ skb->h.raw = savesctp;
+ if (!sk) {
+ ICMP_INC_STATS_BH(IcmpInErrors);
+ return;
+ }
+ /* Warning: The sock lock is held. Remember to call
+ * sctp_err_finish!
+ */
+
+ switch (type) {
+ case ICMP_PARAMETERPROB:
+ err = EPROTO;
+ break;
+ case ICMP_DEST_UNREACH:
+ if (code > NR_ICMP_UNREACH)
+ goto out_unlock;
+
+ /* PMTU discovery (RFC1191) */
+ if (ICMP_FRAG_NEEDED == code) {
+ sctp_icmp_frag_needed(sk, asoc, transport, info);
+ goto out_unlock;
+ }
+ else {
+ if (ICMP_PROT_UNREACH == code) {
+ sctp_icmp_proto_unreachable(sk, ep, asoc,
+ transport);
+ goto out_unlock;
+ }
+ }
+ err = icmp_err_convert[code].errno;
+ break;
+ case ICMP_TIME_EXCEEDED:
+ /* Ignore any time exceeded errors due to fragment reassembly
+ * timeouts.
+ */
+ if (ICMP_EXC_FRAGTIME == code)
+ goto out_unlock;
+
+ err = EHOSTUNREACH;
+ break;
+ default:
+ goto out_unlock;
+ }
+
+ inet = inet_sk(sk);
+ if (!sock_owned_by_user(sk) && inet->recverr) {
+ sk->err = err;
+ sk->error_report(sk);
+ } else { /* Only an error on timeout */
+ sk->err_soft = err;
+ }
+
+out_unlock:
+ sctp_err_finish(sk, ep, asoc);
+}
+
+/*
+ * RFC 2960, 8.4 - Handle "Out of the blue" Packets.
+ *
+ * This function scans all the chunks in the OOTB packet to determine if
+ * the packet should be discarded right away. If a response might be needed
+ * for this packet, or, if further processing is possible, the packet will
+ * be queued to a proper inqueue for the next phase of handling.
+ *
+ * Output:
+ * Return 0 - If further processing is needed.
+ * Return 1 - If the packet can be discarded right away.
+ */
+int sctp_rcv_ootb(struct sk_buff *skb)
+{
+ sctp_chunkhdr_t *ch;
+ __u8 *ch_end;
+ sctp_errhdr_t *err;
+
+ ch = (sctp_chunkhdr_t *) skb->data;
+ ch_end = ((__u8 *) ch) + WORD_ROUND(ntohs(ch->length));
+
+ /* Scan through all the chunks in the packet. */
+ while (ch_end > (__u8 *)ch && ch_end < skb->tail) {
+
+ /* RFC 8.4, 2) If the OOTB packet contains an ABORT chunk, the
+ * receiver MUST silently discard the OOTB packet and take no
+ * further action.
+ */
+ if (SCTP_CID_ABORT == ch->type)
+ goto discard;
+
+ /* RFC 8.4, 6) If the packet contains a SHUTDOWN COMPLETE
+ * chunk, the receiver should silently discard the packet
+ * and take no further action.
+ */
+ if (SCTP_CID_SHUTDOWN_COMPLETE == ch->type)
+ goto discard;
+
+ /* RFC 8.4, 7) If the packet contains a "Stale cookie" ERROR
+ * or a COOKIE ACK the SCTP Packet should be silently
+ * discarded.
+ */
+ if (SCTP_CID_COOKIE_ACK == ch->type)
+ goto discard;
+
+ if (SCTP_CID_ERROR == ch->type) {
+ sctp_walk_errors(err, ch) {
+ if (SCTP_ERROR_STALE_COOKIE == err->cause)
+ goto discard;
+ }
+ }
+
+ ch = (sctp_chunkhdr_t *) ch_end;
+ ch_end = ((__u8 *) ch) + WORD_ROUND(ntohs(ch->length));
+ }
+
+ return 0;
+
+discard:
+ return 1;
+}
+
+/* Insert endpoint into the hash table. */
+static void __sctp_hash_endpoint(struct sctp_endpoint *ep)
+{
+ struct sctp_ep_common **epp;
+ struct sctp_ep_common *epb;
+ struct sctp_hashbucket *head;
+
+ epb = &ep->base;
+
+ epb->hashent = sctp_ep_hashfn(epb->bind_addr.port);
+ head = &sctp_ep_hashtable[epb->hashent];
+
+ sctp_write_lock(&head->lock);
+ epp = &head->chain;
+ epb->next = *epp;
+ if (epb->next)
+ (*epp)->pprev = &epb->next;
+ *epp = epb;
+ epb->pprev = epp;
+ sctp_write_unlock(&head->lock);
+}
+
+/* Add an endpoint to the hash. Local BH-safe. */
+void sctp_hash_endpoint(struct sctp_endpoint *ep)
+{
+ sctp_local_bh_disable();
+ __sctp_hash_endpoint(ep);
+ sctp_local_bh_enable();
+}
+
+/* Remove endpoint from the hash table. */
+static void __sctp_unhash_endpoint(struct sctp_endpoint *ep)
+{
+ struct sctp_hashbucket *head;
+ struct sctp_ep_common *epb;
+
+ epb = &ep->base;
+
+ epb->hashent = sctp_ep_hashfn(epb->bind_addr.port);
+
+ head = &sctp_ep_hashtable[epb->hashent];
+
+ sctp_write_lock(&head->lock);
+
+ if (epb->pprev) {
+ if (epb->next)
+ epb->next->pprev = epb->pprev;
+ *epb->pprev = epb->next;
+ epb->pprev = NULL;
+ }
+
+ sctp_write_unlock(&head->lock);
+}
+
+/* Remove endpoint from the hash. Local BH-safe. */
+void sctp_unhash_endpoint(struct sctp_endpoint *ep)
+{
+ sctp_local_bh_disable();
+ __sctp_unhash_endpoint(ep);
+ sctp_local_bh_enable();
+}
+
+/* Look up an endpoint. */
+static struct sctp_endpoint *__sctp_rcv_lookup_endpoint(const union sctp_addr *laddr)
+{
+ struct sctp_hashbucket *head;
+ struct sctp_ep_common *epb;
+ struct sctp_endpoint *ep;
+ int hash;
+
+ hash = sctp_ep_hashfn(laddr->v4.sin_port);
+ head = &sctp_ep_hashtable[hash];
+ read_lock(&head->lock);
+ for (epb = head->chain; epb; epb = epb->next) {
+ ep = sctp_ep(epb);
+ if (sctp_endpoint_is_match(ep, laddr))
+ goto hit;
+ }
+
+ ep = sctp_sk((sctp_get_ctl_sock()))->ep;
+ epb = &ep->base;
+
+hit:
+ sctp_endpoint_hold(ep);
+ sock_hold(epb->sk);
+ read_unlock(&head->lock);
+ return ep;
+}
+
+/* Insert association into the hash table. */
+static void __sctp_hash_established(struct sctp_association *asoc)
+{
+ struct sctp_ep_common **epp;
+ struct sctp_ep_common *epb;
+ struct sctp_hashbucket *head;
+
+ epb = &asoc->base;
+
+ /* Calculate which chain this entry will belong to. */
+ epb->hashent = sctp_assoc_hashfn(epb->bind_addr.port, asoc->peer.port);
+
+ head = &sctp_assoc_hashtable[epb->hashent];
+
+ sctp_write_lock(&head->lock);
+ epp = &head->chain;
+ epb->next = *epp;
+ if (epb->next)
+ (*epp)->pprev = &epb->next;
+ *epp = epb;
+ epb->pprev = epp;
+ sctp_write_unlock(&head->lock);
+}
+
+/* Add an association to the hash. Local BH-safe. */
+void sctp_hash_established(struct sctp_association *asoc)
+{
+ sctp_local_bh_disable();
+ __sctp_hash_established(asoc);
+ sctp_local_bh_enable();
+}
+
+/* Remove association from the hash table. */
+static void __sctp_unhash_established(struct sctp_association *asoc)
+{
+ struct sctp_hashbucket *head;
+ struct sctp_ep_common *epb;
+
+ epb = &asoc->base;
+
+ epb->hashent = sctp_assoc_hashfn(epb->bind_addr.port,
+ asoc->peer.port);
+
+ head = &sctp_assoc_hashtable[epb->hashent];
+
+ sctp_write_lock(&head->lock);
+
+ if (epb->pprev) {
+ if (epb->next)
+ epb->next->pprev = epb->pprev;
+ *epb->pprev = epb->next;
+ epb->pprev = NULL;
+ }
+
+ sctp_write_unlock(&head->lock);
+}
+
+/* Remove association from the hash table. Local BH-safe. */
+void sctp_unhash_established(struct sctp_association *asoc)
+{
+ sctp_local_bh_disable();
+ __sctp_unhash_established(asoc);
+ sctp_local_bh_enable();
+}
+
+/* Look up an association. */
+static struct sctp_association *__sctp_lookup_association(
+ const union sctp_addr *local,
+ const union sctp_addr *peer,
+ struct sctp_transport **pt)
+{
+ struct sctp_hashbucket *head;
+ struct sctp_ep_common *epb;
+ struct sctp_association *asoc;
+ struct sctp_transport *transport;
+ int hash;
+
+ /* Optimize here for direct hit, only listening connections can
+ * have wildcards anyways.
+ */
+ hash = sctp_assoc_hashfn(local->v4.sin_port, peer->v4.sin_port);
+ head = &sctp_assoc_hashtable[hash];
+ read_lock(&head->lock);
+ for (epb = head->chain; epb; epb = epb->next) {
+ asoc = sctp_assoc(epb);
+ transport = sctp_assoc_is_match(asoc, local, peer);
+ if (transport)
+ goto hit;
+ }
+
+ read_unlock(&head->lock);
+
+ return NULL;
+
+hit:
+ *pt = transport;
+ sctp_association_hold(asoc);
+ sock_hold(epb->sk);
+ read_unlock(&head->lock);
+ return asoc;
+}
+
+/* Look up an association. BH-safe. */
+SCTP_STATIC
+struct sctp_association *sctp_lookup_association(const union sctp_addr *laddr,
+ const union sctp_addr *paddr,
+ struct sctp_transport **transportp)
+{
+ struct sctp_association *asoc;
+
+ sctp_local_bh_disable();
+ asoc = __sctp_lookup_association(laddr, paddr, transportp);
+ sctp_local_bh_enable();
+
+ return asoc;
+}
+
+/* Is there an association matching the given local and peer addresses? */
+int sctp_has_association(const union sctp_addr *laddr,
+ const union sctp_addr *paddr)
+{
+ struct sctp_association *asoc;
+ struct sctp_transport *transport;
+
+ if ((asoc = sctp_lookup_association(laddr, paddr, &transport))) {
+ sock_put(asoc->base.sk);
+ sctp_association_put(asoc);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * SCTP Implementors Guide, 2.18 Handling of address
+ * parameters within the INIT or INIT-ACK.
+ *
+ * D) When searching for a matching TCB upon reception of an INIT
+ * or INIT-ACK chunk the receiver SHOULD use not only the
+ * source address of the packet (containing the INIT or
+ * INIT-ACK) but the receiver SHOULD also use all valid
+ * address parameters contained within the chunk.
+ *
+ * 2.18.3 Solution description
+ *
+ * This new text clearly specifies to an implementor the need
+ * to look within the INIT or INIT-ACK. Any implementation that
+ * does not do this, may not be able to establish associations
+ * in certain circumstances.
+ *
+ */
+static struct sctp_association *__sctp_rcv_init_lookup(struct sk_buff *skb,
+ const union sctp_addr *laddr, struct sctp_transport **transportp)
+{
+ struct sctp_association *asoc;
+ union sctp_addr addr;
+ union sctp_addr *paddr = &addr;
+ struct sctphdr *sh = (struct sctphdr *) skb->h.raw;
+ sctp_chunkhdr_t *ch;
+ union sctp_params params;
+ sctp_init_chunk_t *init;
+ struct sctp_transport *transport;
+ struct sctp_af *af;
+
+ ch = (sctp_chunkhdr_t *) skb->data;
+
+ /* If this is INIT/INIT-ACK look inside the chunk too. */
+ switch (ch->type) {
+ case SCTP_CID_INIT:
+ case SCTP_CID_INIT_ACK:
+ break;
+ default:
+ return NULL;
+ }
+
+ /* The code below will attempt to walk the chunk and extract
+ * parameter information. Before we do that, we need to verify
+ * that the chunk length doesn't cause overflow. Otherwise, we'll
+ * walk off the end.
+ */
+ if (WORD_ROUND(ntohs(ch->length)) > skb->len)
+ return NULL;
+
+ /*
+ * This code will NOT touch anything inside the chunk--it is
+ * strictly READ-ONLY.
+ *
+ * RFC 2960 3 SCTP packet Format
+ *
+ * Multiple chunks can be bundled into one SCTP packet up to
+ * the MTU size, except for the INIT, INIT ACK, and SHUTDOWN
+ * COMPLETE chunks. These chunks MUST NOT be bundled with any
+ * other chunk in a packet. See Section 6.10 for more details
+ * on chunk bundling.
+ */
+
+ /* Find the start of the TLVs and the end of the chunk. This is
+ * the region we search for address parameters.
+ */
+ init = (sctp_init_chunk_t *)skb->data;
+
+ /* Walk the parameters looking for embedded addresses. */
+ sctp_walk_params(params, init, init_hdr.params) {
+
+ /* Note: Ignoring hostname addresses. */
+ af = sctp_get_af_specific(param_type2af(params.p->type));
+ if (!af)
+ continue;
+
+ af->from_addr_param(paddr, params.addr, ntohs(sh->source), 0);
+
+ asoc = __sctp_lookup_association(laddr, paddr, &transport);
+ if (asoc)
+ return asoc;
+ }
+
+ return NULL;
+}
+
+/* Lookup an association for an inbound skb. */
+static struct sctp_association *__sctp_rcv_lookup(struct sk_buff *skb,
+ const union sctp_addr *paddr,
+ const union sctp_addr *laddr,
+ struct sctp_transport **transportp)
+{
+ struct sctp_association *asoc;
+
+ asoc = __sctp_lookup_association(laddr, paddr, transportp);
+
+ /* Further lookup for INIT/INIT-ACK packets.
+ * SCTP Implementors Guide, 2.18 Handling of address
+ * parameters within the INIT or INIT-ACK.
+ */
+ if (!asoc)
+ asoc = __sctp_rcv_init_lookup(skb, laddr, transportp);
+
+ return asoc;
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/inqueue.c b/uClinux-2.4.31-uc0/net/sctp/inqueue.c
new file mode 100644
index 0000000..816455b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/inqueue.c
@@ -0,0 +1,208 @@
+/* SCTP kernel reference Implementation
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2002 International Business Machines, Corp.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * These functions are the methods for accessing the SCTP inqueue.
+ *
+ * An SCTP inqueue is a queue into which you push SCTP packets
+ * (which might be bundles or fragments of chunks) and out of which you
+ * pop SCTP whole chunks.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+#include <linux/interrupt.h>
+
+/* Initialize an SCTP inqueue. */
+void sctp_inq_init(struct sctp_inq *queue)
+{
+ skb_queue_head_init(&queue->in);
+ queue->in_progress = NULL;
+
+ /* Create a task for delivering data. */
+ INIT_LIST_HEAD(&queue->immediate.list);
+ queue->immediate.sync = 0;
+ queue->immediate.routine = NULL;
+ queue->immediate.data = NULL;
+
+ queue->malloced = 0;
+}
+
+/* Release the memory associated with an SCTP inqueue. */
+void sctp_inq_free(struct sctp_inq *queue)
+{
+ struct sctp_chunk *chunk;
+
+ /* Empty the queue. */
+ while ((chunk = (struct sctp_chunk *) skb_dequeue(&queue->in)) != NULL)
+ sctp_chunk_free(chunk);
+
+ /* If there is a packet which is currently being worked on,
+ * free it as well.
+ */
+ if (queue->in_progress)
+ sctp_chunk_free(queue->in_progress);
+
+ if (queue->malloced) {
+ /* Dump the master memory segment. */
+ kfree(queue);
+ }
+}
+
+/* Put a new packet in an SCTP inqueue.
+ * We assume that packet->sctp_hdr is set and in host byte order.
+ */
+void sctp_inq_push(struct sctp_inq *q, struct sctp_chunk *packet)
+{
+ /* Directly call the packet handling routine. */
+
+ /* We are now calling this either from the soft interrupt
+ * or from the backlog processing.
+ * Eventually, we should clean up inqueue to not rely
+ * on the BH related data structures.
+ */
+ skb_queue_tail(&(q->in), (struct sk_buff *) packet);
+ q->immediate.routine(q->immediate.data);
+}
+
+/* Extract a chunk from an SCTP inqueue.
+ *
+ * WARNING: If you need to put the chunk on another queue, you need to
+ * make a shallow copy (clone) of it.
+ */
+struct sctp_chunk *sctp_inq_pop(struct sctp_inq *queue)
+{
+ struct sctp_chunk *chunk;
+ sctp_chunkhdr_t *ch = NULL;
+
+ /* The assumption is that we are safe to process the chunks
+ * at this time.
+ */
+
+ if ((chunk = queue->in_progress)) {
+ /* There is a packet that we have been working on.
+ * Any post processing work to do before we move on?
+ */
+ if (chunk->singleton ||
+ chunk->end_of_packet ||
+ chunk->pdiscard) {
+ sctp_chunk_free(chunk);
+ chunk = queue->in_progress = NULL;
+ } else {
+ /* Nothing to do. Next chunk in the packet, please. */
+ ch = (sctp_chunkhdr_t *) chunk->chunk_end;
+
+ /* Force chunk->skb->data to chunk->chunk_end. */
+ skb_pull(chunk->skb,
+ chunk->chunk_end - chunk->skb->data);
+ }
+ }
+
+ /* Do we need to take the next packet out of the queue to process? */
+ if (!chunk) {
+ /* Is the queue empty? */
+ if (skb_queue_empty(&queue->in))
+ return NULL;
+
+ chunk = queue->in_progress =
+ (struct sctp_chunk *) skb_dequeue(&queue->in);
+
+ /* This is the first chunk in the packet. */
+ chunk->singleton = 1;
+ ch = (sctp_chunkhdr_t *) chunk->skb->data;
+ }
+
+ chunk->chunk_hdr = ch;
+ chunk->chunk_end = ((__u8 *)ch) + WORD_ROUND(ntohs(ch->length));
+ /* In the unlikely case of an IP reassembly, the skb could be
+ * non-linear. If so, update chunk_end so that it doesn't go past
+ * the skb->tail.
+ */
+ if (unlikely(skb_is_nonlinear(chunk->skb))) {
+ if (chunk->chunk_end > chunk->skb->tail)
+ chunk->chunk_end = chunk->skb->tail;
+ }
+ skb_pull(chunk->skb, sizeof(sctp_chunkhdr_t));
+ chunk->subh.v = NULL; /* Subheader is no longer valid. */
+
+ if (chunk->chunk_end < chunk->skb->tail) {
+ /* This is not a singleton */
+ chunk->singleton = 0;
+ } else if (chunk->chunk_end > chunk->skb->tail) {
+ /* RFC 2960, Section 6.10 Bundling
+ *
+ * Partial chunks MUST NOT be placed in an SCTP packet.
+ * If the receiver detects a partial chunk, it MUST drop
+ * the chunk.
+ *
+ * Since the end of the chunk is past the end of our buffer
+ * (which contains the whole packet, we can freely discard
+ * the whole packet.
+ */
+ sctp_chunk_free(chunk);
+ chunk = queue->in_progress = NULL;
+
+ return NULL;
+ } else {
+ /* We are at the end of the packet, so mark the chunk
+ * in case we need to send a SACK.
+ */
+ chunk->end_of_packet = 1;
+ }
+
+ SCTP_DEBUG_PRINTK("+++sctp_inq_pop+++ chunk %p[%s],"
+ " length %d, skb->len %d\n",chunk,
+ sctp_cname(SCTP_ST_CHUNK(chunk->chunk_hdr->type)),
+ ntohs(chunk->chunk_hdr->length), chunk->skb->len);
+ return chunk;
+}
+
+/* Set a top-half handler.
+ *
+ * Originally, we the top-half handler was scheduled as a BH. We now
+ * call the handler directly in sctp_inq_push() at a time that
+ * we know we are lock safe.
+ * The intent is that this routine will pull stuff out of the
+ * inqueue and process it.
+ */
+void sctp_inq_set_th_handler(struct sctp_inq *q,
+ void (*callback)(void *), void *arg)
+{
+ q->immediate.routine = callback;
+ q->immediate.data = arg;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/sctp/ipv6.c b/uClinux-2.4.31-uc0/net/sctp/ipv6.c
new file mode 100644
index 0000000..5b48c71
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/ipv6.c
@@ -0,0 +1,1008 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2002, 2004
+ * Copyright (c) 2001 Nokia, Inc.
+ * Copyright (c) 2001 La Monte H.P. Yarroll
+ * Copyright (c) 2002-2003 Intel Corp.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * SCTP over IPv6.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * Le Yanqun <yanqun.le@nokia.com>
+ * Hui Huang <hui.huang@nokia.com>
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Ardelle Fan <ardelle.fan@intel.com>
+ *
+ * Based on:
+ * linux/net/ipv6/tcp_ipv6.c
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/sched.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/netdevice.h>
+#include <linux/init.h>
+#include <linux/ipsec.h>
+
+#include <linux/ipv6.h>
+#include <linux/icmpv6.h>
+#include <linux/random.h>
+#include <linux/seq_file.h>
+
+#include <net/protocol.h>
+#include <net/tcp.h>
+#include <net/ndisc.h>
+#include <net/ipv6.h>
+#include <net/transp_v6.h>
+#include <net/addrconf.h>
+#include <net/ip6_route.h>
+#include <net/inet_common.h>
+#include <net/inet_ecn.h>
+#include <net/sctp/sctp.h>
+
+#include <asm/uaccess.h>
+
+extern int sctp_inetaddr_event(struct notifier_block *, unsigned long, void *);
+static struct notifier_block sctp_inet6addr_notifier = {
+ .notifier_call = sctp_inetaddr_event,
+};
+
+/* FIXME: This macro needs to be moved to a common header file. */
+#define NIP6(addr) \
+ ntohs((addr)->s6_addr16[0]), \
+ ntohs((addr)->s6_addr16[1]), \
+ ntohs((addr)->s6_addr16[2]), \
+ ntohs((addr)->s6_addr16[3]), \
+ ntohs((addr)->s6_addr16[4]), \
+ ntohs((addr)->s6_addr16[5]), \
+ ntohs((addr)->s6_addr16[6]), \
+ ntohs((addr)->s6_addr16[7])
+
+/* ICMP error handler. */
+SCTP_STATIC void sctp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
+ int type, int code, int offset, __u32 info)
+{
+ struct inet6_dev *idev;
+ struct ipv6hdr *iph = (struct ipv6hdr *)skb->data;
+ struct sctphdr *sh = (struct sctphdr *)(skb->data + offset);
+ struct sock *sk;
+ struct sctp_endpoint *ep;
+ struct sctp_association *asoc;
+ struct sctp_transport *transport;
+ struct ipv6_pinfo *np;
+ char *saveip, *savesctp;
+ int err;
+
+ idev = in6_dev_get(skb->dev);
+
+ /* Fix up skb to look at the embedded net header. */
+ saveip = skb->nh.raw;
+ savesctp = skb->h.raw;
+ skb->nh.ipv6h = iph;
+ skb->h.raw = (char *)sh;
+ sk = sctp_err_lookup(AF_INET6, skb, sh, &ep, &asoc, &transport);
+ /* Put back, the original pointers. */
+ skb->nh.raw = saveip;
+ skb->h.raw = savesctp;
+ if (!sk) {
+ ICMP6_INC_STATS_BH(idev, Icmp6InErrors);
+ goto out;
+ }
+
+ /* Warning: The sock lock is held. Remember to call
+ * sctp_err_finish!
+ */
+
+ switch (type) {
+ case ICMPV6_PKT_TOOBIG:
+ sctp_icmp_frag_needed(sk, asoc, transport, ntohl(info));
+ goto out_unlock;
+ case ICMPV6_PARAMPROB:
+ if (ICMPV6_UNK_NEXTHDR == code) {
+ sctp_icmp_proto_unreachable(sk, ep, asoc, transport);
+ goto out_unlock;
+ }
+ break;
+ default:
+ break;
+ }
+
+ np = inet6_sk(sk);
+ icmpv6_err_convert(type, code, &err);
+ if (!sock_owned_by_user(sk) && np->recverr) {
+ sk->err = err;
+ sk->error_report(sk);
+ } else { /* Only an error on timeout */
+ sk->err_soft = err;
+ }
+
+out_unlock:
+ sctp_err_finish(sk, ep, asoc);
+out:
+ if (likely(idev != NULL))
+ in6_dev_put(idev);
+}
+
+/* Based on tcp_v6_xmit() in tcp_ipv6.c. */
+static int sctp_v6_xmit(struct sk_buff *skb, struct sctp_transport *transport,
+ int ipfragok)
+{
+ struct sock *sk = skb->sk;
+ struct ipv6_pinfo *np = inet6_sk(sk);
+ struct flowi fl;
+
+ memset(&fl, 0, sizeof(fl));
+
+ fl.proto = sk->protocol;
+
+ /* Fill in the dest address from the route entry passed with the skb
+ * and the source address from the transport.
+ */
+ fl.fl6_dst = &transport->ipaddr.v6.sin6_addr;
+ fl.fl6_src = &transport->saddr.v6.sin6_addr;
+
+ fl.fl6_flowlabel = np->flow_label;
+ IP6_ECN_flow_xmit(sk, fl.fl6_flowlabel);
+ if (ipv6_addr_type(fl.fl6_src) & IPV6_ADDR_LINKLOCAL)
+ fl.oif = transport->saddr.v6.sin6_scope_id;
+ else
+ fl.oif = sk->bound_dev_if;
+ fl.uli_u.ports.sport = sk->sport;
+ fl.uli_u.ports.dport = transport->ipaddr.v6.sin6_port;
+
+ if (np->opt && np->opt->srcrt) {
+ struct rt0_hdr *rt0 = (struct rt0_hdr *) np->opt->srcrt;
+ fl.fl6_dst = rt0->addr;
+ }
+
+ SCTP_DEBUG_PRINTK("%s: skb:%p, len:%d, "
+ "src:%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x "
+ "dst:%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
+ __FUNCTION__, skb, skb->len,
+ NIP6(fl.fl6_src), NIP6(fl.fl6_dst));
+
+ SCTP_INC_STATS(SctpOutSCTPPacks);
+
+ return ip6_xmit(sk, skb, &fl, np->opt);
+}
+
+/* Returns the dst cache entry for the given source and destination ip
+ * addresses.
+ */
+static struct dst_entry *sctp_v6_get_dst(struct sctp_association *asoc,
+ union sctp_addr *daddr,
+ union sctp_addr *saddr)
+{
+ struct dst_entry *dst;
+ struct flowi fl;
+
+ memset(&fl, 0, sizeof(fl));
+ fl.fl6_dst = &daddr->v6.sin6_addr;
+ if (ipv6_addr_type(&daddr->v6.sin6_addr) & IPV6_ADDR_LINKLOCAL)
+ fl.oif = daddr->v6.sin6_scope_id;
+
+
+ SCTP_DEBUG_PRINTK("%s: DST=%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x ",
+ __FUNCTION__, NIP6(fl.fl6_dst));
+
+ if (saddr) {
+ fl.fl6_src = &saddr->v6.sin6_addr;
+ SCTP_DEBUG_PRINTK(
+ "SRC=%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x - ",
+ NIP6(fl.fl6_src));
+ }
+
+ dst = ip6_route_output(NULL, &fl);
+ if (dst) {
+ struct rt6_info *rt;
+ rt = (struct rt6_info *)dst;
+ SCTP_DEBUG_PRINTK(
+ "rt6_dst:%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x "
+ "rt6_src:%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
+ NIP6(&rt->rt6i_dst.addr), NIP6(&rt->rt6i_src.addr));
+ } else {
+ SCTP_DEBUG_PRINTK("NO ROUTE\n");
+ }
+
+ return dst;
+}
+
+/* Returns the number of consecutive initial bits that match in the 2 ipv6
+ * addresses.
+ */
+static inline int sctp_v6_addr_match_len(union sctp_addr *s1,
+ union sctp_addr *s2)
+{
+ struct in6_addr *a1 = &s1->v6.sin6_addr;
+ struct in6_addr *a2 = &s2->v6.sin6_addr;
+ int i, j;
+
+ for (i = 0; i < 4 ; i++) {
+ __u32 a1xora2;
+
+ a1xora2 = a1->s6_addr32[i] ^ a2->s6_addr32[i];
+
+ if ((j = fls(ntohl(a1xora2))))
+ return (i * 32 + 32 - j);
+ }
+
+ return (i*32);
+}
+
+/* Fills in the source address(saddr) based on the destination address(daddr)
+ * and asoc's bind address list.
+ */
+static void sctp_v6_get_saddr(struct sctp_association *asoc,
+ struct dst_entry *dst,
+ union sctp_addr *daddr,
+ union sctp_addr *saddr)
+{
+ struct sctp_bind_addr *bp;
+ rwlock_t *addr_lock;
+ struct sctp_sockaddr_entry *laddr;
+ struct list_head *pos;
+ sctp_scope_t scope;
+ union sctp_addr *baddr = NULL;
+ __u8 matchlen = 0;
+ __u8 bmatchlen;
+
+ SCTP_DEBUG_PRINTK("%s: asoc:%p dst:%p "
+ "daddr:%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x ",
+ __FUNCTION__, asoc, dst, NIP6(&daddr->v6.sin6_addr));
+
+ if (!asoc) {
+ /* XXX: we need to inherit tp->use_tempaddr */
+ ipv6_get_saddr(dst, &daddr->v6.sin6_addr,&saddr->v6.sin6_addr, 0);
+ SCTP_DEBUG_PRINTK("saddr from ipv6_get_saddr: "
+ "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
+ NIP6(&saddr->v6.sin6_addr));
+ return;
+ }
+
+ scope = sctp_scope(daddr);
+
+ bp = &asoc->base.bind_addr;
+ addr_lock = &asoc->base.addr_lock;
+
+ /* Go through the bind address list and find the best source address
+ * that matches the scope of the destination address.
+ */
+ sctp_read_lock(addr_lock);
+ list_for_each(pos, &bp->address_list) {
+ laddr = list_entry(pos, struct sctp_sockaddr_entry, list);
+ if ((laddr->a.sa.sa_family == AF_INET6) &&
+ (scope <= sctp_scope(&laddr->a))) {
+ bmatchlen = sctp_v6_addr_match_len(daddr, &laddr->a);
+ if (!baddr || (matchlen < bmatchlen)) {
+ baddr = &laddr->a;
+ matchlen = bmatchlen;
+ }
+ }
+ }
+
+ if (baddr) {
+ memcpy(saddr, baddr, sizeof(union sctp_addr));
+ SCTP_DEBUG_PRINTK("saddr: "
+ "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
+ NIP6(&saddr->v6.sin6_addr));
+ } else {
+ printk(KERN_ERR "%s: asoc:%p Could not find a valid source "
+ "address for the "
+ "dest:%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
+ __FUNCTION__, asoc, NIP6(&daddr->v6.sin6_addr));
+ }
+
+ sctp_read_unlock(addr_lock);
+}
+
+/* Make a copy of all potential local addresses. */
+static void sctp_v6_copy_addrlist(struct list_head *addrlist,
+ struct net_device *dev)
+{
+ struct inet6_dev *in6_dev;
+ struct inet6_ifaddr *ifp;
+ struct sctp_sockaddr_entry *addr;
+
+ read_lock(&addrconf_lock);
+ if ((in6_dev = __in6_dev_get(dev)) == NULL) {
+ read_unlock(&addrconf_lock);
+ return;
+ }
+
+ read_lock(&in6_dev->lock);
+ for (ifp = in6_dev->addr_list; ifp; ifp = ifp->if_next) {
+ /* Add the address to the local list. */
+ addr = t_new(struct sctp_sockaddr_entry, GFP_ATOMIC);
+ if (addr) {
+ addr->a.v6.sin6_family = AF_INET6;
+ addr->a.v6.sin6_port = 0;
+ addr->a.v6.sin6_addr = ifp->addr;
+ addr->a.v6.sin6_scope_id = dev->ifindex;
+ INIT_LIST_HEAD(&addr->list);
+ list_add_tail(&addr->list, addrlist);
+ }
+ }
+
+ read_unlock(&in6_dev->lock);
+ read_unlock(&addrconf_lock);
+}
+
+/* Initialize a sockaddr_storage from in incoming skb. */
+static void sctp_v6_from_skb(union sctp_addr *addr,struct sk_buff *skb,
+ int is_saddr)
+{
+ void *from;
+ __u16 *port;
+ struct sctphdr *sh;
+
+ port = &addr->v6.sin6_port;
+ addr->v6.sin6_family = AF_INET6;
+ addr->v6.sin6_flowinfo = 0; /* FIXME */
+ addr->v6.sin6_scope_id = ((struct inet6_skb_parm *)skb->cb)->iif;
+
+ sh = (struct sctphdr *) skb->h.raw;
+ if (is_saddr) {
+ *port = ntohs(sh->source);
+ from = &skb->nh.ipv6h->saddr;
+ } else {
+ *port = ntohs(sh->dest);
+ from = &skb->nh.ipv6h->daddr;
+ }
+ ipv6_addr_copy(&addr->v6.sin6_addr, from);
+}
+
+/* Initialize an sctp_addr from a socket. */
+static void sctp_v6_from_sk(union sctp_addr *addr, struct sock *sk)
+{
+ addr->v6.sin6_family = AF_INET6;
+ addr->v6.sin6_port = sk->num;
+ addr->v6.sin6_addr = inet6_sk(sk)->rcv_saddr;
+}
+
+/* Initialize sk->sk_rcv_saddr from sctp_addr. */
+static void sctp_v6_to_sk_saddr(union sctp_addr *addr, struct sock *sk)
+{
+ if (addr->sa.sa_family == AF_INET && sctp_sk(sk)->v4mapped) {
+ inet6_sk(sk)->rcv_saddr.s6_addr32[0] = 0;
+ inet6_sk(sk)->rcv_saddr.s6_addr32[1] = 0;
+ inet6_sk(sk)->rcv_saddr.s6_addr32[2] = htonl(0x0000ffff);
+ inet6_sk(sk)->rcv_saddr.s6_addr32[3] =
+ addr->v4.sin_addr.s_addr;
+ } else {
+ inet6_sk(sk)->rcv_saddr = addr->v6.sin6_addr;
+ }
+}
+
+/* Initialize sk->sk_daddr from sctp_addr. */
+static void sctp_v6_to_sk_daddr(union sctp_addr *addr, struct sock *sk)
+{
+ if (addr->sa.sa_family == AF_INET && sctp_sk(sk)->v4mapped) {
+ inet6_sk(sk)->daddr.s6_addr32[0] = 0;
+ inet6_sk(sk)->daddr.s6_addr32[1] = 0;
+ inet6_sk(sk)->daddr.s6_addr32[2] = htonl(0x0000ffff);
+ inet6_sk(sk)->daddr.s6_addr32[3] = addr->v4.sin_addr.s_addr;
+ } else {
+ inet6_sk(sk)->daddr = addr->v6.sin6_addr;
+ }
+}
+
+/* Initialize a sctp_addr from an address parameter. */
+static void sctp_v6_from_addr_param(union sctp_addr *addr,
+ union sctp_addr_param *param,
+ __u16 port, int iif)
+{
+ addr->v6.sin6_family = AF_INET6;
+ addr->v6.sin6_port = port;
+ addr->v6.sin6_flowinfo = 0; /* BUG */
+ ipv6_addr_copy(&addr->v6.sin6_addr, &param->v6.addr);
+ addr->v6.sin6_scope_id = iif;
+}
+
+/* Initialize an address parameter from a sctp_addr and return the length
+ * of the address parameter.
+ */
+static int sctp_v6_to_addr_param(const union sctp_addr *addr,
+ union sctp_addr_param *param)
+{
+ int length = sizeof(sctp_ipv6addr_param_t);
+
+ param->v6.param_hdr.type = SCTP_PARAM_IPV6_ADDRESS;
+ param->v6.param_hdr.length = ntohs(length);
+ ipv6_addr_copy(&param->v6.addr, &addr->v6.sin6_addr);
+
+ return length;
+}
+
+/* Initialize a sctp_addr from a dst_entry. */
+static void sctp_v6_dst_saddr(union sctp_addr *addr, struct dst_entry *dst,
+ unsigned short port)
+{
+ struct rt6_info *rt = (struct rt6_info *)dst;
+ addr->sa.sa_family = AF_INET6;
+ addr->v6.sin6_port = port;
+ ipv6_addr_copy(&addr->v6.sin6_addr, &rt->rt6i_src.addr);
+}
+
+/* Compare addresses exactly.
+ * v4-mapped-v6 is also in consideration.
+ */
+static int sctp_v6_cmp_addr(const union sctp_addr *addr1,
+ const union sctp_addr *addr2)
+{
+ if (addr1->sa.sa_family != addr2->sa.sa_family) {
+ if (addr1->sa.sa_family == AF_INET &&
+ addr2->sa.sa_family == AF_INET6 &&
+ IPV6_ADDR_MAPPED == ipv6_addr_type(&addr2->v6.sin6_addr)) {
+ if (addr2->v6.sin6_port == addr1->v4.sin_port &&
+ addr2->v6.sin6_addr.s6_addr32[3] ==
+ addr1->v4.sin_addr.s_addr)
+ return 1;
+ }
+ if (addr2->sa.sa_family == AF_INET &&
+ addr1->sa.sa_family == AF_INET6 &&
+ IPV6_ADDR_MAPPED == ipv6_addr_type(&addr1->v6.sin6_addr)) {
+ if (addr1->v6.sin6_port == addr2->v4.sin_port &&
+ addr1->v6.sin6_addr.s6_addr32[3] ==
+ addr2->v4.sin_addr.s_addr)
+ return 1;
+ }
+ return 0;
+ }
+ if (ipv6_addr_cmp(&addr1->v6.sin6_addr, &addr2->v6.sin6_addr))
+ return 0;
+ /* If this is a linklocal address, compare the scope_id. */
+ if (ipv6_addr_type(&addr1->v6.sin6_addr) & IPV6_ADDR_LINKLOCAL) {
+ if (addr1->v6.sin6_scope_id && addr2->v6.sin6_scope_id &&
+ (addr1->v6.sin6_scope_id != addr2->v6.sin6_scope_id)) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/* Initialize addr struct to INADDR_ANY. */
+static void sctp_v6_inaddr_any(union sctp_addr *addr, unsigned short port)
+{
+ memset(addr, 0x00, sizeof(union sctp_addr));
+ addr->v6.sin6_family = AF_INET6;
+ addr->v6.sin6_port = port;
+}
+
+/* Is this a wildcard address? */
+static int sctp_v6_is_any(const union sctp_addr *addr)
+{
+ int type;
+ type = ipv6_addr_type((struct in6_addr *)&addr->v6.sin6_addr);
+ return IPV6_ADDR_ANY == type;
+}
+
+/* Should this be available for binding? */
+static int sctp_v6_available(union sctp_addr *addr, struct sctp_opt *sp)
+{
+ int type;
+ struct in6_addr *in6 = (struct in6_addr *)&addr->v6.sin6_addr;
+
+ type = ipv6_addr_type(in6);
+ if (IPV6_ADDR_ANY == type)
+ return 1;
+ if (type == IPV6_ADDR_MAPPED) {
+ if (sp && !sp->v4mapped)
+ return 0;
+ if (sp && ipv6_only_sock(sctp_opt2sk(sp)))
+ return 0;
+ sctp_v6_map_v4(addr);
+ return sctp_get_af_specific(AF_INET)->available(addr, sp);
+ }
+ if (!(type & IPV6_ADDR_UNICAST))
+ return 0;
+
+ return ipv6_chk_addr(in6, NULL);
+}
+
+/* This function checks if the address is a valid address to be used for
+ * SCTP.
+ *
+ * Output:
+ * Return 0 - If the address is a non-unicast or an illegal address.
+ * Return 1 - If the address is a unicast.
+ */
+static int sctp_v6_addr_valid(union sctp_addr *addr, struct sctp_opt *sp)
+{
+ int ret = ipv6_addr_type(&addr->v6.sin6_addr);
+
+ /* Support v4-mapped-v6 address. */
+ if (ret == IPV6_ADDR_MAPPED) {
+ /* Note: This routine is used in input, so v4-mapped-v6
+ * are disallowed here when there is no sctp_opt.
+ */
+ if (!sp || !sp->v4mapped)
+ return 0;
+ if (sp && ipv6_only_sock(sctp_opt2sk(sp)))
+ return 0;
+ sctp_v6_map_v4(addr);
+ return sctp_get_af_specific(AF_INET)->addr_valid(addr, sp);
+ }
+
+ /* Is this a non-unicast address */
+ if (!(ret & IPV6_ADDR_UNICAST))
+ return 0;
+
+ return 1;
+}
+
+/* What is the scope of 'addr'? */
+static sctp_scope_t sctp_v6_scope(union sctp_addr *addr)
+{
+ int v6scope;
+ sctp_scope_t retval;
+
+ /* The IPv6 scope is really a set of bit fields.
+ * See IFA_* in <net/if_inet6.h>. Map to a generic SCTP scope.
+ */
+
+ v6scope = ipv6_addr_scope(&addr->v6.sin6_addr);
+ switch (v6scope) {
+ case IFA_HOST:
+ retval = SCTP_SCOPE_LOOPBACK;
+ break;
+ case IFA_LINK:
+ retval = SCTP_SCOPE_LINK;
+ break;
+ case IFA_SITE:
+ retval = SCTP_SCOPE_PRIVATE;
+ break;
+ default:
+ retval = SCTP_SCOPE_GLOBAL;
+ break;
+ };
+
+ return retval;
+}
+
+/* Create and initialize a new sk for the socket to be returned by accept(). */
+static struct sock *sctp_v6_create_accept_sk(struct sock *sk,
+ struct sctp_association *asoc)
+{
+ struct sock *newsk;
+ struct inet_opt *newinet;
+ struct ipv6_pinfo *newnp, *np = inet6_sk(sk);
+
+ newsk = sk_alloc(PF_INET6, GFP_KERNEL, sizeof(struct sock));
+ if (!newsk)
+ goto out;
+
+ sock_init_data(NULL, newsk);
+ sk_set_owner(newsk, THIS_MODULE);
+
+ newsk->type = SOCK_STREAM;
+
+ newsk->prot = sk->prot;
+ newsk->no_check = sk->no_check;
+ newsk->reuse = sk->reuse;
+
+ newsk->destruct = inet_sock_destruct;
+ newsk->zapped = 0;
+ newsk->family = PF_INET6;
+ newsk->protocol = IPPROTO_SCTP;
+ newsk->backlog_rcv = sk->prot->backlog_rcv;
+ newsk->shutdown = sk->shutdown;
+
+ newinet = inet_sk(newsk);
+ newnp = inet6_sk(newsk);
+
+ memcpy(newnp, np, sizeof(struct ipv6_pinfo));
+
+ /* Initialize sk's sport, dport, rcv_saddr and daddr for getsockname()
+ * and getpeername().
+ */
+ newsk->sport = sk->sport;
+ newsk->saddr = sk->saddr;
+ newnp->rcv_saddr = np->rcv_saddr;
+ newsk->dport = htons(asoc->peer.port);
+ sctp_v6_to_sk_daddr(&asoc->peer.primary_addr, newsk);
+
+ /* Init the ipv4 part of the socket since we can have sockets
+ * using v6 API for ipv4.
+ */
+ newinet->ttl = sysctl_ip_default_ttl;
+ newinet->mc_loop = 1;
+ newinet->mc_ttl = 1;
+ newinet->mc_index = 0;
+ newinet->mc_list = NULL;
+
+ if (ipv4_config.no_pmtu_disc)
+ newinet->pmtudisc = IP_PMTUDISC_DONT;
+ else
+ newinet->pmtudisc = IP_PMTUDISC_WANT;
+
+#ifdef INET_REFCNT_DEBUG
+ atomic_inc(&inet6_sock_nr);
+ atomic_inc(&inet_sock_nr);
+#endif
+
+ if (newsk->prot->init(newsk)) {
+ inet_sock_release(newsk);
+ newsk = NULL;
+ }
+
+out:
+ return newsk;
+}
+
+/* Map v4 address to mapped v6 address */
+static void sctp_v6_addr_v4map(struct sctp_opt *sp, union sctp_addr *addr)
+{
+ if (sp->v4mapped && AF_INET == addr->sa.sa_family)
+ sctp_v4_map_v6(addr);
+}
+
+/* Where did this skb come from? */
+static int sctp_v6_skb_iif(const struct sk_buff *skb)
+{
+ struct inet6_skb_parm *opt = (struct inet6_skb_parm *) skb->cb;
+ return opt->iif;
+}
+
+/* Was this packet marked by Explicit Congestion Notification? */
+static int sctp_v6_is_ce(const struct sk_buff *skb)
+{
+ return *((__u32 *)(skb->nh.ipv6h)) & htonl(1<<20);
+}
+
+/* Dump the v6 addr to the seq file. */
+static void sctp_v6_seq_dump_addr(struct seq_file *seq, union sctp_addr *addr)
+{
+ seq_printf(seq, "%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x ",
+ NIP6(&addr->v6.sin6_addr));
+}
+
+/* Initialize a PF_INET6 socket msg_name. */
+static void sctp_inet6_msgname(char *msgname, int *addr_len)
+{
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)msgname;
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_flowinfo = 0;
+ sin6->sin6_scope_id = 0; /*FIXME */
+ *addr_len = sizeof(struct sockaddr_in6);
+}
+
+/* Initialize a PF_INET msgname from a ulpevent. */
+static void sctp_inet6_event_msgname(struct sctp_ulpevent *event,
+ char *msgname, int *addrlen)
+{
+ struct sockaddr_in6 *sin6, *sin6from;
+
+ if (msgname) {
+ union sctp_addr *addr;
+ struct sctp_association *asoc;
+
+ asoc = event->asoc;
+ sctp_inet6_msgname(msgname, addrlen);
+ sin6 = (struct sockaddr_in6 *)msgname;
+ sin6->sin6_port = htons(asoc->peer.port);
+ addr = &asoc->peer.primary_addr;
+
+ /* Note: If we go to a common v6 format, this code
+ * will change.
+ */
+
+ /* Map ipv4 address into v4-mapped-on-v6 address. */
+ if (sctp_sk(asoc->base.sk)->v4mapped &&
+ AF_INET == addr->sa.sa_family) {
+ sctp_v4_map_v6((union sctp_addr *)sin6);
+ sin6->sin6_addr.s6_addr32[3] =
+ addr->v4.sin_addr.s_addr;
+ return;
+ }
+
+ sin6from = &asoc->peer.primary_addr.v6;
+ ipv6_addr_copy(&sin6->sin6_addr, &sin6from->sin6_addr);
+ if (ipv6_addr_type(&sin6->sin6_addr) & IPV6_ADDR_LINKLOCAL)
+ sin6->sin6_scope_id = sin6from->sin6_scope_id;
+ }
+}
+
+/* Initialize a msg_name from an inbound skb. */
+static void sctp_inet6_skb_msgname(struct sk_buff *skb, char *msgname,
+ int *addr_len)
+{
+ struct sctphdr *sh;
+ struct sockaddr_in6 *sin6;
+
+ if (msgname) {
+ sctp_inet6_msgname(msgname, addr_len);
+ sin6 = (struct sockaddr_in6 *)msgname;
+ sh = (struct sctphdr *)skb->h.raw;
+ sin6->sin6_port = sh->source;
+
+ /* Map ipv4 address into v4-mapped-on-v6 address. */
+ if (sctp_sk(skb->sk)->v4mapped &&
+ skb->nh.iph->version == 4) {
+ sctp_v4_map_v6((union sctp_addr *)sin6);
+ sin6->sin6_addr.s6_addr32[3] = skb->nh.iph->saddr;
+ return;
+ }
+
+ /* Otherwise, just copy the v6 address. */
+ ipv6_addr_copy(&sin6->sin6_addr, &skb->nh.ipv6h->saddr);
+ if (ipv6_addr_type(&sin6->sin6_addr) & IPV6_ADDR_LINKLOCAL) {
+ struct sctp_ulpevent *ev = sctp_skb2event(skb);
+ sin6->sin6_scope_id = ev->iif;
+ }
+ }
+}
+
+/* Do we support this AF? */
+static int sctp_inet6_af_supported(sa_family_t family, struct sctp_opt *sp)
+{
+ switch (family) {
+ case AF_INET6:
+ return 1;
+ /* v4-mapped-v6 addresses */
+ case AF_INET:
+ if (!__ipv6_only_sock(sctp_opt2sk(sp)) && sp->v4mapped)
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/* Address matching with wildcards allowed. This extra level
+ * of indirection lets us choose whether a PF_INET6 should
+ * disallow any v4 addresses if we so choose.
+ */
+static int sctp_inet6_cmp_addr(const union sctp_addr *addr1,
+ const union sctp_addr *addr2,
+ struct sctp_opt *opt)
+{
+ struct sctp_af *af1, *af2;
+
+ af1 = sctp_get_af_specific(addr1->sa.sa_family);
+ af2 = sctp_get_af_specific(addr2->sa.sa_family);
+
+ if (!af1 || !af2)
+ return 0;
+ /* Today, wildcard AF_INET/AF_INET6. */
+ if (sctp_is_any(addr1) || sctp_is_any(addr2))
+ return 1;
+
+ if (addr1->sa.sa_family != addr2->sa.sa_family)
+ return 0;
+
+ return af1->cmp_addr(addr1, addr2);
+}
+
+/* Verify that the provided sockaddr looks bindable. Common verification,
+ * has already been taken care of.
+ */
+static int sctp_inet6_bind_verify(struct sctp_opt *opt, union sctp_addr *addr)
+{
+ struct sctp_af *af;
+
+ /* ASSERT: address family has already been verified. */
+ if (addr->sa.sa_family != AF_INET6)
+ af = sctp_get_af_specific(addr->sa.sa_family);
+ else {
+ struct sock *sk;
+ int type = ipv6_addr_type(&addr->v6.sin6_addr);
+ sk = sctp_opt2sk(opt);
+ if (type & IPV6_ADDR_LINKLOCAL) {
+ /* Note: Behavior similar to af_inet6.c:
+ * 1) Overrides previous bound_dev_if
+ * 2) Destructive even if bind isn't successful.
+ */
+
+ if (addr->v6.sin6_scope_id)
+ sk->bound_dev_if = addr->v6.sin6_scope_id;
+ if (!sk->bound_dev_if)
+ return 0;
+ }
+ af = opt->pf->af;
+ }
+ return af->available(addr, opt);
+}
+
+/* Verify that the provided sockaddr looks bindable. Common verification,
+ * has already been taken care of.
+ */
+static int sctp_inet6_send_verify(struct sctp_opt *opt, union sctp_addr *addr)
+{
+ struct sctp_af *af = NULL;
+
+ /* ASSERT: address family has already been verified. */
+ if (addr->sa.sa_family != AF_INET6)
+ af = sctp_get_af_specific(addr->sa.sa_family);
+ else {
+ struct sock *sk;
+ int type = ipv6_addr_type(&addr->v6.sin6_addr);
+ sk = sctp_opt2sk(opt);
+ if (type & IPV6_ADDR_LINKLOCAL) {
+ /* Note: Behavior similar to af_inet6.c:
+ * 1) Overrides previous bound_dev_if
+ * 2) Destructive even if bind isn't successful.
+ */
+
+ if (addr->v6.sin6_scope_id)
+ sk->bound_dev_if = addr->v6.sin6_scope_id;
+ if (!sk->bound_dev_if)
+ return 0;
+ }
+ af = opt->pf->af;
+ }
+
+ return af != NULL;
+}
+
+/* Fill in Supported Address Type information for INIT and INIT-ACK
+ * chunks. Note: In the future, we may want to look at sock options
+ * to determine whether a PF_INET6 socket really wants to have IPV4
+ * addresses.
+ * Returns number of addresses supported.
+ */
+static int sctp_inet6_supported_addrs(const struct sctp_opt *opt,
+ __u16 *types)
+{
+ types[0] = SCTP_PARAM_IPV4_ADDRESS;
+ types[1] = SCTP_PARAM_IPV6_ADDRESS;
+ return 2;
+}
+
+static struct proto_ops inet6_seqpacket_ops = {
+ .family = PF_INET6,
+ .release = inet6_release,
+ .bind = inet6_bind,
+ .connect = inet_dgram_connect,
+ .socketpair = sock_no_socketpair,
+ .accept = inet_accept,
+ .getname = inet6_getname,
+ .poll = sctp_poll,
+ .ioctl = inet6_ioctl,
+ .listen = sctp_inet_listen,
+ .shutdown = inet_shutdown,
+ .setsockopt = inet_setsockopt,
+ .getsockopt = inet_getsockopt,
+ .sendmsg = inet_sendmsg,
+ .recvmsg = inet_recvmsg,
+ .mmap = sock_no_mmap,
+};
+
+static struct inet_protosw sctpv6_seqpacket_protosw = {
+ .type = SOCK_SEQPACKET,
+ .protocol = IPPROTO_SCTP,
+ .prot = &sctp_prot,
+ .ops = &inet6_seqpacket_ops,
+ .capability = -1,
+ .no_check = 0,
+ .flags = SCTP_PROTOSW_FLAG
+};
+static struct inet_protosw sctpv6_stream_protosw = {
+ .type = SOCK_STREAM,
+ .protocol = IPPROTO_SCTP,
+ .prot = &sctp_prot,
+ .ops = &inet6_seqpacket_ops,
+ .capability = -1,
+ .no_check = 0,
+ .flags = SCTP_PROTOSW_FLAG,
+};
+
+static struct inet6_protocol sctpv6_protocol = {
+ .handler = sctp_rcv,
+ .err_handler = sctp_v6_err,
+ .next = NULL,
+ .protocol = IPPROTO_SCTP,
+ .copy = 0,
+ .data = NULL,
+ .name = "SCTPv6",
+};
+
+static struct sctp_af sctp_ipv6_specific = {
+ .sctp_xmit = sctp_v6_xmit,
+ .setsockopt = ipv6_setsockopt,
+ .getsockopt = ipv6_getsockopt,
+ .get_dst = sctp_v6_get_dst,
+ .get_saddr = sctp_v6_get_saddr,
+ .copy_addrlist = sctp_v6_copy_addrlist,
+ .from_skb = sctp_v6_from_skb,
+ .from_sk = sctp_v6_from_sk,
+ .to_sk_saddr = sctp_v6_to_sk_saddr,
+ .to_sk_daddr = sctp_v6_to_sk_daddr,
+ .from_addr_param = sctp_v6_from_addr_param,
+ .to_addr_param = sctp_v6_to_addr_param,
+ .dst_saddr = sctp_v6_dst_saddr,
+ .cmp_addr = sctp_v6_cmp_addr,
+ .scope = sctp_v6_scope,
+ .addr_valid = sctp_v6_addr_valid,
+ .inaddr_any = sctp_v6_inaddr_any,
+ .is_any = sctp_v6_is_any,
+ .available = sctp_v6_available,
+ .skb_iif = sctp_v6_skb_iif,
+ .is_ce = sctp_v6_is_ce,
+ .seq_dump_addr = sctp_v6_seq_dump_addr,
+ .net_header_len = sizeof(struct ipv6hdr),
+ .sockaddr_len = sizeof(struct sockaddr_in6),
+ .sa_family = AF_INET6,
+};
+
+static struct sctp_pf sctp_pf_inet6_specific = {
+ .event_msgname = sctp_inet6_event_msgname,
+ .skb_msgname = sctp_inet6_skb_msgname,
+ .af_supported = sctp_inet6_af_supported,
+ .cmp_addr = sctp_inet6_cmp_addr,
+ .bind_verify = sctp_inet6_bind_verify,
+ .send_verify = sctp_inet6_send_verify,
+ .supported_addrs = sctp_inet6_supported_addrs,
+ .create_accept_sk = sctp_v6_create_accept_sk,
+ .addr_v4map = sctp_v6_addr_v4map,
+ .af = &sctp_ipv6_specific,
+};
+
+/* Initialize IPv6 support and register with inet6 stack. */
+int sctp_v6_init(void)
+{
+ /* Register inet6 protocol. */
+ inet6_add_protocol(&sctpv6_protocol);
+
+ /* Add SCTPv6(UDP and TCP style) to inetsw6 linked list. */
+ inet6_register_protosw(&sctpv6_seqpacket_protosw);
+ inet6_register_protosw(&sctpv6_stream_protosw);
+
+ /* Register the SCTP specific PF_INET6 functions. */
+ sctp_register_pf(&sctp_pf_inet6_specific, PF_INET6);
+
+ /* Register the SCTP specific AF_INET6 functions. */
+ sctp_register_af(&sctp_ipv6_specific);
+
+ /* Register notifier for inet6 address additions/deletions. */
+ register_inet6addr_notifier(&sctp_inet6addr_notifier);
+
+ return 0;
+}
+
+/* IPv6 specific exit support. */
+void sctp_v6_exit(void)
+{
+ list_del(&sctp_ipv6_specific.list);
+ inet6_del_protocol(&sctpv6_protocol);
+ inet6_unregister_protosw(&sctpv6_seqpacket_protosw);
+ inet6_unregister_protosw(&sctpv6_stream_protosw);
+ unregister_inet6addr_notifier(&sctp_inet6addr_notifier);
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/objcnt.c b/uClinux-2.4.31-uc0/net/sctp/objcnt.c
new file mode 100644
index 0000000..d177a63
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/objcnt.c
@@ -0,0 +1,138 @@
+/* SCTP kernel reference Implementation
+ * Copyright (c) 2001 International Business Machines Corp.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * Support for memory object debugging. This allows one to monitor the
+ * object allocations/deallocations for types instrumented for this
+ * via the proc fs.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * Jon Grimm <jgrimm@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/kernel.h>
+#include <net/sctp/sctp.h>
+
+/*
+ * Global counters to count raw object allocation counts.
+ * To add new counters, choose a unique suffix for the variable
+ * name as the helper macros key off this suffix to make
+ * life easier for the programmer.
+ */
+
+SCTP_DBG_OBJCNT(sock);
+SCTP_DBG_OBJCNT(ep);
+SCTP_DBG_OBJCNT(transport);
+SCTP_DBG_OBJCNT(assoc);
+SCTP_DBG_OBJCNT(bind_addr);
+SCTP_DBG_OBJCNT(chunk);
+SCTP_DBG_OBJCNT(addr);
+SCTP_DBG_OBJCNT(ssnmap);
+SCTP_DBG_OBJCNT(datamsg);
+
+/* An array to make it easy to pretty print the debug information
+ * to the proc fs.
+ */
+static sctp_dbg_objcnt_entry_t sctp_dbg_objcnt[] = {
+ SCTP_DBG_OBJCNT_ENTRY(sock),
+ SCTP_DBG_OBJCNT_ENTRY(ep),
+ SCTP_DBG_OBJCNT_ENTRY(assoc),
+ SCTP_DBG_OBJCNT_ENTRY(transport),
+ SCTP_DBG_OBJCNT_ENTRY(chunk),
+ SCTP_DBG_OBJCNT_ENTRY(bind_addr),
+ SCTP_DBG_OBJCNT_ENTRY(addr),
+ SCTP_DBG_OBJCNT_ENTRY(ssnmap),
+ SCTP_DBG_OBJCNT_ENTRY(datamsg),
+};
+
+/* Callback from procfs to read out objcount information.
+ * Walk through the entries in the sctp_dbg_objcnt array, dumping
+ * the raw object counts for each monitored type.
+ *
+ * This code was modified from similar code in route.c
+ */
+static int sctp_dbg_objcnt_read(char *buffer, char **start, off_t offset,
+ int length, int *eof, void *data)
+{
+ int len = 0;
+ off_t pos = 0;
+ int entries;
+ int i;
+ char temp[128];
+
+ /* How many entries? */
+ entries = ARRAY_SIZE(sctp_dbg_objcnt);
+
+ /* Walk the entries and print out the debug information
+ * for proc fs.
+ */
+ for (i = 0; i < entries; i++) {
+ pos += 128;
+
+ /* Skip ahead. */
+ if (pos <= offset) {
+ len = 0;
+ continue;
+ }
+ /* Print out each entry. */
+ sprintf(temp, "%s: %d",
+ sctp_dbg_objcnt[i].label,
+ atomic_read(sctp_dbg_objcnt[i].counter));
+
+ sprintf(buffer + len, "%-127s\n", temp);
+ len += 128;
+ if (pos >= offset+length)
+ goto done;
+ }
+
+done:
+ *start = buffer + len - (pos - offset);
+ len = pos - offset;
+ if (len > length)
+ len = length;
+
+ return len;
+}
+
+/* Initialize the objcount in the proc filesystem. */
+void sctp_dbg_objcnt_init(void)
+{
+ create_proc_read_entry("sctp_dbg_objcnt", 0, proc_net_sctp,
+ sctp_dbg_objcnt_read, NULL);
+}
+
+/* Cleanup the objcount entry in the proc filesystem. */
+void sctp_dbg_objcnt_exit(void)
+{
+ remove_proc_entry("sctp_dbg_objcnt", proc_net_sctp);
+}
+
+
diff --git a/uClinux-2.4.31-uc0/net/sctp/output.c b/uClinux-2.4.31-uc0/net/sctp/output.c
new file mode 100644
index 0000000..7c62216
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/output.c
@@ -0,0 +1,646 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2001, 2004
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * These functions handle output processing.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Jon Grimm <jgrimm@austin.ibm.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/wait.h>
+#include <linux/time.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/init.h>
+#include <net/inet_ecn.h>
+#include <net/icmp.h>
+
+#ifndef TEST_FRAME
+#include <net/tcp.h>
+#endif /* TEST_FRAME (not defined) */
+
+#include <linux/socket.h> /* for sa_family_t */
+#include <net/sock.h>
+
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+/* Forward declarations for private helpers. */
+static sctp_xmit_t sctp_packet_append_data(struct sctp_packet *packet,
+ struct sctp_chunk *chunk);
+
+/* Config a packet.
+ * This appears to be a followup set of initializations.
+ */
+struct sctp_packet *sctp_packet_config(struct sctp_packet *packet,
+ __u32 vtag, int ecn_capable)
+{
+ struct sctp_chunk *chunk = NULL;
+
+ SCTP_DEBUG_PRINTK("%s: packet:%p vtag:0x%x\n", __FUNCTION__,
+ packet, vtag);
+
+ packet->vtag = vtag;
+ packet->has_cookie_echo = 0;
+ packet->has_sack = 0;
+ packet->ipfragok = 0;
+
+ if (ecn_capable && sctp_packet_empty(packet)) {
+ chunk = sctp_get_ecne_prepend(packet->transport->asoc);
+
+ /* If there a is a prepend chunk stick it on the list before
+ * any other chunks get appended.
+ */
+ if (chunk)
+ sctp_packet_append_chunk(packet, chunk);
+ }
+
+ return packet;
+}
+
+/* Initialize the packet structure. */
+struct sctp_packet *sctp_packet_init(struct sctp_packet *packet,
+ struct sctp_transport *transport,
+ __u16 sport, __u16 dport)
+{
+ struct sctp_association *asoc = transport->asoc;
+ size_t overhead;
+
+ SCTP_DEBUG_PRINTK("%s: packet:%p transport:%p\n", __FUNCTION__,
+ packet, transport);
+
+ packet->transport = transport;
+ packet->source_port = sport;
+ packet->destination_port = dport;
+ skb_queue_head_init(&packet->chunks);
+ if (asoc) {
+ struct sctp_opt *sp = sctp_sk(asoc->base.sk);
+ overhead = sp->pf->af->net_header_len;
+ } else {
+ overhead = sizeof(struct ipv6hdr);
+ }
+ overhead += sizeof(struct sctphdr);
+ packet->overhead = overhead;
+ packet->size = overhead;
+ packet->vtag = 0;
+ packet->has_cookie_echo = 0;
+ packet->has_sack = 0;
+ packet->ipfragok = 0;
+ packet->malloced = 0;
+ return packet;
+}
+
+/* Free a packet. */
+void sctp_packet_free(struct sctp_packet *packet)
+{
+ struct sctp_chunk *chunk;
+
+ SCTP_DEBUG_PRINTK("%s: packet:%p\n", __FUNCTION__, packet);
+
+ while ((chunk = (struct sctp_chunk *)__skb_dequeue(&packet->chunks)) != NULL)
+ sctp_chunk_free(chunk);
+
+ if (packet->malloced)
+ kfree(packet);
+}
+
+/* This routine tries to append the chunk to the offered packet. If adding
+ * the chunk causes the packet to exceed the path MTU and COOKIE_ECHO chunk
+ * is not present in the packet, it transmits the input packet.
+ * Data can be bundled with a packet containing a COOKIE_ECHO chunk as long
+ * as it can fit in the packet, but any more data that does not fit in this
+ * packet can be sent only after receiving the COOKIE_ACK.
+ */
+sctp_xmit_t sctp_packet_transmit_chunk(struct sctp_packet *packet,
+ struct sctp_chunk *chunk)
+{
+ sctp_xmit_t retval;
+ int error = 0;
+
+ SCTP_DEBUG_PRINTK("%s: packet:%p chunk:%p\n", __FUNCTION__,
+ packet, chunk);
+
+ switch ((retval = (sctp_packet_append_chunk(packet, chunk)))) {
+ case SCTP_XMIT_PMTU_FULL:
+ if (!packet->has_cookie_echo) {
+ error = sctp_packet_transmit(packet);
+ if (error < 0)
+ chunk->skb->sk->err = -error;
+
+ /* If we have an empty packet, then we can NOT ever
+ * return PMTU_FULL.
+ */
+ retval = sctp_packet_append_chunk(packet, chunk);
+ }
+ break;
+
+ case SCTP_XMIT_RWND_FULL:
+ case SCTP_XMIT_OK:
+ case SCTP_XMIT_NAGLE_DELAY:
+ break;
+ };
+
+ return retval;
+}
+
+/* Try to bundle a SACK with the packet. */
+static sctp_xmit_t sctp_packet_bundle_sack(struct sctp_packet *pkt,
+ struct sctp_chunk *chunk)
+{
+ sctp_xmit_t retval = SCTP_XMIT_OK;
+
+ /* If sending DATA and haven't aleady bundled a SACK, try to
+ * bundle one in to the packet.
+ */
+ if (sctp_chunk_is_data(chunk) && !pkt->has_sack &&
+ !pkt->has_cookie_echo) {
+ struct sctp_association *asoc;
+ asoc = pkt->transport->asoc;
+
+ if (asoc->a_rwnd > asoc->rwnd) {
+ struct sctp_chunk *sack;
+ asoc->a_rwnd = asoc->rwnd;
+ sack = sctp_make_sack(asoc);
+ if (sack) {
+ struct timer_list *timer;
+ retval = sctp_packet_append_chunk(pkt, sack);
+ asoc->peer.sack_needed = 0;
+ timer = &asoc->timers[SCTP_EVENT_TIMEOUT_SACK];
+ if (timer_pending(timer) && del_timer(timer))
+ sctp_association_put(asoc);
+ }
+ }
+ }
+ return retval;
+}
+
+/* Append a chunk to the offered packet reporting back any inability to do
+ * so.
+ */
+sctp_xmit_t sctp_packet_append_chunk(struct sctp_packet *packet,
+ struct sctp_chunk *chunk)
+{
+ sctp_xmit_t retval = SCTP_XMIT_OK;
+ __u16 chunk_len = WORD_ROUND(ntohs(chunk->chunk_hdr->length));
+ size_t psize;
+ size_t pmtu;
+ int too_big;
+
+ SCTP_DEBUG_PRINTK("%s: packet:%p chunk:%p\n", __FUNCTION__, packet,
+ chunk);
+
+ retval = sctp_packet_bundle_sack(packet, chunk);
+ psize = packet->size;
+
+ if (retval != SCTP_XMIT_OK)
+ goto finish;
+
+ pmtu = ((packet->transport->asoc) ?
+ (packet->transport->asoc->pmtu) :
+ (packet->transport->pmtu));
+
+ too_big = (psize + chunk_len > pmtu);
+
+ /* Decide if we need to fragment or resubmit later. */
+ if (too_big) {
+ /* Both control chunks and data chunks with TSNs are
+ * non-fragmentable.
+ */
+ if (sctp_packet_empty(packet) || !sctp_chunk_is_data(chunk)) {
+ /* We no longer do re-fragmentation.
+ * Just fragment at the IP layer, if we
+ * actually hit this condition
+ */
+ packet->ipfragok = 1;
+ goto append;
+
+ } else {
+ retval = SCTP_XMIT_PMTU_FULL;
+ goto finish;
+ }
+ }
+
+append:
+ /* We believe that this chunk is OK to add to the packet (as
+ * long as we have the cwnd for it).
+ */
+
+ /* DATA is a special case since we must examine both rwnd and cwnd
+ * before we send DATA.
+ */
+ if (sctp_chunk_is_data(chunk)) {
+ retval = sctp_packet_append_data(packet, chunk);
+ /* Disallow SACK bundling after DATA. */
+ packet->has_sack = 1;
+ if (SCTP_XMIT_OK != retval)
+ goto finish;
+ } else if (SCTP_CID_COOKIE_ECHO == chunk->chunk_hdr->type)
+ packet->has_cookie_echo = 1;
+ else if (SCTP_CID_SACK == chunk->chunk_hdr->type)
+ packet->has_sack = 1;
+
+ /* It is OK to send this chunk. */
+ __skb_queue_tail(&packet->chunks, (struct sk_buff *)chunk);
+ packet->size += chunk_len;
+ chunk->transport = packet->transport;
+finish:
+ return retval;
+}
+
+/* All packets are sent to the network through this function from
+ * sctp_outq_tail().
+ *
+ * The return value is a normal kernel error return value.
+ */
+int sctp_packet_transmit(struct sctp_packet *packet)
+{
+ struct sctp_transport *tp = packet->transport;
+ struct sctp_association *asoc = tp->asoc;
+ struct sctphdr *sh;
+ __u32 crc32;
+ struct sk_buff *nskb;
+ struct sctp_chunk *chunk;
+ struct sock *sk;
+ int err = 0;
+ int padding; /* How much padding do we need? */
+ __u8 has_data = 0;
+ struct dst_entry *dst;
+
+ SCTP_DEBUG_PRINTK("%s: packet:%p\n", __FUNCTION__, packet);
+
+ /* Do NOT generate a chunkless packet. */
+ chunk = (struct sctp_chunk *)skb_peek(&packet->chunks);
+ if (unlikely(!chunk))
+ return err;
+
+ /* Set up convenience variables... */
+ sk = chunk->skb->sk;
+
+ /* Allocate the new skb. */
+ nskb = dev_alloc_skb(packet->size);
+ if (!nskb)
+ goto nomem;
+
+ /* Make sure the outbound skb has enough header room reserved. */
+ skb_reserve(nskb, packet->overhead);
+
+ /* Set the owning socket so that we know where to get the
+ * destination IP address.
+ */
+ skb_set_owner_w(nskb, sk);
+
+ /* Build the SCTP header. */
+ sh = (struct sctphdr *)skb_push(nskb, sizeof(struct sctphdr));
+ sh->source = htons(packet->source_port);
+ sh->dest = htons(packet->destination_port);
+
+ /* From 6.8 Adler-32 Checksum Calculation:
+ * After the packet is constructed (containing the SCTP common
+ * header and one or more control or DATA chunks), the
+ * transmitter shall:
+ *
+ * 1) Fill in the proper Verification Tag in the SCTP common
+ * header and initialize the checksum field to 0's.
+ */
+ sh->vtag = htonl(packet->vtag);
+ sh->checksum = 0;
+
+ /* 2) Calculate the Adler-32 checksum of the whole packet,
+ * including the SCTP common header and all the
+ * chunks.
+ *
+ * Note: Adler-32 is no longer applicable, as has been replaced
+ * by CRC32-C as described in <draft-ietf-tsvwg-sctpcsum-02.txt>.
+ */
+ crc32 = sctp_start_cksum((__u8 *)sh, sizeof(struct sctphdr));
+
+ /**
+ * 6.10 Bundling
+ *
+ * An endpoint bundles chunks by simply including multiple
+ * chunks in one outbound SCTP packet. ...
+ */
+
+ /**
+ * 3.2 Chunk Field Descriptions
+ *
+ * The total length of a chunk (including Type, Length and
+ * Value fields) MUST be a multiple of 4 bytes. If the length
+ * of the chunk is not a multiple of 4 bytes, the sender MUST
+ * pad the chunk with all zero bytes and this padding is not
+ * included in the chunk length field. The sender should
+ * never pad with more than 3 bytes.
+ *
+ * [This whole comment explains WORD_ROUND() below.]
+ */
+ SCTP_DEBUG_PRINTK("***sctp_transmit_packet***\n");
+ while ((chunk = (struct sctp_chunk *)__skb_dequeue(&packet->chunks)) != NULL) {
+ if (sctp_chunk_is_data(chunk)) {
+
+ if (!chunk->has_tsn) {
+ sctp_chunk_assign_ssn(chunk);
+ sctp_chunk_assign_tsn(chunk);
+
+ /* 6.3.1 C4) When data is in flight and when allowed
+ * by rule C5, a new RTT measurement MUST be made each
+ * round trip. Furthermore, new RTT measurements
+ * SHOULD be made no more than once per round-trip
+ * for a given destination transport address.
+ */
+
+ if (!tp->rto_pending) {
+ chunk->rtt_in_progress = 1;
+ tp->rto_pending = 1;
+ }
+ } else
+ chunk->resent = 1;
+
+ chunk->sent_at = jiffies;
+ has_data = 1;
+ }
+
+ padding = WORD_ROUND(chunk->skb->len) - chunk->skb->len;
+ if (padding)
+ memset(skb_put(chunk->skb, padding), 0, padding);
+
+ crc32 = sctp_update_copy_cksum(skb_put(nskb, chunk->skb->len),
+ chunk->skb->data,
+ chunk->skb->len, crc32);
+
+ SCTP_DEBUG_PRINTK("%s %p[%s] %s 0x%x, %s %d, %s %d, %s %d\n",
+ "*** Chunk", chunk,
+ sctp_cname(SCTP_ST_CHUNK(
+ chunk->chunk_hdr->type)),
+ chunk->has_tsn ? "TSN" : "No TSN",
+ chunk->has_tsn ?
+ ntohl(chunk->subh.data_hdr->tsn) : 0,
+ "length", ntohs(chunk->chunk_hdr->length),
+ "chunk->skb->len", chunk->skb->len,
+ "rtt_in_progress", chunk->rtt_in_progress);
+
+ /*
+ * If this is a control chunk, this is our last
+ * reference. Free data chunks after they've been
+ * acknowledged or have failed.
+ */
+ if (!sctp_chunk_is_data(chunk))
+ sctp_chunk_free(chunk);
+ }
+
+ /* Perform final transformation on checksum. */
+ crc32 = sctp_end_cksum(crc32);
+
+ /* 3) Put the resultant value into the checksum field in the
+ * common header, and leave the rest of the bits unchanged.
+ */
+ sh->checksum = htonl(crc32);
+
+ /* IP layer ECN support
+ * From RFC 2481
+ * "The ECN-Capable Transport (ECT) bit would be set by the
+ * data sender to indicate that the end-points of the
+ * transport protocol are ECN-capable."
+ *
+ * Now setting the ECT bit all the time, as it should not cause
+ * any problems protocol-wise even if our peer ignores it.
+ *
+ * Note: The works for IPv6 layer checks this bit too later
+ * in transmission. See IP6_ECN_flow_xmit().
+ */
+ INET_ECN_xmit(nskb->sk);
+
+ /* Set up the IP options. */
+ /* BUG: not implemented
+ * For v4 this all lives somewhere in sk->sk_opt...
+ */
+
+ /* Dump that on IP! */
+ if (asoc && asoc->peer.last_sent_to != tp) {
+ /* Considering the multiple CPU scenario, this is a
+ * "correcter" place for last_sent_to. --xguo
+ */
+ asoc->peer.last_sent_to = tp;
+ }
+
+ if (has_data) {
+ struct timer_list *timer;
+ unsigned long timeout;
+
+ tp->last_time_used = jiffies;
+
+ /* Restart the AUTOCLOSE timer when sending data. */
+ if (sctp_state(asoc, ESTABLISHED) && asoc->autoclose) {
+ timer = &asoc->timers[SCTP_EVENT_TIMEOUT_AUTOCLOSE];
+ timeout = asoc->timeouts[SCTP_EVENT_TIMEOUT_AUTOCLOSE];
+
+ if (!mod_timer(timer, jiffies + timeout))
+ sctp_association_hold(asoc);
+ }
+ }
+
+ dst = tp->dst;
+ /* The 'obsolete' field of dst is set to 2 when a dst is freed. */
+ if (!dst || (dst->obsolete > 1)) {
+ dst_release(dst);
+ sctp_transport_route(tp, NULL, sctp_sk(sk));
+ sctp_assoc_sync_pmtu(asoc);
+ }
+
+ nskb->dst = dst_clone(tp->dst);
+ if (!nskb->dst)
+ goto no_route;
+
+ SCTP_DEBUG_PRINTK("***sctp_transmit_packet*** skb len %d\n",
+ nskb->len);
+
+ (*tp->af_specific->sctp_xmit)(nskb, tp, packet->ipfragok);
+
+out:
+ packet->size = packet->overhead;
+ return err;
+no_route:
+ kfree_skb(nskb);
+ IP_INC_STATS_BH(IpOutNoRoutes);
+
+ /* FIXME: Returning the 'err' will effect all the associations
+ * associated with a socket, although only one of the paths of the
+ * association is unreachable.
+ * The real failure of a transport or association can be passed on
+ * to the user via notifications. So setting this error may not be
+ * required.
+ */
+ /* err = -EHOSTUNREACH; */
+err:
+ /* Control chunks are unreliable so just drop them. DATA chunks
+ * will get resent or dropped later.
+ */
+
+ while ((chunk = (struct sctp_chunk *)__skb_dequeue(&packet->chunks)) != NULL) {
+ if (!sctp_chunk_is_data(chunk))
+ sctp_chunk_free(chunk);
+ }
+ goto out;
+nomem:
+ err = -ENOMEM;
+ goto err;
+}
+
+/********************************************************************
+ * 2nd Level Abstractions
+ ********************************************************************/
+
+/* This private function handles the specifics of appending DATA chunks. */
+static sctp_xmit_t sctp_packet_append_data(struct sctp_packet *packet,
+ struct sctp_chunk *chunk)
+{
+ sctp_xmit_t retval = SCTP_XMIT_OK;
+ size_t datasize, rwnd, inflight;
+ struct sctp_transport *transport = packet->transport;
+ __u32 max_burst_bytes;
+ struct sctp_association *asoc = transport->asoc;
+ struct sctp_opt *sp = sctp_sk(asoc->base.sk);
+ struct sctp_outq *q = &asoc->outqueue;
+
+ /* RFC 2960 6.1 Transmission of DATA Chunks
+ *
+ * A) At any given time, the data sender MUST NOT transmit new data to
+ * any destination transport address if its peer's rwnd indicates
+ * that the peer has no buffer space (i.e. rwnd is 0, see Section
+ * 6.2.1). However, regardless of the value of rwnd (including if it
+ * is 0), the data sender can always have one DATA chunk in flight to
+ * the receiver if allowed by cwnd (see rule B below). This rule
+ * allows the sender to probe for a change in rwnd that the sender
+ * missed due to the SACK having been lost in transit from the data
+ * receiver to the data sender.
+ */
+
+ rwnd = asoc->peer.rwnd;
+ inflight = asoc->outqueue.outstanding_bytes;
+
+ datasize = sctp_data_size(chunk);
+
+ if (datasize > rwnd) {
+ if (inflight > 0) {
+ /* We have (at least) one data chunk in flight,
+ * so we can't fall back to rule 6.1 B).
+ */
+ retval = SCTP_XMIT_RWND_FULL;
+ goto finish;
+ }
+ }
+
+ /* sctpimpguide-05 2.14.2
+ * D) When the time comes for the sender to
+ * transmit new DATA chunks, the protocol parameter Max.Burst MUST
+ * first be applied to limit how many new DATA chunks may be sent.
+ * The limit is applied by adjusting cwnd as follows:
+ * if ((flightsize + Max.Burst * MTU) < cwnd)
+ * cwnd = flightsize + Max.Burst * MTU
+ */
+ max_burst_bytes = asoc->max_burst * asoc->pmtu;
+ if ((transport->flight_size + max_burst_bytes) < transport->cwnd) {
+ transport->cwnd = transport->flight_size + max_burst_bytes;
+ SCTP_DEBUG_PRINTK("%s: cwnd limited by max_burst: "
+ "transport: %p, cwnd: %d, "
+ "ssthresh: %d, flight_size: %d, "
+ "pba: %d\n",
+ __FUNCTION__, transport,
+ transport->cwnd,
+ transport->ssthresh,
+ transport->flight_size,
+ transport->partial_bytes_acked);
+ }
+
+ /* RFC 2960 6.1 Transmission of DATA Chunks
+ *
+ * B) At any given time, the sender MUST NOT transmit new data
+ * to a given transport address if it has cwnd or more bytes
+ * of data outstanding to that transport address.
+ */
+ /* RFC 7.2.4 & the Implementers Guide 2.8.
+ *
+ * 3) ...
+ * When a Fast Retransmit is being performed the sender SHOULD
+ * ignore the value of cwnd and SHOULD NOT delay retransmission.
+ */
+ if (!chunk->fast_retransmit)
+ if (transport->flight_size >= transport->cwnd) {
+ retval = SCTP_XMIT_RWND_FULL;
+ goto finish;
+ }
+
+ /* Nagle's algorithm to solve small-packet problem:
+ * Inhibit the sending of new chunks when new outgoing data arrives
+ * if any previously transmitted data on the connection remains
+ * unacknowledged.
+ */
+ if (!sp->nodelay && sctp_packet_empty(packet) &&
+ q->outstanding_bytes && sctp_state(asoc, ESTABLISHED)) {
+ unsigned len = datasize + q->out_qlen;
+
+ /* Check whether this chunk and all the rest of pending
+ * data will fit or delay in hopes of bundling a full
+ * sized packet.
+ */
+ if (len < asoc->pmtu - packet->overhead) {
+ retval = SCTP_XMIT_NAGLE_DELAY;
+ goto finish;
+ }
+ }
+
+ /* Keep track of how many bytes are in flight over this transport. */
+ transport->flight_size += datasize;
+
+ /* Keep track of how many bytes are in flight to the receiver. */
+ asoc->outqueue.outstanding_bytes += datasize;
+
+ /* Update our view of the receiver's rwnd. */
+ if (datasize < rwnd)
+ rwnd -= datasize;
+ else
+ rwnd = 0;
+
+ asoc->peer.rwnd = rwnd;
+ /* Has been accepted for transmission. */
+ if (!asoc->peer.prsctp_capable)
+ chunk->msg->can_abandon = 0;
+
+finish:
+ return retval;
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/outqueue.c b/uClinux-2.4.31-uc0/net/sctp/outqueue.c
new file mode 100644
index 0000000..4f641c8
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/outqueue.c
@@ -0,0 +1,1744 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2001, 2004
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001-2003 Intel Corp.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * These functions implement the sctp_outq class. The outqueue handles
+ * bundling and queueing of outgoing SCTP chunks.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Perry Melange <pmelange@null.cc.uic.edu>
+ * Xingang Guo <xingang.guo@intel.com>
+ * Hui Huang <hui.huang@nokia.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ * Jon Grimm <jgrimm@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <linux/list.h> /* For struct list_head */
+#include <linux/socket.h>
+#include <linux/ip.h>
+#include <net/sock.h> /* For skb_set_owner_w */
+
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+/* Declare internal functions here. */
+static int sctp_acked(struct sctp_sackhdr *sack, __u32 tsn);
+static void sctp_check_transmitted(struct sctp_outq *q,
+ struct list_head *transmitted_queue,
+ struct sctp_transport *transport,
+ struct sctp_sackhdr *sack,
+ __u32 highest_new_tsn);
+
+static void sctp_mark_missing(struct sctp_outq *q,
+ struct list_head *transmitted_queue,
+ struct sctp_transport *transport,
+ __u32 highest_new_tsn,
+ int count_of_newacks);
+
+static void sctp_generate_fwdtsn(struct sctp_outq *q, __u32 sack_ctsn);
+
+/* Add data to the front of the queue. */
+static inline void sctp_outq_head_data(struct sctp_outq *q,
+ struct sctp_chunk *ch)
+{
+ __skb_queue_head(&q->out, (struct sk_buff *)ch);
+ q->out_qlen += ch->skb->len;
+ return;
+}
+
+/* Take data from the front of the queue. */
+static inline struct sctp_chunk *sctp_outq_dequeue_data(struct sctp_outq *q)
+{
+ struct sctp_chunk *ch;
+ ch = (struct sctp_chunk *)__skb_dequeue(&q->out);
+ if (ch)
+ q->out_qlen -= ch->skb->len;
+ return ch;
+}
+/* Add data chunk to the end of the queue. */
+static inline void sctp_outq_tail_data(struct sctp_outq *q,
+ struct sctp_chunk *ch)
+{
+ __skb_queue_tail(&q->out, (struct sk_buff *)ch);
+ q->out_qlen += ch->skb->len;
+ return;
+}
+
+/* Insert a chunk behind chunk 'pos'. */
+static inline void sctp_outq_insert_data(struct sctp_outq *q,
+ struct sctp_chunk *ch,
+ struct sctp_chunk *pos)
+{
+ __skb_insert((struct sk_buff *)ch, (struct sk_buff *)pos->prev,
+ (struct sk_buff *)pos, pos->list);
+ q->out_qlen += ch->skb->len;
+}
+
+/*
+ * SFR-CACC algorithm:
+ * D) If count_of_newacks is greater than or equal to 2
+ * and t was not sent to the current primary then the
+ * sender MUST NOT increment missing report count for t.
+ */
+static inline int sctp_cacc_skip_3_1_d(struct sctp_transport *primary,
+ struct sctp_transport *transport,
+ int count_of_newacks)
+{
+ if (count_of_newacks >=2 && transport != primary)
+ return 1;
+ return 0;
+}
+
+/*
+ * SFR-CACC algorithm:
+ * F) If count_of_newacks is less than 2, let d be the
+ * destination to which t was sent. If cacc_saw_newack
+ * is 0 for destination d, then the sender MUST NOT
+ * increment missing report count for t.
+ */
+static inline int sctp_cacc_skip_3_1_f(struct sctp_transport *transport,
+ int count_of_newacks)
+{
+ if (count_of_newacks < 2 && !transport->cacc.cacc_saw_newack)
+ return 1;
+ return 0;
+}
+
+/*
+ * SFR-CACC algorithm:
+ * 3.1) If CYCLING_CHANGEOVER is 0, the sender SHOULD
+ * execute steps C, D, F.
+ *
+ * C has been implemented in sctp_outq_sack
+ */
+static inline int sctp_cacc_skip_3_1(struct sctp_transport *primary,
+ struct sctp_transport *transport,
+ int count_of_newacks)
+{
+ if (!primary->cacc.cycling_changeover) {
+ if (sctp_cacc_skip_3_1_d(primary, transport, count_of_newacks))
+ return 1;
+ if (sctp_cacc_skip_3_1_f(transport, count_of_newacks))
+ return 1;
+ return 0;
+ }
+ return 0;
+}
+
+/*
+ * SFR-CACC algorithm:
+ * 3.2) Else if CYCLING_CHANGEOVER is 1, and t is less
+ * than next_tsn_at_change of the current primary, then
+ * the sender MUST NOT increment missing report count
+ * for t.
+ */
+static inline int sctp_cacc_skip_3_2(struct sctp_transport *primary, __u32 tsn)
+{
+ if (primary->cacc.cycling_changeover &&
+ TSN_lt(tsn, primary->cacc.next_tsn_at_change))
+ return 1;
+ return 0;
+}
+
+/*
+ * SFR-CACC algorithm:
+ * 3) If the missing report count for TSN t is to be
+ * incremented according to [RFC2960] and
+ * [SCTP_STEWART-2002], and CHANGEOVER_ACTIVE is set,
+ * then the sender MUST futher execute steps 3.1 and
+ * 3.2 to determine if the missing report count for
+ * TSN t SHOULD NOT be incremented.
+ *
+ * 3.3) If 3.1 and 3.2 do not dictate that the missing
+ * report count for t should not be incremented, then
+ * the sender SOULD increment missing report count for
+ * t (according to [RFC2960] and [SCTP_STEWART_2002]).
+ */
+static inline int sctp_cacc_skip(struct sctp_transport *primary,
+ struct sctp_transport *transport,
+ int count_of_newacks,
+ __u32 tsn)
+{
+ if (primary->cacc.changeover_active &&
+ (sctp_cacc_skip_3_1(primary, transport, count_of_newacks)
+ || sctp_cacc_skip_3_2(primary, tsn)))
+ return 1;
+ return 0;
+}
+
+/* Initialize an existing sctp_outq. This does the boring stuff.
+ * You still need to define handlers if you really want to DO
+ * something with this structure...
+ */
+void sctp_outq_init(struct sctp_association *asoc, struct sctp_outq *q)
+{
+ q->asoc = asoc;
+ skb_queue_head_init(&q->out);
+ skb_queue_head_init(&q->control);
+ INIT_LIST_HEAD(&q->retransmit);
+ INIT_LIST_HEAD(&q->sacked);
+ INIT_LIST_HEAD(&q->abandoned);
+
+ q->outstanding_bytes = 0;
+ q->empty = 1;
+ q->cork = 0;
+
+ q->malloced = 0;
+ q->out_qlen = 0;
+}
+
+/* Free the outqueue structure and any related pending chunks.
+ */
+void sctp_outq_teardown(struct sctp_outq *q)
+{
+ struct sctp_transport *transport;
+ struct list_head *lchunk, *pos, *temp;
+ struct sctp_chunk *chunk;
+
+ /* Throw away unacknowledged chunks. */
+ list_for_each(pos, &q->asoc->peer.transport_addr_list) {
+ transport = list_entry(pos, struct sctp_transport, transports);
+ while ((lchunk = sctp_list_dequeue(&transport->transmitted)) != NULL) {
+ chunk = list_entry(lchunk, struct sctp_chunk,
+ transmitted_list);
+ /* Mark as part of a failed message. */
+ sctp_chunk_fail(chunk, q->error);
+ sctp_chunk_free(chunk);
+ }
+ }
+
+ /* Throw away chunks that have been gap ACKed. */
+ list_for_each_safe(lchunk, temp, &q->sacked) {
+ list_del_init(lchunk);
+ chunk = list_entry(lchunk, struct sctp_chunk,
+ transmitted_list);
+ sctp_chunk_fail(chunk, q->error);
+ sctp_chunk_free(chunk);
+ }
+
+ /* Throw away any chunks in the retransmit queue. */
+ list_for_each_safe(lchunk, temp, &q->retransmit) {
+ list_del_init(lchunk);
+ chunk = list_entry(lchunk, struct sctp_chunk,
+ transmitted_list);
+ sctp_chunk_fail(chunk, q->error);
+ sctp_chunk_free(chunk);
+ }
+
+ /* Throw away any chunks that are in the abandoned queue. */
+ list_for_each_safe(lchunk, temp, &q->abandoned) {
+ list_del_init(lchunk);
+ chunk = list_entry(lchunk, struct sctp_chunk,
+ transmitted_list);
+ sctp_chunk_fail(chunk, q->error);
+ sctp_chunk_free(chunk);
+ }
+
+ /* Throw away any leftover data chunks. */
+ while ((chunk = sctp_outq_dequeue_data(q)) != NULL) {
+
+ /* Mark as send failure. */
+ sctp_chunk_fail(chunk, q->error);
+ sctp_chunk_free(chunk);
+ }
+
+ q->error = 0;
+
+ /* Throw away any leftover control chunks. */
+ while ((chunk = (struct sctp_chunk *) skb_dequeue(&q->control)) != NULL)
+ sctp_chunk_free(chunk);
+}
+
+/* Free the outqueue structure and any related pending chunks. */
+void sctp_outq_free(struct sctp_outq *q)
+{
+ /* Throw away leftover chunks. */
+ sctp_outq_teardown(q);
+
+ /* If we were kmalloc()'d, free the memory. */
+ if (q->malloced)
+ kfree(q);
+}
+
+/* Put a new chunk in an sctp_outq. */
+int sctp_outq_tail(struct sctp_outq *q, struct sctp_chunk *chunk)
+{
+ int error = 0;
+
+ SCTP_DEBUG_PRINTK("sctp_outq_tail(%p, %p[%s])\n",
+ q, chunk, chunk && chunk->chunk_hdr ?
+ sctp_cname(SCTP_ST_CHUNK(chunk->chunk_hdr->type))
+ : "Illegal Chunk");
+
+ /* If it is data, queue it up, otherwise, send it
+ * immediately.
+ */
+ if (SCTP_CID_DATA == chunk->chunk_hdr->type) {
+ /* Is it OK to queue data chunks? */
+ /* From 9. Termination of Association
+ *
+ * When either endpoint performs a shutdown, the
+ * association on each peer will stop accepting new
+ * data from its user and only deliver data in queue
+ * at the time of sending or receiving the SHUTDOWN
+ * chunk.
+ */
+ switch (q->asoc->state) {
+ case SCTP_STATE_EMPTY:
+ case SCTP_STATE_CLOSED:
+ case SCTP_STATE_SHUTDOWN_PENDING:
+ case SCTP_STATE_SHUTDOWN_SENT:
+ case SCTP_STATE_SHUTDOWN_RECEIVED:
+ case SCTP_STATE_SHUTDOWN_ACK_SENT:
+ /* Cannot send after transport endpoint shutdown */
+ error = -ESHUTDOWN;
+ break;
+
+ default:
+ SCTP_DEBUG_PRINTK("outqueueing (%p, %p[%s])\n",
+ q, chunk, chunk && chunk->chunk_hdr ?
+ sctp_cname(SCTP_ST_CHUNK(chunk->chunk_hdr->type))
+ : "Illegal Chunk");
+
+ sctp_outq_tail_data(q, chunk);
+ if (chunk->chunk_hdr->flags & SCTP_DATA_UNORDERED)
+ SCTP_INC_STATS(SctpOutUnorderChunks);
+ else
+ SCTP_INC_STATS(SctpOutOrderChunks);
+ q->empty = 0;
+ break;
+ };
+ } else {
+ __skb_queue_tail(&q->control, (struct sk_buff *) chunk);
+ SCTP_INC_STATS(SctpOutCtrlChunks);
+ }
+
+ if (error < 0)
+ return error;
+
+ if (!q->cork)
+ error = sctp_outq_flush(q, 0);
+
+ return error;
+}
+
+/* Insert a chunk into the sorted list based on the TSNs. The retransmit list
+ * and the abandoned list are in ascending order.
+ */
+static void sctp_insert_list(struct list_head *head, struct list_head *new)
+{
+ struct list_head *pos;
+ struct sctp_chunk *nchunk, *lchunk;
+ __u32 ntsn, ltsn;
+ int done = 0;
+
+ nchunk = list_entry(new, struct sctp_chunk, transmitted_list);
+ ntsn = ntohl(nchunk->subh.data_hdr->tsn);
+
+ list_for_each(pos, head) {
+ lchunk = list_entry(pos, struct sctp_chunk, transmitted_list);
+ ltsn = ntohl(lchunk->subh.data_hdr->tsn);
+ if (TSN_lt(ntsn, ltsn)) {
+ list_add(new, pos->prev);
+ done = 1;
+ break;
+ }
+ }
+ if (!done)
+ list_add_tail(new, head);
+}
+
+/* Mark all the eligible packets on a transport for retransmission. */
+void sctp_retransmit_mark(struct sctp_outq *q,
+ struct sctp_transport *transport,
+ __u8 fast_retransmit)
+{
+ struct list_head *lchunk, *ltemp;
+ struct sctp_chunk *chunk;
+
+ /* Walk through the specified transmitted queue. */
+ list_for_each_safe(lchunk, ltemp, &transport->transmitted) {
+ chunk = list_entry(lchunk, struct sctp_chunk,
+ transmitted_list);
+
+ /* If the chunk is abandoned, move it to abandoned list. */
+ if (sctp_chunk_abandoned(chunk)) {
+ list_del_init(lchunk);
+ sctp_insert_list(&q->abandoned, lchunk);
+ continue;
+ }
+
+ /* If we are doing retransmission due to a fast retransmit,
+ * only the chunk's that are marked for fast retransmit
+ * should be added to the retransmit queue. If we are doing
+ * retransmission due to a timeout or pmtu discovery, only the
+ * chunks that are not yet acked should be added to the
+ * retransmit queue.
+ */
+ if ((fast_retransmit && chunk->fast_retransmit) ||
+ (!fast_retransmit && !chunk->tsn_gap_acked)) {
+ /* RFC 2960 6.2.1 Processing a Received SACK
+ *
+ * C) Any time a DATA chunk is marked for
+ * retransmission (via either T3-rtx timer expiration
+ * (Section 6.3.3) or via fast retransmit
+ * (Section 7.2.4)), add the data size of those
+ * chunks to the rwnd.
+ */
+ q->asoc->peer.rwnd += sctp_data_size(chunk);
+ q->outstanding_bytes -= sctp_data_size(chunk);
+ transport->flight_size -= sctp_data_size(chunk);
+
+ /* sctpimpguide-05 Section 2.8.2
+ * M5) If a T3-rtx timer expires, the
+ * 'TSN.Missing.Report' of all affected TSNs is set
+ * to 0.
+ */
+ chunk->tsn_missing_report = 0;
+
+ /* If a chunk that is being used for RTT measurement
+ * has to be retransmitted, we cannot use this chunk
+ * anymore for RTT measurements. Reset rto_pending so
+ * that a new RTT measurement is started when a new
+ * data chunk is sent.
+ */
+ if (chunk->rtt_in_progress) {
+ chunk->rtt_in_progress = 0;
+ transport->rto_pending = 0;
+ }
+
+ /* Move the chunk to the retransmit queue. The chunks
+ * on the retransmit queue are always kept in order.
+ */
+ list_del_init(lchunk);
+ sctp_insert_list(&q->retransmit, lchunk);
+ }
+ }
+
+ SCTP_DEBUG_PRINTK("%s: transport: %p, fast_retransmit: %d, "
+ "cwnd: %d, ssthresh: %d, flight_size: %d, "
+ "pba: %d\n", __FUNCTION__,
+ transport, fast_retransmit,
+ transport->cwnd, transport->ssthresh,
+ transport->flight_size,
+ transport->partial_bytes_acked);
+
+}
+
+/* Mark all the eligible packets on a transport for retransmission and force
+ * one packet out.
+ */
+void sctp_retransmit(struct sctp_outq *q, struct sctp_transport *transport,
+ sctp_retransmit_reason_t reason)
+{
+ int error = 0;
+ __u8 fast_retransmit = 0;
+
+ switch(reason) {
+ case SCTP_RTXR_T3_RTX:
+ sctp_transport_lower_cwnd(transport, SCTP_LOWER_CWND_T3_RTX);
+ /* Update the retran path if the T3-rtx timer has expired for
+ * the current retran path.
+ */
+ if (transport == transport->asoc->peer.retran_path)
+ sctp_assoc_update_retran_path(transport->asoc);
+ break;
+ case SCTP_RTXR_FAST_RTX:
+ sctp_transport_lower_cwnd(transport, SCTP_LOWER_CWND_FAST_RTX);
+ fast_retransmit = 1;
+ break;
+ case SCTP_RTXR_PMTUD:
+ default:
+ break;
+ }
+
+ sctp_retransmit_mark(q, transport, fast_retransmit);
+
+ /* PR-SCTP A5) Any time the T3-rtx timer expires, on any destination,
+ * the sender SHOULD try to advance the "Advanced.Peer.Ack.Point" by
+ * following the procedures outlined in C1 - C5.
+ */
+ sctp_generate_fwdtsn(q, q->asoc->ctsn_ack_point);
+
+ error = sctp_outq_flush(q, /* rtx_timeout */ 1);
+
+ if (error)
+ q->asoc->base.sk->err = -error;
+}
+
+/*
+ * Transmit DATA chunks on the retransmit queue. Upon return from
+ * sctp_outq_flush_rtx() the packet 'pkt' may contain chunks which
+ * need to be transmitted by the caller.
+ * We assume that pkt->transport has already been set.
+ *
+ * The return value is a normal kernel error return value.
+ */
+static int sctp_outq_flush_rtx(struct sctp_outq *q, struct sctp_packet *pkt,
+ int rtx_timeout, int *start_timer)
+{
+ struct list_head *lqueue;
+ struct list_head *lchunk, *lchunk1;
+ struct sctp_transport *transport = pkt->transport;
+ sctp_xmit_t status;
+ struct sctp_chunk *chunk, *chunk1;
+ struct sctp_association *asoc;
+ int error = 0;
+
+ asoc = q->asoc;
+ lqueue = &q->retransmit;
+
+ /* RFC 2960 6.3.3 Handle T3-rtx Expiration
+ *
+ * E3) Determine how many of the earliest (i.e., lowest TSN)
+ * outstanding DATA chunks for the address for which the
+ * T3-rtx has expired will fit into a single packet, subject
+ * to the MTU constraint for the path corresponding to the
+ * destination transport address to which the retransmission
+ * is being sent (this may be different from the address for
+ * which the timer expires [see Section 6.4]). Call this value
+ * K. Bundle and retransmit those K DATA chunks in a single
+ * packet to the destination endpoint.
+ *
+ * [Just to be painfully clear, if we are retransmitting
+ * because a timeout just happened, we should send only ONE
+ * packet of retransmitted data.]
+ */
+ lchunk = sctp_list_dequeue(lqueue);
+
+ while (lchunk) {
+ chunk = list_entry(lchunk, struct sctp_chunk,
+ transmitted_list);
+
+ /* Make sure that Gap Acked TSNs are not retransmitted. A
+ * simple approach is just to move such TSNs out of the
+ * way and into a 'transmitted' queue and skip to the
+ * next chunk.
+ */
+ if (chunk->tsn_gap_acked) {
+ list_add_tail(lchunk, &transport->transmitted);
+ lchunk = sctp_list_dequeue(lqueue);
+ continue;
+ }
+
+ /* Attempt to append this chunk to the packet. */
+ status = sctp_packet_append_chunk(pkt, chunk);
+
+ switch (status) {
+ case SCTP_XMIT_PMTU_FULL:
+ /* Send this packet. */
+ if ((error = sctp_packet_transmit(pkt)) == 0)
+ *start_timer = 1;
+
+ /* If we are retransmitting, we should only
+ * send a single packet.
+ */
+ if (rtx_timeout) {
+ list_add(lchunk, lqueue);
+ lchunk = NULL;
+ }
+
+ /* Bundle lchunk in the next round. */
+ break;
+
+ case SCTP_XMIT_RWND_FULL:
+ /* Send this packet. */
+ if ((error = sctp_packet_transmit(pkt)) == 0)
+ *start_timer = 1;
+
+ /* Stop sending DATA as there is no more room
+ * at the receiver.
+ */
+ list_add(lchunk, lqueue);
+ lchunk = NULL;
+ break;
+
+ case SCTP_XMIT_NAGLE_DELAY:
+ /* Send this packet. */
+ if ((error = sctp_packet_transmit(pkt)) == 0)
+ *start_timer = 1;
+
+ /* Stop sending DATA because of nagle delay. */
+ list_add(lchunk, lqueue);
+ lchunk = NULL;
+ break;
+
+ default:
+ /* The append was successful, so add this chunk to
+ * the transmitted list.
+ */
+ list_add_tail(lchunk, &transport->transmitted);
+
+ /* Mark the chunk as ineligible for fast retransmit
+ * after it is retransmitted.
+ */
+ chunk->fast_retransmit = 0;
+
+ *start_timer = 1;
+ q->empty = 0;
+
+ /* Retrieve a new chunk to bundle. */
+ lchunk = sctp_list_dequeue(lqueue);
+ break;
+ };
+
+ /* If we are here due to a retransmit timeout or a fast
+ * retransmit and if there are any chunks left in the retransmit
+ * queue that could not fit in the PMTU sized packet, they need * to be marked as ineligible for a subsequent fast retransmit.
+ */
+ if (rtx_timeout && !lchunk) {
+ list_for_each(lchunk1, lqueue) {
+ chunk1 = list_entry(lchunk1, struct sctp_chunk,
+ transmitted_list);
+ chunk1->fast_retransmit = 0;
+ }
+ }
+ }
+
+ return error;
+}
+
+/* Cork the outqueue so queued chunks are really queued. */
+int sctp_outq_uncork(struct sctp_outq *q)
+{
+ int error = 0;
+ if (q->cork) {
+ q->cork = 0;
+ error = sctp_outq_flush(q, 0);
+ }
+ return error;
+}
+
+/*
+ * Try to flush an outqueue.
+ *
+ * Description: Send everything in q which we legally can, subject to
+ * congestion limitations.
+ * * Note: This function can be called from multiple contexts so appropriate
+ * locking concerns must be made. Today we use the sock lock to protect
+ * this function.
+ */
+int sctp_outq_flush(struct sctp_outq *q, int rtx_timeout)
+{
+ struct sctp_packet *packet;
+ struct sctp_packet singleton;
+ struct sctp_association *asoc = q->asoc;
+ __u16 sport = asoc->base.bind_addr.port;
+ __u16 dport = asoc->peer.port;
+ __u32 vtag = asoc->peer.i.init_tag;
+ struct sk_buff_head *queue;
+ struct sctp_transport *transport = NULL;
+ struct sctp_transport *new_transport;
+ struct sctp_chunk *chunk;
+ sctp_xmit_t status;
+ int error = 0;
+ int start_timer = 0;
+
+ /* These transports have chunks to send. */
+ struct list_head transport_list;
+ struct list_head *ltransport;
+
+ INIT_LIST_HEAD(&transport_list);
+ packet = NULL;
+
+ /*
+ * 6.10 Bundling
+ * ...
+ * When bundling control chunks with DATA chunks, an
+ * endpoint MUST place control chunks first in the outbound
+ * SCTP packet. The transmitter MUST transmit DATA chunks
+ * within a SCTP packet in increasing order of TSN.
+ * ...
+ */
+
+ queue = &q->control;
+ while ((chunk = (struct sctp_chunk *)skb_dequeue(queue)) != NULL) {
+ /* Pick the right transport to use. */
+ new_transport = chunk->transport;
+
+ if (!new_transport) {
+ new_transport = asoc->peer.active_path;
+ } else if (!new_transport->active) {
+ /* If the chunk is Heartbeat or Heartbeat Ack,
+ * send it to chunk->transport, even if it's
+ * inactive.
+ *
+ * 3.3.6 Heartbeat Acknowledgement:
+ * ...
+ * A HEARTBEAT ACK is always sent to the source IP
+ * address of the IP datagram containing the
+ * HEARTBEAT chunk to which this ack is responding.
+ * ...
+ */
+ if (chunk->chunk_hdr->type != SCTP_CID_HEARTBEAT &&
+ chunk->chunk_hdr->type != SCTP_CID_HEARTBEAT_ACK)
+ new_transport = asoc->peer.active_path;
+ }
+
+ /* Are we switching transports?
+ * Take care of transport locks.
+ */
+ if (new_transport != transport) {
+ transport = new_transport;
+ if (list_empty(&transport->send_ready)) {
+ list_add_tail(&transport->send_ready,
+ &transport_list);
+ }
+ packet = &transport->packet;
+ sctp_packet_config(packet, vtag,
+ asoc->peer.ecn_capable);
+ }
+
+ switch (chunk->chunk_hdr->type) {
+ /*
+ * 6.10 Bundling
+ * ...
+ * An endpoint MUST NOT bundle INIT, INIT ACK or SHUTDOWN
+ * COMPLETE with any other chunks. [Send them immediately.]
+ */
+ case SCTP_CID_INIT:
+ case SCTP_CID_INIT_ACK:
+ case SCTP_CID_SHUTDOWN_COMPLETE:
+ sctp_packet_init(&singleton, transport, sport, dport);
+ sctp_packet_config(&singleton, vtag, 0);
+ sctp_packet_append_chunk(&singleton, chunk);
+ error = sctp_packet_transmit(&singleton);
+ if (error < 0)
+ return error;
+ break;
+
+ case SCTP_CID_ABORT:
+ case SCTP_CID_SACK:
+ case SCTP_CID_HEARTBEAT:
+ case SCTP_CID_HEARTBEAT_ACK:
+ case SCTP_CID_SHUTDOWN:
+ case SCTP_CID_SHUTDOWN_ACK:
+ case SCTP_CID_ERROR:
+ case SCTP_CID_COOKIE_ECHO:
+ case SCTP_CID_COOKIE_ACK:
+ case SCTP_CID_ECN_ECNE:
+ case SCTP_CID_ECN_CWR:
+ case SCTP_CID_ASCONF:
+ case SCTP_CID_ASCONF_ACK:
+ case SCTP_CID_FWD_TSN:
+ sctp_packet_transmit_chunk(packet, chunk);
+ break;
+
+ default:
+ /* We built a chunk with an illegal type! */
+ BUG();
+ };
+ }
+
+ /* Is it OK to send data chunks? */
+ switch (asoc->state) {
+ case SCTP_STATE_COOKIE_ECHOED:
+ /* Only allow bundling when this packet has a COOKIE-ECHO
+ * chunk.
+ */
+ if (!packet || !packet->has_cookie_echo)
+ break;
+
+ /* fallthru */
+ case SCTP_STATE_ESTABLISHED:
+ case SCTP_STATE_SHUTDOWN_PENDING:
+ case SCTP_STATE_SHUTDOWN_RECEIVED:
+ /*
+ * RFC 2960 6.1 Transmission of DATA Chunks
+ *
+ * C) When the time comes for the sender to transmit,
+ * before sending new DATA chunks, the sender MUST
+ * first transmit any outstanding DATA chunks which
+ * are marked for retransmission (limited by the
+ * current cwnd).
+ */
+ if (!list_empty(&q->retransmit)) {
+ if (transport == asoc->peer.retran_path)
+ goto retran;
+
+ /* Switch transports & prepare the packet. */
+
+ transport = asoc->peer.retran_path;
+
+ if (list_empty(&transport->send_ready)) {
+ list_add_tail(&transport->send_ready,
+ &transport_list);
+ }
+
+ packet = &transport->packet;
+ sctp_packet_config(packet, vtag,
+ asoc->peer.ecn_capable);
+ retran:
+ error = sctp_outq_flush_rtx(q, packet,
+ rtx_timeout, &start_timer);
+
+ if (start_timer)
+ sctp_transport_reset_timers(transport);
+
+ /* This can happen on COOKIE-ECHO resend. Only
+ * one chunk can get bundled with a COOKIE-ECHO.
+ */
+ if (packet->has_cookie_echo)
+ goto sctp_flush_out;
+
+ /* Don't send new data if there is still data
+ * waiting to retransmit.
+ */
+ if (!list_empty(&q->retransmit))
+ goto sctp_flush_out;
+ }
+
+ /* Finally, transmit new packets. */
+ start_timer = 0;
+ queue = &q->out;
+
+ while ((chunk = sctp_outq_dequeue_data(q)) != NULL) {
+ /* RFC 2960 6.5 Every DATA chunk MUST carry a valid
+ * stream identifier.
+ */
+ if (chunk->sinfo.sinfo_stream >=
+ asoc->c.sinit_num_ostreams) {
+
+ /* Mark as failed send. */
+ sctp_chunk_fail(chunk, SCTP_ERROR_INV_STRM);
+ sctp_chunk_free(chunk);
+ continue;
+ }
+
+ /* Has this chunk expired? */
+ if (sctp_chunk_abandoned(chunk)) {
+ sctp_chunk_fail(chunk, 0);
+ sctp_chunk_free(chunk);
+ continue;
+ }
+
+ /* If there is a specified transport, use it.
+ * Otherwise, we want to use the active path.
+ */
+ new_transport = chunk->transport;
+ if (!new_transport || !new_transport->active)
+ new_transport = asoc->peer.active_path;
+
+ /* Change packets if necessary. */
+ if (new_transport != transport) {
+ transport = new_transport;
+
+ /* Schedule to have this transport's
+ * packet flushed.
+ */
+ if (list_empty(&transport->send_ready)) {
+ list_add_tail(&transport->send_ready,
+ &transport_list);
+ }
+
+ packet = &transport->packet;
+ sctp_packet_config(packet, vtag,
+ asoc->peer.ecn_capable);
+ }
+
+ SCTP_DEBUG_PRINTK("sctp_outq_flush(%p, %p[%s]), ",
+ q, chunk,
+ chunk && chunk->chunk_hdr ?
+ sctp_cname(SCTP_ST_CHUNK(
+ chunk->chunk_hdr->type))
+ : "Illegal Chunk");
+
+ SCTP_DEBUG_PRINTK("TX TSN 0x%x skb->head "
+ "%p skb->users %d.\n",
+ ntohl(chunk->subh.data_hdr->tsn),
+ chunk->skb ?chunk->skb->head : NULL,
+ chunk->skb ?
+ atomic_read(&chunk->skb->users) : -1);
+
+ /* Add the chunk to the packet. */
+ status = sctp_packet_transmit_chunk(packet, chunk);
+
+ switch (status) {
+ case SCTP_XMIT_PMTU_FULL:
+ case SCTP_XMIT_RWND_FULL:
+ case SCTP_XMIT_NAGLE_DELAY:
+ /* We could not append this chunk, so put
+ * the chunk back on the output queue.
+ */
+ SCTP_DEBUG_PRINTK("sctp_outq_flush: could "
+ "not transmit TSN: 0x%x, status: %d\n",
+ ntohl(chunk->subh.data_hdr->tsn),
+ status);
+ sctp_outq_head_data(q, chunk);
+ goto sctp_flush_out;
+ break;
+
+ case SCTP_XMIT_OK:
+ break;
+
+ default:
+ BUG();
+ }
+
+ /* BUG: We assume that the sctp_packet_transmit()
+ * call below will succeed all the time and add the
+ * chunk to the transmitted list and restart the
+ * timers.
+ * It is possible that the call can fail under OOM
+ * conditions.
+ *
+ * Is this really a problem? Won't this behave
+ * like a lost TSN?
+ */
+ list_add_tail(&chunk->transmitted_list,
+ &transport->transmitted);
+
+ sctp_transport_reset_timers(transport);
+
+ q->empty = 0;
+
+ /* Only let one DATA chunk get bundled with a
+ * COOKIE-ECHO chunk.
+ */
+ if (packet->has_cookie_echo)
+ goto sctp_flush_out;
+ }
+ break;
+
+ default:
+ /* Do nothing. */
+ break;
+ }
+
+sctp_flush_out:
+
+ /* Before returning, examine all the transports touched in
+ * this call. Right now, we bluntly force clear all the
+ * transports. Things might change after we implement Nagle.
+ * But such an examination is still required.
+ *
+ * --xguo
+ */
+ while ((ltransport = sctp_list_dequeue(&transport_list)) != NULL ) {
+ struct sctp_transport *t = list_entry(ltransport,
+ struct sctp_transport,
+ send_ready);
+ packet = &t->packet;
+ if (!sctp_packet_empty(packet))
+ error = sctp_packet_transmit(packet);
+ }
+
+ return error;
+}
+
+/* Update unack_data based on the incoming SACK chunk */
+static void sctp_sack_update_unack_data(struct sctp_association *assoc,
+ struct sctp_sackhdr *sack)
+{
+ sctp_sack_variable_t *frags;
+ __u16 unack_data;
+ int i;
+
+ unack_data = assoc->next_tsn - assoc->ctsn_ack_point - 1;
+
+ frags = sack->variable;
+ for (i = 0; i < ntohs(sack->num_gap_ack_blocks); i++) {
+ unack_data -= ((ntohs(frags[i].gab.end) -
+ ntohs(frags[i].gab.start) + 1));
+ }
+
+ assoc->unack_data = unack_data;
+}
+
+/* Return the highest new tsn that is acknowledged by the given SACK chunk. */
+static __u32 sctp_highest_new_tsn(struct sctp_sackhdr *sack,
+ struct sctp_association *asoc)
+{
+ struct list_head *ltransport, *lchunk;
+ struct sctp_transport *transport;
+ struct sctp_chunk *chunk;
+ __u32 highest_new_tsn, tsn;
+ struct list_head *transport_list = &asoc->peer.transport_addr_list;
+
+ highest_new_tsn = ntohl(sack->cum_tsn_ack);
+
+ list_for_each(ltransport, transport_list) {
+ transport = list_entry(ltransport, struct sctp_transport,
+ transports);
+ list_for_each(lchunk, &transport->transmitted) {
+ chunk = list_entry(lchunk, struct sctp_chunk,
+ transmitted_list);
+ tsn = ntohl(chunk->subh.data_hdr->tsn);
+
+ if (!chunk->tsn_gap_acked &&
+ TSN_lt(highest_new_tsn, tsn) &&
+ sctp_acked(sack, tsn))
+ highest_new_tsn = tsn;
+ }
+ }
+
+ return highest_new_tsn;
+}
+
+/* This is where we REALLY process a SACK.
+ *
+ * Process the SACK against the outqueue. Mostly, this just frees
+ * things off the transmitted queue.
+ */
+int sctp_outq_sack(struct sctp_outq *q, struct sctp_sackhdr *sack)
+{
+ struct sctp_association *asoc = q->asoc;
+ struct sctp_transport *transport;
+ struct sctp_chunk *tchunk = NULL;
+ struct list_head *lchunk, *transport_list, *pos, *temp;
+ sctp_sack_variable_t *frags = sack->variable;
+ __u32 sack_ctsn, ctsn, tsn;
+ __u32 highest_tsn, highest_new_tsn;
+ __u32 sack_a_rwnd;
+ unsigned outstanding;
+ struct sctp_transport *primary = asoc->peer.primary_path;
+ int count_of_newacks = 0;
+
+ /* Grab the association's destination address list. */
+ transport_list = &asoc->peer.transport_addr_list;
+
+ sack_ctsn = ntohl(sack->cum_tsn_ack);
+
+ /*
+ * SFR-CACC algorithm:
+ * On receipt of a SACK the sender SHOULD execute the
+ * following statements.
+ *
+ * 1) If the cumulative ack in the SACK passes next tsn_at_change
+ * on the current primary, the CHANGEOVER_ACTIVE flag SHOULD be
+ * cleared. The CYCLING_CHANGEOVER flag SHOULD also be cleared for
+ * all destinations.
+ */
+ if (TSN_lte(primary->cacc.next_tsn_at_change, sack_ctsn)) {
+ primary->cacc.changeover_active = 0;
+ list_for_each(pos, transport_list) {
+ transport = list_entry(pos, struct sctp_transport,
+ transports);
+ transport->cacc.cycling_changeover = 0;
+ }
+ }
+
+ /*
+ * SFR-CACC algorithm:
+ * 2) If the SACK contains gap acks and the flag CHANGEOVER_ACTIVE
+ * is set the receiver of the SACK MUST take the following actions:
+ *
+ * A) Initialize the cacc_saw_newack to 0 for all destination
+ * addresses.
+ */
+ if (sack->num_gap_ack_blocks > 0 &&
+ primary->cacc.changeover_active) {
+ list_for_each(pos, transport_list) {
+ transport = list_entry(pos, struct sctp_transport,
+ transports);
+ transport->cacc.cacc_saw_newack = 0;
+ }
+ }
+
+ /* Get the highest TSN in the sack. */
+ highest_tsn = sack_ctsn;
+ if (sack->num_gap_ack_blocks)
+ highest_tsn +=
+ ntohs(frags[ntohs(sack->num_gap_ack_blocks) - 1].gab.end);
+
+ if (TSN_lt(asoc->highest_sacked, highest_tsn)) {
+ highest_new_tsn = highest_tsn;
+ asoc->highest_sacked = highest_tsn;
+ } else {
+ highest_new_tsn = sctp_highest_new_tsn(sack, asoc);
+ }
+
+ /* Run through the retransmit queue. Credit bytes received
+ * and free those chunks that we can.
+ */
+ sctp_check_transmitted(q, &q->retransmit, NULL, sack, highest_new_tsn);
+ sctp_mark_missing(q, &q->retransmit, NULL, highest_new_tsn, 0);
+
+ /* Run through the transmitted queue.
+ * Credit bytes received and free those chunks which we can.
+ *
+ * This is a MASSIVE candidate for optimization.
+ */
+ list_for_each(pos, transport_list) {
+ transport = list_entry(pos, struct sctp_transport,
+ transports);
+ sctp_check_transmitted(q, &transport->transmitted,
+ transport, sack, highest_new_tsn);
+ /*
+ * SFR-CACC algorithm:
+ * C) Let count_of_newacks be the number of
+ * destinations for which cacc_saw_newack is set.
+ */
+ if (transport->cacc.cacc_saw_newack)
+ count_of_newacks ++;
+ }
+
+ list_for_each(pos, transport_list) {
+ transport = list_entry(pos, struct sctp_transport,
+ transports);
+ sctp_mark_missing(q, &transport->transmitted, transport,
+ highest_new_tsn, count_of_newacks);
+ }
+
+ /* Move the Cumulative TSN Ack Point if appropriate. */
+ if (TSN_lt(asoc->ctsn_ack_point, sack_ctsn))
+ asoc->ctsn_ack_point = sack_ctsn;
+
+ /* Update unack_data field in the assoc. */
+ sctp_sack_update_unack_data(asoc, sack);
+
+ ctsn = asoc->ctsn_ack_point;
+
+ /* Throw away stuff rotting on the sack queue. */
+ list_for_each_safe(lchunk, temp, &q->sacked) {
+ tchunk = list_entry(lchunk, struct sctp_chunk,
+ transmitted_list);
+ tsn = ntohl(tchunk->subh.data_hdr->tsn);
+ if (TSN_lte(tsn, ctsn))
+ sctp_chunk_free(tchunk);
+ }
+
+ /* ii) Set rwnd equal to the newly received a_rwnd minus the
+ * number of bytes still outstanding after processing the
+ * Cumulative TSN Ack and the Gap Ack Blocks.
+ */
+
+ sack_a_rwnd = ntohl(sack->a_rwnd);
+ outstanding = q->outstanding_bytes;
+
+ if (outstanding < sack_a_rwnd)
+ sack_a_rwnd -= outstanding;
+ else
+ sack_a_rwnd = 0;
+
+ asoc->peer.rwnd = sack_a_rwnd;
+
+ sctp_generate_fwdtsn(q, sack_ctsn);
+
+ SCTP_DEBUG_PRINTK("%s: sack Cumulative TSN Ack is 0x%x.\n",
+ __FUNCTION__, sack_ctsn);
+ SCTP_DEBUG_PRINTK("%s: Cumulative TSN Ack of association, "
+ "%p is 0x%x. Adv peer ack point: 0x%x\n",
+ __FUNCTION__, asoc, ctsn, asoc->adv_peer_ack_point);
+
+ /* See if all chunks are acked.
+ * Make sure the empty queue handler will get run later.
+ */
+ q->empty = skb_queue_empty(&q->out) && skb_queue_empty(&q->control) &&
+ list_empty(&q->retransmit);
+ if (!q->empty)
+ goto finish;
+
+ list_for_each(pos, transport_list) {
+ transport = list_entry(pos, struct sctp_transport,
+ transports);
+ q->empty = q->empty && list_empty(&transport->transmitted);
+ if (!q->empty)
+ goto finish;
+ }
+
+ SCTP_DEBUG_PRINTK("sack queue is empty.\n");
+finish:
+ return q->empty;
+}
+
+/* Is the outqueue empty? */
+int sctp_outq_is_empty(const struct sctp_outq *q)
+{
+ return q->empty;
+}
+
+/********************************************************************
+ * 2nd Level Abstractions
+ ********************************************************************/
+
+/* Go through a transport's transmitted list or the association's retransmit
+ * list and move chunks that are acked by the Cumulative TSN Ack to q->sacked.
+ * The retransmit list will not have an associated transport.
+ *
+ * I added coherent debug information output. --xguo
+ *
+ * Instead of printing 'sacked' or 'kept' for each TSN on the
+ * transmitted_queue, we print a range: SACKED: TSN1-TSN2, TSN3, TSN4-TSN5.
+ * KEPT TSN6-TSN7, etc.
+ */
+static void sctp_check_transmitted(struct sctp_outq *q,
+ struct list_head *transmitted_queue,
+ struct sctp_transport *transport,
+ struct sctp_sackhdr *sack,
+ __u32 highest_new_tsn_in_sack)
+{
+ struct list_head *lchunk;
+ struct sctp_chunk *tchunk;
+ struct list_head tlist;
+ __u32 tsn;
+ __u32 sack_ctsn;
+ __u32 rtt;
+ __u8 restart_timer = 0;
+ int bytes_acked = 0;
+
+ /* These state variables are for coherent debug output. --xguo */
+
+#if SCTP_DEBUG
+ __u32 dbg_ack_tsn = 0; /* An ACKed TSN range starts here... */
+ __u32 dbg_last_ack_tsn = 0; /* ...and finishes here. */
+ __u32 dbg_kept_tsn = 0; /* An un-ACKed range starts here... */
+ __u32 dbg_last_kept_tsn = 0; /* ...and finishes here. */
+
+ /* 0 : The last TSN was ACKed.
+ * 1 : The last TSN was NOT ACKed (i.e. KEPT).
+ * -1: We need to initialize.
+ */
+ int dbg_prt_state = -1;
+#endif /* SCTP_DEBUG */
+
+ sack_ctsn = ntohl(sack->cum_tsn_ack);
+
+ INIT_LIST_HEAD(&tlist);
+
+ /* The while loop will skip empty transmitted queues. */
+ while (NULL != (lchunk = sctp_list_dequeue(transmitted_queue))) {
+ tchunk = list_entry(lchunk, struct sctp_chunk,
+ transmitted_list);
+
+ if (sctp_chunk_abandoned(tchunk)) {
+ /* Move the chunk to abandoned list. */
+ sctp_insert_list(&q->abandoned, lchunk);
+ continue;
+ }
+
+ tsn = ntohl(tchunk->subh.data_hdr->tsn);
+ if (sctp_acked(sack, tsn)) {
+ /* If this queue is the retransmit queue, the
+ * retransmit timer has already reclaimed
+ * the outstanding bytes for this chunk, so only
+ * count bytes associated with a transport.
+ */
+ if (transport) {
+ /* If this chunk is being used for RTT
+ * measurement, calculate the RTT and update
+ * the RTO using this value.
+ *
+ * 6.3.1 C5) Karn's algorithm: RTT measurements
+ * MUST NOT be made using packets that were
+ * retransmitted (and thus for which it is
+ * ambiguous whether the reply was for the
+ * first instance of the packet or a later
+ * instance).
+ */
+ if (!tchunk->tsn_gap_acked &&
+ !tchunk->resent &&
+ tchunk->rtt_in_progress) {
+ rtt = jiffies - tchunk->sent_at;
+ sctp_transport_update_rto(transport,
+ rtt);
+ }
+ }
+ if (TSN_lte(tsn, sack_ctsn)) {
+ /* RFC 2960 6.3.2 Retransmission Timer Rules
+ *
+ * R3) Whenever a SACK is received
+ * that acknowledges the DATA chunk
+ * with the earliest outstanding TSN
+ * for that address, restart T3-rtx
+ * timer for that address with its
+ * current RTO.
+ */
+ restart_timer = 1;
+
+ if (!tchunk->tsn_gap_acked) {
+ tchunk->tsn_gap_acked = 1;
+ bytes_acked += sctp_data_size(tchunk);
+ /*
+ * SFR-CACC algorithm:
+ * 2) If the SACK contains gap acks
+ * and the flag CHANGEOVER_ACTIVE is
+ * set the receiver of the SACK MUST
+ * take the following action:
+ *
+ * B) For each TSN t being acked that
+ * has not been acked in any SACK so
+ * far, set cacc_saw_newack to 1 for
+ * the destination that the TSN was
+ * sent to.
+ */
+ if (transport &&
+ sack->num_gap_ack_blocks &&
+ q->asoc->peer.primary_path->cacc.
+ changeover_active)
+ transport->cacc.cacc_saw_newack
+ = 1;
+ }
+
+ list_add_tail(&tchunk->transmitted_list,
+ &q->sacked);
+ } else {
+ /* RFC2960 7.2.4, sctpimpguide-05 2.8.2
+ * M2) Each time a SACK arrives reporting
+ * 'Stray DATA chunk(s)' record the highest TSN
+ * reported as newly acknowledged, call this
+ * value 'HighestTSNinSack'. A newly
+ * acknowledged DATA chunk is one not
+ * previously acknowledged in a SACK.
+ *
+ * When the SCTP sender of data receives a SACK
+ * chunk that acknowledges, for the first time,
+ * the receipt of a DATA chunk, all the still
+ * unacknowledged DATA chunks whose TSN is
+ * older than that newly acknowledged DATA
+ * chunk, are qualified as 'Stray DATA chunks'.
+ */
+ if (!tchunk->tsn_gap_acked) {
+ tchunk->tsn_gap_acked = 1;
+ bytes_acked += sctp_data_size(tchunk);
+ }
+ list_add_tail(lchunk, &tlist);
+ }
+
+#if SCTP_DEBUG
+ switch (dbg_prt_state) {
+ case 0: /* last TSN was ACKed */
+ if (dbg_last_ack_tsn + 1 == tsn) {
+ /* This TSN belongs to the
+ * current ACK range.
+ */
+ break;
+ }
+
+ if (dbg_last_ack_tsn != dbg_ack_tsn) {
+ /* Display the end of the
+ * current range.
+ */
+ SCTP_DEBUG_PRINTK("-%08x",
+ dbg_last_ack_tsn);
+ }
+
+ /* Start a new range. */
+ SCTP_DEBUG_PRINTK(",%08x", tsn);
+ dbg_ack_tsn = tsn;
+ break;
+
+ case 1: /* The last TSN was NOT ACKed. */
+ if (dbg_last_kept_tsn != dbg_kept_tsn) {
+ /* Display the end of current range. */
+ SCTP_DEBUG_PRINTK("-%08x",
+ dbg_last_kept_tsn);
+ }
+
+ SCTP_DEBUG_PRINTK("\n");
+
+ /* FALL THROUGH... */
+ default:
+ /* This is the first-ever TSN we examined. */
+ /* Start a new range of ACK-ed TSNs. */
+ SCTP_DEBUG_PRINTK("ACKed: %08x", tsn);
+ dbg_prt_state = 0;
+ dbg_ack_tsn = tsn;
+ };
+
+ dbg_last_ack_tsn = tsn;
+#endif /* SCTP_DEBUG */
+
+ } else {
+ if (tchunk->tsn_gap_acked) {
+ SCTP_DEBUG_PRINTK("%s: Receiver reneged on "
+ "data TSN: 0x%x\n",
+ __FUNCTION__,
+ tsn);
+ tchunk->tsn_gap_acked = 0;
+
+ bytes_acked -= sctp_data_size(tchunk);
+
+ /* RFC 2960 6.3.2 Retransmission Timer Rules
+ *
+ * R4) Whenever a SACK is received missing a
+ * TSN that was previously acknowledged via a
+ * Gap Ack Block, start T3-rtx for the
+ * destination address to which the DATA
+ * chunk was originally
+ * transmitted if it is not already running.
+ */
+ restart_timer = 1;
+ }
+
+ list_add_tail(lchunk, &tlist);
+
+#if SCTP_DEBUG
+ /* See the above comments on ACK-ed TSNs. */
+ switch (dbg_prt_state) {
+ case 1:
+ if (dbg_last_kept_tsn + 1 == tsn)
+ break;
+
+ if (dbg_last_kept_tsn != dbg_kept_tsn)
+ SCTP_DEBUG_PRINTK("-%08x",
+ dbg_last_kept_tsn);
+
+ SCTP_DEBUG_PRINTK(",%08x", tsn);
+ dbg_kept_tsn = tsn;
+ break;
+
+ case 0:
+ if (dbg_last_ack_tsn != dbg_ack_tsn)
+ SCTP_DEBUG_PRINTK("-%08x",
+ dbg_last_ack_tsn);
+ SCTP_DEBUG_PRINTK("\n");
+
+ /* FALL THROUGH... */
+ default:
+ SCTP_DEBUG_PRINTK("KEPT: %08x",tsn);
+ dbg_prt_state = 1;
+ dbg_kept_tsn = tsn;
+ };
+
+ dbg_last_kept_tsn = tsn;
+#endif /* SCTP_DEBUG */
+ }
+ }
+
+#if SCTP_DEBUG
+ /* Finish off the last range, displaying its ending TSN. */
+ switch (dbg_prt_state) {
+ case 0:
+ if (dbg_last_ack_tsn != dbg_ack_tsn) {
+ SCTP_DEBUG_PRINTK("-%08x\n", dbg_last_ack_tsn);
+ } else {
+ SCTP_DEBUG_PRINTK("\n");
+ }
+ break;
+
+ case 1:
+ if (dbg_last_kept_tsn != dbg_kept_tsn) {
+ SCTP_DEBUG_PRINTK("-%08x\n", dbg_last_kept_tsn);
+ } else {
+ SCTP_DEBUG_PRINTK("\n");
+ }
+ };
+#endif /* SCTP_DEBUG */
+ if (transport) {
+ if (bytes_acked) {
+ /* 8.2. When an outstanding TSN is acknowledged,
+ * the endpoint shall clear the error counter of
+ * the destination transport address to which the
+ * DATA chunk was last sent.
+ * The association's overall error counter is
+ * also cleared.
+ */
+ transport->error_count = 0;
+ transport->asoc->overall_error_count = 0;
+
+ /* Mark the destination transport address as
+ * active if it is not so marked.
+ */
+ if (!transport->active) {
+ sctp_assoc_control_transport(
+ transport->asoc,
+ transport,
+ SCTP_TRANSPORT_UP,
+ SCTP_RECEIVED_SACK);
+ }
+
+ sctp_transport_raise_cwnd(transport, sack_ctsn,
+ bytes_acked);
+
+ transport->flight_size -= bytes_acked;
+ q->outstanding_bytes -= bytes_acked;
+ } else {
+ /* RFC 2960 6.1, sctpimpguide-06 2.15.2
+ * When a sender is doing zero window probing, it
+ * should not timeout the association if it continues
+ * to receive new packets from the receiver. The
+ * reason is that the receiver MAY keep its window
+ * closed for an indefinite time.
+ * A sender is doing zero window probing when the
+ * receiver's advertised window is zero, and there is
+ * only one data chunk in flight to the receiver.
+ */
+ if (!q->asoc->peer.rwnd &&
+ !list_empty(&tlist) &&
+ (sack_ctsn+2 == q->asoc->next_tsn)) {
+ SCTP_DEBUG_PRINTK("%s: SACK received for zero "
+ "window probe: %u\n",
+ __FUNCTION__, sack_ctsn);
+ q->asoc->overall_error_count = 0;
+ transport->error_count = 0;
+ }
+ }
+
+ /* RFC 2960 6.3.2 Retransmission Timer Rules
+ *
+ * R2) Whenever all outstanding data sent to an address have
+ * been acknowledged, turn off the T3-rtx timer of that
+ * address.
+ */
+ if (!transport->flight_size) {
+ if (timer_pending(&transport->T3_rtx_timer) &&
+ del_timer(&transport->T3_rtx_timer)) {
+ sctp_transport_put(transport);
+ }
+ } else if (restart_timer) {
+ if (!mod_timer(&transport->T3_rtx_timer,
+ jiffies + transport->rto))
+ sctp_transport_hold(transport);
+ }
+ }
+
+ list_splice(&tlist, transmitted_queue);
+}
+
+/* Mark chunks as missing and consequently may get retransmitted. */
+static void sctp_mark_missing(struct sctp_outq *q,
+ struct list_head *transmitted_queue,
+ struct sctp_transport *transport,
+ __u32 highest_new_tsn_in_sack,
+ int count_of_newacks)
+{
+ struct sctp_chunk *chunk;
+ struct list_head *pos;
+ __u32 tsn;
+ char do_fast_retransmit = 0;
+ struct sctp_transport *primary = q->asoc->peer.primary_path;
+
+ list_for_each(pos, transmitted_queue) {
+
+ chunk = list_entry(pos, struct sctp_chunk, transmitted_list);
+ tsn = ntohl(chunk->subh.data_hdr->tsn);
+
+ /* RFC 2960 7.2.4, sctpimpguide-05 2.8.2 M3) Examine all
+ * 'Unacknowledged TSN's', if the TSN number of an
+ * 'Unacknowledged TSN' is smaller than the 'HighestTSNinSack'
+ * value, increment the 'TSN.Missing.Report' count on that
+ * chunk if it has NOT been fast retransmitted or marked for
+ * fast retransmit already.
+ */
+ if (!chunk->fast_retransmit &&
+ !chunk->tsn_gap_acked &&
+ TSN_lt(tsn, highest_new_tsn_in_sack)) {
+
+ /* SFR-CACC may require us to skip marking
+ * this chunk as missing.
+ */
+ if (!transport || !sctp_cacc_skip(primary, transport,
+ count_of_newacks, tsn)) {
+ chunk->tsn_missing_report++;
+
+ SCTP_DEBUG_PRINTK(
+ "%s: TSN 0x%x missing counter: %d\n",
+ __FUNCTION__, tsn,
+ chunk->tsn_missing_report);
+ }
+ }
+ /*
+ * M4) If any DATA chunk is found to have a
+ * 'TSN.Missing.Report'
+ * value larger than or equal to 4, mark that chunk for
+ * retransmission and start the fast retransmit procedure.
+ */
+
+ if (chunk->tsn_missing_report >= 4) {
+ chunk->fast_retransmit = 1;
+ do_fast_retransmit = 1;
+ }
+ }
+
+ if (transport) {
+ if (do_fast_retransmit)
+ sctp_retransmit(q, transport, SCTP_RTXR_FAST_RTX);
+
+ SCTP_DEBUG_PRINTK("%s: transport: %p, cwnd: %d, "
+ "ssthresh: %d, flight_size: %d, pba: %d\n",
+ __FUNCTION__, transport, transport->cwnd,
+ transport->ssthresh, transport->flight_size,
+ transport->partial_bytes_acked);
+ }
+}
+
+/* Is the given TSN acked by this packet? */
+static int sctp_acked(struct sctp_sackhdr *sack, __u32 tsn)
+{
+ int i;
+ sctp_sack_variable_t *frags;
+ __u16 gap;
+ __u32 ctsn = ntohl(sack->cum_tsn_ack);
+
+ if (TSN_lte(tsn, ctsn))
+ goto pass;
+
+ /* 3.3.4 Selective Acknowledgement (SACK) (3):
+ *
+ * Gap Ack Blocks:
+ * These fields contain the Gap Ack Blocks. They are repeated
+ * for each Gap Ack Block up to the number of Gap Ack Blocks
+ * defined in the Number of Gap Ack Blocks field. All DATA
+ * chunks with TSNs greater than or equal to (Cumulative TSN
+ * Ack + Gap Ack Block Start) and less than or equal to
+ * (Cumulative TSN Ack + Gap Ack Block End) of each Gap Ack
+ * Block are assumed to have been received correctly.
+ */
+
+ frags = sack->variable;
+ gap = tsn - ctsn;
+ for (i = 0; i < ntohs(sack->num_gap_ack_blocks); ++i) {
+ if (TSN_lte(ntohs(frags[i].gab.start), gap) &&
+ TSN_lte(gap, ntohs(frags[i].gab.end)))
+ goto pass;
+ }
+
+ return 0;
+pass:
+ return 1;
+}
+
+static inline int sctp_get_skip_pos(struct sctp_fwdtsn_skip *skiplist,
+ int nskips, __u16 stream)
+{
+ int i;
+
+ for (i = 0; i < nskips; i++) {
+ if (skiplist[i].stream == stream)
+ return i;
+ }
+ return i;
+}
+
+/* Create and add a fwdtsn chunk to the outq's control queue if needed. */
+static void sctp_generate_fwdtsn(struct sctp_outq *q, __u32 ctsn)
+{
+ struct sctp_association *asoc = q->asoc;
+ struct sctp_chunk *ftsn_chunk = NULL;
+ struct sctp_fwdtsn_skip ftsn_skip_arr[10];
+ int nskips = 0;
+ int skip_pos = 0;
+ __u32 tsn;
+ struct sctp_chunk *chunk;
+ struct list_head *lchunk, *temp;
+
+ /* PR-SCTP C1) Let SackCumAck be the Cumulative TSN ACK carried in the
+ * received SACK.
+ *
+ * If (Advanced.Peer.Ack.Point < SackCumAck), then update
+ * Advanced.Peer.Ack.Point to be equal to SackCumAck.
+ */
+ if (TSN_lt(asoc->adv_peer_ack_point, ctsn))
+ asoc->adv_peer_ack_point = ctsn;
+
+ /* PR-SCTP C2) Try to further advance the "Advanced.Peer.Ack.Point"
+ * locally, that is, to move "Advanced.Peer.Ack.Point" up as long as
+ * the chunk next in the out-queue space is marked as "abandoned" as
+ * shown in the following example:
+ *
+ * Assuming that a SACK arrived with the Cumulative TSN ACK 102
+ * and the Advanced.Peer.Ack.Point is updated to this value:
+ *
+ * out-queue at the end of ==> out-queue after Adv.Ack.Point
+ * normal SACK processing local advancement
+ * ... ...
+ * Adv.Ack.Pt-> 102 acked 102 acked
+ * 103 abandoned 103 abandoned
+ * 104 abandoned Adv.Ack.P-> 104 abandoned
+ * 105 105
+ * 106 acked 106 acked
+ * ... ...
+ *
+ * In this example, the data sender successfully advanced the
+ * "Advanced.Peer.Ack.Point" from 102 to 104 locally.
+ */
+ list_for_each_safe(lchunk, temp, &q->abandoned) {
+ chunk = list_entry(lchunk, struct sctp_chunk,
+ transmitted_list);
+ tsn = ntohl(chunk->subh.data_hdr->tsn);
+
+ /* Remove any chunks in the abandoned queue that are acked by
+ * the ctsn.
+ */
+ if (TSN_lte(tsn, ctsn)) {
+ list_del_init(lchunk);
+ if (!chunk->tsn_gap_acked) {
+ chunk->transport->flight_size -=
+ sctp_data_size(chunk);
+ q->outstanding_bytes -= sctp_data_size(chunk);
+ }
+ sctp_chunk_free(chunk);
+ } else {
+ if (TSN_lte(tsn, asoc->adv_peer_ack_point+1)) {
+ asoc->adv_peer_ack_point = tsn;
+ if (chunk->chunk_hdr->flags &
+ SCTP_DATA_UNORDERED)
+ continue;
+ skip_pos = sctp_get_skip_pos(&ftsn_skip_arr[0],
+ nskips,
+ chunk->subh.data_hdr->stream);
+ ftsn_skip_arr[skip_pos].stream =
+ chunk->subh.data_hdr->stream;
+ ftsn_skip_arr[skip_pos].ssn =
+ chunk->subh.data_hdr->ssn;
+ if (skip_pos == nskips)
+ nskips++;
+ if (nskips == 10)
+ break;
+ } else
+ break;
+ }
+ }
+
+ /* PR-SCTP C3) If, after step C1 and C2, the "Advanced.Peer.Ack.Point"
+ * is greater than the Cumulative TSN ACK carried in the received
+ * SACK, the data sender MUST send the data receiver a FORWARD TSN
+ * chunk containing the latest value of the
+ * "Advanced.Peer.Ack.Point".
+ *
+ * C4) For each "abandoned" TSN the sender of the FORWARD TSN SHOULD
+ * list each stream and sequence number in the forwarded TSN. This
+ * information will enable the receiver to easily find any
+ * stranded TSN's waiting on stream reorder queues. Each stream
+ * SHOULD only be reported once; this means that if multiple
+ * abandoned messages occur in the same stream then only the
+ * highest abandoned stream sequence number is reported. If the
+ * total size of the FORWARD TSN does NOT fit in a single MTU then
+ * the sender of the FORWARD TSN SHOULD lower the
+ * Advanced.Peer.Ack.Point to the last TSN that will fit in a
+ * single MTU.
+ */
+ if (asoc->adv_peer_ack_point > ctsn)
+ ftsn_chunk = sctp_make_fwdtsn(asoc, asoc->adv_peer_ack_point,
+ nskips, &ftsn_skip_arr[0]);
+
+ if (ftsn_chunk) {
+ __skb_queue_tail(&q->control, (struct sk_buff *)ftsn_chunk);
+ SCTP_INC_STATS(SctpOutCtrlChunks);
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/primitive.c b/uClinux-2.4.31-uc0/net/sctp/primitive.c
new file mode 100644
index 0000000..3a7ebfc
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/primitive.c
@@ -0,0 +1,219 @@
+/* SCTP kernel reference Implementation
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * These functions implement the SCTP primitive functions from Section 10.
+ *
+ * Note that the descriptions from the specification are USER level
+ * functions--this file is the functions which populate the struct proto
+ * for SCTP which is the BOTTOM of the sockets interface.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Narasimha Budihal <narasimha@refcode.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Ardelle Fan <ardelle.fan@intel.com>
+ * Kevin Gao <kevin.gao@intel.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <linux/list.h> /* For struct list_head */
+#include <linux/socket.h>
+#include <linux/ip.h>
+#include <linux/time.h> /* For struct timeval */
+#include <net/sock.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+#define DECLARE_PRIMITIVE(name) \
+/* This is called in the code as sctp_primitive_ ## name. */ \
+int sctp_primitive_ ## name(struct sctp_association *asoc, \
+ void *arg) { \
+ int error = 0; \
+ sctp_event_t event_type; sctp_subtype_t subtype; \
+ sctp_state_t state; \
+ struct sctp_endpoint *ep; \
+ \
+ event_type = SCTP_EVENT_T_PRIMITIVE; \
+ subtype = SCTP_ST_PRIMITIVE(SCTP_PRIMITIVE_ ## name); \
+ state = asoc ? asoc->state : SCTP_STATE_CLOSED; \
+ ep = asoc ? asoc->ep : NULL; \
+ \
+ error = sctp_do_sm(event_type, subtype, state, ep, asoc, \
+ arg, GFP_KERNEL); \
+ return error; \
+}
+
+/* 10.1 ULP-to-SCTP
+ * B) Associate
+ *
+ * Format: ASSOCIATE(local SCTP instance name, destination transport addr,
+ * outbound stream count)
+ * -> association id [,destination transport addr list] [,outbound stream
+ * count]
+ *
+ * This primitive allows the upper layer to initiate an association to a
+ * specific peer endpoint.
+ *
+ * This version assumes that asoc is fully populated with the initial
+ * parameters. We then return a traditional kernel indicator of
+ * success or failure.
+ */
+
+/* This is called in the code as sctp_primitive_ASSOCIATE. */
+
+DECLARE_PRIMITIVE(ASSOCIATE)
+
+/* 10.1 ULP-to-SCTP
+ * C) Shutdown
+ *
+ * Format: SHUTDOWN(association id)
+ * -> result
+ *
+ * Gracefully closes an association. Any locally queued user data
+ * will be delivered to the peer. The association will be terminated only
+ * after the peer acknowledges all the SCTP packets sent. A success code
+ * will be returned on successful termination of the association. If
+ * attempting to terminate the association results in a failure, an error
+ * code shall be returned.
+ */
+
+DECLARE_PRIMITIVE(SHUTDOWN);
+
+/* 10.1 ULP-to-SCTP
+ * C) Abort
+ *
+ * Format: Abort(association id [, cause code])
+ * -> result
+ *
+ * Ungracefully closes an association. Any locally queued user data
+ * will be discarded and an ABORT chunk is sent to the peer. A success
+ * code will be returned on successful abortion of the association. If
+ * attempting to abort the association results in a failure, an error
+ * code shall be returned.
+ */
+
+DECLARE_PRIMITIVE(ABORT);
+
+/* 10.1 ULP-to-SCTP
+ * E) Send
+ *
+ * Format: SEND(association id, buffer address, byte count [,context]
+ * [,stream id] [,life time] [,destination transport address]
+ * [,unorder flag] [,no-bundle flag] [,payload protocol-id] )
+ * -> result
+ *
+ * This is the main method to send user data via SCTP.
+ *
+ * Mandatory attributes:
+ *
+ * o association id - local handle to the SCTP association
+ *
+ * o buffer address - the location where the user message to be
+ * transmitted is stored;
+ *
+ * o byte count - The size of the user data in number of bytes;
+ *
+ * Optional attributes:
+ *
+ * o context - an optional 32 bit integer that will be carried in the
+ * sending failure notification to the ULP if the transportation of
+ * this User Message fails.
+ *
+ * o stream id - to indicate which stream to send the data on. If not
+ * specified, stream 0 will be used.
+ *
+ * o life time - specifies the life time of the user data. The user data
+ * will not be sent by SCTP after the life time expires. This
+ * parameter can be used to avoid efforts to transmit stale
+ * user messages. SCTP notifies the ULP if the data cannot be
+ * initiated to transport (i.e. sent to the destination via SCTP's
+ * send primitive) within the life time variable. However, the
+ * user data will be transmitted if SCTP has attempted to transmit a
+ * chunk before the life time expired.
+ *
+ * o destination transport address - specified as one of the destination
+ * transport addresses of the peer endpoint to which this packet
+ * should be sent. Whenever possible, SCTP should use this destination
+ * transport address for sending the packets, instead of the current
+ * primary path.
+ *
+ * o unorder flag - this flag, if present, indicates that the user
+ * would like the data delivered in an unordered fashion to the peer
+ * (i.e., the U flag is set to 1 on all DATA chunks carrying this
+ * message).
+ *
+ * o no-bundle flag - instructs SCTP not to bundle this user data with
+ * other outbound DATA chunks. SCTP MAY still bundle even when
+ * this flag is present, when faced with network congestion.
+ *
+ * o payload protocol-id - A 32 bit unsigned integer that is to be
+ * passed to the peer indicating the type of payload protocol data
+ * being transmitted. This value is passed as opaque data by SCTP.
+ */
+
+DECLARE_PRIMITIVE(SEND);
+
+/* 10.1 ULP-to-SCTP
+ * J) Request Heartbeat
+ *
+ * Format: REQUESTHEARTBEAT(association id, destination transport address)
+ *
+ * -> result
+ *
+ * Instructs the local endpoint to perform a HeartBeat on the specified
+ * destination transport address of the given association. The returned
+ * result should indicate whether the transmission of the HEARTBEAT
+ * chunk to the destination address is successful.
+ *
+ * Mandatory attributes:
+ *
+ * o association id - local handle to the SCTP association
+ *
+ * o destination transport address - the transport address of the
+ * association on which a heartbeat should be issued.
+ */
+
+DECLARE_PRIMITIVE(REQUESTHEARTBEAT);
+
+/* ADDIP
+* 3.1.1 Address Configuration Change Chunk (ASCONF)
+*
+* This chunk is used to communicate to the remote endpoint one of the
+* configuration change requests that MUST be acknowledged. The
+* information carried in the ASCONF Chunk uses the form of a
+* Type-Length-Value (TLV), as described in "3.2.1 Optional/
+* Variable-length Parameter Format" in RFC2960 [5], forall variable
+* parameters.
+*/
+
+DECLARE_PRIMITIVE(ASCONF);
diff --git a/uClinux-2.4.31-uc0/net/sctp/proc.c b/uClinux-2.4.31-uc0/net/sctp/proc.c
new file mode 100644
index 0000000..b3877a0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/proc.c
@@ -0,0 +1,288 @@
+/* SCTP kernel reference Implementation
+ * Copyright (c) 2003 International Business Machines, Corp.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * Sridhar Samudrala <sri@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <linux/seq_file.h>
+#include <linux/init.h>
+#include <net/sctp/sctp.h>
+
+static char *sctp_snmp_list[] = {
+#define SCTP_SNMP_ENTRY(x) #x
+ SCTP_SNMP_ENTRY(SctpCurrEstab),
+ SCTP_SNMP_ENTRY(SctpActiveEstabs),
+ SCTP_SNMP_ENTRY(SctpPassiveEstabs),
+ SCTP_SNMP_ENTRY(SctpAborteds),
+ SCTP_SNMP_ENTRY(SctpShutdowns),
+ SCTP_SNMP_ENTRY(SctpOutOfBlues),
+ SCTP_SNMP_ENTRY(SctpChecksumErrors),
+ SCTP_SNMP_ENTRY(SctpOutCtrlChunks),
+ SCTP_SNMP_ENTRY(SctpOutOrderChunks),
+ SCTP_SNMP_ENTRY(SctpOutUnorderChunks),
+ SCTP_SNMP_ENTRY(SctpInCtrlChunks),
+ SCTP_SNMP_ENTRY(SctpInOrderChunks),
+ SCTP_SNMP_ENTRY(SctpInUnorderChunks),
+ SCTP_SNMP_ENTRY(SctpFragUsrMsgs),
+ SCTP_SNMP_ENTRY(SctpReasmUsrMsgs),
+ SCTP_SNMP_ENTRY(SctpOutSCTPPacks),
+ SCTP_SNMP_ENTRY(SctpInSCTPPacks),
+#undef SCTP_SNMP_ENTRY
+};
+
+/* Return the current value of a particular entry in the mib by adding its
+ * per cpu counters.
+ */
+static unsigned long
+fold_field(void *mib[], int nr)
+{
+ unsigned long res = 0;
+ int i;
+ int sz = sizeof(struct sctp_mib);
+ unsigned long* begin;
+
+ sz /= sizeof(unsigned long);
+ begin = (unsigned long*) mib;
+
+ for (i=0; i<smp_num_cpus; i++) {
+ res += begin[2*cpu_logical_map(i)*sz + nr];
+ res += begin[(2*cpu_logical_map(i)+1)*sz + nr];
+ }
+ return res;
+}
+
+/* Display sctp snmp mib statistics(/proc/net/sctp/snmp). */
+static int sctp_snmp_seq_show(struct seq_file *seq, void *v)
+{
+ int i;
+
+ for (i = 0; i < sizeof(sctp_snmp_list) / sizeof(char *); i++)
+ seq_printf(seq, "%-32s\t%ld\n", sctp_snmp_list[i],
+ fold_field((void **)sctp_statistics, i));
+
+ return 0;
+}
+
+/* Initialize the seq file operations for 'snmp' object. */
+static int sctp_snmp_seq_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, sctp_snmp_seq_show, NULL);
+}
+
+static struct file_operations sctp_snmp_seq_fops = {
+ .owner = THIS_MODULE,
+ .open = sctp_snmp_seq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/* Set up the proc fs entry for 'snmp' object. */
+int __init sctp_snmp_proc_init(void)
+{
+ struct proc_dir_entry *p;
+
+ p = create_proc_entry("snmp", S_IRUGO, proc_net_sctp);
+ if (!p)
+ return -ENOMEM;
+
+ p->proc_fops = &sctp_snmp_seq_fops;
+
+ return 0;
+}
+
+/* Cleanup the proc fs entry for 'snmp' object. */
+void sctp_snmp_proc_exit(void)
+{
+ remove_proc_entry("snmp", proc_net_sctp);
+}
+
+/* Dump local addresses of an association/endpoint. */
+static void sctp_seq_dump_local_addrs(struct seq_file *seq, struct sctp_ep_common *epb)
+{
+ struct list_head *pos;
+ struct sctp_sockaddr_entry *laddr;
+ union sctp_addr *addr;
+ struct sctp_af *af;
+
+ list_for_each(pos, &epb->bind_addr.address_list) {
+ laddr = list_entry(pos, struct sctp_sockaddr_entry, list);
+ addr = (union sctp_addr *)&laddr->a;
+ af = sctp_get_af_specific(addr->sa.sa_family);
+ af->seq_dump_addr(seq, addr);
+ }
+}
+
+/* Dump remote addresses of an association. */
+static void sctp_seq_dump_remote_addrs(struct seq_file *seq, struct sctp_association *assoc)
+{
+ struct list_head *pos;
+ struct sctp_transport *transport;
+ union sctp_addr *addr;
+ struct sctp_af *af;
+
+ list_for_each(pos, &assoc->peer.transport_addr_list) {
+ transport = list_entry(pos, struct sctp_transport, transports);
+ addr = (union sctp_addr *)&transport->ipaddr;
+ af = sctp_get_af_specific(addr->sa.sa_family);
+ af->seq_dump_addr(seq, addr);
+ }
+}
+
+/* Display sctp endpoints (/proc/net/sctp/eps). */
+static int sctp_eps_seq_show(struct seq_file *seq, void *v)
+{
+ struct sctp_hashbucket *head;
+ struct sctp_ep_common *epb;
+ struct sctp_endpoint *ep;
+ struct sock *sk;
+ int hash;
+
+ seq_printf(seq, " ENDPT SOCK STY SST HBKT LPORT LADDRS\n");
+ for (hash = 0; hash < sctp_ep_hashsize; hash++) {
+ head = &sctp_ep_hashtable[hash];
+ read_lock(&head->lock);
+ for (epb = head->chain; epb; epb = epb->next) {
+ ep = sctp_ep(epb);
+ sk = epb->sk;
+ seq_printf(seq, "%8p %8p %-3d %-3d %-4d %-5d ", ep, sk,
+ sctp_sk(sk)->type, sk->state, hash,
+ epb->bind_addr.port);
+ sctp_seq_dump_local_addrs(seq, epb);
+ seq_printf(seq, "\n");
+ }
+ read_unlock(&head->lock);
+ }
+
+ return 0;
+}
+
+/* Initialize the seq file operations for 'eps' object. */
+static int sctp_eps_seq_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, sctp_eps_seq_show, NULL);
+}
+
+static struct file_operations sctp_eps_seq_fops = {
+ .open = sctp_eps_seq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/* Set up the proc fs entry for 'eps' object. */
+int __init sctp_eps_proc_init(void)
+{
+ struct proc_dir_entry *p;
+
+ p = create_proc_entry("eps", S_IRUGO, proc_net_sctp);
+ if (!p)
+ return -ENOMEM;
+
+ p->proc_fops = &sctp_eps_seq_fops;
+
+ return 0;
+}
+
+/* Cleanup the proc fs entry for 'eps' object. */
+void sctp_eps_proc_exit(void)
+{
+ remove_proc_entry("eps", proc_net_sctp);
+}
+
+/* Display sctp associations (/proc/net/sctp/assocs). */
+static int sctp_assocs_seq_show(struct seq_file *seq, void *v)
+{
+ struct sctp_hashbucket *head;
+ struct sctp_ep_common *epb;
+ struct sctp_association *assoc;
+ struct sock *sk;
+ int hash;
+
+ seq_printf(seq, " ASSOC SOCK STY SST ST HBKT LPORT RPORT "
+ "LADDRS <-> RADDRS\n");
+ for (hash = 0; hash < sctp_assoc_hashsize; hash++) {
+ head = &sctp_assoc_hashtable[hash];
+ read_lock(&head->lock);
+ for (epb = head->chain; epb; epb = epb->next) {
+ assoc = sctp_assoc(epb);
+ sk = epb->sk;
+ seq_printf(seq,
+ "%8p %8p %-3d %-3d %-2d %-4d %-5d %-5d ",
+ assoc, sk, sctp_sk(sk)->type, sk->state,
+ assoc->state, hash, epb->bind_addr.port,
+ assoc->peer.port);
+ sctp_seq_dump_local_addrs(seq, epb);
+ seq_printf(seq, "<-> ");
+ sctp_seq_dump_remote_addrs(seq, assoc);
+ seq_printf(seq, "\n");
+ }
+ read_unlock(&head->lock);
+ }
+
+ return 0;
+}
+
+/* Initialize the seq file operations for 'assocs' object. */
+static int sctp_assocs_seq_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, sctp_assocs_seq_show, NULL);
+}
+
+static struct file_operations sctp_assocs_seq_fops = {
+ .open = sctp_assocs_seq_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+/* Set up the proc fs entry for 'assocs' object. */
+int __init sctp_assocs_proc_init(void)
+{
+ struct proc_dir_entry *p;
+
+ p = create_proc_entry("assocs", S_IRUGO, proc_net_sctp);
+ if (!p)
+ return -ENOMEM;
+
+ p->proc_fops = &sctp_assocs_seq_fops;
+
+ return 0;
+}
+
+/* Cleanup the proc fs entry for 'assocs' object. */
+void sctp_assocs_proc_exit(void)
+{
+ remove_proc_entry("assocs", proc_net_sctp);
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/protocol.c b/uClinux-2.4.31-uc0/net/sctp/protocol.c
new file mode 100644
index 0000000..39afcdc
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/protocol.c
@@ -0,0 +1,1207 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2001, 2004
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001 Intel Corp.
+ * Copyright (c) 2001 Nokia, Inc.
+ * Copyright (c) 2001 La Monte H.P. Yarroll
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * Initialization/cleanup for SCTP protocol support.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ * Daisy Chang <daisyc@us.ibm.com>
+ * Ardelle Fan <ardelle.fan@intel.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/netdevice.h>
+#include <linux/inetdevice.h>
+#include <linux/seq_file.h>
+#include <net/protocol.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/sctp/sctp.h>
+#include <net/addrconf.h>
+#include <net/inet_common.h>
+#include <net/inet_ecn.h>
+
+/* Global data structures. */
+struct sctp_globals sctp_globals;
+struct proc_dir_entry *proc_net_sctp;
+DEFINE_SNMP_STAT(struct sctp_mib, sctp_statistics);
+
+/* This is the global socket data structure used for responding to
+ * the Out-of-the-blue (OOTB) packets. A control sock will be created
+ * for this socket at the initialization time.
+ */
+static struct socket *sctp_ctl_socket;
+
+static struct sctp_pf *sctp_pf_inet6_specific;
+static struct sctp_pf *sctp_pf_inet_specific;
+static struct sctp_af *sctp_af_v4_specific;
+static struct sctp_af *sctp_af_v6_specific;
+
+kmem_cache_t *sctp_chunk_cachep;
+
+extern struct net_proto_family inet_family_ops;
+
+extern int sctp_snmp_proc_init(void);
+extern int sctp_snmp_proc_exit(void);
+extern int sctp_eps_proc_init(void);
+extern int sctp_eps_proc_exit(void);
+extern int sctp_assocs_proc_init(void);
+extern int sctp_assocs_proc_exit(void);
+
+/* Return the address of the control sock. */
+struct sock *sctp_get_ctl_sock(void)
+{
+ return sctp_ctl_socket->sk;
+}
+
+/* Set up the proc fs entry for the SCTP protocol. */
+static __init int sctp_proc_init(void)
+{
+ if (!proc_net_sctp) {
+ struct proc_dir_entry *ent;
+ ent = proc_mkdir("net/sctp", NULL);
+ if (ent) {
+ ent->owner = THIS_MODULE;
+ proc_net_sctp = ent;
+ } else
+ goto out_nomem;
+ }
+
+ if (sctp_snmp_proc_init())
+ goto out_nomem;
+ if (sctp_eps_proc_init())
+ goto out_nomem;
+ if (sctp_assocs_proc_init())
+ goto out_nomem;
+
+ return 0;
+
+out_nomem:
+ return -ENOMEM;
+}
+
+/* Clean up the proc fs entry for the SCTP protocol.
+ * Note: Do not make this __exit as it is used in the init error
+ * path.
+ */
+static void sctp_proc_exit(void)
+{
+ sctp_snmp_proc_exit();
+ sctp_eps_proc_exit();
+ sctp_assocs_proc_exit();
+
+ if (proc_net_sctp) {
+ proc_net_sctp = NULL;
+ remove_proc_entry("net/sctp", 0);
+ }
+}
+
+/* Private helper to extract ipv4 address and stash them in
+ * the protocol structure.
+ */
+static void sctp_v4_copy_addrlist(struct list_head *addrlist,
+ struct net_device *dev)
+{
+ struct in_device *in_dev;
+ struct in_ifaddr *ifa;
+ struct sctp_sockaddr_entry *addr;
+
+ read_lock(&inetdev_lock);
+ if ((in_dev = __in_dev_get(dev)) == NULL) {
+ read_unlock(&inetdev_lock);
+ return;
+ }
+
+ read_lock(&in_dev->lock);
+ for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next) {
+ /* Add the address to the local list. */
+ addr = t_new(struct sctp_sockaddr_entry, GFP_ATOMIC);
+ if (addr) {
+ addr->a.v4.sin_family = AF_INET;
+ addr->a.v4.sin_port = 0;
+ addr->a.v4.sin_addr.s_addr = ifa->ifa_local;
+ list_add_tail(&addr->list, addrlist);
+ }
+ }
+
+ read_unlock(&in_dev->lock);
+ read_unlock(&inetdev_lock);
+}
+
+/* Extract our IP addresses from the system and stash them in the
+ * protocol structure.
+ */
+static void __sctp_get_local_addr_list(void)
+{
+ struct net_device *dev;
+ struct list_head *pos;
+ struct sctp_af *af;
+
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev; dev = dev->next) {
+ list_for_each(pos, &sctp_address_families) {
+ af = list_entry(pos, struct sctp_af, list);
+ af->copy_addrlist(&sctp_local_addr_list, dev);
+ }
+ }
+ read_unlock(&dev_base_lock);
+}
+
+static void sctp_get_local_addr_list(void)
+{
+ unsigned long flags;
+
+ sctp_spin_lock_irqsave(&sctp_local_addr_lock, flags);
+ __sctp_get_local_addr_list();
+ sctp_spin_unlock_irqrestore(&sctp_local_addr_lock, flags);
+}
+
+/* Free the existing local addresses. */
+static void __sctp_free_local_addr_list(void)
+{
+ struct sctp_sockaddr_entry *addr;
+ struct list_head *pos, *temp;
+
+ list_for_each_safe(pos, temp, &sctp_local_addr_list) {
+ addr = list_entry(pos, struct sctp_sockaddr_entry, list);
+ list_del(pos);
+ kfree(addr);
+ }
+}
+
+/* Free the existing local addresses. */
+static void sctp_free_local_addr_list(void)
+{
+ unsigned long flags;
+
+ sctp_spin_lock_irqsave(&sctp_local_addr_lock, flags);
+ __sctp_free_local_addr_list();
+ sctp_spin_unlock_irqrestore(&sctp_local_addr_lock, flags);
+}
+
+/* Copy the local addresses which are valid for 'scope' into 'bp'. */
+int sctp_copy_local_addr_list(struct sctp_bind_addr *bp, sctp_scope_t scope,
+ int gfp, int copy_flags)
+{
+ struct sctp_sockaddr_entry *addr;
+ int error = 0;
+ struct list_head *pos;
+ unsigned long flags;
+
+ sctp_spin_lock_irqsave(&sctp_local_addr_lock, flags);
+ list_for_each(pos, &sctp_local_addr_list) {
+ addr = list_entry(pos, struct sctp_sockaddr_entry, list);
+ if (sctp_in_scope(&addr->a, scope)) {
+ /* Now that the address is in scope, check to see if
+ * the address type is really supported by the local
+ * sock as well as the remote peer.
+ */
+ if ((((AF_INET == addr->a.sa.sa_family) &&
+ (copy_flags & SCTP_ADDR4_PEERSUPP))) ||
+ (((AF_INET6 == addr->a.sa.sa_family) &&
+ (copy_flags & SCTP_ADDR6_ALLOWED) &&
+ (copy_flags & SCTP_ADDR6_PEERSUPP)))) {
+ error = sctp_add_bind_addr(bp, &addr->a,
+ GFP_ATOMIC);
+ if (error)
+ goto end_copy;
+ }
+ }
+ }
+
+end_copy:
+ sctp_spin_unlock_irqrestore(&sctp_local_addr_lock, flags);
+ return error;
+}
+
+/* Initialize a sctp_addr from in incoming skb. */
+static void sctp_v4_from_skb(union sctp_addr *addr, struct sk_buff *skb,
+ int is_saddr)
+{
+ void *from;
+ __u16 *port;
+ struct sctphdr *sh;
+
+ port = &addr->v4.sin_port;
+ addr->v4.sin_family = AF_INET;
+
+ sh = (struct sctphdr *) skb->h.raw;
+ if (is_saddr) {
+ *port = ntohs(sh->source);
+ from = &skb->nh.iph->saddr;
+ } else {
+ *port = ntohs(sh->dest);
+ from = &skb->nh.iph->daddr;
+ }
+ memcpy(&addr->v4.sin_addr.s_addr, from, sizeof(struct in_addr));
+}
+
+/* Initialize an sctp_addr from a socket. */
+static void sctp_v4_from_sk(union sctp_addr *addr, struct sock *sk)
+{
+ addr->v4.sin_family = AF_INET;
+ addr->v4.sin_port = sk->num;
+ addr->v4.sin_addr.s_addr = sk->rcv_saddr;
+}
+
+/* Initialize sk->sk_rcv_saddr from sctp_addr. */
+static void sctp_v4_to_sk_saddr(union sctp_addr *addr, struct sock *sk)
+{
+ sk->rcv_saddr = addr->v4.sin_addr.s_addr;
+}
+
+/* Initialize sk->sk_daddr from sctp_addr. */
+static void sctp_v4_to_sk_daddr(union sctp_addr *addr, struct sock *sk)
+{
+ sk->daddr = addr->v4.sin_addr.s_addr;
+}
+
+/* Initialize a sctp_addr from an address parameter. */
+static void sctp_v4_from_addr_param(union sctp_addr *addr,
+ union sctp_addr_param *param,
+ __u16 port, int iif)
+{
+ addr->v4.sin_family = AF_INET;
+ addr->v4.sin_port = port;
+ addr->v4.sin_addr.s_addr = param->v4.addr.s_addr;
+}
+
+/* Initialize an address parameter from a sctp_addr and return the length
+ * of the address parameter.
+ */
+static int sctp_v4_to_addr_param(const union sctp_addr *addr,
+ union sctp_addr_param *param)
+{
+ int length = sizeof(sctp_ipv4addr_param_t);
+
+ param->v4.param_hdr.type = SCTP_PARAM_IPV4_ADDRESS;
+ param->v4.param_hdr.length = ntohs(length);
+ param->v4.addr.s_addr = addr->v4.sin_addr.s_addr;
+
+ return length;
+}
+
+/* Initialize a sctp_addr from a dst_entry. */
+static void sctp_v4_dst_saddr(union sctp_addr *saddr, struct dst_entry *dst,
+ unsigned short port)
+{
+ struct rtable *rt = (struct rtable *)dst;
+ saddr->v4.sin_family = AF_INET;
+ saddr->v4.sin_port = port;
+ saddr->v4.sin_addr.s_addr = rt->rt_src;
+}
+
+/* Compare two addresses exactly. */
+static int sctp_v4_cmp_addr(const union sctp_addr *addr1,
+ const union sctp_addr *addr2)
+{
+ if (addr1->sa.sa_family != addr2->sa.sa_family)
+ return 0;
+ if (addr1->v4.sin_port != addr2->v4.sin_port)
+ return 0;
+ if (addr1->v4.sin_addr.s_addr != addr2->v4.sin_addr.s_addr)
+ return 0;
+
+ return 1;
+}
+
+/* Initialize addr struct to INADDR_ANY. */
+static void sctp_v4_inaddr_any(union sctp_addr *addr, unsigned short port)
+{
+ addr->v4.sin_family = AF_INET;
+ addr->v4.sin_addr.s_addr = INADDR_ANY;
+ addr->v4.sin_port = port;
+}
+
+/* Is this a wildcard address? */
+static int sctp_v4_is_any(const union sctp_addr *addr)
+{
+ return INADDR_ANY == addr->v4.sin_addr.s_addr;
+}
+
+/* This function checks if the address is a valid address to be used for
+ * SCTP binding.
+ *
+ * Output:
+ * Return 0 - If the address is a non-unicast or an illegal address.
+ * Return 1 - If the address is a unicast.
+ */
+static int sctp_v4_addr_valid(union sctp_addr *addr, struct sctp_opt *sp)
+{
+ /* Is this a non-unicast address or a unusable SCTP address? */
+ if (IS_IPV4_UNUSABLE_ADDRESS(&addr->v4.sin_addr.s_addr))
+ return 0;
+
+ return 1;
+}
+
+/* Should this be available for binding? */
+static int sctp_v4_available(union sctp_addr *addr, struct sctp_opt *sp)
+{
+ int ret = inet_addr_type(addr->v4.sin_addr.s_addr);
+
+ /* FIXME: ip_nonlocal_bind sysctl support. */
+
+ if (addr->v4.sin_addr.s_addr != INADDR_ANY && ret != RTN_LOCAL)
+ return 0;
+ return 1;
+}
+
+/* Checking the loopback, private and other address scopes as defined in
+ * RFC 1918. The IPv4 scoping is based on the draft for SCTP IPv4
+ * scoping <draft-stewart-tsvwg-sctp-ipv4-00.txt>.
+ *
+ * Level 0 - unusable SCTP addresses
+ * Level 1 - loopback address
+ * Level 2 - link-local addresses
+ * Level 3 - private addresses.
+ * Level 4 - global addresses
+ * For INIT and INIT-ACK address list, let L be the level of
+ * of requested destination address, sender and receiver
+ * SHOULD include all of its addresses with level greater
+ * than or equal to L.
+ */
+static sctp_scope_t sctp_v4_scope(union sctp_addr *addr)
+{
+ sctp_scope_t retval;
+
+ /* Should IPv4 scoping be a sysctl configurable option
+ * so users can turn it off (default on) for certain
+ * unconventional networking environments?
+ */
+
+ /* Check for unusable SCTP addresses. */
+ if (IS_IPV4_UNUSABLE_ADDRESS(&addr->v4.sin_addr.s_addr)) {
+ retval = SCTP_SCOPE_UNUSABLE;
+ } else if (LOOPBACK(addr->v4.sin_addr.s_addr)) {
+ retval = SCTP_SCOPE_LOOPBACK;
+ } else if (IS_IPV4_LINK_ADDRESS(&addr->v4.sin_addr.s_addr)) {
+ retval = SCTP_SCOPE_LINK;
+ } else if (IS_IPV4_PRIVATE_ADDRESS(&addr->v4.sin_addr.s_addr)) {
+ retval = SCTP_SCOPE_PRIVATE;
+ } else {
+ retval = SCTP_SCOPE_GLOBAL;
+ }
+
+ return retval;
+}
+
+/* Returns a valid dst cache entry for the given source and destination ip
+ * addresses. If an association is passed, trys to get a dst entry with a
+ * source address that matches an address in the bind address list.
+ */
+static struct dst_entry *sctp_v4_get_dst(struct sctp_association *asoc,
+ union sctp_addr *daddr,
+ union sctp_addr *saddr)
+{
+ struct rtable *rt;
+ struct rt_key key;
+ struct sctp_bind_addr *bp;
+ rwlock_t *addr_lock;
+ struct sctp_sockaddr_entry *laddr;
+ struct list_head *pos;
+ struct dst_entry *dst = NULL;
+ union sctp_addr dst_saddr;
+
+ memset(&key, 0x0, sizeof(struct rt_key));
+ key.dst = daddr->v4.sin_addr.s_addr;
+
+ if (asoc) {
+ key.tos = RT_CONN_FLAGS(asoc->base.sk);
+ key.oif = asoc->base.sk->bound_dev_if;
+ }
+ if (saddr)
+ key.src = saddr->v4.sin_addr.s_addr;
+
+ SCTP_DEBUG_PRINTK("%s: DST:%u.%u.%u.%u, SRC:%u.%u.%u.%u - ",
+ __FUNCTION__, NIPQUAD(key.dst),
+ NIPQUAD(key.src));
+
+ if (!ip_route_output_key(&rt, &key)) {
+ dst = &rt->u.dst;
+ }
+
+ /* If there is no association or if a source address is passed, no
+ * more validation is required.
+ */
+ if (!asoc || saddr)
+ goto out;
+
+ bp = &asoc->base.bind_addr;
+ addr_lock = &asoc->base.addr_lock;
+
+ if (dst) {
+ /* Walk through the bind address list and look for a bind
+ * address that matches the source address of the returned dst.
+ */
+ sctp_read_lock(addr_lock);
+ list_for_each(pos, &bp->address_list) {
+ laddr = list_entry(pos, struct sctp_sockaddr_entry,
+ list);
+ sctp_v4_dst_saddr(&dst_saddr, dst, bp->port);
+ if (sctp_v4_cmp_addr(&dst_saddr, &laddr->a))
+ goto out_unlock;
+ }
+ sctp_read_unlock(addr_lock);
+
+ /* None of the bound addresses match the source address of the
+ * dst. So release it.
+ */
+ dst_release(dst);
+ dst = NULL;
+ }
+
+ /* Walk through the bind address list and try to get a dst that
+ * matches a bind address as the source address.
+ */
+ sctp_read_lock(addr_lock);
+ list_for_each(pos, &bp->address_list) {
+ laddr = list_entry(pos, struct sctp_sockaddr_entry, list);
+
+ if (AF_INET == laddr->a.sa.sa_family) {
+ key.src = laddr->a.v4.sin_addr.s_addr;
+ if (!ip_route_output_key(&rt, &key)) {
+ dst = &rt->u.dst;
+ goto out_unlock;
+ }
+ }
+ }
+
+out_unlock:
+ sctp_read_unlock(addr_lock);
+out:
+ if (dst)
+ SCTP_DEBUG_PRINTK("rt_dst:%u.%u.%u.%u, rt_src:%u.%u.%u.%u\n",
+ NIPQUAD(rt->rt_dst), NIPQUAD(rt->rt_src));
+ else
+ SCTP_DEBUG_PRINTK("NO ROUTE\n");
+
+ return dst;
+}
+
+/* For v4, the source address is cached in the route entry(dst). So no need
+ * to cache it separately and hence this is an empty routine.
+ */
+static void sctp_v4_get_saddr(struct sctp_association *asoc,
+ struct dst_entry *dst,
+ union sctp_addr *daddr,
+ union sctp_addr *saddr)
+{
+ struct rtable *rt = (struct rtable *)dst;
+
+ if (rt) {
+ saddr->v4.sin_family = AF_INET;
+ saddr->v4.sin_port = asoc->base.bind_addr.port;
+ saddr->v4.sin_addr.s_addr = rt->rt_src;
+ }
+}
+
+/* What interface did this skb arrive on? */
+static int sctp_v4_skb_iif(const struct sk_buff *skb)
+{
+ return ((struct rtable *)skb->dst)->rt_iif;
+}
+
+/* Was this packet marked by Explicit Congestion Notification? */
+static int sctp_v4_is_ce(const struct sk_buff *skb)
+{
+ return INET_ECN_is_ce(skb->nh.iph->tos);
+}
+
+/* Create and initialize a new sk for the socket returned by accept(). */
+static struct sock *sctp_v4_create_accept_sk(struct sock *sk,
+ struct sctp_association *asoc)
+{
+ struct sock *newsk;
+ struct inet_opt *inet = inet_sk(sk);
+ struct inet_opt *newinet;
+
+ newsk = sk_alloc(PF_INET, GFP_KERNEL, sizeof(struct sock));
+ if (!newsk)
+ goto out;
+
+ sock_init_data(NULL, newsk);
+ sk_set_owner(newsk, THIS_MODULE);
+
+ newsk->type = SOCK_STREAM;
+
+ newsk->prot = sk->prot;
+ newsk->no_check = sk->no_check;
+ newsk->reuse = sk->reuse;
+ newsk->shutdown = sk->shutdown;
+
+ newsk->destruct = inet_sock_destruct;
+ newsk->zapped = 0;
+ newsk->family = PF_INET;
+ newsk->protocol = IPPROTO_SCTP;
+ newsk->backlog_rcv = sk->prot->backlog_rcv;
+
+ newinet = inet_sk(newsk);
+
+ /* Initialize sk's sport, dport, rcv_saddr and daddr for
+ * getsockname() and getpeername()
+ */
+ newsk->sport = sk->sport;
+ newsk->saddr = sk->saddr;
+ newsk->rcv_saddr = sk->rcv_saddr;
+ newsk->dport = htons(asoc->peer.port);
+ newsk->daddr = asoc->peer.primary_addr.v4.sin_addr.s_addr;
+ newinet->pmtudisc = inet->pmtudisc;
+ newinet->id = 0;
+
+ newinet->ttl = sysctl_ip_default_ttl;
+ newinet->mc_loop = 1;
+ newinet->mc_ttl = 1;
+ newinet->mc_index = 0;
+ newinet->mc_list = NULL;
+
+#ifdef INET_REFCNT_DEBUG
+ atomic_inc(&inet_sock_nr);
+#endif
+
+ if (newsk->prot->init(newsk)) {
+ inet_sock_release(newsk);
+ newsk = NULL;
+ }
+
+out:
+ return newsk;
+}
+
+/* Map address, empty for v4 family */
+static void sctp_v4_addr_v4map(struct sctp_opt *sp, union sctp_addr *addr)
+{
+ /* Empty */
+}
+
+/* Dump the v4 addr to the seq file. */
+static void sctp_v4_seq_dump_addr(struct seq_file *seq, union sctp_addr *addr)
+{
+ seq_printf(seq, "%d.%d.%d.%d ", NIPQUAD(addr->v4.sin_addr));
+}
+
+/* Event handler for inet address addition/deletion events.
+ * Basically, whenever there is an event, we re-build our local address list.
+ */
+int sctp_inetaddr_event(struct notifier_block *this, unsigned long ev,
+ void *ptr)
+{
+ unsigned long flags;
+
+ sctp_spin_lock_irqsave(&sctp_local_addr_lock, flags);
+ __sctp_free_local_addr_list();
+ __sctp_get_local_addr_list();
+ sctp_spin_unlock_irqrestore(&sctp_local_addr_lock, flags);
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Initialize the control inode/socket with a control endpoint data
+ * structure. This endpoint is reserved exclusively for the OOTB processing.
+ */
+static int sctp_ctl_sock_init(void)
+{
+ int err;
+ sa_family_t family;
+
+ if (sctp_get_pf_specific(PF_INET6))
+ family = PF_INET6;
+ else
+ family = PF_INET;
+
+ err = sock_create(family, SOCK_SEQPACKET, IPPROTO_SCTP,
+ &sctp_ctl_socket);
+ if (err < 0) {
+ printk(KERN_ERR
+ "SCTP: Failed to create the SCTP control socket.\n");
+ return err;
+ }
+ sctp_ctl_socket->sk->allocation = GFP_ATOMIC;
+ inet_sk(sctp_ctl_socket->sk)->ttl = MAXTTL;
+
+ return 0;
+}
+
+/* Register address family specific functions. */
+int sctp_register_af(struct sctp_af *af)
+{
+ switch (af->sa_family) {
+ case AF_INET:
+ if (sctp_af_v4_specific)
+ return 0;
+ sctp_af_v4_specific = af;
+ break;
+ case AF_INET6:
+ if (sctp_af_v6_specific)
+ return 0;
+ sctp_af_v6_specific = af;
+ break;
+ default:
+ return 0;
+ }
+
+ INIT_LIST_HEAD(&af->list);
+ list_add_tail(&af->list, &sctp_address_families);
+ return 1;
+}
+
+/* Get the table of functions for manipulating a particular address
+ * family.
+ */
+struct sctp_af *sctp_get_af_specific(sa_family_t family)
+{
+ switch (family) {
+ case AF_INET:
+ return sctp_af_v4_specific;
+ case AF_INET6:
+ return sctp_af_v6_specific;
+ default:
+ return NULL;
+ }
+}
+
+/* Common code to initialize a AF_INET msg_name. */
+static void sctp_inet_msgname(char *msgname, int *addr_len)
+{
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)msgname;
+ *addr_len = sizeof(struct sockaddr_in);
+ sin->sin_family = AF_INET;
+ memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
+}
+
+/* Copy the primary address of the peer primary address as the msg_name. */
+static void sctp_inet_event_msgname(struct sctp_ulpevent *event, char *msgname,
+ int *addr_len)
+{
+ struct sockaddr_in *sin, *sinfrom;
+
+ if (msgname) {
+ struct sctp_association *asoc;
+
+ asoc = event->asoc;
+ sctp_inet_msgname(msgname, addr_len);
+ sin = (struct sockaddr_in *)msgname;
+ sinfrom = &asoc->peer.primary_addr.v4;
+ sin->sin_port = htons(asoc->peer.port);
+ sin->sin_addr.s_addr = sinfrom->sin_addr.s_addr;
+ }
+}
+
+/* Initialize and copy out a msgname from an inbound skb. */
+static void sctp_inet_skb_msgname(struct sk_buff *skb, char *msgname, int *len)
+{
+ struct sctphdr *sh;
+ struct sockaddr_in *sin;
+
+ if (msgname) {
+ sctp_inet_msgname(msgname, len);
+ sin = (struct sockaddr_in *)msgname;
+ sh = (struct sctphdr *)skb->h.raw;
+ sin->sin_port = sh->source;
+ sin->sin_addr.s_addr = skb->nh.iph->saddr;
+ }
+}
+
+/* Do we support this AF? */
+static int sctp_inet_af_supported(sa_family_t family, struct sctp_opt *sp)
+{
+ /* PF_INET only supports AF_INET addresses. */
+ return (AF_INET == family);
+}
+
+/* Address matching with wildcards allowed. */
+static int sctp_inet_cmp_addr(const union sctp_addr *addr1,
+ const union sctp_addr *addr2,
+ struct sctp_opt *opt)
+{
+ /* PF_INET only supports AF_INET addresses. */
+ if (addr1->sa.sa_family != addr2->sa.sa_family)
+ return 0;
+ if (INADDR_ANY == addr1->v4.sin_addr.s_addr ||
+ INADDR_ANY == addr2->v4.sin_addr.s_addr)
+ return 1;
+ if (addr1->v4.sin_addr.s_addr == addr2->v4.sin_addr.s_addr)
+ return 1;
+
+ return 0;
+}
+
+/* Verify that provided sockaddr looks bindable. Common verification has
+ * already been taken care of.
+ */
+static int sctp_inet_bind_verify(struct sctp_opt *opt, union sctp_addr *addr)
+{
+ return sctp_v4_available(addr, opt);
+}
+
+/* Verify that sockaddr looks sendable. Common verification has already
+ * been taken care of.
+ */
+static int sctp_inet_send_verify(struct sctp_opt *opt, union sctp_addr *addr)
+{
+ return 1;
+}
+
+/* Fill in Supported Address Type information for INIT and INIT-ACK
+ * chunks. Returns number of addresses supported.
+ */
+static int sctp_inet_supported_addrs(const struct sctp_opt *opt,
+ __u16 *types)
+{
+ types[0] = SCTP_PARAM_IPV4_ADDRESS;
+ return 1;
+}
+
+/* Wrapper routine that calls the ip transmit routine. */
+static inline int sctp_v4_xmit(struct sk_buff *skb,
+ struct sctp_transport *transport, int ipfragok)
+{
+ SCTP_DEBUG_PRINTK("%s: skb:%p, len:%d, "
+ "src:%u.%u.%u.%u, dst:%u.%u.%u.%u\n",
+ __FUNCTION__, skb, skb->len,
+ NIPQUAD(((struct rtable *)skb->dst)->rt_src),
+ NIPQUAD(((struct rtable *)skb->dst)->rt_dst));
+
+ SCTP_INC_STATS(SctpOutSCTPPacks);
+ return ip_queue_xmit(skb, ipfragok);
+}
+
+static struct sctp_af sctp_ipv4_specific;
+
+static struct sctp_pf sctp_pf_inet = {
+ .event_msgname = sctp_inet_event_msgname,
+ .skb_msgname = sctp_inet_skb_msgname,
+ .af_supported = sctp_inet_af_supported,
+ .cmp_addr = sctp_inet_cmp_addr,
+ .bind_verify = sctp_inet_bind_verify,
+ .send_verify = sctp_inet_send_verify,
+ .supported_addrs = sctp_inet_supported_addrs,
+ .create_accept_sk = sctp_v4_create_accept_sk,
+ .addr_v4map = sctp_v4_addr_v4map,
+ .af = &sctp_ipv4_specific,
+};
+
+/* Notifier for inetaddr addition/deletion events. */
+static struct notifier_block sctp_inetaddr_notifier = {
+ .notifier_call = sctp_inetaddr_event,
+};
+
+/* Socket operations. */
+static struct proto_ops inet_seqpacket_ops = {
+ .family = PF_INET,
+ .release = inet_release, /* Needs to be wrapped... */
+ .bind = inet_bind,
+ .connect = inet_dgram_connect,
+ .socketpair = sock_no_socketpair,
+ .accept = inet_accept,
+ .getname = inet_getname, /* Semantics are different. */
+ .poll = sctp_poll,
+ .ioctl = inet_ioctl,
+ .listen = sctp_inet_listen,
+ .shutdown = inet_shutdown, /* Looks harmless. */
+ .setsockopt = inet_setsockopt, /* IP_SOL IP_OPTION is a problem. */
+ .getsockopt = inet_getsockopt,
+ .sendmsg = inet_sendmsg,
+ .recvmsg = inet_recvmsg,
+ .mmap = sock_no_mmap,
+ .sendpage = sock_no_sendpage,
+};
+
+/* Registration with AF_INET family. */
+static struct inet_protosw sctp_seqpacket_protosw = {
+ .type = SOCK_SEQPACKET,
+ .protocol = IPPROTO_SCTP,
+ .prot = &sctp_prot,
+ .ops = &inet_seqpacket_ops,
+ .capability = -1,
+ .no_check = 0,
+ .flags = SCTP_PROTOSW_FLAG
+};
+static struct inet_protosw sctp_stream_protosw = {
+ .type = SOCK_STREAM,
+ .protocol = IPPROTO_SCTP,
+ .prot = &sctp_prot,
+ .ops = &inet_seqpacket_ops,
+ .capability = -1,
+ .no_check = 0,
+ .flags = SCTP_PROTOSW_FLAG
+};
+
+/* Register with IP layer. */
+static struct inet_protocol sctp_protocol = {
+ .handler = sctp_rcv,
+ .err_handler = sctp_v4_err,
+ .protocol = IPPROTO_SCTP,
+ .name = "SCTP"
+};
+
+/* IPv4 address related functions. */
+static struct sctp_af sctp_ipv4_specific = {
+ .sctp_xmit = sctp_v4_xmit,
+ .setsockopt = ip_setsockopt,
+ .getsockopt = ip_getsockopt,
+ .get_dst = sctp_v4_get_dst,
+ .get_saddr = sctp_v4_get_saddr,
+ .copy_addrlist = sctp_v4_copy_addrlist,
+ .from_skb = sctp_v4_from_skb,
+ .from_sk = sctp_v4_from_sk,
+ .to_sk_saddr = sctp_v4_to_sk_saddr,
+ .to_sk_daddr = sctp_v4_to_sk_daddr,
+ .from_addr_param= sctp_v4_from_addr_param,
+ .to_addr_param = sctp_v4_to_addr_param,
+ .dst_saddr = sctp_v4_dst_saddr,
+ .cmp_addr = sctp_v4_cmp_addr,
+ .addr_valid = sctp_v4_addr_valid,
+ .inaddr_any = sctp_v4_inaddr_any,
+ .is_any = sctp_v4_is_any,
+ .available = sctp_v4_available,
+ .scope = sctp_v4_scope,
+ .skb_iif = sctp_v4_skb_iif,
+ .is_ce = sctp_v4_is_ce,
+ .seq_dump_addr = sctp_v4_seq_dump_addr,
+ .net_header_len = sizeof(struct iphdr),
+ .sockaddr_len = sizeof(struct sockaddr_in),
+ .sa_family = AF_INET,
+};
+
+struct sctp_pf *sctp_get_pf_specific(sa_family_t family) {
+
+ switch (family) {
+ case PF_INET:
+ return sctp_pf_inet_specific;
+ case PF_INET6:
+ return sctp_pf_inet6_specific;
+ default:
+ return NULL;
+ }
+}
+
+/* Register the PF specific function table. */
+int sctp_register_pf(struct sctp_pf *pf, sa_family_t family)
+{
+ switch (family) {
+ case PF_INET:
+ if (sctp_pf_inet_specific)
+ return 0;
+ sctp_pf_inet_specific = pf;
+ break;
+ case PF_INET6:
+ if (sctp_pf_inet6_specific)
+ return 0;
+ sctp_pf_inet6_specific = pf;
+ break;
+ default:
+ return 0;
+ }
+ return 1;
+}
+
+static int __init init_sctp_mibs(void)
+{
+ return 0;
+}
+
+static void cleanup_sctp_mibs(void)
+{
+ return;
+}
+
+/* Initialize the universe into something sensible. */
+SCTP_STATIC __init int sctp_init(void)
+{
+ int i;
+ int status = 0;
+ unsigned long goal;
+ int order;
+
+ /* SCTP_DEBUG sanity check. */
+ if (!sctp_sanity_check())
+ return -EINVAL;
+
+ /* Add SCTP to inet_protos hash table. */
+ inet_add_protocol(&sctp_protocol);
+
+ /* Add SCTP(TCP and UDP style) to inetsw linked list. */
+ inet_register_protosw(&sctp_seqpacket_protosw);
+ inet_register_protosw(&sctp_stream_protosw);
+
+ /* Allocate cache pools. */
+ sctp_chunk_cachep = kmem_cache_create("sctp_chunk",
+ sizeof(struct sctp_chunk),
+ 0, SLAB_HWCACHE_ALIGN,
+ NULL, NULL);
+ if (!sctp_chunk_cachep)
+ goto err_chunk_cachep;
+
+ /* Allocate and initialise sctp mibs. */
+ status = init_sctp_mibs();
+ if (status)
+ goto err_init_mibs;
+
+ /* Initialize proc fs directory. */
+ sctp_proc_init();
+ if (status)
+ goto err_init_proc;
+
+ /* Initialize object count debugging. */
+ sctp_dbg_objcnt_init();
+
+ /* Initialize the SCTP specific PF functions. */
+ sctp_register_pf(&sctp_pf_inet, PF_INET);
+ /*
+ * 14. Suggested SCTP Protocol Parameter Values
+ */
+ /* The following protocol parameters are RECOMMENDED: */
+ /* RTO.Initial - 3 seconds */
+ sctp_rto_initial = SCTP_RTO_INITIAL;
+ /* RTO.Min - 1 second */
+ sctp_rto_min = SCTP_RTO_MIN;
+ /* RTO.Max - 60 seconds */
+ sctp_rto_max = SCTP_RTO_MAX;
+ /* RTO.Alpha - 1/8 */
+ sctp_rto_alpha = SCTP_RTO_ALPHA;
+ /* RTO.Beta - 1/4 */
+ sctp_rto_beta = SCTP_RTO_BETA;
+ /* Valid.Cookie.Life - 60 seconds */
+ sctp_valid_cookie_life = 60 * HZ;
+
+ /* Whether Cookie Preservative is enabled(1) or not(0) */
+ sctp_cookie_preserve_enable = 1;
+
+ /* Max.Burst - 4 */
+ sctp_max_burst = SCTP_MAX_BURST;
+
+ /* Association.Max.Retrans - 10 attempts
+ * Path.Max.Retrans - 5 attempts (per destination address)
+ * Max.Init.Retransmits - 8 attempts
+ */
+ sctp_max_retrans_association = 10;
+ sctp_max_retrans_path = 5;
+ sctp_max_retrans_init = 8;
+
+ /* HB.interval - 30 seconds */
+ sctp_hb_interval = 30 * HZ;
+
+ /* Implementation specific variables. */
+
+ /* Initialize default stream count setup information. */
+ sctp_max_instreams = SCTP_DEFAULT_INSTREAMS;
+ sctp_max_outstreams = SCTP_DEFAULT_OUTSTREAMS;
+
+ /* Size and allocate the association hash table.
+ * The methodology is similar to that of the tcp hash tables.
+ */
+ if (num_physpages >= (128 * 1024))
+ goal = num_physpages >> (22 - PAGE_SHIFT);
+ else
+ goal = num_physpages >> (24 - PAGE_SHIFT);
+
+ for (order = 0; (1UL << order) < goal; order++)
+ ;
+
+ do {
+ sctp_assoc_hashsize = (1UL << order) * PAGE_SIZE /
+ sizeof(struct sctp_hashbucket);
+ if ((sctp_assoc_hashsize > (64 * 1024)) && order > 0)
+ continue;
+ sctp_assoc_hashtable = (struct sctp_hashbucket *)
+ __get_free_pages(GFP_ATOMIC, order);
+ } while (!sctp_assoc_hashtable && --order > 0);
+ if (!sctp_assoc_hashtable) {
+ printk(KERN_ERR "SCTP: Failed association hash alloc.\n");
+ status = -ENOMEM;
+ goto err_ahash_alloc;
+ }
+ for (i = 0; i < sctp_assoc_hashsize; i++) {
+ sctp_assoc_hashtable[i].lock = RW_LOCK_UNLOCKED;
+ sctp_assoc_hashtable[i].chain = NULL;
+ }
+
+ /* Allocate and initialize the endpoint hash table. */
+ sctp_ep_hashsize = 64;
+ sctp_ep_hashtable = (struct sctp_hashbucket *)
+ kmalloc(64 * sizeof(struct sctp_hashbucket), GFP_KERNEL);
+ if (!sctp_ep_hashtable) {
+ printk(KERN_ERR "SCTP: Failed endpoint_hash alloc.\n");
+ status = -ENOMEM;
+ goto err_ehash_alloc;
+ }
+ for (i = 0; i < sctp_ep_hashsize; i++) {
+ sctp_ep_hashtable[i].lock = RW_LOCK_UNLOCKED;
+ sctp_ep_hashtable[i].chain = NULL;
+ }
+
+ /* Allocate and initialize the SCTP port hash table. */
+ do {
+ sctp_port_hashsize = (1UL << order) * PAGE_SIZE /
+ sizeof(struct sctp_bind_hashbucket);
+ if ((sctp_port_hashsize > (64 * 1024)) && order > 0)
+ continue;
+ sctp_port_hashtable = (struct sctp_bind_hashbucket *)
+ __get_free_pages(GFP_ATOMIC, order);
+ } while (!sctp_port_hashtable && --order > 0);
+ if (!sctp_port_hashtable) {
+ printk(KERN_ERR "SCTP: Failed bind hash alloc.");
+ status = -ENOMEM;
+ goto err_bhash_alloc;
+ }
+ for (i = 0; i < sctp_port_hashsize; i++) {
+ sctp_port_hashtable[i].lock = SPIN_LOCK_UNLOCKED;
+ sctp_port_hashtable[i].chain = NULL;
+ }
+
+ sctp_port_alloc_lock = SPIN_LOCK_UNLOCKED;
+ sctp_port_rover = sysctl_local_port_range[0] - 1;
+
+ printk(KERN_INFO "SCTP: Hash tables configured "
+ "(established %d bind %d)\n",
+ sctp_assoc_hashsize, sctp_port_hashsize);
+
+ /* Disable ADDIP by default. */
+ sctp_addip_enable = 0;
+
+ /* Enable PR-SCTP by default. */
+ sctp_prsctp_enable = 1;
+
+ sctp_sysctl_register();
+
+ INIT_LIST_HEAD(&sctp_address_families);
+ sctp_register_af(&sctp_ipv4_specific);
+
+ status = sctp_v6_init();
+ if (status)
+ goto err_v6_init;
+
+ /* Initialize the control inode/socket for handling OOTB packets. */
+ if ((status = sctp_ctl_sock_init())) {
+ printk (KERN_ERR
+ "SCTP: Failed to initialize the SCTP control sock.\n");
+ goto err_ctl_sock_init;
+ }
+
+ /* Initialize the local address list. */
+ INIT_LIST_HEAD(&sctp_local_addr_list);
+ sctp_local_addr_lock = SPIN_LOCK_UNLOCKED;
+
+ /* Register notifier for inet address additions/deletions. */
+ register_inetaddr_notifier(&sctp_inetaddr_notifier);
+
+ sctp_get_local_addr_list();
+
+ __unsafe(THIS_MODULE);
+ return 0;
+
+err_ctl_sock_init:
+ sctp_v6_exit();
+err_v6_init:
+ sctp_sysctl_unregister();
+ list_del(&sctp_ipv4_specific.list);
+ free_pages((unsigned long)sctp_port_hashtable,
+ get_order(sctp_port_hashsize *
+ sizeof(struct sctp_bind_hashbucket)));
+err_bhash_alloc:
+ kfree(sctp_ep_hashtable);
+err_ehash_alloc:
+ free_pages((unsigned long)sctp_assoc_hashtable,
+ get_order(sctp_assoc_hashsize *
+ sizeof(struct sctp_hashbucket)));
+err_ahash_alloc:
+ sctp_dbg_objcnt_exit();
+err_init_proc:
+ sctp_proc_exit();
+ cleanup_sctp_mibs();
+err_init_mibs:
+ kmem_cache_destroy(sctp_chunk_cachep);
+err_chunk_cachep:
+ inet_del_protocol(&sctp_protocol);
+ inet_unregister_protosw(&sctp_seqpacket_protosw);
+ inet_unregister_protosw(&sctp_stream_protosw);
+ return status;
+}
+
+/* Exit handler for the SCTP protocol. */
+SCTP_STATIC __exit void sctp_exit(void)
+{
+ /* BUG. This should probably do something useful like clean
+ * up all the remaining associations and all that memory.
+ */
+
+ /* Unregister notifier for inet address additions/deletions. */
+ unregister_inetaddr_notifier(&sctp_inetaddr_notifier);
+
+ /* Free the local address list. */
+ sctp_free_local_addr_list();
+
+ /* Free the control endpoint. */
+ sock_release(sctp_ctl_socket);
+
+ sctp_v6_exit();
+ sctp_sysctl_unregister();
+ list_del(&sctp_ipv4_specific.list);
+
+ free_pages((unsigned long)sctp_assoc_hashtable,
+ get_order(sctp_assoc_hashsize *
+ sizeof(struct sctp_hashbucket)));
+ kfree(sctp_ep_hashtable);
+ free_pages((unsigned long)sctp_port_hashtable,
+ get_order(sctp_port_hashsize *
+ sizeof(struct sctp_bind_hashbucket)));
+
+ kmem_cache_destroy(sctp_chunk_cachep);
+
+ sctp_dbg_objcnt_exit();
+ sctp_proc_exit();
+ cleanup_sctp_mibs();
+
+ inet_del_protocol(&sctp_protocol);
+ inet_unregister_protosw(&sctp_seqpacket_protosw);
+ inet_unregister_protosw(&sctp_stream_protosw);
+}
+
+module_init(sctp_init);
+module_exit(sctp_exit);
+
+MODULE_AUTHOR("Linux Kernel SCTP developers <lksctp-developers@lists.sourceforge.net>");
+MODULE_DESCRIPTION("Support for the SCTP protocol (RFC2960)");
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/sctp/sla1.c b/uClinux-2.4.31-uc0/net/sctp/sla1.c
new file mode 100644
index 0000000..eb967a3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/sla1.c
@@ -0,0 +1,281 @@
+/* SCTP kernel reference Implementation
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * (It's really SHA-1 but Hey I was tired when I created this
+ * file, and on a plane to France :-)
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * Randall Stewart <rstewar1@email.mot.com>
+ * kmorneau@cisco.com
+ * qxie1@email.mot.com
+ *
+ * Based on:
+ * Randy Stewart, et al. SCTP Reference Implementation which is licenced
+ * under the GPL.
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/fcntl.h>
+#include <asm/string.h> /* for memcpy */
+#include <linux/sched.h> /* dead chicken for in.h */
+#include <linux/in.h> /* for htonl and ntohl */
+
+#include <net/sctp/sla1.h>
+
+void SLA1_Init(struct SLA_1_Context *ctx)
+{
+ /* Init the SLA-1 context structure. */
+ ctx->A = 0;
+ ctx->B = 0;
+ ctx->C = 0;
+ ctx->D = 0;
+ ctx->E = 0;
+ ctx->H0 = H0INIT;
+ ctx->H1 = H1INIT;
+ ctx->H2 = H2INIT;
+ ctx->H3 = H3INIT;
+ ctx->H4 = H4INIT;
+ ctx->TEMP = 0;
+ memset(ctx->words, 0, sizeof(ctx->words));
+ ctx->howManyInBlock = 0;
+ ctx->runningTotal = 0;
+}
+
+void SLA1processABlock(struct SLA_1_Context *ctx,unsigned int *block)
+{
+ int i;
+ /* init the W0-W15 to the block of words being
+ * hashed.
+ */
+ /* step a) */
+
+ for (i = 0; i < 16; i++)
+ ctx->words[i] = ntohl(block[i]);
+
+ /* now init the rest based on the SLA-1 formula, step b) */
+ for (i = 16; i < 80; i++)
+ ctx->words[i] =
+ CSHIFT(1, ((ctx->words[(i-3)]) ^
+ (ctx->words[(i-8)]) ^
+ (ctx->words[(i-14)]) ^
+ (ctx->words[(i-16)])));
+
+ /* step c) */
+ ctx->A = ctx->H0;
+ ctx->B = ctx->H1;
+ ctx->C = ctx->H2;
+ ctx->D = ctx->H3;
+ ctx->E = ctx->H4;
+
+ /* step d) */
+ for (i = 0; i < 80; i++) {
+ if (i < 20) {
+ ctx->TEMP = ((CSHIFT(5, ctx->A)) +
+ (F1(ctx->B, ctx->C, ctx->D)) +
+ (ctx->E) +
+ ctx->words[i] +
+ K1
+ );
+ } else if (i < 40) {
+ ctx->TEMP = ((CSHIFT(5, ctx->A)) +
+ (F2(ctx->B, ctx->C, ctx->D)) +
+ (ctx->E) +
+ (ctx->words[i]) +
+ K2
+ );
+ } else if (i < 60) {
+ ctx->TEMP = ((CSHIFT(5, ctx->A)) +
+ (F3(ctx->B, ctx->C, ctx->D)) +
+ (ctx->E) +
+ (ctx->words[i]) +
+ K3
+ );
+ } else {
+ ctx->TEMP = ((CSHIFT(5, ctx->A)) +
+ (F4(ctx->B, ctx->C, ctx->D)) +
+ (ctx->E) +
+ (ctx->words[i]) +
+ K4
+ );
+ }
+ ctx->E = ctx->D;
+ ctx->D = ctx->C;
+ ctx->C = CSHIFT(30, ctx->B);
+ ctx->B = ctx->A;
+ ctx->A = ctx->TEMP;
+ }
+
+ /* step e) */
+ ctx->H0 = (ctx->H0) + (ctx->A);
+ ctx->H1 = (ctx->H1) + (ctx->B);
+ ctx->H2 = (ctx->H2) + (ctx->C);
+ ctx->H3 = (ctx->H3) + (ctx->D);
+ ctx->H4 = (ctx->H4) + (ctx->E);
+}
+
+void SLA1_Process(struct SLA_1_Context *ctx, const unsigned char *ptr, int siz)
+{
+ int numberLeft, leftToFill;
+
+ numberLeft = siz;
+ while (numberLeft > 0) {
+ leftToFill = sizeof(ctx->SLAblock) - ctx->howManyInBlock;
+ if (leftToFill > numberLeft) {
+ /* can only partially fill up this one */
+ memcpy(&ctx->SLAblock[ctx->howManyInBlock],
+ ptr, numberLeft);
+ ctx->howManyInBlock += siz;
+ ctx->runningTotal += siz;
+ break;
+ } else {
+ /* block is now full, process it */
+ memcpy(&ctx->SLAblock[ctx->howManyInBlock],
+ ptr, leftToFill);
+ SLA1processABlock(ctx, (unsigned int *) ctx->SLAblock);
+ numberLeft -= leftToFill;
+ ctx->runningTotal += leftToFill;
+ ctx->howManyInBlock = 0;
+ }
+ }
+}
+
+void SLA1_Final(struct SLA_1_Context *ctx, unsigned char *digestBuf)
+{
+ /* if any left in block fill with padding
+ * and process. Then transfer the digest to
+ * the pointer. At the last block some special
+ * rules need to apply. We must add a 1 bit
+ * following the message, then we pad with
+ * 0's. The total size is encoded as a 64 bit
+ * number at the end. Now if the last buffer has
+ * more than 55 octets in it we cannot fit
+ * the 64 bit number + 10000000 pad on the end
+ * and must add the 10000000 pad, pad the rest
+ * of the message with 0's and then create a
+ * all 0 message with just the 64 bit size
+ * at the end and run this block through by itself.
+ * Also the 64 bit int must be in network byte
+ * order.
+ */
+ int i, leftToFill;
+ unsigned int *ptr;
+
+ if (ctx->howManyInBlock > 55) {
+ /* special case, we need to process two
+ * blocks here. One for the current stuff
+ * plus possibly the pad. The other for
+ * the size.
+ */
+ leftToFill = sizeof(ctx->SLAblock) - ctx->howManyInBlock;
+ if (leftToFill == 0) {
+ /* Should not really happen but I am paranoid */
+ /* Not paranoid enough! It is possible for leftToFill
+ * to become negative! AAA!!!! This is another reason
+ * to pick MD5 :-)...
+ */
+ SLA1processABlock(ctx, (unsigned int *) ctx->SLAblock);
+ /* init last block, a bit different then the rest :-) */
+ ctx->SLAblock[0] = 0x80;
+ for (i = 1; i < sizeof(ctx->SLAblock); i++) {
+ ctx->SLAblock[i] = 0x0;
+ }
+ } else if (leftToFill == 1) {
+ ctx->SLAblock[ctx->howManyInBlock] = 0x80;
+ SLA1processABlock(ctx, (unsigned int *) ctx->SLAblock);
+ /* init last block */
+ memset(ctx->SLAblock, 0, sizeof(ctx->SLAblock));
+ } else {
+ ctx->SLAblock[ctx->howManyInBlock] = 0x80;
+ for (i = (ctx->howManyInBlock + 1);
+ i < sizeof(ctx->SLAblock);
+ i++) {
+ ctx->SLAblock[i] = 0x0;
+ }
+ SLA1processABlock(ctx, (unsigned int *) ctx->SLAblock);
+ /* init last block */
+ memset(ctx->SLAblock, 0, sizeof(ctx->SLAblock));
+ }
+ /* This is in bits so multiply by 8 */
+ ctx->runningTotal *= 8;
+ ptr = (unsigned int *) &ctx->SLAblock[60];
+ *ptr = htonl(ctx->runningTotal);
+ SLA1processABlock(ctx, (unsigned int *) ctx->SLAblock);
+ } else {
+ /* easy case, we just pad this
+ * message to size - end with 0
+ * add the magic 0x80 to the next
+ * word and then put the network byte
+ * order size in the last spot and
+ * process the block.
+ */
+ ctx->SLAblock[ctx->howManyInBlock] = 0x80;
+ for (i = (ctx->howManyInBlock + 1);
+ i < sizeof(ctx->SLAblock);
+ i++) {
+ ctx->SLAblock[i] = 0x0;
+ }
+ /* get last int spot */
+ ctx->runningTotal *= 8;
+ ptr = (unsigned int *) &ctx->SLAblock[60];
+ *ptr = htonl(ctx->runningTotal);
+ SLA1processABlock(ctx, (unsigned int *) ctx->SLAblock);
+ }
+ /* Now at this point all we need do is transfer the
+ * digest back to the user
+ */
+ digestBuf[3] = (ctx->H0 & 0xff);
+ digestBuf[2] = ((ctx->H0 >> 8) & 0xff);
+ digestBuf[1] = ((ctx->H0 >> 16) & 0xff);
+ digestBuf[0] = ((ctx->H0 >> 24) & 0xff);
+
+ digestBuf[7] = (ctx->H1 & 0xff);
+ digestBuf[6] = ((ctx->H1 >> 8) & 0xff);
+ digestBuf[5] = ((ctx->H1 >> 16) & 0xff);
+ digestBuf[4] = ((ctx->H1 >> 24) & 0xff);
+
+ digestBuf[11] = (ctx->H2 & 0xff);
+ digestBuf[10] = ((ctx->H2 >> 8) & 0xff);
+ digestBuf[9] = ((ctx->H2 >> 16) & 0xff);
+ digestBuf[8] = ((ctx->H2 >> 24) & 0xff);
+
+ digestBuf[15] = (ctx->H3 & 0xff);
+ digestBuf[14] = ((ctx->H3 >> 8) & 0xff);
+ digestBuf[13] = ((ctx->H3 >> 16) & 0xff);
+ digestBuf[12] = ((ctx->H3 >> 24) & 0xff);
+
+ digestBuf[19] = (ctx->H4 & 0xff);
+ digestBuf[18] = ((ctx->H4 >> 8) & 0xff);
+ digestBuf[17] = ((ctx->H4 >> 16) & 0xff);
+ digestBuf[16] = ((ctx->H4 >> 24) & 0xff);
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/sm_make_chunk.c b/uClinux-2.4.31-uc0/net/sctp/sm_make_chunk.c
new file mode 100644
index 0000000..556dee6
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/sm_make_chunk.c
@@ -0,0 +1,2750 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2001, 2003
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001-2002 Intel Corp.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * These functions work with the state functions in sctp_sm_statefuns.c
+ * to implement the state operations. These functions implement the
+ * steps which require modifying existing data structures.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * C. Robin <chris@hundredacre.ac.uk>
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Xingang Guo <xingang.guo@intel.com>
+ * Dajiang Zhang <dajiang.zhang@nokia.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ * Daisy Chang <daisyc@us.ibm.com>
+ * Ardelle Fan <ardelle.fan@intel.com>
+ * Kevin Gao <kevin.gao@intel.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <asm/scatterlist.h>
+#include <linux/crypto.h>
+#include <net/sock.h>
+
+#include <linux/skbuff.h>
+#include <linux/random.h> /* for get_random_bytes */
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+extern kmem_cache_t *sctp_chunk_cachep;
+
+SCTP_STATIC
+struct sctp_chunk *sctp_make_chunk(const struct sctp_association *asoc,
+ __u8 type, __u8 flags, int paylen);
+static sctp_cookie_param_t *sctp_pack_cookie(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const struct sctp_chunk *init_chunk,
+ int *cookie_len,
+ const __u8 *raw_addrs, int addrs_len);
+static int sctp_process_param(struct sctp_association *asoc,
+ union sctp_params param,
+ const union sctp_addr *peer_addr,
+ int gfp);
+
+/* What was the inbound interface for this chunk? */
+int sctp_chunk_iif(const struct sctp_chunk *chunk)
+{
+ struct sctp_af *af;
+ int iif = 0;
+
+ af = sctp_get_af_specific(ipver2af(chunk->skb->nh.iph->version));
+ if (af)
+ iif = af->skb_iif(chunk->skb);
+
+ return iif;
+}
+
+/* RFC 2960 3.3.2 Initiation (INIT) (1)
+ *
+ * Note 2: The ECN capable field is reserved for future use of
+ * Explicit Congestion Notification.
+ */
+static const struct sctp_paramhdr ecap_param = {
+ SCTP_PARAM_ECN_CAPABLE,
+ __constant_htons(sizeof(struct sctp_paramhdr)),
+};
+static const struct sctp_paramhdr prsctp_param = {
+ SCTP_PARAM_FWD_TSN_SUPPORT,
+ __constant_htons(sizeof(struct sctp_paramhdr)),
+};
+
+/* A helper to initialize to initialize an op error inside a
+ * provided chunk, as most cause codes will be embedded inside an
+ * abort chunk.
+ */
+void sctp_init_cause(struct sctp_chunk *chunk, __u16 cause_code,
+ const void *payload, size_t paylen)
+{
+ sctp_errhdr_t err;
+ int padlen;
+ __u16 len;
+
+ /* Cause code constants are now defined in network order. */
+ err.cause = cause_code;
+ len = sizeof(sctp_errhdr_t) + paylen;
+ padlen = len % 4;
+ err.length = htons(len);
+ len += padlen;
+ sctp_addto_chunk(chunk, sizeof(sctp_errhdr_t), &err);
+ chunk->subh.err_hdr = sctp_addto_chunk(chunk, paylen, payload);
+}
+
+/* 3.3.2 Initiation (INIT) (1)
+ *
+ * This chunk is used to initiate a SCTP association between two
+ * endpoints. The format of the INIT chunk is shown below:
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type = 1 | Chunk Flags | Chunk Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Initiate Tag |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Advertised Receiver Window Credit (a_rwnd) |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Number of Outbound Streams | Number of Inbound Streams |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Initial TSN |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * \ \
+ * / Optional/Variable-Length Parameters /
+ * \ \
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ *
+ * The INIT chunk contains the following parameters. Unless otherwise
+ * noted, each parameter MUST only be included once in the INIT chunk.
+ *
+ * Fixed Parameters Status
+ * ----------------------------------------------
+ * Initiate Tag Mandatory
+ * Advertised Receiver Window Credit Mandatory
+ * Number of Outbound Streams Mandatory
+ * Number of Inbound Streams Mandatory
+ * Initial TSN Mandatory
+ *
+ * Variable Parameters Status Type Value
+ * -------------------------------------------------------------
+ * IPv4 Address (Note 1) Optional 5
+ * IPv6 Address (Note 1) Optional 6
+ * Cookie Preservative Optional 9
+ * Reserved for ECN Capable (Note 2) Optional 32768 (0x8000)
+ * Host Name Address (Note 3) Optional 11
+ * Supported Address Types (Note 4) Optional 12
+ */
+struct sctp_chunk *sctp_make_init(const struct sctp_association *asoc,
+ const struct sctp_bind_addr *bp,
+ int gfp, int vparam_len)
+{
+ sctp_inithdr_t init;
+ union sctp_params addrs;
+ size_t chunksize;
+ struct sctp_chunk *retval = NULL;
+ int num_types, addrs_len = 0;
+ struct sctp_opt *sp;
+ sctp_supported_addrs_param_t sat;
+ __u16 types[2];
+ sctp_adaption_ind_param_t aiparam;
+
+ /* RFC 2960 3.3.2 Initiation (INIT) (1)
+ *
+ * Note 1: The INIT chunks can contain multiple addresses that
+ * can be IPv4 and/or IPv6 in any combination.
+ */
+ retval = NULL;
+
+ /* Convert the provided bind address list to raw format. */
+ addrs = sctp_bind_addrs_to_raw(bp, &addrs_len, gfp);
+
+ init.init_tag = htonl(asoc->c.my_vtag);
+ init.a_rwnd = htonl(asoc->rwnd);
+ init.num_outbound_streams = htons(asoc->c.sinit_num_ostreams);
+ init.num_inbound_streams = htons(asoc->c.sinit_max_instreams);
+ init.initial_tsn = htonl(asoc->c.initial_tsn);
+
+ /* How many address types are needed? */
+ sp = sctp_sk(asoc->base.sk);
+ num_types = sp->pf->supported_addrs(sp, types);
+
+ chunksize = sizeof(init) + addrs_len + SCTP_SAT_LEN(num_types);
+ chunksize += sizeof(ecap_param);
+ if (sctp_prsctp_enable)
+ chunksize += sizeof(prsctp_param);
+ chunksize += sizeof(aiparam);
+ chunksize += vparam_len;
+
+ /* RFC 2960 3.3.2 Initiation (INIT) (1)
+ *
+ * Note 3: An INIT chunk MUST NOT contain more than one Host
+ * Name address parameter. Moreover, the sender of the INIT
+ * MUST NOT combine any other address types with the Host Name
+ * address in the INIT. The receiver of INIT MUST ignore any
+ * other address types if the Host Name address parameter is
+ * present in the received INIT chunk.
+ *
+ * PLEASE DO NOT FIXME [This version does not support Host Name.]
+ */
+
+ retval = sctp_make_chunk(asoc, SCTP_CID_INIT, 0, chunksize);
+ if (!retval)
+ goto nodata;
+
+ retval->subh.init_hdr =
+ sctp_addto_chunk(retval, sizeof(init), &init);
+ retval->param_hdr.v =
+ sctp_addto_chunk(retval, addrs_len, addrs.v);
+
+ /* RFC 2960 3.3.2 Initiation (INIT) (1)
+ *
+ * Note 4: This parameter, when present, specifies all the
+ * address types the sending endpoint can support. The absence
+ * of this parameter indicates that the sending endpoint can
+ * support any address type.
+ */
+ sat.param_hdr.type = SCTP_PARAM_SUPPORTED_ADDRESS_TYPES;
+ sat.param_hdr.length = htons(SCTP_SAT_LEN(num_types));
+ sctp_addto_chunk(retval, sizeof(sat), &sat);
+ sctp_addto_chunk(retval, num_types * sizeof(__u16), &types);
+
+ sctp_addto_chunk(retval, sizeof(ecap_param), &ecap_param);
+ if (sctp_prsctp_enable)
+ sctp_addto_chunk(retval, sizeof(prsctp_param), &prsctp_param);
+ aiparam.param_hdr.type = SCTP_PARAM_ADAPTION_LAYER_IND;
+ aiparam.param_hdr.length = htons(sizeof(aiparam));
+ aiparam.adaption_ind = htonl(sp->adaption_ind);
+ sctp_addto_chunk(retval, sizeof(aiparam), &aiparam);
+nodata:
+ if (addrs.v)
+ kfree(addrs.v);
+ return retval;
+}
+
+struct sctp_chunk *sctp_make_init_ack(const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk,
+ int gfp, int unkparam_len)
+{
+ sctp_inithdr_t initack;
+ struct sctp_chunk *retval;
+ union sctp_params addrs;
+ int addrs_len;
+ sctp_cookie_param_t *cookie;
+ int cookie_len;
+ size_t chunksize;
+ sctp_adaption_ind_param_t aiparam;
+
+ retval = NULL;
+
+ /* Note: there may be no addresses to embed. */
+ addrs = sctp_bind_addrs_to_raw(&asoc->base.bind_addr, &addrs_len, gfp);
+
+ initack.init_tag = htonl(asoc->c.my_vtag);
+ initack.a_rwnd = htonl(asoc->rwnd);
+ initack.num_outbound_streams = htons(asoc->c.sinit_num_ostreams);
+ initack.num_inbound_streams = htons(asoc->c.sinit_max_instreams);
+ initack.initial_tsn = htonl(asoc->c.initial_tsn);
+
+ /* FIXME: We really ought to build the cookie right
+ * into the packet instead of allocating more fresh memory.
+ */
+ cookie = sctp_pack_cookie(asoc->ep, asoc, chunk, &cookie_len,
+ addrs.v, addrs_len);
+ if (!cookie)
+ goto nomem_cookie;
+
+ /* Calculate the total size of allocation, include the reserved
+ * space for reporting unknown parameters if it is specified.
+ */
+ chunksize = sizeof(initack) + addrs_len + cookie_len + unkparam_len;
+
+ /* Tell peer that we'll do ECN only if peer advertised such cap. */
+ if (asoc->peer.ecn_capable)
+ chunksize += sizeof(ecap_param);
+
+ /* Tell peer that we'll do PR-SCTP only if peer advertised. */
+ if (asoc->peer.prsctp_capable)
+ chunksize += sizeof(prsctp_param);
+
+ chunksize += sizeof(aiparam);
+
+ /* Now allocate and fill out the chunk. */
+ retval = sctp_make_chunk(asoc, SCTP_CID_INIT_ACK, 0, chunksize);
+ if (!retval)
+ goto nomem_chunk;
+
+ /* Per the advice in RFC 2960 6.4, send this reply to
+ * the source of the INIT packet.
+ */
+ retval->transport = chunk->transport;
+ retval->subh.init_hdr =
+ sctp_addto_chunk(retval, sizeof(initack), &initack);
+ retval->param_hdr.v = sctp_addto_chunk(retval, addrs_len, addrs.v);
+ sctp_addto_chunk(retval, cookie_len, cookie);
+ if (asoc->peer.ecn_capable)
+ sctp_addto_chunk(retval, sizeof(ecap_param), &ecap_param);
+ if (asoc->peer.prsctp_capable)
+ sctp_addto_chunk(retval, sizeof(prsctp_param), &prsctp_param);
+
+ aiparam.param_hdr.type = SCTP_PARAM_ADAPTION_LAYER_IND;
+ aiparam.param_hdr.length = htons(sizeof(aiparam));
+ aiparam.adaption_ind = htonl(sctp_sk(asoc->base.sk)->adaption_ind);
+ sctp_addto_chunk(retval, sizeof(aiparam), &aiparam);
+
+ /* We need to remove the const qualifier at this point. */
+ retval->asoc = (struct sctp_association *) asoc;
+
+ /* RFC 2960 6.4 Multi-homed SCTP Endpoints
+ *
+ * An endpoint SHOULD transmit reply chunks (e.g., SACK,
+ * HEARTBEAT ACK, * etc.) to the same destination transport
+ * address from which it received the DATA or control chunk
+ * to which it is replying.
+ *
+ * [INIT ACK back to where the INIT came from.]
+ */
+ if (chunk)
+ retval->transport = chunk->transport;
+
+nomem_chunk:
+ kfree(cookie);
+nomem_cookie:
+ if (addrs.v)
+ kfree(addrs.v);
+ return retval;
+}
+
+/* 3.3.11 Cookie Echo (COOKIE ECHO) (10):
+ *
+ * This chunk is used only during the initialization of an association.
+ * It is sent by the initiator of an association to its peer to complete
+ * the initialization process. This chunk MUST precede any DATA chunk
+ * sent within the association, but MAY be bundled with one or more DATA
+ * chunks in the same packet.
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type = 10 |Chunk Flags | Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * / Cookie /
+ * \ \
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Chunk Flags: 8 bit
+ *
+ * Set to zero on transmit and ignored on receipt.
+ *
+ * Length: 16 bits (unsigned integer)
+ *
+ * Set to the size of the chunk in bytes, including the 4 bytes of
+ * the chunk header and the size of the Cookie.
+ *
+ * Cookie: variable size
+ *
+ * This field must contain the exact cookie received in the
+ * State Cookie parameter from the previous INIT ACK.
+ *
+ * An implementation SHOULD make the cookie as small as possible
+ * to insure interoperability.
+ */
+struct sctp_chunk *sctp_make_cookie_echo(const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk)
+{
+ struct sctp_chunk *retval;
+ void *cookie;
+ int cookie_len;
+
+ cookie = asoc->peer.cookie;
+ cookie_len = asoc->peer.cookie_len;
+
+ /* Build a cookie echo chunk. */
+ retval = sctp_make_chunk(asoc, SCTP_CID_COOKIE_ECHO, 0, cookie_len);
+ if (!retval)
+ goto nodata;
+ retval->subh.cookie_hdr =
+ sctp_addto_chunk(retval, cookie_len, cookie);
+
+ /* RFC 2960 6.4 Multi-homed SCTP Endpoints
+ *
+ * An endpoint SHOULD transmit reply chunks (e.g., SACK,
+ * HEARTBEAT ACK, * etc.) to the same destination transport
+ * address from which it * received the DATA or control chunk
+ * to which it is replying.
+ *
+ * [COOKIE ECHO back to where the INIT ACK came from.]
+ */
+ if (chunk)
+ retval->transport = chunk->transport;
+
+nodata:
+ return retval;
+}
+
+/* 3.3.12 Cookie Acknowledgement (COOKIE ACK) (11):
+ *
+ * This chunk is used only during the initialization of an
+ * association. It is used to acknowledge the receipt of a COOKIE
+ * ECHO chunk. This chunk MUST precede any DATA or SACK chunk sent
+ * within the association, but MAY be bundled with one or more DATA
+ * chunks or SACK chunk in the same SCTP packet.
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type = 11 |Chunk Flags | Length = 4 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Chunk Flags: 8 bits
+ *
+ * Set to zero on transmit and ignored on receipt.
+ */
+struct sctp_chunk *sctp_make_cookie_ack(const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk)
+{
+ struct sctp_chunk *retval;
+
+ retval = sctp_make_chunk(asoc, SCTP_CID_COOKIE_ACK, 0, 0);
+
+ /* RFC 2960 6.4 Multi-homed SCTP Endpoints
+ *
+ * An endpoint SHOULD transmit reply chunks (e.g., SACK,
+ * HEARTBEAT ACK, * etc.) to the same destination transport
+ * address from which it * received the DATA or control chunk
+ * to which it is replying.
+ *
+ * [COOKIE ACK back to where the COOKIE ECHO came from.]
+ */
+ if (retval && chunk)
+ retval->transport = chunk->transport;
+
+ return retval;
+}
+
+/*
+ * Appendix A: Explicit Congestion Notification:
+ * CWR:
+ *
+ * RFC 2481 details a specific bit for a sender to send in the header of
+ * its next outbound TCP segment to indicate to its peer that it has
+ * reduced its congestion window. This is termed the CWR bit. For
+ * SCTP the same indication is made by including the CWR chunk.
+ * This chunk contains one data element, i.e. the TSN number that
+ * was sent in the ECNE chunk. This element represents the lowest
+ * TSN number in the datagram that was originally marked with the
+ * CE bit.
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Chunk Type=13 | Flags=00000000| Chunk Length = 8 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Lowest TSN Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Note: The CWR is considered a Control chunk.
+ */
+struct sctp_chunk *sctp_make_cwr(const struct sctp_association *asoc,
+ const __u32 lowest_tsn,
+ const struct sctp_chunk *chunk)
+{
+ struct sctp_chunk *retval;
+ sctp_cwrhdr_t cwr;
+
+ cwr.lowest_tsn = htonl(lowest_tsn);
+ retval = sctp_make_chunk(asoc, SCTP_CID_ECN_CWR, 0,
+ sizeof(sctp_cwrhdr_t));
+
+ if (!retval)
+ goto nodata;
+
+ retval->subh.ecn_cwr_hdr =
+ sctp_addto_chunk(retval, sizeof(cwr), &cwr);
+
+ /* RFC 2960 6.4 Multi-homed SCTP Endpoints
+ *
+ * An endpoint SHOULD transmit reply chunks (e.g., SACK,
+ * HEARTBEAT ACK, * etc.) to the same destination transport
+ * address from which it * received the DATA or control chunk
+ * to which it is replying.
+ *
+ * [Report a reduced congestion window back to where the ECNE
+ * came from.]
+ */
+ if (chunk)
+ retval->transport = chunk->transport;
+
+nodata:
+ return retval;
+}
+
+/* Make an ECNE chunk. This is a congestion experienced report. */
+struct sctp_chunk *sctp_make_ecne(const struct sctp_association *asoc,
+ const __u32 lowest_tsn)
+{
+ struct sctp_chunk *retval;
+ sctp_ecnehdr_t ecne;
+
+ ecne.lowest_tsn = htonl(lowest_tsn);
+ retval = sctp_make_chunk(asoc, SCTP_CID_ECN_ECNE, 0,
+ sizeof(sctp_ecnehdr_t));
+ if (!retval)
+ goto nodata;
+ retval->subh.ecne_hdr =
+ sctp_addto_chunk(retval, sizeof(ecne), &ecne);
+
+nodata:
+ return retval;
+}
+
+/* Make a DATA chunk for the given association from the provided
+ * parameters. However, do not populate the data payload.
+ */
+struct sctp_chunk *sctp_make_datafrag_empty(struct sctp_association *asoc,
+ const struct sctp_sndrcvinfo *sinfo,
+ int data_len, __u8 flags, __u16 ssn)
+{
+ struct sctp_chunk *retval;
+ struct sctp_datahdr dp;
+ int chunk_len;
+
+ /* We assign the TSN as LATE as possible, not here when
+ * creating the chunk.
+ */
+ dp.tsn = 0;
+ dp.stream = htons(sinfo->sinfo_stream);
+ dp.ppid = sinfo->sinfo_ppid;
+
+ /* Set the flags for an unordered send. */
+ if (sinfo->sinfo_flags & MSG_UNORDERED) {
+ flags |= SCTP_DATA_UNORDERED;
+ dp.ssn = 0;
+ } else
+ dp.ssn = htons(ssn);
+
+ chunk_len = sizeof(dp) + data_len;
+ retval = sctp_make_chunk(asoc, SCTP_CID_DATA, flags, chunk_len);
+ if (!retval)
+ goto nodata;
+
+ retval->subh.data_hdr = sctp_addto_chunk(retval, sizeof(dp), &dp);
+ memcpy(&retval->sinfo, sinfo, sizeof(struct sctp_sndrcvinfo));
+
+nodata:
+ return retval;
+}
+
+/* Create a selective ackowledgement (SACK) for the given
+ * association. This reports on which TSN's we've seen to date,
+ * including duplicates and gaps.
+ */
+struct sctp_chunk *sctp_make_sack(const struct sctp_association *asoc)
+{
+ struct sctp_chunk *retval;
+ struct sctp_sackhdr sack;
+ int len;
+ __u32 ctsn;
+ __u16 num_gabs, num_dup_tsns;
+ struct sctp_tsnmap *map = (struct sctp_tsnmap *)&asoc->peer.tsn_map;
+
+ ctsn = sctp_tsnmap_get_ctsn(map);
+ SCTP_DEBUG_PRINTK("sackCTSNAck sent: 0x%x.\n", ctsn);
+
+ /* How much room is needed in the chunk? */
+ num_gabs = sctp_tsnmap_num_gabs(map);
+ num_dup_tsns = sctp_tsnmap_num_dups(map);
+
+ /* Initialize the SACK header. */
+ sack.cum_tsn_ack = htonl(ctsn);
+ sack.a_rwnd = htonl(asoc->a_rwnd);
+ sack.num_gap_ack_blocks = htons(num_gabs);
+ sack.num_dup_tsns = htons(num_dup_tsns);
+
+ len = sizeof(sack)
+ + sizeof(struct sctp_gap_ack_block) * num_gabs
+ + sizeof(__u32) * num_dup_tsns;
+
+ /* Create the chunk. */
+ retval = sctp_make_chunk(asoc, SCTP_CID_SACK, 0, len);
+ if (!retval)
+ goto nodata;
+
+ /* RFC 2960 6.4 Multi-homed SCTP Endpoints
+ *
+ * An endpoint SHOULD transmit reply chunks (e.g., SACK,
+ * HEARTBEAT ACK, etc.) to the same destination transport
+ * address from which it received the DATA or control chunk to
+ * which it is replying. This rule should also be followed if
+ * the endpoint is bundling DATA chunks together with the
+ * reply chunk.
+ *
+ * However, when acknowledging multiple DATA chunks received
+ * in packets from different source addresses in a single
+ * SACK, the SACK chunk may be transmitted to one of the
+ * destination transport addresses from which the DATA or
+ * control chunks being acknowledged were received.
+ *
+ * [BUG: We do not implement the following paragraph.
+ * Perhaps we should remember the last transport we used for a
+ * SACK and avoid that (if possible) if we have seen any
+ * duplicates. --piggy]
+ *
+ * When a receiver of a duplicate DATA chunk sends a SACK to a
+ * multi- homed endpoint it MAY be beneficial to vary the
+ * destination address and not use the source address of the
+ * DATA chunk. The reason being that receiving a duplicate
+ * from a multi-homed endpoint might indicate that the return
+ * path (as specified in the source address of the DATA chunk)
+ * for the SACK is broken.
+ *
+ * [Send to the address from which we last received a DATA chunk.]
+ */
+ retval->transport = asoc->peer.last_data_from;
+
+ retval->subh.sack_hdr =
+ sctp_addto_chunk(retval, sizeof(sack), &sack);
+
+ /* Add the gap ack block information. */
+ if (num_gabs)
+ sctp_addto_chunk(retval, sizeof(__u32) * num_gabs,
+ sctp_tsnmap_get_gabs(map));
+
+ /* Add the duplicate TSN information. */
+ if (num_dup_tsns)
+ sctp_addto_chunk(retval, sizeof(__u32) * num_dup_tsns,
+ sctp_tsnmap_get_dups(map));
+
+nodata:
+ return retval;
+}
+
+/* Make a SHUTDOWN chunk. */
+struct sctp_chunk *sctp_make_shutdown(const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk)
+{
+ struct sctp_chunk *retval;
+ sctp_shutdownhdr_t shut;
+ __u32 ctsn;
+
+ ctsn = sctp_tsnmap_get_ctsn(&asoc->peer.tsn_map);
+ shut.cum_tsn_ack = htonl(ctsn);
+
+ retval = sctp_make_chunk(asoc, SCTP_CID_SHUTDOWN, 0,
+ sizeof(sctp_shutdownhdr_t));
+ if (!retval)
+ goto nodata;
+
+ retval->subh.shutdown_hdr =
+ sctp_addto_chunk(retval, sizeof(shut), &shut);
+
+ if (chunk)
+ retval->transport = chunk->transport;
+nodata:
+ return retval;
+}
+
+struct sctp_chunk *sctp_make_shutdown_ack(const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk)
+{
+ struct sctp_chunk *retval;
+
+ retval = sctp_make_chunk(asoc, SCTP_CID_SHUTDOWN_ACK, 0, 0);
+
+ /* RFC 2960 6.4 Multi-homed SCTP Endpoints
+ *
+ * An endpoint SHOULD transmit reply chunks (e.g., SACK,
+ * HEARTBEAT ACK, * etc.) to the same destination transport
+ * address from which it * received the DATA or control chunk
+ * to which it is replying.
+ *
+ * [ACK back to where the SHUTDOWN came from.]
+ */
+ if (retval && chunk)
+ retval->transport = chunk->transport;
+
+ return retval;
+}
+
+struct sctp_chunk *sctp_make_shutdown_complete(
+ const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk)
+{
+ struct sctp_chunk *retval;
+ __u8 flags = 0;
+
+ /* Maybe set the T-bit if we have no association. */
+ flags |= asoc ? 0 : SCTP_CHUNK_FLAG_T;
+
+ retval = sctp_make_chunk(asoc, SCTP_CID_SHUTDOWN_COMPLETE, flags, 0);
+
+ /* RFC 2960 6.4 Multi-homed SCTP Endpoints
+ *
+ * An endpoint SHOULD transmit reply chunks (e.g., SACK,
+ * HEARTBEAT ACK, * etc.) to the same destination transport
+ * address from which it * received the DATA or control chunk
+ * to which it is replying.
+ *
+ * [Report SHUTDOWN COMPLETE back to where the SHUTDOWN ACK
+ * came from.]
+ */
+ if (retval && chunk)
+ retval->transport = chunk->transport;
+
+ return retval;
+}
+
+/* Create an ABORT. Note that we set the T bit if we have no
+ * association.
+ */
+struct sctp_chunk *sctp_make_abort(const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk,
+ const size_t hint)
+{
+ struct sctp_chunk *retval;
+ __u8 flags = 0;
+
+ /* Maybe set the T-bit if we have no association. */
+ flags |= asoc ? 0 : SCTP_CHUNK_FLAG_T;
+
+ retval = sctp_make_chunk(asoc, SCTP_CID_ABORT, flags, hint);
+
+ /* RFC 2960 6.4 Multi-homed SCTP Endpoints
+ *
+ * An endpoint SHOULD transmit reply chunks (e.g., SACK,
+ * HEARTBEAT ACK, * etc.) to the same destination transport
+ * address from which it * received the DATA or control chunk
+ * to which it is replying.
+ *
+ * [ABORT back to where the offender came from.]
+ */
+ if (retval && chunk)
+ retval->transport = chunk->transport;
+
+ return retval;
+}
+
+/* Helper to create ABORT with a NO_USER_DATA error. */
+struct sctp_chunk *sctp_make_abort_no_data(
+ const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk, __u32 tsn)
+{
+ struct sctp_chunk *retval;
+ __u32 payload;
+
+ retval = sctp_make_abort(asoc, chunk, sizeof(sctp_errhdr_t)
+ + sizeof(tsn));
+
+ if (!retval)
+ goto no_mem;
+
+ /* Put the tsn back into network byte order. */
+ payload = htonl(tsn);
+ sctp_init_cause(retval, SCTP_ERROR_NO_DATA, (const void *)&payload,
+ sizeof(payload));
+
+ /* RFC 2960 6.4 Multi-homed SCTP Endpoints
+ *
+ * An endpoint SHOULD transmit reply chunks (e.g., SACK,
+ * HEARTBEAT ACK, * etc.) to the same destination transport
+ * address from which it * received the DATA or control chunk
+ * to which it is replying.
+ *
+ * [ABORT back to where the offender came from.]
+ */
+ if (chunk)
+ retval->transport = chunk->transport;
+
+no_mem:
+ return retval;
+}
+
+/* Helper to create ABORT with a SCTP_ERROR_USER_ABORT error. */
+struct sctp_chunk *sctp_make_abort_user(const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk,
+ const struct msghdr *msg)
+{
+ struct sctp_chunk *retval;
+ void *payload = NULL, *payoff;
+ size_t paylen = 0;
+ struct iovec *iov = NULL;
+ int iovlen = 0;
+
+ if (msg) {
+ iov = msg->msg_iov;
+ iovlen = msg->msg_iovlen;
+ paylen = get_user_iov_size(iov, iovlen);
+ }
+
+ retval = sctp_make_abort(asoc, chunk, sizeof(sctp_errhdr_t) + paylen);
+ if (!retval)
+ goto err_chunk;
+
+ if (paylen) {
+ /* Put the msg_iov together into payload. */
+ payload = kmalloc(paylen, GFP_ATOMIC);
+ if (!payload)
+ goto err_payload;
+ payoff = payload;
+
+ for (; iovlen > 0; --iovlen) {
+ if (copy_from_user(payoff, iov->iov_base,iov->iov_len))
+ goto err_copy;
+ payoff += iov->iov_len;
+ iov++;
+ }
+ }
+
+ sctp_init_cause(retval, SCTP_ERROR_USER_ABORT, payload, paylen);
+
+ if (paylen)
+ kfree(payload);
+
+ return retval;
+
+err_copy:
+ kfree(payload);
+err_payload:
+ sctp_chunk_free(retval);
+ retval = NULL;
+err_chunk:
+ return retval;
+}
+
+/* Make an ABORT chunk with a PROTOCOL VIOLATION cause code. */
+struct sctp_chunk *sctp_make_abort_violation(
+ const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk,
+ const __u8 *payload,
+ const size_t paylen)
+{
+ struct sctp_chunk *retval;
+ struct sctp_paramhdr phdr;
+
+ retval = sctp_make_abort(asoc, chunk, sizeof(sctp_errhdr_t) + paylen
+ + sizeof(sctp_chunkhdr_t));
+ if (!retval)
+ goto end;
+
+ sctp_init_cause(retval, SCTP_ERROR_PROTO_VIOLATION, payload, paylen);
+
+ phdr.type = htons(chunk->chunk_hdr->type);
+ phdr.length = chunk->chunk_hdr->length;
+ sctp_addto_chunk(retval, sizeof(sctp_paramhdr_t), &phdr);
+
+end:
+ return retval;
+}
+
+/* Make a HEARTBEAT chunk. */
+struct sctp_chunk *sctp_make_heartbeat(const struct sctp_association *asoc,
+ const struct sctp_transport *transport,
+ const void *payload, const size_t paylen)
+{
+ struct sctp_chunk *retval = sctp_make_chunk(asoc, SCTP_CID_HEARTBEAT,
+ 0, paylen);
+
+ if (!retval)
+ goto nodata;
+
+ /* Cast away the 'const', as this is just telling the chunk
+ * what transport it belongs to.
+ */
+ retval->transport = (struct sctp_transport *) transport;
+ retval->subh.hbs_hdr = sctp_addto_chunk(retval, paylen, payload);
+
+nodata:
+ return retval;
+}
+
+struct sctp_chunk *sctp_make_heartbeat_ack(const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk,
+ const void *payload, const size_t paylen)
+{
+ struct sctp_chunk *retval;
+
+ retval = sctp_make_chunk(asoc, SCTP_CID_HEARTBEAT_ACK, 0, paylen);
+ if (!retval)
+ goto nodata;
+
+ retval->subh.hbs_hdr = sctp_addto_chunk(retval, paylen, payload);
+
+ /* RFC 2960 6.4 Multi-homed SCTP Endpoints
+ *
+ * An endpoint SHOULD transmit reply chunks (e.g., SACK,
+ * HEARTBEAT ACK, * etc.) to the same destination transport
+ * address from which it * received the DATA or control chunk
+ * to which it is replying.
+ *
+ * [HBACK back to where the HEARTBEAT came from.]
+ */
+ if (chunk)
+ retval->transport = chunk->transport;
+
+nodata:
+ return retval;
+}
+
+/* Create an Operation Error chunk with the specified space reserved.
+ * This routine can be used for containing multiple causes in the chunk.
+ */
+static struct sctp_chunk *sctp_make_op_error_space(
+ const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk,
+ size_t size)
+{
+ struct sctp_chunk *retval;
+
+ retval = sctp_make_chunk(asoc, SCTP_CID_ERROR, 0,
+ sizeof(sctp_errhdr_t) + size);
+ if (!retval)
+ goto nodata;
+
+ /* RFC 2960 6.4 Multi-homed SCTP Endpoints
+ *
+ * An endpoint SHOULD transmit reply chunks (e.g., SACK,
+ * HEARTBEAT ACK, etc.) to the same destination transport
+ * address from which it received the DATA or control chunk
+ * to which it is replying.
+ *
+ */
+ if (chunk)
+ retval->transport = chunk->transport;
+
+nodata:
+ return retval;
+}
+
+/* Create an Operation Error chunk. */
+struct sctp_chunk *sctp_make_op_error(const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk,
+ __u16 cause_code, const void *payload,
+ size_t paylen)
+{
+ struct sctp_chunk *retval;
+
+ retval = sctp_make_op_error_space(asoc, chunk, paylen);
+ if (!retval)
+ goto nodata;
+
+ sctp_init_cause(retval, cause_code, payload, paylen);
+
+nodata:
+ return retval;
+}
+
+/********************************************************************
+ * 2nd Level Abstractions
+ ********************************************************************/
+
+/* Turn an skb into a chunk.
+ * FIXME: Eventually move the structure directly inside the skb->cb[].
+ */
+struct sctp_chunk *sctp_chunkify(struct sk_buff *skb,
+ const struct sctp_association *asoc,
+ struct sock *sk)
+{
+ struct sctp_chunk *retval;
+
+ retval = kmem_cache_alloc(sctp_chunk_cachep, SLAB_ATOMIC);
+
+ if (!retval)
+ goto nodata;
+ memset(retval, 0, sizeof(struct sctp_chunk));
+
+ if (!sk) {
+ SCTP_DEBUG_PRINTK("chunkifying skb %p w/o an sk\n", skb);
+ }
+
+ retval->skb = skb;
+ retval->asoc = (struct sctp_association *)asoc;
+ retval->resent = 0;
+ retval->has_tsn = 0;
+ retval->has_ssn = 0;
+ retval->rtt_in_progress = 0;
+ retval->sent_at = 0;
+ retval->singleton = 1;
+ retval->end_of_packet = 0;
+ retval->ecn_ce_done = 0;
+ retval->pdiscard = 0;
+
+ /* sctpimpguide-05.txt Section 2.8.2
+ * M1) Each time a new DATA chunk is transmitted
+ * set the 'TSN.Missing.Report' count for that TSN to 0. The
+ * 'TSN.Missing.Report' count will be used to determine missing chunks
+ * and when to fast retransmit.
+ */
+ retval->tsn_missing_report = 0;
+ retval->tsn_gap_acked = 0;
+ retval->fast_retransmit = 0;
+
+ /* If this is a fragmented message, track all fragments
+ * of the message (for SEND_FAILED).
+ */
+ retval->msg = NULL;
+
+ /* Polish the bead hole. */
+ INIT_LIST_HEAD(&retval->transmitted_list);
+ INIT_LIST_HEAD(&retval->frag_list);
+ SCTP_DBG_OBJCNT_INC(chunk);
+ atomic_set(&retval->refcnt, 1);
+
+nodata:
+ return retval;
+}
+
+/* Set chunk->source and dest based on the IP header in chunk->skb. */
+void sctp_init_addrs(struct sctp_chunk *chunk, union sctp_addr *src,
+ union sctp_addr *dest)
+{
+ memcpy(&chunk->source, src, sizeof(union sctp_addr));
+ memcpy(&chunk->dest, dest, sizeof(union sctp_addr));
+}
+
+/* Extract the source address from a chunk. */
+const union sctp_addr *sctp_source(const struct sctp_chunk *chunk)
+{
+ /* If we have a known transport, use that. */
+ if (chunk->transport) {
+ return &chunk->transport->ipaddr;
+ } else {
+ /* Otherwise, extract it from the IP header. */
+ return &chunk->source;
+ }
+}
+
+/* Create a new chunk, setting the type and flags headers from the
+ * arguments, reserving enough space for a 'paylen' byte payload.
+ */
+SCTP_STATIC
+struct sctp_chunk *sctp_make_chunk(const struct sctp_association *asoc,
+ __u8 type, __u8 flags, int paylen)
+{
+ struct sctp_chunk *retval;
+ sctp_chunkhdr_t *chunk_hdr;
+ struct sk_buff *skb;
+ struct sock *sk;
+
+ /* No need to allocate LL here, as this is only a chunk. */
+ skb = alloc_skb(WORD_ROUND(sizeof(sctp_chunkhdr_t) + paylen),
+ GFP_ATOMIC);
+ if (!skb)
+ goto nodata;
+
+ /* Make room for the chunk header. */
+ chunk_hdr = (sctp_chunkhdr_t *)skb_put(skb, sizeof(sctp_chunkhdr_t));
+ chunk_hdr->type = type;
+ chunk_hdr->flags = flags;
+ chunk_hdr->length = htons(sizeof(sctp_chunkhdr_t));
+
+ sk = asoc ? asoc->base.sk : NULL;
+ retval = sctp_chunkify(skb, asoc, sk);
+ if (!retval) {
+ kfree_skb(skb);
+ goto nodata;
+ }
+
+ retval->chunk_hdr = chunk_hdr;
+ retval->chunk_end = ((__u8 *)chunk_hdr) + sizeof(struct sctp_chunkhdr);
+
+ /* Set the skb to the belonging sock for accounting. */
+ skb->sk = sk;
+
+ return retval;
+nodata:
+ return NULL;
+}
+
+
+/* Release the memory occupied by a chunk. */
+static void sctp_chunk_destroy(struct sctp_chunk *chunk)
+{
+ /* Free the chunk skb data and the SCTP_chunk stub itself. */
+ dev_kfree_skb(chunk->skb);
+
+ SCTP_DBG_OBJCNT_DEC(chunk);
+ kmem_cache_free(sctp_chunk_cachep, chunk);
+}
+
+/* Possibly, free the chunk. */
+void sctp_chunk_free(struct sctp_chunk *chunk)
+{
+ /* Make sure that we are not on any list. */
+ skb_unlink((struct sk_buff *) chunk);
+ list_del_init(&chunk->transmitted_list);
+
+ /* Release our reference on the message tracker. */
+ if (chunk->msg)
+ sctp_datamsg_put(chunk->msg);
+
+ sctp_chunk_put(chunk);
+}
+
+/* Grab a reference to the chunk. */
+void sctp_chunk_hold(struct sctp_chunk *ch)
+{
+ atomic_inc(&ch->refcnt);
+}
+
+/* Release a reference to the chunk. */
+void sctp_chunk_put(struct sctp_chunk *ch)
+{
+ if (atomic_dec_and_test(&ch->refcnt))
+ sctp_chunk_destroy(ch);
+}
+
+/* Append bytes to the end of a chunk. Will panic if chunk is not big
+ * enough.
+ */
+void *sctp_addto_chunk(struct sctp_chunk *chunk, int len, const void *data)
+{
+ void *target;
+ void *padding;
+ int chunklen = ntohs(chunk->chunk_hdr->length);
+ int padlen = chunklen % 4;
+
+ padding = skb_put(chunk->skb, padlen);
+ target = skb_put(chunk->skb, len);
+
+ memset(padding, 0, padlen);
+ memcpy(target, data, len);
+
+ /* Adjust the chunk length field. */
+ chunk->chunk_hdr->length = htons(chunklen + padlen + len);
+ chunk->chunk_end = chunk->skb->tail;
+
+ return target;
+}
+
+/* Append bytes from user space to the end of a chunk. Will panic if
+ * chunk is not big enough.
+ * Returns a kernel err value.
+ */
+int sctp_user_addto_chunk(struct sctp_chunk *chunk, int off, int len,
+ struct iovec *data)
+{
+ __u8 *target;
+ int err = 0;
+
+ /* Make room in chunk for data. */
+ target = skb_put(chunk->skb, len);
+
+ /* Copy data (whole iovec) into chunk */
+ if ((err = memcpy_fromiovecend(target, data, off, len)))
+ goto out;
+
+ /* Adjust the chunk length field. */
+ chunk->chunk_hdr->length =
+ htons(ntohs(chunk->chunk_hdr->length) + len);
+ chunk->chunk_end = chunk->skb->tail;
+
+out:
+ return err;
+}
+
+/* Helper function to assign a TSN if needed. This assumes that both
+ * the data_hdr and association have already been assigned.
+ */
+void sctp_chunk_assign_ssn(struct sctp_chunk *chunk)
+{
+ __u16 ssn;
+ __u16 sid;
+
+ if (chunk->has_ssn)
+ return;
+
+ /* This is the last possible instant to assign a SSN. */
+ if (chunk->chunk_hdr->flags & SCTP_DATA_UNORDERED) {
+ ssn = 0;
+ } else {
+ sid = htons(chunk->subh.data_hdr->stream);
+ if (chunk->chunk_hdr->flags & SCTP_DATA_LAST_FRAG)
+ ssn = sctp_ssn_next(&chunk->asoc->ssnmap->out, sid);
+ else
+ ssn = sctp_ssn_peek(&chunk->asoc->ssnmap->out, sid);
+ ssn = htons(ssn);
+ }
+
+ chunk->subh.data_hdr->ssn = ssn;
+ chunk->has_ssn = 1;
+}
+
+/* Helper function to assign a TSN if needed. This assumes that both
+ * the data_hdr and association have already been assigned.
+ */
+void sctp_chunk_assign_tsn(struct sctp_chunk *chunk)
+{
+ if (!chunk->has_tsn) {
+ /* This is the last possible instant to
+ * assign a TSN.
+ */
+ chunk->subh.data_hdr->tsn =
+ htonl(sctp_association_get_next_tsn(chunk->asoc));
+ chunk->has_tsn = 1;
+ }
+}
+
+/* Create a CLOSED association to use with an incoming packet. */
+struct sctp_association *sctp_make_temp_asoc(const struct sctp_endpoint *ep,
+ struct sctp_chunk *chunk, int gfp)
+{
+ struct sctp_association *asoc;
+ struct sk_buff *skb;
+ sctp_scope_t scope;
+ struct sctp_af *af;
+
+ /* Create the bare association. */
+ scope = sctp_scope(sctp_source(chunk));
+ asoc = sctp_association_new(ep, ep->base.sk, scope, gfp);
+ if (!asoc)
+ goto nodata;
+ asoc->temp = 1;
+ skb = chunk->skb;
+ /* Create an entry for the source address of the packet. */
+ af = sctp_get_af_specific(ipver2af(skb->nh.iph->version));
+ if (unlikely(!af))
+ goto fail;
+ af->from_skb(&asoc->c.peer_addr, skb, 1);
+nodata:
+ return asoc;
+
+fail:
+ sctp_association_free(asoc);
+ return NULL;
+}
+
+/* Build a cookie representing asoc.
+ * This INCLUDES the param header needed to put the cookie in the INIT ACK.
+ */
+static sctp_cookie_param_t *sctp_pack_cookie(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const struct sctp_chunk *init_chunk,
+ int *cookie_len,
+ const __u8 *raw_addrs, int addrs_len)
+{
+ sctp_cookie_param_t *retval;
+ struct sctp_signed_cookie *cookie;
+ struct scatterlist sg;
+ int headersize, bodysize;
+ unsigned int keylen;
+ char *key;
+
+ headersize = sizeof(sctp_paramhdr_t) + SCTP_SECRET_SIZE;
+ bodysize = sizeof(struct sctp_cookie)
+ + ntohs(init_chunk->chunk_hdr->length) + addrs_len;
+
+ /* Pad out the cookie to a multiple to make the signature
+ * functions simpler to write.
+ */
+ if (bodysize % SCTP_COOKIE_MULTIPLE)
+ bodysize += SCTP_COOKIE_MULTIPLE
+ - (bodysize % SCTP_COOKIE_MULTIPLE);
+ *cookie_len = headersize + bodysize;
+
+ retval = (sctp_cookie_param_t *)kmalloc(*cookie_len, GFP_ATOMIC);
+
+ if (!retval) {
+ *cookie_len = 0;
+ goto nodata;
+ }
+
+ /* Clear this memory since we are sending this data structure
+ * out on the network.
+ */
+ memset(retval, 0x00, *cookie_len);
+ cookie = (struct sctp_signed_cookie *) retval->body;
+
+ /* Set up the parameter header. */
+ retval->p.type = SCTP_PARAM_STATE_COOKIE;
+ retval->p.length = htons(*cookie_len);
+
+ /* Copy the cookie part of the association itself. */
+ cookie->c = asoc->c;
+ /* Save the raw address list length in the cookie. */
+ cookie->c.raw_addr_list_len = addrs_len;
+
+ /* Remember PR-SCTP capability. */
+ cookie->c.prsctp_capable = asoc->peer.prsctp_capable;
+
+ /* Save adaption indication in the cookie. */
+ cookie->c.adaption_ind = asoc->peer.adaption_ind;
+
+ /* Set an expiration time for the cookie. */
+ do_gettimeofday(&cookie->c.expiration);
+ TIMEVAL_ADD(asoc->cookie_life, cookie->c.expiration);
+
+ /* Copy the peer's init packet. */
+ memcpy(&cookie->c.peer_init[0], init_chunk->chunk_hdr,
+ ntohs(init_chunk->chunk_hdr->length));
+
+ /* Copy the raw local address list of the association. */
+ memcpy((__u8 *)&cookie->c.peer_init[0] +
+ ntohs(init_chunk->chunk_hdr->length), raw_addrs, addrs_len);
+
+ if (sctp_sk(ep->base.sk)->hmac) {
+ /* Sign the message. */
+ sg.page = virt_to_page(&cookie->c);
+ sg.offset = (unsigned long)(&cookie->c) % PAGE_SIZE;
+ sg.length = bodysize;
+ keylen = SCTP_SECRET_SIZE;
+ key = (char *)ep->secret_key[ep->current_key];
+
+ sctp_crypto_hmac(sctp_sk(ep->base.sk)->hmac, key, &keylen,
+ &sg, 1, cookie->signature);
+ }
+
+nodata:
+ return retval;
+}
+
+/* Unpack the cookie from COOKIE ECHO chunk, recreating the association. */
+struct sctp_association *sctp_unpack_cookie(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ struct sctp_chunk *chunk, int gfp,
+ int *error, struct sctp_chunk **errp)
+{
+ struct sctp_association *retval = NULL;
+ struct sctp_signed_cookie *cookie;
+ struct sctp_cookie *bear_cookie;
+ int headersize, bodysize, fixed_size;
+ __u8 digest[SCTP_SIGNATURE_SIZE];
+ struct scatterlist sg;
+ unsigned int keylen, len;
+ char *key;
+ sctp_scope_t scope;
+ struct sk_buff *skb = chunk->skb;
+
+ headersize = sizeof(sctp_chunkhdr_t) + SCTP_SECRET_SIZE;
+ bodysize = ntohs(chunk->chunk_hdr->length) - headersize;
+ fixed_size = headersize + sizeof(struct sctp_cookie);
+
+ /* Verify that the chunk looks like it even has a cookie.
+ * There must be enough room for our cookie and our peer's
+ * INIT chunk.
+ */
+ len = ntohs(chunk->chunk_hdr->length);
+ if (len < fixed_size + sizeof(struct sctp_chunkhdr))
+ goto malformed;
+
+ /* Verify that the cookie has been padded out. */
+ if (bodysize % SCTP_COOKIE_MULTIPLE)
+ goto malformed;
+
+ /* Process the cookie. */
+ cookie = chunk->subh.cookie_hdr;
+ bear_cookie = &cookie->c;
+
+ if (!sctp_sk(ep->base.sk)->hmac)
+ goto no_hmac;
+
+ /* Check the signature. */
+ keylen = SCTP_SECRET_SIZE;
+ sg.page = virt_to_page(bear_cookie);
+ sg.offset = (unsigned long)(bear_cookie) % PAGE_SIZE;
+ sg.length = bodysize;
+ key = (char *)ep->secret_key[ep->current_key];
+
+ memset(digest, 0x00, sizeof(digest));
+ sctp_crypto_hmac(sctp_sk(ep->base.sk)->hmac, key, &keylen, &sg,
+ 1, digest);
+
+ if (memcmp(digest, cookie->signature, SCTP_SIGNATURE_SIZE)) {
+ /* Try the previous key. */
+ key = (char *)ep->secret_key[ep->last_key];
+ memset(digest, 0x00, sizeof(digest));
+ sctp_crypto_hmac(sctp_sk(ep->base.sk)->hmac, key, &keylen,
+ &sg, 1, digest);
+
+ if (memcmp(digest, cookie->signature, SCTP_SIGNATURE_SIZE)) {
+ /* Yikes! Still bad signature! */
+ *error = -SCTP_IERROR_BAD_SIG;
+ goto fail;
+ }
+ }
+
+no_hmac:
+ /* IG Section 2.35.2:
+ * 3) Compare the port numbers and the verification tag contained
+ * within the COOKIE ECHO chunk to the actual port numbers and the
+ * verification tag within the SCTP common header of the received
+ * packet. If these values do not match the packet MUST be silently
+ * discarded,
+ */
+ if (ntohl(chunk->sctp_hdr->vtag) != bear_cookie->my_vtag) {
+ *error = -SCTP_IERROR_BAD_TAG;
+ goto fail;
+ }
+
+ if (ntohs(chunk->sctp_hdr->source) != bear_cookie->peer_addr.v4.sin_port ||
+ ntohs(chunk->sctp_hdr->dest) != bear_cookie->my_port) {
+ *error = -SCTP_IERROR_BAD_PORTS;
+ goto fail;
+ }
+
+ /* Check to see if the cookie is stale. If there is already
+ * an association, there is no need to check cookie's expiration
+ * for init collision case of lost COOKIE ACK.
+ */
+ if (!asoc && tv_lt(bear_cookie->expiration, skb->stamp)) {
+ __u16 len;
+ /*
+ * Section 3.3.10.3 Stale Cookie Error (3)
+ *
+ * Cause of error
+ * ---------------
+ * Stale Cookie Error: Indicates the receipt of a valid State
+ * Cookie that has expired.
+ */
+ len = ntohs(chunk->chunk_hdr->length);
+ *errp = sctp_make_op_error_space(asoc, chunk, len);
+ if (*errp) {
+ suseconds_t usecs = (skb->stamp.tv_sec -
+ bear_cookie->expiration.tv_sec) * 1000000L +
+ skb->stamp.tv_usec -
+ bear_cookie->expiration.tv_usec;
+
+ usecs = htonl(usecs);
+ sctp_init_cause(*errp, SCTP_ERROR_STALE_COOKIE,
+ &usecs, sizeof(usecs));
+ *error = -SCTP_IERROR_STALE_COOKIE;
+ } else
+ *error = -SCTP_IERROR_NOMEM;
+
+ goto fail;
+ }
+
+ /* Make a new base association. */
+ scope = sctp_scope(sctp_source(chunk));
+ retval = sctp_association_new(ep, ep->base.sk, scope, gfp);
+ if (!retval) {
+ *error = -SCTP_IERROR_NOMEM;
+ goto fail;
+ }
+
+ /* Set up our peer's port number. */
+ retval->peer.port = ntohs(chunk->sctp_hdr->source);
+
+ /* Populate the association from the cookie. */
+ memcpy(&retval->c, bear_cookie, sizeof(*bear_cookie));
+
+ if (sctp_assoc_set_bind_addr_from_cookie(retval, bear_cookie,
+ GFP_ATOMIC) < 0) {
+ *error = -SCTP_IERROR_NOMEM;
+ goto fail;
+ }
+
+ /* Also, add the destination address. */
+ if (list_empty(&retval->base.bind_addr.address_list)) {
+ sctp_add_bind_addr(&retval->base.bind_addr, &chunk->dest,
+ GFP_ATOMIC);
+ }
+
+ retval->next_tsn = retval->c.initial_tsn;
+ retval->ctsn_ack_point = retval->next_tsn - 1;
+ retval->addip_serial = retval->c.initial_tsn;
+ retval->adv_peer_ack_point = retval->ctsn_ack_point;
+ retval->peer.prsctp_capable = retval->c.prsctp_capable;
+ retval->peer.adaption_ind = retval->c.adaption_ind;
+
+ /* The INIT stuff will be done by the side effects. */
+ return retval;
+
+fail:
+ if (retval)
+ sctp_association_free(retval);
+
+ return NULL;
+
+malformed:
+ /* Yikes! The packet is either corrupt or deliberately
+ * malformed.
+ */
+ *error = -SCTP_IERROR_MALFORMED;
+ goto fail;
+}
+
+/********************************************************************
+ * 3rd Level Abstractions
+ ********************************************************************/
+
+struct __sctp_missing {
+ __u32 num_missing;
+ __u16 type;
+} __attribute__((packed));;
+
+/*
+ * Report a missing mandatory parameter.
+ */
+static int sctp_process_missing_param(const struct sctp_association *asoc,
+ sctp_param_t paramtype,
+ struct sctp_chunk *chunk,
+ struct sctp_chunk **errp)
+{
+ struct __sctp_missing report;
+ __u16 len;
+
+ len = WORD_ROUND(sizeof(report));
+
+ /* Make an ERROR chunk, preparing enough room for
+ * returning multiple unknown parameters.
+ */
+ if (!*errp)
+ *errp = sctp_make_op_error_space(asoc, chunk, len);
+
+ if (*errp) {
+ report.num_missing = htonl(1);
+ report.type = paramtype;
+ sctp_init_cause(*errp, SCTP_ERROR_INV_PARAM,
+ &report, sizeof(report));
+ }
+
+ /* Stop processing this chunk. */
+ return 0;
+}
+
+/* Report an Invalid Mandatory Parameter. */
+static int sctp_process_inv_mandatory(const struct sctp_association *asoc,
+ struct sctp_chunk *chunk,
+ struct sctp_chunk **errp)
+{
+ /* Invalid Mandatory Parameter Error has no payload. */
+
+ if (!*errp)
+ *errp = sctp_make_op_error_space(asoc, chunk, 0);
+
+ if (*errp)
+ sctp_init_cause(*errp, SCTP_ERROR_INV_PARAM, NULL, 0);
+
+ /* Stop processing this chunk. */
+ return 0;
+}
+
+static int sctp_process_inv_paramlength(const struct sctp_association *asoc,
+ struct sctp_paramhdr *param,
+ const struct sctp_chunk *chunk,
+ struct sctp_chunk **errp)
+{
+ char error[] = "The following parameter had invalid length:";
+ size_t payload_len = WORD_ROUND(sizeof(error)) +
+ sizeof(sctp_paramhdr_t);
+
+
+ /* Create an error chunk and fill it in with our payload. */
+ if (!*errp)
+ *errp = sctp_make_op_error_space(asoc, chunk, payload_len);
+
+ if (*errp) {
+ sctp_init_cause(*errp, SCTP_ERROR_PROTO_VIOLATION, error,
+ sizeof(error));
+ sctp_addto_chunk(*errp, sizeof(sctp_paramhdr_t), param);
+ }
+
+ return 0;
+}
+
+
+/* Do not attempt to handle the HOST_NAME parm. However, do
+ * send back an indicator to the peer.
+ */
+static int sctp_process_hn_param(const struct sctp_association *asoc,
+ union sctp_params param,
+ struct sctp_chunk *chunk,
+ struct sctp_chunk **errp)
+{
+ __u16 len = ntohs(param.p->length);
+
+ /* Make an ERROR chunk. */
+ if (!*errp)
+ *errp = sctp_make_op_error_space(asoc, chunk, len);
+
+ if (*errp)
+ sctp_init_cause(*errp, SCTP_ERROR_DNS_FAILED,
+ param.v, len);
+
+ /* Stop processing this chunk. */
+ return 0;
+}
+
+/* RFC 3.2.1 & the Implementers Guide 2.2.
+ *
+ * The Parameter Types are encoded such that the
+ * highest-order two bits specify the action that must be
+ * taken if the processing endpoint does not recognize the
+ * Parameter Type.
+ *
+ * 00 - Stop processing this SCTP chunk and discard it,
+ * do not process any further chunks within it.
+ *
+ * 01 - Stop processing this SCTP chunk and discard it,
+ * do not process any further chunks within it, and report
+ * the unrecognized parameter in an 'Unrecognized
+ * Parameter Type' (in either an ERROR or in the INIT ACK).
+ *
+ * 10 - Skip this parameter and continue processing.
+ *
+ * 11 - Skip this parameter and continue processing but
+ * report the unrecognized parameter in an
+ * 'Unrecognized Parameter Type' (in either an ERROR or in
+ * the INIT ACK).
+ *
+ * Return value:
+ * 0 - discard the chunk
+ * 1 - continue with the chunk
+ */
+static int sctp_process_unk_param(const struct sctp_association *asoc,
+ union sctp_params param,
+ struct sctp_chunk *chunk,
+ struct sctp_chunk **errp)
+{
+ int retval = 1;
+
+ switch (param.p->type & SCTP_PARAM_ACTION_MASK) {
+ case SCTP_PARAM_ACTION_DISCARD:
+ retval = 0;
+ break;
+ case SCTP_PARAM_ACTION_DISCARD_ERR:
+ retval = 0;
+ /* Make an ERROR chunk, preparing enough room for
+ * returning multiple unknown parameters.
+ */
+ if (NULL == *errp)
+ *errp = sctp_make_op_error_space(asoc, chunk,
+ ntohs(chunk->chunk_hdr->length));
+
+ if (*errp)
+ sctp_init_cause(*errp, SCTP_ERROR_UNKNOWN_PARAM,
+ param.v,
+ WORD_ROUND(ntohs(param.p->length)));
+
+ break;
+ case SCTP_PARAM_ACTION_SKIP:
+ break;
+ case SCTP_PARAM_ACTION_SKIP_ERR:
+ /* Make an ERROR chunk, preparing enough room for
+ * returning multiple unknown parameters.
+ */
+ if (NULL == *errp)
+ *errp = sctp_make_op_error_space(asoc, chunk,
+ ntohs(chunk->chunk_hdr->length));
+
+ if (*errp) {
+ sctp_init_cause(*errp, SCTP_ERROR_UNKNOWN_PARAM,
+ param.v,
+ WORD_ROUND(ntohs(param.p->length)));
+ } else {
+ /* If there is no memory for generating the ERROR
+ * report as specified, an ABORT will be triggered
+ * to the peer and the association won't be
+ * established.
+ */
+ retval = 0;
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ return retval;
+}
+
+/* Find unrecognized parameters in the chunk.
+ * Return values:
+ * 0 - discard the chunk
+ * 1 - continue with the chunk
+ */
+static int sctp_verify_param(const struct sctp_association *asoc,
+ union sctp_params param,
+ sctp_cid_t cid,
+ struct sctp_chunk *chunk,
+ struct sctp_chunk **err_chunk)
+{
+ int retval = 1;
+
+ /* FIXME - This routine is not looking at each parameter per the
+ * chunk type, i.e., unrecognized parameters should be further
+ * identified based on the chunk id.
+ */
+
+ switch (param.p->type) {
+ case SCTP_PARAM_IPV4_ADDRESS:
+ case SCTP_PARAM_IPV6_ADDRESS:
+ case SCTP_PARAM_COOKIE_PRESERVATIVE:
+ case SCTP_PARAM_SUPPORTED_ADDRESS_TYPES:
+ case SCTP_PARAM_STATE_COOKIE:
+ case SCTP_PARAM_HEARTBEAT_INFO:
+ case SCTP_PARAM_UNRECOGNIZED_PARAMETERS:
+ case SCTP_PARAM_ECN_CAPABLE:
+ case SCTP_PARAM_ADAPTION_LAYER_IND:
+ break;
+
+ case SCTP_PARAM_HOST_NAME_ADDRESS:
+ /* Tell the peer, we won't support this param. */
+ return sctp_process_hn_param(asoc, param, chunk, err_chunk);
+ case SCTP_PARAM_FWD_TSN_SUPPORT:
+ if (sctp_prsctp_enable)
+ break;
+ /* Fall Through */
+ default:
+ SCTP_DEBUG_PRINTK("Unrecognized param: %d for chunk %d.\n",
+ ntohs(param.p->type), cid);
+ return sctp_process_unk_param(asoc, param, chunk, err_chunk);
+
+ break;
+ }
+ return retval;
+}
+
+/* Verify the INIT packet before we process it. */
+int sctp_verify_init(const struct sctp_association *asoc,
+ sctp_cid_t cid,
+ sctp_init_chunk_t *peer_init,
+ struct sctp_chunk *chunk,
+ struct sctp_chunk **errp)
+{
+ union sctp_params param;
+ int has_cookie = 0;
+
+ /* Verify stream values are non-zero. */
+ if ((0 == peer_init->init_hdr.num_outbound_streams) ||
+ (0 == peer_init->init_hdr.num_inbound_streams)) {
+
+ sctp_process_inv_mandatory(asoc, chunk, errp);
+ return 0;
+ }
+
+ /* Check for missing mandatory parameters. */
+ sctp_walk_params(param, peer_init, init_hdr.params) {
+
+ if (SCTP_PARAM_STATE_COOKIE == param.p->type)
+ has_cookie = 1;
+
+ } /* for (loop through all parameters) */
+
+ /* There is a possibility that a parameter length was bad and
+ * in that case we would have stoped walking the parameters.
+ * The current param.p would point at the bad one.
+ * Current consensus on the mailing list is to generate a PROTOCOL
+ * VIOLATION error. We build the ERROR chunk here and let the normal
+ * error handling code build and send the packet.
+ */
+ if (param.v < (void*)chunk->chunk_end - sizeof(sctp_paramhdr_t)) {
+ sctp_process_inv_paramlength(asoc, param.p, chunk, errp);
+ return 0;
+ }
+
+ /* The only missing mandatory param possible today is
+ * the state cookie for an INIT-ACK chunk.
+ */
+ if ((SCTP_CID_INIT_ACK == cid) && !has_cookie) {
+ sctp_process_missing_param(asoc, SCTP_PARAM_STATE_COOKIE,
+ chunk, errp);
+ return 0;
+ }
+
+ /* Find unrecognized parameters. */
+
+ sctp_walk_params(param, peer_init, init_hdr.params) {
+
+ if (!sctp_verify_param(asoc, param, cid, chunk, errp)) {
+ if (SCTP_PARAM_HOST_NAME_ADDRESS == param.p->type)
+ return 0;
+ else
+ return 1;
+ }
+
+ } /* for (loop through all parameters) */
+
+ return 1;
+}
+
+/* Unpack the parameters in an INIT packet into an association.
+ * Returns 0 on failure, else success.
+ * FIXME: This is an association method.
+ */
+int sctp_process_init(struct sctp_association *asoc, sctp_cid_t cid,
+ const union sctp_addr *peer_addr,
+ sctp_init_chunk_t *peer_init, int gfp)
+{
+ union sctp_params param;
+ struct sctp_transport *transport;
+ struct list_head *pos, *temp;
+ char *cookie;
+
+ /* We must include the address that the INIT packet came from.
+ * This is the only address that matters for an INIT packet.
+ * When processing a COOKIE ECHO, we retrieve the from address
+ * of the INIT from the cookie.
+ */
+
+ /* This implementation defaults to making the first transport
+ * added as the primary transport. The source address seems to
+ * be a a better choice than any of the embedded addresses.
+ */
+ if (peer_addr)
+ if(!sctp_assoc_add_peer(asoc, peer_addr, gfp))
+ goto nomem;
+
+ /* Process the initialization parameters. */
+
+ sctp_walk_params(param, peer_init, init_hdr.params) {
+
+ if (!sctp_process_param(asoc, param, peer_addr, gfp))
+ goto clean_up;
+ }
+
+ /* The fixed INIT headers are always in network byte
+ * order.
+ */
+ asoc->peer.i.init_tag =
+ ntohl(peer_init->init_hdr.init_tag);
+ asoc->peer.i.a_rwnd =
+ ntohl(peer_init->init_hdr.a_rwnd);
+ asoc->peer.i.num_outbound_streams =
+ ntohs(peer_init->init_hdr.num_outbound_streams);
+ asoc->peer.i.num_inbound_streams =
+ ntohs(peer_init->init_hdr.num_inbound_streams);
+ asoc->peer.i.initial_tsn =
+ ntohl(peer_init->init_hdr.initial_tsn);
+
+ /* Apply the upper bounds for output streams based on peer's
+ * number of inbound streams.
+ */
+ if (asoc->c.sinit_num_ostreams >
+ ntohs(peer_init->init_hdr.num_inbound_streams)) {
+ asoc->c.sinit_num_ostreams =
+ ntohs(peer_init->init_hdr.num_inbound_streams);
+ }
+
+ if (asoc->c.sinit_max_instreams >
+ ntohs(peer_init->init_hdr.num_outbound_streams)) {
+ asoc->c.sinit_max_instreams =
+ ntohs(peer_init->init_hdr.num_outbound_streams);
+ }
+
+ /* Copy Initiation tag from INIT to VT_peer in cookie. */
+ asoc->c.peer_vtag = asoc->peer.i.init_tag;
+
+ /* Peer Rwnd : Current calculated value of the peer's rwnd. */
+ asoc->peer.rwnd = asoc->peer.i.a_rwnd;
+
+ /* Copy cookie in case we need to resend COOKIE-ECHO. */
+ cookie = asoc->peer.cookie;
+ if (cookie) {
+ asoc->peer.cookie = kmalloc(asoc->peer.cookie_len, gfp);
+ if (!asoc->peer.cookie)
+ goto clean_up;
+ memcpy(asoc->peer.cookie, cookie, asoc->peer.cookie_len);
+ }
+
+ /* RFC 2960 7.2.1 The initial value of ssthresh MAY be arbitrarily
+ * high (for example, implementations MAY use the size of the receiver
+ * advertised window).
+ */
+ list_for_each(pos, &asoc->peer.transport_addr_list) {
+ transport = list_entry(pos, struct sctp_transport, transports);
+ transport->ssthresh = asoc->peer.i.a_rwnd;
+ }
+
+ /* Set up the TSN tracking pieces. */
+ sctp_tsnmap_init(&asoc->peer.tsn_map, SCTP_TSN_MAP_SIZE,
+ asoc->peer.i.initial_tsn);
+
+ /* RFC 2960 6.5 Stream Identifier and Stream Sequence Number
+ *
+ * The stream sequence number in all the streams shall start
+ * from 0 when the association is established. Also, when the
+ * stream sequence number reaches the value 65535 the next
+ * stream sequence number shall be set to 0.
+ */
+
+ /* Allocate storage for the negotiated streams if it is not a temporary * association.
+ */
+ if (!asoc->temp) {
+ asoc->ssnmap = sctp_ssnmap_new(asoc->c.sinit_max_instreams,
+ asoc->c.sinit_num_ostreams, gfp);
+ if (!asoc->ssnmap)
+ goto nomem_ssnmap;
+ }
+
+ /* ADDIP Section 4.1 ASCONF Chunk Procedures
+ *
+ * When an endpoint has an ASCONF signaled change to be sent to the
+ * remote endpoint it should do the following:
+ * ...
+ * A2) A serial number should be assigned to the Chunk. The serial
+ * number should be a monotonically increasing number. All serial
+ * numbers are defined to be initialized at the start of the
+ * association to the same value as the Initial TSN.
+ */
+ asoc->peer.addip_serial = asoc->peer.i.initial_tsn - 1;
+ return 1;
+
+nomem_ssnmap:
+clean_up:
+ /* Release the transport structures. */
+ list_for_each_safe(pos, temp, &asoc->peer.transport_addr_list) {
+ transport = list_entry(pos, struct sctp_transport, transports);
+ list_del_init(pos);
+ sctp_transport_free(transport);
+ }
+nomem:
+ return 0;
+}
+
+
+/* Update asoc with the option described in param.
+ *
+ * RFC2960 3.3.2.1 Optional/Variable Length Parameters in INIT
+ *
+ * asoc is the association to update.
+ * param is the variable length parameter to use for update.
+ * cid tells us if this is an INIT, INIT ACK or COOKIE ECHO.
+ * If the current packet is an INIT we want to minimize the amount of
+ * work we do. In particular, we should not build transport
+ * structures for the addresses.
+ */
+static int sctp_process_param(struct sctp_association *asoc,
+ union sctp_params param,
+ const union sctp_addr *peer_addr,
+ int gfp)
+{
+ union sctp_addr addr;
+ int i;
+ __u16 sat;
+ int retval = 1;
+ sctp_scope_t scope;
+ time_t stale;
+ struct sctp_af *af;
+
+ /* We maintain all INIT parameters in network byte order all the
+ * time. This allows us to not worry about whether the parameters
+ * came from a fresh INIT, and INIT ACK, or were stored in a cookie.
+ */
+ switch (param.p->type) {
+ case SCTP_PARAM_IPV6_ADDRESS:
+ if (PF_INET6 != asoc->base.sk->family)
+ break;
+ /* Fall through. */
+ case SCTP_PARAM_IPV4_ADDRESS:
+ af = sctp_get_af_specific(param_type2af(param.p->type));
+ af->from_addr_param(&addr, param.addr, asoc->peer.port, 0);
+ scope = sctp_scope(peer_addr);
+ if (sctp_in_scope(&addr, scope))
+ if (!sctp_assoc_add_peer(asoc, &addr, gfp))
+ return 0;
+ break;
+
+ case SCTP_PARAM_COOKIE_PRESERVATIVE:
+ if (!sctp_cookie_preserve_enable)
+ break;
+
+ stale = ntohl(param.life->lifespan_increment);
+
+ /* Suggested Cookie Life span increment's unit is msec,
+ * (1/1000sec).
+ */
+ asoc->cookie_life.tv_sec += stale / 1000;
+ asoc->cookie_life.tv_usec += (stale % 1000) * 1000;
+ break;
+
+ case SCTP_PARAM_HOST_NAME_ADDRESS:
+ SCTP_DEBUG_PRINTK("unimplemented SCTP_HOST_NAME_ADDRESS\n");
+ break;
+
+ case SCTP_PARAM_SUPPORTED_ADDRESS_TYPES:
+ /* Turn off the default values first so we'll know which
+ * ones are really set by the peer.
+ */
+ asoc->peer.ipv4_address = 0;
+ asoc->peer.ipv6_address = 0;
+
+ /* Cycle through address types; avoid divide by 0. */
+ sat = ntohs(param.p->length) - sizeof(sctp_paramhdr_t);
+ if (sat)
+ sat /= sizeof(__u16);
+
+ for (i = 0; i < sat; ++i) {
+ switch (param.sat->types[i]) {
+ case SCTP_PARAM_IPV4_ADDRESS:
+ asoc->peer.ipv4_address = 1;
+ break;
+
+ case SCTP_PARAM_IPV6_ADDRESS:
+ asoc->peer.ipv6_address = 1;
+ break;
+
+ case SCTP_PARAM_HOST_NAME_ADDRESS:
+ asoc->peer.hostname_address = 1;
+ break;
+
+ default: /* Just ignore anything else. */
+ break;
+ };
+ }
+ break;
+
+ case SCTP_PARAM_STATE_COOKIE:
+ asoc->peer.cookie_len =
+ ntohs(param.p->length) - sizeof(sctp_paramhdr_t);
+ asoc->peer.cookie = param.cookie->body;
+ break;
+
+ case SCTP_PARAM_HEARTBEAT_INFO:
+ /* Would be odd to receive, but it causes no problems. */
+ break;
+
+ case SCTP_PARAM_UNRECOGNIZED_PARAMETERS:
+ /* Rejected during verify stage. */
+ break;
+
+ case SCTP_PARAM_ECN_CAPABLE:
+ asoc->peer.ecn_capable = 1;
+ break;
+
+ case SCTP_PARAM_ADAPTION_LAYER_IND:
+ asoc->peer.adaption_ind = param.aind->adaption_ind;
+ break;
+
+ case SCTP_PARAM_FWD_TSN_SUPPORT:
+ if (sctp_prsctp_enable) {
+ asoc->peer.prsctp_capable = 1;
+ break;
+ }
+ /* Fall Through */
+ default:
+ /* Any unrecognized parameters should have been caught
+ * and handled by sctp_verify_param() which should be
+ * called prior to this routine. Simply log the error
+ * here.
+ */
+ SCTP_DEBUG_PRINTK("Ignoring param: %d for association %p.\n",
+ ntohs(param.p->type), asoc);
+ break;
+ };
+
+ return retval;
+}
+
+/* Select a new verification tag. */
+__u32 sctp_generate_tag(const struct sctp_endpoint *ep)
+{
+ /* I believe that this random number generator complies with RFC1750.
+ * A tag of 0 is reserved for special cases (e.g. INIT).
+ */
+ __u32 x;
+
+ do {
+ get_random_bytes(&x, sizeof(__u32));
+ } while (x == 0);
+
+ return x;
+}
+
+/* Select an initial TSN to send during startup. */
+__u32 sctp_generate_tsn(const struct sctp_endpoint *ep)
+{
+ __u32 retval;
+
+ get_random_bytes(&retval, sizeof(__u32));
+ return retval;
+}
+
+/*
+ * ADDIP 3.1.1 Address Configuration Change Chunk (ASCONF)
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type = 0xC1 | Chunk Flags | Chunk Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Serial Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Address Parameter |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | ASCONF Parameter #1 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * \ \
+ * / .... /
+ * \ \
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | ASCONF Parameter #N |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Address Parameter and other parameter will not be wrapped in this function
+ */
+static struct sctp_chunk *sctp_make_asconf(struct sctp_association *asoc,
+ union sctp_addr *addr,
+ int vparam_len)
+{
+ sctp_addiphdr_t asconf;
+ struct sctp_chunk *retval;
+ int length = sizeof(asconf) + vparam_len;
+ union sctp_addr_param addrparam;
+ int addrlen;
+ struct sctp_af *af = sctp_get_af_specific(addr->v4.sin_family);
+
+ addrlen = af->to_addr_param(addr, &addrparam);
+ if (!addrlen)
+ return NULL;
+ length += addrlen;
+
+ /* Create the chunk. */
+ retval = sctp_make_chunk(asoc, SCTP_CID_ASCONF, 0, length);
+ if (!retval)
+ return NULL;
+
+ asconf.serial = htonl(asoc->addip_serial++);
+
+ retval->subh.addip_hdr =
+ sctp_addto_chunk(retval, sizeof(asconf), &asconf);
+ retval->param_hdr.v =
+ sctp_addto_chunk(retval, addrlen, &addrparam);
+
+ return retval;
+}
+
+/* ADDIP
+ * 3.2.1 Add IP Address
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type = 0xC001 | Length = Variable |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | ASCONF-Request Correlation ID |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Address Parameter |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * 3.2.2 Delete IP Address
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type = 0xC002 | Length = Variable |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | ASCONF-Request Correlation ID |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Address Parameter |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ */
+struct sctp_chunk *sctp_make_asconf_update_ip(struct sctp_association *asoc,
+ union sctp_addr *laddr,
+ struct sockaddr *addrs,
+ int addrcnt,
+ __u16 flags)
+{
+ sctp_addip_param_t param;
+ struct sctp_chunk *retval;
+ union sctp_addr_param addr_param;
+ union sctp_addr *addr;
+ void *addr_buf;
+ struct sctp_af *af;
+ int paramlen = sizeof(param);
+ int addr_param_len = 0;
+ int totallen = 0;
+ int i;
+
+ /* Get total length of all the address parameters. */
+ addr_buf = addrs;
+ for (i = 0; i < addrcnt; i++) {
+ addr = (union sctp_addr *)addr_buf;
+ af = sctp_get_af_specific(addr->v4.sin_family);
+ addr_param_len = af->to_addr_param(addr, &addr_param);
+
+ totallen += paramlen;
+ totallen += addr_param_len;
+
+ addr_buf += af->sockaddr_len;
+ }
+
+ /* Create an asconf chunk with the required length. */
+ retval = sctp_make_asconf(asoc, laddr, totallen);
+ if (!retval)
+ return NULL;
+
+ /* Add the address parameters to the asconf chunk. */
+ addr_buf = addrs;
+ for (i = 0; i < addrcnt; i++) {
+ addr = (union sctp_addr *)addr_buf;
+ af = sctp_get_af_specific(addr->v4.sin_family);
+ addr_param_len = af->to_addr_param(addr, &addr_param);
+ param.param_hdr.type = flags;
+ param.param_hdr.length = htons(paramlen + addr_param_len);
+ param.crr_id = i;
+
+ sctp_addto_chunk(retval, paramlen, &param);
+ sctp_addto_chunk(retval, addr_param_len, &addr_param);
+
+ addr_buf += af->sockaddr_len;
+ }
+ return retval;
+}
+
+/* ADDIP
+ * 3.2.4 Set Primary IP Address
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type =0xC004 | Length = Variable |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | ASCONF-Request Correlation ID |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Address Parameter |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Create an ASCONF chunk with Set Primary IP address parameter.
+ */
+struct sctp_chunk *sctp_make_asconf_set_prim(struct sctp_association *asoc,
+ union sctp_addr *addr)
+{
+ sctp_addip_param_t param;
+ struct sctp_chunk *retval;
+ int len = sizeof(param);
+ union sctp_addr_param addrparam;
+ int addrlen;
+ struct sctp_af *af = sctp_get_af_specific(addr->v4.sin_family);
+
+ addrlen = af->to_addr_param(addr, &addrparam);
+ if (!addrlen)
+ return NULL;
+ len += addrlen;
+
+ /* Create the chunk and make asconf header. */
+ retval = sctp_make_asconf(asoc, addr, len);
+ if (!retval)
+ return NULL;
+
+ param.param_hdr.type = SCTP_PARAM_SET_PRIMARY;
+ param.param_hdr.length = htons(len);
+ param.crr_id = 0;
+
+ sctp_addto_chunk(retval, sizeof(param), &param);
+ sctp_addto_chunk(retval, addrlen, &addrparam);
+
+ return retval;
+}
+
+/* ADDIP 3.1.2 Address Configuration Acknowledgement Chunk (ASCONF-ACK)
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type = 0x80 | Chunk Flags | Chunk Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Serial Number |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | ASCONF Parameter Response#1 |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * \ \
+ * / .... /
+ * \ \
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | ASCONF Parameter Response#N |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Create an ASCONF_ACK chunk with enough space for the parameter responses.
+ */
+static struct sctp_chunk *sctp_make_asconf_ack(const struct sctp_association *asoc,
+ __u32 serial, int vparam_len)
+{
+ sctp_addiphdr_t asconf;
+ struct sctp_chunk *retval;
+ int length = sizeof(asconf) + vparam_len;
+
+ /* Create the chunk. */
+ retval = sctp_make_chunk(asoc, SCTP_CID_ASCONF_ACK, 0, length);
+ if (!retval)
+ return NULL;
+
+ asconf.serial = htonl(serial);
+
+ retval->subh.addip_hdr =
+ sctp_addto_chunk(retval, sizeof(asconf), &asconf);
+
+ return retval;
+}
+
+/* Add response parameters to an ASCONF_ACK chunk. */
+static void sctp_add_asconf_response(struct sctp_chunk *chunk, __u32 crr_id,
+ __u16 err_code, sctp_addip_param_t *asconf_param)
+{
+ sctp_addip_param_t ack_param;
+ sctp_errhdr_t err_param;
+ int asconf_param_len = 0;
+ int err_param_len = 0;
+ __u16 response_type;
+
+ if (SCTP_ERROR_NO_ERROR == err_code) {
+ response_type = SCTP_PARAM_SUCCESS_REPORT;
+ } else {
+ response_type = SCTP_PARAM_ERR_CAUSE;
+ err_param_len = sizeof(err_param);
+ if (asconf_param)
+ asconf_param_len =
+ ntohs(asconf_param->param_hdr.length);
+ }
+
+ /* Add Success Indication or Error Cause Indication parameter. */
+ ack_param.param_hdr.type = response_type;
+ ack_param.param_hdr.length = htons(sizeof(ack_param) +
+ err_param_len +
+ asconf_param_len);
+ ack_param.crr_id = crr_id;
+ sctp_addto_chunk(chunk, sizeof(ack_param), &ack_param);
+
+ if (SCTP_ERROR_NO_ERROR == err_code)
+ return;
+
+ /* Add Error Cause parameter. */
+ err_param.cause = err_code;
+ err_param.length = htons(err_param_len + asconf_param_len);
+ sctp_addto_chunk(chunk, err_param_len, &err_param);
+
+ /* Add the failed TLV copied from ASCONF chunk. */
+ if (asconf_param)
+ sctp_addto_chunk(chunk, asconf_param_len, asconf_param);
+}
+
+/* Process a asconf parameter. */
+static __u16 sctp_process_asconf_param(struct sctp_association *asoc,
+ struct sctp_chunk *asconf,
+ sctp_addip_param_t *asconf_param)
+{
+ struct sctp_transport *peer;
+ struct sctp_af *af;
+ union sctp_addr addr;
+ struct list_head *pos;
+ union sctp_addr_param *addr_param;
+
+ addr_param = (union sctp_addr_param *)
+ ((void *)asconf_param + sizeof(sctp_addip_param_t));
+
+ af = sctp_get_af_specific(param_type2af(addr_param->v4.param_hdr.type));
+ if (unlikely(!af))
+ return SCTP_ERROR_INV_PARAM;
+
+ af->from_addr_param(&addr, addr_param, asoc->peer.port, 0);
+ switch (asconf_param->param_hdr.type) {
+ case SCTP_PARAM_ADD_IP:
+ /* ADDIP 4.3 D9) If an endpoint receives an ADD IP address
+ * request and does not have the local resources to add this
+ * new address to the association, it MUST return an Error
+ * Cause TLV set to the new error code 'Operation Refused
+ * Due to Resource Shortage'.
+ */
+
+ peer = sctp_assoc_add_peer(asoc, &addr, GFP_ATOMIC);
+ if (!peer)
+ return SCTP_ERROR_RSRC_LOW;
+
+ /* Start the heartbeat timer. */
+ if (!mod_timer(&peer->hb_timer, sctp_transport_timeout(peer)))
+ sctp_transport_hold(peer);
+ break;
+ case SCTP_PARAM_DEL_IP:
+ /* ADDIP 4.3 D7) If a request is received to delete the
+ * last remaining IP address of a peer endpoint, the receiver
+ * MUST send an Error Cause TLV with the error cause set to the
+ * new error code 'Request to Delete Last Remaining IP Address'.
+ */
+ pos = asoc->peer.transport_addr_list.next;
+ if (pos->next == &asoc->peer.transport_addr_list)
+ return SCTP_ERROR_DEL_LAST_IP;
+
+ /* ADDIP 4.3 D8) If a request is received to delete an IP
+ * address which is also the source address of the IP packet
+ * which contained the ASCONF chunk, the receiver MUST reject
+ * this request. To reject the request the receiver MUST send
+ * an Error Cause TLV set to the new error code 'Request to
+ * Delete Source IP Address'
+ */
+ if (sctp_cmp_addr_exact(sctp_source(asconf), &addr))
+ return SCTP_ERROR_DEL_SRC_IP;
+
+ sctp_assoc_del_peer(asoc, &addr);
+ break;
+ case SCTP_PARAM_SET_PRIMARY:
+ peer = sctp_assoc_lookup_paddr(asoc, &addr);
+ if (!peer)
+ return SCTP_ERROR_INV_PARAM;
+
+ sctp_assoc_set_primary(asoc, peer);
+ break;
+ default:
+ return SCTP_ERROR_INV_PARAM;
+ break;
+ }
+
+ return SCTP_ERROR_NO_ERROR;
+}
+
+/* Process an incoming ASCONF chunk with the next expected serial no. and
+ * return an ASCONF_ACK chunk to be sent in response.
+ */
+struct sctp_chunk *sctp_process_asconf(struct sctp_association *asoc,
+ struct sctp_chunk *asconf)
+{
+ sctp_addiphdr_t *hdr;
+ union sctp_addr_param *addr_param;
+ sctp_addip_param_t *asconf_param;
+ struct sctp_chunk *asconf_ack;
+
+ __u16 err_code;
+ int length = 0;
+ int chunk_len = asconf->skb->len;
+ __u32 serial;
+ int all_param_pass = 1;
+
+ hdr = (sctp_addiphdr_t *)asconf->skb->data;
+ serial = ntohl(hdr->serial);
+
+ /* Skip the addiphdr and store a pointer to address parameter. */
+ length = sizeof(sctp_addiphdr_t);
+ addr_param = (union sctp_addr_param *)(asconf->skb->data + length);
+ chunk_len -= length;
+
+ /* Skip the address parameter and store a pointer to the first
+ * asconf paramter.
+ */
+ length = ntohs(addr_param->v4.param_hdr.length);
+ asconf_param = (sctp_addip_param_t *)((void *)addr_param + length);
+ chunk_len -= length;
+
+ /* create an ASCONF_ACK chunk.
+ * Based on the definitions of parameters, we know that the size of
+ * ASCONF_ACK parameters are less than or equal to the twice of ASCONF
+ * paramters.
+ */
+ asconf_ack = sctp_make_asconf_ack(asoc, serial, chunk_len * 2);
+ if (!asconf_ack)
+ goto done;
+
+ /* Process the TLVs contained within the ASCONF chunk. */
+ while (chunk_len > 0) {
+ err_code = sctp_process_asconf_param(asoc, asconf,
+ asconf_param);
+ /* ADDIP 4.1 A7)
+ * If an error response is received for a TLV parameter,
+ * all TLVs with no response before the failed TLV are
+ * considered successful if not reported. All TLVs after
+ * the failed response are considered unsuccessful unless
+ * a specific success indication is present for the parameter.
+ */
+ if (SCTP_ERROR_NO_ERROR != err_code)
+ all_param_pass = 0;
+
+ if (!all_param_pass)
+ sctp_add_asconf_response(asconf_ack,
+ asconf_param->crr_id, err_code,
+ asconf_param);
+
+ /* ADDIP 4.3 D11) When an endpoint receiving an ASCONF to add
+ * an IP address sends an 'Out of Resource' in its response, it
+ * MUST also fail any subsequent add or delete requests bundled
+ * in the ASCONF.
+ */
+ if (SCTP_ERROR_RSRC_LOW == err_code)
+ goto done;
+
+ /* Move to the next ASCONF param. */
+ length = ntohs(asconf_param->param_hdr.length);
+ asconf_param = (sctp_addip_param_t *)((void *)asconf_param +
+ length);
+ chunk_len -= length;
+ }
+
+done:
+ asoc->peer.addip_serial++;
+
+ /* If we are sending a new ASCONF_ACK hold a reference to it in assoc
+ * after freeing the reference to old asconf ack if any.
+ */
+ if (asconf_ack) {
+ if (asoc->addip_last_asconf_ack)
+ sctp_chunk_free(asoc->addip_last_asconf_ack);
+
+ sctp_chunk_hold(asconf_ack);
+ asoc->addip_last_asconf_ack = asconf_ack;
+ }
+
+ return asconf_ack;
+}
+
+/* Process a asconf parameter that is successfully acked. */
+static int sctp_asconf_param_success(struct sctp_association *asoc,
+ sctp_addip_param_t *asconf_param)
+{
+ struct sctp_af *af;
+ union sctp_addr addr;
+ struct sctp_bind_addr *bp = &asoc->base.bind_addr;
+ union sctp_addr_param *addr_param;
+ struct list_head *pos;
+ struct sctp_transport *transport;
+ int retval = 0;
+
+ addr_param = (union sctp_addr_param *)
+ ((void *)asconf_param + sizeof(sctp_addip_param_t));
+
+ /* We have checked the packet before, so we do not check again. */
+ af = sctp_get_af_specific(param_type2af(addr_param->v4.param_hdr.type));
+ af->from_addr_param(&addr, addr_param, bp->port, 0);
+
+ switch (asconf_param->param_hdr.type) {
+ case SCTP_PARAM_ADD_IP:
+ sctp_local_bh_disable();
+ sctp_write_lock(&asoc->base.addr_lock);
+ retval = sctp_add_bind_addr(bp, &addr, GFP_ATOMIC);
+ sctp_write_unlock(&asoc->base.addr_lock);
+ sctp_local_bh_enable();
+ break;
+ case SCTP_PARAM_DEL_IP:
+ sctp_local_bh_disable();
+ sctp_write_lock(&asoc->base.addr_lock);
+ retval = sctp_del_bind_addr(bp, &addr);
+ sctp_write_unlock(&asoc->base.addr_lock);
+ sctp_local_bh_enable();
+ list_for_each(pos, &asoc->peer.transport_addr_list) {
+ transport = list_entry(pos, struct sctp_transport,
+ transports);
+ sctp_transport_route(transport, NULL,
+ sctp_sk(asoc->base.sk));
+ }
+ break;
+ default:
+ break;
+ }
+
+ return retval;
+}
+
+/* Get the corresponding ASCONF response error code from the ASCONF_ACK chunk
+ * for the given asconf parameter. If there is no response for this parameter,
+ * return the error code based on the third argument 'no_err'.
+ * ADDIP 4.1
+ * A7) If an error response is received for a TLV parameter, all TLVs with no
+ * response before the failed TLV are considered successful if not reported.
+ * All TLVs after the failed response are considered unsuccessful unless a
+ * specific success indication is present for the parameter.
+ */
+static __u16 sctp_get_asconf_response(struct sctp_chunk *asconf_ack,
+ sctp_addip_param_t *asconf_param,
+ int no_err)
+{
+ sctp_addip_param_t *asconf_ack_param;
+ sctp_errhdr_t *err_param;
+ int length;
+ int asconf_ack_len = asconf_ack->skb->len;
+ __u16 err_code;
+
+ if (no_err)
+ err_code = SCTP_ERROR_NO_ERROR;
+ else
+ err_code = SCTP_ERROR_REQ_REFUSED;
+
+ /* Skip the addiphdr from the asconf_ack chunk and store a pointer to
+ * the first asconf_ack parameter.
+ */
+ length = sizeof(sctp_addiphdr_t);
+ asconf_ack_param = (sctp_addip_param_t *)(asconf_ack->skb->data +
+ length);
+ asconf_ack_len -= length;
+
+ while (asconf_ack_len > 0) {
+ if (asconf_ack_param->crr_id == asconf_param->crr_id) {
+ switch(asconf_ack_param->param_hdr.type) {
+ case SCTP_PARAM_SUCCESS_REPORT:
+ return SCTP_ERROR_NO_ERROR;
+ case SCTP_PARAM_ERR_CAUSE:
+ length = sizeof(sctp_addip_param_t);
+ err_param = (sctp_errhdr_t *)
+ ((void *)asconf_ack_param + length);
+ asconf_ack_len -= length;
+ if (asconf_ack_len > 0)
+ return err_param->cause;
+ else
+ return SCTP_ERROR_INV_PARAM;
+ break;
+ default:
+ return SCTP_ERROR_INV_PARAM;
+ }
+ }
+
+ length = ntohs(asconf_ack_param->param_hdr.length);
+ asconf_ack_param = (sctp_addip_param_t *)
+ ((void *)asconf_ack_param + length);
+ asconf_ack_len -= length;
+ }
+
+ return err_code;
+}
+
+/* Process an incoming ASCONF_ACK chunk against the cached last ASCONF chunk. */
+int sctp_process_asconf_ack(struct sctp_association *asoc,
+ struct sctp_chunk *asconf_ack)
+{
+ struct sctp_chunk *asconf = asoc->addip_last_asconf;
+ union sctp_addr_param *addr_param;
+ sctp_addip_param_t *asconf_param;
+ int length = 0;
+ int asconf_len = asconf->skb->len;
+ int all_param_pass = 0;
+ int no_err = 1;
+ int retval = 0;
+ __u16 err_code = SCTP_ERROR_NO_ERROR;
+
+ /* Skip the chunkhdr and addiphdr from the last asconf sent and store
+ * a pointer to address parameter.
+ */
+ length = sizeof(sctp_addip_chunk_t);
+ addr_param = (union sctp_addr_param *)(asconf->skb->data + length);
+ asconf_len -= length;
+
+ /* Skip the address parameter in the last asconf sent and store a
+ * pointer to the first asconf paramter.
+ */
+ length = ntohs(addr_param->v4.param_hdr.length);
+ asconf_param = (sctp_addip_param_t *)((void *)addr_param + length);
+ asconf_len -= length;
+
+ /* ADDIP 4.1
+ * A8) If there is no response(s) to specific TLV parameter(s), and no
+ * failures are indicated, then all request(s) are considered
+ * successful.
+ */
+ if (asconf_ack->skb->len == sizeof(sctp_addiphdr_t))
+ all_param_pass = 1;
+
+ /* Process the TLVs contained in the last sent ASCONF chunk. */
+ while (asconf_len > 0) {
+ if (all_param_pass)
+ err_code = SCTP_ERROR_NO_ERROR;
+ else {
+ err_code = sctp_get_asconf_response(asconf_ack,
+ asconf_param,
+ no_err);
+ if (no_err && (SCTP_ERROR_NO_ERROR != err_code))
+ no_err = 0;
+ }
+
+ switch (err_code) {
+ case SCTP_ERROR_NO_ERROR:
+ retval = sctp_asconf_param_success(asoc, asconf_param);
+ break;
+
+ case SCTP_ERROR_RSRC_LOW:
+ retval = 1;
+ break;
+
+ case SCTP_ERROR_INV_PARAM:
+ /* Disable sending this type of asconf parameter in
+ * future.
+ */
+ asoc->peer.addip_disabled_mask |=
+ asconf_param->param_hdr.type;
+ break;
+
+ case SCTP_ERROR_REQ_REFUSED:
+ case SCTP_ERROR_DEL_LAST_IP:
+ case SCTP_ERROR_DEL_SRC_IP:
+ default:
+ break;
+ }
+
+ /* Skip the processed asconf parameter and move to the next
+ * one.
+ */
+ length = ntohs(asconf_param->param_hdr.length);
+ asconf_param = (sctp_addip_param_t *)((void *)asconf_param +
+ length);
+ asconf_len -= length;
+ }
+
+ /* Free the cached last sent asconf chunk. */
+ sctp_chunk_free(asconf);
+ asoc->addip_last_asconf = NULL;
+
+ /* Send the next asconf chunk from the addip chunk queue. */
+ asconf = (struct sctp_chunk *)__skb_dequeue(&asoc->addip_chunks);
+ if (asconf) {
+ /* Hold the chunk until an ASCONF_ACK is received. */
+ sctp_chunk_hold(asconf);
+ if (sctp_primitive_ASCONF(asoc, asconf))
+ sctp_chunk_free(asconf);
+ else
+ asoc->addip_last_asconf = asconf;
+ }
+
+ return retval;
+}
+
+/* Make a FWD TSN chunk. */
+struct sctp_chunk *sctp_make_fwdtsn(const struct sctp_association *asoc,
+ __u32 new_cum_tsn, size_t nstreams,
+ struct sctp_fwdtsn_skip *skiplist)
+{
+ struct sctp_chunk *retval = NULL;
+ struct sctp_fwdtsn_chunk *ftsn_chunk;
+ struct sctp_fwdtsn_hdr ftsn_hdr;
+ struct sctp_fwdtsn_skip skip;
+ size_t hint;
+ int i;
+
+ hint = (nstreams + 1) * sizeof(__u32);
+
+ /* Maybe set the T-bit if we have no association. */
+ retval = sctp_make_chunk(asoc, SCTP_CID_FWD_TSN, 0, hint);
+
+ if (!retval)
+ return NULL;
+
+ ftsn_chunk = (struct sctp_fwdtsn_chunk *)retval->subh.fwdtsn_hdr;
+
+ ftsn_hdr.new_cum_tsn = htonl(new_cum_tsn);
+ retval->subh.fwdtsn_hdr =
+ sctp_addto_chunk(retval, sizeof(ftsn_hdr), &ftsn_hdr);
+
+ for (i = 0; i < nstreams; i++) {
+ skip.stream = skiplist[i].stream;
+ skip.ssn = skiplist[i].ssn;
+ sctp_addto_chunk(retval, sizeof(skip), &skip);
+ }
+
+ return retval;
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/sm_sideeffect.c b/uClinux-2.4.31-uc0/net/sctp/sm_sideeffect.c
new file mode 100644
index 0000000..573c016
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/sm_sideeffect.c
@@ -0,0 +1,1395 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2001, 2004
+ * Copyright (c) 1999 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * These functions work with the state functions in sctp_sm_statefuns.c
+ * to implement that state operations. These functions implement the
+ * steps which require modifying existing data structures.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Jon Grimm <jgrimm@austin.ibm.com>
+ * Hui Huang <hui.huang@nokia.com>
+ * Dajiang Zhang <dajiang.zhang@nokia.com>
+ * Daisy Chang <daisyc@us.ibm.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ * Ardelle Fan <ardelle.fan@intel.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/skbuff.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/ip.h>
+#include <net/sock.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+static int sctp_cmd_interpreter(sctp_event_t event_type,
+ sctp_subtype_t subtype,
+ sctp_state_t state,
+ struct sctp_endpoint *ep,
+ struct sctp_association *asoc,
+ void *event_arg,
+ sctp_disposition_t status,
+ sctp_cmd_seq_t *commands,
+ int gfp);
+static int sctp_side_effects(sctp_event_t event_type, sctp_subtype_t subtype,
+ sctp_state_t state,
+ struct sctp_endpoint *ep,
+ struct sctp_association *asoc,
+ void *event_arg,
+ sctp_disposition_t status,
+ sctp_cmd_seq_t *commands,
+ int gfp);
+
+/********************************************************************
+ * Helper functions
+ ********************************************************************/
+
+/* A helper function for delayed processing of INET ECN CE bit. */
+static void sctp_do_ecn_ce_work(struct sctp_association *asoc,
+ __u32 lowest_tsn)
+{
+ /* Save the TSN away for comparison when we receive CWR */
+
+ asoc->last_ecne_tsn = lowest_tsn;
+ asoc->need_ecne = 1;
+}
+
+/* Helper function for delayed processing of SCTP ECNE chunk. */
+/* RFC 2960 Appendix A
+ *
+ * RFC 2481 details a specific bit for a sender to send in
+ * the header of its next outbound TCP segment to indicate to
+ * its peer that it has reduced its congestion window. This
+ * is termed the CWR bit. For SCTP the same indication is made
+ * by including the CWR chunk. This chunk contains one data
+ * element, i.e. the TSN number that was sent in the ECNE chunk.
+ * This element represents the lowest TSN number in the datagram
+ * that was originally marked with the CE bit.
+ */
+static struct sctp_chunk *sctp_do_ecn_ecne_work(struct sctp_association *asoc,
+ __u32 lowest_tsn,
+ struct sctp_chunk *chunk)
+{
+ struct sctp_chunk *repl;
+
+ /* Our previously transmitted packet ran into some congestion
+ * so we should take action by reducing cwnd and ssthresh
+ * and then ACK our peer that we we've done so by
+ * sending a CWR.
+ */
+
+ /* First, try to determine if we want to actually lower
+ * our cwnd variables. Only lower them if the ECNE looks more
+ * recent than the last response.
+ */
+ if (TSN_lt(asoc->last_cwr_tsn, lowest_tsn)) {
+ struct sctp_transport *transport;
+
+ /* Find which transport's congestion variables
+ * need to be adjusted.
+ */
+ transport = sctp_assoc_lookup_tsn(asoc, lowest_tsn);
+
+ /* Update the congestion variables. */
+ if (transport)
+ sctp_transport_lower_cwnd(transport,
+ SCTP_LOWER_CWND_ECNE);
+ asoc->last_cwr_tsn = lowest_tsn;
+ }
+
+ /* Always try to quiet the other end. In case of lost CWR,
+ * resend last_cwr_tsn.
+ */
+ repl = sctp_make_cwr(asoc, asoc->last_cwr_tsn, chunk);
+
+ /* If we run out of memory, it will look like a lost CWR. We'll
+ * get back in sync eventually.
+ */
+ return repl;
+}
+
+/* Helper function to do delayed processing of ECN CWR chunk. */
+static void sctp_do_ecn_cwr_work(struct sctp_association *asoc,
+ __u32 lowest_tsn)
+{
+ /* Turn off ECNE getting auto-prepended to every outgoing
+ * packet
+ */
+ asoc->need_ecne = 0;
+}
+
+/* Generate SACK if necessary. We call this at the end of a packet. */
+static int sctp_gen_sack(struct sctp_association *asoc, int force,
+ sctp_cmd_seq_t *commands)
+{
+ __u32 ctsn, max_tsn_seen;
+ struct sctp_chunk *sack;
+ int error = 0;
+
+ if (force)
+ asoc->peer.sack_needed = 1;
+
+ ctsn = sctp_tsnmap_get_ctsn(&asoc->peer.tsn_map);
+ max_tsn_seen = sctp_tsnmap_get_max_tsn_seen(&asoc->peer.tsn_map);
+
+ /* From 12.2 Parameters necessary per association (i.e. the TCB):
+ *
+ * Ack State : This flag indicates if the next received packet
+ * : is to be responded to with a SACK. ...
+ * : When DATA chunks are out of order, SACK's
+ * : are not delayed (see Section 6).
+ *
+ * [This is actually not mentioned in Section 6, but we
+ * implement it here anyway. --piggy]
+ */
+ if (max_tsn_seen != ctsn)
+ asoc->peer.sack_needed = 1;
+
+ /* From 6.2 Acknowledgement on Reception of DATA Chunks:
+ *
+ * Section 4.2 of [RFC2581] SHOULD be followed. Specifically,
+ * an acknowledgement SHOULD be generated for at least every
+ * second packet (not every second DATA chunk) received, and
+ * SHOULD be generated within 200 ms of the arrival of any
+ * unacknowledged DATA chunk. ...
+ */
+ if (!asoc->peer.sack_needed) {
+ /* We will need a SACK for the next packet. */
+ asoc->peer.sack_needed = 1;
+ goto out;
+ } else {
+ if (asoc->a_rwnd > asoc->rwnd)
+ asoc->a_rwnd = asoc->rwnd;
+ sack = sctp_make_sack(asoc);
+ if (!sack)
+ goto nomem;
+
+ asoc->peer.sack_needed = 0;
+
+ error = sctp_outq_tail(&asoc->outqueue, sack);
+
+ /* Stop the SACK timer. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_SACK));
+ }
+out:
+ return error;
+nomem:
+ error = -ENOMEM;
+ return error;
+}
+
+/* When the T3-RTX timer expires, it calls this function to create the
+ * relevant state machine event.
+ */
+void sctp_generate_t3_rtx_event(unsigned long peer)
+{
+ int error;
+ struct sctp_transport *transport = (struct sctp_transport *) peer;
+ struct sctp_association *asoc = transport->asoc;
+
+ /* Check whether a task is in the sock. */
+
+ sctp_bh_lock_sock(asoc->base.sk);
+ if (sock_owned_by_user(asoc->base.sk)) {
+ SCTP_DEBUG_PRINTK("%s:Sock is busy.\n", __FUNCTION__);
+
+ /* Try again later. */
+ if (!mod_timer(&transport->T3_rtx_timer, jiffies + (HZ/20)))
+ sctp_transport_hold(transport);
+ goto out_unlock;
+ }
+
+ /* Is this transport really dead and just waiting around for
+ * the timer to let go of the reference?
+ */
+ if (transport->dead)
+ goto out_unlock;
+
+ /* Run through the state machine. */
+ error = sctp_do_sm(SCTP_EVENT_T_TIMEOUT,
+ SCTP_ST_TIMEOUT(SCTP_EVENT_TIMEOUT_T3_RTX),
+ asoc->state,
+ asoc->ep, asoc,
+ transport, GFP_ATOMIC);
+
+ if (error)
+ asoc->base.sk->err = -error;
+
+out_unlock:
+ sctp_bh_unlock_sock(asoc->base.sk);
+ sctp_transport_put(transport);
+}
+
+/* This is a sa interface for producing timeout events. It works
+ * for timeouts which use the association as their parameter.
+ */
+static void sctp_generate_timeout_event(struct sctp_association *asoc,
+ sctp_event_timeout_t timeout_type)
+{
+ int error = 0;
+
+ sctp_bh_lock_sock(asoc->base.sk);
+ if (sock_owned_by_user(asoc->base.sk)) {
+ SCTP_DEBUG_PRINTK("%s:Sock is busy: timer %d\n",
+ __FUNCTION__,
+ timeout_type);
+
+ /* Try again later. */
+ if (!mod_timer(&asoc->timers[timeout_type], jiffies + (HZ/20)))
+ sctp_association_hold(asoc);
+ goto out_unlock;
+ }
+
+ /* Is this association really dead and just waiting around for
+ * the timer to let go of the reference?
+ */
+ if (asoc->base.dead)
+ goto out_unlock;
+
+ /* Run through the state machine. */
+ error = sctp_do_sm(SCTP_EVENT_T_TIMEOUT,
+ SCTP_ST_TIMEOUT(timeout_type),
+ asoc->state, asoc->ep, asoc,
+ (void *)timeout_type, GFP_ATOMIC);
+
+ if (error)
+ asoc->base.sk->err = -error;
+
+out_unlock:
+ sctp_bh_unlock_sock(asoc->base.sk);
+ sctp_association_put(asoc);
+}
+
+static void sctp_generate_t1_cookie_event(unsigned long data)
+{
+ struct sctp_association *asoc = (struct sctp_association *) data;
+ sctp_generate_timeout_event(asoc, SCTP_EVENT_TIMEOUT_T1_COOKIE);
+}
+
+static void sctp_generate_t1_init_event(unsigned long data)
+{
+ struct sctp_association *asoc = (struct sctp_association *) data;
+ sctp_generate_timeout_event(asoc, SCTP_EVENT_TIMEOUT_T1_INIT);
+}
+
+static void sctp_generate_t2_shutdown_event(unsigned long data)
+{
+ struct sctp_association *asoc = (struct sctp_association *) data;
+ sctp_generate_timeout_event(asoc, SCTP_EVENT_TIMEOUT_T2_SHUTDOWN);
+}
+
+static void sctp_generate_t4_rto_event(unsigned long data)
+{
+ struct sctp_association *asoc = (struct sctp_association *) data;
+ sctp_generate_timeout_event(asoc, SCTP_EVENT_TIMEOUT_T4_RTO);
+}
+
+static void sctp_generate_t5_shutdown_guard_event(unsigned long data)
+{
+ struct sctp_association *asoc = (struct sctp_association *)data;
+ sctp_generate_timeout_event(asoc,
+ SCTP_EVENT_TIMEOUT_T5_SHUTDOWN_GUARD);
+
+} /* sctp_generate_t5_shutdown_guard_event() */
+
+static void sctp_generate_autoclose_event(unsigned long data)
+{
+ struct sctp_association *asoc = (struct sctp_association *) data;
+ sctp_generate_timeout_event(asoc, SCTP_EVENT_TIMEOUT_AUTOCLOSE);
+}
+
+/* Generate a heart beat event. If the sock is busy, reschedule. Make
+ * sure that the transport is still valid.
+ */
+void sctp_generate_heartbeat_event(unsigned long data)
+{
+ int error = 0;
+ struct sctp_transport *transport = (struct sctp_transport *) data;
+ struct sctp_association *asoc = transport->asoc;
+
+ sctp_bh_lock_sock(asoc->base.sk);
+ if (sock_owned_by_user(asoc->base.sk)) {
+ SCTP_DEBUG_PRINTK("%s:Sock is busy.\n", __FUNCTION__);
+
+ /* Try again later. */
+ if (!mod_timer(&transport->hb_timer, jiffies + (HZ/20)))
+ sctp_transport_hold(transport);
+ goto out_unlock;
+ }
+
+ /* Is this structure just waiting around for us to actually
+ * get destroyed?
+ */
+ if (transport->dead)
+ goto out_unlock;
+
+ error = sctp_do_sm(SCTP_EVENT_T_TIMEOUT,
+ SCTP_ST_TIMEOUT(SCTP_EVENT_TIMEOUT_HEARTBEAT),
+ asoc->state, asoc->ep, asoc,
+ transport, GFP_ATOMIC);
+
+ if (error)
+ asoc->base.sk->err = -error;
+
+out_unlock:
+ sctp_bh_unlock_sock(asoc->base.sk);
+ sctp_transport_put(transport);
+}
+
+/* Inject a SACK Timeout event into the state machine. */
+static void sctp_generate_sack_event(unsigned long data)
+{
+ struct sctp_association *asoc = (struct sctp_association *) data;
+ sctp_generate_timeout_event(asoc, SCTP_EVENT_TIMEOUT_SACK);
+}
+
+sctp_timer_event_t *sctp_timer_events[SCTP_NUM_TIMEOUT_TYPES] = {
+ NULL,
+ sctp_generate_t1_cookie_event,
+ sctp_generate_t1_init_event,
+ sctp_generate_t2_shutdown_event,
+ NULL,
+ sctp_generate_t4_rto_event,
+ sctp_generate_t5_shutdown_guard_event,
+ sctp_generate_heartbeat_event,
+ sctp_generate_sack_event,
+ sctp_generate_autoclose_event,
+};
+
+
+/* RFC 2960 8.2 Path Failure Detection
+ *
+ * When its peer endpoint is multi-homed, an endpoint should keep a
+ * error counter for each of the destination transport addresses of the
+ * peer endpoint.
+ *
+ * Each time the T3-rtx timer expires on any address, or when a
+ * HEARTBEAT sent to an idle address is not acknowledged within a RTO,
+ * the error counter of that destination address will be incremented.
+ * When the value in the error counter exceeds the protocol parameter
+ * 'Path.Max.Retrans' of that destination address, the endpoint should
+ * mark the destination transport address as inactive, and a
+ * notification SHOULD be sent to the upper layer.
+ *
+ */
+static void sctp_do_8_2_transport_strike(struct sctp_association *asoc,
+ struct sctp_transport *transport)
+{
+ /* The check for association's overall error counter exceeding the
+ * threshold is done in the state function.
+ */
+ asoc->overall_error_count++;
+
+ if (transport->active &&
+ (transport->error_count++ >= transport->max_retrans)) {
+ SCTP_DEBUG_PRINTK("transport_strike: transport "
+ "IP:%d.%d.%d.%d failed.\n",
+ NIPQUAD(transport->ipaddr.v4.sin_addr));
+ sctp_assoc_control_transport(asoc, transport,
+ SCTP_TRANSPORT_DOWN,
+ SCTP_FAILED_THRESHOLD);
+ }
+
+ /* E2) For the destination address for which the timer
+ * expires, set RTO <- RTO * 2 ("back off the timer"). The
+ * maximum value discussed in rule C7 above (RTO.max) may be
+ * used to provide an upper bound to this doubling operation.
+ */
+ transport->rto = min((transport->rto * 2), transport->asoc->rto_max);
+}
+
+/* Worker routine to handle INIT command failure. */
+static void sctp_cmd_init_failed(sctp_cmd_seq_t *commands,
+ struct sctp_association *asoc,
+ unsigned error)
+{
+ struct sctp_ulpevent *event;
+
+ event = sctp_ulpevent_make_assoc_change(asoc,0, SCTP_CANT_STR_ASSOC,
+ (__u16)error, 0, 0,
+ GFP_ATOMIC);
+
+ if (event)
+ sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP,
+ SCTP_ULPEVENT(event));
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_CLOSED));
+
+ /* SEND_FAILED sent later when cleaning up the association. */
+ asoc->outqueue.error = error;
+ sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB, SCTP_NULL());
+}
+
+/* Worker routine to handle SCTP_CMD_ASSOC_FAILED. */
+static void sctp_cmd_assoc_failed(sctp_cmd_seq_t *commands,
+ struct sctp_association *asoc,
+ sctp_event_t event_type,
+ sctp_subtype_t subtype,
+ struct sctp_chunk *chunk,
+ unsigned error)
+{
+ struct sctp_ulpevent *event;
+
+ /* Cancel any partial delivery in progress. */
+ sctp_ulpq_abort_pd(&asoc->ulpq, GFP_ATOMIC);
+
+ event = sctp_ulpevent_make_assoc_change(asoc, 0, SCTP_COMM_LOST,
+ (__u16)error, 0, 0,
+ GFP_ATOMIC);
+ if (event)
+ sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP,
+ SCTP_ULPEVENT(event));
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_CLOSED));
+
+ /* Set sk->err to ECONNRESET on a 1-1 style socket. */
+ if (!sctp_style(asoc->base.sk, UDP))
+ asoc->base.sk->err = ECONNRESET;
+
+ /* SEND_FAILED sent later when cleaning up the association. */
+ asoc->outqueue.error = error;
+ sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB, SCTP_NULL());
+}
+
+/* Process an init chunk (may be real INIT/INIT-ACK or an embedded INIT
+ * inside the cookie. In reality, this is only used for INIT-ACK processing
+ * since all other cases use "temporary" associations and can do all
+ * their work in statefuns directly.
+ */
+static int sctp_cmd_process_init(sctp_cmd_seq_t *commands,
+ struct sctp_association *asoc,
+ struct sctp_chunk *chunk,
+ sctp_init_chunk_t *peer_init, int gfp)
+{
+ int error;
+
+ /* We only process the init as a sideeffect in a single
+ * case. This is when we process the INIT-ACK. If we
+ * fail during INIT processing (due to malloc problems),
+ * just return the error and stop processing the stack.
+ */
+ if (!sctp_process_init(asoc, chunk->chunk_hdr->type,
+ sctp_source(chunk), peer_init, gfp))
+ error = -ENOMEM;
+ else
+ error = 0;
+
+ return error;
+}
+
+/* Helper function to break out starting up of heartbeat timers. */
+static void sctp_cmd_hb_timers_start(sctp_cmd_seq_t *cmds,
+ struct sctp_association *asoc)
+{
+ struct sctp_transport *t;
+ struct list_head *pos;
+
+ /* Start a heartbeat timer for each transport on the association.
+ * hold a reference on the transport to make sure none of
+ * the needed data structures go away.
+ */
+ list_for_each(pos, &asoc->peer.transport_addr_list) {
+ t = list_entry(pos, struct sctp_transport, transports);
+
+ if (!mod_timer(&t->hb_timer, sctp_transport_timeout(t)))
+ sctp_transport_hold(t);
+ }
+}
+
+static void sctp_cmd_hb_timers_stop(sctp_cmd_seq_t *cmds,
+ struct sctp_association *asoc)
+{
+ struct sctp_transport *t;
+ struct list_head *pos;
+
+ /* Stop all heartbeat timers. */
+
+ list_for_each(pos, &asoc->peer.transport_addr_list) {
+ t = list_entry(pos, struct sctp_transport, transports);
+ if (del_timer(&t->hb_timer))
+ sctp_transport_put(t);
+ }
+}
+
+/* Helper function to stop any pending T3-RTX timers */
+static void sctp_cmd_t3_rtx_timers_stop(sctp_cmd_seq_t *cmds,
+ struct sctp_association *asoc)
+{
+ struct sctp_transport *t;
+ struct list_head *pos;
+
+ list_for_each(pos, &asoc->peer.transport_addr_list) {
+ t = list_entry(pos, struct sctp_transport, transports);
+ if (timer_pending(&t->T3_rtx_timer) &&
+ del_timer(&t->T3_rtx_timer)) {
+ sctp_transport_put(t);
+ }
+ }
+}
+
+
+/* Helper function to update the heartbeat timer. */
+static void sctp_cmd_hb_timer_update(sctp_cmd_seq_t *cmds,
+ struct sctp_association *asoc,
+ struct sctp_transport *t)
+{
+ /* Update the heartbeat timer. */
+ if (!mod_timer(&t->hb_timer, sctp_transport_timeout(t)))
+ sctp_transport_hold(t);
+}
+
+/* Helper function to handle the reception of an HEARTBEAT ACK. */
+static void sctp_cmd_transport_on(sctp_cmd_seq_t *cmds,
+ struct sctp_association *asoc,
+ struct sctp_transport *t,
+ struct sctp_chunk *chunk)
+{
+ sctp_sender_hb_info_t *hbinfo;
+
+ /* 8.3 Upon the receipt of the HEARTBEAT ACK, the sender of the
+ * HEARTBEAT should clear the error counter of the destination
+ * transport address to which the HEARTBEAT was sent.
+ * The association's overall error count is also cleared.
+ */
+ t->error_count = 0;
+ t->asoc->overall_error_count = 0;
+
+ /* Mark the destination transport address as active if it is not so
+ * marked.
+ */
+ if (!t->active)
+ sctp_assoc_control_transport(asoc, t, SCTP_TRANSPORT_UP,
+ SCTP_HEARTBEAT_SUCCESS);
+
+ /* The receiver of the HEARTBEAT ACK should also perform an
+ * RTT measurement for that destination transport address
+ * using the time value carried in the HEARTBEAT ACK chunk.
+ */
+ hbinfo = (sctp_sender_hb_info_t *) chunk->skb->data;
+ sctp_transport_update_rto(t, (jiffies - hbinfo->sent_at));
+}
+
+/* Helper function to do a transport reset at the expiry of the hearbeat
+ * timer.
+ */
+static void sctp_cmd_transport_reset(sctp_cmd_seq_t *cmds,
+ struct sctp_association *asoc,
+ struct sctp_transport *t)
+{
+ sctp_transport_lower_cwnd(t, SCTP_LOWER_CWND_INACTIVE);
+
+ /* Mark one strike against a transport. */
+ sctp_do_8_2_transport_strike(asoc, t);
+}
+
+/* Helper function to process the process SACK command. */
+static int sctp_cmd_process_sack(sctp_cmd_seq_t *cmds,
+ struct sctp_association *asoc,
+ struct sctp_sackhdr *sackh)
+{
+ int err;
+
+ if (sctp_outq_sack(&asoc->outqueue, sackh)) {
+ /* There are no more TSNs awaiting SACK. */
+ err = sctp_do_sm(SCTP_EVENT_T_OTHER,
+ SCTP_ST_OTHER(SCTP_EVENT_NO_PENDING_TSN),
+ asoc->state, asoc->ep, asoc, NULL,
+ GFP_ATOMIC);
+ } else {
+ /* Windows may have opened, so we need
+ * to check if we have DATA to transmit
+ */
+ err = sctp_outq_flush(&asoc->outqueue, 0);
+ }
+
+ return err;
+}
+
+/* Helper function to set the timeout value for T2-SHUTDOWN timer and to set
+ * the transport for a shutdown chunk.
+ */
+static void sctp_cmd_setup_t2(sctp_cmd_seq_t *cmds,
+ struct sctp_association *asoc,
+ struct sctp_chunk *chunk)
+{
+ struct sctp_transport *t;
+
+ t = sctp_assoc_choose_shutdown_transport(asoc);
+ asoc->shutdown_last_sent_to = t;
+ asoc->timeouts[SCTP_EVENT_TIMEOUT_T2_SHUTDOWN] = t->rto;
+ chunk->transport = t;
+}
+
+/* Helper function to change the state of an association. */
+static void sctp_cmd_new_state(sctp_cmd_seq_t *cmds,
+ struct sctp_association *asoc,
+ sctp_state_t state)
+{
+ struct sock *sk = asoc->base.sk;
+
+ asoc->state = state;
+
+ if (sctp_style(sk, TCP)) {
+ /* Change the sk->state of a TCP-style socket that has
+ * sucessfully completed a connect() call.
+ */
+ if (sctp_state(asoc, ESTABLISHED) && sctp_sstate(sk, CLOSED))
+ sk->state = SCTP_SS_ESTABLISHED;
+
+ /* Set the RCV_SHUTDOWN flag when a SHUTDOWN is received. */
+ if (sctp_state(asoc, SHUTDOWN_RECEIVED) &&
+ sctp_sstate(sk, ESTABLISHED))
+ sk->shutdown |= RCV_SHUTDOWN;
+ }
+
+ if (sctp_state(asoc, ESTABLISHED) ||
+ sctp_state(asoc, CLOSED) ||
+ sctp_state(asoc, SHUTDOWN_RECEIVED)) {
+ /* Wake up any processes waiting in the asoc's wait queue in
+ * sctp_wait_for_connect() or sctp_wait_for_sndbuf().
+ */
+ if (waitqueue_active(&asoc->wait))
+ wake_up_interruptible(&asoc->wait);
+
+ /* Wake up any processes waiting in the sk's sleep queue of
+ * a TCP-style or UDP-style peeled-off socket in
+ * sctp_wait_for_accept() or sctp_wait_for_packet().
+ * For a UDP-style socket, the waiters are woken up by the
+ * notifications.
+ */
+ if (!sctp_style(sk, UDP))
+ sk->state_change(sk);
+ }
+}
+
+/* Helper function to delete an association. */
+static void sctp_cmd_delete_tcb(sctp_cmd_seq_t *cmds,
+ struct sctp_association *asoc)
+{
+ struct sock *sk = asoc->base.sk;
+
+ /* If it is a non-temporary association belonging to a TCP-style
+ * listening socket that is not closed, do not free it so that accept()
+ * can pick it up later.
+ */
+ if (sctp_style(sk, TCP) && sctp_sstate(sk, LISTENING) &&
+ (!asoc->temp) && (sk->shutdown != SHUTDOWN_MASK))
+ return;
+
+ sctp_unhash_established(asoc);
+ sctp_association_free(asoc);
+}
+
+/*
+ * ADDIP Section 4.1 ASCONF Chunk Procedures
+ * A4) Start a T-4 RTO timer, using the RTO value of the selected
+ * destination address (we use active path instead of primary path just
+ * because primary path may be inactive.
+ */
+static void sctp_cmd_setup_t4(sctp_cmd_seq_t *cmds,
+ struct sctp_association *asoc,
+ struct sctp_chunk *chunk)
+{
+ struct sctp_transport *t;
+
+ t = asoc->peer.active_path;
+ asoc->timeouts[SCTP_EVENT_TIMEOUT_T4_RTO] = t->rto;
+ chunk->transport = t;
+}
+
+/* Process an incoming Operation Error Chunk. */
+static void sctp_cmd_process_operr(sctp_cmd_seq_t *cmds,
+ struct sctp_association *asoc,
+ struct sctp_chunk *chunk)
+{
+ struct sctp_operr_chunk *operr_chunk;
+ struct sctp_errhdr *err_hdr;
+
+ operr_chunk = (struct sctp_operr_chunk *)chunk->chunk_hdr;
+ err_hdr = &operr_chunk->err_hdr;
+
+ switch (err_hdr->cause) {
+ case SCTP_ERROR_UNKNOWN_CHUNK:
+ {
+ struct sctp_chunkhdr *unk_chunk_hdr;
+
+ unk_chunk_hdr = (struct sctp_chunkhdr *)err_hdr->variable;
+ switch (unk_chunk_hdr->type) {
+ /* ADDIP 4.1 A9) If the peer responds to an ASCONF with an
+ * ERROR chunk reporting that it did not recognized the ASCONF
+ * chunk type, the sender of the ASCONF MUST NOT send any
+ * further ASCONF chunks and MUST stop its T-4 timer.
+ */
+ case SCTP_CID_ASCONF:
+ asoc->peer.asconf_capable = 0;
+ sctp_add_cmd_sf(cmds, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T4_RTO));
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+/* Process variable FWDTSN chunk information. */
+static void sctp_cmd_process_fwdtsn(struct sctp_ulpq *ulpq,
+ struct sctp_chunk *chunk)
+{
+ struct sctp_fwdtsn_skip *skip;
+ /* Walk through all the skipped SSNs */
+ sctp_walk_fwdtsn(skip, chunk) {
+ sctp_ulpq_skip(ulpq, ntohs(skip->stream), ntohs(skip->ssn));
+ }
+
+ return;
+}
+
+/* Helper function to remove the association non-primary peer
+ * transports.
+ */
+static void sctp_cmd_del_non_primary(struct sctp_association *asoc)
+{
+ struct sctp_transport *t;
+ struct list_head *pos;
+ struct list_head *temp;
+
+ list_for_each_safe(pos, temp, &asoc->peer.transport_addr_list) {
+ t = list_entry(pos, struct sctp_transport, transports);
+ if (!sctp_cmp_addr_exact(&t->ipaddr,
+ &asoc->peer.primary_addr)) {
+ sctp_assoc_del_peer(asoc, &t->ipaddr);
+ }
+ }
+
+ return;
+}
+
+/* These three macros allow us to pull the debugging code out of the
+ * main flow of sctp_do_sm() to keep attention focused on the real
+ * functionality there.
+ */
+#define DEBUG_PRE \
+ SCTP_DEBUG_PRINTK("sctp_do_sm prefn: " \
+ "ep %p, %s, %s, asoc %p[%s], %s\n", \
+ ep, sctp_evttype_tbl[event_type], \
+ (*debug_fn)(subtype), asoc, \
+ sctp_state_tbl[state], state_fn->name)
+
+#define DEBUG_POST \
+ SCTP_DEBUG_PRINTK("sctp_do_sm postfn: " \
+ "asoc %p, status: %s\n", \
+ asoc, sctp_status_tbl[status])
+
+#define DEBUG_POST_SFX \
+ SCTP_DEBUG_PRINTK("sctp_do_sm post sfx: error %d, asoc %p[%s]\n", \
+ error, asoc, \
+ sctp_state_tbl[(asoc && sctp_id2assoc(ep->base.sk, \
+ sctp_assoc2id(asoc)))?asoc->state:SCTP_STATE_CLOSED])
+
+/*
+ * This is the master state machine processing function.
+ *
+ * If you want to understand all of lksctp, this is a
+ * good place to start.
+ */
+int sctp_do_sm(sctp_event_t event_type, sctp_subtype_t subtype,
+ sctp_state_t state,
+ struct sctp_endpoint *ep,
+ struct sctp_association *asoc,
+ void *event_arg,
+ int gfp)
+{
+ sctp_cmd_seq_t commands;
+ const sctp_sm_table_entry_t *state_fn;
+ sctp_disposition_t status;
+ int error = 0;
+ typedef const char *(printfn_t)(sctp_subtype_t);
+
+ static printfn_t *table[] = {
+ NULL, sctp_cname, sctp_tname, sctp_oname, sctp_pname,
+ };
+ printfn_t *debug_fn __attribute__ ((unused)) = table[event_type];
+
+ /* Look up the state function, run it, and then process the
+ * side effects. These three steps are the heart of lksctp.
+ */
+ state_fn = sctp_sm_lookup_event(event_type, state, subtype);
+
+ sctp_init_cmd_seq(&commands);
+
+ DEBUG_PRE;
+ status = (*state_fn->fn)(ep, asoc, subtype, event_arg, &commands);
+ DEBUG_POST;
+
+ error = sctp_side_effects(event_type, subtype, state,
+ ep, asoc, event_arg, status,
+ &commands, gfp);
+ DEBUG_POST_SFX;
+
+ return error;
+}
+
+#undef DEBUG_PRE
+#undef DEBUG_POST
+
+/*****************************************************************
+ * This the master state function side effect processing function.
+ *****************************************************************/
+static int sctp_side_effects(sctp_event_t event_type, sctp_subtype_t subtype,
+ sctp_state_t state,
+ struct sctp_endpoint *ep,
+ struct sctp_association *asoc,
+ void *event_arg,
+ sctp_disposition_t status,
+ sctp_cmd_seq_t *commands,
+ int gfp)
+{
+ int error;
+
+ /* FIXME - Most of the dispositions left today would be categorized
+ * as "exceptional" dispositions. For those dispositions, it
+ * may not be proper to run through any of the commands at all.
+ * For example, the command interpreter might be run only with
+ * disposition SCTP_DISPOSITION_CONSUME.
+ */
+ if (0 != (error = sctp_cmd_interpreter(event_type, subtype, state,
+ ep, asoc,
+ event_arg, status,
+ commands, gfp)))
+ goto bail;
+
+ switch (status) {
+ case SCTP_DISPOSITION_DISCARD:
+ SCTP_DEBUG_PRINTK("Ignored sctp protocol event - state %d, "
+ "event_type %d, event_id %d\n",
+ state, event_type, subtype.chunk);
+ break;
+
+ case SCTP_DISPOSITION_NOMEM:
+ /* We ran out of memory, so we need to discard this
+ * packet.
+ */
+ /* BUG--we should now recover some memory, probably by
+ * reneging...
+ */
+ error = -ENOMEM;
+ break;
+
+ case SCTP_DISPOSITION_DELETE_TCB:
+ /* This should now be a command. */
+ break;
+
+ case SCTP_DISPOSITION_CONSUME:
+ case SCTP_DISPOSITION_ABORT:
+ /*
+ * We should no longer have much work to do here as the
+ * real work has been done as explicit commands above.
+ */
+ break;
+
+ case SCTP_DISPOSITION_VIOLATION:
+ printk(KERN_ERR "sctp protocol violation state %d "
+ "chunkid %d\n", state, subtype.chunk);
+ break;
+
+ case SCTP_DISPOSITION_NOT_IMPL:
+ printk(KERN_WARNING "sctp unimplemented feature in state %d, "
+ "event_type %d, event_id %d\n",
+ state, event_type, subtype.chunk);
+ break;
+
+ case SCTP_DISPOSITION_BUG:
+ printk(KERN_ERR "sctp bug in state %d, "
+ "event_type %d, event_id %d\n",
+ state, event_type, subtype.chunk);
+ BUG();
+ break;
+
+ default:
+ printk(KERN_ERR "sctp impossible disposition %d "
+ "in state %d, event_type %d, event_id %d\n",
+ status, state, event_type, subtype.chunk);
+ BUG();
+ break;
+ };
+
+bail:
+ return error;
+}
+
+/********************************************************************
+ * 2nd Level Abstractions
+ ********************************************************************/
+
+/* This is the side-effect interpreter. */
+static int sctp_cmd_interpreter(sctp_event_t event_type,
+ sctp_subtype_t subtype,
+ sctp_state_t state,
+ struct sctp_endpoint *ep,
+ struct sctp_association *asoc,
+ void *event_arg,
+ sctp_disposition_t status,
+ sctp_cmd_seq_t *commands,
+ int gfp)
+{
+ int error = 0;
+ int force;
+ sctp_cmd_t *cmd;
+ struct sctp_chunk *new_obj;
+ struct sctp_chunk *chunk = NULL;
+ struct sctp_packet *packet;
+ struct list_head *pos;
+ struct timer_list *timer;
+ unsigned long timeout;
+ struct sctp_transport *t;
+ struct sctp_sackhdr sackh;
+ int local_cork = 0;
+
+ if (SCTP_EVENT_T_TIMEOUT != event_type)
+ chunk = (struct sctp_chunk *) event_arg;
+
+ /* Note: This whole file is a huge candidate for rework.
+ * For example, each command could either have its own handler, so
+ * the loop would look like:
+ * while (cmds)
+ * cmd->handle(x, y, z)
+ * --jgrimm
+ */
+ while (NULL != (cmd = sctp_next_cmd(commands))) {
+ switch (cmd->verb) {
+ case SCTP_CMD_NOP:
+ /* Do nothing. */
+ break;
+
+ case SCTP_CMD_NEW_ASOC:
+ /* Register a new association. */
+ if (local_cork) {
+ sctp_outq_uncork(&asoc->outqueue);
+ local_cork = 0;
+ }
+ asoc = cmd->obj.ptr;
+ /* Register with the endpoint. */
+ sctp_endpoint_add_asoc(ep, asoc);
+ sctp_hash_established(asoc);
+ break;
+
+ case SCTP_CMD_UPDATE_ASSOC:
+ sctp_assoc_update(asoc, cmd->obj.ptr);
+ break;
+
+ case SCTP_CMD_PURGE_OUTQUEUE:
+ sctp_outq_teardown(&asoc->outqueue);
+ break;
+
+ case SCTP_CMD_DELETE_TCB:
+ if (local_cork) {
+ sctp_outq_uncork(&asoc->outqueue);
+ local_cork = 0;
+ }
+ /* Delete the current association. */
+ sctp_cmd_delete_tcb(commands, asoc);
+ asoc = NULL;
+ break;
+
+ case SCTP_CMD_NEW_STATE:
+ /* Enter a new state. */
+ sctp_cmd_new_state(commands, asoc, cmd->obj.state);
+ break;
+
+ case SCTP_CMD_REPORT_TSN:
+ /* Record the arrival of a TSN. */
+ sctp_tsnmap_mark(&asoc->peer.tsn_map, cmd->obj.u32);
+ break;
+
+ case SCTP_CMD_REPORT_FWDTSN:
+ /* Move the Cumulattive TSN Ack ahead. */
+ sctp_tsnmap_skip(&asoc->peer.tsn_map, cmd->obj.u32);
+
+ /* Abort any in progress partial delivery. */
+ sctp_ulpq_abort_pd(&asoc->ulpq, GFP_ATOMIC);
+ break;
+
+ case SCTP_CMD_PROCESS_FWDTSN:
+ sctp_cmd_process_fwdtsn(&asoc->ulpq, cmd->obj.ptr);
+ break;
+
+ case SCTP_CMD_GEN_SACK:
+ /* Generate a Selective ACK.
+ * The argument tells us whether to just count
+ * the packet and MAYBE generate a SACK, or
+ * force a SACK out.
+ */
+ force = cmd->obj.i32;
+ error = sctp_gen_sack(asoc, force, commands);
+ break;
+
+ case SCTP_CMD_PROCESS_SACK:
+ /* Process an inbound SACK. */
+ error = sctp_cmd_process_sack(commands, asoc,
+ cmd->obj.ptr);
+ break;
+
+ case SCTP_CMD_GEN_INIT_ACK:
+ /* Generate an INIT ACK chunk. */
+ new_obj = sctp_make_init_ack(asoc, chunk, GFP_ATOMIC,
+ 0);
+ if (!new_obj)
+ goto nomem;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(new_obj));
+ break;
+
+ case SCTP_CMD_PEER_INIT:
+ /* Process a unified INIT from the peer.
+ * Note: Only used during INIT-ACK processing. If
+ * there is an error just return to the outter
+ * layer which will bail.
+ */
+ error = sctp_cmd_process_init(commands, asoc, chunk,
+ cmd->obj.ptr, gfp);
+ break;
+
+ case SCTP_CMD_GEN_COOKIE_ECHO:
+ /* Generate a COOKIE ECHO chunk. */
+ new_obj = sctp_make_cookie_echo(asoc, chunk);
+ if (!new_obj) {
+ if (cmd->obj.ptr)
+ sctp_chunk_free(cmd->obj.ptr);
+ goto nomem;
+ }
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(new_obj));
+
+ /* If there is an ERROR chunk to be sent along with
+ * the COOKIE_ECHO, send it, too.
+ */
+ if (cmd->obj.ptr)
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(cmd->obj.ptr));
+
+ /* FIXME - Eventually come up with a cleaner way to
+ * enabling COOKIE-ECHO + DATA bundling during
+ * multihoming stale cookie scenarios, the following
+ * command plays with asoc->peer.retran_path to
+ * avoid the problem of sending the COOKIE-ECHO and
+ * DATA in different paths, which could result
+ * in the association being ABORTed if the DATA chunk
+ * is processed first by the server. Checking the
+ * init error counter simply causes this command
+ * to be executed only during failed attempts of
+ * association establishment.
+ */
+ if ((asoc->peer.retran_path !=
+ asoc->peer.primary_path) &&
+ (asoc->counters[SCTP_COUNTER_INIT_ERROR] > 0)) {
+ sctp_add_cmd_sf(commands,
+ SCTP_CMD_FORCE_PRIM_RETRAN,
+ SCTP_NULL());
+ }
+
+ break;
+
+ case SCTP_CMD_GEN_SHUTDOWN:
+ /* Generate SHUTDOWN when in SHUTDOWN_SENT state.
+ * Reset error counts.
+ */
+ asoc->overall_error_count = 0;
+
+ /* Generate a SHUTDOWN chunk. */
+ new_obj = sctp_make_shutdown(asoc, chunk);
+ if (!new_obj)
+ goto nomem;
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(new_obj));
+ break;
+
+ case SCTP_CMD_CHUNK_ULP:
+ /* Send a chunk to the sockets layer. */
+ SCTP_DEBUG_PRINTK("sm_sideff: %s %p, %s %p.\n",
+ "chunk_up:", cmd->obj.ptr,
+ "ulpq:", &asoc->ulpq);
+ sctp_ulpq_tail_data(&asoc->ulpq, cmd->obj.ptr,
+ GFP_ATOMIC);
+ break;
+
+ case SCTP_CMD_EVENT_ULP:
+ /* Send a notification to the sockets layer. */
+ SCTP_DEBUG_PRINTK("sm_sideff: %s %p, %s %p.\n",
+ "event_up:",cmd->obj.ptr,
+ "ulpq:",&asoc->ulpq);
+ sctp_ulpq_tail_event(&asoc->ulpq, cmd->obj.ptr);
+ break;
+
+ case SCTP_CMD_REPLY:
+ /* If an caller has not already corked, do cork. */
+ if (!asoc->outqueue.cork) {
+ sctp_outq_cork(&asoc->outqueue);
+ local_cork = 1;
+ }
+ /* Send a chunk to our peer. */
+ error = sctp_outq_tail(&asoc->outqueue, cmd->obj.ptr);
+ break;
+
+ case SCTP_CMD_SEND_PKT:
+ /* Send a full packet to our peer. */
+ packet = cmd->obj.ptr;
+ sctp_packet_transmit(packet);
+ sctp_ootb_pkt_free(packet);
+ break;
+
+ case SCTP_CMD_RETRAN:
+ /* Mark a transport for retransmission. */
+ sctp_retransmit(&asoc->outqueue, cmd->obj.transport,
+ SCTP_RTXR_T3_RTX);
+ break;
+
+ case SCTP_CMD_TRANSMIT:
+ /* Kick start transmission. */
+ error = sctp_outq_uncork(&asoc->outqueue);
+ local_cork = 0;
+ break;
+
+ case SCTP_CMD_ECN_CE:
+ /* Do delayed CE processing. */
+ sctp_do_ecn_ce_work(asoc, cmd->obj.u32);
+ break;
+
+ case SCTP_CMD_ECN_ECNE:
+ /* Do delayed ECNE processing. */
+ new_obj = sctp_do_ecn_ecne_work(asoc, cmd->obj.u32,
+ chunk);
+ if (new_obj)
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(new_obj));
+ break;
+
+ case SCTP_CMD_ECN_CWR:
+ /* Do delayed CWR processing. */
+ sctp_do_ecn_cwr_work(asoc, cmd->obj.u32);
+ break;
+
+ case SCTP_CMD_SETUP_T2:
+ sctp_cmd_setup_t2(commands, asoc, cmd->obj.ptr);
+ break;
+
+ case SCTP_CMD_TIMER_START:
+ timer = &asoc->timers[cmd->obj.to];
+ timeout = asoc->timeouts[cmd->obj.to];
+ if (!timeout)
+ BUG();
+
+ timer->expires = jiffies + timeout;
+ sctp_association_hold(asoc);
+ add_timer(timer);
+ break;
+
+ case SCTP_CMD_TIMER_RESTART:
+ timer = &asoc->timers[cmd->obj.to];
+ timeout = asoc->timeouts[cmd->obj.to];
+ if (!mod_timer(timer, jiffies + timeout))
+ sctp_association_hold(asoc);
+ break;
+
+ case SCTP_CMD_TIMER_STOP:
+ timer = &asoc->timers[cmd->obj.to];
+ if (timer_pending(timer) && del_timer(timer))
+ sctp_association_put(asoc);
+ break;
+
+ case SCTP_CMD_INIT_RESTART:
+ /* Do the needed accounting and updates
+ * associated with restarting an initialization
+ * timer.
+ */
+ asoc->counters[SCTP_COUNTER_INIT_ERROR]++;
+ asoc->timeouts[cmd->obj.to] *= 2;
+ if (asoc->timeouts[cmd->obj.to] >
+ asoc->max_init_timeo) {
+ asoc->timeouts[cmd->obj.to] =
+ asoc->max_init_timeo;
+ }
+
+ /* If we've sent any data bundled with
+ * COOKIE-ECHO we need to resend.
+ */
+ list_for_each(pos, &asoc->peer.transport_addr_list) {
+ t = list_entry(pos, struct sctp_transport,
+ transports);
+ sctp_retransmit_mark(&asoc->outqueue, t, 0);
+ }
+
+ sctp_add_cmd_sf(commands,
+ SCTP_CMD_TIMER_RESTART,
+ SCTP_TO(cmd->obj.to));
+ break;
+
+ case SCTP_CMD_INIT_FAILED:
+ sctp_cmd_init_failed(commands, asoc, cmd->obj.u32);
+ break;
+
+ case SCTP_CMD_ASSOC_FAILED:
+ sctp_cmd_assoc_failed(commands, asoc, event_type,
+ subtype, chunk, cmd->obj.u32);
+ break;
+
+ case SCTP_CMD_COUNTER_INC:
+ asoc->counters[cmd->obj.counter]++;
+ break;
+
+ case SCTP_CMD_COUNTER_RESET:
+ asoc->counters[cmd->obj.counter] = 0;
+ break;
+
+ case SCTP_CMD_REPORT_DUP:
+ sctp_tsnmap_mark_dup(&asoc->peer.tsn_map,
+ cmd->obj.u32);
+ break;
+
+ case SCTP_CMD_REPORT_BAD_TAG:
+ SCTP_DEBUG_PRINTK("vtag mismatch!\n");
+ break;
+
+ case SCTP_CMD_STRIKE:
+ /* Mark one strike against a transport. */
+ sctp_do_8_2_transport_strike(asoc, cmd->obj.transport);
+ break;
+
+ case SCTP_CMD_TRANSPORT_RESET:
+ t = cmd->obj.transport;
+ sctp_cmd_transport_reset(commands, asoc, t);
+ break;
+
+ case SCTP_CMD_TRANSPORT_ON:
+ t = cmd->obj.transport;
+ sctp_cmd_transport_on(commands, asoc, t, chunk);
+ break;
+
+ case SCTP_CMD_HB_TIMERS_START:
+ sctp_cmd_hb_timers_start(commands, asoc);
+ break;
+
+ case SCTP_CMD_HB_TIMER_UPDATE:
+ t = cmd->obj.transport;
+ sctp_cmd_hb_timer_update(commands, asoc, t);
+ break;
+
+ case SCTP_CMD_HB_TIMERS_STOP:
+ sctp_cmd_hb_timers_stop(commands, asoc);
+ break;
+
+ case SCTP_CMD_REPORT_ERROR:
+ error = cmd->obj.error;
+ break;
+
+ case SCTP_CMD_PROCESS_CTSN:
+ /* Dummy up a SACK for processing. */
+ sackh.cum_tsn_ack = cmd->obj.u32;
+ sackh.a_rwnd = 0;
+ sackh.num_gap_ack_blocks = 0;
+ sackh.num_dup_tsns = 0;
+ sctp_add_cmd_sf(commands, SCTP_CMD_PROCESS_SACK,
+ SCTP_SACKH(&sackh));
+ break;
+
+ case SCTP_CMD_DISCARD_PACKET:
+ /* We need to discard the whole packet. */
+ chunk->pdiscard = 1;
+ break;
+
+ case SCTP_CMD_RTO_PENDING:
+ t = cmd->obj.transport;
+ t->rto_pending = 1;
+ break;
+
+ case SCTP_CMD_PART_DELIVER:
+ sctp_ulpq_partial_delivery(&asoc->ulpq, cmd->obj.ptr,
+ GFP_ATOMIC);
+ break;
+
+ case SCTP_CMD_RENEGE:
+ sctp_ulpq_renege(&asoc->ulpq, cmd->obj.ptr,
+ GFP_ATOMIC);
+ break;
+
+ case SCTP_CMD_SETUP_T4:
+ sctp_cmd_setup_t4(commands, asoc, cmd->obj.ptr);
+ break;
+
+ case SCTP_CMD_PROCESS_OPERR:
+ sctp_cmd_process_operr(commands, asoc, chunk);
+ break;
+ case SCTP_CMD_CLEAR_INIT_TAG:
+ asoc->peer.i.init_tag = 0;
+ break;
+ case SCTP_CMD_DEL_NON_PRIMARY:
+ sctp_cmd_del_non_primary(asoc);
+ break;
+ case SCTP_CMD_T3_RTX_TIMERS_STOP:
+ sctp_cmd_t3_rtx_timers_stop(commands, asoc);
+ break;
+ case SCTP_CMD_FORCE_PRIM_RETRAN:
+ t = asoc->peer.retran_path;
+ asoc->peer.retran_path = asoc->peer.primary_path;
+ error = sctp_outq_uncork(&asoc->outqueue);
+ local_cork = 0;
+ asoc->peer.retran_path = t;
+ break;
+ default:
+ printk(KERN_WARNING "Impossible command: %u, %p\n",
+ cmd->verb, cmd->obj.ptr);
+ break;
+ };
+ if (error)
+ break;
+ }
+
+out:
+ if (local_cork)
+ sctp_outq_uncork(&asoc->outqueue);
+ return error;
+nomem:
+ error = -ENOMEM;
+ goto out;
+}
+
diff --git a/uClinux-2.4.31-uc0/net/sctp/sm_statefuns.c b/uClinux-2.4.31-uc0/net/sctp/sm_statefuns.c
new file mode 100644
index 0000000..c581808
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/sm_statefuns.c
@@ -0,0 +1,5240 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2001, 2004
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001-2002 Intel Corp.
+ * Copyright (c) 2002 Nokia Corp.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * This is part of the SCTP Linux Kernel Reference Implementation.
+ *
+ * These are the state functions for the state machine.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Mathew Kotowsky <kotowsky@sctp.org>
+ * Sridhar Samudrala <samudrala@us.ibm.com>
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Hui Huang <hui.huang@nokia.com>
+ * Dajiang Zhang <dajiang.zhang@nokia.com>
+ * Daisy Chang <daisyc@us.ibm.com>
+ * Ardelle Fan <ardelle.fan@intel.com>
+ * Ryan Layer <rmlayer@us.ibm.com>
+ * Kevin Gao <kevin.gao@intel.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <net/sock.h>
+#include <net/inet_ecn.h>
+#include <linux/skbuff.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+#include <net/sctp/structs.h>
+
+static struct sctp_packet *sctp_abort_pkt_new(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ struct sctp_chunk *chunk,
+ const void *payload,
+ size_t paylen);
+static int sctp_eat_data(const struct sctp_association *asoc,
+ struct sctp_chunk *chunk,
+ sctp_cmd_seq_t *commands);
+static struct sctp_packet *sctp_ootb_pkt_new(const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk);
+static void sctp_send_stale_cookie_err(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk,
+ sctp_cmd_seq_t *commands,
+ struct sctp_chunk *err_chunk);
+static sctp_disposition_t sctp_sf_do_5_2_6_stale(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands);
+static sctp_disposition_t sctp_sf_shut_8_4_5(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands);
+static struct sctp_sackhdr *sctp_sm_pull_sack(struct sctp_chunk *chunk);
+
+
+/* Small helper function that checks if the chunk length
+ * is of the appropriate length. The 'required_length' argument
+ * is set to be the size of a specific chunk we are testing.
+ * Return Values: 1 = Valid length
+ * 0 = Invalid length
+ *
+ */
+static inline int
+sctp_chunk_length_valid(struct sctp_chunk *chunk,
+ __u16 required_length)
+{
+ __u16 chunk_length = ntohs(chunk->chunk_hdr->length);
+
+ if (unlikely(chunk_length < required_length))
+ return 0;
+
+ return 1;
+}
+
+/**********************************************************
+ * These are the state functions for handling chunk events.
+ **********************************************************/
+
+/*
+ * Process the final SHUTDOWN COMPLETE.
+ *
+ * Section: 4 (C) (diagram), 9.2
+ * Upon reception of the SHUTDOWN COMPLETE chunk the endpoint will verify
+ * that it is in SHUTDOWN-ACK-SENT state, if it is not the chunk should be
+ * discarded. If the endpoint is in the SHUTDOWN-ACK-SENT state the endpoint
+ * should stop the T2-shutdown timer and remove all knowledge of the
+ * association (and thus the association enters the CLOSED state).
+ *
+ * Verification Tag: 8.5.1(C)
+ * C) Rules for packet carrying SHUTDOWN COMPLETE:
+ * ...
+ * - The receiver of a SHUTDOWN COMPLETE shall accept the packet if the
+ * Verification Tag field of the packet matches its own tag OR it is
+ * set to its peer's tag and the T bit is set in the Chunk Flags.
+ * Otherwise, the receiver MUST silently discard the packet and take
+ * no further action. An endpoint MUST ignore the SHUTDOWN COMPLETE if
+ * it is not in the SHUTDOWN-ACK-SENT state.
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_do_4_C(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ struct sctp_ulpevent *ev;
+
+ /* RFC 2960 6.10 Bundling
+ *
+ * An endpoint MUST NOT bundle INIT, INIT ACK or
+ * SHUTDOWN COMPLETE with any other chunks.
+ */
+ if (!chunk->singleton)
+ return SCTP_DISPOSITION_VIOLATION;
+
+ if (!sctp_vtag_verify_either(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* RFC 2960 10.2 SCTP-to-ULP
+ *
+ * H) SHUTDOWN COMPLETE notification
+ *
+ * When SCTP completes the shutdown procedures (section 9.2) this
+ * notification is passed to the upper layer.
+ */
+ ev = sctp_ulpevent_make_assoc_change(asoc, 0, SCTP_SHUTDOWN_COMP,
+ 0, 0, 0, GFP_ATOMIC);
+ if (!ev)
+ goto nomem;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP, SCTP_ULPEVENT(ev));
+
+ /* Upon reception of the SHUTDOWN COMPLETE chunk the endpoint
+ * will verify that it is in SHUTDOWN-ACK-SENT state, if it is
+ * not the chunk should be discarded. If the endpoint is in
+ * the SHUTDOWN-ACK-SENT state the endpoint should stop the
+ * T2-shutdown timer and remove all knowledge of the
+ * association (and thus the association enters the CLOSED
+ * state).
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T2_SHUTDOWN));
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T5_SHUTDOWN_GUARD));
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_CLOSED));
+
+ SCTP_INC_STATS(SctpShutdowns);
+ SCTP_DEC_STATS(SctpCurrEstab);
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB, SCTP_NULL());
+
+ return SCTP_DISPOSITION_DELETE_TCB;
+
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * Respond to a normal INIT chunk.
+ * We are the side that is being asked for an association.
+ *
+ * Section: 5.1 Normal Establishment of an Association, B
+ * B) "Z" shall respond immediately with an INIT ACK chunk. The
+ * destination IP address of the INIT ACK MUST be set to the source
+ * IP address of the INIT to which this INIT ACK is responding. In
+ * the response, besides filling in other parameters, "Z" must set the
+ * Verification Tag field to Tag_A, and also provide its own
+ * Verification Tag (Tag_Z) in the Initiate Tag field.
+ *
+ * Verification Tag: Must be 0.
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_do_5_1B_init(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ struct sctp_chunk *repl;
+ struct sctp_association *new_asoc;
+ struct sctp_chunk *err_chunk;
+ struct sctp_packet *packet;
+ sctp_unrecognized_param_t *unk_param;
+ struct sock *sk;
+ int len;
+
+ /* 6.10 Bundling
+ * An endpoint MUST NOT bundle INIT, INIT ACK or
+ * SHUTDOWN COMPLETE with any other chunks.
+ *
+ * IG Section 2.11.2
+ * Furthermore, we require that the receiver of an INIT chunk MUST
+ * enforce these rules by silently discarding an arriving packet
+ * with an INIT chunk that is bundled with other chunks.
+ */
+ if (!chunk->singleton)
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* If the packet is an OOTB packet which is temporarily on the
+ * control endpoint, respond with an ABORT.
+ */
+ if (ep == sctp_sk((sctp_get_ctl_sock()))->ep)
+ return sctp_sf_tabort_8_4_8(ep, asoc, type, arg, commands);
+
+ sk = ep->base.sk;
+ /* If the endpoint is not listening or if the number of associations
+ * on the TCP-style socket exceed the max backlog, respond with an
+ * ABORT.
+ */
+ if (!sctp_sstate(sk, LISTENING) ||
+ (sctp_style(sk, TCP) &&
+ (sk->ack_backlog >= sk->max_ack_backlog)))
+ return sctp_sf_tabort_8_4_8(ep, asoc, type, arg, commands);
+
+ /* 3.1 A packet containing an INIT chunk MUST have a zero Verification
+ * Tag.
+ */
+ if (chunk->sctp_hdr->vtag != 0)
+ return sctp_sf_tabort_8_4_8(ep, asoc, type, arg, commands);
+
+ /* Make sure that the INIT chunk has a valid length.
+ * Normally, this would cause an ABORT with a Protocol Violation
+ * error, but since we don't have an association, we'll
+ * just discard the packet.
+ */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_init_chunk_t)))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Verify the INIT chunk before processing it. */
+ err_chunk = NULL;
+ if (!sctp_verify_init(asoc, chunk->chunk_hdr->type,
+ (sctp_init_chunk_t *)chunk->chunk_hdr, chunk,
+ &err_chunk)) {
+ /* This chunk contains fatal error. It is to be discarded.
+ * Send an ABORT, with causes if there is any.
+ */
+ if (err_chunk) {
+ packet = sctp_abort_pkt_new(ep, asoc, arg,
+ (__u8 *)(err_chunk->chunk_hdr) +
+ sizeof(sctp_chunkhdr_t),
+ ntohs(err_chunk->chunk_hdr->length) -
+ sizeof(sctp_chunkhdr_t));
+
+ sctp_chunk_free(err_chunk);
+
+ if (packet) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_SEND_PKT,
+ SCTP_PACKET(packet));
+ SCTP_INC_STATS(SctpOutCtrlChunks);
+ return SCTP_DISPOSITION_CONSUME;
+ } else {
+ return SCTP_DISPOSITION_NOMEM;
+ }
+ } else {
+ return sctp_sf_tabort_8_4_8(ep, asoc, type, arg,
+ commands);
+ }
+ }
+
+ /* Grab the INIT header. */
+ chunk->subh.init_hdr = (sctp_inithdr_t *)chunk->skb->data;
+
+ /* Tag the variable length parameters. */
+ chunk->param_hdr.v = skb_pull(chunk->skb, sizeof(sctp_inithdr_t));
+
+ new_asoc = sctp_make_temp_asoc(ep, chunk, GFP_ATOMIC);
+ if (!new_asoc)
+ goto nomem;
+
+ /* The call, sctp_process_init(), can fail on memory allocation. */
+ if (!sctp_process_init(new_asoc, chunk->chunk_hdr->type,
+ sctp_source(chunk),
+ (sctp_init_chunk_t *)chunk->chunk_hdr,
+ GFP_ATOMIC))
+ goto nomem_init;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_ASOC, SCTP_ASOC(new_asoc));
+
+ /* B) "Z" shall respond immediately with an INIT ACK chunk. */
+
+ /* If there are errors need to be reported for unknown parameters,
+ * make sure to reserve enough room in the INIT ACK for them.
+ */
+ len = 0;
+ if (err_chunk)
+ len = ntohs(err_chunk->chunk_hdr->length) -
+ sizeof(sctp_chunkhdr_t);
+
+ if (sctp_assoc_set_bind_addr_from_ep(new_asoc, GFP_ATOMIC) < 0)
+ goto nomem_ack;
+
+ repl = sctp_make_init_ack(new_asoc, chunk, GFP_ATOMIC, len);
+ if (!repl)
+ goto nomem_ack;
+
+ /* If there are errors need to be reported for unknown parameters,
+ * include them in the outgoing INIT ACK as "Unrecognized parameter"
+ * parameter.
+ */
+ if (err_chunk) {
+ /* Get the "Unrecognized parameter" parameter(s) out of the
+ * ERROR chunk generated by sctp_verify_init(). Since the
+ * error cause code for "unknown parameter" and the
+ * "Unrecognized parameter" type is the same, we can
+ * construct the parameters in INIT ACK by copying the
+ * ERROR causes over.
+ */
+ unk_param = (sctp_unrecognized_param_t *)
+ ((__u8 *)(err_chunk->chunk_hdr) +
+ sizeof(sctp_chunkhdr_t));
+ /* Replace the cause code with the "Unrecognized parameter"
+ * parameter type.
+ */
+ sctp_addto_chunk(repl, len, unk_param);
+ sctp_chunk_free(err_chunk);
+ }
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(repl));
+
+ /*
+ * Note: After sending out INIT ACK with the State Cookie parameter,
+ * "Z" MUST NOT allocate any resources, nor keep any states for the
+ * new association. Otherwise, "Z" will be vulnerable to resource
+ * attacks.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB, SCTP_NULL());
+
+ return SCTP_DISPOSITION_DELETE_TCB;
+
+nomem_ack:
+ if (err_chunk)
+ sctp_chunk_free(err_chunk);
+nomem_init:
+ sctp_association_free(new_asoc);
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * Respond to a normal INIT ACK chunk.
+ * We are the side that is initiating the association.
+ *
+ * Section: 5.1 Normal Establishment of an Association, C
+ * C) Upon reception of the INIT ACK from "Z", "A" shall stop the T1-init
+ * timer and leave COOKIE-WAIT state. "A" shall then send the State
+ * Cookie received in the INIT ACK chunk in a COOKIE ECHO chunk, start
+ * the T1-cookie timer, and enter the COOKIE-ECHOED state.
+ *
+ * Note: The COOKIE ECHO chunk can be bundled with any pending outbound
+ * DATA chunks, but it MUST be the first chunk in the packet and
+ * until the COOKIE ACK is returned the sender MUST NOT send any
+ * other packets to the peer.
+ *
+ * Verification Tag: 3.3.3
+ * If the value of the Initiate Tag in a received INIT ACK chunk is
+ * found to be 0, the receiver MUST treat it as an error and close the
+ * association by transmitting an ABORT.
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_do_5_1C_ack(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ sctp_init_chunk_t *initchunk;
+ __u32 init_tag;
+ struct sctp_chunk *err_chunk;
+ struct sctp_packet *packet;
+ sctp_disposition_t ret;
+
+ if (!sctp_vtag_verify(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Make sure that the INIT-ACK chunk has a valid length */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_initack_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+ /* 6.10 Bundling
+ * An endpoint MUST NOT bundle INIT, INIT ACK or
+ * SHUTDOWN COMPLETE with any other chunks.
+ */
+ if (!chunk->singleton)
+ return SCTP_DISPOSITION_VIOLATION;
+
+ /* Grab the INIT header. */
+ chunk->subh.init_hdr = (sctp_inithdr_t *) chunk->skb->data;
+
+ init_tag = ntohl(chunk->subh.init_hdr->init_tag);
+
+ /* Verification Tag: 3.3.3
+ * If the value of the Initiate Tag in a received INIT ACK
+ * chunk is found to be 0, the receiver MUST treat it as an
+ * error and close the association by transmitting an ABORT.
+ */
+ if (!init_tag) {
+ struct sctp_chunk *reply = sctp_make_abort(asoc, chunk, 0);
+ if (!reply)
+ goto nomem;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply));
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_CLOSED));
+ SCTP_INC_STATS(SctpAborteds);
+ sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB, SCTP_NULL());
+ return SCTP_DISPOSITION_DELETE_TCB;
+ }
+
+ /* Verify the INIT chunk before processing it. */
+ err_chunk = NULL;
+ if (!sctp_verify_init(asoc, chunk->chunk_hdr->type,
+ (sctp_init_chunk_t *)chunk->chunk_hdr, chunk,
+ &err_chunk)) {
+
+ SCTP_INC_STATS(SctpAborteds);
+
+ /* This chunk contains fatal error. It is to be discarded.
+ * Send an ABORT, with causes if there is any.
+ */
+ if (err_chunk) {
+ packet = sctp_abort_pkt_new(ep, asoc, arg,
+ (__u8 *)(err_chunk->chunk_hdr) +
+ sizeof(sctp_chunkhdr_t),
+ ntohs(err_chunk->chunk_hdr->length) -
+ sizeof(sctp_chunkhdr_t));
+
+ sctp_chunk_free(err_chunk);
+
+ if (packet) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_SEND_PKT,
+ SCTP_PACKET(packet));
+ SCTP_INC_STATS(SctpOutCtrlChunks);
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_CLOSED));
+ sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB,
+ SCTP_NULL());
+ return SCTP_DISPOSITION_CONSUME;
+ } else {
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_CLOSED));
+ sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB,
+ SCTP_NULL());
+ return SCTP_DISPOSITION_NOMEM;
+ }
+ } else {
+ ret = sctp_sf_tabort_8_4_8(ep, asoc, type, arg,
+ commands);
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_CLOSED));
+ sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB,
+ SCTP_NULL());
+ return ret;
+ }
+ }
+
+ /* Tag the variable length parameters. Note that we never
+ * convert the parameters in an INIT chunk.
+ */
+ chunk->param_hdr.v = skb_pull(chunk->skb, sizeof(sctp_inithdr_t));
+
+ initchunk = (sctp_init_chunk_t *) chunk->chunk_hdr;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_PEER_INIT,
+ SCTP_PEER_INIT(initchunk));
+
+ /* 5.1 C) "A" shall stop the T1-init timer and leave
+ * COOKIE-WAIT state. "A" shall then ... start the T1-cookie
+ * timer, and enter the COOKIE-ECHOED state.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T1_INIT));
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_START,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T1_COOKIE));
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_COOKIE_ECHOED));
+
+ /* 5.1 C) "A" shall then send the State Cookie received in the
+ * INIT ACK chunk in a COOKIE ECHO chunk, ...
+ */
+ /* If there is any errors to report, send the ERROR chunk generated
+ * for unknown parameters as well.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_GEN_COOKIE_ECHO,
+ SCTP_CHUNK(err_chunk));
+
+ return SCTP_DISPOSITION_CONSUME;
+
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * Respond to a normal COOKIE ECHO chunk.
+ * We are the side that is being asked for an association.
+ *
+ * Section: 5.1 Normal Establishment of an Association, D
+ * D) Upon reception of the COOKIE ECHO chunk, Endpoint "Z" will reply
+ * with a COOKIE ACK chunk after building a TCB and moving to
+ * the ESTABLISHED state. A COOKIE ACK chunk may be bundled with
+ * any pending DATA chunks (and/or SACK chunks), but the COOKIE ACK
+ * chunk MUST be the first chunk in the packet.
+ *
+ * IMPLEMENTATION NOTE: An implementation may choose to send the
+ * Communication Up notification to the SCTP user upon reception
+ * of a valid COOKIE ECHO chunk.
+ *
+ * Verification Tag: 8.5.1 Exceptions in Verification Tag Rules
+ * D) Rules for packet carrying a COOKIE ECHO
+ *
+ * - When sending a COOKIE ECHO, the endpoint MUST use the value of the
+ * Initial Tag received in the INIT ACK.
+ *
+ * - The receiver of a COOKIE ECHO follows the procedures in Section 5.
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_do_5_1D_ce(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type, void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ struct sctp_association *new_asoc;
+ sctp_init_chunk_t *peer_init;
+ struct sctp_chunk *repl;
+ struct sctp_ulpevent *ev;
+ int error = 0;
+ struct sctp_chunk *err_chk_p;
+
+ /* If the packet is an OOTB packet which is temporarily on the
+ * control endpoint, respond with an ABORT.
+ */
+ if (ep == sctp_sk((sctp_get_ctl_sock()))->ep)
+ return sctp_sf_ootb(ep, asoc, type, arg, commands);
+
+ /* Make sure that the COOKIE_ECHO chunk has a valid length.
+ * In this case, we check that we have enough for at least a
+ * chunk header. More detailed verification is done
+ * in sctp_unpack_cookie().
+ */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_chunkhdr_t)))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* "Decode" the chunk. We have no optional parameters so we
+ * are in good shape.
+ */
+ chunk->subh.cookie_hdr =
+ (struct sctp_signed_cookie *)chunk->skb->data;
+ skb_pull(chunk->skb,
+ ntohs(chunk->chunk_hdr->length) - sizeof(sctp_chunkhdr_t));
+
+ /* 5.1 D) Upon reception of the COOKIE ECHO chunk, Endpoint
+ * "Z" will reply with a COOKIE ACK chunk after building a TCB
+ * and moving to the ESTABLISHED state.
+ */
+ new_asoc = sctp_unpack_cookie(ep, asoc, chunk, GFP_ATOMIC, &error,
+ &err_chk_p);
+
+ /* FIXME:
+ * If the re-build failed, what is the proper error path
+ * from here?
+ *
+ * [We should abort the association. --piggy]
+ */
+ if (!new_asoc) {
+ /* FIXME: Several errors are possible. A bad cookie should
+ * be silently discarded, but think about logging it too.
+ */
+ switch (error) {
+ case -SCTP_IERROR_NOMEM:
+ goto nomem;
+
+ case -SCTP_IERROR_STALE_COOKIE:
+ sctp_send_stale_cookie_err(ep, asoc, chunk, commands,
+ err_chk_p);
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ case -SCTP_IERROR_BAD_SIG:
+ default:
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+ };
+ }
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_ASOC, SCTP_ASOC(new_asoc));
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_ESTABLISHED));
+ SCTP_INC_STATS(SctpCurrEstab);
+ SCTP_INC_STATS(SctpPassiveEstabs);
+ sctp_add_cmd_sf(commands, SCTP_CMD_HB_TIMERS_START, SCTP_NULL());
+
+ if (new_asoc->autoclose)
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_START,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_AUTOCLOSE));
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_TRANSMIT, SCTP_NULL());
+
+ /* Re-build the bind address for the association is done in
+ * the sctp_unpack_cookie() already.
+ */
+ /* This is a brand-new association, so these are not yet side
+ * effects--it is safe to run them here.
+ */
+ peer_init = &chunk->subh.cookie_hdr->c.peer_init[0];
+
+ if (!sctp_process_init(new_asoc, chunk->chunk_hdr->type,
+ &chunk->subh.cookie_hdr->c.peer_addr,
+ peer_init, GFP_ATOMIC))
+ goto nomem_init;
+
+ repl = sctp_make_cookie_ack(new_asoc, chunk);
+ if (!repl)
+ goto nomem_repl;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(repl));
+
+ /* RFC 2960 5.1 Normal Establishment of an Association
+ *
+ * D) IMPLEMENTATION NOTE: An implementation may choose to
+ * send the Communication Up notification to the SCTP user
+ * upon reception of a valid COOKIE ECHO chunk.
+ */
+ ev = sctp_ulpevent_make_assoc_change(new_asoc, 0, SCTP_COMM_UP, 0,
+ new_asoc->c.sinit_num_ostreams,
+ new_asoc->c.sinit_max_instreams,
+ GFP_ATOMIC);
+ if (!ev)
+ goto nomem_ev;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP, SCTP_ULPEVENT(ev));
+
+ /* Sockets API Draft Section 5.3.1.6
+ * When a peer sends a Adaption Layer Indication parameter , SCTP
+ * delivers this notification to inform the application that of the
+ * peers requested adaption layer.
+ */
+ if (new_asoc->peer.adaption_ind) {
+ ev = sctp_ulpevent_make_adaption_indication(new_asoc,
+ GFP_ATOMIC);
+ if (!ev)
+ goto nomem_ev;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP,
+ SCTP_ULPEVENT(ev));
+ }
+
+ return SCTP_DISPOSITION_CONSUME;
+
+nomem_ev:
+ sctp_chunk_free(repl);
+nomem_repl:
+nomem_init:
+ sctp_association_free(new_asoc);
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * Respond to a normal COOKIE ACK chunk.
+ * We are the side that is being asked for an association.
+ *
+ * RFC 2960 5.1 Normal Establishment of an Association
+ *
+ * E) Upon reception of the COOKIE ACK, endpoint "A" will move from the
+ * COOKIE-ECHOED state to the ESTABLISHED state, stopping the T1-cookie
+ * timer. It may also notify its ULP about the successful
+ * establishment of the association with a Communication Up
+ * notification (see Section 10).
+ *
+ * Verification Tag:
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_do_5_1E_ca(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type, void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ struct sctp_ulpevent *ev;
+
+ if (!sctp_vtag_verify(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Verify that the chunk length for the COOKIE-ACK is OK.
+ * If we don't do this, any bundled chunks may be junked.
+ */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_chunkhdr_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ /* Reset init error count upon receipt of COOKIE-ACK,
+ * to avoid problems with the managemement of this
+ * counter in stale cookie situations when a transition back
+ * from the COOKIE-ECHOED state to the COOKIE-WAIT
+ * state is performed.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_COUNTER_RESET,
+ SCTP_COUNTER(SCTP_COUNTER_INIT_ERROR));
+
+ /* RFC 2960 5.1 Normal Establishment of an Association
+ *
+ * E) Upon reception of the COOKIE ACK, endpoint "A" will move
+ * from the COOKIE-ECHOED state to the ESTABLISHED state,
+ * stopping the T1-cookie timer.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T1_COOKIE));
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_ESTABLISHED));
+ SCTP_INC_STATS(SctpCurrEstab);
+ SCTP_INC_STATS(SctpActiveEstabs);
+ sctp_add_cmd_sf(commands, SCTP_CMD_HB_TIMERS_START, SCTP_NULL());
+ if (asoc->autoclose)
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_START,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_AUTOCLOSE));
+ sctp_add_cmd_sf(commands, SCTP_CMD_TRANSMIT, SCTP_NULL());
+
+ /* It may also notify its ULP about the successful
+ * establishment of the association with a Communication Up
+ * notification (see Section 10).
+ */
+ ev = sctp_ulpevent_make_assoc_change(asoc, 0, SCTP_COMM_UP,
+ 0, asoc->c.sinit_num_ostreams,
+ asoc->c.sinit_max_instreams,
+ GFP_ATOMIC);
+
+ if (!ev)
+ goto nomem;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP, SCTP_ULPEVENT(ev));
+
+ /* Sockets API Draft Section 5.3.1.6
+ * When a peer sends a Adaption Layer Indication parameter , SCTP
+ * delivers this notification to inform the application that of the
+ * peers requested adaption layer.
+ */
+ if (asoc->peer.adaption_ind) {
+ ev = sctp_ulpevent_make_adaption_indication(asoc, GFP_ATOMIC);
+ if (!ev)
+ goto nomem;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP,
+ SCTP_ULPEVENT(ev));
+ }
+
+ return SCTP_DISPOSITION_CONSUME;
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/* Generate and sendout a heartbeat packet. */
+static sctp_disposition_t sctp_sf_heartbeat(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_transport *transport = (struct sctp_transport *) arg;
+ struct sctp_chunk *reply;
+ sctp_sender_hb_info_t hbinfo;
+ size_t paylen = 0;
+
+ hbinfo.param_hdr.type = SCTP_PARAM_HEARTBEAT_INFO;
+ hbinfo.param_hdr.length = htons(sizeof(sctp_sender_hb_info_t));
+ hbinfo.daddr = transport->ipaddr;
+ hbinfo.sent_at = jiffies;
+
+ /* Send a heartbeat to our peer. */
+ paylen = sizeof(sctp_sender_hb_info_t);
+ reply = sctp_make_heartbeat(asoc, transport, &hbinfo, paylen);
+ if (!reply)
+ return SCTP_DISPOSITION_NOMEM;
+
+ /* Set rto_pending indicating that an RTT measurement
+ * is started with this heartbeat chunk.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_RTO_PENDING,
+ SCTP_TRANSPORT(transport));
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply));
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/* Generate a HEARTBEAT packet on the given transport. */
+sctp_disposition_t sctp_sf_sendbeat_8_3(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_transport *transport = (struct sctp_transport *) arg;
+
+ if (asoc->overall_error_count > asoc->max_retrans) {
+ /* CMD_ASSOC_FAILED calls CMD_DELETE_TCB. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
+ SCTP_U32(SCTP_ERROR_NO_ERROR));
+ SCTP_INC_STATS(SctpAborteds);
+ SCTP_DEC_STATS(SctpCurrEstab);
+ return SCTP_DISPOSITION_DELETE_TCB;
+ }
+
+ /* Section 3.3.5.
+ * The Sender-specific Heartbeat Info field should normally include
+ * information about the sender's current time when this HEARTBEAT
+ * chunk is sent and the destination transport address to which this
+ * HEARTBEAT is sent (see Section 8.3).
+ */
+
+ if (transport->hb_allowed) {
+ if (SCTP_DISPOSITION_NOMEM ==
+ sctp_sf_heartbeat(ep, asoc, type, arg,
+ commands))
+ return SCTP_DISPOSITION_NOMEM;
+ /* Set transport error counter and association error counter
+ * when sending heartbeat.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TRANSPORT_RESET,
+ SCTP_TRANSPORT(transport));
+ }
+ sctp_add_cmd_sf(commands, SCTP_CMD_HB_TIMER_UPDATE,
+ SCTP_TRANSPORT(transport));
+
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/*
+ * Process an heartbeat request.
+ *
+ * Section: 8.3 Path Heartbeat
+ * The receiver of the HEARTBEAT should immediately respond with a
+ * HEARTBEAT ACK that contains the Heartbeat Information field copied
+ * from the received HEARTBEAT chunk.
+ *
+ * Verification Tag: 8.5 Verification Tag [Normal verification]
+ * When receiving an SCTP packet, the endpoint MUST ensure that the
+ * value in the Verification Tag field of the received SCTP packet
+ * matches its own Tag. If the received Verification Tag value does not
+ * match the receiver's own tag value, the receiver shall silently
+ * discard the packet and shall not process it any further except for
+ * those cases listed in Section 8.5.1 below.
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_beat_8_3(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ struct sctp_chunk *reply;
+ size_t paylen = 0;
+
+ if (!sctp_vtag_verify(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Make sure that the HEARTBEAT chunk has a valid length. */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_heartbeat_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ /* 8.3 The receiver of the HEARTBEAT should immediately
+ * respond with a HEARTBEAT ACK that contains the Heartbeat
+ * Information field copied from the received HEARTBEAT chunk.
+ */
+ chunk->subh.hb_hdr = (sctp_heartbeathdr_t *) chunk->skb->data;
+ paylen = ntohs(chunk->chunk_hdr->length) - sizeof(sctp_chunkhdr_t);
+ skb_pull(chunk->skb, paylen);
+
+ reply = sctp_make_heartbeat_ack(asoc, chunk,
+ chunk->subh.hb_hdr, paylen);
+ if (!reply)
+ goto nomem;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply));
+ return SCTP_DISPOSITION_CONSUME;
+
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * Process the returning HEARTBEAT ACK.
+ *
+ * Section: 8.3 Path Heartbeat
+ * Upon the receipt of the HEARTBEAT ACK, the sender of the HEARTBEAT
+ * should clear the error counter of the destination transport
+ * address to which the HEARTBEAT was sent, and mark the destination
+ * transport address as active if it is not so marked. The endpoint may
+ * optionally report to the upper layer when an inactive destination
+ * address is marked as active due to the reception of the latest
+ * HEARTBEAT ACK. The receiver of the HEARTBEAT ACK must also
+ * clear the association overall error count as well (as defined
+ * in section 8.1).
+ *
+ * The receiver of the HEARTBEAT ACK should also perform an RTT
+ * measurement for that destination transport address using the time
+ * value carried in the HEARTBEAT ACK chunk.
+ *
+ * Verification Tag: 8.5 Verification Tag [Normal verification]
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_backbeat_8_3(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ union sctp_addr from_addr;
+ struct sctp_transport *link;
+ sctp_sender_hb_info_t *hbinfo;
+ unsigned long max_interval;
+
+ if (!sctp_vtag_verify(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Make sure that the HEARTBEAT-ACK chunk has a valid length. */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_heartbeat_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ hbinfo = (sctp_sender_hb_info_t *) chunk->skb->data;
+ from_addr = hbinfo->daddr;
+ link = sctp_assoc_lookup_paddr(asoc, &from_addr);
+
+ /* This should never happen, but lets log it if so. */
+ if (!link) {
+ printk(KERN_WARNING
+ "%s: Could not find address %d.%d.%d.%d\n",
+ __FUNCTION__, NIPQUAD(from_addr.v4.sin_addr));
+ return SCTP_DISPOSITION_DISCARD;
+ }
+
+ max_interval = link->hb_interval + link->rto;
+
+ /* Check if the timestamp looks valid. */
+ if (time_after(hbinfo->sent_at, jiffies) ||
+ time_after(jiffies, hbinfo->sent_at + max_interval)) {
+ SCTP_DEBUG_PRINTK("%s: HEARTBEAT ACK with invalid timestamp"
+ "received for transport: %p\n",
+ __FUNCTION__, link);
+ return SCTP_DISPOSITION_DISCARD;
+ }
+
+ /* 8.3 Upon the receipt of the HEARTBEAT ACK, the sender of
+ * the HEARTBEAT should clear the error counter of the
+ * destination transport address to which the HEARTBEAT was
+ * sent and mark the destination transport address as active if
+ * it is not so marked.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TRANSPORT_ON, SCTP_TRANSPORT(link));
+
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/* Helper function to send out an abort for the restart
+ * condition.
+ */
+static int sctp_sf_send_restart_abort(union sctp_addr *ssa,
+ struct sctp_chunk *init,
+ sctp_cmd_seq_t *commands)
+{
+ int len;
+ struct sctp_packet *pkt;
+ union sctp_addr_param *addrparm;
+ struct sctp_errhdr *errhdr;
+ struct sctp_endpoint *ep;
+ char buffer[sizeof(struct sctp_errhdr)+sizeof(union sctp_addr_param)];
+ struct sctp_af *af = sctp_get_af_specific(ssa->v4.sin_family);
+
+ /* Build the error on the stack. We are way to malloc crazy
+ * throughout the code today.
+ */
+ errhdr = (struct sctp_errhdr *)buffer;
+ addrparm = (union sctp_addr_param *)errhdr->variable;
+
+ /* Copy into a parm format. */
+ len = af->to_addr_param(ssa, addrparm);
+ len += sizeof(sctp_errhdr_t);
+
+ errhdr->cause = SCTP_ERROR_RESTART;
+ errhdr->length = htons(len);
+
+ /* Assign to the control socket. */
+ ep = sctp_sk((sctp_get_ctl_sock()))->ep;
+
+ /* Association is NULL since this may be a restart attack and we
+ * want to send back the attacker's vtag.
+ */
+ pkt = sctp_abort_pkt_new(ep, NULL, init, errhdr, len);
+
+ if (!pkt)
+ goto out;
+ sctp_add_cmd_sf(commands, SCTP_CMD_SEND_PKT, SCTP_PACKET(pkt));
+
+ SCTP_INC_STATS(SctpOutCtrlChunks);
+
+ /* Discard the rest of the inbound packet. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_DISCARD_PACKET, SCTP_NULL());
+
+out:
+ /* Even if there is no memory, treat as a failure so
+ * the packet will get dropped.
+ */
+ return 0;
+}
+
+/* A restart is occurring, check to make sure no new addresses
+ * are being added as we may be under a takeover attack.
+ */
+static int sctp_sf_check_restart_addrs(const struct sctp_association *new_asoc,
+ const struct sctp_association *asoc,
+ struct sctp_chunk *init,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_transport *new_addr, *addr;
+ struct list_head *pos, *pos2;
+ int found;
+
+ /* Implementor's Guide - Sectin 5.2.2
+ * ...
+ * Before responding the endpoint MUST check to see if the
+ * unexpected INIT adds new addresses to the association. If new
+ * addresses are added to the association, the endpoint MUST respond
+ * with an ABORT..
+ */
+
+ /* Search through all current addresses and make sure
+ * we aren't adding any new ones.
+ */
+ new_addr = NULL;
+ found = 0;
+
+ list_for_each(pos, &new_asoc->peer.transport_addr_list) {
+ new_addr = list_entry(pos, struct sctp_transport, transports);
+ found = 0;
+ list_for_each(pos2, &asoc->peer.transport_addr_list) {
+ addr = list_entry(pos2, struct sctp_transport,
+ transports);
+ if (sctp_cmp_addr_exact(&new_addr->ipaddr,
+ &addr->ipaddr)) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found)
+ break;
+ }
+
+ /* If a new address was added, ABORT the sender. */
+ if (!found && new_addr) {
+ sctp_sf_send_restart_abort(&new_addr->ipaddr, init, commands);
+ }
+
+ /* Return success if all addresses were found. */
+ return found;
+}
+
+/* Populate the verification/tie tags based on overlapping INIT
+ * scenario.
+ *
+ * Note: Do not use in CLOSED or SHUTDOWN-ACK-SENT state.
+ */
+static void sctp_tietags_populate(struct sctp_association *new_asoc,
+ const struct sctp_association *asoc)
+{
+ switch (asoc->state) {
+
+ /* 5.2.1 INIT received in COOKIE-WAIT or COOKIE-ECHOED State */
+
+ case SCTP_STATE_COOKIE_WAIT:
+ new_asoc->c.my_vtag = asoc->c.my_vtag;
+ new_asoc->c.my_ttag = asoc->c.my_vtag;
+ new_asoc->c.peer_ttag = 0;
+ break;
+
+ case SCTP_STATE_COOKIE_ECHOED:
+ new_asoc->c.my_vtag = asoc->c.my_vtag;
+ new_asoc->c.my_ttag = asoc->c.my_vtag;
+ new_asoc->c.peer_ttag = asoc->c.peer_vtag;
+ break;
+
+ /* 5.2.2 Unexpected INIT in States Other than CLOSED, COOKIE-ECHOED,
+ * COOKIE-WAIT and SHUTDOWN-ACK-SENT
+ */
+ default:
+ new_asoc->c.my_ttag = asoc->c.my_vtag;
+ new_asoc->c.peer_ttag = asoc->c.peer_vtag;
+ break;
+ };
+
+ /* Other parameters for the endpoint SHOULD be copied from the
+ * existing parameters of the association (e.g. number of
+ * outbound streams) into the INIT ACK and cookie.
+ */
+ new_asoc->rwnd = asoc->rwnd;
+ new_asoc->c.sinit_num_ostreams = asoc->c.sinit_num_ostreams;
+ new_asoc->c.sinit_max_instreams = asoc->c.sinit_max_instreams;
+ new_asoc->c.initial_tsn = asoc->c.initial_tsn;
+}
+
+/*
+ * Compare vtag/tietag values to determine unexpected COOKIE-ECHO
+ * handling action.
+ *
+ * RFC 2960 5.2.4 Handle a COOKIE ECHO when a TCB exists.
+ *
+ * Returns value representing action to be taken. These action values
+ * correspond to Action/Description values in RFC 2960, Table 2.
+ */
+static char sctp_tietags_compare(struct sctp_association *new_asoc,
+ const struct sctp_association *asoc)
+{
+ /* In this case, the peer may have restarted. */
+ if ((asoc->c.my_vtag != new_asoc->c.my_vtag) &&
+ (asoc->c.peer_vtag != new_asoc->c.peer_vtag) &&
+ (asoc->c.my_vtag == new_asoc->c.my_ttag) &&
+ (asoc->c.peer_vtag == new_asoc->c.peer_ttag))
+ return 'A';
+
+ /* Collision case B. */
+ if ((asoc->c.my_vtag == new_asoc->c.my_vtag) &&
+ ((asoc->c.peer_vtag != new_asoc->c.peer_vtag) ||
+ (0 == asoc->c.peer_vtag))) {
+ return 'B';
+ }
+
+ /* Collision case D. */
+ if ((asoc->c.my_vtag == new_asoc->c.my_vtag) &&
+ (asoc->c.peer_vtag == new_asoc->c.peer_vtag))
+ return 'D';
+
+ /* Collision case C. */
+ if ((asoc->c.my_vtag != new_asoc->c.my_vtag) &&
+ (asoc->c.peer_vtag == new_asoc->c.peer_vtag) &&
+ (0 == new_asoc->c.my_ttag) &&
+ (0 == new_asoc->c.peer_ttag))
+ return 'C';
+
+ /* No match to any of the special cases; discard this packet. */
+ return 'E';
+}
+
+/* Common helper routine for both duplicate and simulataneous INIT
+ * chunk handling.
+ */
+static sctp_disposition_t sctp_sf_do_unexpected_init(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg, sctp_cmd_seq_t *commands)
+{
+ sctp_disposition_t retval;
+ struct sctp_chunk *chunk = arg;
+ struct sctp_chunk *repl;
+ struct sctp_association *new_asoc;
+ struct sctp_chunk *err_chunk;
+ struct sctp_packet *packet;
+ sctp_unrecognized_param_t *unk_param;
+ int len;
+
+ /* 6.10 Bundling
+ * An endpoint MUST NOT bundle INIT, INIT ACK or
+ * SHUTDOWN COMPLETE with any other chunks.
+ *
+ * IG Section 2.11.2
+ * Furthermore, we require that the receiver of an INIT chunk MUST
+ * enforce these rules by silently discarding an arriving packet
+ * with an INIT chunk that is bundled with other chunks.
+ */
+ if (!chunk->singleton)
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* 3.1 A packet containing an INIT chunk MUST have a zero Verification
+ * Tag.
+ */
+ if (chunk->sctp_hdr->vtag != 0)
+ return sctp_sf_tabort_8_4_8(ep, asoc, type, arg, commands);
+
+ /* Make sure that the INIT chunk has a valid length.
+ * In this case, we generate a protocol violation since we have
+ * an association established.
+ */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_init_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+ /* Grab the INIT header. */
+ chunk->subh.init_hdr = (sctp_inithdr_t *) chunk->skb->data;
+
+ /* Tag the variable length parameters. */
+ chunk->param_hdr.v = skb_pull(chunk->skb, sizeof(sctp_inithdr_t));
+
+ /* Verify the INIT chunk before processing it. */
+ err_chunk = NULL;
+ if (!sctp_verify_init(asoc, chunk->chunk_hdr->type,
+ (sctp_init_chunk_t *)chunk->chunk_hdr, chunk,
+ &err_chunk)) {
+ /* This chunk contains fatal error. It is to be discarded.
+ * Send an ABORT, with causes if there is any.
+ */
+ if (err_chunk) {
+ packet = sctp_abort_pkt_new(ep, asoc, arg,
+ (__u8 *)(err_chunk->chunk_hdr) +
+ sizeof(sctp_chunkhdr_t),
+ ntohs(err_chunk->chunk_hdr->length) -
+ sizeof(sctp_chunkhdr_t));
+
+ if (packet) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_SEND_PKT,
+ SCTP_PACKET(packet));
+ SCTP_INC_STATS(SctpOutCtrlChunks);
+ retval = SCTP_DISPOSITION_CONSUME;
+ } else {
+ retval = SCTP_DISPOSITION_NOMEM;
+ }
+ goto cleanup;
+ } else {
+ return sctp_sf_tabort_8_4_8(ep, asoc, type, arg,
+ commands);
+ }
+ }
+
+ /*
+ * Other parameters for the endpoint SHOULD be copied from the
+ * existing parameters of the association (e.g. number of
+ * outbound streams) into the INIT ACK and cookie.
+ * FIXME: We are copying parameters from the endpoint not the
+ * association.
+ */
+ new_asoc = sctp_make_temp_asoc(ep, chunk, GFP_ATOMIC);
+ if (!new_asoc)
+ goto nomem;
+
+ /* In the outbound INIT ACK the endpoint MUST copy its current
+ * Verification Tag and Peers Verification tag into a reserved
+ * place (local tie-tag and per tie-tag) within the state cookie.
+ */
+ if (!sctp_process_init(new_asoc, chunk->chunk_hdr->type,
+ sctp_source(chunk),
+ (sctp_init_chunk_t *)chunk->chunk_hdr,
+ GFP_ATOMIC)) {
+ retval = SCTP_DISPOSITION_NOMEM;
+ goto nomem_init;
+ }
+
+ /* Make sure no new addresses are being added during the
+ * restart. Do not do this check for COOKIE-WAIT state,
+ * since there are no peer addresses to check against.
+ * Upon return an ABORT will have been sent if needed.
+ */
+ if (!sctp_state(asoc, COOKIE_WAIT)) {
+ if (!sctp_sf_check_restart_addrs(new_asoc, asoc, chunk,
+ commands)) {
+ retval = SCTP_DISPOSITION_CONSUME;
+ goto cleanup_asoc;
+ }
+ }
+
+ sctp_tietags_populate(new_asoc, asoc);
+
+ /* B) "Z" shall respond immediately with an INIT ACK chunk. */
+
+ /* If there are errors need to be reported for unknown parameters,
+ * make sure to reserve enough room in the INIT ACK for them.
+ */
+ len = 0;
+ if (err_chunk) {
+ len = ntohs(err_chunk->chunk_hdr->length) -
+ sizeof(sctp_chunkhdr_t);
+ }
+
+ if (sctp_assoc_set_bind_addr_from_ep(new_asoc, GFP_ATOMIC) < 0)
+ goto nomem;
+
+ repl = sctp_make_init_ack(new_asoc, chunk, GFP_ATOMIC, len);
+ if (!repl)
+ goto nomem;
+
+ /* If there are errors need to be reported for unknown parameters,
+ * include them in the outgoing INIT ACK as "Unrecognized parameter"
+ * parameter.
+ */
+ if (err_chunk) {
+ /* Get the "Unrecognized parameter" parameter(s) out of the
+ * ERROR chunk generated by sctp_verify_init(). Since the
+ * error cause code for "unknown parameter" and the
+ * "Unrecognized parameter" type is the same, we can
+ * construct the parameters in INIT ACK by copying the
+ * ERROR causes over.
+ */
+ unk_param = (sctp_unrecognized_param_t *)
+ ((__u8 *)(err_chunk->chunk_hdr) +
+ sizeof(sctp_chunkhdr_t));
+ /* Replace the cause code with the "Unrecognized parameter"
+ * parameter type.
+ */
+ sctp_addto_chunk(repl, len, unk_param);
+ }
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_ASOC, SCTP_ASOC(new_asoc));
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(repl));
+
+ /*
+ * Note: After sending out INIT ACK with the State Cookie parameter,
+ * "Z" MUST NOT allocate any resources for this new association.
+ * Otherwise, "Z" will be vulnerable to resource attacks.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB, SCTP_NULL());
+ retval = SCTP_DISPOSITION_CONSUME;
+
+cleanup:
+ if (err_chunk)
+ sctp_chunk_free(err_chunk);
+ return retval;
+nomem:
+ retval = SCTP_DISPOSITION_NOMEM;
+ goto cleanup;
+nomem_init:
+cleanup_asoc:
+ sctp_association_free(new_asoc);
+ goto cleanup;
+}
+
+/*
+ * Handle simultanous INIT.
+ * This means we started an INIT and then we got an INIT request from
+ * our peer.
+ *
+ * Section: 5.2.1 INIT received in COOKIE-WAIT or COOKIE-ECHOED State (Item B)
+ * This usually indicates an initialization collision, i.e., each
+ * endpoint is attempting, at about the same time, to establish an
+ * association with the other endpoint.
+ *
+ * Upon receipt of an INIT in the COOKIE-WAIT or COOKIE-ECHOED state, an
+ * endpoint MUST respond with an INIT ACK using the same parameters it
+ * sent in its original INIT chunk (including its Verification Tag,
+ * unchanged). These original parameters are combined with those from the
+ * newly received INIT chunk. The endpoint shall also generate a State
+ * Cookie with the INIT ACK. The endpoint uses the parameters sent in its
+ * INIT to calculate the State Cookie.
+ *
+ * After that, the endpoint MUST NOT change its state, the T1-init
+ * timer shall be left running and the corresponding TCB MUST NOT be
+ * destroyed. The normal procedures for handling State Cookies when
+ * a TCB exists will resolve the duplicate INITs to a single association.
+ *
+ * For an endpoint that is in the COOKIE-ECHOED state it MUST populate
+ * its Tie-Tags with the Tag information of itself and its peer (see
+ * section 5.2.2 for a description of the Tie-Tags).
+ *
+ * Verification Tag: Not explicit, but an INIT can not have a valid
+ * verification tag, so we skip the check.
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_do_5_2_1_siminit(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ /* Call helper to do the real work for both simulataneous and
+ * duplicate INIT chunk handling.
+ */
+ return sctp_sf_do_unexpected_init(ep, asoc, type, arg, commands);
+}
+
+/*
+ * Handle duplicated INIT messages. These are usually delayed
+ * restransmissions.
+ *
+ * Section: 5.2.2 Unexpected INIT in States Other than CLOSED,
+ * COOKIE-ECHOED and COOKIE-WAIT
+ *
+ * Unless otherwise stated, upon reception of an unexpected INIT for
+ * this association, the endpoint shall generate an INIT ACK with a
+ * State Cookie. In the outbound INIT ACK the endpoint MUST copy its
+ * current Verification Tag and peer's Verification Tag into a reserved
+ * place within the state cookie. We shall refer to these locations as
+ * the Peer's-Tie-Tag and the Local-Tie-Tag. The outbound SCTP packet
+ * containing this INIT ACK MUST carry a Verification Tag value equal to
+ * the Initiation Tag found in the unexpected INIT. And the INIT ACK
+ * MUST contain a new Initiation Tag (randomly generated see Section
+ * 5.3.1). Other parameters for the endpoint SHOULD be copied from the
+ * existing parameters of the association (e.g. number of outbound
+ * streams) into the INIT ACK and cookie.
+ *
+ * After sending out the INIT ACK, the endpoint shall take no further
+ * actions, i.e., the existing association, including its current state,
+ * and the corresponding TCB MUST NOT be changed.
+ *
+ * Note: Only when a TCB exists and the association is not in a COOKIE-
+ * WAIT state are the Tie-Tags populated. For a normal association INIT
+ * (i.e. the endpoint is in a COOKIE-WAIT state), the Tie-Tags MUST be
+ * set to 0 (indicating that no previous TCB existed). The INIT ACK and
+ * State Cookie are populated as specified in section 5.2.1.
+ *
+ * Verification Tag: Not specified, but an INIT has no way of knowing
+ * what the verification tag could be, so we ignore it.
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_do_5_2_2_dupinit(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ /* Call helper to do the real work for both simulataneous and
+ * duplicate INIT chunk handling.
+ */
+ return sctp_sf_do_unexpected_init(ep, asoc, type, arg, commands);
+}
+
+
+
+/* Unexpected COOKIE-ECHO handler for peer restart (Table 2, action 'A')
+ *
+ * Section 5.2.4
+ * A) In this case, the peer may have restarted.
+ */
+static sctp_disposition_t sctp_sf_do_dupcook_a(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ struct sctp_chunk *chunk,
+ sctp_cmd_seq_t *commands,
+ struct sctp_association *new_asoc)
+{
+ sctp_init_chunk_t *peer_init;
+ struct sctp_ulpevent *ev;
+ struct sctp_chunk *repl;
+ struct sctp_chunk *err;
+ sctp_disposition_t disposition;
+
+ /* new_asoc is a brand-new association, so these are not yet
+ * side effects--it is safe to run them here.
+ */
+ peer_init = &chunk->subh.cookie_hdr->c.peer_init[0];
+
+ if (!sctp_process_init(new_asoc, chunk->chunk_hdr->type,
+ sctp_source(chunk), peer_init,
+ GFP_ATOMIC))
+ goto nomem;
+
+ /* Make sure no new addresses are being added during the
+ * restart. Though this is a pretty complicated attack
+ * since you'd have to get inside the cookie.
+ */
+ if (!sctp_sf_check_restart_addrs(new_asoc, asoc, chunk, commands)) {
+ return SCTP_DISPOSITION_CONSUME;
+ }
+
+ /* If the endpoint is in the SHUTDOWN-ACK-SENT state and recognizes
+ * the peer has restarted (Action A), it MUST NOT setup a new
+ * association but instead resend the SHUTDOWN ACK and send an ERROR
+ * chunk with a "Cookie Received while Shutting Down" error cause to
+ * its peer.
+ */
+ if (sctp_state(asoc, SHUTDOWN_ACK_SENT)) {
+ disposition = sctp_sf_do_9_2_reshutack(ep, asoc,
+ SCTP_ST_CHUNK(chunk->chunk_hdr->type),
+ chunk, commands);
+ if (SCTP_DISPOSITION_NOMEM == disposition)
+ goto nomem;
+
+ err = sctp_make_op_error(asoc, chunk,
+ SCTP_ERROR_COOKIE_IN_SHUTDOWN,
+ NULL, 0);
+ if (err)
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(err));
+
+ return SCTP_DISPOSITION_CONSUME;
+ }
+
+ /* For now, fail any unsent/unacked data. Consider the optional
+ * choice of resending of this data.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_PURGE_OUTQUEUE, SCTP_NULL());
+
+ /* Update the content of current association. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_UPDATE_ASSOC, SCTP_ASOC(new_asoc));
+
+ repl = sctp_make_cookie_ack(new_asoc, chunk);
+ if (!repl)
+ goto nomem;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(repl));
+
+ /* Report association restart to upper layer. */
+ ev = sctp_ulpevent_make_assoc_change(asoc, 0, SCTP_RESTART, 0,
+ new_asoc->c.sinit_num_ostreams,
+ new_asoc->c.sinit_max_instreams,
+ GFP_ATOMIC);
+ if (!ev)
+ goto nomem_ev;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP, SCTP_ULPEVENT(ev));
+ return SCTP_DISPOSITION_CONSUME;
+
+nomem_ev:
+ sctp_chunk_free(repl);
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/* Unexpected COOKIE-ECHO handler for setup collision (Table 2, action 'B')
+ *
+ * Section 5.2.4
+ * B) In this case, both sides may be attempting to start an association
+ * at about the same time but the peer endpoint started its INIT
+ * after responding to the local endpoint's INIT
+ */
+/* This case represents an initialization collision. */
+static sctp_disposition_t sctp_sf_do_dupcook_b(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ struct sctp_chunk *chunk,
+ sctp_cmd_seq_t *commands,
+ struct sctp_association *new_asoc)
+{
+ sctp_init_chunk_t *peer_init;
+ struct sctp_ulpevent *ev;
+ struct sctp_chunk *repl;
+
+ /* new_asoc is a brand-new association, so these are not yet
+ * side effects--it is safe to run them here.
+ */
+ peer_init = &chunk->subh.cookie_hdr->c.peer_init[0];
+ if (!sctp_process_init(new_asoc, chunk->chunk_hdr->type,
+ sctp_source(chunk), peer_init,
+ GFP_ATOMIC))
+ goto nomem;
+
+ /* Update the content of current association. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_UPDATE_ASSOC, SCTP_ASOC(new_asoc));
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_ESTABLISHED));
+ SCTP_INC_STATS(SctpCurrEstab);
+ sctp_add_cmd_sf(commands, SCTP_CMD_HB_TIMERS_START, SCTP_NULL());
+
+ repl = sctp_make_cookie_ack(new_asoc, chunk);
+ if (!repl)
+ goto nomem;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(repl));
+ sctp_add_cmd_sf(commands, SCTP_CMD_TRANSMIT, SCTP_NULL());
+
+ /* RFC 2960 5.1 Normal Establishment of an Association
+ *
+ * D) IMPLEMENTATION NOTE: An implementation may choose to
+ * send the Communication Up notification to the SCTP user
+ * upon reception of a valid COOKIE ECHO chunk.
+ */
+ ev = sctp_ulpevent_make_assoc_change(asoc, 0, SCTP_COMM_UP, 0,
+ new_asoc->c.sinit_num_ostreams,
+ new_asoc->c.sinit_max_instreams,
+ GFP_ATOMIC);
+ if (!ev)
+ goto nomem_ev;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP, SCTP_ULPEVENT(ev));
+
+ /* Sockets API Draft Section 5.3.1.6
+ * When a peer sends a Adaption Layer Indication parameter , SCTP
+ * delivers this notification to inform the application that of the
+ * peers requested adaption layer.
+ */
+ if (asoc->peer.adaption_ind) {
+ ev = sctp_ulpevent_make_adaption_indication(asoc, GFP_ATOMIC);
+ if (!ev)
+ goto nomem_ev;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP,
+ SCTP_ULPEVENT(ev));
+ }
+
+ return SCTP_DISPOSITION_CONSUME;
+
+nomem_ev:
+ sctp_chunk_free(repl);
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/* Unexpected COOKIE-ECHO handler for setup collision (Table 2, action 'C')
+ *
+ * Section 5.2.4
+ * C) In this case, the local endpoint's cookie has arrived late.
+ * Before it arrived, the local endpoint sent an INIT and received an
+ * INIT-ACK and finally sent a COOKIE ECHO with the peer's same tag
+ * but a new tag of its own.
+ */
+/* This case represents an initialization collision. */
+static sctp_disposition_t sctp_sf_do_dupcook_c(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ struct sctp_chunk *chunk,
+ sctp_cmd_seq_t *commands,
+ struct sctp_association *new_asoc)
+{
+ /* The cookie should be silently discarded.
+ * The endpoint SHOULD NOT change states and should leave
+ * any timers running.
+ */
+ return SCTP_DISPOSITION_DISCARD;
+}
+
+/* Unexpected COOKIE-ECHO handler lost chunk (Table 2, action 'D')
+ *
+ * Section 5.2.4
+ *
+ * D) When both local and remote tags match the endpoint should always
+ * enter the ESTABLISHED state, if it has not already done so.
+ */
+/* This case represents an initialization collision. */
+static sctp_disposition_t sctp_sf_do_dupcook_d(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ struct sctp_chunk *chunk,
+ sctp_cmd_seq_t *commands,
+ struct sctp_association *new_asoc)
+{
+ struct sctp_ulpevent *ev = NULL;
+ struct sctp_chunk *repl;
+
+ /* Clarification from Implementor's Guide:
+ * D) When both local and remote tags match the endpoint should
+ * enter the ESTABLISHED state, if it is in the COOKIE-ECHOED state.
+ * It should stop any cookie timer that may be running and send
+ * a COOKIE ACK.
+ */
+
+ /* Don't accidentally move back into established state. */
+ if (asoc->state < SCTP_STATE_ESTABLISHED) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T1_COOKIE));
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_ESTABLISHED));
+ SCTP_INC_STATS(SctpCurrEstab);
+ sctp_add_cmd_sf(commands, SCTP_CMD_HB_TIMERS_START,
+ SCTP_NULL());
+
+ /* RFC 2960 5.1 Normal Establishment of an Association
+ *
+ * D) IMPLEMENTATION NOTE: An implementation may choose
+ * to send the Communication Up notification to the
+ * SCTP user upon reception of a valid COOKIE
+ * ECHO chunk.
+ */
+ ev = sctp_ulpevent_make_assoc_change(new_asoc, 0,
+ SCTP_COMM_UP, 0,
+ new_asoc->c.sinit_num_ostreams,
+ new_asoc->c.sinit_max_instreams,
+ GFP_ATOMIC);
+ if (!ev)
+ goto nomem;
+ sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP,
+ SCTP_ULPEVENT(ev));
+
+ /* Sockets API Draft Section 5.3.1.6
+ * When a peer sends a Adaption Layer Indication parameter,
+ * SCTP delivers this notification to inform the application
+ * that of the peers requested adaption layer.
+ */
+ if (new_asoc->peer.adaption_ind) {
+ ev = sctp_ulpevent_make_adaption_indication(new_asoc,
+ GFP_ATOMIC);
+ if (!ev)
+ goto nomem;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP,
+ SCTP_ULPEVENT(ev));
+ }
+ }
+ sctp_add_cmd_sf(commands, SCTP_CMD_TRANSMIT, SCTP_NULL());
+
+ repl = sctp_make_cookie_ack(new_asoc, chunk);
+ if (!repl)
+ goto nomem;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(repl));
+ sctp_add_cmd_sf(commands, SCTP_CMD_TRANSMIT, SCTP_NULL());
+
+ return SCTP_DISPOSITION_CONSUME;
+
+nomem:
+ if (ev)
+ sctp_ulpevent_free(ev);
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * Handle a duplicate COOKIE-ECHO. This usually means a cookie-carrying
+ * chunk was retransmitted and then delayed in the network.
+ *
+ * Section: 5.2.4 Handle a COOKIE ECHO when a TCB exists
+ *
+ * Verification Tag: None. Do cookie validation.
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_do_5_2_4_dupcook(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ sctp_disposition_t retval;
+ struct sctp_chunk *chunk = arg;
+ struct sctp_association *new_asoc;
+ int error = 0;
+ char action;
+ struct sctp_chunk *err_chk_p;
+
+ /* Make sure that the chunk has a valid length from the protocol
+ * perspective. In this case check to make sure we have at least
+ * enough for the chunk header. Cookie length verification is
+ * done later.
+ */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_chunkhdr_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ /* "Decode" the chunk. We have no optional parameters so we
+ * are in good shape.
+ */
+ chunk->subh.cookie_hdr = (struct sctp_signed_cookie *)chunk->skb->data;
+ skb_pull(chunk->skb, ntohs(chunk->chunk_hdr->length) -
+ sizeof(sctp_chunkhdr_t));
+
+ /* In RFC 2960 5.2.4 3, if both Verification Tags in the State Cookie
+ * of a duplicate COOKIE ECHO match the Verification Tags of the
+ * current association, consider the State Cookie valid even if
+ * the lifespan is exceeded.
+ */
+ new_asoc = sctp_unpack_cookie(ep, asoc, chunk, GFP_ATOMIC, &error,
+ &err_chk_p);
+
+ /* FIXME:
+ * If the re-build failed, what is the proper error path
+ * from here?
+ *
+ * [We should abort the association. --piggy]
+ */
+ if (!new_asoc) {
+ /* FIXME: Several errors are possible. A bad cookie should
+ * be silently discarded, but think about logging it too.
+ */
+ switch (error) {
+ case -SCTP_IERROR_NOMEM:
+ goto nomem;
+
+ case -SCTP_IERROR_STALE_COOKIE:
+ sctp_send_stale_cookie_err(ep, asoc, chunk, commands,
+ err_chk_p);
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+ case -SCTP_IERROR_BAD_SIG:
+ default:
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+ };
+ }
+
+ /* Compare the tie_tag in cookie with the verification tag of
+ * current association.
+ */
+ action = sctp_tietags_compare(new_asoc, asoc);
+
+ switch (action) {
+ case 'A': /* Association restart. */
+ retval = sctp_sf_do_dupcook_a(ep, asoc, chunk, commands,
+ new_asoc);
+ break;
+
+ case 'B': /* Collision case B. */
+ retval = sctp_sf_do_dupcook_b(ep, asoc, chunk, commands,
+ new_asoc);
+ break;
+
+ case 'C': /* Collision case C. */
+ retval = sctp_sf_do_dupcook_c(ep, asoc, chunk, commands,
+ new_asoc);
+ break;
+
+ case 'D': /* Collision case D. */
+ retval = sctp_sf_do_dupcook_d(ep, asoc, chunk, commands,
+ new_asoc);
+ break;
+
+ default: /* Discard packet for all others. */
+ retval = sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+ break;
+ };
+
+ /* Delete the tempory new association. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_ASOC, SCTP_ASOC(new_asoc));
+ sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB, SCTP_NULL());
+
+ return retval;
+
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * Process an ABORT. (SHUTDOWN-PENDING state)
+ *
+ * See sctp_sf_do_9_1_abort().
+ */
+sctp_disposition_t sctp_sf_shutdown_pending_abort(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+
+ if (!sctp_vtag_verify_either(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Make sure that the ABORT chunk has a valid length.
+ * Since this is an ABORT chunk, we have to discard it
+ * because of the following text:
+ * RFC 2960, Section 3.3.7
+ * If an endpoint receives an ABORT with a format error or for an
+ * association that doesn't exist, it MUST silently discard it.
+ * Becasue the length is "invalid", we can't really discard just
+ * as we do not know its true length. So, to be safe, discard the
+ * packet.
+ */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_abort_chunk_t)))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Stop the T5-shutdown guard timer. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T5_SHUTDOWN_GUARD));
+
+ return sctp_sf_do_9_1_abort(ep, asoc, type, arg, commands);
+}
+
+/*
+ * Process an ABORT. (SHUTDOWN-SENT state)
+ *
+ * See sctp_sf_do_9_1_abort().
+ */
+sctp_disposition_t sctp_sf_shutdown_sent_abort(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+
+ if (!sctp_vtag_verify_either(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Make sure that the ABORT chunk has a valid length.
+ * Since this is an ABORT chunk, we have to discard it
+ * because of the following text:
+ * RFC 2960, Section 3.3.7
+ * If an endpoint receives an ABORT with a format error or for an
+ * association that doesn't exist, it MUST silently discard it.
+ * Becasue the length is "invalid", we can't really discard just
+ * as we do not know its true length. So, to be safe, discard the
+ * packet.
+ */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_abort_chunk_t)))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Stop the T2-shutdown timer. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T2_SHUTDOWN));
+
+ /* Stop the T5-shutdown guard timer. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T5_SHUTDOWN_GUARD));
+
+ return sctp_sf_do_9_1_abort(ep, asoc, type, arg, commands);
+}
+
+/*
+ * Process an ABORT. (SHUTDOWN-ACK-SENT state)
+ *
+ * See sctp_sf_do_9_1_abort().
+ */
+sctp_disposition_t sctp_sf_shutdown_ack_sent_abort(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ /* The same T2 timer, so we should be able to use
+ * common function with the SHUTDOWN-SENT state.
+ */
+ return sctp_sf_shutdown_sent_abort(ep, asoc, type, arg, commands);
+}
+
+/*
+ * Handle an Error received in COOKIE_ECHOED state.
+ *
+ * Only handle the error type of stale COOKIE Error, the other errors will
+ * be ignored.
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_cookie_echoed_err(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ sctp_errhdr_t *err;
+
+ if (!sctp_vtag_verify(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Make sure that the ERROR chunk has a valid length.
+ * The parameter walking depends on this as well.
+ */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_operr_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ /* Process the error here */
+ /* FUTURE FIXME: When PR-SCTP related and other optional
+ * parms are emitted, this will have to change to handle multiple
+ * errors.
+ */
+ sctp_walk_errors(err, chunk->chunk_hdr) {
+ if (SCTP_ERROR_STALE_COOKIE == err->cause)
+ return sctp_sf_do_5_2_6_stale(ep, asoc, type,
+ arg, commands);
+ }
+
+ /* It is possible to have malformed error causes, and that
+ * will cause us to end the walk early. However, since
+ * we are discarding the packet, there should be no adverse
+ * affects.
+ */
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+}
+
+/*
+ * Handle a Stale COOKIE Error
+ *
+ * Section: 5.2.6 Handle Stale COOKIE Error
+ * If the association is in the COOKIE-ECHOED state, the endpoint may elect
+ * one of the following three alternatives.
+ * ...
+ * 3) Send a new INIT chunk to the endpoint, adding a Cookie
+ * Preservative parameter requesting an extension to the lifetime of
+ * the State Cookie. When calculating the time extension, an
+ * implementation SHOULD use the RTT information measured based on the
+ * previous COOKIE ECHO / ERROR exchange, and should add no more
+ * than 1 second beyond the measured RTT, due to long State Cookie
+ * lifetimes making the endpoint more subject to a replay attack.
+ *
+ * Verification Tag: Not explicit, but safe to ignore.
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+static sctp_disposition_t sctp_sf_do_5_2_6_stale(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ time_t stale;
+ sctp_cookie_preserve_param_t bht;
+ sctp_errhdr_t *err;
+ struct sctp_chunk *reply;
+ struct sctp_bind_addr *bp;
+ int attempts;
+
+ attempts = asoc->counters[SCTP_COUNTER_INIT_ERROR] + 1;
+
+ if (attempts >= asoc->max_init_attempts) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED,
+ SCTP_U32(SCTP_ERROR_STALE_COOKIE));
+ return SCTP_DISPOSITION_DELETE_TCB;
+ }
+
+ err = (sctp_errhdr_t *)(chunk->skb->data);
+
+ /* When calculating the time extension, an implementation
+ * SHOULD use the RTT information measured based on the
+ * previous COOKIE ECHO / ERROR exchange, and should add no
+ * more than 1 second beyond the measured RTT, due to long
+ * State Cookie lifetimes making the endpoint more subject to
+ * a replay attack.
+ * Measure of Staleness's unit is usec. (1/1000000 sec)
+ * Suggested Cookie Life-span Increment's unit is msec.
+ * (1/1000 sec)
+ * In general, if you use the suggested cookie life, the value
+ * found in the field of measure of staleness should be doubled
+ * to give ample time to retransmit the new cookie and thus
+ * yield a higher probability of success on the reattempt.
+ */
+ stale = ntohl(*(suseconds_t *)((u8 *)err + sizeof(sctp_errhdr_t)));
+ stale = (stale * 2) / 1000;
+
+ bht.param_hdr.type = SCTP_PARAM_COOKIE_PRESERVATIVE;
+ bht.param_hdr.length = htons(sizeof(bht));
+ bht.lifespan_increment = htonl(stale);
+
+ /* Build that new INIT chunk. */
+ bp = (struct sctp_bind_addr *) &asoc->base.bind_addr;
+ reply = sctp_make_init(asoc, bp, GFP_ATOMIC, sizeof(bht));
+ if (!reply)
+ goto nomem;
+
+ sctp_addto_chunk(reply, sizeof(bht), &bht);
+
+ /* Clear peer's init_tag cached in assoc as we are sending a new INIT */
+ sctp_add_cmd_sf(commands, SCTP_CMD_CLEAR_INIT_TAG, SCTP_NULL());
+
+ /* Stop pending T3-rtx and heartbeat timers */
+ sctp_add_cmd_sf(commands, SCTP_CMD_T3_RTX_TIMERS_STOP, SCTP_NULL());
+ sctp_add_cmd_sf(commands, SCTP_CMD_HB_TIMERS_STOP, SCTP_NULL());
+
+ /* Delete non-primary peer ip addresses since we are transitioning
+ * back to the COOKIE-WAIT state
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_DEL_NON_PRIMARY, SCTP_NULL());
+
+ /* If we've sent any data bundled with COOKIE-ECHO we will need to
+ * resend
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_RETRAN,
+ SCTP_TRANSPORT(asoc->peer.primary_path));
+
+ /* Cast away the const modifier, as we want to just
+ * rerun it through as a sideffect.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_COUNTER_INC,
+ SCTP_COUNTER(SCTP_COUNTER_INIT_ERROR));
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T1_COOKIE));
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_COOKIE_WAIT));
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_START,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T1_INIT));
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply));
+
+ return SCTP_DISPOSITION_CONSUME;
+
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * Process an ABORT.
+ *
+ * Section: 9.1
+ * After checking the Verification Tag, the receiving endpoint shall
+ * remove the association from its record, and shall report the
+ * termination to its upper layer.
+ *
+ * Verification Tag: 8.5.1 Exceptions in Verification Tag Rules
+ * B) Rules for packet carrying ABORT:
+ *
+ * - The endpoint shall always fill in the Verification Tag field of the
+ * outbound packet with the destination endpoint's tag value if it
+ * is known.
+ *
+ * - If the ABORT is sent in response to an OOTB packet, the endpoint
+ * MUST follow the procedure described in Section 8.4.
+ *
+ * - The receiver MUST accept the packet if the Verification Tag
+ * matches either its own tag, OR the tag of its peer. Otherwise, the
+ * receiver MUST silently discard the packet and take no further
+ * action.
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_do_9_1_abort(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ unsigned len;
+ __u16 error = SCTP_ERROR_NO_ERROR;
+
+ if (!sctp_vtag_verify_either(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Make sure that the ABORT chunk has a valid length.
+ * Since this is an ABORT chunk, we have to discard it
+ * because of the following text:
+ * RFC 2960, Section 3.3.7
+ * If an endpoint receives an ABORT with a format error or for an
+ * association that doesn't exist, it MUST silently discard it.
+ * Becasue the length is "invalid", we can't really discard just
+ * as we do not know its true length. So, to be safe, discard the
+ * packet.
+ */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_abort_chunk_t)))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* See if we have an error cause code in the chunk. */
+ len = ntohs(chunk->chunk_hdr->length);
+ if (len >= sizeof(struct sctp_chunkhdr) + sizeof(struct sctp_errhdr))
+ error = ((sctp_errhdr_t *)chunk->skb->data)->cause;
+
+ /* ASSOC_FAILED will DELETE_TCB. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED, SCTP_U32(error));
+ SCTP_INC_STATS(SctpAborteds);
+ SCTP_DEC_STATS(SctpCurrEstab);
+
+ return SCTP_DISPOSITION_ABORT;
+}
+
+/*
+ * Process an ABORT. (COOKIE-WAIT state)
+ *
+ * See sctp_sf_do_9_1_abort() above.
+ */
+sctp_disposition_t sctp_sf_cookie_wait_abort(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ unsigned len;
+ __u16 error = SCTP_ERROR_NO_ERROR;
+
+ if (!sctp_vtag_verify_either(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Make sure that the ABORT chunk has a valid length.
+ * Since this is an ABORT chunk, we have to discard it
+ * because of the following text:
+ * RFC 2960, Section 3.3.7
+ * If an endpoint receives an ABORT with a format error or for an
+ * association that doesn't exist, it MUST silently discard it.
+ * Becasue the length is "invalid", we can't really discard just
+ * as we do not know its true length. So, to be safe, discard the
+ * packet.
+ */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_abort_chunk_t)))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* See if we have an error cause code in the chunk. */
+ len = ntohs(chunk->chunk_hdr->length);
+ if (len >= sizeof(struct sctp_chunkhdr) + sizeof(struct sctp_errhdr))
+ error = ((sctp_errhdr_t *)chunk->skb->data)->cause;
+
+ sctp_stop_t1_and_abort(commands, error);
+
+ return SCTP_DISPOSITION_ABORT;
+}
+
+/*
+ * Process an incoming ICMP as an ABORT. (COOKIE-WAIT state)
+ */
+sctp_disposition_t sctp_sf_cookie_wait_icmp_abort(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ sctp_stop_t1_and_abort(commands, SCTP_ERROR_NO_ERROR);
+ return SCTP_DISPOSITION_ABORT;
+}
+
+/*
+ * Process an ABORT. (COOKIE-ECHOED state)
+ */
+sctp_disposition_t sctp_sf_cookie_echoed_abort(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ /* There is a single T1 timer, so we should be able to use
+ * common function with the COOKIE-WAIT state.
+ */
+ return sctp_sf_cookie_wait_abort(ep, asoc, type, arg, commands);
+}
+
+/*
+ * Stop T1 timer and abort association with "INIT failed".
+ *
+ * This is common code called by several sctp_sf_*_abort() functions above.
+ */
+void sctp_stop_t1_and_abort(sctp_cmd_seq_t *commands, __u16 error)
+{
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_CLOSED));
+ SCTP_INC_STATS(SctpAborteds);
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T1_INIT));
+ /* CMD_INIT_FAILED will DELETE_TCB. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED,
+ SCTP_U32(error));
+}
+
+/*
+ * sctp_sf_do_9_2_shut
+ *
+ * Section: 9.2
+ * Upon the reception of the SHUTDOWN, the peer endpoint shall
+ * - enter the SHUTDOWN-RECEIVED state,
+ *
+ * - stop accepting new data from its SCTP user
+ *
+ * - verify, by checking the Cumulative TSN Ack field of the chunk,
+ * that all its outstanding DATA chunks have been received by the
+ * SHUTDOWN sender.
+ *
+ * Once an endpoint as reached the SHUTDOWN-RECEIVED state it MUST NOT
+ * send a SHUTDOWN in response to a ULP request. And should discard
+ * subsequent SHUTDOWN chunks.
+ *
+ * If there are still outstanding DATA chunks left, the SHUTDOWN
+ * receiver shall continue to follow normal data transmission
+ * procedures defined in Section 6 until all outstanding DATA chunks
+ * are acknowledged; however, the SHUTDOWN receiver MUST NOT accept
+ * new data from its SCTP user.
+ *
+ * Verification Tag: 8.5 Verification Tag [Normal verification]
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_do_9_2_shutdown(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ sctp_shutdownhdr_t *sdh;
+ sctp_disposition_t disposition;
+ struct sctp_ulpevent *ev;
+
+ if (!sctp_vtag_verify(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Make sure that the SHUTDOWN chunk has a valid length. */
+ if (!sctp_chunk_length_valid(chunk,
+ sizeof(struct sctp_shutdown_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ /* Convert the elaborate header. */
+ sdh = (sctp_shutdownhdr_t *)chunk->skb->data;
+ skb_pull(chunk->skb, sizeof(sctp_shutdownhdr_t));
+ chunk->subh.shutdown_hdr = sdh;
+
+ /* Upon the reception of the SHUTDOWN, the peer endpoint shall
+ * - enter the SHUTDOWN-RECEIVED state,
+ * - stop accepting new data from its SCTP user
+ *
+ * [This is implicit in the new state.]
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_SHUTDOWN_RECEIVED));
+ disposition = SCTP_DISPOSITION_CONSUME;
+
+ if (sctp_outq_is_empty(&asoc->outqueue)) {
+ disposition = sctp_sf_do_9_2_shutdown_ack(ep, asoc, type,
+ arg, commands);
+ }
+
+ if (SCTP_DISPOSITION_NOMEM == disposition)
+ goto out;
+
+ /* - verify, by checking the Cumulative TSN Ack field of the
+ * chunk, that all its outstanding DATA chunks have been
+ * received by the SHUTDOWN sender.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_PROCESS_CTSN,
+ SCTP_U32(chunk->subh.shutdown_hdr->cum_tsn_ack));
+
+ /* API 5.3.1.5 SCTP_SHUTDOWN_EVENT
+ * When a peer sends a SHUTDOWN, SCTP delivers this notification to
+ * inform the application that it should cease sending data.
+ */
+ ev = sctp_ulpevent_make_shutdown_event(asoc, 0, GFP_ATOMIC);
+ if (!ev) {
+ disposition = SCTP_DISPOSITION_NOMEM;
+ goto out;
+ }
+ sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP, SCTP_ULPEVENT(ev));
+
+out:
+ return disposition;
+}
+
+/* RFC 2960 9.2
+ * If an endpoint is in SHUTDOWN-ACK-SENT state and receives an INIT chunk
+ * (e.g., if the SHUTDOWN COMPLETE was lost) with source and destination
+ * transport addresses (either in the IP addresses or in the INIT chunk)
+ * that belong to this association, it should discard the INIT chunk and
+ * retransmit the SHUTDOWN ACK chunk.
+ */
+sctp_disposition_t sctp_sf_do_9_2_reshutack(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = (struct sctp_chunk *) arg;
+ struct sctp_chunk *reply;
+
+ /* Since we are not going to really process this INIT, there
+ * is no point in verifying chunk boundries. Just generate
+ * the SHUTDOWN ACK.
+ */
+ reply = sctp_make_shutdown_ack(asoc, chunk);
+ if (NULL == reply)
+ goto nomem;
+
+ /* Set the transport for the SHUTDOWN ACK chunk and the timeout for
+ * the T2-SHUTDOWN timer.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_SETUP_T2, SCTP_CHUNK(reply));
+
+ /* and restart the T2-shutdown timer. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_RESTART,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T2_SHUTDOWN));
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply));
+
+ return SCTP_DISPOSITION_CONSUME;
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * sctp_sf_do_ecn_cwr
+ *
+ * Section: Appendix A: Explicit Congestion Notification
+ *
+ * CWR:
+ *
+ * RFC 2481 details a specific bit for a sender to send in the header of
+ * its next outbound TCP segment to indicate to its peer that it has
+ * reduced its congestion window. This is termed the CWR bit. For
+ * SCTP the same indication is made by including the CWR chunk.
+ * This chunk contains one data element, i.e. the TSN number that
+ * was sent in the ECNE chunk. This element represents the lowest
+ * TSN number in the datagram that was originally marked with the
+ * CE bit.
+ *
+ * Verification Tag: 8.5 Verification Tag [Normal verification]
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_do_ecn_cwr(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ sctp_cwrhdr_t *cwr;
+ struct sctp_chunk *chunk = arg;
+
+ if (!sctp_vtag_verify(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_ecne_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ cwr = (sctp_cwrhdr_t *) chunk->skb->data;
+ skb_pull(chunk->skb, sizeof(sctp_cwrhdr_t));
+
+ cwr->lowest_tsn = ntohl(cwr->lowest_tsn);
+
+ /* Does this CWR ack the last sent congestion notification? */
+ if (TSN_lte(asoc->last_ecne_tsn, cwr->lowest_tsn)) {
+ /* Stop sending ECNE. */
+ sctp_add_cmd_sf(commands,
+ SCTP_CMD_ECN_CWR,
+ SCTP_U32(cwr->lowest_tsn));
+ }
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/*
+ * sctp_sf_do_ecne
+ *
+ * Section: Appendix A: Explicit Congestion Notification
+ *
+ * ECN-Echo
+ *
+ * RFC 2481 details a specific bit for a receiver to send back in its
+ * TCP acknowledgements to notify the sender of the Congestion
+ * Experienced (CE) bit having arrived from the network. For SCTP this
+ * same indication is made by including the ECNE chunk. This chunk
+ * contains one data element, i.e. the lowest TSN associated with the IP
+ * datagram marked with the CE bit.....
+ *
+ * Verification Tag: 8.5 Verification Tag [Normal verification]
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_do_ecne(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ sctp_ecnehdr_t *ecne;
+ struct sctp_chunk *chunk = arg;
+
+ if (!sctp_vtag_verify(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_ecne_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ ecne = (sctp_ecnehdr_t *) chunk->skb->data;
+ skb_pull(chunk->skb, sizeof(sctp_ecnehdr_t));
+
+ /* If this is a newer ECNE than the last CWR packet we sent out */
+ sctp_add_cmd_sf(commands, SCTP_CMD_ECN_ECNE,
+ SCTP_U32(ntohl(ecne->lowest_tsn)));
+
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/*
+ * Section: 6.2 Acknowledgement on Reception of DATA Chunks
+ *
+ * The SCTP endpoint MUST always acknowledge the reception of each valid
+ * DATA chunk.
+ *
+ * The guidelines on delayed acknowledgement algorithm specified in
+ * Section 4.2 of [RFC2581] SHOULD be followed. Specifically, an
+ * acknowledgement SHOULD be generated for at least every second packet
+ * (not every second DATA chunk) received, and SHOULD be generated within
+ * 200 ms of the arrival of any unacknowledged DATA chunk. In some
+ * situations it may be beneficial for an SCTP transmitter to be more
+ * conservative than the algorithms detailed in this document allow.
+ * However, an SCTP transmitter MUST NOT be more aggressive than the
+ * following algorithms allow.
+ *
+ * A SCTP receiver MUST NOT generate more than one SACK for every
+ * incoming packet, other than to update the offered window as the
+ * receiving application consumes new data.
+ *
+ * Verification Tag: 8.5 Verification Tag [Normal verification]
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_eat_data_6_2(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ int error;
+
+ if (!sctp_vtag_verify(chunk, asoc)) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPORT_BAD_TAG,
+ SCTP_NULL());
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+ }
+
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_data_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ error = sctp_eat_data(asoc, chunk, commands );
+ switch (error) {
+ case SCTP_IERROR_NO_ERROR:
+ break;
+ case SCTP_IERROR_HIGH_TSN:
+ case SCTP_IERROR_BAD_STREAM:
+ goto discard_noforce;
+ case SCTP_IERROR_DUP_TSN:
+ case SCTP_IERROR_IGNORE_TSN:
+ goto discard_force;
+ case SCTP_IERROR_NO_DATA:
+ goto consume;
+ default:
+ BUG();
+ }
+
+ if (asoc->autoclose) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_RESTART,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_AUTOCLOSE));
+ }
+
+ /* If this is the last chunk in a packet, we need to count it
+ * toward sack generation. Note that we need to SACK every
+ * OTHER packet containing data chunks, EVEN IF WE DISCARD
+ * THEM. We elect to NOT generate SACK's if the chunk fails
+ * the verification tag test.
+ *
+ * RFC 2960 6.2 Acknowledgement on Reception of DATA Chunks
+ *
+ * The SCTP endpoint MUST always acknowledge the reception of
+ * each valid DATA chunk.
+ *
+ * The guidelines on delayed acknowledgement algorithm
+ * specified in Section 4.2 of [RFC2581] SHOULD be followed.
+ * Specifically, an acknowledgement SHOULD be generated for at
+ * least every second packet (not every second DATA chunk)
+ * received, and SHOULD be generated within 200 ms of the
+ * arrival of any unacknowledged DATA chunk. In some
+ * situations it may be beneficial for an SCTP transmitter to
+ * be more conservative than the algorithms detailed in this
+ * document allow. However, an SCTP transmitter MUST NOT be
+ * more aggressive than the following algorithms allow.
+ */
+ if (chunk->end_of_packet) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_GEN_SACK, SCTP_NOFORCE());
+
+ /* Start the SACK timer. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_RESTART,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_SACK));
+ }
+
+ return SCTP_DISPOSITION_CONSUME;
+
+discard_force:
+ /* RFC 2960 6.2 Acknowledgement on Reception of DATA Chunks
+ *
+ * When a packet arrives with duplicate DATA chunk(s) and with
+ * no new DATA chunk(s), the endpoint MUST immediately send a
+ * SACK with no delay. If a packet arrives with duplicate
+ * DATA chunk(s) bundled with new DATA chunks, the endpoint
+ * MAY immediately send a SACK. Normally receipt of duplicate
+ * DATA chunks will occur when the original SACK chunk was lost
+ * and the peer's RTO has expired. The duplicate TSN number(s)
+ * SHOULD be reported in the SACK as duplicate.
+ */
+ /* In our case, we split the MAY SACK advice up whether or not
+ * the last chunk is a duplicate.'
+ */
+ if (chunk->end_of_packet)
+ sctp_add_cmd_sf(commands, SCTP_CMD_GEN_SACK, SCTP_FORCE());
+ return SCTP_DISPOSITION_DISCARD;
+
+discard_noforce:
+ if (chunk->end_of_packet) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_GEN_SACK, SCTP_NOFORCE());
+
+ /* Start the SACK timer. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_RESTART,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_SACK));
+ }
+ return SCTP_DISPOSITION_DISCARD;
+consume:
+ return SCTP_DISPOSITION_CONSUME;
+
+}
+
+/*
+ * sctp_sf_eat_data_fast_4_4
+ *
+ * Section: 4 (4)
+ * (4) In SHUTDOWN-SENT state the endpoint MUST acknowledge any received
+ * DATA chunks without delay.
+ *
+ * Verification Tag: 8.5 Verification Tag [Normal verification]
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_eat_data_fast_4_4(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ int error;
+
+ if (!sctp_vtag_verify(chunk, asoc)) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPORT_BAD_TAG,
+ SCTP_NULL());
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+ }
+
+
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_data_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ error = sctp_eat_data(asoc, chunk, commands );
+ switch (error) {
+ case SCTP_IERROR_NO_ERROR:
+ case SCTP_IERROR_HIGH_TSN:
+ case SCTP_IERROR_DUP_TSN:
+ case SCTP_IERROR_IGNORE_TSN:
+ case SCTP_IERROR_BAD_STREAM:
+ break;
+ case SCTP_IERROR_NO_DATA:
+ goto consume;
+ default:
+ BUG();
+ }
+
+ /* Go a head and force a SACK, since we are shutting down. */
+
+ /* Implementor's Guide.
+ *
+ * While in SHUTDOWN-SENT state, the SHUTDOWN sender MUST immediately
+ * respond to each received packet containing one or more DATA chunk(s)
+ * with a SACK, a SHUTDOWN chunk, and restart the T2-shutdown timer
+ */
+ if (chunk->end_of_packet) {
+ /* We must delay the chunk creation since the cumulative
+ * TSN has not been updated yet.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_GEN_SHUTDOWN, SCTP_NULL());
+ sctp_add_cmd_sf(commands, SCTP_CMD_GEN_SACK, SCTP_FORCE());
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_RESTART,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T2_SHUTDOWN));
+ }
+
+consume:
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/*
+ * Section: 6.2 Processing a Received SACK
+ * D) Any time a SACK arrives, the endpoint performs the following:
+ *
+ * i) If Cumulative TSN Ack is less than the Cumulative TSN Ack Point,
+ * then drop the SACK. Since Cumulative TSN Ack is monotonically
+ * increasing, a SACK whose Cumulative TSN Ack is less than the
+ * Cumulative TSN Ack Point indicates an out-of-order SACK.
+ *
+ * ii) Set rwnd equal to the newly received a_rwnd minus the number
+ * of bytes still outstanding after processing the Cumulative TSN Ack
+ * and the Gap Ack Blocks.
+ *
+ * iii) If the SACK is missing a TSN that was previously
+ * acknowledged via a Gap Ack Block (e.g., the data receiver
+ * reneged on the data), then mark the corresponding DATA chunk
+ * as available for retransmit: Mark it as missing for fast
+ * retransmit as described in Section 7.2.4 and if no retransmit
+ * timer is running for the destination address to which the DATA
+ * chunk was originally transmitted, then T3-rtx is started for
+ * that destination address.
+ *
+ * Verification Tag: 8.5 Verification Tag [Normal verification]
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_eat_sack_6_2(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ sctp_sackhdr_t *sackh;
+ __u32 ctsn;
+
+ if (!sctp_vtag_verify(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Make sure that the SACK chunk has a valid length. */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_sack_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ /* Pull the SACK chunk from the data buffer */
+ sackh = sctp_sm_pull_sack(chunk);
+ /* Was this a bogus SACK? */
+ if (!sackh)
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+ chunk->subh.sack_hdr = sackh;
+ ctsn = ntohl(sackh->cum_tsn_ack);
+
+ /* i) If Cumulative TSN Ack is less than the Cumulative TSN
+ * Ack Point, then drop the SACK. Since Cumulative TSN
+ * Ack is monotonically increasing, a SACK whose
+ * Cumulative TSN Ack is less than the Cumulative TSN Ack
+ * Point indicates an out-of-order SACK.
+ */
+ if (TSN_lt(ctsn, asoc->ctsn_ack_point)) {
+ SCTP_DEBUG_PRINTK("ctsn %x\n", ctsn);
+ SCTP_DEBUG_PRINTK("ctsn_ack_point %x\n", asoc->ctsn_ack_point);
+ return SCTP_DISPOSITION_DISCARD;
+ }
+
+ /* Return this SACK for further processing. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_PROCESS_SACK, SCTP_SACKH(sackh));
+
+ /* Note: We do the rest of the work on the PROCESS_SACK
+ * sideeffect.
+ */
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/*
+ * Generate an ABORT in response to a packet.
+ *
+ * Section: 8.4 Handle "Out of the blue" Packets
+ *
+ * 8) The receiver should respond to the sender of the OOTB packet
+ * with an ABORT. When sending the ABORT, the receiver of the
+ * OOTB packet MUST fill in the Verification Tag field of the
+ * outbound packet with the value found in the Verification Tag
+ * field of the OOTB packet and set the T-bit in the Chunk Flags
+ * to indicate that no TCB was found. After sending this ABORT,
+ * the receiver of the OOTB packet shall discard the OOTB packet
+ * and take no further action.
+ *
+ * Verification Tag:
+ *
+ * The return value is the disposition of the chunk.
+*/
+sctp_disposition_t sctp_sf_tabort_8_4_8(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_packet *packet = NULL;
+ struct sctp_chunk *chunk = arg;
+ struct sctp_chunk *abort;
+
+ packet = sctp_ootb_pkt_new(asoc, chunk);
+
+ if (packet) {
+ /* Make an ABORT. The T bit will be set if the asoc
+ * is NULL.
+ */
+ abort = sctp_make_abort(asoc, chunk, 0);
+ if (!abort) {
+ sctp_ootb_pkt_free(packet);
+ return SCTP_DISPOSITION_NOMEM;
+ }
+
+ /* Set the skb to the belonging sock for accounting. */
+ abort->skb->sk = ep->base.sk;
+
+ sctp_packet_append_chunk(packet, abort);
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_SEND_PKT,
+ SCTP_PACKET(packet));
+
+ SCTP_INC_STATS(SctpOutCtrlChunks);
+
+ return SCTP_DISPOSITION_CONSUME;
+ }
+
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * Received an ERROR chunk from peer. Generate SCTP_REMOTE_ERROR
+ * event as ULP notification for each cause included in the chunk.
+ *
+ * API 5.3.1.3 - SCTP_REMOTE_ERROR
+ *
+ * The return value is the disposition of the chunk.
+*/
+sctp_disposition_t sctp_sf_operr_notify(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ struct sctp_ulpevent *ev;
+
+ if (!sctp_vtag_verify(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Make sure that the ERROR chunk has a valid length. */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_operr_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ while (chunk->chunk_end > chunk->skb->data) {
+ ev = sctp_ulpevent_make_remote_error(asoc, chunk, 0,
+ GFP_ATOMIC);
+ if (!ev)
+ goto nomem;
+
+ if (!sctp_add_cmd(commands, SCTP_CMD_EVENT_ULP,
+ SCTP_ULPEVENT(ev))) {
+ sctp_ulpevent_free(ev);
+ goto nomem;
+ }
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_PROCESS_OPERR,
+ SCTP_CHUNK(chunk));
+ }
+ return SCTP_DISPOSITION_CONSUME;
+
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * Process an inbound SHUTDOWN ACK.
+ *
+ * From Section 9.2:
+ * Upon the receipt of the SHUTDOWN ACK, the SHUTDOWN sender shall
+ * stop the T2-shutdown timer, send a SHUTDOWN COMPLETE chunk to its
+ * peer, and remove all record of the association.
+ *
+ * The return value is the disposition.
+ */
+sctp_disposition_t sctp_sf_do_9_2_final(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ struct sctp_chunk *reply;
+ struct sctp_ulpevent *ev;
+
+ if (!sctp_vtag_verify(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Make sure that the SHUTDOWN_ACK chunk has a valid length. */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_chunkhdr_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ /* 10.2 H) SHUTDOWN COMPLETE notification
+ *
+ * When SCTP completes the shutdown procedures (section 9.2) this
+ * notification is passed to the upper layer.
+ */
+ ev = sctp_ulpevent_make_assoc_change(asoc, 0, SCTP_SHUTDOWN_COMP,
+ 0, 0, 0, GFP_ATOMIC);
+ if (!ev)
+ goto nomem;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_EVENT_ULP, SCTP_ULPEVENT(ev));
+
+ /* Upon the receipt of the SHUTDOWN ACK, the SHUTDOWN sender shall
+ * stop the T2-shutdown timer,
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T2_SHUTDOWN));
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T5_SHUTDOWN_GUARD));
+
+ /* ...send a SHUTDOWN COMPLETE chunk to its peer, */
+ reply = sctp_make_shutdown_complete(asoc, chunk);
+ if (!reply)
+ goto nomem;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_CLOSED));
+ SCTP_INC_STATS(SctpShutdowns);
+ SCTP_DEC_STATS(SctpCurrEstab);
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply));
+
+ /* ...and remove all record of the association. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB, SCTP_NULL());
+ return SCTP_DISPOSITION_DELETE_TCB;
+
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * RFC 2960, 8.4 - Handle "Out of the blue" Packets
+ * 5) If the packet contains a SHUTDOWN ACK chunk, the receiver should
+ * respond to the sender of the OOTB packet with a SHUTDOWN COMPLETE.
+ * When sending the SHUTDOWN COMPLETE, the receiver of the OOTB
+ * packet must fill in the Verification Tag field of the outbound
+ * packet with the Verification Tag received in the SHUTDOWN ACK and
+ * set the T-bit in the Chunk Flags to indicate that no TCB was
+ * found. Otherwise,
+ *
+ * 8) The receiver should respond to the sender of the OOTB packet with
+ * an ABORT. When sending the ABORT, the receiver of the OOTB packet
+ * MUST fill in the Verification Tag field of the outbound packet
+ * with the value found in the Verification Tag field of the OOTB
+ * packet and set the T-bit in the Chunk Flags to indicate that no
+ * TCB was found. After sending this ABORT, the receiver of the OOTB
+ * packet shall discard the OOTB packet and take no further action.
+ */
+sctp_disposition_t sctp_sf_ootb(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ struct sk_buff *skb = chunk->skb;
+ sctp_chunkhdr_t *ch;
+ __u8 *ch_end;
+ int ootb_shut_ack = 0;
+
+ SCTP_INC_STATS(SctpOutOfBlues);
+
+ ch = (sctp_chunkhdr_t *) chunk->chunk_hdr;
+ do {
+ /* Break out if chunk length is less then minimal. */
+ if (ntohs(ch->length) < sizeof(sctp_chunkhdr_t))
+ break;
+
+ ch_end = ((__u8 *)ch) + WORD_ROUND(ntohs(ch->length));
+
+ if (SCTP_CID_SHUTDOWN_ACK == ch->type)
+ ootb_shut_ack = 1;
+
+ /* RFC 2960, Section 3.3.7
+ * Moreover, under any circumstances, an endpoint that
+ * receives an ABORT MUST NOT respond to that ABORT by
+ * sending an ABORT of its own.
+ */
+ if (SCTP_CID_ABORT == ch->type)
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ ch = (sctp_chunkhdr_t *) ch_end;
+ } while (ch_end < skb->tail);
+
+ if (ootb_shut_ack)
+ sctp_sf_shut_8_4_5(ep, asoc, type, arg, commands);
+ else
+ sctp_sf_tabort_8_4_8(ep, asoc, type, arg, commands);
+
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+}
+
+/*
+ * Handle an "Out of the blue" SHUTDOWN ACK.
+ *
+ * Section: 8.4 5)
+ * 5) If the packet contains a SHUTDOWN ACK chunk, the receiver should
+ * respond to the sender of the OOTB packet with a SHUTDOWN COMPLETE.
+ * When sending the SHUTDOWN COMPLETE, the receiver of the OOTB packet
+ * must fill in the Verification Tag field of the outbound packet with
+ * the Verification Tag received in the SHUTDOWN ACK and set the
+ * T-bit in the Chunk Flags to indicate that no TCB was found.
+ *
+ * Inputs
+ * (endpoint, asoc, type, arg, commands)
+ *
+ * Outputs
+ * (sctp_disposition_t)
+ *
+ * The return value is the disposition of the chunk.
+ */
+static sctp_disposition_t sctp_sf_shut_8_4_5(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_packet *packet = NULL;
+ struct sctp_chunk *chunk = arg;
+ struct sctp_chunk *shut;
+
+ packet = sctp_ootb_pkt_new(asoc, chunk);
+
+ if (packet) {
+ /* Make an SHUTDOWN_COMPLETE.
+ * The T bit will be set if the asoc is NULL.
+ */
+ shut = sctp_make_shutdown_complete(asoc, chunk);
+ if (!shut) {
+ sctp_ootb_pkt_free(packet);
+ return SCTP_DISPOSITION_NOMEM;
+ }
+
+ /* Set the skb to the belonging sock for accounting. */
+ shut->skb->sk = ep->base.sk;
+
+ sctp_packet_append_chunk(packet, shut);
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_SEND_PKT,
+ SCTP_PACKET(packet));
+
+ SCTP_INC_STATS(SctpOutCtrlChunks);
+
+ /* If the chunk length is invalid, we don't want to process
+ * the reset of the packet.
+ */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_chunkhdr_t)))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ return SCTP_DISPOSITION_CONSUME;
+ }
+
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * Handle SHUTDOWN ACK in COOKIE_ECHOED or COOKIE_WAIT state.
+ *
+ * Verification Tag: 8.5.1 E) Rules for packet carrying a SHUTDOWN ACK
+ * If the receiver is in COOKIE-ECHOED or COOKIE-WAIT state the
+ * procedures in section 8.4 SHOULD be followed, in other words it
+ * should be treated as an Out Of The Blue packet.
+ * [This means that we do NOT check the Verification Tag on these
+ * chunks. --piggy ]
+ *
+ */
+sctp_disposition_t sctp_sf_do_8_5_1_E_sa(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ /* Although we do have an association in this case, it corresponds
+ * to a restarted association. So the packet is treated as an OOTB
+ * packet and the state function that handles OOTB SHUTDOWN_ACK is
+ * called with a NULL association.
+ */
+ return sctp_sf_shut_8_4_5(ep, NULL, type, arg, commands);
+}
+
+/* ADDIP Section 4.2 Upon reception of an ASCONF Chunk. */
+sctp_disposition_t sctp_sf_do_asconf(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type, void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ struct sctp_chunk *asconf_ack = NULL;
+ sctp_addiphdr_t *hdr;
+ __u32 serial;
+
+ if (!sctp_vtag_verify(chunk, asoc)) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPORT_BAD_TAG,
+ SCTP_NULL());
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+ }
+
+ /* Make sure that the ASCONF ADDIP chunk has a valid length. */
+ if (!sctp_chunk_length_valid(chunk, sizeof(sctp_addip_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ hdr = (sctp_addiphdr_t *)chunk->skb->data;
+ serial = ntohl(hdr->serial);
+
+ /* ADDIP 4.2 C1) Compare the value of the serial number to the value
+ * the endpoint stored in a new association variable
+ * 'Peer-Serial-Number'.
+ */
+ if (serial == asoc->peer.addip_serial + 1) {
+ /* ADDIP 4.2 C2) If the value found in the serial number is
+ * equal to the ('Peer-Serial-Number' + 1), the endpoint MUST
+ * do V1-V5.
+ */
+ asconf_ack = sctp_process_asconf((struct sctp_association *)
+ asoc, chunk);
+ if (!asconf_ack)
+ return SCTP_DISPOSITION_NOMEM;
+ } else if (serial == asoc->peer.addip_serial) {
+ /* ADDIP 4.2 C3) If the value found in the serial number is
+ * equal to the value stored in the 'Peer-Serial-Number'
+ * IMPLEMENTATION NOTE: As an optimization a receiver may wish
+ * to save the last ASCONF-ACK for some predetermined period of
+ * time and instead of re-processing the ASCONF (with the same
+ * serial number) it may just re-transmit the ASCONF-ACK.
+ */
+ if (asoc->addip_last_asconf_ack)
+ asconf_ack = asoc->addip_last_asconf_ack;
+ else
+ return SCTP_DISPOSITION_DISCARD;
+ } else {
+ /* ADDIP 4.2 C4) Otherwise, the ASCONF Chunk is discarded since
+ * it must be either a stale packet or from an attacker.
+ */
+ return SCTP_DISPOSITION_DISCARD;
+ }
+
+ /* ADDIP 4.2 C5) In both cases C2 and C3 the ASCONF-ACK MUST be sent
+ * back to the source address contained in the IP header of the ASCONF
+ * being responded to.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(asconf_ack));
+
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/*
+ * ADDIP Section 4.3 General rules for address manipulation
+ * When building TLV parameters for the ASCONF Chunk that will add or
+ * delete IP addresses the D0 to D13 rules should be applied:
+ */
+sctp_disposition_t sctp_sf_do_asconf_ack(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type, void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *asconf_ack = arg;
+ struct sctp_chunk *last_asconf = asoc->addip_last_asconf;
+ struct sctp_chunk *abort;
+ sctp_addiphdr_t *addip_hdr;
+ __u32 sent_serial, rcvd_serial;
+
+ if (!sctp_vtag_verify(asconf_ack, asoc)) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPORT_BAD_TAG,
+ SCTP_NULL());
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+ }
+
+ /* Make sure that the ADDIP chunk has a valid length. */
+ if (!sctp_chunk_length_valid(asconf_ack, sizeof(sctp_addip_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ addip_hdr = (sctp_addiphdr_t *)asconf_ack->skb->data;
+ rcvd_serial = ntohl(addip_hdr->serial);
+
+ if (last_asconf) {
+ addip_hdr = (sctp_addiphdr_t *)last_asconf->subh.addip_hdr;
+ sent_serial = ntohl(addip_hdr->serial);
+ } else {
+ sent_serial = asoc->addip_serial - 1;
+ }
+
+ /* D0) If an endpoint receives an ASCONF-ACK that is greater than or
+ * equal to the next serial number to be used but no ASCONF chunk is
+ * outstanding the endpoint MUST ABORT the association. Note that a
+ * sequence number is greater than if it is no more than 2^^31-1
+ * larger than the current sequence number (using serial arithmetic).
+ */
+ if (ADDIP_SERIAL_gte(rcvd_serial, sent_serial + 1) &&
+ !(asoc->addip_last_asconf)) {
+ abort = sctp_make_abort(asoc, asconf_ack,
+ sizeof(sctp_errhdr_t));
+ if (abort) {
+ sctp_init_cause(abort, SCTP_ERROR_ASCONF_ACK, NULL, 0);
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(abort));
+ }
+ /* We are going to ABORT, so we might as well stop
+ * processing the rest of the chunks in the packet.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T4_RTO));
+ sctp_add_cmd_sf(commands, SCTP_CMD_DISCARD_PACKET,SCTP_NULL());
+ sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
+ SCTP_U32(SCTP_ERROR_ASCONF_ACK));
+ SCTP_INC_STATS(SctpAborteds);
+ SCTP_DEC_STATS(SctpCurrEstab);
+ return SCTP_DISPOSITION_ABORT;
+ }
+
+ if ((rcvd_serial == sent_serial) && asoc->addip_last_asconf) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T4_RTO));
+
+ if (!sctp_process_asconf_ack((struct sctp_association *)asoc,
+ asconf_ack))
+ return SCTP_DISPOSITION_CONSUME;
+
+ abort = sctp_make_abort(asoc, asconf_ack,
+ sizeof(sctp_errhdr_t));
+ if (abort) {
+ sctp_init_cause(abort, SCTP_ERROR_RSRC_LOW, NULL, 0);
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(abort));
+ }
+ /* We are going to ABORT, so we might as well stop
+ * processing the rest of the chunks in the packet.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_DISCARD_PACKET,SCTP_NULL());
+ sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
+ SCTP_U32(SCTP_ERROR_ASCONF_ACK));
+ SCTP_INC_STATS(SctpAborteds);
+ SCTP_DEC_STATS(SctpCurrEstab);
+ return SCTP_DISPOSITION_ABORT;
+ }
+
+ return SCTP_DISPOSITION_DISCARD;
+}
+
+/*
+ * PR-SCTP Section 3.6 Receiver Side Implementation of PR-SCTP
+ *
+ * When a FORWARD TSN chunk arrives, the data receiver MUST first update
+ * its cumulative TSN point to the value carried in the FORWARD TSN
+ * chunk, and then MUST further advance its cumulative TSN point locally
+ * if possible.
+ * After the above processing, the data receiver MUST stop reporting any
+ * missing TSNs earlier than or equal to the new cumulative TSN point.
+ *
+ * Verification Tag: 8.5 Verification Tag [Normal verification]
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_eat_fwd_tsn(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ struct sctp_fwdtsn_hdr *fwdtsn_hdr;
+ __u16 len;
+ __u32 tsn;
+
+ if (!sctp_vtag_verify(chunk, asoc)) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPORT_BAD_TAG,
+ SCTP_NULL());
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+ }
+
+ /* Make sure that the FORWARD_TSN chunk has valid length. */
+ if (!sctp_chunk_length_valid(chunk, sizeof(struct sctp_fwdtsn_chunk)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ fwdtsn_hdr = (struct sctp_fwdtsn_hdr *)chunk->skb->data;
+ chunk->subh.fwdtsn_hdr = fwdtsn_hdr;
+ len = ntohs(chunk->chunk_hdr->length);
+ len -= sizeof(struct sctp_chunkhdr);
+ skb_pull(chunk->skb, len);
+
+ tsn = ntohl(fwdtsn_hdr->new_cum_tsn);
+ SCTP_DEBUG_PRINTK("%s: TSN 0x%x.\n", __FUNCTION__, tsn);
+
+ /* The TSN is too high--silently discard the chunk and count on it
+ * getting retransmitted later.
+ */
+ if (sctp_tsnmap_check(&asoc->peer.tsn_map, tsn) < 0)
+ goto discard_noforce;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPORT_FWDTSN, SCTP_U32(tsn));
+ if (len > sizeof(struct sctp_fwdtsn_hdr))
+ sctp_add_cmd_sf(commands, SCTP_CMD_PROCESS_FWDTSN,
+ SCTP_CHUNK(chunk));
+
+ /* Count this as receiving DATA. */
+ if (asoc->autoclose) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_RESTART,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_AUTOCLOSE));
+ }
+
+ /* FIXME: For now send a SACK, but DATA processing may
+ * send another.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_GEN_SACK, SCTP_NOFORCE());
+ /* Start the SACK timer. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_RESTART,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_SACK));
+
+ return SCTP_DISPOSITION_CONSUME;
+
+discard_noforce:
+ return SCTP_DISPOSITION_DISCARD;
+}
+
+sctp_disposition_t sctp_sf_eat_fwd_tsn_fast(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ struct sctp_fwdtsn_hdr *fwdtsn_hdr;
+ __u16 len;
+ __u32 tsn;
+
+ if (!sctp_vtag_verify(chunk, asoc)) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPORT_BAD_TAG,
+ SCTP_NULL());
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+ }
+
+ /* Make sure that the FORWARD_TSN chunk has a valid length. */
+ if (!sctp_chunk_length_valid(chunk, sizeof(struct sctp_fwdtsn_chunk)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ fwdtsn_hdr = (struct sctp_fwdtsn_hdr *)chunk->skb->data;
+ chunk->subh.fwdtsn_hdr = fwdtsn_hdr;
+ len = ntohs(chunk->chunk_hdr->length);
+ len -= sizeof(struct sctp_chunkhdr);
+ skb_pull(chunk->skb, len);
+
+ tsn = ntohl(fwdtsn_hdr->new_cum_tsn);
+ SCTP_DEBUG_PRINTK("%s: TSN 0x%x.\n", __FUNCTION__, tsn);
+
+ /* The TSN is too high--silently discard the chunk and count on it
+ * getting retransmitted later.
+ */
+ if (sctp_tsnmap_check(&asoc->peer.tsn_map, tsn) < 0)
+ goto gen_shutdown;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPORT_FWDTSN, SCTP_U32(tsn));
+ if (len > sizeof(struct sctp_fwdtsn_hdr))
+ sctp_add_cmd_sf(commands, SCTP_CMD_PROCESS_FWDTSN,
+ SCTP_CHUNK(chunk));
+
+ /* Go a head and force a SACK, since we are shutting down. */
+gen_shutdown:
+ /* Implementor's Guide.
+ *
+ * While in SHUTDOWN-SENT state, the SHUTDOWN sender MUST immediately
+ * respond to each received packet containing one or more DATA chunk(s)
+ * with a SACK, a SHUTDOWN chunk, and restart the T2-shutdown timer
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_GEN_SHUTDOWN, SCTP_NULL());
+ sctp_add_cmd_sf(commands, SCTP_CMD_GEN_SACK, SCTP_FORCE());
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_RESTART,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T2_SHUTDOWN));
+
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/*
+ * Process an unknown chunk.
+ *
+ * Section: 3.2. Also, 2.1 in the implementor's guide.
+ *
+ * Chunk Types are encoded such that the highest-order two bits specify
+ * the action that must be taken if the processing endpoint does not
+ * recognize the Chunk Type.
+ *
+ * 00 - Stop processing this SCTP packet and discard it, do not process
+ * any further chunks within it.
+ *
+ * 01 - Stop processing this SCTP packet and discard it, do not process
+ * any further chunks within it, and report the unrecognized
+ * chunk in an 'Unrecognized Chunk Type'.
+ *
+ * 10 - Skip this chunk and continue processing.
+ *
+ * 11 - Skip this chunk and continue processing, but report in an ERROR
+ * Chunk using the 'Unrecognized Chunk Type' cause of error.
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_unk_chunk(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *unk_chunk = arg;
+ struct sctp_chunk *err_chunk;
+ sctp_chunkhdr_t *hdr;
+
+ SCTP_DEBUG_PRINTK("Processing the unknown chunk id %d.\n", type.chunk);
+
+ if (!sctp_vtag_verify(unk_chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Make sure that the chunk has a valid length.
+ * Since we don't know the chunk type, we use a general
+ * chunkhdr structure to make a comparison.
+ */
+ if (!sctp_chunk_length_valid(unk_chunk, sizeof(sctp_chunkhdr_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+
+ switch (type.chunk & SCTP_CID_ACTION_MASK) {
+ case SCTP_CID_ACTION_DISCARD:
+ /* Discard the packet. */
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+ break;
+ case SCTP_CID_ACTION_DISCARD_ERR:
+ /* Discard the packet. */
+ sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Generate an ERROR chunk as response. */
+ hdr = unk_chunk->chunk_hdr;
+ err_chunk = sctp_make_op_error(asoc, unk_chunk,
+ SCTP_ERROR_UNKNOWN_CHUNK, hdr,
+ WORD_ROUND(ntohs(hdr->length)));
+ if (err_chunk) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(err_chunk));
+ }
+ return SCTP_DISPOSITION_CONSUME;
+ break;
+ case SCTP_CID_ACTION_SKIP:
+ /* Skip the chunk. */
+ return SCTP_DISPOSITION_DISCARD;
+ break;
+ case SCTP_CID_ACTION_SKIP_ERR:
+ /* Generate an ERROR chunk as response. */
+ hdr = unk_chunk->chunk_hdr;
+ err_chunk = sctp_make_op_error(asoc, unk_chunk,
+ SCTP_ERROR_UNKNOWN_CHUNK, hdr,
+ WORD_ROUND(ntohs(hdr->length)));
+ if (err_chunk) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(err_chunk));
+ }
+ /* Skip the chunk. */
+ return SCTP_DISPOSITION_CONSUME;
+ break;
+ default:
+ break;
+ }
+
+ return SCTP_DISPOSITION_DISCARD;
+}
+
+/*
+ * Discard the chunk.
+ *
+ * Section: 0.2, 5.2.3, 5.2.5, 5.2.6, 6.0, 8.4.6, 8.5.1c, 9.2
+ * [Too numerous to mention...]
+ * Verification Tag: No verification needed.
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_discard_chunk(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ SCTP_DEBUG_PRINTK("Chunk %d is discarded\n", type.chunk);
+ return SCTP_DISPOSITION_DISCARD;
+}
+
+/*
+ * Discard the whole packet.
+ *
+ * Section: 8.4 2)
+ *
+ * 2) If the OOTB packet contains an ABORT chunk, the receiver MUST
+ * silently discard the OOTB packet and take no further action.
+ * Otherwise,
+ *
+ * Verification Tag: No verification necessary
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_pdiscard(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ sctp_add_cmd_sf(commands, SCTP_CMD_DISCARD_PACKET, SCTP_NULL());
+
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+
+/*
+ * The other end is violating protocol.
+ *
+ * Section: Not specified
+ * Verification Tag: Not specified
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (asoc, reply_msg, msg_up, timers, counters)
+ *
+ * We simply tag the chunk as a violation. The state machine will log
+ * the violation and continue.
+ */
+sctp_disposition_t sctp_sf_violation(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ return SCTP_DISPOSITION_VIOLATION;
+}
+
+
+/*
+ * Handle a protocol violation when the chunk length is invalid.
+ * "Invalid" length is identified as smaller then the minimal length a
+ * given chunk can be. For example, a SACK chunk has invalid length
+ * if it's length is set to be smaller then the size of sctp_sack_chunk_t.
+ *
+ * We inform the other end by sending an ABORT with a Protocol Violation
+ * error code.
+ *
+ * Section: Not specified
+ * Verification Tag: Nothing to do
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * Outputs
+ * (reply_msg, msg_up, counters)
+ *
+ * Generate an ABORT chunk and terminate the association.
+ */
+sctp_disposition_t sctp_sf_violation_chunklen(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+ struct sctp_chunk *abort = NULL;
+ char err_str[]="The following chunk had invalid length:";
+
+ /* Make the abort chunk. */
+ abort = sctp_make_abort_violation(asoc, chunk, err_str,
+ sizeof(err_str));
+ if (!abort)
+ goto nomem;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(abort));
+ SCTP_INC_STATS(SctpOutCtrlChunks);
+
+ if (asoc->state <= SCTP_STATE_COOKIE_ECHOED) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T1_INIT));
+ sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED,
+ SCTP_U32(SCTP_ERROR_PROTO_VIOLATION));
+ } else {
+ sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
+ SCTP_U32(SCTP_ERROR_PROTO_VIOLATION));
+ SCTP_DEC_STATS(SctpCurrEstab);
+ }
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_DISCARD_PACKET, SCTP_NULL());
+
+ SCTP_INC_STATS(SctpAborteds);
+
+ return SCTP_DISPOSITION_ABORT;
+
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/***************************************************************************
+ * These are the state functions for handling primitive (Section 10) events.
+ ***************************************************************************/
+/*
+ * sctp_sf_do_prm_asoc
+ *
+ * Section: 10.1 ULP-to-SCTP
+ * B) Associate
+ *
+ * Format: ASSOCIATE(local SCTP instance name, destination transport addr,
+ * outbound stream count)
+ * -> association id [,destination transport addr list] [,outbound stream
+ * count]
+ *
+ * This primitive allows the upper layer to initiate an association to a
+ * specific peer endpoint.
+ *
+ * The peer endpoint shall be specified by one of the transport addresses
+ * which defines the endpoint (see Section 1.4). If the local SCTP
+ * instance has not been initialized, the ASSOCIATE is considered an
+ * error.
+ * [This is not relevant for the kernel implementation since we do all
+ * initialization at boot time. It we hadn't initialized we wouldn't
+ * get anywhere near this code.]
+ *
+ * An association id, which is a local handle to the SCTP association,
+ * will be returned on successful establishment of the association. If
+ * SCTP is not able to open an SCTP association with the peer endpoint,
+ * an error is returned.
+ * [In the kernel implementation, the struct sctp_association needs to
+ * be created BEFORE causing this primitive to run.]
+ *
+ * Other association parameters may be returned, including the
+ * complete destination transport addresses of the peer as well as the
+ * outbound stream count of the local endpoint. One of the transport
+ * address from the returned destination addresses will be selected by
+ * the local endpoint as default primary path for sending SCTP packets
+ * to this peer. The returned "destination transport addr list" can
+ * be used by the ULP to change the default primary path or to force
+ * sending a packet to a specific transport address. [All of this
+ * stuff happens when the INIT ACK arrives. This is a NON-BLOCKING
+ * function.]
+ *
+ * Mandatory attributes:
+ *
+ * o local SCTP instance name - obtained from the INITIALIZE operation.
+ * [This is the argument asoc.]
+ * o destination transport addr - specified as one of the transport
+ * addresses of the peer endpoint with which the association is to be
+ * established.
+ * [This is asoc->peer.active_path.]
+ * o outbound stream count - the number of outbound streams the ULP
+ * would like to open towards this peer endpoint.
+ * [BUG: This is not currently implemented.]
+ * Optional attributes:
+ *
+ * None.
+ *
+ * The return value is a disposition.
+ */
+sctp_disposition_t sctp_sf_do_prm_asoc(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *repl;
+
+ /* The comment below says that we enter COOKIE-WAIT AFTER
+ * sending the INIT, but that doesn't actually work in our
+ * implementation...
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_COOKIE_WAIT));
+
+ /* RFC 2960 5.1 Normal Establishment of an Association
+ *
+ * A) "A" first sends an INIT chunk to "Z". In the INIT, "A"
+ * must provide its Verification Tag (Tag_A) in the Initiate
+ * Tag field. Tag_A SHOULD be a random number in the range of
+ * 1 to 4294967295 (see 5.3.1 for Tag value selection). ...
+ */
+
+ repl = sctp_make_init(asoc, &asoc->base.bind_addr, GFP_ATOMIC, 0);
+ if (!repl)
+ goto nomem;
+
+ /* Cast away the const modifier, as we want to just
+ * rerun it through as a sideffect.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_ASOC,
+ SCTP_ASOC((struct sctp_association *) asoc));
+
+ /* After sending the INIT, "A" starts the T1-init timer and
+ * enters the COOKIE-WAIT state.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_START,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T1_INIT));
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(repl));
+ return SCTP_DISPOSITION_CONSUME;
+
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * Process the SEND primitive.
+ *
+ * Section: 10.1 ULP-to-SCTP
+ * E) Send
+ *
+ * Format: SEND(association id, buffer address, byte count [,context]
+ * [,stream id] [,life time] [,destination transport address]
+ * [,unorder flag] [,no-bundle flag] [,payload protocol-id] )
+ * -> result
+ *
+ * This is the main method to send user data via SCTP.
+ *
+ * Mandatory attributes:
+ *
+ * o association id - local handle to the SCTP association
+ *
+ * o buffer address - the location where the user message to be
+ * transmitted is stored;
+ *
+ * o byte count - The size of the user data in number of bytes;
+ *
+ * Optional attributes:
+ *
+ * o context - an optional 32 bit integer that will be carried in the
+ * sending failure notification to the ULP if the transportation of
+ * this User Message fails.
+ *
+ * o stream id - to indicate which stream to send the data on. If not
+ * specified, stream 0 will be used.
+ *
+ * o life time - specifies the life time of the user data. The user data
+ * will not be sent by SCTP after the life time expires. This
+ * parameter can be used to avoid efforts to transmit stale
+ * user messages. SCTP notifies the ULP if the data cannot be
+ * initiated to transport (i.e. sent to the destination via SCTP's
+ * send primitive) within the life time variable. However, the
+ * user data will be transmitted if SCTP has attempted to transmit a
+ * chunk before the life time expired.
+ *
+ * o destination transport address - specified as one of the destination
+ * transport addresses of the peer endpoint to which this packet
+ * should be sent. Whenever possible, SCTP should use this destination
+ * transport address for sending the packets, instead of the current
+ * primary path.
+ *
+ * o unorder flag - this flag, if present, indicates that the user
+ * would like the data delivered in an unordered fashion to the peer
+ * (i.e., the U flag is set to 1 on all DATA chunks carrying this
+ * message).
+ *
+ * o no-bundle flag - instructs SCTP not to bundle this user data with
+ * other outbound DATA chunks. SCTP MAY still bundle even when
+ * this flag is present, when faced with network congestion.
+ *
+ * o payload protocol-id - A 32 bit unsigned integer that is to be
+ * passed to the peer indicating the type of payload protocol data
+ * being transmitted. This value is passed as opaque data by SCTP.
+ *
+ * The return value is the disposition.
+ */
+sctp_disposition_t sctp_sf_do_prm_send(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(chunk));
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/*
+ * Process the SHUTDOWN primitive.
+ *
+ * Section: 10.1:
+ * C) Shutdown
+ *
+ * Format: SHUTDOWN(association id)
+ * -> result
+ *
+ * Gracefully closes an association. Any locally queued user data
+ * will be delivered to the peer. The association will be terminated only
+ * after the peer acknowledges all the SCTP packets sent. A success code
+ * will be returned on successful termination of the association. If
+ * attempting to terminate the association results in a failure, an error
+ * code shall be returned.
+ *
+ * Mandatory attributes:
+ *
+ * o association id - local handle to the SCTP association
+ *
+ * Optional attributes:
+ *
+ * None.
+ *
+ * The return value is the disposition.
+ */
+sctp_disposition_t sctp_sf_do_9_2_prm_shutdown(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ int disposition;
+
+ /* From 9.2 Shutdown of an Association
+ * Upon receipt of the SHUTDOWN primitive from its upper
+ * layer, the endpoint enters SHUTDOWN-PENDING state and
+ * remains there until all outstanding data has been
+ * acknowledged by its peer. The endpoint accepts no new data
+ * from its upper layer, but retransmits data to the far end
+ * if necessary to fill gaps.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_SHUTDOWN_PENDING));
+
+ /* sctpimpguide-05 Section 2.12.2
+ * The sender of the SHUTDOWN MAY also start an overall guard timer
+ * 'T5-shutdown-guard' to bound the overall time for shutdown sequence.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_START,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T5_SHUTDOWN_GUARD));
+
+ disposition = SCTP_DISPOSITION_CONSUME;
+ if (sctp_outq_is_empty(&asoc->outqueue)) {
+ disposition = sctp_sf_do_9_2_start_shutdown(ep, asoc, type,
+ arg, commands);
+ }
+ return disposition;
+}
+
+/*
+ * Process the ABORT primitive.
+ *
+ * Section: 10.1:
+ * C) Abort
+ *
+ * Format: Abort(association id [, cause code])
+ * -> result
+ *
+ * Ungracefully closes an association. Any locally queued user data
+ * will be discarded and an ABORT chunk is sent to the peer. A success code
+ * will be returned on successful abortion of the association. If
+ * attempting to abort the association results in a failure, an error
+ * code shall be returned.
+ *
+ * Mandatory attributes:
+ *
+ * o association id - local handle to the SCTP association
+ *
+ * Optional attributes:
+ *
+ * o cause code - reason of the abort to be passed to the peer
+ *
+ * None.
+ *
+ * The return value is the disposition.
+ */
+sctp_disposition_t sctp_sf_do_9_1_prm_abort(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ /* From 9.1 Abort of an Association
+ * Upon receipt of the ABORT primitive from its upper
+ * layer, the endpoint enters CLOSED state and
+ * discard all outstanding data has been
+ * acknowledged by its peer. The endpoint accepts no new data
+ * from its upper layer, but retransmits data to the far end
+ * if necessary to fill gaps.
+ */
+ struct msghdr *msg = arg;
+ struct sctp_chunk *abort;
+ sctp_disposition_t retval;
+
+ retval = SCTP_DISPOSITION_CONSUME;
+
+ /* Generate ABORT chunk to send the peer. */
+ abort = sctp_make_abort_user(asoc, NULL, msg);
+ if (!abort)
+ retval = SCTP_DISPOSITION_NOMEM;
+ else
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(abort));
+
+ /* Even if we can't send the ABORT due to low memory delete the
+ * TCB. This is a departure from our typical NOMEM handling.
+ */
+
+ /* Delete the established association. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
+ SCTP_U32(SCTP_ERROR_USER_ABORT));
+
+ SCTP_INC_STATS(SctpAborteds);
+ SCTP_DEC_STATS(SctpCurrEstab);
+
+ return retval;
+}
+
+/* We tried an illegal operation on an association which is closed. */
+sctp_disposition_t sctp_sf_error_closed(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPORT_ERROR, SCTP_ERROR(-EINVAL));
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/* We tried an illegal operation on an association which is shutting
+ * down.
+ */
+sctp_disposition_t sctp_sf_error_shutdown(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPORT_ERROR,
+ SCTP_ERROR(-ESHUTDOWN));
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/*
+ * sctp_cookie_wait_prm_shutdown
+ *
+ * Section: 4 Note: 2
+ * Verification Tag:
+ * Inputs
+ * (endpoint, asoc)
+ *
+ * The RFC does not explicitly address this issue, but is the route through the
+ * state table when someone issues a shutdown while in COOKIE_WAIT state.
+ *
+ * Outputs
+ * (timers)
+ */
+sctp_disposition_t sctp_sf_cookie_wait_prm_shutdown(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T1_INIT));
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_CLOSED));
+
+ SCTP_INC_STATS(SctpShutdowns);
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_DELETE_TCB, SCTP_NULL());
+
+ return SCTP_DISPOSITION_DELETE_TCB;
+}
+
+/*
+ * sctp_cookie_echoed_prm_shutdown
+ *
+ * Section: 4 Note: 2
+ * Verification Tag:
+ * Inputs
+ * (endpoint, asoc)
+ *
+ * The RFC does not explcitly address this issue, but is the route through the
+ * state table when someone issues a shutdown while in COOKIE_ECHOED state.
+ *
+ * Outputs
+ * (timers)
+ */
+sctp_disposition_t sctp_sf_cookie_echoed_prm_shutdown(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg, sctp_cmd_seq_t *commands)
+{
+ /* There is a single T1 timer, so we should be able to use
+ * common function with the COOKIE-WAIT state.
+ */
+ return sctp_sf_cookie_wait_prm_shutdown(ep, asoc, type, arg, commands);
+}
+
+/*
+ * sctp_sf_cookie_wait_prm_abort
+ *
+ * Section: 4 Note: 2
+ * Verification Tag:
+ * Inputs
+ * (endpoint, asoc)
+ *
+ * The RFC does not explicitly address this issue, but is the route through the
+ * state table when someone issues an abort while in COOKIE_WAIT state.
+ *
+ * Outputs
+ * (timers)
+ */
+sctp_disposition_t sctp_sf_cookie_wait_prm_abort(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct msghdr *msg = arg;
+ struct sctp_chunk *abort;
+ sctp_disposition_t retval;
+
+ /* Stop T1-init timer */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T1_INIT));
+ retval = SCTP_DISPOSITION_CONSUME;
+
+ /* Generate ABORT chunk to send the peer */
+ abort = sctp_make_abort_user(asoc, NULL, msg);
+ if (!abort)
+ retval = SCTP_DISPOSITION_NOMEM;
+ else
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(abort));
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_CLOSED));
+
+ SCTP_INC_STATS(SctpAborteds);
+
+ /* Even if we can't send the ABORT due to low memory delete the
+ * TCB. This is a departure from our typical NOMEM handling.
+ */
+
+ /* Delete the established association. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED,
+ SCTP_U32(SCTP_ERROR_USER_ABORT));
+
+ return retval;
+}
+
+/*
+ * sctp_sf_cookie_echoed_prm_abort
+ *
+ * Section: 4 Note: 3
+ * Verification Tag:
+ * Inputs
+ * (endpoint, asoc)
+ *
+ * The RFC does not explcitly address this issue, but is the route through the
+ * state table when someone issues an abort while in COOKIE_ECHOED state.
+ *
+ * Outputs
+ * (timers)
+ */
+sctp_disposition_t sctp_sf_cookie_echoed_prm_abort(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ /* There is a single T1 timer, so we should be able to use
+ * common function with the COOKIE-WAIT state.
+ */
+ return sctp_sf_cookie_wait_prm_abort(ep, asoc, type, arg, commands);
+}
+
+/*
+ * sctp_sf_shutdown_pending_prm_abort
+ *
+ * Inputs
+ * (endpoint, asoc)
+ *
+ * The RFC does not explicitly address this issue, but is the route through the
+ * state table when someone issues an abort while in SHUTDOWN-PENDING state.
+ *
+ * Outputs
+ * (timers)
+ */
+sctp_disposition_t sctp_sf_shutdown_pending_prm_abort(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ /* Stop the T5-shutdown guard timer. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T5_SHUTDOWN_GUARD));
+
+ return sctp_sf_do_9_1_prm_abort(ep, asoc, type, arg, commands);
+}
+
+/*
+ * sctp_sf_shutdown_sent_prm_abort
+ *
+ * Inputs
+ * (endpoint, asoc)
+ *
+ * The RFC does not explicitly address this issue, but is the route through the
+ * state table when someone issues an abort while in SHUTDOWN-SENT state.
+ *
+ * Outputs
+ * (timers)
+ */
+sctp_disposition_t sctp_sf_shutdown_sent_prm_abort(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ /* Stop the T2-shutdown timer. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T2_SHUTDOWN));
+
+ /* Stop the T5-shutdown guard timer. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T5_SHUTDOWN_GUARD));
+
+ return sctp_sf_do_9_1_prm_abort(ep, asoc, type, arg, commands);
+}
+
+/*
+ * sctp_sf_cookie_echoed_prm_abort
+ *
+ * Inputs
+ * (endpoint, asoc)
+ *
+ * The RFC does not explcitly address this issue, but is the route through the
+ * state table when someone issues an abort while in COOKIE_ECHOED state.
+ *
+ * Outputs
+ * (timers)
+ */
+sctp_disposition_t sctp_sf_shutdown_ack_sent_prm_abort(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ /* The same T2 timer, so we should be able to use
+ * common function with the SHUTDOWN-SENT state.
+ */
+ return sctp_sf_shutdown_sent_prm_abort(ep, asoc, type, arg, commands);
+}
+
+/*
+ * Process the REQUESTHEARTBEAT primitive
+ *
+ * 10.1 ULP-to-SCTP
+ * J) Request Heartbeat
+ *
+ * Format: REQUESTHEARTBEAT(association id, destination transport address)
+ *
+ * -> result
+ *
+ * Instructs the local endpoint to perform a HeartBeat on the specified
+ * destination transport address of the given association. The returned
+ * result should indicate whether the transmission of the HEARTBEAT
+ * chunk to the destination address is successful.
+ *
+ * Mandatory attributes:
+ *
+ * o association id - local handle to the SCTP association
+ *
+ * o destination transport address - the transport address of the
+ * association on which a heartbeat should be issued.
+ */
+sctp_disposition_t sctp_sf_do_prm_requestheartbeat(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ return sctp_sf_heartbeat(ep, asoc, type, (struct sctp_transport *)arg,
+ commands);
+}
+
+/*
+ * ADDIP Section 4.1 ASCONF Chunk Procedures
+ * When an endpoint has an ASCONF signaled change to be sent to the
+ * remote endpoint it should do A1 to A9
+ */
+sctp_disposition_t sctp_sf_do_prm_asconf(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = arg;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_SETUP_T4, SCTP_CHUNK(chunk));
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_START,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T4_RTO));
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(chunk));
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/*
+ * Ignore the primitive event
+ *
+ * The return value is the disposition of the primitive.
+ */
+sctp_disposition_t sctp_sf_ignore_primitive(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ SCTP_DEBUG_PRINTK("Primitive type %d is ignored.\n", type.primitive);
+ return SCTP_DISPOSITION_DISCARD;
+}
+
+/***************************************************************************
+ * These are the state functions for the OTHER events.
+ ***************************************************************************/
+
+/*
+ * Start the shutdown negotiation.
+ *
+ * From Section 9.2:
+ * Once all its outstanding data has been acknowledged, the endpoint
+ * shall send a SHUTDOWN chunk to its peer including in the Cumulative
+ * TSN Ack field the last sequential TSN it has received from the peer.
+ * It shall then start the T2-shutdown timer and enter the SHUTDOWN-SENT
+ * state. If the timer expires, the endpoint must re-send the SHUTDOWN
+ * with the updated last sequential TSN received from its peer.
+ *
+ * The return value is the disposition.
+ */
+sctp_disposition_t sctp_sf_do_9_2_start_shutdown(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *reply;
+
+ /* Once all its outstanding data has been acknowledged, the
+ * endpoint shall send a SHUTDOWN chunk to its peer including
+ * in the Cumulative TSN Ack field the last sequential TSN it
+ * has received from the peer.
+ */
+ reply = sctp_make_shutdown(asoc, NULL);
+ if (!reply)
+ goto nomem;
+
+ /* Set the transport for the SHUTDOWN chunk and the timeout for the
+ * T2-shutdown timer.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_SETUP_T2, SCTP_CHUNK(reply));
+
+ /* It shall then start the T2-shutdown timer */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_START,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T2_SHUTDOWN));
+
+ if (asoc->autoclose)
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_AUTOCLOSE));
+
+ /* and enter the SHUTDOWN-SENT state. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_SHUTDOWN_SENT));
+
+ /* sctp-implguide 2.10 Issues with Heartbeating and failover
+ *
+ * HEARTBEAT ... is discontinued after sending either SHUTDOWN
+ * or SHUTDOWN-ACK.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_HB_TIMERS_STOP, SCTP_NULL());
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply));
+
+ return SCTP_DISPOSITION_CONSUME;
+
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * Generate a SHUTDOWN ACK now that everything is SACK'd.
+ *
+ * From Section 9.2:
+ *
+ * If it has no more outstanding DATA chunks, the SHUTDOWN receiver
+ * shall send a SHUTDOWN ACK and start a T2-shutdown timer of its own,
+ * entering the SHUTDOWN-ACK-SENT state. If the timer expires, the
+ * endpoint must re-send the SHUTDOWN ACK.
+ *
+ * The return value is the disposition.
+ */
+sctp_disposition_t sctp_sf_do_9_2_shutdown_ack(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = (struct sctp_chunk *) arg;
+ struct sctp_chunk *reply;
+
+ /* There are 2 ways of getting here:
+ * 1) called in response to a SHUTDOWN chunk
+ * 2) called when SCTP_EVENT_NO_PENDING_TSN event is issued.
+ *
+ * For the case (2), the arg parameter is set to NULL. We need
+ * to check that we have a chunk before accessing it's fields.
+ */
+ if (chunk) {
+ if (!sctp_vtag_verify(chunk, asoc))
+ return sctp_sf_pdiscard(ep, asoc, type, arg, commands);
+
+ /* Make sure that the SHUTDOWN chunk has a valid length. */
+ if (!sctp_chunk_length_valid(chunk, sizeof(struct sctp_shutdown_chunk_t)))
+ return sctp_sf_violation_chunklen(ep, asoc, type, arg,
+ commands);
+ }
+
+ /* If it has no more outstanding DATA chunks, the SHUTDOWN receiver
+ * shall send a SHUTDOWN ACK ...
+ */
+ reply = sctp_make_shutdown_ack(asoc, chunk);
+ if (!reply)
+ goto nomem;
+
+ /* Set the transport for the SHUTDOWN ACK chunk and the timeout for
+ * the T2-shutdown timer.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_SETUP_T2, SCTP_CHUNK(reply));
+
+ /* and start/restart a T2-shutdown timer of its own, */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_RESTART,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T2_SHUTDOWN));
+
+ if (asoc->autoclose)
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_AUTOCLOSE));
+
+ /* Enter the SHUTDOWN-ACK-SENT state. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_SHUTDOWN_ACK_SENT));
+
+ /* sctp-implguide 2.10 Issues with Heartbeating and failover
+ *
+ * HEARTBEAT ... is discontinued after sending either SHUTDOWN
+ * or SHUTDOWN-ACK.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_HB_TIMERS_STOP, SCTP_NULL());
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply));
+
+ return SCTP_DISPOSITION_CONSUME;
+
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * Ignore the event defined as other
+ *
+ * The return value is the disposition of the event.
+ */
+sctp_disposition_t sctp_sf_ignore_other(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ SCTP_DEBUG_PRINTK("The event other type %d is ignored\n", type.other);
+ return SCTP_DISPOSITION_DISCARD;
+}
+
+/************************************************************
+ * These are the state functions for handling timeout events.
+ ************************************************************/
+
+/*
+ * RTX Timeout
+ *
+ * Section: 6.3.3 Handle T3-rtx Expiration
+ *
+ * Whenever the retransmission timer T3-rtx expires for a destination
+ * address, do the following:
+ * [See below]
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_do_6_3_3_rtx(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_transport *transport = arg;
+
+ if (asoc->overall_error_count >= asoc->max_retrans) {
+ /* CMD_ASSOC_FAILED calls CMD_DELETE_TCB. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
+ SCTP_U32(SCTP_ERROR_NO_ERROR));
+ SCTP_INC_STATS(SctpAborteds);
+ SCTP_DEC_STATS(SctpCurrEstab);
+ return SCTP_DISPOSITION_DELETE_TCB;
+ }
+
+ /* E1) For the destination address for which the timer
+ * expires, adjust its ssthresh with rules defined in Section
+ * 7.2.3 and set the cwnd <- MTU.
+ */
+
+ /* E2) For the destination address for which the timer
+ * expires, set RTO <- RTO * 2 ("back off the timer"). The
+ * maximum value discussed in rule C7 above (RTO.max) may be
+ * used to provide an upper bound to this doubling operation.
+ */
+
+ /* E3) Determine how many of the earliest (i.e., lowest TSN)
+ * outstanding DATA chunks for the address for which the
+ * T3-rtx has expired will fit into a single packet, subject
+ * to the MTU constraint for the path corresponding to the
+ * destination transport address to which the retransmission
+ * is being sent (this may be different from the address for
+ * which the timer expires [see Section 6.4]). Call this
+ * value K. Bundle and retransmit those K DATA chunks in a
+ * single packet to the destination endpoint.
+ *
+ * Note: Any DATA chunks that were sent to the address for
+ * which the T3-rtx timer expired but did not fit in one MTU
+ * (rule E3 above), should be marked for retransmission and
+ * sent as soon as cwnd allows (normally when a SACK arrives).
+ */
+
+ /* NB: Rules E4 and F1 are implicit in R1. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_RETRAN, SCTP_TRANSPORT(transport));
+
+ /* Do some failure management (Section 8.2). */
+ sctp_add_cmd_sf(commands, SCTP_CMD_STRIKE, SCTP_TRANSPORT(transport));
+
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/*
+ * Generate delayed SACK on timeout
+ *
+ * Section: 6.2 Acknowledgement on Reception of DATA Chunks
+ *
+ * The guidelines on delayed acknowledgement algorithm specified in
+ * Section 4.2 of [RFC2581] SHOULD be followed. Specifically, an
+ * acknowledgement SHOULD be generated for at least every second packet
+ * (not every second DATA chunk) received, and SHOULD be generated
+ * within 200 ms of the arrival of any unacknowledged DATA chunk. In
+ * some situations it may be beneficial for an SCTP transmitter to be
+ * more conservative than the algorithms detailed in this document
+ * allow. However, an SCTP transmitter MUST NOT be more aggressive than
+ * the following algorithms allow.
+ */
+sctp_disposition_t sctp_sf_do_6_2_sack(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ sctp_add_cmd_sf(commands, SCTP_CMD_GEN_SACK, SCTP_FORCE());
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/*
+ * sctp_sf_t1_timer_expire
+ *
+ * Section: 4 Note: 2
+ * Verification Tag:
+ * Inputs
+ * (endpoint, asoc)
+ *
+ * RFC 2960 Section 4 Notes
+ * 2) If the T1-init timer expires, the endpoint MUST retransmit INIT
+ * and re-start the T1-init timer without changing state. This MUST
+ * be repeated up to 'Max.Init.Retransmits' times. After that, the
+ * endpoint MUST abort the initialization process and report the
+ * error to SCTP user.
+ *
+ * 3) If the T1-cookie timer expires, the endpoint MUST retransmit
+ * COOKIE ECHO and re-start the T1-cookie timer without changing
+ * state. This MUST be repeated up to 'Max.Init.Retransmits' times.
+ * After that, the endpoint MUST abort the initialization process and
+ * report the error to SCTP user.
+ *
+ * Outputs
+ * (timers, events)
+ *
+ */
+sctp_disposition_t sctp_sf_t1_timer_expire(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *repl;
+ struct sctp_bind_addr *bp;
+ sctp_event_timeout_t timer = (sctp_event_timeout_t) arg;
+ int timeout;
+ int attempts;
+
+ timeout = asoc->timeouts[timer];
+ attempts = asoc->counters[SCTP_COUNTER_INIT_ERROR] + 1;
+ repl = NULL;
+
+ SCTP_DEBUG_PRINTK("Timer T1 expired.\n");
+
+ if (attempts < asoc->max_init_attempts) {
+ switch (timer) {
+ case SCTP_EVENT_TIMEOUT_T1_INIT:
+ bp = (struct sctp_bind_addr *) &asoc->base.bind_addr;
+ repl = sctp_make_init(asoc, bp, GFP_ATOMIC, 0);
+ break;
+
+ case SCTP_EVENT_TIMEOUT_T1_COOKIE:
+ repl = sctp_make_cookie_echo(asoc, NULL);
+ break;
+
+ default:
+ BUG();
+ break;
+ };
+
+ if (!repl)
+ goto nomem;
+
+ /* Issue a sideeffect to do the needed accounting. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_INIT_RESTART,
+ SCTP_TO(timer));
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(repl));
+ } else {
+ sctp_add_cmd_sf(commands, SCTP_CMD_INIT_FAILED,
+ SCTP_U32(SCTP_ERROR_NO_ERROR));
+ return SCTP_DISPOSITION_DELETE_TCB;
+ }
+
+ return SCTP_DISPOSITION_CONSUME;
+
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/* RFC2960 9.2 If the timer expires, the endpoint must re-send the SHUTDOWN
+ * with the updated last sequential TSN received from its peer.
+ *
+ * An endpoint should limit the number of retransmissions of the
+ * SHUTDOWN chunk to the protocol parameter 'Association.Max.Retrans'.
+ * If this threshold is exceeded the endpoint should destroy the TCB and
+ * MUST report the peer endpoint unreachable to the upper layer (and
+ * thus the association enters the CLOSED state). The reception of any
+ * packet from its peer (i.e. as the peer sends all of its queued DATA
+ * chunks) should clear the endpoint's retransmission count and restart
+ * the T2-Shutdown timer, giving its peer ample opportunity to transmit
+ * all of its queued DATA chunks that have not yet been sent.
+ */
+sctp_disposition_t sctp_sf_t2_timer_expire(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *reply = NULL;
+
+ SCTP_DEBUG_PRINTK("Timer T2 expired.\n");
+ if (asoc->overall_error_count >= asoc->max_retrans) {
+ /* Note: CMD_ASSOC_FAILED calls CMD_DELETE_TCB. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
+ SCTP_U32(SCTP_ERROR_NO_ERROR));
+ SCTP_INC_STATS(SctpAborteds);
+ SCTP_DEC_STATS(SctpCurrEstab);
+ return SCTP_DISPOSITION_DELETE_TCB;
+ }
+
+ switch (asoc->state) {
+ case SCTP_STATE_SHUTDOWN_SENT:
+ reply = sctp_make_shutdown(asoc, NULL);
+ break;
+
+ case SCTP_STATE_SHUTDOWN_ACK_SENT:
+ reply = sctp_make_shutdown_ack(asoc, NULL);
+ break;
+
+ default:
+ BUG();
+ break;
+ };
+
+ if (!reply)
+ goto nomem;
+
+ /* Do some failure management (Section 8.2). */
+ sctp_add_cmd_sf(commands, SCTP_CMD_STRIKE,
+ SCTP_TRANSPORT(asoc->shutdown_last_sent_to));
+
+ /* Set the transport for the SHUTDOWN/ACK chunk and the timeout for
+ * the T2-shutdown timer.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_SETUP_T2, SCTP_CHUNK(reply));
+
+ /* Restart the T2-shutdown timer. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_RESTART,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T2_SHUTDOWN));
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply));
+ return SCTP_DISPOSITION_CONSUME;
+
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/*
+ * ADDIP Section 4.1 ASCONF CHunk Procedures
+ * If the T4 RTO timer expires the endpoint should do B1 to B5
+ */
+sctp_disposition_t sctp_sf_t4_timer_expire(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *chunk = asoc->addip_last_asconf;
+ struct sctp_transport *transport = chunk->transport;
+
+ /* ADDIP 4.1 B1) Increment the error counters and perform path failure
+ * detection on the appropriate destination address as defined in
+ * RFC2960 [5] section 8.1 and 8.2.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_STRIKE, SCTP_TRANSPORT(transport));
+
+ /* Reconfig T4 timer and transport. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_SETUP_T4, SCTP_CHUNK(chunk));
+
+ /* ADDIP 4.1 B2) Increment the association error counters and perform
+ * endpoint failure detection on the association as defined in
+ * RFC2960 [5] section 8.1 and 8.2.
+ * association error counter is incremented in SCTP_CMD_STRIKE.
+ */
+ if (asoc->overall_error_count >= asoc->max_retrans) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_STOP,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T4_RTO));
+ sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
+ SCTP_U32(SCTP_ERROR_NO_ERROR));
+ SCTP_INC_STATS(SctpAborteds);
+ SCTP_INC_STATS(SctpCurrEstab);
+ return SCTP_DISPOSITION_ABORT;
+ }
+
+ /* ADDIP 4.1 B3) Back-off the destination address RTO value to which
+ * the ASCONF chunk was sent by doubling the RTO timer value.
+ * This is done in SCTP_CMD_STRIKE.
+ */
+
+ /* ADDIP 4.1 B4) Re-transmit the ASCONF Chunk last sent and if possible
+ * choose an alternate destination address (please refer to RFC2960
+ * [5] section 6.4.1). An endpoint MUST NOT add new parameters to this
+ * chunk, it MUST be the same (including its serial number) as the last
+ * ASCONF sent.
+ */
+ sctp_chunk_hold(asoc->addip_last_asconf);
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(asoc->addip_last_asconf));
+
+ /* ADDIP 4.1 B5) Restart the T-4 RTO timer. Note that if a different
+ * destination is selected, then the RTO used will be that of the new
+ * destination address.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_RESTART,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T4_RTO));
+
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/* sctpimpguide-05 Section 2.12.2
+ * The sender of the SHUTDOWN MAY also start an overall guard timer
+ * 'T5-shutdown-guard' to bound the overall time for shutdown sequence.
+ * At the expiration of this timer the sender SHOULD abort the association
+ * by sending an ABORT chunk.
+ */
+sctp_disposition_t sctp_sf_t5_timer_expire(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ struct sctp_chunk *reply = NULL;
+
+ SCTP_DEBUG_PRINTK("Timer T5 expired.\n");
+
+ reply = sctp_make_abort(asoc, NULL, 0);
+ if (!reply)
+ goto nomem;
+
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY, SCTP_CHUNK(reply));
+ sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
+ SCTP_U32(SCTP_ERROR_NO_ERROR));
+
+ return SCTP_DISPOSITION_DELETE_TCB;
+nomem:
+ return SCTP_DISPOSITION_NOMEM;
+}
+
+/* Handle expiration of AUTOCLOSE timer. When the autoclose timer expires,
+ * the association is automatically closed by starting the shutdown process.
+ * The work that needs to be done is same as when SHUTDOWN is initiated by
+ * the user. So this routine looks same as sctp_sf_do_9_2_prm_shutdown().
+ */
+sctp_disposition_t sctp_sf_autoclose_timer_expire(
+ const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ int disposition;
+
+ /* From 9.2 Shutdown of an Association
+ * Upon receipt of the SHUTDOWN primitive from its upper
+ * layer, the endpoint enters SHUTDOWN-PENDING state and
+ * remains there until all outstanding data has been
+ * acknowledged by its peer. The endpoint accepts no new data
+ * from its upper layer, but retransmits data to the far end
+ * if necessary to fill gaps.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_NEW_STATE,
+ SCTP_STATE(SCTP_STATE_SHUTDOWN_PENDING));
+
+ /* sctpimpguide-05 Section 2.12.2
+ * The sender of the SHUTDOWN MAY also start an overall guard timer
+ * 'T5-shutdown-guard' to bound the overall time for shutdown sequence.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_TIMER_START,
+ SCTP_TO(SCTP_EVENT_TIMEOUT_T5_SHUTDOWN_GUARD));
+ disposition = SCTP_DISPOSITION_CONSUME;
+ if (sctp_outq_is_empty(&asoc->outqueue)) {
+ disposition = sctp_sf_do_9_2_start_shutdown(ep, asoc, type,
+ arg, commands);
+ }
+ return disposition;
+}
+
+/*****************************************************************************
+ * These are sa state functions which could apply to all types of events.
+ ****************************************************************************/
+
+/*
+ * This table entry is not implemented.
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_not_impl(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ return SCTP_DISPOSITION_NOT_IMPL;
+}
+
+/*
+ * This table entry represents a bug.
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_bug(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ return SCTP_DISPOSITION_BUG;
+}
+
+/*
+ * This table entry represents the firing of a timer in the wrong state.
+ * Since timer deletion cannot be guaranteed a timer 'may' end up firing
+ * when the association is in the wrong state. This event should
+ * be ignored, so as to prevent any rearming of the timer.
+ *
+ * Inputs
+ * (endpoint, asoc, chunk)
+ *
+ * The return value is the disposition of the chunk.
+ */
+sctp_disposition_t sctp_sf_timer_ignore(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const sctp_subtype_t type,
+ void *arg,
+ sctp_cmd_seq_t *commands)
+{
+ SCTP_DEBUG_PRINTK("Timer %d ignored.\n", type.chunk);
+ return SCTP_DISPOSITION_CONSUME;
+}
+
+/********************************************************************
+ * 2nd Level Abstractions
+ ********************************************************************/
+
+/* Pull the SACK chunk based on the SACK header. */
+static struct sctp_sackhdr *sctp_sm_pull_sack(struct sctp_chunk *chunk)
+{
+ struct sctp_sackhdr *sack;
+ unsigned int len;
+ __u16 num_blocks;
+ __u16 num_dup_tsns;
+
+ /* Protect ourselves from reading too far into
+ * the skb from a bogus sender.
+ */
+ sack = (struct sctp_sackhdr *) chunk->skb->data;
+
+ num_blocks = ntohs(sack->num_gap_ack_blocks);
+ num_dup_tsns = ntohs(sack->num_dup_tsns);
+ len = sizeof(struct sctp_sackhdr);
+ len += (num_blocks + num_dup_tsns) * sizeof(__u32);
+ if (len > chunk->skb->len)
+ return NULL;
+
+ skb_pull(chunk->skb, len);
+
+ return sack;
+}
+
+/* Create an ABORT packet to be sent as a response, with the specified
+ * error causes.
+ */
+static struct sctp_packet *sctp_abort_pkt_new(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ struct sctp_chunk *chunk,
+ const void *payload,
+ size_t paylen)
+{
+ struct sctp_packet *packet;
+ struct sctp_chunk *abort;
+
+ packet = sctp_ootb_pkt_new(asoc, chunk);
+
+ if (packet) {
+ /* Make an ABORT.
+ * The T bit will be set if the asoc is NULL.
+ */
+ abort = sctp_make_abort(asoc, chunk, paylen);
+ if (!abort) {
+ sctp_ootb_pkt_free(packet);
+ return NULL;
+ }
+ /* Add specified error causes, i.e., payload, to the
+ * end of the chunk.
+ */
+ sctp_addto_chunk(abort, paylen, payload);
+
+ /* Set the skb to the belonging sock for accounting. */
+ abort->skb->sk = ep->base.sk;
+
+ sctp_packet_append_chunk(packet, abort);
+
+ }
+
+ return packet;
+}
+
+/* Allocate a packet for responding in the OOTB conditions. */
+static struct sctp_packet *sctp_ootb_pkt_new(const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk)
+{
+ struct sctp_packet *packet;
+ struct sctp_transport *transport;
+ __u16 sport;
+ __u16 dport;
+ __u32 vtag;
+
+ /* Get the source and destination port from the inbound packet. */
+ sport = ntohs(chunk->sctp_hdr->dest);
+ dport = ntohs(chunk->sctp_hdr->source);
+
+ /* The V-tag is going to be the same as the inbound packet if no
+ * association exists, otherwise, use the peer's vtag.
+ */
+ if (asoc) {
+ vtag = asoc->peer.i.init_tag;
+ } else {
+ /* Special case the INIT and stale COOKIE_ECHO as there is no
+ * vtag yet.
+ */
+ switch(chunk->chunk_hdr->type) {
+ case SCTP_CID_INIT:
+ {
+ sctp_init_chunk_t *init;
+
+ init = (sctp_init_chunk_t *)chunk->chunk_hdr;
+ vtag = ntohl(init->init_hdr.init_tag);
+ break;
+ }
+ default:
+ vtag = ntohl(chunk->sctp_hdr->vtag);
+ break;
+ }
+ }
+
+ /* Make a transport for the bucket, Eliza... */
+ transport = sctp_transport_new(sctp_source(chunk), GFP_ATOMIC);
+ if (!transport)
+ goto nomem;
+
+ /* Cache a route for the transport with the chunk's destination as
+ * the source address.
+ */
+ sctp_transport_route(transport, (union sctp_addr *)&chunk->dest,
+ sctp_sk(sctp_get_ctl_sock()));
+
+ packet = sctp_packet_init(&transport->packet, transport, sport, dport);
+ packet = sctp_packet_config(packet, vtag, 0);
+
+ return packet;
+
+nomem:
+ return NULL;
+}
+
+/* Free the packet allocated earlier for responding in the OOTB condition. */
+void sctp_ootb_pkt_free(struct sctp_packet *packet)
+{
+ sctp_transport_free(packet->transport);
+}
+
+/* Send a stale cookie error when a invalid COOKIE ECHO chunk is found */
+static void sctp_send_stale_cookie_err(const struct sctp_endpoint *ep,
+ const struct sctp_association *asoc,
+ const struct sctp_chunk *chunk,
+ sctp_cmd_seq_t *commands,
+ struct sctp_chunk *err_chunk)
+{
+ struct sctp_packet *packet;
+
+ if (err_chunk) {
+ packet = sctp_ootb_pkt_new(asoc, chunk);
+ if (packet) {
+ struct sctp_signed_cookie *cookie;
+
+ /* Override the OOTB vtag from the cookie. */
+ cookie = chunk->subh.cookie_hdr;
+ packet->vtag = cookie->c.peer_vtag;
+
+ /* Set the skb to the belonging sock for accounting. */
+ err_chunk->skb->sk = ep->base.sk;
+ sctp_packet_append_chunk(packet, err_chunk);
+ sctp_add_cmd_sf(commands, SCTP_CMD_SEND_PKT,
+ SCTP_PACKET(packet));
+ SCTP_INC_STATS(SctpOutCtrlChunks);
+ } else
+ sctp_chunk_free (err_chunk);
+ }
+}
+
+
+/* Process a data chunk */
+static int sctp_eat_data(const struct sctp_association *asoc,
+ struct sctp_chunk *chunk,
+ sctp_cmd_seq_t *commands)
+{
+ sctp_datahdr_t *data_hdr;
+ struct sctp_chunk *err;
+ size_t datalen;
+ sctp_verb_t deliver;
+ int tmp;
+ __u32 tsn;
+
+ data_hdr = chunk->subh.data_hdr = (sctp_datahdr_t *)chunk->skb->data;
+ skb_pull(chunk->skb, sizeof(sctp_datahdr_t));
+
+ tsn = ntohl(data_hdr->tsn);
+ SCTP_DEBUG_PRINTK("eat_data: TSN 0x%x.\n", tsn);
+
+ /* ASSERT: Now skb->data is really the user data. */
+
+ /* Process ECN based congestion.
+ *
+ * Since the chunk structure is reused for all chunks within
+ * a packet, we use ecn_ce_done to track if we've already
+ * done CE processing for this packet.
+ *
+ * We need to do ECN processing even if we plan to discard the
+ * chunk later.
+ */
+
+ if (!chunk->ecn_ce_done) {
+ struct sctp_af *af;
+ chunk->ecn_ce_done = 1;
+
+ af = sctp_get_af_specific(
+ ipver2af(chunk->skb->nh.iph->version));
+
+ if (af && af->is_ce(chunk->skb) && asoc->peer.ecn_capable) {
+ /* Do real work as sideffect. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_ECN_CE,
+ SCTP_U32(tsn));
+ }
+ }
+
+ tmp = sctp_tsnmap_check(&asoc->peer.tsn_map, tsn);
+ if (tmp < 0) {
+ /* The TSN is too high--silently discard the chunk and
+ * count on it getting retransmitted later.
+ */
+ return SCTP_IERROR_HIGH_TSN;
+ } else if (tmp > 0) {
+ /* This is a duplicate. Record it. */
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPORT_DUP, SCTP_U32(tsn));
+ return SCTP_IERROR_DUP_TSN;
+ }
+
+ /* This is a new TSN. */
+
+ /* Discard if there is no room in the receive window.
+ * Actually, allow a little bit of overflow (up to a MTU).
+ */
+ datalen = ntohs(chunk->chunk_hdr->length);
+ datalen -= sizeof(sctp_data_chunk_t);
+
+ deliver = SCTP_CMD_CHUNK_ULP;
+
+ /* Think about partial delivery. */
+ if ((datalen >= asoc->rwnd) && (!asoc->ulpq.pd_mode)) {
+
+ /* Even if we don't accept this chunk there is
+ * memory pressure.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_PART_DELIVER, SCTP_NULL());
+ }
+
+ /* Spill over rwnd a little bit. Note: While allowed, this spill over
+ * seems a bit troublesome in that frag_point varies based on
+ * PMTU. In cases, such as loopback, this might be a rather
+ * large spill over.
+ */
+ if (!asoc->rwnd || asoc->rwnd_over ||
+ (datalen > asoc->rwnd + asoc->frag_point)) {
+
+ /* If this is the next TSN, consider reneging to make
+ * room. Note: Playing nice with a confused sender. A
+ * malicious sender can still eat up all our buffer
+ * space and in the future we may want to detect and
+ * do more drastic reneging.
+ */
+ if (sctp_tsnmap_has_gap(&asoc->peer.tsn_map) &&
+ (sctp_tsnmap_get_ctsn(&asoc->peer.tsn_map) + 1) == tsn) {
+ SCTP_DEBUG_PRINTK("Reneging for tsn:%u\n", tsn);
+ deliver = SCTP_CMD_RENEGE;
+ } else {
+ SCTP_DEBUG_PRINTK("Discard tsn: %u len: %Zd, "
+ "rwnd: %d\n", tsn, datalen,
+ asoc->rwnd);
+ return SCTP_IERROR_IGNORE_TSN;
+ }
+ }
+
+ /*
+ * Section 3.3.10.9 No User Data (9)
+ *
+ * Cause of error
+ * ---------------
+ * No User Data: This error cause is returned to the originator of a
+ * DATA chunk if a received DATA chunk has no user data.
+ */
+ if (unlikely(0 == datalen)) {
+ err = sctp_make_abort_no_data(asoc, chunk, tsn);
+ if (err) {
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(err));
+ }
+ /* We are going to ABORT, so we might as well stop
+ * processing the rest of the chunks in the packet.
+ */
+ sctp_add_cmd_sf(commands, SCTP_CMD_DISCARD_PACKET,SCTP_NULL());
+ sctp_add_cmd_sf(commands, SCTP_CMD_ASSOC_FAILED,
+ SCTP_U32(SCTP_ERROR_NO_DATA));
+ SCTP_INC_STATS(SctpAborteds);
+ SCTP_DEC_STATS(SctpCurrEstab);
+ return SCTP_IERROR_NO_DATA;
+ }
+
+ /* If definately accepting the DATA chunk, record its TSN, otherwise
+ * wait for renege processing.
+ */
+ if (SCTP_CMD_CHUNK_ULP == deliver)
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPORT_TSN, SCTP_U32(tsn));
+
+ /* Note: Some chunks may get overcounted (if we drop) or overcounted
+ * if we renege and the chunk arrives again.
+ */
+ if (chunk->chunk_hdr->flags & SCTP_DATA_UNORDERED)
+ SCTP_INC_STATS(SctpInUnorderChunks);
+ else
+ SCTP_INC_STATS(SctpInOrderChunks);
+
+ /* RFC 2960 6.5 Stream Identifier and Stream Sequence Number
+ *
+ * If an endpoint receive a DATA chunk with an invalid stream
+ * identifier, it shall acknowledge the reception of the DATA chunk
+ * following the normal procedure, immediately send an ERROR chunk
+ * with cause set to "Invalid Stream Identifier" (See Section 3.3.10)
+ * and discard the DATA chunk.
+ */
+ if (ntohs(data_hdr->stream) >= asoc->c.sinit_max_instreams) {
+ err = sctp_make_op_error(asoc, chunk, SCTP_ERROR_INV_STRM,
+ &data_hdr->stream,
+ sizeof(data_hdr->stream));
+ if (err)
+ sctp_add_cmd_sf(commands, SCTP_CMD_REPLY,
+ SCTP_CHUNK(err));
+ return SCTP_IERROR_BAD_STREAM;
+ }
+
+ /* Send the data up to the user. Note: Schedule the
+ * SCTP_CMD_CHUNK_ULP cmd before the SCTP_CMD_GEN_SACK, as the SACK
+ * chunk needs the updated rwnd.
+ */
+ sctp_add_cmd_sf(commands, deliver, SCTP_CHUNK(chunk));
+
+ return SCTP_IERROR_NO_ERROR;
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/sm_statetable.c b/uClinux-2.4.31-uc0/net/sctp/sm_statetable.c
new file mode 100644
index 0000000..8967846
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/sm_statetable.c
@@ -0,0 +1,1004 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2001, 2004
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001 Intel Corp.
+ * Copyright (c) 2001 Nokia, Inc.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * These are the state tables for the SCTP state machine.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Hui Huang <hui.huang@nokia.com>
+ * Daisy Chang <daisyc@us.ibm.com>
+ * Ardelle Fan <ardelle.fan@intel.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/skbuff.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+static const sctp_sm_table_entry_t
+primitive_event_table[SCTP_NUM_PRIMITIVE_TYPES][SCTP_STATE_NUM_STATES];
+static const sctp_sm_table_entry_t
+other_event_table[SCTP_NUM_OTHER_TYPES][SCTP_STATE_NUM_STATES];
+static const sctp_sm_table_entry_t
+timeout_event_table[SCTP_NUM_TIMEOUT_TYPES][SCTP_STATE_NUM_STATES];
+
+static const sctp_sm_table_entry_t *sctp_chunk_event_lookup(sctp_cid_t cid,
+ sctp_state_t state);
+
+
+static const sctp_sm_table_entry_t bug = {
+ .fn = sctp_sf_bug,
+ .name = "sctp_sf_bug"
+};
+
+#define DO_LOOKUP(_max, _type, _table) \
+ if ((event_subtype._type > (_max))) { \
+ printk(KERN_WARNING \
+ "sctp table %p possible attack:" \
+ " event %d exceeds max %d\n", \
+ _table, event_subtype._type, _max); \
+ return &bug; \
+ } \
+ return &_table[event_subtype._type][(int)state];
+
+const sctp_sm_table_entry_t *sctp_sm_lookup_event(sctp_event_t event_type,
+ sctp_state_t state,
+ sctp_subtype_t event_subtype)
+{
+ switch (event_type) {
+ case SCTP_EVENT_T_CHUNK:
+ return sctp_chunk_event_lookup(event_subtype.chunk, state);
+ break;
+ case SCTP_EVENT_T_TIMEOUT:
+ DO_LOOKUP(SCTP_EVENT_TIMEOUT_MAX, timeout,
+ timeout_event_table);
+ break;
+
+ case SCTP_EVENT_T_OTHER:
+ DO_LOOKUP(SCTP_EVENT_OTHER_MAX, other, other_event_table);
+ break;
+
+ case SCTP_EVENT_T_PRIMITIVE:
+ DO_LOOKUP(SCTP_EVENT_PRIMITIVE_MAX, primitive,
+ primitive_event_table);
+ break;
+
+ default:
+ /* Yikes! We got an illegal event type. */
+ return &bug;
+ };
+}
+
+#define TYPE_SCTP_DATA { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_tabort_8_4_8, .name = "sctp_sf_tabort_8_4_8"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_eat_data_6_2, .name = "sctp_sf_eat_data_6_2"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_eat_data_6_2, .name = "sctp_sf_eat_data_6_2"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_eat_data_fast_4_4, .name = "sctp_sf_eat_data_fast_4_4"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+} /* TYPE_SCTP_DATA */
+
+#define TYPE_SCTP_INIT { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_do_5_1B_init, .name = "sctp_sf_do_5_1B_init"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_do_5_2_1_siminit, .name = "sctp_sf_do_5_2_1_siminit"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_do_5_2_1_siminit, .name = "sctp_sf_do_5_2_1_siminit"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_5_2_2_dupinit, .name = "sctp_sf_do_5_2_2_dupinit"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_do_5_2_2_dupinit, .name = "sctp_sf_do_5_2_2_dupinit"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_do_5_2_2_dupinit, .name = "sctp_sf_do_5_2_2_dupinit"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_do_5_2_2_dupinit, .name = "sctp_sf_do_5_2_2_dupinit"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_do_9_2_reshutack, .name = "sctp_sf_do_9_2_reshutack"}, \
+} /* TYPE_SCTP_INIT */
+
+#define TYPE_SCTP_INIT_ACK { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_do_5_1C_ack, .name = "sctp_sf_do_5_1C_ack"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+} /* TYPE_SCTP_INIT_ACK */
+
+#define TYPE_SCTP_SACK { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_tabort_8_4_8, .name = "sctp_sf_tabort_8_4_8"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_eat_sack_6_2, .name = "sctp_sf_eat_sack_6_2"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_eat_sack_6_2, .name = "sctp_sf_eat_sack_6_2"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_eat_sack_6_2, .name = "sctp_sf_eat_sack_6_2"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_eat_sack_6_2, .name = "sctp_sf_eat_sack_6_2"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+} /* TYPE_SCTP_SACK */
+
+#define TYPE_SCTP_HEARTBEAT { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_tabort_8_4_8, .name = "sctp_sf_tabort_8_4_8"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_beat_8_3, .name = "sctp_sf_beat_8_3"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_beat_8_3, .name = "sctp_sf_beat_8_3"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_beat_8_3, .name = "sctp_sf_beat_8_3"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_beat_8_3, .name = "sctp_sf_beat_8_3"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_beat_8_3, .name = "sctp_sf_beat_8_3"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ /* This should not happen, but we are nice. */ \
+ {.fn = sctp_sf_beat_8_3, .name = "sctp_sf_beat_8_3"}, \
+} /* TYPE_SCTP_HEARTBEAT */
+
+#define TYPE_SCTP_HEARTBEAT_ACK { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_tabort_8_4_8, .name = "sctp_sf_tabort_8_4_8"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_violation, .name = "sctp_sf_violation"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_backbeat_8_3, .name = "sctp_sf_backbeat_8_3"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_backbeat_8_3, .name = "sctp_sf_backbeat_8_3"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_backbeat_8_3, .name = "sctp_sf_backbeat_8_3"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_backbeat_8_3, .name = "sctp_sf_backbeat_8_3"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+} /* TYPE_SCTP_HEARTBEAT_ACK */
+
+#define TYPE_SCTP_ABORT { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_pdiscard, .name = "sctp_sf_pdiscard"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_cookie_wait_abort, .name = "sctp_sf_cookie_wait_abort"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_cookie_echoed_abort, \
+ .name = "sctp_sf_cookie_echoed_abort"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_9_1_abort, .name = "sctp_sf_do_9_1_abort"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_shutdown_pending_abort, \
+ .name = "sctp_sf_shutdown_pending_abort"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_shutdown_sent_abort, \
+ .name = "sctp_sf_shutdown_sent_abort"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_do_9_1_abort, .name = "sctp_sf_do_9_1_abort"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_shutdown_ack_sent_abort, \
+ .name = "sctp_sf_shutdown_ack_sent_abort"}, \
+} /* TYPE_SCTP_ABORT */
+
+#define TYPE_SCTP_SHUTDOWN { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_tabort_8_4_8, .name = "sctp_sf_tabort_8_4_8"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_9_2_shutdown, .name = "sctp_sf_do_9_2_shutdown"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_do_9_2_shutdown_ack, \
+ .name = "sctp_sf_do_9_2_shutdown_ack"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+} /* TYPE_SCTP_SHUTDOWN */
+
+#define TYPE_SCTP_SHUTDOWN_ACK { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_do_8_5_1_E_sa, .name = "sctp_sf_do_8_5_1_E_sa"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_do_8_5_1_E_sa, .name = "sctp_sf_do_8_5_1_E_sa"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_violation, .name = "sctp_sf_violation"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_violation, .name = "sctp_sf_violation"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_do_9_2_final, .name = "sctp_sf_do_9_2_final"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_violation, .name = "sctp_sf_violation"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_do_9_2_final, .name = "sctp_sf_do_9_2_final"}, \
+} /* TYPE_SCTP_SHUTDOWN_ACK */
+
+#define TYPE_SCTP_ERROR { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_tabort_8_4_8, .name = "sctp_sf_tabort_8_4_8"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_cookie_echoed_err, .name = "sctp_sf_cookie_echoed_err"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_operr_notify, .name = "sctp_sf_operr_notify"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_operr_notify, .name = "sctp_sf_operr_notify"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_operr_notify, .name = "sctp_sf_operr_notify"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+} /* TYPE_SCTP_ERROR */
+
+#define TYPE_SCTP_COOKIE_ECHO { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_do_5_1D_ce, .name = "sctp_sf_do_5_1D_ce"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_do_5_2_4_dupcook, .name = "sctp_sf_do_5_2_4_dupcook"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_do_5_2_4_dupcook, .name = "sctp_sf_do_5_2_4_dupcook"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_5_2_4_dupcook, .name = "sctp_sf_do_5_2_4_dupcook"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_do_5_2_4_dupcook, .name = "sctp_sf_do_5_2_4_dupcook"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_do_5_2_4_dupcook, .name = "sctp_sf_do_5_2_4_dupcook"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_do_5_2_4_dupcook, .name = "sctp_sf_do_5_2_4_dupcook"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_do_5_2_4_dupcook, .name = "sctp_sf_do_5_2_4_dupcook"}, \
+} /* TYPE_SCTP_COOKIE_ECHO */
+
+#define TYPE_SCTP_COOKIE_ACK { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_do_5_1E_ca, .name = "sctp_sf_do_5_1E_ca"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+} /* TYPE_SCTP_COOKIE_ACK */
+
+#define TYPE_SCTP_ECN_ECNE { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_do_ecne, .name = "sctp_sf_do_ecne"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_ecne, .name = "sctp_sf_do_ecne"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_do_ecne, .name = "sctp_sf_do_ecne"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_do_ecne, .name = "sctp_sf_do_ecne"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_do_ecne, .name = "sctp_sf_do_ecne"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+} /* TYPE_SCTP_ECN_ECNE */
+
+#define TYPE_SCTP_ECN_CWR { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_ecn_cwr, .name = "sctp_sf_do_ecn_cwr"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_do_ecn_cwr, .name = "sctp_sf_do_ecn_cwr"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_do_ecn_cwr, .name = "sctp_sf_do_ecn_cwr"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+} /* TYPE_SCTP_ECN_CWR */
+
+#define TYPE_SCTP_SHUTDOWN_COMPLETE { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_do_4_C, .name = "sctp_sf_do_4_C"}, \
+} /* TYPE_SCTP_SHUTDOWN_COMPLETE */
+
+/* The primary index for this table is the chunk type.
+ * The secondary index for this table is the state.
+ *
+ * For base protocol (RFC 2960).
+ */
+static const sctp_sm_table_entry_t chunk_event_table[SCTP_NUM_BASE_CHUNK_TYPES][SCTP_STATE_NUM_STATES] = {
+ TYPE_SCTP_DATA,
+ TYPE_SCTP_INIT,
+ TYPE_SCTP_INIT_ACK,
+ TYPE_SCTP_SACK,
+ TYPE_SCTP_HEARTBEAT,
+ TYPE_SCTP_HEARTBEAT_ACK,
+ TYPE_SCTP_ABORT,
+ TYPE_SCTP_SHUTDOWN,
+ TYPE_SCTP_SHUTDOWN_ACK,
+ TYPE_SCTP_ERROR,
+ TYPE_SCTP_COOKIE_ECHO,
+ TYPE_SCTP_COOKIE_ACK,
+ TYPE_SCTP_ECN_ECNE,
+ TYPE_SCTP_ECN_CWR,
+ TYPE_SCTP_SHUTDOWN_COMPLETE,
+}; /* state_fn_t chunk_event_table[][] */
+
+#define TYPE_SCTP_ASCONF { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_asconf, .name = "sctp_sf_do_asconf"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+} /* TYPE_SCTP_ASCONF */
+
+#define TYPE_SCTP_ASCONF_ACK { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_asconf_ack, .name = "sctp_sf_do_asconf_ack"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+} /* TYPE_SCTP_ASCONF_ACK */
+
+/* The primary index for this table is the chunk type.
+ * The secondary index for this table is the state.
+ */
+static const sctp_sm_table_entry_t addip_chunk_event_table[SCTP_NUM_ADDIP_CHUNK_TYPES][SCTP_STATE_NUM_STATES] = {
+ TYPE_SCTP_ASCONF,
+ TYPE_SCTP_ASCONF_ACK,
+}; /*state_fn_t addip_chunk_event_table[][] */
+
+#define TYPE_SCTP_FWD_TSN { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_tabort_8_4_8, .name = "sctp_sf_tabort_8_4_8"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_eat_fwd_tsn, .name = "sctp_sf_eat_fwd_tsn"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_eat_fwd_tsn, .name = "sctp_sf_eat_fwd_tsn"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_eat_fwd_tsn_fast, .name = "sctp_sf_eat_fwd_tsn_fast"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_discard_chunk, .name = "sctp_sf_discard_chunk"}, \
+} /* TYPE_SCTP_FWD_TSN */
+
+/* The primary index for this table is the chunk type.
+ * The secondary index for this table is the state.
+ */
+static const sctp_sm_table_entry_t prsctp_chunk_event_table[SCTP_NUM_PRSCTP_CHUNK_TYPES][SCTP_STATE_NUM_STATES] = {
+ TYPE_SCTP_FWD_TSN,
+}; /*state_fn_t prsctp_chunk_event_table[][] */
+
+static const sctp_sm_table_entry_t
+chunk_event_table_unknown[SCTP_STATE_NUM_STATES] = {
+ /* SCTP_STATE_EMPTY */
+ {.fn = sctp_sf_ootb, .name = "sctp_sf_ootb"},
+ /* SCTP_STATE_CLOSED */
+ {.fn = sctp_sf_tabort_8_4_8, .name = "sctp_sf_tabort_8_4_8"},
+ /* SCTP_STATE_COOKIE_WAIT */
+ {.fn = sctp_sf_unk_chunk, .name = "sctp_sf_unk_chunk"},
+ /* SCTP_STATE_COOKIE_ECHOED */
+ {.fn = sctp_sf_unk_chunk, .name = "sctp_sf_unk_chunk"},
+ /* SCTP_STATE_ESTABLISHED */
+ {.fn = sctp_sf_unk_chunk, .name = "sctp_sf_unk_chunk"},
+ /* SCTP_STATE_SHUTDOWN_PENDING */
+ {.fn = sctp_sf_unk_chunk, .name = "sctp_sf_unk_chunk"},
+ /* SCTP_STATE_SHUTDOWN_SENT */
+ {.fn = sctp_sf_unk_chunk, .name = "sctp_sf_unk_chunk"},
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */
+ {.fn = sctp_sf_unk_chunk, .name = "sctp_sf_unk_chunk"},
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */
+ {.fn = sctp_sf_unk_chunk, .name = "sctp_sf_unk_chunk"},
+}; /* chunk unknown */
+
+
+#define TYPE_SCTP_PRIMITIVE_ASSOCIATE { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_do_prm_asoc, .name = "sctp_sf_do_prm_asoc"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_not_impl, .name = "sctp_sf_not_impl"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_not_impl, .name = "sctp_sf_not_impl"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_not_impl, .name = "sctp_sf_not_impl"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_not_impl, .name = "sctp_sf_not_impl"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_not_impl, .name = "sctp_sf_not_impl"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_not_impl, .name = "sctp_sf_not_impl"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_not_impl, .name = "sctp_sf_not_impl"}, \
+} /* TYPE_SCTP_PRIMITIVE_ASSOCIATE */
+
+#define TYPE_SCTP_PRIMITIVE_SHUTDOWN { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_error_closed, .name = "sctp_sf_error_closed"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_cookie_wait_prm_shutdown, \
+ .name = "sctp_sf_cookie_wait_prm_shutdown"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_cookie_echoed_prm_shutdown, \
+ .name = "sctp_sf_cookie_echoed_prm_shutdown"},\
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_9_2_prm_shutdown, \
+ .name = "sctp_sf_do_9_2_prm_shutdown"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_ignore_primitive, .name = "sctp_sf_ignore_primitive"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_ignore_primitive, .name = "sctp_sf_ignore_primitive"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_ignore_primitive, .name = "sctp_sf_ignore_primitive"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_ignore_primitive, .name = "sctp_sf_ignore_primitive"}, \
+} /* TYPE_SCTP_PRIMITIVE_SHUTDOWN */
+
+#define TYPE_SCTP_PRIMITIVE_ABORT { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_error_closed, .name = "sctp_sf_error_closed"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_cookie_wait_prm_abort, \
+ .name = "sctp_sf_cookie_wait_prm_abort"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_cookie_echoed_prm_abort, \
+ .name = "sctp_sf_cookie_echoed_prm_abort"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_9_1_prm_abort, \
+ .name = "sctp_sf_do_9_1_prm_abort"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_shutdown_pending_prm_abort, \
+ .name = "sctp_sf_shutdown_pending_prm_abort"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_shutdown_sent_prm_abort, \
+ .name = "sctp_sf_shutdown_sent_prm_abort"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_do_9_1_prm_abort, \
+ .name = "sctp_sf_do_9_1_prm_abort"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_shutdown_ack_sent_prm_abort, \
+ .name = "sctp_sf_shutdown_ack_sent_prm_abort"}, \
+} /* TYPE_SCTP_PRIMITIVE_ABORT */
+
+#define TYPE_SCTP_PRIMITIVE_SEND { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_error_closed, .name = "sctp_sf_error_closed"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_do_prm_send, .name = "sctp_sf_do_prm_send"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_do_prm_send, .name = "sctp_sf_do_prm_send"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_prm_send, .name = "sctp_sf_do_prm_send"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_error_shutdown, .name = "sctp_sf_error_shutdown"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_error_shutdown, .name = "sctp_sf_error_shutdown"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_error_shutdown, .name = "sctp_sf_error_shutdown"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_error_shutdown, .name = "sctp_sf_error_shutdown"}, \
+} /* TYPE_SCTP_PRIMITIVE_SEND */
+
+#define TYPE_SCTP_PRIMITIVE_REQUESTHEARTBEAT { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_do_prm_requestheartbeat, \
+ .name = "sctp_sf_do_prm_requestheartbeat"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_do_prm_requestheartbeat, \
+ .name = "sctp_sf_do_prm_requestheartbeat"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_prm_requestheartbeat, \
+ .name = "sctp_sf_do_prm_requestheartbeat"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_do_prm_requestheartbeat, \
+ .name = "sctp_sf_do_prm_requestheartbeat"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_do_prm_requestheartbeat, \
+ .name = "sctp_sf_do_prm_requestheartbeat"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_do_prm_requestheartbeat, \
+ .name = "sctp_sf_do_prm_requestheartbeat"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_do_prm_requestheartbeat, \
+ .name = "sctp_sf_do_prm_requestheartbeat"}, \
+} /* TYPE_SCTP_PRIMITIVE_REQUESTHEARTBEAT */
+
+#define TYPE_SCTP_PRIMITIVE_ASCONF { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_error_closed, .name = "sctp_sf_error_closed"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_error_closed, .name = "sctp_sf_error_closed"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_error_closed, .name = "sctp_sf_error_closed"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_prm_asconf, .name = "sctp_sf_do_prm_asconf"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_error_shutdown, .name = "sctp_sf_error_shutdown"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_error_shutdown, .name = "sctp_sf_error_shutdown"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_error_shutdown, .name = "sctp_sf_error_shutdown"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_error_shutdown, .name = "sctp_sf_error_shutdown"}, \
+} /* TYPE_SCTP_PRIMITIVE_REQUESTHEARTBEAT */
+
+/* The primary index for this table is the primitive type.
+ * The secondary index for this table is the state.
+ */
+static const sctp_sm_table_entry_t primitive_event_table[SCTP_NUM_PRIMITIVE_TYPES][SCTP_STATE_NUM_STATES] = {
+ TYPE_SCTP_PRIMITIVE_ASSOCIATE,
+ TYPE_SCTP_PRIMITIVE_SHUTDOWN,
+ TYPE_SCTP_PRIMITIVE_ABORT,
+ TYPE_SCTP_PRIMITIVE_SEND,
+ TYPE_SCTP_PRIMITIVE_REQUESTHEARTBEAT,
+ TYPE_SCTP_PRIMITIVE_ASCONF,
+};
+
+#define TYPE_SCTP_OTHER_NO_PENDING_TSN { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_ignore_other, .name = "sctp_sf_ignore_other"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_ignore_other, .name = "sctp_sf_ignore_other"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_ignore_other, .name = "sctp_sf_ignore_other"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_ignore_other, .name = "sctp_sf_ignore_other"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_do_9_2_start_shutdown, \
+ .name = "sctp_do_9_2_start_shutdown"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_ignore_other, .name = "sctp_sf_ignore_other"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_do_9_2_shutdown_ack, \
+ .name = "sctp_sf_do_9_2_shutdown_ack"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_ignore_other, .name = "sctp_sf_ignore_other"}, \
+}
+
+#define TYPE_SCTP_OTHER_ICMP_PROTO_UNREACH { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_ignore_other, .name = "sctp_sf_ignore_other"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_cookie_wait_icmp_abort, \
+ .name = "sctp_sf_cookie_wait_icmp_abort"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_ignore_other, .name = "sctp_sf_ignore_other"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_ignore_other, .name = "sctp_sf_ignore_other"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_ignore_other, .name = "sctp_sf_ignore_other"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_ignore_other, .name = "sctp_sf_ignore_other"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_ignore_other, .name = "sctp_sf_ignore_other"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_ignore_other, .name = "sctp_sf_ignore_other"}, \
+}
+
+static const sctp_sm_table_entry_t other_event_table[SCTP_NUM_OTHER_TYPES][SCTP_STATE_NUM_STATES] = {
+ TYPE_SCTP_OTHER_NO_PENDING_TSN,
+ TYPE_SCTP_OTHER_ICMP_PROTO_UNREACH,
+};
+
+#define TYPE_SCTP_EVENT_TIMEOUT_NONE { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+}
+
+#define TYPE_SCTP_EVENT_TIMEOUT_T1_COOKIE { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_t1_timer_expire, .name = "sctp_sf_t1_timer_expire"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+}
+
+#define TYPE_SCTP_EVENT_TIMEOUT_T1_INIT { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_t1_timer_expire, .name = "sctp_sf_t1_timer_expire"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+}
+
+#define TYPE_SCTP_EVENT_TIMEOUT_T2_SHUTDOWN { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_t2_timer_expire, .name = "sctp_sf_t2_timer_expire"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_t2_timer_expire, .name = "sctp_sf_t2_timer_expire"}, \
+}
+
+#define TYPE_SCTP_EVENT_TIMEOUT_T3_RTX { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_do_6_3_3_rtx, .name = "sctp_sf_do_6_3_3_rtx"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_6_3_3_rtx, .name = "sctp_sf_do_6_3_3_rtx"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_do_6_3_3_rtx, .name = "sctp_sf_do_6_3_3_rtx"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_do_6_3_3_rtx, .name = "sctp_sf_do_6_3_3_rtx"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+}
+
+#define TYPE_SCTP_EVENT_TIMEOUT_T4_RTO { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_t4_timer_expire, .name = "sctp_sf_t4_timer_expire"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+}
+
+#define TYPE_SCTP_EVENT_TIMEOUT_T5_SHUTDOWN_GUARD { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_t5_timer_expire, .name = "sctp_sf_t5_timer_expire"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_t5_timer_expire, .name = "sctp_sf_t5_timer_expire"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+}
+
+#define TYPE_SCTP_EVENT_TIMEOUT_HEARTBEAT { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_sendbeat_8_3, .name = "sctp_sf_sendbeat_8_3"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_sendbeat_8_3, .name = "sctp_sf_sendbeat_8_3"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_sendbeat_8_3, .name = "sctp_sf_sendbeat_8_3"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+}
+
+#define TYPE_SCTP_EVENT_TIMEOUT_SACK { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_bug, .name = "sctp_sf_bug"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_do_6_2_sack, .name = "sctp_sf_do_6_2_sack"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_do_6_2_sack, .name = "sctp_sf_do_6_2_sack"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_do_6_2_sack, .name = "sctp_sf_do_6_2_sack"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+}
+
+#define TYPE_SCTP_EVENT_TIMEOUT_AUTOCLOSE { \
+ /* SCTP_STATE_EMPTY */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_CLOSED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_WAIT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_COOKIE_ECHOED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_ESTABLISHED */ \
+ {.fn = sctp_sf_autoclose_timer_expire, \
+ .name = "sctp_sf_autoclose_timer_expire"}, \
+ /* SCTP_STATE_SHUTDOWN_PENDING */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_SENT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_RECEIVED */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+ /* SCTP_STATE_SHUTDOWN_ACK_SENT */ \
+ {.fn = sctp_sf_timer_ignore, .name = "sctp_sf_timer_ignore"}, \
+}
+
+static const sctp_sm_table_entry_t timeout_event_table[SCTP_NUM_TIMEOUT_TYPES][SCTP_STATE_NUM_STATES] = {
+ TYPE_SCTP_EVENT_TIMEOUT_NONE,
+ TYPE_SCTP_EVENT_TIMEOUT_T1_COOKIE,
+ TYPE_SCTP_EVENT_TIMEOUT_T1_INIT,
+ TYPE_SCTP_EVENT_TIMEOUT_T2_SHUTDOWN,
+ TYPE_SCTP_EVENT_TIMEOUT_T3_RTX,
+ TYPE_SCTP_EVENT_TIMEOUT_T4_RTO,
+ TYPE_SCTP_EVENT_TIMEOUT_T5_SHUTDOWN_GUARD,
+ TYPE_SCTP_EVENT_TIMEOUT_HEARTBEAT,
+ TYPE_SCTP_EVENT_TIMEOUT_SACK,
+ TYPE_SCTP_EVENT_TIMEOUT_AUTOCLOSE,
+};
+
+static const sctp_sm_table_entry_t *sctp_chunk_event_lookup(sctp_cid_t cid,
+ sctp_state_t state)
+{
+ if (state > SCTP_STATE_MAX)
+ return &bug;
+
+ if (cid >= 0 && cid <= SCTP_CID_BASE_MAX)
+ return &chunk_event_table[cid][state];
+
+ if (sctp_prsctp_enable) {
+ if (cid == SCTP_CID_FWD_TSN)
+ return &prsctp_chunk_event_table[0][state];
+ }
+
+ if (sctp_addip_enable) {
+ if (cid == SCTP_CID_ASCONF)
+ return &addip_chunk_event_table[0][state];
+
+ if (cid == SCTP_CID_ASCONF_ACK)
+ return &addip_chunk_event_table[1][state];
+ }
+
+ return &chunk_event_table_unknown[state];
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/socket.c b/uClinux-2.4.31-uc0/net/sctp/socket.c
new file mode 100644
index 0000000..277b19f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/socket.c
@@ -0,0 +1,4766 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2001, 2004
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001-2003 Intel Corp.
+ * Copyright (c) 2001-2002 Nokia, Inc.
+ * Copyright (c) 2001 La Monte H.P. Yarroll
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * These functions interface with the sockets layer to implement the
+ * SCTP Extensions for the Sockets API.
+ *
+ * Note that the descriptions from the specification are USER level
+ * functions--this file is the functions which populate the struct proto
+ * for SCTP which is the BOTTOM of the sockets interface.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Narasimha Budihal <narsi@refcode.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Xingang Guo <xingang.guo@intel.com>
+ * Daisy Chang <daisyc@us.ibm.com>
+ * Sridhar Samudrala <samudrala@us.ibm.com>
+ * Inaky Perez-Gonzalez <inaky.gonzalez@intel.com>
+ * Ardelle Fan <ardelle.fan@intel.com>
+ * Ryan Layer <rmlayer@us.ibm.com>
+ * Anup Pemmaiah <pemmaiah@cc.usu.edu>
+ * Kevin Gao <kevin.gao@intel.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/wait.h>
+#include <linux/time.h>
+#include <linux/ip.h>
+#include <linux/fcntl.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/crypto.h>
+
+#include <net/ip.h>
+#include <net/icmp.h>
+#include <net/route.h>
+#include <net/ipv6.h>
+#include <net/inet_common.h>
+
+#include <linux/socket.h> /* for sa_family_t */
+#include <net/sock.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+/* WARNING: Please do not remove the SCTP_STATIC attribute to
+ * any of the functions below as they are used to export functions
+ * used by a project regression testsuite.
+ */
+
+/* Forward declarations for internal helper functions. */
+static int sctp_writeable(struct sock *sk);
+static void sctp_wfree(struct sk_buff *skb);
+static int sctp_wait_for_sndbuf(struct sctp_association *, long *timeo_p,
+ size_t msg_len);
+static int sctp_wait_for_packet(struct sock * sk, int *err, long *timeo_p);
+static int sctp_wait_for_connect(struct sctp_association *, long *timeo_p);
+static int sctp_wait_for_accept(struct sock *sk, long timeo);
+static void sctp_wait_for_close(struct sock *sk, long timeo);
+static struct sctp_af *sctp_sockaddr_af(struct sctp_opt *opt,
+ union sctp_addr *addr, int len);
+static int sctp_bindx_add(struct sock *, struct sockaddr *, int);
+static int sctp_bindx_rem(struct sock *, struct sockaddr *, int);
+static int sctp_send_asconf_add_ip(struct sock *, struct sockaddr *, int);
+static int sctp_send_asconf_del_ip(struct sock *, struct sockaddr *, int);
+static int sctp_send_asconf(struct sctp_association *asoc,
+ struct sctp_chunk *chunk);
+static int sctp_do_bind(struct sock *, union sctp_addr *, int);
+static int sctp_autobind(struct sock *sk);
+static void sctp_sock_migrate(struct sock *, struct sock *,
+ struct sctp_association *, sctp_socket_type_t);
+static char *sctp_hmac_alg = SCTP_COOKIE_HMAC_ALG;
+
+extern kmem_cache_t *sctp_bucket_cachep;
+extern int sctp_assoc_valid(struct sock *sk, struct sctp_association *asoc);
+
+/* Get the sndbuf space available at the time on the association. */
+static inline int sctp_wspace(struct sctp_association *asoc)
+{
+ struct sock *sk = asoc->base.sk;
+ int amt = 0;
+
+ amt = sk->sndbuf - asoc->sndbuf_used;
+ if (amt < 0)
+ amt = 0;
+ return amt;
+}
+
+/* Increment the used sndbuf space count of the corresponding association by
+ * the size of the outgoing data chunk.
+ * Also, set the skb destructor for sndbuf accounting later.
+ *
+ * Since it is always 1-1 between chunk and skb, and also a new skb is always
+ * allocated for chunk bundling in sctp_packet_transmit(), we can use the
+ * destructor in the data chunk skb for the purpose of the sndbuf space
+ * tracking.
+ */
+static inline void sctp_set_owner_w(struct sctp_chunk *chunk)
+{
+ struct sctp_association *asoc = chunk->asoc;
+ struct sock *sk = asoc->base.sk;
+
+ /* The sndbuf space is tracked per association. */
+ sctp_association_hold(asoc);
+
+ chunk->skb->destructor = sctp_wfree;
+ /* Save the chunk pointer in skb for sctp_wfree to use later. */
+ *((struct sctp_chunk **)(chunk->skb->cb)) = chunk;
+
+ asoc->sndbuf_used += SCTP_DATA_SNDSIZE(chunk);
+ sk->wmem_queued += SCTP_DATA_SNDSIZE(chunk);
+}
+
+/* Verify that this is a valid address. */
+static inline int sctp_verify_addr(struct sock *sk, union sctp_addr *addr,
+ int len)
+{
+ struct sctp_af *af;
+
+ /* Verify basic sockaddr. */
+ af = sctp_sockaddr_af(sctp_sk(sk), addr, len);
+ if (!af)
+ return -EINVAL;
+
+ /* Is this a valid SCTP address? */
+ if (!af->addr_valid(addr, sctp_sk(sk)))
+ return -EINVAL;
+
+ if (!sctp_sk(sk)->pf->send_verify(sctp_sk(sk), (addr)))
+ return -EINVAL;
+
+ return 0;
+}
+
+/* Look up the association by its id. If this is not a UDP-style
+ * socket, the ID field is always ignored.
+ */
+struct sctp_association *sctp_id2assoc(struct sock *sk, sctp_assoc_t id)
+{
+ struct sctp_association *asoc = NULL;
+
+ /* If this is not a UDP-style socket, assoc id should be ignored. */
+ if (!sctp_style(sk, UDP)) {
+ /* Return NULL if the socket state is not ESTABLISHED. It
+ * could be a TCP-style listening socket or a socket which
+ * hasn't yet called connect() to establish an association.
+ */
+ if (!sctp_sstate(sk, ESTABLISHED))
+ return NULL;
+
+ /* Get the first and the only association from the list. */
+ if (!list_empty(&sctp_sk(sk)->ep->asocs))
+ asoc = list_entry(sctp_sk(sk)->ep->asocs.next,
+ struct sctp_association, asocs);
+ return asoc;
+ }
+
+ /* Otherwise this is a UDP-style socket. */
+ asoc = (struct sctp_association *)id;
+ if (!sctp_assoc_valid(sk, asoc))
+ return NULL;
+
+ return asoc;
+}
+
+/* Look up the transport from an address and an assoc id. If both address and
+ * id are specified, the associations matching the address and the id should be
+ * the same.
+ */
+static struct sctp_transport *sctp_addr_id2transport(struct sock *sk,
+ struct sockaddr_storage *addr,
+ sctp_assoc_t id)
+{
+ struct sctp_association *addr_asoc = NULL, *id_asoc = NULL;
+ struct sctp_transport *transport;
+ union sctp_addr *laddr = (union sctp_addr *)addr;
+
+ laddr->v4.sin_port = ntohs(laddr->v4.sin_port);
+ addr_asoc = sctp_endpoint_lookup_assoc(sctp_sk(sk)->ep,
+ (union sctp_addr *)addr,
+ &transport);
+ laddr->v4.sin_port = htons(laddr->v4.sin_port);
+
+ if (!addr_asoc)
+ return NULL;
+
+ id_asoc = sctp_id2assoc(sk, id);
+ if (id_asoc && (id_asoc != addr_asoc))
+ return NULL;
+
+ sctp_get_pf_specific(sk->family)->addr_v4map(sctp_sk(sk),
+ (union sctp_addr *)addr);
+
+ return transport;
+}
+
+/* API 3.1.2 bind() - UDP Style Syntax
+ * The syntax of bind() is,
+ *
+ * ret = bind(int sd, struct sockaddr *addr, int addrlen);
+ *
+ * sd - the socket descriptor returned by socket().
+ * addr - the address structure (struct sockaddr_in or struct
+ * sockaddr_in6 [RFC 2553]),
+ * addr_len - the size of the address structure.
+ */
+SCTP_STATIC int sctp_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
+{
+ int retval = 0;
+
+ sctp_lock_sock(sk);
+
+ SCTP_DEBUG_PRINTK("sctp_bind(sk: %p, uaddr: %p, addr_len: %d)\n",
+ sk, uaddr, addr_len);
+
+ /* Disallow binding twice. */
+ if (!sctp_sk(sk)->ep->base.bind_addr.port)
+ retval = sctp_do_bind(sk, (union sctp_addr *)uaddr,
+ addr_len);
+ else
+ retval = -EINVAL;
+
+ sctp_release_sock(sk);
+
+ return retval;
+}
+
+static long sctp_get_port_local(struct sock *, union sctp_addr *);
+
+/* Verify this is a valid sockaddr. */
+static struct sctp_af *sctp_sockaddr_af(struct sctp_opt *opt,
+ union sctp_addr *addr, int len)
+{
+ struct sctp_af *af;
+
+ /* Check minimum size. */
+ if (len < sizeof (struct sockaddr))
+ return NULL;
+
+ /* Does this PF support this AF? */
+ if (!opt->pf->af_supported(addr->sa.sa_family, opt))
+ return NULL;
+
+ /* If we get this far, af is valid. */
+ af = sctp_get_af_specific(addr->sa.sa_family);
+
+ if (len < af->sockaddr_len)
+ return NULL;
+
+ return af;
+}
+
+/* Bind a local address either to an endpoint or to an association. */
+SCTP_STATIC int sctp_do_bind(struct sock *sk, union sctp_addr *addr, int len)
+{
+ struct sctp_opt *sp = sctp_sk(sk);
+ struct sctp_endpoint *ep = sp->ep;
+ struct sctp_bind_addr *bp = &ep->base.bind_addr;
+ struct sctp_af *af;
+ unsigned short snum;
+ int ret = 0;
+
+ SCTP_DEBUG_PRINTK("sctp_do_bind(sk: %p, newaddr: %p, len: %d)\n",
+ sk, addr, len);
+
+ /* Common sockaddr verification. */
+ af = sctp_sockaddr_af(sp, addr, len);
+ if (!af)
+ return -EINVAL;
+
+ /* PF specific bind() address verification. */
+ if (!sp->pf->bind_verify(sp, addr))
+ return -EADDRNOTAVAIL;
+
+ snum= ntohs(addr->v4.sin_port);
+
+ SCTP_DEBUG_PRINTK("sctp_do_bind: port: %d, new port: %d\n",
+ bp->port, snum);
+
+ /* We must either be unbound, or bind to the same port. */
+ if (bp->port && (snum != bp->port)) {
+ SCTP_DEBUG_PRINTK("sctp_do_bind:"
+ " New port %d does not match existing port "
+ "%d.\n", snum, bp->port);
+ return -EINVAL;
+ }
+
+ if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))
+ return -EACCES;
+
+ /* Make sure we are allowed to bind here.
+ * The function sctp_get_port_local() does duplicate address
+ * detection.
+ */
+ if ((ret = sctp_get_port_local(sk, addr))) {
+ if (ret == (long) sk) {
+ /* This endpoint has a conflicting address. */
+ return -EINVAL;
+ } else {
+ return -EADDRINUSE;
+ }
+ }
+
+ /* Refresh ephemeral port. */
+ if (!bp->port)
+ bp->port = sk->num;
+
+ /* Add the address to the bind address list. */
+ sctp_local_bh_disable();
+ sctp_write_lock(&ep->base.addr_lock);
+
+ /* Use GFP_ATOMIC since BHs are disabled. */
+ addr->v4.sin_port = ntohs(addr->v4.sin_port);
+ ret = sctp_add_bind_addr(bp, addr, GFP_ATOMIC);
+ addr->v4.sin_port = htons(addr->v4.sin_port);
+ sctp_write_unlock(&ep->base.addr_lock);
+ sctp_local_bh_enable();
+
+ /* Copy back into socket for getsockname() use. */
+ if (!ret) {
+ sk->sport = htons(sk->num);
+ af->to_sk_saddr(addr, sk);
+ }
+
+ return ret;
+}
+
+ /* ADDIP Section 4.1.1 Congestion Control of ASCONF Chunks
+ *
+ * R1) One and only one ASCONF Chunk MAY be in transit and unacknowledged
+ * at any one time. If a sender, after sending an ASCONF chunk, decides
+ * it needs to transfer another ASCONF Chunk, it MUST wait until the
+ * ASCONF-ACK Chunk returns from the previous ASCONF Chunk before sending a
+ * subsequent ASCONF. Note this restriction binds each side, so at any
+ * time two ASCONF may be in-transit on any given association (one sent
+ * from each endpoint).
+ */
+static int sctp_send_asconf(struct sctp_association *asoc,
+ struct sctp_chunk *chunk)
+{
+ int retval = 0;
+
+ /* If there is an outstanding ASCONF chunk, queue it for later
+ * transmission.
+ */
+ if (asoc->addip_last_asconf) {
+ __skb_queue_tail(&asoc->addip_chunks, (struct sk_buff *)chunk);
+ goto out;
+ }
+
+ /* Hold the chunk until an ASCONF_ACK is received. */
+ sctp_chunk_hold(chunk);
+ retval = sctp_primitive_ASCONF(asoc, chunk);
+ if (retval)
+ sctp_chunk_free(chunk);
+ else
+ asoc->addip_last_asconf = chunk;
+
+out:
+ return retval;
+}
+
+/* Add a list of addresses as bind addresses to local endpoint or
+ * association.
+ *
+ * Basically run through each address specified in the addrs/addrcnt
+ * array/length pair, determine if it is IPv6 or IPv4 and call
+ * sctp_do_bind() on it.
+ *
+ * If any of them fails, then the operation will be reversed and the
+ * ones that were added will be removed.
+ *
+ * Only sctp_setsockopt_bindx() is supposed to call this function.
+ */
+int sctp_bindx_add(struct sock *sk, struct sockaddr *addrs, int addrcnt)
+{
+ int cnt;
+ int retval = 0;
+ void *addr_buf;
+ struct sockaddr *sa_addr;
+ struct sctp_af *af;
+
+ SCTP_DEBUG_PRINTK("sctp_bindx_add (sk: %p, addrs: %p, addrcnt: %d)\n",
+ sk, addrs, addrcnt);
+
+ addr_buf = addrs;
+ for (cnt = 0; cnt < addrcnt; cnt++) {
+ /* The list may contain either IPv4 or IPv6 address;
+ * determine the address length for walking thru the list.
+ */
+ sa_addr = (struct sockaddr *)addr_buf;
+ af = sctp_get_af_specific(sa_addr->sa_family);
+ if (!af) {
+ retval = -EINVAL;
+ goto err_bindx_add;
+ }
+
+ retval = sctp_do_bind(sk, (union sctp_addr *)sa_addr,
+ af->sockaddr_len);
+
+ addr_buf += af->sockaddr_len;
+
+err_bindx_add:
+ if (retval < 0) {
+ /* Failed. Cleanup the ones that have been added */
+ if (cnt > 0)
+ sctp_bindx_rem(sk, addrs, cnt);
+ return retval;
+ }
+ }
+
+ return retval;
+}
+
+/* Send an ASCONF chunk with Add IP address parameters to all the peers of the
+ * associations that are part of the endpoint indicating that a list of local
+ * addresses are added to the endpoint.
+ *
+ * If any of the addresses is already in the bind address list of the
+ * association, we do not send the chunk for that association. But it will not
+ * affect other associations.
+ *
+ * Only sctp_setsockopt_bindx() is supposed to call this function.
+ */
+static int sctp_send_asconf_add_ip(struct sock *sk,
+ struct sockaddr *addrs,
+ int addrcnt)
+{
+ struct sctp_opt *sp;
+ struct sctp_endpoint *ep;
+ struct sctp_association *asoc;
+ struct sctp_bind_addr *bp;
+ struct sctp_chunk *chunk;
+ struct sctp_sockaddr_entry *laddr;
+ union sctp_addr *addr;
+ void *addr_buf;
+ struct sctp_af *af;
+ struct list_head *pos;
+ struct list_head *p;
+ int i;
+ int retval = 0;
+
+ if (!sctp_addip_enable)
+ return retval;
+
+ sp = sctp_sk(sk);
+ ep = sp->ep;
+
+ SCTP_DEBUG_PRINTK("%s: (sk: %p, addrs: %p, addrcnt: %d)\n",
+ __FUNCTION__, sk, addrs, addrcnt);
+
+ list_for_each(pos, &ep->asocs) {
+ asoc = list_entry(pos, struct sctp_association, asocs);
+
+ if (!asoc->peer.asconf_capable)
+ continue;
+
+ if (asoc->peer.addip_disabled_mask & SCTP_PARAM_ADD_IP)
+ continue;
+
+ if (!sctp_state(asoc, ESTABLISHED))
+ continue;
+
+ /* Check if any address in the packed array of addresses is
+ * in the bind address list of the association. If so,
+ * do not send the asconf chunk to its peer, but continue with
+ * other associations.
+ */
+ addr_buf = addrs;
+ for (i = 0; i < addrcnt; i++) {
+ addr = (union sctp_addr *)addr_buf;
+ af = sctp_get_af_specific(addr->v4.sin_family);
+ if (!af) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ if (sctp_assoc_lookup_laddr(asoc, addr))
+ break;
+
+ addr_buf += af->sockaddr_len;
+ }
+ if (i < addrcnt)
+ continue;
+
+ /* Use the first address in bind addr list of association as
+ * Address Parameter of ASCONF CHUNK.
+ */
+ sctp_read_lock(&asoc->base.addr_lock);
+ bp = &asoc->base.bind_addr;
+ p = bp->address_list.next;
+ laddr = list_entry(p, struct sctp_sockaddr_entry, list);
+ sctp_read_unlock(&asoc->base.addr_lock);
+
+ chunk = sctp_make_asconf_update_ip(asoc, &laddr->a, addrs,
+ addrcnt, SCTP_PARAM_ADD_IP);
+ if (!chunk) {
+ retval = -ENOMEM;
+ goto out;
+ }
+
+ retval = sctp_send_asconf(asoc, chunk);
+
+ /* FIXME: After sending the add address ASCONF chunk, we
+ * cannot append the address to the association's binding
+ * address list, because the new address may be used as the
+ * source of a message sent to the peer before the ASCONF
+ * chunk is received by the peer. So we should wait until
+ * ASCONF_ACK is received.
+ */
+ }
+
+out:
+ return retval;
+}
+
+/* Remove a list of addresses from bind addresses list. Do not remove the
+ * last address.
+ *
+ * Basically run through each address specified in the addrs/addrcnt
+ * array/length pair, determine if it is IPv6 or IPv4 and call
+ * sctp_del_bind() on it.
+ *
+ * If any of them fails, then the operation will be reversed and the
+ * ones that were removed will be added back.
+ *
+ * At least one address has to be left; if only one address is
+ * available, the operation will return -EBUSY.
+ *
+ * Only sctp_setsockopt_bindx() is supposed to call this function.
+ */
+int sctp_bindx_rem(struct sock *sk, struct sockaddr *addrs, int addrcnt)
+{
+ struct sctp_opt *sp = sctp_sk(sk);
+ struct sctp_endpoint *ep = sp->ep;
+ int cnt;
+ struct sctp_bind_addr *bp = &ep->base.bind_addr;
+ int retval = 0;
+ union sctp_addr saveaddr;
+ void *addr_buf;
+ struct sockaddr *sa_addr;
+ struct sctp_af *af;
+
+ SCTP_DEBUG_PRINTK("sctp_bindx_rem (sk: %p, addrs: %p, addrcnt: %d)\n",
+ sk, addrs, addrcnt);
+
+ addr_buf = addrs;
+ for (cnt = 0; cnt < addrcnt; cnt++) {
+ /* If the bind address list is empty or if there is only one
+ * bind address, there is nothing more to be removed (we need
+ * at least one address here).
+ */
+ if (list_empty(&bp->address_list) ||
+ (sctp_list_single_entry(&bp->address_list))) {
+ retval = -EBUSY;
+ goto err_bindx_rem;
+ }
+
+ /* The list may contain either IPv4 or IPv6 address;
+ * determine the address length to copy the address to
+ * saveaddr.
+ */
+ sa_addr = (struct sockaddr *)addr_buf;
+ af = sctp_get_af_specific(sa_addr->sa_family);
+ if (!af) {
+ retval = -EINVAL;
+ goto err_bindx_rem;
+ }
+ memcpy(&saveaddr, sa_addr, af->sockaddr_len);
+ saveaddr.v4.sin_port = ntohs(saveaddr.v4.sin_port);
+ if (saveaddr.v4.sin_port != bp->port) {
+ retval = -EINVAL;
+ goto err_bindx_rem;
+ }
+
+ /* FIXME - There is probably a need to check if sk->sk_saddr and
+ * sk->rcv_addr are currently set to one of the addresses to
+ * be removed. This is something which needs to be looked into
+ * when we are fixing the outstanding issues with multi-homing
+ * socket routing and failover schemes. Refer to comments in
+ * sctp_do_bind(). -daisy
+ */
+ sctp_local_bh_disable();
+ sctp_write_lock(&ep->base.addr_lock);
+
+ retval = sctp_del_bind_addr(bp, &saveaddr);
+
+ sctp_write_unlock(&ep->base.addr_lock);
+ sctp_local_bh_enable();
+
+ addr_buf += af->sockaddr_len;
+err_bindx_rem:
+ if (retval < 0) {
+ /* Failed. Add the ones that has been removed back */
+ if (cnt > 0)
+ sctp_bindx_add(sk, addrs, cnt);
+ return retval;
+ }
+ }
+
+ return retval;
+}
+
+/* Send an ASCONF chunk with Delete IP address parameters to all the peers of
+ * the associations that are part of the endpoint indicating that a list of
+ * local addresses are removed from the endpoint.
+ *
+ * If any of the addresses is already in the bind address list of the
+ * association, we do not send the chunk for that association. But it will not
+ * affect other associations.
+ *
+ * Only sctp_setsockopt_bindx() is supposed to call this function.
+ */
+static int sctp_send_asconf_del_ip(struct sock *sk,
+ struct sockaddr *addrs,
+ int addrcnt)
+{
+ struct sctp_opt *sp;
+ struct sctp_endpoint *ep;
+ struct sctp_association *asoc;
+ struct sctp_bind_addr *bp;
+ struct sctp_chunk *chunk;
+ union sctp_addr *laddr;
+ void *addr_buf;
+ struct sctp_af *af;
+ struct list_head *pos;
+ int i;
+ int retval = 0;
+
+ if (!sctp_addip_enable)
+ return retval;
+
+ sp = sctp_sk(sk);
+ ep = sp->ep;
+
+ SCTP_DEBUG_PRINTK("%s: (sk: %p, addrs: %p, addrcnt: %d)\n",
+ __FUNCTION__, sk, addrs, addrcnt);
+
+ list_for_each(pos, &ep->asocs) {
+ asoc = list_entry(pos, struct sctp_association, asocs);
+
+ if (!asoc->peer.asconf_capable)
+ continue;
+
+ if (asoc->peer.addip_disabled_mask & SCTP_PARAM_DEL_IP)
+ continue;
+
+ if (!sctp_state(asoc, ESTABLISHED))
+ continue;
+
+ /* Check if any address in the packed array of addresses is
+ * not present in the bind address list of the association.
+ * If so, do not send the asconf chunk to its peer, but
+ * continue with other associations.
+ */
+ addr_buf = addrs;
+ for (i = 0; i < addrcnt; i++) {
+ laddr = (union sctp_addr *)addr_buf;
+ af = sctp_get_af_specific(laddr->v4.sin_family);
+ if (!af) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ if (!sctp_assoc_lookup_laddr(asoc, laddr))
+ break;
+
+ addr_buf += af->sockaddr_len;
+ }
+ if (i < addrcnt)
+ continue;
+
+ /* Find one address in the association's bind address list
+ * that is not in the packed array of addresses. This is to
+ * make sure that we do not delete all the addresses in the
+ * association.
+ */
+ sctp_read_lock(&asoc->base.addr_lock);
+ bp = &asoc->base.bind_addr;
+ laddr = sctp_find_unmatch_addr(bp, (union sctp_addr *)addrs,
+ addrcnt, sp);
+ sctp_read_unlock(&asoc->base.addr_lock);
+ if (!laddr)
+ continue;
+
+ chunk = sctp_make_asconf_update_ip(asoc, laddr, addrs, addrcnt,
+ SCTP_PARAM_DEL_IP);
+ if (!chunk) {
+ retval = -ENOMEM;
+ goto out;
+ }
+
+ retval = sctp_send_asconf(asoc, chunk);
+
+ /* FIXME: After sending the delete address ASCONF chunk, we
+ * cannot remove the addresses from the association's bind
+ * address list, because there maybe some packet send to
+ * the delete addresses, so we should wait until ASCONF_ACK
+ * packet is received.
+ */
+ }
+out:
+ return retval;
+}
+
+/* Helper for tunneling sctp_bindx() requests through sctp_setsockopt()
+ *
+ * API 8.1
+ * int sctp_bindx(int sd, struct sockaddr *addrs, int addrcnt,
+ * int flags);
+ *
+ * If sd is an IPv4 socket, the addresses passed must be IPv4 addresses.
+ * If the sd is an IPv6 socket, the addresses passed can either be IPv4
+ * or IPv6 addresses.
+ *
+ * A single address may be specified as INADDR_ANY or IN6ADDR_ANY, see
+ * Section 3.1.2 for this usage.
+ *
+ * addrs is a pointer to an array of one or more socket addresses. Each
+ * address is contained in its appropriate structure (i.e. struct
+ * sockaddr_in or struct sockaddr_in6) the family of the address type
+ * must be used to distengish the address length (note that this
+ * representation is termed a "packed array" of addresses). The caller
+ * specifies the number of addresses in the array with addrcnt.
+ *
+ * On success, sctp_bindx() returns 0. On failure, sctp_bindx() returns
+ * -1, and sets errno to the appropriate error code.
+ *
+ * For SCTP, the port given in each socket address must be the same, or
+ * sctp_bindx() will fail, setting errno to EINVAL.
+ *
+ * The flags parameter is formed from the bitwise OR of zero or more of
+ * the following currently defined flags:
+ *
+ * SCTP_BINDX_ADD_ADDR
+ *
+ * SCTP_BINDX_REM_ADDR
+ *
+ * SCTP_BINDX_ADD_ADDR directs SCTP to add the given addresses to the
+ * association, and SCTP_BINDX_REM_ADDR directs SCTP to remove the given
+ * addresses from the association. The two flags are mutually exclusive;
+ * if both are given, sctp_bindx() will fail with EINVAL. A caller may
+ * not remove all addresses from an association; sctp_bindx() will
+ * reject such an attempt with EINVAL.
+ *
+ * An application can use sctp_bindx(SCTP_BINDX_ADD_ADDR) to associate
+ * additional addresses with an endpoint after calling bind(). Or use
+ * sctp_bindx(SCTP_BINDX_REM_ADDR) to remove some addresses a listening
+ * socket is associated with so that no new association accepted will be
+ * associated with those addresses. If the endpoint supports dynamic
+ * address a SCTP_BINDX_REM_ADDR or SCTP_BINDX_ADD_ADDR may cause a
+ * endpoint to send the appropriate message to the peer to change the
+ * peers address lists.
+ *
+ * Adding and removing addresses from a connected association is
+ * optional functionality. Implementations that do not support this
+ * functionality should return EOPNOTSUPP.
+ *
+ * Basically do nothing but copying the addresses from user to kernel
+ * land and invoking either sctp_bindx_add() or sctp_bindx_rem() on the sk.
+ * This is used for tunneling the sctp_bindx() request through sctp_setsockopt() * from userspace.
+ *
+ * We don't use copy_from_user() for optimization: we first do the
+ * sanity checks (buffer size -fast- and access check-healthy
+ * pointer); if all of those succeed, then we can alloc the memory
+ * (expensive operation) needed to copy the data to kernel. Then we do
+ * the copying without checking the user space area
+ * (__copy_from_user()).
+ *
+ * On exit there is no need to do sockfd_put(), sys_setsockopt() does
+ * it.
+ *
+ * sk The sk of the socket
+ * addrs The pointer to the addresses in user land
+ * addrssize Size of the addrs buffer
+ * op Operation to perform (add or remove, see the flags of
+ * sctp_bindx)
+ *
+ * Returns 0 if ok, <0 errno code on error.
+ */
+SCTP_STATIC int sctp_setsockopt_bindx(struct sock* sk, struct sockaddr *addrs,
+ int addrs_size, int op)
+{
+ struct sockaddr *kaddrs;
+ int err;
+ int addrcnt = 0;
+ int walk_size = 0;
+ struct sockaddr *sa_addr;
+ void *addr_buf;
+ struct sctp_af *af;
+
+ SCTP_DEBUG_PRINTK("sctp_setsocktopt_bindx: sk %p addrs %p"
+ " addrs_size %d opt %d\n", sk, addrs, addrs_size, op);
+
+ if (unlikely(addrs_size <= 0))
+ return -EINVAL;
+
+ /* Check the user passed a healthy pointer. */
+ if (unlikely(!access_ok(VERIFY_READ, addrs, addrs_size)))
+ return -EFAULT;
+
+ /* Alloc space for the address array in kernel memory. */
+ kaddrs = (struct sockaddr *)kmalloc(addrs_size, GFP_KERNEL);
+ if (unlikely(!kaddrs))
+ return -ENOMEM;
+
+ if (__copy_from_user(kaddrs, addrs, addrs_size)) {
+ kfree(kaddrs);
+ return -EFAULT;
+ }
+
+ /* Walk through the addrs buffer and count the number of addresses. */
+ addr_buf = kaddrs;
+ while (walk_size < addrs_size) {
+ sa_addr = (struct sockaddr *)addr_buf;
+ af = sctp_get_af_specific(sa_addr->sa_family);
+
+ /* If the address family is not supported or if this address
+ * causes the address buffer to overflow return EINVAL.
+ */
+ if (!af || (walk_size + af->sockaddr_len) > addrs_size) {
+ kfree(kaddrs);
+ return -EINVAL;
+ }
+ addrcnt++;
+ addr_buf += af->sockaddr_len;
+ walk_size += af->sockaddr_len;
+ }
+
+ /* Do the work. */
+ switch (op) {
+ case SCTP_BINDX_ADD_ADDR:
+ err = sctp_bindx_add(sk, kaddrs, addrcnt);
+ if (err)
+ goto out;
+ err = sctp_send_asconf_add_ip(sk, kaddrs, addrcnt);
+ break;
+
+ case SCTP_BINDX_REM_ADDR:
+ err = sctp_bindx_rem(sk, kaddrs, addrcnt);
+ if (err)
+ goto out;
+ err = sctp_send_asconf_del_ip(sk, kaddrs, addrcnt);
+ break;
+
+ default:
+ err = -EINVAL;
+ break;
+ };
+
+out:
+ kfree(kaddrs);
+
+ return err;
+}
+
+/* API 3.1.4 close() - UDP Style Syntax
+ * Applications use close() to perform graceful shutdown (as described in
+ * Section 10.1 of [SCTP]) on ALL the associations currently represented
+ * by a UDP-style socket.
+ *
+ * The syntax is
+ *
+ * ret = close(int sd);
+ *
+ * sd - the socket descriptor of the associations to be closed.
+ *
+ * To gracefully shutdown a specific association represented by the
+ * UDP-style socket, an application should use the sendmsg() call,
+ * passing no user data, but including the appropriate flag in the
+ * ancillary data (see Section xxxx).
+ *
+ * If sd in the close() call is a branched-off socket representing only
+ * one association, the shutdown is performed on that association only.
+ *
+ * 4.1.6 close() - TCP Style Syntax
+ *
+ * Applications use close() to gracefully close down an association.
+ *
+ * The syntax is:
+ *
+ * int close(int sd);
+ *
+ * sd - the socket descriptor of the association to be closed.
+ *
+ * After an application calls close() on a socket descriptor, no further
+ * socket operations will succeed on that descriptor.
+ *
+ * API 7.1.4 SO_LINGER
+ *
+ * An application using the TCP-style socket can use this option to
+ * perform the SCTP ABORT primitive. The linger option structure is:
+ *
+ * struct linger {
+ * int l_onoff; // option on/off
+ * int l_linger; // linger time
+ * };
+ *
+ * To enable the option, set l_onoff to 1. If the l_linger value is set
+ * to 0, calling close() is the same as the ABORT primitive. If the
+ * value is set to a negative value, the setsockopt() call will return
+ * an error. If the value is set to a positive value linger_time, the
+ * close() can be blocked for at most linger_time ms. If the graceful
+ * shutdown phase does not finish during this period, close() will
+ * return but the graceful shutdown phase continues in the system.
+ */
+SCTP_STATIC void sctp_close(struct sock *sk, long timeout)
+{
+ struct sctp_endpoint *ep;
+ struct sctp_association *asoc;
+ struct list_head *pos, *temp;
+
+ SCTP_DEBUG_PRINTK("sctp_close(sk: 0x%p, timeout:%ld)\n", sk, timeout);
+
+ sctp_lock_sock(sk);
+ sk->shutdown = SHUTDOWN_MASK;
+
+ ep = sctp_sk(sk)->ep;
+
+ /* Walk all associations on a socket, not on an endpoint. */
+ list_for_each_safe(pos, temp, &ep->asocs) {
+ asoc = list_entry(pos, struct sctp_association, asocs);
+
+ if (sctp_style(sk, TCP)) {
+ /* A closed association can still be in the list if
+ * it belongs to a TCP-style listening socket that is
+ * not yet accepted. If so, free it. If not, send an
+ * ABORT or SHUTDOWN based on the linger options.
+ */
+ if (sctp_state(asoc, CLOSED)) {
+ sctp_unhash_established(asoc);
+ sctp_association_free(asoc);
+
+ } else if (sk->linger && !sk->lingertime)
+ sctp_primitive_ABORT(asoc, NULL);
+ else
+ sctp_primitive_SHUTDOWN(asoc, NULL);
+ } else
+ sctp_primitive_SHUTDOWN(asoc, NULL);
+ }
+
+ /* Clean up any skbs sitting on the receive queue. */
+ sctp_queue_purge_ulpevents(&sk->receive_queue);
+ sctp_queue_purge_ulpevents(&sctp_sk(sk)->pd_lobby);
+
+ /* On a TCP-style socket, block for at most linger_time if set. */
+ if (sctp_style(sk, TCP) && timeout)
+ sctp_wait_for_close(sk, timeout);
+
+ /* This will run the backlog queue. */
+ sctp_release_sock(sk);
+
+ /* Supposedly, no process has access to the socket, but
+ * the net layers still may.
+ */
+ sctp_local_bh_disable();
+ sctp_bh_lock_sock(sk);
+
+ /* Hold the sock, since inet_sock_release() will put sock_put()
+ * and we have just a little more cleanup.
+ */
+ sock_hold(sk);
+ inet_sock_release(sk);
+
+ sctp_bh_unlock_sock(sk);
+ sctp_local_bh_enable();
+
+ sock_put(sk);
+
+ SCTP_DBG_OBJCNT_DEC(sock);
+}
+
+/* Handle EPIPE error. */
+static int sctp_error(struct sock *sk, int flags, int err)
+{
+ if (err == -EPIPE)
+ err = sock_error(sk) ? : -EPIPE;
+ if (err == -EPIPE && !(flags & MSG_NOSIGNAL))
+ send_sig(SIGPIPE, current, 0);
+ return err;
+}
+
+/* API 3.1.3 sendmsg() - UDP Style Syntax
+ *
+ * An application uses sendmsg() and recvmsg() calls to transmit data to
+ * and receive data from its peer.
+ *
+ * ssize_t sendmsg(int socket, const struct msghdr *message,
+ * int flags);
+ *
+ * socket - the socket descriptor of the endpoint.
+ * message - pointer to the msghdr structure which contains a single
+ * user message and possibly some ancillary data.
+ *
+ * See Section 5 for complete description of the data
+ * structures.
+ *
+ * flags - flags sent or received with the user message, see Section
+ * 5 for complete description of the flags.
+ *
+ * Note: This function could use a rewrite especially when explicit
+ * connect support comes in.
+ */
+/* BUG: We do not implement the equivalent of wait_for_tcp_memory(). */
+
+SCTP_STATIC int sctp_msghdr_parse(const struct msghdr *, sctp_cmsgs_t *);
+
+SCTP_STATIC int sctp_sendmsg(struct sock *sk, struct msghdr *msg, int msg_len)
+{
+ struct sctp_opt *sp;
+ struct sctp_endpoint *ep;
+ struct sctp_association *new_asoc=NULL, *asoc=NULL;
+ struct sctp_transport *transport, *chunk_tp;
+ struct sctp_chunk *chunk;
+ union sctp_addr to;
+ struct sockaddr *msg_name = NULL;
+ struct sctp_sndrcvinfo default_sinfo = { 0 };
+ struct sctp_sndrcvinfo *sinfo;
+ struct sctp_initmsg *sinit;
+ sctp_assoc_t associd = NULL;
+ sctp_cmsgs_t cmsgs = { NULL };
+ int err;
+ sctp_scope_t scope;
+ long timeo;
+ __u16 sinfo_flags = 0;
+ struct sctp_datamsg *datamsg;
+ struct list_head *pos;
+ int msg_flags = msg->msg_flags;
+
+ SCTP_DEBUG_PRINTK("sctp_sendmsg(sk: %p, msg: %p, msg_len: %u)\n",
+ sk, msg, msg_len);
+
+ err = 0;
+ sp = sctp_sk(sk);
+ ep = sp->ep;
+
+ SCTP_DEBUG_PRINTK("Using endpoint: %s.\n", ep->debug_name);
+
+ /* We cannot send a message over a TCP-style listening socket. */
+ if (sctp_style(sk, TCP) && sctp_sstate(sk, LISTENING)) {
+ err = -EPIPE;
+ goto out_nounlock;
+ }
+
+ /* Parse out the SCTP CMSGs. */
+ err = sctp_msghdr_parse(msg, &cmsgs);
+
+ if (err) {
+ SCTP_DEBUG_PRINTK("msghdr parse err = %x\n", err);
+ goto out_nounlock;
+ }
+
+ /* Fetch the destination address for this packet. This
+ * address only selects the association--it is not necessarily
+ * the address we will send to.
+ * For a peeled-off socket, msg_name is ignored.
+ */
+ if (!sctp_style(sk, UDP_HIGH_BANDWIDTH) && msg->msg_name) {
+ int msg_namelen = msg->msg_namelen;
+
+ err = sctp_verify_addr(sk, (union sctp_addr *)msg->msg_name,
+ msg_namelen);
+ if (err)
+ return err;
+
+ if (msg_namelen > sizeof(to))
+ msg_namelen = sizeof(to);
+ memcpy(&to, msg->msg_name, msg_namelen);
+ SCTP_DEBUG_PRINTK("Just memcpy'd. msg_name is "
+ "0x%x:%u.\n",
+ to.v4.sin_addr.s_addr, to.v4.sin_port);
+
+ to.v4.sin_port = ntohs(to.v4.sin_port);
+ msg_name = msg->msg_name;
+ }
+
+ sinfo = cmsgs.info;
+ sinit = cmsgs.init;
+
+ /* Did the user specify SNDRCVINFO? */
+ if (sinfo) {
+ sinfo_flags = sinfo->sinfo_flags;
+ associd = sinfo->sinfo_assoc_id;
+ }
+
+ SCTP_DEBUG_PRINTK("msg_len: %u, sinfo_flags: 0x%x\n",
+ msg_len, sinfo_flags);
+
+ /* MSG_EOF or MSG_ABORT cannot be set on a TCP-style socket. */
+ if (sctp_style(sk, TCP) && (sinfo_flags & (MSG_EOF | MSG_ABORT))) {
+ err = -EINVAL;
+ goto out_nounlock;
+ }
+
+ /* If MSG_EOF is set, no data can be sent. Disallow sending zero
+ * length messages when MSG_EOF|MSG_ABORT is not set.
+ * If MSG_ABORT is set, the message length could be non zero with
+ * the msg_iov set to the user abort reason.
+ */
+ if (((sinfo_flags & MSG_EOF) && (msg_len > 0)) ||
+ (!(sinfo_flags & (MSG_EOF|MSG_ABORT)) && (msg_len == 0))) {
+ err = -EINVAL;
+ goto out_nounlock;
+ }
+
+ /* If MSG_ADDR_OVER is set, there must be an address
+ * specified in msg_name.
+ */
+ if ((sinfo_flags & MSG_ADDR_OVER) && (!msg->msg_name)) {
+ err = -EINVAL;
+ goto out_nounlock;
+ }
+
+ transport = NULL;
+
+ SCTP_DEBUG_PRINTK("About to look up association.\n");
+
+ sctp_lock_sock(sk);
+
+ /* If a msg_name has been specified, assume this is to be used. */
+ if (msg_name) {
+ /* Look for a matching association on the endpoint. */
+ asoc = sctp_endpoint_lookup_assoc(ep, &to, &transport);
+ if (!asoc) {
+ /* If we could not find a matching association on the
+ * endpoint, make sure that it is not a TCP-style
+ * socket that already has an association or there is
+ * no peeled-off association on another socket.
+ */
+ if ((sctp_style(sk, TCP) &&
+ sctp_sstate(sk, ESTABLISHED)) ||
+ sctp_endpoint_is_peeled_off(ep, &to)) {
+ err = -EADDRNOTAVAIL;
+ goto out_unlock;
+ }
+ }
+ } else {
+ asoc = sctp_id2assoc(sk, associd);
+ if (!asoc) {
+ err = -EPIPE;
+ goto out_unlock;
+ }
+ }
+
+ if (asoc) {
+ SCTP_DEBUG_PRINTK("Just looked up association: %p.\n", asoc);
+
+ /* We cannot send a message on a TCP-style SCTP_SS_ESTABLISHED
+ * socket that has an association in CLOSED state. This can
+ * happen when an accepted socket has an association that is
+ * already CLOSED.
+ */
+ if (sctp_state(asoc, CLOSED) && sctp_style(sk, TCP)) {
+ err = -EPIPE;
+ goto out_unlock;
+ }
+
+ if (sinfo_flags & MSG_EOF) {
+ SCTP_DEBUG_PRINTK("Shutting down association: %p\n",
+ asoc);
+ sctp_primitive_SHUTDOWN(asoc, NULL);
+ err = 0;
+ goto out_unlock;
+ }
+ if (sinfo_flags & MSG_ABORT) {
+ SCTP_DEBUG_PRINTK("Aborting association: %p\n", asoc);
+ sctp_primitive_ABORT(asoc, msg);
+ err = 0;
+ goto out_unlock;
+ }
+ }
+
+ /* Do we need to create the association? */
+ if (!asoc) {
+ SCTP_DEBUG_PRINTK("There is no association yet.\n");
+
+ if (sinfo_flags & (MSG_EOF | MSG_ABORT)) {
+ err = -EINVAL;
+ goto out_unlock;
+ }
+
+ /* Check for invalid stream against the stream counts,
+ * either the default or the user specified stream counts.
+ */
+ if (sinfo) {
+ if (!sinit || (sinit && !sinit->sinit_num_ostreams)) {
+ /* Check against the defaults. */
+ if (sinfo->sinfo_stream >=
+ sp->initmsg.sinit_num_ostreams) {
+ err = -EINVAL;
+ goto out_unlock;
+ }
+ } else {
+ /* Check against the requested. */
+ if (sinfo->sinfo_stream >=
+ sinit->sinit_num_ostreams) {
+ err = -EINVAL;
+ goto out_unlock;
+ }
+ }
+ }
+
+ /*
+ * API 3.1.2 bind() - UDP Style Syntax
+ * If a bind() or sctp_bindx() is not called prior to a
+ * sendmsg() call that initiates a new association, the
+ * system picks an ephemeral port and will choose an address
+ * set equivalent to binding with a wildcard address.
+ */
+ if (!ep->base.bind_addr.port) {
+ if (sctp_autobind(sk)) {
+ err = -EAGAIN;
+ goto out_unlock;
+ }
+ }
+
+ scope = sctp_scope(&to);
+ new_asoc = sctp_association_new(ep, sk, scope, GFP_KERNEL);
+ if (!new_asoc) {
+ err = -ENOMEM;
+ goto out_unlock;
+ }
+ asoc = new_asoc;
+
+ /* If the SCTP_INIT ancillary data is specified, set all
+ * the association init values accordingly.
+ */
+ if (sinit) {
+ if (sinit->sinit_num_ostreams) {
+ asoc->c.sinit_num_ostreams =
+ sinit->sinit_num_ostreams;
+ }
+ if (sinit->sinit_max_instreams) {
+ asoc->c.sinit_max_instreams =
+ sinit->sinit_max_instreams;
+ }
+ if (sinit->sinit_max_attempts) {
+ asoc->max_init_attempts
+ = sinit->sinit_max_attempts;
+ }
+ if (sinit->sinit_max_init_timeo) {
+ asoc->max_init_timeo =
+ SCTP_MSECS_TO_JIFFIES(sinit->sinit_max_init_timeo);
+ }
+ }
+
+ /* Prime the peer's transport structures. */
+ transport = sctp_assoc_add_peer(asoc, &to, GFP_KERNEL);
+ if (!transport) {
+ err = -ENOMEM;
+ goto out_free;
+ }
+ err = sctp_assoc_set_bind_addr_from_ep(asoc, GFP_KERNEL);
+ if (err < 0) {
+ err = -ENOMEM;
+ goto out_free;
+ }
+ }
+
+ /* ASSERT: we have a valid association at this point. */
+ SCTP_DEBUG_PRINTK("We have a valid association.\n");
+
+ if (!sinfo) {
+ /* If the user didn't specify SNDRCVINFO, make up one with
+ * some defaults.
+ */
+ default_sinfo.sinfo_stream = asoc->default_stream;
+ default_sinfo.sinfo_flags = asoc->default_flags;
+ default_sinfo.sinfo_ppid = asoc->default_ppid;
+ default_sinfo.sinfo_context = asoc->default_context;
+ default_sinfo.sinfo_timetolive = asoc->default_timetolive;
+ default_sinfo.sinfo_assoc_id = sctp_assoc2id(asoc);
+ sinfo = &default_sinfo;
+ }
+
+ /* API 7.1.7, the sndbuf size per association bounds the
+ * maximum size of data that can be sent in a single send call.
+ */
+ if (msg_len > sk->sndbuf) {
+ err = -EMSGSIZE;
+ goto out_free;
+ }
+
+ /* If fragmentation is disabled and the message length exceeds the
+ * association fragmentation point, return EMSGSIZE. The I-D
+ * does not specify what this error is, but this looks like
+ * a great fit.
+ */
+ if (sctp_sk(sk)->disable_fragments && (msg_len > asoc->frag_point)) {
+ err = -EMSGSIZE;
+ goto out_free;
+ }
+
+ if (sinfo) {
+ /* Check for invalid stream. */
+ if (sinfo->sinfo_stream >= asoc->c.sinit_num_ostreams) {
+ err = -EINVAL;
+ goto out_free;
+ }
+ }
+
+ timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT);
+ if (!sctp_wspace(asoc)) {
+ err = sctp_wait_for_sndbuf(asoc, &timeo, msg_len);
+ if (err)
+ goto out_free;
+ }
+
+ /* If an address is passed with the sendto/sendmsg call, it is used
+ * to override the primary destination address in the TCP model, or
+ * when MSG_ADDR_OVER flag is set in the UDP model.
+ */
+ if ((sctp_style(sk, TCP) && msg_name) ||
+ (sinfo_flags & MSG_ADDR_OVER)) {
+ chunk_tp = sctp_assoc_lookup_paddr(asoc, &to);
+ if (!chunk_tp) {
+ err = -EINVAL;
+ goto out_free;
+ }
+ } else
+ chunk_tp = NULL;
+
+ /* Auto-connect, if we aren't connected already. */
+ if (sctp_state(asoc, CLOSED)) {
+ err = sctp_primitive_ASSOCIATE(asoc, NULL);
+ if (err < 0)
+ goto out_free;
+ SCTP_DEBUG_PRINTK("We associated primitively.\n");
+ }
+
+ /* Break the message into multiple chunks of maximum size. */
+ datamsg = sctp_datamsg_from_user(asoc, sinfo, msg, msg_len);
+ if (!datamsg) {
+ err = -ENOMEM;
+ goto out_free;
+ }
+
+ /* Now send the (possibly) fragmented message. */
+ list_for_each(pos, &datamsg->chunks) {
+ chunk = list_entry(pos, struct sctp_chunk, frag_list);
+ sctp_datamsg_track(chunk);
+
+ /* Do accounting for the write space. */
+ sctp_set_owner_w(chunk);
+
+ chunk->transport = chunk_tp;
+
+ /* Send it to the lower layers. Note: all chunks
+ * must either fail or succeed. The lower layer
+ * works that way today. Keep it that way or this
+ * breaks.
+ */
+ err = sctp_primitive_SEND(asoc, chunk);
+ /* Did the lower layer accept the chunk? */
+ if (err)
+ sctp_chunk_free(chunk);
+ SCTP_DEBUG_PRINTK("We sent primitively.\n");
+ }
+
+ sctp_datamsg_free(datamsg);
+ if (err)
+ goto out_free;
+ else
+ err = msg_len;
+
+ /* If we are already past ASSOCIATE, the lower
+ * layers are responsible for association cleanup.
+ */
+ goto out_unlock;
+
+out_free:
+ if (new_asoc)
+ sctp_association_free(asoc);
+out_unlock:
+ sctp_release_sock(sk);
+
+out_nounlock:
+ return sctp_error(sk, msg_flags, err);
+
+#if 0
+do_sock_err:
+ if (msg_len)
+ err = msg_len;
+ else
+ err = sock_error(sk);
+ goto out;
+
+do_interrupted:
+ if (msg_len)
+ err = msg_len;
+ goto out;
+#endif /* 0 */
+}
+
+/* This is an extended version of skb_pull() that removes the data from the
+ * start of a skb even when data is spread across the list of skb's in the
+ * frag_list. len specifies the total amount of data that needs to be removed.
+ * when 'len' bytes could be removed from the skb, it returns 0.
+ * If 'len' exceeds the total skb length, it returns the no. of bytes that
+ * could not be removed.
+ */
+static int sctp_skb_pull(struct sk_buff *skb, int len)
+{
+ struct sk_buff *list;
+ int skb_len = skb_headlen(skb);
+ int rlen;
+
+ if (len <= skb_len) {
+ __skb_pull(skb, len);
+ return 0;
+ }
+ len -= skb_len;
+ __skb_pull(skb, skb_len);
+
+ for (list = skb_shinfo(skb)->frag_list; list; list = list->next) {
+ rlen = sctp_skb_pull(list, len);
+ skb->len -= (len-rlen);
+ skb->data_len -= (len-rlen);
+
+ if (!rlen)
+ return 0;
+
+ len = rlen;
+ }
+
+ return len;
+}
+
+/* API 3.1.3 recvmsg() - UDP Style Syntax
+ *
+ * ssize_t recvmsg(int socket, struct msghdr *message,
+ * int flags);
+ *
+ * socket - the socket descriptor of the endpoint.
+ * message - pointer to the msghdr structure which contains a single
+ * user message and possibly some ancillary data.
+ *
+ * See Section 5 for complete description of the data
+ * structures.
+ *
+ * flags - flags sent or received with the user message, see Section
+ * 5 for complete description of the flags.
+ */
+static struct sk_buff *sctp_skb_recv_datagram(struct sock *, int, int, int *);
+
+SCTP_STATIC int sctp_recvmsg(struct sock *sk, struct msghdr *msg, int len,
+ int noblock, int flags, int *addr_len)
+{
+ struct sctp_ulpevent *event = NULL;
+ struct sctp_opt *sp = sctp_sk(sk);
+ struct sk_buff *skb;
+ int copied;
+ int err = 0;
+ int skb_len;
+
+ SCTP_DEBUG_PRINTK("sctp_recvmsg(%s: %p, %s: %p, %s: %d, %s: %d, %s: "
+ "0x%x, %s: %p)\n", "sk", sk, "msghdr", msg,
+ "len", len, "knoblauch", noblock,
+ "flags", flags, "addr_len", addr_len);
+
+ sctp_lock_sock(sk);
+
+ if (sctp_style(sk, TCP) && !sctp_sstate(sk, ESTABLISHED)) {
+ err = -ENOTCONN;
+ goto out;
+ }
+
+ skb = sctp_skb_recv_datagram(sk, flags, noblock, &err);
+ if (!skb)
+ goto out;
+
+ /* Get the total length of the skb including any skb's in the
+ * frag_list.
+ */
+ skb_len = skb->len;
+
+ copied = skb_len;
+ if (copied > len)
+ copied = len;
+
+ err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+
+ event = sctp_skb2event(skb);
+
+ if (err)
+ goto out_free;
+
+ sock_recv_timestamp(msg, sk, skb);
+ if (sctp_ulpevent_is_notification(event)) {
+ msg->msg_flags |= MSG_NOTIFICATION;
+ sp->pf->event_msgname(event, msg->msg_name, addr_len);
+ } else {
+ sp->pf->skb_msgname(skb, msg->msg_name, addr_len);
+ }
+
+ /* Check if we allow SCTP_SNDRCVINFO. */
+ if (sp->subscribe.sctp_data_io_event)
+ sctp_ulpevent_read_sndrcvinfo(event, msg);
+#if 0
+ /* FIXME: we should be calling IP/IPv6 layers. */
+ if (sk->sk_protinfo.af_inet.cmsg_flags)
+ ip_cmsg_recv(msg, skb);
+#endif
+
+ err = copied;
+
+ /* If skb's length exceeds the user's buffer, update the skb and
+ * push it back to the receive_queue so that the next call to
+ * recvmsg() will return the remaining data. Don't set MSG_EOR.
+ */
+ if (skb_len > copied) {
+ msg->msg_flags &= ~MSG_EOR;
+ if (flags & MSG_PEEK)
+ goto out_free;
+ sctp_skb_pull(skb, copied);
+ skb_queue_head(&sk->receive_queue, skb);
+
+ /* When only partial message is copied to the user, increase
+ * rwnd by that amount. If all the data in the skb is read,
+ * rwnd is updated when the event is freed.
+ */
+ sctp_assoc_rwnd_increase(event->asoc, copied);
+ goto out;
+ } else if ((event->msg_flags & MSG_NOTIFICATION) ||
+ (event->msg_flags & MSG_EOR))
+ msg->msg_flags |= MSG_EOR;
+ else
+ msg->msg_flags &= ~MSG_EOR;
+
+out_free:
+ if (flags & MSG_PEEK) {
+ /* Release the skb reference acquired after peeking the skb in
+ * sctp_skb_recv_datagram().
+ */
+ kfree_skb(skb);
+ } else {
+ /* Free the event which includes releasing the reference to
+ * the owner of the skb, freeing the skb and updating the
+ * rwnd.
+ */
+ sctp_ulpevent_free(event);
+ }
+out:
+ sctp_release_sock(sk);
+ return err;
+}
+
+/* 7.1.12 Enable/Disable message fragmentation (SCTP_DISABLE_FRAGMENTS)
+ *
+ * This option is a on/off flag. If enabled no SCTP message
+ * fragmentation will be performed. Instead if a message being sent
+ * exceeds the current PMTU size, the message will NOT be sent and
+ * instead a error will be indicated to the user.
+ */
+static int sctp_setsockopt_disable_fragments(struct sock *sk,
+ char *optval, int optlen)
+{
+ int val;
+
+ if (optlen < sizeof(int))
+ return -EINVAL;
+
+ if (get_user(val, (int *)optval))
+ return -EFAULT;
+
+ sctp_sk(sk)->disable_fragments = (val == 0) ? 0 : 1;
+
+ return 0;
+}
+
+static int sctp_setsockopt_events(struct sock *sk, char *optval,
+ int optlen)
+{
+ if (optlen != sizeof(struct sctp_event_subscribe))
+ return -EINVAL;
+ if (copy_from_user(&sctp_sk(sk)->subscribe, optval, optlen))
+ return -EFAULT;
+ return 0;
+}
+
+/* 7.1.8 Automatic Close of associations (SCTP_AUTOCLOSE)
+ *
+ * This socket option is applicable to the UDP-style socket only. When
+ * set it will cause associations that are idle for more than the
+ * specified number of seconds to automatically close. An association
+ * being idle is defined an association that has NOT sent or received
+ * user data. The special value of '0' indicates that no automatic
+ * close of any associations should be performed. The option expects an
+ * integer defining the number of seconds of idle time before an
+ * association is closed.
+ */
+static int sctp_setsockopt_autoclose(struct sock *sk, char *optval,
+ int optlen)
+{
+ struct sctp_opt *sp = sctp_sk(sk);
+
+ /* Applicable to UDP-style socket only */
+ if (sctp_style(sk, TCP))
+ return -EOPNOTSUPP;
+ if (optlen != sizeof(int))
+ return -EINVAL;
+ if (copy_from_user(&sp->autoclose, optval, optlen))
+ return -EFAULT;
+
+ sp->ep->timeouts[SCTP_EVENT_TIMEOUT_AUTOCLOSE] = sp->autoclose * HZ;
+ return 0;
+}
+
+/* 7.1.13 Peer Address Parameters (SCTP_PEER_ADDR_PARAMS)
+ *
+ * Applications can enable or disable heartbeats for any peer address of
+ * an association, modify an address's heartbeat interval, force a
+ * heartbeat to be sent immediately, and adjust the address's maximum
+ * number of retransmissions sent before an address is considered
+ * unreachable. The following structure is used to access and modify an
+ * address's parameters:
+ *
+ * struct sctp_paddrparams {
+ * sctp_assoc_t spp_assoc_id;
+ * struct sockaddr_storage spp_address;
+ * uint32_t spp_hbinterval;
+ * uint16_t spp_pathmaxrxt;
+ * };
+ *
+ * spp_assoc_id - (UDP style socket) This is filled in the application,
+ * and identifies the association for this query.
+ * spp_address - This specifies which address is of interest.
+ * spp_hbinterval - This contains the value of the heartbeat interval,
+ * in milliseconds. A value of 0, when modifying the
+ * parameter, specifies that the heartbeat on this
+ * address should be disabled. A value of UINT32_MAX
+ * (4294967295), when modifying the parameter,
+ * specifies that a heartbeat should be sent
+ * immediately to the peer address, and the current
+ * interval should remain unchanged.
+ * spp_pathmaxrxt - This contains the maximum number of
+ * retransmissions before this address shall be
+ * considered unreachable.
+ */
+static int sctp_setsockopt_peer_addr_params(struct sock *sk,
+ char *optval, int optlen)
+{
+ struct sctp_paddrparams params;
+ struct sctp_transport *trans;
+ int error;
+
+ if (optlen != sizeof(struct sctp_paddrparams))
+ return -EINVAL;
+ if (copy_from_user(&params, optval, optlen))
+ return -EFAULT;
+
+ /*
+ * API 7. Socket Options (setting the default value for the endpoint)
+ * All options that support specific settings on an association by
+ * filling in either an association id variable or a sockaddr_storage
+ * SHOULD also support setting of the same value for the entire endpoint
+ * (i.e. future associations). To accomplish this the following logic is
+ * used when setting one of these options:
+
+ * c) If neither the sockaddr_storage or association identification is
+ * set i.e. the sockaddr_storage is set to all 0's (INADDR_ANY) and
+ * the association identification is 0, the settings are a default
+ * and to be applied to the endpoint (all future associations).
+ */
+
+ /* update default value for endpoint (all future associations) */
+ if (!params.spp_assoc_id &&
+ sctp_is_any(( union sctp_addr *)&params.spp_address)) {
+ /* Manual heartbeat on an endpoint is invalid. */
+ if (0xffffffff == params.spp_hbinterval)
+ return -EINVAL;
+ else if (params.spp_hbinterval)
+ sctp_sk(sk)->paddrparam.spp_hbinterval =
+ params.spp_hbinterval;
+ if (params.spp_pathmaxrxt)
+ sctp_sk(sk)->paddrparam.spp_pathmaxrxt =
+ params.spp_pathmaxrxt;
+ return 0;
+ }
+
+ trans = sctp_addr_id2transport(sk, &params.spp_address,
+ params.spp_assoc_id);
+ if (!trans)
+ return -EINVAL;
+
+ /* Applications can enable or disable heartbeats for any peer address
+ * of an association, modify an address's heartbeat interval, force a
+ * heartbeat to be sent immediately, and adjust the address's maximum
+ * number of retransmissions sent before an address is considered
+ * unreachable.
+ *
+ * The value of the heartbeat interval, in milliseconds. A value of
+ * UINT32_MAX (4294967295), when modifying the parameter, specifies
+ * that a heartbeat should be sent immediately to the peer address,
+ * and the current interval should remain unchanged.
+ */
+ if (0xffffffff == params.spp_hbinterval) {
+ error = sctp_primitive_REQUESTHEARTBEAT (trans->asoc, trans);
+ if (error)
+ return error;
+ } else {
+ /* The value of the heartbeat interval, in milliseconds. A value of 0,
+ * when modifying the parameter, specifies that the heartbeat on this
+ * address should be disabled.
+ */
+ if (params.spp_hbinterval) {
+ trans->hb_allowed = 1;
+ trans->hb_interval =
+ SCTP_MSECS_TO_JIFFIES(params.spp_hbinterval);
+ } else
+ trans->hb_allowed = 0;
+ }
+
+ /* spp_pathmaxrxt contains the maximum number of retransmissions
+ * before this address shall be considered unreachable.
+ */
+ if (params.spp_pathmaxrxt)
+ trans->max_retrans = params.spp_pathmaxrxt;
+
+ return 0;
+}
+
+/* 7.1.3 Initialization Parameters (SCTP_INITMSG)
+ *
+ * Applications can specify protocol parameters for the default association
+ * initialization. The option name argument to setsockopt() and getsockopt()
+ * is SCTP_INITMSG.
+ *
+ * Setting initialization parameters is effective only on an unconnected
+ * socket (for UDP-style sockets only future associations are effected
+ * by the change). With TCP-style sockets, this option is inherited by
+ * sockets derived from a listener socket.
+ */
+static int sctp_setsockopt_initmsg(struct sock *sk, char *optval, int optlen)
+{
+ struct sctp_initmsg sinit;
+ struct sctp_opt *sp = sctp_sk(sk);
+
+ if (optlen != sizeof(struct sctp_initmsg))
+ return -EINVAL;
+ if (copy_from_user(&sinit, optval, optlen))
+ return -EFAULT;
+
+ if (sinit.sinit_num_ostreams)
+ sp->initmsg.sinit_num_ostreams = sinit.sinit_num_ostreams;
+ if (sinit.sinit_max_instreams)
+ sp->initmsg.sinit_max_instreams = sinit.sinit_max_instreams;
+ if (sinit.sinit_max_attempts)
+ sp->initmsg.sinit_max_attempts = sinit.sinit_max_attempts;
+ if (sinit.sinit_max_init_timeo)
+ sp->initmsg.sinit_max_init_timeo = sinit.sinit_max_init_timeo;
+
+ return 0;
+}
+
+/*
+ * 7.1.14 Set default send parameters (SCTP_DEFAULT_SEND_PARAM)
+ *
+ * Applications that wish to use the sendto() system call may wish to
+ * specify a default set of parameters that would normally be supplied
+ * through the inclusion of ancillary data. This socket option allows
+ * such an application to set the default sctp_sndrcvinfo structure.
+ * The application that wishes to use this socket option simply passes
+ * in to this call the sctp_sndrcvinfo structure defined in Section
+ * 5.2.2) The input parameters accepted by this call include
+ * sinfo_stream, sinfo_flags, sinfo_ppid, sinfo_context,
+ * sinfo_timetolive. The user must provide the sinfo_assoc_id field in
+ * to this call if the caller is using the UDP model.
+ */
+static int sctp_setsockopt_default_send_param(struct sock *sk,
+ char *optval, int optlen)
+{
+ struct sctp_sndrcvinfo info;
+ struct sctp_association *asoc;
+ struct sctp_opt *sp = sctp_sk(sk);
+
+ if (optlen != sizeof(struct sctp_sndrcvinfo))
+ return -EINVAL;
+ if (copy_from_user(&info, optval, optlen))
+ return -EFAULT;
+
+ asoc = sctp_id2assoc(sk, info.sinfo_assoc_id);
+ if (!asoc && info.sinfo_assoc_id && sctp_style(sk, UDP))
+ return -EINVAL;
+
+ if (asoc) {
+ asoc->default_stream = info.sinfo_stream;
+ asoc->default_flags = info.sinfo_flags;
+ asoc->default_ppid = info.sinfo_ppid;
+ asoc->default_context = info.sinfo_context;
+ asoc->default_timetolive = info.sinfo_timetolive;
+ } else {
+ sp->default_stream = info.sinfo_stream;
+ sp->default_flags = info.sinfo_flags;
+ sp->default_ppid = info.sinfo_ppid;
+ sp->default_context = info.sinfo_context;
+ sp->default_timetolive = info.sinfo_timetolive;
+ }
+
+ return 0;
+}
+
+/* 7.1.10 Set Primary Address (SCTP_PRIMARY_ADDR)
+ *
+ * Requests that the local SCTP stack use the enclosed peer address as
+ * the association primary. The enclosed address must be one of the
+ * association peer's addresses.
+ */
+static int sctp_setsockopt_primary_addr(struct sock *sk, char *optval,
+ int optlen)
+{
+ struct sctp_prim prim;
+ struct sctp_transport *trans;
+
+ if (optlen != sizeof(struct sctp_prim))
+ return -EINVAL;
+
+ if (copy_from_user(&prim, optval, sizeof(struct sctp_prim)))
+ return -EFAULT;
+
+ trans = sctp_addr_id2transport(sk, &prim.ssp_addr, prim.ssp_assoc_id);
+ if (!trans)
+ return -EINVAL;
+
+ sctp_assoc_set_primary(trans->asoc, trans);
+
+ return 0;
+}
+
+/*
+ * 7.1.5 SCTP_NODELAY
+ *
+ * Turn on/off any Nagle-like algorithm. This means that packets are
+ * generally sent as soon as possible and no unnecessary delays are
+ * introduced, at the cost of more packets in the network. Expects an
+ * integer boolean flag.
+ */
+static int sctp_setsockopt_nodelay(struct sock *sk, char *optval,
+ int optlen)
+{
+ int val;
+
+ if (optlen < sizeof(int))
+ return -EINVAL;
+ if (get_user(val, (int *)optval))
+ return -EFAULT;
+
+ sctp_sk(sk)->nodelay = (val == 0) ? 0 : 1;
+ return 0;
+}
+
+/*
+ *
+ * 7.1.1 SCTP_RTOINFO
+ *
+ * The protocol parameters used to initialize and bound retransmission
+ * timeout (RTO) are tunable. sctp_rtoinfo structure is used to access
+ * and modify these parameters.
+ * All parameters are time values, in milliseconds. A value of 0, when
+ * modifying the parameters, indicates that the current value should not
+ * be changed.
+ *
+ */
+static int sctp_setsockopt_rtoinfo(struct sock *sk, char *optval, int optlen) {
+ struct sctp_rtoinfo rtoinfo;
+ struct sctp_association *asoc;
+
+ if (optlen != sizeof (struct sctp_rtoinfo))
+ return -EINVAL;
+
+ if (copy_from_user(&rtoinfo, optval, optlen))
+ return -EFAULT;
+
+ asoc = sctp_id2assoc(sk, rtoinfo.srto_assoc_id);
+
+ /* Set the values to the specific association */
+ if (!asoc && rtoinfo.srto_assoc_id && sctp_style(sk, UDP))
+ return -EINVAL;
+
+ if (asoc) {
+ if (rtoinfo.srto_initial != 0)
+ asoc->rto_initial =
+ SCTP_MSECS_TO_JIFFIES(rtoinfo.srto_initial);
+ if (rtoinfo.srto_max != 0)
+ asoc->rto_max = SCTP_MSECS_TO_JIFFIES(rtoinfo.srto_max);
+ if (rtoinfo.srto_min != 0)
+ asoc->rto_min = SCTP_MSECS_TO_JIFFIES(rtoinfo.srto_min);
+ } else {
+ /* If there is no association or the association-id = 0
+ * set the values to the endpoint.
+ */
+ struct sctp_opt *sp = sctp_sk(sk);
+
+ if (rtoinfo.srto_initial != 0)
+ sp->rtoinfo.srto_initial = rtoinfo.srto_initial;
+ if (rtoinfo.srto_max != 0)
+ sp->rtoinfo.srto_max = rtoinfo.srto_max;
+ if (rtoinfo.srto_min != 0)
+ sp->rtoinfo.srto_min = rtoinfo.srto_min;
+ }
+
+ return 0;
+}
+
+/*
+ *
+ * 7.1.2 SCTP_ASSOCINFO
+ *
+ * This option is used to tune the the maximum retransmission attempts
+ * of the association.
+ * Returns an error if the new association retransmission value is
+ * greater than the sum of the retransmission value of the peer.
+ * See [SCTP] for more information.
+ *
+ */
+static int sctp_setsockopt_associnfo(struct sock *sk, char *optval, int optlen)
+{
+
+ struct sctp_assocparams assocparams;
+ struct sctp_association *asoc;
+
+ if (optlen != sizeof(struct sctp_assocparams))
+ return -EINVAL;
+ if (copy_from_user(&assocparams, optval, optlen))
+ return -EFAULT;
+
+ asoc = sctp_id2assoc(sk, assocparams.sasoc_assoc_id);
+
+ if (!asoc && assocparams.sasoc_assoc_id && sctp_style(sk, UDP))
+ return -EINVAL;
+
+ /* Set the values to the specific association */
+ if (asoc) {
+ if (assocparams.sasoc_asocmaxrxt != 0)
+ asoc->max_retrans = assocparams.sasoc_asocmaxrxt;
+ if (assocparams.sasoc_cookie_life != 0) {
+ asoc->cookie_life.tv_sec =
+ assocparams.sasoc_cookie_life / 1000;
+ asoc->cookie_life.tv_usec =
+ (assocparams.sasoc_cookie_life % 1000)
+ * 1000;
+ }
+ } else {
+ /* Set the values to the endpoint */
+ struct sctp_opt *sp = sctp_sk(sk);
+
+ if (assocparams.sasoc_asocmaxrxt != 0)
+ sp->assocparams.sasoc_asocmaxrxt =
+ assocparams.sasoc_asocmaxrxt;
+ if (assocparams.sasoc_cookie_life != 0)
+ sp->assocparams.sasoc_cookie_life =
+ assocparams.sasoc_cookie_life;
+ }
+ return 0;
+}
+
+/*
+ * 7.1.16 Set/clear IPv4 mapped addresses (SCTP_I_WANT_MAPPED_V4_ADDR)
+ *
+ * This socket option is a boolean flag which turns on or off mapped V4
+ * addresses. If this option is turned on and the socket is type
+ * PF_INET6, then IPv4 addresses will be mapped to V6 representation.
+ * If this option is turned off, then no mapping will be done of V4
+ * addresses and a user will receive both PF_INET6 and PF_INET type
+ * addresses on the socket.
+ */
+static int sctp_setsockopt_mappedv4(struct sock *sk, char *optval, int optlen)
+{
+ int val;
+ struct sctp_opt *sp = sctp_sk(sk);
+
+ if (optlen < sizeof(int))
+ return -EINVAL;
+ if (get_user(val, (int *)optval))
+ return -EFAULT;
+ if (val)
+ sp->v4mapped = 1;
+ else
+ sp->v4mapped = 0;
+
+ return 0;
+}
+
+/*
+ * 7.1.17 Set the maximum fragrmentation size (SCTP_MAXSEG)
+ *
+ * This socket option specifies the maximum size to put in any outgoing
+ * SCTP chunk. If a message is larger than this size it will be
+ * fragmented by SCTP into the specified size. Note that the underlying
+ * SCTP implementation may fragment into smaller sized chunks when the
+ * PMTU of the underlying association is smaller than the value set by
+ * the user.
+ */
+static int sctp_setsockopt_maxseg(struct sock *sk, char *optval, int optlen)
+{
+ struct sctp_association *asoc;
+ struct list_head *pos;
+ struct sctp_opt *sp = sctp_sk(sk);
+ int val;
+
+ if (optlen < sizeof(int))
+ return -EINVAL;
+ if (get_user(val, (int *)optval))
+ return -EFAULT;
+ if ((val < 8) || (val > SCTP_MAX_CHUNK_LEN))
+ return -EINVAL;
+ sp->user_frag = val;
+
+ if (val) {
+ /* Update the frag_point of the existing associations. */
+ list_for_each(pos, &(sp->ep->asocs)) {
+ asoc = list_entry(pos, struct sctp_association, asocs);
+ asoc->frag_point = sctp_frag_point(sp, asoc->pmtu);
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ * 7.1.9 Set Peer Primary Address (SCTP_SET_PEER_PRIMARY_ADDR)
+ *
+ * Requests that the peer mark the enclosed address as the association
+ * primary. The enclosed address must be one of the association's
+ * locally bound addresses. The following structure is used to make a
+ * set primary request:
+ */
+static int sctp_setsockopt_peer_primary_addr(struct sock *sk, char *optval,
+ int optlen)
+{
+ struct sctp_opt *sp;
+ struct sctp_endpoint *ep;
+ struct sctp_association *asoc = NULL;
+ struct sctp_setpeerprim prim;
+ struct sctp_chunk *chunk;
+ int err;
+
+ sp = sctp_sk(sk);
+ ep = sp->ep;
+
+ if (!sctp_addip_enable)
+ return -EPERM;
+
+ if (optlen != sizeof(struct sctp_setpeerprim))
+ return -EINVAL;
+
+ if (copy_from_user(&prim, optval, optlen))
+ return -EFAULT;
+
+ asoc = sctp_id2assoc(sk, prim.sspp_assoc_id);
+ if (!asoc)
+ return -EINVAL;
+
+ if (!asoc->peer.asconf_capable)
+ return -EPERM;
+
+ if (asoc->peer.addip_disabled_mask & SCTP_PARAM_SET_PRIMARY)
+ return -EPERM;
+
+ if (!sctp_state(asoc, ESTABLISHED))
+ return -ENOTCONN;
+
+ if (!sctp_assoc_lookup_laddr(asoc, (union sctp_addr *)&prim.sspp_addr))
+ return -EADDRNOTAVAIL;
+
+ /* Create an ASCONF chunk with SET_PRIMARY parameter */
+ chunk = sctp_make_asconf_set_prim(asoc,
+ (union sctp_addr *)&prim.sspp_addr);
+ if (!chunk)
+ return -ENOMEM;
+
+ err = sctp_send_asconf(asoc, chunk);
+
+ SCTP_DEBUG_PRINTK("We set peer primary addr primitively.\n");
+
+ return err;
+}
+
+static int sctp_setsockopt_adaption_layer(struct sock *sk, char __user *optval,
+ int optlen)
+{
+ __u32 val;
+
+ if (optlen < sizeof(__u32))
+ return -EINVAL;
+ if (copy_from_user(&val, optval, sizeof(__u32)))
+ return -EFAULT;
+
+ sctp_sk(sk)->adaption_ind = val;
+
+ return 0;
+}
+
+/* API 6.2 setsockopt(), getsockopt()
+ *
+ * Applications use setsockopt() and getsockopt() to set or retrieve
+ * socket options. Socket options are used to change the default
+ * behavior of sockets calls. They are described in Section 7.
+ *
+ * The syntax is:
+ *
+ * ret = getsockopt(int sd, int level, int optname, void *optval,
+ * int *optlen);
+ * ret = setsockopt(int sd, int level, int optname, const void *optval,
+ * int optlen);
+ *
+ * sd - the socket descript.
+ * level - set to IPPROTO_SCTP for all SCTP options.
+ * optname - the option name.
+ * optval - the buffer to store the value of the option.
+ * optlen - the size of the buffer.
+ */
+SCTP_STATIC int sctp_setsockopt(struct sock *sk, int level, int optname,
+ char *optval, int optlen)
+{
+ int retval = 0;
+
+ SCTP_DEBUG_PRINTK("sctp_setsockopt(sk: %p... optname: %d)\n",
+ sk, optname);
+
+ /* I can hardly begin to describe how wrong this is. This is
+ * so broken as to be worse than useless. The API draft
+ * REALLY is NOT helpful here... I am not convinced that the
+ * semantics of setsockopt() with a level OTHER THAN SOL_SCTP
+ * are at all well-founded.
+ */
+ if (level != SOL_SCTP) {
+ struct sctp_af *af = sctp_sk(sk)->pf->af;
+ retval = af->setsockopt(sk, level, optname, optval, optlen);
+ goto out_nounlock;
+ }
+
+ sctp_lock_sock(sk);
+
+ switch (optname) {
+ case SCTP_SOCKOPT_BINDX_ADD:
+ /* 'optlen' is the size of the addresses buffer. */
+ retval = sctp_setsockopt_bindx(sk, (struct sockaddr *)optval,
+ optlen, SCTP_BINDX_ADD_ADDR);
+ break;
+
+ case SCTP_SOCKOPT_BINDX_REM:
+ /* 'optlen' is the size of the addresses buffer. */
+ retval = sctp_setsockopt_bindx(sk, (struct sockaddr *)optval,
+ optlen, SCTP_BINDX_REM_ADDR);
+ break;
+
+ case SCTP_DISABLE_FRAGMENTS:
+ retval = sctp_setsockopt_disable_fragments(sk, optval, optlen);
+ break;
+
+ case SCTP_EVENTS:
+ retval = sctp_setsockopt_events(sk, optval, optlen);
+ break;
+
+ case SCTP_AUTOCLOSE:
+ retval = sctp_setsockopt_autoclose(sk, optval, optlen);
+ break;
+
+ case SCTP_PEER_ADDR_PARAMS:
+ retval = sctp_setsockopt_peer_addr_params(sk, optval, optlen);
+ break;
+
+ case SCTP_INITMSG:
+ retval = sctp_setsockopt_initmsg(sk, optval, optlen);
+ break;
+ case SCTP_DEFAULT_SEND_PARAM:
+ retval = sctp_setsockopt_default_send_param(sk, optval,
+ optlen);
+ break;
+ case SCTP_PRIMARY_ADDR:
+ retval = sctp_setsockopt_primary_addr(sk, optval, optlen);
+ break;
+ case SCTP_SET_PEER_PRIMARY_ADDR:
+ retval = sctp_setsockopt_peer_primary_addr(sk, optval, optlen);
+ break;
+ case SCTP_NODELAY:
+ retval = sctp_setsockopt_nodelay(sk, optval, optlen);
+ break;
+ case SCTP_RTOINFO:
+ retval = sctp_setsockopt_rtoinfo(sk, optval, optlen);
+ break;
+ case SCTP_ASSOCINFO:
+ retval = sctp_setsockopt_associnfo(sk, optval, optlen);
+ break;
+ case SCTP_I_WANT_MAPPED_V4_ADDR:
+ retval = sctp_setsockopt_mappedv4(sk, optval, optlen);
+ break;
+ case SCTP_MAXSEG:
+ retval = sctp_setsockopt_maxseg(sk, optval, optlen);
+ break;
+ case SCTP_ADAPTION_LAYER:
+ retval = sctp_setsockopt_adaption_layer(sk, optval, optlen);
+ break;
+
+ default:
+ retval = -ENOPROTOOPT;
+ break;
+ };
+
+ sctp_release_sock(sk);
+
+out_nounlock:
+ return retval;
+}
+
+/* API 3.1.6 connect() - UDP Style Syntax
+ *
+ * An application may use the connect() call in the UDP model to initiate an
+ * association without sending data.
+ *
+ * The syntax is:
+ *
+ * ret = connect(int sd, const struct sockaddr *nam, socklen_t len);
+ *
+ * sd: the socket descriptor to have a new association added to.
+ *
+ * nam: the address structure (either struct sockaddr_in or struct
+ * sockaddr_in6 defined in RFC2553 [7]).
+ *
+ * len: the size of the address.
+ */
+SCTP_STATIC int sctp_connect(struct sock *sk, struct sockaddr *uaddr,
+ int addr_len)
+{
+ struct sctp_opt *sp;
+ struct sctp_endpoint *ep;
+ struct sctp_association *asoc;
+ struct sctp_transport *transport;
+ union sctp_addr to;
+ struct sctp_af *af;
+ sctp_scope_t scope;
+ long timeo;
+ int err = 0;
+
+ sctp_lock_sock(sk);
+
+ SCTP_DEBUG_PRINTK("%s - sk: %p, sockaddr: %p, addr_len: %d)\n",
+ __FUNCTION__, sk, uaddr, addr_len);
+
+ sp = sctp_sk(sk);
+ ep = sp->ep;
+
+ /* connect() cannot be done on a socket that is already in ESTABLISHED
+ * state - UDP-style peeled off socket or a TCP-style socket that
+ * is already connected.
+ * It cannot be done even on a TCP-style listening socket.
+ */
+ if (sctp_sstate(sk, ESTABLISHED) ||
+ (sctp_style(sk, TCP) && sctp_sstate(sk, LISTENING))) {
+ err = -EISCONN;
+ goto out_unlock;
+ }
+
+ err = sctp_verify_addr(sk, (union sctp_addr *)uaddr, addr_len);
+ if (err)
+ goto out_unlock;
+
+ if (addr_len > sizeof(to))
+ addr_len = sizeof(to);
+ memcpy(&to, uaddr, addr_len);
+ to.v4.sin_port = ntohs(to.v4.sin_port);
+
+ asoc = sctp_endpoint_lookup_assoc(ep, &to, &transport);
+ if (asoc) {
+ if (asoc->state >= SCTP_STATE_ESTABLISHED)
+ err = -EISCONN;
+ else
+ err = -EALREADY;
+ goto out_unlock;
+ }
+
+ /* If we could not find a matching association on the endpoint,
+ * make sure that there is no peeled-off association matching the
+ * peer address even on another socket.
+ */
+ if (sctp_endpoint_is_peeled_off(ep, &to)) {
+ err = -EADDRNOTAVAIL;
+ goto out_unlock;
+ }
+
+ /* If a bind() or sctp_bindx() is not called prior to a connect()
+ * call, the system picks an ephemeral port and will choose an address
+ * set equivalent to binding with a wildcard address.
+ */
+ if (!ep->base.bind_addr.port) {
+ if (sctp_autobind(sk)) {
+ err = -EAGAIN;
+ goto out_unlock;
+ }
+ }
+
+ scope = sctp_scope(&to);
+ asoc = sctp_association_new(ep, sk, scope, GFP_KERNEL);
+ if (!asoc) {
+ err = -ENOMEM;
+ goto out_unlock;
+ }
+
+ /* Prime the peer's transport structures. */
+ transport = sctp_assoc_add_peer(asoc, &to, GFP_KERNEL);
+ if (!transport) {
+ sctp_association_free(asoc);
+ goto out_unlock;
+ }
+ err = sctp_assoc_set_bind_addr_from_ep(asoc, GFP_KERNEL);
+ if (err < 0) {
+ sctp_association_free(asoc);
+ goto out_unlock;
+ }
+
+ err = sctp_primitive_ASSOCIATE(asoc, NULL);
+ if (err < 0) {
+ sctp_association_free(asoc);
+ goto out_unlock;
+ }
+
+ /* Initialize sk's dport and daddr for getpeername() */
+ sk->dport = htons(asoc->peer.port);
+ af = sctp_get_af_specific(to.sa.sa_family);
+ af->to_sk_daddr(&to, sk);
+
+ timeo = sock_sndtimeo(sk, sk->socket->file->f_flags & O_NONBLOCK);
+ err = sctp_wait_for_connect(asoc, &timeo);
+
+out_unlock:
+ sctp_release_sock(sk);
+
+ return err;
+}
+
+/* FIXME: Write comments. */
+SCTP_STATIC int sctp_disconnect(struct sock *sk, int flags)
+{
+ return -EOPNOTSUPP; /* STUB */
+}
+
+/* 4.1.4 accept() - TCP Style Syntax
+ *
+ * Applications use accept() call to remove an established SCTP
+ * association from the accept queue of the endpoint. A new socket
+ * descriptor will be returned from accept() to represent the newly
+ * formed association.
+ */
+SCTP_STATIC struct sock *sctp_accept(struct sock *sk, int flags, int *err)
+{
+ struct sctp_opt *sp;
+ struct sctp_endpoint *ep;
+ struct sock *newsk = NULL;
+ struct sctp_association *asoc;
+ long timeo;
+ int error = 0;
+
+ sctp_lock_sock(sk);
+
+ sp = sctp_sk(sk);
+ ep = sp->ep;
+
+ if (!sctp_style(sk, TCP)) {
+ error = -EOPNOTSUPP;
+ goto out;
+ }
+
+ if (!sctp_sstate(sk, LISTENING)) {
+ error = -EINVAL;
+ goto out;
+ }
+
+ timeo = sock_rcvtimeo(sk, sk->socket->file->f_flags & O_NONBLOCK);
+
+ error = sctp_wait_for_accept(sk, timeo);
+ if (error)
+ goto out;
+
+ /* We treat the list of associations on the endpoint as the accept
+ * queue and pick the first association on the list.
+ */
+ asoc = list_entry(ep->asocs.next, struct sctp_association, asocs);
+
+ newsk = sp->pf->create_accept_sk(sk, asoc);
+ if (!newsk) {
+ error = -ENOMEM;
+ goto out;
+ }
+
+ /* Populate the fields of the newsk from the oldsk and migrate the
+ * asoc to the newsk.
+ */
+ sctp_sock_migrate(sk, newsk, asoc, SCTP_SOCKET_TCP);
+
+out:
+ sctp_release_sock(sk);
+ *err = error;
+ return newsk;
+}
+
+/* The SCTP ioctl handler. */
+SCTP_STATIC int sctp_ioctl(struct sock *sk, int cmd, unsigned long arg)
+{
+ return -ENOIOCTLCMD;
+}
+
+/* This is the function which gets called during socket creation to
+ * initialized the SCTP-specific portion of the sock.
+ * The sock structure should already be zero-filled memory.
+ */
+SCTP_STATIC int sctp_init_sock(struct sock *sk)
+{
+ struct sctp_endpoint *ep;
+ struct sctp_opt *sp;
+
+ SCTP_DEBUG_PRINTK("sctp_init_sock(sk: %p)\n", sk);
+
+ sp = sctp_sk(sk);
+
+ /* Initialize the SCTP per socket area. */
+ switch (sk->type) {
+ case SOCK_SEQPACKET:
+ sp->type = SCTP_SOCKET_UDP;
+ break;
+ case SOCK_STREAM:
+ sp->type = SCTP_SOCKET_TCP;
+ break;
+ default:
+ return -ESOCKTNOSUPPORT;
+ }
+
+ /* Initialize default send parameters. These parameters can be
+ * modified with the SCTP_DEFAULT_SEND_PARAM socket option.
+ */
+ sp->default_stream = 0;
+ sp->default_ppid = 0;
+ sp->default_flags = 0;
+ sp->default_context = 0;
+ sp->default_timetolive = 0;
+
+ /* Initialize default setup parameters. These parameters
+ * can be modified with the SCTP_INITMSG socket option or
+ * overridden by the SCTP_INIT CMSG.
+ */
+ sp->initmsg.sinit_num_ostreams = sctp_max_outstreams;
+ sp->initmsg.sinit_max_instreams = sctp_max_instreams;
+ sp->initmsg.sinit_max_attempts = sctp_max_retrans_init;
+ sp->initmsg.sinit_max_init_timeo = JIFFIES_TO_MSECS(sctp_rto_max);
+
+ /* Initialize default RTO related parameters. These parameters can
+ * be modified for with the SCTP_RTOINFO socket option.
+ */
+ sp->rtoinfo.srto_initial = JIFFIES_TO_MSECS(sctp_rto_initial);
+ sp->rtoinfo.srto_max = JIFFIES_TO_MSECS(sctp_rto_max);
+ sp->rtoinfo.srto_min = JIFFIES_TO_MSECS(sctp_rto_min);
+
+ /* Initialize default association related parameters. These parameters
+ * can be modified with the SCTP_ASSOCINFO socket option.
+ */
+ sp->assocparams.sasoc_asocmaxrxt = sctp_max_retrans_association;
+ sp->assocparams.sasoc_number_peer_destinations = 0;
+ sp->assocparams.sasoc_peer_rwnd = 0;
+ sp->assocparams.sasoc_local_rwnd = 0;
+ sp->assocparams.sasoc_cookie_life =
+ JIFFIES_TO_MSECS(sctp_valid_cookie_life);
+
+ /* Initialize default event subscriptions. By default, all the
+ * options are off.
+ */
+ memset(&sp->subscribe, 0, sizeof(struct sctp_event_subscribe));
+
+ /* Default Peer Address Parameters. These defaults can
+ * be modified via SCTP_PEER_ADDR_PARAMS
+ */
+ sp->paddrparam.spp_hbinterval = JIFFIES_TO_MSECS(sctp_hb_interval);
+ sp->paddrparam.spp_pathmaxrxt = sctp_max_retrans_path;
+
+ /* If enabled no SCTP message fragmentation will be performed.
+ * Configure through SCTP_DISABLE_FRAGMENTS socket option.
+ */
+ sp->disable_fragments = 0;
+
+ /* Turn on/off any Nagle-like algorithm. */
+ sp->nodelay = 1;
+
+ /* Enable by default. */
+ sp->v4mapped = 1;
+
+ /* Auto-close idle associations after the configured
+ * number of seconds. A value of 0 disables this
+ * feature. Configure through the SCTP_AUTOCLOSE socket option,
+ * for UDP-style sockets only.
+ */
+ sp->autoclose = 0;
+
+ /* User specified fragmentation limit. */
+ sp->user_frag = 0;
+
+ sp->adaption_ind = 0;
+
+ sp->pf = sctp_get_pf_specific(sk->family);
+
+ /* Control variables for partial data delivery. */
+ sp->pd_mode = 0;
+ skb_queue_head_init(&sp->pd_lobby);
+
+ /* Create a per socket endpoint structure. Even if we
+ * change the data structure relationships, this may still
+ * be useful for storing pre-connect address information.
+ */
+ ep = sctp_endpoint_new(sk, GFP_KERNEL);
+ if (!ep)
+ return -ENOMEM;
+
+ sp->ep = ep;
+ sp->hmac = NULL;
+
+ SCTP_DBG_OBJCNT_INC(sock);
+ return 0;
+}
+
+/* Cleanup any SCTP per socket resources. */
+SCTP_STATIC int sctp_destroy_sock(struct sock *sk)
+{
+ struct sctp_endpoint *ep;
+
+ SCTP_DEBUG_PRINTK("sctp_destroy_sock(sk: %p)\n", sk);
+
+ /* Release our hold on the endpoint. */
+ ep = sctp_sk(sk)->ep;
+ sctp_endpoint_free(ep);
+
+ return 0;
+}
+
+/* API 4.1.7 shutdown() - TCP Style Syntax
+ * int shutdown(int socket, int how);
+ *
+ * sd - the socket descriptor of the association to be closed.
+ * how - Specifies the type of shutdown. The values are
+ * as follows:
+ * SHUT_RD
+ * Disables further receive operations. No SCTP
+ * protocol action is taken.
+ * SHUT_WR
+ * Disables further send operations, and initiates
+ * the SCTP shutdown sequence.
+ * SHUT_RDWR
+ * Disables further send and receive operations
+ * and initiates the SCTP shutdown sequence.
+ */
+SCTP_STATIC void sctp_shutdown(struct sock *sk, int how)
+{
+ struct sctp_endpoint *ep;
+ struct sctp_association *asoc;
+
+ if (!sctp_style(sk, TCP))
+ return;
+
+ if (how & SEND_SHUTDOWN) {
+ ep = sctp_sk(sk)->ep;
+ if (!list_empty(&ep->asocs)) {
+ asoc = list_entry(ep->asocs.next,
+ struct sctp_association, asocs);
+ sctp_primitive_SHUTDOWN(asoc, NULL);
+ }
+ }
+}
+
+/* 7.2.1 Association Status (SCTP_STATUS)
+
+ * Applications can retrieve current status information about an
+ * association, including association state, peer receiver window size,
+ * number of unacked data chunks, and number of data chunks pending
+ * receipt. This information is read-only.
+ */
+static int sctp_getsockopt_sctp_status(struct sock *sk, int len, char *optval,
+ int *optlen)
+{
+ struct sctp_status status;
+ struct sctp_association *asoc = NULL;
+ struct sctp_transport *transport;
+ sctp_assoc_t associd;
+ int retval = 0;
+
+ if (len != sizeof(status)) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ if (copy_from_user(&status, optval, sizeof(status))) {
+ retval = -EFAULT;
+ goto out;
+ }
+
+ associd = status.sstat_assoc_id;
+ asoc = sctp_id2assoc(sk, associd);
+ if (!asoc) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ transport = asoc->peer.primary_path;
+
+ status.sstat_assoc_id = sctp_assoc2id(asoc);
+ status.sstat_state = asoc->state;
+ status.sstat_rwnd = asoc->peer.rwnd;
+ status.sstat_unackdata = asoc->unack_data;
+
+ status.sstat_penddata = sctp_tsnmap_pending(&asoc->peer.tsn_map);
+ status.sstat_instrms = asoc->c.sinit_max_instreams;
+ status.sstat_outstrms = asoc->c.sinit_num_ostreams;
+ status.sstat_fragmentation_point = asoc->frag_point;
+ status.sstat_primary.spinfo_assoc_id = sctp_assoc2id(transport->asoc);
+ memcpy(&status.sstat_primary.spinfo_address,
+ &(transport->ipaddr), sizeof(union sctp_addr));
+ /* Map ipv4 address into v4-mapped-on-v6 address. */
+ sctp_get_pf_specific(sk->family)->addr_v4map(sctp_sk(sk),
+ (union sctp_addr *)&status.sstat_primary.spinfo_address);
+ status.sstat_primary.spinfo_state = transport->active;
+ status.sstat_primary.spinfo_cwnd = transport->cwnd;
+ status.sstat_primary.spinfo_srtt = transport->srtt;
+ status.sstat_primary.spinfo_rto = JIFFIES_TO_MSECS(transport->rto);
+ status.sstat_primary.spinfo_mtu = transport->pmtu;
+
+ if (put_user(len, optlen)) {
+ retval = -EFAULT;
+ goto out;
+ }
+
+ SCTP_DEBUG_PRINTK("sctp_getsockopt_sctp_status(%d): %d %d %p\n",
+ len, status.sstat_state, status.sstat_rwnd,
+ status.sstat_assoc_id);
+
+ if (copy_to_user(optval, &status, len)) {
+ retval = -EFAULT;
+ goto out;
+ }
+
+out:
+ return (retval);
+}
+
+
+/* 7.2.2 Peer Address Information (SCTP_GET_PEER_ADDR_INFO)
+ *
+ * Applications can retrieve information about a specific peer address
+ * of an association, including its reachability state, congestion
+ * window, and retransmission timer values. This information is
+ * read-only.
+ */
+static int sctp_getsockopt_peer_addr_info(struct sock *sk, int len,
+ char *optval, int *optlen)
+{
+ struct sctp_paddrinfo pinfo;
+ struct sctp_transport *transport;
+ int retval = 0;
+
+ if (len != sizeof(pinfo)) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ if (copy_from_user(&pinfo, optval, sizeof(pinfo))) {
+ retval = -EFAULT;
+ goto out;
+ }
+
+ transport = sctp_addr_id2transport(sk, &pinfo.spinfo_address,
+ pinfo.spinfo_assoc_id);
+ if (!transport)
+ return -EINVAL;
+
+ pinfo.spinfo_assoc_id = sctp_assoc2id(transport->asoc);
+ pinfo.spinfo_state = transport->active;
+ pinfo.spinfo_cwnd = transport->cwnd;
+ pinfo.spinfo_srtt = transport->srtt;
+ pinfo.spinfo_rto = JIFFIES_TO_MSECS(transport->rto);
+ pinfo.spinfo_mtu = transport->pmtu;
+
+ if (put_user(len, optlen)) {
+ retval = -EFAULT;
+ goto out;
+ }
+
+ if (copy_to_user(optval, &pinfo, len)) {
+ retval = -EFAULT;
+ goto out;
+ }
+
+out:
+ return (retval);
+}
+
+/* 7.1.12 Enable/Disable message fragmentation (SCTP_DISABLE_FRAGMENTS)
+ *
+ * This option is a on/off flag. If enabled no SCTP message
+ * fragmentation will be performed. Instead if a message being sent
+ * exceeds the current PMTU size, the message will NOT be sent and
+ * instead a error will be indicated to the user.
+ */
+static int sctp_getsockopt_disable_fragments(struct sock *sk, int len,
+ char *optval, int *optlen)
+{
+ int val;
+
+ if (len < sizeof(int))
+ return -EINVAL;
+
+ len = sizeof(int);
+ val = (sctp_sk(sk)->disable_fragments == 1);
+ if (put_user(len, optlen))
+ return -EFAULT;
+ if (copy_to_user(optval, &val, len))
+ return -EFAULT;
+ return 0;
+}
+
+/* 7.1.15 Set notification and ancillary events (SCTP_EVENTS)
+ *
+ * This socket option is used to specify various notifications and
+ * ancillary data the user wishes to receive.
+ */
+static int sctp_getsockopt_events(struct sock *sk, int len, char *optval,
+ int *optlen)
+{
+ if (len != sizeof(struct sctp_event_subscribe))
+ return -EINVAL;
+ if (copy_to_user(optval, &sctp_sk(sk)->subscribe, len))
+ return -EFAULT;
+ return 0;
+}
+
+/* 7.1.8 Automatic Close of associations (SCTP_AUTOCLOSE)
+ *
+ * This socket option is applicable to the UDP-style socket only. When
+ * set it will cause associations that are idle for more than the
+ * specified number of seconds to automatically close. An association
+ * being idle is defined an association that has NOT sent or received
+ * user data. The special value of '0' indicates that no automatic
+ * close of any associations should be performed. The option expects an
+ * integer defining the number of seconds of idle time before an
+ * association is closed.
+ */
+static int sctp_getsockopt_autoclose(struct sock *sk, int len, char *optval, int *optlen)
+{
+ /* Applicable to UDP-style socket only */
+ if (sctp_style(sk, TCP))
+ return -EOPNOTSUPP;
+ if (len != sizeof(int))
+ return -EINVAL;
+ if (copy_to_user(optval, &sctp_sk(sk)->autoclose, len))
+ return -EFAULT;
+ return 0;
+}
+
+/* Helper routine to branch off an association to a new socket. */
+SCTP_STATIC int sctp_do_peeloff(struct sctp_association *asoc,
+ struct socket **sockp)
+{
+ struct sock *sk = asoc->base.sk;
+ struct socket *sock;
+ int err = 0;
+
+ /* An association cannot be branched off from an already peeled-off
+ * socket, nor is this supported for tcp style sockets.
+ */
+ if (!sctp_style(sk, UDP))
+ return -EINVAL;
+
+ /* Create a new socket. */
+ err = sock_create(sk->family, SOCK_SEQPACKET, IPPROTO_SCTP, &sock);
+ if (err < 0)
+ return err;
+
+ /* Populate the fields of the newsk from the oldsk and migrate the
+ * asoc to the newsk.
+ */
+ sctp_sock_migrate(sk, sock->sk, asoc, SCTP_SOCKET_UDP_HIGH_BANDWIDTH);
+ *sockp = sock;
+
+ return err;
+}
+
+static int sctp_getsockopt_peeloff(struct sock *sk, int len, char *optval, int *optlen)
+{
+ sctp_peeloff_arg_t peeloff;
+ struct socket *newsock;
+ int retval = 0;
+ struct sctp_association *asoc;
+
+ if (len != sizeof(sctp_peeloff_arg_t))
+ return -EINVAL;
+ if (copy_from_user(&peeloff, optval, len))
+ return -EFAULT;
+
+ asoc = sctp_id2assoc(sk, peeloff.associd);
+ if (!asoc) {
+ retval = -EINVAL;
+ goto out;
+ }
+
+ SCTP_DEBUG_PRINTK("%s: sk: %p asoc: %p\n", __FUNCTION__, sk, asoc);
+
+ retval = sctp_do_peeloff(asoc, &newsock);
+ if (retval < 0)
+ goto out;
+
+ /* Map the socket to an unused fd that can be returned to the user. */
+ retval = sock_map_fd(newsock);
+ if (retval < 0) {
+ sock_release(newsock);
+ goto out;
+ }
+
+ SCTP_DEBUG_PRINTK("%s: sk: %p asoc: %p newsk: %p sd: %d\n",
+ __FUNCTION__, sk, asoc, newsock->sk, retval);
+
+ /* Return the fd mapped to the new socket. */
+ peeloff.sd = retval;
+ if (copy_to_user(optval, &peeloff, len))
+ retval = -EFAULT;
+
+out:
+ return retval;
+}
+
+/* 7.1.13 Peer Address Parameters (SCTP_PEER_ADDR_PARAMS)
+ *
+ * Applications can enable or disable heartbeats for any peer address of
+ * an association, modify an address's heartbeat interval, force a
+ * heartbeat to be sent immediately, and adjust the address's maximum
+ * number of retransmissions sent before an address is considered
+ * unreachable. The following structure is used to access and modify an
+ * address's parameters:
+ *
+ * struct sctp_paddrparams {
+ * sctp_assoc_t spp_assoc_id;
+ * struct sockaddr_storage spp_address;
+ * uint32_t spp_hbinterval;
+ * uint16_t spp_pathmaxrxt;
+ * };
+ *
+ * spp_assoc_id - (UDP style socket) This is filled in the application,
+ * and identifies the association for this query.
+ * spp_address - This specifies which address is of interest.
+ * spp_hbinterval - This contains the value of the heartbeat interval,
+ * in milliseconds. A value of 0, when modifying the
+ * parameter, specifies that the heartbeat on this
+ * address should be disabled. A value of UINT32_MAX
+ * (4294967295), when modifying the parameter,
+ * specifies that a heartbeat should be sent
+ * immediately to the peer address, and the current
+ * interval should remain unchanged.
+ * spp_pathmaxrxt - This contains the maximum number of
+ * retransmissions before this address shall be
+ * considered unreachable.
+ */
+static int sctp_getsockopt_peer_addr_params(struct sock *sk, int len,
+ char *optval, int *optlen)
+{
+ struct sctp_paddrparams params;
+ struct sctp_transport *trans;
+
+ if (len != sizeof(struct sctp_paddrparams))
+ return -EINVAL;
+ if (copy_from_user(&params, optval, *optlen))
+ return -EFAULT;
+
+ /* If no association id is specified retrieve the default value
+ * for the endpoint that will be used for all future associations
+ */
+ if (!params.spp_assoc_id &&
+ sctp_is_any(( union sctp_addr *)&params.spp_address)) {
+ params.spp_hbinterval = sctp_sk(sk)->paddrparam.spp_hbinterval;
+ params.spp_pathmaxrxt = sctp_sk(sk)->paddrparam.spp_pathmaxrxt;
+
+ goto done;
+ }
+
+ trans = sctp_addr_id2transport(sk, &params.spp_address,
+ params.spp_assoc_id);
+ if (!trans)
+ return -EINVAL;
+
+ /* The value of the heartbeat interval, in milliseconds. A value of 0,
+ * when modifying the parameter, specifies that the heartbeat on this
+ * address should be disabled.
+ */
+ if (!trans->hb_allowed)
+ params.spp_hbinterval = 0;
+ else
+ params.spp_hbinterval = JIFFIES_TO_MSECS(trans->hb_interval);
+
+ /* spp_pathmaxrxt contains the maximum number of retransmissions
+ * before this address shall be considered unreachable.
+ */
+ params.spp_pathmaxrxt = trans->max_retrans;
+
+done:
+ if (copy_to_user(optval, &params, len))
+ return -EFAULT;
+
+ if (put_user(len, optlen))
+ return -EFAULT;
+
+ return 0;
+}
+
+/* 7.1.3 Initialization Parameters (SCTP_INITMSG)
+ *
+ * Applications can specify protocol parameters for the default association
+ * initialization. The option name argument to setsockopt() and getsockopt()
+ * is SCTP_INITMSG.
+ *
+ * Setting initialization parameters is effective only on an unconnected
+ * socket (for UDP-style sockets only future associations are effected
+ * by the change). With TCP-style sockets, this option is inherited by
+ * sockets derived from a listener socket.
+ */
+static int sctp_getsockopt_initmsg(struct sock *sk, int len, char *optval, int *optlen)
+{
+ if (len != sizeof(struct sctp_initmsg))
+ return -EINVAL;
+ if (copy_to_user(optval, &sctp_sk(sk)->initmsg, len))
+ return -EFAULT;
+ return 0;
+}
+
+static int sctp_getsockopt_peer_addrs_num(struct sock *sk, int len,
+ char *optval, int *optlen)
+{
+ sctp_assoc_t id;
+ struct sctp_association *asoc;
+ struct list_head *pos;
+ int cnt = 0;
+
+ if (len != sizeof(sctp_assoc_t))
+ return -EINVAL;
+
+ if (copy_from_user(&id, optval, sizeof(sctp_assoc_t)))
+ return -EFAULT;
+
+ /* For UDP-style sockets, id specifies the association to query. */
+ asoc = sctp_id2assoc(sk, id);
+ if (!asoc)
+ return -EINVAL;
+
+ list_for_each(pos, &asoc->peer.transport_addr_list) {
+ cnt ++;
+ }
+
+ return cnt;
+}
+
+static int sctp_getsockopt_peer_addrs(struct sock *sk, int len,
+ char *optval, int *optlen)
+{
+ struct sctp_association *asoc;
+ struct list_head *pos;
+ int cnt = 0;
+ struct sctp_getaddrs getaddrs;
+ struct sctp_transport *from;
+ void *to;
+ union sctp_addr temp;
+ struct sctp_opt *sp = sctp_sk(sk);
+ int addrlen;
+
+ if (len != sizeof(struct sctp_getaddrs))
+ return -EINVAL;
+
+ if (copy_from_user(&getaddrs, optval, sizeof(struct sctp_getaddrs)))
+ return -EFAULT;
+
+ if (getaddrs.addr_num <= 0) return -EINVAL;
+
+ /* For UDP-style sockets, id specifies the association to query. */
+ asoc = sctp_id2assoc(sk, getaddrs.assoc_id);
+ if (!asoc)
+ return -EINVAL;
+
+ to = (void *)getaddrs.addrs;
+ list_for_each(pos, &asoc->peer.transport_addr_list) {
+ from = list_entry(pos, struct sctp_transport, transports);
+ memcpy(&temp, &from->ipaddr, sizeof(temp));
+ sctp_get_pf_specific(sk->family)->addr_v4map(sp, &temp);
+ addrlen = sctp_get_af_specific(sk->family)->sockaddr_len;
+ temp.v4.sin_port = htons(temp.v4.sin_port);
+ if (copy_to_user(to, &temp, addrlen))
+ return -EFAULT;
+ to += addrlen ;
+ cnt ++;
+ if (cnt >= getaddrs.addr_num) break;
+ }
+ getaddrs.addr_num = cnt;
+ if (copy_to_user(optval, &getaddrs, sizeof(struct sctp_getaddrs)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int sctp_getsockopt_local_addrs_num(struct sock *sk, int len,
+ char *optval, int *optlen)
+{
+ sctp_assoc_t id;
+ struct sctp_bind_addr *bp;
+ struct sctp_association *asoc;
+ struct list_head *pos;
+ struct sctp_sockaddr_entry *addr;
+ rwlock_t *addr_lock;
+ unsigned long flags;
+ int cnt = 0;
+
+ if (len != sizeof(sctp_assoc_t))
+ return -EINVAL;
+
+ if (copy_from_user(&id, optval, sizeof(sctp_assoc_t)))
+ return -EFAULT;
+
+ /*
+ * For UDP-style sockets, id specifies the association to query.
+ * If the id field is set to the value '0' then the locally bound
+ * addresses are returned without regard to any particular
+ * association.
+ */
+ if (0 == id) {
+ bp = &sctp_sk(sk)->ep->base.bind_addr;
+ addr_lock = &sctp_sk(sk)->ep->base.addr_lock;
+ } else {
+ asoc = sctp_id2assoc(sk, id);
+ if (!asoc)
+ return -EINVAL;
+ bp = &asoc->base.bind_addr;
+ addr_lock = &asoc->base.addr_lock;
+ }
+
+ sctp_read_lock(addr_lock);
+
+ /* If the endpoint is bound to 0.0.0.0 or ::0, count the valid
+ * addresses from the global local address list.
+ */
+ if (sctp_list_single_entry(&bp->address_list)) {
+ addr = list_entry(bp->address_list.next,
+ struct sctp_sockaddr_entry, list);
+ if (sctp_is_any(&addr->a)) {
+ sctp_spin_lock_irqsave(&sctp_local_addr_lock, flags);
+ list_for_each(pos, &sctp_local_addr_list) {
+ addr = list_entry(pos,
+ struct sctp_sockaddr_entry,
+ list);
+ if ((PF_INET == sk->family) &&
+ (AF_INET6 == addr->a.sa.sa_family))
+ continue;
+ cnt++;
+ }
+ sctp_spin_unlock_irqrestore(&sctp_local_addr_lock,
+ flags);
+ } else {
+ cnt = 1;
+ }
+ goto done;
+ }
+
+ list_for_each(pos, &bp->address_list) {
+ cnt ++;
+ }
+
+done:
+ sctp_read_unlock(addr_lock);
+ return cnt;
+}
+
+/* Helper function that copies local addresses to user and returns the number
+ * of addresses copied.
+ */
+static int sctp_copy_laddrs_to_user(struct sock *sk, __u16 port, int max_addrs,
+ void __user *to)
+{
+ struct list_head *pos;
+ struct sctp_sockaddr_entry *addr;
+ unsigned long flags;
+ union sctp_addr temp;
+ int cnt = 0;
+ int addrlen;
+
+ sctp_spin_lock_irqsave(&sctp_local_addr_lock, flags);
+ list_for_each(pos, &sctp_local_addr_list) {
+ addr = list_entry(pos, struct sctp_sockaddr_entry, list);
+ if ((PF_INET == sk->family) &&
+ (AF_INET6 == addr->a.sa.sa_family))
+ continue;
+ memcpy(&temp, &addr->a, sizeof(temp));
+ sctp_get_pf_specific(sk->family)->addr_v4map(sctp_sk(sk),
+ &temp);
+ addrlen = sctp_get_af_specific(temp.sa.sa_family)->sockaddr_len; temp.v4.sin_port = htons(port);
+ if (copy_to_user(to, &temp, addrlen)) {
+ sctp_spin_unlock_irqrestore(&sctp_local_addr_lock,
+ flags);
+ return -EFAULT;
+ }
+ to += addrlen;
+ cnt ++;
+ if (cnt >= max_addrs) break;
+ }
+ sctp_spin_unlock_irqrestore(&sctp_local_addr_lock, flags);
+
+ return cnt;
+}
+
+static int sctp_getsockopt_local_addrs(struct sock *sk, int len,
+ char *optval, int *optlen)
+{
+ struct sctp_bind_addr *bp;
+ struct sctp_association *asoc;
+ struct list_head *pos;
+ int cnt = 0;
+ struct sctp_getaddrs getaddrs;
+ struct sctp_sockaddr_entry *addr;
+ void *to;
+ union sctp_addr temp;
+ struct sctp_opt *sp = sctp_sk(sk);
+ int addrlen;
+ rwlock_t *addr_lock;
+ int err = 0;
+
+ if (len != sizeof(struct sctp_getaddrs))
+ return -EINVAL;
+
+ if (copy_from_user(&getaddrs, optval, sizeof(struct sctp_getaddrs)))
+ return -EFAULT;
+
+ if (getaddrs.addr_num <= 0) return -EINVAL;
+ /*
+ * For UDP-style sockets, id specifies the association to query.
+ * If the id field is set to the value '0' then the locally bound
+ * addresses are returned without regard to any particular
+ * association.
+ */
+ if (0 == getaddrs.assoc_id) {
+ bp = &sctp_sk(sk)->ep->base.bind_addr;
+ addr_lock = &sctp_sk(sk)->ep->base.addr_lock;
+ } else {
+ asoc = sctp_id2assoc(sk, getaddrs.assoc_id);
+ if (!asoc)
+ return -EINVAL;
+ bp = &asoc->base.bind_addr;
+ addr_lock = &asoc->base.addr_lock;
+ }
+
+ to = (void *)getaddrs.addrs;
+
+ sctp_read_lock(addr_lock);
+
+ /* If the endpoint is bound to 0.0.0.0 or ::0, get the valid
+ * addresses from the global local address list.
+ */
+ if (sctp_list_single_entry(&bp->address_list)) {
+ addr = list_entry(bp->address_list.next,
+ struct sctp_sockaddr_entry, list);
+ if (sctp_is_any(&addr->a)) {
+ cnt = sctp_copy_laddrs_to_user(sk, bp->port,
+ getaddrs.addr_num, to);
+ if (cnt < 0) {
+ err = cnt;
+ goto unlock;
+ }
+ goto copy_getaddrs;
+ }
+ }
+
+ list_for_each(pos, &bp->address_list) {
+ addr = list_entry(pos, struct sctp_sockaddr_entry, list);
+ memcpy(&temp, &addr->a, sizeof(temp));
+ sctp_get_pf_specific(sk->family)->addr_v4map(sp, &temp);
+ addrlen = sctp_get_af_specific(temp.sa.sa_family)->sockaddr_len;
+ temp.v4.sin_port = htons(temp.v4.sin_port);
+ if (copy_to_user(to, &temp, addrlen)) {
+ err = -EFAULT;
+ goto unlock;
+ }
+ to += addrlen;
+ cnt ++;
+ if (cnt >= getaddrs.addr_num) break;
+ }
+
+copy_getaddrs:
+ getaddrs.addr_num = cnt;
+ if (copy_to_user(optval, &getaddrs, sizeof(struct sctp_getaddrs)))
+ err = -EFAULT;
+
+unlock:
+ sctp_read_unlock(addr_lock);
+ return err;
+}
+
+/* 7.1.10 Set Primary Address (SCTP_PRIMARY_ADDR)
+ *
+ * Requests that the local SCTP stack use the enclosed peer address as
+ * the association primary. The enclosed address must be one of the
+ * association peer's addresses.
+ */
+static int sctp_getsockopt_primary_addr(struct sock *sk, int len,
+ char *optval, int *optlen)
+{
+ struct sctp_prim prim;
+ struct sctp_association *asoc;
+ struct sctp_opt *sp = sctp_sk(sk);
+
+ if (len != sizeof(struct sctp_prim))
+ return -EINVAL;
+
+ if (copy_from_user(&prim, optval, sizeof(struct sctp_prim)))
+ return -EFAULT;
+
+ asoc = sctp_id2assoc(sk, prim.ssp_assoc_id);
+ if (!asoc)
+ return -EINVAL;
+
+ if (!asoc->peer.primary_path)
+ return -ENOTCONN;
+
+ asoc->peer.primary_path->ipaddr.v4.sin_port =
+ htons(asoc->peer.primary_path->ipaddr.v4.sin_port);
+ memcpy(&prim.ssp_addr, &asoc->peer.primary_path->ipaddr,
+ sizeof(union sctp_addr));
+ asoc->peer.primary_path->ipaddr.v4.sin_port =
+ ntohs(asoc->peer.primary_path->ipaddr.v4.sin_port);
+
+ sctp_get_pf_specific(sk->family)->addr_v4map(sp,
+ (union sctp_addr *)&prim.ssp_addr);
+
+ if (copy_to_user(optval, &prim, sizeof(struct sctp_prim)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * 7.1.11 Set Adaption Layer Indicator (SCTP_ADAPTION_LAYER)
+ *
+ * Requests that the local endpoint set the specified Adaption Layer
+ * Indication parameter for all future INIT and INIT-ACK exchanges.
+ */
+static int sctp_getsockopt_adaption_layer(struct sock *sk, int len,
+ char __user *optval, int __user *optlen)
+{
+ __u32 val;
+
+ if (len < sizeof(__u32))
+ return -EINVAL;
+
+ len = sizeof(__u32);
+ val = sctp_sk(sk)->adaption_ind;
+ if (put_user(len, optlen))
+ return -EFAULT;
+ if (copy_to_user(optval, &val, len))
+ return -EFAULT;
+ return 0;
+}
+
+/*
+ *
+ * 7.1.14 Set default send parameters (SCTP_DEFAULT_SEND_PARAM)
+ *
+ * Applications that wish to use the sendto() system call may wish to
+ * specify a default set of parameters that would normally be supplied
+ * through the inclusion of ancillary data. This socket option allows
+ * such an application to set the default sctp_sndrcvinfo structure.
+
+
+ * The application that wishes to use this socket option simply passes
+ * in to this call the sctp_sndrcvinfo structure defined in Section
+ * 5.2.2) The input parameters accepted by this call include
+ * sinfo_stream, sinfo_flags, sinfo_ppid, sinfo_context,
+ * sinfo_timetolive. The user must provide the sinfo_assoc_id field in
+ * to this call if the caller is using the UDP model.
+ *
+ * For getsockopt, it get the default sctp_sndrcvinfo structure.
+ */
+static int sctp_getsockopt_default_send_param(struct sock *sk,
+ int len, char *optval, int *optlen)
+{
+ struct sctp_sndrcvinfo info;
+ struct sctp_association *asoc;
+ struct sctp_opt *sp = sctp_sk(sk);
+
+ if (len != sizeof(struct sctp_sndrcvinfo))
+ return -EINVAL;
+ if (copy_from_user(&info, optval, sizeof(struct sctp_sndrcvinfo)))
+ return -EFAULT;
+
+ asoc = sctp_id2assoc(sk, info.sinfo_assoc_id);
+ if (!asoc && info.sinfo_assoc_id && sctp_style(sk, UDP))
+ return -EINVAL;
+
+ if (asoc) {
+ info.sinfo_stream = asoc->default_stream;
+ info.sinfo_flags = asoc->default_flags;
+ info.sinfo_ppid = asoc->default_ppid;
+ info.sinfo_context = asoc->default_context;
+ info.sinfo_timetolive = asoc->default_timetolive;
+ } else {
+ info.sinfo_stream = sp->default_stream;
+ info.sinfo_flags = sp->default_flags;
+ info.sinfo_ppid = sp->default_ppid;
+ info.sinfo_context = sp->default_context;
+ info.sinfo_timetolive = sp->default_timetolive;
+ }
+
+ if (copy_to_user(optval, &info, sizeof(struct sctp_sndrcvinfo)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ *
+ * 7.1.5 SCTP_NODELAY
+ *
+ * Turn on/off any Nagle-like algorithm. This means that packets are
+ * generally sent as soon as possible and no unnecessary delays are
+ * introduced, at the cost of more packets in the network. Expects an
+ * integer boolean flag.
+ */
+
+static int sctp_getsockopt_nodelay(struct sock *sk, int len,
+ char *optval, int *optlen)
+{
+ int val;
+
+ if (len < sizeof(int))
+ return -EINVAL;
+
+ len = sizeof(int);
+ val = (sctp_sk(sk)->nodelay == 1);
+ if (put_user(len, optlen))
+ return -EFAULT;
+ if (copy_to_user(optval, &val, len))
+ return -EFAULT;
+ return 0;
+}
+
+/*
+ *
+ * 7.1.1 SCTP_RTOINFO
+ *
+ * The protocol parameters used to initialize and bound retransmission
+ * timeout (RTO) are tunable. sctp_rtoinfo structure is used to access
+ * and modify these parameters.
+ * All parameters are time values, in milliseconds. A value of 0, when
+ * modifying the parameters, indicates that the current value should not
+ * be changed.
+ *
+ */
+static int sctp_getsockopt_rtoinfo(struct sock *sk, int len, char *optval,
+ int *optlen) {
+ struct sctp_rtoinfo rtoinfo;
+ struct sctp_association *asoc;
+
+ if (len != sizeof (struct sctp_rtoinfo))
+ return -EINVAL;
+
+ if (copy_from_user(&rtoinfo, optval, sizeof (struct sctp_rtoinfo)))
+ return -EFAULT;
+
+ asoc = sctp_id2assoc(sk, rtoinfo.srto_assoc_id);
+
+ if (!asoc && rtoinfo.srto_assoc_id && sctp_style(sk, UDP))
+ return -EINVAL;
+
+ /* Values corresponding to the specific association. */
+ if (asoc) {
+ rtoinfo.srto_initial = JIFFIES_TO_MSECS(asoc->rto_initial);
+ rtoinfo.srto_max = JIFFIES_TO_MSECS(asoc->rto_max);
+ rtoinfo.srto_min = JIFFIES_TO_MSECS(asoc->rto_min);
+ } else {
+ /* Values corresponding to the endpoint. */
+ struct sctp_opt *sp = sctp_sk(sk);
+
+ rtoinfo.srto_initial = sp->rtoinfo.srto_initial;
+ rtoinfo.srto_max = sp->rtoinfo.srto_max;
+ rtoinfo.srto_min = sp->rtoinfo.srto_min;
+ }
+
+ if (put_user(len, optlen))
+ return -EFAULT;
+
+ if (copy_to_user(optval, &rtoinfo, len))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ *
+ * 7.1.2 SCTP_ASSOCINFO
+ *
+ * This option is used to tune the the maximum retransmission attempts
+ * of the association.
+ * Returns an error if the new association retransmission value is
+ * greater than the sum of the retransmission value of the peer.
+ * See [SCTP] for more information.
+ *
+ */
+static int sctp_getsockopt_associnfo(struct sock *sk, int len, char *optval,
+ int *optlen)
+{
+
+ struct sctp_assocparams assocparams;
+ struct sctp_association *asoc;
+ struct list_head *pos;
+ int cnt = 0;
+
+ if (len != sizeof (struct sctp_assocparams))
+ return -EINVAL;
+
+ if (copy_from_user(&assocparams, optval,
+ sizeof (struct sctp_assocparams)))
+ return -EFAULT;
+
+ asoc = sctp_id2assoc(sk, assocparams.sasoc_assoc_id);
+
+ if (!asoc && assocparams.sasoc_assoc_id && sctp_style(sk, UDP))
+ return -EINVAL;
+
+ /* Values correspoinding to the specific association */
+ if (assocparams.sasoc_assoc_id != 0) {
+ assocparams.sasoc_asocmaxrxt = asoc->max_retrans;
+ assocparams.sasoc_peer_rwnd = asoc->peer.rwnd;
+ assocparams.sasoc_local_rwnd = asoc->a_rwnd;
+ assocparams.sasoc_cookie_life = (asoc->cookie_life.tv_sec
+ * 1000) +
+ (asoc->cookie_life.tv_usec
+ / 1000);
+
+ list_for_each(pos, &asoc->peer.transport_addr_list) {
+ cnt ++;
+ }
+
+ assocparams.sasoc_number_peer_destinations = cnt;
+ } else {
+ /* Values corresponding to the endpoint */
+ struct sctp_opt *sp = sctp_sk(sk);
+
+ assocparams.sasoc_asocmaxrxt = sp->assocparams.sasoc_asocmaxrxt;
+ assocparams.sasoc_peer_rwnd = sp->assocparams.sasoc_peer_rwnd;
+ assocparams.sasoc_local_rwnd = sp->assocparams.sasoc_local_rwnd;
+ assocparams.sasoc_cookie_life =
+ sp->assocparams.sasoc_cookie_life;
+ assocparams.sasoc_number_peer_destinations =
+ sp->assocparams.
+ sasoc_number_peer_destinations;
+ }
+
+ if (put_user(len, optlen))
+ return -EFAULT;
+
+ if (copy_to_user(optval, &assocparams, len))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * 7.1.16 Set/clear IPv4 mapped addresses (SCTP_I_WANT_MAPPED_V4_ADDR)
+ *
+ * This socket option is a boolean flag which turns on or off mapped V4
+ * addresses. If this option is turned on and the socket is type
+ * PF_INET6, then IPv4 addresses will be mapped to V6 representation.
+ * If this option is turned off, then no mapping will be done of V4
+ * addresses and a user will receive both PF_INET6 and PF_INET type
+ * addresses on the socket.
+ */
+static int sctp_getsockopt_mappedv4(struct sock *sk, int len,
+ char *optval, int *optlen)
+{
+ int val;
+ struct sctp_opt *sp = sctp_sk(sk);
+
+ if (len < sizeof(int))
+ return -EINVAL;
+
+ len = sizeof(int);
+ val = sp->v4mapped;
+ if (put_user(len, optlen))
+ return -EFAULT;
+ if (copy_to_user(optval, &val, len))
+ return -EFAULT;
+
+ return 0;
+}
+
+/*
+ * 7.1.17 Set the maximum fragrmentation size (SCTP_MAXSEG)
+ *
+ * This socket option specifies the maximum size to put in any outgoing
+ * SCTP chunk. If a message is larger than this size it will be
+ * fragmented by SCTP into the specified size. Note that the underlying
+ * SCTP implementation may fragment into smaller sized chunks when the
+ * PMTU of the underlying association is smaller than the value set by
+ * the user.
+ */
+static int sctp_getsockopt_maxseg(struct sock *sk, int len,
+ char *optval, int *optlen)
+{
+ int val;
+
+ if (len < sizeof(int))
+ return -EINVAL;
+
+ len = sizeof(int);
+
+ val = sctp_sk(sk)->user_frag;
+ if (put_user(len, optlen))
+ return -EFAULT;
+ if (copy_to_user(optval, &val, len))
+ return -EFAULT;
+
+ return 0;
+}
+
+SCTP_STATIC int sctp_getsockopt(struct sock *sk, int level, int optname,
+ char *optval, int *optlen)
+{
+ int retval = 0;
+ int len;
+
+ SCTP_DEBUG_PRINTK("sctp_getsockopt(sk: %p, ...)\n", sk);
+
+ /* I can hardly begin to describe how wrong this is. This is
+ * so broken as to be worse than useless. The API draft
+ * REALLY is NOT helpful here... I am not convinced that the
+ * semantics of getsockopt() with a level OTHER THAN SOL_SCTP
+ * are at all well-founded.
+ */
+ if (level != SOL_SCTP) {
+ struct sctp_af *af = sctp_sk(sk)->pf->af;
+
+ retval = af->getsockopt(sk, level, optname, optval, optlen);
+ return retval;
+ }
+
+ if (get_user(len, optlen))
+ return -EFAULT;
+
+ sctp_lock_sock(sk);
+
+ switch (optname) {
+ case SCTP_STATUS:
+ retval = sctp_getsockopt_sctp_status(sk, len, optval, optlen);
+ break;
+ case SCTP_DISABLE_FRAGMENTS:
+ retval = sctp_getsockopt_disable_fragments(sk, len, optval,
+ optlen);
+ break;
+ case SCTP_EVENTS:
+ retval = sctp_getsockopt_events(sk, len, optval, optlen);
+ break;
+ case SCTP_AUTOCLOSE:
+ retval = sctp_getsockopt_autoclose(sk, len, optval, optlen);
+ break;
+ case SCTP_SOCKOPT_PEELOFF:
+ retval = sctp_getsockopt_peeloff(sk, len, optval, optlen);
+ break;
+ case SCTP_PEER_ADDR_PARAMS:
+ retval = sctp_getsockopt_peer_addr_params(sk, len, optval,
+ optlen);
+ break;
+ case SCTP_INITMSG:
+ retval = sctp_getsockopt_initmsg(sk, len, optval, optlen);
+ break;
+ case SCTP_GET_PEER_ADDRS_NUM:
+ retval = sctp_getsockopt_peer_addrs_num(sk, len, optval,
+ optlen);
+ break;
+ case SCTP_GET_LOCAL_ADDRS_NUM:
+ retval = sctp_getsockopt_local_addrs_num(sk, len, optval,
+ optlen);
+ break;
+ case SCTP_GET_PEER_ADDRS:
+ retval = sctp_getsockopt_peer_addrs(sk, len, optval,
+ optlen);
+ break;
+ case SCTP_GET_LOCAL_ADDRS:
+ retval = sctp_getsockopt_local_addrs(sk, len, optval,
+ optlen);
+ break;
+ case SCTP_DEFAULT_SEND_PARAM:
+ retval = sctp_getsockopt_default_send_param(sk, len,
+ optval, optlen);
+ break;
+ case SCTP_PRIMARY_ADDR:
+ retval = sctp_getsockopt_primary_addr(sk, len, optval, optlen);
+ break;
+ case SCTP_NODELAY:
+ retval = sctp_getsockopt_nodelay(sk, len, optval, optlen);
+ break;
+ case SCTP_RTOINFO:
+ retval = sctp_getsockopt_rtoinfo(sk, len, optval, optlen);
+ break;
+ case SCTP_ASSOCINFO:
+ retval = sctp_getsockopt_associnfo(sk, len, optval, optlen);
+ break;
+ case SCTP_I_WANT_MAPPED_V4_ADDR:
+ retval = sctp_getsockopt_mappedv4(sk, len, optval, optlen);
+ break;
+ case SCTP_MAXSEG:
+ retval = sctp_getsockopt_maxseg(sk, len, optval, optlen);
+ break;
+ case SCTP_GET_PEER_ADDR_INFO:
+ retval = sctp_getsockopt_peer_addr_info(sk, len, optval,
+ optlen);
+ break;
+ case SCTP_ADAPTION_LAYER:
+ retval = sctp_getsockopt_adaption_layer(sk, len, optval,
+ optlen);
+ break;
+ default:
+ retval = -ENOPROTOOPT;
+ break;
+ };
+
+ sctp_release_sock(sk);
+ return retval;
+}
+
+static void sctp_hash(struct sock *sk)
+{
+ /* STUB */
+}
+
+static void sctp_unhash(struct sock *sk)
+{
+ /* STUB */
+}
+
+/* Check if port is acceptable. Possibly find first available port.
+ *
+ * The port hash table (contained in the 'global' SCTP protocol storage
+ * returned by struct sctp_protocol *sctp_get_protocol()). The hash
+ * table is an array of 4096 lists (sctp_bind_hashbucket). Each
+ * list (the list number is the port number hashed out, so as you
+ * would expect from a hash function, all the ports in a given list have
+ * such a number that hashes out to the same list number; you were
+ * expecting that, right?); so each list has a set of ports, with a
+ * link to the socket (struct sock) that uses it, the port number and
+ * a fastreuse flag (FIXME: NPI ipg).
+ */
+static struct sctp_bind_bucket *sctp_bucket_create(
+ struct sctp_bind_hashbucket *head, unsigned short snum);
+
+static long sctp_get_port_local(struct sock *sk, union sctp_addr *addr)
+{
+ struct sctp_bind_hashbucket *head; /* hash list */
+ struct sctp_bind_bucket *pp; /* hash list port iterator */
+ unsigned short snum;
+ int ret;
+
+ /* NOTE: Remember to put this back to net order. */
+ addr->v4.sin_port = ntohs(addr->v4.sin_port);
+ snum = addr->v4.sin_port;
+
+ SCTP_DEBUG_PRINTK("sctp_get_port() begins, snum=%d\n", snum);
+ sctp_local_bh_disable();
+
+ if (snum == 0) {
+ /* Search for an available port.
+ *
+ * 'sctp_port_rover' was the last port assigned, so
+ * we start to search from 'sctp_port_rover +
+ * 1'. What we do is first check if port 'rover' is
+ * already in the hash table; if not, we use that; if
+ * it is, we try next.
+ */
+ int low = sysctl_local_port_range[0];
+ int high = sysctl_local_port_range[1];
+ int remaining = (high - low) + 1;
+ int rover;
+ int index;
+
+ sctp_spin_lock(&sctp_port_alloc_lock);
+ rover = sctp_port_rover;
+ do {
+ rover++;
+ if ((rover < low) || (rover > high))
+ rover = low;
+ index = sctp_phashfn(rover);
+ head = &sctp_port_hashtable[index];
+ sctp_spin_lock(&head->lock);
+ for (pp = head->chain; pp; pp = pp->next)
+ if (pp->port == rover)
+ goto next;
+ break;
+ next:
+ sctp_spin_unlock(&head->lock);
+ } while (--remaining > 0);
+ sctp_port_rover = rover;
+ sctp_spin_unlock(&sctp_port_alloc_lock);
+
+ /* Exhausted local port range during search? */
+ ret = 1;
+ if (remaining <= 0)
+ goto fail;
+
+ /* OK, here is the one we will use. HEAD (the port
+ * hash table list entry) is non-NULL and we hold it's
+ * mutex.
+ */
+ snum = rover;
+ pp = NULL;
+ } else {
+ /* We are given an specific port number; we verify
+ * that it is not being used. If it is used, we will
+ * exahust the search in the hash list corresponding
+ * to the port number (snum) - we detect that with the
+ * port iterator, pp being NULL.
+ */
+ head = &sctp_port_hashtable[sctp_phashfn(snum)];
+ sctp_spin_lock(&head->lock);
+ for (pp = head->chain; pp; pp = pp->next) {
+ if (pp->port == snum)
+ break;
+ }
+ }
+
+
+ if (pp && pp->sk) {
+ /* We had a port hash table hit - there is an
+ * available port (pp != NULL) and it is being
+ * used by other socket (pp->sk != NULL); that other
+ * socket is going to be sk2.
+ */
+ int reuse = sk->reuse;
+ struct sock *sk2 = pp->sk;
+
+ SCTP_DEBUG_PRINTK("sctp_get_port() found a "
+ "possible match\n");
+ if (pp->fastreuse != 0 && sk->reuse != 0)
+ goto success;
+
+ /* Run through the list of sockets bound to the port
+ * (pp->port) [via the pointers bind_next and
+ * bind_pprev in the struct sock *sk2 (pp->sk)]. On each one,
+ * we get the endpoint they describe and run through
+ * the endpoint's list of IP (v4 or v6) addresses,
+ * comparing each of the addresses with the address of
+ * the socket sk. If we find a match, then that means
+ * that this port/socket (sk) combination are already
+ * in an endpoint.
+ */
+ for ( ; sk2 != NULL; sk2 = sk2->bind_next) {
+ struct sctp_endpoint *ep2;
+ ep2 = sctp_sk(sk2)->ep;
+
+ if (reuse && sk2->reuse)
+ continue;
+
+ if (sctp_bind_addr_match(&ep2->base.bind_addr, addr,
+ sctp_sk(sk)))
+ goto found;
+ }
+
+ found:
+ /* If we found a conflict, fail. */
+ if (sk2 != NULL) {
+ ret = (long) sk2;
+ goto fail_unlock;
+ }
+ SCTP_DEBUG_PRINTK("sctp_get_port(): Found a match\n");
+ }
+
+ /* If there was a hash table miss, create a new port. */
+ ret = 1;
+
+ if (!pp && !(pp = sctp_bucket_create(head, snum)))
+ goto fail_unlock;
+
+ /* In either case (hit or miss), make sure fastreuse is 1 only
+ * if sk->reuse is too (that is, if the caller requested
+ * SO_REUSEADDR on this socket -sk-).
+ */
+ if (!pp->sk)
+ pp->fastreuse = sk->reuse ? 1 : 0;
+ else if (pp->fastreuse && sk->reuse == 0)
+ pp->fastreuse = 0;
+
+ /* We are set, so fill up all the data in the hash table
+ * entry, tie the socket list information with the rest of the
+ * sockets FIXME: Blurry, NPI (ipg).
+ */
+success:
+ (sk)->num = snum;
+ if (sk->prev == NULL) {
+ if ((sk->bind_next = pp->sk) != NULL)
+ pp->sk->bind_pprev = &sk->bind_next;
+ pp->sk = sk;
+ sk->bind_pprev = &pp->sk;
+ sk->prev = (struct sock *) pp;
+ }
+ ret = 0;
+
+fail_unlock:
+ sctp_spin_unlock(&head->lock);
+
+fail:
+ sctp_local_bh_enable();
+
+ SCTP_DEBUG_PRINTK("sctp_get_port() ends, ret=%d\n", ret);
+ addr->v4.sin_port = htons(addr->v4.sin_port);
+ return ret;
+}
+
+/* Assign a 'snum' port to the socket. If snum == 0, an ephemeral
+ * port is requested.
+ */
+static int sctp_get_port(struct sock *sk, unsigned short snum)
+{
+ long ret;
+ union sctp_addr addr;
+ struct sctp_af *af = sctp_sk(sk)->pf->af;
+
+ /* Set up a dummy address struct from the sk. */
+ af->from_sk(&addr, sk);
+ addr.v4.sin_port = htons(snum);
+
+ /* Note: sk->sk_num gets filled in if ephemeral port request. */
+ ret = sctp_get_port_local(sk, &addr);
+
+ return (ret ? 1 : 0);
+}
+
+/*
+ * 3.1.3 listen() - UDP Style Syntax
+ *
+ * By default, new associations are not accepted for UDP style sockets.
+ * An application uses listen() to mark a socket as being able to
+ * accept new associations.
+ */
+SCTP_STATIC int sctp_seqpacket_listen(struct sock *sk, int backlog)
+{
+ struct sctp_opt *sp = sctp_sk(sk);
+ struct sctp_endpoint *ep = sp->ep;
+
+ /* Only UDP style sockets that are not peeled off are allowed to
+ * listen().
+ */
+ if (!sctp_style(sk, UDP))
+ return -EINVAL;
+
+ /* If backlog is zero, disable listening. */
+ if (!backlog) {
+ if (sctp_sstate(sk, CLOSED))
+ return 0;
+
+ sctp_unhash_endpoint(ep);
+ sk->state = SCTP_SS_CLOSED;
+ }
+
+ /* Return if we are already listening. */
+ if (sctp_sstate(sk, LISTENING))
+ return 0;
+
+ /*
+ * If a bind() or sctp_bindx() is not called prior to a listen()
+ * call that allows new associations to be accepted, the system
+ * picks an ephemeral port and will choose an address set equivalent
+ * to binding with a wildcard address.
+ *
+ * This is not currently spelled out in the SCTP sockets
+ * extensions draft, but follows the practice as seen in TCP
+ * sockets.
+ */
+ if (!ep->base.bind_addr.port) {
+ if (sctp_autobind(sk))
+ return -EAGAIN;
+ }
+ sk->state = SCTP_SS_LISTENING;
+ sctp_hash_endpoint(ep);
+ return 0;
+}
+
+/*
+ * 4.1.3 listen() - TCP Style Syntax
+ *
+ * Applications uses listen() to ready the SCTP endpoint for accepting
+ * inbound associations.
+ */
+SCTP_STATIC int sctp_stream_listen(struct sock *sk, int backlog)
+{
+ struct sctp_opt *sp = sctp_sk(sk);
+ struct sctp_endpoint *ep = sp->ep;
+
+ /* If backlog is zero, disable listening. */
+ if (!backlog) {
+ if (sctp_sstate(sk, CLOSED))
+ return 0;
+
+ sctp_unhash_endpoint(ep);
+ sk->state = SCTP_SS_CLOSED;
+ }
+
+ if (sctp_sstate(sk, LISTENING))
+ return 0;
+
+ /*
+ * If a bind() or sctp_bindx() is not called prior to a listen()
+ * call that allows new associations to be accepted, the system
+ * picks an ephemeral port and will choose an address set equivalent
+ * to binding with a wildcard address.
+ *
+ * This is not currently spelled out in the SCTP sockets
+ * extensions draft, but follows the practice as seen in TCP
+ * sockets.
+ */
+ if (!ep->base.bind_addr.port) {
+ if (sctp_autobind(sk))
+ return -EAGAIN;
+ }
+ sk->state = SCTP_SS_LISTENING;
+ sk->max_ack_backlog = backlog;
+ sctp_hash_endpoint(ep);
+ return 0;
+}
+
+/*
+ * Move a socket to LISTENING state.
+ */
+int sctp_inet_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+ struct crypto_tfm *tfm=NULL;
+ int err = -EINVAL;
+
+ if (unlikely(backlog < 0))
+ goto out;
+
+ sctp_lock_sock(sk);
+
+ if (sock->state != SS_UNCONNECTED)
+ goto out;
+
+ /* Allocate HMAC for generating cookie. */
+ if (sctp_hmac_alg) {
+ tfm = sctp_crypto_alloc_tfm(sctp_hmac_alg, 0);
+ if (!tfm) {
+ err = -ENOSYS;
+ goto out;
+ }
+ }
+
+ switch (sock->type) {
+ case SOCK_SEQPACKET:
+ err = sctp_seqpacket_listen(sk, backlog);
+ break;
+ case SOCK_STREAM:
+ err = sctp_stream_listen(sk, backlog);
+ break;
+ default:
+ break;
+ };
+ if (err)
+ goto cleanup;
+
+ /* Store away the transform reference. */
+ sctp_sk(sk)->hmac = tfm;
+out:
+ sctp_release_sock(sk);
+ return err;
+cleanup:
+ if (tfm)
+ sctp_crypto_free_tfm(tfm);
+ goto out;
+}
+
+/*
+ * This function is done by modeling the current datagram_poll() and the
+ * tcp_poll(). Note that, based on these implementations, we don't
+ * lock the socket in this function, even though it seems that,
+ * ideally, locking or some other mechanisms can be used to ensure
+ * the integrity of the counters (sndbuf and wmem_queued) used
+ * in this place. We assume that we don't need locks either until proven
+ * otherwise.
+ *
+ * Another thing to note is that we include the Async I/O support
+ * here, again, by modeling the current TCP/UDP code. We don't have
+ * a good way to test with it yet.
+ */
+unsigned int sctp_poll(struct file *file, struct socket *sock, poll_table *wait)
+{
+ struct sock *sk = sock->sk;
+ struct sctp_opt *sp = sctp_sk(sk);
+ unsigned int mask;
+
+ poll_wait(file, sk->sleep, wait);
+
+ /* A TCP-style listening socket becomes readable when the accept queue
+ * is not empty.
+ */
+ if (sctp_style(sk, TCP) && sctp_sstate(sk, LISTENING))
+ return (!list_empty(&sp->ep->asocs)) ?
+ (POLLIN | POLLRDNORM) : 0;
+
+ mask = 0;
+
+ /* Is there any exceptional events? */
+ if (sk->err || !skb_queue_empty(&sk->error_queue))
+ mask |= POLLERR;
+ if (sk->shutdown == SHUTDOWN_MASK)
+ mask |= POLLHUP;
+
+ /* Is it readable? Reconsider this code with TCP-style support. */
+ if (!skb_queue_empty(&sk->receive_queue) ||
+ (sk->shutdown & RCV_SHUTDOWN))
+ mask |= POLLIN | POLLRDNORM;
+
+ /* The association is either gone or not ready. */
+ if (!sctp_style(sk, UDP) && sctp_sstate(sk, CLOSED))
+ return mask;
+
+ /* Is it writable? */
+ if (sctp_writeable(sk)) {
+ mask |= POLLOUT | POLLWRNORM;
+ } else {
+ set_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
+ /*
+ * Since the socket is not locked, the buffer
+ * might be made available after the writeable check and
+ * before the bit is set. This could cause a lost I/O
+ * signal. tcp_poll() has a race breaker for this race
+ * condition. Based on their implementation, we put
+ * in the following code to cover it as well.
+ */
+ if (sctp_writeable(sk))
+ mask |= POLLOUT | POLLWRNORM;
+ }
+ return mask;
+}
+
+/********************************************************************
+ * 2nd Level Abstractions
+ ********************************************************************/
+
+static struct sctp_bind_bucket *sctp_bucket_create(
+ struct sctp_bind_hashbucket *head, unsigned short snum)
+{
+ struct sctp_bind_bucket *pp;
+
+ SCTP_DEBUG_PRINTK( "sctp_bucket_create() begins, snum=%d\n", snum);
+ pp = kmalloc(sizeof(struct sctp_bind_bucket), GFP_ATOMIC);
+ if (pp) {
+ pp->port = snum;
+ pp->fastreuse = 0;
+ pp->sk = NULL;
+ if ((pp->next = head->chain) != NULL)
+ pp->next->pprev = &pp->next;
+ head->chain = pp;
+ pp->pprev = &head->chain;
+ }
+ SCTP_DEBUG_PRINTK("sctp_bucket_create() ends, pp=%p\n", pp);
+ return pp;
+}
+
+/* Release this socket's reference to a local port. */
+static __inline__ void __sctp_put_port(struct sock *sk)
+{
+ struct sctp_bind_hashbucket *head =
+ &sctp_port_hashtable[sctp_phashfn((sk)->num)];
+ struct sctp_bind_bucket *pp;
+
+ sctp_spin_lock(&head->lock);
+ pp = (struct sctp_bind_bucket *) sk->prev;
+ if (sk->bind_next)
+ sk->bind_next->bind_pprev = sk->bind_pprev;
+ *(sk->bind_pprev) = sk->bind_next;
+ sk->prev = NULL;
+ (sk)->num = 0;
+ if (pp->sk) {
+ if (pp->next)
+ pp->next->pprev = pp->pprev;
+ *(pp->pprev) = pp->next;
+ kfree(pp);
+ }
+ sctp_spin_unlock(&head->lock);
+}
+
+void sctp_put_port(struct sock *sk)
+{
+ sctp_local_bh_disable();
+ __sctp_put_port(sk);
+ sctp_local_bh_enable();
+}
+
+/*
+ * The system picks an ephemeral port and choose an address set equivalent
+ * to binding with a wildcard address.
+ * One of those addresses will be the primary address for the association.
+ * This automatically enables the multihoming capability of SCTP.
+ */
+static int sctp_autobind(struct sock *sk)
+{
+ union sctp_addr autoaddr;
+ struct sctp_af *af;
+ unsigned short port;
+
+ /* Initialize a local sockaddr structure to INADDR_ANY. */
+ af = sctp_sk(sk)->pf->af;
+
+ port = htons(sk->num);
+ af->inaddr_any(&autoaddr, port);
+
+ return sctp_do_bind(sk, &autoaddr, af->sockaddr_len);
+}
+
+/* Parse out IPPROTO_SCTP CMSG headers. Perform only minimal validation.
+ *
+ * From RFC 2292
+ * 4.2 The cmsghdr Structure *
+ *
+ * When ancillary data is sent or received, any number of ancillary data
+ * objects can be specified by the msg_control and msg_controllen members of
+ * the msghdr structure, because each object is preceded by
+ * a cmsghdr structure defining the object's length (the cmsg_len member).
+ * Historically Berkeley-derived implementations have passed only one object
+ * at a time, but this API allows multiple objects to be
+ * passed in a single call to sendmsg() or recvmsg(). The following example
+ * shows two ancillary data objects in a control buffer.
+ *
+ * |<--------------------------- msg_controllen -------------------------->|
+ * | |
+ *
+ * |<----- ancillary data object ----->|<----- ancillary data object ----->|
+ *
+ * |<---------- CMSG_SPACE() --------->|<---------- CMSG_SPACE() --------->|
+ * | | |
+ *
+ * |<---------- cmsg_len ---------->| |<--------- cmsg_len ----------->| |
+ *
+ * |<--------- CMSG_LEN() --------->| |<-------- CMSG_LEN() ---------->| |
+ * | | | | |
+ *
+ * +-----+-----+-----+--+-----------+--+-----+-----+-----+--+-----------+--+
+ * |cmsg_|cmsg_|cmsg_|XX| |XX|cmsg_|cmsg_|cmsg_|XX| |XX|
+ *
+ * |len |level|type |XX|cmsg_data[]|XX|len |level|type |XX|cmsg_data[]|XX|
+ *
+ * +-----+-----+-----+--+-----------+--+-----+-----+-----+--+-----------+--+
+ * ^
+ * |
+ *
+ * msg_control
+ * points here
+ */
+SCTP_STATIC int sctp_msghdr_parse(const struct msghdr *msg,
+ sctp_cmsgs_t *cmsgs)
+{
+ struct cmsghdr *cmsg;
+
+ for (cmsg = CMSG_FIRSTHDR(msg);
+ cmsg != NULL;
+ cmsg = CMSG_NXTHDR((struct msghdr*)msg, cmsg)) {
+ if (!CMSG_OK(msg, cmsg))
+ return -EINVAL;
+
+ /* Should we parse this header or ignore? */
+ if (cmsg->cmsg_level != IPPROTO_SCTP)
+ continue;
+
+ /* Strictly check lengths following example in SCM code. */
+ switch (cmsg->cmsg_type) {
+ case SCTP_INIT:
+ /* SCTP Socket API Extension
+ * 5.2.1 SCTP Initiation Structure (SCTP_INIT)
+ *
+ * This cmsghdr structure provides information for
+ * initializing new SCTP associations with sendmsg().
+ * The SCTP_INITMSG socket option uses this same data
+ * structure. This structure is not used for
+ * recvmsg().
+ *
+ * cmsg_level cmsg_type cmsg_data[]
+ * ------------ ------------ ----------------------
+ * IPPROTO_SCTP SCTP_INIT struct sctp_initmsg
+ */
+ if (cmsg->cmsg_len !=
+ CMSG_LEN(sizeof(struct sctp_initmsg)))
+ return -EINVAL;
+ cmsgs->init = (struct sctp_initmsg *)CMSG_DATA(cmsg);
+ break;
+
+ case SCTP_SNDRCV:
+ /* SCTP Socket API Extension
+ * 5.2.2 SCTP Header Information Structure(SCTP_SNDRCV)
+ *
+ * This cmsghdr structure specifies SCTP options for
+ * sendmsg() and describes SCTP header information
+ * about a received message through recvmsg().
+ *
+ * cmsg_level cmsg_type cmsg_data[]
+ * ------------ ------------ ----------------------
+ * IPPROTO_SCTP SCTP_SNDRCV struct sctp_sndrcvinfo
+ */
+ if (cmsg->cmsg_len !=
+ CMSG_LEN(sizeof(struct sctp_sndrcvinfo)))
+ return -EINVAL;
+
+ cmsgs->info =
+ (struct sctp_sndrcvinfo *)CMSG_DATA(cmsg);
+
+ /* Minimally, validate the sinfo_flags. */
+ if (cmsgs->info->sinfo_flags &
+ ~(MSG_UNORDERED | MSG_ADDR_OVER |
+ MSG_ABORT | MSG_EOF))
+ return -EINVAL;
+ break;
+
+ default:
+ return -EINVAL;
+ };
+ }
+ return 0;
+}
+
+/*
+ * Wait for a packet..
+ * Note: This function is the same function as in core/datagram.c
+ * with a few modifications to make lksctp work.
+ */
+static int sctp_wait_for_packet(struct sock * sk, int *err, long *timeo_p)
+{
+ int error;
+ DECLARE_WAITQUEUE(wait, current);
+
+ add_wait_queue_exclusive(sk->sleep, &wait);
+ __set_current_state(TASK_INTERRUPTIBLE);
+
+ /* Socket errors? */
+ error = sock_error(sk);
+ if (error)
+ goto out;
+
+ if (!skb_queue_empty(&sk->receive_queue))
+ goto ready;
+
+ /* Socket shut down? */
+ if (sk->shutdown & RCV_SHUTDOWN)
+ goto out;
+
+ /* Sequenced packets can come disconnected. If so we report the
+ * problem.
+ */
+ error = -ENOTCONN;
+
+ /* Is there a good reason to think that we may receive some data? */
+ if (list_empty(&sctp_sk(sk)->ep->asocs) && !sctp_sstate(sk, LISTENING))
+ goto out;
+
+ /* Handle signals. */
+ if (signal_pending(current))
+ goto interrupted;
+
+ /* Let another process have a go. Since we are going to sleep
+ * anyway. Note: This may cause odd behaviors if the message
+ * does not fit in the user's buffer, but this seems to be the
+ * only way to honor MSG_DONTWAIT realistically.
+ */
+ sctp_release_sock(sk);
+ *timeo_p = schedule_timeout(*timeo_p);
+ sctp_lock_sock(sk);
+
+ready:
+ remove_wait_queue(sk->sleep, &wait);
+ __set_current_state(TASK_RUNNING);
+ return 0;
+
+interrupted:
+ error = sock_intr_errno(*timeo_p);
+
+out:
+ remove_wait_queue(sk->sleep, &wait);
+ __set_current_state(TASK_RUNNING);
+ *err = error;
+ return error;
+}
+
+/* Receive a datagram.
+ * Note: This is pretty much the same routine as in core/datagram.c
+ * with a few changes to make lksctp work.
+ */
+static struct sk_buff *sctp_skb_recv_datagram(struct sock *sk, int flags,
+ int noblock, int *err)
+{
+ int error;
+ struct sk_buff *skb;
+ long timeo;
+
+ /* Caller is allowed not to check sk->err before calling. */
+ error = sock_error(sk);
+ if (error)
+ goto no_packet;
+
+ timeo = sock_rcvtimeo(sk, noblock);
+
+ SCTP_DEBUG_PRINTK("Timeout: timeo: %ld, MAX: %ld.\n",
+ timeo, MAX_SCHEDULE_TIMEOUT);
+
+ do {
+ /* Again only user level code calls this function,
+ * so nothing interrupt level
+ * will suddenly eat the receive_queue.
+ *
+ * Look at current nfs client by the way...
+ * However, this function was corrent in any case. 8)
+ */
+ if (flags & MSG_PEEK) {
+ unsigned long cpu_flags;
+
+ sctp_spin_lock_irqsave(&sk->receive_queue.lock,
+ cpu_flags);
+ skb = skb_peek(&sk->receive_queue);
+ if (skb)
+ atomic_inc(&skb->users);
+ sctp_spin_unlock_irqrestore(&sk->receive_queue.lock,
+ cpu_flags);
+ } else {
+ skb = skb_dequeue(&sk->receive_queue);
+ }
+
+ if (skb)
+ return skb;
+
+ if (sk->shutdown & RCV_SHUTDOWN)
+ break;
+
+ /* User doesn't want to wait. */
+ error = -EAGAIN;
+ if (!timeo)
+ goto no_packet;
+ } while (sctp_wait_for_packet(sk, err, &timeo) == 0);
+
+ return NULL;
+
+no_packet:
+ *err = error;
+ return NULL;
+}
+
+/* If sndbuf has changed, wake up per association sndbuf waiters. */
+static void __sctp_write_space(struct sctp_association *asoc)
+{
+ struct sock *sk = asoc->base.sk;
+ struct socket *sock = sk->socket;
+
+ if ((sctp_wspace(asoc) > 0) && sock) {
+ if (waitqueue_active(&asoc->wait))
+ wake_up_interruptible(&asoc->wait);
+
+ if (sctp_writeable(sk)) {
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible(sk->sleep);
+
+ /* Note that we try to include the Async I/O support
+ * here by modeling from the current TCP/UDP code.
+ * We have not tested with it yet.
+ */
+ if (sock->fasync_list &&
+ !(sk->shutdown & SEND_SHUTDOWN))
+ sock_wake_async(sock, 2, POLL_OUT);
+ }
+ }
+}
+
+/* Do accounting for the sndbuf space.
+ * Decrement the used sndbuf space of the corresponding association by the
+ * data size which was just transmitted(freed).
+ */
+static void sctp_wfree(struct sk_buff *skb)
+{
+ struct sctp_association *asoc;
+ struct sctp_chunk *chunk;
+ struct sock *sk;
+
+ /* Get the saved chunk pointer. */
+ chunk = *((struct sctp_chunk **)(skb->cb));
+ asoc = chunk->asoc;
+ sk = asoc->base.sk;
+ asoc->sndbuf_used -= SCTP_DATA_SNDSIZE(chunk);
+ sk->wmem_queued -= SCTP_DATA_SNDSIZE(chunk);
+ __sctp_write_space(asoc);
+
+ sctp_association_put(asoc);
+}
+
+/* Helper function to wait for space in the sndbuf. */
+static int sctp_wait_for_sndbuf(struct sctp_association *asoc, long *timeo_p,
+ size_t msg_len)
+{
+ struct sock *sk = asoc->base.sk;
+ int err = 0;
+ long current_timeo = *timeo_p;
+ DECLARE_WAITQUEUE(wait, current);
+
+ SCTP_DEBUG_PRINTK("wait_for_sndbuf: asoc=%p, timeo=%ld, msg_len=%u\n",
+ asoc, (long)(*timeo_p), msg_len);
+
+ /* Increment the association's refcnt. */
+ sctp_association_hold(asoc);
+
+ /* Wait on the association specific sndbuf space. */
+ add_wait_queue_exclusive(&asoc->wait, &wait);
+
+ /* Wait on the association specific sndbuf space. */
+ for (;;) {
+ __set_current_state(TASK_INTERRUPTIBLE);
+ if (!*timeo_p)
+ goto do_nonblock;
+ if (sk->err || asoc->state >= SCTP_STATE_SHUTDOWN_PENDING ||
+ asoc->base.dead)
+ goto do_error;
+ if (signal_pending(current))
+ goto do_interrupted;
+ if (msg_len <= sctp_wspace(asoc))
+ break;
+
+ /* Let another process have a go. Since we are going
+ * to sleep anyway.
+ */
+ sctp_release_sock(sk);
+ current_timeo = schedule_timeout(current_timeo);
+ sctp_lock_sock(sk);
+
+ *timeo_p = current_timeo;
+ }
+
+out:
+ remove_wait_queue(&asoc->wait, &wait);
+ __set_current_state(TASK_RUNNING);
+
+ /* Release the association's refcnt. */
+ sctp_association_put(asoc);
+
+ return err;
+
+do_error:
+ err = -EPIPE;
+ goto out;
+
+do_interrupted:
+ err = sock_intr_errno(*timeo_p);
+ goto out;
+
+do_nonblock:
+ err = -EAGAIN;
+ goto out;
+}
+
+/* If socket sndbuf has changed, wake up all per association waiters. */
+void sctp_write_space(struct sock *sk)
+{
+ struct sctp_association *asoc;
+ struct list_head *pos;
+
+ /* Wake up the tasks in each wait queue. */
+ list_for_each(pos, &((sctp_sk(sk))->ep->asocs)) {
+ asoc = list_entry(pos, struct sctp_association, asocs);
+ __sctp_write_space(asoc);
+ }
+}
+
+/* Is there any sndbuf space available on the socket?
+ *
+ * Note that wmem_queued is the sum of the send buffers on all of the
+ * associations on the same socket. For a UDP-style socket with
+ * multiple associations, it is possible for it to be "unwriteable"
+ * prematurely. I assume that this is acceptable because
+ * a premature "unwriteable" is better than an accidental "writeable" which
+ * would cause an unwanted block under certain circumstances. For the 1-1
+ * UDP-style sockets or TCP-style sockets, this code should work.
+ * - Daisy
+ */
+static int sctp_writeable(struct sock *sk)
+{
+ int amt = 0;
+
+ amt = sk->sndbuf - sk->wmem_queued;
+ if (amt < 0)
+ amt = 0;
+ return amt;
+}
+
+/* Wait for an association to go into ESTABLISHED state. If timeout is 0,
+ * returns immediately with EINPROGRESS.
+ */
+static int sctp_wait_for_connect(struct sctp_association *asoc, long *timeo_p)
+{
+ struct sock *sk = asoc->base.sk;
+ int err = 0;
+ long current_timeo = *timeo_p;
+ DECLARE_WAITQUEUE(wait, current);
+
+ SCTP_DEBUG_PRINTK("%s: asoc=%p, timeo=%ld\n", __FUNCTION__, asoc,
+ (long)(*timeo_p));
+
+ /* Increment the association's refcnt. */
+ sctp_association_hold(asoc);
+
+ add_wait_queue_exclusive(&asoc->wait, &wait);
+ for (;;) {
+ __set_current_state(TASK_INTERRUPTIBLE);
+ if (!*timeo_p)
+ goto do_nonblock;
+ if (sk->shutdown & RCV_SHUTDOWN)
+ break;
+ if (sk->err || asoc->state >= SCTP_STATE_SHUTDOWN_PENDING ||
+ asoc->base.dead)
+ goto do_error;
+ if (signal_pending(current))
+ goto do_interrupted;
+
+ if (sctp_state(asoc, ESTABLISHED))
+ break;
+
+ /* Let another process have a go. Since we are going
+ * to sleep anyway.
+ */
+ sctp_release_sock(sk);
+ current_timeo = schedule_timeout(current_timeo);
+ sctp_lock_sock(sk);
+
+ *timeo_p = current_timeo;
+ }
+
+out:
+ remove_wait_queue(&asoc->wait, &wait);
+ __set_current_state(TASK_RUNNING);
+
+ /* Release the association's refcnt. */
+ sctp_association_put(asoc);
+
+ return err;
+
+do_error:
+ if (asoc->counters[SCTP_COUNTER_INIT_ERROR] + 1 >=
+ asoc->max_init_attempts)
+ err = -ETIMEDOUT;
+ else
+ err = -ECONNREFUSED;
+ goto out;
+
+do_interrupted:
+ err = sock_intr_errno(*timeo_p);
+ goto out;
+
+do_nonblock:
+ err = -EINPROGRESS;
+ goto out;
+}
+
+static int sctp_wait_for_accept(struct sock *sk, long timeo)
+{
+ struct sctp_endpoint *ep;
+ int err = 0;
+ DECLARE_WAITQUEUE(wait, current);
+
+ ep = sctp_sk(sk)->ep;
+
+ add_wait_queue_exclusive(sk->sleep, &wait);
+
+ for (;;) {
+ __set_current_state(TASK_INTERRUPTIBLE);
+ if (list_empty(&ep->asocs)) {
+ sctp_release_sock(sk);
+ timeo = schedule_timeout(timeo);
+ sctp_lock_sock(sk);
+ }
+
+ err = -EINVAL;
+ if (!sctp_sstate(sk, LISTENING))
+ break;
+
+ err = 0;
+ if (!list_empty(&ep->asocs))
+ break;
+
+ err = sock_intr_errno(timeo);
+ if (signal_pending(current))
+ break;
+
+ err = -EAGAIN;
+ if (!timeo)
+ break;
+ }
+
+ remove_wait_queue(sk->sleep, &wait);
+ __set_current_state(TASK_RUNNING);
+
+ return err;
+}
+
+void sctp_wait_for_close(struct sock *sk, long timeout)
+{
+ DECLARE_WAITQUEUE(wait, current);
+
+ add_wait_queue_exclusive(sk->sleep, &wait);
+
+ do {
+ __set_current_state(TASK_INTERRUPTIBLE);
+ if (list_empty(&sctp_sk(sk)->ep->asocs))
+ break;
+ sctp_release_sock(sk);
+ timeout = schedule_timeout(timeout);
+ sctp_lock_sock(sk);
+ } while (!signal_pending(current) && timeout);
+
+ remove_wait_queue(sk->sleep, &wait);
+ __set_current_state(TASK_RUNNING);
+}
+
+/* Populate the fields of the newsk from the oldsk and migrate the assoc
+ * and its messages to the newsk.
+ */
+static void sctp_sock_migrate(struct sock *oldsk, struct sock *newsk,
+ struct sctp_association *assoc,
+ sctp_socket_type_t type)
+{
+ struct sctp_opt *oldsp = sctp_sk(oldsk);
+ struct sctp_opt *newsp = sctp_sk(newsk);
+ struct sctp_endpoint *newep = newsp->ep;
+ struct sk_buff *skb, *tmp;
+ struct sctp_ulpevent *event;
+
+ /* Migrate socket buffer sizes and all the socket level options to the
+ * new socket.
+ */
+ newsk->sndbuf = oldsk->sndbuf;
+ newsk->rcvbuf = oldsk->rcvbuf;
+ /* Brute force copy old sctp opt. */
+ memcpy(newsp, oldsp, sizeof(struct sctp_opt));
+
+ /* Restore the ep value that was overwritten with the above structure
+ * copy.
+ */
+ newsp->ep = newep;
+ newsp->hmac = NULL;
+
+ newsk->num = oldsk->num;
+
+ /* Move any messages in the old socket's receive queue that are for the
+ * peeled off association to the new socket's receive queue.
+ */
+ sctp_skb_for_each(skb, &oldsk->receive_queue, tmp) {
+ event = sctp_skb2event(skb);
+ if (event->asoc == assoc) {
+ __skb_unlink(skb, skb->list);
+ __skb_queue_tail(&newsk->receive_queue, skb);
+ }
+ }
+
+ /* Clean up any messages pending delivery due to partial
+ * delivery. Three cases:
+ * 1) No partial deliver; no work.
+ * 2) Peeling off partial delivery; keep pd_lobby in new pd_lobby.
+ * 3) Peeling off non-partial delivery; move pd_lobby to recieve_queue.
+ */
+ skb_queue_head_init(&newsp->pd_lobby);
+ sctp_sk(newsk)->pd_mode = assoc->ulpq.pd_mode;;
+
+ if (sctp_sk(oldsk)->pd_mode) {
+ struct sk_buff_head *queue;
+
+ /* Decide which queue to move pd_lobby skbs to. */
+ if (assoc->ulpq.pd_mode) {
+ queue = &newsp->pd_lobby;
+ } else
+ queue = &newsk->receive_queue;
+
+ /* Walk through the pd_lobby, looking for skbs that
+ * need moved to the new socket.
+ */
+ sctp_skb_for_each(skb, &oldsp->pd_lobby, tmp) {
+ event = sctp_skb2event(skb);
+ if (event->asoc == assoc) {
+ __skb_unlink(skb, skb->list);
+ __skb_queue_tail(queue, skb);
+ }
+ }
+
+ /* Clear up any skbs waiting for the partial
+ * delivery to finish.
+ */
+ if (assoc->ulpq.pd_mode)
+ sctp_clear_pd(oldsk);
+
+ }
+
+ /* Set the type of socket to indicate that it is peeled off from the
+ * original UDP-style socket or created with the accept() call on a
+ * TCP-style socket..
+ */
+ newsp->type = type;
+
+ /* Migrate the association to the new socket. */
+ sctp_assoc_migrate(assoc, newsk);
+
+ /* If the association on the newsk is already closed before accept()
+ * is called, set RCV_SHUTDOWN flag.
+ */
+ if (sctp_state(assoc, CLOSED) && sctp_style(newsk, TCP))
+ newsk->shutdown |= RCV_SHUTDOWN;
+
+ newsk->state = SCTP_SS_ESTABLISHED;
+}
+
+/* This proto struct describes the ULP interface for SCTP. */
+struct proto sctp_prot = {
+ .name = "SCTP",
+ .close = sctp_close,
+ .connect = sctp_connect,
+ .disconnect = sctp_disconnect,
+ .accept = sctp_accept,
+ .ioctl = sctp_ioctl,
+ .init = sctp_init_sock,
+ .destroy = sctp_destroy_sock,
+ .shutdown = sctp_shutdown,
+ .setsockopt = sctp_setsockopt,
+ .getsockopt = sctp_getsockopt,
+ .sendmsg = sctp_sendmsg,
+ .recvmsg = sctp_recvmsg,
+ .bind = sctp_bind,
+ .backlog_rcv = sctp_backlog_rcv,
+ .hash = sctp_hash,
+ .unhash = sctp_unhash,
+ .get_port = sctp_get_port,
+};
diff --git a/uClinux-2.4.31-uc0/net/sctp/ssnmap.c b/uClinux-2.4.31-uc0/net/sctp/ssnmap.c
new file mode 100644
index 0000000..e627d2b
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/ssnmap.c
@@ -0,0 +1,131 @@
+/* SCTP kernel reference Implementation
+ * Copyright (c) 2003 International Business Machines, Corp.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * These functions manipulate sctp SSN tracker.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * Jon Grimm <jgrimm@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+#define MAX_KMALLOC_SIZE 131072
+
+static struct sctp_ssnmap *sctp_ssnmap_init(struct sctp_ssnmap *map, __u16 in,
+ __u16 out);
+
+/* Storage size needed for map includes 2 headers and then the
+ * specific needs of in or out streams.
+ */
+static inline size_t sctp_ssnmap_size(__u16 in, __u16 out)
+{
+ return sizeof(struct sctp_ssnmap) + (in + out) * sizeof(__u16);
+}
+
+
+/* Create a new sctp_ssnmap.
+ * Allocate room to store at least 'len' contiguous TSNs.
+ */
+struct sctp_ssnmap *sctp_ssnmap_new(__u16 in, __u16 out, int gfp)
+{
+ struct sctp_ssnmap *retval;
+ int size;
+
+ size = sctp_ssnmap_size(in, out);
+ if (size <= MAX_KMALLOC_SIZE)
+ retval = kmalloc(size, gfp);
+ else
+ retval = (struct sctp_ssnmap *)
+ __get_free_pages(gfp, get_order(size));
+ if (!retval)
+ goto fail;
+
+ if (!sctp_ssnmap_init(retval, in, out))
+ goto fail_map;
+
+ retval->malloced = 1;
+ SCTP_DBG_OBJCNT_INC(ssnmap);
+
+ return retval;
+
+fail_map:
+ if (size <= MAX_KMALLOC_SIZE)
+ kfree(retval);
+ else
+ free_pages((unsigned long)retval, get_order(size));
+fail:
+ return NULL;
+}
+
+
+/* Initialize a block of memory as a ssnmap. */
+static struct sctp_ssnmap *sctp_ssnmap_init(struct sctp_ssnmap *map, __u16 in,
+ __u16 out)
+{
+ memset(map, 0x00, sctp_ssnmap_size(in, out));
+
+ /* Start 'in' stream just after the map header. */
+ map->in.ssn = (__u16 *)&map[1];
+ map->in.len = in;
+
+ /* Start 'out' stream just after 'in'. */
+ map->out.ssn = &map->in.ssn[in];
+ map->out.len = out;
+
+ return map;
+}
+
+/* Clear out the ssnmap streams. */
+void sctp_ssnmap_clear(struct sctp_ssnmap *map)
+{
+ size_t size;
+
+ size = (map->in.len + map->out.len) * sizeof(__u16);
+ memset(map->in.ssn, 0x00, size);
+}
+
+/* Dispose of a ssnmap. */
+void sctp_ssnmap_free(struct sctp_ssnmap *map)
+{
+ if (map && map->malloced) {
+ int size;
+
+ size = sctp_ssnmap_size(map->in.len, map->out.len);
+ if (size <= MAX_KMALLOC_SIZE)
+ kfree(map);
+ else
+ free_pages((unsigned long)map, get_order(size));
+ SCTP_DBG_OBJCNT_DEC(ssnmap);
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/sysctl.c b/uClinux-2.4.31-uc0/net/sctp/sysctl.c
new file mode 100644
index 0000000..4586153
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/sysctl.c
@@ -0,0 +1,251 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2002, 2004
+ * Copyright (c) 2002 Intel Corp.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * Sysctl related interfaces for SCTP.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * Mingqin Liu <liuming@us.ibm.com>
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Ardelle Fan <ardelle.fan@intel.com>
+ * Ryan Layer <rmlayer@us.ibm.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <net/sctp/structs.h>
+#include <linux/sysctl.h>
+
+static ctl_handler sctp_sysctl_jiffies_ms;
+static long rto_timer_min = 1;
+static long rto_timer_max = 86400000; /* One day */
+
+static ctl_table sctp_table[] = {
+ {
+ .ctl_name = NET_SCTP_RTO_INITIAL,
+ .procname = "rto_initial",
+ .data = &sctp_rto_initial,
+ .maxlen = sizeof(long),
+ .mode = 0644,
+ .proc_handler = &proc_doulongvec_ms_jiffies_minmax,
+ .strategy = &sctp_sysctl_jiffies_ms,
+ .extra1 = &rto_timer_min,
+ .extra2 = &rto_timer_max
+ },
+ {
+ .ctl_name = NET_SCTP_RTO_MIN,
+ .procname = "rto_min",
+ .data = &sctp_rto_min,
+ .maxlen = sizeof(long),
+ .mode = 0644,
+ .proc_handler = &proc_doulongvec_ms_jiffies_minmax,
+ .strategy = &sctp_sysctl_jiffies_ms,
+ .extra1 = &rto_timer_min,
+ .extra2 = &rto_timer_max
+ },
+ {
+ .ctl_name = NET_SCTP_RTO_MAX,
+ .procname = "rto_max",
+ .data = &sctp_rto_max,
+ .maxlen = sizeof(long),
+ .mode = 0644,
+ .proc_handler = &proc_doulongvec_ms_jiffies_minmax,
+ .strategy = &sctp_sysctl_jiffies_ms,
+ .extra1 = &rto_timer_min,
+ .extra2 = &rto_timer_max
+ },
+ {
+ .ctl_name = NET_SCTP_VALID_COOKIE_LIFE,
+ .procname = "valid_cookie_life",
+ .data = &sctp_valid_cookie_life,
+ .maxlen = sizeof(long),
+ .mode = 0644,
+ .proc_handler = &proc_doulongvec_ms_jiffies_minmax,
+ .strategy = &sctp_sysctl_jiffies_ms,
+ .extra1 = &rto_timer_min,
+ .extra2 = &rto_timer_max
+ },
+ {
+ .ctl_name = NET_SCTP_MAX_BURST,
+ .procname = "max_burst",
+ .data = &sctp_max_burst,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec
+ },
+ {
+ .ctl_name = NET_SCTP_ASSOCIATION_MAX_RETRANS,
+ .procname = "association_max_retrans",
+ .data = &sctp_max_retrans_association,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec
+ },
+ {
+ .ctl_name = NET_SCTP_PATH_MAX_RETRANS,
+ .procname = "path_max_retrans",
+ .data = &sctp_max_retrans_path,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec
+ },
+ {
+ .ctl_name = NET_SCTP_MAX_INIT_RETRANSMITS,
+ .procname = "max_init_retransmits",
+ .data = &sctp_max_retrans_init,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec
+ },
+ {
+ .ctl_name = NET_SCTP_HB_INTERVAL,
+ .procname = "hb_interval",
+ .data = &sctp_hb_interval,
+ .maxlen = sizeof(long),
+ .mode = 0644,
+ .proc_handler = &proc_doulongvec_ms_jiffies_minmax,
+ .strategy = &sctp_sysctl_jiffies_ms,
+ .extra1 = &rto_timer_min,
+ .extra2 = &rto_timer_max
+ },
+ {
+ .ctl_name = NET_SCTP_PRESERVE_ENABLE,
+ .procname = "cookie_preserve_enable",
+ .data = &sctp_cookie_preserve_enable,
+ .maxlen = sizeof(long),
+ .mode = 0644,
+ .proc_handler = &proc_doulongvec_ms_jiffies_minmax,
+ .strategy = &sctp_sysctl_jiffies_ms,
+ .extra1 = &rto_timer_min,
+ .extra2 = &rto_timer_max
+ },
+ {
+ .ctl_name = NET_SCTP_RTO_ALPHA,
+ .procname = "rto_alpha_exp_divisor",
+ .data = &sctp_rto_alpha,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec
+ },
+ {
+ .ctl_name = NET_SCTP_RTO_BETA,
+ .procname = "rto_beta_exp_divisor",
+ .data = &sctp_rto_beta,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec
+ },
+ {
+ .ctl_name = NET_SCTP_ADDIP_ENABLE,
+ .procname = "addip_enable",
+ .data = &sctp_addip_enable,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec
+ },
+ {
+ .ctl_name = NET_SCTP_PRSCTP_ENABLE,
+ .procname = "prsctp_enable",
+ .data = &sctp_prsctp_enable,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec
+ },
+ { .ctl_name = 0 }
+};
+
+static ctl_table sctp_net_table[] = {
+ {
+ .ctl_name = NET_SCTP,
+ .procname = "sctp",
+ .mode = 0555,
+ .child = sctp_table
+ },
+ { .ctl_name = 0 }
+};
+
+static ctl_table sctp_root_table[] = {
+ {
+ .ctl_name = CTL_NET,
+ .procname = "net",
+ .mode = 0555,
+ .child = sctp_net_table
+ },
+ { .ctl_name = 0 }
+};
+
+static struct ctl_table_header * sctp_sysctl_header;
+
+/* Sysctl registration. */
+void sctp_sysctl_register(void)
+{
+ sctp_sysctl_header = register_sysctl_table(sctp_root_table, 0);
+}
+
+/* Sysctl deregistration. */
+void sctp_sysctl_unregister(void)
+{
+ unregister_sysctl_table(sctp_sysctl_header);
+}
+
+/* Strategy function to convert jiffies to milliseconds. */
+static int sctp_sysctl_jiffies_ms(ctl_table *table, int *name, int nlen,
+ void *oldval, size_t *oldlenp,
+ void *newval, size_t newlen, void **context)
+{
+ if (oldval) {
+ size_t olen;
+
+ if (oldlenp) {
+ if (get_user(olen, oldlenp))
+ return -EFAULT;
+
+ if (olen != sizeof (int))
+ return -EINVAL;
+ }
+ if (put_user((*(int *)(table->data) * 1000) / HZ,
+ (int *)oldval) ||
+ (oldlenp && put_user(sizeof (int), oldlenp)))
+ return -EFAULT;
+ }
+ if (newval && newlen) {
+ int new;
+
+ if (newlen != sizeof (int))
+ return -EINVAL;
+
+ if (get_user(new, (int *)newval))
+ return -EFAULT;
+
+ *(int *)(table->data) = (new * HZ) / 1000;
+ }
+ return 1;
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/transport.c b/uClinux-2.4.31-uc0/net/sctp/transport.c
new file mode 100644
index 0000000..3fcca5e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/transport.c
@@ -0,0 +1,514 @@
+/* SCTP kernel reference Implementation
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001-2003 International Business Machines Corp.
+ * Copyright (c) 2001 Intel Corp.
+ * Copyright (c) 2001 La Monte H.P. Yarroll
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * This module provides the abstraction for an SCTP tranport representing
+ * a remote transport address. For local transport addresses, we just use
+ * union sctp_addr.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Xingang Guo <xingang.guo@intel.com>
+ * Hui Huang <hui.huang@nokia.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ * Ardelle Fan <ardelle.fan@intel.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+/* 1st Level Abstractions. */
+
+/* Initialize a new transport from provided memory. */
+static struct sctp_transport *sctp_transport_init(struct sctp_transport *peer,
+ const union sctp_addr *addr,
+ int gfp)
+{
+ /* Copy in the address. */
+ peer->ipaddr = *addr;
+ peer->af_specific = sctp_get_af_specific(addr->sa.sa_family);
+ peer->asoc = NULL;
+
+ peer->dst = NULL;
+ memset(&peer->saddr, 0, sizeof(union sctp_addr));
+
+ /* From 6.3.1 RTO Calculation:
+ *
+ * C1) Until an RTT measurement has been made for a packet sent to the
+ * given destination transport address, set RTO to the protocol
+ * parameter 'RTO.Initial'.
+ */
+ peer->rtt = 0;
+ peer->rto = sctp_rto_initial;
+ peer->rttvar = 0;
+ peer->srtt = 0;
+ peer->rto_pending = 0;
+
+ peer->last_time_heard = jiffies;
+ peer->last_time_used = jiffies;
+ peer->last_time_ecne_reduced = jiffies;
+
+ peer->active = SCTP_ACTIVE;
+ peer->hb_allowed = 0;
+
+ /* Initialize the default path max_retrans. */
+ peer->max_retrans = sctp_max_retrans_path;
+ peer->error_count = 0;
+
+ INIT_LIST_HEAD(&peer->transmitted);
+ INIT_LIST_HEAD(&peer->send_ready);
+ INIT_LIST_HEAD(&peer->transports);
+
+ /* Set up the retransmission timer. */
+ init_timer(&peer->T3_rtx_timer);
+ peer->T3_rtx_timer.function = sctp_generate_t3_rtx_event;
+ peer->T3_rtx_timer.data = (unsigned long)peer;
+
+ /* Set up the heartbeat timer. */
+ init_timer(&peer->hb_timer);
+ peer->hb_interval = SCTP_DEFAULT_TIMEOUT_HEARTBEAT;
+ peer->hb_timer.function = sctp_generate_heartbeat_event;
+ peer->hb_timer.data = (unsigned long)peer;
+
+ atomic_set(&peer->refcnt, 1);
+ peer->dead = 0;
+
+ peer->malloced = 0;
+
+ /* Initialize the state information for SFR-CACC */
+ peer->cacc.changeover_active = 0;
+ peer->cacc.cycling_changeover = 0;
+ peer->cacc.next_tsn_at_change = 0;
+ peer->cacc.cacc_saw_newack = 0;
+
+ return peer;
+}
+
+/* Allocate and initialize a new transport. */
+struct sctp_transport *sctp_transport_new(const union sctp_addr *addr, int gfp)
+{
+ struct sctp_transport *transport;
+
+ transport = t_new(struct sctp_transport, gfp);
+ if (!transport)
+ goto fail;
+
+ if (!sctp_transport_init(transport, addr, gfp))
+ goto fail_init;
+
+ transport->malloced = 1;
+ SCTP_DBG_OBJCNT_INC(transport);
+
+ return transport;
+
+fail_init:
+ kfree(transport);
+
+fail:
+ return NULL;
+}
+
+/* This transport is no longer needed. Free up if possible, or
+ * delay until it last reference count.
+ */
+void sctp_transport_free(struct sctp_transport *transport)
+{
+ transport->dead = 1;
+
+ /* Try to delete the heartbeat timer. */
+ if (del_timer(&transport->hb_timer))
+ sctp_transport_put(transport);
+
+ /* Delete the T3_rtx timer if it's active.
+ * There is no point in not doing this now and letting
+ * structure hang around in memory since we know
+ * the tranport is going away.
+ */
+ if (timer_pending(&transport->T3_rtx_timer) &&
+ del_timer(&transport->T3_rtx_timer))
+ sctp_transport_put(transport);
+
+
+ sctp_transport_put(transport);
+}
+
+/* Destroy the transport data structure.
+ * Assumes there are no more users of this structure.
+ */
+static void sctp_transport_destroy(struct sctp_transport *transport)
+{
+ SCTP_ASSERT(transport->dead, "Transport is not dead", return);
+
+ if (transport->asoc)
+ sctp_association_put(transport->asoc);
+
+ sctp_packet_free(&transport->packet);
+
+ dst_release(transport->dst);
+ kfree(transport);
+ SCTP_DBG_OBJCNT_DEC(transport);
+}
+
+/* Start T3_rtx timer if it is not already running and update the heartbeat
+ * timer. This routine is called every time a DATA chunk is sent.
+ */
+void sctp_transport_reset_timers(struct sctp_transport *transport)
+{
+ /* RFC 2960 6.3.2 Retransmission Timer Rules
+ *
+ * R1) Every time a DATA chunk is sent to any address(including a
+ * retransmission), if the T3-rtx timer of that address is not running
+ * start it running so that it will expire after the RTO of that
+ * address.
+ */
+
+ if (!timer_pending(&transport->T3_rtx_timer))
+ if (!mod_timer(&transport->T3_rtx_timer,
+ jiffies + transport->rto))
+ sctp_transport_hold(transport);
+
+ /* When a data chunk is sent, reset the heartbeat interval. */
+ if (!mod_timer(&transport->hb_timer,
+ sctp_transport_timeout(transport)))
+ sctp_transport_hold(transport);
+}
+
+/* This transport has been assigned to an association.
+ * Initialize fields from the association or from the sock itself.
+ * Register the reference count in the association.
+ */
+void sctp_transport_set_owner(struct sctp_transport *transport,
+ struct sctp_association *asoc)
+{
+ transport->asoc = asoc;
+ sctp_association_hold(asoc);
+}
+
+/* Initialize the pmtu of a transport. */
+void sctp_transport_pmtu(struct sctp_transport *transport)
+{
+ struct dst_entry *dst;
+
+ dst = transport->af_specific->get_dst(NULL, &transport->ipaddr, NULL);
+
+ if (dst) {
+ transport->pmtu = dst_pmtu(dst);
+ dst_release(dst);
+ } else
+ transport->pmtu = SCTP_DEFAULT_MAXSEGMENT;
+}
+
+/* Caches the dst entry and source address for a transport's destination
+ * address.
+ */
+void sctp_transport_route(struct sctp_transport *transport,
+ union sctp_addr *saddr, struct sctp_opt *opt)
+{
+ struct sctp_association *asoc = transport->asoc;
+ struct sctp_af *af = transport->af_specific;
+ union sctp_addr *daddr = &transport->ipaddr;
+ struct dst_entry *dst;
+
+ dst = af->get_dst(asoc, daddr, saddr);
+
+ if (saddr)
+ memcpy(&transport->saddr, saddr, sizeof(union sctp_addr));
+ else
+ af->get_saddr(asoc, dst, daddr, &transport->saddr);
+
+ transport->dst = dst;
+ if (dst) {
+ transport->pmtu = dst_pmtu(dst);
+
+ /* Initialize sk->sk_rcv_saddr, if the transport is the
+ * association's active path for getsockname().
+ */
+ if (asoc && (transport == asoc->peer.active_path))
+ af->to_sk_saddr(&transport->saddr, asoc->base.sk);
+ } else
+ transport->pmtu = SCTP_DEFAULT_MAXSEGMENT;
+}
+
+/* Hold a reference to a transport. */
+void sctp_transport_hold(struct sctp_transport *transport)
+{
+ atomic_inc(&transport->refcnt);
+}
+
+/* Release a reference to a transport and clean up
+ * if there are no more references.
+ */
+void sctp_transport_put(struct sctp_transport *transport)
+{
+ if (atomic_dec_and_test(&transport->refcnt))
+ sctp_transport_destroy(transport);
+}
+
+/* Update transport's RTO based on the newly calculated RTT. */
+void sctp_transport_update_rto(struct sctp_transport *tp, __u32 rtt)
+{
+ /* Check for valid transport. */
+ SCTP_ASSERT(tp, "NULL transport", return);
+
+ /* We should not be doing any RTO updates unless rto_pending is set. */
+ SCTP_ASSERT(tp->rto_pending, "rto_pending not set", return);
+
+ if (tp->rttvar || tp->srtt) {
+ /* 6.3.1 C3) When a new RTT measurement R' is made, set
+ * RTTVAR <- (1 - RTO.Beta) * RTTVAR + RTO.Beta * |SRTT - R'|
+ * SRTT <- (1 - RTO.Alpha) * SRTT + RTO.Alpha * R'
+ */
+
+ /* Note: The above algorithm has been rewritten to
+ * express rto_beta and rto_alpha as inverse powers
+ * of two.
+ * For example, assuming the default value of RTO.Alpha of
+ * 1/8, rto_alpha would be expressed as 3.
+ */
+ tp->rttvar = tp->rttvar - (tp->rttvar >> sctp_rto_beta)
+ + ((abs(tp->srtt - rtt)) >> sctp_rto_beta);
+ tp->srtt = tp->srtt - (tp->srtt >> sctp_rto_alpha)
+ + (rtt >> sctp_rto_alpha);
+ } else {
+ /* 6.3.1 C2) When the first RTT measurement R is made, set
+ * SRTT <- R, RTTVAR <- R/2.
+ */
+ tp->srtt = rtt;
+ tp->rttvar = rtt >> 1;
+ }
+
+ /* 6.3.1 G1) Whenever RTTVAR is computed, if RTTVAR = 0, then
+ * adjust RTTVAR <- G, where G is the CLOCK GRANULARITY.
+ */
+ if (tp->rttvar == 0)
+ tp->rttvar = SCTP_CLOCK_GRANULARITY;
+
+ /* 6.3.1 C3) After the computation, update RTO <- SRTT + 4 * RTTVAR. */
+ tp->rto = tp->srtt + (tp->rttvar << 2);
+
+ /* 6.3.1 C6) Whenever RTO is computed, if it is less than RTO.Min
+ * seconds then it is rounded up to RTO.Min seconds.
+ */
+ if (tp->rto < tp->asoc->rto_min)
+ tp->rto = tp->asoc->rto_min;
+
+ /* 6.3.1 C7) A maximum value may be placed on RTO provided it is
+ * at least RTO.max seconds.
+ */
+ if (tp->rto > tp->asoc->rto_max)
+ tp->rto = tp->asoc->rto_max;
+
+ tp->rtt = rtt;
+
+ /* Reset rto_pending so that a new RTT measurement is started when a
+ * new data chunk is sent.
+ */
+ tp->rto_pending = 0;
+
+ SCTP_DEBUG_PRINTK("%s: transport: %p, rtt: %d, srtt: %d "
+ "rttvar: %d, rto: %d\n", __FUNCTION__,
+ tp, rtt, tp->srtt, tp->rttvar, tp->rto);
+}
+
+/* This routine updates the transport's cwnd and partial_bytes_acked
+ * parameters based on the bytes acked in the received SACK.
+ */
+void sctp_transport_raise_cwnd(struct sctp_transport *transport,
+ __u32 sack_ctsn, __u32 bytes_acked)
+{
+ __u32 cwnd, ssthresh, flight_size, pba, pmtu;
+
+ cwnd = transport->cwnd;
+ flight_size = transport->flight_size;
+
+ /* The appropriate cwnd increase algorithm is performed if, and only
+ * if the cumulative TSN has advanced and the congestion window is
+ * being fully utilized.
+ */
+ if ((transport->asoc->ctsn_ack_point >= sack_ctsn) ||
+ (flight_size < cwnd))
+ return;
+
+ ssthresh = transport->ssthresh;
+ pba = transport->partial_bytes_acked;
+ pmtu = transport->asoc->pmtu;
+
+ if (cwnd <= ssthresh) {
+ /* RFC 2960 7.2.1, sctpimpguide-05 2.14.2 When cwnd is less
+ * than or equal to ssthresh an SCTP endpoint MUST use the
+ * slow start algorithm to increase cwnd only if the current
+ * congestion window is being fully utilized and an incoming
+ * SACK advances the Cumulative TSN Ack Point. Only when these
+ * two conditions are met can the cwnd be increased otherwise
+ * the cwnd MUST not be increased. If these conditions are met
+ * then cwnd MUST be increased by at most the lesser of
+ * 1) the total size of the previously outstanding DATA
+ * chunk(s) acknowledged, and 2) the destination's path MTU.
+ */
+ if (bytes_acked > pmtu)
+ cwnd += pmtu;
+ else
+ cwnd += bytes_acked;
+ SCTP_DEBUG_PRINTK("%s: SLOW START: transport: %p, "
+ "bytes_acked: %d, cwnd: %d, ssthresh: %d, "
+ "flight_size: %d, pba: %d\n",
+ __FUNCTION__,
+ transport, bytes_acked, cwnd,
+ ssthresh, flight_size, pba);
+ } else {
+ /* RFC 2960 7.2.2 Whenever cwnd is greater than ssthresh,
+ * upon each SACK arrival that advances the Cumulative TSN Ack
+ * Point, increase partial_bytes_acked by the total number of
+ * bytes of all new chunks acknowledged in that SACK including
+ * chunks acknowledged by the new Cumulative TSN Ack and by
+ * Gap Ack Blocks.
+ *
+ * When partial_bytes_acked is equal to or greater than cwnd
+ * and before the arrival of the SACK the sender had cwnd or
+ * more bytes of data outstanding (i.e., before arrival of the
+ * SACK, flightsize was greater than or equal to cwnd),
+ * increase cwnd by MTU, and reset partial_bytes_acked to
+ * (partial_bytes_acked - cwnd).
+ */
+ pba += bytes_acked;
+ if (pba >= cwnd) {
+ cwnd += pmtu;
+ pba = ((cwnd < pba) ? (pba - cwnd) : 0);
+ }
+ SCTP_DEBUG_PRINTK("%s: CONGESTION AVOIDANCE: "
+ "transport: %p, bytes_acked: %d, cwnd: %d, "
+ "ssthresh: %d, flight_size: %d, pba: %d\n",
+ __FUNCTION__,
+ transport, bytes_acked, cwnd,
+ ssthresh, flight_size, pba);
+ }
+
+ transport->cwnd = cwnd;
+ transport->partial_bytes_acked = pba;
+}
+
+/* This routine is used to lower the transport's cwnd when congestion is
+ * detected.
+ */
+void sctp_transport_lower_cwnd(struct sctp_transport *transport,
+ sctp_lower_cwnd_t reason)
+{
+ switch (reason) {
+ case SCTP_LOWER_CWND_T3_RTX:
+ /* RFC 2960 Section 7.2.3, sctpimpguide
+ * When the T3-rtx timer expires on an address, SCTP should
+ * perform slow start by:
+ * ssthresh = max(cwnd/2, 4*MTU)
+ * cwnd = 1*MTU
+ * partial_bytes_acked = 0
+ */
+ transport->ssthresh = max(transport->cwnd/2,
+ 4*transport->asoc->pmtu);
+ transport->cwnd = transport->asoc->pmtu;
+ break;
+
+ case SCTP_LOWER_CWND_FAST_RTX:
+ /* RFC 2960 7.2.4 Adjust the ssthresh and cwnd of the
+ * destination address(es) to which the missing DATA chunks
+ * were last sent, according to the formula described in
+ * Section 7.2.3.
+ *
+ * RFC 2960 7.2.3, sctpimpguide Upon detection of packet
+ * losses from SACK (see Section 7.2.4), An endpoint
+ * should do the following:
+ * ssthresh = max(cwnd/2, 4*MTU)
+ * cwnd = ssthresh
+ * partial_bytes_acked = 0
+ */
+ transport->ssthresh = max(transport->cwnd/2,
+ 4*transport->asoc->pmtu);
+ transport->cwnd = transport->ssthresh;
+ break;
+
+ case SCTP_LOWER_CWND_ECNE:
+ /* RFC 2481 Section 6.1.2.
+ * If the sender receives an ECN-Echo ACK packet
+ * then the sender knows that congestion was encountered in the
+ * network on the path from the sender to the receiver. The
+ * indication of congestion should be treated just as a
+ * congestion loss in non-ECN Capable TCP. That is, the TCP
+ * source halves the congestion window "cwnd" and reduces the
+ * slow start threshold "ssthresh".
+ * A critical condition is that TCP does not react to
+ * congestion indications more than once every window of
+ * data (or more loosely more than once every round-trip time).
+ */
+ if ((jiffies - transport->last_time_ecne_reduced) >
+ transport->rtt) {
+ transport->ssthresh = max(transport->cwnd/2,
+ 4*transport->asoc->pmtu);
+ transport->cwnd = transport->ssthresh;
+ transport->last_time_ecne_reduced = jiffies;
+ }
+ break;
+
+ case SCTP_LOWER_CWND_INACTIVE:
+ /* RFC 2960 Section 7.2.1, sctpimpguide
+ * When the endpoint does not transmit data on a given
+ * transport address, the cwnd of the transport address
+ * should be adjusted to max(cwnd/2, 4*MTU) per RTO.
+ * NOTE: Although the draft recommends that this check needs
+ * to be done every RTO interval, we do it every hearbeat
+ * interval.
+ */
+ if ((jiffies - transport->last_time_used) > transport->rto)
+ transport->cwnd = max(transport->cwnd/2,
+ 4*transport->asoc->pmtu);
+ break;
+ };
+
+ transport->partial_bytes_acked = 0;
+ SCTP_DEBUG_PRINTK("%s: transport: %p reason: %d cwnd: "
+ "%d ssthresh: %d\n", __FUNCTION__,
+ transport, reason,
+ transport->cwnd, transport->ssthresh);
+}
+
+/* What is the next timeout value for this transport? */
+unsigned long sctp_transport_timeout(struct sctp_transport *t)
+{
+ unsigned long timeout;
+ timeout = t->hb_interval + t->rto + sctp_jitter(t->rto);
+ timeout += jiffies;
+ return timeout;
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/tsnmap.c b/uClinux-2.4.31-uc0/net/sctp/tsnmap.c
new file mode 100644
index 0000000..ac4fae1
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/tsnmap.c
@@ -0,0 +1,417 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2001, 2004
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001 Intel Corp.
+ *
+ * This file is part of the SCTP kernel reference Implementation
+ *
+ * These functions manipulate sctp tsn mapping array.
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * Karl Knutson <karl@athena.chicago.il.us>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+static void sctp_tsnmap_update(struct sctp_tsnmap *map);
+static void sctp_tsnmap_find_gap_ack(__u8 *map, __u16 off,
+ __u16 len, __u16 base,
+ int *started, __u16 *start,
+ int *ended, __u16 *end);
+
+/* Initialize a block of memory as a tsnmap. */
+struct sctp_tsnmap *sctp_tsnmap_init(struct sctp_tsnmap *map, __u16 len,
+ __u32 initial_tsn)
+{
+ map->tsn_map = map->raw_map;
+ map->overflow_map = map->tsn_map + len;
+ map->len = len;
+
+ /* Clear out a TSN ack status. */
+ memset(map->tsn_map, 0x00, map->len + map->len);
+
+ /* Keep track of TSNs represented by tsn_map. */
+ map->base_tsn = initial_tsn;
+ map->overflow_tsn = initial_tsn + map->len;
+ map->cumulative_tsn_ack_point = initial_tsn - 1;
+ map->max_tsn_seen = map->cumulative_tsn_ack_point;
+ map->malloced = 0;
+ map->num_dup_tsns = 0;
+
+ return map;
+}
+
+/* Test the tracking state of this TSN.
+ * Returns:
+ * 0 if the TSN has not yet been seen
+ * >0 if the TSN has been seen (duplicate)
+ * <0 if the TSN is invalid (too large to track)
+ */
+int sctp_tsnmap_check(const struct sctp_tsnmap *map, __u32 tsn)
+{
+ __s32 gap;
+ int dup;
+
+ /* Calculate the index into the mapping arrays. */
+ gap = tsn - map->base_tsn;
+
+ /* Verify that we can hold this TSN. */
+ if (gap >= (/* base */ map->len + /* overflow */ map->len)) {
+ dup = -1;
+ goto out;
+ }
+
+ /* Honk if we've already seen this TSN.
+ * We have three cases:
+ * 1. The TSN is ancient or belongs to a previous tsn_map.
+ * 2. The TSN is already marked in the tsn_map.
+ * 3. The TSN is already marked in the tsn_map_overflow.
+ */
+ if (gap < 0 ||
+ (gap < map->len && map->tsn_map[gap]) ||
+ (gap >= map->len && map->overflow_map[gap - map->len]))
+ dup = 1;
+ else
+ dup = 0;
+
+out:
+ return dup;
+}
+
+
+/* Mark this TSN as seen. */
+void sctp_tsnmap_mark(struct sctp_tsnmap *map, __u32 tsn)
+{
+ __s32 gap;
+
+ /* Vacuously mark any TSN which precedes the map base or
+ * exceeds the end of the map.
+ */
+ if (TSN_lt(tsn, map->base_tsn))
+ return;
+ if (!TSN_lt(tsn, map->base_tsn + map->len + map->len))
+ return;
+
+ /* Bump the max. */
+ if (TSN_lt(map->max_tsn_seen, tsn))
+ map->max_tsn_seen = tsn;
+
+ /* Assert: TSN is in range. */
+ gap = tsn - map->base_tsn;
+
+ /* Mark the TSN as received. */
+ if (gap < map->len)
+ map->tsn_map[gap]++;
+ else
+ map->overflow_map[gap - map->len]++;
+
+ /* Go fixup any internal TSN mapping variables including
+ * cumulative_tsn_ack_point.
+ */
+ sctp_tsnmap_update(map);
+}
+
+
+/* Initialize a Gap Ack Block iterator from memory being provided. */
+SCTP_STATIC void sctp_tsnmap_iter_init(const struct sctp_tsnmap *map,
+ struct sctp_tsnmap_iter *iter)
+{
+ /* Only start looking one past the Cumulative TSN Ack Point. */
+ iter->start = map->cumulative_tsn_ack_point + 1;
+}
+
+/* Get the next Gap Ack Blocks. Returns 0 if there was not another block
+ * to get.
+ */
+SCTP_STATIC int sctp_tsnmap_next_gap_ack(const struct sctp_tsnmap *map,
+ struct sctp_tsnmap_iter *iter,
+ __u16 *start, __u16 *end)
+{
+ int started, ended;
+ __u16 _start, _end, offset;
+
+ /* We haven't found a gap yet. */
+ started = ended = 0;
+
+ /* If there are no more gap acks possible, get out fast. */
+ if (TSN_lte(map->max_tsn_seen, iter->start))
+ return 0;
+
+ /* Search the first mapping array. */
+ if (iter->start - map->base_tsn < map->len) {
+
+ offset = iter->start - map->base_tsn;
+ sctp_tsnmap_find_gap_ack(map->tsn_map, offset, map->len, 0,
+ &started, &_start, &ended, &_end);
+ }
+
+ /* Do we need to check the overflow map? */
+ if (!ended) {
+ /* Fix up where we'd like to start searching in the
+ * overflow map.
+ */
+ if (iter->start - map->base_tsn < map->len)
+ offset = 0;
+ else
+ offset = iter->start - map->base_tsn - map->len;
+
+ /* Search the overflow map. */
+ sctp_tsnmap_find_gap_ack(map->overflow_map,
+ offset,
+ map->len,
+ map->len,
+ &started, &_start,
+ &ended, &_end);
+ }
+
+ /* The Gap Ack Block happens to end at the end of the
+ * overflow map.
+ */
+ if (started && !ended) {
+ ended++;
+ _end = map->len + map->len - 1;
+ }
+
+ /* If we found a Gap Ack Block, return the start and end and
+ * bump the iterator forward.
+ */
+ if (ended) {
+ /* Fix up the start and end based on the
+ * Cumulative TSN Ack offset into the map.
+ */
+ int gap = map->cumulative_tsn_ack_point -
+ map->base_tsn;
+
+ *start = _start - gap;
+ *end = _end - gap;
+
+ /* Move the iterator forward. */
+ iter->start = map->cumulative_tsn_ack_point + *end + 1;
+ }
+
+ return ended;
+}
+
+/* Mark this and any lower TSN as seen. */
+void sctp_tsnmap_skip(struct sctp_tsnmap *map, __u32 tsn)
+{
+ __s32 gap;
+
+ /* Vacuously mark any TSN which precedes the map base or
+ * exceeds the end of the map.
+ */
+ if (TSN_lt(tsn, map->base_tsn))
+ return;
+ if (!TSN_lt(tsn, map->base_tsn + map->len + map->len))
+ return;
+
+ /* Bump the max. */
+ if (TSN_lt(map->max_tsn_seen, tsn))
+ map->max_tsn_seen = tsn;
+
+ /* Assert: TSN is in range. */
+ gap = tsn - map->base_tsn + 1;
+
+ /* Mark the TSNs as received. */
+ if (gap <= map->len)
+ memset(map->tsn_map, 0x01, gap);
+ else {
+ memset(map->tsn_map, 0x01, map->len);
+ memset(map->overflow_map, 0x01, (gap - map->len));
+ }
+
+ /* Go fixup any internal TSN mapping variables including
+ * cumulative_tsn_ack_point.
+ */
+ sctp_tsnmap_update(map);
+}
+
+/********************************************************************
+ * 2nd Level Abstractions
+ ********************************************************************/
+
+/* This private helper function updates the tsnmap buffers and
+ * the Cumulative TSN Ack Point.
+ */
+static void sctp_tsnmap_update(struct sctp_tsnmap *map)
+{
+ __u32 ctsn;
+
+ ctsn = map->cumulative_tsn_ack_point;
+ do {
+ ctsn++;
+ if (ctsn == map->overflow_tsn) {
+ /* Now tsn_map must have been all '1's,
+ * so we swap the map and check the overflow table
+ */
+ __u8 *tmp = map->tsn_map;
+ memset(tmp, 0, map->len);
+ map->tsn_map = map->overflow_map;
+ map->overflow_map = tmp;
+
+ /* Update the tsn_map boundaries. */
+ map->base_tsn += map->len;
+ map->overflow_tsn += map->len;
+ }
+ } while (map->tsn_map[ctsn - map->base_tsn]);
+
+ map->cumulative_tsn_ack_point = ctsn - 1; /* Back up one. */
+}
+
+/* How many data chunks are we missing from our peer?
+ */
+__u16 sctp_tsnmap_pending(struct sctp_tsnmap *map)
+{
+ __u32 cum_tsn = map->cumulative_tsn_ack_point;
+ __u32 max_tsn = map->max_tsn_seen;
+ __u32 base_tsn = map->base_tsn;
+ __u16 pending_data;
+ __s32 gap, start, end, i;
+
+ pending_data = max_tsn - cum_tsn;
+ gap = max_tsn - base_tsn;
+
+ if (gap <= 0 || gap >= (map->len + map->len))
+ goto out;
+
+ start = ((cum_tsn >= base_tsn) ? (cum_tsn - base_tsn + 1) : 0);
+ end = ((gap > map->len ) ? map->len : gap + 1);
+
+ for (i = start; i < end; i++) {
+ if (map->tsn_map[i])
+ pending_data--;
+ }
+
+ if (gap >= map->len) {
+ start = 0;
+ end = gap - map->len + 1;
+ for (i = start; i < end; i++) {
+ if (map->overflow_map[i])
+ pending_data--;
+ }
+ }
+
+out:
+ return pending_data;
+}
+
+/* This is a private helper for finding Gap Ack Blocks. It searches a
+ * single array for the start and end of a Gap Ack Block.
+ *
+ * The flags "started" and "ended" tell is if we found the beginning
+ * or (respectively) the end of a Gap Ack Block.
+ */
+static void sctp_tsnmap_find_gap_ack(__u8 *map, __u16 off,
+ __u16 len, __u16 base,
+ int *started, __u16 *start,
+ int *ended, __u16 *end)
+{
+ int i = off;
+
+ /* Look through the entire array, but break out
+ * early if we have found the end of the Gap Ack Block.
+ */
+
+ /* Also, stop looking past the maximum TSN seen. */
+
+ /* Look for the start. */
+ if (!(*started)) {
+ for (; i < len; i++) {
+ if (map[i]) {
+ (*started)++;
+ *start = base + i;
+ break;
+ }
+ }
+ }
+
+ /* Look for the end. */
+ if (*started) {
+ /* We have found the start, let's find the
+ * end. If we find the end, break out.
+ */
+ for (; i < len; i++) {
+ if (!map[i]) {
+ (*ended)++;
+ *end = base + i - 1;
+ break;
+ }
+ }
+ }
+}
+
+/* Renege that we have seen a TSN. */
+void sctp_tsnmap_renege(struct sctp_tsnmap *map, __u32 tsn)
+{
+ __s32 gap;
+
+ if (TSN_lt(tsn, map->base_tsn))
+ return;
+ if (!TSN_lt(tsn, map->base_tsn + map->len + map->len))
+ return;
+
+ /* Assert: TSN is in range. */
+ gap = tsn - map->base_tsn;
+
+ /* Pretend we never saw the TSN. */
+ if (gap < map->len)
+ map->tsn_map[gap] = 0;
+ else
+ map->overflow_map[gap - map->len] = 0;
+}
+
+/* How many gap ack blocks do we have recorded? */
+__u16 sctp_tsnmap_num_gabs(struct sctp_tsnmap *map)
+{
+ struct sctp_tsnmap_iter iter;
+ int gabs = 0;
+
+ /* Refresh the gap ack information. */
+ if (sctp_tsnmap_has_gap(map)) {
+ sctp_tsnmap_iter_init(map, &iter);
+ while (sctp_tsnmap_next_gap_ack(map, &iter,
+ &map->gabs[gabs].start,
+ &map->gabs[gabs].end)) {
+
+ map->gabs[gabs].start = htons(map->gabs[gabs].start);
+ map->gabs[gabs].end = htons(map->gabs[gabs].end);
+ gabs++;
+ if (gabs >= SCTP_MAX_GABS)
+ break;
+ }
+ }
+ return gabs;
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/ulpevent.c b/uClinux-2.4.31-uc0/net/sctp/ulpevent.c
new file mode 100644
index 0000000..afed5f3
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/ulpevent.c
@@ -0,0 +1,942 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2001, 2004
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001 Intel Corp.
+ * Copyright (c) 2001 Nokia, Inc.
+ * Copyright (c) 2001 La Monte H.P. Yarroll
+ *
+ * These functions manipulate an sctp event. The struct ulpevent is used
+ * to carry notifications and data to the ULP (sockets).
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Ardelle Fan <ardelle.fan@intel.com>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <linux/skbuff.h>
+#include <net/sctp/structs.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+static void sctp_ulpevent_receive_data(struct sctp_ulpevent *event,
+ struct sctp_association *asoc);
+static void sctp_ulpevent_release_data(struct sctp_ulpevent *event);
+
+/* Stub skb destructor. */
+static void sctp_stub_rfree(struct sk_buff *skb)
+{
+/* WARNING: This function is just a warning not to use the
+ * skb destructor. If the skb is shared, we may get the destructor
+ * callback on some processor that does not own the sock_lock. This
+ * was occuring with PACKET socket applications that were monitoring
+ * our skbs. We can't take the sock_lock, because we can't risk
+ * recursing if we do really own the sock lock. Instead, do all
+ * of our rwnd manipulation while we own the sock_lock outright.
+ */
+}
+
+/* Initialize an ULP event from an given skb. */
+SCTP_STATIC void sctp_ulpevent_init(struct sctp_ulpevent *event, int msg_flags)
+{
+ memset(event, 0, sizeof(struct sctp_ulpevent));
+ event->msg_flags = msg_flags;
+}
+
+/* Create a new sctp_ulpevent. */
+SCTP_STATIC struct sctp_ulpevent *sctp_ulpevent_new(int size, int msg_flags,
+ int gfp)
+{
+ struct sctp_ulpevent *event;
+ struct sk_buff *skb;
+
+ skb = alloc_skb(size, gfp);
+ if (!skb)
+ goto fail;
+
+ event = sctp_skb2event(skb);
+ sctp_ulpevent_init(event, msg_flags);
+
+ return event;
+
+fail:
+ return NULL;
+}
+
+/* Is this a MSG_NOTIFICATION? */
+int sctp_ulpevent_is_notification(const struct sctp_ulpevent *event)
+{
+ return MSG_NOTIFICATION == (event->msg_flags & MSG_NOTIFICATION);
+}
+
+/* Hold the association in case the msg_name needs read out of
+ * the association.
+ */
+static inline void sctp_ulpevent_set_owner(struct sctp_ulpevent *event,
+ const struct sctp_association *asoc)
+{
+ struct sk_buff *skb;
+
+ /* Cast away the const, as we are just wanting to
+ * bump the reference count.
+ */
+ sctp_association_hold((struct sctp_association *)asoc);
+ skb = sctp_event2skb(event);
+ skb->sk = asoc->base.sk;
+ event->asoc = (struct sctp_association *)asoc;
+ skb->destructor = sctp_stub_rfree;
+}
+
+/* A simple destructor to give up the reference to the association. */
+static inline void sctp_ulpevent_release_owner(struct sctp_ulpevent *event)
+{
+ sctp_association_put(event->asoc);
+}
+
+/* Create and initialize an SCTP_ASSOC_CHANGE event.
+ *
+ * 5.3.1.1 SCTP_ASSOC_CHANGE
+ *
+ * Communication notifications inform the ULP that an SCTP association
+ * has either begun or ended. The identifier for a new association is
+ * provided by this notification.
+ *
+ * Note: There is no field checking here. If a field is unused it will be
+ * zero'd out.
+ */
+struct sctp_ulpevent *sctp_ulpevent_make_assoc_change(
+ const struct sctp_association *asoc,
+ __u16 flags, __u16 state, __u16 error, __u16 outbound,
+ __u16 inbound, int gfp)
+{
+ struct sctp_ulpevent *event;
+ struct sctp_assoc_change *sac;
+ struct sk_buff *skb;
+
+ event = sctp_ulpevent_new(sizeof(struct sctp_assoc_change),
+ MSG_NOTIFICATION, gfp);
+ if (!event)
+ goto fail;
+ skb = sctp_event2skb(event);
+ sac = (struct sctp_assoc_change *)
+ skb_put(skb, sizeof(struct sctp_assoc_change));
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.1 SCTP_ASSOC_CHANGE
+ *
+ * sac_type:
+ * It should be SCTP_ASSOC_CHANGE.
+ */
+ sac->sac_type = SCTP_ASSOC_CHANGE;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.1 SCTP_ASSOC_CHANGE
+ *
+ * sac_state: 32 bits (signed integer)
+ * This field holds one of a number of values that communicate the
+ * event that happened to the association.
+ */
+ sac->sac_state = state;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.1 SCTP_ASSOC_CHANGE
+ *
+ * sac_flags: 16 bits (unsigned integer)
+ * Currently unused.
+ */
+ sac->sac_flags = 0;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.1 SCTP_ASSOC_CHANGE
+ *
+ * sac_length: sizeof (__u32)
+ * This field is the total length of the notification data, including
+ * the notification header.
+ */
+ sac->sac_length = sizeof(struct sctp_assoc_change);
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.1 SCTP_ASSOC_CHANGE
+ *
+ * sac_error: 32 bits (signed integer)
+ *
+ * If the state was reached due to a error condition (e.g.
+ * COMMUNICATION_LOST) any relevant error information is available in
+ * this field. This corresponds to the protocol error codes defined in
+ * [SCTP].
+ */
+ sac->sac_error = error;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.1 SCTP_ASSOC_CHANGE
+ *
+ * sac_outbound_streams: 16 bits (unsigned integer)
+ * sac_inbound_streams: 16 bits (unsigned integer)
+ *
+ * The maximum number of streams allowed in each direction are
+ * available in sac_outbound_streams and sac_inbound streams.
+ */
+ sac->sac_outbound_streams = outbound;
+ sac->sac_inbound_streams = inbound;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.1 SCTP_ASSOC_CHANGE
+ *
+ * sac_assoc_id: sizeof (sctp_assoc_t)
+ *
+ * The association id field, holds the identifier for the association.
+ * All notifications for a given association have the same association
+ * identifier. For TCP style socket, this field is ignored.
+ */
+ sctp_ulpevent_set_owner(event, asoc);
+ sac->sac_assoc_id = sctp_assoc2id(asoc);
+
+ return event;
+
+fail:
+ return NULL;
+}
+
+/* Create and initialize an SCTP_PEER_ADDR_CHANGE event.
+ *
+ * Socket Extensions for SCTP - draft-01
+ * 5.3.1.2 SCTP_PEER_ADDR_CHANGE
+ *
+ * When a destination address on a multi-homed peer encounters a change
+ * an interface details event is sent.
+ */
+struct sctp_ulpevent *sctp_ulpevent_make_peer_addr_change(
+ const struct sctp_association *asoc,
+ const struct sockaddr_storage *aaddr,
+ int flags, int state, int error, int gfp)
+{
+ struct sctp_ulpevent *event;
+ struct sctp_paddr_change *spc;
+ struct sk_buff *skb;
+
+ event = sctp_ulpevent_new(sizeof(struct sctp_paddr_change),
+ MSG_NOTIFICATION, gfp);
+ if (!event)
+ goto fail;
+
+ skb = sctp_event2skb(event);
+ spc = (struct sctp_paddr_change *)
+ skb_put(skb, sizeof(struct sctp_paddr_change));
+
+ /* Sockets API Extensions for SCTP
+ * Section 5.3.1.2 SCTP_PEER_ADDR_CHANGE
+ *
+ * spc_type:
+ *
+ * It should be SCTP_PEER_ADDR_CHANGE.
+ */
+ spc->spc_type = SCTP_PEER_ADDR_CHANGE;
+
+ /* Sockets API Extensions for SCTP
+ * Section 5.3.1.2 SCTP_PEER_ADDR_CHANGE
+ *
+ * spc_length: sizeof (__u32)
+ *
+ * This field is the total length of the notification data, including
+ * the notification header.
+ */
+ spc->spc_length = sizeof(struct sctp_paddr_change);
+
+ /* Sockets API Extensions for SCTP
+ * Section 5.3.1.2 SCTP_PEER_ADDR_CHANGE
+ *
+ * spc_flags: 16 bits (unsigned integer)
+ * Currently unused.
+ */
+ spc->spc_flags = 0;
+
+ /* Sockets API Extensions for SCTP
+ * Section 5.3.1.2 SCTP_PEER_ADDR_CHANGE
+ *
+ * spc_state: 32 bits (signed integer)
+ *
+ * This field holds one of a number of values that communicate the
+ * event that happened to the address.
+ */
+ spc->spc_state = state;
+
+ /* Sockets API Extensions for SCTP
+ * Section 5.3.1.2 SCTP_PEER_ADDR_CHANGE
+ *
+ * spc_error: 32 bits (signed integer)
+ *
+ * If the state was reached due to any error condition (e.g.
+ * ADDRESS_UNREACHABLE) any relevant error information is available in
+ * this field.
+ */
+ spc->spc_error = error;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.1 SCTP_ASSOC_CHANGE
+ *
+ * spc_assoc_id: sizeof (sctp_assoc_t)
+ *
+ * The association id field, holds the identifier for the association.
+ * All notifications for a given association have the same association
+ * identifier. For TCP style socket, this field is ignored.
+ */
+ sctp_ulpevent_set_owner(event, asoc);
+ spc->spc_assoc_id = sctp_assoc2id(asoc);
+
+ /* Sockets API Extensions for SCTP
+ * Section 5.3.1.2 SCTP_PEER_ADDR_CHANGE
+ *
+ * spc_aaddr: sizeof (struct sockaddr_storage)
+ *
+ * The affected address field, holds the remote peer's address that is
+ * encountering the change of state.
+ */
+ memcpy(&spc->spc_aaddr, aaddr, sizeof(struct sockaddr_storage));
+
+ /* Map ipv4 address into v4-mapped-on-v6 address. */
+ sctp_get_pf_specific(asoc->base.sk->family)->addr_v4map(
+ sctp_sk(asoc->base.sk),
+ (union sctp_addr *)&spc->spc_aaddr);
+
+ return event;
+
+fail:
+ return NULL;
+}
+
+/* Create and initialize an SCTP_REMOTE_ERROR notification.
+ *
+ * Note: This assumes that the chunk->skb->data already points to the
+ * operation error payload.
+ *
+ * Socket Extensions for SCTP - draft-01
+ * 5.3.1.3 SCTP_REMOTE_ERROR
+ *
+ * A remote peer may send an Operational Error message to its peer.
+ * This message indicates a variety of error conditions on an
+ * association. The entire error TLV as it appears on the wire is
+ * included in a SCTP_REMOTE_ERROR event. Please refer to the SCTP
+ * specification [SCTP] and any extensions for a list of possible
+ * error formats.
+ */
+struct sctp_ulpevent *sctp_ulpevent_make_remote_error(
+ const struct sctp_association *asoc, struct sctp_chunk *chunk,
+ __u16 flags, int gfp)
+{
+ struct sctp_ulpevent *event;
+ struct sctp_remote_error *sre;
+ struct sk_buff *skb;
+ sctp_errhdr_t *ch;
+ __u16 cause;
+ int elen;
+
+ ch = (sctp_errhdr_t *)(chunk->skb->data);
+ cause = ch->cause;
+ elen = WORD_ROUND(ntohs(ch->length)) - sizeof(sctp_errhdr_t);
+
+ /* Pull off the ERROR header. */
+ skb_pull(chunk->skb, sizeof(sctp_errhdr_t));
+
+ /* Copy the skb to a new skb with room for us to prepend
+ * notification with.
+ */
+ skb = skb_copy_expand(chunk->skb, sizeof(struct sctp_remote_error),
+ 0, gfp);
+
+ /* Pull off the rest of the cause TLV from the chunk. */
+ skb_pull(chunk->skb, elen);
+ if (!skb)
+ goto fail;
+
+ /* Embed the event fields inside the cloned skb. */
+ event = sctp_skb2event(skb);
+ sctp_ulpevent_init(event, MSG_NOTIFICATION);
+
+ sre = (struct sctp_remote_error *)
+ skb_push(skb, sizeof(struct sctp_remote_error));
+
+ /* Trim the buffer to the right length. */
+ skb_trim(skb, sizeof(struct sctp_remote_error) + elen);
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.3 SCTP_REMOTE_ERROR
+ *
+ * sre_type:
+ * It should be SCTP_REMOTE_ERROR.
+ */
+ sre->sre_type = SCTP_REMOTE_ERROR;
+
+ /*
+ * Socket Extensions for SCTP
+ * 5.3.1.3 SCTP_REMOTE_ERROR
+ *
+ * sre_flags: 16 bits (unsigned integer)
+ * Currently unused.
+ */
+ sre->sre_flags = 0;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.3 SCTP_REMOTE_ERROR
+ *
+ * sre_length: sizeof (__u32)
+ *
+ * This field is the total length of the notification data,
+ * including the notification header.
+ */
+ sre->sre_length = skb->len;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.3 SCTP_REMOTE_ERROR
+ *
+ * sre_error: 16 bits (unsigned integer)
+ * This value represents one of the Operational Error causes defined in
+ * the SCTP specification, in network byte order.
+ */
+ sre->sre_error = cause;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.3 SCTP_REMOTE_ERROR
+ *
+ * sre_assoc_id: sizeof (sctp_assoc_t)
+ *
+ * The association id field, holds the identifier for the association.
+ * All notifications for a given association have the same association
+ * identifier. For TCP style socket, this field is ignored.
+ */
+ sctp_ulpevent_set_owner(event, asoc);
+ sre->sre_assoc_id = sctp_assoc2id(asoc);
+
+ return event;
+
+fail:
+ return NULL;
+}
+
+/* Create and initialize a SCTP_SEND_FAILED notification.
+ *
+ * Socket Extensions for SCTP - draft-01
+ * 5.3.1.4 SCTP_SEND_FAILED
+ */
+struct sctp_ulpevent *sctp_ulpevent_make_send_failed(
+ const struct sctp_association *asoc, struct sctp_chunk *chunk,
+ __u16 flags, __u32 error, int gfp)
+{
+ struct sctp_ulpevent *event;
+ struct sctp_send_failed *ssf;
+ struct sk_buff *skb;
+
+ /* Pull off any padding. */
+ int len = ntohs(chunk->chunk_hdr->length);
+
+ /* Make skb with more room so we can prepend notification. */
+ skb = skb_copy_expand(chunk->skb,
+ sizeof(struct sctp_send_failed), /* headroom */
+ 0, /* tailroom */
+ gfp);
+ if (!skb)
+ goto fail;
+
+ /* Pull off the common chunk header and DATA header. */
+ skb_pull(skb, sizeof(struct sctp_data_chunk));
+ len -= sizeof(struct sctp_data_chunk);
+
+ /* Embed the event fields inside the cloned skb. */
+ event = sctp_skb2event(skb);
+ sctp_ulpevent_init(event, MSG_NOTIFICATION);
+
+ ssf = (struct sctp_send_failed *)
+ skb_push(skb, sizeof(struct sctp_send_failed));
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.4 SCTP_SEND_FAILED
+ *
+ * ssf_type:
+ * It should be SCTP_SEND_FAILED.
+ */
+ ssf->ssf_type = SCTP_SEND_FAILED;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.4 SCTP_SEND_FAILED
+ *
+ * ssf_flags: 16 bits (unsigned integer)
+ * The flag value will take one of the following values
+ *
+ * SCTP_DATA_UNSENT - Indicates that the data was never put on
+ * the wire.
+ *
+ * SCTP_DATA_SENT - Indicates that the data was put on the wire.
+ * Note that this does not necessarily mean that the
+ * data was (or was not) successfully delivered.
+ */
+ ssf->ssf_flags = flags;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.4 SCTP_SEND_FAILED
+ *
+ * ssf_length: sizeof (__u32)
+ * This field is the total length of the notification data, including
+ * the notification header.
+ */
+ ssf->ssf_length = sizeof(struct sctp_send_failed) + len;
+ skb_trim(skb, ssf->ssf_length);
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.4 SCTP_SEND_FAILED
+ *
+ * ssf_error: 16 bits (unsigned integer)
+ * This value represents the reason why the send failed, and if set,
+ * will be a SCTP protocol error code as defined in [SCTP] section
+ * 3.3.10.
+ */
+ ssf->ssf_error = error;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.4 SCTP_SEND_FAILED
+ *
+ * ssf_info: sizeof (struct sctp_sndrcvinfo)
+ * The original send information associated with the undelivered
+ * message.
+ */
+ memcpy(&ssf->ssf_info, &chunk->sinfo, sizeof(struct sctp_sndrcvinfo));
+
+ /* Per TSVWG discussion with Randy. Allow the application to
+ * ressemble a fragmented message.
+ */
+ ssf->ssf_info.sinfo_flags = chunk->chunk_hdr->flags;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.4 SCTP_SEND_FAILED
+ *
+ * ssf_assoc_id: sizeof (sctp_assoc_t)
+ * The association id field, sf_assoc_id, holds the identifier for the
+ * association. All notifications for a given association have the
+ * same association identifier. For TCP style socket, this field is
+ * ignored.
+ */
+ sctp_ulpevent_set_owner(event, asoc);
+ ssf->ssf_assoc_id = sctp_assoc2id(asoc);
+ return event;
+
+fail:
+ return NULL;
+}
+
+/* Create and initialize a SCTP_SHUTDOWN_EVENT notification.
+ *
+ * Socket Extensions for SCTP - draft-01
+ * 5.3.1.5 SCTP_SHUTDOWN_EVENT
+ */
+struct sctp_ulpevent *sctp_ulpevent_make_shutdown_event(
+ const struct sctp_association *asoc,
+ __u16 flags, int gfp)
+{
+ struct sctp_ulpevent *event;
+ struct sctp_shutdown_event *sse;
+ struct sk_buff *skb;
+
+ event = sctp_ulpevent_new(sizeof(struct sctp_shutdown_event),
+ MSG_NOTIFICATION, gfp);
+ if (!event)
+ goto fail;
+
+ skb = sctp_event2skb(event);
+ sse = (struct sctp_shutdown_event *)
+ skb_put(skb, sizeof(struct sctp_shutdown_event));
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.5 SCTP_SHUTDOWN_EVENT
+ *
+ * sse_type
+ * It should be SCTP_SHUTDOWN_EVENT
+ */
+ sse->sse_type = SCTP_SHUTDOWN_EVENT;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.5 SCTP_SHUTDOWN_EVENT
+ *
+ * sse_flags: 16 bits (unsigned integer)
+ * Currently unused.
+ */
+ sse->sse_flags = 0;
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.5 SCTP_SHUTDOWN_EVENT
+ *
+ * sse_length: sizeof (__u32)
+ * This field is the total length of the notification data, including
+ * the notification header.
+ */
+ sse->sse_length = sizeof(struct sctp_shutdown_event);
+
+ /* Socket Extensions for SCTP
+ * 5.3.1.5 SCTP_SHUTDOWN_EVENT
+ *
+ * sse_assoc_id: sizeof (sctp_assoc_t)
+ * The association id field, holds the identifier for the association.
+ * All notifications for a given association have the same association
+ * identifier. For TCP style socket, this field is ignored.
+ */
+ sctp_ulpevent_set_owner(event, asoc);
+ sse->sse_assoc_id = sctp_assoc2id(asoc);
+
+ return event;
+
+fail:
+ return NULL;
+}
+
+/* Create and initialize a SCTP_ADAPTION_INDICATION notification.
+ *
+ * Socket Extensions for SCTP
+ * 5.3.1.6 SCTP_ADAPTION_INDICATION
+ */
+struct sctp_ulpevent *sctp_ulpevent_make_adaption_indication(
+ const struct sctp_association *asoc, int gfp)
+{
+ struct sctp_ulpevent *event;
+ struct sctp_adaption_event *sai;
+ struct sk_buff *skb;
+
+ event = sctp_ulpevent_new(sizeof(struct sctp_adaption_event),
+ MSG_NOTIFICATION, gfp);
+ if (!event)
+ goto fail;
+
+ skb = sctp_event2skb(event);
+ sai = (struct sctp_adaption_event *)
+ skb_put(skb, sizeof(struct sctp_adaption_event));
+
+ sai->sai_type = SCTP_ADAPTION_INDICATION;
+ sai->sai_flags = 0;
+ sai->sai_length = sizeof(struct sctp_adaption_event);
+ sai->sai_adaption_ind = asoc->peer.adaption_ind;
+ sctp_ulpevent_set_owner(event, asoc);
+ sai->sai_assoc_id = sctp_assoc2id(asoc);
+
+ return event;
+
+fail:
+ return NULL;
+}
+
+/* A message has been received. Package this message as a notification
+ * to pass it to the upper layers. Go ahead and calculate the sndrcvinfo
+ * even if filtered out later.
+ *
+ * Socket Extensions for SCTP
+ * 5.2.2 SCTP Header Information Structure (SCTP_SNDRCV)
+ */
+struct sctp_ulpevent *sctp_ulpevent_make_rcvmsg(struct sctp_association *asoc,
+ struct sctp_chunk *chunk,
+ int gfp)
+{
+ struct sctp_ulpevent *event = NULL;
+ struct sk_buff *skb;
+ size_t padding, len;
+
+ /* Clone the original skb, sharing the data. */
+ skb = skb_clone(chunk->skb, gfp);
+ if (!skb)
+ goto fail;
+
+ /* First calculate the padding, so we don't inadvertently
+ * pass up the wrong length to the user.
+ *
+ * RFC 2960 - Section 3.2 Chunk Field Descriptions
+ *
+ * The total length of a chunk(including Type, Length and Value fields)
+ * MUST be a multiple of 4 bytes. If the length of the chunk is not a
+ * multiple of 4 bytes, the sender MUST pad the chunk with all zero
+ * bytes and this padding is not included in the chunk length field.
+ * The sender should never pad with more than 3 bytes. The receiver
+ * MUST ignore the padding bytes.
+ */
+ len = ntohs(chunk->chunk_hdr->length);
+ padding = WORD_ROUND(len) - len;
+
+ /* Fixup cloned skb with just this chunks data. */
+ skb_trim(skb, chunk->chunk_end - padding - skb->data);
+
+ /* Embed the event fields inside the cloned skb. */
+ event = sctp_skb2event(skb);
+
+ /* Initialize event with flags 0. */
+ sctp_ulpevent_init(event, 0);
+
+ sctp_ulpevent_receive_data(event, asoc);
+
+ event->stream = ntohs(chunk->subh.data_hdr->stream);
+ event->ssn = ntohs(chunk->subh.data_hdr->ssn);
+ event->ppid = chunk->subh.data_hdr->ppid;
+ if (chunk->chunk_hdr->flags & SCTP_DATA_UNORDERED) {
+ event->flags |= MSG_UNORDERED;
+ event->cumtsn = sctp_tsnmap_get_ctsn(&asoc->peer.tsn_map);
+ }
+ event->tsn = ntohl(chunk->subh.data_hdr->tsn);
+ event->msg_flags |= chunk->chunk_hdr->flags;
+ event->iif = sctp_chunk_iif(chunk);
+
+fail:
+ return event;
+}
+
+/* Create a partial delivery related event.
+ *
+ * 5.3.1.7 SCTP_PARTIAL_DELIVERY_EVENT
+ *
+ * When a reciever is engaged in a partial delivery of a
+ * message this notification will be used to inidicate
+ * various events.
+ */
+struct sctp_ulpevent *sctp_ulpevent_make_pdapi(
+ const struct sctp_association *asoc, __u32 indication, int gfp)
+{
+ struct sctp_ulpevent *event;
+ struct sctp_pdapi_event *pd;
+ struct sk_buff *skb;
+
+ event = sctp_ulpevent_new(sizeof(struct sctp_pdapi_event),
+ MSG_NOTIFICATION, gfp);
+ if (!event)
+ goto fail;
+
+ skb = sctp_event2skb(event);
+ pd = (struct sctp_pdapi_event *)
+ skb_put(skb, sizeof(struct sctp_pdapi_event));
+
+ /* pdapi_type
+ * It should be SCTP_PARTIAL_DELIVERY_EVENT
+ *
+ * pdapi_flags: 16 bits (unsigned integer)
+ * Currently unused.
+ */
+ pd->pdapi_type = SCTP_PARTIAL_DELIVERY_EVENT;
+ pd->pdapi_flags = 0;
+
+ /* pdapi_length: 32 bits (unsigned integer)
+ *
+ * This field is the total length of the notification data, including
+ * the notification header. It will generally be sizeof (struct
+ * sctp_pdapi_event).
+ */
+ pd->pdapi_length = sizeof(struct sctp_pdapi_event);
+
+ /* pdapi_indication: 32 bits (unsigned integer)
+ *
+ * This field holds the indication being sent to the application.
+ */
+ pd->pdapi_indication = indication;
+
+ /* pdapi_assoc_id: sizeof (sctp_assoc_t)
+ *
+ * The association id field, holds the identifier for the association.
+ */
+ sctp_ulpevent_set_owner(event, asoc);
+ pd->pdapi_assoc_id = sctp_assoc2id(asoc);
+
+ return event;
+fail:
+ return NULL;
+}
+
+/* Return the notification type, assuming this is a notification
+ * event.
+ */
+__u16 sctp_ulpevent_get_notification_type(const struct sctp_ulpevent *event)
+{
+ union sctp_notification *notification;
+ struct sk_buff *skb;
+
+ skb = sctp_event2skb((struct sctp_ulpevent *)event);
+ notification = (union sctp_notification *) skb->data;
+ return notification->sn_header.sn_type;
+}
+
+/* Copy out the sndrcvinfo into a msghdr. */
+void sctp_ulpevent_read_sndrcvinfo(const struct sctp_ulpevent *event,
+ struct msghdr *msghdr)
+{
+ struct sctp_sndrcvinfo sinfo;
+
+ if (sctp_ulpevent_is_notification(event))
+ return;
+
+ /* Sockets API Extensions for SCTP
+ * Section 5.2.2 SCTP Header Information Structure (SCTP_SNDRCV)
+ *
+ * sinfo_stream: 16 bits (unsigned integer)
+ *
+ * For recvmsg() the SCTP stack places the message's stream number in
+ * this value.
+ */
+ sinfo.sinfo_stream = event->stream;
+ /* sinfo_ssn: 16 bits (unsigned integer)
+ *
+ * For recvmsg() this value contains the stream sequence number that
+ * the remote endpoint placed in the DATA chunk. For fragmented
+ * messages this is the same number for all deliveries of the message
+ * (if more than one recvmsg() is needed to read the message).
+ */
+ sinfo.sinfo_ssn = event->ssn;
+ /* sinfo_ppid: 32 bits (unsigned integer)
+ *
+ * In recvmsg() this value is
+ * the same information that was passed by the upper layer in the peer
+ * application. Please note that byte order issues are NOT accounted
+ * for and this information is passed opaquely by the SCTP stack from
+ * one end to the other.
+ */
+ sinfo.sinfo_ppid = event->ppid;
+ /* sinfo_flags: 16 bits (unsigned integer)
+ *
+ * This field may contain any of the following flags and is composed of
+ * a bitwise OR of these values.
+ *
+ * recvmsg() flags:
+ *
+ * MSG_UNORDERED - This flag is present when the message was sent
+ * non-ordered.
+ */
+ sinfo.sinfo_flags = event->flags;
+ /* sinfo_tsn: 32 bit (unsigned integer)
+ *
+ * For the receiving side, this field holds a TSN that was
+ * assigned to one of the SCTP Data Chunks.
+ */
+ sinfo.sinfo_tsn = event->tsn;
+ /* sinfo_cumtsn: 32 bit (unsigned integer)
+ *
+ * This field will hold the current cumulative TSN as
+ * known by the underlying SCTP layer. Note this field is
+ * ignored when sending and only valid for a receive
+ * operation when sinfo_flags are set to MSG_UNORDERED.
+ */
+ sinfo.sinfo_cumtsn = event->cumtsn;
+ /* sinfo_assoc_id: sizeof (sctp_assoc_t)
+ *
+ * The association handle field, sinfo_assoc_id, holds the identifier
+ * for the association announced in the COMMUNICATION_UP notification.
+ * All notifications for a given association have the same identifier.
+ * Ignored for one-to-one style sockets.
+ */
+ sinfo.sinfo_assoc_id = sctp_assoc2id(event->asoc);
+
+ /* These fields are not used while receiving. */
+ sinfo.sinfo_context = 0;
+ sinfo.sinfo_timetolive = 0;
+
+ put_cmsg(msghdr, IPPROTO_SCTP, SCTP_SNDRCV,
+ sizeof(struct sctp_sndrcvinfo), (void *)&sinfo);
+}
+
+/* Do accounting for bytes received and hold a reference to the association
+ * for each skb.
+ */
+static void sctp_ulpevent_receive_data(struct sctp_ulpevent *event,
+ struct sctp_association *asoc)
+{
+ struct sk_buff *skb, *frag;
+
+ skb = sctp_event2skb(event);
+ /* Set the owner and charge rwnd for bytes received. */
+ sctp_ulpevent_set_owner(event, asoc);
+ sctp_assoc_rwnd_decrease(asoc, skb_headlen(skb));
+
+ if (!skb->data_len)
+ return;
+
+ /* Note: Not clearing the entire event struct as this is just a
+ * fragment of the real event. However, we still need to do rwnd
+ * accounting.
+ * In general, the skb passed from IP can have only 1 level of
+ * fragments. But we allow multiple levels of fragments.
+ */
+ for (frag = skb_shinfo(skb)->frag_list; frag; frag = frag->next) {
+ sctp_ulpevent_receive_data(sctp_skb2event(frag), asoc);
+ }
+}
+
+/* Do accounting for bytes just read by user and release the references to
+ * the association.
+ */
+static void sctp_ulpevent_release_data(struct sctp_ulpevent *event)
+{
+ struct sk_buff *skb, *frag;
+
+ /* Current stack structures assume that the rcv buffer is
+ * per socket. For UDP style sockets this is not true as
+ * multiple associations may be on a single UDP-style socket.
+ * Use the local private area of the skb to track the owning
+ * association.
+ */
+
+ skb = sctp_event2skb(event);
+ sctp_assoc_rwnd_increase(event->asoc, skb_headlen(skb));
+
+ if (!skb->data_len)
+ goto done;
+
+ /* Don't forget the fragments. */
+ for (frag = skb_shinfo(skb)->frag_list; frag; frag = frag->next) {
+ /* NOTE: skb_shinfos are recursive. Although IP returns
+ * skb's with only 1 level of fragments, SCTP reassembly can
+ * increase the levels.
+ */
+ sctp_ulpevent_release_data(sctp_skb2event(frag));
+ }
+
+done:
+ sctp_ulpevent_release_owner(event);
+}
+
+/* Free a ulpevent that has an owner. It includes releasing the reference
+ * to the owner, updating the rwnd in case of a DATA event and freeing the
+ * skb.
+ * See comments in sctp_stub_rfree().
+ */
+void sctp_ulpevent_free(struct sctp_ulpevent *event)
+{
+ if (sctp_ulpevent_is_notification(event))
+ sctp_ulpevent_release_owner(event);
+ else
+ sctp_ulpevent_release_data(event);
+
+ kfree_skb(sctp_event2skb(event));
+}
+
+/* Purge the skb lists holding ulpevents. */
+void sctp_queue_purge_ulpevents(struct sk_buff_head *list)
+{
+ struct sk_buff *skb;
+ while ((skb = skb_dequeue(list)) != NULL)
+ sctp_ulpevent_free(sctp_skb2event(skb));
+}
diff --git a/uClinux-2.4.31-uc0/net/sctp/ulpqueue.c b/uClinux-2.4.31-uc0/net/sctp/ulpqueue.c
new file mode 100644
index 0000000..28f41f8
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sctp/ulpqueue.c
@@ -0,0 +1,865 @@
+/* SCTP kernel reference Implementation
+ * (C) Copyright IBM Corp. 2001, 2004
+ * Copyright (c) 1999-2000 Cisco, Inc.
+ * Copyright (c) 1999-2001 Motorola, Inc.
+ * Copyright (c) 2001 Intel Corp.
+ * Copyright (c) 2001 Nokia, Inc.
+ * Copyright (c) 2001 La Monte H.P. Yarroll
+ *
+ * This abstraction carries sctp events to the ULP (sockets).
+ *
+ * The SCTP reference implementation is free software;
+ * you can redistribute it and/or modify it under the terms of
+ * the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * The SCTP reference implementation is distributed in the hope that it
+ * will be useful, but WITHOUT ANY WARRANTY; without even the implied
+ * ************************
+ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GNU CC; see the file COPYING. If not, write to
+ * the Free Software Foundation, 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Please send any bug reports or fixes you make to the
+ * email address(es):
+ * lksctp developers <lksctp-developers@lists.sourceforge.net>
+ *
+ * Or submit a bug report through the following website:
+ * http://www.sf.net/projects/lksctp
+ *
+ * Written or modified by:
+ * Jon Grimm <jgrimm@us.ibm.com>
+ * La Monte H.P. Yarroll <piggy@acm.org>
+ * Sridhar Samudrala <sri@us.ibm.com>
+ *
+ * Any bugs reported given to us we will try to fix... any fixes shared will
+ * be incorporated into the next SCTP release.
+ */
+
+#include <linux/types.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/sctp/structs.h>
+#include <net/sctp/sctp.h>
+#include <net/sctp/sm.h>
+
+/* Forward declarations for internal helpers. */
+static struct sctp_ulpevent * sctp_ulpq_reasm(struct sctp_ulpq *ulpq,
+ struct sctp_ulpevent *);
+static struct sctp_ulpevent * sctp_ulpq_order(struct sctp_ulpq *,
+ struct sctp_ulpevent *);
+
+/* 1st Level Abstractions */
+
+/* Initialize a ULP queue from a block of memory. */
+struct sctp_ulpq *sctp_ulpq_init(struct sctp_ulpq *ulpq,
+ struct sctp_association *asoc)
+{
+ memset(ulpq, 0, sizeof(struct sctp_ulpq));
+
+ ulpq->asoc = asoc;
+ skb_queue_head_init(&ulpq->reasm);
+ skb_queue_head_init(&ulpq->lobby);
+ ulpq->pd_mode = 0;
+ ulpq->malloced = 0;
+
+ return ulpq;
+}
+
+
+/* Flush the reassembly and ordering queues. */
+static void sctp_ulpq_flush(struct sctp_ulpq *ulpq)
+{
+ struct sk_buff *skb;
+ struct sctp_ulpevent *event;
+
+ while ((skb = __skb_dequeue(&ulpq->lobby)) != NULL) {
+ event = sctp_skb2event(skb);
+ sctp_ulpevent_free(event);
+ }
+
+ while ((skb = __skb_dequeue(&ulpq->reasm)) != NULL) {
+ event = sctp_skb2event(skb);
+ sctp_ulpevent_free(event);
+ }
+
+}
+
+/* Dispose of a ulpqueue. */
+void sctp_ulpq_free(struct sctp_ulpq *ulpq)
+{
+ sctp_ulpq_flush(ulpq);
+ if (ulpq->malloced)
+ kfree(ulpq);
+}
+
+/* Process an incoming DATA chunk. */
+int sctp_ulpq_tail_data(struct sctp_ulpq *ulpq, struct sctp_chunk *chunk,
+ int gfp)
+{
+ struct sk_buff_head temp;
+ sctp_data_chunk_t *hdr;
+ struct sctp_ulpevent *event;
+
+ hdr = (sctp_data_chunk_t *) chunk->chunk_hdr;
+
+ /* Create an event from the incoming chunk. */
+ event = sctp_ulpevent_make_rcvmsg(chunk->asoc, chunk, gfp);
+ if (!event)
+ return -ENOMEM;
+
+ /* Do reassembly if needed. */
+ event = sctp_ulpq_reasm(ulpq, event);
+
+ /* Do ordering if needed. */
+ if ((event) && (event->msg_flags & MSG_EOR)){
+ /* Create a temporary list to collect chunks on. */
+ skb_queue_head_init(&temp);
+ __skb_queue_tail(&temp, sctp_event2skb(event));
+
+ event = sctp_ulpq_order(ulpq, event);
+ }
+
+ /* Send event to the ULP. */
+ if (event)
+ sctp_ulpq_tail_event(ulpq, event);
+
+ return 0;
+}
+
+/* Add a new event for propagation to the ULP. */
+/* Clear the partial delivery mode for this socket. Note: This
+ * assumes that no association is currently in partial delivery mode.
+ */
+int sctp_clear_pd(struct sock *sk)
+{
+ struct sctp_opt *sp;
+ sp = sctp_sk(sk);
+
+ sp->pd_mode = 0;
+ if (!skb_queue_empty(&sp->pd_lobby)) {
+ struct list_head *list;
+ sctp_skb_list_tail(&sp->pd_lobby, &sk->receive_queue);
+ list = (struct list_head *)&sctp_sk(sk)->pd_lobby;
+ INIT_LIST_HEAD(list);
+ return 1;
+ }
+ return 0;
+}
+
+/* Clear the pd_mode and restart any pending messages waiting for delivery. */
+static int sctp_ulpq_clear_pd(struct sctp_ulpq *ulpq)
+{
+ ulpq->pd_mode = 0;
+ return sctp_clear_pd(ulpq->asoc->base.sk);
+}
+
+
+
+int sctp_ulpq_tail_event(struct sctp_ulpq *ulpq, struct sctp_ulpevent *event)
+{
+ struct sock *sk = ulpq->asoc->base.sk;
+ struct sk_buff_head *queue;
+ int clear_pd = 0;
+
+ /* If the socket is just going to throw this away, do not
+ * even try to deliver it.
+ */
+ if (sk->dead || (sk->shutdown & RCV_SHUTDOWN))
+ goto out_free;
+
+ /* Check if the user wishes to receive this event. */
+ if (!sctp_ulpevent_is_enabled(event, &sctp_sk(sk)->subscribe))
+ goto out_free;
+
+ /* If we are in partial delivery mode, post to the lobby until
+ * partial delivery is cleared, unless, of course _this_ is
+ * the association the cause of the partial delivery.
+ */
+
+ if (!sctp_sk(sk)->pd_mode) {
+ queue = &sk->receive_queue;
+ } else if (ulpq->pd_mode) {
+ if (event->msg_flags & MSG_NOTIFICATION)
+ queue = &sctp_sk(sk)->pd_lobby;
+ else {
+ clear_pd = event->msg_flags & MSG_EOR;
+ queue = &sk->receive_queue;
+ }
+ } else
+ queue = &sctp_sk(sk)->pd_lobby;
+
+
+ /* If we are harvesting multiple skbs they will be
+ * collected on a list.
+ */
+ if (sctp_event2skb(event)->list)
+ sctp_skb_list_tail(sctp_event2skb(event)->list, queue);
+ else
+ __skb_queue_tail(queue, sctp_event2skb(event));
+
+ /* Did we just complete partial delivery and need to get
+ * rolling again? Move pending data to the receive
+ * queue.
+ */
+ if (clear_pd)
+ sctp_ulpq_clear_pd(ulpq);
+
+ if (queue == &sk->receive_queue)
+ sk->data_ready(sk, 0);
+ return 1;
+
+out_free:
+ if (sctp_event2skb(event)->list)
+ sctp_queue_purge_ulpevents(sctp_event2skb(event)->list);
+ else
+ sctp_ulpevent_free(event);
+ return 0;
+}
+
+/* 2nd Level Abstractions */
+
+/* Helper function to store chunks that need to be reassembled. */
+static inline void sctp_ulpq_store_reasm(struct sctp_ulpq *ulpq,
+ struct sctp_ulpevent *event)
+{
+ struct sk_buff *pos;
+ struct sctp_ulpevent *cevent;
+ __u32 tsn, ctsn;
+
+ tsn = event->tsn;
+
+ /* See if it belongs at the end. */
+ pos = skb_peek_tail(&ulpq->reasm);
+ if (!pos) {
+ __skb_queue_tail(&ulpq->reasm, sctp_event2skb(event));
+ return;
+ }
+
+ /* Short circuit just dropping it at the end. */
+ cevent = sctp_skb2event(pos);
+ ctsn = cevent->tsn;
+ if (TSN_lt(ctsn, tsn)) {
+ __skb_queue_tail(&ulpq->reasm, sctp_event2skb(event));
+ return;
+ }
+
+ /* Find the right place in this list. We store them by TSN. */
+ skb_queue_walk(&ulpq->reasm, pos) {
+ cevent = sctp_skb2event(pos);
+ ctsn = cevent->tsn;
+
+ if (TSN_lt(tsn, ctsn))
+ break;
+ }
+
+ /* Insert before pos. */
+ __skb_insert(sctp_event2skb(event), pos->prev, pos, &ulpq->reasm);
+
+}
+
+/* Helper function to return an event corresponding to the reassembled
+ * datagram.
+ * This routine creates a re-assembled skb given the first and last skb's
+ * as stored in the reassembly queue. The skb's may be non-linear if the sctp
+ * payload was fragmented on the way and ip had to reassemble them.
+ * We add the rest of skb's to the first skb's fraglist.
+ */
+static struct sctp_ulpevent *sctp_make_reassembled_event(struct sk_buff *f_frag, struct sk_buff *l_frag)
+{
+ struct sk_buff *pos;
+ struct sctp_ulpevent *event;
+ struct sk_buff *pnext, *last;
+ struct sk_buff *list = skb_shinfo(f_frag)->frag_list;
+
+ /* Store the pointer to the 2nd skb */
+ if (f_frag == l_frag)
+ pos = NULL;
+ else
+ pos = f_frag->next;
+
+ /* Get the last skb in the f_frag's frag_list if present. */
+ for (last = list; list; last = list, list = list->next);
+
+ /* Add the list of remaining fragments to the first fragments
+ * frag_list.
+ */
+ if (last)
+ last->next = pos;
+ else
+ skb_shinfo(f_frag)->frag_list = pos;
+
+ /* Remove the first fragment from the reassembly queue. */
+ __skb_unlink(f_frag, f_frag->list);
+ while (pos) {
+
+ pnext = pos->next;
+
+ /* Update the len and data_len fields of the first fragment. */
+ f_frag->len += pos->len;
+ f_frag->data_len += pos->len;
+
+ /* Remove the fragment from the reassembly queue. */
+ __skb_unlink(pos, pos->list);
+
+ /* Break if we have reached the last fragment. */
+ if (pos == l_frag)
+ break;
+ pos->next = pnext;
+ pos = pnext;
+ };
+
+ event = sctp_skb2event(f_frag);
+ SCTP_INC_STATS(SctpReasmUsrMsgs);
+
+ return event;
+}
+
+
+/* Helper function to check if an incoming chunk has filled up the last
+ * missing fragment in a SCTP datagram and return the corresponding event.
+ */
+static inline struct sctp_ulpevent *sctp_ulpq_retrieve_reassembled(struct sctp_ulpq *ulpq)
+{
+ struct sk_buff *pos;
+ struct sctp_ulpevent *cevent;
+ struct sk_buff *first_frag = NULL;
+ __u32 ctsn, next_tsn;
+ struct sctp_ulpevent *retval = NULL;
+
+ /* Initialized to 0 just to avoid compiler warning message. Will
+ * never be used with this value. It is referenced only after it
+ * is set when we find the first fragment of a message.
+ */
+ next_tsn = 0;
+
+ /* The chunks are held in the reasm queue sorted by TSN.
+ * Walk through the queue sequentially and look for a sequence of
+ * fragmented chunks that complete a datagram.
+ * 'first_frag' and next_tsn are reset when we find a chunk which
+ * is the first fragment of a datagram. Once these 2 fields are set
+ * we expect to find the remaining middle fragments and the last
+ * fragment in order. If not, first_frag is reset to NULL and we
+ * start the next pass when we find another first fragment.
+ */
+ skb_queue_walk(&ulpq->reasm, pos) {
+ cevent = sctp_skb2event(pos);
+ ctsn = cevent->tsn;
+
+ switch (cevent->msg_flags & SCTP_DATA_FRAG_MASK) {
+ case SCTP_DATA_FIRST_FRAG:
+ first_frag = pos;
+ next_tsn = ctsn + 1;
+ break;
+
+ case SCTP_DATA_MIDDLE_FRAG:
+ if ((first_frag) && (ctsn == next_tsn))
+ next_tsn++;
+ else
+ first_frag = NULL;
+ break;
+
+ case SCTP_DATA_LAST_FRAG:
+ if (first_frag && (ctsn == next_tsn))
+ goto found;
+ else
+ first_frag = NULL;
+ break;
+ };
+
+ }
+done:
+ return retval;
+found:
+ retval = sctp_make_reassembled_event(first_frag, pos);
+ if (retval)
+ retval->msg_flags |= MSG_EOR;
+ goto done;
+}
+
+/* Retrieve the next set of fragments of a partial message. */
+static inline struct sctp_ulpevent *sctp_ulpq_retrieve_partial(struct sctp_ulpq *ulpq)
+{
+ struct sk_buff *pos, *last_frag, *first_frag;
+ struct sctp_ulpevent *cevent;
+ __u32 ctsn, next_tsn;
+ int is_last;
+ struct sctp_ulpevent *retval;
+
+ /* The chunks are held in the reasm queue sorted by TSN.
+ * Walk through the queue sequentially and look for the first
+ * sequence of fragmented chunks.
+ */
+
+ if (skb_queue_empty(&ulpq->reasm))
+ return NULL;
+
+ last_frag = first_frag = NULL;
+ retval = NULL;
+ next_tsn = 0;
+ is_last = 0;
+
+ skb_queue_walk(&ulpq->reasm, pos) {
+ cevent = sctp_skb2event(pos);
+ ctsn = cevent->tsn;
+
+ switch (cevent->msg_flags & SCTP_DATA_FRAG_MASK) {
+ case SCTP_DATA_MIDDLE_FRAG:
+ if (!first_frag) {
+ first_frag = pos;
+ next_tsn = ctsn + 1;
+ last_frag = pos;
+ } else if (next_tsn == ctsn)
+ next_tsn++;
+ else
+ goto done;
+ break;
+ case SCTP_DATA_LAST_FRAG:
+ if (!first_frag)
+ first_frag = pos;
+ else if (ctsn != next_tsn)
+ goto done;
+ last_frag = pos;
+ is_last = 1;
+ goto done;
+ default:
+ return NULL;
+ };
+ }
+
+ /* We have the reassembled event. There is no need to look
+ * further.
+ */
+done:
+ retval = sctp_make_reassembled_event(first_frag, last_frag);
+ if (retval && is_last)
+ retval->msg_flags |= MSG_EOR;
+
+ return retval;
+}
+
+
+/* Helper function to reassemble chunks. Hold chunks on the reasm queue that
+ * need reassembling.
+ */
+static struct sctp_ulpevent *sctp_ulpq_reasm(struct sctp_ulpq *ulpq,
+ struct sctp_ulpevent *event)
+{
+ struct sctp_ulpevent *retval = NULL;
+
+ /* Check if this is part of a fragmented message. */
+ if (SCTP_DATA_NOT_FRAG == (event->msg_flags & SCTP_DATA_FRAG_MASK)) {
+ event->msg_flags |= MSG_EOR;
+ return event;
+ }
+
+ sctp_ulpq_store_reasm(ulpq, event);
+ if (!ulpq->pd_mode)
+ retval = sctp_ulpq_retrieve_reassembled(ulpq);
+ else {
+ __u32 ctsn, ctsnap;
+
+ /* Do not even bother unless this is the next tsn to
+ * be delivered.
+ */
+ ctsn = event->tsn;
+ ctsnap = sctp_tsnmap_get_ctsn(&ulpq->asoc->peer.tsn_map);
+ if (TSN_lte(ctsn, ctsnap))
+ retval = sctp_ulpq_retrieve_partial(ulpq);
+ }
+
+ return retval;
+}
+
+/* Retrieve the first part (sequential fragments) for partial delivery. */
+static inline struct sctp_ulpevent *sctp_ulpq_retrieve_first(struct sctp_ulpq *ulpq)
+{
+ struct sk_buff *pos, *last_frag, *first_frag;
+ struct sctp_ulpevent *cevent;
+ __u32 ctsn, next_tsn;
+ struct sctp_ulpevent *retval;
+
+ /* The chunks are held in the reasm queue sorted by TSN.
+ * Walk through the queue sequentially and look for a sequence of
+ * fragmented chunks that start a datagram.
+ */
+
+ if (skb_queue_empty(&ulpq->reasm))
+ return NULL;
+
+ last_frag = first_frag = NULL;
+ retval = NULL;
+ next_tsn = 0;
+
+ skb_queue_walk(&ulpq->reasm, pos) {
+ cevent = sctp_skb2event(pos);
+ ctsn = cevent->tsn;
+
+ switch (cevent->msg_flags & SCTP_DATA_FRAG_MASK) {
+ case SCTP_DATA_FIRST_FRAG:
+ if (!first_frag) {
+ first_frag = pos;
+ next_tsn = ctsn + 1;
+ last_frag = pos;
+ } else
+ goto done;
+ break;
+
+ case SCTP_DATA_MIDDLE_FRAG:
+ if (!first_frag)
+ return NULL;
+ if (ctsn == next_tsn) {
+ next_tsn++;
+ last_frag = pos;
+ } else
+ goto done;
+ break;
+ default:
+ return NULL;
+ };
+ }
+
+ /* We have the reassembled event. There is no need to look
+ * further.
+ */
+done:
+ retval = sctp_make_reassembled_event(first_frag, last_frag);
+ return retval;
+}
+
+/* Helper function to gather skbs that have possibly become
+ * ordered by an an incoming chunk.
+ */
+static inline void sctp_ulpq_retrieve_ordered(struct sctp_ulpq *ulpq,
+ struct sctp_ulpevent *event)
+{
+ struct sk_buff *pos, *tmp;
+ struct sctp_ulpevent *cevent;
+ struct sctp_stream *in;
+ __u16 sid, csid;
+ __u16 ssn, cssn;
+
+ sid = event->stream;
+ ssn = event->ssn;
+ in = &ulpq->asoc->ssnmap->in;
+
+ /* We are holding the chunks by stream, by SSN. */
+ sctp_skb_for_each(pos, &ulpq->lobby, tmp) {
+ cevent = (struct sctp_ulpevent *) pos->cb;
+ csid = cevent->stream;
+ cssn = cevent->ssn;
+
+ /* Have we gone too far? */
+ if (csid > sid)
+ break;
+
+ /* Have we not gone far enough? */
+ if (csid < sid)
+ continue;
+
+ if (cssn != sctp_ssn_peek(in, sid))
+ break;
+
+ /* Found it, so mark in the ssnmap. */
+ sctp_ssn_next(in, sid);
+
+ __skb_unlink(pos, pos->list);
+
+ /* Attach all gathered skbs to the event. */
+ __skb_queue_tail(sctp_event2skb(event)->list, pos);
+ }
+}
+
+/* Helper function to store chunks needing ordering. */
+static inline void sctp_ulpq_store_ordered(struct sctp_ulpq *ulpq,
+ struct sctp_ulpevent *event)
+{
+ struct sk_buff *pos;
+ struct sctp_ulpevent *cevent;
+ __u16 sid, csid;
+ __u16 ssn, cssn;
+
+ pos = skb_peek_tail(&ulpq->lobby);
+ if (!pos) {
+ __skb_queue_tail(&ulpq->lobby, sctp_event2skb(event));
+ return;
+ }
+
+ sid = event->stream;
+ ssn = event->ssn;
+
+ cevent = (struct sctp_ulpevent *) pos->cb;
+ csid = cevent->stream;
+ cssn = cevent->ssn;
+ if (sid > csid) {
+ __skb_queue_tail(&ulpq->lobby, sctp_event2skb(event));
+ return;
+ }
+
+ if ((sid == csid) && SSN_lt(cssn, ssn)) {
+ __skb_queue_tail(&ulpq->lobby, sctp_event2skb(event));
+ return;
+ }
+
+ /* Find the right place in this list. We store them by
+ * stream ID and then by SSN.
+ */
+ skb_queue_walk(&ulpq->lobby, pos) {
+ cevent = (struct sctp_ulpevent *) pos->cb;
+ csid = cevent->stream;
+ cssn = cevent->ssn;
+
+ if (csid > sid)
+ break;
+ if (csid == sid && SSN_lt(ssn, cssn))
+ break;
+ }
+
+
+ /* Insert before pos. */
+ __skb_insert(sctp_event2skb(event), pos->prev, pos, &ulpq->lobby);
+
+}
+
+static struct sctp_ulpevent *sctp_ulpq_order(struct sctp_ulpq *ulpq,
+ struct sctp_ulpevent *event)
+{
+ __u16 sid, ssn;
+ struct sctp_stream *in;
+
+ /* Check if this message needs ordering. */
+ if (SCTP_DATA_UNORDERED & event->msg_flags)
+ return event;
+
+ /* Note: The stream ID must be verified before this routine. */
+ sid = event->stream;
+ ssn = event->ssn;
+ in = &ulpq->asoc->ssnmap->in;
+
+ /* Is this the expected SSN for this stream ID? */
+ if (ssn != sctp_ssn_peek(in, sid)) {
+ /* We've received something out of order, so find where it
+ * needs to be placed. We order by stream and then by SSN.
+ */
+ sctp_ulpq_store_ordered(ulpq, event);
+ return NULL;
+ }
+
+ /* Mark that the next chunk has been found. */
+ sctp_ssn_next(in, sid);
+
+ /* Go find any other chunks that were waiting for
+ * ordering.
+ */
+ sctp_ulpq_retrieve_ordered(ulpq, event);
+
+ return event;
+}
+
+/* Helper function to gather skbs that have possibly become
+ * ordered by forward tsn skipping their dependencies.
+ */
+static inline void sctp_ulpq_reap_ordered(struct sctp_ulpq *ulpq)
+{
+ struct sk_buff *pos, *tmp;
+ struct sctp_ulpevent *cevent;
+ struct sctp_ulpevent *event = NULL;
+ struct sctp_stream *in;
+ struct sk_buff_head temp;
+ __u16 csid, cssn;
+
+ in = &ulpq->asoc->ssnmap->in;
+
+ /* We are holding the chunks by stream, by SSN. */
+ sctp_skb_for_each(pos, &ulpq->lobby, tmp) {
+ cevent = (struct sctp_ulpevent *) pos->cb;
+ csid = cevent->stream;
+ cssn = cevent->ssn;
+
+ if (cssn != sctp_ssn_peek(in, csid))
+ break;
+
+ /* Found it, so mark in the ssnmap. */
+ sctp_ssn_next(in, csid);
+
+ __skb_unlink(pos, pos->list);
+ if (!event) {
+ /* Create a temporary list to collect chunks on. */
+ event = sctp_skb2event(pos);
+ skb_queue_head_init(&temp);
+ __skb_queue_tail(&temp, sctp_event2skb(event));
+ } else {
+ /* Attach all gathered skbs to the event. */
+ __skb_queue_tail(sctp_event2skb(event)->list, pos);
+ }
+ }
+
+ /* Send event to the ULP. */
+ if (event)
+ sctp_ulpq_tail_event(ulpq, event);
+}
+
+/* Skip over an SSN. */
+void sctp_ulpq_skip(struct sctp_ulpq *ulpq, __u16 sid, __u16 ssn)
+{
+ struct sctp_stream *in;
+
+ /* Note: The stream ID must be verified before this routine. */
+ in = &ulpq->asoc->ssnmap->in;
+
+ /* Is this an old SSN? If so ignore. */
+ if (SSN_lt(ssn, sctp_ssn_peek(in, sid)))
+ return;
+
+ /* Mark that we are no longer expecting this SSN or lower. */
+ sctp_ssn_skip(in, sid, ssn);
+
+ /* Go find any other chunks that were waiting for
+ * ordering and deliver them if needed.
+ */
+ sctp_ulpq_reap_ordered(ulpq);
+ return;
+}
+
+/* Renege 'needed' bytes from the ordering queue. */
+static __u16 sctp_ulpq_renege_order(struct sctp_ulpq *ulpq, __u16 needed)
+{
+ __u16 freed = 0;
+ __u32 tsn;
+ struct sk_buff *skb;
+ struct sctp_ulpevent *event;
+ struct sctp_tsnmap *tsnmap;
+
+ tsnmap = &ulpq->asoc->peer.tsn_map;
+
+ while ((skb = __skb_dequeue_tail(&ulpq->lobby)) != NULL) {
+ freed += skb_headlen(skb);
+ event = sctp_skb2event(skb);
+ tsn = event->tsn;
+
+ sctp_ulpevent_free(event);
+ sctp_tsnmap_renege(tsnmap, tsn);
+ if (freed >= needed)
+ return freed;
+ }
+
+ return freed;
+}
+
+/* Renege 'needed' bytes from the reassembly queue. */
+static __u16 sctp_ulpq_renege_frags(struct sctp_ulpq *ulpq, __u16 needed)
+{
+ __u16 freed = 0;
+ __u32 tsn;
+ struct sk_buff *skb;
+ struct sctp_ulpevent *event;
+ struct sctp_tsnmap *tsnmap;
+
+ tsnmap = &ulpq->asoc->peer.tsn_map;
+
+ /* Walk backwards through the list, reneges the newest tsns. */
+ while ((skb = __skb_dequeue_tail(&ulpq->reasm)) != NULL) {
+ freed += skb_headlen(skb);
+ event = sctp_skb2event(skb);
+ tsn = event->tsn;
+
+ sctp_ulpevent_free(event);
+ sctp_tsnmap_renege(tsnmap, tsn);
+ if (freed >= needed)
+ return freed;
+ }
+
+ return freed;
+}
+
+/* Partial deliver the first message as there is pressure on rwnd. */
+void sctp_ulpq_partial_delivery(struct sctp_ulpq *ulpq,
+ struct sctp_chunk *chunk, int gfp)
+{
+ struct sctp_ulpevent *event;
+ struct sctp_association *asoc;
+
+ asoc = ulpq->asoc;
+
+ /* Are we already in partial delivery mode? */
+ if (!sctp_sk(asoc->base.sk)->pd_mode) {
+
+ /* Is partial delivery possible? */
+ event = sctp_ulpq_retrieve_first(ulpq);
+ /* Send event to the ULP. */
+ if (event) {
+ sctp_ulpq_tail_event(ulpq, event);
+ sctp_sk(asoc->base.sk)->pd_mode = 1;
+ ulpq->pd_mode = 1;
+ return;
+ }
+ }
+}
+
+/* Renege some packets to make room for an incoming chunk. */
+void sctp_ulpq_renege(struct sctp_ulpq *ulpq, struct sctp_chunk *chunk,
+ int gfp)
+{
+ struct sctp_association *asoc;
+ __u16 needed, freed;
+
+ asoc = ulpq->asoc;
+
+ if (chunk) {
+ needed = ntohs(chunk->chunk_hdr->length);
+ needed -= sizeof(sctp_data_chunk_t);
+ } else
+ needed = SCTP_DEFAULT_MAXWINDOW;
+
+ freed = 0;
+
+ if (skb_queue_empty(&asoc->base.sk->receive_queue)) {
+ freed = sctp_ulpq_renege_order(ulpq, needed);
+ if (freed < needed) {
+ freed += sctp_ulpq_renege_frags(ulpq, needed - freed);
+ }
+ }
+ /* If able to free enough room, accept this chunk. */
+ if (chunk && (freed >= needed)) {
+ __u32 tsn;
+ tsn = ntohl(chunk->subh.data_hdr->tsn);
+ sctp_tsnmap_mark(&asoc->peer.tsn_map, tsn);
+ sctp_ulpq_tail_data(ulpq, chunk, gfp);
+
+ sctp_ulpq_partial_delivery(ulpq, chunk, gfp);
+ }
+
+ return;
+}
+
+
+
+/* Notify the application if an association is aborted and in
+ * partial delivery mode. Send up any pending received messages.
+ */
+void sctp_ulpq_abort_pd(struct sctp_ulpq *ulpq, int gfp)
+{
+ struct sctp_ulpevent *ev = NULL;
+ struct sock *sk;
+
+ if (!ulpq->pd_mode)
+ return;
+
+ sk = ulpq->asoc->base.sk;
+ if (sctp_ulpevent_type_enabled(SCTP_PARTIAL_DELIVERY_EVENT,
+ &sctp_sk(sk)->subscribe))
+ ev = sctp_ulpevent_make_pdapi(ulpq->asoc,
+ SCTP_PARTIAL_DELIVERY_ABORTED,
+ gfp);
+ if (ev)
+ __skb_queue_tail(&sk->receive_queue, sctp_event2skb(ev));
+
+ /* If there is data waiting, send it up the socket now. */
+ if (sctp_ulpq_clear_pd(ulpq) || ev)
+ sk->data_ready(sk, 0);
+}
diff --git a/uClinux-2.4.31-uc0/net/socket.c b/uClinux-2.4.31-uc0/net/socket.c
new file mode 100644
index 0000000..ac45b13
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/socket.c
@@ -0,0 +1,1759 @@
+/*
+ * NET An implementation of the SOCKET network access protocol.
+ *
+ * Version: @(#)socket.c 1.1.93 18/02/95
+ *
+ * Authors: Orest Zborowski, <obz@Kodak.COM>
+ * Ross Biro, <bir7@leland.Stanford.Edu>
+ * Fred N. van Kempen, <waltje@uWalt.NL.Mugnet.ORG>
+ *
+ * Fixes:
+ * Anonymous : NOTSOCK/BADF cleanup. Error fix in
+ * shutdown()
+ * Alan Cox : verify_area() fixes
+ * Alan Cox : Removed DDI
+ * Jonathan Kamens : SOCK_DGRAM reconnect bug
+ * Alan Cox : Moved a load of checks to the very
+ * top level.
+ * Alan Cox : Move address structures to/from user
+ * mode above the protocol layers.
+ * Rob Janssen : Allow 0 length sends.
+ * Alan Cox : Asynchronous I/O support (cribbed from the
+ * tty drivers).
+ * Niibe Yutaka : Asynchronous I/O for writes (4.4BSD style)
+ * Jeff Uphoff : Made max number of sockets command-line
+ * configurable.
+ * Matti Aarnio : Made the number of sockets dynamic,
+ * to be allocated when needed, and mr.
+ * Uphoff's max is used as max to be
+ * allowed to allocate.
+ * Linus : Argh. removed all the socket allocation
+ * altogether: it's in the inode now.
+ * Alan Cox : Made sock_alloc()/sock_release() public
+ * for NetROM and future kernel nfsd type
+ * stuff.
+ * Alan Cox : sendmsg/recvmsg basics.
+ * Tom Dyas : Export net symbols.
+ * Marcin Dalecki : Fixed problems with CONFIG_NET="n".
+ * Alan Cox : Added thread locking to sys_* calls
+ * for sockets. May have errors at the
+ * moment.
+ * Kevin Buhr : Fixed the dumb errors in the above.
+ * Andi Kleen : Some small cleanups, optimizations,
+ * and fixed a copy_from_user() bug.
+ * Tigran Aivazian : sys_send(args) calls sys_sendto(args, NULL, 0)
+ * Tigran Aivazian : Made listen(2) backlog sanity checks
+ * protocol-independent
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ *
+ * This module is effectively the top level interface to the BSD socket
+ * paradigm.
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/mm.h>
+#include <linux/smp_lock.h>
+#include <linux/socket.h>
+#include <linux/file.h>
+#include <linux/net.h>
+#include <linux/interrupt.h>
+#include <linux/netdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/wanrouter.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/cache.h>
+#include <linux/module.h>
+#include <linux/highmem.h>
+
+#if defined(CONFIG_KMOD) && defined(CONFIG_NET)
+#include <linux/kmod.h>
+#endif
+
+#include <asm/uaccess.h>
+
+#include <net/sock.h>
+#include <net/scm.h>
+#include <linux/netfilter.h>
+
+static int sock_no_open(struct inode *irrelevant, struct file *dontcare);
+static ssize_t sock_read(struct file *file, char *buf,
+ size_t size, loff_t *ppos);
+static ssize_t sock_write(struct file *file, const char *buf,
+ size_t size, loff_t *ppos);
+static int sock_mmap(struct file *file, struct vm_area_struct * vma);
+
+static int sock_close(struct inode *inode, struct file *file);
+static unsigned int sock_poll(struct file *file,
+ struct poll_table_struct *wait);
+static int sock_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+static int sock_fasync(int fd, struct file *filp, int on);
+static ssize_t sock_readv(struct file *file, const struct iovec *vector,
+ unsigned long count, loff_t *ppos);
+static ssize_t sock_writev(struct file *file, const struct iovec *vector,
+ unsigned long count, loff_t *ppos);
+static ssize_t sock_sendpage(struct file *file, struct page *page,
+ int offset, size_t size, loff_t *ppos, int more);
+
+
+/*
+ * Socket files have a set of 'special' operations as well as the generic file ones. These don't appear
+ * in the operation structures but are done directly via the socketcall() multiplexor.
+ */
+
+static struct file_operations socket_file_ops = {
+ llseek: no_llseek,
+ read: sock_read,
+ write: sock_write,
+ poll: sock_poll,
+ ioctl: sock_ioctl,
+ mmap: sock_mmap,
+ open: sock_no_open, /* special open code to disallow open via /proc */
+ release: sock_close,
+ fasync: sock_fasync,
+ readv: sock_readv,
+ writev: sock_writev,
+ sendpage: sock_sendpage
+};
+
+/*
+ * The protocol list. Each protocol is registered in here.
+ */
+
+static struct net_proto_family *net_families[NPROTO];
+
+#ifdef CONFIG_SMP
+static atomic_t net_family_lockct = ATOMIC_INIT(0);
+static spinlock_t net_family_lock = SPIN_LOCK_UNLOCKED;
+
+/* The strategy is: modifications net_family vector are short, do not
+ sleep and veeery rare, but read access should be free of any exclusive
+ locks.
+ */
+
+static void net_family_write_lock(void)
+{
+ spin_lock(&net_family_lock);
+ while (atomic_read(&net_family_lockct) != 0) {
+ spin_unlock(&net_family_lock);
+
+ yield();
+
+ spin_lock(&net_family_lock);
+ }
+}
+
+static __inline__ void net_family_write_unlock(void)
+{
+ spin_unlock(&net_family_lock);
+}
+
+static __inline__ void net_family_read_lock(void)
+{
+ atomic_inc(&net_family_lockct);
+ spin_unlock_wait(&net_family_lock);
+}
+
+static __inline__ void net_family_read_unlock(void)
+{
+ atomic_dec(&net_family_lockct);
+}
+
+#else
+#define net_family_write_lock() do { } while(0)
+#define net_family_write_unlock() do { } while(0)
+#define net_family_read_lock() do { } while(0)
+#define net_family_read_unlock() do { } while(0)
+#endif
+
+
+/*
+ * Statistics counters of the socket lists
+ */
+
+static union {
+ int counter;
+ char __pad[SMP_CACHE_BYTES];
+} sockets_in_use[NR_CPUS] __cacheline_aligned = {{0}};
+
+/*
+ * Support routines. Move socket addresses back and forth across the kernel/user
+ * divide and look after the messy bits.
+ */
+
+#define MAX_SOCK_ADDR 128 /* 108 for Unix domain -
+ 16 for IP, 16 for IPX,
+ 24 for IPv6,
+ about 80 for AX.25
+ must be at least one bigger than
+ the AF_UNIX size (see net/unix/af_unix.c
+ :unix_mkname()).
+ */
+
+/**
+ * move_addr_to_kernel - copy a socket address into kernel space
+ * @uaddr: Address in user space
+ * @kaddr: Address in kernel space
+ * @ulen: Length in user space
+ *
+ * The address is copied into kernel space. If the provided address is
+ * too long an error code of -EINVAL is returned. If the copy gives
+ * invalid addresses -EFAULT is returned. On a success 0 is returned.
+ */
+
+int move_addr_to_kernel(void *uaddr, int ulen, void *kaddr)
+{
+ if(ulen<0||ulen>MAX_SOCK_ADDR)
+ return -EINVAL;
+ if(ulen==0)
+ return 0;
+ if(copy_from_user(kaddr,uaddr,ulen))
+ return -EFAULT;
+ return 0;
+}
+
+/**
+ * move_addr_to_user - copy an address to user space
+ * @kaddr: kernel space address
+ * @klen: length of address in kernel
+ * @uaddr: user space address
+ * @ulen: pointer to user length field
+ *
+ * The value pointed to by ulen on entry is the buffer length available.
+ * This is overwritten with the buffer space used. -EINVAL is returned
+ * if an overlong buffer is specified or a negative buffer size. -EFAULT
+ * is returned if either the buffer or the length field are not
+ * accessible.
+ * After copying the data up to the limit the user specifies, the true
+ * length of the data is written over the length limit the user
+ * specified. Zero is returned for a success.
+ */
+
+int move_addr_to_user(void *kaddr, int klen, void *uaddr, int *ulen)
+{
+ int err;
+ int len;
+
+ if((err=get_user(len, ulen)))
+ return err;
+ if(len>klen)
+ len=klen;
+ if(len<0 || len> MAX_SOCK_ADDR)
+ return -EINVAL;
+ if(len)
+ {
+ if(copy_to_user(uaddr,kaddr,len))
+ return -EFAULT;
+ }
+ /*
+ * "fromlen shall refer to the value before truncation.."
+ * 1003.1g
+ */
+ return __put_user(klen, ulen);
+}
+
+#define SOCKFS_MAGIC 0x534F434B
+static int sockfs_statfs(struct super_block *sb, struct statfs *buf)
+{
+ buf->f_type = SOCKFS_MAGIC;
+ buf->f_bsize = 1024;
+ buf->f_namelen = 255;
+ return 0;
+}
+
+static struct super_operations sockfs_ops = {
+ statfs: sockfs_statfs,
+};
+
+static struct super_block * sockfs_read_super(struct super_block *sb, void *data, int silent)
+{
+ struct inode *root = new_inode(sb);
+ if (!root)
+ return NULL;
+ root->i_mode = S_IFDIR | S_IRUSR | S_IWUSR;
+ root->i_uid = root->i_gid = 0;
+ root->i_atime = root->i_mtime = root->i_ctime = CURRENT_TIME;
+ sb->s_blocksize = 1024;
+ sb->s_blocksize_bits = 10;
+ sb->s_magic = SOCKFS_MAGIC;
+ sb->s_op = &sockfs_ops;
+ sb->s_root = d_alloc(NULL, &(const struct qstr) { "socket:", 7, 0 });
+ if (!sb->s_root) {
+ iput(root);
+ return NULL;
+ }
+ sb->s_root->d_sb = sb;
+ sb->s_root->d_parent = sb->s_root;
+ d_instantiate(sb->s_root, root);
+ return sb;
+}
+
+static struct vfsmount *sock_mnt;
+static DECLARE_FSTYPE(sock_fs_type, "sockfs", sockfs_read_super, FS_NOMOUNT);
+static int sockfs_delete_dentry(struct dentry *dentry)
+{
+ return 1;
+}
+static struct dentry_operations sockfs_dentry_operations = {
+ d_delete: sockfs_delete_dentry,
+};
+
+/*
+ * Obtains the first available file descriptor and sets it up for use.
+ *
+ * This function creates file structure and maps it to fd space
+ * of current process. On success it returns file descriptor
+ * and file struct implicitly stored in sock->file.
+ * Note that another thread may close file descriptor before we return
+ * from this function. We use the fact that now we do not refer
+ * to socket after mapping. If one day we will need it, this
+ * function will increment ref. count on file by 1.
+ *
+ * In any case returned fd MAY BE not valid!
+ * This race condition is unavoidable
+ * with shared fd spaces, we cannot solve it inside kernel,
+ * but we take care of internal coherence yet.
+ */
+
+int sock_map_fd(struct socket *sock)
+{
+ int fd;
+ struct qstr this;
+ char name[32];
+
+ /*
+ * Find a file descriptor suitable for return to the user.
+ */
+
+ fd = get_unused_fd();
+ if (fd >= 0) {
+ struct file *file = get_empty_filp();
+
+ if (!file) {
+ put_unused_fd(fd);
+ fd = -ENFILE;
+ goto out;
+ }
+
+ sprintf(name, "[%lu]", sock->inode->i_ino);
+ this.name = name;
+ this.len = strlen(name);
+ this.hash = sock->inode->i_ino;
+
+ file->f_dentry = d_alloc(sock_mnt->mnt_sb->s_root, &this);
+ if (!file->f_dentry) {
+ put_filp(file);
+ put_unused_fd(fd);
+ fd = -ENOMEM;
+ goto out;
+ }
+ file->f_dentry->d_op = &sockfs_dentry_operations;
+ d_add(file->f_dentry, sock->inode);
+ file->f_vfsmnt = mntget(sock_mnt);
+
+ sock->file = file;
+ file->f_op = sock->inode->i_fop = &socket_file_ops;
+ file->f_mode = 3;
+ file->f_flags = O_RDWR;
+ file->f_pos = 0;
+ fd_install(fd, file);
+ }
+
+out:
+ return fd;
+}
+
+extern __inline__ struct socket *socki_lookup(struct inode *inode)
+{
+ return &inode->u.socket_i;
+}
+
+/**
+ * sockfd_lookup - Go from a file number to its socket slot
+ * @fd: file handle
+ * @err: pointer to an error code return
+ *
+ * The file handle passed in is locked and the socket it is bound
+ * too is returned. If an error occurs the err pointer is overwritten
+ * with a negative errno code and NULL is returned. The function checks
+ * for both invalid handles and passing a handle which is not a socket.
+ *
+ * On a success the socket object pointer is returned.
+ */
+
+struct socket *sockfd_lookup(int fd, int *err)
+{
+ struct file *file;
+ struct inode *inode;
+ struct socket *sock;
+
+ if (!(file = fget(fd)))
+ {
+ *err = -EBADF;
+ return NULL;
+ }
+
+ inode = file->f_dentry->d_inode;
+ if (!inode->i_sock || !(sock = socki_lookup(inode)))
+ {
+ *err = -ENOTSOCK;
+ fput(file);
+ return NULL;
+ }
+
+ if (sock->file != file) {
+ printk(KERN_ERR "socki_lookup: socket file changed!\n");
+ sock->file = file;
+ }
+ return sock;
+}
+
+extern __inline__ void sockfd_put(struct socket *sock)
+{
+ fput(sock->file);
+}
+
+/**
+ * sock_alloc - allocate a socket
+ *
+ * Allocate a new inode and socket object. The two are bound together
+ * and initialised. The socket is then returned. If we are out of inodes
+ * NULL is returned.
+ */
+
+struct socket *sock_alloc(void)
+{
+ struct inode * inode;
+ struct socket * sock;
+
+ inode = new_inode(sock_mnt->mnt_sb);
+ if (!inode)
+ return NULL;
+
+ inode->i_dev = NODEV;
+ sock = socki_lookup(inode);
+
+ inode->i_mode = S_IFSOCK|S_IRWXUGO;
+ inode->i_sock = 1;
+ inode->i_uid = current->fsuid;
+ inode->i_gid = current->fsgid;
+
+ sock->inode = inode;
+ init_waitqueue_head(&sock->wait);
+ sock->fasync_list = NULL;
+ sock->state = SS_UNCONNECTED;
+ sock->flags = 0;
+ sock->ops = NULL;
+ sock->sk = NULL;
+ sock->file = NULL;
+
+ sockets_in_use[smp_processor_id()].counter++;
+ return sock;
+}
+
+/*
+ * In theory you can't get an open on this inode, but /proc provides
+ * a back door. Remember to keep it shut otherwise you'll let the
+ * creepy crawlies in.
+ */
+
+static int sock_no_open(struct inode *irrelevant, struct file *dontcare)
+{
+ return -ENXIO;
+}
+
+/**
+ * sock_release - close a socket
+ * @sock: socket to close
+ *
+ * The socket is released from the protocol stack if it has a release
+ * callback, and the inode is then released if the socket is bound to
+ * an inode not a file.
+ */
+
+void sock_release(struct socket *sock)
+{
+ if (sock->ops)
+ sock->ops->release(sock);
+
+ if (sock->fasync_list)
+ printk(KERN_ERR "sock_release: fasync list not empty!\n");
+
+ sockets_in_use[smp_processor_id()].counter--;
+ if (!sock->file) {
+ iput(sock->inode);
+ return;
+ }
+ sock->file=NULL;
+}
+
+int sock_sendmsg(struct socket *sock, struct msghdr *msg, int size)
+{
+ int err;
+ struct scm_cookie scm;
+
+ err = scm_send(sock, msg, &scm);
+ if (err >= 0) {
+ err = sock->ops->sendmsg(sock, msg, size, &scm);
+ scm_destroy(&scm);
+ }
+ return err;
+}
+
+int sock_recvmsg(struct socket *sock, struct msghdr *msg, int size, int flags)
+{
+ struct scm_cookie scm;
+
+ memset(&scm, 0, sizeof(scm));
+
+ size = sock->ops->recvmsg(sock, msg, size, flags, &scm);
+ if (size >= 0)
+ scm_recv(sock, msg, &scm, flags);
+
+ return size;
+}
+
+
+/*
+ * Read data from a socket. ubuf is a user mode pointer. We make sure the user
+ * area ubuf...ubuf+size-1 is writable before asking the protocol.
+ */
+
+static ssize_t sock_read(struct file *file, char *ubuf,
+ size_t size, loff_t *ppos)
+{
+ struct socket *sock;
+ struct iovec iov;
+ struct msghdr msg;
+ int flags;
+
+ if (ppos != &file->f_pos)
+ return -ESPIPE;
+ if (size==0) /* Match SYS5 behaviour */
+ return 0;
+
+ sock = socki_lookup(file->f_dentry->d_inode);
+
+ msg.msg_name=NULL;
+ msg.msg_namelen=0;
+ msg.msg_iov=&iov;
+ msg.msg_iovlen=1;
+ msg.msg_control=NULL;
+ msg.msg_controllen=0;
+ iov.iov_base=ubuf;
+ iov.iov_len=size;
+ flags = !(file->f_flags & O_NONBLOCK) ? 0 : MSG_DONTWAIT;
+
+ return sock_recvmsg(sock, &msg, size, flags);
+}
+
+
+/*
+ * Write data to a socket. We verify that the user area ubuf..ubuf+size-1
+ * is readable by the user process.
+ */
+
+static ssize_t sock_write(struct file *file, const char *ubuf,
+ size_t size, loff_t *ppos)
+{
+ struct socket *sock;
+ struct msghdr msg;
+ struct iovec iov;
+
+ if (ppos != &file->f_pos)
+ return -ESPIPE;
+ if(size==0) /* Match SYS5 behaviour */
+ return 0;
+
+ sock = socki_lookup(file->f_dentry->d_inode);
+
+ msg.msg_name=NULL;
+ msg.msg_namelen=0;
+ msg.msg_iov=&iov;
+ msg.msg_iovlen=1;
+ msg.msg_control=NULL;
+ msg.msg_controllen=0;
+ msg.msg_flags=!(file->f_flags & O_NONBLOCK) ? 0 : MSG_DONTWAIT;
+ if (sock->type == SOCK_SEQPACKET)
+ msg.msg_flags |= MSG_EOR;
+ iov.iov_base=(void *)ubuf;
+ iov.iov_len=size;
+
+ return sock_sendmsg(sock, &msg, size);
+}
+
+ssize_t sock_sendpage(struct file *file, struct page *page,
+ int offset, size_t size, loff_t *ppos, int more)
+{
+ struct socket *sock;
+ int flags;
+
+ if (ppos != &file->f_pos)
+ return -ESPIPE;
+
+ sock = socki_lookup(file->f_dentry->d_inode);
+
+ flags = !(file->f_flags & O_NONBLOCK) ? 0 : MSG_DONTWAIT;
+ if (more)
+ flags |= MSG_MORE;
+
+ return sock->ops->sendpage(sock, page, offset, size, flags);
+}
+
+int sock_readv_writev(int type, struct inode * inode, struct file * file,
+ const struct iovec * iov, long count, long size)
+{
+ struct msghdr msg;
+ struct socket *sock;
+
+ sock = socki_lookup(inode);
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_iov = (struct iovec *) iov;
+ msg.msg_iovlen = count;
+ msg.msg_flags = (file->f_flags & O_NONBLOCK) ? MSG_DONTWAIT : 0;
+
+ /* read() does a VERIFY_WRITE */
+ if (type == VERIFY_WRITE)
+ return sock_recvmsg(sock, &msg, size, msg.msg_flags);
+
+ if (sock->type == SOCK_SEQPACKET)
+ msg.msg_flags |= MSG_EOR;
+
+ return sock_sendmsg(sock, &msg, size);
+}
+
+static ssize_t sock_readv(struct file *file, const struct iovec *vector,
+ unsigned long count, loff_t *ppos)
+{
+ size_t tot_len = 0;
+ int i;
+ for (i = 0 ; i < count ; i++)
+ tot_len += vector[i].iov_len;
+ return sock_readv_writev(VERIFY_WRITE, file->f_dentry->d_inode,
+ file, vector, count, tot_len);
+}
+
+static ssize_t sock_writev(struct file *file, const struct iovec *vector,
+ unsigned long count, loff_t *ppos)
+{
+ size_t tot_len = 0;
+ int i;
+ for (i = 0 ; i < count ; i++)
+ tot_len += vector[i].iov_len;
+ return sock_readv_writev(VERIFY_READ, file->f_dentry->d_inode,
+ file, vector, count, tot_len);
+}
+
+/*
+ * With an ioctl arg may well be a user mode pointer, but we don't know what to do
+ * with it - that's up to the protocol still.
+ */
+
+int sock_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct socket *sock;
+ int err;
+
+ unlock_kernel();
+ sock = socki_lookup(inode);
+ err = sock->ops->ioctl(sock, cmd, arg);
+ lock_kernel();
+
+ return err;
+}
+
+
+/* No kernel lock held - perfect */
+static unsigned int sock_poll(struct file *file, poll_table * wait)
+{
+ struct socket *sock;
+
+ /*
+ * We can't return errors to poll, so it's either yes or no.
+ */
+ sock = socki_lookup(file->f_dentry->d_inode);
+ return sock->ops->poll(file, sock, wait);
+}
+
+static int sock_mmap(struct file * file, struct vm_area_struct * vma)
+{
+ struct socket *sock = socki_lookup(file->f_dentry->d_inode);
+
+ return sock->ops->mmap(file, sock, vma);
+}
+
+int sock_close(struct inode *inode, struct file *filp)
+{
+ /*
+ * It was possible the inode is NULL we were
+ * closing an unfinished socket.
+ */
+
+ if (!inode)
+ {
+ printk(KERN_DEBUG "sock_close: NULL inode\n");
+ return 0;
+ }
+ sock_fasync(-1, filp, 0);
+ sock_release(socki_lookup(inode));
+ return 0;
+}
+
+/*
+ * Update the socket async list
+ *
+ * Fasync_list locking strategy.
+ *
+ * 1. fasync_list is modified only under process context socket lock
+ * i.e. under semaphore.
+ * 2. fasync_list is used under read_lock(&sk->callback_lock)
+ * or under socket lock.
+ * 3. fasync_list can be used from softirq context, so that
+ * modification under socket lock have to be enhanced with
+ * write_lock_bh(&sk->callback_lock).
+ * --ANK (990710)
+ */
+
+static int sock_fasync(int fd, struct file *filp, int on)
+{
+ struct fasync_struct *fa, *fna=NULL, **prev;
+ struct socket *sock;
+ struct sock *sk;
+
+ if (on)
+ {
+ fna=(struct fasync_struct *)kmalloc(sizeof(struct fasync_struct), GFP_KERNEL);
+ if(fna==NULL)
+ return -ENOMEM;
+ }
+
+ sock = socki_lookup(filp->f_dentry->d_inode);
+
+ if ((sk=sock->sk) == NULL) {
+ if (fna)
+ kfree(fna);
+ return -EINVAL;
+ }
+
+ lock_sock(sk);
+
+ prev=&(sock->fasync_list);
+
+ for (fa=*prev; fa!=NULL; prev=&fa->fa_next,fa=*prev)
+ if (fa->fa_file==filp)
+ break;
+
+ if(on)
+ {
+ if(fa!=NULL)
+ {
+ write_lock_bh(&sk->callback_lock);
+ fa->fa_fd=fd;
+ write_unlock_bh(&sk->callback_lock);
+
+ kfree(fna);
+ goto out;
+ }
+ fna->fa_file=filp;
+ fna->fa_fd=fd;
+ fna->magic=FASYNC_MAGIC;
+ fna->fa_next=sock->fasync_list;
+ write_lock_bh(&sk->callback_lock);
+ sock->fasync_list=fna;
+ write_unlock_bh(&sk->callback_lock);
+ }
+ else
+ {
+ if (fa!=NULL)
+ {
+ write_lock_bh(&sk->callback_lock);
+ *prev=fa->fa_next;
+ write_unlock_bh(&sk->callback_lock);
+ kfree(fa);
+ }
+ }
+
+out:
+ release_sock(sock->sk);
+ return 0;
+}
+
+/* This function may be called only under socket lock or callback_lock */
+
+int sock_wake_async(struct socket *sock, int how, int band)
+{
+ if (!sock || !sock->fasync_list)
+ return -1;
+ switch (how)
+ {
+ case 1:
+
+ if (test_bit(SOCK_ASYNC_WAITDATA, &sock->flags))
+ break;
+ goto call_kill;
+ case 2:
+ if (!test_and_clear_bit(SOCK_ASYNC_NOSPACE, &sock->flags))
+ break;
+ /* fall through */
+ case 0:
+ call_kill:
+ __kill_fasync(sock->fasync_list, SIGIO, band);
+ break;
+ case 3:
+ __kill_fasync(sock->fasync_list, SIGURG, band);
+ }
+ return 0;
+}
+
+
+int sock_create(int family, int type, int protocol, struct socket **res)
+{
+ int i;
+ struct socket *sock;
+
+ /*
+ * Check protocol is in range
+ */
+ if (family < 0 || family >= NPROTO)
+ return -EAFNOSUPPORT;
+ if (type < 0 || type >= SOCK_MAX)
+ return -EINVAL;
+
+ /* Compatibility.
+
+ This uglymoron is moved from INET layer to here to avoid
+ deadlock in module load.
+ */
+ if (family == PF_INET && type == SOCK_PACKET) {
+ static int warned;
+ if (!warned) {
+ warned = 1;
+ printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)\n", current->comm);
+ }
+ family = PF_PACKET;
+ }
+
+#if defined(CONFIG_KMOD) && defined(CONFIG_NET)
+ /* Attempt to load a protocol module if the find failed.
+ *
+ * 12/09/1996 Marcin: But! this makes REALLY only sense, if the user
+ * requested real, full-featured networking support upon configuration.
+ * Otherwise module support will break!
+ */
+ if (net_families[family]==NULL)
+ {
+ char module_name[30];
+ sprintf(module_name,"net-pf-%d",family);
+ request_module(module_name);
+ }
+#endif
+
+ net_family_read_lock();
+ if (net_families[family] == NULL) {
+ i = -EAFNOSUPPORT;
+ goto out;
+ }
+
+/*
+ * Allocate the socket and allow the family to set things up. if
+ * the protocol is 0, the family is instructed to select an appropriate
+ * default.
+ */
+
+ if (!(sock = sock_alloc()))
+ {
+ printk(KERN_WARNING "socket: no more sockets\n");
+ i = -ENFILE; /* Not exactly a match, but its the
+ closest posix thing */
+ goto out;
+ }
+
+ sock->type = type;
+
+ if ((i = net_families[family]->create(sock, protocol)) < 0)
+ {
+ sock_release(sock);
+ goto out;
+ }
+
+ *res = sock;
+
+out:
+ net_family_read_unlock();
+ return i;
+}
+
+asmlinkage long sys_socket(int family, int type, int protocol)
+{
+ int retval;
+ struct socket *sock;
+
+ retval = sock_create(family, type, protocol, &sock);
+ if (retval < 0)
+ goto out;
+
+ retval = sock_map_fd(sock);
+ if (retval < 0)
+ goto out_release;
+
+out:
+ /* It may be already another descriptor 8) Not kernel problem. */
+ return retval;
+
+out_release:
+ sock_release(sock);
+ return retval;
+}
+
+/*
+ * Create a pair of connected sockets.
+ */
+
+asmlinkage long sys_socketpair(int family, int type, int protocol, int usockvec[2])
+{
+ struct socket *sock1, *sock2;
+ int fd1, fd2, err;
+
+ /*
+ * Obtain the first socket and check if the underlying protocol
+ * supports the socketpair call.
+ */
+
+ err = sock_create(family, type, protocol, &sock1);
+ if (err < 0)
+ goto out;
+
+ err = sock_create(family, type, protocol, &sock2);
+ if (err < 0)
+ goto out_release_1;
+
+ err = sock1->ops->socketpair(sock1, sock2);
+ if (err < 0)
+ goto out_release_both;
+
+ fd1 = fd2 = -1;
+
+ err = sock_map_fd(sock1);
+ if (err < 0)
+ goto out_release_both;
+ fd1 = err;
+
+ err = sock_map_fd(sock2);
+ if (err < 0)
+ goto out_close_1;
+ fd2 = err;
+
+ /* fd1 and fd2 may be already another descriptors.
+ * Not kernel problem.
+ */
+
+ err = put_user(fd1, &usockvec[0]);
+ if (!err)
+ err = put_user(fd2, &usockvec[1]);
+ if (!err)
+ return 0;
+
+ sys_close(fd2);
+ sys_close(fd1);
+ return err;
+
+out_close_1:
+ sock_release(sock2);
+ sys_close(fd1);
+ return err;
+
+out_release_both:
+ sock_release(sock2);
+out_release_1:
+ sock_release(sock1);
+out:
+ return err;
+}
+
+
+/*
+ * Bind a name to a socket. Nothing much to do here since it's
+ * the protocol's responsibility to handle the local address.
+ *
+ * We move the socket address to kernel space before we call
+ * the protocol layer (having also checked the address is ok).
+ */
+
+asmlinkage long sys_bind(int fd, struct sockaddr *umyaddr, int addrlen)
+{
+ struct socket *sock;
+ char address[MAX_SOCK_ADDR];
+ int err;
+
+ if((sock = sockfd_lookup(fd,&err))!=NULL)
+ {
+ if((err=move_addr_to_kernel(umyaddr,addrlen,address))>=0)
+ err = sock->ops->bind(sock, (struct sockaddr *)address, addrlen);
+ sockfd_put(sock);
+ }
+ return err;
+}
+
+
+/*
+ * Perform a listen. Basically, we allow the protocol to do anything
+ * necessary for a listen, and if that works, we mark the socket as
+ * ready for listening.
+ */
+
+int sysctl_somaxconn = SOMAXCONN;
+
+asmlinkage long sys_listen(int fd, int backlog)
+{
+ struct socket *sock;
+ int err;
+
+ if ((sock = sockfd_lookup(fd, &err)) != NULL) {
+ if ((unsigned) backlog > sysctl_somaxconn)
+ backlog = sysctl_somaxconn;
+ err=sock->ops->listen(sock, backlog);
+ sockfd_put(sock);
+ }
+ return err;
+}
+
+
+/*
+ * For accept, we attempt to create a new socket, set up the link
+ * with the client, wake up the client, then return the new
+ * connected fd. We collect the address of the connector in kernel
+ * space and move it to user at the very end. This is unclean because
+ * we open the socket then return an error.
+ *
+ * 1003.1g adds the ability to recvmsg() to query connection pending
+ * status to recvmsg. We need to add that support in a way thats
+ * clean when we restucture accept also.
+ */
+
+asmlinkage long sys_accept(int fd, struct sockaddr *upeer_sockaddr, int *upeer_addrlen)
+{
+ struct socket *sock, *newsock;
+ int err, len;
+ char address[MAX_SOCK_ADDR];
+
+ sock = sockfd_lookup(fd, &err);
+ if (!sock)
+ goto out;
+
+ err = -ENFILE;
+ if (!(newsock = sock_alloc()))
+ goto out_put;
+
+ newsock->type = sock->type;
+ newsock->ops = sock->ops;
+
+ err = sock->ops->accept(sock, newsock, sock->file->f_flags);
+ if (err < 0)
+ goto out_release;
+
+ if (upeer_sockaddr) {
+ if(newsock->ops->getname(newsock, (struct sockaddr *)address, &len, 2)<0) {
+ err = -ECONNABORTED;
+ goto out_release;
+ }
+ err = move_addr_to_user(address, len, upeer_sockaddr, upeer_addrlen);
+ if (err < 0)
+ goto out_release;
+ }
+
+ /* File flags are not inherited via accept() unlike another OSes. */
+
+ if ((err = sock_map_fd(newsock)) < 0)
+ goto out_release;
+
+out_put:
+ sockfd_put(sock);
+out:
+ return err;
+
+out_release:
+ sock_release(newsock);
+ goto out_put;
+}
+
+
+/*
+ * Attempt to connect to a socket with the server address. The address
+ * is in user space so we verify it is OK and move it to kernel space.
+ *
+ * For 1003.1g we need to add clean support for a bind to AF_UNSPEC to
+ * break bindings
+ *
+ * NOTE: 1003.1g draft 6.3 is broken with respect to AX.25/NetROM and
+ * other SEQPACKET protocols that take time to connect() as it doesn't
+ * include the -EINPROGRESS status for such sockets.
+ */
+
+asmlinkage long sys_connect(int fd, struct sockaddr *uservaddr, int addrlen)
+{
+ struct socket *sock;
+ char address[MAX_SOCK_ADDR];
+ int err;
+
+ sock = sockfd_lookup(fd, &err);
+ if (!sock)
+ goto out;
+ err = move_addr_to_kernel(uservaddr, addrlen, address);
+ if (err < 0)
+ goto out_put;
+ err = sock->ops->connect(sock, (struct sockaddr *) address, addrlen,
+ sock->file->f_flags);
+out_put:
+ sockfd_put(sock);
+out:
+ return err;
+}
+
+/*
+ * Get the local address ('name') of a socket object. Move the obtained
+ * name to user space.
+ */
+
+asmlinkage long sys_getsockname(int fd, struct sockaddr *usockaddr, int *usockaddr_len)
+{
+ struct socket *sock;
+ char address[MAX_SOCK_ADDR];
+ int len, err;
+
+ sock = sockfd_lookup(fd, &err);
+ if (!sock)
+ goto out;
+ err = sock->ops->getname(sock, (struct sockaddr *)address, &len, 0);
+ if (err)
+ goto out_put;
+ err = move_addr_to_user(address, len, usockaddr, usockaddr_len);
+
+out_put:
+ sockfd_put(sock);
+out:
+ return err;
+}
+
+/*
+ * Get the remote address ('name') of a socket object. Move the obtained
+ * name to user space.
+ */
+
+asmlinkage long sys_getpeername(int fd, struct sockaddr *usockaddr, int *usockaddr_len)
+{
+ struct socket *sock;
+ char address[MAX_SOCK_ADDR];
+ int len, err;
+
+ if ((sock = sockfd_lookup(fd, &err))!=NULL)
+ {
+ err = sock->ops->getname(sock, (struct sockaddr *)address, &len, 1);
+ if (!err)
+ err=move_addr_to_user(address,len, usockaddr, usockaddr_len);
+ sockfd_put(sock);
+ }
+ return err;
+}
+
+/*
+ * Send a datagram to a given address. We move the address into kernel
+ * space and check the user space data area is readable before invoking
+ * the protocol.
+ */
+
+asmlinkage long sys_sendto(int fd, void * buff, size_t len, unsigned flags,
+ struct sockaddr *addr, int addr_len)
+{
+ struct socket *sock;
+ char address[MAX_SOCK_ADDR];
+ int err;
+ struct msghdr msg;
+ struct iovec iov;
+
+ sock = sockfd_lookup(fd, &err);
+ if (!sock)
+ goto out;
+ iov.iov_base=buff;
+ iov.iov_len=len;
+ msg.msg_name=NULL;
+ msg.msg_iov=&iov;
+ msg.msg_iovlen=1;
+ msg.msg_control=NULL;
+ msg.msg_controllen=0;
+ msg.msg_namelen=0;
+ if(addr)
+ {
+ err = move_addr_to_kernel(addr, addr_len, address);
+ if (err < 0)
+ goto out_put;
+ msg.msg_name=address;
+ msg.msg_namelen=addr_len;
+ }
+ if (sock->file->f_flags & O_NONBLOCK)
+ flags |= MSG_DONTWAIT;
+ msg.msg_flags = flags;
+ err = sock_sendmsg(sock, &msg, len);
+
+out_put:
+ sockfd_put(sock);
+out:
+ return err;
+}
+
+/*
+ * Send a datagram down a socket.
+ */
+
+asmlinkage long sys_send(int fd, void * buff, size_t len, unsigned flags)
+{
+ return sys_sendto(fd, buff, len, flags, NULL, 0);
+}
+
+/*
+ * Receive a frame from the socket and optionally record the address of the
+ * sender. We verify the buffers are writable and if needed move the
+ * sender address from kernel to user space.
+ */
+
+asmlinkage long sys_recvfrom(int fd, void * ubuf, size_t size, unsigned flags,
+ struct sockaddr *addr, int *addr_len)
+{
+ struct socket *sock;
+ struct iovec iov;
+ struct msghdr msg;
+ char address[MAX_SOCK_ADDR];
+ int err,err2;
+
+ sock = sockfd_lookup(fd, &err);
+ if (!sock)
+ goto out;
+
+ msg.msg_control=NULL;
+ msg.msg_controllen=0;
+ msg.msg_iovlen=1;
+ msg.msg_iov=&iov;
+ iov.iov_len=size;
+ iov.iov_base=ubuf;
+ msg.msg_name=address;
+ msg.msg_namelen=MAX_SOCK_ADDR;
+ if (sock->file->f_flags & O_NONBLOCK)
+ flags |= MSG_DONTWAIT;
+ err=sock_recvmsg(sock, &msg, size, flags);
+
+ if(err >= 0 && addr != NULL)
+ {
+ err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len);
+ if(err2<0)
+ err=err2;
+ }
+ sockfd_put(sock);
+out:
+ return err;
+}
+
+/*
+ * Receive a datagram from a socket.
+ */
+
+asmlinkage long sys_recv(int fd, void * ubuf, size_t size, unsigned flags)
+{
+ return sys_recvfrom(fd, ubuf, size, flags, NULL, NULL);
+}
+
+/*
+ * Set a socket option. Because we don't know the option lengths we have
+ * to pass the user mode parameter for the protocols to sort out.
+ */
+
+asmlinkage long sys_setsockopt(int fd, int level, int optname, char *optval, int optlen)
+{
+ int err;
+ struct socket *sock;
+
+ if (optlen < 0)
+ return -EINVAL;
+
+ if ((sock = sockfd_lookup(fd, &err))!=NULL)
+ {
+ if (level == SOL_SOCKET)
+ err=sock_setsockopt(sock,level,optname,optval,optlen);
+ else
+ err=sock->ops->setsockopt(sock, level, optname, optval, optlen);
+ sockfd_put(sock);
+ }
+ return err;
+}
+
+/*
+ * Get a socket option. Because we don't know the option lengths we have
+ * to pass a user mode parameter for the protocols to sort out.
+ */
+
+asmlinkage long sys_getsockopt(int fd, int level, int optname, char *optval, int *optlen)
+{
+ int err;
+ struct socket *sock;
+
+ if ((sock = sockfd_lookup(fd, &err))!=NULL)
+ {
+ if (level == SOL_SOCKET)
+ err=sock_getsockopt(sock,level,optname,optval,optlen);
+ else
+ err=sock->ops->getsockopt(sock, level, optname, optval, optlen);
+ sockfd_put(sock);
+ }
+ return err;
+}
+
+
+/*
+ * Shutdown a socket.
+ */
+
+asmlinkage long sys_shutdown(int fd, int how)
+{
+ int err;
+ struct socket *sock;
+
+ if ((sock = sockfd_lookup(fd, &err))!=NULL)
+ {
+ err=sock->ops->shutdown(sock, how);
+ sockfd_put(sock);
+ }
+ return err;
+}
+
+/*
+ * BSD sendmsg interface
+ */
+
+asmlinkage long sys_sendmsg(int fd, struct msghdr *msg, unsigned flags)
+{
+ struct socket *sock;
+ char address[MAX_SOCK_ADDR];
+ struct iovec iovstack[UIO_FASTIOV], *iov = iovstack;
+ unsigned char ctl[sizeof(struct cmsghdr) + 20]; /* 20 is size of ipv6_pktinfo */
+ unsigned char *ctl_buf = ctl;
+ struct msghdr msg_sys;
+ int err, ctl_len, iov_size, total_len;
+
+ err = -EFAULT;
+ if (copy_from_user(&msg_sys,msg,sizeof(struct msghdr)))
+ goto out;
+
+ sock = sockfd_lookup(fd, &err);
+ if (!sock)
+ goto out;
+
+ /* do not move before msg_sys is valid */
+ err = -EMSGSIZE;
+ if (msg_sys.msg_iovlen > UIO_MAXIOV)
+ goto out_put;
+
+ /* Check whether to allocate the iovec area*/
+ err = -ENOMEM;
+ iov_size = msg_sys.msg_iovlen * sizeof(struct iovec);
+ if (msg_sys.msg_iovlen > UIO_FASTIOV) {
+ iov = sock_kmalloc(sock->sk, iov_size, GFP_KERNEL);
+ if (!iov)
+ goto out_put;
+ }
+
+ /* This will also move the address data into kernel space */
+ err = verify_iovec(&msg_sys, iov, address, VERIFY_READ);
+ if (err < 0)
+ goto out_freeiov;
+ total_len = err;
+
+ err = -ENOBUFS;
+
+ if (msg_sys.msg_controllen > INT_MAX)
+ goto out_freeiov;
+ ctl_len = msg_sys.msg_controllen;
+ if (ctl_len)
+ {
+ if (ctl_len > sizeof(ctl))
+ {
+ ctl_buf = sock_kmalloc(sock->sk, ctl_len, GFP_KERNEL);
+ if (ctl_buf == NULL)
+ goto out_freeiov;
+ }
+ err = -EFAULT;
+ if (copy_from_user(ctl_buf, msg_sys.msg_control, ctl_len))
+ goto out_freectl;
+ msg_sys.msg_control = ctl_buf;
+ }
+ msg_sys.msg_flags = flags;
+
+ if (sock->file->f_flags & O_NONBLOCK)
+ msg_sys.msg_flags |= MSG_DONTWAIT;
+ err = sock_sendmsg(sock, &msg_sys, total_len);
+
+out_freectl:
+ if (ctl_buf != ctl)
+ sock_kfree_s(sock->sk, ctl_buf, ctl_len);
+out_freeiov:
+ if (iov != iovstack)
+ sock_kfree_s(sock->sk, iov, iov_size);
+out_put:
+ sockfd_put(sock);
+out:
+ return err;
+}
+
+/*
+ * BSD recvmsg interface
+ */
+
+asmlinkage long sys_recvmsg(int fd, struct msghdr *msg, unsigned int flags)
+{
+ struct socket *sock;
+ struct iovec iovstack[UIO_FASTIOV];
+ struct iovec *iov=iovstack;
+ struct msghdr msg_sys;
+ unsigned long cmsg_ptr;
+ int err, iov_size, total_len, len;
+
+ /* kernel mode address */
+ char addr[MAX_SOCK_ADDR];
+
+ /* user mode address pointers */
+ struct sockaddr *uaddr;
+ int *uaddr_len;
+
+ err=-EFAULT;
+ if (copy_from_user(&msg_sys,msg,sizeof(struct msghdr)))
+ goto out;
+
+ sock = sockfd_lookup(fd, &err);
+ if (!sock)
+ goto out;
+
+ err = -EMSGSIZE;
+ if (msg_sys.msg_iovlen > UIO_MAXIOV)
+ goto out_put;
+
+ /* Check whether to allocate the iovec area*/
+ err = -ENOMEM;
+ iov_size = msg_sys.msg_iovlen * sizeof(struct iovec);
+ if (msg_sys.msg_iovlen > UIO_FASTIOV) {
+ iov = sock_kmalloc(sock->sk, iov_size, GFP_KERNEL);
+ if (!iov)
+ goto out_put;
+ }
+
+ /*
+ * Save the user-mode address (verify_iovec will change the
+ * kernel msghdr to use the kernel address space)
+ */
+
+ uaddr = msg_sys.msg_name;
+ uaddr_len = &msg->msg_namelen;
+ err = verify_iovec(&msg_sys, iov, addr, VERIFY_WRITE);
+ if (err < 0)
+ goto out_freeiov;
+ total_len=err;
+
+ cmsg_ptr = (unsigned long)msg_sys.msg_control;
+ msg_sys.msg_flags = 0;
+
+ if (sock->file->f_flags & O_NONBLOCK)
+ flags |= MSG_DONTWAIT;
+ err = sock_recvmsg(sock, &msg_sys, total_len, flags);
+ if (err < 0)
+ goto out_freeiov;
+ len = err;
+
+ if (uaddr != NULL) {
+ err = move_addr_to_user(addr, msg_sys.msg_namelen, uaddr, uaddr_len);
+ if (err < 0)
+ goto out_freeiov;
+ }
+ err = __put_user(msg_sys.msg_flags, &msg->msg_flags);
+ if (err)
+ goto out_freeiov;
+ err = __put_user((unsigned long)msg_sys.msg_control-cmsg_ptr,
+ &msg->msg_controllen);
+ if (err)
+ goto out_freeiov;
+ err = len;
+
+out_freeiov:
+ if (iov != iovstack)
+ sock_kfree_s(sock->sk, iov, iov_size);
+out_put:
+ sockfd_put(sock);
+out:
+ return err;
+}
+
+
+/*
+ * Perform a file control on a socket file descriptor.
+ *
+ * Doesn't acquire a fd lock, because no network fcntl
+ * function sleeps currently.
+ */
+
+int sock_fcntl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ struct socket *sock;
+
+ sock = socki_lookup (filp->f_dentry->d_inode);
+ if (sock && sock->ops)
+ return sock_no_fcntl(sock, cmd, arg);
+ return(-EINVAL);
+}
+
+/* Argument list sizes for sys_socketcall */
+#define AL(x) ((x) * sizeof(unsigned long))
+static unsigned char nargs[18]={AL(0),AL(3),AL(3),AL(3),AL(2),AL(3),
+ AL(3),AL(3),AL(4),AL(4),AL(4),AL(6),
+ AL(6),AL(2),AL(5),AL(5),AL(3),AL(3)};
+#undef AL
+
+/*
+ * System call vectors.
+ *
+ * Argument checking cleaned up. Saved 20% in size.
+ * This function doesn't need to set the kernel lock because
+ * it is set by the callees.
+ */
+
+asmlinkage long sys_socketcall(int call, unsigned long *args)
+{
+ unsigned long a[6];
+ unsigned long a0,a1;
+ int err;
+
+ if(call<1||call>SYS_RECVMSG)
+ return -EINVAL;
+
+ /* copy_from_user should be SMP safe. */
+ if (copy_from_user(a, args, nargs[call]))
+ return -EFAULT;
+
+ a0=a[0];
+ a1=a[1];
+
+ switch(call)
+ {
+ case SYS_SOCKET:
+ err = sys_socket(a0,a1,a[2]);
+ break;
+ case SYS_BIND:
+ err = sys_bind(a0,(struct sockaddr *)a1, a[2]);
+ break;
+ case SYS_CONNECT:
+ err = sys_connect(a0, (struct sockaddr *)a1, a[2]);
+ break;
+ case SYS_LISTEN:
+ err = sys_listen(a0,a1);
+ break;
+ case SYS_ACCEPT:
+ err = sys_accept(a0,(struct sockaddr *)a1, (int *)a[2]);
+ break;
+ case SYS_GETSOCKNAME:
+ err = sys_getsockname(a0,(struct sockaddr *)a1, (int *)a[2]);
+ break;
+ case SYS_GETPEERNAME:
+ err = sys_getpeername(a0, (struct sockaddr *)a1, (int *)a[2]);
+ break;
+ case SYS_SOCKETPAIR:
+ err = sys_socketpair(a0,a1, a[2], (int *)a[3]);
+ break;
+ case SYS_SEND:
+ err = sys_send(a0, (void *)a1, a[2], a[3]);
+ break;
+ case SYS_SENDTO:
+ err = sys_sendto(a0,(void *)a1, a[2], a[3],
+ (struct sockaddr *)a[4], a[5]);
+ break;
+ case SYS_RECV:
+ err = sys_recv(a0, (void *)a1, a[2], a[3]);
+ break;
+ case SYS_RECVFROM:
+ err = sys_recvfrom(a0, (void *)a1, a[2], a[3],
+ (struct sockaddr *)a[4], (int *)a[5]);
+ break;
+ case SYS_SHUTDOWN:
+ err = sys_shutdown(a0,a1);
+ break;
+ case SYS_SETSOCKOPT:
+ err = sys_setsockopt(a0, a1, a[2], (char *)a[3], a[4]);
+ break;
+ case SYS_GETSOCKOPT:
+ err = sys_getsockopt(a0, a1, a[2], (char *)a[3], (int *)a[4]);
+ break;
+ case SYS_SENDMSG:
+ err = sys_sendmsg(a0, (struct msghdr *) a1, a[2]);
+ break;
+ case SYS_RECVMSG:
+ err = sys_recvmsg(a0, (struct msghdr *) a1, a[2]);
+ break;
+ default:
+ err = -EINVAL;
+ break;
+ }
+ return err;
+}
+
+/*
+ * This function is called by a protocol handler that wants to
+ * advertise its address family, and have it linked into the
+ * SOCKET module.
+ */
+
+int sock_register(struct net_proto_family *ops)
+{
+ int err;
+
+ if (ops->family >= NPROTO) {
+ printk(KERN_CRIT "protocol %d >= NPROTO(%d)\n", ops->family, NPROTO);
+ return -ENOBUFS;
+ }
+ net_family_write_lock();
+ err = -EEXIST;
+ if (net_families[ops->family] == NULL) {
+ net_families[ops->family]=ops;
+ err = 0;
+ }
+ net_family_write_unlock();
+ return err;
+}
+
+/*
+ * This function is called by a protocol handler that wants to
+ * remove its address family, and have it unlinked from the
+ * SOCKET module.
+ */
+
+int sock_unregister(int family)
+{
+ if (family < 0 || family >= NPROTO)
+ return -1;
+
+ net_family_write_lock();
+ net_families[family]=NULL;
+ net_family_write_unlock();
+ return 0;
+}
+
+
+extern void sk_init(void);
+
+#ifdef CONFIG_WAN_ROUTER
+extern void wanrouter_init(void);
+#endif
+
+#ifdef CONFIG_BLUEZ
+extern void bluez_init(void);
+#endif
+
+void __init sock_init(void)
+{
+ int i;
+
+ printk(KERN_INFO "Linux NET4.0 for Linux 2.4\n");
+ printk(KERN_INFO "Based upon Swansea University Computer Society NET3.039\n");
+
+ /*
+ * Initialize all address (protocol) families.
+ */
+
+ for (i = 0; i < NPROTO; i++)
+ net_families[i] = NULL;
+
+ /*
+ * Initialize sock SLAB cache.
+ */
+
+ sk_init();
+
+#ifdef SLAB_SKB
+ /*
+ * Initialize skbuff SLAB cache
+ */
+ skb_init();
+#endif
+
+ /*
+ * Wan router layer.
+ */
+
+#ifdef CONFIG_WAN_ROUTER
+ wanrouter_init();
+#endif
+
+ /*
+ * Initialize the protocols module.
+ */
+
+ register_filesystem(&sock_fs_type);
+ sock_mnt = kern_mount(&sock_fs_type);
+ /* The real protocol initialization is performed when
+ * do_initcalls is run.
+ */
+
+
+ /*
+ * The netlink device handler may be needed early.
+ */
+
+#ifdef CONFIG_NET
+ netlink_proto_init();
+ rtnetlink_init();
+#endif
+#ifdef CONFIG_NETLINK_DEV
+ init_netlink();
+#endif
+#ifdef CONFIG_NETFILTER
+ netfilter_init();
+#endif
+
+#ifdef CONFIG_BLUEZ
+ bluez_init();
+#endif
+}
+
+int socket_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ int len, cpu;
+ int counter = 0;
+
+ for (cpu=0; cpu<smp_num_cpus; cpu++)
+ counter += sockets_in_use[cpu_logical_map(cpu)].counter;
+
+ /* It can be negative, by the way. 8) */
+ if (counter < 0)
+ counter = 0;
+
+ len = sprintf(buffer, "sockets: used %d\n", counter);
+ if (offset >= len)
+ {
+ *start = buffer;
+ return 0;
+ }
+ *start = buffer + offset;
+ len -= offset;
+ if (len > length)
+ len = length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/Makefile b/uClinux-2.4.31-uc0/net/sunrpc/Makefile
new file mode 100644
index 0000000..3ddd8bf
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/Makefile
@@ -0,0 +1,24 @@
+#
+# Makefile for Linux kernel SUN RPC
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := sunrpc.o
+
+export-objs := sunrpc_syms.o
+
+obj-y := clnt.o xprt.o sched.o \
+ auth.o auth_null.o auth_unix.o \
+ svc.o svcsock.o svcauth.o \
+ pmap_clnt.o timer.o xdr.o sunrpc_syms.o
+
+obj-$(CONFIG_PROC_FS) += stats.o
+obj-$(CONFIG_SYSCTL) += sysctl.o
+
+obj-m := $(O_TARGET)
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/auth.c b/uClinux-2.4.31-uc0/net/sunrpc/auth.c
new file mode 100644
index 0000000..3a04a5f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/auth.c
@@ -0,0 +1,376 @@
+/*
+ * linux/fs/nfs/rpcauth.c
+ *
+ * Generic RPC authentication API.
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/socket.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/spinlock.h>
+
+#ifdef RPC_DEBUG
+# define RPCDBG_FACILITY RPCDBG_AUTH
+#endif
+
+#define RPC_MAXFLAVOR 8
+
+static struct rpc_authops * auth_flavors[RPC_MAXFLAVOR] = {
+ &authnull_ops, /* AUTH_NULL */
+ &authunix_ops, /* AUTH_UNIX */
+ NULL, /* others can be loadable modules */
+};
+
+int
+rpcauth_register(struct rpc_authops *ops)
+{
+ unsigned int flavor;
+
+ if ((flavor = ops->au_flavor) >= RPC_MAXFLAVOR)
+ return -EINVAL;
+ if (auth_flavors[flavor] != NULL)
+ return -EPERM; /* what else? */
+ auth_flavors[flavor] = ops;
+ return 0;
+}
+
+int
+rpcauth_unregister(struct rpc_authops *ops)
+{
+ unsigned int flavor;
+
+ if ((flavor = ops->au_flavor) >= RPC_MAXFLAVOR)
+ return -EINVAL;
+ if (auth_flavors[flavor] != ops)
+ return -EPERM; /* what else? */
+ auth_flavors[flavor] = NULL;
+ return 0;
+}
+
+struct rpc_auth *
+rpcauth_create(unsigned int flavor, struct rpc_clnt *clnt)
+{
+ struct rpc_authops *ops;
+
+ if (flavor >= RPC_MAXFLAVOR || !(ops = auth_flavors[flavor]))
+ return NULL;
+ clnt->cl_auth = ops->create(clnt);
+ return clnt->cl_auth;
+}
+
+void
+rpcauth_destroy(struct rpc_auth *auth)
+{
+ auth->au_ops->destroy(auth);
+}
+
+static spinlock_t rpc_credcache_lock = SPIN_LOCK_UNLOCKED;
+
+/*
+ * Initialize RPC credential cache
+ */
+void
+rpcauth_init_credcache(struct rpc_auth *auth)
+{
+ memset(auth->au_credcache, 0, sizeof(auth->au_credcache));
+ auth->au_nextgc = jiffies + (auth->au_expire >> 1);
+}
+
+/*
+ * Destroy an unreferenced credential
+ */
+static inline void
+rpcauth_crdestroy(struct rpc_cred *cred)
+{
+#ifdef RPC_DEBUG
+ if (cred->cr_magic != RPCAUTH_CRED_MAGIC)
+ BUG();
+ cred->cr_magic = 0;
+ if (atomic_read(&cred->cr_count) || cred->cr_auth)
+ BUG();
+#endif
+ cred->cr_ops->crdestroy(cred);
+}
+
+/*
+ * Destroy a list of credentials
+ */
+static inline
+void rpcauth_destroy_credlist(struct rpc_cred *head)
+{
+ struct rpc_cred *cred;
+
+ while ((cred = head) != NULL) {
+ head = cred->cr_next;
+ rpcauth_crdestroy(cred);
+ }
+}
+
+/*
+ * Clear the RPC credential cache, and delete those credentials
+ * that are not referenced.
+ */
+void
+rpcauth_free_credcache(struct rpc_auth *auth)
+{
+ struct rpc_cred **q, *cred, *free = NULL;
+ int i;
+
+ spin_lock(&rpc_credcache_lock);
+ for (i = 0; i < RPC_CREDCACHE_NR; i++) {
+ q = &auth->au_credcache[i];
+ while ((cred = *q) != NULL) {
+ *q = cred->cr_next;
+ cred->cr_auth = NULL;
+ if (atomic_read(&cred->cr_count) == 0) {
+ cred->cr_next = free;
+ free = cred;
+ } else
+ cred->cr_next = NULL;
+ }
+ }
+ spin_unlock(&rpc_credcache_lock);
+ rpcauth_destroy_credlist(free);
+}
+
+/*
+ * Remove stale credentials. Avoid sleeping inside the loop.
+ */
+static void
+rpcauth_gc_credcache(struct rpc_auth *auth)
+{
+ struct rpc_cred **q, *cred, *free = NULL;
+ int i;
+
+ dprintk("RPC: gc'ing RPC credentials for auth %p\n", auth);
+ spin_lock(&rpc_credcache_lock);
+ for (i = 0; i < RPC_CREDCACHE_NR; i++) {
+ q = &auth->au_credcache[i];
+ while ((cred = *q) != NULL) {
+ if (!atomic_read(&cred->cr_count) &&
+ time_before(cred->cr_expire, jiffies)) {
+ *q = cred->cr_next;
+ cred->cr_auth = NULL;
+ cred->cr_next = free;
+ free = cred;
+ continue;
+ }
+ q = &cred->cr_next;
+ }
+ }
+ spin_unlock(&rpc_credcache_lock);
+ rpcauth_destroy_credlist(free);
+ auth->au_nextgc = jiffies + auth->au_expire;
+}
+
+/*
+ * Insert credential into cache
+ */
+void
+rpcauth_insert_credcache(struct rpc_auth *auth, struct rpc_cred *cred)
+{
+ int nr;
+
+ nr = (cred->cr_uid & RPC_CREDCACHE_MASK);
+ spin_lock(&rpc_credcache_lock);
+ cred->cr_next = auth->au_credcache[nr];
+ auth->au_credcache[nr] = cred;
+ cred->cr_auth = auth;
+ get_rpccred(cred);
+ spin_unlock(&rpc_credcache_lock);
+}
+
+/*
+ * Look up a process' credentials in the authentication cache
+ */
+static struct rpc_cred *
+rpcauth_lookup_credcache(struct rpc_auth *auth, int taskflags)
+{
+ struct rpc_cred **q, *cred = NULL;
+ int nr = 0;
+
+ if (!(taskflags & RPC_TASK_ROOTCREDS))
+ nr = current->uid & RPC_CREDCACHE_MASK;
+
+ if (time_before(auth->au_nextgc, jiffies))
+ rpcauth_gc_credcache(auth);
+
+ spin_lock(&rpc_credcache_lock);
+ q = &auth->au_credcache[nr];
+ while ((cred = *q) != NULL) {
+ if (!(cred->cr_flags & RPCAUTH_CRED_DEAD) &&
+ cred->cr_ops->crmatch(cred, taskflags)) {
+ *q = cred->cr_next;
+ break;
+ }
+ q = &cred->cr_next;
+ }
+ spin_unlock(&rpc_credcache_lock);
+
+ if (!cred) {
+ cred = auth->au_ops->crcreate(taskflags);
+#ifdef RPC_DEBUG
+ if (cred)
+ cred->cr_magic = RPCAUTH_CRED_MAGIC;
+#endif
+ }
+
+ if (cred)
+ rpcauth_insert_credcache(auth, cred);
+
+ return (struct rpc_cred *) cred;
+}
+
+/*
+ * Remove cred handle from cache
+ */
+static void
+rpcauth_remove_credcache(struct rpc_cred *cred)
+{
+ struct rpc_auth *auth = cred->cr_auth;
+ struct rpc_cred **q, *cr;
+ int nr;
+
+ nr = (cred->cr_uid & RPC_CREDCACHE_MASK);
+ q = &auth->au_credcache[nr];
+ while ((cr = *q) != NULL) {
+ if (cred == cr) {
+ *q = cred->cr_next;
+ cred->cr_next = NULL;
+ cred->cr_auth = NULL;
+ break;
+ }
+ q = &cred->cr_next;
+ }
+}
+
+struct rpc_cred *
+rpcauth_lookupcred(struct rpc_auth *auth, int taskflags)
+{
+ dprintk("RPC: looking up %s cred\n",
+ auth->au_ops->au_name);
+ return rpcauth_lookup_credcache(auth, taskflags);
+}
+
+struct rpc_cred *
+rpcauth_bindcred(struct rpc_task *task)
+{
+ struct rpc_auth *auth = task->tk_auth;
+
+ dprintk("RPC: %4d looking up %s cred\n",
+ task->tk_pid, task->tk_auth->au_ops->au_name);
+ task->tk_msg.rpc_cred = rpcauth_lookup_credcache(auth, task->tk_flags);
+ if (task->tk_msg.rpc_cred == 0)
+ task->tk_status = -ENOMEM;
+ return task->tk_msg.rpc_cred;
+}
+
+int
+rpcauth_matchcred(struct rpc_auth *auth, struct rpc_cred *cred, int taskflags)
+{
+ dprintk("RPC: matching %s cred %d\n",
+ auth->au_ops->au_name, taskflags);
+ return cred->cr_ops->crmatch(cred, taskflags);
+}
+
+void
+rpcauth_holdcred(struct rpc_task *task)
+{
+ dprintk("RPC: %4d holding %s cred %p\n",
+ task->tk_pid, task->tk_auth->au_ops->au_name, task->tk_msg.rpc_cred);
+ if (task->tk_msg.rpc_cred)
+ get_rpccred(task->tk_msg.rpc_cred);
+}
+
+void
+put_rpccred(struct rpc_cred *cred)
+{
+ if (!atomic_dec_and_lock(&cred->cr_count, &rpc_credcache_lock))
+ return;
+
+ if (cred->cr_auth && cred->cr_flags & RPCAUTH_CRED_DEAD)
+ rpcauth_remove_credcache(cred);
+
+ if (!cred->cr_auth) {
+ spin_unlock(&rpc_credcache_lock);
+ rpcauth_crdestroy(cred);
+ return;
+ }
+ cred->cr_expire = jiffies + cred->cr_auth->au_expire;
+ spin_unlock(&rpc_credcache_lock);
+}
+
+void
+rpcauth_unbindcred(struct rpc_task *task)
+{
+ struct rpc_auth *auth = task->tk_auth;
+ struct rpc_cred *cred = task->tk_msg.rpc_cred;
+
+ dprintk("RPC: %4d releasing %s cred %p\n",
+ task->tk_pid, auth->au_ops->au_name, cred);
+
+ put_rpccred(cred);
+ task->tk_msg.rpc_cred = NULL;
+}
+
+u32 *
+rpcauth_marshcred(struct rpc_task *task, u32 *p)
+{
+ struct rpc_auth *auth = task->tk_auth;
+ struct rpc_cred *cred = task->tk_msg.rpc_cred;
+
+ dprintk("RPC: %4d marshaling %s cred %p\n",
+ task->tk_pid, auth->au_ops->au_name, cred);
+ return cred->cr_ops->crmarshal(task, p,
+ task->tk_flags & RPC_CALL_REALUID);
+}
+
+u32 *
+rpcauth_checkverf(struct rpc_task *task, u32 *p)
+{
+ struct rpc_auth *auth = task->tk_auth;
+ struct rpc_cred *cred = task->tk_msg.rpc_cred;
+
+ dprintk("RPC: %4d validating %s cred %p\n",
+ task->tk_pid, auth->au_ops->au_name, cred);
+ return cred->cr_ops->crvalidate(task, p);
+}
+
+int
+rpcauth_refreshcred(struct rpc_task *task)
+{
+ struct rpc_auth *auth = task->tk_auth;
+ struct rpc_cred *cred = task->tk_msg.rpc_cred;
+
+ dprintk("RPC: %4d refreshing %s cred %p\n",
+ task->tk_pid, auth->au_ops->au_name, cred);
+ task->tk_status = cred->cr_ops->crrefresh(task);
+ return task->tk_status;
+}
+
+void
+rpcauth_invalcred(struct rpc_task *task)
+{
+ dprintk("RPC: %4d invalidating %s cred %p\n",
+ task->tk_pid, task->tk_auth->au_ops->au_name, task->tk_msg.rpc_cred);
+ spin_lock(&rpc_credcache_lock);
+ if (task->tk_msg.rpc_cred)
+ task->tk_msg.rpc_cred->cr_flags &= ~RPCAUTH_CRED_UPTODATE;
+ spin_unlock(&rpc_credcache_lock);
+}
+
+int
+rpcauth_uptodatecred(struct rpc_task *task)
+{
+ int retval;
+ spin_lock(&rpc_credcache_lock);
+ retval = !(task->tk_msg.rpc_cred) ||
+ (task->tk_msg.rpc_cred->cr_flags & RPCAUTH_CRED_UPTODATE);
+ spin_unlock(&rpc_credcache_lock);
+ return retval;
+}
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/auth_null.c b/uClinux-2.4.31-uc0/net/sunrpc/auth_null.c
new file mode 100644
index 0000000..17e8370
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/auth_null.c
@@ -0,0 +1,140 @@
+/*
+ * linux/net/sunrpc/rpcauth_null.c
+ *
+ * AUTH_NULL authentication. Really :-)
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/utsname.h>
+#include <linux/sunrpc/clnt.h>
+
+#ifdef RPC_DEBUG
+# define RPCDBG_FACILITY RPCDBG_AUTH
+#endif
+
+static struct rpc_credops null_credops;
+
+static struct rpc_auth *
+nul_create(struct rpc_clnt *clnt)
+{
+ struct rpc_auth *auth;
+
+ dprintk("RPC: creating NULL authenticator for client %p\n", clnt);
+ if (!(auth = (struct rpc_auth *) rpc_allocate(0, sizeof(*auth))))
+ return NULL;
+ auth->au_cslack = 4;
+ auth->au_rslack = 2;
+ auth->au_ops = &authnull_ops;
+ auth->au_expire = 1800 * HZ;
+ rpcauth_init_credcache(auth);
+
+ return (struct rpc_auth *) auth;
+}
+
+static void
+nul_destroy(struct rpc_auth *auth)
+{
+ dprintk("RPC: destroying NULL authenticator %p\n", auth);
+ rpcauth_free_credcache(auth);
+ rpc_free(auth);
+}
+
+/*
+ * Create NULL creds for current process
+ */
+static struct rpc_cred *
+nul_create_cred(int flags)
+{
+ struct rpc_cred *cred;
+
+ if (!(cred = (struct rpc_cred *) rpc_allocate(flags, sizeof(*cred))))
+ return NULL;
+ atomic_set(&cred->cr_count, 0);
+ cred->cr_flags = RPCAUTH_CRED_UPTODATE;
+ cred->cr_uid = current->uid;
+ cred->cr_ops = &null_credops;
+
+ return cred;
+}
+
+/*
+ * Destroy cred handle.
+ */
+static void
+nul_destroy_cred(struct rpc_cred *cred)
+{
+ rpc_free(cred);
+}
+
+/*
+ * Match cred handle against current process
+ */
+static int
+nul_match(struct rpc_cred *cred, int taskflags)
+{
+ return 1;
+}
+
+/*
+ * Marshal credential.
+ */
+static u32 *
+nul_marshal(struct rpc_task *task, u32 *p, int ruid)
+{
+ *p++ = htonl(RPC_AUTH_NULL);
+ *p++ = 0;
+ *p++ = htonl(RPC_AUTH_NULL);
+ *p++ = 0;
+
+ return p;
+}
+
+/*
+ * Refresh credential. This is a no-op for AUTH_NULL
+ */
+static int
+nul_refresh(struct rpc_task *task)
+{
+ return task->tk_status = -EACCES;
+}
+
+static u32 *
+nul_validate(struct rpc_task *task, u32 *p)
+{
+ u32 n = ntohl(*p++);
+
+ if (n != RPC_AUTH_NULL) {
+ printk("RPC: bad verf flavor: %ld\n", (unsigned long) n);
+ return NULL;
+ }
+ if ((n = ntohl(*p++)) != 0) {
+ printk("RPC: bad verf size: %ld\n", (unsigned long) n);
+ return NULL;
+ }
+
+ return p;
+}
+
+struct rpc_authops authnull_ops = {
+ RPC_AUTH_NULL,
+#ifdef RPC_DEBUG
+ "NULL",
+#endif
+ nul_create,
+ nul_destroy,
+ nul_create_cred
+};
+
+static
+struct rpc_credops null_credops = {
+ nul_destroy_cred,
+ nul_match,
+ nul_marshal,
+ nul_refresh,
+ nul_validate
+};
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/auth_unix.c b/uClinux-2.4.31-uc0/net/sunrpc/auth_unix.c
new file mode 100644
index 0000000..cb0804f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/auth_unix.c
@@ -0,0 +1,252 @@
+/*
+ * linux/net/sunrpc/rpcauth_unix.c
+ *
+ * UNIX-style authentication; no AUTH_SHORT support
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/auth.h>
+
+#define NFS_NGROUPS 16
+struct unx_cred {
+ struct rpc_cred uc_base;
+ uid_t uc_fsuid;
+ gid_t uc_gid, uc_fsgid;
+ gid_t uc_gids[NFS_NGROUPS];
+};
+#define uc_uid uc_base.cr_uid
+#define uc_count uc_base.cr_count
+#define uc_flags uc_base.cr_flags
+#define uc_expire uc_base.cr_expire
+
+#define UNX_CRED_EXPIRE (60 * HZ)
+
+#define UNX_WRITESLACK (21 + (UNX_MAXNODENAME >> 2))
+
+#ifdef RPC_DEBUG
+# define RPCDBG_FACILITY RPCDBG_AUTH
+#endif
+
+static struct rpc_credops unix_credops;
+
+static struct rpc_auth *
+unx_create(struct rpc_clnt *clnt)
+{
+ struct rpc_auth *auth;
+
+ dprintk("RPC: creating UNIX authenticator for client %p\n", clnt);
+ if (!(auth = (struct rpc_auth *) rpc_allocate(0, sizeof(*auth))))
+ return NULL;
+ auth->au_cslack = UNX_WRITESLACK;
+ auth->au_rslack = 2; /* assume AUTH_NULL verf */
+ auth->au_expire = UNX_CRED_EXPIRE;
+ auth->au_ops = &authunix_ops;
+
+ rpcauth_init_credcache(auth);
+
+ return auth;
+}
+
+static void
+unx_destroy(struct rpc_auth *auth)
+{
+ dprintk("RPC: destroying UNIX authenticator %p\n", auth);
+ rpcauth_free_credcache(auth);
+ rpc_free(auth);
+}
+
+static struct rpc_cred *
+unx_create_cred(int flags)
+{
+ struct unx_cred *cred;
+ int i;
+
+ dprintk("RPC: allocating UNIX cred for uid %d gid %d\n",
+ current->uid, current->gid);
+
+ if (!(cred = (struct unx_cred *) rpc_allocate(flags, sizeof(*cred))))
+ return NULL;
+
+ atomic_set(&cred->uc_count, 0);
+ cred->uc_flags = RPCAUTH_CRED_UPTODATE;
+ if (flags & RPC_TASK_ROOTCREDS) {
+ cred->uc_uid = cred->uc_fsuid = 0;
+ cred->uc_gid = cred->uc_fsgid = 0;
+ cred->uc_gids[0] = NOGROUP;
+ } else {
+ int groups = current->ngroups;
+ if (groups > NFS_NGROUPS)
+ groups = NFS_NGROUPS;
+
+ cred->uc_uid = current->uid;
+ cred->uc_gid = current->gid;
+ cred->uc_fsuid = current->fsuid;
+ cred->uc_fsgid = current->fsgid;
+ for (i = 0; i < groups; i++)
+ cred->uc_gids[i] = (gid_t) current->groups[i];
+ if (i < NFS_NGROUPS)
+ cred->uc_gids[i] = NOGROUP;
+ }
+ cred->uc_base.cr_ops = &unix_credops;
+
+ return (struct rpc_cred *) cred;
+}
+
+struct rpc_cred *
+authunix_fake_cred(struct rpc_task *task, uid_t uid, gid_t gid)
+{
+ struct unx_cred *cred;
+
+ dprintk("RPC: allocating fake UNIX cred for uid %d gid %d\n",
+ uid, gid);
+
+ if (!(cred = (struct unx_cred *) rpc_malloc(task, sizeof(*cred))))
+ return NULL;
+
+ atomic_set(&cred->uc_count, 1);
+ cred->uc_flags = RPCAUTH_CRED_DEAD|RPCAUTH_CRED_UPTODATE;
+ cred->uc_uid = uid;
+ cred->uc_gid = gid;
+ cred->uc_fsuid = uid;
+ cred->uc_fsgid = gid;
+ cred->uc_gids[0] = (gid_t) NOGROUP;
+
+ return task->tk_msg.rpc_cred = (struct rpc_cred *) cred;
+}
+
+static void
+unx_destroy_cred(struct rpc_cred *cred)
+{
+ rpc_free(cred);
+}
+
+/*
+ * Match credentials against current process creds.
+ * The root_override argument takes care of cases where the caller may
+ * request root creds (e.g. for NFS swapping).
+ */
+static int
+unx_match(struct rpc_cred *rcred, int taskflags)
+{
+ struct unx_cred *cred = (struct unx_cred *) rcred;
+ int i;
+
+ if (!(taskflags & RPC_TASK_ROOTCREDS)) {
+ int groups;
+
+ if (cred->uc_uid != current->uid
+ || cred->uc_gid != current->gid
+ || cred->uc_fsuid != current->fsuid
+ || cred->uc_fsgid != current->fsgid)
+ return 0;
+
+ groups = current->ngroups;
+ if (groups > NFS_NGROUPS)
+ groups = NFS_NGROUPS;
+ for (i = 0; i < groups ; i++)
+ if (cred->uc_gids[i] != (gid_t) current->groups[i])
+ return 0;
+ return 1;
+ }
+ return (cred->uc_uid == 0 && cred->uc_fsuid == 0
+ && cred->uc_gid == 0 && cred->uc_fsgid == 0
+ && cred->uc_gids[0] == (gid_t) NOGROUP);
+}
+
+/*
+ * Marshal credentials.
+ * Maybe we should keep a cached credential for performance reasons.
+ */
+static u32 *
+unx_marshal(struct rpc_task *task, u32 *p, int ruid)
+{
+ struct rpc_clnt *clnt = task->tk_client;
+ struct unx_cred *cred = (struct unx_cred *) task->tk_msg.rpc_cred;
+ u32 *base, *hold;
+ int i, n;
+
+ *p++ = htonl(RPC_AUTH_UNIX);
+ base = p++;
+ *p++ = htonl(jiffies/HZ);
+
+ /*
+ * Copy the UTS nodename captured when the client was created.
+ */
+ n = clnt->cl_nodelen;
+ *p++ = htonl(n);
+ memcpy(p, clnt->cl_nodename, n);
+ p += (n + 3) >> 2;
+
+ /* Note: we don't use real uid if it involves raising priviledge */
+ if (ruid && cred->uc_uid != 0 && cred->uc_gid != 0) {
+ *p++ = htonl((u32) cred->uc_uid);
+ *p++ = htonl((u32) cred->uc_gid);
+ } else {
+ *p++ = htonl((u32) cred->uc_fsuid);
+ *p++ = htonl((u32) cred->uc_fsgid);
+ }
+ hold = p++;
+ for (i = 0; i < 16 && cred->uc_gids[i] != (gid_t) NOGROUP; i++)
+ *p++ = htonl((u32) cred->uc_gids[i]);
+ *hold = htonl(p - hold - 1); /* gid array length */
+ *base = htonl((p - base - 1) << 2); /* cred length */
+
+ *p++ = htonl(RPC_AUTH_NULL);
+ *p++ = htonl(0);
+
+ return p;
+}
+
+/*
+ * Refresh credentials. This is a no-op for AUTH_UNIX
+ */
+static int
+unx_refresh(struct rpc_task *task)
+{
+ task->tk_msg.rpc_cred->cr_flags |= RPCAUTH_CRED_UPTODATE;
+ return task->tk_status = -EACCES;
+}
+
+static u32 *
+unx_validate(struct rpc_task *task, u32 *p)
+{
+ u32 n = ntohl(*p++);
+
+ if (n != RPC_AUTH_NULL && n != RPC_AUTH_UNIX && n != RPC_AUTH_SHORT) {
+ printk("RPC: bad verf flavor: %ld\n", (unsigned long) n);
+ return NULL;
+ }
+ if ((n = ntohl(*p++)) > 400) {
+ printk("RPC: giant verf size: %ld\n", (unsigned long) n);
+ return NULL;
+ }
+ task->tk_auth->au_rslack = (n >> 2) + 2;
+ p += (n >> 2);
+
+ return p;
+}
+
+struct rpc_authops authunix_ops = {
+ RPC_AUTH_UNIX,
+#ifdef RPC_DEBUG
+ "UNIX",
+#endif
+ unx_create,
+ unx_destroy,
+ unx_create_cred
+};
+
+static
+struct rpc_credops unix_credops = {
+ unx_destroy_cred,
+ unx_match,
+ unx_marshal,
+ unx_refresh,
+ unx_validate
+};
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/clnt.c b/uClinux-2.4.31-uc0/net/sunrpc/clnt.c
new file mode 100644
index 0000000..ef152eb
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/clnt.c
@@ -0,0 +1,962 @@
+/*
+ * linux/net/sunrpc/rpcclnt.c
+ *
+ * This file contains the high-level RPC interface.
+ * It is modeled as a finite state machine to support both synchronous
+ * and asynchronous requests.
+ *
+ * - RPC header generation and argument serialization.
+ * - Credential refresh.
+ * - TCP reconnect handling (when finished).
+ * - Retry of operation when it is suspected the operation failed because
+ * of uid squashing on the server, or when the credentials were stale
+ * and need to be refreshed, or when a packet was damaged in transit.
+ * This may be have to be moved to the VFS layer.
+ *
+ * NB: BSD uses a more intelligent approach to guessing when a request
+ * or reply has been lost by keeping the RTO estimate for each procedure.
+ * We currently make do with a constant timeout value.
+ *
+ * Copyright (C) 1992,1993 Rick Sladkey <jrs@world.std.com>
+ * Copyright (C) 1995,1996 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <asm/system.h>
+
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/in.h>
+#include <linux/utsname.h>
+
+#include <linux/sunrpc/clnt.h>
+
+#include <linux/nfs.h>
+
+
+#define RPC_SLACK_SPACE 512 /* total overkill */
+
+#ifdef RPC_DEBUG
+# define RPCDBG_FACILITY RPCDBG_CALL
+#endif
+
+static DECLARE_WAIT_QUEUE_HEAD(destroy_wait);
+
+
+static void call_start(struct rpc_task *task);
+static void call_reserve(struct rpc_task *task);
+static void call_reserveresult(struct rpc_task *task);
+static void call_allocate(struct rpc_task *task);
+static void call_encode(struct rpc_task *task);
+static void call_decode(struct rpc_task *task);
+static void call_bind(struct rpc_task *task);
+static void call_transmit(struct rpc_task *task);
+static void call_status(struct rpc_task *task);
+static void call_refresh(struct rpc_task *task);
+static void call_refreshresult(struct rpc_task *task);
+static void call_timeout(struct rpc_task *task);
+static void call_connect(struct rpc_task *task);
+static void call_connect_status(struct rpc_task *);
+static u32 * call_header(struct rpc_task *task);
+static u32 * call_verify(struct rpc_task *task);
+
+
+/*
+ * Create an RPC client
+ * FIXME: This should also take a flags argument (as in task->tk_flags).
+ * It's called (among others) from pmap_create_client, which may in
+ * turn be called by an async task. In this case, rpciod should not be
+ * made to sleep too long.
+ */
+struct rpc_clnt *
+rpc_create_client(struct rpc_xprt *xprt, char *servname,
+ struct rpc_program *program, u32 vers, int flavor)
+{
+ struct rpc_version *version;
+ struct rpc_clnt *clnt = NULL;
+
+ dprintk("RPC: creating %s client for %s (xprt %p)\n",
+ program->name, servname, xprt);
+
+ if (!xprt)
+ goto out;
+ if (vers >= program->nrvers || !(version = program->version[vers]))
+ goto out;
+
+ clnt = (struct rpc_clnt *) rpc_allocate(0, sizeof(*clnt));
+ if (!clnt)
+ goto out_no_clnt;
+ memset(clnt, 0, sizeof(*clnt));
+ atomic_set(&clnt->cl_users, 0);
+
+ clnt->cl_xprt = xprt;
+ clnt->cl_procinfo = version->procs;
+ clnt->cl_maxproc = version->nrprocs;
+ clnt->cl_server = servname;
+ clnt->cl_protname = program->name;
+ clnt->cl_port = xprt->addr.sin_port;
+ clnt->cl_prog = program->number;
+ clnt->cl_vers = version->number;
+ clnt->cl_prot = xprt->prot;
+ clnt->cl_stats = program->stats;
+ INIT_RPC_WAITQ(&clnt->cl_bindwait, "bindwait");
+
+ if (!clnt->cl_port)
+ clnt->cl_autobind = 1;
+
+ rpc_init_rtt(&clnt->cl_rtt, xprt->timeout.to_initval);
+
+ if (!rpcauth_create(flavor, clnt))
+ goto out_no_auth;
+
+ /* save the nodename */
+ clnt->cl_nodelen = strlen(system_utsname.nodename);
+ if (clnt->cl_nodelen > UNX_MAXNODENAME)
+ clnt->cl_nodelen = UNX_MAXNODENAME;
+ memcpy(clnt->cl_nodename, system_utsname.nodename, clnt->cl_nodelen);
+out:
+ return clnt;
+
+out_no_clnt:
+ printk(KERN_INFO "RPC: out of memory in rpc_create_client\n");
+ goto out;
+out_no_auth:
+ printk(KERN_INFO "RPC: Couldn't create auth handle (flavor %d)\n",
+ flavor);
+ rpc_free(clnt);
+ clnt = NULL;
+ goto out;
+}
+
+/*
+ * Properly shut down an RPC client, terminating all outstanding
+ * requests. Note that we must be certain that cl_oneshot and
+ * cl_dead are cleared, or else the client would be destroyed
+ * when the last task releases it.
+ */
+int
+rpc_shutdown_client(struct rpc_clnt *clnt)
+{
+ dprintk("RPC: shutting down %s client for %s\n",
+ clnt->cl_protname, clnt->cl_server);
+ while (atomic_read(&clnt->cl_users)) {
+#ifdef RPC_DEBUG
+ dprintk("RPC: rpc_shutdown_client: client %s, tasks=%d\n",
+ clnt->cl_protname, atomic_read(&clnt->cl_users));
+#endif
+ /* Don't let rpc_release_client destroy us */
+ clnt->cl_oneshot = 0;
+ clnt->cl_dead = 0;
+ rpc_killall_tasks(clnt);
+ sleep_on_timeout(&destroy_wait, 1*HZ);
+ }
+ return rpc_destroy_client(clnt);
+}
+
+/*
+ * Delete an RPC client
+ */
+int
+rpc_destroy_client(struct rpc_clnt *clnt)
+{
+ dprintk("RPC: destroying %s client for %s\n",
+ clnt->cl_protname, clnt->cl_server);
+
+ if (clnt->cl_auth) {
+ rpcauth_destroy(clnt->cl_auth);
+ clnt->cl_auth = NULL;
+ }
+ if (clnt->cl_xprt) {
+ xprt_destroy(clnt->cl_xprt);
+ clnt->cl_xprt = NULL;
+ }
+ rpc_free(clnt);
+ return 0;
+}
+
+/*
+ * Release an RPC client
+ */
+void
+rpc_release_client(struct rpc_clnt *clnt)
+{
+ dprintk("RPC: rpc_release_client(%p, %d)\n",
+ clnt, atomic_read(&clnt->cl_users));
+
+ if (!atomic_dec_and_test(&clnt->cl_users))
+ return;
+ wake_up(&destroy_wait);
+ if (clnt->cl_oneshot || clnt->cl_dead)
+ rpc_destroy_client(clnt);
+}
+
+/*
+ * Default callback for async RPC calls
+ */
+static void
+rpc_default_callback(struct rpc_task *task)
+{
+}
+
+/*
+ * Export the signal mask handling for aysnchronous code that
+ * sleeps on RPC calls
+ */
+
+void rpc_clnt_sigmask(struct rpc_clnt *clnt, sigset_t *oldset)
+{
+ unsigned long sigallow = sigmask(SIGKILL);
+ unsigned long irqflags;
+
+ /* Turn off various signals */
+ if (clnt->cl_intr) {
+ struct k_sigaction *action = current->sig->action;
+ if (action[SIGINT-1].sa.sa_handler == SIG_DFL)
+ sigallow |= sigmask(SIGINT);
+ if (action[SIGQUIT-1].sa.sa_handler == SIG_DFL)
+ sigallow |= sigmask(SIGQUIT);
+ }
+ spin_lock_irqsave(&current->sigmask_lock, irqflags);
+ *oldset = current->blocked;
+ siginitsetinv(&current->blocked, sigallow & ~oldset->sig[0]);
+ recalc_sigpending(current);
+ spin_unlock_irqrestore(&current->sigmask_lock, irqflags);
+}
+
+void rpc_clnt_sigunmask(struct rpc_clnt *clnt, sigset_t *oldset)
+{
+ unsigned long irqflags;
+
+ spin_lock_irqsave(&current->sigmask_lock, irqflags);
+ current->blocked = *oldset;
+ recalc_sigpending(current);
+ spin_unlock_irqrestore(&current->sigmask_lock, irqflags);
+}
+
+/*
+ * New rpc_call implementation
+ */
+int rpc_call_sync(struct rpc_clnt *clnt, struct rpc_message *msg, int flags)
+{
+ struct rpc_task my_task, *task = &my_task;
+ sigset_t oldset;
+ int status;
+
+ /* If this client is slain all further I/O fails */
+ if (clnt->cl_dead)
+ return -EIO;
+
+ if (flags & RPC_TASK_ASYNC) {
+ printk("rpc_call_sync: Illegal flag combination for synchronous task\n");
+ flags &= ~RPC_TASK_ASYNC;
+ }
+
+ rpc_clnt_sigmask(clnt, &oldset);
+
+ /* Create/initialize a new RPC task */
+ rpc_init_task(task, clnt, NULL, flags);
+ rpc_call_setup(task, msg, 0);
+
+ /* Set up the call info struct and execute the task */
+ if (task->tk_status == 0)
+ status = rpc_execute(task);
+ else {
+ status = task->tk_status;
+ rpc_release_task(task);
+ }
+
+ rpc_clnt_sigunmask(clnt, &oldset);
+
+ return status;
+}
+
+/*
+ * New rpc_call implementation
+ */
+int
+rpc_call_async(struct rpc_clnt *clnt, struct rpc_message *msg, int flags,
+ rpc_action callback, void *data)
+{
+ struct rpc_task *task;
+ sigset_t oldset;
+ int status;
+
+ /* If this client is slain all further I/O fails */
+ if (clnt->cl_dead)
+ return -EIO;
+
+ flags |= RPC_TASK_ASYNC;
+
+ rpc_clnt_sigmask(clnt, &oldset);
+
+ /* Create/initialize a new RPC task */
+ if (!callback)
+ callback = rpc_default_callback;
+ status = -ENOMEM;
+ if (!(task = rpc_new_task(clnt, callback, flags)))
+ goto out;
+ task->tk_calldata = data;
+
+ rpc_call_setup(task, msg, 0);
+
+ /* Set up the call info struct and execute the task */
+ if (task->tk_status == 0)
+ status = rpc_execute(task);
+ else {
+ status = task->tk_status;
+ rpc_release_task(task);
+ }
+
+out:
+ rpc_clnt_sigunmask(clnt, &oldset);
+
+ return status;
+}
+
+
+void
+rpc_call_setup(struct rpc_task *task, struct rpc_message *msg, int flags)
+{
+ task->tk_msg = *msg;
+ task->tk_flags |= flags;
+ /* Bind the user cred */
+ if (task->tk_msg.rpc_cred != NULL) {
+ rpcauth_holdcred(task);
+ } else
+ rpcauth_bindcred(task);
+
+ if (task->tk_status == 0)
+ task->tk_action = call_start;
+ else
+ task->tk_action = NULL;
+}
+
+void
+rpc_setbufsize(struct rpc_clnt *clnt, unsigned int sndsize, unsigned int rcvsize)
+{
+ struct rpc_xprt *xprt = clnt->cl_xprt;
+
+ xprt->sndsize = 0;
+ if (sndsize)
+ xprt->sndsize = sndsize + RPC_SLACK_SPACE;
+ xprt->rcvsize = 0;
+ if (rcvsize)
+ xprt->rcvsize = rcvsize + RPC_SLACK_SPACE;
+ xprt_sock_setbufsize(xprt);
+}
+
+/*
+ * Restart an (async) RPC call. Usually called from within the
+ * exit handler.
+ */
+void
+rpc_restart_call(struct rpc_task *task)
+{
+ if (RPC_ASSASSINATED(task))
+ return;
+
+ task->tk_action = call_start;
+}
+
+/*
+ * 0. Initial state
+ *
+ * Other FSM states can be visited zero or more times, but
+ * this state is visited exactly once for each RPC.
+ */
+static void
+call_start(struct rpc_task *task)
+{
+ struct rpc_clnt *clnt = task->tk_client;
+
+ if (task->tk_msg.rpc_proc > clnt->cl_maxproc) {
+ printk(KERN_ERR "%s (vers %d): bad procedure number %d\n",
+ clnt->cl_protname, clnt->cl_vers,
+ task->tk_msg.rpc_proc);
+ rpc_exit(task, -EIO);
+ return;
+ }
+
+ dprintk("RPC: %4d call_start %s%d proc %d (%s)\n", task->tk_pid,
+ clnt->cl_protname, clnt->cl_vers, task->tk_msg.rpc_proc,
+ (RPC_IS_ASYNC(task) ? "async" : "sync"));
+
+ /* Increment call count */
+ rpcproc_count(clnt, task->tk_msg.rpc_proc)++;
+ clnt->cl_stats->rpccnt++;
+ task->tk_action = call_reserve;
+}
+
+/*
+ * 1. Reserve an RPC call slot
+ */
+static void
+call_reserve(struct rpc_task *task)
+{
+ dprintk("RPC: %4d call_reserve\n", task->tk_pid);
+
+ if (!rpcauth_uptodatecred(task)) {
+ task->tk_action = call_refresh;
+ return;
+ }
+
+ task->tk_status = 0;
+ task->tk_action = call_reserveresult;
+ xprt_reserve(task);
+}
+
+/*
+ * 1b. Grok the result of xprt_reserve()
+ */
+static void
+call_reserveresult(struct rpc_task *task)
+{
+ int status = task->tk_status;
+
+ dprintk("RPC: %4d call_reserveresult (status %d)\n",
+ task->tk_pid, task->tk_status);
+
+ /*
+ * After a call to xprt_reserve(), we must have either
+ * a request slot or else an error status.
+ */
+ task->tk_status = 0;
+ if (status >= 0) {
+ if (task->tk_rqstp) {
+ task->tk_action = call_allocate;
+ return;
+ }
+
+ printk(KERN_ERR "%s: status=%d, but no request slot, exiting\n",
+ __FUNCTION__, status);
+ rpc_exit(task, -EIO);
+ return;
+ }
+
+ /*
+ * Even though there was an error, we may have acquired
+ * a request slot somehow. Make sure not to leak it.
+ */
+ if (task->tk_rqstp) {
+ printk(KERN_ERR "%s: status=%d, request allocated anyway\n",
+ __FUNCTION__, status);
+ xprt_release(task);
+ }
+
+ switch (status) {
+ case -EAGAIN: /* woken up; retry */
+ task->tk_action = call_reserve;
+ return;
+ case -EIO: /* probably a shutdown */
+ break;
+ default:
+ printk(KERN_ERR "%s: unrecognized error %d, exiting\n",
+ __FUNCTION__, status);
+ break;
+ }
+ rpc_exit(task, status);
+}
+
+/*
+ * 2. Allocate the buffer. For details, see sched.c:rpc_malloc.
+ * (Note: buffer memory is freed in rpc_task_release).
+ */
+static void
+call_allocate(struct rpc_task *task)
+{
+ struct rpc_clnt *clnt = task->tk_client;
+ unsigned int bufsiz;
+
+ dprintk("RPC: %4d call_allocate (status %d)\n",
+ task->tk_pid, task->tk_status);
+ task->tk_action = call_encode;
+ if (task->tk_buffer)
+ return;
+
+ /* FIXME: compute buffer requirements more exactly using
+ * auth->au_wslack */
+ bufsiz = rpcproc_bufsiz(clnt, task->tk_msg.rpc_proc) + RPC_SLACK_SPACE;
+
+ if ((task->tk_buffer = rpc_malloc(task, bufsiz << 1)) != NULL)
+ return;
+ printk(KERN_INFO "RPC: buffer allocation failed for task %p\n", task);
+
+ if (RPC_IS_ASYNC(task) || !(task->tk_client->cl_intr && signalled())) {
+ xprt_release(task);
+ task->tk_action = call_reserve;
+ rpc_delay(task, HZ>>4);
+ return;
+ }
+
+ rpc_exit(task, -ERESTARTSYS);
+}
+
+/*
+ * 3. Encode arguments of an RPC call
+ */
+static void
+call_encode(struct rpc_task *task)
+{
+ struct rpc_clnt *clnt = task->tk_client;
+ struct rpc_rqst *req = task->tk_rqstp;
+ struct xdr_buf *sndbuf = &req->rq_snd_buf;
+ struct xdr_buf *rcvbuf = &req->rq_rcv_buf;
+ unsigned int bufsiz;
+ kxdrproc_t encode;
+ int status;
+ u32 *p;
+
+ dprintk("RPC: %4d call_encode (status %d)\n",
+ task->tk_pid, task->tk_status);
+
+ task->tk_action = call_bind;
+
+ /* Default buffer setup */
+ bufsiz = rpcproc_bufsiz(clnt, task->tk_msg.rpc_proc)+RPC_SLACK_SPACE;
+ sndbuf->head[0].iov_base = (void *)task->tk_buffer;
+ sndbuf->head[0].iov_len = bufsiz;
+ sndbuf->tail[0].iov_len = 0;
+ sndbuf->page_len = 0;
+ sndbuf->len = 0;
+ rcvbuf->head[0].iov_base = (void *)((char *)task->tk_buffer + bufsiz);
+ rcvbuf->head[0].iov_len = bufsiz;
+ rcvbuf->tail[0].iov_len = 0;
+ rcvbuf->page_len = 0;
+ rcvbuf->len = bufsiz;
+
+ /* Zero buffer so we have automatic zero-padding of opaque & string */
+ memset(task->tk_buffer, 0, bufsiz);
+
+ /* Encode header and provided arguments */
+ encode = rpcproc_encode(clnt, task->tk_msg.rpc_proc);
+ if (!(p = call_header(task))) {
+ printk(KERN_INFO "RPC: call_header failed, exit EIO\n");
+ rpc_exit(task, -EIO);
+ } else
+ if (encode && (status = encode(req, p, task->tk_msg.rpc_argp)) < 0) {
+ printk(KERN_WARNING "%s: can't encode arguments: %d\n",
+ clnt->cl_protname, -status);
+ rpc_exit(task, status);
+ }
+}
+
+/*
+ * 4. Get the server port number if not yet set
+ */
+static void
+call_bind(struct rpc_task *task)
+{
+ struct rpc_clnt *clnt = task->tk_client;
+ struct rpc_xprt *xprt = clnt->cl_xprt;
+
+ dprintk("RPC: %4d call_bind xprt %p %s connected\n", task->tk_pid,
+ xprt, (xprt_connected(xprt) ? "is" : "is not"));
+
+ task->tk_action = (xprt_connected(xprt)) ? call_transmit : call_connect;
+
+ if (!clnt->cl_port) {
+ task->tk_action = call_connect;
+ task->tk_timeout = clnt->cl_timeout.to_maxval;
+ rpc_getport(task, clnt);
+ }
+}
+
+/*
+ * 4a. Establish socket
+ * Connect to the RPC server (TCP case)
+ */
+static void
+call_connect(struct rpc_task *task)
+{
+ struct rpc_clnt *clnt = task->tk_client;
+
+ dprintk("RPC: %4d call_connect status %d\n",
+ task->tk_pid, task->tk_status);
+
+ if (xprt_connected(clnt->cl_xprt)) {
+ task->tk_action = call_transmit;
+ return;
+ }
+ task->tk_action = call_connect_status;
+ if (task->tk_status < 0)
+ return;
+ xprt_connect(task);
+}
+
+/*
+ * 4b. Sort out reconnection result
+ */
+static void call_connect_status(struct rpc_task *task)
+{
+ struct rpc_clnt *clnt = task->tk_client;
+ int status = task->tk_status;
+
+ task->tk_status = 0;
+ if (status >= 0) {
+ clnt->cl_stats->netreconn++;
+ task->tk_action = call_transmit;
+ return;
+ }
+
+ /* Something failed: we may have to rebind */
+ if (clnt->cl_autobind)
+ clnt->cl_port = 0;
+ switch (status) {
+ case -ECONNREFUSED:
+ case -ECONNRESET:
+ case -ENOTCONN:
+ case -ETIMEDOUT:
+ case -EAGAIN:
+ task->tk_action = (clnt->cl_port == 0) ? call_bind : call_connect;
+ break;
+ default:
+ rpc_exit(task, status);
+ }
+}
+
+/*
+ * 5. Transmit the RPC request, and wait for reply
+ */
+static void
+call_transmit(struct rpc_task *task)
+{
+ struct rpc_clnt *clnt = task->tk_client;
+
+ dprintk("RPC: %4d call_transmit (status %d)\n",
+ task->tk_pid, task->tk_status);
+
+ task->tk_action = call_status;
+ if (task->tk_status < 0)
+ return;
+ xprt_transmit(task);
+ if (!rpcproc_decode(clnt, task->tk_msg.rpc_proc) && task->tk_status >= 0) {
+ task->tk_action = NULL;
+ rpc_wake_up_task(task);
+ }
+}
+
+/*
+ * 6. Sort out the RPC call status
+ */
+static void
+call_status(struct rpc_task *task)
+{
+ struct rpc_clnt *clnt = task->tk_client;
+ struct rpc_xprt *xprt = clnt->cl_xprt;
+ struct rpc_rqst *req = task->tk_rqstp;
+ int status;
+
+ smp_rmb();
+ if (req->rq_received > 0 && !req->rq_bytes_sent)
+ task->tk_status = req->rq_received;
+
+ dprintk("RPC: %4d call_status (status %d)\n",
+ task->tk_pid, task->tk_status);
+
+ status = task->tk_status;
+ if (status >= 0) {
+ task->tk_action = call_decode;
+ return;
+ }
+
+ task->tk_status = 0;
+ switch(status) {
+ case -ETIMEDOUT:
+ task->tk_action = call_timeout;
+ break;
+ case -ECONNREFUSED:
+ case -ENOTCONN:
+ req->rq_bytes_sent = 0;
+ if (clnt->cl_autobind || !clnt->cl_port) {
+ clnt->cl_port = 0;
+ task->tk_action = call_bind;
+ break;
+ }
+ task->tk_action = call_connect;
+ break;
+ /*
+ * Sleep and dream of an open connection
+ */
+ task->tk_timeout = 5 * HZ;
+ rpc_sleep_on(&xprt->sending, task, NULL, NULL);
+ case -ENOMEM:
+ case -EAGAIN:
+ task->tk_action = call_transmit;
+ break;
+ default:
+ if (clnt->cl_chatty)
+ printk("%s: RPC call returned error %d\n",
+ clnt->cl_protname, -status);
+ rpc_exit(task, status);
+ }
+}
+
+/*
+ * 6a. Handle RPC timeout
+ * We do not release the request slot, so we keep using the
+ * same XID for all retransmits.
+ */
+static void
+call_timeout(struct rpc_task *task)
+{
+ struct rpc_clnt *clnt = task->tk_client;
+ struct rpc_timeout *to = &task->tk_rqstp->rq_timeout;
+
+ if (xprt_adjust_timeout(to)) {
+ dprintk("RPC: %4d call_timeout (minor)\n", task->tk_pid);
+ goto retry;
+ }
+ to->to_retries = clnt->cl_timeout.to_retries;
+
+ dprintk("RPC: %4d call_timeout (major)\n", task->tk_pid);
+ if (clnt->cl_softrtry) {
+ if (clnt->cl_chatty)
+ printk(KERN_NOTICE "%s: server %s not responding, timed out\n",
+ clnt->cl_protname, clnt->cl_server);
+ rpc_exit(task, -EIO);
+ return;
+ }
+
+ if (clnt->cl_chatty && !(task->tk_flags & RPC_CALL_MAJORSEEN)) {
+ task->tk_flags |= RPC_CALL_MAJORSEEN;
+ printk(KERN_NOTICE "%s: server %s not responding, still trying\n",
+ clnt->cl_protname, clnt->cl_server);
+ }
+ if (clnt->cl_autobind)
+ clnt->cl_port = 0;
+
+retry:
+ clnt->cl_stats->rpcretrans++;
+ task->tk_action = call_bind;
+ task->tk_status = 0;
+}
+
+/*
+ * 7. Decode the RPC reply
+ */
+static void
+call_decode(struct rpc_task *task)
+{
+ struct rpc_clnt *clnt = task->tk_client;
+ struct rpc_rqst *req = task->tk_rqstp;
+ kxdrproc_t decode = rpcproc_decode(clnt, task->tk_msg.rpc_proc);
+ u32 *p;
+
+ dprintk("RPC: %4d call_decode (status %d)\n",
+ task->tk_pid, task->tk_status);
+
+ if (clnt->cl_chatty && (task->tk_flags & RPC_CALL_MAJORSEEN)) {
+ printk(KERN_NOTICE "%s: server %s OK\n",
+ clnt->cl_protname, clnt->cl_server);
+ task->tk_flags &= ~RPC_CALL_MAJORSEEN;
+ }
+
+ if (task->tk_status < 12) {
+ if (!clnt->cl_softrtry) {
+ task->tk_action = call_transmit;
+ clnt->cl_stats->rpcretrans++;
+ goto out_retry;
+ }
+ printk(KERN_WARNING "%s: too small RPC reply size (%d bytes)\n",
+ clnt->cl_protname, task->tk_status);
+ rpc_exit(task, -EIO);
+ return;
+ }
+
+ /* Check that the softirq receive buffer is valid */
+ if (unlikely(memcmp(&req->rq_rcv_buf, &req->rq_private_buf,
+ sizeof(req->rq_rcv_buf)) != 0))
+ printk(KERN_WARNING "%s: receive buffer is inconsistent. Please contact maintainer.\n",
+ __FUNCTION__);
+
+ /* Verify the RPC header */
+ if (!(p = call_verify(task))) {
+ /*
+ * When call_verfiy sets tk_action to NULL (via task_exit)
+ * a non-retry-able error has occurred (like the server
+ * not supporting a particular procedure call).
+ */
+ if (task->tk_action == NULL)
+ return;
+ goto out_retry;
+ }
+ /*
+ * The following is an NFS-specific hack to cater for setuid
+ * processes whose uid is mapped to nobody on the server.
+ */
+ if (task->tk_client->cl_droppriv &&
+ (ntohl(*p) == NFSERR_ACCES || ntohl(*p) == NFSERR_PERM)) {
+ if (RPC_IS_SETUID(task) && task->tk_suid_retry) {
+ dprintk("RPC: %4d retry squashed uid\n", task->tk_pid);
+ task->tk_flags ^= RPC_CALL_REALUID;
+ task->tk_action = call_encode;
+ task->tk_suid_retry--;
+ goto out_retry;
+ }
+ }
+
+ task->tk_action = NULL;
+
+ if (decode)
+ task->tk_status = decode(req, p, task->tk_msg.rpc_resp);
+ dprintk("RPC: %4d call_decode result %d\n", task->tk_pid,
+ task->tk_status);
+ return;
+out_retry:
+ req->rq_received = 0;
+ task->tk_status = 0;
+}
+
+/*
+ * 8. Refresh the credentials if rejected by the server
+ */
+static void
+call_refresh(struct rpc_task *task)
+{
+ dprintk("RPC: %4d call_refresh\n", task->tk_pid);
+
+ xprt_release(task); /* Must do to obtain new XID */
+ task->tk_action = call_refreshresult;
+ task->tk_status = 0;
+ task->tk_client->cl_stats->rpcauthrefresh++;
+ rpcauth_refreshcred(task);
+}
+
+/*
+ * 8a. Process the results of a credential refresh
+ */
+static void
+call_refreshresult(struct rpc_task *task)
+{
+ dprintk("RPC: %4d call_refreshresult (status %d)\n",
+ task->tk_pid, task->tk_status);
+
+ if (task->tk_status < 0)
+ rpc_exit(task, -EACCES);
+ else
+ task->tk_action = call_reserve;
+}
+
+/*
+ * Call header serialization
+ */
+static u32 *
+call_header(struct rpc_task *task)
+{
+ struct rpc_clnt *clnt = task->tk_client;
+ struct rpc_xprt *xprt = clnt->cl_xprt;
+ struct rpc_rqst *req = task->tk_rqstp;
+ u32 *p = req->rq_svec[0].iov_base;
+
+ /* FIXME: check buffer size? */
+ if (xprt->stream)
+ *p++ = 0; /* fill in later */
+ *p++ = req->rq_xid; /* XID */
+ *p++ = htonl(RPC_CALL); /* CALL */
+ *p++ = htonl(RPC_VERSION); /* RPC version */
+ *p++ = htonl(clnt->cl_prog); /* program number */
+ *p++ = htonl(clnt->cl_vers); /* program version */
+ *p++ = htonl(task->tk_msg.rpc_proc); /* procedure */
+ return rpcauth_marshcred(task, p);
+}
+
+/*
+ * Reply header verification
+ */
+static u32 *
+call_verify(struct rpc_task *task)
+{
+ u32 *p = task->tk_rqstp->rq_rvec[0].iov_base, n;
+
+ p += 1; /* skip XID */
+
+ if ((n = ntohl(*p++)) != RPC_REPLY) {
+ printk(KERN_WARNING "call_verify: not an RPC reply: %x\n", n);
+ goto garbage;
+ }
+ if ((n = ntohl(*p++)) != RPC_MSG_ACCEPTED) {
+ int error = -EACCES;
+
+ if ((n = ntohl(*p++)) != RPC_AUTH_ERROR) {
+ printk(KERN_WARNING "call_verify: RPC call rejected: %x\n", n);
+ } else
+ switch ((n = ntohl(*p++))) {
+ case RPC_AUTH_REJECTEDCRED:
+ case RPC_AUTH_REJECTEDVERF:
+ if (!task->tk_cred_retry)
+ break;
+ task->tk_cred_retry--;
+ dprintk("RPC: %4d call_verify: retry stale creds\n",
+ task->tk_pid);
+ rpcauth_invalcred(task);
+ task->tk_action = call_refresh;
+ return NULL;
+ case RPC_AUTH_BADCRED:
+ case RPC_AUTH_BADVERF:
+ /* possibly garbled cred/verf? */
+ if (!task->tk_garb_retry)
+ break;
+ task->tk_garb_retry--;
+ dprintk("RPC: %4d call_verify: retry garbled creds\n",
+ task->tk_pid);
+ task->tk_action = call_encode;
+ return NULL;
+ case RPC_AUTH_TOOWEAK:
+ printk(KERN_NOTICE "call_verify: server requires stronger "
+ "authentication.\n");
+ break;
+ default:
+ printk(KERN_WARNING "call_verify: unknown auth error: %x\n", n);
+ error = -EIO;
+ }
+ dprintk("RPC: %4d call_verify: call rejected %d\n",
+ task->tk_pid, n);
+ rpc_exit(task, error);
+ return NULL;
+ }
+ if (!(p = rpcauth_checkverf(task, p))) {
+ printk(KERN_WARNING "call_verify: auth check failed\n");
+ goto garbage; /* bad verifier, retry */
+ }
+ switch ((n = ntohl(*p++))) {
+ case RPC_SUCCESS:
+ return p;
+ case RPC_PROG_UNAVAIL:
+ printk(KERN_WARNING "RPC: call_verify: program %u is unsupported by server %s\n",
+ (unsigned int)task->tk_client->cl_prog,
+ task->tk_client->cl_server);
+ goto out_eio;
+ case RPC_PROG_MISMATCH:
+ printk(KERN_WARNING "RPC: call_verify: program %u, version %u unsupported by server %s\n",
+ (unsigned int)task->tk_client->cl_prog,
+ (unsigned int)task->tk_client->cl_vers,
+ task->tk_client->cl_server);
+ goto out_eio;
+ case RPC_PROC_UNAVAIL:
+ printk(KERN_WARNING "RPC: call_verify: proc %u unsupported by program %u, version %u on server %s\n",
+ (unsigned int)task->tk_msg.rpc_proc,
+ (unsigned int)task->tk_client->cl_prog,
+ (unsigned int)task->tk_client->cl_vers,
+ task->tk_client->cl_server);
+ goto out_eio;
+ case RPC_GARBAGE_ARGS:
+ break; /* retry */
+ default:
+ printk(KERN_WARNING "call_verify: server accept status: %x\n", n);
+ /* Also retry */
+ }
+
+garbage:
+ dprintk("RPC: %4d call_verify: server saw garbage\n", task->tk_pid);
+ task->tk_client->cl_stats->rpcgarbage++;
+ if (task->tk_garb_retry) {
+ task->tk_garb_retry--;
+ dprintk(KERN_WARNING "RPC: garbage, retrying %4d\n", task->tk_pid);
+ task->tk_action = call_encode;
+ return NULL;
+ }
+ printk(KERN_WARNING "RPC: garbage, exit EIO\n");
+out_eio:
+ rpc_exit(task, -EIO);
+ return NULL;
+}
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/pmap_clnt.c b/uClinux-2.4.31-uc0/net/sunrpc/pmap_clnt.c
new file mode 100644
index 0000000..e3c30ad
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/pmap_clnt.c
@@ -0,0 +1,278 @@
+/*
+ * linux/net/sunrpc/pmap.c
+ *
+ * Portmapper client.
+ *
+ * FIXME: In a secure environment, we may want to use an authentication
+ * flavor other than AUTH_NULL.
+ *
+ * Copyright (C) 1996, Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/uio.h>
+#include <linux/in.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/xprt.h>
+#include <linux/sunrpc/sched.h>
+
+#ifdef RPC_DEBUG
+# define RPCDBG_FACILITY RPCDBG_PMAP
+#endif
+
+#define PMAP_SET 1
+#define PMAP_UNSET 2
+#define PMAP_GETPORT 3
+
+static struct rpc_clnt * pmap_create(char *, struct sockaddr_in *, int);
+static void pmap_getport_done(struct rpc_task *);
+extern struct rpc_program pmap_program;
+static spinlock_t pmap_lock = SPIN_LOCK_UNLOCKED;
+
+/*
+ * Obtain the port for a given RPC service on a given host. This one can
+ * be called for an ongoing RPC request.
+ */
+void
+rpc_getport(struct rpc_task *task, struct rpc_clnt *clnt)
+{
+ struct rpc_portmap *map = &clnt->cl_pmap;
+ struct sockaddr_in *sap = &clnt->cl_xprt->addr;
+ struct rpc_message msg = { PMAP_GETPORT, map, &clnt->cl_port, NULL };
+ struct rpc_clnt *pmap_clnt;
+ struct rpc_task *child;
+
+ dprintk("RPC: %4d rpc_getport(%s, %d, %d, %d)\n",
+ task->tk_pid, clnt->cl_server,
+ map->pm_prog, map->pm_vers, map->pm_prot);
+
+ spin_lock(&pmap_lock);
+ if (clnt->cl_binding) {
+ rpc_sleep_on(&clnt->cl_bindwait, task, NULL, 0);
+ spin_unlock(&pmap_lock);
+ return;
+ }
+ clnt->cl_binding = 1;
+ spin_unlock(&pmap_lock);
+
+ task->tk_status = -EACCES; /* why set this? returns -EIO below */
+ if (!(pmap_clnt = pmap_create(clnt->cl_server, sap, map->pm_prot)))
+ goto bailout;
+ task->tk_status = 0;
+
+ /*
+ * Note: rpc_new_child will release client after a failure.
+ */
+ if (!(child = rpc_new_child(pmap_clnt, task)))
+ goto bailout;
+
+ /* Setup the call info struct */
+ rpc_call_setup(child, &msg, 0);
+
+ /* ... and run the child task */
+ rpc_run_child(task, child, pmap_getport_done);
+ return;
+
+bailout:
+ spin_lock(&pmap_lock);
+ clnt->cl_binding = 0;
+ rpc_wake_up(&clnt->cl_bindwait);
+ spin_unlock(&pmap_lock);
+ task->tk_status = -EIO;
+ task->tk_action = NULL;
+}
+
+#ifdef CONFIG_ROOT_NFS
+int
+rpc_getport_external(struct sockaddr_in *sin, __u32 prog, __u32 vers, int prot)
+{
+ struct rpc_portmap map = { prog, vers, prot, 0 };
+ struct rpc_clnt *pmap_clnt;
+ char hostname[32];
+ int status;
+
+ dprintk("RPC: rpc_getport_external(%u.%u.%u.%u, %d, %d, %d)\n",
+ NIPQUAD(sin->sin_addr.s_addr), prog, vers, prot);
+
+ sprintf(hostname, "%u.%u.%u.%u", NIPQUAD(sin->sin_addr.s_addr));
+ if (!(pmap_clnt = pmap_create(hostname, sin, prot)))
+ return -EACCES;
+
+ /* Setup the call info struct */
+ status = rpc_call(pmap_clnt, PMAP_GETPORT, &map, &map.pm_port, 0);
+
+ if (status >= 0) {
+ if (map.pm_port != 0)
+ return map.pm_port;
+ status = -EACCES;
+ }
+ return status;
+}
+#endif
+
+static void
+pmap_getport_done(struct rpc_task *task)
+{
+ struct rpc_clnt *clnt = task->tk_client;
+
+ dprintk("RPC: %4d pmap_getport_done(status %d, port %d)\n",
+ task->tk_pid, task->tk_status, clnt->cl_port);
+ if (task->tk_status < 0) {
+ /* Make the calling task exit with an error */
+ task->tk_action = NULL;
+ } else if (clnt->cl_port == 0) {
+ /* Program not registered */
+ task->tk_status = -EACCES;
+ task->tk_action = NULL;
+ } else {
+ /* byte-swap port number first */
+ clnt->cl_port = htons(clnt->cl_port);
+ clnt->cl_xprt->addr.sin_port = clnt->cl_port;
+ }
+ spin_lock(&pmap_lock);
+ clnt->cl_binding = 0;
+ rpc_wake_up(&clnt->cl_bindwait);
+ spin_unlock(&pmap_lock);
+}
+
+/*
+ * Set or unset a port registration with the local portmapper.
+ * port == 0 means unregister, port != 0 means register.
+ */
+int
+rpc_register(u32 prog, u32 vers, int prot, unsigned short port, int *okay)
+{
+ struct sockaddr_in sin;
+ struct rpc_portmap map;
+ struct rpc_clnt *pmap_clnt;
+ unsigned int error = 0;
+
+ dprintk("RPC: registering (%d, %d, %d, %d) with portmapper.\n",
+ prog, vers, prot, port);
+
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ if (!(pmap_clnt = pmap_create("localhost", &sin, IPPROTO_UDP))) {
+ dprintk("RPC: couldn't create pmap client\n");
+ return -EACCES;
+ }
+
+ map.pm_prog = prog;
+ map.pm_vers = vers;
+ map.pm_prot = prot;
+ map.pm_port = port;
+
+ error = rpc_call(pmap_clnt, port? PMAP_SET : PMAP_UNSET,
+ &map, okay, 0);
+
+ if (error < 0) {
+ printk(KERN_WARNING
+ "RPC: failed to contact portmap (errno %d).\n",
+ error);
+ }
+ dprintk("RPC: registration status %d/%d\n", error, *okay);
+
+ /* Client deleted automatically because cl_oneshot == 1 */
+ return error;
+}
+
+static struct rpc_clnt *
+pmap_create(char *hostname, struct sockaddr_in *srvaddr, int proto)
+{
+ struct rpc_xprt *xprt;
+ struct rpc_clnt *clnt;
+
+ /* printk("pmap: create xprt\n"); */
+ if (!(xprt = xprt_create_proto(proto, srvaddr, NULL)))
+ return NULL;
+ xprt->addr.sin_port = htons(RPC_PMAP_PORT);
+
+ /* printk("pmap: create clnt\n"); */
+ clnt = rpc_create_client(xprt, hostname,
+ &pmap_program, RPC_PMAP_VERSION,
+ RPC_AUTH_NULL);
+ if (!clnt) {
+ xprt_destroy(xprt);
+ } else {
+ clnt->cl_softrtry = 1;
+ clnt->cl_chatty = 1;
+ clnt->cl_oneshot = 1;
+ }
+ return clnt;
+}
+
+/*
+ * XDR encode/decode functions for PMAP
+ */
+static int
+xdr_error(struct rpc_rqst *req, u32 *p, void *dummy)
+{
+ return -EIO;
+}
+
+static int
+xdr_encode_mapping(struct rpc_rqst *req, u32 *p, struct rpc_portmap *map)
+{
+ dprintk("RPC: xdr_encode_mapping(%d, %d, %d, %d)\n",
+ map->pm_prog, map->pm_vers, map->pm_prot, map->pm_port);
+ *p++ = htonl(map->pm_prog);
+ *p++ = htonl(map->pm_vers);
+ *p++ = htonl(map->pm_prot);
+ *p++ = htonl(map->pm_port);
+
+ req->rq_slen = xdr_adjust_iovec(req->rq_svec, p);
+ return 0;
+}
+
+static int
+xdr_decode_port(struct rpc_rqst *req, u32 *p, unsigned short *portp)
+{
+ *portp = (unsigned short) ntohl(*p++);
+ return 0;
+}
+
+static int
+xdr_decode_bool(struct rpc_rqst *req, u32 *p, unsigned int *boolp)
+{
+ *boolp = (unsigned int) ntohl(*p++);
+ return 0;
+}
+
+static struct rpc_procinfo pmap_procedures[4] = {
+ { "pmap_null",
+ (kxdrproc_t) xdr_error,
+ (kxdrproc_t) xdr_error, 0, 0 },
+ { "pmap_set",
+ (kxdrproc_t) xdr_encode_mapping,
+ (kxdrproc_t) xdr_decode_bool, 4, 1 },
+ { "pmap_unset",
+ (kxdrproc_t) xdr_encode_mapping,
+ (kxdrproc_t) xdr_decode_bool, 4, 1 },
+ { "pmap_get",
+ (kxdrproc_t) xdr_encode_mapping,
+ (kxdrproc_t) xdr_decode_port, 4, 1 },
+};
+
+static struct rpc_version pmap_version2 = {
+ 2, 4, pmap_procedures
+};
+
+static struct rpc_version * pmap_version[] = {
+ NULL,
+ NULL,
+ &pmap_version2,
+};
+
+static struct rpc_stat pmap_stats;
+
+struct rpc_program pmap_program = {
+ "portmap",
+ RPC_PMAP_PROGRAM,
+ sizeof(pmap_version)/sizeof(pmap_version[0]),
+ pmap_version,
+ &pmap_stats,
+};
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/sched.c b/uClinux-2.4.31-uc0/net/sunrpc/sched.c
new file mode 100644
index 0000000..f8d542d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/sched.c
@@ -0,0 +1,1158 @@
+/*
+ * linux/net/sunrpc/sched.c
+ *
+ * Scheduling for synchronous and asynchronous RPC requests.
+ *
+ * Copyright (C) 1996 Olaf Kirch, <okir@monad.swb.de>
+ *
+ * TCP NFS related read + write fixes
+ * (C) 1999 Dave Airlie, University of Limerick, Ireland <airlied@linux.ie>
+ */
+
+#include <linux/module.h>
+
+#define __KERNEL_SYSCALLS__
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/unistd.h>
+#include <linux/smp.h>
+#include <linux/smp_lock.h>
+#include <linux/spinlock.h>
+
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/xprt.h>
+
+#ifdef RPC_DEBUG
+#define RPCDBG_FACILITY RPCDBG_SCHED
+static int rpc_task_id;
+#endif
+
+/*
+ * We give RPC the same get_free_pages priority as NFS
+ */
+#define GFP_RPC GFP_NOFS
+
+static void __rpc_default_timer(struct rpc_task *task);
+static void rpciod_killall(void);
+
+/*
+ * When an asynchronous RPC task is activated within a bottom half
+ * handler, or while executing another RPC task, it is put on
+ * schedq, and rpciod is woken up.
+ */
+static RPC_WAITQ(schedq, "schedq");
+
+/*
+ * RPC tasks that create another task (e.g. for contacting the portmapper)
+ * will wait on this queue for their child's completion
+ */
+static RPC_WAITQ(childq, "childq");
+
+/*
+ * RPC tasks sit here while waiting for conditions to improve.
+ */
+static RPC_WAITQ(delay_queue, "delayq");
+
+/*
+ * All RPC tasks are linked into this list
+ */
+static LIST_HEAD(all_tasks);
+
+/*
+ * rpciod-related stuff
+ */
+static DECLARE_WAIT_QUEUE_HEAD(rpciod_idle);
+static DECLARE_WAIT_QUEUE_HEAD(rpciod_killer);
+static DECLARE_MUTEX(rpciod_sema);
+static unsigned int rpciod_users;
+static pid_t rpciod_pid;
+static int rpc_inhibit;
+
+/*
+ * Spinlock for wait queues. Access to the latter also has to be
+ * interrupt-safe in order to allow timers to wake up sleeping tasks.
+ */
+static spinlock_t rpc_queue_lock = SPIN_LOCK_UNLOCKED;
+/*
+ * Spinlock for other critical sections of code.
+ */
+static spinlock_t rpc_sched_lock = SPIN_LOCK_UNLOCKED;
+
+/*
+ * This is the last-ditch buffer for NFS swap requests
+ */
+static u32 swap_buffer[PAGE_SIZE >> 2];
+static long swap_buffer_used;
+
+/*
+ * Make allocation of the swap_buffer SMP-safe
+ */
+static __inline__ int rpc_lock_swapbuf(void)
+{
+ return !test_and_set_bit(1, &swap_buffer_used);
+}
+static __inline__ void rpc_unlock_swapbuf(void)
+{
+ clear_bit(1, &swap_buffer_used);
+}
+
+/*
+ * Disable the timer for a given RPC task. Should be called with
+ * rpc_queue_lock and bh_disabled in order to avoid races within
+ * rpc_run_timer().
+ */
+static inline void
+__rpc_disable_timer(struct rpc_task *task)
+{
+ dprintk("RPC: %4d disabling timer\n", task->tk_pid);
+ task->tk_timeout_fn = NULL;
+ task->tk_timeout = 0;
+}
+
+/*
+ * Run a timeout function.
+ * We use the callback in order to allow __rpc_wake_up_task()
+ * and friends to disable the timer synchronously on SMP systems
+ * without calling del_timer_sync(). The latter could cause a
+ * deadlock if called while we're holding spinlocks...
+ */
+static void
+rpc_run_timer(struct rpc_task *task)
+{
+ void (*callback)(struct rpc_task *);
+
+ spin_lock_bh(&rpc_queue_lock);
+ callback = task->tk_timeout_fn;
+ task->tk_timeout_fn = NULL;
+ spin_unlock_bh(&rpc_queue_lock);
+ if (callback) {
+ dprintk("RPC: %4d running timer\n", task->tk_pid);
+ callback(task);
+ }
+}
+
+/*
+ * Set up a timer for the current task.
+ */
+static inline void
+__rpc_add_timer(struct rpc_task *task, rpc_action timer)
+{
+ if (!task->tk_timeout)
+ return;
+
+ dprintk("RPC: %4d setting alarm for %lu ms\n",
+ task->tk_pid, task->tk_timeout * 1000 / HZ);
+
+ if (timer)
+ task->tk_timeout_fn = timer;
+ else
+ task->tk_timeout_fn = __rpc_default_timer;
+ mod_timer(&task->tk_timer, jiffies + task->tk_timeout);
+}
+
+/*
+ * Set up a timer for an already sleeping task.
+ */
+void rpc_add_timer(struct rpc_task *task, rpc_action timer)
+{
+ spin_lock_bh(&rpc_queue_lock);
+ if (!RPC_IS_RUNNING(task))
+ __rpc_add_timer(task, timer);
+ spin_unlock_bh(&rpc_queue_lock);
+}
+
+/*
+ * Delete any timer for the current task. Because we use del_timer_sync(),
+ * this function should never be called while holding rpc_queue_lock.
+ */
+static inline void
+rpc_delete_timer(struct rpc_task *task)
+{
+ dprintk("RPC: %4d deleting timer\n", task->tk_pid);
+ del_timer_sync(&task->tk_timer);
+}
+
+/*
+ * Add new request to wait queue.
+ *
+ * Swapper tasks always get inserted at the head of the queue.
+ * This should avoid many nasty memory deadlocks and hopefully
+ * improve overall performance.
+ * Everyone else gets appended to the queue to ensure proper FIFO behavior.
+ */
+static inline int
+__rpc_add_wait_queue(struct rpc_wait_queue *queue, struct rpc_task *task)
+{
+ if (task->tk_rpcwait == queue)
+ return 0;
+
+ if (task->tk_rpcwait) {
+ printk(KERN_WARNING "RPC: doubly enqueued task!\n");
+ return -EWOULDBLOCK;
+ }
+ if (RPC_IS_SWAPPER(task))
+ list_add(&task->tk_list, &queue->tasks);
+ else
+ list_add_tail(&task->tk_list, &queue->tasks);
+ task->tk_rpcwait = queue;
+
+ dprintk("RPC: %4d added to queue %p \"%s\"\n",
+ task->tk_pid, queue, rpc_qname(queue));
+
+ return 0;
+}
+
+int
+rpc_add_wait_queue(struct rpc_wait_queue *q, struct rpc_task *task)
+{
+ int result;
+
+ spin_lock_bh(&rpc_queue_lock);
+ result = __rpc_add_wait_queue(q, task);
+ spin_unlock_bh(&rpc_queue_lock);
+ return result;
+}
+
+/*
+ * Remove request from queue.
+ * Note: must be called with spin lock held.
+ */
+static inline void
+__rpc_remove_wait_queue(struct rpc_task *task)
+{
+ struct rpc_wait_queue *queue = task->tk_rpcwait;
+
+ if (!queue)
+ return;
+
+ list_del(&task->tk_list);
+ task->tk_rpcwait = NULL;
+
+ dprintk("RPC: %4d removed from queue %p \"%s\"\n",
+ task->tk_pid, queue, rpc_qname(queue));
+}
+
+void
+rpc_remove_wait_queue(struct rpc_task *task)
+{
+ if (!task->tk_rpcwait)
+ return;
+ spin_lock_bh(&rpc_queue_lock);
+ __rpc_remove_wait_queue(task);
+ spin_unlock_bh(&rpc_queue_lock);
+}
+
+/*
+ * Make an RPC task runnable.
+ *
+ * Note: If the task is ASYNC, this must be called with
+ * the spinlock held to protect the wait queue operation.
+ */
+static inline void
+rpc_make_runnable(struct rpc_task *task)
+{
+ if (task->tk_timeout_fn) {
+ printk(KERN_ERR "RPC: task w/ running timer in rpc_make_runnable!!\n");
+ return;
+ }
+ rpc_set_running(task);
+ if (RPC_IS_ASYNC(task)) {
+ if (RPC_IS_SLEEPING(task)) {
+ int status;
+ status = __rpc_add_wait_queue(&schedq, task);
+ if (status < 0) {
+ printk(KERN_WARNING "RPC: failed to add task to queue: error: %d!\n", status);
+ task->tk_status = status;
+ return;
+ }
+ rpc_clear_sleeping(task);
+ if (waitqueue_active(&rpciod_idle))
+ wake_up(&rpciod_idle);
+ }
+ } else {
+ rpc_clear_sleeping(task);
+ if (waitqueue_active(&task->tk_wait))
+ wake_up(&task->tk_wait);
+ }
+}
+
+/*
+ * Place a newly initialized task on the schedq.
+ */
+static inline void
+rpc_schedule_run(struct rpc_task *task)
+{
+ /* Don't run a child twice! */
+ if (RPC_IS_ACTIVATED(task))
+ return;
+ task->tk_active = 1;
+ rpc_set_sleeping(task);
+ rpc_make_runnable(task);
+}
+
+/*
+ * For other people who may need to wake the I/O daemon
+ * but should (for now) know nothing about its innards
+ */
+void rpciod_wake_up(void)
+{
+ if(rpciod_pid==0)
+ printk(KERN_ERR "rpciod: wot no daemon?\n");
+ if (waitqueue_active(&rpciod_idle))
+ wake_up(&rpciod_idle);
+}
+
+/*
+ * Prepare for sleeping on a wait queue.
+ * By always appending tasks to the list we ensure FIFO behavior.
+ * NB: An RPC task will only receive interrupt-driven events as long
+ * as it's on a wait queue.
+ */
+static void
+__rpc_sleep_on(struct rpc_wait_queue *q, struct rpc_task *task,
+ rpc_action action, rpc_action timer)
+{
+ int status;
+
+ dprintk("RPC: %4d sleep_on(queue \"%s\" time %ld)\n", task->tk_pid,
+ rpc_qname(q), jiffies);
+
+ if (!RPC_IS_ASYNC(task) && !RPC_IS_ACTIVATED(task)) {
+ printk(KERN_ERR "RPC: Inactive synchronous task put to sleep!\n");
+ return;
+ }
+
+ /* Mark the task as being activated if so needed */
+ if (!RPC_IS_ACTIVATED(task)) {
+ task->tk_active = 1;
+ rpc_set_sleeping(task);
+ }
+
+ status = __rpc_add_wait_queue(q, task);
+ if (status) {
+ printk(KERN_WARNING "RPC: failed to add task to queue: error: %d!\n", status);
+ task->tk_status = status;
+ } else {
+ rpc_clear_running(task);
+ if (task->tk_callback) {
+ dprintk(KERN_ERR "RPC: %4d overwrites an active callback\n", task->tk_pid);
+ BUG();
+ }
+ task->tk_callback = action;
+ __rpc_add_timer(task, timer);
+ }
+}
+
+void
+rpc_sleep_on(struct rpc_wait_queue *q, struct rpc_task *task,
+ rpc_action action, rpc_action timer)
+{
+ /*
+ * Protect the queue operations.
+ */
+ spin_lock_bh(&rpc_queue_lock);
+ __rpc_sleep_on(q, task, action, timer);
+ spin_unlock_bh(&rpc_queue_lock);
+}
+
+/**
+ * __rpc_wake_up_task - wake up a single rpc_task
+ * @task: task to be woken up
+ *
+ * Caller must hold rpc_queue_lock
+ */
+static void
+__rpc_wake_up_task(struct rpc_task *task)
+{
+ dprintk("RPC: %4d __rpc_wake_up_task (now %ld inh %d)\n",
+ task->tk_pid, jiffies, rpc_inhibit);
+
+#ifdef RPC_DEBUG
+ if (task->tk_magic != 0xf00baa) {
+ printk(KERN_ERR "RPC: attempt to wake up non-existing task!\n");
+ rpc_debug = ~0;
+ rpc_show_tasks();
+ return;
+ }
+#endif
+ /* Has the task been executed yet? If not, we cannot wake it up! */
+ if (!RPC_IS_ACTIVATED(task)) {
+ printk(KERN_ERR "RPC: Inactive task (%p) being woken up!\n", task);
+ return;
+ }
+ if (RPC_IS_RUNNING(task))
+ return;
+
+ __rpc_disable_timer(task);
+ if (task->tk_rpcwait != &schedq)
+ __rpc_remove_wait_queue(task);
+
+ rpc_make_runnable(task);
+
+ dprintk("RPC: __rpc_wake_up_task done\n");
+}
+
+/*
+ * Default timeout handler if none specified by user
+ */
+static void
+__rpc_default_timer(struct rpc_task *task)
+{
+ dprintk("RPC: %d timeout (default timer)\n", task->tk_pid);
+ task->tk_status = -ETIMEDOUT;
+ rpc_wake_up_task(task);
+}
+
+/*
+ * Wake up the specified task
+ */
+void
+rpc_wake_up_task(struct rpc_task *task)
+{
+ if (RPC_IS_RUNNING(task))
+ return;
+ spin_lock_bh(&rpc_queue_lock);
+ __rpc_wake_up_task(task);
+ spin_unlock_bh(&rpc_queue_lock);
+}
+
+/*
+ * Wake up the next task on the wait queue.
+ */
+struct rpc_task *
+rpc_wake_up_next(struct rpc_wait_queue *queue)
+{
+ struct rpc_task *task = NULL;
+
+ dprintk("RPC: wake_up_next(%p \"%s\")\n", queue, rpc_qname(queue));
+ spin_lock_bh(&rpc_queue_lock);
+ task_for_first(task, &queue->tasks)
+ __rpc_wake_up_task(task);
+ spin_unlock_bh(&rpc_queue_lock);
+
+ return task;
+}
+
+/**
+ * rpc_wake_up - wake up all rpc_tasks
+ * @queue: rpc_wait_queue on which the tasks are sleeping
+ *
+ * Grabs rpc_queue_lock
+ */
+void
+rpc_wake_up(struct rpc_wait_queue *queue)
+{
+ struct rpc_task *task;
+
+ spin_lock_bh(&rpc_queue_lock);
+ while (!list_empty(&queue->tasks))
+ task_for_first(task, &queue->tasks)
+ __rpc_wake_up_task(task);
+ spin_unlock_bh(&rpc_queue_lock);
+}
+
+/**
+ * rpc_wake_up_status - wake up all rpc_tasks and set their status value.
+ * @queue: rpc_wait_queue on which the tasks are sleeping
+ * @status: status value to set
+ *
+ * Grabs rpc_queue_lock
+ */
+void
+rpc_wake_up_status(struct rpc_wait_queue *queue, int status)
+{
+ struct rpc_task *task;
+
+ spin_lock_bh(&rpc_queue_lock);
+ while (!list_empty(&queue->tasks)) {
+ task_for_first(task, &queue->tasks) {
+ task->tk_status = status;
+ __rpc_wake_up_task(task);
+ }
+ }
+ spin_unlock_bh(&rpc_queue_lock);
+}
+
+/*
+ * Run a task at a later time
+ */
+static void __rpc_atrun(struct rpc_task *);
+void
+rpc_delay(struct rpc_task *task, unsigned long delay)
+{
+ task->tk_timeout = delay;
+ rpc_sleep_on(&delay_queue, task, NULL, __rpc_atrun);
+}
+
+static void
+__rpc_atrun(struct rpc_task *task)
+{
+ task->tk_status = 0;
+ rpc_wake_up_task(task);
+}
+
+/*
+ * This is the RPC `scheduler' (or rather, the finite state machine).
+ */
+static int
+__rpc_execute(struct rpc_task *task)
+{
+ int status = 0;
+
+ dprintk("RPC: %4d rpc_execute flgs %x\n",
+ task->tk_pid, task->tk_flags);
+
+ if (!RPC_IS_RUNNING(task)) {
+ printk(KERN_WARNING "RPC: rpc_execute called for sleeping task!!\n");
+ return 0;
+ }
+
+ restarted:
+ while (1) {
+ /*
+ * Execute any pending callback.
+ */
+ if (RPC_DO_CALLBACK(task)) {
+ /* Define a callback save pointer */
+ void (*save_callback)(struct rpc_task *);
+
+ /*
+ * If a callback exists, save it, reset it,
+ * call it.
+ * The save is needed to stop from resetting
+ * another callback set within the callback handler
+ * - Dave
+ */
+ save_callback=task->tk_callback;
+ task->tk_callback=NULL;
+ save_callback(task);
+ }
+
+ /*
+ * Perform the next FSM step.
+ * tk_action may be NULL when the task has been killed
+ * by someone else.
+ */
+ if (RPC_IS_RUNNING(task)) {
+ /*
+ * Garbage collection of pending timers...
+ */
+ rpc_delete_timer(task);
+ if (!task->tk_action)
+ break;
+ task->tk_action(task);
+ }
+
+ /*
+ * Check whether task is sleeping.
+ */
+ spin_lock_bh(&rpc_queue_lock);
+ if (!RPC_IS_RUNNING(task)) {
+ rpc_set_sleeping(task);
+ if (RPC_IS_ASYNC(task)) {
+ spin_unlock_bh(&rpc_queue_lock);
+ return 0;
+ }
+ }
+ spin_unlock_bh(&rpc_queue_lock);
+
+ while (RPC_IS_SLEEPING(task)) {
+ /* sync task: sleep here */
+ dprintk("RPC: %4d sync task going to sleep\n",
+ task->tk_pid);
+ if (current->pid == rpciod_pid)
+ printk(KERN_ERR "RPC: rpciod waiting on sync task!\n");
+
+ __wait_event(task->tk_wait, !RPC_IS_SLEEPING(task));
+ dprintk("RPC: %4d sync task resuming\n", task->tk_pid);
+
+ /*
+ * When a sync task receives a signal, it exits with
+ * -ERESTARTSYS. In order to catch any callbacks that
+ * clean up after sleeping on some queue, we don't
+ * break the loop here, but go around once more.
+ */
+ if (task->tk_client->cl_intr && signalled()) {
+ dprintk("RPC: %4d got signal\n", task->tk_pid);
+ task->tk_flags |= RPC_TASK_KILLED;
+ rpc_exit(task, -ERESTARTSYS);
+ rpc_wake_up_task(task);
+ }
+ }
+ }
+
+ if (task->tk_exit) {
+ task->tk_exit(task);
+ /* If tk_action is non-null, the user wants us to restart */
+ if (task->tk_action) {
+ if (!RPC_ASSASSINATED(task)) {
+ /* Release RPC slot and buffer memory */
+ if (task->tk_rqstp)
+ xprt_release(task);
+ if (task->tk_buffer) {
+ rpc_free(task->tk_buffer);
+ task->tk_buffer = NULL;
+ }
+ goto restarted;
+ }
+ printk(KERN_ERR "RPC: dead task tries to walk away.\n");
+ }
+ }
+
+ dprintk("RPC: %4d exit() = %d\n", task->tk_pid, task->tk_status);
+ status = task->tk_status;
+
+ /* Release all resources associated with the task */
+ rpc_release_task(task);
+
+ return status;
+}
+
+/*
+ * User-visible entry point to the scheduler.
+ *
+ * This may be called recursively if e.g. an async NFS task updates
+ * the attributes and finds that dirty pages must be flushed.
+ * NOTE: Upon exit of this function the task is guaranteed to be
+ * released. In particular note that tk_release() will have
+ * been called, so your task memory may have been freed.
+ */
+int
+rpc_execute(struct rpc_task *task)
+{
+ int status = -EIO;
+ if (rpc_inhibit) {
+ printk(KERN_INFO "RPC: execution inhibited!\n");
+ goto out_release;
+ }
+
+ status = -EWOULDBLOCK;
+ if (task->tk_active) {
+ printk(KERN_ERR "RPC: active task was run twice!\n");
+ goto out_err;
+ }
+
+ task->tk_active = 1;
+ rpc_set_running(task);
+ return __rpc_execute(task);
+ out_release:
+ rpc_release_task(task);
+ out_err:
+ return status;
+}
+
+/*
+ * This is our own little scheduler for async RPC tasks.
+ */
+static void
+__rpc_schedule(void)
+{
+ struct rpc_task *task;
+ int count = 0;
+
+ dprintk("RPC: rpc_schedule enter\n");
+ while (1) {
+ spin_lock_bh(&rpc_queue_lock);
+
+ task_for_first(task, &schedq.tasks) {
+ __rpc_remove_wait_queue(task);
+ spin_unlock_bh(&rpc_queue_lock);
+
+ __rpc_execute(task);
+ } else {
+ spin_unlock_bh(&rpc_queue_lock);
+ break;
+ }
+
+ if (++count >= 200 || current->need_resched) {
+ count = 0;
+ schedule();
+ }
+ }
+ dprintk("RPC: rpc_schedule leave\n");
+}
+
+/*
+ * Allocate memory for RPC purpose.
+ *
+ * This is yet another tricky issue: For sync requests issued by
+ * a user process, we want to make kmalloc sleep if there isn't
+ * enough memory. Async requests should not sleep too excessively
+ * because that will block rpciod (but that's not dramatic when
+ * it's starved of memory anyway). Finally, swapout requests should
+ * never sleep at all, and should not trigger another swap_out
+ * request through kmalloc which would just increase memory contention.
+ *
+ * I hope the following gets it right, which gives async requests
+ * a slight advantage over sync requests (good for writeback, debatable
+ * for readahead):
+ *
+ * sync user requests: GFP_KERNEL
+ * async requests: GFP_RPC (== GFP_NOFS)
+ * swap requests: GFP_ATOMIC (or new GFP_SWAPPER)
+ */
+void *
+rpc_allocate(unsigned int flags, unsigned int size)
+{
+ u32 *buffer;
+ int gfp;
+
+ if (flags & RPC_TASK_SWAPPER)
+ gfp = GFP_ATOMIC;
+ else if (flags & RPC_TASK_ASYNC)
+ gfp = GFP_RPC;
+ else
+ gfp = GFP_KERNEL;
+
+ do {
+ if ((buffer = (u32 *) kmalloc(size, gfp)) != NULL) {
+ dprintk("RPC: allocated buffer %p\n", buffer);
+ return buffer;
+ }
+ if ((flags & RPC_TASK_SWAPPER) && size <= sizeof(swap_buffer)
+ && rpc_lock_swapbuf()) {
+ dprintk("RPC: used last-ditch swap buffer\n");
+ return swap_buffer;
+ }
+ if (flags & RPC_TASK_ASYNC)
+ return NULL;
+ yield();
+ } while (!signalled());
+
+ return NULL;
+}
+
+void
+rpc_free(void *buffer)
+{
+ if (buffer != swap_buffer) {
+ kfree(buffer);
+ return;
+ }
+ rpc_unlock_swapbuf();
+}
+
+/*
+ * Creation and deletion of RPC task structures
+ */
+inline void
+rpc_init_task(struct rpc_task *task, struct rpc_clnt *clnt,
+ rpc_action callback, int flags)
+{
+ memset(task, 0, sizeof(*task));
+ init_timer(&task->tk_timer);
+ task->tk_timer.data = (unsigned long) task;
+ task->tk_timer.function = (void (*)(unsigned long)) rpc_run_timer;
+ task->tk_client = clnt;
+ task->tk_flags = flags;
+ task->tk_exit = callback;
+ init_waitqueue_head(&task->tk_wait);
+ if (current->uid != current->fsuid || current->gid != current->fsgid)
+ task->tk_flags |= RPC_TASK_SETUID;
+
+ /* Initialize retry counters */
+ task->tk_garb_retry = 2;
+ task->tk_cred_retry = 2;
+ task->tk_suid_retry = 1;
+
+ /* Add to global list of all tasks */
+ spin_lock(&rpc_sched_lock);
+ list_add(&task->tk_task, &all_tasks);
+ spin_unlock(&rpc_sched_lock);
+
+ if (clnt)
+ atomic_inc(&clnt->cl_users);
+
+#ifdef RPC_DEBUG
+ task->tk_magic = 0xf00baa;
+ task->tk_pid = rpc_task_id++;
+#endif
+ dprintk("RPC: %4d new task procpid %d\n", task->tk_pid,
+ current->pid);
+}
+
+static void
+rpc_default_free_task(struct rpc_task *task)
+{
+ dprintk("RPC: %4d freeing task\n", task->tk_pid);
+ rpc_free(task);
+}
+
+/*
+ * Create a new task for the specified client. We have to
+ * clean up after an allocation failure, as the client may
+ * have specified "oneshot".
+ */
+struct rpc_task *
+rpc_new_task(struct rpc_clnt *clnt, rpc_action callback, int flags)
+{
+ struct rpc_task *task;
+
+ task = (struct rpc_task *) rpc_allocate(flags, sizeof(*task));
+ if (!task)
+ goto cleanup;
+
+ rpc_init_task(task, clnt, callback, flags);
+
+ /* Replace tk_release */
+ task->tk_release = rpc_default_free_task;
+
+ dprintk("RPC: %4d allocated task\n", task->tk_pid);
+ task->tk_flags |= RPC_TASK_DYNAMIC;
+out:
+ return task;
+
+cleanup:
+ /* Check whether to release the client */
+ if (clnt) {
+ printk("rpc_new_task: failed, users=%d, oneshot=%d\n",
+ atomic_read(&clnt->cl_users), clnt->cl_oneshot);
+ atomic_inc(&clnt->cl_users); /* pretend we were used ... */
+ rpc_release_client(clnt);
+ }
+ goto out;
+}
+
+void
+rpc_release_task(struct rpc_task *task)
+{
+ dprintk("RPC: %4d release task\n", task->tk_pid);
+
+#ifdef RPC_DEBUG
+ if (task->tk_magic != 0xf00baa) {
+ printk(KERN_ERR "RPC: attempt to release a non-existing task!\n");
+ rpc_debug = ~0;
+ rpc_show_tasks();
+ return;
+ }
+#endif
+
+ /* Remove from global task list */
+ spin_lock(&rpc_sched_lock);
+ list_del(&task->tk_task);
+ spin_unlock(&rpc_sched_lock);
+
+ /* Protect the execution below. */
+ spin_lock_bh(&rpc_queue_lock);
+
+ /* Disable timer to prevent zombie wakeup */
+ __rpc_disable_timer(task);
+
+ /* Remove from any wait queue we're still on */
+ __rpc_remove_wait_queue(task);
+
+ task->tk_active = 0;
+
+ spin_unlock_bh(&rpc_queue_lock);
+
+ /* Synchronously delete any running timer */
+ rpc_delete_timer(task);
+
+ /* Release resources */
+ if (task->tk_rqstp)
+ xprt_release(task);
+ if (task->tk_msg.rpc_cred)
+ rpcauth_unbindcred(task);
+ if (task->tk_buffer) {
+ rpc_free(task->tk_buffer);
+ task->tk_buffer = NULL;
+ }
+ if (task->tk_client) {
+ rpc_release_client(task->tk_client);
+ task->tk_client = NULL;
+ }
+
+#ifdef RPC_DEBUG
+ task->tk_magic = 0;
+#endif
+ if (task->tk_release)
+ task->tk_release(task);
+}
+
+/**
+ * rpc_find_parent - find the parent of a child task.
+ * @child: child task
+ *
+ * Checks that the parent task is still sleeping on the
+ * queue 'childq'. If so returns a pointer to the parent.
+ * Upon failure returns NULL.
+ *
+ * Caller must hold rpc_queue_lock
+ */
+static inline struct rpc_task *
+rpc_find_parent(struct rpc_task *child)
+{
+ struct rpc_task *task, *parent;
+ struct list_head *le;
+
+ parent = (struct rpc_task *) child->tk_calldata;
+ task_for_each(task, le, &childq.tasks)
+ if (task == parent)
+ return parent;
+
+ return NULL;
+}
+
+static void
+rpc_child_exit(struct rpc_task *child)
+{
+ struct rpc_task *parent;
+
+ spin_lock_bh(&rpc_queue_lock);
+ if ((parent = rpc_find_parent(child)) != NULL) {
+ parent->tk_status = child->tk_status;
+ __rpc_wake_up_task(parent);
+ }
+ spin_unlock_bh(&rpc_queue_lock);
+}
+
+/*
+ * Note: rpc_new_task releases the client after a failure.
+ */
+struct rpc_task *
+rpc_new_child(struct rpc_clnt *clnt, struct rpc_task *parent)
+{
+ struct rpc_task *task;
+
+ task = rpc_new_task(clnt, NULL, RPC_TASK_ASYNC | RPC_TASK_CHILD);
+ if (!task)
+ goto fail;
+ task->tk_exit = rpc_child_exit;
+ task->tk_calldata = parent;
+ return task;
+
+fail:
+ parent->tk_status = -ENOMEM;
+ return NULL;
+}
+
+void
+rpc_run_child(struct rpc_task *task, struct rpc_task *child, rpc_action func)
+{
+ spin_lock_bh(&rpc_queue_lock);
+ /* N.B. Is it possible for the child to have already finished? */
+ __rpc_sleep_on(&childq, task, func, NULL);
+ rpc_schedule_run(child);
+ spin_unlock_bh(&rpc_queue_lock);
+}
+
+/*
+ * Kill all tasks for the given client.
+ * XXX: kill their descendants as well?
+ */
+void
+rpc_killall_tasks(struct rpc_clnt *clnt)
+{
+ struct rpc_task *rovr;
+ struct list_head *le;
+
+ dprintk("RPC: killing all tasks for client %p\n", clnt);
+
+ /*
+ * Spin lock all_tasks to prevent changes...
+ */
+ spin_lock(&rpc_sched_lock);
+ alltask_for_each(rovr, le, &all_tasks)
+ if (!clnt || rovr->tk_client == clnt) {
+ rovr->tk_flags |= RPC_TASK_KILLED;
+ rpc_exit(rovr, -EIO);
+ rpc_wake_up_task(rovr);
+ }
+ spin_unlock(&rpc_sched_lock);
+}
+
+static DECLARE_MUTEX_LOCKED(rpciod_running);
+
+static inline int
+rpciod_task_pending(void)
+{
+ return !list_empty(&schedq.tasks);
+}
+
+
+/*
+ * This is the rpciod kernel thread
+ */
+static int
+rpciod(void *ptr)
+{
+ wait_queue_head_t *assassin = (wait_queue_head_t*) ptr;
+ int rounds = 0;
+
+ MOD_INC_USE_COUNT;
+ lock_kernel();
+ /*
+ * Let our maker know we're running ...
+ */
+ rpciod_pid = current->pid;
+ up(&rpciod_running);
+
+ daemonize();
+
+ spin_lock_irq(&current->sigmask_lock);
+ siginitsetinv(&current->blocked, sigmask(SIGKILL));
+ recalc_sigpending(current);
+ spin_unlock_irq(&current->sigmask_lock);
+
+ strcpy(current->comm, "rpciod");
+
+ dprintk("RPC: rpciod starting (pid %d)\n", rpciod_pid);
+ while (rpciod_users) {
+ if (signalled()) {
+ rpciod_killall();
+ flush_signals(current);
+ }
+ __rpc_schedule();
+
+ if (++rounds >= 64) { /* safeguard */
+ schedule();
+ rounds = 0;
+ }
+
+ if (!rpciod_task_pending()) {
+ dprintk("RPC: rpciod back to sleep\n");
+ wait_event_interruptible(rpciod_idle, rpciod_task_pending());
+ dprintk("RPC: switch to rpciod\n");
+ rounds = 0;
+ }
+ }
+
+ dprintk("RPC: rpciod shutdown commences\n");
+ if (!list_empty(&all_tasks)) {
+ printk(KERN_ERR "rpciod: active tasks at shutdown?!\n");
+ rpciod_killall();
+ }
+
+ rpciod_pid = 0;
+ wake_up(assassin);
+
+ dprintk("RPC: rpciod exiting\n");
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+static void
+rpciod_killall(void)
+{
+ unsigned long flags;
+
+ while (!list_empty(&all_tasks)) {
+ current->sigpending = 0;
+ rpc_killall_tasks(NULL);
+ __rpc_schedule();
+ if (!list_empty(&all_tasks)) {
+ dprintk("rpciod_killall: waiting for tasks to exit\n");
+ yield();
+ }
+ }
+
+ spin_lock_irqsave(&current->sigmask_lock, flags);
+ recalc_sigpending(current);
+ spin_unlock_irqrestore(&current->sigmask_lock, flags);
+}
+
+/*
+ * Start up the rpciod process if it's not already running.
+ */
+int
+rpciod_up(void)
+{
+ int error = 0;
+
+ MOD_INC_USE_COUNT;
+ down(&rpciod_sema);
+ dprintk("rpciod_up: pid %d, users %d\n", rpciod_pid, rpciod_users);
+ rpciod_users++;
+ if (rpciod_pid)
+ goto out;
+ /*
+ * If there's no pid, we should be the first user.
+ */
+ if (rpciod_users > 1)
+ printk(KERN_WARNING "rpciod_up: no pid, %d users??\n", rpciod_users);
+ /*
+ * Create the rpciod thread and wait for it to start.
+ */
+ error = kernel_thread(rpciod, &rpciod_killer, 0);
+ if (error < 0) {
+ printk(KERN_WARNING "rpciod_up: create thread failed, error=%d\n", error);
+ rpciod_users--;
+ goto out;
+ }
+ down(&rpciod_running);
+ error = 0;
+out:
+ up(&rpciod_sema);
+ MOD_DEC_USE_COUNT;
+ return error;
+}
+
+void
+rpciod_down(void)
+{
+ unsigned long flags;
+
+ MOD_INC_USE_COUNT;
+ down(&rpciod_sema);
+ dprintk("rpciod_down pid %d sema %d\n", rpciod_pid, rpciod_users);
+ if (rpciod_users) {
+ if (--rpciod_users)
+ goto out;
+ } else
+ printk(KERN_WARNING "rpciod_down: pid=%d, no users??\n", rpciod_pid);
+
+ if (!rpciod_pid) {
+ dprintk("rpciod_down: Nothing to do!\n");
+ goto out;
+ }
+
+ kill_proc(rpciod_pid, SIGKILL, 1);
+ /*
+ * Usually rpciod will exit very quickly, so we
+ * wait briefly before checking the process id.
+ */
+ current->sigpending = 0;
+ yield();
+ /*
+ * Display a message if we're going to wait longer.
+ */
+ while (rpciod_pid) {
+ dprintk("rpciod_down: waiting for pid %d to exit\n", rpciod_pid);
+ if (signalled()) {
+ dprintk("rpciod_down: caught signal\n");
+ break;
+ }
+ interruptible_sleep_on(&rpciod_killer);
+ }
+ spin_lock_irqsave(&current->sigmask_lock, flags);
+ recalc_sigpending(current);
+ spin_unlock_irqrestore(&current->sigmask_lock, flags);
+out:
+ up(&rpciod_sema);
+ MOD_DEC_USE_COUNT;
+}
+
+#ifdef RPC_DEBUG
+void rpc_show_tasks(void)
+{
+ struct list_head *le;
+ struct rpc_task *t;
+
+ spin_lock(&rpc_sched_lock);
+ if (list_empty(&all_tasks)) {
+ spin_unlock(&rpc_sched_lock);
+ return;
+ }
+ printk("-pid- proc flgs status -client- -prog- --rqstp- -timeout "
+ "-rpcwait -action- --exit--\n");
+ alltask_for_each(t, le, &all_tasks)
+ printk("%05d %04d %04x %06d %8p %6d %8p %08ld %8s %8p %8p\n",
+ t->tk_pid, t->tk_msg.rpc_proc, t->tk_flags, t->tk_status,
+ t->tk_client, t->tk_client->cl_prog,
+ t->tk_rqstp, t->tk_timeout,
+ t->tk_rpcwait ? rpc_qname(t->tk_rpcwait) : " <NULL> ",
+ t->tk_action, t->tk_exit);
+ spin_unlock(&rpc_sched_lock);
+}
+#endif
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/stats.c b/uClinux-2.4.31-uc0/net/sunrpc/stats.c
new file mode 100644
index 0000000..562fcc5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/stats.c
@@ -0,0 +1,206 @@
+/*
+ * linux/net/sunrpc/stats.c
+ *
+ * procfs-based user access to generic RPC statistics. The stats files
+ * reside in /proc/net/rpc.
+ *
+ * The read routines assume that the buffer passed in is just big enough.
+ * If you implement an RPC service that has its own stats routine which
+ * appends the generic RPC stats, make sure you don't exceed the PAGE_SIZE
+ * limit.
+ *
+ * Copyright (C) 1995, 1996, 1997 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/module.h>
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/proc_fs.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/svcsock.h>
+#include <linux/init.h>
+
+#define RPCDBG_FACILITY RPCDBG_MISC
+
+static struct proc_dir_entry *proc_net_rpc = NULL;
+
+/*
+ * Get RPC client stats
+ */
+int
+rpc_proc_read(char *buffer, char **start, off_t offset, int count,
+ int *eof, void *data)
+{
+ struct rpc_stat *statp = (struct rpc_stat *) data;
+ struct rpc_program *prog = statp->program;
+ struct rpc_version *vers;
+ int len, i, j;
+
+ len = sprintf(buffer,
+ "net %d %d %d %d\n",
+ statp->netcnt,
+ statp->netudpcnt,
+ statp->nettcpcnt,
+ statp->nettcpconn);
+ len += sprintf(buffer + len,
+ "rpc %d %d %d\n",
+ statp->rpccnt,
+ statp->rpcretrans,
+ statp->rpcauthrefresh);
+
+ for (i = 0; i < prog->nrvers; i++) {
+ if (!(vers = prog->version[i]))
+ continue;
+ len += sprintf(buffer + len, "proc%d %d",
+ vers->number, vers->nrprocs);
+ for (j = 0; j < vers->nrprocs; j++)
+ len += sprintf(buffer + len, " %d",
+ vers->procs[j].p_count);
+ buffer[len++] = '\n';
+ }
+
+ if (offset >= len) {
+ *start = buffer;
+ *eof = 1;
+ return 0;
+ }
+ *start = buffer + offset;
+ if ((len -= offset) > count)
+ return count;
+ *eof = 1;
+ return len;
+}
+
+/*
+ * Get RPC server stats
+ */
+int
+svc_proc_read(char *buffer, char **start, off_t offset, int count,
+ int *eof, void *data)
+{
+ struct svc_stat *statp = (struct svc_stat *) data;
+ struct svc_program *prog = statp->program;
+ struct svc_procedure *proc;
+ struct svc_version *vers;
+ int len, i, j;
+
+ len = sprintf(buffer,
+ "net %d %d %d %d\n",
+ statp->netcnt,
+ statp->netudpcnt,
+ statp->nettcpcnt,
+ statp->nettcpconn);
+ len += sprintf(buffer + len,
+ "rpc %d %d %d %d %d\n",
+ statp->rpccnt,
+ statp->rpcbadfmt+statp->rpcbadauth+statp->rpcbadclnt,
+ statp->rpcbadfmt,
+ statp->rpcbadauth,
+ statp->rpcbadclnt);
+
+ for (i = 0; i < prog->pg_nvers; i++) {
+ if (!(vers = prog->pg_vers[i]) || !(proc = vers->vs_proc))
+ continue;
+ len += sprintf(buffer + len, "proc%d %d", i, vers->vs_nproc);
+ for (j = 0; j < vers->vs_nproc; j++, proc++)
+ len += sprintf(buffer + len, " %d", proc->pc_count);
+ buffer[len++] = '\n';
+ }
+
+ if (offset >= len) {
+ *start = buffer;
+ *eof = 1;
+ return 0;
+ }
+ *start = buffer + offset;
+ if ((len -= offset) > count)
+ return count;
+ *eof = 1;
+ return len;
+}
+
+/*
+ * Register/unregister RPC proc files
+ */
+static inline struct proc_dir_entry *
+do_register(const char *name, void *data, int issvc)
+{
+ rpc_proc_init();
+ dprintk("RPC: registering /proc/net/rpc/%s\n", name);
+ return create_proc_read_entry(name, 0, proc_net_rpc,
+ issvc? svc_proc_read : rpc_proc_read,
+ data);
+}
+
+struct proc_dir_entry *
+rpc_proc_register(struct rpc_stat *statp)
+{
+ return do_register(statp->program->name, statp, 0);
+}
+
+void
+rpc_proc_unregister(const char *name)
+{
+ remove_proc_entry(name, proc_net_rpc);
+}
+
+struct proc_dir_entry *
+svc_proc_register(struct svc_stat *statp)
+{
+ return do_register(statp->program->pg_name, statp, 1);
+}
+
+void
+svc_proc_unregister(const char *name)
+{
+ remove_proc_entry(name, proc_net_rpc);
+}
+
+void
+rpc_proc_init(void)
+{
+ dprintk("RPC: registering /proc/net/rpc\n");
+ if (!proc_net_rpc) {
+ struct proc_dir_entry *ent;
+ ent = proc_mkdir("net/rpc", 0);
+ if (ent) {
+ ent->owner = THIS_MODULE;
+ proc_net_rpc = ent;
+ }
+ }
+}
+
+void
+rpc_proc_exit(void)
+{
+ dprintk("RPC: unregistering /proc/net/rpc\n");
+ if (proc_net_rpc) {
+ proc_net_rpc = NULL;
+ remove_proc_entry("net/rpc", 0);
+ }
+}
+
+
+static int __init
+init_sunrpc(void)
+{
+#ifdef RPC_DEBUG
+ rpc_register_sysctl();
+#endif
+ rpc_proc_init();
+ return 0;
+}
+
+static void __exit
+cleanup_sunrpc(void)
+{
+#ifdef RPC_DEBUG
+ rpc_unregister_sysctl();
+#endif
+ rpc_proc_exit();
+}
+MODULE_LICENSE("GPL");
+module_init(init_sunrpc);
+module_exit(cleanup_sunrpc);
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/sunrpc_syms.c b/uClinux-2.4.31-uc0/net/sunrpc/sunrpc_syms.c
new file mode 100644
index 0000000..8c16a95
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/sunrpc_syms.c
@@ -0,0 +1,109 @@
+/*
+ * linux/net/sunrpc/sunrpc_syms.c
+ *
+ * Symbols exported by the sunrpc module.
+ *
+ * Copyright (C) 1997 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sched.h>
+#include <linux/uio.h>
+#include <linux/unistd.h>
+
+#include <linux/sunrpc/sched.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/svc.h>
+#include <linux/sunrpc/svcsock.h>
+#include <linux/sunrpc/auth.h>
+
+/* RPC scheduler */
+EXPORT_SYMBOL(rpc_allocate);
+EXPORT_SYMBOL(rpc_free);
+EXPORT_SYMBOL(rpc_execute);
+EXPORT_SYMBOL(rpc_init_task);
+EXPORT_SYMBOL(rpc_sleep_on);
+EXPORT_SYMBOL(rpc_wake_up_next);
+EXPORT_SYMBOL(rpc_wake_up_task);
+EXPORT_SYMBOL(rpc_new_child);
+EXPORT_SYMBOL(rpc_run_child);
+EXPORT_SYMBOL(rpciod_down);
+EXPORT_SYMBOL(rpciod_up);
+EXPORT_SYMBOL(rpc_new_task);
+EXPORT_SYMBOL(rpc_wake_up_status);
+EXPORT_SYMBOL(rpc_release_task);
+
+/* RPC client functions */
+EXPORT_SYMBOL(rpc_create_client);
+EXPORT_SYMBOL(rpc_destroy_client);
+EXPORT_SYMBOL(rpc_shutdown_client);
+EXPORT_SYMBOL(rpc_killall_tasks);
+EXPORT_SYMBOL(rpc_call_sync);
+EXPORT_SYMBOL(rpc_call_async);
+EXPORT_SYMBOL(rpc_call_setup);
+EXPORT_SYMBOL(rpc_clnt_sigmask);
+EXPORT_SYMBOL(rpc_clnt_sigunmask);
+EXPORT_SYMBOL(rpc_delay);
+EXPORT_SYMBOL(rpc_restart_call);
+EXPORT_SYMBOL(rpc_setbufsize);
+
+/* Client transport */
+EXPORT_SYMBOL(xprt_create_proto);
+EXPORT_SYMBOL(xprt_destroy);
+EXPORT_SYMBOL(xprt_set_timeout);
+
+/* Client credential cache */
+EXPORT_SYMBOL(rpcauth_register);
+EXPORT_SYMBOL(rpcauth_unregister);
+EXPORT_SYMBOL(rpcauth_init_credcache);
+EXPORT_SYMBOL(rpcauth_free_credcache);
+EXPORT_SYMBOL(rpcauth_insert_credcache);
+EXPORT_SYMBOL(rpcauth_lookupcred);
+EXPORT_SYMBOL(rpcauth_bindcred);
+EXPORT_SYMBOL(rpcauth_matchcred);
+EXPORT_SYMBOL(put_rpccred);
+
+/* RPC server stuff */
+EXPORT_SYMBOL(svc_create);
+EXPORT_SYMBOL(svc_create_thread);
+EXPORT_SYMBOL(svc_exit_thread);
+EXPORT_SYMBOL(svc_destroy);
+EXPORT_SYMBOL(svc_drop);
+EXPORT_SYMBOL(svc_process);
+EXPORT_SYMBOL(svc_recv);
+EXPORT_SYMBOL(svc_wake_up);
+EXPORT_SYMBOL(svc_makesock);
+EXPORT_SYMBOL(svc_reserve);
+
+/* RPC statistics */
+#ifdef CONFIG_PROC_FS
+EXPORT_SYMBOL(rpc_proc_register);
+EXPORT_SYMBOL(rpc_proc_unregister);
+EXPORT_SYMBOL(rpc_proc_read);
+EXPORT_SYMBOL(svc_proc_register);
+EXPORT_SYMBOL(svc_proc_unregister);
+EXPORT_SYMBOL(svc_proc_read);
+#endif
+
+/* Generic XDR */
+EXPORT_SYMBOL(xdr_encode_array);
+EXPORT_SYMBOL(xdr_encode_string);
+EXPORT_SYMBOL(xdr_decode_string);
+EXPORT_SYMBOL(xdr_decode_string_inplace);
+EXPORT_SYMBOL(xdr_decode_netobj);
+EXPORT_SYMBOL(xdr_encode_netobj);
+EXPORT_SYMBOL(xdr_encode_pages);
+EXPORT_SYMBOL(xdr_inline_pages);
+EXPORT_SYMBOL(xdr_shift_buf);
+
+/* Debugging symbols */
+#ifdef RPC_DEBUG
+EXPORT_SYMBOL(rpc_debug);
+EXPORT_SYMBOL(nfs_debug);
+EXPORT_SYMBOL(nfsd_debug);
+EXPORT_SYMBOL(nlm_debug);
+#endif
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/svc.c b/uClinux-2.4.31-uc0/net/sunrpc/svc.c
new file mode 100644
index 0000000..068b28a
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/svc.c
@@ -0,0 +1,427 @@
+/*
+ * linux/net/sunrpc/svc.c
+ *
+ * High-level RPC service routines
+ *
+ * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#define __KERNEL_SYSCALLS__
+#include <linux/linkage.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/net.h>
+#include <linux/in.h>
+#include <linux/unistd.h>
+
+#include <linux/sunrpc/types.h>
+#include <linux/sunrpc/xdr.h>
+#include <linux/sunrpc/stats.h>
+#include <linux/sunrpc/svcsock.h>
+#include <linux/sunrpc/clnt.h>
+
+#define RPCDBG_FACILITY RPCDBG_SVCDSP
+#define RPC_PARANOIA 1
+
+/*
+ * Create an RPC service
+ */
+struct svc_serv *
+svc_create(struct svc_program *prog, unsigned int bufsize, unsigned int xdrsize)
+{
+ struct svc_serv *serv;
+
+ if (!(serv = (struct svc_serv *) kmalloc(sizeof(*serv), GFP_KERNEL)))
+ return NULL;
+
+ memset(serv, 0, sizeof(*serv));
+ serv->sv_program = prog;
+ serv->sv_nrthreads = 1;
+ serv->sv_stats = prog->pg_stats;
+ serv->sv_bufsz = bufsize? bufsize : 4096;
+ serv->sv_xdrsize = xdrsize;
+ INIT_LIST_HEAD(&serv->sv_threads);
+ INIT_LIST_HEAD(&serv->sv_sockets);
+ INIT_LIST_HEAD(&serv->sv_tempsocks);
+ INIT_LIST_HEAD(&serv->sv_permsocks);
+ spin_lock_init(&serv->sv_lock);
+
+ serv->sv_name = prog->pg_name;
+
+ /* Remove any stale portmap registrations */
+ svc_register(serv, 0, 0);
+
+ return serv;
+}
+
+/*
+ * Destroy an RPC service
+ */
+void
+svc_destroy(struct svc_serv *serv)
+{
+ struct svc_sock *svsk;
+
+ dprintk("RPC: svc_destroy(%s, %d)\n",
+ serv->sv_program->pg_name,
+ serv->sv_nrthreads);
+
+ if (serv->sv_nrthreads) {
+ if (--(serv->sv_nrthreads) != 0) {
+ svc_sock_update_bufs(serv);
+ return;
+ }
+ } else
+ printk("svc_destroy: no threads for serv=%p!\n", serv);
+
+ while (!list_empty(&serv->sv_tempsocks)) {
+ svsk = list_entry(serv->sv_tempsocks.next,
+ struct svc_sock,
+ sk_list);
+ svc_delete_socket(svsk);
+ }
+ while (!list_empty(&serv->sv_permsocks)) {
+ svsk = list_entry(serv->sv_permsocks.next,
+ struct svc_sock,
+ sk_list);
+ svc_delete_socket(svsk);
+ }
+
+ /* Unregister service with the portmapper */
+ svc_register(serv, 0, 0);
+ kfree(serv);
+}
+
+/*
+ * Allocate an RPC server buffer
+ * Later versions may do nifty things by allocating multiple pages
+ * of memory directly and putting them into the bufp->iov.
+ */
+int
+svc_init_buffer(struct svc_buf *bufp, unsigned int size)
+{
+ if (!(bufp->area = (u32 *) kmalloc(size, GFP_KERNEL)))
+ return 0;
+ bufp->base = bufp->area;
+ bufp->buf = bufp->area;
+ bufp->len = 0;
+ bufp->buflen = size >> 2;
+
+ bufp->iov[0].iov_base = bufp->area;
+ bufp->iov[0].iov_len = size;
+ bufp->nriov = 1;
+
+ return 1;
+}
+
+/*
+ * Release an RPC server buffer
+ */
+void
+svc_release_buffer(struct svc_buf *bufp)
+{
+ kfree(bufp->area);
+ bufp->area = 0;
+}
+
+/*
+ * Create a server thread
+ */
+int
+svc_create_thread(svc_thread_fn func, struct svc_serv *serv)
+{
+ struct svc_rqst *rqstp;
+ int error = -ENOMEM;
+
+ rqstp = kmalloc(sizeof(*rqstp), GFP_KERNEL);
+ if (!rqstp)
+ goto out;
+
+ memset(rqstp, 0, sizeof(*rqstp));
+ init_waitqueue_head(&rqstp->rq_wait);
+
+ if (!(rqstp->rq_argp = (u32 *) kmalloc(serv->sv_xdrsize, GFP_KERNEL))
+ || !(rqstp->rq_resp = (u32 *) kmalloc(serv->sv_xdrsize, GFP_KERNEL))
+ || !svc_init_buffer(&rqstp->rq_defbuf, serv->sv_bufsz))
+ goto out_thread;
+
+ serv->sv_nrthreads++;
+ rqstp->rq_server = serv;
+ error = kernel_thread((int (*)(void *)) func, rqstp, 0);
+ if (error < 0)
+ goto out_thread;
+ svc_sock_update_bufs(serv);
+ error = 0;
+out:
+ return error;
+
+out_thread:
+ svc_exit_thread(rqstp);
+ goto out;
+}
+
+/*
+ * Destroy an RPC server thread
+ */
+void
+svc_exit_thread(struct svc_rqst *rqstp)
+{
+ struct svc_serv *serv = rqstp->rq_server;
+
+ svc_release_buffer(&rqstp->rq_defbuf);
+ if (rqstp->rq_resp)
+ kfree(rqstp->rq_resp);
+ if (rqstp->rq_argp)
+ kfree(rqstp->rq_argp);
+ kfree(rqstp);
+
+ /* Release the server */
+ if (serv)
+ svc_destroy(serv);
+}
+
+/*
+ * Register an RPC service with the local portmapper.
+ * To unregister a service, call this routine with
+ * proto and port == 0.
+ */
+int
+svc_register(struct svc_serv *serv, int proto, unsigned short port)
+{
+ struct svc_program *progp;
+ unsigned long flags;
+ int i, error = 0, dummy;
+
+ progp = serv->sv_program;
+
+ dprintk("RPC: svc_register(%s, %s, %d)\n",
+ progp->pg_name, proto == IPPROTO_UDP? "udp" : "tcp", port);
+
+ if (!port)
+ current->sigpending = 0;
+
+ for (i = 0; i < progp->pg_nvers; i++) {
+ if (progp->pg_vers[i] == NULL)
+ continue;
+ error = rpc_register(progp->pg_prog, i, proto, port, &dummy);
+ if (error < 0)
+ break;
+ if (port && !dummy) {
+ error = -EACCES;
+ break;
+ }
+ }
+
+ if (!port) {
+ spin_lock_irqsave(&current->sigmask_lock, flags);
+ recalc_sigpending(current);
+ spin_unlock_irqrestore(&current->sigmask_lock, flags);
+ }
+
+ return error;
+}
+
+/*
+ * Process the RPC request.
+ */
+int
+svc_process(struct svc_serv *serv, struct svc_rqst *rqstp)
+{
+ struct svc_program *progp;
+ struct svc_version *versp = NULL; /* compiler food */
+ struct svc_procedure *procp = NULL;
+ struct svc_buf * argp = &rqstp->rq_argbuf;
+ struct svc_buf * resp = &rqstp->rq_resbuf;
+ kxdrproc_t xdr;
+ u32 *bufp, *statp;
+ u32 dir, prog, vers, proc,
+ auth_stat, rpc_stat;
+
+ rpc_stat = rpc_success;
+ bufp = argp->buf;
+
+ if (argp->len < 5)
+ goto err_short_len;
+
+ dir = ntohl(*bufp++);
+ vers = ntohl(*bufp++);
+
+ /* First words of reply: */
+ svc_putlong(resp, xdr_one); /* REPLY */
+ svc_putlong(resp, xdr_zero); /* ACCEPT */
+
+ if (dir != 0) /* direction != CALL */
+ goto err_bad_dir;
+ if (vers != 2) /* RPC version number */
+ goto err_bad_rpc;
+
+ rqstp->rq_prog = prog = ntohl(*bufp++); /* program number */
+ rqstp->rq_vers = vers = ntohl(*bufp++); /* version number */
+ rqstp->rq_proc = proc = ntohl(*bufp++); /* procedure number */
+
+ argp->buf += 5;
+ argp->len -= 5;
+
+ /* Used by nfsd to only allow the NULL procedure for amd. */
+ if (rqstp->rq_auth && !rqstp->rq_client && proc) {
+ auth_stat = rpc_autherr_badcred;
+ goto err_bad_auth;
+ }
+
+ /*
+ * Decode auth data, and add verifier to reply buffer.
+ * We do this before anything else in order to get a decent
+ * auth verifier.
+ */
+ svc_authenticate(rqstp, &rpc_stat, &auth_stat);
+
+ if (rpc_stat != rpc_success)
+ goto err_garbage;
+
+ if (auth_stat != rpc_auth_ok)
+ goto err_bad_auth;
+
+ progp = serv->sv_program;
+ if (prog != progp->pg_prog)
+ goto err_bad_prog;
+
+ if (vers >= progp->pg_nvers ||
+ !(versp = progp->pg_vers[vers]))
+ goto err_bad_vers;
+
+ procp = versp->vs_proc + proc;
+ if (proc >= versp->vs_nproc || !procp->pc_func)
+ goto err_bad_proc;
+ rqstp->rq_server = serv;
+ rqstp->rq_procinfo = procp;
+
+ /* Syntactic check complete */
+ serv->sv_stats->rpccnt++;
+
+ /* Build the reply header. */
+ statp = resp->buf;
+ svc_putlong(resp, rpc_success); /* RPC_SUCCESS */
+
+ /* Bump per-procedure stats counter */
+ procp->pc_count++;
+
+ /* Initialize storage for argp and resp */
+ memset(rqstp->rq_argp, 0, procp->pc_argsize);
+ memset(rqstp->rq_resp, 0, procp->pc_ressize);
+
+ /* un-reserve some of the out-queue now that we have a
+ * better idea of reply size
+ */
+ if (procp->pc_xdrressize)
+ svc_reserve(rqstp, procp->pc_xdrressize<<2);
+
+ /* Call the function that processes the request. */
+ if (!versp->vs_dispatch) {
+ /* Decode arguments */
+ xdr = procp->pc_decode;
+ if (xdr && !xdr(rqstp, rqstp->rq_argbuf.buf, rqstp->rq_argp))
+ goto err_garbage;
+
+ *statp = procp->pc_func(rqstp, rqstp->rq_argp, rqstp->rq_resp);
+
+ /* Encode reply */
+ if (*statp == rpc_success && (xdr = procp->pc_encode)
+ && !xdr(rqstp, rqstp->rq_resbuf.buf, rqstp->rq_resp)) {
+ dprintk("svc: failed to encode reply\n");
+ /* serv->sv_stats->rpcsystemerr++; */
+ *statp = rpc_system_err;
+ }
+ } else {
+ dprintk("svc: calling dispatcher\n");
+ if (!versp->vs_dispatch(rqstp, statp)) {
+ /* Release reply info */
+ if (procp->pc_release)
+ procp->pc_release(rqstp, NULL, rqstp->rq_resp);
+ goto dropit;
+ }
+ }
+
+ /* Check RPC status result */
+ if (*statp != rpc_success)
+ resp->len = statp + 1 - resp->base;
+
+ /* Release reply info */
+ if (procp->pc_release)
+ procp->pc_release(rqstp, NULL, rqstp->rq_resp);
+
+ if (procp->pc_encode == NULL)
+ goto dropit;
+sendit:
+ return svc_send(rqstp);
+
+dropit:
+ dprintk("svc: svc_process dropit\n");
+ svc_drop(rqstp);
+ return 0;
+
+err_short_len:
+#ifdef RPC_PARANOIA
+ printk("svc: short len %d, dropping request\n", argp->len);
+#endif
+ goto dropit; /* drop request */
+
+err_bad_dir:
+#ifdef RPC_PARANOIA
+ printk("svc: bad direction %d, dropping request\n", dir);
+#endif
+ serv->sv_stats->rpcbadfmt++;
+ goto dropit; /* drop request */
+
+err_bad_rpc:
+ serv->sv_stats->rpcbadfmt++;
+ resp->buf[-1] = xdr_one; /* REJECT */
+ svc_putlong(resp, xdr_zero); /* RPC_MISMATCH */
+ svc_putlong(resp, xdr_two); /* Only RPCv2 supported */
+ svc_putlong(resp, xdr_two);
+ goto sendit;
+
+err_bad_auth:
+ dprintk("svc: authentication failed (%d)\n", ntohl(auth_stat));
+ serv->sv_stats->rpcbadauth++;
+ resp->buf[-1] = xdr_one; /* REJECT */
+ svc_putlong(resp, xdr_one); /* AUTH_ERROR */
+ svc_putlong(resp, auth_stat); /* status */
+ goto sendit;
+
+err_bad_prog:
+#ifdef RPC_PARANOIA
+ if (prog != 100227 || progp->pg_prog != 100003)
+ printk("svc: unknown program %d (me %d)\n", prog, progp->pg_prog);
+ /* else it is just a Solaris client seeing if ACLs are supported */
+#endif
+ serv->sv_stats->rpcbadfmt++;
+ svc_putlong(resp, rpc_prog_unavail);
+ goto sendit;
+
+err_bad_vers:
+#ifdef RPC_PARANOIA
+ if (vers)
+ printk("svc: unknown version (%d)\n", vers);
+#endif
+ serv->sv_stats->rpcbadfmt++;
+ svc_putlong(resp, rpc_prog_mismatch);
+ svc_putlong(resp, htonl(progp->pg_lovers));
+ svc_putlong(resp, htonl(progp->pg_hivers));
+ goto sendit;
+
+err_bad_proc:
+#ifdef RPC_PARANOIA
+ printk("svc: unknown procedure (%d)\n", proc);
+#endif
+ serv->sv_stats->rpcbadfmt++;
+ svc_putlong(resp, rpc_proc_unavail);
+ goto sendit;
+
+err_garbage:
+#ifdef RPC_PARANOIA
+ printk("svc: failed to decode args\n");
+#endif
+ serv->sv_stats->rpcbadfmt++;
+ svc_putlong(resp, rpc_garbage_args);
+ goto sendit;
+}
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/svcauth.c b/uClinux-2.4.31-uc0/net/sunrpc/svcauth.c
new file mode 100644
index 0000000..42813ac
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/svcauth.c
@@ -0,0 +1,166 @@
+/*
+ * linux/net/sunrpc/svcauth.c
+ *
+ * The generic interface for RPC authentication on the server side.
+ *
+ * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
+ *
+ * CHANGES
+ * 19-Apr-2000 Chris Evans - Security fix
+ */
+
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/sunrpc/types.h>
+#include <linux/sunrpc/xdr.h>
+#include <linux/sunrpc/svcauth.h>
+#include <linux/sunrpc/svcsock.h>
+
+#define RPCDBG_FACILITY RPCDBG_AUTH
+
+/*
+ * Type of authenticator function
+ */
+typedef void (*auth_fn_t)(struct svc_rqst *rqstp, u32 *statp, u32 *authp);
+
+/*
+ * Builtin auth flavors
+ */
+static void svcauth_null(struct svc_rqst *rqstp, u32 *statp, u32 *authp);
+static void svcauth_unix(struct svc_rqst *rqstp, u32 *statp, u32 *authp);
+
+/*
+ * Max number of authentication flavors we support
+ */
+#define RPC_SVCAUTH_MAX 8
+
+/*
+ * Table of authenticators
+ */
+static auth_fn_t authtab[RPC_SVCAUTH_MAX] = {
+ svcauth_null,
+ svcauth_unix,
+ NULL,
+};
+
+void
+svc_authenticate(struct svc_rqst *rqstp, u32 *statp, u32 *authp)
+{
+ u32 flavor;
+ auth_fn_t func;
+
+ *statp = rpc_success;
+ *authp = rpc_auth_ok;
+
+ svc_getlong(&rqstp->rq_argbuf, flavor);
+ flavor = ntohl(flavor);
+
+ dprintk("svc: svc_authenticate (%d)\n", flavor);
+ if (flavor >= RPC_SVCAUTH_MAX || !(func = authtab[flavor])) {
+ *authp = rpc_autherr_badcred;
+ return;
+ }
+
+ rqstp->rq_cred.cr_flavor = flavor;
+ func(rqstp, statp, authp);
+}
+
+int
+svc_auth_register(u32 flavor, auth_fn_t func)
+{
+ if (flavor >= RPC_SVCAUTH_MAX || authtab[flavor])
+ return -EINVAL;
+ authtab[flavor] = func;
+ return 0;
+}
+
+void
+svc_auth_unregister(u32 flavor)
+{
+ if (flavor < RPC_SVCAUTH_MAX)
+ authtab[flavor] = NULL;
+}
+
+static void
+svcauth_null(struct svc_rqst *rqstp, u32 *statp, u32 *authp)
+{
+ struct svc_buf *argp = &rqstp->rq_argbuf;
+ struct svc_buf *resp = &rqstp->rq_resbuf;
+
+ if ((argp->len -= 3) < 0) {
+ *statp = rpc_garbage_args;
+ return;
+ }
+ if (*(argp->buf)++ != 0) { /* we already skipped the flavor */
+ dprintk("svc: bad null cred\n");
+ *authp = rpc_autherr_badcred;
+ return;
+ }
+ if (*(argp->buf)++ != RPC_AUTH_NULL || *(argp->buf)++ != 0) {
+ dprintk("svc: bad null verf\n");
+ *authp = rpc_autherr_badverf;
+ return;
+ }
+
+ /* Signal that mapping to nobody uid/gid is required */
+ rqstp->rq_cred.cr_uid = (uid_t) -1;
+ rqstp->rq_cred.cr_gid = (gid_t) -1;
+ rqstp->rq_cred.cr_groups[0] = NOGROUP;
+
+ /* Put NULL verifier */
+ rqstp->rq_verfed = 1;
+ svc_putlong(resp, RPC_AUTH_NULL);
+ svc_putlong(resp, 0);
+}
+
+static void
+svcauth_unix(struct svc_rqst *rqstp, u32 *statp, u32 *authp)
+{
+ struct svc_buf *argp = &rqstp->rq_argbuf;
+ struct svc_buf *resp = &rqstp->rq_resbuf;
+ struct svc_cred *cred = &rqstp->rq_cred;
+ u32 *bufp = argp->buf, slen, i;
+ int len = argp->len;
+
+ if ((len -= 3) < 0) {
+ *statp = rpc_garbage_args;
+ return;
+ }
+
+ bufp++; /* length */
+ bufp++; /* time stamp */
+ slen = (ntohl(*bufp++) + 3) >> 2; /* machname length */
+ if (slen > 64 || (len -= slen + 3) < 0)
+ goto badcred;
+ bufp += slen; /* skip machname */
+
+ cred->cr_uid = ntohl(*bufp++); /* uid */
+ cred->cr_gid = ntohl(*bufp++); /* gid */
+
+ slen = ntohl(*bufp++); /* gids length */
+ if (slen > 16 || (len -= slen + 2) < 0)
+ goto badcred;
+ for (i = 0; i < NGROUPS && i < slen; i++)
+ cred->cr_groups[i] = ntohl(*bufp++);
+ if (i < NGROUPS)
+ cred->cr_groups[i] = NOGROUP;
+ bufp += (slen - i);
+
+ if (*bufp++ != RPC_AUTH_NULL || *bufp++ != 0) {
+ *authp = rpc_autherr_badverf;
+ return;
+ }
+
+ argp->buf = bufp;
+ argp->len = len;
+
+ /* Put NULL verifier */
+ rqstp->rq_verfed = 1;
+ svc_putlong(resp, RPC_AUTH_NULL);
+ svc_putlong(resp, 0);
+
+ return;
+
+badcred:
+ *authp = rpc_autherr_badcred;
+}
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/svcauth_des.c b/uClinux-2.4.31-uc0/net/sunrpc/svcauth_des.c
new file mode 100644
index 0000000..5af0ec0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/svcauth_des.c
@@ -0,0 +1,215 @@
+/*
+ * linux/net/sunrpc/svcauth_des.c
+ *
+ * Server-side AUTH_DES handling.
+ *
+ * Copyright (C) 1996, 1997 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/sunrpc/types.h>
+#include <linux/sunrpc/xdr.h>
+#include <linux/sunrpc/svcauth.h>
+#include <linux/sunrpc/svcsock.h>
+
+#define RPCDBG_FACILITY RPCDBG_AUTH
+
+/*
+ * DES cedential cache.
+ * The cache is indexed by fullname/key to allow for multiple sessions
+ * by the same user from different hosts.
+ * It would be tempting to use the client's IP address rather than the
+ * conversation key as an index, but that could become problematic for
+ * multi-homed hosts that distribute traffic across their interfaces.
+ */
+struct des_cred {
+ struct des_cred * dc_next;
+ char * dc_fullname;
+ u32 dc_nickname;
+ des_cblock dc_key; /* conversation key */
+ des_cblock dc_xkey; /* encrypted conv. key */
+ des_key_schedule dc_keysched;
+};
+
+#define ADN_FULLNAME 0
+#define ADN_NICKNAME 1
+
+/*
+ * The default slack allowed when checking for replayed credentials
+ * (in milliseconds).
+ */
+#define DES_REPLAY_SLACK 2000
+
+/*
+ * Make sure we don't place more than one call to the key server at
+ * a time.
+ */
+static int in_keycall = 0;
+
+#define FAIL(err) \
+ { if (data) put_cred(data); \
+ *authp = rpc_autherr_##err; \
+ return; \
+ }
+
+void
+svcauth_des(struct svc_rqst *rqstp, u32 *statp, u32 *authp)
+{
+ struct svc_buf *argp = &rqstp->rq_argbuf;
+ struct svc_buf *resp = &rqstp->rq_resbuf;
+ struct svc_cred *cred = &rqstp->rq_cred;
+ struct des_cred *data = NULL;
+ u32 cryptkey[2];
+ u32 cryptbuf[4];
+ u32 *p = argp->buf;
+ int len = argp->len, slen, i;
+
+ *authp = rpc_auth_ok;
+
+ if ((argp->len -= 3) < 0) {
+ *statp = rpc_garbage_args;
+ return;
+ }
+
+ p++; /* skip length field */
+ namekind = ntohl(*p++); /* fullname/nickname */
+
+ /* Get the credentials */
+ if (namekind == ADN_NICKNAME) {
+ /* If we can't find the cached session key, initiate a
+ * new session. */
+ if (!(data = get_cred_bynick(*p++)))
+ FAIL(rejectedcred);
+ } else if (namekind == ADN_FULLNAME) {
+ p = xdr_decode_string(p, &fullname, &len, RPC_MAXNETNAMELEN);
+ if (p == NULL)
+ FAIL(badcred);
+ cryptkey[0] = *p++; /* get the encrypted key */
+ cryptkey[1] = *p++;
+ cryptbuf[2] = *p++; /* get the encrypted window */
+ } else {
+ FAIL(badcred);
+ }
+
+ /* If we're just updating the key, silently discard the request. */
+ if (data && data->dc_locked) {
+ *authp = rpc_autherr_dropit;
+ _put_cred(data); /* release but don't unlock */
+ return;
+ }
+
+ /* Get the verifier flavor and length */
+ if (ntohl(*p++) != RPC_AUTH_DES && ntohl(*p++) != 12)
+ FAIL(badverf);
+
+ cryptbuf[0] = *p++; /* encrypted time stamp */
+ cryptbuf[1] = *p++;
+ cryptbuf[3] = *p++; /* 0 or window - 1 */
+
+ if (namekind == ADN_NICKNAME) {
+ status = des_ecb_encrypt((des_block *) cryptbuf,
+ (des_block *) cryptbuf,
+ data->dc_keysched, DES_DECRYPT);
+ } else {
+ /* We first have to decrypt the new session key and
+ * fill in the UNIX creds. */
+ if (!(data = get_cred_byname(rqstp, authp, fullname, cryptkey)))
+ return;
+ status = des_cbc_encrypt((des_cblock *) cryptbuf,
+ (des_cblock *) cryptbuf, 16,
+ data->dc_keysched,
+ (des_cblock *) &ivec,
+ DES_DECRYPT);
+ }
+ if (status) {
+ printk("svcauth_des: DES decryption failed (status %d)\n",
+ status);
+ FAIL(badverf);
+ }
+
+ /* Now check the whole lot */
+ if (namekind == ADN_FULLNAME) {
+ unsigned long winverf;
+
+ data->dc_window = ntohl(cryptbuf[2]);
+ winverf = ntohl(cryptbuf[2]);
+ if (window != winverf - 1) {
+ printk("svcauth_des: bad window verifier!\n");
+ FAIL(badverf);
+ }
+ }
+
+ /* XDR the decrypted timestamp */
+ cryptbuf[0] = ntohl(cryptbuf[0]);
+ cryptbuf[1] = ntohl(cryptbuf[1]);
+ if (cryptbuf[1] > 1000000) {
+ dprintk("svcauth_des: bad usec value %u\n", cryptbuf[1]);
+ if (namekind == ADN_NICKNAME)
+ FAIL(rejectedverf);
+ FAIL(badverf);
+ }
+
+ /*
+ * Check for replayed credentials. We must allow for reordering
+ * of requests by the network, and the OS scheduler, hence we
+ * cannot expect timestamps to be increasing monotonically.
+ * This opens a small security hole, therefore the replay_slack
+ * value shouldn't be too large.
+ */
+ if ((delta = cryptbuf[0] - data->dc_timestamp[0]) <= 0) {
+ switch (delta) {
+ case -1:
+ delta = -1000000;
+ case 0:
+ delta += cryptbuf[1] - data->dc_timestamp[1];
+ break;
+ default:
+ delta = -1000000;
+ }
+ if (delta < DES_REPLAY_SLACK)
+ FAIL(rejectedverf);
+#ifdef STRICT_REPLAY_CHECKS
+ /* TODO: compare time stamp to last five timestamps cached
+ * and reject (drop?) request if a match is found. */
+#endif
+ }
+
+ now = xtime;
+ now.tv_secs -= data->dc_window;
+ if (now.tv_secs < cryptbuf[0] ||
+ (now.tv_secs == cryptbuf[0] && now.tv_usec < cryptbuf[1]))
+ FAIL(rejectedverf);
+
+ /* Okay, we're done. Update the lot */
+ if (namekind == ADN_FULLNAME)
+ data->dc_valid = 1;
+ data->dc_timestamp[0] = cryptbuf[0];
+ data->dc_timestamp[1] = cryptbuf[1];
+
+ put_cred(data);
+ return;
+garbage:
+ *statp = rpc_garbage_args;
+ return;
+}
+
+/*
+ * Call the keyserver to obtain the decrypted conversation key and
+ * UNIX creds. We use a Linux-specific keycall extension that does
+ * both things in one go.
+ */
+static struct des_cred *
+get_cred_byname(struct svc_rqst *rqstp, u32 *authp, char *fullname, u32 *cryptkey)
+{
+ static int in_keycall = 0;
+ struct des_cred *cred;
+
+ if (in_keycall) {
+ *authp = rpc_autherr_dropit;
+ return NULL;
+ }
+ in_keycall = 1;
+ in_keycall = 0;
+ return cred;
+}
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/svcsock.c b/uClinux-2.4.31-uc0/net/sunrpc/svcsock.c
new file mode 100644
index 0000000..b5a1447
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/svcsock.c
@@ -0,0 +1,1343 @@
+/*
+ * linux/net/sunrpc/svcsock.c
+ *
+ * These are the RPC server socket internals.
+ *
+ * The server scheduling algorithm does not always distribute the load
+ * evenly when servicing a single client. May need to modify the
+ * svc_sock_enqueue procedure...
+ *
+ * TCP support is largely untested and may be a little slow. The problem
+ * is that we currently do two separate recvfrom's, one for the 4-byte
+ * record length, and the second for the actual record. This could possibly
+ * be improved by always reading a minimum size of around 100 bytes and
+ * tucking any superfluous bytes away in a temporary store. Still, that
+ * leaves write requests out in the rain. An alternative may be to peek at
+ * the first skb in the queue, and if it matches the next TCP sequence
+ * number, to extract the record marker. Yuck.
+ *
+ * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/fcntl.h>
+#include <linux/net.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/udp.h>
+#include <linux/version.h>
+#include <linux/unistd.h>
+#include <linux/slab.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/checksum.h>
+#include <net/ip.h>
+#include <asm/uaccess.h>
+#include <asm/ioctls.h>
+
+#include <linux/sunrpc/types.h>
+#include <linux/sunrpc/xdr.h>
+#include <linux/sunrpc/svcsock.h>
+#include <linux/sunrpc/stats.h>
+
+/* SMP locking strategy:
+ *
+ * svc_serv->sv_lock protects most stuff for that service.
+ *
+ * Some flags can be set to certain values at any time
+ * providing that certain rules are followed:
+ *
+ * SK_BUSY can be set to 0 at any time.
+ * svc_sock_enqueue must be called afterwards
+ * SK_CONN, SK_DATA, can be set or cleared at any time.
+ * after a set, svc_sock_enqueue must be called.
+ * after a clear, the socket must be read/accepted
+ * if this succeeds, it must be set again.
+ * SK_CLOSE can set at any time. It is never cleared.
+ *
+ */
+
+#define RPCDBG_FACILITY RPCDBG_SVCSOCK
+
+
+static struct svc_sock *svc_setup_socket(struct svc_serv *, struct socket *,
+ int *errp, int pmap_reg);
+static void svc_udp_data_ready(struct sock *, int);
+static int svc_udp_recvfrom(struct svc_rqst *);
+static int svc_udp_sendto(struct svc_rqst *);
+
+
+/*
+ * Queue up an idle server thread. Must have serv->sv_lock held.
+ * Note: this is really a stack rather than a queue, so that we only
+ * use as many different threads as we need, and the rest don't polute
+ * the cache.
+ */
+static inline void
+svc_serv_enqueue(struct svc_serv *serv, struct svc_rqst *rqstp)
+{
+ list_add(&rqstp->rq_list, &serv->sv_threads);
+}
+
+/*
+ * Dequeue an nfsd thread. Must have serv->sv_lock held.
+ */
+static inline void
+svc_serv_dequeue(struct svc_serv *serv, struct svc_rqst *rqstp)
+{
+ list_del(&rqstp->rq_list);
+}
+
+/*
+ * Release an skbuff after use
+ */
+static inline void
+svc_release_skb(struct svc_rqst *rqstp)
+{
+ struct sk_buff *skb = rqstp->rq_skbuff;
+
+ if (!skb)
+ return;
+ rqstp->rq_skbuff = NULL;
+
+ dprintk("svc: service %p, releasing skb %p\n", rqstp, skb);
+ skb_free_datagram(rqstp->rq_sock->sk_sk, skb);
+}
+
+/*
+ * Queue up a socket with data pending. If there are idle nfsd
+ * processes, wake 'em up.
+ *
+ */
+static void
+svc_sock_enqueue(struct svc_sock *svsk)
+{
+ struct svc_serv *serv = svsk->sk_server;
+ struct svc_rqst *rqstp;
+
+ if (!(svsk->sk_flags &
+ ( (1<<SK_CONN)|(1<<SK_DATA)|(1<<SK_CLOSE)) ))
+ return;
+ if (test_bit(SK_DEAD, &svsk->sk_flags))
+ return;
+
+ spin_lock_bh(&serv->sv_lock);
+
+ if (!list_empty(&serv->sv_threads) &&
+ !list_empty(&serv->sv_sockets))
+ printk(KERN_ERR
+ "svc_sock_enqueue: threads and sockets both waiting??\n");
+
+ if (test_bit(SK_BUSY, &svsk->sk_flags)) {
+ /* Don't enqueue socket while daemon is receiving */
+ dprintk("svc: socket %p busy, not enqueued\n", svsk->sk_sk);
+ goto out_unlock;
+ }
+
+ if (((svsk->sk_reserved + serv->sv_bufsz)*2
+ > sock_wspace(svsk->sk_sk))
+ && !test_bit(SK_CLOSE, &svsk->sk_flags)
+ && !test_bit(SK_CONN, &svsk->sk_flags)) {
+ /* Don't enqueue while not enough space for reply */
+ dprintk("svc: socket %p no space, %d*2 > %ld, not enqueued\n",
+ svsk->sk_sk, svsk->sk_reserved+serv->sv_bufsz,
+ sock_wspace(svsk->sk_sk));
+ goto out_unlock;
+ }
+
+ /* Mark socket as busy. It will remain in this state until the
+ * server has processed all pending data and put the socket back
+ * on the idle list.
+ */
+ set_bit(SK_BUSY, &svsk->sk_flags);
+
+ if (!list_empty(&serv->sv_threads)) {
+ rqstp = list_entry(serv->sv_threads.next,
+ struct svc_rqst,
+ rq_list);
+ dprintk("svc: socket %p served by daemon %p\n",
+ svsk->sk_sk, rqstp);
+ svc_serv_dequeue(serv, rqstp);
+ if (rqstp->rq_sock)
+ printk(KERN_ERR
+ "svc_sock_enqueue: server %p, rq_sock=%p!\n",
+ rqstp, rqstp->rq_sock);
+ rqstp->rq_sock = svsk;
+ svsk->sk_inuse++;
+ rqstp->rq_reserved = serv->sv_bufsz;
+ svsk->sk_reserved += rqstp->rq_reserved;
+ wake_up(&rqstp->rq_wait);
+ } else {
+ dprintk("svc: socket %p put into queue\n", svsk->sk_sk);
+ list_add_tail(&svsk->sk_ready, &serv->sv_sockets);
+ set_bit(SK_QUED, &svsk->sk_flags);
+ }
+
+out_unlock:
+ spin_unlock_bh(&serv->sv_lock);
+}
+
+/*
+ * Dequeue the first socket. Must be called with the serv->sv_lock held.
+ */
+static inline struct svc_sock *
+svc_sock_dequeue(struct svc_serv *serv)
+{
+ struct svc_sock *svsk;
+
+ if (list_empty(&serv->sv_sockets))
+ return NULL;
+
+ svsk = list_entry(serv->sv_sockets.next,
+ struct svc_sock, sk_ready);
+ list_del(&svsk->sk_ready);
+
+ dprintk("svc: socket %p dequeued, inuse=%d\n",
+ svsk->sk_sk, svsk->sk_inuse);
+ clear_bit(SK_QUED, &svsk->sk_flags);
+
+ return svsk;
+}
+
+/*
+ * Having read something from a socket, check whether it
+ * needs to be re-enqueued.
+ * Note: SK_DATA only gets cleared when a read-attempt finds
+ * no (or insufficient) data.
+ */
+static inline void
+svc_sock_received(struct svc_sock *svsk)
+{
+ clear_bit(SK_BUSY, &svsk->sk_flags);
+ svc_sock_enqueue(svsk);
+}
+
+
+/**
+ * svc_reserve - change the space reserved for the reply to a request.
+ * @rqstp: The request in question
+ * @space: new max space to reserve
+ *
+ * Each request reserves some space on the output queue of the socket
+ * to make sure the reply fits. This function reduces that reserved
+ * space to be the amount of space used already, plus @space.
+ *
+ */
+void svc_reserve(struct svc_rqst *rqstp, int space)
+{
+ space += rqstp->rq_resbuf.len<<2;
+
+ if (space < rqstp->rq_reserved) {
+ struct svc_sock *svsk = rqstp->rq_sock;
+ spin_lock_bh(&svsk->sk_server->sv_lock);
+ svsk->sk_reserved -= (rqstp->rq_reserved - space);
+ rqstp->rq_reserved = space;
+ spin_unlock_bh(&svsk->sk_server->sv_lock);
+
+ svc_sock_enqueue(svsk);
+ }
+}
+
+/*
+ * Release a socket after use.
+ */
+static inline void
+svc_sock_put(struct svc_sock *svsk)
+{
+ struct svc_serv *serv = svsk->sk_server;
+
+ spin_lock_bh(&serv->sv_lock);
+ if (!--(svsk->sk_inuse) && test_bit(SK_DEAD, &svsk->sk_flags)) {
+ spin_unlock_bh(&serv->sv_lock);
+ dprintk("svc: releasing dead socket\n");
+ sock_release(svsk->sk_sock);
+ kfree(svsk);
+ }
+ else
+ spin_unlock_bh(&serv->sv_lock);
+}
+
+static void
+svc_sock_release(struct svc_rqst *rqstp)
+{
+ struct svc_sock *svsk = rqstp->rq_sock;
+
+ svc_release_skb(rqstp);
+
+ /* Reset response buffer and release
+ * the reservation.
+ * But first, check that enough space was reserved
+ * for the reply, otherwise we have a bug!
+ */
+ if ((rqstp->rq_resbuf.len<<2) > rqstp->rq_reserved)
+ printk(KERN_ERR "RPC request reserved %d but used %d\n",
+ rqstp->rq_reserved,
+ rqstp->rq_resbuf.len<<2);
+
+ rqstp->rq_resbuf.buf = rqstp->rq_resbuf.base;
+ rqstp->rq_resbuf.len = 0;
+ svc_reserve(rqstp, 0);
+ rqstp->rq_sock = NULL;
+
+ svc_sock_put(svsk);
+}
+
+/*
+ * External function to wake up a server waiting for data
+ */
+void
+svc_wake_up(struct svc_serv *serv)
+{
+ struct svc_rqst *rqstp;
+
+ spin_lock_bh(&serv->sv_lock);
+ if (!list_empty(&serv->sv_threads)) {
+ rqstp = list_entry(serv->sv_threads.next,
+ struct svc_rqst,
+ rq_list);
+ dprintk("svc: daemon %p woken up.\n", rqstp);
+ /*
+ svc_serv_dequeue(serv, rqstp);
+ rqstp->rq_sock = NULL;
+ */
+ wake_up(&rqstp->rq_wait);
+ }
+ spin_unlock_bh(&serv->sv_lock);
+}
+
+/*
+ * Generic sendto routine
+ */
+static int
+svc_sendto(struct svc_rqst *rqstp, struct iovec *iov, int nr)
+{
+ mm_segment_t oldfs;
+ struct svc_sock *svsk = rqstp->rq_sock;
+ struct socket *sock = svsk->sk_sock;
+ struct msghdr msg;
+ char buffer[CMSG_SPACE(sizeof(struct in_pktinfo))];
+ struct cmsghdr *cmh = (struct cmsghdr *)buffer;
+ struct in_pktinfo *pki = (struct in_pktinfo *)CMSG_DATA(cmh);
+ int i, buflen, len;
+
+ for (i = buflen = 0; i < nr; i++)
+ buflen += iov[i].iov_len;
+
+ msg.msg_name = &rqstp->rq_addr;
+ msg.msg_namelen = sizeof(rqstp->rq_addr);
+ msg.msg_iov = iov;
+ msg.msg_iovlen = nr;
+ if (rqstp->rq_prot == IPPROTO_UDP) {
+ msg.msg_control = cmh;
+ msg.msg_controllen = sizeof(buffer);
+ cmh->cmsg_len = CMSG_LEN(sizeof(*pki));
+ cmh->cmsg_level = SOL_IP;
+ cmh->cmsg_type = IP_PKTINFO;
+ pki->ipi_ifindex = 0;
+ pki->ipi_spec_dst.s_addr = rqstp->rq_daddr;
+ } else {
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ }
+
+ /* This was MSG_DONTWAIT, but I now want it to wait.
+ * The only thing that it would wait for is memory and
+ * if we are fairly low on memory, then we aren't likely
+ * to make much progress anyway.
+ * sk->sndtimeo is set to 30seconds just in case.
+ */
+ msg.msg_flags = 0;
+
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ len = sock_sendmsg(sock, &msg, buflen);
+ set_fs(oldfs);
+
+ dprintk("svc: socket %p sendto([%p %Zu... ], %d, %d) = %d\n",
+ rqstp->rq_sock, iov[0].iov_base, iov[0].iov_len, nr, buflen, len);
+
+ return len;
+}
+
+/*
+ * Check input queue length
+ */
+static int
+svc_recv_available(struct svc_sock *svsk)
+{
+ mm_segment_t oldfs;
+ struct socket *sock = svsk->sk_sock;
+ int avail, err;
+
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ err = sock->ops->ioctl(sock, TIOCINQ, (unsigned long) &avail);
+ set_fs(oldfs);
+
+ return (err >= 0)? avail : err;
+}
+
+/*
+ * Generic recvfrom routine.
+ */
+static int
+svc_recvfrom(struct svc_rqst *rqstp, struct iovec *iov, int nr, int buflen)
+{
+ mm_segment_t oldfs;
+ struct msghdr msg;
+ struct socket *sock;
+ int len, alen;
+
+ rqstp->rq_addrlen = sizeof(rqstp->rq_addr);
+ sock = rqstp->rq_sock->sk_sock;
+
+ msg.msg_name = &rqstp->rq_addr;
+ msg.msg_namelen = sizeof(rqstp->rq_addr);
+ msg.msg_iov = iov;
+ msg.msg_iovlen = nr;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+
+ msg.msg_flags = MSG_DONTWAIT;
+
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ len = sock_recvmsg(sock, &msg, buflen, MSG_DONTWAIT);
+ set_fs(oldfs);
+
+ /* sock_recvmsg doesn't fill in the name/namelen, so we must..
+ * possibly we should cache this in the svc_sock structure
+ * at accept time. FIXME
+ */
+ alen = sizeof(rqstp->rq_addr);
+ sock->ops->getname(sock, (struct sockaddr *)&rqstp->rq_addr, &alen, 1);
+
+ dprintk("svc: socket %p recvfrom(%p, %Zu) = %d\n",
+ rqstp->rq_sock, iov[0].iov_base, iov[0].iov_len, len);
+
+ return len;
+}
+
+/*
+ * Set socket snd and rcv buffer lengths
+ */
+static inline void
+svc_sock_setbufsize(struct socket *sock, unsigned int snd, unsigned int rcv)
+{
+#if 0
+ mm_segment_t oldfs;
+ oldfs = get_fs(); set_fs(KERNEL_DS);
+ sock_setsockopt(sock, SOL_SOCKET, SO_SNDBUF,
+ (char*)&snd, sizeof(snd));
+ sock_setsockopt(sock, SOL_SOCKET, SO_RCVBUF,
+ (char*)&rcv, sizeof(rcv));
+#else
+ /* sock_setsockopt limits use to sysctl_?mem_max,
+ * which isn't acceptable. Until that is made conditional
+ * on not having CAP_SYS_RESOURCE or similar, we go direct...
+ * DaveM said I could!
+ */
+ lock_sock(sock->sk);
+ sock->sk->sndbuf = snd * 2;
+ sock->sk->rcvbuf = rcv * 2;
+ sock->sk->userlocks |= SOCK_SNDBUF_LOCK|SOCK_RCVBUF_LOCK;
+ release_sock(sock->sk);
+#endif
+}
+/*
+ * INET callback when data has been received on the socket.
+ */
+static void
+svc_udp_data_ready(struct sock *sk, int count)
+{
+ struct svc_sock *svsk = (struct svc_sock *)(sk->user_data);
+
+ if (!svsk)
+ goto out;
+ dprintk("svc: socket %p(inet %p), count=%d, busy=%d\n",
+ svsk, sk, count, test_bit(SK_BUSY, &svsk->sk_flags));
+ set_bit(SK_DATA, &svsk->sk_flags);
+ svc_sock_enqueue(svsk);
+ out:
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible(sk->sleep);
+}
+
+/*
+ * INET callback when space is newly available on the socket.
+ */
+static void
+svc_write_space(struct sock *sk)
+{
+ struct svc_sock *svsk = (struct svc_sock *)(sk->user_data);
+
+ if (svsk) {
+ dprintk("svc: socket %p(inet %p), write_space busy=%d\n",
+ svsk, sk, test_bit(SK_BUSY, &svsk->sk_flags));
+ svc_sock_enqueue(svsk);
+ }
+
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible(sk->sleep);
+}
+
+/*
+ * Receive a datagram from a UDP socket.
+ */
+static int
+svc_udp_recvfrom(struct svc_rqst *rqstp)
+{
+ struct svc_sock *svsk = rqstp->rq_sock;
+ struct svc_serv *serv = svsk->sk_server;
+ struct sk_buff *skb;
+ u32 *data;
+ int err, len;
+
+ if (test_and_clear_bit(SK_CHNGBUF, &svsk->sk_flags))
+ /* udp sockets need large rcvbuf as all pending
+ * requests are still in that buffer. sndbuf must
+ * also be large enough that there is enough space
+ * for one reply per thread.
+ */
+ svc_sock_setbufsize(svsk->sk_sock,
+ (serv->sv_nrthreads+3)* serv->sv_bufsz,
+ (serv->sv_nrthreads+3)* serv->sv_bufsz);
+
+ clear_bit(SK_DATA, &svsk->sk_flags);
+ while ((skb = skb_recv_datagram(svsk->sk_sk, 0, 1, &err)) == NULL) {
+ svc_sock_received(svsk);
+ if (err == -EAGAIN)
+ return err;
+ /* possibly an icmp error */
+ dprintk("svc: recvfrom returned error %d\n", -err);
+ }
+ set_bit(SK_DATA, &svsk->sk_flags); /* there may be more data... */
+
+ /* Sorry. */
+ if (skb_is_nonlinear(skb)) {
+ if (skb_linearize(skb, GFP_KERNEL) != 0) {
+ kfree_skb(skb);
+ svc_sock_received(svsk);
+ return 0;
+ }
+ }
+
+ if (skb->ip_summed != CHECKSUM_UNNECESSARY) {
+ if ((unsigned short)csum_fold(skb_checksum(skb, 0, skb->len, skb->csum))) {
+ skb_free_datagram(svsk->sk_sk, skb);
+ svc_sock_received(svsk);
+ return 0;
+ }
+ }
+
+
+ len = skb->len - sizeof(struct udphdr);
+ data = (u32 *) (skb->data + sizeof(struct udphdr));
+
+ rqstp->rq_skbuff = skb;
+ rqstp->rq_argbuf.base = data;
+ rqstp->rq_argbuf.buf = data;
+ rqstp->rq_argbuf.len = (len >> 2);
+ /* rqstp->rq_resbuf = rqstp->rq_defbuf; */
+ rqstp->rq_prot = IPPROTO_UDP;
+
+ /* Get sender address */
+ rqstp->rq_addr.sin_family = AF_INET;
+ rqstp->rq_addr.sin_port = skb->h.uh->source;
+ rqstp->rq_addr.sin_addr.s_addr = skb->nh.iph->saddr;
+ rqstp->rq_daddr = skb->nh.iph->daddr;
+
+ if (serv->sv_stats)
+ serv->sv_stats->netudpcnt++;
+
+ /* One down, maybe more to go... */
+ svsk->sk_sk->stamp = skb->stamp;
+ svc_sock_received(svsk);
+
+ return len;
+}
+
+static int
+svc_udp_sendto(struct svc_rqst *rqstp)
+{
+ struct svc_buf *bufp = &rqstp->rq_resbuf;
+ int error;
+
+ /* Set up the first element of the reply iovec.
+ * Any other iovecs that may be in use have been taken
+ * care of by the server implementation itself.
+ */
+ /* bufp->base = bufp->area; */
+ bufp->iov[0].iov_base = bufp->base;
+ bufp->iov[0].iov_len = bufp->len << 2;
+
+ error = svc_sendto(rqstp, bufp->iov, bufp->nriov);
+ if (error == -ECONNREFUSED)
+ /* ICMP error on earlier request. */
+ error = svc_sendto(rqstp, bufp->iov, bufp->nriov);
+
+ return error;
+}
+
+static int
+svc_udp_init(struct svc_sock *svsk)
+{
+ svsk->sk_sk->data_ready = svc_udp_data_ready;
+ svsk->sk_sk->write_space = svc_write_space;
+ svsk->sk_recvfrom = svc_udp_recvfrom;
+ svsk->sk_sendto = svc_udp_sendto;
+
+ /* initialise setting must have enough space to
+ * receive and respond to one request.
+ * svc_udp_recvfrom will re-adjust if necessary
+ */
+ svc_sock_setbufsize(svsk->sk_sock,
+ 3 * svsk->sk_server->sv_bufsz,
+ 3 * svsk->sk_server->sv_bufsz);
+
+ set_bit(SK_CHNGBUF, &svsk->sk_flags);
+
+ return 0;
+}
+
+/*
+ * A data_ready event on a listening socket means there's a connection
+ * pending. Do not use state_change as a substitute for it.
+ */
+static void
+svc_tcp_listen_data_ready(struct sock *sk, int count_unused)
+{
+ struct svc_sock *svsk;
+
+ dprintk("svc: socket %p TCP (listen) state change %d\n",
+ sk, sk->state);
+
+ if (sk->state != TCP_LISTEN) {
+ /*
+ * This callback may called twice when a new connection
+ * is established as a child socket inherits everything
+ * from a parent LISTEN socket.
+ * 1) data_ready method of the parent socket will be called
+ * when one of child sockets become ESTABLISHED.
+ * 2) data_ready method of the child socket may be called
+ * when it receives data before the socket is accepted.
+ * In case of 2, we should ignore it silently.
+ */
+ goto out;
+ }
+ if (!(svsk = (struct svc_sock *) sk->user_data)) {
+ printk("svc: socket %p: no user data\n", sk);
+ goto out;
+ }
+ set_bit(SK_CONN, &svsk->sk_flags);
+ svc_sock_enqueue(svsk);
+ out:
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible_all(sk->sleep);
+}
+
+/*
+ * A state change on a connected socket means it's dying or dead.
+ */
+static void
+svc_tcp_state_change(struct sock *sk)
+{
+ struct svc_sock *svsk;
+
+ dprintk("svc: socket %p TCP (connected) state change %d (svsk %p)\n",
+ sk, sk->state, sk->user_data);
+
+ if (!(svsk = (struct svc_sock *) sk->user_data)) {
+ printk("svc: socket %p: no user data\n", sk);
+ goto out;
+ }
+ set_bit(SK_CLOSE, &svsk->sk_flags);
+ svc_sock_enqueue(svsk);
+ out:
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible_all(sk->sleep);
+}
+
+static void
+svc_tcp_data_ready(struct sock *sk, int count)
+{
+ struct svc_sock * svsk;
+
+ dprintk("svc: socket %p TCP data ready (svsk %p)\n",
+ sk, sk->user_data);
+ if (!(svsk = (struct svc_sock *)(sk->user_data)))
+ goto out;
+ set_bit(SK_DATA, &svsk->sk_flags);
+ svc_sock_enqueue(svsk);
+ out:
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible(sk->sleep);
+}
+
+/*
+ * Accept a TCP connection
+ */
+static void
+svc_tcp_accept(struct svc_sock *svsk)
+{
+ struct sockaddr_in sin;
+ struct svc_serv *serv = svsk->sk_server;
+ struct socket *sock = svsk->sk_sock;
+ struct socket *newsock;
+ struct proto_ops *ops;
+ struct svc_sock *newsvsk;
+ int err, slen;
+
+ dprintk("svc: tcp_accept %p sock %p\n", svsk, sock);
+ if (!sock)
+ return;
+
+ if (!(newsock = sock_alloc())) {
+ printk(KERN_WARNING "%s: no more sockets!\n", serv->sv_name);
+ return;
+ }
+ dprintk("svc: tcp_accept %p allocated\n", newsock);
+
+ newsock->type = sock->type;
+ newsock->ops = ops = sock->ops;
+
+ clear_bit(SK_CONN, &svsk->sk_flags);
+ if ((err = ops->accept(sock, newsock, O_NONBLOCK)) < 0) {
+ if (err != -EAGAIN && net_ratelimit())
+ printk(KERN_WARNING "%s: accept failed (err %d)!\n",
+ serv->sv_name, -err);
+ goto failed; /* aborted connection or whatever */
+ }
+ set_bit(SK_CONN, &svsk->sk_flags);
+ svc_sock_enqueue(svsk);
+
+ slen = sizeof(sin);
+ err = ops->getname(newsock, (struct sockaddr *) &sin, &slen, 1);
+ if (err < 0) {
+ if (net_ratelimit())
+ printk(KERN_WARNING "%s: peername failed (err %d)!\n",
+ serv->sv_name, -err);
+ goto failed; /* aborted connection or whatever */
+ }
+
+ /* Ideally, we would want to reject connections from unauthorized
+ * hosts here, but when we get encription, the IP of the host won't
+ * tell us anything. For now just warn about unpriv connections.
+ */
+ if (ntohs(sin.sin_port) >= 1024) {
+ dprintk(KERN_WARNING
+ "%s: connect from unprivileged port: %u.%u.%u.%u:%d\n",
+ serv->sv_name,
+ NIPQUAD(sin.sin_addr.s_addr), ntohs(sin.sin_port));
+ }
+
+ dprintk("%s: connect from %u.%u.%u.%u:%04x\n", serv->sv_name,
+ NIPQUAD(sin.sin_addr.s_addr), ntohs(sin.sin_port));
+
+ if (!(newsvsk = svc_setup_socket(serv, newsock, &err, 0)))
+ goto failed;
+
+ /* make sure that a write doesn't block forever when
+ * low on memory
+ */
+ newsock->sk->sndtimeo = HZ*30;
+
+ /* Precharge. Data may have arrived on the socket before we
+ * installed the data_ready callback.
+ */
+ set_bit(SK_DATA, &newsvsk->sk_flags);
+ svc_sock_enqueue(newsvsk);
+
+ /* make sure that we don't have too many active connections.
+ * If we have, something must be dropped.
+ * We randomly choose between newest and oldest (in terms
+ * of recent activity) and drop it.
+ */
+ if (serv->sv_tmpcnt > (serv->sv_nrthreads+3)*10) {
+ struct svc_sock *svsk = NULL;
+ spin_lock_bh(&serv->sv_lock);
+ if (!list_empty(&serv->sv_tempsocks)) {
+ if (net_random()&1)
+ svsk = list_entry(serv->sv_tempsocks.prev,
+ struct svc_sock,
+ sk_list);
+ else
+ svsk = list_entry(serv->sv_tempsocks.next,
+ struct svc_sock,
+ sk_list);
+ set_bit(SK_CLOSE, &svsk->sk_flags);
+ svsk->sk_inuse ++;
+ }
+ spin_unlock_bh(&serv->sv_lock);
+
+ if (svsk) {
+ svc_sock_enqueue(svsk);
+ svc_sock_put(svsk);
+ }
+
+ }
+
+ if (serv->sv_stats)
+ serv->sv_stats->nettcpconn++;
+
+ return;
+
+failed:
+ sock_release(newsock);
+ return;
+}
+
+/*
+ * Receive data from a TCP socket.
+ */
+static int
+svc_tcp_recvfrom(struct svc_rqst *rqstp)
+{
+ struct svc_sock *svsk = rqstp->rq_sock;
+ struct svc_serv *serv = svsk->sk_server;
+ struct svc_buf *bufp = &rqstp->rq_argbuf;
+ int len;
+
+ dprintk("svc: tcp_recv %p data %d conn %d close %d\n",
+ svsk, test_bit(SK_DATA, &svsk->sk_flags),
+ test_bit(SK_CONN, &svsk->sk_flags),
+ test_bit(SK_CLOSE, &svsk->sk_flags));
+
+ if (test_bit(SK_CLOSE, &svsk->sk_flags)) {
+ svc_delete_socket(svsk);
+ return 0;
+ }
+
+ if (test_bit(SK_CONN, &svsk->sk_flags)) {
+ svc_tcp_accept(svsk);
+ svc_sock_received(svsk);
+ return 0;
+ }
+
+ if (test_and_clear_bit(SK_CHNGBUF, &svsk->sk_flags))
+ /* sndbuf needs to have room for one request
+ * per thread, otherwise we can stall even when the
+ * network isn't a bottleneck.
+ * rcvbuf just needs to be able to hold a few requests.
+ * Normally they will be removed from the queue
+ * as soon as a complete request arrives.
+ */
+ svc_sock_setbufsize(svsk->sk_sock,
+ (serv->sv_nrthreads+3) *
+ serv->sv_bufsz,
+ 3 * serv->sv_bufsz);
+
+ clear_bit(SK_DATA, &svsk->sk_flags);
+
+ /* Receive data. If we haven't got the record length yet, get
+ * the next four bytes. Otherwise try to gobble up as much as
+ * possible up to the complete record length.
+ */
+ if (svsk->sk_tcplen < 4) {
+ unsigned long want = 4 - svsk->sk_tcplen;
+ struct iovec iov;
+
+ iov.iov_base = ((char *) &svsk->sk_reclen) + svsk->sk_tcplen;
+ iov.iov_len = want;
+ if ((len = svc_recvfrom(rqstp, &iov, 1, want)) < 0)
+ goto error;
+ svsk->sk_tcplen += len;
+ if (len < want) {
+ dprintk("svc: short recvfrom while reading record length (%d of %ld)\n",
+ len, want);
+ svc_sock_received(svsk);
+ return -EAGAIN; /* record header not complete */
+ }
+
+ svsk->sk_reclen = ntohl(svsk->sk_reclen);
+ if (!(svsk->sk_reclen & 0x80000000)) {
+ /* FIXME: technically, a record can be fragmented,
+ * and non-terminal fragments will not have the top
+ * bit set in the fragment length header.
+ * But apparently no known nfs clients send fragmented
+ * records. */
+ printk(KERN_NOTICE "RPC: bad TCP reclen 0x%08lx (non-terminal)\n",
+ (unsigned long) svsk->sk_reclen);
+ goto err_delete;
+ }
+ svsk->sk_reclen &= 0x7fffffff;
+ dprintk("svc: TCP record, %d bytes\n", svsk->sk_reclen);
+ if (svsk->sk_reclen > (bufp->buflen<<2)) {
+ printk(KERN_NOTICE "RPC: bad TCP reclen 0x%08lx (large)\n",
+ (unsigned long) svsk->sk_reclen);
+ goto err_delete;
+ }
+ }
+
+ /* Check whether enough data is available */
+ len = svc_recv_available(svsk);
+ if (len < 0)
+ goto error;
+
+ if (len < svsk->sk_reclen) {
+ dprintk("svc: incomplete TCP record (%d of %d)\n",
+ len, svsk->sk_reclen);
+ svc_sock_received(svsk);
+ return -EAGAIN; /* record not complete */
+ }
+ set_bit(SK_DATA, &svsk->sk_flags);
+
+ /* Frob argbuf */
+ bufp->iov[0].iov_base += 4;
+ bufp->iov[0].iov_len -= 4;
+
+ /* Now receive data */
+ len = svc_recvfrom(rqstp, bufp->iov, bufp->nriov, svsk->sk_reclen);
+ if (len < 0)
+ goto error;
+
+ dprintk("svc: TCP complete record (%d bytes)\n", len);
+
+ /* Position reply write pointer immediately after
+ * record length */
+ rqstp->rq_resbuf.buf += 1;
+ rqstp->rq_resbuf.len = 1;
+
+ rqstp->rq_skbuff = 0;
+ rqstp->rq_argbuf.buf += 1;
+ rqstp->rq_argbuf.len = (len >> 2);
+ rqstp->rq_prot = IPPROTO_TCP;
+
+ /* Reset TCP read info */
+ svsk->sk_reclen = 0;
+ svsk->sk_tcplen = 0;
+
+ svc_sock_received(svsk);
+ if (serv->sv_stats)
+ serv->sv_stats->nettcpcnt++;
+
+ return len;
+
+ err_delete:
+ svc_delete_socket(svsk);
+ return -EAGAIN;
+
+ error:
+ if (len == -EAGAIN) {
+ dprintk("RPC: TCP recvfrom got EAGAIN\n");
+ svc_sock_received(svsk);
+ } else {
+ printk(KERN_NOTICE "%s: recvfrom returned errno %d\n",
+ svsk->sk_server->sv_name, -len);
+ svc_sock_received(svsk);
+ }
+
+ return len;
+}
+
+/*
+ * Send out data on TCP socket.
+ */
+static int
+svc_tcp_sendto(struct svc_rqst *rqstp)
+{
+ struct svc_buf *bufp = &rqstp->rq_resbuf;
+ int sent;
+
+ /* Set up the first element of the reply iovec.
+ * Any other iovecs that may be in use have been taken
+ * care of by the server implementation itself.
+ */
+ bufp->iov[0].iov_base = bufp->base;
+ bufp->iov[0].iov_len = bufp->len << 2;
+ bufp->base[0] = htonl(0x80000000|((bufp->len << 2) - 4));
+
+ if (test_bit(SK_DEAD, &rqstp->rq_sock->sk_flags))
+ return -ENOTCONN;
+
+ sent = svc_sendto(rqstp, bufp->iov, bufp->nriov);
+ if (sent != bufp->len<<2) {
+ printk(KERN_NOTICE "rpc-srv/tcp: %s: sent only %d bytes of %d - shutting down socket\n",
+ rqstp->rq_sock->sk_server->sv_name,
+ sent, bufp->len << 2);
+ svc_delete_socket(rqstp->rq_sock);
+ sent = -EAGAIN;
+ }
+ return sent;
+}
+
+static int
+svc_tcp_init(struct svc_sock *svsk)
+{
+ struct sock *sk = svsk->sk_sk;
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+
+ svsk->sk_recvfrom = svc_tcp_recvfrom;
+ svsk->sk_sendto = svc_tcp_sendto;
+
+ if (sk->state == TCP_LISTEN) {
+ dprintk("setting up TCP socket for listening\n");
+ sk->data_ready = svc_tcp_listen_data_ready;
+ } else {
+ dprintk("setting up TCP socket for reading\n");
+ sk->state_change = svc_tcp_state_change;
+ sk->data_ready = svc_tcp_data_ready;
+ sk->write_space = svc_write_space;
+
+ svsk->sk_reclen = 0;
+ svsk->sk_tcplen = 0;
+
+ tp->nonagle = 1; /* disable Nagle's algorithm */
+
+ /* initialise setting must have enough space to
+ * receive and respond to one request.
+ * svc_tcp_recvfrom will re-adjust if necessary
+ */
+ svc_sock_setbufsize(svsk->sk_sock,
+ 3 * svsk->sk_server->sv_bufsz,
+ 3 * svsk->sk_server->sv_bufsz);
+
+ set_bit(SK_CHNGBUF, &svsk->sk_flags);
+ if (sk->state != TCP_ESTABLISHED)
+ set_bit(SK_CLOSE, &svsk->sk_flags);
+ }
+
+ return 0;
+}
+
+void
+svc_sock_update_bufs(struct svc_serv *serv)
+{
+ /*
+ * The number of server threads has changed.
+ * flag all socket to the snd/rcv buffer sizes
+ * updated.
+ * We don't just do it, as the locking is rather
+ * awkward at this point
+ */
+ struct list_head *le;
+
+ spin_lock_bh(&serv->sv_lock);
+ list_for_each(le, &serv->sv_permsocks) {
+ struct svc_sock *svsk =
+ list_entry(le, struct svc_sock, sk_list);
+ set_bit(SK_CHNGBUF, &svsk->sk_flags);
+ }
+ list_for_each(le, &serv->sv_tempsocks) {
+ struct svc_sock *svsk =
+ list_entry(le, struct svc_sock, sk_list);
+ set_bit(SK_CHNGBUF, &svsk->sk_flags);
+ }
+ spin_unlock_bh(&serv->sv_lock);
+}
+
+/*
+ * Receive the next request on any socket.
+ */
+int
+svc_recv(struct svc_serv *serv, struct svc_rqst *rqstp, long timeout)
+{
+ struct svc_sock *svsk =NULL;
+ int len;
+ DECLARE_WAITQUEUE(wait, current);
+
+ dprintk("svc: server %p waiting for data (to = %ld)\n",
+ rqstp, timeout);
+
+ if (rqstp->rq_sock)
+ printk(KERN_ERR
+ "svc_recv: service %p, socket not NULL!\n",
+ rqstp);
+ if (waitqueue_active(&rqstp->rq_wait))
+ printk(KERN_ERR
+ "svc_recv: service %p, wait queue active!\n",
+ rqstp);
+
+ /* Initialize the buffers */
+ rqstp->rq_argbuf = rqstp->rq_defbuf;
+ rqstp->rq_resbuf = rqstp->rq_defbuf;
+
+ if (signalled())
+ return -EINTR;
+
+ spin_lock_bh(&serv->sv_lock);
+ if (!list_empty(&serv->sv_tempsocks)) {
+ svsk = list_entry(serv->sv_tempsocks.next,
+ struct svc_sock, sk_list);
+ /* apparently the "standard" is that clients close
+ * idle connections after 5 minutes, servers after
+ * 6 minutes
+ * http://www.connectathon.org/talks96/nfstcp.pdf
+ */
+ if (CURRENT_TIME - svsk->sk_lastrecv < 6*60
+ || test_bit(SK_BUSY, &svsk->sk_flags))
+ svsk = NULL;
+ }
+ if (svsk) {
+ set_bit(SK_BUSY, &svsk->sk_flags);
+ set_bit(SK_CLOSE, &svsk->sk_flags);
+ rqstp->rq_sock = svsk;
+ svsk->sk_inuse++;
+ } else if ((svsk = svc_sock_dequeue(serv)) != NULL) {
+ rqstp->rq_sock = svsk;
+ svsk->sk_inuse++;
+ rqstp->rq_reserved = serv->sv_bufsz;
+ svsk->sk_reserved += rqstp->rq_reserved;
+ } else {
+ /* No data pending. Go to sleep */
+ svc_serv_enqueue(serv, rqstp);
+
+ /*
+ * We have to be able to interrupt this wait
+ * to bring down the daemons ...
+ */
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&rqstp->rq_wait, &wait);
+ spin_unlock_bh(&serv->sv_lock);
+
+ schedule_timeout(timeout);
+
+ spin_lock_bh(&serv->sv_lock);
+ remove_wait_queue(&rqstp->rq_wait, &wait);
+
+ if (!(svsk = rqstp->rq_sock)) {
+ svc_serv_dequeue(serv, rqstp);
+ spin_unlock_bh(&serv->sv_lock);
+ dprintk("svc: server %p, no data yet\n", rqstp);
+ return signalled()? -EINTR : -EAGAIN;
+ }
+ }
+ spin_unlock_bh(&serv->sv_lock);
+
+ dprintk("svc: server %p, socket %p, inuse=%d\n",
+ rqstp, svsk, svsk->sk_inuse);
+ len = svsk->sk_recvfrom(rqstp);
+ dprintk("svc: got len=%d\n", len);
+
+ /* No data, incomplete (TCP) read, or accept() */
+ if (len == 0 || len == -EAGAIN) {
+ svc_sock_release(rqstp);
+ return -EAGAIN;
+ }
+ svsk->sk_lastrecv = CURRENT_TIME;
+ if (test_bit(SK_TEMP, &svsk->sk_flags)) {
+ /* push active sockets to end of list */
+ spin_lock_bh(&serv->sv_lock);
+ list_del(&svsk->sk_list);
+ list_add_tail(&svsk->sk_list, &serv->sv_tempsocks);
+ spin_unlock_bh(&serv->sv_lock);
+ }
+
+ rqstp->rq_secure = ntohs(rqstp->rq_addr.sin_port) < 1024;
+ rqstp->rq_userset = 0;
+ rqstp->rq_verfed = 0;
+
+ svc_getlong(&rqstp->rq_argbuf, rqstp->rq_xid);
+ svc_putlong(&rqstp->rq_resbuf, rqstp->rq_xid);
+
+ /* Assume that the reply consists of a single buffer. */
+ rqstp->rq_resbuf.nriov = 1;
+
+ if (serv->sv_stats)
+ serv->sv_stats->netcnt++;
+ return len;
+}
+
+/*
+ * Drop request
+ */
+void
+svc_drop(struct svc_rqst *rqstp)
+{
+ dprintk("svc: socket %p dropped request\n", rqstp->rq_sock);
+ svc_sock_release(rqstp);
+}
+
+/*
+ * Return reply to client.
+ */
+int
+svc_send(struct svc_rqst *rqstp)
+{
+ struct svc_sock *svsk;
+ int len;
+
+ if ((svsk = rqstp->rq_sock) == NULL) {
+ printk(KERN_WARNING "NULL socket pointer in %s:%d\n",
+ __FILE__, __LINE__);
+ return -EFAULT;
+ }
+
+ /* release the receive skb before sending the reply */
+ svc_release_skb(rqstp);
+
+ len = svsk->sk_sendto(rqstp);
+ svc_sock_release(rqstp);
+
+ if (len == -ECONNREFUSED || len == -ENOTCONN || len == -EAGAIN)
+ return 0;
+ return len;
+}
+
+/*
+ * Initialize socket for RPC use and create svc_sock struct
+ * XXX: May want to setsockopt SO_SNDBUF and SO_RCVBUF.
+ */
+static struct svc_sock *
+svc_setup_socket(struct svc_serv *serv, struct socket *sock,
+ int *errp, int pmap_register)
+{
+ struct svc_sock *svsk;
+ struct sock *inet;
+
+ dprintk("svc: svc_setup_socket %p\n", sock);
+ if (!(svsk = kmalloc(sizeof(*svsk), GFP_KERNEL))) {
+ *errp = -ENOMEM;
+ return NULL;
+ }
+ memset(svsk, 0, sizeof(*svsk));
+
+ inet = sock->sk;
+ inet->user_data = svsk;
+ svsk->sk_sock = sock;
+ svsk->sk_sk = inet;
+ svsk->sk_ostate = inet->state_change;
+ svsk->sk_odata = inet->data_ready;
+ svsk->sk_owspace = inet->write_space;
+ svsk->sk_server = serv;
+ svsk->sk_lastrecv = CURRENT_TIME;
+
+ /* Initialize the socket */
+ if (sock->type == SOCK_DGRAM)
+ *errp = svc_udp_init(svsk);
+ else
+ *errp = svc_tcp_init(svsk);
+if (svsk->sk_sk == NULL)
+ printk(KERN_WARNING "svsk->sk_sk == NULL after svc_prot_init!\n");
+
+ /* Register socket with portmapper */
+ if (*errp >= 0 && pmap_register)
+ *errp = svc_register(serv, inet->protocol, ntohs(inet->sport));
+
+ if (*errp < 0) {
+ inet->user_data = NULL;
+ kfree(svsk);
+ return NULL;
+ }
+
+
+ spin_lock_bh(&serv->sv_lock);
+ if (!pmap_register) {
+ set_bit(SK_TEMP, &svsk->sk_flags);
+ list_add(&svsk->sk_list, &serv->sv_tempsocks);
+ serv->sv_tmpcnt++;
+ } else {
+ clear_bit(SK_TEMP, &svsk->sk_flags);
+ list_add(&svsk->sk_list, &serv->sv_permsocks);
+ }
+ spin_unlock_bh(&serv->sv_lock);
+
+ dprintk("svc: svc_setup_socket created %p (inet %p)\n",
+ svsk, svsk->sk_sk);
+ return svsk;
+}
+
+/*
+ * Create socket for RPC service.
+ */
+static int
+svc_create_socket(struct svc_serv *serv, int protocol, struct sockaddr_in *sin)
+{
+ struct svc_sock *svsk;
+ struct socket *sock;
+ int error;
+ int type;
+
+ dprintk("svc: svc_create_socket(%s, %d, %u.%u.%u.%u:%d)\n",
+ serv->sv_program->pg_name, protocol,
+ NIPQUAD(sin->sin_addr.s_addr),
+ ntohs(sin->sin_port));
+
+ if (protocol != IPPROTO_UDP && protocol != IPPROTO_TCP) {
+ printk(KERN_WARNING "svc: only UDP and TCP "
+ "sockets supported\n");
+ return -EINVAL;
+ }
+ type = (protocol == IPPROTO_UDP)? SOCK_DGRAM : SOCK_STREAM;
+
+ if ((error = sock_create(PF_INET, type, protocol, &sock)) < 0)
+ return error;
+
+ if (sin != NULL) {
+ if (type == SOCK_STREAM)
+ sock->sk->reuse = 1; /* allow address reuse */
+ error = sock->ops->bind(sock, (struct sockaddr *) sin,
+ sizeof(*sin));
+ if (error < 0)
+ goto bummer;
+ }
+
+ if (protocol == IPPROTO_TCP) {
+ if ((error = sock->ops->listen(sock, 64)) < 0)
+ goto bummer;
+ }
+
+ if ((svsk = svc_setup_socket(serv, sock, &error, 1)) != NULL)
+ return 0;
+
+bummer:
+ dprintk("svc: svc_create_socket error = %d\n", -error);
+ sock_release(sock);
+ return error;
+}
+
+/*
+ * Remove a dead socket
+ */
+void
+svc_delete_socket(struct svc_sock *svsk)
+{
+ struct svc_serv *serv;
+ struct sock *sk;
+
+ dprintk("svc: svc_delete_socket(%p)\n", svsk);
+
+ if (test_and_set_bit(SK_DEAD, &svsk->sk_flags))
+ return ;
+
+ serv = svsk->sk_server;
+ sk = svsk->sk_sk;
+
+ sk->state_change = svsk->sk_ostate;
+ sk->data_ready = svsk->sk_odata;
+ sk->write_space = svsk->sk_owspace;
+
+ spin_lock_bh(&serv->sv_lock);
+
+ list_del(&svsk->sk_list);
+ if (test_bit(SK_TEMP, &svsk->sk_flags))
+ serv->sv_tmpcnt--;
+ if (test_bit(SK_QUED, &svsk->sk_flags))
+ list_del(&svsk->sk_ready);
+
+
+ if (!svsk->sk_inuse) {
+ spin_unlock_bh(&serv->sv_lock);
+ sock_release(svsk->sk_sock);
+ kfree(svsk);
+ } else {
+ spin_unlock_bh(&serv->sv_lock);
+ dprintk(KERN_NOTICE "svc: server socket destroy delayed\n");
+ /* svsk->sk_server = NULL; */
+ }
+}
+
+/*
+ * Make a socket for nfsd and lockd
+ */
+int
+svc_makesock(struct svc_serv *serv, int protocol, unsigned short port)
+{
+ struct sockaddr_in sin;
+
+ dprintk("svc: creating socket proto = %d\n", protocol);
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = INADDR_ANY;
+ sin.sin_port = htons(port);
+ return svc_create_socket(serv, protocol, &sin);
+}
+
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/sysctl.c b/uClinux-2.4.31-uc0/net/sunrpc/sysctl.c
new file mode 100644
index 0000000..9c34e87
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/sysctl.c
@@ -0,0 +1,137 @@
+/*
+ * linux/net/sunrpc/sysctl.c
+ *
+ * Sysctl interface to sunrpc module. This is for debugging only now.
+ *
+ * I would prefer to register the sunrpc table below sys/net, but that's
+ * impossible at the moment.
+ */
+
+#include <linux/config.h>
+#include <linux/version.h>
+#include <linux/types.h>
+#include <linux/linkage.h>
+#include <linux/ctype.h>
+#include <linux/fs.h>
+#include <linux/sysctl.h>
+#include <linux/module.h>
+
+#include <asm/uaccess.h>
+#include <linux/sunrpc/types.h>
+#include <linux/sunrpc/sched.h>
+#include <linux/sunrpc/stats.h>
+
+/*
+ * Declare the debug flags here
+ */
+unsigned int rpc_debug;
+unsigned int nfs_debug;
+unsigned int nfsd_debug;
+unsigned int nlm_debug;
+
+#ifdef RPC_DEBUG
+
+static struct ctl_table_header *sunrpc_table_header;
+static ctl_table sunrpc_table[];
+
+void
+rpc_register_sysctl(void)
+{
+ if (!sunrpc_table_header) {
+ sunrpc_table_header = register_sysctl_table(sunrpc_table, 1);
+#ifdef CONFIG_PROC_FS
+ if (sunrpc_table[0].de)
+ sunrpc_table[0].de->owner = THIS_MODULE;
+#endif
+ }
+
+}
+
+void
+rpc_unregister_sysctl(void)
+{
+ if (sunrpc_table_header) {
+ unregister_sysctl_table(sunrpc_table_header);
+ sunrpc_table_header = NULL;
+ }
+}
+
+static int
+proc_dodebug(ctl_table *table, int write, struct file *file,
+ void *buffer, size_t *lenp)
+{
+ char tmpbuf[20], *p, c;
+ unsigned int value;
+ size_t left, len;
+
+ if ((file->f_pos && !write) || !*lenp) {
+ *lenp = 0;
+ return 0;
+ }
+
+ left = *lenp;
+
+ if (write) {
+ if (!access_ok(VERIFY_READ, buffer, left))
+ return -EFAULT;
+ p = (char *) buffer;
+ while (left && __get_user(c, p) >= 0 && isspace(c))
+ left--, p++;
+ if (!left)
+ goto done;
+
+ if (left > sizeof(tmpbuf) - 1)
+ return -EINVAL;
+ copy_from_user(tmpbuf, p, left);
+ tmpbuf[left] = '\0';
+
+ for (p = tmpbuf, value = 0; '0' <= *p && *p <= '9'; p++, left--)
+ value = 10 * value + (*p - '0');
+ if (*p && !isspace(*p))
+ return -EINVAL;
+ while (left && isspace(*p))
+ left--, p++;
+ *(unsigned int *) table->data = value;
+ /* Display the RPC tasks on writing to rpc_debug */
+ if (table->ctl_name == CTL_RPCDEBUG) {
+ rpc_show_tasks();
+ }
+ } else {
+ if (!access_ok(VERIFY_WRITE, buffer, left))
+ return -EFAULT;
+ len = sprintf(tmpbuf, "%d", *(unsigned int *) table->data);
+ if (len > left)
+ len = left;
+ copy_to_user(buffer, tmpbuf, len);
+ if ((left -= len) > 0) {
+ put_user('\n', (char *)buffer + len);
+ left--;
+ }
+ }
+
+done:
+ *lenp -= left;
+ file->f_pos += *lenp;
+ return 0;
+}
+
+#define DIRENTRY(nam1, nam2, child) \
+ {CTL_##nam1, #nam2, NULL, 0, 0555, child }
+#define DBGENTRY(nam1, nam2) \
+ {CTL_##nam1##DEBUG, #nam2 "_debug", &nam2##_debug, sizeof(int),\
+ 0644, NULL, &proc_dodebug}
+
+static ctl_table debug_table[] = {
+ DBGENTRY(RPC, rpc),
+ DBGENTRY(NFS, nfs),
+ DBGENTRY(NFSD, nfsd),
+ DBGENTRY(NLM, nlm),
+ {0}
+};
+
+static ctl_table sunrpc_table[] = {
+ DIRENTRY(SUNRPC, sunrpc, debug_table),
+ {0}
+};
+
+#endif
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/timer.c b/uClinux-2.4.31-uc0/net/sunrpc/timer.c
new file mode 100644
index 0000000..7383873
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/timer.c
@@ -0,0 +1,74 @@
+#include <linux/version.h>
+#include <linux/types.h>
+#include <linux/unistd.h>
+
+#include <linux/sunrpc/clnt.h>
+#include <linux/sunrpc/xprt.h>
+#include <linux/sunrpc/timer.h>
+
+#define RPC_RTO_MAX (60*HZ)
+#define RPC_RTO_INIT (HZ/5)
+#define RPC_RTO_MIN (HZ/10)
+
+void
+rpc_init_rtt(struct rpc_rtt *rt, long timeo)
+{
+ long t = (timeo - RPC_RTO_INIT) << 3;
+ int i;
+ rt->timeo = timeo;
+ if (t < 0)
+ t = 0;
+ for (i = 0; i < 5; i++) {
+ rt->srtt[i] = t;
+ rt->sdrtt[i] = RPC_RTO_INIT;
+ }
+ memset(rt->ntimeouts, 0, sizeof(rt->ntimeouts));
+}
+
+void
+rpc_update_rtt(struct rpc_rtt *rt, int timer, long m)
+{
+ long *srtt, *sdrtt;
+
+ if (timer-- == 0)
+ return;
+
+ if (m == 0)
+ m = 1;
+ srtt = &rt->srtt[timer];
+ m -= *srtt >> 3;
+ *srtt += m;
+ if (m < 0)
+ m = -m;
+ sdrtt = &rt->sdrtt[timer];
+ m -= *sdrtt >> 2;
+ *sdrtt += m;
+ /* Set lower bound on the variance */
+ if (*sdrtt < RPC_RTO_MIN)
+ *sdrtt = RPC_RTO_MIN;
+}
+
+/*
+ * Estimate rto for an nfs rpc sent via. an unreliable datagram.
+ * Use the mean and mean deviation of rtt for the appropriate type of rpc
+ * for the frequent rpcs and a default for the others.
+ * The justification for doing "other" this way is that these rpcs
+ * happen so infrequently that timer est. would probably be stale.
+ * Also, since many of these rpcs are
+ * non-idempotent, a conservative timeout is desired.
+ * getattr, lookup,
+ * read, write, commit - A+4D
+ * other - timeo
+ */
+
+long
+rpc_calc_rto(struct rpc_rtt *rt, int timer)
+{
+ long res;
+ if (timer-- == 0)
+ return rt->timeo;
+ res = (rt->srtt[timer] >> 3) + rt->sdrtt[timer];
+ if (res > RPC_RTO_MAX)
+ res = RPC_RTO_MAX;
+ return res;
+}
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/xdr.c b/uClinux-2.4.31-uc0/net/sunrpc/xdr.c
new file mode 100644
index 0000000..d4236d2
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/xdr.c
@@ -0,0 +1,554 @@
+/*
+ * linux/net/sunrpc/xdr.c
+ *
+ * Generic XDR support.
+ *
+ * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de>
+ */
+
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/pagemap.h>
+#include <linux/errno.h>
+#include <linux/in.h>
+#include <linux/sunrpc/xdr.h>
+#include <linux/sunrpc/msg_prot.h>
+
+/*
+ * XDR functions for basic NFS types
+ */
+u32 *
+xdr_encode_netobj(u32 *p, const struct xdr_netobj *obj)
+{
+ unsigned int quadlen = XDR_QUADLEN(obj->len);
+
+ p[quadlen] = 0; /* zero trailing bytes */
+ *p++ = htonl(obj->len);
+ memcpy(p, obj->data, obj->len);
+ return p + XDR_QUADLEN(obj->len);
+}
+
+u32 *
+xdr_decode_netobj_fixed(u32 *p, void *obj, unsigned int len)
+{
+ if (ntohl(*p++) != len)
+ return NULL;
+ memcpy(obj, p, len);
+ return p + XDR_QUADLEN(len);
+}
+
+u32 *
+xdr_decode_netobj(u32 *p, struct xdr_netobj *obj)
+{
+ unsigned int len;
+
+ if ((len = ntohl(*p++)) > XDR_MAX_NETOBJ)
+ return NULL;
+ obj->len = len;
+ obj->data = (u8 *) p;
+ return p + XDR_QUADLEN(len);
+}
+
+u32 *
+xdr_encode_array(u32 *p, const char *array, unsigned int len)
+{
+ int quadlen = XDR_QUADLEN(len);
+
+ p[quadlen] = 0;
+ *p++ = htonl(len);
+ memcpy(p, array, len);
+ return p + quadlen;
+}
+
+u32 *
+xdr_encode_string(u32 *p, const char *string)
+{
+ return xdr_encode_array(p, string, strlen(string));
+}
+
+u32 *
+xdr_decode_string(u32 *p, char **sp, int *lenp, int maxlen)
+{
+ unsigned int len;
+ char *string;
+
+ if ((len = ntohl(*p++)) > maxlen)
+ return NULL;
+ if (lenp)
+ *lenp = len;
+ if ((len % 4) != 0) {
+ string = (char *) p;
+ } else {
+ string = (char *) (p - 1);
+ memmove(string, p, len);
+ }
+ string[len] = '\0';
+ *sp = string;
+ return p + XDR_QUADLEN(len);
+}
+
+u32 *
+xdr_decode_string_inplace(u32 *p, char **sp, int *lenp, int maxlen)
+{
+ unsigned int len;
+
+ if ((len = ntohl(*p++)) > maxlen)
+ return NULL;
+ *lenp = len;
+ *sp = (char *) p;
+ return p + XDR_QUADLEN(len);
+}
+
+
+void
+xdr_encode_pages(struct xdr_buf *xdr, struct page **pages, unsigned int base,
+ unsigned int len)
+{
+ xdr->pages = pages;
+ xdr->page_base = base;
+ xdr->page_len = len;
+
+ if (len & 3) {
+ struct iovec *iov = xdr->tail;
+ unsigned int pad = 4 - (len & 3);
+
+ iov->iov_base = (void *) "\0\0\0";
+ iov->iov_len = pad;
+ len += pad;
+ }
+ xdr->len += len;
+}
+
+void
+xdr_inline_pages(struct xdr_buf *xdr, unsigned int offset,
+ struct page **pages, unsigned int base, unsigned int len)
+{
+ struct iovec *head = xdr->head;
+ struct iovec *tail = xdr->tail;
+ char *buf = (char *)head->iov_base;
+ unsigned int buflen = head->iov_len;
+
+ head->iov_len = offset;
+
+ xdr->pages = pages;
+ xdr->page_base = base;
+ xdr->page_len = len;
+
+ tail->iov_base = buf + offset;
+ tail->iov_len = buflen - offset;
+
+ xdr->len += len;
+}
+
+/*
+ * Realign the iovec if the server missed out some reply elements
+ * (such as post-op attributes,...)
+ * Note: This is a simple implementation that assumes that
+ * len <= iov->iov_len !!!
+ * The RPC header (assumed to be the 1st element in the iov array)
+ * is not shifted.
+ */
+void xdr_shift_iovec(struct iovec *iov, int nr, size_t len)
+{
+ struct iovec *pvec;
+
+ for (pvec = iov + nr - 1; nr > 1; nr--, pvec--) {
+ struct iovec *svec = pvec - 1;
+
+ if (len > pvec->iov_len) {
+ printk(KERN_DEBUG "RPC: Urk! Large shift of short iovec.\n");
+ return;
+ }
+ memmove((char *)pvec->iov_base + len, pvec->iov_base,
+ pvec->iov_len - len);
+
+ if (len > svec->iov_len) {
+ printk(KERN_DEBUG "RPC: Urk! Large shift of short iovec.\n");
+ return;
+ }
+ memcpy(pvec->iov_base,
+ (char *)svec->iov_base + svec->iov_len - len, len);
+ }
+}
+
+/*
+ * Map a struct xdr_buf into an iovec array.
+ */
+int xdr_kmap(struct iovec *iov_base, struct xdr_buf *xdr, unsigned int base)
+{
+ struct iovec *iov = iov_base;
+ struct page **ppage = xdr->pages;
+ struct page **first_kmap = NULL;
+ unsigned int len, pglen = xdr->page_len;
+
+ len = xdr->head[0].iov_len;
+ if (base < len) {
+ iov->iov_len = len - base;
+ iov->iov_base = (char *)xdr->head[0].iov_base + base;
+ iov++;
+ base = 0;
+ } else
+ base -= len;
+
+ if (pglen == 0)
+ goto map_tail;
+ if (base >= pglen) {
+ base -= pglen;
+ goto map_tail;
+ }
+ if (base || xdr->page_base) {
+ pglen -= base;
+ base += xdr->page_base;
+ ppage += base >> PAGE_CACHE_SHIFT;
+ base &= ~PAGE_CACHE_MASK;
+ }
+ do {
+ len = PAGE_CACHE_SIZE;
+ if (!first_kmap) {
+ first_kmap = ppage;
+ iov->iov_base = kmap(*ppage);
+ } else {
+ iov->iov_base = kmap_nonblock(*ppage);
+ if (!iov->iov_base)
+ goto out_err;
+ }
+ if (base) {
+ iov->iov_base += base;
+ len -= base;
+ base = 0;
+ }
+ if (pglen < len)
+ len = pglen;
+ iov->iov_len = len;
+ iov++;
+ ppage++;
+ } while ((pglen -= len) != 0);
+map_tail:
+ if (xdr->tail[0].iov_len) {
+ iov->iov_len = xdr->tail[0].iov_len - base;
+ iov->iov_base = (char *)xdr->tail[0].iov_base + base;
+ iov++;
+ }
+ return (iov - iov_base);
+out_err:
+ for (; first_kmap != ppage; first_kmap++)
+ kunmap(*first_kmap);
+ return 0;
+}
+
+void xdr_kunmap(struct xdr_buf *xdr, unsigned int base, int niov)
+{
+ struct page **ppage = xdr->pages;
+ unsigned int pglen = xdr->page_len;
+
+ if (!pglen)
+ return;
+ if (base >= xdr->head[0].iov_len)
+ base -= xdr->head[0].iov_len;
+ else {
+ niov--;
+ base = 0;
+ }
+
+ if (base >= pglen)
+ return;
+ if (base || xdr->page_base) {
+ pglen -= base;
+ base += xdr->page_base;
+ ppage += base >> PAGE_CACHE_SHIFT;
+ /* Note: The offset means that the length of the first
+ * page is really (PAGE_CACHE_SIZE - (base & ~PAGE_CACHE_MASK)).
+ * In order to avoid an extra test inside the loop,
+ * we bump pglen here, and just subtract PAGE_CACHE_SIZE... */
+ pglen += base & ~PAGE_CACHE_MASK;
+ }
+ /*
+ * In case we could only do a partial xdr_kmap, all remaining iovecs
+ * refer to pages. Otherwise we detect the end through pglen.
+ */
+ for (; niov; niov--) {
+ flush_dcache_page(*ppage);
+ kunmap(*ppage);
+ if (pglen <= PAGE_CACHE_SIZE)
+ break;
+ pglen -= PAGE_CACHE_SIZE;
+ ppage++;
+ }
+}
+
+void
+xdr_partial_copy_from_skb(struct xdr_buf *xdr, unsigned int base,
+ skb_reader_t *desc,
+ skb_read_actor_t copy_actor)
+{
+ struct page **ppage = xdr->pages;
+ unsigned int len, pglen = xdr->page_len;
+ int ret;
+
+ len = xdr->head[0].iov_len;
+ if (base < len) {
+ len -= base;
+ ret = copy_actor(desc, (char *)xdr->head[0].iov_base + base, len);
+ if (ret != len || !desc->count)
+ return;
+ base = 0;
+ } else
+ base -= len;
+
+ if (pglen == 0)
+ goto copy_tail;
+ if (base >= pglen) {
+ base -= pglen;
+ goto copy_tail;
+ }
+ if (base || xdr->page_base) {
+ pglen -= base;
+ base += xdr->page_base;
+ ppage += base >> PAGE_CACHE_SHIFT;
+ base &= ~PAGE_CACHE_MASK;
+ }
+ do {
+ char *kaddr;
+
+ len = PAGE_CACHE_SIZE;
+ kaddr = kmap_atomic(*ppage, KM_SKB_SUNRPC_DATA);
+ if (base) {
+ len -= base;
+ if (pglen < len)
+ len = pglen;
+ ret = copy_actor(desc, kaddr + base, len);
+ base = 0;
+ } else {
+ if (pglen < len)
+ len = pglen;
+ ret = copy_actor(desc, kaddr, len);
+ }
+ kunmap_atomic(kaddr, KM_SKB_SUNRPC_DATA);
+ if (ret != len || !desc->count)
+ return;
+ ppage++;
+ } while ((pglen -= len) != 0);
+copy_tail:
+ len = xdr->tail[0].iov_len;
+ if (len)
+ copy_actor(desc, (char *)xdr->tail[0].iov_base + base, len);
+}
+
+/*
+ * Helper routines for doing 'memmove' like operations on a struct xdr_buf
+ *
+ * _shift_data_right_pages
+ * @pages: vector of pages containing both the source and dest memory area.
+ * @pgto_base: page vector address of destination
+ * @pgfrom_base: page vector address of source
+ * @len: number of bytes to copy
+ *
+ * Note: the addresses pgto_base and pgfrom_base are both calculated in
+ * the same way:
+ * if a memory area starts at byte 'base' in page 'pages[i]',
+ * then its address is given as (i << PAGE_CACHE_SHIFT) + base
+ * Also note: pgfrom_base must be < pgto_base, but the memory areas
+ * they point to may overlap.
+ */
+static void
+_shift_data_right_pages(struct page **pages, size_t pgto_base,
+ size_t pgfrom_base, size_t len)
+{
+ struct page **pgfrom, **pgto;
+ char *vfrom, *vto;
+ size_t copy;
+
+ BUG_ON(pgto_base <= pgfrom_base);
+
+ pgto_base += len;
+ pgfrom_base += len;
+
+ pgto = pages + (pgto_base >> PAGE_CACHE_SHIFT);
+ pgfrom = pages + (pgfrom_base >> PAGE_CACHE_SHIFT);
+
+ pgto_base &= ~PAGE_CACHE_MASK;
+ pgfrom_base &= ~PAGE_CACHE_MASK;
+
+ do {
+ /* Are any pointers crossing a page boundary? */
+ if (pgto_base == 0) {
+ pgto_base = PAGE_CACHE_SIZE;
+ pgto--;
+ }
+ if (pgfrom_base == 0) {
+ pgfrom_base = PAGE_CACHE_SIZE;
+ pgfrom--;
+ }
+
+ copy = len;
+ if (copy > pgto_base)
+ copy = pgto_base;
+ if (copy > pgfrom_base)
+ copy = pgfrom_base;
+ pgto_base -= copy;
+ pgfrom_base -= copy;
+
+ vto = kmap_atomic(*pgto, KM_USER0);
+ vfrom = kmap_atomic(*pgfrom, KM_USER1);
+ memmove(vto + pgto_base, vfrom + pgfrom_base, copy);
+ kunmap_atomic(vfrom, KM_USER1);
+ kunmap_atomic(vto, KM_USER0);
+
+ } while ((len -= copy) != 0);
+}
+
+/*
+ * _copy_to_pages
+ * @pages: array of pages
+ * @pgbase: page vector address of destination
+ * @p: pointer to source data
+ * @len: length
+ *
+ * Copies data from an arbitrary memory location into an array of pages
+ * The copy is assumed to be non-overlapping.
+ */
+static void
+_copy_to_pages(struct page **pages, size_t pgbase, const char *p, size_t len)
+{
+ struct page **pgto;
+ char *vto;
+ size_t copy;
+
+ pgto = pages + (pgbase >> PAGE_CACHE_SHIFT);
+ pgbase &= ~PAGE_CACHE_MASK;
+
+ do {
+ copy = PAGE_CACHE_SIZE - pgbase;
+ if (copy > len)
+ copy = len;
+
+ vto = kmap_atomic(*pgto, KM_USER0);
+ memcpy(vto + pgbase, p, copy);
+ kunmap_atomic(vto, KM_USER0);
+
+ pgbase += copy;
+ if (pgbase == PAGE_CACHE_SIZE) {
+ pgbase = 0;
+ pgto++;
+ }
+ p += copy;
+
+ } while ((len -= copy) != 0);
+}
+
+/*
+ * _copy_from_pages
+ * @p: pointer to destination
+ * @pages: array of pages
+ * @pgbase: offset of source data
+ * @len: length
+ *
+ * Copies data into an arbitrary memory location from an array of pages
+ * The copy is assumed to be non-overlapping.
+ */
+static void
+_copy_from_pages(char *p, struct page **pages, size_t pgbase, size_t len)
+{
+ struct page **pgfrom;
+ char *vfrom;
+ size_t copy;
+
+ pgfrom = pages + (pgbase >> PAGE_CACHE_SHIFT);
+ pgbase &= ~PAGE_CACHE_MASK;
+
+ do {
+ copy = PAGE_CACHE_SIZE - pgbase;
+ if (copy > len)
+ copy = len;
+
+ vfrom = kmap_atomic(*pgfrom, KM_USER0);
+ memcpy(p, vfrom + pgbase, copy);
+ kunmap_atomic(vfrom, KM_USER0);
+
+ pgbase += copy;
+ if (pgbase == PAGE_CACHE_SIZE) {
+ pgbase = 0;
+ pgfrom++;
+ }
+ p += copy;
+
+ } while ((len -= copy) != 0);
+}
+
+/*
+ * xdr_shrink_bufhead
+ * @buf: xdr_buf
+ * @len: bytes to remove from buf->head[0]
+ *
+ * Shrinks XDR buffer's header iovec buf->head[0] by
+ * 'len' bytes. The extra data is not lost, but is instead
+ * moved into the inlined pages and/or the tail.
+ */
+void
+xdr_shrink_bufhead(struct xdr_buf *buf, size_t len)
+{
+ struct iovec *head, *tail;
+ size_t copy, offs;
+ unsigned int pglen = buf->page_len;
+
+ tail = buf->tail;
+ head = buf->head;
+ BUG_ON (len > head->iov_len);
+
+ /* Shift the tail first */
+ if (tail->iov_len != 0) {
+ if (tail->iov_len > len) {
+ copy = tail->iov_len - len;
+ memmove((char *)tail->iov_base + len,
+ tail->iov_base, copy);
+ }
+ /* Copy from the inlined pages into the tail */
+ copy = len;
+ if (copy > pglen)
+ copy = pglen;
+ offs = len - copy;
+ if (offs >= tail->iov_len)
+ copy = 0;
+ else if (copy > tail->iov_len - offs)
+ copy = tail->iov_len - offs;
+ if (copy != 0)
+ _copy_from_pages((char *)tail->iov_base + offs,
+ buf->pages,
+ buf->page_base + pglen + offs - len,
+ copy);
+ /* Do we also need to copy data from the head into the tail ? */
+ if (len > pglen) {
+ offs = copy = len - pglen;
+ if (copy > tail->iov_len)
+ copy = tail->iov_len;
+ memcpy(tail->iov_base,
+ (char *)head->iov_base +
+ head->iov_len - offs,
+ copy);
+ }
+ }
+ /* Now handle pages */
+ if (pglen != 0) {
+ if (pglen > len)
+ _shift_data_right_pages(buf->pages,
+ buf->page_base + len,
+ buf->page_base,
+ pglen - len);
+ copy = len;
+ if (len > pglen)
+ copy = pglen;
+ _copy_to_pages(buf->pages, buf->page_base,
+ (char *)head->iov_base + head->iov_len - len,
+ copy);
+ }
+ head->iov_len -= len;
+ buf->len -= len;
+}
+
+void
+xdr_shift_buf(struct xdr_buf *buf, size_t len)
+{
+ xdr_shrink_bufhead(buf, len);
+}
diff --git a/uClinux-2.4.31-uc0/net/sunrpc/xprt.c b/uClinux-2.4.31-uc0/net/sunrpc/xprt.c
new file mode 100644
index 0000000..9e59dca
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sunrpc/xprt.c
@@ -0,0 +1,1608 @@
+/*
+ * linux/net/sunrpc/xprt.c
+ *
+ * This is a generic RPC call interface supporting congestion avoidance,
+ * and asynchronous calls.
+ *
+ * The interface works like this:
+ *
+ * - When a process places a call, it allocates a request slot if
+ * one is available. Otherwise, it sleeps on the backlog queue
+ * (xprt_reserve).
+ * - Next, the caller puts together the RPC message, stuffs it into
+ * the request struct, and calls xprt_call().
+ * - xprt_call transmits the message and installs the caller on the
+ * socket's wait list. At the same time, it installs a timer that
+ * is run after the packet's timeout has expired.
+ * - When a packet arrives, the data_ready handler walks the list of
+ * pending requests for that socket. If a matching XID is found, the
+ * caller is woken up, and the timer removed.
+ * - When no reply arrives within the timeout interval, the timer is
+ * fired by the kernel and runs xprt_timer(). It either adjusts the
+ * timeout values (minor timeout) or wakes up the caller with a status
+ * of -ETIMEDOUT.
+ * - When the caller receives a notification from RPC that a reply arrived,
+ * it should release the RPC slot, and process the reply.
+ * If the call timed out, it may choose to retry the operation by
+ * adjusting the initial timeout value, and simply calling rpc_call
+ * again.
+ *
+ * Support for async RPC is done through a set of RPC-specific scheduling
+ * primitives that `transparently' work for processes as well as async
+ * tasks that rely on callbacks.
+ *
+ * Copyright (C) 1995-1997, Olaf Kirch <okir@monad.swb.de>
+ *
+ * TCP callback races fixes (C) 1998 Red Hat Software <alan@redhat.com>
+ * TCP send fixes (C) 1998 Red Hat Software <alan@redhat.com>
+ * TCP NFS related read + write fixes
+ * (C) 1999 Dave Airlie, University of Limerick, Ireland <airlied@linux.ie>
+ *
+ * Rewrite of larges part of the code in order to stabilize TCP stuff.
+ * Fix behaviour when socket buffer is full.
+ * (C) 1999 Trond Myklebust <trond.myklebust@fys.uio.no>
+ */
+
+#define __KERNEL_SYSCALLS__
+
+#include <linux/version.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/capability.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/net.h>
+#include <linux/mm.h>
+#include <linux/udp.h>
+#include <linux/unistd.h>
+#include <linux/sunrpc/clnt.h>
+#include <linux/file.h>
+
+#include <net/sock.h>
+#include <net/checksum.h>
+#include <net/udp.h>
+#include <net/tcp.h>
+
+#include <asm/uaccess.h>
+
+/*
+ * Local variables
+ */
+
+#ifdef RPC_DEBUG
+# undef RPC_DEBUG_DATA
+# define RPCDBG_FACILITY RPCDBG_XPRT
+#endif
+
+#define XPRT_MAX_BACKOFF (8)
+
+/*
+ * Local functions
+ */
+static void xprt_request_init(struct rpc_task *, struct rpc_xprt *);
+static void do_xprt_transmit(struct rpc_task *);
+static inline void do_xprt_reserve(struct rpc_task *);
+static void xprt_disconnect(struct rpc_xprt *);
+static void xprt_connect_status(struct rpc_task *task);
+static struct socket *xprt_create_socket(int, struct rpc_timeout *, int);
+static int xprt_bind_socket(struct rpc_xprt *, struct socket *);
+static int __xprt_get_cong(struct rpc_xprt *, struct rpc_task *);
+
+#ifdef RPC_DEBUG_DATA
+/*
+ * Print the buffer contents (first 128 bytes only--just enough for
+ * diropres return).
+ */
+static void
+xprt_pktdump(char *msg, u32 *packet, unsigned int count)
+{
+ u8 *buf = (u8 *) packet;
+ int j;
+
+ dprintk("RPC: %s\n", msg);
+ for (j = 0; j < count && j < 128; j += 4) {
+ if (!(j & 31)) {
+ if (j)
+ dprintk("\n");
+ dprintk("0x%04x ", j);
+ }
+ dprintk("%02x%02x%02x%02x ",
+ buf[j], buf[j+1], buf[j+2], buf[j+3]);
+ }
+ dprintk("\n");
+}
+#else
+static inline void
+xprt_pktdump(char *msg, u32 *packet, unsigned int count)
+{
+ /* NOP */
+}
+#endif
+
+/*
+ * Look up RPC transport given an INET socket
+ */
+static inline struct rpc_xprt *
+xprt_from_sock(struct sock *sk)
+{
+ return (struct rpc_xprt *) sk->user_data;
+}
+
+/*
+ * Serialize write access to sockets, in order to prevent different
+ * requests from interfering with each other.
+ * Also prevents TCP socket connections from colliding with writes.
+ */
+static int
+__xprt_lock_write(struct rpc_xprt *xprt, struct rpc_task *task)
+{
+ struct rpc_rqst *req = task->tk_rqstp;
+ if (!xprt->snd_task) {
+ if (xprt->nocong || __xprt_get_cong(xprt, task)) {
+ xprt->snd_task = task;
+ if (req) {
+ req->rq_bytes_sent = 0;
+ req->rq_ntrans++;
+ }
+ }
+ }
+ if (xprt->snd_task != task) {
+ dprintk("RPC: %4d TCP write queue full\n", task->tk_pid);
+ task->tk_timeout = 0;
+ task->tk_status = -EAGAIN;
+ if (req && req->rq_ntrans)
+ rpc_sleep_on(&xprt->resend, task, NULL, NULL);
+ else
+ rpc_sleep_on(&xprt->sending, task, NULL, NULL);
+ }
+ return xprt->snd_task == task;
+}
+
+static inline int
+xprt_lock_write(struct rpc_xprt *xprt, struct rpc_task *task)
+{
+ int retval;
+ spin_lock_bh(&xprt->sock_lock);
+ retval = __xprt_lock_write(xprt, task);
+ spin_unlock_bh(&xprt->sock_lock);
+ return retval;
+}
+
+static void
+__xprt_lock_write_next(struct rpc_xprt *xprt)
+{
+ struct rpc_task *task;
+
+ if (xprt->snd_task)
+ return;
+ task = rpc_wake_up_next(&xprt->resend);
+ if (!task) {
+ if (!xprt->nocong && RPCXPRT_CONGESTED(xprt))
+ return;
+ task = rpc_wake_up_next(&xprt->sending);
+ if (!task)
+ return;
+ }
+ if (xprt->nocong || __xprt_get_cong(xprt, task)) {
+ struct rpc_rqst *req = task->tk_rqstp;
+ xprt->snd_task = task;
+ if (req) {
+ req->rq_bytes_sent = 0;
+ req->rq_ntrans++;
+ }
+ }
+}
+
+/*
+ * Releases the socket for use by other requests.
+ */
+static void
+__xprt_release_write(struct rpc_xprt *xprt, struct rpc_task *task)
+{
+ if (xprt->snd_task == task)
+ xprt->snd_task = NULL;
+ __xprt_lock_write_next(xprt);
+}
+
+static inline void
+xprt_release_write(struct rpc_xprt *xprt, struct rpc_task *task)
+{
+ spin_lock_bh(&xprt->sock_lock);
+ __xprt_release_write(xprt, task);
+ spin_unlock_bh(&xprt->sock_lock);
+}
+
+/*
+ * Write data to socket.
+ */
+static inline int
+xprt_sendmsg(struct rpc_xprt *xprt, struct rpc_rqst *req)
+{
+ struct socket *sock = xprt->sock;
+ struct msghdr msg;
+ struct xdr_buf *xdr = &req->rq_snd_buf;
+ struct iovec niv[MAX_IOVEC];
+ unsigned int niov, slen, skip;
+ mm_segment_t oldfs;
+ int result;
+
+ if (!sock)
+ return -ENOTCONN;
+
+ xprt_pktdump("packet data:",
+ req->rq_svec->iov_base,
+ req->rq_svec->iov_len);
+
+ /* Dont repeat bytes */
+ skip = req->rq_bytes_sent;
+ slen = xdr->len - skip;
+ oldfs = get_fs(); set_fs(get_ds());
+ do {
+ unsigned int slen_part, n;
+
+ niov = xdr_kmap(niv, xdr, skip);
+ if (!niov) {
+ result = -EAGAIN;
+ break;
+ }
+
+ msg.msg_flags = MSG_DONTWAIT|MSG_NOSIGNAL;
+ msg.msg_iov = niv;
+ msg.msg_iovlen = niov;
+ msg.msg_name = (struct sockaddr *) &xprt->addr;
+ msg.msg_namelen = sizeof(xprt->addr);
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+
+ slen_part = 0;
+ for (n = 0; n < niov; n++)
+ slen_part += niv[n].iov_len;
+
+ clear_bit(SOCK_ASYNC_NOSPACE, &sock->flags);
+ result = sock_sendmsg(sock, &msg, slen_part);
+
+ xdr_kunmap(xdr, skip, niov);
+
+ skip += slen_part;
+ slen -= slen_part;
+ } while (result >= 0 && slen);
+ set_fs(oldfs);
+
+ dprintk("RPC: xprt_sendmsg(%d) = %d\n", slen, result);
+
+ if (result >= 0)
+ return result;
+
+ switch (result) {
+ case -ECONNREFUSED:
+ /* When the server has died, an ICMP port unreachable message
+ * prompts ECONNREFUSED.
+ */
+ case -EAGAIN:
+ break;
+ case -ECONNRESET:
+ case -ENOTCONN:
+ case -EPIPE:
+ /* connection broken */
+ if (xprt->stream)
+ result = -ENOTCONN;
+ break;
+ default:
+ printk(KERN_NOTICE "RPC: sendmsg returned error %d\n", -result);
+ }
+ return result;
+}
+
+/*
+ * Van Jacobson congestion avoidance. Check if the congestion window
+ * overflowed. Put the task to sleep if this is the case.
+ */
+static int
+__xprt_get_cong(struct rpc_xprt *xprt, struct rpc_task *task)
+{
+ struct rpc_rqst *req = task->tk_rqstp;
+
+ if (req->rq_cong)
+ return 1;
+ dprintk("RPC: %4d xprt_cwnd_limited cong = %ld cwnd = %ld\n",
+ task->tk_pid, xprt->cong, xprt->cwnd);
+ if (RPCXPRT_CONGESTED(xprt))
+ return 0;
+ req->rq_cong = 1;
+ xprt->cong += RPC_CWNDSCALE;
+ return 1;
+}
+
+/*
+ * Adjust the congestion window, and wake up the next task
+ * that has been sleeping due to congestion
+ */
+static void
+__xprt_put_cong(struct rpc_xprt *xprt, struct rpc_rqst *req)
+{
+ if (!req->rq_cong)
+ return;
+ req->rq_cong = 0;
+ xprt->cong -= RPC_CWNDSCALE;
+ __xprt_lock_write_next(xprt);
+}
+
+/*
+ * Adjust RPC congestion window
+ * We use a time-smoothed congestion estimator to avoid heavy oscillation.
+ */
+static void
+xprt_adjust_cwnd(struct rpc_xprt *xprt, int result)
+{
+ unsigned long cwnd;
+
+ cwnd = xprt->cwnd;
+ if (result >= 0 && cwnd <= xprt->cong) {
+ /* The (cwnd >> 1) term makes sure
+ * the result gets rounded properly. */
+ cwnd += (RPC_CWNDSCALE * RPC_CWNDSCALE + (cwnd >> 1)) / cwnd;
+ if (cwnd > RPC_MAXCWND)
+ cwnd = RPC_MAXCWND;
+ __xprt_lock_write_next(xprt);
+ } else if (result == -ETIMEDOUT) {
+ cwnd >>= 1;
+ if (cwnd < RPC_CWNDSCALE)
+ cwnd = RPC_CWNDSCALE;
+ }
+ dprintk("RPC: cong %ld, cwnd was %ld, now %ld\n",
+ xprt->cong, xprt->cwnd, cwnd);
+ xprt->cwnd = cwnd;
+}
+
+/*
+ * Adjust timeout values etc for next retransmit
+ */
+int
+xprt_adjust_timeout(struct rpc_timeout *to)
+{
+ if (to->to_retries > 0) {
+ if (to->to_exponential)
+ to->to_current <<= 1;
+ else
+ to->to_current += to->to_increment;
+ if (to->to_maxval && to->to_current >= to->to_maxval)
+ to->to_current = to->to_maxval;
+ } else {
+ if (to->to_exponential)
+ to->to_initval <<= 1;
+ else
+ to->to_initval += to->to_increment;
+ if (to->to_maxval && to->to_initval >= to->to_maxval)
+ to->to_initval = to->to_maxval;
+ to->to_current = to->to_initval;
+ }
+
+ if (!to->to_current) {
+ printk(KERN_WARNING "xprt_adjust_timeout: to_current = 0!\n");
+ to->to_current = 5 * HZ;
+ }
+ pprintk("RPC: %lu %s\n", jiffies,
+ to->to_retries? "retrans" : "timeout");
+ return to->to_retries-- > 0;
+}
+
+/*
+ * Close down a transport socket
+ */
+static void
+xprt_close(struct rpc_xprt *xprt)
+{
+ struct socket *sock = xprt->sock;
+ struct sock *sk = xprt->inet;
+
+ if (!sk)
+ return;
+
+ write_lock_bh(&sk->callback_lock);
+ xprt->inet = NULL;
+ xprt->sock = NULL;
+
+ sk->user_data = NULL;
+ sk->data_ready = xprt->old_data_ready;
+ sk->state_change = xprt->old_state_change;
+ sk->write_space = xprt->old_write_space;
+ write_unlock_bh(&sk->callback_lock);
+
+ xprt_disconnect(xprt);
+ sk->no_check = 0;
+
+ sock_release(sock);
+}
+
+/*
+ * Mark a transport as disconnected
+ */
+static void
+xprt_disconnect(struct rpc_xprt *xprt)
+{
+ dprintk("RPC: disconnected transport %p\n", xprt);
+ spin_lock_bh(&xprt->sock_lock);
+ xprt_clear_connected(xprt);
+ rpc_wake_up_status(&xprt->pending, -ENOTCONN);
+ spin_unlock_bh(&xprt->sock_lock);
+}
+
+/*
+ * Reconnect a broken TCP connection.
+ *
+ */
+void
+xprt_connect(struct rpc_task *task)
+{
+ struct rpc_xprt *xprt = task->tk_xprt;
+ struct socket *sock = xprt->sock;
+ struct sock *inet;
+ int status;
+
+ dprintk("RPC: %4d xprt_connect %p connected %d\n",
+ task->tk_pid, xprt, xprt_connected(xprt));
+ if (xprt->shutdown)
+ return;
+
+ if (!xprt->addr.sin_port) {
+ task->tk_status = -EIO;
+ return;
+ }
+
+ if (!xprt_lock_write(xprt, task))
+ return;
+ if (xprt_connected(xprt))
+ goto out_write;
+
+ if (task->tk_rqstp)
+ task->tk_rqstp->rq_bytes_sent = 0;
+
+ xprt_close(xprt);
+ /* Create an unconnected socket */
+ sock = xprt_create_socket(xprt->prot, &xprt->timeout, xprt->resvport);
+ if (!sock) {
+ /* couldn't create socket or bind to reserved port;
+ * this is likely a permanent error, so cause an abort */
+ task->tk_status = -EIO;
+ goto out_write;
+ }
+ xprt_bind_socket(xprt, sock);
+
+ if (!xprt->stream)
+ goto out_write;
+
+ inet = sock->sk;
+
+ /* Now connect it asynchronously. */
+ dprintk("RPC: %4d connecting new socket\n", task->tk_pid);
+ status = sock->ops->connect(sock, (struct sockaddr *) &xprt->addr,
+ sizeof(xprt->addr), O_NONBLOCK);
+ dprintk("RPC: %4d connect status %d connected %d\n",
+ task->tk_pid, status, xprt_connected(xprt));
+
+ if (status >= 0)
+ return;
+
+ switch (status) {
+ case -EALREADY:
+ case -EINPROGRESS:
+ /* Protect against TCP socket state changes */
+ lock_sock(inet);
+ if (inet->state != TCP_ESTABLISHED) {
+ dprintk("RPC: %4d waiting for connection\n",
+ task->tk_pid);
+ task->tk_timeout = RPC_CONNECT_TIMEOUT;
+ /* if the socket is already closing, delay briefly */
+ if ((1<<inet->state) & ~(TCPF_SYN_SENT|TCPF_SYN_RECV))
+ task->tk_timeout = RPC_REESTABLISH_TIMEOUT;
+ rpc_sleep_on(&xprt->pending, task, xprt_connect_status,
+ NULL);
+ }
+ release_sock(inet);
+ break;
+ case -ECONNREFUSED:
+ case -ECONNRESET:
+ case -ENOTCONN:
+ if (!task->tk_client->cl_softrtry) {
+ rpc_delay(task, RPC_REESTABLISH_TIMEOUT);
+ task->tk_status = -ENOTCONN;
+ break;
+ }
+ default:
+ /* Report myriad other possible returns. If this file
+ * system is soft mounted, just error out, like Solaris. */
+ if (task->tk_client->cl_softrtry) {
+ printk(KERN_WARNING
+ "RPC: error %d connecting to server %s, exiting\n",
+ -status, task->tk_client->cl_server);
+ task->tk_status = -EIO;
+ goto out_write;
+ }
+ printk(KERN_WARNING "RPC: error %d connecting to server %s\n",
+ -status, task->tk_client->cl_server);
+ /* This will prevent anybody else from connecting */
+ rpc_delay(task, RPC_REESTABLISH_TIMEOUT);
+ task->tk_status = status;
+ break;
+ }
+ return;
+ out_write:
+ xprt_release_write(xprt, task);
+}
+
+/*
+ * We arrive here when awoken from waiting on connection establishment.
+ */
+static void
+xprt_connect_status(struct rpc_task *task)
+{
+ struct rpc_xprt *xprt = task->tk_xprt;
+
+ if (task->tk_status >= 0) {
+ dprintk("RPC: %4d xprt_connect_status: connection established\n",
+ task->tk_pid);
+ return;
+ }
+
+ /* if soft mounted, cause this RPC to fail */
+ if (task->tk_client->cl_softrtry)
+ task->tk_status = -EIO;
+
+ switch (task->tk_status) {
+ case -ENOTCONN:
+ rpc_delay(task, RPC_REESTABLISH_TIMEOUT);
+ return;
+ case -ETIMEDOUT:
+ dprintk("RPC: %4d xprt_connect_status: timed out\n",
+ task->tk_pid);
+ break;
+ default:
+ printk(KERN_ERR "RPC: error %d connecting to server %s\n",
+ -task->tk_status, task->tk_client->cl_server);
+ }
+ xprt_release_write(xprt, task);
+}
+
+/*
+ * Look up the RPC request corresponding to a reply, and then lock it.
+ */
+static inline struct rpc_rqst *
+xprt_lookup_rqst(struct rpc_xprt *xprt, u32 xid)
+{
+ struct list_head *pos;
+ struct rpc_rqst *req = NULL;
+
+ list_for_each(pos, &xprt->recv) {
+ struct rpc_rqst *entry = list_entry(pos, struct rpc_rqst, rq_list);
+ if (entry->rq_xid == xid) {
+ req = entry;
+ break;
+ }
+ }
+ return req;
+}
+
+/*
+ * Complete reply received.
+ * The TCP code relies on us to remove the request from xprt->pending.
+ */
+static void
+xprt_complete_rqst(struct rpc_xprt *xprt, struct rpc_rqst *req, int copied)
+{
+ struct rpc_task *task = req->rq_task;
+ struct rpc_clnt *clnt = task->tk_client;
+
+ /* Adjust congestion window */
+ if (!xprt->nocong) {
+ int timer = rpcproc_timer(clnt, task->tk_msg.rpc_proc);
+ xprt_adjust_cwnd(xprt, copied);
+ __xprt_put_cong(xprt, req);
+ if (req->rq_ntrans == 1) {
+ if (timer)
+ rpc_update_rtt(&clnt->cl_rtt, timer, (long)jiffies - req->rq_xtime);
+ }
+ rpc_set_timeo(&clnt->cl_rtt, timer, req->rq_ntrans - 1);
+ }
+
+#ifdef RPC_PROFILE
+ /* Profile only reads for now */
+ if (copied > 1024) {
+ static unsigned long nextstat = 0;
+ static unsigned long pkt_rtt = 0, pkt_len = 0, pkt_cnt = 0;
+
+ pkt_cnt++;
+ pkt_len += req->rq_slen + copied;
+ pkt_rtt += jiffies - req->rq_xtime;
+ if (time_before(nextstat, jiffies)) {
+ printk("RPC: %lu %ld cwnd\n", jiffies, xprt->cwnd);
+ printk("RPC: %ld %ld %ld %ld stat\n",
+ jiffies, pkt_cnt, pkt_len, pkt_rtt);
+ pkt_rtt = pkt_len = pkt_cnt = 0;
+ nextstat = jiffies + 5 * HZ;
+ }
+ }
+#endif
+
+ dprintk("RPC: %4d has input (%d bytes)\n", task->tk_pid, copied);
+ req->rq_received = copied;
+ list_del_init(&req->rq_list);
+
+ /* ... and wake up the process. */
+ rpc_wake_up_task(task);
+ return;
+}
+
+static size_t
+skb_read_bits(skb_reader_t *desc, void *to, size_t len)
+{
+ if (len > desc->count)
+ len = desc->count;
+ skb_copy_bits(desc->skb, desc->offset, to, len);
+ desc->count -= len;
+ desc->offset += len;
+ return len;
+}
+
+static size_t
+skb_read_and_csum_bits(skb_reader_t *desc, void *to, size_t len)
+{
+ unsigned int csum2, pos;
+
+ if (len > desc->count)
+ len = desc->count;
+ pos = desc->offset;
+ csum2 = skb_copy_and_csum_bits(desc->skb, pos, to, len, 0);
+ desc->csum = csum_block_add(desc->csum, csum2, pos);
+ desc->count -= len;
+ desc->offset += len;
+ return len;
+}
+
+/*
+ * We have set things up such that we perform the checksum of the UDP
+ * packet in parallel with the copies into the RPC client iovec. -DaveM
+ */
+static int
+csum_partial_copy_to_xdr(struct xdr_buf *xdr, struct sk_buff *skb)
+{
+ skb_reader_t desc;
+
+ desc.skb = skb;
+ desc.offset = sizeof(struct udphdr);
+ desc.count = skb->len - desc.offset;
+
+ if (skb->ip_summed == CHECKSUM_UNNECESSARY)
+ goto no_checksum;
+
+ desc.csum = csum_partial(skb->data, desc.offset, skb->csum);
+ xdr_partial_copy_from_skb(xdr, 0, &desc, skb_read_and_csum_bits);
+ if (desc.offset != skb->len) {
+ unsigned int csum2;
+ csum2 = skb_checksum(skb, desc.offset, skb->len - desc.offset, 0);
+ desc.csum = csum_block_add(desc.csum, csum2, desc.offset);
+ }
+ if ((unsigned short)csum_fold(desc.csum))
+ return -1;
+ return 0;
+no_checksum:
+ xdr_partial_copy_from_skb(xdr, 0, &desc, skb_read_bits);
+ return 0;
+}
+
+/*
+ * Input handler for RPC replies. Called from a bottom half and hence
+ * atomic.
+ */
+static void
+udp_data_ready(struct sock *sk, int len)
+{
+ struct rpc_task *task;
+ struct rpc_xprt *xprt;
+ struct rpc_rqst *rovr;
+ struct sk_buff *skb;
+ int err, repsize, copied;
+
+ read_lock(&sk->callback_lock);
+ dprintk("RPC: udp_data_ready...\n");
+ if (sk->dead || !(xprt = xprt_from_sock(sk))) {
+ printk("RPC: udp_data_ready request not found!\n");
+ goto out;
+ }
+
+ dprintk("RPC: udp_data_ready client %p\n", xprt);
+
+ if ((skb = skb_recv_datagram(sk, 0, 1, &err)) == NULL)
+ goto out;
+
+ if (xprt->shutdown)
+ goto dropit;
+
+ repsize = skb->len - sizeof(struct udphdr);
+ if (repsize < 4) {
+ printk("RPC: impossible RPC reply size %d!\n", repsize);
+ goto dropit;
+ }
+
+ /* Look up and lock the request corresponding to the given XID */
+ spin_lock(&xprt->sock_lock);
+ rovr = xprt_lookup_rqst(xprt, *(u32 *) (skb->h.raw + sizeof(struct udphdr)));
+ if (!rovr)
+ goto out_unlock;
+ task = rovr->rq_task;
+
+ dprintk("RPC: %4d received reply\n", task->tk_pid);
+ xprt_pktdump("packet data:",
+ (u32 *) (skb->h.raw+sizeof(struct udphdr)), repsize);
+
+ if ((copied = rovr->rq_private_buf.len) > repsize)
+ copied = repsize;
+
+ /* Suck it into the iovec, verify checksum if not done by hw. */
+ if (csum_partial_copy_to_xdr(&rovr->rq_private_buf, skb))
+ goto out_unlock;
+
+ /* Something worked... */
+ dst_confirm(skb->dst);
+
+ xprt_complete_rqst(xprt, rovr, copied);
+
+ out_unlock:
+ spin_unlock(&xprt->sock_lock);
+ dropit:
+ skb_free_datagram(sk, skb);
+ out:
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible(sk->sleep);
+ read_unlock(&sk->callback_lock);
+}
+
+/*
+ * Copy from an skb into memory and shrink the skb.
+ */
+static inline size_t
+tcp_copy_data(skb_reader_t *desc, void *p, size_t len)
+{
+ if (len > desc->count)
+ len = desc->count;
+ skb_copy_bits(desc->skb, desc->offset, p, len);
+ desc->offset += len;
+ desc->count -= len;
+ return len;
+}
+
+/*
+ * TCP read fragment marker
+ */
+static inline void
+tcp_read_fraghdr(struct rpc_xprt *xprt, skb_reader_t *desc)
+{
+ size_t len, used;
+ char *p;
+
+ p = ((char *) &xprt->tcp_recm) + xprt->tcp_offset;
+ len = sizeof(xprt->tcp_recm) - xprt->tcp_offset;
+ used = tcp_copy_data(desc, p, len);
+ xprt->tcp_offset += used;
+ if (used != len)
+ return;
+ xprt->tcp_reclen = ntohl(xprt->tcp_recm);
+ if (xprt->tcp_reclen & 0x80000000)
+ xprt->tcp_flags |= XPRT_LAST_FRAG;
+ else
+ xprt->tcp_flags &= ~XPRT_LAST_FRAG;
+ xprt->tcp_reclen &= 0x7fffffff;
+ xprt->tcp_flags &= ~XPRT_COPY_RECM;
+ xprt->tcp_offset = 0;
+ /* Sanity check of the record length */
+ if (xprt->tcp_reclen < 4) {
+ printk(KERN_ERR "RPC: Invalid TCP record fragment length\n");
+ xprt_disconnect(xprt);
+ }
+ dprintk("RPC: reading TCP record fragment of length %d\n",
+ xprt->tcp_reclen);
+}
+
+static void
+tcp_check_recm(struct rpc_xprt *xprt)
+{
+ if (xprt->tcp_offset == xprt->tcp_reclen) {
+ xprt->tcp_flags |= XPRT_COPY_RECM;
+ xprt->tcp_offset = 0;
+ if (xprt->tcp_flags & XPRT_LAST_FRAG) {
+ xprt->tcp_flags &= ~XPRT_COPY_DATA;
+ xprt->tcp_flags |= XPRT_COPY_XID;
+ xprt->tcp_copied = 0;
+ }
+ }
+}
+
+/*
+ * TCP read xid
+ */
+static inline void
+tcp_read_xid(struct rpc_xprt *xprt, skb_reader_t *desc)
+{
+ size_t len, used;
+ char *p;
+
+ len = sizeof(xprt->tcp_xid) - xprt->tcp_offset;
+ dprintk("RPC: reading XID (%Zu bytes)\n", len);
+ p = ((char *) &xprt->tcp_xid) + xprt->tcp_offset;
+ used = tcp_copy_data(desc, p, len);
+ xprt->tcp_offset += used;
+ if (used != len)
+ return;
+ xprt->tcp_flags &= ~XPRT_COPY_XID;
+ xprt->tcp_flags |= XPRT_COPY_DATA;
+ xprt->tcp_copied = 4;
+ dprintk("RPC: reading reply for XID %08x\n", xprt->tcp_xid);
+ tcp_check_recm(xprt);
+}
+
+/*
+ * TCP read and complete request
+ */
+static inline void
+tcp_read_request(struct rpc_xprt *xprt, skb_reader_t *desc)
+{
+ struct rpc_rqst *req;
+ struct xdr_buf *rcvbuf;
+ size_t len;
+
+ /* Find and lock the request corresponding to this xid */
+ spin_lock(&xprt->sock_lock);
+ req = xprt_lookup_rqst(xprt, xprt->tcp_xid);
+ if (!req) {
+ xprt->tcp_flags &= ~XPRT_COPY_DATA;
+ dprintk("RPC: XID %08x request not found!\n",
+ xprt->tcp_xid);
+ spin_unlock(&xprt->sock_lock);
+ return;
+ }
+
+ rcvbuf = &req->rq_private_buf;
+ len = desc->count;
+ if (len > xprt->tcp_reclen - xprt->tcp_offset) {
+ skb_reader_t my_desc;
+
+ len = xprt->tcp_reclen - xprt->tcp_offset;
+ memcpy(&my_desc, desc, sizeof(my_desc));
+ my_desc.count = len;
+ xdr_partial_copy_from_skb(rcvbuf, xprt->tcp_copied,
+ &my_desc, tcp_copy_data);
+ desc->count -= len;
+ desc->offset += len;
+ } else
+ xdr_partial_copy_from_skb(rcvbuf, xprt->tcp_copied,
+ desc, tcp_copy_data);
+ xprt->tcp_copied += len;
+ xprt->tcp_offset += len;
+
+ if (xprt->tcp_copied == req->rq_private_buf.len)
+ xprt->tcp_flags &= ~XPRT_COPY_DATA;
+ else if (xprt->tcp_offset == xprt->tcp_reclen) {
+ if (xprt->tcp_flags & XPRT_LAST_FRAG)
+ xprt->tcp_flags &= ~XPRT_COPY_DATA;
+ }
+
+ if (!(xprt->tcp_flags & XPRT_COPY_DATA)) {
+ dprintk("RPC: %4d received reply complete\n",
+ req->rq_task->tk_pid);
+ xprt_complete_rqst(xprt, req, xprt->tcp_copied);
+ }
+ spin_unlock(&xprt->sock_lock);
+ tcp_check_recm(xprt);
+}
+
+/*
+ * TCP discard extra bytes from a short read
+ */
+static inline void
+tcp_read_discard(struct rpc_xprt *xprt, skb_reader_t *desc)
+{
+ size_t len;
+
+ len = xprt->tcp_reclen - xprt->tcp_offset;
+ if (len > desc->count)
+ len = desc->count;
+ desc->count -= len;
+ desc->offset += len;
+ xprt->tcp_offset += len;
+ tcp_check_recm(xprt);
+}
+
+/*
+ * TCP record receive routine
+ * We first have to grab the record marker, then the XID, then the data.
+ */
+static int
+tcp_data_recv(read_descriptor_t *rd_desc, struct sk_buff *skb,
+ unsigned int offset, size_t len)
+{
+ struct rpc_xprt *xprt = (struct rpc_xprt *)rd_desc->buf;
+ skb_reader_t desc = { skb, offset, len };
+
+ dprintk("RPC: tcp_data_recv\n");
+ do {
+ /* Read in a new fragment marker if necessary */
+ /* Can we ever really expect to get completely empty fragments? */
+ if (xprt->tcp_flags & XPRT_COPY_RECM) {
+ tcp_read_fraghdr(xprt, &desc);
+ continue;
+ }
+ /* Read in the xid if necessary */
+ if (xprt->tcp_flags & XPRT_COPY_XID) {
+ tcp_read_xid(xprt, &desc);
+ continue;
+ }
+ /* Read in the request data */
+ if (xprt->tcp_flags & XPRT_COPY_DATA) {
+ tcp_read_request(xprt, &desc);
+ continue;
+ }
+ /* Skip over any trailing bytes on short reads */
+ tcp_read_discard(xprt, &desc);
+ } while (desc.count);
+ dprintk("RPC: tcp_data_recv done\n");
+ return len - desc.count;
+}
+
+static void tcp_data_ready(struct sock *sk, int bytes)
+{
+ struct rpc_xprt *xprt;
+ read_descriptor_t rd_desc;
+
+ read_lock(&sk->callback_lock);
+ dprintk("RPC: tcp_data_ready...\n");
+ if (!(xprt = xprt_from_sock(sk))) {
+ printk("RPC: tcp_data_ready socket info not found!\n");
+ goto out;
+ }
+ if (xprt->shutdown)
+ goto out;
+
+ /* We use rd_desc to pass struct xprt to tcp_data_recv */
+ rd_desc.buf = (char *)xprt;
+ rd_desc.count = 65536;
+ tcp_read_sock(sk, &rd_desc, tcp_data_recv);
+out:
+ read_unlock(&sk->callback_lock);
+}
+
+static void
+tcp_state_change(struct sock *sk)
+{
+ struct rpc_xprt *xprt;
+
+ read_lock(&sk->callback_lock);
+ if (!(xprt = xprt_from_sock(sk)))
+ goto out;
+ dprintk("RPC: tcp_state_change client %p...\n", xprt);
+ dprintk("RPC: state %x conn %d dead %d zapped %d\n",
+ sk->state, xprt_connected(xprt),
+ sk->dead, sk->zapped);
+
+ switch (sk->state) {
+ case TCP_ESTABLISHED:
+ if (xprt_test_and_set_connected(xprt))
+ break;
+
+ /* Reset TCP record info */
+ xprt->tcp_offset = 0;
+ xprt->tcp_reclen = 0;
+ xprt->tcp_copied = 0;
+ xprt->tcp_flags = XPRT_COPY_RECM | XPRT_COPY_XID;
+
+ spin_lock_bh(&xprt->sock_lock);
+ if (xprt->snd_task && xprt->snd_task->tk_rpcwait == &xprt->pending)
+ rpc_wake_up_task(xprt->snd_task);
+ spin_unlock_bh(&xprt->sock_lock);
+ break;
+ case TCP_SYN_SENT:
+ case TCP_SYN_RECV:
+ break;
+ default:
+ xprt_disconnect(xprt);
+ break;
+ }
+ out:
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible_all(sk->sleep);
+ read_unlock(&sk->callback_lock);
+}
+
+/*
+ * Called when more output buffer space is available for this socket.
+ * We try not to wake our writers until they can make "significant"
+ * progress, otherwise we'll waste resources thrashing sock_sendmsg
+ * with a bunch of small requests.
+ */
+static void
+xprt_write_space(struct sock *sk)
+{
+ struct rpc_xprt *xprt;
+ struct socket *sock;
+
+ read_lock(&sk->callback_lock);
+ if (!(xprt = xprt_from_sock(sk)) || !(sock = sk->socket))
+ goto out;
+ if (xprt->shutdown)
+ goto out;
+
+ /* Wait until we have enough socket memory */
+ if (xprt->stream) {
+ /* from net/ipv4/tcp.c:tcp_write_space */
+ if (tcp_wspace(sk) < tcp_min_write_space(sk))
+ goto out;
+ } else {
+ /* from net/core/sock.c:sock_def_write_space */
+ if (!sock_writeable(sk))
+ goto out;
+ }
+
+ if (!test_and_clear_bit(SOCK_NOSPACE, &sock->flags))
+ goto out;
+
+ spin_lock_bh(&xprt->sock_lock);
+ if (xprt->snd_task && xprt->snd_task->tk_rpcwait == &xprt->pending)
+ rpc_wake_up_task(xprt->snd_task);
+ spin_unlock_bh(&xprt->sock_lock);
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible(sk->sleep);
+out:
+ read_unlock(&sk->callback_lock);
+}
+
+/*
+ * RPC receive timeout handler.
+ */
+static void
+xprt_timer(struct rpc_task *task)
+{
+ struct rpc_rqst *req = task->tk_rqstp;
+ struct rpc_xprt *xprt = req->rq_xprt;
+
+ spin_lock(&xprt->sock_lock);
+ if (req->rq_received)
+ goto out;
+
+ xprt_adjust_cwnd(req->rq_xprt, -ETIMEDOUT);
+ __xprt_put_cong(xprt, req);
+
+ dprintk("RPC: %4d xprt_timer (%s request)\n",
+ task->tk_pid, req ? "pending" : "backlogged");
+
+ task->tk_status = -ETIMEDOUT;
+out:
+ task->tk_timeout = 0;
+ rpc_wake_up_task(task);
+ spin_unlock(&xprt->sock_lock);
+}
+
+/*
+ * Place the actual RPC call.
+ * We have to copy the iovec because sendmsg fiddles with its contents.
+ */
+void
+xprt_transmit(struct rpc_task *task)
+{
+ struct rpc_rqst *req = task->tk_rqstp;
+ struct rpc_xprt *xprt = req->rq_xprt;
+
+ dprintk("RPC: %4d xprt_transmit(%x)\n", task->tk_pid,
+ *(u32 *)(req->rq_svec[0].iov_base));
+
+ if (xprt->shutdown)
+ task->tk_status = -EIO;
+
+ if (task->tk_status < 0)
+ return;
+
+ if (task->tk_rpcwait)
+ rpc_remove_wait_queue(task);
+
+ /* set up everything as needed. */
+ /* Write the record marker */
+ if (xprt->stream) {
+ u32 *marker = req->rq_svec[0].iov_base;
+
+ *marker = htonl(0x80000000|(req->rq_slen-sizeof(*marker)));
+ }
+
+ spin_lock_bh(&xprt->sock_lock);
+ if (req->rq_received != 0 && !req->rq_bytes_sent)
+ goto out_notrans;
+
+ if (!__xprt_lock_write(xprt, task))
+ goto out_notrans;
+
+ if (!xprt_connected(xprt)) {
+ task->tk_status = -ENOTCONN;
+ goto out_notrans;
+ }
+
+ if (list_empty(&req->rq_list)) {
+ /* Update the softirq receive buffer */
+ memcpy(&req->rq_private_buf, &req->rq_rcv_buf,
+ sizeof(req->rq_private_buf));
+ list_add_tail(&req->rq_list, &xprt->recv);
+ }
+ spin_unlock_bh(&xprt->sock_lock);
+
+ do_xprt_transmit(task);
+ return;
+out_notrans:
+ spin_unlock_bh(&xprt->sock_lock);
+}
+
+static void
+do_xprt_transmit(struct rpc_task *task)
+{
+ struct rpc_clnt *clnt = task->tk_client;
+ struct rpc_rqst *req = task->tk_rqstp;
+ struct rpc_xprt *xprt = req->rq_xprt;
+ int status, retry = 0;
+
+
+ /* Continue transmitting the packet/record. We must be careful
+ * to cope with writespace callbacks arriving _after_ we have
+ * called xprt_sendmsg().
+ */
+ while (1) {
+ req->rq_xtime = jiffies;
+ status = xprt_sendmsg(xprt, req);
+
+ if (status < 0)
+ break;
+
+ if (xprt->stream) {
+ req->rq_bytes_sent += status;
+
+ /* If we've sent the entire packet, immediately
+ * reset the count of bytes sent. */
+ if (req->rq_bytes_sent >= req->rq_slen) {
+ req->rq_bytes_sent = 0;
+ goto out_receive;
+ }
+ } else {
+ if (status >= req->rq_slen)
+ goto out_receive;
+ status = -EAGAIN;
+ break;
+ }
+
+ dprintk("RPC: %4d xmit incomplete (%d left of %d)\n",
+ task->tk_pid, req->rq_slen - req->rq_bytes_sent,
+ req->rq_slen);
+
+ status = -EAGAIN;
+ if (retry++ > 50)
+ break;
+ }
+
+ /* If we're doing a resend and have received a reply already,
+ * then exit early.
+ * Note, though, that we can't do this if we've already started
+ * resending down a TCP stream.
+ */
+ task->tk_status = status;
+
+ switch (status) {
+ case -EAGAIN:
+ if (test_bit(SOCK_ASYNC_NOSPACE, &xprt->sock->flags)) {
+ /* Protect against races with xprt_write_space */
+ spin_lock_bh(&xprt->sock_lock);
+ /* Don't race with disconnect */
+ if (!xprt_connected(xprt))
+ task->tk_status = -ENOTCONN;
+ else if (test_bit(SOCK_NOSPACE, &xprt->sock->flags)) {
+ task->tk_timeout = req->rq_timeout.to_current;
+ rpc_sleep_on(&xprt->pending, task, NULL, NULL);
+ }
+ spin_unlock_bh(&xprt->sock_lock);
+ return;
+ }
+ /* Keep holding the socket if it is blocked */
+ rpc_delay(task, HZ>>4);
+ return;
+ case -ECONNREFUSED:
+ task->tk_timeout = RPC_REESTABLISH_TIMEOUT;
+ rpc_sleep_on(&xprt->sending, task, NULL, NULL);
+ case -ENOTCONN:
+ return;
+ default:
+ if (xprt->stream)
+ xprt_disconnect(xprt);
+ }
+ xprt_release_write(xprt, task);
+ return;
+ out_receive:
+ dprintk("RPC: %4d xmit complete\n", task->tk_pid);
+ spin_lock_bh(&xprt->sock_lock);
+ /* Set the task's receive timeout value */
+ if (!xprt->nocong) {
+ int timer = rpcproc_timer(clnt, task->tk_msg.rpc_proc);
+ task->tk_timeout = rpc_calc_rto(&clnt->cl_rtt, timer);
+ task->tk_timeout <<= rpc_ntimeo(&clnt->cl_rtt, timer);
+ task->tk_timeout <<= clnt->cl_timeout.to_retries
+ - req->rq_timeout.to_retries;
+ if (task->tk_timeout > req->rq_timeout.to_maxval)
+ task->tk_timeout = req->rq_timeout.to_maxval;
+ } else
+ task->tk_timeout = req->rq_timeout.to_current;
+ /* Don't race with disconnect */
+ if (!xprt_connected(xprt))
+ task->tk_status = -ENOTCONN;
+ else if (!req->rq_received)
+ rpc_sleep_on(&xprt->pending, task, NULL, xprt_timer);
+ __xprt_release_write(xprt, task);
+ spin_unlock_bh(&xprt->sock_lock);
+}
+
+/*
+ * Reserve an RPC call slot.
+ */
+static inline void
+do_xprt_reserve(struct rpc_task *task)
+{
+ struct rpc_xprt *xprt = task->tk_xprt;
+
+ task->tk_status = 0;
+ if (task->tk_rqstp)
+ return;
+ if (xprt->free) {
+ struct rpc_rqst *req = xprt->free;
+ xprt->free = req->rq_next;
+ req->rq_next = NULL;
+ task->tk_rqstp = req;
+ xprt_request_init(task, xprt);
+ return;
+ }
+ dprintk("RPC: waiting for request slot\n");
+ task->tk_status = -EAGAIN;
+ task->tk_timeout = 0;
+ rpc_sleep_on(&xprt->backlog, task, NULL, NULL);
+}
+
+void
+xprt_reserve(struct rpc_task *task)
+{
+ struct rpc_xprt *xprt = task->tk_xprt;
+
+ task->tk_status = -EIO;
+ if (!xprt->shutdown) {
+ spin_lock(&xprt->xprt_lock);
+ do_xprt_reserve(task);
+ spin_unlock(&xprt->xprt_lock);
+ }
+}
+
+/*
+ * Allocate a 'unique' XID
+ */
+static u32
+xprt_alloc_xid(void)
+{
+ static spinlock_t xid_lock = SPIN_LOCK_UNLOCKED;
+ static int need_init = 1;
+ static u32 xid;
+ u32 ret;
+
+ spin_lock(&xid_lock);
+ if (unlikely(need_init)) {
+ xid = CURRENT_TIME << 12;
+ need_init = 0;
+ }
+ ret = xid++;
+ spin_unlock(&xid_lock);
+ return ret;
+}
+
+/*
+ * Initialize RPC request
+ */
+static void
+xprt_request_init(struct rpc_task *task, struct rpc_xprt *xprt)
+{
+ struct rpc_rqst *req = task->tk_rqstp;
+
+ req->rq_timeout = xprt->timeout;
+ req->rq_task = task;
+ req->rq_xprt = xprt;
+ req->rq_xid = xprt_alloc_xid();
+ INIT_LIST_HEAD(&req->rq_list);
+ dprintk("RPC: %4d reserved req %p xid %08x\n", task->tk_pid,
+ req, req->rq_xid);
+}
+
+/*
+ * Release an RPC call slot
+ */
+void
+xprt_release(struct rpc_task *task)
+{
+ struct rpc_xprt *xprt = task->tk_xprt;
+ struct rpc_rqst *req;
+
+ if (!(req = task->tk_rqstp))
+ return;
+ spin_lock_bh(&xprt->sock_lock);
+ __xprt_release_write(xprt, task);
+ __xprt_put_cong(xprt, req);
+ if (!list_empty(&req->rq_list))
+ list_del(&req->rq_list);
+ spin_unlock_bh(&xprt->sock_lock);
+ task->tk_rqstp = NULL;
+ memset(req, 0, sizeof(*req)); /* mark unused */
+
+ dprintk("RPC: %4d release request %p\n", task->tk_pid, req);
+
+ spin_lock(&xprt->xprt_lock);
+ req->rq_next = xprt->free;
+ xprt->free = req;
+
+ xprt_clear_backlog(xprt);
+ spin_unlock(&xprt->xprt_lock);
+}
+
+/*
+ * Set default timeout parameters
+ */
+void
+xprt_default_timeout(struct rpc_timeout *to, int proto)
+{
+ if (proto == IPPROTO_UDP)
+ xprt_set_timeout(to, 5, 5 * HZ);
+ else
+ xprt_set_timeout(to, 5, 60 * HZ);
+}
+
+/*
+ * Set constant timeout
+ */
+void
+xprt_set_timeout(struct rpc_timeout *to, unsigned int retr, unsigned long incr)
+{
+ to->to_current =
+ to->to_initval =
+ to->to_increment = incr;
+ to->to_maxval = incr * retr;
+ to->to_retries = retr;
+ to->to_exponential = 0;
+}
+
+/*
+ * Initialize an RPC client
+ */
+static struct rpc_xprt *
+xprt_setup(int proto, struct sockaddr_in *ap, struct rpc_timeout *to)
+{
+ struct rpc_xprt *xprt;
+ struct rpc_rqst *req;
+ int i;
+
+ dprintk("RPC: setting up %s transport...\n",
+ proto == IPPROTO_UDP? "UDP" : "TCP");
+
+ if ((xprt = kmalloc(sizeof(struct rpc_xprt), GFP_KERNEL)) == NULL)
+ return NULL;
+ memset(xprt, 0, sizeof(*xprt)); /* Nnnngh! */
+
+ xprt->addr = *ap;
+ xprt->prot = proto;
+ xprt->stream = (proto == IPPROTO_TCP)? 1 : 0;
+ if (xprt->stream) {
+ xprt->cwnd = RPC_MAXCWND;
+ xprt->nocong = 1;
+ } else
+ xprt->cwnd = RPC_INITCWND;
+ spin_lock_init(&xprt->sock_lock);
+ spin_lock_init(&xprt->xprt_lock);
+ init_waitqueue_head(&xprt->cong_wait);
+
+ INIT_LIST_HEAD(&xprt->recv);
+
+ /* Set timeout parameters */
+ if (to) {
+ xprt->timeout = *to;
+ xprt->timeout.to_current = to->to_initval;
+ } else
+ xprt_default_timeout(&xprt->timeout, xprt->prot);
+
+ INIT_RPC_WAITQ(&xprt->pending, "xprt_pending");
+ INIT_RPC_WAITQ(&xprt->sending, "xprt_sending");
+ INIT_RPC_WAITQ(&xprt->resend, "xprt_resend");
+ INIT_RPC_WAITQ(&xprt->backlog, "xprt_backlog");
+
+ /* initialize free list */
+ for (i = 0, req = xprt->slot; i < RPC_MAXREQS-1; i++, req++)
+ req->rq_next = req + 1;
+ req->rq_next = NULL;
+ xprt->free = xprt->slot;
+
+ /* Check whether we want to use a reserved port */
+ xprt->resvport = capable(CAP_NET_BIND_SERVICE) ? 1 : 0;
+
+ dprintk("RPC: created transport %p\n", xprt);
+
+ return xprt;
+}
+
+/*
+ * Bind to a reserved port
+ */
+static inline int
+xprt_bindresvport(struct socket *sock)
+{
+ struct sockaddr_in myaddr;
+ int err, port;
+ kernel_cap_t saved_cap = current->cap_effective;
+
+ /* Override capabilities.
+ * They were checked in xprt_create_proto i.e. at mount time
+ */
+ cap_raise (current->cap_effective, CAP_NET_BIND_SERVICE);
+
+ memset(&myaddr, 0, sizeof(myaddr));
+ myaddr.sin_family = AF_INET;
+ port = 800;
+ do {
+ myaddr.sin_port = htons(port);
+ err = sock->ops->bind(sock, (struct sockaddr *) &myaddr,
+ sizeof(myaddr));
+ } while (err == -EADDRINUSE && --port > 0);
+ current->cap_effective = saved_cap;
+
+ if (err < 0)
+ printk("RPC: Can't bind to reserved port (%d).\n", -err);
+
+ return err;
+}
+
+static int
+xprt_bind_socket(struct rpc_xprt *xprt, struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ if (xprt->inet)
+ return -EBUSY;
+
+ write_lock_bh(&sk->callback_lock);
+ sk->user_data = xprt;
+ xprt->old_data_ready = sk->data_ready;
+ xprt->old_state_change = sk->state_change;
+ xprt->old_write_space = sk->write_space;
+ if (xprt->prot == IPPROTO_UDP) {
+ sk->data_ready = udp_data_ready;
+ sk->no_check = UDP_CSUM_NORCV;
+ xprt_set_connected(xprt);
+ } else {
+ struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);
+ tp->nonagle = 1; /* disable Nagle's algorithm */
+ sk->data_ready = tcp_data_ready;
+ sk->state_change = tcp_state_change;
+ xprt_clear_connected(xprt);
+ }
+ sk->write_space = xprt_write_space;
+
+ /* Reset to new socket */
+ xprt->sock = sock;
+ xprt->inet = sk;
+ write_unlock_bh(&sk->callback_lock);
+
+ return 0;
+}
+
+/*
+ * Set socket buffer length
+ */
+void
+xprt_sock_setbufsize(struct rpc_xprt *xprt)
+{
+ struct sock *sk = xprt->inet;
+
+ if (xprt->stream)
+ return;
+ if (xprt->rcvsize) {
+ sk->userlocks |= SOCK_RCVBUF_LOCK;
+ sk->rcvbuf = xprt->rcvsize * RPC_MAXCONG * 2;
+ }
+ if (xprt->sndsize) {
+ sk->userlocks |= SOCK_SNDBUF_LOCK;
+ sk->sndbuf = xprt->sndsize * RPC_MAXCONG * 2;
+ sk->write_space(sk);
+ }
+}
+
+/*
+ * Create a client socket given the protocol and peer address.
+ */
+static struct socket *
+xprt_create_socket(int proto, struct rpc_timeout *to, int resvport)
+{
+ struct socket *sock;
+ int type, err;
+
+ dprintk("RPC: xprt_create_socket(%s %d)\n",
+ (proto == IPPROTO_UDP)? "udp" : "tcp", proto);
+
+ type = (proto == IPPROTO_UDP)? SOCK_DGRAM : SOCK_STREAM;
+
+ if ((err = sock_create(PF_INET, type, proto, &sock)) < 0) {
+ printk("RPC: can't create socket (%d).\n", -err);
+ goto failed;
+ }
+
+ /* bind to a reserved port */
+ if (resvport && xprt_bindresvport(sock) < 0)
+ goto failed;
+
+ return sock;
+
+failed:
+ sock_release(sock);
+ return NULL;
+}
+
+/*
+ * Create an RPC client transport given the protocol and peer address.
+ */
+struct rpc_xprt *
+xprt_create_proto(int proto, struct sockaddr_in *sap, struct rpc_timeout *to)
+{
+ struct rpc_xprt *xprt;
+
+ xprt = xprt_setup(proto, sap, to);
+ if (!xprt)
+ goto out_bad;
+
+ dprintk("RPC: xprt_create_proto created xprt %p\n", xprt);
+ return xprt;
+out_bad:
+ dprintk("RPC: xprt_create_proto failed\n");
+ if (xprt)
+ kfree(xprt);
+ return NULL;
+}
+
+/*
+ * Prepare for transport shutdown.
+ */
+void
+xprt_shutdown(struct rpc_xprt *xprt)
+{
+ xprt->shutdown = 1;
+ rpc_wake_up(&xprt->sending);
+ rpc_wake_up(&xprt->resend);
+ rpc_wake_up(&xprt->pending);
+ rpc_wake_up(&xprt->backlog);
+ if (waitqueue_active(&xprt->cong_wait))
+ wake_up(&xprt->cong_wait);
+}
+
+/*
+ * Clear the xprt backlog queue
+ */
+int
+xprt_clear_backlog(struct rpc_xprt *xprt) {
+ rpc_wake_up_next(&xprt->backlog);
+ if (waitqueue_active(&xprt->cong_wait))
+ wake_up(&xprt->cong_wait);
+ return 1;
+}
+
+/*
+ * Destroy an RPC transport, killing off all requests.
+ */
+int
+xprt_destroy(struct rpc_xprt *xprt)
+{
+ dprintk("RPC: destroying transport %p\n", xprt);
+ xprt_shutdown(xprt);
+ xprt_close(xprt);
+ kfree(xprt);
+
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/sysctl_net.c b/uClinux-2.4.31-uc0/net/sysctl_net.c
new file mode 100644
index 0000000..63b5db7
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/sysctl_net.c
@@ -0,0 +1,53 @@
+/* -*- linux-c -*-
+ * sysctl_net.c: sysctl interface to net subsystem.
+ *
+ * Begun April 1, 1996, Mike Shaver.
+ * Added /proc/sys/net directories for each protocol family. [MS]
+ *
+ * $Log: sysctl_net.c,v $
+ * Revision 1.2 1996/05/08 20:24:40 shaver
+ * Added bits for NET_BRIDGE and the NET_IPV4_ARP stuff and
+ * NET_IPV4_IP_FORWARD.
+ *
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+
+#ifdef CONFIG_INET
+extern ctl_table ipv4_table[];
+#endif
+
+extern ctl_table core_table[];
+
+#ifdef CONFIG_NET
+extern ctl_table ether_table[], e802_table[];
+#endif
+
+#ifdef CONFIG_IPV6
+extern ctl_table ipv6_table[];
+#endif
+
+#ifdef CONFIG_TR
+extern ctl_table tr_table[];
+#endif
+
+ctl_table net_table[] = {
+ {NET_CORE, "core", NULL, 0, 0555, core_table},
+#ifdef CONFIG_NET
+ {NET_802, "802", NULL, 0, 0555, e802_table},
+ {NET_ETHER, "ethernet", NULL, 0, 0555, ether_table},
+#endif
+#ifdef CONFIG_INET
+ {NET_IPV4, "ipv4", NULL, 0, 0555, ipv4_table},
+#endif
+#ifdef CONFIG_IPV6
+ {NET_IPV6, "ipv6", NULL, 0, 0555, ipv6_table},
+#endif
+#ifdef CONFIG_TR
+ {NET_TR, "token-ring", NULL, 0, 0555, tr_table},
+#endif
+ {0}
+};
diff --git a/uClinux-2.4.31-uc0/net/unix/Makefile b/uClinux-2.4.31-uc0/net/unix/Makefile
new file mode 100644
index 0000000..9a840af
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/unix/Makefile
@@ -0,0 +1,18 @@
+#
+# Makefile for the Linux unix domain socket layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := unix.o
+
+obj-y := af_unix.o garbage.o
+obj-m := $(O_TARGET)
+
+obj-$(CONFIG_SYSCTL) += sysctl_net_unix.o
+
+include $(TOPDIR)/Rules.make
+
diff --git a/uClinux-2.4.31-uc0/net/unix/af_unix.c b/uClinux-2.4.31-uc0/net/unix/af_unix.c
new file mode 100644
index 0000000..cb967a5
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/unix/af_unix.c
@@ -0,0 +1,1892 @@
+/*
+ * NET4: Implementation of BSD Unix domain sockets.
+ *
+ * Authors: Alan Cox, <alan.cox@linux.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Version: $Id: af_unix.c,v 1.126.2.5 2002/03/05 12:47:34 davem Exp $
+ *
+ * Fixes:
+ * Linus Torvalds : Assorted bug cures.
+ * Niibe Yutaka : async I/O support.
+ * Carsten Paeth : PF_UNIX check, address fixes.
+ * Alan Cox : Limit size of allocated blocks.
+ * Alan Cox : Fixed the stupid socketpair bug.
+ * Alan Cox : BSD compatibility fine tuning.
+ * Alan Cox : Fixed a bug in connect when interrupted.
+ * Alan Cox : Sorted out a proper draft version of
+ * file descriptor passing hacked up from
+ * Mike Shaver's work.
+ * Marty Leisner : Fixes to fd passing
+ * Nick Nevin : recvmsg bugfix.
+ * Alan Cox : Started proper garbage collector
+ * Heiko EiBfeldt : Missing verify_area check
+ * Alan Cox : Started POSIXisms
+ * Andreas Schwab : Replace inode by dentry for proper
+ * reference counting
+ * Kirk Petersen : Made this a module
+ * Christoph Rohland : Elegant non-blocking accept/connect algorithm.
+ * Lots of bug fixes.
+ * Alexey Kuznetosv : Repaired (I hope) bugs introduces
+ * by above two patches.
+ * Andrea Arcangeli : If possible we block in connect(2)
+ * if the max backlog of the listen socket
+ * is been reached. This won't break
+ * old apps and it will avoid huge amount
+ * of socks hashed (this for unix_gc()
+ * performances reasons).
+ * Security fix that limits the max
+ * number of socks to 2*max_files and
+ * the number of skb queueable in the
+ * dgram receiver.
+ * Artur Skawina : Hash function optimizations
+ * Alexey Kuznetsov : Full scale SMP. Lot of bugs are introduced 8)
+ * Malcolm Beattie : Set peercred for socketpair
+ * Michal Ostrowski : Module initialization cleanup.
+ *
+ *
+ * Known differences from reference BSD that was tested:
+ *
+ * [TO FIX]
+ * ECONNREFUSED is not returned from one end of a connected() socket to the
+ * other the moment one end closes.
+ * fstat() doesn't return st_dev=NODEV, and give the blksize as high water mark
+ * and a fake inode identifier (nor the BSD first socket fstat twice bug).
+ * [NOT TO FIX]
+ * accept() returns a path name even if the connecting socket has closed
+ * in the meantime (BSD loses the path and gives up).
+ * accept() returns 0 length path for an unbound connector. BSD returns 16
+ * and a null first byte in the path (but not for gethost/peername - BSD bug ??)
+ * socketpair(...SOCK_RAW..) doesn't panic the kernel.
+ * BSD af_unix apparently has connect forgetting to block properly.
+ * (need to check this with the POSIX spec in detail)
+ *
+ * Differences from 2.0.0-11-... (ANK)
+ * Bug fixes and improvements.
+ * - client shutdown killed server socket.
+ * - removed all useless cli/sti pairs.
+ *
+ * Semantic changes/extensions.
+ * - generic control message passing.
+ * - SCM_CREDENTIALS control message.
+ * - "Abstract" (not FS based) socket bindings.
+ * Abstract names are sequences of bytes (not zero terminated)
+ * started by 0, so that this name space does not intersect
+ * with BSD names.
+ */
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/major.h>
+#include <linux/signal.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/stat.h>
+#include <linux/socket.h>
+#include <linux/un.h>
+#include <linux/fcntl.h>
+#include <linux/termios.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/in.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <asm/uaccess.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <net/sock.h>
+#include <linux/tcp.h>
+#include <net/af_unix.h>
+#include <linux/proc_fs.h>
+#include <net/scm.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/smp_lock.h>
+#include <linux/rtnetlink.h>
+
+#include <asm/checksum.h>
+
+int sysctl_unix_max_dgram_qlen = 10;
+
+unix_socket *unix_socket_table[UNIX_HASH_SIZE+1];
+rwlock_t unix_table_lock = RW_LOCK_UNLOCKED;
+static atomic_t unix_nr_socks = ATOMIC_INIT(0);
+
+#define unix_sockets_unbound (unix_socket_table[UNIX_HASH_SIZE])
+
+#define UNIX_ABSTRACT(sk) ((sk)->protinfo.af_unix.addr->hash!=UNIX_HASH_SIZE)
+
+/*
+ * SMP locking strategy:
+ * hash table is protected with rwlock unix_table_lock
+ * each socket state is protected by separate rwlock.
+ */
+
+static inline unsigned unix_hash_fold(unsigned hash)
+{
+ hash ^= hash>>16;
+ hash ^= hash>>8;
+ return hash&(UNIX_HASH_SIZE-1);
+}
+
+#define unix_peer(sk) ((sk)->pair)
+
+static inline int unix_our_peer(unix_socket *sk, unix_socket *osk)
+{
+ return unix_peer(osk) == sk;
+}
+
+static inline int unix_may_send(unix_socket *sk, unix_socket *osk)
+{
+ return (unix_peer(osk) == NULL || unix_our_peer(sk, osk));
+}
+
+static inline unix_socket * unix_peer_get(unix_socket *s)
+{
+ unix_socket *peer;
+
+ unix_state_rlock(s);
+ peer = unix_peer(s);
+ if (peer)
+ sock_hold(peer);
+ unix_state_runlock(s);
+ return peer;
+}
+
+extern inline void unix_release_addr(struct unix_address *addr)
+{
+ if (atomic_dec_and_test(&addr->refcnt))
+ kfree(addr);
+}
+
+/*
+ * Check unix socket name:
+ * - should be not zero length.
+ * - if started by not zero, should be NULL terminated (FS object)
+ * - if started by zero, it is abstract name.
+ */
+
+static int unix_mkname(struct sockaddr_un * sunaddr, int len, unsigned *hashp)
+{
+ if (len <= sizeof(short) || len > sizeof(*sunaddr))
+ return -EINVAL;
+ if (!sunaddr || sunaddr->sun_family != AF_UNIX)
+ return -EINVAL;
+ if (sunaddr->sun_path[0]) {
+ ((char *)sunaddr)[len]=0;
+ len = strlen(sunaddr->sun_path)+1+sizeof(short);
+ return len;
+ }
+
+ *hashp = unix_hash_fold(csum_partial((char*)sunaddr, len, 0));
+ return len;
+}
+
+static void __unix_remove_socket(unix_socket *sk)
+{
+ unix_socket **list = sk->protinfo.af_unix.list;
+ if (list) {
+ if (sk->next)
+ sk->next->prev = sk->prev;
+ if (sk->prev)
+ sk->prev->next = sk->next;
+ if (*list == sk)
+ *list = sk->next;
+ sk->protinfo.af_unix.list = NULL;
+ sk->prev = NULL;
+ sk->next = NULL;
+ __sock_put(sk);
+ }
+}
+
+static void __unix_insert_socket(unix_socket **list, unix_socket *sk)
+{
+ BUG_TRAP(sk->protinfo.af_unix.list==NULL);
+
+ sk->protinfo.af_unix.list = list;
+ sk->prev = NULL;
+ sk->next = *list;
+ if (*list)
+ (*list)->prev = sk;
+ *list=sk;
+ sock_hold(sk);
+}
+
+static inline void unix_remove_socket(unix_socket *sk)
+{
+ write_lock(&unix_table_lock);
+ __unix_remove_socket(sk);
+ write_unlock(&unix_table_lock);
+}
+
+static inline void unix_insert_socket(unix_socket **list, unix_socket *sk)
+{
+ write_lock(&unix_table_lock);
+ __unix_insert_socket(list, sk);
+ write_unlock(&unix_table_lock);
+}
+
+static unix_socket *__unix_find_socket_byname(struct sockaddr_un *sunname,
+ int len, int type, unsigned hash)
+{
+ unix_socket *s;
+
+ for (s=unix_socket_table[hash^type]; s; s=s->next) {
+ if(s->protinfo.af_unix.addr->len==len &&
+ memcmp(s->protinfo.af_unix.addr->name, sunname, len) == 0)
+ return s;
+ }
+ return NULL;
+}
+
+static inline unix_socket *
+unix_find_socket_byname(struct sockaddr_un *sunname,
+ int len, int type, unsigned hash)
+{
+ unix_socket *s;
+
+ read_lock(&unix_table_lock);
+ s = __unix_find_socket_byname(sunname, len, type, hash);
+ if (s)
+ sock_hold(s);
+ read_unlock(&unix_table_lock);
+ return s;
+}
+
+static unix_socket *unix_find_socket_byinode(struct inode *i)
+{
+ unix_socket *s;
+
+ read_lock(&unix_table_lock);
+ for (s=unix_socket_table[i->i_ino & (UNIX_HASH_SIZE-1)]; s; s=s->next)
+ {
+ struct dentry *dentry = s->protinfo.af_unix.dentry;
+
+ if(dentry && dentry->d_inode == i)
+ {
+ sock_hold(s);
+ break;
+ }
+ }
+ read_unlock(&unix_table_lock);
+ return s;
+}
+
+static inline int unix_writable(struct sock *sk)
+{
+ return ((atomic_read(&sk->wmem_alloc)<<2) <= sk->sndbuf);
+}
+
+static void unix_write_space(struct sock *sk)
+{
+ read_lock(&sk->callback_lock);
+ if (unix_writable(sk)) {
+ if (sk->sleep && waitqueue_active(sk->sleep))
+ wake_up_interruptible(sk->sleep);
+ sk_wake_async(sk, 2, POLL_OUT);
+ }
+ read_unlock(&sk->callback_lock);
+}
+
+/* When dgram socket disconnects (or changes its peer), we clear its receive
+ * queue of packets arrived from previous peer. First, it allows to do
+ * flow control based only on wmem_alloc; second, sk connected to peer
+ * may receive messages only from that peer. */
+static void unix_dgram_disconnected(struct sock *sk, struct sock *other)
+{
+ if (skb_queue_len(&sk->receive_queue)) {
+ skb_queue_purge(&sk->receive_queue);
+ wake_up_interruptible_all(&sk->protinfo.af_unix.peer_wait);
+
+ /* If one link of bidirectional dgram pipe is disconnected,
+ * we signal error. Messages are lost. Do not make this,
+ * when peer was not connected to us.
+ */
+ if (!other->dead && unix_peer(other) == sk) {
+ other->err = ECONNRESET;
+ other->error_report(other);
+ }
+ }
+}
+
+static void unix_sock_destructor(struct sock *sk)
+{
+ skb_queue_purge(&sk->receive_queue);
+
+ BUG_TRAP(atomic_read(&sk->wmem_alloc) == 0);
+ BUG_TRAP(sk->protinfo.af_unix.list==NULL);
+ BUG_TRAP(sk->socket==NULL);
+ if (sk->dead==0) {
+ printk("Attempt to release alive unix socket: %p\n", sk);
+ return;
+ }
+
+ if (sk->protinfo.af_unix.addr)
+ unix_release_addr(sk->protinfo.af_unix.addr);
+
+ atomic_dec(&unix_nr_socks);
+#ifdef UNIX_REFCNT_DEBUG
+ printk(KERN_DEBUG "UNIX %p is destroyed, %d are still alive.\n", sk, atomic_read(&unix_nr_socks));
+#endif
+ MOD_DEC_USE_COUNT;
+}
+
+static int unix_release_sock (unix_socket *sk, int embrion)
+{
+ struct dentry *dentry;
+ struct vfsmount *mnt;
+ unix_socket *skpair;
+ struct sk_buff *skb;
+ int state;
+
+ unix_remove_socket(sk);
+
+ /* Clear state */
+ unix_state_wlock(sk);
+ sock_orphan(sk);
+ sk->shutdown = SHUTDOWN_MASK;
+ dentry = sk->protinfo.af_unix.dentry;
+ sk->protinfo.af_unix.dentry=NULL;
+ mnt = sk->protinfo.af_unix.mnt;
+ sk->protinfo.af_unix.mnt=NULL;
+ state = sk->state;
+ sk->state = TCP_CLOSE;
+ unix_state_wunlock(sk);
+
+ wake_up_interruptible_all(&sk->protinfo.af_unix.peer_wait);
+
+ skpair=unix_peer(sk);
+
+ if (skpair!=NULL) {
+ if (sk->type==SOCK_STREAM) {
+ unix_state_wlock(skpair);
+ skpair->shutdown=SHUTDOWN_MASK; /* No more writes*/
+ if (!skb_queue_empty(&sk->receive_queue) || embrion)
+ skpair->err = ECONNRESET;
+ unix_state_wunlock(skpair);
+ skpair->state_change(skpair);
+ read_lock(&skpair->callback_lock);
+ sk_wake_async(skpair,1,POLL_HUP);
+ read_unlock(&skpair->callback_lock);
+ }
+ sock_put(skpair); /* It may now die */
+ unix_peer(sk) = NULL;
+ }
+
+ /* Try to flush out this socket. Throw out buffers at least */
+
+ while((skb=skb_dequeue(&sk->receive_queue))!=NULL)
+ {
+ if (state==TCP_LISTEN)
+ unix_release_sock(skb->sk, 1);
+ /* passed fds are erased in the kfree_skb hook */
+ kfree_skb(skb);
+ }
+
+ if (dentry) {
+ dput(dentry);
+ mntput(mnt);
+ }
+
+ sock_put(sk);
+
+ /* ---- Socket is dead now and most probably destroyed ---- */
+
+ /*
+ * Fixme: BSD difference: In BSD all sockets connected to use get
+ * ECONNRESET and we die on the spot. In Linux we behave
+ * like files and pipes do and wait for the last
+ * dereference.
+ *
+ * Can't we simply set sock->err?
+ *
+ * What the above comment does talk about? --ANK(980817)
+ */
+
+ if (atomic_read(&unix_tot_inflight))
+ unix_gc(); /* Garbage collect fds */
+
+ return 0;
+}
+
+static int unix_listen(struct socket *sock, int backlog)
+{
+ int err;
+ struct sock *sk = sock->sk;
+
+ err = -EOPNOTSUPP;
+ if (sock->type!=SOCK_STREAM)
+ goto out; /* Only stream sockets accept */
+ err = -EINVAL;
+ if (!sk->protinfo.af_unix.addr)
+ goto out; /* No listens on an unbound socket */
+ unix_state_wlock(sk);
+ if (sk->state != TCP_CLOSE && sk->state != TCP_LISTEN)
+ goto out_unlock;
+ if (backlog > sk->max_ack_backlog)
+ wake_up_interruptible_all(&sk->protinfo.af_unix.peer_wait);
+ sk->max_ack_backlog=backlog;
+ sk->state=TCP_LISTEN;
+ /* set credentials so connect can copy them */
+ sk->peercred.pid = current->pid;
+ sk->peercred.uid = current->euid;
+ sk->peercred.gid = current->egid;
+ err = 0;
+
+out_unlock:
+ unix_state_wunlock(sk);
+out:
+ return err;
+}
+
+extern struct proto_ops unix_stream_ops;
+extern struct proto_ops unix_dgram_ops;
+
+static struct sock * unix_create1(struct socket *sock)
+{
+ struct sock *sk;
+
+ if (atomic_read(&unix_nr_socks) >= 2*files_stat.max_files)
+ return NULL;
+
+ MOD_INC_USE_COUNT;
+ sk = sk_alloc(PF_UNIX, GFP_KERNEL, 1);
+ if (!sk) {
+ MOD_DEC_USE_COUNT;
+ return NULL;
+ }
+
+ atomic_inc(&unix_nr_socks);
+
+ sock_init_data(sock,sk);
+
+ sk->write_space = unix_write_space;
+
+ sk->max_ack_backlog = sysctl_unix_max_dgram_qlen;
+ sk->destruct = unix_sock_destructor;
+ sk->protinfo.af_unix.dentry=NULL;
+ sk->protinfo.af_unix.mnt=NULL;
+ sk->protinfo.af_unix.lock = RW_LOCK_UNLOCKED;
+ atomic_set(&sk->protinfo.af_unix.inflight, sock ? 0 : -1);
+ init_MUTEX(&sk->protinfo.af_unix.readsem);/* single task reading lock */
+ init_waitqueue_head(&sk->protinfo.af_unix.peer_wait);
+ sk->protinfo.af_unix.list=NULL;
+ unix_insert_socket(&unix_sockets_unbound, sk);
+
+ return sk;
+}
+
+static int unix_create(struct socket *sock, int protocol)
+{
+ if (protocol && protocol != PF_UNIX)
+ return -EPROTONOSUPPORT;
+
+ sock->state = SS_UNCONNECTED;
+
+ switch (sock->type) {
+ case SOCK_STREAM:
+ sock->ops = &unix_stream_ops;
+ break;
+ /*
+ * Believe it or not BSD has AF_UNIX, SOCK_RAW though
+ * nothing uses it.
+ */
+ case SOCK_RAW:
+ sock->type=SOCK_DGRAM;
+ case SOCK_DGRAM:
+ sock->ops = &unix_dgram_ops;
+ break;
+ default:
+ return -ESOCKTNOSUPPORT;
+ }
+
+ return unix_create1(sock) ? 0 : -ENOMEM;
+}
+
+static int unix_release(struct socket *sock)
+{
+ unix_socket *sk = sock->sk;
+
+ if (!sk)
+ return 0;
+
+ sock->sk = NULL;
+
+ return unix_release_sock (sk, 0);
+}
+
+static int unix_autobind(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+ static u32 ordernum = 1;
+ struct unix_address * addr;
+ int err;
+
+ down(&sk->protinfo.af_unix.readsem);
+
+ err = 0;
+ if (sk->protinfo.af_unix.addr)
+ goto out;
+
+ err = -ENOMEM;
+ addr = kmalloc(sizeof(*addr) + sizeof(short) + 16, GFP_KERNEL);
+ if (!addr)
+ goto out;
+
+ memset(addr, 0, sizeof(*addr) + sizeof(short) + 16);
+ addr->name->sun_family = AF_UNIX;
+ atomic_set(&addr->refcnt, 1);
+
+retry:
+ addr->len = sprintf(addr->name->sun_path+1, "%05x", ordernum) + 1 + sizeof(short);
+ addr->hash = unix_hash_fold(csum_partial((void*)addr->name, addr->len, 0));
+
+ write_lock(&unix_table_lock);
+ ordernum = (ordernum+1)&0xFFFFF;
+
+ if (__unix_find_socket_byname(addr->name, addr->len, sock->type,
+ addr->hash)) {
+ write_unlock(&unix_table_lock);
+ /* Sanity yield. It is unusual case, but yet... */
+ if (!(ordernum&0xFF))
+ yield();
+ goto retry;
+ }
+ addr->hash ^= sk->type;
+
+ __unix_remove_socket(sk);
+ sk->protinfo.af_unix.addr = addr;
+ __unix_insert_socket(&unix_socket_table[addr->hash], sk);
+ write_unlock(&unix_table_lock);
+ err = 0;
+
+out:
+ up(&sk->protinfo.af_unix.readsem);
+ return err;
+}
+
+static unix_socket *unix_find_other(struct sockaddr_un *sunname, int len,
+ int type, unsigned hash, int *error)
+{
+ unix_socket *u;
+ struct nameidata nd;
+ int err = 0;
+
+ if (sunname->sun_path[0]) {
+ if (path_init(sunname->sun_path,
+ LOOKUP_POSITIVE|LOOKUP_FOLLOW, &nd))
+ err = path_walk(sunname->sun_path, &nd);
+ if (err)
+ goto fail;
+ err = permission(nd.dentry->d_inode,MAY_WRITE);
+ if (err)
+ goto put_fail;
+
+ err = -ECONNREFUSED;
+ if (!S_ISSOCK(nd.dentry->d_inode->i_mode))
+ goto put_fail;
+ u=unix_find_socket_byinode(nd.dentry->d_inode);
+ if (!u)
+ goto put_fail;
+
+ if (u->type == type)
+ UPDATE_ATIME(nd.dentry->d_inode);
+
+ path_release(&nd);
+
+ err=-EPROTOTYPE;
+ if (u->type != type) {
+ sock_put(u);
+ goto fail;
+ }
+ } else {
+ err = -ECONNREFUSED;
+ u=unix_find_socket_byname(sunname, len, type, hash);
+ if (u) {
+ struct dentry *dentry;
+ dentry = u->protinfo.af_unix.dentry;
+ if (dentry)
+ UPDATE_ATIME(dentry->d_inode);
+ } else
+ goto fail;
+ }
+ return u;
+
+put_fail:
+ path_release(&nd);
+fail:
+ *error=err;
+ return NULL;
+}
+
+
+static int unix_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_un *sunaddr=(struct sockaddr_un *)uaddr;
+ struct dentry * dentry = NULL;
+ struct nameidata nd;
+ int err;
+ unsigned hash;
+ struct unix_address *addr;
+ unix_socket **list;
+
+ err = -EINVAL;
+ if (sunaddr->sun_family != AF_UNIX)
+ goto out;
+
+ if (addr_len==sizeof(short)) {
+ err = unix_autobind(sock);
+ goto out;
+ }
+
+ err = unix_mkname(sunaddr, addr_len, &hash);
+ if (err < 0)
+ goto out;
+ addr_len = err;
+
+ down(&sk->protinfo.af_unix.readsem);
+
+ err = -EINVAL;
+ if (sk->protinfo.af_unix.addr)
+ goto out_up;
+
+ err = -ENOMEM;
+ addr = kmalloc(sizeof(*addr)+addr_len, GFP_KERNEL);
+ if (!addr)
+ goto out_up;
+
+ memcpy(addr->name, sunaddr, addr_len);
+ addr->len = addr_len;
+ addr->hash = hash^sk->type;
+ atomic_set(&addr->refcnt, 1);
+
+ if (sunaddr->sun_path[0]) {
+ unsigned int mode;
+ err = 0;
+ /*
+ * Get the parent directory, calculate the hash for last
+ * component.
+ */
+ if (path_init(sunaddr->sun_path, LOOKUP_PARENT, &nd))
+ err = path_walk(sunaddr->sun_path, &nd);
+ if (err)
+ goto out_mknod_parent;
+ /*
+ * Yucky last component or no last component at all?
+ * (foo/., foo/.., /////)
+ */
+ err = -EEXIST;
+ if (nd.last_type != LAST_NORM)
+ goto out_mknod;
+ /*
+ * Lock the directory.
+ */
+ down(&nd.dentry->d_inode->i_sem);
+ /*
+ * Do the final lookup.
+ */
+ dentry = lookup_hash(&nd.last, nd.dentry);
+ err = PTR_ERR(dentry);
+ if (IS_ERR(dentry))
+ goto out_mknod_unlock;
+ err = -ENOENT;
+ /*
+ * Special case - lookup gave negative, but... we had foo/bar/
+ * From the vfs_mknod() POV we just have a negative dentry -
+ * all is fine. Let's be bastards - you had / on the end, you've
+ * been asking for (non-existent) directory. -ENOENT for you.
+ */
+ if (nd.last.name[nd.last.len] && !dentry->d_inode)
+ goto out_mknod_dput;
+ /*
+ * All right, let's create it.
+ */
+ mode = S_IFSOCK | (sock->inode->i_mode & ~current->fs->umask);
+ err = vfs_mknod(nd.dentry->d_inode, dentry, mode, 0);
+ if (err)
+ goto out_mknod_dput;
+ up(&nd.dentry->d_inode->i_sem);
+ dput(nd.dentry);
+ nd.dentry = dentry;
+
+ addr->hash = UNIX_HASH_SIZE;
+ }
+
+ write_lock(&unix_table_lock);
+
+ if (!sunaddr->sun_path[0]) {
+ err = -EADDRINUSE;
+ if (__unix_find_socket_byname(sunaddr, addr_len,
+ sk->type, hash)) {
+ unix_release_addr(addr);
+ goto out_unlock;
+ }
+
+ list = &unix_socket_table[addr->hash];
+ } else {
+ list = &unix_socket_table[dentry->d_inode->i_ino & (UNIX_HASH_SIZE-1)];
+ sk->protinfo.af_unix.dentry = nd.dentry;
+ sk->protinfo.af_unix.mnt = nd.mnt;
+ }
+
+ err = 0;
+ __unix_remove_socket(sk);
+ sk->protinfo.af_unix.addr = addr;
+ __unix_insert_socket(list, sk);
+
+out_unlock:
+ write_unlock(&unix_table_lock);
+out_up:
+ up(&sk->protinfo.af_unix.readsem);
+out:
+ return err;
+
+out_mknod_dput:
+ dput(dentry);
+out_mknod_unlock:
+ up(&nd.dentry->d_inode->i_sem);
+out_mknod:
+ path_release(&nd);
+out_mknod_parent:
+ if (err==-EEXIST)
+ err=-EADDRINUSE;
+ unix_release_addr(addr);
+ goto out_up;
+}
+
+static int unix_dgram_connect(struct socket *sock, struct sockaddr *addr,
+ int alen, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_un *sunaddr=(struct sockaddr_un*)addr;
+ struct sock *other;
+ unsigned hash;
+ int err;
+
+ if (addr->sa_family != AF_UNSPEC) {
+ err = unix_mkname(sunaddr, alen, &hash);
+ if (err < 0)
+ goto out;
+ alen = err;
+
+ if (sock->passcred && !sk->protinfo.af_unix.addr &&
+ (err = unix_autobind(sock)) != 0)
+ goto out;
+
+ other=unix_find_other(sunaddr, alen, sock->type, hash, &err);
+ if (!other)
+ goto out;
+
+ unix_state_wlock(sk);
+
+ err = -EPERM;
+ if (!unix_may_send(sk, other))
+ goto out_unlock;
+ } else {
+ /*
+ * 1003.1g breaking connected state with AF_UNSPEC
+ */
+ other = NULL;
+ unix_state_wlock(sk);
+ }
+
+ /*
+ * If it was connected, reconnect.
+ */
+ if (unix_peer(sk)) {
+ struct sock *old_peer = unix_peer(sk);
+ unix_peer(sk)=other;
+ unix_state_wunlock(sk);
+
+ if (other != old_peer)
+ unix_dgram_disconnected(sk, old_peer);
+ sock_put(old_peer);
+ } else {
+ unix_peer(sk)=other;
+ unix_state_wunlock(sk);
+ }
+ return 0;
+
+out_unlock:
+ unix_state_wunlock(sk);
+ sock_put(other);
+out:
+ return err;
+}
+
+static long unix_wait_for_peer(unix_socket *other, long timeo)
+{
+ int sched;
+ DECLARE_WAITQUEUE(wait, current);
+
+ __set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue_exclusive(&other->protinfo.af_unix.peer_wait, &wait);
+
+ sched = (!other->dead &&
+ !(other->shutdown&RCV_SHUTDOWN) &&
+ skb_queue_len(&other->receive_queue) > other->max_ack_backlog);
+
+ unix_state_runlock(other);
+
+ if (sched)
+ timeo = schedule_timeout(timeo);
+
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(&other->protinfo.af_unix.peer_wait, &wait);
+ return timeo;
+}
+
+static int unix_stream_connect(struct socket *sock, struct sockaddr *uaddr,
+ int addr_len, int flags)
+{
+ struct sockaddr_un *sunaddr=(struct sockaddr_un *)uaddr;
+ struct sock *sk = sock->sk;
+ struct sock *newsk = NULL;
+ unix_socket *other = NULL;
+ struct sk_buff *skb = NULL;
+ unsigned hash;
+ int st;
+ int err;
+ long timeo;
+
+ err = unix_mkname(sunaddr, addr_len, &hash);
+ if (err < 0)
+ goto out;
+ addr_len = err;
+
+ if (sock->passcred && !sk->protinfo.af_unix.addr &&
+ (err = unix_autobind(sock)) != 0)
+ goto out;
+
+ timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);
+
+ /* First of all allocate resources.
+ If we will make it after state is locked,
+ we will have to recheck all again in any case.
+ */
+
+ err = -ENOMEM;
+
+ /* create new sock for complete connection */
+ newsk = unix_create1(NULL);
+ if (newsk == NULL)
+ goto out;
+
+ /* Allocate skb for sending to listening sock */
+ skb = sock_wmalloc(newsk, 1, 0, GFP_KERNEL);
+ if (skb == NULL)
+ goto out;
+
+restart:
+ /* Find listening sock. */
+ other=unix_find_other(sunaddr, addr_len, sk->type, hash, &err);
+ if (!other)
+ goto out;
+
+ /* Latch state of peer */
+ unix_state_rlock(other);
+
+ /* Apparently VFS overslept socket death. Retry. */
+ if (other->dead) {
+ unix_state_runlock(other);
+ sock_put(other);
+ goto restart;
+ }
+
+ err = -ECONNREFUSED;
+ if (other->state != TCP_LISTEN)
+ goto out_unlock;
+
+ if (skb_queue_len(&other->receive_queue) > other->max_ack_backlog) {
+ err = -EAGAIN;
+ if (!timeo)
+ goto out_unlock;
+
+ timeo = unix_wait_for_peer(other, timeo);
+
+ err = sock_intr_errno(timeo);
+ if (signal_pending(current))
+ goto out;
+ sock_put(other);
+ goto restart;
+ }
+
+ /* Latch our state.
+
+ It is tricky place. We need to grab write lock and cannot
+ drop lock on peer. It is dangerous because deadlock is
+ possible. Connect to self case and simultaneous
+ attempt to connect are eliminated by checking socket
+ state. other is TCP_LISTEN, if sk is TCP_LISTEN we
+ check this before attempt to grab lock.
+
+ Well, and we have to recheck the state after socket locked.
+ */
+ st = sk->state;
+
+ switch (st) {
+ case TCP_CLOSE:
+ /* This is ok... continue with connect */
+ break;
+ case TCP_ESTABLISHED:
+ /* Socket is already connected */
+ err = -EISCONN;
+ goto out_unlock;
+ default:
+ err = -EINVAL;
+ goto out_unlock;
+ }
+
+ unix_state_wlock(sk);
+
+ if (sk->state != st) {
+ unix_state_wunlock(sk);
+ unix_state_runlock(other);
+ sock_put(other);
+ goto restart;
+ }
+
+ /* The way is open! Fastly set all the necessary fields... */
+
+ sock_hold(sk);
+ unix_peer(newsk)=sk;
+ newsk->state=TCP_ESTABLISHED;
+ newsk->type=SOCK_STREAM;
+ newsk->peercred.pid = current->pid;
+ newsk->peercred.uid = current->euid;
+ newsk->peercred.gid = current->egid;
+ newsk->sleep = &newsk->protinfo.af_unix.peer_wait;
+
+ /* copy address information from listening to new sock*/
+ if (other->protinfo.af_unix.addr)
+ {
+ atomic_inc(&other->protinfo.af_unix.addr->refcnt);
+ newsk->protinfo.af_unix.addr=other->protinfo.af_unix.addr;
+ }
+ if (other->protinfo.af_unix.dentry) {
+ newsk->protinfo.af_unix.dentry=dget(other->protinfo.af_unix.dentry);
+ newsk->protinfo.af_unix.mnt=mntget(other->protinfo.af_unix.mnt);
+ }
+
+ /* Set credentials */
+ sk->peercred = other->peercred;
+
+ sock_hold(newsk);
+ unix_peer(sk)=newsk;
+ sock->state=SS_CONNECTED;
+ sk->state=TCP_ESTABLISHED;
+
+ unix_state_wunlock(sk);
+
+ /* take ten and and send info to listening sock */
+ spin_lock(&other->receive_queue.lock);
+ __skb_queue_tail(&other->receive_queue,skb);
+ /* Undo artificially decreased inflight after embrion
+ * is installed to listening socket. */
+ atomic_inc(&newsk->protinfo.af_unix.inflight);
+ spin_unlock(&other->receive_queue.lock);
+ unix_state_runlock(other);
+ other->data_ready(other, 0);
+ sock_put(other);
+ return 0;
+
+out_unlock:
+ if (other)
+ unix_state_runlock(other);
+
+out:
+ if (skb)
+ kfree_skb(skb);
+ if (newsk)
+ unix_release_sock(newsk, 0);
+ if (other)
+ sock_put(other);
+ return err;
+}
+
+static int unix_socketpair(struct socket *socka, struct socket *sockb)
+{
+ struct sock *ska=socka->sk, *skb = sockb->sk;
+
+ /* Join our sockets back to back */
+ sock_hold(ska);
+ sock_hold(skb);
+ unix_peer(ska)=skb;
+ unix_peer(skb)=ska;
+ ska->peercred.pid = skb->peercred.pid = current->pid;
+ ska->peercred.uid = skb->peercred.uid = current->euid;
+ ska->peercred.gid = skb->peercred.gid = current->egid;
+
+ if (ska->type != SOCK_DGRAM)
+ {
+ ska->state=TCP_ESTABLISHED;
+ skb->state=TCP_ESTABLISHED;
+ socka->state=SS_CONNECTED;
+ sockb->state=SS_CONNECTED;
+ }
+ return 0;
+}
+
+static int unix_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+ unix_socket *sk = sock->sk;
+ unix_socket *tsk;
+ struct sk_buff *skb;
+ int err;
+
+ err = -EOPNOTSUPP;
+ if (sock->type!=SOCK_STREAM)
+ goto out;
+
+ err = -EINVAL;
+ if (sk->state!=TCP_LISTEN)
+ goto out;
+
+ /* If socket state is TCP_LISTEN it cannot change (for now...),
+ * so that no locks are necessary.
+ */
+
+ skb = skb_recv_datagram(sk, 0, flags&O_NONBLOCK, &err);
+ if (!skb) {
+ /* This means receive shutdown. */
+ if (err == 0)
+ err = -EINVAL;
+ goto out;
+ }
+
+ tsk = skb->sk;
+ skb_free_datagram(sk, skb);
+ wake_up_interruptible(&sk->protinfo.af_unix.peer_wait);
+
+ /* attach accepted sock to socket */
+ unix_state_wlock(tsk);
+ newsock->state = SS_CONNECTED;
+ sock_graft(tsk, newsock);
+ unix_state_wunlock(tsk);
+ return 0;
+
+out:
+ return err;
+}
+
+
+static int unix_getname(struct socket *sock, struct sockaddr *uaddr, int *uaddr_len, int peer)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_un *sunaddr=(struct sockaddr_un *)uaddr;
+ int err = 0;
+
+ if (peer) {
+ sk = unix_peer_get(sk);
+
+ err = -ENOTCONN;
+ if (!sk)
+ goto out;
+ err = 0;
+ } else {
+ sock_hold(sk);
+ }
+
+ unix_state_rlock(sk);
+ if (!sk->protinfo.af_unix.addr) {
+ sunaddr->sun_family = AF_UNIX;
+ sunaddr->sun_path[0] = 0;
+ *uaddr_len = sizeof(short);
+ } else {
+ struct unix_address *addr = sk->protinfo.af_unix.addr;
+
+ *uaddr_len = addr->len;
+ memcpy(sunaddr, addr->name, *uaddr_len);
+ }
+ unix_state_runlock(sk);
+ sock_put(sk);
+out:
+ return err;
+}
+
+static void unix_detach_fds(struct scm_cookie *scm, struct sk_buff *skb)
+{
+ int i;
+
+ scm->fp = UNIXCB(skb).fp;
+ skb->destructor = sock_wfree;
+ UNIXCB(skb).fp = NULL;
+
+ for (i=scm->fp->count-1; i>=0; i--)
+ unix_notinflight(scm->fp->fp[i]);
+}
+
+static void unix_destruct_fds(struct sk_buff *skb)
+{
+ struct scm_cookie scm;
+ memset(&scm, 0, sizeof(scm));
+ unix_detach_fds(&scm, skb);
+
+ /* Alas, it calls VFS */
+ /* So fscking what? fput() had been SMP-safe since the last Summer */
+ scm_destroy(&scm);
+ sock_wfree(skb);
+}
+
+static void unix_attach_fds(struct scm_cookie *scm, struct sk_buff *skb)
+{
+ int i;
+ for (i=scm->fp->count-1; i>=0; i--)
+ unix_inflight(scm->fp->fp[i]);
+ UNIXCB(skb).fp = scm->fp;
+ skb->destructor = unix_destruct_fds;
+ scm->fp = NULL;
+}
+
+/*
+ * Send AF_UNIX data.
+ */
+
+static int unix_dgram_sendmsg(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_un *sunaddr=msg->msg_name;
+ unix_socket *other = NULL;
+ int namelen = 0; /* fake GCC */
+ int err;
+ unsigned hash;
+ struct sk_buff *skb;
+ long timeo;
+
+ err = -EOPNOTSUPP;
+ if (msg->msg_flags&MSG_OOB)
+ goto out;
+
+ if (msg->msg_namelen) {
+ err = unix_mkname(sunaddr, msg->msg_namelen, &hash);
+ if (err < 0)
+ goto out;
+ namelen = err;
+ } else {
+ sunaddr = NULL;
+ err = -ENOTCONN;
+ other = unix_peer_get(sk);
+ if (!other)
+ goto out;
+ }
+
+ if (sock->passcred && !sk->protinfo.af_unix.addr &&
+ (err = unix_autobind(sock)) != 0)
+ goto out;
+
+ err = -EMSGSIZE;
+ if ((unsigned)len > sk->sndbuf - 32)
+ goto out;
+
+ skb = sock_alloc_send_skb(sk, len, msg->msg_flags&MSG_DONTWAIT, &err);
+ if (skb==NULL)
+ goto out;
+
+ memcpy(UNIXCREDS(skb), &scm->creds, sizeof(struct ucred));
+ if (scm->fp)
+ unix_attach_fds(scm, skb);
+
+ skb->h.raw = skb->data;
+ err = memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len);
+ if (err)
+ goto out_free;
+
+ timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT);
+
+restart:
+ if (!other) {
+ err = -ECONNRESET;
+ if (sunaddr == NULL)
+ goto out_free;
+
+ other = unix_find_other(sunaddr, namelen, sk->type, hash, &err);
+ if (other==NULL)
+ goto out_free;
+ }
+
+ unix_state_rlock(other);
+ err = -EPERM;
+ if (!unix_may_send(sk, other))
+ goto out_unlock;
+
+ if (other->dead) {
+ /*
+ * Check with 1003.1g - what should
+ * datagram error
+ */
+ unix_state_runlock(other);
+ sock_put(other);
+
+ err = 0;
+ unix_state_wlock(sk);
+ if (unix_peer(sk) == other) {
+ unix_peer(sk)=NULL;
+ unix_state_wunlock(sk);
+
+ unix_dgram_disconnected(sk, other);
+ sock_put(other);
+ err = -ECONNREFUSED;
+ } else {
+ unix_state_wunlock(sk);
+ }
+
+ other = NULL;
+ if (err)
+ goto out_free;
+ goto restart;
+ }
+
+ err = -EPIPE;
+ if (other->shutdown&RCV_SHUTDOWN)
+ goto out_unlock;
+
+ if (unix_peer(other) != sk &&
+ skb_queue_len(&other->receive_queue) > other->max_ack_backlog) {
+ if (!timeo) {
+ err = -EAGAIN;
+ goto out_unlock;
+ }
+
+ timeo = unix_wait_for_peer(other, timeo);
+
+ err = sock_intr_errno(timeo);
+ if (signal_pending(current))
+ goto out_free;
+
+ goto restart;
+ }
+
+ skb_queue_tail(&other->receive_queue, skb);
+ unix_state_runlock(other);
+ other->data_ready(other, len);
+ sock_put(other);
+ return len;
+
+out_unlock:
+ unix_state_runlock(other);
+out_free:
+ kfree_skb(skb);
+out:
+ if (other)
+ sock_put(other);
+ return err;
+}
+
+
+static int unix_stream_sendmsg(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ unix_socket *other = NULL;
+ struct sockaddr_un *sunaddr=msg->msg_name;
+ int err,size;
+ struct sk_buff *skb;
+ int sent=0;
+
+ err = -EOPNOTSUPP;
+ if (msg->msg_flags&MSG_OOB)
+ goto out_err;
+
+ if (msg->msg_namelen) {
+ err = (sk->state==TCP_ESTABLISHED ? -EISCONN : -EOPNOTSUPP);
+ goto out_err;
+ } else {
+ sunaddr = NULL;
+ err = -ENOTCONN;
+ other = unix_peer_get(sk);
+ if (!other)
+ goto out_err;
+ }
+
+ if (sk->shutdown&SEND_SHUTDOWN)
+ goto pipe_err;
+
+ while(sent < len)
+ {
+ /*
+ * Optimisation for the fact that under 0.01% of X messages typically
+ * need breaking up.
+ */
+
+ size=len-sent;
+
+ /* Keep two messages in the pipe so it schedules better */
+ if (size > sk->sndbuf/2 - 64)
+ size = sk->sndbuf/2 - 64;
+
+ if (size > SKB_MAX_ALLOC)
+ size = SKB_MAX_ALLOC;
+
+ /*
+ * Grab a buffer
+ */
+
+ skb=sock_alloc_send_skb(sk,size,msg->msg_flags&MSG_DONTWAIT, &err);
+
+ if (skb==NULL)
+ goto out_err;
+
+ /*
+ * If you pass two values to the sock_alloc_send_skb
+ * it tries to grab the large buffer with GFP_NOFS
+ * (which can fail easily), and if it fails grab the
+ * fallback size buffer which is under a page and will
+ * succeed. [Alan]
+ */
+ size = min_t(int, size, skb_tailroom(skb));
+
+ memcpy(UNIXCREDS(skb), &scm->creds, sizeof(struct ucred));
+ if (scm->fp)
+ unix_attach_fds(scm, skb);
+
+ if ((err = memcpy_fromiovec(skb_put(skb,size), msg->msg_iov, size)) != 0) {
+ kfree_skb(skb);
+ goto out_err;
+ }
+
+ unix_state_rlock(other);
+
+ if (other->dead || (other->shutdown & RCV_SHUTDOWN))
+ goto pipe_err_free;
+
+ skb_queue_tail(&other->receive_queue, skb);
+ unix_state_runlock(other);
+ other->data_ready(other, size);
+ sent+=size;
+ }
+ sock_put(other);
+ return sent;
+
+pipe_err_free:
+ unix_state_runlock(other);
+ kfree_skb(skb);
+pipe_err:
+ if (sent==0 && !(msg->msg_flags&MSG_NOSIGNAL))
+ send_sig(SIGPIPE,current,0);
+ err = -EPIPE;
+out_err:
+ if (other)
+ sock_put(other);
+ return sent ? : err;
+}
+
+static void unix_copy_addr(struct msghdr *msg, struct sock *sk)
+{
+ msg->msg_namelen = 0;
+ if (sk->protinfo.af_unix.addr) {
+ msg->msg_namelen=sk->protinfo.af_unix.addr->len;
+ memcpy(msg->msg_name,
+ sk->protinfo.af_unix.addr->name,
+ sk->protinfo.af_unix.addr->len);
+ }
+}
+
+static int unix_dgram_recvmsg(struct socket *sock, struct msghdr *msg, int size,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ int noblock = flags & MSG_DONTWAIT;
+ struct sk_buff *skb;
+ int err;
+
+ err = -EOPNOTSUPP;
+ if (flags&MSG_OOB)
+ goto out;
+
+ msg->msg_namelen = 0;
+
+ down(&sk->protinfo.af_unix.readsem);
+
+ skb = skb_recv_datagram(sk, flags, noblock, &err);
+ if (!skb)
+ goto out_unlock;
+
+ wake_up_interruptible(&sk->protinfo.af_unix.peer_wait);
+
+ if (msg->msg_name)
+ unix_copy_addr(msg, skb->sk);
+
+ if (size > skb->len)
+ size = skb->len;
+ else if (size < skb->len)
+ msg->msg_flags |= MSG_TRUNC;
+
+ err = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, size);
+ if (err)
+ goto out_free;
+
+ scm->creds = *UNIXCREDS(skb);
+
+ if (!(flags & MSG_PEEK))
+ {
+ if (UNIXCB(skb).fp)
+ unix_detach_fds(scm, skb);
+ }
+ else
+ {
+ /* It is questionable: on PEEK we could:
+ - do not return fds - good, but too simple 8)
+ - return fds, and do not return them on read (old strategy,
+ apparently wrong)
+ - clone fds (I choosed it for now, it is the most universal
+ solution)
+
+ POSIX 1003.1g does not actually define this clearly
+ at all. POSIX 1003.1g doesn't define a lot of things
+ clearly however!
+
+ */
+ if (UNIXCB(skb).fp)
+ scm->fp = scm_fp_dup(UNIXCB(skb).fp);
+ }
+ err = size;
+
+out_free:
+ skb_free_datagram(sk,skb);
+out_unlock:
+ up(&sk->protinfo.af_unix.readsem);
+out:
+ return err;
+}
+
+/*
+ * Sleep until data has arrive. But check for races..
+ */
+
+static long unix_stream_data_wait(unix_socket * sk, long timeo)
+{
+ DECLARE_WAITQUEUE(wait, current);
+
+ unix_state_rlock(sk);
+
+ add_wait_queue(sk->sleep, &wait);
+
+ for (;;) {
+ set_current_state(TASK_INTERRUPTIBLE);
+
+ if (skb_queue_len(&sk->receive_queue) ||
+ sk->err ||
+ (sk->shutdown & RCV_SHUTDOWN) ||
+ signal_pending(current) ||
+ !timeo)
+ break;
+
+ set_bit(SOCK_ASYNC_WAITDATA, &sk->socket->flags);
+ unix_state_runlock(sk);
+ timeo = schedule_timeout(timeo);
+ unix_state_rlock(sk);
+ clear_bit(SOCK_ASYNC_WAITDATA, &sk->socket->flags);
+ }
+
+ __set_current_state(TASK_RUNNING);
+ remove_wait_queue(sk->sleep, &wait);
+ unix_state_runlock(sk);
+ return timeo;
+}
+
+
+
+static int unix_stream_recvmsg(struct socket *sock, struct msghdr *msg, int size,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_un *sunaddr=msg->msg_name;
+ int copied = 0;
+ int check_creds = 0;
+ int target;
+ int err = 0;
+ long timeo;
+
+ err = -EINVAL;
+ if (sk->state != TCP_ESTABLISHED)
+ goto out;
+
+ err = -EOPNOTSUPP;
+ if (flags&MSG_OOB)
+ goto out;
+
+ target = sock_rcvlowat(sk, flags&MSG_WAITALL, size);
+ timeo = sock_rcvtimeo(sk, flags&MSG_DONTWAIT);
+
+ msg->msg_namelen = 0;
+
+ /* Lock the socket to prevent queue disordering
+ * while sleeps in memcpy_tomsg
+ */
+
+ down(&sk->protinfo.af_unix.readsem);
+
+ do
+ {
+ int chunk;
+ struct sk_buff *skb;
+
+ skb=skb_dequeue(&sk->receive_queue);
+ if (skb==NULL)
+ {
+ if (copied >= target)
+ break;
+
+ /*
+ * POSIX 1003.1g mandates this order.
+ */
+
+ if ((err = sock_error(sk)) != 0)
+ break;
+ if (sk->shutdown & RCV_SHUTDOWN)
+ break;
+ err = -EAGAIN;
+ if (!timeo)
+ break;
+ up(&sk->protinfo.af_unix.readsem);
+
+ timeo = unix_stream_data_wait(sk, timeo);
+
+ if (signal_pending(current)) {
+ err = sock_intr_errno(timeo);
+ goto out;
+ }
+ down(&sk->protinfo.af_unix.readsem);
+ continue;
+ }
+
+ if (check_creds) {
+ /* Never glue messages from different writers */
+ if (memcmp(UNIXCREDS(skb), &scm->creds, sizeof(scm->creds)) != 0) {
+ skb_queue_head(&sk->receive_queue, skb);
+ break;
+ }
+ } else {
+ /* Copy credentials */
+ scm->creds = *UNIXCREDS(skb);
+ check_creds = 1;
+ }
+
+ /* Copy address just once */
+ if (sunaddr)
+ {
+ unix_copy_addr(msg, skb->sk);
+ sunaddr = NULL;
+ }
+
+ chunk = min_t(unsigned int, skb->len, size);
+ if (memcpy_toiovec(msg->msg_iov, skb->data, chunk)) {
+ skb_queue_head(&sk->receive_queue, skb);
+ if (copied == 0)
+ copied = -EFAULT;
+ break;
+ }
+ copied += chunk;
+ size -= chunk;
+
+ /* Mark read part of skb as used */
+ if (!(flags & MSG_PEEK))
+ {
+ skb_pull(skb, chunk);
+
+ if (UNIXCB(skb).fp)
+ unix_detach_fds(scm, skb);
+
+ /* put the skb back if we didn't use it up.. */
+ if (skb->len)
+ {
+ skb_queue_head(&sk->receive_queue, skb);
+ break;
+ }
+
+ kfree_skb(skb);
+
+ if (scm->fp)
+ break;
+ }
+ else
+ {
+ /* It is questionable, see note in unix_dgram_recvmsg.
+ */
+ if (UNIXCB(skb).fp)
+ scm->fp = scm_fp_dup(UNIXCB(skb).fp);
+
+ /* put message back and return */
+ skb_queue_head(&sk->receive_queue, skb);
+ break;
+ }
+ } while (size);
+
+ up(&sk->protinfo.af_unix.readsem);
+out:
+ return copied ? : err;
+}
+
+static int unix_shutdown(struct socket *sock, int mode)
+{
+ struct sock *sk = sock->sk;
+ unix_socket *other;
+
+ mode = (mode+1)&(RCV_SHUTDOWN|SEND_SHUTDOWN);
+
+ if (mode) {
+ unix_state_wlock(sk);
+ sk->shutdown |= mode;
+ other=unix_peer(sk);
+ if (other)
+ sock_hold(other);
+ unix_state_wunlock(sk);
+ sk->state_change(sk);
+
+ if (other && sk->type == SOCK_STREAM) {
+ int peer_mode = 0;
+
+ if (mode&RCV_SHUTDOWN)
+ peer_mode |= SEND_SHUTDOWN;
+ if (mode&SEND_SHUTDOWN)
+ peer_mode |= RCV_SHUTDOWN;
+ unix_state_wlock(other);
+ other->shutdown |= peer_mode;
+ unix_state_wunlock(other);
+ other->state_change(other);
+ read_lock(&other->callback_lock);
+ if (peer_mode == SHUTDOWN_MASK)
+ sk_wake_async(other,1,POLL_HUP);
+ else if (peer_mode & RCV_SHUTDOWN)
+ sk_wake_async(other,1,POLL_IN);
+ read_unlock(&other->callback_lock);
+ }
+ if (other)
+ sock_put(other);
+ }
+ return 0;
+}
+
+static int unix_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+ long amount=0;
+ int err;
+
+ switch(cmd)
+ {
+ case SIOCOUTQ:
+ amount = atomic_read(&sk->wmem_alloc);
+ err = put_user(amount, (int *)arg);
+ break;
+ case SIOCINQ:
+ {
+ struct sk_buff *skb;
+ if (sk->state==TCP_LISTEN) {
+ err = -EINVAL;
+ break;
+ }
+
+ spin_lock(&sk->receive_queue.lock);
+ if (sk->type == SOCK_STREAM) {
+ skb_queue_walk(&sk->receive_queue, skb)
+ amount += skb->len;
+ } else {
+ if((skb=skb_peek(&sk->receive_queue))!=NULL)
+ amount=skb->len;
+ }
+ spin_unlock(&sk->receive_queue.lock);
+ err = put_user(amount, (int *)arg);
+ break;
+ }
+
+ default:
+ err = dev_ioctl(cmd, (void *)arg);
+ break;
+ }
+ return err;
+}
+
+static unsigned int unix_poll(struct file * file, struct socket *sock, poll_table *wait)
+{
+ struct sock *sk = sock->sk;
+ unsigned int mask;
+
+ poll_wait(file, sk->sleep, wait);
+ mask = 0;
+
+ /* exceptional events? */
+ if (sk->err)
+ mask |= POLLERR;
+ if (sk->shutdown == SHUTDOWN_MASK)
+ mask |= POLLHUP;
+
+ /* readable? */
+ if (!skb_queue_empty(&sk->receive_queue) || (sk->shutdown&RCV_SHUTDOWN))
+ mask |= POLLIN | POLLRDNORM;
+
+ /* Connection-based need to check for termination and startup */
+ if (sk->type == SOCK_STREAM && sk->state==TCP_CLOSE)
+ mask |= POLLHUP;
+
+ /*
+ * we set writable also when the other side has shut down the
+ * connection. This prevents stuck sockets.
+ */
+ if (unix_writable(sk))
+ mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
+
+ return mask;
+}
+
+
+#ifdef CONFIG_PROC_FS
+static int unix_read_proc(char *buffer, char **start, off_t offset,
+ int length, int *eof, void *data)
+{
+ off_t pos=0;
+ off_t begin=0;
+ int len=0;
+ int i;
+ unix_socket *s;
+
+ len+= sprintf(buffer,"Num RefCount Protocol Flags Type St "
+ "Inode Path\n");
+
+ read_lock(&unix_table_lock);
+ forall_unix_sockets (i,s)
+ {
+ unix_state_rlock(s);
+
+ len+=sprintf(buffer+len,"%p: %08X %08X %08X %04X %02X %5lu",
+ s,
+ atomic_read(&s->refcnt),
+ 0,
+ s->state == TCP_LISTEN ? __SO_ACCEPTCON : 0,
+ s->type,
+ s->socket ?
+ (s->state == TCP_ESTABLISHED ? SS_CONNECTED : SS_UNCONNECTED) :
+ (s->state == TCP_ESTABLISHED ? SS_CONNECTING : SS_DISCONNECTING),
+ sock_i_ino(s));
+
+ if (s->protinfo.af_unix.addr)
+ {
+ buffer[len++] = ' ';
+ memcpy(buffer+len, s->protinfo.af_unix.addr->name->sun_path,
+ s->protinfo.af_unix.addr->len-sizeof(short));
+ if (!UNIX_ABSTRACT(s))
+ len--;
+ else
+ buffer[len] = '@';
+ len += s->protinfo.af_unix.addr->len - sizeof(short);
+ }
+ unix_state_runlock(s);
+
+ buffer[len++]='\n';
+
+ pos = begin + len;
+ if(pos<offset)
+ {
+ len=0;
+ begin=pos;
+ }
+ if(pos>offset+length)
+ goto done;
+ }
+ *eof = 1;
+done:
+ read_unlock(&unix_table_lock);
+ *start=buffer+(offset-begin);
+ len-=(offset-begin);
+ if(len>length)
+ len=length;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+#endif
+
+struct proto_ops unix_stream_ops = {
+ family: PF_UNIX,
+
+ release: unix_release,
+ bind: unix_bind,
+ connect: unix_stream_connect,
+ socketpair: unix_socketpair,
+ accept: unix_accept,
+ getname: unix_getname,
+ poll: unix_poll,
+ ioctl: unix_ioctl,
+ listen: unix_listen,
+ shutdown: unix_shutdown,
+ setsockopt: sock_no_setsockopt,
+ getsockopt: sock_no_getsockopt,
+ sendmsg: unix_stream_sendmsg,
+ recvmsg: unix_stream_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+struct proto_ops unix_dgram_ops = {
+ family: PF_UNIX,
+
+ release: unix_release,
+ bind: unix_bind,
+ connect: unix_dgram_connect,
+ socketpair: unix_socketpair,
+ accept: sock_no_accept,
+ getname: unix_getname,
+ poll: datagram_poll,
+ ioctl: unix_ioctl,
+ listen: sock_no_listen,
+ shutdown: unix_shutdown,
+ setsockopt: sock_no_setsockopt,
+ getsockopt: sock_no_getsockopt,
+ sendmsg: unix_dgram_sendmsg,
+ recvmsg: unix_dgram_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+struct net_proto_family unix_family_ops = {
+ family: PF_UNIX,
+ create: unix_create
+};
+
+#ifdef CONFIG_SYSCTL
+extern void unix_sysctl_register(void);
+extern void unix_sysctl_unregister(void);
+#else
+static inline void unix_sysctl_register(void) {}
+static inline void unix_sysctl_unregister(void) {}
+#endif
+
+static char banner[] __initdata = KERN_INFO "NET4: Unix domain sockets 1.0/SMP for Linux NET4.0.\n";
+
+static int __init af_unix_init(void)
+{
+ struct sk_buff *dummy_skb;
+
+ printk(banner);
+ if (sizeof(struct unix_skb_parms) > sizeof(dummy_skb->cb))
+ {
+ printk(KERN_CRIT "unix_proto_init: panic\n");
+ return -1;
+ }
+ sock_register(&unix_family_ops);
+#ifdef CONFIG_PROC_FS
+ create_proc_read_entry("net/unix", 0, 0, unix_read_proc, NULL);
+#endif
+ unix_sysctl_register();
+ return 0;
+}
+
+static void __exit af_unix_exit(void)
+{
+ sock_unregister(PF_UNIX);
+ unix_sysctl_unregister();
+ remove_proc_entry("net/unix", 0);
+}
+
+module_init(af_unix_init);
+module_exit(af_unix_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/unix/garbage.c b/uClinux-2.4.31-uc0/net/unix/garbage.c
new file mode 100644
index 0000000..457515d
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/unix/garbage.c
@@ -0,0 +1,310 @@
+/*
+ * NET3: Garbage Collector For AF_UNIX sockets
+ *
+ * Garbage Collector:
+ * Copyright (C) Barak A. Pearlmutter.
+ * Released under the GPL version 2 or later.
+ *
+ * Chopped about by Alan Cox 22/3/96 to make it fit the AF_UNIX socket problem.
+ * If it doesn't work blame me, it worked when Barak sent it.
+ *
+ * Assumptions:
+ *
+ * - object w/ a bit
+ * - free list
+ *
+ * Current optimizations:
+ *
+ * - explicit stack instead of recursion
+ * - tail recurse on first born instead of immediate push/pop
+ * - we gather the stuff that should not be killed into tree
+ * and stack is just a path from root to the current pointer.
+ *
+ * Future optimizations:
+ *
+ * - don't just push entire root set; process in place
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * Fixes:
+ * Alan Cox 07 Sept 1997 Vmalloc internal stack as needed.
+ * Cope with changing max_files.
+ * Al Viro 11 Oct 1998
+ * Graph may have cycles. That is, we can send the descriptor
+ * of foo to bar and vice versa. Current code chokes on that.
+ * Fix: move SCM_RIGHTS ones into the separate list and then
+ * skb_free() them all instead of doing explicit fput's.
+ * Another problem: since fput() may block somebody may
+ * create a new unix_socket when we are in the middle of sweep
+ * phase. Fix: revert the logic wrt MARKED. Mark everything
+ * upon the beginning and unmark non-junk ones.
+ *
+ * [12 Oct 1998] AAARGH! New code purges all SCM_RIGHTS
+ * sent to connect()'ed but still not accept()'ed sockets.
+ * Fixed. Old code had slightly different problem here:
+ * extra fput() in situation when we passed the descriptor via
+ * such socket and closed it (descriptor). That would happen on
+ * each unix_gc() until the accept(). Since the struct file in
+ * question would go to the free list and might be reused...
+ * That might be the reason of random oopses on filp_close()
+ * in unrelated processes.
+ *
+ * AV 28 Feb 1999
+ * Kill the explicit allocation of stack. Now we keep the tree
+ * with root in dummy + pointer (gc_current) to one of the nodes.
+ * Stack is represented as path from gc_current to dummy. Unmark
+ * now means "add to tree". Push == "make it a son of gc_current".
+ * Pop == "move gc_current to parent". We keep only pointers to
+ * parents (->gc_tree).
+ * AV 1 Mar 1999
+ * Damn. Added missing check for ->dead in listen queues scanning.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/string.h>
+#include <linux/socket.h>
+#include <linux/un.h>
+#include <linux/net.h>
+#include <linux/fs.h>
+#include <linux/slab.h>
+#include <linux/skbuff.h>
+#include <linux/netdevice.h>
+#include <linux/file.h>
+#include <linux/proc_fs.h>
+#include <linux/tcp.h>
+
+#include <net/sock.h>
+#include <net/af_unix.h>
+#include <net/scm.h>
+
+/* Internal data structures and random procedures: */
+
+#define GC_HEAD ((unix_socket *)(-1))
+#define GC_ORPHAN ((unix_socket *)(-3))
+
+static unix_socket *gc_current=GC_HEAD; /* stack of objects to mark */
+
+atomic_t unix_tot_inflight = ATOMIC_INIT(0);
+
+
+extern inline unix_socket *unix_get_socket(struct file *filp)
+{
+ unix_socket * u_sock = NULL;
+ struct inode *inode = filp->f_dentry->d_inode;
+
+ /*
+ * Socket ?
+ */
+ if (inode->i_sock) {
+ struct socket * sock = &inode->u.socket_i;
+ struct sock * s = sock->sk;
+
+ /*
+ * PF_UNIX ?
+ */
+ if (s && sock->ops && sock->ops->family == PF_UNIX)
+ u_sock = s;
+ }
+ return u_sock;
+}
+
+/*
+ * Keep the number of times in flight count for the file
+ * descriptor if it is for an AF_UNIX socket.
+ */
+
+void unix_inflight(struct file *fp)
+{
+ unix_socket *s=unix_get_socket(fp);
+ if(s) {
+ atomic_inc(&s->protinfo.af_unix.inflight);
+ atomic_inc(&unix_tot_inflight);
+ }
+}
+
+void unix_notinflight(struct file *fp)
+{
+ unix_socket *s=unix_get_socket(fp);
+ if(s) {
+ atomic_dec(&s->protinfo.af_unix.inflight);
+ atomic_dec(&unix_tot_inflight);
+ }
+}
+
+
+/*
+ * Garbage Collector Support Functions
+ */
+
+extern inline unix_socket *pop_stack(void)
+{
+ unix_socket *p=gc_current;
+ gc_current = p->protinfo.af_unix.gc_tree;
+ return p;
+}
+
+extern inline int empty_stack(void)
+{
+ return gc_current == GC_HEAD;
+}
+
+extern inline void maybe_unmark_and_push(unix_socket *x)
+{
+ if (x->protinfo.af_unix.gc_tree != GC_ORPHAN)
+ return;
+ sock_hold(x);
+ x->protinfo.af_unix.gc_tree = gc_current;
+ gc_current = x;
+}
+
+
+/* The external entry point: unix_gc() */
+
+void unix_gc(void)
+{
+ static DECLARE_MUTEX(unix_gc_sem);
+ int i;
+ unix_socket *s;
+ struct sk_buff_head hitlist;
+ struct sk_buff *skb;
+
+ /*
+ * Avoid a recursive GC.
+ */
+
+ if (down_trylock(&unix_gc_sem))
+ return;
+
+ read_lock(&unix_table_lock);
+
+ forall_unix_sockets(i, s)
+ {
+ s->protinfo.af_unix.gc_tree=GC_ORPHAN;
+ }
+ /*
+ * Everything is now marked
+ */
+
+ /* Invariant to be maintained:
+ - everything unmarked is either:
+ -- (a) on the stack, or
+ -- (b) has all of its children unmarked
+ - everything on the stack is always unmarked
+ - nothing is ever pushed onto the stack twice, because:
+ -- nothing previously unmarked is ever pushed on the stack
+ */
+
+ /*
+ * Push root set
+ */
+
+ forall_unix_sockets(i, s)
+ {
+ int open_count = 0;
+
+ /*
+ * If all instances of the descriptor are not
+ * in flight we are in use.
+ *
+ * Special case: when socket s is embrion, it may be
+ * hashed but still not in queue of listening socket.
+ * In this case (see unix_create1()) we set artificial
+ * negative inflight counter to close race window.
+ * It is trick of course and dirty one.
+ */
+ if(s->socket && s->socket->file)
+ open_count = file_count(s->socket->file);
+ if (open_count > atomic_read(&s->protinfo.af_unix.inflight))
+ maybe_unmark_and_push(s);
+ }
+
+ /*
+ * Mark phase
+ */
+
+ while (!empty_stack())
+ {
+ unix_socket *x = pop_stack();
+ unix_socket *sk;
+
+ spin_lock(&x->receive_queue.lock);
+ skb=skb_peek(&x->receive_queue);
+
+ /*
+ * Loop through all but first born
+ */
+
+ while(skb && skb != (struct sk_buff *)&x->receive_queue)
+ {
+ /*
+ * Do we have file descriptors ?
+ */
+ if(UNIXCB(skb).fp)
+ {
+ /*
+ * Process the descriptors of this socket
+ */
+ int nfd=UNIXCB(skb).fp->count;
+ struct file **fp = UNIXCB(skb).fp->fp;
+ while(nfd--)
+ {
+ /*
+ * Get the socket the fd matches if
+ * it indeed does so
+ */
+ if((sk=unix_get_socket(*fp++))!=NULL)
+ {
+ maybe_unmark_and_push(sk);
+ }
+ }
+ }
+ /* We have to scan not-yet-accepted ones too */
+ if (x->state == TCP_LISTEN) {
+ maybe_unmark_and_push(skb->sk);
+ }
+ skb=skb->next;
+ }
+ spin_unlock(&x->receive_queue.lock);
+ sock_put(x);
+ }
+
+ skb_queue_head_init(&hitlist);
+
+ forall_unix_sockets(i, s)
+ {
+ if (s->protinfo.af_unix.gc_tree == GC_ORPHAN)
+ {
+ struct sk_buff *nextsk;
+ spin_lock(&s->receive_queue.lock);
+ skb=skb_peek(&s->receive_queue);
+ while(skb && skb != (struct sk_buff *)&s->receive_queue)
+ {
+ nextsk=skb->next;
+ /*
+ * Do we have file descriptors ?
+ */
+ if(UNIXCB(skb).fp)
+ {
+ __skb_unlink(skb, skb->list);
+ __skb_queue_tail(&hitlist,skb);
+ }
+ skb=nextsk;
+ }
+ spin_unlock(&s->receive_queue.lock);
+ }
+ s->protinfo.af_unix.gc_tree = GC_ORPHAN;
+ }
+ read_unlock(&unix_table_lock);
+
+ /*
+ * Here we are. Hitlist is filled. Die.
+ */
+
+ __skb_queue_purge(&hitlist);
+ up(&unix_gc_sem);
+}
diff --git a/uClinux-2.4.31-uc0/net/unix/sysctl_net_unix.c b/uClinux-2.4.31-uc0/net/unix/sysctl_net_unix.c
new file mode 100644
index 0000000..b446d0f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/unix/sysctl_net_unix.c
@@ -0,0 +1,45 @@
+/*
+ * NET4: Sysctl interface to net af_unix subsystem.
+ *
+ * Authors: Mike Shaver.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+
+extern int sysctl_unix_max_dgram_qlen;
+
+ctl_table unix_table[] = {
+ {NET_UNIX_MAX_DGRAM_QLEN, "max_dgram_qlen",
+ &sysctl_unix_max_dgram_qlen, sizeof(int), 0644, NULL,
+ &proc_dointvec },
+ {0}
+};
+
+static ctl_table unix_net_table[] = {
+ {NET_UNIX, "unix", NULL, 0, 0555, unix_table},
+ {0}
+};
+
+static ctl_table unix_root_table[] = {
+ {CTL_NET, "net", NULL, 0, 0555, unix_net_table},
+ {0}
+};
+
+static struct ctl_table_header * unix_sysctl_header;
+
+void unix_sysctl_register(void)
+{
+ unix_sysctl_header = register_sysctl_table(unix_root_table, 0);
+}
+
+void unix_sysctl_unregister(void)
+{
+ unregister_sysctl_table(unix_sysctl_header);
+}
+
diff --git a/uClinux-2.4.31-uc0/net/wanrouter/Makefile b/uClinux-2.4.31-uc0/net/wanrouter/Makefile
new file mode 100644
index 0000000..ff71c3f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/wanrouter/Makefile
@@ -0,0 +1,20 @@
+#
+# Makefile for the Linux WAN router layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := wanrouter.o
+
+export-objs := wanmain.o
+
+obj-y := wanproc.o wanmain.o
+obj-m := $(O_TARGET)
+ifneq ($(CONFIG_VENDOR_SANGOMA),n)
+ obj-m += $(O_TARGET)
+endif
+
+include $(TOPDIR)/Rules.make
diff --git a/uClinux-2.4.31-uc0/net/wanrouter/af_wanpipe.c b/uClinux-2.4.31-uc0/net/wanrouter/af_wanpipe.c
new file mode 100644
index 0000000..52648dd
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/wanrouter/af_wanpipe.c
@@ -0,0 +1,2766 @@
+/*****************************************************************************
+* af_wanpipe.c WANPIPE(tm) Secure Socket Layer.
+*
+* Author: Nenad Corbic <ncorbic@sangoma.com>
+*
+* Copyright: (c) 2000 Sangoma Technologies Inc.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version
+* 2 of the License, or (at your option) any later version.
+* ============================================================================
+* Due Credit:
+* Wanpipe socket layer is based on Packet and
+* the X25 socket layers. The above sockets were
+* used for the specific use of Sangoma Technoloiges
+* API programs.
+* Packet socket Authors: Ross Biro, Fred N. van Kempen and
+* Alan Cox.
+* X25 socket Author: Jonathan Naylor.
+* ============================================================================
+* Apr 25, 2000 Nenad Corbic o Added the ability to send zero length packets.
+* Mar 13, 2000 Nenad Corbic o Added a tx buffer check via ioctl call.
+* Mar 06, 2000 Nenad Corbic o Fixed the corrupt sock lcn problem.
+* Server and client applicaton can run
+* simultaneously without conflicts.
+* Feb 29, 2000 Nenad Corbic o Added support for PVC protocols, such as
+* CHDLC, Frame Relay and HDLC API.
+* Jan 17, 2000 Nenad Corbic o Initial version, based on AF_PACKET socket.
+* X25API support only.
+*
+******************************************************************************/
+
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/fcntl.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/poll.h>
+#include <linux/wireless.h>
+#include <linux/kmod.h>
+#include <net/ip.h>
+#include <net/protocol.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <linux/errno.h>
+#include <linux/timer.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/wanpipe.h>
+#include <linux/if_wanpipe.h>
+#include <linux/pkt_sched.h>
+#include <linux/tcp.h>
+#include <linux/if_wanpipe_common.h>
+#include <linux/sdla_x25.h>
+
+#ifdef CONFIG_INET
+#include <net/inet_common.h>
+#endif
+
+#define SLOW_BACKOFF 0.1*HZ
+#define FAST_BACKOFF 0.01*HZ
+
+//#define PRINT_DEBUG
+#ifdef PRINT_DEBUG
+ #define DBG_PRINTK(format, a...) printk(format, ## a)
+#else
+ #define DBG_PRINTK(format, a...)
+#endif
+
+#if defined(LINUX_2_1)
+ #define dev_put(a)
+ #define __sock_put(a)
+ #define sock_hold(a)
+ #define DECLARE_WAITQUEUE(a,b) \
+ struct wait_queue a = { b, NULL }
+#endif
+
+/* SECURE SOCKET IMPLEMENTATION
+ *
+ * TRANSMIT:
+ *
+ * When the user sends a packet via send() system call
+ * the wanpipe_sendmsg() function is executed.
+ *
+ * Each packet is enqueud into sk->write_queue transmit
+ * queue. When the packet is enqueued, a delayed transmit
+ * timer is triggerd which acts as a Bottom Half hander.
+ *
+ * wanpipe_delay_transmit() function (BH), dequeues packets
+ * from the sk->write_queue transmit queue and sends it
+ * to the deriver via dev->hard_start_xmit(skb, dev) function.
+ * Note, this function is actual a function pointer of if_send()
+ * routine in the wanpipe driver.
+ *
+ * X25API GUARANTEED DELIVERY:
+ *
+ * In order to provide 100% guaranteed packet delivery,
+ * an atomic 'packet_sent' counter is implemented. Counter
+ * is incremented for each packet enqueued
+ * into sk->write_queue. Counter is decremented each
+ * time wanpipe_delayed_transmit() function successfuly
+ * passes the packet to the driver. Before each send(), a poll
+ * routine checks the sock resources The maximum value of
+ * packet sent counter is 1, thus if one packet is queued, the
+ * application will block until that packet is passed to the
+ * driver.
+ *
+ * RECEIVE:
+ *
+ * Wanpipe device drivers call the socket bottom half
+ * function, wanpipe_rcv() to queue the incoming packets
+ * into an AF_WANPIPE socket queue. Based on wanpipe_rcv()
+ * return code, the driver knows whether the packet was
+ * sucessfully queued. If the socket queue is full,
+ * protocol flow control is used by the driver, if any,
+ * to slow down the traffic until the sock queue is free.
+ *
+ * Every time a packet arrives into a socket queue the
+ * socket wakes up processes which are waiting to receive
+ * data.
+ *
+ * If the socket queue is full, the driver sets a block
+ * bit which signals the socket to kick the wanpipe driver
+ * bottom half hander when the socket queue is partialy
+ * empty. wanpipe_recvmsg() function performs this action.
+ *
+ * In case of x25api, packets will never be dropped, since
+ * flow control is available.
+ *
+ * In case of streaming protocols like CHDLC, packets will
+ * be dropped but the statistics will be generated.
+ */
+
+
+/* The code below is used to test memory leaks. It prints out
+ * a message every time kmalloc and kfree system calls get executed.
+ * If the calls match there is no leak :)
+ */
+
+/***********FOR DEBUGGING PURPOSES*********************************************
+#define KMEM_SAFETYZONE 8
+
+static void * dbg_kmalloc(unsigned int size, int prio, int line) {
+ void * v = kmalloc(size,prio);
+ printk(KERN_INFO "line %d kmalloc(%d,%d) = %p\n",line,size,prio,v);
+ return v;
+}
+static void dbg_kfree(void * v, int line) {
+ printk(KERN_INFO "line %d kfree(%p)\n",line,v);
+ kfree(v);
+}
+
+#define kmalloc(x,y) dbg_kmalloc(x,y,__LINE__)
+#define kfree(x) dbg_kfree(x,__LINE__)
+******************************************************************************/
+
+
+/* List of all wanpipe sockets. */
+struct sock * wanpipe_sklist = NULL;
+static rwlock_t wanpipe_sklist_lock = RW_LOCK_UNLOCKED;
+
+atomic_t wanpipe_socks_nr;
+static unsigned long wanpipe_tx_critical=0;
+
+#if 0
+/* Private wanpipe socket structures. */
+struct wanpipe_opt
+{
+ void *mbox; /* Mail box */
+ void *card; /* Card bouded to */
+ netdevice_t *dev; /* Bounded device */
+ unsigned short lcn; /* Binded LCN */
+ unsigned char svc; /* 0=pvc, 1=svc */
+ unsigned char timer; /* flag for delayed transmit*/
+ struct timer_list tx_timer;
+ unsigned poll_cnt;
+ unsigned char force; /* Used to force sock release */
+ atomic_t packet_sent;
+};
+#endif
+
+static int sk_count=0;
+extern struct proto_ops wanpipe_ops;
+static unsigned long find_free_critical=0;
+
+static void wanpipe_unlink_driver (struct sock *);
+static void wanpipe_link_driver (netdevice_t *,struct sock *sk);
+static void wanpipe_wakeup_driver(struct sock *sk);
+static int execute_command(struct sock *, unsigned char, unsigned int);
+static int check_dev (netdevice_t *, sdla_t *);
+netdevice_t * wanpipe_find_free_dev (sdla_t *);
+static void wanpipe_unlink_card (struct sock *);
+static int wanpipe_link_card (struct sock *);
+static struct sock *wanpipe_make_new(struct sock *);
+static struct sock *wanpipe_alloc_socket(void);
+static inline int get_atomic_device (netdevice_t *);
+static int wanpipe_exec_cmd(struct sock *, int, unsigned int);
+static int get_ioctl_cmd (struct sock *, void *);
+static int set_ioctl_cmd (struct sock *, void *);
+static void release_device (netdevice_t *);
+static void wanpipe_kill_sock_timer (unsigned long data);
+static void wanpipe_kill_sock_irq (struct sock *);
+static void wanpipe_kill_sock_accept (struct sock *);
+static int wanpipe_do_bind(struct sock *, netdevice_t *, int);
+struct sock * get_newsk_from_skb (struct sk_buff *);
+static int wanpipe_debug (struct sock *, void *);
+static void wanpipe_delayed_transmit (unsigned long data);
+static void release_driver(struct sock *);
+static void start_cleanup_timer (struct sock *);
+static void check_write_queue(struct sock *);
+static int check_driver_busy (struct sock *);
+
+/*============================================================
+ * wanpipe_rcv
+ *
+ * Wanpipe socket bottom half handler. This function
+ * is called by the WANPIPE device drivers to queue a
+ * incomming packet into the socket receive queue.
+ * Once the packet is queued, all processes waiting to
+ * read are woken up.
+ *
+ * During socket bind, this function is bounded into
+ * WANPIPE driver private.
+ *===========================================================*/
+
+static int wanpipe_rcv(struct sk_buff *skb, netdevice_t *dev, struct sock *sk)
+{
+ struct wan_sockaddr_ll *sll = (struct wan_sockaddr_ll*)skb->cb;
+ wanpipe_common_t *chan = dev->priv;
+ /*
+ * When we registered the protocol we saved the socket in the data
+ * field for just this event.
+ */
+
+ skb->dev = dev;
+
+ sll->sll_family = AF_WANPIPE;
+ sll->sll_hatype = dev->type;
+ sll->sll_protocol = skb->protocol;
+ sll->sll_pkttype = skb->pkt_type;
+ sll->sll_ifindex = dev->ifindex;
+ sll->sll_halen = 0;
+
+ if (dev->hard_header_parse)
+ sll->sll_halen = dev->hard_header_parse(skb, sll->sll_addr);
+
+ /*
+ * WAN_PACKET_DATA : Data which should be passed up the receive queue.
+ * WAN_PACKET_ASYC : Asynchronous data like place call, which should
+ * be passed up the listening sock.
+ * WAN_PACKET_ERR : Asynchronous data like clear call or restart
+ * which should go into an error queue.
+ */
+ switch (skb->pkt_type){
+
+ case WAN_PACKET_DATA:
+ if (sock_queue_rcv_skb(sk,skb)<0){
+ return -ENOMEM;
+ }
+ break;
+ case WAN_PACKET_CMD:
+ sk->state = chan->state;
+ /* Bug fix: update Mar6.
+ * Do not set the sock lcn number here, since
+ * cmd is not guaranteed to be executed on the
+ * board, thus Lcn could be wrong */
+ sk->data_ready(sk,skb->len);
+ kfree_skb(skb);
+ break;
+ case WAN_PACKET_ERR:
+ sk->state = chan->state;
+ if (sock_queue_err_skb(sk,skb)<0){
+ return -ENOMEM;
+ }
+ break;
+ default:
+ printk(KERN_INFO "wansock: BH Illegal Packet Type Dropping\n");
+ kfree_skb(skb);
+ break;
+ }
+
+//??????????????????????
+// if (sk->state == WANSOCK_DISCONNECTED){
+// if (sk->zapped){
+// //printk(KERN_INFO "wansock: Disconnected, killing early\n");
+// wanpipe_unlink_driver(sk);
+// sk->bound_dev_if = 0;
+// }
+// }
+
+ return 0;
+}
+
+/*============================================================
+ * wanpipe_listen_rcv
+ *
+ * Wanpipe LISTEN socket bottom half handler. This function
+ * is called by the WANPIPE device drivers to queue an
+ * incomming call into the socket listening queue.
+ * Once the packet is queued, the waiting accept() process
+ * is woken up.
+ *
+ * During socket bind, this function is bounded into
+ * WANPIPE driver private.
+ *
+ * IMPORTANT NOTE:
+ * The accept call() is waiting for an skb packet
+ * which contains a pointer to a device structure.
+ *
+ * When we do a bind to a device structre, we
+ * bind a newly created socket into "chan->sk". Thus,
+ * when accept receives the skb packet, it will know
+ * from which dev it came form, and in turn it will know
+ * the address of the new sock.
+ *
+ * NOTE: This function gets called from driver ISR.
+ *===========================================================*/
+
+static int wanpipe_listen_rcv (struct sk_buff *skb, struct sock *sk)
+{
+
+ struct wan_sockaddr_ll *sll = (struct wan_sockaddr_ll*)skb->cb;
+ struct sock *newsk;
+ netdevice_t *dev;
+ sdla_t *card;
+ mbox_cmd_t *mbox_ptr;
+ wanpipe_common_t *chan;
+
+ /* Find a free device, if none found, all svc's are busy
+ */
+
+ card = (sdla_t*)sk->protinfo.af_wanpipe->card;
+ if (!card){
+ printk(KERN_INFO "wansock: LISTEN ERROR, No Card\n");
+ return -ENODEV;
+ }
+
+ dev = wanpipe_find_free_dev(card);
+ if (!dev){
+ printk(KERN_INFO "wansock: LISTEN ERROR, No Free Device\n");
+ return -ENODEV;
+ }
+
+ chan=dev->priv;
+ chan->state = WANSOCK_CONNECTING;
+
+ /* Allocate a new sock, which accept will bind
+ * and pass up to the user
+ */
+ if ((newsk = wanpipe_make_new(sk)) == NULL){
+ release_device(dev);
+ return -ENOMEM;
+ }
+
+
+ /* Initialize the new sock structure
+ */
+ newsk->bound_dev_if = dev->ifindex;
+ newsk->protinfo.af_wanpipe->card = sk->protinfo.af_wanpipe->card;
+
+ /* Insert the sock into the main wanpipe
+ * sock list.
+ */
+ atomic_inc(&wanpipe_socks_nr);
+
+ /* Allocate and fill in the new Mail Box. Then
+ * bind the mail box to the sock. It will be
+ * used by the ioctl call to read call information
+ * and to execute commands.
+ */
+ if ((mbox_ptr = kmalloc(sizeof(mbox_cmd_t), GFP_ATOMIC)) == NULL) {
+ wanpipe_kill_sock_irq (newsk);
+ release_device(dev);
+ return -ENOMEM;
+ }
+ memset(mbox_ptr, 0, sizeof(mbox_cmd_t));
+ memcpy(mbox_ptr,skb->data,skb->len);
+
+ /* Register the lcn on which incoming call came
+ * from. Thus, if we have to clear it, we know
+ * whic lcn to clear
+ */
+
+ newsk->protinfo.af_wanpipe->lcn = mbox_ptr->cmd.lcn;
+ newsk->protinfo.af_wanpipe->mbox = (void *)mbox_ptr;
+
+ DBG_PRINTK(KERN_INFO "NEWSOCK : Device %s, bind to lcn %i\n",
+ dev->name,mbox_ptr->cmd.lcn);
+
+ chan->lcn = mbox_ptr->cmd.lcn;
+ card->u.x.svc_to_dev_map[(chan->lcn%MAX_X25_LCN)] = dev;
+
+ newsk->zapped=0;
+ newsk->num = htons(X25_PROT);
+
+ if (wanpipe_do_bind(newsk,dev,newsk->num)){
+ wanpipe_kill_sock_irq (newsk);
+ release_device(dev);
+ return -EINVAL;
+ }
+ newsk->state = WANSOCK_CONNECTING;
+
+
+ /* Fill in the standard sock address info */
+
+ sll->sll_family = AF_WANPIPE;
+ sll->sll_hatype = dev->type;
+ sll->sll_protocol = skb->protocol;
+ sll->sll_pkttype = skb->pkt_type;
+ sll->sll_ifindex = dev->ifindex;
+ sll->sll_halen = 0;
+
+ skb->dev = dev;
+ sk->ack_backlog++;
+
+ /* We must do this manually, since the sock_queue_rcv_skb()
+ * function sets the skb->dev to NULL. However, we use
+ * the dev field in the accept function.*/
+ if (atomic_read(&sk->rmem_alloc) + skb->truesize >=
+ (unsigned)sk->rcvbuf){
+
+ wanpipe_unlink_driver(newsk);
+ wanpipe_kill_sock_irq (newsk);
+ --sk->ack_backlog;
+ return -ENOMEM;
+ }
+
+ skb_set_owner_r(skb, sk);
+ skb_queue_tail(&sk->receive_queue, skb);
+ sk->data_ready(sk,skb->len);
+
+ return 0;
+}
+
+
+
+/*============================================================
+ * wanpipe_make_new
+ *
+ * Create a new sock, and allocate a wanpipe private
+ * structure to it. Also, copy the important data
+ * from the original sock to the new sock.
+ *
+ * This function is used by wanpipe_listen_rcv() listen
+ * bottom half handler. A copy of the listening sock
+ * is created using this function.
+ *
+ *===========================================================*/
+
+static struct sock *wanpipe_make_new(struct sock *osk)
+{
+ struct sock *sk;
+
+ if (osk->type != SOCK_RAW)
+ return NULL;
+
+ if ((sk = wanpipe_alloc_socket()) == NULL)
+ return NULL;
+
+ sk->type = osk->type;
+ sk->socket = osk->socket;
+ sk->priority = osk->priority;
+ sk->protocol = osk->protocol;
+ sk->num = osk->num;
+ sk->rcvbuf = osk->rcvbuf;
+ sk->sndbuf = osk->sndbuf;
+ sk->debug = osk->debug;
+ sk->state = WANSOCK_CONNECTING;
+ sk->sleep = osk->sleep;
+
+ return sk;
+}
+
+/*============================================================
+ * wanpipe_make_new
+ *
+ * Allocate memory for the a new sock, and sock
+ * private data.
+ *
+ * Increment the module use count.
+ *
+ * This function is used by wanpipe_create() and
+ * wanpipe_make_new() functions.
+ *
+ *===========================================================*/
+
+static struct sock *wanpipe_alloc_socket(void)
+{
+ struct sock *sk;
+ struct wanpipe_opt *wan_opt;
+
+ if ((sk = sk_alloc(PF_WANPIPE, GFP_ATOMIC, 1)) == NULL)
+ return NULL;
+
+ if ((wan_opt = kmalloc(sizeof(struct wanpipe_opt), GFP_ATOMIC)) == NULL) {
+ sk_free(sk);
+ return NULL;
+ }
+ memset(wan_opt, 0x00, sizeof(struct wanpipe_opt));
+
+ sk->protinfo.af_wanpipe = wan_opt;
+ sk->protinfo.destruct_hook = wan_opt;
+
+ /* Use timer to send data to the driver. This will act
+ * as a BH handler for sendmsg functions */
+ sk->protinfo.af_wanpipe->tx_timer.data=(unsigned long)sk;
+ sk->protinfo.af_wanpipe->tx_timer.function=wanpipe_delayed_transmit;
+
+ MOD_INC_USE_COUNT;
+
+ sock_init_data(NULL, sk);
+ return sk;
+}
+
+
+/*============================================================
+ * wanpipe_sendmsg
+ *
+ * This function implements a sendto() system call,
+ * for AF_WANPIPE socket family.
+ * During socket bind() sk->bound_dev_if is initialized
+ * to a correct network device. This number is used
+ * to find a network device to which the packet should
+ * be passed to.
+ *
+ * Each packet is queued into sk->write_queue and
+ * delayed transmit bottom half handler is marked for
+ * execution.
+ *
+ * A socket must be in WANSOCK_CONNECTED state before
+ * a packet is queued into sk->write_queue.
+ *===========================================================*/
+
+static int wanpipe_sendmsg(struct socket *sock, struct msghdr *msg, int len,
+ struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct wan_sockaddr_ll *saddr=(struct wan_sockaddr_ll *)msg->msg_name;
+ struct sk_buff *skb;
+ netdevice_t *dev;
+ unsigned short proto;
+ unsigned char *addr;
+ int ifindex, err, reserve = 0;
+
+
+ if (!sk->zapped)
+ return -ENETDOWN;
+
+ if (sk->state != WANSOCK_CONNECTED)
+ return -ENOTCONN;
+
+ if (msg->msg_flags&~MSG_DONTWAIT)
+ return(-EINVAL);
+
+ /* it was <=, now one can send
+ * zero length packets */
+ if (len < sizeof(x25api_hdr_t))
+ return -EINVAL;
+
+ if (saddr == NULL) {
+ ifindex = sk->bound_dev_if;
+ proto = sk->num;
+ addr = NULL;
+
+ }else{
+ if (msg->msg_namelen < sizeof(struct wan_sockaddr_ll)){
+ return -EINVAL;
+ }
+
+ ifindex = sk->bound_dev_if;
+ proto = saddr->sll_protocol;
+ addr = saddr->sll_addr;
+ }
+
+ dev = dev_get_by_index(ifindex);
+ if (dev == NULL){
+ printk(KERN_INFO "wansock: Send failed, dev index: %i\n",ifindex);
+ return -ENXIO;
+ }
+ dev_put(dev);
+
+ if (sock->type == SOCK_RAW)
+ reserve = dev->hard_header_len;
+
+ if (len > dev->mtu+reserve){
+ return -EMSGSIZE;
+ }
+
+ #ifndef LINUX_2_4
+ dev_lock_list();
+ #endif
+
+ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,3)
+ skb = sock_alloc_send_skb(sk, len+dev->hard_header_len+15,
+ msg->msg_flags & MSG_DONTWAIT, &err);
+ #else
+ skb = sock_alloc_send_skb(sk, len+dev->hard_header_len+15, 0,
+ msg->msg_flags & MSG_DONTWAIT, &err);
+ #endif
+
+ if (skb==NULL){
+ goto out_unlock;
+ }
+
+ skb_reserve(skb, (dev->hard_header_len+15)&~15);
+ skb->nh.raw = skb->data;
+
+ /* Returns -EFAULT on error */
+ err = memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len);
+ if (err){
+ goto out_free;
+ }
+
+ if (dev->hard_header) {
+ int res;
+ err = -EINVAL;
+ res = dev->hard_header(skb, dev, ntohs(proto), addr, NULL, len);
+ if (res<0){
+ goto out_free;
+ }
+ }
+
+ skb->protocol = proto;
+ skb->dev = dev;
+ skb->priority = sk->priority;
+ skb->pkt_type = WAN_PACKET_DATA;
+
+ err = -ENETDOWN;
+ if (!(dev->flags & IFF_UP))
+ goto out_free;
+
+ #ifndef LINUX_2_4
+ dev_unlock_list();
+ #endif
+
+ if (atomic_read(&sk->wmem_alloc) + skb->truesize > (unsigned int)sk->sndbuf){
+ kfree_skb(skb);
+ return -ENOBUFS;
+ }
+
+ skb_queue_tail(&sk->write_queue,skb);
+ atomic_inc(&sk->protinfo.af_wanpipe->packet_sent);
+
+ if (!(test_and_set_bit(0,&sk->protinfo.af_wanpipe->timer))){
+ del_timer(&sk->protinfo.af_wanpipe->tx_timer);
+ sk->protinfo.af_wanpipe->tx_timer.expires=jiffies+1;
+ add_timer(&sk->protinfo.af_wanpipe->tx_timer);
+ }
+
+ return(len);
+
+out_free:
+ kfree_skb(skb);
+out_unlock:
+#ifndef LINUX_2_4
+ dev_unlock_list();
+#endif
+ return err;
+}
+
+/*============================================================
+ * wanpipe_delayed_tarnsmit
+ *
+ * Transmit bottom half handeler. It dequeues packets
+ * from sk->write_queue and passes them to the
+ * driver. If the driver is busy, the packet is
+ * re-enqueued.
+ *
+ * Packet Sent counter is decremented on successful
+ * transmission.
+ *===========================================================*/
+
+
+static void wanpipe_delayed_transmit (unsigned long data)
+{
+ struct sock *sk=(struct sock *)data;
+ struct sk_buff *skb;
+ netdevice_t *dev = sk->protinfo.af_wanpipe->dev;
+ sdla_t *card = (sdla_t*)sk->protinfo.af_wanpipe->card;
+
+ if (!card || !dev){
+ clear_bit (0,&sk->protinfo.af_wanpipe->timer);
+ DBG_PRINTK(KERN_INFO "wansock: Transmit delay, no dev or card\n");
+ return;
+ }
+
+ if (sk->state != WANSOCK_CONNECTED || !sk->zapped){
+ clear_bit (0,&sk->protinfo.af_wanpipe->timer);
+ DBG_PRINTK(KERN_INFO "wansock: Tx Timer, State not CONNECTED\n");
+ return;
+ }
+
+ /* If driver is executing command, we must offload
+ * the board by not sending data. Otherwise a
+ * pending command will never get a free buffer
+ * to execute */
+ if (atomic_read(&card->u.x.command_busy)){
+ sk->protinfo.af_wanpipe->tx_timer.expires=jiffies+SLOW_BACKOFF;
+ add_timer(&sk->protinfo.af_wanpipe->tx_timer);
+ DBG_PRINTK(KERN_INFO "wansock: Tx Timer, command bys BACKOFF\n");
+ return;
+ }
+
+
+ if (test_and_set_bit(0,&wanpipe_tx_critical)){
+ printk(KERN_INFO "WanSock: Tx timer critical %s\n",dev->name);
+ sk->protinfo.af_wanpipe->tx_timer.expires=jiffies+SLOW_BACKOFF;
+ add_timer(&sk->protinfo.af_wanpipe->tx_timer);
+ return;
+ }
+
+ /* Check for a packet in the fifo and send */
+ if ((skb=skb_dequeue(&sk->write_queue)) != NULL){
+
+ if (dev->hard_start_xmit(skb, dev) != 0){
+
+ /* Driver failed to transmit, re-enqueue
+ * the packet and retry again later */
+ skb_queue_head(&sk->write_queue,skb);
+ clear_bit(0,&wanpipe_tx_critical);
+ return;
+ }else{
+
+ /* Packet Sent successful. Check for more packets
+ * if more packets, re-trigger the transmit routine
+ * other wise exit
+ */
+ atomic_dec(&sk->protinfo.af_wanpipe->packet_sent);
+
+ if (skb_peek(&sk->write_queue) == NULL){
+ /* If there is nothing to send, kick
+ * the poll routine, which will trigger
+ * the application to send more data */
+ sk->data_ready(sk,0);
+ clear_bit (0,&sk->protinfo.af_wanpipe->timer);
+ }else{
+ /* Reschedule as fast as possible */
+ sk->protinfo.af_wanpipe->tx_timer.expires=jiffies+1;
+ add_timer(&sk->protinfo.af_wanpipe->tx_timer);
+ }
+ }
+ }
+ clear_bit(0,&wanpipe_tx_critical);
+}
+
+/*============================================================
+ * execute_command
+ *
+ * Execute x25api commands. The atomic variable
+ * chan->command is used to indicate to the driver that
+ * command is pending for exection. The acutal command
+ * structure is placed into a sock mbox structure
+ * (sk->protinfo.af_wanpipe->mbox).
+ *
+ * The sock private structure, mbox is
+ * used as shared memory between sock and the driver.
+ * Driver uses the sock mbox to execute the command
+ * and return the result.
+ *
+ * For all command except PLACE CALL, the function
+ * waits for the result. PLACE CALL can be ether
+ * blocking or nonblocking. The user sets this option
+ * via ioctl call.
+ *===========================================================*/
+
+
+static int execute_command(struct sock *sk, unsigned char cmd, unsigned int flags)
+{
+ netdevice_t *dev;
+ wanpipe_common_t *chan=NULL;
+ int err=0;
+ DECLARE_WAITQUEUE(wait, current);
+
+ dev = dev_get_by_index(sk->bound_dev_if);
+ if (dev == NULL){
+ printk(KERN_INFO "wansock: Exec failed no dev %i\n",
+ sk->bound_dev_if);
+ return -ENODEV;
+ }
+ dev_put(dev);
+
+ if ((chan=dev->priv) == NULL){
+ printk(KERN_INFO "wansock: Exec cmd failed no priv area\n");
+ return -ENODEV;
+ }
+
+ if (atomic_read(&chan->command)){
+ printk(KERN_INFO "wansock: ERROR: Command already running %x, %s\n",
+ atomic_read(&chan->command),dev->name);
+ return -EINVAL;
+ }
+
+ if (!sk->protinfo.af_wanpipe->mbox){
+ printk(KERN_INFO "wansock: In execute without MBOX\n");
+ return -EINVAL;
+ }
+
+ ((mbox_cmd_t*)sk->protinfo.af_wanpipe->mbox)->cmd.command=cmd;
+ ((mbox_cmd_t*)sk->protinfo.af_wanpipe->mbox)->cmd.lcn =
+ sk->protinfo.af_wanpipe->lcn;
+ ((mbox_cmd_t*)sk->protinfo.af_wanpipe->mbox)->cmd.result=0x7F;
+
+
+ if (flags & O_NONBLOCK){
+ cmd |= 0x80;
+ atomic_set(&chan->command, cmd);
+ }else{
+ atomic_set(&chan->command, cmd);
+ }
+
+ add_wait_queue(sk->sleep,&wait);
+ current->state = TASK_INTERRUPTIBLE;
+ for (;;){
+ if (((mbox_cmd_t*)sk->protinfo.af_wanpipe->mbox)->cmd.result != 0x7F) {
+ err = 0;
+ break;
+ }
+ if (signal_pending(current)) {
+ err = -ERESTARTSYS;
+ break;
+ }
+ schedule();
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(sk->sleep,&wait);
+
+ return err;
+}
+
+/*============================================================
+ * wanpipe_destroy_timer
+ *
+ * Used by wanpipe_release, to delay release of
+ * the socket.
+ *===========================================================*/
+
+static void wanpipe_destroy_timer(unsigned long data)
+{
+ struct sock *sk=(struct sock *)data;
+
+ if ((!atomic_read(&sk->wmem_alloc) && !atomic_read(&sk->rmem_alloc)) ||
+ (++sk->protinfo.af_wanpipe->force == 5)) {
+
+ if (atomic_read(&sk->wmem_alloc) || atomic_read(&sk->rmem_alloc))
+ printk(KERN_INFO "wansock: Warning, Packet Discarded due to sock shutdown!\n");
+
+ if (sk->protinfo.af_wanpipe){
+ kfree(sk->protinfo.af_wanpipe);
+ sk->protinfo.af_wanpipe=NULL;
+ }
+
+ #ifdef LINUX_2_4
+ if (atomic_read(&sk->refcnt) != 1){
+ atomic_set(&sk->refcnt,1);
+ DBG_PRINTK(KERN_INFO "wansock: Error, wrong reference count: %i ! :delay.\n",
+ atomic_read(&sk->refcnt));
+ }
+ sock_put(sk);
+ #else
+ sk_free(sk);
+ #endif
+ atomic_dec(&wanpipe_socks_nr);
+ MOD_DEC_USE_COUNT;
+ return;
+ }
+
+ sk->timer.expires=jiffies+5*HZ;
+ add_timer(&sk->timer);
+ printk(KERN_INFO "wansock: packet sk destroy delayed\n");
+}
+
+/*============================================================
+ * wanpipe_unlink_driver
+ *
+ * When the socket is released, this function is
+ * used to remove links that bind the sock and the
+ * driver together.
+ *===========================================================*/
+static void wanpipe_unlink_driver (struct sock *sk)
+{
+ netdevice_t *dev;
+ wanpipe_common_t *chan=NULL;
+
+ sk->zapped=0;
+ sk->state = WANSOCK_DISCONNECTED;
+ sk->protinfo.af_wanpipe->dev = NULL;
+
+ dev = dev_get_by_index(sk->bound_dev_if);
+ if (!dev){
+ printk(KERN_INFO "wansock: No dev on release\n");
+ return;
+ }
+ dev_put(dev);
+
+ if ((chan = dev->priv) == NULL){
+ printk(KERN_INFO "wansock: No Priv Area on release\n");
+ return;
+ }
+
+ set_bit(0,&chan->common_critical);
+ chan->sk=NULL;
+ chan->func=NULL;
+ chan->mbox=NULL;
+ chan->tx_timer=NULL;
+ clear_bit(0,&chan->common_critical);
+ release_device(dev);
+
+ return;
+}
+
+/*============================================================
+ * wanpipe_link_driver
+ *
+ * Upon successful bind(), sock is linked to a driver
+ * by binding in the wanpipe_rcv() bottom half handler
+ * to the driver function pointer, as well as sock and
+ * sock mailbox addresses. This way driver can pass
+ * data up the socket.
+ *===========================================================*/
+
+static void wanpipe_link_driver (netdevice_t *dev, struct sock *sk)
+{
+ wanpipe_common_t *chan = dev->priv;
+ if (!chan)
+ return;
+ set_bit(0,&chan->common_critical);
+ chan->sk=sk;
+ chan->func=wanpipe_rcv;
+ chan->mbox=sk->protinfo.af_wanpipe->mbox;
+ chan->tx_timer = &sk->protinfo.af_wanpipe->tx_timer;
+ sk->protinfo.af_wanpipe->dev=dev;
+ sk->zapped = 1;
+ clear_bit(0,&chan->common_critical);
+}
+
+
+/*============================================================
+ * release_device
+ *
+ * During sock release, clear a critical bit, which
+ * marks the device a being taken.
+ *===========================================================*/
+
+
+static void release_device (netdevice_t *dev)
+{
+ wanpipe_common_t *chan=dev->priv;
+ clear_bit(0,(void*)&chan->rw_bind);
+}
+
+/*============================================================
+ * wanpipe_release
+ *
+ * Close a PACKET socket. This is fairly simple. We
+ * immediately go to 'closed' state and remove our
+ * protocol entry in the device list.
+ *===========================================================*/
+
+#ifdef LINUX_2_4
+static int wanpipe_release(struct socket *sock)
+#else
+static int wanpipe_release(struct socket *sock, struct socket *peersock)
+#endif
+{
+
+#ifndef LINUX_2_4
+ struct sk_buff *skb;
+#endif
+ struct sock *sk = sock->sk;
+ struct sock **skp;
+
+ if (!sk)
+ return 0;
+
+ check_write_queue(sk);
+
+ /* Kill the tx timer, if we don't kill it now, the timer
+ * will run after we kill the sock. Timer code will
+ * try to access the sock which has been killed and cause
+ * kernel panic */
+
+ del_timer(&sk->protinfo.af_wanpipe->tx_timer);
+
+ /*
+ * Unhook packet receive handler.
+ */
+
+ if (sk->num == htons(X25_PROT) && sk->state != WANSOCK_DISCONNECTED && sk->zapped){
+ netdevice_t *dev = dev_get_by_index(sk->bound_dev_if);
+ wanpipe_common_t *chan;
+ if (dev){
+ chan=dev->priv;
+ atomic_set(&chan->disconnect,1);
+ DBG_PRINTK(KERN_INFO "wansock: Sending Clear Indication %i\n",
+ sk->state);
+ dev_put(dev);
+ }
+ }
+
+ set_bit(1,&wanpipe_tx_critical);
+ write_lock(&wanpipe_sklist_lock);
+ for (skp = &wanpipe_sklist; *skp; skp = &(*skp)->next) {
+ if (*skp == sk) {
+ *skp = sk->next;
+ __sock_put(sk);
+ break;
+ }
+ }
+ write_unlock(&wanpipe_sklist_lock);
+ clear_bit(1,&wanpipe_tx_critical);
+
+
+
+ release_driver(sk);
+
+
+ /*
+ * Now the socket is dead. No more input will appear.
+ */
+
+ sk->state_change(sk); /* It is useless. Just for sanity. */
+
+ sock->sk = NULL;
+ sk->socket = NULL;
+ sk->dead = 1;
+
+ /* Purge queues */
+#ifdef LINUX_2_4
+ skb_queue_purge(&sk->receive_queue);
+ skb_queue_purge(&sk->write_queue);
+ skb_queue_purge(&sk->error_queue);
+#else
+
+ while ((skb=skb_dequeue(&sk->receive_queue))!=NULL){
+ kfree_skb(skb);
+ }
+ while ((skb=skb_dequeue(&sk->error_queue))!=NULL){
+ kfree_skb(skb);
+ }
+ while ((skb=skb_dequeue(&sk->write_queue))!=NULL){
+ kfree_skb(skb);
+ }
+#endif
+ if (atomic_read(&sk->rmem_alloc) || atomic_read(&sk->wmem_alloc)) {
+ del_timer(&sk->timer);
+ printk(KERN_INFO "wansock: Killing in Timer R %i , W %i\n",
+ atomic_read(&sk->rmem_alloc),atomic_read(&sk->wmem_alloc));
+ sk->timer.data=(unsigned long)sk;
+ sk->timer.expires=jiffies+HZ;
+ sk->timer.function=wanpipe_destroy_timer;
+ add_timer(&sk->timer);
+ return 0;
+ }
+
+ if (sk->protinfo.af_wanpipe){
+ kfree(sk->protinfo.af_wanpipe);
+ sk->protinfo.af_wanpipe=NULL;
+ }
+
+ #ifdef LINUX_2_4
+ if (atomic_read(&sk->refcnt) != 1){
+ DBG_PRINTK(KERN_INFO "wansock: Error, wrong reference count: %i !:release.\n",
+ atomic_read(&sk->refcnt));
+ atomic_set(&sk->refcnt,1);
+ }
+ sock_put(sk);
+ #else
+ sk_free(sk);
+ #endif
+ atomic_dec(&wanpipe_socks_nr);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+/*============================================================
+ * check_write_queue
+ *
+ * During sock shutdown, if the sock state is
+ * WANSOCK_CONNECTED and there is transmit data
+ * pending. Wait until data is released
+ * before proceeding.
+ *===========================================================*/
+
+static void check_write_queue(struct sock *sk)
+{
+
+ if (sk->state != WANSOCK_CONNECTED)
+ return;
+
+ if (!atomic_read(&sk->wmem_alloc))
+ return;
+
+ printk(KERN_INFO "wansock: MAJOR ERROR, Data lost on sock release !!!\n");
+
+}
+
+/*============================================================
+ * release_driver
+ *
+ * This function is called during sock shutdown, to
+ * release any resources and links that bind the sock
+ * to the driver. It also changes the state of the
+ * sock to WANSOCK_DISCONNECTED
+ *===========================================================*/
+
+static void release_driver(struct sock *sk)
+{
+ struct sk_buff *skb=NULL;
+ struct sock *deadsk=NULL;
+
+ if (sk->state == WANSOCK_LISTEN || sk->state == WANSOCK_BIND_LISTEN){
+ while ((skb=skb_dequeue(&sk->receive_queue))!=NULL){
+ if ((deadsk = get_newsk_from_skb(skb))){
+ DBG_PRINTK (KERN_INFO "wansock: RELEASE: FOUND DEAD SOCK\n");
+ deadsk->dead=1;
+ start_cleanup_timer(deadsk);
+ }
+ kfree_skb(skb);
+ }
+ if (sk->zapped)
+ wanpipe_unlink_card(sk);
+ }else{
+ if (sk->zapped)
+ wanpipe_unlink_driver(sk);
+ }
+ sk->state = WANSOCK_DISCONNECTED;
+ sk->bound_dev_if = 0;
+ sk->zapped=0;
+
+ if (sk->protinfo.af_wanpipe){
+ if (sk->protinfo.af_wanpipe->mbox){
+ kfree(sk->protinfo.af_wanpipe->mbox);
+ sk->protinfo.af_wanpipe->mbox=NULL;
+ }
+ }
+}
+
+/*============================================================
+ * start_cleanup_timer
+ *
+ * If new incoming call's are pending but the socket
+ * is being released, start the timer which will
+ * envoke the kill routines for pending socks.
+ *===========================================================*/
+
+
+static void start_cleanup_timer (struct sock *sk)
+{
+ del_timer(&sk->timer);
+ sk->timer.data = (unsigned long)sk;
+ sk->timer.expires = jiffies + HZ;
+ sk->timer.function = wanpipe_kill_sock_timer;
+ add_timer(&sk->timer);
+}
+
+
+/*============================================================
+ * wanpipe_kill_sock
+ *
+ * This is a function which performs actual killing
+ * of the sock. It releases socket resources,
+ * and unlinks the sock from the driver.
+ *===========================================================*/
+
+static void wanpipe_kill_sock_timer (unsigned long data)
+{
+
+ struct sock *sk = (struct sock *)data;
+#ifndef LINUX_2_4
+ struct sk_buff *skb;
+#endif
+
+ struct sock **skp;
+
+ if (!sk)
+ return;
+
+ /* This functin can be called from interrupt. We must use
+ * appropriate locks */
+
+ if (test_bit(1,&wanpipe_tx_critical)){
+ sk->timer.expires=jiffies+10;
+ add_timer(&sk->timer);
+ return;
+ }
+
+ write_lock(&wanpipe_sklist_lock);
+ for (skp = &wanpipe_sklist; *skp; skp = &(*skp)->next) {
+ if (*skp == sk) {
+ *skp = sk->next;
+ __sock_put(sk);
+ break;
+ }
+ }
+ write_unlock(&wanpipe_sklist_lock);
+
+
+ if (sk->num == htons(X25_PROT) && sk->state != WANSOCK_DISCONNECTED){
+ netdevice_t *dev = dev_get_by_index(sk->bound_dev_if);
+ wanpipe_common_t *chan;
+ if (dev){
+ chan=dev->priv;
+ atomic_set(&chan->disconnect,1);
+ dev_put(dev);
+ }
+ }
+
+ release_driver(sk);
+
+ sk->socket = NULL;
+
+ /* Purge queues */
+#ifdef LINUX_2_4
+ skb_queue_purge(&sk->receive_queue);
+ skb_queue_purge(&sk->write_queue);
+ skb_queue_purge(&sk->error_queue);
+#else
+ while ((skb=skb_dequeue(&sk->receive_queue)) != NULL){
+ kfree_skb(skb);
+ }
+ while ((skb=skb_dequeue(&sk->write_queue)) != NULL) {
+ kfree_skb(skb);
+ }
+ while ((skb=skb_dequeue(&sk->error_queue)) != NULL){
+ kfree_skb(skb);
+ }
+#endif
+
+ if (atomic_read(&sk->rmem_alloc) || atomic_read(&sk->wmem_alloc)) {
+ del_timer(&sk->timer);
+ printk(KERN_INFO "wansock: Killing SOCK in Timer\n");
+ sk->timer.data=(unsigned long)sk;
+ sk->timer.expires=jiffies+HZ;
+ sk->timer.function=wanpipe_destroy_timer;
+ add_timer(&sk->timer);
+ return;
+ }
+
+ if (sk->protinfo.af_wanpipe){
+ kfree(sk->protinfo.af_wanpipe);
+ sk->protinfo.af_wanpipe=NULL;
+ }
+
+ #ifdef LINUX_2_4
+ if (atomic_read(&sk->refcnt) != 1){
+ atomic_set(&sk->refcnt,1);
+ DBG_PRINTK(KERN_INFO "wansock: Error, wrong reference count: %i ! :timer.\n",
+ atomic_read(&sk->refcnt));
+ }
+ sock_put(sk);
+ #else
+ sk_free(sk);
+ #endif
+ atomic_dec(&wanpipe_socks_nr);
+ MOD_DEC_USE_COUNT;
+ return;
+}
+
+static void wanpipe_kill_sock_accept (struct sock *sk)
+{
+
+ struct sock **skp;
+
+ if (!sk)
+ return;
+
+ /* This functin can be called from interrupt. We must use
+ * appropriate locks */
+
+ write_lock(&wanpipe_sklist_lock);
+ for (skp = &wanpipe_sklist; *skp; skp = &(*skp)->next) {
+ if (*skp == sk) {
+ *skp = sk->next;
+ __sock_put(sk);
+ break;
+ }
+ }
+ write_unlock(&wanpipe_sklist_lock);
+
+ sk->socket = NULL;
+
+
+ if (sk->protinfo.af_wanpipe){
+ kfree(sk->protinfo.af_wanpipe);
+ sk->protinfo.af_wanpipe=NULL;
+ }
+
+ #ifdef LINUX_2_4
+ if (atomic_read(&sk->refcnt) != 1){
+ atomic_set(&sk->refcnt,1);
+ DBG_PRINTK(KERN_INFO "wansock: Error, wrong reference count: %i ! :timer.\n",
+ atomic_read(&sk->refcnt));
+ }
+ sock_put(sk);
+ #else
+ sk_free(sk);
+ #endif
+ atomic_dec(&wanpipe_socks_nr);
+ MOD_DEC_USE_COUNT;
+ return;
+}
+
+
+static void wanpipe_kill_sock_irq (struct sock *sk)
+{
+
+ if (!sk)
+ return;
+
+ sk->socket = NULL;
+
+ if (sk->protinfo.af_wanpipe){
+ kfree(sk->protinfo.af_wanpipe);
+ sk->protinfo.af_wanpipe=NULL;
+ }
+
+ #ifdef LINUX_2_4
+ if (atomic_read(&sk->refcnt) != 1){
+ atomic_set(&sk->refcnt,1);
+ DBG_PRINTK(KERN_INFO "wansock: Error, wrong reference count: %i !:listen.\n",
+ atomic_read(&sk->refcnt));
+ }
+ sock_put(sk);
+ #else
+ sk_free(sk);
+ #endif
+ atomic_dec(&wanpipe_socks_nr);
+ MOD_DEC_USE_COUNT;
+ return;
+}
+
+
+/*============================================================
+ * wanpipe_do_bind
+ *
+ * Bottom half of the binding system call.
+ * Once the wanpipe_bind() function checks the
+ * legality of the call, this function binds the
+ * sock to the driver.
+ *===========================================================*/
+
+static int wanpipe_do_bind(struct sock *sk, netdevice_t *dev, int protocol)
+{
+ wanpipe_common_t *chan=NULL;
+ int err=0;
+
+ if (sk->zapped){
+ err = -EALREADY;
+ goto bind_unlock_exit;
+ }
+
+ sk->num = protocol;
+
+ if (protocol == 0){
+ release_device(dev);
+ err = -EINVAL;
+ goto bind_unlock_exit;
+ }
+
+ if (dev) {
+ if (dev->flags&IFF_UP) {
+ chan=dev->priv;
+ sk->state = chan->state;
+
+ if (sk->num == htons(X25_PROT) &&
+ sk->state != WANSOCK_DISCONNECTED &&
+ sk->state != WANSOCK_CONNECTING){
+ DBG_PRINTK(KERN_INFO
+ "wansock: Binding to Device not DISCONNECTED %i\n",
+ sk->state);
+ release_device(dev);
+ err = -EAGAIN;
+ goto bind_unlock_exit;
+ }
+
+ wanpipe_link_driver(dev,sk);
+ sk->bound_dev_if = dev->ifindex;
+
+ /* X25 Specific option */
+ if (sk->num == htons(X25_PROT))
+ sk->protinfo.af_wanpipe->svc = chan->svc;
+
+ } else {
+ sk->err = ENETDOWN;
+ sk->error_report(sk);
+ release_device(dev);
+ err = -EINVAL;
+ }
+ } else {
+ err = -ENODEV;
+ }
+bind_unlock_exit:
+ /* FIXME where is this lock */
+
+ return err;
+}
+
+/*============================================================
+ * wanpipe_bind
+ *
+ * BIND() System call, which is bound to the AF_WANPIPE
+ * operations structure. It checks for correct wanpipe
+ * card name, and cross references interface names with
+ * the card names. Thus, interface name must belong to
+ * the actual card.
+ *===========================================================*/
+
+
+static int wanpipe_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct wan_sockaddr_ll *sll = (struct wan_sockaddr_ll*)uaddr;
+ struct sock *sk=sock->sk;
+ netdevice_t *dev = NULL;
+ sdla_t *card=NULL;
+ char name[15];
+
+ /*
+ * Check legality
+ */
+
+ if (addr_len < sizeof(struct wan_sockaddr_ll)){
+ printk(KERN_INFO "wansock: Address length error\n");
+ return -EINVAL;
+ }
+ if (sll->sll_family != AF_WANPIPE){
+ printk(KERN_INFO "wansock: Illegal family name specified.\n");
+ return -EINVAL;
+ }
+
+ card = wanpipe_find_card (sll->sll_card);
+ if (!card){
+ printk(KERN_INFO "wansock: Wanpipe card not found: %s\n",sll->sll_card);
+ return -ENODEV;
+ }else{
+ sk->protinfo.af_wanpipe->card = (void *)card;
+ }
+
+ if (!strcmp(sll->sll_device,"svc_listen")){
+
+ /* Bind a sock to a card structure for listening
+ */
+ int err=0;
+
+ /* This is x25 specific area if protocol doesn't
+ * match, return error */
+ if (sll->sll_protocol != htons(X25_PROT))
+ return -EINVAL;
+
+ err= wanpipe_link_card (sk);
+ if (err < 0)
+ return err;
+
+ if (sll->sll_protocol)
+ sk->num = sll->sll_protocol;
+ sk->state = WANSOCK_BIND_LISTEN;
+ return 0;
+
+ }else if (!strcmp(sll->sll_device,"svc_connect")){
+
+ /* This is x25 specific area if protocol doesn't
+ * match, return error */
+ if (sll->sll_protocol != htons(X25_PROT))
+ return -EINVAL;
+
+ /* Find a free device
+ */
+ dev = wanpipe_find_free_dev(card);
+ if (dev == NULL){
+ DBG_PRINTK(KERN_INFO "wansock: No free network devices for card %s\n",
+ card->devname);
+ return -EINVAL;
+ }
+ }else{
+ /* Bind a socket to a interface name
+ * This is used by PVC mostly
+ */
+ strncpy(name,sll->sll_device,14);
+ name[14]=0;
+#ifdef LINUX_2_4
+ dev = dev_get_by_name(name);
+#else
+ dev = dev_get(name);
+#endif
+ if (dev == NULL){
+ printk(KERN_INFO "wansock: Failed to get Dev from name: %s,\n",
+ name);
+ return -ENODEV;
+ }
+
+ dev_put(dev);
+
+ if (check_dev(dev, card)){
+ printk(KERN_INFO "wansock: Device %s, doesn't belong to card %s\n",
+ dev->name, card->devname);
+ return -EINVAL;
+ }
+ if (get_atomic_device (dev))
+ return -EINVAL;
+ }
+
+ return wanpipe_do_bind(sk, dev, sll->sll_protocol ? : sk->num);
+}
+
+/*============================================================
+ * get_atomic_device
+ *
+ * Sets a bit atomically which indicates that
+ * the interface is taken. This avoids race conditions.
+ *===========================================================*/
+
+
+static inline int get_atomic_device (netdevice_t *dev)
+{
+ wanpipe_common_t *chan = dev->priv;
+ if (!test_and_set_bit(0,(void *)&chan->rw_bind)){
+ return 0;
+ }
+ return 1;
+}
+
+/*============================================================
+ * check_dev
+ *
+ * Check that device name belongs to a particular card.
+ *===========================================================*/
+
+static int check_dev (netdevice_t *dev, sdla_t *card)
+{
+ netdevice_t* tmp_dev;
+
+ for (tmp_dev = card->wandev.dev; tmp_dev; tmp_dev=*((netdevice_t**)tmp_dev->priv)){
+ if (tmp_dev->ifindex == dev->ifindex){
+ return 0;
+ }
+ }
+ return 1;
+}
+
+/*============================================================
+ * wanpipe_find_free_dev
+ *
+ * Find a free network interface. If found set atomic
+ * bit indicating that the interface is taken.
+ * X25API Specific.
+ *===========================================================*/
+
+netdevice_t * wanpipe_find_free_dev (sdla_t *card)
+{
+ netdevice_t* dev;
+ volatile wanpipe_common_t *chan;
+
+ if (test_and_set_bit(0,&find_free_critical)){
+ printk(KERN_INFO "CRITICAL in Find Free\n");
+ }
+
+ for (dev = card->wandev.dev; dev; dev=*((netdevice_t**)dev->priv)){
+ chan = dev->priv;
+ if (!chan)
+ continue;
+ if (chan->usedby == API && chan->svc){
+ if (!get_atomic_device (dev)){
+ if (chan->state != WANSOCK_DISCONNECTED){
+ release_device(dev);
+ }else{
+ clear_bit(0,&find_free_critical);
+ return dev;
+ }
+ }
+ }
+ }
+ clear_bit(0,&find_free_critical);
+ return NULL;
+}
+
+/*============================================================
+ * wanpipe_create
+ *
+ * SOCKET() System call. It allocates a sock structure
+ * and adds the socket to the wanpipe_sk_list.
+ * Crates AF_WANPIPE socket.
+ *===========================================================*/
+
+static int wanpipe_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+
+ //FIXME: This checks for root user, SECURITY ?
+ //if (!capable(CAP_NET_RAW))
+ // return -EPERM;
+
+ if (sock->type != SOCK_DGRAM && sock->type != SOCK_RAW)
+ return -ESOCKTNOSUPPORT;
+
+ sock->state = SS_UNCONNECTED;
+
+ if ((sk = wanpipe_alloc_socket()) == NULL)
+ return -ENOBUFS;
+
+ sk->reuse = 1;
+ sock->ops = &wanpipe_ops;
+ sock_init_data(sock,sk);
+
+ sk->zapped=0;
+ sk->family = PF_WANPIPE;
+ sk->num = protocol;
+ sk->state = WANSOCK_DISCONNECTED;
+ sk->ack_backlog = 0;
+ sk->bound_dev_if=0;
+
+ atomic_inc(&wanpipe_socks_nr);
+
+ /* We must disable interrupts because the ISR
+ * can also change the list */
+ set_bit(1,&wanpipe_tx_critical);
+ write_lock(&wanpipe_sklist_lock);
+ sk->next = wanpipe_sklist;
+ wanpipe_sklist = sk;
+ sock_hold(sk);
+ write_unlock(&wanpipe_sklist_lock);
+ clear_bit(1,&wanpipe_tx_critical);
+
+ return(0);
+}
+
+
+/*============================================================
+ * wanpipe_recvmsg
+ *
+ * Pull a packet from our receive queue and hand it
+ * to the user. If necessary we block.
+ *===========================================================*/
+
+static int wanpipe_recvmsg(struct socket *sock, struct msghdr *msg, int len,
+ int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sk_buff *skb;
+ int copied, err=-ENOBUFS;
+
+
+ /*
+ * If the address length field is there to be filled in, we fill
+ * it in now.
+ */
+
+ msg->msg_namelen = sizeof(struct wan_sockaddr_ll);
+
+ /*
+ * Call the generic datagram receiver. This handles all sorts
+ * of horrible races and re-entrancy so we can forget about it
+ * in the protocol layers.
+ *
+ * Now it will return ENETDOWN, if device have just gone down,
+ * but then it will block.
+ */
+
+ if (flags & MSG_OOB){
+ skb=skb_dequeue(&sk->error_queue);
+ }else{
+ skb=skb_recv_datagram(sk,flags,1,&err);
+ }
+ /*
+ * An error occurred so return it. Because skb_recv_datagram()
+ * handles the blocking we don't see and worry about blocking
+ * retries.
+ */
+
+ if(skb==NULL)
+ goto out;
+
+ /*
+ * You lose any data beyond the buffer you gave. If it worries a
+ * user program they can ask the device for its MTU anyway.
+ */
+
+ copied = skb->len;
+ if (copied > len)
+ {
+ copied=len;
+ msg->msg_flags|=MSG_TRUNC;
+ }
+
+ wanpipe_wakeup_driver(sk);
+
+ /* We can't use skb_copy_datagram here */
+ err = memcpy_toiovec(msg->msg_iov, skb->data, copied);
+ if (err)
+ goto out_free;
+
+#ifdef LINUX_2_1
+ sk->stamp=skb->stamp;
+#else
+ sock_recv_timestamp(msg, sk, skb);
+#endif
+
+ if (msg->msg_name)
+ memcpy(msg->msg_name, skb->cb, msg->msg_namelen);
+
+ /*
+ * Free or return the buffer as appropriate. Again this
+ * hides all the races and re-entrancy issues from us.
+ */
+ err = (flags&MSG_TRUNC) ? skb->len : copied;
+
+out_free:
+ skb_free_datagram(sk, skb);
+out:
+ return err;
+}
+
+
+/*============================================================
+ * wanpipe_wakeup_driver
+ *
+ * If socket receive buffer is full and driver cannot
+ * pass data up the sock, it sets a packet_block flag.
+ * This function check that flag and if sock receive
+ * queue has room it kicks the driver BH handler.
+ *
+ * This way, driver doesn't have to poll the sock
+ * receive queue.
+ *===========================================================*/
+
+static void wanpipe_wakeup_driver(struct sock *sk)
+{
+ netdevice_t *dev=NULL;
+ wanpipe_common_t *chan=NULL;
+
+ dev = dev_get_by_index(sk->bound_dev_if);
+ if (!dev)
+ return;
+
+ dev_put(dev);
+
+ if ((chan = dev->priv) == NULL)
+ return;
+
+ if (atomic_read(&chan->receive_block)){
+ if (atomic_read(&sk->rmem_alloc) < ((unsigned)sk->rcvbuf*0.9) ){
+ printk(KERN_INFO "wansock: Queuing task for wanpipe\n");
+ atomic_set(&chan->receive_block,0);
+ wanpipe_queue_tq(&chan->wanpipe_task);
+ wanpipe_mark_bh();
+ }
+ }
+}
+
+/*============================================================
+ * wanpipe_getname
+ *
+ * I don't know what to do with this yet.
+ * User can use this function to get sock address
+ * information. Not very useful for Sangoma's purposes.
+ *===========================================================*/
+
+
+static int wanpipe_getname(struct socket *sock, struct sockaddr *uaddr,
+ int *uaddr_len, int peer)
+{
+ netdevice_t *dev;
+ struct sock *sk = sock->sk;
+ struct wan_sockaddr_ll *sll = (struct wan_sockaddr_ll*)uaddr;
+
+ sll->sll_family = AF_WANPIPE;
+ sll->sll_ifindex = sk->bound_dev_if;
+ sll->sll_protocol = sk->num;
+ dev = dev_get_by_index(sk->bound_dev_if);
+ if (dev) {
+ sll->sll_hatype = dev->type;
+ sll->sll_halen = dev->addr_len;
+ memcpy(sll->sll_addr, dev->dev_addr, dev->addr_len);
+ } else {
+ sll->sll_hatype = 0; /* Bad: we have no ARPHRD_UNSPEC */
+ sll->sll_halen = 0;
+ }
+ *uaddr_len = sizeof(*sll);
+
+ dev_put(dev);
+
+ return 0;
+}
+
+/*============================================================
+ * wanpipe_notifier
+ *
+ * If driver turns off network interface, this function
+ * will be envoked. Currently I treate it as a
+ * call disconnect. More thought should go into this
+ * function.
+ *
+ * FIXME: More thought should go into this function.
+ *
+ *===========================================================*/
+
+static int wanpipe_notifier(struct notifier_block *this, unsigned long msg, void *data)
+{
+ struct sock *sk;
+ netdevice_t *dev = (netdevice_t*)data;
+ struct wanpipe_opt *po;
+
+ for (sk = wanpipe_sklist; sk; sk = sk->next) {
+
+ if ((po = sk->protinfo.af_wanpipe)==NULL)
+ continue;
+ if (dev == NULL)
+ continue;
+
+ switch (msg) {
+ case NETDEV_DOWN:
+ case NETDEV_UNREGISTER:
+ if (dev->ifindex == sk->bound_dev_if) {
+ printk(KERN_INFO "wansock: Device down %s\n",dev->name);
+ if (sk->zapped){
+ wanpipe_unlink_driver(sk);
+ sk->err = ENETDOWN;
+ sk->error_report(sk);
+ }
+
+ if (msg == NETDEV_UNREGISTER) {
+ printk(KERN_INFO "wansock: Unregistering Device: %s\n",
+ dev->name);
+ wanpipe_unlink_driver(sk);
+ sk->bound_dev_if = 0;
+ }
+ }
+ break;
+ case NETDEV_UP:
+ if (dev->ifindex == sk->bound_dev_if && sk->num && !sk->zapped) {
+ printk(KERN_INFO "wansock: Registering Device: %s\n",
+ dev->name);
+ wanpipe_link_driver(dev,sk);
+ }
+ break;
+ }
+ }
+ return NOTIFY_DONE;
+}
+
+/*============================================================
+ * wanpipe_ioctl
+ *
+ * Execute a user commands, and set socket options.
+ *
+ * FIXME: More thought should go into this function.
+ *
+ *===========================================================*/
+
+static int wanpipe_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+ int err;
+ int pid;
+
+ switch(cmd)
+ {
+ case FIOSETOWN:
+ case SIOCSPGRP:
+ err = get_user(pid, (int *) arg);
+ if (err)
+ return err;
+ if (current->pid != pid && current->pgrp != -pid &&
+ !capable(CAP_NET_ADMIN))
+ return -EPERM;
+ sk->proc = pid;
+ return(0);
+ case FIOGETOWN:
+ case SIOCGPGRP:
+ return put_user(sk->proc, (int *)arg);
+ case SIOCGSTAMP:
+ if(sk->stamp.tv_sec==0)
+ return -ENOENT;
+ err = -EFAULT;
+ if (!copy_to_user((void *)arg, &sk->stamp, sizeof(struct timeval)))
+ err = 0;
+ return err;
+
+ case SIOC_WANPIPE_CHECK_TX:
+
+ return atomic_read(&sk->wmem_alloc);
+
+ case SIOC_WANPIPE_SOCK_STATE:
+
+ if (sk->state == WANSOCK_CONNECTED)
+ return 0;
+
+ return 1;
+
+
+ case SIOC_WANPIPE_GET_CALL_DATA:
+
+ return get_ioctl_cmd (sk,(void*)arg);
+
+ case SIOC_WANPIPE_SET_CALL_DATA:
+
+ return set_ioctl_cmd (sk,(void*)arg);
+
+ case SIOC_WANPIPE_ACCEPT_CALL:
+ case SIOC_WANPIPE_CLEAR_CALL:
+ case SIOC_WANPIPE_RESET_CALL:
+
+ if ((err=set_ioctl_cmd(sk,(void*)arg)) < 0)
+ return err;
+
+ err=wanpipe_exec_cmd(sk,cmd,0);
+ get_ioctl_cmd(sk,(void*)arg);
+ return err;
+
+ case SIOC_WANPIPE_DEBUG:
+
+ return wanpipe_debug(sk,(void*)arg);
+
+ case SIOC_WANPIPE_SET_NONBLOCK:
+
+ if (sk->state != WANSOCK_DISCONNECTED)
+ return -EINVAL;
+
+ sock->file->f_flags |= O_NONBLOCK;
+ return 0;
+
+ case SIOCGIFFLAGS:
+#ifndef CONFIG_INET
+ case SIOCSIFFLAGS:
+#endif
+ case SIOCGIFCONF:
+ case SIOCGIFMETRIC:
+ case SIOCSIFMETRIC:
+ case SIOCGIFMEM:
+ case SIOCSIFMEM:
+ case SIOCGIFMTU:
+ case SIOCSIFMTU:
+ case SIOCSIFLINK:
+ case SIOCGIFHWADDR:
+ case SIOCSIFHWADDR:
+ case SIOCSIFMAP:
+ case SIOCGIFMAP:
+ case SIOCSIFSLAVE:
+ case SIOCGIFSLAVE:
+ case SIOCGIFINDEX:
+ case SIOCGIFNAME:
+ case SIOCGIFCOUNT:
+ case SIOCSIFHWBROADCAST:
+ return(dev_ioctl(cmd,(void *) arg));
+
+#ifdef CONFIG_INET
+ case SIOCADDRT:
+ case SIOCDELRT:
+ case SIOCDARP:
+ case SIOCGARP:
+ case SIOCSARP:
+ case SIOCDRARP:
+ case SIOCGRARP:
+ case SIOCSRARP:
+ case SIOCGIFADDR:
+ case SIOCSIFADDR:
+ case SIOCGIFBRDADDR:
+ case SIOCSIFBRDADDR:
+ case SIOCGIFNETMASK:
+ case SIOCSIFNETMASK:
+ case SIOCGIFDSTADDR:
+ case SIOCSIFDSTADDR:
+ case SIOCSIFFLAGS:
+ case SIOCADDDLCI:
+ case SIOCDELDLCI:
+ return inet_dgram_ops.ioctl(sock, cmd, arg);
+#endif
+
+ default:
+ if ((cmd >= SIOCDEVPRIVATE) &&
+ (cmd <= (SIOCDEVPRIVATE + 15)))
+ return(dev_ioctl(cmd,(void *) arg));
+
+#ifdef CONFIG_NET_RADIO
+ if((cmd >= SIOCIWFIRST) && (cmd <= SIOCIWLAST))
+ return(dev_ioctl(cmd,(void *) arg));
+#endif
+ return -EOPNOTSUPP;
+ }
+ /*NOTREACHED*/
+}
+
+/*============================================================
+ * wanpipe_debug
+ *
+ * This function will pass up information about all
+ * active sockets.
+ *
+ * FIXME: More thought should go into this function.
+ *
+ *===========================================================*/
+
+static int wanpipe_debug (struct sock *origsk, void *arg)
+{
+ struct sock *sk=NULL;
+ netdevice_t *dev=NULL;
+ wanpipe_common_t *chan=NULL;
+ int cnt=0, err=0;
+ wan_debug_t *dbg_data = (wan_debug_t *)arg;
+
+ for (sk = wanpipe_sklist; sk; sk = sk->next){
+
+ if (sk == origsk){
+ continue;
+ }
+
+ if ((err=put_user(1, &dbg_data->debug[cnt].free)))
+ return err;
+ if ((err=put_user(sk->state, &dbg_data->debug[cnt].sk_state)))
+ return err;
+ if ((err=put_user(sk->rcvbuf, &dbg_data->debug[cnt].rcvbuf)))
+ return err;
+ if ((err=put_user(atomic_read(&sk->rmem_alloc), &dbg_data->debug[cnt].rmem)))
+ return err;
+ if ((err=put_user(atomic_read(&sk->wmem_alloc), &dbg_data->debug[cnt].wmem)))
+ return err;
+ if ((err=put_user(sk->sndbuf, &dbg_data->debug[cnt].sndbuf)))
+ return err;
+ if ((err=put_user(sk_count, &dbg_data->debug[cnt].sk_count)))
+ return err;
+ if ((err=put_user(sk->protinfo.af_wanpipe->poll_cnt,
+ &dbg_data->debug[cnt].poll_cnt)))
+ return err;
+ if ((err=put_user(sk->bound_dev_if, &dbg_data->debug[cnt].bound)))
+ return err;
+
+ if (sk->bound_dev_if){
+ dev = dev_get_by_index(sk->bound_dev_if);
+ if (!dev)
+ continue;
+
+ chan=dev->priv;
+ dev_put(dev);
+
+ if ((err=put_user(chan->state, &dbg_data->debug[cnt].d_state)))
+ return err;
+ if ((err=put_user(chan->svc, &dbg_data->debug[cnt].svc)))
+ return err;
+
+ if ((err=put_user(atomic_read(&chan->command),
+ &dbg_data->debug[cnt].command)))
+ return err;
+
+
+ if (sk->protinfo.af_wanpipe){
+ sdla_t *card = (sdla_t*)sk->protinfo.af_wanpipe->card;
+
+ if (card){
+ if ((err=put_user(atomic_read(&card->u.x.command_busy),
+ &dbg_data->debug[cnt].cmd_busy)))
+ return err;
+ }
+
+ if ((err=put_user(sk->protinfo.af_wanpipe->lcn,
+ &dbg_data->debug[cnt].lcn)))
+ return err;
+
+ if (sk->protinfo.af_wanpipe->mbox){
+ if ((err=put_user(1, &dbg_data->debug[cnt].mbox)))
+ return err;
+ }
+ }
+
+ if ((err=put_user(atomic_read(&chan->receive_block),
+ &dbg_data->debug[cnt].rblock)))
+ return err;
+
+ if (copy_to_user(dbg_data->debug[cnt].name, dev->name, strlen(dev->name)))
+ return -EFAULT;
+ }
+
+ if (++cnt == MAX_NUM_DEBUG)
+ break;
+ }
+ return 0;
+}
+
+/*============================================================
+ * get_ioctl_cmd
+ *
+ * Pass up the contents of socket MBOX to the user.
+ *===========================================================*/
+
+static int get_ioctl_cmd (struct sock *sk, void *arg)
+{
+ x25api_t *usr_data = (x25api_t *)arg;
+ mbox_cmd_t *mbox_ptr;
+ int err;
+
+ if (usr_data == NULL)
+ return -EINVAL;
+
+ if (!sk->protinfo.af_wanpipe->mbox){
+ return -EINVAL;
+ }
+
+ mbox_ptr = (mbox_cmd_t *)sk->protinfo.af_wanpipe->mbox;
+
+ if ((err=put_user(mbox_ptr->cmd.qdm, &usr_data->hdr.qdm)))
+ return err;
+ if ((err=put_user(mbox_ptr->cmd.cause, &usr_data->hdr.cause)))
+ return err;
+ if ((err=put_user(mbox_ptr->cmd.diagn, &usr_data->hdr.diagn)))
+ return err;
+ if ((err=put_user(mbox_ptr->cmd.length, &usr_data->hdr.length)))
+ return err;
+ if ((err=put_user(mbox_ptr->cmd.result, &usr_data->hdr.result)))
+ return err;
+ if ((err=put_user(mbox_ptr->cmd.lcn, &usr_data->hdr.lcn)))
+ return err;
+
+ if (mbox_ptr->cmd.length > 0){
+ if (mbox_ptr->cmd.length > X25_MAX_DATA)
+ return -EINVAL;
+
+ if (copy_to_user(usr_data->data, mbox_ptr->data, mbox_ptr->cmd.length)){
+ printk(KERN_INFO "wansock: Copy failed !!!\n");
+ return -EFAULT;
+ }
+ }
+ return 0;
+}
+
+/*============================================================
+ * set_ioctl_cmd
+ *
+ * Before command can be execute, socket MBOX must
+ * be created, and initialized with user data.
+ *===========================================================*/
+
+static int set_ioctl_cmd (struct sock *sk, void *arg)
+{
+ x25api_t *usr_data = (x25api_t *)arg;
+ mbox_cmd_t *mbox_ptr;
+ int err;
+
+ if (!sk->protinfo.af_wanpipe->mbox){
+ void *mbox_ptr;
+ netdevice_t *dev = dev_get_by_index(sk->bound_dev_if);
+ if (!dev)
+ return -ENODEV;
+
+ dev_put(dev);
+
+ if ((mbox_ptr = kmalloc(sizeof(mbox_cmd_t), GFP_ATOMIC)) == NULL)
+ return -ENOMEM;
+
+ memset(mbox_ptr, 0, sizeof(mbox_cmd_t));
+ sk->protinfo.af_wanpipe->mbox = mbox_ptr;
+
+ wanpipe_link_driver(dev,sk);
+ }
+
+ mbox_ptr = (mbox_cmd_t*)sk->protinfo.af_wanpipe->mbox;
+ memset(mbox_ptr, 0, sizeof(mbox_cmd_t));
+
+ if (usr_data == NULL){
+ return 0;
+ }
+ if ((err=get_user(mbox_ptr->cmd.qdm, &usr_data->hdr.qdm)))
+ return err;
+ if ((err=get_user(mbox_ptr->cmd.cause, &usr_data->hdr.cause)))
+ return err;
+ if ((err=get_user(mbox_ptr->cmd.diagn, &usr_data->hdr.diagn)))
+ return err;
+ if ((err=get_user(mbox_ptr->cmd.length, &usr_data->hdr.length)))
+ return err;
+ if ((err=get_user(mbox_ptr->cmd.result, &usr_data->hdr.result)))
+ return err;
+
+ if (mbox_ptr->cmd.length > 0){
+ if (mbox_ptr->cmd.length > X25_MAX_DATA)
+ return -EINVAL;
+
+ if (copy_from_user(mbox_ptr->data, usr_data->data, mbox_ptr->cmd.length)){
+ printk(KERN_INFO "Copy failed\n");
+ return -EFAULT;
+ }
+ }
+ return 0;
+}
+
+
+/*======================================================================
+ * wanpipe_poll
+ *
+ * Datagram poll: Again totally generic. This also handles
+ * sequenced packet sockets providing the socket receive queue
+ * is only ever holding data ready to receive.
+ *
+ * Note: when you _don't_ use this routine for this protocol,
+ * and you use a different write policy from sock_writeable()
+ * then please supply your own write_space callback.
+ *=====================================================================*/
+
+unsigned int wanpipe_poll(struct file * file, struct socket *sock, poll_table *wait)
+{
+ struct sock *sk = sock->sk;
+ unsigned int mask;
+
+ ++sk->protinfo.af_wanpipe->poll_cnt;
+
+ poll_wait(file, sk->sleep, wait);
+ mask = 0;
+
+ /* exceptional events? */
+ if (sk->err || !skb_queue_empty(&sk->error_queue)){
+ mask |= POLLPRI;
+ return mask;
+ }
+ if (sk->shutdown & RCV_SHUTDOWN)
+ mask |= POLLHUP;
+
+ /* readable? */
+ if (!skb_queue_empty(&sk->receive_queue)){
+ mask |= POLLIN | POLLRDNORM;
+ }
+
+ /* connection hasn't started yet */
+ if (sk->state == WANSOCK_CONNECTING){
+ return mask;
+ }
+
+ if (sk->state == WANSOCK_DISCONNECTED){
+ mask = POLLPRI;
+ return mask;
+ }
+
+ /* This check blocks the user process if there is
+ * a packet already queued in the socket write queue.
+ * This option is only for X25API protocol, for other
+ * protocol like chdlc enable streaming mode,
+ * where multiple packets can be pending in the socket
+ * transmit queue */
+
+ if (sk->num == htons(X25_PROT)){
+ if (atomic_read(&sk->protinfo.af_wanpipe->packet_sent))
+ return mask;
+ }
+
+ /* writable? */
+ if (sock_writeable(sk)){
+ mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
+ }else{
+ #ifdef LINUX_2_4
+ set_bit(SOCK_ASYNC_NOSPACE, &sk->socket->flags);
+ #else
+ sk->socket->flags |= SO_NOSPACE;
+ #endif
+ }
+
+ return mask;
+}
+
+/*======================================================================
+ * wanpipe_listen
+ *
+ * X25API Specific function. Set a socket into LISTENING MODE.
+ *=====================================================================*/
+
+
+static int wanpipe_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+
+ /* This is x25 specific area if protocol doesn't
+ * match, return error */
+ if (sk->num != htons(X25_PROT))
+ return -EINVAL;
+
+ if (sk->state == WANSOCK_BIND_LISTEN) {
+
+ sk->max_ack_backlog = backlog;
+ sk->state = WANSOCK_LISTEN;
+ return 0;
+ }else{
+ printk(KERN_INFO "wansock: Listening sock was not binded\n");
+ }
+
+ return -EINVAL;
+}
+
+/*======================================================================
+ * wanpipe_link_card
+ *
+ * Connects the listening socket to the driver
+ *=====================================================================*/
+
+static int wanpipe_link_card (struct sock *sk)
+{
+ sdla_t *card;
+
+ card = (sdla_t*)sk->protinfo.af_wanpipe->card;
+ if (!card)
+ return -ENOMEM;
+
+ if ((card->sk != NULL) || (card->func != NULL)){
+ printk(KERN_INFO "wansock: Listening queue is already established\n");
+ return -EINVAL;
+ }
+
+ card->sk=sk;
+ card->func=wanpipe_listen_rcv;
+ sk->zapped=1;
+
+ return 0;
+}
+
+/*======================================================================
+ * wanpipe_listen
+ *
+ * X25API Specific function. Disconnect listening socket from
+ * the driver.
+ *=====================================================================*/
+
+static void wanpipe_unlink_card (struct sock *sk)
+{
+ sdla_t *card;
+
+ card = (sdla_t*)sk->protinfo.af_wanpipe->card;
+
+ if (card){
+ card->sk=NULL;
+ card->func=NULL;
+ }
+}
+
+/*======================================================================
+ * wanpipe_exec_cmd
+ *
+ * Ioctl function calls this function to execute user command.
+ * Connect() sytem call also calls this function to execute
+ * place call. This function blocks until command is executed.
+ *=====================================================================*/
+
+static int wanpipe_exec_cmd(struct sock *sk, int cmd, unsigned int flags)
+{
+ int err = -EINVAL;
+ mbox_cmd_t *mbox_ptr = (mbox_cmd_t*)sk->protinfo.af_wanpipe->mbox;
+
+ if (!mbox_ptr){
+ printk(KERN_INFO "NO MBOX PTR !!!!!\n");
+ return -EINVAL;
+ }
+
+ /* This is x25 specific area if protocol doesn't
+ * match, return error */
+ if (sk->num != htons(X25_PROT))
+ return -EINVAL;
+
+
+ switch (cmd){
+
+ case SIOC_WANPIPE_ACCEPT_CALL:
+
+ if (sk->state != WANSOCK_CONNECTING){
+ err = -EHOSTDOWN;
+ break;
+ }
+
+ err = execute_command(sk,X25_ACCEPT_CALL,0);
+ if (err < 0)
+ break;
+
+ /* Update. Mar6 2000.
+ * Do not set the sock lcn number here, since
+ * it is done in wanpipe_listen_rcv().
+ */
+ if (sk->state == WANSOCK_CONNECTED){
+ sk->protinfo.af_wanpipe->lcn =
+ ((mbox_cmd_t*)sk->protinfo.af_wanpipe->mbox)->cmd.lcn;
+ DBG_PRINTK(KERN_INFO "\nwansock: Accept OK %i\n",
+ sk->protinfo.af_wanpipe->lcn );
+ err = 0;
+
+ }else{
+ DBG_PRINTK (KERN_INFO "\nwansock: Accept Failed %i\n",
+ sk->protinfo.af_wanpipe->lcn);
+ sk->protinfo.af_wanpipe->lcn = 0;
+ err = -ECONNREFUSED;
+ }
+ break;
+
+ case SIOC_WANPIPE_CLEAR_CALL:
+
+ if (sk->state == WANSOCK_DISCONNECTED){
+ err = -EINVAL;
+ break;
+ }
+
+
+ /* Check if data buffers are pending for transmission,
+ * if so, check whether user wants to wait until data
+ * is transmitted, or clear a call and drop packets */
+
+ if (atomic_read(&sk->wmem_alloc) || check_driver_busy(sk)){
+ mbox_cmd_t *mbox = sk->protinfo.af_wanpipe->mbox;
+ if (mbox->cmd.qdm & 0x80){
+ mbox->cmd.result = 0x35;
+ err = -EAGAIN;
+ break;
+ }
+ }
+
+ sk->state = WANSOCK_DISCONNECTING;
+
+ err = execute_command(sk,X25_CLEAR_CALL,0);
+ if (err < 0)
+ break;
+
+ err = -ECONNREFUSED;
+ if (sk->state == WANSOCK_DISCONNECTED){
+ DBG_PRINTK(KERN_INFO "\nwansock: CLEAR OK %i\n",
+ sk->protinfo.af_wanpipe->lcn);
+ sk->protinfo.af_wanpipe->lcn=0;
+ err = 0;
+ }
+ break;
+
+ case SIOC_WANPIPE_RESET_CALL:
+
+ if (sk->state != WANSOCK_CONNECTED){
+ err = -EINVAL;
+ break;
+ }
+
+
+ /* Check if data buffers are pending for transmission,
+ * if so, check whether user wants to wait until data
+ * is transmitted, or reset a call and drop packets */
+
+ if (atomic_read(&sk->wmem_alloc) || check_driver_busy(sk)){
+ mbox_cmd_t *mbox = sk->protinfo.af_wanpipe->mbox;
+ if (mbox->cmd.qdm & 0x80){
+ mbox->cmd.result = 0x35;
+ err = -EAGAIN;
+ break;
+ }
+ }
+
+
+ err = execute_command(sk, X25_RESET,0);
+ if (err < 0)
+ break;
+
+ err = mbox_ptr->cmd.result;
+ break;
+
+
+ case X25_PLACE_CALL:
+
+ err=execute_command(sk,X25_PLACE_CALL,flags);
+ if (err < 0)
+ break;
+
+ if (sk->state == WANSOCK_CONNECTED){
+
+ sk->protinfo.af_wanpipe->lcn =
+ ((mbox_cmd_t*)sk->protinfo.af_wanpipe->mbox)->cmd.lcn;
+
+ DBG_PRINTK(KERN_INFO "\nwansock: PLACE CALL OK %i\n",
+ sk->protinfo.af_wanpipe->lcn);
+ err = 0;
+
+ }else if (sk->state == WANSOCK_CONNECTING && (flags & O_NONBLOCK)){
+ sk->protinfo.af_wanpipe->lcn =
+ ((mbox_cmd_t*)sk->protinfo.af_wanpipe->mbox)->cmd.lcn;
+ DBG_PRINTK(KERN_INFO "\nwansock: Place Call OK: Waiting %i\n",
+ sk->protinfo.af_wanpipe->lcn);
+
+ err = 0;
+
+ }else{
+ DBG_PRINTK(KERN_INFO "\nwansock: Place call Failed\n");
+ err = -ECONNREFUSED;
+ }
+
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return err;
+}
+
+static int check_driver_busy (struct sock *sk)
+{
+ netdevice_t *dev = dev_get_by_index(sk->bound_dev_if);
+ wanpipe_common_t *chan;
+
+ if (!dev)
+ return 0;
+
+ dev_put(dev);
+
+ if ((chan=dev->priv) == NULL)
+ return 0;
+
+ return atomic_read(&chan->driver_busy);
+}
+
+
+/*======================================================================
+ * wanpipe_accept
+ *
+ * ACCEPT() System call. X25API Specific function.
+ * For each incoming call, create a new socket and
+ * return it to the user.
+ *=====================================================================*/
+
+static int wanpipe_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+ struct sock *sk;
+ struct sock *newsk;
+ struct sk_buff *skb;
+ DECLARE_WAITQUEUE(wait, current);
+ int err=0;
+
+ if (newsock->sk != NULL){
+ wanpipe_kill_sock_accept(newsock->sk);
+ newsock->sk=NULL;
+ }
+
+ if ((sk = sock->sk) == NULL)
+ return -EINVAL;
+
+ if (sk->type != SOCK_RAW)
+ return -EOPNOTSUPP;
+
+ if (sk->state != WANSOCK_LISTEN)
+ return -EINVAL;
+
+ if (sk->num != htons(X25_PROT))
+ return -EINVAL;
+
+ add_wait_queue(sk->sleep,&wait);
+ current->state = TASK_INTERRUPTIBLE;
+ for (;;){
+ skb = skb_dequeue(&sk->receive_queue);
+ if (skb){
+ err=0;
+ break;
+ }
+ if (signal_pending(current)) {
+ err = -ERESTARTSYS;
+ break;
+ }
+ schedule();
+ }
+ current->state = TASK_RUNNING;
+ remove_wait_queue(sk->sleep,&wait);
+
+ if (err != 0)
+ return err;
+
+ newsk = get_newsk_from_skb(skb);
+ if (!newsk){
+ return -EINVAL;
+ }
+
+ set_bit(1,&wanpipe_tx_critical);
+ write_lock(&wanpipe_sklist_lock);
+ newsk->next = wanpipe_sklist;
+ wanpipe_sklist = newsk;
+ sock_hold(sk);
+ write_unlock(&wanpipe_sklist_lock);
+ clear_bit(1,&wanpipe_tx_critical);
+
+ newsk->pair = NULL;
+ newsk->socket = newsock;
+ newsk->sleep = &newsock->wait;
+
+ /* Now attach up the new socket */
+ sk->ack_backlog--;
+ newsock->sk = newsk;
+
+ kfree_skb(skb);
+
+ DBG_PRINTK(KERN_INFO "\nwansock: ACCEPT Got LCN %i\n",newsk->protinfo.af_wanpipe->lcn);
+ return 0;
+}
+
+/*======================================================================
+ * get_newsk_from_skb
+ *
+ * Accept() uses this function to get the address of the new
+ * socket structure.
+ *=====================================================================*/
+
+struct sock * get_newsk_from_skb (struct sk_buff *skb)
+{
+ netdevice_t *dev = skb->dev;
+ wanpipe_common_t *chan;
+
+ if (!dev){
+ return NULL;
+ }
+
+ if ((chan = dev->priv) == NULL){
+ return NULL;
+ }
+
+ if (!chan->sk){
+ return NULL;
+ }
+ return (struct sock *)chan->sk;
+}
+
+/*======================================================================
+ * wanpipe_connect
+ *
+ * CONNECT() System Call. X25API specific function
+ * Check the state of the sock, and execute PLACE_CALL command.
+ * Connect can ether block or return without waiting for connection,
+ * if specified by user.
+ *=====================================================================*/
+
+static int wanpipe_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct wan_sockaddr_ll *addr = (struct wan_sockaddr_ll*)uaddr;
+ netdevice_t *dev;
+ int err;
+
+ if (sk->num != htons(X25_PROT))
+ return -EINVAL;
+
+ if (sk->state == WANSOCK_CONNECTED)
+ return -EISCONN; /* No reconnect on a seqpacket socket */
+
+ if (sk->state != WAN_DISCONNECTED){
+ printk(KERN_INFO "wansock: Trying to connect on channel NON DISCONNECT\n");
+ return -ECONNREFUSED;
+ }
+
+ sk->state = WANSOCK_DISCONNECTED;
+ sock->state = SS_UNCONNECTED;
+
+ if (addr_len != sizeof(struct wan_sockaddr_ll))
+ return -EINVAL;
+
+ if (addr->sll_family != AF_WANPIPE)
+ return -EINVAL;
+
+ if ((dev = dev_get_by_index(sk->bound_dev_if)) == NULL)
+ return -ENETUNREACH;
+
+ dev_put(dev);
+
+ if (!sk->zapped) /* Must bind first - autobinding does not work */
+ return -EINVAL;
+
+ sock->state = SS_CONNECTING;
+ sk->state = WANSOCK_CONNECTING;
+
+ if (!sk->protinfo.af_wanpipe->mbox){
+ if (sk->protinfo.af_wanpipe->svc){
+ return -EINVAL;
+ }else{
+ int err;
+ if ((err=set_ioctl_cmd(sk,NULL)) < 0)
+ return err;
+ }
+ }
+
+ if ((err=wanpipe_exec_cmd(sk, X25_PLACE_CALL,flags)) != 0){
+ sock->state = SS_UNCONNECTED;
+ sk->state = WANSOCK_CONNECTED;
+ return err;
+ }
+
+ if (sk->state != WANSOCK_CONNECTED && (flags & O_NONBLOCK)){
+ return 0;
+ }
+
+ if (sk->state != WANSOCK_CONNECTED) {
+ sock->state = SS_UNCONNECTED;
+ return -ECONNREFUSED;
+ }
+
+ sock->state = SS_CONNECTED;
+ return 0;
+}
+
+#ifdef LINUX_2_4
+struct proto_ops wanpipe_ops = {
+ family: PF_WANPIPE,
+
+ release: wanpipe_release,
+ bind: wanpipe_bind,
+ connect: wanpipe_connect,
+ socketpair: sock_no_socketpair,
+ accept: wanpipe_accept,
+ getname: wanpipe_getname,
+ poll: wanpipe_poll,
+ ioctl: wanpipe_ioctl,
+ listen: wanpipe_listen,
+ shutdown: sock_no_shutdown,
+ setsockopt: sock_no_setsockopt,
+ getsockopt: sock_no_getsockopt,
+ sendmsg: wanpipe_sendmsg,
+ recvmsg: wanpipe_recvmsg
+};
+#else
+struct proto_ops wanpipe_ops = {
+ PF_WANPIPE,
+
+ sock_no_dup,
+ wanpipe_release,
+ wanpipe_bind,
+ wanpipe_connect,
+ sock_no_socketpair,
+ wanpipe_accept,
+ wanpipe_getname,
+ wanpipe_poll,
+ wanpipe_ioctl,
+ wanpipe_listen,
+ sock_no_shutdown,
+ sock_no_setsockopt,
+ sock_no_getsockopt,
+ sock_no_fcntl,
+ wanpipe_sendmsg,
+ wanpipe_recvmsg
+};
+#endif
+
+
+static struct net_proto_family wanpipe_family_ops = {
+ PF_WANPIPE,
+ wanpipe_create
+};
+
+struct notifier_block wanpipe_netdev_notifier={
+ wanpipe_notifier,
+ NULL,
+ 0
+};
+
+
+#ifdef MODULE
+void cleanup_module(void)
+{
+ printk(KERN_INFO "wansock: Cleaning up \n");
+ unregister_netdevice_notifier(&wanpipe_netdev_notifier);
+ sock_unregister(PF_WANPIPE);
+ return;
+}
+
+
+int init_module(void)
+{
+
+ printk(KERN_INFO "wansock: Registering Socket \n");
+ sock_register(&wanpipe_family_ops);
+ register_netdevice_notifier(&wanpipe_netdev_notifier);
+ return 0;
+}
+#endif
+MODULE_LICENSE("GPL");
diff --git a/uClinux-2.4.31-uc0/net/wanrouter/patchlevel b/uClinux-2.4.31-uc0/net/wanrouter/patchlevel
new file mode 100644
index 0000000..c043eea
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/wanrouter/patchlevel
@@ -0,0 +1 @@
+2.2.1
diff --git a/uClinux-2.4.31-uc0/net/wanrouter/wanmain.c b/uClinux-2.4.31-uc0/net/wanrouter/wanmain.c
new file mode 100644
index 0000000..b58338f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/wanrouter/wanmain.c
@@ -0,0 +1,1083 @@
+/*****************************************************************************
+* wanmain.c WAN Multiprotocol Router Module. Main code.
+*
+* This module is completely hardware-independent and provides
+* the following common services for the WAN Link Drivers:
+* o WAN device managenment (registering, unregistering)
+* o Network interface management
+* o Physical connection management (dial-up, incoming calls)
+* o Logical connection management (switched virtual circuits)
+* o Protocol encapsulation/decapsulation
+*
+* Author: Gideon Hack
+*
+* Copyright: (c) 1995-1999 Sangoma Technologies Inc.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version
+* 2 of the License, or (at your option) any later version.
+* ============================================================================
+* Nov 24, 2000 Nenad Corbic Updated for 2.4.X kernels
+* Nov 07, 2000 Nenad Corbic Fixed the Mulit-Port PPP for kernels 2.2.16 and
+* greater.
+* Aug 2, 2000 Nenad Corbic Block the Multi-Port PPP from running on
+* kernels 2.2.16 or greater. The SyncPPP
+* has changed.
+* Jul 13, 2000 Nenad Corbic Added SyncPPP support
+* Added extra debugging in device_setup().
+* Oct 01, 1999 Gideon Hack Update for s514 PCI card
+* Dec 27, 1996 Gene Kozin Initial version (based on Sangoma's WANPIPE)
+* Jan 16, 1997 Gene Kozin router_devlist made public
+* Jan 31, 1997 Alan Cox Hacked it about a bit for 2.1
+* Jun 27, 1997 Alan Cox realigned with vendor code
+* Oct 15, 1997 Farhan Thawar changed wan_encapsulate to add a pad byte of 0
+* Apr 20, 1998 Alan Cox Fixed 2.1 symbols
+* May 17, 1998 K. Baranowski Fixed SNAP encapsulation in wan_encapsulate
+* Dec 15, 1998 Arnaldo Melo support for firmwares of up to 128000 bytes
+* check wandev->setup return value
+* Dec 22, 1998 Arnaldo Melo vmalloc/vfree used in device_setup to allocate
+* kernel memory and copy configuration data to
+* kernel space (for big firmwares)
+* Jun 02, 1999 Gideon Hack Updates for Linux 2.0.X and 2.2.X kernels.
+*****************************************************************************/
+
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/stddef.h> /* offsetof(), etc. */
+#include <linux/errno.h> /* return codes */
+#include <linux/kernel.h>
+#include <linux/module.h> /* support for loadable modules */
+#include <linux/slab.h> /* kmalloc(), kfree() */
+#include <linux/mm.h> /* verify_area(), etc. */
+#include <linux/string.h> /* inline mem*, str* functions */
+
+#include <asm/byteorder.h> /* htons(), etc. */
+#include <linux/wanrouter.h> /* WAN router API definitions */
+
+
+#if defined(LINUX_2_4)
+ #include <linux/vmalloc.h> /* vmalloc, vfree */
+ #include <asm/uaccess.h> /* copy_to/from_user */
+ #include <linux/init.h> /* __initfunc et al. */
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,3)
+ #include <net/syncppp.h>
+#else
+ #include <../drivers/net/wan/syncppp.h>
+#endif
+
+#elif defined(LINUX_2_1)
+ #define LINUX_2_1
+ #include <linux/vmalloc.h> /* vmalloc, vfree */
+ #include <asm/uaccess.h> /* copy_to/from_user */
+ #include <linux/init.h> /* __initfunc et al. */
+ #include <../drivers/net/syncppp.h>
+
+#else
+ #include <asm/segment.h> /* kernel <-> user copy */
+#endif
+
+#define KMEM_SAFETYZONE 8
+
+/***********FOR DEBUGGING PURPOSES*********************************************
+static void * dbg_kmalloc(unsigned int size, int prio, int line) {
+ int i = 0;
+ void * v = kmalloc(size+sizeof(unsigned int)+2*KMEM_SAFETYZONE*8,prio);
+ char * c1 = v;
+ c1 += sizeof(unsigned int);
+ *((unsigned int *)v) = size;
+
+ for (i = 0; i < KMEM_SAFETYZONE; i++) {
+ c1[0] = 'D'; c1[1] = 'E'; c1[2] = 'A'; c1[3] = 'D';
+ c1[4] = 'B'; c1[5] = 'E'; c1[6] = 'E'; c1[7] = 'F';
+ c1 += 8;
+ }
+ c1 += size;
+ for (i = 0; i < KMEM_SAFETYZONE; i++) {
+ c1[0] = 'M'; c1[1] = 'U'; c1[2] = 'N'; c1[3] = 'G';
+ c1[4] = 'W'; c1[5] = 'A'; c1[6] = 'L'; c1[7] = 'L';
+ c1 += 8;
+ }
+ v = ((char *)v) + sizeof(unsigned int) + KMEM_SAFETYZONE*8;
+ printk(KERN_INFO "line %d kmalloc(%d,%d) = %p\n",line,size,prio,v);
+ return v;
+}
+static void dbg_kfree(void * v, int line) {
+ unsigned int * sp = (unsigned int *)(((char *)v) - (sizeof(unsigned int) + KMEM_SAFETYZONE*8));
+ unsigned int size = *sp;
+ char * c1 = ((char *)v) - KMEM_SAFETYZONE*8;
+ int i = 0;
+ for (i = 0; i < KMEM_SAFETYZONE; i++) {
+ if ( c1[0] != 'D' || c1[1] != 'E' || c1[2] != 'A' || c1[3] != 'D'
+ || c1[4] != 'B' || c1[5] != 'E' || c1[6] != 'E' || c1[7] != 'F') {
+ printk(KERN_INFO "kmalloced block at %p has been corrupted (underrun)!\n",v);
+ printk(KERN_INFO " %4x: %2x %2x %2x %2x %2x %2x %2x %2x\n", i*8,
+ c1[0],c1[1],c1[2],c1[3],c1[4],c1[5],c1[6],c1[7] );
+ }
+ c1 += 8;
+ }
+ c1 += size;
+ for (i = 0; i < KMEM_SAFETYZONE; i++) {
+ if ( c1[0] != 'M' || c1[1] != 'U' || c1[2] != 'N' || c1[3] != 'G'
+ || c1[4] != 'W' || c1[5] != 'A' || c1[6] != 'L' || c1[7] != 'L'
+ ) {
+ printk(KERN_INFO "kmalloced block at %p has been corrupted (overrun):\n",v);
+ printk(KERN_INFO " %4x: %2x %2x %2x %2x %2x %2x %2x %2x\n", i*8,
+ c1[0],c1[1],c1[2],c1[3],c1[4],c1[5],c1[6],c1[7] );
+ }
+ c1 += 8;
+ }
+ printk(KERN_INFO "line %d kfree(%p)\n",line,v);
+ v = ((char *)v) - (sizeof(unsigned int) + KMEM_SAFETYZONE*8);
+ kfree(v);
+}
+
+#define kmalloc(x,y) dbg_kmalloc(x,y,__LINE__)
+#define kfree(x) dbg_kfree(x,__LINE__)
+*****************************************************************************/
+
+
+/*
+ * Function Prototypes
+ */
+
+/*
+ * Kernel loadable module interface.
+ */
+#ifdef MODULE
+int init_module (void);
+void cleanup_module (void);
+#endif
+
+/*
+ * WAN device IOCTL handlers
+ */
+
+static int device_setup(wan_device_t *wandev, wandev_conf_t *u_conf);
+static int device_stat(wan_device_t *wandev, wandev_stat_t *u_stat);
+static int device_shutdown(wan_device_t *wandev);
+static int device_new_if(wan_device_t *wandev, wanif_conf_t *u_conf);
+static int device_del_if(wan_device_t *wandev, char *u_name);
+
+/*
+ * Miscellaneous
+ */
+
+static wan_device_t *find_device (char *name);
+static int delete_interface (wan_device_t *wandev, char *name);
+void lock_adapter_irq(spinlock_t *lock, unsigned long *smp_flags);
+void unlock_adapter_irq(spinlock_t *lock, unsigned long *smp_flags);
+
+
+
+/*
+ * Global Data
+ */
+
+static char fullname[] = "Sangoma WANPIPE Router";
+static char copyright[] = "(c) 1995-2000 Sangoma Technologies Inc.";
+static char modname[] = ROUTER_NAME; /* short module name */
+wan_device_t* router_devlist = NULL; /* list of registered devices */
+static int devcnt = 0;
+
+/*
+ * Organize Unique Identifiers for encapsulation/decapsulation
+ */
+
+static unsigned char oui_ether[] = { 0x00, 0x00, 0x00 };
+#if 0
+static unsigned char oui_802_2[] = { 0x00, 0x80, 0xC2 };
+#endif
+
+#ifndef MODULE
+
+int wanrouter_init(void)
+{
+ int err;
+ extern int wanpipe_init(void);
+ extern int sdladrv_init(void);
+
+ printk(KERN_INFO "%s v%u.%u %s\n",
+ fullname, ROUTER_VERSION, ROUTER_RELEASE, copyright);
+
+ err = wanrouter_proc_init();
+ if (err){
+ printk(KERN_INFO "%s: can't create entry in proc filesystem!\n", modname);
+ }
+
+ /*
+ * Initialise compiled in boards
+ */
+
+#ifdef CONFIG_VENDOR_SANGOMA
+ sdladrv_init();
+ wanpipe_init();
+#endif
+
+ return err;
+}
+
+
+#ifdef LINUX_2_4
+static void __exit wanrouter_cleanup (void)
+{
+ wanrouter_proc_cleanup();
+}
+#endif
+
+#else
+
+/*
+ * Kernel Loadable Module Entry Points
+ */
+
+/*
+ * Module 'insert' entry point.
+ * o print announcement
+ * o initialize static data
+ * o create /proc/net/router directory and static entries
+ *
+ * Return: 0 Ok
+ * < 0 error.
+ * Context: process
+ */
+
+int init_module (void)
+{
+ int err;
+
+ printk(KERN_INFO "%s v%u.%u %s\n",
+ fullname, ROUTER_VERSION, ROUTER_RELEASE, copyright);
+
+ err = wanrouter_proc_init();
+
+ if (err){
+ printk(KERN_INFO
+ "%s: can't create entry in proc filesystem!\n", modname);
+ }
+ return err;
+}
+
+/*
+ * Module 'remove' entry point.
+ * o delete /proc/net/router directory and static entries.
+ */
+
+void cleanup_module (void)
+{
+ wanrouter_proc_cleanup();
+}
+
+#endif
+
+/*
+ * Kernel APIs
+ */
+
+/*
+ * Register WAN device.
+ * o verify device credentials
+ * o create an entry for the device in the /proc/net/router directory
+ * o initialize internally maintained fields of the wan_device structure
+ * o link device data space to a singly-linked list
+ * o if it's the first device, then start kernel 'thread'
+ * o increment module use count
+ *
+ * Return:
+ * 0 Ok
+ * < 0 error.
+ *
+ * Context: process
+ */
+
+
+int register_wan_device(wan_device_t *wandev)
+{
+ int err, namelen;
+
+ if ((wandev == NULL) || (wandev->magic != ROUTER_MAGIC) ||
+ (wandev->name == NULL))
+ return -EINVAL;
+
+ namelen = strlen(wandev->name);
+ if (!namelen || (namelen > WAN_DRVNAME_SZ))
+ return -EINVAL;
+
+ if (find_device(wandev->name) != NULL)
+ return -EEXIST;
+
+#ifdef WANDEBUG
+ printk(KERN_INFO "%s: registering WAN device %s\n",
+ modname, wandev->name);
+#endif
+
+ /*
+ * Register /proc directory entry
+ */
+ err = wanrouter_proc_add(wandev);
+ if (err) {
+ printk(KERN_INFO
+ "%s: can't create /proc/net/router/%s entry!\n",
+ modname, wandev->name);
+ return err;
+ }
+
+ /*
+ * Initialize fields of the wan_device structure maintained by the
+ * router and update local data.
+ */
+
+ wandev->ndev = 0;
+ wandev->dev = NULL;
+ wandev->next = router_devlist;
+ router_devlist = wandev;
+ ++devcnt;
+ MOD_INC_USE_COUNT; /* prevent module from unloading */
+ return 0;
+}
+
+/*
+ * Unregister WAN device.
+ * o shut down device
+ * o unlink device data space from the linked list
+ * o delete device entry in the /proc/net/router directory
+ * o decrement module use count
+ *
+ * Return: 0 Ok
+ * <0 error.
+ * Context: process
+ */
+
+
+int unregister_wan_device(char *name)
+{
+ wan_device_t *wandev, *prev;
+
+ if (name == NULL)
+ return -EINVAL;
+
+ for (wandev = router_devlist, prev = NULL;
+ wandev && strcmp(wandev->name, name);
+ prev = wandev, wandev = wandev->next)
+ ;
+ if (wandev == NULL)
+ return -ENODEV;
+
+#ifdef WANDEBUG
+ printk(KERN_INFO "%s: unregistering WAN device %s\n", modname, name);
+#endif
+
+ if (wandev->state != WAN_UNCONFIGURED) {
+ device_shutdown(wandev);
+ }
+
+ if (prev){
+ prev->next = wandev->next;
+ }else{
+ router_devlist = wandev->next;
+ }
+
+ --devcnt;
+ wanrouter_proc_delete(wandev);
+ MOD_DEC_USE_COUNT;
+ return 0;
+}
+
+/*
+ * Encapsulate packet.
+ *
+ * Return: encapsulation header size
+ * < 0 - unsupported Ethertype
+ *
+ * Notes:
+ * 1. This function may be called on interrupt context.
+ */
+
+
+int wanrouter_encapsulate (struct sk_buff *skb, netdevice_t *dev,
+ unsigned short type)
+{
+ int hdr_len = 0;
+
+ switch (type) {
+ case ETH_P_IP: /* IP datagram encapsulation */
+ hdr_len += 1;
+ skb_push(skb, 1);
+ skb->data[0] = NLPID_IP;
+ break;
+
+ case ETH_P_IPX: /* SNAP encapsulation */
+ case ETH_P_ARP:
+ hdr_len += 7;
+ skb_push(skb, 7);
+ skb->data[0] = 0;
+ skb->data[1] = NLPID_SNAP;
+ memcpy(&skb->data[2], oui_ether, sizeof(oui_ether));
+ *((unsigned short*)&skb->data[5]) = htons(type);
+ break;
+
+ default: /* Unknown packet type */
+ printk(KERN_INFO
+ "%s: unsupported Ethertype 0x%04X on interface %s!\n",
+ modname, type, dev->name);
+ hdr_len = -EINVAL;
+ }
+ return hdr_len;
+}
+
+
+/*
+ * Decapsulate packet.
+ *
+ * Return: Ethertype (in network order)
+ * 0 unknown encapsulation
+ *
+ * Notes:
+ * 1. This function may be called on interrupt context.
+ */
+
+
+unsigned short wanrouter_type_trans (struct sk_buff *skb, netdevice_t *dev)
+{
+ int cnt = skb->data[0] ? 0 : 1; /* there may be a pad present */
+ unsigned short ethertype;
+
+ switch (skb->data[cnt]) {
+ case NLPID_IP: /* IP datagramm */
+ ethertype = htons(ETH_P_IP);
+ cnt += 1;
+ break;
+
+ case NLPID_SNAP: /* SNAP encapsulation */
+ if (memcmp(&skb->data[cnt + 1], oui_ether, sizeof(oui_ether))){
+ printk(KERN_INFO
+ "%s: unsupported SNAP OUI %02X-%02X-%02X "
+ "on interface %s!\n", modname,
+ skb->data[cnt+1], skb->data[cnt+2],
+ skb->data[cnt+3], dev->name);
+ return 0;
+ }
+ ethertype = *((unsigned short*)&skb->data[cnt+4]);
+ cnt += 6;
+ break;
+
+ /* add other protocols, e.g. CLNP, ESIS, ISIS, if needed */
+
+ default:
+ printk(KERN_INFO
+ "%s: unsupported NLPID 0x%02X on interface %s!\n",
+ modname, skb->data[cnt], dev->name);
+ return 0;
+ }
+ skb->protocol = ethertype;
+ skb->pkt_type = PACKET_HOST; /* Physically point to point */
+ skb_pull(skb, cnt);
+ skb->mac.raw = skb->data;
+ return ethertype;
+}
+
+
+/*
+ * WAN device IOCTL.
+ * o find WAN device associated with this node
+ * o execute requested action or pass command to the device driver
+ */
+
+int wanrouter_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ int err = 0;
+ struct proc_dir_entry *dent;
+ wan_device_t *wandev;
+
+ #if defined (LINUX_2_1) || defined (LINUX_2_4)
+ if (!capable(CAP_NET_ADMIN)){
+ return -EPERM;
+ }
+ #endif
+
+ if ((cmd >> 8) != ROUTER_IOCTL)
+ return -EINVAL;
+
+ dent = inode->u.generic_ip;
+ if ((dent == NULL) || (dent->data == NULL))
+ return -EINVAL;
+
+ wandev = dent->data;
+ if (wandev->magic != ROUTER_MAGIC)
+ return -EINVAL;
+
+ switch (cmd) {
+ case ROUTER_SETUP:
+ err = device_setup(wandev, (void*)arg);
+ break;
+
+ case ROUTER_DOWN:
+ err = device_shutdown(wandev);
+ break;
+
+ case ROUTER_STAT:
+ err = device_stat(wandev, (void*)arg);
+ break;
+
+ case ROUTER_IFNEW:
+ err = device_new_if(wandev, (void*)arg);
+ break;
+
+ case ROUTER_IFDEL:
+ err = device_del_if(wandev, (void*)arg);
+ break;
+
+ case ROUTER_IFSTAT:
+ break;
+
+ default:
+ if ((cmd >= ROUTER_USER) &&
+ (cmd <= ROUTER_USER_MAX) &&
+ wandev->ioctl)
+ err = wandev->ioctl(wandev, cmd, arg);
+ else err = -EINVAL;
+ }
+ return err;
+}
+
+/*
+ * WAN Driver IOCTL Handlers
+ */
+
+/*
+ * Setup WAN link device.
+ * o verify user address space
+ * o allocate kernel memory and copy configuration data to kernel space
+ * o if configuration data includes extension, copy it to kernel space too
+ * o call driver's setup() entry point
+ */
+
+static int device_setup (wan_device_t *wandev, wandev_conf_t *u_conf)
+{
+ void *data = NULL;
+ wandev_conf_t *conf;
+ int err = -EINVAL;
+
+ if (wandev->setup == NULL){ /* Nothing to do ? */
+ printk(KERN_INFO "%s: ERROR, No setup script: wandev->setup()\n",
+ wandev->name);
+ return 0;
+ }
+
+ #ifdef LINUX_2_0
+ err = verify_area (VERIFY_READ, u_conf, sizeof(wandev_conf_t));
+ if(err){
+ return err;
+ }
+ #endif
+
+ conf = kmalloc(sizeof(wandev_conf_t), GFP_KERNEL);
+ if (conf == NULL){
+ printk(KERN_INFO "%s: ERROR, Failed to allocate kernel memory !\n",
+ wandev->name);
+ return -ENOBUFS;
+ }
+
+ #if defined (LINUX_2_1) || defined (LINUX_2_4)
+ if(copy_from_user(conf, u_conf, sizeof(wandev_conf_t))) {
+ printk(KERN_INFO "%s: Failed to copy user config data to kernel space!\n",
+ wandev->name);
+ kfree(conf);
+ return -EFAULT;
+ }
+ #else
+ memcpy_fromfs ((void *)conf, (void *)u_conf, sizeof(wandev_conf_t));
+ #endif
+
+ if (conf->magic != ROUTER_MAGIC){
+ kfree(conf);
+ printk(KERN_INFO "%s: ERROR, Invalid MAGIC Number\n",
+ wandev->name);
+ return -EINVAL;
+ }
+
+ if (conf->data_size && conf->data){
+ if(conf->data_size > 128000 || conf->data_size < 0) {
+ printk(KERN_INFO
+ "%s: ERROR, Invalid firmware data size %i !\n",
+ wandev->name, conf->data_size);
+ kfree(conf);
+ return -EINVAL;;
+ }
+
+#if defined (LINUX_2_1) || defined (LINUX_2_4)
+ data = vmalloc(conf->data_size);
+ if (data) {
+ if(!copy_from_user(data, conf->data, conf->data_size)){
+ conf->data=data;
+ err = wandev->setup(wandev,conf);
+ }else{
+ printk(KERN_INFO
+ "%s: ERROR, Faild to copy from user data !\n",
+ wandev->name);
+ err = -EFAULT;
+ }
+ }else{
+ printk(KERN_INFO
+ "%s: ERROR, Faild allocate kernel memory !\n",
+ wandev->name);
+ err = -ENOBUFS;
+ }
+
+ if (data){
+ vfree(data);
+ }
+#else
+ err = verify_area(VERIFY_READ, conf->data, conf->data_size);
+ if (!err) {
+ data = kmalloc(conf->data_size, GFP_KERNEL);
+ if (data) {
+ memcpy_fromfs(data, (void*)conf->data,
+ conf->data_size);
+ conf->data = data;
+ }else{
+ printk(KERN_INFO
+ "%s: ERROR, Faild allocate kernel memory !\n",wandev->name);
+ err = -ENOMEM;
+ }
+ }else{
+ printk(KERN_INFO
+ "%s: ERROR, Faild to copy from user data !\n",wandev->name);
+ }
+
+ if (!err){
+ err = wandev->setup(wandev, conf);
+ }
+
+ if (data){
+ kfree(data);
+ }
+#endif
+ }else{
+ printk(KERN_INFO
+ "%s: ERROR, No firmware found ! Firmware size = %i !\n",
+ wandev->name, conf->data_size);
+ }
+
+ kfree(conf);
+ return err;
+}
+
+/*
+ * Shutdown WAN device.
+ * o delete all not opened logical channels for this device
+ * o call driver's shutdown() entry point
+ */
+
+static int device_shutdown (wan_device_t *wandev)
+{
+ netdevice_t *dev;
+ int err=0;
+
+ if (wandev->state == WAN_UNCONFIGURED){
+ return 0;
+ }
+
+ printk(KERN_INFO "\n%s: Shutting Down!\n",wandev->name);
+
+ for (dev = wandev->dev; dev;) {
+ if ((err=delete_interface(wandev, dev->name)) != 0){
+ return err;
+ }
+
+ /* The above function deallocates the current dev
+ * structure. Therefore, we cannot use dev->priv
+ * as the next element: wandev->dev points to the
+ * next element */
+ dev = wandev->dev;
+ }
+
+ if (wandev->ndev){
+ return -EBUSY; /* there are opened interfaces */
+ }
+
+ if (wandev->shutdown)
+ err=wandev->shutdown(wandev);
+
+ return err;
+}
+
+/*
+ * Get WAN device status & statistics.
+ */
+
+static int device_stat (wan_device_t *wandev, wandev_stat_t *u_stat)
+{
+ wandev_stat_t stat;
+
+ #ifdef LINUX_2_0
+ int err;
+ err = verify_area(VERIFY_WRITE, u_stat, sizeof(wandev_stat_t));
+ if (err)
+ return err;
+ #endif
+
+ memset(&stat, 0, sizeof(stat));
+
+ /* Ask device driver to update device statistics */
+ if ((wandev->state != WAN_UNCONFIGURED) && wandev->update)
+ wandev->update(wandev);
+
+ /* Fill out structure */
+ stat.ndev = wandev->ndev;
+ stat.state = wandev->state;
+
+ #if defined (LINUX_2_1) || defined (LINUX_2_4)
+ if(copy_to_user(u_stat, &stat, sizeof(stat)))
+ return -EFAULT;
+ #else
+ memcpy_tofs((void*)u_stat, (void*)&stat, sizeof(stat));
+ #endif
+
+ return 0;
+}
+
+/*
+ * Create new WAN interface.
+ * o verify user address space
+ * o copy configuration data to kernel address space
+ * o allocate network interface data space
+ * o call driver's new_if() entry point
+ * o make sure there is no interface name conflict
+ * o register network interface
+ */
+
+static int device_new_if (wan_device_t *wandev, wanif_conf_t *u_conf)
+{
+ wanif_conf_t conf;
+ netdevice_t *dev=NULL;
+#ifdef CONFIG_WANPIPE_MULTPPP
+ struct ppp_device *pppdev=NULL;
+#endif
+ int err;
+
+ if ((wandev->state == WAN_UNCONFIGURED) || (wandev->new_if == NULL))
+ return -ENODEV;
+
+#if defined (LINUX_2_1) || defined (LINUX_2_4)
+ if(copy_from_user(&conf, u_conf, sizeof(wanif_conf_t)))
+ return -EFAULT;
+#else
+ err = verify_area(VERIFY_READ, u_conf, sizeof(wanif_conf_t));
+ if (err)
+ return err;
+ memcpy_fromfs((void*)&conf, (void*)u_conf, sizeof(wanif_conf_t));
+#endif
+
+ if (conf.magic != ROUTER_MAGIC)
+ return -EINVAL;
+
+ err = -EPROTONOSUPPORT;
+
+
+#ifdef CONFIG_WANPIPE_MULTPPP
+ if (conf.config_id == WANCONFIG_MPPP){
+
+ pppdev = kmalloc(sizeof(struct ppp_device), GFP_KERNEL);
+ if (pppdev == NULL){
+ return -ENOBUFS;
+ }
+ memset(pppdev, 0, sizeof(struct ppp_device));
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,16)
+ pppdev->dev = kmalloc(sizeof(netdevice_t), GFP_KERNEL);
+ if (pppdev->dev == NULL){
+ kfree(pppdev);
+ return -ENOBUFS;
+ }
+ memset(pppdev->dev, 0, sizeof(netdevice_t));
+#endif
+
+ err = wandev->new_if(wandev, (netdevice_t *)pppdev, &conf);
+
+ #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,16)
+ dev = pppdev->dev;
+ #else
+ dev = &pppdev->dev;
+ #endif
+
+ }else{
+
+ dev = kmalloc(sizeof(netdevice_t), GFP_KERNEL);
+ if (dev == NULL){
+ return -ENOBUFS;
+ }
+ memset(dev, 0, sizeof(netdevice_t));
+ err = wandev->new_if(wandev, dev, &conf);
+ }
+
+#else
+ /* Sync PPP is disabled */
+ if (conf.config_id != WANCONFIG_MPPP){
+
+ dev = kmalloc(sizeof(netdevice_t), GFP_KERNEL);
+ if (dev == NULL){
+ return -ENOBUFS;
+ }
+ memset(dev, 0, sizeof(netdevice_t));
+ err = wandev->new_if(wandev, dev, &conf);
+ }else{
+ printk(KERN_INFO "%s: Wanpipe Mulit-Port PPP support has not been compiled in!\n",
+ wandev->name);
+ return err;
+ }
+#endif
+
+ if (!err) {
+ /* Register network interface. This will invoke init()
+ * function supplied by the driver. If device registered
+ * successfully, add it to the interface list.
+ */
+
+ if (dev->name == NULL){
+ err = -EINVAL;
+ }else if (dev_get(dev->name)){
+ err = -EEXIST; /* name already exists */
+ }else{
+
+ #ifdef WANDEBUG
+ printk(KERN_INFO "%s: registering interface %s...\n",
+ modname, dev->name);
+ #endif
+
+ err = register_netdev(dev);
+ if (!err) {
+ netdevice_t *slave=NULL;
+ unsigned long smp_flags=0;
+
+ lock_adapter_irq(&wandev->lock, &smp_flags);
+
+ if (wandev->dev == NULL){
+ wandev->dev = dev;
+ }else{
+ for (slave=wandev->dev;
+ *((netdevice_t**)slave->priv);
+ slave=*((netdevice_t**)slave->priv));
+
+ *((netdevice_t**)slave->priv) = dev;
+ }
+ ++wandev->ndev;
+
+ unlock_adapter_irq(&wandev->lock, &smp_flags);
+ return 0; /* done !!! */
+ }
+ }
+ if (wandev->del_if)
+ wandev->del_if(wandev, dev);
+ }
+
+ /* This code has moved from del_if() function */
+ if (dev->priv){
+ kfree(dev->priv);
+ dev->priv=NULL;
+ }
+
+
+ #ifdef CONFIG_WANPIPE_MULTPPP
+ if (conf.config_id == WANCONFIG_MPPP){
+ kfree(pppdev);
+ }else{
+ kfree(dev);
+ }
+ #else
+ /* Sync PPP is disabled */
+ if (conf.config_id != WANCONFIG_MPPP){
+ kfree(dev);
+ }
+ #endif
+
+ return err;
+}
+
+
+/*
+ * Delete WAN logical channel.
+ * o verify user address space
+ * o copy configuration data to kernel address space
+ */
+
+static int device_del_if (wan_device_t *wandev, char *u_name)
+{
+ char name[WAN_IFNAME_SZ + 1];
+ int err = 0;
+
+ if (wandev->state == WAN_UNCONFIGURED)
+ return -ENODEV;
+
+ #ifdef LINUX_2_0
+ err = verify_area(VERIFY_READ, u_name, WAN_IFNAME_SZ);
+ if (err)
+ return err;
+ #endif
+
+ memset(name, 0, sizeof(name));
+
+ #if defined (LINUX_2_1) || defined (LINUX_2_4)
+ if(copy_from_user(name, u_name, WAN_IFNAME_SZ))
+ return -EFAULT;
+ #else
+ memcpy_fromfs((void*)name, (void*)u_name, WAN_IFNAME_SZ);
+ #endif
+
+ err = delete_interface(wandev, name);
+ if (err)
+ return(err);
+
+ /* If last interface being deleted, shutdown card
+ * This helps with administration at leaf nodes
+ * (You can tell if the person at the other end of the phone
+ * has an interface configured) and avoids DoS vulnerabilities
+ * in binary driver files - this fixes a problem with the current
+ * Sangoma driver going into strange states when all the network
+ * interfaces are deleted and the link irrecoverably disconnected.
+ */
+
+ if (!wandev->ndev && wandev->shutdown){
+ err = wandev->shutdown(wandev);
+ }
+ return err;
+}
+
+
+/*
+ * Miscellaneous Functions
+ */
+
+/*
+ * Find WAN device by name.
+ * Return pointer to the WAN device data space or NULL if device not found.
+ */
+
+static wan_device_t *find_device(char *name)
+{
+ wan_device_t *wandev;
+
+ for (wandev = router_devlist;wandev && strcmp(wandev->name, name);
+ wandev = wandev->next);
+ return wandev;
+}
+
+/*
+ * Delete WAN logical channel identified by its name.
+ * o find logical channel by its name
+ * o call driver's del_if() entry point
+ * o unregister network interface
+ * o unlink channel data space from linked list of channels
+ * o release channel data space
+ *
+ * Return: 0 success
+ * -ENODEV channel not found.
+ * -EBUSY interface is open
+ *
+ * Note: If (force != 0), then device will be destroyed even if interface
+ * associated with it is open. It's caller's responsibility to make
+ * sure that opened interfaces are not removed!
+ */
+
+static int delete_interface (wan_device_t *wandev, char *name)
+{
+ netdevice_t *dev=NULL, *prev=NULL;
+ unsigned long smp_flags=0;
+
+ lock_adapter_irq(&wandev->lock, &smp_flags);
+ dev = wandev->dev;
+ prev = NULL;
+ while (dev && strcmp(name, dev->name)) {
+ netdevice_t **slave = dev->priv;
+ prev = dev;
+ dev = *slave;
+ }
+ unlock_adapter_irq(&wandev->lock, &smp_flags);
+
+ if (dev == NULL){
+ return -ENODEV; /* interface not found */
+ }
+
+ #ifdef LINUX_2_4
+ if (netif_running(dev)){
+ #else
+ if (dev->start) {
+ #endif
+ return -EBUSY; /* interface in use */
+ }
+
+ if (wandev->del_if)
+ wandev->del_if(wandev, dev);
+
+ lock_adapter_irq(&wandev->lock, &smp_flags);
+ if (prev) {
+ netdevice_t **prev_slave = prev->priv;
+ netdevice_t **slave = dev->priv;
+
+ *prev_slave = *slave;
+ } else {
+ netdevice_t **slave = dev->priv;
+ wandev->dev = *slave;
+ }
+ --wandev->ndev;
+ unlock_adapter_irq(&wandev->lock, &smp_flags);
+
+ printk(KERN_INFO "%s: unregistering '%s'\n", wandev->name, dev->name);
+
+ /* Due to new interface linking method using dev->priv,
+ * this code has moved from del_if() function.*/
+ if (dev->priv){
+ kfree(dev->priv);
+ dev->priv=NULL;
+ }
+
+ unregister_netdev(dev);
+
+ #ifdef LINUX_2_4
+ kfree(dev);
+ #else
+ if (dev->name){
+ kfree(dev->name);
+ }
+ kfree(dev);
+ #endif
+
+ return 0;
+}
+
+void lock_adapter_irq(spinlock_t *lock, unsigned long *smp_flags)
+{
+ #ifdef LINUX_2_0
+ save_flags(*smp_flags);
+ cli();
+ #else
+ spin_lock_irqsave(lock, *smp_flags);
+ #endif
+}
+
+
+void unlock_adapter_irq(spinlock_t *lock, unsigned long *smp_flags)
+{
+ #ifdef LINUX_2_0
+ restore_flags(*smp_flags);
+ #else
+ spin_unlock_irqrestore(lock, *smp_flags);
+ #endif
+}
+
+
+
+#if defined (LINUX_2_1) || defined (LINUX_2_4)
+EXPORT_SYMBOL(register_wan_device);
+EXPORT_SYMBOL(unregister_wan_device);
+EXPORT_SYMBOL(wanrouter_encapsulate);
+EXPORT_SYMBOL(wanrouter_type_trans);
+EXPORT_SYMBOL(lock_adapter_irq);
+EXPORT_SYMBOL(unlock_adapter_irq);
+#endif
+
+/*
+ * End
+ */
diff --git a/uClinux-2.4.31-uc0/net/wanrouter/wanproc.c b/uClinux-2.4.31-uc0/net/wanrouter/wanproc.c
new file mode 100644
index 0000000..0fe14b4
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/wanrouter/wanproc.c
@@ -0,0 +1,1088 @@
+/*****************************************************************************
+* wanproc.c WAN Router Module. /proc filesystem interface.
+*
+* This module is completely hardware-independent and provides
+* access to the router using Linux /proc filesystem.
+*
+* Author: Gideon Hack
+*
+* Copyright: (c) 1995-1999 Sangoma Technologies Inc.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version
+* 2 of the License, or (at your option) any later version.
+* ============================================================================
+* Jun 02, 1999 Gideon Hack Updates for Linux 2.2.X kernels.
+* Jun 29, 1997 Alan Cox Merged with 1.0.3 vendor code
+* Jan 29, 1997 Gene Kozin v1.0.1. Implemented /proc read routines
+* Jan 30, 1997 Alan Cox Hacked around for 2.1
+* Dec 13, 1996 Gene Kozin Initial version (based on Sangoma's WANPIPE)
+*****************************************************************************/
+
+#include <linux/version.h>
+#include <linux/config.h>
+#include <linux/stddef.h> /* offsetof(), etc. */
+#include <linux/errno.h> /* return codes */
+#include <linux/kernel.h>
+#include <linux/slab.h> /* kmalloc(), kfree() */
+#include <linux/mm.h> /* verify_area(), etc. */
+#include <linux/string.h> /* inline mem*, str* functions */
+#include <asm/byteorder.h> /* htons(), etc. */
+#include <asm/io.h>
+#include <linux/wanrouter.h> /* WAN router API definitions */
+
+
+
+#if defined(LINUX_2_1) || defined(LINUX_2_4)
+ #include <linux/init.h> /* __initfunc et al. */
+ #include <asm/uaccess.h> /* copy_to_user */
+ #define PROC_STATS_FORMAT "%30s: %12lu\n"
+#else
+ #define PROC_STATS_FORMAT "%30s: %12u\n"
+ #include <asm/segment.h> /* kernel <-> user copy */
+#endif
+
+
+/****** Defines and Macros **************************************************/
+
+#define PROC_BUFSZ 4000 /* buffer size for printing proc info */
+
+#define PROT_DECODE(prot) ((prot == WANCONFIG_FR) ? " FR" :\
+ (prot == WANCONFIG_X25) ? " X25" : \
+ (prot == WANCONFIG_PPP) ? " PPP" : \
+ (prot == WANCONFIG_CHDLC) ? " CHDLC": \
+ (prot == WANCONFIG_MPPP) ? " MPPP" : \
+ " Unknown" )
+
+/****** Data Types **********************************************************/
+
+typedef struct wan_stat_entry
+{
+ struct wan_stat_entry *next;
+ char *description; /* description string */
+ void *data; /* -> data */
+ unsigned data_type; /* data type */
+} wan_stat_entry_t;
+
+/****** Function Prototypes *************************************************/
+
+#ifdef CONFIG_PROC_FS
+
+
+#ifdef LINUX_2_4 /* Start of LINUX 2.4.X code */
+
+
+ /* Proc filesystem interface */
+ static int router_proc_perms(struct inode *, int);
+ static ssize_t router_proc_read(struct file* file, char* buf, size_t count, loff_t *ppos);
+
+ /* Methods for preparing data for reading proc entries */
+
+ static int config_get_info(char* buf, char** start, off_t offs, int len);
+ static int status_get_info(char* buf, char** start, off_t offs, int len);
+ static int wandev_get_info(char* buf, char** start, off_t offs, int len);
+
+ /* Miscellaneous */
+
+ /*
+ * Structures for interfacing with the /proc filesystem.
+ * Router creates its own directory /proc/net/router with the folowing
+ * entries:
+ * config device configuration
+ * status global device statistics
+ * <device> entry for each WAN device
+ */
+
+ /*
+ * Generic /proc/net/router/<file> file and inode operations
+ */
+ static struct file_operations router_fops =
+ {
+ read: router_proc_read,
+ };
+
+ static struct inode_operations router_inode =
+ {
+ permission: router_proc_perms,
+ };
+
+ /*
+ * /proc/net/router/<device> file operations
+ */
+
+ static struct file_operations wandev_fops =
+ {
+ read: router_proc_read,
+ ioctl: wanrouter_ioctl,
+ };
+
+ /*
+ * /proc/net/router
+ */
+
+ static struct proc_dir_entry *proc_router;
+
+ /* Strings */
+ static char conf_hdr[] =
+ "Device name | port |IRQ|DMA| mem.addr |mem.size|"
+ "option1|option2|option3|option4\n";
+
+ static char stat_hdr[] =
+ "Device name |protocol|station|interface|clocking|baud rate"
+ "| MTU |ndev|link state\n";
+
+
+ /*
+ * Interface functions
+ */
+
+ /*
+ * Initialize router proc interface.
+ */
+
+ int __init wanrouter_proc_init (void)
+ {
+ struct proc_dir_entry *p;
+ proc_router = proc_mkdir(ROUTER_NAME, proc_net);
+ if (!proc_router)
+ goto fail;
+
+ p = create_proc_entry("config",0,proc_router);
+ if (!p)
+ goto fail_config;
+ p->proc_fops = &router_fops;
+ p->proc_iops = &router_inode;
+ p->get_info = config_get_info;
+ p = create_proc_entry("status",0,proc_router);
+ if (!p)
+ goto fail_stat;
+ p->proc_fops = &router_fops;
+ p->proc_iops = &router_inode;
+ p->get_info = status_get_info;
+ return 0;
+ fail_stat:
+ remove_proc_entry("config", proc_router);
+ fail_config:
+ remove_proc_entry(ROUTER_NAME, proc_net);
+ fail:
+ return -ENOMEM;
+ }
+
+ /*
+ * Clean up router proc interface.
+ */
+
+ void wanrouter_proc_cleanup (void)
+ {
+ remove_proc_entry("config", proc_router);
+ remove_proc_entry("status", proc_router);
+ remove_proc_entry(ROUTER_NAME,proc_net);
+ }
+
+ /*
+ * Add directory entry for WAN device.
+ */
+
+ int wanrouter_proc_add (wan_device_t* wandev)
+ {
+ if (wandev->magic != ROUTER_MAGIC)
+ return -EINVAL;
+
+ wandev->dent = create_proc_entry(wandev->name, 0, proc_router);
+ if (!wandev->dent)
+ return -ENOMEM;
+ wandev->dent->proc_fops = &wandev_fops;
+ wandev->dent->proc_iops = &router_inode;
+ wandev->dent->get_info = wandev_get_info;
+ wandev->dent->data = wandev;
+ return 0;
+ }
+
+ /*
+ * Delete directory entry for WAN device.
+ */
+
+ int wanrouter_proc_delete(wan_device_t* wandev)
+ {
+ if (wandev->magic != ROUTER_MAGIC)
+ return -EINVAL;
+ remove_proc_entry(wandev->name, proc_router);
+ return 0;
+ }
+
+ /****** Proc filesystem entry points ****************************************/
+
+ /*
+ * Verify access rights.
+ */
+
+ static int router_proc_perms (struct inode* inode, int op)
+ {
+ return 0;
+ }
+
+ /*
+ * Read router proc directory entry.
+ * This is universal routine for reading all entries in /proc/net/wanrouter
+ * directory. Each directory entry contains a pointer to the 'method' for
+ * preparing data for that entry.
+ * o verify arguments
+ * o allocate kernel buffer
+ * o call get_info() to prepare data
+ * o copy data to user space
+ * o release kernel buffer
+ *
+ * Return: number of bytes copied to user space (0, if no data)
+ * <0 error
+ */
+
+ static ssize_t router_proc_read(struct file* file, char* buf, size_t count,
+ loff_t *ppos)
+ {
+ struct inode *inode = file->f_dentry->d_inode;
+ struct proc_dir_entry* dent;
+ char* page;
+ int pos, len;
+ loff_t n = *ppos;
+ unsigned offs = n;
+
+ if (count <= 0)
+ return 0;
+
+ dent = inode->u.generic_ip;
+ if ((dent == NULL) || (dent->get_info == NULL))
+ return 0;
+
+ page = kmalloc(PROC_BUFSZ, GFP_KERNEL);
+ if (page == NULL)
+ return -ENOBUFS;
+
+ pos = dent->get_info(page, dent->data, 0, 0);
+ if (offs == n && offs < pos) {
+ len = min_t(unsigned int, pos - offs, count);
+ if (copy_to_user(buf, (page + offs), len)) {
+ kfree(page);
+ return -EFAULT;
+ }
+ *ppos = offs + len;
+ }
+ else
+ len = 0;
+ kfree(page);
+ return len;
+ }
+
+ /*
+ * Prepare data for reading 'Config' entry.
+ * Return length of data.
+ */
+
+ static int config_get_info(char* buf, char** start, off_t offs, int len)
+ {
+ int cnt = sizeof(conf_hdr) - 1;
+ wan_device_t* wandev;
+ strcpy(buf, conf_hdr);
+ for (wandev = router_devlist;
+ wandev && (cnt < (PROC_BUFSZ - 120));
+ wandev = wandev->next) {
+ if (wandev->state) cnt += sprintf(&buf[cnt],
+ "%-15s|0x%-4X|%3u|%3u| 0x%-8lX |0x%-6X|%7u|%7u|%7u|%7u\n",
+ wandev->name,
+ wandev->ioport,
+ wandev->irq,
+ wandev->dma,
+ wandev->maddr,
+ wandev->msize,
+ wandev->hw_opt[0],
+ wandev->hw_opt[1],
+ wandev->hw_opt[2],
+ wandev->hw_opt[3]);
+ }
+
+ return cnt;
+ }
+
+ /*
+ * Prepare data for reading 'Status' entry.
+ * Return length of data.
+ */
+
+ static int status_get_info(char* buf, char** start, off_t offs, int len)
+ {
+ int cnt = 0;
+ wan_device_t* wandev;
+
+ //cnt += sprintf(&buf[cnt], "\nSTATUS:\n\n");
+ strcpy(&buf[cnt], stat_hdr);
+ cnt += sizeof(stat_hdr) - 1;
+
+ for (wandev = router_devlist;
+ wandev && (cnt < (PROC_BUFSZ - 80));
+ wandev = wandev->next) {
+ if (!wandev->state) continue;
+ cnt += sprintf(&buf[cnt],
+ "%-15s|%-8s|%-7s|%-9s|%-8s|%9u|%5u|%3u |",
+ wandev->name,
+ PROT_DECODE(wandev->config_id),
+ wandev->config_id == WANCONFIG_FR ?
+ (wandev->station ? " Node" : " CPE") :
+ (wandev->config_id == WANCONFIG_X25 ?
+ (wandev->station ? " DCE" : " DTE") :
+ (" N/A")),
+ wandev->interface ? " V.35" : " RS-232",
+ wandev->clocking ? "internal" : "external",
+ wandev->bps,
+ wandev->mtu,
+ wandev->ndev);
+
+ switch (wandev->state) {
+
+ case WAN_UNCONFIGURED:
+ cnt += sprintf(&buf[cnt], "%-12s\n", "unconfigured");
+ break;
+
+ case WAN_DISCONNECTED:
+ cnt += sprintf(&buf[cnt], "%-12s\n", "disconnected");
+ break;
+
+ case WAN_CONNECTING:
+ cnt += sprintf(&buf[cnt], "%-12s\n", "connecting");
+ break;
+
+ case WAN_CONNECTED:
+ cnt += sprintf(&buf[cnt], "%-12s\n", "connected");
+ break;
+
+ default:
+ cnt += sprintf(&buf[cnt], "%-12s\n", "invalid");
+ break;
+ }
+ }
+ return cnt;
+ }
+
+ /*
+ * Prepare data for reading <device> entry.
+ * Return length of data.
+ *
+ * On entry, the 'start' argument will contain a pointer to WAN device
+ * data space.
+ */
+
+ static int wandev_get_info(char* buf, char** start, off_t offs, int len)
+ {
+ wan_device_t* wandev = (void*)start;
+ int cnt = 0;
+ int rslt = 0;
+
+ if ((wandev == NULL) || (wandev->magic != ROUTER_MAGIC))
+ return 0;
+ if (!wandev->state)
+ return sprintf(&buf[cnt], "device is not configured!\n");
+
+ /* Update device statistics */
+ if (wandev->update) {
+
+ rslt = wandev->update(wandev);
+ if(rslt) {
+ switch (rslt) {
+ case -EAGAIN:
+ return sprintf(&buf[cnt], "Device is busy!\n");
+
+ default:
+ return sprintf(&buf[cnt],
+ "Device is not configured!\n");
+ }
+ }
+ }
+
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "total packets received", wandev->stats.rx_packets);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "total packets transmitted", wandev->stats.tx_packets);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "total bytes received", wandev->stats.rx_bytes);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "total bytes transmitted", wandev->stats.tx_bytes);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "bad packets received", wandev->stats.rx_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "packet transmit problems", wandev->stats.tx_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "received frames dropped", wandev->stats.rx_dropped);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "transmit frames dropped", wandev->stats.tx_dropped);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "multicast packets received", wandev->stats.multicast);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "transmit collisions", wandev->stats.collisions);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "receive length errors", wandev->stats.rx_length_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "receiver overrun errors", wandev->stats.rx_over_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "CRC errors", wandev->stats.rx_crc_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "frame format errors (aborts)", wandev->stats.rx_frame_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "receiver fifo overrun", wandev->stats.rx_fifo_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "receiver missed packet", wandev->stats.rx_missed_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "aborted frames transmitted", wandev->stats.tx_aborted_errors);
+ return cnt;
+ }
+
+
+#else /* ------------------- END OF LINUX 2.4.X VERSION -------------*/
+
+
+
+ /* Proc filesystem interface */
+ static int router_proc_perms(struct inode *, int);
+#ifdef LINUX_2_1
+ static ssize_t router_proc_read(struct file *file, char *buf, size_t count, loff_t *ppos);
+#else
+ static int router_proc_read(
+ struct inode* inode, struct file* file, char* buf, int count);
+ static int device_write(
+ struct inode* inode, struct file* file, const char* buf, int count);
+#endif
+
+ /* Methods for preparing data for reading proc entries */
+ static int config_get_info(char* buf, char** start, off_t offs, int len,
+ int dummy);
+ static int status_get_info(char* buf, char** start, off_t offs, int len,
+ int dummy);
+ static int wandev_get_info(char* buf, char** start, off_t offs, int len,
+ int dummy);
+
+ /* Miscellaneous */
+
+ /*
+ * Global Data
+ */
+
+ /*
+ * Names of the proc directory entries
+ */
+
+ static char name_root[] = ROUTER_NAME;
+ static char name_conf[] = "config";
+ static char name_stat[] = "status";
+
+ /*
+ * Structures for interfacing with the /proc filesystem.
+ * Router creates its own directory /proc/net/router with the folowing
+ * entries:
+ * config device configuration
+ * status global device statistics
+ * <device> entry for each WAN device
+ */
+
+ /*
+ * Generic /proc/net/router/<file> file and inode operations
+ */
+#ifdef LINUX_2_1
+ static struct file_operations router_fops =
+ {
+ NULL, /* lseek */
+ router_proc_read, /* read */
+ NULL, /* write */
+ NULL, /* readdir */
+ NULL, /* select */
+ NULL, /* ioctl */
+ NULL, /* mmap */
+ NULL, /* no special open code */
+ NULL, /* flush */
+ NULL, /* no special release code */
+ NULL /* can't fsync */
+ };
+#else
+ static struct file_operations router_fops =
+ {
+ NULL, /* lseek */
+ router_proc_read, /* read */
+ NULL, /* write */
+ NULL, /* readdir */
+ NULL, /* select */
+ NULL, /* ioctl */
+ NULL, /* mmap */
+ NULL, /* no special open code */
+ NULL, /* no special release code */
+ NULL /* can't fsync */
+ };
+#endif
+
+ static struct inode_operations router_inode =
+ {
+ &router_fops,
+ NULL, /* create */
+ NULL, /* lookup */
+ NULL, /* link */
+ NULL, /* unlink */
+ NULL, /* symlink */
+ NULL, /* mkdir */
+ NULL, /* rmdir */
+ NULL, /* mknod */
+ NULL, /* rename */
+ NULL, /* follow link */
+ NULL, /* readlink */
+ NULL, /* readpage */
+ NULL, /* writepage */
+ NULL, /* bmap */
+ NULL, /* truncate */
+ router_proc_perms
+ };
+
+ /*
+ * /proc/net/router/<device> file and inode operations
+ */
+
+#ifdef LINUX_2_1
+ static struct file_operations wandev_fops =
+ {
+ NULL, /* lseek */
+ router_proc_read, /* read */
+ NULL, /* write */
+ NULL, /* readdir */
+ NULL, /* select */
+ wanrouter_ioctl, /* ioctl */
+ NULL, /* mmap */
+ NULL, /* no special open code */
+ NULL, /* flush */
+ NULL, /* no special release code */
+ NULL /* can't fsync */
+ };
+#else
+ static struct file_operations wandev_fops =
+ {
+ NULL, /* lseek */
+ router_proc_read, /* read */
+ device_write, /* write */
+ NULL, /* readdir */
+ NULL, /* select */
+ wanrouter_ioctl, /* ioctl */
+ NULL, /* mmap */
+ NULL, /* no special open code */
+ NULL, /* no special release code */
+ NULL /* can't fsync */
+ };
+#endif
+
+ static struct inode_operations wandev_inode =
+ {
+ &wandev_fops,
+ NULL, /* create */
+ NULL, /* lookup */
+ NULL, /* link */
+ NULL, /* unlink */
+ NULL, /* symlink */
+ NULL, /* mkdir */
+ NULL, /* rmdir */
+ NULL, /* mknod */
+ NULL, /* rename */
+ NULL, /* readlink */
+ NULL, /* follow_link */
+ NULL, /* readpage */
+ NULL, /* writepage */
+ NULL, /* bmap */
+ NULL, /* truncate */
+ router_proc_perms
+ };
+
+ /*
+ * Proc filesystem derectory entries.
+ */
+
+ /*
+ * /proc/net/router
+ */
+
+ static struct proc_dir_entry proc_router =
+ {
+ 0, /* .low_ino */
+ sizeof(name_root) - 1, /* .namelen */
+ name_root, /* .name */
+ 0555 | S_IFDIR, /* .mode */
+ 2, /* .nlink */
+ 0, /* .uid */
+ 0, /* .gid */
+ 0, /* .size */
+ &proc_dir_inode_operations, /* .ops */
+ NULL, /* .get_info */
+ NULL, /* .fill_node */
+ NULL, /* .next */
+ NULL, /* .parent */
+ NULL, /* .subdir */
+ NULL, /* .data */
+ };
+
+ /*
+ * /proc/net/router/config
+ */
+
+ static struct proc_dir_entry proc_router_conf =
+ {
+ 0, /* .low_ino */
+ sizeof(name_conf) - 1, /* .namelen */
+ name_conf, /* .name */
+ 0444 | S_IFREG, /* .mode */
+ 1, /* .nlink */
+ 0, /* .uid */
+ 0, /* .gid */
+ 0, /* .size */
+ &router_inode, /* .ops */
+ &config_get_info, /* .get_info */
+ NULL, /* .fill_node */
+ NULL, /* .next */
+ NULL, /* .parent */
+ NULL, /* .subdir */
+ NULL, /* .data */
+ };
+
+ /*
+ * /proc/net/router/status
+ */
+
+ static struct proc_dir_entry proc_router_stat =
+ {
+ 0, /* .low_ino */
+ sizeof(name_stat) - 1, /* .namelen */
+ name_stat, /* .name */
+ 0444 | S_IFREG, /* .mode */
+ 1, /* .nlink */
+ 0, /* .uid */
+ 0, /* .gid */
+ 0, /* .size */
+ &router_inode, /* .ops */
+ status_get_info, /* .get_info */
+ NULL, /* .fill_node */
+ NULL, /* .next */
+ NULL, /* .parent */
+ NULL, /* .subdir */
+ NULL, /* .data */
+ };
+
+ /* Strings */
+ static char conf_hdr[] =
+ "Device name | port |IRQ|DMA| mem.addr |mem.size|"
+ "option1|option2|option3|option4\n";
+
+ static char stat_hdr[] =
+ "Device name |protocol|station|interface|clocking|baud rate| MTU |ndev"
+ "|link state\n";
+
+
+ /*
+ * Interface functions
+ */
+
+ /*
+ * Initialize router proc interface.
+ */
+
+#ifdef LINUX_2_1
+ __initfunc(int wanrouter_proc_init (void))
+ {
+ int err = proc_register(proc_net, &proc_router);
+
+ if (!err) {
+ proc_register(&proc_router, &proc_router_conf);
+ proc_register(&proc_router, &proc_router_stat);
+ }
+ return err;
+ }
+#else
+ int wanrouter_proc_init (void)
+ {
+ int err = proc_register_dynamic(&proc_net, &proc_router);
+
+ if (!err) {
+ proc_register_dynamic(&proc_router, &proc_router_conf);
+ proc_register_dynamic(&proc_router, &proc_router_stat);
+ }
+ return err;
+ }
+#endif
+
+ /*
+ * Clean up router proc interface.
+ */
+
+ void wanrouter_proc_cleanup (void)
+ {
+ proc_unregister(&proc_router, proc_router_conf.low_ino);
+ proc_unregister(&proc_router, proc_router_stat.low_ino);
+#ifdef LINUX_2_1
+ proc_unregister(proc_net, proc_router.low_ino);
+#else
+ proc_unregister(&proc_net, proc_router.low_ino);
+#endif
+ }
+
+ /*
+ * Add directory entry for WAN device.
+ */
+
+ int wanrouter_proc_add (wan_device_t* wandev)
+ {
+ if (wandev->magic != ROUTER_MAGIC)
+ return -EINVAL;
+
+ memset(&wandev->dent, 0, sizeof(wandev->dent));
+ wandev->dent.namelen = strlen(wandev->name);
+ wandev->dent.name = wandev->name;
+ wandev->dent.mode = 0444 | S_IFREG;
+ wandev->dent.nlink = 1;
+ wandev->dent.ops = &wandev_inode;
+ wandev->dent.get_info = &wandev_get_info;
+ wandev->dent.data = wandev;
+#ifdef LINUX_2_1
+ return proc_register(&proc_router, &wandev->dent);
+#else
+ return proc_register_dynamic(&proc_router, &wandev->dent);
+#endif
+ }
+
+ /*
+ * Delete directory entry for WAN device.
+ */
+
+ int wanrouter_proc_delete(wan_device_t* wandev)
+ {
+ if (wandev->magic != ROUTER_MAGIC)
+ return -EINVAL;
+ proc_unregister(&proc_router, wandev->dent.low_ino);
+ return 0;
+ }
+
+ /****** Proc filesystem entry points ****************************************/
+
+ /*
+ * Verify access rights.
+ */
+
+ static int router_proc_perms (struct inode* inode, int op)
+ {
+ return 0;
+ }
+
+ /*
+ * Read router proc directory entry.
+ * This is universal routine for reading all entries in /proc/net/wanrouter
+ * directory. Each directory entry contains a pointer to the 'method' for
+ * preparing data for that entry.
+ * o verify arguments
+ * o allocate kernel buffer
+ * o call get_info() to prepare data
+ * o copy data to user space
+ * o release kernel buffer
+ *
+ * Return: number of bytes copied to user space (0, if no data)
+ * <0 error
+ */
+#ifdef LINUX_2_1
+ static ssize_t router_proc_read(struct file* file, char* buf, size_t count,
+ loff_t *ppos)
+ {
+ struct inode *inode = file->f_dentry->d_inode;
+ struct proc_dir_entry* dent;
+ char* page;
+ int pos, offs, len;
+
+ if (count <= 0)
+ return 0;
+
+ dent = inode->u.generic_ip;
+ if ((dent == NULL) || (dent->get_info == NULL))
+ return 0;
+
+ page = kmalloc(PROC_BUFSZ, GFP_KERNEL);
+ if (page == NULL)
+ return -ENOBUFS;
+
+ pos = dent->get_info(page, dent->data, 0, 0, 0);
+ offs = file->f_pos;
+ if (offs < pos) {
+ len = min_t(unsigned int, pos - offs, count);
+ if (copy_to_user(buf, (page + offs), len)) {
+ kfree(page);
+ return -EFAULT;
+ }
+ file->f_pos += len;
+ }
+ else
+ len = 0;
+ kfree(page);
+ return len;
+ }
+
+#else
+ static int router_proc_read(
+ struct inode* inode, struct file* file, char* buf, int count)
+ {
+ struct proc_dir_entry* dent;
+ char* page;
+ int err, pos, offs, len;
+
+ if (count <= 0)
+ return 0;
+ dent = inode->u.generic_ip;
+ if ((dent == NULL) || (dent->get_info == NULL))
+ return -ENODATA;
+ err = verify_area(VERIFY_WRITE, buf, count);
+ if (err) return err;
+
+ page = kmalloc(PROC_BUFSZ, GFP_KERNEL);
+ if (page == NULL)
+ return -ENOMEM;
+
+ pos = dent->get_info(page, dent->data, 0, 0, 0);
+ offs = file->f_pos;
+ if (offs < pos) {
+ len = min_t(unsigned int, pos - offs, count);
+ memcpy_tofs((void*)buf, (void*)(page + offs), len);
+ file->f_pos += len;
+ }
+ else len = 0;
+ kfree(page);
+ return len;
+ }
+#endif
+
+
+ /*
+ * Prepare data for reading 'Config' entry.
+ * Return length of data.
+ */
+
+ static int config_get_info(char* buf, char** start, off_t offs, int len,
+ int dummy)
+ {
+ int cnt = sizeof(conf_hdr) - 1;
+ wan_device_t* wandev;
+ strcpy(buf, conf_hdr);
+ for (wandev = router_devlist;
+ wandev && (cnt < (PROC_BUFSZ - 120));
+ wandev = wandev->next) {
+ if (wandev->state) cnt += sprintf(&buf[cnt],
+ "%-15s|0x%-4X|%3u|%3u| 0x%-8lX |0x%-6X|%7u|%7u|%7u|%7u\n",
+ wandev->name,
+ wandev->ioport,
+ wandev->irq,
+ wandev->dma,
+ wandev->maddr,
+ wandev->msize,
+ wandev->hw_opt[0],
+ wandev->hw_opt[1],
+ wandev->hw_opt[2],
+ wandev->hw_opt[3]);
+ }
+
+ return cnt;
+ }
+
+ /*
+ * Prepare data for reading 'Status' entry.
+ * Return length of data.
+ */
+
+ static int status_get_info(char* buf, char** start, off_t offs, int len,
+ int dummy)
+ {
+ int cnt = 0;
+ wan_device_t* wandev;
+
+ //cnt += sprintf(&buf[cnt], "\nSTATUS:\n\n");
+ strcpy(&buf[cnt], stat_hdr);
+ cnt += sizeof(stat_hdr) - 1;
+
+ for (wandev = router_devlist;
+ wandev && (cnt < (PROC_BUFSZ - 80));
+ wandev = wandev->next) {
+ if (!wandev->state) continue;
+ cnt += sprintf(&buf[cnt],
+ "%-15s|%-8s|%-7s|%-9s|%-8s|%9u|%5u|%3u |",
+ wandev->name,
+ PROT_DECODE(wandev->config_id),
+ wandev->config_id == WANCONFIG_FR ?
+ (wandev->station ? " Node" : " CPE") :
+ (wandev->config_id == WANCONFIG_X25 ?
+ (wandev->station ? " DCE" : " DTE") :
+ (" N/A")),
+ wandev->interface ? " V.35" : " RS-232",
+ wandev->clocking ? "internal" : "external",
+ wandev->bps,
+ wandev->mtu,
+ wandev->ndev);
+
+ switch (wandev->state) {
+
+ case WAN_UNCONFIGURED:
+ cnt += sprintf(&buf[cnt], "%-12s\n", "unconfigured");
+ break;
+
+ case WAN_DISCONNECTED:
+ cnt += sprintf(&buf[cnt], "%-12s\n", "disconnected");
+ break;
+
+ case WAN_CONNECTING:
+ cnt += sprintf(&buf[cnt], "%-12s\n", "connecting");
+ break;
+
+ case WAN_CONNECTED:
+ cnt += sprintf(&buf[cnt], "%-12s\n", "connected");
+ break;
+
+ case WAN_FT1_READY:
+ cnt += sprintf(&buf[cnt], "%-12s\n", "ft1 ready");
+ break;
+
+ default:
+ cnt += sprintf(&buf[cnt], "%-12s\n", "invalid");
+ break;
+ }
+ }
+ return cnt;
+ }
+
+ /*
+ * Prepare data for reading <device> entry.
+ * Return length of data.
+ *
+ * On entry, the 'start' argument will contain a pointer to WAN device
+ * data space.
+ */
+
+ static int wandev_get_info(char* buf, char** start, off_t offs, int len,
+ int dummy)
+ {
+ wan_device_t* wandev = (void*)start;
+ int cnt = 0;
+ int rslt = 0;
+
+ if ((wandev == NULL) || (wandev->magic != ROUTER_MAGIC))
+ return 0;
+ if (!wandev->state)
+ return sprintf(&buf[cnt], "Device is not configured!\n");
+
+ /* Update device statistics */
+ if (wandev->update) {
+
+ rslt = wandev->update(wandev);
+ if(rslt) {
+ switch (rslt) {
+ case -EAGAIN:
+ return sprintf(&buf[cnt], "Device is busy!\n");
+
+ default:
+ return sprintf(&buf[cnt],
+ "Device is not configured!\n");
+ }
+ }
+ }
+
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "total packets received", wandev->stats.rx_packets);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "total packets transmitted", wandev->stats.tx_packets);
+#ifdef LINUX_2_1
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "total bytes received", wandev->stats.rx_bytes);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "total bytes transmitted", wandev->stats.tx_bytes);
+#endif
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "bad packets received", wandev->stats.rx_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "packet transmit problems", wandev->stats.tx_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "received frames dropped", wandev->stats.rx_dropped);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "transmit frames dropped", wandev->stats.tx_dropped);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "multicast packets received", wandev->stats.multicast);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "transmit collisions", wandev->stats.collisions);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "receive length errors", wandev->stats.rx_length_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "receiver overrun errors", wandev->stats.rx_over_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "CRC errors", wandev->stats.rx_crc_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "frame format errors (aborts)", wandev->stats.rx_frame_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "receiver fifo overrun", wandev->stats.rx_fifo_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "receiver missed packet", wandev->stats.rx_missed_errors);
+ cnt += sprintf(&buf[cnt], PROC_STATS_FORMAT,
+ "aborted frames transmitted", wandev->stats.tx_aborted_errors);
+
+ return cnt;
+ }
+
+#endif /* End of ifdef LINUX_2_4 */
+
+
+#else
+
+/*
+ * No /proc - output stubs
+ */
+
+int __init wanrouter_proc_init(void)
+{
+ return 0;
+}
+
+void wanrouter_proc_cleanup(void)
+{
+ return;
+}
+
+int wanrouter_proc_add(wan_device_t *wandev)
+{
+ return 0;
+}
+
+int wanrouter_proc_delete(wan_device_t *wandev)
+{
+ return 0;
+}
+
+#endif
+
+/*============================================================================
+ * Write WAN device ???.
+ * o Find WAN device associated with this node
+ */
+#ifdef LINUX_2_0
+static int device_write(
+ struct inode* inode, struct file* file, const char* buf, int count)
+{
+ int err = verify_area(VERIFY_READ, buf, count);
+ struct proc_dir_entry* dent;
+ wan_device_t* wandev;
+
+ if (err) return err;
+
+ dent = inode->u.generic_ip;
+ if ((dent == NULL) || (dent->data == NULL))
+ return -ENODATA;
+
+ wandev = dent->data;
+
+ printk(KERN_ERR "%s: writing %d bytes to %s...\n",
+ name_root, count, dent->name);
+
+ return 0;
+}
+#endif
+
+/*
+ * End
+ */
+
diff --git a/uClinux-2.4.31-uc0/net/x25/Makefile b/uClinux-2.4.31-uc0/net/x25/Makefile
new file mode 100644
index 0000000..d008293
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/x25/Makefile
@@ -0,0 +1,19 @@
+#
+# Makefile for the Linux X.25 Packet layer.
+#
+# Note! Dependencies are done automagically by 'make dep', which also
+# removes any old dependencies. DON'T put your own dependencies here
+# unless it's something special (ie not a .c file).
+#
+# Note 2! The CFLAGS definition is now in the main makefile...
+
+O_TARGET := x25.o
+
+obj-y := af_x25.o x25_dev.o x25_facilities.o x25_in.o x25_link.o x25_out.o \
+ x25_route.o x25_subr.o x25_timer.o
+obj-m := $(O_TARGET)
+
+obj-$(CONFIG_SYSCTL) += sysctl_net_x25.o
+
+include $(TOPDIR)/Rules.make
+
diff --git a/uClinux-2.4.31-uc0/net/x25/af_x25.c b/uClinux-2.4.31-uc0/net/x25/af_x25.c
new file mode 100644
index 0000000..0168745
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/x25/af_x25.c
@@ -0,0 +1,1382 @@
+/*
+ * X.25 Packet Layer release 002
+ *
+ * This is ALPHA test software. This code may break your machine, randomly fail to work with new
+ * releases, misbehave and/or generally screw up. It might even work.
+ *
+ * This code REQUIRES 2.1.15 or higher
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * X.25 001 Jonathan Naylor Started coding.
+ * X.25 002 Jonathan Naylor Centralised disconnect handling.
+ * New timer architecture.
+ * 2000-03-11 Henner Eisen MSG_EOR handling more POSIX compliant.
+ * 2000-03-22 Daniela Squassoni Allowed disabling/enabling of
+ * facilities negotiation and increased
+ * the throughput upper limit.
+ * 2000-08-27 Arnaldo C. Melo s/suser/capable/ + micro cleanups
+ * 2000-09-04 Henner Eisen Set sock->state in x25_accept().
+ * Fixed x25_output() related skb leakage.
+ * 2000-10-02 Henner Eisen Made x25_kick() single threaded per socket.
+ * 2000-10-27 Henner Eisen MSG_DONTWAIT for fragment allocation.
+ * 2000-11-14 Henner Eisen Closing datalink from NETDEV_GOING_DOWN
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/stat.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <linux/fcntl.h>
+#include <linux/termios.h> /* For TIOCINQ/OUTQ */
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <net/x25.h>
+
+int sysctl_x25_restart_request_timeout = X25_DEFAULT_T20;
+int sysctl_x25_call_request_timeout = X25_DEFAULT_T21;
+int sysctl_x25_reset_request_timeout = X25_DEFAULT_T22;
+int sysctl_x25_clear_request_timeout = X25_DEFAULT_T23;
+int sysctl_x25_ack_holdback_timeout = X25_DEFAULT_T2;
+
+static struct sock *volatile x25_list /* = NULL initially */;
+
+static struct proto_ops x25_proto_ops;
+
+static x25_address null_x25_address = {" "};
+
+int x25_addr_ntoa(unsigned char *p, x25_address *called_addr, x25_address *calling_addr)
+{
+ int called_len, calling_len;
+ char *called, *calling;
+ int i;
+
+ called_len = (*p >> 0) & 0x0F;
+ calling_len = (*p >> 4) & 0x0F;
+
+ called = called_addr->x25_addr;
+ calling = calling_addr->x25_addr;
+ p++;
+
+ for (i = 0; i < (called_len + calling_len); i++) {
+ if (i < called_len) {
+ if (i % 2 != 0) {
+ *called++ = ((*p >> 0) & 0x0F) + '0';
+ p++;
+ } else {
+ *called++ = ((*p >> 4) & 0x0F) + '0';
+ }
+ } else {
+ if (i % 2 != 0) {
+ *calling++ = ((*p >> 0) & 0x0F) + '0';
+ p++;
+ } else {
+ *calling++ = ((*p >> 4) & 0x0F) + '0';
+ }
+ }
+ }
+
+ *called = '\0';
+ *calling = '\0';
+
+ return 1 + (called_len + calling_len + 1) / 2;
+}
+
+int x25_addr_aton(unsigned char *p, x25_address *called_addr, x25_address *calling_addr)
+{
+ unsigned int called_len, calling_len;
+ char *called, *calling;
+ int i;
+
+ called = called_addr->x25_addr;
+ calling = calling_addr->x25_addr;
+
+ called_len = strlen(called);
+ calling_len = strlen(calling);
+
+ *p++ = (calling_len << 4) | (called_len << 0);
+
+ for (i = 0; i < (called_len + calling_len); i++) {
+ if (i < called_len) {
+ if (i % 2 != 0) {
+ *p |= (*called++ - '0') << 0;
+ p++;
+ } else {
+ *p = 0x00;
+ *p |= (*called++ - '0') << 4;
+ }
+ } else {
+ if (i % 2 != 0) {
+ *p |= (*calling++ - '0') << 0;
+ p++;
+ } else {
+ *p = 0x00;
+ *p |= (*calling++ - '0') << 4;
+ }
+ }
+ }
+
+ return 1 + (called_len + calling_len + 1) / 2;
+}
+
+/*
+ * Socket removal during an interrupt is now safe.
+ */
+static void x25_remove_socket(struct sock *sk)
+{
+ struct sock *s;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ if ((s = x25_list) == sk) {
+ x25_list = s->next;
+ restore_flags(flags);
+ return;
+ }
+
+ while (s != NULL && s->next != NULL) {
+ if (s->next == sk) {
+ s->next = sk->next;
+ restore_flags(flags);
+ return;
+ }
+
+ s = s->next;
+ }
+
+ restore_flags(flags);
+}
+
+/*
+ * Kill all bound sockets on a dropped device.
+ */
+static void x25_kill_by_device(struct net_device *dev)
+{
+ struct sock *s;
+
+ for (s = x25_list; s != NULL; s = s->next)
+ if (s->protinfo.x25->neighbour &&
+ s->protinfo.x25->neighbour->dev == dev)
+ x25_disconnect(s, ENETUNREACH, 0, 0);
+}
+
+/*
+ * Handle device status changes.
+ */
+static int x25_device_event(struct notifier_block *this, unsigned long event, void *ptr)
+{
+ struct net_device *dev = (struct net_device *)ptr;
+ struct x25_neigh *neigh;
+
+ if (dev->type == ARPHRD_X25
+#if defined(CONFIG_LLC) || defined(CONFIG_LLC_MODULE)
+ || dev->type == ARPHRD_ETHER
+#endif
+ ) {
+ switch (event) {
+ case NETDEV_UP:
+ x25_link_device_up(dev);
+ break;
+ case NETDEV_GOING_DOWN:
+ if ((neigh = x25_get_neigh(dev)))
+ x25_terminate_link(neigh);
+ break;
+ case NETDEV_DOWN:
+ x25_kill_by_device(dev);
+ x25_route_device_down(dev);
+ x25_link_device_down(dev);
+ break;
+ }
+ }
+
+ return NOTIFY_DONE;
+}
+
+/*
+ * Add a socket to the bound sockets list.
+ */
+static void x25_insert_socket(struct sock *sk)
+{
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ sk->next = x25_list;
+ x25_list = sk;
+
+ restore_flags(flags);
+}
+
+/*
+ * Find a socket that wants to accept the Call Request we just
+ * received.
+ */
+static struct sock *x25_find_listener(x25_address *addr)
+{
+ unsigned long flags;
+ struct sock *s;
+
+ save_flags(flags);
+ cli();
+
+ for (s = x25_list; s != NULL; s = s->next) {
+ if ((strcmp(addr->x25_addr, s->protinfo.x25->source_addr.x25_addr) == 0 ||
+ strcmp(addr->x25_addr, null_x25_address.x25_addr) == 0) &&
+ s->state == TCP_LISTEN) {
+ restore_flags(flags);
+ return s;
+ }
+ }
+
+ restore_flags(flags);
+ return NULL;
+}
+
+/*
+ * Find a connected X.25 socket given my LCI and neighbour.
+ */
+struct sock *x25_find_socket(unsigned int lci, struct x25_neigh *neigh)
+{
+ struct sock *s;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ for (s = x25_list; s != NULL; s = s->next) {
+ if (s->protinfo.x25->lci == lci && s->protinfo.x25->neighbour == neigh) {
+ restore_flags(flags);
+ return s;
+ }
+ }
+
+ restore_flags(flags);
+ return NULL;
+}
+
+/*
+ * Find a unique LCI for a given device.
+ */
+unsigned int x25_new_lci(struct x25_neigh *neigh)
+{
+ unsigned int lci = 1;
+
+ while (x25_find_socket(lci, neigh) != NULL) {
+ lci++;
+ if (lci == 4096) return 0;
+ }
+
+ return lci;
+}
+
+/*
+ * Deferred destroy.
+ */
+void x25_destroy_socket(struct sock *);
+
+/*
+ * handler for deferred kills.
+ */
+static void x25_destroy_timer(unsigned long data)
+{
+ x25_destroy_socket((struct sock *)data);
+}
+
+/*
+ * This is called from user mode and the timers. Thus it protects itself against
+ * interrupt users but doesn't worry about being called during work.
+ * Once it is removed from the queue no interrupt or bottom half will
+ * touch it and we are (fairly 8-) ) safe.
+ */
+void x25_destroy_socket(struct sock *sk) /* Not static as it's used by the timer */
+{
+ struct sk_buff *skb;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ x25_stop_heartbeat(sk);
+ x25_stop_timer(sk);
+
+ x25_remove_socket(sk);
+ x25_clear_queues(sk); /* Flush the queues */
+
+ while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
+ if (skb->sk != sk) { /* A pending connection */
+ skb->sk->dead = 1; /* Queue the unaccepted socket for death */
+ x25_start_heartbeat(skb->sk);
+ skb->sk->protinfo.x25->state = X25_STATE_0;
+ }
+
+ kfree_skb(skb);
+ }
+
+ if (atomic_read(&sk->wmem_alloc) != 0 || atomic_read(&sk->rmem_alloc) != 0) {
+ /* Defer: outstanding buffers */
+ init_timer(&sk->timer);
+ sk->timer.expires = jiffies + 10 * HZ;
+ sk->timer.function = x25_destroy_timer;
+ sk->timer.data = (unsigned long)sk;
+ add_timer(&sk->timer);
+ } else {
+ sk_free(sk);
+ MOD_DEC_USE_COUNT;
+ }
+
+ restore_flags(flags);
+}
+
+/*
+ * Handling for system calls applied via the various interfaces to a
+ * X.25 socket object.
+ */
+
+static int x25_setsockopt(struct socket *sock, int level, int optname,
+ char *optval, int optlen)
+{
+ struct sock *sk = sock->sk;
+ int opt;
+
+ if (level != SOL_X25)
+ return -ENOPROTOOPT;
+
+ if (optlen < sizeof(int))
+ return-EINVAL;
+
+ if (get_user(opt, (int *)optval))
+ return -EFAULT;
+
+ switch (optname) {
+ case X25_QBITINCL:
+ sk->protinfo.x25->qbitincl = opt ? 1 : 0;
+ return 0;
+
+ default:
+ return -ENOPROTOOPT;
+ }
+}
+
+static int x25_getsockopt(struct socket *sock, int level, int optname,
+ char *optval, int *optlen)
+{
+ struct sock *sk = sock->sk;
+ int val = 0;
+ int len;
+
+ if (level != SOL_X25)
+ return -ENOPROTOOPT;
+
+ if (get_user(len, optlen))
+ return -EFAULT;
+
+ switch (optname) {
+ case X25_QBITINCL:
+ val = sk->protinfo.x25->qbitincl;
+ break;
+
+ default:
+ return -ENOPROTOOPT;
+ }
+
+ len = min_t(unsigned int, len, sizeof(int));
+
+ if (len < 0)
+ return -EINVAL;
+
+ if (put_user(len, optlen))
+ return -EFAULT;
+
+ return copy_to_user(optval, &val, len) ? -EFAULT : 0;
+}
+
+static int x25_listen(struct socket *sock, int backlog)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk->state != TCP_LISTEN) {
+ memset(&sk->protinfo.x25->dest_addr, '\0', X25_ADDR_LEN);
+ sk->max_ack_backlog = backlog;
+ sk->state = TCP_LISTEN;
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static struct sock *x25_alloc_socket(void)
+{
+ struct sock *sk;
+ x25_cb *x25;
+
+ if ((sk = sk_alloc(AF_X25, GFP_ATOMIC, 1)) == NULL)
+ return NULL;
+
+ if ((x25 = kmalloc(sizeof(*x25), GFP_ATOMIC)) == NULL) {
+ sk_free(sk);
+ return NULL;
+ }
+
+ memset(x25, 0x00, sizeof(*x25));
+
+ x25->sk = sk;
+ sk->protinfo.x25 = x25;
+
+ MOD_INC_USE_COUNT;
+
+ sock_init_data(NULL, sk);
+
+ skb_queue_head_init(&x25->ack_queue);
+ skb_queue_head_init(&x25->fragment_queue);
+ skb_queue_head_init(&x25->interrupt_in_queue);
+ skb_queue_head_init(&x25->interrupt_out_queue);
+
+ return sk;
+}
+
+static int x25_create(struct socket *sock, int protocol)
+{
+ struct sock *sk;
+ x25_cb *x25;
+
+ if (sock->type != SOCK_SEQPACKET || protocol != 0)
+ return -ESOCKTNOSUPPORT;
+
+ if ((sk = x25_alloc_socket()) == NULL)
+ return -ENOMEM;
+
+ x25 = sk->protinfo.x25;
+
+ sock_init_data(sock, sk);
+
+ init_timer(&x25->timer);
+
+ sock->ops = &x25_proto_ops;
+ sk->protocol = protocol;
+ sk->backlog_rcv = x25_backlog_rcv;
+
+ x25->t21 = sysctl_x25_call_request_timeout;
+ x25->t22 = sysctl_x25_reset_request_timeout;
+ x25->t23 = sysctl_x25_clear_request_timeout;
+ x25->t2 = sysctl_x25_ack_holdback_timeout;
+ x25->state = X25_STATE_0;
+
+ x25->facilities.winsize_in = X25_DEFAULT_WINDOW_SIZE;
+ x25->facilities.winsize_out = X25_DEFAULT_WINDOW_SIZE;
+ x25->facilities.pacsize_in = X25_DEFAULT_PACKET_SIZE;
+ x25->facilities.pacsize_out = X25_DEFAULT_PACKET_SIZE;
+ x25->facilities.throughput = X25_DEFAULT_THROUGHPUT;
+ x25->facilities.reverse = X25_DEFAULT_REVERSE;
+
+ return 0;
+}
+
+static struct sock *x25_make_new(struct sock *osk)
+{
+ struct sock *sk;
+ x25_cb *x25;
+
+ if (osk->type != SOCK_SEQPACKET)
+ return NULL;
+
+ if ((sk = x25_alloc_socket()) == NULL)
+ return NULL;
+
+ x25 = sk->protinfo.x25;
+
+ sk->type = osk->type;
+ sk->socket = osk->socket;
+ sk->priority = osk->priority;
+ sk->protocol = osk->protocol;
+ sk->rcvbuf = osk->rcvbuf;
+ sk->sndbuf = osk->sndbuf;
+ sk->debug = osk->debug;
+ sk->state = TCP_ESTABLISHED;
+ sk->sleep = osk->sleep;
+ sk->zapped = osk->zapped;
+ sk->backlog_rcv = osk->backlog_rcv;
+
+ x25->t21 = osk->protinfo.x25->t21;
+ x25->t22 = osk->protinfo.x25->t22;
+ x25->t23 = osk->protinfo.x25->t23;
+ x25->t2 = osk->protinfo.x25->t2;
+
+ x25->facilities = osk->protinfo.x25->facilities;
+
+ x25->qbitincl = osk->protinfo.x25->qbitincl;
+
+ init_timer(&x25->timer);
+
+ return sk;
+}
+
+static int x25_release(struct socket *sock)
+{
+ struct sock *sk = sock->sk;
+
+ if (sk == NULL) return 0;
+
+ switch (sk->protinfo.x25->state) {
+
+ case X25_STATE_0:
+ case X25_STATE_2:
+ x25_disconnect(sk, 0, 0, 0);
+ x25_destroy_socket(sk);
+ break;
+
+ case X25_STATE_1:
+ case X25_STATE_3:
+ case X25_STATE_4:
+ x25_clear_queues(sk);
+ x25_write_internal(sk, X25_CLEAR_REQUEST);
+ x25_start_t23timer(sk);
+ sk->protinfo.x25->state = X25_STATE_2;
+ sk->state = TCP_CLOSE;
+ sk->shutdown |= SEND_SHUTDOWN;
+ sk->state_change(sk);
+ sk->dead = 1;
+ sk->destroy = 1;
+ break;
+
+ default:
+ break;
+ }
+
+ sock->sk = NULL;
+ sk->socket = NULL; /* Not used, but we should do this */
+
+ return 0;
+}
+
+static int x25_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_x25 *addr = (struct sockaddr_x25 *)uaddr;
+
+ if (sk->zapped == 0)
+ return -EINVAL;
+
+ if (addr_len != sizeof(struct sockaddr_x25))
+ return -EINVAL;
+
+ if (addr->sx25_family != AF_X25)
+ return -EINVAL;
+
+ sk->protinfo.x25->source_addr = addr->sx25_addr;
+
+ x25_insert_socket(sk);
+
+ sk->zapped = 0;
+
+ SOCK_DEBUG(sk, "x25_bind: socket is bound\n");
+
+ return 0;
+}
+
+static int x25_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_x25 *addr = (struct sockaddr_x25 *)uaddr;
+ struct net_device *dev;
+
+ if (sk->state == TCP_ESTABLISHED && sock->state == SS_CONNECTING) {
+ sock->state = SS_CONNECTED;
+ return 0; /* Connect completed during a ERESTARTSYS event */
+ }
+
+ if (sk->state == TCP_CLOSE && sock->state == SS_CONNECTING) {
+ sock->state = SS_UNCONNECTED;
+ return -ECONNREFUSED;
+ }
+
+ if (sk->state == TCP_ESTABLISHED)
+ return -EISCONN; /* No reconnect on a seqpacket socket */
+
+ sk->state = TCP_CLOSE;
+ sock->state = SS_UNCONNECTED;
+
+ if (addr_len != sizeof(struct sockaddr_x25))
+ return -EINVAL;
+
+ if (addr->sx25_family != AF_X25)
+ return -EINVAL;
+
+ if ((dev = x25_get_route(&addr->sx25_addr)) == NULL)
+ return -ENETUNREACH;
+
+ if ((sk->protinfo.x25->neighbour = x25_get_neigh(dev)) == NULL)
+ return -ENETUNREACH;
+
+ x25_limit_facilities(&sk->protinfo.x25->facilities,
+ sk->protinfo.x25->neighbour);
+
+ if ((sk->protinfo.x25->lci = x25_new_lci(sk->protinfo.x25->neighbour)) == 0)
+ return -ENETUNREACH;
+
+ if (sk->zapped) /* Must bind first - autobinding does not work */
+ return -EINVAL;
+
+ if (strcmp(sk->protinfo.x25->source_addr.x25_addr, null_x25_address.x25_addr) == 0)
+ memset(&sk->protinfo.x25->source_addr, '\0', X25_ADDR_LEN);
+
+ sk->protinfo.x25->dest_addr = addr->sx25_addr;
+
+ /* Move to connecting socket, start sending Connect Requests */
+ sock->state = SS_CONNECTING;
+ sk->state = TCP_SYN_SENT;
+
+ sk->protinfo.x25->state = X25_STATE_1;
+
+ x25_write_internal(sk, X25_CALL_REQUEST);
+
+ x25_start_heartbeat(sk);
+ x25_start_t21timer(sk);
+
+ /* Now the loop */
+ if (sk->state != TCP_ESTABLISHED && (flags & O_NONBLOCK))
+ return -EINPROGRESS;
+
+ cli(); /* To avoid races on the sleep */
+
+ /*
+ * A Clear Request or timeout or failed routing will go to closed.
+ */
+ while (sk->state == TCP_SYN_SENT) {
+ interruptible_sleep_on(sk->sleep);
+ if (signal_pending(current)) {
+ sti();
+ return -ERESTARTSYS;
+ }
+ }
+
+ if (sk->state != TCP_ESTABLISHED) {
+ sti();
+ sock->state = SS_UNCONNECTED;
+ return sock_error(sk); /* Always set at this point */
+ }
+
+ sock->state = SS_CONNECTED;
+
+ sti();
+
+ return 0;
+}
+
+static int x25_accept(struct socket *sock, struct socket *newsock, int flags)
+{
+ struct sock *sk;
+ struct sock *newsk;
+ struct sk_buff *skb;
+
+ if ((sk = sock->sk) == NULL)
+ return -EINVAL;
+
+ if (sk->type != SOCK_SEQPACKET)
+ return -EOPNOTSUPP;
+
+ if (sk->state != TCP_LISTEN)
+ return -EINVAL;
+
+ /*
+ * The write queue this time is holding sockets ready to use
+ * hooked into the CALL INDICATION we saved
+ */
+ do {
+ cli();
+ if ((skb = skb_dequeue(&sk->receive_queue)) == NULL) {
+ if (flags & O_NONBLOCK) {
+ sti();
+ return -EWOULDBLOCK;
+ }
+ interruptible_sleep_on(sk->sleep);
+ if (signal_pending(current)) {
+ sti();
+ return -ERESTARTSYS;
+ }
+ }
+ } while (skb == NULL);
+
+ newsk = skb->sk;
+ newsk->pair = NULL;
+ newsk->socket = newsock;
+ newsk->sleep = &newsock->wait;
+ sti();
+
+ /* Now attach up the new socket */
+ skb->sk = NULL;
+ kfree_skb(skb);
+ sk->ack_backlog--;
+ newsock->sk = newsk;
+ newsock->state = SS_CONNECTED;
+
+ return 0;
+}
+
+static int x25_getname(struct socket *sock, struct sockaddr *uaddr, int *uaddr_len, int peer)
+{
+ struct sockaddr_x25 *sx25 = (struct sockaddr_x25 *)uaddr;
+ struct sock *sk = sock->sk;
+
+ if (peer != 0) {
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+ sx25->sx25_addr = sk->protinfo.x25->dest_addr;
+ } else {
+ sx25->sx25_addr = sk->protinfo.x25->source_addr;
+ }
+
+ sx25->sx25_family = AF_X25;
+ *uaddr_len = sizeof(struct sockaddr_x25);
+
+ return 0;
+}
+
+int x25_rx_call_request(struct sk_buff *skb, struct x25_neigh *neigh, unsigned int lci)
+{
+ struct sock *sk;
+ struct sock *make;
+ x25_address source_addr, dest_addr;
+ struct x25_facilities facilities;
+ int len;
+
+ /*
+ * Remove the LCI and frame type.
+ */
+ skb_pull(skb, X25_STD_MIN_LEN);
+
+ /*
+ * Extract the X.25 addresses and convert them to ASCII strings,
+ * and remove them.
+ */
+ skb_pull(skb, x25_addr_ntoa(skb->data, &source_addr, &dest_addr));
+
+ /*
+ * Find a listener for the particular address.
+ */
+ sk = x25_find_listener(&source_addr);
+
+ /*
+ * We can't accept the Call Request.
+ */
+ if (sk == NULL || sk->ack_backlog == sk->max_ack_backlog) {
+ x25_transmit_clear_request(neigh, lci, 0x01);
+ return 0;
+ }
+
+ /*
+ * Try to reach a compromise on the requested facilities.
+ */
+ if ((len = x25_negotiate_facilities(skb, sk, &facilities)) == -1) {
+ x25_transmit_clear_request(neigh, lci, 0x01);
+ return 0;
+ }
+
+ /*
+ * current neighbour/link might impose additional limits
+ * on certain facilties
+ */
+
+ x25_limit_facilities(&facilities,neigh);
+
+ /*
+ * Try to create a new socket.
+ */
+ if ((make = x25_make_new(sk)) == NULL) {
+ x25_transmit_clear_request(neigh, lci, 0x01);
+ return 0;
+ }
+
+ /*
+ * Remove the facilities, leaving any Call User Data.
+ */
+ skb_pull(skb, len);
+
+ skb->sk = make;
+ make->state = TCP_ESTABLISHED;
+
+ make->protinfo.x25->lci = lci;
+ make->protinfo.x25->dest_addr = dest_addr;
+ make->protinfo.x25->source_addr = source_addr;
+ make->protinfo.x25->neighbour = neigh;
+ make->protinfo.x25->facilities = facilities;
+ make->protinfo.x25->vc_facil_mask = sk->protinfo.x25->vc_facil_mask;
+
+ x25_write_internal(make, X25_CALL_ACCEPTED);
+
+ /*
+ * Incoming Call User Data.
+ */
+ if (skb->len >= 0) {
+ memcpy(make->protinfo.x25->calluserdata.cuddata, skb->data, skb->len);
+ make->protinfo.x25->calluserdata.cudlength = skb->len;
+ }
+
+ make->protinfo.x25->state = X25_STATE_3;
+
+ sk->ack_backlog++;
+ make->pair = sk;
+
+ x25_insert_socket(make);
+
+ skb_queue_head(&sk->receive_queue, skb);
+
+ x25_start_heartbeat(make);
+
+ if (!sk->dead)
+ sk->data_ready(sk, skb->len);
+
+ return 1;
+}
+
+static int x25_sendmsg(struct socket *sock, struct msghdr *msg, int len, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_x25 *usx25 = (struct sockaddr_x25 *)msg->msg_name;
+ int err;
+ struct sockaddr_x25 sx25;
+ struct sk_buff *skb;
+ unsigned char *asmptr;
+ int size, qbit = 0;
+
+ if (msg->msg_flags & ~(MSG_DONTWAIT | MSG_OOB | MSG_EOR))
+ return -EINVAL;
+
+ /* we currently don't support segmented records at the user interface */
+ if (!(msg->msg_flags & (MSG_EOR|MSG_OOB)))
+ return -EINVAL;
+
+ if (sk->zapped)
+ return -EADDRNOTAVAIL;
+
+ if (sk->shutdown & SEND_SHUTDOWN) {
+ send_sig(SIGPIPE, current, 0);
+ return -EPIPE;
+ }
+
+ if (sk->protinfo.x25->neighbour == NULL)
+ return -ENETUNREACH;
+
+ if (usx25 != NULL) {
+ if (msg->msg_namelen < sizeof(sx25))
+ return -EINVAL;
+ sx25 = *usx25;
+ if (strcmp(sk->protinfo.x25->dest_addr.x25_addr, sx25.sx25_addr.x25_addr) != 0)
+ return -EISCONN;
+ if (sx25.sx25_family != AF_X25)
+ return -EINVAL;
+ } else {
+ /*
+ * FIXME 1003.1g - if the socket is like this because
+ * it has become closed (not started closed) we ought
+ * to SIGPIPE, EPIPE;
+ */
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+
+ sx25.sx25_family = AF_X25;
+ sx25.sx25_addr = sk->protinfo.x25->dest_addr;
+ }
+
+ SOCK_DEBUG(sk, "x25_sendmsg: sendto: Addresses built.\n");
+
+ /* Build a packet */
+ SOCK_DEBUG(sk, "x25_sendmsg: sendto: building packet.\n");
+
+ if ((msg->msg_flags & MSG_OOB) && len > 32)
+ len = 32;
+
+ size = len + X25_MAX_L2_LEN + X25_EXT_MIN_LEN;
+
+ if ((skb = sock_alloc_send_skb(sk, size, msg->msg_flags & MSG_DONTWAIT, &err)) == NULL)
+ return err;
+ X25_SKB_CB(skb)->flags = msg->msg_flags;
+
+ skb_reserve(skb, X25_MAX_L2_LEN + X25_EXT_MIN_LEN);
+
+ /*
+ * Put the data on the end
+ */
+ SOCK_DEBUG(sk, "x25_sendmsg: Copying user data\n");
+
+ asmptr = skb->h.raw = skb_put(skb, len);
+
+ memcpy_fromiovec(asmptr, msg->msg_iov, len);
+
+ /*
+ * If the Q BIT Include socket option is in force, the first
+ * byte of the user data is the logical value of the Q Bit.
+ */
+ if (sk->protinfo.x25->qbitincl) {
+ qbit = skb->data[0];
+ skb_pull(skb, 1);
+ }
+
+ /*
+ * Push down the X.25 header
+ */
+ SOCK_DEBUG(sk, "x25_sendmsg: Building X.25 Header.\n");
+
+ if (msg->msg_flags & MSG_OOB) {
+ if (sk->protinfo.x25->neighbour->extended) {
+ asmptr = skb_push(skb, X25_STD_MIN_LEN);
+ *asmptr++ = ((sk->protinfo.x25->lci >> 8) & 0x0F) | X25_GFI_EXTSEQ;
+ *asmptr++ = (sk->protinfo.x25->lci >> 0) & 0xFF;
+ *asmptr++ = X25_INTERRUPT;
+ } else {
+ asmptr = skb_push(skb, X25_STD_MIN_LEN);
+ *asmptr++ = ((sk->protinfo.x25->lci >> 8) & 0x0F) | X25_GFI_STDSEQ;
+ *asmptr++ = (sk->protinfo.x25->lci >> 0) & 0xFF;
+ *asmptr++ = X25_INTERRUPT;
+ }
+ } else {
+ if (sk->protinfo.x25->neighbour->extended) {
+ /* Build an Extended X.25 header */
+ asmptr = skb_push(skb, X25_EXT_MIN_LEN);
+ *asmptr++ = ((sk->protinfo.x25->lci >> 8) & 0x0F) | X25_GFI_EXTSEQ;
+ *asmptr++ = (sk->protinfo.x25->lci >> 0) & 0xFF;
+ *asmptr++ = X25_DATA;
+ *asmptr++ = X25_DATA;
+ } else {
+ /* Build an Standard X.25 header */
+ asmptr = skb_push(skb, X25_STD_MIN_LEN);
+ *asmptr++ = ((sk->protinfo.x25->lci >> 8) & 0x0F) | X25_GFI_STDSEQ;
+ *asmptr++ = (sk->protinfo.x25->lci >> 0) & 0xFF;
+ *asmptr++ = X25_DATA;
+ }
+
+ if (qbit)
+ skb->data[0] |= X25_Q_BIT;
+ }
+
+ SOCK_DEBUG(sk, "x25_sendmsg: Built header.\n");
+ SOCK_DEBUG(sk, "x25_sendmsg: Transmitting buffer\n");
+
+ if (sk->state != TCP_ESTABLISHED) {
+ kfree_skb(skb);
+ return -ENOTCONN;
+ }
+
+ if (msg->msg_flags & MSG_OOB) {
+ skb_queue_tail(&sk->protinfo.x25->interrupt_out_queue, skb);
+ } else {
+ len = x25_output(sk, skb);
+ if(len<0){
+ kfree_skb(skb);
+ } else {
+ if(sk->protinfo.x25->qbitincl) len++;
+ }
+ }
+
+ /*
+ * lock_sock() is currently only used to serialize this x25_kick()
+ * against input-driven x25_kick() calls. It currently only blocks
+ * incoming packets for this socket and does not protect against
+ * any other socket state changes and is not called from anywhere
+ * else. As x25_kick() cannot block and as long as all socket
+ * operations are BKL-wrapped, we don't need take to care about
+ * purging the backlog queue in x25_release().
+ *
+ * Using lock_sock() to protect all socket operations entirely
+ * (and making the whole x25 stack SMP aware) unfortunately would
+ * require major changes to {send,recv}msg and skb allocation methods.
+ * -> 2.5 ;)
+ */
+ lock_sock(sk);
+ x25_kick(sk);
+ release_sock(sk);
+
+ return len;
+}
+
+
+static int x25_recvmsg(struct socket *sock, struct msghdr *msg, int size, int flags, struct scm_cookie *scm)
+{
+ struct sock *sk = sock->sk;
+ struct sockaddr_x25 *sx25 = (struct sockaddr_x25 *)msg->msg_name;
+ int copied, qbit;
+ struct sk_buff *skb;
+ unsigned char *asmptr;
+ int er;
+
+ /*
+ * This works for seqpacket too. The receiver has ordered the queue for
+ * us! We do one quick check first though
+ */
+ if (sk->state != TCP_ESTABLISHED)
+ return -ENOTCONN;
+
+ if (flags & MSG_OOB) {
+ if (sk->urginline || skb_peek(&sk->protinfo.x25->interrupt_in_queue) == NULL)
+ return -EINVAL;
+
+ skb = skb_dequeue(&sk->protinfo.x25->interrupt_in_queue);
+
+ skb_pull(skb, X25_STD_MIN_LEN);
+
+ /*
+ * No Q bit information on Interrupt data.
+ */
+ if (sk->protinfo.x25->qbitincl) {
+ asmptr = skb_push(skb, 1);
+ *asmptr = 0x00;
+ }
+
+ msg->msg_flags |= MSG_OOB;
+ } else {
+ /* Now we can treat all alike */
+ if ((skb = skb_recv_datagram(sk, flags & ~MSG_DONTWAIT, flags & MSG_DONTWAIT, &er)) == NULL)
+ return er;
+
+ qbit = (skb->data[0] & X25_Q_BIT) == X25_Q_BIT;
+
+ skb_pull(skb, (sk->protinfo.x25->neighbour->extended) ? X25_EXT_MIN_LEN : X25_STD_MIN_LEN);
+
+ if (sk->protinfo.x25->qbitincl) {
+ asmptr = skb_push(skb, 1);
+ *asmptr = qbit;
+ }
+ }
+
+ skb->h.raw = skb->data;
+
+ copied = skb->len;
+
+ if (copied > size) {
+ copied = size;
+ msg->msg_flags |= MSG_TRUNC;
+ }
+
+ /* Currently, each datagram always contains a complete record */
+ msg->msg_flags |= MSG_EOR;
+
+ skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
+
+ if (sx25 != NULL) {
+ sx25->sx25_family = AF_X25;
+ sx25->sx25_addr = sk->protinfo.x25->dest_addr;
+ }
+
+ msg->msg_namelen = sizeof(struct sockaddr_x25);
+
+ skb_free_datagram(sk, skb);
+ lock_sock(sk);
+ x25_check_rbuf(sk);
+ release_sock(sk);
+
+ return copied;
+}
+
+
+static int x25_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
+{
+ struct sock *sk = sock->sk;
+
+ switch (cmd) {
+ case TIOCOUTQ: {
+ int amount;
+ amount = sk->sndbuf - atomic_read(&sk->wmem_alloc);
+ if (amount < 0)
+ amount = 0;
+ return put_user(amount, (unsigned int *)arg);
+ }
+
+ case TIOCINQ: {
+ struct sk_buff *skb;
+ int amount = 0;
+ /* These two are safe on a single CPU system as only user tasks fiddle here */
+ if ((skb = skb_peek(&sk->receive_queue)) != NULL)
+ amount = skb->len;
+ return put_user(amount, (unsigned int *)arg);
+ }
+
+ case SIOCGSTAMP:
+ if (sk != NULL) {
+ if (sk->stamp.tv_sec == 0)
+ return -ENOENT;
+ return copy_to_user((void *)arg, &sk->stamp, sizeof(struct timeval)) ? -EFAULT : 0;
+ }
+ return -EINVAL;
+
+ case SIOCGIFADDR:
+ case SIOCSIFADDR:
+ case SIOCGIFDSTADDR:
+ case SIOCSIFDSTADDR:
+ case SIOCGIFBRDADDR:
+ case SIOCSIFBRDADDR:
+ case SIOCGIFNETMASK:
+ case SIOCSIFNETMASK:
+ case SIOCGIFMETRIC:
+ case SIOCSIFMETRIC:
+ return -EINVAL;
+
+ case SIOCADDRT:
+ case SIOCDELRT:
+ if (!capable(CAP_NET_ADMIN)) return -EPERM;
+ return x25_route_ioctl(cmd, (void *)arg);
+
+ case SIOCX25GSUBSCRIP:
+ return x25_subscr_ioctl(cmd, (void *)arg);
+
+ case SIOCX25SSUBSCRIP:
+ if (!capable(CAP_NET_ADMIN)) return -EPERM;
+ return x25_subscr_ioctl(cmd, (void *)arg);
+
+ case SIOCX25GFACILITIES: {
+ struct x25_facilities facilities;
+ facilities = sk->protinfo.x25->facilities;
+ return copy_to_user((void *)arg, &facilities, sizeof(facilities)) ? -EFAULT : 0;
+ }
+
+ case SIOCX25SFACILITIES: {
+ struct x25_facilities facilities;
+ if (copy_from_user(&facilities, (void *)arg, sizeof(facilities)))
+ return -EFAULT;
+ if (sk->state != TCP_LISTEN && sk->state != TCP_CLOSE)
+ return -EINVAL;
+ if (facilities.pacsize_in < X25_PS16 || facilities.pacsize_in > X25_PS4096)
+ return -EINVAL;
+ if (facilities.pacsize_out < X25_PS16 || facilities.pacsize_out > X25_PS4096)
+ return -EINVAL;
+ if (facilities.winsize_in < 1 || facilities.winsize_in > 127)
+ return -EINVAL;
+ if (facilities.throughput < 0x03 || facilities.throughput > 0xDD)
+ return -EINVAL;
+ if (facilities.reverse != 0 && facilities.reverse != 1)
+ return -EINVAL;
+ sk->protinfo.x25->facilities = facilities;
+ return 0;
+ }
+
+ case SIOCX25GCALLUSERDATA: {
+ struct x25_calluserdata calluserdata;
+ calluserdata = sk->protinfo.x25->calluserdata;
+ return copy_to_user((void *)arg, &calluserdata, sizeof(calluserdata)) ? -EFAULT : 0;
+ }
+
+ case SIOCX25SCALLUSERDATA: {
+ struct x25_calluserdata calluserdata;
+ if (copy_from_user(&calluserdata, (void *)arg, sizeof(calluserdata)))
+ return -EFAULT;
+ if (calluserdata.cudlength > X25_MAX_CUD_LEN)
+ return -EINVAL;
+ sk->protinfo.x25->calluserdata = calluserdata;
+ return 0;
+ }
+
+ case SIOCX25GCAUSEDIAG: {
+ struct x25_causediag causediag;
+ causediag = sk->protinfo.x25->causediag;
+ return copy_to_user((void *)arg, &causediag, sizeof(causediag)) ? -EFAULT : 0;
+ }
+
+ default:
+ return dev_ioctl(cmd, (void *)arg);
+ }
+
+ /*NOTREACHED*/
+ return 0;
+}
+
+static int x25_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct sock *s;
+ struct net_device *dev;
+ const char *devname;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+
+ cli();
+
+ len += sprintf(buffer, "dest_addr src_addr dev lci st vs vr va t t2 t21 t22 t23 Snd-Q Rcv-Q inode\n");
+
+ for (s = x25_list; s != NULL; s = s->next) {
+ if (s->protinfo.x25->neighbour == NULL || (dev = s->protinfo.x25->neighbour->dev) == NULL)
+ devname = "???";
+ else
+ devname = s->protinfo.x25->neighbour->dev->name;
+
+ len += sprintf(buffer + len, "%-10s %-10s %-5s %3.3X %d %d %d %d %3lu %3lu %3lu %3lu %3lu %5d %5d %ld\n",
+ (s->protinfo.x25->dest_addr.x25_addr[0] == '\0') ? "*" : s->protinfo.x25->dest_addr.x25_addr,
+ (s->protinfo.x25->source_addr.x25_addr[0] == '\0') ? "*" : s->protinfo.x25->source_addr.x25_addr,
+ devname,
+ s->protinfo.x25->lci & 0x0FFF,
+ s->protinfo.x25->state,
+ s->protinfo.x25->vs,
+ s->protinfo.x25->vr,
+ s->protinfo.x25->va,
+ x25_display_timer(s) / HZ,
+ s->protinfo.x25->t2 / HZ,
+ s->protinfo.x25->t21 / HZ,
+ s->protinfo.x25->t22 / HZ,
+ s->protinfo.x25->t23 / HZ,
+ atomic_read(&s->wmem_alloc),
+ atomic_read(&s->rmem_alloc),
+ s->socket != NULL ? s->socket->inode->i_ino : 0L);
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ break;
+ }
+
+ sti();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return(len);
+}
+
+struct net_proto_family x25_family_ops = {
+ family: AF_X25,
+ create: x25_create,
+};
+
+static struct proto_ops SOCKOPS_WRAPPED(x25_proto_ops) = {
+ family: AF_X25,
+
+ release: x25_release,
+ bind: x25_bind,
+ connect: x25_connect,
+ socketpair: sock_no_socketpair,
+ accept: x25_accept,
+ getname: x25_getname,
+ poll: datagram_poll,
+ ioctl: x25_ioctl,
+ listen: x25_listen,
+ shutdown: sock_no_shutdown,
+ setsockopt: x25_setsockopt,
+ getsockopt: x25_getsockopt,
+ sendmsg: x25_sendmsg,
+ recvmsg: x25_recvmsg,
+ mmap: sock_no_mmap,
+ sendpage: sock_no_sendpage,
+};
+
+#include <linux/smp_lock.h>
+SOCKOPS_WRAP(x25_proto, AF_X25);
+
+
+static struct packet_type x25_packet_type = {
+ type: __constant_htons(ETH_P_X25),
+ func: x25_lapb_receive_frame,
+};
+
+struct notifier_block x25_dev_notifier = {
+ notifier_call: x25_device_event,
+};
+
+void x25_kill_by_neigh(struct x25_neigh *neigh)
+{
+ struct sock *s;
+
+ for( s=x25_list; s != NULL; s=s->next){
+ if( s->protinfo.x25->neighbour == neigh )
+ x25_disconnect(s, ENETUNREACH, 0, 0);
+ }
+}
+
+static int __init x25_init(void)
+{
+#ifdef MODULE
+ struct net_device *dev;
+#endif /* MODULE */
+ sock_register(&x25_family_ops);
+
+ dev_add_pack(&x25_packet_type);
+
+ register_netdevice_notifier(&x25_dev_notifier);
+
+ printk(KERN_INFO "X.25 for Linux. Version 0.2 for Linux 2.1.15\n");
+
+#ifdef CONFIG_SYSCTL
+ x25_register_sysctl();
+#endif
+
+ proc_net_create("x25", 0, x25_get_info);
+ proc_net_create("x25_routes", 0, x25_routes_get_info);
+
+#ifdef MODULE
+ /*
+ * Register any pre existing devices.
+ */
+ read_lock(&dev_base_lock);
+ for (dev = dev_base; dev != NULL; dev = dev->next) {
+ if ((dev->flags & IFF_UP) && (dev->type == ARPHRD_X25
+#if defined(CONFIG_LLC) || defined(CONFIG_LLC_MODULE)
+ || dev->type == ARPHRD_ETHER
+#endif
+ ))
+ x25_link_device_up(dev);
+ }
+ read_unlock(&dev_base_lock);
+#endif /* MODULE */
+ return 0;
+}
+module_init(x25_init);
+
+
+
+EXPORT_NO_SYMBOLS;
+
+MODULE_AUTHOR("Jonathan Naylor <g4klx@g4klx.demon.co.uk>");
+MODULE_DESCRIPTION("The X.25 Packet Layer network layer protocol");
+MODULE_LICENSE("GPL");
+
+static void __exit x25_exit(void)
+{
+
+ proc_net_remove("x25");
+ proc_net_remove("x25_routes");
+
+ x25_link_free();
+ x25_route_free();
+
+#ifdef CONFIG_SYSCTL
+ x25_unregister_sysctl();
+#endif
+
+ unregister_netdevice_notifier(&x25_dev_notifier);
+
+ dev_remove_pack(&x25_packet_type);
+
+ sock_unregister(AF_X25);
+}
+module_exit(x25_exit);
+
diff --git a/uClinux-2.4.31-uc0/net/x25/sysctl_net_x25.c b/uClinux-2.4.31-uc0/net/x25/sysctl_net_x25.c
new file mode 100644
index 0000000..92b3b8f
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/x25/sysctl_net_x25.c
@@ -0,0 +1,58 @@
+/* -*- linux-c -*-
+ * sysctl_net_x25.c: sysctl interface to net X.25 subsystem.
+ *
+ * Begun April 1, 1996, Mike Shaver.
+ * Added /proc/sys/net/x25 directory entry (empty =) ). [MS]
+ */
+
+#include <linux/mm.h>
+#include <linux/sysctl.h>
+#include <linux/skbuff.h>
+#include <linux/socket.h>
+#include <linux/netdevice.h>
+#include <linux/init.h>
+#include <net/x25.h>
+
+static int min_timer[] = {1 * HZ};
+static int max_timer[] = {300 * HZ};
+
+static struct ctl_table_header *x25_table_header;
+
+static ctl_table x25_table[] = {
+ {NET_X25_RESTART_REQUEST_TIMEOUT, "restart_request_timeout",
+ &sysctl_x25_restart_request_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_timer, &max_timer},
+ {NET_X25_CALL_REQUEST_TIMEOUT, "call_request_timeout",
+ &sysctl_x25_call_request_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_timer, &max_timer},
+ {NET_X25_RESET_REQUEST_TIMEOUT, "reset_request_timeout",
+ &sysctl_x25_reset_request_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_timer, &max_timer},
+ {NET_X25_CLEAR_REQUEST_TIMEOUT, "clear_request_timeout",
+ &sysctl_x25_clear_request_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_timer, &max_timer},
+ {NET_X25_ACK_HOLD_BACK_TIMEOUT, "acknowledgement_hold_back_timeout",
+ &sysctl_x25_ack_holdback_timeout, sizeof(int), 0644, NULL,
+ &proc_dointvec_minmax, &sysctl_intvec, NULL, &min_timer, &max_timer},
+ {0}
+};
+
+static ctl_table x25_dir_table[] = {
+ {NET_X25, "x25", NULL, 0, 0555, x25_table},
+ {0}
+};
+
+static ctl_table x25_root_table[] = {
+ {CTL_NET, "net", NULL, 0, 0555, x25_dir_table},
+ {0}
+};
+
+void __init x25_register_sysctl(void)
+{
+ x25_table_header = register_sysctl_table(x25_root_table, 1);
+}
+
+void x25_unregister_sysctl(void)
+{
+ unregister_sysctl_table(x25_table_header);
+}
diff --git a/uClinux-2.4.31-uc0/net/x25/x25_dev.c b/uClinux-2.4.31-uc0/net/x25/x25_dev.c
new file mode 100644
index 0000000..1abb283
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/x25/x25_dev.c
@@ -0,0 +1,250 @@
+/*
+ * X.25 Packet Layer release 002
+ *
+ * This is ALPHA test software. This code may break your machine, randomly fail to work with new
+ * releases, misbehave and/or generally screw up. It might even work.
+ *
+ * This code REQUIRES 2.1.15 or higher
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * X.25 001 Jonathan Naylor Started coding.
+ * 2000-09-04 Henner Eisen Prevent freeing a dangling skb.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/stat.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <linux/fcntl.h>
+#include <linux/termios.h> /* For TIOCINQ/OUTQ */
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/proc_fs.h>
+#include <linux/if_arp.h>
+#include <net/x25.h>
+
+static int x25_receive_data(struct sk_buff *skb, struct x25_neigh *neigh)
+{
+ struct sock *sk;
+ unsigned short frametype;
+ unsigned int lci;
+
+ frametype = skb->data[2];
+ lci = ((skb->data[0] << 8) & 0xF00) + ((skb->data[1] << 0) & 0x0FF);
+
+ /*
+ * LCI of zero is always for us, and its always a link control
+ * frame.
+ */
+ if (lci == 0) {
+ x25_link_control(skb, neigh, frametype);
+ return 0;
+ }
+
+ /*
+ * Find an existing socket.
+ */
+ if ((sk = x25_find_socket(lci, neigh)) != NULL) {
+ int queued = 1;
+
+ skb->h.raw = skb->data;
+ bh_lock_sock(sk);
+ if (!sk->lock.users) {
+ queued = x25_process_rx_frame(sk, skb);
+ } else {
+ sk_add_backlog(sk, skb);
+ }
+ bh_unlock_sock(sk);
+ return queued;
+ }
+
+ /*
+ * Is is a Call Request ? if so process it.
+ */
+ if (frametype == X25_CALL_REQUEST)
+ return x25_rx_call_request(skb, neigh, lci);
+
+ /*
+ * Its not a Call Request, nor is it a control frame.
+ * Let caller throw it away.
+ */
+/*
+ x25_transmit_clear_request(neigh, lci, 0x0D);
+*/
+ printk(KERN_DEBUG "x25_receive_data(): unknown frame type %2x\n",frametype);
+
+ return 0;
+}
+
+int x25_lapb_receive_frame(struct sk_buff *skb, struct net_device *dev, struct packet_type *ptype)
+{
+ struct x25_neigh *neigh;
+ int queued;
+
+ skb->sk = NULL;
+
+ /*
+ * Packet received from unrecognised device, throw it away.
+ */
+ if ((neigh = x25_get_neigh(dev)) == NULL) {
+ printk(KERN_DEBUG "X.25: unknown neighbour - %s\n", dev->name);
+ kfree_skb(skb);
+ return 0;
+ }
+
+ switch (skb->data[0]) {
+ case 0x00:
+ skb_pull(skb, 1);
+ queued = x25_receive_data(skb, neigh);
+ if( ! queued )
+ /* We need to free the skb ourselves because
+ * net_bh() won't care about our return code.
+ */
+ kfree_skb(skb);
+ return 0;
+
+ case 0x01:
+ x25_link_established(neigh);
+ kfree_skb(skb);
+ return 0;
+
+ case 0x02:
+ x25_link_terminated(neigh);
+ kfree_skb(skb);
+ return 0;
+
+ case 0x03:
+ kfree_skb(skb);
+ return 0;
+
+ default:
+ kfree_skb(skb);
+ return 0;
+ }
+}
+
+int x25_llc_receive_frame(struct sk_buff *skb, struct net_device *dev, struct packet_type *ptype)
+{
+ struct x25_neigh *neigh;
+
+ skb->sk = NULL;
+
+ /*
+ * Packet received from unrecognised device, throw it away.
+ */
+ if ((neigh = x25_get_neigh(dev)) == NULL) {
+ printk(KERN_DEBUG "X.25: unknown_neighbour - %s\n", dev->name);
+ kfree_skb(skb);
+ return 0;
+ }
+
+ return x25_receive_data(skb, neigh);
+}
+
+void x25_establish_link(struct x25_neigh *neigh)
+{
+ struct sk_buff *skb;
+ unsigned char *ptr;
+
+ switch (neigh->dev->type) {
+ case ARPHRD_X25:
+ if ((skb = alloc_skb(1, GFP_ATOMIC)) == NULL) {
+ printk(KERN_ERR "x25_dev: out of memory\n");
+ return;
+ }
+ ptr = skb_put(skb, 1);
+ *ptr = 0x01;
+ break;
+
+#if defined(CONFIG_LLC) || defined(CONFIG_LLC_MODULE)
+ case ARPHRD_ETHER:
+ return;
+#endif
+ default:
+ return;
+ }
+
+ skb->protocol = htons(ETH_P_X25);
+ skb->dev = neigh->dev;
+
+ dev_queue_xmit(skb);
+}
+
+void x25_terminate_link(struct x25_neigh *neigh)
+{
+ struct sk_buff *skb;
+ unsigned char *ptr;
+
+ switch (neigh->dev->type) {
+ case ARPHRD_X25:
+ if ((skb = alloc_skb(1, GFP_ATOMIC)) == NULL) {
+ printk(KERN_ERR "x25_dev: out of memory\n");
+ return;
+ }
+ ptr = skb_put(skb, 1);
+ *ptr = 0x02;
+ break;
+
+#if defined(CONFIG_LLC) || defined(CONFIG_LLC_MODULE)
+ case ARPHRD_ETHER:
+ return;
+#endif
+ default:
+ return;
+ }
+
+ skb->protocol = htons(ETH_P_X25);
+ skb->dev = neigh->dev;
+
+ dev_queue_xmit(skb);
+}
+
+void x25_send_frame(struct sk_buff *skb, struct x25_neigh *neigh)
+{
+ unsigned char *dptr;
+
+ skb->nh.raw = skb->data;
+
+ switch (neigh->dev->type) {
+ case ARPHRD_X25:
+ dptr = skb_push(skb, 1);
+ *dptr = 0x00;
+ break;
+
+#if defined(CONFIG_LLC) || defined(CONFIG_LLC_MODULE)
+ case ARPHRD_ETHER:
+ kfree_skb(skb);
+ return;
+#endif
+ default:
+ kfree_skb(skb);
+ return;
+ }
+
+ skb->protocol = htons(ETH_P_X25);
+ skb->dev = neigh->dev;
+
+ dev_queue_xmit(skb);
+}
diff --git a/uClinux-2.4.31-uc0/net/x25/x25_facilities.c b/uClinux-2.4.31-uc0/net/x25/x25_facilities.c
new file mode 100644
index 0000000..bdd0fd0
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/x25/x25_facilities.c
@@ -0,0 +1,232 @@
+/*
+ * X.25 Packet Layer release 002
+ *
+ * This is ALPHA test software. This code may break your machine, randomly fail to work with new
+ * releases, misbehave and/or generally screw up. It might even work.
+ *
+ * This code REQUIRES 2.1.15 or higher
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * X.25 001 Split from x25_subr.c
+ * mar/20/00 Daniela Squassoni Disabling/enabling of facilities
+ * negotiation.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/x25.h>
+
+/*
+ * Parse a set of facilities into the facilities structure. Unrecognised
+ * facilities are written to the debug log file.
+ */
+int x25_parse_facilities(struct sk_buff *skb, struct x25_facilities *facilities, unsigned long *vc_fac_mask)
+{
+ unsigned int len;
+ unsigned char *p = skb->data;
+
+ len = *p++;
+ *vc_fac_mask = 0;
+
+ while (len > 0) {
+ switch (*p & X25_FAC_CLASS_MASK) {
+ case X25_FAC_CLASS_A:
+ switch (*p) {
+ case X25_FAC_REVERSE:
+ facilities->reverse = (p[1] & 0x01);
+ *vc_fac_mask |= X25_MASK_REVERSE;
+ break;
+ case X25_FAC_THROUGHPUT:
+ facilities->throughput = p[1];
+ *vc_fac_mask |= X25_MASK_THROUGHPUT;
+ break;
+ default:
+ printk(KERN_DEBUG "X.25: unknown facility %02X, value %02X\n", p[0], p[1]);
+ break;
+ }
+ p += 2;
+ len -= 2;
+ break;
+
+ case X25_FAC_CLASS_B:
+ switch (*p) {
+ case X25_FAC_PACKET_SIZE:
+ facilities->pacsize_in = p[1];
+ facilities->pacsize_out = p[2];
+ *vc_fac_mask |= X25_MASK_PACKET_SIZE;
+ break;
+ case X25_FAC_WINDOW_SIZE:
+ facilities->winsize_in = p[1];
+ facilities->winsize_out = p[2];
+ *vc_fac_mask |= X25_MASK_WINDOW_SIZE;
+ break;
+ default:
+ printk(KERN_DEBUG "X.25: unknown facility %02X, values %02X, %02X\n", p[0], p[1], p[2]);
+ break;
+ }
+ p += 3;
+ len -= 3;
+ break;
+
+ case X25_FAC_CLASS_C:
+ printk(KERN_DEBUG "X.25: unknown facility %02X, values %02X, %02X, %02X\n", p[0], p[1], p[2], p[3]);
+ p += 4;
+ len -= 4;
+ break;
+
+ case X25_FAC_CLASS_D:
+ printk(KERN_DEBUG "X.25: unknown facility %02X, length %d, values %02X, %02X, %02X, %02X\n", p[0], p[1], p[2], p[3], p[4], p[5]);
+ len -= p[1] + 2;
+ p += p[1] + 2;
+ break;
+ }
+ }
+
+ return p - skb->data;
+}
+
+/*
+ * Create a set of facilities.
+ */
+int x25_create_facilities(unsigned char *buffer, struct x25_facilities *facilities, unsigned long facil_mask)
+{
+ unsigned char *p = buffer + 1;
+ int len;
+
+ if (facil_mask == 0) {
+ buffer [0] = 0; /* length of the facilities field in call_req or call_accept packets */
+ len = 1; /* 1 byte for the length field */
+ return len;
+ }
+
+ if ((facilities->reverse != 0) && (facil_mask & X25_MASK_REVERSE)) {
+ *p++ = X25_FAC_REVERSE;
+ *p++ = (facilities->reverse) ? 0x01 : 0x00;
+ }
+
+ if ((facilities->throughput != 0) && (facil_mask & X25_MASK_THROUGHPUT)) {
+ *p++ = X25_FAC_THROUGHPUT;
+ *p++ = facilities->throughput;
+ }
+
+ if ((facilities->pacsize_in != 0 || facilities->pacsize_out != 0) && (facil_mask & X25_MASK_PACKET_SIZE)) {
+ *p++ = X25_FAC_PACKET_SIZE;
+ *p++ = (facilities->pacsize_in == 0) ? facilities->pacsize_out : facilities->pacsize_in;
+ *p++ = (facilities->pacsize_out == 0) ? facilities->pacsize_in : facilities->pacsize_out;
+ }
+
+ if ((facilities->winsize_in != 0 || facilities->winsize_out != 0) && (facil_mask & X25_MASK_WINDOW_SIZE)) {
+ *p++ = X25_FAC_WINDOW_SIZE;
+ *p++ = (facilities->winsize_in == 0) ? facilities->winsize_out : facilities->winsize_in;
+ *p++ = (facilities->winsize_out == 0) ? facilities->winsize_in : facilities->winsize_out;
+ }
+
+ len = p - buffer;
+ buffer[0] = len - 1;
+
+ return len;
+}
+
+/*
+ * Try to reach a compromise on a set of facilities.
+ *
+ * The only real problem is with reverse charging.
+ */
+int x25_negotiate_facilities(struct sk_buff *skb, struct sock *sk, struct x25_facilities *new)
+{
+ struct x25_facilities *ours;
+ struct x25_facilities theirs;
+ int len;
+
+ memset(&theirs, 0x00, sizeof(struct x25_facilities));
+
+ ours = &sk->protinfo.x25->facilities;
+
+ *new = *ours;
+
+ len = x25_parse_facilities(skb, &theirs, &sk->protinfo.x25->vc_facil_mask);
+
+ /*
+ * They want reverse charging, we won't accept it.
+ */
+ if (theirs.reverse != 0 && ours->reverse == 0) {
+ SOCK_DEBUG(sk, "X.25: rejecting reverse charging request");
+ return -1;
+ }
+
+ new->reverse = theirs.reverse;
+
+ if (theirs.throughput != 0) {
+ if (theirs.throughput < ours->throughput) {
+ SOCK_DEBUG(sk, "X.25: throughput negotiated down");
+ new->throughput = theirs.throughput;
+ }
+ }
+
+ if (theirs.pacsize_in != 0 && theirs.pacsize_out != 0) {
+ if (theirs.pacsize_in < ours->pacsize_in) {
+ SOCK_DEBUG(sk, "X.25: packet size inwards negotiated down");
+ new->pacsize_in = theirs.pacsize_in;
+ }
+ if (theirs.pacsize_out < ours->pacsize_out) {
+ SOCK_DEBUG(sk, "X.25: packet size outwards negotiated down");
+ new->pacsize_out = theirs.pacsize_out;
+ }
+ }
+
+ if (theirs.winsize_in != 0 && theirs.winsize_out != 0) {
+ if (theirs.winsize_in < ours->winsize_in) {
+ SOCK_DEBUG(sk, "X.25: window size inwards negotiated down");
+ new->winsize_in = theirs.winsize_in;
+ }
+ if (theirs.winsize_out < ours->winsize_out) {
+ SOCK_DEBUG(sk, "X.25: window size outwards negotiated down");
+ new->winsize_out = theirs.winsize_out;
+ }
+ }
+
+ return len;
+}
+
+/*
+ * Limit values of certain facilities according to the capability of the
+ * currently attached x25 link.
+ */
+void x25_limit_facilities(struct x25_facilities *facilities,
+ struct x25_neigh *neighbour)
+{
+
+ if( ! neighbour->extended ){
+ if( facilities->winsize_in > 7 ){
+ printk(KERN_DEBUG "X.25: incoming winsize limited to 7\n");
+ facilities->winsize_in = 7;
+ }
+ if( facilities->winsize_out > 7 ){
+ facilities->winsize_out = 7;
+ printk( KERN_DEBUG "X.25: outgoing winsize limited to 7\n");
+ }
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/x25/x25_in.c b/uClinux-2.4.31-uc0/net/x25/x25_in.c
new file mode 100644
index 0000000..73a567e
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/x25/x25_in.c
@@ -0,0 +1,369 @@
+/*
+ * X.25 Packet Layer release 002
+ *
+ * This is ALPHA test software. This code may break your machine, randomly fail to work with new
+ * releases, misbehave and/or generally screw up. It might even work.
+ *
+ * This code REQUIRES 2.1.15 or higher
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * X.25 001 Jonathan Naylor Started coding.
+ * X.25 002 Jonathan Naylor Centralised disconnection code.
+ * New timer architecture.
+ * 2000-03-20 Daniela Squassoni Disabling/enabling of facilities
+ * negotiation.
+ * 2000-11-10 Henner Eisen Check and reset for out-of-sequence
+ * i-frames.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <net/ip.h> /* For ip_rcv */
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/x25.h>
+
+static int x25_queue_rx_frame(struct sock *sk, struct sk_buff *skb, int more)
+{
+ struct sk_buff *skbo, *skbn = skb;
+
+ if (more) {
+ sk->protinfo.x25->fraglen += skb->len;
+ skb_queue_tail(&sk->protinfo.x25->fragment_queue, skb);
+ skb_set_owner_r(skb, sk);
+ return 0;
+ }
+
+ if (!more && sk->protinfo.x25->fraglen > 0) { /* End of fragment */
+ int len = sk->protinfo.x25->fraglen + skb->len;
+
+ if ((skbn = alloc_skb(len, GFP_ATOMIC)) == NULL){
+ kfree_skb(skb);
+ return 1;
+ }
+
+ skb_queue_tail(&sk->protinfo.x25->fragment_queue, skb);
+
+ skbn->h.raw = skbn->data;
+
+ skbo = skb_dequeue(&sk->protinfo.x25->fragment_queue);
+ memcpy(skb_put(skbn, skbo->len), skbo->data, skbo->len);
+ kfree_skb(skbo);
+
+ while ((skbo = skb_dequeue(&sk->protinfo.x25->fragment_queue)) != NULL) {
+ skb_pull(skbo, (sk->protinfo.x25->neighbour->extended) ? X25_EXT_MIN_LEN : X25_STD_MIN_LEN);
+ memcpy(skb_put(skbn, skbo->len), skbo->data, skbo->len);
+ kfree_skb(skbo);
+ }
+
+ sk->protinfo.x25->fraglen = 0;
+ }
+
+ skb_set_owner_r(skbn, sk);
+ skb_queue_tail(&sk->receive_queue, skbn);
+ if (!sk->dead)
+ sk->data_ready(sk,skbn->len);
+
+ return 0;
+}
+
+/*
+ * State machine for state 1, Awaiting Call Accepted State.
+ * The handling of the timer(s) is in file x25_timer.c.
+ * Handling of state 0 and connection release is in af_x25.c.
+ */
+static int x25_state1_machine(struct sock *sk, struct sk_buff *skb, int frametype)
+{
+ x25_address source_addr, dest_addr;
+
+ switch (frametype) {
+
+ case X25_CALL_ACCEPTED:
+ x25_stop_timer(sk);
+ sk->protinfo.x25->condition = 0x00;
+ sk->protinfo.x25->vs = 0;
+ sk->protinfo.x25->va = 0;
+ sk->protinfo.x25->vr = 0;
+ sk->protinfo.x25->vl = 0;
+ sk->protinfo.x25->state = X25_STATE_3;
+ sk->state = TCP_ESTABLISHED;
+ /*
+ * Parse the data in the frame.
+ */
+ skb_pull(skb, X25_STD_MIN_LEN);
+ skb_pull(skb, x25_addr_ntoa(skb->data, &source_addr, &dest_addr));
+ skb_pull(skb, x25_parse_facilities(skb, &sk->protinfo.x25->facilities, &sk->protinfo.x25->vc_facil_mask));
+ /*
+ * Copy any Call User Data.
+ */
+ if (skb->len >= 0) {
+ memcpy(sk->protinfo.x25->calluserdata.cuddata, skb->data, skb->len);
+ sk->protinfo.x25->calluserdata.cudlength = skb->len;
+ }
+ if (!sk->dead)
+ sk->state_change(sk);
+ break;
+
+ case X25_CLEAR_REQUEST:
+ x25_write_internal(sk, X25_CLEAR_CONFIRMATION);
+ x25_disconnect(sk, ECONNREFUSED, skb->data[3], skb->data[4]);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * State machine for state 2, Awaiting Clear Confirmation State.
+ * The handling of the timer(s) is in file x25_timer.c
+ * Handling of state 0 and connection release is in af_x25.c.
+ */
+static int x25_state2_machine(struct sock *sk, struct sk_buff *skb, int frametype)
+{
+ switch (frametype) {
+
+ case X25_CLEAR_REQUEST:
+ x25_write_internal(sk, X25_CLEAR_CONFIRMATION);
+ x25_disconnect(sk, 0, skb->data[3], skb->data[4]);
+ break;
+
+ case X25_CLEAR_CONFIRMATION:
+ x25_disconnect(sk, 0, 0, 0);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * State machine for state 3, Connected State.
+ * The handling of the timer(s) is in file x25_timer.c
+ * Handling of state 0 and connection release is in af_x25.c.
+ */
+static int x25_state3_machine(struct sock *sk, struct sk_buff *skb, int frametype, int ns, int nr, int q, int d, int m)
+{
+ int queued = 0;
+ int modulus;
+
+ modulus = (sk->protinfo.x25->neighbour->extended) ? X25_EMODULUS : X25_SMODULUS;
+
+ switch (frametype) {
+
+ case X25_RESET_REQUEST:
+ x25_write_internal(sk, X25_RESET_CONFIRMATION);
+ x25_stop_timer(sk);
+ sk->protinfo.x25->condition = 0x00;
+ sk->protinfo.x25->vs = 0;
+ sk->protinfo.x25->vr = 0;
+ sk->protinfo.x25->va = 0;
+ sk->protinfo.x25->vl = 0;
+ x25_requeue_frames(sk);
+ break;
+
+ case X25_CLEAR_REQUEST:
+ x25_write_internal(sk, X25_CLEAR_CONFIRMATION);
+ x25_disconnect(sk, 0, skb->data[3], skb->data[4]);
+ break;
+
+ case X25_RR:
+ case X25_RNR:
+ if (!x25_validate_nr(sk, nr)) {
+ x25_clear_queues(sk);
+ x25_write_internal(sk, X25_RESET_REQUEST);
+ x25_start_t22timer(sk);
+ sk->protinfo.x25->condition = 0x00;
+ sk->protinfo.x25->vs = 0;
+ sk->protinfo.x25->vr = 0;
+ sk->protinfo.x25->va = 0;
+ sk->protinfo.x25->vl = 0;
+ sk->protinfo.x25->state = X25_STATE_4;
+ } else {
+ x25_frames_acked(sk, nr);
+ if (frametype == X25_RNR) {
+ sk->protinfo.x25->condition |= X25_COND_PEER_RX_BUSY;
+ } else {
+ sk->protinfo.x25->condition &= ~X25_COND_PEER_RX_BUSY;
+ }
+ }
+ break;
+
+ case X25_DATA: /* XXX */
+ sk->protinfo.x25->condition &= ~X25_COND_PEER_RX_BUSY;
+ if ((ns!=sk->protinfo.x25->vr) ||
+ !x25_validate_nr(sk, nr)) {
+ x25_clear_queues(sk);
+ x25_write_internal(sk, X25_RESET_REQUEST);
+ x25_start_t22timer(sk);
+ sk->protinfo.x25->condition = 0x00;
+ sk->protinfo.x25->vs = 0;
+ sk->protinfo.x25->vr = 0;
+ sk->protinfo.x25->va = 0;
+ sk->protinfo.x25->vl = 0;
+ sk->protinfo.x25->state = X25_STATE_4;
+ break;
+ }
+ x25_frames_acked(sk, nr);
+ if (ns == sk->protinfo.x25->vr) {
+ if (x25_queue_rx_frame(sk, skb, m) == 0) {
+ sk->protinfo.x25->vr = (sk->protinfo.x25->vr + 1) % modulus;
+ queued = 1;
+ } else {
+ /* Should never happen */
+ x25_clear_queues(sk);
+ x25_write_internal(sk, X25_RESET_REQUEST);
+ x25_start_t22timer(sk);
+ sk->protinfo.x25->condition = 0x00;
+ sk->protinfo.x25->vs = 0;
+ sk->protinfo.x25->vr = 0;
+ sk->protinfo.x25->va = 0;
+ sk->protinfo.x25->vl = 0;
+ sk->protinfo.x25->state = X25_STATE_4;
+ break;
+ }
+ if (atomic_read(&sk->rmem_alloc) > (sk->rcvbuf / 2))
+ sk->protinfo.x25->condition |= X25_COND_OWN_RX_BUSY;
+ }
+ /*
+ * If the window is full Ack it immediately, else
+ * start the holdback timer.
+ */
+ if (((sk->protinfo.x25->vl + sk->protinfo.x25->facilities.winsize_in) % modulus) == sk->protinfo.x25->vr) {
+ sk->protinfo.x25->condition &= ~X25_COND_ACK_PENDING;
+ x25_stop_timer(sk);
+ x25_enquiry_response(sk);
+ } else {
+ sk->protinfo.x25->condition |= X25_COND_ACK_PENDING;
+ x25_start_t2timer(sk);
+ }
+ break;
+
+ case X25_INTERRUPT_CONFIRMATION:
+ sk->protinfo.x25->intflag = 0;
+ break;
+
+ case X25_INTERRUPT:
+ if (sk->urginline) {
+ queued = (sock_queue_rcv_skb(sk, skb) == 0);
+ } else {
+ skb_set_owner_r(skb, sk);
+ skb_queue_tail(&sk->protinfo.x25->interrupt_in_queue, skb);
+ queued = 1;
+ }
+ if (sk->proc != 0) {
+ if (sk->proc > 0)
+ kill_proc(sk->proc, SIGURG, 1);
+ else
+ kill_pg(-sk->proc, SIGURG, 1);
+ sock_wake_async(sk->socket, 3, POLL_PRI);
+ }
+ x25_write_internal(sk, X25_INTERRUPT_CONFIRMATION);
+ break;
+
+ default:
+ printk(KERN_WARNING "x25: unknown %02X in state 3\n", frametype);
+ break;
+ }
+
+ return queued;
+}
+
+/*
+ * State machine for state 4, Awaiting Reset Confirmation State.
+ * The handling of the timer(s) is in file x25_timer.c
+ * Handling of state 0 and connection release is in af_x25.c.
+ */
+static int x25_state4_machine(struct sock *sk, struct sk_buff *skb, int frametype)
+{
+ switch (frametype) {
+
+ case X25_RESET_REQUEST:
+ x25_write_internal(sk, X25_RESET_CONFIRMATION);
+ case X25_RESET_CONFIRMATION:
+ x25_stop_timer(sk);
+ sk->protinfo.x25->condition = 0x00;
+ sk->protinfo.x25->va = 0;
+ sk->protinfo.x25->vr = 0;
+ sk->protinfo.x25->vs = 0;
+ sk->protinfo.x25->vl = 0;
+ sk->protinfo.x25->state = X25_STATE_3;
+ x25_requeue_frames(sk);
+ break;
+
+ case X25_CLEAR_REQUEST:
+ x25_write_internal(sk, X25_CLEAR_CONFIRMATION);
+ x25_disconnect(sk, 0, skb->data[3], skb->data[4]);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+/* Higher level upcall for a LAPB frame */
+int x25_process_rx_frame(struct sock *sk, struct sk_buff *skb)
+{
+ int queued = 0, frametype, ns, nr, q, d, m;
+
+ if (sk->protinfo.x25->state == X25_STATE_0)
+ return 0;
+
+ frametype = x25_decode(sk, skb, &ns, &nr, &q, &d, &m);
+
+ switch (sk->protinfo.x25->state) {
+ case X25_STATE_1:
+ queued = x25_state1_machine(sk, skb, frametype);
+ break;
+ case X25_STATE_2:
+ queued = x25_state2_machine(sk, skb, frametype);
+ break;
+ case X25_STATE_3:
+ queued = x25_state3_machine(sk, skb, frametype, ns, nr, q, d, m);
+ break;
+ case X25_STATE_4:
+ queued = x25_state4_machine(sk, skb, frametype);
+ break;
+ }
+
+ x25_kick(sk);
+
+ return queued;
+}
+
+int x25_backlog_rcv(struct sock *sk, struct sk_buff *skb)
+{
+ int queued;
+
+ queued = x25_process_rx_frame(sk,skb);
+ if(!queued) kfree_skb(skb);
+
+ return 0;
+}
diff --git a/uClinux-2.4.31-uc0/net/x25/x25_link.c b/uClinux-2.4.31-uc0/net/x25/x25_link.c
new file mode 100644
index 0000000..3e89efa
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/x25/x25_link.c
@@ -0,0 +1,436 @@
+/*
+ * X.25 Packet Layer release 002
+ *
+ * This is ALPHA test software. This code may break your machine, randomly fail to work with new
+ * releases, misbehave and/or generally screw up. It might even work.
+ *
+ * This code REQUIRES 2.1.15 or higher
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * X.25 001 Jonathan Naylor Started coding.
+ * X.25 002 Jonathan Naylor New timer architecture.
+ * mar/20/00 Daniela Squassoni Disabling/enabling of facilities
+ * negotiation.
+ * 2000-09-04 Henner Eisen dev_hold() / dev_put() for x25_neigh.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/init.h>
+#include <net/x25.h>
+
+static struct x25_neigh *x25_neigh_list /* = NULL initially */;
+
+static void x25_t20timer_expiry(unsigned long);
+
+/*
+ * Linux set/reset timer routines
+ */
+static void x25_start_t20timer(struct x25_neigh *neigh)
+{
+ del_timer(&neigh->t20timer);
+
+ neigh->t20timer.data = (unsigned long)neigh;
+ neigh->t20timer.function = &x25_t20timer_expiry;
+ neigh->t20timer.expires = jiffies + neigh->t20;
+
+ add_timer(&neigh->t20timer);
+}
+
+static void x25_t20timer_expiry(unsigned long param)
+{
+ struct x25_neigh *neigh = (struct x25_neigh *)param;
+
+ x25_transmit_restart_request(neigh);
+
+ x25_start_t20timer(neigh);
+}
+
+static void x25_stop_t20timer(struct x25_neigh *neigh)
+{
+ del_timer(&neigh->t20timer);
+}
+
+static int x25_t20timer_pending(struct x25_neigh *neigh)
+{
+ return timer_pending(&neigh->t20timer);
+}
+
+/*
+ * This handles all restart and diagnostic frames.
+ */
+void x25_link_control(struct sk_buff *skb, struct x25_neigh *neigh, unsigned short frametype)
+{
+ struct sk_buff *skbn;
+ int confirm;
+
+ switch (frametype) {
+ case X25_RESTART_REQUEST:
+ confirm = !x25_t20timer_pending(neigh);
+ x25_stop_t20timer(neigh);
+ neigh->state = X25_LINK_STATE_3;
+ if (confirm) x25_transmit_restart_confirmation(neigh);
+ break;
+
+ case X25_RESTART_CONFIRMATION:
+ x25_stop_t20timer(neigh);
+ neigh->state = X25_LINK_STATE_3;
+ break;
+
+ case X25_DIAGNOSTIC:
+ printk(KERN_WARNING "x25: diagnostic #%d - %02X %02X %02X\n", skb->data[3], skb->data[4], skb->data[5], skb->data[6]);
+ break;
+
+ default:
+ printk(KERN_WARNING "x25: received unknown %02X with LCI 000\n", frametype);
+ break;
+ }
+
+ if (neigh->state == X25_LINK_STATE_3) {
+ while ((skbn = skb_dequeue(&neigh->queue)) != NULL)
+ x25_send_frame(skbn, neigh);
+ }
+}
+
+/*
+ * This routine is called when a Restart Request is needed
+ */
+void x25_transmit_restart_request(struct x25_neigh *neigh)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+ int len;
+
+ len = X25_MAX_L2_LEN + X25_STD_MIN_LEN + 2;
+
+ if ((skb = alloc_skb(len, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skb, X25_MAX_L2_LEN);
+
+ dptr = skb_put(skb, X25_STD_MIN_LEN + 2);
+
+ *dptr++ = (neigh->extended) ? X25_GFI_EXTSEQ : X25_GFI_STDSEQ;
+ *dptr++ = 0x00;
+ *dptr++ = X25_RESTART_REQUEST;
+ *dptr++ = 0x00;
+ *dptr++ = 0;
+
+ skb->sk = NULL;
+
+ x25_send_frame(skb, neigh);
+}
+
+/*
+ * This routine is called when a Restart Confirmation is needed
+ */
+void x25_transmit_restart_confirmation(struct x25_neigh *neigh)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+ int len;
+
+ len = X25_MAX_L2_LEN + X25_STD_MIN_LEN;
+
+ if ((skb = alloc_skb(len, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skb, X25_MAX_L2_LEN);
+
+ dptr = skb_put(skb, X25_STD_MIN_LEN);
+
+ *dptr++ = (neigh->extended) ? X25_GFI_EXTSEQ : X25_GFI_STDSEQ;
+ *dptr++ = 0x00;
+ *dptr++ = X25_RESTART_CONFIRMATION;
+
+ skb->sk = NULL;
+
+ x25_send_frame(skb, neigh);
+}
+
+/*
+ * This routine is called when a Diagnostic is required.
+ */
+void x25_transmit_diagnostic(struct x25_neigh *neigh, unsigned char diag)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+ int len;
+
+ len = X25_MAX_L2_LEN + X25_STD_MIN_LEN + 1;
+
+ if ((skb = alloc_skb(len, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skb, X25_MAX_L2_LEN);
+
+ dptr = skb_put(skb, X25_STD_MIN_LEN + 1);
+
+ *dptr++ = (neigh->extended) ? X25_GFI_EXTSEQ : X25_GFI_STDSEQ;
+ *dptr++ = 0x00;
+ *dptr++ = X25_DIAGNOSTIC;
+ *dptr++ = diag;
+
+ skb->sk = NULL;
+
+ x25_send_frame(skb, neigh);
+}
+
+/*
+ * This routine is called when a Clear Request is needed outside of the context
+ * of a connected socket.
+ */
+void x25_transmit_clear_request(struct x25_neigh *neigh, unsigned int lci, unsigned char cause)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+ int len;
+
+ len = X25_MAX_L2_LEN + X25_STD_MIN_LEN + 2;
+
+ if ((skb = alloc_skb(len, GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_reserve(skb, X25_MAX_L2_LEN);
+
+ dptr = skb_put(skb, X25_STD_MIN_LEN + 2);
+
+ *dptr++ = ((lci >> 8) & 0x0F) | (neigh->extended ? X25_GFI_EXTSEQ : X25_GFI_STDSEQ);
+ *dptr++ = ((lci >> 0) & 0xFF);
+ *dptr++ = X25_CLEAR_REQUEST;
+ *dptr++ = cause;
+ *dptr++ = 0x00;
+
+ skb->sk = NULL;
+
+ x25_send_frame(skb, neigh);
+}
+
+void x25_transmit_link(struct sk_buff *skb, struct x25_neigh *neigh)
+{
+ switch (neigh->state) {
+ case X25_LINK_STATE_0:
+ skb_queue_tail(&neigh->queue, skb);
+ neigh->state = X25_LINK_STATE_1;
+ x25_establish_link(neigh);
+ break;
+ case X25_LINK_STATE_1:
+ case X25_LINK_STATE_2:
+ skb_queue_tail(&neigh->queue, skb);
+ break;
+ case X25_LINK_STATE_3:
+ x25_send_frame(skb, neigh);
+ break;
+ }
+}
+
+/*
+ * Called when the link layer has become established.
+ */
+void x25_link_established(struct x25_neigh *neigh)
+{
+ switch (neigh->state) {
+ case X25_LINK_STATE_0:
+ neigh->state = X25_LINK_STATE_2;
+ break;
+ case X25_LINK_STATE_1:
+ x25_transmit_restart_request(neigh);
+ neigh->state = X25_LINK_STATE_2;
+ x25_start_t20timer(neigh);
+ break;
+ }
+}
+
+/*
+ * Called when the link layer has terminated, or an establishment
+ * request has failed.
+ */
+
+void x25_link_terminated(struct x25_neigh *neigh)
+{
+ neigh->state = X25_LINK_STATE_0;
+ /* Out of order: clear existing virtual calls (X.25 03/93 4.6.3) */
+ x25_kill_by_neigh(neigh);
+}
+
+/*
+ * Add a new device.
+ */
+void x25_link_device_up(struct net_device *dev)
+{
+ struct x25_neigh *x25_neigh;
+ unsigned long flags;
+
+ if ((x25_neigh = kmalloc(sizeof(*x25_neigh), GFP_ATOMIC)) == NULL)
+ return;
+
+ skb_queue_head_init(&x25_neigh->queue);
+
+ init_timer(&x25_neigh->t20timer);
+
+ dev_hold(dev);
+ x25_neigh->dev = dev;
+ x25_neigh->state = X25_LINK_STATE_0;
+ x25_neigh->extended = 0;
+ x25_neigh->global_facil_mask = (X25_MASK_REVERSE | X25_MASK_THROUGHPUT | X25_MASK_PACKET_SIZE | X25_MASK_WINDOW_SIZE); /* enables negotiation */
+ x25_neigh->t20 = sysctl_x25_restart_request_timeout;
+
+ save_flags(flags); cli();
+ x25_neigh->next = x25_neigh_list;
+ x25_neigh_list = x25_neigh;
+ restore_flags(flags);
+}
+
+static void x25_remove_neigh(struct x25_neigh *x25_neigh)
+{
+ struct x25_neigh *s;
+ unsigned long flags;
+
+ skb_queue_purge(&x25_neigh->queue);
+
+ x25_stop_t20timer(x25_neigh);
+
+ save_flags(flags); cli();
+
+ if ((s = x25_neigh_list) == x25_neigh) {
+ x25_neigh_list = x25_neigh->next;
+ restore_flags(flags);
+ kfree(x25_neigh);
+ return;
+ }
+
+ while (s != NULL && s->next != NULL) {
+ if (s->next == x25_neigh) {
+ s->next = x25_neigh->next;
+ restore_flags(flags);
+ kfree(x25_neigh);
+ return;
+ }
+
+ s = s->next;
+ }
+
+ restore_flags(flags);
+}
+
+/*
+ * A device has been removed, remove its links.
+ */
+void x25_link_device_down(struct net_device *dev)
+{
+ struct x25_neigh *neigh, *x25_neigh = x25_neigh_list;
+
+ while (x25_neigh != NULL) {
+ neigh = x25_neigh;
+ x25_neigh = x25_neigh->next;
+
+ if (neigh->dev == dev){
+ x25_remove_neigh(neigh);
+ dev_put(dev);
+ }
+ }
+}
+
+/*
+ * Given a device, return the neighbour address.
+ */
+struct x25_neigh *x25_get_neigh(struct net_device *dev)
+{
+ struct x25_neigh *x25_neigh;
+
+ for (x25_neigh = x25_neigh_list; x25_neigh != NULL; x25_neigh = x25_neigh->next)
+ if (x25_neigh->dev == dev)
+ return x25_neigh;
+
+ return NULL;
+}
+
+/*
+ * Handle the ioctls that control the subscription functions.
+ */
+int x25_subscr_ioctl(unsigned int cmd, void *arg)
+{
+ struct x25_subscrip_struct x25_subscr;
+ struct x25_neigh *x25_neigh;
+ struct net_device *dev;
+
+ switch (cmd) {
+
+ case SIOCX25GSUBSCRIP:
+ if (copy_from_user(&x25_subscr, arg, sizeof(struct x25_subscrip_struct)))
+ return -EFAULT;
+ if ((dev = x25_dev_get(x25_subscr.device)) == NULL)
+ return -EINVAL;
+ if ((x25_neigh = x25_get_neigh(dev)) == NULL) {
+ dev_put(dev);
+ return -EINVAL;
+ }
+ dev_put(dev);
+ x25_subscr.extended = x25_neigh->extended;
+ x25_subscr.global_facil_mask = x25_neigh->global_facil_mask;
+ if (copy_to_user(arg, &x25_subscr, sizeof(struct x25_subscrip_struct)))
+ return -EFAULT;
+ break;
+
+ case SIOCX25SSUBSCRIP:
+ if (copy_from_user(&x25_subscr, arg, sizeof(struct x25_subscrip_struct)))
+ return -EFAULT;
+ if ((dev = x25_dev_get(x25_subscr.device)) == NULL)
+ return -EINVAL;
+ if ((x25_neigh = x25_get_neigh(dev)) == NULL) {
+ dev_put(dev);
+ return -EINVAL;
+ }
+ dev_put(dev);
+ if (x25_subscr.extended != 0 && x25_subscr.extended != 1)
+ return -EINVAL;
+ x25_neigh->extended = x25_subscr.extended;
+ x25_neigh->global_facil_mask = x25_subscr.global_facil_mask;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Release all memory associated with X.25 neighbour structures.
+ */
+void __exit x25_link_free(void)
+{
+ struct x25_neigh *neigh, *x25_neigh = x25_neigh_list;
+
+ while (x25_neigh != NULL) {
+ neigh = x25_neigh;
+ x25_neigh = x25_neigh->next;
+
+ x25_remove_neigh(neigh);
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/x25/x25_out.c b/uClinux-2.4.31-uc0/net/x25/x25_out.c
new file mode 100644
index 0000000..fdf5554
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/x25/x25_out.c
@@ -0,0 +1,229 @@
+/*
+ * X.25 Packet Layer release 002
+ *
+ * This is ALPHA test software. This code may break your machine, randomly fail to work with new
+ * releases, misbehave and/or generally screw up. It might even work.
+ *
+ * This code REQUIRES 2.1.15 or higher
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * X.25 001 Jonathan Naylor Started coding.
+ * X.25 002 Jonathan Naylor New timer architecture.
+ * 2000-09-04 Henner Eisen Prevented x25_output() skb leakage.
+ * 2000-10-27 Henner Eisen MSG_DONTWAIT for fragment allocation.
+ * 2000-11-10 Henner Eisen x25_send_iframe(): re-queued frames
+ * needed cleaned seq-number fields.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/x25.h>
+
+static int x25_pacsize_to_bytes(unsigned int pacsize)
+{
+ int bytes = 1;
+
+ if (pacsize == 0)
+ return 128;
+
+ while (pacsize-- > 0)
+ bytes *= 2;
+
+ return bytes;
+}
+
+/*
+ * This is where all X.25 information frames pass.
+ *
+ * Returns the amount of user data bytes sent on success
+ * or a negative error code on failure.
+ */
+int x25_output(struct sock *sk, struct sk_buff *skb)
+{
+ struct sk_buff *skbn;
+ unsigned char header[X25_EXT_MIN_LEN];
+ int err, frontlen, len, header_len, max_len;
+ int sent=0, noblock = X25_SKB_CB(skb)->flags & MSG_DONTWAIT;
+
+ header_len = (sk->protinfo.x25->neighbour->extended) ? X25_EXT_MIN_LEN : X25_STD_MIN_LEN;
+ max_len = x25_pacsize_to_bytes(sk->protinfo.x25->facilities.pacsize_out);
+
+ if (skb->len - header_len > max_len) {
+ /* Save a copy of the Header */
+ memcpy(header, skb->data, header_len);
+ skb_pull(skb, header_len);
+
+ frontlen = skb_headroom(skb);
+
+ while (skb->len > 0) {
+ if ((skbn = sock_alloc_send_skb(sk, frontlen + max_len, noblock, &err)) == NULL){
+ if(err == -EWOULDBLOCK && noblock){
+ kfree_skb(skb);
+ return sent;
+ }
+ SOCK_DEBUG(sk, "x25_output: fragment allocation failed, err=%d, %d bytes sent\n", err, sent);
+ return err;
+ }
+
+ skb_reserve(skbn, frontlen);
+
+ len = (max_len > skb->len) ? skb->len : max_len;
+
+ /* Copy the user data */
+ memcpy(skb_put(skbn, len), skb->data, len);
+ skb_pull(skb, len);
+
+ /* Duplicate the Header */
+ skb_push(skbn, header_len);
+ memcpy(skbn->data, header, header_len);
+
+ if (skb->len > 0) {
+ if (sk->protinfo.x25->neighbour->extended)
+ skbn->data[3] |= X25_EXT_M_BIT;
+ else
+ skbn->data[2] |= X25_STD_M_BIT;
+ }
+
+ skb_queue_tail(&sk->write_queue, skbn);
+ sent += len;
+ }
+
+ kfree_skb(skb);
+ } else {
+ skb_queue_tail(&sk->write_queue, skb);
+ sent = skb->len - header_len;
+ }
+ return sent;
+}
+
+/*
+ * This procedure is passed a buffer descriptor for an iframe. It builds
+ * the rest of the control part of the frame and then writes it out.
+ */
+static void x25_send_iframe(struct sock *sk, struct sk_buff *skb)
+{
+ if (skb == NULL)
+ return;
+
+ if (sk->protinfo.x25->neighbour->extended) {
+ skb->data[2] = (sk->protinfo.x25->vs << 1) & 0xFE;
+ skb->data[3] &= X25_EXT_M_BIT;
+ skb->data[3] |= (sk->protinfo.x25->vr << 1) & 0xFE;
+ } else {
+ skb->data[2] &= X25_STD_M_BIT;
+ skb->data[2] |= (sk->protinfo.x25->vs << 1) & 0x0E;
+ skb->data[2] |= (sk->protinfo.x25->vr << 5) & 0xE0;
+ }
+
+ x25_transmit_link(skb, sk->protinfo.x25->neighbour);
+}
+
+void x25_kick(struct sock *sk)
+{
+ struct sk_buff *skb, *skbn;
+ unsigned short start, end;
+ int modulus;
+
+ if (sk->protinfo.x25->state != X25_STATE_3)
+ return;
+
+ /*
+ * Transmit interrupt data.
+ */
+ if (!sk->protinfo.x25->intflag && skb_peek(&sk->protinfo.x25->interrupt_out_queue) != NULL) {
+ sk->protinfo.x25->intflag = 1;
+ skb = skb_dequeue(&sk->protinfo.x25->interrupt_out_queue);
+ x25_transmit_link(skb, sk->protinfo.x25->neighbour);
+ }
+
+ if (sk->protinfo.x25->condition & X25_COND_PEER_RX_BUSY)
+ return;
+
+ if (skb_peek(&sk->write_queue) == NULL)
+ return;
+
+ modulus = (sk->protinfo.x25->neighbour->extended) ? X25_EMODULUS : X25_SMODULUS;
+
+ start = (skb_peek(&sk->protinfo.x25->ack_queue) == NULL) ? sk->protinfo.x25->va : sk->protinfo.x25->vs;
+ end = (sk->protinfo.x25->va + sk->protinfo.x25->facilities.winsize_out) % modulus;
+
+ if (start == end)
+ return;
+
+ sk->protinfo.x25->vs = start;
+
+ /*
+ * Transmit data until either we're out of data to send or
+ * the window is full.
+ */
+
+ skb = skb_dequeue(&sk->write_queue);
+
+ do {
+ if ((skbn = skb_clone(skb, GFP_ATOMIC)) == NULL) {
+ skb_queue_head(&sk->write_queue, skb);
+ break;
+ }
+
+ skb_set_owner_w(skbn, sk);
+
+ /*
+ * Transmit the frame copy.
+ */
+ x25_send_iframe(sk, skbn);
+
+ sk->protinfo.x25->vs = (sk->protinfo.x25->vs + 1) % modulus;
+
+ /*
+ * Requeue the original data frame.
+ */
+ skb_queue_tail(&sk->protinfo.x25->ack_queue, skb);
+
+ } while (sk->protinfo.x25->vs != end && (skb = skb_dequeue(&sk->write_queue)) != NULL);
+
+ sk->protinfo.x25->vl = sk->protinfo.x25->vr;
+ sk->protinfo.x25->condition &= ~X25_COND_ACK_PENDING;
+
+ x25_stop_timer(sk);
+}
+
+/*
+ * The following routines are taken from page 170 of the 7th ARRL Computer
+ * Networking Conference paper, as is the whole state machine.
+ */
+
+void x25_enquiry_response(struct sock *sk)
+{
+ if (sk->protinfo.x25->condition & X25_COND_OWN_RX_BUSY)
+ x25_write_internal(sk, X25_RNR);
+ else
+ x25_write_internal(sk, X25_RR);
+
+ sk->protinfo.x25->vl = sk->protinfo.x25->vr;
+ sk->protinfo.x25->condition &= ~X25_COND_ACK_PENDING;
+
+ x25_stop_timer(sk);
+}
diff --git a/uClinux-2.4.31-uc0/net/x25/x25_route.c b/uClinux-2.4.31-uc0/net/x25/x25_route.c
new file mode 100644
index 0000000..a465169
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/x25/x25_route.c
@@ -0,0 +1,271 @@
+/*
+ * X.25 Packet Layer release 002
+ *
+ * This is ALPHA test software. This code may break your machine, randomly fail to work with new
+ * releases, misbehave and/or generally screw up. It might even work.
+ *
+ * This code REQUIRES 2.1.15 or higher
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * X.25 001 Jonathan Naylor Started coding.
+ */
+
+#include <linux/config.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <net/arp.h>
+#include <linux/if_arp.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <linux/fcntl.h>
+#include <linux/termios.h> /* For TIOCINQ/OUTQ */
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/init.h>
+#include <net/x25.h>
+
+static struct x25_route *x25_route_list /* = NULL initially */;
+
+/*
+ * Add a new route.
+ */
+static int x25_add_route(x25_address *address, unsigned int sigdigits, struct net_device *dev)
+{
+ struct x25_route *x25_route;
+ unsigned long flags;
+
+ for (x25_route = x25_route_list; x25_route != NULL; x25_route = x25_route->next)
+ if (memcmp(&x25_route->address, address, sigdigits) == 0 && x25_route->sigdigits == sigdigits)
+ return -EINVAL;
+
+ if ((x25_route = kmalloc(sizeof(*x25_route), GFP_ATOMIC)) == NULL)
+ return -ENOMEM;
+
+ strcpy(x25_route->address.x25_addr, "000000000000000");
+ memcpy(x25_route->address.x25_addr, address->x25_addr, sigdigits);
+
+ x25_route->sigdigits = sigdigits;
+ x25_route->dev = dev;
+
+ save_flags(flags); cli();
+ x25_route->next = x25_route_list;
+ x25_route_list = x25_route;
+ restore_flags(flags);
+
+ return 0;
+}
+
+static void x25_remove_route(struct x25_route *x25_route)
+{
+ struct x25_route *s;
+ unsigned long flags;
+
+ save_flags(flags);
+ cli();
+
+ if ((s = x25_route_list) == x25_route) {
+ x25_route_list = x25_route->next;
+ restore_flags(flags);
+ kfree(x25_route);
+ return;
+ }
+
+ while (s != NULL && s->next != NULL) {
+ if (s->next == x25_route) {
+ s->next = x25_route->next;
+ restore_flags(flags);
+ kfree(x25_route);
+ return;
+ }
+
+ s = s->next;
+ }
+
+ restore_flags(flags);
+}
+
+static int x25_del_route(x25_address *address, unsigned int sigdigits, struct net_device *dev)
+{
+ struct x25_route *x25_route;
+
+ for (x25_route = x25_route_list; x25_route != NULL; x25_route = x25_route->next) {
+ if (memcmp(&x25_route->address, address, sigdigits) == 0 && x25_route->sigdigits == sigdigits && x25_route->dev == dev) {
+ x25_remove_route(x25_route);
+ return 0;
+ }
+ }
+
+ return -EINVAL;
+}
+
+/*
+ * A device has been removed, remove its routes.
+ */
+void x25_route_device_down(struct net_device *dev)
+{
+ struct x25_route *route, *x25_route = x25_route_list;
+
+ while (x25_route != NULL) {
+ route = x25_route;
+ x25_route = x25_route->next;
+
+ if (route->dev == dev)
+ x25_remove_route(route);
+ }
+}
+
+/*
+ * Check that the device given is a valid X.25 interface that is "up".
+ */
+struct net_device *x25_dev_get(char *devname)
+{
+ struct net_device *dev;
+
+ if ((dev = dev_get_by_name(devname)) == NULL)
+ return NULL;
+
+ if ((dev->flags & IFF_UP) && (dev->type == ARPHRD_X25
+#if defined(CONFIG_LLC) || defined(CONFIG_LLC_MODULE)
+ || dev->type == ARPHRD_ETHER
+#endif
+ ))
+ return dev;
+
+ dev_put(dev);
+
+ return NULL;
+}
+
+/*
+ * Find a device given an X.25 address.
+ */
+struct net_device *x25_get_route(x25_address *addr)
+{
+ struct x25_route *route, *use = NULL;
+
+ for (route = x25_route_list; route != NULL; route = route->next) {
+ if (memcmp(&route->address, addr, route->sigdigits) == 0) {
+ if (use == NULL) {
+ use = route;
+ } else {
+ if (route->sigdigits > use->sigdigits)
+ use = route;
+ }
+ }
+ }
+
+ return (use != NULL) ? use->dev : NULL;
+}
+
+/*
+ * Handle the ioctls that control the routing functions.
+ */
+int x25_route_ioctl(unsigned int cmd, void *arg)
+{
+ struct x25_route_struct x25_route;
+ struct net_device *dev;
+ int err;
+
+ switch (cmd) {
+
+ case SIOCADDRT:
+ if (copy_from_user(&x25_route, arg, sizeof(struct x25_route_struct)))
+ return -EFAULT;
+ if (x25_route.sigdigits < 0 || x25_route.sigdigits > 15)
+ return -EINVAL;
+ if ((dev = x25_dev_get(x25_route.device)) == NULL)
+ return -EINVAL;
+ err = x25_add_route(&x25_route.address, x25_route.sigdigits, dev);
+ dev_put(dev);
+ return err;
+
+ case SIOCDELRT:
+ if (copy_from_user(&x25_route, arg, sizeof(struct x25_route_struct)))
+ return -EFAULT;
+ if (x25_route.sigdigits < 0 || x25_route.sigdigits > 15)
+ return -EINVAL;
+ if ((dev = x25_dev_get(x25_route.device)) == NULL)
+ return -EINVAL;
+ err = x25_del_route(&x25_route.address, x25_route.sigdigits, dev);
+ dev_put(dev);
+ return err;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int x25_routes_get_info(char *buffer, char **start, off_t offset, int length)
+{
+ struct x25_route *x25_route;
+ int len = 0;
+ off_t pos = 0;
+ off_t begin = 0;
+
+ cli();
+
+ len += sprintf(buffer, "address digits device\n");
+
+ for (x25_route = x25_route_list; x25_route != NULL; x25_route = x25_route->next) {
+ len += sprintf(buffer + len, "%-15s %-6d %-5s\n",
+ x25_route->address.x25_addr,
+ x25_route->sigdigits,
+ (x25_route->dev != NULL) ? x25_route->dev->name : "???");
+
+ pos = begin + len;
+
+ if (pos < offset) {
+ len = 0;
+ begin = pos;
+ }
+
+ if (pos > offset + length)
+ break;
+ }
+
+ sti();
+
+ *start = buffer + (offset - begin);
+ len -= (offset - begin);
+
+ if (len > length) len = length;
+
+ return len;
+}
+
+/*
+ * Release all memory associated with X.25 routing structures.
+ */
+void __exit x25_route_free(void)
+{
+ struct x25_route *route, *x25_route = x25_route_list;
+
+ while (x25_route != NULL) {
+ route = x25_route;
+ x25_route = x25_route->next;
+
+ x25_remove_route(route);
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/x25/x25_subr.c b/uClinux-2.4.31-uc0/net/x25/x25_subr.c
new file mode 100644
index 0000000..a5804fa
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/x25/x25_subr.c
@@ -0,0 +1,356 @@
+/*
+ * X.25 Packet Layer release 002
+ *
+ * This is ALPHA test software. This code may break your machine, randomly fail to work with new
+ * releases, misbehave and/or generally screw up. It might even work.
+ *
+ * This code REQUIRES 2.1.15 or higher
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * X.25 001 Jonathan Naylor Started coding.
+ * X.25 002 Jonathan Naylor Centralised disconnection processing.
+ * mar/20/00 Daniela Squassoni Disabling/enabling of facilities
+ * negotiation.
+ * jun/24/01 Arnaldo C. Melo use skb_queue_purge, cleanups
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/x25.h>
+
+/*
+ * This routine purges all of the queues of frames.
+ */
+void x25_clear_queues(struct sock *sk)
+{
+ skb_queue_purge(&sk->write_queue);
+ skb_queue_purge(&sk->protinfo.x25->ack_queue);
+ skb_queue_purge(&sk->protinfo.x25->interrupt_in_queue);
+ skb_queue_purge(&sk->protinfo.x25->interrupt_out_queue);
+ skb_queue_purge(&sk->protinfo.x25->fragment_queue);
+}
+
+
+/*
+ * This routine purges the input queue of those frames that have been
+ * acknowledged. This replaces the boxes labelled "V(a) <- N(r)" on the
+ * SDL diagram.
+*/
+void x25_frames_acked(struct sock *sk, unsigned short nr)
+{
+ struct sk_buff *skb;
+ int modulus = sk->protinfo.x25->neighbour->extended ? X25_EMODULUS :
+ X25_SMODULUS;
+
+ /*
+ * Remove all the ack-ed frames from the ack queue.
+ */
+ if (sk->protinfo.x25->va != nr)
+ while (skb_peek(&sk->protinfo.x25->ack_queue) != NULL &&
+ sk->protinfo.x25->va != nr) {
+ skb = skb_dequeue(&sk->protinfo.x25->ack_queue);
+ kfree_skb(skb);
+ sk->protinfo.x25->va = (sk->protinfo.x25->va + 1) %
+ modulus;
+ }
+}
+
+void x25_requeue_frames(struct sock *sk)
+{
+ struct sk_buff *skb, *skb_prev = NULL;
+
+ /*
+ * Requeue all the un-ack-ed frames on the output queue to be picked
+ * up by x25_kick. This arrangement handles the possibility of an empty
+ * output queue.
+ */
+ while ((skb = skb_dequeue(&sk->protinfo.x25->ack_queue)) != NULL) {
+ if (skb_prev == NULL)
+ skb_queue_head(&sk->write_queue, skb);
+ else
+ skb_append(skb_prev, skb);
+ skb_prev = skb;
+ }
+}
+
+/*
+ * Validate that the value of nr is between va and vs. Return true or
+ * false for testing.
+ */
+int x25_validate_nr(struct sock *sk, unsigned short nr)
+{
+ unsigned short vc = sk->protinfo.x25->va;
+ int modulus = sk->protinfo.x25->neighbour->extended ? X25_EMODULUS :
+ X25_SMODULUS;
+
+ while (vc != sk->protinfo.x25->vs) {
+ if (nr == vc) return 1;
+ vc = (vc + 1) % modulus;
+ }
+
+ return nr == sk->protinfo.x25->vs ? 1 : 0;
+}
+
+/*
+ * This routine is called when the packet layer internally generates a
+ * control frame.
+ */
+void x25_write_internal(struct sock *sk, int frametype)
+{
+ struct sk_buff *skb;
+ unsigned char *dptr;
+ unsigned char facilities[X25_MAX_FAC_LEN];
+ unsigned char addresses[1 + X25_ADDR_LEN];
+ unsigned char lci1, lci2;
+ int len;
+
+ /*
+ * Default safe frame size.
+ */
+ len = X25_MAX_L2_LEN + X25_EXT_MIN_LEN;
+
+ /*
+ * Adjust frame size.
+ */
+ switch (frametype) {
+ case X25_CALL_REQUEST:
+ len += 1 + X25_ADDR_LEN + X25_MAX_FAC_LEN +
+ X25_MAX_CUD_LEN;
+ break;
+ case X25_CALL_ACCEPTED:
+ len += 1 + X25_MAX_FAC_LEN + X25_MAX_CUD_LEN;
+ break;
+ case X25_CLEAR_REQUEST:
+ case X25_RESET_REQUEST:
+ len += 2;
+ break;
+ case X25_RR:
+ case X25_RNR:
+ case X25_REJ:
+ case X25_CLEAR_CONFIRMATION:
+ case X25_INTERRUPT_CONFIRMATION:
+ case X25_RESET_CONFIRMATION:
+ break;
+ default:
+ printk(KERN_ERR "X.25: invalid frame type %02X\n",
+ frametype);
+ return;
+ }
+
+ if ((skb = alloc_skb(len, GFP_ATOMIC)) == NULL)
+ return;
+
+ /*
+ * Space for Ethernet and 802.2 LLC headers.
+ */
+ skb_reserve(skb, X25_MAX_L2_LEN);
+
+ /*
+ * Make space for the GFI and LCI, and fill them in.
+ */
+ dptr = skb_put(skb, 2);
+
+ lci1 = (sk->protinfo.x25->lci >> 8) & 0x0F;
+ lci2 = (sk->protinfo.x25->lci >> 0) & 0xFF;
+
+ if (sk->protinfo.x25->neighbour->extended) {
+ *dptr++ = lci1 | X25_GFI_EXTSEQ;
+ *dptr++ = lci2;
+ } else {
+ *dptr++ = lci1 | X25_GFI_STDSEQ;
+ *dptr++ = lci2;
+ }
+
+ /*
+ * Now fill in the frame type specific information.
+ */
+ switch (frametype) {
+
+ case X25_CALL_REQUEST:
+ dptr = skb_put(skb, 1);
+ *dptr++ = X25_CALL_REQUEST;
+ len = x25_addr_aton(addresses, &sk->protinfo.x25->dest_addr, &sk->protinfo.x25->source_addr);
+ dptr = skb_put(skb, len);
+ memcpy(dptr, addresses, len);
+ len = x25_create_facilities(facilities, &sk->protinfo.x25->facilities, sk->protinfo.x25->neighbour->global_facil_mask);
+ dptr = skb_put(skb, len);
+ memcpy(dptr, facilities, len);
+ dptr = skb_put(skb, sk->protinfo.x25->calluserdata.cudlength);
+ memcpy(dptr, sk->protinfo.x25->calluserdata.cuddata, sk->protinfo.x25->calluserdata.cudlength);
+ sk->protinfo.x25->calluserdata.cudlength = 0;
+ break;
+
+ case X25_CALL_ACCEPTED:
+ dptr = skb_put(skb, 2);
+ *dptr++ = X25_CALL_ACCEPTED;
+ *dptr++ = 0x00; /* Address lengths */
+ len = x25_create_facilities(facilities, &sk->protinfo.x25->facilities, sk->protinfo.x25->vc_facil_mask);
+ dptr = skb_put(skb, len);
+ memcpy(dptr, facilities, len);
+ dptr = skb_put(skb, sk->protinfo.x25->calluserdata.cudlength);
+ memcpy(dptr, sk->protinfo.x25->calluserdata.cuddata, sk->protinfo.x25->calluserdata.cudlength);
+ sk->protinfo.x25->calluserdata.cudlength = 0;
+ break;
+
+ case X25_CLEAR_REQUEST:
+ case X25_RESET_REQUEST:
+ dptr = skb_put(skb, 3);
+ *dptr++ = frametype;
+ *dptr++ = 0x00; /* XXX */
+ *dptr++ = 0x00; /* XXX */
+ break;
+
+ case X25_RR:
+ case X25_RNR:
+ case X25_REJ:
+ if (sk->protinfo.x25->neighbour->extended) {
+ dptr = skb_put(skb, 2);
+ *dptr++ = frametype;
+ *dptr++ = (sk->protinfo.x25->vr << 1) & 0xFE;
+ } else {
+ dptr = skb_put(skb, 1);
+ *dptr = frametype;
+ *dptr++ |= (sk->protinfo.x25->vr << 5) & 0xE0;
+ }
+ break;
+
+ case X25_CLEAR_CONFIRMATION:
+ case X25_INTERRUPT_CONFIRMATION:
+ case X25_RESET_CONFIRMATION:
+ dptr = skb_put(skb, 1);
+ *dptr = frametype;
+ break;
+ }
+
+ x25_transmit_link(skb, sk->protinfo.x25->neighbour);
+}
+
+/*
+ * Unpick the contents of the passed X.25 Packet Layer frame.
+ */
+int x25_decode(struct sock *sk, struct sk_buff *skb, int *ns, int *nr, int *q,
+ int *d, int *m)
+{
+ unsigned char *frame = skb->data;
+
+ *ns = *nr = *q = *d = *m = 0;
+
+ switch (frame[2]) {
+ case X25_CALL_REQUEST:
+ case X25_CALL_ACCEPTED:
+ case X25_CLEAR_REQUEST:
+ case X25_CLEAR_CONFIRMATION:
+ case X25_INTERRUPT:
+ case X25_INTERRUPT_CONFIRMATION:
+ case X25_RESET_REQUEST:
+ case X25_RESET_CONFIRMATION:
+ case X25_RESTART_REQUEST:
+ case X25_RESTART_CONFIRMATION:
+ case X25_REGISTRATION_REQUEST:
+ case X25_REGISTRATION_CONFIRMATION:
+ case X25_DIAGNOSTIC:
+ return frame[2];
+ }
+
+ if (sk->protinfo.x25->neighbour->extended) {
+ if (frame[2] == X25_RR ||
+ frame[2] == X25_RNR ||
+ frame[2] == X25_REJ) {
+ *nr = (frame[3] >> 1) & 0x7F;
+ return frame[2];
+ }
+ } else {
+ if ((frame[2] & 0x1F) == X25_RR ||
+ (frame[2] & 0x1F) == X25_RNR ||
+ (frame[2] & 0x1F) == X25_REJ) {
+ *nr = (frame[2] >> 5) & 0x07;
+ return frame[2] & 0x1F;
+ }
+ }
+
+ if (sk->protinfo.x25->neighbour->extended) {
+ if ((frame[2] & 0x01) == X25_DATA) {
+ *q = (frame[0] & X25_Q_BIT) == X25_Q_BIT;
+ *d = (frame[0] & X25_D_BIT) == X25_D_BIT;
+ *m = (frame[3] & X25_EXT_M_BIT) == X25_EXT_M_BIT;
+ *nr = (frame[3] >> 1) & 0x7F;
+ *ns = (frame[2] >> 1) & 0x7F;
+ return X25_DATA;
+ }
+ } else {
+ if ((frame[2] & 0x01) == X25_DATA) {
+ *q = (frame[0] & X25_Q_BIT) == X25_Q_BIT;
+ *d = (frame[0] & X25_D_BIT) == X25_D_BIT;
+ *m = (frame[2] & X25_STD_M_BIT) == X25_STD_M_BIT;
+ *nr = (frame[2] >> 5) & 0x07;
+ *ns = (frame[2] >> 1) & 0x07;
+ return X25_DATA;
+ }
+ }
+
+ printk(KERN_DEBUG "X.25: invalid PLP frame %02X %02X %02X\n",
+ frame[0], frame[1], frame[2]);
+
+ return X25_ILLEGAL;
+}
+
+void x25_disconnect(struct sock *sk, int reason, unsigned char cause,
+ unsigned char diagnostic)
+{
+ x25_clear_queues(sk);
+ x25_stop_timer(sk);
+
+ sk->protinfo.x25->lci = 0;
+ sk->protinfo.x25->state = X25_STATE_0;
+
+ sk->protinfo.x25->causediag.cause = cause;
+ sk->protinfo.x25->causediag.diagnostic = diagnostic;
+
+ sk->state = TCP_CLOSE;
+ sk->err = reason;
+ sk->shutdown |= SEND_SHUTDOWN;
+
+ if (!sk->dead)
+ sk->state_change(sk);
+
+ sk->dead = 1;
+}
+
+/*
+ * Clear an own-rx-busy condition and tell the peer about this, provided
+ * that there is a significant amount of free receive buffer space available.
+ */
+void x25_check_rbuf(struct sock *sk)
+{
+ if (atomic_read(&sk->rmem_alloc) < (sk->rcvbuf / 2) &&
+ (sk->protinfo.x25->condition & X25_COND_OWN_RX_BUSY)) {
+ sk->protinfo.x25->condition &= ~X25_COND_OWN_RX_BUSY;
+ sk->protinfo.x25->condition &= ~X25_COND_ACK_PENDING;
+ sk->protinfo.x25->vl = sk->protinfo.x25->vr;
+ x25_write_internal(sk, X25_RR);
+ x25_stop_timer(sk);
+ }
+}
diff --git a/uClinux-2.4.31-uc0/net/x25/x25_timer.c b/uClinux-2.4.31-uc0/net/x25/x25_timer.c
new file mode 100644
index 0000000..cf9b426
--- /dev/null
+++ b/uClinux-2.4.31-uc0/net/x25/x25_timer.c
@@ -0,0 +1,192 @@
+/*
+ * X.25 Packet Layer release 002
+ *
+ * This is ALPHA test software. This code may break your machine, randomly fail to work with new
+ * releases, misbehave and/or generally screw up. It might even work.
+ *
+ * This code REQUIRES 2.1.15 or higher
+ *
+ * This module:
+ * This module is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ *
+ * History
+ * X.25 001 Jonathan Naylor Started coding.
+ * X.25 002 Jonathan Naylor New timer architecture.
+ * Centralised disconnection processing.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/in.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/timer.h>
+#include <linux/string.h>
+#include <linux/sockios.h>
+#include <linux/net.h>
+#include <linux/inet.h>
+#include <linux/netdevice.h>
+#include <linux/skbuff.h>
+#include <net/sock.h>
+#include <asm/segment.h>
+#include <asm/system.h>
+#include <linux/fcntl.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <net/x25.h>
+
+static void x25_heartbeat_expiry(unsigned long);
+static void x25_timer_expiry(unsigned long);
+
+void x25_start_heartbeat(struct sock *sk)
+{
+ del_timer(&sk->timer);
+
+ sk->timer.data = (unsigned long)sk;
+ sk->timer.function = &x25_heartbeat_expiry;
+ sk->timer.expires = jiffies + 5 * HZ;
+
+ add_timer(&sk->timer);
+}
+
+void x25_stop_heartbeat(struct sock *sk)
+{
+ del_timer(&sk->timer);
+}
+
+void x25_start_t2timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.x25->timer);
+
+ sk->protinfo.x25->timer.data = (unsigned long)sk;
+ sk->protinfo.x25->timer.function = &x25_timer_expiry;
+ sk->protinfo.x25->timer.expires = jiffies + sk->protinfo.x25->t2;
+
+ add_timer(&sk->protinfo.x25->timer);
+}
+
+void x25_start_t21timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.x25->timer);
+
+ sk->protinfo.x25->timer.data = (unsigned long)sk;
+ sk->protinfo.x25->timer.function = &x25_timer_expiry;
+ sk->protinfo.x25->timer.expires = jiffies + sk->protinfo.x25->t21;
+
+ add_timer(&sk->protinfo.x25->timer);
+}
+
+void x25_start_t22timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.x25->timer);
+
+ sk->protinfo.x25->timer.data = (unsigned long)sk;
+ sk->protinfo.x25->timer.function = &x25_timer_expiry;
+ sk->protinfo.x25->timer.expires = jiffies + sk->protinfo.x25->t22;
+
+ add_timer(&sk->protinfo.x25->timer);
+}
+
+void x25_start_t23timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.x25->timer);
+
+ sk->protinfo.x25->timer.data = (unsigned long)sk;
+ sk->protinfo.x25->timer.function = &x25_timer_expiry;
+ sk->protinfo.x25->timer.expires = jiffies + sk->protinfo.x25->t23;
+
+ add_timer(&sk->protinfo.x25->timer);
+}
+
+void x25_stop_timer(struct sock *sk)
+{
+ del_timer(&sk->protinfo.x25->timer);
+}
+
+unsigned long x25_display_timer(struct sock *sk)
+{
+ if (!timer_pending(&sk->protinfo.x25->timer))
+ return 0;
+
+ return sk->protinfo.x25->timer.expires - jiffies;
+}
+
+static void x25_heartbeat_expiry(unsigned long param)
+{
+ struct sock *sk = (struct sock *)param;
+
+ bh_lock_sock(sk);
+ if (sk->lock.users) { /* can currently only occur in state 3 */
+ goto restart_heartbeat;
+ }
+
+ switch (sk->protinfo.x25->state) {
+
+ case X25_STATE_0:
+ /* Magic here: If we listen() and a new link dies before it
+ is accepted() it isn't 'dead' so doesn't get removed. */
+ if (sk->destroy || (sk->state == TCP_LISTEN && sk->dead)) {
+ x25_destroy_socket(sk);
+ goto unlock;
+ }
+ break;
+
+ case X25_STATE_3:
+ /*
+ * Check for the state of the receive buffer.
+ */
+ x25_check_rbuf(sk);
+ break;
+ }
+ restart_heartbeat:
+ x25_start_heartbeat(sk);
+ unlock:
+ bh_unlock_sock(sk);
+}
+
+/*
+ * Timer has expired, it may have been T2, T21, T22, or T23. We can tell
+ * by the state machine state.
+ */
+static inline void x25_do_timer_expiry(struct sock * sk)
+{
+ switch (sk->protinfo.x25->state) {
+
+ case X25_STATE_3: /* T2 */
+ if (sk->protinfo.x25->condition & X25_COND_ACK_PENDING) {
+ sk->protinfo.x25->condition &= ~X25_COND_ACK_PENDING;
+ x25_enquiry_response(sk);
+ }
+ break;
+
+ case X25_STATE_1: /* T21 */
+ case X25_STATE_4: /* T22 */
+ x25_write_internal(sk, X25_CLEAR_REQUEST);
+ sk->protinfo.x25->state = X25_STATE_2;
+ x25_start_t23timer(sk);
+ break;
+
+ case X25_STATE_2: /* T23 */
+ x25_disconnect(sk, ETIMEDOUT, 0, 0);
+ break;
+ }
+}
+
+static void x25_timer_expiry(unsigned long param)
+{
+ struct sock *sk = (struct sock *)param;
+
+ bh_lock_sock(sk);
+ if (sk->lock.users) { /* can currently only occur in state 3 */
+ if (sk->protinfo.x25->state == X25_STATE_3) {
+ x25_start_t2timer(sk);
+ }
+ } else {
+ x25_do_timer_expiry(sk);
+ }
+ bh_unlock_sock(sk);
+}